[PATCH net-next v4 4/4] net: phy: dp83td510: Add support for the DP83TD510 Ethernet PHY

From: Dan Murphy
Date: Tue Nov 17 2020 - 15:17:05 EST


The DP83TD510E is an ultra-low power Ethernet physical layer transceiver
that supports 10M single pair cable.

The device supports both 2.4-V p2p and 1-V p2p output voltage as defined
by IEEE 802.3cg 10Base-T1L specfications. These modes can be forced via
the device tree or the device is defaulted to auto negotiation to
determine the proper p2p voltage.

Signed-off-by: Dan Murphy <dmurphy@xxxxxx>
---

v4 - Considerable rework of the code after secondary test setup was created.
This version also uses the handle_interrupt call back and reduces the
configuration arrays as it was determined that 80% of the array was the same.

drivers/net/phy/Kconfig | 6 +
drivers/net/phy/Makefile | 1 +
drivers/net/phy/dp83td510.c | 505 ++++++++++++++++++++++++++++++++++++
3 files changed, 512 insertions(+)
create mode 100644 drivers/net/phy/dp83td510.c

diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index 698bea312adc..017252e1504c 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -302,6 +302,12 @@ config DP83869_PHY
Currently supports the DP83869 PHY. This PHY supports copper and
fiber connections.

+config DP83TD510_PHY
+ tristate "Texas Instruments DP83TD510 10M Single Pair Ethernet PHY"
+ help
+ Support for the DP83TD510 Ethernet PHY. This PHY supports a 10M single
+ pair Ethernet connection.
+
config VITESSE_PHY
tristate "Vitesse PHYs"
help
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index a13e402074cf..bf62ce211eb4 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -56,6 +56,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..a4456e0da447
--- /dev/null
+++ b/drivers/net/phy/dp83td510.c
@@ -0,0 +1,505 @@
+// SPDX-License-Identifier: GPL-2.0
+/* Driver for the Texas Instruments DP83TD510 PHY
+ * Copyright (C) 2020 Texas Instruments Incorporated - https://www.ti.com/
+ */
+
+#include <linux/ethtool.h>
+#include <linux/etherdevice.h>
+#include <linux/kernel.h>
+#include <linux/mii.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/phy.h>
+#include <linux/netdevice.h>
+
+#define DP83TD510E_PHY_ID 0x20000180
+#define DP83TD510_DEVADDR_AN 0x7
+#define DP83TD510_DEVADDR 0x1f
+#define DP83TD510_PMD_DEVADDR 0x1
+
+#define DP83TD510_PHY_STAT 0x10
+#define DP83TD510_GEN_CFG 0x11
+#define DP83TD510_INT_REG1 0x12
+#define DP83TD510_INT_REG2 0x13
+#define DP83TD510_MAC_CFG_1 0x17
+#define DP83TD510_CTRL_REG 0x1f
+
+#define DP83TD510_ANEG_CTRL 0x200
+#define DP83TD510_PMD_CTRL 0x834
+#define DP83TD510_M_S_CTRL 0x8f6
+
+#define DP83TD510_SOR_1 0x467
+
+#define DP83TD510_HW_RESET BIT(15)
+#define DP83TD510_SW_RESET BIT(14)
+
+#define DP83TD510_LINK_STS BIT(0)
+
+/* GEN CFG bits */
+#define DP83TD510_INT_OE BIT(0)
+#define DP83TD510_INT_EN BIT(1)
+
+/* INT REG 1 bits */
+#define DP83TD510_INT1_ESD_EN BIT(3)
+#define DP83TD510_INT1_LINK_EN BIT(5)
+#define DP83TD510_INT1_RHF_EN BIT(7)
+#define DP83TD510_INT1_ESD BIT(11)
+#define DP83TD510_INT1_LINK BIT(13)
+#define DP83TD510_INT1_RHF BIT(15)
+
+/* INT REG 2 bits */
+#define DP83TD510_INT2_POR_EN BIT(0)
+#define DP83TD510_INT2_POL_EN BIT(1)
+#define DP83TD510_INT2_PAGE_EN BIT(5)
+#define DP83TD510_INT2_POR BIT(8)
+#define DP83TD510_INT2_POL BIT(9)
+#define DP83TD510_INT2_PAGE BIT(13)
+
+/* MAC CFG bits */
+#define DP83TD510_RX_CLK_SHIFT BIT(12)
+#define DP83TD510_TX_CLK_SHIFT BIT(11)
+
+#define DP83TD510_MASTER_MODE BIT(14)
+#define DP83TD510_AUTO_NEG_EN BIT(12)
+#define DP83TD510_RGMII BIT(8)
+
+#define DP83TD510_FIFO_DEPTH_MASK GENMASK(6, 5)
+#define DP83TD510_FIFO_DEPTH_4_B_NIB 0
+#define DP83TD510_FIFO_DEPTH_5_B_NIB BIT(5)
+#define DP83TD510_FIFO_DEPTH_6_B_NIB BIT(6)
+#define DP83TD510_FIFO_DEPTH_8_B_NIB (BIT(5) | BIT(6))
+
+#define DP83TD510_2_4V BIT(12)
+#define DP83TD510_2_4V_P2P 2400
+#define DP83TD510_1_1V_P2P 1100
+#define DP83TD510_AUTO_NEG_P2P 0
+
+const int dp83td510_feature_array[4] = {
+ ETHTOOL_LINK_MODE_Autoneg_BIT,
+ ETHTOOL_LINK_MODE_10baseT1L_Half_BIT,
+ ETHTOOL_LINK_MODE_10baseT1L_Full_BIT,
+ ETHTOOL_LINK_MODE_TP_BIT,
+};
+
+struct dp83td510_private {
+ u32 hi_diff_output;
+ u32 tx_fifo_depth;
+ u32 rgmii_delay;
+ bool is_rgmii;
+};
+
+struct dp83td510_init_reg {
+ int reg;
+ int val;
+};
+
+static struct dp83td510_init_reg dp83td510_errata[] = {
+ { 0x608, 0x003b }, /* disable_0_transition */
+ { 0x862, 0x39f8 }, /* AGC Gain during Autoneg */
+ { 0x81a, 0x67c0 }, /* deq offset for 1V swing */
+ { 0x81c, 0xfb62 }, /* deq offset for 2.4V swing */
+ { 0x830, 0x05a3 }, /* Enable energy lost fallback */
+ { 0x855, 0x1b55 }, /* MSE Threshold change */
+ { 0x831, 0x0403 }, /* energy detect threshold */
+ { 0x856, 0x1800 }, /* good1 MSE threshold change */
+ { 0x857, 0x8fa0 }, /* Enable fallback to phase 1 on watchdog trigger */
+ { 0x871, 0x000c }, /* TED input changed to slicer_in without FFE */
+ { 0x883, 0x022e }, /* Enable Rx Filter for Short Cable detection */
+ { 0x402, 0x1800 }, /* Adjust LD swing */
+ { 0x878, 0x2248 }, /* Change PI up/down polarity */
+ { 0x10c, 0x0008 }, /* tx filter coefficient */
+ { 0x112, 0x1212 }, /* tx filter scaling factor */
+ { 0x809, 0x5c80 }, /* AGC retrain */
+ { 0x803, 0x1529 }, /* Master Ph1 Back-off */
+ { 0x804, 0x1a33 }, /* Master Ph1 Back-off */
+ { 0x805, 0x1f3d }, /* Master Ph1 Back-off */
+ { 0x850, 0x045b }, /* hybrid gain & delay */
+ { 0x874, 0x6967 }, /* kp step 0 for master */
+ { 0x852, 0x7800 }, /* FAGC init gain */
+ { 0x806, 0x1e1e }, /* Master/Slave Ph2 Back-off */
+ { 0x807, 0x2525 }, /* Master/Slave Ph2 Back-off */
+ { 0x808, 0x2c2c }, /* Master/Slave Ph2 Back-off */
+ { 0x850, 0x0590 }, /* Hybrid Gain/Delay Code */
+ { 0x827, 0x4000 }, /* Echo Fixed Delay */
+ { 0x849, 0x0fe4 }, /* Hybrid Cal enable */
+ { 0x84b, 0x04b5 }, /* Echo Score Sel */
+ { 0x018, 0x0043 }, /* CRS/RX_DV pin as RX_DV for RMII repeater mode */
+};
+
+static int dp83td510_ack_interrupt(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_read(phydev, DP83TD510_INT_REG1);
+ if (ret < 0)
+ return ret;
+
+ ret = phy_read(phydev, DP83TD510_INT_REG2);
+ if (ret < 0)
+ return ret;
+
+ phy_trigger_machine(phydev);
+
+ return 0;
+}
+
+static irqreturn_t dp83td510_handle_interrupt(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = dp83td510_ack_interrupt(phydev);
+ if (ret)
+ return IRQ_NONE;
+
+ return IRQ_HANDLED;
+}
+
+static int dp83td510_config_intr(struct phy_device *phydev)
+{
+ int int_status;
+ int gen_cfg_val;
+ int ret;
+
+ if (phydev->interrupts == PHY_INTERRUPT_ENABLED) {
+ int_status = phy_read(phydev, DP83TD510_INT_REG1);
+ if (int_status < 0)
+ return int_status;
+
+ int_status = (DP83TD510_INT1_ESD_EN | DP83TD510_INT1_LINK_EN |
+ DP83TD510_INT1_RHF_EN);
+
+ ret = phy_write(phydev, DP83TD510_INT_REG1, int_status);
+ if (ret)
+ return ret;
+
+ int_status = phy_read(phydev, DP83TD510_INT_REG2);
+ if (int_status < 0)
+ return int_status;
+
+ int_status = (DP83TD510_INT2_POR | DP83TD510_INT2_POL |
+ DP83TD510_INT2_PAGE);
+
+ ret = phy_write(phydev, DP83TD510_INT_REG2, int_status);
+ if (ret)
+ return ret;
+
+ gen_cfg_val = phy_read(phydev, DP83TD510_GEN_CFG);
+ if (gen_cfg_val < 0)
+ return gen_cfg_val;
+
+ gen_cfg_val |= DP83TD510_INT_OE | DP83TD510_INT_EN;
+
+ } else {
+ ret = phy_write(phydev, DP83TD510_INT_REG1, 0);
+ if (ret)
+ return ret;
+
+ ret = phy_write(phydev, DP83TD510_INT_REG2, 0);
+ if (ret)
+ return ret;
+
+ gen_cfg_val = phy_read(phydev, DP83TD510_GEN_CFG);
+ if (gen_cfg_val < 0)
+ return gen_cfg_val;
+
+ gen_cfg_val &= ~DP83TD510_INT_EN;
+ }
+
+ ret = phy_write(phydev, DP83TD510_GEN_CFG, gen_cfg_val);
+ if (ret)
+ return ret;
+
+ return dp83td510_ack_interrupt(phydev);
+}
+
+static int dp83td510_configure_mode(struct phy_device *phydev, int pmd_ctrl)
+{
+ struct dp83td510_private *dp83td510 = phydev->priv;
+ int ret;
+ int i;
+
+ ret = phy_set_bits(phydev, DP83TD510_CTRL_REG, DP83TD510_HW_RESET);
+ if (ret < 0)
+ return ret;
+
+ if (dp83td510->hi_diff_output == DP83TD510_2_4V_P2P)
+ ret = phy_write_mmd(phydev, DP83TD510_DEVADDR,
+ DP83TD510_M_S_CTRL, DP83TD510_2_4V);
+ else
+ ret = phy_write_mmd(phydev, DP83TD510_DEVADDR,
+ DP83TD510_M_S_CTRL, 0);
+
+ if (phydev->autoneg) {
+ ret = phy_modify_mmd(phydev, DP83TD510_DEVADDR_AN,
+ DP83TD510_ANEG_CTRL,
+ DP83TD510_AUTO_NEG_EN,
+ DP83TD510_AUTO_NEG_EN);
+ if (ret)
+ return ret;
+ } else {
+ ret = phy_modify_mmd(phydev, DP83TD510_DEVADDR_AN,
+ DP83TD510_ANEG_CTRL,
+ DP83TD510_AUTO_NEG_EN, 0);
+ if (ret)
+ return ret;
+ }
+
+ ret = phy_modify_mmd(phydev, DP83TD510_PMD_DEVADDR,
+ DP83TD510_PMD_CTRL,
+ DP83TD510_MASTER_MODE, pmd_ctrl);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < ARRAY_SIZE(dp83td510_errata); i++) {
+ ret = phy_write_mmd(phydev, DP83TD510_DEVADDR,
+ dp83td510_errata[i].reg,
+ dp83td510_errata[i].val);
+
+ if (ret)
+ return ret;
+ }
+
+ return phy_set_bits(phydev, DP83TD510_CTRL_REG, DP83TD510_SW_RESET);
+}
+
+static int dp83td510_set_master_slave(struct phy_device *phydev)
+{
+ int mst_slave_cfg;
+
+ mst_slave_cfg = phy_read_mmd(phydev, DP83TD510_PMD_DEVADDR,
+ DP83TD510_PMD_CTRL);
+ if (mst_slave_cfg < 0)
+ return mst_slave_cfg;
+
+ if (mst_slave_cfg & DP83TD510_MASTER_MODE) {
+ if (phydev->autoneg) {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+ phydev->master_slave_set = MASTER_SLAVE_CFG_MASTER_PREFERRED;
+ } else {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_MASTER_FORCE;
+ phydev->master_slave_set = MASTER_SLAVE_CFG_MASTER_FORCE;
+ }
+ } else {
+ if (phydev->autoneg) {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+ phydev->master_slave_set = MASTER_SLAVE_CFG_SLAVE_PREFERRED;
+ } else {
+ phydev->master_slave_get = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ phydev->master_slave_set = MASTER_SLAVE_CFG_SLAVE_FORCE;
+ }
+ }
+
+ return 0;
+}
+
+static int dp83td510_read_status(struct phy_device *phydev)
+{
+ int phy_status;
+ int phy_bmcr;
+
+ phy_status = phy_read(phydev, DP83TD510_PHY_STAT);
+ if (phy_status < 0)
+ return phy_status;
+
+ phy_bmcr = phy_read(phydev, MII_BMCR);
+ if (phy_bmcr < 0)
+ return phy_bmcr;
+
+ phydev->link = phy_status & DP83TD510_LINK_STS;
+ if (phydev->link) {
+ phydev->duplex = phy_bmcr & BMCR_FULLDPLX ? DUPLEX_FULL : DUPLEX_HALF;
+ phydev->speed = SPEED_10;
+ } else {
+ phydev->speed = SPEED_UNKNOWN;
+ phydev->duplex = DUPLEX_UNKNOWN;
+ }
+
+ return dp83td510_set_master_slave(phydev);
+}
+
+static int dp83td510_get_features(struct phy_device *phydev)
+{
+ linkmode_set_bit_array(dp83td510_feature_array,
+ ARRAY_SIZE(dp83td510_feature_array),
+ phydev->supported);
+
+ return 0;
+}
+
+static int dp83td510_config_aneg(struct phy_device *phydev)
+{
+ int pmd_ctrl;
+
+ switch (phydev->master_slave_set) {
+ case MASTER_SLAVE_CFG_MASTER_PREFERRED:
+ case MASTER_SLAVE_CFG_MASTER_FORCE:
+ pmd_ctrl = DP83TD510_MASTER_MODE;
+ break;
+ case MASTER_SLAVE_CFG_SLAVE_PREFERRED:
+ case MASTER_SLAVE_CFG_SLAVE_FORCE:
+ pmd_ctrl = 0;
+ break;
+ case MASTER_SLAVE_CFG_UNKNOWN:
+ case MASTER_SLAVE_CFG_UNSUPPORTED:
+ default:
+ phydev_warn(phydev, "Unsupported Master/Slave mode\n");
+ return -ENOTSUPP;
+ }
+
+ return dp83td510_configure_mode(phydev, pmd_ctrl);
+}
+
+static int dp83td510_config_init(struct phy_device *phydev)
+{
+ struct dp83td510_private *dp83td510 = phydev->priv;
+ int ret = 0;
+
+ if (phy_interface_is_rgmii(phydev)) {
+ if (dp83td510->rgmii_delay) {
+ ret = phy_set_bits_mmd(phydev, DP83TD510_DEVADDR,
+ DP83TD510_MAC_CFG_1,
+ dp83td510->rgmii_delay);
+ if (ret)
+ return ret;
+ }
+ }
+
+ if (phydev->interface == PHY_INTERFACE_MODE_RMII) {
+ ret = phy_modify(phydev, DP83TD510_GEN_CFG,
+ DP83TD510_FIFO_DEPTH_MASK,
+ dp83td510->tx_fifo_depth);
+ if (ret)
+ return ret;
+ }
+
+ return dp83td510_set_master_slave(phydev);
+}
+
+static int dp83td510_phy_reset(struct phy_device *phydev)
+{
+ int ret;
+
+ ret = phy_set_bits(phydev, DP83TD510_CTRL_REG, DP83TD510_SW_RESET);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(10, 20);
+
+ return dp83td510_config_init(phydev);
+}
+
+#if IS_ENABLED(CONFIG_OF_MDIO)
+static int dp83td510_of_init(struct phy_device *phydev)
+{
+ struct dp83td510_private *dp83td510 = phydev->priv;
+ struct device *dev = &phydev->mdio.dev;
+ struct device_node *of_node = dev->of_node;
+ s32 rx_int_delay;
+ s32 tx_int_delay;
+ int ret;
+
+ if (!of_node)
+ return -ENODEV;
+
+ ret = of_property_read_u32(phydev->mdio.dev.of_node,
+ "max-tx-rx-p2p-microvolt",
+ &dp83td510->hi_diff_output);
+ if (ret)
+ dp83td510->hi_diff_output = DP83TD510_2_4V_P2P;
+
+ if (dp83td510->hi_diff_output != DP83TD510_2_4V_P2P &&
+ dp83td510->hi_diff_output != DP83TD510_1_1V_P2P)
+ return -EINVAL;
+
+ if (of_property_read_u32(phydev->mdio.dev.of_node, "tx-fifo-depth",
+ &dp83td510->tx_fifo_depth))
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_5_B_NIB;
+
+ switch (dp83td510->tx_fifo_depth) {
+ case 4:
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_4_B_NIB;
+ break;
+ case 6:
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_6_B_NIB;
+ break;
+ case 8:
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_8_B_NIB;
+ break;
+ case 5:
+ default:
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_5_B_NIB;
+ }
+
+ rx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, true);
+ if (rx_int_delay <= 0)
+ dp83td510->rgmii_delay = DP83TD510_RX_CLK_SHIFT;
+ else
+ dp83td510->rgmii_delay = 0;
+
+ tx_int_delay = phy_get_internal_delay(phydev, dev, NULL, 0, false);
+ if (tx_int_delay <= 0)
+ dp83td510->rgmii_delay |= DP83TD510_TX_CLK_SHIFT;
+ else
+ dp83td510->rgmii_delay &= ~DP83TD510_TX_CLK_SHIFT;
+
+ return 0;
+}
+#else
+static int dp83869_of_init(struct phy_device *phydev)
+{
+ dp83td510->hi_diff_output = DP83TD510_2_4V_P2P
+ dp83td510->tx_fifo_depth = DP83TD510_FIFO_DEPTH_5_B_NIB
+ return 0;
+}
+#endif /* CONFIG_OF_MDIO */
+
+static int dp83td510_probe(struct phy_device *phydev)
+{
+ struct dp83td510_private *dp83td510;
+ int ret;
+
+ dp83td510 = devm_kzalloc(&phydev->mdio.dev, sizeof(*dp83td510),
+ GFP_KERNEL);
+ if (!dp83td510)
+ return -ENOMEM;
+
+ phydev->priv = dp83td510;
+
+ ret = dp83td510_of_init(phydev);
+ if (ret)
+ return ret;
+
+ return dp83td510_config_init(phydev);
+}
+
+static struct phy_driver dp83td510_driver[] = {
+ {
+ PHY_ID_MATCH_MODEL(DP83TD510E_PHY_ID),
+ .name = "TI DP83TD510E",
+ .probe = dp83td510_probe,
+ .config_init = dp83td510_config_init,
+ .soft_reset = dp83td510_phy_reset,
+
+ /* IRQ related */
+ .handle_interrupt = dp83td510_handle_interrupt,
+ .config_intr = dp83td510_config_intr,
+ .config_aneg = dp83td510_config_aneg,
+ .read_status = dp83td510_read_status,
+
+ .get_features = dp83td510_get_features,
+
+ .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("Dan Murphy <dmurphy@xxxxxx");
+MODULE_LICENSE("GPL v2");
--
2.29.2