[PATCH] Bitbanging MDIO driver for DM framework.

Tom Rini trini at konsulko.com
Tue Nov 19 15:26:44 CET 2024


From: Markus Gothe <markus.gothe at genexis.eu>

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.gothe at 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 576cd2d50ad9..5e862158c566 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -1007,6 +1007,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 f5ab1f5dedf6..e51a917933eb 100644
--- a/drivers/net/Makefile
+++ b/drivers/net/Makefile
@@ -61,6 +61,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 000000000000..a2a41f95190c
--- /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.gothe at 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 <laurentp at cse-semaphore.com>
+ *
+ * Copyright (C) 2008, Paulius Zaleckas <paulius.zaleckas at teltonika.lt>
+ *
+ * Author: Scott Wood <scottwood at freescale.com>
+ * Copyright (c) 2007 Freescale Semiconductor
+ *
+ * Copyright (c) 2003 Intracom S.A.
+ *  by Pantelis Antoniou <panto at intracom.gr>
+ *
+ * 2005 (c) MontaVista Software, Inc.
+ * Vitaly Bordug <vbordug at 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.34.1



More information about the U-Boot mailing list