mirror of
https://github.com/torvalds/linux.git
synced 2026-07-27 01:32:21 +02:00
To ensure that the Airoha AN8801R PHY uses the maximum available link speed, an additional register write is needed to configure the function mode for either 1G or 100M/10M operation after link detection. So, in air_an8801 driver, implement a custom read_status callback, that after genphy_read_status determines the link speed, sets the bit 0 of the link mode register (REG_LINK_MODE) if the detected speed is 1Gbps, or unsets it otherwise. Signed-off-by: Louis-Alexis Eyraud <louisalexis.eyraud@collabora.com> Reviewed-by: Andrew Lunn <andrew@lunn.ch> Link: https://patch.msgid.link/20260526-add-airoha-an8801-support-v5-6-01aea8dee69b@collabora.com Signed-off-by: Jakub Kicinski <kuba@kernel.org>
1157 lines
29 KiB
C
1157 lines
29 KiB
C
// 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");
|