1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Driver for LEDs found on QNAP MCU devices
4 *
5 * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
6 */
7
8 #include <linux/leds.h>
9 #include <linux/mfd/qnap-mcu.h>
10 #include <linux/module.h>
11 #include <linux/platform_device.h>
12 #include <linux/slab.h>
13 #include <uapi/linux/uleds.h>
14
15 enum qnap_mcu_err_led_mode {
16 QNAP_MCU_ERR_LED_ON = 0,
17 QNAP_MCU_ERR_LED_OFF = 1,
18 QNAP_MCU_ERR_LED_BLINK_FAST = 2,
19 QNAP_MCU_ERR_LED_BLINK_SLOW = 3,
20 };
21
22 struct qnap_mcu_err_led {
23 struct qnap_mcu *mcu;
24 struct led_classdev cdev;
25 char name[LED_MAX_NAME_SIZE];
26 u8 num;
27 u8 mode;
28 };
29
30 static inline struct qnap_mcu_err_led *
cdev_to_qnap_mcu_err_led(struct led_classdev * led_cdev)31 cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev)
32 {
33 return container_of(led_cdev, struct qnap_mcu_err_led, cdev);
34 }
35
qnap_mcu_err_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)36 static int qnap_mcu_err_led_set(struct led_classdev *led_cdev,
37 enum led_brightness brightness)
38 {
39 struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
40 u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
41
42 /* Don't disturb a possible set blink-mode if LED stays on */
43 if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST)
44 return 0;
45
46 err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF;
47 cmd[3] = '0' + err_led->mode;
48
49 return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
50 }
51
qnap_mcu_err_led_blink_set(struct led_classdev * led_cdev,unsigned long * delay_on,unsigned long * delay_off)52 static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev,
53 unsigned long *delay_on,
54 unsigned long *delay_off)
55 {
56 struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
57 u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
58
59 /* LED is off, nothing to do */
60 if (err_led->mode == QNAP_MCU_ERR_LED_OFF)
61 return 0;
62
63 if (*delay_on < 500) {
64 *delay_on = 100;
65 *delay_off = 100;
66 err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST;
67 } else {
68 *delay_on = 500;
69 *delay_off = 500;
70 err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW;
71 }
72
73 cmd[3] = '0' + err_led->mode;
74
75 return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
76 }
77
qnap_mcu_register_err_led(struct device * dev,struct qnap_mcu * mcu,int num_err_led)78 static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led)
79 {
80 struct qnap_mcu_err_led *err_led;
81 int ret;
82
83 err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL);
84 if (!err_led)
85 return -ENOMEM;
86
87 err_led->mcu = mcu;
88 err_led->num = num_err_led;
89 err_led->mode = QNAP_MCU_ERR_LED_OFF;
90
91 scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1);
92 err_led->cdev.name = err_led->name;
93
94 err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set;
95 err_led->cdev.blink_set = qnap_mcu_err_led_blink_set;
96 err_led->cdev.brightness = 0;
97 err_led->cdev.max_brightness = 1;
98
99 ret = devm_led_classdev_register(dev, &err_led->cdev);
100 if (ret)
101 return ret;
102
103 return qnap_mcu_err_led_set(&err_led->cdev, 0);
104 }
105
106 enum qnap_mcu_usb_led_mode {
107 QNAP_MCU_USB_LED_ON = 0,
108 QNAP_MCU_USB_LED_OFF = 2,
109 QNAP_MCU_USB_LED_BLINK = 1,
110 };
111
112 struct qnap_mcu_usb_led {
113 struct qnap_mcu *mcu;
114 struct led_classdev cdev;
115 u8 mode;
116 };
117
118 static inline struct qnap_mcu_usb_led *
cdev_to_qnap_mcu_usb_led(struct led_classdev * led_cdev)119 cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev)
120 {
121 return container_of(led_cdev, struct qnap_mcu_usb_led, cdev);
122 }
123
qnap_mcu_usb_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)124 static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev,
125 enum led_brightness brightness)
126 {
127 struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
128 u8 cmd[] = { '@', 'C', 0 };
129
130 /* Don't disturb a possible set blink-mode if LED stays on */
131 if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK)
132 return 0;
133
134 usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF;
135
136 /*
137 * Byte 3 is shared between the usb led target on/off/blink
138 * and also the buzzer control (in the input driver)
139 */
140 cmd[2] = 'E' + usb_led->mode;
141
142 return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
143 }
144
qnap_mcu_usb_led_blink_set(struct led_classdev * led_cdev,unsigned long * delay_on,unsigned long * delay_off)145 static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev,
146 unsigned long *delay_on,
147 unsigned long *delay_off)
148 {
149 struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
150 u8 cmd[] = { '@', 'C', 0 };
151
152 /* LED is off, nothing to do */
153 if (usb_led->mode == QNAP_MCU_USB_LED_OFF)
154 return 0;
155
156 *delay_on = 250;
157 *delay_off = 250;
158 usb_led->mode = QNAP_MCU_USB_LED_BLINK;
159
160 /*
161 * Byte 3 is shared between the USB LED target on/off/blink
162 * and also the buzzer control (in the input driver)
163 */
164 cmd[2] = 'E' + usb_led->mode;
165
166 return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
167 }
168
qnap_mcu_register_usb_led(struct device * dev,struct qnap_mcu * mcu)169 static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu)
170 {
171 struct qnap_mcu_usb_led *usb_led;
172 int ret;
173
174 usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL);
175 if (!usb_led)
176 return -ENOMEM;
177
178 usb_led->mcu = mcu;
179 usb_led->mode = QNAP_MCU_USB_LED_OFF;
180 usb_led->cdev.name = "usb:blue:disk";
181 usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set;
182 usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set;
183 usb_led->cdev.brightness = 0;
184 usb_led->cdev.max_brightness = 1;
185
186 ret = devm_led_classdev_register(dev, &usb_led->cdev);
187 if (ret)
188 return ret;
189
190 return qnap_mcu_usb_led_set(&usb_led->cdev, 0);
191 }
192
193 enum qnap_mcu_status_led_mode {
194 QNAP_MCU_STATUS_LED_OFF = 0,
195 QNAP_MCU_STATUS_LED_ON = 1,
196 QNAP_MCU_STATUS_LED_BLINK_FAST = 2, /* 500ms / 500ms */
197 QNAP_MCU_STATUS_LED_BLINK_SLOW = 3, /* 1s / 1s */
198 };
199
200 struct qnap_mcu_status_led {
201 struct led_classdev cdev;
202 struct qnap_mcu_status_led *red;
203 u8 mode;
204 };
205
206 struct qnap_mcu_status {
207 struct qnap_mcu *mcu;
208 struct qnap_mcu_status_led red;
209 struct qnap_mcu_status_led green;
210 };
211
cdev_to_qnap_mcu_status_led(struct led_classdev * led_cdev)212 static inline struct qnap_mcu_status_led *cdev_to_qnap_mcu_status_led(struct led_classdev *led_cdev)
213 {
214 return container_of(led_cdev, struct qnap_mcu_status_led, cdev);
215 }
216
statusled_to_qnap_mcu_status(struct qnap_mcu_status_led * led)217 static inline struct qnap_mcu_status *statusled_to_qnap_mcu_status(struct qnap_mcu_status_led *led)
218 {
219 return container_of(led->red, struct qnap_mcu_status, red);
220 }
221
qnap_mcu_status_led_encode(struct qnap_mcu_status * status)222 static u8 qnap_mcu_status_led_encode(struct qnap_mcu_status *status)
223 {
224 if (status->red.mode == QNAP_MCU_STATUS_LED_OFF) {
225 switch (status->green.mode) {
226 case QNAP_MCU_STATUS_LED_OFF:
227 return '9';
228 case QNAP_MCU_STATUS_LED_ON:
229 return '6';
230 case QNAP_MCU_STATUS_LED_BLINK_FAST:
231 return '5';
232 case QNAP_MCU_STATUS_LED_BLINK_SLOW:
233 return 'A';
234 }
235 } else if (status->green.mode == QNAP_MCU_STATUS_LED_OFF) {
236 switch (status->red.mode) {
237 case QNAP_MCU_STATUS_LED_OFF:
238 return '9';
239 case QNAP_MCU_STATUS_LED_ON:
240 return '7';
241 case QNAP_MCU_STATUS_LED_BLINK_FAST:
242 return '4';
243 case QNAP_MCU_STATUS_LED_BLINK_SLOW:
244 return 'B';
245 }
246 } else if (status->green.mode == QNAP_MCU_STATUS_LED_ON &&
247 status->red.mode == QNAP_MCU_STATUS_LED_ON) {
248 return 'D';
249 } else if (status->green.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW &&
250 status->red.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW) {
251 return 'C';
252 }
253
254 /*
255 * Here both LEDs are on in some fashion, either both blinking fast,
256 * or in different speeds, so default to fast blinking for both.
257 */
258 return '8';
259 }
260
qnap_mcu_status_led_update(struct qnap_mcu * mcu,struct qnap_mcu_status * status)261 static int qnap_mcu_status_led_update(struct qnap_mcu *mcu,
262 struct qnap_mcu_status *status)
263 {
264 u8 cmd[] = { '@', 'C', 0 };
265
266 cmd[2] = qnap_mcu_status_led_encode(status);
267
268 return qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd));
269 }
270
qnap_mcu_status_led_set(struct led_classdev * led_cdev,enum led_brightness brightness)271 static int qnap_mcu_status_led_set(struct led_classdev *led_cdev,
272 enum led_brightness brightness)
273 {
274 struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev);
275 struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led);
276
277 /* Don't disturb a possible set blink-mode if LED stays on */
278 if (brightness != 0 && status_led->mode >= QNAP_MCU_STATUS_LED_BLINK_FAST)
279 return 0;
280
281 status_led->mode = brightness ? QNAP_MCU_STATUS_LED_ON :
282 QNAP_MCU_STATUS_LED_OFF;
283
284 return qnap_mcu_status_led_update(base->mcu, base);
285 }
286
qnap_mcu_status_led_blink_set(struct led_classdev * led_cdev,unsigned long * delay_on,unsigned long * delay_off)287 static int qnap_mcu_status_led_blink_set(struct led_classdev *led_cdev,
288 unsigned long *delay_on,
289 unsigned long *delay_off)
290 {
291 struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev);
292 struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led);
293
294 if (status_led->mode == QNAP_MCU_STATUS_LED_OFF)
295 return 0;
296
297 if (*delay_on <= 500) {
298 *delay_on = 500;
299 *delay_off = 500;
300 status_led->mode = QNAP_MCU_STATUS_LED_BLINK_FAST;
301 } else {
302 *delay_on = 1000;
303 *delay_off = 1000;
304 status_led->mode = QNAP_MCU_STATUS_LED_BLINK_SLOW;
305 }
306
307 return qnap_mcu_status_led_update(base->mcu, base);
308 }
309
qnap_mcu_register_status_leds(struct device * dev,struct qnap_mcu * mcu)310 static int qnap_mcu_register_status_leds(struct device *dev, struct qnap_mcu *mcu)
311 {
312 struct qnap_mcu_status *status;
313 int ret;
314
315 status = devm_kzalloc(dev, sizeof(*status), GFP_KERNEL);
316 if (!status)
317 return -ENOMEM;
318
319 status->mcu = mcu;
320
321 /*
322 * point to the red led, so that statusled_to_qnap_mcu_status
323 * can resolve the main status struct containing both leds
324 */
325 status->red.red = &status->red;
326 status->green.red = &status->red;
327
328 status->red.mode = QNAP_MCU_STATUS_LED_OFF;
329 status->red.cdev.name = "red:status";
330 status->red.cdev.brightness_set_blocking = qnap_mcu_status_led_set;
331 status->red.cdev.blink_set = qnap_mcu_status_led_blink_set;
332 status->red.cdev.brightness = 0;
333 status->red.cdev.max_brightness = 1;
334
335 status->green.mode = QNAP_MCU_STATUS_LED_OFF;
336 status->green.cdev.name = "green:status";
337 status->green.cdev.brightness_set_blocking = qnap_mcu_status_led_set;
338 status->green.cdev.blink_set = qnap_mcu_status_led_blink_set;
339 status->green.cdev.brightness = 0;
340 status->green.cdev.max_brightness = 1;
341
342 ret = devm_led_classdev_register(dev, &status->red.cdev);
343 if (ret)
344 return ret;
345
346 ret = devm_led_classdev_register(dev, &status->green.cdev);
347 if (ret)
348 return ret;
349
350 return qnap_mcu_status_led_update(status->mcu, status);
351 }
352
qnap_mcu_leds_probe(struct platform_device * pdev)353 static int qnap_mcu_leds_probe(struct platform_device *pdev)
354 {
355 struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
356 const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
357 int ret;
358
359 for (int i = 0; i < variant->num_drives; i++) {
360 ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
361 if (ret)
362 return dev_err_probe(&pdev->dev, ret,
363 "failed to register error LED %d\n", i);
364 }
365
366 if (variant->usb_led) {
367 ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
368 if (ret)
369 return dev_err_probe(&pdev->dev, ret,
370 "failed to register USB LED\n");
371 }
372
373 ret = qnap_mcu_register_status_leds(&pdev->dev, mcu);
374 if (ret)
375 return dev_err_probe(&pdev->dev, ret,
376 "failed to register status LEDs\n");
377
378 return 0;
379 }
380
381 static struct platform_driver qnap_mcu_leds_driver = {
382 .probe = qnap_mcu_leds_probe,
383 .driver = {
384 .name = "qnap-mcu-leds",
385 },
386 };
387 module_platform_driver(qnap_mcu_leds_driver);
388
389 MODULE_ALIAS("platform:qnap-mcu-leds");
390 MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
391 MODULE_DESCRIPTION("QNAP MCU LEDs driver");
392 MODULE_LICENSE("GPL");
393