diff mbox series

[net-next,v1,1/1] net: phy: dp83td510: Add support for the DP83TD510 Ethernet PHY

Message ID 20220502085437.142000-1-o.rempel@pengutronix.de (mailing list archive)
State Changes Requested
Delegated to: Netdev Maintainers
Headers show
Series [net-next,v1,1/1] net: phy: dp83td510: Add support for the DP83TD510 Ethernet PHY | expand

Checks

Context Check Description
netdev/tree_selection success Clearly marked for net-next
netdev/fixes_present success Fixes tag not required for -next series
netdev/subject_prefix success Link
netdev/cover_letter success Single patches do not need cover letters
netdev/patch_count success Link
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 1 maintainers not CCed: linux@armlinux.org.uk
netdev/build_clang success Errors and warnings before: 0 this patch: 0
netdev/module_param success Was 0 now: 0
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 0 this patch: 0
netdev/checkpatch warning WARNING: ENOTSUPP is not a SUSV4 error code, prefer EOPNOTSUPP WARNING: Prefer "GPL" over "GPL v2" - see commit bf7fbeeae6db ("module: Cure the MODULE_LICENSE "GPL" vs. "GPL v2" bogosity") WARNING: added, moved or deleted file(s), does MAINTAINERS need updating? WARNING: line length of 87 exceeds 80 columns WARNING: please write a help paragraph that fully describes the config symbol
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Oleksij Rempel May 2, 2022, 8:54 a.m. UTC
The DP83TD510E is an ultra-low power Ethernet physical layer transceiver
that supports 10M single pair cable.

This driver was tested with NXP SJA1105, STMMAC and ASIX AX88772B USB Ethernet
controller.

Signed-off-by: Oleksij Rempel <o.rempel@pengutronix.de>
---
 drivers/net/phy/Kconfig     |   6 +
 drivers/net/phy/Makefile    |   1 +
 drivers/net/phy/dp83td510.c | 299 ++++++++++++++++++++++++++++++++++++
 3 files changed, 306 insertions(+)
 create mode 100644 drivers/net/phy/dp83td510.c

Comments

Andrew Lunn May 2, 2022, 12:42 p.m. UTC | #1
> +static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
> +			     u16 regnum)
> +{
> +	/* Write the desired MMD Devad */
> +	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
> +
> +	/* Write the desired MMD register address */
> +	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
> +
> +	/* Select the Function : DATA with no post increment */
> +	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
> +			devad | MII_MMD_CTRL_NOINCR);
> +}

Please make the version in phy-core.c global scope.

A better explanation of what is going on here would be good.

> +	/* This PHY supports only C22 MDIO opcodes. We can use only indirect
> +	 * access.
> +	 */
> +	mmd_phy_indirect(bus, phy_addr, devad, regnum);

This comment suggests it is because it cannot do C45. But the core
should handle this, it would use indirect access. However, you have
hijacked phydev->drv->read_mmd to allow you to translate standard
registers to vendor registers. This bypasses the cores fallback to
indirect access.

> +static struct phy_driver dp83td510_driver[] = {
> +{
> +	PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID),
> +	.name		= "TI DP83TD510E",
> +
> +	.config_aneg	= genphy_c45_config_aneg,
> +	.read_status	= genphy_c45_read_status,
> +	.get_features	= dp83td510_get_features,
> +	.config_intr	= dp83td510_config_intr,
> +	.handle_interrupt = dp83td510_handle_interrupt,
> +
> +	.suspend	= genphy_suspend,
> +	.resume		= genphy_resume,
> +	.read_mmd	= dp83td510_read_mmd,
> +	.write_mmd	= dp83td510_write_mmd,

Given how far this PHY is away from standards, you might get a smaller
simpler driver if you ignore genphy all together, write your own
config_aneg and read_status, and don't mess with .read_mmd and
write_mmd.

	Andrew
diff mbox series

Patch

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index bbbf6c07ea53..9fee639ee5c8 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -342,6 +342,12 @@  config DP83869_PHY
 	  Currently supports the DP83869 PHY.  This PHY supports copper and
 	  fiber connections.
 
+config DP83TD510_PHY
+	tristate "Texas Instruments DP83TD510 Ethernet 10Base-T1L PHY"
+	help
+	  Support for the DP83TD510 Ethernet 10Base-T1L PHY. This PHY supports
+	  a 10M single pair Ethernet connection for up to 1000 meter cable.
+
 config VITESSE_PHY
 	tristate "Vitesse PHYs"
 	help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index b82651b57043..b12b1d86fc99 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -57,6 +57,7 @@  obj-$(CONFIG_DP83848_PHY)	+= dp83848.o
 obj-$(CONFIG_DP83867_PHY)	+= dp83867.o
 obj-$(CONFIG_DP83869_PHY)	+= dp83869.o
 obj-$(CONFIG_DP83TC811_PHY)	+= dp83tc811.o
+obj-$(CONFIG_DP83TD510_PHY)	+= dp83td510.o
 obj-$(CONFIG_FIXED_PHY)		+= fixed_phy.o
 obj-$(CONFIG_ICPLUS_PHY)	+= icplus.o
 obj-$(CONFIG_INTEL_XWAY_PHY)	+= intel-xway.o
diff --git a/drivers/net/phy/dp83td510.c b/drivers/net/phy/dp83td510.c
new file mode 100644
index 000000000000..90531d849545
--- /dev/null
+++ b/drivers/net/phy/dp83td510.c
@@ -0,0 +1,299 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83TD510 PHY
+ * Copyright (c) 2022 Pengutronix, Oleksij Rempel <kernel@pengutronix.de>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/phy.h>
+
+#define DP83TD510E_PHY_ID			0x20000181
+
+/* MDIO_MMD_VEND2 registers */
+#define DP83TD510E_PHY_STS			0x10
+#define DP83TD510E_STS_MII_INT			BIT(7)
+
+#define DP83TD510E_GEN_CFG			0x11
+#define DP83TD510E_GENCFG_INT_POLARITY		BIT(3)
+#define DP83TD510E_GENCFG_INT_EN		BIT(1)
+#define DP83TD510E_GENCFG_INT_OE		BIT(0)
+
+#define DP83TD510E_INTERRUPT_REG_1		0x12
+#define DP83TD510E_INT1_LINK			BIT(13)
+#define DP83TD510E_INT1_LINK_EN			BIT(5)
+
+static const u16 devad1[] = {
+	0x000, 0x007, 0x00B, 0x012, 0x834, 0x8F6, 0x8F7, 0x8F8
+};
+
+static const u16 devad3[] = {
+	0x3000, 0x38E6, 0x38E7
+};
+
+static const u16 devad7[] = {
+	0x200, 0x201, 0x202, 0x203, 0x204, 0x205, 0x206, 0x207, 0x208, 0x209,
+	0x20A, 0x20B, 0x20C, 0x20D, 0x20E, 0x20F };
+
+static const u16 devad1f[] = {
+	0x0, 0x2, 0x3, 0x10, 0x11, 0x12, 0x13, 0x15, 0x16, 0x17, 0x18, 0x19,
+	0x1E, 0x119, 0x11A, 0x11B, 0x11C, 0x11D, 0x11E, 0x11F, 0x120, 0x121,
+	0x122, 0x123, 0x124, 0x125, 0x126, 0x127, 0x128, 0x129, 0x12A, 0x12B,
+	0x12C, 0x12D, 0x12E, 0x12F, 0x130, 0x300, 0x301, 0x302, 0x303, 0x304,
+	0x305, 0x306, 0x307, 0x308, 0x309, 0x30A, 0x420, 0x460, 0x461, 0x462,
+	0x463, 0x467, 0x468, 0x469, 0x60C, 0x872, 0x88D, 0x88E, 0x88F, 0x890,
+	0x891, 0x892, 0x898, 0x899, 0x89A, 0x89B, 0x89C, 0x89D, 0x8E9, 0x8EA,
+	0x8EB, 0x8EC, 0x8ED, 0x8EE, 0xA9D, 0xA9F, 0xE01
+};
+
+/* This PHY has some differences compared to 802.3cg-2019 specification. To
+ * avoid misconfiguration it is better to validate register accesses.
+ */
+static int dp83td510_validate_mmd(struct phy_device *phydev, int devad,
+				  u16 regnum)
+{
+	const u16 *regs;
+	size_t size;
+	int i;
+
+	switch (devad) {
+	case MDIO_MMD_PMAPMD:
+		regs = &devad1[0];
+		size = ARRAY_SIZE(devad1);
+		break;
+	case MDIO_MMD_PCS:
+		regs = &devad3[0];
+		size = ARRAY_SIZE(devad3);
+		break;
+	case MDIO_MMD_AN:
+		regs = &devad7[0];
+		size = ARRAY_SIZE(devad7);
+		break;
+	case MDIO_MMD_VEND2:
+		regs = &devad1f[0];
+		size = ARRAY_SIZE(devad1f);
+		break;
+	default:
+		phydev_err(phydev, "Unknown device %i.%i\n", devad, regnum);
+		return -ENOTSUPP;
+	}
+
+	for (i = 0; i < size; i++)
+		if (regs[i] == regnum)
+			return 0;
+
+	phydev_err(phydev, "Unknown register %i.%i\n", devad, regnum);
+
+	WARN_ON_ONCE(1);
+
+	return -ENOTSUPP;
+}
+
+static void mmd_phy_indirect(struct mii_bus *bus, int phy_addr, int devad,
+			     u16 regnum)
+{
+	/* Write the desired MMD Devad */
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL, devad);
+
+	/* Write the desired MMD register address */
+	__mdiobus_write(bus, phy_addr, MII_MMD_DATA, regnum);
+
+	/* Select the Function : DATA with no post increment */
+	__mdiobus_write(bus, phy_addr, MII_MMD_CTRL,
+			devad | MII_MMD_CTRL_NOINCR);
+}
+
+static int dp83td510_read_mmd(struct phy_device *phydev, int devad, u16 regnum)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+	int phy_addr = phydev->mdio.addr;
+	int val;
+
+	if (regnum > (u16)~0 || devad > 32)
+		return -EINVAL;
+
+	switch (devad) {
+	case MDIO_MMD_PMAPMD:
+		/* we do not have 1.1 register. Link status 1.1.2 can get on 7.513.2 */
+		if (regnum == MDIO_STAT1) {
+			devad = MDIO_MMD_AN;
+			regnum = MDIO_AN_T1_STAT;
+		}
+		/* 1.8 register is not present on this PHY, without it PHYlib
+		 * can't find T1 capabilities register.
+		 */
+		if (regnum == MDIO_STAT2)
+			return MDIO_PMA_STAT2_EXTABLE;
+
+		break;
+	case MDIO_MMD_AN:
+		/* Not supported on T1 or at least not on this PHY, use 7.512
+		 * instead. This regs look similar.
+		 */
+		if (regnum == MDIO_CTRL1)
+			regnum = MDIO_AN_T1_CTRL;
+		break;
+	case MDIO_MMD_PCS:
+		if (regnum == MDIO_PCS_EEE_ABLE)
+			return 0;
+		break;
+	}
+
+	val = dp83td510_validate_mmd(phydev, devad, regnum);
+	if (val < 0)
+		return 0;
+
+	/* This PHY supports only C22 MDIO opcodes. We can use only indirect
+	 * access.
+	 */
+	mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+	/* Read the content of the MMD's selected register */
+	val = __mdiobus_read(bus, phy_addr, MII_MMD_DATA);
+
+	return val;
+}
+
+static int dp83td510_write_mmd(struct phy_device *phydev, int devad, u16 regnum,
+			       u16 val)
+{
+	struct mii_bus *bus = phydev->mdio.bus;
+	int phy_addr = phydev->mdio.addr;
+	int ret;
+
+	if (regnum > (u16)~0 || devad > 32)
+		return -EINVAL;
+
+	if (devad == MDIO_MMD_PMAPMD) {
+		/* RO regs, nothing to write */
+		if (regnum == MDIO_CTRL1 || regnum == MDIO_CTRL2)
+			return 0;
+	}
+
+	ret = dp83td510_validate_mmd(phydev, devad, regnum);
+	if (ret < 0)
+		return 0;
+
+	/* This PHY supports only C22 MDIO opcodes. We can use only indirect
+	 * access.
+	 */
+	mmd_phy_indirect(bus, phy_addr, devad, regnum);
+
+	/* Write the data into MMD's selected register */
+	return __mdiobus_write(bus, phy_addr, MII_MMD_DATA, val);
+}
+
+static int dp83td510_config_intr(struct phy_device *phydev)
+{
+	int ret;
+
+	if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+		/* Clear any pending interrupts */
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS,
+				    0x0);
+		if (ret)
+			return ret;
+
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+				    DP83TD510E_INTERRUPT_REG_1,
+				    DP83TD510E_INT1_LINK_EN);
+		if (ret)
+			return ret;
+
+		ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2,
+				       DP83TD510E_GEN_CFG,
+				       DP83TD510E_GENCFG_INT_POLARITY |
+				       DP83TD510E_GENCFG_INT_EN |
+				       DP83TD510E_GENCFG_INT_OE);
+	} else {
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2,
+				    DP83TD510E_INTERRUPT_REG_1, 0x0);
+		if (ret)
+			return ret;
+
+		ret = phy_clear_bits_mmd(phydev, MDIO_MMD_VEND2,
+					 DP83TD510E_GEN_CFG,
+					 DP83TD510E_GENCFG_INT_EN);
+		if (ret)
+			return ret;
+
+		/* Clear any pending interrupts */
+		ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS,
+				    0x0);
+	}
+
+	return ret;
+}
+
+static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev)
+{
+	int  ret;
+
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_PHY_STS);
+	if (ret < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	} else if (!(ret & DP83TD510E_STS_MII_INT)) {
+		return IRQ_NONE;
+	}
+
+	/* Read the current enabled interrupts */
+	ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, DP83TD510E_INTERRUPT_REG_1);
+	if (ret < 0) {
+		phy_error(phydev);
+		return IRQ_NONE;
+	} else if (!(ret & DP83TD510E_INT1_LINK_EN) ||
+		   !(ret & DP83TD510E_INT1_LINK)) {
+		return IRQ_NONE;
+	}
+
+	phy_trigger_machine(phydev);
+
+	return IRQ_HANDLED;
+}
+
+static int dp83td510_get_features(struct phy_device *phydev)
+{
+	/* This PHY can't respond on MDIO bus if no RMII clock is enabled.
+	 * In case RMII mode is used (most meaningful mode for this PHY) and
+	 * the PHY do not have own XTAL, and CLK providing MAC is not probed,
+	 * we won't be able to read all needed ability registers.
+	 * So provide it manually.
+	 */
+
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Autoneg_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Asym_Pause_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_Pause_BIT, phydev->supported);
+	linkmode_set_bit(ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+			 phydev->supported);
+
+	return 0;
+}
+
+static struct phy_driver dp83td510_driver[] = {
+{
+	PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID),
+	.name		= "TI DP83TD510E",
+
+	.config_aneg	= genphy_c45_config_aneg,
+	.read_status	= genphy_c45_read_status,
+	.get_features	= dp83td510_get_features,
+	.config_intr	= dp83td510_config_intr,
+	.handle_interrupt = dp83td510_handle_interrupt,
+
+	.suspend	= genphy_suspend,
+	.resume		= genphy_resume,
+	.read_mmd	= dp83td510_read_mmd,
+	.write_mmd	= dp83td510_write_mmd,
+} };
+module_phy_driver(dp83td510_driver);
+
+static struct mdio_device_id __maybe_unused dp83td510_tbl[] = {
+	{ PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID) },
+	{ }
+};
+MODULE_DEVICE_TABLE(mdio, dp83td510_tbl);
+
+MODULE_DESCRIPTION("Texas Instruments DP83TD510E PHY driver");
+MODULE_AUTHOR("Oleksij Rempel <kernel@pengutronix.de>");
+MODULE_LICENSE("GPL v2");