net: phy: adin1100: Add initial support for ADIN1100 industrial PHY
authorAlexandru Ardelean <alexandru.ardelean@analog.com>
Fri, 29 Apr 2022 15:34:35 +0000 (18:34 +0300)
committerDavid S. Miller <davem@davemloft.net>
Sun, 1 May 2022 16:45:35 +0000 (17:45 +0100)
The ADIN1100 is a low power single port 10BASE-T1L transceiver designed for
industrial Ethernet applications and is compliant with the IEEE 802.3cg
Ethernet standard for long reach 10 Mb/s Single Pair Ethernet.

Signed-off-by: Alexandru Ardelean <alexandru.ardelean@analog.com>
Signed-off-by: Alexandru Tachici <alexandru.tachici@analog.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
drivers/net/phy/Kconfig
drivers/net/phy/Makefile
drivers/net/phy/adin1100.c [new file with mode: 0644]

index ea7571a2b39be82c76389a31218e98ace644f699..bbbf6c07ea530518d3b10b256656917b57a3d885 100644 (file)
@@ -83,6 +83,13 @@ config ADIN_PHY
          - ADIN1300 - Robust,Industrial, Low Latency 10/100/1000 Gigabit
            Ethernet PHY
 
+config ADIN1100_PHY
+       tristate "Analog Devices Industrial Ethernet T1L PHYs"
+       help
+         Adds support for the Analog Devices Industrial T1L Ethernet PHYs.
+         Currently supports the:
+         - ADIN1100 - Robust,Industrial, Low Power 10BASE-T1L Ethernet PHY
+
 config AQUANTIA_PHY
        tristate "Aquantia PHYs"
        help
index b2728d00fc9a1fceea483845d4c977c8b6fd79ff..b82651b57043abf35130f28d18c70705a33742a9 100644 (file)
@@ -31,6 +31,7 @@ sfp-obj-$(CONFIG_SFP)         += sfp-bus.o
 obj-y                          += $(sfp-obj-y) $(sfp-obj-m)
 
 obj-$(CONFIG_ADIN_PHY)         += adin.o
+obj-$(CONFIG_ADIN1100_PHY)     += adin1100.o
 obj-$(CONFIG_AMD_PHY)          += amd.o
 aquantia-objs                  += aquantia_main.o
 ifdef CONFIG_HWMON
diff --git a/drivers/net/phy/adin1100.c b/drivers/net/phy/adin1100.c
new file mode 100644 (file)
index 0000000..f20bbef
--- /dev/null
@@ -0,0 +1,240 @@
+// SPDX-License-Identifier: (GPL-2.0+ OR BSD-3-Clause)
+/*
+ *  Driver for Analog Devices Industrial Ethernet T1L PHYs
+ *
+ * Copyright 2020 Analog Devices Inc.
+ */
+#include <linux/kernel.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/mii.h>
+#include <linux/phy.h>
+#include <linux/property.h>
+
+#define PHY_ID_ADIN1100                                0x0283bc81
+
+#define ADIN_FORCED_MODE                       0x8000
+#define   ADIN_FORCED_MODE_EN                  BIT(0)
+
+#define ADIN_CRSM_SFT_RST                      0x8810
+#define   ADIN_CRSM_SFT_RST_EN                 BIT(0)
+
+#define ADIN_CRSM_SFT_PD_CNTRL                 0x8812
+#define   ADIN_CRSM_SFT_PD_CNTRL_EN            BIT(0)
+
+#define ADIN_AN_PHY_INST_STATUS                        0x8030
+#define   ADIN_IS_CFG_SLV                      BIT(2)
+#define   ADIN_IS_CFG_MST                      BIT(3)
+
+#define ADIN_CRSM_STAT                         0x8818
+#define   ADIN_CRSM_SFT_PD_RDY                 BIT(1)
+#define   ADIN_CRSM_SYS_RDY                    BIT(0)
+
+/**
+ * struct adin_priv - ADIN PHY driver private data
+ * @tx_level_2v4_able:         set if the PHY supports 2.4V TX levels (10BASE-T1L)
+ * @tx_level_2v4:              set if the PHY requests 2.4V TX levels (10BASE-T1L)
+ * @tx_level_prop_present:     set if the TX level is specified in DT
+ */
+struct adin_priv {
+       unsigned int            tx_level_2v4_able:1;
+       unsigned int            tx_level_2v4:1;
+       unsigned int            tx_level_prop_present:1;
+};
+
+static int adin_read_status(struct phy_device *phydev)
+{
+       int ret;
+
+       ret = genphy_c45_read_status(phydev);
+       if (ret)
+               return ret;
+
+       ret = phy_read_mmd(phydev, MDIO_MMD_AN, ADIN_AN_PHY_INST_STATUS);
+       if (ret < 0)
+               return ret;
+
+       if (ret & ADIN_IS_CFG_SLV)
+               phydev->master_slave_state = MASTER_SLAVE_STATE_SLAVE;
+
+       if (ret & ADIN_IS_CFG_MST)
+               phydev->master_slave_state = MASTER_SLAVE_STATE_MASTER;
+
+       return 0;
+}
+
+static int adin_config_aneg(struct phy_device *phydev)
+{
+       struct adin_priv *priv = phydev->priv;
+       int ret;
+
+       if (phydev->autoneg == AUTONEG_DISABLE) {
+               ret = genphy_c45_pma_setup_forced(phydev);
+               if (ret < 0)
+                       return ret;
+
+               if (priv->tx_level_prop_present && priv->tx_level_2v4)
+                       ret = phy_set_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
+                                              MDIO_PMA_10T1L_CTRL_2V4_EN);
+               else
+                       ret = phy_clear_bits_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_B10L_PMA_CTRL,
+                                                MDIO_PMA_10T1L_CTRL_2V4_EN);
+               if (ret < 0)
+                       return ret;
+
+               /* Force PHY to use above configurations */
+               return phy_set_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
+       }
+
+       ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, ADIN_FORCED_MODE, ADIN_FORCED_MODE_EN);
+       if (ret < 0)
+               return ret;
+
+       /* Request increased transmit level from LP. */
+       if (priv->tx_level_prop_present && priv->tx_level_2v4) {
+               ret = phy_set_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
+                                      MDIO_AN_T1_ADV_H_10L_TX_HI |
+                                      MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
+               if (ret < 0)
+                       return ret;
+       }
+
+       /* Disable 2.4 Vpp transmit level. */
+       if ((priv->tx_level_prop_present && !priv->tx_level_2v4) || !priv->tx_level_2v4_able) {
+               ret = phy_clear_bits_mmd(phydev, MDIO_MMD_AN, MDIO_AN_T1_ADV_H,
+                                        MDIO_AN_T1_ADV_H_10L_TX_HI |
+                                        MDIO_AN_T1_ADV_H_10L_TX_HI_REQ);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return genphy_c45_config_aneg(phydev);
+}
+
+static int adin_set_powerdown_mode(struct phy_device *phydev, bool en)
+{
+       int ret;
+       int val;
+
+       val = en ? ADIN_CRSM_SFT_PD_CNTRL_EN : 0;
+       ret = phy_write_mmd(phydev, MDIO_MMD_VEND1,
+                           ADIN_CRSM_SFT_PD_CNTRL, val);
+       if (ret < 0)
+               return ret;
+
+       return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
+                                        (ret & ADIN_CRSM_SFT_PD_RDY) == val,
+                                        1000, 30000, true);
+}
+
+static int adin_suspend(struct phy_device *phydev)
+{
+       return adin_set_powerdown_mode(phydev, true);
+}
+
+static int adin_resume(struct phy_device *phydev)
+{
+       return adin_set_powerdown_mode(phydev, false);
+}
+
+static int adin_set_loopback(struct phy_device *phydev, bool enable)
+{
+       if (enable)
+               return phy_set_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
+                                       BMCR_LOOPBACK);
+
+       /* PCS loopback (according to 10BASE-T1L spec) */
+       return phy_clear_bits_mmd(phydev, MDIO_MMD_PCS, MDIO_PCS_10T1L_CTRL,
+                                BMCR_LOOPBACK);
+}
+
+static int adin_soft_reset(struct phy_device *phydev)
+{
+       int ret;
+
+       ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND1, ADIN_CRSM_SFT_RST, ADIN_CRSM_SFT_RST_EN);
+       if (ret < 0)
+               return ret;
+
+       return phy_read_mmd_poll_timeout(phydev, MDIO_MMD_VEND1, ADIN_CRSM_STAT, ret,
+                                        (ret & ADIN_CRSM_SYS_RDY),
+                                        10000, 30000, true);
+}
+
+static int adin_get_features(struct phy_device *phydev)
+{
+       struct adin_priv *priv = phydev->priv;
+       struct device *dev = &phydev->mdio.dev;
+       int ret;
+       u8 val;
+
+       ret = phy_read_mmd(phydev, MDIO_MMD_PMAPMD, MDIO_PMA_10T1L_STAT);
+       if (ret < 0)
+               return ret;
+
+       /* This depends on the voltage level from the power source */
+       priv->tx_level_2v4_able = !!(ret & MDIO_PMA_10T1L_STAT_2V4_ABLE);
+
+       phydev_dbg(phydev, "PHY supports 2.4V TX level: %s\n",
+                  priv->tx_level_2v4_able ? "yes" : "no");
+
+       priv->tx_level_prop_present = device_property_present(dev, "phy-10base-t1l-2.4vpp");
+       if (priv->tx_level_prop_present) {
+               ret = device_property_read_u8(dev, "phy-10base-t1l-2.4vpp", &val);
+               if (ret < 0)
+                       return ret;
+
+               priv->tx_level_2v4 = val;
+               if (!priv->tx_level_2v4 && priv->tx_level_2v4_able)
+                       phydev_info(phydev,
+                                   "PHY supports 2.4V TX level, but disabled via config\n");
+       }
+
+       linkmode_set_bit_array(phy_basic_ports_array, ARRAY_SIZE(phy_basic_ports_array),
+                              phydev->supported);
+
+       return genphy_c45_pma_read_abilities(phydev);
+}
+
+static int adin_probe(struct phy_device *phydev)
+{
+       struct device *dev = &phydev->mdio.dev;
+       struct adin_priv *priv;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       phydev->priv = priv;
+
+       return 0;
+}
+
+static struct phy_driver adin_driver[] = {
+       {
+               PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100),
+               .name                   = "ADIN1100",
+               .get_features           = adin_get_features,
+               .soft_reset             = adin_soft_reset,
+               .probe                  = adin_probe,
+               .config_aneg            = adin_config_aneg,
+               .read_status            = adin_read_status,
+               .set_loopback           = adin_set_loopback,
+               .suspend                = adin_suspend,
+               .resume                 = adin_resume,
+       },
+};
+
+module_phy_driver(adin_driver);
+
+static struct mdio_device_id __maybe_unused adin_tbl[] = {
+       { PHY_ID_MATCH_MODEL(PHY_ID_ADIN1100) },
+       { }
+};
+
+MODULE_DEVICE_TABLE(mdio, adin_tbl);
+MODULE_DESCRIPTION("Analog Devices Industrial Ethernet T1L PHY driver");
+MODULE_LICENSE("Dual BSD/GPL");