generic: backport initial LEDs hw control support
authorChristian Marangi <ansuelsmth@gmail.com>
Sun, 4 Jun 2023 02:26:59 +0000 (04:26 +0200)
committerDaniel Golle <daniel@makrotopia.org>
Mon, 4 Sep 2023 22:00:34 +0000 (23:00 +0100)
Backport initial LEDs hw control support. Currently this is limited to
only rx/tx and link events for the netdev trigger but the API got
accepted and the additional modes are working on and will be backported
later.

Refresh every patch and add the additional config flag for QCA8K new
LEDs support.

Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
(cherry picked from commit 0a4b309f41062ef40706162ae53b6428982a0685)

49 files changed:
target/linux/bcm27xx/patches-5.15/950-0079-leds-Add-the-input-trigger-for-pwr_led.patch
target/linux/generic/backport-5.15/814-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/815-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/816-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/817-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/817-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/817-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/817-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch [new file with mode: 0644]
target/linux/generic/backport-5.15/818-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch [new file with mode: 0644]
target/linux/generic/config-5.15
target/linux/generic/hack-5.15/700-swconfig_switch_drivers.patch
target/linux/generic/pending-5.15/703-phy-add-detach-callback-to-struct-phy_driver.patch
target/linux/ipq40xx/patches-5.15/704-net-phy-define-PSGMII-PHY-interface-mode.patch
target/linux/ipq40xx/patches-5.15/705-net-dsa-add-Qualcomm-IPQ4019-built-in-switch-support.patch
target/linux/ipq40xx/patches-5.15/708-net-phy-Add-Qualcom-QCA807x-driver.patch
target/linux/ipq806x/config-5.15
target/linux/layerscape/patches-5.15/702-phy-Add-2.5G-SGMII-interface-mode.patch
target/linux/mediatek/patches-5.15/500-gsw-rtl8367s-mt7622-support.patch
target/linux/mediatek/patches-5.15/730-v6.5-net-phy-add-driver-for-MediaTek-SoC-built-in-GE-PHYs.patch
target/linux/mediatek/patches-5.15/733-net-phy-add-driver-for-MediaTek-2.5G-PHY.patch
target/linux/mpc85xx/p1010/config-default
target/linux/ramips/patches-5.15/720-Revert-net-phy-simplify-phy_link_change-arguments.patch
target/linux/ramips/patches-5.15/721-NET-no-auto-carrier-off-support.patch
target/linux/realtek/patches-5.15/703-include-linux-add-phy-ops-for-rtl838x.patch
target/linux/realtek/patches-5.15/704-include-linux-add-phy-hsgmii-mode.patch
target/linux/realtek/patches-5.15/705-add-rtl-phy.patch
target/linux/realtek/patches-5.15/705-include-linux-phy-increase-phy-address-number-for-rtl839x.patch
target/linux/realtek/patches-5.15/711-net-phy-add-an-MDIO-SMBus-library.patch
target/linux/realtek/patches-5.15/800-net-mdio-support-hardware-assisted-indirect-access.patch

index cc1202059cd2a2cb4b1d535081b0894cb71d9b30..f8fec4294b7117382ca25ff33e80e4b098da61f3 100644 (file)
@@ -156,7 +156,7 @@ See: https://github.com/raspberrypi/linux/issues/1064
 +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)
diff --git a/target/linux/generic/backport-5.15/814-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch b/target/linux/generic/backport-5.15/814-v6.3-leds-Move-led_init_default_state_get-to-the-global-h.patch
new file mode 100644 (file)
index 0000000..592111f
--- /dev/null
@@ -0,0 +1,39 @@
+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;
+ };
diff --git a/target/linux/generic/backport-5.15/815-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch b/target/linux/generic/backport-5.15/815-v6.4-01-net-dsa-qca8k-move-qca8k_port_to_phy-to-header.patch
new file mode 100644 (file)
index 0000000..dcdca90
--- /dev/null
@@ -0,0 +1,67 @@
+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;
diff --git a/target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch b/target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch
new file mode 100644 (file)
index 0000000..baf4c2e
--- /dev/null
@@ -0,0 +1,435 @@
+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, &reg_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, &reg_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 */
diff --git a/target/linux/generic/backport-5.15/815-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch b/target/linux/generic/backport-5.15/815-v6.4-03-net-dsa-qca8k-add-LEDs-blink_set-support.patch
new file mode 100644 (file)
index 0000000..231c415
--- /dev/null
@@ -0,0 +1,74 @@
+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, &reg_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;
diff --git a/target/linux/generic/backport-5.15/815-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch b/target/linux/generic/backport-5.15/815-v6.4-04-leds-Provide-stubs-for-when-CLASS_LED-NEW_LEDS-are-d.patch
new file mode 100644 (file)
index 0000000..bc905b4
--- /dev/null
@@ -0,0 +1,59 @@
+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)
diff --git a/target/linux/generic/backport-5.15/815-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch b/target/linux/generic/backport-5.15/815-v6.4-05-net-phy-Add-a-binding-for-PHY-LEDs.patch
new file mode 100644 (file)
index 0000000..3e60f91
--- /dev/null
@@ -0,0 +1,191 @@
+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
diff --git a/target/linux/generic/backport-5.15/815-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch b/target/linux/generic/backport-5.15/815-v6.4-06-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
new file mode 100644 (file)
index 0000000..f990557
--- /dev/null
@@ -0,0 +1,97 @@
+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)
diff --git a/target/linux/generic/backport-5.15/815-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch b/target/linux/generic/backport-5.15/815-v6.4-07-net-phy-marvell-Add-software-control-of-the-LEDs.patch
new file mode 100644 (file)
index 0000000..053288c
--- /dev/null
@@ -0,0 +1,112 @@
+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,
+       },
+ };
diff --git a/target/linux/generic/backport-5.15/815-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch b/target/linux/generic/backport-5.15/815-v6.4-08-net-phy-phy_device-Call-into-the-PHY-driver-to-set-L.patch
new file mode 100644 (file)
index 0000000..4814688
--- /dev/null
@@ -0,0 +1,73 @@
+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)
diff --git a/target/linux/generic/backport-5.15/815-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch b/target/linux/generic/backport-5.15/815-v6.4-09-net-phy-marvell-Implement-led_blink_set.patch
new file mode 100644 (file)
index 0000000..0f258c6
--- /dev/null
@@ -0,0 +1,104 @@
+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,
+       },
+ };
diff --git a/target/linux/generic/backport-5.15/816-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch b/target/linux/generic/backport-5.15/816-v6.4-net-phy-marvell-Fix-inconsistent-indenting-in-led_bl.patch
new file mode 100644 (file)
index 0000000..c5b611a
--- /dev/null
@@ -0,0 +1,38 @@
+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;
diff --git a/target/linux/generic/backport-5.15/817-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch b/target/linux/generic/backport-5.15/817-v6.5-02-leds-trigger-netdev-Drop-NETDEV_LED_MODE_LINKUP-from.patch
new file mode 100644 (file)
index 0000000..3170c26
--- /dev/null
@@ -0,0 +1,87 @@
+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;
+       }
diff --git a/target/linux/generic/backport-5.15/817-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch b/target/linux/generic/backport-5.15/817-v6.5-03-leds-trigger-netdev-Rename-add-namespace-to-netdev-t.patch
new file mode 100644 (file)
index 0000000..19cc1d7
--- /dev/null
@@ -0,0 +1,149 @@
+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) */
diff --git a/target/linux/generic/backport-5.15/817-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch b/target/linux/generic/backport-5.15/817-v6.5-04-leds-trigger-netdev-Convert-device-attr-to-macro.patch
new file mode 100644 (file)
index 0000000..3b45951
--- /dev/null
@@ -0,0 +1,82 @@
+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)
diff --git a/target/linux/generic/backport-5.15/817-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch b/target/linux/generic/backport-5.15/817-v6.5-05-leds-trigger-netdev-Use-mutex-instead-of-spinlocks.patch
new file mode 100644 (file)
index 0000000..180bee9
--- /dev/null
@@ -0,0 +1,106 @@
+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;
diff --git a/target/linux/generic/backport-5.15/818-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch b/target/linux/generic/backport-5.15/818-v6.5-01-leds-add-APIs-for-LEDs-hw-control.patch
new file mode 100644 (file)
index 0000000..ac18611
--- /dev/null
@@ -0,0 +1,74 @@
+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
diff --git a/target/linux/generic/backport-5.15/818-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch b/target/linux/generic/backport-5.15/818-v6.5-02-leds-add-API-to-get-attached-device-for-LED-hw-contr.patch
new file mode 100644 (file)
index 0000000..1a22172
--- /dev/null
@@ -0,0 +1,37 @@
+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
diff --git a/target/linux/generic/backport-5.15/818-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch b/target/linux/generic/backport-5.15/818-v6.5-03-Documentation-leds-leds-class-Document-new-Hardware-.patch
new file mode 100644 (file)
index 0000000..af9fb7f
--- /dev/null
@@ -0,0 +1,113 @@
+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
+ ============
diff --git a/target/linux/generic/backport-5.15/818-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch b/target/linux/generic/backport-5.15/818-v6.5-04-leds-trigger-netdev-refactor-code-setting-device-nam.patch
new file mode 100644 (file)
index 0000000..3c804c0
--- /dev/null
@@ -0,0 +1,69 @@
+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;
+ }
diff --git a/target/linux/generic/backport-5.15/818-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch b/target/linux/generic/backport-5.15/818-v6.5-05-leds-trigger-netdev-introduce-check-for-possible-hw-.patch
new file mode 100644 (file)
index 0000000..284b519
--- /dev/null
@@ -0,0 +1,54 @@
+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;
diff --git a/target/linux/generic/backport-5.15/818-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch b/target/linux/generic/backport-5.15/818-v6.5-06-leds-trigger-netdev-add-basic-check-for-hw-control-s.patch
new file mode 100644 (file)
index 0000000..09759bc
--- /dev/null
@@ -0,0 +1,42 @@
+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;
+ }
diff --git a/target/linux/generic/backport-5.15/818-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch b/target/linux/generic/backport-5.15/818-v6.5-07-leds-trigger-netdev-reject-interval-store-for-hw_con.patch
new file mode 100644 (file)
index 0000000..6634906
--- /dev/null
@@ -0,0 +1,28 @@
+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;
diff --git a/target/linux/generic/backport-5.15/818-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch b/target/linux/generic/backport-5.15/818-v6.5-08-leds-trigger-netdev-add-support-for-LED-hw-control.patch
new file mode 100644 (file)
index 0000000..52faa48
--- /dev/null
@@ -0,0 +1,107 @@
+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);
diff --git a/target/linux/generic/backport-5.15/818-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch b/target/linux/generic/backport-5.15/818-v6.5-09-leds-trigger-netdev-validate-configured-netdev.patch
new file mode 100644 (file)
index 0000000..c129ffa
--- /dev/null
@@ -0,0 +1,58 @@
+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 */
diff --git a/target/linux/generic/backport-5.15/818-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch b/target/linux/generic/backport-5.15/818-v6.5-10-leds-trigger-netdev-init-mode-if-hw-control-already-.patch
new file mode 100644 (file)
index 0000000..e20594c
--- /dev/null
@@ -0,0 +1,53 @@
+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);
diff --git a/target/linux/generic/backport-5.15/818-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch b/target/linux/generic/backport-5.15/818-v6.5-11-leds-trigger-netdev-expose-netdev-trigger-modes-in-l.patch
new file mode 100644 (file)
index 0000000..70aed85
--- /dev/null
@@ -0,0 +1,54 @@
+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);
diff --git a/target/linux/generic/backport-5.15/818-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch b/target/linux/generic/backport-5.15/818-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch
new file mode 100644 (file)
index 0000000..ad76d89
--- /dev/null
@@ -0,0 +1,200 @@
+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, &reg_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, &reg_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, &reg_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, &reg_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;
diff --git a/target/linux/generic/backport-5.15/818-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch b/target/linux/generic/backport-5.15/818-v6.5-13-net-dsa-qca8k-add-op-to-get-ports-netdev.patch
new file mode 100644 (file)
index 0000000..feb6b9e
--- /dev/null
@@ -0,0 +1,70 @@
+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;
index 5cac839f593c704a8f5bc38b55102644773481b1..6f28147ee3a0af3611fc108d0534207a4334a6de 100644 (file)
@@ -4069,6 +4069,7 @@ CONFIG_NET_CORE=y
 # 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
index 48be4400256c8d71de5b2a56c2252c65bb825ad0..9d77efaca6ef99b9f4775f837b386cd52731ffae 100644 (file)
@@ -12,7 +12,7 @@ Signed-off-by: Felix Fietkau <nbd@nbd.name>
 
 --- 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
  
index 5baccf73cb8c7d3e4f7f1d1f75dda4d1596ce579..83587b5c93153333fcd1646ccd5da8e676a498f2 100644 (file)
@@ -11,7 +11,7 @@ Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
 
 --- 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;
  
@@ -23,7 +23,7 @@ Signed-off-by: Gabor Juhos <juhosg@openwrt.org>
                        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);
  
index faac6fa145a59cb14016ee5f7902d6b344597daf..7fbf38a7e34f3132f681235b012e03aefc6e33d8 100644 (file)
@@ -50,7 +50,7 @@ Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
                        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,
@@ -58,7 +58,7 @@ Signed-off-by: Gabor Juhos <j4g8y7@gmail.com>
        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";
index d75b5e514d2837bfbc23e4bba76c9ff9a8878400..13b169e576c479a6499153dfd22ff5dea7411614 100644 (file)
@@ -32,10 +32,10 @@ Signed-off-by: Robert Marko <robert.marko@sartura.hr>
 
 --- 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"
@@ -48,9 +48,10 @@ Signed-off-by: Robert Marko <robert.marko@sartura.hr>
 +
 --- 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
index 87be5980c4aa0e4de436425aa0612884b08a6ca0..6a92a103d644ba0db62b351f4f9e30e3c1a308a6 100644 (file)
@@ -25,7 +25,7 @@ Signed-off-by: Robert Marko <robert.marko@sartura.hr>
 
 --- 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
  
index 25d63f8b04cb06d814e5364d175e470898e7d8c9..1f456b2ce9f1501563feb0eb126d80a44789d433 100644 (file)
@@ -287,6 +287,7 @@ CONFIG_NEON=y
 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
index 4a78bfe955a42f3eec3d7075bbe8b0fe234159e8..30fc56e618503cbd2188bdda11d1f23791453315 100644 (file)
@@ -34,7 +34,7 @@ Signed-off-by: Bhaskar Upadhaya <Bhaskar.Upadhaya@nxp.com>
                        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,
@@ -42,7 +42,7 @@ Signed-off-by: Bhaskar Upadhaya <Bhaskar.Upadhaya@nxp.com>
        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";
index 262df9d2cfabc1962cb71ed582032b56a9ecba8e..b8964f4e765756be2fdce1da417c7509c71b0623 100644 (file)
@@ -1,6 +1,6 @@
 --- 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.
  
index 1d8b509678e1875cf8fb0aca2708c6eb0a2aa23c..9da29755825cd10d467c554a9a04d9c1e85ed477 100644 (file)
@@ -42,7 +42,7 @@ Signed-off-by: David S. Miller <davem@davemloft.net>
  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.
  
index a2a3bab6322d2693354822109188e1235a176a06..7151eb35cc09084019735334614090b59e3d9fe9 100644 (file)
@@ -13,7 +13,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
 
 --- 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.
  
index b6d483cd80ea24c88133377eba62d0d199cac9ee..f00fb4d5fdfcb8a10dc4ce3e741080ae4a141543 100644 (file)
@@ -17,6 +17,7 @@ CONFIG_MTD_UBI_WL_THRESHOLD=4096
 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
index 204756b7515a472d24d36ac93f1ad07ecd5c3bdc..87f319abb561309fb20bcb2486e6307e4309da25 100644 (file)
@@ -71,7 +71,7 @@ still required by target/linux/ramips/files/drivers/net/ethernet/ralink/mdio.c
                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);
  
@@ -107,7 +107,7 @@ still required by target/linux/ramips/files/drivers/net/ethernet/ralink/mdio.c
        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;
  
index d80877b41f85a7470fdc59a01315afcda12eb343..5205a8f9367be457347dc405bbd57289bd3a3c5c 100644 (file)
@@ -37,7 +37,7 @@ Signed-off-by: John Crispin <blogic@openwrt.org>
                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;
index c4594980a9032dd57df01050f5a6b5d030f18dff..04a110a2339486a208137ddf75b16177fa3beb18 100644 (file)
@@ -21,10 +21,10 @@ Submitted-by: John Crispin <john@phrozen.org>
 
 --- 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);
index 1dd328b34ef4b6f69824a57fa0cbd243fa7c0f5b..f9411e47d32477cc4af00eb813756cb4f969bcdf 100644 (file)
@@ -33,7 +33,7 @@ Submitted-by: Birger Koblitz <git@birger-koblitz.de>
                        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,
@@ -41,7 +41,7 @@ Submitted-by: Birger Koblitz <git@birger-koblitz.de>
        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";
index cb80a72fe25fbece18e7eb870a0279bdc09581f8..98c5e714b1946e1cd83c8153d3956d64fee4c247 100644 (file)
@@ -14,7 +14,7 @@ Submitted-by: Birger Koblitz <mail@birger-koblitz.de>
 
 --- 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.
  
index 3c273dca66db614f966c4c28797ecf0c1ec77b52..56ce80bb20bdb38560afdb1ddacad59b7dca2247 100644 (file)
@@ -21,7 +21,7 @@ Submitted-by: John Crispin <john@phrozen.org>
 
 --- 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
  
index 4d851d05bd18c2bc31da34312eb8b2426eb9c970..767a5d8ec56b02916da4a14bf834e2fc6c46a653 100644 (file)
@@ -110,7 +110,7 @@ Signed-off-by: Antoine Tenart <antoine.tenart@bootlin.com>
 +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
index b31c5e78acca75229e6eba1e78a162919700b801..b0a8fadb5f4fbbe9f51e1be79a94e4c11e426a07 100644 (file)
@@ -657,7 +657,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
  {
 --- 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
@@ -665,7 +665,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
  #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];
@@ -688,7 +688,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
  };
  #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);
  }
  
@@ -755,7 +755,7 @@ Signed-off-by: Daniel Golle <daniel@makrotopia.org>
  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);
  }