diff options
Diffstat (limited to 'drivers/leds')
-rw-r--r-- | drivers/leds/Kconfig | 44 | ||||
-rw-r--r-- | drivers/leds/Makefile | 4 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 6 | ||||
-rw-r--r-- | drivers/leds/led-triggers.c | 4 | ||||
-rw-r--r-- | drivers/leds/leds-aw200xx.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-cht-wcove.c | 6 | ||||
-rw-r--r-- | drivers/leds/leds-lp8860.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-lp8864.c | 296 | ||||
-rw-r--r-- | drivers/leds/leds-netxbig.c | 1 | ||||
-rw-r--r-- | drivers/leds/leds-qnap-mcu.c | 227 | ||||
-rw-r--r-- | drivers/leds/leds-st1202.c | 415 | ||||
-rw-r--r-- | drivers/leds/leds-turris-omnia.c | 336 | ||||
-rw-r--r-- | drivers/leds/leds-upboard.c | 126 | ||||
-rw-r--r-- | drivers/leds/leds.h | 4 | ||||
-rw-r--r-- | drivers/leds/rgb/leds-pwm-multicolor.c | 8 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-activity.c | 2 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-netdev.c | 2 | ||||
-rw-r--r-- | drivers/leds/trigger/ledtrig-pattern.c | 4 |
18 files changed, 1303 insertions, 186 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b784bb74a837..2b27d043921c 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -217,6 +217,8 @@ config LEDS_TURRIS_OMNIA depends on I2C depends on MACH_ARMADA_38X || COMPILE_TEST depends on OF + depends on TURRIS_OMNIA_MCU + depends on TURRIS_OMNIA_MCU_GPIO select LEDS_TRIGGERS help This option enables basic support for the LEDs found on the front @@ -511,6 +513,18 @@ config LEDS_LP8860 on the LP8860 4 channel LED driver using the I2C communication bus. +config LEDS_LP8864 + tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers" + depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C + help + If you say yes here you get support for the TI LP8864-Q1, + LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight + drivers with I2C interface. + + To compile this driver as a module, choose M here: the + module will be called leds-lp8864. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on LEDS_CLASS && BROKEN @@ -580,6 +594,17 @@ config LEDS_PCA995X LED driver chips accessed via the I2C bus. Supported devices include PCA9955BTW, PCA9952TW and PCA9955TW. +config LEDS_QNAP_MCU + tristate "LED Support for QNAP MCU controllers" + depends on LEDS_CLASS + depends on MFD_QNAP_MCU + help + This option enables support for LEDs available on embedded + controllers used in QNAP NAS devices. + + This driver can also be built as a module. If so, the module + will be called qnap-mcu-leds. + config LEDS_WM831X_STATUS tristate "LED support for status LEDs on WM831x PMICs" depends on LEDS_CLASS @@ -815,6 +840,15 @@ config LEDS_SC27XX_BLTC This driver can also be built as a module. If so the module will be called leds-sc27xx-bltc. +config LEDS_UPBOARD + tristate "LED support for the UP board" + depends on LEDS_CLASS && MFD_UPBOARD_FPGA + help + This option enables support for the UP board LEDs. + + This driver can also be built as a module. If so the module will be + called leds-upboard. + comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" config LEDS_BLINKM @@ -931,6 +965,16 @@ config LEDS_LM36274 Say Y to enable the LM36274 LED driver for TI LMU devices. This supports the LED device LM36274. +config LEDS_ST1202 + tristate "LED Support for STMicroelectronics LED1202 I2C chips" + depends on LEDS_CLASS + depends on I2C + depends on OF + select LEDS_TRIGGERS + help + Say Y to enable support for LEDs connected to LED1202 + LED driver chips accessed via the I2C bus. + config LEDS_TPS6105X tristate "LED support for TI TPS6105X" depends on LEDS_CLASS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 18afbb5a23ee..6ad52e219ec6 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o +obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o @@ -79,8 +80,10 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o +obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o +obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o @@ -89,6 +92,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o +obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 2a04ac61574d..c20ac8ccf52b 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -85,13 +85,13 @@ static ssize_t max_brightness_show(struct device *dev, static DEVICE_ATTR_RO(max_brightness); #ifdef CONFIG_LEDS_TRIGGERS -static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); -static struct bin_attribute *led_trigger_bin_attrs[] = { +static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0); +static const struct bin_attribute *const led_trigger_bin_attrs[] = { &bin_attr_trigger, NULL, }; static const struct attribute_group led_trigger_group = { - .bin_attrs = led_trigger_bin_attrs, + .bin_attrs_new = led_trigger_bin_attrs, }; #endif diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 78eb20093b2c..b2d40f87a5ff 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -34,7 +34,7 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig) } ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); @@ -123,7 +123,7 @@ static int led_trigger_format(char *buf, size_t size, * copy it. */ ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev = kobj_to_dev(kobj); diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 08cca128458c..fe223d363a5d 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -379,7 +379,7 @@ static void aw200xx_enable(const struct aw200xx *const chip) static void aw200xx_disable(const struct aw200xx *const chip) { - return gpiod_set_value_cansleep(chip->hwen, 0); + gpiod_set_value_cansleep(chip->hwen, 0); } static int aw200xx_probe_get_display_rows(struct device *dev, diff --git a/drivers/leds/leds-cht-wcove.c b/drivers/leds/leds-cht-wcove.c index 8246f048edcb..9a609dd5acdc 100644 --- a/drivers/leds/leds-cht-wcove.c +++ b/drivers/leds/leds-cht-wcove.c @@ -394,7 +394,7 @@ static int cht_wc_leds_probe(struct platform_device *pdev) led->cdev.pattern_clear = cht_wc_leds_pattern_clear; led->cdev.max_brightness = 255; - ret = led_classdev_register(&pdev->dev, &led->cdev); + ret = devm_led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) return ret; } @@ -406,10 +406,6 @@ static int cht_wc_leds_probe(struct platform_device *pdev) static void cht_wc_leds_remove(struct platform_device *pdev) { struct cht_wc_leds *leds = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < CHT_WC_LED_COUNT; i++) - led_classdev_unregister(&leds->leds[i].cdev); /* Restore LED1 regs if hw-control was active else leave LED1 off */ if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL)) diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 7a136fd81720..06196d851ade 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -265,7 +265,7 @@ static int lp8860_init(struct lp8860_led *led) goto out; } - reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]); + reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs); for (i = 0; i < reg_count; i++) { ret = regmap_write(led->eeprom_regmap, lp8860_eeprom_disp_regs[i].reg, diff --git a/drivers/leds/leds-lp8864.c b/drivers/leds/leds-lp8864.c new file mode 100644 index 000000000000..3afd729d2f8a --- /dev/null +++ b/drivers/leds/leds-lp8864.c @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * TI LP8864/LP8866 4/6 Channel LED Driver + * + * Copyright (C) 2024 Siemens AG + * + * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com> + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> +#include <linux/slab.h> + +#define LP8864_BRT_CONTROL 0x00 +#define LP8864_USER_CONFIG1 0x04 +#define LP8864_BRT_MODE_MASK GENMASK(9, 8) +#define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */ +#define LP8864_SUPPLY_STATUS 0x0e +#define LP8864_BOOST_STATUS 0x10 +#define LP8864_LED_STATUS 0x12 +#define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */ + +/* Textual meaning for status bits, starting from bit 1 */ +static const char *const lp8864_supply_status_msg[] = { + "Vin under-voltage fault", + "Vin over-voltage fault", + "Vdd under-voltage fault", + "Vin over-current fault", + "Missing charge pump fault", + "Charge pump fault", + "Missing boost sync fault", + "CRC error fault ", +}; + +/* Textual meaning for status bits, starting from bit 1 */ +static const char *const lp8864_boost_status_msg[] = { + "Boost OVP low fault", + "Boost OVP high fault", + "Boost over-current fault", + "Missing boost FSET resistor fault", + "Missing MODE SEL resistor fault", + "Missing LED resistor fault", + "ISET resistor short to ground fault", + "Thermal shutdown fault", +}; + +/* Textual meaning for every register bit */ +static const char *const lp8864_led_status_msg[] = { + "LED 1 fault", + "LED 2 fault", + "LED 3 fault", + "LED 4 fault", + "LED 5 fault", + "LED 6 fault", + "LED open fault", + "LED internal short fault", + "LED short to GND fault", + NULL, NULL, NULL, + "Invalid string configuration fault", + NULL, + "I2C time out fault", +}; + +/** + * struct lp8864_led + * @client: Pointer to the I2C client + * @led_dev: led class device pointer + * @regmap: Devices register map + * @led_status_mask: Helps to report LED fault only once + */ +struct lp8864_led { + struct i2c_client *client; + struct led_classdev led_dev; + struct regmap *regmap; + u16 led_status_mask; +}; + +static int lp8864_fault_check(struct lp8864_led *led) +{ + int ret, i; + unsigned int val; + + ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val); + if (ret) + goto err; + + /* Odd bits are status bits, even bits are clear bits */ + for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++) + if (val & BIT(i * 2 + 1)) + dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]); + + /* + * Clear bits have an index preceding the corresponding Status bits; + * both have to be written "1" simultaneously to clear the corresponding + * Status bit. + */ + if (val) + ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val); + if (ret) + goto err; + + ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val); + if (ret) + goto err; + + /* Odd bits are status bits, even bits are clear bits */ + for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++) + if (val & BIT(i * 2 + 1)) + dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]); + + if (val) + ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val); + if (ret) + goto err; + + ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val); + if (ret) + goto err; + + /* + * Clear already reported faults that maintain their value until device + * power-down + */ + val &= ~led->led_status_mask; + + for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++) + if (lp8864_led_status_msg[i] && val & BIT(i)) + dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]); + + /* + * Mark those which maintain their value until device power-down as + * "already reported" + */ + led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK; + + /* + * Only bits 14, 12, 10 have to be cleared here, but others are RO, + * we don't care what we write to them. + */ + if (val & LP8864_LED_STATUS_WR_MASK) + ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val); + if (ret) + goto err; + + return 0; + +err: + dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static int lp8864_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); + /* Scale 0..LED_FULL into 16-bit HW brightness */ + unsigned int val = brt_val * 0xffff / LED_FULL; + int ret; + + ret = lp8864_fault_check(led); + if (ret) + return ret; + + ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val); + if (ret) + dev_err(&led->client->dev, "Failed to write brightness value\n"); + + return ret; +} + +static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev) +{ + struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev); + unsigned int val; + int ret; + + ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val); + if (ret) { + dev_err(&led->client->dev, "Failed to read brightness value\n"); + return ret; + } + + /* Scale 16-bit HW brightness into 0..LED_FULL */ + return val * LED_FULL / 0xffff; +} + +static const struct regmap_config lp8864_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_LITTLE, +}; + +static void lp8864_disable_gpio(void *data) +{ + struct gpio_desc *gpio = data; + + gpiod_set_value(gpio, 0); +} + +static int lp8864_probe(struct i2c_client *client) +{ + int ret; + struct lp8864_led *led; + struct device_node *np = dev_of_node(&client->dev); + struct device_node *child_node; + struct led_init_data init_data = {}; + struct gpio_desc *enable_gpio; + + led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + child_node = of_get_next_available_child(np, NULL); + if (!child_node) { + dev_err(&client->dev, "No LED function defined\n"); + return -EINVAL; + } + + ret = devm_regulator_get_enable_optional(&client->dev, "vled"); + if (ret && ret != -ENODEV) + return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n"); + + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH); + if (IS_ERR(enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(enable_gpio), + "Failed to get enable GPIO\n"); + + ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio); + if (ret) + return ret; + + led->client = client; + led->led_dev.brightness_set_blocking = lp8864_brightness_set; + led->led_dev.brightness_get = lp8864_brightness_get; + + led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config); + if (IS_ERR(led->regmap)) + return dev_err_probe(&client->dev, PTR_ERR(led->regmap), + "Failed to allocate regmap\n"); + + /* Control brightness by DISPLAY_BRT register */ + ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK, + LP8864_BRT_MODE_REG); + if (ret) { + dev_err(&led->client->dev, "Failed to set brightness control mode\n"); + return ret; + } + + ret = lp8864_fault_check(led); + if (ret) + return ret; + + init_data.fwnode = of_fwnode_handle(child_node); + init_data.devicename = "lp8864"; + init_data.default_label = ":display_cluster"; + + ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data); + if (ret) + dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret)); + + return ret; +} + +static const struct i2c_device_id lp8864_id[] = { + { "lp8864" }, + {} +}; +MODULE_DEVICE_TABLE(i2c, lp8864_id); + +static const struct of_device_id of_lp8864_leds_match[] = { + { .compatible = "ti,lp8864" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_lp8864_leds_match); + +static struct i2c_driver lp8864_driver = { + .driver = { + .name = "lp8864", + .of_match_table = of_lp8864_leds_match, + }, + .probe = lp8864_probe, + .id_table = lp8864_id, +}; +module_i2c_driver(lp8864_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver"); +MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index af5a908b8d9e..e95287416ef8 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -439,6 +439,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev, } gpio_ext_pdev = of_find_device_by_node(gpio_ext_np); if (!gpio_ext_pdev) { + of_node_put(gpio_ext_np); dev_err(dev, "Failed to find platform device for gpio-ext\n"); return -ENODEV; } diff --git a/drivers/leds/leds-qnap-mcu.c b/drivers/leds/leds-qnap-mcu.c new file mode 100644 index 000000000000..4e4709456261 --- /dev/null +++ b/drivers/leds/leds-qnap-mcu.c @@ -0,0 +1,227 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for LEDs found on QNAP MCU devices + * + * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de> + */ + +#include <linux/leds.h> +#include <linux/mfd/qnap-mcu.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <uapi/linux/uleds.h> + +enum qnap_mcu_err_led_mode { + QNAP_MCU_ERR_LED_ON = 0, + QNAP_MCU_ERR_LED_OFF = 1, + QNAP_MCU_ERR_LED_BLINK_FAST = 2, + QNAP_MCU_ERR_LED_BLINK_SLOW = 3, +}; + +struct qnap_mcu_err_led { + struct qnap_mcu *mcu; + struct led_classdev cdev; + char name[LED_MAX_NAME_SIZE]; + u8 num; + u8 mode; +}; + +static inline struct qnap_mcu_err_led * + cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_err_led, cdev); +} + +static int qnap_mcu_err_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); + u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' }; + + /* Don't disturb a possible set blink-mode if LED stays on */ + if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST) + return 0; + + err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF; + cmd[3] = '0' + err_led->mode; + + return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev); + u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' }; + + /* LED is off, nothing to do */ + if (err_led->mode == QNAP_MCU_ERR_LED_OFF) + return 0; + + if (*delay_on < 500) { + *delay_on = 100; + *delay_off = 100; + err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST; + } else { + *delay_on = 500; + *delay_off = 500; + err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW; + } + + cmd[3] = '0' + err_led->mode; + + return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led) +{ + struct qnap_mcu_err_led *err_led; + int ret; + + err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL); + if (!err_led) + return -ENOMEM; + + err_led->mcu = mcu; + err_led->num = num_err_led; + err_led->mode = QNAP_MCU_ERR_LED_OFF; + + scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1); + err_led->cdev.name = err_led->name; + + err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set; + err_led->cdev.blink_set = qnap_mcu_err_led_blink_set; + err_led->cdev.brightness = 0; + err_led->cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &err_led->cdev); + if (ret) + return ret; + + return qnap_mcu_err_led_set(&err_led->cdev, 0); +} + +enum qnap_mcu_usb_led_mode { + QNAP_MCU_USB_LED_ON = 1, + QNAP_MCU_USB_LED_OFF = 3, + QNAP_MCU_USB_LED_BLINK = 2, +}; + +struct qnap_mcu_usb_led { + struct qnap_mcu *mcu; + struct led_classdev cdev; + u8 mode; +}; + +static inline struct qnap_mcu_usb_led * + cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_usb_led, cdev); +} + +static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); + u8 cmd[] = { '@', 'C', 0 }; + + /* Don't disturb a possible set blink-mode if LED stays on */ + if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK) + return 0; + + usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF; + + /* + * 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; + + return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev); + u8 cmd[] = { '@', 'C', 0 }; + + /* LED is off, nothing to do */ + if (usb_led->mode == QNAP_MCU_USB_LED_OFF) + return 0; + + *delay_on = 250; + *delay_off = 250; + usb_led->mode = QNAP_MCU_USB_LED_BLINK; + + /* + * 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; + + return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu) +{ + struct qnap_mcu_usb_led *usb_led; + int ret; + + usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL); + if (!usb_led) + return -ENOMEM; + + usb_led->mcu = mcu; + usb_led->mode = QNAP_MCU_USB_LED_OFF; + usb_led->cdev.name = "usb:blue:disk"; + usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set; + usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set; + usb_led->cdev.brightness = 0; + usb_led->cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &usb_led->cdev); + if (ret) + return ret; + + return qnap_mcu_usb_led_set(&usb_led->cdev, 0); +} + +static int qnap_mcu_leds_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + const struct qnap_mcu_variant *variant = pdev->dev.platform_data; + int ret; + + for (int i = 0; i < variant->num_drives; i++) { + ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register error LED %d\n", i); + } + + if (variant->usb_led) { + ret = qnap_mcu_register_usb_led(&pdev->dev, mcu); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register USB LED\n"); + } + + return 0; +} + +static struct platform_driver qnap_mcu_leds_driver = { + .probe = qnap_mcu_leds_probe, + .driver = { + .name = "qnap-mcu-leds", + }, +}; +module_platform_driver(qnap_mcu_leds_driver); + +MODULE_ALIAS("platform:qnap-mcu-leds"); +MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>"); +MODULE_DESCRIPTION("QNAP MCU LEDs driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c new file mode 100644 index 000000000000..e894b3f9a0f4 --- /dev/null +++ b/drivers/leds/leds-st1202.c @@ -0,0 +1,415 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED driver for STMicroelectronics LED1202 chip + * + * Copyright (C) 2024 Remote-Tech Ltd. UK + */ + +#include <linux/cleanup.h> +#include <linux/ctype.h> +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/string.h> + +#define ST1202_CHAN_DISABLE_ALL 0x00 +#define ST1202_CHAN_ENABLE_HIGH 0x03 +#define ST1202_CHAN_ENABLE_LOW 0x02 +#define ST1202_CONFIG_REG 0x04 +/* PATS: Pattern sequence feature enable */ +#define ST1202_CONFIG_REG_PATS BIT(7) +/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */ +#define ST1202_CONFIG_REG_PATSR BIT(6) +#define ST1202_CONFIG_REG_SHFT BIT(3) +#define ST1202_DEV_ENABLE 0x01 +#define ST1202_DEV_ENABLE_ON BIT(0) +#define ST1202_DEV_ENABLE_RESET BIT(7) +#define ST1202_DEVICE_ID 0x00 +#define ST1202_ILED_REG0 0x09 +#define ST1202_MAX_LEDS 12 +#define ST1202_MAX_PATTERNS 8 +#define ST1202_MILLIS_PATTERN_DUR_MAX 5660 +#define ST1202_MILLIS_PATTERN_DUR_MIN 22 +#define ST1202_PATTERN_DUR 0x16 +#define ST1202_PATTERN_PWM 0x1E +#define ST1202_PATTERN_REP 0x15 + +struct st1202_led { + struct fwnode_handle *fwnode; + struct led_classdev led_cdev; + struct st1202_chip *chip; + bool is_active; + int led_num; +}; + +struct st1202_chip { + struct i2c_client *client; + struct mutex lock; + struct st1202_led leds[ST1202_MAX_LEDS]; +}; + +static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct st1202_led, led_cdev); +} + +static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val) +{ + struct device *dev = &chip->client->dev; + int ret; + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret); + return ret; + } + + *val = (uint8_t)ret; + return 0; +} + +static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val) +{ + struct device *dev = &chip->client->dev; + int ret; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret != 0) + dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret); + + return ret; +} + +static uint8_t st1202_prescalar_to_miliseconds(unsigned int value) +{ + return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1; +} + +static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num, + int pattern, unsigned int value) +{ + u8 value_l, value_h; + int ret; + + value_l = (u8)value; + value_h = (u8)(value >> 8); + + /* + * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh), + * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh) + * and y is the pattern number in hexadecimal (y = 00h .. 07h) + */ + ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern), + value_l); + if (ret != 0) + return ret; + + /* + * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh), + * where x is the channel number in hexadecimal (x = 00h .. 0Bh) + * and y is the pattern number in hexadecimal (y = 00h .. 07h) + */ + ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern), + value_h); + if (ret != 0) + return ret; + + return 0; +} + +static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern, + unsigned int value) +{ + return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern), + st1202_prescalar_to_miliseconds(value)); +} + +static void st1202_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + + guard(mutex)(&chip->lock); + + st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value); +} + +static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev) +{ + struct st1202_led *led = cdev_to_st1202_led(led_cdev); + struct st1202_chip *chip = led->chip; + u8 value = 0; + + guard(mutex)(&chip->lock); + + st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value); + + return value; +} + +static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active) +{ + u8 chan_low, chan_high; + int ret; + + guard(mutex)(&chip->lock); + + if (led_num <= 7) { + ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low); + if (ret < 0) + return ret; + + chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num); + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low); + if (ret < 0) + return ret; + + } else { + ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high); + if (ret < 0) + return ret; + + chan_high = active ? chan_high | (BIT(led_num) >> 8) : + chan_high & ~(BIT(led_num) >> 8); + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high); + if (ret < 0) + return ret; + } + + return 0; +} + +static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + + return st1202_channel_set(chip, led->led_num, value == LED_OFF ? false : true); +} + +static int st1202_led_pattern_clear(struct led_classdev *ldev) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + int ret; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) { + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF); + if (ret != 0) + return ret; + + ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN); + if (ret != 0) + return ret; + } + + return 0; +} + +static int st1202_led_pattern_set(struct led_classdev *ldev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct st1202_led *led = cdev_to_st1202_led(ldev); + struct st1202_chip *chip = led->chip; + int ret; + + if (len > ST1202_MAX_PATTERNS) + return -EINVAL; + + guard(mutex)(&chip->lock); + + for (int patt = 0; patt < len; patt++) { + if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN || + pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX) + return -EINVAL; + + ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness); + if (ret != 0) + return ret; + + ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t); + if (ret != 0) + return ret; + } + + ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat); + if (ret != 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR | + ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT)); + if (ret != 0) + return ret; + + return 0; +} + +static int st1202_dt_init(struct st1202_chip *chip) +{ + struct device *dev = &chip->client->dev; + struct st1202_led *led; + int err, reg; + + for_each_available_child_of_node_scoped(dev_of_node(dev), child) { + err = of_property_read_u32(child, "reg", ®); + if (err) + return dev_err_probe(dev, err, "Invalid register\n"); + + led = &chip->leds[reg]; + led->is_active = true; + led->fwnode = of_fwnode_handle(child); + + led->led_cdev.max_brightness = U8_MAX; + led->led_cdev.brightness_set_blocking = st1202_led_set; + led->led_cdev.pattern_set = st1202_led_pattern_set; + led->led_cdev.pattern_clear = st1202_led_pattern_clear; + led->led_cdev.default_trigger = "pattern"; + led->led_cdev.brightness_set = st1202_brightness_set; + led->led_cdev.brightness_get = st1202_brightness_get; + } + + return 0; +} + +static int st1202_setup(struct st1202_chip *chip) +{ + int ret; + + guard(mutex)(&chip->lock); + + /* + * Once the supply voltage is applied, the LED1202 executes some internal checks, + * afterwords it stops the oscillator and puts the internal LDO in quiescent mode. + * To start the device, EN bit must be set inside the “Device Enable” register at + * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters + * from the internal non-volatile memory and performs an auto-calibration procedure + * in order to increase the output current precision. + * Such initialization lasts about 6.5 ms. + */ + + /* Reset the chip during setup */ + ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET); + if (ret < 0) + return ret; + + /* Enable phase-shift delay feature */ + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT); + if (ret < 0) + return ret; + + /* Enable the device */ + ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON); + if (ret < 0) + return ret; + + /* Duration of initialization */ + usleep_range(6500, 10000); + + /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */ + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL); + if (ret < 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL); + if (ret < 0) + return ret; + + ret = st1202_write_reg(chip, ST1202_CONFIG_REG, + ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR); + if (ret < 0) + return ret; + + return 0; +} + +static int st1202_probe(struct i2c_client *client) +{ + struct st1202_chip *chip; + struct st1202_led *led; + int ret; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n"); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + devm_mutex_init(&client->dev, &chip->lock); + chip->client = client; + + ret = st1202_dt_init(chip); + if (ret < 0) + return ret; + + ret = st1202_setup(chip); + if (ret < 0) + return ret; + + for (int i = 0; i < ST1202_MAX_LEDS; i++) { + struct led_init_data init_data = {}; + led = &chip->leds[i]; + led->chip = chip; + led->led_num = i; + + if (!led->is_active) + continue; + + ret = st1202_channel_set(led->chip, led->led_num, true); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to activate LED channel\n"); + + ret = st1202_led_pattern_clear(&led->led_cdev); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to clear LED pattern\n"); + + init_data.fwnode = led->fwnode; + init_data.devicename = "st1202"; + init_data.default_label = ":"; + + ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data); + if (ret < 0) + return dev_err_probe(&client->dev, ret, + "Failed to register LED class device\n"); + } + + return 0; +} + +static const struct i2c_device_id st1202_id[] = { + { "st1202-i2c" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(i2c, st1202_id); + +static const struct of_device_id st1202_dt_ids[] = { + { .compatible = "st,led1202" }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, st1202_dt_ids); + +static struct i2c_driver st1202_driver = { + .driver = { + .name = "leds-st1202", + .of_match_table = of_match_ptr(st1202_dt_ids), + }, + .probe = st1202_probe, + .id_table = st1202_id, +}; +module_i2c_driver(st1202_driver); + +MODULE_AUTHOR("Remote Tech LTD"); +MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index 2de825ac08b3..4fe1a9c0bc1b 100644 --- a/drivers/leds/leds-turris-omnia.c +++ b/drivers/leds/leds-turris-omnia.c @@ -2,7 +2,7 @@ /* * CZ.NIC's Turris Omnia LEDs driver * - * 2020, 2023 by Marek Behún <kabel@kernel.org> + * 2020, 2023, 2024 by Marek Behún <kabel@kernel.org> */ #include <linux/i2c.h> @@ -10,35 +10,23 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> +#include <linux/turris-omnia-mcu-interface.h> #define OMNIA_BOARD_LEDS 12 #define OMNIA_LED_NUM_CHANNELS 3 -/* MCU controller commands at I2C address 0x2a */ -#define OMNIA_MCU_I2C_ADDR 0x2a - -#define CMD_GET_STATUS_WORD 0x01 -#define STS_FEATURES_SUPPORTED BIT(2) - -#define CMD_GET_FEATURES 0x10 -#define FEAT_LED_GAMMA_CORRECTION BIT(5) - -/* LED controller commands at I2C address 0x2b */ -#define CMD_LED_MODE 0x03 -#define CMD_LED_MODE_LED(l) ((l) & 0x0f) -#define CMD_LED_MODE_USER 0x10 - -#define CMD_LED_STATE 0x04 -#define CMD_LED_STATE_LED(l) ((l) & 0x0f) -#define CMD_LED_STATE_ON 0x10 - -#define CMD_LED_COLOR 0x05 -#define CMD_LED_SET_BRIGHTNESS 0x07 -#define CMD_LED_GET_BRIGHTNESS 0x08 - -#define CMD_SET_GAMMA_CORRECTION 0x30 -#define CMD_GET_GAMMA_CORRECTION 0x31 - +/* MCU controller I2C address 0x2a, needed for detecting MCU features */ +#define OMNIA_MCU_I2C_ADDR 0x2a + +/** + * struct omnia_led - per-LED part of driver private data structure + * @mc_cdev: multi-color LED class device + * @subled_info: per-channel information + * @cached_channels: cached values of per-channel brightness that was sent to the MCU + * @on: whether the LED was set on + * @hwtrig: whether the LED blinking was offloaded to the MCU + * @reg: LED identifier to the MCU + */ struct omnia_led { struct led_classdev_mc mc_cdev; struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS]; @@ -49,73 +37,38 @@ struct omnia_led { #define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev) +/** + * struct omnia_leds - driver private data structure + * @client: I2C client device + * @lock: mutex to protect cached state + * @has_gamma_correction: whether the MCU firmware supports gamma correction + * @brightness_knode: kernel node of the "brightness" device sysfs attribute (this is the + * driver specific global brightness, not the LED classdev brightness) + * @leds: flexible array of per-LED data + */ struct omnia_leds { struct i2c_client *client; struct mutex lock; bool has_gamma_correction; + struct kernfs_node *brightness_knode; struct omnia_led leds[]; }; -static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val) -{ - u8 buf[2] = { cmd, val }; - int ret; - - ret = i2c_master_send(client, buf, sizeof(buf)); - - return ret < 0 ? ret : 0; -} - -static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd, - void *reply, size_t len) -{ - struct i2c_msg msgs[2]; - int ret; - - msgs[0].addr = addr; - msgs[0].flags = 0; - msgs[0].len = 1; - msgs[0].buf = &cmd; - msgs[1].addr = addr; - msgs[1].flags = I2C_M_RD; - msgs[1].len = len; - msgs[1].buf = reply; - - ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs)); - if (likely(ret == ARRAY_SIZE(msgs))) - return 0; - else if (ret < 0) - return ret; - else - return -EIO; -} - -static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd) +static int omnia_cmd_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b) { - u8 reply; - int err; + u8 buf[5] = { OMNIA_CMD_LED_COLOR, led, r, g, b }; - err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1); - if (err) - return err; - - return reply; + return omnia_cmd_write(client, buf, sizeof(buf)); } static int omnia_led_send_color_cmd(const struct i2c_client *client, struct omnia_led *led) { - char cmd[5]; int ret; - cmd[0] = CMD_LED_COLOR; - cmd[1] = led->reg; - cmd[2] = led->subled_info[0].brightness; - cmd[3] = led->subled_info[1].brightness; - cmd[4] = led->subled_info[2].brightness; - /* Send the color change command */ - ret = i2c_master_send(client, cmd, 5); + ret = omnia_cmd_set_color(client, led->reg, led->subled_info[0].brightness, + led->subled_info[1].brightness, led->subled_info[2].brightness); if (ret < 0) return ret; @@ -170,12 +123,12 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, * is not being blinked by HW. */ if (!err && !led->hwtrig && !brightness != !led->on) { - u8 state = CMD_LED_STATE_LED(led->reg); + u8 state = OMNIA_CMD_LED_STATE_LED(led->reg); if (brightness) - state |= CMD_LED_STATE_ON; + state |= OMNIA_CMD_LED_STATE_ON; - err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_STATE, state); if (!err) led->on = !!brightness; } @@ -210,8 +163,8 @@ static int omnia_hwtrig_activate(struct led_classdev *cdev) if (!err) { /* Put the LED into MCU controlled mode */ - err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg)); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE, + OMNIA_CMD_LED_MODE_LED(led->reg)); if (!err) led->hwtrig = true; } @@ -232,9 +185,8 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev) led->hwtrig = false; /* Put the LED into software mode */ - err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg) | - CMD_LED_MODE_USER); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE, + OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER); mutex_unlock(&leds->lock); @@ -300,38 +252,26 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, */ cdev->default_trigger = omnia_hw_trigger.name; - /* put the LED into software mode */ - ret = omnia_cmd_write_u8(client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg) | - CMD_LED_MODE_USER); - if (ret) { - dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, - ret); - return ret; - } + /* Put the LED into software mode */ + ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) | + OMNIA_CMD_LED_MODE_USER); + if (ret) + return dev_err_probe(dev, ret, "Cannot set LED %pOF to software mode\n", np); - /* disable the LED */ - ret = omnia_cmd_write_u8(client, CMD_LED_STATE, - CMD_LED_STATE_LED(led->reg)); - if (ret) { - dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); - return ret; - } + /* Disable the LED */ + ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg)); + if (ret) + return dev_err_probe(dev, ret, "Cannot set LED %pOF brightness\n", np); /* Set initial color and cache it */ ret = omnia_led_send_color_cmd(client, led); - if (ret < 0) { - dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np, - ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot set LED %pOF initial color\n", np); ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); - if (ret < 0) { - dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot register LED %pOF\n", np); return 1; } @@ -351,14 +291,14 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf) { struct i2c_client *client = to_i2c_client(dev); - int ret; - - ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS); + u8 reply; + int err; - if (ret < 0) - return ret; + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_BRIGHTNESS, &reply); + if (err < 0) + return err; - return sysfs_emit(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", reply); } static ssize_t brightness_store(struct device *dev, struct device_attribute *a, @@ -374,7 +314,7 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, if (brightness > 100) return -EINVAL; - err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness); + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_BRIGHTNESS, brightness); return err ?: count; } @@ -385,17 +325,16 @@ static ssize_t gamma_correction_show(struct device *dev, { struct i2c_client *client = to_i2c_client(dev); struct omnia_leds *leds = i2c_get_clientdata(client); - int ret; + u8 reply = 0; + int err; if (leds->has_gamma_correction) { - ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION); - if (ret < 0) - return ret; - } else { - ret = 0; + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_GAMMA_CORRECTION, &reply); + if (err < 0) + return err; } - return sysfs_emit(buf, "%d\n", !!ret); + return sysfs_emit(buf, "%d\n", !!reply); } static ssize_t gamma_correction_store(struct device *dev, @@ -413,7 +352,7 @@ static ssize_t gamma_correction_store(struct device *dev, if (kstrtobool(buf, &val) < 0) return -EINVAL; - err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val); + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_GAMMA_CORRECTION, val); return err ?: count; } @@ -426,26 +365,104 @@ static struct attribute *omnia_led_controller_attrs[] = { }; ATTRIBUTE_GROUPS(omnia_led_controller); -static int omnia_mcu_get_features(const struct i2c_client *client) +static irqreturn_t omnia_brightness_changed_threaded_fn(int irq, void *data) +{ + struct omnia_leds *leds = data; + + if (unlikely(!leds->brightness_knode)) { + /* + * Note that sysfs_get_dirent() may sleep. This is okay, because we are in threaded + * context. + */ + leds->brightness_knode = sysfs_get_dirent(leds->client->dev.kobj.sd, "brightness"); + if (!leds->brightness_knode) + return IRQ_NONE; + } + + sysfs_notify_dirent(leds->brightness_knode); + + return IRQ_HANDLED; +} + +static void omnia_brightness_knode_put(void *data) +{ + struct omnia_leds *leds = data; + + if (leds->brightness_knode) + sysfs_put(leds->brightness_knode); +} + +static int omnia_request_brightness_irq(struct omnia_leds *leds) +{ + struct device *dev = &leds->client->dev; + int ret; + + if (!leds->client->irq) { + dev_info(dev, + "Brightness change interrupt supported by MCU firmware but not described in device-tree\n"); + + return 0; + } + + /* + * Registering the brightness_knode destructor before requesting the IRQ ensures that on + * removal the brightness_knode sysfs node is put only after the IRQ is freed. + * This is needed because the interrupt handler uses the knode. + */ + ret = devm_add_action(dev, omnia_brightness_knode_put, leds); + if (ret < 0) + return ret; + + return devm_request_threaded_irq(dev, leds->client->irq, NULL, + omnia_brightness_changed_threaded_fn, IRQF_ONESHOT, + "leds-turris-omnia", leds); +} + +static int omnia_mcu_get_features(const struct i2c_client *mcu_client) { u16 reply; int err; - err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, - CMD_GET_STATUS_WORD, &reply, sizeof(reply)); + err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_STATUS_WORD, &reply); if (err) return err; - /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */ - if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED)) + /* Check whether MCU firmware supports the OMNIA_CMD_GET_FEAUTRES command */ + if (!(reply & OMNIA_STS_FEATURES_SUPPORTED)) return 0; - err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR, - CMD_GET_FEATURES, &reply, sizeof(reply)); + err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_FEATURES, &reply); if (err) return err; - return le16_to_cpu(reply); + return reply; +} + +static int omnia_match_mcu_client(struct device *dev, const void *data) +{ + struct i2c_client *client; + + client = i2c_verify_client(dev); + if (!client) + return 0; + + return client->addr == OMNIA_MCU_I2C_ADDR; +} + +static int omnia_find_mcu_and_get_features(struct device *dev) +{ + struct device *mcu_dev; + int ret; + + mcu_dev = device_find_child(dev->parent, NULL, omnia_match_mcu_client); + if (!mcu_dev) + return -ENODEV; + + ret = omnia_mcu_get_features(i2c_verify_client(mcu_dev)); + + put_device(mcu_dev); + + return ret; } static int omnia_leds_probe(struct i2c_client *client) @@ -457,13 +474,10 @@ static int omnia_leds_probe(struct i2c_client *client) int ret, count; count = of_get_available_child_count(np); - if (!count) { - dev_err(dev, "LEDs are not defined in device tree!\n"); - return -ENODEV; - } else if (count > OMNIA_BOARD_LEDS) { - dev_err(dev, "Too many LEDs defined in device tree!\n"); - return -EINVAL; - } + if (count == 0) + return dev_err_probe(dev, -ENODEV, "LEDs are not defined in device tree!\n"); + if (count > OMNIA_BOARD_LEDS) + return dev_err_probe(dev, -EINVAL, "Too many LEDs defined in device tree!\n"); leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL); if (!leds) @@ -472,28 +486,23 @@ static int omnia_leds_probe(struct i2c_client *client) leds->client = client; i2c_set_clientdata(client, leds); - ret = omnia_mcu_get_features(client); - if (ret < 0) { - dev_err(dev, "Cannot determine MCU supported features: %d\n", - ret); - return ret; - } + ret = omnia_find_mcu_and_get_features(dev); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot determine MCU supported features\n"); - leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION; - if (!leds->has_gamma_correction) { - dev_info(dev, - "Your board's MCU firmware does not support the LED gamma correction feature.\n"); - dev_info(dev, - "Consider upgrading MCU firmware with the omnia-mcutool utility.\n"); + leds->has_gamma_correction = ret & OMNIA_FEAT_LED_GAMMA_CORRECTION; + + if (ret & OMNIA_FEAT_BRIGHTNESS_INT) { + ret = omnia_request_brightness_irq(leds); + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot request brightness IRQ\n"); } mutex_init(&leds->lock); ret = devm_led_trigger_register(dev, &omnia_hw_trigger); - if (ret < 0) { - dev_err(dev, "Cannot register private LED trigger: %d\n", ret); - return ret; - } + if (ret < 0) + return dev_err_probe(dev, ret, "Cannot register private LED trigger\n"); led = &leds->leds[0]; for_each_available_child_of_node_scoped(np, child) { @@ -509,20 +518,11 @@ static int omnia_leds_probe(struct i2c_client *client) static void omnia_leds_remove(struct i2c_client *client) { - u8 buf[5]; - - /* put all LEDs into default (HW triggered) mode */ - omnia_cmd_write_u8(client, CMD_LED_MODE, - CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); - - /* set all LEDs color to [255, 255, 255] */ - buf[0] = CMD_LED_COLOR; - buf[1] = OMNIA_BOARD_LEDS; - buf[2] = 255; - buf[3] = 255; - buf[4] = 255; + /* Put all LEDs into default (HW triggered) mode */ + omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); - i2c_master_send(client, buf, 5); + /* Set all LEDs color to [255, 255, 255] */ + omnia_cmd_set_color(client, OMNIA_BOARD_LEDS, 255, 255, 255); } static const struct of_device_id of_omnia_leds_match[] = { diff --git a/drivers/leds/leds-upboard.c b/drivers/leds/leds-upboard.c new file mode 100644 index 000000000000..b350eb294280 --- /dev/null +++ b/drivers/leds/leds-upboard.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * UP board LED driver. + * + * Copyright (c) AAEON. All rights reserved. + * Copyright (C) 2024 Bootlin + * + * Author: Gary Wang <garywang@aaeon.com.tw> + * Author: Thomas Richard <thomas.richard@bootlin.com> + */ + +#include <linux/device.h> +#include <linux/container_of.h> +#include <linux/leds.h> +#include <linux/mfd/upboard-fpga.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define led_cdev_to_led_upboard(c) container_of(c, struct upboard_led, cdev) + +struct upboard_led { + struct regmap_field *field; + struct led_classdev cdev; +}; + +struct upboard_led_profile { + const char *name; + unsigned int bit; +}; + +static struct upboard_led_profile upboard_up_led_profile[] = { + { "upboard:yellow:" LED_FUNCTION_STATUS, 0 }, + { "upboard:green:" LED_FUNCTION_STATUS, 1 }, + { "upboard:red:" LED_FUNCTION_STATUS, 2 }, +}; + +static struct upboard_led_profile upboard_up2_led_profile[] = { + { "upboard:blue:" LED_FUNCTION_STATUS, 0 }, + { "upboard:yellow:" LED_FUNCTION_STATUS, 1 }, + { "upboard:green:" LED_FUNCTION_STATUS, 2 }, + { "upboard:red:" LED_FUNCTION_STATUS, 3 }, +}; + +static enum led_brightness upboard_led_brightness_get(struct led_classdev *cdev) +{ + struct upboard_led *led = led_cdev_to_led_upboard(cdev); + int brightness, ret; + + ret = regmap_field_read(led->field, &brightness); + + return ret ? LED_OFF : brightness; +}; + +static int upboard_led_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct upboard_led *led = led_cdev_to_led_upboard(cdev); + + return regmap_field_write(led->field, brightness != LED_OFF); +}; + +static int upboard_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct upboard_fpga *fpga = dev_get_drvdata(dev->parent); + struct upboard_led_profile *led_profile; + struct upboard_led *led; + int led_instances, ret, i; + + switch (fpga->fpga_data->type) { + case UPBOARD_UP_FPGA: + led_profile = upboard_up_led_profile; + led_instances = ARRAY_SIZE(upboard_up_led_profile); + break; + case UPBOARD_UP2_FPGA: + led_profile = upboard_up2_led_profile; + led_instances = ARRAY_SIZE(upboard_up2_led_profile); + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown device type %d\n", + fpga->fpga_data->type); + } + + for (i = 0; i < led_instances; i++) { + const struct reg_field fldconf = { + .reg = UPBOARD_REG_FUNC_EN0, + .lsb = led_profile[i].bit, + .msb = led_profile[i].bit, + }; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->field = devm_regmap_field_alloc(&pdev->dev, fpga->regmap, fldconf); + if (IS_ERR(led->field)) + return PTR_ERR(led->field); + + led->cdev.brightness_get = upboard_led_brightness_get; + led->cdev.brightness_set_blocking = upboard_led_brightness_set; + led->cdev.max_brightness = LED_ON; + + led->cdev.name = led_profile[i].name; + + ret = devm_led_classdev_register(dev, &led->cdev); + if (ret) + return ret; + } + + return 0; +} + +static struct platform_driver upboard_led_driver = { + .driver = { + .name = "upboard-leds", + }, + .probe = upboard_led_probe, +}; + +module_platform_driver(upboard_led_driver); + +MODULE_AUTHOR("Gary Wang <garywang@aaeon.com.tw>"); +MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>"); +MODULE_DESCRIPTION("UP Board LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:upboard-led"); diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index d7999e7372a4..bee46651e068 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -22,10 +22,10 @@ void led_stop_software_blink(struct led_classdev *led_cdev); void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value); void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value); ssize_t led_trigger_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count); ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *bin_attr, char *buf, + const struct bin_attribute *bin_attr, char *buf, loff_t pos, size_t count); extern struct rw_semaphore leds_list_lock; diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index e1a81e0109e8..f80a06cc31f8 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -50,7 +50,13 @@ static int led_pwm_mc_set(struct led_classdev *cdev, duty = priv->leds[i].state.period - duty; priv->leds[i].state.duty_cycle = duty; - priv->leds[i].state.enabled = duty > 0; + /* + * Disabling a PWM doesn't guarantee that it emits the inactive level. + * So keep it on. Only for suspending the PWM should be disabled because + * otherwise it refuses to suspend. The possible downside is that the + * LED might stay (or even go) on. + */ + priv->leds[i].state.enabled = !(cdev->flags & LED_SUSPENDED); ret = pwm_apply_might_sleep(priv->leds[i].pwm, &priv->leds[i].state); if (ret) diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c index 33cbf8413658..b3ee33aed36e 100644 --- a/drivers/leds/trigger/ledtrig-activity.c +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -156,7 +156,7 @@ static ssize_t led_invert_show(struct device *dev, { struct activity_data *activity_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", activity_data->invert); + return sprintf(buf, "%d\n", activity_data->invert); } static ssize_t led_invert_store(struct device *dev, diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index 4b0863db901a..c15efe3e5078 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -605,6 +605,8 @@ static int netdev_trig_notify(struct notifier_block *nb, trigger_data->net_dev = NULL; break; case NETDEV_UP: + trigger_data->hw_control = can_hw_control(trigger_data); + fallthrough; case NETDEV_CHANGE: get_device_state(trigger_data); /* Refresh link_speed visibility */ diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index aad48c2540fc..a594bd5e2233 100644 --- a/drivers/leds/trigger/ledtrig-pattern.c +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -483,8 +483,8 @@ static int pattern_trig_activate(struct led_classdev *led_cdev) data->led_cdev = led_cdev; led_set_trigger_data(led_cdev, data); timer_setup(&data->timer, pattern_trig_timer_function, 0); - hrtimer_init(&data->hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); - data->hrtimer.function = pattern_trig_hrtimer_function; + hrtimer_setup(&data->hrtimer, pattern_trig_hrtimer_function, CLOCK_MONOTONIC, + HRTIMER_MODE_REL); led_cdev->activated = true; if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) { |