aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch')
-rw-r--r--target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch435
1 files changed, 435 insertions, 0 deletions
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
index 0000000000..baf4c2e4ba
--- /dev/null
+++ b/target/linux/generic/backport-5.15/815-v6.4-02-net-dsa-qca8k-add-LEDs-basic-support.patch
@@ -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 */