From: Christian Marangi <[email protected]>

The Airoha AN7583 SoC have 2 dedicated MDIO controller in the SCU
register map. Add a dedicated driver for it ported from linux kernel.

Signed-off-by: Christian Marangi <[email protected]>
---
 drivers/net/Kconfig       |   7 ++
 drivers/net/Makefile      |   1 +
 drivers/net/mdio-airoha.c | 221 ++++++++++++++++++++++++++++++++++++++
 3 files changed, 229 insertions(+)
 create mode 100644 drivers/net/mdio-airoha.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index faa08e9ebba..d2515a98ebf 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -118,6 +118,13 @@ config AG7XXX
          This driver supports the Atheros AG7xxx Ethernet MAC. This MAC is
          present in the Atheros AR7xxx, AR9xxx and QCA9xxx MIPS chips.
 
+config MDIO_AIROHA
+       bool "Airoha AN7583 MDIO interface support"
+       depends on DM_MDIO
+       help
+         This driver supports the MDIO interface found in Airoha
+         AN7583 SoC.
+
 source "drivers/net/airoha/Kconfig"
 
 config AIROHA_ETH
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 4c1f4094ab2..ad5f6dbd434 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -63,6 +63,7 @@ obj-$(CONFIG_KSZ9477) += ksz9477.o
 obj-$(CONFIG_LITEETH) += liteeth.o
 obj-$(CONFIG_MACB) += macb.o
 obj-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o
+obj-$(CONFIG_MDIO_AIROHA) += mdio-airoha.o
 obj-$(CONFIG_MDIO_GPIO_BITBANG) += mdio_gpio.o
 obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o
 obj-$(CONFIG_MDIO_MSCC_MIIM) += mdio-mscc-miim.o
diff --git a/drivers/net/mdio-airoha.c b/drivers/net/mdio-airoha.c
new file mode 100644
index 00000000000..ac1fb13622e
--- /dev/null
+++ b/drivers/net/mdio-airoha.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Airoha AN7583 MDIO interface driver
+ *
+ * Copyright (C) 2025 Christian Marangi <[email protected]>
+ */
+
+#include <clk.h>
+#include <dm.h>
+#include <dm/ofnode.h>
+#include <linux/delay.h>
+#include <linux/bitfield.h>
+#include <linux/time.h>
+#include <miiphy.h>
+#include <syscon.h>
+#include <regmap.h>
+#include <reset.h>
+
+/* MII address register definitions */
+#define   AN7583_MII_BUSY                      BIT(31)
+#define   AN7583_MII_RDY                       BIT(30) /* RO signal BUS is 
ready */
+#define   AN7583_MII_CL22_REG_ADDR             GENMASK(29, 25)
+#define   AN7583_MII_CL45_DEV_ADDR             AN7583_MII_CL22_REG_ADDR
+#define   AN7583_MII_PHY_ADDR                  GENMASK(24, 20)
+#define   AN7583_MII_CMD                       GENMASK(19, 18)
+#define   AN7583_MII_CMD_CL22_WRITE            
FIELD_PREP_CONST(AN7583_MII_CMD, 0x1)
+#define   AN7583_MII_CMD_CL22_READ             
FIELD_PREP_CONST(AN7583_MII_CMD, 0x2)
+#define   AN7583_MII_CMD_CL45_ADDR             
FIELD_PREP_CONST(AN7583_MII_CMD, 0x0)
+#define   AN7583_MII_CMD_CL45_WRITE            
FIELD_PREP_CONST(AN7583_MII_CMD, 0x1)
+#define   AN7583_MII_CMD_CL45_POSTREAD_INCADDR 
FIELD_PREP_CONST(AN7583_MII_CMD, 0x2)
+#define   AN7583_MII_CMD_CL45_READ             
FIELD_PREP_CONST(AN7583_MII_CMD, 0x3)
+#define   AN7583_MII_ST                                GENMASK(17, 16)
+#define   AN7583_MII_ST_CL45                   FIELD_PREP_CONST(AN7583_MII_ST, 
0x0)
+#define   AN7583_MII_ST_CL22                   FIELD_PREP_CONST(AN7583_MII_ST, 
0x1)
+#define   AN7583_MII_RWDATA                    GENMASK(15, 0)
+#define   AN7583_MII_CL45_REG_ADDR             AN7583_MII_RWDATA
+
+#define AN7583_MII_MDIO_DELAY_USEC             100
+#define AN7583_MII_MDIO_RETRY_MSEC             100
+
+struct airoha_mdio_data {
+       u32 base_addr;
+       struct regmap *regmap;
+       struct clk *clk;
+       struct reset_ctl *reset;
+};
+
+static int airoha_mdio_wait_busy(struct airoha_mdio_data *priv)
+{
+       u32 busy;
+
+       return regmap_read_poll_timeout(priv->regmap, priv->base_addr, busy,
+                                       !(busy & AN7583_MII_BUSY),
+                                       AN7583_MII_MDIO_DELAY_USEC,
+                                       AN7583_MII_MDIO_RETRY_MSEC * 
USEC_PER_MSEC);
+}
+
+static void airoha_mdio_reset(struct airoha_mdio_data *priv)
+{
+       /* There seems to be Hardware bug where AN7583_MII_RWDATA
+        * is not wiped in the context of unconnected PHY and the
+        * previous read value is returned.
+        *
+        * Example: (only one PHY on the BUS at 0x1f)
+        *  - read at 0x1f report at 0x2 0x7500
+        *  - read at 0x0 report 0x7500 on every address
+        *
+        * To workaround this, we reset the Mdio BUS at every read
+        * to have consistent values on read operation.
+        */
+       reset_assert(priv->reset);
+       reset_deassert(priv->reset);
+}
+
+static int airoha_mdio_read(struct udevice *dev, int addr, int devnum,
+                           int regnum)
+{
+       struct airoha_mdio_data *priv = dev_get_priv(dev);
+       u32 val;
+       int ret;
+
+       airoha_mdio_reset(priv);
+
+       if (devnum != MDIO_DEVAD_NONE) {
+               val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 |
+                     AN7583_MII_CMD_CL45_ADDR;
+               val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr);
+               val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum);
+               val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum);
+
+               ret = regmap_write(priv->regmap, priv->base_addr, val);
+               if (ret)
+                       return ret;
+
+               ret = airoha_mdio_wait_busy(priv);
+               if (ret)
+                       return ret;
+       }
+
+       val = AN7583_MII_BUSY | FIELD_PREP(AN7583_MII_PHY_ADDR, addr);
+       if (devnum != MDIO_DEVAD_NONE) {
+               val |= AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_READ;
+               val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum);
+       } else {
+               val |= AN7583_MII_ST_CL22 | AN7583_MII_CMD_CL22_READ;
+               val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum);
+       }
+
+       ret = regmap_write(priv->regmap, priv->base_addr, val);
+       if (ret)
+               return ret;
+
+       ret = airoha_mdio_wait_busy(priv);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(priv->regmap, priv->base_addr, &val);
+       if (ret)
+               return ret;
+
+       return FIELD_GET(AN7583_MII_RWDATA, val);
+}
+
+static int airoha_mdio_write(struct udevice *dev, int addr, int devnum,
+                            int regnum, u16 value)
+{
+       struct airoha_mdio_data *priv = dev_get_priv(dev);
+       u32 val;
+       int ret;
+
+       if (devnum != MDIO_DEVAD_NONE) {
+               val = AN7583_MII_BUSY | AN7583_MII_ST_CL45 |
+                     AN7583_MII_CMD_CL45_ADDR;
+               val |= FIELD_PREP(AN7583_MII_PHY_ADDR, addr);
+               val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum);
+               val |= FIELD_PREP(AN7583_MII_CL45_REG_ADDR, regnum);
+
+               ret = regmap_write(priv->regmap, priv->base_addr, val);
+               if (ret)
+                       return ret;
+
+               ret = airoha_mdio_wait_busy(priv);
+               if (ret)
+                       return ret;
+       }
+
+       val = AN7583_MII_BUSY | FIELD_PREP(AN7583_MII_PHY_ADDR, addr) |
+             FIELD_PREP(AN7583_MII_RWDATA, value);
+       if (devnum != MDIO_DEVAD_NONE) {
+               val |= AN7583_MII_ST_CL45 | AN7583_MII_CMD_CL45_WRITE;
+               val |= FIELD_PREP(AN7583_MII_CL45_DEV_ADDR, devnum);
+       } else {
+               val |= AN7583_MII_ST_CL22 | AN7583_MII_CMD_CL22_WRITE;
+               val |= FIELD_PREP(AN7583_MII_CL22_REG_ADDR, regnum);
+       }
+
+       ret = regmap_write(priv->regmap, priv->base_addr, val);
+       if (ret)
+               return ret;
+
+       ret = airoha_mdio_wait_busy(priv);
+
+       return ret;
+}
+
+static const struct mdio_ops airoha_mdio_ops = {
+       .read = airoha_mdio_read,
+       .write = airoha_mdio_write,
+};
+
+static int airoha_mdio_probe(struct udevice *dev)
+{
+       struct airoha_mdio_data *priv = dev_get_priv(dev);
+       ofnode ofnode = dev_ofnode(dev);
+       u32 addr, freq;
+       int ret;
+
+       ret = dev_read_u32(dev, "reg", &addr);
+       if (ret)
+               return ret;
+
+       priv->base_addr = addr;
+       priv->regmap = syscon_node_to_regmap(ofnode_get_parent(ofnode));
+
+       priv->clk = devm_clk_get(dev, NULL);
+       if (IS_ERR(priv->clk))
+               return PTR_ERR(priv->clk);
+
+       priv->reset = devm_reset_control_get(dev, NULL);
+       if (IS_ERR(priv->reset))
+               return PTR_ERR(priv->reset);
+
+       reset_deassert(priv->reset);
+
+       /* Check if a custom frequency is defined in DT or default to 2.5 MHz */
+       if (dev_read_u32(dev, "clock-frequency", &freq))
+               freq = 2500000;
+
+       ret = clk_enable(priv->clk);
+       if (ret)
+               return ret;
+
+       ret = clk_set_rate(priv->clk, freq);
+       if (ret < 0)
+               return ret;
+
+       return 0;
+}
+
+static const struct udevice_id airoha_mdio_dt_ids[] = {
+       { .compatible = "airoha,an7583-mdio" },
+       { }
+};
+
+U_BOOT_DRIVER(airoha_mdio) = {
+       .name           = "airoha-mdio",
+       .id             = UCLASS_MDIO,
+       .of_match       = airoha_mdio_dt_ids,
+       .probe          = airoha_mdio_probe,
+       .ops            = &airoha_mdio_ops,
+       .priv_auto      = sizeof(struct airoha_mdio_data),
+};
-- 
2.53.0

Reply via email to