+MODULE_LICENSE("GPL");
--- a/include/linux/leds.h
+++ b/include/linux/leds.h
-@@ -85,6 +85,9 @@ struct led_classdev {
+@@ -95,6 +95,9 @@ struct led_classdev {
#define LED_BRIGHT_HW_CHANGED BIT(21)
#define LED_RETAIN_AT_SHUTDOWN BIT(22)
#define LED_INIT_DEFAULT_TRIGGER BIT(23)
--- /dev/null
+From 156a5bb89ca6f3edd2be0bfd0de15e575442927e Mon Sep 17 00:00:00 2001
+From: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Date: Tue, 3 Jan 2023 15:12:47 +0200
+Subject: [PATCH] leds: Move led_init_default_state_get() to the global header
+
+There are users inside and outside LED framework that have implemented
+a local copy of led_init_default_state_get(). In order to deduplicate
+that, as the first step move the declaration from LED header to the
+global one.
+
+Signed-off-by: Andy Shevchenko <andriy.shevchenko@linux.intel.com>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230103131256.33894-3-andriy.shevchenko@linux.intel.com
+---
+ drivers/leds/leds.h | 1 -
+ include/linux/leds.h | 2 ++
+ 2 files changed, 2 insertions(+), 1 deletion(-)
+
+--- a/drivers/leds/leds.h
++++ b/drivers/leds/leds.h
+@@ -27,7 +27,6 @@ ssize_t led_trigger_read(struct file *fi
+ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count);
+-enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
+
+ extern struct rw_semaphore leds_list_lock;
+ extern struct list_head leds_list;
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -63,6 +63,8 @@ struct led_init_data {
+ bool devname_mandatory;
+ };
+
++enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
++
+ struct led_hw_trigger_type {
+ int dummy;
+ };
--- /dev/null
+From 3e8b4d6277fd19d98c817576954dd6a4ff3caa2b Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:23 +0200
+Subject: [PATCH 1/9] net: dsa: qca8k: move qca8k_port_to_phy() to header
+
+Move qca8k_port_to_phy() to qca8k header as it's useful for future
+reference in Switch LEDs module since the same logic is applied to get
+the right index of the switch port.
+Make it inline as it's simple function that just decrease the port.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Reviewed-by: Michal Kubiak <michal.kubiak@intel.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-8xxx.c | 15 ---------------
+ drivers/net/dsa/qca/qca8k.h | 14 ++++++++++++++
+ 2 files changed, 14 insertions(+), 15 deletions(-)
+
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -716,21 +716,6 @@ err_clear_skb:
+ return ret;
+ }
+
+-static u32
+-qca8k_port_to_phy(int port)
+-{
+- /* From Andrew Lunn:
+- * Port 0 has no internal phy.
+- * Port 1 has an internal PHY at MDIO address 0.
+- * Port 2 has an internal PHY at MDIO address 1.
+- * ...
+- * Port 5 has an internal PHY at MDIO address 4.
+- * Port 6 has no internal PHY.
+- */
+-
+- return port - 1;
+-}
+-
+ static int
+ qca8k_mdio_busy_wait(struct mii_bus *bus, u32 reg, u32 mask)
+ {
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -414,6 +414,20 @@ struct qca8k_fdb {
+ u8 mac[6];
+ };
+
++static inline u32 qca8k_port_to_phy(int port)
++{
++ /* From Andrew Lunn:
++ * Port 0 has no internal phy.
++ * Port 1 has an internal PHY at MDIO address 0.
++ * Port 2 has an internal PHY at MDIO address 1.
++ * ...
++ * Port 5 has an internal PHY at MDIO address 4.
++ * Port 6 has no internal PHY.
++ */
++
++ return port - 1;
++}
++
+ /* Common setup function */
+ extern const struct qca8k_mib_desc ar8327_mib[];
+ extern const struct regmap_access_table qca8k_readable_table;
--- /dev/null
+From 1e264f9d2918b5737023c44a23ae04def1095210 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:24 +0200
+Subject: [PATCH 2/9] net: dsa: qca8k: add LEDs basic support
+
+Add LEDs basic support for qca8k Switch Family by adding basic
+brightness_set() support.
+
+Since these LEDs refelect port status, the default label is set to
+":port". DT binding should describe the color and function of the
+LEDs using standard LEDs api.
+Each LED always have the device name as prefix. The device name is
+composed from the mii bus id and the PHY addr resulting in example
+names like:
+- qca8k-0.0:00:amber:lan
+- qca8k-0.0:00:white:lan
+- qca8k-0.0:01:amber:lan
+- qca8k-0.0:01:white:lan
+
+These LEDs supports only blocking variant of the brightness_set()
+function since they can sleep during access of the switch leds to set
+the brightness.
+
+While at it add to the qca8k header file each mode defined by the Switch
+Documentation for future use.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/Kconfig | 8 ++
+ drivers/net/dsa/qca/Makefile | 3 +
+ drivers/net/dsa/qca/qca8k-8xxx.c | 5 +
+ drivers/net/dsa/qca/qca8k-leds.c | 239 +++++++++++++++++++++++++++++++
+ drivers/net/dsa/qca/qca8k.h | 60 ++++++++
+ drivers/net/dsa/qca/qca8k_leds.h | 16 +++
+ 6 files changed, 331 insertions(+)
+ create mode 100644 drivers/net/dsa/qca/qca8k-leds.c
+ create mode 100644 drivers/net/dsa/qca/qca8k_leds.h
+
+--- a/drivers/net/dsa/qca/Kconfig
++++ b/drivers/net/dsa/qca/Kconfig
+@@ -15,3 +15,11 @@ config NET_DSA_QCA8K
+ help
+ This enables support for the Qualcomm Atheros QCA8K Ethernet
+ switch chips.
++
++config NET_DSA_QCA8K_LEDS_SUPPORT
++ bool "Qualcomm Atheros QCA8K Ethernet switch family LEDs support"
++ depends on NET_DSA_QCA8K
++ depends on LEDS_CLASS
++ help
++ This enabled support for LEDs present on the Qualcomm Atheros
++ QCA8K Ethernet switch chips.
+--- a/drivers/net/dsa/qca/Makefile
++++ b/drivers/net/dsa/qca/Makefile
+@@ -2,3 +2,6 @@
+ obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
+ obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
+ qca8k-y += qca8k-common.o qca8k-8xxx.o
++ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
++qca8k-y += qca8k-leds.o
++endif
+--- a/drivers/net/dsa/qca/qca8k-8xxx.c
++++ b/drivers/net/dsa/qca/qca8k-8xxx.c
+@@ -22,6 +22,7 @@
+ #include <linux/dsa/tag_qca.h>
+
+ #include "qca8k.h"
++#include "qca8k_leds.h"
+
+ static void
+ qca8k_split_addr(u32 regaddr, u16 *r1, u16 *r2, u16 *page)
+@@ -1185,6 +1186,10 @@ qca8k_setup(struct dsa_switch *ds)
+ if (ret)
+ return ret;
+
++ ret = qca8k_setup_led_ctrl(priv);
++ if (ret)
++ return ret;
++
+ /* Make sure MAC06 is disabled */
+ ret = regmap_clear_bits(priv->regmap, QCA8K_REG_PORT0_PAD_CTRL,
+ QCA8K_PORT0_PAD_MAC06_EXCHANGE_EN);
+--- /dev/null
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -0,0 +1,239 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/regmap.h>
++#include <net/dsa.h>
++
++#include "qca8k.h"
++#include "qca8k_leds.h"
++
++static int
++qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
++{
++ switch (port_num) {
++ case 0:
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++ reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
++ break;
++ case 1:
++ case 2:
++ case 3:
++ /* Port 123 are controlled on a different reg */
++ reg_info->reg = QCA8K_LED_CTRL3_REG;
++ reg_info->shift = QCA8K_LED_PHY123_PATTERN_EN_SHIFT(port_num, led_num);
++ break;
++ case 4:
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++ reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++static int
++qca8k_led_brightness_set(struct qca8k_led *led,
++ enum led_brightness brightness)
++{
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 mask, val;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
++
++ val = QCA8K_LED_ALWAYS_OFF;
++ if (brightness)
++ val = QCA8K_LED_ALWAYS_ON;
++
++ /* HW regs to control brightness is special and port 1-2-3
++ * are placed in a different reg.
++ *
++ * To control port 0 brightness:
++ * - the 2 bit (15, 14) of:
++ * - QCA8K_LED_CTRL0_REG for led1
++ * - QCA8K_LED_CTRL1_REG for led2
++ * - QCA8K_LED_CTRL2_REG for led3
++ *
++ * To control port 4:
++ * - the 2 bit (31, 30) of:
++ * - QCA8K_LED_CTRL0_REG for led1
++ * - QCA8K_LED_CTRL1_REG for led2
++ * - QCA8K_LED_CTRL2_REG for led3
++ *
++ * To control port 1:
++ * - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To control port 2:
++ * - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To control port 3:
++ * - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1
++ * - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2
++ * - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3
++ *
++ * To abstract this and have less code, we use the port and led numm
++ * to calculate the shift and the correct reg due to this problem of
++ * not having a 1:1 map of LED with the regs.
++ */
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return regmap_update_bits(priv->regmap, reg_info.reg,
++ mask << reg_info.shift,
++ val << reg_info.shift);
++}
++
++static int
++qca8k_cled_brightness_set_blocking(struct led_classdev *ldev,
++ enum led_brightness brightness)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ return qca8k_led_brightness_set(led, brightness);
++}
++
++static enum led_brightness
++qca8k_led_brightness_get(struct qca8k_led *led)
++{
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++ int ret;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
++
++ ret = regmap_read(priv->regmap, reg_info.reg, &val);
++ if (ret)
++ return 0;
++
++ val >>= reg_info.shift;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ val &= QCA8K_LED_PATTERN_EN_MASK;
++ val >>= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ /* Assume brightness ON only when the LED is set to always ON */
++ return val == QCA8K_LED_ALWAYS_ON;
++}
++
++static int
++qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
++{
++ struct fwnode_handle *led = NULL, *leds = NULL;
++ struct led_init_data init_data = { };
++ struct dsa_switch *ds = priv->ds;
++ enum led_default_state state;
++ struct qca8k_led *port_led;
++ int led_num, led_index;
++ int ret;
++
++ leds = fwnode_get_named_child_node(port, "leds");
++ if (!leds) {
++ dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n",
++ port_num);
++ return 0;
++ }
++
++ fwnode_for_each_child_node(leds, led) {
++ /* Reg represent the led number of the port.
++ * Each port can have at most 3 leds attached
++ * Commonly:
++ * 1. is gigabit led
++ * 2. is mbit led
++ * 3. additional status led
++ */
++ if (fwnode_property_read_u32(led, "reg", &led_num))
++ continue;
++
++ if (led_num >= QCA8K_LED_PORT_COUNT) {
++ dev_warn(priv->dev, "Invalid LED reg %d defined for port %d",
++ led_num, port_num);
++ continue;
++ }
++
++ led_index = QCA8K_LED_PORT_INDEX(port_num, led_num);
++
++ port_led = &priv->ports_led[led_index];
++ port_led->port_num = port_num;
++ port_led->led_num = led_num;
++ port_led->priv = priv;
++
++ state = led_init_default_state_get(led);
++ switch (state) {
++ case LEDS_DEFSTATE_ON:
++ port_led->cdev.brightness = 1;
++ qca8k_led_brightness_set(port_led, 1);
++ break;
++ case LEDS_DEFSTATE_KEEP:
++ port_led->cdev.brightness =
++ qca8k_led_brightness_get(port_led);
++ break;
++ default:
++ port_led->cdev.brightness = 0;
++ qca8k_led_brightness_set(port_led, 0);
++ }
++
++ port_led->cdev.max_brightness = 1;
++ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
++ init_data.default_label = ":port";
++ init_data.fwnode = led;
++ init_data.devname_mandatory = true;
++ init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id,
++ port_num);
++ if (!init_data.devicename)
++ return -ENOMEM;
++
++ ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data);
++ if (ret)
++ dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num);
++
++ kfree(init_data.devicename);
++ }
++
++ return 0;
++}
++
++int
++qca8k_setup_led_ctrl(struct qca8k_priv *priv)
++{
++ struct fwnode_handle *ports, *port;
++ int port_num;
++ int ret;
++
++ ports = device_get_named_child_node(priv->dev, "ports");
++ if (!ports) {
++ dev_info(priv->dev, "No ports node specified in device tree!");
++ return 0;
++ }
++
++ fwnode_for_each_child_node(ports, port) {
++ if (fwnode_property_read_u32(port, "reg", &port_num))
++ continue;
++
++ /* Skip checking for CPU port 0 and CPU port 6 as not supported */
++ if (port_num == 0 || port_num == 6)
++ continue;
++
++ /* Each port can have at most 3 different leds attached.
++ * Switch port starts from 0 to 6, but port 0 and 6 are CPU
++ * port. The port index needs to be decreased by one to identify
++ * the correct port for LED setup.
++ */
++ ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num));
++ if (ret)
++ return ret;
++ }
++
++ return 0;
++}
+--- a/drivers/net/dsa/qca/qca8k.h
++++ b/drivers/net/dsa/qca/qca8k.h
+@@ -11,6 +11,7 @@
+ #include <linux/delay.h>
+ #include <linux/regmap.h>
+ #include <linux/gpio.h>
++#include <linux/leds.h>
+ #include <linux/dsa/tag_qca.h>
+
+ #define QCA8K_ETHERNET_MDIO_PRIORITY 7
+@@ -85,6 +86,51 @@
+ #define QCA8K_MDIO_MASTER_DATA(x) FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x)
+ #define QCA8K_MDIO_MASTER_MAX_PORTS 5
+ #define QCA8K_MDIO_MASTER_MAX_REG 32
++
++/* LED control register */
++#define QCA8K_LED_PORT_COUNT 3
++#define QCA8K_LED_COUNT ((QCA8K_NUM_PORTS - QCA8K_NUM_CPU_PORTS) * QCA8K_LED_PORT_COUNT)
++#define QCA8K_LED_RULE_COUNT 6
++#define QCA8K_LED_RULE_MAX 11
++#define QCA8K_LED_PORT_INDEX(_phy, _led) (((_phy) * QCA8K_LED_PORT_COUNT) + (_led))
++
++#define QCA8K_LED_PHY123_PATTERN_EN_SHIFT(_phy, _led) ((((_phy) - 1) * 6) + 8 + (2 * (_led)))
++#define QCA8K_LED_PHY123_PATTERN_EN_MASK GENMASK(1, 0)
++
++#define QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT 0
++#define QCA8K_LED_PHY4_CONTROL_RULE_SHIFT 16
++
++#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4)
++#define QCA8K_LED_CTRL0_REG 0x50
++#define QCA8K_LED_CTRL1_REG 0x54
++#define QCA8K_LED_CTRL2_REG 0x58
++#define QCA8K_LED_CTRL3_REG 0x5C
++#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16)
++#define QCA8K_LED_CTRL_MASK GENMASK(15, 0)
++#define QCA8K_LED_RULE_MASK GENMASK(13, 0)
++#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0)
++#define QCA8K_LED_BLINK_FREQ_SHITF 0
++#define QCA8K_LED_BLINK_2HZ 0
++#define QCA8K_LED_BLINK_4HZ 1
++#define QCA8K_LED_BLINK_8HZ 2
++#define QCA8K_LED_BLINK_AUTO 3
++#define QCA8K_LED_LINKUP_OVER_MASK BIT(2)
++#define QCA8K_LED_TX_BLINK_MASK BIT(4)
++#define QCA8K_LED_RX_BLINK_MASK BIT(5)
++#define QCA8K_LED_COL_BLINK_MASK BIT(7)
++#define QCA8K_LED_LINK_10M_EN_MASK BIT(8)
++#define QCA8K_LED_LINK_100M_EN_MASK BIT(9)
++#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10)
++#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11)
++#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12)
++#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13)
++#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14)
++#define QCA8K_LED_PATTERN_EN_SHIFT 14
++#define QCA8K_LED_ALWAYS_OFF 0
++#define QCA8K_LED_ALWAYS_BLINK_4HZ 1
++#define QCA8K_LED_ALWAYS_ON 2
++#define QCA8K_LED_RULE_CONTROLLED 3
++
+ #define QCA8K_GOL_MAC_ADDR0 0x60
+ #define QCA8K_GOL_MAC_ADDR1 0x64
+ #define QCA8K_MAX_FRAME_SIZE 0x78
+@@ -377,6 +423,19 @@ struct qca8k_mdio_cache {
+ u16 page;
+ };
+
++struct qca8k_led_pattern_en {
++ u32 reg;
++ u8 shift;
++};
++
++struct qca8k_led {
++ u8 port_num;
++ u8 led_num;
++ u16 old_rule;
++ struct qca8k_priv *priv;
++ struct led_classdev cdev;
++};
++
+ struct qca8k_priv {
+ u8 switch_id;
+ u8 switch_revision;
+@@ -399,6 +458,7 @@ struct qca8k_priv {
+ struct qca8k_mib_eth_data mib_eth_data;
+ struct qca8k_mdio_cache mdio_cache;
+ const struct qca8k_match_data *info;
++ struct qca8k_led ports_led[QCA8K_LED_COUNT];
+ };
+
+ struct qca8k_mib_desc {
+--- /dev/null
++++ b/drivers/net/dsa/qca/qca8k_leds.h
+@@ -0,0 +1,16 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++
++#ifndef __QCA8K_LEDS_H
++#define __QCA8K_LEDS_H
++
++/* Leds Support function */
++#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
++int qca8k_setup_led_ctrl(struct qca8k_priv *priv);
++#else
++static inline int qca8k_setup_led_ctrl(struct qca8k_priv *priv)
++{
++ return 0;
++}
++#endif
++
++#endif /* __QCA8K_LEDS_H */
--- /dev/null
+From 91acadcc6e599dfc62717abcdad58a459cfb1684 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 17 Apr 2023 17:17:25 +0200
+Subject: [PATCH 3/9] net: dsa: qca8k: add LEDs blink_set() support
+
+Add LEDs blink_set() support to qca8k Switch Family.
+These LEDs support hw accellerated blinking at a fixed rate
+of 4Hz.
+
+Reject any other value since not supported by the LEDs switch.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Acked-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 38 ++++++++++++++++++++++++++++++++
+ 1 file changed, 38 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -128,6 +128,43 @@ qca8k_led_brightness_get(struct qca8k_le
+ }
+
+ static int
++qca8k_cled_blink_set(struct led_classdev *ldev,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ u32 mask, val = QCA8K_LED_ALWAYS_BLINK_4HZ;
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++
++ if (*delay_on == 0 && *delay_off == 0) {
++ *delay_on = 125;
++ *delay_off = 125;
++ }
++
++ if (*delay_on != 125 || *delay_off != 125) {
++ /* The hardware only supports blinking at 4Hz. Fall back
++ * to software implementation in other cases.
++ */
++ return -EINVAL;
++ }
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
++ val << reg_info.shift);
++
++ return 0;
++}
++
++static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+ struct fwnode_handle *led = NULL, *leds = NULL;
+@@ -186,6 +223,7 @@ qca8k_parse_port_leds(struct qca8k_priv
+
+ port_led->cdev.max_brightness = 1;
+ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
++ port_led->cdev.blink_set = qca8k_cled_blink_set;
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
+ init_data.devname_mandatory = true;
--- /dev/null
+From e5029edd53937a29801ef507cee12e657ff31ea9 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:26 +0200
+Subject: [PATCH 4/9] leds: Provide stubs for when CLASS_LED & NEW_LEDS are
+ disabled
+
+Provide stubs for devm_led_classdev_register_ext() and
+led_init_default_state_get() so that LED drivers embedded within other
+drivers such as PHYs and Ethernet switches still build when LEDS_CLASS
+or NEW_LEDS are disabled. This also helps with Kconfig dependencies,
+which are somewhat hairy for phylib and mdio and only get worse when
+adding a dependency on LED_CLASS.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -63,7 +63,15 @@ struct led_init_data {
+ bool devname_mandatory;
+ };
+
++#if IS_ENABLED(CONFIG_NEW_LEDS)
+ enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode);
++#else
++static inline enum led_default_state
++led_init_default_state_get(struct fwnode_handle *fwnode)
++{
++ return LEDS_DEFSTATE_OFF;
++}
++#endif
+
+ struct led_hw_trigger_type {
+ int dummy;
+@@ -198,9 +206,19 @@ static inline int led_classdev_register(
+ return led_classdev_register_ext(parent, led_cdev, NULL);
+ }
+
++#if IS_ENABLED(CONFIG_LEDS_CLASS)
+ int devm_led_classdev_register_ext(struct device *parent,
+ struct led_classdev *led_cdev,
+ struct led_init_data *init_data);
++#else
++static inline int
++devm_led_classdev_register_ext(struct device *parent,
++ struct led_classdev *led_cdev,
++ struct led_init_data *init_data)
++{
++ return 0;
++}
++#endif
+
+ static inline int devm_led_classdev_register(struct device *parent,
+ struct led_classdev *led_cdev)
--- /dev/null
+From 01e5b728e9e43ae444e0369695a5f72209906464 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:27 +0200
+Subject: [PATCH 5/9] net: phy: Add a binding for PHY LEDs
+
+Define common binding parsing for all PHY drivers with LEDs using
+phylib. Parse the DT as part of the phy_probe and add LEDs to the
+linux LED class infrastructure. For the moment, provide a dummy
+brightness function, which will later be replaced with a call into the
+PHY driver. This allows testing since the LED core might otherwise
+reject an LED whose brightness cannot be set.
+
+Add a dependency on LED_CLASS. It either needs to be built in, or not
+enabled, since a modular build can result in linker errors.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/Kconfig | 1 +
+ drivers/net/phy/phy_device.c | 76 ++++++++++++++++++++++++++++++++++++
+ include/linux/phy.h | 16 ++++++++
+ 3 files changed, 93 insertions(+)
+
+--- a/drivers/net/phy/Kconfig
++++ b/drivers/net/phy/Kconfig
+@@ -18,6 +18,7 @@ menuconfig PHYLIB
+ depends on NETDEVICES
+ select MDIO_DEVICE
+ select MDIO_DEVRES
++ depends on LEDS_CLASS || LEDS_CLASS=n
+ help
+ Ethernet controllers are usually attached to PHY
+ devices. This option provides infrastructure for
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -19,10 +19,12 @@
+ #include <linux/interrupt.h>
+ #include <linux/io.h>
+ #include <linux/kernel.h>
++#include <linux/list.h>
+ #include <linux/mdio.h>
+ #include <linux/mii.h>
+ #include <linux/mm.h>
+ #include <linux/module.h>
++#include <linux/of.h>
+ #include <linux/netdevice.h>
+ #include <linux/phy.h>
+ #include <linux/phy_led_triggers.h>
+@@ -641,6 +643,7 @@ struct phy_device *phy_device_create(str
+ device_initialize(&mdiodev->dev);
+
+ dev->state = PHY_DOWN;
++ INIT_LIST_HEAD(&dev->leds);
+
+ mutex_init(&dev->lock);
+ INIT_DELAYED_WORK(&dev->state_queue, phy_state_machine);
+@@ -2942,6 +2945,74 @@ static bool phy_drv_supports_irq(struct
+ return phydrv->config_intr && phydrv->handle_interrupt;
+ }
+
++/* Dummy implementation until calls into PHY driver are added */
++static int phy_led_set_brightness(struct led_classdev *led_cdev,
++ enum led_brightness value)
++{
++ return 0;
++}
++
++static int of_phy_led(struct phy_device *phydev,
++ struct device_node *led)
++{
++ struct device *dev = &phydev->mdio.dev;
++ struct led_init_data init_data = {};
++ struct led_classdev *cdev;
++ struct phy_led *phyled;
++ int err;
++
++ phyled = devm_kzalloc(dev, sizeof(*phyled), GFP_KERNEL);
++ if (!phyled)
++ return -ENOMEM;
++
++ cdev = &phyled->led_cdev;
++
++ err = of_property_read_u8(led, "reg", &phyled->index);
++ if (err)
++ return err;
++
++ cdev->brightness_set_blocking = phy_led_set_brightness;
++ cdev->max_brightness = 1;
++ init_data.devicename = dev_name(&phydev->mdio.dev);
++ init_data.fwnode = of_fwnode_handle(led);
++ init_data.devname_mandatory = true;
++
++ err = devm_led_classdev_register_ext(dev, cdev, &init_data);
++ if (err)
++ return err;
++
++ list_add(&phyled->list, &phydev->leds);
++
++ return 0;
++}
++
++static int of_phy_leds(struct phy_device *phydev)
++{
++ struct device_node *node = phydev->mdio.dev.of_node;
++ struct device_node *leds, *led;
++ int err;
++
++ if (!IS_ENABLED(CONFIG_OF_MDIO))
++ return 0;
++
++ if (!node)
++ return 0;
++
++ leds = of_get_child_by_name(node, "leds");
++ if (!leds)
++ return 0;
++
++ for_each_available_child_of_node(leds, led) {
++ err = of_phy_led(phydev, led);
++ if (err) {
++ of_node_put(led);
++ return err;
++ }
++ }
++
++ return 0;
++}
++
+ /**
+ * fwnode_mdio_find_device - Given a fwnode, find the mdio_device
+ * @fwnode: pointer to the mdio_device's fwnode
+@@ -3120,6 +3191,11 @@ static int phy_probe(struct device *dev)
+ /* Set the state to READY by default */
+ phydev->state = PHY_READY;
+
++ /* Get the LEDs from the device tree, and instantiate standard
++ * LEDs for them.
++ */
++ err = of_phy_leds(phydev);
++
+ out:
+ /* Re-assert the reset signal on error */
+ if (err)
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -14,6 +14,7 @@
+ #include <linux/compiler.h>
+ #include <linux/spinlock.h>
+ #include <linux/ethtool.h>
++#include <linux/leds.h>
+ #include <linux/linkmode.h>
+ #include <linux/netlink.h>
+ #include <linux/mdio.h>
+@@ -582,6 +583,7 @@ struct macsec_ops;
+ * @phy_num_led_triggers: Number of triggers in @phy_led_triggers
+ * @led_link_trigger: LED trigger for link up/down
+ * @last_triggered: last LED trigger for link speed
++ * @leds: list of PHY LED structures
+ * @master_slave_set: User requested master/slave configuration
+ * @master_slave_get: Current master/slave advertisement
+ * @master_slave_state: Current master/slave configuration
+@@ -668,6 +670,7 @@ struct phy_device {
+
+ struct phy_led_trigger *led_link_trigger;
+ #endif
++ struct list_head leds;
+
+ /*
+ * Interrupt number for this PHY
+@@ -739,6 +742,19 @@ struct phy_tdr_config {
+ #define PHY_PAIR_ALL -1
+
+ /**
++ * struct phy_led: An LED driven by the PHY
++ *
++ * @list: List of LEDs
++ * @led_cdev: Standard LED class structure
++ * @index: Number of the LED
++ */
++struct phy_led {
++ struct list_head list;
++ struct led_classdev led_cdev;
++ u8 index;
++};
++
++/**
+ * struct phy_driver - Driver structure for a particular PHY type
+ *
+ * @mdiodrv: Data common to all MDIO devices
--- /dev/null
+From 684818189b04b095b34964ed4a3ea5249a840eab Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:28 +0200
+Subject: [PATCH 6/9] net: phy: phy_device: Call into the PHY driver to set LED
+ brightness
+
+Linux LEDs can be software controlled via the brightness file in /sys.
+LED drivers need to implement a brightness_set function which the core
+will call. Implement an intermediary in phy_device, which will call
+into the phy driver if it implements the necessary function.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 15 ++++++++++++---
+ include/linux/phy.h | 13 +++++++++++++
+ 2 files changed, 25 insertions(+), 3 deletions(-)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2945,11 +2945,18 @@ static bool phy_drv_supports_irq(struct
+ return phydrv->config_intr && phydrv->handle_interrupt;
+ }
+
+-/* Dummy implementation until calls into PHY driver are added */
+ static int phy_led_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+ {
+- return 0;
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_brightness_set(phydev, phyled->index, value);
++ mutex_unlock(&phydev->lock);
++
++ return err;
+ }
+
+ static int of_phy_led(struct phy_device *phydev,
+@@ -2966,12 +2973,14 @@ static int of_phy_led(struct phy_device
+ return -ENOMEM;
+
+ cdev = &phyled->led_cdev;
++ phyled->phydev = phydev;
+
+ err = of_property_read_u8(led, "reg", &phyled->index);
+ if (err)
+ return err;
+
+- cdev->brightness_set_blocking = phy_led_set_brightness;
++ if (phydev->drv->led_brightness_set)
++ cdev->brightness_set_blocking = phy_led_set_brightness;
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -745,15 +745,19 @@ struct phy_tdr_config {
+ * struct phy_led: An LED driven by the PHY
+ *
+ * @list: List of LEDs
++ * @phydev: PHY this LED is attached to
+ * @led_cdev: Standard LED class structure
+ * @index: Number of the LED
+ */
+ struct phy_led {
+ struct list_head list;
++ struct phy_device *phydev;
+ struct led_classdev led_cdev;
+ u8 index;
+ };
+
++#define to_phy_led(d) container_of(d, struct phy_led, led_cdev)
++
+ /**
+ * struct phy_driver - Driver structure for a particular PHY type
+ *
+@@ -953,6 +957,15 @@ struct phy_driver {
+ int (*get_sqi)(struct phy_device *dev);
+ /** @get_sqi_max: Get the maximum signal quality indication */
+ int (*get_sqi_max)(struct phy_device *dev);
++
++ /**
++ * @led_brightness_set: Set a PHY LED brightness. Index
++ * indicates which of the PHYs led should be set. Value
++ * follows the standard LED class meaning, e.g. LED_OFF,
++ * LED_HALF, LED_FULL.
++ */
++ int (*led_brightness_set)(struct phy_device *dev,
++ u8 index, enum led_brightness value);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
--- /dev/null
+From 2d3960e58ef7c83fe1dbf952f056b9e906cb6df8 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:29 +0200
+Subject: [PATCH 7/9] net: phy: marvell: Add software control of the LEDs
+
+Add a brightness function, so the LEDs can be controlled from
+software using the standard Linux LED infrastructure.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/marvell.c | 45 ++++++++++++++++++++++++++++++++++-----
+ 1 file changed, 40 insertions(+), 5 deletions(-)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -144,11 +144,13 @@
+ /* WOL Event Interrupt Enable */
+ #define MII_88E1318S_PHY_CSIER_WOL_EIE BIT(7)
+
+-/* LED Timer Control Register */
+-#define MII_88E1318S_PHY_LED_TCR 0x12
+-#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
+-#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
+-#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)
++#define MII_88E1318S_PHY_LED_FUNC 0x10
++#define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
++#define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
++#define MII_88E1318S_PHY_LED_TCR 0x12
++#define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
++#define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
++#define MII_88E1318S_PHY_LED_TCR_INT_ACTIVE_LOW BIT(11)
+
+ /* Magic Packet MAC address registers */
+ #define MII_88E1318S_PHY_MAGIC_PACKET_WORD2 0x17
+@@ -2793,6 +2795,34 @@ static int marvell_hwmon_probe(struct ph
+ }
+ #endif
+
++static int m88e1318_led_brightness_set(struct phy_device *phydev,
++ u8 index, enum led_brightness value)
++{
++ int reg;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ reg &= ~(0xf << (4 * index));
++ if (value == LED_OFF)
++ reg |= MII_88E1318S_PHY_LED_FUNC_OFF << (4 * index);
++ else
++ reg |= MII_88E1318S_PHY_LED_FUNC_ON << (4 * index);
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3041,6 +3071,7 @@ static struct phy_driver marvell_drivers
+ .get_sset_count = marvell_get_sset_count,
+ .get_strings = marvell_get_strings,
+ .get_stats = marvell_get_stats,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3147,6 +3178,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3173,6 +3205,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3199,6 +3232,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_start = marvell_vct7_cable_test_start,
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3340,6 +3374,7 @@ static struct phy_driver marvell_drivers
+ .get_stats = marvell_get_stats,
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
++ .led_brightness_set = m88e1318_led_brightness_set,
+ },
+ };
+
--- /dev/null
+From 4e901018432e38eab35d2a352661ce4727795be1 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:30 +0200
+Subject: [PATCH 8/9] net: phy: phy_device: Call into the PHY driver to set LED
+ blinking
+
+Linux LEDs can be requested to perform hardware accelerated
+blinking. Pass this to the PHY driver, if it implements the op.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/phy_device.c | 18 ++++++++++++++++++
+ include/linux/phy.h | 12 ++++++++++++
+ 2 files changed, 30 insertions(+)
+
+--- a/drivers/net/phy/phy_device.c
++++ b/drivers/net/phy/phy_device.c
+@@ -2959,6 +2959,22 @@ static int phy_led_set_brightness(struct
+ return err;
+ }
+
++static int phy_led_blink_set(struct led_classdev *led_cdev,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ struct phy_led *phyled = to_phy_led(led_cdev);
++ struct phy_device *phydev = phyled->phydev;
++ int err;
++
++ mutex_lock(&phydev->lock);
++ err = phydev->drv->led_blink_set(phydev, phyled->index,
++ delay_on, delay_off);
++ mutex_unlock(&phydev->lock);
++
++ return err;
++}
++
+ static int of_phy_led(struct phy_device *phydev,
+ struct device_node *led)
+ {
+@@ -2981,6 +2997,8 @@ static int of_phy_led(struct phy_device
+
+ if (phydev->drv->led_brightness_set)
+ cdev->brightness_set_blocking = phy_led_set_brightness;
++ if (phydev->drv->led_blink_set)
++ cdev->blink_set = phy_led_blink_set;
+ cdev->max_brightness = 1;
+ init_data.devicename = dev_name(&phydev->mdio.dev);
+ init_data.fwnode = of_fwnode_handle(led);
+--- a/include/linux/phy.h
++++ b/include/linux/phy.h
+@@ -966,6 +966,18 @@ struct phy_driver {
+ */
+ int (*led_brightness_set)(struct phy_device *dev,
+ u8 index, enum led_brightness value);
++
++ /**
++ * @led_blink_set: Set a PHY LED brightness. Index indicates
++ * which of the PHYs led should be configured to blink. Delays
++ * are in milliseconds and if both are zero then a sensible
++ * default should be chosen. The call should adjust the
++ * timings in that case and if it can't match the values
++ * specified exactly.
++ */
++ int (*led_blink_set)(struct phy_device *dev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off);
+ };
+ #define to_phy_driver(d) container_of(to_mdio_common_driver(d), \
+ struct phy_driver, mdiodrv)
--- /dev/null
+From ea9e86485decb2ac1750005bd96c166c9b780406 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 17 Apr 2023 17:17:31 +0200
+Subject: [PATCH 9/9] net: phy: marvell: Implement led_blink_set()
+
+The Marvell PHY can blink the LEDs, simple on/off. All LEDs blink at
+the same rate, and the reset default is 84ms per blink, which is
+around 12Hz.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Florian Fainelli <f.fainelli@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/phy/marvell.c | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -147,6 +147,8 @@
+ #define MII_88E1318S_PHY_LED_FUNC 0x10
+ #define MII_88E1318S_PHY_LED_FUNC_OFF (0x8)
+ #define MII_88E1318S_PHY_LED_FUNC_ON (0x9)
++#define MII_88E1318S_PHY_LED_FUNC_HI_Z (0xa)
++#define MII_88E1318S_PHY_LED_FUNC_BLINK (0xb)
+ #define MII_88E1318S_PHY_LED_TCR 0x12
+ #define MII_88E1318S_PHY_LED_TCR_FORCE_INT BIT(15)
+ #define MII_88E1318S_PHY_LED_TCR_INTn_ENABLE BIT(7)
+@@ -2823,6 +2825,35 @@ static int m88e1318_led_brightness_set(s
+ MII_88E1318S_PHY_LED_FUNC, reg);
+ }
+
++static int m88e1318_led_blink_set(struct phy_device *phydev, u8 index,
++ unsigned long *delay_on,
++ unsigned long *delay_off)
++{
++ int reg;
++
++ reg = phy_read_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC);
++ if (reg < 0)
++ return reg;
++
++ switch (index) {
++ case 0:
++ case 1:
++ case 2:
++ reg &= ~(0xf << (4 * index));
++ reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
++ /* Reset default is 84ms */
++ *delay_on = 84 / 2;
++ *delay_off = 84 / 2;
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return phy_write_paged(phydev, MII_MARVELL_LED_PAGE,
++ MII_88E1318S_PHY_LED_FUNC, reg);
++}
++
+ static int marvell_probe(struct phy_device *phydev)
+ {
+ struct marvell_priv *priv;
+@@ -3072,6 +3103,7 @@ static struct phy_driver marvell_drivers
+ .get_strings = marvell_get_strings,
+ .get_stats = marvell_get_stats,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1145,
+@@ -3179,6 +3211,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1540,
+@@ -3206,6 +3239,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E1545,
+@@ -3233,6 +3267,7 @@ static struct phy_driver marvell_drivers
+ .cable_test_tdr_start = marvell_vct5_cable_test_tdr_start,
+ .cable_test_get_status = marvell_vct7_cable_test_get_status,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ {
+ .phy_id = MARVELL_PHY_ID_88E3016,
+@@ -3375,6 +3410,7 @@ static struct phy_driver marvell_drivers
+ .get_tunable = m88e1540_get_tunable,
+ .set_tunable = m88e1540_set_tunable,
+ .led_brightness_set = m88e1318_led_brightness_set,
++ .led_blink_set = m88e1318_led_blink_set,
+ },
+ };
+
--- /dev/null
+From 4774ad841bef97cc51df90195338c5b2573dd4cb Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Sun, 23 Apr 2023 19:28:00 +0200
+Subject: [PATCH] net: phy: marvell: Fix inconsistent indenting in
+ led_blink_set
+
+Fix inconsistent indeinting in m88e1318_led_blink_set reported by kernel
+test robot, probably done by the presence of an if condition dropped in
+later revision of the same code.
+
+Reported-by: kernel test robot <lkp@intel.com>
+Link: https://lore.kernel.org/oe-kbuild-all/202304240007.0VEX8QYG-lkp@intel.com/
+Fixes: ea9e86485dec ("net: phy: marvell: Implement led_blink_set()")
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Link: https://lore.kernel.org/r/20230423172800.3470-1-ansuelsmth@gmail.com
+Signed-off-by: Jakub Kicinski <kuba@kernel.org>
+---
+ drivers/net/phy/marvell.c | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+--- a/drivers/net/phy/marvell.c
++++ b/drivers/net/phy/marvell.c
+@@ -2841,10 +2841,10 @@ static int m88e1318_led_blink_set(struct
+ case 1:
+ case 2:
+ reg &= ~(0xf << (4 * index));
+- reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
+- /* Reset default is 84ms */
+- *delay_on = 84 / 2;
+- *delay_off = 84 / 2;
++ reg |= MII_88E1318S_PHY_LED_FUNC_BLINK << (4 * index);
++ /* Reset default is 84ms */
++ *delay_on = 84 / 2;
++ *delay_off = 84 / 2;
+ break;
+ default:
+ return -EINVAL;
--- /dev/null
+From e2f24cb1b5daf9a4f6f3ba574c1fa74aab9807a4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:40 +0200
+Subject: [PATCH 2/5] leds: trigger: netdev: Drop NETDEV_LED_MODE_LINKUP from
+ mode
+
+Putting NETDEV_LED_MODE_LINKUP in the same list of the netdev trigger
+modes is wrong as it's used to set the link state of the device and not
+to set a blink mode as it's done by NETDEV_LED_LINK, NETDEV_LED_TX and
+NETDEV_LED_RX. It's also wrong to put this state in the same bitmap of the
+netdev trigger mode and should be external to it.
+
+Drop NETDEV_LED_MODE_LINKUP from mode list and convert to a simple bool
+that will be true or false based on the carrier link. No functional
+change intended.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-3-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 19 ++++++++-----------
+ 1 file changed, 8 insertions(+), 11 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -50,10 +50,10 @@ struct led_netdev_data {
+ unsigned int last_activity;
+
+ unsigned long mode;
++ bool carrier_link_up;
+ #define NETDEV_LED_LINK 0
+ #define NETDEV_LED_TX 1
+ #define NETDEV_LED_RX 2
+-#define NETDEV_LED_MODE_LINKUP 3
+ };
+
+ enum netdev_led_attr {
+@@ -73,9 +73,9 @@ static void set_baseline_state(struct le
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+
+- if (!test_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode))
++ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+- else {
++ } else {
+ if (test_bit(NETDEV_LED_LINK, &trigger_data->mode))
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+@@ -131,10 +131,9 @@ static ssize_t device_name_store(struct
+ trigger_data->net_dev =
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = false;
+ if (trigger_data->net_dev != NULL)
+- if (netif_carrier_ok(trigger_data->net_dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
+
+ trigger_data->last_activity = 0;
+
+@@ -315,11 +314,10 @@ static int netdev_trig_notify(struct not
+
+ spin_lock_bh(&trigger_data->lock);
+
+- clear_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = false;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+- if (netif_carrier_ok(dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(dev);
+ fallthrough;
+ case NETDEV_REGISTER:
+ if (trigger_data->net_dev)
+@@ -333,8 +331,7 @@ static int netdev_trig_notify(struct not
+ break;
+ case NETDEV_UP:
+ case NETDEV_CHANGE:
+- if (netif_carrier_ok(dev))
+- set_bit(NETDEV_LED_MODE_LINKUP, &trigger_data->mode);
++ trigger_data->carrier_link_up = netif_carrier_ok(dev);
+ break;
+ }
+
--- /dev/null
+From bdec9cb83936e0ac4cb87fed5b49fad0175f7dec Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:41 +0200
+Subject: [PATCH 3/5] leds: trigger: netdev: Rename add namespace to netdev
+ trigger enum modes
+
+Rename NETDEV trigger enum modes to a more symbolic name and add a
+namespace to them.
+
+Also add __TRIGGER_NETDEV_MAX to identify the max modes of the netdev
+trigger.
+
+This is a cleanup to drop the define and no behaviour change are
+intended.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-4-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 58 ++++++++++++---------------
+ 1 file changed, 25 insertions(+), 33 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -51,15 +51,15 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ bool carrier_link_up;
+-#define NETDEV_LED_LINK 0
+-#define NETDEV_LED_TX 1
+-#define NETDEV_LED_RX 2
+ };
+
+-enum netdev_led_attr {
+- NETDEV_ATTR_LINK,
+- NETDEV_ATTR_TX,
+- NETDEV_ATTR_RX
++enum led_trigger_netdev_modes {
++ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_TX,
++ TRIGGER_NETDEV_RX,
++
++ /* Keep last */
++ __TRIGGER_NETDEV_MAX,
+ };
+
+ static void set_baseline_state(struct led_netdev_data *trigger_data)
+@@ -76,7 +76,7 @@ static void set_baseline_state(struct le
+ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+ } else {
+- if (test_bit(NETDEV_LED_LINK, &trigger_data->mode))
++ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+ else
+@@ -85,8 +85,8 @@ static void set_baseline_state(struct le
+ /* If we are looking for RX/TX start periodically
+ * checking stats
+ */
+- if (test_bit(NETDEV_LED_TX, &trigger_data->mode) ||
+- test_bit(NETDEV_LED_RX, &trigger_data->mode))
++ if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
++ test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ schedule_delayed_work(&trigger_data->work, 0);
+ }
+ }
+@@ -146,20 +146,16 @@ static ssize_t device_name_store(struct
+ static DEVICE_ATTR_RW(device_name);
+
+ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
+- enum netdev_led_attr attr)
++ enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ int bit;
+
+ switch (attr) {
+- case NETDEV_ATTR_LINK:
+- bit = NETDEV_LED_LINK;
+- break;
+- case NETDEV_ATTR_TX:
+- bit = NETDEV_LED_TX;
+- break;
+- case NETDEV_ATTR_RX:
+- bit = NETDEV_LED_RX;
++ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_TX:
++ case TRIGGER_NETDEV_RX:
++ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+@@ -169,7 +165,7 @@ static ssize_t netdev_led_attr_show(stru
+ }
+
+ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
+- size_t size, enum netdev_led_attr attr)
++ size_t size, enum led_trigger_netdev_modes attr)
+ {
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ unsigned long state;
+@@ -181,14 +177,10 @@ static ssize_t netdev_led_attr_store(str
+ return ret;
+
+ switch (attr) {
+- case NETDEV_ATTR_LINK:
+- bit = NETDEV_LED_LINK;
+- break;
+- case NETDEV_ATTR_TX:
+- bit = NETDEV_LED_TX;
+- break;
+- case NETDEV_ATTR_RX:
+- bit = NETDEV_LED_RX;
++ case TRIGGER_NETDEV_LINK:
++ case TRIGGER_NETDEV_TX:
++ case TRIGGER_NETDEV_RX:
++ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+@@ -360,21 +352,21 @@ static void netdev_trig_work(struct work
+ }
+
+ /* If we are not looking for RX/TX then return */
+- if (!test_bit(NETDEV_LED_TX, &trigger_data->mode) &&
+- !test_bit(NETDEV_LED_RX, &trigger_data->mode))
++ if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
++ !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ return;
+
+ dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
+ new_activity =
+- (test_bit(NETDEV_LED_TX, &trigger_data->mode) ?
++ (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ?
+ dev_stats->tx_packets : 0) +
+- (test_bit(NETDEV_LED_RX, &trigger_data->mode) ?
++ (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ?
+ dev_stats->rx_packets : 0);
+
+ if (trigger_data->last_activity != new_activity) {
+ led_stop_software_blink(trigger_data->led_cdev);
+
+- invert = test_bit(NETDEV_LED_LINK, &trigger_data->mode);
++ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
--- /dev/null
+From 164b67d53476a9d114be85c885bd31f783835be4 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:42 +0200
+Subject: [PATCH 4/5] leds: trigger: netdev: Convert device attr to macro
+
+Convert link tx and rx device attr to a common macro to reduce common
+code and in preparation for additional attr.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-5-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 57 ++++++++-------------------
+ 1 file changed, 16 insertions(+), 41 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -198,47 +198,22 @@ static ssize_t netdev_led_attr_store(str
+ return size;
+ }
+
+-static ssize_t link_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_LINK);
+-}
+-
+-static ssize_t link_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_LINK);
+-}
+-
+-static DEVICE_ATTR_RW(link);
+-
+-static ssize_t tx_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_TX);
+-}
+-
+-static ssize_t tx_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_TX);
+-}
+-
+-static DEVICE_ATTR_RW(tx);
+-
+-static ssize_t rx_show(struct device *dev,
+- struct device_attribute *attr, char *buf)
+-{
+- return netdev_led_attr_show(dev, buf, NETDEV_ATTR_RX);
+-}
+-
+-static ssize_t rx_store(struct device *dev,
+- struct device_attribute *attr, const char *buf, size_t size)
+-{
+- return netdev_led_attr_store(dev, buf, size, NETDEV_ATTR_RX);
+-}
+-
+-static DEVICE_ATTR_RW(rx);
++#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \
++ static ssize_t trigger_name##_show(struct device *dev, \
++ struct device_attribute *attr, char *buf) \
++ { \
++ return netdev_led_attr_show(dev, buf, trigger); \
++ } \
++ static ssize_t trigger_name##_store(struct device *dev, \
++ struct device_attribute *attr, const char *buf, size_t size) \
++ { \
++ return netdev_led_attr_store(dev, buf, size, trigger); \
++ } \
++ static DEVICE_ATTR_RW(trigger_name)
++
++DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
++DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
++DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+
+ static ssize_t interval_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
--- /dev/null
+From d1b9e1391ab2dc80e9db87fe8b2de015c651e4c9 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Wed, 19 Apr 2023 23:07:43 +0200
+Subject: [PATCH 5/5] leds: trigger: netdev: Use mutex instead of spinlocks
+
+Some LEDs may require to sleep while doing some operation like setting
+brightness and other cleanup.
+
+For this reason, using a spinlock will cause a sleep under spinlock
+warning.
+
+It should be safe to convert this to a sleepable lock since:
+- sysfs read/write can sleep
+- netdev_trig_work is a work queue and can sleep
+- netdev _trig_notify can sleep
+
+The spinlock was used when brightness didn't support sleeping, but this
+changed and now it supported with brightness_set_blocking().
+
+Convert to mutex lock to permit sleeping using brightness_set_blocking().
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Lee Jones <lee@kernel.org>
+Link: https://lore.kernel.org/r/20230419210743.3594-6-ansuelsmth@gmail.com
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 18 +++++++++---------
+ 1 file changed, 9 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -20,7 +20,7 @@
+ #include <linux/list.h>
+ #include <linux/module.h>
+ #include <linux/netdevice.h>
+-#include <linux/spinlock.h>
++#include <linux/mutex.h>
+ #include <linux/timer.h>
+ #include "../leds.h"
+
+@@ -37,7 +37,7 @@
+ */
+
+ struct led_netdev_data {
+- spinlock_t lock;
++ struct mutex lock;
+
+ struct delayed_work work;
+ struct notifier_block notifier;
+@@ -97,9 +97,9 @@ static ssize_t device_name_show(struct d
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ ssize_t len;
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+ len = sprintf(buf, "%s\n", trigger_data->device_name);
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return len;
+ }
+@@ -115,7 +115,7 @@ static ssize_t device_name_store(struct
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+
+ if (trigger_data->net_dev) {
+ dev_put(trigger_data->net_dev);
+@@ -138,7 +138,7 @@ static ssize_t device_name_store(struct
+ trigger_data->last_activity = 0;
+
+ set_baseline_state(trigger_data);
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return size;
+ }
+@@ -279,7 +279,7 @@ static int netdev_trig_notify(struct not
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+- spin_lock_bh(&trigger_data->lock);
++ mutex_lock(&trigger_data->lock);
+
+ trigger_data->carrier_link_up = false;
+ switch (evt) {
+@@ -304,7 +304,7 @@ static int netdev_trig_notify(struct not
+
+ set_baseline_state(trigger_data);
+
+- spin_unlock_bh(&trigger_data->lock);
++ mutex_unlock(&trigger_data->lock);
+
+ return NOTIFY_DONE;
+ }
+@@ -365,7 +365,7 @@ static int netdev_trig_activate(struct l
+ if (!trigger_data)
+ return -ENOMEM;
+
+- spin_lock_init(&trigger_data->lock);
++ mutex_init(&trigger_data->lock);
+
+ trigger_data->notifier.notifier_call = netdev_trig_notify;
+ trigger_data->notifier.priority = 10;
--- /dev/null
+From ed554d3f945179c5b159bddfad7be34b403fe11a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:31 +0200
+Subject: [PATCH 01/13] leds: add APIs for LEDs hw control
+
+Add an option to permit LED driver to declare support for a specific
+trigger to use hw control and setup the LED to blink based on specific
+provided modes.
+
+Add APIs for LEDs hw control. These functions will be used to activate
+hardware control where a LED will use the provided flags, from an
+unique defined supported trigger, to setup the LED to be driven by
+hardware.
+
+Add hw_control_is_supported() to ask the LED driver if the requested
+mode by the trigger are supported and the LED can be setup to follow
+the requested modes.
+
+Deactivate hardware blink control by setting brightness to LED_OFF via
+the brightness_set() callback.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 37 +++++++++++++++++++++++++++++++++++++
+ 1 file changed, 37 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -164,6 +164,43 @@ struct led_classdev {
+
+ /* LEDs that have private triggers have this set */
+ struct led_hw_trigger_type *trigger_type;
++
++ /* Unique trigger name supported by LED set in hw control mode */
++ const char *hw_control_trigger;
++ /*
++ * Check if the LED driver supports the requested mode provided by the
++ * defined supported trigger to setup the LED to hw control mode.
++ *
++ * Return 0 on success. Return -EOPNOTSUPP when the passed flags are not
++ * supported and software fallback needs to be used.
++ * Return a negative error number on any other case for check fail due
++ * to various reason like device not ready or timeouts.
++ */
++ int (*hw_control_is_supported)(struct led_classdev *led_cdev,
++ unsigned long flags);
++ /*
++ * Activate hardware control, LED driver will use the provided flags
++ * from the supported trigger and setup the LED to be driven by hardware
++ * following the requested mode from the trigger flags.
++ * Deactivate hardware blink control by setting brightness to LED_OFF via
++ * the brightness_set() callback.
++ *
++ * Return 0 on success, a negative error number on flags apply fail.
++ */
++ int (*hw_control_set)(struct led_classdev *led_cdev,
++ unsigned long flags);
++ /*
++ * Get from the LED driver the current mode that the LED is set in hw
++ * control mode and put them in flags.
++ * Trigger can use this to get the initial state of a LED already set in
++ * hardware blink control.
++ *
++ * Return 0 on success, a negative error number on failing parsing the
++ * initial mode. Error from this function is NOT FATAL as the device
++ * may be in a not supported initial state by the attached LED trigger.
++ */
++ int (*hw_control_get)(struct led_classdev *led_cdev,
++ unsigned long *flags);
+ #endif
+
+ #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
--- /dev/null
+From 052c38eb17e866c5b4cd43924e7a5e20167b55c0 Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:32 +0200
+Subject: [PATCH 02/13] leds: add API to get attached device for LED hw control
+
+Some specific LED triggers blink the LED based on events from a device
+or subsystem.
+For example, an LED could be blinked to indicate a network device is
+receiving packets, or a disk is reading blocks. To correctly enable and
+request the hw control of the LED, the trigger has to check if the
+network interface or block device configured via a /sys/class/led file
+match the one the LED driver provide for hw control for.
+
+Provide an API call to get the device which the LED blinks for.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ include/linux/leds.h | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -201,6 +201,12 @@ struct led_classdev {
+ */
+ int (*hw_control_get)(struct led_classdev *led_cdev,
+ unsigned long *flags);
++ /*
++ * Get the device this LED blinks in response to.
++ * e.g. for a PHY LED, it is the network device. If the LED is
++ * not yet associated to a device, return NULL.
++ */
++ struct device *(*hw_control_get_device)(struct led_classdev *led_cdev);
+ #endif
+
+ #ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
--- /dev/null
+From 8aa2fd7b66980ecd2e45e95af61cf7eafede1211 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:33 +0200
+Subject: [PATCH 03/13] Documentation: leds: leds-class: Document new Hardware
+ driven LEDs APIs
+
+Document new Hardware driven LEDs APIs.
+
+Some LEDs can be programmed to be driven by hardware. This is not
+limited to blink but also to turn off or on autonomously.
+To support this feature, a LED needs to implement various additional
+ops and needs to declare specific support for the supported triggers.
+
+Add documentation for each required value and API to make hw control
+possible and implementable by both LEDs and triggers.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ Documentation/leds/leds-class.rst | 81 +++++++++++++++++++++++++++++++
+ 1 file changed, 81 insertions(+)
+
+--- a/Documentation/leds/leds-class.rst
++++ b/Documentation/leds/leds-class.rst
+@@ -169,6 +169,87 @@ Setting the brightness to zero with brig
+ should completely turn off the LED and cancel the previously programmed
+ hardware blinking function, if any.
+
++Hardware driven LEDs
++====================
++
++Some LEDs can be programmed to be driven by hardware. This is not
++limited to blink but also to turn off or on autonomously.
++To support this feature, a LED needs to implement various additional
++ops and needs to declare specific support for the supported triggers.
++
++With hw control we refer to the LED driven by hardware.
++
++LED driver must define the following value to support hw control:
++
++ - hw_control_trigger:
++ unique trigger name supported by the LED in hw control
++ mode.
++
++LED driver must implement the following API to support hw control:
++ - hw_control_is_supported:
++ check if the flags passed by the supported trigger can
++ be parsed and activate hw control on the LED.
++
++ Return 0 if the passed flags mask is supported and
++ can be set with hw_control_set().
++
++ If the passed flags mask is not supported -EOPNOTSUPP
++ must be returned, the LED trigger will use software
++ fallback in this case.
++
++ Return a negative error in case of any other error like
++ device not ready or timeouts.
++
++ - hw_control_set:
++ activate hw control. LED driver will use the provided
++ flags passed from the supported trigger, parse them to
++ a set of mode and setup the LED to be driven by hardware
++ following the requested modes.
++
++ Set LED_OFF via the brightness_set to deactivate hw control.
++
++ Return 0 on success, a negative error number on failing to
++ apply flags.
++
++ - hw_control_get:
++ get active modes from a LED already in hw control, parse
++ them and set in flags the current active flags for the
++ supported trigger.
++
++ Return 0 on success, a negative error number on failing
++ parsing the initial mode.
++ Error from this function is NOT FATAL as the device may
++ be in a not supported initial state by the attached LED
++ trigger.
++
++ - hw_control_get_device:
++ return the device associated with the LED driver in
++ hw control. A trigger might use this to match the
++ returned device from this function with a configured
++ device for the trigger as the source for blinking
++ events and correctly enable hw control.
++ (example a netdev trigger configured to blink for a
++ particular dev match the returned dev from get_device
++ to set hw control)
++
++ Returns a pointer to a struct device or NULL if nothing
++ is currently attached.
++
++LED driver can activate additional modes by default to workaround the
++impossibility of supporting each different mode on the supported trigger.
++Examples are hardcoding the blink speed to a set interval, enable special
++feature like bypassing blink if some requirements are not met.
++
++A trigger should first check if the hw control API are supported by the LED
++driver and check if the trigger is supported to verify if hw control is possible,
++use hw_control_is_supported to check if the flags are supported and only at
++the end use hw_control_set to activate hw control.
++
++A trigger can use hw_control_get to check if a LED is already in hw control
++and init their flags.
++
++When the LED is in hw control, no software blink is possible and doing so
++will effectively disable hw control.
+
+ Known Issues
+ ============
--- /dev/null
+From 28a6a2ef18ad840a390d519840c303b03040961c Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:34 +0200
+Subject: [PATCH 04/13] leds: trigger: netdev: refactor code setting device
+ name
+
+Move the code into a helper, ready for it to be called at
+other times. No intended behaviour change.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 29 ++++++++++++++++++---------
+ 1 file changed, 20 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -104,15 +104,9 @@ static ssize_t device_name_show(struct d
+ return len;
+ }
+
+-static ssize_t device_name_store(struct device *dev,
+- struct device_attribute *attr, const char *buf,
+- size_t size)
++static int set_device_name(struct led_netdev_data *trigger_data,
++ const char *name, size_t size)
+ {
+- struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+-
+- if (size >= IFNAMSIZ)
+- return -EINVAL;
+-
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ mutex_lock(&trigger_data->lock);
+@@ -122,7 +116,7 @@ static ssize_t device_name_store(struct
+ trigger_data->net_dev = NULL;
+ }
+
+- memcpy(trigger_data->device_name, buf, size);
++ memcpy(trigger_data->device_name, name, size);
+ trigger_data->device_name[size] = 0;
+ if (size > 0 && trigger_data->device_name[size - 1] == '\n')
+ trigger_data->device_name[size - 1] = 0;
+@@ -140,6 +134,23 @@ static ssize_t device_name_store(struct
+ set_baseline_state(trigger_data);
+ mutex_unlock(&trigger_data->lock);
+
++ return 0;
++}
++
++static ssize_t device_name_store(struct device *dev,
++ struct device_attribute *attr, const char *buf,
++ size_t size)
++{
++ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
++ int ret;
++
++ if (size >= IFNAMSIZ)
++ return -EINVAL;
++
++ ret = set_device_name(trigger_data, buf, size);
++
++ if (ret < 0)
++ return ret;
+ return size;
+ }
+
--- /dev/null
+From 4fd1b6d47a7a38e81fdc6f8be2ccd4216b3f93db Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:35 +0200
+Subject: [PATCH 05/13] leds: trigger: netdev: introduce check for possible hw
+ control
+
+Introduce function to check if the requested mode can use hw control in
+preparation for hw control support. Currently everything is handled in
+software so can_hw_control will always return false.
+
+Add knob with the new value hw_control in trigger_data struct to
+set hw control possible. Useful for future implementation to implement
+in set_baseline_state() the required function to set the requested mode
+using LEDs hw control ops and in other function to reject set if hw
+control is currently active.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 8 ++++++++
+ 1 file changed, 8 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -51,6 +51,7 @@ struct led_netdev_data {
+
+ unsigned long mode;
+ bool carrier_link_up;
++ bool hw_control;
+ };
+
+ enum led_trigger_netdev_modes {
+@@ -91,6 +92,11 @@ static void set_baseline_state(struct le
+ }
+ }
+
++static bool can_hw_control(struct led_netdev_data *trigger_data)
++{
++ return false;
++}
++
+ static ssize_t device_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+ {
+@@ -204,6 +210,8 @@ static ssize_t netdev_led_attr_store(str
+ else
+ clear_bit(bit, &trigger_data->mode);
+
++ trigger_data->hw_control = can_hw_control(trigger_data);
++
+ set_baseline_state(trigger_data);
+
+ return size;
--- /dev/null
+From 6352f25f9fadba59d5df2ba7139495759ccc81d5 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:36 +0200
+Subject: [PATCH 06/13] leds: trigger: netdev: add basic check for hw control
+ support
+
+Add basic check for hw control support. Check if the required API are
+defined and check if the defined trigger supported in hw control for the
+LED driver match netdev.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 14 ++++++++++++++
+ 1 file changed, 14 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -92,8 +92,22 @@ static void set_baseline_state(struct le
+ }
+ }
+
++static bool supports_hw_control(struct led_classdev *led_cdev)
++{
++ if (!led_cdev->hw_control_get || !led_cdev->hw_control_set ||
++ !led_cdev->hw_control_is_supported)
++ return false;
++
++ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
++}
++
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
++ struct led_classdev *led_cdev = trigger_data->led_cdev;
++
++ if (!supports_hw_control(led_cdev))
++ return false;
++
+ return false;
+ }
+
--- /dev/null
+From c84c80c7388f887b10dafd70fc55bc6c5fe9fa5a Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:37 +0200
+Subject: [PATCH 07/13] leds: trigger: netdev: reject interval store for
+ hw_control
+
+Reject interval store with hw_control enabled. It's are currently not
+supported and MUST be set to the default value with hw control enabled.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 3 +++
+ 1 file changed, 3 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -265,6 +265,9 @@ static ssize_t interval_store(struct dev
+ unsigned long value;
+ int ret;
+
++ if (trigger_data->hw_control)
++ return -EINVAL;
++
+ ret = kstrtoul(buf, 0, &value);
+ if (ret)
+ return ret;
--- /dev/null
+From 7c145a34ba6e380616af93262fcab9fc7261d851 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:38 +0200
+Subject: [PATCH 08/13] leds: trigger: netdev: add support for LED hw control
+
+Add support for LED hw control for the netdev trigger.
+
+The trigger on calling set_baseline_state to configure a new mode, will
+do various check to verify if hw control can be used for the requested
+mode in can_hw_control() function.
+
+It will first check if the LED driver supports hw control for the netdev
+trigger, then will use hw_control_is_supported() and finally will call
+hw_control_set() to apply the requested mode.
+
+To use such mode, interval MUST be set to the default value and net_dev
+MUST be set. If one of these 2 value are not valid, hw control will
+never be used and normal software fallback is used.
+
+The default interval value is moved to a define to make sure they are
+always synced.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 43 +++++++++++++++++++++++++--
+ 1 file changed, 41 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -24,6 +24,8 @@
+ #include <linux/timer.h>
+ #include "../leds.h"
+
++#define NETDEV_LED_DEFAULT_INTERVAL 50
++
+ /*
+ * Configurable sysfs attributes:
+ *
+@@ -68,6 +70,13 @@ static void set_baseline_state(struct le
+ int current_brightness;
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+
++ /* Already validated, hw control is possible with the requested mode */
++ if (trigger_data->hw_control) {
++ led_cdev->hw_control_set(led_cdev, trigger_data->mode);
++
++ return;
++ }
++
+ current_brightness = led_cdev->brightness;
+ if (current_brightness)
+ led_cdev->blink_brightness = current_brightness;
+@@ -103,12 +112,42 @@ static bool supports_hw_control(struct l
+
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
++ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
++ unsigned int interval = atomic_read(&trigger_data->interval);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
++ int ret;
+
+ if (!supports_hw_control(led_cdev))
+ return false;
+
+- return false;
++ /*
++ * Interval must be set to the default
++ * value. Any different value is rejected if in hw
++ * control.
++ */
++ if (interval != default_interval)
++ return false;
++
++ /*
++ * net_dev must be set with hw control, otherwise no
++ * blinking can be happening and there is nothing to
++ * offloaded.
++ */
++ if (!trigger_data->net_dev)
++ return false;
++
++ /* Check if the requested mode is supported */
++ ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode);
++ /* Fall back to software blinking if not supported */
++ if (ret == -EOPNOTSUPP)
++ return false;
++ if (ret) {
++ dev_warn(led_cdev->dev,
++ "Current mode check failed with error %d\n", ret);
++ return false;
++ }
++
++ return true;
+ }
+
+ static ssize_t device_name_show(struct device *dev,
+@@ -413,7 +452,7 @@ static int netdev_trig_activate(struct l
+ trigger_data->device_name[0] = 0;
+
+ trigger_data->mode = 0;
+- atomic_set(&trigger_data->interval, msecs_to_jiffies(50));
++ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
+ trigger_data->last_activity = 0;
+
+ led_set_trigger_data(led_cdev, trigger_data);
--- /dev/null
+From 33ec0b53befff2c0a7f3aa19ff08556d60585d6b Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:39 +0200
+Subject: [PATCH 09/13] leds: trigger: netdev: validate configured netdev
+
+The netdev which the LED should blink for is configurable in
+/sys/class/led/foo/device_name. Ensure when offloading that the
+configured netdev is the same as the netdev the LED is associated
+with. If it is not, only perform software blinking.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 24 ++++++++++++++++++++++--
+ 1 file changed, 22 insertions(+), 2 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -110,6 +110,24 @@ static bool supports_hw_control(struct l
+ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
+ }
+
++/*
++ * Validate the configured netdev is the same as the one associated with
++ * the LED driver in hw control.
++ */
++static bool validate_net_dev(struct led_classdev *led_cdev,
++ struct net_device *net_dev)
++{
++ struct device *dev = led_cdev->hw_control_get_device(led_cdev);
++ struct net_device *ndev;
++
++ if (!dev)
++ return false;
++
++ ndev = to_net_dev(dev);
++
++ return ndev == net_dev;
++}
++
+ static bool can_hw_control(struct led_netdev_data *trigger_data)
+ {
+ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
+@@ -131,9 +149,11 @@ static bool can_hw_control(struct led_ne
+ /*
+ * net_dev must be set with hw control, otherwise no
+ * blinking can be happening and there is nothing to
+- * offloaded.
++ * offloaded. Additionally, for hw control to be
++ * valid, the configured netdev must be the same as
++ * netdev associated to the LED.
+ */
+- if (!trigger_data->net_dev)
++ if (!validate_net_dev(led_cdev, trigger_data->net_dev))
+ return false;
+
+ /* Check if the requested mode is supported */
--- /dev/null
+From 0316cc5629d15880dd3f097d221c55ca648bcd61 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:40 +0200
+Subject: [PATCH 10/13] leds: trigger: netdev: init mode if hw control already
+ active
+
+On netdev trigger activation, hw control may be already active by
+default. If this is the case and a device is actually provided by
+hw_control_get_device(), init the already active mode and set the
+bool to hw_control bool to true to reflect the already set mode in the
+trigger_data.
+
+Co-developed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 17 +++++++++++++++++
+ 1 file changed, 17 insertions(+)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -454,6 +454,8 @@ static void netdev_trig_work(struct work
+ static int netdev_trig_activate(struct led_classdev *led_cdev)
+ {
+ struct led_netdev_data *trigger_data;
++ unsigned long mode;
++ struct device *dev;
+ int rc;
+
+ trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
+@@ -475,6 +477,21 @@ static int netdev_trig_activate(struct l
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
+ trigger_data->last_activity = 0;
+
++ /* Check if hw control is active by default on the LED.
++ * Init already enabled mode in hw control.
++ */
++ if (supports_hw_control(led_cdev) &&
++ !led_cdev->hw_control_get(led_cdev, &mode)) {
++ dev = led_cdev->hw_control_get_device(led_cdev);
++ if (dev) {
++ const char *name = dev_name(dev);
++
++ set_device_name(trigger_data, name, strlen(name));
++ trigger_data->hw_control = true;
++ trigger_data->mode = mode;
++ }
++ }
++
+ led_set_trigger_data(led_cdev, trigger_data);
+
+ rc = register_netdevice_notifier(&trigger_data->notifier);
--- /dev/null
+From 947acacab5ea151291b861cdfbde16ff5cf1b08c Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:41 +0200
+Subject: [PATCH 11/13] leds: trigger: netdev: expose netdev trigger modes in
+ linux include
+
+Expose netdev trigger modes to make them accessible by LED driver that
+will support netdev trigger for hw control.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/leds/trigger/ledtrig-netdev.c | 9 ---------
+ include/linux/leds.h | 10 ++++++++++
+ 2 files changed, 10 insertions(+), 9 deletions(-)
+
+--- a/drivers/leds/trigger/ledtrig-netdev.c
++++ b/drivers/leds/trigger/ledtrig-netdev.c
+@@ -56,15 +56,6 @@ struct led_netdev_data {
+ bool hw_control;
+ };
+
+-enum led_trigger_netdev_modes {
+- TRIGGER_NETDEV_LINK = 0,
+- TRIGGER_NETDEV_TX,
+- TRIGGER_NETDEV_RX,
+-
+- /* Keep last */
+- __TRIGGER_NETDEV_MAX,
+-};
+-
+ static void set_baseline_state(struct led_netdev_data *trigger_data)
+ {
+ int current_brightness;
+--- a/include/linux/leds.h
++++ b/include/linux/leds.h
+@@ -527,6 +527,16 @@ static inline void *led_get_trigger_data
+
+ #endif /* CONFIG_LEDS_TRIGGERS */
+
++/* Trigger specific enum */
++enum led_trigger_netdev_modes {
++ TRIGGER_NETDEV_LINK = 0,
++ TRIGGER_NETDEV_TX,
++ TRIGGER_NETDEV_RX,
++
++ /* Keep last */
++ __TRIGGER_NETDEV_MAX,
++};
++
+ /* Trigger specific functions */
+ #ifdef CONFIG_LEDS_TRIGGER_DISK
+ void ledtrig_disk_activity(bool write);
--- /dev/null
+From e0256648c831af13cbfe4a1787327fcec01c2807 Mon Sep 17 00:00:00 2001
+From: Christian Marangi <ansuelsmth@gmail.com>
+Date: Mon, 29 May 2023 18:32:42 +0200
+Subject: [PATCH 12/13] net: dsa: qca8k: implement hw_control ops
+
+Implement hw_control ops to drive Switch LEDs based on hardware events.
+
+Netdev trigger is the declared supported trigger for hw control
+operation and supports the following mode:
+- tx
+- rx
+
+When hw_control_set is called, LEDs are set to follow the requested
+mode.
+Each LEDs will blink at 4Hz by default.
+
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Reviewed-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 154 +++++++++++++++++++++++++++++++
+ 1 file changed, 154 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -32,6 +32,43 @@ qca8k_get_enable_led_reg(int port_num, i
+ }
+
+ static int
++qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
++{
++ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
++
++ /* 6 total control rule:
++ * 3 control rules for phy0-3 that applies to all their leds
++ * 3 control rules for phy4
++ */
++ if (port_num == 4)
++ reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
++ else
++ reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
++
++ return 0;
++}
++
++static int
++qca8k_parse_netdev(unsigned long rules, u32 *offload_trigger)
++{
++ /* Parsing specific to netdev trigger */
++ if (test_bit(TRIGGER_NETDEV_TX, &rules))
++ *offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
++ if (test_bit(TRIGGER_NETDEV_RX, &rules))
++ *offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
++
++ if (rules && !*offload_trigger)
++ return -EOPNOTSUPP;
++
++ /* Enable some default rule by default to the requested mode:
++ * - Blink at 4Hz by default
++ */
++ *offload_trigger |= QCA8K_LED_BLINK_4HZ;
++
++ return 0;
++}
++
++static int
+ qca8k_led_brightness_set(struct qca8k_led *led,
+ enum led_brightness brightness)
+ {
+@@ -165,6 +202,119 @@ qca8k_cled_blink_set(struct led_classdev
+ }
+
+ static int
++qca8k_cled_trigger_offload(struct led_classdev *ldev, bool enable)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 mask, val = QCA8K_LED_ALWAYS_OFF;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
++
++ if (enable)
++ val = QCA8K_LED_RULE_CONTROLLED;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ mask = QCA8K_LED_PATTERN_EN_MASK;
++ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
++ val << reg_info.shift);
++}
++
++static bool
++qca8k_cled_hw_control_status(struct led_classdev *ldev)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++
++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
++
++ regmap_read(priv->regmap, reg_info.reg, &val);
++
++ val >>= reg_info.shift;
++
++ if (led->port_num == 0 || led->port_num == 4) {
++ val &= QCA8K_LED_PATTERN_EN_MASK;
++ val >>= QCA8K_LED_PATTERN_EN_SHIFT;
++ } else {
++ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
++ }
++
++ return val == QCA8K_LED_RULE_CONTROLLED;
++}
++
++static int
++qca8k_cled_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
++{
++ u32 offload_trigger = 0;
++
++ return qca8k_parse_netdev(rules, &offload_trigger);
++}
++
++static int
++qca8k_cled_hw_control_set(struct led_classdev *ldev, unsigned long rules)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 offload_trigger = 0;
++ int ret;
++
++ ret = qca8k_parse_netdev(rules, &offload_trigger);
++ if (ret)
++ return ret;
++
++ ret = qca8k_cled_trigger_offload(ldev, true);
++ if (ret)
++ return ret;
++
++ qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
++
++ return regmap_update_bits(priv->regmap, reg_info.reg,
++ QCA8K_LED_RULE_MASK << reg_info.shift,
++ offload_trigger << reg_info.shift);
++}
++
++static int
++qca8k_cled_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_led_pattern_en reg_info;
++ struct qca8k_priv *priv = led->priv;
++ u32 val;
++ int ret;
++
++ /* With hw control not active return err */
++ if (!qca8k_cled_hw_control_status(ldev))
++ return -EINVAL;
++
++ qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
++
++ ret = regmap_read(priv->regmap, reg_info.reg, &val);
++ if (ret)
++ return ret;
++
++ val >>= reg_info.shift;
++ val &= QCA8K_LED_RULE_MASK;
++
++ /* Parsing specific to netdev trigger */
++ if (val & QCA8K_LED_TX_BLINK_MASK)
++ set_bit(TRIGGER_NETDEV_TX, rules);
++ if (val & QCA8K_LED_RX_BLINK_MASK)
++ set_bit(TRIGGER_NETDEV_RX, rules);
++
++ return 0;
++}
++
++static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+ struct fwnode_handle *led = NULL, *leds = NULL;
+@@ -224,6 +374,10 @@ qca8k_parse_port_leds(struct qca8k_priv
+ port_led->cdev.max_brightness = 1;
+ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
+ port_led->cdev.blink_set = qca8k_cled_blink_set;
++ port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
++ port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
++ port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
++ port_led->cdev.hw_control_trigger = "netdev";
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
+ init_data.devname_mandatory = true;
--- /dev/null
+From 4f53c27f772e27e4cf4e5507d6f4d5980002cb6a Mon Sep 17 00:00:00 2001
+From: Andrew Lunn <andrew@lunn.ch>
+Date: Mon, 29 May 2023 18:32:43 +0200
+Subject: [PATCH 13/13] net: dsa: qca8k: add op to get ports netdev
+
+In order that the LED trigger can blink the switch MAC ports LED, it
+needs to know the netdev associated to the port. Add the callback to
+return the struct device of the netdev.
+
+Add an helper function qca8k_phy_to_port() to convert the phy back to
+dsa_port index, as we reference LED port based on the internal PHY
+index and needs to be converted back.
+
+Signed-off-by: Andrew Lunn <andrew@lunn.ch>
+Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
+Signed-off-by: David S. Miller <davem@davemloft.net>
+---
+ drivers/net/dsa/qca/qca8k-leds.c | 27 +++++++++++++++++++++++++++
+ 1 file changed, 27 insertions(+)
+
+--- a/drivers/net/dsa/qca/qca8k-leds.c
++++ b/drivers/net/dsa/qca/qca8k-leds.c
+@@ -5,6 +5,18 @@
+ #include "qca8k.h"
+ #include "qca8k_leds.h"
+
++static u32 qca8k_phy_to_port(int phy)
++{
++ /* Internal PHY 0 has port at index 1.
++ * Internal PHY 1 has port at index 2.
++ * Internal PHY 2 has port at index 3.
++ * Internal PHY 3 has port at index 4.
++ * Internal PHY 4 has port at index 5.
++ */
++
++ return phy + 1;
++}
++
+ static int
+ qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
+ {
+@@ -314,6 +326,20 @@ qca8k_cled_hw_control_get(struct led_cla
+ return 0;
+ }
+
++static struct device *qca8k_cled_hw_control_get_device(struct led_classdev *ldev)
++{
++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
++ struct qca8k_priv *priv = led->priv;
++ struct dsa_port *dp;
++
++ dp = dsa_to_port(priv->ds, qca8k_phy_to_port(led->port_num));
++ if (!dp)
++ return NULL;
++ if (dp->slave)
++ return &dp->slave->dev;
++ return NULL;
++}
++
+ static int
+ qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
+ {
+@@ -377,6 +403,7 @@ qca8k_parse_port_leds(struct qca8k_priv
+ port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
+ port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
+ port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
++ port_led->cdev.hw_control_get_device = qca8k_cled_hw_control_get_device;
+ port_led->cdev.hw_control_trigger = "netdev";
+ init_data.default_label = ":port";
+ init_data.fwnode = led;
# CONFIG_NET_DSA_MV88E6XXX_NEED_PPU 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
# CONFIG_NET_DSA_REALTEK_SMI is not set
# CONFIG_NET_DSA_SJA1105 is not set
# CONFIG_NET_DSA_SMSC_LAN9303_I2C is not set
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -61,6 +61,80 @@ config SFP
+@@ -62,6 +62,80 @@ config SFP
depends on HWMON || HWMON=n
select MDIO_I2C
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
-@@ -1748,6 +1748,9 @@ void phy_detach(struct phy_device *phyde
+@@ -1751,6 +1751,9 @@ void phy_detach(struct phy_device *phyde
struct module *ndev_owner = NULL;
struct mii_bus *bus;
sysfs_remove_link(&dev->dev.kobj, "phydev");
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -823,6 +823,12 @@ struct phy_driver {
+@@ -843,6 +843,12 @@ struct phy_driver {
/** @handle_interrupt: Override default interrupt handling */
irqreturn_t (*handle_interrupt)(struct phy_device *phydev);
phylink_set(pl->supported, 10baseT_Full);
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -138,6 +138,7 @@ typedef enum {
+@@ -139,6 +139,7 @@ typedef enum {
PHY_INTERFACE_MODE_XGMII,
PHY_INTERFACE_MODE_XLGMII,
PHY_INTERFACE_MODE_MOCA,
PHY_INTERFACE_MODE_QSGMII,
PHY_INTERFACE_MODE_TRGMII,
PHY_INTERFACE_MODE_100BASEX,
-@@ -243,6 +244,8 @@ static inline const char *phy_modes(phy_
+@@ -244,6 +245,8 @@ static inline const char *phy_modes(phy_
return "xlgmii";
case PHY_INTERFACE_MODE_MOCA:
return "moca";
--- a/drivers/net/dsa/qca/Kconfig
+++ b/drivers/net/dsa/qca/Kconfig
-@@ -15,3 +15,13 @@ config NET_DSA_QCA8K
+@@ -23,3 +23,13 @@ config NET_DSA_QCA8K_LEDS_SUPPORT
help
- This enables support for the Qualcomm Atheros QCA8K Ethernet
- switch chips.
+ This enabled support for LEDs present on the Qualcomm Atheros
+ QCA8K Ethernet switch chips.
+
+config NET_DSA_QCA8K_IPQ4019
+ tristate "Qualcomm Atheros IPQ4019 built-in Ethernet switch support"
+
--- a/drivers/net/dsa/qca/Makefile
+++ b/drivers/net/dsa/qca/Makefile
-@@ -1,4 +1,5 @@
+@@ -1,5 +1,6 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NET_DSA_AR9331) += ar9331.o
+obj-$(CONFIG_NET_DSA_QCA8K_IPQ4019) += qca8k-ipq4019.o
obj-$(CONFIG_NET_DSA_QCA8K) += qca8k.o
qca8k-y += qca8k-common.o qca8k-8xxx.o
+ ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -346,6 +346,12 @@ config AT803X_PHY
+@@ -347,6 +347,12 @@ config AT803X_PHY
Currently supports the AR8030, AR8031, AR8033, AR8035 and internal
QCA8337(Internal qca8k PHY) model
CONFIG_NET_DEVLINK=y
CONFIG_NET_DSA=y
CONFIG_NET_DSA_QCA8K=y
+CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT=y
CONFIG_NET_DSA_TAG_QCA=y
CONFIG_NET_FLOW_LIMIT=y
CONFIG_NET_PTP_CLASSIFY=y
break;
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -152,6 +152,7 @@ typedef enum {
+@@ -153,6 +153,7 @@ typedef enum {
PHY_INTERFACE_MODE_USXGMII,
/* 10GBASE-KR - with Clause 73 AN */
PHY_INTERFACE_MODE_10GKR,
PHY_INTERFACE_MODE_MAX,
} phy_interface_t;
-@@ -267,6 +268,8 @@ static inline const char *phy_modes(phy_
+@@ -268,6 +269,8 @@ static inline const char *phy_modes(phy_
return "10gbase-kr";
case PHY_INTERFACE_MODE_100BASEX:
return "100base-x";
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -366,6 +366,12 @@ config ROCKCHIP_PHY
+@@ -367,6 +367,12 @@ config ROCKCHIP_PHY
help
Currently supports the integrated Ethernet PHY.
L: linux-i2c@vger.kernel.org
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -292,6 +292,18 @@ config MEDIATEK_GE_PHY
+@@ -293,6 +293,18 @@ config MEDIATEK_GE_PHY
help
Supports the MediaTek Gigabit Ethernet PHYs.
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -304,6 +304,13 @@ config MEDIATEK_GE_SOC_PHY
+@@ -305,6 +305,13 @@ config MEDIATEK_GE_SOC_PHY
present in the SoCs efuse and will dynamically calibrate VCM
(common-mode voltage) during startup.
CONFIG_NET_DEVLINK=y
CONFIG_NET_DSA=y
CONFIG_NET_DSA_QCA8K=y
+CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT=y
CONFIG_NET_DSA_TAG_QCA=y
CONFIG_NET_SWITCHDEV=y
CONFIG_PHYLINK=y
break;
--- a/drivers/net/phy/phy_device.c
+++ b/drivers/net/phy/phy_device.c
-@@ -1032,14 +1032,16 @@ struct phy_device *phy_find_first(struct
+@@ -1035,14 +1035,16 @@ struct phy_device *phy_find_first(struct
}
EXPORT_SYMBOL(phy_find_first);
bool tx_pause, rx_pause;
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -703,7 +703,7 @@ struct phy_device {
+@@ -706,7 +706,7 @@ struct phy_device {
u8 mdix;
u8 mdix_ctrl;
break;
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -620,6 +620,7 @@ struct phy_device {
+@@ -622,6 +622,7 @@ struct phy_device {
unsigned downshifted_rate:1;
unsigned is_on_sfp_module:1;
unsigned mac_managed_pm:1;
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -943,6 +943,10 @@ struct phy_driver {
- int (*get_sqi)(struct phy_device *dev);
- /** @get_sqi_max: Get the maximum signal quality indication */
- int (*get_sqi_max)(struct phy_device *dev);
+@@ -984,6 +984,10 @@ struct phy_driver {
+ int (*led_blink_set)(struct phy_device *dev, u8 index,
+ unsigned long *delay_on,
+ unsigned long *delay_off);
+ int (*get_port)(struct phy_device *dev);
+ int (*set_port)(struct phy_device *dev, int port);
+ int (*get_eee)(struct phy_device *dev, struct ethtool_eee *e);
phylink_set(pl->supported, 10baseT_Full);
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -138,6 +138,7 @@ typedef enum {
+@@ -139,6 +139,7 @@ typedef enum {
PHY_INTERFACE_MODE_XGMII,
PHY_INTERFACE_MODE_XLGMII,
PHY_INTERFACE_MODE_MOCA,
PHY_INTERFACE_MODE_QSGMII,
PHY_INTERFACE_MODE_TRGMII,
PHY_INTERFACE_MODE_100BASEX,
-@@ -243,6 +244,8 @@ static inline const char *phy_modes(phy_
+@@ -244,6 +245,8 @@ static inline const char *phy_modes(phy_
return "xlgmii";
case PHY_INTERFACE_MODE_MOCA:
return "moca";
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -356,6 +356,12 @@ config REALTEK_PHY
+@@ -357,6 +357,12 @@ config REALTEK_PHY
help
Supports the Realtek 821x PHY.
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -279,7 +279,7 @@ static inline const char *phy_modes(phy_
+@@ -280,7 +280,7 @@ static inline const char *phy_modes(phy_
#define PHY_INIT_TIMEOUT 100000
#define PHY_FORCE_TIMEOUT 10
+MODULE_LICENSE("GPL");
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
-@@ -60,6 +60,7 @@ config SFP
+@@ -61,6 +61,7 @@ config SFP
depends on I2C && PHYLINK
depends on HWMON || HWMON=n
select MDIO_I2C
{
--- a/include/linux/phy.h
+++ b/include/linux/phy.h
-@@ -80,6 +80,7 @@ extern const int phy_10gbit_features_arr
+@@ -81,6 +81,7 @@ extern const int phy_10gbit_features_arr
#define PHY_IS_INTERNAL 0x00000001
#define PHY_RST_AFTER_CLK_EN 0x00000002
#define PHY_POLL_CABLE_TEST 0x00000004
#define MDIO_DEVICE_IS_PHY 0x80000000
/**
-@@ -420,6 +421,22 @@ struct mii_bus {
+@@ -421,6 +422,22 @@ struct mii_bus {
/** @shared: shared state across different PHYs */
struct phy_package_shared *shared[PHY_MAX_ADDR];
};
#define to_mii_bus(d) container_of(d, struct mii_bus, dev)
-@@ -1754,6 +1771,66 @@ static inline int __phy_package_read(str
+@@ -1795,6 +1812,66 @@ static inline int __phy_package_read(str
return __mdiobus_read(phydev->mdio.bus, shared->addr, regnum);
}
static inline int phy_package_write(struct phy_device *phydev,
u32 regnum, u16 val)
{
-@@ -1776,6 +1853,72 @@ static inline int __phy_package_write(st
+@@ -1817,6 +1894,72 @@ static inline int __phy_package_write(st
return __mdiobus_write(phydev->mdio.bus, shared->addr, regnum, val);
}