leds-qnap-mcu.c 10 KB

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