--- /dev/null
+From 5d9e068402dcf7354cc8ee66c2152845306d2ccb Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:51 +0200
+Subject: [PATCH] net: dsa: qca8k: change simple print to dev variant
+
+Change pr_err and pr_warn to dev variant.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -701,7 +701,7 @@ qca8k_setup(struct dsa_switch *ds)
+
+ /* Make sure that port 0 is the cpu port */
+ if (!dsa_is_cpu_port(ds, 0)) {
+- pr_err("port 0 is not the CPU port\n");
++ dev_err(priv->dev, "port 0 is not the CPU port");
+ return -EINVAL;
+ }
+
+@@ -711,7 +711,7 @@ qca8k_setup(struct dsa_switch *ds)
+ priv->regmap = devm_regmap_init(ds->dev, NULL, priv,
+ &qca8k_regmap_config);
+ if (IS_ERR(priv->regmap))
+- pr_warn("regmap initialization failed");
++ dev_warn(priv->dev, "regmap initialization failed");
+
+ ret = qca8k_setup_mdio_bus(priv);
+ if (ret)
--- /dev/null
+From 2ad255f2faaffb3af786031fba2e7955454b558a Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:52 +0200
+Subject: [PATCH] net: dsa: qca8k: use iopoll macro for qca8k_busy_wait
+
+Use iopoll macro instead of while loop.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 23 +++++++++++------------
+ drivers/net/dsa/qca8k.h | 2 ++
+ 2 files changed, 13 insertions(+), 12 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -262,21 +262,20 @@ static struct regmap_config qca8k_regmap
+ static int
+ qca8k_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
+ {
+- unsigned long timeout;
++ u32 val;
++ int ret;
+
+- timeout = jiffies + msecs_to_jiffies(20);
++ ret = read_poll_timeout(qca8k_read, val, !(val & mask),
++ 0, QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++ priv, reg);
+
+- /* loop until the busy flag has cleared */
+- do {
+- u32 val = qca8k_read(priv, reg);
+- int busy = val & mask;
++ /* Check if qca8k_read has failed for a different reason
++ * before returning -ETIMEDOUT
++ */
++ if (ret < 0 && val < 0)
++ return val;
+
+- if (!busy)
+- break;
+- cond_resched();
+- } while (!time_after_eq(jiffies, timeout));
+-
+- return time_after_eq(jiffies, timeout);
++ return ret;
+ }
+
+ static void
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -18,6 +18,8 @@
+ #define PHY_ID_QCA8337 0x004dd036
+ #define QCA8K_ID_QCA8337 0x13
+
++#define QCA8K_BUSY_WAIT_TIMEOUT 20
++
+ #define QCA8K_NUM_FDB_RECORDS 2048
+
+ #define QCA8K_CPU_PORT 0
--- /dev/null
+From 504bf65931824eda83494e5b5d75686e27ace03e Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:53 +0200
+Subject: [PATCH] net: dsa: qca8k: improve qca8k read/write/rmw bus access
+
+Put bus in local variable to improve faster access to the mdio bus.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 29 ++++++++++++++++-------------
+ 1 file changed, 16 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -142,17 +142,18 @@ qca8k_set_page(struct mii_bus *bus, u16
+ static u32
+ qca8k_read(struct qca8k_priv *priv, u32 reg)
+ {
++ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ u32 val;
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(priv->bus, page);
+- val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
++ qca8k_set_page(bus, page);
++ val = qca8k_mii_read32(bus, 0x10 | r2, r1);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ mutex_unlock(&bus->mdio_lock);
+
+ return val;
+ }
+@@ -160,35 +161,37 @@ qca8k_read(struct qca8k_priv *priv, u32
+ static void
+ qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+ {
++ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(priv->bus, page);
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
++ qca8k_set_page(bus, page);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ mutex_unlock(&bus->mdio_lock);
+ }
+
+ static u32
+ qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val)
+ {
++ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ u32 ret;
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(priv->bus, page);
+- ret = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
++ qca8k_set_page(bus, page);
++ ret = qca8k_mii_read32(bus, 0x10 | r2, r1);
+ ret &= ~mask;
+ ret |= val;
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, ret);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, ret);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
+ }
--- /dev/null
+From ba5707ec58cfb6853dff41c2aae72deb6a03d389 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:54 +0200
+Subject: [PATCH] net: dsa: qca8k: handle qca8k_set_page errors
+
+With a remote possibility, the set_page function can fail. Since this is
+a critical part of the write/read qca8k regs, propagate the error and
+terminate any read/write operation.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 33 ++++++++++++++++++++++++++-------
+ 1 file changed, 26 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -127,16 +127,23 @@ qca8k_mii_write32(struct mii_bus *bus, i
+ "failed to write qca8k 32bit register\n");
+ }
+
+-static void
++static int
+ qca8k_set_page(struct mii_bus *bus, u16 page)
+ {
++ int ret;
++
+ if (page == qca8k_current_page)
+- return;
++ return 0;
+
+- if (bus->write(bus, 0x18, 0, page) < 0)
++ ret = bus->write(bus, 0x18, 0, page);
++ if (ret < 0) {
+ dev_err_ratelimited(&bus->dev,
+ "failed to set qca8k page\n");
++ return ret;
++ }
++
+ qca8k_current_page = page;
++ return 0;
+ }
+
+ static u32
+@@ -150,11 +157,14 @@ qca8k_read(struct qca8k_priv *priv, u32
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(bus, page);
++ val = qca8k_set_page(bus, page);
++ if (val < 0)
++ goto exit;
++
+ val = qca8k_mii_read32(bus, 0x10 | r2, r1);
+
++exit:
+ mutex_unlock(&bus->mdio_lock);
+-
+ return val;
+ }
+
+@@ -163,14 +173,19 @@ qca8k_write(struct qca8k_priv *priv, u32
+ {
+ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
++ int ret;
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(bus, page);
++ ret = qca8k_set_page(bus, page);
++ if (ret < 0)
++ goto exit;
++
+ qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+
++exit:
+ mutex_unlock(&bus->mdio_lock);
+ }
+
+@@ -185,12 +200,16 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
+
+ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- qca8k_set_page(bus, page);
++ ret = qca8k_set_page(bus, page);
++ if (ret < 0)
++ goto exit;
++
+ ret = qca8k_mii_read32(bus, 0x10 | r2, r1);
+ ret &= ~mask;
+ ret |= val;
+ qca8k_mii_write32(bus, 0x10 | r2, r1, ret);
+
++exit:
+ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
--- /dev/null
+From 028f5f8ef44fcf87a456772cbb9f0d90a0a22884 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:55 +0200
+Subject: [PATCH] net: dsa: qca8k: handle error with qca8k_read operation
+
+qca8k_read can fail. Rework any user to handle error values and
+correctly return.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 73 ++++++++++++++++++++++++++++++++---------
+ 1 file changed, 58 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -231,8 +231,13 @@ static int
+ qca8k_regmap_read(void *ctx, uint32_t reg, uint32_t *val)
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
++ int ret;
++
++ ret = qca8k_read(priv, reg);
++ if (ret < 0)
++ return ret;
+
+- *val = qca8k_read(priv, reg);
++ *val = ret;
+
+ return 0;
+ }
+@@ -300,15 +305,20 @@ qca8k_busy_wait(struct qca8k_priv *priv,
+ return ret;
+ }
+
+-static void
++static int
+ qca8k_fdb_read(struct qca8k_priv *priv, struct qca8k_fdb *fdb)
+ {
+- u32 reg[4];
++ u32 reg[4], val;
+ int i;
+
+ /* load the ARL table into an array */
+- for (i = 0; i < 4; i++)
+- reg[i] = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4));
++ for (i = 0; i < 4; i++) {
++ val = qca8k_read(priv, QCA8K_REG_ATU_DATA0 + (i * 4));
++ if (val < 0)
++ return val;
++
++ reg[i] = val;
++ }
+
+ /* vid - 83:72 */
+ fdb->vid = (reg[2] >> QCA8K_ATU_VID_S) & QCA8K_ATU_VID_M;
+@@ -323,6 +333,8 @@ qca8k_fdb_read(struct qca8k_priv *priv,
+ fdb->mac[3] = (reg[0] >> QCA8K_ATU_ADDR3_S) & 0xff;
+ fdb->mac[4] = (reg[0] >> QCA8K_ATU_ADDR4_S) & 0xff;
+ fdb->mac[5] = reg[0] & 0xff;
++
++ return 0;
+ }
+
+ static void
+@@ -374,6 +386,8 @@ qca8k_fdb_access(struct qca8k_priv *priv
+ /* Check for table full violation when adding an entry */
+ if (cmd == QCA8K_FDB_LOAD) {
+ reg = qca8k_read(priv, QCA8K_REG_ATU_FUNC);
++ if (reg < 0)
++ return reg;
+ if (reg & QCA8K_ATU_FUNC_FULL)
+ return -1;
+ }
+@@ -388,10 +402,10 @@ qca8k_fdb_next(struct qca8k_priv *priv,
+
+ qca8k_fdb_write(priv, fdb->vid, fdb->port_mask, fdb->mac, fdb->aging);
+ ret = qca8k_fdb_access(priv, QCA8K_FDB_NEXT, port);
+- if (ret >= 0)
+- qca8k_fdb_read(priv, fdb);
++ if (ret < 0)
++ return ret;
+
+- return ret;
++ return qca8k_fdb_read(priv, fdb);
+ }
+
+ static int
+@@ -449,6 +463,8 @@ qca8k_vlan_access(struct qca8k_priv *pri
+ /* Check for table full violation when adding an entry */
+ if (cmd == QCA8K_VLAN_LOAD) {
+ reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC1);
++ if (reg < 0)
++ return reg;
+ if (reg & QCA8K_VTU_FUNC1_FULL)
+ return -ENOMEM;
+ }
+@@ -475,6 +491,8 @@ qca8k_vlan_add(struct qca8k_priv *priv,
+ goto out;
+
+ reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0);
++ if (reg < 0)
++ return reg;
+ reg |= QCA8K_VTU_FUNC0_VALID | QCA8K_VTU_FUNC0_IVL_EN;
+ reg &= ~(QCA8K_VTU_FUNC0_EG_MODE_MASK << QCA8K_VTU_FUNC0_EG_MODE_S(port));
+ if (untagged)
+@@ -506,6 +524,8 @@ qca8k_vlan_del(struct qca8k_priv *priv,
+ goto out;
+
+ reg = qca8k_read(priv, QCA8K_REG_VTU_FUNC0);
++ if (reg < 0)
++ return reg;
+ reg &= ~(3 << QCA8K_VTU_FUNC0_EG_MODE_S(port));
+ reg |= QCA8K_VTU_FUNC0_EG_MODE_NOT <<
+ QCA8K_VTU_FUNC0_EG_MODE_S(port);
+@@ -621,8 +641,11 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+ QCA8K_MDIO_MASTER_BUSY))
+ return -ETIMEDOUT;
+
+- val = (qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL) &
+- QCA8K_MDIO_MASTER_DATA_MASK);
++ val = qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL);
++ if (val < 0)
++ return val;
++
++ val &= QCA8K_MDIO_MASTER_DATA_MASK;
+
+ return val;
+ }
+@@ -978,6 +1001,8 @@ qca8k_phylink_mac_link_state(struct dsa_
+ u32 reg;
+
+ reg = qca8k_read(priv, QCA8K_REG_PORT_STATUS(port));
++ if (reg < 0)
++ return reg;
+
+ state->link = !!(reg & QCA8K_PORT_STATUS_LINK_UP);
+ state->an_complete = state->link;
+@@ -1078,18 +1103,26 @@ qca8k_get_ethtool_stats(struct dsa_switc
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+ const struct qca8k_mib_desc *mib;
+- u32 reg, i;
++ u32 reg, i, val;
+ u64 hi;
+
+ for (i = 0; i < ARRAY_SIZE(ar8327_mib); i++) {
+ mib = &ar8327_mib[i];
+ reg = QCA8K_PORT_MIB_COUNTER(port) + mib->offset;
+
+- data[i] = qca8k_read(priv, reg);
++ val = qca8k_read(priv, reg);
++ if (val < 0)
++ continue;
++
+ if (mib->size == 2) {
+ hi = qca8k_read(priv, reg + 4);
+- data[i] |= hi << 32;
++ if (hi < 0)
++ continue;
+ }
++
++ data[i] = val;
++ if (mib->size == 2)
++ data[i] |= hi << 32;
+ }
+ }
+
+@@ -1107,18 +1140,25 @@ qca8k_set_mac_eee(struct dsa_switch *ds,
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+ u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
++ int ret = 0;
+ u32 reg;
+
+ mutex_lock(&priv->reg_mutex);
+ reg = qca8k_read(priv, QCA8K_REG_EEE_CTRL);
++ if (reg < 0) {
++ ret = reg;
++ goto exit;
++ }
++
+ if (eee->eee_enabled)
+ reg |= lpi_en;
+ else
+ reg &= ~lpi_en;
+ qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
+- mutex_unlock(&priv->reg_mutex);
+
+- return 0;
++exit:
++ mutex_unlock(&priv->reg_mutex);
++ return ret;
+ }
+
+ static int
+@@ -1456,6 +1496,9 @@ qca8k_sw_probe(struct mdio_device *mdiod
+
+ /* read the switches ID register */
+ id = qca8k_read(priv, QCA8K_REG_MASK_CTRL);
++ if (id < 0)
++ return id;
++
+ id >>= QCA8K_MASK_CTRL_ID_S;
+ id &= QCA8K_MASK_CTRL_ID_M;
+ if (id != QCA8K_ID_QCA8337)
--- /dev/null
+From d7805757c75c76e9518fc1023a29f0c4eed5b581 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:56 +0200
+Subject: [PATCH] net: dsa: qca8k: handle error with qca8k_write operation
+
+qca8k_write can fail. Rework any user to handle error values and
+correctly return.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 102 ++++++++++++++++++++++++++--------------
+ 1 file changed, 67 insertions(+), 35 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -168,7 +168,7 @@ exit:
+ return val;
+ }
+
+-static void
++static int
+ qca8k_write(struct qca8k_priv *priv, u32 reg, u32 val)
+ {
+ struct mii_bus *bus = priv->bus;
+@@ -187,6 +187,7 @@ qca8k_write(struct qca8k_priv *priv, u32
+
+ exit:
+ mutex_unlock(&bus->mdio_lock);
++ return ret;
+ }
+
+ static u32
+@@ -247,9 +248,7 @@ qca8k_regmap_write(void *ctx, uint32_t r
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ctx;
+
+- qca8k_write(priv, reg, val);
+-
+- return 0;
++ return qca8k_write(priv, reg, val);
+ }
+
+ static const struct regmap_range qca8k_readable_ranges[] = {
+@@ -367,6 +366,7 @@ static int
+ qca8k_fdb_access(struct qca8k_priv *priv, enum qca8k_fdb_cmd cmd, int port)
+ {
+ u32 reg;
++ int ret;
+
+ /* Set the command and FDB index */
+ reg = QCA8K_ATU_FUNC_BUSY;
+@@ -377,7 +377,9 @@ qca8k_fdb_access(struct qca8k_priv *priv
+ }
+
+ /* Write the function register triggering the table access */
+- qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++ ret = qca8k_write(priv, QCA8K_REG_ATU_FUNC, reg);
++ if (ret)
++ return ret;
+
+ /* wait for completion */
+ if (qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY))
+@@ -447,6 +449,7 @@ static int
+ qca8k_vlan_access(struct qca8k_priv *priv, enum qca8k_vlan_cmd cmd, u16 vid)
+ {
+ u32 reg;
++ int ret;
+
+ /* Set the command and VLAN index */
+ reg = QCA8K_VTU_FUNC1_BUSY;
+@@ -454,7 +457,9 @@ qca8k_vlan_access(struct qca8k_priv *pri
+ reg |= vid << QCA8K_VTU_FUNC1_VID_S;
+
+ /* Write the function register triggering the table access */
+- qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++ ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC1, reg);
++ if (ret)
++ return ret;
+
+ /* wait for completion */
+ if (qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY))
+@@ -502,7 +507,9 @@ qca8k_vlan_add(struct qca8k_priv *priv,
+ reg |= QCA8K_VTU_FUNC0_EG_MODE_TAG <<
+ QCA8K_VTU_FUNC0_EG_MODE_S(port);
+
+- qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++ ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++ if (ret)
++ return ret;
+ ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+
+ out:
+@@ -545,7 +552,9 @@ qca8k_vlan_del(struct qca8k_priv *priv,
+ if (del) {
+ ret = qca8k_vlan_access(priv, QCA8K_VLAN_PURGE, vid);
+ } else {
+- qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++ ret = qca8k_write(priv, QCA8K_REG_VTU_FUNC0, reg);
++ if (ret)
++ return ret;
+ ret = qca8k_vlan_access(priv, QCA8K_VLAN_LOAD, vid);
+ }
+
+@@ -555,15 +564,20 @@ out:
+ return ret;
+ }
+
+-static void
++static int
+ qca8k_mib_init(struct qca8k_priv *priv)
+ {
++ int ret;
++
+ mutex_lock(&priv->reg_mutex);
+ qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
+ qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
+ qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+- qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
++
++ ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
++
+ mutex_unlock(&priv->reg_mutex);
++ return ret;
+ }
+
+ static void
+@@ -600,6 +614,7 @@ static int
+ qca8k_mdio_write(struct qca8k_priv *priv, int port, u32 regnum, u16 data)
+ {
+ u32 phy, val;
++ int ret;
+
+ if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+ return -EINVAL;
+@@ -613,7 +628,9 @@ qca8k_mdio_write(struct qca8k_priv *priv
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
+ QCA8K_MDIO_MASTER_DATA(data);
+
+- qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
++ ret = qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
++ if (ret)
++ return ret;
+
+ return qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+@@ -623,6 +640,7 @@ static int
+ qca8k_mdio_read(struct qca8k_priv *priv, int port, u32 regnum)
+ {
+ u32 phy, val;
++ int ret;
+
+ if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+ return -EINVAL;
+@@ -635,7 +653,9 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+ QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+
+- qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
++ ret = qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
++ if (ret)
++ return ret;
+
+ if (qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY))
+@@ -766,12 +786,18 @@ qca8k_setup(struct dsa_switch *ds)
+ QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+
+ /* Enable MIB counters */
+- qca8k_mib_init(priv);
++ ret = qca8k_mib_init(priv);
++ if (ret)
++ dev_warn(priv->dev, "mib init failed");
+
+ /* Enable QCA header mode on the cpu port */
+- qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT),
+- QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
+- QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
++ ret = qca8k_write(priv, QCA8K_REG_PORT_HDR_CTRL(QCA8K_CPU_PORT),
++ QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_TX_S |
++ QCA8K_PORT_HDR_CTRL_ALL << QCA8K_PORT_HDR_CTRL_RX_S);
++ if (ret) {
++ dev_err(priv->dev, "failed enabling QCA header mode");
++ return ret;
++ }
+
+ /* Disable forwarding by default on all ports */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++)
+@@ -783,11 +809,13 @@ qca8k_setup(struct dsa_switch *ds)
+ qca8k_port_set_status(priv, i, 0);
+
+ /* Forward all unknown frames to CPU port for Linux processing */
+- qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
+- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
+- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
+- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
+- BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
++ ret = qca8k_write(priv, QCA8K_REG_GLOBAL_FW_CTRL1,
++ BIT(0) << QCA8K_GLOBAL_FW_CTRL1_IGMP_DP_S |
++ BIT(0) << QCA8K_GLOBAL_FW_CTRL1_BC_DP_S |
++ BIT(0) << QCA8K_GLOBAL_FW_CTRL1_MC_DP_S |
++ BIT(0) << QCA8K_GLOBAL_FW_CTRL1_UC_DP_S);
++ if (ret)
++ return ret;
+
+ /* Setup connection between CPU port & user ports */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+@@ -815,16 +843,20 @@ qca8k_setup(struct dsa_switch *ds)
+ qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
+ 0xfff << shift,
+ QCA8K_PORT_VID_DEF << shift);
+- qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+- QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+- QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
++ ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
++ QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
++ QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
++ if (ret)
++ return ret;
+ }
+ }
+
+ /* Setup our port MTUs to match power on defaults */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++)
+ priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
+- qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
++ ret = qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, ETH_FRAME_LEN + ETH_FCS_LEN);
++ if (ret)
++ dev_warn(priv->dev, "failed setting MTU settings");
+
+ /* Flush the FDB table */
+ qca8k_fdb_flush(priv);
+@@ -1140,8 +1172,8 @@ qca8k_set_mac_eee(struct dsa_switch *ds,
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+ u32 lpi_en = QCA8K_REG_EEE_CTRL_LPI_EN(port);
+- int ret = 0;
+ u32 reg;
++ int ret;
+
+ mutex_lock(&priv->reg_mutex);
+ reg = qca8k_read(priv, QCA8K_REG_EEE_CTRL);
+@@ -1154,7 +1186,7 @@ qca8k_set_mac_eee(struct dsa_switch *ds,
+ reg |= lpi_en;
+ else
+ reg &= ~lpi_en;
+- qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
++ ret = qca8k_write(priv, QCA8K_REG_EEE_CTRL, reg);
+
+ exit:
+ mutex_unlock(&priv->reg_mutex);
+@@ -1284,9 +1316,7 @@ qca8k_port_change_mtu(struct dsa_switch
+ mtu = priv->port_mtu[i];
+
+ /* Include L2 header / FCS length */
+- qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN);
+-
+- return 0;
++ return qca8k_write(priv, QCA8K_MAX_FRAME_SIZE, mtu + ETH_HLEN + ETH_FCS_LEN);
+ }
+
+ static int
--- /dev/null
+From aaf421425cbdec4eb6fd75a29e65c2867b0b7bbd Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:57 +0200
+Subject: [PATCH] net: dsa: qca8k: handle error with qca8k_rmw operation
+
+qca8k_rmw can fail. Rework any user to handle error values and
+correctly return. Change qca8k_rmw to return the error code or 0 instead
+of the reg value. The reg returned by qca8k_rmw wasn't used anywhere,
+so this doesn't cause any functional change.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 133 +++++++++++++++++++++++++---------------
+ 1 file changed, 83 insertions(+), 50 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -190,12 +190,13 @@ exit:
+ return ret;
+ }
+
+-static u32
+-qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 val)
++static int
++qca8k_rmw(struct qca8k_priv *priv, u32 reg, u32 mask, u32 write_val)
+ {
+ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+- u32 ret;
++ u32 val;
++ int ret;
+
+ qca8k_split_addr(reg, &r1, &r2, &page);
+
+@@ -205,10 +206,15 @@ qca8k_rmw(struct qca8k_priv *priv, u32 r
+ if (ret < 0)
+ goto exit;
+
+- ret = qca8k_mii_read32(bus, 0x10 | r2, r1);
+- ret &= ~mask;
+- ret |= val;
+- qca8k_mii_write32(bus, 0x10 | r2, r1, ret);
++ val = qca8k_mii_read32(bus, 0x10 | r2, r1);
++ if (val < 0) {
++ ret = val;
++ goto exit;
++ }
++
++ val &= ~mask;
++ val |= write_val;
++ qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+
+ exit:
+ mutex_unlock(&bus->mdio_lock);
+@@ -216,16 +222,16 @@ exit:
+ return ret;
+ }
+
+-static void
++static int
+ qca8k_reg_set(struct qca8k_priv *priv, u32 reg, u32 val)
+ {
+- qca8k_rmw(priv, reg, 0, val);
++ return qca8k_rmw(priv, reg, 0, val);
+ }
+
+-static void
++static int
+ qca8k_reg_clear(struct qca8k_priv *priv, u32 reg, u32 val)
+ {
+- qca8k_rmw(priv, reg, val, 0);
++ return qca8k_rmw(priv, reg, val, 0);
+ }
+
+ static int
+@@ -570,12 +576,19 @@ qca8k_mib_init(struct qca8k_priv *priv)
+ int ret;
+
+ mutex_lock(&priv->reg_mutex);
+- qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
++ ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_FLUSH | QCA8K_MIB_BUSY);
++ if (ret)
++ goto exit;
++
+ qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
+- qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++
++ ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
++ if (ret)
++ goto exit;
+
+ ret = qca8k_write(priv, QCA8K_REG_MODULE_EN, QCA8K_MODULE_EN_MIB);
+
++exit:
+ mutex_unlock(&priv->reg_mutex);
+ return ret;
+ }
+@@ -747,9 +760,8 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
+ * a dt-overlay and driver reload changed the configuration
+ */
+
+- qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_EN);
+- return 0;
++ return qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_EN);
+ }
+
+ priv->ops.phy_read = qca8k_phy_read;
+@@ -782,8 +794,12 @@ qca8k_setup(struct dsa_switch *ds)
+ return ret;
+
+ /* Enable CPU Port */
+- qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
+- QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++ ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
++ QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
++ if (ret) {
++ dev_err(priv->dev, "failed enabling CPU port");
++ return ret;
++ }
+
+ /* Enable MIB counters */
+ ret = qca8k_mib_init(priv);
+@@ -800,9 +816,12 @@ qca8k_setup(struct dsa_switch *ds)
+ }
+
+ /* Disable forwarding by default on all ports */
+- for (i = 0; i < QCA8K_NUM_PORTS; i++)
+- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_MEMBER, 0);
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ QCA8K_PORT_LOOKUP_MEMBER, 0);
++ if (ret)
++ return ret;
++ }
+
+ /* Disable MAC by default on all ports */
+ for (i = 1; i < QCA8K_NUM_PORTS; i++)
+@@ -821,28 +840,37 @@ qca8k_setup(struct dsa_switch *ds)
+ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
+ /* CPU port gets connected to all user ports of the switch */
+ if (dsa_is_cpu_port(ds, i)) {
+- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT),
+- QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(QCA8K_CPU_PORT),
++ QCA8K_PORT_LOOKUP_MEMBER, dsa_user_ports(ds));
++ if (ret)
++ return ret;
+ }
+
+ /* Individual user ports get connected to CPU port only */
+ if (dsa_is_user_port(ds, i)) {
+ int shift = 16 * (i % 2);
+
+- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_MEMBER,
+- BIT(QCA8K_CPU_PORT));
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ QCA8K_PORT_LOOKUP_MEMBER,
++ BIT(QCA8K_CPU_PORT));
++ if (ret)
++ return ret;
+
+ /* Enable ARP Auto-learning by default */
+- qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i),
+- QCA8K_PORT_LOOKUP_LEARN);
++ ret = qca8k_reg_set(priv, QCA8K_PORT_LOOKUP_CTRL(i),
++ QCA8K_PORT_LOOKUP_LEARN);
++ if (ret)
++ return ret;
+
+ /* For port based vlans to work we need to set the
+ * default egress vid
+ */
+- qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
+- 0xfff << shift,
+- QCA8K_PORT_VID_DEF << shift);
++ ret = qca8k_rmw(priv, QCA8K_EGRESS_VLAN(i),
++ 0xfff << shift,
++ QCA8K_PORT_VID_DEF << shift);
++ if (ret)
++ return ret;
++
+ ret = qca8k_write(priv, QCA8K_REG_PORT_VLAN_CTRL0(i),
+ QCA8K_PORT_VLAN_CVID(QCA8K_PORT_VID_DEF) |
+ QCA8K_PORT_VLAN_SVID(QCA8K_PORT_VID_DEF));
+@@ -1234,7 +1262,7 @@ qca8k_port_bridge_join(struct dsa_switch
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+ int port_mask = BIT(QCA8K_CPU_PORT);
+- int i;
++ int i, ret;
+
+ for (i = 1; i < QCA8K_NUM_PORTS; i++) {
+ if (dsa_to_port(ds, i)->bridge_dev != br)
+@@ -1242,17 +1270,20 @@ qca8k_port_bridge_join(struct dsa_switch
+ /* Add this port to the portvlan mask of the other ports
+ * in the bridge
+ */
+- qca8k_reg_set(priv,
+- QCA8K_PORT_LOOKUP_CTRL(i),
+- BIT(port));
++ ret = qca8k_reg_set(priv,
++ QCA8K_PORT_LOOKUP_CTRL(i),
++ BIT(port));
++ if (ret)
++ return ret;
+ if (i != port)
+ port_mask |= BIT(i);
+ }
++
+ /* Add all other ports to this ports portvlan mask */
+- qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
+- QCA8K_PORT_LOOKUP_MEMBER, port_mask);
++ ret = qca8k_rmw(priv, QCA8K_PORT_LOOKUP_CTRL(port),
++ QCA8K_PORT_LOOKUP_MEMBER, port_mask);
+
+- return 0;
++ return ret;
+ }
+
+ static void
--- /dev/null
+From b7c818d194927bdc60ed15db55bb8654496a36b7 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:58 +0200
+Subject: [PATCH] net: dsa: qca8k: handle error from qca8k_busy_wait
+
+Propagate errors from qca8k_busy_wait instead of hardcoding return
+value.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 21 +++++++++++++--------
+ 1 file changed, 13 insertions(+), 8 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -388,8 +388,9 @@ qca8k_fdb_access(struct qca8k_priv *priv
+ return ret;
+
+ /* wait for completion */
+- if (qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY))
+- return -1;
++ ret = qca8k_busy_wait(priv, QCA8K_REG_ATU_FUNC, QCA8K_ATU_FUNC_BUSY);
++ if (ret)
++ return ret;
+
+ /* Check for table full violation when adding an entry */
+ if (cmd == QCA8K_FDB_LOAD) {
+@@ -468,8 +469,9 @@ qca8k_vlan_access(struct qca8k_priv *pri
+ return ret;
+
+ /* wait for completion */
+- if (qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY))
+- return -ETIMEDOUT;
++ ret = qca8k_busy_wait(priv, QCA8K_REG_VTU_FUNC1, QCA8K_VTU_FUNC1_BUSY);
++ if (ret)
++ return ret;
+
+ /* Check for table full violation when adding an entry */
+ if (cmd == QCA8K_VLAN_LOAD) {
+@@ -580,7 +582,9 @@ qca8k_mib_init(struct qca8k_priv *priv)
+ if (ret)
+ goto exit;
+
+- qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
++ ret = qca8k_busy_wait(priv, QCA8K_REG_MIB, QCA8K_MIB_BUSY);
++ if (ret)
++ goto exit;
+
+ ret = qca8k_reg_set(priv, QCA8K_REG_MIB, QCA8K_MIB_CPU_KEEP);
+ if (ret)
+@@ -670,9 +674,10 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+ if (ret)
+ return ret;
+
+- if (qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_BUSY))
+- return -ETIMEDOUT;
++ ret = qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_BUSY);
++ if (ret)
++ return ret;
+
+ val = qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL);
+ if (val < 0)
--- /dev/null
+From 6e82a457e06252b59102486767539cc9c2aba60b Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 22:59:59 +0200
+Subject: [PATCH] net: dsa: qca8k: add support for qca8327 switch
+
+qca8327 switch is a low tier version of the more recent qca8337.
+It does share the same regs used by the qca8k driver and can be
+supported with minimal change.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Vladimir Oltean <olteanv@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 23 ++++++++++++++++++++---
+ drivers/net/dsa/qca8k.h | 6 ++++++
+ 2 files changed, 26 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -1533,6 +1533,7 @@ static const struct dsa_switch_ops qca8k
+ static int
+ qca8k_sw_probe(struct mdio_device *mdiodev)
+ {
++ const struct qca8k_match_data *data;
+ struct qca8k_priv *priv;
+ u32 id;
+
+@@ -1560,6 +1561,11 @@ qca8k_sw_probe(struct mdio_device *mdiod
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+ }
+
++ /* get the switches ID from the compatible */
++ data = of_device_get_match_data(&mdiodev->dev);
++ if (!data)
++ return -ENODEV;
++
+ /* read the switches ID register */
+ id = qca8k_read(priv, QCA8K_REG_MASK_CTRL);
+ if (id < 0)
+@@ -1567,8 +1573,10 @@ qca8k_sw_probe(struct mdio_device *mdiod
+
+ id >>= QCA8K_MASK_CTRL_ID_S;
+ id &= QCA8K_MASK_CTRL_ID_M;
+- if (id != QCA8K_ID_QCA8337)
++ if (id != data->id) {
++ dev_err(&mdiodev->dev, "Switch id detected %x but expected %x", id, data->id);
+ return -ENODEV;
++ }
+
+ priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+ if (!priv->ds)
+@@ -1634,9 +1642,18 @@ static int qca8k_resume(struct device *d
+ static SIMPLE_DEV_PM_OPS(qca8k_pm_ops,
+ qca8k_suspend, qca8k_resume);
+
++static const struct qca8k_match_data qca832x = {
++ .id = QCA8K_ID_QCA8327,
++};
++
++static const struct qca8k_match_data qca833x = {
++ .id = QCA8K_ID_QCA8337,
++};
++
+ static const struct of_device_id qca8k_of_match[] = {
+- { .compatible = "qca,qca8334" },
+- { .compatible = "qca,qca8337" },
++ { .compatible = "qca,qca8327", .data = &qca832x },
++ { .compatible = "qca,qca8334", .data = &qca833x },
++ { .compatible = "qca,qca8337", .data = &qca833x },
+ { /* sentinel */ },
+ };
+
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -15,6 +15,8 @@
+ #define QCA8K_NUM_PORTS 7
+ #define QCA8K_MAX_MTU 9000
+
++#define PHY_ID_QCA8327 0x004dd034
++#define QCA8K_ID_QCA8327 0x12
+ #define PHY_ID_QCA8337 0x004dd036
+ #define QCA8K_ID_QCA8337 0x13
+
+@@ -213,6 +215,10 @@ struct ar8xxx_port_status {
+ int enabled;
+ };
+
++struct qca8k_match_data {
++ u8 id;
++};
++
+ struct qca8k_priv {
+ struct regmap *regmap;
+ struct mii_bus *bus;
--- /dev/null
+From 227a9ffc1bc77037339530607fe129af3824620e Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:00 +0200
+Subject: [PATCH] devicetree: net: dsa: qca8k: Document new compatible qca8327
+
+Add support for qca8327 in the compatible list.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Rob Herring <robh@kernel.org>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/devicetree/bindings/net/dsa/qca8k.txt | 1 +
+ 1 file changed, 1 insertion(+)
+
+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+@@ -3,6 +3,7 @@
+ Required properties:
+
+ - compatible: should be one of:
++ "qca,qca8327"
+ "qca,qca8334"
+ "qca,qca8337"
+
--- /dev/null
+From 83a3ceb39b2495171aabe9446271b94c678354f3 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:01 +0200
+Subject: [PATCH] net: dsa: qca8k: add priority tweak to qca8337 switch
+
+The port 5 of the qca8337 have some problem in flood condition. The
+original legacy driver had some specific buffer and priority settings
+for the different port suggested by the QCA switch team. Add this
+missing settings to improve switch stability under load condition.
+The packet priority tweak is only needed for the qca8337 switch and
+other qca8k switch are not affected.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 47 +++++++++++++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca8k.h | 25 ++++++++++++++++++++++
+ 2 files changed, 72 insertions(+)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -779,6 +779,7 @@ qca8k_setup(struct dsa_switch *ds)
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+ int ret, i;
++ u32 mask;
+
+ /* Make sure that port 0 is the cpu port */
+ if (!dsa_is_cpu_port(ds, 0)) {
+@@ -884,6 +885,51 @@ qca8k_setup(struct dsa_switch *ds)
+ }
+ }
+
++ /* The port 5 of the qca8337 have some problem in flood condition. The
++ * original legacy driver had some specific buffer and priority settings
++ * for the different port suggested by the QCA switch team. Add this
++ * missing settings to improve switch stability under load condition.
++ * This problem is limited to qca8337 and other qca8k switch are not affected.
++ */
++ if (priv->switch_id == QCA8K_ID_QCA8337) {
++ for (i = 0; i < QCA8K_NUM_PORTS; i++) {
++ switch (i) {
++ /* The 2 CPU port and port 5 requires some different
++ * priority than any other ports.
++ */
++ case 0:
++ case 5:
++ case 6:
++ mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI4(0x6) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI5(0x8) |
++ QCA8K_PORT_HOL_CTRL0_EG_PORT(0x1e);
++ break;
++ default:
++ mask = QCA8K_PORT_HOL_CTRL0_EG_PRI0(0x3) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI1(0x4) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI2(0x6) |
++ QCA8K_PORT_HOL_CTRL0_EG_PRI3(0x8) |
++ QCA8K_PORT_HOL_CTRL0_EG_PORT(0x19);
++ }
++ qca8k_write(priv, QCA8K_REG_PORT_HOL_CTRL0(i), mask);
++
++ mask = QCA8K_PORT_HOL_CTRL1_ING(0x6) |
++ QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_WRED_EN;
++ qca8k_rmw(priv, QCA8K_REG_PORT_HOL_CTRL1(i),
++ QCA8K_PORT_HOL_CTRL1_ING_BUF |
++ QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN |
++ QCA8K_PORT_HOL_CTRL1_WRED_EN,
++ mask);
++ }
++ }
++
+ /* Setup our port MTUs to match power on defaults */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++)
+ priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
+@@ -1578,6 +1624,7 @@ qca8k_sw_probe(struct mdio_device *mdiod
+ return -ENODEV;
+ }
+
++ priv->switch_id = id;
+ priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+ if (!priv->ds)
+ return -ENOMEM;
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -168,6 +168,30 @@
+ #define QCA8K_PORT_LOOKUP_STATE GENMASK(18, 16)
+ #define QCA8K_PORT_LOOKUP_LEARN BIT(20)
+
++#define QCA8K_REG_PORT_HOL_CTRL0(_i) (0x970 + (_i) * 0x8)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF GENMASK(3, 0)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI0(x) ((x) << 0)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI1_BUF GENMASK(7, 4)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI1(x) ((x) << 4)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI2_BUF GENMASK(11, 8)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI2(x) ((x) << 8)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI3_BUF GENMASK(15, 12)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI3(x) ((x) << 12)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI4_BUF GENMASK(19, 16)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI4(x) ((x) << 16)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI5_BUF GENMASK(23, 20)
++#define QCA8K_PORT_HOL_CTRL0_EG_PRI5(x) ((x) << 20)
++#define QCA8K_PORT_HOL_CTRL0_EG_PORT_BUF GENMASK(29, 24)
++#define QCA8K_PORT_HOL_CTRL0_EG_PORT(x) ((x) << 24)
++
++#define QCA8K_REG_PORT_HOL_CTRL1(_i) (0x974 + (_i) * 0x8)
++#define QCA8K_PORT_HOL_CTRL1_ING_BUF GENMASK(3, 0)
++#define QCA8K_PORT_HOL_CTRL1_ING(x) ((x) << 0)
++#define QCA8K_PORT_HOL_CTRL1_EG_PRI_BUF_EN BIT(6)
++#define QCA8K_PORT_HOL_CTRL1_EG_PORT_BUF_EN BIT(7)
++#define QCA8K_PORT_HOL_CTRL1_WRED_EN BIT(8)
++#define QCA8K_PORT_HOL_CTRL1_EG_MIRROR_EN BIT(16)
++
+ /* Pkt edit registers */
+ #define QCA8K_EGRESS_VLAN(x) (0x0c70 + (4 * (x / 2)))
+
+@@ -220,6 +244,7 @@ struct qca8k_match_data {
+ };
+
+ struct qca8k_priv {
++ u8 switch_id;
+ struct regmap *regmap;
+ struct mii_bus *bus;
+ struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
--- /dev/null
+From 5bf9ff3b9fb5ecb67a1a3517b26db3a00f2a2f11 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:02 +0200
+Subject: [PATCH] net: dsa: qca8k: limit port5 delay to qca8337
+
+Limit port5 rx delay to qca8337. This is taken from the legacy QSDK code
+that limits the rx delay on port5 to only this particular switch version,
+on other switch only the tx and rx delay for port0 are needed.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 6 ++++--
+ 1 file changed, 4 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -1003,8 +1003,10 @@ qca8k_phylink_mac_config(struct dsa_swit
+ QCA8K_PORT_PAD_RGMII_EN |
+ QCA8K_PORT_PAD_RGMII_TX_DELAY(QCA8K_MAX_DELAY) |
+ QCA8K_PORT_PAD_RGMII_RX_DELAY(QCA8K_MAX_DELAY));
+- qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
+- QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
++ /* QCA8337 requires to set rgmii rx delay */
++ if (priv->switch_id == QCA8K_ID_QCA8337)
++ qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
++ QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
+ break;
+ case PHY_INTERFACE_MODE_SGMII:
+ case PHY_INTERFACE_MODE_1000BASEX:
--- /dev/null
+From 0fc57e4b5e39461fc0a54aae0afe4241363a7267 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:03 +0200
+Subject: [PATCH] net: dsa: qca8k: add GLOBAL_FC settings needed for qca8327
+
+Switch qca8327 needs special settings for the GLOBAL_FC_THRES regs.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 10 ++++++++++
+ drivers/net/dsa/qca8k.h | 6 ++++++
+ 2 files changed, 16 insertions(+)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -930,6 +930,16 @@ qca8k_setup(struct dsa_switch *ds)
+ }
+ }
+
++ /* Special GLOBAL_FC_THRESH value are needed for ar8327 switch */
++ if (priv->switch_id == QCA8K_ID_QCA8327) {
++ mask = QCA8K_GLOBAL_FC_GOL_XON_THRES(288) |
++ QCA8K_GLOBAL_FC_GOL_XOFF_THRES(496);
++ qca8k_rmw(priv, QCA8K_REG_GLOBAL_FC_THRESH,
++ QCA8K_GLOBAL_FC_GOL_XON_THRES_S |
++ QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S,
++ mask);
++ }
++
+ /* Setup our port MTUs to match power on defaults */
+ for (i = 0; i < QCA8K_NUM_PORTS; i++)
+ priv->port_mtu[i] = ETH_FRAME_LEN + ETH_FCS_LEN;
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -168,6 +168,12 @@
+ #define QCA8K_PORT_LOOKUP_STATE GENMASK(18, 16)
+ #define QCA8K_PORT_LOOKUP_LEARN BIT(20)
+
++#define QCA8K_REG_GLOBAL_FC_THRESH 0x800
++#define QCA8K_GLOBAL_FC_GOL_XON_THRES(x) ((x) << 16)
++#define QCA8K_GLOBAL_FC_GOL_XON_THRES_S GENMASK(24, 16)
++#define QCA8K_GLOBAL_FC_GOL_XOFF_THRES(x) ((x) << 0)
++#define QCA8K_GLOBAL_FC_GOL_XOFF_THRES_S GENMASK(8, 0)
++
+ #define QCA8K_REG_PORT_HOL_CTRL0(_i) (0x970 + (_i) * 0x8)
+ #define QCA8K_PORT_HOL_CTRL0_EG_PRI0_BUF GENMASK(3, 0)
+ #define QCA8K_PORT_HOL_CTRL0_EG_PRI0(x) ((x) << 0)
--- /dev/null
+From 95ffeaf18b3bb90eeef52cbf7d79ccc9d0345ff5 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:04 +0200
+Subject: [PATCH] net: dsa: qca8k: add support for switch rev
+
+qca8k internal phy driver require some special debug value to be set
+based on the switch revision. Rework the switch id read function to
+also read the chip revision.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 53 ++++++++++++++++++++++++++---------------
+ drivers/net/dsa/qca8k.h | 7 ++++--
+ 2 files changed, 39 insertions(+), 21 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -1588,12 +1588,40 @@ static const struct dsa_switch_ops qca8k
+ .phylink_mac_link_up = qca8k_phylink_mac_link_up,
+ };
+
++static int qca8k_read_switch_id(struct qca8k_priv *priv)
++{
++ const struct qca8k_match_data *data;
++ u32 val;
++ u8 id;
++
++ /* get the switches ID from the compatible */
++ data = of_device_get_match_data(priv->dev);
++ if (!data)
++ return -ENODEV;
++
++ val = qca8k_read(priv, QCA8K_REG_MASK_CTRL);
++ if (val < 0)
++ return -ENODEV;
++
++ id = QCA8K_MASK_CTRL_DEVICE_ID(val & QCA8K_MASK_CTRL_DEVICE_ID_MASK);
++ if (id != data->id) {
++ dev_err(priv->dev, "Switch id detected %x but expected %x", id, data->id);
++ return -ENODEV;
++ }
++
++ priv->switch_id = id;
++
++ /* Save revision to communicate to the internal PHY driver */
++ priv->switch_revision = (val & QCA8K_MASK_CTRL_REV_ID_MASK);
++
++ return 0;
++}
++
+ static int
+ qca8k_sw_probe(struct mdio_device *mdiodev)
+ {
+- const struct qca8k_match_data *data;
+ struct qca8k_priv *priv;
+- u32 id;
++ int ret;
+
+ /* allocate the private data struct so that we can probe the switches
+ * ID register
+@@ -1619,24 +1647,11 @@ qca8k_sw_probe(struct mdio_device *mdiod
+ gpiod_set_value_cansleep(priv->reset_gpio, 0);
+ }
+
+- /* get the switches ID from the compatible */
+- data = of_device_get_match_data(&mdiodev->dev);
+- if (!data)
+- return -ENODEV;
++ /* Check the detected switch id */
++ ret = qca8k_read_switch_id(priv);
++ if (ret)
++ return ret;
+
+- /* read the switches ID register */
+- id = qca8k_read(priv, QCA8K_REG_MASK_CTRL);
+- if (id < 0)
+- return id;
+-
+- id >>= QCA8K_MASK_CTRL_ID_S;
+- id &= QCA8K_MASK_CTRL_ID_M;
+- if (id != data->id) {
+- dev_err(&mdiodev->dev, "Switch id detected %x but expected %x", id, data->id);
+- return -ENODEV;
+- }
+-
+- priv->switch_id = id;
+ priv->ds = devm_kzalloc(&mdiodev->dev, sizeof(*priv->ds), GFP_KERNEL);
+ if (!priv->ds)
+ return -ENOMEM;
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -30,8 +30,10 @@
+
+ /* Global control registers */
+ #define QCA8K_REG_MASK_CTRL 0x000
+-#define QCA8K_MASK_CTRL_ID_M 0xff
+-#define QCA8K_MASK_CTRL_ID_S 8
++#define QCA8K_MASK_CTRL_REV_ID_MASK GENMASK(7, 0)
++#define QCA8K_MASK_CTRL_REV_ID(x) ((x) >> 0)
++#define QCA8K_MASK_CTRL_DEVICE_ID_MASK GENMASK(15, 8)
++#define QCA8K_MASK_CTRL_DEVICE_ID(x) ((x) >> 8)
+ #define QCA8K_REG_PORT0_PAD_CTRL 0x004
+ #define QCA8K_REG_PORT5_PAD_CTRL 0x008
+ #define QCA8K_REG_PORT6_PAD_CTRL 0x00c
+@@ -251,6 +253,7 @@ struct qca8k_match_data {
+
+ struct qca8k_priv {
+ u8 switch_id;
++ u8 switch_revision;
+ struct regmap *regmap;
+ struct mii_bus *bus;
+ struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
--- /dev/null
+From 1ee0591a1093c2448642c33433483e9260275f7b Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:05 +0200
+Subject: [PATCH] net: dsa: qca8k: add ethernet-ports fallback to
+ setup_mdio_bus
+
+Dsa now also supports ethernet-ports. Add this new binding as a fallback
+if the ports node can't be found.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -719,6 +719,9 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
+
+ ports = of_get_child_by_name(priv->dev->of_node, "ports");
+ if (!ports)
++ ports = of_get_child_by_name(priv->dev->of_node, "ethernet-ports");
++
++ if (!ports)
+ return -EINVAL;
+
+ for_each_available_child_of_node(ports, port) {
--- /dev/null
+From e4b9977cee1583da38a6e9118078bb728aaccf7b Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:06 +0200
+Subject: [PATCH] net: dsa: qca8k: make rgmii delay configurable
+
+The legacy qsdk code used a different delay instead of the max value.
+Qsdk use 1 ns for rx and 2 ns for tx. Make these values configurable
+using the standard rx/tx-internal-delay-ps ethernet binding and apply
+qsdk values by default. The connected gmac doesn't add any delay so no
+additional delay is added to tx/rx.
+On this switch the delay is actually in ns so value should be in the
+1000 order. Any value converted from ps to ns by dividing it by 1000
+as the switch max value for delay is 3ns.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 82 ++++++++++++++++++++++++++++++++++++++++-
+ drivers/net/dsa/qca8k.h | 11 +++---
+ 2 files changed, 86 insertions(+), 7 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -778,6 +778,68 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
+ }
+
+ static int
++qca8k_setup_of_rgmii_delay(struct qca8k_priv *priv)
++{
++ struct device_node *port_dn;
++ phy_interface_t mode;
++ struct dsa_port *dp;
++ u32 val;
++
++ /* CPU port is already checked */
++ dp = dsa_to_port(priv->ds, 0);
++
++ port_dn = dp->dn;
++
++ /* Check if port 0 is set to the correct type */
++ of_get_phy_mode(port_dn, &mode);
++ if (mode != PHY_INTERFACE_MODE_RGMII_ID &&
++ mode != PHY_INTERFACE_MODE_RGMII_RXID &&
++ mode != PHY_INTERFACE_MODE_RGMII_TXID) {
++ return 0;
++ }
++
++ switch (mode) {
++ case PHY_INTERFACE_MODE_RGMII_ID:
++ case PHY_INTERFACE_MODE_RGMII_RXID:
++ if (of_property_read_u32(port_dn, "rx-internal-delay-ps", &val))
++ val = 2;
++ else
++ /* Switch regs accept value in ns, convert ps to ns */
++ val = val / 1000;
++
++ if (val > QCA8K_MAX_DELAY) {
++ dev_err(priv->dev, "rgmii rx delay is limited to a max value of 3ns, setting to the max value");
++ val = 3;
++ }
++
++ priv->rgmii_rx_delay = val;
++ /* Stop here if we need to check only for rx delay */
++ if (mode != PHY_INTERFACE_MODE_RGMII_ID)
++ break;
++
++ fallthrough;
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ if (of_property_read_u32(port_dn, "tx-internal-delay-ps", &val))
++ val = 1;
++ else
++ /* Switch regs accept value in ns, convert ps to ns */
++ val = val / 1000;
++
++ if (val > QCA8K_MAX_DELAY) {
++ dev_err(priv->dev, "rgmii tx delay is limited to a max value of 3ns, setting to the max value");
++ val = 3;
++ }
++
++ priv->rgmii_tx_delay = val;
++ break;
++ default:
++ return 0;
++ }
++
++ return 0;
++}
++
++static int
+ qca8k_setup(struct dsa_switch *ds)
+ {
+ struct qca8k_priv *priv = (struct qca8k_priv *)ds->priv;
+@@ -802,6 +864,10 @@ qca8k_setup(struct dsa_switch *ds)
+ if (ret)
+ return ret;
+
++ ret = qca8k_setup_of_rgmii_delay(priv);
++ if (ret)
++ return ret;
++
+ /* Enable CPU Port */
+ ret = qca8k_reg_set(priv, QCA8K_REG_GLOBAL_FW_CTRL0,
+ QCA8K_GLOBAL_FW_CTRL0_CPU_PORT_EN);
+@@ -970,6 +1036,8 @@ qca8k_phylink_mac_config(struct dsa_swit
+ case 0: /* 1st CPU port */
+ if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+ state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+ state->interface != PHY_INTERFACE_MODE_SGMII)
+ return;
+
+@@ -985,6 +1053,8 @@ qca8k_phylink_mac_config(struct dsa_swit
+ case 6: /* 2nd CPU port / external PHY */
+ if (state->interface != PHY_INTERFACE_MODE_RGMII &&
+ state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+ state->interface != PHY_INTERFACE_MODE_SGMII &&
+ state->interface != PHY_INTERFACE_MODE_1000BASEX)
+ return;
+@@ -1008,14 +1078,18 @@ qca8k_phylink_mac_config(struct dsa_swit
+ qca8k_write(priv, reg, QCA8K_PORT_PAD_RGMII_EN);
+ break;
+ case PHY_INTERFACE_MODE_RGMII_ID:
++ case PHY_INTERFACE_MODE_RGMII_TXID:
++ case PHY_INTERFACE_MODE_RGMII_RXID:
+ /* RGMII_ID needs internal delay. This is enabled through
+ * PORT5_PAD_CTRL for all ports, rather than individual port
+ * registers
+ */
+ qca8k_write(priv, reg,
+ QCA8K_PORT_PAD_RGMII_EN |
+- QCA8K_PORT_PAD_RGMII_TX_DELAY(QCA8K_MAX_DELAY) |
+- QCA8K_PORT_PAD_RGMII_RX_DELAY(QCA8K_MAX_DELAY));
++ QCA8K_PORT_PAD_RGMII_TX_DELAY(priv->rgmii_tx_delay) |
++ QCA8K_PORT_PAD_RGMII_RX_DELAY(priv->rgmii_rx_delay) |
++ QCA8K_PORT_PAD_RGMII_TX_DELAY_EN |
++ QCA8K_PORT_PAD_RGMII_RX_DELAY_EN);
+ /* QCA8337 requires to set rgmii rx delay */
+ if (priv->switch_id == QCA8K_ID_QCA8337)
+ qca8k_write(priv, QCA8K_REG_PORT5_PAD_CTRL,
+@@ -1073,6 +1147,8 @@ qca8k_phylink_validate(struct dsa_switch
+ if (state->interface != PHY_INTERFACE_MODE_NA &&
+ state->interface != PHY_INTERFACE_MODE_RGMII &&
+ state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+ state->interface != PHY_INTERFACE_MODE_SGMII)
+ goto unsupported;
+ break;
+@@ -1090,6 +1166,8 @@ qca8k_phylink_validate(struct dsa_switch
+ if (state->interface != PHY_INTERFACE_MODE_NA &&
+ state->interface != PHY_INTERFACE_MODE_RGMII &&
+ state->interface != PHY_INTERFACE_MODE_RGMII_ID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_TXID &&
++ state->interface != PHY_INTERFACE_MODE_RGMII_RXID &&
+ state->interface != PHY_INTERFACE_MODE_SGMII &&
+ state->interface != PHY_INTERFACE_MODE_1000BASEX)
+ goto unsupported;
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -38,12 +38,11 @@
+ #define QCA8K_REG_PORT5_PAD_CTRL 0x008
+ #define QCA8K_REG_PORT6_PAD_CTRL 0x00c
+ #define QCA8K_PORT_PAD_RGMII_EN BIT(26)
+-#define QCA8K_PORT_PAD_RGMII_TX_DELAY(x) \
+- ((0x8 + (x & 0x3)) << 22)
+-#define QCA8K_PORT_PAD_RGMII_RX_DELAY(x) \
+- ((0x10 + (x & 0x3)) << 20)
+-#define QCA8K_MAX_DELAY 3
++#define QCA8K_PORT_PAD_RGMII_TX_DELAY(x) ((x) << 22)
++#define QCA8K_PORT_PAD_RGMII_RX_DELAY(x) ((x) << 20)
++#define QCA8K_PORT_PAD_RGMII_TX_DELAY_EN BIT(25)
+ #define QCA8K_PORT_PAD_RGMII_RX_DELAY_EN BIT(24)
++#define QCA8K_MAX_DELAY 3
+ #define QCA8K_PORT_PAD_SGMII_EN BIT(7)
+ #define QCA8K_REG_PWS 0x010
+ #define QCA8K_PWS_SERDES_AEN_DIS BIT(7)
+@@ -254,6 +253,8 @@ struct qca8k_match_data {
+ struct qca8k_priv {
+ u8 switch_id;
+ u8 switch_revision;
++ u8 rgmii_tx_delay;
++ u8 rgmii_rx_delay;
+ struct regmap *regmap;
+ struct mii_bus *bus;
+ struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
--- /dev/null
+From 63c33bbfeb6842a956a0eb12901e28eb335bdb18 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:07 +0200
+Subject: [PATCH] net: dsa: qca8k: clear MASTER_EN after phy read/write
+
+Clear MDIO_MASTER_EN bit from MDIO_MASTER_CTRL after read/write
+operation. The MDIO_MASTER_EN bit is not reset after read/write
+operation and the next operation can be wrongly interpreted by the
+switch as a mdio operation. This cause a production of wrong/garbage
+data from the switch and underfined bheavior. (random port drop,
+unplugged port flagged with link up, wrong port speed)
+Also on driver remove the MASTER_CTRL can be left set and cause the
+malfunction of any next driver using the mdio device.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 14 ++++++++++++--
+ 1 file changed, 12 insertions(+), 2 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -649,8 +649,14 @@ qca8k_mdio_write(struct qca8k_priv *priv
+ if (ret)
+ return ret;
+
+- return qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_BUSY);
++ ret = qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_BUSY);
++
++ /* even if the busy_wait timeouts try to clear the MASTER_EN */
++ qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_EN);
++
++ return ret;
+ }
+
+ static int
+@@ -685,6 +691,10 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+
+ val &= QCA8K_MDIO_MASTER_DATA_MASK;
+
++ /* even if the busy_wait timeouts try to clear the MASTER_EN */
++ qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_EN);
++
+ return val;
+ }
+
--- /dev/null
+From 60df02b6ea4581d72eb7a3ab7204504a54059b72 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:08 +0200
+Subject: [PATCH] net: dsa: qca8k: dsa: qca8k: protect MASTER busy_wait with
+ mdio mutex
+
+MDIO_MASTER operation have a dedicated busy wait that is not protected
+by the mdio mutex. This can cause situation where the MASTER operation
+is done and a normal operation is executed between the MASTER read/write
+and the MASTER busy_wait. Rework the qca8k_mdio_read/write function to
+address this issue by binding the lock for the whole MASTER operation
+and not only the mdio read/write common operation.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 68 +++++++++++++++++++++++++++++++++--------
+ 1 file changed, 55 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -628,8 +628,31 @@ qca8k_port_to_phy(int port)
+ }
+
+ static int
++qca8k_mdio_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++{
++ u16 r1, r2, page;
++ u32 val;
++ int ret;
++
++ qca8k_split_addr(reg, &r1, &r2, &page);
++
++ ret = read_poll_timeout(qca8k_mii_read32, val, !(val & mask), 0,
++ QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++ priv->bus, 0x10 | r2, r1);
++
++ /* Check if qca8k_read has failed for a different reason
++ * before returnting -ETIMEDOUT
++ */
++ if (ret < 0 && val < 0)
++ return val;
++
++ return ret;
++}
++
++static int
+ qca8k_mdio_write(struct qca8k_priv *priv, int port, u32 regnum, u16 data)
+ {
++ u16 r1, r2, page;
+ u32 phy, val;
+ int ret;
+
+@@ -645,12 +668,21 @@ qca8k_mdio_write(struct qca8k_priv *priv
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
+ QCA8K_MDIO_MASTER_DATA(data);
+
+- ret = qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
++ qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++
++ mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++
++ ret = qca8k_set_page(priv->bus, page);
+ if (ret)
+- return ret;
++ goto exit;
++
++ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
+
+- ret = qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_BUSY);
++ ret = qca8k_mdio_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_BUSY);
++
++exit:
++ mutex_unlock(&priv->bus->mdio_lock);
+
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+ qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
+@@ -662,6 +694,7 @@ qca8k_mdio_write(struct qca8k_priv *priv
+ static int
+ qca8k_mdio_read(struct qca8k_priv *priv, int port, u32 regnum)
+ {
++ u16 r1, r2, page;
+ u32 phy, val;
+ int ret;
+
+@@ -676,21 +709,30 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+ QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+
+- ret = qca8k_write(priv, QCA8K_MDIO_MASTER_CTRL, val);
+- if (ret)
+- return ret;
++ qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
++
++ mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- ret = qca8k_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_BUSY);
++ ret = qca8k_set_page(priv->bus, page);
+ if (ret)
+- return ret;
++ goto exit;
+
+- val = qca8k_read(priv, QCA8K_MDIO_MASTER_CTRL);
+- if (val < 0)
+- return val;
++ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
++
++ ret = qca8k_mdio_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ QCA8K_MDIO_MASTER_BUSY);
++ if (ret)
++ goto exit;
+
++ val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
+ val &= QCA8K_MDIO_MASTER_DATA_MASK;
+
++exit:
++ mutex_unlock(&priv->bus->mdio_lock);
++
++ if (val >= 0)
++ val &= QCA8K_MDIO_MASTER_DATA_MASK;
++
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+ qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_EN);
--- /dev/null
+From 617960d72e93de0f3fa52407e2d39e8c43e73b0a Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:09 +0200
+Subject: [PATCH] net: dsa: qca8k: enlarge mdio delay and timeout
+
+The witch require some extra delay after setting page or the next
+read/write can use still use the old page. Add a delay after the
+set_page function to address this as it's done in QSDK legacy driver.
+Some timeouts were notice with VLAN and phy function, enlarge the
+mdio busy wait timeout to fix these problems.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 1 +
+ drivers/net/dsa/qca8k.h | 2 +-
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -143,6 +143,7 @@ qca8k_set_page(struct mii_bus *bus, u16
+ }
+
+ qca8k_current_page = page;
++ usleep_range(1000, 2000);
+ return 0;
+ }
+
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -20,7 +20,7 @@
+ #define PHY_ID_QCA8337 0x004dd036
+ #define QCA8K_ID_QCA8337 0x13
+
+-#define QCA8K_BUSY_WAIT_TIMEOUT 20
++#define QCA8K_BUSY_WAIT_TIMEOUT 2000
+
+ #define QCA8K_NUM_FDB_RECORDS 2048
+
--- /dev/null
+From 759bafb8a3226326ca357613bc90acf738f80c32 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:10 +0200
+Subject: [PATCH] net: dsa: qca8k: add support for internal phy and internal
+ mdio
+
+Add support to setup_mdio_bus for internal phy declaration. Introduce a
+flag to use the legacy port phy mapping by default and use the direct
+mapping if a mdio node is detected in the switch node. Register a
+dedicated mdio internal mdio bus to address the different mapping
+between port and phy if the mdio node is detected.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 112 +++++++++++++++++++++++++++++-----------
+ drivers/net/dsa/qca8k.h | 1 +
+ 2 files changed, 83 insertions(+), 30 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -11,6 +11,7 @@
+ #include <linux/netdevice.h>
+ #include <net/dsa.h>
+ #include <linux/of_net.h>
++#include <linux/of_mdio.h>
+ #include <linux/of_platform.h>
+ #include <linux/if_bridge.h>
+ #include <linux/mdio.h>
+@@ -629,7 +630,7 @@ qca8k_port_to_phy(int port)
+ }
+
+ static int
+-qca8k_mdio_busy_wait(struct qca8k_priv *priv, u32 reg, u32 mask)
++qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+ {
+ u16 r1, r2, page;
+ u32 val;
+@@ -639,7 +640,7 @@ qca8k_mdio_busy_wait(struct qca8k_priv *
+
+ ret = read_poll_timeout(qca8k_mii_read32, val, !(val & mask), 0,
+ QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
+- priv->bus, 0x10 | r2, r1);
++ bus, 0x10 | r2, r1);
+
+ /* Check if qca8k_read has failed for a different reason
+ * before returnting -ETIMEDOUT
+@@ -651,19 +652,16 @@ qca8k_mdio_busy_wait(struct qca8k_priv *
+ }
+
+ static int
+-qca8k_mdio_write(struct qca8k_priv *priv, int port, u32 regnum, u16 data)
++qca8k_mdio_write(struct mii_bus *salve_bus, int phy, int regnum, u16 data)
+ {
++ struct qca8k_priv *priv = salve_bus->priv;
+ u16 r1, r2, page;
+- u32 phy, val;
++ u32 val;
+ int ret;
+
+ if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+ return -EINVAL;
+
+- /* callee is responsible for not passing bad ports,
+- * but we still would like to make spills impossible.
+- */
+- phy = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+ val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+ QCA8K_MDIO_MASTER_WRITE | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum) |
+@@ -679,33 +677,29 @@ qca8k_mdio_write(struct qca8k_priv *priv
+
+ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
+
+- ret = qca8k_mdio_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ ret = qca8k_mdio_busy_wait(priv->bus, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+
+ exit:
+- mutex_unlock(&priv->bus->mdio_lock);
+-
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+- qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_EN);
++ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, 0);
++
++ mutex_unlock(&priv->bus->mdio_lock);
+
+ return ret;
+ }
+
+ static int
+-qca8k_mdio_read(struct qca8k_priv *priv, int port, u32 regnum)
++qca8k_mdio_read(struct mii_bus *salve_bus, int phy, int regnum)
+ {
++ struct qca8k_priv *priv = salve_bus->priv;
+ u16 r1, r2, page;
+- u32 phy, val;
++ u32 val;
+ int ret;
+
+ if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
+ return -EINVAL;
+
+- /* callee is responsible for not passing bad ports,
+- * but we still would like to make spills impossible.
+- */
+- phy = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
+ val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
+ QCA8K_MDIO_MASTER_READ | QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
+ QCA8K_MDIO_MASTER_REG_ADDR(regnum);
+@@ -720,24 +714,22 @@ qca8k_mdio_read(struct qca8k_priv *priv,
+
+ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
+
+- ret = qca8k_mdio_busy_wait(priv, QCA8K_MDIO_MASTER_CTRL,
++ ret = qca8k_mdio_busy_wait(priv->bus, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+ if (ret)
+ goto exit;
+
+ val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
+- val &= QCA8K_MDIO_MASTER_DATA_MASK;
+
+ exit:
++ /* even if the busy_wait timeouts try to clear the MASTER_EN */
++ qca8k_mii_write32(priv->bus, 0x10 | r2, r1, 0);
++
+ mutex_unlock(&priv->bus->mdio_lock);
+
+ if (val >= 0)
+ val &= QCA8K_MDIO_MASTER_DATA_MASK;
+
+- /* even if the busy_wait timeouts try to clear the MASTER_EN */
+- qca8k_reg_clear(priv, QCA8K_MDIO_MASTER_CTRL,
+- QCA8K_MDIO_MASTER_EN);
+-
+ return val;
+ }
+
+@@ -746,7 +738,14 @@ qca8k_phy_write(struct dsa_switch *ds, i
+ {
+ struct qca8k_priv *priv = ds->priv;
+
+- return qca8k_mdio_write(priv, port, regnum, data);
++ /* Check if the legacy mapping should be used and the
++ * port is not correctly mapped to the right PHY in the
++ * devicetree
++ */
++ if (priv->legacy_phy_port_mapping)
++ port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++
++ return qca8k_mdio_write(priv->bus, port, regnum, data);
+ }
+
+ static int
+@@ -755,7 +754,14 @@ qca8k_phy_read(struct dsa_switch *ds, in
+ struct qca8k_priv *priv = ds->priv;
+ int ret;
+
+- ret = qca8k_mdio_read(priv, port, regnum);
++ /* Check if the legacy mapping should be used and the
++ * port is not correctly mapped to the right PHY in the
++ * devicetree
++ */
++ if (priv->legacy_phy_port_mapping)
++ port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++
++ ret = qca8k_mdio_read(priv->bus, port, regnum);
+
+ if (ret < 0)
+ return 0xffff;
+@@ -764,10 +770,37 @@ qca8k_phy_read(struct dsa_switch *ds, in
+ }
+
+ static int
++qca8k_mdio_register(struct qca8k_priv *priv, struct device_node *mdio)
++{
++ struct dsa_switch *ds = priv->ds;
++ struct mii_bus *bus;
++
++ bus = devm_mdiobus_alloc(ds->dev);
++
++ if (!bus)
++ return -ENOMEM;
++
++ bus->priv = (void *)priv;
++ bus->name = "qca8k slave mii";
++ bus->read = qca8k_mdio_read;
++ bus->write = qca8k_mdio_write;
++ snprintf(bus->id, MII_BUS_ID_SIZE, "qca8k-%d",
++ ds->index);
++
++ bus->parent = ds->dev;
++ bus->phy_mask = ~ds->phys_mii_mask;
++
++ ds->slave_mii_bus = bus;
++
++ return devm_of_mdiobus_register(priv->dev, bus, mdio);
++}
++
++static int
+ qca8k_setup_mdio_bus(struct qca8k_priv *priv)
+ {
+ u32 internal_mdio_mask = 0, external_mdio_mask = 0, reg;
+- struct device_node *ports, *port;
++ struct device_node *ports, *port, *mdio;
++ phy_interface_t mode;
+ int err;
+
+ ports = of_get_child_by_name(priv->dev->of_node, "ports");
+@@ -788,7 +821,10 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
+ if (!dsa_is_user_port(priv->ds, reg))
+ continue;
+
+- if (of_property_read_bool(port, "phy-handle"))
++ of_get_phy_mode(port, &mode);
++
++ if (of_property_read_bool(port, "phy-handle") &&
++ mode != PHY_INTERFACE_MODE_INTERNAL)
+ external_mdio_mask |= BIT(reg);
+ else
+ internal_mdio_mask |= BIT(reg);
+@@ -825,8 +861,23 @@ qca8k_setup_mdio_bus(struct qca8k_priv *
+ QCA8K_MDIO_MASTER_EN);
+ }
+
++ /* Check if the devicetree declare the port:phy mapping */
++ mdio = of_get_child_by_name(priv->dev->of_node, "mdio");
++ if (of_device_is_available(mdio)) {
++ err = qca8k_mdio_register(priv, mdio);
++ if (err)
++ of_node_put(mdio);
++
++ return err;
++ }
++
++ /* If a mapping can't be found the legacy mapping is used,
++ * using the qca8k_port_to_phy function
++ */
++ priv->legacy_phy_port_mapping = true;
+ priv->ops.phy_read = qca8k_phy_read;
+ priv->ops.phy_write = qca8k_phy_write;
++
+ return 0;
+ }
+
+@@ -1212,7 +1263,8 @@ qca8k_phylink_validate(struct dsa_switch
+ case 5:
+ /* Internal PHY */
+ if (state->interface != PHY_INTERFACE_MODE_NA &&
+- state->interface != PHY_INTERFACE_MODE_GMII)
++ state->interface != PHY_INTERFACE_MODE_GMII &&
++ state->interface != PHY_INTERFACE_MODE_INTERNAL)
+ goto unsupported;
+ break;
+ case 6: /* 2nd CPU port / external PHY */
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -255,6 +255,7 @@ struct qca8k_priv {
+ u8 switch_revision;
+ u8 rgmii_tx_delay;
+ u8 rgmii_rx_delay;
++ bool legacy_phy_port_mapping;
+ struct regmap *regmap;
+ struct mii_bus *bus;
+ struct ar8xxx_port_status port_sts[QCA8K_NUM_PORTS];
--- /dev/null
+From 0c994a28e7518f098c84a3049cb2915780db873a Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:11 +0200
+Subject: [PATCH] devicetree: bindings: dsa: qca8k: Document internal mdio
+ definition
+
+Document new way of declare mapping of internal PHY to port.
+The new implementation directly declare the PHY connected to the port
+by adding a node in the switch node. The driver detect this and register
+an internal mdiobus using the mapping defined in the mdio node.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Rob Herring <robh@kernel.org>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ .../devicetree/bindings/net/dsa/qca8k.txt | 39 +++++++++++++++++++
+ 1 file changed, 39 insertions(+)
+
+--- a/Documentation/devicetree/bindings/net/dsa/qca8k.txt
++++ b/Documentation/devicetree/bindings/net/dsa/qca8k.txt
+@@ -21,6 +21,10 @@ described in dsa/dsa.txt. If the QCA8K s
+ mdio-bus each subnode describing a port needs to have a valid phandle
+ referencing the internal PHY it is connected to. This is because there's no
+ N:N mapping of port and PHY id.
++To declare the internal mdio-bus configuration, declare a mdio node in the
++switch node and declare the phandle for the port referencing the internal
++PHY is connected to. In this config a internal mdio-bus is registered and
++the mdio MASTER is used as communication.
+
+ Don't use mixed external and internal mdio-bus configurations, as this is
+ not supported by the hardware.
+@@ -150,26 +154,61 @@ for the internal master mdio-bus configu
+ port@1 {
+ reg = <1>;
+ label = "lan1";
++ phy-mode = "internal";
++ phy-handle = <&phy_port1>;
+ };
+
+ port@2 {
+ reg = <2>;
+ label = "lan2";
++ phy-mode = "internal";
++ phy-handle = <&phy_port2>;
+ };
+
+ port@3 {
+ reg = <3>;
+ label = "lan3";
++ phy-mode = "internal";
++ phy-handle = <&phy_port3>;
+ };
+
+ port@4 {
+ reg = <4>;
+ label = "lan4";
++ phy-mode = "internal";
++ phy-handle = <&phy_port4>;
+ };
+
+ port@5 {
+ reg = <5>;
+ label = "wan";
++ phy-mode = "internal";
++ phy-handle = <&phy_port5>;
++ };
++ };
++
++ mdio {
++ #address-cells = <1>;
++ #size-cells = <0>;
++
++ phy_port1: phy@0 {
++ reg = <0>;
++ };
++
++ phy_port2: phy@1 {
++ reg = <1>;
++ };
++
++ phy_port3: phy@2 {
++ reg = <2>;
++ };
++
++ phy_port4: phy@3 {
++ reg = <3>;
++ };
++
++ phy_port5: phy@4 {
++ reg = <4>;
+ };
+ };
+ };
--- /dev/null
+From b7ebac354d54f1657bb89b7a7ca149db50203e6a Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:12 +0200
+Subject: [PATCH] net: dsa: qca8k: improve internal mdio read/write bus access
+
+Improve the internal mdio read/write bus access by caching the value
+without accessing it for every read/write.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 28 +++++++++++++++-------------
+ 1 file changed, 15 insertions(+), 13 deletions(-)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -655,6 +655,7 @@ static int
+ qca8k_mdio_write(struct mii_bus *salve_bus, int phy, int regnum, u16 data)
+ {
+ struct qca8k_priv *priv = salve_bus->priv;
++ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ u32 val;
+ int ret;
+@@ -669,22 +670,22 @@ qca8k_mdio_write(struct mii_bus *salve_b
+
+ qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- ret = qca8k_set_page(priv->bus, page);
++ ret = qca8k_set_page(bus, page);
+ if (ret)
+ goto exit;
+
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+
+- ret = qca8k_mdio_busy_wait(priv->bus, QCA8K_MDIO_MASTER_CTRL,
++ ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+
+ exit:
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, 0);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ mutex_unlock(&bus->mdio_lock);
+
+ return ret;
+ }
+@@ -693,6 +694,7 @@ static int
+ qca8k_mdio_read(struct mii_bus *salve_bus, int phy, int regnum)
+ {
+ struct qca8k_priv *priv = salve_bus->priv;
++ struct mii_bus *bus = priv->bus;
+ u16 r1, r2, page;
+ u32 val;
+ int ret;
+@@ -706,26 +708,26 @@ qca8k_mdio_read(struct mii_bus *salve_bu
+
+ qca8k_split_addr(QCA8K_MDIO_MASTER_CTRL, &r1, &r2, &page);
+
+- mutex_lock_nested(&priv->bus->mdio_lock, MDIO_MUTEX_NESTED);
++ mutex_lock_nested(&bus->mdio_lock, MDIO_MUTEX_NESTED);
+
+- ret = qca8k_set_page(priv->bus, page);
++ ret = qca8k_set_page(bus, page);
+ if (ret)
+ goto exit;
+
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, val);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, val);
+
+- ret = qca8k_mdio_busy_wait(priv->bus, QCA8K_MDIO_MASTER_CTRL,
++ ret = qca8k_mdio_busy_wait(bus, QCA8K_MDIO_MASTER_CTRL,
+ QCA8K_MDIO_MASTER_BUSY);
+ if (ret)
+ goto exit;
+
+- val = qca8k_mii_read32(priv->bus, 0x10 | r2, r1);
++ val = qca8k_mii_read32(bus, 0x10 | r2, r1);
+
+ exit:
+ /* even if the busy_wait timeouts try to clear the MASTER_EN */
+- qca8k_mii_write32(priv->bus, 0x10 | r2, r1, 0);
++ qca8k_mii_write32(bus, 0x10 | r2, r1, 0);
+
+- mutex_unlock(&priv->bus->mdio_lock);
++ mutex_unlock(&bus->mdio_lock);
+
+ if (val >= 0)
+ val &= QCA8K_MDIO_MASTER_DATA_MASK;
--- /dev/null
+From a46aec02bc06ac2c33f326339e4ef88c735dc30d Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:13 +0200
+Subject: [PATCH] net: dsa: qca8k: pass switch_revision info to phy dev_flags
+
+Define get_phy_flags to pass switch_Revision needed to tweak the
+internal PHY with debug values based on the revision.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -1740,6 +1740,22 @@ qca8k_port_vlan_del(struct dsa_switch *d
+ return ret;
+ }
+
++static u32 qca8k_get_phy_flags(struct dsa_switch *ds, int port)
++{
++ struct qca8k_priv *priv = ds->priv;
++
++ /* Communicate to the phy internal driver the switch revision.
++ * Based on the switch revision different values needs to be
++ * set to the dbg and mmd reg on the phy.
++ * The first 2 bit are used to communicate the switch revision
++ * to the phy driver.
++ */
++ if (port > 0 && port < 6)
++ return priv->switch_revision;
++
++ return 0;
++}
++
+ static enum dsa_tag_protocol
+ qca8k_get_tag_protocol(struct dsa_switch *ds, int port,
+ enum dsa_tag_protocol mp)
+@@ -1774,6 +1790,7 @@ static const struct dsa_switch_ops qca8k
+ .phylink_mac_config = qca8k_phylink_mac_config,
+ .phylink_mac_link_down = qca8k_phylink_mac_link_down,
+ .phylink_mac_link_up = qca8k_phylink_mac_link_up,
++ .get_phy_flags = qca8k_get_phy_flags,
+ };
+
+ static int qca8k_read_switch_id(struct qca8k_priv *priv)
--- /dev/null
+From 272833b9b3b3969be7a91839121d86662c8c4253 Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Fri, 14 May 2021 23:00:15 +0200
+Subject: [PATCH] net: phy: add support for qca8k switch internal PHY in at803x
+
+Since the at803x share the same regs, it's assumed they are based on the
+same implementation. Make it part of the at803x PHY driver to skip
+having redudant code.
+Add initial support for qca8k internal PHYs. The internal PHYs requires
+special mmd and debug values to be set based on the switch revision
+passwd using the dev_flags. Supports output of idle, receive and eee_wake
+errors stats.
+Some debug values sets can't be translated as the documentation lacks any
+reference about them.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 5 +-
+ drivers/net/phy/at803x.c | 132 ++++++++++++++++++++++++++++++++++++++-
+ 2 files changed, 134 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -235,10 +235,11 @@ config NXP_TJA11XX_PHY
+ Currently supports the NXP TJA1100 and TJA1101 PHY.
+
+ config AT803X_PHY
+- tristate "Qualcomm Atheros AR803X PHYs"
++ tristate "Qualcomm Atheros AR803X PHYs and QCA833x PHYs"
+ depends on REGULATOR
+ help
+- Currently supports the AR8030, AR8031, AR8033 and AR8035 model
++ Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
++ QCA8337(Internal qca8k PHY) model
+
+ config QSEMI_PHY
+ tristate "Quality Semiconductor PHYs"
+--- a/drivers/net/phy/at803x.c
++++ b/drivers/net/phy/at803x.c
+@@ -92,10 +92,16 @@
+ #define AT803X_DEBUG_REG_5 0x05
+ #define AT803X_DEBUG_TX_CLK_DLY_EN BIT(8)
+
++#define AT803X_DEBUG_REG_3C 0x3C
++
++#define AT803X_DEBUG_REG_3D 0x3D
++
+ #define AT803X_DEBUG_REG_1F 0x1F
+ #define AT803X_DEBUG_PLL_ON BIT(2)
+ #define AT803X_DEBUG_RGMII_1V8 BIT(3)
+
++#define MDIO_AZ_DEBUG 0x800D
++
+ /* AT803x supports either the XTAL input pad, an internal PLL or the
+ * DSP as clock reference for the clock output pad. The XTAL reference
+ * is only used for 25 MHz output, all other frequencies need the PLL.
+@@ -142,10 +148,34 @@
+ #define AT803X_PAGE_FIBER 0
+ #define AT803X_PAGE_COPPER 1
+
++#define QCA8327_PHY_ID 0x004dd034
++#define QCA8337_PHY_ID 0x004dd036
++#define QCA8K_PHY_ID_MASK 0xffffffff
++
++#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0)
++
+ MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver");
+ MODULE_AUTHOR("Matus Ujhelyi");
+ MODULE_LICENSE("GPL");
+
++enum stat_access_type {
++ PHY,
++ MMD
++};
++
++struct at803x_hw_stat {
++ const char *string;
++ u8 reg;
++ u32 mask;
++ enum stat_access_type access_type;
++};
++
++static struct at803x_hw_stat at803x_hw_stats[] = {
++ { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY},
++ { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY},
++ { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD},
++};
++
+ struct at803x_priv {
+ int flags;
+ #define AT803X_KEEP_PLL_ENABLED BIT(0) /* don't turn off internal PLL */
+@@ -154,6 +184,7 @@ struct at803x_priv {
+ struct regulator_dev *vddio_rdev;
+ struct regulator_dev *vddh_rdev;
+ struct regulator *vddio;
++ u64 stats[ARRAY_SIZE(at803x_hw_stats)];
+ };
+
+ struct at803x_context {
+@@ -165,6 +196,17 @@ struct at803x_context {
+ u16 led_control;
+ };
+
++static int at803x_debug_reg_write(struct phy_device *phydev, u16 reg, u16 data)
++{
++ int ret;
++
++ ret = phy_write(phydev, AT803X_DEBUG_ADDR, reg);
++ if (ret < 0)
++ return ret;
++
++ return phy_write(phydev, AT803X_DEBUG_DATA, data);
++}
++
+ static int at803x_debug_reg_read(struct phy_device *phydev, u16 reg)
+ {
+ int ret;
+@@ -327,6 +369,53 @@ static void at803x_get_wol(struct phy_de
+ wol->wolopts |= WAKE_MAGIC;
+ }
+
++static int at803x_get_sset_count(struct phy_device *phydev)
++{
++ return ARRAY_SIZE(at803x_hw_stats);
++}
++
++static void at803x_get_strings(struct phy_device *phydev, u8 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++) {
++ strscpy(data + i * ETH_GSTRING_LEN,
++ at803x_hw_stats[i].string, ETH_GSTRING_LEN);
++ }
++}
++
++static u64 at803x_get_stat(struct phy_device *phydev, int i)
++{
++ struct at803x_hw_stat stat = at803x_hw_stats[i];
++ struct at803x_priv *priv = phydev->priv;
++ int val;
++ u64 ret;
++
++ if (stat.access_type == MMD)
++ val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg);
++ else
++ val = phy_read(phydev, stat.reg);
++
++ if (val < 0) {
++ ret = U64_MAX;
++ } else {
++ val = val & stat.mask;
++ priv->stats[i] += val;
++ ret = priv->stats[i];
++ }
++
++ return ret;
++}
++
++static void at803x_get_stats(struct phy_device *phydev,
++ struct ethtool_stats *stats, u64 *data)
++{
++ int i;
++
++ for (i = 0; i < ARRAY_SIZE(at803x_hw_stats); i++)
++ data[i] = at803x_get_stat(phydev, i);
++}
++
+ static int at803x_suspend(struct phy_device *phydev)
+ {
+ int value;
+@@ -1102,6 +1191,34 @@ static int at803x_cable_test_start(struc
+ return 0;
+ }
+
++static int qca83xx_config_init(struct phy_device *phydev)
++{
++ u8 switch_revision;
++
++ switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK;
++
++ switch (switch_revision) {
++ case 1:
++ /* For 100M waveform */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_0, 0x02ea);
++ /* Turn on Gigabit clock */
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x68a0);
++ break;
++
++ case 2:
++ phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0);
++ fallthrough;
++ case 4:
++ phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f);
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3D, 0x6860);
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_5, 0x2c46);
++ at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000);
++ break;
++ }
++
++ return 0;
++}
++
+ static struct phy_driver at803x_driver[] = {
+ {
+ /* Qualcomm Atheros AR8035 */
+@@ -1198,7 +1315,20 @@ static struct phy_driver at803x_driver[]
+ .read_status = at803x_read_status,
+ .soft_reset = genphy_soft_reset,
+ .config_aneg = at803x_config_aneg,
+-} };
++}, {
++ /* QCA8337 */
++ .phy_id = QCA8337_PHY_ID,
++ .phy_id_mask = QCA8K_PHY_ID_MASK,
++ .name = "QCA PHY 8337",
++ /* PHY_GBIT_FEATURES */
++ .probe = at803x_probe,
++ .flags = PHY_IS_INTERNAL,
++ .config_init = qca83xx_config_init,
++ .soft_reset = genphy_soft_reset,
++ .get_sset_count = at803x_get_sset_count,
++ .get_strings = at803x_get_strings,
++ .get_stats = at803x_get_stats,
++}, };
+
+ module_phy_driver(at803x_driver);
+
--- a/drivers/net/phy/at803x.c
+++ b/drivers/net/phy/at803x.c
-@@ -935,6 +935,34 @@ static int at803x_set_tunable(struct phy
+@@ -1024,6 +1024,34 @@ static int at803x_set_tunable(struct phy
}
}
static int at803x_cable_test_result_trans(u16 status)
{
switch (FIELD_GET(AT803X_CDT_STATUS_STAT_MASK, status)) {
-@@ -1156,7 +1184,7 @@ static struct phy_driver at803x_driver[]
+@@ -1273,7 +1301,7 @@ static struct phy_driver at803x_driver[]
.resume = at803x_resume,
.read_page = at803x_read_page,
.write_page = at803x_write_page,
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -314,6 +314,12 @@ config AT803X_PHY
- help
- Currently supports the AR8030, AR8031, AR8033 and AR8035 model
+@@ -315,6 +315,12 @@ config AT803X_PHY
+ Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
+ QCA8337(Internal qca8k PHY) model
+config QCA807X_PHY
+ tristate "Qualcomm QCA807X PHYs"