/**
 * @file hv_drv_UsbMusbCore.c
 * @brief This is musb controller core 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_UsbCoreDevice.h"
#include "hv_drv_UsbMusbCore.h"
#include "hv_drv_UsbMusbRegs.h"
#include "hv_drv_UsbMusbHost.h"
#include "hv_drv_UsbMusbDma.h"
#include "hv_drv_UsbMusbHsDma.h"

#include "hv_chip_Irq.h"
#include "hv_drv_UsbCoreUtils.h"
#include "hv_drv_UsbComm.h"


struct musb __musb = {0};

void musb_recovery_host_mode(void) {
    u8 devctl = 0;

    devctl = musb_readb(MUSB_DEVCTL);
    devctl &= ~MUSB_DEVCTL_BDEVICE;
    devctl |= MUSB_DEVCTL_SESSION;
    musb_writeb(MUSB_DEVCTL, devctl);

    devctl = musb_readb(MUSB_DEVCTL);
    while (devctl & 0x80) {
        USB_LOGI("B-Device mode, reset controller:0x%02X\n", devctl);
        devctl &= ~MUSB_DEVCTL_BDEVICE;
        devctl |= MUSB_DEVCTL_SESSION;
        musb_writeb(MUSB_DEVCTL, devctl);
        USB_MSleep(50);
        devctl = musb_readb(MUSB_DEVCTL);
    }
}

void musb_reactive(void) {
    musb_writeb(MUSB_POWER, MUSB_POWER_ENSUSPEND | MUSB_POWER_HSENAB);
}

void musb_controller_suspend(boolean suspend) {
    u8 power = musb_readb(MUSB_POWER);

    if (suspend) {
        musb_writeb(MUSB_POWER, power | MUSB_POWER_ENSUSPEND);
        musb_writeb(MUSB_POWER, power | MUSB_POWER_SUSPENDM);
    } else if (power & MUSB_POWER_SUSPENDM) {
        musb_writeb(MUSB_POWER, power | MUSB_POWER_RESUME);
        USB_MSleep(12);
        musb_writeb(MUSB_POWER, power & ~MUSB_POWER_RESUME);
        musb_writeb(MUSB_POWER, power & ~MUSB_POWER_SUSPENDM);
    }
}

static int musb_stage0_irq(struct musb *musb, u8 int_usb, u8 devctl) {

    if (int_usb & MUSB_INTR_CONNECT) {
        Hv_Drv_USB_SetConnect(HV_TRUE);
        USB_LOGI("---plug-in");
    }

    if (int_usb & MUSB_INTR_DISCONNECT) {
        USB_LOGI("---plug-out");
        Hv_Drv_USB_SetConnect(HV_FALSE);
        musb->port1_status = USB_PORT_STAT_POWER
                             | (USB_PORT_STAT_C_CONNECTION << 16);

        musb->is_active = 0;
        Hv_Drv_USB_SendEventToUsbQueueFromISR(USB_EVENT_DETECT_PORT, 0);
    }

    return 0;
}

void musb_interrupt(struct musb *musb) {
    u8  devctl;
    u8  ep_num;
    u32 reg;

    if (musb->int_usb & 0xf7) {
        if ((musb->int_usb & 0x30) == 0x30  /* unexpected disconnect interrupt */
                && (musb->port1_status & USB_PORT_STAT_CONNECTION)) {
            musb->hcd->self.hc_needreset = 1;
        }
    }

    devctl = musb_readb(MUSB_DEVCTL);

    /* usb interrupt */
    if (musb->int_usb) {
        (void) musb_stage0_irq(musb, musb->int_usb, devctl);
    }

    /* dma interrupt */
    if (musb->int_hsdma) {
        (void) dma_controller_irq(musb, musb->int_hsdma);
    }

    /* ep0 interrupt. */
    if (musb->int_tx & 1) {
        if (devctl & MUSB_DEVCTL_HM)
           (void) musb_h_ep0_irq(musb);
          ;
    }

    reg = musb->int_rx >> 1;
    ep_num = 1;
    while (reg) {
        if (reg & 1) {
            if (devctl & MUSB_DEVCTL_HM) {
                musb_host_rx(musb, ep_num);
            }
        }

        reg >>= 1;
        ep_num++;
    }

    reg = musb->int_tx >> 1;
    ep_num = 1;
    while (reg) {
        if (reg & 1) {
            if (devctl & MUSB_DEVCTL_HM) {
                musb_host_tx(musb, ep_num);
            }
        }
        reg >>= 1;
        ep_num++;
    }
}

void musb_irq_handler(void *param) {
    HANDLE sc;
    struct musb *musb = &__musb;
    unsigned int csr;
    sc = spin_lock_irq();
    musb->int_usb = musb_readb(MUSB_INTRUSB);
    musb->int_tx = musb_readw(MUSB_INTRTX);      
    musb->int_rx = musb_readw(MUSB_INTRRX);
    musb->int_hsdma = musb_readb(MUSB_HSDMA_INTR);
    
    if (musb->int_usb || musb->int_tx || musb->int_rx || musb->int_hsdma) {
        musb_interrupt(musb);
    }

    spin_unlock_irq(sc);
}

static HV_VOS_ISR_RESULT_E HS_HAL_USB_IRQHandler(u32 irqNum, void *arg) {
    musb_irq_handler(arg);
    return HV_VOS_ISR_HANDLED;
}


void musb_interrput_switch(u8 enable) {

}

/* enable platform usb interrupt */
static void musb_platirq_enable(struct musb *musb) {
    int nIrq = HV_CHIP_IRQ_USB;
    
    Hv_Vos_AttachIsr(nIrq, HS_HAL_USB_IRQHandler, (void *)musb);
    Hv_Vos_UnmaskIrq(nIrq);
}

void musb_start(struct musb *musb) {
    u8 devctl = musb_readb(MUSB_DEVCTL);

    musb_writew(MUSB_INTRTXE, musb->epmask);
    musb_writew(MUSB_INTRRXE, musb->epmask & 0xfffe);
    musb_writeb(MUSB_INTRUSBE, MUSB_INTR_CONNECT | MUSB_INTR_DISCONNECT);
    musb_writeb(MUSB_TESTMODE, 0);

#if HV_CONFIG_USB_SPEED == USBSPEED_HIGH
    musb_writeb(MUSB_POWER, MUSB_POWER_ENSUSPEND | MUSB_POWER_HSENAB);
#else
    musb_writeb(MUSB_POWER, MUSB_POWER_ENSUSPEND);
#endif

    USB_MSleep(5);  
    devctl = musb_readb(MUSB_DEVCTL);
    devctl &= ~MUSB_DEVCTL_BDEVICE;

    /* assume ID pin is hard-wired to ground */
    devctl |= MUSB_DEVCTL_SESSION;

    musb_writeb(MUSB_DEVCTL, devctl);
    MUSB_HST_MODE(musb);
}


static int ep_config_from_hw(struct musb *musb) {
    u8 epnum = 0;
    struct musb_hw_ep *hw_ep;
    int ret = 0;

    /* fifosize: ep0: 64 ``` ep{1-4}:1024 ``` ep{5, 6}:512 ``` ep{7-15}:256 */
    for(epnum = 1; epnum < 16; epnum++) {
        musb_ep_select(epnum);
        hw_ep = musb->endpoints + epnum;

        ret = musb_read_fifosize(musb, hw_ep, epnum);

        if (ret < 0)
            break;

        hw_ep->tx_double_buffered = true;
        hw_ep->rx_double_buffered = true;
        if (hw_ep->max_packet_sz_tx < 512
                || hw_ep->max_packet_sz_rx < 512)
            continue;

        if (musb->bulk_ep)
            continue;

        musb->bulk_ep = hw_ep;
    }
     
    if (!musb->bulk_ep) {
        return -EINVAL;
    } else {
    }
    return 0;
}

int musb_read_fifosize(struct musb *musb, struct musb_hw_ep *hw_ep, u8 epnum) {
    u8 reg = 0;

    /* read from core using indexed model */
    reg = musb_readb(MUSB_FIFOSIZE);
    /* 0's returned when no more endpoints */

    if (!reg)
        return -ENODEV;

    musb->nr_endpoints++;
    musb->epmask |= (1 << epnum);

    hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f);

    /* shared TX/RX FIFO? */
    if ((reg & 0xf0) == 0xf0) {
        hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx;
        hw_ep->is_shared_fifo = true;
    } else {
        hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4);
        hw_ep->is_shared_fifo = false;
    }
    return 0;
}

/* Initialize MUSB (M)HDRC part of the USB hardware subsystem;
 * configure endpoints, or take their config from silicon
 */
static int musb_core_init(struct musb *musb) {
    unsigned char reg;
    int status = 0;
    int i;

    reg = musb_read_configdata();

    if (reg & MUSB_CONFIGDATA_MPRXE) {
        musb->bulk_combine = true;
    }

    if (reg & MUSB_CONFIGDATA_MPTXE) {
        musb->bulk_split = true;
    }

    musb->hwvers = musb_read_hwvers();
    musb_configure_ep0(musb);

    musb->nr_endpoints = 1;
    musb->epmask = 1;

    status = ep_config_from_hw(musb);

    if (status < 0)
        return status;

    for (i = 0; i < musb->nr_endpoints; i++) {
        struct musb_hw_ep *hw_ep = musb->endpoints + i;

        hw_ep->fifo = (void*)musb->ops->fifo_offset(i);
        hw_ep->rx_reinit = 1;
        hw_ep->tx_reinit = 1;
        hw_ep->bundled = 0;
    }

    return 0;
}

static void musb_release(struct musb *musb) {
    if (musb->dma_controller) {
        struct dma_controller *c = musb->dma_controller;

        c->stop(c);
        dma_controller_destroy(c);
        musb->dma_controller = NULL;
    }

    HV_MEMSET(musb, 0, sizeof(struct musb));
}

/* disable musb interrupt by controller */
static void musb_generic_disable(void) {
    u16 temp;

    /* disable interrupts */
    musb_writeb(MUSB_INTRUSBE, 0);
    musb_writew(MUSB_INTRTXE, 0);
    musb_writew(MUSB_INTRRXE, 0);

    /* flush pending interrupts */
    temp = musb_readb(MUSB_INTRUSB);
    temp = musb_readw(MUSB_INTRTX);
    temp = musb_readw(MUSB_INTRRX);
    
    /* off */
    //musb_writeb(MUSB_DEVCTL, 0);
}

/* disable platform usb interrupt */
static void musb_platirq_disable(void) {
    int nIrq = HV_CHIP_IRQ_USB;
    
    Hv_Vos_DetachIsr(nIrq, HS_HAL_USB_IRQHandler);
    Hv_Vos_MaskIrq(nIrq);
}

/*
 * Make the HDRC stop (disable interrupts, etc.);
 * reversible by musb_active
 * called on gadget driver unregister
 * with controller locked, irqs blocked
 * acts as a NOP unless some role activated the hardware
 */
static void musb_deactive(void) {
    /* stop IRQs, timers, ... */
    musb_platirq_disable();
    musb_generic_disable();
}


void musb_stop(struct musb *musb) {
    musb_deactive();
}


static struct musb *allocate_instance(struct device *dev) {
    struct musb *musb;
    struct musb_hw_ep *ep;
    int epnum;
    int ret;

    musb = &__musb;

    if (is_dma_capable()) {
        musb->dma_mask = 1;
#ifdef MUSB_TX_DMA_USED
        musb->is_tx_dma_used = true;
#else
        musb->is_tx_dma_used = false;
#endif

#ifdef MUSB_RX_DMA_USED
        musb->is_rx_dma_used = true;
#else
        musb->is_rx_dma_used = false;
#endif
    }

    for (epnum = 0, ep = musb->endpoints;
            epnum < 16; ep++, epnum++) {
        ep->musb = musb;
        ep->epnum = epnum;
    }

    musb->controller = dev;

    ret = musb_host_alloc(musb);
    if (ret < 0) {
        goto err_free;
    }

    musb->is_active = 0;

    dev_set_drvdata(dev, musb);

    return musb;

err_free:
    HV_MEMSET(musb, 0, sizeof(*musb));
    return NULL;
}


static void musb_free(struct musb *musb) {
    musb_host_free(musb);
    musb_release(musb);
}

static int musb_init_controller(struct device *dev) {
    int status = 0;
    struct musb *musb = NULL;
    struct musb_hdrc_platform_data *plat = (struct musb_hdrc_platform_data *)dev_get_platdata(dev);

    if (!plat) {
        status = -ENODEV;
        goto failed2;
    }

    musb = allocate_instance(dev);
    if (!musb) {
        status = -ENOMEM;
        goto failed2;
    }

    musb->board_set_power = plat->set_power;
    musb->ops = plat->platform_ops;
    //musb->is_multipoint = 1;
    musb->mregs = (void*)MUSB_REGS_ADDR_BASE;

    musb->isr = musb_irq_handler;
    status = musb_platform_init(musb);
    if (status < 0) {
        goto failed1;
    }

    if (musb->dma_mask) {
        struct dma_controller *c;

        c = dma_controller_create(musb);
        musb->dma_controller = c;

        if (c) {
            (void) c->start(c);
        }
    } else {
        musb->dma_controller = NULL;
    }

    /* ideally this would be abstracted in platform setup */
    if (!musb->dma_controller) {
        musb->dma_mask = 0;
    }

    //musb_platirq_disable();
    musb_generic_disable();

    status = musb_core_init(musb);
    if (status < 0) {
        USB_LOGE("musb_core_init failed, ignored\n");
        //goto failed;
    }

    musb_platirq_enable(musb);

    status = musb_host_setup(musb);
    if (status < 0) {
        USB_LOGE("musb_host_setup failed, ignored\n");
        goto failed;
    }

    musb->is_active = 1;
    return 0;

failed:
    USB_LOGE("controller init failed\n");
    musb_deactive();
failed1:
    USB_LOGE("controller init failed1\n");
    musb_free(musb);
failed2:
    USB_LOGE("controller init failed2\n");
    return status;
}

static int musb_shutdown(struct platform_device *pdev) {
    struct musb *musb = dev_to_musb(&pdev->dev);
    musb_host_cleanup(musb);
    musb_platirq_disable();
    musb_generic_disable();

    musb_writeb(MUSB_DEVCTL, 0);

    return 0;
}

static int musb_remove(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    struct musb *musb = dev_to_musb(dev);

    musb_shutdown(pdev);
    musb_free(musb);
    return 0;
}

static int musb_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    return musb_init_controller(dev);
}

/* FORCE HOST MODE */
struct platform_driver musb_driver = {
    "musb-hdrc",    //name
    musb_probe,     //probe
    musb_remove,    //remove
    musb_shutdown,  //shutdown
};
