diff --git a/package/kernel/linux/modules/netdevices.mk b/package/kernel/linux/modules/netdevices.mk index 1470c5c176..64a373a80d 100644 --- a/package/kernel/linux/modules/netdevices.mk +++ b/package/kernel/linux/modules/netdevices.mk @@ -560,6 +560,7 @@ define KernelPackage/dsa-mv88e6xxx TITLE:=Marvell MV88E6XXX DSA Switch DEPENDS:=+kmod-dsa +kmod-ptp +kmod-phy-marvell +kmod-dsa-tag-dsa KCONFIG:=CONFIG_NET_DSA_MV88E6XXX \ + CONFIG_NET_DSA_MV88E6XXX_LEDS=y \ CONFIG_NET_DSA_MV88E6XXX_PTP=y FILES:=$(LINUX_DIR)/drivers/net/dsa/mv88e6xxx/mv88e6xxx.ko AUTOLOAD:=$(call AutoLoad,41,mv88e6xxx,1) diff --git a/target/linux/generic/backport-6.6/901-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch b/target/linux/generic/backport-6.6/901-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch new file mode 100644 index 0000000000..8557fe2510 --- /dev/null +++ b/target/linux/generic/backport-6.6/901-v6.13-net-dsa-mv88e6xxx-Support-LED-control.patch @@ -0,0 +1,1250 @@ +From 7b590490e3aa6bfa38bf6e2069a529017fd3c1d2 Mon Sep 17 00:00:00 2001 +From: Linus Walleij +Date: Fri, 13 Oct 2023 00:08:35 +0200 +Subject: [PATCH] net: dsa: mv88e6xxx: Support LED control + +This adds control over the hardware LEDs in the Marvell +MV88E6xxx DSA switch and enables it for MV88E6352. + +This fixes an imminent problem on the Inteno XG6846 which +has a WAN LED that simply do not work with hardware +defaults: driver amendment is necessary. + +The patch is modeled after Christian Marangis LED support +code for the QCA8k DSA switch, I got help with the register +definitions from Tim Harvey. + +After this patch it is possible to activate hardware link +indication like this (or with a similar script): + + cd /sys/class/leds/Marvell\ 88E6352:05:00:green:wan/ + echo netdev > trigger + echo 1 > link + +This makes the green link indicator come up on any link +speed. It is also possible to be more elaborate, like this: + + cd /sys/class/leds/Marvell\ 88E6352:05:00:green:wan/ + echo netdev > trigger + echo 1 > link_1000 + cd /sys/class/leds/Marvell\ 88E6352:05:01:amber:wan/ + echo netdev > trigger + echo 1 > link_100 + +Making the green LED come on for a gigabit link and the +amber LED come on for a 100 mbit link. + +Each port has 2 LED slots (the hardware may use just one or +none) and the hardware triggers are specified in four bits per +LED, and some of the hardware triggers are only available on the +SFP (fiber) uplink. The restrictions are described in the +port.h header file where the registers are described. For +example, selector 1 set for LED 1 on port 5 or 6 will indicate +Fiber 1000 (gigabit) and activity with a blinking LED, but +ONLY for an SFP connection. If port 5/6 is used with something +not SFP, this selector is a noop: something else need to be +selected. + +After the previous series rewriting the MV88E6xxx DT +bindings to use YAML a "leds" subnode is already valid +for each port, in my scratch device tree it looks like +this: + + leds { + #address-cells = <1>; + #size-cells = <0>; + + led@0 { + reg = <0>; + color = ; + function = LED_FUNCTION_LAN; + default-state = "off"; + linux,default-trigger = "netdev"; + }; + led@1 { + reg = <1>; + color = ; + function = LED_FUNCTION_LAN; + default-state = "off"; + }; + }; + +This DT config is not yet configuring everything: when the netdev +default trigger is assigned the hw acceleration callbacks are +not called, and there is no way to set the netdev sub-trigger +type (such as link_1000) from the device tree, such as if you want +a gigabit link indicator. This has to be done from userspace at +this point. + +We add LED operations to all switches in the 6352 family: +6172, 6176, 6240 and 6352. + +Signed-off-by: Linus Walleij +--- + drivers/net/dsa/mv88e6xxx/Kconfig | 10 + + drivers/net/dsa/mv88e6xxx/Makefile | 1 + + drivers/net/dsa/mv88e6xxx/chip.c | 38 +- + drivers/net/dsa/mv88e6xxx/chip.h | 11 + + drivers/net/dsa/mv88e6xxx/leds.c | 839 +++++++++++++++++++++++++++++ + drivers/net/dsa/mv88e6xxx/port.c | 1 + + drivers/net/dsa/mv88e6xxx/port.h | 133 +++++ + 7 files changed, 1031 insertions(+), 2 deletions(-) + create mode 100644 drivers/net/dsa/mv88e6xxx/leds.c + +--- a/drivers/net/dsa/mv88e6xxx/Kconfig ++++ b/drivers/net/dsa/mv88e6xxx/Kconfig +@@ -17,3 +17,13 @@ config NET_DSA_MV88E6XXX_PTP + help + Say Y to enable PTP hardware timestamping on Marvell 88E6xxx switch + chips that support it. ++ ++config NET_DSA_MV88E6XXX_LEDS ++ bool "LED support for Marvell 88E6xxx" ++ default y ++ depends on NET_DSA_MV88E6XXX ++ depends on LEDS_CLASS=y || LEDS_CLASS=NET_DSA_MV88E6XXX ++ depends on LEDS_TRIGGERS ++ help ++ This enabled support for controlling the LEDs attached to the ++ Marvell 88E6xxx switch chips. +--- a/drivers/net/dsa/mv88e6xxx/Makefile ++++ b/drivers/net/dsa/mv88e6xxx/Makefile +@@ -9,6 +9,7 @@ mv88e6xxx-objs += global2.o + mv88e6xxx-objs += global2_avb.o + mv88e6xxx-objs += global2_scratch.o + mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_PTP) += hwtstamp.o ++mv88e6xxx-$(CONFIG_NET_DSA_MV88E6XXX_LEDS) += leds.o + mv88e6xxx-objs += pcs-6185.o + mv88e6xxx-objs += pcs-6352.o + mv88e6xxx-objs += pcs-639x.o +--- a/drivers/net/dsa/mv88e6xxx/chip.c ++++ b/drivers/net/dsa/mv88e6xxx/chip.c +@@ -27,6 +27,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -3235,14 +3236,43 @@ static int mv88e6xxx_setup_upstream_port + static int mv88e6xxx_setup_port(struct mv88e6xxx_chip *chip, int port) + { + struct device_node *phy_handle = NULL; ++ struct fwnode_handle *ports_fwnode; ++ struct fwnode_handle *port_fwnode; + struct dsa_switch *ds = chip->ds; ++ struct mv88e6xxx_port *p; + struct dsa_port *dp; + int tx_amp; + int err; + u16 reg; ++ u32 val; + +- chip->ports[port].chip = chip; +- chip->ports[port].port = port; ++ p = &chip->ports[port]; ++ p->chip = chip; ++ p->port = port; ++ ++ /* Look up corresponding fwnode if any */ ++ ports_fwnode = device_get_named_child_node(chip->dev, "ethernet-ports"); ++ if (!ports_fwnode) ++ ports_fwnode = device_get_named_child_node(chip->dev, "ports"); ++ if (ports_fwnode) { ++ fwnode_for_each_child_node(ports_fwnode, port_fwnode) { ++ if (fwnode_property_read_u32(port_fwnode, "reg", &val)) ++ continue; ++ if (val == port) { ++ p->fwnode = port_fwnode; ++ p->fiber = fwnode_property_present(port_fwnode, "sfp"); ++ break; ++ } ++ } ++ } else { ++ dev_dbg(chip->dev, "no ethernet ports node defined for the device\n"); ++ } ++ ++ if (chip->info->ops->port_setup_leds) { ++ err = chip->info->ops->port_setup_leds(chip, port); ++ if (err && err != -EOPNOTSUPP) ++ return err; ++ } + + err = mv88e6xxx_port_setup_mac(chip, port, LINK_UNFORCED, + SPEED_UNFORCED, DUPLEX_UNFORCED, +@@ -4461,6 +4491,7 @@ static const struct mv88e6xxx_ops mv88e6 + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, ++ .port_setup_leds = mv88e6xxx_port_setup_leds, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_set_histogram = mv88e6095_g1_stats_set_histogram, +@@ -4563,6 +4594,7 @@ static const struct mv88e6xxx_ops mv88e6 + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, ++ .port_setup_leds = mv88e6xxx_port_setup_leds, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_set_histogram = mv88e6095_g1_stats_set_histogram, +@@ -4838,6 +4870,7 @@ static const struct mv88e6xxx_ops mv88e6 + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, ++ .port_setup_leds = mv88e6xxx_port_setup_leds, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_set_histogram = mv88e6095_g1_stats_set_histogram, +@@ -5260,6 +5293,7 @@ static const struct mv88e6xxx_ops mv88e6 + .port_disable_learn_limit = mv88e6xxx_port_disable_learn_limit, + .port_disable_pri_override = mv88e6xxx_port_disable_pri_override, + .port_get_cmode = mv88e6352_port_get_cmode, ++ .port_setup_leds = mv88e6xxx_port_setup_leds, + .port_setup_message_port = mv88e6xxx_setup_message_port, + .stats_snapshot = mv88e6320_g1_stats_snapshot, + .stats_set_histogram = mv88e6095_g1_stats_set_histogram, +--- a/drivers/net/dsa/mv88e6xxx/chip.h ++++ b/drivers/net/dsa/mv88e6xxx/chip.h +@@ -13,7 +13,9 @@ + #include + #include + #include ++#include + #include ++#include + #include + #include + #include +@@ -275,6 +277,7 @@ struct mv88e6xxx_vlan { + struct mv88e6xxx_port { + struct mv88e6xxx_chip *chip; + int port; ++ struct fwnode_handle *fwnode; + struct mv88e6xxx_vlan bridge_pvid; + u64 serdes_stats[2]; + u64 atu_member_violation; +@@ -289,6 +292,11 @@ struct mv88e6xxx_port { + struct devlink_region *region; + void *pcs_private; + ++ /* LED related information */ ++ bool fiber; ++ struct led_classdev led0; ++ struct led_classdev led1; ++ + /* MacAuth Bypass control flag */ + bool mab; + }; +@@ -561,6 +569,9 @@ struct mv88e6xxx_ops { + phy_interface_t mode); + int (*port_get_cmode)(struct mv88e6xxx_chip *chip, int port, u8 *cmode); + ++ /* LED control */ ++ int (*port_setup_leds)(struct mv88e6xxx_chip *chip, int port); ++ + /* Some devices have a per port register indicating what is + * the upstream port this port should forward to. + */ +--- /dev/null ++++ b/drivers/net/dsa/mv88e6xxx/leds.c +@@ -0,0 +1,839 @@ ++// SPDX-License-Identifier: GPL-2.0-or-later ++#include ++#include ++#include ++ ++#include "chip.h" ++#include "global2.h" ++#include "port.h" ++ ++/* Offset 0x16: LED control */ ++ ++static int mv88e6xxx_port_led_write(struct mv88e6xxx_chip *chip, int port, u16 reg) ++{ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_UPDATE; ++ ++ return mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, reg); ++} ++ ++static int mv88e6xxx_port_led_read(struct mv88e6xxx_chip *chip, int port, ++ u16 ptr, u16 *val) ++{ ++ int err; ++ ++ err = mv88e6xxx_port_write(chip, port, MV88E6XXX_PORT_LED_CONTROL, ptr); ++ if (err) ++ return err; ++ ++ err = mv88e6xxx_port_read(chip, port, MV88E6XXX_PORT_LED_CONTROL, val); ++ *val &= 0x3ff; ++ ++ return err; ++} ++ ++static int mv88e6xxx_led_brightness_set(struct mv88e6xxx_port *p, int led, ++ int brightness) ++{ ++ u16 reg; ++ int err; ++ ++ err = mv88e6xxx_port_led_read(p->chip, p->port, ++ MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, ++ ®); ++ if (err) ++ return err; ++ ++ if (led == 1) ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK; ++ else ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK; ++ ++ if (brightness) { ++ /* Selector 0x0f == Force LED ON */ ++ if (led == 1) ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELF; ++ else ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELF; ++ } else { ++ /* Selector 0x0e == Force LED OFF */ ++ if (led == 1) ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE; ++ else ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE; ++ } ++ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL; ++ ++ return mv88e6xxx_port_led_write(p->chip, p->port, reg); ++} ++ ++static int mv88e6xxx_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 = mv88e6xxx_led_brightness_set(p, 0, brightness); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++static int mv88e6xxx_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 = mv88e6xxx_led_brightness_set(p, 1, brightness); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++struct mv88e6xxx_led_hwconfig { ++ int led; ++ u8 portmask; ++ unsigned long rules; ++ bool fiber; ++ bool blink_activity; ++ u16 selector; ++}; ++ ++/* 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. ++ */ ++#define MV88E6XXX_PORTS_0_3 (BIT(0) | BIT(1) | BIT(2) | BIT(3)) ++#define MV88E6XXX_PORTS_4_5 (BIT(4) | BIT(5)) ++#define MV88E6XXX_PORT_4 BIT(4) ++#define MV88E6XXX_PORT_5 BIT(5) ++ ++/* Entries are listed in selector order. ++ * ++ * These configurations vary across different switch families, list ++ * different tables per-family here. ++ */ ++static const struct mv88e6xxx_led_hwconfig mv88e6352_led_hwconfigs[] = { ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORT_4, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORT_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_4_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100), ++ .blink_activity = true, ++ .fiber = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_4_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .fiber = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_100), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_4_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .fiber = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_4_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100), ++ .blink_activity = true, ++ .fiber = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_1000), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_4_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .fiber = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORT_4, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORT_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORT_4, ++ .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORT_5, ++ .rules = BIT(TRIGGER_NETDEV_FULL_DUPLEX), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORT_5, ++ .rules = BIT(TRIGGER_NETDEV_LINK), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_10), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELA, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELA, ++ }, ++ { ++ .led = 0, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED0_SELB, ++ }, ++ { ++ .led = 1, ++ .portmask = MV88E6XXX_PORTS_0_3, ++ .rules = BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_1000), ++ .blink_activity = true, ++ .selector = MV88E6XXX_PORT_LED_CONTROL_LED1_SELB, ++ }, ++}; ++ ++/* mv88e6xxx_led_match_selector() - look up the appropriate LED mode selector ++ * @p: port state container ++ * @led: LED number, 0 or 1 ++ * @blink_activity: blink the LED (usually blink on indicated activity) ++ * @fiber: the link is connected to fiber such as SFP ++ * @rules: LED status flags from the LED classdev core ++ * @selector: fill in the selector in this parameter with an OR operation ++ */ ++static int mv88e6xxx_led_match_selector(struct mv88e6xxx_port *p, int led, bool blink_activity, ++ bool fiber, unsigned long rules, u16 *selector) ++{ ++ const struct mv88e6xxx_led_hwconfig *conf; ++ int i; ++ ++ /* No rules means we turn the LED off */ ++ if (!rules) { ++ if (led == 1) ++ *selector |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELE; ++ else ++ *selector |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELE; ++ return 0; ++ } ++ ++ /* TODO: these rules are for MV88E6352, when adding other families, ++ * think about making sure you select the table that match the ++ * specific switch family. ++ */ ++ for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) { ++ conf = &mv88e6352_led_hwconfigs[i]; ++ ++ if (conf->led != led) ++ continue; ++ ++ if (!(conf->portmask & BIT(p->port))) ++ continue; ++ ++ if (conf->blink_activity != blink_activity) ++ continue; ++ ++ if (conf->fiber != fiber) ++ continue; ++ ++ if (conf->rules == rules) { ++ dev_dbg(p->chip->dev, "port%d LED %d set selector %04x for rules %08lx\n", ++ p->port, led, conf->selector, rules); ++ *selector |= conf->selector; ++ return 0; ++ } ++ } ++ ++ return -EOPNOTSUPP; ++} ++ ++/* mv88e6xxx_led_match_selector() - find Linux netdev rules from a selector value ++ * @p: port state container ++ * @selector: the selector value from the LED actity register ++ * @led: LED number, 0 or 1 ++ * @rules: Linux netdev activity rules found from selector ++ */ ++static int ++mv88e6xxx_led_match_rule(struct mv88e6xxx_port *p, u16 selector, int led, unsigned long *rules) ++{ ++ const struct mv88e6xxx_led_hwconfig *conf; ++ int i; ++ ++ /* Find the selector in the table, we just look for the right selector ++ * and ignore if the activity has special properties such as blinking ++ * or is fiber-only. ++ */ ++ for (i = 0; i < ARRAY_SIZE(mv88e6352_led_hwconfigs); i++) { ++ conf = &mv88e6352_led_hwconfigs[i]; ++ ++ if (conf->led != led) ++ continue; ++ ++ if (!(conf->portmask & BIT(p->port))) ++ continue; ++ ++ if (conf->selector == selector) { ++ dev_dbg(p->chip->dev, "port%d LED %d has selector %04x, rules %08lx\n", ++ p->port, led, selector, conf->rules); ++ *rules = conf->rules; ++ return 0; ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++/* mv88e6xxx_led_get_selector() - get the appropriate LED mode selector ++ * @p: port state container ++ * @led: LED number, 0 or 1 ++ * @fiber: the link is connected to fiber such as SFP ++ * @rules: LED status flags from the LED classdev core ++ * @selector: fill in the selector in this parameter with an OR operation ++ */ ++static int mv88e6xxx_led_get_selector(struct mv88e6xxx_port *p, int led, ++ bool fiber, unsigned long rules, u16 *selector) ++{ ++ int err; ++ ++ /* What happens here is that we first try to locate a trigger with solid ++ * indicator (such as LED is on for a 1000 link) else we try a second ++ * sweep to find something suitable with a trigger that will blink on ++ * activity. ++ */ ++ err = mv88e6xxx_led_match_selector(p, led, false, fiber, rules, selector); ++ if (err) ++ return mv88e6xxx_led_match_selector(p, led, true, fiber, rules, selector); ++ ++ return 0; ++} ++ ++/* Sets up the hardware blinking period */ ++static int mv88e6xxx_led_set_blinking_period(struct mv88e6xxx_port *p, int led, ++ unsigned long delay_on, unsigned long delay_off) ++{ ++ unsigned long period; ++ u16 reg; ++ ++ period = delay_on + delay_off; ++ ++ reg = 0; ++ ++ switch (period) { ++ case 21: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS; ++ break; ++ case 42: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS; ++ break; ++ case 84: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS; ++ break; ++ case 168: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS; ++ break; ++ case 336: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS; ++ break; ++ case 672: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS; ++ break; ++ default: ++ /* Fall back to software blinking */ ++ return -EINVAL; ++ } ++ ++ /* This is essentially PWM duty cycle: how long time of the period ++ * will the LED be on. Zero isn't great in most cases. ++ */ ++ switch (delay_on) { ++ case 0: ++ /* This is usually pretty useless and will make the LED look OFF */ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE; ++ break; ++ case 21: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS; ++ break; ++ case 42: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS; ++ break; ++ case 84: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS; ++ break; ++ case 168: ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS; ++ break; ++ default: ++ /* Just use something non-zero */ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS; ++ break; ++ } ++ ++ /* Set up blink rate */ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK; ++ ++ return mv88e6xxx_port_led_write(p->chip, p->port, reg); ++} ++ ++static int mv88e6xxx_led_blink_set(struct mv88e6xxx_port *p, int led, ++ unsigned long *delay_on, unsigned long *delay_off) ++{ ++ u16 reg; ++ int err; ++ ++ /* Choose a sensible default 336 ms (~3 Hz) */ ++ if ((*delay_on == 0) && (*delay_off == 0)) { ++ *delay_on = 168; ++ *delay_off = 168; ++ } ++ ++ /* No off delay is just on */ ++ if (*delay_off == 0) ++ return mv88e6xxx_led_brightness_set(p, led, 1); ++ ++ err = mv88e6xxx_led_set_blinking_period(p, led, *delay_on, *delay_off); ++ if (err) ++ return err; ++ ++ err = mv88e6xxx_port_led_read(p->chip, p->port, ++ MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, ++ ®); ++ if (err) ++ return err; ++ ++ if (led == 1) ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK; ++ else ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK; ++ ++ /* This will select the forced blinking status */ ++ if (led == 1) ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED1_SELD; ++ else ++ reg |= MV88E6XXX_PORT_LED_CONTROL_LED0_SELD; ++ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL; ++ ++ return mv88e6xxx_port_led_write(p->chip, p->port, reg); ++} ++ ++static int mv88e6xxx_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); ++ int err; ++ ++ mv88e6xxx_reg_lock(p->chip); ++ err = mv88e6xxx_led_blink_set(p, 0, delay_on, delay_off); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++static int mv88e6xxx_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); ++ int err; ++ ++ mv88e6xxx_reg_lock(p->chip); ++ err = mv88e6xxx_led_blink_set(p, 1, delay_on, delay_off); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++static int ++mv88e6xxx_led0_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0); ++ u16 selector = 0; ++ ++ return mv88e6xxx_led_get_selector(p, 0, p->fiber, rules, &selector); ++} ++ ++static int ++mv88e6xxx_led1_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1); ++ u16 selector = 0; ++ ++ return mv88e6xxx_led_get_selector(p, 1, p->fiber, rules, &selector); ++} ++ ++static int mv88e6xxx_led_hw_control_set(struct mv88e6xxx_port *p, ++ int led, unsigned long rules) ++{ ++ u16 reg; ++ int err; ++ ++ err = mv88e6xxx_port_led_read(p->chip, p->port, ++ MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, ++ ®); ++ if (err) ++ return err; ++ ++ if (led == 1) ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK; ++ else ++ reg &= ~MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK; ++ ++ err = mv88e6xxx_led_get_selector(p, led, p->fiber, rules, ®); ++ if (err) ++ return err; ++ ++ reg |= MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL; ++ ++ if (led == 0) ++ dev_dbg(p->chip->dev, "LED 0 hw control on port %d trigger selector 0x%02x\n", ++ p->port, ++ (unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK)); ++ else ++ dev_dbg(p->chip->dev, "LED 1 hw control on port %d trigger selector 0x%02x\n", ++ p->port, ++ (unsigned int)(reg & MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK) >> 4); ++ ++ return mv88e6xxx_port_led_write(p->chip, p->port, reg); ++} ++ ++static int ++mv88e6xxx_led_hw_control_get(struct mv88e6xxx_port *p, int led, unsigned long *rules) ++{ ++ u16 val; ++ int err; ++ ++ mv88e6xxx_reg_lock(p->chip); ++ err = mv88e6xxx_port_led_read(p->chip, p->port, ++ MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL, &val); ++ mv88e6xxx_reg_unlock(p->chip); ++ if (err) ++ return err; ++ ++ /* Mask out the selector bits for this port */ ++ if (led == 1) { ++ val &= MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK; ++ /* It's forced blinking/OFF/ON */ ++ if (val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELD || ++ val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELE || ++ val == MV88E6XXX_PORT_LED_CONTROL_LED1_SELF) { ++ *rules = 0; ++ return 0; ++ } ++ } else { ++ val &= MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK; ++ /* It's forced blinking/OFF/ON */ ++ if (val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELD || ++ val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELE || ++ val == MV88E6XXX_PORT_LED_CONTROL_LED0_SELF) { ++ *rules = 0; ++ return 0; ++ } ++ } ++ ++ err = mv88e6xxx_led_match_rule(p, val, led, rules); ++ if (!err) ++ return 0; ++ ++ dev_dbg(p->chip->dev, "couldn't find matching selector for %04x\n", val); ++ *rules = 0; ++ return 0; ++} ++ ++static int ++mv88e6xxx_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 = mv88e6xxx_led_hw_control_set(p, 0, rules); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++static int ++mv88e6xxx_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 = mv88e6xxx_led_hw_control_set(p, 1, rules); ++ mv88e6xxx_reg_unlock(p->chip); ++ ++ return err; ++} ++ ++static int ++mv88e6xxx_led0_hw_control_get(struct led_classdev *ldev, unsigned long *rules) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0); ++ ++ return mv88e6xxx_led_hw_control_get(p, 0, rules); ++} ++ ++static int ++mv88e6xxx_led1_hw_control_get(struct led_classdev *ldev, unsigned long *rules) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1); ++ ++ return mv88e6xxx_led_hw_control_get(p, 1, rules); ++} ++ ++static struct device *mv88e6xxx_led_hw_control_get_device(struct mv88e6xxx_port *p) ++{ ++ struct dsa_port *dp; ++ ++ dp = dsa_to_port(p->chip->ds, p->port); ++ if (!dp) ++ return NULL; ++ if (dp->slave) ++ return &dp->slave->dev; ++ return NULL; ++} ++ ++static struct device * ++mv88e6xxx_led0_hw_control_get_device(struct led_classdev *ldev) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led0); ++ ++ return mv88e6xxx_led_hw_control_get_device(p); ++} ++ ++static struct device * ++mv88e6xxx_led1_hw_control_get_device(struct led_classdev *ldev) ++{ ++ struct mv88e6xxx_port *p = container_of(ldev, struct mv88e6xxx_port, led1); ++ ++ return mv88e6xxx_led_hw_control_get_device(p); ++} ++ ++int mv88e6xxx_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; ++ ++ /* LEDs are on ports 1,2,3,4, 5 and 6 (index 0..5), no more */ ++ if (port > 5) ++ 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) { ++ /* Reg represent the led number of the port, max 2 ++ * LEDs can be connected to each port, in some designs ++ * only one LED is connected. ++ */ ++ if (fwnode_property_read_u32(led, "reg", &led_num)) ++ continue; ++ if (led_num > 1) { ++ dev_err(dev, "invalid LED specified port %d\n", port); ++ return -EINVAL; ++ } ++ ++ 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; ++ mv88e6xxx_led_brightness_set(p, led_num, 1); ++ break; ++ case LEDS_DEFSTATE_KEEP: ++ break; ++ default: ++ l->brightness = 0; ++ mv88e6xxx_led_brightness_set(p, led_num, 0); ++ } ++ ++ l->max_brightness = 1; ++ if (led_num == 0) { ++ l->brightness_set_blocking = mv88e6xxx_led0_brightness_set_blocking; ++ l->blink_set = mv88e6xxx_led0_blink_set; ++ l->hw_control_is_supported = mv88e6xxx_led0_hw_control_is_supported; ++ l->hw_control_set = mv88e6xxx_led0_hw_control_set; ++ l->hw_control_get = mv88e6xxx_led0_hw_control_get; ++ l->hw_control_get_device = mv88e6xxx_led0_hw_control_get_device; ++ } else { ++ l->brightness_set_blocking = mv88e6xxx_led1_brightness_set_blocking; ++ l->blink_set = mv88e6xxx_led1_blink_set; ++ l->hw_control_is_supported = mv88e6xxx_led1_hw_control_is_supported; ++ l->hw_control_set = mv88e6xxx_led1_hw_control_set; ++ l->hw_control_get = mv88e6xxx_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) ++ return -ENOMEM; ++ ++ 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); ++ return ret; ++ } ++ } ++ ++ return 0; ++} +--- a/drivers/net/dsa/mv88e6xxx/port.c ++++ b/drivers/net/dsa/mv88e6xxx/port.c +@@ -12,6 +12,7 @@ + #include + #include + #include ++#include + + #include "chip.h" + #include "global2.h" +--- a/drivers/net/dsa/mv88e6xxx/port.h ++++ b/drivers/net/dsa/mv88e6xxx/port.h +@@ -309,6 +309,130 @@ + /* Offset 0x13: OutFiltered Counter */ + #define MV88E6XXX_PORT_OUT_FILTERED 0x13 + ++/* Offset 0x16: LED Control */ ++#define MV88E6XXX_PORT_LED_CONTROL 0x16 ++#define MV88E6XXX_PORT_LED_CONTROL_UPDATE BIT(15) ++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_MASK GENMASK(14, 12) ++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_LED01_CTRL (0x00 << 12) /* Control for LED 0 and 1 */ ++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_STRETCH_BLINK (0x06 << 12) /* Stetch and Blink Rate */ ++#define MV88E6XXX_PORT_LED_CONTROL_POINTER_CNTL_SPECIAL (0x07 << 12) /* Control for the Port's Special LED */ ++#define MV88E6XXX_PORT_LED_CONTROL_DATA_MASK GENMASK(10, 0) ++/* Selection masks valid for either port 1,2,3,4 or 5 */ ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL_MASK GENMASK(3, 0) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL_MASK GENMASK(7, 4) ++/* Selection control for LED 0 and 1, ports 5 and 6 only has LED 0 ++ * Bits Function ++ * 0..3 LED 0 control selector on ports 1-5 ++ * 4..7 LED 1 control selector on ports 1-4 on port 5 this controls LED 0 of port 6 ++ * ++ * Sel Port LED Function for the 6352 family: ++ * 0 1-4 0 Link/Act/Speed by Blink Rate (off=no link, on=link, blink=activity, blink speed=link speed) ++ * 1-4 1 Port 2's Special LED ++ * 5-6 0 Port 5 Link/Act (off=no link, on=link, blink=activity) ++ * 5-6 1 Port 6 Link/Act (off=no link, on=link 1000, blink=activity) ++ * 1 1-4 0 100/1000 Link/Act (off=no link, on=100 or 1000 link, blink=activity) ++ * 1-4 1 10/100 Link Act (off=no link, on=10 or 100 link, blink=activity) ++ * 5-6 0 Fiber 100 Link/Act (off=no link, on=link 100, blink=activity) ++ * 5-6 1 Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity) ++ * 2 1-4 0 1000 Link/Act (off=no link, on=link 1000, blink=activity) ++ * 1-4 1 10/100 Link/Act (off=no link, on=10 or 100 link, blink=activity) ++ * 5-6 0 Fiber 1000 Link/Act (off=no link, on=link 1000, blink=activity) ++ * 5-6 1 Fiber 100 Link/Act (off=no link, on=link 100, blink=activity) ++ * 3 1-4 0 Link/Act (off=no link, on=link, blink=activity) ++ * 1-4 1 1000 Link (off=no link, on=1000 link) ++ * 5-6 0 Port 0's Special LED ++ * 5-6 1 Fiber Link (off=no link, on=link) ++ * 4 1-4 0 Port 0's Special LED ++ * 1-4 1 Port 1's Special LED ++ * 5-6 0 Port 1's Special LED ++ * 5-6 1 Port 5 Link/Act (off=no link, on=link, blink=activity) ++ * 5 1-4 0 Reserved ++ * 1-4 1 Reserved ++ * 5-6 0 Port 2's Special LED ++ * 5-6 1 Port 6 Link (off=no link, on=link) ++ * 6 1-4 0 Duplex/Collision (off=half-duplex,on=full-duplex,blink=collision) ++ * 1-4 1 10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity) ++ * 5-6 0 Port 5 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col) ++ * 5-6 1 Port 6 Duplex/Collision (off=half-duplex, on=full-duplex, blink=col) ++ * 7 1-4 0 10/1000 Link/Act (off=no link, on=10 or 1000 link, blink=activity) ++ * 1-4 1 10/1000 Link (off=no link, on=10 or 1000 link) ++ * 5-6 0 Port 5 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity, blink speed=link speed) ++ * 5-6 1 Port 6 Link/Act/Speed by Blink rate (off=no link, on=link, blink=activity, blink speed=link speed) ++ * 8 1-4 0 Link (off=no link, on=link) ++ * 1-4 1 Activity (off=no link, blink on=activity) ++ * 5-6 0 Port 6 Link/Act (off=no link, on=link, blink=activity) ++ * 5-6 1 Port 0's Special LED ++ * 9 1-4 0 10 Link (off=no link, on=10 link) ++ * 1-4 1 100 Link (off=no link, on=100 link) ++ * 5-6 0 Reserved ++ * 5-6 1 Port 1's Special LED ++ * a 1-4 0 10 Link/Act (off=no link, on=10 link, blink=activity) ++ * 1-4 1 100 Link/Act (off=no link, on=100 link, blink=activity) ++ * 5-6 0 Reserved ++ * 5-6 1 Port 2's Special LED ++ * b 1-4 0 100/1000 Link (off=no link, on=100 or 1000 link) ++ * 1-4 1 10/100 Link (off=no link, on=100 link, blink=activity) ++ * 5-6 0 Reserved ++ * 5-6 1 Reserved ++ * c * * PTP Act (blink on=PTP activity) ++ * d * * Force Blink ++ * e * * Force Off ++ * f * * Force On ++ */ ++/* Select LED0 output */ ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL0 0x0 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL1 0x1 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL2 0x2 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL3 0x3 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL4 0x4 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL5 0x5 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL6 0x6 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL7 0x7 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL8 0x8 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SEL9 0x9 ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELA 0xa ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELB 0xb ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELC 0xc ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELD 0xd ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELE 0xe ++#define MV88E6XXX_PORT_LED_CONTROL_LED0_SELF 0xf ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL0 (0x0 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL1 (0x1 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL2 (0x2 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL3 (0x3 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL4 (0x4 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL5 (0x5 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL6 (0x6 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL7 (0x7 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL8 (0x8 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SEL9 (0x9 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELA (0xa << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELB (0xb << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELC (0xc << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELD (0xd << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELE (0xe << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_LED1_SELF (0xf << 4) ++/* Stretch and Blink Rate Control (Index 0x06 of LED Control) */ ++/* Pulse Stretch Selection for all LED's on this port */ ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_NONE (0 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_21MS (1 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_42MS (2 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_84MS (3 << 4) ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_PULSE_STRETCH_168MS (4 << 4) ++/* Blink Rate Selection for all LEDs on this port */ ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_21MS 0 ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_42MS 1 ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_84MS 2 ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_168MS 3 ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_336MS 4 ++#define MV88E6XXX_PORT_LED_CONTROL_0x06_BLINK_RATE_672MS 5 ++ /* Control for Special LED (Index 0x7 of LED Control on Port0) */ ++#define MV88E6XXX_PORT_LED_CONTROL_0x07_P0_LAN_LINKACT_SHIFT 0 /* bits 6:0 LAN Link Activity LED */ ++/* Control for Special LED (Index 0x7 of LED Control on Port 1) */ ++#define MV88E6XXX_PORT_LED_CONTROL_0x07_P1_WAN_LINKACT_SHIFT 0 /* bits 6:0 WAN Link Activity LED */ ++/* 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 0x18: IEEE Priority Mapping Table */ + #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE 0x18 + #define MV88E6390_PORT_IEEE_PRIO_MAP_TABLE_UPDATE 0x8000 +@@ -457,6 +581,15 @@ int mv88e6393x_port_set_cmode(struct mv8 + phy_interface_t mode); + 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); ++#else ++static inline int mv88e6xxx_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); + int mv88e6xxx_port_set_map_da(struct mv88e6xxx_chip *chip, int port, bool map); diff --git a/target/linux/generic/config-6.6 b/target/linux/generic/config-6.6 index 0118b8dfba..a869d141a8 100644 --- a/target/linux/generic/config-6.6 +++ b/target/linux/generic/config-6.6 @@ -3969,6 +3969,7 @@ CONFIG_NET_CORE=y # CONFIG_NET_DSA_MT7530 is not set # CONFIG_NET_DSA_MV88E6060 is not set # CONFIG_NET_DSA_MV88E6XXX is not set +# CONFIG_NET_DSA_MV88E6XXX_LEDS is not set # CONFIG_NET_DSA_MV88E6XXX_PTP is not set # CONFIG_NET_DSA_QCA8K is not set # CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT is not set diff --git a/target/linux/generic/hack-6.6/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch b/target/linux/generic/hack-6.6/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch index dbf1da04ef..bf93a87e02 100644 --- a/target/linux/generic/hack-6.6/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch +++ b/target/linux/generic/hack-6.6/711-net-dsa-mv88e6xxx-disable-ATU-violation.patch @@ -9,7 +9,7 @@ Subject: [PATCH] net/dsa/mv88e6xxx: disable ATU violation --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c -@@ -3375,6 +3375,9 @@ static int mv88e6xxx_setup_port(struct m +@@ -3405,6 +3405,9 @@ static int mv88e6xxx_setup_port(struct m else reg = 1 << port; diff --git a/target/linux/generic/pending-6.6/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch b/target/linux/generic/pending-6.6/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch index 3f3d7572e0..92f87f6198 100644 --- a/target/linux/generic/pending-6.6/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch +++ b/target/linux/generic/pending-6.6/768-net-dsa-mv88e6xxx-Request-assisted-learning-on-CPU-port.patch @@ -17,7 +17,7 @@ Signed-off-by: Tobias Waldekranz --- a/drivers/net/dsa/mv88e6xxx/chip.c +++ b/drivers/net/dsa/mv88e6xxx/chip.c -@@ -6993,6 +6993,7 @@ static int mv88e6xxx_register_switch(str +@@ -7027,6 +7027,7 @@ static int mv88e6xxx_register_switch(str ds->ops = &mv88e6xxx_switch_ops; ds->ageing_time_min = chip->info->age_time_coeff; ds->ageing_time_max = chip->info->age_time_coeff * U8_MAX;