@@ -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-objs += leds.o
mv88e6xxx-objs += pcs-6185.o
mv88e6xxx-objs += pcs-6352.o
mv88e6xxx-objs += pcs-639x.o
@@ -37,6 +37,7 @@
#include "global1.h"
#include "global2.h"
#include "hwtstamp.h"
+#include "leds.h"
#include "phy.h"
#include "port.h"
#include "ptp.h"
@@ -4006,6 +4007,10 @@ static int mv88e6xxx_port_setup(struct dsa_switch *ds, int port)
struct mv88e6xxx_chip *chip = ds->priv;
int err;
+ err = mv88e6xxx_port_setup_leds(ds, port);
+ if (err)
+ return err;
+
if (chip->info->ops->pcs_ops &&
chip->info->ops->pcs_ops->pcs_init) {
err = chip->info->ops->pcs_ops->pcs_init(chip, port);
@@ -206,6 +206,7 @@ struct mv88e6xxx_gpio_ops;
struct mv88e6xxx_avb_ops;
struct mv88e6xxx_ptp_ops;
struct mv88e6xxx_pcs_ops;
+struct mv88e6xxx_led_ops;
struct mv88e6xxx_irq {
u16 masked;
@@ -653,6 +654,9 @@ struct mv88e6xxx_ops {
/* Precision Time Protocol operations */
const struct mv88e6xxx_ptp_ops *ptp_ops;
+ /* LED operations */
+ const struct mv88e6xxx_led_ops *led_ops;
+
/* Phylink */
void (*phylink_get_caps)(struct mv88e6xxx_chip *chip, int port,
struct phylink_config *config);
new file mode 100644
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+#include <net/dsa.h>
+
+#include "chip.h"
+#include "port.h"
+
+struct mv88e6xxx_led {
+ struct mv88e6xxx_chip *chip;
+ int port;
+ u8 index;
+
+ struct led_classdev ldev;
+};
+
+struct mv88e6xxx_led_ops {
+ int (*brightness_set)(struct mv88e6xxx_led *led,
+ enum led_brightness brightness);
+ int (*blink_set)(struct mv88e6xxx_led *led,
+ unsigned long *delay_on, unsigned long *delay_off);
+ int (*hw_control_is_supported)(struct mv88e6xxx_led *led,
+ unsigned long flags);
+ int (*hw_control_set)(struct mv88e6xxx_led *led, unsigned long flags);
+ int (*hw_control_get)(struct mv88e6xxx_led *led, unsigned long *flags);
+};
+
+static int mv88e6xxx_led_brightness_set(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ const struct mv88e6xxx_led_ops *ops;
+ struct mv88e6xxx_led *led;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ ops = led->chip->info->ops->led_ops;
+
+ if (!ops->brightness_set)
+ return -EOPNOTSUPP;
+
+ return ops->brightness_set(led, brightness);
+}
+
+static int mv88e6xxx_led_blink_set(struct led_classdev *ldev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ const struct mv88e6xxx_led_ops *ops;
+ struct mv88e6xxx_led *led;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ ops = led->chip->info->ops->led_ops;
+
+ if (!ops->blink_set)
+ return -EOPNOTSUPP;
+
+ return ops->blink_set(led, delay_on, delay_off);
+}
+
+static int mv88e6xxx_led_hw_control_is_supported(struct led_classdev *ldev,
+ unsigned long flags)
+{
+ const struct mv88e6xxx_led_ops *ops;
+ struct mv88e6xxx_led *led;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ ops = led->chip->info->ops->led_ops;
+
+ if (!ops->hw_control_is_supported)
+ return -EOPNOTSUPP;
+
+ return ops->hw_control_is_supported(led, flags);
+}
+
+static int mv88e6xxx_led_hw_control_set(struct led_classdev *ldev,
+ unsigned long flags)
+{
+ const struct mv88e6xxx_led_ops *ops;
+ struct mv88e6xxx_led *led;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ ops = led->chip->info->ops->led_ops;
+
+ if (!ops->hw_control_set)
+ return -EOPNOTSUPP;
+
+ return ops->hw_control_set(led, flags);
+}
+
+static int mv88e6xxx_led_hw_control_get(struct led_classdev *ldev,
+ unsigned long *flags)
+{
+ const struct mv88e6xxx_led_ops *ops;
+ struct mv88e6xxx_led *led;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ ops = led->chip->info->ops->led_ops;
+
+ if (!ops->hw_control_get)
+ return -EOPNOTSUPP;
+
+ return ops->hw_control_get(led, flags);
+}
+
+static struct device *mv88e6xxx_led_hw_control_get_device(struct led_classdev *ldev)
+{
+ struct mv88e6xxx_led *led;
+ struct dsa_port *dp;
+
+ led = container_of(ldev, struct mv88e6xxx_led, ldev);
+ dp = dsa_to_port(led->chip->ds, led->port);
+
+ if (dp && dp->user)
+ return &dp->user->dev;
+
+ return NULL;
+}
+
+static int mv88e6xxx_port_setup_led(struct mv88e6xxx_chip *chip, int port,
+ struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ struct mv88e6xxx_led *led;
+ char *devname;
+ u32 index;
+ int err;
+
+ err = of_property_read_u32(np, "reg", &index);
+ if (err)
+ return err;
+
+ if (index >= 2)
+ return -EINVAL;
+
+ led = devm_kzalloc(chip->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ *led = (struct mv88e6xxx_led) {
+ .chip = chip,
+ .port = port,
+ .index = index,
+
+ .ldev = {
+ .max_brightness = 1,
+ .brightness_set_blocking = mv88e6xxx_led_brightness_set,
+ .blink_set = mv88e6xxx_led_blink_set,
+
+#ifdef CONFIG_LEDS_TRIGGERS
+ .hw_control_trigger = "netdev",
+ .hw_control_get_device = mv88e6xxx_led_hw_control_get_device,
+
+ .hw_control_is_supported = mv88e6xxx_led_hw_control_is_supported,
+ .hw_control_set = mv88e6xxx_led_hw_control_set,
+ .hw_control_get = mv88e6xxx_led_hw_control_get,
+#endif
+ },
+ };
+
+ devname = devm_kasprintf(chip->dev, GFP_KERNEL, "%s.%d",
+ dev_name(chip->dev), port);
+ if (!devname)
+ return -ENOMEM;
+
+ init_data = (struct led_init_data) {
+ .fwnode = of_fwnode_handle(np),
+ .devname_mandatory = true,
+ .devicename = devname,
+ };
+
+ return devm_led_classdev_register_ext(chip->dev, &led->ldev, &init_data);
+}
+
+int mv88e6xxx_port_setup_leds(struct dsa_switch *ds, int port)
+{
+ struct dsa_port *dp = dsa_to_port(ds, port);
+ struct mv88e6xxx_chip *chip = ds->priv;
+ struct device_node *pnp, *np;
+ int err;
+
+ if (!chip->info->ops->led_ops)
+ return 0;
+
+ if (!dp->dn)
+ return 0;
+
+ pnp = of_get_child_by_name(dp->dn, "leds");
+ if (!pnp)
+ return 0;
+
+ for_each_available_child_of_node(pnp, np) {
+ err = mv88e6xxx_port_setup_led(chip, port, np);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
new file mode 100644
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+/* Marvell 88E6xxx Switch LED support. */
+
+#ifndef _MV88E6XXX_LEDS_H
+#define _MV88E6XXX_LEDS_H
+
+#include "chip.h"
+
+int mv88e6xxx_port_setup_leds(struct dsa_switch *ds, int port);
+
+#endif /* _MV88E6XXX_LEDS_H */
Parse LEDs from DT and register them with the kernel, for chips that support it. No actual implementations exist yet, they will be added in upcoming commits. Signed-off-by: Tobias Waldekranz <tobias@waldekranz.com> --- drivers/net/dsa/mv88e6xxx/Makefile | 1 + drivers/net/dsa/mv88e6xxx/chip.c | 5 + drivers/net/dsa/mv88e6xxx/chip.h | 4 + drivers/net/dsa/mv88e6xxx/leds.c | 195 +++++++++++++++++++++++++++++ drivers/net/dsa/mv88e6xxx/leds.h | 12 ++ 5 files changed, 217 insertions(+) create mode 100644 drivers/net/dsa/mv88e6xxx/leds.c create mode 100644 drivers/net/dsa/mv88e6xxx/leds.h