OSDN Git Service

net: dsa: qca8k: implement hw_control ops
authorChristian Marangi <ansuelsmth@gmail.com>
Mon, 29 May 2023 16:32:42 +0000 (18:32 +0200)
committerDavid S. Miller <davem@davemloft.net>
Wed, 31 May 2023 08:42:09 +0000 (09:42 +0100)
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

index b883692..1e0c617 100644 (file)
@@ -32,6 +32,43 @@ qca8k_get_enable_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en
 }
 
 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 *ldev,
 }
 
 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, &reg_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, &reg_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, &reg_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, &reg_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 *priv, struct fwnode_handle *port, int p
                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;