aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/ath79/files/drivers
diff options
context:
space:
mode:
authorDavid Bauer <mail@david-bauer.net>2019-04-11 17:59:43 +0200
committerAdrian Schmutzler <freifunk@adrianschmutzler.de>2020-01-23 15:28:03 +0100
commit0d416a8d3b990e3b78628f0e7546527709c877f7 (patch)
tree4de19d42cee647765574ae1a0dc337e22a83a7ef /target/linux/ath79/files/drivers
parent7d39946ea0fe954bcd0c4d52fffc6d9dc6248cf6 (diff)
downloadupstream-0d416a8d3b990e3b78628f0e7546527709c877f7.tar.gz
upstream-0d416a8d3b990e3b78628f0e7546527709c877f7.tar.bz2
upstream-0d416a8d3b990e3b78628f0e7546527709c877f7.zip
ath79: add QCA955x SGMII link loss workaround
This commit adds a workaround for the loss of the SGMII link observed on the QCA955x generation of SoCs. The workaround originates part from the U-Boot source code, part from the implementation from AVM found in the GPL tarball for the AVM FRITZ!WLAN Repeater 450E. The bug results in a stuck SGMII link between the PHY device and the SoC side. This has only been observed with the Atheros AR8033 PHY and most likely all devices using such combination are affected. It is worked around by reading a hidden SGMII status register and issuing a SGMII PHY reset until the link becomes useable again. Signed-off-by: David Bauer <mail@david-bauer.net>
Diffstat (limited to 'target/linux/ath79/files/drivers')
-rw-r--r--target/linux/ath79/files/drivers/net/ethernet/atheros/ag71xx/ag71xx_main.c108
1 files changed, 108 insertions, 0 deletions
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 0924b81b92..6123a26f2c 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
@@ -559,6 +559,112 @@ static void ath79_set_pll(struct ag71xx *ag)
udelay(100);
}
+static void ag71xx_bit_set(void __iomem *reg, u32 bit)
+{
+ u32 val;
+
+ val = __raw_readl(reg) | bit;
+ __raw_writel(val, reg);
+ __raw_readl(reg);
+}
+
+static void ag71xx_bit_clear(void __iomem *reg, u32 bit)
+{
+ u32 val;
+
+ val = __raw_readl(reg) & ~bit;
+ __raw_writel(val, reg);
+ __raw_readl(reg);
+}
+
+static void ag71xx_sgmii_init_qca955x(struct device_node *np)
+{
+ struct device_node *np_dev;
+ void __iomem *gmac_base;
+ u32 mr_an_status;
+ u32 sgmii_status;
+ u8 tries = 0;
+ int err = 0;
+
+ np = of_get_child_by_name(np, "gmac-config");
+ if (!np)
+ return;
+
+ np_dev = of_parse_phandle(np, "device", 0);
+ if (!np_dev)
+ goto out;
+
+ gmac_base = of_iomap(np_dev, 0);
+ if (!gmac_base) {
+ pr_err("%pOF: can't map GMAC registers\n", np_dev);
+ err = -ENOMEM;
+ goto err_iomap;
+ }
+
+ mr_an_status = __raw_readl(gmac_base + QCA955X_GMAC_REG_MR_AN_STATUS);
+ if (!(mr_an_status & QCA955X_MR_AN_STATUS_AN_ABILITY))
+ goto sgmii_out;
+
+ /* SGMII reset sequence */
+ __raw_writel(QCA955X_SGMII_RESET_RX_CLK_N_RESET,
+ gmac_base + QCA955X_GMAC_REG_SGMII_RESET);
+ __raw_readl(gmac_base + QCA955X_GMAC_REG_SGMII_RESET);
+ udelay(10);
+
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_SGMII_RESET,
+ QCA955X_SGMII_RESET_HW_RX_125M_N);
+ udelay(10);
+
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_SGMII_RESET,
+ QCA955X_SGMII_RESET_RX_125M_N);
+ udelay(10);
+
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_SGMII_RESET,
+ QCA955X_SGMII_RESET_TX_125M_N);
+ udelay(10);
+
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_SGMII_RESET,
+ QCA955X_SGMII_RESET_RX_CLK_N);
+ udelay(10);
+
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_SGMII_RESET,
+ QCA955X_SGMII_RESET_TX_CLK_N);
+ udelay(10);
+
+ /*
+ * The following is what QCA has to say about what happens here:
+ *
+ * Across resets SGMII link status goes to weird state.
+ * If SGMII_DEBUG register reads other than 0x1f or 0x10,
+ * we are for sure in a bad state.
+ *
+ * Issue a PHY reset in MR_AN_CONTROL to keep going.
+ */
+ do {
+ ag71xx_bit_set(gmac_base + QCA955X_GMAC_REG_MR_AN_CONTROL,
+ QCA955X_MR_AN_CONTROL_PHY_RESET |
+ QCA955X_MR_AN_CONTROL_AN_ENABLE);
+ udelay(200);
+ ag71xx_bit_clear(gmac_base + QCA955X_GMAC_REG_MR_AN_CONTROL,
+ QCA955X_MR_AN_CONTROL_PHY_RESET);
+ mdelay(300);
+ sgmii_status = __raw_readl(gmac_base + QCA955X_GMAC_REG_SGMII_DEBUG) &
+ QCA955X_SGMII_DEBUG_TX_STATE_MASK;
+
+ if (tries++ >= 20) {
+ pr_err("ag71xx: max retries for SGMII fixup exceeded\n");
+ break;
+ }
+ } while (!(sgmii_status == 0xf || sgmii_status == 0x10));
+
+sgmii_out:
+ iounmap(gmac_base);
+err_iomap:
+ of_node_put(np_dev);
+out:
+ of_node_put(np);
+}
+
static void ath79_mii_ctrl_set_if(struct ag71xx *ag, unsigned int mii_if)
{
u32 t;
@@ -707,6 +813,8 @@ __ag71xx_link_adjust(struct ag71xx *ag, bool update)
of_device_is_compatible(np, "qca,qca9550-eth") ||
of_device_is_compatible(np, "qca,qca9560-eth")) {
ath79_set_pllval(ag);
+ if (of_property_read_bool(np, "qca955x-sgmii-fixup"))
+ ag71xx_sgmii_init_qca955x(np);
}
}