diff options
Diffstat (limited to 'drivers/leds')
121 files changed, 5831 insertions, 3004 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 05e6af88b88c..a104cbb0a001 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -179,6 +179,21 @@ config LEDS_CR0014114 To compile this driver as a module, choose M here: the module will be called leds-cr0014114. +config LEDS_CROS_EC + tristate "LED Support for ChromeOS EC" + depends on MFD_CROS_EC_DEV + depends on LEDS_CLASS_MULTICOLOR + select LEDS_TRIGGERS + default MFD_CROS_EC_DEV + help + This option enables support for LEDs managed by ChromeOS ECs. + All LEDs exposed by the EC are supported in multicolor mode. + A hardware trigger to switch back to the automatic behaviour is + provided. + + To compile this driver as a module, choose M here: the module + will be called leds-cros_ec. + config LEDS_EL15203000 tristate "LED Support for Crane EL15203000" depends on LEDS_CLASS @@ -202,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 @@ -414,7 +431,7 @@ config LEDS_LP50XX module will be called leds-lp50xx. config LEDS_LP55XX_COMMON - tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501" + tristate "Common Driver for TI/National LP5521/5523/55231/5562/5569/8501" depends on LEDS_CLASS depends on LEDS_CLASS_MULTICOLOR depends on OF @@ -422,8 +439,8 @@ config LEDS_LP55XX_COMMON select FW_LOADER select FW_LOADER_USER_HELPER help - This option supports common operations for LP5521/5523/55231/5562/8501 - devices. + This option supports common operations for LP5521/5523/55231/5562/5569/ + 8501 devices. config LEDS_LP5521 tristate "LED Support for N.S. LP5521 LED driver chip" @@ -456,6 +473,16 @@ config LEDS_LP5562 Driver provides direct control via LED class and interface for programming the engines. +config LEDS_LP5569 + tristate "LED Support for TI LP5569 LED driver chip" + depends on LEDS_CLASS && I2C + depends on LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP5569 LED driver. + It is 9 channels chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + config LEDS_LP8501 tristate "LED Support for TI LP8501 LED driver chip" depends on LEDS_CLASS && I2C @@ -486,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 @@ -555,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 @@ -728,6 +778,14 @@ config LEDS_MAX77650 help LEDs driver for MAX77650 family of PMICs from Maxim Integrated. +config LEDS_MAX77705 + tristate "LED support for Maxim MAX77705 PMIC" + depends on MFD_MAX77705 + depends on LEDS_CLASS + depends on LEDS_CLASS_MULTICOLOR + help + LED driver for MAX77705 PMIC from Maxim Integrated. + config LEDS_MAX8997 tristate "LED support for MAX8997 PMIC" depends on LEDS_CLASS && MFD_MAX8997 @@ -790,6 +848,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 @@ -800,6 +867,14 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. +config LEDS_BLINKM_MULTICOLOR + bool "Enable multicolor support for BlinkM I2C RGB LED" + depends on LEDS_BLINKM + depends on LEDS_CLASS_MULTICOLOR=y || LEDS_CLASS_MULTICOLOR=LEDS_BLINKM + help + This option enables multicolor sysfs class support for BlinkM LED and + disables the older, separated sysfs interface + config LEDS_POWERNV tristate "LED support for PowerNV Platform" depends on LEDS_CLASS @@ -857,7 +932,8 @@ config LEDS_USER config LEDS_NIC78BX tristate "LED support for NI PXI NIC78bx devices" depends on LEDS_CLASS - depends on X86 && ACPI + depends on HAS_IOPORT + depends on X86 || COMPILE_TEST help This option enables support for the User1 and User2 LEDs on NI PXI NIC78bx devices. @@ -869,7 +945,6 @@ config LEDS_SPI_BYTE tristate "LED support for SPI LED controller with a single byte" depends on LEDS_CLASS depends on SPI - depends on OF help This option enables support for LED controller which use a single byte for controlling the brightness. Currently the following controller is @@ -899,6 +974,17 @@ 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 + select LEDS_TRIGGER_PATTERN + 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 @@ -938,7 +1024,7 @@ source "drivers/leds/rgb/Kconfig" comment "LED Triggers" source "drivers/leds/trigger/Kconfig" -comment "Simple LED drivers" -source "drivers/leds/simple/Kconfig" +comment "Simatic LED drivers" +source "drivers/leds/simatic/Kconfig" endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index effdfc6f1e95..2f170d69dcbf 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -26,6 +26,7 @@ obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o obj-$(CONFIG_LEDS_CPCAP) += leds-cpcap.o +obj-$(CONFIG_LEDS_CROS_EC) += leds-cros_ec.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o @@ -51,13 +52,16 @@ obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o +obj-$(CONFIG_LEDS_LP5569) += leds-lp5569.o 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 +obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o @@ -77,8 +81,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 @@ -87,6 +93,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 @@ -115,5 +122,5 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ # LED Blink obj-y += blink/ -# Simple LED drivers -obj-y += simple/ +# Simatic LED drivers +obj-y += simatic/ diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c index 2cf2761e4914..ef2e511438cc 100644 --- a/drivers/leds/blink/leds-bcm63138.c +++ b/drivers/leds/blink/leds-bcm63138.c @@ -2,6 +2,8 @@ /* * Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl> */ +#include <linux/bits.h> +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/io.h> #include <linux/leds.h> @@ -19,8 +21,10 @@ #define BCM63138_LEDS_PER_REG (32 / BCM63138_LED_BITS) /* 8 */ #define BCM63138_GLB_CTRL 0x00 -#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL 0x00000002 -#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL 0x00000008 +#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL BIT(1) +#define BCM63138_GLB_CTRL_SERIAL_LED_CLK_POL BIT(2) +#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL BIT(3) +#define BCM63138_GLB_CTRL_SERIAL_LED_MSB_FIRST BIT(4) #define BCM63138_MASK 0x04 #define BCM63138_HW_LED_EN 0x08 #define BCM63138_SERIAL_LED_SHIFT_SEL 0x0c @@ -33,6 +37,7 @@ #define BCM63138_BRIGHT_CTRL3 0x28 #define BCM63138_BRIGHT_CTRL4 0x2c #define BCM63138_POWER_LED_CFG 0x30 +#define BCM63138_POWER_LUT_BASE0 0x34 /* -> b0 */ #define BCM63138_HW_POLARITY 0xb4 #define BCM63138_SW_DATA 0xb8 #define BCM63138_SW_POLARITY 0xbc @@ -125,17 +130,14 @@ static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev, { struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); struct bcm63138_leds *leds = led->leds; - unsigned long flags; - spin_lock_irqsave(&leds->lock, flags); + guard(spinlock_irqsave)(&leds->lock); bcm63138_leds_enable_led(leds, led, value); if (!value) bcm63138_leds_set_flash_rate(leds, led, 0); else bcm63138_leds_set_bright(leds, led, value); - - spin_unlock_irqrestore(&leds->lock, flags); } static int bcm63138_leds_blink_set(struct led_classdev *led_cdev, @@ -144,7 +146,6 @@ static int bcm63138_leds_blink_set(struct led_classdev *led_cdev, { struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev); struct bcm63138_leds *leds = led->leds; - unsigned long flags; u8 value; if (!*delay_on && !*delay_off) { @@ -179,13 +180,11 @@ static int bcm63138_leds_blink_set(struct led_classdev *led_cdev, return -EINVAL; } - spin_lock_irqsave(&leds->lock, flags); + guard(spinlock_irqsave)(&leds->lock); bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS); bcm63138_leds_set_flash_rate(leds, led, value); - spin_unlock_irqrestore(&leds->lock, flags); - return 0; } @@ -259,7 +258,7 @@ static int bcm63138_leds_probe(struct platform_device *pdev) struct device_node *np = dev_of_node(&pdev->dev); struct device *dev = &pdev->dev; struct bcm63138_leds *leds; - struct device_node *child; + u32 shift_bits; leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); if (!leds) @@ -273,6 +272,12 @@ static int bcm63138_leds_probe(struct platform_device *pdev) spin_lock_init(&leds->lock); + /* If this property is not present, we use boot defaults */ + if (!of_property_read_u32(np, "brcm,serial-shift-bits", &shift_bits)) { + bcm63138_leds_write(leds, BCM63138_SERIAL_LED_SHIFT_SEL, + GENMASK(shift_bits - 1, 0)); + } + bcm63138_leds_write(leds, BCM63138_GLB_CTRL, BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL | BCM63138_GLB_CTRL_SERIAL_LED_EN_POL); @@ -280,7 +285,7 @@ static int bcm63138_leds_probe(struct platform_device *pdev) bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0); bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0); - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { bcm63138_leds_create_led(leds, child); } @@ -303,5 +308,6 @@ static struct platform_driver bcm63138_leds_driver = { module_platform_driver(bcm63138_leds_driver); MODULE_AUTHOR("Rafał Miłecki"); +MODULE_DESCRIPTION("Broadcom BCM63138 SoC LED driver"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table); diff --git a/drivers/leds/blink/leds-lgm-sso.c b/drivers/leds/blink/leds-lgm-sso.c index 7b04ea146260..effaaaf302b5 100644 --- a/drivers/leds/blink/leds-lgm-sso.c +++ b/drivers/leds/blink/leds-lgm-sso.c @@ -861,7 +861,7 @@ MODULE_DEVICE_TABLE(of, of_sso_led_match); static struct platform_driver intel_sso_led_driver = { .probe = intel_sso_led_probe, - .remove_new = intel_sso_led_remove, + .remove = intel_sso_led_remove, .driver = { .name = "lgm-ssoled", .of_match_table = of_sso_led_match, diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig index 809b6d98bb3e..f39f0bfe6eef 100644 --- a/drivers/leds/flash/Kconfig +++ b/drivers/leds/flash/Kconfig @@ -121,4 +121,15 @@ config LEDS_SGM3140 This option enables support for the SGM3140 500mA Buck/Boost Charge Pump LED Driver. +config LEDS_SY7802 + tristate "LED support for the Silergy SY7802" + depends on I2C && OF + depends on GPIOLIB + select REGMAP_I2C + help + This option enables support for the SY7802 flash LED controller. + SY7802 includes torch and flash functions with programmable current. + + This driver can be built as a module, it will be called "leds-sy7802". + endif # LEDS_CLASS_FLASH diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile index 91d60a4b7952..48860eeced79 100644 --- a/drivers/leds/flash/Makefile +++ b/drivers/leds/flash/Makefile @@ -11,3 +11,4 @@ obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o +obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c index 0195935a7c99..49251cfd3350 100644 --- a/drivers/leds/flash/leds-aat1290.c +++ b/drivers/leds/flash/leds-aat1290.c @@ -7,6 +7,7 @@ * Author: Jacek Anaszewski <j.anaszewski@samsung.com> */ +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/gpio/consumer.h> #include <linux/led-class-flash.h> @@ -77,8 +78,6 @@ struct aat1290_led { int *mm_current_scale; /* device mode */ bool movie_mode; - /* brightness cache */ - unsigned int torch_brightness; }; static struct aat1290_led *fled_cdev_to_led( @@ -217,7 +216,6 @@ static int aat1290_led_parse_dt(struct aat1290_led *led, struct device_node **sub_node) { struct device *dev = &led->pdev->dev; - struct device_node *child_node; #if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS) struct pinctrl *pinctrl; #endif @@ -248,7 +246,8 @@ static int aat1290_led_parse_dt(struct aat1290_led *led, } #endif - child_node = of_get_next_available_child(dev_of_node(dev), NULL); + struct device_node *child_node __free(device_node) = + of_get_next_available_child(dev_of_node(dev), NULL); if (!child_node) { dev_err(dev, "No DT child node found for connected LED.\n"); return -EINVAL; @@ -269,7 +268,7 @@ static int aat1290_led_parse_dt(struct aat1290_led *led, if (ret < 0) { dev_err(dev, "flash-max-microamp DT property missing\n"); - goto err_parse_dt; + return ret; } ret = of_property_read_u32(child_node, "flash-max-timeout-us", @@ -277,15 +276,12 @@ static int aat1290_led_parse_dt(struct aat1290_led *led, if (ret < 0) { dev_err(dev, "flash-max-timeout-us DT property missing\n"); - goto err_parse_dt; + return ret; } *sub_node = child_node; -err_parse_dt: - of_node_put(child_node); - - return ret; + return 0; } static void aat1290_led_validate_mm_current(struct aat1290_led *led, @@ -540,7 +536,7 @@ MODULE_DEVICE_TABLE(of, aat1290_led_dt_match); static struct platform_driver aat1290_led_driver = { .probe = aat1290_led_probe, - .remove_new = aat1290_led_remove, + .remove = aat1290_led_remove, .driver = { .name = "aat1290", .of_match_table = aat1290_led_dt_match, diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c index 12c2609c1137..2f2d783c62c3 100644 --- a/drivers/leds/flash/leds-as3645a.c +++ b/drivers/leds/flash/leds-as3645a.c @@ -478,14 +478,12 @@ static int as3645a_detect(struct as3645a *flash) return as3645a_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE); } -static int as3645a_parse_node(struct as3645a *flash, - struct fwnode_handle *fwnode) +static int as3645a_parse_node(struct device *dev, struct as3645a *flash) { struct as3645a_config *cfg = &flash->cfg; - struct fwnode_handle *child; int rval; - fwnode_for_each_child_node(fwnode, child) { + device_for_each_child_node_scoped(dev, child) { u32 id = 0; fwnode_property_read_u32(child, "reg", &id); @@ -686,7 +684,7 @@ static int as3645a_probe(struct i2c_client *client) flash->client = client; - rval = as3645a_parse_node(flash, dev_fwnode(&client->dev)); + rval = as3645a_parse_node(&client->dev, flash); if (rval < 0) return rval; @@ -743,8 +741,8 @@ static void as3645a_remove(struct i2c_client *client) } static const struct i2c_device_id as3645a_id_table[] = { - { AS_NAME, 0 }, - { }, + { AS_NAME }, + { } }; MODULE_DEVICE_TABLE(i2c, as3645a_id_table); diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c index 7bb0aa2753e3..0f16eefcfe4c 100644 --- a/drivers/leds/flash/leds-ktd2692.c +++ b/drivers/leds/flash/leds-ktd2692.c @@ -6,6 +6,7 @@ * Ingi Kim <ingi2.kim@samsung.com> */ +#include <linux/cleanup.h> #include <linux/err.h> #include <linux/gpio/consumer.h> #include <linux/leds-expresswire.h> @@ -208,7 +209,6 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, struct ktd2692_led_config_data *cfg) { struct device_node *np = dev_of_node(dev); - struct device_node *child_node; int ret; if (!np) @@ -239,7 +239,8 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, } } - child_node = of_get_next_available_child(np, NULL); + struct device_node *child_node __free(device_node) = + of_get_next_available_child(np, NULL); if (!child_node) { dev_err(dev, "No DT child node found for connected LED.\n"); return -EINVAL; @@ -252,26 +253,24 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev, &cfg->movie_max_microamp); if (ret) { dev_err(dev, "failed to parse led-max-microamp\n"); - goto err_parse_dt; + return ret; } ret = of_property_read_u32(child_node, "flash-max-microamp", &cfg->flash_max_microamp); if (ret) { dev_err(dev, "failed to parse flash-max-microamp\n"); - goto err_parse_dt; + return ret; } ret = of_property_read_u32(child_node, "flash-max-timeout-us", &cfg->flash_max_timeout); if (ret) { dev_err(dev, "failed to parse flash-max-timeout-us\n"); - goto err_parse_dt; + return ret; } -err_parse_dt: - of_node_put(child_node); - return ret; + return 0; } static const struct led_flash_ops flash_ops = { @@ -293,6 +292,7 @@ static int ktd2692_probe(struct platform_device *pdev) fled_cdev = &led->fled_cdev; led_cdev = &fled_cdev->led_cdev; + led->props.timing = ktd2692_timing; ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg); if (ret) @@ -344,12 +344,12 @@ static struct platform_driver ktd2692_driver = { .of_match_table = ktd2692_match, }, .probe = ktd2692_probe, - .remove_new = ktd2692_remove, + .remove = ktd2692_remove, }; module_platform_driver(ktd2692_driver); -MODULE_IMPORT_NS(EXPRESSWIRE); +MODULE_IMPORT_NS("EXPRESSWIRE"); MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>"); MODULE_DESCRIPTION("Kinetic KTD2692 LED driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c index 7e93c447fec5..abf6b96ade3d 100644 --- a/drivers/leds/flash/leds-lm3601x.c +++ b/drivers/leds/flash/leds-lm3601x.c @@ -190,7 +190,7 @@ static int lm3601x_brightness_set(struct led_classdev *cdev, goto out; } - ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness); + ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness - 1); if (ret < 0) goto out; @@ -341,8 +341,9 @@ static int lm3601x_register_leds(struct lm3601x_led *led, led_cdev = &led->fled_cdev.led_cdev; led_cdev->brightness_set_blocking = lm3601x_brightness_set; - led_cdev->max_brightness = DIV_ROUND_UP(led->torch_current_max, - LM3601X_TORCH_REG_DIV); + led_cdev->max_brightness = + DIV_ROUND_UP(led->torch_current_max - LM3601X_MIN_TORCH_I_UA + 1, + LM3601X_TORCH_REG_DIV); led_cdev->flags |= LED_DEV_CAP_FLASH; init_data.fwnode = fwnode; @@ -386,6 +387,14 @@ static int lm3601x_parse_node(struct lm3601x_led *led, goto out_err; } + if (led->torch_current_max > LM3601X_MAX_TORCH_I_UA) { + dev_warn(&led->client->dev, + "Max torch current set too high (%d vs %d)\n", + led->torch_current_max, + LM3601X_MAX_TORCH_I_UA); + led->torch_current_max = LM3601X_MAX_TORCH_I_UA; + } + ret = fwnode_property_read_u32(child, "flash-max-microamp", &led->flash_current_max); if (ret) { @@ -434,6 +443,10 @@ static int lm3601x_probe(struct i2c_client *client) return ret; } + ret = regmap_write(led->regmap, LM3601X_DEV_ID_REG, LM3601X_SW_RESET); + if (ret) + dev_warn(&client->dev, "Failed to reset the LED controller\n"); + mutex_init(&led->lock); return lm3601x_register_leds(led, fwnode); diff --git a/drivers/leds/flash/leds-max77693.c b/drivers/leds/flash/leds-max77693.c index 9f016b851193..daee10986108 100644 --- a/drivers/leds/flash/leds-max77693.c +++ b/drivers/leds/flash/leds-max77693.c @@ -599,7 +599,7 @@ static int max77693_led_parse_dt(struct max77693_led_device *led, { struct device *dev = &led->pdev->dev; struct max77693_sub_led *sub_leds = led->sub_leds; - struct device_node *node = dev_of_node(dev), *child_node; + struct device_node *node = dev_of_node(dev); struct property *prop; u32 led_sources[2]; int i, ret, fled_id; @@ -608,7 +608,7 @@ static int max77693_led_parse_dt(struct max77693_led_device *led, of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout); of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys); - for_each_available_child_of_node(node, child_node) { + for_each_available_child_of_node_scoped(node, child_node) { prop = of_find_property(child_node, "led-sources", NULL); if (prop) { const __be32 *srcs = NULL; @@ -622,7 +622,6 @@ static int max77693_led_parse_dt(struct max77693_led_device *led, } else { dev_err(dev, "led-sources DT property missing\n"); - of_node_put(child_node); return -EINVAL; } @@ -638,18 +637,16 @@ static int max77693_led_parse_dt(struct max77693_led_device *led, } else { dev_err(dev, "Wrong led-sources DT property value.\n"); - of_node_put(child_node); return -EINVAL; } if (sub_nodes[fled_id]) { dev_err(dev, "Conflicting \"led-sources\" DT properties\n"); - of_node_put(child_node); return -EINVAL; } - sub_nodes[fled_id] = child_node; + sub_nodes[fled_id] = of_node_get(child_node); sub_leds[fled_id].fled_id = fled_id; cfg->label[fled_id] = @@ -681,10 +678,8 @@ static int max77693_led_parse_dt(struct max77693_led_device *led, if (++cfg->num_leds == 2 || (max77693_fled_used(led, FLED1) && - max77693_fled_used(led, FLED2))) { - of_node_put(child_node); + max77693_fled_used(led, FLED2))) break; - } } if (cfg->num_leds == 0) { @@ -968,7 +963,7 @@ static int max77693_led_probe(struct platform_device *pdev) ret = max77693_setup(led, &led_cfg); if (ret < 0) - return ret; + goto err_setup; mutex_init(&led->lock); @@ -1000,6 +995,8 @@ static int max77693_led_probe(struct platform_device *pdev) else goto err_register_led1; } + of_node_put(sub_nodes[i]); + sub_nodes[i] = NULL; } return 0; @@ -1013,6 +1010,9 @@ err_register_led2: err_register_led1: mutex_destroy(&led->lock); +err_setup: + for (i = FLED1; i <= FLED2; i++) + of_node_put(sub_nodes[i]); return ret; } @@ -1042,7 +1042,7 @@ MODULE_DEVICE_TABLE(of, max77693_led_dt_match); static struct platform_driver max77693_led_driver = { .probe = max77693_led_probe, - .remove_new = max77693_led_remove, + .remove = max77693_led_remove, .driver = { .name = "max77693-led", .of_match_table = max77693_led_dt_match, diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c index a90de82f4568..462a902f54e0 100644 --- a/drivers/leds/flash/leds-mt6360.c +++ b/drivers/leds/flash/leds-mt6360.c @@ -241,11 +241,21 @@ static int mt6360_strobe_set(struct led_classdev_flash *fl_cdev, bool state) u32 enable_mask = MT6360_STROBEN_MASK | MT6360_FLCSEN_MASK(led->led_no); u32 val = state ? MT6360_FLCSEN_MASK(led->led_no) : 0; u32 prev = priv->fled_strobe_used, curr; - int ret; + int ret = 0; mutex_lock(&priv->lock); /* + * If the state of the upcoming change is the same as the current LED + * device state, then skip the subsequent code to avoid conflict + * with the flow of turning on LED torch mode in V4L2. + */ + if (state == !!(BIT(led->led_no) & prev)) { + dev_info(lcdev->dev, "No change in strobe state [0x%x]\n", prev); + goto unlock; + } + + /* * Only one set of flash control logic, use the flag to avoid torch is * currently used */ @@ -633,14 +643,17 @@ static int mt6360_init_isnk_properties(struct mt6360_led *led, ret = fwnode_property_read_u32(child, "reg", ®); if (ret || reg > MT6360_LED_ISNK3 || - priv->leds_active & BIT(reg)) + priv->leds_active & BIT(reg)) { + fwnode_handle_put(child); return -EINVAL; + } ret = fwnode_property_read_u32(child, "color", &color); if (ret) { dev_err(priv->dev, "led %d, no color specified\n", led->led_no); + fwnode_handle_put(child); return ret; } @@ -771,7 +784,6 @@ static void mt6360_v4l2_flash_release(struct mt6360_priv *priv) static int mt6360_led_probe(struct platform_device *pdev) { struct mt6360_priv *priv; - struct fwnode_handle *child; size_t count; int i = 0, ret; @@ -798,7 +810,7 @@ static int mt6360_led_probe(struct platform_device *pdev) return -ENODEV; } - device_for_each_child_node(&pdev->dev, child) { + device_for_each_child_node_scoped(&pdev->dev, child) { struct mt6360_led *led = priv->leds + i; struct led_init_data init_data = { .fwnode = child, }; u32 reg, led_color; @@ -874,7 +886,7 @@ static struct platform_driver mt6360_led_driver = { .of_match_table = mt6360_led_of_id, }, .probe = mt6360_led_probe, - .remove_new = mt6360_led_remove, + .remove = mt6360_led_remove, }; module_platform_driver(mt6360_led_driver); diff --git a/drivers/leds/flash/leds-mt6370-flash.c b/drivers/leds/flash/leds-mt6370-flash.c index 912d9d622320..dbdbe92309db 100644 --- a/drivers/leds/flash/leds-mt6370-flash.c +++ b/drivers/leds/flash/leds-mt6370-flash.c @@ -509,7 +509,6 @@ static int mt6370_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mt6370_priv *priv; - struct fwnode_handle *child; size_t count; int i = 0, ret; @@ -529,22 +528,18 @@ static int mt6370_led_probe(struct platform_device *pdev) if (!priv->regmap) return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n"); - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct mt6370_led *led = priv->leds + i; led->priv = priv; ret = mt6370_init_flash_properties(dev, led, child); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } ret = mt6370_led_register(dev, led, child); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } i++; } diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c index 7c99a3039171..b4c19be51c4d 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 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved. */ #include <linux/bitfield.h> @@ -14,6 +14,9 @@ #include <media/v4l2-flash-led-class.h> /* registers definitions */ +#define FLASH_REVISION_REG 0x00 +#define FLASH_4CH_REVISION_V0P1 0x01 + #define FLASH_TYPE_REG 0x04 #define FLASH_TYPE_VAL 0x18 @@ -73,6 +76,16 @@ #define UA_PER_MA 1000 +/* thermal threshold constants */ +#define OTST_3CH_MIN_VAL 3 +#define OTST1_4CH_MIN_VAL 0 +#define OTST1_4CH_V0P1_MIN_VAL 3 +#define OTST2_4CH_MIN_VAL 0 + +#define OTST1_MAX_CURRENT_MA 1000 +#define OTST2_MAX_CURRENT_MA 500 +#define OTST3_MAX_CURRENT_MA 200 + enum hw_type { QCOM_MVFLASH_3CH, QCOM_MVFLASH_4CH, @@ -98,6 +111,9 @@ enum { REG_IRESOLUTION, REG_CHAN_STROBE, REG_CHAN_EN, + REG_THERM_THRSH1, + REG_THERM_THRSH2, + REG_THERM_THRSH3, REG_MAX_COUNT, }; @@ -111,6 +127,9 @@ static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = { 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 */ }; static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { @@ -123,6 +142,8 @@ static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { 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 */ }; struct qcom_flash_data { @@ -130,9 +151,11 @@ struct qcom_flash_data { struct regmap_field *r_fields[REG_MAX_COUNT]; struct mutex lock; enum hw_type hw_type; + u32 total_ma; u8 leds_count; u8 max_channels; u8 chan_en_bits; + u8 revision; }; struct qcom_flash_led { @@ -143,6 +166,7 @@ struct qcom_flash_led { u32 max_timeout_ms; u32 flash_current_ma; u32 flash_timeout_ms; + u32 current_in_use_ma; u8 *chan_id; u8 chan_count; bool enabled; @@ -172,6 +196,127 @@ static int set_flash_module_en(struct qcom_flash_led *led, bool en) return rc; } +static int update_allowed_flash_current(struct qcom_flash_led *led, u32 *current_ma, bool strobe) +{ + struct qcom_flash_data *flash_data = led->flash_data; + u32 therm_ma, avail_ma, thrsh[3], min_thrsh, sts; + int rc = 0; + + mutex_lock(&flash_data->lock); + /* + * Put previously allocated current into allowed budget in either of these two cases: + * 1) LED is disabled; + * 2) LED is enabled repeatedly + */ + if (!strobe || led->current_in_use_ma != 0) { + if (flash_data->total_ma >= led->current_in_use_ma) + flash_data->total_ma -= led->current_in_use_ma; + else + flash_data->total_ma = 0; + + led->current_in_use_ma = 0; + if (!strobe) + goto unlock; + } + + /* + * Cache the default thermal threshold settings, and set them to the lowest levels before + * reading over-temp real time status. If over-temp has been triggered at the lowest + * threshold, it's very likely that it would be triggered at a higher (default) threshold + * when more flash current is requested. Prevent device from triggering over-temp condition + * by limiting the flash current for the new request. + */ + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH1], &thrsh[0]); + if (rc < 0) + goto unlock; + + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH2], &thrsh[1]); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH3], &thrsh[2]); + if (rc < 0) + goto unlock; + } + + min_thrsh = OTST_3CH_MIN_VAL; + if (flash_data->hw_type == QCOM_MVFLASH_4CH) + min_thrsh = (flash_data->revision == FLASH_4CH_REVISION_V0P1) ? + OTST1_4CH_V0P1_MIN_VAL : OTST1_4CH_MIN_VAL; + + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], min_thrsh); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_4CH) + min_thrsh = OTST2_4CH_MIN_VAL; + + /* + * The default thermal threshold settings have been updated hence + * restore them if any fault happens starting from here. + */ + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh); + if (rc < 0) + goto restore; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], min_thrsh); + if (rc < 0) + goto restore; + } + + /* Read thermal level status to get corresponding derating flash current */ + rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &sts); + if (rc) + goto restore; + + therm_ma = FLASH_TOTAL_CURRENT_MAX_UA / 1000; + if (flash_data->hw_type == QCOM_MVFLASH_3CH) { + if (sts & FLASH_STS_3CH_OTST3) + therm_ma = OTST3_MAX_CURRENT_MA; + else if (sts & FLASH_STS_3CH_OTST2) + therm_ma = OTST2_MAX_CURRENT_MA; + else if (sts & FLASH_STS_3CH_OTST1) + therm_ma = OTST1_MAX_CURRENT_MA; + } else { + if (sts & FLASH_STS_4CH_OTST2) + therm_ma = OTST2_MAX_CURRENT_MA; + else if (sts & FLASH_STS_4CH_OTST1) + therm_ma = OTST1_MAX_CURRENT_MA; + } + + /* Calculate the allowed flash current for the request */ + if (therm_ma <= flash_data->total_ma) + avail_ma = 0; + else + avail_ma = therm_ma - flash_data->total_ma; + + *current_ma = min_t(u32, *current_ma, avail_ma); + led->current_in_use_ma = *current_ma; + flash_data->total_ma += led->current_in_use_ma; + + dev_dbg(led->flash.led_cdev.dev, "allowed flash current: %dmA, total current: %dmA\n", + led->current_in_use_ma, flash_data->total_ma); + +restore: + /* Restore to default thermal threshold settings */ + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], thrsh[0]); + if (rc < 0) + goto unlock; + + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], thrsh[1]); + if (rc < 0) + goto unlock; + + if (flash_data->hw_type == QCOM_MVFLASH_3CH) + rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], thrsh[2]); + +unlock: + mutex_unlock(&flash_data->lock); + return rc; +} + static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode) { struct qcom_flash_data *flash_data = led->flash_data; @@ -313,6 +458,10 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat if (rc) return rc; + rc = update_allowed_flash_current(led, &led->flash_current_ma, state); + if (rc < 0) + return rc; + rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE); if (rc) return rc; @@ -429,6 +578,10 @@ static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev, if (rc) return rc; + rc = update_allowed_flash_current(led, ¤t_ma, enable); + if (rc < 0) + return rc; + rc = set_flash_current(led, current_ma, TORCH_MODE); if (rc) return rc; @@ -505,6 +658,7 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno struct qcom_flash_data *flash_data = led->flash_data; struct v4l2_flash_config v4l2_cfg = { 0 }; struct led_flash_setting *intensity = &v4l2_cfg.intensity; + struct v4l2_flash *v4l2_flash; if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH)) return 0; @@ -523,9 +677,12 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno LED_FAULT_OVER_TEMPERATURE | LED_FAULT_TIMEOUT; - flash_data->v4l2_flash[flash_data->leds_count] = - v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg); - return PTR_ERR_OR_ZERO(flash_data->v4l2_flash); + v4l2_flash = v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg); + if (IS_ERR(v4l2_flash)) + return PTR_ERR(v4l2_flash); + + flash_data->v4l2_flash[flash_data->leds_count] = v4l2_flash; + return 0; } # else static int @@ -655,7 +812,6 @@ static int qcom_flash_led_probe(struct platform_device *pdev) { struct qcom_flash_data *flash_data; struct qcom_flash_led *led; - struct fwnode_handle *child; struct device *dev = &pdev->dev; struct regmap *regmap; struct reg_field *regs; @@ -703,6 +859,14 @@ static int qcom_flash_led_probe(struct platform_device *pdev) flash_data->hw_type = QCOM_MVFLASH_4CH; flash_data->max_channels = 4; regs = mvflash_4ch_regs; + + rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val); + if (rc < 0) { + dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc); + return rc; + } + + flash_data->revision = val; } else { dev_err(dev, "flash LED subtype %#x is not yet supported\n", val); return -ENODEV; @@ -731,7 +895,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev) if (!flash_data->v4l2_flash) return -ENOMEM; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) { rc = -ENOMEM; @@ -749,7 +913,6 @@ static int qcom_flash_led_probe(struct platform_device *pdev) return 0; release: - fwnode_handle_put(child); while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count) v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); return rc; @@ -777,7 +940,7 @@ static struct platform_driver qcom_flash_led_driver = { .of_match_table = qcom_flash_led_match_table, }, .probe = qcom_flash_led_probe, - .remove_new = qcom_flash_led_remove, + .remove = qcom_flash_led_remove, }; module_platform_driver(qcom_flash_led_driver); diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c index 1ae5b387f4a5..f16358b8dfc1 100644 --- a/drivers/leds/flash/leds-rt4505.c +++ b/drivers/leds/flash/leds-rt4505.c @@ -426,4 +426,5 @@ static struct i2c_driver rt4505_driver = { module_i2c_driver(rt4505_driver); MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>"); +MODULE_DESCRIPTION("Richtek RT4505 LED driver"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/flash/leds-rt8515.c b/drivers/leds/flash/leds-rt8515.c index eef426924eaf..32ba397a33d2 100644 --- a/drivers/leds/flash/leds-rt8515.c +++ b/drivers/leds/flash/leds-rt8515.c @@ -127,7 +127,7 @@ static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled, mod_timer(&rt->powerdown_timer, jiffies + usecs_to_jiffies(timeout->val)); } else { - del_timer_sync(&rt->powerdown_timer); + timer_delete_sync(&rt->powerdown_timer); /* Turn the LED off */ rt8515_gpio_led_off(rt); } @@ -372,7 +372,7 @@ static void rt8515_remove(struct platform_device *pdev) struct rt8515 *rt = platform_get_drvdata(pdev); rt8515_v4l2_flash_release(rt); - del_timer_sync(&rt->powerdown_timer); + timer_delete_sync(&rt->powerdown_timer); mutex_destroy(&rt->lock); } @@ -388,7 +388,7 @@ static struct platform_driver rt8515_driver = { .of_match_table = rt8515_match, }, .probe = rt8515_probe, - .remove_new = rt8515_remove, + .remove = rt8515_remove, }; module_platform_driver(rt8515_driver); diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c index db0ac6641954..48fb8a9ec703 100644 --- a/drivers/leds/flash/leds-sgm3140.c +++ b/drivers/leds/flash/leds-sgm3140.c @@ -55,7 +55,7 @@ static int sgm3140_strobe_set(struct led_classdev_flash *fled_cdev, bool state) mod_timer(&priv->powerdown_timer, jiffies + usecs_to_jiffies(priv->timeout)); } else { - del_timer_sync(&priv->powerdown_timer); + timer_delete_sync(&priv->powerdown_timer); gpiod_set_value_cansleep(priv->enable_gpio, 0); gpiod_set_value_cansleep(priv->flash_gpio, 0); ret = regulator_disable(priv->vin_regulator); @@ -117,7 +117,7 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev, gpiod_set_value_cansleep(priv->flash_gpio, 0); gpiod_set_value_cansleep(priv->enable_gpio, 1); } else { - del_timer_sync(&priv->powerdown_timer); + timer_delete_sync(&priv->powerdown_timer); gpiod_set_value_cansleep(priv->flash_gpio, 0); gpiod_set_value_cansleep(priv->enable_gpio, 0); ret = regulator_disable(priv->vin_regulator); @@ -285,7 +285,7 @@ static void sgm3140_remove(struct platform_device *pdev) { struct sgm3140 *priv = platform_get_drvdata(pdev); - del_timer_sync(&priv->powerdown_timer); + timer_delete_sync(&priv->powerdown_timer); v4l2_flash_release(priv->v4l2_flash); } @@ -300,7 +300,7 @@ MODULE_DEVICE_TABLE(of, sgm3140_dt_match); static struct platform_driver sgm3140_driver = { .probe = sgm3140_probe, - .remove_new = sgm3140_remove, + .remove = sgm3140_remove, .driver = { .name = "sgm3140", .of_match_table = sgm3140_dt_match, diff --git a/drivers/leds/flash/leds-sy7802.c b/drivers/leds/flash/leds-sy7802.c new file mode 100644 index 000000000000..ddac836762af --- /dev/null +++ b/drivers/leds/flash/leds-sy7802.c @@ -0,0 +1,539 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Silergy SY7802 flash LED driver with an I2C interface + * + * Copyright 2024 André Apitzsch <git@apitzsch.eu> + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/kernel.h> +#include <linux/led-class-flash.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define SY7802_MAX_LEDS 2 +#define SY7802_LED_JOINT 2 + +#define SY7802_REG_ENABLE 0x10 +#define SY7802_REG_TORCH_BRIGHTNESS 0xa0 +#define SY7802_REG_FLASH_BRIGHTNESS 0xb0 +#define SY7802_REG_FLASH_DURATION 0xc0 +#define SY7802_REG_FLAGS 0xd0 +#define SY7802_REG_CONFIG_1 0xe0 +#define SY7802_REG_CONFIG_2 0xf0 +#define SY7802_REG_VIN_MONITOR 0x80 +#define SY7802_REG_LAST_FLASH 0x81 +#define SY7802_REG_VLED_MONITOR 0x30 +#define SY7802_REG_ADC_DELAY 0x31 +#define SY7802_REG_DEV_ID 0xff + +#define SY7802_MODE_OFF 0 +#define SY7802_MODE_TORCH 2 +#define SY7802_MODE_FLASH 3 +#define SY7802_MODE_MASK GENMASK(1, 0) + +#define SY7802_LEDS_SHIFT 3 +#define SY7802_LEDS_MASK(_id) (BIT(_id) << SY7802_LEDS_SHIFT) +#define SY7802_LEDS_MASK_ALL (SY7802_LEDS_MASK(0) | SY7802_LEDS_MASK(1)) + +#define SY7802_TORCH_CURRENT_SHIFT 3 +#define SY7802_TORCH_CURRENT_MASK(_id) \ + (GENMASK(2, 0) << (SY7802_TORCH_CURRENT_SHIFT * (_id))) +#define SY7802_TORCH_CURRENT_MASK_ALL \ + (SY7802_TORCH_CURRENT_MASK(0) | SY7802_TORCH_CURRENT_MASK(1)) + +#define SY7802_FLASH_CURRENT_SHIFT 4 +#define SY7802_FLASH_CURRENT_MASK(_id) \ + (GENMASK(3, 0) << (SY7802_FLASH_CURRENT_SHIFT * (_id))) +#define SY7802_FLASH_CURRENT_MASK_ALL \ + (SY7802_FLASH_CURRENT_MASK(0) | SY7802_FLASH_CURRENT_MASK(1)) + +#define SY7802_TIMEOUT_DEFAULT_US 512000U +#define SY7802_TIMEOUT_MIN_US 32000U +#define SY7802_TIMEOUT_MAX_US 1024000U +#define SY7802_TIMEOUT_STEPSIZE_US 32000U + +#define SY7802_TORCH_BRIGHTNESS_MAX 8 + +#define SY7802_FLASH_BRIGHTNESS_DEFAULT 14 +#define SY7802_FLASH_BRIGHTNESS_MIN 0 +#define SY7802_FLASH_BRIGHTNESS_MAX 15 +#define SY7802_FLASH_BRIGHTNESS_STEP 1 + +#define SY7802_FLAG_TIMEOUT BIT(0) +#define SY7802_FLAG_THERMAL_SHUTDOWN BIT(1) +#define SY7802_FLAG_LED_FAULT BIT(2) +#define SY7802_FLAG_TX1_INTERRUPT BIT(3) +#define SY7802_FLAG_TX2_INTERRUPT BIT(4) +#define SY7802_FLAG_LED_THERMAL_FAULT BIT(5) +#define SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW BIT(6) +#define SY7802_FLAG_INPUT_VOLTAGE_LOW BIT(7) + +#define SY7802_CHIP_ID 0x51 + +static const struct reg_default sy7802_regmap_defs[] = { + { SY7802_REG_ENABLE, SY7802_LEDS_MASK_ALL }, + { SY7802_REG_TORCH_BRIGHTNESS, 0x92 }, + { SY7802_REG_FLASH_BRIGHTNESS, SY7802_FLASH_BRIGHTNESS_DEFAULT | + SY7802_FLASH_BRIGHTNESS_DEFAULT << SY7802_FLASH_CURRENT_SHIFT }, + { SY7802_REG_FLASH_DURATION, 0x6f }, + { SY7802_REG_FLAGS, 0x0 }, + { SY7802_REG_CONFIG_1, 0x68 }, + { SY7802_REG_CONFIG_2, 0xf0 }, +}; + +struct sy7802_led { + struct led_classdev_flash flash; + struct sy7802 *chip; + u8 led_id; +}; + +struct sy7802 { + struct device *dev; + struct regmap *regmap; + struct mutex mutex; + + struct gpio_desc *enable_gpio; + struct regulator *vin_regulator; + + unsigned int fled_strobe_used; + unsigned int fled_torch_used; + unsigned int leds_active; + int num_leds; + struct sy7802_led leds[] __counted_by(num_leds); +}; + +static int sy7802_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness brightness) +{ + struct sy7802_led *led = container_of(lcdev, struct sy7802_led, flash.led_cdev); + struct sy7802 *chip = led->chip; + u32 fled_torch_used_tmp; + u32 led_enable_mask; + u32 enable_mask; + u32 torch_mask; + u32 val; + int ret; + + mutex_lock(&chip->mutex); + + if (chip->fled_strobe_used) { + dev_warn(chip->dev, "Cannot set torch brightness whilst strobe is enabled\n"); + ret = -EBUSY; + goto unlock; + } + + if (brightness) + fled_torch_used_tmp = chip->fled_torch_used | BIT(led->led_id); + else + fled_torch_used_tmp = chip->fled_torch_used & ~BIT(led->led_id); + + led_enable_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_LEDS_MASK_ALL : + SY7802_LEDS_MASK(led->led_id); + + val = brightness ? led_enable_mask : SY7802_MODE_OFF; + if (fled_torch_used_tmp) + val |= SY7802_MODE_TORCH; + + /* Disable torch to apply brightness */ + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_MASK, + SY7802_MODE_OFF); + if (ret) + goto unlock; + + torch_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_TORCH_CURRENT_MASK_ALL : + SY7802_TORCH_CURRENT_MASK(led->led_id); + + /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */ + if (brightness) + brightness -= 1; + + brightness |= (brightness << SY7802_TORCH_CURRENT_SHIFT); + + ret = regmap_update_bits(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, torch_mask, brightness); + if (ret) + goto unlock; + + enable_mask = SY7802_MODE_MASK | led_enable_mask; + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val); + if (ret) + goto unlock; + + chip->fled_torch_used = fled_torch_used_tmp; + +unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int sy7802_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct led_flash_setting *s = &fl_cdev->brightness; + u32 val = (brightness - s->min) / s->step; + struct sy7802 *chip = led->chip; + u32 flash_mask; + int ret; + + val |= (val << SY7802_FLASH_CURRENT_SHIFT); + flash_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_FLASH_CURRENT_MASK_ALL : + SY7802_FLASH_CURRENT_MASK(led->led_id); + + mutex_lock(&chip->mutex); + ret = regmap_update_bits(chip->regmap, SY7802_REG_FLASH_BRIGHTNESS, flash_mask, val); + mutex_unlock(&chip->mutex); + + return ret; +} + +static int sy7802_strobe_set(struct led_classdev_flash *fl_cdev, bool state) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + u32 fled_strobe_used_tmp; + u32 led_enable_mask; + u32 enable_mask; + u32 val; + int ret; + + mutex_lock(&chip->mutex); + + if (chip->fled_torch_used) { + dev_warn(chip->dev, "Cannot set strobe brightness whilst torch is enabled\n"); + ret = -EBUSY; + goto unlock; + } + + if (state) + fled_strobe_used_tmp = chip->fled_strobe_used | BIT(led->led_id); + else + fled_strobe_used_tmp = chip->fled_strobe_used & ~BIT(led->led_id); + + led_enable_mask = led->led_id == SY7802_LED_JOINT ? + SY7802_LEDS_MASK_ALL : + SY7802_LEDS_MASK(led->led_id); + + val = state ? led_enable_mask : SY7802_MODE_OFF; + if (fled_strobe_used_tmp) + val |= SY7802_MODE_FLASH; + + enable_mask = SY7802_MODE_MASK | led_enable_mask; + ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val); + + if (ret) + goto unlock; + + chip->fled_strobe_used = fled_strobe_used_tmp; + +unlock: + mutex_unlock(&chip->mutex); + return ret; +} + +static int sy7802_strobe_get(struct led_classdev_flash *fl_cdev, bool *state) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + + mutex_lock(&chip->mutex); + *state = !!(chip->fled_strobe_used & BIT(led->led_id)); + mutex_unlock(&chip->mutex); + + return 0; +} + +static int sy7802_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct led_flash_setting *s = &fl_cdev->timeout; + u32 val = (timeout - s->min) / s->step; + struct sy7802 *chip = led->chip; + + return regmap_write(chip->regmap, SY7802_REG_FLASH_DURATION, val); +} + +static int sy7802_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault) +{ + struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash); + struct sy7802 *chip = led->chip; + u32 val, led_faults = 0; + int ret; + + /* NOTE: reading register clears fault status */ + ret = regmap_read(chip->regmap, SY7802_REG_FLAGS, &val); + if (ret) + return ret; + + if (val & (SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW | SY7802_FLAG_INPUT_VOLTAGE_LOW)) + led_faults |= LED_FAULT_INPUT_VOLTAGE; + + if (val & SY7802_FLAG_THERMAL_SHUTDOWN) + led_faults |= LED_FAULT_OVER_TEMPERATURE; + + if (val & SY7802_FLAG_TIMEOUT) + led_faults |= LED_FAULT_TIMEOUT; + + *fault = led_faults; + return 0; +} + +static const struct led_flash_ops sy7802_flash_ops = { + .flash_brightness_set = sy7802_flash_brightness_set, + .strobe_set = sy7802_strobe_set, + .strobe_get = sy7802_strobe_get, + .timeout_set = sy7802_timeout_set, + .fault_get = sy7802_fault_get, +}; + +static void sy7802_init_flash_brightness(struct led_classdev_flash *fl_cdev) +{ + struct led_flash_setting *s; + + /* Init flash brightness setting */ + s = &fl_cdev->brightness; + s->min = SY7802_FLASH_BRIGHTNESS_MIN; + s->max = SY7802_FLASH_BRIGHTNESS_MAX; + s->step = SY7802_FLASH_BRIGHTNESS_STEP; + s->val = SY7802_FLASH_BRIGHTNESS_DEFAULT; +} + +static void sy7802_init_flash_timeout(struct led_classdev_flash *fl_cdev) +{ + struct led_flash_setting *s; + + /* Init flash timeout setting */ + s = &fl_cdev->timeout; + s->min = SY7802_TIMEOUT_MIN_US; + s->max = SY7802_TIMEOUT_MAX_US; + s->step = SY7802_TIMEOUT_STEPSIZE_US; + s->val = SY7802_TIMEOUT_DEFAULT_US; +} + +static int sy7802_led_register(struct device *dev, struct sy7802_led *led, + struct device_node *np) +{ + struct led_init_data init_data = {}; + int ret; + + init_data.fwnode = of_fwnode_handle(np); + + ret = devm_led_classdev_flash_register_ext(dev, &led->flash, &init_data); + if (ret) { + dev_err(dev, "Couldn't register flash %d\n", led->led_id); + return ret; + } + + return 0; +} + +static int sy7802_init_flash_properties(struct device *dev, struct sy7802_led *led, + struct device_node *np) +{ + struct led_classdev_flash *flash = &led->flash; + struct led_classdev *lcdev = &flash->led_cdev; + u32 sources[SY7802_MAX_LEDS]; + int i, num, ret; + + num = of_property_count_u32_elems(np, "led-sources"); + if (num < 1) { + dev_err(dev, "Not specified or wrong number of led-sources\n"); + return -EINVAL; + } + + ret = of_property_read_u32_array(np, "led-sources", sources, num); + if (ret) + return ret; + + for (i = 0; i < num; i++) { + if (sources[i] >= SY7802_MAX_LEDS) + return -EINVAL; + if (led->chip->leds_active & BIT(sources[i])) + return -EINVAL; + led->chip->leds_active |= BIT(sources[i]); + } + + /* If both channels are specified in 'led-sources', joint flash output mode is used */ + led->led_id = num == 2 ? SY7802_LED_JOINT : sources[0]; + + lcdev->max_brightness = SY7802_TORCH_BRIGHTNESS_MAX; + lcdev->brightness_set_blocking = sy7802_torch_brightness_set; + lcdev->flags |= LED_DEV_CAP_FLASH; + + flash->ops = &sy7802_flash_ops; + + sy7802_init_flash_brightness(flash); + sy7802_init_flash_timeout(flash); + + return 0; +} + +static int sy7802_chip_check(struct sy7802 *chip) +{ + struct device *dev = chip->dev; + u32 chipid; + int ret; + + ret = regmap_read(chip->regmap, SY7802_REG_DEV_ID, &chipid); + if (ret) + return dev_err_probe(dev, ret, "Failed to read chip ID\n"); + + if (chipid != SY7802_CHIP_ID) + return dev_err_probe(dev, -ENODEV, "Unsupported chip detected: %x\n", chipid); + + return 0; +} + +static void sy7802_enable(struct sy7802 *chip) +{ + gpiod_set_value_cansleep(chip->enable_gpio, 1); + usleep_range(200, 300); +} + +static void sy7802_disable(struct sy7802 *chip) +{ + gpiod_set_value_cansleep(chip->enable_gpio, 0); +} + +static int sy7802_probe_dt(struct sy7802 *chip) +{ + struct device_node *np = dev_of_node(chip->dev); + int child_num; + int ret; + + regmap_write(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_OFF); + regmap_write(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, LED_OFF); + + child_num = 0; + for_each_available_child_of_node_scoped(np, child) { + struct sy7802_led *led = chip->leds + child_num; + + led->chip = chip; + led->led_id = child_num; + + ret = sy7802_init_flash_properties(chip->dev, led, child); + if (ret) + return ret; + + ret = sy7802_led_register(chip->dev, led, child); + if (ret) + return ret; + + child_num++; + } + return 0; +} + +static void sy7802_chip_disable_action(void *data) +{ + struct sy7802 *chip = data; + + sy7802_disable(chip); +} + +static void sy7802_regulator_disable_action(void *data) +{ + struct sy7802 *chip = data; + + regulator_disable(chip->vin_regulator); +} + +static const struct regmap_config sy7802_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xff, + .cache_type = REGCACHE_MAPLE, + .reg_defaults = sy7802_regmap_defs, + .num_reg_defaults = ARRAY_SIZE(sy7802_regmap_defs), +}; + +static int sy7802_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct sy7802 *chip; + size_t count; + int ret; + + count = device_get_child_node_count(dev); + if (!count || count > SY7802_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, "Invalid amount of LED nodes %zu\n", count); + + chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->num_leds = count; + + chip->dev = dev; + i2c_set_clientdata(client, chip); + + chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW); + ret = PTR_ERR_OR_ZERO(chip->enable_gpio); + if (ret) + return dev_err_probe(dev, ret, "Failed to request enable gpio\n"); + + chip->vin_regulator = devm_regulator_get(dev, "vin"); + ret = PTR_ERR_OR_ZERO(chip->vin_regulator); + if (ret) + return dev_err_probe(dev, ret, "Failed to request regulator\n"); + + ret = regulator_enable(chip->vin_regulator); + if (ret) + return dev_err_probe(dev, ret, "Failed to enable regulator\n"); + + ret = devm_add_action_or_reset(dev, sy7802_regulator_disable_action, chip); + if (ret) + return ret; + + ret = devm_mutex_init(dev, &chip->mutex); + if (ret) + return ret; + + mutex_lock(&chip->mutex); + + chip->regmap = devm_regmap_init_i2c(client, &sy7802_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = PTR_ERR(chip->regmap); + dev_err_probe(dev, ret, "Failed to allocate register map\n"); + goto error; + } + + ret = sy7802_probe_dt(chip); + if (ret < 0) + goto error; + + sy7802_enable(chip); + + ret = devm_add_action_or_reset(dev, sy7802_chip_disable_action, chip); + if (ret) + goto error; + + ret = sy7802_chip_check(chip); + +error: + mutex_unlock(&chip->mutex); + return ret; +} + +static const struct of_device_id __maybe_unused sy7802_leds_match[] = { + { .compatible = "silergy,sy7802", }, + {} +}; +MODULE_DEVICE_TABLE(of, sy7802_leds_match); + +static struct i2c_driver sy7802_driver = { + .driver = { + .name = "sy7802", + .of_match_table = of_match_ptr(sy7802_leds_match), + }, + .probe = sy7802_probe, +}; +module_i2c_driver(sy7802_driver); + +MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>"); +MODULE_DESCRIPTION("Silergy SY7802 flash LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c index 6fe9d700dfef..f4e26ce84862 100644 --- a/drivers/leds/led-class-flash.c +++ b/drivers/leds/led-class-flash.c @@ -12,7 +12,6 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/slab.h> -#include "leds.h" #define has_flash_op(fled_cdev, op) \ (fled_cdev && fled_cdev->ops->op) diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c index ec62a4811613..b2a87c994816 100644 --- a/drivers/leds/led-class-multicolor.c +++ b/drivers/leds/led-class-multicolor.c @@ -11,8 +11,6 @@ #include <linux/slab.h> #include <linux/uaccess.h> -#include "leds.h" - int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, enum led_brightness brightness) { @@ -101,7 +99,7 @@ static ssize_t multi_index_show(struct device *dev, for (i = 0; i < mcled_cdev->num_colors; i++) { index = mcled_cdev->subled_info[i].color_index; - len += sprintf(buf + len, "%s", led_colors[index]); + len += sprintf(buf + len, "%s", led_get_color_name(index)); if (i < mcled_cdev->num_colors - 1) len += sprintf(buf + len, " "); } @@ -134,6 +132,7 @@ int led_classdev_multicolor_register_ext(struct device *parent, return -EINVAL; led_cdev = &mcled_cdev->led_cdev; + led_cdev->flags |= LED_MULTI_COLOR; mcled_cdev->led_cdev.groups = led_multicolor_groups; return led_classdev_register_ext(parent, led_cdev, init_data); diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 24fcff682b24..c20ac8ccf52b 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -25,15 +25,20 @@ static DEFINE_MUTEX(leds_lookup_lock); static LIST_HEAD(leds_lookup_list); +static struct workqueue_struct *leds_wq; + static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned int brightness; - /* no lock needed for this */ + mutex_lock(&led_cdev->led_access); led_update_brightness(led_cdev); + brightness = led_cdev->brightness; + mutex_unlock(&led_cdev->led_access); - return sprintf(buf, "%u\n", led_cdev->brightness); + return sprintf(buf, "%u\n", brightness); } static ssize_t brightness_store(struct device *dev, @@ -57,7 +62,6 @@ static ssize_t brightness_store(struct device *dev, if (state == LED_OFF) led_trigger_remove(led_cdev); led_set_brightness(led_cdev, state); - flush_work(&led_cdev->set_brightness_work); ret = size; unlock: @@ -70,19 +74,24 @@ static ssize_t max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned int max_brightness; + + mutex_lock(&led_cdev->led_access); + max_brightness = led_cdev->max_brightness; + mutex_unlock(&led_cdev->led_access); - return sprintf(buf, "%u\n", led_cdev->max_brightness); + return sprintf(buf, "%u\n", max_brightness); } 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 @@ -258,7 +267,6 @@ struct led_classdev *of_led_get(struct device_node *np, int index) led_dev = class_find_device_by_of_node(&leds_class, led_node); of_node_put(led_node); - put_device(led_dev); return led_module_get(led_dev); } @@ -503,6 +511,11 @@ int led_classdev_register_ext(struct device *parent, ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name)); if (ret < 0) return ret; + else if (ret && led_cdev->flags & LED_REJECT_NAME_CONFLICT) + return -EEXIST; + else if (ret) + dev_warn(parent, "Led %s renamed to %s due to name collision\n", + proposed_name, final_name); if (led_cdev->color >= LED_COLOR_ID_MAX) dev_warn(parent, "LED %s color identifier out of range\n", final_name); @@ -518,10 +531,6 @@ int led_classdev_register_ext(struct device *parent, if (init_data && init_data->fwnode) device_set_node(led_cdev->dev, init_data->fwnode); - if (ret) - dev_warn(parent, "Led %s renamed to %s due to name collision", - proposed_name, dev_name(led_cdev->dev)); - if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) { ret = led_add_brightness_hw_changed(led_cdev); if (ret) { @@ -549,15 +558,11 @@ int led_classdev_register_ext(struct device *parent, led_update_brightness(led_cdev); + led_cdev->wq = leds_wq; + led_init_core(led_cdev); #ifdef CONFIG_LEDS_TRIGGERS - /* - * If no default trigger was given and hw_control_trigger is set, - * make it the default trigger. - */ - if (!led_cdev->default_trigger && led_cdev->hw_control_trigger) - led_cdev->default_trigger = led_cdev->hw_control_trigger; led_trigger_set_default(led_cdev); #endif @@ -673,12 +678,19 @@ EXPORT_SYMBOL_GPL(devm_led_classdev_unregister); static int __init leds_init(void) { + leds_wq = alloc_ordered_workqueue("leds", 0); + if (!leds_wq) { + pr_err("Failed to create LEDs ordered workqueue\n"); + return -ENOMEM; + } + return class_register(&leds_class); } static void __exit leds_exit(void) { class_unregister(&leds_class); + destroy_workqueue(leds_wq); } subsys_initcall(leds_init); diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 89c9806cc97f..907fc703e0c5 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -8,6 +8,7 @@ */ #include <linux/kernel.h> +#include <linux/led-class-multicolor.h> #include <linux/leds.h> #include <linux/list.h> #include <linux/module.h> @@ -25,7 +26,7 @@ EXPORT_SYMBOL_GPL(leds_list_lock); LIST_HEAD(leds_list); EXPORT_SYMBOL_GPL(leds_list); -const char * const led_colors[LED_COLOR_ID_MAX] = { +static const char * const led_colors[LED_COLOR_ID_MAX] = { [LED_COLOR_ID_WHITE] = "white", [LED_COLOR_ID_RED] = "red", [LED_COLOR_ID_GREEN] = "green", @@ -42,7 +43,6 @@ const char * const led_colors[LED_COLOR_ID_MAX] = { [LED_COLOR_ID_CYAN] = "cyan", [LED_COLOR_ID_LIME] = "lime", }; -EXPORT_SYMBOL_GPL(led_colors); static int __led_set_brightness(struct led_classdev *led_cdev, unsigned int value) { @@ -122,15 +122,22 @@ static void led_timer_function(struct timer_list *t) static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev, unsigned int value) { - int ret = 0; + int ret; ret = __led_set_brightness(led_cdev, value); - if (ret == -ENOTSUPP) + if (ret == -ENOTSUPP) { ret = __led_set_brightness_blocking(led_cdev, value); - if (ret < 0 && - /* LED HW might have been unplugged, therefore don't warn */ - !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) && - (led_cdev->flags & LED_HW_PLUGGABLE))) + if (ret == -ENOTSUPP) + /* No back-end support to set a fixed brightness value */ + return; + } + + /* LED HW might have been unplugged, therefore don't warn */ + if (ret == -ENODEV && led_cdev->flags & LED_UNREGISTERING && + led_cdev->flags & LED_HW_PLUGGABLE) + return; + + if (ret < 0) dev_err(led_cdev->dev, "Setting an LED's brightness failed (%d)\n", ret); } @@ -152,8 +159,19 @@ static void set_brightness_delayed(struct work_struct *ws) * before this work item runs once. To make sure this works properly * handle LED_SET_BRIGHTNESS_OFF first. */ - if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) + if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) { set_brightness_delayed_set_brightness(led_cdev, LED_OFF); + /* + * The consecutives led_set_brightness(LED_OFF), + * led_set_brightness(LED_FULL) could have been executed out of + * order (LED_FULL first), if the work_flags has been set + * between LED_SET_BRIGHTNESS_OFF and LED_SET_BRIGHTNESS of this + * work. To avoid ending with the LED turned off, turn the LED + * on again. + */ + if (led_cdev->delayed_set_value != LED_OFF) + set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); + } if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags)) set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value); @@ -227,7 +245,7 @@ void led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { - del_timer_sync(&led_cdev->blink_timer); + timer_delete_sync(&led_cdev->blink_timer); clear_bit(LED_BLINK_SW, &led_cdev->work_flags); clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags); @@ -266,7 +284,7 @@ void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on led_cdev->delayed_delay_on = delay_on; led_cdev->delayed_delay_off = delay_off; set_bit(LED_SET_BLINK, &led_cdev->work_flags); - schedule_work(&led_cdev->set_brightness_work); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); return; } @@ -276,7 +294,7 @@ EXPORT_SYMBOL_GPL(led_blink_set_nosleep); void led_stop_software_blink(struct led_classdev *led_cdev) { - del_timer_sync(&led_cdev->blink_timer); + timer_delete_sync(&led_cdev->blink_timer); led_cdev->blink_delay_on = 0; led_cdev->blink_delay_off = 0; clear_bit(LED_BLINK_SW, &led_cdev->work_flags); @@ -297,7 +315,7 @@ void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness) */ if (!brightness) { set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags); - schedule_work(&led_cdev->set_brightness_work); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); } else { set_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags); @@ -324,16 +342,19 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value) * change is done immediately afterwards (before the work runs), * it uses a separate work_flag. */ - if (value) { - led_cdev->delayed_set_value = value; + led_cdev->delayed_set_value = value; + /* Ensure delayed_set_value is seen before work_flags modification */ + smp_mb__before_atomic(); + + if (value) set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); - } else { + else { clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags); clear_bit(LED_SET_BLINK, &led_cdev->work_flags); set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags); } - schedule_work(&led_cdev->set_brightness_work); + queue_work(led_cdev->wq, &led_cdev->set_brightness_work); } EXPORT_SYMBOL_GPL(led_set_brightness_nopm); @@ -362,6 +383,36 @@ int led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value) } EXPORT_SYMBOL_GPL(led_set_brightness_sync); +/* + * This is a led-core function because just like led_set_brightness() + * it is used in the kernel by e.g. triggers. + */ +void led_mc_set_brightness(struct led_classdev *led_cdev, + unsigned int *intensity_value, unsigned int num_colors, + unsigned int brightness) +{ + struct led_classdev_mc *mcled_cdev; + unsigned int i; + + if (!(led_cdev->flags & LED_MULTI_COLOR)) { + dev_err_once(led_cdev->dev, "error not a multi-color LED\n"); + return; + } + + mcled_cdev = lcdev_to_mccdev(led_cdev); + if (num_colors != mcled_cdev->num_colors) { + dev_err_once(led_cdev->dev, "error num_colors mismatch %u != %u\n", + num_colors, mcled_cdev->num_colors); + return; + } + + for (i = 0; i < mcled_cdev->num_colors; i++) + mcled_cdev->subled_info[i].intensity = intensity_value[i]; + + led_set_brightness(led_cdev, brightness); +} +EXPORT_SYMBOL_GPL(led_mc_set_brightness); + int led_update_brightness(struct led_classdev *led_cdev) { int ret; @@ -534,6 +585,15 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data, } EXPORT_SYMBOL_GPL(led_compose_name); +const char *led_get_color_name(u8 color_id) +{ + if (color_id >= ARRAY_SIZE(led_colors)) + return NULL; + + return led_colors[color_id]; +} +EXPORT_SYMBOL_GPL(led_get_color_name); + enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode) { const char *state = NULL; diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 0f5ac30053ad..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); @@ -179,9 +179,9 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) cancel_work_sync(&led_cdev->set_brightness_work); led_stop_software_blink(led_cdev); + device_remove_groups(led_cdev->dev, led_cdev->trigger->groups); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); - device_remove_groups(led_cdev->dev, led_cdev->trigger->groups); led_cdev->trigger = NULL; led_cdev->trigger_data = NULL; led_cdev->activated = false; @@ -194,11 +194,24 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) spin_unlock(&trig->leddev_list_lock); led_cdev->trigger = trig; + /* + * Some activate() calls use led_trigger_event() to initialize + * the brightness of the LED for which the trigger is being set. + * Ensure the led_cdev is visible on trig->led_cdevs for this. + */ + synchronize_rcu(); + + /* + * If "set brightness to 0" is pending in workqueue, + * we don't want that to be reordered after ->activate() + */ + flush_work(&led_cdev->set_brightness_work); + + ret = 0; if (trig->activate) ret = trig->activate(led_cdev); else - ret = 0; - + led_set_brightness(led_cdev, trig->brightness); if (ret) goto err_activate; @@ -387,6 +400,8 @@ void led_trigger_event(struct led_trigger *trig, if (!trig) return; + trig->brightness = brightness; + rcu_read_lock(); list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) led_set_brightness(led_cdev, brightness); @@ -394,6 +409,26 @@ void led_trigger_event(struct led_trigger *trig, } EXPORT_SYMBOL_GPL(led_trigger_event); +void led_mc_trigger_event(struct led_trigger *trig, + unsigned int *intensity_value, unsigned int num_colors, + enum led_brightness brightness) +{ + struct led_classdev *led_cdev; + + if (!trig) + return; + + rcu_read_lock(); + list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) { + if (!(led_cdev->flags & LED_MULTI_COLOR)) + continue; + + led_mc_set_brightness(led_cdev, intensity_value, num_colors, brightness); + } + rcu_read_unlock(); +} +EXPORT_SYMBOL_GPL(led_mc_trigger_event); + static void led_trigger_blink_setup(struct led_trigger *trig, unsigned long delay_on, unsigned long delay_off, diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index 033ab5fed38a..ef5c6c4667ab 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -115,7 +115,7 @@ static int pm860x_led_set(struct led_classdev *cdev, static int pm860x_led_dt_init(struct platform_device *pdev, struct pm860x_led *data) { - struct device_node *nproot, *np; + struct device_node *nproot; int iset = 0; if (!dev_of_node(pdev->dev.parent)) @@ -125,12 +125,11 @@ static int pm860x_led_dt_init(struct platform_device *pdev, dev_err(&pdev->dev, "failed to find leds node\n"); return -ENODEV; } - for_each_available_child_of_node(nproot, np) { + for_each_available_child_of_node_scoped(nproot, np) { if (of_node_name_eq(np, data->name)) { of_property_read_u32(np, "marvell,88pm860x-iset", &iset); data->iset = PM8606_LED_CURRENT(iset); - of_node_put(np); break; } } @@ -227,7 +226,7 @@ static struct platform_driver pm860x_led_driver = { .name = "88pm860x-led", }, .probe = pm860x_led_probe, - .remove_new = pm860x_led_remove, + .remove = pm860x_led_remove, }; module_platform_driver(pm860x_led_driver); diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c index d89a4dca50ae..13e5bc80e56e 100644 --- a/drivers/leds/leds-adp5520.c +++ b/drivers/leds/leds-adp5520.c @@ -184,7 +184,7 @@ static struct platform_driver adp5520_led_driver = { .name = "adp5520-led", }, .probe = adp5520_led_probe, - .remove_new = adp5520_led_remove, + .remove = adp5520_led_remove, }; module_platform_driver(adp5520_led_driver); diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c index 0216afed3b6e..a42cc4bc6917 100644 --- a/drivers/leds/leds-an30259a.c +++ b/drivers/leds/leds-an30259a.c @@ -283,7 +283,10 @@ static int an30259a_probe(struct i2c_client *client) if (err < 0) return err; - mutex_init(&chip->mutex); + err = devm_mutex_init(&client->dev, &chip->mutex); + if (err) + return err; + chip->client = client; i2c_set_clientdata(client, chip); @@ -317,17 +320,9 @@ static int an30259a_probe(struct i2c_client *client) return 0; exit: - mutex_destroy(&chip->mutex); return err; } -static void an30259a_remove(struct i2c_client *client) -{ - struct an30259a *chip = i2c_get_clientdata(client); - - mutex_destroy(&chip->mutex); -} - static const struct of_device_id an30259a_match_table[] = { { .compatible = "panasonic,an30259a", }, { /* sentinel */ }, @@ -336,8 +331,8 @@ static const struct of_device_id an30259a_match_table[] = { MODULE_DEVICE_TABLE(of, an30259a_match_table); static const struct i2c_device_id an30259a_id[] = { - { "an30259a", 0 }, - { /* sentinel */ }, + { "an30259a" }, + { /* sentinel */ } }; MODULE_DEVICE_TABLE(i2c, an30259a_id); @@ -347,7 +342,6 @@ static struct i2c_driver an30259a_driver = { .of_match_table = an30259a_match_table, }, .probe = an30259a_probe, - .remove = an30259a_remove, .id_table = an30259a_id, }; diff --git a/drivers/leds/leds-apu.c b/drivers/leds/leds-apu.c index c409b80c236d..1c116aaa9b6e 100644 --- a/drivers/leds/leds-apu.c +++ b/drivers/leds/leds-apu.c @@ -181,8 +181,7 @@ static int __init apu_led_init(void) struct platform_device *pdev; int err; - if (!(dmi_match(DMI_SYS_VENDOR, "PC Engines") && - (dmi_match(DMI_PRODUCT_NAME, "APU") || dmi_match(DMI_PRODUCT_NAME, "apu1")))) { + if (!dmi_check_system(apu_led_dmi_table)) { pr_err("No PC Engines APUv1 board detected. For APUv2,3 support, enable CONFIG_PCENGINES_APU2\n"); return -ENODEV; } diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 6c8c9f2c19e3..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, @@ -409,7 +409,6 @@ static int aw200xx_probe_get_display_rows(struct device *dev, static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) { - struct fwnode_handle *child; u32 current_min, current_max, min_uA; int ret; int i; @@ -424,7 +423,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) min_uA = UINT_MAX; i = 0; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_init_data init_data = {}; struct aw200xx_led *led; u32 source, imax; @@ -468,10 +467,8 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); - if (ret) { - fwnode_handle_put(child); + if (ret) break; - } i++; } @@ -530,6 +527,16 @@ static const struct regmap_config aw200xx_regmap_config = { .disable_locking = true, }; +static void aw200xx_chip_reset_action(void *data) +{ + aw200xx_chip_reset(data); +} + +static void aw200xx_disable_action(void *data) +{ + aw200xx_disable(data); +} + static int aw200xx_probe(struct i2c_client *client) { const struct aw200xx_chipdef *cdef; @@ -568,11 +575,17 @@ static int aw200xx_probe(struct i2c_client *client) aw200xx_enable(chip); + ret = devm_add_action(&client->dev, aw200xx_disable_action, chip); + if (ret) + return ret; + ret = aw200xx_chip_check(chip); if (ret) return ret; - mutex_init(&chip->mutex); + ret = devm_mutex_init(&client->dev, &chip->mutex); + if (ret) + return ret; /* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */ mutex_lock(&chip->mutex); @@ -581,6 +594,10 @@ static int aw200xx_probe(struct i2c_client *client) if (ret) goto out_unlock; + ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip); + if (ret) + goto out_unlock; + ret = aw200xx_probe_fw(&client->dev, chip); if (ret) goto out_unlock; @@ -595,15 +612,6 @@ out_unlock: return ret; } -static void aw200xx_remove(struct i2c_client *client) -{ - struct aw200xx *chip = i2c_get_clientdata(client); - - aw200xx_chip_reset(chip); - aw200xx_disable(chip); - mutex_destroy(&chip->mutex); -} - static const struct aw200xx_chipdef aw20036_cdef = { .channels = 36, .display_size_rows_max = 3, @@ -652,7 +660,6 @@ static struct i2c_driver aw200xx_driver = { .of_match_table = aw200xx_match_table, }, .probe = aw200xx_probe, - .remove = aw200xx_remove, .id_table = aw200xx_id, }; module_i2c_driver(aw200xx_driver); diff --git a/drivers/leds/leds-aw2013.c b/drivers/leds/leds-aw2013.c index 17235a5e576a..216755d6010f 100644 --- a/drivers/leds/leds-aw2013.c +++ b/drivers/leds/leds-aw2013.c @@ -263,7 +263,7 @@ out: static int aw2013_probe_dt(struct aw2013 *chip) { - struct device_node *np = dev_of_node(&chip->client->dev), *child; + struct device_node *np = dev_of_node(&chip->client->dev); int count, ret = 0, i = 0; struct aw2013_led *led; @@ -273,7 +273,7 @@ static int aw2013_probe_dt(struct aw2013 *chip) regmap_write(chip->regmap, AW2013_RSTR, AW2013_RSTR_RESET); - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { struct led_init_data init_data = {}; u32 source; u32 imax; @@ -304,10 +304,8 @@ static int aw2013_probe_dt(struct aw2013 *chip) ret = devm_led_classdev_register_ext(&chip->client->dev, &led->cdev, &init_data); - if (ret < 0) { - of_node_put(child); + if (ret < 0) return ret; - } i++; } @@ -320,6 +318,11 @@ static int aw2013_probe_dt(struct aw2013 *chip) return 0; } +static void aw2013_chip_disable_action(void *data) +{ + aw2013_chip_disable(data); +} + static const struct regmap_config aw2013_regmap_config = { .reg_bits = 8, .val_bits = 8, @@ -336,7 +339,10 @@ static int aw2013_probe(struct i2c_client *client) if (!chip) return -ENOMEM; - mutex_init(&chip->mutex); + ret = devm_mutex_init(&client->dev, &chip->mutex); + if (ret) + return ret; + mutex_lock(&chip->mutex); chip->client = client; @@ -384,6 +390,10 @@ static int aw2013_probe(struct i2c_client *client) goto error_reg; } + ret = devm_add_action(&client->dev, aw2013_chip_disable_action, chip); + if (ret) + goto error_reg; + ret = aw2013_probe_dt(chip); if (ret < 0) goto error_reg; @@ -406,19 +416,9 @@ error_reg: error: mutex_unlock(&chip->mutex); - mutex_destroy(&chip->mutex); return ret; } -static void aw2013_remove(struct i2c_client *client) -{ - struct aw2013 *chip = i2c_get_clientdata(client); - - aw2013_chip_disable(chip); - - mutex_destroy(&chip->mutex); -} - static const struct of_device_id aw2013_match_table[] = { { .compatible = "awinic,aw2013", }, { /* sentinel */ }, @@ -432,7 +432,6 @@ static struct i2c_driver aw2013_driver = { .of_match_table = aw2013_match_table, }, .probe = aw2013_probe, - .remove = aw2013_remove, }; module_i2c_driver(aw2013_driver); diff --git a/drivers/leds/leds-bcm6328.c b/drivers/leds/leds-bcm6328.c index 246f1296ab09..592bbf4b7e35 100644 --- a/drivers/leds/leds-bcm6328.c +++ b/drivers/leds/leds-bcm6328.c @@ -113,7 +113,7 @@ static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value) unsigned long val, shift; shift = bcm6328_pin2shift(led->pin); - if (shift / 16) + if (shift >= 16) mode = led->mem + BCM6328_REG_MODE_HI; else mode = led->mem + BCM6328_REG_MODE_LO; @@ -357,7 +357,7 @@ static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg, break; case LEDS_DEFSTATE_KEEP: shift = bcm6328_pin2shift(led->pin); - if (shift / 16) + if (shift >= 16) mode = mem + BCM6328_REG_MODE_HI; else mode = mem + BCM6328_REG_MODE_LO; @@ -392,7 +392,6 @@ static int bcm6328_leds_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev_of_node(&pdev->dev); - struct device_node *child; void __iomem *mem; spinlock_t *lock; /* memory lock */ unsigned long val, *blink_leds, *blink_delay; @@ -435,7 +434,7 @@ static int bcm6328_leds_probe(struct platform_device *pdev) val |= BCM6328_SERIAL_LED_SHIFT_DIR; bcm6328_led_write(mem + BCM6328_REG_INIT, val); - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { int rc; u32 reg; @@ -454,10 +453,8 @@ static int bcm6328_leds_probe(struct platform_device *pdev) rc = bcm6328_led(dev, child, reg, mem, lock, blink_leds, blink_delay); - if (rc < 0) { - of_node_put(child); + if (rc < 0) return rc; - } } return 0; diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c index 86e51d44a5a7..51fcff2a64fd 100644 --- a/drivers/leds/leds-bcm6358.c +++ b/drivers/leds/leds-bcm6358.c @@ -147,7 +147,6 @@ static int bcm6358_leds_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev_of_node(&pdev->dev); - struct device_node *child; void __iomem *mem; spinlock_t *lock; /* memory lock */ unsigned long val; @@ -184,7 +183,7 @@ static int bcm6358_leds_probe(struct platform_device *pdev) } bcm6358_led_write(mem + BCM6358_REG_CTRL, val); - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { int rc; u32 reg; @@ -198,10 +197,8 @@ static int bcm6358_leds_probe(struct platform_device *pdev) } rc = bcm6358_led(dev, child, reg, mem, lock); - if (rc < 0) { - of_node_put(child); + if (rc < 0) return rc; - } } return 0; diff --git a/drivers/leds/leds-bd2606mvv.c b/drivers/leds/leds-bd2606mvv.c index 3fda712d2f80..c1181a35d0f7 100644 --- a/drivers/leds/leds-bd2606mvv.c +++ b/drivers/leds/leds-bd2606mvv.c @@ -69,16 +69,14 @@ static const struct regmap_config bd2606mvv_regmap = { static int bd2606mvv_probe(struct i2c_client *client) { - struct fwnode_handle *np, *child; struct device *dev = &client->dev; struct bd2606mvv_priv *priv; struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 }; int active_pairs[BD2606_MAX_LEDS / 2] = { 0 }; int err, reg; - int i; + int i, j; - np = dev_fwnode(dev); - if (!np) + if (!dev_fwnode(dev)) return -ENODEV; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); @@ -94,20 +92,18 @@ static int bd2606mvv_probe(struct i2c_client *client) i2c_set_clientdata(client, priv); - fwnode_for_each_available_child_node(np, child) { + device_for_each_child_node_scoped(dev, child) { struct bd2606mvv_led *led; err = fwnode_property_read_u32(child, "reg", ®); - if (err) { - fwnode_handle_put(child); + if (err) return err; - } - if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) { - fwnode_handle_put(child); + + if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) return -EINVAL; - } + led = &priv->leds[reg]; - led_fwnodes[reg] = child; + led_fwnodes[reg] = fwnode_handle_get(child); active_pairs[reg / 2]++; led->priv = priv; led->led_no = reg; @@ -130,7 +126,8 @@ static int bd2606mvv_probe(struct i2c_client *client) &priv->leds[i].ldev, &init_data); if (err < 0) { - fwnode_handle_put(child); + for (j = i; j < BD2606_MAX_LEDS; j++) + fwnode_handle_put(led_fwnodes[j]); return dev_err_probe(dev, err, "couldn't register LED %s\n", priv->leds[i].ldev.name); diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 0792ea126cea..2a08c5f27608 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -776,7 +776,7 @@ static int bd2802_resume(struct device *dev) static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); static const struct i2c_device_id bd2802_id[] = { - { "BD2802", 0 }, + { "BD2802" }, { } }; MODULE_DEVICE_TABLE(i2c, bd2802_id); diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c index 2782da1a1930..577497b9d426 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -2,6 +2,7 @@ /* * leds-blinkm.c * (c) Jan-Simon Möller (dl9pf@gmx.de) + * (c) Joseph Strauss (jstrauss@mailbox.org) */ #include <linux/module.h> @@ -15,6 +16,10 @@ #include <linux/pm_runtime.h> #include <linux/leds.h> #include <linux/delay.h> +#include <linux/led-class-multicolor.h> +#include <linux/kconfig.h> + +#define NUM_LEDS 3 /* Addresses to scan - BlinkM is on 0x09 by default*/ static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; @@ -22,19 +27,25 @@ static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; static int blinkm_transfer_hw(struct i2c_client *client, int cmd); static int blinkm_test_run(struct i2c_client *client); +/* Contains structs for both the color-separated sysfs classes, and the new multicolor class */ struct blinkm_led { struct i2c_client *i2c_client; - struct led_classdev led_cdev; + union { + /* used when multicolor support is disabled */ + struct led_classdev led_cdev; + struct led_classdev_mc mcled_cdev; + } cdev; int id; }; -#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) +#define led_cdev_to_blmled(c) container_of(c, struct blinkm_led, cdev.led_cdev) +#define mcled_cdev_to_led(c) container_of(c, struct blinkm_led, cdev.mcled_cdev) struct blinkm_data { struct i2c_client *i2c_client; struct mutex update_lock; /* used for led class interface */ - struct blinkm_led blinkm_leds[3]; + struct blinkm_led blinkm_leds[NUM_LEDS]; /* used for "blinkm" sysfs interface */ u8 red; /* color red */ u8 green; /* color green */ @@ -419,11 +430,29 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd) return 0; } +static int blinkm_set_mc_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev); + struct blinkm_led *led = mcled_cdev_to_led(mcled_cdev); + struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); + + led_mc_calc_color_components(mcled_cdev, value); + + data->next_red = (u8) mcled_cdev->subled_info[RED].brightness; + data->next_green = (u8) mcled_cdev->subled_info[GREEN].brightness; + data->next_blue = (u8) mcled_cdev->subled_info[BLUE].brightness; + + blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB); + + return 0; +} + static int blinkm_led_common_set(struct led_classdev *led_cdev, enum led_brightness value, int color) { /* led_brightness is 0, 127 or 255 - we just use it here as-is */ - struct blinkm_led *led = cdev_to_blmled(led_cdev); + struct blinkm_led *led = led_cdev_to_blmled(led_cdev); struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); switch (color) { @@ -565,117 +594,175 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) return 0; } -static int blinkm_probe(struct i2c_client *client) +static int register_separate_colors(struct i2c_client *client, struct blinkm_data *data) { - struct blinkm_data *data; - struct blinkm_led *led[3]; - int err, i; + /* 3 separate classes for red, green, and blue respectively */ + struct blinkm_led *leds[NUM_LEDS]; + int err; char blinkm_led_name[28]; - - data = devm_kzalloc(&client->dev, - sizeof(struct blinkm_data), GFP_KERNEL); - if (!data) { - err = -ENOMEM; - goto exit; - } - - data->i2c_addr = 0x08; - /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ - data->fw_ver = 0xfe; - /* firmware version - use fake until we read real value - * (currently broken - BlinkM confused!) */ - data->script_id = 0x01; - data->i2c_client = client; - - i2c_set_clientdata(client, data); - mutex_init(&data->update_lock); - - /* Register sysfs hooks */ - err = sysfs_create_group(&client->dev.kobj, &blinkm_group); - if (err < 0) { - dev_err(&client->dev, "couldn't register sysfs group\n"); - goto exit; - } - - for (i = 0; i < 3; i++) { + /* Register red, green, and blue sysfs classes */ + for (int i = 0; i < NUM_LEDS; i++) { /* RED = 0, GREEN = 1, BLUE = 2 */ - led[i] = &data->blinkm_leds[i]; - led[i]->i2c_client = client; - led[i]->id = i; - led[i]->led_cdev.max_brightness = 255; - led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; + leds[i] = &data->blinkm_leds[i]; + leds[i]->i2c_client = client; + leds[i]->id = i; + leds[i]->cdev.led_cdev.max_brightness = 255; + leds[i]->cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME; switch (i) { case RED: - snprintf(blinkm_led_name, sizeof(blinkm_led_name), + scnprintf(blinkm_led_name, sizeof(blinkm_led_name), "blinkm-%d-%d-red", client->adapter->nr, client->addr); - led[i]->led_cdev.name = blinkm_led_name; - led[i]->led_cdev.brightness_set_blocking = + leds[i]->cdev.led_cdev.name = blinkm_led_name; + leds[i]->cdev.led_cdev.brightness_set_blocking = blinkm_led_red_set; err = led_classdev_register(&client->dev, - &led[i]->led_cdev); + &leds[i]->cdev.led_cdev); if (err < 0) { dev_err(&client->dev, "couldn't register LED %s\n", - led[i]->led_cdev.name); + leds[i]->cdev.led_cdev.name); goto failred; } break; case GREEN: - snprintf(blinkm_led_name, sizeof(blinkm_led_name), + scnprintf(blinkm_led_name, sizeof(blinkm_led_name), "blinkm-%d-%d-green", client->adapter->nr, client->addr); - led[i]->led_cdev.name = blinkm_led_name; - led[i]->led_cdev.brightness_set_blocking = + leds[i]->cdev.led_cdev.name = blinkm_led_name; + leds[i]->cdev.led_cdev.brightness_set_blocking = blinkm_led_green_set; err = led_classdev_register(&client->dev, - &led[i]->led_cdev); + &leds[i]->cdev.led_cdev); if (err < 0) { dev_err(&client->dev, "couldn't register LED %s\n", - led[i]->led_cdev.name); + leds[i]->cdev.led_cdev.name); goto failgreen; } break; case BLUE: - snprintf(blinkm_led_name, sizeof(blinkm_led_name), + scnprintf(blinkm_led_name, sizeof(blinkm_led_name), "blinkm-%d-%d-blue", client->adapter->nr, client->addr); - led[i]->led_cdev.name = blinkm_led_name; - led[i]->led_cdev.brightness_set_blocking = + leds[i]->cdev.led_cdev.name = blinkm_led_name; + leds[i]->cdev.led_cdev.brightness_set_blocking = blinkm_led_blue_set; err = led_classdev_register(&client->dev, - &led[i]->led_cdev); + &leds[i]->cdev.led_cdev); if (err < 0) { dev_err(&client->dev, "couldn't register LED %s\n", - led[i]->led_cdev.name); + leds[i]->cdev.led_cdev.name); goto failblue; } break; + default: + break; } /* end switch */ } /* end for */ - - /* Initialize the blinkm */ - blinkm_init_hw(client); - return 0; failblue: - led_classdev_unregister(&led[GREEN]->led_cdev); - + led_classdev_unregister(&leds[GREEN]->cdev.led_cdev); failgreen: - led_classdev_unregister(&led[RED]->led_cdev); - + led_classdev_unregister(&leds[RED]->cdev.led_cdev); failred: sysfs_remove_group(&client->dev.kobj, &blinkm_group); -exit: + return err; } +static int register_multicolor(struct i2c_client *client, struct blinkm_data *data) +{ + struct blinkm_led *mc_led; + struct mc_subled *mc_led_info; + char blinkm_led_name[28]; + int err; + + /* Register multicolor sysfs class */ + /* The first element of leds is used for multicolor facilities */ + mc_led = &data->blinkm_leds[RED]; + mc_led->i2c_client = client; + + mc_led_info = devm_kcalloc(&client->dev, NUM_LEDS, sizeof(*mc_led_info), + GFP_KERNEL); + if (!mc_led_info) + return -ENOMEM; + + mc_led_info[RED].color_index = LED_COLOR_ID_RED; + mc_led_info[GREEN].color_index = LED_COLOR_ID_GREEN; + mc_led_info[BLUE].color_index = LED_COLOR_ID_BLUE; + + mc_led->cdev.mcled_cdev.subled_info = mc_led_info; + mc_led->cdev.mcled_cdev.num_colors = NUM_LEDS; + mc_led->cdev.mcled_cdev.led_cdev.brightness = 255; + mc_led->cdev.mcled_cdev.led_cdev.max_brightness = 255; + mc_led->cdev.mcled_cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME; + + scnprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d:rgb:indicator", + client->adapter->nr, + client->addr); + mc_led->cdev.mcled_cdev.led_cdev.name = blinkm_led_name; + mc_led->cdev.mcled_cdev.led_cdev.brightness_set_blocking = blinkm_set_mc_brightness; + + err = led_classdev_multicolor_register(&client->dev, &mc_led->cdev.mcled_cdev); + if (err < 0) { + dev_err(&client->dev, "couldn't register LED %s\n", + mc_led->cdev.led_cdev.name); + sysfs_remove_group(&client->dev.kobj, &blinkm_group); + } + return 0; +} + +static int blinkm_probe(struct i2c_client *client) +{ + struct blinkm_data *data; + int err; + + data = devm_kzalloc(&client->dev, + sizeof(struct blinkm_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->i2c_addr = 0x08; + /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ + data->fw_ver = 0xfe; + /* firmware version - use fake until we read real value + * (currently broken - BlinkM confused!) + */ + data->script_id = 0x01; + data->i2c_client = client; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &blinkm_group); + if (err < 0) { + dev_err(&client->dev, "couldn't register sysfs group\n"); + return err; + } + + if (!IS_ENABLED(CONFIG_LEDS_BLINKM_MULTICOLOR)) { + err = register_separate_colors(client, data); + if (err < 0) + return err; + } else { + err = register_multicolor(client, data); + if (err < 0) + return err; + } + + blinkm_init_hw(client); + + return 0; +} + static void blinkm_remove(struct i2c_client *client) { struct blinkm_data *data = i2c_get_clientdata(client); @@ -683,8 +770,8 @@ static void blinkm_remove(struct i2c_client *client) int i; /* make sure no workqueue entries are pending */ - for (i = 0; i < 3; i++) - led_classdev_unregister(&data->blinkm_leds[i].led_cdev); + for (i = 0; i < NUM_LEDS; i++) + led_classdev_unregister(&data->blinkm_leds[i].cdev.led_cdev); /* reset rgb */ data->next_red = 0x00; @@ -718,7 +805,7 @@ static void blinkm_remove(struct i2c_client *client) } static const struct i2c_device_id blinkm_id[] = { - {"blinkm", 0}, + { "blinkm" }, {} }; @@ -740,6 +827,7 @@ static struct i2c_driver blinkm_driver = { module_i2c_driver(blinkm_driver); MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>"); +MODULE_AUTHOR("Joseph Strauss <jstrauss@mailbox.org>"); MODULE_DESCRIPTION("BlinkM RGB LED driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-cht-wcove.c b/drivers/leds/leds-cht-wcove.c index b4998402b8c6..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)) @@ -461,7 +457,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds static struct platform_driver cht_wc_leds_driver = { .probe = cht_wc_leds_probe, - .remove_new = cht_wc_leds_remove, + .remove = cht_wc_leds_remove, .shutdown = cht_wc_leds_disable, .driver = { .name = "cht_wcove_leds", diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c index 82da0fe688ad..f00b16ac1586 100644 --- a/drivers/leds/leds-clevo-mail.c +++ b/drivers/leds/leds-clevo-mail.c @@ -165,7 +165,7 @@ static void clevo_mail_led_remove(struct platform_device *pdev) } static struct platform_driver clevo_mail_led_driver = { - .remove_new = clevo_mail_led_remove, + .remove = clevo_mail_led_remove, .driver = { .name = KBUILD_MODNAME, }, diff --git a/drivers/leds/leds-cr0014114.c b/drivers/leds/leds-cr0014114.c index c9914fc51f20..7e51c374edd4 100644 --- a/drivers/leds/leds-cr0014114.c +++ b/drivers/leds/leds-cr0014114.c @@ -181,11 +181,10 @@ static int cr0014114_probe_dt(struct cr0014114 *priv) { size_t i = 0; struct cr0014114_led *led; - struct fwnode_handle *child; struct led_init_data init_data = {}; int ret; - device_for_each_child_node(priv->dev, child) { + device_for_each_child_node_scoped(priv->dev, child) { led = &priv->leds[i]; led->priv = priv; @@ -201,7 +200,6 @@ static int cr0014114_probe_dt(struct cr0014114 *priv) if (ret) { dev_err(priv->dev, "failed to register LED device, err %d", ret); - fwnode_handle_put(child); return ret; } diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c new file mode 100644 index 000000000000..275522b81ea5 --- /dev/null +++ b/drivers/leds/leds-cros_ec.c @@ -0,0 +1,277 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * ChromeOS EC LED Driver + * + * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net> + */ + +#include <linux/device.h> +#include <linux/leds.h> +#include <linux/led-class-multicolor.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/cros_ec_commands.h> +#include <linux/platform_data/cros_ec_proto.h> + +static const char * const cros_ec_led_functions[] = { + [EC_LED_ID_BATTERY_LED] = LED_FUNCTION_CHARGING, + [EC_LED_ID_POWER_LED] = LED_FUNCTION_POWER, + [EC_LED_ID_ADAPTER_LED] = "adapter", + [EC_LED_ID_LEFT_LED] = "left", + [EC_LED_ID_RIGHT_LED] = "right", + [EC_LED_ID_RECOVERY_HW_REINIT_LED] = "recovery-hw-reinit", + [EC_LED_ID_SYSRQ_DEBUG_LED] = "sysrq-debug", +}; + +static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT); + +static const int cros_ec_led_to_linux_id[] = { + [EC_LED_COLOR_RED] = LED_COLOR_ID_RED, + [EC_LED_COLOR_GREEN] = LED_COLOR_ID_GREEN, + [EC_LED_COLOR_BLUE] = LED_COLOR_ID_BLUE, + [EC_LED_COLOR_YELLOW] = LED_COLOR_ID_YELLOW, + [EC_LED_COLOR_WHITE] = LED_COLOR_ID_WHITE, + [EC_LED_COLOR_AMBER] = LED_COLOR_ID_AMBER, +}; + +static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT); + +static const int cros_ec_linux_to_ec_id[] = { + [LED_COLOR_ID_RED] = EC_LED_COLOR_RED, + [LED_COLOR_ID_GREEN] = EC_LED_COLOR_GREEN, + [LED_COLOR_ID_BLUE] = EC_LED_COLOR_BLUE, + [LED_COLOR_ID_YELLOW] = EC_LED_COLOR_YELLOW, + [LED_COLOR_ID_WHITE] = EC_LED_COLOR_WHITE, + [LED_COLOR_ID_AMBER] = EC_LED_COLOR_AMBER, +}; + +struct cros_ec_led_priv { + struct led_classdev_mc led_mc_cdev; + struct cros_ec_device *cros_ec; + enum ec_led_id led_id; +}; + +static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_classdev *led_cdev) +{ + return container_of(lcdev_to_mccdev(led_cdev), struct cros_ec_led_priv, led_mc_cdev); +} + +union cros_ec_led_cmd_data { + struct ec_params_led_control req; + struct ec_response_led_control resp; +} __packed; + +static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec, + union cros_ec_led_cmd_data *arg) +{ + int ret; + struct { + struct cros_ec_command msg; + union cros_ec_led_cmd_data data; + } __packed buf = { + .msg = { + .version = 1, + .command = EC_CMD_LED_CONTROL, + .insize = sizeof(arg->resp), + .outsize = sizeof(arg->req), + }, + .data.req = arg->req + }; + + ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg); + if (ret < 0) + return ret; + + arg->resp = buf.data.resp; + + return 0; +} + +static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev) +{ + struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev); + union cros_ec_led_cmd_data arg = {}; + + arg.req.led_id = priv->led_id; + arg.req.flags = EC_LED_FLAGS_AUTO; + + return cros_ec_led_send_cmd(priv->cros_ec, &arg); +} + +static struct led_hw_trigger_type cros_ec_led_trigger_type; + +static struct led_trigger cros_ec_led_trigger = { + .name = "chromeos-auto", + .trigger_type = &cros_ec_led_trigger_type, + .activate = cros_ec_led_trigger_activate, +}; + +static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev); + union cros_ec_led_cmd_data arg = {}; + enum ec_led_colors led_color; + struct mc_subled *subled; + size_t i; + + led_mc_calc_color_components(&priv->led_mc_cdev, brightness); + + arg.req.led_id = priv->led_id; + + for (i = 0; i < priv->led_mc_cdev.num_colors; i++) { + subled = &priv->led_mc_cdev.subled_info[i]; + led_color = cros_ec_linux_to_ec_id[subled->color_index]; + arg.req.brightness[led_color] = subled->brightness; + } + + return cros_ec_led_send_cmd(priv->cros_ec, &arg); +} + +static int cros_ec_led_count_subleds(struct device *dev, + struct ec_response_led_control *resp, + unsigned int *max_brightness) +{ + unsigned int range, common_range = 0; + int num_subleds = 0; + size_t i; + + for (i = 0; i < EC_LED_COLOR_COUNT; i++) { + range = resp->brightness_range[i]; + + if (!range) + continue; + + num_subleds++; + + if (!common_range) + common_range = range; + + if (common_range != range) { + /* The multicolor LED API expects a uniform max_brightness */ + dev_err(dev, "Inconsistent LED brightness values\n"); + return -EINVAL; + } + } + + if (!num_subleds) + return -EINVAL; + + *max_brightness = common_range; + return num_subleds; +} + +static const char *cros_ec_led_get_color_name(struct led_classdev_mc *led_mc_cdev) +{ + int color; + + if (led_mc_cdev->num_colors == 1) + color = led_mc_cdev->subled_info[0].color_index; + else + color = LED_COLOR_ID_MULTI; + + return led_get_color_name(color); +} + +static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros_ec, + enum ec_led_id id) +{ + union cros_ec_led_cmd_data arg = {}; + struct cros_ec_led_priv *priv; + struct led_classdev *led_cdev; + struct mc_subled *subleds; + int i, ret, num_subleds; + size_t subled; + + arg.req.led_id = id; + arg.req.flags = EC_LED_FLAGS_QUERY; + ret = cros_ec_led_send_cmd(cros_ec, &arg); + if (ret == -EINVAL) + return 0; /* Unknown LED, skip */ + if (ret == -EOPNOTSUPP) + return -ENODEV; + if (ret < 0) + return ret; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + num_subleds = cros_ec_led_count_subleds(dev, &arg.resp, + &priv->led_mc_cdev.led_cdev.max_brightness); + if (num_subleds < 0) + return num_subleds; + + priv->cros_ec = cros_ec; + priv->led_id = id; + + subleds = devm_kcalloc(dev, num_subleds, sizeof(*subleds), GFP_KERNEL); + if (!subleds) + return -ENOMEM; + + subled = 0; + for (i = 0; i < EC_LED_COLOR_COUNT; i++) { + if (!arg.resp.brightness_range[i]) + continue; + + subleds[subled].color_index = cros_ec_led_to_linux_id[i]; + if (subled == 0) + subleds[subled].intensity = 100; + subled++; + } + + priv->led_mc_cdev.subled_info = subleds; + priv->led_mc_cdev.num_colors = num_subleds; + + led_cdev = &priv->led_mc_cdev.led_cdev; + led_cdev->brightness_set_blocking = cros_ec_led_brightness_set_blocking; + led_cdev->trigger_type = &cros_ec_led_trigger_type; + led_cdev->default_trigger = cros_ec_led_trigger.name; + led_cdev->hw_control_trigger = cros_ec_led_trigger.name; + + led_cdev->name = devm_kasprintf(dev, GFP_KERNEL, "chromeos:%s:%s", + cros_ec_led_get_color_name(&priv->led_mc_cdev), + cros_ec_led_functions[id]); + if (!led_cdev->name) + return -ENOMEM; + + return devm_led_classdev_multicolor_register(dev, &priv->led_mc_cdev); +} + +static int cros_ec_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent); + struct cros_ec_device *cros_ec = ec_dev->ec_dev; + int i, ret = 0; + + ret = devm_led_trigger_register(dev, &cros_ec_led_trigger); + if (ret) + return ret; + + for (i = 0; i < EC_LED_ID_COUNT; i++) { + ret = cros_ec_led_probe_one(dev, cros_ec, i); + if (ret) + break; + } + + return ret; +} + +static const struct platform_device_id cros_ec_led_id[] = { + { "cros-ec-led", 0 }, + {} +}; + +static struct platform_driver cros_ec_led_driver = { + .driver.name = "cros-ec-led", + .probe = cros_ec_led_probe, + .id_table = cros_ec_led_id, +}; +module_platform_driver(cros_ec_led_driver); + +MODULE_DEVICE_TABLE(platform, cros_ec_led_id); +MODULE_DESCRIPTION("ChromeOS EC LED Driver"); +MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c index f067a5f4d3c4..71209b3c8f1e 100644 --- a/drivers/leds/leds-da903x.c +++ b/drivers/leds/leds-da903x.c @@ -133,7 +133,7 @@ static struct platform_driver da903x_led_driver = { .name = "da903x-led", }, .probe = da903x_led_probe, - .remove_new = da903x_led_remove, + .remove = da903x_led_remove, }; module_platform_driver(da903x_led_driver); diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c index 64679d62076b..7c9051184a59 100644 --- a/drivers/leds/leds-da9052.c +++ b/drivers/leds/leds-da9052.c @@ -179,7 +179,7 @@ static struct platform_driver da9052_led_driver = { .name = "da9052-leds", }, .probe = da9052_led_probe, - .remove_new = da9052_led_remove, + .remove = da9052_led_remove, }; module_platform_driver(da9052_led_driver); diff --git a/drivers/leds/leds-el15203000.c b/drivers/leds/leds-el15203000.c index d40194a3029f..e26d1654bd0d 100644 --- a/drivers/leds/leds-el15203000.c +++ b/drivers/leds/leds-el15203000.c @@ -237,22 +237,20 @@ static int el15203000_pattern_clear(struct led_classdev *ldev) static int el15203000_probe_dt(struct el15203000 *priv) { struct el15203000_led *led = priv->leds; - struct fwnode_handle *child; int ret; - device_for_each_child_node(priv->dev, child) { + device_for_each_child_node_scoped(priv->dev, child) { struct led_init_data init_data = {}; ret = fwnode_property_read_u32(child, "reg", &led->reg); if (ret) { dev_err(priv->dev, "LED without ID number"); - goto err_child_out; + return ret; } if (led->reg > U8_MAX) { dev_err(priv->dev, "LED value %d is invalid", led->reg); - ret = -EINVAL; - goto err_child_out; + return -EINVAL; } led->priv = priv; @@ -274,17 +272,13 @@ static int el15203000_probe_dt(struct el15203000 *priv) dev_err(priv->dev, "failed to register LED device %s, err %d", led->ldev.name, ret); - goto err_child_out; + return ret; } led++; } return 0; - -err_child_out: - fwnode_handle_put(child); - return ret; } static int el15203000_probe(struct spi_device *spi) diff --git a/drivers/leds/leds-expresswire.c b/drivers/leds/leds-expresswire.c index e4937a8e0f44..bb69be228a6d 100644 --- a/drivers/leds/leds-expresswire.c +++ b/drivers/leds/leds-expresswire.c @@ -18,7 +18,7 @@ void expresswire_power_off(struct expresswire_common_props *props) gpiod_set_value_cansleep(props->ctrl_gpio, 0); usleep_range(props->timing.poweroff_us, props->timing.poweroff_us * 2); } -EXPORT_SYMBOL_NS_GPL(expresswire_power_off, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_power_off, "EXPRESSWIRE"); void expresswire_enable(struct expresswire_common_props *props) { @@ -28,14 +28,14 @@ void expresswire_enable(struct expresswire_common_props *props) udelay(props->timing.detect_us); gpiod_set_value(props->ctrl_gpio, 1); } -EXPORT_SYMBOL_NS_GPL(expresswire_enable, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_enable, "EXPRESSWIRE"); void expresswire_start(struct expresswire_common_props *props) { gpiod_set_value(props->ctrl_gpio, 1); udelay(props->timing.data_start_us); } -EXPORT_SYMBOL_NS_GPL(expresswire_start, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_start, "EXPRESSWIRE"); void expresswire_end(struct expresswire_common_props *props) { @@ -44,7 +44,7 @@ void expresswire_end(struct expresswire_common_props *props) gpiod_set_value(props->ctrl_gpio, 1); udelay(props->timing.end_of_data_high_us); } -EXPORT_SYMBOL_NS_GPL(expresswire_end, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE"); void expresswire_set_bit(struct expresswire_common_props *props, bool bit) { @@ -60,7 +60,7 @@ void expresswire_set_bit(struct expresswire_common_props *props, bool bit) udelay(props->timing.short_bitset_us); } } -EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE"); void expresswire_write_u8(struct expresswire_common_props *props, u8 val) { @@ -69,4 +69,4 @@ void expresswire_write_u8(struct expresswire_common_props *props, u8 val) expresswire_set_bit(props, val & BIT(i)); expresswire_end(props); } -EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, EXPRESSWIRE); +EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, "EXPRESSWIRE"); diff --git a/drivers/leds/leds-gpio-register.c b/drivers/leds/leds-gpio-register.c index de3f12c2b80d..ccc01fa72e6f 100644 --- a/drivers/leds/leds-gpio-register.c +++ b/drivers/leds/leds-gpio-register.c @@ -10,8 +10,8 @@ /** * gpio_led_register_device - register a gpio-led device - * @pdata: the platform data used for the new device * @id: platform ID + * @pdata: the platform data used for the new device * * Makes a copy of pdata and pdata->leds and registers a new leds-gpio device * with the result. This allows to have pdata and pdata-leds in .init.rodata diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 83fcd7b6afff..a3428b22de3a 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -21,8 +21,6 @@ #include <linux/slab.h> #include <linux/types.h> -#include "leds.h" - struct gpio_led_data { struct led_classdev cdev; struct gpio_desc *gpiod; @@ -148,9 +146,8 @@ struct gpio_leds_priv { static struct gpio_leds_priv *gpio_leds_create(struct device *dev) { - struct fwnode_handle *child; struct gpio_leds_priv *priv; - int count, ret; + int count, used, ret; count = device_get_child_node_count(dev); if (!count) @@ -159,9 +156,11 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev) priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL); if (!priv) return ERR_PTR(-ENOMEM); + priv->num_leds = count; + used = 0; - device_for_each_child_node(dev, child) { - struct gpio_led_data *led_dat = &priv->leds[priv->num_leds]; + device_for_each_child_node_scoped(dev, child) { + struct gpio_led_data *led_dat = &priv->leds[used]; struct gpio_led led = {}; /* @@ -174,7 +173,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev) if (IS_ERR(led.gpiod)) { dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n", child); - fwnode_handle_put(child); return ERR_CAST(led.gpiod); } @@ -190,15 +188,15 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev) led.panic_indicator = 1; ret = create_gpio_led(&led, led_dat, dev, child, NULL); - if (ret < 0) { - fwnode_handle_put(child); + if (ret < 0) return ERR_PTR(ret); - } + /* Set gpiod label to match the corresponding LED name. */ gpiod_set_consumer_name(led_dat->gpiod, led_dat->cdev.dev->kobj.name); - priv->num_leds++; + used++; } + priv->num_leds = used; return priv; } @@ -214,7 +212,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, const struct gpio_led *template) { struct gpio_desc *gpiod; - unsigned long flags = GPIOF_OUT_INIT_LOW; int ret; /* @@ -241,10 +238,7 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, if (!gpio_is_valid(template->gpio)) return ERR_PTR(-ENOENT); - if (template->active_low) - flags |= GPIOF_ACTIVE_LOW; - - ret = devm_gpio_request_one(dev, template->gpio, flags, + ret = devm_gpio_request_one(dev, template->gpio, GPIOF_OUT_INIT_LOW, template->name); if (ret < 0) return ERR_PTR(ret); @@ -253,6 +247,9 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, if (!gpiod) return ERR_PTR(-EINVAL); + if (template->active_low ^ gpiod_is_active_low(gpiod)) + gpiod_toggle_active_low(gpiod); + return gpiod; } diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 66c65741202e..27bfab3da479 100644 --- a/drivers/leds/leds-is31fl319x.c +++ b/drivers/leds/leds-is31fl319x.c @@ -140,7 +140,7 @@ static const struct reg_default is31fl3190_reg_defaults[] = { { IS31FL3190_PWM(2), 0x00 }, }; -static struct regmap_config is31fl3190_regmap_config = { +static const struct regmap_config is31fl3190_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = IS31FL3190_RESET, @@ -178,7 +178,7 @@ static const struct reg_default is31fl3196_reg_defaults[] = { { IS31FL3196_PWM(8), 0x00 }, }; -static struct regmap_config is31fl3196_regmap_config = { +static const struct regmap_config is31fl3196_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = IS31FL3196_REG_CNT, @@ -392,7 +392,7 @@ static int is31fl319x_parse_child_fw(const struct device *dev, static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) { - struct fwnode_handle *fwnode = dev_fwnode(dev), *child; + struct fwnode_handle *fwnode = dev_fwnode(dev); int count; int ret; @@ -404,7 +404,7 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) is31->cdef = device_get_match_data(dev); count = 0; - fwnode_for_each_available_child_node(fwnode, child) + device_for_each_child_node_scoped(dev, child) count++; dev_dbg(dev, "probing with %d leds defined in DT\n", count); @@ -414,33 +414,25 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) "Number of leds defined must be between 1 and %u\n", is31->cdef->num_leds); - fwnode_for_each_available_child_node(fwnode, child) { + device_for_each_child_node_scoped(dev, child) { struct is31fl319x_led *led; u32 reg; ret = fwnode_property_read_u32(child, "reg", ®); - if (ret) { - ret = dev_err_probe(dev, ret, "Failed to read led 'reg' property\n"); - goto put_child_node; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to read led 'reg' property\n"); - if (reg < 1 || reg > is31->cdef->num_leds) { - ret = dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg); - goto put_child_node; - } + if (reg < 1 || reg > is31->cdef->num_leds) + return dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg); led = &is31->leds[reg - 1]; - if (led->configured) { - ret = dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg); - goto put_child_node; - } + if (led->configured) + return dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg); ret = is31fl319x_parse_child_fw(dev, child, led, is31); - if (ret) { - ret = dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg); - goto put_child_node; - } + if (ret) + return dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg); led->configured = true; } @@ -454,10 +446,6 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31) } return 0; - -put_child_node: - fwnode_handle_put(child); - return ret; } static inline int is31fl3190_microamp_to_cs(struct device *dev, u32 microamp) diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index b0a0be77bb33..8793330dd414 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -363,10 +363,9 @@ static struct is31fl32xx_led_data *is31fl32xx_find_led_data( static int is31fl32xx_parse_dt(struct device *dev, struct is31fl32xx_priv *priv) { - struct device_node *child; int ret = 0; - for_each_available_child_of_node(dev_of_node(dev), child) { + for_each_available_child_of_node_scoped(dev_of_node(dev), child) { struct led_init_data init_data = {}; struct is31fl32xx_led_data *led_data = &priv->leds[priv->num_leds]; @@ -376,7 +375,7 @@ static int is31fl32xx_parse_dt(struct device *dev, ret = is31fl32xx_parse_child_dt(dev, child, led_data); if (ret) - goto err; + return ret; /* Detect if channel is already in use by another child */ other_led_data = is31fl32xx_find_led_data(priv, @@ -385,8 +384,7 @@ static int is31fl32xx_parse_dt(struct device *dev, dev_err(dev, "Node %pOF 'reg' conflicts with another LED\n", child); - ret = -EINVAL; - goto err; + return -EINVAL; } init_data.fwnode = of_fwnode_handle(child); @@ -396,17 +394,13 @@ static int is31fl32xx_parse_dt(struct device *dev, if (ret) { dev_err(dev, "Failed to register LED for %pOF: %d\n", child, ret); - goto err; + return ret; } priv->num_leds++; } return 0; - -err: - of_node_put(child); - return ret; } static const struct of_device_id of_is31fl32xx_match[] = { diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index a2feef8e4ac5..e44a3db106c3 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -478,7 +478,7 @@ static void lm3530_remove(struct i2c_client *client) } static const struct i2c_device_id lm3530_id[] = { - {LM3530_NAME, 0}, + { LM3530_NAME }, {} }; MODULE_DEVICE_TABLE(i2c, lm3530_id); diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c index 13662a4aa1f2..24dc8ad27bb3 100644 --- a/drivers/leds/leds-lm3532.c +++ b/drivers/leds/leds-lm3532.c @@ -542,9 +542,15 @@ static int lm3532_parse_als(struct lm3532_data *priv) return ret; } +static void gpio_set_low_action(void *data) +{ + struct lm3532_data *priv = data; + + gpiod_direction_output(priv->enable_gpio, 0); +} + static int lm3532_parse_node(struct lm3532_data *priv) { - struct fwnode_handle *child = NULL; struct lm3532_led *led; int control_bank; u32 ramp_time; @@ -556,6 +562,12 @@ static int lm3532_parse_node(struct lm3532_data *priv) if (IS_ERR(priv->enable_gpio)) priv->enable_gpio = NULL; + if (priv->enable_gpio) { + ret = devm_add_action(&priv->client->dev, gpio_set_low_action, priv); + if (ret) + return ret; + } + priv->regulator = devm_regulator_get(&priv->client->dev, "vin"); if (IS_ERR(priv->regulator)) priv->regulator = NULL; @@ -574,7 +586,7 @@ static int lm3532_parse_node(struct lm3532_data *priv) else priv->runtime_ramp_down = lm3532_get_ramp_index(ramp_time); - device_for_each_child_node(priv->dev, child) { + device_for_each_child_node_scoped(priv->dev, child) { struct led_init_data idata = { .fwnode = child, .default_label = ":", @@ -586,7 +598,7 @@ static int lm3532_parse_node(struct lm3532_data *priv) ret = fwnode_property_read_u32(child, "reg", &control_bank); if (ret) { dev_err(&priv->client->dev, "reg property missing\n"); - goto child_out; + return ret; } if (control_bank > LM3532_CONTROL_C) { @@ -600,7 +612,7 @@ static int lm3532_parse_node(struct lm3532_data *priv) &led->mode); if (ret) { dev_err(&priv->client->dev, "ti,led-mode property missing\n"); - goto child_out; + return ret; } if (fwnode_property_present(child, "led-max-microamp") && @@ -634,7 +646,7 @@ static int lm3532_parse_node(struct lm3532_data *priv) led->num_leds); if (ret) { dev_err(&priv->client->dev, "led-sources property missing\n"); - goto child_out; + return ret; } led->priv = priv; @@ -644,23 +656,20 @@ static int lm3532_parse_node(struct lm3532_data *priv) if (ret) { dev_err(&priv->client->dev, "led register err: %d\n", ret); - goto child_out; + return ret; } ret = lm3532_init_registers(led); if (ret) { dev_err(&priv->client->dev, "register init err: %d\n", ret); - goto child_out; + return ret; } i++; } - return 0; -child_out: - fwnode_handle_put(child); - return ret; + return 0; } static int lm3532_probe(struct i2c_client *client) @@ -691,7 +700,10 @@ static int lm3532_probe(struct i2c_client *client) return ret; } - mutex_init(&drvdata->lock); + ret = devm_mutex_init(&client->dev, &drvdata->lock); + if (ret) + return ret; + i2c_set_clientdata(client, drvdata); ret = lm3532_parse_node(drvdata); @@ -703,16 +715,6 @@ static int lm3532_probe(struct i2c_client *client) return ret; } -static void lm3532_remove(struct i2c_client *client) -{ - struct lm3532_data *drvdata = i2c_get_clientdata(client); - - mutex_destroy(&drvdata->lock); - - if (drvdata->enable_gpio) - gpiod_direction_output(drvdata->enable_gpio, 0); -} - static const struct of_device_id of_lm3532_leds_match[] = { { .compatible = "ti,lm3532", }, {}, @@ -720,14 +722,13 @@ static const struct of_device_id of_lm3532_leds_match[] = { MODULE_DEVICE_TABLE(of, of_lm3532_leds_match); static const struct i2c_device_id lm3532_id[] = { - {LM3532_NAME, 0}, + { LM3532_NAME }, {} }; MODULE_DEVICE_TABLE(i2c, lm3532_id); static struct i2c_driver lm3532_i2c_driver = { .probe = lm3532_probe, - .remove = lm3532_remove, .id_table = lm3532_id, .driver = { .name = LM3532_NAME, diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c index a3d33165d262..45795f2a1042 100644 --- a/drivers/leds/leds-lm3533.c +++ b/drivers/leds/leds-lm3533.c @@ -744,7 +744,7 @@ static struct platform_driver lm3533_led_driver = { .name = "lm3533-leds", }, .probe = lm3533_led_probe, - .remove_new = lm3533_led_remove, + .remove = lm3533_led_remove, .shutdown = lm3533_led_shutdown, }; module_platform_driver(lm3533_led_driver); diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index 6eee52e211be..61629d5d6703 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -390,7 +390,7 @@ static void lm3642_remove(struct i2c_client *client) } static const struct i2c_device_id lm3642_id[] = { - {LM3642_NAME, 0}, + { LM3642_NAME }, {} }; diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c index 380d17a58fe9..7ad232780a31 100644 --- a/drivers/leds/leds-lm3697.c +++ b/drivers/leds/leds-lm3697.c @@ -202,7 +202,6 @@ out: static int lm3697_probe_dt(struct lm3697 *priv) { - struct fwnode_handle *child = NULL; struct device *dev = priv->dev; struct lm3697_led *led; int ret = -EINVAL; @@ -220,19 +219,18 @@ static int lm3697_probe_dt(struct lm3697 *priv) if (IS_ERR(priv->regulator)) priv->regulator = NULL; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_init_data init_data = {}; ret = fwnode_property_read_u32(child, "reg", &control_bank); if (ret) { dev_err(dev, "reg property missing\n"); - goto child_out; + return ret; } if (control_bank > LM3697_CONTROL_B) { dev_err(dev, "reg property is invalid\n"); - ret = -EINVAL; - goto child_out; + return -EINVAL; } led = &priv->leds[i]; @@ -262,7 +260,7 @@ static int lm3697_probe_dt(struct lm3697 *priv) led->num_leds); if (ret) { dev_err(dev, "led-sources property missing\n"); - goto child_out; + return ret; } for (j = 0; j < led->num_leds; j++) @@ -286,17 +284,13 @@ static int lm3697_probe_dt(struct lm3697 *priv) &init_data); if (ret) { dev_err(dev, "led register err: %d\n", ret); - goto child_out; + return ret; } i++; } - return ret; - -child_out: - fwnode_handle_put(child); - return ret; + return 0; } static int lm3697_probe(struct i2c_client *client) @@ -360,7 +354,7 @@ static void lm3697_remove(struct i2c_client *client) } static const struct i2c_device_id lm3697_id[] = { - { "lm3697", 0 }, + { "lm3697" }, { } }; MODULE_DEVICE_TABLE(i2c, lm3697_id); diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index 8ea746c499d1..ccfeee49ea78 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -417,7 +417,7 @@ static void lp3944_remove(struct i2c_client *client) /* lp3944 i2c driver struct */ static const struct i2c_device_id lp3944_id[] = { - {"lp3944", 0}, + { "lp3944" }, {} }; diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c index 5d18bbfd1f23..17219a582704 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -207,6 +207,13 @@ static const struct regmap_config lp3952_regmap = { .cache_type = REGCACHE_MAPLE, }; +static void gpio_set_low_action(void *data) +{ + struct lp3952_led_array *priv = data; + + gpiod_set_value(priv->enable_gpio, 0); +} + static int lp3952_probe(struct i2c_client *client) { int status; @@ -226,6 +233,10 @@ static int lp3952_probe(struct i2c_client *client) return status; } + status = devm_add_action(&client->dev, gpio_set_low_action, priv); + if (status) + return status; + priv->regmap = devm_regmap_init_i2c(client, &lp3952_regmap); if (IS_ERR(priv->regmap)) { int err = PTR_ERR(priv->regmap); @@ -254,17 +265,8 @@ static int lp3952_probe(struct i2c_client *client) return 0; } -static void lp3952_remove(struct i2c_client *client) -{ - struct lp3952_led_array *priv; - - priv = i2c_get_clientdata(client); - lp3952_on_off(priv, LP3952_LED_ALL, false); - gpiod_set_value(priv->enable_gpio, 0); -} - static const struct i2c_device_id lp3952_id[] = { - {LP3952_NAME, 0}, + { LP3952_NAME }, {} }; MODULE_DEVICE_TABLE(i2c, lp3952_id); @@ -274,7 +276,6 @@ static struct i2c_driver lp3952_i2c_driver = { .name = LP3952_NAME, }, .probe = lp3952_probe, - .remove = lp3952_remove, .id_table = lp3952_id, }; diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c index 68c4d9967d68..02cb1565a9fb 100644 --- a/drivers/leds/leds-lp50xx.c +++ b/drivers/leds/leds-lp50xx.c @@ -16,8 +16,6 @@ #include <linux/led-class-multicolor.h> -#include "leds.h" - #define LP50XX_DEV_CFG0 0x00 #define LP50XX_DEV_CFG1 0x01 #define LP50XX_LED_CFG0 0x02 @@ -265,7 +263,6 @@ static const struct lp50xx_chip_info lp50xx_chip_info_tbl[] = { struct lp50xx_led { struct led_classdev_mc mc_cdev; struct lp50xx *priv; - unsigned long bank_modules; u8 ctrl_bank_enabled; int led_number; }; @@ -279,7 +276,6 @@ struct lp50xx_led { * @dev: pointer to the devices device struct * @lock: lock for reading/writing the device * @chip_info: chip specific information (ie num_leds) - * @num_of_banked_leds: holds the number of banked LEDs * @leds: array of LED strings */ struct lp50xx { @@ -290,7 +286,6 @@ struct lp50xx { struct device *dev; struct mutex lock; const struct lp50xx_chip_info *chip_info; - int num_of_banked_leds; /* This needs to be at the end of the struct */ struct lp50xx_led leds[]; @@ -404,8 +399,6 @@ static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv, return -EINVAL; } - priv->num_of_banked_leds = num_leds; - ret = fwnode_property_read_u32_array(child, "reg", led_banks, num_leds); if (ret) { dev_err(priv->dev, "reg property is missing\n"); @@ -439,7 +432,6 @@ static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv, static int lp50xx_probe_dt(struct lp50xx *priv) { - struct fwnode_handle *child = NULL; struct fwnode_handle *led_node = NULL; struct led_init_data init_data = {}; struct led_classdev *led_cdev; @@ -459,17 +451,17 @@ static int lp50xx_probe_dt(struct lp50xx *priv) if (IS_ERR(priv->regulator)) priv->regulator = NULL; - device_for_each_child_node(priv->dev, child) { + device_for_each_child_node_scoped(priv->dev, child) { led = &priv->leds[i]; ret = fwnode_property_count_u32(child, "reg"); if (ret < 0) { dev_err(priv->dev, "reg property is invalid\n"); - goto child_out; + return ret; } ret = lp50xx_probe_leds(child, priv, led, ret); if (ret) - goto child_out; + return ret; init_data.fwnode = child; num_colors = 0; @@ -480,10 +472,8 @@ static int lp50xx_probe_dt(struct lp50xx *priv) */ mc_led_info = devm_kcalloc(priv->dev, LP50XX_LEDS_PER_MODULE, sizeof(*mc_led_info), GFP_KERNEL); - if (!mc_led_info) { - ret = -ENOMEM; - goto child_out; - } + if (!mc_led_info) + return -ENOMEM; fwnode_for_each_child_node(child, led_node) { ret = fwnode_property_read_u32(led_node, "color", @@ -491,7 +481,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv) if (ret) { fwnode_handle_put(led_node); dev_err(priv->dev, "Cannot read color\n"); - goto child_out; + return ret; } mc_led_info[num_colors].color_index = color_id; @@ -509,16 +499,12 @@ static int lp50xx_probe_dt(struct lp50xx *priv) &init_data); if (ret) { dev_err(priv->dev, "led register err: %d\n", ret); - goto child_out; + return ret; } i++; } return 0; - -child_out: - fwnode_handle_put(child); - return ret; } static int lp50xx_probe(struct i2c_client *client) diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index f9c8b568b652..7564b9953408 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -9,6 +9,7 @@ * Milo(Woogyom) Kim <milo.kim@ti.com> */ +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/firmware.h> #include <linux/i2c.h> @@ -21,7 +22,6 @@ #include "leds-lp55xx-common.h" -#define LP5521_PROGRAM_LENGTH 32 #define LP5521_MAX_LEDS 3 #define LP5521_CMD_DIRECT 0x3F @@ -73,29 +73,6 @@ /* Reset register value */ #define LP5521_RESET 0xFF -/* Program Memory Operations */ -#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */ -#define LP5521_MODE_G_M 0x0C -#define LP5521_MODE_B_M 0x03 -#define LP5521_LOAD_R 0x10 -#define LP5521_LOAD_G 0x04 -#define LP5521_LOAD_B 0x01 - -#define LP5521_R_IS_LOADING(mode) \ - ((mode & LP5521_MODE_R_M) == LP5521_LOAD_R) -#define LP5521_G_IS_LOADING(mode) \ - ((mode & LP5521_MODE_G_M) == LP5521_LOAD_G) -#define LP5521_B_IS_LOADING(mode) \ - ((mode & LP5521_MODE_B_M) == LP5521_LOAD_B) - -#define LP5521_EXEC_R_M 0x30 /* Enable Register */ -#define LP5521_EXEC_G_M 0x0C -#define LP5521_EXEC_B_M 0x03 -#define LP5521_EXEC_M 0x3F -#define LP5521_RUN_R 0x20 -#define LP5521_RUN_G 0x08 -#define LP5521_RUN_B 0x02 - static inline void lp5521_wait_opmode_done(void) { /* operation mode change needs to be longer than 153 us */ @@ -108,170 +85,21 @@ static inline void lp5521_wait_enable_done(void) usleep_range(500, 600); } -static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current) -{ - led->led_current = led_current; - lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr, - led_current); -} - -static void lp5521_load_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP5521_MODE_R_M, - [LP55XX_ENGINE_2] = LP5521_MODE_G_M, - [LP55XX_ENGINE_3] = LP5521_MODE_B_M, - }; - - static const u8 val[] = { - [LP55XX_ENGINE_1] = LP5521_LOAD_R, - [LP55XX_ENGINE_2] = LP5521_LOAD_G, - [LP55XX_ENGINE_3] = LP5521_LOAD_B, - }; - - lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]); - - lp5521_wait_opmode_done(); -} - -static void lp5521_stop_all_engines(struct lp55xx_chip *chip) -{ - lp55xx_write(chip, LP5521_REG_OP_MODE, 0); - lp5521_wait_opmode_done(); -} - -static void lp5521_stop_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP5521_MODE_R_M, - [LP55XX_ENGINE_2] = LP5521_MODE_G_M, - [LP55XX_ENGINE_3] = LP5521_MODE_B_M, - }; - - lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0); - - lp5521_wait_opmode_done(); -} - static void lp5521_run_engine(struct lp55xx_chip *chip, bool start) { int ret; - u8 mode; - u8 exec; /* stop engine */ if (!start) { - lp5521_stop_engine(chip); + lp55xx_stop_engine(chip); lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); lp5521_wait_opmode_done(); return; } - /* - * To run the engine, - * operation mode and enable register should updated at the same time - */ - - ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode); - if (ret) - return; - - ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec); - if (ret) - return; - - /* change operation mode to RUN only when each engine is loading */ - if (LP5521_R_IS_LOADING(mode)) { - mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R; - exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R; - } - - if (LP5521_G_IS_LOADING(mode)) { - mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G; - exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G; - } - - if (LP5521_B_IS_LOADING(mode)) { - mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B; - exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B; - } - - lp55xx_write(chip, LP5521_REG_OP_MODE, mode); - lp5521_wait_opmode_done(); - - lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec); - lp5521_wait_enable_done(); -} - -static int lp5521_update_program_memory(struct lp55xx_chip *chip, - const u8 *data, size_t size) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; - static const u8 addr[] = { - [LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM, - [LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM, - [LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM, - }; - unsigned cmd; - char c[3]; - int nrchars; - int ret; - int offset = 0; - int i = 0; - - while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(data + offset, "%2s%n ", c, &nrchars); - if (ret != 1) - goto err; - - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto err; - - pattern[i] = (u8)cmd; - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto err; - - for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) { - ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); - if (ret) - return -EINVAL; - } - - return size; - -err: - dev_err(&chip->cl->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static void lp5521_firmware_loaded(struct lp55xx_chip *chip) -{ - const struct firmware *fw = chip->fw; - - if (fw->size > LP5521_PROGRAM_LENGTH) { - dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", - fw->size); - return; - } - - /* - * Program memory sequence - * 1) set engine mode to "LOAD" - * 2) write firmware data into program memory - */ - - lp5521_load_engine(chip); - lp5521_update_program_memory(chip, fw->data, fw->size); + ret = lp55xx_run_engine_common(chip); + if (!ret) + lp5521_wait_enable_done(); } static int lp5521_post_init_device(struct lp55xx_chip *chip) @@ -350,114 +178,6 @@ static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf) return 0; } -static int lp5521_multicolor_brightness(struct lp55xx_led *led) -{ - struct lp55xx_chip *chip = led->chip; - int ret; - int i; - - mutex_lock(&chip->lock); - for (i = 0; i < led->mc_cdev.num_colors; i++) { - ret = lp55xx_write(chip, - LP5521_REG_LED_PWM_BASE + - led->mc_cdev.subled_info[i].channel, - led->mc_cdev.subled_info[i].brightness); - if (ret) - break; - } - mutex_unlock(&chip->lock); - return ret; -} - -static int lp5521_led_brightness(struct lp55xx_led *led) -{ - struct lp55xx_chip *chip = led->chip; - int ret; - - mutex_lock(&chip->lock); - ret = lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr, - led->brightness); - mutex_unlock(&chip->lock); - - return ret; -} - -static ssize_t show_engine_mode(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; - - switch (mode) { - case LP55XX_ENGINE_RUN: - return sprintf(buf, "run\n"); - case LP55XX_ENGINE_LOAD: - return sprintf(buf, "load\n"); - case LP55XX_ENGINE_DISABLED: - default: - return sprintf(buf, "disabled\n"); - } -} -show_mode(1) -show_mode(2) -show_mode(3) - -static ssize_t store_engine_mode(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - struct lp55xx_engine *engine = &chip->engines[nr - 1]; - - mutex_lock(&chip->lock); - - chip->engine_idx = nr; - - if (!strncmp(buf, "run", 3)) { - lp5521_run_engine(chip, true); - engine->mode = LP55XX_ENGINE_RUN; - } else if (!strncmp(buf, "load", 4)) { - lp5521_stop_engine(chip); - lp5521_load_engine(chip); - engine->mode = LP55XX_ENGINE_LOAD; - } else if (!strncmp(buf, "disabled", 8)) { - lp5521_stop_engine(chip); - engine->mode = LP55XX_ENGINE_DISABLED; - } - - mutex_unlock(&chip->lock); - - return len; -} -store_mode(1) -store_mode(2) -store_mode(3) - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int ret; - - mutex_lock(&chip->lock); - - chip->engine_idx = nr; - lp5521_load_engine(chip); - ret = lp5521_update_program_memory(chip, buf, len); - - mutex_unlock(&chip->lock); - - return ret; -} -store_load(1) -store_load(2) -store_load(3) - static ssize_t lp5521_selftest(struct device *dev, struct device_attribute *attr, char *buf) @@ -466,20 +186,20 @@ static ssize_t lp5521_selftest(struct device *dev, struct lp55xx_chip *chip = led->chip; int ret; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + ret = lp5521_run_selftest(chip, buf); - mutex_unlock(&chip->lock); return sysfs_emit(buf, "%s\n", ret ? "FAIL" : "OK"); } /* device attributes */ -static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); -static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); -static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); -static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); -static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); -static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +LP55XX_DEV_ATTR_ENGINE_MODE(1); +LP55XX_DEV_ATTR_ENGINE_MODE(2); +LP55XX_DEV_ATTR_ENGINE_MODE(3); +LP55XX_DEV_ATTR_ENGINE_LOAD(1); +LP55XX_DEV_ATTR_ENGINE_LOAD(2); +LP55XX_DEV_ATTR_ENGINE_LOAD(3); static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest); static struct attribute *lp5521_attributes[] = { @@ -499,6 +219,12 @@ static const struct attribute_group lp5521_group = { /* Chip specific configurations */ static struct lp55xx_device_config lp5521_cfg = { + .reg_op_mode = { + .addr = LP5521_REG_OP_MODE, + }, + .reg_exec = { + .addr = LP5521_REG_ENABLE, + }, .reset = { .addr = LP5521_REG_RESET, .val = LP5521_RESET, @@ -507,97 +233,33 @@ static struct lp55xx_device_config lp5521_cfg = { .addr = LP5521_REG_ENABLE, .val = LP5521_ENABLE_DEFAULT, }, + .prog_mem_base = { + .addr = LP5521_REG_R_PROG_MEM, + }, + .reg_led_pwm_base = { + .addr = LP5521_REG_LED_PWM_BASE, + }, + .reg_led_current_base = { + .addr = LP5521_REG_LED_CURRENT_BASE, + }, .max_channel = LP5521_MAX_LEDS, .post_init_device = lp5521_post_init_device, - .brightness_fn = lp5521_led_brightness, - .multicolor_brightness_fn = lp5521_multicolor_brightness, - .set_led_current = lp5521_set_led_current, - .firmware_cb = lp5521_firmware_loaded, + .brightness_fn = lp55xx_led_brightness, + .multicolor_brightness_fn = lp55xx_multicolor_brightness, + .set_led_current = lp55xx_set_led_current, + .firmware_cb = lp55xx_firmware_loaded_cb, .run_engine = lp5521_run_engine, .dev_attr_group = &lp5521_group, }; -static int lp5521_probe(struct i2c_client *client) -{ - const struct i2c_device_id *id = i2c_client_get_device_id(client); - int ret; - struct lp55xx_chip *chip; - struct lp55xx_led *led; - struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev); - struct device_node *np = dev_of_node(&client->dev); - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->cfg = &lp5521_cfg; - - if (!pdata) { - if (np) { - pdata = lp55xx_of_populate_pdata(&client->dev, np, - chip); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - } else { - dev_err(&client->dev, "no platform data\n"); - return -EINVAL; - } - } - - led = devm_kcalloc(&client->dev, - pdata->num_channels, sizeof(*led), GFP_KERNEL); - if (!led) - return -ENOMEM; - - chip->cl = client; - chip->pdata = pdata; - - mutex_init(&chip->lock); - - i2c_set_clientdata(client, led); - - ret = lp55xx_init_device(chip); - if (ret) - goto err_init; - - dev_info(&client->dev, "%s programmable led chip found\n", id->name); - - ret = lp55xx_register_leds(led, chip); - if (ret) - goto err_out; - - ret = lp55xx_register_sysfs(chip); - if (ret) { - dev_err(&client->dev, "registering sysfs failed\n"); - goto err_out; - } - - return 0; - -err_out: - lp55xx_deinit_device(chip); -err_init: - return ret; -} - -static void lp5521_remove(struct i2c_client *client) -{ - struct lp55xx_led *led = i2c_get_clientdata(client); - struct lp55xx_chip *chip = led->chip; - - lp5521_stop_all_engines(chip); - lp55xx_unregister_sysfs(chip); - lp55xx_deinit_device(chip); -} - static const struct i2c_device_id lp5521_id[] = { - { "lp5521", 0 }, /* Three channel chip */ + { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */ { } }; MODULE_DEVICE_TABLE(i2c, lp5521_id); static const struct of_device_id of_lp5521_leds_match[] = { - { .compatible = "national,lp5521", }, + { .compatible = "national,lp5521", .data = &lp5521_cfg, }, {}, }; @@ -608,8 +270,8 @@ static struct i2c_driver lp5521_driver = { .name = "lp5521", .of_match_table = of_lp5521_leds_match, }, - .probe = lp5521_probe, - .remove = lp5521_remove, + .probe = lp55xx_probe, + .remove = lp55xx_remove, .id_table = lp5521_id, }; diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 38de853f9939..4ed3e735260c 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -9,6 +9,7 @@ * Milo(Woogyom) Kim <milo.kim@ti.com> */ +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/firmware.h> #include <linux/i2c.h> @@ -21,7 +22,6 @@ #include "leds-lp55xx-common.h" -#define LP5523_PROGRAM_LENGTH 32 /* bytes */ /* Memory is used like this: * 0x00 engine 1 program * 0x10 engine 2 program @@ -30,6 +30,7 @@ * 0x40 engine 2 muxing info * 0x50 engine 3 muxing info */ +#define LP5523_PAGES_PER_ENGINE 1 #define LP5523_MAX_LEDS 9 /* Registers */ @@ -41,7 +42,10 @@ #define LP5523_REG_LED_PWM_BASE 0x16 #define LP5523_REG_LED_CURRENT_BASE 0x26 #define LP5523_REG_CONFIG 0x36 + #define LP5523_REG_STATUS 0x3A +#define LP5523_ENGINE_BUSY BIT(4) + #define LP5523_REG_RESET 0x3D #define LP5523_REG_LED_TEST_CTRL 0x41 #define LP5523_REG_LED_TEST_ADC 0x42 @@ -70,61 +74,8 @@ #define LP5523_EXT_CLK_USED 0x08 #define LP5523_ENG_STATUS_MASK 0x07 -#define LP5523_FADER_MAPPING_MASK 0xC0 -#define LP5523_FADER_MAPPING_SHIFT 6 - -/* Memory Page Selection */ -#define LP5523_PAGE_ENG1 0 -#define LP5523_PAGE_ENG2 1 -#define LP5523_PAGE_ENG3 2 -#define LP5523_PAGE_MUX1 3 -#define LP5523_PAGE_MUX2 4 -#define LP5523_PAGE_MUX3 5 - -/* Program Memory Operations */ -#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */ -#define LP5523_MODE_ENG2_M 0x0C -#define LP5523_MODE_ENG3_M 0x03 -#define LP5523_LOAD_ENG1 0x10 -#define LP5523_LOAD_ENG2 0x04 -#define LP5523_LOAD_ENG3 0x01 - -#define LP5523_ENG1_IS_LOADING(mode) \ - ((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1) -#define LP5523_ENG2_IS_LOADING(mode) \ - ((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2) -#define LP5523_ENG3_IS_LOADING(mode) \ - ((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3) - -#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */ -#define LP5523_EXEC_ENG2_M 0x0C -#define LP5523_EXEC_ENG3_M 0x03 -#define LP5523_EXEC_M 0x3F -#define LP5523_RUN_ENG1 0x20 -#define LP5523_RUN_ENG2 0x08 -#define LP5523_RUN_ENG3 0x02 - -#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led))) - -enum lp5523_chip_id { - LP5523, - LP55231, -}; - static int lp5523_init_program_engine(struct lp55xx_chip *chip); -static inline void lp5523_wait_opmode_done(void) -{ - usleep_range(1000, 2000); -} - -static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current) -{ - led->led_current = led_current; - lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr, - led_current); -} - static int lp5523_post_init_device(struct lp55xx_chip *chip) { int ret; @@ -156,114 +107,16 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip) return lp5523_init_program_engine(chip); } -static void lp5523_load_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M, - [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M, - [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M, - }; - - static const u8 val[] = { - [LP55XX_ENGINE_1] = LP5523_LOAD_ENG1, - [LP55XX_ENGINE_2] = LP5523_LOAD_ENG2, - [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3, - }; - - lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]); - - lp5523_wait_opmode_done(); -} - -static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 page_sel[] = { - [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1, - [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2, - [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3, - }; - - lp5523_load_engine(chip); - - lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]); -} - -static void lp5523_stop_all_engines(struct lp55xx_chip *chip) -{ - lp55xx_write(chip, LP5523_REG_OP_MODE, 0); - lp5523_wait_opmode_done(); -} - -static void lp5523_stop_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M, - [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M, - [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M, - }; - - lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0); - - lp5523_wait_opmode_done(); -} - -static void lp5523_turn_off_channels(struct lp55xx_chip *chip) -{ - int i; - - for (i = 0; i < LP5523_MAX_LEDS; i++) - lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0); -} - static void lp5523_run_engine(struct lp55xx_chip *chip, bool start) { - int ret; - u8 mode; - u8 exec; - /* stop engine */ if (!start) { - lp5523_stop_engine(chip); - lp5523_turn_off_channels(chip); + lp55xx_stop_engine(chip); + lp55xx_turn_off_channels(chip); return; } - /* - * To run the engine, - * operation mode and enable register should updated at the same time - */ - - ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode); - if (ret) - return; - - ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec); - if (ret) - return; - - /* change operation mode to RUN only when each engine is loading */ - if (LP5523_ENG1_IS_LOADING(mode)) { - mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1; - exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1; - } - - if (LP5523_ENG2_IS_LOADING(mode)) { - mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2; - exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2; - } - - if (LP5523_ENG3_IS_LOADING(mode)) { - mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3; - exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3; - } - - lp55xx_write(chip, LP5523_REG_OP_MODE, mode); - lp5523_wait_opmode_done(); - - lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec); + lp55xx_run_engine_common(chip); } static int lp5523_init_program_engine(struct lp55xx_chip *chip) @@ -273,7 +126,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip) int ret; u8 status; /* one pattern per engine setting LED MUX start and stop addresses */ - static const u8 pattern[][LP5523_PROGRAM_LENGTH] = { + static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = { { 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0}, @@ -295,9 +148,9 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip) /* write LED MUX address space for each engine */ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { chip->engine_idx = i; - lp5523_load_engine_and_select_page(chip); + lp55xx_load_engine(chip); - for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) { + for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) { ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j, pattern[i - 1][j]); if (ret) @@ -322,261 +175,9 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip) } out: - lp5523_stop_all_engines(chip); - return ret; -} - -static int lp5523_update_program_memory(struct lp55xx_chip *chip, - const u8 *data, size_t size) -{ - u8 pattern[LP5523_PROGRAM_LENGTH] = {0}; - unsigned int cmd; - char c[3]; - int nrchars; - int ret; - int offset = 0; - int i = 0; - - while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(data + offset, "%2s%n ", c, &nrchars); - if (ret != 1) - goto err; - - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto err; - - pattern[i] = (u8)cmd; - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto err; - - for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) { - ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]); - if (ret) - return -EINVAL; - } - - return size; - -err: - dev_err(&chip->cl->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static void lp5523_firmware_loaded(struct lp55xx_chip *chip) -{ - const struct firmware *fw = chip->fw; - - if (fw->size > LP5523_PROGRAM_LENGTH) { - dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", - fw->size); - return; - } - - /* - * Program memory sequence - * 1) set engine mode to "LOAD" - * 2) write firmware data into program memory - */ - - lp5523_load_engine_and_select_page(chip); - lp5523_update_program_memory(chip, fw->data, fw->size); -} - -static ssize_t show_engine_mode(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; - - switch (mode) { - case LP55XX_ENGINE_RUN: - return sprintf(buf, "run\n"); - case LP55XX_ENGINE_LOAD: - return sprintf(buf, "load\n"); - case LP55XX_ENGINE_DISABLED: - default: - return sprintf(buf, "disabled\n"); - } -} -show_mode(1) -show_mode(2) -show_mode(3) - -static ssize_t store_engine_mode(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - struct lp55xx_engine *engine = &chip->engines[nr - 1]; - - mutex_lock(&chip->lock); - - chip->engine_idx = nr; - - if (!strncmp(buf, "run", 3)) { - lp5523_run_engine(chip, true); - engine->mode = LP55XX_ENGINE_RUN; - } else if (!strncmp(buf, "load", 4)) { - lp5523_stop_engine(chip); - lp5523_load_engine(chip); - engine->mode = LP55XX_ENGINE_LOAD; - } else if (!strncmp(buf, "disabled", 8)) { - lp5523_stop_engine(chip); - engine->mode = LP55XX_ENGINE_DISABLED; - } - - mutex_unlock(&chip->lock); - - return len; -} -store_mode(1) -store_mode(2) -store_mode(3) - -static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len) -{ - u16 tmp_mux = 0; - int i; - - len = min_t(int, len, LP5523_MAX_LEDS); - - for (i = 0; i < len; i++) { - switch (buf[i]) { - case '1': - tmp_mux |= (1 << i); - break; - case '0': - break; - case '\n': - i = len; - break; - default: - return -1; - } - } - *mux = tmp_mux; - - return 0; -} - -static void lp5523_mux_to_array(u16 led_mux, char *array) -{ - int i, pos = 0; - - for (i = 0; i < LP5523_MAX_LEDS; i++) - pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i)); - - array[pos] = '\0'; -} - -static ssize_t show_engine_leds(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - char mux[LP5523_MAX_LEDS + 1]; - - lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux); - - return sprintf(buf, "%s\n", mux); -} -show_leds(1) -show_leds(2) -show_leds(3) - -static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr) -{ - struct lp55xx_engine *engine = &chip->engines[nr - 1]; - int ret; - static const u8 mux_page[] = { - [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1, - [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2, - [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3, - }; - - lp5523_load_engine(chip); - - ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]); - if (ret) - return ret; - - ret = lp55xx_write(chip, LP5523_REG_PROG_MEM, (u8)(mux >> 8)); - if (ret) - return ret; - - ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux)); - if (ret) - return ret; - - engine->led_mux = mux; - return 0; -} - -static ssize_t store_engine_leds(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - struct lp55xx_engine *engine = &chip->engines[nr - 1]; - u16 mux = 0; - ssize_t ret; - - if (lp5523_mux_parse(buf, &mux, len)) - return -EINVAL; - - mutex_lock(&chip->lock); - - chip->engine_idx = nr; - ret = -EINVAL; - - if (engine->mode != LP55XX_ENGINE_LOAD) - goto leave; - - if (lp5523_load_mux(chip, mux, nr)) - goto leave; - - ret = len; -leave: - mutex_unlock(&chip->lock); + lp55xx_stop_all_engine(chip); return ret; } -store_leds(1) -store_leds(2) -store_leds(3) - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int ret; - - mutex_lock(&chip->lock); - - chip->engine_idx = nr; - lp5523_load_engine_and_select_page(chip); - ret = lp5523_update_program_memory(chip, buf, len); - - mutex_unlock(&chip->lock); - - return ret; -} -store_load(1) -store_load(2) -store_load(3) static ssize_t lp5523_selftest(struct device *dev, struct device_attribute *attr, @@ -588,16 +189,16 @@ static ssize_t lp5523_selftest(struct device *dev, int ret, pos = 0; u8 status, adc, vdd, i; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); ret = lp55xx_read(chip, LP5523_REG_STATUS, &status); if (ret < 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); /* Check that ext clock is really in use if requested */ if (pdata->clock_mode == LP55XX_CLOCK_EXT) { if ((status & LP5523_EXT_CLK_USED) == 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); } /* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */ @@ -605,14 +206,14 @@ static ssize_t lp5523_selftest(struct device *dev, usleep_range(3000, 6000); /* ADC conversion time is typically 2.7 ms */ ret = lp55xx_read(chip, LP5523_REG_STATUS, &status); if (ret < 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); if (!(status & LP5523_LEDTEST_DONE)) usleep_range(3000, 6000); /* Was not ready. Wait little bit */ ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &vdd); if (ret < 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); vdd--; /* There may be some fluctuation in measurement */ @@ -635,18 +236,18 @@ static ssize_t lp5523_selftest(struct device *dev, usleep_range(3000, 6000); ret = lp55xx_read(chip, LP5523_REG_STATUS, &status); if (ret < 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); if (!(status & LP5523_LEDTEST_DONE)) usleep_range(3000, 6000); /* Was not ready. Wait. */ ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc); if (ret < 0) - goto fail; + return sysfs_emit(buf, "FAIL\n"); if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM) - pos += sprintf(buf + pos, "LED %d FAIL\n", - led->chan_nr); + pos += sysfs_emit_at(buf, pos, "LED %d FAIL\n", + led->chan_nr); lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr, 0x00); @@ -656,198 +257,25 @@ static ssize_t lp5523_selftest(struct device *dev, led->led_current); led++; } - if (pos == 0) - pos = sprintf(buf, "OK\n"); - goto release_lock; -fail: - pos = sprintf(buf, "FAIL\n"); - -release_lock: - mutex_unlock(&chip->lock); - - return pos; -} - -#define show_fader(nr) \ -static ssize_t show_master_fader##nr(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_master_fader(dev, attr, buf, nr); \ -} - -#define store_fader(nr) \ -static ssize_t store_master_fader##nr(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_master_fader(dev, attr, buf, len, nr); \ -} - -static ssize_t show_master_fader(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int ret; - u8 val; - mutex_lock(&chip->lock); - ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val); - mutex_unlock(&chip->lock); - - if (ret == 0) - ret = sprintf(buf, "%u\n", val); - - return ret; + return pos == 0 ? sysfs_emit(buf, "OK\n") : pos; } -show_fader(1) -show_fader(2) -show_fader(3) - -static ssize_t store_master_fader(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int ret; - unsigned long val; - - if (kstrtoul(buf, 0, &val)) - return -EINVAL; - - if (val > 0xff) - return -EINVAL; - - mutex_lock(&chip->lock); - ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, - (u8)val); - mutex_unlock(&chip->lock); - - if (ret == 0) - ret = len; - return ret; -} -store_fader(1) -store_fader(2) -store_fader(3) - -static ssize_t show_master_fader_leds(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int i, ret, pos = 0; - u8 val; - - mutex_lock(&chip->lock); - - for (i = 0; i < LP5523_MAX_LEDS; i++) { - ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val); - if (ret) - goto leave; - - val = (val & LP5523_FADER_MAPPING_MASK) - >> LP5523_FADER_MAPPING_SHIFT; - if (val > 3) { - ret = -EINVAL; - goto leave; - } - buf[pos++] = val + '0'; - } - buf[pos++] = '\n'; - ret = pos; -leave: - mutex_unlock(&chip->lock); - return ret; -} - -static ssize_t store_master_fader_leds(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); - struct lp55xx_chip *chip = led->chip; - int i, n, ret; - u8 val; - - n = min_t(int, len, LP5523_MAX_LEDS); - - mutex_lock(&chip->lock); - - for (i = 0; i < n; i++) { - if (buf[i] >= '0' && buf[i] <= '3') { - val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT; - ret = lp55xx_update_bits(chip, - LP5523_REG_LED_CTRL_BASE + i, - LP5523_FADER_MAPPING_MASK, - val); - if (ret) - goto leave; - } else { - ret = -EINVAL; - goto leave; - } - } - ret = len; -leave: - mutex_unlock(&chip->lock); - return ret; -} - -static int lp5523_multicolor_brightness(struct lp55xx_led *led) -{ - struct lp55xx_chip *chip = led->chip; - int ret; - int i; - - mutex_lock(&chip->lock); - for (i = 0; i < led->mc_cdev.num_colors; i++) { - ret = lp55xx_write(chip, - LP5523_REG_LED_PWM_BASE + - led->mc_cdev.subled_info[i].channel, - led->mc_cdev.subled_info[i].brightness); - if (ret) - break; - } - mutex_unlock(&chip->lock); - return ret; -} - -static int lp5523_led_brightness(struct lp55xx_led *led) -{ - struct lp55xx_chip *chip = led->chip; - int ret; - - mutex_lock(&chip->lock); - ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr, - led->brightness); - mutex_unlock(&chip->lock); - return ret; -} - -static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); -static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); -static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); -static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds); -static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds); -static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds); -static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); -static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); -static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +LP55XX_DEV_ATTR_ENGINE_MODE(1); +LP55XX_DEV_ATTR_ENGINE_MODE(2); +LP55XX_DEV_ATTR_ENGINE_MODE(3); +LP55XX_DEV_ATTR_ENGINE_LEDS(1); +LP55XX_DEV_ATTR_ENGINE_LEDS(2); +LP55XX_DEV_ATTR_ENGINE_LEDS(3); +LP55XX_DEV_ATTR_ENGINE_LOAD(1); +LP55XX_DEV_ATTR_ENGINE_LOAD(2); +LP55XX_DEV_ATTR_ENGINE_LOAD(3); static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest); -static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1, - store_master_fader1); -static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2, - store_master_fader2); -static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3, - store_master_fader3); -static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds, - store_master_fader_leds); +LP55XX_DEV_ATTR_MASTER_FADER(1); +LP55XX_DEV_ATTR_MASTER_FADER(2); +LP55XX_DEV_ATTR_MASTER_FADER(3); +static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds, + lp55xx_store_master_fader_leds); static struct attribute *lp5523_attributes[] = { &dev_attr_engine1_mode.attr, @@ -873,6 +301,16 @@ static const struct attribute_group lp5523_group = { /* Chip specific configurations */ static struct lp55xx_device_config lp5523_cfg = { + .reg_op_mode = { + .addr = LP5523_REG_OP_MODE, + }, + .reg_exec = { + .addr = LP5523_REG_ENABLE, + }, + .engine_busy = { + .addr = LP5523_REG_STATUS, + .mask = LP5523_ENGINE_BUSY, + }, .reset = { .addr = LP5523_REG_RESET, .val = LP5523_RESET, @@ -881,100 +319,43 @@ static struct lp55xx_device_config lp5523_cfg = { .addr = LP5523_REG_ENABLE, .val = LP5523_ENABLE, }, + .prog_mem_base = { + .addr = LP5523_REG_PROG_MEM, + }, + .reg_led_pwm_base = { + .addr = LP5523_REG_LED_PWM_BASE, + }, + .reg_led_current_base = { + .addr = LP5523_REG_LED_CURRENT_BASE, + }, + .reg_master_fader_base = { + .addr = LP5523_REG_MASTER_FADER_BASE, + }, + .reg_led_ctrl_base = { + .addr = LP5523_REG_LED_CTRL_BASE, + }, + .pages_per_engine = LP5523_PAGES_PER_ENGINE, .max_channel = LP5523_MAX_LEDS, .post_init_device = lp5523_post_init_device, - .brightness_fn = lp5523_led_brightness, - .multicolor_brightness_fn = lp5523_multicolor_brightness, - .set_led_current = lp5523_set_led_current, - .firmware_cb = lp5523_firmware_loaded, + .brightness_fn = lp55xx_led_brightness, + .multicolor_brightness_fn = lp55xx_multicolor_brightness, + .set_led_current = lp55xx_set_led_current, + .firmware_cb = lp55xx_firmware_loaded_cb, .run_engine = lp5523_run_engine, .dev_attr_group = &lp5523_group, }; -static int lp5523_probe(struct i2c_client *client) -{ - const struct i2c_device_id *id = i2c_client_get_device_id(client); - int ret; - struct lp55xx_chip *chip; - struct lp55xx_led *led; - struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev); - struct device_node *np = dev_of_node(&client->dev); - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->cfg = &lp5523_cfg; - - if (!pdata) { - if (np) { - pdata = lp55xx_of_populate_pdata(&client->dev, np, - chip); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - } else { - dev_err(&client->dev, "no platform data\n"); - return -EINVAL; - } - } - - led = devm_kcalloc(&client->dev, - pdata->num_channels, sizeof(*led), GFP_KERNEL); - if (!led) - return -ENOMEM; - - chip->cl = client; - chip->pdata = pdata; - - mutex_init(&chip->lock); - - i2c_set_clientdata(client, led); - - ret = lp55xx_init_device(chip); - if (ret) - goto err_init; - - dev_info(&client->dev, "%s Programmable led chip found\n", id->name); - - ret = lp55xx_register_leds(led, chip); - if (ret) - goto err_out; - - ret = lp55xx_register_sysfs(chip); - if (ret) { - dev_err(&client->dev, "registering sysfs failed\n"); - goto err_out; - } - - return 0; - -err_out: - lp55xx_deinit_device(chip); -err_init: - return ret; -} - -static void lp5523_remove(struct i2c_client *client) -{ - struct lp55xx_led *led = i2c_get_clientdata(client); - struct lp55xx_chip *chip = led->chip; - - lp5523_stop_all_engines(chip); - lp55xx_unregister_sysfs(chip); - lp55xx_deinit_device(chip); -} - static const struct i2c_device_id lp5523_id[] = { - { "lp5523", LP5523 }, - { "lp55231", LP55231 }, + { "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, + { "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, }, { } }; MODULE_DEVICE_TABLE(i2c, lp5523_id); static const struct of_device_id of_lp5523_leds_match[] = { - { .compatible = "national,lp5523", }, - { .compatible = "ti,lp55231", }, + { .compatible = "national,lp5523", .data = &lp5523_cfg, }, + { .compatible = "ti,lp55231", .data = &lp5523_cfg, }, {}, }; @@ -985,8 +366,8 @@ static struct i2c_driver lp5523_driver = { .name = "lp5523x", .of_match_table = of_lp5523_leds_match, }, - .probe = lp5523_probe, - .remove = lp5523_remove, + .probe = lp55xx_probe, + .remove = lp55xx_remove, .id_table = lp5523_id, }; diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c index 39db9aeb67c5..14a4af361b26 100644 --- a/drivers/leds/leds-lp5562.c +++ b/drivers/leds/leds-lp5562.c @@ -7,6 +7,7 @@ * Author: Milo(Woogyom) Kim <milo.kim@ti.com> */ +#include <linux/cleanup.h> #include <linux/delay.h> #include <linux/firmware.h> #include <linux/i2c.h> @@ -19,7 +20,6 @@ #include "leds-lp55xx-common.h" -#define LP5562_PROGRAM_LENGTH 32 #define LP5562_MAX_LEDS 4 /* ENABLE Register 00h */ @@ -38,21 +38,6 @@ /* OPMODE Register 01h */ #define LP5562_REG_OP_MODE 0x01 -#define LP5562_MODE_ENG1_M 0x30 -#define LP5562_MODE_ENG2_M 0x0C -#define LP5562_MODE_ENG3_M 0x03 -#define LP5562_LOAD_ENG1 0x10 -#define LP5562_LOAD_ENG2 0x04 -#define LP5562_LOAD_ENG3 0x01 -#define LP5562_RUN_ENG1 0x20 -#define LP5562_RUN_ENG2 0x08 -#define LP5562_RUN_ENG3 0x02 -#define LP5562_ENG1_IS_LOADING(mode) \ - ((mode & LP5562_MODE_ENG1_M) == LP5562_LOAD_ENG1) -#define LP5562_ENG2_IS_LOADING(mode) \ - ((mode & LP5562_MODE_ENG2_M) == LP5562_LOAD_ENG2) -#define LP5562_ENG3_IS_LOADING(mode) \ - ((mode & LP5562_MODE_ENG3_M) == LP5562_LOAD_ENG3) /* BRIGHTNESS Registers */ #define LP5562_REG_R_PWM 0x04 @@ -124,160 +109,24 @@ static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current) lp55xx_write(led->chip, addr[led->chan_nr], led_current); } -static void lp5562_load_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M, - [LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M, - [LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M, - }; - - static const u8 val[] = { - [LP55XX_ENGINE_1] = LP5562_LOAD_ENG1, - [LP55XX_ENGINE_2] = LP5562_LOAD_ENG2, - [LP55XX_ENGINE_3] = LP5562_LOAD_ENG3, - }; - - lp55xx_update_bits(chip, LP5562_REG_OP_MODE, mask[idx], val[idx]); - - lp5562_wait_opmode_done(); -} - -static void lp5562_stop_engine(struct lp55xx_chip *chip) -{ - lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DISABLE); - lp5562_wait_opmode_done(); -} - static void lp5562_run_engine(struct lp55xx_chip *chip, bool start) { int ret; - u8 mode; - u8 exec; /* stop engine */ if (!start) { lp55xx_write(chip, LP5562_REG_ENABLE, LP5562_ENABLE_DEFAULT); lp5562_wait_enable_done(); - lp5562_stop_engine(chip); + lp55xx_stop_all_engine(chip); lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM); lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); lp5562_wait_opmode_done(); return; } - /* - * To run the engine, - * operation mode and enable register should updated at the same time - */ - - ret = lp55xx_read(chip, LP5562_REG_OP_MODE, &mode); - if (ret) - return; - - ret = lp55xx_read(chip, LP5562_REG_ENABLE, &exec); - if (ret) - return; - - /* change operation mode to RUN only when each engine is loading */ - if (LP5562_ENG1_IS_LOADING(mode)) { - mode = (mode & ~LP5562_MODE_ENG1_M) | LP5562_RUN_ENG1; - exec = (exec & ~LP5562_EXEC_ENG1_M) | LP5562_RUN_ENG1; - } - - if (LP5562_ENG2_IS_LOADING(mode)) { - mode = (mode & ~LP5562_MODE_ENG2_M) | LP5562_RUN_ENG2; - exec = (exec & ~LP5562_EXEC_ENG2_M) | LP5562_RUN_ENG2; - } - - if (LP5562_ENG3_IS_LOADING(mode)) { - mode = (mode & ~LP5562_MODE_ENG3_M) | LP5562_RUN_ENG3; - exec = (exec & ~LP5562_EXEC_ENG3_M) | LP5562_RUN_ENG3; - } - - lp55xx_write(chip, LP5562_REG_OP_MODE, mode); - lp5562_wait_opmode_done(); - - lp55xx_update_bits(chip, LP5562_REG_ENABLE, LP5562_EXEC_M, exec); - lp5562_wait_enable_done(); -} - -static int lp5562_update_firmware(struct lp55xx_chip *chip, - const u8 *data, size_t size) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - u8 pattern[LP5562_PROGRAM_LENGTH] = {0}; - static const u8 addr[] = { - [LP55XX_ENGINE_1] = LP5562_REG_PROG_MEM_ENG1, - [LP55XX_ENGINE_2] = LP5562_REG_PROG_MEM_ENG2, - [LP55XX_ENGINE_3] = LP5562_REG_PROG_MEM_ENG3, - }; - unsigned cmd; - char c[3]; - int program_size; - int nrchars; - int offset = 0; - int ret; - int i; - - /* clear program memory before updating */ - for (i = 0; i < LP5562_PROGRAM_LENGTH; i++) - lp55xx_write(chip, addr[idx] + i, 0); - - i = 0; - while ((offset < size - 1) && (i < LP5562_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(data + offset, "%2s%n ", c, &nrchars); - if (ret != 1) - goto err; - - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto err; - - pattern[i] = (u8)cmd; - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto err; - - program_size = i; - for (i = 0; i < program_size; i++) - lp55xx_write(chip, addr[idx] + i, pattern[i]); - - return 0; - -err: - dev_err(&chip->cl->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static void lp5562_firmware_loaded(struct lp55xx_chip *chip) -{ - const struct firmware *fw = chip->fw; - - /* - * the firmware is encoded in ascii hex character, with 2 chars - * per byte - */ - if (fw->size > (LP5562_PROGRAM_LENGTH * 2)) { - dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", - fw->size); - return; - } - - /* - * Program memory sequence - * 1) set engine mode to "LOAD" - * 2) write firmware data into program memory - */ - - lp5562_load_engine(chip); - lp5562_update_firmware(chip, fw->data, fw->size); + ret = lp55xx_run_engine_common(chip); + if (!ret) + lp5562_wait_enable_done(); } static int lp5562_post_init_device(struct lp55xx_chip *chip) @@ -312,6 +161,30 @@ static int lp5562_post_init_device(struct lp55xx_chip *chip) return 0; } +static int lp5562_multicolor_brightness(struct lp55xx_led *led) +{ + struct lp55xx_chip *chip = led->chip; + static const u8 addr[] = { + LP5562_REG_R_PWM, + LP5562_REG_G_PWM, + LP5562_REG_B_PWM, + LP5562_REG_W_PWM, + }; + int ret; + int i; + + guard(mutex)(&chip->lock); + for (i = 0; i < led->mc_cdev.num_colors; i++) { + ret = lp55xx_write(chip, + addr[led->mc_cdev.subled_info[i].channel], + led->mc_cdev.subled_info[i].brightness); + if (ret) + break; + } + + return ret; +} + static int lp5562_led_brightness(struct lp55xx_led *led) { struct lp55xx_chip *chip = led->chip; @@ -323,9 +196,9 @@ static int lp5562_led_brightness(struct lp55xx_led *led) }; int ret; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + ret = lp55xx_write(chip, addr[led->chan_nr], led->brightness); - mutex_unlock(&chip->lock); return ret; } @@ -348,9 +221,9 @@ static void lp5562_write_program_memory(struct lp55xx_chip *chip, /* check the size of program count */ static inline bool _is_pc_overflow(struct lp55xx_predef_pattern *ptn) { - return ptn->size_r >= LP5562_PROGRAM_LENGTH || - ptn->size_g >= LP5562_PROGRAM_LENGTH || - ptn->size_b >= LP5562_PROGRAM_LENGTH; + return ptn->size_r >= LP55xx_BYTES_PER_PAGE || + ptn->size_g >= LP55xx_BYTES_PER_PAGE || + ptn->size_b >= LP55xx_BYTES_PER_PAGE; } static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode) @@ -369,7 +242,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode) return -EINVAL; } - lp5562_stop_engine(chip); + lp55xx_stop_all_engine(chip); /* Set LED map as RGB */ lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB); @@ -377,7 +250,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode) /* Load engines */ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { chip->engine_idx = i; - lp5562_load_engine(chip); + lp55xx_load_engine(chip); } /* Clear program registers */ @@ -420,9 +293,9 @@ static ssize_t lp5562_store_pattern(struct device *dev, if (mode > num_patterns || !ptn) return -EINVAL; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + ret = lp5562_run_predef_led_pattern(chip, mode); - mutex_unlock(&chip->lock); if (ret) return ret; @@ -472,9 +345,9 @@ static ssize_t lp5562_store_engine_mux(struct device *dev, return -EINVAL; } - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val); - mutex_unlock(&chip->lock); return len; } @@ -495,6 +368,12 @@ static const struct attribute_group lp5562_group = { /* Chip specific configurations */ static struct lp55xx_device_config lp5562_cfg = { .max_channel = LP5562_MAX_LEDS, + .reg_op_mode = { + .addr = LP5562_REG_OP_MODE, + }, + .reg_exec = { + .addr = LP5562_REG_ENABLE, + }, .reset = { .addr = LP5562_REG_RESET, .val = LP5562_RESET, @@ -503,94 +382,26 @@ static struct lp55xx_device_config lp5562_cfg = { .addr = LP5562_REG_ENABLE, .val = LP5562_ENABLE_DEFAULT, }, + .prog_mem_base = { + .addr = LP5562_REG_PROG_MEM_ENG1, + }, .post_init_device = lp5562_post_init_device, .set_led_current = lp5562_set_led_current, .brightness_fn = lp5562_led_brightness, + .multicolor_brightness_fn = lp5562_multicolor_brightness, .run_engine = lp5562_run_engine, - .firmware_cb = lp5562_firmware_loaded, + .firmware_cb = lp55xx_firmware_loaded_cb, .dev_attr_group = &lp5562_group, }; -static int lp5562_probe(struct i2c_client *client) -{ - int ret; - struct lp55xx_chip *chip; - struct lp55xx_led *led; - struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev); - struct device_node *np = dev_of_node(&client->dev); - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->cfg = &lp5562_cfg; - - if (!pdata) { - if (np) { - pdata = lp55xx_of_populate_pdata(&client->dev, np, - chip); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - } else { - dev_err(&client->dev, "no platform data\n"); - return -EINVAL; - } - } - - - led = devm_kcalloc(&client->dev, - pdata->num_channels, sizeof(*led), GFP_KERNEL); - if (!led) - return -ENOMEM; - - chip->cl = client; - chip->pdata = pdata; - - mutex_init(&chip->lock); - - i2c_set_clientdata(client, led); - - ret = lp55xx_init_device(chip); - if (ret) - goto err_init; - - ret = lp55xx_register_leds(led, chip); - if (ret) - goto err_out; - - ret = lp55xx_register_sysfs(chip); - if (ret) { - dev_err(&client->dev, "registering sysfs failed\n"); - goto err_out; - } - - return 0; - -err_out: - lp55xx_deinit_device(chip); -err_init: - return ret; -} - -static void lp5562_remove(struct i2c_client *client) -{ - struct lp55xx_led *led = i2c_get_clientdata(client); - struct lp55xx_chip *chip = led->chip; - - lp5562_stop_engine(chip); - - lp55xx_unregister_sysfs(chip); - lp55xx_deinit_device(chip); -} - static const struct i2c_device_id lp5562_id[] = { - { "lp5562", 0 }, + { "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, }, { } }; MODULE_DEVICE_TABLE(i2c, lp5562_id); static const struct of_device_id of_lp5562_leds_match[] = { - { .compatible = "ti,lp5562", }, + { .compatible = "ti,lp5562", .data = &lp5562_cfg, }, {}, }; @@ -601,8 +412,8 @@ static struct i2c_driver lp5562_driver = { .name = "lp5562", .of_match_table = of_lp5562_leds_match, }, - .probe = lp5562_probe, - .remove = lp5562_remove, + .probe = lp55xx_probe, + .remove = lp55xx_remove, .id_table = lp5562_id, }; diff --git a/drivers/leds/leds-lp5569.c b/drivers/leds/leds-lp5569.c new file mode 100644 index 000000000000..786f2aa35319 --- /dev/null +++ b/drivers/leds/leds-lp5569.c @@ -0,0 +1,544 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com> + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/iopoll.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_data/leds-lp55xx.h> +#include <linux/slab.h> +#include <dt-bindings/leds/leds-lp55xx.h> + +#include "leds-lp55xx-common.h" + +#define LP5569_MAX_LEDS 9 + +/* Memory is used like this: + * 0x00 engine 1 program (4 pages) + * 0x40 engine 2 program (4 pages) + * 0x80 engine 3 program (4 pages) + * 0xc0 engine 1 muxing info (1 page) + * 0xd0 engine 2 muxing info (1 page) + * 0xe0 engine 3 muxing info (1 page) + */ +#define LP5569_PAGES_PER_ENGINE 4 + +#define LP5569_REG_ENABLE 0x00 +#define LP5569_ENABLE BIT(6) + +#define LP5569_REG_EXEC_CTRL 0x01 +#define LP5569_MODE_ENG_SHIFT 2 + +#define LP5569_REG_OP_MODE 0x02 +#define LP5569_EXEC_ENG_SHIFT 2 + +#define LP5569_REG_ENABLE_LEDS_MSB 0x04 +#define LP5569_REG_ENABLE_LEDS_LSB 0x05 +#define LP5569_REG_LED_CTRL_BASE 0x07 +#define LP5569_FADER_MAPPING_MASK GENMASK(7, 5) +#define LP5569_REG_LED_PWM_BASE 0x16 +#define LP5569_REG_LED_CURRENT_BASE 0x22 +#define LP5569_REG_MISC 0x2F +#define LP5569_AUTO_INC BIT(6) +#define LP5569_PWR_SAVE BIT(5) +#define LP5569_CP_MODE_MASK GENMASK(4, 3) +#define LP5569_PWM_PWR_SAVE BIT(2) +#define LP5569_INTERNAL_CLK BIT(0) +#define LP5569_REG_MISC2 0x33 +#define LP5569_LED_SHORT_TEST BIT(4) +#define LP5569_LED_OPEN_TEST BIT(3) +#define LP5569_REG_STATUS 0x3C +#define LP5569_MASK_BUSY BIT(7) +#define LP5569_STARTUP_BUSY BIT(6) +#define LP5569_ENGINE_BUSY BIT(5) +#define LP5569_ENGINE1_INT BIT(2) +#define LP5569_ENGINE2_INT BIT(1) +#define LP5569_ENGINE3_INT BIT(0) +#define LP5569_ENG_STATUS_MASK (LP5569_ENGINE1_INT | LP5569_ENGINE2_INT | \ + LP5569_ENGINE3_INT) +#define LP5569_REG_IO_CONTROL 0x3D +#define LP5569_CLK_OUTPUT BIT(3) +#define LP5569_REG_RESET 0x3F +#define LP5569_RESET 0xFF +#define LP5569_REG_MASTER_FADER_BASE 0x46 +#define LP5569_REG_CH1_PROG_START 0x4B +#define LP5569_REG_CH2_PROG_START 0x4C +#define LP5569_REG_CH3_PROG_START 0x4D +#define LP5569_REG_PROG_PAGE_SEL 0x4F +#define LP5569_REG_PROG_MEM 0x50 +#define LP5569_REG_LED_FAULT1 0x81 +#define LP5569_LED_FAULT8 BIT(0) +#define LP5569_REG_LED_FAULT2 0x82 +#define LP5569_LED_FAULT7 BIT(7) +#define LP5569_LED_FAULT6 BIT(6) +#define LP5569_LED_FAULT5 BIT(5) +#define LP5569_LED_FAULT4 BIT(4) +#define LP5569_LED_FAULT3 BIT(3) +#define LP5569_LED_FAULT2 BIT(2) +#define LP5569_LED_FAULT1 BIT(1) +#define LP5569_LED_FAULT0 BIT(0) + +#define LP5569_ENG1_PROG_ADDR 0x0 +#define LP5569_ENG2_PROG_ADDR 0x40 +#define LP5569_ENG3_PROG_ADDR 0x80 +#define LP5569_ENG1_MUX_ADDR 0xc0 +#define LP5569_ENG2_MUX_ADDR 0xd0 +#define LP5569_ENG3_MUX_ADDR 0xe0 + +#define LP5569_STARTUP_SLEEP 500 + +#define LEDn_STATUS_FAULT(n, status) ((status) >> (n) & BIT(0)) + +#define LP5569_DEFAULT_CONFIG \ + (LP5569_AUTO_INC | LP5569_PWR_SAVE | LP5569_PWM_PWR_SAVE) + +static void lp5569_run_engine(struct lp55xx_chip *chip, bool start) +{ + if (!start) { + lp55xx_stop_engine(chip); + lp55xx_turn_off_channels(chip); + return; + } + + lp55xx_run_engine_common(chip); +} + +static int lp5569_init_program_engine(struct lp55xx_chip *chip) +{ + int i; + int j; + int ret; + u8 status; + /* Precompiled pattern per ENGINE setting LED MUX start and stop addresses */ + static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = { + { 0x9c, LP5569_ENG1_MUX_ADDR, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0}, + { 0x9c, LP5569_ENG2_MUX_ADDR, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0}, + { 0x9c, LP5569_ENG3_MUX_ADDR, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0}, + }; + + /* Setup each ENGINE program start address */ + ret = lp55xx_write(chip, LP5569_REG_CH1_PROG_START, LP5569_ENG1_PROG_ADDR); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5569_REG_CH2_PROG_START, LP5569_ENG2_PROG_ADDR); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5569_REG_CH3_PROG_START, LP5569_ENG3_PROG_ADDR); + if (ret) + return ret; + + /* Write precompiled pattern for LED MUX address space for each ENGINE */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + chip->engine_idx = i; + lp55xx_load_engine(chip); + + for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) { + ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + j, + pattern[i - 1][j]); + if (ret) + goto out; + } + } + + lp5569_run_engine(chip, true); + + /* Let the programs run for couple of ms and check the engine status */ + usleep_range(3000, 6000); + lp55xx_read(chip, LP5569_REG_STATUS, &status); + status = FIELD_GET(LP5569_ENG_STATUS_MASK, status); + + if (status != LP5569_ENG_STATUS_MASK) { + dev_err(&chip->cl->dev, + "could not configure LED engine, status = 0x%.2x\n", + status); + ret = -EINVAL; + } + +out: + lp55xx_stop_all_engine(chip); + return ret; +} + +static int lp5569_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 val; + + val = LP5569_DEFAULT_CONFIG; + val |= FIELD_PREP(LP5569_CP_MODE_MASK, chip->pdata->charge_pump_mode); + ret = lp55xx_write(chip, LP5569_REG_MISC, val); + if (ret) + return ret; + + if (chip->pdata->clock_mode == LP55XX_CLOCK_INT) { + /* Internal clock MUST be configured before CLK output */ + ret = lp55xx_update_bits(chip, LP5569_REG_MISC, + LP5569_INTERNAL_CLK, + LP5569_INTERNAL_CLK); + if (ret) + return ret; + + ret = lp55xx_update_bits(chip, LP5569_REG_IO_CONTROL, + LP5569_CLK_OUTPUT, + LP5569_CLK_OUTPUT); + if (ret) + return ret; + } + + ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE); + if (ret) + return ret; + + read_poll_timeout(lp55xx_read, ret, !(val & LP5569_STARTUP_BUSY), + LP5569_STARTUP_SLEEP, LP5569_STARTUP_SLEEP * 10, false, + chip, LP5569_REG_STATUS, &val); + + return lp5569_init_program_engine(chip); +} + +static ssize_t lp5569_led_open_test(struct lp55xx_led *led, char *buf) +{ + struct lp55xx_chip *chip = led->chip; + struct lp55xx_platform_data *pdata = chip->pdata; + bool leds_fault[LP5569_MAX_LEDS]; + struct lp55xx_led *led_tmp = led; + int i, ret, pos = 0; + u8 status; + + /* Set in STANDBY state */ + ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0); + if (ret) + goto exit; + + /* Wait 1ms for device to enter STANDBY state */ + usleep_range(1000, 2000); + + /* Set Charge Pump to 1.5x */ + ret = lp55xx_update_bits(chip, LP5569_REG_MISC, + FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BOOST), + LP5569_CP_MODE_MASK); + if (ret) + goto exit; + + /* Enable LED Open Test */ + ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, + LP5569_LED_OPEN_TEST); + if (ret) + goto exit; + + /* Put Device in NORMAL state */ + ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE); + if (ret) + goto exit; + + /* Wait 500 us for device to enter NORMAL state */ + usleep_range(500, 750); + + /* Enable LED and set to 100% brightness */ + for (i = 0; i < pdata->num_channels; i++) { + ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, + LED_FULL); + if (ret) + goto exit; + + led_tmp++; + } + + /* Wait 500 us for device to fill status regs */ + usleep_range(500, 750); + + /* Parse status led fault 1 regs */ + ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status); + if (ret < 0) + goto exit; + + for (i = 0; i < 8; i++) + leds_fault[i] = !!((status >> i) & 0x1); + + /* Parse status led fault 2 regs */ + ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status); + if (ret < 0) + goto exit; + + for (i = 0; i < 1; i++) + leds_fault[i + 8] = !!((status >> i) & 0x1); + + /* Report LED fault */ + led_tmp = led; + for (i = 0; i < pdata->num_channels; i++) { + if (leds_fault[led_tmp->chan_nr]) + pos += sysfs_emit_at(buf, pos, "LED %d OPEN FAIL\n", + led_tmp->chan_nr); + + led_tmp++; + } + + ret = pos; + +exit: + /* Disable LED Open Test */ + lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, 0); + + led_tmp = led; + for (i = 0; i < pdata->num_channels; i++) { + lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0); + + led_tmp++; + } + + return ret; +} + +static ssize_t lp5569_led_short_test(struct lp55xx_led *led, char *buf) +{ + struct lp55xx_chip *chip = led->chip; + struct lp55xx_platform_data *pdata = chip->pdata; + bool leds_fault[LP5569_MAX_LEDS]; + struct lp55xx_led *led_tmp = led; + int i, ret, pos = 0; + u8 status; + + /* Set in STANDBY state */ + ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0); + if (ret) + goto exit; + + /* Wait 1ms for device to enter STANDBY state */ + usleep_range(1000, 2000); + + /* Set Charge Pump to 1x */ + ret = lp55xx_update_bits(chip, LP5569_REG_MISC, + FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BYPASS), + LP5569_CP_MODE_MASK); + if (ret) + goto exit; + + /* Enable LED and set to 100% brightness and current to 100% (25.5mA) */ + for (i = 0; i < pdata->num_channels; i++) { + ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, + LED_FULL); + if (ret) + goto exit; + + ret = lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led_tmp->chan_nr, + LED_FULL); + if (ret) + goto exit; + + led_tmp++; + } + + /* Put Device in NORMAL state */ + ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE); + if (ret) + goto exit; + + /* Wait 500 us for device to enter NORMAL state */ + usleep_range(500, 750); + + /* Enable LED Shorted Test */ + ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, + LP5569_LED_SHORT_TEST); + if (ret) + goto exit; + + /* Wait 500 us for device to fill status regs */ + usleep_range(500, 750); + + /* Parse status led fault 1 regs */ + ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status); + if (ret < 0) + goto exit; + + for (i = 0; i < 8; i++) + leds_fault[i] = !!LEDn_STATUS_FAULT(i, status); + + /* Parse status led fault 2 regs */ + ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status); + if (ret < 0) + goto exit; + + for (i = 0; i < 1; i++) + leds_fault[i + 8] = !!LEDn_STATUS_FAULT(i, status); + + /* Report LED fault */ + led_tmp = led; + for (i = 0; i < pdata->num_channels; i++) { + if (leds_fault[led_tmp->chan_nr]) + pos += sysfs_emit_at(buf, pos, "LED %d SHORTED FAIL\n", + led_tmp->chan_nr); + + led_tmp++; + } + + ret = pos; + +exit: + /* Disable LED Shorted Test */ + lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_SHORT_TEST, 0); + + led_tmp = led; + for (i = 0; i < pdata->num_channels; i++) { + lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0); + + led_tmp++; + } + + return ret; +} + +static ssize_t lp5569_selftest(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + int i, pos = 0; + + guard(mutex)(&chip->lock); + + /* Test LED Open */ + pos = lp5569_led_open_test(led, buf); + if (pos < 0) + return sprintf(buf, "FAIL\n"); + + /* Test LED Shorted */ + pos += lp5569_led_short_test(led, buf); + if (pos < 0) + return sprintf(buf, "FAIL\n"); + + for (i = 0; i < chip->pdata->num_channels; i++) { + /* Restore current */ + lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led->chan_nr, + led->led_current); + + /* Restore brightness */ + lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + led++; + } + + return pos == 0 ? sysfs_emit(buf, "OK\n") : pos; +} + +LP55XX_DEV_ATTR_ENGINE_MODE(1); +LP55XX_DEV_ATTR_ENGINE_MODE(2); +LP55XX_DEV_ATTR_ENGINE_MODE(3); +LP55XX_DEV_ATTR_ENGINE_LEDS(1); +LP55XX_DEV_ATTR_ENGINE_LEDS(2); +LP55XX_DEV_ATTR_ENGINE_LEDS(3); +LP55XX_DEV_ATTR_ENGINE_LOAD(1); +LP55XX_DEV_ATTR_ENGINE_LOAD(2); +LP55XX_DEV_ATTR_ENGINE_LOAD(3); +static LP55XX_DEV_ATTR_RO(selftest, lp5569_selftest); +LP55XX_DEV_ATTR_MASTER_FADER(1); +LP55XX_DEV_ATTR_MASTER_FADER(2); +LP55XX_DEV_ATTR_MASTER_FADER(3); +static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds, + lp55xx_store_master_fader_leds); + +static struct attribute *lp5569_attributes[] = { + &dev_attr_engine1_mode.attr, + &dev_attr_engine2_mode.attr, + &dev_attr_engine3_mode.attr, + &dev_attr_engine1_load.attr, + &dev_attr_engine2_load.attr, + &dev_attr_engine3_load.attr, + &dev_attr_engine1_leds.attr, + &dev_attr_engine2_leds.attr, + &dev_attr_engine3_leds.attr, + &dev_attr_selftest.attr, + &dev_attr_master_fader1.attr, + &dev_attr_master_fader2.attr, + &dev_attr_master_fader3.attr, + &dev_attr_master_fader_leds.attr, + NULL, +}; + +static const struct attribute_group lp5569_group = { + .attrs = lp5569_attributes, +}; + +/* Chip specific configurations */ +static struct lp55xx_device_config lp5569_cfg = { + .reg_op_mode = { + .addr = LP5569_REG_OP_MODE, + .shift = LP5569_MODE_ENG_SHIFT, + }, + .reg_exec = { + .addr = LP5569_REG_EXEC_CTRL, + .shift = LP5569_EXEC_ENG_SHIFT, + }, + .reset = { + .addr = LP5569_REG_RESET, + .val = LP5569_RESET, + }, + .enable = { + .addr = LP5569_REG_ENABLE, + .val = LP5569_ENABLE, + }, + .prog_mem_base = { + .addr = LP5569_REG_PROG_MEM, + }, + .reg_led_pwm_base = { + .addr = LP5569_REG_LED_PWM_BASE, + }, + .reg_led_current_base = { + .addr = LP5569_REG_LED_CURRENT_BASE, + }, + .reg_master_fader_base = { + .addr = LP5569_REG_MASTER_FADER_BASE, + }, + .reg_led_ctrl_base = { + .addr = LP5569_REG_LED_CTRL_BASE, + }, + .pages_per_engine = LP5569_PAGES_PER_ENGINE, + .max_channel = LP5569_MAX_LEDS, + .post_init_device = lp5569_post_init_device, + .brightness_fn = lp55xx_led_brightness, + .multicolor_brightness_fn = lp55xx_multicolor_brightness, + .set_led_current = lp55xx_set_led_current, + .firmware_cb = lp55xx_firmware_loaded_cb, + .run_engine = lp5569_run_engine, + .dev_attr_group = &lp5569_group, +}; + +static const struct i2c_device_id lp5569_id[] = { + { "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, lp5569_id); + +static const struct of_device_id of_lp5569_leds_match[] = { + { .compatible = "ti,lp5569", .data = &lp5569_cfg, }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5569_leds_match); + +static struct i2c_driver lp5569_driver = { + .driver = { + .name = "lp5569", + .of_match_table = of_lp5569_leds_match, + }, + .probe = lp55xx_probe, + .remove = lp55xx_remove, + .id_table = lp5569_id, +}; + +module_i2c_driver(lp5569_driver); + +MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>"); +MODULE_DESCRIPTION("LP5569 LED engine"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c index 8e7074f0fee0..e71456a56ab8 100644 --- a/drivers/leds/leds-lp55xx-common.c +++ b/drivers/leds/leds-lp55xx-common.c @@ -9,10 +9,13 @@ * Derived from leds-lp5521.c, leds-lp5523.c */ +#include <linux/bitfield.h> +#include <linux/cleanup.h> #include <linux/clk.h> #include <linux/delay.h> #include <linux/firmware.h> #include <linux/i2c.h> +#include <linux/iopoll.h> #include <linux/leds.h> #include <linux/module.h> #include <linux/platform_data/leds-lp55xx.h> @@ -22,6 +25,50 @@ #include "leds-lp55xx-common.h" +/* OP MODE require at least 153 us to clear regs */ +#define LP55XX_CMD_SLEEP 200 + +#define LP55xx_PROGRAM_PAGES 16 +#define LP55xx_MAX_PROGRAM_LENGTH (LP55xx_BYTES_PER_PAGE * 4) /* 128 bytes (4 pages) */ + +/* + * Program Memory Operations + * Same Mask for each engine for both mode and exec + * ENG1 GENMASK(3, 2) + * ENG2 GENMASK(5, 4) + * ENG3 GENMASK(7, 6) + */ +#define LP55xx_MODE_DISABLE_ALL_ENG 0x0 +#define LP55xx_MODE_ENG_MASK GENMASK(1, 0) +#define LP55xx_MODE_DISABLE_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x0) +#define LP55xx_MODE_LOAD_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x1) +#define LP55xx_MODE_RUN_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x2) +#define LP55xx_MODE_HALT_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x3) + +#define LP55xx_MODE_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n)))) +#define LP55xx_MODE_ENGn_MASK(n, shift) (LP55xx_MODE_ENG_MASK << LP55xx_MODE_ENGn_SHIFT(n, shift)) +#define LP55xx_MODE_ENGn_GET(n, mode, shift) \ + (((mode) >> LP55xx_MODE_ENGn_SHIFT(n, shift)) & LP55xx_MODE_ENG_MASK) + +#define LP55xx_EXEC_ENG_MASK GENMASK(1, 0) +#define LP55xx_EXEC_HOLD_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x0) +#define LP55xx_EXEC_STEP_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x1) +#define LP55xx_EXEC_RUN_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x2) +#define LP55xx_EXEC_ONCE_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x3) + +#define LP55xx_EXEC_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n)))) +#define LP55xx_EXEC_ENGn_MASK(n, shift) (LP55xx_EXEC_ENG_MASK << LP55xx_EXEC_ENGn_SHIFT(n, shift)) + +/* Memory Page Selection */ +#define LP55xx_REG_PROG_PAGE_SEL 0x4f +/* If supported, each ENGINE have an equal amount of pages offset from page 0 */ +#define LP55xx_PAGE_OFFSET(n, pages) (((n) - 1) * (pages)) + +#define LED_ACTIVE(mux, led) (!!((mux) & (0x0001 << (led)))) + +/* MASTER FADER common property */ +#define LP55xx_FADER_MAPPING_MASK GENMASK(7, 6) + /* External clock rate */ #define LP55XX_CLK_32K 32768 @@ -40,9 +87,259 @@ static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev) return container_of(mc_cdev, struct lp55xx_led, mc_cdev); } +static void lp55xx_wait_opmode_done(struct lp55xx_chip *chip) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + int __always_unused ret; + u8 val; + + /* + * Recent chip supports BUSY bit for engine. + * Check support by checking if val is not 0. + * For legacy device, sleep at least 153 us. + */ + if (cfg->engine_busy.val) { + read_poll_timeout(lp55xx_read, ret, !(val & cfg->engine_busy.mask), + LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 10, false, + chip, cfg->engine_busy.addr, &val); + } else { + usleep_range(LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 2); + } +} + +void lp55xx_stop_all_engine(struct lp55xx_chip *chip) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + + lp55xx_write(chip, cfg->reg_op_mode.addr, LP55xx_MODE_DISABLE_ALL_ENG); + lp55xx_wait_opmode_done(chip); +} +EXPORT_SYMBOL_GPL(lp55xx_stop_all_engine); + +void lp55xx_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + const struct lp55xx_device_config *cfg = chip->cfg; + u8 mask, val; + + mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift); + val = LP55xx_MODE_LOAD_ENG << LP55xx_MODE_ENGn_SHIFT(idx, cfg->reg_op_mode.shift); + + lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, val); + lp55xx_wait_opmode_done(chip); + + /* Setup PAGE if supported (pages_per_engine not 0)*/ + if (cfg->pages_per_engine) + lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, + LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine)); +} +EXPORT_SYMBOL_GPL(lp55xx_load_engine); + +int lp55xx_run_engine_common(struct lp55xx_chip *chip) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + u8 mode, exec; + int i, ret; + + /* To run the engine, both OP MODE and EXEC needs to be put in RUN mode */ + ret = lp55xx_read(chip, cfg->reg_op_mode.addr, &mode); + if (ret) + return ret; + + ret = lp55xx_read(chip, cfg->reg_exec.addr, &exec); + if (ret) + return ret; + + /* Switch to RUN only for engine that were put in LOAD previously */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + if (LP55xx_MODE_ENGn_GET(i, mode, cfg->reg_op_mode.shift) != LP55xx_MODE_LOAD_ENG) + continue; + + mode &= ~LP55xx_MODE_ENGn_MASK(i, cfg->reg_op_mode.shift); + mode |= LP55xx_MODE_RUN_ENG << LP55xx_MODE_ENGn_SHIFT(i, cfg->reg_op_mode.shift); + exec &= ~LP55xx_EXEC_ENGn_MASK(i, cfg->reg_exec.shift); + exec |= LP55xx_EXEC_RUN_ENG << LP55xx_EXEC_ENGn_SHIFT(i, cfg->reg_exec.shift); + } + + lp55xx_write(chip, cfg->reg_op_mode.addr, mode); + lp55xx_wait_opmode_done(chip); + lp55xx_write(chip, cfg->reg_exec.addr, exec); + + return 0; +} +EXPORT_SYMBOL_GPL(lp55xx_run_engine_common); + +int lp55xx_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + const struct lp55xx_device_config *cfg = chip->cfg; + u8 pattern[LP55xx_MAX_PROGRAM_LENGTH] = { }; + u8 start_addr = cfg->prog_mem_base.addr; + int page, i = 0, offset = 0; + int program_length, ret; + + program_length = LP55xx_BYTES_PER_PAGE; + if (cfg->pages_per_engine) + program_length *= cfg->pages_per_engine; + + while ((offset < size - 1) && (i < program_length)) { + unsigned int cmd; + int nrchars; + char c[3]; + + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; + + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; + + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } + + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; + + /* + * For legacy LED chip with no page support, engine base address are + * one after another at offset of 32. + * 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; + + for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) { + /* Write to the next page each 32 bytes (if supported) */ + if (cfg->pages_per_engine) + lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, + LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine) + page); + + for (i = 0; i < LP55xx_BYTES_PER_PAGE; i++) { + ret = lp55xx_write(chip, start_addr + i, + pattern[i + (page * LP55xx_BYTES_PER_PAGE)]); + if (ret) + return -EINVAL; + } + } + + return size; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} +EXPORT_SYMBOL_GPL(lp55xx_update_program_memory); + +void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + const struct firmware *fw = chip->fw; + int program_length; + + program_length = LP55xx_BYTES_PER_PAGE; + if (cfg->pages_per_engine) + program_length *= cfg->pages_per_engine; + + /* + * the firmware is encoded in ascii hex character, with 2 chars + * per byte + */ + if (fw->size > program_length * 2) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } + + /* + * Program memory sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ + + lp55xx_load_engine(chip); + lp55xx_update_program_memory(chip, fw->data, fw->size); +} +EXPORT_SYMBOL_GPL(lp55xx_firmware_loaded_cb); + +int lp55xx_led_brightness(struct lp55xx_led *led) +{ + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int ret; + + guard(mutex)(&chip->lock); + + ret = lp55xx_write(chip, cfg->reg_led_pwm_base.addr + led->chan_nr, + led->brightness); + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_led_brightness); + +int lp55xx_multicolor_brightness(struct lp55xx_led *led) +{ + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int ret; + int i; + + guard(mutex)(&chip->lock); + + for (i = 0; i < led->mc_cdev.num_colors; i++) { + ret = lp55xx_write(chip, + cfg->reg_led_pwm_base.addr + + led->mc_cdev.subled_info[i].channel, + led->mc_cdev.subled_info[i].brightness); + if (ret) + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_multicolor_brightness); + +void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + + led->led_current = led_current; + lp55xx_write(led->chip, cfg->reg_led_current_base.addr + led->chan_nr, + led_current); +} +EXPORT_SYMBOL_GPL(lp55xx_set_led_current); + +void lp55xx_turn_off_channels(struct lp55xx_chip *chip) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + int i; + + for (i = 0; i < cfg->max_channel; i++) + lp55xx_write(chip, cfg->reg_led_pwm_base.addr + i, 0); +} +EXPORT_SYMBOL_GPL(lp55xx_turn_off_channels); + +void lp55xx_stop_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + const struct lp55xx_device_config *cfg = chip->cfg; + u8 mask; + + mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift); + lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, 0); + + lp55xx_wait_opmode_done(chip); +} +EXPORT_SYMBOL_GPL(lp55xx_stop_engine); + static void lp55xx_reset_device(struct lp55xx_chip *chip) { - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; u8 addr = cfg->reset.addr; u8 val = cfg->reset.val; @@ -52,7 +349,7 @@ static void lp55xx_reset_device(struct lp55xx_chip *chip) static int lp55xx_detect_device(struct lp55xx_chip *chip) { - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; u8 addr = cfg->enable.addr; u8 val = cfg->enable.val; int ret; @@ -75,7 +372,7 @@ static int lp55xx_detect_device(struct lp55xx_chip *chip) static int lp55xx_post_init_device(struct lp55xx_chip *chip) { - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; if (!cfg->post_init_device) return 0; @@ -109,9 +406,9 @@ static ssize_t led_current_store(struct device *dev, if (!chip->cfg->set_led_current) return len; - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + chip->cfg->set_led_current(led, (u8)curr); - mutex_unlock(&chip->lock); return len; } @@ -140,7 +437,7 @@ static int lp55xx_set_mc_brightness(struct led_classdev *cdev, { struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev); struct lp55xx_led *led = mcled_cdev_to_led(mc_dev); - struct lp55xx_device_config *cfg = led->chip->cfg; + const struct lp55xx_device_config *cfg = led->chip->cfg; led_mc_calc_color_components(&led->mc_cdev, brightness); return cfg->multicolor_brightness_fn(led); @@ -151,7 +448,7 @@ static int lp55xx_set_brightness(struct led_classdev *cdev, enum led_brightness brightness) { struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); - struct lp55xx_device_config *cfg = led->chip->cfg; + const struct lp55xx_device_config *cfg = led->chip->cfg; led->brightness = (u8)brightness; return cfg->brightness_fn(led); @@ -161,7 +458,7 @@ static int lp55xx_init_led(struct lp55xx_led *led, struct lp55xx_chip *chip, int chan) { struct lp55xx_platform_data *pdata = chip->pdata; - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; struct device *dev = &chip->cl->dev; int max_channel = cfg->max_channel; struct mc_subled *mc_led_info; @@ -246,14 +543,12 @@ static void lp55xx_firmware_loaded(const struct firmware *fw, void *context) } /* handling firmware data is chip dependent */ - mutex_lock(&chip->lock); - - chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; - chip->fw = fw; - if (chip->cfg->firmware_cb) - chip->cfg->firmware_cb(chip); - - mutex_unlock(&chip->lock); + scoped_guard(mutex, &chip->lock) { + chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; + chip->fw = fw; + if (chip->cfg->firmware_cb) + chip->cfg->firmware_cb(chip); + } /* firmware should be released for other channel use */ release_firmware(chip->fw); @@ -270,8 +565,8 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip) } static ssize_t select_engine_show(struct device *dev, - struct device_attribute *attr, - char *buf) + struct device_attribute *attr, + char *buf) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; @@ -280,8 +575,8 @@ static ssize_t select_engine_show(struct device *dev, } static ssize_t select_engine_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) + struct device_attribute *attr, + const char *buf, size_t len) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; @@ -297,10 +592,10 @@ static ssize_t select_engine_store(struct device *dev, case LP55XX_ENGINE_1: case LP55XX_ENGINE_2: case LP55XX_ENGINE_3: - mutex_lock(&chip->lock); - chip->engine_idx = val; - ret = lp55xx_request_firmware(chip); - mutex_unlock(&chip->lock); + scoped_guard(mutex, &chip->lock) { + chip->engine_idx = val; + ret = lp55xx_request_firmware(chip); + } break; default: dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val); @@ -322,8 +617,8 @@ static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start) } static ssize_t run_engine_store(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) + struct device_attribute *attr, + const char *buf, size_t len) { struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); struct lp55xx_chip *chip = led->chip; @@ -339,9 +634,9 @@ static ssize_t run_engine_store(struct device *dev, return len; } - mutex_lock(&chip->lock); + guard(mutex)(&chip->lock); + lp55xx_run_engine(chip, true); - mutex_unlock(&chip->lock); return len; } @@ -349,6 +644,279 @@ static ssize_t run_engine_store(struct device *dev, static DEVICE_ATTR_RW(select_engine); static DEVICE_ATTR_WO(run_engine); +ssize_t lp55xx_show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; + + switch (mode) { + case LP55XX_ENGINE_RUN: + return sysfs_emit(buf, "run\n"); + case LP55XX_ENGINE_LOAD: + return sysfs_emit(buf, "load\n"); + case LP55XX_ENGINE_DISABLED: + default: + return sysfs_emit(buf, "disabled\n"); + } +} +EXPORT_SYMBOL_GPL(lp55xx_show_engine_mode); + +ssize_t lp55xx_store_engine_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + + guard(mutex)(&chip->lock); + + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + cfg->run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp55xx_stop_engine(chip); + lp55xx_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp55xx_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } + + return len; +} +EXPORT_SYMBOL_GPL(lp55xx_store_engine_mode); + +ssize_t lp55xx_store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + int ret; + + guard(mutex)(&chip->lock); + + chip->engine_idx = nr; + lp55xx_load_engine(chip); + ret = lp55xx_update_program_memory(chip, buf, len); + + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_store_engine_load); + +static int lp55xx_mux_parse(struct lp55xx_chip *chip, const char *buf, + u16 *mux, size_t len) +{ + const struct lp55xx_device_config *cfg = chip->cfg; + u16 tmp_mux = 0; + int i; + + len = min_t(int, len, cfg->max_channel); + + for (i = 0; i < len; i++) { + switch (buf[i]) { + case '1': + tmp_mux |= (1 << i); + break; + case '0': + break; + case '\n': + i = len; + break; + default: + return -1; + } + } + *mux = tmp_mux; + + return 0; +} + +ssize_t lp55xx_show_engine_leds(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + unsigned int led_active; + int i, pos = 0; + + for (i = 0; i < cfg->max_channel; i++) { + led_active = LED_ACTIVE(chip->engines[nr - 1].led_mux, i); + pos += sysfs_emit_at(buf, pos, "%x", led_active); + } + + pos += sysfs_emit_at(buf, pos, "\n"); + + return pos; +} +EXPORT_SYMBOL_GPL(lp55xx_show_engine_leds); + +static int lp55xx_load_mux(struct lp55xx_chip *chip, u16 mux, int nr) +{ + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + const struct lp55xx_device_config *cfg = chip->cfg; + u8 mux_page; + int ret; + + lp55xx_load_engine(chip); + + /* Derive the MUX page offset by starting at the end of the ENGINE pages */ + mux_page = cfg->pages_per_engine * LP55XX_ENGINE_MAX + (nr - 1); + ret = lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, mux_page); + if (ret) + return ret; + + ret = lp55xx_write(chip, cfg->prog_mem_base.addr, (u8)(mux >> 8)); + if (ret) + return ret; + + ret = lp55xx_write(chip, cfg->prog_mem_base.addr + 1, (u8)(mux)); + if (ret) + return ret; + + engine->led_mux = mux; + return 0; +} + +ssize_t lp55xx_store_engine_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + u16 mux = 0; + + if (lp55xx_mux_parse(chip, buf, &mux, len)) + return -EINVAL; + + guard(mutex)(&chip->lock); + + chip->engine_idx = nr; + + if (engine->mode != LP55XX_ENGINE_LOAD) + return -EINVAL; + + if (lp55xx_load_mux(chip, mux, nr)) + return -EINVAL; + + return len; +} +EXPORT_SYMBOL_GPL(lp55xx_store_engine_leds); + +ssize_t lp55xx_show_master_fader(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int ret; + u8 val; + + guard(mutex)(&chip->lock); + + ret = lp55xx_read(chip, cfg->reg_master_fader_base.addr + nr - 1, &val); + + return ret ? ret : sysfs_emit(buf, "%u\n", val); +} +EXPORT_SYMBOL_GPL(lp55xx_show_master_fader); + +ssize_t lp55xx_store_master_fader(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int ret; + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + if (val > 0xff) + return -EINVAL; + + guard(mutex)(&chip->lock); + + ret = lp55xx_write(chip, cfg->reg_master_fader_base.addr + nr - 1, + (u8)val); + + return ret ? ret : len; +} +EXPORT_SYMBOL_GPL(lp55xx_store_master_fader); + +ssize_t lp55xx_show_master_fader_leds(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int i, ret, pos = 0; + u8 val; + + guard(mutex)(&chip->lock); + + for (i = 0; i < cfg->max_channel; i++) { + ret = lp55xx_read(chip, cfg->reg_led_ctrl_base.addr + i, &val); + if (ret) + return ret; + + val = FIELD_GET(LP55xx_FADER_MAPPING_MASK, val); + if (val > FIELD_MAX(LP55xx_FADER_MAPPING_MASK)) { + return -EINVAL; + } + buf[pos++] = val + '0'; + } + buf[pos++] = '\n'; + + return pos; +} +EXPORT_SYMBOL_GPL(lp55xx_show_master_fader_leds); + +ssize_t lp55xx_store_master_fader_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + const struct lp55xx_device_config *cfg = chip->cfg; + int i, n, ret; + u8 val; + + n = min_t(int, len, cfg->max_channel); + + guard(mutex)(&chip->lock); + + for (i = 0; i < n; i++) { + if (buf[i] >= '0' && buf[i] <= '3') { + val = (buf[i] - '0') << __bf_shf(LP55xx_FADER_MAPPING_MASK); + ret = lp55xx_update_bits(chip, + cfg->reg_led_ctrl_base.addr + i, + LP55xx_FADER_MAPPING_MASK, + val); + if (ret) + return ret; + } else { + return -EINVAL; + } + } + + return len; +} +EXPORT_SYMBOL_GPL(lp55xx_store_master_fader_leds); + static struct attribute *lp55xx_engine_attributes[] = { &dev_attr_select_engine.attr, &dev_attr_run_engine.attr, @@ -397,24 +965,16 @@ EXPORT_SYMBOL_GPL(lp55xx_update_bits); bool lp55xx_is_extclk_used(struct lp55xx_chip *chip) { struct clk *clk; - int err; - clk = devm_clk_get(&chip->cl->dev, "32k_clk"); + clk = devm_clk_get_enabled(&chip->cl->dev, "32k_clk"); if (IS_ERR(clk)) goto use_internal_clk; - err = clk_prepare_enable(clk); - if (err) - goto use_internal_clk; - - if (clk_get_rate(clk) != LP55XX_CLK_32K) { - clk_disable_unprepare(clk); + if (clk_get_rate(clk) != LP55XX_CLK_32K) goto use_internal_clk; - } dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K); - chip->clk = clk; return true; use_internal_clk: @@ -423,10 +983,18 @@ use_internal_clk: } EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); -int lp55xx_init_device(struct lp55xx_chip *chip) +static void lp55xx_deinit_device(struct lp55xx_chip *chip) +{ + struct lp55xx_platform_data *pdata = chip->pdata; + + if (pdata->enable_gpiod) + gpiod_set_value(pdata->enable_gpiod, 0); +} + +static int lp55xx_init_device(struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata; - struct lp55xx_device_config *cfg; + const struct lp55xx_device_config *cfg; struct device *dev = &chip->cl->dev; int ret = 0; @@ -476,24 +1044,11 @@ err_post_init: err: return ret; } -EXPORT_SYMBOL_GPL(lp55xx_init_device); -void lp55xx_deinit_device(struct lp55xx_chip *chip) +static int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) { struct lp55xx_platform_data *pdata = chip->pdata; - - if (chip->clk) - clk_disable_unprepare(chip->clk); - - if (pdata->enable_gpiod) - gpiod_set_value(pdata->enable_gpiod, 0); -} -EXPORT_SYMBOL_GPL(lp55xx_deinit_device); - -int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) -{ - struct lp55xx_platform_data *pdata = chip->pdata; - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; int num_channels = pdata->num_channels; struct lp55xx_led *each; u8 led_current; @@ -530,12 +1085,11 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) err_init_led: return ret; } -EXPORT_SYMBOL_GPL(lp55xx_register_leds); -int lp55xx_register_sysfs(struct lp55xx_chip *chip) +static int lp55xx_register_sysfs(struct lp55xx_chip *chip) { struct device *dev = &chip->cl->dev; - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; int ret; if (!cfg->run_engine || !cfg->firmware_cb) @@ -549,19 +1103,17 @@ dev_specific_attrs: return cfg->dev_attr_group ? sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0; } -EXPORT_SYMBOL_GPL(lp55xx_register_sysfs); -void lp55xx_unregister_sysfs(struct lp55xx_chip *chip) +static void lp55xx_unregister_sysfs(struct lp55xx_chip *chip) { struct device *dev = &chip->cl->dev; - struct lp55xx_device_config *cfg = chip->cfg; + const struct lp55xx_device_config *cfg = chip->cfg; if (cfg->dev_attr_group) sysfs_remove_group(&dev->kobj, cfg->dev_attr_group); sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group); } -EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs); static int lp55xx_parse_common_child(struct device_node *np, struct lp55xx_led_config *cfg, @@ -580,9 +1132,6 @@ static int lp55xx_parse_common_child(struct device_node *np, if (ret) return ret; - if (*chan_nr < 0 || *chan_nr > cfg->max_channel) - return -EINVAL; - return 0; } @@ -610,16 +1159,13 @@ static int lp55xx_parse_multi_led(struct device_node *np, struct lp55xx_led_config *cfg, int child_number) { - struct device_node *child; int num_colors = 0, ret; - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { ret = lp55xx_parse_multi_led_child(child, cfg, child_number, num_colors); - if (ret) { - of_node_put(child); + if (ret) return ret; - } num_colors++; } @@ -654,9 +1200,9 @@ static int lp55xx_parse_logical_led(struct device_node *np, return ret; } -struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, - struct device_node *np, - struct lp55xx_chip *chip) +static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, + struct device_node *np, + struct lp55xx_chip *chip) { struct device_node *child; struct lp55xx_platform_data *pdata; @@ -713,7 +1259,92 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev, return pdata; } -EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata); + +int lp55xx_probe(struct i2c_client *client) +{ + const struct i2c_device_id *id = i2c_client_get_device_id(client); + int program_length, ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev); + struct device_node *np = dev_of_node(&client->dev); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->cfg = i2c_get_match_data(client); + + if (!pdata) { + if (np) { + pdata = lp55xx_of_populate_pdata(&client->dev, np, + chip); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + + /* Validate max program page */ + program_length = LP55xx_BYTES_PER_PAGE; + if (chip->cfg->pages_per_engine) + program_length *= chip->cfg->pages_per_engine; + + /* support a max of 128bytes */ + if (program_length > LP55xx_MAX_PROGRAM_LENGTH) { + dev_err(&client->dev, "invalid pages_per_engine configured\n"); + return -EINVAL; + } + + led = devm_kcalloc(&client->dev, + pdata->num_channels, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + chip->cl = client; + chip->pdata = pdata; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + dev_info(&client->dev, "%s Programmable led chip found\n", id->name); + + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_out; + + ret = lp55xx_register_sysfs(chip); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto err_out; + } + + return 0; + +err_out: + lp55xx_deinit_device(chip); +err_init: + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_probe); + +void lp55xx_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp55xx_stop_all_engine(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_deinit_device(chip); +} +EXPORT_SYMBOL_GPL(lp55xx_remove); MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); MODULE_DESCRIPTION("LP55xx Common Driver"); diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h index 2f38c5b33830..8fd64ec40919 100644 --- a/drivers/leds/leds-lp55xx-common.h +++ b/drivers/leds/leds-lp55xx-common.h @@ -14,6 +14,8 @@ #include <linux/led-class-multicolor.h> +#define LP55xx_BYTES_PER_PAGE 32 /* bytes */ + enum lp55xx_engine_index { LP55XX_ENGINE_INVALID, LP55XX_ENGINE_1, @@ -35,45 +37,62 @@ enum lp55xx_engine_mode { #define LP55XX_DEV_ATTR_WO(name, store) \ DEVICE_ATTR(name, S_IWUSR, NULL, store) -#define show_mode(nr) \ +#define LP55XX_DEV_ATTR_ENGINE_MODE(nr) \ static ssize_t show_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ + struct device_attribute *attr, \ + char *buf) \ { \ - return show_engine_mode(dev, attr, buf, nr); \ -} - -#define store_mode(nr) \ + return lp55xx_show_engine_mode(dev, attr, buf, nr); \ +} \ static ssize_t store_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ { \ - return store_engine_mode(dev, attr, buf, len, nr); \ -} + return lp55xx_store_engine_mode(dev, attr, buf, len, nr); \ +} \ +static LP55XX_DEV_ATTR_RW(engine##nr##_mode, show_engine##nr##_mode, \ + store_engine##nr##_mode) -#define show_leds(nr) \ +#define LP55XX_DEV_ATTR_ENGINE_LEDS(nr) \ static ssize_t show_engine##nr##_leds(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ + struct device_attribute *attr, \ + char *buf) \ { \ - return show_engine_leds(dev, attr, buf, nr); \ -} - -#define store_leds(nr) \ -static ssize_t store_engine##nr##_leds(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_leds(dev, attr, buf, len, nr); \ -} - -#define store_load(nr) \ + return lp55xx_show_engine_leds(dev, attr, buf, nr); \ +} \ +static ssize_t store_engine##nr##_leds(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return lp55xx_store_engine_leds(dev, attr, buf, len, nr); \ +} \ +static LP55XX_DEV_ATTR_RW(engine##nr##_leds, show_engine##nr##_leds, \ + store_engine##nr##_leds) + +#define LP55XX_DEV_ATTR_ENGINE_LOAD(nr) \ static ssize_t store_engine##nr##_load(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return lp55xx_store_engine_load(dev, attr, buf, len, nr); \ +} \ +static LP55XX_DEV_ATTR_WO(engine##nr##_load, store_engine##nr##_load) + +#define LP55XX_DEV_ATTR_MASTER_FADER(nr) \ +static ssize_t show_master_fader##nr(struct device *dev, \ struct device_attribute *attr, \ - const char *buf, size_t len) \ + char *buf) \ { \ - return store_engine_load(dev, attr, buf, len, nr); \ -} + return lp55xx_show_master_fader(dev, attr, buf, nr); \ +} \ +static ssize_t store_master_fader##nr(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return lp55xx_store_master_fader(dev, attr, buf, len, nr); \ +} \ +static LP55XX_DEV_ATTR_RW(master_fader##nr, show_master_fader##nr, \ + store_master_fader##nr) struct lp55xx_led; struct lp55xx_chip; @@ -81,17 +100,31 @@ struct lp55xx_chip; /* * struct lp55xx_reg * @addr : Register address - * @val : Register value + * @val : Register value (can also used as mask or shift) */ struct lp55xx_reg { u8 addr; - u8 val; + union { + u8 val; + u8 mask; + u8 shift; + }; }; /* * struct lp55xx_device_config + * @reg_op_mode : Chip specific OP MODE reg addr + * @engine_busy : Chip specific engine busy + * (if not supported 153 us sleep) * @reset : Chip specific reset command * @enable : Chip specific enable command + * @prog_mem_base : Chip specific base reg address for chip SMEM programming + * @reg_led_pwm_base : Chip specific base reg address for LED PWM conf + * @reg_led_current_base : Chip specific base reg address for LED current conf + * @reg_master_fader_base : Chip specific base reg address for master fader base + * @reg_led_ctrl_base : Chip specific base reg address for LED ctrl base + * @pages_per_engine : Assigned pages for each engine + * (if not set chip doesn't support pages) * @max_channel : Maximum number of channels * @post_init_device : Chip specific initialization code * @brightness_fn : Brightness function @@ -102,8 +135,17 @@ struct lp55xx_reg { * @dev_attr_group : Device specific attributes */ struct lp55xx_device_config { + const struct lp55xx_reg reg_op_mode; /* addr, shift */ + const struct lp55xx_reg reg_exec; /* addr, shift */ + const struct lp55xx_reg engine_busy; /* addr, mask */ const struct lp55xx_reg reset; const struct lp55xx_reg enable; + const struct lp55xx_reg prog_mem_base; + const struct lp55xx_reg reg_led_pwm_base; + const struct lp55xx_reg reg_led_current_base; + const struct lp55xx_reg reg_master_fader_base; + const struct lp55xx_reg reg_led_ctrl_base; + const int pages_per_engine; const int max_channel; /* define if the device has specific initialization process */ @@ -151,11 +193,10 @@ struct lp55xx_engine { */ struct lp55xx_chip { struct i2c_client *cl; - struct clk *clk; struct lp55xx_platform_data *pdata; struct mutex lock; /* lock for user-space interface */ int num_leds; - struct lp55xx_device_config *cfg; + const struct lp55xx_device_config *cfg; enum lp55xx_engine_index engine_idx; struct lp55xx_engine engines[LP55XX_ENGINE_MAX]; const struct firmware *fw; @@ -191,21 +232,50 @@ extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, /* external clock detection */ extern bool lp55xx_is_extclk_used(struct lp55xx_chip *chip); -/* common device init/deinit functions */ -extern int lp55xx_init_device(struct lp55xx_chip *chip); -extern void lp55xx_deinit_device(struct lp55xx_chip *chip); - -/* common LED class device functions */ -extern int lp55xx_register_leds(struct lp55xx_led *led, - struct lp55xx_chip *chip); +/* common chip functions */ +extern void lp55xx_stop_all_engine(struct lp55xx_chip *chip); +extern void lp55xx_load_engine(struct lp55xx_chip *chip); +extern int lp55xx_run_engine_common(struct lp55xx_chip *chip); +extern int lp55xx_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size); +extern void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip); +extern int lp55xx_led_brightness(struct lp55xx_led *led); +extern int lp55xx_multicolor_brightness(struct lp55xx_led *led); +extern void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current); +extern void lp55xx_turn_off_channels(struct lp55xx_chip *chip); +extern void lp55xx_stop_engine(struct lp55xx_chip *chip); -/* common device attributes functions */ -extern int lp55xx_register_sysfs(struct lp55xx_chip *chip); -extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip); +/* common probe/remove function */ +extern int lp55xx_probe(struct i2c_client *client); +extern void lp55xx_remove(struct i2c_client *client); -/* common device tree population function */ -extern struct lp55xx_platform_data -*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np, - struct lp55xx_chip *chip); +/* common sysfs function */ +extern ssize_t lp55xx_show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr); +extern ssize_t lp55xx_store_engine_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr); +extern ssize_t lp55xx_store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr); +extern ssize_t lp55xx_show_engine_leds(struct device *dev, + struct device_attribute *attr, + char *buf, int nr); +extern ssize_t lp55xx_store_engine_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr); +extern ssize_t lp55xx_show_master_fader(struct device *dev, + struct device_attribute *attr, + char *buf, int nr); +extern ssize_t lp55xx_store_master_fader(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr); +extern ssize_t lp55xx_show_master_fader_leds(struct device *dev, + struct device_attribute *attr, + char *buf); +extern ssize_t lp55xx_store_master_fader_leds(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len); #endif /* _LEDS_LP55XX_COMMON_H */ diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index ac50aa88939a..ee4ff4586bc0 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -20,27 +20,14 @@ #include "leds-lp55xx-common.h" -#define LP8501_PROGRAM_LENGTH 32 +#define LP8501_PAGES_PER_ENGINE 1 #define LP8501_MAX_LEDS 9 /* Registers */ #define LP8501_REG_ENABLE 0x00 #define LP8501_ENABLE BIT(6) -#define LP8501_EXEC_M 0x3F -#define LP8501_EXEC_ENG1_M 0x30 -#define LP8501_EXEC_ENG2_M 0x0C -#define LP8501_EXEC_ENG3_M 0x03 -#define LP8501_RUN_ENG1 0x20 -#define LP8501_RUN_ENG2 0x08 -#define LP8501_RUN_ENG3 0x02 #define LP8501_REG_OP_MODE 0x01 -#define LP8501_MODE_ENG1_M 0x30 -#define LP8501_MODE_ENG2_M 0x0C -#define LP8501_MODE_ENG3_M 0x03 -#define LP8501_LOAD_ENG1 0x10 -#define LP8501_LOAD_ENG2 0x04 -#define LP8501_LOAD_ENG3 0x01 #define LP8501_REG_PWR_CONFIG 0x05 #define LP8501_PWR_CONFIG_M 0x03 @@ -58,35 +45,14 @@ #define LP8501_INT_CLK BIT(0) #define LP8501_DEFAULT_CFG (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE) +#define LP8501_REG_STATUS 0x3A +#define LP8501_ENGINE_BUSY BIT(4) + #define LP8501_REG_RESET 0x3D #define LP8501_RESET 0xFF -#define LP8501_REG_PROG_PAGE_SEL 0x4F -#define LP8501_PAGE_ENG1 0 -#define LP8501_PAGE_ENG2 1 -#define LP8501_PAGE_ENG3 2 - #define LP8501_REG_PROG_MEM 0x50 -#define LP8501_ENG1_IS_LOADING(mode) \ - ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1) -#define LP8501_ENG2_IS_LOADING(mode) \ - ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2) -#define LP8501_ENG3_IS_LOADING(mode) \ - ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3) - -static inline void lp8501_wait_opmode_done(void) -{ - usleep_range(1000, 2000); -} - -static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current) -{ - led->led_current = led_current; - lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr, - led_current); -} - static int lp8501_post_init_device(struct lp55xx_chip *chip) { int ret; @@ -113,178 +79,30 @@ static int lp8501_post_init_device(struct lp55xx_chip *chip) LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel); } -static void lp8501_load_engine(struct lp55xx_chip *chip) -{ - enum lp55xx_engine_index idx = chip->engine_idx; - static const u8 mask[] = { - [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M, - [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M, - [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M, - }; - - static const u8 val[] = { - [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1, - [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2, - [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3, - }; - - static const u8 page_sel[] = { - [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1, - [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2, - [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3, - }; - - lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]); - - lp8501_wait_opmode_done(); - - lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]); -} - -static void lp8501_stop_engine(struct lp55xx_chip *chip) -{ - lp55xx_write(chip, LP8501_REG_OP_MODE, 0); - lp8501_wait_opmode_done(); -} - -static void lp8501_turn_off_channels(struct lp55xx_chip *chip) -{ - int i; - - for (i = 0; i < LP8501_MAX_LEDS; i++) - lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0); -} - static void lp8501_run_engine(struct lp55xx_chip *chip, bool start) { - int ret; - u8 mode; - u8 exec; - /* stop engine */ if (!start) { - lp8501_stop_engine(chip); - lp8501_turn_off_channels(chip); - return; - } - - /* - * To run the engine, - * operation mode and enable register should updated at the same time - */ - - ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode); - if (ret) - return; - - ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec); - if (ret) - return; - - /* change operation mode to RUN only when each engine is loading */ - if (LP8501_ENG1_IS_LOADING(mode)) { - mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1; - exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1; - } - - if (LP8501_ENG2_IS_LOADING(mode)) { - mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2; - exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2; - } - - if (LP8501_ENG3_IS_LOADING(mode)) { - mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3; - exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3; - } - - lp55xx_write(chip, LP8501_REG_OP_MODE, mode); - lp8501_wait_opmode_done(); - - lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec); -} - -static int lp8501_update_program_memory(struct lp55xx_chip *chip, - const u8 *data, size_t size) -{ - u8 pattern[LP8501_PROGRAM_LENGTH] = {0}; - unsigned cmd; - char c[3]; - int update_size; - int nrchars; - int offset = 0; - int ret; - int i; - - /* clear program memory before updating */ - for (i = 0; i < LP8501_PROGRAM_LENGTH; i++) - lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0); - - i = 0; - while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(data + offset, "%2s%n ", c, &nrchars); - if (ret != 1) - goto err; - - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto err; - - pattern[i] = (u8)cmd; - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto err; - - update_size = i; - for (i = 0; i < update_size; i++) - lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]); - - return 0; - -err: - dev_err(&chip->cl->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static void lp8501_firmware_loaded(struct lp55xx_chip *chip) -{ - const struct firmware *fw = chip->fw; - - if (fw->size > LP8501_PROGRAM_LENGTH) { - dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", - fw->size); + lp55xx_stop_all_engine(chip); + lp55xx_turn_off_channels(chip); return; } - /* - * Program memory sequence - * 1) set engine mode to "LOAD" - * 2) write firmware data into program memory - */ - - lp8501_load_engine(chip); - lp8501_update_program_memory(chip, fw->data, fw->size); -} - -static int lp8501_led_brightness(struct lp55xx_led *led) -{ - struct lp55xx_chip *chip = led->chip; - int ret; - - mutex_lock(&chip->lock); - ret = lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr, - led->brightness); - mutex_unlock(&chip->lock); - - return ret; + lp55xx_run_engine_common(chip); } /* Chip specific configurations */ static struct lp55xx_device_config lp8501_cfg = { + .reg_op_mode = { + .addr = LP8501_REG_OP_MODE, + }, + .reg_exec = { + .addr = LP8501_REG_ENABLE, + }, + .engine_busy = { + .addr = LP8501_REG_STATUS, + .mask = LP8501_ENGINE_BUSY, + }, .reset = { .addr = LP8501_REG_RESET, .val = LP8501_RESET, @@ -293,95 +111,32 @@ static struct lp55xx_device_config lp8501_cfg = { .addr = LP8501_REG_ENABLE, .val = LP8501_ENABLE, }, + .prog_mem_base = { + .addr = LP8501_REG_PROG_MEM, + }, + .reg_led_pwm_base = { + .addr = LP8501_REG_LED_PWM_BASE, + }, + .reg_led_current_base = { + .addr = LP8501_REG_LED_CURRENT_BASE, + }, + .pages_per_engine = LP8501_PAGES_PER_ENGINE, .max_channel = LP8501_MAX_LEDS, .post_init_device = lp8501_post_init_device, - .brightness_fn = lp8501_led_brightness, - .set_led_current = lp8501_set_led_current, - .firmware_cb = lp8501_firmware_loaded, + .brightness_fn = lp55xx_led_brightness, + .set_led_current = lp55xx_set_led_current, + .firmware_cb = lp55xx_firmware_loaded_cb, .run_engine = lp8501_run_engine, }; -static int lp8501_probe(struct i2c_client *client) -{ - const struct i2c_device_id *id = i2c_client_get_device_id(client); - int ret; - struct lp55xx_chip *chip; - struct lp55xx_led *led; - struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev); - struct device_node *np = dev_of_node(&client->dev); - - chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->cfg = &lp8501_cfg; - - if (!pdata) { - if (np) { - pdata = lp55xx_of_populate_pdata(&client->dev, np, - chip); - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - } else { - dev_err(&client->dev, "no platform data\n"); - return -EINVAL; - } - } - - led = devm_kcalloc(&client->dev, - pdata->num_channels, sizeof(*led), GFP_KERNEL); - if (!led) - return -ENOMEM; - - chip->cl = client; - chip->pdata = pdata; - - mutex_init(&chip->lock); - - i2c_set_clientdata(client, led); - - ret = lp55xx_init_device(chip); - if (ret) - goto err_init; - - dev_info(&client->dev, "%s Programmable led chip found\n", id->name); - - ret = lp55xx_register_leds(led, chip); - if (ret) - goto err_out; - - ret = lp55xx_register_sysfs(chip); - if (ret) { - dev_err(&client->dev, "registering sysfs failed\n"); - goto err_out; - } - - return 0; - -err_out: - lp55xx_deinit_device(chip); -err_init: - return ret; -} - -static void lp8501_remove(struct i2c_client *client) -{ - struct lp55xx_led *led = i2c_get_clientdata(client); - struct lp55xx_chip *chip = led->chip; - - lp8501_stop_engine(chip); - lp55xx_unregister_sysfs(chip); - lp55xx_deinit_device(chip); -} - static const struct i2c_device_id lp8501_id[] = { - { "lp8501", 0 }, + { "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg, }, { } }; MODULE_DEVICE_TABLE(i2c, lp8501_id); static const struct of_device_id of_lp8501_leds_match[] = { - { .compatible = "ti,lp8501", }, + { .compatible = "ti,lp8501", .data = &lp8501_cfg, }, {}, }; @@ -392,8 +147,8 @@ static struct i2c_driver lp8501_driver = { .name = "lp8501", .of_match_table = of_lp8501_leds_match, }, - .probe = lp8501_probe, - .remove = lp8501_remove, + .probe = lp55xx_probe, + .remove = lp55xx_remove, .id_table = lp8501_id, }; diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 19b621012e58..995f2adf8569 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, @@ -331,7 +331,6 @@ static const struct regmap_config lp8860_regmap_config = { .max_register = LP8860_EEPROM_UNLOCK, .reg_defaults = lp8860_reg_defs, .num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs), - .cache_type = REGCACHE_NONE, }; static const struct reg_default lp8860_eeprom_defs[] = { @@ -369,7 +368,6 @@ static const struct regmap_config lp8860_eeprom_regmap_config = { .max_register = LP8860_EEPROM_REG_24, .reg_defaults = lp8860_eeprom_defs, .num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs), - .cache_type = REGCACHE_NONE, }; static int lp8860_probe(struct i2c_client *client) @@ -459,7 +457,7 @@ static void lp8860_remove(struct i2c_client *client) } static const struct i2c_device_id lp8860_id[] = { - { "lp8860", 0 }, + { "lp8860" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8860_id); 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-max5970.c b/drivers/leds/leds-max5970.c index 56a584311581..285074c53b23 100644 --- a/drivers/leds/leds-max5970.c +++ b/drivers/leds/leds-max5970.c @@ -45,7 +45,7 @@ static int max5970_led_set_brightness(struct led_classdev *cdev, static int max5970_led_probe(struct platform_device *pdev) { - struct fwnode_handle *led_node, *child; + struct fwnode_handle *child; struct device *dev = &pdev->dev; struct regmap *regmap; struct max5970_led *ddata; @@ -55,7 +55,8 @@ static int max5970_led_probe(struct platform_device *pdev) if (!regmap) return -ENODEV; - led_node = device_get_named_child_node(dev->parent, "leds"); + struct fwnode_handle *led_node __free(fwnode_handle) = + device_get_named_child_node(dev->parent, "leds"); if (!led_node) return -ENODEV; diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c index 1eeac56b0014..f8c47078a3bb 100644 --- a/drivers/leds/leds-max77650.c +++ b/drivers/leds/leds-max77650.c @@ -62,7 +62,6 @@ static int max77650_led_brightness_set(struct led_classdev *cdev, static int max77650_led_probe(struct platform_device *pdev) { - struct fwnode_handle *child; struct max77650_led *leds, *led; struct device *dev; struct regmap *map; @@ -84,14 +83,12 @@ static int max77650_led_probe(struct platform_device *pdev) if (!num_leds || num_leds > MAX77650_LED_NUM_LEDS) return -ENODEV; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_init_data init_data = {}; rv = fwnode_property_read_u32(child, "reg", ®); - if (rv || reg >= MAX77650_LED_NUM_LEDS) { - rv = -EINVAL; - goto err_node_put; - } + if (rv || reg >= MAX77650_LED_NUM_LEDS) + return -EINVAL; led = &leds[reg]; led->map = map; @@ -108,23 +105,20 @@ static int max77650_led_probe(struct platform_device *pdev) rv = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); if (rv) - goto err_node_put; + return rv; rv = regmap_write(map, led->regA, MAX77650_LED_A_DEFAULT); if (rv) - goto err_node_put; + return rv; rv = regmap_write(map, led->regB, MAX77650_LED_B_DEFAULT); if (rv) - goto err_node_put; + return rv; } return regmap_write(map, MAX77650_REG_CNFG_LED_TOP, MAX77650_LED_TOP_DEFAULT); -err_node_put: - fwnode_handle_put(child); - return rv; } static const struct of_device_id max77650_led_of_match[] = { diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c new file mode 100644 index 000000000000..933cb4f19be9 --- /dev/null +++ b/drivers/leds/leds-max77705.c @@ -0,0 +1,275 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Based on leds-max77650 driver + * + * LED driver for MAXIM 77705 PMIC. + * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org> + */ + +#include <linux/i2c.h> +#include <linux/led-class-multicolor.h> +#include <linux/leds.h> +#include <linux/mfd/max77705-private.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> + +#define MAX77705_LED_NUM_LEDS 4 +#define MAX77705_LED_EN_MASK GENMASK(1, 0) +#define MAX77705_LED_MAX_BRIGHTNESS 0xff +#define MAX77705_LED_EN_SHIFT(reg) (reg * MAX77705_RGBLED_EN_WIDTH) +#define MAX77705_LED_REG_BRIGHTNESS(reg) (reg + MAX77705_RGBLED_REG_LED0BRT) + +struct max77705_led { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + struct regmap *regmap; + + struct mc_subled *subled_info; +}; + +static const struct regmap_config max77705_leds_regmap_config = { + .reg_base = MAX77705_RGBLED_REG_BASE, + .reg_bits = 8, + .val_bits = 8, + .max_register = MAX77705_LED_REG_END, +}; + +static int max77705_rgb_blink(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct max77705_led *led = container_of(cdev, struct max77705_led, cdev); + int value, on_value, off_value; + + if (*delay_on < MAX77705_RGB_DELAY_100_STEP) + on_value = 0; + else if (*delay_on < MAX77705_RGB_DELAY_100_STEP_LIM) + on_value = *delay_on / MAX77705_RGB_DELAY_100_STEP - 1; + else if (*delay_on < MAX77705_RGB_DELAY_250_STEP_LIM) + on_value = (*delay_on - MAX77705_RGB_DELAY_100_STEP_LIM) / + MAX77705_RGB_DELAY_250_STEP + + MAX77705_RGB_DELAY_100_STEP_COUNT; + else + on_value = 15; + + on_value <<= 4; + + if (*delay_off < 1) + off_value = 0; + else if (*delay_off < MAX77705_RGB_DELAY_500_STEP) + off_value = 1; + else if (*delay_off < MAX77705_RGB_DELAY_500_STEP_LIM) + off_value = *delay_off / MAX77705_RGB_DELAY_500_STEP; + else if (*delay_off < MAX77705_RGB_DELAY_1000_STEP_LIM) + off_value = (*delay_off - MAX77705_RGB_DELAY_1000_STEP_LIM) / + MAX77705_RGB_DELAY_1000_STEP + + MAX77705_RGB_DELAY_500_STEP_COUNT; + else if (*delay_off < MAX77705_RGB_DELAY_2000_STEP_LIM) + off_value = (*delay_off - MAX77705_RGB_DELAY_2000_STEP_LIM) / + MAX77705_RGB_DELAY_2000_STEP + + MAX77705_RGB_DELAY_1000_STEP_COUNT; + else + off_value = 15; + + value = on_value | off_value; + return regmap_write(led->regmap, MAX77705_RGBLED_REG_LEDBLNK, value); +} + +static int max77705_led_brightness_set(struct regmap *regmap, struct mc_subled *subled, + int num_colors) +{ + int ret; + + for (int i = 0; i < num_colors; i++) { + unsigned int channel, brightness; + + channel = subled[i].channel; + brightness = subled[i].brightness; + + if (brightness == LED_OFF) { + /* Flash OFF */ + ret = regmap_update_bits(regmap, + MAX77705_RGBLED_REG_LEDEN, + MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel), 0); + } else { + /* Set current */ + ret = regmap_write(regmap, MAX77705_LED_REG_BRIGHTNESS(channel), + brightness); + if (ret < 0) + return ret; + + ret = regmap_update_bits(regmap, + MAX77705_RGBLED_REG_LEDEN, + LED_ON << MAX77705_LED_EN_SHIFT(channel), + MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel)); + } + } + + return ret; +} + +static int max77705_led_brightness_set_single(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct max77705_led *led = container_of(cdev, struct max77705_led, cdev); + + led->subled_info->brightness = brightness; + + return max77705_led_brightness_set(led->regmap, led->subled_info, 1); +} + +static int max77705_led_brightness_set_multi(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev); + struct max77705_led *led = container_of(mcdev, struct max77705_led, mcdev); + + led_mc_calc_color_components(mcdev, brightness); + + return max77705_led_brightness_set(led->regmap, led->mcdev.subled_info, mcdev->num_colors); +} + +static int max77705_parse_subled(struct device *dev, struct fwnode_handle *np, + struct mc_subled *info) +{ + u32 color = LED_COLOR_ID_GREEN; + u32 reg; + int ret; + + ret = fwnode_property_read_u32(np, "reg", ®); + if (ret || !reg || reg >= MAX77705_LED_NUM_LEDS) + return dev_err_probe(dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np); + + info->channel = reg; + + ret = fwnode_property_read_u32(np, "color", &color); + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(dev, ret, + "failed to parse \"color\" of %pOF\n", np); + + info->color_index = color; + + return 0; +} + +static int max77705_add_led(struct device *dev, struct regmap *regmap, struct fwnode_handle *np) +{ + int ret, i = 0; + unsigned int color, reg; + struct max77705_led *led; + struct led_classdev *cdev; + struct mc_subled *info; + struct fwnode_handle *child; + struct led_init_data init_data = {}; + + led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + ret = fwnode_property_read_u32(np, "color", &color); + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(dev, ret, + "failed to parse \"color\" of %pOF\n", np); + + led->regmap = regmap; + init_data.fwnode = np; + + if (color == LED_COLOR_ID_RGB) { + int num_channels = of_get_available_child_count(to_of_node(np)); + + ret = fwnode_property_read_u32(np, "reg", ®); + if (ret || reg >= MAX77705_LED_NUM_LEDS) + ret = -EINVAL; + + info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + cdev = &led->mcdev.led_cdev; + cdev->max_brightness = MAX77705_LED_MAX_BRIGHTNESS; + cdev->brightness_set_blocking = max77705_led_brightness_set_multi; + cdev->blink_set = max77705_rgb_blink; + + fwnode_for_each_available_child_node(np, child) { + ret = max77705_parse_subled(dev, child, &info[i]); + if (ret < 0) + return ret; + + info[i].intensity = 0; + i++; + } + + led->mcdev.subled_info = info; + led->mcdev.num_colors = num_channels; + led->cdev = *cdev; + + ret = devm_led_classdev_multicolor_register_ext(dev, &led->mcdev, &init_data); + if (ret) + return ret; + + ret = max77705_led_brightness_set_multi(&led->cdev, LED_OFF); + if (ret) + return ret; + } else { + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + max77705_parse_subled(dev, np, info); + + led->subled_info = info; + led->cdev.brightness_set_blocking = max77705_led_brightness_set_single; + led->cdev.blink_set = max77705_rgb_blink; + led->cdev.max_brightness = MAX77705_LED_MAX_BRIGHTNESS; + + ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); + if (ret) + return ret; + + ret = max77705_led_brightness_set_single(&led->cdev, LED_OFF); + if (ret) + return ret; + } + + return 0; +} + +static int max77705_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct i2c_client *i2c = to_i2c_client(pdev->dev.parent); + struct regmap *regmap; + int ret; + + regmap = devm_regmap_init_i2c(i2c, &max77705_leds_regmap_config); + if (IS_ERR(regmap)) + return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register LEDs regmap\n"); + + device_for_each_child_node_scoped(dev, child) { + ret = max77705_add_led(dev, regmap, child); + if (ret) + return ret; + } + + return 0; +} + +static const struct of_device_id max77705_led_of_match[] = { + { .compatible = "maxim,max77705-rgb" }, + { } +}; +MODULE_DEVICE_TABLE(of, max77705_led_of_match); + +static struct platform_driver max77705_led_driver = { + .driver = { + .name = "max77705-led", + .of_match_table = max77705_led_of_match, + }, + .probe = max77705_led_probe, +}; +module_platform_driver(max77705_led_driver); + +MODULE_DESCRIPTION("Maxim MAX77705 LED driver"); +MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index bbd1d359bba4..e22f09d13798 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -12,6 +12,7 @@ * Eric Miao <eric.miao@marvell.com> */ +#include <linux/cleanup.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/platform_device.h> @@ -113,7 +114,7 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( { struct mc13xxx_leds *leds = platform_get_drvdata(pdev); struct mc13xxx_leds_platform_data *pdata; - struct device_node *parent, *child; + struct device_node *child; struct device *dev = &pdev->dev; int i = 0, ret = -ENODATA; @@ -121,24 +122,23 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( if (!pdata) return ERR_PTR(-ENOMEM); - parent = of_get_child_by_name(dev_of_node(dev->parent), "leds"); + struct device_node *parent __free(device_node) = + of_get_child_by_name(dev_of_node(dev->parent), "leds"); if (!parent) - goto out_node_put; + return ERR_PTR(-ENODATA); ret = of_property_read_u32_array(parent, "led-control", pdata->led_control, leds->devtype->num_regs); if (ret) - goto out_node_put; + return ERR_PTR(ret); pdata->num_leds = of_get_available_child_count(parent); pdata->led = devm_kcalloc(dev, pdata->num_leds, sizeof(*pdata->led), GFP_KERNEL); - if (!pdata->led) { - ret = -ENOMEM; - goto out_node_put; - } + if (!pdata->led) + return ERR_PTR(-ENOMEM); for_each_available_child_of_node(parent, child) { const char *str; @@ -158,12 +158,10 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( } pdata->num_leds = i; - ret = i > 0 ? 0 : -ENODATA; - -out_node_put: - of_node_put(parent); + if (i <= 0) + return ERR_PTR(-ENODATA); - return ret ? ERR_PTR(ret) : pdata; + return pdata; } #else static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( @@ -303,7 +301,7 @@ static struct platform_driver mc13xxx_led_driver = { .driver = { .name = "mc13xxx-led", }, - .remove_new = mc13xxx_led_remove, + .remove = mc13xxx_led_remove, .id_table = mc13xxx_led_id_table, }; module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c index 718f55096e90..f25f68789281 100644 --- a/drivers/leds/leds-mlxcpld.c +++ b/drivers/leds/leds-mlxcpld.c @@ -32,7 +32,6 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#include <linux/acpi.h> #include <linux/device.h> #include <linux/dmi.h> #include <linux/hwmon.h> diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c index 5595788d98d2..1b70de72376c 100644 --- a/drivers/leds/leds-mlxreg.c +++ b/drivers/leds/leds-mlxreg.c @@ -256,6 +256,7 @@ static int mlxreg_led_probe(struct platform_device *pdev) { struct mlxreg_core_platform_data *led_pdata; struct mlxreg_led_priv_data *priv; + int err; led_pdata = dev_get_platdata(&pdev->dev); if (!led_pdata) { @@ -267,26 +268,21 @@ static int mlxreg_led_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - mutex_init(&priv->access_lock); + err = devm_mutex_init(&pdev->dev, &priv->access_lock); + if (err) + return err; + priv->pdev = pdev; priv->pdata = led_pdata; return mlxreg_led_config(priv); } -static void mlxreg_led_remove(struct platform_device *pdev) -{ - struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); - - mutex_destroy(&priv->access_lock); -} - static struct platform_driver mlxreg_led_driver = { .driver = { .name = "leds-mlxreg", }, .probe = mlxreg_led_probe, - .remove_new = mlxreg_led_remove, }; module_platform_driver(mlxreg_led_driver); diff --git a/drivers/leds/leds-mt6323.c b/drivers/leds/leds-mt6323.c index 40d508510823..dbdc221c3828 100644 --- a/drivers/leds/leds-mt6323.c +++ b/drivers/leds/leds-mt6323.c @@ -527,7 +527,6 @@ static int mt6323_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct device_node *np = dev_of_node(dev); - struct device_node *child; struct mt6397_chip *hw = dev_get_drvdata(dev->parent); struct mt6323_leds *leds; struct mt6323_led *led; @@ -565,28 +564,25 @@ static int mt6323_led_probe(struct platform_device *pdev) return ret; } - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { struct led_init_data init_data = {}; bool is_wled; ret = of_property_read_u32(child, "reg", ®); if (ret) { dev_err(dev, "Failed to read led 'reg' property\n"); - goto put_child_node; + return ret; } if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS || leds->led[reg]) { dev_err(dev, "Invalid led reg %u\n", reg); - ret = -EINVAL; - goto put_child_node; + return -EINVAL; } led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); - if (!led) { - ret = -ENOMEM; - goto put_child_node; - } + if (!led) + return -ENOMEM; is_wled = of_property_read_bool(child, "mediatek,is-wled"); @@ -612,7 +608,7 @@ static int mt6323_led_probe(struct platform_device *pdev) if (ret < 0) { dev_err(leds->dev, "Failed to LED set default from devicetree\n"); - goto put_child_node; + return ret; } init_data.fwnode = of_fwnode_handle(child); @@ -621,15 +617,11 @@ static int mt6323_led_probe(struct platform_device *pdev) &init_data); if (ret) { dev_err(dev, "Failed to register LED: %d\n", ret); - goto put_child_node; + return ret; } } return 0; - -put_child_node: - of_node_put(child); - return ret; } static void mt6323_led_remove(struct platform_device *pdev) @@ -721,7 +713,7 @@ MODULE_DEVICE_TABLE(of, mt6323_led_dt_match); static struct platform_driver mt6323_led_driver = { .probe = mt6323_led_probe, - .remove_new = mt6323_led_remove, + .remove = mt6323_led_remove, .driver = { .name = "mt6323-led", .of_match_table = mt6323_led_dt_match, diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index 77213b79f84d..e95287416ef8 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -423,7 +423,6 @@ static int netxbig_leds_get_of_pdata(struct device *dev, struct device_node *gpio_ext_np; struct platform_device *gpio_ext_pdev; struct device *gpio_ext_dev; - struct device_node *child; struct netxbig_gpio_ext *gpio_ext; struct netxbig_led_timer *timers; struct netxbig_led *leds, *led; @@ -440,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; } @@ -507,7 +507,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev, } led = leds; - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { const char *string; int *mode_val; int num_modes; @@ -515,17 +515,17 @@ static int netxbig_leds_get_of_pdata(struct device *dev, ret = of_property_read_u32(child, "mode-addr", &led->mode_addr); if (ret) - goto err_node_put; + goto put_device; ret = of_property_read_u32(child, "bright-addr", &led->bright_addr); if (ret) - goto err_node_put; + goto put_device; ret = of_property_read_u32(child, "max-brightness", &led->bright_max); if (ret) - goto err_node_put; + goto put_device; mode_val = devm_kcalloc(dev, @@ -533,7 +533,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev, GFP_KERNEL); if (!mode_val) { ret = -ENOMEM; - goto err_node_put; + goto put_device; } for (i = 0; i < NETXBIG_LED_MODE_NUM; i++) @@ -542,12 +542,12 @@ static int netxbig_leds_get_of_pdata(struct device *dev, ret = of_property_count_u32_elems(child, "mode-val"); if (ret < 0 || ret % 2) { ret = -EINVAL; - goto err_node_put; + goto put_device; } num_modes = ret / 2; if (num_modes > NETXBIG_LED_MODE_NUM) { ret = -EINVAL; - goto err_node_put; + goto put_device; } for (i = 0; i < num_modes; i++) { @@ -560,7 +560,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev, "mode-val", 2 * i + 1, &val); if (mode >= NETXBIG_LED_MODE_NUM) { ret = -EINVAL; - goto err_node_put; + goto put_device; } mode_val[mode] = val; } @@ -583,8 +583,6 @@ static int netxbig_leds_get_of_pdata(struct device *dev, return 0; -err_node_put: - of_node_put(child); put_device: put_device(gpio_ext_dev); return ret; diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c index a86b43dd995e..f3161266b8ad 100644 --- a/drivers/leds/leds-nic78bx.c +++ b/drivers/leds/leds-nic78bx.c @@ -3,11 +3,19 @@ * Copyright (C) 2016 National Instruments Corp. */ -#include <linux/acpi.h> +#include <linux/array_size.h> +#include <linux/bits.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/errno.h> +#include <linux/io.h> +#include <linux/ioport.h> #include <linux/leds.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/spinlock.h> +#include <linux/types.h> #define NIC78BX_USER1_LED_MASK 0x3 #define NIC78BX_USER1_GREEN_LED BIT(0) @@ -118,6 +126,15 @@ static struct nic78bx_led nic78bx_leds[] = { } }; +static void lock_led_reg_action(void *data) +{ + struct nic78bx_led_data *led_data = data; + + /* Lock LED register */ + outb(NIC78BX_LOCK_VALUE, + led_data->io_base + NIC78BX_LOCK_REG_OFFSET); +} + static int nic78bx_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -152,6 +169,10 @@ static int nic78bx_probe(struct platform_device *pdev) led_data->io_base = io_rc->start; spin_lock_init(&led_data->lock); + ret = devm_add_action(dev, lock_led_reg_action, led_data); + if (ret) + return ret; + for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) { nic78bx_leds[i].data = led_data; @@ -167,27 +188,17 @@ static int nic78bx_probe(struct platform_device *pdev) return ret; } -static void nic78bx_remove(struct platform_device *pdev) -{ - struct nic78bx_led_data *led_data = platform_get_drvdata(pdev); - - /* Lock LED register */ - outb(NIC78BX_LOCK_VALUE, - led_data->io_base + NIC78BX_LOCK_REG_OFFSET); -} - static const struct acpi_device_id led_device_ids[] = { - {"NIC78B3", 0}, - {"", 0}, + { "NIC78B3" }, + { } }; MODULE_DEVICE_TABLE(acpi, led_device_ids); static struct platform_driver led_driver = { .probe = nic78bx_probe, - .remove_new = nic78bx_remove, .driver = { .name = KBUILD_MODNAME, - .acpi_match_table = ACPI_PTR(led_device_ids), + .acpi_match_table = led_device_ids, }, }; diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index f3010c472bbd..4c6f04a5bd87 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -238,7 +238,6 @@ static int ns2_led_register(struct device *dev, struct fwnode_handle *node, static int ns2_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct fwnode_handle *child; struct ns2_led *leds; int count; int ret; @@ -251,12 +250,10 @@ static int ns2_led_probe(struct platform_device *pdev) if (!leds) return -ENOMEM; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { ret = ns2_led_register(dev, child, leds++); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } } return 0; diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index bf8bb8fc007c..1b47acf54720 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -29,6 +29,9 @@ #define LED_SHIFT(led) (LED_NUM(led) * 2) #define LED_MASK(led) (0x3 << LED_SHIFT(led)) +#define PCA9532_PWM_PERIOD_DIV 152 +#define PCA9532_PWM_DUTY_DIV 256 + #define ldev_to_led(c) container_of(c, struct pca9532_led, ldev) struct pca9532_chip_info { @@ -45,8 +48,12 @@ struct pca9532_data { struct gpio_chip gpio; #endif const struct pca9532_chip_info *chip_info; + +#define PCA9532_PWM_ID_0 0 +#define PCA9532_PWM_ID_1 1 u8 pwm[2]; u8 psc[2]; + bool hw_blink; }; static int pca9532_probe(struct i2c_client *client); @@ -181,39 +188,73 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev, led->state = PCA9532_ON; else { led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */ - err = pca9532_calcpwm(led->client, 0, 0, value); + err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value); if (err) return err; } if (led->state == PCA9532_PWM0) - pca9532_setpwm(led->client, 0); + pca9532_setpwm(led->client, PCA9532_PWM_ID_0); pca9532_setled(led); return err; } +static int pca9532_update_hw_blink(struct pca9532_led *led, + unsigned long delay_on, unsigned long delay_off) +{ + struct pca9532_data *data = i2c_get_clientdata(led->client); + unsigned int psc; + int i; + + /* Look for others LEDs that already use PWM1 */ + for (i = 0; i < data->chip_info->num_leds; i++) { + struct pca9532_led *other = &data->leds[i]; + + if (other == led) + continue; + + if (other->state == PCA9532_PWM1) { + if (other->ldev.blink_delay_on != delay_on || + other->ldev.blink_delay_off != delay_off) { + /* HW can handle only one blink configuration at a time */ + return -EINVAL; + } + } + } + + psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000; + if (psc > U8_MAX) { + /* Blink period too long to be handled by hardware */ + return -EINVAL; + } + + led->state = PCA9532_PWM1; + data->psc[PCA9532_PWM_ID_1] = psc; + data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off); + + return pca9532_setpwm(data->client, PCA9532_PWM_ID_1); +} + static int pca9532_set_blink(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { struct pca9532_led *led = ldev_to_led(led_cdev); struct i2c_client *client = led->client; - int psc; - int err = 0; + struct pca9532_data *data = i2c_get_clientdata(client); + int err; + + if (!data->hw_blink) + return -EINVAL; if (*delay_on == 0 && *delay_off == 0) { /* led subsystem ask us for a blink rate */ - *delay_on = 1000; - *delay_off = 1000; + *delay_on = 500; + *delay_off = 500; } - if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6) - return -EINVAL; - /* Thecus specific: only use PSC/PWM 0 */ - psc = (*delay_on * 152-1)/1000; - err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness); + err = pca9532_update_hw_blink(led, *delay_on, *delay_off); if (err) return err; - if (led->state == PCA9532_PWM0) - pca9532_setpwm(led->client, 0); + pca9532_setled(led); return 0; @@ -229,9 +270,9 @@ static int pca9532_event(struct input_dev *dev, unsigned int type, /* XXX: allow different kind of beeps with psc/pwm modifications */ if (value > 1 && value < 32767) - data->pwm[1] = 127; + data->pwm[PCA9532_PWM_ID_1] = 127; else - data->pwm[1] = 0; + data->pwm[PCA9532_PWM_ID_1] = 0; schedule_work(&data->work); @@ -246,7 +287,7 @@ static void pca9532_input_work(struct work_struct *work) mutex_lock(&data->update_lock); i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1), - data->pwm[1]); + data->pwm[PCA9532_PWM_ID_1]); mutex_unlock(&data->update_lock); } @@ -359,6 +400,7 @@ static int pca9532_configure(struct i2c_client *client, data->psc[i]); } + data->hw_blink = true; for (i = 0; i < data->chip_info->num_leds; i++) { struct pca9532_led *led = &data->leds[i]; struct pca9532_led *pled = &pdata->leds[i]; @@ -393,6 +435,8 @@ static int pca9532_configure(struct i2c_client *client, pca9532_setled(led); break; case PCA9532_TYPE_N2100_BEEP: + /* PWM1 is reserved for beeper so blink will not use hardware */ + data->hw_blink = false; BUG_ON(data->idev); led->state = PCA9532_PWM1; pca9532_setled(led); @@ -461,7 +505,6 @@ static struct pca9532_platform_data * pca9532_of_populate_pdata(struct device *dev, struct device_node *np) { struct pca9532_platform_data *pdata; - struct device_node *child; int devid, maxleds; int i = 0; const char *state; @@ -475,12 +518,12 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np) pdata->gpio_base = -1; - of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[0], + of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[PCA9532_PWM_ID_0], ARRAY_SIZE(pdata->pwm)); - of_property_read_u8_array(np, "nxp,psc", &pdata->psc[0], + of_property_read_u8_array(np, "nxp,psc", &pdata->psc[PCA9532_PWM_ID_0], ARRAY_SIZE(pdata->psc)); - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { if (of_property_read_string(child, "label", &pdata->leds[i].name)) pdata->leds[i].name = child->name; @@ -493,10 +536,8 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np) else if (!strcmp(state, "keep")) pdata->leds[i].state = PCA9532_KEEP; } - if (++i >= maxleds) { - of_node_put(child); + if (++i >= maxleds) break; - } } return pdata; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index 94a9f8a54b35..e9cfde9fe4b1 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -62,6 +62,8 @@ #define PCA955X_GPIO_HIGH LED_OFF #define PCA955X_GPIO_LOW LED_FULL +#define PCA955X_BLINK_DEFAULT_MS 1000 + enum pca955x_type { pca9550, pca9551, @@ -74,6 +76,7 @@ struct pca955x_chipdef { int bits; u8 slv_addr; /* 7-bit slave address mask */ int slv_addr_shift; /* Number of bits to ignore */ + int blink_div; /* PSC divider */ }; static const struct pca955x_chipdef pca955x_chipdefs[] = { @@ -81,26 +84,31 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = { .bits = 2, .slv_addr = /* 110000x */ 0x60, .slv_addr_shift = 1, + .blink_div = 44, }, [pca9551] = { .bits = 8, .slv_addr = /* 1100xxx */ 0x60, .slv_addr_shift = 3, + .blink_div = 38, }, [pca9552] = { .bits = 16, .slv_addr = /* 1100xxx */ 0x60, .slv_addr_shift = 3, + .blink_div = 44, }, [ibm_pca9552] = { .bits = 16, .slv_addr = /* 0110xxx */ 0x30, .slv_addr_shift = 3, + .blink_div = 44, }, [pca9553] = { .bits = 4, .slv_addr = /* 110001x */ 0x62, .slv_addr_shift = 1, + .blink_div = 44, }, }; @@ -109,7 +117,9 @@ struct pca955x { struct pca955x_led *leds; const struct pca955x_chipdef *chipdef; struct i2c_client *client; + unsigned long active_blink; unsigned long active_pins; + unsigned long blink_period; #ifdef CONFIG_LEDS_PCA955X_GPIO struct gpio_chip gpio; #endif @@ -124,17 +134,25 @@ struct pca955x_led { struct fwnode_handle *fwnode; }; +#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev) + struct pca955x_platform_data { struct pca955x_led *leds; int num_leds; }; /* 8 bits per input register */ -static inline int pca95xx_num_input_regs(int bits) +static inline int pca955x_num_input_regs(int bits) { return (bits + 7) / 8; } +/* 4 bits per LED selector register */ +static inline int pca955x_num_led_regs(int bits) +{ + return (bits + 3) / 4; +} + /* * Return an LED selector register value based on an existing one, with * the appropriate 2-bit state value set for the given LED number (0-3). @@ -145,20 +163,25 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) ((state & 0x3) << (led_num << 1)); } +static inline int pca955x_ledstate(u8 ls, int led_num) +{ + return (ls >> (led_num << 1)) & 0x3; +} + /* * Write to frequency prescaler register, used to program the - * period of the PWM output. period = (PSCx + 1) / 38 + * period of the PWM output. period = (PSCx + 1) / coeff + * Where for pca9551 chips coeff = 38 and for all other chips coeff = 44 */ -static int pca955x_write_psc(struct i2c_client *client, int n, u8 val) +static int pca955x_write_psc(struct pca955x *pca955x, int n, u8 val) { - struct pca955x *pca955x = i2c_get_clientdata(client); - u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + (2 * n); + u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n); int ret; - ret = i2c_smbus_write_byte_data(client, cmd, val); + ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val); if (ret < 0) - dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", - __func__, n, val, ret); + dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n, + val, ret); return ret; } @@ -169,16 +192,15 @@ static int pca955x_write_psc(struct i2c_client *client, int n, u8 val) * * Duty cycle is (256 - PWMx) / 256 */ -static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val) +static int pca955x_write_pwm(struct pca955x *pca955x, int n, u8 val) { - struct pca955x *pca955x = i2c_get_clientdata(client); - u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n); + u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n); int ret; - ret = i2c_smbus_write_byte_data(client, cmd, val); + ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val); if (ret < 0) - dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", - __func__, n, val, ret); + dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n, + val, ret); return ret; } @@ -186,16 +208,15 @@ static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val) * Write to LED selector register, which determines the source that * drives the LED output. */ -static int pca955x_write_ls(struct i2c_client *client, int n, u8 val) +static int pca955x_write_ls(struct pca955x *pca955x, int n, u8 val) { - struct pca955x *pca955x = i2c_get_clientdata(client); - u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n; + u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n; int ret; - ret = i2c_smbus_write_byte_data(client, cmd, val); + ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val); if (ret < 0) - dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", - __func__, n, val, ret); + dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n, + val, ret); return ret; } @@ -203,32 +224,43 @@ static int pca955x_write_ls(struct i2c_client *client, int n, u8 val) * Read the LED selector register, which determines the source that * drives the LED output. */ -static int pca955x_read_ls(struct i2c_client *client, int n, u8 *val) +static int pca955x_read_ls(struct pca955x *pca955x, int n, u8 *val) { - struct pca955x *pca955x = i2c_get_clientdata(client); - u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n; + u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n; int ret; - ret = i2c_smbus_read_byte_data(client, cmd); + ret = i2c_smbus_read_byte_data(pca955x->client, cmd); if (ret < 0) { - dev_err(&client->dev, "%s: reg 0x%x, err %d\n", - __func__, n, ret); + dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret); return ret; } *val = (u8)ret; return 0; } -static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val) +static int pca955x_read_pwm(struct pca955x *pca955x, int n, u8 *val) { - struct pca955x *pca955x = i2c_get_clientdata(client); - u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n); + u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n); int ret; - ret = i2c_smbus_read_byte_data(client, cmd); + ret = i2c_smbus_read_byte_data(pca955x->client, cmd); if (ret < 0) { - dev_err(&client->dev, "%s: reg 0x%x, err %d\n", - __func__, n, ret); + dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret); + return ret; + } + *val = (u8)ret; + return 0; +} + +static int pca955x_read_psc(struct pca955x *pca955x, int n, u8 *val) +{ + int ret; + u8 cmd; + + cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n); + ret = i2c_smbus_read_byte_data(pca955x->client, cmd); + if (ret < 0) { + dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret); return ret; } *val = (u8)ret; @@ -237,30 +269,25 @@ static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val) static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev) { - struct pca955x_led *pca955x_led = container_of(led_cdev, - struct pca955x_led, - led_cdev); + struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev); struct pca955x *pca955x = pca955x_led->pca955x; u8 ls, pwm; int ret; - ret = pca955x_read_ls(pca955x->client, pca955x_led->led_num / 4, &ls); + ret = pca955x_read_ls(pca955x, pca955x_led->led_num / 4, &ls); if (ret) return ret; - ls = (ls >> ((pca955x_led->led_num % 4) << 1)) & 0x3; - switch (ls) { + switch (pca955x_ledstate(ls, pca955x_led->led_num % 4)) { case PCA955X_LS_LED_ON: + case PCA955X_LS_BLINK0: ret = LED_FULL; break; case PCA955X_LS_LED_OFF: ret = LED_OFF; break; - case PCA955X_LS_BLINK0: - ret = LED_HALF; - break; case PCA955X_LS_BLINK1: - ret = pca955x_read_pwm(pca955x->client, 1, &pwm); + ret = pca955x_read_pwm(pca955x, 1, &pwm); if (ret) return ret; ret = 255 - pwm; @@ -273,51 +300,150 @@ static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev) static int pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value) { - struct pca955x_led *pca955x_led; - struct pca955x *pca955x; + struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev); + struct pca955x *pca955x = pca955x_led->pca955x; + int reg = pca955x_led->led_num / 4; + int bit = pca955x_led->led_num % 4; u8 ls; - int chip_ls; /* which LSx to use (0-3 potentially) */ - int ls_led; /* which set of bits within LSx to use (0-3) */ int ret; - pca955x_led = container_of(led_cdev, struct pca955x_led, led_cdev); - pca955x = pca955x_led->pca955x; - - chip_ls = pca955x_led->led_num / 4; - ls_led = pca955x_led->led_num % 4; - mutex_lock(&pca955x->lock); - ret = pca955x_read_ls(pca955x->client, chip_ls, &ls); + ret = pca955x_read_ls(pca955x, reg, &ls); if (ret) goto out; - switch (value) { - case LED_FULL: - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); - break; - case LED_OFF: - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF); - break; - case LED_HALF: - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0); - break; - default: - /* - * Use PWM1 for all other values. This has the unwanted - * side effect of making all LEDs on the chip share the - * same brightness level if set to a value other than - * OFF, HALF, or FULL. But, this is probably better than - * just turning off for all other values. - */ - ret = pca955x_write_pwm(pca955x->client, 1, 255 - value); - if (ret) + if (test_bit(pca955x_led->led_num, &pca955x->active_blink)) { + if (value == LED_OFF) { + clear_bit(pca955x_led->led_num, &pca955x->active_blink); + ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF); + } else { + /* No variable brightness for blinking LEDs */ goto out; - ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); - break; + } + } else { + switch (value) { + case LED_FULL: + ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_ON); + break; + case LED_OFF: + ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF); + break; + default: + /* + * Use PWM1 for all other values. This has the unwanted + * side effect of making all LEDs on the chip share the + * same brightness level if set to a value other than + * OFF or FULL. But, this is probably better than just + * turning off for all other values. + */ + ret = pca955x_write_pwm(pca955x, 1, 255 - value); + if (ret) + goto out; + ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK1); + break; + } } - ret = pca955x_write_ls(pca955x->client, chip_ls, ls); + ret = pca955x_write_ls(pca955x, reg, ls); + +out: + mutex_unlock(&pca955x->lock); + + return ret; +} + +static u8 pca955x_period_to_psc(struct pca955x *pca955x, unsigned long period) +{ + /* psc register value = (blink period * coeff) - 1 */ + period *= pca955x->chipdef->blink_div; + period /= MSEC_PER_SEC; + period -= 1; + + return period; +} + +static unsigned long pca955x_psc_to_period(struct pca955x *pca955x, u8 psc) +{ + unsigned long period = psc; + + /* blink period = (psc register value + 1) / coeff */ + period += 1; + period *= MSEC_PER_SEC; + period /= pca955x->chipdef->blink_div; + + return period; +} + +static int pca955x_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev); + struct pca955x *pca955x = pca955x_led->pca955x; + unsigned long period = *delay_on + *delay_off; + int ret = 0; + + mutex_lock(&pca955x->lock); + + if (period) { + if (*delay_on != *delay_off) { + ret = -EINVAL; + goto out; + } + + if (period < pca955x_psc_to_period(pca955x, 0) || + period > pca955x_psc_to_period(pca955x, 0xff)) { + ret = -EINVAL; + goto out; + } + } else { + period = pca955x->active_blink ? pca955x->blink_period : + PCA955X_BLINK_DEFAULT_MS; + } + + if (!pca955x->active_blink || + pca955x->active_blink == BIT(pca955x_led->led_num) || + pca955x->blink_period == period) { + u8 psc = pca955x_period_to_psc(pca955x, period); + + if (!test_and_set_bit(pca955x_led->led_num, + &pca955x->active_blink)) { + u8 ls; + int reg = pca955x_led->led_num / 4; + int bit = pca955x_led->led_num % 4; + + ret = pca955x_read_ls(pca955x, reg, &ls); + if (ret) + goto out; + + ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK0); + ret = pca955x_write_ls(pca955x, reg, ls); + if (ret) + goto out; + + /* + * Force 50% duty cycle to maintain the specified + * blink rate. + */ + ret = pca955x_write_pwm(pca955x, 0, 128); + if (ret) + goto out; + } + + if (pca955x->blink_period != period) { + pca955x->blink_period = period; + ret = pca955x_write_psc(pca955x, 0, psc); + if (ret) + goto out; + } + + period = pca955x_psc_to_period(pca955x, psc); + period /= 2; + *delay_on = period; + *delay_off = period; + } else { + ret = -EBUSY; + } out: mutex_unlock(&pca955x->lock); @@ -455,10 +581,13 @@ static int pca955x_probe(struct i2c_client *client) struct led_classdev *led; struct led_init_data init_data; struct i2c_adapter *adapter; - int i, err; + int i, bit, err, nls, reg; + u8 ls1[4]; + u8 ls2[4]; struct pca955x_platform_data *pdata; + u8 psc0; + bool keep_psc0 = false; bool set_default_label = false; - bool keep_pwm = false; char default_label[8]; chip = i2c_get_match_data(client); @@ -509,10 +638,22 @@ static int pca955x_probe(struct i2c_client *client) mutex_init(&pca955x->lock); pca955x->client = client; pca955x->chipdef = chip; + pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS; init_data.devname_mandatory = false; init_data.devicename = "pca955x"; + nls = pca955x_num_led_regs(chip->bits); + /* Use auto-increment feature to read all the LED selectors at once. */ + err = i2c_smbus_read_i2c_block_data(client, + 0x10 | (pca955x_num_input_regs(chip->bits) + 4), nls, + ls1); + if (err < 0) + return err; + + for (i = 0; i < nls; i++) + ls2[i] = ls1[i]; + for (i = 0; i < chip->bits; i++) { pca955x_led = &pca955x->leds[i]; pca955x_led->led_num = i; @@ -524,18 +665,20 @@ static int pca955x_probe(struct i2c_client *client) case PCA955X_TYPE_GPIO: break; case PCA955X_TYPE_LED: + bit = i % 4; + reg = i / 4; led = &pca955x_led->led_cdev; led->brightness_set_blocking = pca955x_led_set; led->brightness_get = pca955x_led_get; - - if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) { - err = pca955x_led_set(led, LED_OFF); - if (err) - return err; - } else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) { - err = pca955x_led_set(led, LED_FULL); - if (err) - return err; + led->blink_set = pca955x_led_blink; + + if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) + ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF); + else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) + ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON); + else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) { + keep_psc0 = true; + set_bit(i, &pca955x->active_blink); } init_data.fwnode = pdata->leds[i].fwnode; @@ -564,39 +707,31 @@ static int pca955x_probe(struct i2c_client *client) return err; set_bit(i, &pca955x->active_pins); - - /* - * For default-state == "keep", let the core update the - * brightness from the hardware, then check the - * brightness to see if it's using PWM1. If so, PWM1 - * should not be written below. - */ - if (pdata->leds[i].default_state == LEDS_DEFSTATE_KEEP) { - if (led->brightness != LED_FULL && - led->brightness != LED_OFF && - led->brightness != LED_HALF) - keep_pwm = true; - } } } - /* PWM0 is used for half brightness or 50% duty cycle */ - err = pca955x_write_pwm(client, 0, 255 - LED_HALF); - if (err) - return err; + for (i = 0; i < nls; i++) { + if (ls1[i] != ls2[i]) { + err = pca955x_write_ls(pca955x, i, ls2[i]); + if (err) + return err; + } + } - if (!keep_pwm) { - /* PWM1 is used for variable brightness, default to OFF */ - err = pca955x_write_pwm(client, 1, 0); - if (err) - return err; + if (keep_psc0) { + err = pca955x_read_psc(pca955x, 0, &psc0); + } else { + psc0 = pca955x_period_to_psc(pca955x, pca955x->blink_period); + err = pca955x_write_psc(pca955x, 0, psc0); } - /* Set to fast frequency so we do not see flashing */ - err = pca955x_write_psc(client, 0, 0); if (err) return err; - err = pca955x_write_psc(client, 1, 0); + + pca955x->blink_period = pca955x_psc_to_period(pca955x, psc0); + + /* Set PWM1 to fast frequency so we do not see flashing */ + err = pca955x_write_psc(pca955x, 1, 0); if (err) return err; diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index b53905da3592..050e93b04884 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -306,7 +306,6 @@ static int pca963x_register_leds(struct i2c_client *client, struct pca963x_chipdef *chipdef = chip->chipdef; struct pca963x_led *led = chip->leds; struct device *dev = &client->dev; - struct fwnode_handle *child; bool hw_blink; s32 mode2; u32 reg; @@ -338,7 +337,7 @@ static int pca963x_register_leds(struct i2c_client *client, if (ret < 0) return ret; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_init_data init_data = {}; char default_label[32]; @@ -346,8 +345,7 @@ static int pca963x_register_leds(struct i2c_client *client, if (ret || reg >= chipdef->n_leds) { dev_err(dev, "Invalid 'reg' property for node %pfw\n", child); - ret = -EINVAL; - goto err; + return -EINVAL; } led->led_num = reg; @@ -369,16 +367,13 @@ static int pca963x_register_leds(struct i2c_client *client, if (ret) { dev_err(dev, "Failed to register LED for node %pfw\n", child); - goto err; + return ret; } ++led; } return 0; -err: - fwnode_handle_put(child); - return ret; } static int pca963x_suspend(struct device *dev) diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c index 78215dff1499..11c7bb69573e 100644 --- a/drivers/leds/leds-pca995x.c +++ b/drivers/leds/leds-pca995x.c @@ -19,10 +19,6 @@ #define PCA995X_MODE1 0x00 #define PCA995X_MODE2 0x01 #define PCA995X_LEDOUT0 0x02 -#define PCA9955B_PWM0 0x08 -#define PCA9952_PWM0 0x0A -#define PCA9952_IREFALL 0x43 -#define PCA9955B_IREFALL 0x45 /* Auto-increment disabled. Normal mode */ #define PCA995X_MODE1_CFG 0x00 @@ -34,17 +30,38 @@ #define PCA995X_LDRX_MASK 0x3 #define PCA995X_LDRX_BITS 2 -#define PCA995X_MAX_OUTPUTS 16 +#define PCA995X_MAX_OUTPUTS 24 #define PCA995X_OUTPUTS_PER_REG 4 #define PCA995X_IREFALL_FULL_CFG 0xFF #define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2) -#define PCA995X_TYPE_NON_B 0 -#define PCA995X_TYPE_B 1 - #define ldev_to_led(c) container_of(c, struct pca995x_led, ldev) +struct pca995x_chipdef { + unsigned int num_leds; + u8 pwm_base; + u8 irefall; +}; + +static const struct pca995x_chipdef pca9952_chipdef = { + .num_leds = 16, + .pwm_base = 0x0a, + .irefall = 0x43, +}; + +static const struct pca995x_chipdef pca9955b_chipdef = { + .num_leds = 16, + .pwm_base = 0x08, + .irefall = 0x45, +}; + +static const struct pca995x_chipdef pca9956b_chipdef = { + .num_leds = 24, + .pwm_base = 0x0a, + .irefall = 0x40, +}; + struct pca995x_led { unsigned int led_no; struct led_classdev ldev; @@ -54,7 +71,7 @@ struct pca995x_led { struct pca995x_chip { struct regmap *regmap; struct pca995x_led leds[PCA995X_MAX_OUTPUTS]; - int btype; + const struct pca995x_chipdef *chipdef; }; static int pca995x_brightness_set(struct led_classdev *led_cdev, @@ -62,10 +79,11 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev, { struct pca995x_led *led = ldev_to_led(led_cdev); struct pca995x_chip *chip = led->chip; + const struct pca995x_chipdef *chipdef = chip->chipdef; u8 ledout_addr, pwmout_addr; int shift, ret; - pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no; + pwmout_addr = chipdef->pwm_base + led->led_no; ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG); shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG); @@ -102,43 +120,38 @@ static const struct regmap_config pca995x_regmap = { static int pca995x_probe(struct i2c_client *client) { struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 }; - struct fwnode_handle *np, *child; struct device *dev = &client->dev; + const struct pca995x_chipdef *chipdef; struct pca995x_chip *chip; struct pca995x_led *led; - int i, btype, reg, ret; + int i, j, reg, ret; - btype = (unsigned long)device_get_match_data(&client->dev); + chipdef = device_get_match_data(&client->dev); - np = dev_fwnode(dev); - if (!np) + if (!dev_fwnode(dev)) return -ENODEV; chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - chip->btype = btype; + chip->chipdef = chipdef; chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap); if (IS_ERR(chip->regmap)) return PTR_ERR(chip->regmap); i2c_set_clientdata(client, chip); - fwnode_for_each_available_child_node(np, child) { + device_for_each_child_node_scoped(dev, child) { ret = fwnode_property_read_u32(child, "reg", ®); - if (ret) { - fwnode_handle_put(child); + if (ret) return ret; - } - if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) { - fwnode_handle_put(child); + if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) return -EINVAL; - } led = &chip->leds[reg]; - led_fwnodes[reg] = child; + led_fwnodes[reg] = fwnode_handle_get(child); led->chip = chip; led->led_no = reg; led->ldev.brightness_set_blocking = pca995x_brightness_set; @@ -157,7 +170,8 @@ static int pca995x_probe(struct i2c_client *client) &chip->leds[i].ldev, &init_data); if (ret < 0) { - fwnode_handle_put(child); + for (j = i; j < PCA995X_MAX_OUTPUTS; j++) + fwnode_handle_put(led_fwnodes[j]); return dev_err_probe(dev, ret, "Could not register LED %s\n", chip->leds[i].ldev.name); @@ -170,21 +184,21 @@ static int pca995x_probe(struct i2c_client *client) return ret; /* IREF Output current value for all LEDn outputs */ - return regmap_write(chip->regmap, - btype ? PCA9955B_IREFALL : PCA9952_IREFALL, - PCA995X_IREFALL_HALF_CFG); + return regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG); } static const struct i2c_device_id pca995x_id[] = { - { "pca9952", .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B }, - { "pca9955b", .driver_data = (kernel_ulong_t)PCA995X_TYPE_B }, + { "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef }, + { "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef }, + { "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef }, {} }; MODULE_DEVICE_TABLE(i2c, pca995x_id); static const struct of_device_id pca995x_of_match[] = { - { .compatible = "nxp,pca9952", .data = (void *)PCA995X_TYPE_NON_B }, - { .compatible = "nxp,pca9955b", .data = (void *)PCA995X_TYPE_B }, + { .compatible = "nxp,pca9952", .data = &pca9952_chipdef }, + { .compatible = "nxp,pca9955b", . data = &pca9955b_chipdef }, + { .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef }, {}, }; MODULE_DEVICE_TABLE(of, pca995x_of_match); diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c index 4f01acb75727..3a38578ce8e4 100644 --- a/drivers/leds/leds-powernv.c +++ b/drivers/leds/leds-powernv.c @@ -246,29 +246,25 @@ static int powernv_led_classdev(struct platform_device *pdev, const char *cur = NULL; int rc = -1; struct property *p; - struct device_node *np; struct powernv_led_data *powernv_led; struct device *dev = &pdev->dev; - for_each_available_child_of_node(led_node, np) { + for_each_available_child_of_node_scoped(led_node, np) { p = of_find_property(np, "led-types", NULL); while ((cur = of_prop_next_string(p, cur)) != NULL) { powernv_led = devm_kzalloc(dev, sizeof(*powernv_led), GFP_KERNEL); - if (!powernv_led) { - of_node_put(np); + if (!powernv_led) return -ENOMEM; - } powernv_led->common = powernv_led_common; powernv_led->loc_code = (char *)np->name; rc = powernv_led_create(dev, powernv_led, cur); - if (rc) { - of_node_put(np); + if (rc) return rc; - } + } /* while end */ } @@ -278,12 +274,11 @@ static int powernv_led_classdev(struct platform_device *pdev, /* Platform driver probe */ static int powernv_led_probe(struct platform_device *pdev) { - struct device_node *led_node; struct powernv_led_common *powernv_led_common; struct device *dev = &pdev->dev; - int rc; + struct device_node *led_node + __free(device_node) = of_find_node_by_path("/ibm,opal/leds"); - led_node = of_find_node_by_path("/ibm,opal/leds"); if (!led_node) { dev_err(dev, "%s: LED parent device node not found\n", __func__); @@ -292,20 +287,15 @@ static int powernv_led_probe(struct platform_device *pdev) powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common), GFP_KERNEL); - if (!powernv_led_common) { - rc = -ENOMEM; - goto out; - } + if (!powernv_led_common) + return -ENOMEM; mutex_init(&powernv_led_common->lock); powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX); platform_set_drvdata(pdev, powernv_led_common); - rc = powernv_led_classdev(pdev, led_node, powernv_led_common); -out: - of_node_put(led_node); - return rc; + return powernv_led_classdev(pdev, led_node, powernv_led_common); } /* Platform driver remove */ @@ -333,8 +323,8 @@ static const struct of_device_id powernv_led_match[] = { MODULE_DEVICE_TABLE(of, powernv_led_match); static struct platform_driver powernv_led_driver = { - .probe = powernv_led_probe, - .remove_new = powernv_led_remove, + .probe = powernv_led_probe, + .remove = powernv_led_remove, .driver = { .name = "powernv-led-driver", .of_match_table = powernv_led_match, diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 4e3936a39d0e..c73134e7b951 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -17,7 +17,6 @@ #include <linux/err.h> #include <linux/pwm.h> #include <linux/slab.h> -#include "leds.h" struct led_pwm { const char *name; @@ -53,10 +52,30 @@ static int led_pwm_set(struct led_classdev *led_cdev, duty = led_dat->pwmstate.period - duty; led_dat->pwmstate.duty_cycle = duty; - led_dat->pwmstate.enabled = true; + /* + * 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. + */ + led_dat->pwmstate.enabled = !(led_cdev->flags & LED_SUSPENDED); return pwm_apply_might_sleep(led_dat->pwm, &led_dat->pwmstate); } +static int led_pwm_default_brightness_get(struct fwnode_handle *fwnode, + int max_brightness) +{ + unsigned int default_brightness; + int ret; + + ret = fwnode_property_read_u32(fwnode, "default-brightness", + &default_brightness); + if (ret < 0 || default_brightness > max_brightness) + default_brightness = max_brightness; + + return default_brightness; +} + __attribute__((nonnull)) static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, struct led_pwm *led, struct fwnode_handle *fwnode) @@ -98,7 +117,8 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, /* set brightness */ switch (led->default_state) { case LEDS_DEFSTATE_ON: - led_data->cdev.brightness = led->max_brightness; + led_data->cdev.brightness = + led_pwm_default_brightness_get(fwnode, led->max_brightness); break; case LEDS_DEFSTATE_KEEP: { @@ -134,21 +154,18 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) { - struct fwnode_handle *fwnode; struct led_pwm led; int ret; - device_for_each_child_node(dev, fwnode) { + device_for_each_child_node_scoped(dev, fwnode) { memset(&led, 0, sizeof(led)); ret = fwnode_property_read_string(fwnode, "label", &led.name); if (ret && is_of_node(fwnode)) led.name = to_of_node(fwnode)->name; - if (!led.name) { - ret = -EINVAL; - goto err_child_out; - } + if (!led.name) + return -EINVAL; led.active_low = fwnode_property_read_bool(fwnode, "active-low"); @@ -159,14 +176,10 @@ static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv) ret = led_pwm_add(dev, priv, &led, fwnode); if (ret) - goto err_child_out; + return ret; } return 0; - -err_child_out: - fwnode_handle_put(fwnode); - return ret; } static int led_pwm_probe(struct platform_device *pdev) 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-rb532.c b/drivers/leds/leds-rb532.c index e66f73879c8e..782e1c11ee44 100644 --- a/drivers/leds/leds-rb532.c +++ b/drivers/leds/leds-rb532.c @@ -49,7 +49,7 @@ static void rb532_led_remove(struct platform_device *pdev) static struct platform_driver rb532_led_driver = { .probe = rb532_led_probe, - .remove_new = rb532_led_remove, + .remove = rb532_led_remove, .driver = { .name = "rb532-led", }, diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index 848e929c4a61..ade64629431a 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -193,7 +193,7 @@ static struct platform_driver regulator_led_driver = { .of_match_table = regulator_led_of_match, }, .probe = regulator_led_probe, - .remove_new = regulator_led_remove, + .remove = regulator_led_remove, }; module_platform_driver(regulator_led_driver); diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c index f04db793e8d6..0c5169773949 100644 --- a/drivers/leds/leds-sc27xx-bltc.c +++ b/drivers/leds/leds-sc27xx-bltc.c @@ -276,7 +276,7 @@ static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv) static int sc27xx_led_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *np = dev_of_node(dev), *child; + struct device_node *np = dev_of_node(dev); struct sc27xx_led_priv *priv; u32 base, count, reg; int err; @@ -304,17 +304,13 @@ static int sc27xx_led_probe(struct platform_device *pdev) return err; } - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { err = of_property_read_u32(child, "reg", ®); - if (err) { - of_node_put(child); + if (err) return err; - } - if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { - of_node_put(child); + if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) return -EINVAL; - } priv->leds[reg].fwnode = of_fwnode_handle(child); priv->leds[reg].active = true; @@ -348,7 +344,7 @@ static struct platform_driver sc27xx_led_driver = { .of_match_table = sc27xx_led_of_match, }, .probe = sc27xx_led_probe, - .remove_new = sc27xx_led_remove, + .remove = sc27xx_led_remove, }; module_platform_driver(sc27xx_led_driver); diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c index 96296db5f410..d24d0ddf347c 100644 --- a/drivers/leds/leds-spi-byte.c +++ b/drivers/leds/leds-spi-byte.c @@ -29,10 +29,11 @@ */ #include <linux/leds.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> -#include <linux/of.h> -#include <linux/spi/spi.h> #include <linux/mutex.h> +#include <linux/property.h> +#include <linux/spi/spi.h> #include <uapi/linux/uleds.h> struct spi_byte_chipdef { @@ -55,13 +56,6 @@ static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = { .max_value = 0x3F, }; -static const struct of_device_id spi_byte_dt_ids[] = { - { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef }, - {}, -}; - -MODULE_DEVICE_TABLE(of, spi_byte_dt_ids); - static int spi_byte_brightness_set_blocking(struct led_classdev *dev, enum led_brightness brightness) { @@ -80,73 +74,60 @@ static int spi_byte_brightness_set_blocking(struct led_classdev *dev, static int spi_byte_probe(struct spi_device *spi) { - struct device_node *child; + struct fwnode_handle *child __free(fwnode_handle) = NULL; struct device *dev = &spi->dev; struct spi_byte_led *led; struct led_init_data init_data = {}; - const char *state; + enum led_default_state state; int ret; - if (of_get_available_child_count(dev_of_node(dev)) != 1) { + if (device_get_child_node_count(dev) != 1) { dev_err(dev, "Device must have exactly one LED sub-node."); return -EINVAL; } - child = of_get_next_available_child(dev_of_node(dev), NULL); led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; + ret = devm_mutex_init(dev, &led->mutex); + if (ret) + return ret; + led->spi = spi; - mutex_init(&led->mutex); led->cdef = device_get_match_data(dev); led->ldev.brightness = LED_OFF; led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value; led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking; - state = of_get_property(child, "default-state", NULL); - if (state) { - if (!strcmp(state, "on")) { - led->ldev.brightness = led->ldev.max_brightness; - } else if (strcmp(state, "off")) { - /* all other cases except "off" */ - dev_err(dev, "default-state can only be 'on' or 'off'"); - return -EINVAL; - } - } + child = device_get_next_child_node(dev, NULL); + + state = led_init_default_state_get(child); + if (state == LEDS_DEFSTATE_ON) + led->ldev.brightness = led->ldev.max_brightness; spi_byte_brightness_set_blocking(&led->ldev, led->ldev.brightness); - init_data.fwnode = of_fwnode_handle(child); + init_data.fwnode = child; init_data.devicename = "leds-spi-byte"; init_data.default_label = ":"; - ret = devm_led_classdev_register_ext(&spi->dev, &led->ldev, &init_data); - if (ret) { - mutex_destroy(&led->mutex); - return ret; - } - spi_set_drvdata(spi, led); - - return 0; + return devm_led_classdev_register_ext(dev, &led->ldev, &init_data); } -static void spi_byte_remove(struct spi_device *spi) -{ - struct spi_byte_led *led = spi_get_drvdata(spi); - - mutex_destroy(&led->mutex); -} +static const struct of_device_id spi_byte_dt_ids[] = { + { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef }, + {} +}; +MODULE_DEVICE_TABLE(of, spi_byte_dt_ids); static struct spi_driver spi_byte_driver = { .probe = spi_byte_probe, - .remove = spi_byte_remove, .driver = { .name = KBUILD_MODNAME, .of_match_table = spi_byte_dt_ids, }, }; - module_spi_driver(spi_byte_driver); MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>"); diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index fcaa34706b6c..f24ca75c7cb1 100644 --- a/drivers/leds/leds-ss4200.c +++ b/drivers/leds/leds-ss4200.c @@ -356,8 +356,10 @@ static int ich7_lpc_probe(struct pci_dev *dev, nas_gpio_pci_dev = dev; status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base); - if (status) + if (status) { + status = pcibios_err_to_errno(status); goto out; + } g_pm_io_base &= 0x00000ff80; status = pci_read_config_dword(dev, GPIO_CTRL, &gc); @@ -369,8 +371,9 @@ static int ich7_lpc_probe(struct pci_dev *dev, } status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base); - if (0 > status) { + if (status) { dev_info(&dev->dev, "Unable to read GPIOBASE.\n"); + status = pcibios_err_to_errno(status); goto out; } dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base); @@ -448,7 +451,7 @@ static ssize_t blink_show(struct device *dev, int blinking = 0; if (nasgpio_led_get_attr(led, GPO_BLINK)) blinking = 1; - return sprintf(buf, "%u\n", blinking); + return sprintf(buf, "%d\n", blinking); } static ssize_t blink_store(struct device *dev, diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c new file mode 100644 index 000000000000..4e5dd76d714d --- /dev/null +++ b/drivers/leds/leds-st1202.c @@ -0,0 +1,416 @@ +// 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); + + return st1202_channel_set(led->chip, led->led_num, !!value); +} + +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. + * Afterwards, 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; + + ret = devm_mutex_init(&client->dev, &chip->lock); + if (ret < 0) + return ret; + chip->client = client; + + ret = st1202_setup(chip); + if (ret < 0) + return ret; + + ret = st1202_dt_init(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-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c index 62d21c3a3575..2c9bd360ab81 100644 --- a/drivers/leds/leds-sun50i-a100.c +++ b/drivers/leds/leds-sun50i-a100.c @@ -252,18 +252,16 @@ static int sun50i_a100_ledc_parse_format(struct device *dev, struct sun50i_a100_ledc *priv) { const char *format = "grb"; - u32 i; + int i; device_property_read_string(dev, "allwinner,pixel-format", &format); - for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) { - if (!strcmp(format, sun50i_a100_ledc_formats[i])) { - priv->format = i; - return 0; - } - } + i = match_string(sun50i_a100_ledc_formats, ARRAY_SIZE(sun50i_a100_ledc_formats), format); + if (i < 0) + return dev_err_probe(dev, i, "Bad pixel format '%s'\n", format); - return dev_err_probe(dev, -EINVAL, "Bad pixel format '%s'\n", format); + priv->format = i; + return 0; } static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv) @@ -370,7 +368,7 @@ static int sun50i_a100_ledc_suspend(struct device *dev) if (!xfer_active) break; - msleep(1); + usleep_range(1000, 1100); } clk_disable_unprepare(priv->mod_clk); @@ -394,7 +392,6 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) struct sun50i_a100_ledc_led *led; struct device *dev = &pdev->dev; struct sun50i_a100_ledc *priv; - struct fwnode_handle *child; struct resource *mem; u32 max_addr = 0; u32 num_leds = 0; @@ -404,21 +401,17 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) * The maximum LED address must be known in sun50i_a100_ledc_resume() before * class device registration, so parse and validate the subnodes up front. */ - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { u32 addr, color; ret = fwnode_property_read_u32(child, "reg", &addr); - if (ret || addr >= LEDC_MAX_LEDS) { - fwnode_handle_put(child); + if (ret || addr >= LEDC_MAX_LEDS) return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n", LEDC_MAX_LEDS - 1); - } ret = fwnode_property_read_u32(child, "color", &color); - if (ret || color != LED_COLOR_ID_RGB) { - fwnode_handle_put(child); + if (ret || color != LED_COLOR_ID_RGB) return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n"); - } max_addr = max(max_addr, addr); num_leds++; @@ -504,7 +497,7 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) return ret; led = priv->leds; - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_classdev *cdev; /* The node was already validated above. */ @@ -529,7 +522,11 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data); if (ret) { dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr); - goto err_put_child; + while (led-- > priv->leds) + led_classdev_multicolor_unregister(&led->mc_cdev); + sun50i_a100_ledc_suspend(&pdev->dev); + + return ret; } led++; @@ -538,14 +535,6 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev) dev_info(dev, "Registered %u LEDs\n", num_leds); return 0; - -err_put_child: - fwnode_handle_put(child); - while (led-- > priv->leds) - led_classdev_multicolor_unregister(&led->mc_cdev); - sun50i_a100_ledc_suspend(&pdev->dev); - - return ret; } static void sun50i_a100_ledc_remove(struct platform_device *pdev) @@ -569,7 +558,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm, static struct platform_driver sun50i_a100_ledc_driver = { .probe = sun50i_a100_ledc_probe, - .remove_new = sun50i_a100_ledc_remove, + .remove = sun50i_a100_ledc_remove, .shutdown = sun50i_a100_ledc_remove, .driver = { .name = "sun50i-a100-ledc", diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c index a621e5e5c75c..bd24e7f5947a 100644 --- a/drivers/leds/leds-sunfire.c +++ b/drivers/leds/leds-sunfire.c @@ -219,7 +219,7 @@ MODULE_ALIAS("platform:sunfire-fhc-leds"); static struct platform_driver sunfire_clockboard_led_driver = { .probe = sunfire_clockboard_led_probe, - .remove_new = sunfire_led_generic_remove, + .remove = sunfire_led_generic_remove, .driver = { .name = "sunfire-clockboard-leds", }, @@ -227,7 +227,7 @@ static struct platform_driver sunfire_clockboard_led_driver = { static struct platform_driver sunfire_fhc_led_driver = { .probe = sunfire_fhc_led_probe, - .remove_new = sunfire_led_generic_remove, + .remove = sunfire_led_generic_remove, .driver = { .name = "sunfire-fhc-leds", }, diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 4f22f4224946..acbd8169723c 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -658,7 +658,6 @@ static struct tca6507_platform_data * tca6507_led_dt_init(struct device *dev) { struct tca6507_platform_data *pdata; - struct fwnode_handle *child; struct led_info *tca_leds; int count; @@ -671,7 +670,7 @@ tca6507_led_dt_init(struct device *dev) if (!tca_leds) return ERR_PTR(-ENOMEM); - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct led_info led; u32 reg; int ret; @@ -688,10 +687,8 @@ tca6507_led_dt_init(struct device *dev) led.flags |= TCA6507_MAKE_GPIO; ret = fwnode_property_read_u32(child, "reg", ®); - if (ret || reg >= NUM_LEDS) { - fwnode_handle_put(child); + if (ret || reg >= NUM_LEDS) return ERR_PTR(ret ? : -EINVAL); - } tca_leds[reg] = led; } diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c index 945e831ef4ac..6605e08a042a 100644 --- a/drivers/leds/leds-tlc591xx.c +++ b/drivers/leds/leds-tlc591xx.c @@ -146,7 +146,7 @@ MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match); static int tlc591xx_probe(struct i2c_client *client) { - struct device_node *np, *child; + struct device_node *np; struct device *dev = &client->dev; const struct tlc591xx *tlc591xx; struct tlc591xx_priv *priv; @@ -182,22 +182,20 @@ tlc591xx_probe(struct i2c_client *client) if (err < 0) return err; - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { struct tlc591xx_led *led; struct led_init_data init_data = {}; init_data.fwnode = of_fwnode_handle(child); err = of_property_read_u32(child, "reg", ®); - if (err) { - of_node_put(child); + if (err) return err; - } + if (reg < 0 || reg >= tlc591xx->max_leds || - priv->leds[reg].active) { - of_node_put(child); + priv->leds[reg].active) return -EINVAL; - } + led = &priv->leds[reg]; led->active = true; @@ -207,12 +205,10 @@ tlc591xx_probe(struct i2c_client *client) led->ldev.max_brightness = TLC591XX_MAX_BRIGHTNESS; err = devm_led_classdev_register_ext(dev, &led->ldev, &init_data); - if (err < 0) { - of_node_put(child); + if (err < 0) return dev_err_probe(dev, err, "couldn't register LED %s\n", led->ldev.name); - } } return 0; } diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c index b443f8c989fa..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,36 +10,23 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/of.h> -#include "leds.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]; @@ -50,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; @@ -171,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; } @@ -211,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; } @@ -233,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); @@ -301,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; } @@ -352,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, @@ -375,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; } @@ -386,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, @@ -414,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; } @@ -427,44 +365,119 @@ 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) { struct device *dev = &client->dev; - struct device_node *np = dev_of_node(dev), *child; + struct device_node *np = dev_of_node(dev); struct omnia_leds *leds; struct omnia_led *led; 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) @@ -473,36 +486,29 @@ 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(np, child) { + for_each_available_child_of_node_scoped(np, child) { ret = omnia_led_register(client, led, child); - if (ret < 0) { - of_node_put(child); + if (ret < 0) return ret; - } led += ret; } @@ -512,29 +518,21 @@ 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[] = { { .compatible = "cznic,turris-omnia-leds", }, {}, }; +MODULE_DEVICE_TABLE(of, of_omnia_leds_match); static const struct i2c_device_id omnia_id[] = { - { "omnia", 0 }, + { "omnia" }, { } }; MODULE_DEVICE_TABLE(i2c, omnia_id); 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-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index 70b32d80f960..05930e9e8887 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -292,7 +292,7 @@ static struct platform_driver wm831x_status_driver = { .name = "wm831x-status", }, .probe = wm831x_status_probe, - .remove_new = wm831x_status_remove, + .remove = wm831x_status_remove, }; module_platform_driver(wm831x_status_driver); diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index 61cbefa05710..87e60ea927b9 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -255,7 +255,7 @@ static struct platform_driver wm8350_led_driver = { .name = "wm8350-led", }, .probe = wm8350_led_probe, - .remove_new = wm8350_led_remove, + .remove = wm8350_led_remove, .shutdown = wm8350_led_shutdown, }; diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index 1138e2ab82e5..bee46651e068 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -22,14 +22,13 @@ 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; extern struct list_head leds_list; -extern const char * const led_colors[LED_COLOR_ID_MAX]; #endif /* __LEDS_H_INCLUDED */ diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig index 8fc12d6a2958..222d943d826a 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -17,7 +17,6 @@ config LEDS_GROUP_MULTICOLOR config LEDS_KTD202X tristate "LED support for KTD202x Chips" depends on I2C - depends on OF select REGMAP_I2C help This option enables support for the Kinetic KTD2026/KTD2027 diff --git a/drivers/leds/rgb/leds-group-multicolor.c b/drivers/leds/rgb/leds-group-multicolor.c index b6c7679015fd..548c7dd63ba1 100644 --- a/drivers/leds/rgb/leds-group-multicolor.c +++ b/drivers/leds/rgb/leds-group-multicolor.c @@ -55,7 +55,7 @@ static void restore_sysfs_write_access(void *data) { struct led_classdev *led_cdev = data; - /* Restore the write acccess to the LED */ + /* Restore the write access to the LED */ mutex_lock(&led_cdev->led_access); led_sysfs_enable(led_cdev); mutex_unlock(&led_cdev->led_access); diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c index 514965795a10..04e62faa3a00 100644 --- a/drivers/leds/rgb/leds-ktd202x.c +++ b/drivers/leds/rgb/leds-ktd202x.c @@ -99,7 +99,7 @@ struct ktd202x { struct device *dev; struct regmap *regmap; bool enabled; - int num_leds; + unsigned long num_leds; struct ktd202x_led leds[] __counted_by(num_leds); }; @@ -381,16 +381,19 @@ static int ktd202x_blink_mc_set(struct led_classdev *cdev, mc->num_colors); } -static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np, +static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct fwnode_handle *fwnode, struct ktd202x_led *led, struct led_init_data *init_data) { + struct fwnode_handle *child; struct led_classdev *cdev; - struct device_node *child; struct mc_subled *info; int num_channels; int i = 0; - num_channels = of_get_available_child_count(np); + num_channels = 0; + fwnode_for_each_available_child_node(fwnode, child) + num_channels++; + if (!num_channels || num_channels > chip->num_leds) return -EINVAL; @@ -398,22 +401,22 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np, if (!info) return -ENOMEM; - for_each_available_child_of_node(np, child) { + fwnode_for_each_available_child_node(fwnode, child) { u32 mono_color; u32 reg; int ret; - ret = of_property_read_u32(child, "reg", ®); + ret = fwnode_property_read_u32(child, "reg", ®); if (ret != 0 || reg >= chip->num_leds) { - dev_err(chip->dev, "invalid 'reg' of %pOFn\n", child); - of_node_put(child); - return -EINVAL; + dev_err(chip->dev, "invalid 'reg' of %pfw\n", child); + fwnode_handle_put(child); + return ret; } - ret = of_property_read_u32(child, "color", &mono_color); + ret = fwnode_property_read_u32(child, "color", &mono_color); if (ret < 0 && ret != -EINVAL) { - dev_err(chip->dev, "failed to parse 'color' of %pOF\n", child); - of_node_put(child); + dev_err(chip->dev, "failed to parse 'color' of %pfw\n", child); + fwnode_handle_put(child); return ret; } @@ -433,16 +436,16 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np, return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data); } -static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np, +static int ktd202x_setup_led_single(struct ktd202x *chip, struct fwnode_handle *fwnode, struct ktd202x_led *led, struct led_init_data *init_data) { struct led_classdev *cdev; u32 reg; int ret; - ret = of_property_read_u32(np, "reg", ®); + ret = fwnode_property_read_u32(fwnode, "reg", ®); if (ret != 0 || reg >= chip->num_leds) { - dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np); + dev_err(chip->dev, "invalid 'reg' of %pfw\n", fwnode); return -EINVAL; } led->index = reg; @@ -454,7 +457,7 @@ static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data); } -static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigned int index) +static int ktd202x_add_led(struct ktd202x *chip, struct fwnode_handle *fwnode, unsigned int index) { struct ktd202x_led *led = &chip->leds[index]; struct led_init_data init_data = {}; @@ -463,21 +466,21 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne int ret; /* Color property is optional in single color case */ - ret = of_property_read_u32(np, "color", &color); + ret = fwnode_property_read_u32(fwnode, "color", &color); if (ret < 0 && ret != -EINVAL) { - dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np); + dev_err(chip->dev, "failed to parse 'color' of %pfw\n", fwnode); return ret; } led->chip = chip; - init_data.fwnode = of_fwnode_handle(np); + init_data.fwnode = fwnode; if (color == LED_COLOR_ID_RGB) { cdev = &led->mcdev.led_cdev; - ret = ktd202x_setup_led_rgb(chip, np, led, &init_data); + ret = ktd202x_setup_led_rgb(chip, fwnode, led, &init_data); } else { cdev = &led->cdev; - ret = ktd202x_setup_led_single(chip, np, led, &init_data); + ret = ktd202x_setup_led_single(chip, fwnode, led, &init_data); } if (ret) { @@ -490,15 +493,13 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne return 0; } -static int ktd202x_probe_dt(struct ktd202x *chip) +static int ktd202x_probe_fw(struct ktd202x *chip) { - struct device_node *np = dev_of_node(chip->dev), *child; + struct device *dev = chip->dev; int count; int i = 0; - chip->num_leds = (int)(unsigned long)of_device_get_match_data(chip->dev); - - count = of_get_available_child_count(np); + count = device_get_child_node_count(dev); if (!count || count > chip->num_leds) return -EINVAL; @@ -507,13 +508,12 @@ static int ktd202x_probe_dt(struct ktd202x *chip) /* Allow the device to execute the complete reset */ usleep_range(200, 300); - for_each_available_child_of_node(np, child) { + device_for_each_child_node_scoped(dev, child) { int ret = ktd202x_add_led(chip, child, i); - if (ret) { - of_node_put(child); + if (ret) return ret; - } + i++; } @@ -554,6 +554,12 @@ static int ktd202x_probe(struct i2c_client *client) return ret; } + ret = devm_mutex_init(dev, &chip->mutex); + if (ret) + return ret; + + chip->num_leds = (unsigned long)i2c_get_match_data(client); + chip->regulators[0].supply = "vin"; chip->regulators[1].supply = "vio"; ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators); @@ -568,7 +574,7 @@ static int ktd202x_probe(struct i2c_client *client) return ret; } - ret = ktd202x_probe_dt(chip); + ret = ktd202x_probe_fw(chip); if (ret < 0) { regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); return ret; @@ -580,8 +586,6 @@ static int ktd202x_probe(struct i2c_client *client) return ret; } - mutex_init(&chip->mutex); - return 0; } @@ -590,8 +594,6 @@ static void ktd202x_remove(struct i2c_client *client) struct ktd202x *chip = i2c_get_clientdata(client); ktd202x_chip_disable(chip); - - mutex_destroy(&chip->mutex); } static void ktd202x_shutdown(struct i2c_client *client) @@ -602,10 +604,17 @@ static void ktd202x_shutdown(struct i2c_client *client) regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET); } +static const struct i2c_device_id ktd202x_id[] = { + {"ktd2026", KTD2026_NUM_LEDS}, + {"ktd2027", KTD2027_NUM_LEDS}, + {} +}; +MODULE_DEVICE_TABLE(i2c, ktd202x_id); + static const struct of_device_id ktd202x_match_table[] = { { .compatible = "kinetic,ktd2026", .data = (void *)KTD2026_NUM_LEDS }, { .compatible = "kinetic,ktd2027", .data = (void *)KTD2027_NUM_LEDS }, - {}, + {} }; MODULE_DEVICE_TABLE(of, ktd202x_match_table); @@ -617,6 +626,7 @@ static struct i2c_driver ktd202x_driver = { .probe = ktd202x_probe, .remove = ktd202x_remove, .shutdown = ktd202x_shutdown, + .id_table = ktd202x_id, }; module_i2c_driver(ktd202x_driver); diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c b/drivers/leds/rgb/leds-mt6370-rgb.c index 448d0da11848..ebd3ba878dd5 100644 --- a/drivers/leds/rgb/leds-mt6370-rgb.c +++ b/drivers/leds/rgb/leds-mt6370-rgb.c @@ -21,7 +21,7 @@ #include <linux/regmap.h> #include <linux/util_macros.h> -#include <asm/unaligned.h> +#include <linux/unaligned.h> enum { MT6370_LED_ISNK1 = 0, @@ -149,7 +149,6 @@ struct mt6370_priv { struct regmap_field *fields[F_MAX_FIELDS]; const struct reg_field *reg_fields; const struct linear_range *ranges; - struct reg_cfg *reg_cfgs; const struct mt6370_pdata *pdata; unsigned int leds_count; unsigned int leds_active; @@ -588,7 +587,7 @@ static inline int mt6370_mc_pattern_clear(struct led_classdev *lcdev) struct mt6370_led *led = container_of(mccdev, struct mt6370_led, mc); struct mt6370_priv *priv = led->priv; struct mc_subled *subled; - int i, ret; + int i, ret = 0; mutex_lock(&led->priv->lock); @@ -906,7 +905,6 @@ static int mt6370_leds_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct mt6370_priv *priv; - struct fwnode_handle *child; size_t count; unsigned int i = 0; int ret; @@ -937,37 +935,27 @@ static int mt6370_leds_probe(struct platform_device *pdev) if (ret) return dev_err_probe(dev, ret, "Failed to allocate regmap field\n"); - device_for_each_child_node(dev, child) { + device_for_each_child_node_scoped(dev, child) { struct mt6370_led *led = priv->leds + i++; struct led_init_data init_data = { .fwnode = child }; u32 reg, color; ret = fwnode_property_read_u32(child, "reg", ®); - if (ret) { - dev_err(dev, "Failed to parse reg property\n"); - goto fwnode_release; - } + if (ret) + dev_err_probe(dev, ret, "Failed to parse reg property\n"); - if (reg >= MT6370_MAX_LEDS) { - ret = -EINVAL; - dev_err(dev, "Error reg property number\n"); - goto fwnode_release; - } + if (reg >= MT6370_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, "Error reg property number\n"); ret = fwnode_property_read_u32(child, "color", &color); - if (ret) { - dev_err(dev, "Failed to parse color property\n"); - goto fwnode_release; - } + if (ret) + return dev_err_probe(dev, ret, "Failed to parse color property\n"); if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) reg = MT6370_VIRTUAL_MULTICOLOR; - if (priv->leds_active & BIT(reg)) { - ret = -EINVAL; - dev_err(dev, "Duplicate reg property\n"); - goto fwnode_release; - } + if (priv->leds_active & BIT(reg)) + return dev_err_probe(dev, -EINVAL, "Duplicate reg property\n"); priv->leds_active |= BIT(reg); @@ -976,18 +964,14 @@ static int mt6370_leds_probe(struct platform_device *pdev) ret = mt6370_init_led_properties(dev, led, &init_data); if (ret) - goto fwnode_release; + return ret; ret = mt6370_led_register(dev, led, &init_data); if (ret) - goto fwnode_release; + return ret; } return 0; - -fwnode_release: - fwnode_handle_put(child); - return ret; } static const struct of_device_id mt6370_rgbled_device_table[] = { diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c index 2be4ff918516..f18156683375 100644 --- a/drivers/leds/rgb/leds-ncp5623.c +++ b/drivers/leds/rgb/leds-ncp5623.c @@ -183,16 +183,12 @@ static int ncp5623_probe(struct i2c_client *client) fwnode_for_each_available_child_node(mc_node, led_node) { ret = fwnode_property_read_u32(led_node, "color", &color_index); - if (ret) { - fwnode_handle_put(led_node); - goto release_mc_node; - } + if (ret) + goto release_led_node; ret = fwnode_property_read_u32(led_node, "reg", ®); - if (ret) { - fwnode_handle_put(led_node); - goto release_mc_node; - } + if (ret) + goto release_led_node; subled_info[ncp->mc_dev.num_colors].channel = reg; subled_info[ncp->mc_dev.num_colors++].color_index = color_index; @@ -223,6 +219,10 @@ release_mc_node: fwnode_handle_put(mc_node); return ret; + +release_led_node: + fwnode_handle_put(led_node); + goto release_mc_node; } static void ncp5623_remove(struct i2c_client *client) diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index e1a81e0109e8..1c7705bafdfc 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) @@ -135,8 +141,11 @@ static int led_pwm_mc_probe(struct platform_device *pdev) /* init the multicolor's LED class device */ cdev = &priv->mc_cdev.led_cdev; - fwnode_property_read_u32(mcnode, "max-brightness", + ret = fwnode_property_read_u32(mcnode, "max-brightness", &cdev->max_brightness); + if (ret) + goto release_mcnode; + cdev->flags = LED_CORE_SUSPENDRESUME; cdev->brightness_set_blocking = led_pwm_mc_set; diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c index 6bdc5b923f98..4f2a178e3d26 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -2,7 +2,7 @@ /* * Copyright (c) 2017-2022 Linaro Ltd * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved. - * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved. */ #include <linux/bits.h> #include <linux/bitfield.h> @@ -24,6 +24,7 @@ #define LPG_PATTERN_CONFIG_REG 0x40 #define LPG_SIZE_CLK_REG 0x41 #define PWM_CLK_SELECT_MASK GENMASK(1, 0) +#define PWM_SIZE_SELECT_MASK BIT(2) #define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0) #define PWM_SIZE_HI_RES_MASK GENMASK(6, 4) #define LPG_PREDIV_CLK_REG 0x42 @@ -254,6 +255,9 @@ static int lpg_clear_pbs_trigger(struct lpg *lpg, unsigned int lut_mask) u8 val = 0; int rc; + if (!lpg->lpg_chan_sdam) + return 0; + lpg->pbs_en_bitmap &= (~lut_mask); if (!lpg->pbs_en_bitmap) { rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val); @@ -276,6 +280,9 @@ static int lpg_set_pbs_trigger(struct lpg *lpg, unsigned int lut_mask) u8 val = PBS_SW_TRIG_BIT; int rc; + if (!lpg->lpg_chan_sdam) + return 0; + if (!lpg->pbs_en_bitmap) { rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val); if (rc < 0) @@ -406,8 +413,8 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask) static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000}; static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000}; static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6}; -static const unsigned int lpg_pwm_resolution[] = {9}; -static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15}; +static const unsigned int lpg_pwm_resolution[] = {6, 9}; +static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15}; static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) { @@ -430,12 +437,12 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) * period = -------------------------- * refclk * - * Resolution = 2^9 bits for PWM or + * Resolution = 2^{6 or 9} bits for PWM or * 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM * pre_div = {1, 3, 5, 6} and * M = [0..7]. * - * This allows for periods between 27uS and 384s for PWM channels and periods between + * This allows for periods between 3uS and 384s for PWM channels and periods between * 3uS and 24576s for high resolution PWMs. * The PWM framework wants a period of equal or lower length than requested, * reject anything below minimum period. @@ -455,7 +462,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) max_res = LPG_RESOLUTION_9BIT; } - min_period = div64_u64((u64)NSEC_PER_SEC * (1 << pwm_resolution_arr[0]), + min_period = div64_u64((u64)NSEC_PER_SEC * ((1 << pwm_resolution_arr[0]) - 1), clk_rate_arr[clk_len - 1]); if (period <= min_period) return -EINVAL; @@ -476,7 +483,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period) */ for (i = 0; i < pwm_resolution_count; i++) { - resolution = 1 << pwm_resolution_arr[i]; + resolution = (1 << pwm_resolution_arr[i]) - 1; for (clk_sel = 1; clk_sel < clk_len; clk_sel++) { u64 numerator = period * clk_rate_arr[clk_sel]; @@ -523,10 +530,10 @@ static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty) unsigned int clk_rate; if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) { - max = LPG_RESOLUTION_15BIT - 1; + max = BIT(lpg_pwm_resolution_hi_res[chan->pwm_resolution_sel]) - 1; clk_rate = lpg_clk_rates_hi_res[chan->clk_sel]; } else { - max = LPG_RESOLUTION_9BIT - 1; + max = BIT(lpg_pwm_resolution[chan->pwm_resolution_sel]) - 1; clk_rate = lpg_clk_rates[chan->clk_sel]; } @@ -552,7 +559,7 @@ static void lpg_apply_freq(struct lpg_channel *chan) val |= GENMASK(5, 4); break; case LPG_SUBTYPE_PWM: - val |= BIT(2); + val |= FIELD_PREP(PWM_SIZE_SELECT_MASK, chan->pwm_resolution_sel); break; case LPG_SUBTYPE_HI_RES_PWM: val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel); @@ -1270,7 +1277,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)]; } else { refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)]; - resolution = 9; + resolution = lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)]; } if (refclk) { @@ -1285,7 +1292,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, if (ret) return ret; - state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) * + state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * ((1 << resolution) - 1) * pre_div * (1 << m), refclk); state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk); } else { @@ -1362,7 +1369,6 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) { struct led_init_data init_data = {}; struct led_classdev *cdev; - struct device_node *child; struct mc_subled *info; struct lpg_led *led; const char *state; @@ -1393,12 +1399,10 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) if (!info) return -ENOMEM; i = 0; - for_each_available_child_of_node(np, child) { + for_each_available_child_of_node_scoped(np, child) { ret = lpg_parse_channel(lpg, child, &led->channels[i]); - if (ret < 0) { - of_node_put(child); + if (ret < 0) return ret; - } info[i].color_index = led->channels[i]->color; info[i].intensity = 0; @@ -1594,7 +1598,6 @@ static int lpg_init_sdam(struct lpg *lpg) static int lpg_probe(struct platform_device *pdev) { - struct device_node *np; struct lpg *lpg; int ret; int i; @@ -1634,12 +1637,10 @@ static int lpg_probe(struct platform_device *pdev) if (ret < 0) return ret; - for_each_available_child_of_node(pdev->dev.of_node, np) { + for_each_available_child_of_node_scoped(pdev->dev.of_node, np) { ret = lpg_add_led(lpg, np); - if (ret) { - of_node_put(np); + if (ret) return ret; - } } for (i = 0; i < lpg->num_channels; i++) @@ -1693,6 +1694,13 @@ static const struct lpg_data pm8941_lpg_data = { }, }; +static const struct lpg_data pmi8950_pwm_data = { + .num_channels = 1, + .channels = (const struct lpg_channel_data[]) { + { .base = 0xb000 }, + }, +}; + static const struct lpg_data pm8994_lpg_data = { .lut_base = 0xb000, .lut_size = 64, @@ -1819,6 +1827,7 @@ static const struct of_device_id lpg_of_table[] = { { .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data }, { .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data }, { .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data }, + { .compatible = "qcom,pmi8950-pwm", .data = &pmi8950_pwm_data }, { .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data }, { .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data }, { .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data }, diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simatic/Kconfig index e616cc6d6051..e616cc6d6051 100644 --- a/drivers/leds/simple/Kconfig +++ b/drivers/leds/simatic/Kconfig diff --git a/drivers/leds/simple/Makefile b/drivers/leds/simatic/Makefile index 783578f11bb0..783578f11bb0 100644 --- a/drivers/leds/simple/Makefile +++ b/drivers/leds/simatic/Makefile diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c index 4183ee71fcce..c98c370687c2 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c @@ -53,13 +53,14 @@ static void simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = { .probe = simatic_ipc_leds_gpio_apollolake_probe, - .remove_new = simatic_ipc_leds_gpio_apollolake_remove, + .remove = simatic_ipc_leds_gpio_apollolake_remove, .driver = { .name = KBUILD_MODNAME, }, }; module_platform_driver(simatic_ipc_led_gpio_apollolake_driver); +MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" KBUILD_MODNAME); MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:apollolake-pinctrl"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c index 667ba1bc3a30..9bc5f361a06b 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c @@ -56,6 +56,7 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, case SIMATIC_IPC_DEVICE_127E: case SIMATIC_IPC_DEVICE_227G: case SIMATIC_IPC_DEVICE_BX_21A: + case SIMATIC_IPC_DEVICE_BX_59A: break; default: return -ENODEV; @@ -101,6 +102,7 @@ out: } EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe); +MODULE_DESCRIPTION("Siemens SIMATIC IPC core driver for GPIO based LEDs"); MODULE_LICENSE("GPL v2"); MODULE_SOFTDEP("pre: platform:leds-gpio"); MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c index 4a53d4dbf52f..7f7cff275448 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c @@ -43,13 +43,14 @@ static void simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pde static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = { .probe = simatic_ipc_leds_gpio_elkhartlake_probe, - .remove_new = simatic_ipc_leds_gpio_elkhartlake_remove, + .remove = simatic_ipc_leds_gpio_elkhartlake_remove, .driver = { .name = KBUILD_MODNAME, }, }; module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver); +MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" KBUILD_MODNAME); MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl"); diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c new file mode 100644 index 000000000000..bc23d701bcb7 --- /dev/null +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Siemens SIMATIC IPC driver for GPIO based LEDs + * + * Copyright (c) Siemens AG, 2023 + * + * Author: + * Henning Schild <henning.schild@siemens.com> + */ + +#include <linux/gpio/machine.h> +#include <linux/gpio/consumer.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/platform_data/x86/simatic-ipc-base.h> + +#include "simatic-ipc-leds-gpio.h" + +struct simatic_ipc_led_tables { + struct gpiod_lookup_table *led_lookup_table; + struct gpiod_lookup_table *led_lookup_table_extra; +}; + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table_227g = { + .dev_id = "leds-gpio", + .table = { + GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + }, +}; + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra_227g = { + .dev_id = NULL, /* Filled during initialization */ + .table = { + GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH), + {} /* Terminating entry */ + }, +}; + +static struct gpiod_lookup_table simatic_ipc_led_gpio_table_bx_59a = { + .dev_id = "leds-gpio", + .table = { + GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 1, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-5", 3, NULL, 2, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-5", 2, NULL, 3, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-7", 7, NULL, 4, GPIO_ACTIVE_LOW), + GPIO_LOOKUP_IDX("gpio-f7188x-7", 4, NULL, 5, GPIO_ACTIVE_LOW), + {} /* Terminating entry */ + } +}; + +static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev) +{ + const struct simatic_ipc_platform *plat = dev_get_platdata(&pdev->dev); + struct simatic_ipc_led_tables *led_tables; + + led_tables = devm_kzalloc(&pdev->dev, sizeof(*led_tables), GFP_KERNEL); + if (!led_tables) + return -ENOMEM; + + switch (plat->devmode) { + case SIMATIC_IPC_DEVICE_227G: + led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_227g; + led_tables->led_lookup_table_extra = &simatic_ipc_led_gpio_table_extra_227g; + break; + case SIMATIC_IPC_DEVICE_BX_59A: + led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_bx_59a; + break; + default: + return -ENODEV; + } + + platform_set_drvdata(pdev, led_tables); + return simatic_ipc_leds_gpio_probe(pdev, led_tables->led_lookup_table, + led_tables->led_lookup_table_extra); +} + +static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev) +{ + struct simatic_ipc_led_tables *led_tables = platform_get_drvdata(pdev); + + simatic_ipc_leds_gpio_remove(pdev, led_tables->led_lookup_table, + led_tables->led_lookup_table_extra); +} + +static struct platform_driver simatic_ipc_led_gpio_driver = { + .probe = simatic_ipc_leds_gpio_f7188x_probe, + .remove = simatic_ipc_leds_gpio_f7188x_remove, + .driver = { + .name = KBUILD_MODNAME, + }, +}; +module_platform_driver(simatic_ipc_led_gpio_driver); + +MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Nuvoton GPIO"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); +MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x"); +MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio.h b/drivers/leds/simatic/simatic-ipc-leds-gpio.h index 6b2519809cee..6b2519809cee 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio.h +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio.h diff --git a/drivers/leds/simple/simatic-ipc-leds.c b/drivers/leds/simatic/simatic-ipc-leds.c index 2124f6d09930..348679f0d1b7 100644 --- a/drivers/leds/simple/simatic-ipc-leds.c +++ b/drivers/leds/simatic/simatic-ipc-leds.c @@ -128,6 +128,7 @@ static struct platform_driver simatic_ipc_led_driver = { }; module_platform_driver(simatic_ipc_led_driver); +MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs"); MODULE_LICENSE("GPL v2"); MODULE_ALIAS("platform:" KBUILD_MODNAME); MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c deleted file mode 100644 index c7c3a1f986e6..000000000000 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Siemens SIMATIC IPC driver for GPIO based LEDs - * - * Copyright (c) Siemens AG, 2023 - * - * Author: - * Henning Schild <henning.schild@siemens.com> - */ - -#include <linux/gpio/machine.h> -#include <linux/gpio/consumer.h> -#include <linux/leds.h> -#include <linux/module.h> -#include <linux/platform_device.h> -#include <linux/platform_data/x86/simatic-ipc-base.h> - -#include "simatic-ipc-leds-gpio.h" - -static struct gpiod_lookup_table simatic_ipc_led_gpio_table = { - .dev_id = "leds-gpio", - .table = { - GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW), - GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW), - {} /* Terminating entry */ - }, -}; - -static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = { - .dev_id = NULL, /* Filled during initialization */ - .table = { - GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH), - GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH), - {} /* Terminating entry */ - }, -}; - -static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev) -{ - return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table, - &simatic_ipc_led_gpio_table_extra); -} - -static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev) -{ - simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, - &simatic_ipc_led_gpio_table_extra); -} - -static struct platform_driver simatic_ipc_led_gpio_driver = { - .probe = simatic_ipc_leds_gpio_f7188x_probe, - .remove_new = simatic_ipc_leds_gpio_f7188x_remove, - .driver = { - .name = KBUILD_MODNAME, - }, -}; -module_platform_driver(simatic_ipc_led_gpio_driver); - -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("platform:" KBUILD_MODNAME); -MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x"); -MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig index d11d80176fc0..c11282a74b5a 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -136,13 +136,6 @@ config LEDS_TRIGGER_PATTERN which is a series of tuples, of brightness and duration (ms). If unsure, say N -config LEDS_TRIGGER_AUDIO - tristate "Audio Mute LED Trigger" - help - This allows LEDs to be controlled by audio drivers for following - the audio mute and mic-mute changes. - If unsure, say N - config LEDS_TRIGGER_TTY tristate "LED Trigger for TTY devices" depends on TTY @@ -152,4 +145,20 @@ config LEDS_TRIGGER_TTY When build as a module this driver will be called ledtrig-tty. +config LEDS_TRIGGER_INPUT_EVENTS + tristate "LED Input events trigger" + depends on INPUT + help + Turn LEDs on when there is input (/dev/input/event*) activity and turn + them back off again after there has been no activity for 5 seconds. + + This is primarily intended to control LEDs which are a backlight for + capacitive touch-buttons, such as e.g. the menu / home / back buttons + found on the bottom bezel of many older smartphones and tablets. + + This can also be used to turn on the keyboard backlight LED on + input events and turn the keyboard backlight off again when idle. + + When build as a module this driver will be called ledtrig-input-events. + endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile index 25c4db97cdd4..3b3628889f68 100644 --- a/drivers/leds/trigger/Makefile +++ b/drivers/leds/trigger/Makefile @@ -14,5 +14,5 @@ obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o -obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o +obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS) += ledtrig-input-events.o 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-audio.c b/drivers/leds/trigger/ledtrig-audio.c deleted file mode 100644 index 2ecd4b760fc3..000000000000 --- a/drivers/leds/trigger/ledtrig-audio.c +++ /dev/null @@ -1,67 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -// -// Audio Mute LED trigger -// - -#include <linux/kernel.h> -#include <linux/leds.h> -#include <linux/module.h> -#include "../leds.h" - -static enum led_brightness audio_state[NUM_AUDIO_LEDS]; - -static int ledtrig_audio_mute_activate(struct led_classdev *led_cdev) -{ - led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MUTE]); - return 0; -} - -static int ledtrig_audio_micmute_activate(struct led_classdev *led_cdev) -{ - led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MICMUTE]); - return 0; -} - -static struct led_trigger ledtrig_audio[NUM_AUDIO_LEDS] = { - [LED_AUDIO_MUTE] = { - .name = "audio-mute", - .activate = ledtrig_audio_mute_activate, - }, - [LED_AUDIO_MICMUTE] = { - .name = "audio-micmute", - .activate = ledtrig_audio_micmute_activate, - }, -}; - -enum led_brightness ledtrig_audio_get(enum led_audio type) -{ - return audio_state[type]; -} -EXPORT_SYMBOL_GPL(ledtrig_audio_get); - -void ledtrig_audio_set(enum led_audio type, enum led_brightness state) -{ - audio_state[type] = state; - led_trigger_event(&ledtrig_audio[type], state); -} -EXPORT_SYMBOL_GPL(ledtrig_audio_set); - -static int __init ledtrig_audio_init(void) -{ - led_trigger_register(&ledtrig_audio[LED_AUDIO_MUTE]); - led_trigger_register(&ledtrig_audio[LED_AUDIO_MICMUTE]); - return 0; -} -module_init(ledtrig_audio_init); - -static void __exit ledtrig_audio_exit(void) -{ - led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MUTE]); - led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MICMUTE]); -} -module_exit(ledtrig_audio_exit); - -MODULE_DESCRIPTION("LED trigger for audio mute control"); -MODULE_LICENSE("GPL v2"); -MODULE_ALIAS("ledtrig:audio-mute"); -MODULE_ALIAS("ledtrig:audio-micmute"); diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c new file mode 100644 index 000000000000..1c79731562c2 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-input-events.c @@ -0,0 +1,165 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Input Events LED trigger + * + * Copyright (C) 2024 Hans de Goede <hansg@kernel.org> + */ + +#include <linux/input.h> +#include <linux/jiffies.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include "../leds.h" + +static unsigned long led_off_delay_ms = 5000; +module_param(led_off_delay_ms, ulong, 0644); +MODULE_PARM_DESC(led_off_delay_ms, + "Specify delay in ms for turning LEDs off after last input event"); + +static struct input_events_data { + struct delayed_work work; + spinlock_t lock; + /* To avoid repeatedly setting the brightness while there are events */ + bool led_on; + unsigned long led_off_time; +} input_events_data; + +static struct led_trigger *input_events_led_trigger; + +static void led_input_events_work(struct work_struct *work) +{ + struct input_events_data *data = + container_of(work, struct input_events_data, work.work); + + spin_lock_irq(&data->lock); + + /* + * This time_after_eq() check avoids a race where this work starts + * running before a new event pushed led_off_time back. + */ + if (time_after_eq(jiffies, data->led_off_time)) { + led_trigger_event(input_events_led_trigger, LED_OFF); + data->led_on = false; + } + + spin_unlock_irq(&data->lock); +} + +static void input_events_event(struct input_handle *handle, unsigned int type, + unsigned int code, int val) +{ + struct input_events_data *data = &input_events_data; + unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms); + unsigned long flags; + + spin_lock_irqsave(&data->lock, flags); + + if (!data->led_on) { + led_trigger_event(input_events_led_trigger, LED_FULL); + data->led_on = true; + } + data->led_off_time = jiffies + led_off_delay; + + spin_unlock_irqrestore(&data->lock, flags); + + mod_delayed_work(system_wq, &data->work, led_off_delay); +} + +static int input_events_connect(struct input_handler *handler, struct input_dev *dev, + const struct input_device_id *id) +{ + struct input_handle *handle; + int ret; + + handle = kzalloc(sizeof(*handle), GFP_KERNEL); + if (!handle) + return -ENOMEM; + + handle->dev = dev; + handle->handler = handler; + handle->name = KBUILD_MODNAME; + + ret = input_register_handle(handle); + if (ret) + goto err_free_handle; + + ret = input_open_device(handle); + if (ret) + goto err_unregister_handle; + + return 0; + +err_unregister_handle: + input_unregister_handle(handle); +err_free_handle: + kfree(handle); + return ret; +} + +static void input_events_disconnect(struct input_handle *handle) +{ + input_close_device(handle); + input_unregister_handle(handle); + kfree(handle); +} + +static const struct input_device_id input_events_ids[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_KEY) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_REL) }, + }, + { + .flags = INPUT_DEVICE_ID_MATCH_EVBIT, + .evbit = { BIT_MASK(EV_ABS) }, + }, + { } +}; + +static struct input_handler input_events_handler = { + .name = KBUILD_MODNAME, + .event = input_events_event, + .connect = input_events_connect, + .disconnect = input_events_disconnect, + .id_table = input_events_ids, +}; + +static int __init input_events_init(void) +{ + int ret; + + INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work); + spin_lock_init(&input_events_data.lock); + + led_trigger_register_simple("input-events", &input_events_led_trigger); + + ret = input_register_handler(&input_events_handler); + if (ret) { + led_trigger_unregister_simple(input_events_led_trigger); + return ret; + } + + return 0; +} + +static void __exit input_events_exit(void) +{ + input_unregister_handler(&input_events_handler); + cancel_delayed_work_sync(&input_events_data.work); + led_trigger_unregister_simple(input_events_led_trigger); +} + +module_init(input_events_init); +module_exit(input_events_exit); + +MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>"); +MODULE_DESCRIPTION("Input Events LED trigger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("ledtrig:input-events"); diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c index ea00f6c70882..4e048e08c4fd 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -39,6 +39,8 @@ * (has carrier) or not * tx - LED blinks on transmitted data * rx - LED blinks on receive data + * tx_err - LED blinks on transmit error + * rx_err - LED blinks on receive error * * Note: If the user selects a mode that is not supported by hw, default * behavior is to fall back to software control of the LED. However not every @@ -66,6 +68,7 @@ struct led_netdev_data { unsigned int last_activity; unsigned long mode; + unsigned long blink_delay; int link_speed; __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes); u8 duplex; @@ -84,6 +87,10 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) /* Already validated, hw control is possible with the requested mode */ if (trigger_data->hw_control) { led_cdev->hw_control_set(led_cdev, trigger_data->mode); + if (led_cdev->blink_set) { + led_cdev->blink_set(led_cdev, &trigger_data->blink_delay, + &trigger_data->blink_delay); + } return; } @@ -144,7 +151,9 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) * checking stats */ if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) || - test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) + test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) schedule_delayed_work(&trigger_data->work, 0); } } @@ -337,6 +346,8 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: case TRIGGER_NETDEV_RX: + case TRIGGER_NETDEV_TX_ERR: + case TRIGGER_NETDEV_RX_ERR: bit = attr; break; default: @@ -371,6 +382,8 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, case TRIGGER_NETDEV_FULL_DUPLEX: case TRIGGER_NETDEV_TX: case TRIGGER_NETDEV_RX: + case TRIGGER_NETDEV_TX_ERR: + case TRIGGER_NETDEV_RX_ERR: bit = attr; break; default: @@ -429,6 +442,8 @@ DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX); DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX); DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX); DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX); +DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR); +DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR); static ssize_t interval_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -444,10 +459,11 @@ static ssize_t interval_store(struct device *dev, size_t size) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + struct led_classdev *led_cdev = trigger_data->led_cdev; unsigned long value; int ret; - if (trigger_data->hw_control) + if (trigger_data->hw_control && !led_cdev->blink_set) return -EINVAL; ret = kstrtoul(buf, 0, &value); @@ -456,9 +472,13 @@ static ssize_t interval_store(struct device *dev, /* impose some basic bounds on the timer interval */ if (value >= 5 && value <= 10000) { - cancel_delayed_work_sync(&trigger_data->work); + if (trigger_data->hw_control) { + trigger_data->blink_delay = value; + } else { + cancel_delayed_work_sync(&trigger_data->work); - atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); + atomic_set(&trigger_data->interval, msecs_to_jiffies(value)); + } set_baseline_state(trigger_data); /* resets timer */ } @@ -538,6 +558,8 @@ static struct attribute *netdev_trig_attrs[] = { &dev_attr_half_duplex.attr, &dev_attr_rx.attr, &dev_attr_tx.attr, + &dev_attr_rx_err.attr, + &dev_attr_tx_err.attr, &dev_attr_interval.attr, &dev_attr_offloaded.attr, NULL @@ -593,6 +615,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 */ @@ -628,7 +652,9 @@ static void netdev_trig_work(struct work_struct *work) /* If we are not looking for RX/TX then return */ if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) && - !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode)) + !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) && + !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode)) return; dev_stats = dev_get_stats(trigger_data->net_dev, &temp); @@ -636,7 +662,11 @@ static void netdev_trig_work(struct work_struct *work) (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ? dev_stats->tx_packets : 0) + (test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ? - dev_stats->rx_packets : 0); + dev_stats->rx_packets : 0) + + (test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ? + dev_stats->tx_errors : 0) + + (test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode) ? + dev_stats->rx_errors : 0); if (trigger_data->last_activity != new_activity) { led_stop_software_blink(trigger_data->led_cdev); @@ -724,8 +754,6 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->work); - led_set_brightness(led_cdev, LED_OFF); - dev_put(trigger_data->net_dev); kfree(trigger_data); diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index fadd87dbe993..06d052957d37 100644 --- a/drivers/leds/trigger/ledtrig-pattern.c +++ b/drivers/leds/trigger/ledtrig-pattern.c @@ -13,6 +13,7 @@ #include <linux/mutex.h> #include <linux/slab.h> #include <linux/timer.h> +#include <linux/hrtimer.h> #define MAX_PATTERNS 1024 /* @@ -21,6 +22,12 @@ */ #define UPDATE_INTERVAL 50 +enum pattern_type { + PATTERN_TYPE_SW, /* Use standard timer for software pattern */ + PATTERN_TYPE_HR, /* Use hrtimer for software pattern */ + PATTERN_TYPE_HW, /* Hardware pattern */ +}; + struct pattern_trig_data { struct led_classdev *led_cdev; struct led_pattern patterns[MAX_PATTERNS]; @@ -32,8 +39,9 @@ struct pattern_trig_data { int last_repeat; int delta_t; bool is_indefinite; - bool is_hw_pattern; + enum pattern_type type; struct timer_list timer; + struct hrtimer hrtimer; }; static void pattern_trig_update_patterns(struct pattern_trig_data *data) @@ -71,10 +79,35 @@ static int pattern_trig_compute_brightness(struct pattern_trig_data *data) return data->curr->brightness - step_brightness; } -static void pattern_trig_timer_function(struct timer_list *t) +static void pattern_trig_timer_start(struct pattern_trig_data *data) { - struct pattern_trig_data *data = from_timer(data, t, timer); + if (data->type == PATTERN_TYPE_HR) { + hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL); + } else { + data->timer.expires = jiffies; + add_timer(&data->timer); + } +} +static void pattern_trig_timer_cancel(struct pattern_trig_data *data) +{ + if (data->type == PATTERN_TYPE_HR) + hrtimer_cancel(&data->hrtimer); + else + timer_delete_sync(&data->timer); +} + +static void pattern_trig_timer_restart(struct pattern_trig_data *data, + unsigned long interval) +{ + if (data->type == PATTERN_TYPE_HR) + hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval)); + else + mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval)); +} + +static void pattern_trig_timer_common_function(struct pattern_trig_data *data) +{ for (;;) { if (!data->is_indefinite && !data->repeat) break; @@ -83,8 +116,7 @@ static void pattern_trig_timer_function(struct timer_list *t) /* Step change of brightness */ led_set_brightness(data->led_cdev, data->curr->brightness); - mod_timer(&data->timer, - jiffies + msecs_to_jiffies(data->curr->delta_t)); + pattern_trig_timer_restart(data, data->curr->delta_t); if (!data->next->delta_t) { /* Skip the tuple with zero duration */ pattern_trig_update_patterns(data); @@ -106,8 +138,7 @@ static void pattern_trig_timer_function(struct timer_list *t) led_set_brightness(data->led_cdev, pattern_trig_compute_brightness(data)); - mod_timer(&data->timer, - jiffies + msecs_to_jiffies(UPDATE_INTERVAL)); + pattern_trig_timer_restart(data, UPDATE_INTERVAL); /* Accumulate the gradual dimming time */ data->delta_t += UPDATE_INTERVAL; @@ -117,6 +148,25 @@ static void pattern_trig_timer_function(struct timer_list *t) } } +static void pattern_trig_timer_function(struct timer_list *t) +{ + struct pattern_trig_data *data = from_timer(data, t, timer); + + return pattern_trig_timer_common_function(data); +} + +static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t) +{ + struct pattern_trig_data *data = + container_of(t, struct pattern_trig_data, hrtimer); + + pattern_trig_timer_common_function(data); + if (!data->is_indefinite && !data->repeat) + return HRTIMER_NORESTART; + + return HRTIMER_RESTART; +} + static int pattern_trig_start_pattern(struct led_classdev *led_cdev) { struct pattern_trig_data *data = led_cdev->trigger_data; @@ -124,7 +174,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev) if (!data->npatterns) return 0; - if (data->is_hw_pattern) { + if (data->type == PATTERN_TYPE_HW) { return led_cdev->pattern_set(led_cdev, data->patterns, data->npatterns, data->repeat); } @@ -136,8 +186,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev) data->delta_t = 0; data->curr = data->patterns; data->next = data->patterns + 1; - data->timer.expires = jiffies; - add_timer(&data->timer); + pattern_trig_timer_start(data); return 0; } @@ -175,9 +224,9 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, mutex_lock(&data->lock); - del_timer_sync(&data->timer); + pattern_trig_timer_cancel(data); - if (data->is_hw_pattern) + if (data->type == PATTERN_TYPE_HW) led_cdev->pattern_clear(led_cdev); data->last_repeat = data->repeat = res; @@ -196,14 +245,14 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr, static DEVICE_ATTR_RW(repeat); static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data, - char *buf, bool hw_pattern) + char *buf, enum pattern_type type) { ssize_t count = 0; int i; mutex_lock(&data->lock); - if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern)) + if (!data->npatterns || data->type != type) goto out; for (i = 0; i < data->npatterns; i++) { @@ -260,19 +309,19 @@ static int pattern_trig_store_patterns_int(struct pattern_trig_data *data, static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev, const char *buf, const u32 *buf_int, - size_t count, bool hw_pattern) + size_t count, enum pattern_type type) { struct pattern_trig_data *data = led_cdev->trigger_data; int err = 0; mutex_lock(&data->lock); - del_timer_sync(&data->timer); + pattern_trig_timer_cancel(data); - if (data->is_hw_pattern) + if (data->type == PATTERN_TYPE_HW) led_cdev->pattern_clear(led_cdev); - data->is_hw_pattern = hw_pattern; + data->type = type; data->npatterns = 0; if (buf) @@ -297,7 +346,7 @@ static ssize_t pattern_show(struct device *dev, struct device_attribute *attr, struct led_classdev *led_cdev = dev_get_drvdata(dev); struct pattern_trig_data *data = led_cdev->trigger_data; - return pattern_trig_show_patterns(data, buf, false); + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW); } static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, @@ -305,7 +354,8 @@ static ssize_t pattern_store(struct device *dev, struct device_attribute *attr, { struct led_classdev *led_cdev = dev_get_drvdata(dev); - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false); + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_SW); } static DEVICE_ATTR_RW(pattern); @@ -316,7 +366,7 @@ static ssize_t hw_pattern_show(struct device *dev, struct led_classdev *led_cdev = dev_get_drvdata(dev); struct pattern_trig_data *data = led_cdev->trigger_data; - return pattern_trig_show_patterns(data, buf, true); + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW); } static ssize_t hw_pattern_store(struct device *dev, @@ -325,11 +375,33 @@ static ssize_t hw_pattern_store(struct device *dev, { struct led_classdev *led_cdev = dev_get_drvdata(dev); - return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true); + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_HW); } static DEVICE_ATTR_RW(hw_pattern); +static ssize_t hr_pattern_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct pattern_trig_data *data = led_cdev->trigger_data; + + return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR); +} + +static ssize_t hr_pattern_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return pattern_trig_store_patterns(led_cdev, buf, NULL, count, + PATTERN_TYPE_HR); +} + +static DEVICE_ATTR_RW(hr_pattern); + static umode_t pattern_trig_attrs_mode(struct kobject *kobj, struct attribute *attr, int index) { @@ -338,6 +410,8 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj, if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr) return attr->mode; + else if (attr == &dev_attr_hr_pattern.attr) + return attr->mode; else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set) return attr->mode; @@ -347,6 +421,7 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj, static struct attribute *pattern_trig_attrs[] = { &dev_attr_pattern.attr, &dev_attr_hw_pattern.attr, + &dev_attr_hr_pattern.attr, &dev_attr_repeat.attr, NULL }; @@ -376,7 +451,8 @@ static void pattern_init(struct led_classdev *led_cdev) goto out; } - err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false); + err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, + PATTERN_TYPE_SW); if (err < 0) dev_warn(led_cdev->dev, "Pattern initialization failed with error %d\n", err); @@ -400,12 +476,15 @@ static int pattern_trig_activate(struct led_classdev *led_cdev) led_cdev->pattern_clear = NULL; } + data->type = PATTERN_TYPE_SW; data->is_indefinite = true; data->last_repeat = -1; mutex_init(&data->lock); data->led_cdev = led_cdev; led_set_trigger_data(led_cdev, data); timer_setup(&data->timer, pattern_trig_timer_function, 0); + 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) { @@ -431,6 +510,7 @@ static void pattern_trig_deactivate(struct led_classdev *led_cdev) led_cdev->pattern_clear(led_cdev); timer_shutdown_sync(&data->timer); + hrtimer_cancel(&data->hrtimer); led_set_brightness(led_cdev, LED_OFF); kfree(data); diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c index b4688d1d9d2b..1d213c999d40 100644 --- a/drivers/leds/trigger/ledtrig-timer.c +++ b/drivers/leds/trigger/ledtrig-timer.c @@ -110,11 +110,6 @@ static int timer_trig_activate(struct led_classdev *led_cdev) led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER; } - /* - * If "set brightness to 0" is pending in workqueue, we don't - * want that to be reordered after blink_set() - */ - flush_work(&led_cdev->set_brightness_work); led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off); diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index f111fa7635e5..e103c7ed830b 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -66,7 +66,7 @@ static ssize_t transient_activate_store(struct device *dev, /* cancel the running timer */ if (state == 0 && transient_data->activate == 1) { - del_timer(&transient_data->timer); + timer_delete(&transient_data->timer); transient_data->activate = state; led_set_brightness_nosleep(led_cdev, transient_data->restore_state); diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c index 3d361c920030..374a841f18c3 100644 --- a/drivers/leds/uleds.c +++ b/drivers/leds/uleds.c @@ -200,7 +200,6 @@ static const struct file_operations uleds_fops = { .read = uleds_read, .write = uleds_write, .poll = uleds_poll, - .llseek = no_llseek, }; static struct miscdevice uleds_misc = { |