File Index
Files and directories
Sign in
FILE

leds-qnap-mcu.c

File preview with syntax highlighting.

linux-6.19.14/drivers/leds/leds-qnap-mcu.c C
  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
 15enum 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
 22struct 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
 30static inline struct qnap_mcu_err_led *
 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
 36static 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
 52static 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
 78static 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
106enum 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
112struct qnap_mcu_usb_led {
113	struct qnap_mcu *mcu;
114	struct led_classdev cdev;
115	u8 mode;
116};
117
118static inline struct qnap_mcu_usb_led *
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
124static 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
145static 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
169static 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
193enum 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
200struct qnap_mcu_status_led {
201	struct led_classdev cdev;
202	struct qnap_mcu_status_led *red;
203	u8 mode;
204};
205
206struct qnap_mcu_status {
207	struct qnap_mcu *mcu;
208	struct qnap_mcu_status_led red;
209	struct qnap_mcu_status_led green;
210};
211
212static 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
217static 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
222static 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
261static 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
271static 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
287static 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
310static 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
353static 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
381static struct platform_driver qnap_mcu_leds_driver = {
382	.probe = qnap_mcu_leds_probe,
383	.driver = {
384		.name = "qnap-mcu-leds",
385	},
386};
387module_platform_driver(qnap_mcu_leds_driver);
388
389MODULE_ALIAS("platform:qnap-mcu-leds");
390MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
391MODULE_DESCRIPTION("QNAP MCU LEDs driver");
392MODULE_LICENSE("GPL");
~/ Read-only file preview.