diff options
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.patch | 435 |
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, ®_info); ++ ++ val = QCA8K_LED_ALWAYS_OFF; ++ if (brightness) ++ val = QCA8K_LED_ALWAYS_ON; ++ ++ /* HW regs to control brightness is special and port 1-2-3 ++ * are placed in a different reg. ++ * ++ * To control port 0 brightness: ++ * - the 2 bit (15, 14) of: ++ * - QCA8K_LED_CTRL0_REG for led1 ++ * - QCA8K_LED_CTRL1_REG for led2 ++ * - QCA8K_LED_CTRL2_REG for led3 ++ * ++ * To control port 4: ++ * - the 2 bit (31, 30) of: ++ * - QCA8K_LED_CTRL0_REG for led1 ++ * - QCA8K_LED_CTRL1_REG for led2 ++ * - QCA8K_LED_CTRL2_REG for led3 ++ * ++ * To control port 1: ++ * - the 2 bit at (9, 8) of QCA8K_LED_CTRL3_REG are used for led1 ++ * - the 2 bit at (11, 10) of QCA8K_LED_CTRL3_REG are used for led2 ++ * - the 2 bit at (13, 12) of QCA8K_LED_CTRL3_REG are used for led3 ++ * ++ * To control port 2: ++ * - the 2 bit at (15, 14) of QCA8K_LED_CTRL3_REG are used for led1 ++ * - the 2 bit at (17, 16) of QCA8K_LED_CTRL3_REG are used for led2 ++ * - the 2 bit at (19, 18) of QCA8K_LED_CTRL3_REG are used for led3 ++ * ++ * To control port 3: ++ * - the 2 bit at (21, 20) of QCA8K_LED_CTRL3_REG are used for led1 ++ * - the 2 bit at (23, 22) of QCA8K_LED_CTRL3_REG are used for led2 ++ * - the 2 bit at (25, 24) of QCA8K_LED_CTRL3_REG are used for led3 ++ * ++ * To abstract this and have less code, we use the port and led numm ++ * to calculate the shift and the correct reg due to this problem of ++ * not having a 1:1 map of LED with the regs. ++ */ ++ if (led->port_num == 0 || led->port_num == 4) { ++ mask = QCA8K_LED_PATTERN_EN_MASK; ++ val <<= QCA8K_LED_PATTERN_EN_SHIFT; ++ } else { ++ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK; ++ } ++ ++ return regmap_update_bits(priv->regmap, reg_info.reg, ++ mask << reg_info.shift, ++ val << reg_info.shift); ++} ++ ++static int ++qca8k_cled_brightness_set_blocking(struct led_classdev *ldev, ++ enum led_brightness brightness) ++{ ++ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev); ++ ++ return qca8k_led_brightness_set(led, brightness); ++} ++ ++static enum led_brightness ++qca8k_led_brightness_get(struct qca8k_led *led) ++{ ++ struct qca8k_led_pattern_en reg_info; ++ struct qca8k_priv *priv = led->priv; ++ u32 val; ++ int ret; ++ ++ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info); ++ ++ ret = regmap_read(priv->regmap, reg_info.reg, &val); ++ if (ret) ++ return 0; ++ ++ val >>= reg_info.shift; ++ ++ if (led->port_num == 0 || led->port_num == 4) { ++ val &= QCA8K_LED_PATTERN_EN_MASK; ++ val >>= QCA8K_LED_PATTERN_EN_SHIFT; ++ } else { ++ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK; ++ } ++ ++ /* Assume brightness ON only when the LED is set to always ON */ ++ return val == QCA8K_LED_ALWAYS_ON; ++} ++ ++static int ++qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num) ++{ ++ struct fwnode_handle *led = NULL, *leds = NULL; ++ struct led_init_data init_data = { }; ++ struct dsa_switch *ds = priv->ds; ++ enum led_default_state state; ++ struct qca8k_led *port_led; ++ int led_num, led_index; ++ int ret; ++ ++ leds = fwnode_get_named_child_node(port, "leds"); ++ if (!leds) { ++ dev_dbg(priv->dev, "No Leds node specified in device tree for port %d!\n", ++ port_num); ++ return 0; ++ } ++ ++ fwnode_for_each_child_node(leds, led) { ++ /* Reg represent the led number of the port. ++ * Each port can have at most 3 leds attached ++ * Commonly: ++ * 1. is gigabit led ++ * 2. is mbit led ++ * 3. additional status led ++ */ ++ if (fwnode_property_read_u32(led, "reg", &led_num)) ++ continue; ++ ++ if (led_num >= QCA8K_LED_PORT_COUNT) { ++ dev_warn(priv->dev, "Invalid LED reg %d defined for port %d", ++ led_num, port_num); ++ continue; ++ } ++ ++ led_index = QCA8K_LED_PORT_INDEX(port_num, led_num); ++ ++ port_led = &priv->ports_led[led_index]; ++ port_led->port_num = port_num; ++ port_led->led_num = led_num; ++ port_led->priv = priv; ++ ++ state = led_init_default_state_get(led); ++ switch (state) { ++ case LEDS_DEFSTATE_ON: ++ port_led->cdev.brightness = 1; ++ qca8k_led_brightness_set(port_led, 1); ++ break; ++ case LEDS_DEFSTATE_KEEP: ++ port_led->cdev.brightness = ++ qca8k_led_brightness_get(port_led); ++ break; ++ default: ++ port_led->cdev.brightness = 0; ++ qca8k_led_brightness_set(port_led, 0); ++ } ++ ++ port_led->cdev.max_brightness = 1; ++ port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking; ++ init_data.default_label = ":port"; ++ init_data.fwnode = led; ++ init_data.devname_mandatory = true; ++ init_data.devicename = kasprintf(GFP_KERNEL, "%s:0%d", ds->slave_mii_bus->id, ++ port_num); ++ if (!init_data.devicename) ++ return -ENOMEM; ++ ++ ret = devm_led_classdev_register_ext(priv->dev, &port_led->cdev, &init_data); ++ if (ret) ++ dev_warn(priv->dev, "Failed to init LED %d for port %d", led_num, port_num); ++ ++ kfree(init_data.devicename); ++ } ++ ++ return 0; ++} ++ ++int ++qca8k_setup_led_ctrl(struct qca8k_priv *priv) ++{ ++ struct fwnode_handle *ports, *port; ++ int port_num; ++ int ret; ++ ++ ports = device_get_named_child_node(priv->dev, "ports"); ++ if (!ports) { ++ dev_info(priv->dev, "No ports node specified in device tree!"); ++ return 0; ++ } ++ ++ fwnode_for_each_child_node(ports, port) { ++ if (fwnode_property_read_u32(port, "reg", &port_num)) ++ continue; ++ ++ /* Skip checking for CPU port 0 and CPU port 6 as not supported */ ++ if (port_num == 0 || port_num == 6) ++ continue; ++ ++ /* Each port can have at most 3 different leds attached. ++ * Switch port starts from 0 to 6, but port 0 and 6 are CPU ++ * port. The port index needs to be decreased by one to identify ++ * the correct port for LED setup. ++ */ ++ ret = qca8k_parse_port_leds(priv, port, qca8k_port_to_phy(port_num)); ++ if (ret) ++ return ret; ++ } ++ ++ return 0; ++} +--- a/drivers/net/dsa/qca/qca8k.h ++++ b/drivers/net/dsa/qca/qca8k.h +@@ -11,6 +11,7 @@ + #include <linux/delay.h> + #include <linux/regmap.h> + #include <linux/gpio.h> ++#include <linux/leds.h> + #include <linux/dsa/tag_qca.h> + + #define QCA8K_ETHERNET_MDIO_PRIORITY 7 +@@ -85,6 +86,51 @@ + #define QCA8K_MDIO_MASTER_DATA(x) FIELD_PREP(QCA8K_MDIO_MASTER_DATA_MASK, x) + #define QCA8K_MDIO_MASTER_MAX_PORTS 5 + #define QCA8K_MDIO_MASTER_MAX_REG 32 ++ ++/* LED control register */ ++#define QCA8K_LED_PORT_COUNT 3 ++#define QCA8K_LED_COUNT ((QCA8K_NUM_PORTS - QCA8K_NUM_CPU_PORTS) * QCA8K_LED_PORT_COUNT) ++#define QCA8K_LED_RULE_COUNT 6 ++#define QCA8K_LED_RULE_MAX 11 ++#define QCA8K_LED_PORT_INDEX(_phy, _led) (((_phy) * QCA8K_LED_PORT_COUNT) + (_led)) ++ ++#define QCA8K_LED_PHY123_PATTERN_EN_SHIFT(_phy, _led) ((((_phy) - 1) * 6) + 8 + (2 * (_led))) ++#define QCA8K_LED_PHY123_PATTERN_EN_MASK GENMASK(1, 0) ++ ++#define QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT 0 ++#define QCA8K_LED_PHY4_CONTROL_RULE_SHIFT 16 ++ ++#define QCA8K_LED_CTRL_REG(_i) (0x050 + (_i) * 4) ++#define QCA8K_LED_CTRL0_REG 0x50 ++#define QCA8K_LED_CTRL1_REG 0x54 ++#define QCA8K_LED_CTRL2_REG 0x58 ++#define QCA8K_LED_CTRL3_REG 0x5C ++#define QCA8K_LED_CTRL_SHIFT(_i) (((_i) % 2) * 16) ++#define QCA8K_LED_CTRL_MASK GENMASK(15, 0) ++#define QCA8K_LED_RULE_MASK GENMASK(13, 0) ++#define QCA8K_LED_BLINK_FREQ_MASK GENMASK(1, 0) ++#define QCA8K_LED_BLINK_FREQ_SHITF 0 ++#define QCA8K_LED_BLINK_2HZ 0 ++#define QCA8K_LED_BLINK_4HZ 1 ++#define QCA8K_LED_BLINK_8HZ 2 ++#define QCA8K_LED_BLINK_AUTO 3 ++#define QCA8K_LED_LINKUP_OVER_MASK BIT(2) ++#define QCA8K_LED_TX_BLINK_MASK BIT(4) ++#define QCA8K_LED_RX_BLINK_MASK BIT(5) ++#define QCA8K_LED_COL_BLINK_MASK BIT(7) ++#define QCA8K_LED_LINK_10M_EN_MASK BIT(8) ++#define QCA8K_LED_LINK_100M_EN_MASK BIT(9) ++#define QCA8K_LED_LINK_1000M_EN_MASK BIT(10) ++#define QCA8K_LED_POWER_ON_LIGHT_MASK BIT(11) ++#define QCA8K_LED_HALF_DUPLEX_MASK BIT(12) ++#define QCA8K_LED_FULL_DUPLEX_MASK BIT(13) ++#define QCA8K_LED_PATTERN_EN_MASK GENMASK(15, 14) ++#define QCA8K_LED_PATTERN_EN_SHIFT 14 ++#define QCA8K_LED_ALWAYS_OFF 0 ++#define QCA8K_LED_ALWAYS_BLINK_4HZ 1 ++#define QCA8K_LED_ALWAYS_ON 2 ++#define QCA8K_LED_RULE_CONTROLLED 3 ++ + #define QCA8K_GOL_MAC_ADDR0 0x60 + #define QCA8K_GOL_MAC_ADDR1 0x64 + #define QCA8K_MAX_FRAME_SIZE 0x78 +@@ -377,6 +423,19 @@ struct qca8k_mdio_cache { + u16 page; + }; + ++struct qca8k_led_pattern_en { ++ u32 reg; ++ u8 shift; ++}; ++ ++struct qca8k_led { ++ u8 port_num; ++ u8 led_num; ++ u16 old_rule; ++ struct qca8k_priv *priv; ++ struct led_classdev cdev; ++}; ++ + struct qca8k_priv { + u8 switch_id; + u8 switch_revision; +@@ -399,6 +458,7 @@ struct qca8k_priv { + struct qca8k_mib_eth_data mib_eth_data; + struct qca8k_mdio_cache mdio_cache; + const struct qca8k_match_data *info; ++ struct qca8k_led ports_led[QCA8K_LED_COUNT]; + }; + + struct qca8k_mib_desc { +--- /dev/null ++++ b/drivers/net/dsa/qca/qca8k_leds.h +@@ -0,0 +1,16 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++ ++#ifndef __QCA8K_LEDS_H ++#define __QCA8K_LEDS_H ++ ++/* Leds Support function */ ++#ifdef CONFIG_NET_DSA_QCA8K_LEDS_SUPPORT ++int qca8k_setup_led_ctrl(struct qca8k_priv *priv); ++#else ++static inline int qca8k_setup_led_ctrl(struct qca8k_priv *priv) ++{ ++ return 0; ++} ++#endif ++ ++#endif /* __QCA8K_LEDS_H */ |