/*++ Copyright (c) 2021 Motor-comm Corporation. Confidential and Proprietary. All rights reserved. This is Motor-comm Corporation NIC driver relevant files. Please don't copy, modify, distribute without commercial permission. --*/ #include #include #include "fuxi-gmac.h" #include "fuxi-gmac-reg.h" void fxgmac_phy_force_speed(struct fxgmac_pdata *pdata, int speed) { struct fxgmac_hw_ops* hw_ops = &pdata->hw_ops; u32 regval = 0; unsigned int high_bit = 0, low_bit = 0; switch (speed) { case SPEED_1000: high_bit = 1, low_bit = 0; break; case SPEED_100: high_bit = 0, low_bit = 1; break; case SPEED_10: high_bit = 0, low_bit = 0; break; default: break; } /* disable autoneg */ hw_ops->read_ephy_reg(pdata, REG_MII_BMCR, ®val); regval = FXGMAC_SET_REG_BITS(regval, PHY_CR_AUTOENG_POS, PHY_CR_AUTOENG_LEN, 0); regval = FXGMAC_SET_REG_BITS(regval, PHY_CR_SPEED_SEL_H_POS, PHY_CR_SPEED_SEL_H_LEN, high_bit); regval = FXGMAC_SET_REG_BITS(regval, PHY_CR_SPEED_SEL_L_POS, PHY_CR_SPEED_SEL_L_LEN, low_bit); hw_ops->write_ephy_reg(pdata, REG_MII_BMCR, regval); } void fxgmac_phy_force_duplex(struct fxgmac_pdata *pdata, int duplex) { struct fxgmac_hw_ops* hw_ops = &pdata->hw_ops; u32 regval = 0; hw_ops->read_ephy_reg(pdata, REG_MII_BMCR, ®val); regval = FXGMAC_SET_REG_BITS(regval, PHY_CR_DUPLEX_POS, PHY_CR_DUPLEX_LEN, (duplex ? 1 : 0)); hw_ops->write_ephy_reg(pdata, REG_MII_BMCR, regval); } void fxgmac_phy_force_autoneg(struct fxgmac_pdata *pdata, int autoneg) { struct fxgmac_hw_ops* hw_ops = &pdata->hw_ops; u32 regval = 0; hw_ops->read_ephy_reg(pdata, REG_MII_BMCR, ®val); regval = FXGMAC_SET_REG_BITS(regval, PHY_CR_AUTOENG_POS, PHY_CR_AUTOENG_LEN, (autoneg? 1 : 0)); hw_ops->write_ephy_reg(pdata, REG_MII_BMCR, regval); } /* * input: lport * output: * cap_mask, bit definitions: * pause capbility and 100/10 capbilitys follow the definition of mii reg4. * for 1000M capability, bit0=1000M half; bit1=1000M full, refer to mii reg9.[9:8]. */ int fxgmac_ephy_autoneg_ability_get(struct fxgmac_pdata *pdata, unsigned int *cap_mask) { struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops; unsigned int val; unsigned int reg; if((!hw_ops->read_ephy_reg) || (!hw_ops->write_ephy_reg)) return -1; reg = REG_MII_ADVERTISE; if(hw_ops->read_ephy_reg(pdata, reg, &val) < 0) goto busy_exit; //DPRINTK("fxgmac_ephy_autoneg_ability_get, reg %d=0x%04x\n", reg, val); if(FXGMAC_ADVERTISE_10HALF & val) { *cap_mask |= FXGMAC_ADVERTISE_10HALF; } else { *cap_mask &= ~FXGMAC_ADVERTISE_10HALF; } if(FXGMAC_ADVERTISE_10FULL & val) { *cap_mask |= FXGMAC_ADVERTISE_10FULL; } else { *cap_mask &= ~FXGMAC_ADVERTISE_10FULL; } if(FXGMAC_ADVERTISE_100HALF & val) { *cap_mask |= FXGMAC_ADVERTISE_100HALF; } else { *cap_mask &= ~FXGMAC_ADVERTISE_100HALF; } if(FXGMAC_ADVERTISE_100FULL & val) { *cap_mask |= FXGMAC_ADVERTISE_100FULL; } else { *cap_mask &= ~FXGMAC_ADVERTISE_100FULL; } if(FXGMAC_ADVERTISE_PAUSE_CAP & val) { *cap_mask |= FXGMAC_ADVERTISE_PAUSE_CAP; } else { *cap_mask &= ~FXGMAC_ADVERTISE_PAUSE_CAP; } if(FXGMAC_ADVERTISE_PAUSE_ASYM & val) { *cap_mask |= FXGMAC_ADVERTISE_PAUSE_ASYM; } else { *cap_mask &= ~FXGMAC_ADVERTISE_PAUSE_ASYM; } reg = REG_MII_CTRL1000; if(hw_ops->read_ephy_reg(pdata, reg, &val) < 0) goto busy_exit; //DPRINTK("fxgmac_ephy_autoneg_ability_get, reg %d=0x%04x\n", reg, val); if(REG_BIT_ADVERTISE_1000HALF & val ) { *cap_mask |= FXGMAC_ADVERTISE_1000HALF; } else { *cap_mask &= ~FXGMAC_ADVERTISE_1000HALF; } if(REG_BIT_ADVERTISE_1000FULL & val ) { *cap_mask |= FXGMAC_ADVERTISE_1000FULL; } else { *cap_mask &= ~FXGMAC_ADVERTISE_1000FULL; } //DPRINTK("fxgmac_ephy_autoneg_ability_get done, 0x%08x.\n", *cap_mask); return 0; busy_exit: DPRINTK("fxgmac_ephy_autoneg_ability_get exit due to ephy reg access fail.\n"); return -1; } int fxgmac_ephy_soft_reset(struct fxgmac_pdata *pdata) { struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops; int ret; volatile unsigned int val; int busy = 15; ret = hw_ops->read_ephy_reg(pdata, REG_MII_BMCR, (unsigned int *)&val); if (0 > ret) goto busy_exit; ret = hw_ops->write_ephy_reg(pdata, REG_MII_BMCR, (val | 0x8000)); if (0 > ret) goto busy_exit; do { ret = hw_ops->read_ephy_reg(pdata, REG_MII_BMCR, (unsigned int *)&val); busy--; //DPRINTK("fxgmac_ephy_soft_reset, check busy=%d.\n", busy); }while((ret >= 0) && (0 != (val & 0x8000)) && (busy)); if(0 == (val & 0x8000)) return 0; DPRINTK("fxgmac_ephy_soft_reset, timeout, busy=%d.\n", busy); return -EBUSY; busy_exit: DPRINTK("fxgmac_ephy_soft_reset exit due to ephy reg access fail.\n"); return ret; } /* this function used to double check the speed. for fiber, to correct there is no 10M */ static int fxgmac_ephy_adjust_status(u32 lport, int val, int is_utp, int* speed, int* duplex) { int speed_mode; *speed = -1; *duplex = (val & BIT(FUXI_EPHY_DUPLEX_BIT)) >> FUXI_EPHY_DUPLEX_BIT; speed_mode = (val & FUXI_EPHY_SPEED_MODE) >> FUXI_EPHY_SPEED_MODE_BIT; switch (speed_mode) { case 0: if (is_utp) *speed = SPEED_10M; break; case 1: *speed = SPEED_100M; break; case 2: *speed = SPEED_1000M; break; case 3: break; default: break; } return 0; } /* * this function for polling to get status of ephy link. * output: * speed: SPEED_10M, SPEED_100M, SPEED_1000M or -1; * duplex: 0 or 1, see reg 0x11, bit YT8614_DUPLEX_BIT. * ret_link: 0 or 1, link down or up. * media: only valid when ret_link=1, (YT8614_SMI_SEL_SDS_SGMII + 1) for fiber; (YT8614_SMI_SEL_PHY + 1) for utp. -1 for link down. */ int fxgmac_ephy_status_get(struct fxgmac_pdata *pdata, int* speed, int* duplex, int* ret_link, int *media) { struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops; int ret; u16 reg; volatile unsigned int val; volatile int link; int link_utp = 0, link_fiber = 0; reg = REG_MII_SPEC_STATUS; ret = hw_ops->read_ephy_reg(pdata, reg, (unsigned int *)&val); if (0 > ret) goto busy_exit; link = val & (BIT(FUXI_EPHY_LINK_STATUS_BIT)); if (link) { link_utp = 1; fxgmac_ephy_adjust_status(0, val, 1, speed, duplex); } else { link_utp = 0; } if (link_utp || link_fiber) { /* case of fiber of priority */ if(link_utp) *media = (FUXI_EPHY_SMI_SEL_PHY + 1); if(link_fiber) *media = (FUXI_EPHY_SMI_SEL_SDS_SGMII + 1); *ret_link = 1; } else { *ret_link = 0; *media = -1; *speed= -1; *duplex = -1; } return 0; busy_exit: DPRINTK("fxgmac_ephy_status_get exit due to ephy reg access fail.\n"); return ret; } #if 0 /** * fxgmac_phy_update_link - update the phy link status * @adapter: pointer to the device adapter structure **/ static void fxgmac_phy_update_link(struct net_device *netdev) { struct fxgmac_pdata *pdata = netdev_priv(netdev); struct fxgmac_hw_ops *hw_ops = &pdata->hw_ops; bool b_linkup = false; u32 phy_speed = 0, ephy_val1, ephy_val2; u32 pre_phy_speed = 0xff; if (hw_ops->get_xlgmii_phy_status) { hw_ops->get_xlgmii_phy_status(pdata, (u32*)&phy_speed, (bool *)&b_linkup, 0); } else { /* always assume link is down, if no check link function */ } pre_phy_speed = ((SPEED_1000 == pdata->phy_speed) ? 2 : ((SPEED_100 == pdata->phy_speed) ? 1 : 0) ); if(pre_phy_speed != phy_speed) { DPRINTK("fuxi_phy link phy speed changed,%d->%d\n", pre_phy_speed, phy_speed); switch(phy_speed){ case 2: pdata->phy_speed = SPEED_1000; break; case 1: pdata->phy_speed = SPEED_100; break; case 0: pdata->phy_speed = SPEED_10; break; default: pdata->phy_speed = SPEED_1000; break; } fxgmac_config_mac_speed(pdata); pre_phy_speed = phy_speed; } if(pdata->phy_link != b_linkup) { pdata->phy_link = b_linkup; fxgmac_act_phy_link(pdata); if(b_linkup && (hw_ops->read_ephy_reg)) { hw_ops->read_ephy_reg(pdata, 0x1/* ephy latched status */, (unsigned int *)&ephy_val1); hw_ops->read_ephy_reg(pdata, 0x1/* ephy latched status */, (unsigned int *)&ephy_val2); DPRINTK("%s phy reg1=0x%04x, 0x%04x\n", __FUNCTION__, ephy_val1 & 0xffff, ephy_val2 & 0xffff); } } } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) static void fxgmac_phy_link_poll(struct timer_list *t) #else static void fxgmac_phy_link_poll(unsigned long data) #endif { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) struct fxgmac_pdata *pdata = from_timer(pdata, t, expansion.phy_poll_tm); #else struct fxgmac_pdata *pdata = (struct fxgmac_pdata*)data; #endif if(NULL == pdata->netdev) { DPRINTK("fuxi_phy_timer polling with NULL netdev %lx\n",(unsigned long)(pdata->netdev)); return; } pdata->stats.ephy_poll_timer_cnt++; #if FXGMAC_PM_FEATURE_ENABLED /* 20210709 for net power down */ if(!test_bit(FXGMAC_POWER_STATE_DOWN, &pdata->expansion.powerstate)) #endif { //yzhang if(2 > pdata->stats.ephy_poll_timer_cnt) { mod_timer(&pdata->phy_poll_tm,jiffies + HZ / 2); } fxgmac_phy_update_link(pdata->netdev); }else { DPRINTK("fuxi_phy_timer polling, powerstate changed, %ld, netdev=%lx, tm=%lx\n", pdata->expansion.powerstate, (unsigned long)(pdata->netdev), (unsigned long)&pdata->phy_poll_tm); } //DPRINTK("fuxi_phy_timer polled,%d\n",cnt_polling); } /* * used when fxgmac is powerdown and resume * 20210709 for net power down */ void fxgmac_phy_timer_resume(struct fxgmac_pdata *pdata) { if(NULL == pdata->netdev) { DPRINTK("fxgmac_phy_timer_resume, failed due to NULL netdev %lx\n",(unsigned long)(pdata->netdev)); return; } mod_timer(&pdata->phy_poll_tm,jiffies + HZ / 2); DPRINTK("fxgmac_phy_timer_resume ok, fxgmac powerstate=%ld, netdev=%lx, tm=%lx\n", pdata->expansion.powerstate, (unsigned long)(pdata->netdev), (unsigned long)&pdata->phy_poll_tm); } int fxgmac_phy_timer_init(struct fxgmac_pdata *pdata) { #if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,15,0)) init_timer_key(&pdata->phy_poll_tm,NULL/*function*/,0/*flags*/,"fuxi_phy_link_update_timer"/*name*/,NULL/*class lock key*/); #else init_timer_key(&pdata->phy_poll_tm,0/*flags*/,"fuxi_phy_link_update_timer"/*name*/,NULL/*class lock key*/); #endif pdata->phy_poll_tm.expires = jiffies + HZ / 2; pdata->phy_poll_tm.function = (void *)(fxgmac_phy_link_poll); #if (LINUX_VERSION_CODE < KERNEL_VERSION(4,15,0)) pdata->phy_poll_tm.data = (unsigned long)pdata; #endif add_timer(&pdata->phy_poll_tm); DPRINTK("fuxi_phy_timer started, %lx\n", jiffies); return 0; } void fxgmac_phy_timer_destroy(struct fxgmac_pdata *pdata) { del_timer_sync(&pdata->phy_poll_tm); DPRINTK("fuxi_phy_timer removed\n"); } #endif