423 lines
11 KiB
C
Executable File

/*++
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 <linux/timer.h>
#include <linux/module.h>
#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, &regval);
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, &regval);
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, &regval);
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