diff options
Diffstat (limited to 'drivers/net/phy/qcom/qca83xx.c')
-rw-r--r-- | drivers/net/phy/qcom/qca83xx.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/drivers/net/phy/qcom/qca83xx.c b/drivers/net/phy/qcom/qca83xx.c new file mode 100644 index 0000000000..5d083ef025 --- /dev/null +++ b/drivers/net/phy/qcom/qca83xx.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <linux/phy.h> +#include <linux/module.h> + +#include "qcom.h" + +#define AT803X_DEBUG_REG_3C 0x3C + +#define AT803X_DEBUG_REG_GREEN 0x3D +#define AT803X_DEBUG_GATE_CLK_IN1000 BIT(6) + +#define MDIO_AZ_DEBUG 0x800D + +#define QCA8327_A_PHY_ID 0x004dd033 +#define QCA8327_B_PHY_ID 0x004dd034 +#define QCA8337_PHY_ID 0x004dd036 +#define QCA8K_PHY_ID_MASK 0xffffffff + +#define QCA8K_DEVFLAGS_REVISION_MASK GENMASK(2, 0) + +static struct at803x_hw_stat qca83xx_hw_stats[] = { + { "phy_idle_errors", 0xa, GENMASK(7, 0), PHY}, + { "phy_receive_errors", 0x15, GENMASK(15, 0), PHY}, + { "eee_wake_errors", 0x16, GENMASK(15, 0), MMD}, +}; + +struct qca83xx_priv { + u64 stats[ARRAY_SIZE(qca83xx_hw_stats)]; +}; + +MODULE_DESCRIPTION("Qualcomm Atheros QCA83XX PHY driver"); +MODULE_AUTHOR("Matus Ujhelyi"); +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_LICENSE("GPL"); + +static int qca83xx_get_sset_count(struct phy_device *phydev) +{ + return ARRAY_SIZE(qca83xx_hw_stats); +} + +static void qca83xx_get_strings(struct phy_device *phydev, u8 *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) { + strscpy(data + i * ETH_GSTRING_LEN, + qca83xx_hw_stats[i].string, ETH_GSTRING_LEN); + } +} + +static u64 qca83xx_get_stat(struct phy_device *phydev, int i) +{ + struct at803x_hw_stat stat = qca83xx_hw_stats[i]; + struct qca83xx_priv *priv = phydev->priv; + int val; + u64 ret; + + if (stat.access_type == MMD) + val = phy_read_mmd(phydev, MDIO_MMD_PCS, stat.reg); + else + val = phy_read(phydev, stat.reg); + + if (val < 0) { + ret = U64_MAX; + } else { + val = val & stat.mask; + priv->stats[i] += val; + ret = priv->stats[i]; + } + + return ret; +} + +static void qca83xx_get_stats(struct phy_device *phydev, + struct ethtool_stats *stats, u64 *data) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(qca83xx_hw_stats); i++) + data[i] = qca83xx_get_stat(phydev, i); +} + +static int qca83xx_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct qca83xx_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + phydev->priv = priv; + + return 0; +} + +static int qca83xx_config_init(struct phy_device *phydev) +{ + u8 switch_revision; + + switch_revision = phydev->dev_flags & QCA8K_DEVFLAGS_REVISION_MASK; + + switch (switch_revision) { + case 1: + /* For 100M waveform */ + at803x_debug_reg_write(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, 0x02ea); + /* Turn on Gigabit clock */ + at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x68a0); + break; + + case 2: + phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0); + fallthrough; + case 4: + phy_write_mmd(phydev, MDIO_MMD_PCS, MDIO_AZ_DEBUG, 0x803f); + at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_GREEN, 0x6860); + at803x_debug_reg_write(phydev, AT803X_DEBUG_SYSTEM_CTRL_MODE, 0x2c46); + at803x_debug_reg_write(phydev, AT803X_DEBUG_REG_3C, 0x6000); + break; + } + + /* Following original QCA sourcecode set port to prefer master */ + phy_set_bits(phydev, MII_CTRL1000, CTL1000_PREFER_MASTER); + + return 0; +} + +static int qca8327_config_init(struct phy_device *phydev) +{ + /* QCA8327 require DAC amplitude adjustment for 100m set to +6%. + * Disable on init and enable only with 100m speed following + * qca original source code. + */ + at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, + QCA8327_DEBUG_MANU_CTRL_EN, 0); + + return qca83xx_config_init(phydev); +} + +static void qca83xx_link_change_notify(struct phy_device *phydev) +{ + /* Set DAC Amplitude adjustment to +6% for 100m on link running */ + if (phydev->state == PHY_RUNNING) { + if (phydev->speed == SPEED_100) + at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, + QCA8327_DEBUG_MANU_CTRL_EN, + QCA8327_DEBUG_MANU_CTRL_EN); + } else { + /* Reset DAC Amplitude adjustment */ + at803x_debug_reg_mask(phydev, AT803X_DEBUG_ANALOG_TEST_CTRL, + QCA8327_DEBUG_MANU_CTRL_EN, 0); + } +} + +static int qca83xx_resume(struct phy_device *phydev) +{ + int ret, val; + + /* Skip reset if not suspended */ + if (!phydev->suspended) + return 0; + + /* Reinit the port, reset values set by suspend */ + qca83xx_config_init(phydev); + + /* Reset the port on port resume */ + phy_set_bits(phydev, MII_BMCR, BMCR_RESET | BMCR_ANENABLE); + + /* On resume from suspend the switch execute a reset and + * restart auto-negotiation. Wait for reset to complete. + */ + ret = phy_read_poll_timeout(phydev, MII_BMCR, val, !(val & BMCR_RESET), + 50000, 600000, true); + if (ret) + return ret; + + usleep_range(1000, 2000); + + return 0; +} + +static int qca83xx_suspend(struct phy_device *phydev) +{ + at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_GREEN, + AT803X_DEBUG_GATE_CLK_IN1000, 0); + + at803x_debug_reg_mask(phydev, AT803X_DEBUG_REG_HIB_CTRL, + AT803X_DEBUG_HIB_CTRL_EN_ANY_CHANGE | + AT803X_DEBUG_HIB_CTRL_SEL_RST_80U, 0); + + return 0; +} + +static int qca8337_suspend(struct phy_device *phydev) +{ + /* Only QCA8337 support actual suspend. */ + genphy_suspend(phydev); + + return qca83xx_suspend(phydev); +} + +static int qca8327_suspend(struct phy_device *phydev) +{ + u16 mask = 0; + + /* QCA8327 cause port unreliability when phy suspend + * is set. + */ + mask |= ~(BMCR_SPEED1000 | BMCR_FULLDPLX); + phy_modify(phydev, MII_BMCR, mask, 0); + + return qca83xx_suspend(phydev); +} + +static struct phy_driver qca83xx_driver[] = { +{ + /* QCA8337 */ + .phy_id = QCA8337_PHY_ID, + .phy_id_mask = QCA8K_PHY_ID_MASK, + .name = "Qualcomm Atheros 8337 internal PHY", + /* PHY_GBIT_FEATURES */ + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca83xx_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8337_suspend, + .resume = qca83xx_resume, +}, { + /* QCA8327-A from switch QCA8327-AL1A */ + .phy_id = QCA8327_A_PHY_ID, + .phy_id_mask = QCA8K_PHY_ID_MASK, + .name = "Qualcomm Atheros 8327-A internal PHY", + /* PHY_GBIT_FEATURES */ + .link_change_notify = qca83xx_link_change_notify, + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca8327_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8327_suspend, + .resume = qca83xx_resume, +}, { + /* QCA8327-B from switch QCA8327-BL1A */ + .phy_id = QCA8327_B_PHY_ID, + .phy_id_mask = QCA8K_PHY_ID_MASK, + .name = "Qualcomm Atheros 8327-B internal PHY", + /* PHY_GBIT_FEATURES */ + .link_change_notify = qca83xx_link_change_notify, + .probe = qca83xx_probe, + .flags = PHY_IS_INTERNAL, + .config_init = qca8327_config_init, + .soft_reset = genphy_soft_reset, + .get_sset_count = qca83xx_get_sset_count, + .get_strings = qca83xx_get_strings, + .get_stats = qca83xx_get_stats, + .suspend = qca8327_suspend, + .resume = qca83xx_resume, +}, }; + +module_phy_driver(qca83xx_driver); + +static struct mdio_device_id __maybe_unused qca83xx_tbl[] = { + { PHY_ID_MATCH_EXACT(QCA8337_PHY_ID) }, + { PHY_ID_MATCH_EXACT(QCA8327_A_PHY_ID) }, + { PHY_ID_MATCH_EXACT(QCA8327_B_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, qca83xx_tbl); |