[PATCH 1/2] phy: mxl-8611x: add driver for MaxLinear mxl-8611x PHYs

Nate Drude nate.d at variscite.com
Wed Jul 12 22:48:49 CEST 2023


The MxL86110 is a low power Ethernet PHY transceiver integrated
circuit following the IEEE 802.3 [1] standard. It offers a
cost-optimized solution that is well-suited for routers,
switches, and home gateways. It performs data transmission on
an Ethernet twisted pair copper cable of category CAT5e or higher.
The MxL86110 supports 1000, 100, and 10 Mbit/s data rates.

The current driver implementation supports:
- configuring rgmii from the device tree
- configuring the LEDs from the device tree
- reading extended registers using the mdio command, e.g.:
     => mdio rx ethernet at 428a0000 0xa00c
     Reading from bus ethernet at 428a0000
     PHY at address 0:
     40972 - 0x2600

Signed-off-by: Nate Drude <nate.d at variscite.com>
---
  drivers/net/phy/Kconfig     |   5 +
  drivers/net/phy/Makefile    |   1 +
  drivers/net/phy/mxl-8611x.c | 271 ++++++++++++++++++++++++++++++++++++
  3 files changed, 277 insertions(+)
  create mode 100644 drivers/net/phy/mxl-8611x.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 24158776f52..0fc0fb2c5bf 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -220,6 +220,11 @@ config PHY_MICREL_KSZ8XXX

  endif # PHY_MICREL

+config PHY_MXL8611X
+	bool "MaxLinear MXL8611X Ethernet PHYs"
+	help
+		Add support for configuring MaxLinear MXL8611X Ethernet PHYs.
+
  config PHY_MSCC
  	bool "Microsemi Corp Ethernet PHYs support"

diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 85d17f109cd..b3f4d75936c 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -24,6 +24,7 @@ obj-$(CONFIG_PHY_MARVELL_10G) += marvell10g.o
  obj-$(CONFIG_PHY_MICREL_KSZ8XXX) += micrel_ksz8xxx.o
  obj-$(CONFIG_PHY_MICREL_KSZ90X1) += micrel_ksz90x1.o
  obj-$(CONFIG_PHY_MESON_GXL) += meson-gxl.o
+obj-$(CONFIG_PHY_MXL8611X) += mxl-8611x.o
  obj-$(CONFIG_PHY_NATSEMI) += natsemi.o
  obj-$(CONFIG_PHY_NXP_C45_TJA11XX) += nxp-c45-tja11xx.o
  obj-$(CONFIG_PHY_NXP_TJA11XX) += nxp-tja11xx.o
diff --git a/drivers/net/phy/mxl-8611x.c b/drivers/net/phy/mxl-8611x.c
new file mode 100644
index 00000000000..467edc5bb5f
--- /dev/null
+++ b/drivers/net/phy/mxl-8611x.c
@@ -0,0 +1,271 @@
+// SPDX-License-Identifier: GPL-2.0+
+/**
+ *  Driver for MaxLinear MXL861100 Ethernet PHY
+ *
+ * Copyright 2023 Variscite Ltd.
+ * Copyright 2023 MaxLinear Inc.
+ * Author: Nate Drude <nate.d at variscite.com>
+ */
+#include <common.h>
+#include <phy.h>
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+
+/* PHY IDs */
+#define PHY_ID_MXL86110		0xC1335580
+#define PHY_ID_MXL86111		0xC1335588
+
+/* required to access extended registers */
+#define MXL8611X_EXTD_REG_ADDR_OFFSET				0x1E
+#define MXL8611X_EXTD_REG_ADDR_DATA				0x1F
+
+/* RGMII register */
+#define MXL8611X_EXT_RGMII_CFG1_REG				0xA003
+#define MXL8611X_EXT_RGMII_CFG1_NO_DELAY			0
+
+#define MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK			GENMASK(13, 10)
+#define MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK		GENMASK(3, 0)
+#define MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK	GENMASK(7, 4)
+
+/* LED registers and defines */
+#define MXL8611X_LED0_CFG_REG					0xA00C
+#define MXL8611X_LED1_CFG_REG					0xA00D
+#define MXL8611X_LED2_CFG_REG					0xA00E
+
+/**
+ * struct mxl8611x_cfg_reg_map - map a config value to aregister value
+ * @cfg		value in device configuration
+ * @reg		value in the register
+ */
+struct mxl8611x_cfg_reg_map {
+	int cfg;
+	int reg;
+};
+
+static const struct mxl8611x_cfg_reg_map mxl8611x_rgmii_delays[] = {
+	{ 0, 0 },
+	{ 150, 1 },
+	{ 300, 2 },
+	{ 450, 3 },
+	{ 600, 4 },
+	{ 750, 5 },
+	{ 900, 6 },
+	{ 1050, 7 },
+	{ 1200, 8 },
+	{ 1350, 9 },
+	{ 1500, 10 },
+	{ 1650, 11 },
+	{ 1800, 12 },
+	{ 1950, 13 },
+	{ 2100, 14 },
+	{ 2250, 15 },
+	{ 0, 0 } // Marks the end of the array
+};
+
+static int mxl8611x_lookup_reg_value(const struct mxl8611x_cfg_reg_map 
*tbl,
+				     const int cfg, int *reg)
+{
+	size_t i;
+
+	for (i = 0; i == 0 || tbl[i].cfg; i++) {
+		if (tbl[i].cfg == cfg) {
+			*reg = tbl[i].reg;
+			return 0;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static u16 mxl8611x_ext_read(struct phy_device *phydev, const u32 regnum)
+{
+	u16 val;
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum);
+	val = phy_read(phydev, MDIO_DEVAD_NONE, MXL8611X_EXTD_REG_ADDR_DATA);
+
+	debug("%s: MXL86110 at 0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, 
val);
+
+	return val;
+}
+
+static int mxl8611x_ext_write(struct phy_device *phydev, const u32 
regnum, const u16 val)
+{
+	debug("%s: MXL86110 at 0x%x 0x%x=0x%x\n", __func__, phydev->addr, regnum, 
val);
+
+	phy_write(phydev, MDIO_DEVAD_NONE, MXL8611X_EXTD_REG_ADDR_OFFSET, regnum);
+
+	return phy_write(phydev, MDIO_DEVAD_NONE, MXL8611X_EXTD_REG_ADDR_DATA, 
val);
+}
+
+static int mxl8611x_extread(struct phy_device *phydev, int addr, int 
devaddr,
+			       int regnum)
+{
+	return mxl8611x_ext_read(phydev, regnum);
+}
+
+static int mxl8611x_extwrite(struct phy_device *phydev, int addr,
+				int devaddr, int regnum, u16 val)
+{
+	return mxl8611x_ext_write(phydev, regnum, val);
+}
+
+static int mxl8611x_led_cfg(struct phy_device *phydev)
+{
+	int ret = 0;
+	int i;
+	char propname[25];
+	u32 val;
+
+	ofnode node = phy_get_ofnode(phydev);
+
+	if (!ofnode_valid(node)) {
+		printf("%s: failed to get node\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Loop through three the LED registers */
+	for (i = 0; i < 3; i++) {
+		/* Read property from device tree */
+		ret = snprintf(propname, 25, "mxl-8611x,led%d_cfg", i);
+		if (ofnode_read_u32(node, propname, &val))
+			continue;
+
+		/* Update PHY LED register */
+		mxl8611x_ext_write(phydev, MXL8611X_LED0_CFG_REG + i, val);
+	}
+
+	return 0;
+}
+
+static int mxl8611x_rgmii_cfg_of_delay(struct phy_device *phydev, const 
char *property, int *val)
+{
+	u32 of_val;
+	int ret;
+
+	ofnode node = phy_get_ofnode(phydev);
+
+	if (!ofnode_valid(node)) {
+		printf("%s: failed to get node\n", __func__);
+		return -EINVAL;
+	}
+
+	/* Get property from dts */
+	ret = ofnode_read_u32(node, property, &of_val);
+	if (ret)
+		return ret;
+
+	/* Convert delay in ps to register value */
+	ret = mxl8611x_lookup_reg_value(mxl8611x_rgmii_delays, of_val, val);
+	if (ret)
+		printf("%s: Error: %s = %d is invalid, using default value\n",
+		       __func__, property, of_val);
+
+	return ret;
+}
+
+static int mxl8611x_rgmii_cfg(struct phy_device *phydev)
+{
+	u32 val = 0;
+	int rxdelay, txdelay_100m, txdelay_1g;
+
+	/* Get rgmii register value */
+	val = mxl8611x_ext_read(phydev, MXL8611X_EXT_RGMII_CFG1_REG);
+
+	/* Get RGMII Rx Delay Selection from device tree or rgmii register */
+	if (mxl8611x_rgmii_cfg_of_delay(phydev, 
"mxl-8611x,rx-internal-delay-ps", &rxdelay))
+		rxdelay = FIELD_GET(MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK, val);
+
+	/* Get Fast Ethernet RGMII Tx Clock Delay Selection from device tree 
or rgmii register */
+	if (mxl8611x_rgmii_cfg_of_delay(phydev, 
"mxl-8611x,tx-internal-delay-ps-100m",
+					&txdelay_100m))
+		txdelay_100m = 
FIELD_GET(MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK, val);
+
+	/* Get Gigabit Ethernet RGMII Tx Clock Delay Selection from device 
tree or rgmii register */
+	if (mxl8611x_rgmii_cfg_of_delay(phydev, 
"mxl-8611x,tx-internal-delay-ps-1g", &txdelay_1g))
+		txdelay_1g = FIELD_GET(MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK, val);
+
+	switch (phydev->interface) {
+	case PHY_INTERFACE_MODE_RGMII:
+		val = MXL8611X_EXT_RGMII_CFG1_NO_DELAY;
+		break;
+	case PHY_INTERFACE_MODE_RGMII_RXID:
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK, rxdelay);
+		break;
+	case PHY_INTERFACE_MODE_RGMII_TXID:
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK, 
txdelay_100m);
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK, txdelay_1g);
+		break;
+	case PHY_INTERFACE_MODE_RGMII_ID:
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_RX_DELAY_MASK, rxdelay);
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_TX_10MB_100MB_DELAY_MASK, 
txdelay_100m);
+		val = (val & ~MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK) |
+			FIELD_PREP(MXL8611X_EXT_RGMII_CFG1_TX_1G_DELAY_MASK, txdelay_1g);
+		break;
+	default:
+		printf("%s: Error: Unsupported rgmii mode %d\n", __func__, 
phydev->interface);
+		return -EINVAL;
+	}
+
+	return mxl8611x_ext_write(phydev, MXL8611X_EXT_RGMII_CFG1_REG, val);
+}
+
+static int mxl8611x_config(struct phy_device *phydev)
+{
+	int ret;
+
+	/* Configure rgmii */
+	ret = mxl8611x_rgmii_cfg(phydev);
+
+	if (ret < 0)
+		return ret;
+
+	/* Configure LEDs */
+	ret = mxl8611x_led_cfg(phydev);
+
+	if (ret < 0)
+		return ret;
+
+	return genphy_config(phydev);
+}
+
+static int mxl86110_config(struct phy_device *phydev)
+{
+	printf("MXL86110 PHY detected at addr %d\n", phydev->addr);
+	return mxl8611x_config(phydev);
+}
+
+static int mxl86111_config(struct phy_device *phydev)
+{
+	printf("MXL86111 PHY detected at addr %d\n", phydev->addr);
+	return mxl8611x_config(phydev);
+}
+
+U_BOOT_PHY_DRIVER(mxl86110) = {
+	.name = "MXL86110",
+	.uid = PHY_ID_MXL86110,
+	.mask = 0xffffffff,
+	.features = PHY_GBIT_FEATURES,
+	.config = mxl86110_config,
+	.startup = genphy_startup,
+	.shutdown = genphy_shutdown,
+	.readext = mxl8611x_extread,
+	.writeext = mxl8611x_extwrite,
+};
+
+U_BOOT_PHY_DRIVER(mxl86111) = {
+	.name = "MXL86111",
+	.uid = PHY_ID_MXL86111,
+	.mask = 0xffffffff,
+	.features = PHY_GBIT_FEATURES,
+	.config = mxl86111_config,
+	.startup = genphy_startup,
+	.shutdown = genphy_shutdown,
+	.readext = mxl8611x_extread,
+	.writeext = mxl8611x_extwrite,
+};
-- 
2.40.1


More information about the U-Boot mailing list