Commit | Line | Data |
---|---|---|
ea578703 KK |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (C) 2024 Linutronix GmbH */ | |
3 | ||
4 | #include <linux/bits.h> | |
5 | #include <linux/leds.h> | |
6 | #include <linux/netdevice.h> | |
7 | #include <linux/pm_runtime.h> | |
8 | #include <uapi/linux/uleds.h> | |
9 | ||
10 | #include "igc.h" | |
11 | ||
12 | #define IGC_NUM_LEDS 3 | |
13 | ||
14 | #define IGC_LEDCTL_LED0_MODE_SHIFT 0 | |
15 | #define IGC_LEDCTL_LED0_MODE_MASK GENMASK(3, 0) | |
16 | #define IGC_LEDCTL_LED0_BLINK BIT(7) | |
17 | #define IGC_LEDCTL_LED1_MODE_SHIFT 8 | |
18 | #define IGC_LEDCTL_LED1_MODE_MASK GENMASK(11, 8) | |
19 | #define IGC_LEDCTL_LED1_BLINK BIT(15) | |
20 | #define IGC_LEDCTL_LED2_MODE_SHIFT 16 | |
21 | #define IGC_LEDCTL_LED2_MODE_MASK GENMASK(19, 16) | |
22 | #define IGC_LEDCTL_LED2_BLINK BIT(23) | |
23 | ||
24 | #define IGC_LEDCTL_MODE_ON 0x00 | |
25 | #define IGC_LEDCTL_MODE_OFF 0x01 | |
26 | #define IGC_LEDCTL_MODE_LINK_10 0x05 | |
27 | #define IGC_LEDCTL_MODE_LINK_100 0x06 | |
28 | #define IGC_LEDCTL_MODE_LINK_1000 0x07 | |
29 | #define IGC_LEDCTL_MODE_LINK_2500 0x08 | |
30 | #define IGC_LEDCTL_MODE_ACTIVITY 0x0b | |
31 | ||
32 | #define IGC_SUPPORTED_MODES \ | |
33 | (BIT(TRIGGER_NETDEV_LINK_2500) | BIT(TRIGGER_NETDEV_LINK_1000) | \ | |
34 | BIT(TRIGGER_NETDEV_LINK_100) | BIT(TRIGGER_NETDEV_LINK_10) | \ | |
35 | BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) | |
36 | ||
37 | #define IGC_ACTIVITY_MODES \ | |
38 | (BIT(TRIGGER_NETDEV_RX) | BIT(TRIGGER_NETDEV_TX)) | |
39 | ||
40 | struct igc_led_classdev { | |
41 | struct net_device *netdev; | |
42 | struct led_classdev led; | |
43 | int index; | |
44 | }; | |
45 | ||
46 | #define lcdev_to_igc_ldev(lcdev) \ | |
47 | container_of(lcdev, struct igc_led_classdev, led) | |
48 | ||
49 | static void igc_led_select(struct igc_adapter *adapter, int led, | |
50 | u32 *mask, u32 *shift, u32 *blink) | |
51 | { | |
52 | switch (led) { | |
53 | case 0: | |
54 | *mask = IGC_LEDCTL_LED0_MODE_MASK; | |
55 | *shift = IGC_LEDCTL_LED0_MODE_SHIFT; | |
56 | *blink = IGC_LEDCTL_LED0_BLINK; | |
57 | break; | |
58 | case 1: | |
59 | *mask = IGC_LEDCTL_LED1_MODE_MASK; | |
60 | *shift = IGC_LEDCTL_LED1_MODE_SHIFT; | |
61 | *blink = IGC_LEDCTL_LED1_BLINK; | |
62 | break; | |
63 | case 2: | |
64 | *mask = IGC_LEDCTL_LED2_MODE_MASK; | |
65 | *shift = IGC_LEDCTL_LED2_MODE_SHIFT; | |
66 | *blink = IGC_LEDCTL_LED2_BLINK; | |
67 | break; | |
68 | default: | |
69 | *mask = *shift = *blink = 0; | |
70 | netdev_err(adapter->netdev, "Unknown LED %d selected!\n", led); | |
71 | } | |
72 | } | |
73 | ||
74 | static void igc_led_set(struct igc_adapter *adapter, int led, u32 mode, | |
75 | bool blink) | |
76 | { | |
77 | u32 shift, mask, blink_bit, ledctl; | |
78 | struct igc_hw *hw = &adapter->hw; | |
79 | ||
80 | igc_led_select(adapter, led, &mask, &shift, &blink_bit); | |
81 | ||
82 | pm_runtime_get_sync(&adapter->pdev->dev); | |
83 | mutex_lock(&adapter->led_mutex); | |
84 | ||
85 | /* Set mode */ | |
86 | ledctl = rd32(IGC_LEDCTL); | |
87 | ledctl &= ~mask; | |
88 | ledctl |= mode << shift; | |
89 | ||
90 | /* Configure blinking */ | |
91 | if (blink) | |
92 | ledctl |= blink_bit; | |
93 | else | |
94 | ledctl &= ~blink_bit; | |
95 | wr32(IGC_LEDCTL, ledctl); | |
96 | ||
97 | mutex_unlock(&adapter->led_mutex); | |
98 | pm_runtime_put(&adapter->pdev->dev); | |
99 | } | |
100 | ||
101 | static u32 igc_led_get(struct igc_adapter *adapter, int led) | |
102 | { | |
103 | u32 shift, mask, blink_bit, ledctl; | |
104 | struct igc_hw *hw = &adapter->hw; | |
105 | ||
106 | igc_led_select(adapter, led, &mask, &shift, &blink_bit); | |
107 | ||
108 | pm_runtime_get_sync(&adapter->pdev->dev); | |
109 | mutex_lock(&adapter->led_mutex); | |
110 | ledctl = rd32(IGC_LEDCTL); | |
111 | mutex_unlock(&adapter->led_mutex); | |
112 | pm_runtime_put(&adapter->pdev->dev); | |
113 | ||
114 | return (ledctl & mask) >> shift; | |
115 | } | |
116 | ||
117 | static int igc_led_brightness_set_blocking(struct led_classdev *led_cdev, | |
118 | enum led_brightness brightness) | |
119 | { | |
120 | struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); | |
121 | struct igc_adapter *adapter = netdev_priv(ldev->netdev); | |
122 | u32 mode; | |
123 | ||
124 | if (brightness) | |
125 | mode = IGC_LEDCTL_MODE_ON; | |
126 | else | |
127 | mode = IGC_LEDCTL_MODE_OFF; | |
128 | ||
129 | netdev_dbg(adapter->netdev, "Set brightness for LED %d to mode %u!\n", | |
130 | ldev->index, mode); | |
131 | ||
132 | igc_led_set(adapter, ldev->index, mode, false); | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int igc_led_hw_control_is_supported(struct led_classdev *led_cdev, | |
138 | unsigned long flags) | |
139 | { | |
140 | if (flags & ~IGC_SUPPORTED_MODES) | |
141 | return -EOPNOTSUPP; | |
142 | ||
143 | /* If Tx and Rx selected, activity can be offloaded unless some other | |
144 | * mode is selected as well. | |
145 | */ | |
146 | if ((flags & BIT(TRIGGER_NETDEV_TX)) && | |
147 | (flags & BIT(TRIGGER_NETDEV_RX)) && | |
148 | !(flags & ~IGC_ACTIVITY_MODES)) | |
149 | return 0; | |
150 | ||
151 | /* Single Rx or Tx activity is not supported. */ | |
152 | if (flags & IGC_ACTIVITY_MODES) | |
153 | return -EOPNOTSUPP; | |
154 | ||
155 | /* Only one mode can be active at a given time. */ | |
156 | if (flags & (flags - 1)) | |
157 | return -EOPNOTSUPP; | |
158 | ||
159 | return 0; | |
160 | } | |
161 | ||
162 | static int igc_led_hw_control_set(struct led_classdev *led_cdev, | |
163 | unsigned long flags) | |
164 | { | |
165 | struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); | |
166 | struct igc_adapter *adapter = netdev_priv(ldev->netdev); | |
167 | u32 mode = IGC_LEDCTL_MODE_OFF; | |
168 | bool blink = false; | |
169 | ||
170 | if (flags & BIT(TRIGGER_NETDEV_LINK_10)) | |
171 | mode = IGC_LEDCTL_MODE_LINK_10; | |
172 | if (flags & BIT(TRIGGER_NETDEV_LINK_100)) | |
173 | mode = IGC_LEDCTL_MODE_LINK_100; | |
174 | if (flags & BIT(TRIGGER_NETDEV_LINK_1000)) | |
175 | mode = IGC_LEDCTL_MODE_LINK_1000; | |
176 | if (flags & BIT(TRIGGER_NETDEV_LINK_2500)) | |
177 | mode = IGC_LEDCTL_MODE_LINK_2500; | |
178 | if ((flags & BIT(TRIGGER_NETDEV_TX)) && | |
179 | (flags & BIT(TRIGGER_NETDEV_RX))) | |
180 | mode = IGC_LEDCTL_MODE_ACTIVITY; | |
181 | ||
182 | netdev_dbg(adapter->netdev, "Set HW control for LED %d to mode %u!\n", | |
183 | ldev->index, mode); | |
184 | ||
185 | /* blink is recommended for activity */ | |
186 | if (mode == IGC_LEDCTL_MODE_ACTIVITY) | |
187 | blink = true; | |
188 | ||
189 | igc_led_set(adapter, ldev->index, mode, blink); | |
190 | ||
191 | return 0; | |
192 | } | |
193 | ||
194 | static int igc_led_hw_control_get(struct led_classdev *led_cdev, | |
195 | unsigned long *flags) | |
196 | { | |
197 | struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); | |
198 | struct igc_adapter *adapter = netdev_priv(ldev->netdev); | |
199 | u32 mode; | |
200 | ||
201 | mode = igc_led_get(adapter, ldev->index); | |
202 | ||
203 | switch (mode) { | |
204 | case IGC_LEDCTL_MODE_ACTIVITY: | |
205 | *flags = BIT(TRIGGER_NETDEV_TX) | BIT(TRIGGER_NETDEV_RX); | |
206 | break; | |
207 | case IGC_LEDCTL_MODE_LINK_10: | |
208 | *flags = BIT(TRIGGER_NETDEV_LINK_10); | |
209 | break; | |
210 | case IGC_LEDCTL_MODE_LINK_100: | |
211 | *flags = BIT(TRIGGER_NETDEV_LINK_100); | |
212 | break; | |
213 | case IGC_LEDCTL_MODE_LINK_1000: | |
214 | *flags = BIT(TRIGGER_NETDEV_LINK_1000); | |
215 | break; | |
216 | case IGC_LEDCTL_MODE_LINK_2500: | |
217 | *flags = BIT(TRIGGER_NETDEV_LINK_2500); | |
218 | break; | |
219 | } | |
220 | ||
221 | return 0; | |
222 | } | |
223 | ||
224 | static struct device *igc_led_hw_control_get_device(struct led_classdev *led_cdev) | |
225 | { | |
226 | struct igc_led_classdev *ldev = lcdev_to_igc_ldev(led_cdev); | |
227 | ||
228 | return &ldev->netdev->dev; | |
229 | } | |
230 | ||
231 | static void igc_led_get_name(struct igc_adapter *adapter, int index, char *buf, | |
232 | size_t buf_len) | |
233 | { | |
234 | snprintf(buf, buf_len, "igc-%x%x-led%d", | |
235 | pci_domain_nr(adapter->pdev->bus), | |
236 | pci_dev_id(adapter->pdev), index); | |
237 | } | |
238 | ||
c04d1b9e LW |
239 | static int igc_setup_ldev(struct igc_led_classdev *ldev, |
240 | struct net_device *netdev, int index) | |
ea578703 KK |
241 | { |
242 | struct igc_adapter *adapter = netdev_priv(netdev); | |
243 | struct led_classdev *led_cdev = &ldev->led; | |
244 | char led_name[LED_MAX_NAME_SIZE]; | |
245 | ||
246 | ldev->netdev = netdev; | |
247 | ldev->index = index; | |
248 | ||
249 | igc_led_get_name(adapter, index, led_name, LED_MAX_NAME_SIZE); | |
250 | led_cdev->name = led_name; | |
251 | led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; | |
252 | led_cdev->max_brightness = 1; | |
253 | led_cdev->brightness_set_blocking = igc_led_brightness_set_blocking; | |
254 | led_cdev->hw_control_trigger = "netdev"; | |
255 | led_cdev->hw_control_is_supported = igc_led_hw_control_is_supported; | |
256 | led_cdev->hw_control_set = igc_led_hw_control_set; | |
257 | led_cdev->hw_control_get = igc_led_hw_control_get; | |
258 | led_cdev->hw_control_get_device = igc_led_hw_control_get_device; | |
259 | ||
c04d1b9e | 260 | return led_classdev_register(&netdev->dev, led_cdev); |
ea578703 KK |
261 | } |
262 | ||
263 | int igc_led_setup(struct igc_adapter *adapter) | |
264 | { | |
265 | struct net_device *netdev = adapter->netdev; | |
ea578703 | 266 | struct igc_led_classdev *leds; |
c04d1b9e | 267 | int i, err; |
ea578703 KK |
268 | |
269 | mutex_init(&adapter->led_mutex); | |
270 | ||
c04d1b9e | 271 | leds = kcalloc(IGC_NUM_LEDS, sizeof(*leds), GFP_KERNEL); |
ea578703 KK |
272 | if (!leds) |
273 | return -ENOMEM; | |
274 | ||
c04d1b9e LW |
275 | for (i = 0; i < IGC_NUM_LEDS; i++) { |
276 | err = igc_setup_ldev(leds + i, netdev, i); | |
277 | if (err) | |
278 | goto err; | |
279 | } | |
280 | ||
281 | adapter->leds = leds; | |
ea578703 KK |
282 | |
283 | return 0; | |
c04d1b9e LW |
284 | |
285 | err: | |
286 | for (i--; i >= 0; i--) | |
287 | led_classdev_unregister(&((leds + i)->led)); | |
288 | ||
289 | kfree(leds); | |
290 | return err; | |
291 | } | |
292 | ||
293 | void igc_led_free(struct igc_adapter *adapter) | |
294 | { | |
295 | struct igc_led_classdev *leds = adapter->leds; | |
296 | int i; | |
297 | ||
298 | for (i = 0; i < IGC_NUM_LEDS; i++) | |
299 | led_classdev_unregister(&((leds + i)->led)); | |
300 | ||
301 | kfree(leds); | |
ea578703 | 302 | } |