[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20251106200309.1096131-1-prabhakar.mahadev-lad.rj@bp.renesas.com>
Date: Thu, 6 Nov 2025 20:03:09 +0000
From: Prabhakar <prabhakar.csengg@...il.com>
To: Andrew Lunn <andrew@...n.ch>,
Heiner Kallweit <hkallweit1@...il.com>,
Russell King <linux@...linux.org.uk>,
"David S. Miller" <davem@...emloft.net>,
Eric Dumazet <edumazet@...gle.com>,
Jakub Kicinski <kuba@...nel.org>,
Paolo Abeni <pabeni@...hat.com>,
Horatiu Vultur <horatiu.vultur@...rochip.com>,
Geert Uytterhoeven <geert+renesas@...der.be>,
Vladimir Oltean <vladimir.oltean@....com>,
Vadim Fedorenko <vadim.fedorenko@...ux.dev>,
Maxime Chevallier <maxime.chevallier@...tlin.com>
Cc: netdev@...r.kernel.org,
linux-kernel@...r.kernel.org,
linux-renesas-soc@...r.kernel.org,
Prabhakar <prabhakar.csengg@...il.com>,
Biju Das <biju.das.jz@...renesas.com>,
Fabrizio Castro <fabrizio.castro.jz@...esas.com>,
Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
Subject: [PATCH net-next] net: phy: mscc: Add support for PHY LEDs on VSC8541
From: Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
Add a minimal LED controller implementation supporting common use cases
with the 'netdev' trigger.
The driver now defaults to VSC8531_LINK_ACTIVITY at initialization and
allows users to configure LED behavior through the LED subsystem. Support
for controlling LED behavior is also added.
The LED Behavior (register 30) bits [0:1] control the combine feature:
0: Combine enabled (link/activity, duplex/collision)
1: Combine disabled (link only, duplex only)
This feature is now managed based on the RX/TX rules. If both RX and TX
are disabled, the combine feature is turned off; otherwise, it remains
enabled.
Signed-off-by: Lad Prabhakar <prabhakar.mahadev-lad.rj@...renesas.com>
---
drivers/net/phy/mscc/mscc.h | 4 +
drivers/net/phy/mscc/mscc_main.c | 223 ++++++++++++++++++++++++++++++-
2 files changed, 222 insertions(+), 5 deletions(-)
diff --git a/drivers/net/phy/mscc/mscc.h b/drivers/net/phy/mscc/mscc.h
index 2eef5956b9cc..65c9d7bd9315 100644
--- a/drivers/net/phy/mscc/mscc.h
+++ b/drivers/net/phy/mscc/mscc.h
@@ -85,6 +85,10 @@ enum rgmii_clock_delay {
#define LED_MODE_SEL_MASK(x) (GENMASK(3, 0) << LED_MODE_SEL_POS(x))
#define LED_MODE_SEL(x, mode) (((mode) << LED_MODE_SEL_POS(x)) & LED_MODE_SEL_MASK(x))
+#define MSCC_PHY_LED_BEHAVIOR 30
+#define LED_COMBINE_DIS_MASK(x) BIT(x)
+#define LED_COMBINE_DIS(x, dis) (((dis) ? 1 : 0) << (x))
+
#define MSCC_EXT_PAGE_CSR_CNTL_17 17
#define MSCC_EXT_PAGE_CSR_CNTL_18 18
diff --git a/drivers/net/phy/mscc/mscc_main.c b/drivers/net/phy/mscc/mscc_main.c
index 8678ebf89cca..0c4e368527b5 100644
--- a/drivers/net/phy/mscc/mscc_main.c
+++ b/drivers/net/phy/mscc/mscc_main.c
@@ -173,23 +173,43 @@ static void vsc85xx_get_stats(struct phy_device *phydev,
data[i] = vsc85xx_get_stat(phydev, i);
}
-static int vsc85xx_led_cntl_set(struct phy_device *phydev,
- u8 led_num,
- u8 mode)
+static int vsc85xx_led_cntl_set_lock_unlock(struct phy_device *phydev,
+ u8 led_num,
+ u8 mode, bool lock)
{
int rc;
u16 reg_val;
- mutex_lock(&phydev->lock);
+ if (lock)
+ mutex_lock(&phydev->lock);
reg_val = phy_read(phydev, MSCC_PHY_LED_MODE_SEL);
reg_val &= ~LED_MODE_SEL_MASK(led_num);
reg_val |= LED_MODE_SEL(led_num, (u16)mode);
rc = phy_write(phydev, MSCC_PHY_LED_MODE_SEL, reg_val);
- mutex_unlock(&phydev->lock);
+ if (lock)
+ mutex_unlock(&phydev->lock);
return rc;
}
+static int vsc85xx_led_cntl_set(struct phy_device *phydev, u8 led_num,
+ u8 mode)
+{
+ return vsc85xx_led_cntl_set_lock_unlock(phydev, led_num, mode, true);
+}
+
+static int vsc8541_led_combine_disable_set(struct phy_device *phydev, u8 led_num,
+ bool combine_disable)
+{
+ u16 reg_val;
+
+ reg_val = phy_read(phydev, MSCC_PHY_LED_BEHAVIOR);
+ reg_val &= ~LED_COMBINE_DIS_MASK(led_num);
+ reg_val |= LED_COMBINE_DIS(led_num, combine_disable);
+
+ return phy_write(phydev, MSCC_PHY_LED_BEHAVIOR, reg_val);
+}
+
static int vsc85xx_mdix_get(struct phy_device *phydev, u8 *mdix)
{
u16 reg_val;
@@ -2218,6 +2238,174 @@ static int vsc85xx_config_inband(struct phy_device *phydev, unsigned int modes)
reg_val);
}
+static int vsc8541_led_brightness_set(struct phy_device *phydev,
+ u8 index, enum led_brightness value)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+
+ if (index >= vsc8531->nleds)
+ return -EINVAL;
+
+ return vsc85xx_led_cntl_set_lock_unlock(phydev, index, value == LED_OFF ?
+ VSC8531_FORCE_LED_OFF : VSC8531_FORCE_LED_ON, false);
+}
+
+static int vsc8541_led_hw_is_supported(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ static const unsigned long supported = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+
+ if (index >= vsc8531->nleds)
+ return -EINVAL;
+
+ if (rules & ~supported)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
+
+static int vsc8541_led_hw_control_get(struct phy_device *phydev, u8 index,
+ unsigned long *rules)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ u16 reg;
+
+ if (index >= vsc8531->nleds)
+ return -EINVAL;
+
+ reg = phy_read(phydev, MSCC_PHY_LED_MODE_SEL) & LED_MODE_SEL_MASK(index);
+ reg >>= LED_MODE_SEL_POS(index);
+ switch (reg) {
+ case VSC8531_LINK_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_1000_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_100_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_10_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_100_1000_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_10_1000_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_1000) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_LINK_10_100_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_LINK_10) |
+ BIT(TRIGGER_NETDEV_LINK_100) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ case VSC8531_ACTIVITY:
+ *rules = BIT(TRIGGER_NETDEV_LINK) |
+ BIT(TRIGGER_NETDEV_RX) |
+ BIT(TRIGGER_NETDEV_TX);
+ break;
+
+ default:
+ *rules = 0;
+ break;
+ }
+
+ return 0;
+}
+
+static int vsc8541_led_hw_control_set(struct phy_device *phydev, u8 index,
+ unsigned long rules)
+{
+ struct vsc8531_private *vsc8531 = phydev->priv;
+ bool combine_disable = false;
+ u16 mode = VSC8531_LINK_ACTIVITY;
+ bool has_rx, has_tx;
+ int ret;
+
+ if (index >= vsc8531->nleds)
+ return -EINVAL;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK))
+ mode = VSC8531_LINK_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_10))
+ mode = VSC8531_LINK_10_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_100))
+ mode = VSC8531_LINK_100_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_1000))
+ mode = VSC8531_LINK_1000_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_100) &&
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
+ mode = VSC8531_LINK_100_1000_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_10) &&
+ rules & BIT(TRIGGER_NETDEV_LINK_1000))
+ mode = VSC8531_LINK_10_1000_ACTIVITY;
+
+ if (rules & BIT(TRIGGER_NETDEV_LINK_10) &&
+ rules & BIT(TRIGGER_NETDEV_LINK_100))
+ mode = VSC8531_LINK_10_100_ACTIVITY;
+
+ /*
+ * The VSC8541 PHY provides an option to control LED behavior. By
+ * default, the LEDx combine function is enabled, meaning the LED
+ * will be on when there is link/activity or duplex/collision. If
+ * the combine function is disabled, the LED will be on only for
+ * link or duplex.
+ *
+ * To control this behavior, we check the selected rules. If both
+ * RX and TX activity are not selected, the LED combine function
+ * is disabled; otherwise, it remains enabled.
+ */
+ has_rx = !!(rules & BIT(TRIGGER_NETDEV_RX));
+ has_tx = !!(rules & BIT(TRIGGER_NETDEV_TX));
+ if (!has_rx && !has_tx)
+ combine_disable = true;
+
+ ret = vsc8541_led_combine_disable_set(phydev, index, combine_disable);
+ if (ret < 0)
+ return ret;
+
+ return vsc85xx_led_cntl_set_lock_unlock(phydev, index, mode, false);
+}
+
static int vsc8514_probe(struct phy_device *phydev)
{
struct vsc8531_private *vsc8531;
@@ -2322,6 +2510,7 @@ static int vsc85xx_probe(struct phy_device *phydev)
int rate_magic;
u32 default_mode[2] = {VSC8531_LINK_1000_ACTIVITY,
VSC8531_LINK_100_ACTIVITY};
+ int phy_id;
rate_magic = vsc85xx_edge_rate_magic_get(phydev);
if (rate_magic < 0)
@@ -2343,6 +2532,26 @@ static int vsc85xx_probe(struct phy_device *phydev)
if (!vsc8531->stats)
return -ENOMEM;
+ phy_id = phydev->drv->phy_id & phydev->drv->phy_id_mask;
+ if (phy_id == PHY_ID_VSC8541) {
+ struct device_node *np;
+
+ /*
+ * Check for LED configuration in device tree if available
+ * or fall back to default `vsc8531,led-x-mode` DT properties.
+ */
+ np = of_get_child_by_name(phydev->mdio.dev.of_node, "leds");
+ if (np) {
+ of_node_put(np);
+
+ /* default to link activity */
+ for (unsigned int i = 0; i < vsc8531->nleds; i++)
+ vsc8531->leds_mode[i] = VSC8531_LINK_ACTIVITY;
+
+ return 0;
+ }
+ }
+
return vsc85xx_dt_led_modes_get(phydev, default_mode);
}
@@ -2548,6 +2757,10 @@ static struct phy_driver vsc85xx_driver[] = {
.get_sset_count = &vsc85xx_get_sset_count,
.get_strings = &vsc85xx_get_strings,
.get_stats = &vsc85xx_get_stats,
+ .led_brightness_set = vsc8541_led_brightness_set,
+ .led_hw_is_supported = vsc8541_led_hw_is_supported,
+ .led_hw_control_get = vsc8541_led_hw_control_get,
+ .led_hw_control_set = vsc8541_led_hw_control_set,
},
{
.phy_id = PHY_ID_VSC8552,
--
2.43.0
Powered by blists - more mailing lists