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