diff options
Diffstat (limited to 'drivers/net/phy/airoha')
| -rw-r--r-- | drivers/net/phy/airoha/Kconfig | 14 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/Makefile | 2 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/air_an8801.c | 594 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/air_en8811.c | 357 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/air_phy_lib.c | 216 | ||||
| -rw-r--r-- | drivers/net/phy/airoha/air_phy_lib.h | 39 |
6 files changed, 972 insertions, 250 deletions
diff --git a/drivers/net/phy/airoha/Kconfig b/drivers/net/phy/airoha/Kconfig index da8747939e3..2d58d674200 100644 --- a/drivers/net/phy/airoha/Kconfig +++ b/drivers/net/phy/airoha/Kconfig @@ -2,11 +2,25 @@ menuconfig PHY_AIROHA bool "Airoha Ethernet PHYs support" +config PHY_AIROHA_AN8801 + bool "Airoha Ethernet AN8801 support" + depends on PHY_AIROHA + select PHY_AIROHA_PHYLIB + help + Currently support AIROHA AN8801 1G PHY. + config PHY_AIROHA_EN8811 bool "Airoha Ethernet EN8811H support" depends on PHY_AIROHA depends on SUPPORTS_FW_LOADER select FW_LOADER + select PHY_AIROHA_PHYLIB + select PHY_COMMON_PROPS help AIROHA EN8811H supported. AIROHA AN8811HB supported. + +config PHY_AIROHA_PHYLIB + bool + help + Airoha Ethernet PHY common library diff --git a/drivers/net/phy/airoha/Makefile b/drivers/net/phy/airoha/Makefile index 84d23b19ab0..25e44004cfd 100644 --- a/drivers/net/phy/airoha/Makefile +++ b/drivers/net/phy/airoha/Makefile @@ -1,3 +1,5 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_PHY_AIROHA_AN8801) += air_an8801.o obj-$(CONFIG_PHY_AIROHA_EN8811) += air_en8811.o +obj-$(CONFIG_PHY_AIROHA_PHYLIB) += air_phy_lib.o diff --git a/drivers/net/phy/airoha/air_an8801.c b/drivers/net/phy/airoha/air_an8801.c new file mode 100644 index 00000000000..9d9958fc665 --- /dev/null +++ b/drivers/net/phy/airoha/air_an8801.c @@ -0,0 +1,594 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * air_an8801.c - PHY driver for Airoha AN8801. + * Copyright (c) 2026 Airoha Technology Corp. + * Copyright (C) 2026 BayLibre, SAS. + * Author: Kevin-KW Huang <[email protected]> + * Sita Huang <[email protected]> + * Julien Stephan <[email protected]> + */ + +#include <malloc.h> +#include <phy.h> +#include <dm/device_compat.h> + +#include "air_phy_lib.h" + +#define AN8801R_PHY_ID1 0xc0ff +#define AN8801R_PHY_ID2 0x0421 +#define AN8801R_PHY_ID ((u32)((AN8801R_PHY_ID1 << 16) | AN8801R_PHY_ID2)) + +#define AN8801R_MAX_LED_SIZE 3 + +/* MII Registers - Airoha Page 4 */ +#define AN8801_PBUS_ACCESS BIT(28) + +/* BPBUS Registers */ +#define AN8801_BPBUS_REG_LED_GPIO 0x54 +#define AN8801_BPBUS_REG_LED_ID_SEL 0x58 +#define LED_ID_GPIO_SEL(led, gpio) ((led) << ((gpio) * 3)) + +#define AN8801_BPBUS_REG_GPIO_MODE 0x70 + +#define AN8801_BPBUS_REG_LINK_MODE 0x5054 +#define AN8801_BPBUS_LINK_MODE_1000 BIT(0) + +#define AN8801_BPBUS_REG_BYPASS_PTP 0x21c004 +#define AN8801_BYP_PTP_RGMII_TO_GPHY BIT(0) + +#define AN8801_BPBUS_REG_TXDLY_STEP 0x21c024 +#define RGMII_DELAY_STEP_MASK GENMASK(2, 0) +#define AIR_RGMII_DELAY_NOSTEP 0 +#define AIR_RGMII_DELAY_STEP_1 1 +#define AIR_RGMII_DELAY_STEP_2 2 +#define AIR_RGMII_DELAY_STEP_3 3 +#define AIR_RGMII_DELAY_STEP_4 4 +#define AIR_RGMII_DELAY_STEP_5 5 +#define AIR_RGMII_DELAY_STEP_6 6 +#define AIR_RGMII_DELAY_STEP_7 7 +#define RGMII_TXDELAY_FORCE_MODE BIT(24) + +#define AN8801_BPBUS_REG_RXDLY_STEP 0x21c02c +#define RGMII_RXDELAY_ALIGN BIT(4) +#define RGMII_RXDELAY_FORCE_MODE BIT(24) + +#define AN8801_BPBUS_REG_EFIFO_CTL(x) (0x270004 + (0x100 * (x))) /* 0..2 */ +#define AN8801_EFIFO_ALL_EN GENMASK(7, 0) +#define AN8801_EFIFO_RX_EN BIT(0) +#define AN8801_EFIFO_TX_EN BIT(1) +#define AN8801_EFIFO_RX_CLK_EN BIT(2) +#define AN8801_EFIFO_TX_CLK_EN BIT(3) +#define AN8801_EFIFO_RX_EEE_EN BIT(4) +#define AN8801_EFIFO_TX_EEE_EN BIT(5) +#define AN8801_EFIFO_RX_ODD_NIBBLE_EN BIT(6) +#define AN8801_EFIFO_TX_ODD_NIBBLE_EN BIT(7) + +#define AN8801_BPBUS_REG_HWRST_DE_GLITCH 0xc8 +#define AN8801_DE_GLITCH_EN BIT(2) +#define AN8801_11_CYCLE_XTAL_PERIOD_DE_GLITCH GENMASK(1, 0) + +#define LED_BCR 0x21 +#define LED_BCR_MODE_MASK GENMASK(1, 0) +#define LED_BCR_TIME_TEST BIT(2) +#define LED_BCR_CLK_EN BIT(3) +#define LED_BCR_EVT_ALL BIT(4) +#define LED_BCR_EXT_CTRL BIT(15) +#define LED_BCR_MODE_DISABLE 0 +#define LED_BCR_MODE_2LED 1 +#define LED_BCR_MODE_3LED_1 2 +#define LED_BCR_MODE_3LED_2 3 + +#define LED_ON_DUR 0x22 +#define LED_ON_DUR_MASK GENMASK(15, 0) + +#define LED_BLINK_DUR 0x23 +#define LED_BLINK_DUR_MASK GENMASK(15, 0) + +#define LED_ON_CTRL(i) (0x024 + ((i) * 2)) +#define LED_ON_EVT_MASK GENMASK(6, 0) +#define LED_ON_EVT_LINK_1000M BIT(0) +#define LED_ON_EVT_LINK_100M BIT(1) +#define LED_ON_EVT_LINK_10M BIT(2) +#define LED_ON_EVT_LINK_DN BIT(3) +#define LED_ON_EVT_FDX BIT(4) +#define LED_ON_EVT_HDX BIT(5) +#define LED_ON_EVT_FORCE BIT(6) +#define LED_ON_POL BIT(14) +#define LED_ON_EN BIT(15) + +#define LED_BLINK_CTRL(i) (0x025 + ((i) * 2)) +#define LED_BLINK_EVT_MASK GENMASK(9, 0) +#define LED_BLINK_EVT_1000M_TX BIT(0) +#define LED_BLINK_EVT_1000M_RX BIT(1) +#define LED_BLINK_EVT_100M_TX BIT(2) +#define LED_BLINK_EVT_100M_RX BIT(3) +#define LED_BLINK_EVT_10M_TX BIT(4) +#define LED_BLINK_EVT_10M_RX BIT(5) +#define LED_BLINK_EVT_FORCE BIT(9) + +#define UNIT_LED_BLINK_DURATION 780 +#define LED_BLINK_DURATION(f) (UNIT_LED_BLINK_DURATION << (f)) + +/* Link on(1G/100M/10M), no activity */ +#define AIR_LED0_ON \ + (LED_ON_EVT_LINK_1000M | LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M) +#define AIR_LED0_BLINK 0x0 +/* No link on, activity(1G/100M/10M TX/RX) */ +#define AIR_LED1_ON 0x0 +#define AIR_LED1_BLINK \ + (LED_BLINK_EVT_1000M_TX | LED_BLINK_EVT_1000M_RX | \ + LED_BLINK_EVT_100M_TX | LED_BLINK_EVT_100M_RX | \ + LED_BLINK_EVT_10M_TX | LED_BLINK_EVT_10M_RX) +/* Link on(100M/10M), activity(100M/10M TX/RX) */ +#define AIR_LED2_ON \ + (LED_ON_EVT_LINK_100M | LED_ON_EVT_LINK_10M) +#define AIR_LED2_BLINK \ + (LED_BLINK_EVT_100M_TX | LED_BLINK_EVT_100M_RX | \ + LED_BLINK_EVT_10M_TX | LED_BLINK_EVT_10M_RX) + +#define INVALID_DATA GENMASK(31, 0) + +#define AN8801_REG_PHY_INTERNAL0 0x600 +#define AN8801_REG_PHY_INTERNAL1 0x601 + +#define AN8801_LED_ENABLE 1 + +enum air_led_gpio_pin { + AIR_LED_GPIO1 = 1, + AIR_LED_GPIO2, + AIR_LED_GPIO3 +}; + +enum air_led { + AIR_LED0 = 0, + AIR_LED1, + AIR_LED2, + AIR_LED3 +}; + +enum air_led_blink_dut { + AIR_LED_BLINK_DUR_32M = 0, + AIR_LED_BLINK_DUR_64M, + AIR_LED_BLINK_DUR_128M, + AIR_LED_BLINK_DUR_256M, + AIR_LED_BLINK_DUR_512M, + AIR_LED_BLINK_DUR_1024M, + AIR_LED_BLINK_DUR_LAST +}; + +enum air_led_polarity { + AIR_ACTIVE_LOW = 0, + AIR_ACTIVE_HIGH, +}; + +enum air_led_mode { + AIR_LED_MODE_DISABLE = 0, + AIR_LED_MODE_USER_DEFINE, + AIR_LED_MODE_LAST +}; + +struct air_led_cfg { + u16 led_en; + u16 gpio; + u16 led_polarity; + u16 led_on_cfg; + u16 led_blk_cfg; +}; + +struct an8801r_priv { + struct air_led_cfg led_cfg[AN8801R_MAX_LED_SIZE]; + u32 led_blink_cfg; + u8 rxdelay_force; + u8 txdelay_force; + u16 rxdelay_step; + u8 rxdelay_align; + u16 txdelay_step; +}; + +#define phydev_cfg(phy) ((struct an8801r_priv *)(phy)->priv) + +/* + * GPIO1 <-> LED0, + * GPIO2 <-> LED1, + * GPIO3 <-> LED2, + */ +static const struct an8801r_priv an8801r_priv_defaults = { + .led_cfg = { + /* LED Enable, GPIO, LED Polarity, LED ON, LED Blink */ + {AN8801_LED_ENABLE, AIR_LED_GPIO1, AIR_ACTIVE_LOW, AIR_LED0_ON, AIR_LED0_BLINK}, + {AN8801_LED_ENABLE, AIR_LED_GPIO2, AIR_ACTIVE_HIGH, AIR_LED1_ON, AIR_LED1_BLINK}, + {AN8801_LED_ENABLE, AIR_LED_GPIO3, AIR_ACTIVE_HIGH, AIR_LED2_ON, AIR_LED2_BLINK}, + }, + .led_blink_cfg = AIR_LED_BLINK_DUR_64M, + .rxdelay_force = false, + .txdelay_force = false, + .rxdelay_step = AIR_RGMII_DELAY_NOSTEP, + .rxdelay_align = false, + .txdelay_step = AIR_RGMII_DELAY_NOSTEP, +}; + +static int an8801_buckpbus_reg_rmw(struct phy_device *phydev, + u32 addr, u32 mask, u32 set) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, set); +} + +static int an8801_buckpbus_reg_set_bits(struct phy_device *phydev, + u32 addr, u32 mask) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, mask); +} + +static int an8801_buckpbus_reg_clear_bits(struct phy_device *phydev, + u32 addr, u32 mask) +{ + return air_phy_buckpbus_reg_modify(phydev, + addr | AN8801_PBUS_ACCESS, + mask, 0); +} + +static int an8801_buckpbus_reg_write(struct phy_device *phydev, u32 addr, u32 data) +{ + return air_phy_buckpbus_reg_write(phydev, addr | AN8801_PBUS_ACCESS, data); +} + +static int an8801r_led_set_usr_def(struct phy_device *phydev, u8 entity, + u16 polar, u16 on_evt, u16 blk_evt) +{ + int ret; + + if (polar == AIR_ACTIVE_HIGH) + on_evt |= LED_ON_POL; + else + on_evt &= ~LED_ON_POL; + + on_evt |= LED_ON_EN; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity), on_evt); + if (ret) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(entity), blk_evt); +} + +static int an8801r_led_set_blink(struct phy_device *phydev, u16 blink) +{ + int ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_DUR, + LED_BLINK_DURATION(blink)); + if (ret) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_DUR, + LED_BLINK_DURATION(blink) / 2); +} + +static int an8801r_led_set_mode(struct phy_device *phydev, u8 mode) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_BCR); + if (ret < 0) + return ret; + + switch (mode) { + case AIR_LED_MODE_DISABLE: + ret &= ~LED_BCR_EXT_CTRL; + ret &= ~LED_BCR_MODE_MASK; + ret |= LED_BCR_MODE_DISABLE; + break; + case AIR_LED_MODE_USER_DEFINE: + ret |= LED_BCR_EXT_CTRL | LED_BCR_CLK_EN; + break; + } + return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BCR, ret); +} + +static int an8801r_led_set_state(struct phy_device *phydev, u8 entity, u8 state) +{ + int ret; + + ret = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity)); + if (ret < 0) + return ret; + + if (state) + ret |= LED_ON_EN; + else + ret &= ~LED_ON_EN; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(entity), ret); +} + +static int an8801r_led_init(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev_cfg(phydev); + struct air_led_cfg *led_cfg = priv->led_cfg; + u16 led_blink_cfg = priv->led_blink_cfg; + int ret, led_id; + + ret = an8801r_led_set_blink(phydev, led_blink_cfg); + if (ret) + return ret; + + ret = an8801r_led_set_mode(phydev, AIR_LED_MODE_USER_DEFINE); + if (ret) { + dev_err(phydev->dev, "AN8801R: Fail to set LED mode, ret %d!\n", ret); + return ret; + } + + for (led_id = AIR_LED0; led_id < AN8801R_MAX_LED_SIZE; led_id++) { + ret = an8801r_led_set_state(phydev, led_id, led_cfg[led_id].led_en); + if (ret) { + dev_err(phydev->dev, "AN8801R: Fail to set LED%d state, ret %d!\n", + led_id, ret); + return ret; + } + + if (!led_cfg[led_id].led_en) + continue; + + ret = an8801_buckpbus_reg_set_bits(phydev, AN8801_BPBUS_REG_LED_GPIO, + BIT(led_cfg[led_id].gpio)); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_set_bits(phydev, AN8801_BPBUS_REG_LED_ID_SEL, + LED_ID_GPIO_SEL(led_id, + led_cfg[led_id].gpio)); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_clear_bits(phydev, AN8801_BPBUS_REG_GPIO_MODE, + BIT(led_cfg[led_id].gpio)); + if (ret) + return ret; + + ret = an8801r_led_set_usr_def(phydev, led_id, + led_cfg[led_id].led_polarity, + led_cfg[led_id].led_on_cfg, + led_cfg[led_id].led_blk_cfg); + if (ret) { + dev_err(phydev->dev, "AN8801R: Fail to set LED%d, ret %d!\n", + led_id, ret); + return ret; + } + } + return 0; +} + +static int an8801r_of_init(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev_cfg(phydev); + ofnode node = phy_get_ofnode(phydev); + u32 val = 0; + int ret; + + if (!ofnode_valid(node)) + return -EINVAL; + + if (ofnode_has_property(node, "airoha,rxclk-delay")) { + ret = ofnode_read_u32(node, "airoha,rxclk-delay", &val); + if (ret) { + dev_err(phydev->dev, "airoha,rxclk-delay value is invalid.\n"); + return ret; + } + if (val > AIR_RGMII_DELAY_STEP_7) { + dev_err(phydev->dev, "airoha,rxclk-delay value %u out of range.\n", val); + return -EINVAL; + } + priv->rxdelay_force = true; + priv->rxdelay_step = val; + priv->rxdelay_align = ofnode_read_bool(node, + "airoha,rxclk-delay-align"); + } + + if (ofnode_has_property(node, "airoha,txclk-delay")) { + ret = ofnode_read_u32(node, "airoha,txclk-delay", &val); + if (ret) { + dev_err(phydev->dev, "airoha,txclk-delay value is invalid.\n"); + return ret; + } + if (val > AIR_RGMII_DELAY_STEP_7) { + dev_err(phydev->dev, "airoha,txclk-delay value %u out of range.\n", val); + return -EINVAL; + } + priv->txdelay_force = true; + priv->txdelay_step = val; + } + return 0; +} + +static int an8801r_rgmii_rxdelay(struct phy_device *phydev, u16 delay, u8 align) +{ + u32 reg_val = delay & RGMII_DELAY_STEP_MASK; + int ret; + + if (align) { + reg_val |= RGMII_RXDELAY_ALIGN; + debug("AN8801R: Rxdelay align\n"); + } + reg_val |= RGMII_RXDELAY_FORCE_MODE; + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_RXDLY_STEP, reg_val); + if (ret) + return ret; + + debug("AN8801R: Force rxdelay = %d(0x%x)\n", delay, reg_val); + return 0; +} + +static int an8801r_rgmii_txdelay(struct phy_device *phydev, u16 delay) +{ + u32 reg_val = delay & RGMII_DELAY_STEP_MASK; + int ret; + + reg_val |= RGMII_TXDELAY_FORCE_MODE; + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_TXDLY_STEP, reg_val); + if (ret) + return ret; + + debug("AN8801R: Force txdelay = %d(0x%x)\n", delay, reg_val); + return 0; +} + +static int an8801r_rgmii_delay_config(struct phy_device *phydev) +{ + struct an8801r_priv *priv = phydev_cfg(phydev); + int ret; + + switch (phydev->interface) { + case PHY_INTERFACE_MODE_RGMII_TXID: + return an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4); + case PHY_INTERFACE_MODE_RGMII_RXID: + return an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_NOSTEP, true); + case PHY_INTERFACE_MODE_RGMII_ID: + ret = an8801r_rgmii_txdelay(phydev, AIR_RGMII_DELAY_STEP_4); + if (ret) + return ret; + return an8801r_rgmii_rxdelay(phydev, AIR_RGMII_DELAY_NOSTEP, true); + case PHY_INTERFACE_MODE_RGMII: + default: + if (priv->rxdelay_force) { + ret = an8801r_rgmii_rxdelay(phydev, priv->rxdelay_step, + priv->rxdelay_align); + if (ret) + return ret; + } + if (priv->txdelay_force) + return an8801r_rgmii_txdelay(phydev, priv->txdelay_step); + return 0; + } +} + +static int an8801r_config_init(struct phy_device *phydev) +{ + int ret; + + ret = an8801r_of_init(phydev); + if (ret < 0) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_HWRST_DE_GLITCH, + AN8801_DE_GLITCH_EN | + AN8801_11_CYCLE_XTAL_PERIOD_DE_GLITCH); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL0, 0x1e); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL1, 0x02); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_AN, MDIO_AN_EEE_ADV, 0x0); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_BYPASS_PTP, + AN8801_BYP_PTP_RGMII_TO_GPHY); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(0), + AN8801_EFIFO_RX_EN | + AN8801_EFIFO_TX_EN | + AN8801_EFIFO_RX_CLK_EN | + AN8801_EFIFO_TX_CLK_EN | + AN8801_EFIFO_RX_EEE_EN | + AN8801_EFIFO_TX_EEE_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(1), + AN8801_EFIFO_ALL_EN); + if (ret) + return ret; + + ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_EFIFO_CTL(2), + AN8801_EFIFO_ALL_EN); + if (ret) + return ret; + + ret = an8801r_rgmii_delay_config(phydev); + if (ret) + return ret; + + ret = an8801r_led_init(phydev); + if (ret) { + dev_err(phydev->dev, "AN8801R: LED initialize fail, ret %d!\n", ret); + return ret; + } + return 0; +} + +static int an8801r_phy_probe(struct phy_device *phydev) +{ + struct an8801r_priv *priv; + u32 phy_id; + int ret; + + ret = get_phy_id(phydev->bus, phydev->addr, MDIO_DEVAD_NONE, &phy_id); + if (ret) + return ret; + + if (phy_id != AN8801R_PHY_ID) { + dev_err(phydev->dev, + "AN8801R can't be detected (id=0x%08x).\n", phy_id); + return -ENODEV; + } + + priv = malloc(sizeof(*priv)); + if (!priv) + return -ENOMEM; + + *priv = an8801r_priv_defaults; + + phydev->priv = priv; + + return 0; +} + +static int an8801r_read_status(struct phy_device *phydev) +{ + u32 data; + + if (!phydev->link) + return 0; + + debug("AN8801R: SPEED %d\n", phydev->speed); + data = phydev->speed == SPEED_1000 ? AN8801_BPBUS_LINK_MODE_1000 : 0; + + return an8801_buckpbus_reg_rmw(phydev, AN8801_BPBUS_REG_LINK_MODE, + AN8801_BPBUS_LINK_MODE_1000, data); +} + +static int an8801r_startup(struct phy_device *phydev) +{ + int ret; + + ret = genphy_startup(phydev); + if (ret) + return ret; + + return an8801r_read_status(phydev); +} + +U_BOOT_PHY_DRIVER(an8801r) = { + .name = "Airoha AN8801R", + .uid = AN8801R_PHY_ID, + .mask = 0x0ffffff0, + .features = PHY_GBIT_FEATURES, + .probe = &an8801r_phy_probe, + .config = &an8801r_config_init, + .read_page = &air_phy_read_page, + .write_page = &air_phy_write_page, + .startup = &an8801r_startup, + .shutdown = &genphy_shutdown, +}; diff --git a/drivers/net/phy/airoha/air_en8811.c b/drivers/net/phy/airoha/air_en8811.c index 0b974472732..7a07be2e956 100644 --- a/drivers/net/phy/airoha/air_en8811.c +++ b/drivers/net/phy/airoha/air_en8811.c @@ -23,6 +23,9 @@ #include <linux/compat.h> #include <dm/device_compat.h> #include <u-boot/crc.h> +#include <linux/phy/phy-common-props.h> + +#include "air_phy_lib.h" /* MII Registers */ #define AIR_AUX_CTRL_STATUS 0x1d @@ -32,10 +35,6 @@ #define AIR_AUX_CTRL_STATUS_SPEED_1000 0x8 #define AIR_AUX_CTRL_STATUS_SPEED_2500 0xc -#define AIR_EXT_PAGE_ACCESS 0x1f -#define AIR_PHY_PAGE_STANDARD 0x0000 -#define AIR_PHY_PAGE_EXTENDED_4 0x0004 - #define AIR_PBUS_MODE_ADDR_HIGH 0x1c /* MII Registers Page 4 */ #define AIR_BPBUS_MODE 0x10 @@ -309,166 +308,6 @@ static int air_pbus_reg_write(struct phy_device *phydev, return ret; } -static int air_buckpbus_reg_write(struct phy_device *phydev, - u32 pbus_address, u32 pbus_data) -{ - int ret, saved_page; - - saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); - if (saved_page < 0) - return saved_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, - AIR_BPBUS_MODE_ADDR_FIXED); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, - upper_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, - lower_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, - upper_16_bits(pbus_data)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, - lower_16_bits(pbus_data)); - if (ret < 0) - goto restore_page; - -restore_page: - if (ret < 0) - dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, - pbus_address, ret); - - return phy_restore_page(phydev, saved_page, ret); -} - -static int air_buckpbus_reg_read(struct phy_device *phydev, - u32 pbus_address, u32 *pbus_data) -{ - int pbus_data_low, pbus_data_high; - int ret = 0, saved_page; - - saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); - if (saved_page < 0) - return saved_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, - AIR_BPBUS_MODE_ADDR_FIXED); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, - upper_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, - lower_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); - if (pbus_data_high < 0) { - ret = pbus_data_high; - goto restore_page; - } - - pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); - if (pbus_data_low < 0) { - ret = pbus_data_low; - goto restore_page; - } - - *pbus_data = pbus_data_low | (pbus_data_high << 16); - -restore_page: - if (ret < 0) - dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, - pbus_address, ret); - - return phy_restore_page(phydev, saved_page, ret); -} - -static int air_buckpbus_reg_modify(struct phy_device *phydev, - u32 pbus_address, u32 mask, u32 set) -{ - int pbus_data_low, pbus_data_high; - u32 pbus_data_old, pbus_data_new; - int ret = 0, saved_page; - - saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); - if (saved_page < 0) - return saved_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, - AIR_BPBUS_MODE_ADDR_FIXED); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, - upper_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, - lower_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_HIGH); - if (pbus_data_high < 0) { - ret = pbus_data_high; - goto restore_page; - } - - pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_DATA_LOW); - if (pbus_data_low < 0) { - ret = pbus_data_low; - goto restore_page; - } - - pbus_data_old = pbus_data_low | (pbus_data_high << 16); - pbus_data_new = (pbus_data_old & ~mask) | set; - if (pbus_data_new == pbus_data_old) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, - upper_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, - lower_16_bits(pbus_address)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, - upper_16_bits(pbus_data_new)); - if (ret < 0) - goto restore_page; - - ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, - lower_16_bits(pbus_data_new)); - if (ret < 0) - goto restore_page; - -restore_page: - if (ret < 0) - dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, - pbus_address, ret); - - return phy_restore_page(phydev, saved_page, ret); -} - static int air_write_buf(struct phy_device *phydev, unsigned long address, unsigned long array_size, const unsigned char *buffer) { @@ -539,12 +378,12 @@ static int an8811hb_check_crc(struct phy_device *phydev, u32 pbus_value; /* Configure CRC */ - ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, - AN8811HB_CRC_RD_EN); + ret = air_phy_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, + AN8811HB_CRC_RD_EN); if (ret < 0) return ret; - ret = air_buckpbus_reg_read(phydev, set1, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, set1, &pbus_value); if (ret < 0) return ret; @@ -553,14 +392,14 @@ static int an8811hb_check_crc(struct phy_device *phydev, do { mdelay(300); - ret = air_buckpbus_reg_read(phydev, mon2, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, mon2, &pbus_value); if (ret < 0) return ret; debug("%d: reg 0x%x val 0x%x!\n", __LINE__, mon2, pbus_value); if (pbus_value & AN8811HB_CRC_ST) { - ret = air_buckpbus_reg_read(phydev, mon3, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, mon3, &pbus_value); if (ret < 0) return ret; @@ -584,11 +423,11 @@ static int an8811hb_check_crc(struct phy_device *phydev, } } while (--retry); - ret = air_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, 0); + ret = air_phy_buckpbus_reg_modify(phydev, set1, AN8811HB_CRC_RD_EN, 0); if (ret < 0) return ret; - ret = air_buckpbus_reg_read(phydev, set1, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, set1, &pbus_value); if (ret < 0) return ret; @@ -646,9 +485,9 @@ static int an8811hb_surge_protect_cfg(struct phy_device *phydev) return ret; } - ret = air_buckpbus_reg_modify(phydev, AIR_PHY_CONTROL, - AIR_PHY_CONTROL_SURGE_5R, - AIR_PHY_CONTROL_SURGE_5R); + ret = air_phy_buckpbus_reg_modify(phydev, AIR_PHY_CONTROL, + AIR_PHY_CONTROL_SURGE_5R, + AIR_PHY_CONTROL_SURGE_5R); if (ret < 0) return ret; @@ -706,14 +545,14 @@ static int en8811h_load_firmware(struct phy_device *phydev) goto en8811h_load_firmware_out; } - ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_START); + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); if (ret < 0) goto en8811h_load_firmware_out; - ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, - EN8811H_FW_CTRL_2_LOADING, - EN8811H_FW_CTRL_2_LOADING); + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, + EN8811H_FW_CTRL_2_LOADING); if (ret < 0) goto en8811h_load_firmware_out; @@ -727,13 +566,13 @@ static int en8811h_load_firmware(struct phy_device *phydev) if (ret < 0) goto en8811h_load_firmware_out; - ret = air_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, - EN8811H_FW_CTRL_2_LOADING, 0); + ret = air_phy_buckpbus_reg_modify(phydev, EN8811H_FW_CTRL_2, + EN8811H_FW_CTRL_2_LOADING, 0); if (ret < 0) goto en8811h_load_firmware_out; - ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); if (ret < 0) goto en8811h_load_firmware_out; @@ -741,8 +580,8 @@ static int en8811h_load_firmware(struct phy_device *phydev) if (ret < 0) goto en8811h_load_firmware_out; - air_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, - &priv->firmware_version); + air_phy_buckpbus_reg_read(phydev, EN8811H_FW_VERSION, + &priv->firmware_version); dev_info(phydev->dev, "MD32 firmware version: %08x\n", priv->firmware_version); @@ -778,8 +617,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev) if (ret < 0) goto an8811hb_load_firmware_out; - ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, - AIR_PHY_FW_CTRL_1_START); + ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, + AIR_PHY_FW_CTRL_1_START); if (ret < 0) goto an8811hb_load_firmware_out; @@ -803,8 +642,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev) if (ret < 0) goto an8811hb_load_firmware_out; - ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, - AIR_PHY_FW_CTRL_1_FINISH); + ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, + AIR_PHY_FW_CTRL_1_FINISH); if (ret < 0) goto an8811hb_load_firmware_out; @@ -817,7 +656,7 @@ static int an8811hb_load_firmware(struct phy_device *phydev) do { mdelay(300); - ret = air_buckpbus_reg_read(phydev, AIR_PHY_FW_CTRL_1, ®_val); + ret = air_phy_buckpbus_reg_read(phydev, AIR_PHY_FW_CTRL_1, ®_val); if (ret < 0) goto an8811hb_load_firmware_out; @@ -827,8 +666,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev) debug("%d: reg 0x%x val 0x%x!\n", __LINE__, AIR_PHY_FW_CTRL_1, reg_val); - ret = air_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, - AIR_PHY_FW_CTRL_1_FINISH); + ret = air_phy_buckpbus_reg_write(phydev, AIR_PHY_FW_CTRL_1, + AIR_PHY_FW_CTRL_1_FINISH); if (ret < 0) goto an8811hb_load_firmware_out; @@ -838,8 +677,8 @@ static int an8811hb_load_firmware(struct phy_device *phydev) if (ret < 0) goto an8811hb_load_firmware_out; - air_buckpbus_reg_read(phydev, AIR_PHY_MD32FW_VERSION, - &priv->firmware_version); + air_phy_buckpbus_reg_read(phydev, AIR_PHY_MD32FW_VERSION, + &priv->firmware_version); debug("MD32 firmware version: %08x\n", priv->firmware_version); @@ -858,17 +697,17 @@ int an8811hb_cko_cfg(struct phy_device *phydev) int ret = 0; if (!ofnode_read_bool(node, "airoha,phy-output-clock")) { - ret = air_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, - AN8811HB_CLK_DRV_CKO_MASK, - AN8811HB_CLK_DRV_CKOPWD | - AN8811HB_CLK_DRV_CKO_LDPWD | - AN8811HB_CLK_DRV_CKO_LPPWD); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_CLK_DRV, + AN8811HB_CLK_DRV_CKO_MASK, + AN8811HB_CLK_DRV_CKOPWD | + AN8811HB_CLK_DRV_CKO_LDPWD | + AN8811HB_CLK_DRV_CKO_LPPWD); if (ret < 0) return ret; debug("CKO Output mode - Disabled\n"); } else { - ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); if (ret < 0) return ret; @@ -887,13 +726,13 @@ static int en8811h_restart_mcu(struct phy_device *phydev) if (ret < 0) return ret; - ret = air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_START); + ret = air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_START); if (ret < 0) return ret; - return air_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, - EN8811H_FW_CTRL_1_FINISH); + return air_phy_buckpbus_reg_write(phydev, EN8811H_FW_CTRL_1, + EN8811H_FW_CTRL_1_FINISH); } static int air_led_hw_control_set(struct phy_device *phydev, u8 index, @@ -1046,11 +885,51 @@ static int air_leds_init(struct phy_device *phydev, int num, u16 dur, int mode) return 0; } -static int en8811h_config(struct phy_device *phydev) +static int en8811h_config_serdes_polarity(struct phy_device *phydev) { - struct en8811h_priv *priv = phydev->priv; ofnode node = phy_get_ofnode(phydev); + unsigned int pol, default_pol; u32 pbus_value = 0; + int ret; + + if (!ofnode_valid(node)) + return 0; + + default_pol = PHY_POL_NORMAL; + if (ofnode_read_bool(node, "airoha,pnswap-rx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_rx_polarity(node, + phy_string_for_interface(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_INVERT) + pbus_value |= EN8811H_POLARITY_RX_REVERSE; + + default_pol = PHY_POL_NORMAL; + if (ofnode_read_bool(node, "airoha,pnswap-tx")) + default_pol = PHY_POL_INVERT; + + ret = phy_get_tx_polarity(node, + phy_string_for_interface(phydev->interface), + BIT(PHY_POL_NORMAL) | BIT(PHY_POL_INVERT), + default_pol, &pol); + if (ret) + return ret; + if (pol == PHY_POL_NORMAL) + pbus_value |= EN8811H_POLARITY_TX_NORMAL; + + return air_phy_buckpbus_reg_modify(phydev, EN8811H_POLARITY, + EN8811H_POLARITY_RX_REVERSE | + EN8811H_POLARITY_TX_NORMAL, + pbus_value); +} + +static int en8811h_config(struct phy_device *phydev) +{ + struct en8811h_priv *priv = phydev->priv; int ret = 0; /* If restart happened in .probe(), no need to restart now */ @@ -1081,20 +960,8 @@ static int en8811h_config(struct phy_device *phydev) if (ret < 0) return ret; - /* Serdes polarity */ - pbus_value = 0; - if (ofnode_read_bool(node, "airoha,pnswap-rx")) - pbus_value |= EN8811H_POLARITY_RX_REVERSE; - else - pbus_value &= ~EN8811H_POLARITY_RX_REVERSE; - if (ofnode_read_bool(node, "airoha,pnswap-tx")) - pbus_value &= ~EN8811H_POLARITY_TX_NORMAL; - else - pbus_value |= EN8811H_POLARITY_TX_NORMAL; - ret = air_buckpbus_reg_modify(phydev, EN8811H_POLARITY, - EN8811H_POLARITY_RX_REVERSE | - EN8811H_POLARITY_TX_NORMAL, - pbus_value); + /* Configure Serdes polarity from device tree */ + ret = en8811h_config_serdes_polarity(phydev); if (ret < 0) return ret; @@ -1142,12 +1009,12 @@ static int an8811hb_config(struct phy_device *phydev) priv->mcu_needs_restart = true; } - ret = air_buckpbus_reg_read(phydev, AN8811HB_PRO_ID, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_PRO_ID, &pbus_value); if (ret < 0) return ret; priv->pro_id = (pbus_value & AN8811HB_PRO_ID_VERSION) + 1; - ret = air_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); + ret = air_phy_buckpbus_reg_read(phydev, AN8811HB_HWTRAP2, &pbus_value); if (ret < 0) return ret; priv->pkg_sel = (pbus_value & AN8811HB_HWTRAP2_PKG) >> 12; @@ -1163,8 +1030,8 @@ static int an8811hb_config(struct phy_device *phydev) pbus_value |= AN8811HB_RX_POLARITY_NORMAL; debug("1 pbus_value 0x%x\n", pbus_value); - ret = air_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY, - AN8811HB_RX_POLARITY_NORMAL, pbus_value); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_RX_POLARITY, + AN8811HB_RX_POLARITY_NORMAL, pbus_value); if (ret < 0) return ret; @@ -1175,35 +1042,35 @@ static int an8811hb_config(struct phy_device *phydev) pbus_value |= AN8811HB_TX_POLARITY_NORMAL; debug("2 pbus_value 0x%x\n", pbus_value); - ret = air_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY, - AN8811HB_TX_POLARITY_NORMAL, pbus_value); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_TX_POLARITY, + AN8811HB_TX_POLARITY_NORMAL, pbus_value); if (ret < 0) return ret; /* Configure led gpio pins as output */ if (priv->pkg_sel) { - ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, - AN8811HB_GPIO_OUTPUT_MASK, - AN8811HB_GPIO_OUTPUT_0115); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, + AN8811HB_GPIO_OUTPUT_MASK, + AN8811HB_GPIO_OUTPUT_0115); if (ret < 0) return ret; - ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_1, - AN8811HB_GPIO_SEL_1_0_MASK | - AN8811HB_GPIO_SEL_1_1_MASK, - AN8811HB_GPIO_SEL_1_0 | - AN8811HB_GPIO_SEL_1_1); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_1, + AN8811HB_GPIO_SEL_1_0_MASK | + AN8811HB_GPIO_SEL_1_1_MASK, + AN8811HB_GPIO_SEL_1_0 | + AN8811HB_GPIO_SEL_1_1); if (ret < 0) return ret; - ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_2, - AN8811HB_GPIO_SEL_2_15_MASK, - AN8811HB_GPIO_SEL_2_15); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_SEL_2, + AN8811HB_GPIO_SEL_2_15_MASK, + AN8811HB_GPIO_SEL_2_15); if (ret < 0) return ret; } else { - ret = air_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, - AN8811HB_GPIO_OUTPUT_345, - AN8811HB_GPIO_OUTPUT_345); + ret = air_phy_buckpbus_reg_modify(phydev, AN8811HB_GPIO_OUTPUT, + AN8811HB_GPIO_OUTPUT_345, + AN8811HB_GPIO_OUTPUT_345); if (ret < 0) return ret; } @@ -1373,16 +1240,6 @@ static int en8811h_probe(struct phy_device *phydev) return 0; } -static int air_phy_read_page(struct phy_device *phydev) -{ - return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS); -} - -static int air_phy_write_page(struct phy_device *phydev, int page) -{ - return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page); -} - U_BOOT_PHY_DRIVER(en8811h) = { .name = "Airoha EN8811H", .uid = EN8811H_PHY_ID, diff --git a/drivers/net/phy/airoha/air_phy_lib.c b/drivers/net/phy/airoha/air_phy_lib.c new file mode 100644 index 00000000000..61c3bf82822 --- /dev/null +++ b/drivers/net/phy/airoha/air_phy_lib.c @@ -0,0 +1,216 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Airoha Ethernet PHY common library + * + * Copyright (C) 2026 Airoha Technology Corp. + * Copyright (C) 2026 Collabora Ltd. + * Louis-Alexis Eyraud <[email protected]> + * + * Adapated from https://lore.kernel.org/all/20260326-add-airoha-an8801-support-v2-2-1a42d6b6050f@collabora.com/ + */ + +#include <dm/device_compat.h> +#include <linux/compat.h> +#include <phy.h> + +#include "air_phy_lib.h" + +#define AIR_EXT_PAGE_ACCESS 0x1f + +static int __air_buckpbus_reg_read(struct phy_device *phydev, + u32 pbus_address, u32 *pbus_data) +{ + int pbus_data_low, pbus_data_high; + int ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, + AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, + AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + *pbus_data = pbus_data_low | (pbus_data_high << 16); + return 0; +} + +static int __air_buckpbus_reg_write(struct phy_device *phydev, + u32 pbus_address, u32 pbus_data) +{ + int ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data)); + if (ret < 0) + return ret; + + return 0; +} + +static int __air_buckpbus_reg_modify(struct phy_device *phydev, + u32 pbus_address, u32 mask, u32 set) +{ + int pbus_data_low, pbus_data_high; + u32 pbus_data_old, pbus_data_new; + int ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_MODE, + AIR_BPBUS_MODE_ADDR_FIXED); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_RD_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + pbus_data_high = phy_read(phydev, MDIO_DEVAD_NONE, + AIR_BPBUS_RD_DATA_HIGH); + if (pbus_data_high < 0) + return pbus_data_high; + + pbus_data_low = phy_read(phydev, MDIO_DEVAD_NONE, + AIR_BPBUS_RD_DATA_LOW); + if (pbus_data_low < 0) + return pbus_data_low; + + pbus_data_old = pbus_data_low | (pbus_data_high << 16); + pbus_data_new = (pbus_data_old & ~mask) | set; + if (pbus_data_new == pbus_data_old) + return 0; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_HIGH, + upper_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_ADDR_LOW, + lower_16_bits(pbus_address)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_HIGH, + upper_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + ret = phy_write(phydev, MDIO_DEVAD_NONE, AIR_BPBUS_WR_DATA_LOW, + lower_16_bits(pbus_data_new)); + if (ret < 0) + return ret; + + return 0; +} + +int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, + u32 *pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_read(phydev, pbus_address, pbus_data); + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, + u32 pbus_data) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_write(phydev, pbus_address, + pbus_data); + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, + u32 mask, u32 set) +{ + int saved_page; + int ret = 0; + + saved_page = phy_select_page(phydev, AIR_PHY_PAGE_EXTENDED_4); + + if (saved_page >= 0) { + ret = __air_buckpbus_reg_modify(phydev, pbus_address, mask, + set); + if (ret < 0) + dev_err(phydev->dev, "%s 0x%08x failed: %d\n", __func__, + pbus_address, ret); + } + + return phy_restore_page(phydev, saved_page, ret); +} + +int air_phy_read_page(struct phy_device *phydev) +{ + return phy_read(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS); +} + +int air_phy_write_page(struct phy_device *phydev, int page) +{ + return phy_write(phydev, MDIO_DEVAD_NONE, AIR_EXT_PAGE_ACCESS, page); +} + +MODULE_DESCRIPTION("Airoha PHY Library"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Louis-Alexis Eyraud"); diff --git a/drivers/net/phy/airoha/air_phy_lib.h b/drivers/net/phy/airoha/air_phy_lib.h new file mode 100644 index 00000000000..845d2f7cfb4 --- /dev/null +++ b/drivers/net/phy/airoha/air_phy_lib.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2026 Airoha Technology Corp. + * Copyright (C) 2026 Collabora Ltd. + * Louis-Alexis Eyraud <[email protected]> + */ + +#ifndef __AIR_PHY_LIB_H +#define __AIR_PHY_LIB_H + +#define AIR_EXT_PAGE_ACCESS 0x1f + +#define AIR_PHY_PAGE_STANDARD 0x0000 +#define AIR_PHY_PAGE_EXTENDED_1 0x0001 +#define AIR_PHY_PAGE_EXTENDED_4 0x0004 + +/* MII Registers Page 4*/ +#define AIR_BPBUS_MODE 0x10 +#define AIR_BPBUS_MODE_ADDR_FIXED 0x0000 +#define AIR_BPBUS_MODE_ADDR_INCR BIT(15) +#define AIR_BPBUS_WR_ADDR_HIGH 0x11 +#define AIR_BPBUS_WR_ADDR_LOW 0x12 +#define AIR_BPBUS_WR_DATA_HIGH 0x13 +#define AIR_BPBUS_WR_DATA_LOW 0x14 +#define AIR_BPBUS_RD_ADDR_HIGH 0x15 +#define AIR_BPBUS_RD_ADDR_LOW 0x16 +#define AIR_BPBUS_RD_DATA_HIGH 0x17 +#define AIR_BPBUS_RD_DATA_LOW 0x18 + +int air_phy_buckpbus_reg_modify(struct phy_device *phydev, u32 pbus_address, + u32 mask, u32 set); +int air_phy_buckpbus_reg_read(struct phy_device *phydev, u32 pbus_address, + u32 *pbus_data); +int air_phy_buckpbus_reg_write(struct phy_device *phydev, u32 pbus_address, + u32 pbus_data); +int air_phy_read_page(struct phy_device *phydev); +int air_phy_write_page(struct phy_device *phydev, int page); + +#endif /* __AIR_PHY_LIB_H */ |
