Commit d3b2afa9 authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'add-support-for-the-ipq5018-internal-ge-phy'

George Moussalem says:

====================
Add support for the IPQ5018 Internal GE PHY

The IPQ5018 SoC contains an internal Gigabit Ethernet PHY with its
output pins that provide an MDI interface to either an external switch
in a PHY to PHY link architecture or directly to an attached RJ45
connector.

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

The LDO controller found in the IPQ5018 SoC needs to be enabled to drive
power to the CMN Ethernet Block (CMN BLK) which the GE PHY depends on.
The LDO must be enabled in TCSR by writing to a specific register.

In a phy to phy architecture, DAC values need to be set to accommodate
for the short cable length.
====================

Link: https://patch.msgid.link/20250613-ipq5018-ge-phy-v5-0-9af06e34ea6b@outlook.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents 27390db9 d4650227
Loading
Loading
Loading
Loading
+43 −0
Original line number Diff line number Diff line
@@ -16,8 +16,37 @@ description: |

allOf:
  - $ref: ethernet-phy.yaml#
  - if:
      properties:
        compatible:
          contains:
            enum:
              - ethernet-phy-id004d.d0c0

    then:
      properties:
        reg:
          const: 7  # This PHY is always at MDIO address 7 in the IPQ5018 SoC

        resets:
          items:
            - description:
                GE PHY MISC reset which triggers a reset across MDC, DSP, RX, and TX lines.

        qcom,dac-preset-short-cable:
          description:
            Set if this phy is connected to another phy to adjust the values for
            MDAC and EDAC to adjust amplitude, bias current settings, and error
            detection and correction algorithm to accommodate for short cable length.
            If not set, DAC values are not modified and it is assumed the MDI output pins
            of this PHY are directly connected to an RJ45 connector.
          type: boolean

properties:
  compatible:
    enum:
      - ethernet-phy-id004d.d0c0

  qca,clk-out-frequency:
    description: Clock output frequency in Hertz.
    $ref: /schemas/types.yaml#/definitions/uint32
@@ -132,3 +161,17 @@ examples:
            };
        };
    };
  - |
    #include <dt-bindings/reset/qcom,gcc-ipq5018.h>

    mdio {
        #address-cells = <1>;
        #size-cells = <0>;

        ge_phy: ethernet-phy@7 {
            compatible = "ethernet-phy-id004d.d0c0";
            reg = <7>;

            resets = <&gcc GCC_GEPHY_MISC_ARES>;
        };
    };
+1 −1
Original line number Diff line number Diff line
@@ -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"
+167 −0
Original line number Diff line number Diff line
@@ -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
@@ -108,6 +111,48 @@
/* 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) },
	{ }
};