mirror of
https://github.com/torvalds/linux.git
synced 2026-05-31 02:24:24 +02:00
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: 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>
This commit is contained in:
parent
82eaf94d69
commit
d46502279a
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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) },
|
||||
{ }
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user