/********************************************************************
* OK335xS LAN8710 phy driver hacking
* 说明:
* 本文主要是对OK335xS中的phy的驱动进行代码跟踪,并解决当前遇到
* LAN8710上电后插入网线,会导致LAN8710无法自动握手,Link灯不亮,内核
* 也检测不到LAN8710有状态发生了改变,最终问题定位于LAN8710的驱动初
* 始化部分,本文解决办法选择注释掉对应的内容就行了。
*
* 2016-3-3 深圳 南山平山村 曾剑锋
*******************************************************************/ 一、make menuconfig 配置:
.config - Linux/arm 3.2. Kernel Configuration
──────────────────────────────────────────────────────────────────────────────
┌───────────────── PHY Device support and infrastructure ─────────────────┐
│ Arrow keys navigate the menu. <Enter> selects submenus --->. │
│ Highlighted letters are hotkeys. Pressing <Y> includes, <N> excludes, │
│ <M> modularizes features. Press <Esc><Esc> to exit, <?> for Help, </> │
│ for Search. Legend: [*] built-in [ ] excluded <M> module < > │
│ ┌────^(-)─────────────────────────────────────────────────────────────┐ │
│ │ < > Drivers for Davicom PHYs │ │
│ │ < > Drivers for Quality Semiconductor PHYs │ │
│ │ < > Drivers for the Intel LXT PHYs │ │
│ │ < > Drivers for the Cicada PHYs │ │
│ │ < > Drivers for the Vitesse PHYs │ │
│ │ <*> Drivers for SMSC PHYs │ │
│ │ < > Drivers for Broadcom PHYs │ │
│ │ < > Drivers for ICPlus PHYs │ │
│ │ < > Drivers for Realtek PHYs │ │
│ │ < > Drivers for National Semiconductor PHYs │ │
│ └────v(+)─────────────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────────────────────┤
│ <Select> < Exit > < Help > │
└─────────────────────────────────────────────────────────────────────────┘ 二、linux-3.2./drivers/net/phy/smsc.c 跟踪:
static struct phy_driver lan8710_driver = { <---------+
/* OUI=0x00800f, Model#=0x0f */ |
.phy_id = 0x0007c0f0, |
// mask导致phy_id=0x0007c0f1也行的 |
.phy_id_mask = 0xfffffff0, |
.name = "SMSC LAN8710/LAN8720", |
|
.features = (PHY_BASIC_FEATURES | SUPPORTED_Pause |
| SUPPORTED_Asym_Pause), |
.flags = PHY_HAS_INTERRUPT | PHY_HAS_MAGICANEG, |
|
/* basic functions */ |
.config_aneg = genphy_config_aneg, --------*------+
.read_status = genphy_read_status, --------*------*--+
.config_init = smsc_phy_config_init, --------*------*--*-+
| | | |
/* IRQ related */ | | | |
.ack_interrupt = smsc_phy_ack_interrupt, | | | |
.config_intr = smsc_phy_config_intr, | | | |
| | | |
.suspend = genphy_suspend, | | | |
.resume = genphy_resume, | | | |
| | | |
.driver = { .owner = THIS_MODULE, } | | | |
}; | | | |
| | | |
static int __init smsc_init(void) <---------+ | | | |
{ | | | | |
int ret; | | | | |
| | | | |
ret = phy_driver_register (&lan83c185_driver); | | | | |
if (ret) | | | | |
goto err1; | | | | |
| | | | |
ret = phy_driver_register (&lan8187_driver); | | | | |
if (ret) | | | | |
goto err2; | | | | |
| | | | |
ret = phy_driver_register (&lan8700_driver); | | | | |
if (ret) | | | | |
goto err3; | | | | |
| | | | |
ret = phy_driver_register (&lan911x_int_driver);| | | | |
if (ret) | | | | |
goto err4; | | | | |
| | | | |
ret = phy_driver_register (&lan8710_driver); | -----+ | | |
if (ret) | | | |
goto err5; | | | |
| | | |
return ; | | | |
err5: | | | |
phy_driver_unregister (&lan911x_int_driver); | | | |
err4: | | | |
phy_driver_unregister (&lan8700_driver); | | | |
err3: | | | |
phy_driver_unregister (&lan8187_driver); | | | |
err2: | | | |
phy_driver_unregister (&lan83c185_driver); | | | |
err1: | | | |
return ret; | | | |
} | | | |
| | | |
static void __exit smsc_exit(void) | | | |
{ | | | |
phy_driver_unregister (&lan8710_driver); | | | |
phy_driver_unregister (&lan911x_int_driver); | | | |
phy_driver_unregister (&lan8700_driver); | | | |
phy_driver_unregister (&lan8187_driver); | | | |
phy_driver_unregister (&lan83c185_driver); | | | |
} | | | |
| | | |
MODULE_DESCRIPTION("SMSC PHY driver"); | | | |
MODULE_AUTHOR("Herbert Valerio Riedel"); | | | |
MODULE_LICENSE("GPL"); | | | |
| | | |
module_init(smsc_init); -----------+ | | |
module_exit(smsc_exit); | | |
| | |
static struct mdio_device_id __maybe_unused smsc_tbl[] = { | | |
{ 0x0007c0a0, 0xfffffff0 }, | | |
{ 0x0007c0b0, 0xfffffff0 }, | | |
{ 0x0007c0c0, 0xfffffff0 }, | | |
{ 0x0007c0d0, 0xfffffff0 }, | | |
{ 0x0007c0f0, 0xfffffff0 }, | | |
{ } | | |
}; | | |
| | |
MODULE_DEVICE_TABLE(mdio, smsc_tbl); | | |
| | |
| | |
| | |
static int __init phy_init(void) | | |
{ | | |
int rc; | | |
| | |
rc = mdio_bus_init(); ------------------+ | | |
if (rc) | | | |
return rc; | | | |
| | | |
rc = phy_driver_register(&genphy_driver); ---*-----+ | | |
if (rc) | | | | |
mdio_bus_exit(); | | | | |
| | | | |
return rc; | | | | |
} | | | | |
| | | | |
static void __exit phy_exit(void) | | | | |
{ | | | | |
phy_driver_unregister(&genphy_driver); | | | | |
mdio_bus_exit(); | | | | |
} | | | | |
| | | | |
subsys_initcall(phy_init); | | | | |
module_exit(phy_exit); | | | | |
| | | | |
struct bus_type mdio_bus_type = { <-------+ | | | | |
.name = "mdio_bus", | | | | | |
.match = mdio_bus_match, ------*-*-+ | | | |
.pm = MDIO_BUS_PM_OPS, | | | | | | |
}; | | | | | | |
EXPORT_SYMBOL(mdio_bus_type); | | | | | | |
| | | | | | |
int __init mdio_bus_init(void) <-----------*-+ | | | | |
{ | | | | | |
int ret; | | | | | |
| | | | | |
ret = class_register(&mdio_bus_class); | | | | | |
if (!ret) { | | | | | |
ret = bus_register(&mdio_bus_type); -----+ | | | | |
if (ret) | | | | |
class_unregister(&mdio_bus_class); | | | | |
} | | | | |
| | | | |
return ret; | | | | |
} | | | | |
| | | | |
void mdio_bus_exit(void) | | | | |
{ | | | | |
class_unregister(&mdio_bus_class); | | | | |
bus_unregister(&mdio_bus_type); | | | | |
} | | | | |
| | | | |
static int mdio_bus_match(struct device *dev, <-----+ | | | |
struct device_driver *drv) | | | |
{ | | | |
struct phy_device *phydev = to_phy_device(dev); | | | |
struct phy_driver *phydrv = to_phy_driver(drv); | | | |
| | | |
return ((phydrv->phy_id & phydrv->phy_id_mask) == | | | |
(phydev->phy_id & phydrv->phy_id_mask)); | | | |
} | | | |
| | | |
static struct phy_driver genphy_driver = { <---------+ | | |
.phy_id = 0xffffffff, | | | |
.phy_id_mask = 0xffffffff, | | | |
.name = "Generic PHY", | | | |
.config_init = genphy_config_init, ------*-------+ | | |
.features = , | | | | |
.config_aneg = genphy_config_aneg, ------*-------*-+ | |
.read_status = genphy_read_status, ------*-------*-*--+ |
.suspend = genphy_suspend, | | | | |
.resume = genphy_resume, | | | | |
.driver = {.owner= THIS_MODULE, }, | | | | |
}; | | | | |
| | | | |
int phy_driver_register(struct phy_driver *new_driver) <--+ | | | |
{ | | | |
int retval; | | | |
| | | |
new_driver->driver.name = new_driver->name; | | | |
new_driver->driver.bus = &mdio_bus_type; | | | |
new_driver->driver.probe = phy_probe; -----------+ | | | |
new_driver->driver.remove = phy_remove; | | | | |
| | | | |
retval = driver_register(&new_driver->driver); | | | | |
| | | | |
if (retval) { | | | | |
printk(KERN_ERR "%s: Error %d in registering driver\n", | | | | |
new_driver->name, retval); | | | | |
| | | | |
return retval; | | | | |
} | | | | |
| | | | |
pr_debug("%s: Registered new driver\n", new_driver->name); | | | | |
| | | | |
return ; | | | | |
} | | | | |
EXPORT_SYMBOL(phy_driver_register); | | | | |
| | | | |
static int phy_probe(struct device *dev) <-----------+ | | | |
{ | | | |
struct phy_device *phydev; | | | |
struct phy_driver *phydrv; | | | |
struct device_driver *drv; | | | |
int err = ; | | | |
| | | |
phydev = to_phy_device(dev); | | | |
| | | |
/* Make sure the driver is held. | | | |
* XXX -- Is this correct? */ | | | |
drv = get_driver(phydev->dev.driver); | | | |
phydrv = to_phy_driver(drv); | | | |
phydev->drv = phydrv; | | | |
| | | |
/* Disable the interrupt if the PHY doesn't support it */ | | | |
if (!(phydrv->flags & PHY_HAS_INTERRUPT)) | | | |
phydev->irq = PHY_POLL; | | | |
| | | |
mutex_lock(&phydev->lock); | | | |
| | | |
/* Start out supporting everything. Eventually, | | | |
* a controller will attach, and may modify one | | | |
* or both of these values */ | | | |
phydev->supported = phydrv->features; | | | |
phydev->advertising = phydrv->features; | | | |
| | | |
| | | |
/* Set the state to READY by default */ | | | |
phydev->state = PHY_READY; | | | |
| | | |
if (phydev->drv->probe) | | | |
err = phydev->drv->probe(phydev); | | | |
| | | |
mutex_unlock(&phydev->lock); | | | |
| | | |
return err; | | | |
| | | |
} | | | |
| | | |
static int genphy_config_init(struct phy_device *phydev) <-------+ | | |
{ | | |
int val; | | |
u32 features; | | |
| | |
/* For now, I'll claim that the generic driver supports | | |
* all possible port types */ | | |
features = (SUPPORTED_TP | SUPPORTED_MII | | |
| SUPPORTED_AUI | SUPPORTED_FIBRE | | | |
SUPPORTED_BNC); | | |
| | |
/* Do we support autonegotiation? */ | | |
val = phy_read(phydev, MII_BMSR); | | |
| | |
if (val < ) | | |
return val; | | |
| | |
if (val & BMSR_ANEGCAPABLE) | | |
features |= SUPPORTED_Autoneg; | | |
| | |
if (val & BMSR_100FULL) | | |
features |= SUPPORTED_100baseT_Full; | | |
if (val & BMSR_100HALF) | | |
features |= SUPPORTED_100baseT_Half; | | |
if (val & BMSR_10FULL) | | |
features |= SUPPORTED_10baseT_Full; | | |
if (val & BMSR_10HALF) | | |
features |= SUPPORTED_10baseT_Half; | | |
| | |
if (val & BMSR_ESTATEN) { | | |
val = phy_read(phydev, MII_ESTATUS); | | |
| | |
if (val < ) | | |
return val; | | |
| | |
if (val & ESTATUS_1000_TFULL) | | |
features |= SUPPORTED_1000baseT_Full; | | |
if (val & ESTATUS_1000_THALF) | | |
features |= SUPPORTED_1000baseT_Half; | | |
} | | |
| | |
phydev->supported = features; | | |
phydev->advertising = features; | | |
| | |
return ; | | |
} | | |
| | |
int genphy_config_aneg(struct phy_device *phydev) <----------+ | |
{ | |
int result; | |
| |
printk("zengjf check postion at %s.\n", __func__); | |
| |
if (AUTONEG_ENABLE != phydev->autoneg) | |
return genphy_setup_forced(phydev); | |
| |
result = genphy_config_advert(phydev); | |
| |
if (result < ) /* error */ | |
return result; | |
| |
if (result == ) { | |
/* Advertisement hasn't changed, but maybe aneg was never on to| |
* begin with? Or maybe phy was isolated? */ | |
int ctl = phy_read(phydev, MII_BMCR); | |
| |
if (ctl < ) | |
return ctl; | |
| |
if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) | |
result = ; /* do restart aneg */ | |
} | |
| |
/* Only restart aneg if we are advertising something different | |
* than we were before. */ | |
if (result > ) | |
result = genphy_restart_aneg(phydev); | |
| |
return result; | |
} | |
EXPORT_SYMBOL(genphy_config_aneg); | |
| |
int genphy_read_status(struct phy_device *phydev) <----------+ |
{ |
int adv; |
int err; |
int lpa; |
int lpagb = ; |
|
/* Update the link, but return if there |
* was an error */ |
err = genphy_update_link(phydev); |
if (err) |
return err; |
|
if (AUTONEG_ENABLE == phydev->autoneg) { |
if (phydev->supported & (SUPPORTED_1000baseT_Half |
| SUPPORTED_1000baseT_Full)) { |
lpagb = phy_read(phydev, MII_STAT1000); |
|
if (lpagb < ) |
return lpagb; |
|
adv = phy_read(phydev, MII_CTRL1000); |
|
if (adv < ) |
return adv; |
|
lpagb &= adv << ; |
} |
|
lpa = phy_read(phydev, MII_LPA); |
|
if (lpa < ) |
return lpa; |
|
adv = phy_read(phydev, MII_ADVERTISE); |
|
if (adv < ) |
return adv; |
|
lpa &= adv; |
|
phydev->speed = SPEED_10; |
phydev->duplex = DUPLEX_HALF; |
phydev->pause = phydev->asym_pause = ; |
|
if (lpagb & (LPA_1000FULL | LPA_1000HALF)) { |
phydev->speed = SPEED_1000; |
|
if (lpagb & LPA_1000FULL) |
phydev->duplex = DUPLEX_FULL; |
} else if (lpa & (LPA_100FULL | LPA_100HALF)) { |
phydev->speed = SPEED_100; |
|
if (lpa & LPA_100FULL) |
phydev->duplex = DUPLEX_FULL; |
} else |
if (lpa & LPA_10FULL) |
phydev->duplex = DUPLEX_FULL; |
|
if (phydev->duplex == DUPLEX_FULL){ |
phydev->pause = lpa & LPA_PAUSE_CAP ? : ; |
phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? : ; |
} |
} else { |
int bmcr = phy_read(phydev, MII_BMCR); |
if (bmcr < ) |
return bmcr; |
|
if (bmcr & BMCR_FULLDPLX) |
phydev->duplex = DUPLEX_FULL; |
else |
phydev->duplex = DUPLEX_HALF; |
|
if (bmcr & BMCR_SPEED1000) |
phydev->speed = SPEED_1000; |
else if (bmcr & BMCR_SPEED100) |
phydev->speed = SPEED_100; |
else |
phydev->speed = SPEED_10; |
|
phydev->pause = phydev->asym_pause = ; |
} |
|
return ; |
} |
EXPORT_SYMBOL(genphy_read_status); |
|
|
static int smsc_phy_config_init(struct phy_device *phydev) <---------+
{
printk("zengjf check position %s.\n", __func__);
#if 0 // 这段代码会导致PHY无法自动识别到网线插入
int rc = phy_read(phydev, MII_LAN83C185_CTRL_STATUS);
if (rc < )
return rc; /* Enable energy detect mode for this SMSC Transceivers */
rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS,
rc | MII_LAN83C185_EDPWRDOWN);
if (rc < )
return rc;
#endif return smsc_phy_ack_interrupt (phydev);
}