Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FS#1703 - ath79: ag71xx (ar7240/ar934x builtin) switch does not detect link status change on all ports #6669

Closed
openwrt-bot opened this issue Jul 28, 2018 · 0 comments
Labels

Comments

@openwrt-bot
Copy link

cshoredaniel:

I'm adding support for some device in ath79 arch and found the following issue:

At least for AR9341 with phy4-mii-enable (fifth port configured as a separate ethX device rather than 'just' as a VLAN, using SoC capabilities), the 'normal' switch (i.e. not the fifth port) only detects link status on a single port of the switch.

I have a hack (hence not on list yet; real solution involves ag71xx-phy driver which ends up meaning also ag71xx-mdio etc) below which gets this working for me.

I will update whether this is more than just AR9341 as I test more devices without and with this patch.

.../net/ethernet/atheros/ag71xx/ag71xx.h | 5 +
.../ethernet/atheros/ag71xx/ag71xx_ar7240.c | 180 ++++++++++++++++++
.../net/ethernet/atheros/ag71xx/ag71xx_main.c | 16 ++
3 files changed, 201 insertions(+)

diff --git a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h
index 22b22522a3..2bebec4ac5 100644
--- a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h
+++ b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx.h
@@ -179,6 +179,7 @@ struct ag71xx {
struct phy_device *phy_dev;
void *phy_priv;
int phy_if_mode;

  • bool is_switch;

    unsigned int link;
    unsigned int speed;
    @@ -450,4 +451,8 @@ int ag71xx_mdio_mii_write(struct mii_bus *bus, int addr, int reg, u16 val);
    int ar7240sw_phy_read(struct mii_bus *mii, int addr, int reg);
    int ar7240sw_phy_write(struct mii_bus *mii, int addr, int reg, u16 val);

+int ar7240sw_update_link(struct phy_device *phydev);
+int ar7240sw_read_status(struct phy_device *phydev);
+int ar7240sw_aneg_done(struct phy_device phydev);
+
#endif /
_AG71XX_H */
diff --git a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
index 52c0f964cf..12c62660b4 100644
--- a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
+++ b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_ar7240.c
@@ -194,6 +194,7 @@
#define AR7240_PORT_CPU 0
#define AR7240_NUM_PORTS 6
#define AR7240_NUM_PHYS 5
+#define AR7240_PHY_POLL_MASK 0x3e

#define AR7240_PHY_ID1 0x004d
#define AR7240_PHY_ID2 0xd041
@@ -294,6 +295,7 @@ struct ar7240sw {
struct ag71xx_switch_platform_data *swdata;
struct switch_dev swdev;
int num_ports;

  • u32 phy_poll_mask;
    u8 ver;
    bool vlan;
    u16 vlan_id[AR7240_MAX_VLANS];
    @@ -1245,10 +1247,20 @@ ar7240_probe(struct ag71xx *ag, struct device_node *np)
    as->ver = (ctrl >> AR7240_MASK_CTRL_VERSION_S) &
    AR7240_MASK_CTRL_VERSION_M;

  • if (of_property_read_u32(np, "phy-poll-mask", &as->phy_poll_mask))

  •   as->phy_poll_mask = AR7240_PHY_POLL_MASK;
    
  • else

  •   as->phy_poll_mask &= AR7240_PHY_POLL_MASK;
    
  • pr_info("phy-poll-mask %02x", as->phy_poll_mask);

  • ag->is_switch = false;
    if (sw_is_ar7240(as)) {
    swdev->name = "AR7240/AR9330 built-in switch";
    swdev->ports = AR7240_NUM_PORTS - 1;

  •   ag->is_switch = true;
    

    } else if (sw_is_ar934x(as)) {

  •   ag->is_switch = true;
      swdev->name = "AR934X built-in switch";
    
      if (ag->phy_if_mode == PHY_INTERFACE_MODE_GMII) {
    

@@ -1267,6 +1279,8 @@ ar7240_probe(struct ag71xx *ag, struct device_node *np)
ar7240sw_reg_set(mii, AR934X_REG_OPER_MODE1,
AR934X_REG_OPER_MODE1_PHY4_MII_EN);
swdev->ports = AR7240_NUM_PORTS - 1;

  •   	/* Don't include port 5 as it's now a separate PHY */
    
  •   	as->phy_poll_mask &= 0x1f;
      } else {
      	swdev->ports = AR7240_NUM_PORTS;
      }
    

@@ -1305,6 +1319,9 @@ void ag71xx_ar7240_start(struct ag71xx *ag)
if (!as)
return;

  • if (ag->phy_dev)

  •   ag->phy_dev->priv = ag->phy_priv;
    
  • ar7240sw_reset(as);

    ar7240_set_addr(as, ag->dev->dev_addr);
    @@ -1322,6 +1339,9 @@ int ag71xx_ar7240_init(struct ag71xx *ag, struct device_node *np)
    ag->phy_priv = as;
    ar7240sw_reset(as);

  • if (ag->phy_dev)

  •   ag->phy_dev->priv = ag->phy_priv;
    
  • rwlock_init(&as->stats_lock);

    return 0;
    @@ -1330,6 +1350,7 @@ int ag71xx_ar7240_init(struct ag71xx *ag, struct device_node *np)
    void ag71xx_ar7240_cleanup(struct ag71xx *ag)
    {
    struct ar7240sw *as = ag->phy_priv;

  • ag->phy_dev->priv = NULL;

    if (!as)
    return;
    @@ -1338,3 +1359,162 @@ void ag71xx_ar7240_cleanup(struct ag71xx *ag)
    kfree(as);
    ag->phy_priv = NULL;
    }

+/* Copy of genphy_update_link without the call to driver hook for update_link

    • else we get an infinite loop; from kernel drivers/net/phy/phy_device.c
  • */
    +int ar7240sw_copy_genphy_update_link(struct phy_device *phydev) {
  • int status;
  • /* Do a fake read */
  • status = phy_read(phydev, MII_BMSR);
  • if (status < 0)
  •   return status;
    
  • /* Read link and autonegotiation status */
  • status = phy_read(phydev, MII_BMSR);
  • if (status < 0)
  •   return status;
    
  • if ((status & BMSR_LSTATUS) == 0)
  •   phydev->link = 0;
    
  • else
  •   phydev->link = 1;
    
  • return 0;
    +}

+int ar7240sw_update_link (struct phy_device *phydev) {

  • struct ar7240sw *as = NULL;
  • struct switch_dev *swdev = NULL;
  • int i;
  • int status = 0;
  • struct switch_port_link link;
  • if (!phydev->priv) {
  •   return ar7240sw_copy_genphy_update_link(phydev);
    
  • }
  • as = phydev->priv;
  • swdev = &(as->swdev);
  • if (!swdev) {
  •   return ar7240sw_copy_genphy_update_link(phydev);
    
  • }
  • /* Don't touch link state until we actully know the state */
  • for (i = 0; i < swdev->ports; i++) {
  •   if (i == swdev->cpu_port)
    
  •   	continue;
    
  •   if (!(as->phy_poll_mask & BIT(i))) {
    
  •   	continue;
    
  •   }
    
  •   status = ar7240_get_port_link(swdev, i, &link);
    
  •   if (status < 0) {
    
  •   	return status;
    
  •   }
    
  •   /* If any non-masked port is active, consider
    
  •    * device to be 'up'
    
  •    */
    
  •   if (link.link) {
    
  •   	phydev->link = 1;
    
  •   	return 0;
    
  •   }
    
  • }
  • /* otherwise, device is 'down' */
  • phydev->link = 0;
  • return 0;
    +}

+int ar7240sw_read_status(struct phy_device *phydev) {

  • struct ar7240sw *as = NULL;
  • struct switch_dev *swdev = NULL;
  • int status;
  • int i;
  • struct switch_port_link link;
  • if (!phydev->priv)
  •   return genphy_read_status(phydev);
    
  • status = ar7240sw_update_link(phydev);
  • if (status)
  •   return status;
    
  • as = phydev->priv;
  • swdev = &(as->swdev);
  • if (!swdev)
  •   return genphy_read_status(phydev);
    
  • /* Set link status for base device */
  • if (phydev->link) {
  •   /* While down set link speed duplex lower, but valid */
    
  •   phydev->speed = SPEED_10;
    
  •   phydev->duplex = DUPLEX_HALF;
    
  •   for (i = 0; i < swdev->ports; i++) {
    
  •   	/* We report the speed and duplex as the
    
  •   	 * highest any non-masked port has
    
  •   	 * negotiated.
    
  •   	 */
    
  •   	if (i == swdev->cpu_port)
    
  •   		continue;
    
  •   	if (!(as->phy_poll_mask & BIT(i))) {
    
  •   		continue;
    
  •   	}
    
  •   	status = ar7240_get_port_link(swdev, i, &link);
    
  •   	if (status < 0) {
    
  •   		return status;
    
  •   	}
    
  •   	switch(link.speed) {
    
  •   	case SPEED_100:
    
  •   		if (phydev->speed != SPEED_1000)
    
  •   			phydev->speed = SPEED_100;
    
  •   		break;
    
  •   	case SPEED_1000:
    
  •   			phydev->speed = SPEED_1000;
    
  •   		break;
    
  •   	default:
    
  •   		/* We never reduce detected speed */
    
  •   		break;
    
  •   	}
    
  •   	switch(link.duplex) {
    
  •   	case DUPLEX_FULL:
    
  •   		phydev->duplex = DUPLEX_FULL;
    
  •   		break;
    
  •   	default:
    
  •   		/* We never reduce detected duplex */
    
  •   		break;
    
  •   	}
    
  •   }
    
  •   phydev->pause = 0;
    
  •   phydev->asym_pause = 0;
    
  • }
  • return 0;
    +}

+int ar7240sw_aneg_done (struct phy_device *phydev) {

  • int status;

  • if (!phydev->priv) {

  •   return genphy_aneg_done(phydev);
    
  • }

  • status = ar7240sw_read_status(phydev);

  • if (status)

  •   return status;
    
  • /* If we were able to get a status we've

    • autonegotiated on the port(s) already.
  • */

  • return 1;
    +}
    diff --git a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c
    index 635c3b9127..f33e148e2e 100644
    --- a/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c
    +++ b/target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c
    @@ -1509,6 +1509,22 @@ static int ag71xx_probe(struct platform_device *pdev)

    platform_set_drvdata(pdev, dev);

  • ag->phy_dev->priv = NULL;

  • if (ag->phy_priv)

  •   if (ag->is_switch)
    
  •   	ag->phy_dev->priv = ag->phy_priv;
    
  • /* NB: This replaces update_link for all instances of the

    • the driver (so all 'Generic PHY' instances are affected)
  • */

  • if (ag->phy_dev->drv) {

  •   ag->phy_dev->drv->update_link = ar7240sw_update_link;
    
  •   ag->phy_dev->drv->read_status = ar7240sw_read_status;
    
  •   ag->phy_dev->drv->aneg_done = ar7240sw_aneg_done;
    
  • }

  • err = register_netdev(dev);
    if (err) {
    dev_err(&pdev->dev, "unable to register net device\n");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant