/**
 * @file hv_drv_UsbMusbHsDma.c
 * @brief This is musb controller hs dma driver.
 * @details 
 * @author HiView SoC Software Team
 * @version 0.0.1
 * @date 20230417
 * @copyright Copyright(c),2022-9, Hiview Software. All rights reserved.
 * @par History:
 * <table>
 * <tr><th>Author                         <th>Date             <th>Details
 * <tr><td>HiView SoC Software Team   <td>20230417       <td>Create and first Version.
 * </table>
 **/

#include "hv_drv_UsbMusbCore.h"
#include "hv_drv_UsbMusbHsDma.h"
#include "hv_drv_UsbMusbDma.h"
#include "hv_drv_UsbMusbHost.h"
#include "hv_drv_UsbCoreUtils.h"

static int dma_controller_start(struct dma_controller *c) {
    /* nothing to do */
    return 0;
}

static void dma_channel_release(struct dma_channel *channel);

static int dma_controller_stop(struct dma_controller *c) {
    struct musb_dma_controller *controller = container_of(c,
            struct musb_dma_controller, controller);
    struct dma_channel *channel;
    u8 bit;

    if (controller->used_channels != 0) {
        for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
            if (controller->used_channels & (1 << bit)) {
                channel = &controller->channel[bit].channel;
                dma_channel_release(channel);

                if (!controller->used_channels)
                    break;
            }
        }
    }

    return 0;
}


static struct dma_channel *dma_channel_allocate(struct dma_controller *c,
        struct musb_hw_ep *hw_ep, u8 transmit) {
    struct musb_dma_controller *controller = container_of(c, struct musb_dma_controller, controller);
    struct musb_dma_channel *musb_channel = NULL;
    struct dma_channel *channel = NULL;
    u8 bit;

    for (bit = 0; bit < MUSB_HSDMA_CHANNELS; bit++) {
        if (!(controller->used_channels & (1 << bit))) {
            controller->used_channels |= (1 << bit);
            musb_channel = &(controller->channel[bit]);
            musb_channel->controller = controller;
            musb_channel->idx = bit;
            musb_channel->epnum = hw_ep->epnum;
            musb_channel->transmit = transmit;
            channel = &(musb_channel->channel);
            channel->private_data = musb_channel;
            channel->status = MUSB_DMA_STATUS_FREE;
            channel->max_len = 0x10000;
            /* Tx => mode 1; Rx => mode 0 */
            channel->desired_mode = transmit;
            channel->actual_len = 0;

            break;
        }
    }
    return channel;
}

static void dma_channel_release(struct dma_channel *channel) {
    struct musb_dma_channel *musb_channel = channel->private_data;

    channel->actual_len = 0;
    musb_channel->start_addr = 0;
    musb_channel->len = 0;

    musb_channel->controller->used_channels &= ~(1 << musb_channel->idx);

    channel->status = MUSB_DMA_STATUS_UNKNOWN;
}

static void configure_channel(struct dma_channel *channel,
                              u16 packet_sz, u8 mode,
                              dma_addr_t dma_addr, u32 len) {
    struct musb_dma_channel *musb_channel = channel->private_data;
    u8 bchannel = musb_channel->idx;
    u16 csr = 0;

    if (mode) {
        csr |= 1 << MUSB_HSDMA_MODE1_SHIFT;
    }

    csr |= MUSB_HSDMA_BURSTMODE_UNSPEC<< MUSB_HSDMA_BURSTMODE_SHIFT;

    csr |= (musb_channel->epnum << MUSB_HSDMA_ENDPOINT_SHIFT)
           | (1 << MUSB_HSDMA_ENABLE_SHIFT)
           | (1 << MUSB_HSDMA_IRQENABLE_SHIFT)
           | (musb_channel->transmit
              ? (1 << MUSB_HSDMA_TRANSMIT_SHIFT)
              : 0);

    /* address/count */
    musb_write_hsdma_addr(bchannel, dma_addr);
    musb_write_hsdma_count(bchannel, len);

    /* control (this should start things) */
    musb_writew(MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL), csr);
}

static int dma_channel_program(struct dma_channel *channel,
                               u16 packet_sz, u8 mode,
                               dma_addr_t dma_addr, u32 len) {
    struct musb_dma_channel *musb_channel = channel->private_data;

    channel->actual_len = 0;
    musb_channel->start_addr = dma_addr;
    musb_channel->len = len;
    musb_channel->max_packet_sz = packet_sz;
    channel->status = MUSB_DMA_STATUS_BUSY;

    if ((mode == 1) && (len >= packet_sz))
        configure_channel(channel, packet_sz, 1, dma_addr, len);
    else
        configure_channel(channel, packet_sz, 0, dma_addr, len);

    return true;
}

static int dma_channel_abort(struct dma_channel *channel) {
    struct musb_dma_channel *musb_channel = channel->private_data;

    u8 bchannel = musb_channel->idx;
    int offset;
    u16 csr;

    if (channel->status == MUSB_DMA_STATUS_BUSY) {
        if (musb_channel->transmit) {
            offset = MUSB_EP_OFFSET(musb_channel->epnum,
                                    MUSB_TXCSR);

            /*
            * The programming guide says that we must clear
            * the DMAENAB bit before the DMAMODE bit...
            */
            csr = musb_readw(offset);
            csr &= ~(MUSB_TXCSR_AUTOSET | MUSB_TXCSR_DMAENAB);
            musb_writew(offset, csr);
            csr &= ~MUSB_TXCSR_DMAMODE;
            musb_writew(offset, csr);
        } else {
            offset = MUSB_EP_OFFSET(musb_channel->epnum,
                                    MUSB_RXCSR);

            csr = musb_readw(offset);

            csr &= ~(MUSB_RXCSR_AUTOCLEAR | MUSB_RXCSR_H_AUTOREQ | MUSB_RXCSR_DMAENAB);
            musb_writew_hi(offset, csr);
            csr &= ~MUSB_RXCSR_DMAMODE;
            musb_writew_hi(offset, csr);

            musb_writew_lo(offset, (csr & MUSB_RXCSR_RXPKTRDY)? MUSB_RXCSR_FLUSHFIFO : 0);
        }

        musb_writew(MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL), 0);
        musb_write_hsdma_addr(bchannel, 0);
        musb_write_hsdma_count(bchannel, 0);
        channel->status = MUSB_DMA_STATUS_FREE;
    }

    return 0;
}

void dma_controller_irq(struct musb *musb, u8 int_hsdma) {
    struct musb_dma_controller *controller = musb->musb_dma_controller;
    struct musb_dma_channel *musb_channel;
    struct dma_channel *channel;
    u8 bchannel;
    u32 addr, count;
    u16 csr;

    HANDLE sc = spin_lock_irq();

    if (!int_hsdma) {
        for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
            musb_channel = (struct musb_dma_channel *)&(controller->channel[bchannel]);
            channel = &musb_channel->channel;

            if (channel->status == MUSB_DMA_STATUS_BUSY) {
                count = musb_read_hsdma_count(bchannel);

                if (count == 0)
                    int_hsdma |= (1 << bchannel);
            }
        }

        if (!int_hsdma)
            goto done;
    }

    for (bchannel = 0; bchannel < MUSB_HSDMA_CHANNELS; bchannel++) {
        if (int_hsdma & (1 << bchannel)) {
            musb_channel = (struct musb_dma_channel *)&(controller->channel[bchannel]);
            channel = &musb_channel->channel;

            csr = musb_readw(MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL));

            if (csr & (1 << MUSB_HSDMA_BUSERROR_SHIFT)) {
                musb_channel->channel.status = MUSB_DMA_STATUS_BUS_ABORT;
            } else {
                u8 devctl;

                addr = musb_read_hsdma_addr(bchannel);
                channel->actual_len = addr - musb_channel->start_addr;

                devctl = musb_readb(MUSB_DEVCTL);

                channel->status = MUSB_DMA_STATUS_FREE;

                /* completed */
                if ((devctl & MUSB_DEVCTL_HM) && (musb_channel->transmit)
                        && ((channel->desired_mode == 0) || (channel->actual_len &
                                (musb_channel->max_packet_sz - 1)))) {
                    u8  epnum  = musb_channel->epnum;
                    u16 txcsr;

                    /*
                    * The programming guide says that we
                    * must clear DMAENAB before DMAMODE.
                    */
                    musb_ep_select(epnum);
                    txcsr = musb_readw(MUSB_TXCSR);
                    txcsr &= ~(MUSB_TXCSR_DMAENAB |MUSB_TXCSR_AUTOSET);
                    musb_writew(MUSB_TXCSR, txcsr);
                    /* Send out the packet */
                    txcsr &= ~MUSB_TXCSR_DMAMODE;
                    txcsr |=  MUSB_TXCSR_TXPKTRDY;
                    musb_writew(MUSB_TXCSR, txcsr);

                }
                musb_dma_completion(musb, musb_channel->epnum, musb_channel->transmit);
            }
        }
    }

done:
    spin_unlock_irq(sc);
}

void dma_controller_destroy(struct dma_controller *c) {
    struct musb_dma_controller *controller = container_of(c, struct musb_dma_controller, controller);

    if (!controller)
        return;

    if(controller != NULL)
    {
        USB_FREE(controller);
        controller = NULL;
    }
}

void musb_dma_completion(struct musb *musb, u8 epnum, u8 transmit) {
    u8   devctl = musb_readb(MUSB_DEVCTL);

    if (!epnum) {
        if (!is_cppi_enabled()) {
            /* endpoint 0 */
            if (devctl & MUSB_DEVCTL_HM)
                musb_h_ep0_irq(musb);
        }
    } else {
        /* endpoints 1..15 */
        if (transmit) {
            if (devctl & MUSB_DEVCTL_HM) {
                musb_host_tx_dma(musb, epnum);
            }
        } else {
            /* receive */
            if (devctl & MUSB_DEVCTL_HM) {
                musb_host_rx_dma(musb, epnum);
            }
        }
    }
}


struct dma_controller *dma_controller_create(struct musb *musb) {
    struct musb_dma_controller *controller;

    controller = (struct musb_dma_controller *)USB_MALLOC(sizeof(*controller));

    if (!controller)
        return NULL;
    HV_MEMSET((void*)(controller), 0, sizeof(*controller));


    controller->channel_count = MUSB_HSDMA_CHANNELS;
    controller->private_data = musb;
    musb->musb_dma_controller = controller;

    controller->controller.start = dma_controller_start;
    controller->controller.stop = dma_controller_stop;
    controller->controller.channel_alloc = dma_channel_allocate;
    controller->controller.channel_release = dma_channel_release;
    controller->controller.channel_program = dma_channel_program;
    controller->controller.channel_abort = dma_channel_abort;

    return &controller->controller;
}
