| // SPDX-License-Identifier: GPL-2.0 |
| /* LED driver for Aquantia PHY |
| * |
| * Author: Daniel Golle <daniel@makrotopia.org> |
| */ |
| |
| #include <linux/phy.h> |
| |
| #include "aquantia.h" |
| |
| int aqr_phy_led_brightness_set(struct phy_device *phydev, |
| u8 index, enum led_brightness value) |
| { |
| if (index >= AQR_MAX_LEDS) |
| return -EINVAL; |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index), |
| VEND1_GLOBAL_LED_PROV_LINK_MASK | |
| VEND1_GLOBAL_LED_PROV_FORCE_ON | |
| VEND1_GLOBAL_LED_PROV_RX_ACT | |
| VEND1_GLOBAL_LED_PROV_TX_ACT, |
| value ? VEND1_GLOBAL_LED_PROV_FORCE_ON : 0); |
| } |
| |
| static const unsigned long supported_triggers = (BIT(TRIGGER_NETDEV_LINK) | |
| BIT(TRIGGER_NETDEV_LINK_100) | |
| BIT(TRIGGER_NETDEV_LINK_1000) | |
| BIT(TRIGGER_NETDEV_LINK_2500) | |
| BIT(TRIGGER_NETDEV_LINK_5000) | |
| BIT(TRIGGER_NETDEV_LINK_10000) | |
| BIT(TRIGGER_NETDEV_RX) | |
| BIT(TRIGGER_NETDEV_TX)); |
| |
| int aqr_phy_led_hw_is_supported(struct phy_device *phydev, u8 index, |
| unsigned long rules) |
| { |
| if (index >= AQR_MAX_LEDS) |
| return -EINVAL; |
| |
| /* All combinations of the supported triggers are allowed */ |
| if (rules & ~supported_triggers) |
| return -EOPNOTSUPP; |
| |
| return 0; |
| } |
| |
| int aqr_phy_led_hw_control_get(struct phy_device *phydev, u8 index, |
| unsigned long *rules) |
| { |
| int val; |
| |
| if (index >= AQR_MAX_LEDS) |
| return -EINVAL; |
| |
| val = phy_read_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index)); |
| if (val < 0) |
| return val; |
| |
| *rules = 0; |
| if (val & VEND1_GLOBAL_LED_PROV_LINK100) |
| *rules |= BIT(TRIGGER_NETDEV_LINK_100); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_LINK1000) |
| *rules |= BIT(TRIGGER_NETDEV_LINK_1000); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_LINK2500) |
| *rules |= BIT(TRIGGER_NETDEV_LINK_2500); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_LINK5000) |
| *rules |= BIT(TRIGGER_NETDEV_LINK_5000); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_LINK10000) |
| *rules |= BIT(TRIGGER_NETDEV_LINK_10000); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_RX_ACT) |
| *rules |= BIT(TRIGGER_NETDEV_RX); |
| |
| if (val & VEND1_GLOBAL_LED_PROV_TX_ACT) |
| *rules |= BIT(TRIGGER_NETDEV_TX); |
| |
| return 0; |
| } |
| |
| int aqr_phy_led_hw_control_set(struct phy_device *phydev, u8 index, |
| unsigned long rules) |
| { |
| u16 val = 0; |
| |
| if (index >= AQR_MAX_LEDS) |
| return -EINVAL; |
| |
| if (rules & (BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK))) |
| val |= VEND1_GLOBAL_LED_PROV_LINK100; |
| |
| if (rules & (BIT(TRIGGER_NETDEV_LINK_1000) | BIT(TRIGGER_NETDEV_LINK))) |
| val |= VEND1_GLOBAL_LED_PROV_LINK1000; |
| |
| if (rules & (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK))) |
| val |= VEND1_GLOBAL_LED_PROV_LINK2500; |
| |
| if (rules & (BIT(TRIGGER_NETDEV_LINK_5000) | BIT(TRIGGER_NETDEV_LINK))) |
| val |= VEND1_GLOBAL_LED_PROV_LINK5000; |
| |
| if (rules & (BIT(TRIGGER_NETDEV_LINK_10000) | BIT(TRIGGER_NETDEV_LINK))) |
| val |= VEND1_GLOBAL_LED_PROV_LINK10000; |
| |
| if (rules & BIT(TRIGGER_NETDEV_RX)) |
| val |= VEND1_GLOBAL_LED_PROV_RX_ACT; |
| |
| if (rules & BIT(TRIGGER_NETDEV_TX)) |
| val |= VEND1_GLOBAL_LED_PROV_TX_ACT; |
| |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_PROV(index), |
| VEND1_GLOBAL_LED_PROV_LINK_MASK | |
| VEND1_GLOBAL_LED_PROV_FORCE_ON | |
| VEND1_GLOBAL_LED_PROV_RX_ACT | |
| VEND1_GLOBAL_LED_PROV_TX_ACT, val); |
| } |
| |
| int aqr_phy_led_active_low_set(struct phy_device *phydev, int index, bool enable) |
| { |
| return phy_modify_mmd(phydev, MDIO_MMD_VEND1, AQR_LED_DRIVE(index), |
| VEND1_GLOBAL_LED_DRIVE_VDD, |
| enable ? VEND1_GLOBAL_LED_DRIVE_VDD : 0); |
| } |
| |
| int aqr_phy_led_polarity_set(struct phy_device *phydev, int index, unsigned long modes) |
| { |
| struct aqr107_priv *priv = phydev->priv; |
| bool active_low = false; |
| u32 mode; |
| |
| if (index >= AQR_MAX_LEDS) |
| return -EINVAL; |
| |
| for_each_set_bit(mode, &modes, __PHY_LED_MODES_NUM) { |
| switch (mode) { |
| case PHY_LED_ACTIVE_LOW: |
| active_low = true; |
| break; |
| default: |
| return -EINVAL; |
| } |
| } |
| |
| /* Save LED driver vdd state to restore on SW reset */ |
| if (active_low) |
| priv->leds_active_low |= BIT(index); |
| |
| return aqr_phy_led_active_low_set(phydev, index, active_low); |
| } |