From 2bff4bf4e5660346e78f1529043ae9241bf4f480 Mon Sep 17 00:00:00 2001 From: Felix Fietkau Date: Wed, 9 Dec 2009 03:25:33 +0000 Subject: [PATCH] rtl8306: manage the wan port as a separate phy device with proper link status reading and autonegotiation - the genphy driver does not handle it properly SVN-Revision: 18709 --- .../files/arch/mips/ar71xx/mach-wrt160nl.c | 2 - .../files/drivers/net/phy/rtl8306.c | 116 ++++++++++++++---- 2 files changed, 89 insertions(+), 29 deletions(-) diff --git a/target/linux/ar71xx/files/arch/mips/ar71xx/mach-wrt160nl.c b/target/linux/ar71xx/files/arch/mips/ar71xx/mach-wrt160nl.c index 9f5f6f1971..ca811d3566 100644 --- a/target/linux/ar71xx/files/arch/mips/ar71xx/mach-wrt160nl.c +++ b/target/linux/ar71xx/files/arch/mips/ar71xx/mach-wrt160nl.c @@ -125,8 +125,6 @@ static void __init wrt160nl_setup(void) ar71xx_eth0_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; ar71xx_eth0_data.phy_mask = 0x01; - ar71xx_eth0_data.speed = SPEED_100; - ar71xx_eth0_data.duplex = DUPLEX_FULL; ar71xx_eth1_data.phy_if_mode = PHY_INTERFACE_MODE_RMII; ar71xx_eth1_data.phy_mask = 0x10; diff --git a/target/linux/generic-2.6/files/drivers/net/phy/rtl8306.c b/target/linux/generic-2.6/files/drivers/net/phy/rtl8306.c index 5c8e81f79d..901b5b2f83 100644 --- a/target/linux/generic-2.6/files/drivers/net/phy/rtl8306.c +++ b/target/linux/generic-2.6/files/drivers/net/phy/rtl8306.c @@ -46,18 +46,22 @@ #define RTL8306_MAGIC 0x8306 +static LIST_HEAD(phydevs); + struct rtl_priv { + struct list_head list; struct switch_dev dev; int page; int type; int do_cpu; struct mii_bus *bus; char hwname[sizeof(RTL_NAME_UNKNOWN)]; +}; - /* temporary register saves for port operations */ - int tmp_speed; - int tmp_nway; - int tmp_duplex; +struct rtl_phyregs { + int nway; + int speed; + int duplex; }; #define to_rtl(_dev) container_of(_dev, struct rtl_priv, dev) @@ -157,6 +161,7 @@ enum rtl_regidx { RTL_REG_PORT##id##_LINK, \ RTL_REG_PORT##id##_SPEED, \ RTL_REG_PORT##id##_NWAY, \ + RTL_REG_PORT##id##_NRESTART, \ RTL_REG_PORT##id##_DUPLEX, \ RTL_REG_PORT##id##_RXEN, \ RTL_REG_PORT##id##_TXEN @@ -217,6 +222,7 @@ static const struct rtl_reg rtl_regs[] = { #define REG_PORT_SETTING(port, phy) \ [RTL_REG_PORT##port##_SPEED] = { 0, phy, 0, 1, 13, 0 }, \ [RTL_REG_PORT##port##_NWAY] = { 0, phy, 0, 1, 12, 0 }, \ + [RTL_REG_PORT##port##_NRESTART] = { 0, phy, 0, 1, 9, 0 }, \ [RTL_REG_PORT##port##_DUPLEX] = { 0, phy, 0, 1, 8, 0 }, \ [RTL_REG_PORT##port##_TXEN] = { 0, phy, 24, 1, 11, 0 }, \ [RTL_REG_PORT##port##_RXEN] = { 0, phy, 24, 1, 10, 0 }, \ @@ -355,23 +361,19 @@ rtl_set(struct switch_dev *dev, enum rtl_regidx s, unsigned int val) } static void -rtl_phy_save(struct switch_dev *dev, int port) +rtl_phy_save(struct switch_dev *dev, int port, struct rtl_phyregs *regs) { - struct rtl_priv *priv = to_rtl(dev); - - priv->tmp_nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); - priv->tmp_speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); - priv->tmp_duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); + regs->nway = rtl_get(dev, RTL_PORT_REG(port, NWAY)); + regs->speed = rtl_get(dev, RTL_PORT_REG(port, SPEED)); + regs->duplex = rtl_get(dev, RTL_PORT_REG(port, DUPLEX)); } static void -rtl_phy_restore(struct switch_dev *dev, int port) +rtl_phy_restore(struct switch_dev *dev, int port, struct rtl_phyregs *regs) { - struct rtl_priv *priv = to_rtl(dev); - - rtl_set(dev, RTL_PORT_REG(port, NWAY), priv->tmp_nway); - rtl_set(dev, RTL_PORT_REG(port, SPEED), priv->tmp_speed); - rtl_set(dev, RTL_PORT_REG(port, DUPLEX), priv->tmp_duplex); + rtl_set(dev, RTL_PORT_REG(port, NWAY), regs->nway); + rtl_set(dev, RTL_PORT_REG(port, SPEED), regs->speed); + rtl_set(dev, RTL_PORT_REG(port, DUPLEX), regs->duplex); } static void @@ -379,6 +381,12 @@ rtl_port_set_enable(struct switch_dev *dev, int port, int enabled) { rtl_set(dev, RTL_PORT_REG(port, RXEN), enabled); rtl_set(dev, RTL_PORT_REG(port, TXEN), enabled); + + if ((port >= 5) || !enabled) + return; + + /* restart autonegotiation if enabled */ + rtl_set(dev, RTL_PORT_REG(port, NRESTART), 1); } static int @@ -386,8 +394,9 @@ rtl_hw_apply(struct switch_dev *dev) { int i; int trunk_en, trunk_psel; + struct rtl_phyregs port5; - rtl_phy_save(dev, 5); + rtl_phy_save(dev, 5, &port5); /* disable rx/tx from PHYs */ for (i = 0; i < RTL8306_NUM_PORTS - 1; i++) { @@ -423,8 +432,7 @@ rtl_hw_apply(struct switch_dev *dev) /* restore trunking settings */ rtl_set(dev, RTL_REG_EN_TRUNK, trunk_en); rtl_set(dev, RTL_REG_TRUNK_PORTSEL, trunk_psel); - - rtl_phy_restore(dev, 5); + rtl_phy_restore(dev, 5, &port5); return 0; } @@ -522,6 +530,7 @@ static int rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) { int idx = attr->id + (val->port_vlan * attr->ofs); + struct rtl_phyregs port; if (attr->id >= ARRAY_SIZE(rtl_regs)) return -EINVAL; @@ -535,9 +544,9 @@ rtl_attr_set_int(struct switch_dev *dev, const struct switch_attr *attr, struct (rtl_regs[idx].reg == 22) && (rtl_regs[idx].page == 0)) { - rtl_phy_save(dev, val->port_vlan); + rtl_phy_save(dev, val->port_vlan, &port); rtl_set(dev, idx, val->value.i); - rtl_phy_restore(dev, val->port_vlan); + rtl_phy_restore(dev, val->port_vlan, &port); } else { rtl_set(dev, idx, val->value.i); } @@ -617,6 +626,7 @@ static int rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct switch_val *val) { struct rtl_priv *priv = to_rtl(dev); + struct rtl_phyregs port; int en = val->value.i; int i; @@ -629,12 +639,12 @@ rtl_set_vlan(struct switch_dev *dev, const struct switch_attr *attr, struct swit for (i = 0; i < RTL8306_NUM_PORTS; i++) { if (i > 3) - rtl_phy_save(dev, val->port_vlan); + rtl_phy_save(dev, val->port_vlan, &port); rtl_set(dev, RTL_PORT_REG(i, NULL_VID_REPLACE), 1); rtl_set(dev, RTL_PORT_REG(i, VID_INSERT), (en ? (i == dev->cpu_port ? 0 : 1) : 1)); rtl_set(dev, RTL_PORT_REG(i, TAG_INSERT), (en ? (i == dev->cpu_port ? 2 : 1) : 3)); if (i > 3) - rtl_phy_restore(dev, val->port_vlan); + rtl_phy_restore(dev, val->port_vlan, &port); } rtl_set(dev, RTL_REG_VLAN_ENABLE, en); @@ -706,6 +716,10 @@ rtl8306_config_init(struct phy_device *pdev) unsigned int chipid, chipver, chiptype; int err; + /* Only init the switch for the primary PHY */ + if (pdev->addr != 0) + return 0; + val.value.i = 1; memcpy(&priv->dev, &rtldev, sizeof(struct switch_dev)); priv->do_cpu = 0; @@ -913,7 +927,8 @@ rtl8306_fixup(struct phy_device *pdev) struct rtl_priv priv; u16 chipid; - if (pdev->addr != 0) + /* Attach to primary LAN port and WAN port */ + if (pdev->addr != 0 && pdev->addr != 4) return 0; priv.page = -1; @@ -930,10 +945,21 @@ rtl8306_probe(struct phy_device *pdev) { struct rtl_priv *priv; + list_for_each_entry(priv, &phydevs, list) { + /* + * share one rtl_priv instance between virtual phy + * devices on the same bus + */ + if (priv->bus == pdev->bus) + goto found; + } priv = kzalloc(sizeof(struct rtl_priv), GFP_KERNEL); if (!priv) return -ENOMEM; + priv->bus = pdev->bus; + +found: pdev->priv = priv; return 0; } @@ -949,15 +975,50 @@ rtl8306_remove(struct phy_device *pdev) static int rtl8306_config_aneg(struct phy_device *pdev) { + struct rtl_priv *priv = pdev->priv; + + /* Only for WAN */ + if (pdev->addr == 0) + return 0; + + /* Restart autonegotiation */ + rtl_set(&priv->dev, RTL_PORT_REG(4, NWAY), 1); + rtl_set(&priv->dev, RTL_PORT_REG(4, NRESTART), 1); + return 0; } static int rtl8306_read_status(struct phy_device *pdev) { - pdev->speed = SPEED_100; - pdev->duplex = DUPLEX_FULL; - pdev->link = 1; + struct rtl_priv *priv = pdev->priv; + struct switch_dev *dev = &priv->dev; + + if (pdev->addr == 4) { + /* WAN */ + pdev->speed = rtl_get(dev, RTL_PORT_REG(4, SPEED)) ? SPEED_100 : SPEED_10; + pdev->duplex = rtl_get(dev, RTL_PORT_REG(4, DUPLEX)) ? DUPLEX_FULL : DUPLEX_HALF; + pdev->link = !!rtl_get(dev, RTL_PORT_REG(4, LINK)); + } else { + /* LAN */ + pdev->speed = SPEED_100; + pdev->duplex = DUPLEX_FULL; + pdev->link = 1; + } + + /* + * Bypass generic PHY status read, + * it doesn't work with this switch + */ + if (pdev->link) { + pdev->state = PHY_RUNNING; + netif_carrier_on(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } else { + pdev->state = PHY_NOLINK; + netif_carrier_off(pdev->attached_dev); + pdev->adjust_link(pdev->attached_dev); + } return 0; } @@ -965,6 +1026,7 @@ rtl8306_read_status(struct phy_device *pdev) static struct phy_driver rtl8306_driver = { .name = "Realtek RTL8306S", + .flags = PHY_HAS_MAGICANEG, .phy_id = RTL8306_MAGIC, .phy_id_mask = 0xffffffff, .features = PHY_BASIC_FEATURES, -- 2.25.1