mirror of
https://github.com/openwrt/openwrt.git
synced 2024-12-23 15:32:33 +00:00
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;
|