generic: 5.15: qca8k: add kernel version tag on backport patch
[openwrt/staging/svanheule.git] / target / linux / generic / backport-5.15 / 766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
diff --git a/target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch b/target/linux/generic/backport-5.15/766-v5.18-12-net-dsa-qca8k-add-support-for-phy-read-write-with-mg.patch
new file mode 100644 (file)
index 0000000..f5899eb
--- /dev/null
@@ -0,0 +1,287 @@
+From 2cd5485663847d468dc207b3ff85fb1fab44d97f Mon Sep 17 00:00:00 2001
+From: Ansuel Smith <ansuelsmth@gmail.com>
+Date: Wed, 2 Feb 2022 01:03:31 +0100
+Subject: [PATCH 12/16] net: dsa: qca8k: add support for phy read/write with
+ mgmt Ethernet
+
+Use mgmt Ethernet also for phy read/write if availabale. Use a different
+seq number to make sure we receive the correct packet.
+On any error, we fallback to the legacy mdio read/write.
+
+Signed-off-by: Ansuel Smith <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca8k.c | 216 ++++++++++++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca8k.h |   1 +
+ 2 files changed, 217 insertions(+)
+
+--- a/drivers/net/dsa/qca8k.c
++++ b/drivers/net/dsa/qca8k.c
+@@ -867,6 +867,199 @@ qca8k_port_set_status(struct qca8k_priv
+               regmap_clear_bits(priv->regmap, QCA8K_REG_PORT_STATUS(port), mask);
+ }
++static int
++qca8k_phy_eth_busy_wait(struct qca8k_mgmt_eth_data *mgmt_eth_data,
++                      struct sk_buff *read_skb, u32 *val)
++{
++      struct sk_buff *skb = skb_copy(read_skb, GFP_KERNEL);
++      bool ack;
++      int ret;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the copy pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        QCA8K_ETHERNET_TIMEOUT);
++
++      ack = mgmt_eth_data->ack;
++
++      if (ret <= 0)
++              return -ETIMEDOUT;
++
++      if (!ack)
++              return -EINVAL;
++
++      *val = mgmt_eth_data->data[0];
++
++      return 0;
++}
++
++static int
++qca8k_phy_eth_command(struct qca8k_priv *priv, bool read, int phy,
++                    int regnum, u16 data)
++{
++      struct sk_buff *write_skb, *clear_skb, *read_skb;
++      struct qca8k_mgmt_eth_data *mgmt_eth_data;
++      u32 write_val, clear_val = 0, val;
++      struct net_device *mgmt_master;
++      int ret, ret1;
++      bool ack;
++
++      if (regnum >= QCA8K_MDIO_MASTER_MAX_REG)
++              return -EINVAL;
++
++      mgmt_eth_data = &priv->mgmt_eth_data;
++
++      write_val = QCA8K_MDIO_MASTER_BUSY | QCA8K_MDIO_MASTER_EN |
++                  QCA8K_MDIO_MASTER_PHY_ADDR(phy) |
++                  QCA8K_MDIO_MASTER_REG_ADDR(regnum);
++
++      if (read) {
++              write_val |= QCA8K_MDIO_MASTER_READ;
++      } else {
++              write_val |= QCA8K_MDIO_MASTER_WRITE;
++              write_val |= QCA8K_MDIO_MASTER_DATA(data);
++      }
++
++      /* Prealloc all the needed skb before the lock */
++      write_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++                                          &write_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb)
++              return -ENOMEM;
++
++      clear_skb = qca8k_alloc_mdio_header(MDIO_WRITE, QCA8K_MDIO_MASTER_CTRL,
++                                          &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb) {
++              ret = -ENOMEM;
++              goto err_clear_skb;
++      }
++
++      read_skb = qca8k_alloc_mdio_header(MDIO_READ, QCA8K_MDIO_MASTER_CTRL,
++                                         &clear_val, QCA8K_ETHERNET_PHY_PRIORITY);
++      if (!write_skb) {
++              ret = -ENOMEM;
++              goto err_read_skb;
++      }
++
++      /* Actually start the request:
++       * 1. Send mdio master packet
++       * 2. Busy Wait for mdio master command
++       * 3. Get the data if we are reading
++       * 4. Reset the mdio master (even with error)
++       */
++      mutex_lock(&mgmt_eth_data->mutex);
++
++      /* Check if mgmt_master is operational */
++      mgmt_master = priv->mgmt_master;
++      if (!mgmt_master) {
++              mutex_unlock(&mgmt_eth_data->mutex);
++              ret = -EINVAL;
++              goto err_mgmt_master;
++      }
++
++      read_skb->dev = mgmt_master;
++      clear_skb->dev = mgmt_master;
++      write_skb->dev = mgmt_master;
++
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the write pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(write_skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(write_skb);
++
++      ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                        QCA8K_ETHERNET_TIMEOUT);
++
++      ack = mgmt_eth_data->ack;
++
++      if (ret <= 0) {
++              ret = -ETIMEDOUT;
++              kfree_skb(read_skb);
++              goto exit;
++      }
++
++      if (!ack) {
++              ret = -EINVAL;
++              kfree_skb(read_skb);
++              goto exit;
++      }
++
++      ret = read_poll_timeout(qca8k_phy_eth_busy_wait, ret1,
++                              !(val & QCA8K_MDIO_MASTER_BUSY), 0,
++                              QCA8K_BUSY_WAIT_TIMEOUT * USEC_PER_MSEC, false,
++                              mgmt_eth_data, read_skb, &val);
++
++      if (ret < 0 && ret1 < 0) {
++              ret = ret1;
++              goto exit;
++      }
++
++      if (read) {
++              reinit_completion(&mgmt_eth_data->rw_done);
++
++              /* Increment seq_num and set it in the read pkt */
++              mgmt_eth_data->seq++;
++              qca8k_mdio_header_fill_seq_num(read_skb, mgmt_eth_data->seq);
++              mgmt_eth_data->ack = false;
++
++              dev_queue_xmit(read_skb);
++
++              ret = wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                                QCA8K_ETHERNET_TIMEOUT);
++
++              ack = mgmt_eth_data->ack;
++
++              if (ret <= 0) {
++                      ret = -ETIMEDOUT;
++                      goto exit;
++              }
++
++              if (!ack) {
++                      ret = -EINVAL;
++                      goto exit;
++              }
++
++              ret = mgmt_eth_data->data[0] & QCA8K_MDIO_MASTER_DATA_MASK;
++      } else {
++              kfree_skb(read_skb);
++      }
++exit:
++      reinit_completion(&mgmt_eth_data->rw_done);
++
++      /* Increment seq_num and set it in the clear pkt */
++      mgmt_eth_data->seq++;
++      qca8k_mdio_header_fill_seq_num(clear_skb, mgmt_eth_data->seq);
++      mgmt_eth_data->ack = false;
++
++      dev_queue_xmit(clear_skb);
++
++      wait_for_completion_timeout(&mgmt_eth_data->rw_done,
++                                  QCA8K_ETHERNET_TIMEOUT);
++
++      mutex_unlock(&mgmt_eth_data->mutex);
++
++      return ret;
++
++      /* Error handling before lock */
++err_mgmt_master:
++      kfree_skb(read_skb);
++err_read_skb:
++      kfree_skb(clear_skb);
++err_clear_skb:
++      kfree_skb(write_skb);
++
++      return ret;
++}
++
+ static u32
+ qca8k_port_to_phy(int port)
+ {
+@@ -989,6 +1182,12 @@ qca8k_internal_mdio_write(struct mii_bus
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+       struct mii_bus *bus = priv->bus;
++      int ret;
++
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, false, phy, regnum, data);
++      if (!ret)
++              return 0;
+       return qca8k_mdio_write(bus, phy, regnum, data);
+ }
+@@ -998,6 +1197,12 @@ qca8k_internal_mdio_read(struct mii_bus
+ {
+       struct qca8k_priv *priv = slave_bus->priv;
+       struct mii_bus *bus = priv->bus;
++      int ret;
++
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, true, phy, regnum, 0);
++      if (ret >= 0)
++              return ret;
+       return qca8k_mdio_read(bus, phy, regnum);
+ }
+@@ -1006,6 +1211,7 @@ static int
+ qca8k_phy_write(struct dsa_switch *ds, int port, int regnum, u16 data)
+ {
+       struct qca8k_priv *priv = ds->priv;
++      int ret;
+       /* Check if the legacy mapping should be used and the
+        * port is not correctly mapped to the right PHY in the
+@@ -1014,6 +1220,11 @@ qca8k_phy_write(struct dsa_switch *ds, i
+       if (priv->legacy_phy_port_mapping)
+               port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, false, port, regnum, 0);
++      if (!ret)
++              return ret;
++
+       return qca8k_mdio_write(priv->bus, port, regnum, data);
+ }
+@@ -1030,6 +1241,11 @@ qca8k_phy_read(struct dsa_switch *ds, in
+       if (priv->legacy_phy_port_mapping)
+               port = qca8k_port_to_phy(port) % PHY_MAX_ADDR;
++      /* Use mdio Ethernet when available, fallback to legacy one on error */
++      ret = qca8k_phy_eth_command(priv, true, port, regnum, 0);
++      if (ret >= 0)
++              return ret;
++
+       ret = qca8k_mdio_read(priv->bus, port, regnum);
+       if (ret < 0)
+--- a/drivers/net/dsa/qca8k.h
++++ b/drivers/net/dsa/qca8k.h
+@@ -14,6 +14,7 @@
+ #include <linux/dsa/tag_qca.h>
+ #define QCA8K_ETHERNET_MDIO_PRIORITY                  7
++#define QCA8K_ETHERNET_PHY_PRIORITY                   6
+ #define QCA8K_ETHERNET_TIMEOUT                                100
+ #define QCA8K_NUM_PORTS                                       7