Add support for Airoha AN8855 Internal Switch Gigabit PHY. This is a simple PHY driver to configure and calibrate the PHY for the AN8855 Switch. Signed-off-by: Christian Marangi <ansuelsmth@xxxxxxxxx> --- MAINTAINERS | 1 + drivers/net/phy/Kconfig | 5 + drivers/net/phy/Makefile | 1 + drivers/net/phy/air_an8855.c | 187 +++++++++++++++++++++++++++++++++++ 4 files changed, 194 insertions(+) create mode 100644 drivers/net/phy/air_an8855.c diff --git a/MAINTAINERS b/MAINTAINERS index e3077d9feee2..cf34add2a0bb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -726,6 +726,7 @@ S: Maintained F: Documentation/devicetree/bindings/net/dsa/airoha,an8855.yaml F: drivers/net/dsa/an8855.c F: drivers/net/dsa/an8855.h +F: drivers/net/phy/air_an8855.c AIROHA ETHERNET DRIVER M: Lorenzo Bianconi <lorenzo@xxxxxxxxxx> diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig index ee3ea0b56d48..1d474038ea7f 100644 --- a/drivers/net/phy/Kconfig +++ b/drivers/net/phy/Kconfig @@ -79,6 +79,11 @@ config SFP comment "MII PHY device drivers" +config AIR_AN8855_PHY + tristate "Airoha AN8855 Internal Gigabit PHY" + help + Currently supports the internal Airoha AN8855 Switch PHY. + config AIR_EN8811H_PHY tristate "Airoha EN8811H 2.5 Gigabit PHY" help diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile index 90f886844381..baba7894785b 100644 --- a/drivers/net/phy/Makefile +++ b/drivers/net/phy/Makefile @@ -35,6 +35,7 @@ obj-y += $(sfp-obj-y) $(sfp-obj-m) obj-$(CONFIG_ADIN_PHY) += adin.o obj-$(CONFIG_ADIN1100_PHY) += adin1100.o +obj-$(CONFIG_AIR_AN8855_PHY) += air_an8855.o obj-$(CONFIG_AIR_EN8811H_PHY) += air_en8811h.o obj-$(CONFIG_AMD_PHY) += amd.o obj-$(CONFIG_AMCC_QT2025_PHY) += qt2025.o diff --git a/drivers/net/phy/air_an8855.c b/drivers/net/phy/air_an8855.c new file mode 100644 index 000000000000..d40b0b17a2e4 --- /dev/null +++ b/drivers/net/phy/air_an8855.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <linux/phy.h> +#include <linux/module.h> +#include <linux/bitfield.h> + +#define AN8855_PHY_PAGE_CTRL 0x1f +#define AN8855_PHY_NORMAL_PAGE 0x0 +#define AN8855_PHY_EXT_PAGE 0x1 + +#define AN8855_PHY_EXT_REG_14 0x14 +#define AN8855_PHY_EN_DOWN_SHFIT BIT(4) + +/* R50 Calibration regs in MDIO_MMD_VEND1 */ +#define AN8855_PHY_R500HM_RSEL_TX_AB 0x174 +#define AN8855_PHY_R50OHM_RSEL_TX_A_EN BIT(15) +#define AN8855_PHY_R50OHM_RSEL_TX_A GENMASK(14, 8) +#define AN8855_PHY_R50OHM_RSEL_TX_B_EN BIT(7) +#define AN8855_PHY_R50OHM_RSEL_TX_B GENMASK(6, 0) +#define AN8855_PHY_R500HM_RSEL_TX_CD 0x175 +#define AN8855_PHY_R50OHM_RSEL_TX_C_EN BIT(15) +#define AN8855_PHY_R50OHM_RSEL_TX_C GENMASK(14, 8) +#define AN8855_PHY_R50OHM_RSEL_TX_D_EN BIT(7) +#define AN8855_PHY_R50OHM_RSEL_TX_D GENMASK(6, 0) + +/* PHY TX PAIR DELAY SELECT Register */ +#define PHY_TX_PAIR_DLY_SEL_GBE 0x013 +/* PHY ADC Register */ +#define PHY_RXADC_CTRL 0x0d8 +#define PHY_RXADC_REV_0 0x0d9 +#define PHY_RXADC_REV_1 0x0da + +#define AN8855_PHY_ID 0xc0ff0410 + +static int an8855_config_init(struct phy_device *phydev) +{ + u8 calibration_data[4]; + int ret; + + memcpy(calibration_data, &phydev->dev_flags, sizeof(u32)); + + /* Enable HW auto downshift */ + ret = phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_EXT_PAGE); + if (ret) + return ret; + ret = phy_set_bits(phydev, AN8855_PHY_EXT_REG_14, + AN8855_PHY_EN_DOWN_SHFIT); + if (ret) + return ret; + ret = phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_NORMAL_PAGE); + if (ret) + return ret; + + /* Enable Asymmetric Pause Capability */ + ret = phy_set_bits(phydev, MII_ADVERTISE, ADVERTISE_PAUSE_ASYM); + if (ret) + return ret; + + /* Disable EEE */ + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0); + if (ret) + return ret; + + /* Apply calibration values, if needed. */ + if (phydev->dev_flags) { + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_AB, + AN8855_PHY_R50OHM_RSEL_TX_A | AN8855_PHY_R50OHM_RSEL_TX_B, + FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_A, calibration_data[0]) | + FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_B, calibration_data[1])); + if (ret) + return ret; + ret = phy_modify_mmd(phydev, MDIO_MMD_VEND1, AN8855_PHY_R500HM_RSEL_TX_CD, + AN8855_PHY_R50OHM_RSEL_TX_C | AN8855_PHY_R50OHM_RSEL_TX_D, + FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_C, calibration_data[2]) | + FIELD_PREP(AN8855_PHY_R50OHM_RSEL_TX_D, calibration_data[3])); + if (ret) + return ret; + } + + /* Apply values to decude signal noise */ + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, PHY_TX_PAIR_DLY_SEL_GBE, 0x4040); + if (ret) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, PHY_RXADC_CTRL, 0x1010); + if (ret) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, PHY_RXADC_REV_0, 0x100); + if (ret) + return ret; + ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, PHY_RXADC_REV_1, 0x100); + if (ret) + return ret; + + return 0; +} + +static int an8855_get_downshift(struct phy_device *phydev, u8 *data) +{ + int val; + int ret; + + ret = phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_EXT_PAGE); + if (ret) + return ret; + + val = phy_read(phydev, AN8855_PHY_EXT_REG_14); + *data = val & AN8855_PHY_EXT_REG_14 ? DOWNSHIFT_DEV_DEFAULT_COUNT : + DOWNSHIFT_DEV_DISABLE; + + ret = phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_NORMAL_PAGE); + if (ret) + return ret; + + return 0; +} + +static int an8855_set_downshift(struct phy_device *phydev, u8 cnt) +{ + int ret; + + ret = phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_EXT_PAGE); + if (ret) + return ret; + + if (cnt != DOWNSHIFT_DEV_DISABLE) { + ret = phy_set_bits(phydev, AN8855_PHY_EXT_REG_14, + AN8855_PHY_EN_DOWN_SHFIT); + if (ret) + return ret; + } else { + ret = phy_clear_bits(phydev, AN8855_PHY_EXT_REG_14, + AN8855_PHY_EN_DOWN_SHFIT); + if (ret) + return ret; + } + + return phy_write(phydev, AN8855_PHY_PAGE_CTRL, AN8855_PHY_NORMAL_PAGE); +} + +static int an8855_get_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return an8855_get_downshift(phydev, data); + default: + return -EOPNOTSUPP; + } +} + +static int an8855_set_tunable(struct phy_device *phydev, + struct ethtool_tunable *tuna, const void *data) +{ + switch (tuna->id) { + case ETHTOOL_PHY_DOWNSHIFT: + return an8855_set_downshift(phydev, *(const u8 *)data); + default: + return -EOPNOTSUPP; + } +} + +static struct phy_driver An8855_driver[] = { +{ + PHY_ID_MATCH_EXACT(AN8855_PHY_ID), + .name = "Airoha AN8855 internal PHY", + /* PHY_GBIT_FEATURES */ + .flags = PHY_IS_INTERNAL, + .config_init = an8855_config_init, + .soft_reset = genphy_soft_reset, + .get_tunable = an8855_get_tunable, + .set_tunable = an8855_set_tunable, + .suspend = genphy_suspend, + .resume = genphy_resume, +}, }; + +module_phy_driver(An8855_driver); + +static struct mdio_device_id __maybe_unused An8855_tbl[] = { + { PHY_ID_MATCH_EXACT(AN8855_PHY_ID) }, + { } +}; + +MODULE_DEVICE_TABLE(mdio, An8855_tbl); + +MODULE_DESCRIPTION("Airoha AN8855 PHY driver"); +MODULE_AUTHOR("Christian Marangi <ansuelsmth@xxxxxxxxx>"); +MODULE_LICENSE("GPL"); -- 2.45.2