Commit d4650227 authored by George Moussalem's avatar George Moussalem Committed by Jakub Kicinski
Browse files

net: phy: qcom: at803x: Add Qualcomm IPQ5018 Internal PHY support



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: default avatarAndrew Lunn <andrew@lunn.ch>
Signed-off-by: default avatarGeorge Moussalem <george.moussalem@outlook.com>
Link: https://patch.msgid.link/20250613-ipq5018-ge-phy-v5-2-9af06e34ea6b@outlook.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent 82eaf94d
Loading
Loading
Loading
Loading
+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) },
	{ }
};