From b5ccc175297291d19697cd775c6f2a5691828e68 Mon Sep 17 00:00:00 2001
From: Mattias Walström <lazzer@gmail.com>
Date: Wed, 14 Jan 2026 18:22:41 +0100
Subject: [PATCH 28/33] net: dsa: mv88e6xxx: Add LED support for 6393X
Organization: Wires

Original commit:
commit 462277b926140ee2d231317e92afb6cabf640268
Author: Tobias Waldekranz <tobias@waldekranz.com>
Date:   Thu Nov 16 21:59:35 2023 +0100

    net: dsa: mv88e6xxx: Add LED support for 6393X

    Trigger support:
    - "none"
    - "timer"
    - "netdev"
---
 drivers/net/dsa/mv88e6xxx/chip.c |   1 +
 drivers/net/dsa/mv88e6xxx/leds.c | 403 +++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/port.c |  33 +++
 drivers/net/dsa/mv88e6xxx/port.h |  13 +
 4 files changed, 450 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index d379799c0a16..7bc3169839d3 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5658,6 +5658,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.port_disable_pri_override = mv88e6xxx_port_disable_pri_override,
 	.port_get_cmode = mv88e6352_port_get_cmode,
 	.port_set_cmode = mv88e6393x_port_set_cmode,
+	.port_setup_leds = mv88e6393x_port_setup_leds,
 	.port_setup_message_port = mv88e6xxx_setup_message_port,
 	.port_set_upstream_port = mv88e6393x_port_set_upstream_port,
 	.stats_snapshot = mv88e6390_g1_stats_snapshot,
diff --git a/drivers/net/dsa/mv88e6xxx/leds.c b/drivers/net/dsa/mv88e6xxx/leds.c
index ab3bc645da56..aa539df21f8d 100644
--- a/drivers/net/dsa/mv88e6xxx/leds.c
+++ b/drivers/net/dsa/mv88e6xxx/leds.c
@@ -102,6 +102,58 @@ struct mv88e6xxx_led_hwconfig {
 	u16 selector;
 };
 
+/* 6393X LED mode flags */
+#define MV88E6393X_FLAG_ACT      (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX))
+#define MV88E6393X_FLAG_LINK     BIT(TRIGGER_NETDEV_LINK)
+#define MV88E6393X_FLAG_LINK_10  BIT(TRIGGER_NETDEV_LINK_10)
+#define MV88E6393X_FLAG_LINK_100 BIT(TRIGGER_NETDEV_LINK_100)
+#define MV88E6393X_FLAG_LINK_1G  BIT(TRIGGER_NETDEV_LINK_1000)
+#define MV88E6393X_FLAG_FULL     BIT(TRIGGER_NETDEV_FULL_DUPLEX)
+
+/* 6393X LED modes */
+#define MV88E6393X_LED_MODE_BLINK 0xd
+#define MV88E6393X_LED_MODE_OFF   0xe
+#define MV88E6393X_LED_MODE_ON    0xf
+#define MV88E6393X_LED_MODES      0x10
+
+/* 6393X LED mode to flags mapping for ports 1-8 */
+static const unsigned long mv88e6393x_led_map_p1_p8[2][MV88E6393X_LED_MODES] = {
+	[0] = {
+		[0x1] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_100 | MV88E6393X_FLAG_LINK_1G,
+		[0x2] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_1G,
+		[0x3] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK,
+		[0x6] = MV88E6393X_FLAG_FULL,
+		[0x7] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_10 | MV88E6393X_FLAG_LINK_1G,
+		[0x8] = MV88E6393X_FLAG_LINK,
+		[0x9] = MV88E6393X_FLAG_LINK_10,
+		[0xa] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_10,
+		[0xb] = MV88E6393X_FLAG_LINK_100 | MV88E6393X_FLAG_LINK_1G,
+	},
+	[1] = {
+		[0x1] = MV88E6393X_FLAG_ACT,
+		[0x2] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_10 | MV88E6393X_FLAG_LINK_100,
+		[0x3] = MV88E6393X_FLAG_LINK_1G,
+		[0x5] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK,
+		[0x6] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_10 | MV88E6393X_FLAG_LINK_1G,
+		[0x7] = MV88E6393X_FLAG_LINK_10 | MV88E6393X_FLAG_LINK_1G,
+		[0x9] = MV88E6393X_FLAG_LINK_100,
+		[0xa] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK_100,
+		[0xb] = MV88E6393X_FLAG_LINK_10 | MV88E6393X_FLAG_LINK_100,
+	}
+};
+
+/* 6393X LED mode to flags mapping for ports 9-10 */
+static const unsigned long mv88e6393x_led_map_p9_p10[2][MV88E6393X_LED_MODES] = {
+	[0] = {
+		[0x1] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK,
+	},
+	[1] = {
+		[0x6] = MV88E6393X_FLAG_FULL,
+		[0x7] = MV88E6393X_FLAG_ACT | MV88E6393X_FLAG_LINK,
+		[0x8] = MV88E6393X_FLAG_LINK,
+	}
+};
+
 /* The following is a lookup table to check what rules we can support on a
  * certain LED given restrictions such as that some rules only work with fiber
  * (SFP) connections and some blink on activity by default.
@@ -846,3 +898,354 @@ int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port)
 	fwnode_handle_put(leds);
 	return ret;
 }
+
+/* 6393X LED support */
+
+static const unsigned long *mv88e6393x_led_map(int port, int led)
+{
+	if (port >= 1 && port <= 8)
+		return mv88e6393x_led_map_p1_p8[led];
+	if (port >= 9 && port <= 10)
+		return mv88e6393x_led_map_p9_p10[led];
+	return NULL;
+}
+
+static int mv88e6393x_led_flags_to_mode(int port, int led, unsigned long flags)
+{
+	const unsigned long *map = mv88e6393x_led_map(port, led);
+	int i;
+
+	if (!map)
+		return -ENODEV;
+
+	if (!flags)
+		return MV88E6393X_LED_MODE_OFF;
+
+	for (i = 0; i < MV88E6393X_LED_MODES; i++) {
+		if (map[i] == flags)
+			return i;
+	}
+
+	return -EOPNOTSUPP;
+}
+
+static int mv88e6393x_led_mode_to_flags(int port, int led, u8 mode,
+					unsigned long *flags)
+{
+	const unsigned long *map = mv88e6393x_led_map(port, led);
+
+	if (!map)
+		return -ENODEV;
+
+	if (mode == MV88E6393X_LED_MODE_OFF) {
+		*flags = 0;
+		return 0;
+	}
+
+	if (mode < MV88E6393X_LED_MODES && map[mode]) {
+		*flags = map[mode];
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int mv88e6393x_led_set_mode(struct mv88e6xxx_port *p, int led, int mode)
+{
+	u16 ctrl;
+	int err;
+
+	if (mode < 0)
+		return mode;
+
+	err = mv88e6393x_port_led_read(p->chip, p->port, 0, &ctrl);
+	if (err)
+		return err;
+
+	if (led == 0) {
+		ctrl &= ~0x0f;
+		ctrl |= mode;
+	} else {
+		ctrl &= ~0xf0;
+		ctrl |= mode << 4;
+	}
+
+	return mv88e6393x_port_led_write(p->chip, p->port, 0, ctrl);
+}
+
+static int mv88e6393x_led_get_mode(struct mv88e6xxx_port *p, int led)
+{
+	u16 ctrl;
+	int err;
+
+	err = mv88e6393x_port_led_read(p->chip, p->port, 0, &ctrl);
+	if (err)
+		return err;
+
+	if (led == 0)
+		return ctrl & 0xf;
+	else
+		return (ctrl >> 4) & 0xf;
+}
+
+static int mv88e6393x_led_brightness_set(struct mv88e6xxx_port *p, int led,
+					 int brightness)
+{
+	if (brightness == LED_OFF)
+		return mv88e6393x_led_set_mode(p, led, MV88E6393X_LED_MODE_OFF);
+
+	return mv88e6393x_led_set_mode(p, led, MV88E6393X_LED_MODE_ON);
+}
+
+static int mv88e6393x_led0_brightness_set_blocking(struct led_classdev *ldev,
+						   enum led_brightness brightness)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
+	int err;
+
+	mv88e6xxx_reg_lock(p->chip);
+	err = mv88e6393x_led_brightness_set(p, 0, brightness);
+	mv88e6xxx_reg_unlock(p->chip);
+
+	return err;
+}
+
+static int mv88e6393x_led1_brightness_set_blocking(struct led_classdev *ldev,
+						   enum led_brightness brightness)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
+	int err;
+
+	mv88e6xxx_reg_lock(p->chip);
+	err = mv88e6393x_led_brightness_set(p, 1, brightness);
+	mv88e6xxx_reg_unlock(p->chip);
+
+	return err;
+}
+
+static int mv88e6393x_led_blink_set(struct mv88e6xxx_port *p, int led,
+				    unsigned long *delay_on,
+				    unsigned long *delay_off)
+{
+	int err;
+
+	/* Defer anything other than 50% duty cycles to software */
+	if (*delay_on != *delay_off)
+		return -EINVAL;
+
+	/* Reject values outside ~20% of our default rate (84ms) */
+	if (*delay_on && ((*delay_on < 30) || (*delay_on > 50)))
+		return -EINVAL;
+
+	mv88e6xxx_reg_lock(p->chip);
+	err = mv88e6393x_led_set_mode(p, led, MV88E6393X_LED_MODE_BLINK);
+	mv88e6xxx_reg_unlock(p->chip);
+	if (!err)
+		*delay_on = *delay_off = 42;
+
+	return err;
+}
+
+static int mv88e6393x_led0_blink_set(struct led_classdev *ldev,
+				     unsigned long *delay_on,
+				     unsigned long *delay_off)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
+
+	return mv88e6393x_led_blink_set(p, 0, delay_on, delay_off);
+}
+
+static int mv88e6393x_led1_blink_set(struct led_classdev *ldev,
+				     unsigned long *delay_on,
+				     unsigned long *delay_off)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
+
+	return mv88e6393x_led_blink_set(p, 1, delay_on, delay_off);
+}
+
+static int
+mv88e6393x_led0_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
+	int mode = mv88e6393x_led_flags_to_mode(p->port, 0, rules);
+
+	return (mode < 0) ? mode : 0;
+}
+
+static int
+mv88e6393x_led1_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
+	int mode = mv88e6393x_led_flags_to_mode(p->port, 1, rules);
+
+	return (mode < 0) ? mode : 0;
+}
+
+static int mv88e6393x_led_hw_control_set(struct mv88e6xxx_port *p,
+					 int led, unsigned long rules)
+{
+	int mode = mv88e6393x_led_flags_to_mode(p->port, led, rules);
+
+	if (mode < 0)
+		return mode;
+
+	return mv88e6393x_led_set_mode(p, led, mode);
+}
+
+static int
+mv88e6393x_led0_hw_control_set(struct led_classdev *ldev, unsigned long rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
+	int err;
+
+	mv88e6xxx_reg_lock(p->chip);
+	err = mv88e6393x_led_hw_control_set(p, 0, rules);
+	mv88e6xxx_reg_unlock(p->chip);
+
+	return err;
+}
+
+static int
+mv88e6393x_led1_hw_control_set(struct led_classdev *ldev, unsigned long rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
+	int err;
+
+	mv88e6xxx_reg_lock(p->chip);
+	err = mv88e6393x_led_hw_control_set(p, 1, rules);
+	mv88e6xxx_reg_unlock(p->chip);
+
+	return err;
+}
+
+static int
+mv88e6393x_led_hw_control_get(struct mv88e6xxx_port *p, int led, unsigned long *rules)
+{
+	int mode;
+
+	mv88e6xxx_reg_lock(p->chip);
+	mode = mv88e6393x_led_get_mode(p, led);
+	mv88e6xxx_reg_unlock(p->chip);
+	if (mode < 0)
+		return mode;
+
+	return mv88e6393x_led_mode_to_flags(p->port, led, mode, rules);
+}
+
+static int
+mv88e6393x_led0_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0);
+
+	return mv88e6393x_led_hw_control_get(p, 0, rules);
+}
+
+static int
+mv88e6393x_led1_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
+{
+	struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1);
+
+	return mv88e6393x_led_hw_control_get(p, 1, rules);
+}
+
+int mv88e6393x_port_setup_leds(struct mv88e6xxx_chip *chip, int port)
+{
+	struct fwnode_handle *led = NULL, *leds = NULL;
+	struct led_init_data init_data = { };
+	enum led_default_state state;
+	struct mv88e6xxx_port *p;
+	struct led_classdev *l;
+	struct device *dev;
+	u32 led_num;
+	int ret;
+
+	/* 6393X has LEDs on ports 1-10 */
+	if (port < 1 || port > 10)
+		return -EOPNOTSUPP;
+
+	p = &chip->ports[port];
+	if (!p->fwnode)
+		return 0;
+
+	dev = chip->dev;
+
+	leds = fwnode_get_named_child_node(p->fwnode, "leds");
+	if (!leds) {
+		dev_dbg(dev, "No Leds node specified in device tree for port %d!\n",
+			port);
+		return 0;
+	}
+
+	fwnode_for_each_child_node(leds, led) {
+		if (fwnode_property_read_u32(led, "reg", &led_num))
+			continue;
+		if (led_num > 1) {
+			dev_err(dev, "invalid LED specified port %d\n", port);
+			ret = -EINVAL;
+			goto err_put_led;
+		}
+
+		if (led_num == 0)
+			l = &p->led0;
+		else
+			l = &p->led1;
+
+		state = led_init_default_state_get(led);
+		switch (state) {
+		case LEDS_DEFSTATE_ON:
+			l->brightness = 1;
+			mv88e6393x_led_brightness_set(p, led_num, 1);
+			break;
+		case LEDS_DEFSTATE_KEEP:
+			break;
+		default:
+			l->brightness = 0;
+			mv88e6393x_led_brightness_set(p, led_num, 0);
+		}
+
+		l->max_brightness = 1;
+		if (led_num == 0) {
+			l->brightness_set_blocking = mv88e6393x_led0_brightness_set_blocking;
+			l->blink_set = mv88e6393x_led0_blink_set;
+			l->hw_control_is_supported = mv88e6393x_led0_hw_control_is_supported;
+			l->hw_control_set = mv88e6393x_led0_hw_control_set;
+			l->hw_control_get = mv88e6393x_led0_hw_control_get;
+			l->hw_control_get_device = mv88e6xxx_led0_hw_control_get_device;
+		} else {
+			l->brightness_set_blocking = mv88e6393x_led1_brightness_set_blocking;
+			l->blink_set = mv88e6393x_led1_blink_set;
+			l->hw_control_is_supported = mv88e6393x_led1_hw_control_is_supported;
+			l->hw_control_set = mv88e6393x_led1_hw_control_set;
+			l->hw_control_get = mv88e6393x_led1_hw_control_get;
+			l->hw_control_get_device = mv88e6xxx_led1_hw_control_get_device;
+		}
+		l->hw_control_trigger = "netdev";
+
+		init_data.default_label = ":port";
+		init_data.fwnode = led;
+		init_data.devname_mandatory = true;
+		init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d:0%d", chip->info->name,
+						 port, led_num);
+		if (!init_data.devicename) {
+			ret = -ENOMEM;
+			goto err_put_led;
+		}
+
+		ret = devm_led_classdev_register_ext(dev, l, &init_data);
+		kfree(init_data.devicename);
+
+		if (ret) {
+			dev_err(dev, "Failed to init LED %d for port %d", led_num, port);
+			goto err_put_led;
+		}
+	}
+
+	fwnode_handle_put(leds);
+	return 0;
+
+err_put_led:
+	fwnode_handle_put(led);
+	fwnode_handle_put(leds);
+	return ret;
+}
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index e28fdd21de38..1985b8553611 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1604,6 +1604,39 @@ int mv88e6xxx_port_relinquish_ether_type(struct mv88e6xxx_chip *chip, int port)
 	return err;
 }
 
+/* Offset 0x16: LED Control (6393X family) */
+
+int mv88e6393x_port_led_write(struct mv88e6xxx_chip *chip, int port,
+			      unsigned int pointer, u16 data)
+{
+	u16 cmd = BIT(15) | ((pointer & 0x7) << 12) | (data & 0x7ff);
+	int err;
+
+	err = mv88e6xxx_port_write(chip, port, MV88E6393X_PORT_LED_CONTROL, cmd);
+	if (err)
+		return err;
+
+	return mv88e6xxx_port_wait_bit(chip, port, MV88E6393X_PORT_LED_CONTROL, 15, 0);
+}
+
+int mv88e6393x_port_led_read(struct mv88e6xxx_chip *chip, int port,
+			     unsigned int pointer, u16 *data)
+{
+	u16 cmd = (pointer & 0x7) << 12;
+	int err;
+
+	err = mv88e6xxx_port_write(chip, port, MV88E6393X_PORT_LED_CONTROL, cmd);
+	if (err)
+		return err;
+
+	err = mv88e6xxx_port_read(chip, port, MV88E6393X_PORT_LED_CONTROL, &cmd);
+	if (err)
+		return err;
+
+	*data = cmd & 0x7ff;
+	return 0;
+}
+
 /* Offset 0x18: Port IEEE Priority Remapping Registers [0-3]
  * Offset 0x19: Port IEEE Priority Remapping Registers [4-7]
  */
diff --git a/drivers/net/dsa/mv88e6xxx/port.h b/drivers/net/dsa/mv88e6xxx/port.h
index d2ba9ed1f23e..f4a56671fc4c 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -444,6 +444,9 @@
 /* Control for Special LED (Index 0x7 of LED Control on Port 2) */
 #define MV88E6XXX_PORT_LED_CONTROL_0x07_P2_PTP_ACT		0 /* bits 6:0 PTP Activity */
 
+/* Offset 0x16: LED Control (6393X family) */
+#define MV88E6393X_PORT_LED_CONTROL		0x16
+
 /* Offset 0x18: IEEE Priority Mapping Table */
 #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE			0x18
 #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE		0x8000
@@ -565,6 +568,10 @@ int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
 int mv88e6xxx_port_claim_ether_type(struct mv88e6xxx_chip *chip, int port,
 				    u16 etype);
 int mv88e6xxx_port_relinquish_ether_type(struct mv88e6xxx_chip *chip, int port);
+int mv88e6393x_port_led_write(struct mv88e6xxx_chip *chip, int port,
+			      unsigned int pointer, u16 data);
+int mv88e6393x_port_led_read(struct mv88e6xxx_chip *chip, int port,
+			     unsigned int pointer, u16 *data);
 int mv88e6393x_set_egress_port(struct mv88e6xxx_chip *chip,
 			       enum mv88e6xxx_egress_direction direction,
 			       int port);
@@ -597,12 +604,18 @@ int mv88e6185_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
 int mv88e6352_port_get_cmode(struct mv88e6xxx_chip *chip, int port, u8 *cmode);
 #ifdef CONFIG_NET_DSA_MV88E6XXX_LEDS
 int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip, int port);
+int mv88e6393x_port_setup_leds(struct mv88e6xxx_chip *chip, int port);
 #else
 static inline int mv88e6xxx_port_setup_leds(struct mv88e6xxx_chip *chip,
 					    int port)
 {
 	return 0;
 }
+static inline int mv88e6393x_port_setup_leds(struct mv88e6xxx_chip *chip,
+					     int port)
+{
+	return 0;
+}
 #endif
 int mv88e6xxx_port_drop_untagged(struct mv88e6xxx_chip *chip, int port,
 				 bool drop_untagged);
