diff options
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 2 | ||||
-rw-r--r-- | drivers/leds/flash/leds-qcom-flash.c | 87 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 17 | ||||
-rw-r--r-- | drivers/leds/leds-is31fl319x.c | 8 | ||||
-rw-r--r-- | drivers/leds/leds-is31fl32xx.c | 47 | ||||
-rw-r--r-- | drivers/leds/leds-lp55xx-common.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-max77705.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-qnap-mcu.c | 175 |
8 files changed, 290 insertions, 50 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6e3dce7e35a4..06e6291be11b 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -674,7 +674,7 @@ config LEDS_BD2606MVV help This option enables support for BD2606MVV LED driver chips accessed via the I2C bus. It supports setting brightness, with - the limitiation that there are groups of two channels sharing + the limitation that there are groups of two channels sharing a brightness setting, but not the on/off setting. To compile this driver as a module, choose M here: the module will diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c index 89cf5120f5d5..b03a6833e3e3 100644 --- a/drivers/leds/flash/leds-qcom-flash.c +++ b/drivers/leds/flash/leds-qcom-flash.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022, 2024-2025 Qualcomm Innovation Center, Inc. All rights reserved. */ #include <linux/bitfield.h> @@ -114,36 +114,55 @@ enum { REG_THERM_THRSH1, REG_THERM_THRSH2, REG_THERM_THRSH3, + REG_TORCH_CLAMP, REG_MAX_COUNT, }; +static const struct reg_field mvflash_3ch_pmi8998_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x08, 0, 5), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xea, 0, 6), +}; + static const struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = { - REG_FIELD(0x08, 0, 7), /* status1 */ - REG_FIELD(0x09, 0, 7), /* status2 */ - REG_FIELD(0x0a, 0, 7), /* status3 */ - REG_FIELD_ID(0x40, 0, 7, 3, 1), /* chan_timer */ - REG_FIELD_ID(0x43, 0, 6, 3, 1), /* itarget */ - REG_FIELD(0x46, 7, 7), /* module_en */ - REG_FIELD(0x47, 0, 5), /* iresolution */ - REG_FIELD_ID(0x49, 0, 2, 3, 1), /* chan_strobe */ - REG_FIELD(0x4c, 0, 2), /* chan_en */ - REG_FIELD(0x56, 0, 2), /* therm_thrsh1 */ - REG_FIELD(0x57, 0, 2), /* therm_thrsh2 */ - REG_FIELD(0x58, 0, 2), /* therm_thrsh3 */ + [REG_STATUS1] = REG_FIELD(0x08, 0, 7), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xec, 0, 6), }; static const struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { - REG_FIELD(0x06, 0, 7), /* status1 */ - REG_FIELD(0x07, 0, 6), /* status2 */ - REG_FIELD(0x09, 0, 7), /* status3 */ - REG_FIELD_ID(0x3e, 0, 7, 4, 1), /* chan_timer */ - REG_FIELD_ID(0x42, 0, 6, 4, 1), /* itarget */ - REG_FIELD(0x46, 7, 7), /* module_en */ - REG_FIELD(0x49, 0, 3), /* iresolution */ - REG_FIELD_ID(0x4a, 0, 6, 4, 1), /* chan_strobe */ - REG_FIELD(0x4e, 0, 3), /* chan_en */ - REG_FIELD(0x7a, 0, 2), /* therm_thrsh1 */ - REG_FIELD(0x78, 0, 2), /* therm_thrsh2 */ + [REG_STATUS1] = REG_FIELD(0x06, 0, 7), + [REG_STATUS2] = REG_FIELD(0x07, 0, 6), + [REG_STATUS3] = REG_FIELD(0x09, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x3e, 0, 7, 4, 1), + [REG_ITARGET] = REG_FIELD_ID(0x42, 0, 6, 4, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x49, 0, 3), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x4a, 0, 6, 4, 1), + [REG_CHAN_EN] = REG_FIELD(0x4e, 0, 3), + [REG_THERM_THRSH1] = REG_FIELD(0x7a, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x78, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xed, 0, 6), }; struct qcom_flash_data { @@ -156,6 +175,7 @@ struct qcom_flash_data { u8 max_channels; u8 chan_en_bits; u8 revision; + u8 torch_clamp; }; struct qcom_flash_led { @@ -702,6 +722,7 @@ static int qcom_flash_register_led_device(struct device *dev, u32 current_ua, timeout_us; u32 channels[4]; int i, rc, count; + u8 torch_clamp; count = fwnode_property_count_u32(node, "led-sources"); if (count <= 0) { @@ -751,6 +772,12 @@ static int qcom_flash_register_led_device(struct device *dev, current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count); led->max_torch_current_ma = current_ua / UA_PER_MA; + torch_clamp = (current_ua / led->chan_count) / TORCH_IRES_UA; + if (torch_clamp != 0) + torch_clamp--; + + flash_data->torch_clamp = max_t(u8, flash_data->torch_clamp, torch_clamp); + if (fwnode_property_present(node, "flash-max-microamp")) { flash->led_cdev.flags |= LED_DEV_CAP_FLASH; @@ -851,13 +878,20 @@ static int qcom_flash_led_probe(struct platform_device *pdev) return rc; } - if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { + if (val == FLASH_SUBTYPE_3CH_PM8150_VAL) { flash_data->hw_type = QCOM_MVFLASH_3CH; flash_data->max_channels = 3; regs = devm_kmemdup(dev, mvflash_3ch_regs, sizeof(mvflash_3ch_regs), GFP_KERNEL); if (!regs) return -ENOMEM; + } else if (val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { + flash_data->hw_type = QCOM_MVFLASH_3CH; + flash_data->max_channels = 3; + regs = devm_kmemdup(dev, mvflash_3ch_pmi8998_regs, + sizeof(mvflash_3ch_pmi8998_regs), GFP_KERNEL); + if (!regs) + return -ENOMEM; } else if (val == FLASH_SUBTYPE_4CH_VAL) { flash_data->hw_type = QCOM_MVFLASH_4CH; flash_data->max_channels = 4; @@ -917,8 +951,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev) flash_data->leds_count++; } - return 0; - + return regmap_field_write(flash_data->r_fields[REG_TORCH_CLAMP], flash_data->torch_clamp); release: while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count) v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 15633fbf3c16..f3faf37f9a08 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -252,15 +252,23 @@ static const struct class leds_class = { * of_led_get() - request a LED device via the LED framework * @np: device node to get the LED device from * @index: the index of the LED + * @name: the name of the LED used to map it to its function, if present * * Returns the LED device parsed from the phandle specified in the "leds" * property of a device tree node or a negative error-code on failure. */ -static struct led_classdev *of_led_get(struct device_node *np, int index) +static struct led_classdev *of_led_get(struct device_node *np, int index, + const char *name) { struct device *led_dev; struct device_node *led_node; + /* + * For named LEDs, first look up the name in the "led-names" property. + * If it cannot be found, then of_parse_phandle() will propagate the error. + */ + if (name) + index = of_property_match_string(np, "led-names", name); led_node = of_parse_phandle(np, "leds", index); if (!led_node) return ERR_PTR(-ENOENT); @@ -324,7 +332,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, if (!dev) return ERR_PTR(-EINVAL); - led = of_led_get(dev->of_node, index); + led = of_led_get(dev->of_node, index, NULL); if (IS_ERR(led)) return led; @@ -342,9 +350,14 @@ EXPORT_SYMBOL_GPL(devm_of_led_get); struct led_classdev *led_get(struct device *dev, char *con_id) { struct led_lookup_data *lookup; + struct led_classdev *led_cdev; const char *provider = NULL; struct device *led_dev; + led_cdev = of_led_get(dev->of_node, -1, con_id); + if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT) + return led_cdev; + mutex_lock(&leds_lookup_lock); list_for_each_entry(lookup, &leds_lookup_list, list) { if (!strcmp(lookup->dev_id, dev_name(dev)) && diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 27bfab3da479..e411cee06dab 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -483,11 +483,6 @@ static inline int is31fl3196_db_to_gain(u32 dezibel) return dezibel / IS31FL3196_AUDIO_GAIN_DB_STEP; } -static void is31f1319x_mutex_destroy(void *lock) -{ - mutex_destroy(lock); -} - static int is31fl319x_probe(struct i2c_client *client) { struct is31fl319x_chip *is31; @@ -503,8 +498,7 @@ static int is31fl319x_probe(struct i2c_client *client) if (!is31) return -ENOMEM; - mutex_init(&is31->lock); - err = devm_add_action_or_reset(dev, is31f1319x_mutex_destroy, &is31->lock); + err = devm_mutex_init(dev, &is31->lock); if (err) return err; diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index 8793330dd414..dc9349f9d350 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -32,6 +32,8 @@ #define IS31FL3216_CONFIG_SSD_ENABLE BIT(7) #define IS31FL3216_CONFIG_SSD_DISABLE 0 +#define IS31FL32XX_PWM_FREQUENCY_22KHZ 0x01 + struct is31fl32xx_priv; struct is31fl32xx_led_data { struct led_classdev cdev; @@ -53,6 +55,7 @@ struct is31fl32xx_priv { * @pwm_update_reg : address of PWM Update register * @global_control_reg : address of Global Control register (optional) * @reset_reg : address of Reset register (optional) + * @output_frequency_setting_reg: address of output frequency register (optional) * @pwm_register_base : address of first PWM register * @pwm_registers_reversed: : true if PWM registers count down instead of up * @led_control_register_base : address of first LED control register (optional) @@ -76,6 +79,7 @@ struct is31fl32xx_chipdef { u8 pwm_update_reg; u8 global_control_reg; u8 reset_reg; + u8 output_frequency_setting_reg; u8 pwm_register_base; bool pwm_registers_reversed; u8 led_control_register_base; @@ -90,6 +94,19 @@ static const struct is31fl32xx_chipdef is31fl3236_cdef = { .pwm_update_reg = 0x25, .global_control_reg = 0x4a, .reset_reg = 0x4f, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, + .pwm_register_base = 0x01, + .led_control_register_base = 0x26, + .enable_bits_per_led_control_register = 1, +}; + +static const struct is31fl32xx_chipdef is31fl3236a_cdef = { + .channels = 36, + .shutdown_reg = 0x00, + .pwm_update_reg = 0x25, + .global_control_reg = 0x4a, + .reset_reg = 0x4f, + .output_frequency_setting_reg = 0x4b, .pwm_register_base = 0x01, .led_control_register_base = 0x26, .enable_bits_per_led_control_register = 1, @@ -101,6 +118,7 @@ static const struct is31fl32xx_chipdef is31fl3235_cdef = { .pwm_update_reg = 0x25, .global_control_reg = 0x4a, .reset_reg = 0x4f, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x05, .led_control_register_base = 0x2a, .enable_bits_per_led_control_register = 1, @@ -112,6 +130,7 @@ static const struct is31fl32xx_chipdef is31fl3218_cdef = { .pwm_update_reg = 0x16, .global_control_reg = IS31FL32XX_REG_NONE, .reset_reg = 0x17, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x01, .led_control_register_base = 0x13, .enable_bits_per_led_control_register = 6, @@ -126,6 +145,7 @@ static const struct is31fl32xx_chipdef is31fl3216_cdef = { .pwm_update_reg = 0xB0, .global_control_reg = IS31FL32XX_REG_NONE, .reset_reg = IS31FL32XX_REG_NONE, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x10, .pwm_registers_reversed = true, .led_control_register_base = 0x01, @@ -363,8 +383,21 @@ static struct is31fl32xx_led_data *is31fl32xx_find_led_data( static int is31fl32xx_parse_dt(struct device *dev, struct is31fl32xx_priv *priv) { + const struct is31fl32xx_chipdef *cdef = priv->cdef; int ret = 0; + if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) && + of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) { + + ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg, + IS31FL32XX_PWM_FREQUENCY_22KHZ); + + if (ret) { + dev_err(dev, "Failed to write output PWM frequency register\n"); + return ret; + } + } + for_each_available_child_of_node_scoped(dev_of_node(dev), child) { struct led_init_data init_data = {}; struct is31fl32xx_led_data *led_data = @@ -404,12 +437,13 @@ static int is31fl32xx_parse_dt(struct device *dev, } static const struct of_device_id of_is31fl32xx_match[] = { - { .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, }, - { .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, }, - { .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, }, - { .compatible = "si-en,sn3218", .data = &is31fl3218_cdef, }, - { .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, }, - { .compatible = "si-en,sn3216", .data = &is31fl3216_cdef, }, + { .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, }, + { .compatible = "issi,is31fl3236a", .data = &is31fl3236a_cdef, }, + { .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, }, + { .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, }, + { .compatible = "si-en,sn3218", .data = &is31fl3218_cdef, }, + { .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, }, + { .compatible = "si-en,sn3216", .data = &is31fl3216_cdef, }, {}, }; @@ -466,6 +500,7 @@ static void is31fl32xx_remove(struct i2c_client *client) */ static const struct i2c_device_id is31fl32xx_id[] = { { "is31fl3236" }, + { "is31fl3236a" }, { "is31fl3235" }, { "is31fl3218" }, { "sn3218" }, diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index e71456a56ab8..fd447eb7eb15 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -212,7 +212,7 @@ int lp55xx_update_program_memory(struct lp55xx_chip *chip, * For LED chip that support page, PAGE is already set in load_engine. */ if (!cfg->pages_per_engine) - start_addr += LP55xx_BYTES_PER_PAGE * idx; + start_addr += LP55xx_BYTES_PER_PAGE * (idx - 1); for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) { /* Write to the next page each 32 bytes (if supported) */ diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c index 933cb4f19be9..b7403b3fcf5e 100644 --- a/drivers/leds/leds-max77705.c +++ b/drivers/leds/leds-max77705.c @@ -180,7 +180,7 @@ static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fw ret = fwnode_property_read_u32(np, "reg", ®); if (ret || reg >= MAX77705_LED_NUM_LEDS) - ret = -EINVAL; + return -EINVAL; info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL); if (!info) diff --git a/drivers/leds/leds-qnap-mcu.c b/drivers/leds/leds-qnap-mcu.c index 4e4709456261..6df110e33ac9 100644 --- a/drivers/leds/leds-qnap-mcu.c +++ b/drivers/leds/leds-qnap-mcu.c @@ -104,9 +104,9 @@ static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, i } enum qnap_mcu_usb_led_mode { - QNAP_MCU_USB_LED_ON = 1, - QNAP_MCU_USB_LED_OFF = 3, - QNAP_MCU_USB_LED_BLINK = 2, + QNAP_MCU_USB_LED_ON = 0, + QNAP_MCU_USB_LED_OFF = 2, + QNAP_MCU_USB_LED_BLINK = 1, }; struct qnap_mcu_usb_led { @@ -137,7 +137,7 @@ static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev, * Byte 3 is shared between the usb led target on/off/blink * and also the buzzer control (in the input driver) */ - cmd[2] = 'D' + usb_led->mode; + cmd[2] = 'E' + usb_led->mode; return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); } @@ -161,7 +161,7 @@ static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev, * Byte 3 is shared between the USB LED target on/off/blink * and also the buzzer control (in the input driver) */ - cmd[2] = 'D' + usb_led->mode; + cmd[2] = 'E' + usb_led->mode; return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); } @@ -190,6 +190,166 @@ static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu) return qnap_mcu_usb_led_set(&usb_led->cdev, 0); } +enum qnap_mcu_status_led_mode { + QNAP_MCU_STATUS_LED_OFF = 0, + QNAP_MCU_STATUS_LED_ON = 1, + QNAP_MCU_STATUS_LED_BLINK_FAST = 2, /* 500ms / 500ms */ + QNAP_MCU_STATUS_LED_BLINK_SLOW = 3, /* 1s / 1s */ +}; + +struct qnap_mcu_status_led { + struct led_classdev cdev; + struct qnap_mcu_status_led *red; + u8 mode; +}; + +struct qnap_mcu_status { + struct qnap_mcu *mcu; + struct qnap_mcu_status_led red; + struct qnap_mcu_status_led green; +}; + +static inline struct qnap_mcu_status_led *cdev_to_qnap_mcu_status_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_status_led, cdev); +} + +static inline struct qnap_mcu_status *statusled_to_qnap_mcu_status(struct qnap_mcu_status_led *led) +{ + return container_of(led->red, struct qnap_mcu_status, red); +} + +static u8 qnap_mcu_status_led_encode(struct qnap_mcu_status *status) +{ + if (status->red.mode == QNAP_MCU_STATUS_LED_OFF) { + switch (status->green.mode) { + case QNAP_MCU_STATUS_LED_OFF: + return '9'; + case QNAP_MCU_STATUS_LED_ON: + return '6'; + case QNAP_MCU_STATUS_LED_BLINK_FAST: + return '5'; + case QNAP_MCU_STATUS_LED_BLINK_SLOW: + return 'A'; + } + } else if (status->green.mode == QNAP_MCU_STATUS_LED_OFF) { + switch (status->red.mode) { + case QNAP_MCU_STATUS_LED_OFF: + return '9'; + case QNAP_MCU_STATUS_LED_ON: + return '7'; + case QNAP_MCU_STATUS_LED_BLINK_FAST: + return '4'; + case QNAP_MCU_STATUS_LED_BLINK_SLOW: + return 'B'; + } + } else if (status->green.mode == QNAP_MCU_STATUS_LED_ON && + status->red.mode == QNAP_MCU_STATUS_LED_ON) { + return 'D'; + } else if (status->green.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW && + status->red.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW) { + return 'C'; + } + + /* + * Here both LEDs are on in some fashion, either both blinking fast, + * or in different speeds, so default to fast blinking for both. + */ + return '8'; +} + +static int qnap_mcu_status_led_update(struct qnap_mcu *mcu, + struct qnap_mcu_status *status) +{ + u8 cmd[] = { '@', 'C', 0 }; + + cmd[2] = qnap_mcu_status_led_encode(status); + + return qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_status_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev); + struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led); + + /* Don't disturb a possible set blink-mode if LED stays on */ + if (brightness != 0 && status_led->mode >= QNAP_MCU_STATUS_LED_BLINK_FAST) + return 0; + + status_led->mode = brightness ? QNAP_MCU_STATUS_LED_ON : + QNAP_MCU_STATUS_LED_OFF; + + return qnap_mcu_status_led_update(base->mcu, base); +} + +static int qnap_mcu_status_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev); + struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led); + + if (status_led->mode == QNAP_MCU_STATUS_LED_OFF) + return 0; + + if (*delay_on <= 500) { + *delay_on = 500; + *delay_off = 500; + status_led->mode = QNAP_MCU_STATUS_LED_BLINK_FAST; + } else { + *delay_on = 1000; + *delay_off = 1000; + status_led->mode = QNAP_MCU_STATUS_LED_BLINK_SLOW; + } + + return qnap_mcu_status_led_update(base->mcu, base); +} + +static int qnap_mcu_register_status_leds(struct device *dev, struct qnap_mcu *mcu) +{ + struct qnap_mcu_status *status; + int ret; + + status = devm_kzalloc(dev, sizeof(*status), GFP_KERNEL); + if (!status) + return -ENOMEM; + + status->mcu = mcu; + + /* + * point to the red led, so that statusled_to_qnap_mcu_status + * can resolve the main status struct containing both leds + */ + status->red.red = &status->red; + status->green.red = &status->red; + + status->red.mode = QNAP_MCU_STATUS_LED_OFF; + status->red.cdev.name = "red:status"; + status->red.cdev.brightness_set_blocking = qnap_mcu_status_led_set; + status->red.cdev.blink_set = qnap_mcu_status_led_blink_set; + status->red.cdev.brightness = 0; + status->red.cdev.max_brightness = 1; + + status->green.mode = QNAP_MCU_STATUS_LED_OFF; + status->green.cdev.name = "green:status"; + status->green.cdev.brightness_set_blocking = qnap_mcu_status_led_set; + status->green.cdev.blink_set = qnap_mcu_status_led_blink_set; + status->green.cdev.brightness = 0; + status->green.cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &status->red.cdev); + if (ret) + return ret; + + ret = devm_led_classdev_register(dev, &status->green.cdev); + if (ret) + return ret; + + return qnap_mcu_status_led_update(status->mcu, status); +} + static int qnap_mcu_leds_probe(struct platform_device *pdev) { struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); @@ -210,6 +370,11 @@ static int qnap_mcu_leds_probe(struct platform_device *pdev) "failed to register USB LED\n"); } + ret = qnap_mcu_register_status_leds(&pdev->dev, mcu); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register status LEDs\n"); + return 0; } |