|
|
@@ -0,0 +1,332 @@
|
|
|
+From 78997e9a5e4d8a4df561e083a92c91ae23010e07 Mon Sep 17 00:00:00 2001
|
|
|
+From: Daniel Golle <[email protected]>
|
|
|
+Date: Tue, 1 Oct 2024 01:17:18 +0100
|
|
|
+Subject: [PATCH] net: phy: mxl-gpy: add basic LED support
|
|
|
+
|
|
|
+Add basic support for LEDs connected to MaxLinear GPY2xx and GPY115 PHYs.
|
|
|
+The PHYs allow up to 4 LEDs to be connected.
|
|
|
+Implement controlling LEDs in software as well as netdev trigger offloading
|
|
|
+and LED polarity setup.
|
|
|
+
|
|
|
+The hardware claims to support 16 PWM brightness levels but there is no
|
|
|
+documentation on how to use that feature, hence this is not supported.
|
|
|
+
|
|
|
+Signed-off-by: Daniel Golle <[email protected]>
|
|
|
+Reviewed-by: Andrew Lunn <[email protected]>
|
|
|
+Link: https://patch.msgid.link/b6ec9050339f8244ff898898a1cecc33b13a48fc.1727741563.git.daniel@makrotopia.org
|
|
|
+Signed-off-by: Jakub Kicinski <[email protected]>
|
|
|
+---
|
|
|
+ drivers/net/phy/mxl-gpy.c | 218 ++++++++++++++++++++++++++++++++++++++
|
|
|
+ 1 file changed, 218 insertions(+)
|
|
|
+
|
|
|
+--- a/drivers/net/phy/mxl-gpy.c
|
|
|
++++ b/drivers/net/phy/mxl-gpy.c
|
|
|
+@@ -38,6 +38,7 @@
|
|
|
+ #define PHY_MIISTAT 0x18 /* MII state */
|
|
|
+ #define PHY_IMASK 0x19 /* interrupt mask */
|
|
|
+ #define PHY_ISTAT 0x1A /* interrupt status */
|
|
|
++#define PHY_LED 0x1B /* LEDs */
|
|
|
+ #define PHY_FWV 0x1E /* firmware version */
|
|
|
+
|
|
|
+ #define PHY_MIISTAT_SPD_MASK GENMASK(2, 0)
|
|
|
+@@ -61,6 +62,11 @@
|
|
|
+ PHY_IMASK_ADSC | \
|
|
|
+ PHY_IMASK_ANC)
|
|
|
+
|
|
|
++#define GPY_MAX_LEDS 4
|
|
|
++#define PHY_LED_POLARITY(idx) BIT(12 + (idx))
|
|
|
++#define PHY_LED_HWCONTROL(idx) BIT(8 + (idx))
|
|
|
++#define PHY_LED_ON(idx) BIT(idx)
|
|
|
++
|
|
|
+ #define PHY_FWV_REL_MASK BIT(15)
|
|
|
+ #define PHY_FWV_MAJOR_MASK GENMASK(11, 8)
|
|
|
+ #define PHY_FWV_MINOR_MASK GENMASK(7, 0)
|
|
|
+@@ -72,6 +78,23 @@
|
|
|
+ #define PHY_MDI_MDI_X_CD 0x1
|
|
|
+ #define PHY_MDI_MDI_X_CROSS 0x0
|
|
|
+
|
|
|
++/* LED */
|
|
|
++#define VSPEC1_LED(idx) (1 + (idx))
|
|
|
++#define VSPEC1_LED_BLINKS GENMASK(15, 12)
|
|
|
++#define VSPEC1_LED_PULSE GENMASK(11, 8)
|
|
|
++#define VSPEC1_LED_CON GENMASK(7, 4)
|
|
|
++#define VSPEC1_LED_BLINKF GENMASK(3, 0)
|
|
|
++
|
|
|
++#define VSPEC1_LED_LINK10 BIT(0)
|
|
|
++#define VSPEC1_LED_LINK100 BIT(1)
|
|
|
++#define VSPEC1_LED_LINK1000 BIT(2)
|
|
|
++#define VSPEC1_LED_LINK2500 BIT(3)
|
|
|
++
|
|
|
++#define VSPEC1_LED_TXACT BIT(0)
|
|
|
++#define VSPEC1_LED_RXACT BIT(1)
|
|
|
++#define VSPEC1_LED_COL BIT(2)
|
|
|
++#define VSPEC1_LED_NO_CON BIT(3)
|
|
|
++
|
|
|
+ /* SGMII */
|
|
|
+ #define VSPEC1_SGMII_CTRL 0x08
|
|
|
+ #define VSPEC1_SGMII_CTRL_ANEN BIT(12) /* Aneg enable */
|
|
|
+@@ -827,6 +850,156 @@ static int gpy115_loopback(struct phy_de
|
|
|
+ return genphy_soft_reset(phydev);
|
|
|
+ }
|
|
|
+
|
|
|
++static int gpy_led_brightness_set(struct phy_device *phydev,
|
|
|
++ u8 index, enum led_brightness value)
|
|
|
++{
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ if (index >= GPY_MAX_LEDS)
|
|
|
++ return -EINVAL;
|
|
|
++
|
|
|
++ /* clear HWCONTROL and set manual LED state */
|
|
|
++ ret = phy_modify(phydev, PHY_LED,
|
|
|
++ ((value == LED_OFF) ? PHY_LED_HWCONTROL(index) : 0) |
|
|
|
++ PHY_LED_ON(index),
|
|
|
++ (value == LED_OFF) ? 0 : PHY_LED_ON(index));
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++
|
|
|
++ /* ToDo: set PWM brightness */
|
|
|
++
|
|
|
++ /* clear HW LED setup */
|
|
|
++ if (value == LED_OFF)
|
|
|
++ return phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), 0);
|
|
|
++ else
|
|
|
++ return 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_RX) |
|
|
|
++ BIT(TRIGGER_NETDEV_TX));
|
|
|
++
|
|
|
++static int gpy_led_hw_is_supported(struct phy_device *phydev, u8 index,
|
|
|
++ unsigned long rules)
|
|
|
++{
|
|
|
++ if (index >= GPY_MAX_LEDS)
|
|
|
++ return -EINVAL;
|
|
|
++
|
|
|
++ /* All combinations of the supported triggers are allowed */
|
|
|
++ if (rules & ~supported_triggers)
|
|
|
++ return -EOPNOTSUPP;
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int gpy_led_hw_control_get(struct phy_device *phydev, u8 index,
|
|
|
++ unsigned long *rules)
|
|
|
++{
|
|
|
++ int val;
|
|
|
++
|
|
|
++ if (index >= GPY_MAX_LEDS)
|
|
|
++ return -EINVAL;
|
|
|
++
|
|
|
++ val = phy_read_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index));
|
|
|
++ if (val < 0)
|
|
|
++ return val;
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK10)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_LINK_10);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK100)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_LINK_100);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK1000)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_LINK_1000);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_CON, val) & VSPEC1_LED_LINK2500)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_LINK_2500);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_CON, val) == (VSPEC1_LED_LINK10 |
|
|
|
++ VSPEC1_LED_LINK100 |
|
|
|
++ VSPEC1_LED_LINK1000 |
|
|
|
++ VSPEC1_LED_LINK2500))
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_LINK);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_TXACT)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_TX);
|
|
|
++
|
|
|
++ if (FIELD_GET(VSPEC1_LED_PULSE, val) & VSPEC1_LED_RXACT)
|
|
|
++ *rules |= BIT(TRIGGER_NETDEV_RX);
|
|
|
++
|
|
|
++ return 0;
|
|
|
++}
|
|
|
++
|
|
|
++static int gpy_led_hw_control_set(struct phy_device *phydev, u8 index,
|
|
|
++ unsigned long rules)
|
|
|
++{
|
|
|
++ u16 val = 0;
|
|
|
++ int ret;
|
|
|
++
|
|
|
++ if (index >= GPY_MAX_LEDS)
|
|
|
++ return -EINVAL;
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
|
|
++ rules & BIT(TRIGGER_NETDEV_LINK_10))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK10);
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
|
|
++ rules & BIT(TRIGGER_NETDEV_LINK_100))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK100);
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
|
|
++ rules & BIT(TRIGGER_NETDEV_LINK_1000))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK1000);
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_LINK) ||
|
|
|
++ rules & BIT(TRIGGER_NETDEV_LINK_2500))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_CON, VSPEC1_LED_LINK2500);
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_TX))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_TXACT);
|
|
|
++
|
|
|
++ if (rules & BIT(TRIGGER_NETDEV_RX))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_RXACT);
|
|
|
++
|
|
|
++ /* allow RX/TX pulse without link indication */
|
|
|
++ if ((rules & BIT(TRIGGER_NETDEV_TX) || rules & BIT(TRIGGER_NETDEV_RX)) &&
|
|
|
++ !(val & VSPEC1_LED_CON))
|
|
|
++ val |= FIELD_PREP(VSPEC1_LED_PULSE, VSPEC1_LED_NO_CON) | VSPEC1_LED_CON;
|
|
|
++
|
|
|
++ ret = phy_write_mmd(phydev, MDIO_MMD_VEND1, VSPEC1_LED(index), val);
|
|
|
++ if (ret)
|
|
|
++ return ret;
|
|
|
++
|
|
|
++ return phy_set_bits(phydev, PHY_LED, PHY_LED_HWCONTROL(index));
|
|
|
++}
|
|
|
++
|
|
|
++static int gpy_led_polarity_set(struct phy_device *phydev, int index,
|
|
|
++ unsigned long modes)
|
|
|
++{
|
|
|
++ bool active_low = false;
|
|
|
++ u32 mode;
|
|
|
++
|
|
|
++ if (index >= GPY_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;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ return phy_modify(phydev, PHY_LED, PHY_LED_POLARITY(index),
|
|
|
++ active_low ? 0 : PHY_LED_POLARITY(index));
|
|
|
++}
|
|
|
++
|
|
|
+ static struct phy_driver gpy_drivers[] = {
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY2xx),
|
|
|
+@@ -844,6 +1017,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .phy_id = PHY_ID_GPY115B,
|
|
|
+@@ -862,6 +1040,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy115_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY115C),
|
|
|
+@@ -879,6 +1062,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy115_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .phy_id = PHY_ID_GPY211B,
|
|
|
+@@ -897,6 +1085,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY211C),
|
|
|
+@@ -914,6 +1107,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .phy_id = PHY_ID_GPY212B,
|
|
|
+@@ -932,6 +1130,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY212C),
|
|
|
+@@ -949,6 +1152,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .phy_id = PHY_ID_GPY215B,
|
|
|
+@@ -967,6 +1175,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY215C),
|
|
|
+@@ -984,6 +1197,11 @@ static struct phy_driver gpy_drivers[] =
|
|
|
+ .set_wol = gpy_set_wol,
|
|
|
+ .get_wol = gpy_get_wol,
|
|
|
+ .set_loopback = gpy_loopback,
|
|
|
++ .led_brightness_set = gpy_led_brightness_set,
|
|
|
++ .led_hw_is_supported = gpy_led_hw_is_supported,
|
|
|
++ .led_hw_control_get = gpy_led_hw_control_get,
|
|
|
++ .led_hw_control_set = gpy_led_hw_control_set,
|
|
|
++ .led_polarity_set = gpy_led_polarity_set,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ PHY_ID_MATCH_MODEL(PHY_ID_GPY241B),
|