Linux DTS compatible MDIO bitbanging driver.
Both clause 22 and clause 45 MDIO supported and validated.

Heavily based on the Linux drivers (more or less the same code base).

Signed-off-by: Markus Gothe <markus.go...@genexis.eu>
---
  drivers/net/Kconfig     |   6 +
  drivers/net/Makefile    |   1 +
  drivers/net/mdio_gpio.c | 313 ++++++++++++++++++++++++++++++++++++++++
  3 files changed, 320 insertions(+)
  create mode 100644 drivers/net/mdio_gpio.c

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index 69ae7c0750..15019d8700 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -953,6 +953,12 @@ config FSL_ENETC
        This driver supports the NXP ENETC Ethernet controller found on some
        of the NXP SoCs.

+config MDIO_GPIO_BITBANG
+    bool "GPIO bitbanging MDIO driver"
+    depends on DM_MDIO && DM_GPIO
+    help
+     Driver for bitbanging MDIO
+
  config MDIO_MUX_I2CREG
      bool "MDIO MUX accessed as a register over I2C"
      depends on DM_MDIO_MUX && DM_I2C
diff --git a/drivers/net/Makefile b/drivers/net/Makefile
index 425dd721f9..ec178beb67 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -59,6 +59,7 @@ obj-$(CONFIG_LITEETH) += liteeth.o
  obj-$(CONFIG_MACB) += macb.o
  obj-$(CONFIG_MCFFEC) += mcffec.o mcfmii.o
  obj-$(CONFIG_MDIO_IPQ4019) += mdio-ipq4019.o
+obj-$(CONFIG_MDIO_GPIO_BITBANG) += mdio_gpio.o
  obj-$(CONFIG_MDIO_MUX_I2CREG) += mdio_mux_i2creg.o
  obj-$(CONFIG_MDIO_MUX_MESON_G12A) += mdio_mux_meson_g12a.o
  obj-$(CONFIG_MDIO_MUX_MESON_GXL) += mdio_mux_meson_gxl.o
diff --git a/drivers/net/mdio_gpio.c b/drivers/net/mdio_gpio.c
new file mode 100644
index 0000000000..a2a41f9519
--- /dev/null
+++ b/drivers/net/mdio_gpio.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * GPIO based MDIO bitbang driver.
+ *
+ * Copyright 2024 Markus Gothe <markus.go...@genexis.eu>
+ *
+ * This file is based on the Linux kernel drivers 
drivers/net/phy/mdio-gpio.c
+ * and drivers/net/phy/mdio-bitbang.c which have the following copyrights:
+ *
+ * Copyright (c) 2008 CSE Semaphore Belgium.
+ *  by Laurent Pinchart <laure...@cse-semaphore.com>
+ *
+ * Copyright (C) 2008, Paulius Zaleckas <paulius.zalec...@teltonika.lt>
+ *
+ * Author: Scott Wood <scottw...@freescale.com>
+ * Copyright (c) 2007 Freescale Semiconductor
+ *
+ * Copyright (c) 2003 Intracom S.A.
+ *  by Pantelis Antoniou <pa...@intracom.gr>
+ *
+ * 2005 (c) MontaVista Software, Inc.
+ * Vitaly Bordug <vbor...@ru.mvista.com>
+ */
+
+#include <dm.h>
+#include <log.h>
+#include <miiphy.h>
+#include <asm/gpio.h>
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/mdio.h>
+
+#define MDIO_READ 2
+#define MDIO_WRITE 1
+
+#define MDIO_C45 BIT(15)
+#define MDIO_C45_ADDR (MDIO_C45 | 0)
+#define MDIO_C45_READ (MDIO_C45 | 3)
+#define MDIO_C45_WRITE (MDIO_C45 | 1)
+
+/* Minimum MDC period is 400 ns, plus some margin for error. MDIO_DELAY
+ * is done twice per period.
+ */
+#define MDIO_DELAY 250
+
+/* The PHY may take up to 300 ns to produce data, plus some margin
+ * for error.
+ */
+#define MDIO_READ_DELAY 350
+
+#define MDIO_GPIO_MDC    0
+#define MDIO_GPIO_MDIO    1
+#define MDIO_GPIO_MDO    2
+
+struct mdio_gpio_priv {
+    struct gpio_desc mdc, mdio, mdo;
+};
+
+static void mdio_dir(struct udevice *mdio_dev, int dir)
+{
+    struct mdio_gpio_priv *priv = dev_get_priv(mdio_dev);
+
+    if (dm_gpio_is_valid(&priv->mdo)) {
+        /* Separate output pin. Always set its value to high
+         * when changing direction. If direction is input,
+         * assume the pin serves as pull-up. If direction is
+         * output, the default value is high.
+         */
+        dm_gpio_set_value(&priv->mdo, 1);
+        return;
+    }
+
+    if (dir)
+        dm_gpio_set_dir_flags(&priv->mdio, GPIOD_IS_OUT | 
GPIOD_IS_OUT_ACTIVE);
+    else
+        dm_gpio_set_dir_flags(&priv->mdio, GPIOD_IS_IN);
+}
+
+static int mdio_get(struct udevice *mdio_dev)
+{
+    struct mdio_gpio_priv *priv = dev_get_priv(mdio_dev);
+
+    return dm_gpio_get_value(&priv->mdio);
+}
+
+static void mdio_set(struct udevice *mdio_dev, int what)
+{
+    struct mdio_gpio_priv *priv = dev_get_priv(mdio_dev);
+
+    if (dm_gpio_is_valid(&priv->mdo))
+        dm_gpio_set_value(&priv->mdo, what);
+    else
+        dm_gpio_set_value(&priv->mdio, what);
+}
+
+static void mdc_set(struct udevice *mdio_dev, int what)
+{
+    struct mdio_gpio_priv *priv = dev_get_priv(mdio_dev);
+
+    dm_gpio_set_value(&priv->mdc, what);
+}
+
+/* MDIO must already be configured as output. */
+static void mdio_gpio_send_bit(struct udevice *mdio_dev, int val)
+{
+    mdio_set(mdio_dev, val);
+    ndelay(MDIO_DELAY);
+    mdc_set(mdio_dev, 1);
+    ndelay(MDIO_DELAY);
+    mdc_set(mdio_dev, 0);
+}
+
+/* MDIO must already be configured as input. */
+static int mdio_gpio_get_bit(struct udevice *mdio_dev)
+{
+    ndelay(MDIO_DELAY);
+    mdc_set(mdio_dev, 1);
+    ndelay(MDIO_READ_DELAY);
+    mdc_set(mdio_dev, 0);
+
+    return mdio_get(mdio_dev);
+}
+
+/* MDIO must already be configured as output. */
+static void mdio_gpio_send_num(struct udevice *mdio_dev, u16 val, int bits)
+{
+    int i;
+
+    for (i = bits - 1; i >= 0; i--)
+        mdio_gpio_send_bit(mdio_dev, (val >> i) & 1);
+}
+
+/* MDIO must already be configured as input. */
+static u16 mdio_gpio_get_num(struct udevice *mdio_dev, int bits)
+{
+    int i;
+    u16 ret = 0;
+
+    for (i = bits - 1; i >= 0; i--) {
+        ret <<= 1;
+        ret |= mdio_gpio_get_bit(mdio_dev);
+    }
+
+    return ret;
+}
+
+/* Utility to send the preamble, address, and
+ * register (common to read and write).
+ */
+static void mdio_gpio_cmd(struct udevice *mdio_dev, int op, u8 phy, u8 reg)
+{
+    int i;
+
+    mdio_dir(mdio_dev, 1);
+
+    /*
+     * Send a 32 bit preamble ('1's) with an extra '1' bit for good
+     * measure.  The IEEE spec says this is a PHY optional
+     * requirement.  The AMD 79C874 requires one after power up and
+     * one after a MII communications error.  This means that we are
+     * doing more preambles than we need, but it is safer and will be
+     * much more robust.
+     */
+    for (i = 0; i < 32; i++)
+        mdio_gpio_send_bit(mdio_dev, 1);
+
+    /*
+     * Send the start bit (01) and the read opcode (10) or write (01).
+     * Clause 45 operation uses 00 for the start and 11, 10 for
+     * read/write.
+     */
+    mdio_gpio_send_bit(mdio_dev, 0);
+    if (op & MDIO_C45)
+        mdio_gpio_send_bit(mdio_dev, 0);
+    else
+        mdio_gpio_send_bit(mdio_dev, 1);
+    mdio_gpio_send_bit(mdio_dev, (op >> 1) & 1);
+    mdio_gpio_send_bit(mdio_dev, (op >> 0) & 1);
+
+    mdio_gpio_send_num(mdio_dev, phy, 5);
+    mdio_gpio_send_num(mdio_dev, reg, 5);
+}
+
+/*
+ * In clause 45 mode all commands are prefixed by MDIO_ADDR to specify the
+ * lower 16 bits of the 21 bit address. This transfer is done 
identically to a
+ * MDIO_WRITE except for a different code. To enable clause 45 mode or
+ * MII_ADDR_C45 into the address. Theoretically clause 45 and normal 
devices
+ * can exist on the same bus. Normal devices should ignore the MDIO_ADDR
+ * phase.
+ */
+static int mdio_gpio_cmd_addr(struct udevice *mdio_dev, int phy, u32 
dev_addr, u32 reg)
+{
+    mdio_gpio_cmd(mdio_dev, MDIO_C45_ADDR, phy, dev_addr);
+
+    /* send the turnaround (10) */
+    mdio_gpio_send_bit(mdio_dev, 1);
+    mdio_gpio_send_bit(mdio_dev, 0);
+
+    mdio_gpio_send_num(mdio_dev, reg, 16);
+
+    mdio_dir(mdio_dev, 0);
+    mdio_gpio_get_bit(mdio_dev);
+
+    return dev_addr;
+}
+
+static int mdio_gpio_read(struct udevice *mdio_dev, int addr, int 
devad, int reg)
+{
+    int ret, i;
+
+    if (devad != MDIO_DEVAD_NONE) {
+        reg = mdio_gpio_cmd_addr(mdio_dev, addr, devad, reg);
+        mdio_gpio_cmd(mdio_dev, MDIO_C45_READ, addr, reg);
+    } else {
+        mdio_gpio_cmd(mdio_dev, MDIO_READ, addr, reg);
+    }
+
+    mdio_dir(mdio_dev, 0);
+
+    /* check the turnaround bit: the PHY should be driving it to zero.
+     */
+    if (mdio_gpio_get_bit(mdio_dev) != 0) {
+        /* PHY didn't drive TA low -- flush any bits it
+         * may be trying to send.
+         */
+        for (i = 0; i < 32; i++)
+            mdio_gpio_get_bit(mdio_dev);
+
+        return 0xffff;
+    }
+
+    ret = mdio_gpio_get_num(mdio_dev, 16);
+    mdio_gpio_get_bit(mdio_dev);
+
+    return ret;
+}
+
+static int mdio_gpio_write(struct udevice *mdio_dev, int addr, int 
devad, int reg, u16 val)
+{
+    if (devad != MDIO_DEVAD_NONE) {
+        reg = mdio_gpio_cmd_addr(mdio_dev, addr, devad, reg);
+        mdio_gpio_cmd(mdio_dev, MDIO_C45_WRITE, addr, reg);
+    } else {
+        mdio_gpio_cmd(mdio_dev, MDIO_WRITE, addr, reg);
+    }
+
+    /* send the turnaround (10) */
+    mdio_gpio_send_bit(mdio_dev, 1);
+    mdio_gpio_send_bit(mdio_dev, 0);
+
+    mdio_gpio_send_num(mdio_dev, val, 16);
+
+    mdio_dir(mdio_dev, 0);
+    mdio_gpio_get_bit(mdio_dev);
+
+    return 0;
+}
+
+static const struct mdio_ops mdio_gpio_ops = {
+    .read = mdio_gpio_read,
+    .write = mdio_gpio_write,
+    .reset = NULL,
+};
+
+/*
+ * Name the device, we use the device tree node name.
+ * This can be overwritten by MDIO class code if device-name property is
+ * present.
+ */
+static int mdio_gpio_bind(struct udevice *mdio_dev)
+{
+    if (ofnode_valid(dev_ofnode(mdio_dev)))
+        device_set_name(mdio_dev, ofnode_get_name(dev_ofnode(mdio_dev)));
+
+    return 0;
+}
+
+static int mdio_gpio_probe(struct udevice *mdio_dev)
+{
+    struct mdio_gpio_priv *priv = dev_get_priv(mdio_dev);
+    int ret = 0;
+
+    ret = gpio_request_by_name(mdio_dev, "gpios", MDIO_GPIO_MDC, 
&priv->mdc, GPIOD_IS_OUT);
+    if (ret)
+        return ret;
+
+    ret = gpio_request_by_name(mdio_dev, "gpios", MDIO_GPIO_MDIO, 
&priv->mdio, GPIOD_IS_IN);
+    if (ret)
+        return ret;
+
+    ret = gpio_request_by_name(mdio_dev, "gpios", MDIO_GPIO_MDO, 
&priv->mdo, GPIOD_IS_OUT);
+    if (ret && ret != -ENOENT)
+        return ret;
+
+    return 0;
+}
+
+static const struct udevice_id mdio_gpio_ids[] = {
+    { .compatible = "virtual,mdio-gpio" },
+    { /* sentinel */ }
+};
+
+U_BOOT_DRIVER(gpio_mdio) = {
+    .name = "gpio_mdio",
+    .id = UCLASS_MDIO,
+    .of_match = mdio_gpio_ids,
+    .bind = mdio_gpio_bind,
+    .probe = mdio_gpio_probe,
+    .ops = &mdio_gpio_ops,
+    .plat_auto = sizeof(struct mdio_perdev_priv),
+    .priv_auto = sizeof(struct mdio_gpio_priv),
+};
-- 
2.46.1

Reply via email to