From 8510028fa738493a46b235057ed166f9bbbf0ed4 Mon Sep 17 00:00:00 2001
From: Tobias Waldekranz <tobias@waldekranz.com>
Date: Thu, 16 Nov 2023 21:59:35 +0100
Subject: [PATCH 15/17] net: dsa: mv88e6xxx: Add LED support for 6393X

Trigger support:
- "none"
- "timer"
- "netdev"

Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com>
---
 drivers/net/dsa/mv88e6xxx/chip.c |   1 +
 drivers/net/dsa/mv88e6xxx/leds.c | 226 +++++++++++++++++++++++++++++++
 drivers/net/dsa/mv88e6xxx/leds.h |   2 +
 drivers/net/dsa/mv88e6xxx/port.c |  33 +++++
 drivers/net/dsa/mv88e6xxx/port.h |   7 +
 5 files changed, 269 insertions(+)

diff --git a/drivers/net/dsa/mv88e6xxx/chip.c b/drivers/net/dsa/mv88e6xxx/chip.c
index d343ea5cc4a1..7d6b2b85fc31 100644
--- a/drivers/net/dsa/mv88e6xxx/chip.c
+++ b/drivers/net/dsa/mv88e6xxx/chip.c
@@ -5833,6 +5833,7 @@ static const struct mv88e6xxx_ops mv88e6393x_ops = {
 	.gpio_ops = &mv88e6352_gpio_ops,
 	.avb_ops = &mv88e6390_avb_ops,
 	.ptp_ops = &mv88e6352_ptp_ops,
+	.led_ops = &mv88e6393x_led_ops,
 	.phylink_get_caps = mv88e6393x_phylink_get_caps,
 };
 
diff --git a/drivers/net/dsa/mv88e6xxx/leds.c b/drivers/net/dsa/mv88e6xxx/leds.c
index 42e1661e1f47..b2a55d96b506 100644
--- a/drivers/net/dsa/mv88e6xxx/leds.c
+++ b/drivers/net/dsa/mv88e6xxx/leds.c
@@ -4,6 +4,13 @@
 #include "chip.h"
 #include "port.h"
 
+#define FLAG_ACT      (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX))
+#define FLAG_LINK     BIT(TRIGGER_NETDEV_LINK)
+#define FLAG_LINK_10  BIT(TRIGGER_NETDEV_LINK_10)
+#define FLAG_LINK_100 BIT(TRIGGER_NETDEV_LINK_100)
+#define FLAG_LINK_1G  BIT(TRIGGER_NETDEV_LINK_1000)
+#define FLAG_FULL     BIT(TRIGGER_NETDEV_FULL_DUPLEX)
+
 struct mv88e6xxx_led {
 	struct mv88e6xxx_chip *chip;
 	int port;
@@ -12,6 +19,225 @@ struct mv88e6xxx_led {
 	struct led_classdev ldev;
 };
 
+enum mv88e6393x_led_mode {
+	MV88E6393X_LED_MODE_BLINK = 0xd,
+	MV88E6393X_LED_MODE_OFF = 0xe,
+	MV88E6393X_LED_MODE_ON = 0xf,
+
+	MV88E6393X_LED_MODES = 0x10
+};
+
+static const unsigned long mv88e6393x_led_map_p1_p8[2][MV88E6393X_LED_MODES] = {
+	{
+		[0x1] = FLAG_ACT | FLAG_LINK_100 | FLAG_LINK_1G,
+		[0x2] = FLAG_ACT | FLAG_LINK_1G,
+		[0x3] = FLAG_ACT | FLAG_LINK,
+		[0x6] = FLAG_FULL,
+		[0x7] = FLAG_ACT | FLAG_LINK_10 | FLAG_LINK_1G,
+		[0x8] = FLAG_LINK,
+		[0x9] = FLAG_LINK_10,
+		[0xa] = FLAG_ACT | FLAG_LINK_10,
+		[0xb] = FLAG_LINK_100 | FLAG_LINK_1G,
+	},
+	{
+		[0x1] = FLAG_ACT,
+		[0x2] = FLAG_ACT | FLAG_LINK_10 | FLAG_LINK_100,
+		[0x3] = FLAG_LINK_1G,
+		[0x5] = FLAG_ACT | FLAG_LINK,
+		[0x6] = FLAG_ACT | FLAG_LINK_10 | FLAG_LINK_1G,
+		[0x7] = FLAG_LINK_10 | FLAG_LINK_1G,
+		[0x9] = FLAG_LINK_100,
+		[0xa] = FLAG_ACT | FLAG_LINK_100,
+		[0xb] = FLAG_LINK_10 | FLAG_LINK_100,
+	}
+};
+
+static const unsigned long mv88e6393x_led_map_p9_p10[2][MV88E6393X_LED_MODES] = {
+	{
+		[0x1] = FLAG_ACT | FLAG_LINK,
+	},
+	{
+		[0x6] = FLAG_FULL,
+		[0x7] = FLAG_ACT | FLAG_LINK,
+		[0x8] = FLAG_LINK,
+	}
+};
+
+const unsigned long *mv88e6393x_led_map(struct mv88e6xxx_led *led)
+{
+	switch (led->port) {
+	case 1:
+	case 2:
+	case 3:
+	case 4:
+	case 5:
+	case 6:
+	case 7:
+	case 8:
+		return mv88e6393x_led_map_p1_p8[led->index];
+	case 9:
+	case 10:
+		return mv88e6393x_led_map_p9_p10[led->index];
+	}
+
+	return NULL;
+}
+
+static int mv88e6393x_led_flags_to_mode(struct mv88e6xxx_led *led, unsigned long flags)
+{
+	const unsigned long *map = mv88e6393x_led_map(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 -EINVAL;
+}
+
+static int mv88e6393x_led_mode_to_flags(struct mv88e6xxx_led *led, u8 mode,
+					unsigned long *flags)
+{
+	const unsigned long *map = mv88e6393x_led_map(led);
+
+	if (!map)
+		return -ENODEV;
+
+	if (mode == MV88E6393X_LED_MODE_OFF) {
+		*flags = 0;
+		return 0;
+	}
+
+	if (map[mode]) {
+		*flags = map[mode];
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static int mv88e6393x_led_set(struct mv88e6xxx_led *led, int mode)
+{
+	u16 ctrl;
+	int err;
+
+	if (mode < 0)
+		return mode;
+
+	mv88e6xxx_reg_lock(led->chip);
+
+	err = mv88e6393x_port_led_read(led->chip, led->port, 0, &ctrl);
+	if (err)
+		goto out;
+
+	switch (led->index) {
+	case 0:
+		ctrl &= ~0x0f;
+		ctrl |= mode;
+		break;
+	case 1:
+		ctrl &= ~0xf0;
+		ctrl |= mode << 4;
+	}
+
+	err = mv88e6393x_port_led_write(led->chip, led->port, 0, ctrl);
+out:
+	mv88e6xxx_reg_unlock(led->chip);
+	return err;
+}
+
+static int mv88e6393x_led_get(struct mv88e6xxx_led *led)
+{
+	u16 ctrl;
+	int err;
+
+	mv88e6xxx_reg_lock(led->chip);
+	err = mv88e6393x_port_led_read(led->chip, led->port, 0, &ctrl);
+	mv88e6xxx_reg_unlock(led->chip);
+	if (err)
+		return err;
+
+	switch (led->index) {
+	case 0:
+		return ctrl & 0xf;
+	case 1:
+		return (ctrl >> 4) & 0xf;
+	}
+
+	return -EINVAL;
+}
+
+static int mv88e6393x_led_brightness_set(struct mv88e6xxx_led *led,
+					 enum led_brightness brightness)
+{
+	if (brightness == LED_OFF)
+		return mv88e6393x_led_set(led, MV88E6393X_LED_MODE_OFF);
+
+	return mv88e6393x_led_set(led, MV88E6393X_LED_MODE_ON);
+}
+static int mv88e6393x_led_blink_set(struct mv88e6xxx_led *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;
+
+	err = mv88e6393x_led_set(led, MV88E6393X_LED_MODE_BLINK);
+	if (!err)
+		*delay_on = *delay_off = 42;
+
+	return err;
+}
+
+static int mv88e6393x_led_hw_control_is_supported(struct mv88e6xxx_led *led,
+						  unsigned long flags)
+{
+	int mode = mv88e6393x_led_flags_to_mode(led, flags);
+
+	return (mode < 0) ? mode : 0;
+}
+
+static int mv88e6393x_led_hw_control_set(struct mv88e6xxx_led *led,
+					 unsigned long flags)
+{
+	int mode = mv88e6393x_led_flags_to_mode(led, flags);
+
+	return mv88e6393x_led_set(led, mode);
+}
+
+static int mv88e6393x_led_hw_control_get(struct mv88e6xxx_led *led,
+					 unsigned long *flags)
+{
+	int mode = mv88e6393x_led_get(led);
+
+	if (mode < 0)
+		return mode;
+
+	return mv88e6393x_led_mode_to_flags(led, mode, flags);
+}
+
+const struct mv88e6xxx_led_ops mv88e6393x_led_ops = {
+	.brightness_set = mv88e6393x_led_brightness_set,
+	.blink_set = mv88e6393x_led_blink_set,
+	.hw_control_is_supported = mv88e6393x_led_hw_control_is_supported,
+	.hw_control_set = mv88e6393x_led_hw_control_set,
+	.hw_control_get = mv88e6393x_led_hw_control_get,
+};
+
 static int mv88e6xxx_led_brightness_set(struct led_classdev *ldev,
 					enum led_brightness brightness)
 {
diff --git a/drivers/net/dsa/mv88e6xxx/leds.h b/drivers/net/dsa/mv88e6xxx/leds.h
index 8afa2a0c0527..542345d23dc5 100644
--- a/drivers/net/dsa/mv88e6xxx/leds.h
+++ b/drivers/net/dsa/mv88e6xxx/leds.h
@@ -7,6 +7,8 @@
 
 #include "chip.h"
 
+extern const struct mv88e6xxx_led_ops mv88e6393x_led_ops;
+
 int mv88e6xxx_port_setup_leds(struct dsa_switch *ds, int port);
 
 #endif /* _MV88E6XXX_LEDS_H */
diff --git a/drivers/net/dsa/mv88e6xxx/port.c b/drivers/net/dsa/mv88e6xxx/port.c
index dd66ec902d4c..e6733a3ab19f 100644
--- a/drivers/net/dsa/mv88e6xxx/port.c
+++ b/drivers/net/dsa/mv88e6xxx/port.c
@@ -1569,6 +1569,39 @@ int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
 	return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_ETH_TYPE, etype);
 }
 
+/* Offset 0x16: LED Control Register */
+
+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 86deeb347cbc..3605a59d1399 100644
--- a/drivers/net/dsa/mv88e6xxx/port.h
+++ b/drivers/net/dsa/mv88e6xxx/port.h
@@ -294,6 +294,9 @@
 /* Offset 0x13: OutFiltered Counter */
 #define MV88E6XXX_PORT_OUT_FILTERED	0x13
 
+/* Offset 0x16: LED Control Register */
+#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
@@ -410,6 +413,10 @@ int mv88e6393x_port_set_policy(struct mv88e6xxx_chip *chip, int port,
 			       enum mv88e6xxx_policy_action action);
 int mv88e6351_port_set_ether_type(struct mv88e6xxx_chip *chip, int port,
 				  u16 etype);
+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);
-- 
2.34.1

