generic: backport initial LEDs hw control support
[openwrt/openwrt.git] / target / linux / generic / backport-5.15 / 818-v6.5-12-net-dsa-qca8k-implement-hw_control-ops.patch
1 From e0256648c831af13cbfe4a1787327fcec01c2807 Mon Sep 17 00:00:00 2001
2 From: Christian Marangi <ansuelsmth@gmail.com>
3 Date: Mon, 29 May 2023 18:32:42 +0200
4 Subject: [PATCH 12/13] net: dsa: qca8k: implement hw_control ops
5
6 Implement hw_control ops to drive Switch LEDs based on hardware events.
7
8 Netdev trigger is the declared supported trigger for hw control
9 operation and supports the following mode:
10 - tx
11 - rx
12
13 When hw_control_set is called, LEDs are set to follow the requested
14 mode.
15 Each LEDs will blink at 4Hz by default.
16
17 Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
18 Reviewed-by: Andrew Lunn <andrew@lunn.ch>
19 Signed-off-by: David S. Miller <davem@davemloft.net>
20 ---
21 drivers/net/dsa/qca/qca8k-leds.c | 154 +++++++++++++++++++++++++++++++
22 1 file changed, 154 insertions(+)
23
24 --- a/drivers/net/dsa/qca/qca8k-leds.c
25 +++ b/drivers/net/dsa/qca/qca8k-leds.c
26 @@ -32,6 +32,43 @@ qca8k_get_enable_led_reg(int port_num, i
27 }
28
29 static int
30 +qca8k_get_control_led_reg(int port_num, int led_num, struct qca8k_led_pattern_en *reg_info)
31 +{
32 + reg_info->reg = QCA8K_LED_CTRL_REG(led_num);
33 +
34 + /* 6 total control rule:
35 + * 3 control rules for phy0-3 that applies to all their leds
36 + * 3 control rules for phy4
37 + */
38 + if (port_num == 4)
39 + reg_info->shift = QCA8K_LED_PHY4_CONTROL_RULE_SHIFT;
40 + else
41 + reg_info->shift = QCA8K_LED_PHY0123_CONTROL_RULE_SHIFT;
42 +
43 + return 0;
44 +}
45 +
46 +static int
47 +qca8k_parse_netdev(unsigned long rules, u32 *offload_trigger)
48 +{
49 + /* Parsing specific to netdev trigger */
50 + if (test_bit(TRIGGER_NETDEV_TX, &rules))
51 + *offload_trigger |= QCA8K_LED_TX_BLINK_MASK;
52 + if (test_bit(TRIGGER_NETDEV_RX, &rules))
53 + *offload_trigger |= QCA8K_LED_RX_BLINK_MASK;
54 +
55 + if (rules && !*offload_trigger)
56 + return -EOPNOTSUPP;
57 +
58 + /* Enable some default rule by default to the requested mode:
59 + * - Blink at 4Hz by default
60 + */
61 + *offload_trigger |= QCA8K_LED_BLINK_4HZ;
62 +
63 + return 0;
64 +}
65 +
66 +static int
67 qca8k_led_brightness_set(struct qca8k_led *led,
68 enum led_brightness brightness)
69 {
70 @@ -165,6 +202,119 @@ qca8k_cled_blink_set(struct led_classdev
71 }
72
73 static int
74 +qca8k_cled_trigger_offload(struct led_classdev *ldev, bool enable)
75 +{
76 + struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
77 +
78 + struct qca8k_led_pattern_en reg_info;
79 + struct qca8k_priv *priv = led->priv;
80 + u32 mask, val = QCA8K_LED_ALWAYS_OFF;
81 +
82 + qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
83 +
84 + if (enable)
85 + val = QCA8K_LED_RULE_CONTROLLED;
86 +
87 + if (led->port_num == 0 || led->port_num == 4) {
88 + mask = QCA8K_LED_PATTERN_EN_MASK;
89 + val <<= QCA8K_LED_PATTERN_EN_SHIFT;
90 + } else {
91 + mask = QCA8K_LED_PHY123_PATTERN_EN_MASK;
92 + }
93 +
94 + return regmap_update_bits(priv->regmap, reg_info.reg, mask << reg_info.shift,
95 + val << reg_info.shift);
96 +}
97 +
98 +static bool
99 +qca8k_cled_hw_control_status(struct led_classdev *ldev)
100 +{
101 + struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
102 +
103 + struct qca8k_led_pattern_en reg_info;
104 + struct qca8k_priv *priv = led->priv;
105 + u32 val;
106 +
107 + qca8k_get_enable_led_reg(led->port_num, led->led_num, &reg_info);
108 +
109 + regmap_read(priv->regmap, reg_info.reg, &val);
110 +
111 + val >>= reg_info.shift;
112 +
113 + if (led->port_num == 0 || led->port_num == 4) {
114 + val &= QCA8K_LED_PATTERN_EN_MASK;
115 + val >>= QCA8K_LED_PATTERN_EN_SHIFT;
116 + } else {
117 + val &= QCA8K_LED_PHY123_PATTERN_EN_MASK;
118 + }
119 +
120 + return val == QCA8K_LED_RULE_CONTROLLED;
121 +}
122 +
123 +static int
124 +qca8k_cled_hw_control_is_supported(struct led_classdev *ldev, unsigned long rules)
125 +{
126 + u32 offload_trigger = 0;
127 +
128 + return qca8k_parse_netdev(rules, &offload_trigger);
129 +}
130 +
131 +static int
132 +qca8k_cled_hw_control_set(struct led_classdev *ldev, unsigned long rules)
133 +{
134 + struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
135 + struct qca8k_led_pattern_en reg_info;
136 + struct qca8k_priv *priv = led->priv;
137 + u32 offload_trigger = 0;
138 + int ret;
139 +
140 + ret = qca8k_parse_netdev(rules, &offload_trigger);
141 + if (ret)
142 + return ret;
143 +
144 + ret = qca8k_cled_trigger_offload(ldev, true);
145 + if (ret)
146 + return ret;
147 +
148 + qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
149 +
150 + return regmap_update_bits(priv->regmap, reg_info.reg,
151 + QCA8K_LED_RULE_MASK << reg_info.shift,
152 + offload_trigger << reg_info.shift);
153 +}
154 +
155 +static int
156 +qca8k_cled_hw_control_get(struct led_classdev *ldev, unsigned long *rules)
157 +{
158 + struct qca8k_led *led = container_of(ldev, struct qca8k_led, cdev);
159 + struct qca8k_led_pattern_en reg_info;
160 + struct qca8k_priv *priv = led->priv;
161 + u32 val;
162 + int ret;
163 +
164 + /* With hw control not active return err */
165 + if (!qca8k_cled_hw_control_status(ldev))
166 + return -EINVAL;
167 +
168 + qca8k_get_control_led_reg(led->port_num, led->led_num, &reg_info);
169 +
170 + ret = regmap_read(priv->regmap, reg_info.reg, &val);
171 + if (ret)
172 + return ret;
173 +
174 + val >>= reg_info.shift;
175 + val &= QCA8K_LED_RULE_MASK;
176 +
177 + /* Parsing specific to netdev trigger */
178 + if (val & QCA8K_LED_TX_BLINK_MASK)
179 + set_bit(TRIGGER_NETDEV_TX, rules);
180 + if (val & QCA8K_LED_RX_BLINK_MASK)
181 + set_bit(TRIGGER_NETDEV_RX, rules);
182 +
183 + return 0;
184 +}
185 +
186 +static int
187 qca8k_parse_port_leds(struct qca8k_priv *priv, struct fwnode_handle *port, int port_num)
188 {
189 struct fwnode_handle *led = NULL, *leds = NULL;
190 @@ -224,6 +374,10 @@ qca8k_parse_port_leds(struct qca8k_priv
191 port_led->cdev.max_brightness = 1;
192 port_led->cdev.brightness_set_blocking = qca8k_cled_brightness_set_blocking;
193 port_led->cdev.blink_set = qca8k_cled_blink_set;
194 + port_led->cdev.hw_control_is_supported = qca8k_cled_hw_control_is_supported;
195 + port_led->cdev.hw_control_set = qca8k_cled_hw_control_set;
196 + port_led->cdev.hw_control_get = qca8k_cled_hw_control_get;
197 + port_led->cdev.hw_control_trigger = "netdev";
198 init_data.default_label = ":port";
199 init_data.fwnode = led;
200 init_data.devname_mandatory = true;