net: phy: dp83td510: Add support for the DP83TD510 Ethernet PHY
authorOleksij Rempel <o.rempel@pengutronix.de>
Fri, 6 May 2022 04:23:57 +0000 (06:23 +0200)
committerDavid S. Miller <davem@davemloft.net>
Mon, 9 May 2022 11:09:30 +0000 (12:09 +0100)
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>
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/dp83td510.c [new file with mode: 0644]

index bbbf6c07ea530518d3b10b256656917b57a3d885..9fee639ee5c8b1b3879e91c299587907f7e9768c 100644 (file)
@@ -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
index b82651b57043abf35130f28d18c70705a33742a9..b12b1d86fc990591820bd8744c4056bec4dae85f 100644 (file)
@@ -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 (file)
index 0000000..1ae792b
--- /dev/null
@@ -0,0 +1,209 @@
+// 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_LINK_STATUS                 BIT(0)
+
+#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)
+
+#define DP83TD510E_AN_STAT_1                   0x60c
+#define DP83TD510E_MASTER_SLAVE_RESOL_FAIL     BIT(15)
+
+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_read_status(struct phy_device *phydev)
+{
+       u16 phy_sts;
+       int ret;
+
+       phydev->speed = SPEED_UNKNOWN;
+       phydev->duplex = DUPLEX_UNKNOWN;
+       phydev->pause = 0;
+       phydev->asym_pause = 0;
+       linkmode_zero(phydev->lp_advertising);
+
+       phy_sts = phy_read(phydev, DP83TD510E_PHY_STS);
+
+       phydev->link = !!(phy_sts & DP83TD510E_LINK_STATUS);
+       if (phydev->link) {
+               /* This PHY supports only one link mode: 10BaseT1L_Full */
+               phydev->duplex = DUPLEX_FULL;
+               phydev->speed = SPEED_10;
+
+               if (phydev->autoneg == AUTONEG_ENABLE) {
+                       ret = genphy_c45_read_lpa(phydev);
+                       if (ret)
+                               return ret;
+
+                       phy_resolve_aneg_linkmode(phydev);
+               }
+       }
+
+       if (phydev->autoneg == AUTONEG_ENABLE) {
+               ret = genphy_c45_baset1_read_status(phydev);
+               if (ret < 0)
+                       return ret;
+
+               ret = phy_read_mmd(phydev, MDIO_MMD_VEND2,
+                                  DP83TD510E_AN_STAT_1);
+               if (ret < 0)
+                       return ret;
+
+               if (ret & DP83TD510E_MASTER_SLAVE_RESOL_FAIL)
+                       phydev->master_slave_state = MASTER_SLAVE_STATE_ERR;
+       } else {
+               return genphy_c45_pma_baset1_read_master_slave(phydev);
+       }
+
+       return 0;
+}
+
+static int dp83td510_config_aneg(struct phy_device *phydev)
+{
+       bool changed = false;
+       int ret;
+
+       ret = genphy_c45_pma_baset1_setup_master_slave(phydev);
+       if (ret < 0)
+               return ret;
+
+       if (phydev->autoneg == AUTONEG_DISABLE)
+               return genphy_c45_an_disable_aneg(phydev);
+
+       ret = genphy_c45_an_config_aneg(phydev);
+       if (ret < 0)
+               return ret;
+       if (ret > 0)
+               changed = true;
+
+       return genphy_c45_check_and_restart_aneg(phydev, changed);
+}
+
+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    = dp83td510_config_aneg,
+       .read_status    = dp83td510_read_status,
+       .get_features   = dp83td510_get_features,
+       .config_intr    = dp83td510_config_intr,
+       .handle_interrupt = dp83td510_handle_interrupt,
+
+       .suspend        = genphy_suspend,
+       .resume         = genphy_resume,
+} };
+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");