123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- From e0256648c831af13cbfe4a1787327fcec01c2807 Mon Sep 17 00:00:00 2001
- From: Christian Marangi <[email protected]>
- 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 <[email protected]>
- Reviewed-by: Andrew Lunn <[email protected]>
- Signed-off-by: David S. Miller <[email protected]>
- ---
- 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;
|