diff options
Diffstat (limited to 'drivers/leds')
140 files changed, 10824 insertions, 3829 deletions
diff --git a/drivers/leds/.kunitconfig b/drivers/leds/.kunitconfig new file mode 100644 index 000000000000..5180f77910a1 --- /dev/null +++ b/drivers/leds/.kunitconfig @@ -0,0 +1,4 @@ +CONFIG_KUNIT=y +CONFIG_NEW_LEDS=y +CONFIG_LEDS_CLASS=y +CONFIG_LEDS_KUNIT_TEST=y diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6046dfeca16f..11e7282dc297 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -6,6 +6,12 @@ config LEDS_GPIO_REGISTER As this function is used by arch code it must not be compiled as a module. +# This library does not depend on NEW_LEDS and must be independent so it can be +# selected from other subsystems (specifically backlight). +config LEDS_EXPRESSWIRE + bool + depends on GPIOLIB + menuconfig NEW_LEDS bool "LED Support" help @@ -49,6 +55,13 @@ config LEDS_BRIGHTNESS_HW_CHANGED See Documentation/ABI/testing/sysfs-class-led for details. +config LEDS_KUNIT_TEST + tristate "KUnit tests for LEDs" + depends on KUNIT && LEDS_CLASS + default KUNIT_ALL_TESTS + help + Say Y here to enable KUnit testing for the LEDs framework. + comment "LED drivers" config LEDS_88PM860X @@ -95,14 +108,18 @@ config LEDS_ARIEL Say Y to if your machine is a Dell Wyse 3020 thin client. config LEDS_AW200XX - tristate "LED support for Awinic AW20036/AW20054/AW20072" + tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108" depends on LEDS_CLASS depends on I2C help - This option enables support for the AW20036/AW20054/AW20072 LED driver. - It is a 3x12/6x9/6x12 matrix LED driver programmed via - an I2C interface, up to 36/54/72 LEDs or 12/18/24 RGBs, - 3 pattern controllers for auto breathing or group dimming control. + This option enables support for the Awinic AW200XX LED controllers. + It is a matrix LED driver programmed via an I2C interface. Devices have + a set of individually controlled LEDs and support 3 pattern controllers + for auto breathing or group dimming control. Supported devices: + - AW20036 (3x12) 36 LEDs + - AW20054 (6x9) 54 LEDs + - AW20072 (6x12) 72 LEDs + - AW20108 (9x12) 108 LEDs To compile this driver as a module, choose M here: the module will be called leds-aw200xx. @@ -110,6 +127,7 @@ config LEDS_AW200XX config LEDS_AW2013 tristate "LED support for Awinic AW2013" depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C help This option enables support for the AW2013 3-channel LED driver. @@ -168,6 +186,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 @@ -187,6 +220,9 @@ 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 side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the @@ -297,6 +333,15 @@ config LEDS_COBALT_RAQ help This option enables support for the Cobalt Raq series LEDs. +config LEDS_SUN50I_A100 + tristate "LED support for Allwinner A100 RGB LED controller" + depends on LEDS_CLASS_MULTICOLOR + depends on ARCH_SUNXI || COMPILE_TEST + help + This option enables support for the RGB LED controller found + in some Allwinner sunxi SoCs, including A100, R329, and D1. + It uses a one-wire interface to control up to 1024 LEDs. + config LEDS_SUNFIRE tristate "LED support for SunFire servers." depends on LEDS_CLASS @@ -380,7 +425,7 @@ config LEDS_LP3952 config LEDS_LP50XX tristate "LED Support for TI LP5036/30/24/18/12/09 LED driver chip" depends on LEDS_CLASS && REGMAP_I2C - depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR + depends on LEDS_CLASS_MULTICOLOR help If you say yes here you get support for the Texas Instruments LP5036, LP5030, LP5024, LP5018, LP5012 and LP5009 LED driver. @@ -389,16 +434,16 @@ 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 || !LEDS_CLASS_MULTICOLOR + depends on LEDS_CLASS_MULTICOLOR depends on OF depends on I2C - select FW_LOADER - select FW_LOADER_USER_HELPER + imply FW_LOADER + imply 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" @@ -431,6 +476,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 @@ -461,6 +516,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 @@ -521,6 +588,26 @@ config LEDS_PCA963X LED driver chip accessed via the I2C bus. Supported devices include PCA9633 and PCA9634 +config LEDS_PCA995X + tristate "LED Support for PCA995x I2C chips" + depends on LEDS_CLASS + depends on I2C + help + This option enables support for LEDs connected to 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 @@ -583,7 +670,7 @@ config LEDS_BD2606MVV help This option enables support for BD2606MVV LED driver chips accessed via the I2C bus. It supports setting brightness, with - the limitiation that there are groups of two channels sharing + the limitation that there are groups of two channels sharing a brightness setting, but not the on/off setting. To compile this driver as a module, choose M here: the module will @@ -628,6 +715,17 @@ config LEDS_ADP5520 To compile this driver as a module, choose M here: the module will be called leds-adp5520. +config LEDS_MAX5970 + tristate "LED Support for Maxim 5970" + depends on LEDS_CLASS + depends on MFD_MAX5970 + help + This option enables support for the Maxim MAX5970 & MAX5978 smart + switch indication LEDs via the I2C bus. + + To compile this driver as a module, choose M here: the module will + be called leds-max5970. + config LEDS_MC13783 tristate "LED Support for MC13XXX PMIC" depends on LEDS_CLASS @@ -640,7 +738,7 @@ config LEDS_NS2 tristate "LED support for Network Space v2 GPIO LEDs" depends on LEDS_CLASS depends on MACH_KIRKWOOD || MACH_ARMADA_370 || COMPILE_TEST - default y + default y if MACH_KIRKWOOD || MACH_ARMADA_370 help This option enables support for the dual-GPIO LEDs found on the following LaCie/Seagate boards: @@ -655,7 +753,7 @@ config LEDS_NETXBIG depends on LEDS_CLASS depends on MACH_KIRKWOOD || COMPILE_TEST depends on OF_GPIO - default y + default MACH_KIRKWOOD help This option enables support for LEDs found on the LaCie 2Big and 5Big Network v2 boards. The LEDs are wired to a CPLD and are @@ -683,6 +781,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 @@ -745,6 +851,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 @@ -755,6 +870,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 @@ -812,7 +935,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. @@ -824,7 +948,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 @@ -854,6 +977,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 @@ -893,7 +1027,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 d71f1226540c..9a0333ec1a86 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -6,6 +6,7 @@ obj-$(CONFIG_LEDS_CLASS) += led-class.o obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o +obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o # LED Platform Drivers (keep this sorted, M-| sort) obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o @@ -26,6 +27,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,12 +53,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 @@ -72,11 +78,15 @@ obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o +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 obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o @@ -84,10 +94,14 @@ 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 +# Kinetic ExpressWire Protocol +obj-$(CONFIG_LEDS_EXPRESSWIRE) += leds-expresswire.o + # LED SPI Drivers obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o @@ -109,5 +123,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/Kconfig b/drivers/leds/blink/Kconfig index 945c84286a4e..bdcb7377cd4e 100644 --- a/drivers/leds/blink/Kconfig +++ b/drivers/leds/blink/Kconfig @@ -1,10 +1,10 @@ config LEDS_BCM63138 tristate "LED Support for Broadcom BCM63138 SoC" depends on LEDS_CLASS - depends on ARCH_BCM4908 || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST + depends on ARCH_BCMBCA || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST depends on HAS_IOMEM depends on OF - default ARCH_BCM4908 + default ARCH_BCMBCA help This option enables support for LED controller that is part of BCM63138 SoC. The same hardware block is known to be also used 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 35c61311e7fd..8923d2df4704 100644 --- a/drivers/leds/blink/leds-lgm-sso.c +++ b/drivers/leds/blink/leds-lgm-sso.c @@ -450,7 +450,7 @@ static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset) return !!(reg_val & BIT(offset)); } -static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) +static int sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) { struct sso_led_priv *priv = gpiochip_get_data(chip); @@ -458,6 +458,8 @@ static void sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value) if (!priv->gpio.freq) regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU, SSO_CON0_SWU); + + return 0; } static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv) @@ -837,7 +839,7 @@ static int intel_sso_led_probe(struct platform_device *pdev) return 0; } -static int intel_sso_led_remove(struct platform_device *pdev) +static void intel_sso_led_remove(struct platform_device *pdev) { struct sso_led_priv *priv; struct sso_led *led, *n; @@ -850,8 +852,6 @@ static int intel_sso_led_remove(struct platform_device *pdev) } regmap_exit(priv->mmap); - - return 0; } static const struct of_device_id of_sso_led_match[] = { diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig index 4ed2efc65434..5e08102a6784 100644 --- a/drivers/leds/flash/Kconfig +++ b/drivers/leds/flash/Kconfig @@ -23,7 +23,8 @@ config LEDS_AS3645A config LEDS_KTD2692 tristate "LED support for Kinetic KTD2692 flash LED controller" depends on OF - depends on GPIOLIB || COMPILE_TEST + depends on GPIOLIB + select LEDS_EXPRESSWIRE help This option enables support for Kinetic KTD2692 LED flash connected through ExpressWire interface. @@ -51,8 +52,8 @@ config LEDS_MAX77693 config LEDS_MT6360 tristate "LED Support for Mediatek MT6360 PMIC" depends on LEDS_CLASS && OF - depends on LEDS_CLASS_FLASH || !LEDS_CLASS_FLASH - depends on LEDS_CLASS_MULTICOLOR || !LEDS_CLASS_MULTICOLOR + depends on LEDS_CLASS_FLASH + depends on LEDS_CLASS_MULTICOLOR depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS depends on MFD_MT6360 help @@ -89,6 +90,8 @@ config LEDS_QCOM_FLASH the total LED current will be split symmetrically on each channel and they will be enabled/disabled at the same time. + This driver can be built as a module, it will be called "leds-qcom-flash". + config LEDS_RT4505 tristate "LED support for RT4505 flashlight controller" depends on I2C && OF @@ -118,4 +121,27 @@ 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". + +config LEDS_TPS6131X + tristate "LED support for TI TPS6131x flash LED driver" + depends on I2C && OF + depends on GPIOLIB + depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS + select REGMAP_I2C + help + This option enables support for Texas Instruments TPS61310/TPS61311 + flash LED driver. + + This driver can be built as a module, it will be called "leds-tps6131x". + endif # LEDS_CLASS_FLASH diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile index 91d60a4b7952..712fb737a428 100644 --- a/drivers/leds/flash/Makefile +++ b/drivers/leds/flash/Makefile @@ -11,3 +11,5 @@ 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 +obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c index f12ecb2c6580..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, @@ -522,7 +518,7 @@ err_flash_register: return ret; } -static int aat1290_led_remove(struct platform_device *pdev) +static void aat1290_led_remove(struct platform_device *pdev) { struct aat1290_led *led = platform_get_drvdata(pdev); @@ -530,8 +526,6 @@ static int aat1290_led_remove(struct platform_device *pdev) led_classdev_flash_unregister(&led->fled_cdev); mutex_destroy(&led->lock); - - return 0; } static const struct of_device_id 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 670f3bf2e906..0f16eefcfe4c 100644 --- a/drivers/leds/flash/leds-ktd2692.c +++ b/drivers/leds/flash/leds-ktd2692.c @@ -6,9 +6,10 @@ * Ingi Kim <ingi2.kim@samsung.com> */ -#include <linux/delay.h> +#include <linux/cleanup.h> #include <linux/err.h> #include <linux/gpio/consumer.h> +#include <linux/leds-expresswire.h> #include <linux/led-class-flash.h> #include <linux/module.h> #include <linux/mutex.h> @@ -37,22 +38,9 @@ #define KTD2692_REG_FLASH_CURRENT_BASE 0x80 #define KTD2692_REG_MODE_BASE 0xA0 -/* Set bit coding time for expresswire interface */ -#define KTD2692_TIME_RESET_US 700 -#define KTD2692_TIME_DATA_START_TIME_US 10 -#define KTD2692_TIME_HIGH_END_OF_DATA_US 350 -#define KTD2692_TIME_LOW_END_OF_DATA_US 10 -#define KTD2692_TIME_SHORT_BITSET_US 4 -#define KTD2692_TIME_LONG_BITSET_US 12 - /* KTD2692 default length of name */ #define KTD2692_NAME_LENGTH 20 -enum ktd2692_bitset { - KTD2692_LOW = 0, - KTD2692_HIGH, -}; - /* Movie / Flash Mode Control */ enum ktd2692_led_mode { KTD2692_MODE_DISABLE = 0, /* default */ @@ -71,7 +59,19 @@ struct ktd2692_led_config_data { enum led_brightness max_brightness; }; +const struct expresswire_timing ktd2692_timing = { + .poweroff_us = 700, + .data_start_us = 10, + .end_of_data_low_us = 10, + .end_of_data_high_us = 350, + .short_bitset_us = 4, + .long_bitset_us = 12 +}; + struct ktd2692_context { + /* Common ExpressWire properties (ctrl GPIO and timing) */ + struct expresswire_common_props props; + /* Related LED Flash class device */ struct led_classdev_flash fled_cdev; @@ -80,7 +80,6 @@ struct ktd2692_context { struct regulator *regulator; struct gpio_desc *aux_gpio; - struct gpio_desc *ctrl_gpio; enum ktd2692_led_mode mode; enum led_brightness torch_brightness; @@ -92,67 +91,6 @@ static struct ktd2692_context *fled_cdev_to_led( return container_of(fled_cdev, struct ktd2692_context, fled_cdev); } -static void ktd2692_expresswire_start(struct ktd2692_context *led) -{ - gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); - udelay(KTD2692_TIME_DATA_START_TIME_US); -} - -static void ktd2692_expresswire_reset(struct ktd2692_context *led) -{ - gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); - udelay(KTD2692_TIME_RESET_US); -} - -static void ktd2692_expresswire_end(struct ktd2692_context *led) -{ - gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); - udelay(KTD2692_TIME_LOW_END_OF_DATA_US); - gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); - udelay(KTD2692_TIME_HIGH_END_OF_DATA_US); -} - -static void ktd2692_expresswire_set_bit(struct ktd2692_context *led, bool bit) -{ - /* - * The Low Bit(0) and High Bit(1) is based on a time detection - * algorithm between time low and time high - * Time_(L_LB) : Low time of the Low Bit(0) - * Time_(H_LB) : High time of the LOW Bit(0) - * Time_(L_HB) : Low time of the High Bit(1) - * Time_(H_HB) : High time of the High Bit(1) - * - * It can be simplified to: - * Low Bit(0) : 2 * Time_(H_LB) < Time_(L_LB) - * High Bit(1) : 2 * Time_(L_HB) < Time_(H_HB) - * HIGH ___ ____ _.. _________ ___ - * |_________| |_.. |____| |__| - * LOW <L_LB> <H_LB> <L_HB> <H_HB> - * [ Low Bit (0) ] [ High Bit(1) ] - */ - if (bit) { - gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); - udelay(KTD2692_TIME_SHORT_BITSET_US); - gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); - udelay(KTD2692_TIME_LONG_BITSET_US); - } else { - gpiod_direction_output(led->ctrl_gpio, KTD2692_LOW); - udelay(KTD2692_TIME_LONG_BITSET_US); - gpiod_direction_output(led->ctrl_gpio, KTD2692_HIGH); - udelay(KTD2692_TIME_SHORT_BITSET_US); - } -} - -static void ktd2692_expresswire_write(struct ktd2692_context *led, u8 value) -{ - int i; - - ktd2692_expresswire_start(led); - for (i = 7; i >= 0; i--) - ktd2692_expresswire_set_bit(led, value & BIT(i)); - ktd2692_expresswire_end(led); -} - static int ktd2692_led_brightness_set(struct led_classdev *led_cdev, enum led_brightness brightness) { @@ -163,14 +101,14 @@ static int ktd2692_led_brightness_set(struct led_classdev *led_cdev, if (brightness == LED_OFF) { led->mode = KTD2692_MODE_DISABLE; - gpiod_direction_output(led->aux_gpio, KTD2692_LOW); + gpiod_direction_output(led->aux_gpio, 0); } else { - ktd2692_expresswire_write(led, brightness | + expresswire_write_u8(&led->props, brightness | KTD2692_REG_MOVIE_CURRENT_BASE); led->mode = KTD2692_MODE_MOVIE; } - ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE); + expresswire_write_u8(&led->props, led->mode | KTD2692_REG_MODE_BASE); mutex_unlock(&led->lock); return 0; @@ -187,17 +125,17 @@ static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev, if (state) { flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step); - ktd2692_expresswire_write(led, flash_tm_reg + expresswire_write_u8(&led->props, flash_tm_reg | KTD2692_REG_FLASH_TIMEOUT_BASE); led->mode = KTD2692_MODE_FLASH; - gpiod_direction_output(led->aux_gpio, KTD2692_HIGH); + gpiod_direction_output(led->aux_gpio, 1); } else { led->mode = KTD2692_MODE_DISABLE; - gpiod_direction_output(led->aux_gpio, KTD2692_LOW); + gpiod_direction_output(led->aux_gpio, 0); } - ktd2692_expresswire_write(led, led->mode | KTD2692_REG_MODE_BASE); + expresswire_write_u8(&led->props, led->mode | KTD2692_REG_MODE_BASE); fled_cdev->led_cdev.brightness = LED_OFF; led->mode = KTD2692_MODE_DISABLE; @@ -247,12 +185,12 @@ static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev, static void ktd2692_setup(struct ktd2692_context *led) { led->mode = KTD2692_MODE_DISABLE; - ktd2692_expresswire_reset(led); - gpiod_direction_output(led->aux_gpio, KTD2692_LOW); + expresswire_power_off(&led->props); + gpiod_direction_output(led->aux_gpio, 0); - ktd2692_expresswire_write(led, (KTD2692_MM_MIN_CURR_THRESHOLD_SCALE - 1) + expresswire_write_u8(&led->props, (KTD2692_MM_MIN_CURR_THRESHOLD_SCALE - 1) | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE); - ktd2692_expresswire_write(led, KTD2692_FLASH_MODE_CURR_PERCENT(45) + expresswire_write_u8(&led->props, KTD2692_FLASH_MODE_CURR_PERCENT(45) | KTD2692_REG_FLASH_CURRENT_BASE); } @@ -271,14 +209,13 @@ 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) return -ENXIO; - led->ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS); - ret = PTR_ERR_OR_ZERO(led->ctrl_gpio); + led->props.ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS); + ret = PTR_ERR_OR_ZERO(led->props.ctrl_gpio); if (ret) return dev_err_probe(dev, ret, "cannot get ctrl-gpios\n"); @@ -302,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; @@ -315,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 = { @@ -356,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) @@ -386,15 +323,13 @@ static int ktd2692_probe(struct platform_device *pdev) return 0; } -static int ktd2692_remove(struct platform_device *pdev) +static void ktd2692_remove(struct platform_device *pdev) { struct ktd2692_context *led = platform_get_drvdata(pdev); led_classdev_flash_unregister(&led->fled_cdev); mutex_destroy(&led->lock); - - return 0; } static const struct of_device_id ktd2692_match[] = { @@ -414,6 +349,7 @@ static struct platform_driver ktd2692_driver = { module_platform_driver(ktd2692_driver); +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 b6c524facf49..abf6b96ade3d 100644 --- a/drivers/leds/flash/leds-lm3601x.c +++ b/drivers/leds/flash/leds-lm3601x.c @@ -70,12 +70,11 @@ enum lm3601x_type { }; /** - * struct lm3601x_led - + * struct lm3601x_led - private lm3601x LED data * @fled_cdev: flash LED class device pointer * @client: Pointer to the I2C client * @regmap: Devices register map * @lock: Lock for reading/writing the device - * @led_name: LED label for the Torch or IR LED * @flash_timeout: the timeout for the flash * @last_flag: last known flags register value * @torch_current_max: maximum current for the torch @@ -123,7 +122,7 @@ static const struct regmap_config lm3601x_regmap = { .max_register = LM3601X_DEV_ID_REG, .reg_defaults = lm3601x_regmap_defs, .num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, .volatile_reg = lm3601x_volatile_reg, }; @@ -191,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; @@ -342,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; @@ -387,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) { @@ -435,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 5c1faeb55a31..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,10 +1010,13 @@ 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; } -static int max77693_led_remove(struct platform_device *pdev) +static void max77693_led_remove(struct platform_device *pdev) { struct max77693_led_device *led = platform_get_drvdata(pdev); struct max77693_sub_led *sub_leds = led->sub_leds; @@ -1032,8 +1032,6 @@ static int max77693_led_remove(struct platform_device *pdev) } mutex_destroy(&led->lock); - - return 0; } static const struct of_device_id max77693_led_dt_match[] = { diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c index 1af6c5898343..462a902f54e0 100644 --- a/drivers/leds/flash/leds-mt6360.c +++ b/drivers/leds/flash/leds-mt6360.c @@ -91,7 +91,7 @@ struct mt6360_priv { unsigned int fled_torch_used; unsigned int leds_active; unsigned int leds_count; - struct mt6360_led leds[]; + struct mt6360_led leds[] __counted_by(leds_count); }; static int mt6360_mc_brightness_set(struct led_classdev *lcdev, @@ -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; @@ -855,12 +867,11 @@ out_flash_release: return ret; } -static int mt6360_led_remove(struct platform_device *pdev) +static void mt6360_led_remove(struct platform_device *pdev) { struct mt6360_priv *priv = platform_get_drvdata(pdev); mt6360_v4l2_flash_release(priv); - return 0; } static const struct of_device_id __maybe_unused mt6360_led_of_id[] = { diff --git a/drivers/leds/flash/leds-mt6370-flash.c b/drivers/leds/flash/leds-mt6370-flash.c index 931067c8a75f..dbdbe92309db 100644 --- a/drivers/leds/flash/leds-mt6370-flash.c +++ b/drivers/leds/flash/leds-mt6370-flash.c @@ -81,7 +81,7 @@ struct mt6370_priv { unsigned int fled_torch_used; unsigned int leds_active; unsigned int leds_count; - struct mt6370_led leds[]; + struct mt6370_led leds[] __counted_by(leds_count); }; static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level) @@ -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 b089ca1a1901..b03a6833e3e3 100644 --- a/drivers/leds/flash/leds-qcom-flash.c +++ b/drivers/leds/flash/leds-qcom-flash.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved. + * Copyright (c) 2022, 2024-2025 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,31 +111,58 @@ enum { REG_IRESOLUTION, REG_CHAN_STROBE, REG_CHAN_EN, + REG_THERM_THRSH1, + REG_THERM_THRSH2, + REG_THERM_THRSH3, + REG_TORCH_CLAMP, REG_MAX_COUNT, }; -static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = { - REG_FIELD(0x08, 0, 7), /* status1 */ - REG_FIELD(0x09, 0, 7), /* status2 */ - REG_FIELD(0x0a, 0, 7), /* status3 */ - REG_FIELD_ID(0x40, 0, 7, 3, 1), /* chan_timer */ - REG_FIELD_ID(0x43, 0, 6, 3, 1), /* itarget */ - REG_FIELD(0x46, 7, 7), /* module_en */ - REG_FIELD(0x47, 0, 5), /* iresolution */ - REG_FIELD_ID(0x49, 0, 2, 3, 1), /* chan_strobe */ - REG_FIELD(0x4c, 0, 2), /* chan_en */ +static const struct reg_field mvflash_3ch_pmi8998_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x08, 0, 5), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xea, 0, 6), +}; + +static const struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x08, 0, 7), + [REG_STATUS2] = REG_FIELD(0x09, 0, 7), + [REG_STATUS3] = REG_FIELD(0x0a, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1), + [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1), + [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2), + [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2), + [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xec, 0, 6), }; -static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { - REG_FIELD(0x06, 0, 7), /* status1 */ - REG_FIELD(0x07, 0, 6), /* status2 */ - REG_FIELD(0x09, 0, 7), /* status3 */ - REG_FIELD_ID(0x3e, 0, 7, 4, 1), /* chan_timer */ - REG_FIELD_ID(0x42, 0, 6, 4, 1), /* itarget */ - REG_FIELD(0x46, 7, 7), /* module_en */ - REG_FIELD(0x49, 0, 3), /* iresolution */ - REG_FIELD_ID(0x4a, 0, 6, 4, 1), /* chan_strobe */ - REG_FIELD(0x4e, 0, 3), /* chan_en */ +static const struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = { + [REG_STATUS1] = REG_FIELD(0x06, 0, 7), + [REG_STATUS2] = REG_FIELD(0x07, 0, 6), + [REG_STATUS3] = REG_FIELD(0x09, 0, 7), + [REG_CHAN_TIMER] = REG_FIELD_ID(0x3e, 0, 7, 4, 1), + [REG_ITARGET] = REG_FIELD_ID(0x42, 0, 6, 4, 1), + [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7), + [REG_IRESOLUTION] = REG_FIELD(0x49, 0, 3), + [REG_CHAN_STROBE] = REG_FIELD_ID(0x4a, 0, 6, 4, 1), + [REG_CHAN_EN] = REG_FIELD(0x4e, 0, 3), + [REG_THERM_THRSH1] = REG_FIELD(0x7a, 0, 2), + [REG_THERM_THRSH2] = REG_FIELD(0x78, 0, 2), + [REG_TORCH_CLAMP] = REG_FIELD(0xed, 0, 6), }; struct qcom_flash_data { @@ -130,9 +170,12 @@ 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; + u8 torch_clamp; }; struct qcom_flash_led { @@ -143,6 +186,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 +216,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; @@ -309,6 +474,14 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev); int rc; + rc = set_flash_strobe(led, SW_STROBE, false); + 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; @@ -425,6 +598,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; @@ -501,6 +678,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; @@ -519,9 +697,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 @@ -541,6 +722,7 @@ static int qcom_flash_register_led_device(struct device *dev, u32 current_ua, timeout_us; u32 channels[4]; int i, rc, count; + u8 torch_clamp; count = fwnode_property_count_u32(node, "led-sources"); if (count <= 0) { @@ -590,6 +772,12 @@ static int qcom_flash_register_led_device(struct device *dev, current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count); led->max_torch_current_ma = current_ua / UA_PER_MA; + torch_clamp = (current_ua / led->chan_count) / TORCH_IRES_UA; + if (torch_clamp != 0) + torch_clamp--; + + flash_data->torch_clamp = max_t(u8, flash_data->torch_clamp, torch_clamp); + if (fwnode_property_present(node, "flash-max-microamp")) { flash->led_cdev.flags |= LED_DEV_CAP_FLASH; @@ -651,7 +839,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; @@ -691,14 +878,35 @@ static int qcom_flash_led_probe(struct platform_device *pdev) return rc; } - if (val == FLASH_SUBTYPE_3CH_PM8150_VAL || val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { + if (val == FLASH_SUBTYPE_3CH_PM8150_VAL) { + flash_data->hw_type = QCOM_MVFLASH_3CH; + flash_data->max_channels = 3; + regs = devm_kmemdup(dev, mvflash_3ch_regs, sizeof(mvflash_3ch_regs), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + } else if (val == FLASH_SUBTYPE_3CH_PMI8998_VAL) { flash_data->hw_type = QCOM_MVFLASH_3CH; flash_data->max_channels = 3; - regs = mvflash_3ch_regs; + regs = devm_kmemdup(dev, mvflash_3ch_pmi8998_regs, + sizeof(mvflash_3ch_pmi8998_regs), GFP_KERNEL); + if (!regs) + return -ENOMEM; } else if (val == FLASH_SUBTYPE_4CH_VAL) { flash_data->hw_type = QCOM_MVFLASH_4CH; flash_data->max_channels = 4; - regs = mvflash_4ch_regs; + regs = devm_kmemdup(dev, mvflash_4ch_regs, sizeof(mvflash_4ch_regs), + GFP_KERNEL); + if (!regs) + return -ENOMEM; + + 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; @@ -712,6 +920,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev) dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc); return rc; } + devm_kfree(dev, regs); /* devm_regmap_field_bulk_alloc() makes copies */ platform_set_drvdata(pdev, flash_data); mutex_init(&flash_data->lock); @@ -727,7 +936,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; @@ -742,15 +951,14 @@ static int qcom_flash_led_probe(struct platform_device *pdev) flash_data->leds_count++; } - return 0; - + return regmap_field_write(flash_data->r_fields[REG_TORCH_CLAMP], flash_data->torch_clamp); release: while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count) v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); return rc; } -static int qcom_flash_led_remove(struct platform_device *pdev) +static void qcom_flash_led_remove(struct platform_device *pdev) { struct qcom_flash_data *flash_data = platform_get_drvdata(pdev); @@ -758,7 +966,6 @@ static int qcom_flash_led_remove(struct platform_device *pdev) v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]); mutex_destroy(&flash_data->lock); - return 0; } static const struct of_device_id qcom_flash_led_match_table[] = { diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c index 1ae5b387f4a5..18fd5b7e528f 100644 --- a/drivers/leds/flash/leds-rt4505.c +++ b/drivers/leds/flash/leds-rt4505.c @@ -365,7 +365,7 @@ static int rt4505_probe(struct i2c_client *client) return ret; } - child = fwnode_get_next_available_child_node(client->dev.fwnode, NULL); + child = device_get_next_child_node(&client->dev, NULL); if (!child) { dev_err(priv->dev, "Failed to get child node\n"); return -EINVAL; @@ -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 44904fdee3cc..f6b439674c03 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); } @@ -165,7 +165,7 @@ static const struct led_flash_ops rt8515_flash_ops = { static void rt8515_powerdown_timer(struct timer_list *t) { - struct rt8515 *rt = from_timer(rt, t, powerdown_timer); + struct rt8515 *rt = timer_container_of(rt, t, powerdown_timer); /* Turn the LED off */ rt8515_gpio_led_off(rt); @@ -304,7 +304,7 @@ static int rt8515_probe(struct platform_device *pdev) return dev_err_probe(dev, PTR_ERR(rt->enable_torch), "cannot get ENT (enable torch) GPIO\n"); - child = fwnode_get_next_available_child_node(dev->fwnode, NULL); + child = device_get_next_child_node(dev, NULL); if (!child) { dev_err(dev, "No fwnode child node found for connected LED.\n"); @@ -367,15 +367,13 @@ static int rt8515_probe(struct platform_device *pdev) return 0; } -static int rt8515_remove(struct platform_device *pdev) +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); - - return 0; } static const struct of_device_id rt8515_match[] = { diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c index d3f50dca5136..dc6840357370 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); @@ -114,8 +114,11 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev, "failed to enable regulator: %d\n", ret); return ret; } + gpiod_set_value_cansleep(priv->flash_gpio, 0); gpiod_set_value_cansleep(priv->enable_gpio, 1); } else { + 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); if (ret) { @@ -132,7 +135,7 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev, static void sgm3140_powerdown_timer(struct timer_list *t) { - struct sgm3140 *priv = from_timer(priv, t, powerdown_timer); + struct sgm3140 *priv = timer_container_of(priv, t, powerdown_timer); gpiod_set_value(priv->enable_gpio, 0); gpiod_set_value(priv->flash_gpio, 0); @@ -211,8 +214,7 @@ static int sgm3140_probe(struct platform_device *pdev) return dev_err_probe(&pdev->dev, ret, "Failed to request regulator\n"); - child_node = fwnode_get_next_available_child_node(pdev->dev.fwnode, - NULL); + child_node = device_get_next_child_node(&pdev->dev, NULL); if (!child_node) { dev_err(&pdev->dev, "No fwnode child node found for connected LED.\n"); @@ -278,15 +280,13 @@ err: return ret; } -static int sgm3140_remove(struct platform_device *pdev) +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); - - return 0; } static const struct of_device_id 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/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c new file mode 100644 index 000000000000..f0f1f2b77d5a --- /dev/null +++ b/drivers/leds/flash/leds-tps6131x.c @@ -0,0 +1,815 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface + * + * Copyright 2025 Matthias Fend <matthias.fend@emfend.at> + */ + +#include <linux/gpio/consumer.h> +#include <linux/i2c.h> +#include <linux/led-class-flash.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/regmap.h> +#include <media/v4l2-flash-led-class.h> + +#define TPS6131X_REG_0 0x00 +#define TPS6131X_REG_0_RESET BIT(7) +#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3) +#define TPS6131X_REG_0_DCLC13_SHIFT 3 +#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0) +#define TPS6131X_REG_0_DCLC2_SHIFT 0 + +#define TPS6131X_REG_1 0x01 +#define TPS6131X_REG_1_MODE GENMASK(7, 6) +#define TPS6131X_REG_1_MODE_SHIFT 6 +#define TPS6131X_REG_1_FC2 GENMASK(5, 0) +#define TPS6131X_REG_1_FC2_SHIFT 0 + +#define TPS6131X_REG_2 0x02 +#define TPS6131X_REG_2_MODE GENMASK(7, 6) +#define TPS6131X_REG_2_MODE_SHIFT 6 +#define TPS6131X_REG_2_ENVM BIT(5) +#define TPS6131X_REG_2_FC13 GENMASK(4, 0) +#define TPS6131X_REG_2_FC13_SHIFT 0 + +#define TPS6131X_REG_3 0x03 +#define TPS6131X_REG_3_STIM GENMASK(7, 5) +#define TPS6131X_REG_3_STIM_SHIFT 5 +#define TPS6131X_REG_3_HPFL BIT(4) +#define TPS6131X_REG_3_SELSTIM_TO BIT(3) +#define TPS6131X_REG_3_STT BIT(2) +#define TPS6131X_REG_3_SFT BIT(1) +#define TPS6131X_REG_3_TXMASK BIT(0) + +#define TPS6131X_REG_4 0x04 +#define TPS6131X_REG_4_PG BIT(7) +#define TPS6131X_REG_4_HOTDIE_HI BIT(6) +#define TPS6131X_REG_4_HOTDIE_LO BIT(5) +#define TPS6131X_REG_4_ILIM BIT(4) +#define TPS6131X_REG_4_INDC GENMASK(3, 0) +#define TPS6131X_REG_4_INDC_SHIFT 0 + +#define TPS6131X_REG_5 0x05 +#define TPS6131X_REG_5_SELFCAL BIT(7) +#define TPS6131X_REG_5_ENPSM BIT(6) +#define TPS6131X_REG_5_STSTRB1_DIR BIT(5) +#define TPS6131X_REG_5_GPIO BIT(4) +#define TPS6131X_REG_5_GPIOTYPE BIT(3) +#define TPS6131X_REG_5_ENLED3 BIT(2) +#define TPS6131X_REG_5_ENLED2 BIT(1) +#define TPS6131X_REG_5_ENLED1 BIT(0) + +#define TPS6131X_REG_6 0x06 +#define TPS6131X_REG_6_ENTS BIT(7) +#define TPS6131X_REG_6_LEDHOT BIT(6) +#define TPS6131X_REG_6_LEDWARN BIT(5) +#define TPS6131X_REG_6_LEDHDR BIT(4) +#define TPS6131X_REG_6_OV GENMASK(3, 0) +#define TPS6131X_REG_6_OV_SHIFT 0 + +#define TPS6131X_REG_7 0x07 +#define TPS6131X_REG_7_ENBATMON BIT(7) +#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4) +#define TPS6131X_REG_7_BATDROOP_SHIFT 4 +#define TPS6131X_REG_7_REVID GENMASK(2, 0) +#define TPS6131X_REG_7_REVID_SHIFT 0 + +#define TPS6131X_MAX_CHANNELS 3 + +#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400 +#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800 +#define TPS6131X_FLASH_STEP_I_MA 25 + +#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175 +#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175 +#define TPS6131X_TORCH_STEP_I_MA 25 + +/* The torch watchdog timer must be refreshed within an interval of 13 seconds. */ +#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000) + +#define UA_TO_MA(UA) ((UA) / 1000) + +enum tps6131x_mode { + TPS6131X_MODE_SHUTDOWN = 0x0, + TPS6131X_MODE_TORCH = 0x1, + TPS6131X_MODE_FLASH = 0x2, +}; + +struct tps6131x { + struct device *dev; + struct regmap *regmap; + struct gpio_desc *reset_gpio; + /* + * Registers 0, 1, 2, and 3 control parts of the controller that are not completely + * independent of each other. Since some operations require the registers to be written in + * a specific order to avoid unwanted side effects, they are synchronized with a lock. + */ + struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */ + struct delayed_work torch_refresh_work; + bool valley_current_limit; + bool chan1_en; + bool chan2_en; + bool chan3_en; + struct fwnode_handle *led_node; + u32 max_flash_current_ma; + u32 step_flash_current_ma; + u32 max_torch_current_ma; + u32 step_torch_current_ma; + u32 max_timeout_us; + struct led_classdev_flash fled_cdev; + struct v4l2_flash *v4l2_flash; +}; + +static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev) +{ + return container_of(fled_cdev, struct tps6131x, fled_cdev); +} + +/* + * Register contents after a power on/reset. These values cannot be changed. + */ + +#define TPS6131X_DCLC2_50MA 2 +#define TPS6131X_DCLC13_25MA 1 +#define TPS6131X_FC2_400MA 16 +#define TPS6131X_FC13_200MA 8 +#define TPS6131X_STIM_0_579MS_1_37MS 6 +#define TPS6131X_SELSTIM_RANGE0 0 +#define TPS6131X_INDC_OFF 0 +#define TPS6131X_OV_4950MV 9 +#define TPS6131X_BATDROOP_150MV 4 + +static const struct reg_default tps6131x_regmap_defaults[] = { + { TPS6131X_REG_0, (TPS6131X_DCLC13_25MA << TPS6131X_REG_0_DCLC13_SHIFT) | + (TPS6131X_DCLC2_50MA << TPS6131X_REG_0_DCLC2_SHIFT) }, + { TPS6131X_REG_1, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_1_MODE_SHIFT) | + (TPS6131X_FC2_400MA << TPS6131X_REG_1_FC2_SHIFT) }, + { TPS6131X_REG_2, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_2_MODE_SHIFT) | + (TPS6131X_FC13_200MA << TPS6131X_REG_2_FC13_SHIFT) }, + { TPS6131X_REG_3, (TPS6131X_STIM_0_579MS_1_37MS << TPS6131X_REG_3_STIM_SHIFT) | + (TPS6131X_SELSTIM_RANGE0 << TPS6131X_REG_3_SELSTIM_TO) | + TPS6131X_REG_3_TXMASK }, + { TPS6131X_REG_4, (TPS6131X_INDC_OFF << TPS6131X_REG_4_INDC_SHIFT) }, + { TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | + TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 }, + { TPS6131X_REG_6, (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT) }, + { TPS6131X_REG_7, (TPS6131X_BATDROOP_150MV << TPS6131X_REG_7_BATDROOP_SHIFT) }, +}; + +/* + * These registers contain flags that are reset when read. + */ +static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg) +{ + switch (reg) { + case TPS6131X_REG_3: + case TPS6131X_REG_4: + case TPS6131X_REG_6: + return true; + default: + return false; + } +} + +static const struct regmap_config tps6131x_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = TPS6131X_REG_7, + .reg_defaults = tps6131x_regmap_defaults, + .num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults), + .cache_type = REGCACHE_FLAT, + .precious_reg = &tps6131x_regmap_precious, +}; + +struct tps6131x_timer_config { + u8 val; + u8 range; + u32 time_us; +}; + +static const struct tps6131x_timer_config tps6131x_timer_configs[] = { + { .val = 0, .range = 1, .time_us = 5300 }, + { .val = 1, .range = 1, .time_us = 10700 }, + { .val = 2, .range = 1, .time_us = 16000 }, + { .val = 3, .range = 1, .time_us = 21300 }, + { .val = 4, .range = 1, .time_us = 26600 }, + { .val = 5, .range = 1, .time_us = 32000 }, + { .val = 6, .range = 1, .time_us = 37300 }, + { .val = 0, .range = 0, .time_us = 68200 }, + { .val = 7, .range = 1, .time_us = 71500 }, + { .val = 1, .range = 0, .time_us = 102200 }, + { .val = 2, .range = 0, .time_us = 136300 }, + { .val = 3, .range = 0, .time_us = 170400 }, + { .val = 4, .range = 0, .time_us = 204500 }, + { .val = 5, .range = 0, .time_us = 340800 }, + { .val = 6, .range = 0, .time_us = 579300 }, + { .val = 7, .range = 0, .time_us = 852000 }, +}; + +static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us) +{ + const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0]; + u32 diff, min_diff = U32_MAX; + int i; + + for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) { + diff = abs(tps6131x_timer_configs[i].time_us - timeout_us); + if (diff < min_diff) { + timer_config = &tps6131x_timer_configs[i]; + min_diff = diff; + if (!min_diff) + break; + } + } + + return timer_config; +} + +static int tps6131x_reset_chip(struct tps6131x *tps6131x) +{ + int ret; + + if (tps6131x->reset_gpio) { + gpiod_set_value_cansleep(tps6131x->reset_gpio, 1); + fsleep(10); + gpiod_set_value_cansleep(tps6131x->reset_gpio, 0); + fsleep(100); + } else { + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, + TPS6131X_REG_0_RESET); + if (ret) + return ret; + + fsleep(100); + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0); + if (ret) + return ret; + } + + return 0; +} + +static int tps6131x_init_chip(struct tps6131x *tps6131x) +{ + u32 val; + int ret; + + val = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0; + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, val); + if (ret) + return ret; + + val = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE; + + if (tps6131x->chan1_en) + val |= TPS6131X_REG_5_ENLED1; + + if (tps6131x->chan2_en) + val |= TPS6131X_REG_5_ENLED2; + + if (tps6131x->chan3_en) + val |= TPS6131X_REG_5_ENLED3; + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, val); + if (ret) + return ret; + + val = TPS6131X_REG_6_ENTS; + + ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force) +{ + u8 val = mode << TPS6131X_REG_1_MODE_SHIFT; + + return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val, + NULL, false, force); +} + +static void tps6131x_torch_refresh_handler(struct work_struct *work) +{ + struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work); + int ret; + + guard(mutex)(&tps6131x->lock); + + ret = tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true); + if (ret < 0) { + dev_err(tps6131x->dev, "Failed to refresh torch watchdog timer\n"); + return; + } + + schedule_delayed_work(&tps6131x->torch_refresh_work, + TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES); +} + +static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev); + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u32 num_chans, steps_chan13, steps_chan2, steps_remaining; + u8 reg0; + int ret; + + cancel_delayed_work_sync(&tps6131x->torch_refresh_work); + + /* + * The brightness parameter uses the number of current steps as the unit (not the current + * value itself). Since the reported step size can vary depending on the configuration, + * this value must be converted into actual register steps. + */ + steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA; + + num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en; + + /* + * The currents are distributed as evenly as possible across the activated channels. + * Since channels 1 and 3 share the same register setting, they always use the same current + * value. Channel 2 supports higher currents and thus takes over the remaining additional + * portion that cannot be covered by the other channels. + */ + steps_chan13 = min_t(u32, steps_remaining / num_chans, + TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA); + if (tps6131x->chan1_en) + steps_remaining -= steps_chan13; + if (tps6131x->chan3_en) + steps_remaining -= steps_chan13; + + steps_chan2 = min_t(u32, steps_remaining, + TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA); + + guard(mutex)(&tps6131x->lock); + + reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) | + (steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT); + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, + TPS6131X_REG_0_DCLC13 | TPS6131X_REG_0_DCLC2, reg0); + if (ret < 0) + return ret; + + ret = tps6131x_set_mode(tps6131x, brightness ? TPS6131X_MODE_TORCH : TPS6131X_MODE_SHUTDOWN, + true); + if (ret < 0) + return ret; + + /* + * In order to use both the flash and the video light functions purely via the I2C + * interface, STRB1 must be low. If STRB1 is low, then the video light watchdog timer + * is also active, which puts the device into the shutdown state after around 13 seconds. + * To prevent this, the mode must be refreshed within the watchdog timeout. + */ + if (brightness) + schedule_delayed_work(&tps6131x->torch_refresh_work, + TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES); + + return 0; +} + +static int tps6131x_strobe_set(struct led_classdev_flash *fled_cdev, bool state) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + int ret; + + guard(mutex)(&tps6131x->lock); + + ret = tps6131x_set_mode(tps6131x, state ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN, + true); + if (ret < 0) + return ret; + + if (state) { + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, + TPS6131X_REG_3_SFT, NULL, false, true); + if (ret) + return ret; + } + + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, 0, NULL, + false, true); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u32 num_chans; + u32 steps_chan13, steps_chan2; + u32 steps_remaining; + int ret; + + steps_remaining = brightness / TPS6131X_FLASH_STEP_I_MA; + num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en; + steps_chan13 = min_t(u32, steps_remaining / num_chans, + TPS6131X_FLASH_MAX_I_CHAN13_MA / TPS6131X_FLASH_STEP_I_MA); + if (tps6131x->chan1_en) + steps_remaining -= steps_chan13; + if (tps6131x->chan3_en) + steps_remaining -= steps_chan13; + steps_chan2 = min_t(u32, steps_remaining, + TPS6131X_FLASH_MAX_I_CHAN2_MA / TPS6131X_FLASH_STEP_I_MA); + + guard(mutex)(&tps6131x->lock); + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_2, TPS6131X_REG_2_FC13, + steps_chan13 << TPS6131X_REG_2_FC13_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_FC2, + steps_chan2 << TPS6131X_REG_1_FC2_SHIFT); + if (ret < 0) + return ret; + + fled_cdev->brightness.val = brightness; + + return 0; +} + +static int tps6131x_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout_us) +{ + const struct tps6131x_timer_config *timer_config; + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + u8 reg3; + int ret; + + guard(mutex)(&tps6131x->lock); + + timer_config = tps6131x_find_closest_timer_config(timeout_us); + + reg3 = timer_config->val << TPS6131X_REG_3_STIM_SHIFT; + if (timer_config->range) + reg3 |= TPS6131X_REG_3_SELSTIM_TO; + + ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_3, + TPS6131X_REG_3_STIM | TPS6131X_REG_3_SELSTIM_TO, reg3); + if (ret < 0) + return ret; + + fled_cdev->timeout.val = timer_config->time_us; + + return 0; +} + +static int tps6131x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + unsigned int reg3; + int ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3); + if (ret) + return ret; + + *state = !!(reg3 & TPS6131X_REG_3_SFT); + + return 0; +} + +static int tps6131x_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault) +{ + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + unsigned int reg3, reg4, reg6; + int ret; + + *fault = 0; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, ®3); + if (ret < 0) + return ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_4, ®4); + if (ret < 0) + return ret; + + ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_6, ®6); + if (ret < 0) + return ret; + + if (reg3 & TPS6131X_REG_3_HPFL) + *fault |= LED_FAULT_SHORT_CIRCUIT; + + if (reg3 & TPS6131X_REG_3_SELSTIM_TO) + *fault |= LED_FAULT_TIMEOUT; + + if (reg4 & TPS6131X_REG_4_HOTDIE_HI) + *fault |= LED_FAULT_OVER_TEMPERATURE; + + if (reg6 & (TPS6131X_REG_6_LEDHOT | TPS6131X_REG_6_LEDWARN)) + *fault |= LED_FAULT_LED_OVER_TEMPERATURE; + + if (!(reg6 & TPS6131X_REG_6_LEDHDR)) + *fault |= LED_FAULT_UNDER_VOLTAGE; + + if (reg6 & TPS6131X_REG_6_LEDHOT) { + ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_6, + TPS6131X_REG_6_LEDHOT, 0, NULL, false, true); + if (ret < 0) + return ret; + } + + return 0; +} + +static const struct led_flash_ops flash_ops = { + .flash_brightness_set = tps6131x_flash_brightness_set, + .strobe_set = tps6131x_strobe_set, + .strobe_get = tps6131x_strobe_get, + .timeout_set = tps6131x_flash_timeout_set, + .fault_get = tps6131x_flash_fault_get, +}; + +static int tps6131x_parse_node(struct tps6131x *tps6131x) +{ + const struct tps6131x_timer_config *timer_config; + struct device *dev = tps6131x->dev; + u32 channels[TPS6131X_MAX_CHANNELS]; + u32 current_step_multiplier; + u32 current_ua; + u32 max_current_flash_ma, max_current_torch_ma; + u32 timeout_us; + int num_channels; + int i; + int ret; + + tps6131x->valley_current_limit = device_property_read_bool(dev, "ti,valley-current-limit"); + + tps6131x->led_node = device_get_next_child_node(dev, NULL); + if (!tps6131x->led_node) { + dev_err(dev, "Missing LED node\n"); + return -EINVAL; + } + + num_channels = fwnode_property_count_u32(tps6131x->led_node, "led-sources"); + if (num_channels <= 0) { + dev_err(dev, "Failed to read led-sources property\n"); + return -EINVAL; + } + + if (num_channels > TPS6131X_MAX_CHANNELS) { + dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n", + num_channels, TPS6131X_MAX_CHANNELS); + return -EINVAL; + } + + ret = fwnode_property_read_u32_array(tps6131x->led_node, "led-sources", channels, + num_channels); + if (ret < 0) { + dev_err(dev, "Failed to read led-sources property\n"); + return ret; + } + + max_current_flash_ma = 0; + max_current_torch_ma = 0; + for (i = 0; i < num_channels; i++) { + switch (channels[i]) { + case 1: + tps6131x->chan1_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA; + break; + case 2: + tps6131x->chan2_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN2_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN2_MA; + break; + case 3: + tps6131x->chan3_en = true; + max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA; + max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA; + break; + default: + dev_err(dev, "led-source out of range [1-3]\n"); + return -EINVAL; + } + } + + /* + * If only channels 1 and 3 are used, the step size is doubled because the two channels + * share the same current control register. + */ + current_step_multiplier = + (tps6131x->chan1_en && tps6131x->chan3_en && !tps6131x->chan2_en) ? 2 : 1; + tps6131x->step_flash_current_ma = current_step_multiplier * TPS6131X_FLASH_STEP_I_MA; + tps6131x->step_torch_current_ma = current_step_multiplier * TPS6131X_TORCH_STEP_I_MA; + + ret = fwnode_property_read_u32(tps6131x->led_node, "led-max-microamp", ¤t_ua); + if (ret < 0) { + dev_err(dev, "Failed to read led-max-microamp property\n"); + return ret; + } + + tps6131x->max_torch_current_ma = UA_TO_MA(current_ua); + + if (!tps6131x->max_torch_current_ma || + tps6131x->max_torch_current_ma > max_current_torch_ma || + (tps6131x->max_torch_current_ma % tps6131x->step_torch_current_ma)) { + dev_err(dev, "led-max-microamp out of range or not a multiple of %u\n", + tps6131x->step_torch_current_ma); + return -EINVAL; + } + + ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-microamp", ¤t_ua); + if (ret < 0) { + dev_err(dev, "Failed to read flash-max-microamp property\n"); + return ret; + } + + tps6131x->max_flash_current_ma = UA_TO_MA(current_ua); + + if (!tps6131x->max_flash_current_ma || + tps6131x->max_flash_current_ma > max_current_flash_ma || + (tps6131x->max_flash_current_ma % tps6131x->step_flash_current_ma)) { + dev_err(dev, "flash-max-microamp out of range or not a multiple of %u\n", + tps6131x->step_flash_current_ma); + return -EINVAL; + } + + ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-timeout-us", &timeout_us); + if (ret < 0) { + dev_err(dev, "Failed to read flash-max-timeout-us property\n"); + return ret; + } + + timer_config = tps6131x_find_closest_timer_config(timeout_us); + tps6131x->max_timeout_us = timer_config->time_us; + + if (tps6131x->max_timeout_us != timeout_us) + dev_warn(dev, "flash-max-timeout-us %u not supported (using %u)\n", timeout_us, + tps6131x->max_timeout_us); + + return 0; +} + +static int tps6131x_led_class_setup(struct tps6131x *tps6131x) +{ + const struct tps6131x_timer_config *timer_config; + struct led_classdev *led_cdev; + struct led_flash_setting *setting; + struct led_init_data init_data = {}; + int ret; + + tps6131x->fled_cdev.ops = &flash_ops; + + setting = &tps6131x->fled_cdev.timeout; + timer_config = tps6131x_find_closest_timer_config(0); + setting->min = timer_config->time_us; + setting->max = tps6131x->max_timeout_us; + setting->step = 1; /* Only some specific time periods are supported. No fixed step size. */ + setting->val = setting->min; + + setting = &tps6131x->fled_cdev.brightness; + setting->min = tps6131x->step_flash_current_ma; + setting->max = tps6131x->max_flash_current_ma; + setting->step = tps6131x->step_flash_current_ma; + setting->val = setting->min; + + led_cdev = &tps6131x->fled_cdev.led_cdev; + led_cdev->brightness_set_blocking = tps6131x_brightness_set; + led_cdev->max_brightness = tps6131x->max_torch_current_ma; + led_cdev->flags |= LED_DEV_CAP_FLASH; + + init_data.fwnode = tps6131x->led_node; + init_data.devicename = NULL; + init_data.default_label = NULL; + init_data.devname_mandatory = false; + + ret = devm_led_classdev_flash_register_ext(tps6131x->dev, &tps6131x->fled_cdev, + &init_data); + if (ret) + return ret; + + return 0; +} + +static int tps6131x_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable) +{ + struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev; + struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev); + + guard(mutex)(&tps6131x->lock); + + return tps6131x_set_mode(tps6131x, enable ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN, + false); +} + +static const struct v4l2_flash_ops tps6131x_v4l2_flash_ops = { + .external_strobe_set = tps6131x_flash_external_strobe_set, +}; + +static int tps6131x_v4l2_setup(struct tps6131x *tps6131x) +{ + struct v4l2_flash_config v4l2_cfg = { 0 }; + struct led_flash_setting *intensity = &v4l2_cfg.intensity; + + intensity->min = tps6131x->step_torch_current_ma; + intensity->max = tps6131x->max_torch_current_ma; + intensity->step = tps6131x->step_torch_current_ma; + intensity->val = intensity->min; + + strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name, + sizeof(v4l2_cfg.dev_name)); + + v4l2_cfg.has_external_strobe = true; + v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE | + LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE | + LED_FAULT_LED_OVER_TEMPERATURE; + + tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node, + &tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops, + &v4l2_cfg); + if (IS_ERR(tps6131x->v4l2_flash)) { + dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n"); + return PTR_ERR(tps6131x->v4l2_flash); + } + + return 0; +} + +static int tps6131x_probe(struct i2c_client *client) +{ + struct tps6131x *tps6131x; + int ret; + + tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL); + if (!tps6131x) + return -ENOMEM; + + tps6131x->dev = &client->dev; + i2c_set_clientdata(client, tps6131x); + mutex_init(&tps6131x->lock); + INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler); + + ret = tps6131x_parse_node(tps6131x); + if (ret) + return ret; + + tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap); + if (IS_ERR(tps6131x->regmap)) { + ret = PTR_ERR(tps6131x->regmap); + return dev_err_probe(&client->dev, ret, "Failed to allocate register map\n"); + } + + tps6131x->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(tps6131x->reset_gpio)) { + ret = PTR_ERR(tps6131x->reset_gpio); + return dev_err_probe(&client->dev, ret, "Failed to get reset GPIO\n"); + } + + ret = tps6131x_reset_chip(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to reset LED controller\n"); + + ret = tps6131x_init_chip(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to initialize LED controller\n"); + + ret = tps6131x_led_class_setup(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to setup LED class\n"); + + ret = tps6131x_v4l2_setup(tps6131x); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to setup v4l2 flash\n"); + + return 0; +} + +static void tps6131x_remove(struct i2c_client *client) +{ + struct tps6131x *tps6131x = i2c_get_clientdata(client); + + v4l2_flash_release(tps6131x->v4l2_flash); + + cancel_delayed_work_sync(&tps6131x->torch_refresh_work); +} + +static const struct of_device_id of_tps6131x_leds_match[] = { + { .compatible = "ti,tps61310" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_tps6131x_leds_match); + +static struct i2c_driver tps6131x_i2c_driver = { + .driver = { + .name = "tps6131x", + .of_match_table = of_tps6131x_leds_match, + }, + .probe = tps6131x_probe, + .remove = tps6131x_remove, +}; +module_i2c_driver(tps6131x_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments TPS6131X flash LED driver"); +MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c index 6fe9d700dfef..165035a8826c 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) @@ -441,6 +440,21 @@ int led_update_flash_brightness(struct led_classdev_flash *fled_cdev) } EXPORT_SYMBOL_GPL(led_update_flash_brightness); +int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration) +{ + struct led_classdev *led_cdev = &fled_cdev->led_cdev; + struct led_flash_setting *s = &fled_cdev->duration; + + s->val = duration; + led_clamp_align(s); + + if (!(led_cdev->flags & LED_SUSPENDED)) + return call_flash_op(fled_cdev, duration_set, s->val); + + return 0; +} +EXPORT_SYMBOL_GPL(led_set_flash_duration); + MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>"); MODULE_DESCRIPTION("LED Flash class interface"); MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c index e317408583df..fd66d2bdeace 100644 --- a/drivers/leds/led-class-multicolor.c +++ b/drivers/leds/led-class-multicolor.c @@ -6,12 +6,11 @@ #include <linux/device.h> #include <linux/init.h> #include <linux/led-class-multicolor.h> +#include <linux/math.h> #include <linux/module.h> #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) { @@ -19,9 +18,10 @@ int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev, int i; for (i = 0; i < mcled_cdev->num_colors; i++) - mcled_cdev->subled_info[i].brightness = brightness * - mcled_cdev->subled_info[i].intensity / - led_cdev->max_brightness; + mcled_cdev->subled_info[i].brightness = + DIV_ROUND_CLOSEST(brightness * + mcled_cdev->subled_info[i].intensity, + led_cdev->max_brightness); return 0; } @@ -59,7 +59,8 @@ static ssize_t multi_intensity_store(struct device *dev, for (i = 0; i < mcled_cdev->num_colors; i++) mcled_cdev->subled_info[i].intensity = intensity_value[i]; - led_set_brightness(led_cdev, led_cdev->brightness); + if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags)) + led_set_brightness(led_cdev, led_cdev->brightness); ret = size; err_out: mutex_unlock(&led_cdev->led_access); @@ -99,7 +100,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, " "); } @@ -132,6 +133,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 6dae56b914fe..885399ed0776 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -22,19 +22,23 @@ #include <linux/of.h> #include "leds.h" -static struct class *leds_class; 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 sysfs_emit(buf, "%u\n", brightness); } static ssize_t brightness_store(struct device *dev, @@ -58,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: @@ -71,14 +74,19 @@ 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; - return sprintf(buf, "%u\n", led_cdev->max_brightness); + mutex_lock(&led_cdev->led_access); + max_brightness = led_cdev->max_brightness; + mutex_unlock(&led_cdev->led_access); + + return sysfs_emit(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, }; @@ -114,7 +122,7 @@ static ssize_t brightness_hw_changed_show(struct device *dev, if (led_cdev->brightness_hw_changed == -1) return -ENODATA; - return sprintf(buf, "%u\n", led_cdev->brightness_hw_changed); + return sysfs_emit(buf, "%u\n", led_cdev->brightness_hw_changed); } static DEVICE_ATTR_RO(brightness_hw_changed); @@ -234,30 +242,42 @@ static struct led_classdev *led_module_get(struct device *led_dev) return led_cdev; } +static const struct class leds_class = { + .name = "leds", + .dev_groups = led_groups, + .pm = &leds_class_dev_pm_ops, +}; + /** * of_led_get() - request a LED device via the LED framework * @np: device node to get the LED device from * @index: the index of the LED + * @name: the name of the LED used to map it to its function, if present * * Returns the LED device parsed from the phandle specified in the "leds" * property of a device tree node or a negative error-code on failure. */ -struct led_classdev *of_led_get(struct device_node *np, int index) +static struct led_classdev *of_led_get(struct device_node *np, int index, + const char *name) { struct device *led_dev; struct device_node *led_node; + /* + * For named LEDs, first look up the name in the "led-names" property. + * If it cannot be found, then of_parse_phandle() will propagate the error. + */ + if (name) + index = of_property_match_string(np, "led-names", name); led_node = of_parse_phandle(np, "leds", index); if (!led_node) return ERR_PTR(-ENOENT); - led_dev = class_find_device_by_of_node(leds_class, led_node); + 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); } -EXPORT_SYMBOL_GPL(of_led_get); /** * led_put() - release a LED device @@ -312,7 +332,7 @@ struct led_classdev *__must_check devm_of_led_get(struct device *dev, if (!dev) return ERR_PTR(-EINVAL); - led = of_led_get(dev->of_node, index); + led = of_led_get(dev->of_node, index, NULL); if (IS_ERR(led)) return led; @@ -330,9 +350,14 @@ EXPORT_SYMBOL_GPL(devm_of_led_get); struct led_classdev *led_get(struct device *dev, char *con_id) { struct led_lookup_data *lookup; + struct led_classdev *led_cdev; const char *provider = NULL; struct device *led_dev; + led_cdev = of_led_get(dev->of_node, -1, con_id); + if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT) + return led_cdev; + mutex_lock(&leds_lookup_lock); list_for_each_entry(lookup, &leds_lookup_list, list) { if (!strcmp(lookup->dev_id, dev_name(dev)) && @@ -346,7 +371,7 @@ struct led_classdev *led_get(struct device *dev, char *con_id) if (!provider) return ERR_PTR(-ENOENT); - led_dev = class_find_device_by_name(leds_class, provider); + led_dev = class_find_device_by_name(&leds_class, provider); kfree_const(provider); return led_module_get(led_dev); @@ -402,6 +427,31 @@ void led_remove_lookup(struct led_lookup_data *led_lookup) } EXPORT_SYMBOL_GPL(led_remove_lookup); +/** + * devm_of_led_get_optional - Resource-managed request of an optional LED device + * @dev: LED consumer + * @index: index of the LED to obtain in the consumer + * + * The device node of the device is parsed to find the requested LED device. + * The LED device returned from this function is automatically released + * on driver detach. + * + * @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the + * led was not found. + */ +struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev, + int index) +{ + struct led_classdev *led; + + led = devm_of_led_get(dev, index); + if (IS_ERR(led) && PTR_ERR(led) == -ENOENT) + return NULL; + + return led; +} +EXPORT_SYMBOL_GPL(devm_of_led_get_optional); + static int led_classdev_next_name(const char *init_name, char *name, size_t len) { @@ -412,7 +462,7 @@ static int led_classdev_next_name(const char *init_name, char *name, strscpy(name, init_name, len); while ((ret < len) && - (dev = class_find_device_by_name(leds_class, name))) { + (dev = class_find_device_by_name(&leds_class, name))) { put_device(dev); ret = snprintf(name, len, "%s_%u", init_name, ++i); } @@ -457,6 +507,14 @@ int led_classdev_register_ext(struct device *parent, if (fwnode_property_present(init_data->fwnode, "retain-state-shutdown")) led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN; + + fwnode_property_read_u32(init_data->fwnode, + "max-brightness", + &led_cdev->max_brightness); + + if (fwnode_property_present(init_data->fwnode, "color")) + fwnode_property_read_u32(init_data->fwnode, "color", + &led_cdev->color); } } else { proposed_name = led_cdev->name; @@ -465,11 +523,19 @@ 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); mutex_init(&led_cdev->led_access); mutex_lock(&led_cdev->led_access); - led_cdev->dev = device_create_with_groups(leds_class, parent, 0, - led_cdev, led_cdev->groups, "%s", final_name); + led_cdev->dev = device_create_with_groups(&leds_class, parent, 0, + led_cdev, led_cdev->groups, "%s", final_name); if (IS_ERR(led_cdev->dev)) { mutex_unlock(&led_cdev->led_access); return PTR_ERR(led_cdev->dev); @@ -477,10 +543,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) { @@ -508,6 +570,8 @@ 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 @@ -626,17 +690,19 @@ EXPORT_SYMBOL_GPL(devm_led_classdev_unregister); static int __init leds_init(void) { - leds_class = class_create("leds"); - if (IS_ERR(leds_class)) - return PTR_ERR(leds_class); - leds_class->pm = &leds_class_dev_pm_ops; - leds_class->dev_groups = led_groups; - return 0; + 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_destroy(leds_class); + 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 b9b1295833c9..59473f286b31 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", @@ -36,8 +37,12 @@ const char * const led_colors[LED_COLOR_ID_MAX] = { [LED_COLOR_ID_IR] = "ir", [LED_COLOR_ID_MULTI] = "multicolor", [LED_COLOR_ID_RGB] = "rgb", + [LED_COLOR_ID_PURPLE] = "purple", + [LED_COLOR_ID_ORANGE] = "orange", + [LED_COLOR_ID_PINK] = "pink", + [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) { @@ -59,7 +64,8 @@ static int __led_set_brightness_blocking(struct led_classdev *led_cdev, unsigned static void led_timer_function(struct timer_list *t) { - struct led_classdev *led_cdev = from_timer(led_cdev, t, blink_timer); + struct led_classdev *led_cdev = timer_container_of(led_cdev, t, + blink_timer); unsigned long brightness; unsigned long delay; @@ -117,15 +123,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); } @@ -147,8 +160,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); @@ -222,7 +246,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); @@ -261,7 +285,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; } @@ -271,7 +295,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); @@ -292,7 +316,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); @@ -319,16 +343,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); @@ -357,19 +384,49 @@ 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 = 0; + int ret; if (led_cdev->brightness_get) { ret = led_cdev->brightness_get(led_cdev); - if (ret >= 0) { - led_cdev->brightness = ret; - return 0; - } + if (ret < 0) + return ret; + + led_cdev->brightness = ret; } - return ret; + return 0; } EXPORT_SYMBOL_GPL(led_update_brightness); @@ -473,10 +530,7 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data, struct led_properties props = {}; struct fwnode_handle *fwnode = init_data->fwnode; const char *devicename = init_data->devicename; - - /* We want to label LEDs that can produce full range of colors - * as RGB, not multicolor */ - BUG_ON(props.color == LED_COLOR_ID_MULTI); + int n; if (!led_classdev_name) return -EINVAL; @@ -490,49 +544,62 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data, * Otherwise the label is prepended with devicename to compose * the final LED class device name. */ - if (!devicename) { - strscpy(led_classdev_name, props.label, - LED_MAX_NAME_SIZE); + if (devicename) { + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", + devicename, props.label); } else { - snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", - devicename, props.label); + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", props.label); } } else if (props.function || props.color_present) { char tmp_buf[LED_MAX_NAME_SIZE]; if (props.func_enum_present) { - snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d", - props.color_present ? led_colors[props.color] : "", - props.function ?: "", props.func_enum); + n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d", + props.color_present ? led_colors[props.color] : "", + props.function ?: "", props.func_enum); } else { - snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s", - props.color_present ? led_colors[props.color] : "", - props.function ?: ""); + n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s", + props.color_present ? led_colors[props.color] : "", + props.function ?: ""); } + if (n >= LED_MAX_NAME_SIZE) + return -E2BIG; + if (init_data->devname_mandatory) { - snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", - devicename, tmp_buf); + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", + devicename, tmp_buf); } else { - strscpy(led_classdev_name, tmp_buf, LED_MAX_NAME_SIZE); - + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", tmp_buf); } } else if (init_data->default_label) { if (!devicename) { dev_err(dev, "Legacy LED naming requires devicename segment"); return -EINVAL; } - snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", - devicename, init_data->default_label); + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s", + devicename, init_data->default_label); } else if (is_of_node(fwnode)) { - strscpy(led_classdev_name, to_of_node(fwnode)->name, - LED_MAX_NAME_SIZE); + n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", + to_of_node(fwnode)->name); } else return -EINVAL; + if (n >= LED_MAX_NAME_SIZE) + return -E2BIG; + return 0; } 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-test.c b/drivers/leds/led-test.c new file mode 100644 index 000000000000..ddf9aa967a6a --- /dev/null +++ b/drivers/leds/led-test.c @@ -0,0 +1,132 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2025 Google LLC + * + * Author: Lee Jones <lee@kernel.org> + */ + +#include <kunit/device.h> +#include <kunit/test.h> +#include <linux/device.h> +#include <linux/leds.h> + +#define LED_TEST_POST_REG_BRIGHTNESS 10 + +struct led_test_ddata { + struct led_classdev cdev; + struct device *dev; +}; + +static enum led_brightness led_test_brightness_get(struct led_classdev *cdev) +{ + return LED_TEST_POST_REG_BRIGHTNESS; +} + +static void led_test_class_register(struct kunit *test) +{ + struct led_test_ddata *ddata = test->priv; + struct led_classdev *cdev_clash, *cdev = &ddata->cdev; + struct device *dev = ddata->dev; + int ret; + + /* Register a LED class device */ + cdev->name = "led-test"; + cdev->brightness_get = led_test_brightness_get; + cdev->brightness = 0; + + ret = devm_led_classdev_register(dev, cdev); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_EQ(test, cdev->max_brightness, LED_FULL); + KUNIT_EXPECT_EQ(test, cdev->brightness, LED_TEST_POST_REG_BRIGHTNESS); + KUNIT_EXPECT_STREQ(test, cdev->dev->kobj.name, "led-test"); + + /* Register again with the same name - expect it to pass with the LED renamed */ + cdev_clash = devm_kmemdup(dev, cdev, sizeof(*cdev), GFP_KERNEL); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cdev_clash); + + ret = devm_led_classdev_register(dev, cdev_clash); + KUNIT_ASSERT_EQ(test, ret, 0); + + KUNIT_EXPECT_STREQ(test, cdev_clash->dev->kobj.name, "led-test_1"); + KUNIT_EXPECT_STREQ(test, cdev_clash->name, "led-test"); + + /* Enable name conflict rejection and register with the same name again - expect failure */ + cdev_clash->flags |= LED_REJECT_NAME_CONFLICT; + ret = devm_led_classdev_register(dev, cdev_clash); + KUNIT_EXPECT_EQ(test, ret, -EEXIST); +} + +static void led_test_class_add_lookup_and_get(struct kunit *test) +{ + struct led_test_ddata *ddata = test->priv; + struct led_classdev *cdev = &ddata->cdev, *cdev_get; + struct device *dev = ddata->dev; + struct led_lookup_data lookup; + int ret; + + /* First, register a LED class device */ + cdev->name = "led-test"; + ret = devm_led_classdev_register(dev, cdev); + KUNIT_ASSERT_EQ(test, ret, 0); + + /* Then make the LED available for lookup */ + lookup.provider = cdev->name; + lookup.dev_id = dev_name(dev); + lookup.con_id = "led-test-1"; + led_add_lookup(&lookup); + + /* Finally, attempt to look it up via the API - imagine this was an orthogonal driver */ + cdev_get = devm_led_get(dev, "led-test-1"); + KUNIT_ASSERT_FALSE(test, IS_ERR(cdev_get)); + + KUNIT_EXPECT_STREQ(test, cdev_get->name, cdev->name); + + led_remove_lookup(&lookup); +} + +static struct kunit_case led_test_cases[] = { + KUNIT_CASE(led_test_class_register), + KUNIT_CASE(led_test_class_add_lookup_and_get), + { } +}; + +static int led_test_init(struct kunit *test) +{ + struct led_test_ddata *ddata; + struct device *dev; + + ddata = kunit_kzalloc(test, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + test->priv = ddata; + + dev = kunit_device_register(test, "led_test"); + if (IS_ERR(dev)) + return PTR_ERR(dev); + + ddata->dev = get_device(dev); + + return 0; +} + +static void led_test_exit(struct kunit *test) +{ + struct led_test_ddata *ddata = test->priv; + + if (ddata && ddata->dev) + put_device(ddata->dev); +} + +static struct kunit_suite led_test_suite = { + .name = "led", + .init = led_test_init, + .exit = led_test_exit, + .test_cases = led_test_cases, +}; +kunit_test_suite(led_test_suite); + +MODULE_AUTHOR("Lee Jones <lee@kernel.org>"); +MODULE_DESCRIPTION("KUnit tests for the LED framework"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 6a5e1f41f9a4..3799dcc1cf07 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -23,7 +23,7 @@ * Nests outside led_cdev->trigger_lock */ static DECLARE_RWSEM(triggers_list_lock); -LIST_HEAD(trigger_list); +static LIST_HEAD(trigger_list); /* Used by LED Class */ @@ -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); @@ -54,6 +54,11 @@ ssize_t led_trigger_write(struct file *filp, struct kobject *kobj, goto unlock; } + if (sysfs_streq(buf, "default")) { + led_trigger_set_default(led_cdev); + goto unlock; + } + down_read(&triggers_list_lock); list_for_each_entry(trig, &trigger_list, next_trig) { if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) { @@ -98,6 +103,9 @@ static int led_trigger_format(char *buf, size_t size, int len = led_trigger_snprintf(buf, size, "%s", led_cdev->trigger ? "none" : "[none]"); + if (led_cdev->default_trigger) + len += led_trigger_snprintf(buf + len, size - len, " default"); + list_for_each_entry(trig, &trigger_list, next_trig) { bool hit; @@ -123,7 +131,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 +187,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 +202,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; @@ -247,40 +268,50 @@ void led_trigger_remove(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_trigger_remove); +static bool led_match_default_trigger(struct led_classdev *led_cdev, + struct led_trigger *trig) +{ + if (!strcmp(led_cdev->default_trigger, trig->name) && + trigger_relevant(led_cdev, trig)) { + led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; + led_trigger_set(led_cdev, trig); + return true; + } + + return false; +} + void led_trigger_set_default(struct led_classdev *led_cdev) { struct led_trigger *trig; + bool found = false; if (!led_cdev->default_trigger) return; + if (!strcmp(led_cdev->default_trigger, "none")) { + led_trigger_remove(led_cdev); + return; + } + down_read(&triggers_list_lock); down_write(&led_cdev->trigger_lock); list_for_each_entry(trig, &trigger_list, next_trig) { - if (!strcmp(led_cdev->default_trigger, trig->name) && - trigger_relevant(led_cdev, trig)) { - led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; - led_trigger_set(led_cdev, trig); + found = led_match_default_trigger(led_cdev, trig); + if (found) break; - } } up_write(&led_cdev->trigger_lock); up_read(&triggers_list_lock); -} -EXPORT_SYMBOL_GPL(led_trigger_set_default); -void led_trigger_rename_static(const char *name, struct led_trigger *trig) -{ - /* new name must be on a temporary string to prevent races */ - BUG_ON(name == trig->name); - - down_write(&triggers_list_lock); - /* this assumes that trig->name was originaly allocated to - * non constant storage */ - strcpy((char *)trig->name, name); - up_write(&triggers_list_lock); + /* + * If default trigger wasn't found, maybe trigger module isn't loaded yet. + * Once loaded it will re-probe with all led_cdev's. + */ + if (!found) + request_module_nowait("ledtrig:%s", led_cdev->default_trigger); } -EXPORT_SYMBOL_GPL(led_trigger_rename_static); +EXPORT_SYMBOL_GPL(led_trigger_set_default); /* LED Trigger Interface */ @@ -310,12 +341,8 @@ int led_trigger_register(struct led_trigger *trig) down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { down_write(&led_cdev->trigger_lock); - if (!led_cdev->trigger && led_cdev->default_trigger && - !strcmp(led_cdev->default_trigger, trig->name) && - trigger_relevant(led_cdev, trig)) { - led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER; - led_trigger_set(led_cdev, trig); - } + if (!led_cdev->trigger && led_cdev->default_trigger) + led_match_default_trigger(led_cdev, trig); up_write(&led_cdev->trigger_lock); } up_read(&leds_list_lock); @@ -386,6 +413,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); @@ -393,6 +422,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 508d0d859f2e..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; } } @@ -215,13 +214,11 @@ static int pm860x_led_probe(struct platform_device *pdev) return 0; } -static int pm860x_led_remove(struct platform_device *pdev) +static void pm860x_led_remove(struct platform_device *pdev) { struct pm860x_led *data = platform_get_drvdata(pdev); led_classdev_unregister(&data->cdev); - - return 0; } static struct platform_driver pm860x_led_driver = { diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c index 5a0cc7af2df8..13e5bc80e56e 100644 --- a/drivers/leds/leds-adp5520.c +++ b/drivers/leds/leds-adp5520.c @@ -163,7 +163,7 @@ err: return ret; } -static int adp5520_led_remove(struct platform_device *pdev) +static void adp5520_led_remove(struct platform_device *pdev) { struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led; @@ -177,8 +177,6 @@ static int adp5520_led_remove(struct platform_device *pdev) for (i = 0; i < pdata->num_leds; i++) { led_classdev_unregister(&led[i].cdev); } - - return 0; } static struct platform_driver adp5520_led_driver = { diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c index 24b1041213c2..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,18 +331,17 @@ 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); static struct i2c_driver an30259a_driver = { .driver = { .name = "leds-an30259a", - .of_match_table = of_match_ptr(an30259a_match_table), + .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-ariel.c b/drivers/leds/leds-ariel.c index 49e1bddaa15e..dd319c7e385f 100644 --- a/drivers/leds/leds-ariel.c +++ b/drivers/leds/leds-ariel.c @@ -7,8 +7,8 @@ #include <linux/module.h> #include <linux/leds.h> +#include <linux/platform_device.h> #include <linux/regmap.h> -#include <linux/of_platform.h> enum ec_index { EC_BLUE_LED = 0x01, diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c index 96979b8e09b7..fe223d363a5d 100644 --- a/drivers/leds/leds-aw200xx.c +++ b/drivers/leds/leds-aw200xx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 /* - * Awinic AW20036/AW20054/AW20072 LED driver + * Awinic AW20036/AW20054/AW20072/AW20108 LED driver * * Copyright (c) 2023, SberDevices. All Rights Reserved. * @@ -10,6 +10,7 @@ #include <linux/bitfield.h> #include <linux/bits.h> #include <linux/container_of.h> +#include <linux/gpio/consumer.h> #include <linux/i2c.h> #include <linux/leds.h> #include <linux/mod_devicetable.h> @@ -74,6 +75,10 @@ #define AW200XX_LED2REG(x, columns) \ ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns)))) +/* DIM current configuration register on page 1 */ +#define AW200XX_REG_DIM_PAGE1(x, columns) \ + AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns)) + /* * DIM current configuration register (page 4). * The even address for current DIM configuration. @@ -82,6 +87,8 @@ #define AW200XX_REG_DIM(x, columns) \ AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2) #define AW200XX_REG_DIM2FADE(x) ((x) + 1) +#define AW200XX_REG_FADE2DIM(fade) \ + DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX) /* * Duty ratio of display scan (see p.15 of datasheet for formula): @@ -112,7 +119,8 @@ struct aw200xx { struct mutex mutex; u32 num_leds; u32 display_rows; - struct aw200xx_led leds[]; + struct gpio_desc *hwen; + struct aw200xx_led leds[] __counted_by(num_leds); }; static ssize_t dim_show(struct device *dev, struct device_attribute *devattr, @@ -153,7 +161,8 @@ static ssize_t dim_store(struct device *dev, struct device_attribute *devattr, if (dim >= 0) { ret = regmap_write(chip->regmap, - AW200XX_REG_DIM(led->num, columns), dim); + AW200XX_REG_DIM_PAGE1(led->num, columns), + dim); if (ret) goto out_unlock; } @@ -188,9 +197,7 @@ static int aw200xx_brightness_set(struct led_classdev *cdev, dim = led->dim; if (dim < 0) - dim = max_t(int, - brightness / (AW200XX_FADE_MAX / AW200XX_DIM_MAX), - 1); + dim = AW200XX_REG_FADE2DIM(brightness); ret = regmap_write(chip->regmap, reg, dim); if (ret) @@ -275,7 +282,7 @@ static int aw200xx_set_imax(const struct aw200xx *const chip, u32 led_imax_uA) { u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA); - u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16}; + static const u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16}; u32 gccr_imax = UINT_MAX; u32 cur_imax = 0; int i; @@ -314,6 +321,9 @@ static int aw200xx_chip_reset(const struct aw200xx *const chip) if (ret) return ret; + /* According to the datasheet software reset takes at least 1ms */ + fsleep(1000); + regcache_mark_dirty(chip->regmap); return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR); } @@ -353,32 +363,67 @@ static int aw200xx_chip_check(const struct aw200xx *const chip) return 0; } -static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) +static void aw200xx_enable(const struct aw200xx *const chip) +{ + gpiod_set_value_cansleep(chip->hwen, 1); + + /* + * After HWEN pin set high the chip begins to load the OTP information, + * which takes 200us to complete. About 200us wait time is needed for + * internal oscillator startup and display SRAM initialization. After + * display SRAM initialization, the registers in page1 to page5 can be + * configured via i2c interface. + */ + fsleep(400); +} + +static void aw200xx_disable(const struct aw200xx *const chip) +{ + gpiod_set_value_cansleep(chip->hwen, 0); +} + +static int aw200xx_probe_get_display_rows(struct device *dev, + struct aw200xx *chip) { struct fwnode_handle *child; + u32 max_source = 0; + + device_for_each_child_node(dev, child) { + u32 source; + int ret; + + ret = fwnode_property_read_u32(child, "reg", &source); + if (ret || source >= chip->cdef->channels) + continue; + + max_source = max(max_source, source); + } + + if (max_source == 0) + return -EINVAL; + + chip->display_rows = max_source / chip->cdef->display_size_columns + 1; + + return 0; +} + +static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) +{ u32 current_min, current_max, min_uA; int ret; int i; - ret = device_property_read_u32(dev, "awinic,display-rows", - &chip->display_rows); + ret = aw200xx_probe_get_display_rows(dev, chip); if (ret) return dev_err_probe(dev, ret, - "Failed to read 'display-rows' property\n"); - - if (!chip->display_rows || - chip->display_rows > chip->cdef->display_size_rows_max) { - return dev_err_probe(dev, ret, - "Invalid leds display size %u\n", - chip->display_rows); - } + "No valid led definitions found\n"); current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA); current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA); 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; @@ -416,15 +461,14 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip) led->num = source; led->chip = chip; led->cdev.brightness_set_blocking = aw200xx_brightness_set; + led->cdev.max_brightness = AW200XX_FADE_MAX; led->cdev.groups = dim_groups; init_data.fwnode = child; ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data); - if (ret) { - fwnode_handle_put(child); + if (ret) break; - } i++; } @@ -479,9 +523,20 @@ static const struct regmap_config aw200xx_regmap_config = { .num_ranges = ARRAY_SIZE(aw200xx_ranges), .rd_table = &aw200xx_readable_table, .wr_table = &aw200xx_writeable_table, - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, + .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; @@ -512,11 +567,25 @@ static int aw200xx_probe(struct i2c_client *client) if (IS_ERR(chip->regmap)) return PTR_ERR(chip->regmap); + chip->hwen = devm_gpiod_get_optional(&client->dev, "enable", + GPIOD_OUT_HIGH); + if (IS_ERR(chip->hwen)) + return dev_err_probe(&client->dev, PTR_ERR(chip->hwen), + "Cannot get enable GPIO"); + + 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); @@ -525,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; @@ -532,18 +605,13 @@ static int aw200xx_probe(struct i2c_client *client) ret = aw200xx_chip_init(chip); out_unlock: + if (ret) + aw200xx_disable(chip); + mutex_unlock(&chip->mutex); return ret; } -static void aw200xx_remove(struct i2c_client *client) -{ - struct aw200xx *chip = i2c_get_clientdata(client); - - aw200xx_chip_reset(chip); - mutex_destroy(&chip->mutex); -} - static const struct aw200xx_chipdef aw20036_cdef = { .channels = 36, .display_size_rows_max = 3, @@ -562,10 +630,17 @@ static const struct aw200xx_chipdef aw20072_cdef = { .display_size_columns = 12, }; +static const struct aw200xx_chipdef aw20108_cdef = { + .channels = 108, + .display_size_rows_max = 9, + .display_size_columns = 12, +}; + static const struct i2c_device_id aw200xx_id[] = { { "aw20036" }, { "aw20054" }, { "aw20072" }, + { "aw20108" }, {} }; MODULE_DEVICE_TABLE(i2c, aw200xx_id); @@ -574,6 +649,7 @@ static const struct of_device_id aw200xx_match_table[] = { { .compatible = "awinic,aw20036", .data = &aw20036_cdef, }, { .compatible = "awinic,aw20054", .data = &aw20054_cdef, }, { .compatible = "awinic,aw20072", .data = &aw20072_cdef, }, + { .compatible = "awinic,aw20108", .data = &aw20108_cdef, }, {} }; MODULE_DEVICE_TABLE(of, aw200xx_match_table); @@ -583,8 +659,7 @@ static struct i2c_driver aw200xx_driver = { .name = "aw200xx", .of_match_table = aw200xx_match_table, }, - .probe_new = aw200xx_probe, - .remove = aw200xx_remove, + .probe = aw200xx_probe, .id_table = aw200xx_id, }; module_i2c_driver(aw200xx_driver); diff --git a/drivers/leds/leds-aw2013.c b/drivers/leds/leds-aw2013.c index 59765640b70f..216755d6010f 100644 --- a/drivers/leds/leds-aw2013.c +++ b/drivers/leds/leds-aw2013.c @@ -62,7 +62,7 @@ struct aw2013_led { struct aw2013 { struct mutex mutex; /* held when writing to registers */ - struct regulator *vcc_regulator; + struct regulator_bulk_data regulators[2]; struct i2c_client *client; struct aw2013_led leds[AW2013_MAX_LEDS]; struct regmap *regmap; @@ -106,10 +106,11 @@ static void aw2013_chip_disable(struct aw2013 *chip) regmap_write(chip->regmap, AW2013_GCR, 0); - ret = regulator_disable(chip->vcc_regulator); + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), + chip->regulators); if (ret) { dev_err(&chip->client->dev, - "Failed to disable regulator: %d\n", ret); + "Failed to disable regulators: %d\n", ret); return; } @@ -123,10 +124,11 @@ static int aw2013_chip_enable(struct aw2013 *chip) if (chip->enabled) return 0; - ret = regulator_enable(chip->vcc_regulator); + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), + chip->regulators); if (ret) { dev_err(&chip->client->dev, - "Failed to enable regulator: %d\n", ret); + "Failed to enable regulators: %d\n", ret); return ret; } chip->enabled = true; @@ -261,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; @@ -271,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; @@ -302,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++; } @@ -318,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, @@ -334,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; @@ -348,19 +356,23 @@ static int aw2013_probe(struct i2c_client *client) goto error; } - chip->vcc_regulator = devm_regulator_get(&client->dev, "vcc"); - ret = PTR_ERR_OR_ZERO(chip->vcc_regulator); - if (ret) { + chip->regulators[0].supply = "vcc"; + chip->regulators[1].supply = "vio"; + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(chip->regulators), + chip->regulators); + if (ret < 0) { if (ret != -EPROBE_DEFER) dev_err(&client->dev, - "Failed to request regulator: %d\n", ret); + "Failed to request regulators: %d\n", ret); goto error; } - ret = regulator_enable(chip->vcc_regulator); + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), + chip->regulators); if (ret) { dev_err(&client->dev, - "Failed to enable regulator: %d\n", ret); + "Failed to enable regulators: %d\n", ret); goto error; } @@ -378,14 +390,19 @@ 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; - ret = regulator_disable(chip->vcc_regulator); + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), + chip->regulators); if (ret) { dev_err(&client->dev, - "Failed to disable regulator: %d\n", ret); + "Failed to disable regulators: %d\n", ret); goto error; } @@ -394,22 +411,14 @@ static int aw2013_probe(struct i2c_client *client) return 0; error_reg: - regulator_disable(chip->vcc_regulator); + regulator_bulk_disable(ARRAY_SIZE(chip->regulators), + chip->regulators); error: - mutex_destroy(&chip->mutex); + mutex_unlock(&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 */ }, @@ -420,10 +429,9 @@ MODULE_DEVICE_TABLE(of, aw2013_match_table); static struct i2c_driver aw2013_driver = { .driver = { .name = "leds-aw2013", - .of_match_table = of_match_ptr(aw2013_match_table), + .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 f512e99b976b..f00b16ac1586 100644 --- a/drivers/leds/leds-clevo-mail.c +++ b/drivers/leds/leds-clevo-mail.c @@ -159,10 +159,9 @@ static int __init clevo_mail_led_probe(struct platform_device *pdev) return led_classdev_register(&pdev->dev, &clevo_mail_led); } -static int clevo_mail_led_remove(struct platform_device *pdev) +static void clevo_mail_led_remove(struct platform_device *pdev) { led_classdev_unregister(&clevo_mail_led); - return 0; } static struct platform_driver clevo_mail_led_driver = { diff --git a/drivers/leds/leds-cpcap.c b/drivers/leds/leds-cpcap.c index 7d41ce8c9bb1..87354f17644b 100644 --- a/drivers/leds/leds-cpcap.c +++ b/drivers/leds/leds-cpcap.c @@ -7,7 +7,7 @@ #include <linux/mfd/motorola-cpcap.h> #include <linux/module.h> #include <linux/mutex.h> -#include <linux/of_device.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/regmap.h> #include <linux/regulator/consumer.h> diff --git a/drivers/leds/leds-cr0014114.c b/drivers/leds/leds-cr0014114.c index c87686bd7c18..7e51c374edd4 100644 --- a/drivers/leds/leds-cr0014114.c +++ b/drivers/leds/leds-cr0014114.c @@ -4,8 +4,8 @@ #include <linux/delay.h> #include <linux/leds.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> -#include <linux/of_device.h> #include <linux/spi/spi.h> #include <linux/workqueue.h> @@ -56,7 +56,7 @@ struct cr0014114 { struct spi_device *spi; u8 *buf; unsigned long delay; - struct cr0014114_led leds[]; + struct cr0014114_led leds[] __counted_by(count); }; static void cr0014114_calc_crc(u8 *buf, const size_t len) @@ -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..bea3cc3fbfd2 --- /dev/null +++ b/drivers/leds/leds-cros_ec.c @@ -0,0 +1,263 @@ +// 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; +}; + +static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec, + union cros_ec_led_cmd_data *arg) +{ + int ret; + + ret = cros_ec_cmd(cros_ec, 1, EC_CMD_LED_CONTROL, &arg->req, + sizeof(arg->req), &arg->resp, sizeof(arg->resp)); + if (ret < 0) + return ret; + + 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; + } + } + + *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; + if (num_subleds == 0) + return 0; /* LED without any colors, skip */ + + 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 2b5fb00438a2..71209b3c8f1e 100644 --- a/drivers/leds/leds-da903x.c +++ b/drivers/leds/leds-da903x.c @@ -121,13 +121,11 @@ static int da903x_led_probe(struct platform_device *pdev) return 0; } -static int da903x_led_remove(struct platform_device *pdev) +static void da903x_led_remove(struct platform_device *pdev) { struct da903x_led *led = platform_get_drvdata(pdev); led_classdev_unregister(&led->cdev); - - return 0; } static struct platform_driver da903x_led_driver = { diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c index 04060c862bf9..7c9051184a59 100644 --- a/drivers/leds/leds-da9052.c +++ b/drivers/leds/leds-da9052.c @@ -156,7 +156,7 @@ err: return error; } -static int da9052_led_remove(struct platform_device *pdev) +static void da9052_led_remove(struct platform_device *pdev) { struct da9052_led *led = platform_get_drvdata(pdev); struct da9052_pdata *pdata; @@ -172,8 +172,6 @@ static int da9052_led_remove(struct platform_device *pdev) da9052_set_led_brightness(&led[i], LED_OFF); led_classdev_unregister(&led[i].cdev); } - - return 0; } static struct platform_driver da9052_led_driver = { diff --git a/drivers/leds/leds-el15203000.c b/drivers/leds/leds-el15203000.c index 7e7b617bcd56..e26d1654bd0d 100644 --- a/drivers/leds/leds-el15203000.c +++ b/drivers/leds/leds-el15203000.c @@ -80,7 +80,7 @@ struct el15203000 { struct spi_device *spi; unsigned long delay; size_t count; - struct el15203000_led leds[]; + struct el15203000_led leds[] __counted_by(count); }; #define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev) @@ -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 new file mode 100644 index 000000000000..bb69be228a6d --- /dev/null +++ b/drivers/leds/leds-expresswire.c @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Shared library for Kinetic's ExpressWire protocol. + * This protocol works by pulsing the ExpressWire IC's control GPIO. + * ktd2692 and ktd2801 are known to use this protocol. + */ + +#include <linux/bits.h> +#include <linux/delay.h> +#include <linux/export.h> +#include <linux/gpio/consumer.h> +#include <linux/types.h> + +#include <linux/leds-expresswire.h> + +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"); + +void expresswire_enable(struct expresswire_common_props *props) +{ + gpiod_set_value(props->ctrl_gpio, 1); + udelay(props->timing.detect_delay_us); + gpiod_set_value(props->ctrl_gpio, 0); + udelay(props->timing.detect_us); + gpiod_set_value(props->ctrl_gpio, 1); +} +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"); + +void expresswire_end(struct expresswire_common_props *props) +{ + gpiod_set_value(props->ctrl_gpio, 0); + udelay(props->timing.end_of_data_low_us); + gpiod_set_value(props->ctrl_gpio, 1); + udelay(props->timing.end_of_data_high_us); +} +EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE"); + +void expresswire_set_bit(struct expresswire_common_props *props, bool bit) +{ + if (bit) { + gpiod_set_value(props->ctrl_gpio, 0); + udelay(props->timing.short_bitset_us); + gpiod_set_value(props->ctrl_gpio, 1); + udelay(props->timing.long_bitset_us); + } else { + gpiod_set_value(props->ctrl_gpio, 0); + udelay(props->timing.long_bitset_us); + gpiod_set_value(props->ctrl_gpio, 1); + udelay(props->timing.short_bitset_us); + } +} +EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE"); + +void expresswire_write_u8(struct expresswire_common_props *props, u8 val) +{ + expresswire_start(props); + for (int i = 7; i >= 0; i--) + expresswire_set_bit(props, val & BIT(i)); + expresswire_end(props); +} +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 7bfe40a6bfdd..a3428b22de3a 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -6,18 +6,20 @@ * Raphael Assenat <raph@8d.com> * Copyright (C) 2008 Freescale Semiconductor, Inc. */ +#include <linux/container_of.h> +#include <linux/device.h> #include <linux/err.h> #include <linux/gpio.h> #include <linux/gpio/consumer.h> -#include <linux/kernel.h> #include <linux/leds.h> +#include <linux/mod_devicetable.h> #include <linux/module.h> -#include <linux/of.h> +#include <linux/overflow.h> #include <linux/pinctrl/consumer.h> #include <linux/platform_device.h> #include <linux/property.h> #include <linux/slab.h> -#include "leds.h" +#include <linux/types.h> struct gpio_led_data { struct led_classdev cdev; @@ -125,16 +127,13 @@ static int create_gpio_led(const struct gpio_led *template, return ret; pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev); - if (IS_ERR(pinctrl)) { - ret = PTR_ERR(pinctrl); - if (ret != -ENODEV) { - dev_warn(led_dat->cdev.dev, - "Failed to select %pOF pinctrl: %d\n", - to_of_node(fwnode), ret); - } else { - /* pinctrl-%d not present, not an error */ - ret = 0; - } + ret = PTR_ERR_OR_ZERO(pinctrl); + /* pinctrl-%d not present, not an error */ + if (ret == -ENODEV) + ret = 0; + if (ret) { + dev_warn(led_dat->cdev.dev, "Failed to select %pfw pinctrl: %d\n", + fwnode, ret); } return ret; @@ -142,15 +141,13 @@ static int create_gpio_led(const struct gpio_led *template, struct gpio_leds_priv { int num_leds; - struct gpio_led_data leds[]; + struct gpio_led_data leds[] __counted_by(num_leds); }; -static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) +static struct gpio_leds_priv *gpio_leds_create(struct device *dev) { - struct device *dev = &pdev->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 platform_device *pdev) 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 = {}; /* @@ -172,7 +171,8 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL, GPIOD_ASIS, NULL); if (IS_ERR(led.gpiod)) { - fwnode_handle_put(child); + dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n", + child); return ERR_CAST(led.gpiod); } @@ -188,15 +188,15 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) 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; } @@ -212,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; /* @@ -221,13 +220,13 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx, * device, this will hit the board file, if any and get * the GPIO from there. */ - gpiod = devm_gpiod_get_index(dev, NULL, idx, GPIOD_OUT_LOW); - if (!IS_ERR(gpiod)) { + gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW); + if (IS_ERR(gpiod)) + return gpiod; + if (gpiod) { gpiod_set_consumer_name(gpiod, template->name); return gpiod; } - if (PTR_ERR(gpiod) != -ENOENT) - return gpiod; /* * This is the legacy code path for platform code that @@ -239,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); @@ -251,18 +247,21 @@ 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; } static int gpio_led_probe(struct platform_device *pdev) { - struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct device *dev = &pdev->dev; + struct gpio_led_platform_data *pdata = dev_get_platdata(dev); struct gpio_leds_priv *priv; - int i, ret = 0; + int i, ret; if (pdata && pdata->num_leds) { - priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, pdata->num_leds), - GFP_KERNEL); + priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL); if (!priv) return -ENOMEM; @@ -275,22 +274,20 @@ static int gpio_led_probe(struct platform_device *pdev) led_dat->gpiod = template->gpiod; else led_dat->gpiod = - gpio_led_get_gpiod(&pdev->dev, - i, template); + gpio_led_get_gpiod(dev, i, template); if (IS_ERR(led_dat->gpiod)) { - dev_info(&pdev->dev, "Skipping unavailable LED gpio %d (%s)\n", + dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n", template->gpio, template->name); continue; } - ret = create_gpio_led(template, led_dat, - &pdev->dev, NULL, + ret = create_gpio_led(template, led_dat, dev, NULL, pdata->gpio_blink_set); if (ret < 0) return ret; } } else { - priv = gpio_leds_create(pdev); + priv = gpio_leds_create(dev); if (IS_ERR(priv)) return PTR_ERR(priv); } diff --git a/drivers/leds/leds-ip30.c b/drivers/leds/leds-ip30.c index 1f952bad0fe8..2df24c303366 100644 --- a/drivers/leds/leds-ip30.c +++ b/drivers/leds/leds-ip30.c @@ -27,22 +27,16 @@ static void ip30led_set(struct led_classdev *led_cdev, static int ip30led_create(struct platform_device *pdev, int num) { - struct resource *res; struct ip30_led *data; - res = platform_get_resource(pdev, IORESOURCE_MEM, num); - if (!res) - return -EBUSY; - data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; - data->reg = devm_ioremap_resource(&pdev->dev, res); + data->reg = devm_platform_ioremap_resource(pdev, num); if (IS_ERR(data->reg)) return PTR_ERR(data->reg); - switch (num) { case IP30_LED_SYSTEM: data->cdev.name = "white:power"; diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c index 66c65741202e..e411cee06dab 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) @@ -495,11 +483,6 @@ static inline int is31fl3196_db_to_gain(u32 dezibel) return dezibel / IS31FL3196_AUDIO_GAIN_DB_STEP; } -static void is31f1319x_mutex_destroy(void *lock) -{ - mutex_destroy(lock); -} - static int is31fl319x_probe(struct i2c_client *client) { struct is31fl319x_chip *is31; @@ -515,8 +498,7 @@ static int is31fl319x_probe(struct i2c_client *client) if (!is31) return -ENOMEM; - mutex_init(&is31->lock); - err = devm_add_action_or_reset(dev, is31f1319x_mutex_destroy, &is31->lock); + err = devm_mutex_init(dev, &is31->lock); if (err) return err; diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c index 72cb56d305c4..dc9349f9d350 100644 --- a/drivers/leds/leds-is31fl32xx.c +++ b/drivers/leds/leds-is31fl32xx.c @@ -15,7 +15,6 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> /* Used to indicate a device has no such register */ #define IS31FL32XX_REG_NONE 0xFF @@ -33,6 +32,8 @@ #define IS31FL3216_CONFIG_SSD_ENABLE BIT(7) #define IS31FL3216_CONFIG_SSD_DISABLE 0 +#define IS31FL32XX_PWM_FREQUENCY_22KHZ 0x01 + struct is31fl32xx_priv; struct is31fl32xx_led_data { struct led_classdev cdev; @@ -54,6 +55,7 @@ struct is31fl32xx_priv { * @pwm_update_reg : address of PWM Update register * @global_control_reg : address of Global Control register (optional) * @reset_reg : address of Reset register (optional) + * @output_frequency_setting_reg: address of output frequency register (optional) * @pwm_register_base : address of first PWM register * @pwm_registers_reversed: : true if PWM registers count down instead of up * @led_control_register_base : address of first LED control register (optional) @@ -77,6 +79,7 @@ struct is31fl32xx_chipdef { u8 pwm_update_reg; u8 global_control_reg; u8 reset_reg; + u8 output_frequency_setting_reg; u8 pwm_register_base; bool pwm_registers_reversed; u8 led_control_register_base; @@ -91,6 +94,19 @@ static const struct is31fl32xx_chipdef is31fl3236_cdef = { .pwm_update_reg = 0x25, .global_control_reg = 0x4a, .reset_reg = 0x4f, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, + .pwm_register_base = 0x01, + .led_control_register_base = 0x26, + .enable_bits_per_led_control_register = 1, +}; + +static const struct is31fl32xx_chipdef is31fl3236a_cdef = { + .channels = 36, + .shutdown_reg = 0x00, + .pwm_update_reg = 0x25, + .global_control_reg = 0x4a, + .reset_reg = 0x4f, + .output_frequency_setting_reg = 0x4b, .pwm_register_base = 0x01, .led_control_register_base = 0x26, .enable_bits_per_led_control_register = 1, @@ -102,6 +118,7 @@ static const struct is31fl32xx_chipdef is31fl3235_cdef = { .pwm_update_reg = 0x25, .global_control_reg = 0x4a, .reset_reg = 0x4f, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x05, .led_control_register_base = 0x2a, .enable_bits_per_led_control_register = 1, @@ -113,6 +130,7 @@ static const struct is31fl32xx_chipdef is31fl3218_cdef = { .pwm_update_reg = 0x16, .global_control_reg = IS31FL32XX_REG_NONE, .reset_reg = 0x17, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x01, .led_control_register_base = 0x13, .enable_bits_per_led_control_register = 6, @@ -127,6 +145,7 @@ static const struct is31fl32xx_chipdef is31fl3216_cdef = { .pwm_update_reg = 0xB0, .global_control_reg = IS31FL32XX_REG_NONE, .reset_reg = IS31FL32XX_REG_NONE, + .output_frequency_setting_reg = IS31FL32XX_REG_NONE, .pwm_register_base = 0x10, .pwm_registers_reversed = true, .led_control_register_base = 0x01, @@ -364,10 +383,22 @@ 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; + const struct is31fl32xx_chipdef *cdef = priv->cdef; int ret = 0; - for_each_available_child_of_node(dev_of_node(dev), child) { + if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) && + of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) { + + ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg, + IS31FL32XX_PWM_FREQUENCY_22KHZ); + + if (ret) { + dev_err(dev, "Failed to write output PWM frequency register\n"); + return ret; + } + } + + for_each_available_child_of_node_scoped(dev_of_node(dev), child) { struct led_init_data init_data = {}; struct is31fl32xx_led_data *led_data = &priv->leds[priv->num_leds]; @@ -377,7 +408,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, @@ -386,8 +417,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); @@ -397,26 +427,23 @@ 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[] = { - { .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, }, - { .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, }, - { .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, }, - { .compatible = "si-en,sn3218", .data = &is31fl3218_cdef, }, - { .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, }, - { .compatible = "si-en,sn3216", .data = &is31fl3216_cdef, }, + { .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, }, + { .compatible = "issi,is31fl3236a", .data = &is31fl3236a_cdef, }, + { .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, }, + { .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, }, + { .compatible = "si-en,sn3218", .data = &is31fl3218_cdef, }, + { .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, }, + { .compatible = "si-en,sn3216", .data = &is31fl3216_cdef, }, {}, }; @@ -473,6 +500,7 @@ static void is31fl32xx_remove(struct i2c_client *client) */ static const struct i2c_device_id is31fl32xx_id[] = { { "is31fl3236" }, + { "is31fl3236a" }, { "is31fl3235" }, { "is31fl3218" }, { "sn3218" }, diff --git a/drivers/leds/leds-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 bcd414eb4724..45795f2a1042 100644 --- a/drivers/leds/leds-lm3533.c +++ b/drivers/leds/leds-lm3533.c @@ -718,7 +718,7 @@ err_deregister: return ret; } -static int lm3533_led_remove(struct platform_device *pdev) +static void lm3533_led_remove(struct platform_device *pdev) { struct lm3533_led *led = platform_get_drvdata(pdev); @@ -726,8 +726,6 @@ static int lm3533_led_remove(struct platform_device *pdev) lm3533_ctrlbank_disable(&led->cb); led_classdev_unregister(&led->cdev); - - return 0; } static void lm3533_led_shutdown(struct platform_device *pdev) 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-lm3692x.c b/drivers/leds/leds-lm3692x.c index f8ad61e47a19..c319ff4d70b2 100644 --- a/drivers/leds/leds-lm3692x.c +++ b/drivers/leds/leds-lm3692x.c @@ -139,7 +139,7 @@ static const struct regmap_config lm3692x_regmap_config = { .max_register = LM3692X_FAULT_FLAGS, .reg_defaults = lm3692x_reg_defs, .num_reg_defaults = ARRAY_SIZE(lm3692x_reg_defs), - .cache_type = REGCACHE_RBTREE, + .cache_type = REGCACHE_MAPLE, }; static int lm3692x_fault_check(struct lm3692x_led *led) diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c index cfb8ac220db6..7ad232780a31 100644 --- a/drivers/leds/leds-lm3697.c +++ b/drivers/leds/leds-lm3697.c @@ -89,7 +89,7 @@ struct lm3697 { int bank_cfg; int num_banks; - struct lm3697_led leds[]; + struct lm3697_led leds[] __counted_by(num_banks); }; static const struct reg_default lm3697_reg_defs[] = { @@ -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 3bd55652a706..17219a582704 100644 --- a/drivers/leds/leds-lp3952.c +++ b/drivers/leds/leds-lp3952.c @@ -101,7 +101,7 @@ static int lp3952_get_label(struct device *dev, const char *label, char *dest) if (ret) return ret; - strncpy(dest, str, LP3952_LABEL_MAX_LEN); + strscpy(dest, str, LP3952_LABEL_MAX_LEN); return 0; } @@ -204,9 +204,16 @@ static const struct regmap_config lp3952_regmap = { .reg_bits = 8, .val_bits = 8, .max_register = REG_MAX, - .cache_type = REGCACHE_RBTREE, + .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..e2a9c8592953 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 @@ -52,11 +50,17 @@ #define LP50XX_SW_RESET 0xff #define LP50XX_CHIP_EN BIT(6) +#define LP50XX_CHIP_DISABLE 0x00 +#define LP50XX_START_TIME_US 500 +#define LP50XX_RESET_TIME_US 3 + +#define LP50XX_EN_GPIO_LOW 0 +#define LP50XX_EN_GPIO_HIGH 1 /* There are 3 LED outputs per bank */ #define LP50XX_LEDS_PER_MODULE 3 -#define LP5009_MAX_LED_MODULES 2 +#define LP5009_MAX_LED_MODULES 3 #define LP5012_MAX_LED_MODULES 4 #define LP5018_MAX_LED_MODULES 6 #define LP5024_MAX_LED_MODULES 8 @@ -265,7 +269,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 +282,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 +292,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[]; @@ -346,17 +347,15 @@ out: return ret; } -static int lp50xx_set_banks(struct lp50xx *priv, u32 led_banks[]) +static int lp50xx_set_banks(struct lp50xx *priv, u32 led_banks[], int num_leds) { u8 led_config_lo, led_config_hi; u32 bank_enable_mask = 0; int ret; int i; - for (i = 0; i < priv->chip_info->max_modules; i++) { - if (led_banks[i]) - bank_enable_mask |= (1 << led_banks[i]); - } + for (i = 0; i < num_leds; i++) + bank_enable_mask |= (1 << led_banks[i]); led_config_lo = bank_enable_mask; led_config_hi = bank_enable_mask >> 8; @@ -376,19 +375,42 @@ static int lp50xx_reset(struct lp50xx *priv) return regmap_write(priv->regmap, priv->chip_info->reset_reg, LP50XX_SW_RESET); } -static int lp50xx_enable_disable(struct lp50xx *priv, int enable_disable) +static int lp50xx_enable(struct lp50xx *priv) { int ret; - ret = gpiod_direction_output(priv->enable_gpio, enable_disable); + if (priv->enable_gpio) { + ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_HIGH); + if (ret) + return ret; + + udelay(LP50XX_START_TIME_US); + } + + ret = lp50xx_reset(priv); if (ret) return ret; - if (enable_disable) - return regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_EN); - else - return regmap_write(priv->regmap, LP50XX_DEV_CFG0, 0); + return regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_EN); +} +static int lp50xx_disable(struct lp50xx *priv) +{ + int ret; + + ret = regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_DISABLE); + if (ret) + return ret; + + if (priv->enable_gpio) { + ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_LOW); + if (ret) + return ret; + + udelay(LP50XX_RESET_TIME_US); + } + + return 0; } static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv, @@ -404,15 +426,13 @@ 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"); return ret; } - ret = lp50xx_set_banks(priv, led_banks); + ret = lp50xx_set_banks(priv, led_banks, num_leds); if (ret) { dev_err(priv->dev, "Cannot setup banked LEDs\n"); return ret; @@ -439,7 +459,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; @@ -455,21 +474,25 @@ static int lp50xx_probe_dt(struct lp50xx *priv) return dev_err_probe(priv->dev, PTR_ERR(priv->enable_gpio), "Failed to get enable GPIO\n"); + ret = lp50xx_enable(priv); + if (ret) + return ret; + priv->regulator = devm_regulator_get(priv->dev, "vled"); 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,21 +503,28 @@ 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) { + int multi_index; ret = fwnode_property_read_u32(led_node, "color", &color_id); if (ret) { fwnode_handle_put(led_node); dev_err(priv->dev, "Cannot read color\n"); - goto child_out; + return ret; + } + ret = fwnode_property_read_u32(led_node, "reg", &multi_index); + if (ret != 0) { + dev_err(priv->dev, "reg must be set\n"); + return -EINVAL; + } else if (multi_index >= LP50XX_LEDS_PER_MODULE) { + dev_err(priv->dev, "reg %i out of range\n", multi_index); + return -EINVAL; } - mc_led_info[num_colors].color_index = color_id; + mc_led_info[multi_index].color_index = color_id; num_colors++; } @@ -509,16 +539,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) @@ -552,14 +578,6 @@ static int lp50xx_probe(struct i2c_client *client) return ret; } - ret = lp50xx_reset(led); - if (ret) - return ret; - - ret = lp50xx_enable_disable(led, 1); - if (ret) - return ret; - return lp50xx_probe_dt(led); } @@ -568,7 +586,7 @@ static void lp50xx_remove(struct i2c_client *client) struct lp50xx *led = i2c_get_clientdata(client); int ret; - ret = lp50xx_enable_disable(led, 0); + ret = lp50xx_disable(led); if (ret) dev_err(led->dev, "Failed to disable chip\n"); diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 030c040fdf6d..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) @@ -301,6 +129,8 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip) /* Set all PWMs to direct control mode */ ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + if (ret) + return ret; /* Update configuration for the clock setting */ val = LP5521_DEFAULT_CFG; @@ -348,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) @@ -464,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[] = { @@ -497,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, @@ -505,110 +233,45 @@ 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); -#ifdef CONFIG_OF static const struct of_device_id of_lp5521_leds_match[] = { - { .compatible = "national,lp5521", }, + { .compatible = "national,lp5521", .data = &lp5521_cfg, }, {}, }; MODULE_DEVICE_TABLE(of, of_lp5521_leds_match); -#endif + static struct i2c_driver lp5521_driver = { .driver = { .name = "lp5521", - .of_match_table = of_match_ptr(of_lp5521_leds_match), + .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 daa6a165fba6..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,114 +319,55 @@ 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); -#ifdef CONFIG_OF 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, }, {}, }; MODULE_DEVICE_TABLE(of, of_lp5523_leds_match); -#endif static struct i2c_driver lp5523_driver = { .driver = { .name = "lp5523x", - .of_match_table = of_match_ptr(of_lp5523_leds_match), + .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 4565cc12cea8..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,108 +382,38 @@ 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); -#ifdef CONFIG_OF static const struct of_device_id of_lp5562_leds_match[] = { - { .compatible = "ti,lp5562", }, + { .compatible = "ti,lp5562", .data = &lp5562_cfg, }, {}, }; MODULE_DEVICE_TABLE(of, of_lp5562_leds_match); -#endif static struct i2c_driver lp5562_driver = { .driver = { .name = "lp5562", - .of_match_table = of_match_ptr(of_lp5562_leds_match), + .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 77bb26906ea6..fd447eb7eb15 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 - 1); + + 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; @@ -442,9 +1010,9 @@ int lp55xx_init_device(struct lp55xx_chip *chip) gpiod_direction_output(pdata->enable_gpiod, 0); gpiod_set_consumer_name(pdata->enable_gpiod, "LP55xx enable"); - gpiod_set_value(pdata->enable_gpiod, 0); + gpiod_set_value_cansleep(pdata->enable_gpiod, 0); usleep_range(1000, 2000); /* Keep enable down at least 1ms */ - gpiod_set_value(pdata->enable_gpiod, 1); + gpiod_set_value_cansleep(pdata->enable_gpiod, 1); usleep_range(1000, 2000); /* 500us abs min. */ } @@ -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 f11886aa8965..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,109 +111,44 @@ 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); -#ifdef CONFIG_OF static const struct of_device_id of_lp8501_leds_match[] = { - { .compatible = "ti,lp8501", }, + { .compatible = "ti,lp8501", .data = &lp8501_cfg, }, {}, }; MODULE_DEVICE_TABLE(of, of_lp8501_leds_match); -#endif static struct i2c_driver lp8501_driver = { .driver = { .name = "lp8501", - .of_match_table = of_match_ptr(of_lp8501_leds_match), + .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..0962c00c215a 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -90,8 +90,6 @@ * @led_dev: led class device pointer * @regmap: Devices register map * @eeprom_regmap: EEPROM register map - * @enable_gpio: VDDIO/EN gpio to enable communication interface - * @regulator: LED supply regulator pointer */ struct lp8860_led { struct mutex lock; @@ -99,16 +97,9 @@ struct lp8860_led { struct led_classdev led_dev; struct regmap *regmap; struct regmap *eeprom_regmap; - struct gpio_desc *enable_gpio; - struct regulator *regulator; -}; - -struct lp8860_eeprom_reg { - uint8_t reg; - uint8_t value; }; -static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = { +static const struct reg_sequence lp8860_eeprom_disp_regs[] = { { LP8860_EEPROM_REG_0, 0xed }, { LP8860_EEPROM_REG_1, 0xdf }, { LP8860_EEPROM_REG_2, 0xdc }, @@ -136,43 +127,29 @@ static struct lp8860_eeprom_reg lp8860_eeprom_disp_regs[] = { { LP8860_EEPROM_REG_24, 0x3E }, }; -static int lp8860_unlock_eeprom(struct lp8860_led *led, int lock) +static int lp8860_unlock_eeprom(struct lp8860_led *led) { int ret; - mutex_lock(&led->lock); - - if (lock == LP8860_UNLOCK_EEPROM) { - ret = regmap_write(led->regmap, - LP8860_EEPROM_UNLOCK, - LP8860_EEPROM_CODE_1); - if (ret) { - dev_err(&led->client->dev, "EEPROM Unlock failed\n"); - goto out; - } - - ret = regmap_write(led->regmap, - LP8860_EEPROM_UNLOCK, - LP8860_EEPROM_CODE_2); - if (ret) { - dev_err(&led->client->dev, "EEPROM Unlock failed\n"); - goto out; - } - ret = regmap_write(led->regmap, - LP8860_EEPROM_UNLOCK, - LP8860_EEPROM_CODE_3); - if (ret) { - dev_err(&led->client->dev, "EEPROM Unlock failed\n"); - goto out; - } - } else { - ret = regmap_write(led->regmap, - LP8860_EEPROM_UNLOCK, - LP8860_LOCK_EEPROM); + guard(mutex)(&led->lock); + + ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_1); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + return ret; + } + + ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_2); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + return ret; + } + ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_3); + if (ret) { + dev_err(&led->client->dev, "EEPROM Unlock failed\n"); + return ret; } -out: - mutex_unlock(&led->lock); return ret; } @@ -209,47 +186,35 @@ static int lp8860_brightness_set(struct led_classdev *led_cdev, int disp_brightness = brt_val * 255; int ret; - mutex_lock(&led->lock); + guard(mutex)(&led->lock); ret = lp8860_fault_check(led); if (ret) { dev_err(&led->client->dev, "Cannot read/clear faults\n"); - goto out; + return ret; } ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_MSB, (disp_brightness & 0xff00) >> 8); if (ret) { dev_err(&led->client->dev, "Cannot write CL1 MSB\n"); - goto out; + return ret; } ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_LSB, disp_brightness & 0xff); if (ret) { dev_err(&led->client->dev, "Cannot write CL1 LSB\n"); - goto out; + return ret; } -out: - mutex_unlock(&led->lock); - return ret; + + return 0; } static int lp8860_init(struct lp8860_led *led) { unsigned int read_buf; - int ret, i, reg_count; - - if (led->regulator) { - ret = regulator_enable(led->regulator); - if (ret) { - dev_err(&led->client->dev, - "Failed to enable regulator\n"); - return ret; - } - } - - gpiod_direction_output(led->enable_gpio, 1); + int ret, reg_count; ret = lp8860_fault_check(led); if (ret) @@ -259,24 +224,20 @@ static int lp8860_init(struct lp8860_led *led) if (ret) goto out; - ret = lp8860_unlock_eeprom(led, LP8860_UNLOCK_EEPROM); + ret = lp8860_unlock_eeprom(led); if (ret) { dev_err(&led->client->dev, "Failed unlocking EEPROM\n"); goto out; } - reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]); - for (i = 0; i < reg_count; i++) { - ret = regmap_write(led->eeprom_regmap, - lp8860_eeprom_disp_regs[i].reg, - lp8860_eeprom_disp_regs[i].value); - if (ret) { - dev_err(&led->client->dev, "Failed writing EEPROM\n"); - goto out; - } + reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs); + ret = regmap_multi_reg_write(led->eeprom_regmap, lp8860_eeprom_disp_regs, reg_count); + if (ret) { + dev_err(&led->client->dev, "Failed writing EEPROM\n"); + goto out; } - ret = lp8860_unlock_eeprom(led, LP8860_LOCK_EEPROM); + ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_LOCK_EEPROM); if (ret) goto out; @@ -291,75 +252,14 @@ static int lp8860_init(struct lp8860_led *led) return ret; out: - if (ret) - gpiod_direction_output(led->enable_gpio, 0); - - if (led->regulator) { - ret = regulator_disable(led->regulator); - if (ret) - dev_err(&led->client->dev, - "Failed to disable regulator\n"); - } - return ret; } -static const struct reg_default lp8860_reg_defs[] = { - { LP8860_DISP_CL1_BRT_MSB, 0x00}, - { LP8860_DISP_CL1_BRT_LSB, 0x00}, - { LP8860_DISP_CL1_CURR_MSB, 0x00}, - { LP8860_DISP_CL1_CURR_LSB, 0x00}, - { LP8860_CL2_BRT_MSB, 0x00}, - { LP8860_CL2_BRT_LSB, 0x00}, - { LP8860_CL2_CURRENT, 0x00}, - { LP8860_CL3_BRT_MSB, 0x00}, - { LP8860_CL3_BRT_LSB, 0x00}, - { LP8860_CL3_CURRENT, 0x00}, - { LP8860_CL4_BRT_MSB, 0x00}, - { LP8860_CL4_BRT_LSB, 0x00}, - { LP8860_CL4_CURRENT, 0x00}, - { LP8860_CONFIG, 0x00}, - { LP8860_FAULT_CLEAR, 0x00}, - { LP8860_EEPROM_CNTRL, 0x80}, - { LP8860_EEPROM_UNLOCK, 0x00}, -}; - static const struct regmap_config lp8860_regmap_config = { .reg_bits = 8, .val_bits = 8, .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[] = { - { LP8860_EEPROM_REG_0, 0x00 }, - { LP8860_EEPROM_REG_1, 0x00 }, - { LP8860_EEPROM_REG_2, 0x00 }, - { LP8860_EEPROM_REG_3, 0x00 }, - { LP8860_EEPROM_REG_4, 0x00 }, - { LP8860_EEPROM_REG_5, 0x00 }, - { LP8860_EEPROM_REG_6, 0x00 }, - { LP8860_EEPROM_REG_7, 0x00 }, - { LP8860_EEPROM_REG_8, 0x00 }, - { LP8860_EEPROM_REG_9, 0x00 }, - { LP8860_EEPROM_REG_10, 0x00 }, - { LP8860_EEPROM_REG_11, 0x00 }, - { LP8860_EEPROM_REG_12, 0x00 }, - { LP8860_EEPROM_REG_13, 0x00 }, - { LP8860_EEPROM_REG_14, 0x00 }, - { LP8860_EEPROM_REG_15, 0x00 }, - { LP8860_EEPROM_REG_16, 0x00 }, - { LP8860_EEPROM_REG_17, 0x00 }, - { LP8860_EEPROM_REG_18, 0x00 }, - { LP8860_EEPROM_REG_19, 0x00 }, - { LP8860_EEPROM_REG_20, 0x00 }, - { LP8860_EEPROM_REG_21, 0x00 }, - { LP8860_EEPROM_REG_22, 0x00 }, - { LP8860_EEPROM_REG_23, 0x00 }, - { LP8860_EEPROM_REG_24, 0x00 }, }; static const struct regmap_config lp8860_eeprom_regmap_config = { @@ -367,11 +267,15 @@ static const struct regmap_config lp8860_eeprom_regmap_config = { .val_bits = 8, .max_register = LP8860_EEPROM_REG_24, - .reg_defaults = lp8860_eeprom_defs, - .num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs), - .cache_type = REGCACHE_NONE, }; +static void lp8860_disable_gpio(void *data) +{ + struct gpio_desc *gpio = data; + + gpiod_set_value(gpio, 0); +} + static int lp8860_probe(struct i2c_client *client) { int ret; @@ -379,6 +283,7 @@ static int lp8860_probe(struct i2c_client *client) 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) @@ -388,24 +293,23 @@ static int lp8860_probe(struct i2c_client *client) if (!child_node) return -EINVAL; - led->enable_gpio = devm_gpiod_get_optional(&client->dev, - "enable", GPIOD_OUT_LOW); - if (IS_ERR(led->enable_gpio)) { - ret = PTR_ERR(led->enable_gpio); - dev_err(&client->dev, "Failed to get enable gpio: %d\n", ret); - return ret; - } + enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW); + if (IS_ERR(enable_gpio)) + return dev_err_probe(&client->dev, PTR_ERR(enable_gpio), + "Failed to get enable GPIO\n"); + devm_add_action_or_reset(&client->dev, lp8860_disable_gpio, enable_gpio); - led->regulator = devm_regulator_get(&client->dev, "vled"); - if (IS_ERR(led->regulator)) - led->regulator = NULL; + 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"); led->client = client; led->led_dev.brightness_set_blocking = lp8860_brightness_set; - mutex_init(&led->lock); - - i2c_set_clientdata(client, led); + ret = devm_mutex_init(&client->dev, &led->lock); + if (ret) + return dev_err_probe(&client->dev, ret, "Failed to initialize lock\n"); led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config); if (IS_ERR(led->regmap)) { @@ -441,25 +345,8 @@ static int lp8860_probe(struct i2c_client *client) return 0; } -static void lp8860_remove(struct i2c_client *client) -{ - struct lp8860_led *led = i2c_get_clientdata(client); - int ret; - - gpiod_direction_output(led->enable_gpio, 0); - - if (led->regulator) { - ret = regulator_disable(led->regulator); - if (ret) - dev_err(&led->client->dev, - "Failed to disable regulator\n"); - } - - mutex_destroy(&led->lock); -} - static const struct i2c_device_id lp8860_id[] = { - { "lp8860", 0 }, + { "lp8860" }, { } }; MODULE_DEVICE_TABLE(i2c, lp8860_id); @@ -476,7 +363,6 @@ static struct i2c_driver lp8860_driver = { .of_match_table = of_lp8860_leds_match, }, .probe = lp8860_probe, - .remove = lp8860_remove, .id_table = lp8860_id, }; module_i2c_driver(lp8860_driver); 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 new file mode 100644 index 000000000000..a1e91a06249c --- /dev/null +++ b/drivers/leds/leds-max5970.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Device driver for leds in MAX5970 and MAX5978 IC + * + * Copyright (c) 2022 9elements GmbH + * + * Author: Patrick Rudolph <patrick.rudolph@9elements.com> + */ + +#include <linux/bits.h> +#include <linux/container_of.h> +#include <linux/device.h> +#include <linux/leds.h> +#include <linux/mfd/max5970.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/property.h> +#include <linux/regmap.h> + +#define ldev_to_maxled(c) container_of(c, struct max5970_led, cdev) + +struct max5970_led { + struct device *dev; + struct regmap *regmap; + struct led_classdev cdev; + unsigned int index; +}; + +static int max5970_led_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct max5970_led *ddata = ldev_to_maxled(cdev); + int ret, val; + + /* Set/clear corresponding bit for given led index */ + val = !brightness ? BIT(ddata->index) : 0; + + ret = regmap_update_bits(ddata->regmap, MAX5970_REG_LED_FLASH, BIT(ddata->index), val); + if (ret < 0) + dev_err(cdev->dev, "failed to set brightness %d", ret); + + return ret; +} + +static int max5970_led_probe(struct platform_device *pdev) +{ + struct fwnode_handle *child; + struct device *dev = &pdev->dev; + struct regmap *regmap; + struct max5970_led *ddata; + int ret = -ENODEV; + + regmap = dev_get_regmap(dev->parent, NULL); + if (!regmap) + return -ENODEV; + + struct fwnode_handle *led_node __free(fwnode_handle) = + device_get_named_child_node(dev->parent, "leds"); + if (!led_node) + return -ENODEV; + + fwnode_for_each_child_node(led_node, child) { + u32 reg; + + if (fwnode_property_read_u32(child, "reg", ®)) + continue; + + if (reg >= MAX5970_NUM_LEDS) { + dev_err_probe(dev, -EINVAL, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS); + continue; + } + + ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) { + fwnode_handle_put(child); + return -ENOMEM; + } + + ddata->index = reg; + ddata->regmap = regmap; + ddata->dev = dev; + + if (fwnode_property_read_string(child, "label", &ddata->cdev.name)) + ddata->cdev.name = fwnode_get_name(child); + + ddata->cdev.max_brightness = 1; + ddata->cdev.brightness_set_blocking = max5970_led_set_brightness; + ddata->cdev.default_trigger = "none"; + + ret = devm_led_classdev_register(dev, &ddata->cdev); + if (ret < 0) { + fwnode_handle_put(child); + return dev_err_probe(dev, ret, "Failed to initialize LED %u\n", reg); + } + } + + return ret; +} + +static struct platform_driver max5970_led_driver = { + .driver = { + .name = "max5970-led", + }, + .probe = max5970_led_probe, +}; +module_platform_driver(max5970_led_driver); + +MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>"); +MODULE_AUTHOR("Naresh Solanki <Naresh.Solanki@9elements.com>"); +MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver"); +MODULE_LICENSE("GPL"); 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..1e2054c1bf80 --- /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) + return -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_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 675502c15c2b..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; + if (i <= 0) + return ERR_PTR(-ENODATA); -out_node_put: - of_node_put(parent); - - return ret ? ERR_PTR(ret) : pdata; + return pdata; } #else static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( @@ -261,15 +259,13 @@ static int __init mc13xxx_led_probe(struct platform_device *pdev) return ret; } -static int mc13xxx_led_remove(struct platform_device *pdev) +static void mc13xxx_led_remove(struct platform_device *pdev) { struct mc13xxx_leds *leds = platform_get_drvdata(pdev); int i; for (i = 0; i < leds->num_leds; i++) led_classdev_unregister(&leds->led[i].cdev); - - return 0; } static const struct mc13xxx_led_devtype mc13783_led_devtype = { diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c index 1355c84a2919..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> @@ -77,7 +76,7 @@ struct mlxcpld_param { /** * struct mlxcpld_led_priv - LED private data: - * @cled: LED class device instance + * @cdev: LED class device instance * @param: LED CPLD access parameters **/ struct mlxcpld_led_priv { diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c index b7855c93bd72..1b70de72376c 100644 --- a/drivers/leds/leds-mlxreg.c +++ b/drivers/leds/leds-mlxreg.c @@ -8,7 +8,6 @@ #include <linux/io.h> #include <linux/leds.h> #include <linux/module.h> -#include <linux/of_device.h> #include <linux/platform_data/mlxreg.h> #include <linux/platform_device.h> #include <linux/regmap.h> @@ -30,7 +29,6 @@ * @data: led configuration data; * @led_cdev: led class data; * @base_color: base led color (other colors have constant offset from base); - * @led_data: led data; * @data_parent: pointer to private device control data of parent; * @led_cdev_name: class device name */ @@ -258,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) { @@ -269,28 +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 int mlxreg_led_remove(struct platform_device *pdev) -{ - struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev); - - mutex_destroy(&priv->access_lock); - - return 0; -} - static struct platform_driver mlxreg_led_driver = { .driver = { .name = "leds-mlxreg", }, .probe = mlxreg_led_probe, - .remove = mlxreg_led_remove, }; module_platform_driver(mlxreg_led_driver); diff --git a/drivers/leds/leds-mt6323.c b/drivers/leds/leds-mt6323.c index 24f35bdb55fb..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,18 +617,14 @@ 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 int mt6323_led_remove(struct platform_device *pdev) +static void mt6323_led_remove(struct platform_device *pdev) { struct mt6323_leds *leds = platform_get_drvdata(pdev); const struct mt6323_regs *regs = leds->pdata->regs; @@ -647,8 +639,6 @@ static int mt6323_led_remove(struct platform_device *pdev) RG_DRV_32K_CK_PDN); mutex_destroy(&leds->lock); - - return 0; } static const struct mt6323_regs mt6323_registers = { diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index 77213b79f84d..99df46f2d9f5 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -364,6 +364,9 @@ static int netxbig_gpio_ext_get(struct device *dev, if (!addr) return -ENOMEM; + gpio_ext->addr = addr; + gpio_ext->num_addr = 0; + /* * We cannot use devm_ managed resources with these GPIO descriptors * since they are associated with the "GPIO extension device" which @@ -375,45 +378,58 @@ static int netxbig_gpio_ext_get(struct device *dev, gpiod = gpiod_get_index(gpio_ext_dev, "addr", i, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + goto err_set_code; gpiod_set_consumer_name(gpiod, "GPIO extension addr"); addr[i] = gpiod; + gpio_ext->num_addr++; } - gpio_ext->addr = addr; - gpio_ext->num_addr = num_addr; ret = gpiod_count(gpio_ext_dev, "data"); if (ret < 0) { dev_err(dev, "Failed to count GPIOs in DT property data-gpios\n"); - return ret; + goto err_free_addr; } num_data = ret; data = devm_kcalloc(dev, num_data, sizeof(*data), GFP_KERNEL); - if (!data) - return -ENOMEM; + if (!data) { + ret = -ENOMEM; + goto err_free_addr; + } + + gpio_ext->data = data; + gpio_ext->num_data = 0; for (i = 0; i < num_data; i++) { gpiod = gpiod_get_index(gpio_ext_dev, "data", i, GPIOD_OUT_LOW); if (IS_ERR(gpiod)) - return PTR_ERR(gpiod); + goto err_free_data; gpiod_set_consumer_name(gpiod, "GPIO extension data"); data[i] = gpiod; + gpio_ext->num_data++; } - gpio_ext->data = data; - gpio_ext->num_data = num_data; gpiod = gpiod_get(gpio_ext_dev, "enable", GPIOD_OUT_LOW); if (IS_ERR(gpiod)) { dev_err(dev, "Failed to get GPIO from DT property enable-gpio\n"); - return PTR_ERR(gpiod); + goto err_free_data; } gpiod_set_consumer_name(gpiod, "GPIO extension enable"); gpio_ext->enable = gpiod; return devm_add_action_or_reset(dev, netxbig_gpio_ext_remove, gpio_ext); + +err_free_data: + for (i = 0; i < gpio_ext->num_data; i++) + gpiod_put(gpio_ext->data[i]); +err_set_code: + ret = PTR_ERR(gpiod); +err_free_addr: + for (i = 0; i < gpio_ext->num_addr; i++) + gpiod_put(gpio_ext->addr[i]); + return ret; } static int netxbig_leds_get_of_pdata(struct device *dev, @@ -423,7 +439,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 +455,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 +523,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 +531,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 +549,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 +558,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 +576,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 +599,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 f196f52eec1e..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,29 +188,17 @@ static int nic78bx_probe(struct platform_device *pdev) return ret; } -static int 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); - - return 0; -} - 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 = 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 1677d66d8b0e..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; @@ -247,16 +246,14 @@ static int ns2_led_probe(struct platform_device *pdev) if (!count) return -ENODEV; - leds = devm_kzalloc(dev, array_size(sizeof(*leds), count), GFP_KERNEL); + leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL); 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 8b5c62083e50..0344189bb991 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -18,7 +18,6 @@ #include <linux/leds-pca9532.h> #include <linux/gpio/driver.h> #include <linux/of.h> -#include <linux/of_device.h> /* m = num_leds*/ #define PCA9532_REG_INPUT(i) ((i) >> 3) @@ -30,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 { @@ -46,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); @@ -182,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; @@ -230,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); @@ -247,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); } @@ -278,7 +318,8 @@ static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset) return -EBUSY; } -static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val) +static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset, + int val) { struct pca9532_data *data = gpiochip_get_data(gc); struct pca9532_led *led = &data->leds[offset]; @@ -289,6 +330,8 @@ static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int va led->state = PCA9532_OFF; pca9532_setled(led); + + return 0; } static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset) @@ -311,9 +354,7 @@ static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset) static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val) { - pca9532_gpio_set_value(gc, offset, val); - - return 0; + return pca9532_gpio_set_value(gc, offset, val); } #endif /* CONFIG_LEDS_PCA9532_GPIO */ @@ -360,6 +401,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]; @@ -394,6 +436,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); @@ -462,7 +506,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; @@ -476,12 +519,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; @@ -494,10 +537,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 b10e1ef38db0..2007fe6217ec 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, @@ -71,55 +73,53 @@ enum pca955x_type { }; struct pca955x_chipdef { - int bits; + u8 bits; u8 slv_addr; /* 7-bit slave address mask */ int slv_addr_shift; /* Number of bits to ignore */ + int blink_div; /* PSC divider */ }; -static struct pca955x_chipdef pca955x_chipdefs[] = { +static const struct pca955x_chipdef pca955x_chipdefs[] = { [pca9550] = { .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, }, }; -static const struct i2c_device_id pca955x_id[] = { - { "pca9550", pca9550 }, - { "pca9551", pca9551 }, - { "pca9552", pca9552 }, - { "ibm-pca9552", ibm_pca9552 }, - { "pca9553", pca9553 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, pca955x_id); - struct pca955x { struct mutex lock; struct pca955x_led *leds; - struct pca955x_chipdef *chipdef; + 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 @@ -134,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 u8 pca955x_num_input_regs(u8 bits) { return (bits + 7) / 8; } +/* 4 bits per LED selector register */ +static inline u8 pca955x_num_led_regs(u8 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). @@ -155,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; } @@ -179,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; } @@ -196,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; } @@ -213,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; @@ -247,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; @@ -283,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, 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; } - ret = pca955x_write_ls(pca955x->client, chip_ls, ls); + 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); @@ -379,10 +495,10 @@ static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset, return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW); } -static void pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset, - int val) +static int pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset, + int val) { - pca955x_set_value(gc, offset, val); + return pca955x_set_value(gc, offset, val); } static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset) @@ -415,7 +531,7 @@ static int pca955x_gpio_direction_output(struct gpio_chip *gc, #endif /* CONFIG_LEDS_PCA955X_GPIO */ static struct pca955x_platform_data * -pca955x_get_pdata(struct i2c_client *client, struct pca955x_chipdef *chip) +pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip) { struct pca955x_platform_data *pdata; struct pca955x_led *led; @@ -457,47 +573,27 @@ pca955x_get_pdata(struct i2c_client *client, struct pca955x_chipdef *chip) return pdata; } -static const struct of_device_id of_pca955x_match[] = { - { .compatible = "nxp,pca9550", .data = (void *)pca9550 }, - { .compatible = "nxp,pca9551", .data = (void *)pca9551 }, - { .compatible = "nxp,pca9552", .data = (void *)pca9552 }, - { .compatible = "ibm,pca9552", .data = (void *)ibm_pca9552 }, - { .compatible = "nxp,pca9553", .data = (void *)pca9553 }, - {}, -}; -MODULE_DEVICE_TABLE(of, of_pca955x_match); - static int pca955x_probe(struct i2c_client *client) { struct pca955x *pca955x; struct pca955x_led *pca955x_led; - struct pca955x_chipdef *chip; + const struct pca955x_chipdef *chip; struct led_classdev *led; struct led_init_data init_data; struct i2c_adapter *adapter; - int i, err; + u8 i, nls, psc0; + u8 ls1[4]; + u8 ls2[4]; struct pca955x_platform_data *pdata; + bool keep_psc0 = false; bool set_default_label = false; - bool keep_pwm = false; - char default_label[8]; - enum pca955x_type chip_type; - const void *md = device_get_match_data(&client->dev); - - if (md) { - chip_type = (enum pca955x_type)md; - } else { - const struct i2c_device_id *id = i2c_match_id(pca955x_id, - client); + char default_label[4]; + int bit, err, reg; - if (id) { - chip_type = (enum pca955x_type)id->driver_data; - } else { - dev_err(&client->dev, "unknown chip\n"); - return -ENODEV; - } - } + chip = i2c_get_match_data(client); + if (!chip) + return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n"); - chip = &pca955x_chipdefs[chip_type]; adapter = client->adapter; pdata = dev_get_platdata(&client->dev); if (!pdata) { @@ -514,16 +610,15 @@ static int pca955x_probe(struct i2c_client *client) return -ENODEV; } - dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at " - "slave address 0x%02x\n", client->name, chip->bits, - client->addr); + dev_info(&client->dev, "Using %s %u-bit LED driver at slave address 0x%02x\n", + client->name, chip->bits, client->addr); if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -EIO; if (pdata->num_leds != chip->bits) { dev_err(&client->dev, - "board info claims %d LEDs on a %d-bit chip\n", + "board info claims %d LEDs on a %u-bit chip\n", pdata->num_leds, chip->bits); return -ENODEV; } @@ -542,10 +637,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; @@ -557,18 +664,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; @@ -584,8 +693,7 @@ static int pca955x_probe(struct i2c_client *client) } if (set_default_label) { - snprintf(default_label, sizeof(default_label), - "%d", i); + snprintf(default_label, sizeof(default_label), "%hhu", i); init_data.default_label = default_label; } else { init_data.default_label = NULL; @@ -597,39 +705,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; @@ -663,6 +763,26 @@ static int pca955x_probe(struct i2c_client *client) return 0; } +static const struct i2c_device_id pca955x_id[] = { + { "pca9550", (kernel_ulong_t)&pca955x_chipdefs[pca9550] }, + { "pca9551", (kernel_ulong_t)&pca955x_chipdefs[pca9551] }, + { "pca9552", (kernel_ulong_t)&pca955x_chipdefs[pca9552] }, + { "ibm-pca9552", (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] }, + { "pca9553", (kernel_ulong_t)&pca955x_chipdefs[pca9553] }, + {} +}; +MODULE_DEVICE_TABLE(i2c, pca955x_id); + +static const struct of_device_id of_pca955x_match[] = { + { .compatible = "nxp,pca9550", .data = &pca955x_chipdefs[pca9550] }, + { .compatible = "nxp,pca9551", .data = &pca955x_chipdefs[pca9551] }, + { .compatible = "nxp,pca9552", .data = &pca955x_chipdefs[pca9552] }, + { .compatible = "ibm,pca9552", .data = &pca955x_chipdefs[ibm_pca9552] }, + { .compatible = "nxp,pca9553", .data = &pca955x_chipdefs[pca9553] }, + {} +}; +MODULE_DEVICE_TABLE(of, of_pca955x_match); + static struct i2c_driver pca955x_driver = { .driver = { .name = "leds-pca955x", diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index 47223c850e4b..050e93b04884 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -39,6 +39,7 @@ #define PCA963X_LED_PWM 0x2 /* Controlled through PWM */ #define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ +#define PCA963X_MODE1_SLEEP 0x04 /* Normal mode or Low Power mode, oscillator off */ #define PCA963X_MODE2_OUTDRV 0x04 /* Open-drain or totem pole */ #define PCA963X_MODE2_INVRT 0x10 /* Normal or inverted direction */ #define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */ @@ -305,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; @@ -337,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]; @@ -345,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; @@ -368,18 +367,41 @@ 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) +{ + struct pca963x *chip = dev_get_drvdata(dev); + u8 reg; + + reg = i2c_smbus_read_byte_data(chip->client, PCA963X_MODE1); + reg = reg | BIT(PCA963X_MODE1_SLEEP); + i2c_smbus_write_byte_data(chip->client, PCA963X_MODE1, reg); + + return 0; +} + +static int pca963x_resume(struct device *dev) +{ + struct pca963x *chip = dev_get_drvdata(dev); + u8 reg; + + reg = i2c_smbus_read_byte_data(chip->client, PCA963X_MODE1); + reg = reg & ~BIT(PCA963X_MODE1_SLEEP); + i2c_smbus_write_byte_data(chip->client, PCA963X_MODE1, reg); + + return 0; +} + +static DEFINE_SIMPLE_DEV_PM_OPS(pca963x_pm, pca963x_suspend, pca963x_resume); + static const struct of_device_id of_pca963x_match[] = { { .compatible = "nxp,pca9632", }, { .compatible = "nxp,pca9633", }, @@ -430,6 +452,7 @@ static struct i2c_driver pca963x_driver = { .driver = { .name = "leds-pca963x", .of_match_table = of_pca963x_match, + .pm = pm_sleep_ptr(&pca963x_pm) }, .probe = pca963x_probe, .id_table = pca963x_id, diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c new file mode 100644 index 000000000000..6ad06ce2bf64 --- /dev/null +++ b/drivers/leds/leds-pca995x.c @@ -0,0 +1,218 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * LED driver for PCA995x I2C LED drivers + * + * Copyright 2011 bct electronic GmbH + * Copyright 2013 Qtechnology/AS + * Copyright 2022 NXP + * Copyright 2023 Marek Vasut + */ + +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/property.h> +#include <linux/regmap.h> + +/* Register definition */ +#define PCA995X_MODE1 0x00 +#define PCA995X_MODE2 0x01 +#define PCA995X_LEDOUT0 0x02 + +/* Auto-increment disabled. Normal mode */ +#define PCA995X_MODE1_CFG 0x00 + +/* LED select registers determine the source that drives LED outputs */ +#define PCA995X_LED_OFF 0x0 +#define PCA995X_LED_ON 0x1 +#define PCA995X_LED_PWM_MODE 0x2 +#define PCA995X_LDRX_MASK 0x3 +#define PCA995X_LDRX_BITS 2 + +#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 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; + struct pca995x_chip *chip; +}; + +struct pca995x_chip { + struct regmap *regmap; + struct pca995x_led leds[PCA995X_MAX_OUTPUTS]; + const struct pca995x_chipdef *chipdef; +}; + +static int pca995x_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + 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 = 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); + + switch (brightness) { + case LED_FULL: + return regmap_update_bits(chip->regmap, ledout_addr, + PCA995X_LDRX_MASK << shift, + PCA995X_LED_ON << shift); + case LED_OFF: + return regmap_update_bits(chip->regmap, ledout_addr, + PCA995X_LDRX_MASK << shift, 0); + default: + /* Adjust brightness as per user input by changing individual PWM */ + ret = regmap_write(chip->regmap, pwmout_addr, brightness); + if (ret) + return ret; + + /* + * Change LDRx configuration to individual brightness via PWM. + * LED will stop blinking if it's doing so. + */ + return regmap_update_bits(chip->regmap, ledout_addr, + PCA995X_LDRX_MASK << shift, + PCA995X_LED_PWM_MODE << shift); + } +} + +static const struct regmap_config pca995x_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x49, +}; + +static int pca995x_probe(struct i2c_client *client) +{ + struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 }; + struct device *dev = &client->dev; + const struct pca995x_chipdef *chipdef; + struct pca995x_chip *chip; + struct pca995x_led *led; + int i, j, reg, ret; + + chipdef = device_get_match_data(&client->dev); + + if (!dev_fwnode(dev)) + return -ENODEV; + + chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + 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); + + device_for_each_child_node_scoped(dev, child) { + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret) + return ret; + + if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) + return -EINVAL; + + led = &chip->leds[reg]; + led_fwnodes[reg] = fwnode_handle_get(child); + led->chip = chip; + led->led_no = reg; + led->ldev.brightness_set_blocking = pca995x_brightness_set; + led->ldev.max_brightness = 255; + } + + for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) { + struct led_init_data init_data = {}; + + if (!led_fwnodes[i]) + continue; + + init_data.fwnode = led_fwnodes[i]; + + ret = devm_led_classdev_register_ext(dev, + &chip->leds[i].ldev, + &init_data); + if (ret < 0) { + 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); + } + } + + /* Disable LED all-call address and set normal mode */ + ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG); + if (ret) + return ret; + + /* IREF Output current value for all LEDn outputs */ + 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)&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 = &pca9952_chipdef }, + { .compatible = "nxp,pca9955b", .data = &pca9955b_chipdef }, + { .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef }, + {}, +}; +MODULE_DEVICE_TABLE(of, pca995x_of_match); + +static struct i2c_driver pca995x_driver = { + .driver = { + .name = "leds-pca995x", + .of_match_table = pca995x_of_match, + }, + .probe = pca995x_probe, + .id_table = pca995x_id, +}; +module_i2c_driver(pca995x_driver); + +MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>"); +MODULE_DESCRIPTION("PCA995x LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pm8058.c b/drivers/leds/leds-pm8058.c index b9233f14b646..3f49a5181892 100644 --- a/drivers/leds/leds-pm8058.c +++ b/drivers/leds/leds-pm8058.c @@ -4,7 +4,6 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pm.h> #include <linux/regmap.h> diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c index 743e2cdd0891..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,24 +287,19 @@ 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 */ -static int powernv_led_remove(struct platform_device *pdev) +static void powernv_led_remove(struct platform_device *pdev) { struct powernv_led_common *powernv_led_common; @@ -321,7 +311,6 @@ static int powernv_led_remove(struct platform_device *pdev) mutex_destroy(&powernv_led_common->lock); dev_info(&pdev->dev, "PowerNV led module unregistered\n"); - return 0; } /* Platform driver property match */ @@ -334,7 +323,7 @@ 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, + .probe = powernv_led_probe, .remove = powernv_led_remove, .driver = { .name = "powernv-led-driver", diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 29194cc382af..6c1f2f50ff85 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -9,15 +9,15 @@ * based on leds-gpio.c by Raphael Assenat <raph@8d.com> */ -#include <linux/module.h> +#include <linux/err.h> +#include <linux/gpio/consumer.h> #include <linux/kernel.h> -#include <linux/platform_device.h> -#include <linux/of_platform.h> #include <linux/leds.h> -#include <linux/err.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/slab.h> -#include "leds.h" struct led_pwm { const char *name; @@ -27,6 +27,7 @@ struct led_pwm { }; struct led_pwm_data { + struct gpio_desc *enable_gpio; struct led_classdev cdev; struct pwm_device *pwm; struct pwm_state pwmstate; @@ -52,9 +53,31 @@ static int led_pwm_set(struct led_classdev *led_cdev, if (led_dat->active_low) duty = led_dat->pwmstate.period - duty; + gpiod_set_value_cansleep(led_dat->enable_gpio, !!brightness); + led_dat->pwmstate.duty_cycle = duty; - led_dat->pwmstate.enabled = duty > 0; - return pwm_apply_state(led_dat->pwm, &led_dat->pwmstate); + /* + * 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)) @@ -98,7 +121,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: { @@ -112,6 +136,21 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, break; } + /* + * Claim the GPIO as GPIOD_ASIS and set the value + * later on to honor the different default states + */ + led_data->enable_gpio = devm_fwnode_gpiod_get(dev, fwnode, "enable", GPIOD_ASIS, NULL); + if (IS_ERR(led_data->enable_gpio)) { + if (PTR_ERR(led_data->enable_gpio) == -ENOENT) + /* Enable GPIO is optional */ + led_data->enable_gpio = NULL; + else + return PTR_ERR(led_data->enable_gpio); + } + + gpiod_direction_output(led_data->enable_gpio, !!led_data->cdev.brightness); + ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data); if (ret) { dev_err(dev, "failed to register PWM led for %s: %d\n", @@ -134,21 +173,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 +195,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..6df110e33ac9 --- /dev/null +++ b/drivers/leds/leds-qnap-mcu.c @@ -0,0 +1,392 @@ +// 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 = 0, + QNAP_MCU_USB_LED_OFF = 2, + QNAP_MCU_USB_LED_BLINK = 1, +}; + +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] = 'E' + 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] = 'E' + 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); +} + +enum qnap_mcu_status_led_mode { + QNAP_MCU_STATUS_LED_OFF = 0, + QNAP_MCU_STATUS_LED_ON = 1, + QNAP_MCU_STATUS_LED_BLINK_FAST = 2, /* 500ms / 500ms */ + QNAP_MCU_STATUS_LED_BLINK_SLOW = 3, /* 1s / 1s */ +}; + +struct qnap_mcu_status_led { + struct led_classdev cdev; + struct qnap_mcu_status_led *red; + u8 mode; +}; + +struct qnap_mcu_status { + struct qnap_mcu *mcu; + struct qnap_mcu_status_led red; + struct qnap_mcu_status_led green; +}; + +static inline struct qnap_mcu_status_led *cdev_to_qnap_mcu_status_led(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct qnap_mcu_status_led, cdev); +} + +static inline struct qnap_mcu_status *statusled_to_qnap_mcu_status(struct qnap_mcu_status_led *led) +{ + return container_of(led->red, struct qnap_mcu_status, red); +} + +static u8 qnap_mcu_status_led_encode(struct qnap_mcu_status *status) +{ + if (status->red.mode == QNAP_MCU_STATUS_LED_OFF) { + switch (status->green.mode) { + case QNAP_MCU_STATUS_LED_OFF: + return '9'; + case QNAP_MCU_STATUS_LED_ON: + return '6'; + case QNAP_MCU_STATUS_LED_BLINK_FAST: + return '5'; + case QNAP_MCU_STATUS_LED_BLINK_SLOW: + return 'A'; + } + } else if (status->green.mode == QNAP_MCU_STATUS_LED_OFF) { + switch (status->red.mode) { + case QNAP_MCU_STATUS_LED_OFF: + return '9'; + case QNAP_MCU_STATUS_LED_ON: + return '7'; + case QNAP_MCU_STATUS_LED_BLINK_FAST: + return '4'; + case QNAP_MCU_STATUS_LED_BLINK_SLOW: + return 'B'; + } + } else if (status->green.mode == QNAP_MCU_STATUS_LED_ON && + status->red.mode == QNAP_MCU_STATUS_LED_ON) { + return 'D'; + } else if (status->green.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW && + status->red.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW) { + return 'C'; + } + + /* + * Here both LEDs are on in some fashion, either both blinking fast, + * or in different speeds, so default to fast blinking for both. + */ + return '8'; +} + +static int qnap_mcu_status_led_update(struct qnap_mcu *mcu, + struct qnap_mcu_status *status) +{ + u8 cmd[] = { '@', 'C', 0 }; + + cmd[2] = qnap_mcu_status_led_encode(status); + + return qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd)); +} + +static int qnap_mcu_status_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev); + struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led); + + /* Don't disturb a possible set blink-mode if LED stays on */ + if (brightness != 0 && status_led->mode >= QNAP_MCU_STATUS_LED_BLINK_FAST) + return 0; + + status_led->mode = brightness ? QNAP_MCU_STATUS_LED_ON : + QNAP_MCU_STATUS_LED_OFF; + + return qnap_mcu_status_led_update(base->mcu, base); +} + +static int qnap_mcu_status_led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev); + struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led); + + if (status_led->mode == QNAP_MCU_STATUS_LED_OFF) + return 0; + + if (*delay_on <= 500) { + *delay_on = 500; + *delay_off = 500; + status_led->mode = QNAP_MCU_STATUS_LED_BLINK_FAST; + } else { + *delay_on = 1000; + *delay_off = 1000; + status_led->mode = QNAP_MCU_STATUS_LED_BLINK_SLOW; + } + + return qnap_mcu_status_led_update(base->mcu, base); +} + +static int qnap_mcu_register_status_leds(struct device *dev, struct qnap_mcu *mcu) +{ + struct qnap_mcu_status *status; + int ret; + + status = devm_kzalloc(dev, sizeof(*status), GFP_KERNEL); + if (!status) + return -ENOMEM; + + status->mcu = mcu; + + /* + * point to the red led, so that statusled_to_qnap_mcu_status + * can resolve the main status struct containing both leds + */ + status->red.red = &status->red; + status->green.red = &status->red; + + status->red.mode = QNAP_MCU_STATUS_LED_OFF; + status->red.cdev.name = "red:status"; + status->red.cdev.brightness_set_blocking = qnap_mcu_status_led_set; + status->red.cdev.blink_set = qnap_mcu_status_led_blink_set; + status->red.cdev.brightness = 0; + status->red.cdev.max_brightness = 1; + + status->green.mode = QNAP_MCU_STATUS_LED_OFF; + status->green.cdev.name = "green:status"; + status->green.cdev.brightness_set_blocking = qnap_mcu_status_led_set; + status->green.cdev.blink_set = qnap_mcu_status_led_blink_set; + status->green.cdev.brightness = 0; + status->green.cdev.max_brightness = 1; + + ret = devm_led_classdev_register(dev, &status->red.cdev); + if (ret) + return ret; + + ret = devm_led_classdev_register(dev, &status->green.cdev); + if (ret) + return ret; + + return qnap_mcu_status_led_update(status->mcu, status); +} + +static int qnap_mcu_leds_probe(struct platform_device *pdev) +{ + struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent); + 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"); + } + + ret = qnap_mcu_register_status_leds(&pdev->dev, mcu); + if (ret) + return dev_err_probe(&pdev->dev, ret, + "failed to register status LEDs\n"); + + return 0; +} + +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 b6447c1721b4..782e1c11ee44 100644 --- a/drivers/leds/leds-rb532.c +++ b/drivers/leds/leds-rb532.c @@ -42,10 +42,9 @@ static int rb532_led_probe(struct platform_device *pdev) return led_classdev_register(&pdev->dev, &rb532_uled); } -static int rb532_led_remove(struct platform_device *pdev) +static void rb532_led_remove(struct platform_device *pdev) { led_classdev_unregister(&rb532_uled); - return 0; } static struct platform_driver rb532_led_driver = { diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index 8a8b73b4e358..ade64629431a 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -173,13 +173,12 @@ static int regulator_led_probe(struct platform_device *pdev) return 0; } -static int regulator_led_remove(struct platform_device *pdev) +static void regulator_led_remove(struct platform_device *pdev) { struct regulator_led *led = platform_get_drvdata(pdev); led_classdev_unregister(&led->cdev); regulator_led_disable(led); - return 0; } static const struct of_device_id regulator_led_of_match[] = { diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c index e199ea15e406..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; @@ -296,7 +296,6 @@ static int sc27xx_led_probe(struct platform_device *pdev) return -ENOMEM; platform_set_drvdata(pdev, priv); - mutex_init(&priv->lock); priv->base = base; priv->regmap = dev_get_regmap(dev->parent, NULL); if (!priv->regmap) { @@ -305,24 +304,20 @@ 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); - mutex_destroy(&priv->lock); + if (err) return err; - } - if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) { - of_node_put(child); - mutex_destroy(&priv->lock); + if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) return -EINVAL; - } priv->leds[reg].fwnode = of_fwnode_handle(child); priv->leds[reg].active = true; } + mutex_init(&priv->lock); + err = sc27xx_led_register(dev, priv); if (err) mutex_destroy(&priv->lock); @@ -330,12 +325,11 @@ static int sc27xx_led_probe(struct platform_device *pdev) return err; } -static int sc27xx_led_remove(struct platform_device *pdev) +static void sc27xx_led_remove(struct platform_device *pdev) { struct sc27xx_led_priv *priv = platform_get_drvdata(pdev); mutex_destroy(&priv->lock); - return 0; } static const struct of_device_id sc27xx_led_of_match[] = { diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c index 2c7ffc3c78e6..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_device.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,72 +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; - const char *name = "leds-spi-byte::"; - const char *state; + struct led_init_data init_data = {}; + 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; - of_property_read_string(child, "label", &name); - strscpy(led->name, name, sizeof(led->name)); + 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.name = led->name; 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); - ret = devm_led_classdev_register(&spi->dev, &led->ldev); - if (ret) { - mutex_destroy(&led->mutex); - return ret; - } - spi_set_drvdata(spi, led); + init_data.fwnode = child; + init_data.devicename = "leds-spi-byte"; + init_data.default_label = ":"; - 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 new file mode 100644 index 000000000000..2c9bd360ab81 --- /dev/null +++ b/drivers/leds/leds-sun50i-a100.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2021-2023 Samuel Holland <samuel@sholland.org> + * + * Partly based on drivers/leds/leds-turris-omnia.c, which is: + * Copyright (c) 2020 by Marek Behún <kabel@kernel.org> + */ + +#include <linux/bitfield.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/dma-mapping.h> +#include <linux/dmaengine.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/led-class-multicolor.h> +#include <linux/leds.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/property.h> +#include <linux/reset.h> +#include <linux/spinlock.h> + +#define LEDC_CTRL_REG 0x0000 +#define LEDC_CTRL_REG_DATA_LENGTH GENMASK(28, 16) +#define LEDC_CTRL_REG_RGB_MODE GENMASK(8, 6) +#define LEDC_CTRL_REG_LEDC_EN BIT(0) +#define LEDC_T01_TIMING_CTRL_REG 0x0004 +#define LEDC_T01_TIMING_CTRL_REG_T1H GENMASK(26, 21) +#define LEDC_T01_TIMING_CTRL_REG_T1L GENMASK(20, 16) +#define LEDC_T01_TIMING_CTRL_REG_T0H GENMASK(10, 6) +#define LEDC_T01_TIMING_CTRL_REG_T0L GENMASK(5, 0) +#define LEDC_RESET_TIMING_CTRL_REG 0x000c +#define LEDC_RESET_TIMING_CTRL_REG_TR GENMASK(28, 16) +#define LEDC_RESET_TIMING_CTRL_REG_LED_NUM GENMASK(9, 0) +#define LEDC_DATA_REG 0x0014 +#define LEDC_DMA_CTRL_REG 0x0018 +#define LEDC_DMA_CTRL_REG_DMA_EN BIT(5) +#define LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL GENMASK(4, 0) +#define LEDC_INT_CTRL_REG 0x001c +#define LEDC_INT_CTRL_REG_GLOBAL_INT_EN BIT(5) +#define LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN BIT(1) +#define LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN BIT(0) +#define LEDC_INT_STS_REG 0x0020 +#define LEDC_INT_STS_REG_FIFO_WLW GENMASK(15, 10) +#define LEDC_INT_STS_REG_FIFO_CPUREQ_INT BIT(1) +#define LEDC_INT_STS_REG_TRANS_FINISH_INT BIT(0) + +#define LEDC_FIFO_DEPTH 32U +#define LEDC_MAX_LEDS 1024 +#define LEDC_CHANNELS_PER_LED 3 /* RGB */ + +#define LEDS_TO_BYTES(n) ((n) * sizeof(u32)) + +struct sun50i_a100_ledc_led { + struct led_classdev_mc mc_cdev; + struct mc_subled subled_info[LEDC_CHANNELS_PER_LED]; + u32 addr; +}; + +#define to_ledc_led(mc) container_of(mc, struct sun50i_a100_ledc_led, mc_cdev) + +struct sun50i_a100_ledc_timing { + u32 t0h_ns; + u32 t0l_ns; + u32 t1h_ns; + u32 t1l_ns; + u32 treset_ns; +}; + +struct sun50i_a100_ledc { + struct device *dev; + void __iomem *base; + struct clk *bus_clk; + struct clk *mod_clk; + struct reset_control *reset; + + u32 *buffer; + struct dma_chan *dma_chan; + dma_addr_t dma_handle; + unsigned int pio_length; + unsigned int pio_offset; + + spinlock_t lock; + unsigned int next_length; + bool xfer_active; + + u32 format; + struct sun50i_a100_ledc_timing timing; + + u32 max_addr; + u32 num_leds; + struct sun50i_a100_ledc_led leds[] __counted_by(num_leds); +}; + +static int sun50i_a100_ledc_dma_xfer(struct sun50i_a100_ledc *priv, unsigned int length) +{ + struct dma_async_tx_descriptor *desc; + dma_cookie_t cookie; + + desc = dmaengine_prep_slave_single(priv->dma_chan, priv->dma_handle, + LEDS_TO_BYTES(length), DMA_MEM_TO_DEV, 0); + if (!desc) + return -ENOMEM; + + cookie = dmaengine_submit(desc); + if (dma_submit_error(cookie)) + return -EIO; + + dma_async_issue_pending(priv->dma_chan); + + return 0; +} + +static void sun50i_a100_ledc_pio_xfer(struct sun50i_a100_ledc *priv, unsigned int fifo_used) +{ + unsigned int burst, length, offset; + u32 control; + + length = priv->pio_length; + offset = priv->pio_offset; + burst = min(length, LEDC_FIFO_DEPTH - fifo_used); + + iowrite32_rep(priv->base + LEDC_DATA_REG, priv->buffer + offset, burst); + + if (burst < length) { + priv->pio_length = length - burst; + priv->pio_offset = offset + burst; + + if (!offset) { + control = readl(priv->base + LEDC_INT_CTRL_REG); + control |= LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN; + writel(control, priv->base + LEDC_INT_CTRL_REG); + } + } else { + /* Disable the request IRQ once all data is written. */ + control = readl(priv->base + LEDC_INT_CTRL_REG); + control &= ~LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN; + writel(control, priv->base + LEDC_INT_CTRL_REG); + } +} + +static void sun50i_a100_ledc_start_xfer(struct sun50i_a100_ledc *priv, unsigned int length) +{ + bool use_dma = false; + u32 control; + + if (priv->dma_chan && length > LEDC_FIFO_DEPTH) { + int ret; + + ret = sun50i_a100_ledc_dma_xfer(priv, length); + if (ret) + dev_warn(priv->dev, "Failed to set up DMA (%d), using PIO\n", ret); + else + use_dma = true; + } + + /* The DMA trigger level must be at least the burst length. */ + control = FIELD_PREP(LEDC_DMA_CTRL_REG_DMA_EN, use_dma) | + FIELD_PREP_CONST(LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL, LEDC_FIFO_DEPTH / 2); + writel(control, priv->base + LEDC_DMA_CTRL_REG); + + control = readl(priv->base + LEDC_CTRL_REG); + control &= ~LEDC_CTRL_REG_DATA_LENGTH; + control |= FIELD_PREP(LEDC_CTRL_REG_DATA_LENGTH, length) | LEDC_CTRL_REG_LEDC_EN; + writel(control, priv->base + LEDC_CTRL_REG); + + if (!use_dma) { + /* The FIFO is empty when starting a new transfer. */ + unsigned int fifo_used = 0; + + priv->pio_length = length; + priv->pio_offset = 0; + + sun50i_a100_ledc_pio_xfer(priv, fifo_used); + } +} + +static irqreturn_t sun50i_a100_ledc_irq(int irq, void *data) +{ + struct sun50i_a100_ledc *priv = data; + u32 status; + + status = readl(priv->base + LEDC_INT_STS_REG); + + if (status & LEDC_INT_STS_REG_TRANS_FINISH_INT) { + unsigned int next_length; + + spin_lock(&priv->lock); + + /* If another transfer is queued, dequeue and start it. */ + next_length = priv->next_length; + if (next_length) + priv->next_length = 0; + else + priv->xfer_active = false; + + spin_unlock(&priv->lock); + + if (next_length) + sun50i_a100_ledc_start_xfer(priv, next_length); + } else if (status & LEDC_INT_STS_REG_FIFO_CPUREQ_INT) { + /* Continue the current transfer. */ + sun50i_a100_ledc_pio_xfer(priv, FIELD_GET(LEDC_INT_STS_REG_FIFO_WLW, status)); + } + + /* Clear the W1C status bits. */ + writel(status, priv->base + LEDC_INT_STS_REG); + + return IRQ_HANDLED; +} + +static void sun50i_a100_ledc_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(cdev->dev->parent); + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct sun50i_a100_ledc_led *led = to_ledc_led(mc_cdev); + unsigned int next_length; + unsigned long flags; + bool xfer_active; + + led_mc_calc_color_components(mc_cdev, brightness); + + priv->buffer[led->addr] = led->subled_info[0].brightness << 16 | + led->subled_info[1].brightness << 8 | + led->subled_info[2].brightness; + + spin_lock_irqsave(&priv->lock, flags); + + /* Start, enqueue, or extend an enqueued transfer, as appropriate. */ + next_length = max(priv->next_length, led->addr + 1); + xfer_active = priv->xfer_active; + if (xfer_active) + priv->next_length = next_length; + else + priv->xfer_active = true; + + spin_unlock_irqrestore(&priv->lock, flags); + + if (!xfer_active) + sun50i_a100_ledc_start_xfer(priv, next_length); +} + +static const char *const sun50i_a100_ledc_formats[] = { + "rgb", "rbg", "grb", "gbr", "brg", "bgr", +}; + +static int sun50i_a100_ledc_parse_format(struct device *dev, + struct sun50i_a100_ledc *priv) +{ + const char *format = "grb"; + int i; + + device_property_read_string(dev, "allwinner,pixel-format", &format); + + 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); + + priv->format = i; + return 0; +} + +static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv) +{ + u32 control; + + control = readl(priv->base + LEDC_CTRL_REG); + control &= ~LEDC_CTRL_REG_RGB_MODE; + control |= FIELD_PREP(LEDC_CTRL_REG_RGB_MODE, priv->format); + writel(control, priv->base + LEDC_CTRL_REG); +} + +static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = { + .t0h_ns = 336, + .t0l_ns = 840, + .t1h_ns = 882, + .t1l_ns = 294, + .treset_ns = 300000, +}; + +static int sun50i_a100_ledc_parse_timing(struct device *dev, + struct sun50i_a100_ledc *priv) +{ + struct sun50i_a100_ledc_timing *timing = &priv->timing; + + *timing = sun50i_a100_ledc_default_timing; + + device_property_read_u32(dev, "allwinner,t0h-ns", &timing->t0h_ns); + device_property_read_u32(dev, "allwinner,t0l-ns", &timing->t0l_ns); + device_property_read_u32(dev, "allwinner,t1h-ns", &timing->t1h_ns); + device_property_read_u32(dev, "allwinner,t1l-ns", &timing->t1l_ns); + device_property_read_u32(dev, "allwinner,treset-ns", &timing->treset_ns); + + return 0; +} + +static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv) +{ + const struct sun50i_a100_ledc_timing *timing = &priv->timing; + unsigned long mod_freq = clk_get_rate(priv->mod_clk); + u32 cycle_ns; + u32 control; + + if (!mod_freq) + return; + + cycle_ns = NSEC_PER_SEC / mod_freq; + control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) | + FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0L, timing->t0l_ns / cycle_ns); + writel(control, priv->base + LEDC_T01_TIMING_CTRL_REG); + + control = FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_TR, timing->treset_ns / cycle_ns) | + FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_LED_NUM, priv->max_addr); + writel(control, priv->base + LEDC_RESET_TIMING_CTRL_REG); +} + +static int sun50i_a100_ledc_resume(struct device *dev) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(dev); + int ret; + + ret = reset_control_deassert(priv->reset); + if (ret) + return ret; + + ret = clk_prepare_enable(priv->bus_clk); + if (ret) + goto err_assert_reset; + + ret = clk_prepare_enable(priv->mod_clk); + if (ret) + goto err_disable_bus_clk; + + sun50i_a100_ledc_set_format(priv); + sun50i_a100_ledc_set_timing(priv); + + writel(LEDC_INT_CTRL_REG_GLOBAL_INT_EN | LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN, + priv->base + LEDC_INT_CTRL_REG); + + return 0; + +err_disable_bus_clk: + clk_disable_unprepare(priv->bus_clk); +err_assert_reset: + reset_control_assert(priv->reset); + + return ret; +} + +static int sun50i_a100_ledc_suspend(struct device *dev) +{ + struct sun50i_a100_ledc *priv = dev_get_drvdata(dev); + + /* Wait for all transfers to complete. */ + for (;;) { + unsigned long flags; + bool xfer_active; + + spin_lock_irqsave(&priv->lock, flags); + xfer_active = priv->xfer_active; + spin_unlock_irqrestore(&priv->lock, flags); + if (!xfer_active) + break; + + usleep_range(1000, 1100); + } + + clk_disable_unprepare(priv->mod_clk); + clk_disable_unprepare(priv->bus_clk); + reset_control_assert(priv->reset); + + return 0; +} + +static void sun50i_a100_ledc_dma_cleanup(void *data) +{ + struct sun50i_a100_ledc *priv = data; + + dma_release_channel(priv->dma_chan); +} + +static int sun50i_a100_ledc_probe(struct platform_device *pdev) +{ + struct dma_slave_config dma_cfg = {}; + struct led_init_data init_data = {}; + struct sun50i_a100_ledc_led *led; + struct device *dev = &pdev->dev; + struct sun50i_a100_ledc *priv; + struct resource *mem; + u32 max_addr = 0; + u32 num_leds = 0; + int irq, ret; + + /* + * 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_scoped(dev, child) { + u32 addr, color; + + ret = fwnode_property_read_u32(child, "reg", &addr); + 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) + return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n"); + + max_addr = max(max_addr, addr); + num_leds++; + } + + if (!num_leds) + return -ENODEV; + + priv = devm_kzalloc(dev, struct_size(priv, leds, num_leds), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + priv->dev = dev; + priv->max_addr = max_addr; + priv->num_leds = num_leds; + spin_lock_init(&priv->lock); + dev_set_drvdata(dev, priv); + + ret = sun50i_a100_ledc_parse_format(dev, priv); + if (ret) + return ret; + + ret = sun50i_a100_ledc_parse_timing(dev, priv); + if (ret) + return ret; + + priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem); + if (IS_ERR(priv->base)) + return PTR_ERR(priv->base); + + priv->bus_clk = devm_clk_get(dev, "bus"); + if (IS_ERR(priv->bus_clk)) + return PTR_ERR(priv->bus_clk); + + priv->mod_clk = devm_clk_get(dev, "mod"); + if (IS_ERR(priv->mod_clk)) + return PTR_ERR(priv->mod_clk); + + priv->reset = devm_reset_control_get_exclusive(dev, NULL); + if (IS_ERR(priv->reset)) + return PTR_ERR(priv->reset); + + priv->dma_chan = dma_request_chan(dev, "tx"); + if (IS_ERR(priv->dma_chan)) { + if (PTR_ERR(priv->dma_chan) != -ENODEV) + return PTR_ERR(priv->dma_chan); + + priv->dma_chan = NULL; + + priv->buffer = devm_kzalloc(dev, LEDS_TO_BYTES(LEDC_MAX_LEDS), GFP_KERNEL); + if (!priv->buffer) + return -ENOMEM; + } else { + ret = devm_add_action_or_reset(dev, sun50i_a100_ledc_dma_cleanup, priv); + if (ret) + return ret; + + dma_cfg.dst_addr = mem->start + LEDC_DATA_REG; + dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES; + dma_cfg.dst_maxburst = LEDC_FIFO_DEPTH / 2; + + ret = dmaengine_slave_config(priv->dma_chan, &dma_cfg); + if (ret) + return ret; + + priv->buffer = dmam_alloc_attrs(dmaengine_get_dma_device(priv->dma_chan), + LEDS_TO_BYTES(LEDC_MAX_LEDS), &priv->dma_handle, + GFP_KERNEL, DMA_ATTR_WRITE_COMBINE); + if (!priv->buffer) + return -ENOMEM; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + ret = devm_request_irq(dev, irq, sun50i_a100_ledc_irq, 0, dev_name(dev), priv); + if (ret) + return ret; + + ret = sun50i_a100_ledc_resume(dev); + if (ret) + return ret; + + led = priv->leds; + device_for_each_child_node_scoped(dev, child) { + struct led_classdev *cdev; + + /* The node was already validated above. */ + fwnode_property_read_u32(child, "reg", &led->addr); + + led->subled_info[0].color_index = LED_COLOR_ID_RED; + led->subled_info[0].channel = 0; + led->subled_info[1].color_index = LED_COLOR_ID_GREEN; + led->subled_info[1].channel = 1; + led->subled_info[2].color_index = LED_COLOR_ID_BLUE; + led->subled_info[2].channel = 2; + + led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info); + led->mc_cdev.subled_info = led->subled_info; + + cdev = &led->mc_cdev.led_cdev; + cdev->max_brightness = U8_MAX; + cdev->brightness_set = sun50i_a100_ledc_brightness_set; + + init_data.fwnode = child; + + 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); + while (led-- > priv->leds) + led_classdev_multicolor_unregister(&led->mc_cdev); + sun50i_a100_ledc_suspend(&pdev->dev); + + return ret; + } + + led++; + } + + dev_info(dev, "Registered %u LEDs\n", num_leds); + + return 0; +} + +static void sun50i_a100_ledc_remove(struct platform_device *pdev) +{ + struct sun50i_a100_ledc *priv = platform_get_drvdata(pdev); + + for (u32 i = 0; i < priv->num_leds; i++) + led_classdev_multicolor_unregister(&priv->leds[i].mc_cdev); + sun50i_a100_ledc_suspend(&pdev->dev); +} + +static const struct of_device_id sun50i_a100_ledc_of_match[] = { + { .compatible = "allwinner,sun50i-a100-ledc" }, + {} +}; +MODULE_DEVICE_TABLE(of, sun50i_a100_ledc_of_match); + +static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm, + sun50i_a100_ledc_suspend, + sun50i_a100_ledc_resume); + +static struct platform_driver sun50i_a100_ledc_driver = { + .probe = sun50i_a100_ledc_probe, + .remove = sun50i_a100_ledc_remove, + .shutdown = sun50i_a100_ledc_remove, + .driver = { + .name = "sun50i-a100-ledc", + .of_match_table = sun50i_a100_ledc_of_match, + .pm = pm_ptr(&sun50i_a100_ledc_pm), + }, +}; +module_platform_driver(sun50i_a100_ledc_driver); + +MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>"); +MODULE_DESCRIPTION("Allwinner A100 LED controller driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c index eba7313719bf..bd24e7f5947a 100644 --- a/drivers/leds/leds-sunfire.c +++ b/drivers/leds/leds-sunfire.c @@ -17,7 +17,7 @@ #include <asm/fhc.h> #include <asm/upa.h> -MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); +MODULE_AUTHOR("David S. Miller <davem@davemloft.net>"); MODULE_DESCRIPTION("Sun Fire LED driver"); MODULE_LICENSE("GPL"); @@ -163,15 +163,13 @@ static int sunfire_led_generic_probe(struct platform_device *pdev, return 0; } -static int sunfire_led_generic_remove(struct platform_device *pdev) +static void sunfire_led_generic_remove(struct platform_device *pdev) { struct sunfire_drvdata *p = platform_get_drvdata(pdev); int i; for (i = 0; i < NUM_LEDS_PER_BOARD; i++) led_classdev_unregister(&p->leds[i].led_cdev); - - return 0; } static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = { diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c index e38abb5e60c1..d633ad519d0c 100644 --- a/drivers/leds/leds-syscon.c +++ b/drivers/leds/leds-syscon.c @@ -7,8 +7,7 @@ */ #include <linux/io.h> #include <linux/init.h> -#include <linux/of_device.h> -#include <linux/of_address.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/stat.h> #include <linux/slab.h> @@ -82,7 +81,8 @@ static int syscon_led_probe(struct platform_device *pdev) sled->map = map; - if (of_property_read_u32(np, "offset", &sled->offset)) + if (of_property_read_u32(np, "reg", &sled->offset) && + of_property_read_u32(np, "offset", &sled->offset)) return -EINVAL; if (of_property_read_u32(np, "mask", &sled->mask)) return -EINVAL; diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index aab861771210..fd0e8bab9a4b 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -92,9 +92,6 @@ struct tca6507_platform_data { struct led_platform_data leds; -#ifdef CONFIG_GPIOLIB - int gpio_base; -#endif }; #define TCA6507_MAKE_GPIO 1 @@ -591,8 +588,8 @@ static int tca6507_blink_set(struct led_classdev *led_cdev, } #ifdef CONFIG_GPIOLIB -static void tca6507_gpio_set_value(struct gpio_chip *gc, - unsigned offset, int val) +static int tca6507_gpio_set_value(struct gpio_chip *gc, unsigned int offset, + int val) { struct tca6507_chip *tca = gpiochip_get_data(gc); unsigned long flags; @@ -607,13 +604,14 @@ static void tca6507_gpio_set_value(struct gpio_chip *gc, spin_unlock_irqrestore(&tca->lock, flags); if (tca->reg_set) schedule_work(&tca->work); + + return 0; } static int tca6507_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val) { - tca6507_gpio_set_value(gc, offset, val); - return 0; + return tca6507_gpio_set_value(gc, offset, val); } static int tca6507_probe_gpios(struct device *dev, @@ -636,24 +634,18 @@ static int tca6507_probe_gpios(struct device *dev, tca->gpio.label = "gpio-tca6507"; tca->gpio.ngpio = gpios; - tca->gpio.base = pdata->gpio_base; + tca->gpio.base = -1; tca->gpio.owner = THIS_MODULE; tca->gpio.direction_output = tca6507_gpio_direction_output; tca->gpio.set = tca6507_gpio_set_value; tca->gpio.parent = dev; - err = gpiochip_add_data(&tca->gpio, tca); + err = devm_gpiochip_add_data(dev, &tca->gpio, tca); if (err) { tca->gpio.ngpio = 0; return err; } return 0; } - -static void tca6507_remove_gpio(struct tca6507_chip *tca) -{ - if (tca->gpio.ngpio) - gpiochip_remove(&tca->gpio); -} #else /* CONFIG_GPIOLIB */ static int tca6507_probe_gpios(struct device *dev, struct tca6507_chip *tca, @@ -661,16 +653,12 @@ static int tca6507_probe_gpios(struct device *dev, { return 0; } -static void tca6507_remove_gpio(struct tca6507_chip *tca) -{ -} #endif /* CONFIG_GPIOLIB */ 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; @@ -683,7 +671,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; @@ -700,10 +688,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; } @@ -715,9 +701,6 @@ tca6507_led_dt_init(struct device *dev) pdata->leds.leds = tca_leds; pdata->leds.num_leds = NUM_LEDS; -#ifdef CONFIG_GPIOLIB - pdata->gpio_base = -1; -#endif return pdata; } @@ -768,38 +751,25 @@ static int tca6507_probe(struct i2c_client *client) l->led_cdev.brightness_set = tca6507_brightness_set; l->led_cdev.blink_set = tca6507_blink_set; l->bank = -1; - err = led_classdev_register(dev, &l->led_cdev); + err = devm_led_classdev_register(dev, &l->led_cdev); if (err < 0) - goto exit; + return err; } } err = tca6507_probe_gpios(dev, tca, pdata); if (err) - goto exit; + return err; /* set all registers to known state - zero */ tca->reg_set = 0x7f; schedule_work(&tca->work); return 0; -exit: - while (i--) { - if (tca->leds[i].led_cdev.name) - led_classdev_unregister(&tca->leds[i].led_cdev); - } - return err; } static void tca6507_remove(struct i2c_client *client) { - int i; struct tca6507_chip *tca = i2c_get_clientdata(client); - struct tca6507_led *tca_leds = tca->leds; - for (i = 0; i < NUM_LEDS; i++) { - if (tca_leds[i].led_cdev.name) - led_classdev_unregister(&tca_leds[i].led_cdev); - } - tca6507_remove_gpio(tca); cancel_work_sync(&tca->work); } diff --git a/drivers/leds/leds-ti-lmu-common.c b/drivers/leds/leds-ti-lmu-common.c index d7f10ad721ba..b2491666b5dc 100644 --- a/drivers/leds/leds-ti-lmu-common.c +++ b/drivers/leds/leds-ti-lmu-common.c @@ -7,7 +7,7 @@ #include <linux/bitops.h> #include <linux/err.h> -#include <linux/of_device.h> +#include <linux/property.h> #include <linux/leds-ti-lmu-common.h> diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c index dfc6fb2b3e52..6605e08a042a 100644 --- a/drivers/leds/leds-tlc591xx.c +++ b/drivers/leds/leds-tlc591xx.c @@ -8,7 +8,6 @@ #include <linux/leds.h> #include <linux/module.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/regmap.h> #include <linux/slab.h> @@ -147,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; @@ -183,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; @@ -208,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 64b2d7b6d3f3..25ee5c1eb820 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 by Marek Behún <kabel@kernel.org> + * 2020, 2023, 2024 by Marek Behún <kabel@kernel.org> */ #include <linux/i2c.h> @@ -10,69 +10,198 @@ #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 -#define CMD_LED_MODE 3 -#define CMD_LED_MODE_LED(l) ((l) & 0x0f) -#define CMD_LED_MODE_USER 0x10 - -#define CMD_LED_STATE 4 -#define CMD_LED_STATE_LED(l) ((l) & 0x0f) -#define CMD_LED_STATE_ON 0x10 - -#define CMD_LED_COLOR 5 -#define CMD_LED_SET_BRIGHTNESS 7 -#define CMD_LED_GET_BRIGHTNESS 8 - +/* 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]; + u8 cached_channels[OMNIA_LED_NUM_CHANNELS]; + bool on, hwtrig; int reg; }; #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_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b) +{ + u8 buf[5] = { OMNIA_CMD_LED_COLOR, led, r, g, b }; + + return omnia_cmd_write(client, buf, sizeof(buf)); +} + +static int omnia_led_send_color_cmd(const struct i2c_client *client, + struct omnia_led *led) +{ + int ret; + + /* Send the color change command */ + 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; + + /* Cache the RGB channel brightnesses */ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) + led->cached_channels[i] = led->subled_info[i].brightness; + + return 0; +} + +/* Determine if the computed RGB channels are different from the cached ones */ +static bool omnia_led_channels_changed(struct omnia_led *led) +{ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) + if (led->subled_info[i].brightness != led->cached_channels[i]) + return true; + + return false; +} + static int omnia_led_brightness_set_blocking(struct led_classdev *cdev, enum led_brightness brightness) { struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); struct omnia_led *led = to_omnia_led(mc_cdev); - u8 buf[5], state; - int ret; + int err = 0; mutex_lock(&leds->lock); - led_mc_calc_color_components(&led->mc_cdev, brightness); + /* + * Only recalculate RGB brightnesses from intensities if brightness is + * non-zero (if it is zero and the LED is in HW blinking mode, we use + * max_brightness as brightness). Otherwise we won't be using them and + * we can save ourselves some software divisions (Omnia's CPU does not + * implement the division instruction). + */ + if (brightness || led->hwtrig) { + led_mc_calc_color_components(mc_cdev, brightness ?: + cdev->max_brightness); + + /* + * Send color command only if brightness is non-zero and the RGB + * channel brightnesses changed. + */ + if (omnia_led_channels_changed(led)) + err = omnia_led_send_color_cmd(leds->client, led); + } - buf[0] = CMD_LED_COLOR; - buf[1] = led->reg; - buf[2] = mc_cdev->subled_info[0].brightness; - buf[3] = mc_cdev->subled_info[1].brightness; - buf[4] = mc_cdev->subled_info[2].brightness; + /* + * Send on/off state change only if (bool)brightness changed and the LED + * is not being blinked by HW. + */ + if (!err && !led->hwtrig && !brightness != !led->on) { + u8 state = OMNIA_CMD_LED_STATE_LED(led->reg); - state = CMD_LED_STATE_LED(led->reg); - if (buf[2] || buf[3] || buf[4]) - state |= CMD_LED_STATE_ON; + if (brightness) + state |= OMNIA_CMD_LED_STATE_ON; - ret = i2c_smbus_write_byte_data(leds->client, CMD_LED_STATE, state); - if (ret >= 0 && (state & CMD_LED_STATE_ON)) - ret = i2c_master_send(leds->client, buf, 5); + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_STATE, state); + if (!err) + led->on = !!brightness; + } mutex_unlock(&leds->lock); - return ret; + return err; } +static struct led_hw_trigger_type omnia_hw_trigger_type; + +static int omnia_hwtrig_activate(struct led_classdev *cdev) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); + struct omnia_led *led = to_omnia_led(mc_cdev); + int err = 0; + + mutex_lock(&leds->lock); + + if (!led->on) { + /* + * If the LED is off (brightness was set to 0), the last + * configured color was not necessarily sent to the MCU. + * Recompute with max_brightness and send if needed. + */ + led_mc_calc_color_components(mc_cdev, cdev->max_brightness); + + if (omnia_led_channels_changed(led)) + err = omnia_led_send_color_cmd(leds->client, led); + } + + if (!err) { + /* Put the LED into MCU controlled mode */ + err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE, + OMNIA_CMD_LED_MODE_LED(led->reg)); + if (!err) + led->hwtrig = true; + } + + mutex_unlock(&leds->lock); + + return err; +} + +static void omnia_hwtrig_deactivate(struct led_classdev *cdev) +{ + struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent); + struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev)); + int err; + + mutex_lock(&leds->lock); + + led->hwtrig = false; + + /* Put the LED into software mode */ + 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); + + if (err) + dev_err(cdev->dev, "Cannot put LED to software mode: %i\n", + err); +} + +static struct led_trigger omnia_hw_trigger = { + .name = "omnia-mcu", + .activate = omnia_hwtrig_activate, + .deactivate = omnia_hwtrig_deactivate, + .trigger_type = &omnia_hw_trigger_type, +}; + static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, struct device_node *np) { @@ -98,11 +227,15 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, } led->subled_info[0].color_index = LED_COLOR_ID_RED; - led->subled_info[0].channel = 0; led->subled_info[1].color_index = LED_COLOR_ID_GREEN; - led->subled_info[1].channel = 1; led->subled_info[2].color_index = LED_COLOR_ID_BLUE; - led->subled_info[2].channel = 2; + + /* Initial color is white */ + for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) { + led->subled_info[i].intensity = 255; + led->subled_info[i].brightness = 255; + led->subled_info[i].channel = i; + } led->mc_cdev.subled_info = led->subled_info; led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS; @@ -112,31 +245,33 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led, cdev = &led->mc_cdev.led_cdev; cdev->max_brightness = 255; cdev->brightness_set_blocking = omnia_led_brightness_set_blocking; - - /* put the LED into software mode */ - ret = i2c_smbus_write_byte_data(client, CMD_LED_MODE, - CMD_LED_MODE_LED(led->reg) | - CMD_LED_MODE_USER); - if (ret < 0) { - dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np, - ret); - return ret; - } - - /* disable the LED */ - ret = i2c_smbus_write_byte_data(client, CMD_LED_STATE, - CMD_LED_STATE_LED(led->reg)); - if (ret < 0) { - dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret); - return ret; - } + cdev->trigger_type = &omnia_hw_trigger_type; + /* + * Use the omnia-mcu trigger as the default trigger. It may be rewritten + * by LED class from the linux,default-trigger property. + */ + cdev->default_trigger = omnia_hw_trigger.name; + + /* 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, 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) + 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; } @@ -156,26 +291,22 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a, char *buf) { struct i2c_client *client = to_i2c_client(dev); - struct omnia_leds *leds = i2c_get_clientdata(client); - int ret; + u8 reply; + int err; - mutex_lock(&leds->lock); - ret = i2c_smbus_read_byte_data(client, CMD_LED_GET_BRIGHTNESS); - mutex_unlock(&leds->lock); - - if (ret < 0) - return ret; + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_BRIGHTNESS, &reply); + if (err < 0) + return err; - return sprintf(buf, "%d\n", ret); + return sysfs_emit(buf, "%d\n", reply); } static ssize_t brightness_store(struct device *dev, struct device_attribute *a, const char *buf, size_t count) { struct i2c_client *client = to_i2c_client(dev); - struct omnia_leds *leds = i2c_get_clientdata(client); unsigned long brightness; - int ret; + int err; if (kstrtoul(buf, 10, &brightness)) return -EINVAL; @@ -183,40 +314,170 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a, if (brightness > 100) return -EINVAL; - mutex_lock(&leds->lock); - ret = i2c_smbus_write_byte_data(client, CMD_LED_SET_BRIGHTNESS, - (u8)brightness); - mutex_unlock(&leds->lock); + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_BRIGHTNESS, brightness); - if (ret < 0) - return ret; - - return count; + return err ?: count; } static DEVICE_ATTR_RW(brightness); +static ssize_t gamma_correction_show(struct device *dev, + struct device_attribute *a, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_leds *leds = i2c_get_clientdata(client); + u8 reply = 0; + int err; + + if (leds->has_gamma_correction) { + err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_GAMMA_CORRECTION, &reply); + if (err < 0) + return err; + } + + return sysfs_emit(buf, "%d\n", !!reply); +} + +static ssize_t gamma_correction_store(struct device *dev, + struct device_attribute *a, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct omnia_leds *leds = i2c_get_clientdata(client); + bool val; + int err; + + if (!leds->has_gamma_correction) + return -EOPNOTSUPP; + + if (kstrtobool(buf, &val) < 0) + return -EINVAL; + + err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_GAMMA_CORRECTION, val); + + return err ?: count; +} +static DEVICE_ATTR_RW(gamma_correction); + static struct attribute *omnia_led_controller_attrs[] = { &dev_attr_brightness.attr, - NULL, + &dev_attr_gamma_correction.attr, + NULL }; ATTRIBUTE_GROUPS(omnia_led_controller); +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_u16(mcu_client, OMNIA_CMD_GET_STATUS_WORD, &reply); + if (err) + return err; + + /* Check whether MCU firmware supports the OMNIA_CMD_GET_FEAUTRES command */ + if (!(reply & OMNIA_STS_FEATURES_SUPPORTED)) + return 0; + + err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_FEATURES, &reply); + if (err) + return err; + + 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) @@ -225,15 +486,29 @@ static int omnia_leds_probe(struct i2c_client *client) leds->client = client; i2c_set_clientdata(client, leds); + 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 & 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) + 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; } @@ -243,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 */ - i2c_smbus_write_byte_data(client, CMD_LED_MODE, - CMD_LED_MODE_LED(OMNIA_BOARD_LEDS)); + /* 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)); - /* 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; - - 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..12989b2f1953 --- /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-leds"); diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index c48b80574f02..05930e9e8887 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -280,13 +280,11 @@ static int wm831x_status_probe(struct platform_device *pdev) return 0; } -static int wm831x_status_remove(struct platform_device *pdev) +static void wm831x_status_remove(struct platform_device *pdev) { struct wm831x_status *drvdata = platform_get_drvdata(pdev); led_classdev_unregister(&drvdata->cdev); - - return 0; } static struct platform_driver wm831x_status_driver = { diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index 8f243c413723..87e60ea927b9 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -242,13 +242,12 @@ static int wm8350_led_probe(struct platform_device *pdev) return led_classdev_register(&pdev->dev, &led->cdev); } -static int wm8350_led_remove(struct platform_device *pdev) +static void wm8350_led_remove(struct platform_device *pdev) { struct wm8350_led *led = platform_get_drvdata(pdev); led_classdev_unregister(&led->cdev); wm8350_led_disable(led); - return 0; } static struct platform_driver wm8350_led_driver = { diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index 345062ccabda..bee46651e068 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -22,15 +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 struct list_head trigger_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 360c8679c6e2..222d943d826a 100644 --- a/drivers/leds/rgb/Kconfig +++ b/drivers/leds/rgb/Kconfig @@ -2,6 +2,41 @@ if LEDS_CLASS_MULTICOLOR +config LEDS_GROUP_MULTICOLOR + tristate "LEDs group multi-color support" + depends on OF + help + This option enables support for monochrome LEDs that are grouped + into multicolor LEDs which is useful in the case where LEDs of + different colors are physically grouped in a single multi-color LED + and driven by a controller that doesn't have multi-color support. + + To compile this driver as a module, choose M here: the module + will be called leds-group-multicolor. + +config LEDS_KTD202X + tristate "LED support for KTD202x Chips" + depends on I2C + select REGMAP_I2C + help + This option enables support for the Kinetic KTD2026/KTD2027 + RGB/White LED driver found in different BQ mobile phones. + It is a 3 or 4 channel LED driver programmed via an I2C interface. + + To compile this driver as a module, choose M here: the module + will be called leds-ktd202x. + +config LEDS_NCP5623 + tristate "LED support for NCP5623" + depends on I2C + depends on OF + help + This option enables support for ON semiconductor NCP5623 + Triple Output I2C Controlled RGB LED Driver. + + To compile this driver as a module, choose M here: the module + will be called leds-ncp5623. + config LEDS_PWM_MULTICOLOR tristate "PWM driven multi-color LED Support" depends on PWM @@ -16,6 +51,7 @@ config LEDS_QCOM_LPG tristate "LED support for Qualcomm LPG" depends on OF depends on PWM + depends on QCOM_PBS || !QCOM_PBS depends on SPMI help This option enables support for the Light Pulse Generator found in a diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile index 8c01daf63f61..a501fd27f179 100644 --- a/drivers/leds/rgb/Makefile +++ b/drivers/leds/rgb/Makefile @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0 +obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o +obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o +obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o diff --git a/drivers/leds/rgb/leds-group-multicolor.c b/drivers/leds/rgb/leds-group-multicolor.c new file mode 100644 index 000000000000..548c7dd63ba1 --- /dev/null +++ b/drivers/leds/rgb/leds-group-multicolor.c @@ -0,0 +1,173 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Multi-color LED built with monochromatic LED devices + * + * This driver groups several monochromatic LED devices in a single multicolor LED device. + * + * Compared to handling this grouping in user-space, the benefits are: + * - The state of the monochromatic LED relative to each other is always consistent. + * - The sysfs interface of the LEDs can be used for the group as a whole. + * + * Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com> + */ + +#include <linux/err.h> +#include <linux/leds.h> +#include <linux/led-class-multicolor.h> +#include <linux/math.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/property.h> + +struct leds_multicolor { + struct led_classdev_mc mc_cdev; + struct led_classdev **monochromatics; +}; + +static int leds_gmc_set(struct led_classdev *cdev, enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct leds_multicolor *priv = container_of(mc_cdev, struct leds_multicolor, mc_cdev); + const unsigned int group_max_brightness = mc_cdev->led_cdev.max_brightness; + int i; + + for (i = 0; i < mc_cdev->num_colors; i++) { + struct led_classdev *mono = priv->monochromatics[i]; + const unsigned int mono_max_brightness = mono->max_brightness; + unsigned int intensity = mc_cdev->subled_info[i].intensity; + int mono_brightness; + + /* + * Scale the brightness according to relative intensity of the + * color AND the max brightness of the monochromatic LED. + */ + mono_brightness = DIV_ROUND_CLOSEST(brightness * intensity * mono_max_brightness, + group_max_brightness * group_max_brightness); + + led_set_brightness(mono, mono_brightness); + } + + return 0; +} + +static void restore_sysfs_write_access(void *data) +{ + struct led_classdev *led_cdev = data; + + /* Restore the write access to the LED */ + mutex_lock(&led_cdev->led_access); + led_sysfs_enable(led_cdev); + mutex_unlock(&led_cdev->led_access); +} + +static int leds_gmc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct led_init_data init_data = {}; + struct led_classdev *cdev; + struct mc_subled *subled; + struct leds_multicolor *priv; + unsigned int max_brightness = 0; + int i, ret, count = 0, common_flags = 0; + + priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); + if (!priv) + return -ENOMEM; + + for (;;) { + struct led_classdev *led_cdev; + + led_cdev = devm_of_led_get_optional(dev, count); + if (IS_ERR(led_cdev)) + return dev_err_probe(dev, PTR_ERR(led_cdev), "Unable to get LED #%d", + count); + if (!led_cdev) + break; + + priv->monochromatics = devm_krealloc_array(dev, priv->monochromatics, + count + 1, sizeof(*priv->monochromatics), + GFP_KERNEL); + if (!priv->monochromatics) + return -ENOMEM; + + common_flags |= led_cdev->flags; + priv->monochromatics[count] = led_cdev; + + max_brightness = max(max_brightness, led_cdev->max_brightness); + + count++; + } + + subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL); + if (!subled) + return -ENOMEM; + priv->mc_cdev.subled_info = subled; + + for (i = 0; i < count; i++) { + struct led_classdev *led_cdev = priv->monochromatics[i]; + + subled[i].color_index = led_cdev->color; + + /* Configure the LED intensity to its maximum */ + subled[i].intensity = max_brightness; + } + + /* Initialise the multicolor's LED class device */ + cdev = &priv->mc_cdev.led_cdev; + cdev->brightness_set_blocking = leds_gmc_set; + cdev->max_brightness = max_brightness; + cdev->color = LED_COLOR_ID_MULTI; + priv->mc_cdev.num_colors = count; + + /* we only need suspend/resume if a sub-led requests it */ + if (common_flags & LED_CORE_SUSPENDRESUME) + cdev->flags = LED_CORE_SUSPENDRESUME; + + init_data.fwnode = dev_fwnode(dev); + ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data); + if (ret) + return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n", + cdev->name); + + ret = leds_gmc_set(cdev, cdev->brightness); + if (ret) + return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name); + + for (i = 0; i < count; i++) { + struct led_classdev *led_cdev = priv->monochromatics[i]; + + /* + * Make the individual LED sysfs interface read-only to prevent the user + * to change the brightness of the individual LEDs of the group. + */ + mutex_lock(&led_cdev->led_access); + led_sysfs_disable(led_cdev); + mutex_unlock(&led_cdev->led_access); + + /* Restore the write access to the LED sysfs when the group is destroyed */ + devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev); + } + + return 0; +} + +static const struct of_device_id of_leds_group_multicolor_match[] = { + { .compatible = "leds-group-multicolor" }, + {} +}; +MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match); + +static struct platform_driver leds_group_multicolor_driver = { + .probe = leds_gmc_probe, + .driver = { + .name = "leds_group_multicolor", + .of_match_table = of_leds_group_multicolor_match, + } +}; +module_platform_driver(leds_group_multicolor_driver); + +MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>"); +MODULE_DESCRIPTION("LEDs group multicolor driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-group-multicolor"); diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c new file mode 100644 index 000000000000..e4f0f25a5e45 --- /dev/null +++ b/drivers/leds/rgb/leds-ktd202x.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Kinetic KTD2026/7 RGB/White LED driver with I2C interface + * + * Copyright 2023 André Apitzsch <git@apitzsch.eu> + * + * Datasheet: https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf + */ + +#include <linux/i2c.h> +#include <linux/led-class-multicolor.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/regulator/consumer.h> + +#define KTD2026_NUM_LEDS 3 +#define KTD2027_NUM_LEDS 4 +#define KTD202X_MAX_LEDS 4 + +/* Register bank */ +#define KTD202X_REG_RESET_CONTROL 0x00 +#define KTD202X_REG_FLASH_PERIOD 0x01 +#define KTD202X_REG_PWM1_TIMER 0x02 +#define KTD202X_REG_PWM2_TIMER 0x03 +#define KTD202X_REG_CHANNEL_CTRL 0x04 +#define KTD202X_REG_TRISE_FALL 0x05 +#define KTD202X_REG_LED_IOUT(x) (0x06 + (x)) + +/* Register 0 */ +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT1 0x00 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT2 0x01 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT3 0x02 +#define KTD202X_TIMER_SLOT_CONTROL_TSLOT4 0x03 +#define KTD202X_RSTR_RESET 0x07 + +#define KTD202X_ENABLE_CTRL_WAKE 0x00 /* SCL High & SDA High */ +#define KTD202X_ENABLE_CTRL_SLEEP 0x08 /* SCL High & SDA Toggling */ + +#define KTD202X_TRISE_FALL_SCALE_NORMAL 0x00 +#define KTD202X_TRISE_FALL_SCALE_SLOW_X2 0x20 +#define KTD202X_TRISE_FALL_SCALE_SLOW_X4 0x40 +#define KTD202X_TRISE_FALL_SCALE_FAST_X8 0x60 + +/* Register 1 */ +#define KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP 0x00 + +/* Register 2-3 */ +#define KTD202X_FLASH_ON_TIME_0_4_PERCENT 0x01 + +/* Register 4 */ +#define KTD202X_CHANNEL_CTRL_MASK(x) (BIT(2 * (x)) | BIT(2 * (x) + 1)) +#define KTD202X_CHANNEL_CTRL_OFF 0x00 +#define KTD202X_CHANNEL_CTRL_ON(x) BIT(2 * (x)) +#define KTD202X_CHANNEL_CTRL_PWM1(x) BIT(2 * (x) + 1) +#define KTD202X_CHANNEL_CTRL_PWM2(x) (BIT(2 * (x)) | BIT(2 * (x) + 1)) + +/* Register 5 */ +#define KTD202X_RAMP_TIMES_2_MS 0x00 + +/* Register 6-9 */ +#define KTD202X_LED_CURRENT_10_mA 0x4f + +#define KTD202X_FLASH_PERIOD_MIN_MS 256 +#define KTD202X_FLASH_PERIOD_STEP_MS 128 +#define KTD202X_FLASH_PERIOD_MAX_STEPS 126 +#define KTD202X_FLASH_ON_MAX 256 + +#define KTD202X_MAX_BRIGHTNESS 192 + +static const struct reg_default ktd202x_reg_defaults[] = { + { KTD202X_REG_RESET_CONTROL, KTD202X_TIMER_SLOT_CONTROL_TSLOT1 | + KTD202X_ENABLE_CTRL_WAKE | KTD202X_TRISE_FALL_SCALE_NORMAL }, + { KTD202X_REG_FLASH_PERIOD, KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP }, + { KTD202X_REG_PWM1_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT }, + { KTD202X_REG_PWM2_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT }, + { KTD202X_REG_CHANNEL_CTRL, KTD202X_CHANNEL_CTRL_OFF }, + { KTD202X_REG_TRISE_FALL, KTD202X_RAMP_TIMES_2_MS }, + { KTD202X_REG_LED_IOUT(0), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(1), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(2), KTD202X_LED_CURRENT_10_mA }, + { KTD202X_REG_LED_IOUT(3), KTD202X_LED_CURRENT_10_mA }, +}; + +struct ktd202x_led { + struct ktd202x *chip; + union { + struct led_classdev cdev; + struct led_classdev_mc mcdev; + }; + u32 index; +}; + +struct ktd202x { + struct mutex mutex; + struct regulator_bulk_data regulators[2]; + struct device *dev; + struct regmap *regmap; + bool enabled; + unsigned long num_leds; + struct ktd202x_led leds[] __counted_by(num_leds); +}; + +static int ktd202x_chip_disable(struct ktd202x *chip) +{ + int ret; + + if (!chip->enabled) + return 0; + + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_SLEEP); + + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err(chip->dev, "Failed to disable regulators: %d\n", ret); + return ret; + } + + chip->enabled = false; + return 0; +} + +static int ktd202x_chip_enable(struct ktd202x *chip) +{ + int ret; + + if (chip->enabled) + return 0; + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err(chip->dev, "Failed to enable regulators: %d\n", ret); + return ret; + } + chip->enabled = true; + + ret = regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_WAKE); + + if (ret) { + dev_err(chip->dev, "Failed to enable the chip: %d\n", ret); + ktd202x_chip_disable(chip); + } + + return ret; +} + +static bool ktd202x_chip_in_use(struct ktd202x *chip) +{ + int i; + + for (i = 0; i < chip->num_leds; i++) { + if (chip->leds[i].cdev.brightness) + return true; + } + + return false; +} + +static int ktd202x_brightness_set(struct ktd202x_led *led, + struct mc_subled *subleds, + unsigned int num_channels) +{ + bool mode_blink = false; + int channel; + int state; + int ret; + int i; + + if (ktd202x_chip_in_use(led->chip)) { + ret = ktd202x_chip_enable(led->chip); + if (ret) + return ret; + } + + ret = regmap_read(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, &state); + if (ret) + return ret; + + /* + * In multicolor case, assume blink mode if PWM is set for at least one + * channel because another channel cannot be in state ON at the same time + */ + for (i = 0; i < num_channels; i++) { + int channel_state; + + channel = subleds[i].channel; + channel_state = (state >> 2 * channel) & KTD202X_CHANNEL_CTRL_MASK(0); + if (channel_state == KTD202X_CHANNEL_CTRL_OFF) + continue; + mode_blink = channel_state == KTD202X_CHANNEL_CTRL_PWM1(0); + break; + } + + for (i = 0; i < num_channels; i++) { + enum led_brightness brightness; + int mode; + + brightness = subleds[i].brightness; + channel = subleds[i].channel; + + if (brightness) { + /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */ + ret = regmap_write(led->chip->regmap, KTD202X_REG_LED_IOUT(channel), + brightness - 1); + if (ret) + return ret; + + if (mode_blink) + mode = KTD202X_CHANNEL_CTRL_PWM1(channel); + else + mode = KTD202X_CHANNEL_CTRL_ON(channel); + } else { + mode = KTD202X_CHANNEL_CTRL_OFF; + } + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + KTD202X_CHANNEL_CTRL_MASK(channel), mode); + if (ret) + return ret; + } + + if (!ktd202x_chip_in_use(led->chip)) + return ktd202x_chip_disable(led->chip); + + return 0; +} + +static int ktd202x_brightness_single_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev); + struct mc_subled info; + int ret; + + cdev->brightness = value; + + mutex_lock(&led->chip->mutex); + + info.brightness = value; + info.channel = led->index; + ret = ktd202x_brightness_set(led, &info, 1); + + mutex_unlock(&led->chip->mutex); + + return ret; +} + +static int ktd202x_brightness_mc_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev); + int ret; + + cdev->brightness = value; + + mutex_lock(&led->chip->mutex); + + led_mc_calc_color_components(mc, value); + ret = ktd202x_brightness_set(led, mc->subled_info, mc->num_colors); + + mutex_unlock(&led->chip->mutex); + + return ret; +} + +static int ktd202x_blink_set(struct ktd202x_led *led, unsigned long *delay_on, + unsigned long *delay_off, struct mc_subled *subleds, + unsigned int num_channels) +{ + unsigned long delay_total_ms; + int ret, num_steps, on; + u8 ctrl_mask = 0; + u8 ctrl_pwm1 = 0; + u8 ctrl_on = 0; + int i; + + mutex_lock(&led->chip->mutex); + + for (i = 0; i < num_channels; i++) { + int channel = subleds[i].channel; + + ctrl_mask |= KTD202X_CHANNEL_CTRL_MASK(channel); + ctrl_on |= KTD202X_CHANNEL_CTRL_ON(channel); + ctrl_pwm1 |= KTD202X_CHANNEL_CTRL_PWM1(channel); + } + + /* Never off - brightness is already set, disable blinking */ + if (!*delay_off) { + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + ctrl_mask, ctrl_on); + goto out; + } + + /* Convert into values the HW will understand. */ + + /* Integer representation of time of flash period */ + num_steps = (*delay_on + *delay_off - KTD202X_FLASH_PERIOD_MIN_MS) / + KTD202X_FLASH_PERIOD_STEP_MS; + num_steps = clamp(num_steps, 0, KTD202X_FLASH_PERIOD_MAX_STEPS); + + /* Integer representation of percentage of LED ON time */ + on = (*delay_on * KTD202X_FLASH_ON_MAX) / (*delay_on + *delay_off); + + /* Actually used delay_{on,off} values */ + delay_total_ms = num_steps * KTD202X_FLASH_PERIOD_STEP_MS + KTD202X_FLASH_PERIOD_MIN_MS; + *delay_on = (delay_total_ms * on) / KTD202X_FLASH_ON_MAX; + *delay_off = delay_total_ms - *delay_on; + + /* Set timings */ + ret = regmap_write(led->chip->regmap, KTD202X_REG_FLASH_PERIOD, num_steps); + if (ret) + goto out; + + ret = regmap_write(led->chip->regmap, KTD202X_REG_PWM1_TIMER, on); + if (ret) + goto out; + + ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, + ctrl_mask, ctrl_pwm1); +out: + mutex_unlock(&led->chip->mutex); + return ret; +} + +static int ktd202x_blink_single_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev); + struct mc_subled info; + int ret; + + if (!cdev->brightness) { + ret = ktd202x_brightness_single_set(cdev, KTD202X_MAX_BRIGHTNESS); + if (ret) + return ret; + } + + /* If no blink specified, default to 1 Hz. */ + if (!*delay_off && !*delay_on) { + *delay_off = 500; + *delay_on = 500; + } + + /* Never on - just set to off */ + if (!*delay_on) + return ktd202x_brightness_single_set(cdev, LED_OFF); + + info.channel = led->index; + + return ktd202x_blink_set(led, delay_on, delay_off, &info, 1); +} + +static int ktd202x_blink_mc_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); + struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev); + int ret; + + if (!cdev->brightness) { + ret = ktd202x_brightness_mc_set(cdev, KTD202X_MAX_BRIGHTNESS); + if (ret) + return ret; + } + + /* If no blink specified, default to 1 Hz. */ + if (!*delay_off && !*delay_on) { + *delay_off = 500; + *delay_on = 500; + } + + /* Never on - just set to off */ + if (!*delay_on) + return ktd202x_brightness_mc_set(cdev, LED_OFF); + + return ktd202x_blink_set(led, delay_on, delay_off, mc->subled_info, + mc->num_colors); +} + +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 mc_subled *info; + int num_channels; + int i = 0; + + num_channels = 0; + fwnode_for_each_child_node(fwnode, child) + num_channels++; + + if (!num_channels || num_channels > chip->num_leds) + return -EINVAL; + + info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + fwnode_for_each_child_node(fwnode, child) { + u32 mono_color; + u32 reg; + int ret; + + ret = fwnode_property_read_u32(child, "reg", ®); + if (ret != 0 || reg >= chip->num_leds) { + dev_err(chip->dev, "invalid 'reg' of %pfw\n", child); + fwnode_handle_put(child); + return ret; + } + + ret = fwnode_property_read_u32(child, "color", &mono_color); + if (ret < 0 && ret != -EINVAL) { + dev_err(chip->dev, "failed to parse 'color' of %pfw\n", child); + fwnode_handle_put(child); + return ret; + } + + info[i].color_index = mono_color; + info[i].channel = reg; + info[i].intensity = KTD202X_MAX_BRIGHTNESS; + i++; + } + + led->mcdev.subled_info = info; + led->mcdev.num_colors = num_channels; + + cdev = &led->mcdev.led_cdev; + cdev->brightness_set_blocking = ktd202x_brightness_mc_set; + cdev->blink_set = ktd202x_blink_mc_set; + + return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data); +} + +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 = fwnode_property_read_u32(fwnode, "reg", ®); + if (ret != 0 || reg >= chip->num_leds) { + dev_err(chip->dev, "invalid 'reg' of %pfw\n", fwnode); + return -EINVAL; + } + led->index = reg; + + cdev = &led->cdev; + cdev->brightness_set_blocking = ktd202x_brightness_single_set; + cdev->blink_set = ktd202x_blink_single_set; + + return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data); +} + +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 = {}; + struct led_classdev *cdev; + u32 color; + int ret; + + /* Color property is optional in single color case */ + ret = fwnode_property_read_u32(fwnode, "color", &color); + if (ret < 0 && ret != -EINVAL) { + dev_err(chip->dev, "failed to parse 'color' of %pfw\n", fwnode); + return ret; + } + + led->chip = chip; + init_data.fwnode = fwnode; + + if (color == LED_COLOR_ID_RGB) { + cdev = &led->mcdev.led_cdev; + ret = ktd202x_setup_led_rgb(chip, fwnode, led, &init_data); + } else { + cdev = &led->cdev; + ret = ktd202x_setup_led_single(chip, fwnode, led, &init_data); + } + + if (ret) { + dev_err(chip->dev, "unable to register %s\n", cdev->name); + return ret; + } + + cdev->max_brightness = KTD202X_MAX_BRIGHTNESS; + + return 0; +} + +static int ktd202x_probe_fw(struct ktd202x *chip) +{ + struct device *dev = chip->dev; + int count; + int i = 0; + + count = device_get_child_node_count(dev); + if (!count || count > chip->num_leds) + return -EINVAL; + + regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET); + + /* Allow the device to execute the complete reset */ + usleep_range(200, 300); + + device_for_each_child_node_scoped(dev, child) { + int ret = ktd202x_add_led(chip, child, i); + + if (ret) + return ret; + + i++; + } + + return 0; +} + +static const struct regmap_config ktd202x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0x09, + .cache_type = REGCACHE_FLAT, + .reg_defaults = ktd202x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(ktd202x_reg_defaults), +}; + +static int ktd202x_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct ktd202x *chip; + int count; + int ret; + + count = device_get_child_node_count(dev); + if (!count || count > KTD202X_MAX_LEDS) + return dev_err_probe(dev, -EINVAL, "Incorrect number of leds (%d)", count); + + chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = dev; + i2c_set_clientdata(client, chip); + + chip->regmap = devm_regmap_init_i2c(client, &ktd202x_regmap_config); + if (IS_ERR(chip->regmap)) { + ret = dev_err_probe(dev, PTR_ERR(chip->regmap), + "Failed to allocate register map.\n"); + 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); + if (ret < 0) { + dev_err_probe(dev, ret, "Failed to request regulators.\n"); + return ret; + } + + ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err_probe(dev, ret, "Failed to enable regulators.\n"); + return ret; + } + + ret = ktd202x_probe_fw(chip); + if (ret < 0) { + regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + return ret; + } + + ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators); + if (ret) { + dev_err_probe(dev, ret, "Failed to disable regulators.\n"); + return ret; + } + + return 0; +} + +static void ktd202x_remove(struct i2c_client *client) +{ + struct ktd202x *chip = i2c_get_clientdata(client); + + ktd202x_chip_disable(chip); +} + +static void ktd202x_shutdown(struct i2c_client *client) +{ + struct ktd202x *chip = i2c_get_clientdata(client); + + /* Reset registers to make sure all LEDs are off before shutdown */ + 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); + +static struct i2c_driver ktd202x_driver = { + .driver = { + .name = "leds-ktd202x", + .of_match_table = ktd202x_match_table, + }, + .probe = ktd202x_probe, + .remove = ktd202x_remove, + .shutdown = ktd202x_shutdown, + .id_table = ktd202x_id, +}; +module_i2c_driver(ktd202x_driver); + +MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>"); +MODULE_DESCRIPTION("Kinetic KTD2026/7 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c b/drivers/leds/rgb/leds-mt6370-rgb.c index bb62431efe83..c5927d0eb830 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,11 +149,10 @@ 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; - struct mt6370_led leds[]; + struct mt6370_led leds[] __counted_by(leds_count); }; static const struct reg_field common_reg_fields[F_MAX_FIELDS] = { @@ -200,17 +199,17 @@ static const struct reg_field mt6372_reg_fields[F_MAX_FIELDS] = { /* Current unit: microamp, time unit: millisecond */ static const struct linear_range common_led_ranges[R_MAX_RANGES] = { - [R_LED123_CURR] = { 4000, 1, 6, 4000 }, - [R_LED4_CURR] = { 2000, 1, 3, 2000 }, - [R_LED_TRFON] = { 125, 0, 15, 200 }, - [R_LED_TOFF] = { 250, 0, 15, 400 }, + [R_LED123_CURR] = LINEAR_RANGE(4000, 1, 6, 4000), + [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 3, 2000), + [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 200), + [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 400), }; static const struct linear_range mt6372_led_ranges[R_MAX_RANGES] = { - [R_LED123_CURR] = { 2000, 1, 14, 2000 }, - [R_LED4_CURR] = { 2000, 1, 14, 2000 }, - [R_LED_TRFON] = { 125, 0, 15, 250 }, - [R_LED_TOFF] = { 250, 0, 15, 500 }, + [R_LED123_CURR] = LINEAR_RANGE(2000, 1, 14, 2000), + [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 14, 2000), + [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 250), + [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 500), }; static const unsigned int common_tfreqs[] = { @@ -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 new file mode 100644 index 000000000000..85d6be6fff2b --- /dev/null +++ b/drivers/leds/rgb/leds-ncp5623.c @@ -0,0 +1,270 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * NCP5623 Multi-LED Driver + * + * Author: Abdel Alkuor <alkuor@gmail.com> + * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf + */ + +#include <linux/i2c.h> +#include <linux/module.h> + +#include <linux/led-class-multicolor.h> + +#define NCP5623_FUNCTION_OFFSET 0x5 +#define NCP5623_REG(x) ((x) << NCP5623_FUNCTION_OFFSET) + +#define NCP5623_SHUTDOWN_REG NCP5623_REG(0x0) +#define NCP5623_ILED_REG NCP5623_REG(0x1) +#define NCP5623_PWM_REG(index) NCP5623_REG(0x2 + (index)) +#define NCP5623_UPWARD_STEP_REG NCP5623_REG(0x5) +#define NCP5623_DOWNWARD_STEP_REG NCP5623_REG(0x6) +#define NCP5623_DIMMING_TIME_REG NCP5623_REG(0x7) + +#define NCP5623_MAX_BRIGHTNESS 0x1f +#define NCP5623_MAX_DIM_TIME_MS 240 +#define NCP5623_DIM_STEP_MS 8 + +struct ncp5623 { + struct i2c_client *client; + struct led_classdev_mc mc_dev; + struct mutex lock; + + int current_brightness; + unsigned long delay; +}; + +static int ncp5623_write(struct i2c_client *client, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(client, reg | data, 0); +} + +static int ncp5623_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); + int ret; + + guard(mutex)(&ncp->lock); + + if (ncp->delay && time_is_after_jiffies(ncp->delay)) + return -EBUSY; + + ncp->delay = 0; + + for (int i = 0; i < mc_cdev->num_colors; i++) { + ret = ncp5623_write(ncp->client, + NCP5623_PWM_REG(mc_cdev->subled_info[i].channel), + min(mc_cdev->subled_info[i].intensity, + NCP5623_MAX_BRIGHTNESS)); + if (ret) + return ret; + } + + ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, 0); + if (ret) + return ret; + + ret = ncp5623_write(ncp->client, NCP5623_ILED_REG, brightness); + if (ret) + return ret; + + ncp->current_brightness = brightness; + + return 0; +} + +static int ncp5623_pattern_set(struct led_classdev *cdev, + struct led_pattern *pattern, + u32 len, int repeat) +{ + struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev); + struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev); + int brightness_diff; + u8 reg; + int ret; + + guard(mutex)(&ncp->lock); + + if (ncp->delay && time_is_after_jiffies(ncp->delay)) + return -EBUSY; + + ncp->delay = 0; + + if (pattern[0].delta_t > NCP5623_MAX_DIM_TIME_MS || + (pattern[0].delta_t % NCP5623_DIM_STEP_MS) != 0) + return -EINVAL; + + brightness_diff = pattern[0].brightness - ncp->current_brightness; + + if (brightness_diff == 0) + return 0; + + if (pattern[0].delta_t) { + if (brightness_diff > 0) + reg = NCP5623_UPWARD_STEP_REG; + else + reg = NCP5623_DOWNWARD_STEP_REG; + } else { + reg = NCP5623_ILED_REG; + } + + ret = ncp5623_write(ncp->client, reg, + min(pattern[0].brightness, NCP5623_MAX_BRIGHTNESS)); + if (ret) + return ret; + + ret = ncp5623_write(ncp->client, + NCP5623_DIMMING_TIME_REG, + pattern[0].delta_t / NCP5623_DIM_STEP_MS); + if (ret) + return ret; + + /* + * During testing, when the brightness difference is 1, for some + * unknown reason, the time factor it takes to change to the new + * value is the longest time possible. Otherwise, the time factor + * is simply the brightness difference. + * + * For example: + * current_brightness = 20 and new_brightness = 21 then the time it + * takes to set the new brightness increments to the maximum possible + * brightness from 20 then from 0 to 21. + * time_factor = max_brightness - 20 + 21 + */ + if (abs(brightness_diff) == 1) + ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff; + else + ncp->delay = abs(brightness_diff); + + ncp->delay = msecs_to_jiffies(ncp->delay * pattern[0].delta_t) + jiffies; + + ncp->current_brightness = pattern[0].brightness; + + return 0; +} + +static int ncp5623_pattern_clear(struct led_classdev *led_cdev) +{ + return 0; +} + +static int ncp5623_probe(struct i2c_client *client) +{ + struct device *dev = &client->dev; + struct fwnode_handle *mc_node, *led_node; + struct led_init_data init_data = { }; + struct ncp5623 *ncp; + struct mc_subled *subled_info; + unsigned int num_subleds; + u32 color_index; + u32 reg; + int ret; + + ncp = devm_kzalloc(dev, sizeof(*ncp), GFP_KERNEL); + if (!ncp) + return -ENOMEM; + + ncp->client = client; + + mc_node = device_get_named_child_node(dev, "multi-led"); + if (!mc_node) + return -EINVAL; + + num_subleds = fwnode_get_child_node_count(mc_node); + + subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL); + if (!subled_info) { + ret = -ENOMEM; + goto release_mc_node; + } + + fwnode_for_each_child_node(mc_node, led_node) { + ret = fwnode_property_read_u32(led_node, "color", &color_index); + if (ret) + goto release_led_node; + + ret = fwnode_property_read_u32(led_node, "reg", ®); + 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; + } + + init_data.fwnode = mc_node; + + ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS; + ncp->mc_dev.subled_info = subled_info; + ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set; + ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set; + ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear; + ncp->mc_dev.led_cdev.default_trigger = "pattern"; + + mutex_init(&ncp->lock); + i2c_set_clientdata(client, ncp); + + ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data); + if (ret) + goto destroy_lock; + + return 0; + +destroy_lock: + mutex_destroy(&ncp->lock); + +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) +{ + struct ncp5623 *ncp = i2c_get_clientdata(client); + + mutex_lock(&ncp->lock); + ncp->delay = 0; + mutex_unlock(&ncp->lock); + + ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0); + led_classdev_multicolor_unregister(&ncp->mc_dev); + mutex_destroy(&ncp->lock); +} + +static void ncp5623_shutdown(struct i2c_client *client) +{ + struct ncp5623 *ncp = i2c_get_clientdata(client); + + if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN)) + ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0); + + mutex_destroy(&ncp->lock); +} + +static const struct of_device_id ncp5623_id[] = { + { .compatible = "onnn,ncp5623" }, + { } +}; +MODULE_DEVICE_TABLE(of, ncp5623_id); + +static struct i2c_driver ncp5623_i2c_driver = { + .driver = { + .name = "ncp5623", + .of_match_table = ncp5623_id, + }, + .probe = ncp5623_probe, + .remove = ncp5623_remove, + .shutdown = ncp5623_shutdown, +}; + +module_i2c_driver(ncp5623_i2c_driver); + +MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>"); +MODULE_DESCRIPTION("NCP5623 Multi-LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c index 46cd062b8b24..e0d7d3c9215c 100644 --- a/drivers/leds/rgb/leds-pwm-multicolor.c +++ b/drivers/leds/rgb/leds-pwm-multicolor.c @@ -50,9 +50,15 @@ 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; - ret = pwm_apply_state(priv->leds[i].pwm, - &priv->leds[i].state); + /* + * 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) break; } @@ -101,12 +107,12 @@ release_fwnode: static int led_pwm_mc_probe(struct platform_device *pdev) { - struct fwnode_handle *mcnode, *fwnode; + struct fwnode_handle *mcnode; struct led_init_data init_data = {}; struct led_classdev *cdev; struct mc_subled *subled; struct pwm_mc_led *priv; - int count = 0; + unsigned int count; int ret = 0; mcnode = device_get_named_child_node(&pdev->dev, "multi-led"); @@ -115,8 +121,7 @@ static int led_pwm_mc_probe(struct platform_device *pdev) "expected multi-led node\n"); /* count the nodes inside the multi-led node */ - fwnode_for_each_child_node(mcnode, fwnode) - count++; + count = fwnode_get_child_node_count(mcnode); priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count), GFP_KERNEL); @@ -135,8 +140,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 59581b3e25ca..72da0bf469ad 100644 --- a/drivers/leds/rgb/leds-qcom-lpg.c +++ b/drivers/leds/rgb/leds-qcom-lpg.c @@ -2,18 +2,19 @@ /* * 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) Qualcomm Technologies, Inc. and/or its subsidiaries. */ #include <linux/bits.h> #include <linux/bitfield.h> #include <linux/led-class-multicolor.h> #include <linux/module.h> +#include <linux/nvmem-consumer.h> #include <linux/of.h> -#include <linux/of_device.h> #include <linux/platform_device.h> #include <linux/pwm.h> #include <linux/regmap.h> #include <linux/slab.h> +#include <linux/soc/qcom/qcom-pbs.h> #define LPG_SUBTYPE_REG 0x05 #define LPG_SUBTYPE_LPG 0x2 @@ -23,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 @@ -40,6 +42,10 @@ #define PWM_SEC_ACCESS_REG 0xd0 #define PWM_DTEST_REG(x) (0xe2 + (x) - 1) +#define SDAM_REG_PBS_SEQ_EN 0x42 +#define SDAM_PBS_TRIG_SET 0xe5 +#define SDAM_PBS_TRIG_CLR 0xe6 + #define TRI_LED_SRC_SEL 0x45 #define TRI_LED_EN_CTL 0x46 #define TRI_LED_ATC_CTL 0x47 @@ -49,9 +55,31 @@ #define LPG_RESOLUTION_9BIT BIT(9) #define LPG_RESOLUTION_15BIT BIT(15) +#define PPG_MAX_LED_BRIGHTNESS 255 + #define LPG_MAX_M 7 #define LPG_MAX_PREDIV 6 +#define DEFAULT_TICK_DURATION_US 7800 +#define RAMP_STEP_DURATION(x) (((x) * 1000 / DEFAULT_TICK_DURATION_US) & 0xff) + +#define SDAM_MAX_DEVICES 2 +/* LPG common config settings for PPG */ +#define SDAM_START_BASE 0x40 +#define SDAM_REG_RAMP_STEP_DURATION 0x47 + +#define SDAM_LUT_SDAM_LUT_PATTERN_OFFSET 0x45 +#define SDAM_LPG_SDAM_LUT_PATTERN_OFFSET 0x80 + +/* LPG per channel config settings for PPG */ +#define SDAM_LUT_EN_OFFSET 0x0 +#define SDAM_PATTERN_CONFIG_OFFSET 0x1 +#define SDAM_END_INDEX_OFFSET 0x3 +#define SDAM_START_INDEX_OFFSET 0x4 +#define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6 +#define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8 +#define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9 + struct lpg_channel; struct lpg_data; @@ -65,6 +93,10 @@ struct lpg_data; * @lut_base: base address of the LUT block (optional) * @lut_size: number of entries in the LUT block * @lut_bitmap: allocation bitmap for LUT entries + * @pbs_dev: PBS device + * @lpg_chan_sdam: LPG SDAM peripheral device + * @lut_sdam: LUT SDAM peripheral device + * @pbs_en_bitmap: bitmap for tracking PBS triggers * @triled_base: base address of the TRILED block (optional) * @triled_src: power-source for the TRILED * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register @@ -78,7 +110,7 @@ struct lpg { struct mutex lock; - struct pwm_chip pwm; + struct pwm_chip *pwm; const struct lpg_data *data; @@ -86,6 +118,11 @@ struct lpg { u32 lut_size; unsigned long *lut_bitmap; + struct pbs_dev *pbs_dev; + struct nvmem_device *lpg_chan_sdam; + struct nvmem_device *lut_sdam; + unsigned long pbs_en_bitmap; + u32 triled_base; u32 triled_src; bool triled_has_atc_ctl; @@ -102,6 +139,7 @@ struct lpg { * @triled_mask: mask in TRILED to enable this channel * @lut_mask: mask in LUT to start pattern generator for this channel * @subtype: PMIC hardware block subtype + * @sdam_offset: channel offset in LPG SDAM * @in_use: channel is exposed to LED framework * @color: color of the LED attached to this channel * @dtest_line: DTEST line for output, or 0 if disabled @@ -130,6 +168,7 @@ struct lpg_channel { unsigned int triled_mask; unsigned int lut_mask; unsigned int subtype; + u32 sdam_offset; bool in_use; @@ -174,15 +213,17 @@ struct lpg_led { struct led_classdev_mc mcdev; unsigned int num_channels; - struct lpg_channel *channels[]; + struct lpg_channel *channels[] __counted_by(num_channels); }; /** * struct lpg_channel_data - per channel initialization data + * @sdam_offset: Channel offset in LPG SDAM * @base: base address for PWM channel registers * @triled_mask: bitmask for controlling this channel in TRILED */ struct lpg_channel_data { + unsigned int sdam_offset; unsigned int base; u8 triled_mask; }; @@ -207,6 +248,71 @@ struct lpg_data { const struct lpg_channel_data *channels; }; +#define PBS_SW_TRIG_BIT BIT(0) + +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); + if (rc < 0) + return rc; + + if (lpg->lut_sdam) { + val = PBS_SW_TRIG_BIT; + rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_CLR, 1, &val); + if (rc < 0) + return rc; + } + } + + return 0; +} + +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) + return rc; + + if (lpg->lut_sdam) { + rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_SET, 1, &val); + if (rc < 0) + return rc; + } else { + rc = qcom_pbs_trigger_event(lpg->pbs_dev, val); + if (rc < 0) + return rc; + } + } + lpg->pbs_en_bitmap |= lut_mask; + + return 0; +} + +static int lpg_sdam_configure_triggers(struct lpg_channel *chan, u8 set_trig) +{ + u32 addr = SDAM_LUT_EN_OFFSET + chan->sdam_offset; + + if (!chan->lpg->lpg_chan_sdam) + return 0; + + return nvmem_device_write(chan->lpg->lpg_chan_sdam, addr, 1, &set_trig); +} + static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable) { /* Skip if we don't have a triled block */ @@ -217,6 +323,47 @@ static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable) mask, enable); } +static int lpg_lut_store_sdam(struct lpg *lpg, struct led_pattern *pattern, + size_t len, unsigned int *lo_idx, unsigned int *hi_idx) +{ + unsigned int idx; + u8 brightness; + int i, rc; + u16 addr; + + if (len > lpg->lut_size) { + dev_err(lpg->dev, "Pattern length (%zu) exceeds maximum pattern length (%d)\n", + len, lpg->lut_size); + return -EINVAL; + } + + idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, 0, len, 0); + if (idx >= lpg->lut_size) + return -ENOSPC; + + for (i = 0; i < len; i++) { + brightness = pattern[i].brightness; + + if (lpg->lut_sdam) { + addr = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET + i + idx; + rc = nvmem_device_write(lpg->lut_sdam, addr, 1, &brightness); + } else { + addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx; + rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness); + } + + if (rc < 0) + return rc; + } + + bitmap_set(lpg->lut_bitmap, idx, len); + + *lo_idx = idx; + *hi_idx = idx + len - 1; + + return 0; +} + static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern, size_t len, unsigned int *lo_idx, unsigned int *hi_idx) { @@ -257,14 +404,17 @@ static void lpg_lut_free(struct lpg *lpg, unsigned int lo_idx, unsigned int hi_i static int lpg_lut_sync(struct lpg *lpg, unsigned int mask) { + if (!lpg->lut_base) + return 0; + return regmap_write(lpg->map, lpg->lut_base + RAMP_CONTROL_REG, 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) { @@ -287,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. @@ -312,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; @@ -333,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]; @@ -380,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]; } @@ -409,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); @@ -463,6 +613,49 @@ static void lpg_apply_pwm_value(struct lpg_channel *chan) #define LPG_PATTERN_CONFIG_PAUSE_HI BIT(1) #define LPG_PATTERN_CONFIG_PAUSE_LO BIT(0) +static void lpg_sdam_apply_lut_control(struct lpg_channel *chan) +{ + struct nvmem_device *lpg_chan_sdam = chan->lpg->lpg_chan_sdam; + unsigned int lo_idx = chan->pattern_lo_idx; + unsigned int hi_idx = chan->pattern_hi_idx; + u8 val = 0, conf = 0, lut_offset = 0; + unsigned int hi_pause, lo_pause; + struct lpg *lpg = chan->lpg; + + if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx) + return; + + hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, chan->ramp_tick_ms); + lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, chan->ramp_tick_ms); + + if (!chan->ramp_oneshot) + conf |= LPG_PATTERN_CONFIG_REPEAT; + if (chan->ramp_hi_pause_ms && lpg->lut_sdam) + conf |= LPG_PATTERN_CONFIG_PAUSE_HI; + if (chan->ramp_lo_pause_ms && lpg->lut_sdam) + conf |= LPG_PATTERN_CONFIG_PAUSE_LO; + + if (lpg->lut_sdam) { + lut_offset = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET - SDAM_START_BASE; + hi_idx += lut_offset; + lo_idx += lut_offset; + } + + nvmem_device_write(lpg_chan_sdam, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val); + nvmem_device_write(lpg_chan_sdam, SDAM_PATTERN_CONFIG_OFFSET + chan->sdam_offset, 1, &conf); + nvmem_device_write(lpg_chan_sdam, SDAM_END_INDEX_OFFSET + chan->sdam_offset, 1, &hi_idx); + nvmem_device_write(lpg_chan_sdam, SDAM_START_INDEX_OFFSET + chan->sdam_offset, 1, &lo_idx); + + val = RAMP_STEP_DURATION(chan->ramp_tick_ms); + nvmem_device_write(lpg_chan_sdam, SDAM_REG_RAMP_STEP_DURATION, 1, &val); + + if (lpg->lut_sdam) { + nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_HI_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &hi_pause); + nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_LO_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &lo_pause); + } + +} + static void lpg_apply_lut_control(struct lpg_channel *chan) { struct lpg *lpg = chan->lpg; @@ -553,9 +746,9 @@ static int lpg_parse_dtest(struct lpg *lpg) ret = count; goto err_malformed; } else if (count != lpg->data->num_channels * 2) { - dev_err(lpg->dev, "qcom,dtest needs to be %d items\n", - lpg->data->num_channels * 2); - return -EINVAL; + return dev_err_probe(lpg->dev, -EINVAL, + "qcom,dtest needs to be %d items\n", + lpg->data->num_channels * 2); } for (i = 0; i < lpg->data->num_channels; i++) { @@ -575,8 +768,7 @@ static int lpg_parse_dtest(struct lpg *lpg) return 0; err_malformed: - dev_err(lpg->dev, "malformed qcom,dtest\n"); - return ret; + return dev_err_probe(lpg->dev, ret, "malformed qcom,dtest\n"); } static void lpg_apply_dtest(struct lpg_channel *chan) @@ -598,7 +790,10 @@ static void lpg_apply(struct lpg_channel *chan) lpg_apply_pwm_value(chan); lpg_apply_control(chan); lpg_apply_sync(chan); - lpg_apply_lut_control(chan); + if (chan->lpg->lpg_chan_sdam) + lpg_sdam_apply_lut_control(chan); + else + lpg_apply_lut_control(chan); lpg_enable_glitch(chan); } @@ -623,6 +818,7 @@ static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev, chan->ramp_enabled = false; } else if (chan->pattern_lo_idx != chan->pattern_hi_idx) { lpg_calc_freq(chan, NSEC_PER_MSEC); + lpg_sdam_configure_triggers(chan, 1); chan->enabled = true; chan->ramp_enabled = true; @@ -650,8 +846,10 @@ static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev, triled_set(lpg, triled_mask, triled_enabled); /* Trigger start of ramp generator(s) */ - if (lut_mask) + if (lut_mask) { lpg_lut_sync(lpg, lut_mask); + lpg_set_pbs_trigger(lpg, lut_mask); + } } static int lpg_brightness_single_set(struct led_classdev *cdev, @@ -768,9 +966,9 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, struct led_pattern *pattern; unsigned int brightness_a; unsigned int brightness_b; + unsigned int hi_pause = 0; + unsigned int lo_pause = 0; unsigned int actual_len; - unsigned int hi_pause; - unsigned int lo_pause; unsigned int delta_t; unsigned int lo_idx; unsigned int hi_idx; @@ -837,18 +1035,24 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, * If the specified pattern is a palindrome the ping pong mode is * enabled. In this scenario the delta_t of the middle entry (i.e. the * last in the programmed pattern) determines the "high pause". + * + * SDAM-based devices do not support "ping pong", and only supports + * "low pause" and "high pause" with a dedicated SDAM LUT. */ /* Detect palindromes and use "ping pong" to reduce LUT usage */ - for (i = 0; i < len / 2; i++) { - brightness_a = pattern[i].brightness; - brightness_b = pattern[len - i - 1].brightness; - - if (brightness_a != brightness_b) { - ping_pong = false; - break; + if (lpg->lut_base) { + for (i = 0; i < len / 2; i++) { + brightness_a = pattern[i].brightness; + brightness_b = pattern[len - i - 1].brightness; + + if (brightness_a != brightness_b) { + ping_pong = false; + break; + } } - } + } else + ping_pong = false; /* The pattern length to be written to the LUT */ if (ping_pong) @@ -876,12 +1080,27 @@ static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern, if (delta_t >= BIT(9)) goto out_free_pattern; - /* Find "low pause" and "high pause" in the pattern */ - lo_pause = pattern[0].delta_t; - hi_pause = pattern[actual_len - 1].delta_t; + /* + * Find "low pause" and "high pause" in the pattern in the LUT case. + * SDAM-based devices without dedicated LUT SDAM require equal + * duration of all steps. + */ + if (lpg->lut_base || lpg->lut_sdam) { + lo_pause = pattern[0].delta_t; + hi_pause = pattern[actual_len - 1].delta_t; + } else { + if (delta_t != pattern[0].delta_t || delta_t != pattern[actual_len - 1].delta_t) + goto out_free_pattern; + } + mutex_lock(&lpg->lock); - ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx); + + if (lpg->lut_base) + ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx); + else + ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx); + if (ret < 0) goto out_unlock; @@ -929,7 +1148,12 @@ static int lpg_pattern_mc_set(struct led_classdev *cdev, { struct led_classdev_mc *mc = lcdev_to_mccdev(cdev); struct lpg_led *led = container_of(mc, struct lpg_led, mcdev); - int ret; + unsigned int triled_mask = 0; + int ret, i; + + for (i = 0; i < led->num_channels; i++) + triled_mask |= led->channels[i]->triled_mask; + triled_set(led->lpg, triled_mask, 0); ret = lpg_pattern_set(led, pattern, len, repeat); if (ret < 0) @@ -954,6 +1178,8 @@ static int lpg_pattern_clear(struct lpg_led *led) for (i = 0; i < led->num_channels; i++) { chan = led->channels[i]; + lpg_sdam_configure_triggers(chan, 0); + lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask); chan->pattern_lo_idx = 0; chan->pattern_hi_idx = 0; } @@ -978,9 +1204,14 @@ static int lpg_pattern_mc_clear(struct led_classdev *cdev) return lpg_pattern_clear(led); } +static inline struct lpg *lpg_pwm_from_chip(struct pwm_chip *chip) +{ + return pwmchip_get_drvdata(chip); +} + static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; return chan->in_use ? -EBUSY : 0; @@ -996,7 +1227,7 @@ static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm) static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, const struct pwm_state *state) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; int ret = 0; @@ -1016,8 +1247,6 @@ static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm, lpg_apply(chan); - triled_set(lpg, chan->triled_mask, chan->enabled ? chan->triled_mask : 0); - out_unlock: mutex_unlock(&lpg->lock); @@ -1027,7 +1256,7 @@ out_unlock: static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm, struct pwm_state *state) { - struct lpg *lpg = container_of(chip, struct lpg, pwm); + struct lpg *lpg = lpg_pwm_from_chip(chip); struct lpg_channel *chan = &lpg->channels[pwm->hwpwm]; unsigned int resolution; unsigned int pre_div; @@ -1046,7 +1275,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) { @@ -1061,7 +1290,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 { @@ -1086,21 +1315,23 @@ static const struct pwm_ops lpg_pwm_ops = { .request = lpg_pwm_request, .apply = lpg_pwm_apply, .get_state = lpg_pwm_get_state, - .owner = THIS_MODULE, }; static int lpg_add_pwm(struct lpg *lpg) { + struct pwm_chip *chip; int ret; - lpg->pwm.base = -1; - lpg->pwm.dev = lpg->dev; - lpg->pwm.npwm = lpg->num_channels; - lpg->pwm.ops = &lpg_pwm_ops; + lpg->pwm = chip = devm_pwmchip_alloc(lpg->dev, lpg->num_channels, 0); + if (IS_ERR(chip)) + return PTR_ERR(chip); - ret = pwmchip_add(&lpg->pwm); + chip->ops = &lpg_pwm_ops; + pwmchip_set_drvdata(chip, lpg); + + ret = devm_pwmchip_add(lpg->dev, chip); if (ret) - dev_err(lpg->dev, "failed to add PWM chip: ret %d\n", ret); + dev_err_probe(lpg->dev, ret, "failed to add PWM chip\n"); return ret; } @@ -1114,19 +1345,16 @@ static int lpg_parse_channel(struct lpg *lpg, struct device_node *np, int ret; ret = of_property_read_u32(np, "reg", ®); - if (ret || !reg || reg > lpg->num_channels) { - dev_err(lpg->dev, "invalid \"reg\" of %pOFn\n", np); - return -EINVAL; - } + if (ret || !reg || reg > lpg->num_channels) + return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np); chan = &lpg->channels[reg - 1]; chan->in_use = true; ret = of_property_read_u32(np, "color", &color); - if (ret < 0 && ret != -EINVAL) { - dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); - return ret; - } + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(lpg->dev, ret, + "failed to parse \"color\" of %pOF\n", np); chan->color = color; @@ -1139,7 +1367,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; @@ -1149,12 +1376,11 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) int i; ret = of_property_read_u32(np, "color", &color); - if (ret < 0 && ret != -EINVAL) { - dev_err(lpg->dev, "failed to parse \"color\" of %pOF\n", np); - return ret; - } + if (ret < 0 && ret != -EINVAL) + return dev_err_probe(lpg->dev, ret, + "failed to parse \"color\" of %pOF\n", np); - if (color == LED_COLOR_ID_RGB) + if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) num_channels = of_get_available_child_count(np); else num_channels = 1; @@ -1166,17 +1392,15 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) led->lpg = lpg; led->num_channels = num_channels; - if (color == LED_COLOR_ID_RGB) { + if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) { info = devm_kcalloc(lpg->dev, num_channels, sizeof(*info), GFP_KERNEL); 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; @@ -1190,8 +1414,8 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set_blocking = lpg_brightness_mc_set; cdev->blink_set = lpg_blink_mc_set; - /* Register pattern accessors only if we have a LUT block */ - if (lpg->lut_base) { + /* Register pattern accessors if we have a LUT block or when using PPG */ + if (lpg->lut_base || lpg->lpg_chan_sdam) { cdev->pattern_set = lpg_pattern_mc_set; cdev->pattern_clear = lpg_pattern_mc_clear; } @@ -1204,15 +1428,19 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) cdev->brightness_set_blocking = lpg_brightness_single_set; cdev->blink_set = lpg_blink_single_set; - /* Register pattern accessors only if we have a LUT block */ - if (lpg->lut_base) { + /* Register pattern accessors if we have a LUT block or when using PPG */ + if (lpg->lut_base || lpg->lpg_chan_sdam) { cdev->pattern_set = lpg_pattern_single_set; cdev->pattern_clear = lpg_pattern_single_clear; } } cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL); - cdev->max_brightness = LPG_RESOLUTION_9BIT - 1; + + if (lpg->lpg_chan_sdam) + cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS; + else + cdev->max_brightness = LPG_RESOLUTION_9BIT - 1; if (!of_property_read_string(np, "default-state", &state) && !strcmp(state, "on")) @@ -1224,12 +1452,12 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np) init_data.fwnode = of_fwnode_handle(np); - if (color == LED_COLOR_ID_RGB) + if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data); else ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data); if (ret) - dev_err(lpg->dev, "unable to register %s\n", cdev->name); + dev_err_probe(lpg->dev, ret, "unable to register %s\n", cdev->name); return ret; } @@ -1253,6 +1481,7 @@ static int lpg_init_channels(struct lpg *lpg) chan->base = data->channels[i].base; chan->triled_mask = data->channels[i].triled_mask; chan->lut_mask = BIT(i); + chan->sdam_offset = data->channels[i].sdam_offset; regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype); } @@ -1275,10 +1504,9 @@ static int lpg_init_triled(struct lpg *lpg) if (lpg->triled_has_src_sel) { ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src); - if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) { - dev_err(lpg->dev, "invalid power source\n"); - return -EINVAL; - } + if (ret || lpg->triled_src == 2 || lpg->triled_src > 3) + return dev_err_probe(lpg->dev, -EINVAL, + "invalid power source\n"); } /* Disable automatic trickle charge LED */ @@ -1299,11 +1527,12 @@ static int lpg_init_lut(struct lpg *lpg) { const struct lpg_data *data = lpg->data; - if (!data->lut_base) + if (!data->lut_size) return 0; - lpg->lut_base = data->lut_base; lpg->lut_size = data->lut_size; + if (data->lut_base) + lpg->lut_base = data->lut_base; lpg->lut_bitmap = devm_bitmap_zalloc(lpg->dev, lpg->lut_size, GFP_KERNEL); if (!lpg->lut_bitmap) @@ -1312,9 +1541,61 @@ static int lpg_init_lut(struct lpg *lpg) return 0; } +static int lpg_init_sdam(struct lpg *lpg) +{ + int i, sdam_count, rc; + u8 val = 0; + + sdam_count = of_property_count_strings(lpg->dev->of_node, "nvmem-names"); + if (sdam_count <= 0) + return 0; + if (sdam_count > SDAM_MAX_DEVICES) + return -EINVAL; + + /* Get the 1st SDAM device for LPG/LUT config */ + lpg->lpg_chan_sdam = devm_nvmem_device_get(lpg->dev, "lpg_chan_sdam"); + if (IS_ERR(lpg->lpg_chan_sdam)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->lpg_chan_sdam), + "Failed to get LPG chan SDAM device\n"); + + if (sdam_count == 1) { + /* Get PBS device node if single SDAM device */ + lpg->pbs_dev = get_pbs_client_device(lpg->dev); + if (IS_ERR(lpg->pbs_dev)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev), + "Failed to get PBS client device\n"); + } else if (sdam_count == 2) { + /* Get the 2nd SDAM device for LUT pattern */ + lpg->lut_sdam = devm_nvmem_device_get(lpg->dev, "lut_sdam"); + if (IS_ERR(lpg->lut_sdam)) + return dev_err_probe(lpg->dev, PTR_ERR(lpg->lut_sdam), + "Failed to get LPG LUT SDAM device\n"); + } + + for (i = 0; i < lpg->num_channels; i++) { + struct lpg_channel *chan = &lpg->channels[i]; + + if (chan->sdam_offset) { + rc = nvmem_device_write(lpg->lpg_chan_sdam, + SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val); + if (rc < 0) + return rc; + + rc = lpg_sdam_configure_triggers(chan, 0); + if (rc < 0) + return rc; + + rc = lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask); + if (rc < 0) + return rc; + } + } + + return 0; +} + static int lpg_probe(struct platform_device *pdev) { - struct device_node *np; struct lpg *lpg; int ret; int i; @@ -1327,8 +1608,6 @@ static int lpg_probe(struct platform_device *pdev) if (!lpg->data) return -EINVAL; - platform_set_drvdata(pdev, lpg); - lpg->dev = &pdev->dev; mutex_init(&lpg->lock); @@ -1348,16 +1627,18 @@ static int lpg_probe(struct platform_device *pdev) if (ret < 0) return ret; + ret = lpg_init_sdam(lpg); + if (ret < 0) + return ret; + ret = lpg_init_lut(lpg); 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++) @@ -1366,14 +1647,22 @@ static int lpg_probe(struct platform_device *pdev) return lpg_add_pwm(lpg); } -static int lpg_remove(struct platform_device *pdev) -{ - struct lpg *lpg = platform_get_drvdata(pdev); +static const struct lpg_data pm660l_lpg_data = { + .lut_base = 0xb000, + .lut_size = 49, - pwmchip_remove(&lpg->pwm); + .triled_base = 0xd000, + .triled_has_atc_ctl = true, + .triled_has_src_sel = true, - return 0; -} + .num_channels = 4, + .channels = (const struct lpg_channel_data[]) { + { .base = 0xb100, .triled_mask = BIT(5) }, + { .base = 0xb200, .triled_mask = BIT(6) }, + { .base = 0xb300, .triled_mask = BIT(7) }, + { .base = 0xb400 }, + }, +}; static const struct lpg_data pm8916_pwm_data = { .num_channels = 1, @@ -1403,6 +1692,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, @@ -1422,11 +1718,13 @@ static const struct lpg_data pm8994_lpg_data = { static const struct lpg_data pmi632_lpg_data = { .triled_base = 0xd000, + .lut_size = 64, + .num_channels = 5, .channels = (const struct lpg_channel_data[]) { - { .base = 0xb300, .triled_mask = BIT(7) }, - { .base = 0xb400, .triled_mask = BIT(6) }, - { .base = 0xb500, .triled_mask = BIT(5) }, + { .base = 0xb300, .triled_mask = BIT(7), .sdam_offset = 0x48 }, + { .base = 0xb400, .triled_mask = BIT(6), .sdam_offset = 0x56 }, + { .base = 0xb500, .triled_mask = BIT(5), .sdam_offset = 0x64 }, { .base = 0xb600 }, { .base = 0xb700 }, }, @@ -1499,11 +1797,13 @@ static const struct lpg_data pm8150l_lpg_data = { static const struct lpg_data pm8350c_pwm_data = { .triled_base = 0xef00, + .lut_size = 122, + .num_channels = 4, .channels = (const struct lpg_channel_data[]) { - { .base = 0xe800, .triled_mask = BIT(7) }, - { .base = 0xe900, .triled_mask = BIT(6) }, - { .base = 0xea00, .triled_mask = BIT(5) }, + { .base = 0xe800, .triled_mask = BIT(7), .sdam_offset = 0x48 }, + { .base = 0xe900, .triled_mask = BIT(6), .sdam_offset = 0x56 }, + { .base = 0xea00, .triled_mask = BIT(5), .sdam_offset = 0x64 }, { .base = 0xeb00 }, }, }; @@ -1517,6 +1817,7 @@ static const struct lpg_data pmk8550_pwm_data = { }; static const struct of_device_id lpg_of_table[] = { + { .compatible = "qcom,pm660l-lpg", .data = &pm660l_lpg_data }, { .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data }, { .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data }, { .compatible = "qcom,pm8350c-pwm", .data = &pm8350c_pwm_data }, @@ -1524,6 +1825,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 }, @@ -1534,7 +1836,6 @@ MODULE_DEVICE_TABLE(of, lpg_of_table); static struct platform_driver lpg_driver = { .probe = lpg_probe, - .remove = lpg_remove, .driver = { .name = "qcom-spmi-lpg", .of_match_table = lpg_of_table, diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simatic/Kconfig index 44fa0f93cb3b..e616cc6d6051 100644 --- a/drivers/leds/simple/Kconfig +++ b/drivers/leds/simatic/Kconfig @@ -1,7 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-only config LEDS_SIEMENS_SIMATIC_IPC tristate "LED driver for Siemens Simatic IPCs" + depends on LEDS_CLASS depends on SIEMENS_SIMATIC_IPC + default y help This option enables support for the LEDs of several Industrial PCs from Siemens. @@ -34,3 +36,16 @@ config LEDS_SIEMENS_SIMATIC_IPC_F7188X To compile this driver as a module, choose M here: the module will be called simatic-ipc-leds-gpio-f7188x. + +config LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE + tristate "LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO" + depends on LEDS_GPIO + depends on PINCTRL_ELKHARTLAKE + depends on SIEMENS_SIMATIC_IPC + default LEDS_SIEMENS_SIMATIC_IPC + help + This option enables support for the LEDs of several Industrial PCs + from Siemens based on Elkhart Lake GPIO i.e. BX-21A. + + To compile this driver as a module, choose M here: the module + will be called simatic-ipc-leds-gpio-elkhartlake. diff --git a/drivers/leds/simple/Makefile b/drivers/leds/simatic/Makefile index e3e840cea275..783578f11bb0 100644 --- a/drivers/leds/simple/Makefile +++ b/drivers/leds/simatic/Makefile @@ -2,3 +2,4 @@ obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o +obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-elkhartlake.o diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c index e1c712729dcf..c98c370687c2 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c @@ -45,10 +45,10 @@ static int simatic_ipc_leds_gpio_apollolake_probe(struct platform_device *pdev) &simatic_ipc_led_gpio_table_extra); } -static int simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev) +static void simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev) { - return simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, - &simatic_ipc_led_gpio_table_extra); + 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_apollolake_driver = { @@ -60,6 +60,7 @@ static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = { }; 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 2a21b663df87..9bc5f361a06b 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c @@ -33,15 +33,13 @@ static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = { .leds = simatic_ipc_gpio_leds, }; -int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, +void simatic_ipc_leds_gpio_remove(struct platform_device *pdev, struct gpiod_lookup_table *table, struct gpiod_lookup_table *table_extra) { gpiod_remove_lookup_table(table); gpiod_remove_lookup_table(table_extra); platform_device_unregister(simatic_leds_pdev); - - return 0; } EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove); @@ -57,6 +55,8 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, switch (plat->devmode) { 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; @@ -72,6 +72,9 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, goto out; } + if (!table_extra) + return 0; + table_extra->dev_id = dev_name(dev); gpiod_add_lookup_table(table_extra); @@ -99,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/simatic/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c new file mode 100644 index 000000000000..7f7cff275448 --- /dev/null +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c @@ -0,0 +1,57 @@ +// 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("INTC1020:04", 72, NULL, 0, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("INTC1020:04", 77, NULL, 1, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("INTC1020:04", 78, NULL, 2, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("INTC1020:04", 58, NULL, 3, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("INTC1020:04", 60, NULL, 4, GPIO_ACTIVE_HIGH), + GPIO_LOOKUP_IDX("INTC1020:04", 62, NULL, 5, GPIO_ACTIVE_HIGH), + {} /* Terminating entry */ + }, +}; + +static int simatic_ipc_leds_gpio_elkhartlake_probe(struct platform_device *pdev) +{ + return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table, + NULL); +} + +static void simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev) +{ + simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, NULL); +} + +static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = { + .probe = simatic_ipc_leds_gpio_elkhartlake_probe, + .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"); +MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>"); 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 bf258c32f83d..6b2519809cee 100644 --- a/drivers/leds/simple/simatic-ipc-leds-gpio.h +++ b/drivers/leds/simatic/simatic-ipc-leds-gpio.h @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: GPL-2.0 +/* SPDX-License-Identifier: GPL-2.0 */ /* * Siemens SIMATIC IPC driver for GPIO based LEDs * @@ -15,8 +15,8 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev, struct gpiod_lookup_table *table, struct gpiod_lookup_table *table_extra); -int simatic_ipc_leds_gpio_remove(struct platform_device *pdev, - struct gpiod_lookup_table *table, - struct gpiod_lookup_table *table_extra); +void simatic_ipc_leds_gpio_remove(struct platform_device *pdev, + struct gpiod_lookup_table *table, + struct gpiod_lookup_table *table_extra); #endif /* _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 583a6b6c7c22..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 int simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev) -{ - return 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 = 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 2a57328eca20..c11282a74b5a 100644 --- a/drivers/leds/trigger/Kconfig +++ b/drivers/leds/trigger/Kconfig @@ -83,13 +83,10 @@ config LEDS_TRIGGER_ACTIVITY config LEDS_TRIGGER_GPIO tristate "LED GPIO Trigger" depends on GPIOLIB || COMPILE_TEST - depends on BROKEN help This allows LEDs to be controlled by gpio events. It's good when using gpios as switches and triggering the needed LEDs - from there. One use case is n810's keypad LEDs that could - be triggered by this trigger when user slides up to show - keypad. + from there. Triggers are defined as device properties. If unsure, say N. @@ -139,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 @@ -155,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..c973246a57f9 100644 --- a/drivers/leds/trigger/ledtrig-activity.c +++ b/drivers/leds/trigger/ledtrig-activity.c @@ -32,8 +32,8 @@ struct activity_data { static void led_activity_function(struct timer_list *t) { - struct activity_data *activity_data = from_timer(activity_data, t, - timer); + struct activity_data *activity_data = timer_container_of(activity_data, + t, timer); struct led_classdev *led_cdev = activity_data->led_cdev; unsigned int target; unsigned int usage; @@ -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 c6b437e6369b..000000000000 --- a/drivers/leds/trigger/ledtrig-audio.c +++ /dev/null @@ -1,65 +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"); diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c index 487577d22cfc..c1f0f5becaee 100644 --- a/drivers/leds/trigger/ledtrig-backlight.c +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -10,7 +10,6 @@ #include <linux/kernel.h> #include <linux/slab.h> #include <linux/init.h> -#include <linux/fb.h> #include <linux/leds.h> #include "../leds.h" @@ -21,29 +20,20 @@ struct bl_trig_notifier { struct led_classdev *led; int brightness; int old_status; - struct notifier_block notifier; unsigned invert; + + struct list_head entry; }; -static int fb_notifier_callback(struct notifier_block *p, - unsigned long event, void *data) +static DEFINE_MUTEX(ledtrig_backlight_list_mutex); +static LIST_HEAD(ledtrig_backlight_list); + +static void ledtrig_backlight_notify_blank(struct bl_trig_notifier *n, int new_status) { - struct bl_trig_notifier *n = container_of(p, - struct bl_trig_notifier, notifier); struct led_classdev *led = n->led; - struct fb_event *fb_event = data; - int *blank; - int new_status; - - /* If we aren't interested in this event, skip it immediately ... */ - if (event != FB_EVENT_BLANK) - return 0; - - blank = fb_event->data; - new_status = *blank ? BLANK : UNBLANK; if (new_status == n->old_status) - return 0; + return; if ((n->old_status == UNBLANK) ^ n->invert) { n->brightness = led->brightness; @@ -53,9 +43,19 @@ static int fb_notifier_callback(struct notifier_block *p, } n->old_status = new_status; +} - return 0; +void ledtrig_backlight_blank(bool blank) +{ + struct bl_trig_notifier *n; + int new_status = blank ? BLANK : UNBLANK; + + guard(mutex)(&ledtrig_backlight_list_mutex); + + list_for_each_entry(n, &ledtrig_backlight_list, entry) + ledtrig_backlight_notify_blank(n, new_status); } +EXPORT_SYMBOL(ledtrig_backlight_blank); static ssize_t bl_trig_invert_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -100,8 +100,6 @@ ATTRIBUTE_GROUPS(bl_trig); static int bl_trig_activate(struct led_classdev *led) { - int ret; - struct bl_trig_notifier *n; n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); @@ -112,11 +110,9 @@ static int bl_trig_activate(struct led_classdev *led) n->led = led; n->brightness = led->brightness; n->old_status = UNBLANK; - n->notifier.notifier_call = fb_notifier_callback; - ret = fb_register_client(&n->notifier); - if (ret) - dev_err(led->dev, "unable to register backlight trigger\n"); + guard(mutex)(&ledtrig_backlight_list_mutex); + list_add(&n->entry, &ledtrig_backlight_list); return 0; } @@ -125,7 +121,9 @@ static void bl_trig_deactivate(struct led_classdev *led) { struct bl_trig_notifier *n = led_get_trigger_data(led); - fb_unregister_client(&n->notifier); + guard(mutex)(&ledtrig_backlight_list_mutex); + list_del(&n->entry); + kfree(n); } diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c index 8af4f9bb9cde..679323c2ccda 100644 --- a/drivers/leds/trigger/ledtrig-cpu.c +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -94,28 +94,32 @@ void ledtrig_cpu(enum cpu_led_event ledevt) } EXPORT_SYMBOL(ledtrig_cpu); -static int ledtrig_cpu_syscore_suspend(void) +static int ledtrig_cpu_syscore_suspend(void *data) { ledtrig_cpu(CPU_LED_STOP); return 0; } -static void ledtrig_cpu_syscore_resume(void) +static void ledtrig_cpu_syscore_resume(void *data) { ledtrig_cpu(CPU_LED_START); } -static void ledtrig_cpu_syscore_shutdown(void) +static void ledtrig_cpu_syscore_shutdown(void *data) { ledtrig_cpu(CPU_LED_HALTED); } -static struct syscore_ops ledtrig_cpu_syscore_ops = { +static const struct syscore_ops ledtrig_cpu_syscore_ops = { .shutdown = ledtrig_cpu_syscore_shutdown, .suspend = ledtrig_cpu_syscore_suspend, .resume = ledtrig_cpu_syscore_resume, }; +static struct syscore ledtrig_cpu_syscore = { + .ops = &ledtrig_cpu_syscore_ops, +}; + static int ledtrig_online_cpu(unsigned int cpu) { ledtrig_cpu(CPU_LED_START); @@ -130,7 +134,7 @@ static int ledtrig_prepare_down_cpu(unsigned int cpu) static int __init ledtrig_cpu_init(void) { - int cpu; + unsigned int cpu; int ret; /* Supports up to 9999 cpu cores */ @@ -152,12 +156,12 @@ static int __init ledtrig_cpu_init(void) if (cpu >= 8) continue; - snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); + snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu); led_trigger_register_simple(trig->name, &trig->_trig); } - register_syscore_ops(&ledtrig_cpu_syscore_ops); + register_syscore(&ledtrig_cpu_syscore); ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "leds/trigger:starting", ledtrig_online_cpu, ledtrig_prepare_down_cpu); diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c index 8207f85eceb1..8678e64a5c33 100644 --- a/drivers/leds/trigger/ledtrig-default-on.c +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -28,3 +28,4 @@ module_led_trigger(defon_led_trigger); MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>"); MODULE_DESCRIPTION("Default-ON LED trigger"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ledtrig:default-on"); diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 0120faa3dafa..7f6a2352b0ac 100644 --- a/drivers/leds/trigger/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -3,12 +3,13 @@ * ledtrig-gio.c - LED Trigger Based on GPIO events * * Copyright 2009 Felipe Balbi <me@felipebalbi.com> + * Copyright 2023 Linus Walleij <linus.walleij@linaro.org> */ #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> -#include <linux/gpio.h> +#include <linux/gpio/consumer.h> #include <linux/interrupt.h> #include <linux/leds.h> #include <linux/slab.h> @@ -16,10 +17,8 @@ struct gpio_trig_data { struct led_classdev *led; - unsigned desired_brightness; /* desired brightness when led is on */ - unsigned inverted; /* true when gpio is inverted */ - unsigned gpio; /* gpio that triggers the leds */ + struct gpio_desc *gpiod; /* gpio that triggers the led */ }; static irqreturn_t gpio_trig_irq(int irq, void *_led) @@ -28,10 +27,7 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led) struct gpio_trig_data *gpio_data = led_get_trigger_data(led); int tmp; - tmp = gpio_get_value_cansleep(gpio_data->gpio); - if (gpio_data->inverted) - tmp = !tmp; - + tmp = gpiod_get_value_cansleep(gpio_data->gpiod); if (tmp) { if (gpio_data->desired_brightness) led_set_brightness_nosleep(gpio_data->led, @@ -45,121 +41,33 @@ static irqreturn_t gpio_trig_irq(int irq, void *_led) return IRQ_HANDLED; } -static ssize_t gpio_trig_brightness_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - - return sprintf(buf, "%u\n", gpio_data->desired_brightness); -} - -static ssize_t gpio_trig_brightness_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned desired_brightness; - int ret; - - ret = sscanf(buf, "%u", &desired_brightness); - if (ret < 1 || desired_brightness > 255) { - dev_err(dev, "invalid value\n"); - return -EINVAL; - } - - gpio_data->desired_brightness = desired_brightness; - - return n; -} -static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show, - gpio_trig_brightness_store); - -static ssize_t gpio_trig_inverted_show(struct device *dev, +static ssize_t desired_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - return sprintf(buf, "%u\n", gpio_data->inverted); + return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness); } -static ssize_t gpio_trig_inverted_store(struct device *dev, +static ssize_t desired_brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t n) { - struct led_classdev *led = led_trigger_get_led(dev); struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned long inverted; + u8 desired_brightness; int ret; - ret = kstrtoul(buf, 10, &inverted); - if (ret < 0) + ret = kstrtou8(buf, 10, &desired_brightness); + if (ret) return ret; - if (inverted > 1) - return -EINVAL; - - gpio_data->inverted = inverted; - - /* After inverting, we need to update the LED. */ - if (gpio_is_valid(gpio_data->gpio)) - gpio_trig_irq(0, led); + gpio_data->desired_brightness = desired_brightness; return n; } -static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show, - gpio_trig_inverted_store); - -static ssize_t gpio_trig_gpio_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - - return sprintf(buf, "%u\n", gpio_data->gpio); -} - -static ssize_t gpio_trig_gpio_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t n) -{ - struct led_classdev *led = led_trigger_get_led(dev); - struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev); - unsigned gpio; - int ret; - - ret = sscanf(buf, "%u", &gpio); - if (ret < 1) { - dev_err(dev, "couldn't read gpio number\n"); - return -EINVAL; - } - - if (gpio_data->gpio == gpio) - return n; - - if (!gpio_is_valid(gpio)) { - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = gpio; - return n; - } - - ret = request_threaded_irq(gpio_to_irq(gpio), NULL, gpio_trig_irq, - IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING - | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); - if (ret) { - dev_err(dev, "request_irq failed with error %d\n", ret); - } else { - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); - gpio_data->gpio = gpio; - /* After changing the GPIO, we need to update the LED. */ - gpio_trig_irq(0, led); - } - - return ret ? ret : n; -} -static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store); +static DEVICE_ATTR_RW(desired_brightness); static struct attribute *gpio_trig_attrs[] = { &dev_attr_desired_brightness.attr, - &dev_attr_inverted.attr, - &dev_attr_gpio.attr, NULL }; ATTRIBUTE_GROUPS(gpio_trig); @@ -167,16 +75,47 @@ ATTRIBUTE_GROUPS(gpio_trig); static int gpio_trig_activate(struct led_classdev *led) { struct gpio_trig_data *gpio_data; + struct device *dev = led->dev; + int ret; gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL); if (!gpio_data) return -ENOMEM; - gpio_data->led = led; - gpio_data->gpio = -ENOENT; + /* + * The generic property "trigger-sources" is followed, + * and we hope that this is a GPIO. + */ + gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN); + if (IS_ERR(gpio_data->gpiod)) { + ret = PTR_ERR(gpio_data->gpiod); + kfree(gpio_data); + return ret; + } + if (!gpio_data->gpiod) { + dev_err(dev, "no valid GPIO for the trigger\n"); + kfree(gpio_data); + return -EINVAL; + } + gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger"); + + gpio_data->led = led; led_set_trigger_data(led, gpio_data); + ret = request_threaded_irq(gpiod_to_irq(gpio_data->gpiod), NULL, gpio_trig_irq, + IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING + | IRQF_TRIGGER_FALLING, "ledtrig-gpio", led); + if (ret) { + dev_err(dev, "request_irq failed with error %d\n", ret); + gpiod_put(gpio_data->gpiod); + kfree(gpio_data); + return ret; + } + + /* Finally update the LED to initial status */ + gpio_trig_irq(0, led); + return 0; } @@ -184,8 +123,8 @@ static void gpio_trig_deactivate(struct led_classdev *led) { struct gpio_trig_data *gpio_data = led_get_trigger_data(led); - if (gpio_is_valid(gpio_data->gpio)) - free_irq(gpio_to_irq(gpio_data->gpio), led); + free_irq(gpiod_to_irq(gpio_data->gpiod), led); + gpiod_put(gpio_data->gpiod); kfree(gpio_data); } diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index 393b3ae832f4..40eb61b6d54e 100644 --- a/drivers/leds/trigger/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -33,7 +33,7 @@ struct heartbeat_trig_data { static void led_heartbeat_function(struct timer_list *t) { struct heartbeat_trig_data *heartbeat_data = - from_timer(heartbeat_data, t, timer); + timer_container_of(heartbeat_data, t, timer); struct led_classdev *led_cdev; unsigned long brightness = LED_OFF; unsigned long delay = 0; diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c new file mode 100644 index 000000000000..3c6414259c27 --- /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_percpu_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 c9bc5a91ec83..c15efe3e5078 100644 --- a/drivers/leds/trigger/ledtrig-netdev.c +++ b/drivers/leds/trigger/ledtrig-netdev.c @@ -18,10 +18,12 @@ #include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/leds.h> +#include <linux/linkmode.h> #include <linux/list.h> #include <linux/module.h> #include <linux/netdevice.h> #include <linux/mutex.h> +#include <linux/phy.h> #include <linux/rtnetlink.h> #include <linux/timer.h> #include "../leds.h" @@ -37,7 +39,19 @@ * (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 + * hw supports software control. LED callbacks brightness_set() and + * brightness_set_blocking() are NULL in this case. hw_control_is_supported() + * should use available means supported by hw to inform the user that selected + * mode isn't supported by hw. This could be switching off the LED or any + * hw blink mode. If software control fallback isn't possible, we return + * -EOPNOTSUPP to the user, but still store the selected mode. This is needed + * in case an intermediate unsupported mode is necessary to switch from one + * supported mode to another. */ struct led_netdev_data { @@ -55,12 +69,15 @@ struct led_netdev_data { unsigned long mode; int link_speed; + __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes); u8 duplex; bool carrier_link_up; bool hw_control; }; +static const struct attribute_group netdev_trig_link_speed_attrs_group; + static void set_baseline_state(struct led_netdev_data *trigger_data) { int current_brightness; @@ -99,6 +116,18 @@ static void set_baseline_state(struct led_netdev_data *trigger_data) trigger_data->link_speed == SPEED_1000) blink_on = true; + if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) && + trigger_data->link_speed == SPEED_2500) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_5000) + blink_on = true; + + if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) && + trigger_data->link_speed == SPEED_10000) + blink_on = true; + if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) && trigger_data->duplex == DUPLEX_HALF) blink_on = true; @@ -117,7 +146,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); } } @@ -196,13 +227,20 @@ static void get_device_state(struct led_netdev_data *trigger_data) struct ethtool_link_ksettings cmd; trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev); - if (!trigger_data->carrier_link_up) + + if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) return; - if (!__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd)) { + if (trigger_data->carrier_link_up) { trigger_data->link_speed = cmd.base.speed; trigger_data->duplex = cmd.base.duplex; } + + /* + * Have a local copy of the link speed supported to avoid rtnl lock every time + * modes are refreshed on any change event + */ + linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported); } static ssize_t device_name_show(struct device *dev, @@ -221,8 +259,16 @@ static ssize_t device_name_show(struct device *dev, static int set_device_name(struct led_netdev_data *trigger_data, const char *name, size_t size) { + if (size >= IFNAMSIZ) + return -EINVAL; + cancel_delayed_work_sync(&trigger_data->work); + /* + * Take RTNL lock before trigger_data lock to prevent potential + * deadlock with netdev notifier registration. + */ + rtnl_lock(); mutex_lock(&trigger_data->lock); if (trigger_data->net_dev) { @@ -242,16 +288,17 @@ static int set_device_name(struct led_netdev_data *trigger_data, trigger_data->carrier_link_up = false; trigger_data->link_speed = SPEED_UNKNOWN; trigger_data->duplex = DUPLEX_UNKNOWN; - if (trigger_data->net_dev != NULL) { - rtnl_lock(); + if (trigger_data->net_dev) get_device_state(trigger_data); - rtnl_unlock(); - } trigger_data->last_activity = 0; - set_baseline_state(trigger_data); + /* Skip if we're called from netdev_trig_activate() and hw_control is true */ + if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev)) + set_baseline_state(trigger_data); + mutex_unlock(&trigger_data->lock); + rtnl_unlock(); return 0; } @@ -263,13 +310,14 @@ static ssize_t device_name_store(struct device *dev, struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); int ret; - if (size >= IFNAMSIZ) - return -EINVAL; - ret = set_device_name(trigger_data, buf, size); if (ret < 0) return ret; + + /* Refresh link_speed visibility */ + sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group); + return size; } @@ -286,10 +334,15 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf, case TRIGGER_NETDEV_LINK_10: case TRIGGER_NETDEV_LINK_100: case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: case TRIGGER_NETDEV_HALF_DUPLEX: 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: @@ -303,6 +356,7 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, size_t size, enum led_trigger_netdev_modes attr) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); + struct led_classdev *led_cdev = trigger_data->led_cdev; unsigned long state, mode = trigger_data->mode; int ret; int bit; @@ -316,10 +370,15 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, case TRIGGER_NETDEV_LINK_10: case TRIGGER_NETDEV_LINK_100: case TRIGGER_NETDEV_LINK_1000: + case TRIGGER_NETDEV_LINK_2500: + case TRIGGER_NETDEV_LINK_5000: + case TRIGGER_NETDEV_LINK_10000: case TRIGGER_NETDEV_HALF_DUPLEX: 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: @@ -334,7 +393,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, if (test_bit(TRIGGER_NETDEV_LINK, &mode) && (test_bit(TRIGGER_NETDEV_LINK_10, &mode) || test_bit(TRIGGER_NETDEV_LINK_100, &mode) || - test_bit(TRIGGER_NETDEV_LINK_1000, &mode))) + test_bit(TRIGGER_NETDEV_LINK_1000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &mode))) return -EINVAL; cancel_delayed_work_sync(&trigger_data->work); @@ -342,6 +404,10 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf, trigger_data->mode = mode; trigger_data->hw_control = can_hw_control(trigger_data); + if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking && + !trigger_data->hw_control) + return -EOPNOTSUPP; + set_baseline_state(trigger_data); return size; @@ -364,10 +430,15 @@ DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK); DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10); DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100); DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000); +DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500); +DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000); +DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000); 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) @@ -406,31 +477,93 @@ static ssize_t interval_store(struct device *dev, static DEVICE_ATTR_RW(interval); -static ssize_t hw_control_show(struct device *dev, - struct device_attribute *attr, char *buf) +static ssize_t offloaded_show(struct device *dev, + struct device_attribute *attr, char *buf) { struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev); return sprintf(buf, "%d\n", trigger_data->hw_control); } -static DEVICE_ATTR_RO(hw_control); +static DEVICE_ATTR_RO(offloaded); -static struct attribute *netdev_trig_attrs[] = { - &dev_attr_device_name.attr, - &dev_attr_link.attr, +#define CHECK_LINK_MODE_ATTR(link_speed) \ + do { \ + if (attr == &dev_attr_link_##link_speed.attr && \ + link_ksettings.base.speed == SPEED_##link_speed) \ + return attr->mode; \ + } while (0) + +static umode_t netdev_trig_link_speed_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = kobj_to_dev(kobj); + struct led_netdev_data *trigger_data; + unsigned long *supported_link_modes; + u32 mode; + + trigger_data = led_trigger_get_drvdata(dev); + supported_link_modes = trigger_data->supported_link_modes; + + /* + * Search in the supported link mode mask a matching supported mode. + * Stop at the first matching entry as we care only to check if a particular + * speed is supported and not the kind. + */ + for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) { + struct ethtool_link_ksettings link_ksettings; + + ethtool_params_from_link_mode(&link_ksettings, mode); + + CHECK_LINK_MODE_ATTR(10); + CHECK_LINK_MODE_ATTR(100); + CHECK_LINK_MODE_ATTR(1000); + CHECK_LINK_MODE_ATTR(2500); + CHECK_LINK_MODE_ATTR(5000); + CHECK_LINK_MODE_ATTR(10000); + } + + return 0; +} + +static struct attribute *netdev_trig_link_speed_attrs[] = { &dev_attr_link_10.attr, &dev_attr_link_100.attr, &dev_attr_link_1000.attr, + &dev_attr_link_2500.attr, + &dev_attr_link_5000.attr, + &dev_attr_link_10000.attr, + NULL +}; + +static const struct attribute_group netdev_trig_link_speed_attrs_group = { + .attrs = netdev_trig_link_speed_attrs, + .is_visible = netdev_trig_link_speed_visible, +}; + +static struct attribute *netdev_trig_attrs[] = { + &dev_attr_device_name.attr, + &dev_attr_link.attr, &dev_attr_full_duplex.attr, &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_hw_control.attr, + &dev_attr_offloaded.attr, NULL }; -ATTRIBUTE_GROUPS(netdev_trig); + +static const struct attribute_group netdev_trig_attrs_group = { + .attrs = netdev_trig_attrs, +}; + +static const struct attribute_group *netdev_trig_groups[] = { + &netdev_trig_attrs_group, + &netdev_trig_link_speed_attrs_group, + NULL, +}; static int netdev_trig_notify(struct notifier_block *nb, unsigned long evt, void *dv) @@ -439,6 +572,7 @@ static int netdev_trig_notify(struct notifier_block *nb, netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv); struct led_netdev_data *trigger_data = container_of(nb, struct led_netdev_data, notifier); + struct led_classdev *led_cdev = trigger_data->led_cdev; if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER @@ -459,20 +593,26 @@ static int netdev_trig_notify(struct notifier_block *nb, trigger_data->duplex = DUPLEX_UNKNOWN; switch (evt) { case NETDEV_CHANGENAME: - get_device_state(trigger_data); - fallthrough; case NETDEV_REGISTER: dev_put(trigger_data->net_dev); dev_hold(dev); trigger_data->net_dev = dev; + if (evt == NETDEV_CHANGENAME) + get_device_state(trigger_data); break; case NETDEV_UNREGISTER: dev_put(trigger_data->net_dev); 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 */ + if (evt == NETDEV_CHANGE) + sysfs_update_group(&led_cdev->dev->kobj, + &netdev_trig_link_speed_attrs_group); break; } @@ -502,7 +642,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); @@ -510,7 +652,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); @@ -519,6 +665,9 @@ static void netdev_trig_work(struct work_struct *work) test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) || + test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) || test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode); interval = jiffies_to_msecs( @@ -564,15 +713,17 @@ static int netdev_trig_activate(struct led_classdev *led_cdev) /* Check if hw control is active by default on the LED. * Init already enabled mode in hw control. */ - if (supports_hw_control(led_cdev) && - !led_cdev->hw_control_get(led_cdev, &mode)) { + if (supports_hw_control(led_cdev)) { dev = led_cdev->hw_control_get_device(led_cdev); if (dev) { const char *name = dev_name(dev); - set_device_name(trigger_data, name, strlen(name)); trigger_data->hw_control = true; - trigger_data->mode = mode; + set_device_name(trigger_data, name, strlen(name)); + + rc = led_cdev->hw_control_get(led_cdev, &mode); + if (!rc) + trigger_data->mode = mode; } } @@ -605,20 +756,10 @@ static struct led_trigger netdev_led_trigger = { .groups = netdev_trig_groups, }; -static int __init netdev_trig_init(void) -{ - return led_trigger_register(&netdev_led_trigger); -} - -static void __exit netdev_trig_exit(void) -{ - led_trigger_unregister(&netdev_led_trigger); -} - -module_init(netdev_trig_init); -module_exit(netdev_trig_exit); +module_led_trigger(netdev_led_trigger); MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>"); MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>"); MODULE_DESCRIPTION("Netdev LED trigger"); MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("ledtrig:netdev"); diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c index 64abf2e91608..1d49c1078091 100644 --- a/drivers/leds/trigger/ledtrig-panic.c +++ b/drivers/leds/trigger/ledtrig-panic.c @@ -21,24 +21,15 @@ static struct led_trigger *trigger; */ static void led_trigger_set_panic(struct led_classdev *led_cdev) { - struct led_trigger *trig; + if (led_cdev->trigger) + list_del(&led_cdev->trig_list); + list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); - list_for_each_entry(trig, &trigger_list, next_trig) { - if (strcmp("panic", trig->name)) - continue; - if (led_cdev->trigger) - list_del(&led_cdev->trig_list); - list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); + /* Avoid the delayed blink path */ + led_cdev->blink_delay_on = 0; + led_cdev->blink_delay_off = 0; - /* Avoid the delayed blink path */ - led_cdev->blink_delay_on = 0; - led_cdev->blink_delay_off = 0; - - led_cdev->trigger = trig; - if (trig->activate) - trig->activate(led_cdev); - break; - } + led_cdev->trigger = trigger; } static int led_trigger_panic_notifier(struct notifier_block *nb, @@ -64,10 +55,13 @@ static long led_panic_blink(int state) static int __init ledtrig_panic_init(void) { + led_trigger_register_simple("panic", &trigger); + if (!trigger) + return -ENOMEM; + atomic_notifier_chain_register(&panic_notifier_list, &led_trigger_panic_nb); - led_trigger_register_simple("panic", &trigger); panic_blink = led_panic_blink; return 0; } diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c index fadd87dbe993..9af3c18f14f4 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) +{ + 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) { - struct pattern_trig_data *data = from_timer(data, t, timer); + 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 = timer_container_of(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..20f1351464b1 100644 --- a/drivers/leds/trigger/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -32,7 +32,7 @@ struct transient_trig_data { static void transient_timer_function(struct timer_list *t) { struct transient_trig_data *transient_data = - from_timer(transient_data, t, timer); + timer_container_of(transient_data, t, timer); struct led_classdev *led_cdev = transient_data->led_cdev; transient_data->activate = 0; @@ -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/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c index f62db7e520b5..8cf1485e8165 100644 --- a/drivers/leds/trigger/ledtrig-tty.c +++ b/drivers/leds/trigger/ledtrig-tty.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include <linux/completion.h> #include <linux/delay.h> #include <linux/leds.h> #include <linux/module.h> @@ -7,18 +8,50 @@ #include <linux/tty.h> #include <uapi/linux/serial.h> +#define LEDTRIG_TTY_INTERVAL 50 + struct ledtrig_tty_data { struct led_classdev *led_cdev; struct delayed_work dwork; - struct mutex mutex; + struct completion sysfs; const char *ttyname; struct tty_struct *tty; int rx, tx; + bool mode_rx; + bool mode_tx; + bool mode_cts; + bool mode_dsr; + bool mode_dcd; + bool mode_rng; +}; + +/* Indicates which state the LED should now display */ +enum led_trigger_tty_state { + TTY_LED_BLINK, + TTY_LED_ENABLE, + TTY_LED_DISABLE, }; -static void ledtrig_tty_restart(struct ledtrig_tty_data *trigger_data) +enum led_trigger_tty_modes { + TRIGGER_TTY_RX = 0, + TRIGGER_TTY_TX, + TRIGGER_TTY_CTS, + TRIGGER_TTY_DSR, + TRIGGER_TTY_DCD, + TRIGGER_TTY_RNG, +}; + +static int ledtrig_tty_wait_for_completion(struct device *dev) { - schedule_delayed_work(&trigger_data->dwork, 0); + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + int ret; + + ret = wait_for_completion_timeout(&trigger_data->sysfs, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20)); + if (ret == 0) + return -ETIMEDOUT; + + return ret; } static ssize_t ttyname_show(struct device *dev, @@ -26,14 +59,16 @@ static ssize_t ttyname_show(struct device *dev, { struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); ssize_t len = 0; + int completion; - mutex_lock(&trigger_data->mutex); + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; if (trigger_data->ttyname) len = sprintf(buf, "%s\n", trigger_data->ttyname); - mutex_unlock(&trigger_data->mutex); - return len; } @@ -44,7 +79,7 @@ static ssize_t ttyname_store(struct device *dev, struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); char *ttyname; ssize_t ret = size; - bool running; + int completion; if (size > 0 && buf[size - 1] == '\n') size -= 1; @@ -57,9 +92,10 @@ static ssize_t ttyname_store(struct device *dev, ttyname = NULL; } - mutex_lock(&trigger_data->mutex); - - running = trigger_data->ttyname != NULL; + reinit_completion(&trigger_data->sysfs); + completion = ledtrig_tty_wait_for_completion(dev); + if (completion < 0) + return completion; kfree(trigger_data->ttyname); tty_kref_put(trigger_data->tty); @@ -67,29 +103,107 @@ static ssize_t ttyname_store(struct device *dev, trigger_data->ttyname = ttyname; - mutex_unlock(&trigger_data->mutex); - - if (ttyname && !running) - ledtrig_tty_restart(trigger_data); - return ret; } static DEVICE_ATTR_RW(ttyname); +static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf, + enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + + switch (attr) { + case TRIGGER_TTY_RX: + state = trigger_data->mode_rx; + break; + case TRIGGER_TTY_TX: + state = trigger_data->mode_tx; + break; + case TRIGGER_TTY_CTS: + state = trigger_data->mode_cts; + break; + case TRIGGER_TTY_DSR: + state = trigger_data->mode_dsr; + break; + case TRIGGER_TTY_DCD: + state = trigger_data->mode_dcd; + break; + case TRIGGER_TTY_RNG: + state = trigger_data->mode_rng; + break; + } + + return sysfs_emit(buf, "%u\n", state); +} + +static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf, + size_t size, enum led_trigger_tty_modes attr) +{ + struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev); + bool state; + int ret; + + ret = kstrtobool(buf, &state); + if (ret) + return ret; + + switch (attr) { + case TRIGGER_TTY_RX: + trigger_data->mode_rx = state; + break; + case TRIGGER_TTY_TX: + trigger_data->mode_tx = state; + break; + case TRIGGER_TTY_CTS: + trigger_data->mode_cts = state; + break; + case TRIGGER_TTY_DSR: + trigger_data->mode_dsr = state; + break; + case TRIGGER_TTY_DCD: + trigger_data->mode_dcd = state; + break; + case TRIGGER_TTY_RNG: + trigger_data->mode_rng = state; + break; + } + + return size; +} + +#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \ + static ssize_t trigger_name##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ + { \ + return ledtrig_tty_attr_show(dev, buf, trigger); \ + } \ + static ssize_t trigger_name##_store(struct device *dev, \ + struct device_attribute *attr, const char *buf, size_t size) \ + { \ + return ledtrig_tty_attr_store(dev, buf, size, trigger); \ + } \ + static DEVICE_ATTR_RW(trigger_name) + +DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX); +DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX); +DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS); +DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR); +DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD); +DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG); + static void ledtrig_tty_work(struct work_struct *work) { struct ledtrig_tty_data *trigger_data = container_of(work, struct ledtrig_tty_data, dwork.work); - struct serial_icounter_struct icount; + enum led_trigger_tty_state state = TTY_LED_DISABLE; + unsigned long interval = LEDTRIG_TTY_INTERVAL; + bool invert = false; + int status; int ret; - mutex_lock(&trigger_data->mutex); - - if (!trigger_data->ttyname) { - /* exit without rescheduling */ - mutex_unlock(&trigger_data->mutex); - return; - } + if (!trigger_data->ttyname) + goto out; /* try to get the tty corresponding to $ttyname */ if (!trigger_data->tty) { @@ -113,30 +227,83 @@ static void ledtrig_tty_work(struct work_struct *work) trigger_data->tty = tty; } - ret = tty_get_icount(trigger_data->tty, &icount); - if (ret) { - dev_info(trigger_data->tty->dev, "Failed to get icount, stopped polling\n"); - mutex_unlock(&trigger_data->mutex); - return; + status = tty_get_tiocm(trigger_data->tty); + if (status > 0) { + if (trigger_data->mode_cts) { + if (status & TIOCM_CTS) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dsr) { + if (status & TIOCM_DSR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_dcd) { + if (status & TIOCM_CAR) + state = TTY_LED_ENABLE; + } + + if (trigger_data->mode_rng) { + if (status & TIOCM_RNG) + state = TTY_LED_ENABLE; + } } - if (icount.rx != trigger_data->rx || - icount.tx != trigger_data->tx) { - led_set_brightness_sync(trigger_data->led_cdev, LED_ON); + /* + * The evaluation of rx/tx must be done after the evaluation + * of TIOCM_*, because rx/tx has priority. + */ + if (trigger_data->mode_rx || trigger_data->mode_tx) { + struct serial_icounter_struct icount; - trigger_data->rx = icount.rx; - trigger_data->tx = icount.tx; - } else { - led_set_brightness_sync(trigger_data->led_cdev, LED_OFF); + ret = tty_get_icount(trigger_data->tty, &icount); + if (ret) + goto out; + + if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) { + trigger_data->tx = icount.tx; + invert = state == TTY_LED_ENABLE; + state = TTY_LED_BLINK; + } + + if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) { + trigger_data->rx = icount.rx; + invert = state == TTY_LED_ENABLE; + state = TTY_LED_BLINK; + } } out: - mutex_unlock(&trigger_data->mutex); - schedule_delayed_work(&trigger_data->dwork, msecs_to_jiffies(100)); + switch (state) { + case TTY_LED_BLINK: + led_blink_set_oneshot(trigger_data->led_cdev, &interval, + &interval, invert); + break; + case TTY_LED_ENABLE: + led_set_brightness(trigger_data->led_cdev, + trigger_data->led_cdev->blink_brightness); + break; + case TTY_LED_DISABLE: + fallthrough; + default: + led_set_brightness(trigger_data->led_cdev, LED_OFF); + break; + } + + complete_all(&trigger_data->sysfs); + schedule_delayed_work(&trigger_data->dwork, + msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2)); } static struct attribute *ledtrig_tty_attrs[] = { &dev_attr_ttyname.attr, + &dev_attr_rx.attr, + &dev_attr_tx.attr, + &dev_attr_cts.attr, + &dev_attr_dsr.attr, + &dev_attr_dcd.attr, + &dev_attr_rng.attr, NULL }; ATTRIBUTE_GROUPS(ledtrig_tty); @@ -149,11 +316,17 @@ static int ledtrig_tty_activate(struct led_classdev *led_cdev) if (!trigger_data) return -ENOMEM; + /* Enable default rx/tx mode */ + trigger_data->mode_rx = true; + trigger_data->mode_tx = true; + led_set_trigger_data(led_cdev, trigger_data); INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work); trigger_data->led_cdev = led_cdev; - mutex_init(&trigger_data->mutex); + init_completion(&trigger_data->sysfs); + + schedule_delayed_work(&trigger_data->dwork, 0); return 0; } @@ -164,6 +337,10 @@ static void ledtrig_tty_deactivate(struct led_classdev *led_cdev) cancel_delayed_work_sync(&trigger_data->dwork); + kfree(trigger_data->ttyname); + tty_kref_put(trigger_data->tty); + trigger_data->tty = NULL; + kfree(trigger_data); } diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c index 7320337b22d2..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 = { @@ -209,17 +208,7 @@ static struct miscdevice uleds_misc = { .name = ULEDS_NAME, }; -static int __init uleds_init(void) -{ - return misc_register(&uleds_misc); -} -module_init(uleds_init); - -static void __exit uleds_exit(void) -{ - misc_deregister(&uleds_misc); -} -module_exit(uleds_exit); +module_misc_device(uleds_misc); MODULE_AUTHOR("David Lechner <david@lechnology.com>"); MODULE_DESCRIPTION("Userspace driver for the LED subsystem"); |
