/**
 * @file hv_drv_UsbMusbVirtHub.c
 * @brief This is musb virtual hub 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_UsbCh9.h"
#include "hv_drv_UsbCoreCh11.h"
#include "hv_drv_UsbMusbCore.h"
#include "hv_drv_UsbMusbHost.h"
#include "hv_drv_UsbMusbRegs.h"

#include "hv_drv_UsbCoreUtils.h"

int musb_hub_status_data(struct usb_hcd *hcd, char *buf) {
    struct musb *musb = hcd_to_musb(hcd);
    int retval = 0;

    if (musb->port1_status & 0xffff0000) {
        *buf = 0x02;
        retval = 1;
    }

    return retval;
}

static void musb_port_reset(struct musb *musb) {
    u8  power;
    u8 devctl;
    if (!is_host_active(musb)) {
        return;
    }

    power = musb_readb(MUSB_POWER);

    devctl = musb_readb(MUSB_DEVCTL);
    if (power & MUSB_POWER_RESUME) {
        /* TODO something */
        musb_writeb(MUSB_POWER, power & ~MUSB_POWER_RESUME);
        USB_MSleep(10);
    }

    power &= 0xf0;
    musb_writeb(MUSB_POWER, power | MUSB_POWER_RESET);
    musb->port1_status |= USB_PORT_STAT_RESET;
    musb->port1_status &= ~USB_PORT_STAT_ENABLE;

    USB_MSleep(50);

    musb_writeb(MUSB_POWER, power & ~MUSB_POWER_RESET);
    power = musb_readb(MUSB_POWER);

    if (power & MUSB_POWER_HSMODE) {
        musb->port1_status |= USB_PORT_STAT_HIGH_SPEED;
    }

    musb->port1_status &= ~USB_PORT_STAT_RESET;
    musb->port1_status |= USB_PORT_STAT_ENABLE
                          | (USB_PORT_STAT_C_RESET << 16)
                          | (USB_PORT_STAT_C_ENABLE << 16);
}

void musb_load_testpacket(struct musb *musb) {
    int i;
    static const u8 packets[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                          0x00, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA,
                          0xAA, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE,
                          0xEE, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
                          0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F, 0xBF, 0xDF,
                          0xEF, 0xF7, 0xFB, 0xFD, 0xFC, 0x7E, 0xBF, 0xDF,
                          0xEF, 0xF7, 0xFB, 0xFD, 0x7E
                         };
    u8 fifo_addr = 0x20;
    musb_ep_select(0);
    for(i = 0; i < sizeof(packets)/sizeof(packets[0]); i++) {
        musb_writeb(fifo_addr, packets[i]);
    }
}

int musb_hub_control(
    struct usb_hcd *hcd,
    u16     typeReq,
    u16     wValue,
    u16     wIndex,
    char    *buf,
    u16     wLength) {
    u8 devctl, conn;
    struct musb *musb = hcd_to_musb(hcd);
    u32     temp;
    int     retval = 0;
    HANDLE  sc;
    bool        start_musb = false;

    switch (typeReq) {
    case ClearHubFeature:
    case SetHubFeature:
        switch (wValue) {
        case C_HUB_OVER_CURRENT:
        case C_HUB_LOCAL_POWER:
            break;
        default:
            goto error;
        }
        break;
    case ClearPortFeature:
        if ((wIndex & 0xff) != 1)
            goto error;

        switch (wValue) {
        case USB_PORT_FEAT_ENABLE:
            break;
        case USB_PORT_FEAT_SUSPEND:
            break;
        case USB_PORT_FEAT_POWER:
            if (!hcd->self.is_b_host)   /* must */
                musb_platform_set_vbus(musb, 0);
            break;
        case USB_PORT_FEAT_C_CONNECTION:
        case USB_PORT_FEAT_C_ENABLE:
        case USB_PORT_FEAT_C_OVER_CURRENT:
        case USB_PORT_FEAT_C_RESET:
        case USB_PORT_FEAT_C_SUSPEND:
            break;
        default:
            goto error;
        }
        musb->port1_status &= ~(1 << wValue);
        break;
    case GetHubDescriptor: {
        struct usb_hub_descriptor *desc = (void *)buf;

        desc->bDescLength = 9;
        desc->bDescriptorType = 0x29;
        desc->bNbrPorts = 1;
        desc->wHubCharacteristics = (
                                        HUB_CHAR_INDV_PORT_LPSM /* per-port power switching */
                                        | HUB_CHAR_NO_OCPM  /* no overcurrent reporting */
                                    );
        desc->bPwrOn2PwrGood = 5;   /* msec/2 */
        desc->bHubContrCurrent = 0;

        /* workaround bogus struct definition */
        desc->u.hs.DeviceRemovable[0] = 0x02;   /* port 1 */
        desc->u.hs.DeviceRemovable[1] = 0xff;
    }
    break;
    case GetHubStatus:
        temp = 0;
        *(u32 *) buf = temp;
        break;
    case GetPortStatus:
        if (wIndex != 1)
            goto error;

        devctl = musb_readb(MUSB_DEVCTL);
        if (devctl & MUSB_DEVCTL_BDEVICE) {
            sc = spin_lock_irq();
            musb_recovery_host_mode();
            spin_unlock_irq(sc);
            devctl = musb_readb(MUSB_DEVCTL);
        }
        if (!(devctl & MUSB_DEVCTL_BDEVICE)) {
        }

        conn = ((devctl & MUSB_DEVCTL_HM ? 1 : 0) << 4)  /* current */
               | ((musb->port1_status & USB_PORT_STAT_CONNECTION) ? 1 : 0 );   /* previous */

        sc = spin_lock_irq();
        if (conn & 0x10) {  /* there is a device connected */
            musb->is_active = 1;
            musb->port1_status |= USB_PORT_STAT_CONNECTION;
        } else {
            musb->is_active = 0;
            musb->port1_status &= ~USB_PORT_STAT_CONNECTION;
        }

        if (((conn & 0x10) >> 4) ^ (conn & 0x01)) { /* status change */
            musb->port1_status &= ~(USB_PORT_STAT_LOW_SPEED
                                    | USB_PORT_STAT_HIGH_SPEED
                                    | USB_PORT_STAT_ENABLE);

            musb->port1_status |= (USB_PORT_STAT_C_CONNECTION << 16);
            if (!(conn & 0x10)) {
                musb->hcd->driver->repower_and_reset(musb->hcd);
            }
        }
        spin_unlock_irq(sc);

        *(u32 *) buf = musb->port1_status& ~MUSB_PORT_STAT_RESUME;

        /* port change status is more interesting */
        break;
    case SetPortFeature:
        if ((wIndex & 0xff) != 1)
            goto error;

        switch (wValue) {
        case USB_PORT_FEAT_POWER:
            /* NOTE: this controller has a strange state machine
             * that involves "requesting sessions" according to
             * magic side effects from incompletely-described
             * rules about startup...
             *
             * This call is what really starts the host mode; be
             * very careful about side effects if you reorder any
             * initialization logic, e.g. for OTG, or change any
             * logic relating to VBUS power-up.
             */
            if (!hcd->self.is_b_host) {/* && musb_has_gadget(musb) */
                sc = spin_lock_irq();
                start_musb = true;
                spin_unlock_irq(sc);
            }
            break;
        case USB_PORT_FEAT_RESET:
            musb_port_reset(musb);
            break;
        case USB_PORT_FEAT_SUSPEND:
            // TODO musb_port_suspend(musb, true);
            break;
        case USB_PORT_FEAT_TEST:
            if (unlikely(is_host_active(musb)))
                goto error;

            wIndex >>= 8;
            switch (wIndex) {
            case 1:
                temp = MUSB_TEST_J;
                break;
            case 2:
                temp = MUSB_TEST_K;
                break;
            case 3:
                temp = MUSB_TEST_SE0_NAK;
                break;
            case 4:
                temp = MUSB_TEST_PACKET;
                musb_load_testpacket(musb);
                break;
            case 5:
                temp = MUSB_TEST_FORCE_HOST
                       | MUSB_TEST_FORCE_HS;

                musb_writeb(MUSB_DEVCTL, MUSB_DEVCTL_SESSION);
                break;
            case 6:
                temp = MUSB_TEST_FIFO_ACCESS;
                break;
            default:
                goto error;
            }
            musb_writeb(MUSB_TESTMODE, temp);
            temp = musb_readb(MUSB_CSR0);
            temp |= 0x02;
            musb_writeb(MUSB_CSR0, temp);
            break;
        default:
            goto error;
        }
        musb->port1_status |= 1 << wValue;
        musb->port1_status &= ~USB_PORT_STAT_RESET;
        break;

    default:
error:
        /* "protocol stall" on error */
        retval = -EPIPE;
    }

    if (start_musb)
        musb_start(musb);

    return retval;
}

int musb_hub_port1_connect_state(void) {
    u8 devctl = musb_readb(MUSB_DEVCTL);
    if (devctl & MUSB_DEVCTL_BDEVICE) {
        HANDLE sc;
        sc = spin_lock_irq();
        musb_recovery_host_mode();
        spin_unlock_irq(sc);
        devctl = musb_readb(MUSB_DEVCTL);
    }

    return (devctl & MUSB_DEVCTL_HM) ? 1 : 0;
}
