Files
infix/patches/linux/6.18.39/0028-net-dsa-mv88e6xxx-Add-LED-support-for-6393X.patch
T

542 lines
16 KiB
Diff

From d42f3887e25aa770c0dd7d722f0410a926a943ae 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/50] net: dsa: mv88e6xxx: Add LED support for 6393X
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);