Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

leds: Add driver for LEDs from qnap-mcu devices

This adds a driver that connects to the qnap-mcu mfd driver and provides
access to the LEDs on it.

Signed-off-by: Heiko Stuebner <heiko@sntech.de>
Link: https://lore.kernel.org/r/20241107114712.538976-6-heiko@sntech.de
Signed-off-by: Lee Jones <lee@kernel.org>

authored by

Heiko Stuebner and committed by
Lee Jones
2ec8bb47 998f70d1

+240
+1
MAINTAINERS
··· 19108 19108 QNAP MCU DRIVER 19109 19109 M: Heiko Stuebner <heiko@sntech.de> 19110 19110 S: Maintained 19111 + F: drivers/leds/leds-qnap-mcu.c 19111 19112 F: drivers/mfd/qnap-mcu.c 19112 19113 F: include/linux/qnap-mcu.h 19113 19114
+11
drivers/leds/Kconfig
··· 580 580 LED driver chips accessed via the I2C bus. Supported 581 581 devices include PCA9955BTW, PCA9952TW and PCA9955TW. 582 582 583 + config LEDS_QNAP_MCU 584 + tristate "LED Support for QNAP MCU controllers" 585 + depends on LEDS_CLASS 586 + depends on MFD_QNAP_MCU 587 + help 588 + This option enables support for LEDs available on embedded 589 + controllers used in QNAP NAS devices. 590 + 591 + This driver can also be built as a module. If so, the module 592 + will be called qnap-mcu-leds. 593 + 583 594 config LEDS_WM831X_STATUS 584 595 tristate "LED support for status LEDs on WM831x PMICs" 585 596 depends on LEDS_CLASS
+1
drivers/leds/Makefile
··· 79 79 obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o 80 80 obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o 81 81 obj-$(CONFIG_LEDS_PWM) += leds-pwm.o 82 + obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o 82 83 obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o 83 84 obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o 84 85 obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
+227
drivers/leds/leds-qnap-mcu.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 + 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");