| // SPDX-License-Identifier: GPL-2.0+ |
| /* |
| * Driver for the Airoha AN8801 Gigabit PHY. |
| * |
| * Copyright (C) 2025 Airoha Technology Corp. |
| * Copyright (C) 2025 Collabora Ltd. |
| * AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com> |
| */ |
| |
| #include <linux/bitfield.h> |
| #include <linux/bitops.h> |
| #include <linux/delay.h> |
| #include <linux/errno.h> |
| #include <linux/etherdevice.h> |
| #include <linux/init.h> |
| #include <linux/minmax.h> |
| #include <linux/netdevice.h> |
| #include <linux/of.h> |
| #include <linux/phy.h> |
| #include <linux/pm_wakeirq.h> |
| |
| #include "air_phy_lib.h" |
| |
| #define AN8801R_PHY_ID 0xc0ff0421 |
| |
| /* MII Registers */ |
| |
| /* MII Registers - Airoha Page 1 */ |
| #define AN8801_EXT_REG_PHY 0x14 |
| #define AN8801_EXT_PHY_STATUS0 GENMASK(1, 0) |
| #define AN8801_EXT_PHY_DOWNSHIFT_CTL GENMASK(3, 2) /* 2 to 5 1G auto-neg attempts (0..3) */ |
| #define AN8801_EXT_PHY_DOWNSHIFT_EN BIT(4) |
| #define AN8801_EXT_PHY_CTRL0 BIT(5) |
| #define AN8801_EXT_PHY_STATUS1 GENMASK(8, 6) |
| #define AN8801_EXT_PHY_CTRL1 GENMASK(14, 9) |
| |
| /* MII Registers - Airoha Page 4 */ |
| #define AN8801_PBUS_ACCESS BIT(28) |
| #define AN8801_PBUS_EPHY_ACCESS BIT(24) |
| #define AN8801_PBUS_CL22_ACCESS BIT(23) |
| |
| /* 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 LED_ID_GPIO_SEL_MASK(gpio) (0x7 << ((gpio) * 3)) |
| #define AN8801_BPBUS_REG_GPIO_MODE 0x70 |
| #define AN8801_BPBUS_REG_PHY_IRQ_GPIO 0x7c |
| #define AN8801_PHY_IRQ_GPIO_NUM_MASK GENMASK(19, 16) |
| #define AN8801_PHY_IRQ_GPIO_NUM 1 |
| |
| #define AN8801_BPBUS_REG_CKO 0x1a4 |
| #define AN8801_CKO_OUTPUT_MODE_AUTO 3 |
| |
| #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_SGMII_TO_GPHY BIT(8) |
| #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 RGMII_DELAY_NO_STEP 0 |
| #define RGMII_DELAY_STEP_1 1 |
| #define RGMII_DELAY_STEP_2 2 |
| #define RGMII_DELAY_STEP_3 3 |
| #define RGMII_DELAY_STEP_4 4 |
| #define RGMII_DELAY_STEP_5 5 |
| #define RGMII_DELAY_STEP_6 6 |
| #define RGMII_DELAY_STEP_7 7 |
| #define RGMII_TXDELAY_FORCE_MODE BIT(24) |
| |
| /* Default RGMII TX delay setting, corresponding to a 1.883ns delay */ |
| #define AN8801_RGMII_TXDELAY_DEFAULT RGMII_DELAY_STEP_4 |
| |
| #define AN8801_BPBUS_REG_RXDLY_STEP 0x21c02c |
| #define RGMII_RXDELAY_ALIGN BIT(4) |
| #define RGMII_RXDELAY_FORCE_MODE BIT(24) |
| |
| /* Default RGMII RX delay setting, corresponding to a 1.992ns delay, |
| * when align bit is set or -0.008ns otherwise. |
| */ |
| #define AN8801_RGMII_RXDELAY_DEFAULT RGMII_DELAY_NO_STEP |
| |
| #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_WOL_MAC_16_47 0x285114 |
| #define AN8801_BPBUS_REG_WOL_MAC_0_15 0x285118 |
| |
| #define AN8801_BPBUS_REG_WAKEUP_CTL1 0x285400 |
| #define AN8801_WOL_WAKE_MAGIC_EN GENMASK(3, 1) |
| #define AN8801_WOL_WAKE_LNKCHG_EN BIT(4) |
| |
| #define AN8801_BPBUS_REG_WAKEUP_CTL2 0x285404 |
| #define AN8801_WAKE_OUT_TYPE_PULSE BIT(0) /* Set/Unset: Pulse/Static */ |
| #define AN8801_WAKE_OUT_POLARITY_NEG BIT(1) /* Set/Unset: Negative/Positive */ |
| #define AN8801_WAKE_OUT_WIDTH GENMASK(3, 2) |
| #define AN8801_WAKE_OUT_84MS 0 |
| #define AN8801_WAKE_OUT_168MS 1 |
| #define AN8801_WAKE_OUT_336MS 2 |
| #define AN8801_WAKE_OUT_672MS 3 |
| #define AN8801_WAKE_OUT_EN BIT(4) |
| #define AN8801_PME_WAKEUP_CLR BIT(8) |
| |
| #define AN8801_BPBUS_REG_WAKE_IRQ_EN 0x285700 |
| #define AN8801_BPBUS_REG_WAKE_IRQ_STS 0x285704 |
| #define AN8801_IRQ_WAKE_LNKCHG BIT(0) /* Wake on link change */ |
| #define AN8801_IRQ_WAKE_UNIPKT BIT(1) /* Wake on unicast packet */ |
| #define AN8801_IRQ_WAKE_MULPKT BIT(2) /* Wake on multicast packet */ |
| #define AN8801_IRQ_WAKE_BCPKT BIT(3) /* Wake on broadcast packet */ |
| #define AN8801_IRQ_WAKE_MAGICPKT BIT(4) /* Wake on magic packet */ |
| #define AN8801_IRQ_WAKE_ALL GENMASK(4, 0) |
| |
| /* MDIO_MMD_VEND1 Registers */ |
| #define AN8801_PHY_TX_PAIR_DLY_SEL_GBE 0x13 |
| #define AN8801_PHY_PAIR_DLY_SEL_A_GBE GENMASK(14, 12) |
| #define AN8801_PHY_PAIR_DLY_SEL_B_GBE GENMASK(10, 8) |
| #define AN8801_PHY_PAIR_DLY_SEL_C_GBE GENMASK(6, 4) |
| #define AN8801_PHY_PAIR_DLY_SEL_D_GBE GENMASK(2, 0) |
| #define AN8801_PHY_RXADC_CTRL 0xd8 |
| #define AN8801_PHY_RXADC_SAMP_PHSEL_A BIT(12) |
| #define AN8801_PHY_RXADC_SAMP_PHSEL_B BIT(8) |
| #define AN8801_PHY_RXADC_SAMP_PHSEL_C BIT(4) |
| #define AN8801_PHY_RXADC_SAMP_PHSEL_D BIT(0) |
| #define AN8801_PHY_RXADC_REV_0 0xd9 |
| #define AN8801_PHY_RXADC_REV_MASK_A GENMASK(15, 8) |
| #define AN8801_PHY_RXADC_REV_MASK_B GENMASK(7, 0) |
| #define AN8801_PHY_RXADC_REV_1 0xda |
| #define AN8801_PHY_RXADC_REV_MASK_C GENMASK(15, 8) |
| #define AN8801_PHY_RXADC_REV_MASK_D GENMASK(7, 0) |
| |
| /* MDIO_MMD_VEND2 Registers */ |
| #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) (0x24 + ((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) (0x25 + ((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_COLLISION BIT(6) |
| #define LED_BLINK_EVT_RX_CRC_ERR BIT(7) |
| #define LED_BLINK_EVT_RX_IDLE_ERR BIT(8) |
| #define LED_BLINK_EVT_FORCE BIT(9) |
| |
| #define AN8801R_NUM_LEDS 3 |
| #define AN8801_PERIOD_SHIFT 15 |
| #define AN8801_PERIOD_UNIT 32768 /* (1 << AN8801_PERIOD_SHIFT) */ |
| #define AN8801_MAX_PERIOD_MS 2147 |
| |
| #define AN8801_REG_PHY_INTERNAL0 0x600 |
| #define AN8801_REG_PHY_INTERNAL1 0x601 |
| #define AN8801_PHY_INTFUNC_MASK GENMASK(15, 0) /* PHY internal functions */ |
| |
| enum an8801r_led_fn { |
| AN8801R_LED_FN_NONE, |
| AN8801R_LED_FN_LINK, |
| AN8801R_LED_FN_ACTIVITY, |
| AN8801R_LED_FN_MAX, |
| }; |
| |
| struct an8801r_priv { |
| bool wake_magic_enabled; |
| bool wake_lnkchg_enabled; |
| }; |
| |
| static const unsigned long an8801r_led_trig = BIT(TRIGGER_NETDEV_LINK) | |
| BIT(TRIGGER_NETDEV_LINK_10) | |
| BIT(TRIGGER_NETDEV_LINK_100) | |
| BIT(TRIGGER_NETDEV_LINK_1000) | |
| BIT(TRIGGER_NETDEV_RX) | |
| BIT(TRIGGER_NETDEV_RX_ERR) | |
| BIT(TRIGGER_NETDEV_TX); |
| |
| 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 an8801_buckpbus_reg_read(struct phy_device *phydev, u32 addr, |
| u32 *data) |
| { |
| return air_phy_buckpbus_reg_read(phydev, |
| addr | AN8801_PBUS_ACCESS, |
| data); |
| } |
| |
| static u32 an8801r_led_blink_ms_to_hw(unsigned long req_ms) |
| { |
| u32 req_ns, regval; |
| |
| if (req_ms > AN8801_MAX_PERIOD_MS) |
| req_ms = AN8801_MAX_PERIOD_MS; |
| |
| req_ns = req_ms * NSEC_PER_MSEC; |
| |
| /* Round to the nearest period unit... */ |
| regval = req_ns + (AN8801_PERIOD_UNIT / 2); |
| |
| /* ...and now divide by the full period */ |
| regval >>= AN8801_PERIOD_SHIFT; |
| |
| return regval; |
| } |
| |
| static int an8801r_led_blink_set(struct phy_device *phydev, u8 index, |
| unsigned long *delay_on, |
| unsigned long *delay_off) |
| { |
| u32 hw_delay_on, hw_delay_off; |
| u16 blink_dur; |
| bool blink; |
| int ret; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| if (delay_on && delay_off) { |
| blink = true; |
| |
| if (*delay_on == 0 || *delay_off == 0) { |
| *delay_on = 64; |
| *delay_off = 64; |
| } |
| |
| hw_delay_on = an8801r_led_blink_ms_to_hw(*delay_on); |
| hw_delay_off = an8801r_led_blink_ms_to_hw(*delay_off); |
| } else { |
| blink = false; |
| } |
| |
| if (blink) { |
| blink_dur = (u16)min(hw_delay_on + hw_delay_off, |
| LED_BLINK_DUR_MASK); |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_DUR, |
| blink_dur); |
| if (ret) |
| goto error; |
| |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, LED_ON_DUR, |
| hw_delay_on); |
| if (ret) |
| goto error; |
| } |
| |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), |
| LED_BLINK_EVT_MASK, |
| blink ? LED_BLINK_EVT_FORCE : 0); |
| if (ret) |
| return ret; |
| |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EVT_MASK, |
| 0); |
| if (ret) |
| return ret; |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EN, blink ? LED_ON_EN : 0); |
| |
| error: |
| phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EN, 0); |
| return ret; |
| } |
| |
| static int an8801r_led_brightness_set(struct phy_device *phydev, u8 index, |
| enum led_brightness value) |
| { |
| int ret; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| /* Disable blink first if previously enabled */ |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), |
| LED_BLINK_EVT_MASK, 0); |
| if (ret) |
| return ret; |
| |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EVT_MASK, |
| (value == LED_OFF) ? 0 : LED_ON_EVT_FORCE); |
| if (ret) |
| return ret; |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EN, (value == LED_OFF) ? 0 : LED_ON_EN); |
| } |
| |
| static int an8801r_led_hw_control_get(struct phy_device *phydev, u8 index, |
| unsigned long *rules) |
| { |
| int on, blink; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| on = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index)); |
| if (on < 0) |
| return on; |
| |
| blink = phy_read_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index)); |
| if (blink < 0) |
| return blink; |
| |
| if (FIELD_GET(LED_ON_EVT_LINK_10M, on)) |
| __set_bit(TRIGGER_NETDEV_LINK_10, rules); |
| |
| if (FIELD_GET(LED_ON_EVT_LINK_100M, on)) |
| __set_bit(TRIGGER_NETDEV_LINK_100, rules); |
| |
| if (FIELD_GET(LED_ON_EVT_LINK_1000M, on)) |
| __set_bit(TRIGGER_NETDEV_LINK_1000, rules); |
| |
| if (FIELD_GET(LED_ON_EVT_LINK_10M, on) && |
| FIELD_GET(LED_ON_EVT_LINK_100M, on) && |
| FIELD_GET(LED_ON_EVT_LINK_1000M, on)) |
| __set_bit(TRIGGER_NETDEV_LINK, rules); |
| |
| if (FIELD_GET(LED_BLINK_EVT_10M_RX, blink) || |
| FIELD_GET(LED_BLINK_EVT_100M_RX, blink) || |
| FIELD_GET(LED_BLINK_EVT_1000M_RX, blink)) |
| __set_bit(TRIGGER_NETDEV_RX, rules); |
| |
| if (FIELD_GET(LED_BLINK_EVT_10M_TX, blink) || |
| FIELD_GET(LED_BLINK_EVT_100M_TX, blink) || |
| FIELD_GET(LED_BLINK_EVT_1000M_TX, blink)) |
| __set_bit(TRIGGER_NETDEV_TX, rules); |
| |
| if (FIELD_GET(LED_BLINK_EVT_RX_CRC_ERR, blink)) |
| __set_bit(TRIGGER_NETDEV_RX_ERR, rules); |
| |
| return 0; |
| } |
| |
| static int an8801r_led_trig_to_hw(unsigned long rules, u16 *on, u16 *blink) |
| { |
| /* All combinations of the supported triggers are allowed */ |
| if (rules & ~an8801r_led_trig) |
| return -EOPNOTSUPP; |
| |
| if (test_bit(TRIGGER_NETDEV_LINK_10, &rules)) |
| *on |= LED_ON_EVT_LINK_10M; |
| |
| if (test_bit(TRIGGER_NETDEV_LINK_100, &rules)) |
| *on |= LED_ON_EVT_LINK_100M; |
| |
| if (test_bit(TRIGGER_NETDEV_LINK_1000, &rules)) |
| *on |= LED_ON_EVT_LINK_1000M; |
| |
| if (test_bit(TRIGGER_NETDEV_LINK, &rules)) { |
| *on |= LED_ON_EVT_LINK_10M; |
| *on |= LED_ON_EVT_LINK_100M; |
| *on |= LED_ON_EVT_LINK_1000M; |
| } |
| |
| if (test_bit(TRIGGER_NETDEV_RX, &rules)) { |
| *blink |= LED_BLINK_EVT_10M_RX; |
| *blink |= LED_BLINK_EVT_100M_RX; |
| *blink |= LED_BLINK_EVT_1000M_RX; |
| } |
| |
| if (test_bit(TRIGGER_NETDEV_TX, &rules)) { |
| *blink |= LED_BLINK_EVT_10M_TX; |
| *blink |= LED_BLINK_EVT_100M_TX; |
| *blink |= LED_BLINK_EVT_1000M_TX; |
| } |
| |
| if (test_bit(TRIGGER_NETDEV_RX_ERR, &rules)) |
| *blink |= LED_BLINK_EVT_RX_CRC_ERR; |
| |
| return 0; |
| } |
| |
| static int an8801r_led_hw_is_supported(struct phy_device *phydev, u8 index, |
| unsigned long rules) |
| { |
| u16 on = 0, blink = 0; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| return an8801r_led_trig_to_hw(rules, &on, &blink); |
| } |
| |
| static int an8801r_led_hw_control_set(struct phy_device *phydev, u8 index, |
| unsigned long rules) |
| { |
| u16 on = 0, blink = 0; |
| int ret; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| ret = an8801r_led_trig_to_hw(rules, &on, &blink); |
| if (ret) |
| return ret; |
| |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EVT_MASK, on); |
| if (ret) |
| return ret; |
| |
| ret = phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_BLINK_CTRL(index), |
| LED_BLINK_EVT_MASK, blink); |
| if (ret) |
| return ret; |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_EN, (on | blink) ? LED_ON_EN : 0); |
| } |
| |
| static int an8801r_led_polarity_set(struct phy_device *phydev, int index, |
| unsigned long modes) |
| { |
| bool active_high = true; |
| unsigned long mode; |
| |
| if (index >= AN8801R_NUM_LEDS) |
| return -EINVAL; |
| |
| for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { |
| switch (mode) { |
| case PHY_LED_ACTIVE_HIGH: |
| break; |
| case PHY_LED_ACTIVE_LOW: |
| active_high = false; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND2, LED_ON_CTRL(index), |
| LED_ON_POL, active_high ? LED_ON_POL : 0); |
| } |
| |
| static int an8801r_led_init(struct phy_device *phydev, u8 *led_cfg) |
| { |
| int led_id, ret; |
| |
| /* Set LED BCR Enable */ |
| ret = phy_set_bits_mmd(phydev, MDIO_MMD_VEND2, LED_BCR, |
| LED_BCR_EXT_CTRL | LED_BCR_CLK_EN); |
| if (ret) |
| return ret; |
| |
| for (led_id = 0; led_id < AN8801R_NUM_LEDS; led_id++) { |
| unsigned long led_trigger = 0; |
| u32 led_gpio = led_id + 1; |
| |
| switch (led_cfg[led_id]) { |
| case AN8801R_LED_FN_LINK: |
| led_trigger = BIT(TRIGGER_NETDEV_LINK); |
| break; |
| case AN8801R_LED_FN_ACTIVITY: |
| led_trigger = BIT(TRIGGER_NETDEV_RX) | |
| BIT(TRIGGER_NETDEV_TX); |
| break; |
| default: |
| led_trigger = 0; |
| break; |
| } |
| |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_LED_GPIO, |
| BIT(led_gpio)); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_rmw(phydev, |
| AN8801_BPBUS_REG_LED_ID_SEL, |
| LED_ID_GPIO_SEL_MASK(led_gpio), |
| LED_ID_GPIO_SEL(led_id, |
| led_gpio)); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_clear_bits(phydev, |
| AN8801_BPBUS_REG_GPIO_MODE, |
| BIT(led_gpio)); |
| if (ret) |
| return ret; |
| |
| if (!led_trigger) |
| continue; |
| |
| ret = an8801r_led_hw_control_set(phydev, led_id, led_trigger); |
| if (ret) |
| return ret; |
| } |
| |
| return ret; |
| } |
| |
| static int an8801r_reset_wake(struct phy_device *phydev) |
| { |
| struct an8801r_priv *priv = phydev->priv; |
| u32 reg_val = 0; |
| int ret; |
| |
| /* Enable wakeup clear and disable wake up output */ |
| ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL2, |
| AN8801_PME_WAKEUP_CLR | |
| AN8801_WAKE_OUT_POLARITY_NEG); |
| if (ret) |
| return ret; |
| |
| /* Clear WAKEUP_CTL1 register before enabling the wakeup events |
| * again |
| */ |
| ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, |
| 0); |
| if (ret) |
| return ret; |
| |
| if (priv->wake_magic_enabled) |
| reg_val |= AN8801_WOL_WAKE_MAGIC_EN; |
| |
| if (priv->wake_lnkchg_enabled) |
| reg_val |= AN8801_WOL_WAKE_LNKCHG_EN; |
| |
| ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, |
| reg_val); |
| if (ret) |
| return ret; |
| |
| /* Disable wake up clear and re-enable wake up output */ |
| return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKEUP_CTL2, |
| AN8801_WAKE_OUT_POLARITY_NEG | |
| AN8801_WAKE_OUT_EN); |
| } |
| |
| static int an8801r_ack_interrupt(struct phy_device *phydev) |
| { |
| int ret; |
| |
| /* Reset wake status */ |
| ret = an8801r_reset_wake(phydev); |
| if (ret) |
| return ret; |
| |
| /* Clear the interrupts by writing the reg */ |
| return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_WAKE_IRQ_STS, |
| AN8801_IRQ_WAKE_ALL); |
| } |
| |
| static int an8801r_config_intr(struct phy_device *phydev) |
| { |
| int ret; |
| |
| if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
| u32 val = FIELD_PREP(AN8801_PHY_IRQ_GPIO_NUM_MASK, |
| AN8801_PHY_IRQ_GPIO_NUM); |
| |
| ret = an8801_buckpbus_reg_write(phydev, |
| AN8801_BPBUS_REG_PHY_IRQ_GPIO, |
| val); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_LNKCHG); |
| if (ret) |
| return ret; |
| |
| } else { |
| ret = an8801_buckpbus_reg_write(phydev, |
| AN8801_BPBUS_REG_PHY_IRQ_GPIO, |
| 0); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_clear_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_LNKCHG); |
| if (ret) |
| return ret; |
| } |
| |
| return an8801r_ack_interrupt(phydev); |
| } |
| |
| static irqreturn_t an8801r_handle_interrupt(struct phy_device *phydev) |
| { |
| u32 irq_status = 0; |
| bool irq_handled = false; |
| int ret; |
| |
| ret = an8801_buckpbus_reg_read(phydev, AN8801_BPBUS_REG_WAKE_IRQ_STS, |
| &irq_status); |
| if (ret) |
| return IRQ_NONE; |
| |
| ret = an8801r_ack_interrupt(phydev); |
| if (ret) |
| return IRQ_NONE; |
| |
| if (irq_status & AN8801_IRQ_WAKE_MAGICPKT) { |
| pm_wakeup_event(&phydev->mdio.dev, 0); |
| irq_handled = true; |
| } |
| |
| if (irq_status & AN8801_IRQ_WAKE_LNKCHG) { |
| phy_trigger_machine(phydev); |
| irq_handled = true; |
| } |
| |
| return irq_handled ? IRQ_HANDLED : IRQ_NONE; |
| } |
| |
| static void an8801r_get_wol(struct phy_device *phydev, |
| struct ethtool_wolinfo *wol) |
| { |
| u32 reg_val; |
| int ret; |
| |
| /* If the PHY is not capable of waking the system, then WoL can not |
| * be supported. |
| */ |
| if (!device_can_wakeup(&phydev->mdio.dev)) { |
| wol->supported = 0; |
| return; |
| } |
| |
| wol->supported = WAKE_MAGIC; |
| wol->wolopts = 0; |
| |
| ret = an8801_buckpbus_reg_read(phydev, AN8801_BPBUS_REG_WAKEUP_CTL1, |
| ®_val); |
| if (ret) |
| return; |
| |
| if (reg_val & AN8801_WOL_WAKE_MAGIC_EN) |
| wol->wolopts |= WAKE_MAGIC; |
| else |
| wol->wolopts &= ~WAKE_MAGIC; |
| } |
| |
| static int an8801r_set_wol(struct phy_device *phydev, |
| struct ethtool_wolinfo *wol) |
| { |
| struct an8801r_priv *priv = phydev->priv; |
| struct net_device *attach_dev; |
| const u8 *macaddr; |
| u32 reg_val; |
| int ret; |
| |
| if (!device_can_wakeup(&phydev->mdio.dev)) |
| return -EOPNOTSUPP; |
| |
| if (wol->wolopts & ~WAKE_MAGIC) |
| return -EINVAL; |
| |
| if (wol->wolopts & WAKE_MAGIC) { |
| attach_dev = phydev->attached_dev; |
| if (!attach_dev) |
| return -ENODEV; |
| |
| macaddr = (const u8 *)attach_dev->dev_addr; |
| if (!is_valid_ether_addr(macaddr)) |
| return -EINVAL; |
| |
| /* MAC bits 16..47 */ |
| reg_val = (macaddr[2] << 24) | (macaddr[3] << 16); |
| reg_val |= (macaddr[4] << 8) | (macaddr[5]); |
| |
| ret = an8801_buckpbus_reg_write(phydev, |
| AN8801_BPBUS_REG_WOL_MAC_16_47, |
| reg_val); |
| if (ret) |
| return ret; |
| |
| /* MAC bits 0..15 */ |
| reg_val = (macaddr[0] << 8) | (macaddr[1]); |
| |
| ret = an8801_buckpbus_reg_write(phydev, |
| AN8801_BPBUS_REG_WOL_MAC_0_15, |
| reg_val); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_WAKEUP_CTL1, |
| AN8801_WOL_WAKE_MAGIC_EN); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_MAGICPKT); |
| if (ret) |
| return ret; |
| |
| } else { |
| ret = an8801_buckpbus_reg_clear_bits(phydev, |
| AN8801_BPBUS_REG_WAKEUP_CTL1, |
| AN8801_WOL_WAKE_MAGIC_EN); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_clear_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_MAGICPKT); |
| if (ret) |
| return ret; |
| } |
| |
| priv->wake_magic_enabled = !!(wol->wolopts & WAKE_MAGIC); |
| |
| return device_set_wakeup_enable(&phydev->mdio.dev, |
| priv->wake_magic_enabled); |
| } |
| |
| static int an8801r_of_init_leds(struct phy_device *phydev, u8 *led_cfg) |
| { |
| struct device *dev = &phydev->mdio.dev; |
| struct device_node *np = dev->of_node; |
| struct device_node *leds; |
| u32 function_enum_idx; |
| int ret = 0; |
| |
| if (!np) |
| return 0; |
| |
| /* If devicetree is present, leds configuration is required */ |
| leds = of_get_child_by_name(np, "leds"); |
| if (!leds) |
| return 0; |
| |
| for_each_available_child_of_node_scoped(leds, led) { |
| u32 led_idx; |
| |
| ret = of_property_read_u32(led, "reg", &led_idx); |
| if (ret) |
| goto out; |
| |
| if (led_idx >= AN8801R_NUM_LEDS) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| ret = of_property_read_u32(led, "function-enumerator", |
| &function_enum_idx); |
| if (ret) { |
| function_enum_idx = AN8801R_LED_FN_NONE; |
| ret = 0; |
| } |
| |
| if (function_enum_idx >= AN8801R_LED_FN_MAX) { |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| led_cfg[led_idx] = function_enum_idx; |
| } |
| out: |
| of_node_put(leds); |
| return ret; |
| } |
| |
| static int an8801r_rgmii_rxdelay(struct phy_device *phydev, bool enable, |
| u16 delay_steps) |
| { |
| u32 reg_val; |
| |
| if (delay_steps > RGMII_DELAY_STEP_MASK) |
| return -EINVAL; |
| |
| if (enable) { |
| /* Set force mode bit to enable RX delay insertion */ |
| reg_val = delay_steps | RGMII_RXDELAY_FORCE_MODE; |
| |
| /* Set align bit to add extra offset for RX delay */ |
| reg_val |= RGMII_RXDELAY_ALIGN; |
| } else { |
| reg_val = 0; |
| } |
| |
| return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_RXDLY_STEP, |
| reg_val); |
| } |
| |
| static int an8801r_rgmii_txdelay(struct phy_device *phydev, bool enable, |
| u16 delay_steps) |
| { |
| u32 reg_val; |
| |
| if (delay_steps > RGMII_DELAY_STEP_MASK) |
| return -EINVAL; |
| |
| if (enable) { |
| /* Set force mode bit to enable TX delay insertion */ |
| reg_val = delay_steps | RGMII_TXDELAY_FORCE_MODE; |
| } else { |
| reg_val = 0; |
| } |
| |
| return an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_TXDLY_STEP, |
| reg_val); |
| } |
| |
| static int an8801r_rgmii_delay_config(struct phy_device *phydev) |
| { |
| bool enable_delay; |
| u16 delay_step; |
| int ret; |
| |
| if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || |
| phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { |
| enable_delay = true; |
| delay_step = AN8801_RGMII_TXDELAY_DEFAULT; |
| } else { |
| enable_delay = false; |
| delay_step = RGMII_DELAY_NO_STEP; |
| } |
| |
| ret = an8801r_rgmii_txdelay(phydev, enable_delay, delay_step); |
| if (ret) |
| return ret; |
| |
| if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || |
| phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { |
| enable_delay = true; |
| delay_step = AN8801_RGMII_RXDELAY_DEFAULT; |
| } else { |
| enable_delay = false; |
| delay_step = RGMII_DELAY_NO_STEP; |
| } |
| |
| return an8801r_rgmii_rxdelay(phydev, enable_delay, delay_step); |
| } |
| |
| static int an8801r_config_init(struct phy_device *phydev) |
| { |
| u8 led_default_function[AN8801R_NUM_LEDS] = { 0 }; |
| int ret; |
| |
| ret = an8801r_of_init_leds(phydev, led_default_function); |
| if (ret) |
| return ret; |
| |
| /* Disable Low Power Mode (LPM) */ |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL0, |
| FIELD_PREP(AN8801_PHY_INTFUNC_MASK, 0x1e)); |
| if (ret) |
| return ret; |
| |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, AN8801_REG_PHY_INTERNAL1, |
| FIELD_PREP(AN8801_PHY_INTFUNC_MASK, 0x2)); |
| if (ret) |
| return ret; |
| |
| /* Set the PHY to perform auto-downshift after 3 auto-negotiation |
| * attempts |
| */ |
| ret = phy_write_paged(phydev, AIR_PHY_PAGE_EXTENDED_1, |
| AN8801_EXT_REG_PHY, |
| FIELD_PREP(AN8801_EXT_PHY_CTRL1, 0x1d) | |
| FIELD_PREP(AN8801_EXT_PHY_DOWNSHIFT_CTL, 1) | |
| AN8801_EXT_PHY_DOWNSHIFT_EN); |
| if (ret < 0) |
| 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 = phy_write_mmd(phydev, MDIO_MMD_VEND1, |
| AN8801_PHY_TX_PAIR_DLY_SEL_GBE, |
| FIELD_PREP(AN8801_PHY_PAIR_DLY_SEL_A_GBE, 4) | |
| FIELD_PREP(AN8801_PHY_PAIR_DLY_SEL_C_GBE, 4)); |
| if (ret) |
| return ret; |
| |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_CTRL, |
| AN8801_PHY_RXADC_SAMP_PHSEL_A | |
| AN8801_PHY_RXADC_SAMP_PHSEL_C); |
| if (ret) |
| return ret; |
| |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_REV_0, |
| FIELD_PREP(AN8801_PHY_RXADC_REV_MASK_A, 1)); |
| if (ret) |
| return ret; |
| |
| ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, AN8801_PHY_RXADC_REV_1, |
| FIELD_PREP(AN8801_PHY_RXADC_REV_MASK_C, 1)); |
| if (ret) |
| return ret; |
| |
| ret = an8801r_rgmii_delay_config(phydev); |
| if (ret) |
| return ret; |
| |
| ret = an8801_buckpbus_reg_write(phydev, AN8801_BPBUS_REG_CKO, |
| AN8801_CKO_OUTPUT_MODE_AUTO); |
| if (ret) |
| return ret; |
| |
| ret = an8801r_led_init(phydev, led_default_function); |
| if (ret) { |
| phydev_err(phydev, "Cannot initialize LEDs: %d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int an8801r_read_status(struct phy_device *phydev) |
| { |
| int prev_speed, ret; |
| u32 val; |
| |
| prev_speed = phydev->speed; |
| |
| ret = genphy_read_status(phydev); |
| if (ret) |
| return ret; |
| |
| if (!phydev->link) { |
| phydev->speed = SPEED_UNKNOWN; |
| return 0; |
| } |
| |
| if (prev_speed != phydev->speed) { |
| /* Ensure that PHY switches to 1G speed when available, |
| * by configuring the function mode for either 1G or 100M/10M |
| * operation. |
| * Therefore, set the link mode register, after read_status |
| * determines the link speed. |
| */ |
| val = 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, |
| val); |
| } |
| |
| return 0; |
| } |
| |
| static int an8801r_probe(struct phy_device *phydev) |
| { |
| struct device *dev = &phydev->mdio.dev; |
| struct an8801r_priv *priv; |
| |
| priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); |
| if (!priv) |
| return -ENOMEM; |
| |
| priv->wake_lnkchg_enabled = true; |
| |
| phydev->priv = priv; |
| |
| /* Mark this PHY as wakeup capable and register the interrupt as a |
| * wakeup IRQ if the PHY is marked as a wakeup source in devicetree, |
| * and the interrupt is valid. |
| */ |
| if (of_property_read_bool(dev->of_node, "wakeup-source") && |
| phy_interrupt_is_valid(phydev)) { |
| device_set_wakeup_capable(dev, true); |
| devm_pm_set_wake_irq(dev, phydev->irq); |
| } |
| |
| return 0; |
| } |
| |
| static int an8801r_suspend(struct phy_device *phydev) |
| { |
| struct an8801r_priv *priv = phydev->priv; |
| int ret; |
| |
| /* If the PHY may wake up by a Wake-on-LAN (WOL) event, disable the link |
| * interrupt to only keep the WOL magic interrupt enabled |
| */ |
| if (device_may_wakeup(&phydev->mdio.dev)) { |
| priv->wake_lnkchg_enabled = false; |
| |
| if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
| ret = an8801_buckpbus_reg_clear_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_LNKCHG); |
| if (ret) |
| return ret; |
| } |
| |
| /* Reset WOL status */ |
| ret = an8801r_reset_wake(phydev); |
| if (ret) |
| return ret; |
| } |
| |
| if (!phydev->wol_enabled) |
| return genphy_suspend(phydev); |
| |
| return 0; |
| } |
| |
| static int an8801r_resume(struct phy_device *phydev) |
| { |
| struct an8801r_priv *priv = phydev->priv; |
| int ret; |
| |
| ret = genphy_resume(phydev); |
| if (ret) |
| return ret; |
| |
| /* Restore the interrupt enable so phylib can receive link |
| * state interrupts. |
| */ |
| if (device_may_wakeup(&phydev->mdio.dev)) { |
| priv->wake_lnkchg_enabled = true; |
| |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_WAKEUP_CTL1, |
| AN8801_WOL_WAKE_LNKCHG_EN); |
| if (ret) |
| return ret; |
| |
| if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { |
| ret = an8801_buckpbus_reg_set_bits(phydev, |
| AN8801_BPBUS_REG_WAKE_IRQ_EN, |
| AN8801_IRQ_WAKE_LNKCHG); |
| } |
| } |
| |
| return ret; |
| } |
| |
| static struct phy_driver airoha_driver[] = { |
| { |
| PHY_ID_MATCH_MODEL(AN8801R_PHY_ID), |
| .name = "Airoha AN8801R", |
| .probe = an8801r_probe, |
| .config_init = an8801r_config_init, |
| .suspend = an8801r_suspend, |
| .resume = an8801r_resume, |
| .config_aneg = genphy_config_aneg, |
| .read_status = an8801r_read_status, |
| .config_intr = an8801r_config_intr, |
| .handle_interrupt = an8801r_handle_interrupt, |
| .set_wol = an8801r_set_wol, |
| .get_wol = an8801r_get_wol, |
| .read_page = air_phy_read_page, |
| .write_page = air_phy_write_page, |
| .flags = PHY_ALWAYS_CALL_SUSPEND, |
| .led_brightness_set = an8801r_led_brightness_set, |
| .led_blink_set = an8801r_led_blink_set, |
| .led_hw_is_supported = an8801r_led_hw_is_supported, |
| .led_hw_control_set = an8801r_led_hw_control_set, |
| .led_hw_control_get = an8801r_led_hw_control_get, |
| .led_polarity_set = an8801r_led_polarity_set, |
| } }; |
| module_phy_driver(airoha_driver); |
| |
| static struct mdio_device_id __maybe_unused an8801_tbl[] = { |
| { PHY_ID_MATCH_MODEL(AN8801R_PHY_ID) }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(mdio, an8801_tbl); |
| |
| MODULE_DESCRIPTION("Airoha AN8801 PHY driver"); |
| MODULE_AUTHOR("AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>"); |
| MODULE_LICENSE("GPL"); |