0a4b309f41
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>
201 lines
5.6 KiB
Diff
201 lines
5.6 KiB
Diff
From e0256648c831af13cbfe4a1787327fcec01c2807 Mon Sep 17 00:00:00 2001
|
|
From: Christian Marangi <ansuelsmth@gmail.com>
|
|
Date: Mon, 29 May 2023 18:32:42 +0200
|
|
Subject: [PATCH 12/13] net: dsa: qca8k: implement hw_control ops
|
|
|
|
Implement hw_control ops to drive Switch LEDs based on hardware events.
|
|
|
|
Netdev trigger is the declared supported trigger for hw control
|
|
operation and supports the following mode:
|
|
- tx
|
|
- rx
|
|
|
|
When hw_control_set is called, LEDs are set to follow the requested
|
|
mode.
|
|
Each LEDs will blink at 4Hz by default.
|
|
|
|
Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
|
|
Reviewed-by: Andrew Lunn <andrew@lunn.ch>
|
|
Signed-off-by: David S. Miller <davem@davemloft.net>
|
|
---
|
|
drivers/net/dsa/qca/qca8k-leds.c | 154 +++++++++++++++++++++++++++++++
|
|
1 file changed, 154 insertions(+)
|
|
|
|
--- a/drivers/net/dsa/qca/qca8k-leds.c
|
|
+++ b/drivers/net/dsa/qca/qca8k-leds.c
|
|
@@ -32,6 +32,43 @@ qca8k_get_enable_led_reg(int port_num, i
|
|
}
|
|
|
|
static int
|
|
+qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
|
|
+{
|
|
+ reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
|
|
+
|
|
+ /* 6 total control rule:
|
|
+ * 3 control rules for phy0-3 that applies to all their leds
|
|
+ * 3 control rules for phy4
|
|
+ */
|
|
+ if (port_num == 4)
|
|
+ reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
|
|
+ else
|
|
+ reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_parse_netdev(unsigned long rules, u32 *offload_trigger)
|
|
+{
|
|
+ /* Parsing specific to netdev trigger */
|
|
+ if (test_bit(TRIGGER_NETDEV_TX, &rules))
|
|
+ *offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
|
|
+ if (test_bit(TRIGGER_NETDEV_RX, &rules))
|
|
+ *offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
|
|
+
|
|
+ if (rules && !*offload_trigger)
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ /* Enable some default rule by default to the requested mode:
|
|
+ * - Blink at 4Hz by default
|
|
+ */
|
|
+ *offload_trigger |= QCA8K_LED_BLINK_4HZ;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
qca8k_led_brightness_set(struct qca8k_led *led,
|
|
enum led_brightness brightness)
|
|
{
|
|
@@ -165,6 +202,119 @@ qca8k_cled_blink_set(struct led_classdev
|
|
}
|
|
|
|
static int
|
|
+qca8k_cled_trigger_offload(struct led_classdev *ldev, bool enable)
|
|
+{
|
|
+ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
|
|
+
|
|
+ struct qca8k_led_pattern_en reg_info;
|
|
+ struct qca8k_priv *priv = led->priv;
|
|
+ u32 mask, val = QCA8K_LED_ALWAYS_OFF;
|
|
+
|
|
+ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
|
|
+
|
|
+ if (enable)
|
|
+ val = QCA8K_LED_RULE_CONTROLLED;
|
|
+
|
|
+ if (led->port_num == 0 || led->port_num == 4) {
|
|
+ mask = QCA8K_LED_PATTERN_EN_MASK;
|
|
+ val <<= QCA8K_LED_PATTERN_EN_SHIFT;
|
|
+ } else {
|
|
+ mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
|
|
+ }
|
|
+
|
|
+ return regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
|
|
+ val << reg_info.shift);
|
|
+}
|
|
+
|
|
+static bool
|
|
+qca8k_cled_hw_control_status(struct led_classdev *ldev)
|
|
+{
|
|
+ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
|
|
+
|
|
+ struct qca8k_led_pattern_en reg_info;
|
|
+ struct qca8k_priv *priv = led->priv;
|
|
+ u32 val;
|
|
+
|
|
+ qca8k_get_enable_led_reg(led->port_num, led->led_num, ®_info);
|
|
+
|
|
+ regmap_read(priv->regmap, reg_info.reg, &val);
|
|
+
|
|
+ val >>= reg_info.shift;
|
|
+
|
|
+ if (led->port_num == 0 || led->port_num == 4) {
|
|
+ val &= QCA8K_LED_PATTERN_EN_MASK;
|
|
+ val >>= QCA8K_LED_PATTERN_EN_SHIFT;
|
|
+ } else {
|
|
+ val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
|
|
+ }
|
|
+
|
|
+ return val == QCA8K_LED_RULE_CONTROLLED;
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_cled_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
|
|
+{
|
|
+ u32 offload_trigger = 0;
|
|
+
|
|
+ return qca8k_parse_netdev(rules, &offload_trigger);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_cled_hw_control_set(struct led_classdev *ldev, unsigned long rules)
|
|
+{
|
|
+ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
|
|
+ struct qca8k_led_pattern_en reg_info;
|
|
+ struct qca8k_priv *priv = led->priv;
|
|
+ u32 offload_trigger = 0;
|
|
+ int ret;
|
|
+
|
|
+ ret = qca8k_parse_netdev(rules, &offload_trigger);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = qca8k_cled_trigger_offload(ldev, true);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
|
|
+
|
|
+ return regmap_update_bits(priv->regmap, reg_info.reg,
|
|
+ QCA8K_LED_RULE_MASK << reg_info.shift,
|
|
+ offload_trigger << reg_info.shift);
|
|
+}
|
|
+
|
|
+static int
|
|
+qca8k_cled_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
|
|
+{
|
|
+ struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
|
|
+ struct qca8k_led_pattern_en reg_info;
|
|
+ struct qca8k_priv *priv = led->priv;
|
|
+ u32 val;
|
|
+ int ret;
|
|
+
|
|
+ /* With hw control not active return err */
|
|
+ if (!qca8k_cled_hw_control_status(ldev))
|
|
+ return -EINVAL;
|
|
+
|
|
+ qca8k_get_control_led_reg(led->port_num, led->led_num, ®_info);
|
|
+
|
|
+ ret = regmap_read(priv->regmap, reg_info.reg, &val);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ val >>= reg_info.shift;
|
|
+ val &= QCA8K_LED_RULE_MASK;
|
|
+
|
|
+ /* Parsing specific to netdev trigger */
|
|
+ if (val & QCA8K_LED_TX_BLINK_MASK)
|
|
+ set_bit(TRIGGER_NETDEV_TX, rules);
|
|
+ if (val & QCA8K_LED_RX_BLINK_MASK)
|
|
+ set_bit(TRIGGER_NETDEV_RX, rules);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
|
|
{
|
|
struct fwnode_handle *led = NULL, *leds = NULL;
|
|
@@ -224,6 +374,10 @@ qca8k_parse_port_leds(struct qca8k_priv
|
|
port_led->cdev.max_brightness = 1;
|
|
port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
|
|
port_led->cdev.blink_set = qca8k_cled_blink_set;
|
|
+ port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
|
|
+ port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
|
|
+ port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
|
|
+ port_led->cdev.hw_control_trigger = "netdev";
|
|
init_data.default_label = ":port";
|
|
init_data.fwnode = led;
|
|
init_data.devname_mandatory = true;
|