OK335xS LAN8710 phy driver hacking
/******************************************************************** * OK335xS LAN8710 phy driver hacking * 说明: * 本文主要是对OK335xS中的phy的驱动进行代码跟踪,并解决当前遇到 * LAN8710上电后插入网线,会导致LAN8710无法自动握手,Link灯不亮,内核 * 也检测不到LAN8710有状态发生了改变,最终问题定位于LAN8710的驱动初 * 始化部分,本文解决办法选择注释掉对应的内容就行了。 * * 2016-3-3 深圳 南山平山村 曾剑锋 *******************************************************************/ 一、make menuconfig 配置: .config - Linux/arm 3.2.0 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.0/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 0; | | | | 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 = 0, | | | | | .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 0; | | | | | } | | | | | 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 = 0; | | | | | | | | 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 < 0) | | | 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 < 0) | | | 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 0; | | | } | | | | | | 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 < 0) /* error */ | | return result; | | | | if (result == 0) { | | /* 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 < 0) | | return ctl; | | | | if (!(ctl & BMCR_ANENABLE) || (ctl & BMCR_ISOLATE)) | | result = 1; /* do restart aneg */ | | } | | | | /* Only restart aneg if we are advertising something different | | * than we were before. */ | | if (result > 0) | | 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 = 0; | | /* 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 < 0) | return lpagb; | | adv = phy_read(phydev, MII_CTRL1000); | | if (adv < 0) | return adv; | | lpagb &= adv << 2; | } | | lpa = phy_read(phydev, MII_LPA); | | if (lpa < 0) | return lpa; | | adv = phy_read(phydev, MII_ADVERTISE); | | if (adv < 0) | return adv; | | lpa &= adv; | | phydev->speed = SPEED_10; | phydev->duplex = DUPLEX_HALF; | phydev->pause = phydev->asym_pause = 0; | | 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 ? 1 : 0; | phydev->asym_pause = lpa & LPA_PAUSE_ASYM ? 1 : 0; | } | } else { | int bmcr = phy_read(phydev, MII_BMCR); | if (bmcr < 0) | 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 = 0; | } | | return 0; | } | 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 < 0) return rc; /* Enable energy detect mode for this SMSC Transceivers */ rc = phy_write(phydev, MII_LAN83C185_CTRL_STATUS, rc | MII_LAN83C185_EDPWRDOWN); if (rc < 0) return rc; #endif return smsc_phy_ack_interrupt (phydev); }