/*
 * Copyright (c) 2024, sakumisu
 *
 * SPDX-License-Identifier: Apache-2.0
 */
#include "usbh_core.h"
#include "usbh_rtl8152.h"

#undef USB_DBG_TAG
#define USB_DBG_TAG "rtl8152"
#include "usb_log.h"

#define DEV_FORMAT "/dev/rtl8152"

static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_rtl8152_rx_buffer[CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE];
static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_rtl8152_tx_buffer[CONFIG_USBHOST_RTL8152_ETH_MAX_TX_SIZE];
static USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_rtl8152_inttx_buffer[2];
USB_NOCACHE_RAM_SECTION USB_MEM_ALIGNX uint8_t g_rtl8152_buf[32];

static struct usbh_rtl8152 g_rtl8152_class;

#define RTL8152_REQ_GET_REGS 0x05
#define RTL8152_REQ_SET_REGS 0x05

#define BYTE_EN_DWORD      0xff
#define BYTE_EN_WORD       0x33
#define BYTE_EN_BYTE       0x11
#define BYTE_EN_SIX_BYTES  0x3f
#define BYTE_EN_START_MASK 0x0f
#define BYTE_EN_END_MASK   0xf0

#define MCU_TYPE_PLA 0x0100
#define MCU_TYPE_USB 0x0000

/* Define these values to match your device */
#define VENDOR_ID_REALTEK   0x0bda
#define VENDOR_ID_MICROSOFT 0x045e
#define VENDOR_ID_SAMSUNG   0x04e8
#define VENDOR_ID_LENOVO    0x17ef
#define VENDOR_ID_LINKSYS   0x13b1
#define VENDOR_ID_NVIDIA    0x0955
#define VENDOR_ID_TPLINK    0x2357
#define VENDOR_ID_DLINK     0x2001
#define VENDOR_ID_ASUS      0x0b05

#define R8152_PHY_ID 32

#define PLA_IDR              0xc000
#define PLA_RCR              0xc010
#define PLA_RCR1             0xc012
#define PLA_RMS              0xc016
#define PLA_RXFIFO_CTRL0     0xc0a0
#define PLA_RXFIFO_FULL      0xc0a2
#define PLA_RXFIFO_CTRL1     0xc0a4
#define PLA_RX_FIFO_FULL     0xc0a6
#define PLA_RXFIFO_CTRL2     0xc0a8
#define PLA_RX_FIFO_EMPTY    0xc0aa
#define PLA_DMY_REG0         0xc0b0
#define PLA_FMC              0xc0b4
#define PLA_CFG_WOL          0xc0b6
#define PLA_TEREDO_CFG       0xc0bc
#define PLA_TEREDO_WAKE_BASE 0xc0c4
#define PLA_MAR              0xcd00
#define PLA_BACKUP           0xd000
#define PLA_BDC_CR           0xd1a0
#define PLA_TEREDO_TIMER     0xd2cc
#define PLA_REALWOW_TIMER    0xd2e8
#define PLA_UPHY_TIMER       0xd388
#define PLA_SUSPEND_FLAG     0xd38a
#define PLA_INDICATE_FALG    0xd38c
#define PLA_MACDBG_PRE       0xd38c /* RTL_VER_04 only */
#define PLA_MACDBG_POST      0xd38e /* RTL_VER_04 only */
#define PLA_EXTRA_STATUS     0xd398
#define PLA_GPHY_CTRL        0xd3ae
#define PLA_POL_GPIO_CTRL    0xdc6a
#define PLA_EFUSE_DATA       0xdd00
#define PLA_EFUSE_CMD        0xdd02
#define PLA_LEDSEL           0xdd90
#define PLA_LED_FEATURE      0xdd92
#define PLA_PHYAR            0xde00
#define PLA_BOOT_CTRL        0xe004
#define PLA_LWAKE_CTRL_REG   0xe007
#define PLA_GPHY_INTR_IMR    0xe022
#define PLA_EEE_CR           0xe040
#define PLA_EEE_TXTWSYS      0xe04c
#define PLA_EEE_TXTWSYS_2P5G 0xe058
#define PLA_EEEP_CR          0xe080
#define PLA_MAC_PWR_CTRL     0xe0c0
#define PLA_MAC_PWR_CTRL2    0xe0ca
#define PLA_MAC_PWR_CTRL3    0xe0cc
#define PLA_MAC_PWR_CTRL4    0xe0ce
#define PLA_WDT6_CTRL        0xe428
#define PLA_TCR0             0xe610
#define PLA_TCR1             0xe612
#define PLA_MTPS             0xe615
#define PLA_TXFIFO_CTRL      0xe618
#define PLA_TXFIFO_FULL      0xe61a
#define PLA_RSTTALLY         0xe800
#define PLA_CR               0xe813
#define PLA_CRWECR           0xe81c
#define PLA_CONFIG12         0xe81e /* CONFIG1, CONFIG2 */
#define PLA_CONFIG34         0xe820 /* CONFIG3, CONFIG4 */
#define PLA_CONFIG5          0xe822
#define PLA_PHY_PWR          0xe84c
#define PLA_OOB_CTRL         0xe84f
#define PLA_CPCR             0xe854
#define PLA_MISC_0           0xe858
#define PLA_MISC_1           0xe85a
#define PLA_OCP_GPHY_BASE    0xe86c
#define PLA_TALLYCNT         0xe890
#define PLA_SFF_STS_7        0xe8de
#define PLA_PHYSTATUS        0xe908
#define PLA_CONFIG6          0xe90a /* CONFIG6 */
#define PLA_USB_CFG          0xe952
#define PLA_BP_BA            0xfc26
#define PLA_BP_0             0xfc28
#define PLA_BP_1             0xfc2a
#define PLA_BP_2             0xfc2c
#define PLA_BP_3             0xfc2e
#define PLA_BP_4             0xfc30
#define PLA_BP_5             0xfc32
#define PLA_BP_6             0xfc34
#define PLA_BP_7             0xfc36
#define PLA_BP_EN            0xfc38

#define USB_USB2PHY           0xb41e
#define USB_SSPHYLINK1        0xb426
#define USB_SSPHYLINK2        0xb428
#define USB_L1_CTRL           0xb45e
#define USB_U2P3_CTRL         0xb460
#define USB_CSR_DUMMY1        0xb464
#define USB_CSR_DUMMY2        0xb466
#define USB_DEV_STAT          0xb808
#define USB_CONNECT_TIMER     0xcbf8
#define USB_MSC_TIMER         0xcbfc
#define USB_BURST_SIZE        0xcfc0
#define USB_FW_FIX_EN0        0xcfca
#define USB_FW_FIX_EN1        0xcfcc
#define USB_LPM_CONFIG        0xcfd8
#define USB_ECM_OPTION        0xcfee
#define USB_CSTMR             0xcfef /* RTL8153A */
#define USB_MISC_2            0xcfff
#define USB_ECM_OP            0xd26b
#define USB_GPHY_CTRL         0xd284
#define USB_SPEED_OPTION      0xd32a
#define USB_FW_CTRL           0xd334 /* RTL8153B */
#define USB_FC_TIMER          0xd340
#define USB_USB_CTRL          0xd406
#define USB_PHY_CTRL          0xd408
#define USB_TX_AGG            0xd40a
#define USB_RX_BUF_TH         0xd40c
#define USB_USB_TIMER         0xd428
#define USB_RX_EARLY_TIMEOUT  0xd42c
#define USB_RX_EARLY_SIZE     0xd42e
#define USB_PM_CTRL_STATUS    0xd432 /* RTL8153A */
#define USB_RX_EXTRA_AGGR_TMR 0xd432 /* RTL8153B */
#define USB_TX_DMA            0xd434
#define USB_UPT_RXDMA_OWN     0xd437
#define USB_UPHY3_MDCMDIO     0xd480
#define USB_TOLERANCE         0xd490
#define USB_LPM_CTRL          0xd41a
#define USB_BMU_RESET         0xd4b0
#define USB_BMU_CONFIG        0xd4b4
#define USB_U1U2_TIMER        0xd4da
#define USB_FW_TASK           0xd4e8 /* RTL8153B */
#define USB_RX_AGGR_NUM       0xd4ee
#define USB_UPS_CTRL          0xd800
#define USB_POWER_CUT         0xd80a
#define USB_MISC_0            0xd81a
#define USB_MISC_1            0xd81f
#define USB_AFE_CTRL2         0xd824
#define USB_UPHY_XTAL         0xd826
#define USB_UPS_CFG           0xd842
#define USB_UPS_FLAGS         0xd848
#define USB_WDT1_CTRL         0xe404
#define USB_WDT11_CTRL        0xe43c
#define USB_BP_BA             PLA_BP_BA
#define USB_BP_0              PLA_BP_0
#define USB_BP_1              PLA_BP_1
#define USB_BP_2              PLA_BP_2
#define USB_BP_3              PLA_BP_3
#define USB_BP_4              PLA_BP_4
#define USB_BP_5              PLA_BP_5
#define USB_BP_6              PLA_BP_6
#define USB_BP_7              PLA_BP_7
#define USB_BP_EN             PLA_BP_EN /* RTL8153A */
#define USB_BP_8              0xfc38    /* RTL8153B */
#define USB_BP_9              0xfc3a
#define USB_BP_10             0xfc3c
#define USB_BP_11             0xfc3e
#define USB_BP_12             0xfc40
#define USB_BP_13             0xfc42
#define USB_BP_14             0xfc44
#define USB_BP_15             0xfc46
#define USB_BP2_EN            0xfc48

/* OCP Registers */
#define OCP_ALDPS_CONFIG   0x2010
#define OCP_EEE_CONFIG1    0x2080
#define OCP_EEE_CONFIG2    0x2092
#define OCP_EEE_CONFIG3    0x2094
#define OCP_BASE_MII       0xa400
#define OCP_EEE_AR         0xa41a
#define OCP_EEE_DATA       0xa41c
#define OCP_PHY_STATUS     0xa420
#define OCP_INTR_EN        0xa424
#define OCP_NCTL_CFG       0xa42c
#define OCP_POWER_CFG      0xa430
#define OCP_EEE_CFG        0xa432
#define OCP_SRAM_ADDR      0xa436
#define OCP_SRAM_DATA      0xa438
#define OCP_DOWN_SPEED     0xa442
#define OCP_EEE_ABLE       0xa5c4
#define OCP_EEE_ADV        0xa5d0
#define OCP_EEE_LPABLE     0xa5d2
#define OCP_10GBT_CTRL     0xa5d4
#define OCP_10GBT_STAT     0xa5d6
#define OCP_EEE_ADV2       0xa6d4
#define OCP_PHY_STATE      0xa708 /* nway state for 8153 */
#define OCP_PHY_PATCH_STAT 0xb800
#define OCP_PHY_PATCH_CMD  0xb820
#define OCP_PHY_LOCK       0xb82e
#define OCP_ADC_IOFFSET    0xbcfc
#define OCP_ADC_CFG        0xbc06
#define OCP_SYSCLK_CFG     0xc416

/* SRAM Register */
#define SRAM_GREEN_CFG   0x8011
#define SRAM_LPF_CFG     0x8012
#define SRAM_GPHY_FW_VER 0x801e
#define SRAM_10M_AMP1    0x8080
#define SRAM_10M_AMP2    0x8082
#define SRAM_IMPEDANCE   0x8084
#define SRAM_PHY_LOCK    0xb82e

/* PLA_RCR */
#define RCR_AAP      0x00000001
#define RCR_APM      0x00000002
#define RCR_AM       0x00000004
#define RCR_AB       0x00000008
#define RCR_ACPT_ALL (RCR_AAP | RCR_APM | RCR_AM | RCR_AB)
#define SLOT_EN      BIT(11)

/* PLA_RCR1 */
#define OUTER_VLAN BIT(7)
#define INNER_VLAN BIT(6)

/* PLA_RXFIFO_CTRL0 */
#define RXFIFO_THR1_NORMAL 0x00080002
#define RXFIFO_THR1_OOB    0x01800003

/* PLA_RXFIFO_FULL */
#define RXFIFO_FULL_MASK 0xfff

/* PLA_RXFIFO_CTRL1 */
#define RXFIFO_THR2_FULL   0x00000060
#define RXFIFO_THR2_HIGH   0x00000038
#define RXFIFO_THR2_OOB    0x0000004a
#define RXFIFO_THR2_NORMAL 0x00a0

/* PLA_RXFIFO_CTRL2 */
#define RXFIFO_THR3_FULL   0x00000078
#define RXFIFO_THR3_HIGH   0x00000048
#define RXFIFO_THR3_OOB    0x0000005a
#define RXFIFO_THR3_NORMAL 0x0110

/* PLA_TXFIFO_CTRL */
#define TXFIFO_THR_NORMAL  0x00400008
#define TXFIFO_THR_NORMAL2 0x01000008

/* PLA_DMY_REG0 */
#define ECM_ALDPS 0x0002

/* PLA_FMC */
#define FMC_FCR_MCU_EN 0x0001

/* PLA_EEEP_CR */
#define EEEP_CR_EEEP_TX 0x0002

/* PLA_WDT6_CTRL */
#define WDT6_SET_MODE 0x0010

/* PLA_TCR0 */
#define TCR0_TX_EMPTY  0x0800
#define TCR0_AUTO_FIFO 0x0080

/* PLA_TCR1 */
#define VERSION_MASK 0x7cf0
#define IFG_MASK     (BIT(3) | BIT(9) | BIT(8))
#define IFG_144NS    BIT(9)
#define IFG_96NS     (BIT(9) | BIT(8))

/* PLA_MTPS */
#define MTPS_JUMBO   (12 * 1024 / 64)
#define MTPS_DEFAULT (6 * 1024 / 64)

/* PLA_RSTTALLY */
#define TALLY_RESET 0x0001

/* PLA_CR */
#define CR_RST 0x10
#define CR_RE  0x08
#define CR_TE  0x04

/* PLA_CRWECR */
#define CRWECR_NORAML 0x00
#define CRWECR_CONFIG 0xc0

/* PLA_OOB_CTRL */
#define NOW_IS_OOB      0x80
#define TXFIFO_EMPTY    0x20
#define RXFIFO_EMPTY    0x10
#define LINK_LIST_READY 0x02
#define DIS_MCU_CLROOB  0x01
#define FIFO_EMPTY      (TXFIFO_EMPTY | RXFIFO_EMPTY)

/* PLA_MISC_1 */
#define RXDY_GATED_EN 0x0008

/* PLA_SFF_STS_7 */
#define RE_INIT_LL  0x8000
#define MCU_BORW_EN 0x4000

/* PLA_CPCR */
#define FLOW_CTRL_EN BIT(0)
#define CPCR_RX_VLAN 0x0040

/* PLA_CFG_WOL */
#define MAGIC_EN 0x0001

/* PLA_TEREDO_CFG */
#define TEREDO_SEL           0x8000
#define TEREDO_WAKE_MASK     0x7f00
#define TEREDO_RS_EVENT_MASK 0x00fe
#define OOB_TEREDO_EN        0x0001

/* PLA_BDC_CR */
#define ALDPS_PROXY_MODE 0x0001

/* PLA_EFUSE_CMD */
#define EFUSE_READ_CMD   BIT(15)
#define EFUSE_DATA_BIT16 BIT(7)

/* PLA_CONFIG34 */
#define LINK_ON_WAKE_EN  0x0010
#define LINK_OFF_WAKE_EN 0x0008

/* PLA_CONFIG6 */
#define LANWAKE_CLR_EN BIT(0)

/* PLA_USB_CFG */
#define EN_XG_LIP BIT(1)
#define EN_G_LIP  BIT(2)

/* PLA_CONFIG5 */
#define BWF_EN      0x0040
#define MWF_EN      0x0020
#define UWF_EN      0x0010
#define LAN_WAKE_EN 0x0002

/* PLA_LED_FEATURE */
#define LED_MODE_MASK 0x0700

/* PLA_PHY_PWR */
#define TX_10M_IDLE_EN 0x0080
#define PFM_PWM_SWITCH 0x0040
#define TEST_IO_OFF    BIT(4)

/* PLA_MAC_PWR_CTRL */
#define D3_CLK_GATED_EN    0x00004000
#define MCU_CLK_RATIO      0x07010f07
#define MCU_CLK_RATIO_MASK 0x0f0f0f0f
#define ALDPS_SPDWN_RATIO  0x0f87

/* PLA_MAC_PWR_CTRL2 */
#define EEE_SPDWN_RATIO      0x8007
#define MAC_CLK_SPDWN_EN     BIT(15)
#define EEE_SPDWN_RATIO_MASK 0xff

/* PLA_MAC_PWR_CTRL3 */
#define PLA_MCU_SPDWN_EN   BIT(14)
#define PKT_AVAIL_SPDWN_EN 0x0100
#define SUSPEND_SPDWN_EN   0x0004
#define U1U2_SPDWN_EN      0x0002
#define L1_SPDWN_EN        0x0001

/* PLA_MAC_PWR_CTRL4 */
#define PWRSAVE_SPDWN_EN 0x1000
#define RXDV_SPDWN_EN    0x0800
#define TX10MIDLE_EN     0x0100
#define IDLE_SPDWN_EN    BIT(6)
#define TP100_SPDWN_EN   0x0020
#define TP500_SPDWN_EN   0x0010
#define TP1000_SPDWN_EN  0x0008
#define EEE_SPDWN_EN     0x0001

/* PLA_GPHY_INTR_IMR */
#define GPHY_STS_MSK      0x0001
#define SPEED_DOWN_MSK    0x0002
#define SPDWN_RXDV_MSK    0x0004
#define SPDWN_LINKCHG_MSK 0x0008

/* PLA_PHYAR */
#define PHYAR_FLAG 0x80000000

/* PLA_EEE_CR */
#define EEE_RX_EN 0x0001
#define EEE_TX_EN 0x0002

/* PLA_BOOT_CTRL */
#define AUTOLOAD_DONE 0x0002

/* PLA_LWAKE_CTRL_REG */
#define LANWAKE_PIN BIT(7)

/* PLA_SUSPEND_FLAG */
#define LINK_CHG_EVENT BIT(0)

/* PLA_INDICATE_FALG */
#define UPCOMING_RUNTIME_D3 BIT(0)

/* PLA_MACDBG_PRE and PLA_MACDBG_POST */
#define DEBUG_OE    BIT(0)
#define DEBUG_LTSSM 0x0082

/* PLA_EXTRA_STATUS */
#define CUR_LINK_OK      BIT(15)
#define U3P3_CHECK_EN    BIT(7) /* RTL_VER_05 only */
#define LINK_CHANGE_FLAG BIT(8)
#define POLL_LINK_CHG    BIT(0)

/* PLA_GPHY_CTRL */
#define GPHY_FLASH BIT(1)

/* PLA_POL_GPIO_CTRL */
#define DACK_DET_EN    BIT(15)
#define POL_GPHY_PATCH BIT(4)

/* USB_USB2PHY */
#define USB2PHY_SUSPEND 0x0001
#define USB2PHY_L1      0x0002

/* USB_SSPHYLINK1 */
#define DELAY_PHY_PWR_CHG BIT(1)

/* USB_SSPHYLINK2 */
#define pwd_dn_scale_mask 0x3ffe
#define pwd_dn_scale(x)   ((x) << 1)

/* USB_CSR_DUMMY1 */
#define DYNAMIC_BURST 0x0001

/* USB_CSR_DUMMY2 */
#define EP4_FULL_FC 0x0001

/* USB_DEV_STAT */
#define STAT_SPEED_MASK 0x0006
#define STAT_SPEED_HIGH 0x0000
#define STAT_SPEED_FULL 0x0002

/* USB_FW_FIX_EN0 */
#define FW_FIX_SUSPEND BIT(14)

/* USB_FW_FIX_EN1 */
#define FW_IP_RESET_EN BIT(9)

/* USB_LPM_CONFIG */
#define LPM_U1U2_EN BIT(0)

/* USB_TX_AGG */
#define TX_AGG_MAX_THRESHOLD 0x03

/* USB_RX_BUF_TH */
#define RX_THR_SUPPER 0x0c350180
#define RX_THR_HIGH   0x7a120180
#define RX_THR_SLOW   0xffff0180
#define RX_THR_B      0x00010001

/* USB_TX_DMA */
#define TEST_MODE_DISABLE 0x00000001
#define TX_SIZE_ADJUST1   0x00000100

/* USB_BMU_RESET */
#define BMU_RESET_EP_IN  0x01
#define BMU_RESET_EP_OUT 0x02

/* USB_BMU_CONFIG */
#define ACT_ODMA BIT(1)

/* USB_UPT_RXDMA_OWN */
#define OWN_UPDATE BIT(0)
#define OWN_CLEAR  BIT(1)

/* USB_FW_TASK */
#define FC_PATCH_TASK BIT(1)

/* USB_RX_AGGR_NUM */
#define RX_AGGR_NUM_MASK 0x1ff

/* USB_UPS_CTRL */
#define POWER_CUT 0x0100

/* USB_PM_CTRL_STATUS */
#define RESUME_INDICATE 0x0001

/* USB_ECM_OPTION */
#define BYPASS_MAC_RESET BIT(5)

/* USB_CSTMR */
#define FORCE_SUPER BIT(0)

/* USB_MISC_2 */
#define UPS_FORCE_PWR_DOWN BIT(0)

/* USB_ECM_OP */
#define EN_ALL_SPEED BIT(0)

/* USB_GPHY_CTRL */
#define GPHY_PATCH_DONE BIT(2)
#define BYPASS_FLASH    BIT(5)
#define BACKUP_RESTRORE BIT(6)

/* USB_SPEED_OPTION */
#define RG_PWRDN_EN   BIT(8)
#define ALL_SPEED_OFF BIT(9)

/* USB_FW_CTRL */
#define FLOW_CTRL_PATCH_OPT BIT(1)
#define AUTO_SPEEDUP        BIT(3)
#define FLOW_CTRL_PATCH_2   BIT(8)

/* USB_FC_TIMER */
#define CTRL_TIMER_EN BIT(15)

/* USB_USB_CTRL */
#define CDC_ECM_EN     BIT(3)
#define RX_AGG_DISABLE 0x0010
#define RX_ZERO_EN     0x0080

/* USB_U2P3_CTRL */
#define U2P3_ENABLE 0x0001
#define RX_DETECT8  BIT(3)

/* USB_POWER_CUT */
#define PWR_EN      0x0001
#define PHASE2_EN   0x0008
#define UPS_EN      BIT(4)
#define USP_PREWAKE BIT(5)

/* USB_MISC_0 */
#define PCUT_STATUS 0x0001

/* USB_RX_EARLY_TIMEOUT */
#define COALESCE_SUPER 85000U
#define COALESCE_HIGH  250000U
#define COALESCE_SLOW  524280U

/* USB_WDT1_CTRL */
#define WTD1_EN BIT(0)

/* USB_WDT11_CTRL */
#define TIMER11_EN 0x0001

/* USB_LPM_CTRL */
/* bit 4 ~ 5: fifo empty boundary */
#define FIFO_EMPTY_1FB 0x30 /* 0x1fb * 64 = 32448 bytes */
/* bit 2 ~ 3: LMP timer */
#define LPM_TIMER_MASK  0x0c
#define LPM_TIMER_500MS 0x04 /* 500 ms */
#define LPM_TIMER_500US 0x0c /* 500 us */
#define ROK_EXIT_LPM    0x02

/* USB_AFE_CTRL2 */
#define SEN_VAL_MASK   0xf800
#define SEN_VAL_NORMAL 0xa000
#define SEL_RXIDLE     0x0100

/* USB_UPHY_XTAL */
#define OOBS_POLLING BIT(8)

/* USB_UPS_CFG */
#define SAW_CNT_1MS_MASK 0x0fff
#define MID_REVERSE      BIT(5) /* RTL8156A */

/* USB_UPS_FLAGS */
#define UPS_FLAGS_R_TUNE          BIT(0)
#define UPS_FLAGS_EN_10M_CKDIV    BIT(1)
#define UPS_FLAGS_250M_CKDIV      BIT(2)
#define UPS_FLAGS_EN_ALDPS        BIT(3)
#define UPS_FLAGS_CTAP_SHORT_DIS  BIT(4)
#define UPS_FLAGS_SPEED_MASK      (0xf << 16)
#define ups_flags_speed(x)        ((x) << 16)
#define UPS_FLAGS_EN_EEE          BIT(20)
#define UPS_FLAGS_EN_500M_EEE     BIT(21)
#define UPS_FLAGS_EN_EEE_CKDIV    BIT(22)
#define UPS_FLAGS_EEE_PLLOFF_100  BIT(23)
#define UPS_FLAGS_EEE_PLLOFF_GIGA BIT(24)
#define UPS_FLAGS_EEE_CMOD_LV_EN  BIT(25)
#define UPS_FLAGS_EN_GREEN        BIT(26)
#define UPS_FLAGS_EN_FLOW_CTR     BIT(27)

enum spd_duplex {
    NWAY_10M_HALF,
    NWAY_10M_FULL,
    NWAY_100M_HALF,
    NWAY_100M_FULL,
    NWAY_1000M_FULL,
    FORCE_10M_HALF,
    FORCE_10M_FULL,
    FORCE_100M_HALF,
    FORCE_100M_FULL,
    FORCE_1000M_FULL,
    NWAY_2500M_FULL,
};

/* OCP_ALDPS_CONFIG */
#define ENPWRSAVE  0x8000
#define ENPDNPS    0x0200
#define LINKENA    0x0100
#define DIS_SDSAVE 0x0010

/* OCP_PHY_STATUS */
#define PHY_STAT_MASK     0x0007
#define PHY_STAT_EXT_INIT 2
#define PHY_STAT_LAN_ON   3
#define PHY_STAT_PWRDN    5

/* OCP_INTR_EN */
#define INTR_SPEED_FORCE BIT(3)

/* OCP_NCTL_CFG */
#define PGA_RETURN_EN BIT(1)

/* OCP_POWER_CFG */
#define EEE_CLKDIV_EN 0x8000
#define EN_ALDPS      0x0004
#define EN_10M_PLLOFF 0x0001

/* OCP_EEE_CONFIG1 */
#define RG_TXLPI_MSK_HFDUP 0x8000
#define RG_MATCLR_EN       0x4000
#define EEE_10_CAP         0x2000
#define EEE_NWAY_EN        0x1000
#define TX_QUIET_EN        0x0200
#define RX_QUIET_EN        0x0100
#define sd_rise_time_mask  0x0070
#define sd_rise_time(x)    (MIN(x, 7) << 4) /* bit 4 ~ 6 */
#define RG_RXLPI_MSK_HFDUP 0x0008
#define SDFALLTIME         0x0007 /* bit 0 ~ 2 */

/* OCP_EEE_CONFIG2 */
#define RG_LPIHYS_NUM  0x7000 /* bit 12 ~ 15 */
#define RG_DACQUIET_EN 0x0400
#define RG_LDVQUIET_EN 0x0200
#define RG_CKRSEL      0x0020
#define RG_EEEPRG_EN   0x0010

/* OCP_EEE_CONFIG3 */
#define fast_snr_mask 0xff80
#define fast_snr(x)   (MIN(x, 0x1ff) << 7) /* bit 7 ~ 15 */
#define RG_LFS_SEL    0x0060               /* bit 6 ~ 5 */
#define MSK_PH        0x0006               /* bit 0 ~ 3 */

/* OCP_EEE_AR */
/* bit[15:14] function */
#define FUN_ADDR 0x0000
#define FUN_DATA 0x4000
/* bit[4:0] device addr */

/* OCP_EEE_CFG */
#define CTAP_SHORT_EN 0x0040
#define EEE10_EN      0x0010

/* OCP_DOWN_SPEED */
#define EN_EEE_CMODE  BIT(14)
#define EN_EEE_1000   BIT(13)
#define EN_EEE_100    BIT(12)
#define EN_10M_CLKDIV BIT(11)
#define EN_10M_BGOFF  0x0080

/* OCP_10GBT_CTRL */
#define RTL_ADV2_5G_F_R BIT(5) /* Advertise 2.5GBASE-T fast-retrain */

/* OCP_PHY_STATE */
#define TXDIS_STATE 0x01
#define ABD_STATE   0x02

/* OCP_PHY_PATCH_STAT */
#define PATCH_READY BIT(6)

/* OCP_PHY_PATCH_CMD */
#define PATCH_REQUEST BIT(4)

/* OCP_PHY_LOCK */
#define PATCH_LOCK BIT(0)

/* OCP_ADC_CFG */
#define CKADSEL_L 0x0100
#define ADC_EN    0x0080
#define EN_EMI_L  0x0040

/* OCP_SYSCLK_CFG */
#define sysclk_div_expo(x) (MIN(x, 5) << 8)
#define clk_div_expo(x)    (MIN(x, 5) << 4)

/* SRAM_GREEN_CFG */
#define GREEN_ETH_EN BIT(15)
#define R_TUNE_EN    BIT(11)

/* SRAM_LPF_CFG */
#define LPF_AUTO_TUNE 0x8000

/* SRAM_10M_AMP1 */
#define GDAC_IB_UPALL 0x0008

/* SRAM_10M_AMP2 */
#define AMP_DN 0x0200

/* SRAM_IMPEDANCE */
#define RX_DRIVING_MASK 0x6000

/* SRAM_PHY_LOCK */
#define PHY_PATCH_LOCK 0x0001

/* MAC PASSTHRU */
#define AD_MASK        0xfee0
#define BND_MASK       0x0004
#define BD_MASK        0x0001
#define EFUSE          0xcfdb
#define PASS_THRU_MASK 0x1

#define BP4_SUPER_ONLY 0x1578 /* RTL_VER_04 only */

enum rtl_register_content {
    _2500bps = BIT(10),
    _1250bps = BIT(9),
    _500bps = BIT(8),
    _tx_flow = BIT(6),
    _rx_flow = BIT(5),
    _1000bps = 0x10,
    _100bps = 0x08,
    _10bps = 0x04,
    LINK_STATUS = 0x02,
    FULL_DUP = 0x01,
};

#define is_speed_2500(_speed)   (((_speed) & (_2500bps | LINK_STATUS)) == (_2500bps | LINK_STATUS))
#define is_flow_control(_speed) (((_speed) & (_tx_flow | _rx_flow)) == (_tx_flow | _rx_flow))

#define RTL8152_MAX_TX 4
#define RTL8152_MAX_RX 10
#define INTBUFSIZE     2
#define TX_ALIGN       4
#define RX_ALIGN       8

#define RTL8152_RX_MAX_PENDING 4096
#define RTL8152_RXFG_HEADSZ    256

#define INTR_LINK 0x0004

#define VLAN_ETH_HLEN      18
#define ETH_FCS_LEN        4
#define VLAN_ETH_FRAME_LEN 1514

#define RTL8152_RMS         (VLAN_ETH_FRAME_LEN + ETH_FCS_LEN)
#define RTL8153_RMS         RTL8153_MAX_PACKET
#define mtu_to_size(m)      ((m) + VLAN_ETH_HLEN + ETH_FCS_LEN)
#define size_to_mtu(s)      ((s)-VLAN_ETH_HLEN - ETH_FCS_LEN)
#define rx_reserved_size(x) (mtu_to_size(x) + sizeof(struct rx_desc) + RX_ALIGN)

struct rx_desc {
    uint32_t opts1;
#define RD_CRC      BIT(15)
#define RX_LEN_MASK 0x7fff

    uint32_t opts2;
#define RD_UDP_CS  BIT(23)
#define RD_TCP_CS  BIT(22)
#define RD_IPV6_CS BIT(20)
#define RD_IPV4_CS BIT(19)

    uint32_t opts3;
#define IPF         BIT(23) /* IP checksum fail */
#define UDPF        BIT(22) /* UDP checksum fail */
#define TCPF        BIT(21) /* TCP checksum fail */
#define RX_VLAN_TAG BIT(16)

    uint32_t opts4;
    uint32_t opts5;
    uint32_t opts6;
};

struct tx_desc {
    uint32_t opts1;
#define TX_FS         BIT(31) /* First segment of a packet */
#define TX_LS         BIT(30) /* Final segment of a packet */
#define LGSEND        BIT(29)
#define GTSENDV4      BIT(28)
#define GTSENDV6      BIT(27)
#define GTTCPHO_SHIFT 18
#define GTTCPHO_MAX   0x7fU
#define TX_LEN_MAX    0x3ffffU

    uint32_t opts2;
#define UDP_CS      BIT(31) /* Calculate UDP/IP checksum */
#define TCP_CS      BIT(30) /* Calculate TCP/IP checksum */
#define IPV4_CS     BIT(29) /* Calculate IPv4 checksum */
#define IPV6_CS     BIT(28) /* Calculate IPv6 checksum */
#define MSS_SHIFT   17
#define MSS_MAX     0x7ffU
#define TCPHO_SHIFT 17
#define TCPHO_MAX   0x7ffU
#define TX_VLAN_TAG BIT(16)
};

enum rtl_version {
    RTL_VER_UNKNOWN = 0,
    RTL_VER_01,
    RTL_VER_02,
    RTL_VER_03,
    RTL_VER_04,
    RTL_VER_05,
    RTL_VER_06,
    RTL_VER_07,
    RTL_VER_08,
    RTL_VER_09,

    RTL_TEST_01,
    RTL_VER_10,
    RTL_VER_11,
    RTL_VER_12,
    RTL_VER_13,
    RTL_VER_14,
    RTL_VER_15,

    RTL_VER_MAX
};

/* mii.h */
/* Generic MII registers. */
#define MII_BMCR        0x00 /* Basic mode control register */
#define MII_BMSR        0x01 /* Basic mode status register  */
#define MII_PHYSID1     0x02 /* PHYS ID 1                   */
#define MII_PHYSID2     0x03 /* PHYS ID 2                   */
#define MII_ADVERTISE   0x04 /* Advertisement control reg   */
#define MII_LPA         0x05 /* Link partner ability reg    */
#define MII_EXPANSION   0x06 /* Expansion register          */
#define MII_CTRL1000    0x09 /* 1000BASE-T control          */
#define MII_STAT1000    0x0a /* 1000BASE-T status           */
#define MII_MMD_CTRL    0x0d /* MMD Access Control Register */
#define MII_MMD_DATA    0x0e /* MMD Access Data Register */
#define MII_ESTATUS     0x0f /* Extended Status             */
#define MII_DCOUNTER    0x12 /* Disconnect counter          */
#define MII_FCSCOUNTER  0x13 /* False carrier counter       */
#define MII_NWAYTEST    0x14 /* N-way auto-neg test reg     */
#define MII_RERRCOUNTER 0x15 /* Receive error counter       */
#define MII_SREVISION   0x16 /* Silicon revision            */
#define MII_RESV1       0x17 /* Reserved...                 */
#define MII_LBRERROR    0x18 /* Lpback, rx, bypass error    */
#define MII_PHYADDR     0x19 /* PHY address                 */
#define MII_RESV2       0x1a /* Reserved...                 */
#define MII_TPISTATUS   0x1b /* TPI status for 10mbps       */
#define MII_NCONFIG     0x1c /* Network interface config    */

/* Basic mode control register. */
#define BMCR_RESV      0x003f /* Unused...                   */
#define BMCR_SPEED1000 0x0040 /* MSB of Speed (1000)         */
#define BMCR_CTST      0x0080 /* Collision test              */
#define BMCR_FULLDPLX  0x0100 /* Full duplex                 */
#define BMCR_ANRESTART 0x0200 /* Auto negotiation restart    */
#define BMCR_ISOLATE   0x0400 /* Isolate data paths from MII */
#define BMCR_PDOWN     0x0800 /* Enable low power state      */
#define BMCR_ANENABLE  0x1000 /* Enable auto negotiation     */
#define BMCR_SPEED100  0x2000 /* Select 100Mbps              */
#define BMCR_LOOPBACK  0x4000 /* TXD loopback bits           */
#define BMCR_RESET     0x8000 /* Reset to default state      */
#define BMCR_SPEED10   0x0000 /* Select 10Mbps               */

/* Basic mode status register. */
#define BMSR_ERCAP        0x0001 /* Ext-reg capability          */
#define BMSR_JCD          0x0002 /* Jabber detected             */
#define BMSR_LSTATUS      0x0004 /* Link status                 */
#define BMSR_ANEGCAPABLE  0x0008 /* Able to do auto-negotiation */
#define BMSR_RFAULT       0x0010 /* Remote fault detected       */
#define BMSR_ANEGCOMPLETE 0x0020 /* Auto-negotiation complete   */
#define BMSR_RESV         0x00c0 /* Unused...                   */
#define BMSR_ESTATEN      0x0100 /* Extended Status in R15      */
#define BMSR_100HALF2     0x0200 /* Can do 100BASE-T2 HDX       */
#define BMSR_100FULL2     0x0400 /* Can do 100BASE-T2 FDX       */
#define BMSR_10HALF       0x0800 /* Can do 10mbps, half-duplex  */
#define BMSR_10FULL       0x1000 /* Can do 10mbps, full-duplex  */
#define BMSR_100HALF      0x2000 /* Can do 100mbps, half-duplex */
#define BMSR_100FULL      0x4000 /* Can do 100mbps, full-duplex */
#define BMSR_100BASE4     0x8000 /* Can do 100mbps, 4k packets  */

/* Advertisement control register. */
#define ADVERTISE_SLCT          0x001f /* Selector bits               */
#define ADVERTISE_CSMA          0x0001 /* Only selector supported     */
#define ADVERTISE_10HALF        0x0020 /* Try for 10mbps half-duplex  */
#define ADVERTISE_1000XFULL     0x0020 /* Try for 1000BASE-X full-duplex */
#define ADVERTISE_10FULL        0x0040 /* Try for 10mbps full-duplex  */
#define ADVERTISE_1000XHALF     0x0040 /* Try for 1000BASE-X half-duplex */
#define ADVERTISE_100HALF       0x0080 /* Try for 100mbps half-duplex */
#define ADVERTISE_1000XPAUSE    0x0080 /* Try for 1000BASE-X pause    */
#define ADVERTISE_100FULL       0x0100 /* Try for 100mbps full-duplex */
#define ADVERTISE_1000XPSE_ASYM 0x0100 /* Try for 1000BASE-X asym pause */
#define ADVERTISE_100BASE4      0x0200 /* Try for 100mbps 4k packets  */
#define ADVERTISE_PAUSE_CAP     0x0400 /* Try for pause               */
#define ADVERTISE_PAUSE_ASYM    0x0800 /* Try for asymetric pause     */
#define ADVERTISE_RESV          0x1000 /* Unused...                   */
#define ADVERTISE_RFAULT        0x2000 /* Say we can detect faults    */
#define ADVERTISE_LPACK         0x4000 /* Ack link partners response  */
#define ADVERTISE_NPAGE         0x8000 /* Next page bit               */

#define ADVERTISE_FULL (ADVERTISE_100FULL | ADVERTISE_10FULL | \
                        ADVERTISE_CSMA)
#define ADVERTISE_ALL (ADVERTISE_10HALF | ADVERTISE_10FULL | \
                       ADVERTISE_100HALF | ADVERTISE_100FULL)

/* 1000BASE-T Control register */
#define ADVERTISE_1000FULL    0x0200 /* Advertise 1000BASE-T full duplex */
#define ADVERTISE_1000HALF    0x0100 /* Advertise 1000BASE-T half duplex */
#define CTL1000_AS_MASTER     0x0800
#define CTL1000_ENABLE_MASTER 0x1000

/* ethtool.h */
/* The forced speed, 10Mb, 100Mb, gigabit. */
#define SPEED_10   10
#define SPEED_100  100
#define SPEED_1000 1000

#define SPEED_UNKNOWN -1

/* Duplex, half or full. */
#define DUPLEX_HALF    0x00
#define DUPLEX_FULL    0x01
#define DUPLEX_UNKNOWN 0xff

/* Enable or disable autonegotiation. */
#define AUTONEG_DISABLE 0x00
#define AUTONEG_ENABLE  0x01

static int usbh_rtl8152_read_regs(struct usbh_rtl8152 *rtl8152_class,
                                  uint16_t value,
                                  uint16_t index,
                                  uint16_t size,
                                  void *data)
{
    struct usb_setup_packet *setup;
    int ret;

    if (!rtl8152_class || !rtl8152_class->hport) {
        return -USB_ERR_INVAL;
    }
    setup = rtl8152_class->hport->setup;

    setup->bmRequestType = USB_REQUEST_DIR_IN | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
    setup->bRequest = RTL8152_REQ_GET_REGS;
    setup->wValue = value;
    setup->wIndex = index;
    setup->wLength = size;

    ret = usbh_control_transfer(rtl8152_class->hport, setup, g_rtl8152_buf);
    if (ret < 0) {
        return ret;
    }
    memcpy(data, g_rtl8152_buf, ret - 8);

    return ret;
}

static int usbh_rtl8152_write_regs(struct usbh_rtl8152 *rtl8152_class,
                                   uint16_t value,
                                   uint16_t index,
                                   uint16_t size,
                                   void *data)
{
    struct usb_setup_packet *setup;

    if (!rtl8152_class || !rtl8152_class->hport) {
        return -USB_ERR_INVAL;
    }
    setup = rtl8152_class->hport->setup;

    setup->bmRequestType = USB_REQUEST_DIR_OUT | USB_REQUEST_VENDOR | USB_REQUEST_RECIPIENT_DEVICE;
    setup->bRequest = RTL8152_REQ_SET_REGS;
    setup->wValue = value;
    setup->wIndex = index;
    setup->wLength = size;

    memcpy(g_rtl8152_buf, data, size);
    return usbh_control_transfer(rtl8152_class->hport, setup, g_rtl8152_buf);
}

static int generic_ocp_read(struct usbh_rtl8152 *tp, uint16_t index, uint16_t size,
                            void *data, uint16_t type)
{
    uint16_t limit = 64;
    int ret = 0;

    /* both size and indix must be 4 bytes align */
    if ((size & 3) || !size || (index & 3) || !data)
        return -USB_ERR_INVAL;

    if ((uint32_t)index + (uint32_t)size > 0xffff)
        return -USB_ERR_INVAL;

    while (size) {
        if (size > limit) {
            ret = usbh_rtl8152_read_regs(tp, index, type, limit, data);
            if (ret < 0)
                break;

            index += limit;
            data += limit;
            size -= limit;
        } else {
            ret = usbh_rtl8152_read_regs(tp, index, type, size, data);
            if (ret < 0)
                break;

            index += size;
            data += size;
            size = 0;
            break;
        }
    }

    return ret;
}

static int generic_ocp_write(struct usbh_rtl8152 *tp, uint16_t index, uint16_t byteen,
                             uint16_t size, void *data, uint16_t type)
{
    int ret;
    uint16_t byteen_start, byteen_end, byen;
    uint16_t limit = 512;

    /* both size and indix must be 4 bytes align */
    if ((size & 3) || !size || (index & 3) || !data)
        return -USB_ERR_INVAL;

    if ((uint32_t)index + (uint32_t)size > 0xffff)
        return -USB_ERR_INVAL;

    byteen_start = byteen & BYTE_EN_START_MASK;
    byteen_end = byteen & BYTE_EN_END_MASK;

    byen = byteen_start | (byteen_start << 4);

    /* Split the first DWORD if the byte_en is not 0xff */
    if (byen != BYTE_EN_DWORD) {
        ret = usbh_rtl8152_write_regs(tp, index, type | byen, 4, data);
        if (ret < 0)
            goto error1;

        index += 4;
        data += 4;
        size -= 4;
    }

    if (size) {
        byen = byteen_end | (byteen_end >> 4);

        /* Split the last DWORD if the byte_en is not 0xff */
        if (byen != BYTE_EN_DWORD)
            size -= 4;

        while (size) {
            if (size > limit) {
                ret = usbh_rtl8152_write_regs(tp, index,
                                              type | BYTE_EN_DWORD,
                                              limit, data);
                if (ret < 0)
                    goto error1;

                index += limit;
                data += limit;
                size -= limit;
            } else {
                ret = usbh_rtl8152_write_regs(tp, index,
                                              type | BYTE_EN_DWORD,
                                              size, data);
                if (ret < 0)
                    goto error1;

                index += size;
                data += size;
                size = 0;
                break;
            }
        }

        /* Set the last DWORD */
        if (byen != BYTE_EN_DWORD)
            ret = usbh_rtl8152_write_regs(tp, index, type | byen, 4, data);
    }

error1:

    return ret;
}

static inline int pla_ocp_read(struct usbh_rtl8152 *tp, uint16_t index, uint16_t size, void *data)
{
    return generic_ocp_read(tp, index, size, data, MCU_TYPE_PLA);
}

static inline int pla_ocp_write(struct usbh_rtl8152 *tp, uint16_t index, uint16_t byteen, uint16_t size, void *data)
{
    return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_PLA);
}

static inline int usb_ocp_write(struct usbh_rtl8152 *tp, uint16_t index, uint16_t byteen, uint16_t size, void *data)
{
    return generic_ocp_write(tp, index, byteen, size, data, MCU_TYPE_USB);
}

static uint32_t ocp_read_dword(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index)
{
    uint32_t data;

    generic_ocp_read(tp, index, sizeof(data), &data, type);

    return data;
}

static void ocp_write_dword(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index, uint32_t data)
{
    uint32_t tmp = data;

    generic_ocp_write(tp, index, BYTE_EN_DWORD, sizeof(tmp), &tmp, type);
}

static uint16_t ocp_read_word(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index)
{
    uint32_t data;
    uint32_t tmp;
    uint16_t byen = BYTE_EN_WORD;
    uint8_t shift = index & 2;

    index &= ~3;
    byen <<= shift;

    generic_ocp_read(tp, index, sizeof(tmp), &tmp, type | byen);

    data = tmp;
    data >>= (shift * 8);
    data &= 0xffff;

    return (uint16_t)data;
}

static void ocp_write_word(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index, uint32_t data)
{
    uint32_t mask = 0xffff;
    uint32_t tmp;
    uint16_t byen = BYTE_EN_WORD;
    uint8_t shift = index & 2;

    data &= mask;

    if (index & 2) {
        byen <<= shift;
        mask <<= (shift * 8);
        data <<= (shift * 8);
        index &= ~3;
    }

    tmp = data;

    generic_ocp_write(tp, index, byen, sizeof(tmp), &tmp, type);
}

static uint8_t ocp_read_byte(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index)
{
    uint32_t data;
    uint32_t tmp;
    uint8_t shift = index & 3;

    index &= ~3;

    generic_ocp_read(tp, index, sizeof(tmp), &tmp, type);

    data = tmp;
    data >>= (shift * 8);
    data &= 0xff;

    return (uint8_t)data;
}

static void ocp_write_byte(struct usbh_rtl8152 *tp, uint16_t type, uint16_t index, uint32_t data)
{
    uint32_t mask = 0xff;
    uint32_t tmp;
    uint16_t byen = BYTE_EN_BYTE;
    uint8_t shift = index & 3;

    data &= mask;

    if (index & 3) {
        byen <<= shift;
        mask <<= (shift * 8);
        data <<= (shift * 8);
        index &= ~3;
    }

    tmp = data;

    generic_ocp_write(tp, index, byen, sizeof(tmp), &tmp, type);
}

static uint16_t ocp_reg_read(struct usbh_rtl8152 *tp, uint16_t addr)
{
    uint16_t ocp_base, ocp_index;

    ocp_base = addr & 0xf000;
    if (ocp_base != tp->ocp_base) {
        ocp_write_word(tp, MCU_TYPE_PLA, PLA_OCP_GPHY_BASE, ocp_base);
        tp->ocp_base = ocp_base;
    }

    ocp_index = (addr & 0x0fff) | 0xb000;
    return ocp_read_word(tp, MCU_TYPE_PLA, ocp_index);
}

static void ocp_reg_write(struct usbh_rtl8152 *tp, uint16_t addr, uint16_t data)
{
    uint16_t ocp_base, ocp_index;

    ocp_base = addr & 0xf000;
    if (ocp_base != tp->ocp_base) {
        ocp_write_word(tp, MCU_TYPE_PLA, PLA_OCP_GPHY_BASE, ocp_base);
        tp->ocp_base = ocp_base;
    }

    ocp_index = (addr & 0x0fff) | 0xb000;
    ocp_write_word(tp, MCU_TYPE_PLA, ocp_index, data);
}

static inline void r8152_mdio_write(struct usbh_rtl8152 *tp, uint32_t reg_addr, uint32_t value)
{
    ocp_reg_write(tp, OCP_BASE_MII + reg_addr * 2, value);
}

static inline int r8152_mdio_read(struct usbh_rtl8152 *tp, uint32_t reg_addr)
{
    return ocp_reg_read(tp, OCP_BASE_MII + reg_addr * 2);
}

static uint8_t usbh_rtl8152_get_version(struct usbh_rtl8152 *rtl8152_class)
{
    uint8_t version;
    uint32_t temp;
    uint32_t ocp_data;

    usbh_rtl8152_read_regs(rtl8152_class, PLA_TCR0, MCU_TYPE_PLA, 4, &temp);

    ocp_data = (temp >> 16) & VERSION_MASK;
    switch (ocp_data) {
        case 0x4c00:
            version = RTL_VER_01;
            break;
        case 0x4c10:
            version = RTL_VER_02;
            break;
        case 0x5c00:
            version = RTL_VER_03;
            break;
        case 0x5c10:
            version = RTL_VER_04;
            break;
        case 0x5c20:
            version = RTL_VER_05;
            break;
        case 0x5c30:
            version = RTL_VER_06;
            break;
        case 0x4800:
            version = RTL_VER_07;
            break;
        case 0x6000:
            version = RTL_VER_08;
            break;
        case 0x6010:
            version = RTL_VER_09;
            break;
        case 0x7010:
            version = RTL_TEST_01;
            break;
        case 0x7020:
            version = RTL_VER_10;
            break;
        case 0x7030:
            version = RTL_VER_11;
            break;
        case 0x7400:
            version = RTL_VER_12;
            break;
        case 0x7410:
            version = RTL_VER_13;
            break;
        case 0x6400:
            version = RTL_VER_14;
            break;
        case 0x7420:
            version = RTL_VER_15;
            break;
        default:
            version = RTL_VER_UNKNOWN;
            break;
    }

    return version;
}

#define WAKE_PHY         (1 << 0)
#define WAKE_UCAST       (1 << 1)
#define WAKE_MCAST       (1 << 2)
#define WAKE_BCAST       (1 << 3)
#define WAKE_ARP         (1 << 4)
#define WAKE_MAGIC       (1 << 5)
#define WAKE_MAGICSECURE (1 << 6) /* only meaningful if WAKE_MAGIC */
#define WAKE_FILTER      (1 << 7)

#define WAKE_ANY (WAKE_PHY | WAKE_MAGIC | WAKE_UCAST | WAKE_BCAST | WAKE_MCAST)

static uint32_t __rtl_get_wol(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    uint32_t wolopts = 0;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG34);
    if (ocp_data & LINK_ON_WAKE_EN)
        wolopts |= WAKE_PHY;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CONFIG5);
    if (ocp_data & UWF_EN)
        wolopts |= WAKE_UCAST;
    if (ocp_data & BWF_EN)
        wolopts |= WAKE_BCAST;
    if (ocp_data & MWF_EN)
        wolopts |= WAKE_MCAST;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CFG_WOL);
    if (ocp_data & MAGIC_EN)
        wolopts |= WAKE_MAGIC;

    return wolopts;
}

static void r8152_aldps_en(struct usbh_rtl8152 *tp, bool enable)
{
    if (enable) {
        ocp_reg_write(tp, OCP_ALDPS_CONFIG, ENPWRSAVE | ENPDNPS | LINKENA | DIS_SDSAVE);
    } else {
        ocp_reg_write(tp, OCP_ALDPS_CONFIG, ENPDNPS | LINKENA | DIS_SDSAVE);
        usb_osal_msleep(20);
    }
}

static void r8152_power_cut_en(struct usbh_rtl8152 *tp, bool enable)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_UPS_CTRL);
    if (enable)
        ocp_data |= POWER_CUT;
    else
        ocp_data &= ~POWER_CUT;
    ocp_write_word(tp, MCU_TYPE_USB, USB_UPS_CTRL, ocp_data);

    ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_PM_CTRL_STATUS);
    ocp_data &= ~RESUME_INDICATE;
    ocp_write_word(tp, MCU_TYPE_USB, USB_PM_CTRL_STATUS, ocp_data);
}

static void rtl_tally_reset(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_RSTTALLY);
    ocp_data |= TALLY_RESET;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_RSTTALLY, ocp_data);
}

static void r8152b_reset_packet_filter(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_FMC);
    ocp_data &= ~FMC_FCR_MCU_EN;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data);
    ocp_data |= FMC_FCR_MCU_EN;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_FMC, ocp_data);
}

static inline void r8153b_rx_agg_chg_indicate(struct usbh_rtl8152 *tp)
{
    ocp_write_byte(tp, MCU_TYPE_USB, USB_UPT_RXDMA_OWN,
                   OWN_UPDATE | OWN_CLEAR);
}

static void rxdy_gated_en(struct usbh_rtl8152 *tp, bool enable)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_MISC_1);
    if (enable)
        ocp_data |= RXDY_GATED_EN;
    else
        ocp_data &= ~RXDY_GATED_EN;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_MISC_1, ocp_data);
}

static inline uint16_t rtl8152_get_speed(struct usbh_rtl8152 *tp)
{
    return ocp_read_word(tp, MCU_TYPE_PLA, PLA_PHYSTATUS);
}

static void rtl_eee_plus_en(struct usbh_rtl8152 *tp, bool enable)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR);
    if (enable)
        ocp_data |= EEEP_CR_EEEP_TX;
    else
        ocp_data &= ~EEEP_CR_EEEP_TX;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_EEEP_CR, ocp_data);
}

static void rtl_set_eee_plus(struct usbh_rtl8152 *tp)
{
    if (rtl8152_get_speed(tp) & _10bps)
        rtl_eee_plus_en(tp, true);
    else
        rtl_eee_plus_en(tp, false);
}

static void rtl8152_nic_reset(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    int i;

    switch (tp->version) {
        case RTL_TEST_01:
        case RTL_VER_10:
        case RTL_VER_11:
            ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR);
            ocp_data &= ~CR_TE;
            ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, ocp_data);

            ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_BMU_RESET);
            ocp_data &= ~BMU_RESET_EP_IN;
            ocp_write_word(tp, MCU_TYPE_USB, USB_BMU_RESET, ocp_data);

            ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL);
            ocp_data |= CDC_ECM_EN;
            ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data);

            ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR);
            ocp_data &= ~CR_RE;
            ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, ocp_data);

            ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_BMU_RESET);
            ocp_data |= BMU_RESET_EP_IN;
            ocp_write_word(tp, MCU_TYPE_USB, USB_BMU_RESET, ocp_data);

            ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL);
            ocp_data &= ~CDC_ECM_EN;
            ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data);
            break;

        default:
            ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, CR_RST);

            for (i = 0; i < 1000; i++) {
                if (!(ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR) & CR_RST))
                    break;
                usb_osal_msleep(400);
            }
            break;
    }
}

static void rtl_disable(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    int i;

    ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR);
    ocp_data &= ~RCR_ACPT_ALL;
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data);

    rxdy_gated_en(tp, true);

    for (i = 0; i < 1000; i++) {
        ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL);
        if ((ocp_data & FIFO_EMPTY) == FIFO_EMPTY)
            break;
        usb_osal_msleep(1);
    }

    for (i = 0; i < 1000; i++) {
        if (ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0) & TCR0_TX_EMPTY)
            break;
        usb_osal_msleep(1);
    }

    rtl8152_nic_reset(tp);
}

static void rtl_rx_vlan_en(struct usbh_rtl8152 *tp, bool enable)
{
    uint32_t ocp_data;

    switch (tp->version) {
        case RTL_VER_01:
        case RTL_VER_02:
        case RTL_VER_03:
        case RTL_VER_04:
        case RTL_VER_05:
        case RTL_VER_06:
        case RTL_VER_07:
        case RTL_VER_08:
        case RTL_VER_09:
        case RTL_VER_14:
            ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_CPCR);
            if (enable)
                ocp_data |= CPCR_RX_VLAN;
            else
                ocp_data &= ~CPCR_RX_VLAN;
            ocp_write_word(tp, MCU_TYPE_PLA, PLA_CPCR, ocp_data);
            break;

        case RTL_TEST_01:
        case RTL_VER_10:
        case RTL_VER_11:
        case RTL_VER_12:
        case RTL_VER_13:
        case RTL_VER_15:
        default:
            ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_RCR1);
            if (enable)
                ocp_data |= OUTER_VLAN | INNER_VLAN;
            else
                ocp_data &= ~(OUTER_VLAN | INNER_VLAN);
            ocp_write_word(tp, MCU_TYPE_PLA, PLA_RCR1, ocp_data);
            break;
    }
}

static void wait_oob_link_list_ready(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    int i;

    for (i = 0; i < 1000; i++) {
        ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL);
        if (ocp_data & LINK_LIST_READY)
            break;
        usb_osal_msleep(1);
    }
}

static void r8153_teredo_off(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    switch (tp->version) {
        case RTL_VER_01:
        case RTL_VER_02:
        case RTL_VER_03:
        case RTL_VER_04:
        case RTL_VER_05:
        case RTL_VER_06:
        case RTL_VER_07:
            ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG);
            ocp_data &= ~(TEREDO_SEL | TEREDO_RS_EVENT_MASK |
                          OOB_TEREDO_EN);
            ocp_write_word(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG, ocp_data);
            break;

        case RTL_VER_08:
        case RTL_VER_09:
        case RTL_TEST_01:
        case RTL_VER_10:
        case RTL_VER_11:
        case RTL_VER_12:
        case RTL_VER_13:
        case RTL_VER_14:
        case RTL_VER_15:
        default:
            /* The bit 0 ~ 7 are relative with teredo settings. They are
		 * W1C (write 1 to clear), so set all 1 to disable it.
		 */
            ocp_write_byte(tp, MCU_TYPE_PLA, PLA_TEREDO_CFG, 0xff);
            break;
    }

    ocp_write_word(tp, MCU_TYPE_PLA, PLA_WDT6_CTRL, WDT6_SET_MODE);
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_REALWOW_TIMER, 0);
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_TEREDO_TIMER, 0);
}

static void r8152b_exit_oob(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR);
    ocp_data &= ~RCR_ACPT_ALL;
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data);

    rxdy_gated_en(tp, true);
    r8153_teredo_off(tp);
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML);
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, 0x00);

    ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL);
    ocp_data &= ~NOW_IS_OOB;
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7);
    ocp_data &= ~MCU_BORW_EN;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data);

    wait_oob_link_list_ready(tp);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7);
    ocp_data |= RE_INIT_LL;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data);

    wait_oob_link_list_ready(tp);

    rtl8152_nic_reset(tp);

    /* rx share fifo credit full threshold */
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_NORMAL);

    if (tp->hport->speed == USB_SPEED_FULL ||
        tp->hport->speed == USB_SPEED_LOW) {
        /* rx share fifo credit near full threshold */
        ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1,
                        RXFIFO_THR2_FULL);
        ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2,
                        RXFIFO_THR3_FULL);
    } else {
        /* rx share fifo credit near full threshold */
        ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1,
                        RXFIFO_THR2_HIGH);
        ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2,
                        RXFIFO_THR3_HIGH);
    }

    /* TX share fifo free credit full threshold */
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_TXFIFO_CTRL, TXFIFO_THR_NORMAL2);

    ocp_write_byte(tp, MCU_TYPE_USB, USB_TX_AGG, TX_AGG_MAX_THRESHOLD);
    ocp_write_dword(tp, MCU_TYPE_USB, USB_RX_BUF_TH, RX_THR_HIGH);
    ocp_write_dword(tp, MCU_TYPE_USB, USB_TX_DMA,
                    TEST_MODE_DISABLE | TX_SIZE_ADJUST1);

    rtl_rx_vlan_en(tp, true);

    ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8152_RMS);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_TCR0);
    ocp_data |= TCR0_AUTO_FIFO;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_TCR0, ocp_data);
}

static void r8152b_enter_oob(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL);
    ocp_data &= ~NOW_IS_OOB;
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data);

    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL0, RXFIFO_THR1_OOB);
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL1, RXFIFO_THR2_OOB);
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RXFIFO_CTRL2, RXFIFO_THR3_OOB);

    rtl_disable(tp);

    wait_oob_link_list_ready(tp);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7);
    ocp_data |= RE_INIT_LL;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_SFF_STS_7, ocp_data);

    wait_oob_link_list_ready(tp);

    ocp_write_word(tp, MCU_TYPE_PLA, PLA_RMS, RTL8152_RMS);

    rtl_rx_vlan_en(tp, true);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_BDC_CR);
    ocp_data |= ALDPS_PROXY_MODE;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_BDC_CR, ocp_data);

    ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL);
    ocp_data |= NOW_IS_OOB | DIS_MCU_CLROOB;
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_OOB_CTRL, ocp_data);

    rxdy_gated_en(tp, false);

    ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR);
    ocp_data |= RCR_APM | RCR_AM | RCR_AB;
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data);
}

static void r8152b_init(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    uint16_t data;

    data = r8152_mdio_read(tp, MII_BMCR);
    if (data & BMCR_PDOWN) {
        data &= ~BMCR_PDOWN;
        r8152_mdio_write(tp, MII_BMCR, data);
    }

    r8152_aldps_en(tp, false);

    if (tp->version == RTL_VER_01) {
        ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE);
        ocp_data &= ~LED_MODE_MASK;
        ocp_write_word(tp, MCU_TYPE_PLA, PLA_LED_FEATURE, ocp_data);
    }

    r8152_power_cut_en(tp, false);

    ocp_data = ocp_read_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR);
    ocp_data |= TX_10M_IDLE_EN | PFM_PWM_SWITCH;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_PHY_PWR, ocp_data);
    ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL);
    ocp_data &= ~MCU_CLK_RATIO_MASK;
    ocp_data |= MCU_CLK_RATIO | D3_CLK_GATED_EN;
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_MAC_PWR_CTRL, ocp_data);
    ocp_data = GPHY_STS_MSK | SPEED_DOWN_MSK |
               SPDWN_RXDV_MSK | SPDWN_LINKCHG_MSK;
    ocp_write_word(tp, MCU_TYPE_PLA, PLA_GPHY_INTR_IMR, ocp_data);

    rtl_tally_reset(tp);

    /* enable rx aggregation */
    ocp_data = ocp_read_word(tp, MCU_TYPE_USB, USB_USB_CTRL);
    ocp_data &= ~(RX_AGG_DISABLE | RX_ZERO_EN);
    ocp_write_word(tp, MCU_TYPE_USB, USB_USB_CTRL, ocp_data);
}

static int rtl_enable(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;

    r8152b_reset_packet_filter(tp);

    ocp_data = ocp_read_byte(tp, MCU_TYPE_PLA, PLA_CR);
    ocp_data |= CR_RE | CR_TE;
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CR, ocp_data);

    switch (tp->version) {
        case RTL_VER_01:
        case RTL_VER_02:
        case RTL_VER_03:
        case RTL_VER_04:
        case RTL_VER_05:
        case RTL_VER_06:
        case RTL_VER_07:
            break;
        default:
            r8153b_rx_agg_chg_indicate(tp);
            break;
    }

    rxdy_gated_en(tp, false);

    return 0;
}

static int rtl8152_enable(struct usbh_rtl8152 *tp)
{
    rtl_set_eee_plus(tp);

    return rtl_enable(tp);
}

static void rtl8152_disable(struct usbh_rtl8152 *tp)
{
    r8152_aldps_en(tp, false);
    rtl_disable(tp);
    r8152_aldps_en(tp, true);
}

static void rtl8152_up(struct usbh_rtl8152 *tp)
{
    r8152_aldps_en(tp, false);
    r8152b_exit_oob(tp);
    r8152_aldps_en(tp, true);
}

static void rtl8152_down(struct usbh_rtl8152 *tp)
{
    r8152_power_cut_en(tp, false);
    r8152_aldps_en(tp, false);
    r8152b_enter_oob(tp);
    r8152_aldps_en(tp, true);
}

static int rtl_ops_init(struct usbh_rtl8152 *tp)
{
    struct rtl_ops *ops = &tp->rtl_ops;
    int ret = 0;

    switch (tp->version) {
        case RTL_VER_01:
        case RTL_VER_02:
        case RTL_VER_07:
            ops->init = r8152b_init;
            ops->enable = rtl8152_enable;
            ops->disable = rtl8152_disable;
            ops->up = rtl8152_up;
            ops->down = rtl8152_down;
            // ops->unload = rtl8152_unload;
            // ops->eee_get		= r8152_get_eee;
            // ops->eee_set		= r8152_set_eee;
            // ops->in_nway = rtl8152_in_nway;
            // ops->hw_phy_cfg = r8152b_hw_phy_cfg;
            // ops->autosuspend_en = rtl_runtime_suspend_enable;
            tp->rx_buf_sz = 16 * 1024;
            tp->eee_en = true;
            //tp->eee_adv		= MDIO_EEE_100TX;
            break;

            // case RTL_VER_03:
            // case RTL_VER_04:
            // case RTL_VER_05:
            // case RTL_VER_06:
            //     break;

            // case RTL_VER_08:
            // case RTL_VER_09:
            //     break;

            // case RTL_VER_11:
            // case RTL_VER_10:
            //     break;

            // case RTL_VER_12:
            // case RTL_VER_13:
            // case RTL_VER_15:
            //     break;

            // case RTL_VER_14:
            //     break;

        default:
            ret = -USB_ERR_NODEV;
            USB_LOG_ERR("Unsupport rtl version:%d\r\n", tp->version);
            break;
    }

    return ret;
}

static void rtl8152_set_rx_mode(struct usbh_rtl8152 *tp)
{
    uint32_t ocp_data;
    uint32_t mc_filter[2];

    ocp_data = ocp_read_dword(tp, MCU_TYPE_PLA, PLA_RCR);
    ocp_data &= ~RCR_ACPT_ALL;
    ocp_data |= RCR_AB | RCR_APM;

    ocp_data |= RCR_AM;
    mc_filter[1] = 0xffffffff;
    mc_filter[0] = 0xffffffff;

    pla_ocp_write(tp, PLA_MAR, BYTE_EN_DWORD, sizeof(mc_filter), mc_filter);
    ocp_write_dword(tp, MCU_TYPE_PLA, PLA_RCR, ocp_data);
}

static int rtl8152_set_speed(struct usbh_rtl8152 *tp, uint8_t autoneg, uint16_t speed, uint8_t duplex)
{
    uint16_t bmcr, anar, gbcr;

    anar = r8152_mdio_read(tp, MII_ADVERTISE);
    anar &= ~(ADVERTISE_10HALF | ADVERTISE_10FULL |
              ADVERTISE_100HALF | ADVERTISE_100FULL);
    if (tp->supports_gmii) {
        gbcr = r8152_mdio_read(tp, MII_CTRL1000);
        gbcr &= ~(ADVERTISE_1000FULL | ADVERTISE_1000HALF);
    } else {
        gbcr = 0;
    }

    if (autoneg == AUTONEG_DISABLE) {
        if (speed == SPEED_10) {
            bmcr = 0;
            anar |= ADVERTISE_10HALF | ADVERTISE_10FULL;
        } else if (speed == SPEED_100) {
            bmcr = BMCR_SPEED100;
            anar |= ADVERTISE_100HALF | ADVERTISE_100FULL;
        } else if (speed == SPEED_1000 && tp->supports_gmii) {
            bmcr = BMCR_SPEED1000;
            gbcr |= ADVERTISE_1000FULL | ADVERTISE_1000HALF;
        } else {
            return -USB_ERR_INVAL;
        }

        if (duplex == DUPLEX_FULL)
            bmcr |= BMCR_FULLDPLX;
    } else {
        if (speed == SPEED_10) {
            if (duplex == DUPLEX_FULL)
                anar |= ADVERTISE_10HALF | ADVERTISE_10FULL;
            else
                anar |= ADVERTISE_10HALF;
        } else if (speed == SPEED_100) {
            if (duplex == DUPLEX_FULL) {
                anar |= ADVERTISE_10HALF | ADVERTISE_10FULL;
                anar |= ADVERTISE_100HALF | ADVERTISE_100FULL;
            } else {
                anar |= ADVERTISE_10HALF;
                anar |= ADVERTISE_100HALF;
            }
        } else if (speed == SPEED_1000 && tp->supports_gmii) {
            if (duplex == DUPLEX_FULL) {
                anar |= ADVERTISE_10HALF | ADVERTISE_10FULL;
                anar |= ADVERTISE_100HALF | ADVERTISE_100FULL;
                gbcr |= ADVERTISE_1000FULL | ADVERTISE_1000HALF;
            } else {
                anar |= ADVERTISE_10HALF;
                anar |= ADVERTISE_100HALF;
                gbcr |= ADVERTISE_1000HALF;
            }
        } else {
            return -USB_ERR_INVAL;
        }

        bmcr = BMCR_ANENABLE | BMCR_ANRESTART | BMCR_RESET;
    }

    if (tp->supports_gmii)
        r8152_mdio_write(tp, MII_CTRL1000, gbcr);

    r8152_mdio_write(tp, MII_ADVERTISE, anar);
    r8152_mdio_write(tp, MII_BMCR, bmcr);

    return 0;
}

int r8152_write_hwaddr(struct usbh_rtl8152 *tp, unsigned char *mac)
{
    unsigned char enetaddr[8] = { 0 };
    memcpy(enetaddr, mac, 6);
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_CONFIG);
    pla_ocp_write(tp, PLA_IDR, BYTE_EN_SIX_BYTES, 8, enetaddr);
    ocp_write_byte(tp, MCU_TYPE_PLA, PLA_CRWECR, CRWECR_NORAML);
    return 0;
}

int usbh_rtl8152_get_connect_status(struct usbh_rtl8152 *rtl8152_class)
{
    int ret;

    usbh_int_urb_fill(&rtl8152_class->intin_urb, rtl8152_class->hport, rtl8152_class->intin, g_rtl8152_inttx_buffer, 2, USB_OSAL_WAITING_FOREVER, NULL, NULL);
    ret = usbh_submit_urb(&rtl8152_class->intin_urb);
    if (ret < 0) {
        return ret;
    }

    if (g_rtl8152_inttx_buffer[0] & INTR_LINK) {
        rtl8152_class->connect_status = true;
    } else {
        rtl8152_class->connect_status = false;
    }
    return 0;
}

static int usbh_rtl8152_connect(struct usbh_hubport *hport, uint8_t intf)
{
    struct usb_endpoint_descriptor *ep_desc;
    char mac_buffer[12];
    int ret;

    struct usbh_rtl8152 *rtl8152_class = &g_rtl8152_class;

    memset(rtl8152_class, 0, sizeof(struct usbh_rtl8152));

    rtl8152_class->hport = hport;
    rtl8152_class->intf = intf;

    hport->config.intf[intf].priv = rtl8152_class;

    rtl8152_class->version = usbh_rtl8152_get_version(rtl8152_class);

    if (rtl8152_class->version == RTL_VER_UNKNOWN) {
        USB_LOG_ERR("Unknown version 0x%04x\r\n", rtl8152_class->version);
        return -USB_ERR_NOTSUPP;
    } else {
        USB_LOG_INFO("rtl8152 version 0x%04x\r\n", rtl8152_class->version);
    }

    /* MTU range: 68 - 1500 or 9194 */
    rtl8152_class->min_mtu = 68;
    switch (rtl8152_class->version) {
        case RTL_VER_03:
        case RTL_VER_04:
        case RTL_VER_05:
        case RTL_VER_06:
        case RTL_VER_08:
        case RTL_VER_09:
        case RTL_VER_14:
            rtl8152_class->max_mtu = size_to_mtu(9 * 1024);
            break;
        case RTL_VER_10:
        case RTL_VER_11:
            rtl8152_class->max_mtu = size_to_mtu(15 * 1024);
            break;
        case RTL_VER_12:
        case RTL_VER_13:
        case RTL_VER_15:
            rtl8152_class->max_mtu = size_to_mtu(16 * 1024);
            break;
        case RTL_VER_01:
        case RTL_VER_02:
        case RTL_VER_07:
        default:
            rtl8152_class->max_mtu = 1500;
            break;
    }

    rtl8152_class->saved_wolopts = __rtl_get_wol(rtl8152_class);
    if (rtl_ops_init(rtl8152_class) < 0) {
        return -USB_ERR_NODEV;
    }

    rtl8152_class->rtl_ops.init(rtl8152_class);
    rtl8152_class->rtl_ops.up(rtl8152_class);

    if (rtl8152_class->rx_buf_sz > CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE) {
        USB_LOG_ERR("rx_buf_sz is overflow, default is %d\r\n", CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE);
        return -USB_ERR_NOMEM;
    }

    memset(mac_buffer, 0, 12);
    ret = usbh_get_string_desc(rtl8152_class->hport, 3, (uint8_t *)mac_buffer);
    if (ret < 0) {
        return ret;
    }

    for (int i = 0, j = 0; i < 12; i += 2, j++) {
        char byte_str[3];
        byte_str[0] = mac_buffer[i];
        byte_str[1] = mac_buffer[i + 1];
        byte_str[2] = '\0';

        uint32_t byte = strtoul(byte_str, NULL, 16);
        rtl8152_class->mac[j] = (unsigned char)byte;
    }

    r8152_write_hwaddr(rtl8152_class, rtl8152_class->mac);

    USB_LOG_INFO("RTL8152 MAC address %02x:%02x:%02x:%02x:%02x:%02x\r\n",
                 rtl8152_class->mac[0],
                 rtl8152_class->mac[1],
                 rtl8152_class->mac[2],
                 rtl8152_class->mac[3],
                 rtl8152_class->mac[4],
                 rtl8152_class->mac[5]);

    for (uint8_t i = 0; i < hport->config.intf[intf].altsetting[0].intf_desc.bNumEndpoints; i++) {
        ep_desc = &hport->config.intf[intf].altsetting[0].ep[i].ep_desc;

        if (USB_GET_ENDPOINT_TYPE(ep_desc->bmAttributes) == USB_ENDPOINT_TYPE_INTERRUPT) {
            if (ep_desc->bEndpointAddress & 0x80) {
                USBH_EP_INIT(rtl8152_class->intin, ep_desc);
            } else {
                return -USB_ERR_NOTSUPP;
            }
        } else {
            if (ep_desc->bEndpointAddress & 0x80) {
                USBH_EP_INIT(rtl8152_class->bulkin, ep_desc);
            } else {
                USBH_EP_INIT(rtl8152_class->bulkout, ep_desc);
            }
        }
    }

    strncpy(hport->config.intf[intf].devname, DEV_FORMAT, CONFIG_USBHOST_DEV_NAMELEN);

    USB_LOG_INFO("Register RTL8152 Class:%s\r\n", hport->config.intf[intf].devname);

    usbh_rtl8152_run(rtl8152_class);
    return 0;
}

static int usbh_rtl8152_disconnect(struct usbh_hubport *hport, uint8_t intf)
{
    int ret = 0;

    struct usbh_rtl8152 *rtl8152_class = (struct usbh_rtl8152 *)hport->config.intf[intf].priv;

    if (rtl8152_class) {
        if (rtl8152_class->bulkin) {
            usbh_kill_urb(&rtl8152_class->bulkin_urb);
        }

        if (rtl8152_class->bulkout) {
            usbh_kill_urb(&rtl8152_class->bulkout_urb);
        }

        if (rtl8152_class->intin) {
            usbh_kill_urb(&rtl8152_class->intin_urb);
        }

        if (hport->config.intf[intf].devname[0] != '\0') {
            USB_LOG_INFO("Unregister rtl8152 Class:%s\r\n", hport->config.intf[intf].devname);
            usbh_rtl8152_stop(rtl8152_class);
        }

        memset(rtl8152_class, 0, sizeof(struct usbh_rtl8152));
    }

    return ret;
}

void usbh_rtl8152_rx_thread(void *argument)
{
    uint32_t g_rtl8152_rx_length;
    int ret;
    uint16_t len;
    uint16_t data_offset;
#if CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE <= (16 * 1024)
    uint32_t transfer_size = CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE;
#else
    uint32_t transfer_size = (16 * 1024);
#endif

    USB_LOG_INFO("Create rtl8152 rx thread\r\n");
    // clang-format off
find_class:
    // clang-format on
    g_rtl8152_class.connect_status = false;
    if (usbh_find_class_instance("/dev/rtl8152") == NULL) {
        goto delete;
    }

    while (g_rtl8152_class.connect_status == false) {
        ret = usbh_rtl8152_get_connect_status(&g_rtl8152_class);
        if (ret < 0) {
            usb_osal_msleep(100);
            goto find_class;
        }
        usb_osal_msleep(128);
    }

    if (g_rtl8152_class.rtl_ops.enable) {
        g_rtl8152_class.rtl_ops.enable(&g_rtl8152_class);
    } else {
        goto delete;
    }

    rtl8152_set_rx_mode(&g_rtl8152_class);
    rtl8152_set_speed(&g_rtl8152_class, AUTONEG_ENABLE, g_rtl8152_class.supports_gmii ? SPEED_1000 : SPEED_100, DUPLEX_FULL);

    g_rtl8152_rx_length = 0;
    while (1) {
        usbh_bulk_urb_fill(&g_rtl8152_class.bulkin_urb, g_rtl8152_class.hport, g_rtl8152_class.bulkin, &g_rtl8152_rx_buffer[g_rtl8152_rx_length], transfer_size, USB_OSAL_WAITING_FOREVER, NULL, NULL);
        ret = usbh_submit_urb(&g_rtl8152_class.bulkin_urb);
        if (ret < 0) {
            goto find_class;
        }

        g_rtl8152_rx_length += g_rtl8152_class.bulkin_urb.actual_length;

        /* A transfer is complete because last packet is a short packet.
         * Short packet is not zero, match g_rtl8152_rx_length % USB_GET_MAXPACKETSIZE(g_rtl8152_class.bulkin->wMaxPacketSize).
         * Short packet is zero, check if g_rtl8152_class.bulkin_urb.actual_length < transfer_size, for example transfer is complete with size is 1024 < 2048.
        */
        if (g_rtl8152_rx_length % USB_GET_MAXPACKETSIZE(g_rtl8152_class.bulkin->wMaxPacketSize) ||
            (g_rtl8152_class.bulkin_urb.actual_length < transfer_size)) {
            data_offset = 0;

            USB_LOG_DBG("rxlen:%d\r\n", g_rtl8152_rx_length);
            while (g_rtl8152_rx_length > 0) {
                struct rx_desc *rx_desc = (struct rx_desc *)&g_rtl8152_rx_buffer[data_offset];

                len = rx_desc->opts1 & RX_LEN_MASK;

                USB_LOG_DBG("data_offset:%d, eth len:%d\r\n", data_offset, len);

                uint8_t *buf = (uint8_t *)&g_rtl8152_rx_buffer[data_offset + sizeof(struct rx_desc)];
                usbh_rtl8152_eth_input(buf, len);

                data_offset += (len + sizeof(struct rx_desc));
                g_rtl8152_rx_length -= (len + sizeof(struct rx_desc));

                if (len & (RX_ALIGN - 1)) {
                    data_offset += (RX_ALIGN - (len & (RX_ALIGN - 1)));
                    g_rtl8152_rx_length -= (RX_ALIGN - (len & (RX_ALIGN - 1)));
                }
            }
        } else {
#if CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE <= (16 * 1024)
            if (g_rtl8152_rx_length == CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE) {
#else
            if ((g_rtl8152_rx_length + (16 * 1024)) > CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE) {
#endif
                USB_LOG_ERR("Rx packet is overflow, please ruduce tcp window size or increase CONFIG_USBHOST_RTL8152_ETH_MAX_RX_SIZE\r\n");
                while (1) {
                }
            }
        }
    }
    // clang-format off
delete:
    USB_LOG_INFO("Delete rtl8152 rx thread\r\n");
    usb_osal_thread_delete(NULL);
    // clang-format on
}

uint8_t *usbh_rtl8152_get_eth_txbuf(void)
{
    return (g_rtl8152_tx_buffer + sizeof(struct tx_desc));
}

int usbh_rtl8152_eth_output(uint32_t buflen)
{
    struct tx_desc *tx_desc;

    if (g_rtl8152_class.connect_status == false) {
        return -USB_ERR_NOTCONN;
    }

    tx_desc = (struct tx_desc *)g_rtl8152_tx_buffer;
    tx_desc->opts1 = buflen | TX_FS | TX_LS;
    tx_desc->opts2 = 0;

    USB_LOG_DBG("txlen:%d\r\n", buflen + sizeof(struct tx_desc));

    usbh_bulk_urb_fill(&g_rtl8152_class.bulkout_urb, g_rtl8152_class.hport, g_rtl8152_class.bulkout, g_rtl8152_tx_buffer, buflen + sizeof(struct tx_desc), USB_OSAL_WAITING_FOREVER, NULL, NULL);
    return usbh_submit_urb(&g_rtl8152_class.bulkout_urb);
}

__WEAK void usbh_rtl8152_run(struct usbh_rtl8152 *rtl8152_class)
{
}

__WEAK void usbh_rtl8152_stop(struct usbh_rtl8152 *rtl8152_class)
{
}

static const uint16_t rtl_id_table[][2] = {
    { 0x0BDA, 0x8152 },
    { 0, 0 },
};

static const struct usbh_class_driver rtl8152_class_driver = {
    .driver_name = "rtl8152",
    .connect = usbh_rtl8152_connect,
    .disconnect = usbh_rtl8152_disconnect
};

CLASS_INFO_DEFINE const struct usbh_class_info rtl8152_class_info = {
    .match_flags = USB_CLASS_MATCH_VID_PID | USB_CLASS_MATCH_INTF_CLASS,
    .class = 0xff,
    .subclass = 0x00,
    .protocol = 0x00,
    .id_table = rtl_id_table,
    .class_driver = &rtl8152_class_driver
};
