mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-29 18:19:02 +00:00
4fc47c6000
Changelog: https://cdn.kernel.org/pub/linux/kernel/v6.x/ChangeLog-6.6.59 Removed upstreamed: - generic/backport-6.6/777-netfilter-xtables-fix-typo-causing-some-targets-to-not-load-on-IPv6.patch[1] - generic/backport-6.6/780-24-v6.12-r8169-avoid-unsolicited-interrupts.patch[2] All other patches automatically rebased. 1. https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v6.6.59&id=433742ba96baf30c21e654ce3e698ad87100593b 2. https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?h=v6.6.59&id=7d6d46b429804b1a182106e27e2f8c0e84689e1a Signed-off-by: Mieczyslaw Nalewaj <namiltd@yahoo.com> Link: https://github.com/openwrt/openwrt/pull/16835 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>
1251 lines
40 KiB
Diff
1251 lines
40 KiB
Diff
From 7b590490e3aa6bfa38bf6e2069a529017fd3c1d2 Mon Sep 17 00:00:00 2001
|
|
From: Linus Walleij <linus.walleij@linaro.org>
|
|
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 = <LED_COLOR_ID_GREEN>;
|
|
function = LED_FUNCTION_LAN;
|
|
default-state = "off";
|
|
linux,default-trigger = "netdev";
|
|
};
|
|
led@1 {
|
|
reg = <1>;
|
|
color = <LED_COLOR_ID_AMBER>;
|
|
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 <linus.walleij@linaro.org>
|
|
---
|
|
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 <linux/of_irq.h>
|
|
#include <linux/of_mdio.h>
|
|
#include <linux/platform_data/mv88e6xxx.h>
|
|
+#include <linux/property.h>
|
|
#include <linux/netdevice.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/phylink.h>
|
|
@@ -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 <linux/irq.h>
|
|
#include <linux/gpio/consumer.h>
|
|
#include <linux/kthread.h>
|
|
+#include <linux/leds.h>
|
|
#include <linux/phy.h>
|
|
+#include <linux/property.h>
|
|
#include <linux/ptp_clock_kernel.h>
|
|
#include <linux/timecounter.h>
|
|
#include <net/dsa.h>
|
|
@@ -276,6 +278,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;
|
|
@@ -290,6 +293,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;
|
|
};
|
|
@@ -563,6 +571,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 <linux/bitfield.h>
|
|
+#include <linux/leds.h>
|
|
+#include <linux/property.h>
|
|
+
|
|
+#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 <linux/if_bridge.h>
|
|
#include <linux/phy.h>
|
|
#include <linux/phylink.h>
|
|
+#include <linux/property.h>
|
|
|
|
#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);
|