diff options
Diffstat (limited to 'drivers/net/phy/intel-xway.c')
| -rw-r--r-- | drivers/net/phy/intel-xway.c | 789 |
1 files changed, 789 insertions, 0 deletions
diff --git a/drivers/net/phy/intel-xway.c b/drivers/net/phy/intel-xway.c new file mode 100644 index 000000000..afbcec711 --- /dev/null +++ b/drivers/net/phy/intel-xway.c @@ -0,0 +1,789 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2012 Daniel Schwierzeck <daniel.schwierzeck@googlemail.com> + * Copyright (C) 2016 Hauke Mehrtens <hauke@hauke-m.de> + */ + +#include <linux/mdio.h> +#include <linux/module.h> +#include <linux/phy.h> +#include <linux/of.h> +#include <linux/bitfield.h> + +#define XWAY_MDIO_ERRCNT 0x15 /* error counter */ +#define XWAY_MDIO_MIICTRL 0x17 /* mii control */ +#define XWAY_MDIO_IMASK 0x19 /* interrupt mask */ +#define XWAY_MDIO_ISTAT 0x1A /* interrupt status */ +#define XWAY_MDIO_LED 0x1B /* led control */ + +#define XWAY_MDIO_ERRCNT_SEL GENMASK(11, 8) +#define XWAY_MDIO_ERRCNT_COUNT GENMASK(7, 0) +#define XWAY_MDIO_ERRCNT_SEL_RXERR 0 + +#define XWAY_MDIO_MIICTRL_RXSKEW_MASK GENMASK(14, 12) +#define XWAY_MDIO_MIICTRL_TXSKEW_MASK GENMASK(10, 8) + +/* bit 15:12 are reserved */ +#define XWAY_MDIO_LED_LED3_EN BIT(11) /* Enable the integrated function of LED3 */ +#define XWAY_MDIO_LED_LED2_EN BIT(10) /* Enable the integrated function of LED2 */ +#define XWAY_MDIO_LED_LED1_EN BIT(9) /* Enable the integrated function of LED1 */ +#define XWAY_MDIO_LED_LED0_EN BIT(8) /* Enable the integrated function of LED0 */ +/* bit 7:4 are reserved */ +#define XWAY_MDIO_LED_LED3_DA BIT(3) /* Direct Access to LED3 */ +#define XWAY_MDIO_LED_LED2_DA BIT(2) /* Direct Access to LED2 */ +#define XWAY_MDIO_LED_LED1_DA BIT(1) /* Direct Access to LED1 */ +#define XWAY_MDIO_LED_LED0_DA BIT(0) /* Direct Access to LED0 */ + +#define XWAY_MDIO_INIT_WOL BIT(15) /* Wake-On-LAN */ +#define XWAY_MDIO_INIT_MSRE BIT(14) +#define XWAY_MDIO_INIT_NPRX BIT(13) +#define XWAY_MDIO_INIT_NPTX BIT(12) +#define XWAY_MDIO_INIT_ANE BIT(11) /* Auto-Neg error */ +#define XWAY_MDIO_INIT_ANC BIT(10) /* Auto-Neg complete */ +#define XWAY_MDIO_INIT_ADSC BIT(5) /* Link auto-downspeed detect */ +#define XWAY_MDIO_INIT_MPIPC BIT(4) +#define XWAY_MDIO_INIT_MDIXC BIT(3) +#define XWAY_MDIO_INIT_DXMC BIT(2) /* Duplex mode change */ +#define XWAY_MDIO_INIT_LSPC BIT(1) /* Link speed change */ +#define XWAY_MDIO_INIT_LSTC BIT(0) /* Link state change */ +#define XWAY_MDIO_INIT_MASK (XWAY_MDIO_INIT_LSTC | \ + XWAY_MDIO_INIT_ADSC) + +#define ADVERTISED_MPD BIT(10) /* Multi-port device */ + +/* LED Configuration */ +#define XWAY_MMD_LEDCH 0x01E0 +/* Inverse of SCAN Function */ +#define XWAY_MMD_LEDCH_NACS_NONE 0x0000 +#define XWAY_MMD_LEDCH_NACS_LINK 0x0001 +#define XWAY_MMD_LEDCH_NACS_PDOWN 0x0002 +#define XWAY_MMD_LEDCH_NACS_EEE 0x0003 +#define XWAY_MMD_LEDCH_NACS_ANEG 0x0004 +#define XWAY_MMD_LEDCH_NACS_ABIST 0x0005 +#define XWAY_MMD_LEDCH_NACS_CDIAG 0x0006 +#define XWAY_MMD_LEDCH_NACS_TEST 0x0007 +/* Slow Blink Frequency */ +#define XWAY_MMD_LEDCH_SBF_F02HZ 0x0000 +#define XWAY_MMD_LEDCH_SBF_F04HZ 0x0010 +#define XWAY_MMD_LEDCH_SBF_F08HZ 0x0020 +#define XWAY_MMD_LEDCH_SBF_F16HZ 0x0030 +/* Fast Blink Frequency */ +#define XWAY_MMD_LEDCH_FBF_F02HZ 0x0000 +#define XWAY_MMD_LEDCH_FBF_F04HZ 0x0040 +#define XWAY_MMD_LEDCH_FBF_F08HZ 0x0080 +#define XWAY_MMD_LEDCH_FBF_F16HZ 0x00C0 +/* LED Configuration */ +#define XWAY_MMD_LEDCL 0x01E1 +/* Complex Blinking Configuration */ +#define XWAY_MMD_LEDCH_CBLINK_NONE 0x0000 +#define XWAY_MMD_LEDCH_CBLINK_LINK 0x0001 +#define XWAY_MMD_LEDCH_CBLINK_PDOWN 0x0002 +#define XWAY_MMD_LEDCH_CBLINK_EEE 0x0003 +#define XWAY_MMD_LEDCH_CBLINK_ANEG 0x0004 +#define XWAY_MMD_LEDCH_CBLINK_ABIST 0x0005 +#define XWAY_MMD_LEDCH_CBLINK_CDIAG 0x0006 +#define XWAY_MMD_LEDCH_CBLINK_TEST 0x0007 +/* Complex SCAN Configuration */ +#define XWAY_MMD_LEDCH_SCAN_NONE 0x0000 +#define XWAY_MMD_LEDCH_SCAN_LINK 0x0010 +#define XWAY_MMD_LEDCH_SCAN_PDOWN 0x0020 +#define XWAY_MMD_LEDCH_SCAN_EEE 0x0030 +#define XWAY_MMD_LEDCH_SCAN_ANEG 0x0040 +#define XWAY_MMD_LEDCH_SCAN_ABIST 0x0050 +#define XWAY_MMD_LEDCH_SCAN_CDIAG 0x0060 +#define XWAY_MMD_LEDCH_SCAN_TEST 0x0070 +/* Configuration for LED Pin x */ +#define XWAY_MMD_LED0H 0x01E2 +/* Fast Blinking Configuration */ +#define XWAY_MMD_LEDxH_BLINKF_MASK 0x000F +#define XWAY_MMD_LEDxH_BLINKF_NONE 0x0000 +#define XWAY_MMD_LEDxH_BLINKF_LINK10 0x0001 +#define XWAY_MMD_LEDxH_BLINKF_LINK100 0x0002 +#define XWAY_MMD_LEDxH_BLINKF_LINK10X 0x0003 +#define XWAY_MMD_LEDxH_BLINKF_LINK1000 0x0004 +#define XWAY_MMD_LEDxH_BLINKF_LINK10_0 0x0005 +#define XWAY_MMD_LEDxH_BLINKF_LINK100X 0x0006 +#define XWAY_MMD_LEDxH_BLINKF_LINK10XX 0x0007 +#define XWAY_MMD_LEDxH_BLINKF_PDOWN 0x0008 +#define XWAY_MMD_LEDxH_BLINKF_EEE 0x0009 +#define XWAY_MMD_LEDxH_BLINKF_ANEG 0x000A +#define XWAY_MMD_LEDxH_BLINKF_ABIST 0x000B +#define XWAY_MMD_LEDxH_BLINKF_CDIAG 0x000C +/* Constant On Configuration */ +#define XWAY_MMD_LEDxH_CON_MASK 0x00F0 +#define XWAY_MMD_LEDxH_CON_NONE 0x0000 +#define XWAY_MMD_LEDxH_CON_LINK10 0x0010 +#define XWAY_MMD_LEDxH_CON_LINK100 0x0020 +#define XWAY_MMD_LEDxH_CON_LINK10X 0x0030 +#define XWAY_MMD_LEDxH_CON_LINK1000 0x0040 +#define XWAY_MMD_LEDxH_CON_LINK10_0 0x0050 +#define XWAY_MMD_LEDxH_CON_LINK100X 0x0060 +#define XWAY_MMD_LEDxH_CON_LINK10XX 0x0070 +#define XWAY_MMD_LEDxH_CON_PDOWN 0x0080 +#define XWAY_MMD_LEDxH_CON_EEE 0x0090 +#define XWAY_MMD_LEDxH_CON_ANEG 0x00A0 +#define XWAY_MMD_LEDxH_CON_ABIST 0x00B0 +#define XWAY_MMD_LEDxH_CON_CDIAG 0x00C0 +#define XWAY_MMD_LEDxH_CON_COPPER 0x00D0 +#define XWAY_MMD_LEDxH_CON_FIBER 0x00E0 +/* Configuration for LED Pin x */ +#define XWAY_MMD_LED0L 0x01E3 +/* Pulsing Configuration */ +#define XWAY_MMD_LEDxL_PULSE_MASK 0x000F +#define XWAY_MMD_LEDxL_PULSE_NONE 0x0000 +#define XWAY_MMD_LEDxL_PULSE_TXACT 0x0001 +#define XWAY_MMD_LEDxL_PULSE_RXACT 0x0002 +#define XWAY_MMD_LEDxL_PULSE_COL 0x0004 +/* Slow Blinking Configuration */ +#define XWAY_MMD_LEDxL_BLINKS_MASK 0x00F0 +#define XWAY_MMD_LEDxL_BLINKS_NONE 0x0000 +#define XWAY_MMD_LEDxL_BLINKS_LINK10 0x0010 +#define XWAY_MMD_LEDxL_BLINKS_LINK100 0x0020 +#define XWAY_MMD_LEDxL_BLINKS_LINK10X 0x0030 +#define XWAY_MMD_LEDxL_BLINKS_LINK1000 0x0040 +#define XWAY_MMD_LEDxL_BLINKS_LINK10_0 0x0050 +#define XWAY_MMD_LEDxL_BLINKS_LINK100X 0x0060 +#define XWAY_MMD_LEDxL_BLINKS_LINK10XX 0x0070 +#define XWAY_MMD_LEDxL_BLINKS_PDOWN 0x0080 +#define XWAY_MMD_LEDxL_BLINKS_EEE 0x0090 +#define XWAY_MMD_LEDxL_BLINKS_ANEG 0x00A0 +#define XWAY_MMD_LEDxL_BLINKS_ABIST 0x00B0 +#define XWAY_MMD_LEDxL_BLINKS_CDIAG 0x00C0 +#define XWAY_MMD_LED1H 0x01E4 +#define XWAY_MMD_LED1L 0x01E5 +#define XWAY_MMD_LED2H 0x01E6 +#define XWAY_MMD_LED2L 0x01E7 +#define XWAY_MMD_LED3H 0x01E8 +#define XWAY_MMD_LED3L 0x01E9 + +#define XWAY_GPHY_MAX_LEDS 3 +#define XWAY_GPHY_LED_INV(idx) BIT(12 + (idx)) +#define XWAY_GPHY_LED_EN(idx) BIT(8 + (idx)) +#define XWAY_GPHY_LED_DA(idx) BIT(idx) +#define XWAY_MMD_LEDxH(idx) (XWAY_MMD_LED0H + 2 * (idx)) +#define XWAY_MMD_LEDxL(idx) (XWAY_MMD_LED0L + 2 * (idx)) + +#define PHY_ID_PHY11G_1_3 0x030260D1 +#define PHY_ID_PHY22F_1_3 0x030260E1 +#define PHY_ID_PHY11G_1_4 0xD565A400 +#define PHY_ID_PHY22F_1_4 0xD565A410 +#define PHY_ID_PHY11G_1_5 0xD565A401 +#define PHY_ID_PHY22F_1_5 0xD565A411 +#define PHY_ID_PHY11G_VR9_1_1 0xD565A408 +#define PHY_ID_PHY22F_VR9_1_1 0xD565A418 +#define PHY_ID_PHY11G_VR9_1_2 0xD565A409 +#define PHY_ID_PHY22F_VR9_1_2 0xD565A419 + +struct xway_gphy_priv { + u64 rx_errors; +}; + +static const int xway_internal_delay[] = {0, 500, 1000, 1500, 2000, 2500, + 3000, 3500}; + +static int xway_gphy_rgmii_init(struct phy_device *phydev) +{ + unsigned int delay_size = ARRAY_SIZE(xway_internal_delay); + s32 int_delay; + int val = 0; + + if (!phy_interface_is_rgmii(phydev)) + return 0; + + /* Existing behavior was to use default pin strapping delay in rgmii + * mode, but rgmii should have meant no delay. Warn existing users, + * but do not change anything at the moment. + */ + if (phydev->interface == PHY_INTERFACE_MODE_RGMII) { + u16 txskew, rxskew; + + val = phy_read(phydev, XWAY_MDIO_MIICTRL); + if (val < 0) + return val; + + txskew = FIELD_GET(XWAY_MDIO_MIICTRL_TXSKEW_MASK, val); + rxskew = FIELD_GET(XWAY_MDIO_MIICTRL_RXSKEW_MASK, val); + + if (txskew > 0 || rxskew > 0) + phydev_warn(phydev, + "PHY has delays (e.g. via pin strapping), but phy-mode = 'rgmii'\n" + "Should be 'rgmii-id' to use internal delays txskew:%d ps rxskew:%d ps\n", + xway_internal_delay[txskew], + xway_internal_delay[rxskew]); + return 0; + } + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_RXID) { + int_delay = phy_get_internal_delay(phydev, xway_internal_delay, + delay_size, true); + + /* if rx-internal-delay-ps is missing, use default of 2.0 ns */ + if (int_delay < 0) + int_delay = 4; /* 2000 ps */ + + val |= FIELD_PREP(XWAY_MDIO_MIICTRL_RXSKEW_MASK, int_delay); + } + + if (phydev->interface == PHY_INTERFACE_MODE_RGMII_ID || + phydev->interface == PHY_INTERFACE_MODE_RGMII_TXID) { + int_delay = phy_get_internal_delay(phydev, xway_internal_delay, + delay_size, false); + + /* if tx-internal-delay-ps is missing, use default of 2.0 ns */ + if (int_delay < 0) + int_delay = 4; /* 2000 ps */ + + val |= FIELD_PREP(XWAY_MDIO_MIICTRL_TXSKEW_MASK, int_delay); + } + + return phy_modify(phydev, XWAY_MDIO_MIICTRL, + XWAY_MDIO_MIICTRL_RXSKEW_MASK | + XWAY_MDIO_MIICTRL_TXSKEW_MASK, val); +} + +static int xway_gphy_init_leds(struct phy_device *phydev) +{ + int err; + u32 ledxh; + u32 ledxl; + + /* Ensure that integrated led function is enabled for all leds */ + err = phy_write(phydev, XWAY_MDIO_LED, + XWAY_MDIO_LED_LED0_EN | + XWAY_MDIO_LED_LED1_EN | + XWAY_MDIO_LED_LED2_EN | + XWAY_MDIO_LED_LED3_EN); + if (err) + return err; + + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCH, + XWAY_MMD_LEDCH_NACS_NONE | + XWAY_MMD_LEDCH_SBF_F02HZ | + XWAY_MMD_LEDCH_FBF_F16HZ); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDCL, + XWAY_MMD_LEDCH_CBLINK_NONE | + XWAY_MMD_LEDCH_SCAN_NONE); + + /** + * In most cases only one LED is connected to this phy, so + * configure them all to constant on and pulse mode. LED3 is + * only available in some packages, leave it in its reset + * configuration. + */ + ledxh = XWAY_MMD_LEDxH_BLINKF_NONE | XWAY_MMD_LEDxH_CON_LINK10XX; + ledxl = XWAY_MMD_LEDxL_PULSE_TXACT | XWAY_MMD_LEDxL_PULSE_RXACT | + XWAY_MMD_LEDxL_BLINKS_NONE; + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED0L, ledxl); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED1L, ledxl); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2H, ledxh); + phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LED2L, ledxl); + + return 0; +} + +static int xway_gphy_config_init(struct phy_device *phydev) +{ + struct device_node *np; + int err; + + /* Mask all interrupts */ + err = phy_write(phydev, XWAY_MDIO_IMASK, 0); + if (err) + return err; + + /* Use default LED configuration if 'leds' node isn't defined */ + np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds"); + if (np) + of_node_put(np); + else + xway_gphy_init_leds(phydev); + + /* Clear all pending interrupts */ + phy_read(phydev, XWAY_MDIO_ISTAT); + + err = xway_gphy_rgmii_init(phydev); + if (err) + return err; + + /* Count MDI RX errors (SymbolErrorDuringCarrier) */ + return phy_write(phydev, XWAY_MDIO_ERRCNT, + FIELD_PREP(XWAY_MDIO_ERRCNT_SEL, XWAY_MDIO_ERRCNT_SEL_RXERR)); +} + +static int xway_gphy_probe(struct phy_device *phydev) +{ + struct device *dev = &phydev->mdio.dev; + struct xway_gphy_priv *priv; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + phydev->priv = priv; + + return 0; +} + +static int xway_gphy14_config_aneg(struct phy_device *phydev) +{ + int reg, err; + + /* Advertise as multi-port device, see IEEE802.3-2002 40.5.1.1 */ + /* This is a workaround for an errata in rev < 1.5 devices */ + reg = phy_read(phydev, MII_CTRL1000); + reg |= ADVERTISED_MPD; + err = phy_write(phydev, MII_CTRL1000, reg); + if (err) + return err; + + return genphy_config_aneg(phydev); +} + +static int xway_gphy_ack_interrupt(struct phy_device *phydev) +{ + int reg; + + reg = phy_read(phydev, XWAY_MDIO_ISTAT); + return (reg < 0) ? reg : 0; +} + +static int xway_gphy_config_intr(struct phy_device *phydev) +{ + u16 mask = 0; + int err; + + if (phydev->interrupts == PHY_INTERRUPT_ENABLED) { + err = xway_gphy_ack_interrupt(phydev); + if (err) + return err; + + mask = XWAY_MDIO_INIT_MASK; + err = phy_write(phydev, XWAY_MDIO_IMASK, mask); + } else { + err = phy_write(phydev, XWAY_MDIO_IMASK, mask); + if (err) + return err; + + err = xway_gphy_ack_interrupt(phydev); + } + + return err; +} + +static irqreturn_t xway_gphy_handle_interrupt(struct phy_device *phydev) +{ + int irq_status; + + irq_status = phy_read(phydev, XWAY_MDIO_ISTAT); + if (irq_status < 0) { + phy_error(phydev); + return IRQ_NONE; + } + + if (!(irq_status & XWAY_MDIO_INIT_MASK)) + return IRQ_NONE; + + phy_trigger_machine(phydev); + + return IRQ_HANDLED; +} + +static int xway_gphy_led_brightness_set(struct phy_device *phydev, + u8 index, enum led_brightness value) +{ + int ret; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + /* clear EN and set manual LED state */ + ret = phy_modify(phydev, XWAY_MDIO_LED, + ((value == LED_OFF) ? XWAY_GPHY_LED_EN(index) : 0) | + XWAY_GPHY_LED_DA(index), + (value == LED_OFF) ? 0 : XWAY_GPHY_LED_DA(index)); + if (ret) + return ret; + + /* clear HW LED setup */ + if (value == LED_OFF) { + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), 0); + if (ret) + return ret; + + return phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), 0); + } else { + return 0; + } +} + +static const unsigned long supported_triggers = (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_TX)); + +static int xway_gphy_led_hw_is_supported(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + /* activity triggers are not possible without combination with a link + * trigger. + */ + if (rules & (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) && + !(rules & (BIT(TRIGGER_NETDEV_LINK) | + BIT(TRIGGER_NETDEV_LINK_10) | + BIT(TRIGGER_NETDEV_LINK_100) | + BIT(TRIGGER_NETDEV_LINK_1000)))) + return -EOPNOTSUPP; + + /* All other combinations of the supported triggers are allowed */ + if (rules & ~supported_triggers) + return -EOPNOTSUPP; + + return 0; +} + +static int xway_gphy_led_hw_control_get(struct phy_device *phydev, u8 index, + unsigned long *rules) +{ + int lval, hval; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + hval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index)); + if (hval < 0) + return hval; + + lval = phy_read_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index)); + if (lval < 0) + return lval; + + if (hval & XWAY_MMD_LEDxH_CON_LINK10) + *rules |= BIT(TRIGGER_NETDEV_LINK_10); + + if (hval & XWAY_MMD_LEDxH_CON_LINK100) + *rules |= BIT(TRIGGER_NETDEV_LINK_100); + + if (hval & XWAY_MMD_LEDxH_CON_LINK1000) + *rules |= BIT(TRIGGER_NETDEV_LINK_1000); + + if ((hval & XWAY_MMD_LEDxH_CON_LINK10) && + (hval & XWAY_MMD_LEDxH_CON_LINK100) && + (hval & XWAY_MMD_LEDxH_CON_LINK1000)) + *rules |= BIT(TRIGGER_NETDEV_LINK); + + if (lval & XWAY_MMD_LEDxL_PULSE_TXACT) + *rules |= BIT(TRIGGER_NETDEV_TX); + + if (lval & XWAY_MMD_LEDxL_PULSE_RXACT) + *rules |= BIT(TRIGGER_NETDEV_RX); + + return 0; +} + +static int xway_gphy_led_hw_control_set(struct phy_device *phydev, u8 index, + unsigned long rules) +{ + u16 hval = 0, lval = 0; + int ret; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_10)) + hval |= XWAY_MMD_LEDxH_CON_LINK10; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_100)) + hval |= XWAY_MMD_LEDxH_CON_LINK100; + + if (rules & BIT(TRIGGER_NETDEV_LINK) || + rules & BIT(TRIGGER_NETDEV_LINK_1000)) + hval |= XWAY_MMD_LEDxH_CON_LINK1000; + + if (rules & BIT(TRIGGER_NETDEV_TX)) + lval |= XWAY_MMD_LEDxL_PULSE_TXACT; + + if (rules & BIT(TRIGGER_NETDEV_RX)) + lval |= XWAY_MMD_LEDxL_PULSE_RXACT; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxH(index), hval); + if (ret) + return ret; + + ret = phy_write_mmd(phydev, MDIO_MMD_VEND2, XWAY_MMD_LEDxL(index), lval); + if (ret) + return ret; + + return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_EN(index)); +} + +static int xway_gphy_led_polarity_set(struct phy_device *phydev, int index, + unsigned long modes) +{ + bool force_active_low = false, force_active_high = false; + u32 mode; + + if (index >= XWAY_GPHY_MAX_LEDS) + return -EINVAL; + + for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { + switch (mode) { + case PHY_LED_ACTIVE_LOW: + force_active_low = true; + break; + case PHY_LED_ACTIVE_HIGH: + force_active_high = true; + break; + default: + return -EINVAL; + } + } + + if (force_active_low) + return phy_set_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index)); + + if (force_active_high) + return phy_clear_bits(phydev, XWAY_MDIO_LED, XWAY_GPHY_LED_INV(index)); + + return -EINVAL; +} + +static int xway_gphy_update_stats(struct phy_device *phydev) +{ + struct xway_gphy_priv *priv = phydev->priv; + int ret; + + /* XWAY_MDIO_ERRCNT: 8-bit read-clear counter, SEL set to RXERR */ + ret = phy_read(phydev, XWAY_MDIO_ERRCNT); + if (ret < 0) + return ret; + + priv->rx_errors += FIELD_GET(XWAY_MDIO_ERRCNT_COUNT, ret); + + return 0; +} + +static void xway_gphy_get_phy_stats(struct phy_device *phydev, + struct ethtool_eth_phy_stats *eth_stats, + struct ethtool_phy_stats *stats) +{ + struct xway_gphy_priv *priv = phydev->priv; + + eth_stats->SymbolErrorDuringCarrier = priv->rx_errors; + stats->rx_errors = priv->rx_errors; +} + +static struct phy_driver xway_gphy[] = { + { + .phy_id = PHY_ID_PHY11G_1_3, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.3", + /* PHY_GBIT_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY22F_1_3, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY22F (PEF 7061) v1.3", + /* PHY_BASIC_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY11G_1_4, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.4", + /* PHY_GBIT_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY22F_1_4, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY22F (PEF 7061) v1.4", + /* PHY_BASIC_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .config_aneg = xway_gphy14_config_aneg, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY11G_1_5, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY11G (PEF 7071/PEF 7072) v1.5 / v1.6", + /* PHY_GBIT_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY22F_1_5, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY22F (PEF 7061) v1.5 / v1.6", + /* PHY_BASIC_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY11G_VR9_1_1, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY11G (xRX v1.1 integrated)", + /* PHY_GBIT_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY22F_VR9_1_1, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY22F (xRX v1.1 integrated)", + /* PHY_BASIC_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY11G_VR9_1_2, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY11G (xRX v1.2 integrated)", + /* PHY_GBIT_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, { + .phy_id = PHY_ID_PHY22F_VR9_1_2, + .phy_id_mask = 0xffffffff, + .name = "Intel XWAY PHY22F (xRX v1.2 integrated)", + /* PHY_BASIC_FEATURES */ + .config_init = xway_gphy_config_init, + .probe = xway_gphy_probe, + .handle_interrupt = xway_gphy_handle_interrupt, + .config_intr = xway_gphy_config_intr, + .suspend = genphy_suspend, + .resume = genphy_resume, + .led_brightness_set = xway_gphy_led_brightness_set, + .led_hw_is_supported = xway_gphy_led_hw_is_supported, + .led_hw_control_get = xway_gphy_led_hw_control_get, + .led_hw_control_set = xway_gphy_led_hw_control_set, + .led_polarity_set = xway_gphy_led_polarity_set, + .update_stats = xway_gphy_update_stats, + .get_phy_stats = xway_gphy_get_phy_stats, + }, +}; +module_phy_driver(xway_gphy); + +static const struct mdio_device_id __maybe_unused xway_gphy_tbl[] = { + { PHY_ID_PHY11G_1_3, 0xffffffff }, + { PHY_ID_PHY22F_1_3, 0xffffffff }, + { PHY_ID_PHY11G_1_4, 0xffffffff }, + { PHY_ID_PHY22F_1_4, 0xffffffff }, + { PHY_ID_PHY11G_1_5, 0xffffffff }, + { PHY_ID_PHY22F_1_5, 0xffffffff }, + { PHY_ID_PHY11G_VR9_1_1, 0xffffffff }, + { PHY_ID_PHY22F_VR9_1_1, 0xffffffff }, + { PHY_ID_PHY11G_VR9_1_2, 0xffffffff }, + { PHY_ID_PHY22F_VR9_1_2, 0xffffffff }, + { } +}; +MODULE_DEVICE_TABLE(mdio, xway_gphy_tbl); + +MODULE_DESCRIPTION("Intel XWAY PHY driver"); +MODULE_LICENSE("GPL"); |
