From 3e3d482c98024aadd94200acfdf811c43b5cd7c4 Mon Sep 17 00:00:00 2001 From: Sergey Ryazanov Date: Wed, 7 Jun 2017 01:49:38 +0300 Subject: [PATCH] ath25: 4.9: fix Ethernet link autonegotiation Drop the own PHY polling function and switch to using the kernel PHY state machine. This change allows driver to work correctly with devices that do not support PHY behaviour but whose driver could emulate autonegotiation completion (e.g. MV88E6060 and IP17xx switches). NB: earlier this driver rely on flaws in PHY core code and could use PHY device without really starting it. But now (at least in kernel 4.9) this trick no more work and network interface could stuck in not-running state. Signed-off-by: Sergey Ryazanov --- .../patches-4.9/110-ar2313_ethernet.patch | 142 ++++-------------- .../220-enet_micrel_workaround.patch | 24 ++- 2 files changed, 50 insertions(+), 116 deletions(-) diff --git a/target/linux/ath25/patches-4.9/110-ar2313_ethernet.patch b/target/linux/ath25/patches-4.9/110-ar2313_ethernet.patch index ed3dcb491c..39638a24dc 100644 --- a/target/linux/ath25/patches-4.9/110-ar2313_ethernet.patch +++ b/target/linux/ath25/patches-4.9/110-ar2313_ethernet.patch @@ -33,7 +33,7 @@ +obj-$(CONFIG_NET_AR231X) += ar231x.o --- /dev/null +++ b/drivers/net/ethernet/atheros/ar231x/ar231x.c -@@ -0,0 +1,1198 @@ +@@ -0,0 +1,1119 @@ +/* + * ar231x.c: Linux driver for the Atheros AR231x Ethernet device. + * @@ -318,9 +318,6 @@ + return -ENODEV; + } + -+ /* start link poll timer */ -+ ar231x_setup_timer(dev); -+ + return 0; +} + @@ -498,93 +495,6 @@ + } +} + -+static int ar231x_setup_timer(struct net_device *dev) -+{ -+ struct ar231x_private *sp = netdev_priv(dev); -+ -+ init_timer(&sp->link_timer); -+ -+ sp->link_timer.function = ar231x_link_timer_fn; -+ sp->link_timer.data = (int)dev; -+ sp->link_timer.expires = jiffies + HZ; -+ -+ add_timer(&sp->link_timer); -+ return 0; -+} -+ -+static void ar231x_link_timer_fn(unsigned long data) -+{ -+ struct net_device *dev = (struct net_device *)data; -+ struct ar231x_private *sp = netdev_priv(dev); -+ -+ /** -+ * See if the link status changed. -+ * This was needed to make sure we set the PHY to the -+ * autonegotiated value of half or full duplex. -+ */ -+ ar231x_check_link(dev); -+ -+ /** -+ * Loop faster when we don't have link. -+ * This was needed to speed up the AP bootstrap time. -+ */ -+ if (sp->link == 0) -+ mod_timer(&sp->link_timer, jiffies + HZ / 2); -+ else -+ mod_timer(&sp->link_timer, jiffies + LINK_TIMER); -+} -+ -+static void ar231x_check_link(struct net_device *dev) -+{ -+ struct ar231x_private *sp = netdev_priv(dev); -+ u16 phy_data; -+ -+ phy_data = ar231x_mdiobus_read(sp->mii_bus, sp->phy, MII_BMSR); -+ if (sp->phy_data != phy_data) { -+ if (phy_data & BMSR_LSTATUS) { -+ /** -+ * Link is present, ready link partner ability to -+ * deterine duplexity. -+ */ -+ int duplex = 0; -+ u16 reg; -+ -+ sp->link = 1; -+ reg = ar231x_mdiobus_read(sp->mii_bus, sp->phy, -+ MII_BMCR); -+ if (reg & BMCR_ANENABLE) { -+ /* auto neg enabled */ -+ reg = ar231x_mdiobus_read(sp->mii_bus, sp->phy, -+ MII_LPA); -+ duplex = reg & (LPA_100FULL | LPA_10FULL) ? -+ 1 : 0; -+ } else { -+ /* no auto neg, just read duplex config */ -+ duplex = (reg & BMCR_FULLDPLX) ? 1 : 0; -+ } -+ -+ printk(KERN_INFO "%s: Configuring MAC for %s duplex\n", -+ dev->name, (duplex) ? "full" : "half"); -+ -+ if (duplex) { -+ /* full duplex */ -+ sp->eth_regs->mac_control = -+ (sp->eth_regs->mac_control | -+ MAC_CONTROL_F) & ~MAC_CONTROL_DRO; -+ } else { -+ /* half duplex */ -+ sp->eth_regs->mac_control = -+ (sp->eth_regs->mac_control | -+ MAC_CONTROL_DRO) & ~MAC_CONTROL_F; -+ } -+ } else { -+ /* no link */ -+ sp->link = 0; -+ } -+ sp->phy_data = phy_data; -+ } -+} -+ +static int ar231x_reset_reg(struct net_device *dev) +{ + struct ar231x_private *sp = netdev_priv(dev); @@ -996,6 +906,8 @@ + + sp->eth_regs->mac_control |= MAC_CONTROL_RE; + ++ phy_start(sp->phy_dev); ++ + return 0; +} + @@ -1055,6 +967,7 @@ + */ +static int ar231x_close(struct net_device *dev) +{ ++ struct ar231x_private *sp = netdev_priv(dev); +#if 0 + /* Disable interrupts */ + disable_irq(dev->irq); @@ -1073,6 +986,9 @@ + free_irq(dev->irq, dev); + +#endif ++ ++ phy_stop(sp->phy_dev); ++ + return 0; +} + @@ -1131,21 +1047,28 @@ +static void ar231x_adjust_link(struct net_device *dev) +{ + struct ar231x_private *sp = netdev_priv(dev); -+ unsigned int mc; ++ struct phy_device *phydev = sp->phy_dev; ++ u32 mc; + -+ if (!sp->phy_dev->link) ++ if (!phydev->link) { ++ if (sp->link) { ++ pr_info("%s: link down\n", dev->name); ++ sp->link = 0; ++ } + return; -+ -+ if (sp->phy_dev->duplex != sp->oldduplex) { -+ mc = readl(&sp->eth_regs->mac_control); -+ mc &= ~(MAC_CONTROL_F | MAC_CONTROL_DRO); -+ if (sp->phy_dev->duplex) -+ mc |= MAC_CONTROL_F; -+ else -+ mc |= MAC_CONTROL_DRO; -+ writel(mc, &sp->eth_regs->mac_control); -+ sp->oldduplex = sp->phy_dev->duplex; + } ++ sp->link = 1; ++ ++ pr_info("%s: link up (%uMbps/%s duplex)\n", dev->name, ++ phydev->speed, phydev->duplex ? "full" : "half"); ++ ++ mc = sp->eth_regs->mac_control; ++ if (phydev->duplex) ++ mc = (mc | MAC_CONTROL_F) & ~MAC_CONTROL_DRO; ++ else ++ mc = (mc | MAC_CONTROL_DRO) & ~MAC_CONTROL_F; ++ sp->eth_regs->mac_control = mc; ++ sp->duplex = phydev->duplex; +} + +#define MII_ADDR(phy, reg) \ @@ -1222,9 +1145,7 @@ + + phydev->advertising = phydev->supported; + -+ sp->oldduplex = -1; + sp->phy_dev = phydev; -+ sp->phy = phydev->mdio.addr; + + printk(KERN_INFO "%s: attached PHY driver [%s] (mii_bus:phy_addr=%s)\n", + dev->name, phydev->drv->name, phydev_name(phydev)); @@ -1234,7 +1155,7 @@ + --- /dev/null +++ b/drivers/net/ethernet/atheros/ar231x/ar231x.h -@@ -0,0 +1,288 @@ +@@ -0,0 +1,281 @@ +/* + * ar231x.h: Linux driver for the Atheros AR231x Ethernet device. + * @@ -1491,18 +1412,14 @@ + char *mapping; + } desc; + -+ struct timer_list link_timer; -+ unsigned short phy; /* merlot phy = 1, samsung phy = 0x1f */ -+ unsigned short mac; + unsigned short link; /* 0 - link down, 1 - link up */ -+ u16 phy_data; ++ unsigned short duplex; /* 0 - half, 1 - full */ + + struct tasklet_struct rx_tasklet; + int unloading; + + struct phy_device *phy_dev; + struct mii_bus *mii_bus; -+ int oldduplex; +}; + +/* Prototypes */ @@ -1518,9 +1435,6 @@ +static int ar231x_close(struct net_device *dev); +static int ar231x_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd); +static void ar231x_init_cleanup(struct net_device *dev); -+static int ar231x_setup_timer(struct net_device *dev); -+static void ar231x_link_timer_fn(unsigned long data); -+static void ar231x_check_link(struct net_device *dev); + +#endif /* _AR2313_H_ */ --- a/arch/mips/ath25/ar2315_regs.h diff --git a/target/linux/ath25/patches-4.9/220-enet_micrel_workaround.patch b/target/linux/ath25/patches-4.9/220-enet_micrel_workaround.patch index 398495a80c..91b9792515 100644 --- a/target/linux/ath25/patches-4.9/220-enet_micrel_workaround.patch +++ b/target/linux/ath25/patches-4.9/220-enet_micrel_workaround.patch @@ -66,7 +66,7 @@ if (ar231x_mdiobus_probe(dev) != 0) { printk(KERN_ERR "%s: mdiobus_probe failed\n", dev->name); rx_tasklet_cleanup(dev); -@@ -329,8 +374,10 @@ static int ar231x_remove(struct platform +@@ -326,8 +371,10 @@ static int ar231x_remove(struct platform rx_tasklet_cleanup(dev); ar231x_init_cleanup(dev); unregister_netdev(dev); @@ -79,7 +79,27 @@ kfree(dev); return 0; } -@@ -1079,6 +1126,9 @@ static int ar231x_ioctl(struct net_devic +@@ -870,7 +917,8 @@ static int ar231x_open(struct net_device + + sp->eth_regs->mac_control |= MAC_CONTROL_RE; + +- phy_start(sp->phy_dev); ++ if (sp->phy_dev) ++ phy_start(sp->phy_dev); + + return 0; + } +@@ -951,7 +999,8 @@ static int ar231x_close(struct net_devic + + #endif + +- phy_stop(sp->phy_dev); ++ if (sp->phy_dev) ++ phy_stop(sp->phy_dev); + + return 0; + } +@@ -995,6 +1044,9 @@ static int ar231x_ioctl(struct net_devic { struct ar231x_private *sp = netdev_priv(dev); -- 2.30.2