xref: /linux/drivers/leds/leds-qnap-mcu.c (revision 909fd2b89f2edab8ec440a67dcf8627614e272b6)
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 *
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 
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 
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 
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 = 1,
108 	QNAP_MCU_USB_LED_OFF = 3,
109 	QNAP_MCU_USB_LED_BLINK = 2,
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 *
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 
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] = 'D' + usb_led->mode;
141 
142 	return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
143 }
144 
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] = 'D' + usb_led->mode;
165 
166 	return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
167 }
168 
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 static int qnap_mcu_leds_probe(struct platform_device *pdev)
194 {
195 	struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
196 	const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
197 	int ret;
198 
199 	for (int i = 0; i < variant->num_drives; i++) {
200 		ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
201 		if (ret)
202 			return dev_err_probe(&pdev->dev, ret,
203 					"failed to register error LED %d\n", i);
204 	}
205 
206 	if (variant->usb_led) {
207 		ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
208 		if (ret)
209 			return dev_err_probe(&pdev->dev, ret,
210 					"failed to register USB LED\n");
211 	}
212 
213 	return 0;
214 }
215 
216 static struct platform_driver qnap_mcu_leds_driver = {
217 	.probe = qnap_mcu_leds_probe,
218 	.driver = {
219 		.name = "qnap-mcu-leds",
220 	},
221 };
222 module_platform_driver(qnap_mcu_leds_driver);
223 
224 MODULE_ALIAS("platform:qnap-mcu-leds");
225 MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
226 MODULE_DESCRIPTION("QNAP MCU LEDs driver");
227 MODULE_LICENSE("GPL");
228