xref: /linux/drivers/leds/leds-qnap-mcu.c (revision 909fd2b89f2edab8ec440a67dcf8627614e272b6)
1*2ec8bb47SHeiko Stuebner // SPDX-License-Identifier: GPL-2.0-only
2*2ec8bb47SHeiko Stuebner /*
3*2ec8bb47SHeiko Stuebner  * Driver for LEDs found on QNAP MCU devices
4*2ec8bb47SHeiko Stuebner  *
5*2ec8bb47SHeiko Stuebner  * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
6*2ec8bb47SHeiko Stuebner  */
7*2ec8bb47SHeiko Stuebner 
8*2ec8bb47SHeiko Stuebner #include <linux/leds.h>
9*2ec8bb47SHeiko Stuebner #include <linux/mfd/qnap-mcu.h>
10*2ec8bb47SHeiko Stuebner #include <linux/module.h>
11*2ec8bb47SHeiko Stuebner #include <linux/platform_device.h>
12*2ec8bb47SHeiko Stuebner #include <linux/slab.h>
13*2ec8bb47SHeiko Stuebner #include <uapi/linux/uleds.h>
14*2ec8bb47SHeiko Stuebner 
15*2ec8bb47SHeiko Stuebner enum qnap_mcu_err_led_mode {
16*2ec8bb47SHeiko Stuebner 	QNAP_MCU_ERR_LED_ON = 0,
17*2ec8bb47SHeiko Stuebner 	QNAP_MCU_ERR_LED_OFF = 1,
18*2ec8bb47SHeiko Stuebner 	QNAP_MCU_ERR_LED_BLINK_FAST = 2,
19*2ec8bb47SHeiko Stuebner 	QNAP_MCU_ERR_LED_BLINK_SLOW = 3,
20*2ec8bb47SHeiko Stuebner };
21*2ec8bb47SHeiko Stuebner 
22*2ec8bb47SHeiko Stuebner struct qnap_mcu_err_led {
23*2ec8bb47SHeiko Stuebner 	struct qnap_mcu *mcu;
24*2ec8bb47SHeiko Stuebner 	struct led_classdev cdev;
25*2ec8bb47SHeiko Stuebner 	char name[LED_MAX_NAME_SIZE];
26*2ec8bb47SHeiko Stuebner 	u8 num;
27*2ec8bb47SHeiko Stuebner 	u8 mode;
28*2ec8bb47SHeiko Stuebner };
29*2ec8bb47SHeiko Stuebner 
30*2ec8bb47SHeiko Stuebner static inline struct qnap_mcu_err_led *
31*2ec8bb47SHeiko Stuebner 		cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev)
32*2ec8bb47SHeiko Stuebner {
33*2ec8bb47SHeiko Stuebner 	return container_of(led_cdev, struct qnap_mcu_err_led, cdev);
34*2ec8bb47SHeiko Stuebner }
35*2ec8bb47SHeiko Stuebner 
36*2ec8bb47SHeiko Stuebner static int qnap_mcu_err_led_set(struct led_classdev *led_cdev,
37*2ec8bb47SHeiko Stuebner 				enum led_brightness brightness)
38*2ec8bb47SHeiko Stuebner {
39*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
40*2ec8bb47SHeiko Stuebner 	u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
41*2ec8bb47SHeiko Stuebner 
42*2ec8bb47SHeiko Stuebner 	/* Don't disturb a possible set blink-mode if LED stays on */
43*2ec8bb47SHeiko Stuebner 	if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST)
44*2ec8bb47SHeiko Stuebner 		return 0;
45*2ec8bb47SHeiko Stuebner 
46*2ec8bb47SHeiko Stuebner 	err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF;
47*2ec8bb47SHeiko Stuebner 	cmd[3] = '0' + err_led->mode;
48*2ec8bb47SHeiko Stuebner 
49*2ec8bb47SHeiko Stuebner 	return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
50*2ec8bb47SHeiko Stuebner }
51*2ec8bb47SHeiko Stuebner 
52*2ec8bb47SHeiko Stuebner static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev,
53*2ec8bb47SHeiko Stuebner 				      unsigned long *delay_on,
54*2ec8bb47SHeiko Stuebner 				      unsigned long *delay_off)
55*2ec8bb47SHeiko Stuebner {
56*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
57*2ec8bb47SHeiko Stuebner 	u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
58*2ec8bb47SHeiko Stuebner 
59*2ec8bb47SHeiko Stuebner 	/* LED is off, nothing to do */
60*2ec8bb47SHeiko Stuebner 	if (err_led->mode == QNAP_MCU_ERR_LED_OFF)
61*2ec8bb47SHeiko Stuebner 		return 0;
62*2ec8bb47SHeiko Stuebner 
63*2ec8bb47SHeiko Stuebner 	if (*delay_on < 500) {
64*2ec8bb47SHeiko Stuebner 		*delay_on = 100;
65*2ec8bb47SHeiko Stuebner 		*delay_off = 100;
66*2ec8bb47SHeiko Stuebner 		err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST;
67*2ec8bb47SHeiko Stuebner 	} else {
68*2ec8bb47SHeiko Stuebner 		*delay_on = 500;
69*2ec8bb47SHeiko Stuebner 		*delay_off = 500;
70*2ec8bb47SHeiko Stuebner 		err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW;
71*2ec8bb47SHeiko Stuebner 	}
72*2ec8bb47SHeiko Stuebner 
73*2ec8bb47SHeiko Stuebner 	cmd[3] = '0' + err_led->mode;
74*2ec8bb47SHeiko Stuebner 
75*2ec8bb47SHeiko Stuebner 	return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
76*2ec8bb47SHeiko Stuebner }
77*2ec8bb47SHeiko Stuebner 
78*2ec8bb47SHeiko Stuebner static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led)
79*2ec8bb47SHeiko Stuebner {
80*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_err_led *err_led;
81*2ec8bb47SHeiko Stuebner 	int ret;
82*2ec8bb47SHeiko Stuebner 
83*2ec8bb47SHeiko Stuebner 	err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL);
84*2ec8bb47SHeiko Stuebner 	if (!err_led)
85*2ec8bb47SHeiko Stuebner 		return -ENOMEM;
86*2ec8bb47SHeiko Stuebner 
87*2ec8bb47SHeiko Stuebner 	err_led->mcu = mcu;
88*2ec8bb47SHeiko Stuebner 	err_led->num = num_err_led;
89*2ec8bb47SHeiko Stuebner 	err_led->mode = QNAP_MCU_ERR_LED_OFF;
90*2ec8bb47SHeiko Stuebner 
91*2ec8bb47SHeiko Stuebner 	scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1);
92*2ec8bb47SHeiko Stuebner 	err_led->cdev.name = err_led->name;
93*2ec8bb47SHeiko Stuebner 
94*2ec8bb47SHeiko Stuebner 	err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set;
95*2ec8bb47SHeiko Stuebner 	err_led->cdev.blink_set = qnap_mcu_err_led_blink_set;
96*2ec8bb47SHeiko Stuebner 	err_led->cdev.brightness = 0;
97*2ec8bb47SHeiko Stuebner 	err_led->cdev.max_brightness = 1;
98*2ec8bb47SHeiko Stuebner 
99*2ec8bb47SHeiko Stuebner 	ret = devm_led_classdev_register(dev, &err_led->cdev);
100*2ec8bb47SHeiko Stuebner 	if (ret)
101*2ec8bb47SHeiko Stuebner 		return ret;
102*2ec8bb47SHeiko Stuebner 
103*2ec8bb47SHeiko Stuebner 	return qnap_mcu_err_led_set(&err_led->cdev, 0);
104*2ec8bb47SHeiko Stuebner }
105*2ec8bb47SHeiko Stuebner 
106*2ec8bb47SHeiko Stuebner enum qnap_mcu_usb_led_mode {
107*2ec8bb47SHeiko Stuebner 	QNAP_MCU_USB_LED_ON = 1,
108*2ec8bb47SHeiko Stuebner 	QNAP_MCU_USB_LED_OFF = 3,
109*2ec8bb47SHeiko Stuebner 	QNAP_MCU_USB_LED_BLINK = 2,
110*2ec8bb47SHeiko Stuebner };
111*2ec8bb47SHeiko Stuebner 
112*2ec8bb47SHeiko Stuebner struct qnap_mcu_usb_led {
113*2ec8bb47SHeiko Stuebner 	struct qnap_mcu *mcu;
114*2ec8bb47SHeiko Stuebner 	struct led_classdev cdev;
115*2ec8bb47SHeiko Stuebner 	u8 mode;
116*2ec8bb47SHeiko Stuebner };
117*2ec8bb47SHeiko Stuebner 
118*2ec8bb47SHeiko Stuebner static inline struct qnap_mcu_usb_led *
119*2ec8bb47SHeiko Stuebner 		cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev)
120*2ec8bb47SHeiko Stuebner {
121*2ec8bb47SHeiko Stuebner 	return container_of(led_cdev, struct qnap_mcu_usb_led, cdev);
122*2ec8bb47SHeiko Stuebner }
123*2ec8bb47SHeiko Stuebner 
124*2ec8bb47SHeiko Stuebner static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev,
125*2ec8bb47SHeiko Stuebner 				enum led_brightness brightness)
126*2ec8bb47SHeiko Stuebner {
127*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
128*2ec8bb47SHeiko Stuebner 	u8 cmd[] = { '@', 'C', 0 };
129*2ec8bb47SHeiko Stuebner 
130*2ec8bb47SHeiko Stuebner 	/* Don't disturb a possible set blink-mode if LED stays on */
131*2ec8bb47SHeiko Stuebner 	if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK)
132*2ec8bb47SHeiko Stuebner 		return 0;
133*2ec8bb47SHeiko Stuebner 
134*2ec8bb47SHeiko Stuebner 	usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF;
135*2ec8bb47SHeiko Stuebner 
136*2ec8bb47SHeiko Stuebner 	/*
137*2ec8bb47SHeiko Stuebner 	 * Byte 3 is shared between the usb led target on/off/blink
138*2ec8bb47SHeiko Stuebner 	 * and also the buzzer control (in the input driver)
139*2ec8bb47SHeiko Stuebner 	 */
140*2ec8bb47SHeiko Stuebner 	cmd[2] = 'D' + usb_led->mode;
141*2ec8bb47SHeiko Stuebner 
142*2ec8bb47SHeiko Stuebner 	return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
143*2ec8bb47SHeiko Stuebner }
144*2ec8bb47SHeiko Stuebner 
145*2ec8bb47SHeiko Stuebner static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev,
146*2ec8bb47SHeiko Stuebner 				      unsigned long *delay_on,
147*2ec8bb47SHeiko Stuebner 				      unsigned long *delay_off)
148*2ec8bb47SHeiko Stuebner {
149*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
150*2ec8bb47SHeiko Stuebner 	u8 cmd[] = { '@', 'C', 0 };
151*2ec8bb47SHeiko Stuebner 
152*2ec8bb47SHeiko Stuebner 	/* LED is off, nothing to do */
153*2ec8bb47SHeiko Stuebner 	if (usb_led->mode == QNAP_MCU_USB_LED_OFF)
154*2ec8bb47SHeiko Stuebner 		return 0;
155*2ec8bb47SHeiko Stuebner 
156*2ec8bb47SHeiko Stuebner 	*delay_on = 250;
157*2ec8bb47SHeiko Stuebner 	*delay_off = 250;
158*2ec8bb47SHeiko Stuebner 	usb_led->mode = QNAP_MCU_USB_LED_BLINK;
159*2ec8bb47SHeiko Stuebner 
160*2ec8bb47SHeiko Stuebner 	/*
161*2ec8bb47SHeiko Stuebner 	 * Byte 3 is shared between the USB LED target on/off/blink
162*2ec8bb47SHeiko Stuebner 	 * and also the buzzer control (in the input driver)
163*2ec8bb47SHeiko Stuebner 	 */
164*2ec8bb47SHeiko Stuebner 	cmd[2] = 'D' + usb_led->mode;
165*2ec8bb47SHeiko Stuebner 
166*2ec8bb47SHeiko Stuebner 	return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
167*2ec8bb47SHeiko Stuebner }
168*2ec8bb47SHeiko Stuebner 
169*2ec8bb47SHeiko Stuebner static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu)
170*2ec8bb47SHeiko Stuebner {
171*2ec8bb47SHeiko Stuebner 	struct qnap_mcu_usb_led *usb_led;
172*2ec8bb47SHeiko Stuebner 	int ret;
173*2ec8bb47SHeiko Stuebner 
174*2ec8bb47SHeiko Stuebner 	usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL);
175*2ec8bb47SHeiko Stuebner 	if (!usb_led)
176*2ec8bb47SHeiko Stuebner 		return -ENOMEM;
177*2ec8bb47SHeiko Stuebner 
178*2ec8bb47SHeiko Stuebner 	usb_led->mcu = mcu;
179*2ec8bb47SHeiko Stuebner 	usb_led->mode = QNAP_MCU_USB_LED_OFF;
180*2ec8bb47SHeiko Stuebner 	usb_led->cdev.name = "usb:blue:disk";
181*2ec8bb47SHeiko Stuebner 	usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set;
182*2ec8bb47SHeiko Stuebner 	usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set;
183*2ec8bb47SHeiko Stuebner 	usb_led->cdev.brightness = 0;
184*2ec8bb47SHeiko Stuebner 	usb_led->cdev.max_brightness = 1;
185*2ec8bb47SHeiko Stuebner 
186*2ec8bb47SHeiko Stuebner 	ret = devm_led_classdev_register(dev, &usb_led->cdev);
187*2ec8bb47SHeiko Stuebner 	if (ret)
188*2ec8bb47SHeiko Stuebner 		return ret;
189*2ec8bb47SHeiko Stuebner 
190*2ec8bb47SHeiko Stuebner 	return qnap_mcu_usb_led_set(&usb_led->cdev, 0);
191*2ec8bb47SHeiko Stuebner }
192*2ec8bb47SHeiko Stuebner 
193*2ec8bb47SHeiko Stuebner static int qnap_mcu_leds_probe(struct platform_device *pdev)
194*2ec8bb47SHeiko Stuebner {
195*2ec8bb47SHeiko Stuebner 	struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
196*2ec8bb47SHeiko Stuebner 	const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
197*2ec8bb47SHeiko Stuebner 	int ret;
198*2ec8bb47SHeiko Stuebner 
199*2ec8bb47SHeiko Stuebner 	for (int i = 0; i < variant->num_drives; i++) {
200*2ec8bb47SHeiko Stuebner 		ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
201*2ec8bb47SHeiko Stuebner 		if (ret)
202*2ec8bb47SHeiko Stuebner 			return dev_err_probe(&pdev->dev, ret,
203*2ec8bb47SHeiko Stuebner 					"failed to register error LED %d\n", i);
204*2ec8bb47SHeiko Stuebner 	}
205*2ec8bb47SHeiko Stuebner 
206*2ec8bb47SHeiko Stuebner 	if (variant->usb_led) {
207*2ec8bb47SHeiko Stuebner 		ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
208*2ec8bb47SHeiko Stuebner 		if (ret)
209*2ec8bb47SHeiko Stuebner 			return dev_err_probe(&pdev->dev, ret,
210*2ec8bb47SHeiko Stuebner 					"failed to register USB LED\n");
211*2ec8bb47SHeiko Stuebner 	}
212*2ec8bb47SHeiko Stuebner 
213*2ec8bb47SHeiko Stuebner 	return 0;
214*2ec8bb47SHeiko Stuebner }
215*2ec8bb47SHeiko Stuebner 
216*2ec8bb47SHeiko Stuebner static struct platform_driver qnap_mcu_leds_driver = {
217*2ec8bb47SHeiko Stuebner 	.probe = qnap_mcu_leds_probe,
218*2ec8bb47SHeiko Stuebner 	.driver = {
219*2ec8bb47SHeiko Stuebner 		.name = "qnap-mcu-leds",
220*2ec8bb47SHeiko Stuebner 	},
221*2ec8bb47SHeiko Stuebner };
222*2ec8bb47SHeiko Stuebner module_platform_driver(qnap_mcu_leds_driver);
223*2ec8bb47SHeiko Stuebner 
224*2ec8bb47SHeiko Stuebner MODULE_ALIAS("platform:qnap-mcu-leds");
225*2ec8bb47SHeiko Stuebner MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
226*2ec8bb47SHeiko Stuebner MODULE_DESCRIPTION("QNAP MCU LEDs driver");
227*2ec8bb47SHeiko Stuebner MODULE_LICENSE("GPL");
228