]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: phy: qcom: at803x: Add Qualcomm IPQ5018 Internal PHY support
authorGeorge Moussalem <george.moussalem@outlook.com>
Fri, 13 Jun 2025 01:55:08 +0000 (05:55 +0400)
committerJakub Kicinski <kuba@kernel.org>
Mon, 23 Jun 2025 18:14:05 +0000 (11:14 -0700)
The IPQ5018 SoC contains a single internal Gigabit Ethernet PHY which
provides an MDI interface directly to an RJ45 connector or an external
switch over a PHY to PHY link.

The PHY supports 10BASE-T/100BASE-TX/1000BASE-T link modes in SGMII
interface mode, CDT, auto-negotiation and 802.3az EEE.

Let's add support for this PHY in the at803x driver as it falls within
the Qualcomm Atheros OUI.

Reviewed-by: Andrew Lunn <andrew@lunn.ch>
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
Link: https://patch.msgid.link/20250613-ipq5018-ge-phy-v5-2-9af06e34ea6b@outlook.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
drivers/net/phy/qcom/Kconfig
drivers/net/phy/qcom/at803x.c

index bba14be8da2f23b482dfcf7f7ecd57cafadb72e8..06e8430c13b16818fe5cab0eaed2d7d6908a0305 100644 (file)
@@ -7,7 +7,7 @@ config AT803X_PHY
        select QCOM_NET_PHYLIB
        depends on REGULATOR
        help
-         Currently supports the AR8030, AR8031, AR8033, AR8035 model
+         Currently supports the AR8030, AR8031, AR8033, AR8035, IPQ5018 model
 
 config QCA83XX_PHY
        tristate "Qualcomm Atheros QCA833x PHYs"
index 26350b962890b0321153d74758b13d817407d094..43e604171828ce35d5950e02b1d08ee3e4523fdc 100644 (file)
@@ -19,6 +19,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/of.h>
 #include <linux/phylink.h>
+#include <linux/reset.h>
 #include <linux/sfp.h>
 #include <dt-bindings/net/qca-ar803x.h>
 
@@ -96,6 +97,8 @@
 #define ATH8035_PHY_ID                         0x004dd072
 #define AT8030_PHY_ID_MASK                     0xffffffef
 
+#define IPQ5018_PHY_ID                         0x004dd0c0
+
 #define QCA9561_PHY_ID                         0x004dd042
 
 #define AT803X_PAGE_FIBER                      0
 /* disable hibernation mode */
 #define AT803X_DISABLE_HIBERNATION_MODE                BIT(2)
 
+#define IPQ5018_PHY_FIFO_CONTROL               0x19
+#define IPQ5018_PHY_FIFO_RESET                 GENMASK(1, 0)
+
+#define IPQ5018_PHY_DEBUG_EDAC                 0x4380
+#define IPQ5018_PHY_MMD1_MDAC                  0x8100
+#define IPQ5018_PHY_DAC_MASK                   GENMASK(15, 8)
+
+/* MDAC and EDAC values for short cable length */
+#define IPQ5018_PHY_DEBUG_EDAC_VAL             0x10
+#define IPQ5018_PHY_MMD1_MDAC_VAL              0x10
+
+#define IPQ5018_PHY_MMD1_MSE_THRESH1           0x1000
+#define IPQ5018_PHY_MMD1_MSE_THRESH2           0x1001
+#define IPQ5018_PHY_PCS_EEE_TX_TIMER           0x8008
+#define IPQ5018_PHY_PCS_EEE_RX_TIMER           0x8009
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3       0x8074
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4       0x8075
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5       0x8076
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6       0x8077
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7       0x8078
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9       0x807a
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13      0x807e
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL14      0x807f
+
+#define IPQ5018_PHY_MMD1_MSE_THRESH1_VAL       0xf1
+#define IPQ5018_PHY_MMD1_MSE_THRESH2_VAL       0x1f6
+#define IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL       0x7880
+#define IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL       0xc8
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL   0xc040
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL   0xa060
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL   0xc040
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL   0xa060
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL   0xc24c
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL   0xc060
+#define IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL  0xb060
+#define IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL   0x90b0
+
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE                0x1
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK   GENMASK(7, 4)
+#define IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT        0x50
+#define IPQ5018_PHY_DEBUG_ANA_DAC_FILTER       0xa080
+
 MODULE_DESCRIPTION("Qualcomm Atheros AR803x PHY driver");
 MODULE_AUTHOR("Matus Ujhelyi");
 MODULE_LICENSE("GPL");
@@ -133,6 +178,11 @@ struct at803x_context {
        u16 led_control;
 };
 
+struct ipq5018_priv {
+       struct reset_control *rst;
+       bool set_short_cable_dac;
+};
+
 static int at803x_write_page(struct phy_device *phydev, int page)
 {
        int mask;
@@ -987,6 +1037,109 @@ static int at8035_probe(struct phy_device *phydev)
        return at8035_parse_dt(phydev);
 }
 
+static int ipq5018_cable_test_start(struct phy_device *phydev)
+{
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL3_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL4,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL4_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL5,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL5_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL6,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL6_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL7,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL7_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL9,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL9_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL13,
+                     IPQ5018_PHY_PCS_CDT_THRESH_CTRL13_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_CDT_THRESH_CTRL3,
+                     IPQ5018_PHY_PCS_NEAR_ECHO_THRESH_VAL);
+
+       /* we do all the (time consuming) work later */
+       return 0;
+}
+
+static int ipq5018_config_init(struct phy_device *phydev)
+{
+       struct ipq5018_priv *priv = phydev->priv;
+       u16 val;
+
+       /*
+        * set LDO efuse: first temporarily store ANA_DAC_FILTER value from
+        * debug register as it will be reset once the ANA_LDO_EFUSE register
+        * is written to
+        */
+       val = at803x_debug_reg_read(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER);
+       at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE,
+                             IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_MASK,
+                             IPQ5018_PHY_DEBUG_ANA_LDO_EFUSE_DEFAULT);
+       at803x_debug_reg_write(phydev, IPQ5018_PHY_DEBUG_ANA_DAC_FILTER, val);
+
+       /* set 8023AZ EEE TX and RX timer values */
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_TX_TIMER,
+                     IPQ5018_PHY_PCS_EEE_TX_TIMER_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PCS, IPQ5018_PHY_PCS_EEE_RX_TIMER,
+                     IPQ5018_PHY_PCS_EEE_RX_TIMER_VAL);
+
+       /* set MSE threshold values */
+       phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH1,
+                     IPQ5018_PHY_MMD1_MSE_THRESH1_VAL);
+       phy_write_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MSE_THRESH2,
+                     IPQ5018_PHY_MMD1_MSE_THRESH2_VAL);
+
+       /* PHY DAC values are optional and only set in a PHY to PHY link architecture */
+       if (priv->set_short_cable_dac) {
+               /* setting MDAC (Multi-level Digital-to-Analog Converter) in MMD1 */
+               phy_modify_mmd(phydev, MDIO_MMD_PMAPMD, IPQ5018_PHY_MMD1_MDAC,
+                              IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_MMD1_MDAC_VAL);
+
+               /* setting EDAC (Error-detection and Correction) in debug register */
+               at803x_debug_reg_mask(phydev, IPQ5018_PHY_DEBUG_EDAC,
+                                     IPQ5018_PHY_DAC_MASK, IPQ5018_PHY_DEBUG_EDAC_VAL);
+       }
+
+       return 0;
+}
+
+static void ipq5018_link_change_notify(struct phy_device *phydev)
+{
+       /*
+        * Reset the FIFO buffer upon link disconnects to clear any residual data
+        * which may cause issues with the FIFO which it cannot recover from.
+        */
+       mdiobus_modify_changed(phydev->mdio.bus, phydev->mdio.addr,
+                              IPQ5018_PHY_FIFO_CONTROL, IPQ5018_PHY_FIFO_RESET,
+                              phydev->link ? IPQ5018_PHY_FIFO_RESET : 0);
+}
+
+static int ipq5018_probe(struct phy_device *phydev)
+{
+       struct device *dev = &phydev->mdio.dev;
+       struct ipq5018_priv *priv;
+       int ret;
+
+       priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+       if (!priv)
+               return -ENOMEM;
+
+       priv->set_short_cable_dac = of_property_read_bool(dev->of_node,
+                                                         "qcom,dac-preset-short-cable");
+
+       priv->rst = devm_reset_control_array_get_exclusive(dev);
+       if (IS_ERR(priv->rst))
+               return dev_err_probe(dev, PTR_ERR(priv->rst),
+                                    "failed to acquire reset\n");
+
+       ret = reset_control_reset(priv->rst);
+       if (ret)
+               return dev_err_probe(dev, ret, "failed to reset\n");
+
+       phydev->priv = priv;
+
+       return 0;
+}
+
 static struct phy_driver at803x_driver[] = {
 {
        /* Qualcomm Atheros AR8035 */
@@ -1078,6 +1231,19 @@ static struct phy_driver at803x_driver[] = {
        .read_status            = at803x_read_status,
        .soft_reset             = genphy_soft_reset,
        .config_aneg            = at803x_config_aneg,
+}, {
+       PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID),
+       .name                   = "Qualcomm Atheros IPQ5018 internal PHY",
+       .flags                  = PHY_IS_INTERNAL | PHY_POLL_CABLE_TEST,
+       .probe                  = ipq5018_probe,
+       .config_init            = ipq5018_config_init,
+       .link_change_notify     = ipq5018_link_change_notify,
+       .read_status            = at803x_read_status,
+       .config_intr            = at803x_config_intr,
+       .handle_interrupt       = at803x_handle_interrupt,
+       .cable_test_start       = ipq5018_cable_test_start,
+       .cable_test_get_status  = qca808x_cable_test_get_status,
+       .soft_reset             = genphy_soft_reset,
 }, {
        /* Qualcomm Atheros QCA9561 */
        PHY_ID_MATCH_EXACT(QCA9561_PHY_ID),
@@ -1104,6 +1270,7 @@ static const struct mdio_device_id __maybe_unused atheros_tbl[] = {
        { PHY_ID_MATCH_EXACT(ATH8032_PHY_ID) },
        { PHY_ID_MATCH_EXACT(ATH8035_PHY_ID) },
        { PHY_ID_MATCH_EXACT(ATH9331_PHY_ID) },
+       { PHY_ID_MATCH_EXACT(IPQ5018_PHY_ID) },
        { PHY_ID_MATCH_EXACT(QCA9561_PHY_ID) },
        { }
 };