// SPDX-License-Identifier: GPL-2.0+ #include #include #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 "); 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);