mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-29 10:08:59 +00:00
436 lines
13 KiB
Diff
436 lines
13 KiB
Diff
|
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 */
|