summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/Kconfig100
-rw-r--r--drivers/leds/Makefile11
-rw-r--r--drivers/leds/blink/leds-bcm63138.c30
-rw-r--r--drivers/leds/blink/leds-lgm-sso.c2
-rw-r--r--drivers/leds/flash/Kconfig11
-rw-r--r--drivers/leds/flash/Makefile1
-rw-r--r--drivers/leds/flash/leds-aat1290.c18
-rw-r--r--drivers/leds/flash/leds-as3645a.c12
-rw-r--r--drivers/leds/flash/leds-ktd2692.c20
-rw-r--r--drivers/leds/flash/leds-lm3601x.c19
-rw-r--r--drivers/leds/flash/leds-max77693.c22
-rw-r--r--drivers/leds/flash/leds-mt6360.c22
-rw-r--r--drivers/leds/flash/leds-mt6370-flash.c11
-rw-r--r--drivers/leds/flash/leds-qcom-flash.c179
-rw-r--r--drivers/leds/flash/leds-rt4505.c1
-rw-r--r--drivers/leds/flash/leds-rt8515.c6
-rw-r--r--drivers/leds/flash/leds-sgm3140.c8
-rw-r--r--drivers/leds/flash/leds-sy7802.c539
-rw-r--r--drivers/leds/led-class-flash.c1
-rw-r--r--drivers/leds/led-class-multicolor.c5
-rw-r--r--drivers/leds/led-class.c48
-rw-r--r--drivers/leds/led-core.c94
-rw-r--r--drivers/leds/led-triggers.c45
-rw-r--r--drivers/leds/leds-88pm860x.c7
-rw-r--r--drivers/leds/leds-adp5520.c2
-rw-r--r--drivers/leds/leds-an30259a.c18
-rw-r--r--drivers/leds/leds-apu.c3
-rw-r--r--drivers/leds/leds-aw200xx.c41
-rw-r--r--drivers/leds/leds-aw2013.c33
-rw-r--r--drivers/leds/leds-bcm6328.c11
-rw-r--r--drivers/leds/leds-bcm6358.c7
-rw-r--r--drivers/leds/leds-bd2606mvv.c23
-rw-r--r--drivers/leds/leds-bd2802.c2
-rw-r--r--drivers/leds/leds-blinkm.c222
-rw-r--r--drivers/leds/leds-cht-wcove.c8
-rw-r--r--drivers/leds/leds-clevo-mail.c2
-rw-r--r--drivers/leds/leds-cr0014114.c4
-rw-r--r--drivers/leds/leds-cros_ec.c277
-rw-r--r--drivers/leds/leds-da903x.c2
-rw-r--r--drivers/leds/leds-da9052.c2
-rw-r--r--drivers/leds/leds-el15203000.c14
-rw-r--r--drivers/leds/leds-expresswire.c12
-rw-r--r--drivers/leds/leds-gpio-register.c2
-rw-r--r--drivers/leds/leds-gpio.c29
-rw-r--r--drivers/leds/leds-is31fl319x.c38
-rw-r--r--drivers/leds/leds-is31fl32xx.c14
-rw-r--r--drivers/leds/leds-lm3530.c2
-rw-r--r--drivers/leds/leds-lm3532.c49
-rw-r--r--drivers/leds/leds-lm3533.c2
-rw-r--r--drivers/leds/leds-lm3642.c2
-rw-r--r--drivers/leds/leds-lm3697.c20
-rw-r--r--drivers/leds/leds-lp3944.c2
-rw-r--r--drivers/leds/leds-lp3952.c23
-rw-r--r--drivers/leds/leds-lp50xx.c28
-rw-r--r--drivers/leds/leds-lp5521.c410
-rw-r--r--drivers/leds/leds-lp5523.c763
-rw-r--r--drivers/leds/leds-lp5562.c299
-rw-r--r--drivers/leds/leds-lp5569.c544
-rw-r--r--drivers/leds/leds-lp55xx-common.c779
-rw-r--r--drivers/leds/leds-lp55xx-common.h164
-rw-r--r--drivers/leds/leds-lp8501.c313
-rw-r--r--drivers/leds/leds-lp8860.c6
-rw-r--r--drivers/leds/leds-lp8864.c296
-rw-r--r--drivers/leds/leds-max5970.c5
-rw-r--r--drivers/leds/leds-max77650.c18
-rw-r--r--drivers/leds/leds-max77705.c275
-rw-r--r--drivers/leds/leds-mc13783.c26
-rw-r--r--drivers/leds/leds-mlxcpld.c1
-rw-r--r--drivers/leds/leds-mlxreg.c14
-rw-r--r--drivers/leds/leds-mt6323.c24
-rw-r--r--drivers/leds/leds-netxbig.c20
-rw-r--r--drivers/leds/leds-nic78bx.c39
-rw-r--r--drivers/leds/leds-ns2.c7
-rw-r--r--drivers/leds/leds-pca9532.c87
-rw-r--r--drivers/leds/leds-pca955x.c359
-rw-r--r--drivers/leds/leds-pca963x.c11
-rw-r--r--drivers/leds/leds-pca995x.c78
-rw-r--r--drivers/leds/leds-powernv.c32
-rw-r--r--drivers/leds/leds-pwm.c41
-rw-r--r--drivers/leds/leds-qnap-mcu.c227
-rw-r--r--drivers/leds/leds-rb532.c2
-rw-r--r--drivers/leds/leds-regulator.c2
-rw-r--r--drivers/leds/leds-sc27xx-bltc.c14
-rw-r--r--drivers/leds/leds-spi-byte.c63
-rw-r--r--drivers/leds/leds-ss4200.c9
-rw-r--r--drivers/leds/leds-st1202.c416
-rw-r--r--drivers/leds/leds-sun50i-a100.c45
-rw-r--r--drivers/leds/leds-sunfire.c4
-rw-r--r--drivers/leds/leds-tca6507.c7
-rw-r--r--drivers/leds/leds-tlc591xx.c18
-rw-r--r--drivers/leds/leds-turris-omnia.c348
-rw-r--r--drivers/leds/leds-upboard.c126
-rw-r--r--drivers/leds/leds-wm831x-status.c2
-rw-r--r--drivers/leds/leds-wm8350.c2
-rw-r--r--drivers/leds/leds.h5
-rw-r--r--drivers/leds/rgb/Kconfig1
-rw-r--r--drivers/leds/rgb/leds-group-multicolor.c2
-rw-r--r--drivers/leds/rgb/leds-ktd202x.c82
-rw-r--r--drivers/leds/rgb/leds-mt6370-rgb.c42
-rw-r--r--drivers/leds/rgb/leds-ncp5623.c16
-rw-r--r--drivers/leds/rgb/leds-pwm-multicolor.c13
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c53
-rw-r--r--drivers/leds/simatic/Kconfig (renamed from drivers/leds/simple/Kconfig)0
-rw-r--r--drivers/leds/simatic/Makefile (renamed from drivers/leds/simple/Makefile)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c)3
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-core.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-core.c)2
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c (renamed from drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c)3
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c107
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio.h (renamed from drivers/leds/simple/simatic-ipc-leds-gpio.h)0
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds.c (renamed from drivers/leds/simple/simatic-ipc-leds.c)1
-rw-r--r--drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c66
-rw-r--r--drivers/leds/trigger/Kconfig23
-rw-r--r--drivers/leds/trigger/Makefile2
-rw-r--r--drivers/leds/trigger/ledtrig-activity.c2
-rw-r--r--drivers/leds/trigger/ledtrig-audio.c67
-rw-r--r--drivers/leds/trigger/ledtrig-input-events.c165
-rw-r--r--drivers/leds/trigger/ledtrig-netdev.c44
-rw-r--r--drivers/leds/trigger/ledtrig-pattern.c126
-rw-r--r--drivers/leds/trigger/ledtrig-timer.c5
-rw-r--r--drivers/leds/trigger/ledtrig-transient.c2
-rw-r--r--drivers/leds/uleds.c1
121 files changed, 5831 insertions, 3004 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 05e6af88b88c..a104cbb0a001 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -179,6 +179,21 @@ config LEDS_CR0014114
To compile this driver as a module, choose M here: the module
will be called leds-cr0014114.
+config LEDS_CROS_EC
+ tristate "LED Support for ChromeOS EC"
+ depends on MFD_CROS_EC_DEV
+ depends on LEDS_CLASS_MULTICOLOR
+ select LEDS_TRIGGERS
+ default MFD_CROS_EC_DEV
+ help
+ This option enables support for LEDs managed by ChromeOS ECs.
+ All LEDs exposed by the EC are supported in multicolor mode.
+ A hardware trigger to switch back to the automatic behaviour is
+ provided.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-cros_ec.
+
config LEDS_EL15203000
tristate "LED Support for Crane EL15203000"
depends on LEDS_CLASS
@@ -202,6 +217,8 @@ config LEDS_TURRIS_OMNIA
depends on I2C
depends on MACH_ARMADA_38X || COMPILE_TEST
depends on OF
+ depends on TURRIS_OMNIA_MCU
+ depends on TURRIS_OMNIA_MCU_GPIO
select LEDS_TRIGGERS
help
This option enables basic support for the LEDs found on the front
@@ -414,7 +431,7 @@ config LEDS_LP50XX
module will be called leds-lp50xx.
config LEDS_LP55XX_COMMON
- tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
+ tristate "Common Driver for TI/National LP5521/5523/55231/5562/5569/8501"
depends on LEDS_CLASS
depends on LEDS_CLASS_MULTICOLOR
depends on OF
@@ -422,8 +439,8 @@ config LEDS_LP55XX_COMMON
select FW_LOADER
select FW_LOADER_USER_HELPER
help
- This option supports common operations for LP5521/5523/55231/5562/8501
- devices.
+ This option supports common operations for LP5521/5523/55231/5562/5569/
+ 8501 devices.
config LEDS_LP5521
tristate "LED Support for N.S. LP5521 LED driver chip"
@@ -456,6 +473,16 @@ config LEDS_LP5562
Driver provides direct control via LED class and interface for
programming the engines.
+config LEDS_LP5569
+ tristate "LED Support for TI LP5569 LED driver chip"
+ depends on LEDS_CLASS && I2C
+ depends on LEDS_LP55XX_COMMON
+ help
+ If you say yes here you get support for TI LP5569 LED driver.
+ It is 9 channels chip with programmable engines.
+ Driver provides direct control via LED class and interface for
+ programming the engines.
+
config LEDS_LP8501
tristate "LED Support for TI LP8501 LED driver chip"
depends on LEDS_CLASS && I2C
@@ -486,6 +513,18 @@ config LEDS_LP8860
on the LP8860 4 channel LED driver using the I2C communication
bus.
+config LEDS_LP8864
+ tristate "LED support for the TI LP8864/LP8866 4/6 channel LED drivers"
+ depends on LEDS_CLASS && I2C && OF
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the TI LP8864-Q1,
+ LP8864S-Q1, LP8866-Q1, LP8866S-Q1 4/6 channel LED backlight
+ drivers with I2C interface.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-lp8864.
+
config LEDS_CLEVO_MAIL
tristate "Mail LED on Clevo notebook"
depends on LEDS_CLASS && BROKEN
@@ -555,6 +594,17 @@ config LEDS_PCA995X
LED driver chips accessed via the I2C bus. Supported
devices include PCA9955BTW, PCA9952TW and PCA9955TW.
+config LEDS_QNAP_MCU
+ tristate "LED Support for QNAP MCU controllers"
+ depends on LEDS_CLASS
+ depends on MFD_QNAP_MCU
+ help
+ This option enables support for LEDs available on embedded
+ controllers used in QNAP NAS devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called qnap-mcu-leds.
+
config LEDS_WM831X_STATUS
tristate "LED support for status LEDs on WM831x PMICs"
depends on LEDS_CLASS
@@ -728,6 +778,14 @@ config LEDS_MAX77650
help
LEDs driver for MAX77650 family of PMICs from Maxim Integrated.
+config LEDS_MAX77705
+ tristate "LED support for Maxim MAX77705 PMIC"
+ depends on MFD_MAX77705
+ depends on LEDS_CLASS
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ LED driver for MAX77705 PMIC from Maxim Integrated.
+
config LEDS_MAX8997
tristate "LED support for MAX8997 PMIC"
depends on LEDS_CLASS && MFD_MAX8997
@@ -790,6 +848,15 @@ config LEDS_SC27XX_BLTC
This driver can also be built as a module. If so the module will be
called leds-sc27xx-bltc.
+config LEDS_UPBOARD
+ tristate "LED support for the UP board"
+ depends on LEDS_CLASS && MFD_UPBOARD_FPGA
+ help
+ This option enables support for the UP board LEDs.
+
+ This driver can also be built as a module. If so the module will be
+ called leds-upboard.
+
comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
config LEDS_BLINKM
@@ -800,6 +867,14 @@ config LEDS_BLINKM
This option enables support for the BlinkM RGB LED connected
through I2C. Say Y to enable support for the BlinkM LED.
+config LEDS_BLINKM_MULTICOLOR
+ bool "Enable multicolor support for BlinkM I2C RGB LED"
+ depends on LEDS_BLINKM
+ depends on LEDS_CLASS_MULTICOLOR=y || LEDS_CLASS_MULTICOLOR=LEDS_BLINKM
+ help
+ This option enables multicolor sysfs class support for BlinkM LED and
+ disables the older, separated sysfs interface
+
config LEDS_POWERNV
tristate "LED support for PowerNV Platform"
depends on LEDS_CLASS
@@ -857,7 +932,8 @@ config LEDS_USER
config LEDS_NIC78BX
tristate "LED support for NI PXI NIC78bx devices"
depends on LEDS_CLASS
- depends on X86 && ACPI
+ depends on HAS_IOPORT
+ depends on X86 || COMPILE_TEST
help
This option enables support for the User1 and User2 LEDs on NI
PXI NIC78bx devices.
@@ -869,7 +945,6 @@ config LEDS_SPI_BYTE
tristate "LED support for SPI LED controller with a single byte"
depends on LEDS_CLASS
depends on SPI
- depends on OF
help
This option enables support for LED controller which use a single byte
for controlling the brightness. Currently the following controller is
@@ -899,6 +974,17 @@ config LEDS_LM36274
Say Y to enable the LM36274 LED driver for TI LMU devices.
This supports the LED device LM36274.
+config LEDS_ST1202
+ tristate "LED Support for STMicroelectronics LED1202 I2C chips"
+ depends on LEDS_CLASS
+ depends on I2C
+ depends on OF
+ select LEDS_TRIGGERS
+ select LEDS_TRIGGER_PATTERN
+ help
+ Say Y to enable support for LEDs connected to LED1202
+ LED driver chips accessed via the I2C bus.
+
config LEDS_TPS6105X
tristate "LED support for TI TPS6105X"
depends on LEDS_CLASS
@@ -938,7 +1024,7 @@ source "drivers/leds/rgb/Kconfig"
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
-comment "Simple LED drivers"
-source "drivers/leds/simple/Kconfig"
+comment "Simatic LED drivers"
+source "drivers/leds/simatic/Kconfig"
endif # NEW_LEDS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index effdfc6f1e95..2f170d69dcbf 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o
obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o
obj-$(CONFIG_LEDS_CPCAP) += leds-cpcap.o
+obj-$(CONFIG_LEDS_CROS_EC) += leds-cros_ec.o
obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o
obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o
@@ -51,13 +52,16 @@ obj-$(CONFIG_LEDS_LP50XX) += leds-lp50xx.o
obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o
obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o
obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o
+obj-$(CONFIG_LEDS_LP5569) += leds-lp5569.o
obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o
obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o
obj-$(CONFIG_LEDS_LP8860) += leds-lp8860.o
+obj-$(CONFIG_LEDS_LP8864) += leds-lp8864.o
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
obj-$(CONFIG_LEDS_MAX5970) += leds-max5970.o
obj-$(CONFIG_LEDS_MAX77650) += leds-max77650.o
+obj-$(CONFIG_LEDS_MAX77705) += leds-max77705.o
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
@@ -77,8 +81,10 @@ obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o
obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
+obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
+obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
@@ -87,6 +93,7 @@ obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o
obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o
+obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o
obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
@@ -115,5 +122,5 @@ obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
# LED Blink
obj-y += blink/
-# Simple LED drivers
-obj-y += simple/
+# Simatic LED drivers
+obj-y += simatic/
diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c
index 2cf2761e4914..ef2e511438cc 100644
--- a/drivers/leds/blink/leds-bcm63138.c
+++ b/drivers/leds/blink/leds-bcm63138.c
@@ -2,6 +2,8 @@
/*
* Copyright (C) 2021 Rafał Miłecki <rafal@milecki.pl>
*/
+#include <linux/bits.h>
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/leds.h>
@@ -19,8 +21,10 @@
#define BCM63138_LEDS_PER_REG (32 / BCM63138_LED_BITS) /* 8 */
#define BCM63138_GLB_CTRL 0x00
-#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL 0x00000002
-#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL 0x00000008
+#define BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL BIT(1)
+#define BCM63138_GLB_CTRL_SERIAL_LED_CLK_POL BIT(2)
+#define BCM63138_GLB_CTRL_SERIAL_LED_EN_POL BIT(3)
+#define BCM63138_GLB_CTRL_SERIAL_LED_MSB_FIRST BIT(4)
#define BCM63138_MASK 0x04
#define BCM63138_HW_LED_EN 0x08
#define BCM63138_SERIAL_LED_SHIFT_SEL 0x0c
@@ -33,6 +37,7 @@
#define BCM63138_BRIGHT_CTRL3 0x28
#define BCM63138_BRIGHT_CTRL4 0x2c
#define BCM63138_POWER_LED_CFG 0x30
+#define BCM63138_POWER_LUT_BASE0 0x34 /* -> b0 */
#define BCM63138_HW_POLARITY 0xb4
#define BCM63138_SW_DATA 0xb8
#define BCM63138_SW_POLARITY 0xbc
@@ -125,17 +130,14 @@ static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev,
{
struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
struct bcm63138_leds *leds = led->leds;
- unsigned long flags;
- spin_lock_irqsave(&leds->lock, flags);
+ guard(spinlock_irqsave)(&leds->lock);
bcm63138_leds_enable_led(leds, led, value);
if (!value)
bcm63138_leds_set_flash_rate(leds, led, 0);
else
bcm63138_leds_set_bright(leds, led, value);
-
- spin_unlock_irqrestore(&leds->lock, flags);
}
static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
@@ -144,7 +146,6 @@ static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
{
struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
struct bcm63138_leds *leds = led->leds;
- unsigned long flags;
u8 value;
if (!*delay_on && !*delay_off) {
@@ -179,13 +180,11 @@ static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
return -EINVAL;
}
- spin_lock_irqsave(&leds->lock, flags);
+ guard(spinlock_irqsave)(&leds->lock);
bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS);
bcm63138_leds_set_flash_rate(leds, led, value);
- spin_unlock_irqrestore(&leds->lock, flags);
-
return 0;
}
@@ -259,7 +258,7 @@ static int bcm63138_leds_probe(struct platform_device *pdev)
struct device_node *np = dev_of_node(&pdev->dev);
struct device *dev = &pdev->dev;
struct bcm63138_leds *leds;
- struct device_node *child;
+ u32 shift_bits;
leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
if (!leds)
@@ -273,6 +272,12 @@ static int bcm63138_leds_probe(struct platform_device *pdev)
spin_lock_init(&leds->lock);
+ /* If this property is not present, we use boot defaults */
+ if (!of_property_read_u32(np, "brcm,serial-shift-bits", &shift_bits)) {
+ bcm63138_leds_write(leds, BCM63138_SERIAL_LED_SHIFT_SEL,
+ GENMASK(shift_bits - 1, 0));
+ }
+
bcm63138_leds_write(leds, BCM63138_GLB_CTRL,
BCM63138_GLB_CTRL_SERIAL_LED_DATA_PPOL |
BCM63138_GLB_CTRL_SERIAL_LED_EN_POL);
@@ -280,7 +285,7 @@ static int bcm63138_leds_probe(struct platform_device *pdev)
bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0);
bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0);
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
bcm63138_leds_create_led(leds, child);
}
@@ -303,5 +308,6 @@ static struct platform_driver bcm63138_leds_driver = {
module_platform_driver(bcm63138_leds_driver);
MODULE_AUTHOR("Rafał Miłecki");
+MODULE_DESCRIPTION("Broadcom BCM63138 SoC LED driver");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, bcm63138_leds_of_match_table);
diff --git a/drivers/leds/blink/leds-lgm-sso.c b/drivers/leds/blink/leds-lgm-sso.c
index 7b04ea146260..effaaaf302b5 100644
--- a/drivers/leds/blink/leds-lgm-sso.c
+++ b/drivers/leds/blink/leds-lgm-sso.c
@@ -861,7 +861,7 @@ MODULE_DEVICE_TABLE(of, of_sso_led_match);
static struct platform_driver intel_sso_led_driver = {
.probe = intel_sso_led_probe,
- .remove_new = intel_sso_led_remove,
+ .remove = intel_sso_led_remove,
.driver = {
.name = "lgm-ssoled",
.of_match_table = of_sso_led_match,
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
index 809b6d98bb3e..f39f0bfe6eef 100644
--- a/drivers/leds/flash/Kconfig
+++ b/drivers/leds/flash/Kconfig
@@ -121,4 +121,15 @@ config LEDS_SGM3140
This option enables support for the SGM3140 500mA Buck/Boost Charge
Pump LED Driver.
+config LEDS_SY7802
+ tristate "LED support for the Silergy SY7802"
+ depends on I2C && OF
+ depends on GPIOLIB
+ select REGMAP_I2C
+ help
+ This option enables support for the SY7802 flash LED controller.
+ SY7802 includes torch and flash functions with programmable current.
+
+ This driver can be built as a module, it will be called "leds-sy7802".
+
endif # LEDS_CLASS_FLASH
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
index 91d60a4b7952..48860eeced79 100644
--- a/drivers/leds/flash/Makefile
+++ b/drivers/leds/flash/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
+obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c
index 0195935a7c99..49251cfd3350 100644
--- a/drivers/leds/flash/leds-aat1290.c
+++ b/drivers/leds/flash/leds-aat1290.c
@@ -7,6 +7,7 @@
* Author: Jacek Anaszewski <j.anaszewski@samsung.com>
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/led-class-flash.h>
@@ -77,8 +78,6 @@ struct aat1290_led {
int *mm_current_scale;
/* device mode */
bool movie_mode;
- /* brightness cache */
- unsigned int torch_brightness;
};
static struct aat1290_led *fled_cdev_to_led(
@@ -217,7 +216,6 @@ static int aat1290_led_parse_dt(struct aat1290_led *led,
struct device_node **sub_node)
{
struct device *dev = &led->pdev->dev;
- struct device_node *child_node;
#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
struct pinctrl *pinctrl;
#endif
@@ -248,7 +246,8 @@ static int aat1290_led_parse_dt(struct aat1290_led *led,
}
#endif
- child_node = of_get_next_available_child(dev_of_node(dev), NULL);
+ struct device_node *child_node __free(device_node) =
+ of_get_next_available_child(dev_of_node(dev), NULL);
if (!child_node) {
dev_err(dev, "No DT child node found for connected LED.\n");
return -EINVAL;
@@ -269,7 +268,7 @@ static int aat1290_led_parse_dt(struct aat1290_led *led,
if (ret < 0) {
dev_err(dev,
"flash-max-microamp DT property missing\n");
- goto err_parse_dt;
+ return ret;
}
ret = of_property_read_u32(child_node, "flash-max-timeout-us",
@@ -277,15 +276,12 @@ static int aat1290_led_parse_dt(struct aat1290_led *led,
if (ret < 0) {
dev_err(dev,
"flash-max-timeout-us DT property missing\n");
- goto err_parse_dt;
+ return ret;
}
*sub_node = child_node;
-err_parse_dt:
- of_node_put(child_node);
-
- return ret;
+ return 0;
}
static void aat1290_led_validate_mm_current(struct aat1290_led *led,
@@ -540,7 +536,7 @@ MODULE_DEVICE_TABLE(of, aat1290_led_dt_match);
static struct platform_driver aat1290_led_driver = {
.probe = aat1290_led_probe,
- .remove_new = aat1290_led_remove,
+ .remove = aat1290_led_remove,
.driver = {
.name = "aat1290",
.of_match_table = aat1290_led_dt_match,
diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c
index 12c2609c1137..2f2d783c62c3 100644
--- a/drivers/leds/flash/leds-as3645a.c
+++ b/drivers/leds/flash/leds-as3645a.c
@@ -478,14 +478,12 @@ static int as3645a_detect(struct as3645a *flash)
return as3645a_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE);
}
-static int as3645a_parse_node(struct as3645a *flash,
- struct fwnode_handle *fwnode)
+static int as3645a_parse_node(struct device *dev, struct as3645a *flash)
{
struct as3645a_config *cfg = &flash->cfg;
- struct fwnode_handle *child;
int rval;
- fwnode_for_each_child_node(fwnode, child) {
+ device_for_each_child_node_scoped(dev, child) {
u32 id = 0;
fwnode_property_read_u32(child, "reg", &id);
@@ -686,7 +684,7 @@ static int as3645a_probe(struct i2c_client *client)
flash->client = client;
- rval = as3645a_parse_node(flash, dev_fwnode(&client->dev));
+ rval = as3645a_parse_node(&client->dev, flash);
if (rval < 0)
return rval;
@@ -743,8 +741,8 @@ static void as3645a_remove(struct i2c_client *client)
}
static const struct i2c_device_id as3645a_id_table[] = {
- { AS_NAME, 0 },
- { },
+ { AS_NAME },
+ { }
};
MODULE_DEVICE_TABLE(i2c, as3645a_id_table);
diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c
index 7bb0aa2753e3..0f16eefcfe4c 100644
--- a/drivers/leds/flash/leds-ktd2692.c
+++ b/drivers/leds/flash/leds-ktd2692.c
@@ -6,6 +6,7 @@
* Ingi Kim <ingi2.kim@samsung.com>
*/
+#include <linux/cleanup.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
#include <linux/leds-expresswire.h>
@@ -208,7 +209,6 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
struct ktd2692_led_config_data *cfg)
{
struct device_node *np = dev_of_node(dev);
- struct device_node *child_node;
int ret;
if (!np)
@@ -239,7 +239,8 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
}
}
- child_node = of_get_next_available_child(np, NULL);
+ struct device_node *child_node __free(device_node) =
+ of_get_next_available_child(np, NULL);
if (!child_node) {
dev_err(dev, "No DT child node found for connected LED.\n");
return -EINVAL;
@@ -252,26 +253,24 @@ static int ktd2692_parse_dt(struct ktd2692_context *led, struct device *dev,
&cfg->movie_max_microamp);
if (ret) {
dev_err(dev, "failed to parse led-max-microamp\n");
- goto err_parse_dt;
+ return ret;
}
ret = of_property_read_u32(child_node, "flash-max-microamp",
&cfg->flash_max_microamp);
if (ret) {
dev_err(dev, "failed to parse flash-max-microamp\n");
- goto err_parse_dt;
+ return ret;
}
ret = of_property_read_u32(child_node, "flash-max-timeout-us",
&cfg->flash_max_timeout);
if (ret) {
dev_err(dev, "failed to parse flash-max-timeout-us\n");
- goto err_parse_dt;
+ return ret;
}
-err_parse_dt:
- of_node_put(child_node);
- return ret;
+ return 0;
}
static const struct led_flash_ops flash_ops = {
@@ -293,6 +292,7 @@ static int ktd2692_probe(struct platform_device *pdev)
fled_cdev = &led->fled_cdev;
led_cdev = &fled_cdev->led_cdev;
+ led->props.timing = ktd2692_timing;
ret = ktd2692_parse_dt(led, &pdev->dev, &led_cfg);
if (ret)
@@ -344,12 +344,12 @@ static struct platform_driver ktd2692_driver = {
.of_match_table = ktd2692_match,
},
.probe = ktd2692_probe,
- .remove_new = ktd2692_remove,
+ .remove = ktd2692_remove,
};
module_platform_driver(ktd2692_driver);
-MODULE_IMPORT_NS(EXPRESSWIRE);
+MODULE_IMPORT_NS("EXPRESSWIRE");
MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>");
MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c
index 7e93c447fec5..abf6b96ade3d 100644
--- a/drivers/leds/flash/leds-lm3601x.c
+++ b/drivers/leds/flash/leds-lm3601x.c
@@ -190,7 +190,7 @@ static int lm3601x_brightness_set(struct led_classdev *cdev,
goto out;
}
- ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness);
+ ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness - 1);
if (ret < 0)
goto out;
@@ -341,8 +341,9 @@ static int lm3601x_register_leds(struct lm3601x_led *led,
led_cdev = &led->fled_cdev.led_cdev;
led_cdev->brightness_set_blocking = lm3601x_brightness_set;
- led_cdev->max_brightness = DIV_ROUND_UP(led->torch_current_max,
- LM3601X_TORCH_REG_DIV);
+ led_cdev->max_brightness =
+ DIV_ROUND_UP(led->torch_current_max - LM3601X_MIN_TORCH_I_UA + 1,
+ LM3601X_TORCH_REG_DIV);
led_cdev->flags |= LED_DEV_CAP_FLASH;
init_data.fwnode = fwnode;
@@ -386,6 +387,14 @@ static int lm3601x_parse_node(struct lm3601x_led *led,
goto out_err;
}
+ if (led->torch_current_max > LM3601X_MAX_TORCH_I_UA) {
+ dev_warn(&led->client->dev,
+ "Max torch current set too high (%d vs %d)\n",
+ led->torch_current_max,
+ LM3601X_MAX_TORCH_I_UA);
+ led->torch_current_max = LM3601X_MAX_TORCH_I_UA;
+ }
+
ret = fwnode_property_read_u32(child, "flash-max-microamp",
&led->flash_current_max);
if (ret) {
@@ -434,6 +443,10 @@ static int lm3601x_probe(struct i2c_client *client)
return ret;
}
+ ret = regmap_write(led->regmap, LM3601X_DEV_ID_REG, LM3601X_SW_RESET);
+ if (ret)
+ dev_warn(&client->dev, "Failed to reset the LED controller\n");
+
mutex_init(&led->lock);
return lm3601x_register_leds(led, fwnode);
diff --git a/drivers/leds/flash/leds-max77693.c b/drivers/leds/flash/leds-max77693.c
index 9f016b851193..daee10986108 100644
--- a/drivers/leds/flash/leds-max77693.c
+++ b/drivers/leds/flash/leds-max77693.c
@@ -599,7 +599,7 @@ static int max77693_led_parse_dt(struct max77693_led_device *led,
{
struct device *dev = &led->pdev->dev;
struct max77693_sub_led *sub_leds = led->sub_leds;
- struct device_node *node = dev_of_node(dev), *child_node;
+ struct device_node *node = dev_of_node(dev);
struct property *prop;
u32 led_sources[2];
int i, ret, fled_id;
@@ -608,7 +608,7 @@ static int max77693_led_parse_dt(struct max77693_led_device *led,
of_property_read_u32(node, "maxim,boost-mvout", &cfg->boost_vout);
of_property_read_u32(node, "maxim,mvsys-min", &cfg->low_vsys);
- for_each_available_child_of_node(node, child_node) {
+ for_each_available_child_of_node_scoped(node, child_node) {
prop = of_find_property(child_node, "led-sources", NULL);
if (prop) {
const __be32 *srcs = NULL;
@@ -622,7 +622,6 @@ static int max77693_led_parse_dt(struct max77693_led_device *led,
} else {
dev_err(dev,
"led-sources DT property missing\n");
- of_node_put(child_node);
return -EINVAL;
}
@@ -638,18 +637,16 @@ static int max77693_led_parse_dt(struct max77693_led_device *led,
} else {
dev_err(dev,
"Wrong led-sources DT property value.\n");
- of_node_put(child_node);
return -EINVAL;
}
if (sub_nodes[fled_id]) {
dev_err(dev,
"Conflicting \"led-sources\" DT properties\n");
- of_node_put(child_node);
return -EINVAL;
}
- sub_nodes[fled_id] = child_node;
+ sub_nodes[fled_id] = of_node_get(child_node);
sub_leds[fled_id].fled_id = fled_id;
cfg->label[fled_id] =
@@ -681,10 +678,8 @@ static int max77693_led_parse_dt(struct max77693_led_device *led,
if (++cfg->num_leds == 2 ||
(max77693_fled_used(led, FLED1) &&
- max77693_fled_used(led, FLED2))) {
- of_node_put(child_node);
+ max77693_fled_used(led, FLED2)))
break;
- }
}
if (cfg->num_leds == 0) {
@@ -968,7 +963,7 @@ static int max77693_led_probe(struct platform_device *pdev)
ret = max77693_setup(led, &led_cfg);
if (ret < 0)
- return ret;
+ goto err_setup;
mutex_init(&led->lock);
@@ -1000,6 +995,8 @@ static int max77693_led_probe(struct platform_device *pdev)
else
goto err_register_led1;
}
+ of_node_put(sub_nodes[i]);
+ sub_nodes[i] = NULL;
}
return 0;
@@ -1013,6 +1010,9 @@ err_register_led2:
err_register_led1:
mutex_destroy(&led->lock);
+err_setup:
+ for (i = FLED1; i <= FLED2; i++)
+ of_node_put(sub_nodes[i]);
return ret;
}
@@ -1042,7 +1042,7 @@ MODULE_DEVICE_TABLE(of, max77693_led_dt_match);
static struct platform_driver max77693_led_driver = {
.probe = max77693_led_probe,
- .remove_new = max77693_led_remove,
+ .remove = max77693_led_remove,
.driver = {
.name = "max77693-led",
.of_match_table = max77693_led_dt_match,
diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c
index a90de82f4568..462a902f54e0 100644
--- a/drivers/leds/flash/leds-mt6360.c
+++ b/drivers/leds/flash/leds-mt6360.c
@@ -241,11 +241,21 @@ static int mt6360_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
u32 enable_mask = MT6360_STROBEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
u32 val = state ? MT6360_FLCSEN_MASK(led->led_no) : 0;
u32 prev = priv->fled_strobe_used, curr;
- int ret;
+ int ret = 0;
mutex_lock(&priv->lock);
/*
+ * If the state of the upcoming change is the same as the current LED
+ * device state, then skip the subsequent code to avoid conflict
+ * with the flow of turning on LED torch mode in V4L2.
+ */
+ if (state == !!(BIT(led->led_no) & prev)) {
+ dev_info(lcdev->dev, "No change in strobe state [0x%x]\n", prev);
+ goto unlock;
+ }
+
+ /*
* Only one set of flash control logic, use the flag to avoid torch is
* currently used
*/
@@ -633,14 +643,17 @@ static int mt6360_init_isnk_properties(struct mt6360_led *led,
ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret || reg > MT6360_LED_ISNK3 ||
- priv->leds_active & BIT(reg))
+ priv->leds_active & BIT(reg)) {
+ fwnode_handle_put(child);
return -EINVAL;
+ }
ret = fwnode_property_read_u32(child, "color", &color);
if (ret) {
dev_err(priv->dev,
"led %d, no color specified\n",
led->led_no);
+ fwnode_handle_put(child);
return ret;
}
@@ -771,7 +784,6 @@ static void mt6360_v4l2_flash_release(struct mt6360_priv *priv)
static int mt6360_led_probe(struct platform_device *pdev)
{
struct mt6360_priv *priv;
- struct fwnode_handle *child;
size_t count;
int i = 0, ret;
@@ -798,7 +810,7 @@ static int mt6360_led_probe(struct platform_device *pdev)
return -ENODEV;
}
- device_for_each_child_node(&pdev->dev, child) {
+ device_for_each_child_node_scoped(&pdev->dev, child) {
struct mt6360_led *led = priv->leds + i;
struct led_init_data init_data = { .fwnode = child, };
u32 reg, led_color;
@@ -874,7 +886,7 @@ static struct platform_driver mt6360_led_driver = {
.of_match_table = mt6360_led_of_id,
},
.probe = mt6360_led_probe,
- .remove_new = mt6360_led_remove,
+ .remove = mt6360_led_remove,
};
module_platform_driver(mt6360_led_driver);
diff --git a/drivers/leds/flash/leds-mt6370-flash.c b/drivers/leds/flash/leds-mt6370-flash.c
index 912d9d622320..dbdbe92309db 100644
--- a/drivers/leds/flash/leds-mt6370-flash.c
+++ b/drivers/leds/flash/leds-mt6370-flash.c
@@ -509,7 +509,6 @@ static int mt6370_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mt6370_priv *priv;
- struct fwnode_handle *child;
size_t count;
int i = 0, ret;
@@ -529,22 +528,18 @@ static int mt6370_led_probe(struct platform_device *pdev)
if (!priv->regmap)
return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n");
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct mt6370_led *led = priv->leds + i;
led->priv = priv;
ret = mt6370_init_flash_properties(dev, led, child);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
ret = mt6370_led_register(dev, led, child);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
i++;
}
diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c
index 7c99a3039171..b4c19be51c4d 100644
--- a/drivers/leds/flash/leds-qcom-flash.c
+++ b/drivers/leds/flash/leds-qcom-flash.c
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
- * Copyright (c) 2022 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/bitfield.h>
@@ -14,6 +14,9 @@
#include <media/v4l2-flash-led-class.h>
/* registers definitions */
+#define FLASH_REVISION_REG 0x00
+#define FLASH_4CH_REVISION_V0P1 0x01
+
#define FLASH_TYPE_REG 0x04
#define FLASH_TYPE_VAL 0x18
@@ -73,6 +76,16 @@
#define UA_PER_MA 1000
+/* thermal threshold constants */
+#define OTST_3CH_MIN_VAL 3
+#define OTST1_4CH_MIN_VAL 0
+#define OTST1_4CH_V0P1_MIN_VAL 3
+#define OTST2_4CH_MIN_VAL 0
+
+#define OTST1_MAX_CURRENT_MA 1000
+#define OTST2_MAX_CURRENT_MA 500
+#define OTST3_MAX_CURRENT_MA 200
+
enum hw_type {
QCOM_MVFLASH_3CH,
QCOM_MVFLASH_4CH,
@@ -98,6 +111,9 @@ enum {
REG_IRESOLUTION,
REG_CHAN_STROBE,
REG_CHAN_EN,
+ REG_THERM_THRSH1,
+ REG_THERM_THRSH2,
+ REG_THERM_THRSH3,
REG_MAX_COUNT,
};
@@ -111,6 +127,9 @@ static struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
REG_FIELD(0x47, 0, 5), /* iresolution */
REG_FIELD_ID(0x49, 0, 2, 3, 1), /* chan_strobe */
REG_FIELD(0x4c, 0, 2), /* chan_en */
+ REG_FIELD(0x56, 0, 2), /* therm_thrsh1 */
+ REG_FIELD(0x57, 0, 2), /* therm_thrsh2 */
+ REG_FIELD(0x58, 0, 2), /* therm_thrsh3 */
};
static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
@@ -123,6 +142,8 @@ static struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
REG_FIELD(0x49, 0, 3), /* iresolution */
REG_FIELD_ID(0x4a, 0, 6, 4, 1), /* chan_strobe */
REG_FIELD(0x4e, 0, 3), /* chan_en */
+ REG_FIELD(0x7a, 0, 2), /* therm_thrsh1 */
+ REG_FIELD(0x78, 0, 2), /* therm_thrsh2 */
};
struct qcom_flash_data {
@@ -130,9 +151,11 @@ struct qcom_flash_data {
struct regmap_field *r_fields[REG_MAX_COUNT];
struct mutex lock;
enum hw_type hw_type;
+ u32 total_ma;
u8 leds_count;
u8 max_channels;
u8 chan_en_bits;
+ u8 revision;
};
struct qcom_flash_led {
@@ -143,6 +166,7 @@ struct qcom_flash_led {
u32 max_timeout_ms;
u32 flash_current_ma;
u32 flash_timeout_ms;
+ u32 current_in_use_ma;
u8 *chan_id;
u8 chan_count;
bool enabled;
@@ -172,6 +196,127 @@ static int set_flash_module_en(struct qcom_flash_led *led, bool en)
return rc;
}
+static int update_allowed_flash_current(struct qcom_flash_led *led, u32 *current_ma, bool strobe)
+{
+ struct qcom_flash_data *flash_data = led->flash_data;
+ u32 therm_ma, avail_ma, thrsh[3], min_thrsh, sts;
+ int rc = 0;
+
+ mutex_lock(&flash_data->lock);
+ /*
+ * Put previously allocated current into allowed budget in either of these two cases:
+ * 1) LED is disabled;
+ * 2) LED is enabled repeatedly
+ */
+ if (!strobe || led->current_in_use_ma != 0) {
+ if (flash_data->total_ma >= led->current_in_use_ma)
+ flash_data->total_ma -= led->current_in_use_ma;
+ else
+ flash_data->total_ma = 0;
+
+ led->current_in_use_ma = 0;
+ if (!strobe)
+ goto unlock;
+ }
+
+ /*
+ * Cache the default thermal threshold settings, and set them to the lowest levels before
+ * reading over-temp real time status. If over-temp has been triggered at the lowest
+ * threshold, it's very likely that it would be triggered at a higher (default) threshold
+ * when more flash current is requested. Prevent device from triggering over-temp condition
+ * by limiting the flash current for the new request.
+ */
+ rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH1], &thrsh[0]);
+ if (rc < 0)
+ goto unlock;
+
+ rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH2], &thrsh[1]);
+ if (rc < 0)
+ goto unlock;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ rc = regmap_field_read(flash_data->r_fields[REG_THERM_THRSH3], &thrsh[2]);
+ if (rc < 0)
+ goto unlock;
+ }
+
+ min_thrsh = OTST_3CH_MIN_VAL;
+ if (flash_data->hw_type == QCOM_MVFLASH_4CH)
+ min_thrsh = (flash_data->revision == FLASH_4CH_REVISION_V0P1) ?
+ OTST1_4CH_V0P1_MIN_VAL : OTST1_4CH_MIN_VAL;
+
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], min_thrsh);
+ if (rc < 0)
+ goto unlock;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_4CH)
+ min_thrsh = OTST2_4CH_MIN_VAL;
+
+ /*
+ * The default thermal threshold settings have been updated hence
+ * restore them if any fault happens starting from here.
+ */
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], min_thrsh);
+ if (rc < 0)
+ goto restore;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], min_thrsh);
+ if (rc < 0)
+ goto restore;
+ }
+
+ /* Read thermal level status to get corresponding derating flash current */
+ rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &sts);
+ if (rc)
+ goto restore;
+
+ therm_ma = FLASH_TOTAL_CURRENT_MAX_UA / 1000;
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ if (sts & FLASH_STS_3CH_OTST3)
+ therm_ma = OTST3_MAX_CURRENT_MA;
+ else if (sts & FLASH_STS_3CH_OTST2)
+ therm_ma = OTST2_MAX_CURRENT_MA;
+ else if (sts & FLASH_STS_3CH_OTST1)
+ therm_ma = OTST1_MAX_CURRENT_MA;
+ } else {
+ if (sts & FLASH_STS_4CH_OTST2)
+ therm_ma = OTST2_MAX_CURRENT_MA;
+ else if (sts & FLASH_STS_4CH_OTST1)
+ therm_ma = OTST1_MAX_CURRENT_MA;
+ }
+
+ /* Calculate the allowed flash current for the request */
+ if (therm_ma <= flash_data->total_ma)
+ avail_ma = 0;
+ else
+ avail_ma = therm_ma - flash_data->total_ma;
+
+ *current_ma = min_t(u32, *current_ma, avail_ma);
+ led->current_in_use_ma = *current_ma;
+ flash_data->total_ma += led->current_in_use_ma;
+
+ dev_dbg(led->flash.led_cdev.dev, "allowed flash current: %dmA, total current: %dmA\n",
+ led->current_in_use_ma, flash_data->total_ma);
+
+restore:
+ /* Restore to default thermal threshold settings */
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH1], thrsh[0]);
+ if (rc < 0)
+ goto unlock;
+
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH2], thrsh[1]);
+ if (rc < 0)
+ goto unlock;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH)
+ rc = regmap_field_write(flash_data->r_fields[REG_THERM_THRSH3], thrsh[2]);
+
+unlock:
+ mutex_unlock(&flash_data->lock);
+ return rc;
+}
+
static int set_flash_current(struct qcom_flash_led *led, u32 current_ma, enum led_mode mode)
{
struct qcom_flash_data *flash_data = led->flash_data;
@@ -313,6 +458,10 @@ static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool stat
if (rc)
return rc;
+ rc = update_allowed_flash_current(led, &led->flash_current_ma, state);
+ if (rc < 0)
+ return rc;
+
rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
if (rc)
return rc;
@@ -429,6 +578,10 @@ static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
if (rc)
return rc;
+ rc = update_allowed_flash_current(led, &current_ma, enable);
+ if (rc < 0)
+ return rc;
+
rc = set_flash_current(led, current_ma, TORCH_MODE);
if (rc)
return rc;
@@ -505,6 +658,7 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno
struct qcom_flash_data *flash_data = led->flash_data;
struct v4l2_flash_config v4l2_cfg = { 0 };
struct led_flash_setting *intensity = &v4l2_cfg.intensity;
+ struct v4l2_flash *v4l2_flash;
if (!(led->flash.led_cdev.flags & LED_DEV_CAP_FLASH))
return 0;
@@ -523,9 +677,12 @@ qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwno
LED_FAULT_OVER_TEMPERATURE |
LED_FAULT_TIMEOUT;
- flash_data->v4l2_flash[flash_data->leds_count] =
- v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
- return PTR_ERR_OR_ZERO(flash_data->v4l2_flash);
+ v4l2_flash = v4l2_flash_init(dev, fwnode, &led->flash, &qcom_v4l2_flash_ops, &v4l2_cfg);
+ if (IS_ERR(v4l2_flash))
+ return PTR_ERR(v4l2_flash);
+
+ flash_data->v4l2_flash[flash_data->leds_count] = v4l2_flash;
+ return 0;
}
# else
static int
@@ -655,7 +812,6 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
{
struct qcom_flash_data *flash_data;
struct qcom_flash_led *led;
- struct fwnode_handle *child;
struct device *dev = &pdev->dev;
struct regmap *regmap;
struct reg_field *regs;
@@ -703,6 +859,14 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
flash_data->hw_type = QCOM_MVFLASH_4CH;
flash_data->max_channels = 4;
regs = mvflash_4ch_regs;
+
+ rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc);
+ return rc;
+ }
+
+ flash_data->revision = val;
} else {
dev_err(dev, "flash LED subtype %#x is not yet supported\n", val);
return -ENODEV;
@@ -731,7 +895,7 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
if (!flash_data->v4l2_flash)
return -ENOMEM;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led) {
rc = -ENOMEM;
@@ -749,7 +913,6 @@ static int qcom_flash_led_probe(struct platform_device *pdev)
return 0;
release:
- fwnode_handle_put(child);
while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
return rc;
@@ -777,7 +940,7 @@ static struct platform_driver qcom_flash_led_driver = {
.of_match_table = qcom_flash_led_match_table,
},
.probe = qcom_flash_led_probe,
- .remove_new = qcom_flash_led_remove,
+ .remove = qcom_flash_led_remove,
};
module_platform_driver(qcom_flash_led_driver);
diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c
index 1ae5b387f4a5..f16358b8dfc1 100644
--- a/drivers/leds/flash/leds-rt4505.c
+++ b/drivers/leds/flash/leds-rt4505.c
@@ -426,4 +426,5 @@ static struct i2c_driver rt4505_driver = {
module_i2c_driver(rt4505_driver);
MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("Richtek RT4505 LED driver");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-rt8515.c b/drivers/leds/flash/leds-rt8515.c
index eef426924eaf..32ba397a33d2 100644
--- a/drivers/leds/flash/leds-rt8515.c
+++ b/drivers/leds/flash/leds-rt8515.c
@@ -127,7 +127,7 @@ static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,
mod_timer(&rt->powerdown_timer,
jiffies + usecs_to_jiffies(timeout->val));
} else {
- del_timer_sync(&rt->powerdown_timer);
+ timer_delete_sync(&rt->powerdown_timer);
/* Turn the LED off */
rt8515_gpio_led_off(rt);
}
@@ -372,7 +372,7 @@ static void rt8515_remove(struct platform_device *pdev)
struct rt8515 *rt = platform_get_drvdata(pdev);
rt8515_v4l2_flash_release(rt);
- del_timer_sync(&rt->powerdown_timer);
+ timer_delete_sync(&rt->powerdown_timer);
mutex_destroy(&rt->lock);
}
@@ -388,7 +388,7 @@ static struct platform_driver rt8515_driver = {
.of_match_table = rt8515_match,
},
.probe = rt8515_probe,
- .remove_new = rt8515_remove,
+ .remove = rt8515_remove,
};
module_platform_driver(rt8515_driver);
diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c
index db0ac6641954..48fb8a9ec703 100644
--- a/drivers/leds/flash/leds-sgm3140.c
+++ b/drivers/leds/flash/leds-sgm3140.c
@@ -55,7 +55,7 @@ static int sgm3140_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
mod_timer(&priv->powerdown_timer,
jiffies + usecs_to_jiffies(priv->timeout));
} else {
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
gpiod_set_value_cansleep(priv->enable_gpio, 0);
gpiod_set_value_cansleep(priv->flash_gpio, 0);
ret = regulator_disable(priv->vin_regulator);
@@ -117,7 +117,7 @@ static int sgm3140_brightness_set(struct led_classdev *led_cdev,
gpiod_set_value_cansleep(priv->flash_gpio, 0);
gpiod_set_value_cansleep(priv->enable_gpio, 1);
} else {
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
gpiod_set_value_cansleep(priv->flash_gpio, 0);
gpiod_set_value_cansleep(priv->enable_gpio, 0);
ret = regulator_disable(priv->vin_regulator);
@@ -285,7 +285,7 @@ static void sgm3140_remove(struct platform_device *pdev)
{
struct sgm3140 *priv = platform_get_drvdata(pdev);
- del_timer_sync(&priv->powerdown_timer);
+ timer_delete_sync(&priv->powerdown_timer);
v4l2_flash_release(priv->v4l2_flash);
}
@@ -300,7 +300,7 @@ MODULE_DEVICE_TABLE(of, sgm3140_dt_match);
static struct platform_driver sgm3140_driver = {
.probe = sgm3140_probe,
- .remove_new = sgm3140_remove,
+ .remove = sgm3140_remove,
.driver = {
.name = "sgm3140",
.of_match_table = sgm3140_dt_match,
diff --git a/drivers/leds/flash/leds-sy7802.c b/drivers/leds/flash/leds-sy7802.c
new file mode 100644
index 000000000000..ddac836762af
--- /dev/null
+++ b/drivers/leds/flash/leds-sy7802.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Silergy SY7802 flash LED driver with an I2C interface
+ *
+ * Copyright 2024 André Apitzsch <git@apitzsch.eu>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define SY7802_MAX_LEDS 2
+#define SY7802_LED_JOINT 2
+
+#define SY7802_REG_ENABLE 0x10
+#define SY7802_REG_TORCH_BRIGHTNESS 0xa0
+#define SY7802_REG_FLASH_BRIGHTNESS 0xb0
+#define SY7802_REG_FLASH_DURATION 0xc0
+#define SY7802_REG_FLAGS 0xd0
+#define SY7802_REG_CONFIG_1 0xe0
+#define SY7802_REG_CONFIG_2 0xf0
+#define SY7802_REG_VIN_MONITOR 0x80
+#define SY7802_REG_LAST_FLASH 0x81
+#define SY7802_REG_VLED_MONITOR 0x30
+#define SY7802_REG_ADC_DELAY 0x31
+#define SY7802_REG_DEV_ID 0xff
+
+#define SY7802_MODE_OFF 0
+#define SY7802_MODE_TORCH 2
+#define SY7802_MODE_FLASH 3
+#define SY7802_MODE_MASK GENMASK(1, 0)
+
+#define SY7802_LEDS_SHIFT 3
+#define SY7802_LEDS_MASK(_id) (BIT(_id) << SY7802_LEDS_SHIFT)
+#define SY7802_LEDS_MASK_ALL (SY7802_LEDS_MASK(0) | SY7802_LEDS_MASK(1))
+
+#define SY7802_TORCH_CURRENT_SHIFT 3
+#define SY7802_TORCH_CURRENT_MASK(_id) \
+ (GENMASK(2, 0) << (SY7802_TORCH_CURRENT_SHIFT * (_id)))
+#define SY7802_TORCH_CURRENT_MASK_ALL \
+ (SY7802_TORCH_CURRENT_MASK(0) | SY7802_TORCH_CURRENT_MASK(1))
+
+#define SY7802_FLASH_CURRENT_SHIFT 4
+#define SY7802_FLASH_CURRENT_MASK(_id) \
+ (GENMASK(3, 0) << (SY7802_FLASH_CURRENT_SHIFT * (_id)))
+#define SY7802_FLASH_CURRENT_MASK_ALL \
+ (SY7802_FLASH_CURRENT_MASK(0) | SY7802_FLASH_CURRENT_MASK(1))
+
+#define SY7802_TIMEOUT_DEFAULT_US 512000U
+#define SY7802_TIMEOUT_MIN_US 32000U
+#define SY7802_TIMEOUT_MAX_US 1024000U
+#define SY7802_TIMEOUT_STEPSIZE_US 32000U
+
+#define SY7802_TORCH_BRIGHTNESS_MAX 8
+
+#define SY7802_FLASH_BRIGHTNESS_DEFAULT 14
+#define SY7802_FLASH_BRIGHTNESS_MIN 0
+#define SY7802_FLASH_BRIGHTNESS_MAX 15
+#define SY7802_FLASH_BRIGHTNESS_STEP 1
+
+#define SY7802_FLAG_TIMEOUT BIT(0)
+#define SY7802_FLAG_THERMAL_SHUTDOWN BIT(1)
+#define SY7802_FLAG_LED_FAULT BIT(2)
+#define SY7802_FLAG_TX1_INTERRUPT BIT(3)
+#define SY7802_FLAG_TX2_INTERRUPT BIT(4)
+#define SY7802_FLAG_LED_THERMAL_FAULT BIT(5)
+#define SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW BIT(6)
+#define SY7802_FLAG_INPUT_VOLTAGE_LOW BIT(7)
+
+#define SY7802_CHIP_ID 0x51
+
+static const struct reg_default sy7802_regmap_defs[] = {
+ { SY7802_REG_ENABLE, SY7802_LEDS_MASK_ALL },
+ { SY7802_REG_TORCH_BRIGHTNESS, 0x92 },
+ { SY7802_REG_FLASH_BRIGHTNESS, SY7802_FLASH_BRIGHTNESS_DEFAULT |
+ SY7802_FLASH_BRIGHTNESS_DEFAULT << SY7802_FLASH_CURRENT_SHIFT },
+ { SY7802_REG_FLASH_DURATION, 0x6f },
+ { SY7802_REG_FLAGS, 0x0 },
+ { SY7802_REG_CONFIG_1, 0x68 },
+ { SY7802_REG_CONFIG_2, 0xf0 },
+};
+
+struct sy7802_led {
+ struct led_classdev_flash flash;
+ struct sy7802 *chip;
+ u8 led_id;
+};
+
+struct sy7802 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex mutex;
+
+ struct gpio_desc *enable_gpio;
+ struct regulator *vin_regulator;
+
+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+ unsigned int leds_active;
+ int num_leds;
+ struct sy7802_led leds[] __counted_by(num_leds);
+};
+
+static int sy7802_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness brightness)
+{
+ struct sy7802_led *led = container_of(lcdev, struct sy7802_led, flash.led_cdev);
+ struct sy7802 *chip = led->chip;
+ u32 fled_torch_used_tmp;
+ u32 led_enable_mask;
+ u32 enable_mask;
+ u32 torch_mask;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ if (chip->fled_strobe_used) {
+ dev_warn(chip->dev, "Cannot set torch brightness whilst strobe is enabled\n");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (brightness)
+ fled_torch_used_tmp = chip->fled_torch_used | BIT(led->led_id);
+ else
+ fled_torch_used_tmp = chip->fled_torch_used & ~BIT(led->led_id);
+
+ led_enable_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_LEDS_MASK_ALL :
+ SY7802_LEDS_MASK(led->led_id);
+
+ val = brightness ? led_enable_mask : SY7802_MODE_OFF;
+ if (fled_torch_used_tmp)
+ val |= SY7802_MODE_TORCH;
+
+ /* Disable torch to apply brightness */
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_MASK,
+ SY7802_MODE_OFF);
+ if (ret)
+ goto unlock;
+
+ torch_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_TORCH_CURRENT_MASK_ALL :
+ SY7802_TORCH_CURRENT_MASK(led->led_id);
+
+ /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */
+ if (brightness)
+ brightness -= 1;
+
+ brightness |= (brightness << SY7802_TORCH_CURRENT_SHIFT);
+
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, torch_mask, brightness);
+ if (ret)
+ goto unlock;
+
+ enable_mask = SY7802_MODE_MASK | led_enable_mask;
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
+ if (ret)
+ goto unlock;
+
+ chip->fled_torch_used = fled_torch_used_tmp;
+
+unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static int sy7802_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+ struct sy7802 *chip = led->chip;
+ u32 flash_mask;
+ int ret;
+
+ val |= (val << SY7802_FLASH_CURRENT_SHIFT);
+ flash_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_FLASH_CURRENT_MASK_ALL :
+ SY7802_FLASH_CURRENT_MASK(led->led_id);
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_FLASH_BRIGHTNESS, flash_mask, val);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static int sy7802_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+ u32 fled_strobe_used_tmp;
+ u32 led_enable_mask;
+ u32 enable_mask;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ if (chip->fled_torch_used) {
+ dev_warn(chip->dev, "Cannot set strobe brightness whilst torch is enabled\n");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (state)
+ fled_strobe_used_tmp = chip->fled_strobe_used | BIT(led->led_id);
+ else
+ fled_strobe_used_tmp = chip->fled_strobe_used & ~BIT(led->led_id);
+
+ led_enable_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_LEDS_MASK_ALL :
+ SY7802_LEDS_MASK(led->led_id);
+
+ val = state ? led_enable_mask : SY7802_MODE_OFF;
+ if (fled_strobe_used_tmp)
+ val |= SY7802_MODE_FLASH;
+
+ enable_mask = SY7802_MODE_MASK | led_enable_mask;
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
+
+ if (ret)
+ goto unlock;
+
+ chip->fled_strobe_used = fled_strobe_used_tmp;
+
+unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static int sy7802_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+
+ mutex_lock(&chip->mutex);
+ *state = !!(chip->fled_strobe_used & BIT(led->led_id));
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int sy7802_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ struct sy7802 *chip = led->chip;
+
+ return regmap_write(chip->regmap, SY7802_REG_FLASH_DURATION, val);
+}
+
+static int sy7802_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+ u32 val, led_faults = 0;
+ int ret;
+
+ /* NOTE: reading register clears fault status */
+ ret = regmap_read(chip->regmap, SY7802_REG_FLAGS, &val);
+ if (ret)
+ return ret;
+
+ if (val & (SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW | SY7802_FLAG_INPUT_VOLTAGE_LOW))
+ led_faults |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (val & SY7802_FLAG_THERMAL_SHUTDOWN)
+ led_faults |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (val & SY7802_FLAG_TIMEOUT)
+ led_faults |= LED_FAULT_TIMEOUT;
+
+ *fault = led_faults;
+ return 0;
+}
+
+static const struct led_flash_ops sy7802_flash_ops = {
+ .flash_brightness_set = sy7802_flash_brightness_set,
+ .strobe_set = sy7802_strobe_set,
+ .strobe_get = sy7802_strobe_get,
+ .timeout_set = sy7802_timeout_set,
+ .fault_get = sy7802_fault_get,
+};
+
+static void sy7802_init_flash_brightness(struct led_classdev_flash *fl_cdev)
+{
+ struct led_flash_setting *s;
+
+ /* Init flash brightness setting */
+ s = &fl_cdev->brightness;
+ s->min = SY7802_FLASH_BRIGHTNESS_MIN;
+ s->max = SY7802_FLASH_BRIGHTNESS_MAX;
+ s->step = SY7802_FLASH_BRIGHTNESS_STEP;
+ s->val = SY7802_FLASH_BRIGHTNESS_DEFAULT;
+}
+
+static void sy7802_init_flash_timeout(struct led_classdev_flash *fl_cdev)
+{
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fl_cdev->timeout;
+ s->min = SY7802_TIMEOUT_MIN_US;
+ s->max = SY7802_TIMEOUT_MAX_US;
+ s->step = SY7802_TIMEOUT_STEPSIZE_US;
+ s->val = SY7802_TIMEOUT_DEFAULT_US;
+}
+
+static int sy7802_led_register(struct device *dev, struct sy7802_led *led,
+ struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ int ret;
+
+ init_data.fwnode = of_fwnode_handle(np);
+
+ ret = devm_led_classdev_flash_register_ext(dev, &led->flash, &init_data);
+ if (ret) {
+ dev_err(dev, "Couldn't register flash %d\n", led->led_id);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sy7802_init_flash_properties(struct device *dev, struct sy7802_led *led,
+ struct device_node *np)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ u32 sources[SY7802_MAX_LEDS];
+ int i, num, ret;
+
+ num = of_property_count_u32_elems(np, "led-sources");
+ if (num < 1) {
+ dev_err(dev, "Not specified or wrong number of led-sources\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(np, "led-sources", sources, num);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num; i++) {
+ if (sources[i] >= SY7802_MAX_LEDS)
+ return -EINVAL;
+ if (led->chip->leds_active & BIT(sources[i]))
+ return -EINVAL;
+ led->chip->leds_active |= BIT(sources[i]);
+ }
+
+ /* If both channels are specified in 'led-sources', joint flash output mode is used */
+ led->led_id = num == 2 ? SY7802_LED_JOINT : sources[0];
+
+ lcdev->max_brightness = SY7802_TORCH_BRIGHTNESS_MAX;
+ lcdev->brightness_set_blocking = sy7802_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ flash->ops = &sy7802_flash_ops;
+
+ sy7802_init_flash_brightness(flash);
+ sy7802_init_flash_timeout(flash);
+
+ return 0;
+}
+
+static int sy7802_chip_check(struct sy7802 *chip)
+{
+ struct device *dev = chip->dev;
+ u32 chipid;
+ int ret;
+
+ ret = regmap_read(chip->regmap, SY7802_REG_DEV_ID, &chipid);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read chip ID\n");
+
+ if (chipid != SY7802_CHIP_ID)
+ return dev_err_probe(dev, -ENODEV, "Unsupported chip detected: %x\n", chipid);
+
+ return 0;
+}
+
+static void sy7802_enable(struct sy7802 *chip)
+{
+ gpiod_set_value_cansleep(chip->enable_gpio, 1);
+ usleep_range(200, 300);
+}
+
+static void sy7802_disable(struct sy7802 *chip)
+{
+ gpiod_set_value_cansleep(chip->enable_gpio, 0);
+}
+
+static int sy7802_probe_dt(struct sy7802 *chip)
+{
+ struct device_node *np = dev_of_node(chip->dev);
+ int child_num;
+ int ret;
+
+ regmap_write(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_OFF);
+ regmap_write(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, LED_OFF);
+
+ child_num = 0;
+ for_each_available_child_of_node_scoped(np, child) {
+ struct sy7802_led *led = chip->leds + child_num;
+
+ led->chip = chip;
+ led->led_id = child_num;
+
+ ret = sy7802_init_flash_properties(chip->dev, led, child);
+ if (ret)
+ return ret;
+
+ ret = sy7802_led_register(chip->dev, led, child);
+ if (ret)
+ return ret;
+
+ child_num++;
+ }
+ return 0;
+}
+
+static void sy7802_chip_disable_action(void *data)
+{
+ struct sy7802 *chip = data;
+
+ sy7802_disable(chip);
+}
+
+static void sy7802_regulator_disable_action(void *data)
+{
+ struct sy7802 *chip = data;
+
+ regulator_disable(chip->vin_regulator);
+}
+
+static const struct regmap_config sy7802_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults = sy7802_regmap_defs,
+ .num_reg_defaults = ARRAY_SIZE(sy7802_regmap_defs),
+};
+
+static int sy7802_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sy7802 *chip;
+ size_t count;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > SY7802_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL, "Invalid amount of LED nodes %zu\n", count);
+
+ chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->num_leds = count;
+
+ chip->dev = dev;
+ i2c_set_clientdata(client, chip);
+
+ chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(chip->enable_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request enable gpio\n");
+
+ chip->vin_regulator = devm_regulator_get(dev, "vin");
+ ret = PTR_ERR_OR_ZERO(chip->vin_regulator);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request regulator\n");
+
+ ret = regulator_enable(chip->vin_regulator);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulator\n");
+
+ ret = devm_add_action_or_reset(dev, sy7802_regulator_disable_action, chip);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &chip->mutex);
+ if (ret)
+ return ret;
+
+ mutex_lock(&chip->mutex);
+
+ chip->regmap = devm_regmap_init_i2c(client, &sy7802_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err_probe(dev, ret, "Failed to allocate register map\n");
+ goto error;
+ }
+
+ ret = sy7802_probe_dt(chip);
+ if (ret < 0)
+ goto error;
+
+ sy7802_enable(chip);
+
+ ret = devm_add_action_or_reset(dev, sy7802_chip_disable_action, chip);
+ if (ret)
+ goto error;
+
+ ret = sy7802_chip_check(chip);
+
+error:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static const struct of_device_id __maybe_unused sy7802_leds_match[] = {
+ { .compatible = "silergy,sy7802", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sy7802_leds_match);
+
+static struct i2c_driver sy7802_driver = {
+ .driver = {
+ .name = "sy7802",
+ .of_match_table = of_match_ptr(sy7802_leds_match),
+ },
+ .probe = sy7802_probe,
+};
+module_i2c_driver(sy7802_driver);
+
+MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>");
+MODULE_DESCRIPTION("Silergy SY7802 flash LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
index 6fe9d700dfef..f4e26ce84862 100644
--- a/drivers/leds/led-class-flash.c
+++ b/drivers/leds/led-class-flash.c
@@ -12,7 +12,6 @@
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/slab.h>
-#include "leds.h"
#define has_flash_op(fled_cdev, op) \
(fled_cdev && fled_cdev->ops->op)
diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
index ec62a4811613..b2a87c994816 100644
--- a/drivers/leds/led-class-multicolor.c
+++ b/drivers/leds/led-class-multicolor.c
@@ -11,8 +11,6 @@
#include <linux/slab.h>
#include <linux/uaccess.h>
-#include "leds.h"
-
int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
enum led_brightness brightness)
{
@@ -101,7 +99,7 @@ static ssize_t multi_index_show(struct device *dev,
for (i = 0; i < mcled_cdev->num_colors; i++) {
index = mcled_cdev->subled_info[i].color_index;
- len += sprintf(buf + len, "%s", led_colors[index]);
+ len += sprintf(buf + len, "%s", led_get_color_name(index));
if (i < mcled_cdev->num_colors - 1)
len += sprintf(buf + len, " ");
}
@@ -134,6 +132,7 @@ int led_classdev_multicolor_register_ext(struct device *parent,
return -EINVAL;
led_cdev = &mcled_cdev->led_cdev;
+ led_cdev->flags |= LED_MULTI_COLOR;
mcled_cdev->led_cdev.groups = led_multicolor_groups;
return led_classdev_register_ext(parent, led_cdev, init_data);
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 24fcff682b24..c20ac8ccf52b 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -25,15 +25,20 @@
static DEFINE_MUTEX(leds_lookup_lock);
static LIST_HEAD(leds_lookup_list);
+static struct workqueue_struct *leds_wq;
+
static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned int brightness;
- /* no lock needed for this */
+ mutex_lock(&led_cdev->led_access);
led_update_brightness(led_cdev);
+ brightness = led_cdev->brightness;
+ mutex_unlock(&led_cdev->led_access);
- return sprintf(buf, "%u\n", led_cdev->brightness);
+ return sprintf(buf, "%u\n", brightness);
}
static ssize_t brightness_store(struct device *dev,
@@ -57,7 +62,6 @@ static ssize_t brightness_store(struct device *dev,
if (state == LED_OFF)
led_trigger_remove(led_cdev);
led_set_brightness(led_cdev, state);
- flush_work(&led_cdev->set_brightness_work);
ret = size;
unlock:
@@ -70,19 +74,24 @@ static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned int max_brightness;
+
+ mutex_lock(&led_cdev->led_access);
+ max_brightness = led_cdev->max_brightness;
+ mutex_unlock(&led_cdev->led_access);
- return sprintf(buf, "%u\n", led_cdev->max_brightness);
+ return sprintf(buf, "%u\n", max_brightness);
}
static DEVICE_ATTR_RO(max_brightness);
#ifdef CONFIG_LEDS_TRIGGERS
-static BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
-static struct bin_attribute *led_trigger_bin_attrs[] = {
+static const BIN_ATTR(trigger, 0644, led_trigger_read, led_trigger_write, 0);
+static const struct bin_attribute *const led_trigger_bin_attrs[] = {
&bin_attr_trigger,
NULL,
};
static const struct attribute_group led_trigger_group = {
- .bin_attrs = led_trigger_bin_attrs,
+ .bin_attrs_new = led_trigger_bin_attrs,
};
#endif
@@ -258,7 +267,6 @@ struct led_classdev *of_led_get(struct device_node *np, int index)
led_dev = class_find_device_by_of_node(&leds_class, led_node);
of_node_put(led_node);
- put_device(led_dev);
return led_module_get(led_dev);
}
@@ -503,6 +511,11 @@ int led_classdev_register_ext(struct device *parent,
ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
if (ret < 0)
return ret;
+ else if (ret && led_cdev->flags & LED_REJECT_NAME_CONFLICT)
+ return -EEXIST;
+ else if (ret)
+ dev_warn(parent, "Led %s renamed to %s due to name collision\n",
+ proposed_name, final_name);
if (led_cdev->color >= LED_COLOR_ID_MAX)
dev_warn(parent, "LED %s color identifier out of range\n", final_name);
@@ -518,10 +531,6 @@ int led_classdev_register_ext(struct device *parent,
if (init_data && init_data->fwnode)
device_set_node(led_cdev->dev, init_data->fwnode);
- if (ret)
- dev_warn(parent, "Led %s renamed to %s due to name collision",
- proposed_name, dev_name(led_cdev->dev));
-
if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
ret = led_add_brightness_hw_changed(led_cdev);
if (ret) {
@@ -549,15 +558,11 @@ int led_classdev_register_ext(struct device *parent,
led_update_brightness(led_cdev);
+ led_cdev->wq = leds_wq;
+
led_init_core(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
- /*
- * If no default trigger was given and hw_control_trigger is set,
- * make it the default trigger.
- */
- if (!led_cdev->default_trigger && led_cdev->hw_control_trigger)
- led_cdev->default_trigger = led_cdev->hw_control_trigger;
led_trigger_set_default(led_cdev);
#endif
@@ -673,12 +678,19 @@ EXPORT_SYMBOL_GPL(devm_led_classdev_unregister);
static int __init leds_init(void)
{
+ leds_wq = alloc_ordered_workqueue("leds", 0);
+ if (!leds_wq) {
+ pr_err("Failed to create LEDs ordered workqueue\n");
+ return -ENOMEM;
+ }
+
return class_register(&leds_class);
}
static void __exit leds_exit(void)
{
class_unregister(&leds_class);
+ destroy_workqueue(leds_wq);
}
subsys_initcall(leds_init);
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 89c9806cc97f..907fc703e0c5 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -8,6 +8,7 @@
*/
#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
#include <linux/leds.h>
#include <linux/list.h>
#include <linux/module.h>
@@ -25,7 +26,7 @@ EXPORT_SYMBOL_GPL(leds_list_lock);
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
-const char * const led_colors[LED_COLOR_ID_MAX] = {
+static const char * const led_colors[LED_COLOR_ID_MAX] = {
[LED_COLOR_ID_WHITE] = "white",
[LED_COLOR_ID_RED] = "red",
[LED_COLOR_ID_GREEN] = "green",
@@ -42,7 +43,6 @@ const char * const led_colors[LED_COLOR_ID_MAX] = {
[LED_COLOR_ID_CYAN] = "cyan",
[LED_COLOR_ID_LIME] = "lime",
};
-EXPORT_SYMBOL_GPL(led_colors);
static int __led_set_brightness(struct led_classdev *led_cdev, unsigned int value)
{
@@ -122,15 +122,22 @@ static void led_timer_function(struct timer_list *t)
static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev,
unsigned int value)
{
- int ret = 0;
+ int ret;
ret = __led_set_brightness(led_cdev, value);
- if (ret == -ENOTSUPP)
+ if (ret == -ENOTSUPP) {
ret = __led_set_brightness_blocking(led_cdev, value);
- if (ret < 0 &&
- /* LED HW might have been unplugged, therefore don't warn */
- !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&
- (led_cdev->flags & LED_HW_PLUGGABLE)))
+ if (ret == -ENOTSUPP)
+ /* No back-end support to set a fixed brightness value */
+ return;
+ }
+
+ /* LED HW might have been unplugged, therefore don't warn */
+ if (ret == -ENODEV && led_cdev->flags & LED_UNREGISTERING &&
+ led_cdev->flags & LED_HW_PLUGGABLE)
+ return;
+
+ if (ret < 0)
dev_err(led_cdev->dev,
"Setting an LED's brightness failed (%d)\n", ret);
}
@@ -152,8 +159,19 @@ static void set_brightness_delayed(struct work_struct *ws)
* before this work item runs once. To make sure this works properly
* handle LED_SET_BRIGHTNESS_OFF first.
*/
- if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags))
+ if (test_and_clear_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags)) {
set_brightness_delayed_set_brightness(led_cdev, LED_OFF);
+ /*
+ * The consecutives led_set_brightness(LED_OFF),
+ * led_set_brightness(LED_FULL) could have been executed out of
+ * order (LED_FULL first), if the work_flags has been set
+ * between LED_SET_BRIGHTNESS_OFF and LED_SET_BRIGHTNESS of this
+ * work. To avoid ending with the LED turned off, turn the LED
+ * on again.
+ */
+ if (led_cdev->delayed_set_value != LED_OFF)
+ set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
+ }
if (test_and_clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags))
set_brightness_delayed_set_brightness(led_cdev, led_cdev->delayed_set_value);
@@ -227,7 +245,7 @@ void led_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
- del_timer_sync(&led_cdev->blink_timer);
+ timer_delete_sync(&led_cdev->blink_timer);
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
@@ -266,7 +284,7 @@ void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on
led_cdev->delayed_delay_on = delay_on;
led_cdev->delayed_delay_off = delay_off;
set_bit(LED_SET_BLINK, &led_cdev->work_flags);
- schedule_work(&led_cdev->set_brightness_work);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
return;
}
@@ -276,7 +294,7 @@ EXPORT_SYMBOL_GPL(led_blink_set_nosleep);
void led_stop_software_blink(struct led_classdev *led_cdev)
{
- del_timer_sync(&led_cdev->blink_timer);
+ timer_delete_sync(&led_cdev->blink_timer);
led_cdev->blink_delay_on = 0;
led_cdev->blink_delay_off = 0;
clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
@@ -297,7 +315,7 @@ void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness)
*/
if (!brightness) {
set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
- schedule_work(&led_cdev->set_brightness_work);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
} else {
set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
&led_cdev->work_flags);
@@ -324,16 +342,19 @@ void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value)
* change is done immediately afterwards (before the work runs),
* it uses a separate work_flag.
*/
- if (value) {
- led_cdev->delayed_set_value = value;
+ led_cdev->delayed_set_value = value;
+ /* Ensure delayed_set_value is seen before work_flags modification */
+ smp_mb__before_atomic();
+
+ if (value)
set_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
- } else {
+ else {
clear_bit(LED_SET_BRIGHTNESS, &led_cdev->work_flags);
clear_bit(LED_SET_BLINK, &led_cdev->work_flags);
set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags);
}
- schedule_work(&led_cdev->set_brightness_work);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
@@ -362,6 +383,36 @@ int led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value)
}
EXPORT_SYMBOL_GPL(led_set_brightness_sync);
+/*
+ * This is a led-core function because just like led_set_brightness()
+ * it is used in the kernel by e.g. triggers.
+ */
+void led_mc_set_brightness(struct led_classdev *led_cdev,
+ unsigned int *intensity_value, unsigned int num_colors,
+ unsigned int brightness)
+{
+ struct led_classdev_mc *mcled_cdev;
+ unsigned int i;
+
+ if (!(led_cdev->flags & LED_MULTI_COLOR)) {
+ dev_err_once(led_cdev->dev, "error not a multi-color LED\n");
+ return;
+ }
+
+ mcled_cdev = lcdev_to_mccdev(led_cdev);
+ if (num_colors != mcled_cdev->num_colors) {
+ dev_err_once(led_cdev->dev, "error num_colors mismatch %u != %u\n",
+ num_colors, mcled_cdev->num_colors);
+ return;
+ }
+
+ for (i = 0; i < mcled_cdev->num_colors; i++)
+ mcled_cdev->subled_info[i].intensity = intensity_value[i];
+
+ led_set_brightness(led_cdev, brightness);
+}
+EXPORT_SYMBOL_GPL(led_mc_set_brightness);
+
int led_update_brightness(struct led_classdev *led_cdev)
{
int ret;
@@ -534,6 +585,15 @@ int led_compose_name(struct device *dev, struct led_init_data *init_data,
}
EXPORT_SYMBOL_GPL(led_compose_name);
+const char *led_get_color_name(u8 color_id)
+{
+ if (color_id >= ARRAY_SIZE(led_colors))
+ return NULL;
+
+ return led_colors[color_id];
+}
+EXPORT_SYMBOL_GPL(led_get_color_name);
+
enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode)
{
const char *state = NULL;
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 0f5ac30053ad..b2d40f87a5ff 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -34,7 +34,7 @@ trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
}
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr, char *buf,
+ const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
@@ -123,7 +123,7 @@ static int led_trigger_format(char *buf, size_t size,
* copy it.
*/
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *attr, char *buf,
+ const struct bin_attribute *attr, char *buf,
loff_t pos, size_t count)
{
struct device *dev = kobj_to_dev(kobj);
@@ -179,9 +179,9 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
cancel_work_sync(&led_cdev->set_brightness_work);
led_stop_software_blink(led_cdev);
+ device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
- device_remove_groups(led_cdev->dev, led_cdev->trigger->groups);
led_cdev->trigger = NULL;
led_cdev->trigger_data = NULL;
led_cdev->activated = false;
@@ -194,11 +194,24 @@ int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
spin_unlock(&trig->leddev_list_lock);
led_cdev->trigger = trig;
+ /*
+ * Some activate() calls use led_trigger_event() to initialize
+ * the brightness of the LED for which the trigger is being set.
+ * Ensure the led_cdev is visible on trig->led_cdevs for this.
+ */
+ synchronize_rcu();
+
+ /*
+ * If "set brightness to 0" is pending in workqueue,
+ * we don't want that to be reordered after ->activate()
+ */
+ flush_work(&led_cdev->set_brightness_work);
+
+ ret = 0;
if (trig->activate)
ret = trig->activate(led_cdev);
else
- ret = 0;
-
+ led_set_brightness(led_cdev, trig->brightness);
if (ret)
goto err_activate;
@@ -387,6 +400,8 @@ void led_trigger_event(struct led_trigger *trig,
if (!trig)
return;
+ trig->brightness = brightness;
+
rcu_read_lock();
list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list)
led_set_brightness(led_cdev, brightness);
@@ -394,6 +409,26 @@ void led_trigger_event(struct led_trigger *trig,
}
EXPORT_SYMBOL_GPL(led_trigger_event);
+void led_mc_trigger_event(struct led_trigger *trig,
+ unsigned int *intensity_value, unsigned int num_colors,
+ enum led_brightness brightness)
+{
+ struct led_classdev *led_cdev;
+
+ if (!trig)
+ return;
+
+ rcu_read_lock();
+ list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) {
+ if (!(led_cdev->flags & LED_MULTI_COLOR))
+ continue;
+
+ led_mc_set_brightness(led_cdev, intensity_value, num_colors, brightness);
+ }
+ rcu_read_unlock();
+}
+EXPORT_SYMBOL_GPL(led_mc_trigger_event);
+
static void led_trigger_blink_setup(struct led_trigger *trig,
unsigned long delay_on,
unsigned long delay_off,
diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c
index 033ab5fed38a..ef5c6c4667ab 100644
--- a/drivers/leds/leds-88pm860x.c
+++ b/drivers/leds/leds-88pm860x.c
@@ -115,7 +115,7 @@ static int pm860x_led_set(struct led_classdev *cdev,
static int pm860x_led_dt_init(struct platform_device *pdev,
struct pm860x_led *data)
{
- struct device_node *nproot, *np;
+ struct device_node *nproot;
int iset = 0;
if (!dev_of_node(pdev->dev.parent))
@@ -125,12 +125,11 @@ static int pm860x_led_dt_init(struct platform_device *pdev,
dev_err(&pdev->dev, "failed to find leds node\n");
return -ENODEV;
}
- for_each_available_child_of_node(nproot, np) {
+ for_each_available_child_of_node_scoped(nproot, np) {
if (of_node_name_eq(np, data->name)) {
of_property_read_u32(np, "marvell,88pm860x-iset",
&iset);
data->iset = PM8606_LED_CURRENT(iset);
- of_node_put(np);
break;
}
}
@@ -227,7 +226,7 @@ static struct platform_driver pm860x_led_driver = {
.name = "88pm860x-led",
},
.probe = pm860x_led_probe,
- .remove_new = pm860x_led_remove,
+ .remove = pm860x_led_remove,
};
module_platform_driver(pm860x_led_driver);
diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c
index d89a4dca50ae..13e5bc80e56e 100644
--- a/drivers/leds/leds-adp5520.c
+++ b/drivers/leds/leds-adp5520.c
@@ -184,7 +184,7 @@ static struct platform_driver adp5520_led_driver = {
.name = "adp5520-led",
},
.probe = adp5520_led_probe,
- .remove_new = adp5520_led_remove,
+ .remove = adp5520_led_remove,
};
module_platform_driver(adp5520_led_driver);
diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c
index 0216afed3b6e..a42cc4bc6917 100644
--- a/drivers/leds/leds-an30259a.c
+++ b/drivers/leds/leds-an30259a.c
@@ -283,7 +283,10 @@ static int an30259a_probe(struct i2c_client *client)
if (err < 0)
return err;
- mutex_init(&chip->mutex);
+ err = devm_mutex_init(&client->dev, &chip->mutex);
+ if (err)
+ return err;
+
chip->client = client;
i2c_set_clientdata(client, chip);
@@ -317,17 +320,9 @@ static int an30259a_probe(struct i2c_client *client)
return 0;
exit:
- mutex_destroy(&chip->mutex);
return err;
}
-static void an30259a_remove(struct i2c_client *client)
-{
- struct an30259a *chip = i2c_get_clientdata(client);
-
- mutex_destroy(&chip->mutex);
-}
-
static const struct of_device_id an30259a_match_table[] = {
{ .compatible = "panasonic,an30259a", },
{ /* sentinel */ },
@@ -336,8 +331,8 @@ static const struct of_device_id an30259a_match_table[] = {
MODULE_DEVICE_TABLE(of, an30259a_match_table);
static const struct i2c_device_id an30259a_id[] = {
- { "an30259a", 0 },
- { /* sentinel */ },
+ { "an30259a" },
+ { /* sentinel */ }
};
MODULE_DEVICE_TABLE(i2c, an30259a_id);
@@ -347,7 +342,6 @@ static struct i2c_driver an30259a_driver = {
.of_match_table = an30259a_match_table,
},
.probe = an30259a_probe,
- .remove = an30259a_remove,
.id_table = an30259a_id,
};
diff --git a/drivers/leds/leds-apu.c b/drivers/leds/leds-apu.c
index c409b80c236d..1c116aaa9b6e 100644
--- a/drivers/leds/leds-apu.c
+++ b/drivers/leds/leds-apu.c
@@ -181,8 +181,7 @@ static int __init apu_led_init(void)
struct platform_device *pdev;
int err;
- if (!(dmi_match(DMI_SYS_VENDOR, "PC Engines") &&
- (dmi_match(DMI_PRODUCT_NAME, "APU") || dmi_match(DMI_PRODUCT_NAME, "apu1")))) {
+ if (!dmi_check_system(apu_led_dmi_table)) {
pr_err("No PC Engines APUv1 board detected. For APUv2,3 support, enable CONFIG_PCENGINES_APU2\n");
return -ENODEV;
}
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c
index 6c8c9f2c19e3..fe223d363a5d 100644
--- a/drivers/leds/leds-aw200xx.c
+++ b/drivers/leds/leds-aw200xx.c
@@ -379,7 +379,7 @@ static void aw200xx_enable(const struct aw200xx *const chip)
static void aw200xx_disable(const struct aw200xx *const chip)
{
- return gpiod_set_value_cansleep(chip->hwen, 0);
+ gpiod_set_value_cansleep(chip->hwen, 0);
}
static int aw200xx_probe_get_display_rows(struct device *dev,
@@ -409,7 +409,6 @@ static int aw200xx_probe_get_display_rows(struct device *dev,
static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
{
- struct fwnode_handle *child;
u32 current_min, current_max, min_uA;
int ret;
int i;
@@ -424,7 +423,7 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
min_uA = UINT_MAX;
i = 0;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
struct aw200xx_led *led;
u32 source, imax;
@@ -468,10 +467,8 @@ static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
ret = devm_led_classdev_register_ext(dev, &led->cdev,
&init_data);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
break;
- }
i++;
}
@@ -530,6 +527,16 @@ static const struct regmap_config aw200xx_regmap_config = {
.disable_locking = true,
};
+static void aw200xx_chip_reset_action(void *data)
+{
+ aw200xx_chip_reset(data);
+}
+
+static void aw200xx_disable_action(void *data)
+{
+ aw200xx_disable(data);
+}
+
static int aw200xx_probe(struct i2c_client *client)
{
const struct aw200xx_chipdef *cdef;
@@ -568,11 +575,17 @@ static int aw200xx_probe(struct i2c_client *client)
aw200xx_enable(chip);
+ ret = devm_add_action(&client->dev, aw200xx_disable_action, chip);
+ if (ret)
+ return ret;
+
ret = aw200xx_chip_check(chip);
if (ret)
return ret;
- mutex_init(&chip->mutex);
+ ret = devm_mutex_init(&client->dev, &chip->mutex);
+ if (ret)
+ return ret;
/* Need a lock now since after call aw200xx_probe_fw, sysfs nodes created */
mutex_lock(&chip->mutex);
@@ -581,6 +594,10 @@ static int aw200xx_probe(struct i2c_client *client)
if (ret)
goto out_unlock;
+ ret = devm_add_action(&client->dev, aw200xx_chip_reset_action, chip);
+ if (ret)
+ goto out_unlock;
+
ret = aw200xx_probe_fw(&client->dev, chip);
if (ret)
goto out_unlock;
@@ -595,15 +612,6 @@ out_unlock:
return ret;
}
-static void aw200xx_remove(struct i2c_client *client)
-{
- struct aw200xx *chip = i2c_get_clientdata(client);
-
- aw200xx_chip_reset(chip);
- aw200xx_disable(chip);
- mutex_destroy(&chip->mutex);
-}
-
static const struct aw200xx_chipdef aw20036_cdef = {
.channels = 36,
.display_size_rows_max = 3,
@@ -652,7 +660,6 @@ static struct i2c_driver aw200xx_driver = {
.of_match_table = aw200xx_match_table,
},
.probe = aw200xx_probe,
- .remove = aw200xx_remove,
.id_table = aw200xx_id,
};
module_i2c_driver(aw200xx_driver);
diff --git a/drivers/leds/leds-aw2013.c b/drivers/leds/leds-aw2013.c
index 17235a5e576a..216755d6010f 100644
--- a/drivers/leds/leds-aw2013.c
+++ b/drivers/leds/leds-aw2013.c
@@ -263,7 +263,7 @@ out:
static int aw2013_probe_dt(struct aw2013 *chip)
{
- struct device_node *np = dev_of_node(&chip->client->dev), *child;
+ struct device_node *np = dev_of_node(&chip->client->dev);
int count, ret = 0, i = 0;
struct aw2013_led *led;
@@ -273,7 +273,7 @@ static int aw2013_probe_dt(struct aw2013 *chip)
regmap_write(chip->regmap, AW2013_RSTR, AW2013_RSTR_RESET);
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
struct led_init_data init_data = {};
u32 source;
u32 imax;
@@ -304,10 +304,8 @@ static int aw2013_probe_dt(struct aw2013 *chip)
ret = devm_led_classdev_register_ext(&chip->client->dev,
&led->cdev, &init_data);
- if (ret < 0) {
- of_node_put(child);
+ if (ret < 0)
return ret;
- }
i++;
}
@@ -320,6 +318,11 @@ static int aw2013_probe_dt(struct aw2013 *chip)
return 0;
}
+static void aw2013_chip_disable_action(void *data)
+{
+ aw2013_chip_disable(data);
+}
+
static const struct regmap_config aw2013_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
@@ -336,7 +339,10 @@ static int aw2013_probe(struct i2c_client *client)
if (!chip)
return -ENOMEM;
- mutex_init(&chip->mutex);
+ ret = devm_mutex_init(&client->dev, &chip->mutex);
+ if (ret)
+ return ret;
+
mutex_lock(&chip->mutex);
chip->client = client;
@@ -384,6 +390,10 @@ static int aw2013_probe(struct i2c_client *client)
goto error_reg;
}
+ ret = devm_add_action(&client->dev, aw2013_chip_disable_action, chip);
+ if (ret)
+ goto error_reg;
+
ret = aw2013_probe_dt(chip);
if (ret < 0)
goto error_reg;
@@ -406,19 +416,9 @@ error_reg:
error:
mutex_unlock(&chip->mutex);
- mutex_destroy(&chip->mutex);
return ret;
}
-static void aw2013_remove(struct i2c_client *client)
-{
- struct aw2013 *chip = i2c_get_clientdata(client);
-
- aw2013_chip_disable(chip);
-
- mutex_destroy(&chip->mutex);
-}
-
static const struct of_device_id aw2013_match_table[] = {
{ .compatible = "awinic,aw2013", },
{ /* sentinel */ },
@@ -432,7 +432,6 @@ static struct i2c_driver aw2013_driver = {
.of_match_table = aw2013_match_table,
},
.probe = aw2013_probe,
- .remove = aw2013_remove,
};
module_i2c_driver(aw2013_driver);
diff --git a/drivers/leds/leds-bcm6328.c b/drivers/leds/leds-bcm6328.c
index 246f1296ab09..592bbf4b7e35 100644
--- a/drivers/leds/leds-bcm6328.c
+++ b/drivers/leds/leds-bcm6328.c
@@ -113,7 +113,7 @@ static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
unsigned long val, shift;
shift = bcm6328_pin2shift(led->pin);
- if (shift / 16)
+ if (shift >= 16)
mode = led->mem + BCM6328_REG_MODE_HI;
else
mode = led->mem + BCM6328_REG_MODE_LO;
@@ -357,7 +357,7 @@ static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
break;
case LEDS_DEFSTATE_KEEP:
shift = bcm6328_pin2shift(led->pin);
- if (shift / 16)
+ if (shift >= 16)
mode = mem + BCM6328_REG_MODE_HI;
else
mode = mem + BCM6328_REG_MODE_LO;
@@ -392,7 +392,6 @@ static int bcm6328_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev_of_node(&pdev->dev);
- struct device_node *child;
void __iomem *mem;
spinlock_t *lock; /* memory lock */
unsigned long val, *blink_leds, *blink_delay;
@@ -435,7 +434,7 @@ static int bcm6328_leds_probe(struct platform_device *pdev)
val |= BCM6328_SERIAL_LED_SHIFT_DIR;
bcm6328_led_write(mem + BCM6328_REG_INIT, val);
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
int rc;
u32 reg;
@@ -454,10 +453,8 @@ static int bcm6328_leds_probe(struct platform_device *pdev)
rc = bcm6328_led(dev, child, reg, mem, lock,
blink_leds, blink_delay);
- if (rc < 0) {
- of_node_put(child);
+ if (rc < 0)
return rc;
- }
}
return 0;
diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c
index 86e51d44a5a7..51fcff2a64fd 100644
--- a/drivers/leds/leds-bcm6358.c
+++ b/drivers/leds/leds-bcm6358.c
@@ -147,7 +147,6 @@ static int bcm6358_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev_of_node(&pdev->dev);
- struct device_node *child;
void __iomem *mem;
spinlock_t *lock; /* memory lock */
unsigned long val;
@@ -184,7 +183,7 @@ static int bcm6358_leds_probe(struct platform_device *pdev)
}
bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
int rc;
u32 reg;
@@ -198,10 +197,8 @@ static int bcm6358_leds_probe(struct platform_device *pdev)
}
rc = bcm6358_led(dev, child, reg, mem, lock);
- if (rc < 0) {
- of_node_put(child);
+ if (rc < 0)
return rc;
- }
}
return 0;
diff --git a/drivers/leds/leds-bd2606mvv.c b/drivers/leds/leds-bd2606mvv.c
index 3fda712d2f80..c1181a35d0f7 100644
--- a/drivers/leds/leds-bd2606mvv.c
+++ b/drivers/leds/leds-bd2606mvv.c
@@ -69,16 +69,14 @@ static const struct regmap_config bd2606mvv_regmap = {
static int bd2606mvv_probe(struct i2c_client *client)
{
- struct fwnode_handle *np, *child;
struct device *dev = &client->dev;
struct bd2606mvv_priv *priv;
struct fwnode_handle *led_fwnodes[BD2606_MAX_LEDS] = { 0 };
int active_pairs[BD2606_MAX_LEDS / 2] = { 0 };
int err, reg;
- int i;
+ int i, j;
- np = dev_fwnode(dev);
- if (!np)
+ if (!dev_fwnode(dev))
return -ENODEV;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
@@ -94,20 +92,18 @@ static int bd2606mvv_probe(struct i2c_client *client)
i2c_set_clientdata(client, priv);
- fwnode_for_each_available_child_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct bd2606mvv_led *led;
err = fwnode_property_read_u32(child, "reg", &reg);
- if (err) {
- fwnode_handle_put(child);
+ if (err)
return err;
- }
- if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg]) {
- fwnode_handle_put(child);
+
+ if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg])
return -EINVAL;
- }
+
led = &priv->leds[reg];
- led_fwnodes[reg] = child;
+ led_fwnodes[reg] = fwnode_handle_get(child);
active_pairs[reg / 2]++;
led->priv = priv;
led->led_no = reg;
@@ -130,7 +126,8 @@ static int bd2606mvv_probe(struct i2c_client *client)
&priv->leds[i].ldev,
&init_data);
if (err < 0) {
- fwnode_handle_put(child);
+ for (j = i; j < BD2606_MAX_LEDS; j++)
+ fwnode_handle_put(led_fwnodes[j]);
return dev_err_probe(dev, err,
"couldn't register LED %s\n",
priv->leds[i].ldev.name);
diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c
index 0792ea126cea..2a08c5f27608 100644
--- a/drivers/leds/leds-bd2802.c
+++ b/drivers/leds/leds-bd2802.c
@@ -776,7 +776,7 @@ static int bd2802_resume(struct device *dev)
static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume);
static const struct i2c_device_id bd2802_id[] = {
- { "BD2802", 0 },
+ { "BD2802" },
{ }
};
MODULE_DEVICE_TABLE(i2c, bd2802_id);
diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c
index 2782da1a1930..577497b9d426 100644
--- a/drivers/leds/leds-blinkm.c
+++ b/drivers/leds/leds-blinkm.c
@@ -2,6 +2,7 @@
/*
* leds-blinkm.c
* (c) Jan-Simon Möller (dl9pf@gmx.de)
+ * (c) Joseph Strauss (jstrauss@mailbox.org)
*/
#include <linux/module.h>
@@ -15,6 +16,10 @@
#include <linux/pm_runtime.h>
#include <linux/leds.h>
#include <linux/delay.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/kconfig.h>
+
+#define NUM_LEDS 3
/* Addresses to scan - BlinkM is on 0x09 by default*/
static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };
@@ -22,19 +27,25 @@ static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END };
static int blinkm_transfer_hw(struct i2c_client *client, int cmd);
static int blinkm_test_run(struct i2c_client *client);
+/* Contains structs for both the color-separated sysfs classes, and the new multicolor class */
struct blinkm_led {
struct i2c_client *i2c_client;
- struct led_classdev led_cdev;
+ union {
+ /* used when multicolor support is disabled */
+ struct led_classdev led_cdev;
+ struct led_classdev_mc mcled_cdev;
+ } cdev;
int id;
};
-#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev)
+#define led_cdev_to_blmled(c) container_of(c, struct blinkm_led, cdev.led_cdev)
+#define mcled_cdev_to_led(c) container_of(c, struct blinkm_led, cdev.mcled_cdev)
struct blinkm_data {
struct i2c_client *i2c_client;
struct mutex update_lock;
/* used for led class interface */
- struct blinkm_led blinkm_leds[3];
+ struct blinkm_led blinkm_leds[NUM_LEDS];
/* used for "blinkm" sysfs interface */
u8 red; /* color red */
u8 green; /* color green */
@@ -419,11 +430,29 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd)
return 0;
}
+static int blinkm_set_mc_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
+ struct blinkm_led *led = mcled_cdev_to_led(mcled_cdev);
+ struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);
+
+ led_mc_calc_color_components(mcled_cdev, value);
+
+ data->next_red = (u8) mcled_cdev->subled_info[RED].brightness;
+ data->next_green = (u8) mcled_cdev->subled_info[GREEN].brightness;
+ data->next_blue = (u8) mcled_cdev->subled_info[BLUE].brightness;
+
+ blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);
+
+ return 0;
+}
+
static int blinkm_led_common_set(struct led_classdev *led_cdev,
enum led_brightness value, int color)
{
/* led_brightness is 0, 127 or 255 - we just use it here as-is */
- struct blinkm_led *led = cdev_to_blmled(led_cdev);
+ struct blinkm_led *led = led_cdev_to_blmled(led_cdev);
struct blinkm_data *data = i2c_get_clientdata(led->i2c_client);
switch (color) {
@@ -565,117 +594,175 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
return 0;
}
-static int blinkm_probe(struct i2c_client *client)
+static int register_separate_colors(struct i2c_client *client, struct blinkm_data *data)
{
- struct blinkm_data *data;
- struct blinkm_led *led[3];
- int err, i;
+ /* 3 separate classes for red, green, and blue respectively */
+ struct blinkm_led *leds[NUM_LEDS];
+ int err;
char blinkm_led_name[28];
-
- data = devm_kzalloc(&client->dev,
- sizeof(struct blinkm_data), GFP_KERNEL);
- if (!data) {
- err = -ENOMEM;
- goto exit;
- }
-
- data->i2c_addr = 0x08;
- /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */
- data->fw_ver = 0xfe;
- /* firmware version - use fake until we read real value
- * (currently broken - BlinkM confused!) */
- data->script_id = 0x01;
- data->i2c_client = client;
-
- i2c_set_clientdata(client, data);
- mutex_init(&data->update_lock);
-
- /* Register sysfs hooks */
- err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
- if (err < 0) {
- dev_err(&client->dev, "couldn't register sysfs group\n");
- goto exit;
- }
-
- for (i = 0; i < 3; i++) {
+ /* Register red, green, and blue sysfs classes */
+ for (int i = 0; i < NUM_LEDS; i++) {
/* RED = 0, GREEN = 1, BLUE = 2 */
- led[i] = &data->blinkm_leds[i];
- led[i]->i2c_client = client;
- led[i]->id = i;
- led[i]->led_cdev.max_brightness = 255;
- led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME;
+ leds[i] = &data->blinkm_leds[i];
+ leds[i]->i2c_client = client;
+ leds[i]->id = i;
+ leds[i]->cdev.led_cdev.max_brightness = 255;
+ leds[i]->cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;
switch (i) {
case RED:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-red",
client->adapter->nr,
client->addr);
- led[i]->led_cdev.name = blinkm_led_name;
- led[i]->led_cdev.brightness_set_blocking =
+ leds[i]->cdev.led_cdev.name = blinkm_led_name;
+ leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_red_set;
err = led_classdev_register(&client->dev,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failred;
}
break;
case GREEN:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-green",
client->adapter->nr,
client->addr);
- led[i]->led_cdev.name = blinkm_led_name;
- led[i]->led_cdev.brightness_set_blocking =
+ leds[i]->cdev.led_cdev.name = blinkm_led_name;
+ leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_green_set;
err = led_classdev_register(&client->dev,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failgreen;
}
break;
case BLUE:
- snprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
"blinkm-%d-%d-blue",
client->adapter->nr,
client->addr);
- led[i]->led_cdev.name = blinkm_led_name;
- led[i]->led_cdev.brightness_set_blocking =
+ leds[i]->cdev.led_cdev.name = blinkm_led_name;
+ leds[i]->cdev.led_cdev.brightness_set_blocking =
blinkm_led_blue_set;
err = led_classdev_register(&client->dev,
- &led[i]->led_cdev);
+ &leds[i]->cdev.led_cdev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
- led[i]->led_cdev.name);
+ leds[i]->cdev.led_cdev.name);
goto failblue;
}
break;
+ default:
+ break;
} /* end switch */
} /* end for */
-
- /* Initialize the blinkm */
- blinkm_init_hw(client);
-
return 0;
failblue:
- led_classdev_unregister(&led[GREEN]->led_cdev);
-
+ led_classdev_unregister(&leds[GREEN]->cdev.led_cdev);
failgreen:
- led_classdev_unregister(&led[RED]->led_cdev);
-
+ led_classdev_unregister(&leds[RED]->cdev.led_cdev);
failred:
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
-exit:
+
return err;
}
+static int register_multicolor(struct i2c_client *client, struct blinkm_data *data)
+{
+ struct blinkm_led *mc_led;
+ struct mc_subled *mc_led_info;
+ char blinkm_led_name[28];
+ int err;
+
+ /* Register multicolor sysfs class */
+ /* The first element of leds is used for multicolor facilities */
+ mc_led = &data->blinkm_leds[RED];
+ mc_led->i2c_client = client;
+
+ mc_led_info = devm_kcalloc(&client->dev, NUM_LEDS, sizeof(*mc_led_info),
+ GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ mc_led_info[RED].color_index = LED_COLOR_ID_RED;
+ mc_led_info[GREEN].color_index = LED_COLOR_ID_GREEN;
+ mc_led_info[BLUE].color_index = LED_COLOR_ID_BLUE;
+
+ mc_led->cdev.mcled_cdev.subled_info = mc_led_info;
+ mc_led->cdev.mcled_cdev.num_colors = NUM_LEDS;
+ mc_led->cdev.mcled_cdev.led_cdev.brightness = 255;
+ mc_led->cdev.mcled_cdev.led_cdev.max_brightness = 255;
+ mc_led->cdev.mcled_cdev.led_cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ scnprintf(blinkm_led_name, sizeof(blinkm_led_name),
+ "blinkm-%d-%d:rgb:indicator",
+ client->adapter->nr,
+ client->addr);
+ mc_led->cdev.mcled_cdev.led_cdev.name = blinkm_led_name;
+ mc_led->cdev.mcled_cdev.led_cdev.brightness_set_blocking = blinkm_set_mc_brightness;
+
+ err = led_classdev_multicolor_register(&client->dev, &mc_led->cdev.mcled_cdev);
+ if (err < 0) {
+ dev_err(&client->dev, "couldn't register LED %s\n",
+ mc_led->cdev.led_cdev.name);
+ sysfs_remove_group(&client->dev.kobj, &blinkm_group);
+ }
+ return 0;
+}
+
+static int blinkm_probe(struct i2c_client *client)
+{
+ struct blinkm_data *data;
+ int err;
+
+ data = devm_kzalloc(&client->dev,
+ sizeof(struct blinkm_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->i2c_addr = 0x08;
+ /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */
+ data->fw_ver = 0xfe;
+ /* firmware version - use fake until we read real value
+ * (currently broken - BlinkM confused!)
+ */
+ data->script_id = 0x01;
+ data->i2c_client = client;
+
+ i2c_set_clientdata(client, data);
+ mutex_init(&data->update_lock);
+
+ /* Register sysfs hooks */
+ err = sysfs_create_group(&client->dev.kobj, &blinkm_group);
+ if (err < 0) {
+ dev_err(&client->dev, "couldn't register sysfs group\n");
+ return err;
+ }
+
+ if (!IS_ENABLED(CONFIG_LEDS_BLINKM_MULTICOLOR)) {
+ err = register_separate_colors(client, data);
+ if (err < 0)
+ return err;
+ } else {
+ err = register_multicolor(client, data);
+ if (err < 0)
+ return err;
+ }
+
+ blinkm_init_hw(client);
+
+ return 0;
+}
+
static void blinkm_remove(struct i2c_client *client)
{
struct blinkm_data *data = i2c_get_clientdata(client);
@@ -683,8 +770,8 @@ static void blinkm_remove(struct i2c_client *client)
int i;
/* make sure no workqueue entries are pending */
- for (i = 0; i < 3; i++)
- led_classdev_unregister(&data->blinkm_leds[i].led_cdev);
+ for (i = 0; i < NUM_LEDS; i++)
+ led_classdev_unregister(&data->blinkm_leds[i].cdev.led_cdev);
/* reset rgb */
data->next_red = 0x00;
@@ -718,7 +805,7 @@ static void blinkm_remove(struct i2c_client *client)
}
static const struct i2c_device_id blinkm_id[] = {
- {"blinkm", 0},
+ { "blinkm" },
{}
};
@@ -740,6 +827,7 @@ static struct i2c_driver blinkm_driver = {
module_i2c_driver(blinkm_driver);
MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>");
+MODULE_AUTHOR("Joseph Strauss <jstrauss@mailbox.org>");
MODULE_DESCRIPTION("BlinkM RGB LED driver");
MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-cht-wcove.c b/drivers/leds/leds-cht-wcove.c
index b4998402b8c6..9a609dd5acdc 100644
--- a/drivers/leds/leds-cht-wcove.c
+++ b/drivers/leds/leds-cht-wcove.c
@@ -394,7 +394,7 @@ static int cht_wc_leds_probe(struct platform_device *pdev)
led->cdev.pattern_clear = cht_wc_leds_pattern_clear;
led->cdev.max_brightness = 255;
- ret = led_classdev_register(&pdev->dev, &led->cdev);
+ ret = devm_led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0)
return ret;
}
@@ -406,10 +406,6 @@ static int cht_wc_leds_probe(struct platform_device *pdev)
static void cht_wc_leds_remove(struct platform_device *pdev)
{
struct cht_wc_leds *leds = platform_get_drvdata(pdev);
- int i;
-
- for (i = 0; i < CHT_WC_LED_COUNT; i++)
- led_classdev_unregister(&leds->leds[i].cdev);
/* Restore LED1 regs if hw-control was active else leave LED1 off */
if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
@@ -461,7 +457,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds
static struct platform_driver cht_wc_leds_driver = {
.probe = cht_wc_leds_probe,
- .remove_new = cht_wc_leds_remove,
+ .remove = cht_wc_leds_remove,
.shutdown = cht_wc_leds_disable,
.driver = {
.name = "cht_wcove_leds",
diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c
index 82da0fe688ad..f00b16ac1586 100644
--- a/drivers/leds/leds-clevo-mail.c
+++ b/drivers/leds/leds-clevo-mail.c
@@ -165,7 +165,7 @@ static void clevo_mail_led_remove(struct platform_device *pdev)
}
static struct platform_driver clevo_mail_led_driver = {
- .remove_new = clevo_mail_led_remove,
+ .remove = clevo_mail_led_remove,
.driver = {
.name = KBUILD_MODNAME,
},
diff --git a/drivers/leds/leds-cr0014114.c b/drivers/leds/leds-cr0014114.c
index c9914fc51f20..7e51c374edd4 100644
--- a/drivers/leds/leds-cr0014114.c
+++ b/drivers/leds/leds-cr0014114.c
@@ -181,11 +181,10 @@ static int cr0014114_probe_dt(struct cr0014114 *priv)
{
size_t i = 0;
struct cr0014114_led *led;
- struct fwnode_handle *child;
struct led_init_data init_data = {};
int ret;
- device_for_each_child_node(priv->dev, child) {
+ device_for_each_child_node_scoped(priv->dev, child) {
led = &priv->leds[i];
led->priv = priv;
@@ -201,7 +200,6 @@ static int cr0014114_probe_dt(struct cr0014114 *priv)
if (ret) {
dev_err(priv->dev,
"failed to register LED device, err %d", ret);
- fwnode_handle_put(child);
return ret;
}
diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
new file mode 100644
index 000000000000..275522b81ea5
--- /dev/null
+++ b/drivers/leds/leds-cros_ec.c
@@ -0,0 +1,277 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ChromeOS EC LED Driver
+ *
+ * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
+ */
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+
+static const char * const cros_ec_led_functions[] = {
+ [EC_LED_ID_BATTERY_LED] = LED_FUNCTION_CHARGING,
+ [EC_LED_ID_POWER_LED] = LED_FUNCTION_POWER,
+ [EC_LED_ID_ADAPTER_LED] = "adapter",
+ [EC_LED_ID_LEFT_LED] = "left",
+ [EC_LED_ID_RIGHT_LED] = "right",
+ [EC_LED_ID_RECOVERY_HW_REINIT_LED] = "recovery-hw-reinit",
+ [EC_LED_ID_SYSRQ_DEBUG_LED] = "sysrq-debug",
+};
+
+static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT);
+
+static const int cros_ec_led_to_linux_id[] = {
+ [EC_LED_COLOR_RED] = LED_COLOR_ID_RED,
+ [EC_LED_COLOR_GREEN] = LED_COLOR_ID_GREEN,
+ [EC_LED_COLOR_BLUE] = LED_COLOR_ID_BLUE,
+ [EC_LED_COLOR_YELLOW] = LED_COLOR_ID_YELLOW,
+ [EC_LED_COLOR_WHITE] = LED_COLOR_ID_WHITE,
+ [EC_LED_COLOR_AMBER] = LED_COLOR_ID_AMBER,
+};
+
+static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT);
+
+static const int cros_ec_linux_to_ec_id[] = {
+ [LED_COLOR_ID_RED] = EC_LED_COLOR_RED,
+ [LED_COLOR_ID_GREEN] = EC_LED_COLOR_GREEN,
+ [LED_COLOR_ID_BLUE] = EC_LED_COLOR_BLUE,
+ [LED_COLOR_ID_YELLOW] = EC_LED_COLOR_YELLOW,
+ [LED_COLOR_ID_WHITE] = EC_LED_COLOR_WHITE,
+ [LED_COLOR_ID_AMBER] = EC_LED_COLOR_AMBER,
+};
+
+struct cros_ec_led_priv {
+ struct led_classdev_mc led_mc_cdev;
+ struct cros_ec_device *cros_ec;
+ enum ec_led_id led_id;
+};
+
+static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_classdev *led_cdev)
+{
+ return container_of(lcdev_to_mccdev(led_cdev), struct cros_ec_led_priv, led_mc_cdev);
+}
+
+union cros_ec_led_cmd_data {
+ struct ec_params_led_control req;
+ struct ec_response_led_control resp;
+} __packed;
+
+static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec,
+ union cros_ec_led_cmd_data *arg)
+{
+ int ret;
+ struct {
+ struct cros_ec_command msg;
+ union cros_ec_led_cmd_data data;
+ } __packed buf = {
+ .msg = {
+ .version = 1,
+ .command = EC_CMD_LED_CONTROL,
+ .insize = sizeof(arg->resp),
+ .outsize = sizeof(arg->req),
+ },
+ .data.req = arg->req
+ };
+
+ ret = cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
+ if (ret < 0)
+ return ret;
+
+ arg->resp = buf.data.resp;
+
+ return 0;
+}
+
+static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev)
+{
+ struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev);
+ union cros_ec_led_cmd_data arg = {};
+
+ arg.req.led_id = priv->led_id;
+ arg.req.flags = EC_LED_FLAGS_AUTO;
+
+ return cros_ec_led_send_cmd(priv->cros_ec, &arg);
+}
+
+static struct led_hw_trigger_type cros_ec_led_trigger_type;
+
+static struct led_trigger cros_ec_led_trigger = {
+ .name = "chromeos-auto",
+ .trigger_type = &cros_ec_led_trigger_type,
+ .activate = cros_ec_led_trigger_activate,
+};
+
+static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev);
+ union cros_ec_led_cmd_data arg = {};
+ enum ec_led_colors led_color;
+ struct mc_subled *subled;
+ size_t i;
+
+ led_mc_calc_color_components(&priv->led_mc_cdev, brightness);
+
+ arg.req.led_id = priv->led_id;
+
+ for (i = 0; i < priv->led_mc_cdev.num_colors; i++) {
+ subled = &priv->led_mc_cdev.subled_info[i];
+ led_color = cros_ec_linux_to_ec_id[subled->color_index];
+ arg.req.brightness[led_color] = subled->brightness;
+ }
+
+ return cros_ec_led_send_cmd(priv->cros_ec, &arg);
+}
+
+static int cros_ec_led_count_subleds(struct device *dev,
+ struct ec_response_led_control *resp,
+ unsigned int *max_brightness)
+{
+ unsigned int range, common_range = 0;
+ int num_subleds = 0;
+ size_t i;
+
+ for (i = 0; i < EC_LED_COLOR_COUNT; i++) {
+ range = resp->brightness_range[i];
+
+ if (!range)
+ continue;
+
+ num_subleds++;
+
+ if (!common_range)
+ common_range = range;
+
+ if (common_range != range) {
+ /* The multicolor LED API expects a uniform max_brightness */
+ dev_err(dev, "Inconsistent LED brightness values\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!num_subleds)
+ return -EINVAL;
+
+ *max_brightness = common_range;
+ return num_subleds;
+}
+
+static const char *cros_ec_led_get_color_name(struct led_classdev_mc *led_mc_cdev)
+{
+ int color;
+
+ if (led_mc_cdev->num_colors == 1)
+ color = led_mc_cdev->subled_info[0].color_index;
+ else
+ color = LED_COLOR_ID_MULTI;
+
+ return led_get_color_name(color);
+}
+
+static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros_ec,
+ enum ec_led_id id)
+{
+ union cros_ec_led_cmd_data arg = {};
+ struct cros_ec_led_priv *priv;
+ struct led_classdev *led_cdev;
+ struct mc_subled *subleds;
+ int i, ret, num_subleds;
+ size_t subled;
+
+ arg.req.led_id = id;
+ arg.req.flags = EC_LED_FLAGS_QUERY;
+ ret = cros_ec_led_send_cmd(cros_ec, &arg);
+ if (ret == -EINVAL)
+ return 0; /* Unknown LED, skip */
+ if (ret == -EOPNOTSUPP)
+ return -ENODEV;
+ if (ret < 0)
+ return ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ num_subleds = cros_ec_led_count_subleds(dev, &arg.resp,
+ &priv->led_mc_cdev.led_cdev.max_brightness);
+ if (num_subleds < 0)
+ return num_subleds;
+
+ priv->cros_ec = cros_ec;
+ priv->led_id = id;
+
+ subleds = devm_kcalloc(dev, num_subleds, sizeof(*subleds), GFP_KERNEL);
+ if (!subleds)
+ return -ENOMEM;
+
+ subled = 0;
+ for (i = 0; i < EC_LED_COLOR_COUNT; i++) {
+ if (!arg.resp.brightness_range[i])
+ continue;
+
+ subleds[subled].color_index = cros_ec_led_to_linux_id[i];
+ if (subled == 0)
+ subleds[subled].intensity = 100;
+ subled++;
+ }
+
+ priv->led_mc_cdev.subled_info = subleds;
+ priv->led_mc_cdev.num_colors = num_subleds;
+
+ led_cdev = &priv->led_mc_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = cros_ec_led_brightness_set_blocking;
+ led_cdev->trigger_type = &cros_ec_led_trigger_type;
+ led_cdev->default_trigger = cros_ec_led_trigger.name;
+ led_cdev->hw_control_trigger = cros_ec_led_trigger.name;
+
+ led_cdev->name = devm_kasprintf(dev, GFP_KERNEL, "chromeos:%s:%s",
+ cros_ec_led_get_color_name(&priv->led_mc_cdev),
+ cros_ec_led_functions[id]);
+ if (!led_cdev->name)
+ return -ENOMEM;
+
+ return devm_led_classdev_multicolor_register(dev, &priv->led_mc_cdev);
+}
+
+static int cros_ec_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
+ struct cros_ec_device *cros_ec = ec_dev->ec_dev;
+ int i, ret = 0;
+
+ ret = devm_led_trigger_register(dev, &cros_ec_led_trigger);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < EC_LED_ID_COUNT; i++) {
+ ret = cros_ec_led_probe_one(dev, cros_ec, i);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static const struct platform_device_id cros_ec_led_id[] = {
+ { "cros-ec-led", 0 },
+ {}
+};
+
+static struct platform_driver cros_ec_led_driver = {
+ .driver.name = "cros-ec-led",
+ .probe = cros_ec_led_probe,
+ .id_table = cros_ec_led_id,
+};
+module_platform_driver(cros_ec_led_driver);
+
+MODULE_DEVICE_TABLE(platform, cros_ec_led_id);
+MODULE_DESCRIPTION("ChromeOS EC LED Driver");
+MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c
index f067a5f4d3c4..71209b3c8f1e 100644
--- a/drivers/leds/leds-da903x.c
+++ b/drivers/leds/leds-da903x.c
@@ -133,7 +133,7 @@ static struct platform_driver da903x_led_driver = {
.name = "da903x-led",
},
.probe = da903x_led_probe,
- .remove_new = da903x_led_remove,
+ .remove = da903x_led_remove,
};
module_platform_driver(da903x_led_driver);
diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c
index 64679d62076b..7c9051184a59 100644
--- a/drivers/leds/leds-da9052.c
+++ b/drivers/leds/leds-da9052.c
@@ -179,7 +179,7 @@ static struct platform_driver da9052_led_driver = {
.name = "da9052-leds",
},
.probe = da9052_led_probe,
- .remove_new = da9052_led_remove,
+ .remove = da9052_led_remove,
};
module_platform_driver(da9052_led_driver);
diff --git a/drivers/leds/leds-el15203000.c b/drivers/leds/leds-el15203000.c
index d40194a3029f..e26d1654bd0d 100644
--- a/drivers/leds/leds-el15203000.c
+++ b/drivers/leds/leds-el15203000.c
@@ -237,22 +237,20 @@ static int el15203000_pattern_clear(struct led_classdev *ldev)
static int el15203000_probe_dt(struct el15203000 *priv)
{
struct el15203000_led *led = priv->leds;
- struct fwnode_handle *child;
int ret;
- device_for_each_child_node(priv->dev, child) {
+ device_for_each_child_node_scoped(priv->dev, child) {
struct led_init_data init_data = {};
ret = fwnode_property_read_u32(child, "reg", &led->reg);
if (ret) {
dev_err(priv->dev, "LED without ID number");
- goto err_child_out;
+ return ret;
}
if (led->reg > U8_MAX) {
dev_err(priv->dev, "LED value %d is invalid", led->reg);
- ret = -EINVAL;
- goto err_child_out;
+ return -EINVAL;
}
led->priv = priv;
@@ -274,17 +272,13 @@ static int el15203000_probe_dt(struct el15203000 *priv)
dev_err(priv->dev,
"failed to register LED device %s, err %d",
led->ldev.name, ret);
- goto err_child_out;
+ return ret;
}
led++;
}
return 0;
-
-err_child_out:
- fwnode_handle_put(child);
- return ret;
}
static int el15203000_probe(struct spi_device *spi)
diff --git a/drivers/leds/leds-expresswire.c b/drivers/leds/leds-expresswire.c
index e4937a8e0f44..bb69be228a6d 100644
--- a/drivers/leds/leds-expresswire.c
+++ b/drivers/leds/leds-expresswire.c
@@ -18,7 +18,7 @@ void expresswire_power_off(struct expresswire_common_props *props)
gpiod_set_value_cansleep(props->ctrl_gpio, 0);
usleep_range(props->timing.poweroff_us, props->timing.poweroff_us * 2);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_power_off, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_power_off, "EXPRESSWIRE");
void expresswire_enable(struct expresswire_common_props *props)
{
@@ -28,14 +28,14 @@ void expresswire_enable(struct expresswire_common_props *props)
udelay(props->timing.detect_us);
gpiod_set_value(props->ctrl_gpio, 1);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_enable, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_enable, "EXPRESSWIRE");
void expresswire_start(struct expresswire_common_props *props)
{
gpiod_set_value(props->ctrl_gpio, 1);
udelay(props->timing.data_start_us);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_start, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_start, "EXPRESSWIRE");
void expresswire_end(struct expresswire_common_props *props)
{
@@ -44,7 +44,7 @@ void expresswire_end(struct expresswire_common_props *props)
gpiod_set_value(props->ctrl_gpio, 1);
udelay(props->timing.end_of_data_high_us);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_end, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE");
void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
{
@@ -60,7 +60,7 @@ void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
udelay(props->timing.short_bitset_us);
}
}
-EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE");
void expresswire_write_u8(struct expresswire_common_props *props, u8 val)
{
@@ -69,4 +69,4 @@ void expresswire_write_u8(struct expresswire_common_props *props, u8 val)
expresswire_set_bit(props, val & BIT(i));
expresswire_end(props);
}
-EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, EXPRESSWIRE);
+EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, "EXPRESSWIRE");
diff --git a/drivers/leds/leds-gpio-register.c b/drivers/leds/leds-gpio-register.c
index de3f12c2b80d..ccc01fa72e6f 100644
--- a/drivers/leds/leds-gpio-register.c
+++ b/drivers/leds/leds-gpio-register.c
@@ -10,8 +10,8 @@
/**
* gpio_led_register_device - register a gpio-led device
- * @pdata: the platform data used for the new device
* @id: platform ID
+ * @pdata: the platform data used for the new device
*
* Makes a copy of pdata and pdata->leds and registers a new leds-gpio device
* with the result. This allows to have pdata and pdata-leds in .init.rodata
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c
index 83fcd7b6afff..a3428b22de3a 100644
--- a/drivers/leds/leds-gpio.c
+++ b/drivers/leds/leds-gpio.c
@@ -21,8 +21,6 @@
#include <linux/slab.h>
#include <linux/types.h>
-#include "leds.h"
-
struct gpio_led_data {
struct led_classdev cdev;
struct gpio_desc *gpiod;
@@ -148,9 +146,8 @@ struct gpio_leds_priv {
static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
{
- struct fwnode_handle *child;
struct gpio_leds_priv *priv;
- int count, ret;
+ int count, used, ret;
count = device_get_child_node_count(dev);
if (!count)
@@ -159,9 +156,11 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
+ priv->num_leds = count;
+ used = 0;
- device_for_each_child_node(dev, child) {
- struct gpio_led_data *led_dat = &priv->leds[priv->num_leds];
+ device_for_each_child_node_scoped(dev, child) {
+ struct gpio_led_data *led_dat = &priv->leds[used];
struct gpio_led led = {};
/*
@@ -174,7 +173,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
if (IS_ERR(led.gpiod)) {
dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n",
child);
- fwnode_handle_put(child);
return ERR_CAST(led.gpiod);
}
@@ -190,15 +188,15 @@ static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
led.panic_indicator = 1;
ret = create_gpio_led(&led, led_dat, dev, child, NULL);
- if (ret < 0) {
- fwnode_handle_put(child);
+ if (ret < 0)
return ERR_PTR(ret);
- }
+
/* Set gpiod label to match the corresponding LED name. */
gpiod_set_consumer_name(led_dat->gpiod,
led_dat->cdev.dev->kobj.name);
- priv->num_leds++;
+ used++;
}
+ priv->num_leds = used;
return priv;
}
@@ -214,7 +212,6 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
const struct gpio_led *template)
{
struct gpio_desc *gpiod;
- unsigned long flags = GPIOF_OUT_INIT_LOW;
int ret;
/*
@@ -241,10 +238,7 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
if (!gpio_is_valid(template->gpio))
return ERR_PTR(-ENOENT);
- if (template->active_low)
- flags |= GPIOF_ACTIVE_LOW;
-
- ret = devm_gpio_request_one(dev, template->gpio, flags,
+ ret = devm_gpio_request_one(dev, template->gpio, GPIOF_OUT_INIT_LOW,
template->name);
if (ret < 0)
return ERR_PTR(ret);
@@ -253,6 +247,9 @@ static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
if (!gpiod)
return ERR_PTR(-EINVAL);
+ if (template->active_low ^ gpiod_is_active_low(gpiod))
+ gpiod_toggle_active_low(gpiod);
+
return gpiod;
}
diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c
index 66c65741202e..27bfab3da479 100644
--- a/drivers/leds/leds-is31fl319x.c
+++ b/drivers/leds/leds-is31fl319x.c
@@ -140,7 +140,7 @@ static const struct reg_default is31fl3190_reg_defaults[] = {
{ IS31FL3190_PWM(2), 0x00 },
};
-static struct regmap_config is31fl3190_regmap_config = {
+static const struct regmap_config is31fl3190_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = IS31FL3190_RESET,
@@ -178,7 +178,7 @@ static const struct reg_default is31fl3196_reg_defaults[] = {
{ IS31FL3196_PWM(8), 0x00 },
};
-static struct regmap_config is31fl3196_regmap_config = {
+static const struct regmap_config is31fl3196_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = IS31FL3196_REG_CNT,
@@ -392,7 +392,7 @@ static int is31fl319x_parse_child_fw(const struct device *dev,
static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
{
- struct fwnode_handle *fwnode = dev_fwnode(dev), *child;
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
int count;
int ret;
@@ -404,7 +404,7 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
is31->cdef = device_get_match_data(dev);
count = 0;
- fwnode_for_each_available_child_node(fwnode, child)
+ device_for_each_child_node_scoped(dev, child)
count++;
dev_dbg(dev, "probing with %d leds defined in DT\n", count);
@@ -414,33 +414,25 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
"Number of leds defined must be between 1 and %u\n",
is31->cdef->num_leds);
- fwnode_for_each_available_child_node(fwnode, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct is31fl319x_led *led;
u32 reg;
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret) {
- ret = dev_err_probe(dev, ret, "Failed to read led 'reg' property\n");
- goto put_child_node;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read led 'reg' property\n");
- if (reg < 1 || reg > is31->cdef->num_leds) {
- ret = dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg);
- goto put_child_node;
- }
+ if (reg < 1 || reg > is31->cdef->num_leds)
+ return dev_err_probe(dev, -EINVAL, "invalid led reg %u\n", reg);
led = &is31->leds[reg - 1];
- if (led->configured) {
- ret = dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg);
- goto put_child_node;
- }
+ if (led->configured)
+ return dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg);
ret = is31fl319x_parse_child_fw(dev, child, led, is31);
- if (ret) {
- ret = dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg);
- goto put_child_node;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg);
led->configured = true;
}
@@ -454,10 +446,6 @@ static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
}
return 0;
-
-put_child_node:
- fwnode_handle_put(child);
- return ret;
}
static inline int is31fl3190_microamp_to_cs(struct device *dev, u32 microamp)
diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
index b0a0be77bb33..8793330dd414 100644
--- a/drivers/leds/leds-is31fl32xx.c
+++ b/drivers/leds/leds-is31fl32xx.c
@@ -363,10 +363,9 @@ static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
static int is31fl32xx_parse_dt(struct device *dev,
struct is31fl32xx_priv *priv)
{
- struct device_node *child;
int ret = 0;
- for_each_available_child_of_node(dev_of_node(dev), child) {
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
struct led_init_data init_data = {};
struct is31fl32xx_led_data *led_data =
&priv->leds[priv->num_leds];
@@ -376,7 +375,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
ret = is31fl32xx_parse_child_dt(dev, child, led_data);
if (ret)
- goto err;
+ return ret;
/* Detect if channel is already in use by another child */
other_led_data = is31fl32xx_find_led_data(priv,
@@ -385,8 +384,7 @@ static int is31fl32xx_parse_dt(struct device *dev,
dev_err(dev,
"Node %pOF 'reg' conflicts with another LED\n",
child);
- ret = -EINVAL;
- goto err;
+ return -EINVAL;
}
init_data.fwnode = of_fwnode_handle(child);
@@ -396,17 +394,13 @@ static int is31fl32xx_parse_dt(struct device *dev,
if (ret) {
dev_err(dev, "Failed to register LED for %pOF: %d\n",
child, ret);
- goto err;
+ return ret;
}
priv->num_leds++;
}
return 0;
-
-err:
- of_node_put(child);
- return ret;
}
static const struct of_device_id of_is31fl32xx_match[] = {
diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c
index a2feef8e4ac5..e44a3db106c3 100644
--- a/drivers/leds/leds-lm3530.c
+++ b/drivers/leds/leds-lm3530.c
@@ -478,7 +478,7 @@ static void lm3530_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3530_id[] = {
- {LM3530_NAME, 0},
+ { LM3530_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lm3530_id);
diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c
index 13662a4aa1f2..24dc8ad27bb3 100644
--- a/drivers/leds/leds-lm3532.c
+++ b/drivers/leds/leds-lm3532.c
@@ -542,9 +542,15 @@ static int lm3532_parse_als(struct lm3532_data *priv)
return ret;
}
+static void gpio_set_low_action(void *data)
+{
+ struct lm3532_data *priv = data;
+
+ gpiod_direction_output(priv->enable_gpio, 0);
+}
+
static int lm3532_parse_node(struct lm3532_data *priv)
{
- struct fwnode_handle *child = NULL;
struct lm3532_led *led;
int control_bank;
u32 ramp_time;
@@ -556,6 +562,12 @@ static int lm3532_parse_node(struct lm3532_data *priv)
if (IS_ERR(priv->enable_gpio))
priv->enable_gpio = NULL;
+ if (priv->enable_gpio) {
+ ret = devm_add_action(&priv->client->dev, gpio_set_low_action, priv);
+ if (ret)
+ return ret;
+ }
+
priv->regulator = devm_regulator_get(&priv->client->dev, "vin");
if (IS_ERR(priv->regulator))
priv->regulator = NULL;
@@ -574,7 +586,7 @@ static int lm3532_parse_node(struct lm3532_data *priv)
else
priv->runtime_ramp_down = lm3532_get_ramp_index(ramp_time);
- device_for_each_child_node(priv->dev, child) {
+ device_for_each_child_node_scoped(priv->dev, child) {
struct led_init_data idata = {
.fwnode = child,
.default_label = ":",
@@ -586,7 +598,7 @@ static int lm3532_parse_node(struct lm3532_data *priv)
ret = fwnode_property_read_u32(child, "reg", &control_bank);
if (ret) {
dev_err(&priv->client->dev, "reg property missing\n");
- goto child_out;
+ return ret;
}
if (control_bank > LM3532_CONTROL_C) {
@@ -600,7 +612,7 @@ static int lm3532_parse_node(struct lm3532_data *priv)
&led->mode);
if (ret) {
dev_err(&priv->client->dev, "ti,led-mode property missing\n");
- goto child_out;
+ return ret;
}
if (fwnode_property_present(child, "led-max-microamp") &&
@@ -634,7 +646,7 @@ static int lm3532_parse_node(struct lm3532_data *priv)
led->num_leds);
if (ret) {
dev_err(&priv->client->dev, "led-sources property missing\n");
- goto child_out;
+ return ret;
}
led->priv = priv;
@@ -644,23 +656,20 @@ static int lm3532_parse_node(struct lm3532_data *priv)
if (ret) {
dev_err(&priv->client->dev, "led register err: %d\n",
ret);
- goto child_out;
+ return ret;
}
ret = lm3532_init_registers(led);
if (ret) {
dev_err(&priv->client->dev, "register init err: %d\n",
ret);
- goto child_out;
+ return ret;
}
i++;
}
- return 0;
-child_out:
- fwnode_handle_put(child);
- return ret;
+ return 0;
}
static int lm3532_probe(struct i2c_client *client)
@@ -691,7 +700,10 @@ static int lm3532_probe(struct i2c_client *client)
return ret;
}
- mutex_init(&drvdata->lock);
+ ret = devm_mutex_init(&client->dev, &drvdata->lock);
+ if (ret)
+ return ret;
+
i2c_set_clientdata(client, drvdata);
ret = lm3532_parse_node(drvdata);
@@ -703,16 +715,6 @@ static int lm3532_probe(struct i2c_client *client)
return ret;
}
-static void lm3532_remove(struct i2c_client *client)
-{
- struct lm3532_data *drvdata = i2c_get_clientdata(client);
-
- mutex_destroy(&drvdata->lock);
-
- if (drvdata->enable_gpio)
- gpiod_direction_output(drvdata->enable_gpio, 0);
-}
-
static const struct of_device_id of_lm3532_leds_match[] = {
{ .compatible = "ti,lm3532", },
{},
@@ -720,14 +722,13 @@ static const struct of_device_id of_lm3532_leds_match[] = {
MODULE_DEVICE_TABLE(of, of_lm3532_leds_match);
static const struct i2c_device_id lm3532_id[] = {
- {LM3532_NAME, 0},
+ { LM3532_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lm3532_id);
static struct i2c_driver lm3532_i2c_driver = {
.probe = lm3532_probe,
- .remove = lm3532_remove,
.id_table = lm3532_id,
.driver = {
.name = LM3532_NAME,
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
index a3d33165d262..45795f2a1042 100644
--- a/drivers/leds/leds-lm3533.c
+++ b/drivers/leds/leds-lm3533.c
@@ -744,7 +744,7 @@ static struct platform_driver lm3533_led_driver = {
.name = "lm3533-leds",
},
.probe = lm3533_led_probe,
- .remove_new = lm3533_led_remove,
+ .remove = lm3533_led_remove,
.shutdown = lm3533_led_shutdown,
};
module_platform_driver(lm3533_led_driver);
diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c
index 6eee52e211be..61629d5d6703 100644
--- a/drivers/leds/leds-lm3642.c
+++ b/drivers/leds/leds-lm3642.c
@@ -390,7 +390,7 @@ static void lm3642_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3642_id[] = {
- {LM3642_NAME, 0},
+ { LM3642_NAME },
{}
};
diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c
index 380d17a58fe9..7ad232780a31 100644
--- a/drivers/leds/leds-lm3697.c
+++ b/drivers/leds/leds-lm3697.c
@@ -202,7 +202,6 @@ out:
static int lm3697_probe_dt(struct lm3697 *priv)
{
- struct fwnode_handle *child = NULL;
struct device *dev = priv->dev;
struct lm3697_led *led;
int ret = -EINVAL;
@@ -220,19 +219,18 @@ static int lm3697_probe_dt(struct lm3697 *priv)
if (IS_ERR(priv->regulator))
priv->regulator = NULL;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
ret = fwnode_property_read_u32(child, "reg", &control_bank);
if (ret) {
dev_err(dev, "reg property missing\n");
- goto child_out;
+ return ret;
}
if (control_bank > LM3697_CONTROL_B) {
dev_err(dev, "reg property is invalid\n");
- ret = -EINVAL;
- goto child_out;
+ return -EINVAL;
}
led = &priv->leds[i];
@@ -262,7 +260,7 @@ static int lm3697_probe_dt(struct lm3697 *priv)
led->num_leds);
if (ret) {
dev_err(dev, "led-sources property missing\n");
- goto child_out;
+ return ret;
}
for (j = 0; j < led->num_leds; j++)
@@ -286,17 +284,13 @@ static int lm3697_probe_dt(struct lm3697 *priv)
&init_data);
if (ret) {
dev_err(dev, "led register err: %d\n", ret);
- goto child_out;
+ return ret;
}
i++;
}
- return ret;
-
-child_out:
- fwnode_handle_put(child);
- return ret;
+ return 0;
}
static int lm3697_probe(struct i2c_client *client)
@@ -360,7 +354,7 @@ static void lm3697_remove(struct i2c_client *client)
}
static const struct i2c_device_id lm3697_id[] = {
- { "lm3697", 0 },
+ { "lm3697" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lm3697_id);
diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c
index 8ea746c499d1..ccfeee49ea78 100644
--- a/drivers/leds/leds-lp3944.c
+++ b/drivers/leds/leds-lp3944.c
@@ -417,7 +417,7 @@ static void lp3944_remove(struct i2c_client *client)
/* lp3944 i2c driver struct */
static const struct i2c_device_id lp3944_id[] = {
- {"lp3944", 0},
+ { "lp3944" },
{}
};
diff --git a/drivers/leds/leds-lp3952.c b/drivers/leds/leds-lp3952.c
index 5d18bbfd1f23..17219a582704 100644
--- a/drivers/leds/leds-lp3952.c
+++ b/drivers/leds/leds-lp3952.c
@@ -207,6 +207,13 @@ static const struct regmap_config lp3952_regmap = {
.cache_type = REGCACHE_MAPLE,
};
+static void gpio_set_low_action(void *data)
+{
+ struct lp3952_led_array *priv = data;
+
+ gpiod_set_value(priv->enable_gpio, 0);
+}
+
static int lp3952_probe(struct i2c_client *client)
{
int status;
@@ -226,6 +233,10 @@ static int lp3952_probe(struct i2c_client *client)
return status;
}
+ status = devm_add_action(&client->dev, gpio_set_low_action, priv);
+ if (status)
+ return status;
+
priv->regmap = devm_regmap_init_i2c(client, &lp3952_regmap);
if (IS_ERR(priv->regmap)) {
int err = PTR_ERR(priv->regmap);
@@ -254,17 +265,8 @@ static int lp3952_probe(struct i2c_client *client)
return 0;
}
-static void lp3952_remove(struct i2c_client *client)
-{
- struct lp3952_led_array *priv;
-
- priv = i2c_get_clientdata(client);
- lp3952_on_off(priv, LP3952_LED_ALL, false);
- gpiod_set_value(priv->enable_gpio, 0);
-}
-
static const struct i2c_device_id lp3952_id[] = {
- {LP3952_NAME, 0},
+ { LP3952_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lp3952_id);
@@ -274,7 +276,6 @@ static struct i2c_driver lp3952_i2c_driver = {
.name = LP3952_NAME,
},
.probe = lp3952_probe,
- .remove = lp3952_remove,
.id_table = lp3952_id,
};
diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
index 68c4d9967d68..02cb1565a9fb 100644
--- a/drivers/leds/leds-lp50xx.c
+++ b/drivers/leds/leds-lp50xx.c
@@ -16,8 +16,6 @@
#include <linux/led-class-multicolor.h>
-#include "leds.h"
-
#define LP50XX_DEV_CFG0 0x00
#define LP50XX_DEV_CFG1 0x01
#define LP50XX_LED_CFG0 0x02
@@ -265,7 +263,6 @@ static const struct lp50xx_chip_info lp50xx_chip_info_tbl[] = {
struct lp50xx_led {
struct led_classdev_mc mc_cdev;
struct lp50xx *priv;
- unsigned long bank_modules;
u8 ctrl_bank_enabled;
int led_number;
};
@@ -279,7 +276,6 @@ struct lp50xx_led {
* @dev: pointer to the devices device struct
* @lock: lock for reading/writing the device
* @chip_info: chip specific information (ie num_leds)
- * @num_of_banked_leds: holds the number of banked LEDs
* @leds: array of LED strings
*/
struct lp50xx {
@@ -290,7 +286,6 @@ struct lp50xx {
struct device *dev;
struct mutex lock;
const struct lp50xx_chip_info *chip_info;
- int num_of_banked_leds;
/* This needs to be at the end of the struct */
struct lp50xx_led leds[];
@@ -404,8 +399,6 @@ static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv,
return -EINVAL;
}
- priv->num_of_banked_leds = num_leds;
-
ret = fwnode_property_read_u32_array(child, "reg", led_banks, num_leds);
if (ret) {
dev_err(priv->dev, "reg property is missing\n");
@@ -439,7 +432,6 @@ static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv,
static int lp50xx_probe_dt(struct lp50xx *priv)
{
- struct fwnode_handle *child = NULL;
struct fwnode_handle *led_node = NULL;
struct led_init_data init_data = {};
struct led_classdev *led_cdev;
@@ -459,17 +451,17 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
if (IS_ERR(priv->regulator))
priv->regulator = NULL;
- device_for_each_child_node(priv->dev, child) {
+ device_for_each_child_node_scoped(priv->dev, child) {
led = &priv->leds[i];
ret = fwnode_property_count_u32(child, "reg");
if (ret < 0) {
dev_err(priv->dev, "reg property is invalid\n");
- goto child_out;
+ return ret;
}
ret = lp50xx_probe_leds(child, priv, led, ret);
if (ret)
- goto child_out;
+ return ret;
init_data.fwnode = child;
num_colors = 0;
@@ -480,10 +472,8 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
*/
mc_led_info = devm_kcalloc(priv->dev, LP50XX_LEDS_PER_MODULE,
sizeof(*mc_led_info), GFP_KERNEL);
- if (!mc_led_info) {
- ret = -ENOMEM;
- goto child_out;
- }
+ if (!mc_led_info)
+ return -ENOMEM;
fwnode_for_each_child_node(child, led_node) {
ret = fwnode_property_read_u32(led_node, "color",
@@ -491,7 +481,7 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
if (ret) {
fwnode_handle_put(led_node);
dev_err(priv->dev, "Cannot read color\n");
- goto child_out;
+ return ret;
}
mc_led_info[num_colors].color_index = color_id;
@@ -509,16 +499,12 @@ static int lp50xx_probe_dt(struct lp50xx *priv)
&init_data);
if (ret) {
dev_err(priv->dev, "led register err: %d\n", ret);
- goto child_out;
+ return ret;
}
i++;
}
return 0;
-
-child_out:
- fwnode_handle_put(child);
- return ret;
}
static int lp50xx_probe(struct i2c_client *client)
diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c
index f9c8b568b652..7564b9953408 100644
--- a/drivers/leds/leds-lp5521.c
+++ b/drivers/leds/leds-lp5521.c
@@ -9,6 +9,7 @@
* Milo(Woogyom) Kim <milo.kim@ti.com>
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@@ -21,7 +22,6 @@
#include "leds-lp55xx-common.h"
-#define LP5521_PROGRAM_LENGTH 32
#define LP5521_MAX_LEDS 3
#define LP5521_CMD_DIRECT 0x3F
@@ -73,29 +73,6 @@
/* Reset register value */
#define LP5521_RESET 0xFF
-/* Program Memory Operations */
-#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */
-#define LP5521_MODE_G_M 0x0C
-#define LP5521_MODE_B_M 0x03
-#define LP5521_LOAD_R 0x10
-#define LP5521_LOAD_G 0x04
-#define LP5521_LOAD_B 0x01
-
-#define LP5521_R_IS_LOADING(mode) \
- ((mode & LP5521_MODE_R_M) == LP5521_LOAD_R)
-#define LP5521_G_IS_LOADING(mode) \
- ((mode & LP5521_MODE_G_M) == LP5521_LOAD_G)
-#define LP5521_B_IS_LOADING(mode) \
- ((mode & LP5521_MODE_B_M) == LP5521_LOAD_B)
-
-#define LP5521_EXEC_R_M 0x30 /* Enable Register */
-#define LP5521_EXEC_G_M 0x0C
-#define LP5521_EXEC_B_M 0x03
-#define LP5521_EXEC_M 0x3F
-#define LP5521_RUN_R 0x20
-#define LP5521_RUN_G 0x08
-#define LP5521_RUN_B 0x02
-
static inline void lp5521_wait_opmode_done(void)
{
/* operation mode change needs to be longer than 153 us */
@@ -108,170 +85,21 @@ static inline void lp5521_wait_enable_done(void)
usleep_range(500, 600);
}
-static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current)
-{
- led->led_current = led_current;
- lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
- led_current);
-}
-
-static void lp5521_load_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5521_MODE_R_M,
- [LP55XX_ENGINE_2] = LP5521_MODE_G_M,
- [LP55XX_ENGINE_3] = LP5521_MODE_B_M,
- };
-
- static const u8 val[] = {
- [LP55XX_ENGINE_1] = LP5521_LOAD_R,
- [LP55XX_ENGINE_2] = LP5521_LOAD_G,
- [LP55XX_ENGINE_3] = LP5521_LOAD_B,
- };
-
- lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]);
-
- lp5521_wait_opmode_done();
-}
-
-static void lp5521_stop_all_engines(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP5521_REG_OP_MODE, 0);
- lp5521_wait_opmode_done();
-}
-
-static void lp5521_stop_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5521_MODE_R_M,
- [LP55XX_ENGINE_2] = LP5521_MODE_G_M,
- [LP55XX_ENGINE_3] = LP5521_MODE_B_M,
- };
-
- lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0);
-
- lp5521_wait_opmode_done();
-}
-
static void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
- u8 mode;
- u8 exec;
/* stop engine */
if (!start) {
- lp5521_stop_engine(chip);
+ lp55xx_stop_engine(chip);
lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
lp5521_wait_opmode_done();
return;
}
- /*
- * To run the engine,
- * operation mode and enable register should updated at the same time
- */
-
- ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode);
- if (ret)
- return;
-
- ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec);
- if (ret)
- return;
-
- /* change operation mode to RUN only when each engine is loading */
- if (LP5521_R_IS_LOADING(mode)) {
- mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R;
- exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R;
- }
-
- if (LP5521_G_IS_LOADING(mode)) {
- mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
- exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
- }
-
- if (LP5521_B_IS_LOADING(mode)) {
- mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B;
- exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B;
- }
-
- lp55xx_write(chip, LP5521_REG_OP_MODE, mode);
- lp5521_wait_opmode_done();
-
- lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec);
- lp5521_wait_enable_done();
-}
-
-static int lp5521_update_program_memory(struct lp55xx_chip *chip,
- const u8 *data, size_t size)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
- static const u8 addr[] = {
- [LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM,
- [LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM,
- [LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM,
- };
- unsigned cmd;
- char c[3];
- int nrchars;
- int ret;
- int offset = 0;
- int i = 0;
-
- while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
- if (ret != 1)
- goto err;
-
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto err;
-
- pattern[i] = (u8)cmd;
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto err;
-
- for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) {
- ret = lp55xx_write(chip, addr[idx] + i, pattern[i]);
- if (ret)
- return -EINVAL;
- }
-
- return size;
-
-err:
- dev_err(&chip->cl->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static void lp5521_firmware_loaded(struct lp55xx_chip *chip)
-{
- const struct firmware *fw = chip->fw;
-
- if (fw->size > LP5521_PROGRAM_LENGTH) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
- return;
- }
-
- /*
- * Program memory sequence
- * 1) set engine mode to "LOAD"
- * 2) write firmware data into program memory
- */
-
- lp5521_load_engine(chip);
- lp5521_update_program_memory(chip, fw->data, fw->size);
+ ret = lp55xx_run_engine_common(chip);
+ if (!ret)
+ lp5521_wait_enable_done();
}
static int lp5521_post_init_device(struct lp55xx_chip *chip)
@@ -350,114 +178,6 @@ static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
return 0;
}
-static int lp5521_multicolor_brightness(struct lp55xx_led *led)
-{
- struct lp55xx_chip *chip = led->chip;
- int ret;
- int i;
-
- mutex_lock(&chip->lock);
- for (i = 0; i < led->mc_cdev.num_colors; i++) {
- ret = lp55xx_write(chip,
- LP5521_REG_LED_PWM_BASE +
- led->mc_cdev.subled_info[i].channel,
- led->mc_cdev.subled_info[i].brightness);
- if (ret)
- break;
- }
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static int lp5521_led_brightness(struct lp55xx_led *led)
-{
- struct lp55xx_chip *chip = led->chip;
- int ret;
-
- mutex_lock(&chip->lock);
- ret = lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
-
- return ret;
-}
-
-static ssize_t show_engine_mode(struct device *dev,
- struct device_attribute *attr,
- char *buf, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
-
- switch (mode) {
- case LP55XX_ENGINE_RUN:
- return sprintf(buf, "run\n");
- case LP55XX_ENGINE_LOAD:
- return sprintf(buf, "load\n");
- case LP55XX_ENGINE_DISABLED:
- default:
- return sprintf(buf, "disabled\n");
- }
-}
-show_mode(1)
-show_mode(2)
-show_mode(3)
-
-static ssize_t store_engine_mode(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- struct lp55xx_engine *engine = &chip->engines[nr - 1];
-
- mutex_lock(&chip->lock);
-
- chip->engine_idx = nr;
-
- if (!strncmp(buf, "run", 3)) {
- lp5521_run_engine(chip, true);
- engine->mode = LP55XX_ENGINE_RUN;
- } else if (!strncmp(buf, "load", 4)) {
- lp5521_stop_engine(chip);
- lp5521_load_engine(chip);
- engine->mode = LP55XX_ENGINE_LOAD;
- } else if (!strncmp(buf, "disabled", 8)) {
- lp5521_stop_engine(chip);
- engine->mode = LP55XX_ENGINE_DISABLED;
- }
-
- mutex_unlock(&chip->lock);
-
- return len;
-}
-store_mode(1)
-store_mode(2)
-store_mode(3)
-
-static ssize_t store_engine_load(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int ret;
-
- mutex_lock(&chip->lock);
-
- chip->engine_idx = nr;
- lp5521_load_engine(chip);
- ret = lp5521_update_program_memory(chip, buf, len);
-
- mutex_unlock(&chip->lock);
-
- return ret;
-}
-store_load(1)
-store_load(2)
-store_load(3)
-
static ssize_t lp5521_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -466,20 +186,20 @@ static ssize_t lp5521_selftest(struct device *dev,
struct lp55xx_chip *chip = led->chip;
int ret;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
ret = lp5521_run_selftest(chip, buf);
- mutex_unlock(&chip->lock);
return sysfs_emit(buf, "%s\n", ret ? "FAIL" : "OK");
}
/* device attributes */
-static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
-static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
-static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
-static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
-static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
-static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
+LP55XX_DEV_ATTR_ENGINE_MODE(1);
+LP55XX_DEV_ATTR_ENGINE_MODE(2);
+LP55XX_DEV_ATTR_ENGINE_MODE(3);
+LP55XX_DEV_ATTR_ENGINE_LOAD(1);
+LP55XX_DEV_ATTR_ENGINE_LOAD(2);
+LP55XX_DEV_ATTR_ENGINE_LOAD(3);
static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest);
static struct attribute *lp5521_attributes[] = {
@@ -499,6 +219,12 @@ static const struct attribute_group lp5521_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5521_cfg = {
+ .reg_op_mode = {
+ .addr = LP5521_REG_OP_MODE,
+ },
+ .reg_exec = {
+ .addr = LP5521_REG_ENABLE,
+ },
.reset = {
.addr = LP5521_REG_RESET,
.val = LP5521_RESET,
@@ -507,97 +233,33 @@ static struct lp55xx_device_config lp5521_cfg = {
.addr = LP5521_REG_ENABLE,
.val = LP5521_ENABLE_DEFAULT,
},
+ .prog_mem_base = {
+ .addr = LP5521_REG_R_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5521_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5521_REG_LED_CURRENT_BASE,
+ },
.max_channel = LP5521_MAX_LEDS,
.post_init_device = lp5521_post_init_device,
- .brightness_fn = lp5521_led_brightness,
- .multicolor_brightness_fn = lp5521_multicolor_brightness,
- .set_led_current = lp5521_set_led_current,
- .firmware_cb = lp5521_firmware_loaded,
+ .brightness_fn = lp55xx_led_brightness,
+ .multicolor_brightness_fn = lp55xx_multicolor_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp5521_run_engine,
.dev_attr_group = &lp5521_group,
};
-static int lp5521_probe(struct i2c_client *client)
-{
- const struct i2c_device_id *id = i2c_client_get_device_id(client);
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
- struct device_node *np = dev_of_node(&client->dev);
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- chip->cfg = &lp5521_cfg;
-
- if (!pdata) {
- if (np) {
- pdata = lp55xx_of_populate_pdata(&client->dev, np,
- chip);
- if (IS_ERR(pdata))
- return PTR_ERR(pdata);
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
-
- led = devm_kcalloc(&client->dev,
- pdata->num_channels, sizeof(*led), GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
-
- mutex_init(&chip->lock);
-
- i2c_set_clientdata(client, led);
-
- ret = lp55xx_init_device(chip);
- if (ret)
- goto err_init;
-
- dev_info(&client->dev, "%s programmable led chip found\n", id->name);
-
- ret = lp55xx_register_leds(led, chip);
- if (ret)
- goto err_out;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_out;
- }
-
- return 0;
-
-err_out:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static void lp5521_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp5521_stop_all_engines(chip);
- lp55xx_unregister_sysfs(chip);
- lp55xx_deinit_device(chip);
-}
-
static const struct i2c_device_id lp5521_id[] = {
- { "lp5521", 0 }, /* Three channel chip */
+ { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5521_id);
static const struct of_device_id of_lp5521_leds_match[] = {
- { .compatible = "national,lp5521", },
+ { .compatible = "national,lp5521", .data = &lp5521_cfg, },
{},
};
@@ -608,8 +270,8 @@ static struct i2c_driver lp5521_driver = {
.name = "lp5521",
.of_match_table = of_lp5521_leds_match,
},
- .probe = lp5521_probe,
- .remove = lp5521_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5521_id,
};
diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
index 38de853f9939..4ed3e735260c 100644
--- a/drivers/leds/leds-lp5523.c
+++ b/drivers/leds/leds-lp5523.c
@@ -9,6 +9,7 @@
* Milo(Woogyom) Kim <milo.kim@ti.com>
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@@ -21,7 +22,6 @@
#include "leds-lp55xx-common.h"
-#define LP5523_PROGRAM_LENGTH 32 /* bytes */
/* Memory is used like this:
* 0x00 engine 1 program
* 0x10 engine 2 program
@@ -30,6 +30,7 @@
* 0x40 engine 2 muxing info
* 0x50 engine 3 muxing info
*/
+#define LP5523_PAGES_PER_ENGINE 1
#define LP5523_MAX_LEDS 9
/* Registers */
@@ -41,7 +42,10 @@
#define LP5523_REG_LED_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36
+
#define LP5523_REG_STATUS 0x3A
+#define LP5523_ENGINE_BUSY BIT(4)
+
#define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42
@@ -70,61 +74,8 @@
#define LP5523_EXT_CLK_USED 0x08
#define LP5523_ENG_STATUS_MASK 0x07
-#define LP5523_FADER_MAPPING_MASK 0xC0
-#define LP5523_FADER_MAPPING_SHIFT 6
-
-/* Memory Page Selection */
-#define LP5523_PAGE_ENG1 0
-#define LP5523_PAGE_ENG2 1
-#define LP5523_PAGE_ENG3 2
-#define LP5523_PAGE_MUX1 3
-#define LP5523_PAGE_MUX2 4
-#define LP5523_PAGE_MUX3 5
-
-/* Program Memory Operations */
-#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */
-#define LP5523_MODE_ENG2_M 0x0C
-#define LP5523_MODE_ENG3_M 0x03
-#define LP5523_LOAD_ENG1 0x10
-#define LP5523_LOAD_ENG2 0x04
-#define LP5523_LOAD_ENG3 0x01
-
-#define LP5523_ENG1_IS_LOADING(mode) \
- ((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1)
-#define LP5523_ENG2_IS_LOADING(mode) \
- ((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2)
-#define LP5523_ENG3_IS_LOADING(mode) \
- ((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3)
-
-#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */
-#define LP5523_EXEC_ENG2_M 0x0C
-#define LP5523_EXEC_ENG3_M 0x03
-#define LP5523_EXEC_M 0x3F
-#define LP5523_RUN_ENG1 0x20
-#define LP5523_RUN_ENG2 0x08
-#define LP5523_RUN_ENG3 0x02
-
-#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
-
-enum lp5523_chip_id {
- LP5523,
- LP55231,
-};
-
static int lp5523_init_program_engine(struct lp55xx_chip *chip);
-static inline void lp5523_wait_opmode_done(void)
-{
- usleep_range(1000, 2000);
-}
-
-static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current)
-{
- led->led_current = led_current;
- lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
- led_current);
-}
-
static int lp5523_post_init_device(struct lp55xx_chip *chip)
{
int ret;
@@ -156,114 +107,16 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip)
return lp5523_init_program_engine(chip);
}
-static void lp5523_load_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
- };
-
- static const u8 val[] = {
- [LP55XX_ENGINE_1] = LP5523_LOAD_ENG1,
- [LP55XX_ENGINE_2] = LP5523_LOAD_ENG2,
- [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3,
- };
-
- lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
-
- lp5523_wait_opmode_done();
-}
-
-static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 page_sel[] = {
- [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
- [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
- [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
- };
-
- lp5523_load_engine(chip);
-
- lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
-}
-
-static void lp5523_stop_all_engines(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
- lp5523_wait_opmode_done();
-}
-
-static void lp5523_stop_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
- };
-
- lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0);
-
- lp5523_wait_opmode_done();
-}
-
-static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
-{
- int i;
-
- for (i = 0; i < LP5523_MAX_LEDS; i++)
- lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
-}
-
static void lp5523_run_engine(struct lp55xx_chip *chip, bool start)
{
- int ret;
- u8 mode;
- u8 exec;
-
/* stop engine */
if (!start) {
- lp5523_stop_engine(chip);
- lp5523_turn_off_channels(chip);
+ lp55xx_stop_engine(chip);
+ lp55xx_turn_off_channels(chip);
return;
}
- /*
- * To run the engine,
- * operation mode and enable register should updated at the same time
- */
-
- ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode);
- if (ret)
- return;
-
- ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec);
- if (ret)
- return;
-
- /* change operation mode to RUN only when each engine is loading */
- if (LP5523_ENG1_IS_LOADING(mode)) {
- mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1;
- exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1;
- }
-
- if (LP5523_ENG2_IS_LOADING(mode)) {
- mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2;
- exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2;
- }
-
- if (LP5523_ENG3_IS_LOADING(mode)) {
- mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3;
- exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3;
- }
-
- lp55xx_write(chip, LP5523_REG_OP_MODE, mode);
- lp5523_wait_opmode_done();
-
- lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec);
+ lp55xx_run_engine_common(chip);
}
static int lp5523_init_program_engine(struct lp55xx_chip *chip)
@@ -273,7 +126,7 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
int ret;
u8 status;
/* one pattern per engine setting LED MUX start and stop addresses */
- static const u8 pattern[][LP5523_PROGRAM_LENGTH] = {
+ static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = {
{ 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
{ 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
@@ -295,9 +148,9 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
/* write LED MUX address space for each engine */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
chip->engine_idx = i;
- lp5523_load_engine_and_select_page(chip);
+ lp55xx_load_engine(chip);
- for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) {
+ for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j,
pattern[i - 1][j]);
if (ret)
@@ -322,261 +175,9 @@ static int lp5523_init_program_engine(struct lp55xx_chip *chip)
}
out:
- lp5523_stop_all_engines(chip);
- return ret;
-}
-
-static int lp5523_update_program_memory(struct lp55xx_chip *chip,
- const u8 *data, size_t size)
-{
- u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
- unsigned int cmd;
- char c[3];
- int nrchars;
- int ret;
- int offset = 0;
- int i = 0;
-
- while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
- if (ret != 1)
- goto err;
-
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto err;
-
- pattern[i] = (u8)cmd;
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto err;
-
- for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) {
- ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
- if (ret)
- return -EINVAL;
- }
-
- return size;
-
-err:
- dev_err(&chip->cl->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
-{
- const struct firmware *fw = chip->fw;
-
- if (fw->size > LP5523_PROGRAM_LENGTH) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
- return;
- }
-
- /*
- * Program memory sequence
- * 1) set engine mode to "LOAD"
- * 2) write firmware data into program memory
- */
-
- lp5523_load_engine_and_select_page(chip);
- lp5523_update_program_memory(chip, fw->data, fw->size);
-}
-
-static ssize_t show_engine_mode(struct device *dev,
- struct device_attribute *attr,
- char *buf, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
-
- switch (mode) {
- case LP55XX_ENGINE_RUN:
- return sprintf(buf, "run\n");
- case LP55XX_ENGINE_LOAD:
- return sprintf(buf, "load\n");
- case LP55XX_ENGINE_DISABLED:
- default:
- return sprintf(buf, "disabled\n");
- }
-}
-show_mode(1)
-show_mode(2)
-show_mode(3)
-
-static ssize_t store_engine_mode(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- struct lp55xx_engine *engine = &chip->engines[nr - 1];
-
- mutex_lock(&chip->lock);
-
- chip->engine_idx = nr;
-
- if (!strncmp(buf, "run", 3)) {
- lp5523_run_engine(chip, true);
- engine->mode = LP55XX_ENGINE_RUN;
- } else if (!strncmp(buf, "load", 4)) {
- lp5523_stop_engine(chip);
- lp5523_load_engine(chip);
- engine->mode = LP55XX_ENGINE_LOAD;
- } else if (!strncmp(buf, "disabled", 8)) {
- lp5523_stop_engine(chip);
- engine->mode = LP55XX_ENGINE_DISABLED;
- }
-
- mutex_unlock(&chip->lock);
-
- return len;
-}
-store_mode(1)
-store_mode(2)
-store_mode(3)
-
-static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
-{
- u16 tmp_mux = 0;
- int i;
-
- len = min_t(int, len, LP5523_MAX_LEDS);
-
- for (i = 0; i < len; i++) {
- switch (buf[i]) {
- case '1':
- tmp_mux |= (1 << i);
- break;
- case '0':
- break;
- case '\n':
- i = len;
- break;
- default:
- return -1;
- }
- }
- *mux = tmp_mux;
-
- return 0;
-}
-
-static void lp5523_mux_to_array(u16 led_mux, char *array)
-{
- int i, pos = 0;
-
- for (i = 0; i < LP5523_MAX_LEDS; i++)
- pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
-
- array[pos] = '\0';
-}
-
-static ssize_t show_engine_leds(struct device *dev,
- struct device_attribute *attr,
- char *buf, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- char mux[LP5523_MAX_LEDS + 1];
-
- lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
-
- return sprintf(buf, "%s\n", mux);
-}
-show_leds(1)
-show_leds(2)
-show_leds(3)
-
-static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
-{
- struct lp55xx_engine *engine = &chip->engines[nr - 1];
- int ret;
- static const u8 mux_page[] = {
- [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1,
- [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2,
- [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3,
- };
-
- lp5523_load_engine(chip);
-
- ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]);
- if (ret)
- return ret;
-
- ret = lp55xx_write(chip, LP5523_REG_PROG_MEM, (u8)(mux >> 8));
- if (ret)
- return ret;
-
- ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux));
- if (ret)
- return ret;
-
- engine->led_mux = mux;
- return 0;
-}
-
-static ssize_t store_engine_leds(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- struct lp55xx_engine *engine = &chip->engines[nr - 1];
- u16 mux = 0;
- ssize_t ret;
-
- if (lp5523_mux_parse(buf, &mux, len))
- return -EINVAL;
-
- mutex_lock(&chip->lock);
-
- chip->engine_idx = nr;
- ret = -EINVAL;
-
- if (engine->mode != LP55XX_ENGINE_LOAD)
- goto leave;
-
- if (lp5523_load_mux(chip, mux, nr))
- goto leave;
-
- ret = len;
-leave:
- mutex_unlock(&chip->lock);
+ lp55xx_stop_all_engine(chip);
return ret;
}
-store_leds(1)
-store_leds(2)
-store_leds(3)
-
-static ssize_t store_engine_load(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int ret;
-
- mutex_lock(&chip->lock);
-
- chip->engine_idx = nr;
- lp5523_load_engine_and_select_page(chip);
- ret = lp5523_update_program_memory(chip, buf, len);
-
- mutex_unlock(&chip->lock);
-
- return ret;
-}
-store_load(1)
-store_load(2)
-store_load(3)
static ssize_t lp5523_selftest(struct device *dev,
struct device_attribute *attr,
@@ -588,16 +189,16 @@ static ssize_t lp5523_selftest(struct device *dev,
int ret, pos = 0;
u8 status, adc, vdd, i;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
/* Check that ext clock is really in use if requested */
if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
if ((status & LP5523_EXT_CLK_USED) == 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
}
/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
@@ -605,14 +206,14 @@ static ssize_t lp5523_selftest(struct device *dev,
usleep_range(3000, 6000); /* ADC conversion time is typically 2.7 ms */
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000); /* Was not ready. Wait little bit */
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &vdd);
if (ret < 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
vdd--; /* There may be some fluctuation in measurement */
@@ -635,18 +236,18 @@ static ssize_t lp5523_selftest(struct device *dev,
usleep_range(3000, 6000);
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
if (!(status & LP5523_LEDTEST_DONE))
usleep_range(3000, 6000); /* Was not ready. Wait. */
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc);
if (ret < 0)
- goto fail;
+ return sysfs_emit(buf, "FAIL\n");
if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM)
- pos += sprintf(buf + pos, "LED %d FAIL\n",
- led->chan_nr);
+ pos += sysfs_emit_at(buf, pos, "LED %d FAIL\n",
+ led->chan_nr);
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
0x00);
@@ -656,198 +257,25 @@ static ssize_t lp5523_selftest(struct device *dev,
led->led_current);
led++;
}
- if (pos == 0)
- pos = sprintf(buf, "OK\n");
- goto release_lock;
-fail:
- pos = sprintf(buf, "FAIL\n");
-
-release_lock:
- mutex_unlock(&chip->lock);
-
- return pos;
-}
-
-#define show_fader(nr) \
-static ssize_t show_master_fader##nr(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- return show_master_fader(dev, attr, buf, nr); \
-}
-
-#define store_fader(nr) \
-static ssize_t store_master_fader##nr(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_master_fader(dev, attr, buf, len, nr); \
-}
-
-static ssize_t show_master_fader(struct device *dev,
- struct device_attribute *attr,
- char *buf, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int ret;
- u8 val;
- mutex_lock(&chip->lock);
- ret = lp55xx_read(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1, &val);
- mutex_unlock(&chip->lock);
-
- if (ret == 0)
- ret = sprintf(buf, "%u\n", val);
-
- return ret;
+ return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
}
-show_fader(1)
-show_fader(2)
-show_fader(3)
-
-static ssize_t store_master_fader(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int ret;
- unsigned long val;
-
- if (kstrtoul(buf, 0, &val))
- return -EINVAL;
-
- if (val > 0xff)
- return -EINVAL;
-
- mutex_lock(&chip->lock);
- ret = lp55xx_write(chip, LP5523_REG_MASTER_FADER_BASE + nr - 1,
- (u8)val);
- mutex_unlock(&chip->lock);
-
- if (ret == 0)
- ret = len;
- return ret;
-}
-store_fader(1)
-store_fader(2)
-store_fader(3)
-
-static ssize_t show_master_fader_leds(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int i, ret, pos = 0;
- u8 val;
-
- mutex_lock(&chip->lock);
-
- for (i = 0; i < LP5523_MAX_LEDS; i++) {
- ret = lp55xx_read(chip, LP5523_REG_LED_CTRL_BASE + i, &val);
- if (ret)
- goto leave;
-
- val = (val & LP5523_FADER_MAPPING_MASK)
- >> LP5523_FADER_MAPPING_SHIFT;
- if (val > 3) {
- ret = -EINVAL;
- goto leave;
- }
- buf[pos++] = val + '0';
- }
- buf[pos++] = '\n';
- ret = pos;
-leave:
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static ssize_t store_master_fader_leds(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
- struct lp55xx_chip *chip = led->chip;
- int i, n, ret;
- u8 val;
-
- n = min_t(int, len, LP5523_MAX_LEDS);
-
- mutex_lock(&chip->lock);
-
- for (i = 0; i < n; i++) {
- if (buf[i] >= '0' && buf[i] <= '3') {
- val = (buf[i] - '0') << LP5523_FADER_MAPPING_SHIFT;
- ret = lp55xx_update_bits(chip,
- LP5523_REG_LED_CTRL_BASE + i,
- LP5523_FADER_MAPPING_MASK,
- val);
- if (ret)
- goto leave;
- } else {
- ret = -EINVAL;
- goto leave;
- }
- }
- ret = len;
-leave:
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static int lp5523_multicolor_brightness(struct lp55xx_led *led)
-{
- struct lp55xx_chip *chip = led->chip;
- int ret;
- int i;
-
- mutex_lock(&chip->lock);
- for (i = 0; i < led->mc_cdev.num_colors; i++) {
- ret = lp55xx_write(chip,
- LP5523_REG_LED_PWM_BASE +
- led->mc_cdev.subled_info[i].channel,
- led->mc_cdev.subled_info[i].brightness);
- if (ret)
- break;
- }
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static int lp5523_led_brightness(struct lp55xx_led *led)
-{
- struct lp55xx_chip *chip = led->chip;
- int ret;
-
- mutex_lock(&chip->lock);
- ret = lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
- return ret;
-}
-
-static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
-static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
-static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
-static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds);
-static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds);
-static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds);
-static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
-static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
-static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
+LP55XX_DEV_ATTR_ENGINE_MODE(1);
+LP55XX_DEV_ATTR_ENGINE_MODE(2);
+LP55XX_DEV_ATTR_ENGINE_MODE(3);
+LP55XX_DEV_ATTR_ENGINE_LEDS(1);
+LP55XX_DEV_ATTR_ENGINE_LEDS(2);
+LP55XX_DEV_ATTR_ENGINE_LEDS(3);
+LP55XX_DEV_ATTR_ENGINE_LOAD(1);
+LP55XX_DEV_ATTR_ENGINE_LOAD(2);
+LP55XX_DEV_ATTR_ENGINE_LOAD(3);
static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
-static LP55XX_DEV_ATTR_RW(master_fader1, show_master_fader1,
- store_master_fader1);
-static LP55XX_DEV_ATTR_RW(master_fader2, show_master_fader2,
- store_master_fader2);
-static LP55XX_DEV_ATTR_RW(master_fader3, show_master_fader3,
- store_master_fader3);
-static LP55XX_DEV_ATTR_RW(master_fader_leds, show_master_fader_leds,
- store_master_fader_leds);
+LP55XX_DEV_ATTR_MASTER_FADER(1);
+LP55XX_DEV_ATTR_MASTER_FADER(2);
+LP55XX_DEV_ATTR_MASTER_FADER(3);
+static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds,
+ lp55xx_store_master_fader_leds);
static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr,
@@ -873,6 +301,16 @@ static const struct attribute_group lp5523_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5523_cfg = {
+ .reg_op_mode = {
+ .addr = LP5523_REG_OP_MODE,
+ },
+ .reg_exec = {
+ .addr = LP5523_REG_ENABLE,
+ },
+ .engine_busy = {
+ .addr = LP5523_REG_STATUS,
+ .mask = LP5523_ENGINE_BUSY,
+ },
.reset = {
.addr = LP5523_REG_RESET,
.val = LP5523_RESET,
@@ -881,100 +319,43 @@ static struct lp55xx_device_config lp5523_cfg = {
.addr = LP5523_REG_ENABLE,
.val = LP5523_ENABLE,
},
+ .prog_mem_base = {
+ .addr = LP5523_REG_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5523_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5523_REG_LED_CURRENT_BASE,
+ },
+ .reg_master_fader_base = {
+ .addr = LP5523_REG_MASTER_FADER_BASE,
+ },
+ .reg_led_ctrl_base = {
+ .addr = LP5523_REG_LED_CTRL_BASE,
+ },
+ .pages_per_engine = LP5523_PAGES_PER_ENGINE,
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
- .brightness_fn = lp5523_led_brightness,
- .multicolor_brightness_fn = lp5523_multicolor_brightness,
- .set_led_current = lp5523_set_led_current,
- .firmware_cb = lp5523_firmware_loaded,
+ .brightness_fn = lp55xx_led_brightness,
+ .multicolor_brightness_fn = lp55xx_multicolor_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp5523_run_engine,
.dev_attr_group = &lp5523_group,
};
-static int lp5523_probe(struct i2c_client *client)
-{
- const struct i2c_device_id *id = i2c_client_get_device_id(client);
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
- struct device_node *np = dev_of_node(&client->dev);
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- chip->cfg = &lp5523_cfg;
-
- if (!pdata) {
- if (np) {
- pdata = lp55xx_of_populate_pdata(&client->dev, np,
- chip);
- if (IS_ERR(pdata))
- return PTR_ERR(pdata);
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
-
- led = devm_kcalloc(&client->dev,
- pdata->num_channels, sizeof(*led), GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
-
- mutex_init(&chip->lock);
-
- i2c_set_clientdata(client, led);
-
- ret = lp55xx_init_device(chip);
- if (ret)
- goto err_init;
-
- dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
-
- ret = lp55xx_register_leds(led, chip);
- if (ret)
- goto err_out;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_out;
- }
-
- return 0;
-
-err_out:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static void lp5523_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp5523_stop_all_engines(chip);
- lp55xx_unregister_sysfs(chip);
- lp55xx_deinit_device(chip);
-}
-
static const struct i2c_device_id lp5523_id[] = {
- { "lp5523", LP5523 },
- { "lp55231", LP55231 },
+ { "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
+ { "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5523_id);
static const struct of_device_id of_lp5523_leds_match[] = {
- { .compatible = "national,lp5523", },
- { .compatible = "ti,lp55231", },
+ { .compatible = "national,lp5523", .data = &lp5523_cfg, },
+ { .compatible = "ti,lp55231", .data = &lp5523_cfg, },
{},
};
@@ -985,8 +366,8 @@ static struct i2c_driver lp5523_driver = {
.name = "lp5523x",
.of_match_table = of_lp5523_leds_match,
},
- .probe = lp5523_probe,
- .remove = lp5523_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5523_id,
};
diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c
index 39db9aeb67c5..14a4af361b26 100644
--- a/drivers/leds/leds-lp5562.c
+++ b/drivers/leds/leds-lp5562.c
@@ -7,6 +7,7 @@
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
@@ -19,7 +20,6 @@
#include "leds-lp55xx-common.h"
-#define LP5562_PROGRAM_LENGTH 32
#define LP5562_MAX_LEDS 4
/* ENABLE Register 00h */
@@ -38,21 +38,6 @@
/* OPMODE Register 01h */
#define LP5562_REG_OP_MODE 0x01
-#define LP5562_MODE_ENG1_M 0x30
-#define LP5562_MODE_ENG2_M 0x0C
-#define LP5562_MODE_ENG3_M 0x03
-#define LP5562_LOAD_ENG1 0x10
-#define LP5562_LOAD_ENG2 0x04
-#define LP5562_LOAD_ENG3 0x01
-#define LP5562_RUN_ENG1 0x20
-#define LP5562_RUN_ENG2 0x08
-#define LP5562_RUN_ENG3 0x02
-#define LP5562_ENG1_IS_LOADING(mode) \
- ((mode & LP5562_MODE_ENG1_M) == LP5562_LOAD_ENG1)
-#define LP5562_ENG2_IS_LOADING(mode) \
- ((mode & LP5562_MODE_ENG2_M) == LP5562_LOAD_ENG2)
-#define LP5562_ENG3_IS_LOADING(mode) \
- ((mode & LP5562_MODE_ENG3_M) == LP5562_LOAD_ENG3)
/* BRIGHTNESS Registers */
#define LP5562_REG_R_PWM 0x04
@@ -124,160 +109,24 @@ static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current)
lp55xx_write(led->chip, addr[led->chan_nr], led_current);
}
-static void lp5562_load_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M,
- };
-
- static const u8 val[] = {
- [LP55XX_ENGINE_1] = LP5562_LOAD_ENG1,
- [LP55XX_ENGINE_2] = LP5562_LOAD_ENG2,
- [LP55XX_ENGINE_3] = LP5562_LOAD_ENG3,
- };
-
- lp55xx_update_bits(chip, LP5562_REG_OP_MODE, mask[idx], val[idx]);
-
- lp5562_wait_opmode_done();
-}
-
-static void lp5562_stop_engine(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DISABLE);
- lp5562_wait_opmode_done();
-}
-
static void lp5562_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
- u8 mode;
- u8 exec;
/* stop engine */
if (!start) {
lp55xx_write(chip, LP5562_REG_ENABLE, LP5562_ENABLE_DEFAULT);
lp5562_wait_enable_done();
- lp5562_stop_engine(chip);
+ lp55xx_stop_all_engine(chip);
lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM);
lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT);
lp5562_wait_opmode_done();
return;
}
- /*
- * To run the engine,
- * operation mode and enable register should updated at the same time
- */
-
- ret = lp55xx_read(chip, LP5562_REG_OP_MODE, &mode);
- if (ret)
- return;
-
- ret = lp55xx_read(chip, LP5562_REG_ENABLE, &exec);
- if (ret)
- return;
-
- /* change operation mode to RUN only when each engine is loading */
- if (LP5562_ENG1_IS_LOADING(mode)) {
- mode = (mode & ~LP5562_MODE_ENG1_M) | LP5562_RUN_ENG1;
- exec = (exec & ~LP5562_EXEC_ENG1_M) | LP5562_RUN_ENG1;
- }
-
- if (LP5562_ENG2_IS_LOADING(mode)) {
- mode = (mode & ~LP5562_MODE_ENG2_M) | LP5562_RUN_ENG2;
- exec = (exec & ~LP5562_EXEC_ENG2_M) | LP5562_RUN_ENG2;
- }
-
- if (LP5562_ENG3_IS_LOADING(mode)) {
- mode = (mode & ~LP5562_MODE_ENG3_M) | LP5562_RUN_ENG3;
- exec = (exec & ~LP5562_EXEC_ENG3_M) | LP5562_RUN_ENG3;
- }
-
- lp55xx_write(chip, LP5562_REG_OP_MODE, mode);
- lp5562_wait_opmode_done();
-
- lp55xx_update_bits(chip, LP5562_REG_ENABLE, LP5562_EXEC_M, exec);
- lp5562_wait_enable_done();
-}
-
-static int lp5562_update_firmware(struct lp55xx_chip *chip,
- const u8 *data, size_t size)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- u8 pattern[LP5562_PROGRAM_LENGTH] = {0};
- static const u8 addr[] = {
- [LP55XX_ENGINE_1] = LP5562_REG_PROG_MEM_ENG1,
- [LP55XX_ENGINE_2] = LP5562_REG_PROG_MEM_ENG2,
- [LP55XX_ENGINE_3] = LP5562_REG_PROG_MEM_ENG3,
- };
- unsigned cmd;
- char c[3];
- int program_size;
- int nrchars;
- int offset = 0;
- int ret;
- int i;
-
- /* clear program memory before updating */
- for (i = 0; i < LP5562_PROGRAM_LENGTH; i++)
- lp55xx_write(chip, addr[idx] + i, 0);
-
- i = 0;
- while ((offset < size - 1) && (i < LP5562_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
- if (ret != 1)
- goto err;
-
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto err;
-
- pattern[i] = (u8)cmd;
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto err;
-
- program_size = i;
- for (i = 0; i < program_size; i++)
- lp55xx_write(chip, addr[idx] + i, pattern[i]);
-
- return 0;
-
-err:
- dev_err(&chip->cl->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static void lp5562_firmware_loaded(struct lp55xx_chip *chip)
-{
- const struct firmware *fw = chip->fw;
-
- /*
- * the firmware is encoded in ascii hex character, with 2 chars
- * per byte
- */
- if (fw->size > (LP5562_PROGRAM_LENGTH * 2)) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
- return;
- }
-
- /*
- * Program memory sequence
- * 1) set engine mode to "LOAD"
- * 2) write firmware data into program memory
- */
-
- lp5562_load_engine(chip);
- lp5562_update_firmware(chip, fw->data, fw->size);
+ ret = lp55xx_run_engine_common(chip);
+ if (!ret)
+ lp5562_wait_enable_done();
}
static int lp5562_post_init_device(struct lp55xx_chip *chip)
@@ -312,6 +161,30 @@ static int lp5562_post_init_device(struct lp55xx_chip *chip)
return 0;
}
+static int lp5562_multicolor_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ static const u8 addr[] = {
+ LP5562_REG_R_PWM,
+ LP5562_REG_G_PWM,
+ LP5562_REG_B_PWM,
+ LP5562_REG_W_PWM,
+ };
+ int ret;
+ int i;
+
+ guard(mutex)(&chip->lock);
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ ret = lp55xx_write(chip,
+ addr[led->mc_cdev.subled_info[i].channel],
+ led->mc_cdev.subled_info[i].brightness);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
static int lp5562_led_brightness(struct lp55xx_led *led)
{
struct lp55xx_chip *chip = led->chip;
@@ -323,9 +196,9 @@ static int lp5562_led_brightness(struct lp55xx_led *led)
};
int ret;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
ret = lp55xx_write(chip, addr[led->chan_nr], led->brightness);
- mutex_unlock(&chip->lock);
return ret;
}
@@ -348,9 +221,9 @@ static void lp5562_write_program_memory(struct lp55xx_chip *chip,
/* check the size of program count */
static inline bool _is_pc_overflow(struct lp55xx_predef_pattern *ptn)
{
- return ptn->size_r >= LP5562_PROGRAM_LENGTH ||
- ptn->size_g >= LP5562_PROGRAM_LENGTH ||
- ptn->size_b >= LP5562_PROGRAM_LENGTH;
+ return ptn->size_r >= LP55xx_BYTES_PER_PAGE ||
+ ptn->size_g >= LP55xx_BYTES_PER_PAGE ||
+ ptn->size_b >= LP55xx_BYTES_PER_PAGE;
}
static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
@@ -369,7 +242,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
return -EINVAL;
}
- lp5562_stop_engine(chip);
+ lp55xx_stop_all_engine(chip);
/* Set LED map as RGB */
lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB);
@@ -377,7 +250,7 @@ static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
/* Load engines */
for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
chip->engine_idx = i;
- lp5562_load_engine(chip);
+ lp55xx_load_engine(chip);
}
/* Clear program registers */
@@ -420,9 +293,9 @@ static ssize_t lp5562_store_pattern(struct device *dev,
if (mode > num_patterns || !ptn)
return -EINVAL;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
ret = lp5562_run_predef_led_pattern(chip, mode);
- mutex_unlock(&chip->lock);
if (ret)
return ret;
@@ -472,9 +345,9 @@ static ssize_t lp5562_store_engine_mux(struct device *dev,
return -EINVAL;
}
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val);
- mutex_unlock(&chip->lock);
return len;
}
@@ -495,6 +368,12 @@ static const struct attribute_group lp5562_group = {
/* Chip specific configurations */
static struct lp55xx_device_config lp5562_cfg = {
.max_channel = LP5562_MAX_LEDS,
+ .reg_op_mode = {
+ .addr = LP5562_REG_OP_MODE,
+ },
+ .reg_exec = {
+ .addr = LP5562_REG_ENABLE,
+ },
.reset = {
.addr = LP5562_REG_RESET,
.val = LP5562_RESET,
@@ -503,94 +382,26 @@ static struct lp55xx_device_config lp5562_cfg = {
.addr = LP5562_REG_ENABLE,
.val = LP5562_ENABLE_DEFAULT,
},
+ .prog_mem_base = {
+ .addr = LP5562_REG_PROG_MEM_ENG1,
+ },
.post_init_device = lp5562_post_init_device,
.set_led_current = lp5562_set_led_current,
.brightness_fn = lp5562_led_brightness,
+ .multicolor_brightness_fn = lp5562_multicolor_brightness,
.run_engine = lp5562_run_engine,
- .firmware_cb = lp5562_firmware_loaded,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
.dev_attr_group = &lp5562_group,
};
-static int lp5562_probe(struct i2c_client *client)
-{
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
- struct device_node *np = dev_of_node(&client->dev);
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- chip->cfg = &lp5562_cfg;
-
- if (!pdata) {
- if (np) {
- pdata = lp55xx_of_populate_pdata(&client->dev, np,
- chip);
- if (IS_ERR(pdata))
- return PTR_ERR(pdata);
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
-
-
- led = devm_kcalloc(&client->dev,
- pdata->num_channels, sizeof(*led), GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
-
- mutex_init(&chip->lock);
-
- i2c_set_clientdata(client, led);
-
- ret = lp55xx_init_device(chip);
- if (ret)
- goto err_init;
-
- ret = lp55xx_register_leds(led, chip);
- if (ret)
- goto err_out;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_out;
- }
-
- return 0;
-
-err_out:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static void lp5562_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp5562_stop_engine(chip);
-
- lp55xx_unregister_sysfs(chip);
- lp55xx_deinit_device(chip);
-}
-
static const struct i2c_device_id lp5562_id[] = {
- { "lp5562", 0 },
+ { "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5562_id);
static const struct of_device_id of_lp5562_leds_match[] = {
- { .compatible = "ti,lp5562", },
+ { .compatible = "ti,lp5562", .data = &lp5562_cfg, },
{},
};
@@ -601,8 +412,8 @@ static struct i2c_driver lp5562_driver = {
.name = "lp5562",
.of_match_table = of_lp5562_leds_match,
},
- .probe = lp5562_probe,
- .remove = lp5562_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5562_id,
};
diff --git a/drivers/leds/leds-lp5569.c b/drivers/leds/leds-lp5569.c
new file mode 100644
index 000000000000..786f2aa35319
--- /dev/null
+++ b/drivers/leds/leds-lp5569.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_data/leds-lp55xx.h>
+#include <linux/slab.h>
+#include <dt-bindings/leds/leds-lp55xx.h>
+
+#include "leds-lp55xx-common.h"
+
+#define LP5569_MAX_LEDS 9
+
+/* Memory is used like this:
+ * 0x00 engine 1 program (4 pages)
+ * 0x40 engine 2 program (4 pages)
+ * 0x80 engine 3 program (4 pages)
+ * 0xc0 engine 1 muxing info (1 page)
+ * 0xd0 engine 2 muxing info (1 page)
+ * 0xe0 engine 3 muxing info (1 page)
+ */
+#define LP5569_PAGES_PER_ENGINE 4
+
+#define LP5569_REG_ENABLE 0x00
+#define LP5569_ENABLE BIT(6)
+
+#define LP5569_REG_EXEC_CTRL 0x01
+#define LP5569_MODE_ENG_SHIFT 2
+
+#define LP5569_REG_OP_MODE 0x02
+#define LP5569_EXEC_ENG_SHIFT 2
+
+#define LP5569_REG_ENABLE_LEDS_MSB 0x04
+#define LP5569_REG_ENABLE_LEDS_LSB 0x05
+#define LP5569_REG_LED_CTRL_BASE 0x07
+#define LP5569_FADER_MAPPING_MASK GENMASK(7, 5)
+#define LP5569_REG_LED_PWM_BASE 0x16
+#define LP5569_REG_LED_CURRENT_BASE 0x22
+#define LP5569_REG_MISC 0x2F
+#define LP5569_AUTO_INC BIT(6)
+#define LP5569_PWR_SAVE BIT(5)
+#define LP5569_CP_MODE_MASK GENMASK(4, 3)
+#define LP5569_PWM_PWR_SAVE BIT(2)
+#define LP5569_INTERNAL_CLK BIT(0)
+#define LP5569_REG_MISC2 0x33
+#define LP5569_LED_SHORT_TEST BIT(4)
+#define LP5569_LED_OPEN_TEST BIT(3)
+#define LP5569_REG_STATUS 0x3C
+#define LP5569_MASK_BUSY BIT(7)
+#define LP5569_STARTUP_BUSY BIT(6)
+#define LP5569_ENGINE_BUSY BIT(5)
+#define LP5569_ENGINE1_INT BIT(2)
+#define LP5569_ENGINE2_INT BIT(1)
+#define LP5569_ENGINE3_INT BIT(0)
+#define LP5569_ENG_STATUS_MASK (LP5569_ENGINE1_INT | LP5569_ENGINE2_INT | \
+ LP5569_ENGINE3_INT)
+#define LP5569_REG_IO_CONTROL 0x3D
+#define LP5569_CLK_OUTPUT BIT(3)
+#define LP5569_REG_RESET 0x3F
+#define LP5569_RESET 0xFF
+#define LP5569_REG_MASTER_FADER_BASE 0x46
+#define LP5569_REG_CH1_PROG_START 0x4B
+#define LP5569_REG_CH2_PROG_START 0x4C
+#define LP5569_REG_CH3_PROG_START 0x4D
+#define LP5569_REG_PROG_PAGE_SEL 0x4F
+#define LP5569_REG_PROG_MEM 0x50
+#define LP5569_REG_LED_FAULT1 0x81
+#define LP5569_LED_FAULT8 BIT(0)
+#define LP5569_REG_LED_FAULT2 0x82
+#define LP5569_LED_FAULT7 BIT(7)
+#define LP5569_LED_FAULT6 BIT(6)
+#define LP5569_LED_FAULT5 BIT(5)
+#define LP5569_LED_FAULT4 BIT(4)
+#define LP5569_LED_FAULT3 BIT(3)
+#define LP5569_LED_FAULT2 BIT(2)
+#define LP5569_LED_FAULT1 BIT(1)
+#define LP5569_LED_FAULT0 BIT(0)
+
+#define LP5569_ENG1_PROG_ADDR 0x0
+#define LP5569_ENG2_PROG_ADDR 0x40
+#define LP5569_ENG3_PROG_ADDR 0x80
+#define LP5569_ENG1_MUX_ADDR 0xc0
+#define LP5569_ENG2_MUX_ADDR 0xd0
+#define LP5569_ENG3_MUX_ADDR 0xe0
+
+#define LP5569_STARTUP_SLEEP 500
+
+#define LEDn_STATUS_FAULT(n, status) ((status) >> (n) & BIT(0))
+
+#define LP5569_DEFAULT_CONFIG \
+ (LP5569_AUTO_INC | LP5569_PWR_SAVE | LP5569_PWM_PWR_SAVE)
+
+static void lp5569_run_engine(struct lp55xx_chip *chip, bool start)
+{
+ if (!start) {
+ lp55xx_stop_engine(chip);
+ lp55xx_turn_off_channels(chip);
+ return;
+ }
+
+ lp55xx_run_engine_common(chip);
+}
+
+static int lp5569_init_program_engine(struct lp55xx_chip *chip)
+{
+ int i;
+ int j;
+ int ret;
+ u8 status;
+ /* Precompiled pattern per ENGINE setting LED MUX start and stop addresses */
+ static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = {
+ { 0x9c, LP5569_ENG1_MUX_ADDR, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ { 0x9c, LP5569_ENG2_MUX_ADDR, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ { 0x9c, LP5569_ENG3_MUX_ADDR, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ };
+
+ /* Setup each ENGINE program start address */
+ ret = lp55xx_write(chip, LP5569_REG_CH1_PROG_START, LP5569_ENG1_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5569_REG_CH2_PROG_START, LP5569_ENG2_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5569_REG_CH3_PROG_START, LP5569_ENG3_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ /* Write precompiled pattern for LED MUX address space for each ENGINE */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ chip->engine_idx = i;
+ lp55xx_load_engine(chip);
+
+ for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
+ ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + j,
+ pattern[i - 1][j]);
+ if (ret)
+ goto out;
+ }
+ }
+
+ lp5569_run_engine(chip, true);
+
+ /* Let the programs run for couple of ms and check the engine status */
+ usleep_range(3000, 6000);
+ lp55xx_read(chip, LP5569_REG_STATUS, &status);
+ status = FIELD_GET(LP5569_ENG_STATUS_MASK, status);
+
+ if (status != LP5569_ENG_STATUS_MASK) {
+ dev_err(&chip->cl->dev,
+ "could not configure LED engine, status = 0x%.2x\n",
+ status);
+ ret = -EINVAL;
+ }
+
+out:
+ lp55xx_stop_all_engine(chip);
+ return ret;
+}
+
+static int lp5569_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
+ u8 val;
+
+ val = LP5569_DEFAULT_CONFIG;
+ val |= FIELD_PREP(LP5569_CP_MODE_MASK, chip->pdata->charge_pump_mode);
+ ret = lp55xx_write(chip, LP5569_REG_MISC, val);
+ if (ret)
+ return ret;
+
+ if (chip->pdata->clock_mode == LP55XX_CLOCK_INT) {
+ /* Internal clock MUST be configured before CLK output */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ LP5569_INTERNAL_CLK,
+ LP5569_INTERNAL_CLK);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_update_bits(chip, LP5569_REG_IO_CONTROL,
+ LP5569_CLK_OUTPUT,
+ LP5569_CLK_OUTPUT);
+ if (ret)
+ return ret;
+ }
+
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ return ret;
+
+ read_poll_timeout(lp55xx_read, ret, !(val & LP5569_STARTUP_BUSY),
+ LP5569_STARTUP_SLEEP, LP5569_STARTUP_SLEEP * 10, false,
+ chip, LP5569_REG_STATUS, &val);
+
+ return lp5569_init_program_engine(chip);
+}
+
+static ssize_t lp5569_led_open_test(struct lp55xx_led *led, char *buf)
+{
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ bool leds_fault[LP5569_MAX_LEDS];
+ struct lp55xx_led *led_tmp = led;
+ int i, ret, pos = 0;
+ u8 status;
+
+ /* Set in STANDBY state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
+ if (ret)
+ goto exit;
+
+ /* Wait 1ms for device to enter STANDBY state */
+ usleep_range(1000, 2000);
+
+ /* Set Charge Pump to 1.5x */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BOOST),
+ LP5569_CP_MODE_MASK);
+ if (ret)
+ goto exit;
+
+ /* Enable LED Open Test */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
+ LP5569_LED_OPEN_TEST);
+ if (ret)
+ goto exit;
+
+ /* Put Device in NORMAL state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to enter NORMAL state */
+ usleep_range(500, 750);
+
+ /* Enable LED and set to 100% brightness */
+ for (i = 0; i < pdata->num_channels; i++) {
+ ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ led_tmp++;
+ }
+
+ /* Wait 500 us for device to fill status regs */
+ usleep_range(500, 750);
+
+ /* Parse status led fault 1 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 8; i++)
+ leds_fault[i] = !!((status >> i) & 0x1);
+
+ /* Parse status led fault 2 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 1; i++)
+ leds_fault[i + 8] = !!((status >> i) & 0x1);
+
+ /* Report LED fault */
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ if (leds_fault[led_tmp->chan_nr])
+ pos += sysfs_emit_at(buf, pos, "LED %d OPEN FAIL\n",
+ led_tmp->chan_nr);
+
+ led_tmp++;
+ }
+
+ ret = pos;
+
+exit:
+ /* Disable LED Open Test */
+ lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, 0);
+
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
+
+ led_tmp++;
+ }
+
+ return ret;
+}
+
+static ssize_t lp5569_led_short_test(struct lp55xx_led *led, char *buf)
+{
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ bool leds_fault[LP5569_MAX_LEDS];
+ struct lp55xx_led *led_tmp = led;
+ int i, ret, pos = 0;
+ u8 status;
+
+ /* Set in STANDBY state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
+ if (ret)
+ goto exit;
+
+ /* Wait 1ms for device to enter STANDBY state */
+ usleep_range(1000, 2000);
+
+ /* Set Charge Pump to 1x */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BYPASS),
+ LP5569_CP_MODE_MASK);
+ if (ret)
+ goto exit;
+
+ /* Enable LED and set to 100% brightness and current to 100% (25.5mA) */
+ for (i = 0; i < pdata->num_channels; i++) {
+ ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ ret = lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ led_tmp++;
+ }
+
+ /* Put Device in NORMAL state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to enter NORMAL state */
+ usleep_range(500, 750);
+
+ /* Enable LED Shorted Test */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
+ LP5569_LED_SHORT_TEST);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to fill status regs */
+ usleep_range(500, 750);
+
+ /* Parse status led fault 1 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 8; i++)
+ leds_fault[i] = !!LEDn_STATUS_FAULT(i, status);
+
+ /* Parse status led fault 2 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 1; i++)
+ leds_fault[i + 8] = !!LEDn_STATUS_FAULT(i, status);
+
+ /* Report LED fault */
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ if (leds_fault[led_tmp->chan_nr])
+ pos += sysfs_emit_at(buf, pos, "LED %d SHORTED FAIL\n",
+ led_tmp->chan_nr);
+
+ led_tmp++;
+ }
+
+ ret = pos;
+
+exit:
+ /* Disable LED Shorted Test */
+ lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_SHORT_TEST, 0);
+
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
+
+ led_tmp++;
+ }
+
+ return ret;
+}
+
+static ssize_t lp5569_selftest(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int i, pos = 0;
+
+ guard(mutex)(&chip->lock);
+
+ /* Test LED Open */
+ pos = lp5569_led_open_test(led, buf);
+ if (pos < 0)
+ return sprintf(buf, "FAIL\n");
+
+ /* Test LED Shorted */
+ pos += lp5569_led_short_test(led, buf);
+ if (pos < 0)
+ return sprintf(buf, "FAIL\n");
+
+ for (i = 0; i < chip->pdata->num_channels; i++) {
+ /* Restore current */
+ lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led->chan_nr,
+ led->led_current);
+
+ /* Restore brightness */
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led->chan_nr,
+ led->brightness);
+ led++;
+ }
+
+ return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
+}
+
+LP55XX_DEV_ATTR_ENGINE_MODE(1);
+LP55XX_DEV_ATTR_ENGINE_MODE(2);
+LP55XX_DEV_ATTR_ENGINE_MODE(3);
+LP55XX_DEV_ATTR_ENGINE_LEDS(1);
+LP55XX_DEV_ATTR_ENGINE_LEDS(2);
+LP55XX_DEV_ATTR_ENGINE_LEDS(3);
+LP55XX_DEV_ATTR_ENGINE_LOAD(1);
+LP55XX_DEV_ATTR_ENGINE_LOAD(2);
+LP55XX_DEV_ATTR_ENGINE_LOAD(3);
+static LP55XX_DEV_ATTR_RO(selftest, lp5569_selftest);
+LP55XX_DEV_ATTR_MASTER_FADER(1);
+LP55XX_DEV_ATTR_MASTER_FADER(2);
+LP55XX_DEV_ATTR_MASTER_FADER(3);
+static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds,
+ lp55xx_store_master_fader_leds);
+
+static struct attribute *lp5569_attributes[] = {
+ &dev_attr_engine1_mode.attr,
+ &dev_attr_engine2_mode.attr,
+ &dev_attr_engine3_mode.attr,
+ &dev_attr_engine1_load.attr,
+ &dev_attr_engine2_load.attr,
+ &dev_attr_engine3_load.attr,
+ &dev_attr_engine1_leds.attr,
+ &dev_attr_engine2_leds.attr,
+ &dev_attr_engine3_leds.attr,
+ &dev_attr_selftest.attr,
+ &dev_attr_master_fader1.attr,
+ &dev_attr_master_fader2.attr,
+ &dev_attr_master_fader3.attr,
+ &dev_attr_master_fader_leds.attr,
+ NULL,
+};
+
+static const struct attribute_group lp5569_group = {
+ .attrs = lp5569_attributes,
+};
+
+/* Chip specific configurations */
+static struct lp55xx_device_config lp5569_cfg = {
+ .reg_op_mode = {
+ .addr = LP5569_REG_OP_MODE,
+ .shift = LP5569_MODE_ENG_SHIFT,
+ },
+ .reg_exec = {
+ .addr = LP5569_REG_EXEC_CTRL,
+ .shift = LP5569_EXEC_ENG_SHIFT,
+ },
+ .reset = {
+ .addr = LP5569_REG_RESET,
+ .val = LP5569_RESET,
+ },
+ .enable = {
+ .addr = LP5569_REG_ENABLE,
+ .val = LP5569_ENABLE,
+ },
+ .prog_mem_base = {
+ .addr = LP5569_REG_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5569_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5569_REG_LED_CURRENT_BASE,
+ },
+ .reg_master_fader_base = {
+ .addr = LP5569_REG_MASTER_FADER_BASE,
+ },
+ .reg_led_ctrl_base = {
+ .addr = LP5569_REG_LED_CTRL_BASE,
+ },
+ .pages_per_engine = LP5569_PAGES_PER_ENGINE,
+ .max_channel = LP5569_MAX_LEDS,
+ .post_init_device = lp5569_post_init_device,
+ .brightness_fn = lp55xx_led_brightness,
+ .multicolor_brightness_fn = lp55xx_multicolor_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
+ .run_engine = lp5569_run_engine,
+ .dev_attr_group = &lp5569_group,
+};
+
+static const struct i2c_device_id lp5569_id[] = {
+ { "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, lp5569_id);
+
+static const struct of_device_id of_lp5569_leds_match[] = {
+ { .compatible = "ti,lp5569", .data = &lp5569_cfg, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_lp5569_leds_match);
+
+static struct i2c_driver lp5569_driver = {
+ .driver = {
+ .name = "lp5569",
+ .of_match_table = of_lp5569_leds_match,
+ },
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
+ .id_table = lp5569_id,
+};
+
+module_i2c_driver(lp5569_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("LP5569 LED engine");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index 8e7074f0fee0..e71456a56ab8 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -9,10 +9,13 @@
* Derived from leds-lp5521.c, leds-lp5523.c
*/
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
+#include <linux/iopoll.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_data/leds-lp55xx.h>
@@ -22,6 +25,50 @@
#include "leds-lp55xx-common.h"
+/* OP MODE require at least 153 us to clear regs */
+#define LP55XX_CMD_SLEEP 200
+
+#define LP55xx_PROGRAM_PAGES 16
+#define LP55xx_MAX_PROGRAM_LENGTH (LP55xx_BYTES_PER_PAGE * 4) /* 128 bytes (4 pages) */
+
+/*
+ * Program Memory Operations
+ * Same Mask for each engine for both mode and exec
+ * ENG1 GENMASK(3, 2)
+ * ENG2 GENMASK(5, 4)
+ * ENG3 GENMASK(7, 6)
+ */
+#define LP55xx_MODE_DISABLE_ALL_ENG 0x0
+#define LP55xx_MODE_ENG_MASK GENMASK(1, 0)
+#define LP55xx_MODE_DISABLE_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x0)
+#define LP55xx_MODE_LOAD_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x1)
+#define LP55xx_MODE_RUN_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x2)
+#define LP55xx_MODE_HALT_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x3)
+
+#define LP55xx_MODE_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
+#define LP55xx_MODE_ENGn_MASK(n, shift) (LP55xx_MODE_ENG_MASK << LP55xx_MODE_ENGn_SHIFT(n, shift))
+#define LP55xx_MODE_ENGn_GET(n, mode, shift) \
+ (((mode) >> LP55xx_MODE_ENGn_SHIFT(n, shift)) & LP55xx_MODE_ENG_MASK)
+
+#define LP55xx_EXEC_ENG_MASK GENMASK(1, 0)
+#define LP55xx_EXEC_HOLD_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x0)
+#define LP55xx_EXEC_STEP_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x1)
+#define LP55xx_EXEC_RUN_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x2)
+#define LP55xx_EXEC_ONCE_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x3)
+
+#define LP55xx_EXEC_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
+#define LP55xx_EXEC_ENGn_MASK(n, shift) (LP55xx_EXEC_ENG_MASK << LP55xx_EXEC_ENGn_SHIFT(n, shift))
+
+/* Memory Page Selection */
+#define LP55xx_REG_PROG_PAGE_SEL 0x4f
+/* If supported, each ENGINE have an equal amount of pages offset from page 0 */
+#define LP55xx_PAGE_OFFSET(n, pages) (((n) - 1) * (pages))
+
+#define LED_ACTIVE(mux, led) (!!((mux) & (0x0001 << (led))))
+
+/* MASTER FADER common property */
+#define LP55xx_FADER_MAPPING_MASK GENMASK(7, 6)
+
/* External clock rate */
#define LP55XX_CLK_32K 32768
@@ -40,9 +87,259 @@ static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
return container_of(mc_cdev, struct lp55xx_led, mc_cdev);
}
+static void lp55xx_wait_opmode_done(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int __always_unused ret;
+ u8 val;
+
+ /*
+ * Recent chip supports BUSY bit for engine.
+ * Check support by checking if val is not 0.
+ * For legacy device, sleep at least 153 us.
+ */
+ if (cfg->engine_busy.val) {
+ read_poll_timeout(lp55xx_read, ret, !(val & cfg->engine_busy.mask),
+ LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 10, false,
+ chip, cfg->engine_busy.addr, &val);
+ } else {
+ usleep_range(LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 2);
+ }
+}
+
+void lp55xx_stop_all_engine(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, LP55xx_MODE_DISABLE_ALL_ENG);
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_all_engine);
+
+void lp55xx_load_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask, val;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ val = LP55xx_MODE_LOAD_ENG << LP55xx_MODE_ENGn_SHIFT(idx, cfg->reg_op_mode.shift);
+
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, val);
+ lp55xx_wait_opmode_done(chip);
+
+ /* Setup PAGE if supported (pages_per_engine not 0)*/
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine));
+}
+EXPORT_SYMBOL_GPL(lp55xx_load_engine);
+
+int lp55xx_run_engine_common(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mode, exec;
+ int i, ret;
+
+ /* To run the engine, both OP MODE and EXEC needs to be put in RUN mode */
+ ret = lp55xx_read(chip, cfg->reg_op_mode.addr, &mode);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_read(chip, cfg->reg_exec.addr, &exec);
+ if (ret)
+ return ret;
+
+ /* Switch to RUN only for engine that were put in LOAD previously */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ if (LP55xx_MODE_ENGn_GET(i, mode, cfg->reg_op_mode.shift) != LP55xx_MODE_LOAD_ENG)
+ continue;
+
+ mode &= ~LP55xx_MODE_ENGn_MASK(i, cfg->reg_op_mode.shift);
+ mode |= LP55xx_MODE_RUN_ENG << LP55xx_MODE_ENGn_SHIFT(i, cfg->reg_op_mode.shift);
+ exec &= ~LP55xx_EXEC_ENGn_MASK(i, cfg->reg_exec.shift);
+ exec |= LP55xx_EXEC_RUN_ENG << LP55xx_EXEC_ENGn_SHIFT(i, cfg->reg_exec.shift);
+ }
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, mode);
+ lp55xx_wait_opmode_done(chip);
+ lp55xx_write(chip, cfg->reg_exec.addr, exec);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lp55xx_run_engine_common);
+
+int lp55xx_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 pattern[LP55xx_MAX_PROGRAM_LENGTH] = { };
+ u8 start_addr = cfg->prog_mem_base.addr;
+ int page, i = 0, offset = 0;
+ int program_length, ret;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ while ((offset < size - 1) && (i < program_length)) {
+ unsigned int cmd;
+ int nrchars;
+ char c[3];
+
+ /* separate sscanfs because length is working only for %s */
+ ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
+ if (ret != 1)
+ goto err;
+
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
+
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
+
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
+
+ /*
+ * For legacy LED chip with no page support, engine base address are
+ * one after another at offset of 32.
+ * For LED chip that support page, PAGE is already set in load_engine.
+ */
+ if (!cfg->pages_per_engine)
+ start_addr += LP55xx_BYTES_PER_PAGE * idx;
+
+ for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) {
+ /* Write to the next page each 32 bytes (if supported) */
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine) + page);
+
+ for (i = 0; i < LP55xx_BYTES_PER_PAGE; i++) {
+ ret = lp55xx_write(chip, start_addr + i,
+ pattern[i + (page * LP55xx_BYTES_PER_PAGE)]);
+ if (ret)
+ return -EINVAL;
+ }
+ }
+
+ return size;
+
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(lp55xx_update_program_memory);
+
+void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ const struct firmware *fw = chip->fw;
+ int program_length;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ /*
+ * the firmware is encoded in ascii hex character, with 2 chars
+ * per byte
+ */
+ if (fw->size > program_length * 2) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
+
+ /*
+ * Program memory sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
+
+ lp55xx_load_engine(chip);
+ lp55xx_update_program_memory(chip, fw->data, fw->size);
+}
+EXPORT_SYMBOL_GPL(lp55xx_firmware_loaded_cb);
+
+int lp55xx_led_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_write(chip, cfg->reg_led_pwm_base.addr + led->chan_nr,
+ led->brightness);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_led_brightness);
+
+int lp55xx_multicolor_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ int i;
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ ret = lp55xx_write(chip,
+ cfg->reg_led_pwm_base.addr +
+ led->mc_cdev.subled_info[i].channel,
+ led->mc_cdev.subled_info[i].brightness);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_multicolor_brightness);
+
+void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ led->led_current = led_current;
+ lp55xx_write(led->chip, cfg->reg_led_current_base.addr + led->chan_nr,
+ led_current);
+}
+EXPORT_SYMBOL_GPL(lp55xx_set_led_current);
+
+void lp55xx_turn_off_channels(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i;
+
+ for (i = 0; i < cfg->max_channel; i++)
+ lp55xx_write(chip, cfg->reg_led_pwm_base.addr + i, 0);
+}
+EXPORT_SYMBOL_GPL(lp55xx_turn_off_channels);
+
+void lp55xx_stop_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, 0);
+
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_engine);
+
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->reset.addr;
u8 val = cfg->reset.val;
@@ -52,7 +349,7 @@ static void lp55xx_reset_device(struct lp55xx_chip *chip)
static int lp55xx_detect_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->enable.addr;
u8 val = cfg->enable.val;
int ret;
@@ -75,7 +372,7 @@ static int lp55xx_detect_device(struct lp55xx_chip *chip)
static int lp55xx_post_init_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
if (!cfg->post_init_device)
return 0;
@@ -109,9 +406,9 @@ static ssize_t led_current_store(struct device *dev,
if (!chip->cfg->set_led_current)
return len;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
chip->cfg->set_led_current(led, (u8)curr);
- mutex_unlock(&chip->lock);
return len;
}
@@ -140,7 +437,7 @@ static int lp55xx_set_mc_brightness(struct led_classdev *cdev,
{
struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
struct lp55xx_led *led = mcled_cdev_to_led(mc_dev);
- struct lp55xx_device_config *cfg = led->chip->cfg;
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
led_mc_calc_color_components(&led->mc_cdev, brightness);
return cfg->multicolor_brightness_fn(led);
@@ -151,7 +448,7 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
- struct lp55xx_device_config *cfg = led->chip->cfg;
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
led->brightness = (u8)brightness;
return cfg->brightness_fn(led);
@@ -161,7 +458,7 @@ static int lp55xx_init_led(struct lp55xx_led *led,
struct lp55xx_chip *chip, int chan)
{
struct lp55xx_platform_data *pdata = chip->pdata;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
struct device *dev = &chip->cl->dev;
int max_channel = cfg->max_channel;
struct mc_subled *mc_led_info;
@@ -246,14 +543,12 @@ static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
}
/* handling firmware data is chip dependent */
- mutex_lock(&chip->lock);
-
- chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
- chip->fw = fw;
- if (chip->cfg->firmware_cb)
- chip->cfg->firmware_cb(chip);
-
- mutex_unlock(&chip->lock);
+ scoped_guard(mutex, &chip->lock) {
+ chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
+ chip->fw = fw;
+ if (chip->cfg->firmware_cb)
+ chip->cfg->firmware_cb(chip);
+ }
/* firmware should be released for other channel use */
release_firmware(chip->fw);
@@ -270,8 +565,8 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
}
static ssize_t select_engine_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+ struct device_attribute *attr,
+ char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -280,8 +575,8 @@ static ssize_t select_engine_show(struct device *dev,
}
static ssize_t select_engine_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+ struct device_attribute *attr,
+ const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -297,10 +592,10 @@ static ssize_t select_engine_store(struct device *dev,
case LP55XX_ENGINE_1:
case LP55XX_ENGINE_2:
case LP55XX_ENGINE_3:
- mutex_lock(&chip->lock);
- chip->engine_idx = val;
- ret = lp55xx_request_firmware(chip);
- mutex_unlock(&chip->lock);
+ scoped_guard(mutex, &chip->lock) {
+ chip->engine_idx = val;
+ ret = lp55xx_request_firmware(chip);
+ }
break;
default:
dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
@@ -322,8 +617,8 @@ static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
}
static ssize_t run_engine_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+ struct device_attribute *attr,
+ const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -339,9 +634,9 @@ static ssize_t run_engine_store(struct device *dev,
return len;
}
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
lp55xx_run_engine(chip, true);
- mutex_unlock(&chip->lock);
return len;
}
@@ -349,6 +644,279 @@ static ssize_t run_engine_store(struct device *dev,
static DEVICE_ATTR_RW(select_engine);
static DEVICE_ATTR_WO(run_engine);
+ssize_t lp55xx_show_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
+
+ switch (mode) {
+ case LP55XX_ENGINE_RUN:
+ return sysfs_emit(buf, "run\n");
+ case LP55XX_ENGINE_LOAD:
+ return sysfs_emit(buf, "load\n");
+ case LP55XX_ENGINE_DISABLED:
+ default:
+ return sysfs_emit(buf, "disabled\n");
+ }
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_engine_mode);
+
+ssize_t lp55xx_store_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+
+ if (!strncmp(buf, "run", 3)) {
+ cfg->run_engine(chip, true);
+ engine->mode = LP55XX_ENGINE_RUN;
+ } else if (!strncmp(buf, "load", 4)) {
+ lp55xx_stop_engine(chip);
+ lp55xx_load_engine(chip);
+ engine->mode = LP55XX_ENGINE_LOAD;
+ } else if (!strncmp(buf, "disabled", 8)) {
+ lp55xx_stop_engine(chip);
+ engine->mode = LP55XX_ENGINE_DISABLED;
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_mode);
+
+ssize_t lp55xx_store_engine_load(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+ lp55xx_load_engine(chip);
+ ret = lp55xx_update_program_memory(chip, buf, len);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_load);
+
+static int lp55xx_mux_parse(struct lp55xx_chip *chip, const char *buf,
+ u16 *mux, size_t len)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u16 tmp_mux = 0;
+ int i;
+
+ len = min_t(int, len, cfg->max_channel);
+
+ for (i = 0; i < len; i++) {
+ switch (buf[i]) {
+ case '1':
+ tmp_mux |= (1 << i);
+ break;
+ case '0':
+ break;
+ case '\n':
+ i = len;
+ break;
+ default:
+ return -1;
+ }
+ }
+ *mux = tmp_mux;
+
+ return 0;
+}
+
+ssize_t lp55xx_show_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ unsigned int led_active;
+ int i, pos = 0;
+
+ for (i = 0; i < cfg->max_channel; i++) {
+ led_active = LED_ACTIVE(chip->engines[nr - 1].led_mux, i);
+ pos += sysfs_emit_at(buf, pos, "%x", led_active);
+ }
+
+ pos += sysfs_emit_at(buf, pos, "\n");
+
+ return pos;
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_engine_leds);
+
+static int lp55xx_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
+{
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mux_page;
+ int ret;
+
+ lp55xx_load_engine(chip);
+
+ /* Derive the MUX page offset by starting at the end of the ENGINE pages */
+ mux_page = cfg->pages_per_engine * LP55XX_ENGINE_MAX + (nr - 1);
+ ret = lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, mux_page);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, cfg->prog_mem_base.addr, (u8)(mux >> 8));
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, cfg->prog_mem_base.addr + 1, (u8)(mux));
+ if (ret)
+ return ret;
+
+ engine->led_mux = mux;
+ return 0;
+}
+
+ssize_t lp55xx_store_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+ u16 mux = 0;
+
+ if (lp55xx_mux_parse(chip, buf, &mux, len))
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+
+ if (engine->mode != LP55XX_ENGINE_LOAD)
+ return -EINVAL;
+
+ if (lp55xx_load_mux(chip, mux, nr))
+ return -EINVAL;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_leds);
+
+ssize_t lp55xx_show_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ u8 val;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_read(chip, cfg->reg_master_fader_base.addr + nr - 1, &val);
+
+ return ret ? ret : sysfs_emit(buf, "%u\n", val);
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_master_fader);
+
+ssize_t lp55xx_store_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ if (val > 0xff)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_write(chip, cfg->reg_master_fader_base.addr + nr - 1,
+ (u8)val);
+
+ return ret ? ret : len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_master_fader);
+
+ssize_t lp55xx_show_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i, ret, pos = 0;
+ u8 val;
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < cfg->max_channel; i++) {
+ ret = lp55xx_read(chip, cfg->reg_led_ctrl_base.addr + i, &val);
+ if (ret)
+ return ret;
+
+ val = FIELD_GET(LP55xx_FADER_MAPPING_MASK, val);
+ if (val > FIELD_MAX(LP55xx_FADER_MAPPING_MASK)) {
+ return -EINVAL;
+ }
+ buf[pos++] = val + '0';
+ }
+ buf[pos++] = '\n';
+
+ return pos;
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_master_fader_leds);
+
+ssize_t lp55xx_store_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i, n, ret;
+ u8 val;
+
+ n = min_t(int, len, cfg->max_channel);
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < n; i++) {
+ if (buf[i] >= '0' && buf[i] <= '3') {
+ val = (buf[i] - '0') << __bf_shf(LP55xx_FADER_MAPPING_MASK);
+ ret = lp55xx_update_bits(chip,
+ cfg->reg_led_ctrl_base.addr + i,
+ LP55xx_FADER_MAPPING_MASK,
+ val);
+ if (ret)
+ return ret;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_master_fader_leds);
+
static struct attribute *lp55xx_engine_attributes[] = {
&dev_attr_select_engine.attr,
&dev_attr_run_engine.attr,
@@ -397,24 +965,16 @@ EXPORT_SYMBOL_GPL(lp55xx_update_bits);
bool lp55xx_is_extclk_used(struct lp55xx_chip *chip)
{
struct clk *clk;
- int err;
- clk = devm_clk_get(&chip->cl->dev, "32k_clk");
+ clk = devm_clk_get_enabled(&chip->cl->dev, "32k_clk");
if (IS_ERR(clk))
goto use_internal_clk;
- err = clk_prepare_enable(clk);
- if (err)
- goto use_internal_clk;
-
- if (clk_get_rate(clk) != LP55XX_CLK_32K) {
- clk_disable_unprepare(clk);
+ if (clk_get_rate(clk) != LP55XX_CLK_32K)
goto use_internal_clk;
- }
dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K);
- chip->clk = clk;
return true;
use_internal_clk:
@@ -423,10 +983,18 @@ use_internal_clk:
}
EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used);
-int lp55xx_init_device(struct lp55xx_chip *chip)
+static void lp55xx_deinit_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_platform_data *pdata = chip->pdata;
+
+ if (pdata->enable_gpiod)
+ gpiod_set_value(pdata->enable_gpiod, 0);
+}
+
+static int lp55xx_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata;
- struct lp55xx_device_config *cfg;
+ const struct lp55xx_device_config *cfg;
struct device *dev = &chip->cl->dev;
int ret = 0;
@@ -476,24 +1044,11 @@ err_post_init:
err:
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_init_device);
-void lp55xx_deinit_device(struct lp55xx_chip *chip)
+static int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
-
- if (chip->clk)
- clk_disable_unprepare(chip->clk);
-
- if (pdata->enable_gpiod)
- gpiod_set_value(pdata->enable_gpiod, 0);
-}
-EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
-
-int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
-{
- struct lp55xx_platform_data *pdata = chip->pdata;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
int num_channels = pdata->num_channels;
struct lp55xx_led *each;
u8 led_current;
@@ -530,12 +1085,11 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
err_init_led:
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_register_leds);
-int lp55xx_register_sysfs(struct lp55xx_chip *chip)
+static int lp55xx_register_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
if (!cfg->run_engine || !cfg->firmware_cb)
@@ -549,19 +1103,17 @@ dev_specific_attrs:
return cfg->dev_attr_group ?
sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0;
}
-EXPORT_SYMBOL_GPL(lp55xx_register_sysfs);
-void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
+static void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
if (cfg->dev_attr_group)
sysfs_remove_group(&dev->kobj, cfg->dev_attr_group);
sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group);
}
-EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
static int lp55xx_parse_common_child(struct device_node *np,
struct lp55xx_led_config *cfg,
@@ -580,9 +1132,6 @@ static int lp55xx_parse_common_child(struct device_node *np,
if (ret)
return ret;
- if (*chan_nr < 0 || *chan_nr > cfg->max_channel)
- return -EINVAL;
-
return 0;
}
@@ -610,16 +1159,13 @@ static int lp55xx_parse_multi_led(struct device_node *np,
struct lp55xx_led_config *cfg,
int child_number)
{
- struct device_node *child;
int num_colors = 0, ret;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
ret = lp55xx_parse_multi_led_child(child, cfg, child_number,
num_colors);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
num_colors++;
}
@@ -654,9 +1200,9 @@ static int lp55xx_parse_logical_led(struct device_node *np,
return ret;
}
-struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
- struct device_node *np,
- struct lp55xx_chip *chip)
+static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
+ struct device_node *np,
+ struct lp55xx_chip *chip)
{
struct device_node *child;
struct lp55xx_platform_data *pdata;
@@ -713,7 +1259,92 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
return pdata;
}
-EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata);
+
+int lp55xx_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ int program_length, ret;
+ struct lp55xx_chip *chip;
+ struct lp55xx_led *led;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct device_node *np = dev_of_node(&client->dev);
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->cfg = i2c_get_match_data(client);
+
+ if (!pdata) {
+ if (np) {
+ pdata = lp55xx_of_populate_pdata(&client->dev, np,
+ chip);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ } else {
+ dev_err(&client->dev, "no platform data\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Validate max program page */
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (chip->cfg->pages_per_engine)
+ program_length *= chip->cfg->pages_per_engine;
+
+ /* support a max of 128bytes */
+ if (program_length > LP55xx_MAX_PROGRAM_LENGTH) {
+ dev_err(&client->dev, "invalid pages_per_engine configured\n");
+ return -EINVAL;
+ }
+
+ led = devm_kcalloc(&client->dev,
+ pdata->num_channels, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ chip->cl = client;
+ chip->pdata = pdata;
+
+ mutex_init(&chip->lock);
+
+ i2c_set_clientdata(client, led);
+
+ ret = lp55xx_init_device(chip);
+ if (ret)
+ goto err_init;
+
+ dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
+
+ ret = lp55xx_register_leds(led, chip);
+ if (ret)
+ goto err_out;
+
+ ret = lp55xx_register_sysfs(chip);
+ if (ret) {
+ dev_err(&client->dev, "registering sysfs failed\n");
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ lp55xx_deinit_device(chip);
+err_init:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_probe);
+
+void lp55xx_remove(struct i2c_client *client)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(client);
+ struct lp55xx_chip *chip = led->chip;
+
+ lp55xx_stop_all_engine(chip);
+ lp55xx_unregister_sysfs(chip);
+ lp55xx_deinit_device(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_remove);
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP55xx Common Driver");
diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
index 2f38c5b33830..8fd64ec40919 100644
--- a/drivers/leds/leds-lp55xx-common.h
+++ b/drivers/leds/leds-lp55xx-common.h
@@ -14,6 +14,8 @@
#include <linux/led-class-multicolor.h>
+#define LP55xx_BYTES_PER_PAGE 32 /* bytes */
+
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
@@ -35,45 +37,62 @@ enum lp55xx_engine_mode {
#define LP55XX_DEV_ATTR_WO(name, store) \
DEVICE_ATTR(name, S_IWUSR, NULL, store)
-#define show_mode(nr) \
+#define LP55XX_DEV_ATTR_ENGINE_MODE(nr) \
static ssize_t show_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
+ struct device_attribute *attr, \
+ char *buf) \
{ \
- return show_engine_mode(dev, attr, buf, nr); \
-}
-
-#define store_mode(nr) \
+ return lp55xx_show_engine_mode(dev, attr, buf, nr); \
+} \
static ssize_t store_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
{ \
- return store_engine_mode(dev, attr, buf, len, nr); \
-}
+ return lp55xx_store_engine_mode(dev, attr, buf, len, nr); \
+} \
+static LP55XX_DEV_ATTR_RW(engine##nr##_mode, show_engine##nr##_mode, \
+ store_engine##nr##_mode)
-#define show_leds(nr) \
+#define LP55XX_DEV_ATTR_ENGINE_LEDS(nr) \
static ssize_t show_engine##nr##_leds(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
+ struct device_attribute *attr, \
+ char *buf) \
{ \
- return show_engine_leds(dev, attr, buf, nr); \
-}
-
-#define store_leds(nr) \
-static ssize_t store_engine##nr##_leds(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_leds(dev, attr, buf, len, nr); \
-}
-
-#define store_load(nr) \
+ return lp55xx_show_engine_leds(dev, attr, buf, nr); \
+} \
+static ssize_t store_engine##nr##_leds(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return lp55xx_store_engine_leds(dev, attr, buf, len, nr); \
+} \
+static LP55XX_DEV_ATTR_RW(engine##nr##_leds, show_engine##nr##_leds, \
+ store_engine##nr##_leds)
+
+#define LP55XX_DEV_ATTR_ENGINE_LOAD(nr) \
static ssize_t store_engine##nr##_load(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return lp55xx_store_engine_load(dev, attr, buf, len, nr); \
+} \
+static LP55XX_DEV_ATTR_WO(engine##nr##_load, store_engine##nr##_load)
+
+#define LP55XX_DEV_ATTR_MASTER_FADER(nr) \
+static ssize_t show_master_fader##nr(struct device *dev, \
struct device_attribute *attr, \
- const char *buf, size_t len) \
+ char *buf) \
{ \
- return store_engine_load(dev, attr, buf, len, nr); \
-}
+ return lp55xx_show_master_fader(dev, attr, buf, nr); \
+} \
+static ssize_t store_master_fader##nr(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return lp55xx_store_master_fader(dev, attr, buf, len, nr); \
+} \
+static LP55XX_DEV_ATTR_RW(master_fader##nr, show_master_fader##nr, \
+ store_master_fader##nr)
struct lp55xx_led;
struct lp55xx_chip;
@@ -81,17 +100,31 @@ struct lp55xx_chip;
/*
* struct lp55xx_reg
* @addr : Register address
- * @val : Register value
+ * @val : Register value (can also used as mask or shift)
*/
struct lp55xx_reg {
u8 addr;
- u8 val;
+ union {
+ u8 val;
+ u8 mask;
+ u8 shift;
+ };
};
/*
* struct lp55xx_device_config
+ * @reg_op_mode : Chip specific OP MODE reg addr
+ * @engine_busy : Chip specific engine busy
+ * (if not supported 153 us sleep)
* @reset : Chip specific reset command
* @enable : Chip specific enable command
+ * @prog_mem_base : Chip specific base reg address for chip SMEM programming
+ * @reg_led_pwm_base : Chip specific base reg address for LED PWM conf
+ * @reg_led_current_base : Chip specific base reg address for LED current conf
+ * @reg_master_fader_base : Chip specific base reg address for master fader base
+ * @reg_led_ctrl_base : Chip specific base reg address for LED ctrl base
+ * @pages_per_engine : Assigned pages for each engine
+ * (if not set chip doesn't support pages)
* @max_channel : Maximum number of channels
* @post_init_device : Chip specific initialization code
* @brightness_fn : Brightness function
@@ -102,8 +135,17 @@ struct lp55xx_reg {
* @dev_attr_group : Device specific attributes
*/
struct lp55xx_device_config {
+ const struct lp55xx_reg reg_op_mode; /* addr, shift */
+ const struct lp55xx_reg reg_exec; /* addr, shift */
+ const struct lp55xx_reg engine_busy; /* addr, mask */
const struct lp55xx_reg reset;
const struct lp55xx_reg enable;
+ const struct lp55xx_reg prog_mem_base;
+ const struct lp55xx_reg reg_led_pwm_base;
+ const struct lp55xx_reg reg_led_current_base;
+ const struct lp55xx_reg reg_master_fader_base;
+ const struct lp55xx_reg reg_led_ctrl_base;
+ const int pages_per_engine;
const int max_channel;
/* define if the device has specific initialization process */
@@ -151,11 +193,10 @@ struct lp55xx_engine {
*/
struct lp55xx_chip {
struct i2c_client *cl;
- struct clk *clk;
struct lp55xx_platform_data *pdata;
struct mutex lock; /* lock for user-space interface */
int num_leds;
- struct lp55xx_device_config *cfg;
+ const struct lp55xx_device_config *cfg;
enum lp55xx_engine_index engine_idx;
struct lp55xx_engine engines[LP55XX_ENGINE_MAX];
const struct firmware *fw;
@@ -191,21 +232,50 @@ extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg,
/* external clock detection */
extern bool lp55xx_is_extclk_used(struct lp55xx_chip *chip);
-/* common device init/deinit functions */
-extern int lp55xx_init_device(struct lp55xx_chip *chip);
-extern void lp55xx_deinit_device(struct lp55xx_chip *chip);
-
-/* common LED class device functions */
-extern int lp55xx_register_leds(struct lp55xx_led *led,
- struct lp55xx_chip *chip);
+/* common chip functions */
+extern void lp55xx_stop_all_engine(struct lp55xx_chip *chip);
+extern void lp55xx_load_engine(struct lp55xx_chip *chip);
+extern int lp55xx_run_engine_common(struct lp55xx_chip *chip);
+extern int lp55xx_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size);
+extern void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip);
+extern int lp55xx_led_brightness(struct lp55xx_led *led);
+extern int lp55xx_multicolor_brightness(struct lp55xx_led *led);
+extern void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current);
+extern void lp55xx_turn_off_channels(struct lp55xx_chip *chip);
+extern void lp55xx_stop_engine(struct lp55xx_chip *chip);
-/* common device attributes functions */
-extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
-extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
+/* common probe/remove function */
+extern int lp55xx_probe(struct i2c_client *client);
+extern void lp55xx_remove(struct i2c_client *client);
-/* common device tree population function */
-extern struct lp55xx_platform_data
-*lp55xx_of_populate_pdata(struct device *dev, struct device_node *np,
- struct lp55xx_chip *chip);
+/* common sysfs function */
+extern ssize_t lp55xx_show_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr);
+extern ssize_t lp55xx_store_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr);
+extern ssize_t lp55xx_store_engine_load(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr);
+extern ssize_t lp55xx_show_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr);
+extern ssize_t lp55xx_store_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr);
+extern ssize_t lp55xx_show_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr);
+extern ssize_t lp55xx_store_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr);
+extern ssize_t lp55xx_show_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf);
+extern ssize_t lp55xx_store_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len);
#endif /* _LEDS_LP55XX_COMMON_H */
diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c
index ac50aa88939a..ee4ff4586bc0 100644
--- a/drivers/leds/leds-lp8501.c
+++ b/drivers/leds/leds-lp8501.c
@@ -20,27 +20,14 @@
#include "leds-lp55xx-common.h"
-#define LP8501_PROGRAM_LENGTH 32
+#define LP8501_PAGES_PER_ENGINE 1
#define LP8501_MAX_LEDS 9
/* Registers */
#define LP8501_REG_ENABLE 0x00
#define LP8501_ENABLE BIT(6)
-#define LP8501_EXEC_M 0x3F
-#define LP8501_EXEC_ENG1_M 0x30
-#define LP8501_EXEC_ENG2_M 0x0C
-#define LP8501_EXEC_ENG3_M 0x03
-#define LP8501_RUN_ENG1 0x20
-#define LP8501_RUN_ENG2 0x08
-#define LP8501_RUN_ENG3 0x02
#define LP8501_REG_OP_MODE 0x01
-#define LP8501_MODE_ENG1_M 0x30
-#define LP8501_MODE_ENG2_M 0x0C
-#define LP8501_MODE_ENG3_M 0x03
-#define LP8501_LOAD_ENG1 0x10
-#define LP8501_LOAD_ENG2 0x04
-#define LP8501_LOAD_ENG3 0x01
#define LP8501_REG_PWR_CONFIG 0x05
#define LP8501_PWR_CONFIG_M 0x03
@@ -58,35 +45,14 @@
#define LP8501_INT_CLK BIT(0)
#define LP8501_DEFAULT_CFG (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE)
+#define LP8501_REG_STATUS 0x3A
+#define LP8501_ENGINE_BUSY BIT(4)
+
#define LP8501_REG_RESET 0x3D
#define LP8501_RESET 0xFF
-#define LP8501_REG_PROG_PAGE_SEL 0x4F
-#define LP8501_PAGE_ENG1 0
-#define LP8501_PAGE_ENG2 1
-#define LP8501_PAGE_ENG3 2
-
#define LP8501_REG_PROG_MEM 0x50
-#define LP8501_ENG1_IS_LOADING(mode) \
- ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1)
-#define LP8501_ENG2_IS_LOADING(mode) \
- ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2)
-#define LP8501_ENG3_IS_LOADING(mode) \
- ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3)
-
-static inline void lp8501_wait_opmode_done(void)
-{
- usleep_range(1000, 2000);
-}
-
-static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current)
-{
- led->led_current = led_current;
- lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr,
- led_current);
-}
-
static int lp8501_post_init_device(struct lp55xx_chip *chip)
{
int ret;
@@ -113,178 +79,30 @@ static int lp8501_post_init_device(struct lp55xx_chip *chip)
LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel);
}
-static void lp8501_load_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- static const u8 mask[] = {
- [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M,
- };
-
- static const u8 val[] = {
- [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1,
- [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2,
- [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3,
- };
-
- static const u8 page_sel[] = {
- [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1,
- [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2,
- [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3,
- };
-
- lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]);
-
- lp8501_wait_opmode_done();
-
- lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]);
-}
-
-static void lp8501_stop_engine(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP8501_REG_OP_MODE, 0);
- lp8501_wait_opmode_done();
-}
-
-static void lp8501_turn_off_channels(struct lp55xx_chip *chip)
-{
- int i;
-
- for (i = 0; i < LP8501_MAX_LEDS; i++)
- lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0);
-}
-
static void lp8501_run_engine(struct lp55xx_chip *chip, bool start)
{
- int ret;
- u8 mode;
- u8 exec;
-
/* stop engine */
if (!start) {
- lp8501_stop_engine(chip);
- lp8501_turn_off_channels(chip);
- return;
- }
-
- /*
- * To run the engine,
- * operation mode and enable register should updated at the same time
- */
-
- ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode);
- if (ret)
- return;
-
- ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec);
- if (ret)
- return;
-
- /* change operation mode to RUN only when each engine is loading */
- if (LP8501_ENG1_IS_LOADING(mode)) {
- mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1;
- exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1;
- }
-
- if (LP8501_ENG2_IS_LOADING(mode)) {
- mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2;
- exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2;
- }
-
- if (LP8501_ENG3_IS_LOADING(mode)) {
- mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3;
- exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3;
- }
-
- lp55xx_write(chip, LP8501_REG_OP_MODE, mode);
- lp8501_wait_opmode_done();
-
- lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec);
-}
-
-static int lp8501_update_program_memory(struct lp55xx_chip *chip,
- const u8 *data, size_t size)
-{
- u8 pattern[LP8501_PROGRAM_LENGTH] = {0};
- unsigned cmd;
- char c[3];
- int update_size;
- int nrchars;
- int offset = 0;
- int ret;
- int i;
-
- /* clear program memory before updating */
- for (i = 0; i < LP8501_PROGRAM_LENGTH; i++)
- lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0);
-
- i = 0;
- while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
- if (ret != 1)
- goto err;
-
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto err;
-
- pattern[i] = (u8)cmd;
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto err;
-
- update_size = i;
- for (i = 0; i < update_size; i++)
- lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]);
-
- return 0;
-
-err:
- dev_err(&chip->cl->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static void lp8501_firmware_loaded(struct lp55xx_chip *chip)
-{
- const struct firmware *fw = chip->fw;
-
- if (fw->size > LP8501_PROGRAM_LENGTH) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
+ lp55xx_stop_all_engine(chip);
+ lp55xx_turn_off_channels(chip);
return;
}
- /*
- * Program memory sequence
- * 1) set engine mode to "LOAD"
- * 2) write firmware data into program memory
- */
-
- lp8501_load_engine(chip);
- lp8501_update_program_memory(chip, fw->data, fw->size);
-}
-
-static int lp8501_led_brightness(struct lp55xx_led *led)
-{
- struct lp55xx_chip *chip = led->chip;
- int ret;
-
- mutex_lock(&chip->lock);
- ret = lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
-
- return ret;
+ lp55xx_run_engine_common(chip);
}
/* Chip specific configurations */
static struct lp55xx_device_config lp8501_cfg = {
+ .reg_op_mode = {
+ .addr = LP8501_REG_OP_MODE,
+ },
+ .reg_exec = {
+ .addr = LP8501_REG_ENABLE,
+ },
+ .engine_busy = {
+ .addr = LP8501_REG_STATUS,
+ .mask = LP8501_ENGINE_BUSY,
+ },
.reset = {
.addr = LP8501_REG_RESET,
.val = LP8501_RESET,
@@ -293,95 +111,32 @@ static struct lp55xx_device_config lp8501_cfg = {
.addr = LP8501_REG_ENABLE,
.val = LP8501_ENABLE,
},
+ .prog_mem_base = {
+ .addr = LP8501_REG_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP8501_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP8501_REG_LED_CURRENT_BASE,
+ },
+ .pages_per_engine = LP8501_PAGES_PER_ENGINE,
.max_channel = LP8501_MAX_LEDS,
.post_init_device = lp8501_post_init_device,
- .brightness_fn = lp8501_led_brightness,
- .set_led_current = lp8501_set_led_current,
- .firmware_cb = lp8501_firmware_loaded,
+ .brightness_fn = lp55xx_led_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
.run_engine = lp8501_run_engine,
};
-static int lp8501_probe(struct i2c_client *client)
-{
- const struct i2c_device_id *id = i2c_client_get_device_id(client);
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
- struct device_node *np = dev_of_node(&client->dev);
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- chip->cfg = &lp8501_cfg;
-
- if (!pdata) {
- if (np) {
- pdata = lp55xx_of_populate_pdata(&client->dev, np,
- chip);
- if (IS_ERR(pdata))
- return PTR_ERR(pdata);
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
-
- led = devm_kcalloc(&client->dev,
- pdata->num_channels, sizeof(*led), GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
-
- mutex_init(&chip->lock);
-
- i2c_set_clientdata(client, led);
-
- ret = lp55xx_init_device(chip);
- if (ret)
- goto err_init;
-
- dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
-
- ret = lp55xx_register_leds(led, chip);
- if (ret)
- goto err_out;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_out;
- }
-
- return 0;
-
-err_out:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static void lp8501_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp8501_stop_engine(chip);
- lp55xx_unregister_sysfs(chip);
- lp55xx_deinit_device(chip);
-}
-
static const struct i2c_device_id lp8501_id[] = {
- { "lp8501", 0 },
+ { "lp8501", .driver_data = (kernel_ulong_t)&lp8501_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp8501_id);
static const struct of_device_id of_lp8501_leds_match[] = {
- { .compatible = "ti,lp8501", },
+ { .compatible = "ti,lp8501", .data = &lp8501_cfg, },
{},
};
@@ -392,8 +147,8 @@ static struct i2c_driver lp8501_driver = {
.name = "lp8501",
.of_match_table = of_lp8501_leds_match,
},
- .probe = lp8501_probe,
- .remove = lp8501_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp8501_id,
};
diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c
index 19b621012e58..995f2adf8569 100644
--- a/drivers/leds/leds-lp8860.c
+++ b/drivers/leds/leds-lp8860.c
@@ -265,7 +265,7 @@ static int lp8860_init(struct lp8860_led *led)
goto out;
}
- reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs) / sizeof(lp8860_eeprom_disp_regs[0]);
+ reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs);
for (i = 0; i < reg_count; i++) {
ret = regmap_write(led->eeprom_regmap,
lp8860_eeprom_disp_regs[i].reg,
@@ -331,7 +331,6 @@ static const struct regmap_config lp8860_regmap_config = {
.max_register = LP8860_EEPROM_UNLOCK,
.reg_defaults = lp8860_reg_defs,
.num_reg_defaults = ARRAY_SIZE(lp8860_reg_defs),
- .cache_type = REGCACHE_NONE,
};
static const struct reg_default lp8860_eeprom_defs[] = {
@@ -369,7 +368,6 @@ static const struct regmap_config lp8860_eeprom_regmap_config = {
.max_register = LP8860_EEPROM_REG_24,
.reg_defaults = lp8860_eeprom_defs,
.num_reg_defaults = ARRAY_SIZE(lp8860_eeprom_defs),
- .cache_type = REGCACHE_NONE,
};
static int lp8860_probe(struct i2c_client *client)
@@ -459,7 +457,7 @@ static void lp8860_remove(struct i2c_client *client)
}
static const struct i2c_device_id lp8860_id[] = {
- { "lp8860", 0 },
+ { "lp8860" },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp8860_id);
diff --git a/drivers/leds/leds-lp8864.c b/drivers/leds/leds-lp8864.c
new file mode 100644
index 000000000000..3afd729d2f8a
--- /dev/null
+++ b/drivers/leds/leds-lp8864.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI LP8864/LP8866 4/6 Channel LED Driver
+ *
+ * Copyright (C) 2024 Siemens AG
+ *
+ * Based on LP8860 driver by Dan Murphy <dmurphy@ti.com>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define LP8864_BRT_CONTROL 0x00
+#define LP8864_USER_CONFIG1 0x04
+#define LP8864_BRT_MODE_MASK GENMASK(9, 8)
+#define LP8864_BRT_MODE_REG BIT(9) /* Brightness control by DISPLAY_BRT reg */
+#define LP8864_SUPPLY_STATUS 0x0e
+#define LP8864_BOOST_STATUS 0x10
+#define LP8864_LED_STATUS 0x12
+#define LP8864_LED_STATUS_WR_MASK GENMASK(14, 9) /* Writeable bits in the LED_STATUS reg */
+
+/* Textual meaning for status bits, starting from bit 1 */
+static const char *const lp8864_supply_status_msg[] = {
+ "Vin under-voltage fault",
+ "Vin over-voltage fault",
+ "Vdd under-voltage fault",
+ "Vin over-current fault",
+ "Missing charge pump fault",
+ "Charge pump fault",
+ "Missing boost sync fault",
+ "CRC error fault ",
+};
+
+/* Textual meaning for status bits, starting from bit 1 */
+static const char *const lp8864_boost_status_msg[] = {
+ "Boost OVP low fault",
+ "Boost OVP high fault",
+ "Boost over-current fault",
+ "Missing boost FSET resistor fault",
+ "Missing MODE SEL resistor fault",
+ "Missing LED resistor fault",
+ "ISET resistor short to ground fault",
+ "Thermal shutdown fault",
+};
+
+/* Textual meaning for every register bit */
+static const char *const lp8864_led_status_msg[] = {
+ "LED 1 fault",
+ "LED 2 fault",
+ "LED 3 fault",
+ "LED 4 fault",
+ "LED 5 fault",
+ "LED 6 fault",
+ "LED open fault",
+ "LED internal short fault",
+ "LED short to GND fault",
+ NULL, NULL, NULL,
+ "Invalid string configuration fault",
+ NULL,
+ "I2C time out fault",
+};
+
+/**
+ * struct lp8864_led
+ * @client: Pointer to the I2C client
+ * @led_dev: led class device pointer
+ * @regmap: Devices register map
+ * @led_status_mask: Helps to report LED fault only once
+ */
+struct lp8864_led {
+ struct i2c_client *client;
+ struct led_classdev led_dev;
+ struct regmap *regmap;
+ u16 led_status_mask;
+};
+
+static int lp8864_fault_check(struct lp8864_led *led)
+{
+ int ret, i;
+ unsigned int val;
+
+ ret = regmap_read(led->regmap, LP8864_SUPPLY_STATUS, &val);
+ if (ret)
+ goto err;
+
+ /* Odd bits are status bits, even bits are clear bits */
+ for (i = 0; i < ARRAY_SIZE(lp8864_supply_status_msg); i++)
+ if (val & BIT(i * 2 + 1))
+ dev_warn(&led->client->dev, "%s\n", lp8864_supply_status_msg[i]);
+
+ /*
+ * Clear bits have an index preceding the corresponding Status bits;
+ * both have to be written "1" simultaneously to clear the corresponding
+ * Status bit.
+ */
+ if (val)
+ ret = regmap_write(led->regmap, LP8864_SUPPLY_STATUS, val >> 1 | val);
+ if (ret)
+ goto err;
+
+ ret = regmap_read(led->regmap, LP8864_BOOST_STATUS, &val);
+ if (ret)
+ goto err;
+
+ /* Odd bits are status bits, even bits are clear bits */
+ for (i = 0; i < ARRAY_SIZE(lp8864_boost_status_msg); i++)
+ if (val & BIT(i * 2 + 1))
+ dev_warn(&led->client->dev, "%s\n", lp8864_boost_status_msg[i]);
+
+ if (val)
+ ret = regmap_write(led->regmap, LP8864_BOOST_STATUS, val >> 1 | val);
+ if (ret)
+ goto err;
+
+ ret = regmap_read(led->regmap, LP8864_LED_STATUS, &val);
+ if (ret)
+ goto err;
+
+ /*
+ * Clear already reported faults that maintain their value until device
+ * power-down
+ */
+ val &= ~led->led_status_mask;
+
+ for (i = 0; i < ARRAY_SIZE(lp8864_led_status_msg); i++)
+ if (lp8864_led_status_msg[i] && val & BIT(i))
+ dev_warn(&led->client->dev, "%s\n", lp8864_led_status_msg[i]);
+
+ /*
+ * Mark those which maintain their value until device power-down as
+ * "already reported"
+ */
+ led->led_status_mask |= val & ~LP8864_LED_STATUS_WR_MASK;
+
+ /*
+ * Only bits 14, 12, 10 have to be cleared here, but others are RO,
+ * we don't care what we write to them.
+ */
+ if (val & LP8864_LED_STATUS_WR_MASK)
+ ret = regmap_write(led->regmap, LP8864_LED_STATUS, val >> 1 | val);
+ if (ret)
+ goto err;
+
+ return 0;
+
+err:
+ dev_err(&led->client->dev, "Failed to read/clear faults (%pe)\n", ERR_PTR(ret));
+
+ return ret;
+}
+
+static int lp8864_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
+ /* Scale 0..LED_FULL into 16-bit HW brightness */
+ unsigned int val = brt_val * 0xffff / LED_FULL;
+ int ret;
+
+ ret = lp8864_fault_check(led);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(led->regmap, LP8864_BRT_CONTROL, val);
+ if (ret)
+ dev_err(&led->client->dev, "Failed to write brightness value\n");
+
+ return ret;
+}
+
+static enum led_brightness lp8864_brightness_get(struct led_classdev *led_cdev)
+{
+ struct lp8864_led *led = container_of(led_cdev, struct lp8864_led, led_dev);
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(led->regmap, LP8864_BRT_CONTROL, &val);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed to read brightness value\n");
+ return ret;
+ }
+
+ /* Scale 16-bit HW brightness into 0..LED_FULL */
+ return val * LED_FULL / 0xffff;
+}
+
+static const struct regmap_config lp8864_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_LITTLE,
+};
+
+static void lp8864_disable_gpio(void *data)
+{
+ struct gpio_desc *gpio = data;
+
+ gpiod_set_value(gpio, 0);
+}
+
+static int lp8864_probe(struct i2c_client *client)
+{
+ int ret;
+ struct lp8864_led *led;
+ struct device_node *np = dev_of_node(&client->dev);
+ struct device_node *child_node;
+ struct led_init_data init_data = {};
+ struct gpio_desc *enable_gpio;
+
+ led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ child_node = of_get_next_available_child(np, NULL);
+ if (!child_node) {
+ dev_err(&client->dev, "No LED function defined\n");
+ return -EINVAL;
+ }
+
+ ret = devm_regulator_get_enable_optional(&client->dev, "vled");
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(&client->dev, ret, "Failed to enable vled regulator\n");
+
+ enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_HIGH);
+ if (IS_ERR(enable_gpio))
+ return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
+ "Failed to get enable GPIO\n");
+
+ ret = devm_add_action_or_reset(&client->dev, lp8864_disable_gpio, enable_gpio);
+ if (ret)
+ return ret;
+
+ led->client = client;
+ led->led_dev.brightness_set_blocking = lp8864_brightness_set;
+ led->led_dev.brightness_get = lp8864_brightness_get;
+
+ led->regmap = devm_regmap_init_i2c(client, &lp8864_regmap_config);
+ if (IS_ERR(led->regmap))
+ return dev_err_probe(&client->dev, PTR_ERR(led->regmap),
+ "Failed to allocate regmap\n");
+
+ /* Control brightness by DISPLAY_BRT register */
+ ret = regmap_update_bits(led->regmap, LP8864_USER_CONFIG1, LP8864_BRT_MODE_MASK,
+ LP8864_BRT_MODE_REG);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed to set brightness control mode\n");
+ return ret;
+ }
+
+ ret = lp8864_fault_check(led);
+ if (ret)
+ return ret;
+
+ init_data.fwnode = of_fwnode_handle(child_node);
+ init_data.devicename = "lp8864";
+ init_data.default_label = ":display_cluster";
+
+ ret = devm_led_classdev_register_ext(&client->dev, &led->led_dev, &init_data);
+ if (ret)
+ dev_err(&client->dev, "Failed to register LED device (%pe)\n", ERR_PTR(ret));
+
+ return ret;
+}
+
+static const struct i2c_device_id lp8864_id[] = {
+ { "lp8864" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lp8864_id);
+
+static const struct of_device_id of_lp8864_leds_match[] = {
+ { .compatible = "ti,lp8864" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_lp8864_leds_match);
+
+static struct i2c_driver lp8864_driver = {
+ .driver = {
+ .name = "lp8864",
+ .of_match_table = of_lp8864_leds_match,
+ },
+ .probe = lp8864_probe,
+ .id_table = lp8864_id,
+};
+module_i2c_driver(lp8864_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP8864/LP8866 LED driver");
+MODULE_AUTHOR("Alexander Sverdlin <alexander.sverdlin@siemens.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c
index 56a584311581..285074c53b23 100644
--- a/drivers/leds/leds-max5970.c
+++ b/drivers/leds/leds-max5970.c
@@ -45,7 +45,7 @@ static int max5970_led_set_brightness(struct led_classdev *cdev,
static int max5970_led_probe(struct platform_device *pdev)
{
- struct fwnode_handle *led_node, *child;
+ struct fwnode_handle *child;
struct device *dev = &pdev->dev;
struct regmap *regmap;
struct max5970_led *ddata;
@@ -55,7 +55,8 @@ static int max5970_led_probe(struct platform_device *pdev)
if (!regmap)
return -ENODEV;
- led_node = device_get_named_child_node(dev->parent, "leds");
+ struct fwnode_handle *led_node __free(fwnode_handle) =
+ device_get_named_child_node(dev->parent, "leds");
if (!led_node)
return -ENODEV;
diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c
index 1eeac56b0014..f8c47078a3bb 100644
--- a/drivers/leds/leds-max77650.c
+++ b/drivers/leds/leds-max77650.c
@@ -62,7 +62,6 @@ static int max77650_led_brightness_set(struct led_classdev *cdev,
static int max77650_led_probe(struct platform_device *pdev)
{
- struct fwnode_handle *child;
struct max77650_led *leds, *led;
struct device *dev;
struct regmap *map;
@@ -84,14 +83,12 @@ static int max77650_led_probe(struct platform_device *pdev)
if (!num_leds || num_leds > MAX77650_LED_NUM_LEDS)
return -ENODEV;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
rv = fwnode_property_read_u32(child, "reg", &reg);
- if (rv || reg >= MAX77650_LED_NUM_LEDS) {
- rv = -EINVAL;
- goto err_node_put;
- }
+ if (rv || reg >= MAX77650_LED_NUM_LEDS)
+ return -EINVAL;
led = &leds[reg];
led->map = map;
@@ -108,23 +105,20 @@ static int max77650_led_probe(struct platform_device *pdev)
rv = devm_led_classdev_register_ext(dev, &led->cdev,
&init_data);
if (rv)
- goto err_node_put;
+ return rv;
rv = regmap_write(map, led->regA, MAX77650_LED_A_DEFAULT);
if (rv)
- goto err_node_put;
+ return rv;
rv = regmap_write(map, led->regB, MAX77650_LED_B_DEFAULT);
if (rv)
- goto err_node_put;
+ return rv;
}
return regmap_write(map,
MAX77650_REG_CNFG_LED_TOP,
MAX77650_LED_TOP_DEFAULT);
-err_node_put:
- fwnode_handle_put(child);
- return rv;
}
static const struct of_device_id max77650_led_of_match[] = {
diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c
new file mode 100644
index 000000000000..933cb4f19be9
--- /dev/null
+++ b/drivers/leds/leds-max77705.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on leds-max77650 driver
+ *
+ * LED driver for MAXIM 77705 PMIC.
+ * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77705_LED_NUM_LEDS 4
+#define MAX77705_LED_EN_MASK GENMASK(1, 0)
+#define MAX77705_LED_MAX_BRIGHTNESS 0xff
+#define MAX77705_LED_EN_SHIFT(reg) (reg * MAX77705_RGBLED_EN_WIDTH)
+#define MAX77705_LED_REG_BRIGHTNESS(reg) (reg + MAX77705_RGBLED_REG_LED0BRT)
+
+struct max77705_led {
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+ struct regmap *regmap;
+
+ struct mc_subled *subled_info;
+};
+
+static const struct regmap_config max77705_leds_regmap_config = {
+ .reg_base = MAX77705_RGBLED_REG_BASE,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX77705_LED_REG_END,
+};
+
+static int max77705_rgb_blink(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+ int value, on_value, off_value;
+
+ if (*delay_on < MAX77705_RGB_DELAY_100_STEP)
+ on_value = 0;
+ else if (*delay_on < MAX77705_RGB_DELAY_100_STEP_LIM)
+ on_value = *delay_on / MAX77705_RGB_DELAY_100_STEP - 1;
+ else if (*delay_on < MAX77705_RGB_DELAY_250_STEP_LIM)
+ on_value = (*delay_on - MAX77705_RGB_DELAY_100_STEP_LIM) /
+ MAX77705_RGB_DELAY_250_STEP +
+ MAX77705_RGB_DELAY_100_STEP_COUNT;
+ else
+ on_value = 15;
+
+ on_value <<= 4;
+
+ if (*delay_off < 1)
+ off_value = 0;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP)
+ off_value = 1;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP_LIM)
+ off_value = *delay_off / MAX77705_RGB_DELAY_500_STEP;
+ else if (*delay_off < MAX77705_RGB_DELAY_1000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_1000_STEP_LIM) /
+ MAX77705_RGB_DELAY_1000_STEP +
+ MAX77705_RGB_DELAY_500_STEP_COUNT;
+ else if (*delay_off < MAX77705_RGB_DELAY_2000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_2000_STEP_LIM) /
+ MAX77705_RGB_DELAY_2000_STEP +
+ MAX77705_RGB_DELAY_1000_STEP_COUNT;
+ else
+ off_value = 15;
+
+ value = on_value | off_value;
+ return regmap_write(led->regmap, MAX77705_RGBLED_REG_LEDBLNK, value);
+}
+
+static int max77705_led_brightness_set(struct regmap *regmap, struct mc_subled *subled,
+ int num_colors)
+{
+ int ret;
+
+ for (int i = 0; i < num_colors; i++) {
+ unsigned int channel, brightness;
+
+ channel = subled[i].channel;
+ brightness = subled[i].brightness;
+
+ if (brightness == LED_OFF) {
+ /* Flash OFF */
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel), 0);
+ } else {
+ /* Set current */
+ ret = regmap_write(regmap, MAX77705_LED_REG_BRIGHTNESS(channel),
+ brightness);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ LED_ON << MAX77705_LED_EN_SHIFT(channel),
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel));
+ }
+ }
+
+ return ret;
+}
+
+static int max77705_led_brightness_set_single(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+
+ led->subled_info->brightness = brightness;
+
+ return max77705_led_brightness_set(led->regmap, led->subled_info, 1);
+}
+
+static int max77705_led_brightness_set_multi(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev);
+ struct max77705_led *led = container_of(mcdev, struct max77705_led, mcdev);
+
+ led_mc_calc_color_components(mcdev, brightness);
+
+ return max77705_led_brightness_set(led->regmap, led->mcdev.subled_info, mcdev->num_colors);
+}
+
+static int max77705_parse_subled(struct device *dev, struct fwnode_handle *np,
+ struct mc_subled *info)
+{
+ u32 color = LED_COLOR_ID_GREEN;
+ u32 reg;
+ int ret;
+
+ ret = fwnode_property_read_u32(np, "reg", &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", &reg);
+ if (ret || reg >= MAX77705_LED_NUM_LEDS)
+ ret = -EINVAL;
+
+ info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ cdev = &led->mcdev.led_cdev;
+ cdev->max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+ cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
+ cdev->blink_set = max77705_rgb_blink;
+
+ fwnode_for_each_available_child_node(np, child) {
+ ret = max77705_parse_subled(dev, child, &info[i]);
+ if (ret < 0)
+ return ret;
+
+ info[i].intensity = 0;
+ i++;
+ }
+
+ led->mcdev.subled_info = info;
+ led->mcdev.num_colors = num_channels;
+ led->cdev = *cdev;
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mcdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_multi(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ } else {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ max77705_parse_subled(dev, np, info);
+
+ led->subled_info = info;
+ led->cdev.brightness_set_blocking = max77705_led_brightness_set_single;
+ led->cdev.blink_set = max77705_rgb_blink;
+ led->cdev.max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_single(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max77705_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct i2c_client *i2c = to_i2c_client(pdev->dev.parent);
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(i2c, &max77705_leds_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register LEDs regmap\n");
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = max77705_add_led(dev, regmap, child);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id max77705_led_of_match[] = {
+ { .compatible = "maxim,max77705-rgb" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77705_led_of_match);
+
+static struct platform_driver max77705_led_driver = {
+ .driver = {
+ .name = "max77705-led",
+ .of_match_table = max77705_led_of_match,
+ },
+ .probe = max77705_led_probe,
+};
+module_platform_driver(max77705_led_driver);
+
+MODULE_DESCRIPTION("Maxim MAX77705 LED driver");
+MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c
index bbd1d359bba4..e22f09d13798 100644
--- a/drivers/leds/leds-mc13783.c
+++ b/drivers/leds/leds-mc13783.c
@@ -12,6 +12,7 @@
* Eric Miao <eric.miao@marvell.com>
*/
+#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h>
@@ -113,7 +114,7 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
{
struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
struct mc13xxx_leds_platform_data *pdata;
- struct device_node *parent, *child;
+ struct device_node *child;
struct device *dev = &pdev->dev;
int i = 0, ret = -ENODATA;
@@ -121,24 +122,23 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
if (!pdata)
return ERR_PTR(-ENOMEM);
- parent = of_get_child_by_name(dev_of_node(dev->parent), "leds");
+ struct device_node *parent __free(device_node) =
+ of_get_child_by_name(dev_of_node(dev->parent), "leds");
if (!parent)
- goto out_node_put;
+ return ERR_PTR(-ENODATA);
ret = of_property_read_u32_array(parent, "led-control",
pdata->led_control,
leds->devtype->num_regs);
if (ret)
- goto out_node_put;
+ return ERR_PTR(ret);
pdata->num_leds = of_get_available_child_count(parent);
pdata->led = devm_kcalloc(dev, pdata->num_leds, sizeof(*pdata->led),
GFP_KERNEL);
- if (!pdata->led) {
- ret = -ENOMEM;
- goto out_node_put;
- }
+ if (!pdata->led)
+ return ERR_PTR(-ENOMEM);
for_each_available_child_of_node(parent, child) {
const char *str;
@@ -158,12 +158,10 @@ static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
}
pdata->num_leds = i;
- ret = i > 0 ? 0 : -ENODATA;
-
-out_node_put:
- of_node_put(parent);
+ if (i <= 0)
+ return ERR_PTR(-ENODATA);
- return ret ? ERR_PTR(ret) : pdata;
+ return pdata;
}
#else
static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
@@ -303,7 +301,7 @@ static struct platform_driver mc13xxx_led_driver = {
.driver = {
.name = "mc13xxx-led",
},
- .remove_new = mc13xxx_led_remove,
+ .remove = mc13xxx_led_remove,
.id_table = mc13xxx_led_id_table,
};
module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c
index 718f55096e90..f25f68789281 100644
--- a/drivers/leds/leds-mlxcpld.c
+++ b/drivers/leds/leds-mlxcpld.c
@@ -32,7 +32,6 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
-#include <linux/acpi.h>
#include <linux/device.h>
#include <linux/dmi.h>
#include <linux/hwmon.h>
diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c
index 5595788d98d2..1b70de72376c 100644
--- a/drivers/leds/leds-mlxreg.c
+++ b/drivers/leds/leds-mlxreg.c
@@ -256,6 +256,7 @@ static int mlxreg_led_probe(struct platform_device *pdev)
{
struct mlxreg_core_platform_data *led_pdata;
struct mlxreg_led_priv_data *priv;
+ int err;
led_pdata = dev_get_platdata(&pdev->dev);
if (!led_pdata) {
@@ -267,26 +268,21 @@ static int mlxreg_led_probe(struct platform_device *pdev)
if (!priv)
return -ENOMEM;
- mutex_init(&priv->access_lock);
+ err = devm_mutex_init(&pdev->dev, &priv->access_lock);
+ if (err)
+ return err;
+
priv->pdev = pdev;
priv->pdata = led_pdata;
return mlxreg_led_config(priv);
}
-static void mlxreg_led_remove(struct platform_device *pdev)
-{
- struct mlxreg_led_priv_data *priv = dev_get_drvdata(&pdev->dev);
-
- mutex_destroy(&priv->access_lock);
-}
-
static struct platform_driver mlxreg_led_driver = {
.driver = {
.name = "leds-mlxreg",
},
.probe = mlxreg_led_probe,
- .remove_new = mlxreg_led_remove,
};
module_platform_driver(mlxreg_led_driver);
diff --git a/drivers/leds/leds-mt6323.c b/drivers/leds/leds-mt6323.c
index 40d508510823..dbdc221c3828 100644
--- a/drivers/leds/leds-mt6323.c
+++ b/drivers/leds/leds-mt6323.c
@@ -527,7 +527,6 @@ static int mt6323_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev_of_node(dev);
- struct device_node *child;
struct mt6397_chip *hw = dev_get_drvdata(dev->parent);
struct mt6323_leds *leds;
struct mt6323_led *led;
@@ -565,28 +564,25 @@ static int mt6323_led_probe(struct platform_device *pdev)
return ret;
}
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
struct led_init_data init_data = {};
bool is_wled;
ret = of_property_read_u32(child, "reg", &reg);
if (ret) {
dev_err(dev, "Failed to read led 'reg' property\n");
- goto put_child_node;
+ return ret;
}
if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS ||
leds->led[reg]) {
dev_err(dev, "Invalid led reg %u\n", reg);
- ret = -EINVAL;
- goto put_child_node;
+ return -EINVAL;
}
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
- if (!led) {
- ret = -ENOMEM;
- goto put_child_node;
- }
+ if (!led)
+ return -ENOMEM;
is_wled = of_property_read_bool(child, "mediatek,is-wled");
@@ -612,7 +608,7 @@ static int mt6323_led_probe(struct platform_device *pdev)
if (ret < 0) {
dev_err(leds->dev,
"Failed to LED set default from devicetree\n");
- goto put_child_node;
+ return ret;
}
init_data.fwnode = of_fwnode_handle(child);
@@ -621,15 +617,11 @@ static int mt6323_led_probe(struct platform_device *pdev)
&init_data);
if (ret) {
dev_err(dev, "Failed to register LED: %d\n", ret);
- goto put_child_node;
+ return ret;
}
}
return 0;
-
-put_child_node:
- of_node_put(child);
- return ret;
}
static void mt6323_led_remove(struct platform_device *pdev)
@@ -721,7 +713,7 @@ MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);
static struct platform_driver mt6323_led_driver = {
.probe = mt6323_led_probe,
- .remove_new = mt6323_led_remove,
+ .remove = mt6323_led_remove,
.driver = {
.name = "mt6323-led",
.of_match_table = mt6323_led_dt_match,
diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c
index 77213b79f84d..e95287416ef8 100644
--- a/drivers/leds/leds-netxbig.c
+++ b/drivers/leds/leds-netxbig.c
@@ -423,7 +423,6 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
struct device_node *gpio_ext_np;
struct platform_device *gpio_ext_pdev;
struct device *gpio_ext_dev;
- struct device_node *child;
struct netxbig_gpio_ext *gpio_ext;
struct netxbig_led_timer *timers;
struct netxbig_led *leds, *led;
@@ -440,6 +439,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
}
gpio_ext_pdev = of_find_device_by_node(gpio_ext_np);
if (!gpio_ext_pdev) {
+ of_node_put(gpio_ext_np);
dev_err(dev, "Failed to find platform device for gpio-ext\n");
return -ENODEV;
}
@@ -507,7 +507,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
}
led = leds;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
const char *string;
int *mode_val;
int num_modes;
@@ -515,17 +515,17 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
ret = of_property_read_u32(child, "mode-addr",
&led->mode_addr);
if (ret)
- goto err_node_put;
+ goto put_device;
ret = of_property_read_u32(child, "bright-addr",
&led->bright_addr);
if (ret)
- goto err_node_put;
+ goto put_device;
ret = of_property_read_u32(child, "max-brightness",
&led->bright_max);
if (ret)
- goto err_node_put;
+ goto put_device;
mode_val =
devm_kcalloc(dev,
@@ -533,7 +533,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
GFP_KERNEL);
if (!mode_val) {
ret = -ENOMEM;
- goto err_node_put;
+ goto put_device;
}
for (i = 0; i < NETXBIG_LED_MODE_NUM; i++)
@@ -542,12 +542,12 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
ret = of_property_count_u32_elems(child, "mode-val");
if (ret < 0 || ret % 2) {
ret = -EINVAL;
- goto err_node_put;
+ goto put_device;
}
num_modes = ret / 2;
if (num_modes > NETXBIG_LED_MODE_NUM) {
ret = -EINVAL;
- goto err_node_put;
+ goto put_device;
}
for (i = 0; i < num_modes; i++) {
@@ -560,7 +560,7 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
"mode-val", 2 * i + 1, &val);
if (mode >= NETXBIG_LED_MODE_NUM) {
ret = -EINVAL;
- goto err_node_put;
+ goto put_device;
}
mode_val[mode] = val;
}
@@ -583,8 +583,6 @@ static int netxbig_leds_get_of_pdata(struct device *dev,
return 0;
-err_node_put:
- of_node_put(child);
put_device:
put_device(gpio_ext_dev);
return ret;
diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c
index a86b43dd995e..f3161266b8ad 100644
--- a/drivers/leds/leds-nic78bx.c
+++ b/drivers/leds/leds-nic78bx.c
@@ -3,11 +3,19 @@
* Copyright (C) 2016 National Instruments Corp.
*/
-#include <linux/acpi.h>
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/spinlock.h>
+#include <linux/types.h>
#define NIC78BX_USER1_LED_MASK 0x3
#define NIC78BX_USER1_GREEN_LED BIT(0)
@@ -118,6 +126,15 @@ static struct nic78bx_led nic78bx_leds[] = {
}
};
+static void lock_led_reg_action(void *data)
+{
+ struct nic78bx_led_data *led_data = data;
+
+ /* Lock LED register */
+ outb(NIC78BX_LOCK_VALUE,
+ led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
+}
+
static int nic78bx_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -152,6 +169,10 @@ static int nic78bx_probe(struct platform_device *pdev)
led_data->io_base = io_rc->start;
spin_lock_init(&led_data->lock);
+ ret = devm_add_action(dev, lock_led_reg_action, led_data);
+ if (ret)
+ return ret;
+
for (i = 0; i < ARRAY_SIZE(nic78bx_leds); i++) {
nic78bx_leds[i].data = led_data;
@@ -167,27 +188,17 @@ static int nic78bx_probe(struct platform_device *pdev)
return ret;
}
-static void nic78bx_remove(struct platform_device *pdev)
-{
- struct nic78bx_led_data *led_data = platform_get_drvdata(pdev);
-
- /* Lock LED register */
- outb(NIC78BX_LOCK_VALUE,
- led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
-}
-
static const struct acpi_device_id led_device_ids[] = {
- {"NIC78B3", 0},
- {"", 0},
+ { "NIC78B3" },
+ { }
};
MODULE_DEVICE_TABLE(acpi, led_device_ids);
static struct platform_driver led_driver = {
.probe = nic78bx_probe,
- .remove_new = nic78bx_remove,
.driver = {
.name = KBUILD_MODNAME,
- .acpi_match_table = ACPI_PTR(led_device_ids),
+ .acpi_match_table = led_device_ids,
},
};
diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c
index f3010c472bbd..4c6f04a5bd87 100644
--- a/drivers/leds/leds-ns2.c
+++ b/drivers/leds/leds-ns2.c
@@ -238,7 +238,6 @@ static int ns2_led_register(struct device *dev, struct fwnode_handle *node,
static int ns2_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct fwnode_handle *child;
struct ns2_led *leds;
int count;
int ret;
@@ -251,12 +250,10 @@ static int ns2_led_probe(struct platform_device *pdev)
if (!leds)
return -ENOMEM;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
ret = ns2_led_register(dev, child, leds++);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
}
return 0;
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index bf8bb8fc007c..1b47acf54720 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -29,6 +29,9 @@
#define LED_SHIFT(led) (LED_NUM(led) * 2)
#define LED_MASK(led) (0x3 << LED_SHIFT(led))
+#define PCA9532_PWM_PERIOD_DIV 152
+#define PCA9532_PWM_DUTY_DIV 256
+
#define ldev_to_led(c) container_of(c, struct pca9532_led, ldev)
struct pca9532_chip_info {
@@ -45,8 +48,12 @@ struct pca9532_data {
struct gpio_chip gpio;
#endif
const struct pca9532_chip_info *chip_info;
+
+#define PCA9532_PWM_ID_0 0
+#define PCA9532_PWM_ID_1 1
u8 pwm[2];
u8 psc[2];
+ bool hw_blink;
};
static int pca9532_probe(struct i2c_client *client);
@@ -181,39 +188,73 @@ static int pca9532_set_brightness(struct led_classdev *led_cdev,
led->state = PCA9532_ON;
else {
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
- err = pca9532_calcpwm(led->client, 0, 0, value);
+ err = pca9532_calcpwm(led->client, PCA9532_PWM_ID_0, 0, value);
if (err)
return err;
}
if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, 0);
+ pca9532_setpwm(led->client, PCA9532_PWM_ID_0);
pca9532_setled(led);
return err;
}
+static int pca9532_update_hw_blink(struct pca9532_led *led,
+ unsigned long delay_on, unsigned long delay_off)
+{
+ struct pca9532_data *data = i2c_get_clientdata(led->client);
+ unsigned int psc;
+ int i;
+
+ /* Look for others LEDs that already use PWM1 */
+ for (i = 0; i < data->chip_info->num_leds; i++) {
+ struct pca9532_led *other = &data->leds[i];
+
+ if (other == led)
+ continue;
+
+ if (other->state == PCA9532_PWM1) {
+ if (other->ldev.blink_delay_on != delay_on ||
+ other->ldev.blink_delay_off != delay_off) {
+ /* HW can handle only one blink configuration at a time */
+ return -EINVAL;
+ }
+ }
+ }
+
+ psc = ((delay_on + delay_off) * PCA9532_PWM_PERIOD_DIV - 1) / 1000;
+ if (psc > U8_MAX) {
+ /* Blink period too long to be handled by hardware */
+ return -EINVAL;
+ }
+
+ led->state = PCA9532_PWM1;
+ data->psc[PCA9532_PWM_ID_1] = psc;
+ data->pwm[PCA9532_PWM_ID_1] = (delay_on * PCA9532_PWM_DUTY_DIV) / (delay_on + delay_off);
+
+ return pca9532_setpwm(data->client, PCA9532_PWM_ID_1);
+}
+
static int pca9532_set_blink(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client;
- int psc;
- int err = 0;
+ struct pca9532_data *data = i2c_get_clientdata(client);
+ int err;
+
+ if (!data->hw_blink)
+ return -EINVAL;
if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */
- *delay_on = 1000;
- *delay_off = 1000;
+ *delay_on = 500;
+ *delay_off = 500;
}
- if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6)
- return -EINVAL;
- /* Thecus specific: only use PSC/PWM 0 */
- psc = (*delay_on * 152-1)/1000;
- err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness);
+ err = pca9532_update_hw_blink(led, *delay_on, *delay_off);
if (err)
return err;
- if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, 0);
+
pca9532_setled(led);
return 0;
@@ -229,9 +270,9 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
/* XXX: allow different kind of beeps with psc/pwm modifications */
if (value > 1 && value < 32767)
- data->pwm[1] = 127;
+ data->pwm[PCA9532_PWM_ID_1] = 127;
else
- data->pwm[1] = 0;
+ data->pwm[PCA9532_PWM_ID_1] = 0;
schedule_work(&data->work);
@@ -246,7 +287,7 @@ static void pca9532_input_work(struct work_struct *work)
mutex_lock(&data->update_lock);
i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
- data->pwm[1]);
+ data->pwm[PCA9532_PWM_ID_1]);
mutex_unlock(&data->update_lock);
}
@@ -359,6 +400,7 @@ static int pca9532_configure(struct i2c_client *client,
data->psc[i]);
}
+ data->hw_blink = true;
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
@@ -393,6 +435,8 @@ static int pca9532_configure(struct i2c_client *client,
pca9532_setled(led);
break;
case PCA9532_TYPE_N2100_BEEP:
+ /* PWM1 is reserved for beeper so blink will not use hardware */
+ data->hw_blink = false;
BUG_ON(data->idev);
led->state = PCA9532_PWM1;
pca9532_setled(led);
@@ -461,7 +505,6 @@ static struct pca9532_platform_data *
pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
{
struct pca9532_platform_data *pdata;
- struct device_node *child;
int devid, maxleds;
int i = 0;
const char *state;
@@ -475,12 +518,12 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
pdata->gpio_base = -1;
- of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[0],
+ of_property_read_u8_array(np, "nxp,pwm", &pdata->pwm[PCA9532_PWM_ID_0],
ARRAY_SIZE(pdata->pwm));
- of_property_read_u8_array(np, "nxp,psc", &pdata->psc[0],
+ of_property_read_u8_array(np, "nxp,psc", &pdata->psc[PCA9532_PWM_ID_0],
ARRAY_SIZE(pdata->psc));
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
if (of_property_read_string(child, "label",
&pdata->leds[i].name))
pdata->leds[i].name = child->name;
@@ -493,10 +536,8 @@ pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
else if (!strcmp(state, "keep"))
pdata->leds[i].state = PCA9532_KEEP;
}
- if (++i >= maxleds) {
- of_node_put(child);
+ if (++i >= maxleds)
break;
- }
}
return pdata;
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c
index 94a9f8a54b35..e9cfde9fe4b1 100644
--- a/drivers/leds/leds-pca955x.c
+++ b/drivers/leds/leds-pca955x.c
@@ -62,6 +62,8 @@
#define PCA955X_GPIO_HIGH LED_OFF
#define PCA955X_GPIO_LOW LED_FULL
+#define PCA955X_BLINK_DEFAULT_MS 1000
+
enum pca955x_type {
pca9550,
pca9551,
@@ -74,6 +76,7 @@ struct pca955x_chipdef {
int bits;
u8 slv_addr; /* 7-bit slave address mask */
int slv_addr_shift; /* Number of bits to ignore */
+ int blink_div; /* PSC divider */
};
static const struct pca955x_chipdef pca955x_chipdefs[] = {
@@ -81,26 +84,31 @@ static const struct pca955x_chipdef pca955x_chipdefs[] = {
.bits = 2,
.slv_addr = /* 110000x */ 0x60,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
[pca9551] = {
.bits = 8,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 38,
},
[pca9552] = {
.bits = 16,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 44,
},
[ibm_pca9552] = {
.bits = 16,
.slv_addr = /* 0110xxx */ 0x30,
.slv_addr_shift = 3,
+ .blink_div = 44,
},
[pca9553] = {
.bits = 4,
.slv_addr = /* 110001x */ 0x62,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
};
@@ -109,7 +117,9 @@ struct pca955x {
struct pca955x_led *leds;
const struct pca955x_chipdef *chipdef;
struct i2c_client *client;
+ unsigned long active_blink;
unsigned long active_pins;
+ unsigned long blink_period;
#ifdef CONFIG_LEDS_PCA955X_GPIO
struct gpio_chip gpio;
#endif
@@ -124,17 +134,25 @@ struct pca955x_led {
struct fwnode_handle *fwnode;
};
+#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
+
struct pca955x_platform_data {
struct pca955x_led *leds;
int num_leds;
};
/* 8 bits per input register */
-static inline int pca95xx_num_input_regs(int bits)
+static inline int pca955x_num_input_regs(int bits)
{
return (bits + 7) / 8;
}
+/* 4 bits per LED selector register */
+static inline int pca955x_num_led_regs(int bits)
+{
+ return (bits + 3) / 4;
+}
+
/*
* Return an LED selector register value based on an existing one, with
* the appropriate 2-bit state value set for the given LED number (0-3).
@@ -145,20 +163,25 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
((state & 0x3) << (led_num << 1));
}
+static inline int pca955x_ledstate(u8 ls, int led_num)
+{
+ return (ls >> (led_num << 1)) & 0x3;
+}
+
/*
* Write to frequency prescaler register, used to program the
- * period of the PWM output. period = (PSCx + 1) / 38
+ * period of the PWM output. period = (PSCx + 1) / coeff
+ * Where for pca9551 chips coeff = 38 and for all other chips coeff = 44
*/
-static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_psc(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -169,16 +192,15 @@ static int pca955x_write_psc(struct i2c_client *client, int n, u8 val)
*
* Duty cycle is (256 - PWMx) / 256
*/
-static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_pwm(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -186,16 +208,15 @@ static int pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
* Write to LED selector register, which determines the source that
* drives the LED output.
*/
-static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
+static int pca955x_write_ls(struct pca955x *pca955x, int n, u8 val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
int ret;
- ret = i2c_smbus_write_byte_data(client, cmd, val);
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
if (ret < 0)
- dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
- __func__, n, val, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
return ret;
}
@@ -203,32 +224,43 @@ static int pca955x_write_ls(struct i2c_client *client, int n, u8 val)
* Read the LED selector register, which determines the source that
* drives the LED output.
*/
-static int pca955x_read_ls(struct i2c_client *client, int n, u8 *val)
+static int pca955x_read_ls(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
int ret;
- ret = i2c_smbus_read_byte_data(client, cmd);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
if (ret < 0) {
- dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
- __func__, n, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
return ret;
}
*val = (u8)ret;
return 0;
}
-static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
+static int pca955x_read_pwm(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- u8 cmd = pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
int ret;
- ret = i2c_smbus_read_byte_data(client, cmd);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
if (ret < 0) {
- dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
- __func__, n, ret);
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
+ return ret;
+ }
+ *val = (u8)ret;
+ return 0;
+}
+
+static int pca955x_read_psc(struct pca955x *pca955x, int n, u8 *val)
+{
+ int ret;
+ u8 cmd;
+
+ cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
+ ret = i2c_smbus_read_byte_data(pca955x->client, cmd);
+ if (ret < 0) {
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, err %d\n", __func__, n, ret);
return ret;
}
*val = (u8)ret;
@@ -237,30 +269,25 @@ static int pca955x_read_pwm(struct i2c_client *client, int n, u8 *val)
static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
{
- struct pca955x_led *pca955x_led = container_of(led_cdev,
- struct pca955x_led,
- led_cdev);
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
struct pca955x *pca955x = pca955x_led->pca955x;
u8 ls, pwm;
int ret;
- ret = pca955x_read_ls(pca955x->client, pca955x_led->led_num / 4, &ls);
+ ret = pca955x_read_ls(pca955x, pca955x_led->led_num / 4, &ls);
if (ret)
return ret;
- ls = (ls >> ((pca955x_led->led_num % 4) << 1)) & 0x3;
- switch (ls) {
+ switch (pca955x_ledstate(ls, pca955x_led->led_num % 4)) {
case PCA955X_LS_LED_ON:
+ case PCA955X_LS_BLINK0:
ret = LED_FULL;
break;
case PCA955X_LS_LED_OFF:
ret = LED_OFF;
break;
- case PCA955X_LS_BLINK0:
- ret = LED_HALF;
- break;
case PCA955X_LS_BLINK1:
- ret = pca955x_read_pwm(pca955x->client, 1, &pwm);
+ ret = pca955x_read_pwm(pca955x, 1, &pwm);
if (ret)
return ret;
ret = 255 - pwm;
@@ -273,51 +300,150 @@ static enum led_brightness pca955x_led_get(struct led_classdev *led_cdev)
static int pca955x_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- struct pca955x_led *pca955x_led;
- struct pca955x *pca955x;
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
+ struct pca955x *pca955x = pca955x_led->pca955x;
+ int reg = pca955x_led->led_num / 4;
+ int bit = pca955x_led->led_num % 4;
u8 ls;
- int chip_ls; /* which LSx to use (0-3 potentially) */
- int ls_led; /* which set of bits within LSx to use (0-3) */
int ret;
- pca955x_led = container_of(led_cdev, struct pca955x_led, led_cdev);
- pca955x = pca955x_led->pca955x;
-
- chip_ls = pca955x_led->led_num / 4;
- ls_led = pca955x_led->led_num % 4;
-
mutex_lock(&pca955x->lock);
- ret = pca955x_read_ls(pca955x->client, chip_ls, &ls);
+ ret = pca955x_read_ls(pca955x, reg, &ls);
if (ret)
goto out;
- switch (value) {
- case LED_FULL:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
- break;
- case LED_OFF:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF);
- break;
- case LED_HALF:
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0);
- break;
- default:
- /*
- * Use PWM1 for all other values. This has the unwanted
- * side effect of making all LEDs on the chip share the
- * same brightness level if set to a value other than
- * OFF, HALF, or FULL. But, this is probably better than
- * just turning off for all other values.
- */
- ret = pca955x_write_pwm(pca955x->client, 1, 255 - value);
- if (ret)
+ if (test_bit(pca955x_led->led_num, &pca955x->active_blink)) {
+ if (value == LED_OFF) {
+ clear_bit(pca955x_led->led_num, &pca955x->active_blink);
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
+ } else {
+ /* No variable brightness for blinking LEDs */
goto out;
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
- break;
+ }
+ } else {
+ switch (value) {
+ case LED_FULL:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_ON);
+ break;
+ case LED_OFF:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
+ break;
+ default:
+ /*
+ * Use PWM1 for all other values. This has the unwanted
+ * side effect of making all LEDs on the chip share the
+ * same brightness level if set to a value other than
+ * OFF or FULL. But, this is probably better than just
+ * turning off for all other values.
+ */
+ ret = pca955x_write_pwm(pca955x, 1, 255 - value);
+ if (ret)
+ goto out;
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK1);
+ break;
+ }
}
- ret = pca955x_write_ls(pca955x->client, chip_ls, ls);
+ ret = pca955x_write_ls(pca955x, reg, ls);
+
+out:
+ mutex_unlock(&pca955x->lock);
+
+ return ret;
+}
+
+static u8 pca955x_period_to_psc(struct pca955x *pca955x, unsigned long period)
+{
+ /* psc register value = (blink period * coeff) - 1 */
+ period *= pca955x->chipdef->blink_div;
+ period /= MSEC_PER_SEC;
+ period -= 1;
+
+ return period;
+}
+
+static unsigned long pca955x_psc_to_period(struct pca955x *pca955x, u8 psc)
+{
+ unsigned long period = psc;
+
+ /* blink period = (psc register value + 1) / coeff */
+ period += 1;
+ period *= MSEC_PER_SEC;
+ period /= pca955x->chipdef->blink_div;
+
+ return period;
+}
+
+static int pca955x_led_blink(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct pca955x_led *pca955x_led = led_to_pca955x(led_cdev);
+ struct pca955x *pca955x = pca955x_led->pca955x;
+ unsigned long period = *delay_on + *delay_off;
+ int ret = 0;
+
+ mutex_lock(&pca955x->lock);
+
+ if (period) {
+ if (*delay_on != *delay_off) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (period < pca955x_psc_to_period(pca955x, 0) ||
+ period > pca955x_psc_to_period(pca955x, 0xff)) {
+ ret = -EINVAL;
+ goto out;
+ }
+ } else {
+ period = pca955x->active_blink ? pca955x->blink_period :
+ PCA955X_BLINK_DEFAULT_MS;
+ }
+
+ if (!pca955x->active_blink ||
+ pca955x->active_blink == BIT(pca955x_led->led_num) ||
+ pca955x->blink_period == period) {
+ u8 psc = pca955x_period_to_psc(pca955x, period);
+
+ if (!test_and_set_bit(pca955x_led->led_num,
+ &pca955x->active_blink)) {
+ u8 ls;
+ int reg = pca955x_led->led_num / 4;
+ int bit = pca955x_led->led_num % 4;
+
+ ret = pca955x_read_ls(pca955x, reg, &ls);
+ if (ret)
+ goto out;
+
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK0);
+ ret = pca955x_write_ls(pca955x, reg, ls);
+ if (ret)
+ goto out;
+
+ /*
+ * Force 50% duty cycle to maintain the specified
+ * blink rate.
+ */
+ ret = pca955x_write_pwm(pca955x, 0, 128);
+ if (ret)
+ goto out;
+ }
+
+ if (pca955x->blink_period != period) {
+ pca955x->blink_period = period;
+ ret = pca955x_write_psc(pca955x, 0, psc);
+ if (ret)
+ goto out;
+ }
+
+ period = pca955x_psc_to_period(pca955x, psc);
+ period /= 2;
+ *delay_on = period;
+ *delay_off = period;
+ } else {
+ ret = -EBUSY;
+ }
out:
mutex_unlock(&pca955x->lock);
@@ -455,10 +581,13 @@ static int pca955x_probe(struct i2c_client *client)
struct led_classdev *led;
struct led_init_data init_data;
struct i2c_adapter *adapter;
- int i, err;
+ int i, bit, err, nls, reg;
+ u8 ls1[4];
+ u8 ls2[4];
struct pca955x_platform_data *pdata;
+ u8 psc0;
+ bool keep_psc0 = false;
bool set_default_label = false;
- bool keep_pwm = false;
char default_label[8];
chip = i2c_get_match_data(client);
@@ -509,10 +638,22 @@ static int pca955x_probe(struct i2c_client *client)
mutex_init(&pca955x->lock);
pca955x->client = client;
pca955x->chipdef = chip;
+ pca955x->blink_period = PCA955X_BLINK_DEFAULT_MS;
init_data.devname_mandatory = false;
init_data.devicename = "pca955x";
+ nls = pca955x_num_led_regs(chip->bits);
+ /* Use auto-increment feature to read all the LED selectors at once. */
+ err = i2c_smbus_read_i2c_block_data(client,
+ 0x10 | (pca955x_num_input_regs(chip->bits) + 4), nls,
+ ls1);
+ if (err < 0)
+ return err;
+
+ for (i = 0; i < nls; i++)
+ ls2[i] = ls1[i];
+
for (i = 0; i < chip->bits; i++) {
pca955x_led = &pca955x->leds[i];
pca955x_led->led_num = i;
@@ -524,18 +665,20 @@ static int pca955x_probe(struct i2c_client *client)
case PCA955X_TYPE_GPIO:
break;
case PCA955X_TYPE_LED:
+ bit = i % 4;
+ reg = i / 4;
led = &pca955x_led->led_cdev;
led->brightness_set_blocking = pca955x_led_set;
led->brightness_get = pca955x_led_get;
-
- if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF) {
- err = pca955x_led_set(led, LED_OFF);
- if (err)
- return err;
- } else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON) {
- err = pca955x_led_set(led, LED_FULL);
- if (err)
- return err;
+ led->blink_set = pca955x_led_blink;
+
+ if (pdata->leds[i].default_state == LEDS_DEFSTATE_OFF)
+ ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_OFF);
+ else if (pdata->leds[i].default_state == LEDS_DEFSTATE_ON)
+ ls2[reg] = pca955x_ledsel(ls2[reg], bit, PCA955X_LS_LED_ON);
+ else if (pca955x_ledstate(ls2[reg], bit) == PCA955X_LS_BLINK0) {
+ keep_psc0 = true;
+ set_bit(i, &pca955x->active_blink);
}
init_data.fwnode = pdata->leds[i].fwnode;
@@ -564,39 +707,31 @@ static int pca955x_probe(struct i2c_client *client)
return err;
set_bit(i, &pca955x->active_pins);
-
- /*
- * For default-state == "keep", let the core update the
- * brightness from the hardware, then check the
- * brightness to see if it's using PWM1. If so, PWM1
- * should not be written below.
- */
- if (pdata->leds[i].default_state == LEDS_DEFSTATE_KEEP) {
- if (led->brightness != LED_FULL &&
- led->brightness != LED_OFF &&
- led->brightness != LED_HALF)
- keep_pwm = true;
- }
}
}
- /* PWM0 is used for half brightness or 50% duty cycle */
- err = pca955x_write_pwm(client, 0, 255 - LED_HALF);
- if (err)
- return err;
+ for (i = 0; i < nls; i++) {
+ if (ls1[i] != ls2[i]) {
+ err = pca955x_write_ls(pca955x, i, ls2[i]);
+ if (err)
+ return err;
+ }
+ }
- if (!keep_pwm) {
- /* PWM1 is used for variable brightness, default to OFF */
- err = pca955x_write_pwm(client, 1, 0);
- if (err)
- return err;
+ if (keep_psc0) {
+ err = pca955x_read_psc(pca955x, 0, &psc0);
+ } else {
+ psc0 = pca955x_period_to_psc(pca955x, pca955x->blink_period);
+ err = pca955x_write_psc(pca955x, 0, psc0);
}
- /* Set to fast frequency so we do not see flashing */
- err = pca955x_write_psc(client, 0, 0);
if (err)
return err;
- err = pca955x_write_psc(client, 1, 0);
+
+ pca955x->blink_period = pca955x_psc_to_period(pca955x, psc0);
+
+ /* Set PWM1 to fast frequency so we do not see flashing */
+ err = pca955x_write_psc(pca955x, 1, 0);
if (err)
return err;
diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c
index b53905da3592..050e93b04884 100644
--- a/drivers/leds/leds-pca963x.c
+++ b/drivers/leds/leds-pca963x.c
@@ -306,7 +306,6 @@ static int pca963x_register_leds(struct i2c_client *client,
struct pca963x_chipdef *chipdef = chip->chipdef;
struct pca963x_led *led = chip->leds;
struct device *dev = &client->dev;
- struct fwnode_handle *child;
bool hw_blink;
s32 mode2;
u32 reg;
@@ -338,7 +337,7 @@ static int pca963x_register_leds(struct i2c_client *client,
if (ret < 0)
return ret;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_init_data init_data = {};
char default_label[32];
@@ -346,8 +345,7 @@ static int pca963x_register_leds(struct i2c_client *client,
if (ret || reg >= chipdef->n_leds) {
dev_err(dev, "Invalid 'reg' property for node %pfw\n",
child);
- ret = -EINVAL;
- goto err;
+ return -EINVAL;
}
led->led_num = reg;
@@ -369,16 +367,13 @@ static int pca963x_register_leds(struct i2c_client *client,
if (ret) {
dev_err(dev, "Failed to register LED for node %pfw\n",
child);
- goto err;
+ return ret;
}
++led;
}
return 0;
-err:
- fwnode_handle_put(child);
- return ret;
}
static int pca963x_suspend(struct device *dev)
diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c
index 78215dff1499..11c7bb69573e 100644
--- a/drivers/leds/leds-pca995x.c
+++ b/drivers/leds/leds-pca995x.c
@@ -19,10 +19,6 @@
#define PCA995X_MODE1 0x00
#define PCA995X_MODE2 0x01
#define PCA995X_LEDOUT0 0x02
-#define PCA9955B_PWM0 0x08
-#define PCA9952_PWM0 0x0A
-#define PCA9952_IREFALL 0x43
-#define PCA9955B_IREFALL 0x45
/* Auto-increment disabled. Normal mode */
#define PCA995X_MODE1_CFG 0x00
@@ -34,17 +30,38 @@
#define PCA995X_LDRX_MASK 0x3
#define PCA995X_LDRX_BITS 2
-#define PCA995X_MAX_OUTPUTS 16
+#define PCA995X_MAX_OUTPUTS 24
#define PCA995X_OUTPUTS_PER_REG 4
#define PCA995X_IREFALL_FULL_CFG 0xFF
#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2)
-#define PCA995X_TYPE_NON_B 0
-#define PCA995X_TYPE_B 1
-
#define ldev_to_led(c) container_of(c, struct pca995x_led, ldev)
+struct pca995x_chipdef {
+ unsigned int num_leds;
+ u8 pwm_base;
+ u8 irefall;
+};
+
+static const struct pca995x_chipdef pca9952_chipdef = {
+ .num_leds = 16,
+ .pwm_base = 0x0a,
+ .irefall = 0x43,
+};
+
+static const struct pca995x_chipdef pca9955b_chipdef = {
+ .num_leds = 16,
+ .pwm_base = 0x08,
+ .irefall = 0x45,
+};
+
+static const struct pca995x_chipdef pca9956b_chipdef = {
+ .num_leds = 24,
+ .pwm_base = 0x0a,
+ .irefall = 0x40,
+};
+
struct pca995x_led {
unsigned int led_no;
struct led_classdev ldev;
@@ -54,7 +71,7 @@ struct pca995x_led {
struct pca995x_chip {
struct regmap *regmap;
struct pca995x_led leds[PCA995X_MAX_OUTPUTS];
- int btype;
+ const struct pca995x_chipdef *chipdef;
};
static int pca995x_brightness_set(struct led_classdev *led_cdev,
@@ -62,10 +79,11 @@ static int pca995x_brightness_set(struct led_classdev *led_cdev,
{
struct pca995x_led *led = ldev_to_led(led_cdev);
struct pca995x_chip *chip = led->chip;
+ const struct pca995x_chipdef *chipdef = chip->chipdef;
u8 ledout_addr, pwmout_addr;
int shift, ret;
- pwmout_addr = (chip->btype ? PCA9955B_PWM0 : PCA9952_PWM0) + led->led_no;
+ pwmout_addr = chipdef->pwm_base + led->led_no;
ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG);
shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
@@ -102,43 +120,38 @@ static const struct regmap_config pca995x_regmap = {
static int pca995x_probe(struct i2c_client *client)
{
struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
- struct fwnode_handle *np, *child;
struct device *dev = &client->dev;
+ const struct pca995x_chipdef *chipdef;
struct pca995x_chip *chip;
struct pca995x_led *led;
- int i, btype, reg, ret;
+ int i, j, reg, ret;
- btype = (unsigned long)device_get_match_data(&client->dev);
+ chipdef = device_get_match_data(&client->dev);
- np = dev_fwnode(dev);
- if (!np)
+ if (!dev_fwnode(dev))
return -ENODEV;
chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
- chip->btype = btype;
+ chip->chipdef = chipdef;
chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap);
if (IS_ERR(chip->regmap))
return PTR_ERR(chip->regmap);
i2c_set_clientdata(client, chip);
- fwnode_for_each_available_child_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret) {
- fwnode_handle_put(child);
+ if (ret)
return ret;
- }
- if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg]) {
- fwnode_handle_put(child);
+ if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg])
return -EINVAL;
- }
led = &chip->leds[reg];
- led_fwnodes[reg] = child;
+ led_fwnodes[reg] = fwnode_handle_get(child);
led->chip = chip;
led->led_no = reg;
led->ldev.brightness_set_blocking = pca995x_brightness_set;
@@ -157,7 +170,8 @@ static int pca995x_probe(struct i2c_client *client)
&chip->leds[i].ldev,
&init_data);
if (ret < 0) {
- fwnode_handle_put(child);
+ for (j = i; j < PCA995X_MAX_OUTPUTS; j++)
+ fwnode_handle_put(led_fwnodes[j]);
return dev_err_probe(dev, ret,
"Could not register LED %s\n",
chip->leds[i].ldev.name);
@@ -170,21 +184,21 @@ static int pca995x_probe(struct i2c_client *client)
return ret;
/* IREF Output current value for all LEDn outputs */
- return regmap_write(chip->regmap,
- btype ? PCA9955B_IREFALL : PCA9952_IREFALL,
- PCA995X_IREFALL_HALF_CFG);
+ return regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
}
static const struct i2c_device_id pca995x_id[] = {
- { "pca9952", .driver_data = (kernel_ulong_t)PCA995X_TYPE_NON_B },
- { "pca9955b", .driver_data = (kernel_ulong_t)PCA995X_TYPE_B },
+ { "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef },
+ { "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef },
+ { "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef },
{}
};
MODULE_DEVICE_TABLE(i2c, pca995x_id);
static const struct of_device_id pca995x_of_match[] = {
- { .compatible = "nxp,pca9952", .data = (void *)PCA995X_TYPE_NON_B },
- { .compatible = "nxp,pca9955b", .data = (void *)PCA995X_TYPE_B },
+ { .compatible = "nxp,pca9952", .data = &pca9952_chipdef },
+ { .compatible = "nxp,pca9955b", . data = &pca9955b_chipdef },
+ { .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef },
{},
};
MODULE_DEVICE_TABLE(of, pca995x_of_match);
diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c
index 4f01acb75727..3a38578ce8e4 100644
--- a/drivers/leds/leds-powernv.c
+++ b/drivers/leds/leds-powernv.c
@@ -246,29 +246,25 @@ static int powernv_led_classdev(struct platform_device *pdev,
const char *cur = NULL;
int rc = -1;
struct property *p;
- struct device_node *np;
struct powernv_led_data *powernv_led;
struct device *dev = &pdev->dev;
- for_each_available_child_of_node(led_node, np) {
+ for_each_available_child_of_node_scoped(led_node, np) {
p = of_find_property(np, "led-types", NULL);
while ((cur = of_prop_next_string(p, cur)) != NULL) {
powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),
GFP_KERNEL);
- if (!powernv_led) {
- of_node_put(np);
+ if (!powernv_led)
return -ENOMEM;
- }
powernv_led->common = powernv_led_common;
powernv_led->loc_code = (char *)np->name;
rc = powernv_led_create(dev, powernv_led, cur);
- if (rc) {
- of_node_put(np);
+ if (rc)
return rc;
- }
+
} /* while end */
}
@@ -278,12 +274,11 @@ static int powernv_led_classdev(struct platform_device *pdev,
/* Platform driver probe */
static int powernv_led_probe(struct platform_device *pdev)
{
- struct device_node *led_node;
struct powernv_led_common *powernv_led_common;
struct device *dev = &pdev->dev;
- int rc;
+ struct device_node *led_node
+ __free(device_node) = of_find_node_by_path("/ibm,opal/leds");
- led_node = of_find_node_by_path("/ibm,opal/leds");
if (!led_node) {
dev_err(dev, "%s: LED parent device node not found\n",
__func__);
@@ -292,20 +287,15 @@ static int powernv_led_probe(struct platform_device *pdev)
powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
GFP_KERNEL);
- if (!powernv_led_common) {
- rc = -ENOMEM;
- goto out;
- }
+ if (!powernv_led_common)
+ return -ENOMEM;
mutex_init(&powernv_led_common->lock);
powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);
platform_set_drvdata(pdev, powernv_led_common);
- rc = powernv_led_classdev(pdev, led_node, powernv_led_common);
-out:
- of_node_put(led_node);
- return rc;
+ return powernv_led_classdev(pdev, led_node, powernv_led_common);
}
/* Platform driver remove */
@@ -333,8 +323,8 @@ static const struct of_device_id powernv_led_match[] = {
MODULE_DEVICE_TABLE(of, powernv_led_match);
static struct platform_driver powernv_led_driver = {
- .probe = powernv_led_probe,
- .remove_new = powernv_led_remove,
+ .probe = powernv_led_probe,
+ .remove = powernv_led_remove,
.driver = {
.name = "powernv-led-driver",
.of_match_table = powernv_led_match,
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index 4e3936a39d0e..c73134e7b951 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -17,7 +17,6 @@
#include <linux/err.h>
#include <linux/pwm.h>
#include <linux/slab.h>
-#include "leds.h"
struct led_pwm {
const char *name;
@@ -53,10 +52,30 @@ static int led_pwm_set(struct led_classdev *led_cdev,
duty = led_dat->pwmstate.period - duty;
led_dat->pwmstate.duty_cycle = duty;
- led_dat->pwmstate.enabled = true;
+ /*
+ * Disabling a PWM doesn't guarantee that it emits the inactive level.
+ * So keep it on. Only for suspending the PWM should be disabled because
+ * otherwise it refuses to suspend. The possible downside is that the
+ * LED might stay (or even go) on.
+ */
+ led_dat->pwmstate.enabled = !(led_cdev->flags & LED_SUSPENDED);
return pwm_apply_might_sleep(led_dat->pwm, &led_dat->pwmstate);
}
+static int led_pwm_default_brightness_get(struct fwnode_handle *fwnode,
+ int max_brightness)
+{
+ unsigned int default_brightness;
+ int ret;
+
+ ret = fwnode_property_read_u32(fwnode, "default-brightness",
+ &default_brightness);
+ if (ret < 0 || default_brightness > max_brightness)
+ default_brightness = max_brightness;
+
+ return default_brightness;
+}
+
__attribute__((nonnull))
static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
struct led_pwm *led, struct fwnode_handle *fwnode)
@@ -98,7 +117,8 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
/* set brightness */
switch (led->default_state) {
case LEDS_DEFSTATE_ON:
- led_data->cdev.brightness = led->max_brightness;
+ led_data->cdev.brightness =
+ led_pwm_default_brightness_get(fwnode, led->max_brightness);
break;
case LEDS_DEFSTATE_KEEP:
{
@@ -134,21 +154,18 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
{
- struct fwnode_handle *fwnode;
struct led_pwm led;
int ret;
- device_for_each_child_node(dev, fwnode) {
+ device_for_each_child_node_scoped(dev, fwnode) {
memset(&led, 0, sizeof(led));
ret = fwnode_property_read_string(fwnode, "label", &led.name);
if (ret && is_of_node(fwnode))
led.name = to_of_node(fwnode)->name;
- if (!led.name) {
- ret = -EINVAL;
- goto err_child_out;
- }
+ if (!led.name)
+ return -EINVAL;
led.active_low = fwnode_property_read_bool(fwnode,
"active-low");
@@ -159,14 +176,10 @@ static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
ret = led_pwm_add(dev, priv, &led, fwnode);
if (ret)
- goto err_child_out;
+ return ret;
}
return 0;
-
-err_child_out:
- fwnode_handle_put(fwnode);
- return ret;
}
static int led_pwm_probe(struct platform_device *pdev)
diff --git a/drivers/leds/leds-qnap-mcu.c b/drivers/leds/leds-qnap-mcu.c
new file mode 100644
index 000000000000..4e4709456261
--- /dev/null
+++ b/drivers/leds/leds-qnap-mcu.c
@@ -0,0 +1,227 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for LEDs found on QNAP MCU devices
+ *
+ * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/qnap-mcu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+enum qnap_mcu_err_led_mode {
+ QNAP_MCU_ERR_LED_ON = 0,
+ QNAP_MCU_ERR_LED_OFF = 1,
+ QNAP_MCU_ERR_LED_BLINK_FAST = 2,
+ QNAP_MCU_ERR_LED_BLINK_SLOW = 3,
+};
+
+struct qnap_mcu_err_led {
+ struct qnap_mcu *mcu;
+ struct led_classdev cdev;
+ char name[LED_MAX_NAME_SIZE];
+ u8 num;
+ u8 mode;
+};
+
+static inline struct qnap_mcu_err_led *
+ cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct qnap_mcu_err_led, cdev);
+}
+
+static int qnap_mcu_err_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
+ u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
+
+ /* Don't disturb a possible set blink-mode if LED stays on */
+ if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST)
+ return 0;
+
+ err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF;
+ cmd[3] = '0' + err_led->mode;
+
+ return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
+ u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
+
+ /* LED is off, nothing to do */
+ if (err_led->mode == QNAP_MCU_ERR_LED_OFF)
+ return 0;
+
+ if (*delay_on < 500) {
+ *delay_on = 100;
+ *delay_off = 100;
+ err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST;
+ } else {
+ *delay_on = 500;
+ *delay_off = 500;
+ err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW;
+ }
+
+ cmd[3] = '0' + err_led->mode;
+
+ return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led)
+{
+ struct qnap_mcu_err_led *err_led;
+ int ret;
+
+ err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL);
+ if (!err_led)
+ return -ENOMEM;
+
+ err_led->mcu = mcu;
+ err_led->num = num_err_led;
+ err_led->mode = QNAP_MCU_ERR_LED_OFF;
+
+ scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1);
+ err_led->cdev.name = err_led->name;
+
+ err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set;
+ err_led->cdev.blink_set = qnap_mcu_err_led_blink_set;
+ err_led->cdev.brightness = 0;
+ err_led->cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, &err_led->cdev);
+ if (ret)
+ return ret;
+
+ return qnap_mcu_err_led_set(&err_led->cdev, 0);
+}
+
+enum qnap_mcu_usb_led_mode {
+ QNAP_MCU_USB_LED_ON = 1,
+ QNAP_MCU_USB_LED_OFF = 3,
+ QNAP_MCU_USB_LED_BLINK = 2,
+};
+
+struct qnap_mcu_usb_led {
+ struct qnap_mcu *mcu;
+ struct led_classdev cdev;
+ u8 mode;
+};
+
+static inline struct qnap_mcu_usb_led *
+ cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct qnap_mcu_usb_led, cdev);
+}
+
+static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
+ u8 cmd[] = { '@', 'C', 0 };
+
+ /* Don't disturb a possible set blink-mode if LED stays on */
+ if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK)
+ return 0;
+
+ usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF;
+
+ /*
+ * Byte 3 is shared between the usb led target on/off/blink
+ * and also the buzzer control (in the input driver)
+ */
+ cmd[2] = 'D' + usb_led->mode;
+
+ return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
+ u8 cmd[] = { '@', 'C', 0 };
+
+ /* LED is off, nothing to do */
+ if (usb_led->mode == QNAP_MCU_USB_LED_OFF)
+ return 0;
+
+ *delay_on = 250;
+ *delay_off = 250;
+ usb_led->mode = QNAP_MCU_USB_LED_BLINK;
+
+ /*
+ * Byte 3 is shared between the USB LED target on/off/blink
+ * and also the buzzer control (in the input driver)
+ */
+ cmd[2] = 'D' + usb_led->mode;
+
+ return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu)
+{
+ struct qnap_mcu_usb_led *usb_led;
+ int ret;
+
+ usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL);
+ if (!usb_led)
+ return -ENOMEM;
+
+ usb_led->mcu = mcu;
+ usb_led->mode = QNAP_MCU_USB_LED_OFF;
+ usb_led->cdev.name = "usb:blue:disk";
+ usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set;
+ usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set;
+ usb_led->cdev.brightness = 0;
+ usb_led->cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, &usb_led->cdev);
+ if (ret)
+ return ret;
+
+ return qnap_mcu_usb_led_set(&usb_led->cdev, 0);
+}
+
+static int qnap_mcu_leds_probe(struct platform_device *pdev)
+{
+ struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
+ const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
+ int ret;
+
+ for (int i = 0; i < variant->num_drives; i++) {
+ ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register error LED %d\n", i);
+ }
+
+ if (variant->usb_led) {
+ ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register USB LED\n");
+ }
+
+ return 0;
+}
+
+static struct platform_driver qnap_mcu_leds_driver = {
+ .probe = qnap_mcu_leds_probe,
+ .driver = {
+ .name = "qnap-mcu-leds",
+ },
+};
+module_platform_driver(qnap_mcu_leds_driver);
+
+MODULE_ALIAS("platform:qnap-mcu-leds");
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("QNAP MCU LEDs driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-rb532.c b/drivers/leds/leds-rb532.c
index e66f73879c8e..782e1c11ee44 100644
--- a/drivers/leds/leds-rb532.c
+++ b/drivers/leds/leds-rb532.c
@@ -49,7 +49,7 @@ static void rb532_led_remove(struct platform_device *pdev)
static struct platform_driver rb532_led_driver = {
.probe = rb532_led_probe,
- .remove_new = rb532_led_remove,
+ .remove = rb532_led_remove,
.driver = {
.name = "rb532-led",
},
diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c
index 848e929c4a61..ade64629431a 100644
--- a/drivers/leds/leds-regulator.c
+++ b/drivers/leds/leds-regulator.c
@@ -193,7 +193,7 @@ static struct platform_driver regulator_led_driver = {
.of_match_table = regulator_led_of_match,
},
.probe = regulator_led_probe,
- .remove_new = regulator_led_remove,
+ .remove = regulator_led_remove,
};
module_platform_driver(regulator_led_driver);
diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c
index f04db793e8d6..0c5169773949 100644
--- a/drivers/leds/leds-sc27xx-bltc.c
+++ b/drivers/leds/leds-sc27xx-bltc.c
@@ -276,7 +276,7 @@ static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
static int sc27xx_led_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
- struct device_node *np = dev_of_node(dev), *child;
+ struct device_node *np = dev_of_node(dev);
struct sc27xx_led_priv *priv;
u32 base, count, reg;
int err;
@@ -304,17 +304,13 @@ static int sc27xx_led_probe(struct platform_device *pdev)
return err;
}
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
err = of_property_read_u32(child, "reg", &reg);
- if (err) {
- of_node_put(child);
+ if (err)
return err;
- }
- if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active) {
- of_node_put(child);
+ if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active)
return -EINVAL;
- }
priv->leds[reg].fwnode = of_fwnode_handle(child);
priv->leds[reg].active = true;
@@ -348,7 +344,7 @@ static struct platform_driver sc27xx_led_driver = {
.of_match_table = sc27xx_led_of_match,
},
.probe = sc27xx_led_probe,
- .remove_new = sc27xx_led_remove,
+ .remove = sc27xx_led_remove,
};
module_platform_driver(sc27xx_led_driver);
diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c
index 96296db5f410..d24d0ddf347c 100644
--- a/drivers/leds/leds-spi-byte.c
+++ b/drivers/leds/leds-spi-byte.c
@@ -29,10 +29,11 @@
*/
#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
-#include <linux/of.h>
-#include <linux/spi/spi.h>
#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
#include <uapi/linux/uleds.h>
struct spi_byte_chipdef {
@@ -55,13 +56,6 @@ static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = {
.max_value = 0x3F,
};
-static const struct of_device_id spi_byte_dt_ids[] = {
- { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef },
- {},
-};
-
-MODULE_DEVICE_TABLE(of, spi_byte_dt_ids);
-
static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
enum led_brightness brightness)
{
@@ -80,73 +74,60 @@ static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
static int spi_byte_probe(struct spi_device *spi)
{
- struct device_node *child;
+ struct fwnode_handle *child __free(fwnode_handle) = NULL;
struct device *dev = &spi->dev;
struct spi_byte_led *led;
struct led_init_data init_data = {};
- const char *state;
+ enum led_default_state state;
int ret;
- if (of_get_available_child_count(dev_of_node(dev)) != 1) {
+ if (device_get_child_node_count(dev) != 1) {
dev_err(dev, "Device must have exactly one LED sub-node.");
return -EINVAL;
}
- child = of_get_next_available_child(dev_of_node(dev), NULL);
led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
if (!led)
return -ENOMEM;
+ ret = devm_mutex_init(dev, &led->mutex);
+ if (ret)
+ return ret;
+
led->spi = spi;
- mutex_init(&led->mutex);
led->cdef = device_get_match_data(dev);
led->ldev.brightness = LED_OFF;
led->ldev.max_brightness = led->cdef->max_value - led->cdef->off_value;
led->ldev.brightness_set_blocking = spi_byte_brightness_set_blocking;
- state = of_get_property(child, "default-state", NULL);
- if (state) {
- if (!strcmp(state, "on")) {
- led->ldev.brightness = led->ldev.max_brightness;
- } else if (strcmp(state, "off")) {
- /* all other cases except "off" */
- dev_err(dev, "default-state can only be 'on' or 'off'");
- return -EINVAL;
- }
- }
+ child = device_get_next_child_node(dev, NULL);
+
+ state = led_init_default_state_get(child);
+ if (state == LEDS_DEFSTATE_ON)
+ led->ldev.brightness = led->ldev.max_brightness;
spi_byte_brightness_set_blocking(&led->ldev,
led->ldev.brightness);
- init_data.fwnode = of_fwnode_handle(child);
+ init_data.fwnode = child;
init_data.devicename = "leds-spi-byte";
init_data.default_label = ":";
- ret = devm_led_classdev_register_ext(&spi->dev, &led->ldev, &init_data);
- if (ret) {
- mutex_destroy(&led->mutex);
- return ret;
- }
- spi_set_drvdata(spi, led);
-
- return 0;
+ return devm_led_classdev_register_ext(dev, &led->ldev, &init_data);
}
-static void spi_byte_remove(struct spi_device *spi)
-{
- struct spi_byte_led *led = spi_get_drvdata(spi);
-
- mutex_destroy(&led->mutex);
-}
+static const struct of_device_id spi_byte_dt_ids[] = {
+ { .compatible = "ubnt,acb-spi-led", .data = &ubnt_acb_spi_led_cdef },
+ {}
+};
+MODULE_DEVICE_TABLE(of, spi_byte_dt_ids);
static struct spi_driver spi_byte_driver = {
.probe = spi_byte_probe,
- .remove = spi_byte_remove,
.driver = {
.name = KBUILD_MODNAME,
.of_match_table = spi_byte_dt_ids,
},
};
-
module_spi_driver(spi_byte_driver);
MODULE_AUTHOR("Christian Mauderer <oss@c-mauderer.de>");
diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c
index fcaa34706b6c..f24ca75c7cb1 100644
--- a/drivers/leds/leds-ss4200.c
+++ b/drivers/leds/leds-ss4200.c
@@ -356,8 +356,10 @@ static int ich7_lpc_probe(struct pci_dev *dev,
nas_gpio_pci_dev = dev;
status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base);
- if (status)
+ if (status) {
+ status = pcibios_err_to_errno(status);
goto out;
+ }
g_pm_io_base &= 0x00000ff80;
status = pci_read_config_dword(dev, GPIO_CTRL, &gc);
@@ -369,8 +371,9 @@ static int ich7_lpc_probe(struct pci_dev *dev,
}
status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base);
- if (0 > status) {
+ if (status) {
dev_info(&dev->dev, "Unable to read GPIOBASE.\n");
+ status = pcibios_err_to_errno(status);
goto out;
}
dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base);
@@ -448,7 +451,7 @@ static ssize_t blink_show(struct device *dev,
int blinking = 0;
if (nasgpio_led_get_attr(led, GPO_BLINK))
blinking = 1;
- return sprintf(buf, "%u\n", blinking);
+ return sprintf(buf, "%d\n", blinking);
}
static ssize_t blink_store(struct device *dev,
diff --git a/drivers/leds/leds-st1202.c b/drivers/leds/leds-st1202.c
new file mode 100644
index 000000000000..4e5dd76d714d
--- /dev/null
+++ b/drivers/leds/leds-st1202.c
@@ -0,0 +1,416 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for STMicroelectronics LED1202 chip
+ *
+ * Copyright (C) 2024 Remote-Tech Ltd. UK
+ */
+
+#include <linux/cleanup.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#define ST1202_CHAN_DISABLE_ALL 0x00
+#define ST1202_CHAN_ENABLE_HIGH 0x03
+#define ST1202_CHAN_ENABLE_LOW 0x02
+#define ST1202_CONFIG_REG 0x04
+/* PATS: Pattern sequence feature enable */
+#define ST1202_CONFIG_REG_PATS BIT(7)
+/* PATSR: Pattern sequence runs (self-clear when sequence is finished) */
+#define ST1202_CONFIG_REG_PATSR BIT(6)
+#define ST1202_CONFIG_REG_SHFT BIT(3)
+#define ST1202_DEV_ENABLE 0x01
+#define ST1202_DEV_ENABLE_ON BIT(0)
+#define ST1202_DEV_ENABLE_RESET BIT(7)
+#define ST1202_DEVICE_ID 0x00
+#define ST1202_ILED_REG0 0x09
+#define ST1202_MAX_LEDS 12
+#define ST1202_MAX_PATTERNS 8
+#define ST1202_MILLIS_PATTERN_DUR_MAX 5660
+#define ST1202_MILLIS_PATTERN_DUR_MIN 22
+#define ST1202_PATTERN_DUR 0x16
+#define ST1202_PATTERN_PWM 0x1E
+#define ST1202_PATTERN_REP 0x15
+
+struct st1202_led {
+ struct fwnode_handle *fwnode;
+ struct led_classdev led_cdev;
+ struct st1202_chip *chip;
+ bool is_active;
+ int led_num;
+};
+
+struct st1202_chip {
+ struct i2c_client *client;
+ struct mutex lock;
+ struct st1202_led leds[ST1202_MAX_LEDS];
+};
+
+static struct st1202_led *cdev_to_st1202_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct st1202_led, led_cdev);
+}
+
+static int st1202_read_reg(struct st1202_chip *chip, int reg, uint8_t *val)
+{
+ struct device *dev = &chip->client->dev;
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(chip->client, reg);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read register [0x%x]: %d\n", reg, ret);
+ return ret;
+ }
+
+ *val = (uint8_t)ret;
+ return 0;
+}
+
+static int st1202_write_reg(struct st1202_chip *chip, int reg, uint8_t val)
+{
+ struct device *dev = &chip->client->dev;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+ if (ret != 0)
+ dev_err(dev, "Failed to write %d to register [0x%x]: %d\n", val, reg, ret);
+
+ return ret;
+}
+
+static uint8_t st1202_prescalar_to_miliseconds(unsigned int value)
+{
+ return value / ST1202_MILLIS_PATTERN_DUR_MIN - 1;
+}
+
+static int st1202_pwm_pattern_write(struct st1202_chip *chip, int led_num,
+ int pattern, unsigned int value)
+{
+ u8 value_l, value_h;
+ int ret;
+
+ value_l = (u8)value;
+ value_h = (u8)(value >> 8);
+
+ /*
+ * Datasheet: Register address low = 1Eh + 2*(xh) + 18h*(yh),
+ * where x is the channel number (led number) in hexadecimal (x = 00h .. 0Bh)
+ * and y is the pattern number in hexadecimal (y = 00h .. 07h)
+ */
+ ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + (led_num * 2) + 0x18 * pattern),
+ value_l);
+ if (ret != 0)
+ return ret;
+
+ /*
+ * Datasheet: Register address high = 1Eh + 01h + 2(xh) +18h*(yh),
+ * where x is the channel number in hexadecimal (x = 00h .. 0Bh)
+ * and y is the pattern number in hexadecimal (y = 00h .. 07h)
+ */
+ ret = st1202_write_reg(chip, (ST1202_PATTERN_PWM + 0x1 + (led_num * 2) + 0x18 * pattern),
+ value_h);
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_duration_pattern_write(struct st1202_chip *chip, int pattern,
+ unsigned int value)
+{
+ return st1202_write_reg(chip, (ST1202_PATTERN_DUR + pattern),
+ st1202_prescalar_to_miliseconds(value));
+}
+
+static void st1202_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct st1202_led *led = cdev_to_st1202_led(led_cdev);
+ struct st1202_chip *chip = led->chip;
+
+ guard(mutex)(&chip->lock);
+
+ st1202_write_reg(chip, ST1202_ILED_REG0 + led->led_num, value);
+}
+
+static enum led_brightness st1202_brightness_get(struct led_classdev *led_cdev)
+{
+ struct st1202_led *led = cdev_to_st1202_led(led_cdev);
+ struct st1202_chip *chip = led->chip;
+ u8 value = 0;
+
+ guard(mutex)(&chip->lock);
+
+ st1202_read_reg(chip, ST1202_ILED_REG0 + led->led_num, &value);
+
+ return value;
+}
+
+static int st1202_channel_set(struct st1202_chip *chip, int led_num, bool active)
+{
+ u8 chan_low, chan_high;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ if (led_num <= 7) {
+ ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_LOW, &chan_low);
+ if (ret < 0)
+ return ret;
+
+ chan_low = active ? chan_low | BIT(led_num) : chan_low & ~BIT(led_num);
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, chan_low);
+ if (ret < 0)
+ return ret;
+
+ } else {
+ ret = st1202_read_reg(chip, ST1202_CHAN_ENABLE_HIGH, &chan_high);
+ if (ret < 0)
+ return ret;
+
+ chan_high = active ? chan_high | (BIT(led_num) >> 8) :
+ chan_high & ~(BIT(led_num) >> 8);
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, chan_high);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int st1202_led_set(struct led_classdev *ldev, enum led_brightness value)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+
+ return st1202_channel_set(led->chip, led->led_num, !!value);
+}
+
+static int st1202_led_pattern_clear(struct led_classdev *ldev)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ for (int patt = 0; patt < ST1202_MAX_PATTERNS; patt++) {
+ ret = st1202_pwm_pattern_write(chip, led->led_num, patt, LED_OFF);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, patt, ST1202_MILLIS_PATTERN_DUR_MIN);
+ if (ret != 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int st1202_led_pattern_set(struct led_classdev *ldev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct st1202_led *led = cdev_to_st1202_led(ldev);
+ struct st1202_chip *chip = led->chip;
+ int ret;
+
+ if (len > ST1202_MAX_PATTERNS)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ for (int patt = 0; patt < len; patt++) {
+ if (pattern[patt].delta_t < ST1202_MILLIS_PATTERN_DUR_MIN ||
+ pattern[patt].delta_t > ST1202_MILLIS_PATTERN_DUR_MAX)
+ return -EINVAL;
+
+ ret = st1202_pwm_pattern_write(chip, led->led_num, patt, pattern[patt].brightness);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_duration_pattern_write(chip, patt, pattern[patt].delta_t);
+ if (ret != 0)
+ return ret;
+ }
+
+ ret = st1202_write_reg(chip, ST1202_PATTERN_REP, repeat);
+ if (ret != 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, (ST1202_CONFIG_REG_PATSR |
+ ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_SHFT));
+ if (ret != 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_dt_init(struct st1202_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+ struct st1202_led *led;
+ int err, reg;
+
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+ err = of_property_read_u32(child, "reg", &reg);
+ if (err)
+ return dev_err_probe(dev, err, "Invalid register\n");
+
+ led = &chip->leds[reg];
+ led->is_active = true;
+ led->fwnode = of_fwnode_handle(child);
+
+ led->led_cdev.max_brightness = U8_MAX;
+ led->led_cdev.brightness_set_blocking = st1202_led_set;
+ led->led_cdev.pattern_set = st1202_led_pattern_set;
+ led->led_cdev.pattern_clear = st1202_led_pattern_clear;
+ led->led_cdev.default_trigger = "pattern";
+ led->led_cdev.brightness_set = st1202_brightness_set;
+ led->led_cdev.brightness_get = st1202_brightness_get;
+ }
+
+ return 0;
+}
+
+static int st1202_setup(struct st1202_chip *chip)
+{
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ /*
+ * Once the supply voltage is applied, the LED1202 executes some internal checks.
+ * Afterwards, it stops the oscillator and puts the internal LDO in quiescent mode.
+ * To start the device, EN bit must be set inside the “Device Enable” register at
+ * address 01h. As soon as EN is set, the LED1202 loads the adjustment parameters
+ * from the internal non-volatile memory and performs an auto-calibration procedure
+ * in order to increase the output current precision.
+ * Such initialization lasts about 6.5 ms.
+ */
+
+ /* Reset the chip during setup */
+ ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_RESET);
+ if (ret < 0)
+ return ret;
+
+ /* Enable phase-shift delay feature */
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG, ST1202_CONFIG_REG_SHFT);
+ if (ret < 0)
+ return ret;
+
+ /* Enable the device */
+ ret = st1202_write_reg(chip, ST1202_DEV_ENABLE, ST1202_DEV_ENABLE_ON);
+ if (ret < 0)
+ return ret;
+
+ /* Duration of initialization */
+ usleep_range(6500, 10000);
+
+ /* Deactivate all LEDS (channels) and activate only the ones found in Device Tree */
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_LOW, ST1202_CHAN_DISABLE_ALL);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CHAN_ENABLE_HIGH, ST1202_CHAN_DISABLE_ALL);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_write_reg(chip, ST1202_CONFIG_REG,
+ ST1202_CONFIG_REG_PATS | ST1202_CONFIG_REG_PATSR);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int st1202_probe(struct i2c_client *client)
+{
+ struct st1202_chip *chip;
+ struct st1202_led *led;
+ int ret;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA))
+ return dev_err_probe(&client->dev, -EIO, "SMBUS Byte Data not Supported\n");
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&client->dev, &chip->lock);
+ if (ret < 0)
+ return ret;
+ chip->client = client;
+
+ ret = st1202_setup(chip);
+ if (ret < 0)
+ return ret;
+
+ ret = st1202_dt_init(chip);
+ if (ret < 0)
+ return ret;
+
+ for (int i = 0; i < ST1202_MAX_LEDS; i++) {
+ struct led_init_data init_data = {};
+ led = &chip->leds[i];
+ led->chip = chip;
+ led->led_num = i;
+
+ if (!led->is_active)
+ continue;
+
+ ret = st1202_channel_set(led->chip, led->led_num, true);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to activate LED channel\n");
+
+ ret = st1202_led_pattern_clear(&led->led_cdev);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to clear LED pattern\n");
+
+ init_data.fwnode = led->fwnode;
+ init_data.devicename = "st1202";
+ init_data.default_label = ":";
+
+ ret = devm_led_classdev_register_ext(&client->dev, &led->led_cdev, &init_data);
+ if (ret < 0)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to register LED class device\n");
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id st1202_id[] = {
+ { "st1202-i2c" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, st1202_id);
+
+static const struct of_device_id st1202_dt_ids[] = {
+ { .compatible = "st,led1202" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, st1202_dt_ids);
+
+static struct i2c_driver st1202_driver = {
+ .driver = {
+ .name = "leds-st1202",
+ .of_match_table = of_match_ptr(st1202_dt_ids),
+ },
+ .probe = st1202_probe,
+ .id_table = st1202_id,
+};
+module_i2c_driver(st1202_driver);
+
+MODULE_AUTHOR("Remote Tech LTD");
+MODULE_DESCRIPTION("STMicroelectronics LED1202 : 12-channel constant current LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-sun50i-a100.c b/drivers/leds/leds-sun50i-a100.c
index 62d21c3a3575..2c9bd360ab81 100644
--- a/drivers/leds/leds-sun50i-a100.c
+++ b/drivers/leds/leds-sun50i-a100.c
@@ -252,18 +252,16 @@ static int sun50i_a100_ledc_parse_format(struct device *dev,
struct sun50i_a100_ledc *priv)
{
const char *format = "grb";
- u32 i;
+ int i;
device_property_read_string(dev, "allwinner,pixel-format", &format);
- for (i = 0; i < ARRAY_SIZE(sun50i_a100_ledc_formats); i++) {
- if (!strcmp(format, sun50i_a100_ledc_formats[i])) {
- priv->format = i;
- return 0;
- }
- }
+ i = match_string(sun50i_a100_ledc_formats, ARRAY_SIZE(sun50i_a100_ledc_formats), format);
+ if (i < 0)
+ return dev_err_probe(dev, i, "Bad pixel format '%s'\n", format);
- return dev_err_probe(dev, -EINVAL, "Bad pixel format '%s'\n", format);
+ priv->format = i;
+ return 0;
}
static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv)
@@ -370,7 +368,7 @@ static int sun50i_a100_ledc_suspend(struct device *dev)
if (!xfer_active)
break;
- msleep(1);
+ usleep_range(1000, 1100);
}
clk_disable_unprepare(priv->mod_clk);
@@ -394,7 +392,6 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev)
struct sun50i_a100_ledc_led *led;
struct device *dev = &pdev->dev;
struct sun50i_a100_ledc *priv;
- struct fwnode_handle *child;
struct resource *mem;
u32 max_addr = 0;
u32 num_leds = 0;
@@ -404,21 +401,17 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev)
* The maximum LED address must be known in sun50i_a100_ledc_resume() before
* class device registration, so parse and validate the subnodes up front.
*/
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
u32 addr, color;
ret = fwnode_property_read_u32(child, "reg", &addr);
- if (ret || addr >= LEDC_MAX_LEDS) {
- fwnode_handle_put(child);
+ if (ret || addr >= LEDC_MAX_LEDS)
return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n",
LEDC_MAX_LEDS - 1);
- }
ret = fwnode_property_read_u32(child, "color", &color);
- if (ret || color != LED_COLOR_ID_RGB) {
- fwnode_handle_put(child);
+ if (ret || color != LED_COLOR_ID_RGB)
return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n");
- }
max_addr = max(max_addr, addr);
num_leds++;
@@ -504,7 +497,7 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev)
return ret;
led = priv->leds;
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_classdev *cdev;
/* The node was already validated above. */
@@ -529,7 +522,11 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev)
ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
if (ret) {
dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr);
- goto err_put_child;
+ while (led-- > priv->leds)
+ led_classdev_multicolor_unregister(&led->mc_cdev);
+ sun50i_a100_ledc_suspend(&pdev->dev);
+
+ return ret;
}
led++;
@@ -538,14 +535,6 @@ static int sun50i_a100_ledc_probe(struct platform_device *pdev)
dev_info(dev, "Registered %u LEDs\n", num_leds);
return 0;
-
-err_put_child:
- fwnode_handle_put(child);
- while (led-- > priv->leds)
- led_classdev_multicolor_unregister(&led->mc_cdev);
- sun50i_a100_ledc_suspend(&pdev->dev);
-
- return ret;
}
static void sun50i_a100_ledc_remove(struct platform_device *pdev)
@@ -569,7 +558,7 @@ static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm,
static struct platform_driver sun50i_a100_ledc_driver = {
.probe = sun50i_a100_ledc_probe,
- .remove_new = sun50i_a100_ledc_remove,
+ .remove = sun50i_a100_ledc_remove,
.shutdown = sun50i_a100_ledc_remove,
.driver = {
.name = "sun50i-a100-ledc",
diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c
index a621e5e5c75c..bd24e7f5947a 100644
--- a/drivers/leds/leds-sunfire.c
+++ b/drivers/leds/leds-sunfire.c
@@ -219,7 +219,7 @@ MODULE_ALIAS("platform:sunfire-fhc-leds");
static struct platform_driver sunfire_clockboard_led_driver = {
.probe = sunfire_clockboard_led_probe,
- .remove_new = sunfire_led_generic_remove,
+ .remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-clockboard-leds",
},
@@ -227,7 +227,7 @@ static struct platform_driver sunfire_clockboard_led_driver = {
static struct platform_driver sunfire_fhc_led_driver = {
.probe = sunfire_fhc_led_probe,
- .remove_new = sunfire_led_generic_remove,
+ .remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-fhc-leds",
},
diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index 4f22f4224946..acbd8169723c 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -658,7 +658,6 @@ static struct tca6507_platform_data *
tca6507_led_dt_init(struct device *dev)
{
struct tca6507_platform_data *pdata;
- struct fwnode_handle *child;
struct led_info *tca_leds;
int count;
@@ -671,7 +670,7 @@ tca6507_led_dt_init(struct device *dev)
if (!tca_leds)
return ERR_PTR(-ENOMEM);
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_info led;
u32 reg;
int ret;
@@ -688,10 +687,8 @@ tca6507_led_dt_init(struct device *dev)
led.flags |= TCA6507_MAKE_GPIO;
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret || reg >= NUM_LEDS) {
- fwnode_handle_put(child);
+ if (ret || reg >= NUM_LEDS)
return ERR_PTR(ret ? : -EINVAL);
- }
tca_leds[reg] = led;
}
diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c
index 945e831ef4ac..6605e08a042a 100644
--- a/drivers/leds/leds-tlc591xx.c
+++ b/drivers/leds/leds-tlc591xx.c
@@ -146,7 +146,7 @@ MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
static int
tlc591xx_probe(struct i2c_client *client)
{
- struct device_node *np, *child;
+ struct device_node *np;
struct device *dev = &client->dev;
const struct tlc591xx *tlc591xx;
struct tlc591xx_priv *priv;
@@ -182,22 +182,20 @@ tlc591xx_probe(struct i2c_client *client)
if (err < 0)
return err;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
struct tlc591xx_led *led;
struct led_init_data init_data = {};
init_data.fwnode = of_fwnode_handle(child);
err = of_property_read_u32(child, "reg", &reg);
- if (err) {
- of_node_put(child);
+ if (err)
return err;
- }
+
if (reg < 0 || reg >= tlc591xx->max_leds ||
- priv->leds[reg].active) {
- of_node_put(child);
+ priv->leds[reg].active)
return -EINVAL;
- }
+
led = &priv->leds[reg];
led->active = true;
@@ -207,12 +205,10 @@ tlc591xx_probe(struct i2c_client *client)
led->ldev.max_brightness = TLC591XX_MAX_BRIGHTNESS;
err = devm_led_classdev_register_ext(dev, &led->ldev,
&init_data);
- if (err < 0) {
- of_node_put(child);
+ if (err < 0)
return dev_err_probe(dev, err,
"couldn't register LED %s\n",
led->ldev.name);
- }
}
return 0;
}
diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
index b443f8c989fa..4fe1a9c0bc1b 100644
--- a/drivers/leds/leds-turris-omnia.c
+++ b/drivers/leds/leds-turris-omnia.c
@@ -2,7 +2,7 @@
/*
* CZ.NIC's Turris Omnia LEDs driver
*
- * 2020, 2023 by Marek Behún <kabel@kernel.org>
+ * 2020, 2023, 2024 by Marek Behún <kabel@kernel.org>
*/
#include <linux/i2c.h>
@@ -10,36 +10,23 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/of.h>
-#include "leds.h"
+#include <linux/turris-omnia-mcu-interface.h>
#define OMNIA_BOARD_LEDS 12
#define OMNIA_LED_NUM_CHANNELS 3
-/* MCU controller commands at I2C address 0x2a */
-#define OMNIA_MCU_I2C_ADDR 0x2a
-
-#define CMD_GET_STATUS_WORD 0x01
-#define STS_FEATURES_SUPPORTED BIT(2)
-
-#define CMD_GET_FEATURES 0x10
-#define FEAT_LED_GAMMA_CORRECTION BIT(5)
-
-/* LED controller commands at I2C address 0x2b */
-#define CMD_LED_MODE 0x03
-#define CMD_LED_MODE_LED(l) ((l) & 0x0f)
-#define CMD_LED_MODE_USER 0x10
-
-#define CMD_LED_STATE 0x04
-#define CMD_LED_STATE_LED(l) ((l) & 0x0f)
-#define CMD_LED_STATE_ON 0x10
-
-#define CMD_LED_COLOR 0x05
-#define CMD_LED_SET_BRIGHTNESS 0x07
-#define CMD_LED_GET_BRIGHTNESS 0x08
-
-#define CMD_SET_GAMMA_CORRECTION 0x30
-#define CMD_GET_GAMMA_CORRECTION 0x31
-
+/* MCU controller I2C address 0x2a, needed for detecting MCU features */
+#define OMNIA_MCU_I2C_ADDR 0x2a
+
+/**
+ * struct omnia_led - per-LED part of driver private data structure
+ * @mc_cdev: multi-color LED class device
+ * @subled_info: per-channel information
+ * @cached_channels: cached values of per-channel brightness that was sent to the MCU
+ * @on: whether the LED was set on
+ * @hwtrig: whether the LED blinking was offloaded to the MCU
+ * @reg: LED identifier to the MCU
+ */
struct omnia_led {
struct led_classdev_mc mc_cdev;
struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
@@ -50,73 +37,38 @@ struct omnia_led {
#define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
+/**
+ * struct omnia_leds - driver private data structure
+ * @client: I2C client device
+ * @lock: mutex to protect cached state
+ * @has_gamma_correction: whether the MCU firmware supports gamma correction
+ * @brightness_knode: kernel node of the "brightness" device sysfs attribute (this is the
+ * driver specific global brightness, not the LED classdev brightness)
+ * @leds: flexible array of per-LED data
+ */
struct omnia_leds {
struct i2c_client *client;
struct mutex lock;
bool has_gamma_correction;
+ struct kernfs_node *brightness_knode;
struct omnia_led leds[];
};
-static int omnia_cmd_write_u8(const struct i2c_client *client, u8 cmd, u8 val)
-{
- u8 buf[2] = { cmd, val };
- int ret;
-
- ret = i2c_master_send(client, buf, sizeof(buf));
-
- return ret < 0 ? ret : 0;
-}
-
-static int omnia_cmd_read_raw(struct i2c_adapter *adapter, u8 addr, u8 cmd,
- void *reply, size_t len)
-{
- struct i2c_msg msgs[2];
- int ret;
-
- msgs[0].addr = addr;
- msgs[0].flags = 0;
- msgs[0].len = 1;
- msgs[0].buf = &cmd;
- msgs[1].addr = addr;
- msgs[1].flags = I2C_M_RD;
- msgs[1].len = len;
- msgs[1].buf = reply;
-
- ret = i2c_transfer(adapter, msgs, ARRAY_SIZE(msgs));
- if (likely(ret == ARRAY_SIZE(msgs)))
- return 0;
- else if (ret < 0)
- return ret;
- else
- return -EIO;
-}
-
-static int omnia_cmd_read_u8(const struct i2c_client *client, u8 cmd)
+static int omnia_cmd_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b)
{
- u8 reply;
- int err;
+ u8 buf[5] = { OMNIA_CMD_LED_COLOR, led, r, g, b };
- err = omnia_cmd_read_raw(client->adapter, client->addr, cmd, &reply, 1);
- if (err)
- return err;
-
- return reply;
+ return omnia_cmd_write(client, buf, sizeof(buf));
}
static int omnia_led_send_color_cmd(const struct i2c_client *client,
struct omnia_led *led)
{
- char cmd[5];
int ret;
- cmd[0] = CMD_LED_COLOR;
- cmd[1] = led->reg;
- cmd[2] = led->subled_info[0].brightness;
- cmd[3] = led->subled_info[1].brightness;
- cmd[4] = led->subled_info[2].brightness;
-
/* Send the color change command */
- ret = i2c_master_send(client, cmd, 5);
+ ret = omnia_cmd_set_color(client, led->reg, led->subled_info[0].brightness,
+ led->subled_info[1].brightness, led->subled_info[2].brightness);
if (ret < 0)
return ret;
@@ -171,12 +123,12 @@ static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
* is not being blinked by HW.
*/
if (!err && !led->hwtrig && !brightness != !led->on) {
- u8 state = CMD_LED_STATE_LED(led->reg);
+ u8 state = OMNIA_CMD_LED_STATE_LED(led->reg);
if (brightness)
- state |= CMD_LED_STATE_ON;
+ state |= OMNIA_CMD_LED_STATE_ON;
- err = omnia_cmd_write_u8(leds->client, CMD_LED_STATE, state);
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_STATE, state);
if (!err)
led->on = !!brightness;
}
@@ -211,8 +163,8 @@ static int omnia_hwtrig_activate(struct led_classdev *cdev)
if (!err) {
/* Put the LED into MCU controlled mode */
- err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
- CMD_LED_MODE_LED(led->reg));
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE,
+ OMNIA_CMD_LED_MODE_LED(led->reg));
if (!err)
led->hwtrig = true;
}
@@ -233,9 +185,8 @@ static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
led->hwtrig = false;
/* Put the LED into software mode */
- err = omnia_cmd_write_u8(leds->client, CMD_LED_MODE,
- CMD_LED_MODE_LED(led->reg) |
- CMD_LED_MODE_USER);
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE,
+ OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER);
mutex_unlock(&leds->lock);
@@ -301,38 +252,26 @@ static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
*/
cdev->default_trigger = omnia_hw_trigger.name;
- /* put the LED into software mode */
- ret = omnia_cmd_write_u8(client, CMD_LED_MODE,
- CMD_LED_MODE_LED(led->reg) |
- CMD_LED_MODE_USER);
- if (ret) {
- dev_err(dev, "Cannot set LED %pOF to software mode: %i\n", np,
- ret);
- return ret;
- }
+ /* Put the LED into software mode */
+ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) |
+ OMNIA_CMD_LED_MODE_USER);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF to software mode\n", np);
- /* disable the LED */
- ret = omnia_cmd_write_u8(client, CMD_LED_STATE,
- CMD_LED_STATE_LED(led->reg));
- if (ret) {
- dev_err(dev, "Cannot set LED %pOF brightness: %i\n", np, ret);
- return ret;
- }
+ /* Disable the LED */
+ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg));
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF brightness\n", np);
/* Set initial color and cache it */
ret = omnia_led_send_color_cmd(client, led);
- if (ret < 0) {
- dev_err(dev, "Cannot set LED %pOF initial color: %i\n", np,
- ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF initial color\n", np);
ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
&init_data);
- if (ret < 0) {
- dev_err(dev, "Cannot register LED %pOF: %i\n", np, ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot register LED %pOF\n", np);
return 1;
}
@@ -352,14 +291,14 @@ static ssize_t brightness_show(struct device *dev, struct device_attribute *a,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
- int ret;
-
- ret = omnia_cmd_read_u8(client, CMD_LED_GET_BRIGHTNESS);
+ u8 reply;
+ int err;
- if (ret < 0)
- return ret;
+ err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_BRIGHTNESS, &reply);
+ if (err < 0)
+ return err;
- return sysfs_emit(buf, "%d\n", ret);
+ return sysfs_emit(buf, "%d\n", reply);
}
static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
@@ -375,7 +314,7 @@ static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
if (brightness > 100)
return -EINVAL;
- err = omnia_cmd_write_u8(client, CMD_LED_SET_BRIGHTNESS, brightness);
+ err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_BRIGHTNESS, brightness);
return err ?: count;
}
@@ -386,17 +325,16 @@ static ssize_t gamma_correction_show(struct device *dev,
{
struct i2c_client *client = to_i2c_client(dev);
struct omnia_leds *leds = i2c_get_clientdata(client);
- int ret;
+ u8 reply = 0;
+ int err;
if (leds->has_gamma_correction) {
- ret = omnia_cmd_read_u8(client, CMD_GET_GAMMA_CORRECTION);
- if (ret < 0)
- return ret;
- } else {
- ret = 0;
+ err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_GAMMA_CORRECTION, &reply);
+ if (err < 0)
+ return err;
}
- return sysfs_emit(buf, "%d\n", !!ret);
+ return sysfs_emit(buf, "%d\n", !!reply);
}
static ssize_t gamma_correction_store(struct device *dev,
@@ -414,7 +352,7 @@ static ssize_t gamma_correction_store(struct device *dev,
if (kstrtobool(buf, &val) < 0)
return -EINVAL;
- err = omnia_cmd_write_u8(client, CMD_SET_GAMMA_CORRECTION, val);
+ err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_GAMMA_CORRECTION, val);
return err ?: count;
}
@@ -427,44 +365,119 @@ static struct attribute *omnia_led_controller_attrs[] = {
};
ATTRIBUTE_GROUPS(omnia_led_controller);
-static int omnia_mcu_get_features(const struct i2c_client *client)
+static irqreturn_t omnia_brightness_changed_threaded_fn(int irq, void *data)
+{
+ struct omnia_leds *leds = data;
+
+ if (unlikely(!leds->brightness_knode)) {
+ /*
+ * Note that sysfs_get_dirent() may sleep. This is okay, because we are in threaded
+ * context.
+ */
+ leds->brightness_knode = sysfs_get_dirent(leds->client->dev.kobj.sd, "brightness");
+ if (!leds->brightness_knode)
+ return IRQ_NONE;
+ }
+
+ sysfs_notify_dirent(leds->brightness_knode);
+
+ return IRQ_HANDLED;
+}
+
+static void omnia_brightness_knode_put(void *data)
+{
+ struct omnia_leds *leds = data;
+
+ if (leds->brightness_knode)
+ sysfs_put(leds->brightness_knode);
+}
+
+static int omnia_request_brightness_irq(struct omnia_leds *leds)
+{
+ struct device *dev = &leds->client->dev;
+ int ret;
+
+ if (!leds->client->irq) {
+ dev_info(dev,
+ "Brightness change interrupt supported by MCU firmware but not described in device-tree\n");
+
+ return 0;
+ }
+
+ /*
+ * Registering the brightness_knode destructor before requesting the IRQ ensures that on
+ * removal the brightness_knode sysfs node is put only after the IRQ is freed.
+ * This is needed because the interrupt handler uses the knode.
+ */
+ ret = devm_add_action(dev, omnia_brightness_knode_put, leds);
+ if (ret < 0)
+ return ret;
+
+ return devm_request_threaded_irq(dev, leds->client->irq, NULL,
+ omnia_brightness_changed_threaded_fn, IRQF_ONESHOT,
+ "leds-turris-omnia", leds);
+}
+
+static int omnia_mcu_get_features(const struct i2c_client *mcu_client)
{
u16 reply;
int err;
- err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
- CMD_GET_STATUS_WORD, &reply, sizeof(reply));
+ err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_STATUS_WORD, &reply);
if (err)
return err;
- /* Check whether MCU firmware supports the CMD_GET_FEAUTRES command */
- if (!(le16_to_cpu(reply) & STS_FEATURES_SUPPORTED))
+ /* Check whether MCU firmware supports the OMNIA_CMD_GET_FEAUTRES command */
+ if (!(reply & OMNIA_STS_FEATURES_SUPPORTED))
return 0;
- err = omnia_cmd_read_raw(client->adapter, OMNIA_MCU_I2C_ADDR,
- CMD_GET_FEATURES, &reply, sizeof(reply));
+ err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_FEATURES, &reply);
if (err)
return err;
- return le16_to_cpu(reply);
+ return reply;
+}
+
+static int omnia_match_mcu_client(struct device *dev, const void *data)
+{
+ struct i2c_client *client;
+
+ client = i2c_verify_client(dev);
+ if (!client)
+ return 0;
+
+ return client->addr == OMNIA_MCU_I2C_ADDR;
+}
+
+static int omnia_find_mcu_and_get_features(struct device *dev)
+{
+ struct device *mcu_dev;
+ int ret;
+
+ mcu_dev = device_find_child(dev->parent, NULL, omnia_match_mcu_client);
+ if (!mcu_dev)
+ return -ENODEV;
+
+ ret = omnia_mcu_get_features(i2c_verify_client(mcu_dev));
+
+ put_device(mcu_dev);
+
+ return ret;
}
static int omnia_leds_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
- struct device_node *np = dev_of_node(dev), *child;
+ struct device_node *np = dev_of_node(dev);
struct omnia_leds *leds;
struct omnia_led *led;
int ret, count;
count = of_get_available_child_count(np);
- if (!count) {
- dev_err(dev, "LEDs are not defined in device tree!\n");
- return -ENODEV;
- } else if (count > OMNIA_BOARD_LEDS) {
- dev_err(dev, "Too many LEDs defined in device tree!\n");
- return -EINVAL;
- }
+ if (count == 0)
+ return dev_err_probe(dev, -ENODEV, "LEDs are not defined in device tree!\n");
+ if (count > OMNIA_BOARD_LEDS)
+ return dev_err_probe(dev, -EINVAL, "Too many LEDs defined in device tree!\n");
leds = devm_kzalloc(dev, struct_size(leds, leds, count), GFP_KERNEL);
if (!leds)
@@ -473,36 +486,29 @@ static int omnia_leds_probe(struct i2c_client *client)
leds->client = client;
i2c_set_clientdata(client, leds);
- ret = omnia_mcu_get_features(client);
- if (ret < 0) {
- dev_err(dev, "Cannot determine MCU supported features: %d\n",
- ret);
- return ret;
- }
+ ret = omnia_find_mcu_and_get_features(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot determine MCU supported features\n");
- leds->has_gamma_correction = ret & FEAT_LED_GAMMA_CORRECTION;
- if (!leds->has_gamma_correction) {
- dev_info(dev,
- "Your board's MCU firmware does not support the LED gamma correction feature.\n");
- dev_info(dev,
- "Consider upgrading MCU firmware with the omnia-mcutool utility.\n");
+ leds->has_gamma_correction = ret & OMNIA_FEAT_LED_GAMMA_CORRECTION;
+
+ if (ret & OMNIA_FEAT_BRIGHTNESS_INT) {
+ ret = omnia_request_brightness_irq(leds);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot request brightness IRQ\n");
}
mutex_init(&leds->lock);
ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
- if (ret < 0) {
- dev_err(dev, "Cannot register private LED trigger: %d\n", ret);
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot register private LED trigger\n");
led = &leds->leds[0];
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
ret = omnia_led_register(client, led, child);
- if (ret < 0) {
- of_node_put(child);
+ if (ret < 0)
return ret;
- }
led += ret;
}
@@ -512,29 +518,21 @@ static int omnia_leds_probe(struct i2c_client *client)
static void omnia_leds_remove(struct i2c_client *client)
{
- u8 buf[5];
-
- /* put all LEDs into default (HW triggered) mode */
- omnia_cmd_write_u8(client, CMD_LED_MODE,
- CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
-
- /* set all LEDs color to [255, 255, 255] */
- buf[0] = CMD_LED_COLOR;
- buf[1] = OMNIA_BOARD_LEDS;
- buf[2] = 255;
- buf[3] = 255;
- buf[4] = 255;
+ /* Put all LEDs into default (HW triggered) mode */
+ omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
- i2c_master_send(client, buf, 5);
+ /* Set all LEDs color to [255, 255, 255] */
+ omnia_cmd_set_color(client, OMNIA_BOARD_LEDS, 255, 255, 255);
}
static const struct of_device_id of_omnia_leds_match[] = {
{ .compatible = "cznic,turris-omnia-leds", },
{},
};
+MODULE_DEVICE_TABLE(of, of_omnia_leds_match);
static const struct i2c_device_id omnia_id[] = {
- { "omnia", 0 },
+ { "omnia" },
{ }
};
MODULE_DEVICE_TABLE(i2c, omnia_id);
diff --git a/drivers/leds/leds-upboard.c b/drivers/leds/leds-upboard.c
new file mode 100644
index 000000000000..b350eb294280
--- /dev/null
+++ b/drivers/leds/leds-upboard.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UP board LED driver.
+ *
+ * Copyright (c) AAEON. All rights reserved.
+ * Copyright (C) 2024 Bootlin
+ *
+ * Author: Gary Wang <garywang@aaeon.com.tw>
+ * Author: Thomas Richard <thomas.richard@bootlin.com>
+ */
+
+#include <linux/device.h>
+#include <linux/container_of.h>
+#include <linux/leds.h>
+#include <linux/mfd/upboard-fpga.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define led_cdev_to_led_upboard(c) container_of(c, struct upboard_led, cdev)
+
+struct upboard_led {
+ struct regmap_field *field;
+ struct led_classdev cdev;
+};
+
+struct upboard_led_profile {
+ const char *name;
+ unsigned int bit;
+};
+
+static struct upboard_led_profile upboard_up_led_profile[] = {
+ { "upboard:yellow:" LED_FUNCTION_STATUS, 0 },
+ { "upboard:green:" LED_FUNCTION_STATUS, 1 },
+ { "upboard:red:" LED_FUNCTION_STATUS, 2 },
+};
+
+static struct upboard_led_profile upboard_up2_led_profile[] = {
+ { "upboard:blue:" LED_FUNCTION_STATUS, 0 },
+ { "upboard:yellow:" LED_FUNCTION_STATUS, 1 },
+ { "upboard:green:" LED_FUNCTION_STATUS, 2 },
+ { "upboard:red:" LED_FUNCTION_STATUS, 3 },
+};
+
+static enum led_brightness upboard_led_brightness_get(struct led_classdev *cdev)
+{
+ struct upboard_led *led = led_cdev_to_led_upboard(cdev);
+ int brightness, ret;
+
+ ret = regmap_field_read(led->field, &brightness);
+
+ return ret ? LED_OFF : brightness;
+};
+
+static int upboard_led_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct upboard_led *led = led_cdev_to_led_upboard(cdev);
+
+ return regmap_field_write(led->field, brightness != LED_OFF);
+};
+
+static int upboard_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct upboard_fpga *fpga = dev_get_drvdata(dev->parent);
+ struct upboard_led_profile *led_profile;
+ struct upboard_led *led;
+ int led_instances, ret, i;
+
+ switch (fpga->fpga_data->type) {
+ case UPBOARD_UP_FPGA:
+ led_profile = upboard_up_led_profile;
+ led_instances = ARRAY_SIZE(upboard_up_led_profile);
+ break;
+ case UPBOARD_UP2_FPGA:
+ led_profile = upboard_up2_led_profile;
+ led_instances = ARRAY_SIZE(upboard_up2_led_profile);
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL, "Unknown device type %d\n",
+ fpga->fpga_data->type);
+ }
+
+ for (i = 0; i < led_instances; i++) {
+ const struct reg_field fldconf = {
+ .reg = UPBOARD_REG_FUNC_EN0,
+ .lsb = led_profile[i].bit,
+ .msb = led_profile[i].bit,
+ };
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->field = devm_regmap_field_alloc(&pdev->dev, fpga->regmap, fldconf);
+ if (IS_ERR(led->field))
+ return PTR_ERR(led->field);
+
+ led->cdev.brightness_get = upboard_led_brightness_get;
+ led->cdev.brightness_set_blocking = upboard_led_brightness_set;
+ led->cdev.max_brightness = LED_ON;
+
+ led->cdev.name = led_profile[i].name;
+
+ ret = devm_led_classdev_register(dev, &led->cdev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver upboard_led_driver = {
+ .driver = {
+ .name = "upboard-leds",
+ },
+ .probe = upboard_led_probe,
+};
+
+module_platform_driver(upboard_led_driver);
+
+MODULE_AUTHOR("Gary Wang <garywang@aaeon.com.tw>");
+MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
+MODULE_DESCRIPTION("UP Board LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:upboard-led");
diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c
index 70b32d80f960..05930e9e8887 100644
--- a/drivers/leds/leds-wm831x-status.c
+++ b/drivers/leds/leds-wm831x-status.c
@@ -292,7 +292,7 @@ static struct platform_driver wm831x_status_driver = {
.name = "wm831x-status",
},
.probe = wm831x_status_probe,
- .remove_new = wm831x_status_remove,
+ .remove = wm831x_status_remove,
};
module_platform_driver(wm831x_status_driver);
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c
index 61cbefa05710..87e60ea927b9 100644
--- a/drivers/leds/leds-wm8350.c
+++ b/drivers/leds/leds-wm8350.c
@@ -255,7 +255,7 @@ static struct platform_driver wm8350_led_driver = {
.name = "wm8350-led",
},
.probe = wm8350_led_probe,
- .remove_new = wm8350_led_remove,
+ .remove = wm8350_led_remove,
.shutdown = wm8350_led_shutdown,
};
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 1138e2ab82e5..bee46651e068 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -22,14 +22,13 @@ void led_stop_software_blink(struct led_classdev *led_cdev);
void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value);
void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value);
ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
- struct bin_attribute *attr, char *buf,
+ const struct bin_attribute *attr, char *buf,
loff_t pos, size_t count);
ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
- struct bin_attribute *bin_attr, char *buf,
+ const struct bin_attribute *bin_attr, char *buf,
loff_t pos, size_t count);
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
-extern const char * const led_colors[LED_COLOR_ID_MAX];
#endif /* __LEDS_H_INCLUDED */
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
index 8fc12d6a2958..222d943d826a 100644
--- a/drivers/leds/rgb/Kconfig
+++ b/drivers/leds/rgb/Kconfig
@@ -17,7 +17,6 @@ config LEDS_GROUP_MULTICOLOR
config LEDS_KTD202X
tristate "LED support for KTD202x Chips"
depends on I2C
- depends on OF
select REGMAP_I2C
help
This option enables support for the Kinetic KTD2026/KTD2027
diff --git a/drivers/leds/rgb/leds-group-multicolor.c b/drivers/leds/rgb/leds-group-multicolor.c
index b6c7679015fd..548c7dd63ba1 100644
--- a/drivers/leds/rgb/leds-group-multicolor.c
+++ b/drivers/leds/rgb/leds-group-multicolor.c
@@ -55,7 +55,7 @@ static void restore_sysfs_write_access(void *data)
{
struct led_classdev *led_cdev = data;
- /* Restore the write acccess to the LED */
+ /* Restore the write access to the LED */
mutex_lock(&led_cdev->led_access);
led_sysfs_enable(led_cdev);
mutex_unlock(&led_cdev->led_access);
diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c
index 514965795a10..04e62faa3a00 100644
--- a/drivers/leds/rgb/leds-ktd202x.c
+++ b/drivers/leds/rgb/leds-ktd202x.c
@@ -99,7 +99,7 @@ struct ktd202x {
struct device *dev;
struct regmap *regmap;
bool enabled;
- int num_leds;
+ unsigned long num_leds;
struct ktd202x_led leds[] __counted_by(num_leds);
};
@@ -381,16 +381,19 @@ static int ktd202x_blink_mc_set(struct led_classdev *cdev,
mc->num_colors);
}
-static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
+static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct fwnode_handle *fwnode,
struct ktd202x_led *led, struct led_init_data *init_data)
{
+ struct fwnode_handle *child;
struct led_classdev *cdev;
- struct device_node *child;
struct mc_subled *info;
int num_channels;
int i = 0;
- num_channels = of_get_available_child_count(np);
+ num_channels = 0;
+ fwnode_for_each_available_child_node(fwnode, child)
+ num_channels++;
+
if (!num_channels || num_channels > chip->num_leds)
return -EINVAL;
@@ -398,22 +401,22 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
if (!info)
return -ENOMEM;
- for_each_available_child_of_node(np, child) {
+ fwnode_for_each_available_child_node(fwnode, child) {
u32 mono_color;
u32 reg;
int ret;
- ret = of_property_read_u32(child, "reg", &reg);
+ ret = fwnode_property_read_u32(child, "reg", &reg);
if (ret != 0 || reg >= chip->num_leds) {
- dev_err(chip->dev, "invalid 'reg' of %pOFn\n", child);
- of_node_put(child);
- return -EINVAL;
+ dev_err(chip->dev, "invalid 'reg' of %pfw\n", child);
+ fwnode_handle_put(child);
+ return ret;
}
- ret = of_property_read_u32(child, "color", &mono_color);
+ ret = fwnode_property_read_u32(child, "color", &mono_color);
if (ret < 0 && ret != -EINVAL) {
- dev_err(chip->dev, "failed to parse 'color' of %pOF\n", child);
- of_node_put(child);
+ dev_err(chip->dev, "failed to parse 'color' of %pfw\n", child);
+ fwnode_handle_put(child);
return ret;
}
@@ -433,16 +436,16 @@ static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct device_node *np,
return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data);
}
-static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np,
+static int ktd202x_setup_led_single(struct ktd202x *chip, struct fwnode_handle *fwnode,
struct ktd202x_led *led, struct led_init_data *init_data)
{
struct led_classdev *cdev;
u32 reg;
int ret;
- ret = of_property_read_u32(np, "reg", &reg);
+ ret = fwnode_property_read_u32(fwnode, "reg", &reg);
if (ret != 0 || reg >= chip->num_leds) {
- dev_err(chip->dev, "invalid 'reg' of %pOFn\n", np);
+ dev_err(chip->dev, "invalid 'reg' of %pfw\n", fwnode);
return -EINVAL;
}
led->index = reg;
@@ -454,7 +457,7 @@ static int ktd202x_setup_led_single(struct ktd202x *chip, struct device_node *np
return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data);
}
-static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigned int index)
+static int ktd202x_add_led(struct ktd202x *chip, struct fwnode_handle *fwnode, unsigned int index)
{
struct ktd202x_led *led = &chip->leds[index];
struct led_init_data init_data = {};
@@ -463,21 +466,21 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne
int ret;
/* Color property is optional in single color case */
- ret = of_property_read_u32(np, "color", &color);
+ ret = fwnode_property_read_u32(fwnode, "color", &color);
if (ret < 0 && ret != -EINVAL) {
- dev_err(chip->dev, "failed to parse 'color' of %pOF\n", np);
+ dev_err(chip->dev, "failed to parse 'color' of %pfw\n", fwnode);
return ret;
}
led->chip = chip;
- init_data.fwnode = of_fwnode_handle(np);
+ init_data.fwnode = fwnode;
if (color == LED_COLOR_ID_RGB) {
cdev = &led->mcdev.led_cdev;
- ret = ktd202x_setup_led_rgb(chip, np, led, &init_data);
+ ret = ktd202x_setup_led_rgb(chip, fwnode, led, &init_data);
} else {
cdev = &led->cdev;
- ret = ktd202x_setup_led_single(chip, np, led, &init_data);
+ ret = ktd202x_setup_led_single(chip, fwnode, led, &init_data);
}
if (ret) {
@@ -490,15 +493,13 @@ static int ktd202x_add_led(struct ktd202x *chip, struct device_node *np, unsigne
return 0;
}
-static int ktd202x_probe_dt(struct ktd202x *chip)
+static int ktd202x_probe_fw(struct ktd202x *chip)
{
- struct device_node *np = dev_of_node(chip->dev), *child;
+ struct device *dev = chip->dev;
int count;
int i = 0;
- chip->num_leds = (int)(unsigned long)of_device_get_match_data(chip->dev);
-
- count = of_get_available_child_count(np);
+ count = device_get_child_node_count(dev);
if (!count || count > chip->num_leds)
return -EINVAL;
@@ -507,13 +508,12 @@ static int ktd202x_probe_dt(struct ktd202x *chip)
/* Allow the device to execute the complete reset */
usleep_range(200, 300);
- for_each_available_child_of_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
int ret = ktd202x_add_led(chip, child, i);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
+
i++;
}
@@ -554,6 +554,12 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
+ ret = devm_mutex_init(dev, &chip->mutex);
+ if (ret)
+ return ret;
+
+ chip->num_leds = (unsigned long)i2c_get_match_data(client);
+
chip->regulators[0].supply = "vin";
chip->regulators[1].supply = "vio";
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators);
@@ -568,7 +574,7 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
- ret = ktd202x_probe_dt(chip);
+ ret = ktd202x_probe_fw(chip);
if (ret < 0) {
regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
return ret;
@@ -580,8 +586,6 @@ static int ktd202x_probe(struct i2c_client *client)
return ret;
}
- mutex_init(&chip->mutex);
-
return 0;
}
@@ -590,8 +594,6 @@ static void ktd202x_remove(struct i2c_client *client)
struct ktd202x *chip = i2c_get_clientdata(client);
ktd202x_chip_disable(chip);
-
- mutex_destroy(&chip->mutex);
}
static void ktd202x_shutdown(struct i2c_client *client)
@@ -602,10 +604,17 @@ static void ktd202x_shutdown(struct i2c_client *client)
regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
}
+static const struct i2c_device_id ktd202x_id[] = {
+ {"ktd2026", KTD2026_NUM_LEDS},
+ {"ktd2027", KTD2027_NUM_LEDS},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ktd202x_id);
+
static const struct of_device_id ktd202x_match_table[] = {
{ .compatible = "kinetic,ktd2026", .data = (void *)KTD2026_NUM_LEDS },
{ .compatible = "kinetic,ktd2027", .data = (void *)KTD2027_NUM_LEDS },
- {},
+ {}
};
MODULE_DEVICE_TABLE(of, ktd202x_match_table);
@@ -617,6 +626,7 @@ static struct i2c_driver ktd202x_driver = {
.probe = ktd202x_probe,
.remove = ktd202x_remove,
.shutdown = ktd202x_shutdown,
+ .id_table = ktd202x_id,
};
module_i2c_driver(ktd202x_driver);
diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c b/drivers/leds/rgb/leds-mt6370-rgb.c
index 448d0da11848..ebd3ba878dd5 100644
--- a/drivers/leds/rgb/leds-mt6370-rgb.c
+++ b/drivers/leds/rgb/leds-mt6370-rgb.c
@@ -21,7 +21,7 @@
#include <linux/regmap.h>
#include <linux/util_macros.h>
-#include <asm/unaligned.h>
+#include <linux/unaligned.h>
enum {
MT6370_LED_ISNK1 = 0,
@@ -149,7 +149,6 @@ struct mt6370_priv {
struct regmap_field *fields[F_MAX_FIELDS];
const struct reg_field *reg_fields;
const struct linear_range *ranges;
- struct reg_cfg *reg_cfgs;
const struct mt6370_pdata *pdata;
unsigned int leds_count;
unsigned int leds_active;
@@ -588,7 +587,7 @@ static inline int mt6370_mc_pattern_clear(struct led_classdev *lcdev)
struct mt6370_led *led = container_of(mccdev, struct mt6370_led, mc);
struct mt6370_priv *priv = led->priv;
struct mc_subled *subled;
- int i, ret;
+ int i, ret = 0;
mutex_lock(&led->priv->lock);
@@ -906,7 +905,6 @@ static int mt6370_leds_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct mt6370_priv *priv;
- struct fwnode_handle *child;
size_t count;
unsigned int i = 0;
int ret;
@@ -937,37 +935,27 @@ static int mt6370_leds_probe(struct platform_device *pdev)
if (ret)
return dev_err_probe(dev, ret, "Failed to allocate regmap field\n");
- device_for_each_child_node(dev, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct mt6370_led *led = priv->leds + i++;
struct led_init_data init_data = { .fwnode = child };
u32 reg, color;
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret) {
- dev_err(dev, "Failed to parse reg property\n");
- goto fwnode_release;
- }
+ if (ret)
+ dev_err_probe(dev, ret, "Failed to parse reg property\n");
- if (reg >= MT6370_MAX_LEDS) {
- ret = -EINVAL;
- dev_err(dev, "Error reg property number\n");
- goto fwnode_release;
- }
+ if (reg >= MT6370_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL, "Error reg property number\n");
ret = fwnode_property_read_u32(child, "color", &color);
- if (ret) {
- dev_err(dev, "Failed to parse color property\n");
- goto fwnode_release;
- }
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to parse color property\n");
if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
reg = MT6370_VIRTUAL_MULTICOLOR;
- if (priv->leds_active & BIT(reg)) {
- ret = -EINVAL;
- dev_err(dev, "Duplicate reg property\n");
- goto fwnode_release;
- }
+ if (priv->leds_active & BIT(reg))
+ return dev_err_probe(dev, -EINVAL, "Duplicate reg property\n");
priv->leds_active |= BIT(reg);
@@ -976,18 +964,14 @@ static int mt6370_leds_probe(struct platform_device *pdev)
ret = mt6370_init_led_properties(dev, led, &init_data);
if (ret)
- goto fwnode_release;
+ return ret;
ret = mt6370_led_register(dev, led, &init_data);
if (ret)
- goto fwnode_release;
+ return ret;
}
return 0;
-
-fwnode_release:
- fwnode_handle_put(child);
- return ret;
}
static const struct of_device_id mt6370_rgbled_device_table[] = {
diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c
index 2be4ff918516..f18156683375 100644
--- a/drivers/leds/rgb/leds-ncp5623.c
+++ b/drivers/leds/rgb/leds-ncp5623.c
@@ -183,16 +183,12 @@ static int ncp5623_probe(struct i2c_client *client)
fwnode_for_each_available_child_node(mc_node, led_node) {
ret = fwnode_property_read_u32(led_node, "color", &color_index);
- if (ret) {
- fwnode_handle_put(led_node);
- goto release_mc_node;
- }
+ if (ret)
+ goto release_led_node;
ret = fwnode_property_read_u32(led_node, "reg", &reg);
- if (ret) {
- fwnode_handle_put(led_node);
- goto release_mc_node;
- }
+ if (ret)
+ goto release_led_node;
subled_info[ncp->mc_dev.num_colors].channel = reg;
subled_info[ncp->mc_dev.num_colors++].color_index = color_index;
@@ -223,6 +219,10 @@ release_mc_node:
fwnode_handle_put(mc_node);
return ret;
+
+release_led_node:
+ fwnode_handle_put(led_node);
+ goto release_mc_node;
}
static void ncp5623_remove(struct i2c_client *client)
diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c
index e1a81e0109e8..1c7705bafdfc 100644
--- a/drivers/leds/rgb/leds-pwm-multicolor.c
+++ b/drivers/leds/rgb/leds-pwm-multicolor.c
@@ -50,7 +50,13 @@ static int led_pwm_mc_set(struct led_classdev *cdev,
duty = priv->leds[i].state.period - duty;
priv->leds[i].state.duty_cycle = duty;
- priv->leds[i].state.enabled = duty > 0;
+ /*
+ * Disabling a PWM doesn't guarantee that it emits the inactive level.
+ * So keep it on. Only for suspending the PWM should be disabled because
+ * otherwise it refuses to suspend. The possible downside is that the
+ * LED might stay (or even go) on.
+ */
+ priv->leds[i].state.enabled = !(cdev->flags & LED_SUSPENDED);
ret = pwm_apply_might_sleep(priv->leds[i].pwm,
&priv->leds[i].state);
if (ret)
@@ -135,8 +141,11 @@ static int led_pwm_mc_probe(struct platform_device *pdev)
/* init the multicolor's LED class device */
cdev = &priv->mc_cdev.led_cdev;
- fwnode_property_read_u32(mcnode, "max-brightness",
+ ret = fwnode_property_read_u32(mcnode, "max-brightness",
&cdev->max_brightness);
+ if (ret)
+ goto release_mcnode;
+
cdev->flags = LED_CORE_SUSPENDRESUME;
cdev->brightness_set_blocking = led_pwm_mc_set;
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
index 6bdc5b923f98..4f2a178e3d26 100644
--- a/drivers/leds/rgb/leds-qcom-lpg.c
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -2,7 +2,7 @@
/*
* Copyright (c) 2017-2022 Linaro Ltd
* Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
- * Copyright (c) 2023, Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2023-2024, Qualcomm Innovation Center, Inc. All rights reserved.
*/
#include <linux/bits.h>
#include <linux/bitfield.h>
@@ -24,6 +24,7 @@
#define LPG_PATTERN_CONFIG_REG 0x40
#define LPG_SIZE_CLK_REG 0x41
#define PWM_CLK_SELECT_MASK GENMASK(1, 0)
+#define PWM_SIZE_SELECT_MASK BIT(2)
#define PWM_CLK_SELECT_HI_RES_MASK GENMASK(2, 0)
#define PWM_SIZE_HI_RES_MASK GENMASK(6, 4)
#define LPG_PREDIV_CLK_REG 0x42
@@ -254,6 +255,9 @@ static int lpg_clear_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
u8 val = 0;
int rc;
+ if (!lpg->lpg_chan_sdam)
+ return 0;
+
lpg->pbs_en_bitmap &= (~lut_mask);
if (!lpg->pbs_en_bitmap) {
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
@@ -276,6 +280,9 @@ static int lpg_set_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
u8 val = PBS_SW_TRIG_BIT;
int rc;
+ if (!lpg->lpg_chan_sdam)
+ return 0;
+
if (!lpg->pbs_en_bitmap) {
rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
if (rc < 0)
@@ -406,8 +413,8 @@ static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
-static const unsigned int lpg_pwm_resolution[] = {9};
-static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
+static const unsigned int lpg_pwm_resolution[] = {6, 9};
+static const unsigned int lpg_pwm_resolution_hi_res[] = {8, 9, 10, 11, 12, 13, 14, 15};
static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
{
@@ -430,12 +437,12 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
* period = --------------------------
* refclk
*
- * Resolution = 2^9 bits for PWM or
+ * Resolution = 2^{6 or 9} bits for PWM or
* 2^{8, 9, 10, 11, 12, 13, 14, 15} bits for high resolution PWM
* pre_div = {1, 3, 5, 6} and
* M = [0..7].
*
- * This allows for periods between 27uS and 384s for PWM channels and periods between
+ * This allows for periods between 3uS and 384s for PWM channels and periods between
* 3uS and 24576s for high resolution PWMs.
* The PWM framework wants a period of equal or lower length than requested,
* reject anything below minimum period.
@@ -455,7 +462,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
max_res = LPG_RESOLUTION_9BIT;
}
- min_period = div64_u64((u64)NSEC_PER_SEC * (1 << pwm_resolution_arr[0]),
+ min_period = div64_u64((u64)NSEC_PER_SEC * ((1 << pwm_resolution_arr[0]) - 1),
clk_rate_arr[clk_len - 1]);
if (period <= min_period)
return -EINVAL;
@@ -476,7 +483,7 @@ static int lpg_calc_freq(struct lpg_channel *chan, uint64_t period)
*/
for (i = 0; i < pwm_resolution_count; i++) {
- resolution = 1 << pwm_resolution_arr[i];
+ resolution = (1 << pwm_resolution_arr[i]) - 1;
for (clk_sel = 1; clk_sel < clk_len; clk_sel++) {
u64 numerator = period * clk_rate_arr[clk_sel];
@@ -523,10 +530,10 @@ static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
unsigned int clk_rate;
if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
- max = LPG_RESOLUTION_15BIT - 1;
+ max = BIT(lpg_pwm_resolution_hi_res[chan->pwm_resolution_sel]) - 1;
clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
} else {
- max = LPG_RESOLUTION_9BIT - 1;
+ max = BIT(lpg_pwm_resolution[chan->pwm_resolution_sel]) - 1;
clk_rate = lpg_clk_rates[chan->clk_sel];
}
@@ -552,7 +559,7 @@ static void lpg_apply_freq(struct lpg_channel *chan)
val |= GENMASK(5, 4);
break;
case LPG_SUBTYPE_PWM:
- val |= BIT(2);
+ val |= FIELD_PREP(PWM_SIZE_SELECT_MASK, chan->pwm_resolution_sel);
break;
case LPG_SUBTYPE_HI_RES_PWM:
val |= FIELD_PREP(PWM_SIZE_HI_RES_MASK, chan->pwm_resolution_sel);
@@ -1270,7 +1277,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
resolution = lpg_pwm_resolution_hi_res[FIELD_GET(PWM_SIZE_HI_RES_MASK, val)];
} else {
refclk = lpg_clk_rates[FIELD_GET(PWM_CLK_SELECT_MASK, val)];
- resolution = 9;
+ resolution = lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)];
}
if (refclk) {
@@ -1285,7 +1292,7 @@ static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
if (ret)
return ret;
- state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * (1 << resolution) *
+ state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * ((1 << resolution) - 1) *
pre_div * (1 << m), refclk);
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pwm_value * pre_div * (1 << m), refclk);
} else {
@@ -1362,7 +1369,6 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
{
struct led_init_data init_data = {};
struct led_classdev *cdev;
- struct device_node *child;
struct mc_subled *info;
struct lpg_led *led;
const char *state;
@@ -1393,12 +1399,10 @@ static int lpg_add_led(struct lpg *lpg, struct device_node *np)
if (!info)
return -ENOMEM;
i = 0;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
ret = lpg_parse_channel(lpg, child, &led->channels[i]);
- if (ret < 0) {
- of_node_put(child);
+ if (ret < 0)
return ret;
- }
info[i].color_index = led->channels[i]->color;
info[i].intensity = 0;
@@ -1594,7 +1598,6 @@ static int lpg_init_sdam(struct lpg *lpg)
static int lpg_probe(struct platform_device *pdev)
{
- struct device_node *np;
struct lpg *lpg;
int ret;
int i;
@@ -1634,12 +1637,10 @@ static int lpg_probe(struct platform_device *pdev)
if (ret < 0)
return ret;
- for_each_available_child_of_node(pdev->dev.of_node, np) {
+ for_each_available_child_of_node_scoped(pdev->dev.of_node, np) {
ret = lpg_add_led(lpg, np);
- if (ret) {
- of_node_put(np);
+ if (ret)
return ret;
- }
}
for (i = 0; i < lpg->num_channels; i++)
@@ -1693,6 +1694,13 @@ static const struct lpg_data pm8941_lpg_data = {
},
};
+static const struct lpg_data pmi8950_pwm_data = {
+ .num_channels = 1,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb000 },
+ },
+};
+
static const struct lpg_data pm8994_lpg_data = {
.lut_base = 0xb000,
.lut_size = 64,
@@ -1819,6 +1827,7 @@ static const struct of_device_id lpg_of_table[] = {
{ .compatible = "qcom,pm8941-lpg", .data = &pm8941_lpg_data },
{ .compatible = "qcom,pm8994-lpg", .data = &pm8994_lpg_data },
{ .compatible = "qcom,pmi632-lpg", .data = &pmi632_lpg_data },
+ { .compatible = "qcom,pmi8950-pwm", .data = &pmi8950_pwm_data },
{ .compatible = "qcom,pmi8994-lpg", .data = &pmi8994_lpg_data },
{ .compatible = "qcom,pmi8998-lpg", .data = &pmi8998_lpg_data },
{ .compatible = "qcom,pmc8180c-lpg", .data = &pm8150l_lpg_data },
diff --git a/drivers/leds/simple/Kconfig b/drivers/leds/simatic/Kconfig
index e616cc6d6051..e616cc6d6051 100644
--- a/drivers/leds/simple/Kconfig
+++ b/drivers/leds/simatic/Kconfig
diff --git a/drivers/leds/simple/Makefile b/drivers/leds/simatic/Makefile
index 783578f11bb0..783578f11bb0 100644
--- a/drivers/leds/simple/Makefile
+++ b/drivers/leds/simatic/Makefile
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
index 4183ee71fcce..c98c370687c2 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-apollolake.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
@@ -53,13 +53,14 @@ static void simatic_ipc_leds_gpio_apollolake_remove(struct platform_device *pdev
static struct platform_driver simatic_ipc_led_gpio_apollolake_driver = {
.probe = simatic_ipc_leds_gpio_apollolake_probe,
- .remove_new = simatic_ipc_leds_gpio_apollolake_remove,
+ .remove = simatic_ipc_leds_gpio_apollolake_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_led_gpio_apollolake_driver);
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:apollolake-pinctrl");
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
index 667ba1bc3a30..9bc5f361a06b 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-core.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
@@ -56,6 +56,7 @@ int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
case SIMATIC_IPC_DEVICE_127E:
case SIMATIC_IPC_DEVICE_227G:
case SIMATIC_IPC_DEVICE_BX_21A:
+ case SIMATIC_IPC_DEVICE_BX_59A:
break;
default:
return -ENODEV;
@@ -101,6 +102,7 @@ out:
}
EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe);
+MODULE_DESCRIPTION("Siemens SIMATIC IPC core driver for GPIO based LEDs");
MODULE_LICENSE("GPL v2");
MODULE_SOFTDEP("pre: platform:leds-gpio");
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
index 4a53d4dbf52f..7f7cff275448 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-elkhartlake.c
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
@@ -43,13 +43,14 @@ static void simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pde
static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
.probe = simatic_ipc_leds_gpio_elkhartlake_probe,
- .remove_new = simatic_ipc_leds_gpio_elkhartlake_remove,
+ .remove = simatic_ipc_leds_gpio_elkhartlake_remove,
.driver = {
.name = KBUILD_MODNAME,
},
};
module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver);
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl");
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
new file mode 100644
index 000000000000..bc23d701bcb7
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for GPIO based LEDs
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Author:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+
+#include "simatic-ipc-leds-gpio.h"
+
+struct simatic_ipc_led_tables {
+ struct gpiod_lookup_table *led_lookup_table;
+ struct gpiod_lookup_table *led_lookup_table_extra;
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_227g = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra_227g = {
+ .dev_id = NULL, /* Filled during initialization */
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_bx_59a = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 1, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-5", 3, NULL, 2, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-5", 2, NULL, 3, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 7, NULL, 4, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 4, NULL, 5, GPIO_ACTIVE_LOW),
+ {} /* Terminating entry */
+ }
+};
+
+static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev)
+{
+ const struct simatic_ipc_platform *plat = dev_get_platdata(&pdev->dev);
+ struct simatic_ipc_led_tables *led_tables;
+
+ led_tables = devm_kzalloc(&pdev->dev, sizeof(*led_tables), GFP_KERNEL);
+ if (!led_tables)
+ return -ENOMEM;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_227G:
+ led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_227g;
+ led_tables->led_lookup_table_extra = &simatic_ipc_led_gpio_table_extra_227g;
+ break;
+ case SIMATIC_IPC_DEVICE_BX_59A:
+ led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_bx_59a;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, led_tables);
+ return simatic_ipc_leds_gpio_probe(pdev, led_tables->led_lookup_table,
+ led_tables->led_lookup_table_extra);
+}
+
+static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev)
+{
+ struct simatic_ipc_led_tables *led_tables = platform_get_drvdata(pdev);
+
+ simatic_ipc_leds_gpio_remove(pdev, led_tables->led_lookup_table,
+ led_tables->led_lookup_table_extra);
+}
+
+static struct platform_driver simatic_ipc_led_gpio_driver = {
+ .probe = simatic_ipc_leds_gpio_f7188x_probe,
+ .remove = simatic_ipc_leds_gpio_f7188x_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+module_platform_driver(simatic_ipc_led_gpio_driver);
+
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Nuvoton GPIO");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio.h b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
index 6b2519809cee..6b2519809cee 100644
--- a/drivers/leds/simple/simatic-ipc-leds-gpio.h
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
diff --git a/drivers/leds/simple/simatic-ipc-leds.c b/drivers/leds/simatic/simatic-ipc-leds.c
index 2124f6d09930..348679f0d1b7 100644
--- a/drivers/leds/simple/simatic-ipc-leds.c
+++ b/drivers/leds/simatic/simatic-ipc-leds.c
@@ -128,6 +128,7 @@ static struct platform_driver simatic_ipc_led_driver = {
};
module_platform_driver(simatic_ipc_led_driver);
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs");
MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:" KBUILD_MODNAME);
MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c
deleted file mode 100644
index c7c3a1f986e6..000000000000
--- a/drivers/leds/simple/simatic-ipc-leds-gpio-f7188x.c
+++ /dev/null
@@ -1,66 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-/*
- * Siemens SIMATIC IPC driver for GPIO based LEDs
- *
- * Copyright (c) Siemens AG, 2023
- *
- * Author:
- * Henning Schild <henning.schild@siemens.com>
- */
-
-#include <linux/gpio/machine.h>
-#include <linux/gpio/consumer.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/platform_data/x86/simatic-ipc-base.h>
-
-#include "simatic-ipc-leds-gpio.h"
-
-static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
- .dev_id = "leds-gpio",
- .table = {
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW),
- GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW),
- {} /* Terminating entry */
- },
-};
-
-static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra = {
- .dev_id = NULL, /* Filled during initialization */
- .table = {
- GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH),
- GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH),
- {} /* Terminating entry */
- },
-};
-
-static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev)
-{
- return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
- &simatic_ipc_led_gpio_table_extra);
-}
-
-static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev)
-{
- simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table,
- &simatic_ipc_led_gpio_table_extra);
-}
-
-static struct platform_driver simatic_ipc_led_gpio_driver = {
- .probe = simatic_ipc_leds_gpio_f7188x_probe,
- .remove_new = simatic_ipc_leds_gpio_f7188x_remove,
- .driver = {
- .name = KBUILD_MODNAME,
- },
-};
-module_platform_driver(simatic_ipc_led_gpio_driver);
-
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("platform:" KBUILD_MODNAME);
-MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x");
-MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index d11d80176fc0..c11282a74b5a 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -136,13 +136,6 @@ config LEDS_TRIGGER_PATTERN
which is a series of tuples, of brightness and duration (ms).
If unsure, say N
-config LEDS_TRIGGER_AUDIO
- tristate "Audio Mute LED Trigger"
- help
- This allows LEDs to be controlled by audio drivers for following
- the audio mute and mic-mute changes.
- If unsure, say N
-
config LEDS_TRIGGER_TTY
tristate "LED Trigger for TTY devices"
depends on TTY
@@ -152,4 +145,20 @@ config LEDS_TRIGGER_TTY
When build as a module this driver will be called ledtrig-tty.
+config LEDS_TRIGGER_INPUT_EVENTS
+ tristate "LED Input events trigger"
+ depends on INPUT
+ help
+ Turn LEDs on when there is input (/dev/input/event*) activity and turn
+ them back off again after there has been no activity for 5 seconds.
+
+ This is primarily intended to control LEDs which are a backlight for
+ capacitive touch-buttons, such as e.g. the menu / home / back buttons
+ found on the bottom bezel of many older smartphones and tablets.
+
+ This can also be used to turn on the keyboard backlight LED on
+ input events and turn the keyboard backlight off again when idle.
+
+ When build as a module this driver will be called ledtrig-input-events.
+
endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
index 25c4db97cdd4..3b3628889f68 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -14,5 +14,5 @@ obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
obj-$(CONFIG_LEDS_TRIGGER_PANIC) += ledtrig-panic.o
obj-$(CONFIG_LEDS_TRIGGER_NETDEV) += ledtrig-netdev.o
obj-$(CONFIG_LEDS_TRIGGER_PATTERN) += ledtrig-pattern.o
-obj-$(CONFIG_LEDS_TRIGGER_AUDIO) += ledtrig-audio.o
obj-$(CONFIG_LEDS_TRIGGER_TTY) += ledtrig-tty.o
+obj-$(CONFIG_LEDS_TRIGGER_INPUT_EVENTS) += ledtrig-input-events.o
diff --git a/drivers/leds/trigger/ledtrig-activity.c b/drivers/leds/trigger/ledtrig-activity.c
index 33cbf8413658..b3ee33aed36e 100644
--- a/drivers/leds/trigger/ledtrig-activity.c
+++ b/drivers/leds/trigger/ledtrig-activity.c
@@ -156,7 +156,7 @@ static ssize_t led_invert_show(struct device *dev,
{
struct activity_data *activity_data = led_trigger_get_drvdata(dev);
- return sprintf(buf, "%u\n", activity_data->invert);
+ return sprintf(buf, "%d\n", activity_data->invert);
}
static ssize_t led_invert_store(struct device *dev,
diff --git a/drivers/leds/trigger/ledtrig-audio.c b/drivers/leds/trigger/ledtrig-audio.c
deleted file mode 100644
index 2ecd4b760fc3..000000000000
--- a/drivers/leds/trigger/ledtrig-audio.c
+++ /dev/null
@@ -1,67 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0
-//
-// Audio Mute LED trigger
-//
-
-#include <linux/kernel.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include "../leds.h"
-
-static enum led_brightness audio_state[NUM_AUDIO_LEDS];
-
-static int ledtrig_audio_mute_activate(struct led_classdev *led_cdev)
-{
- led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MUTE]);
- return 0;
-}
-
-static int ledtrig_audio_micmute_activate(struct led_classdev *led_cdev)
-{
- led_set_brightness_nosleep(led_cdev, audio_state[LED_AUDIO_MICMUTE]);
- return 0;
-}
-
-static struct led_trigger ledtrig_audio[NUM_AUDIO_LEDS] = {
- [LED_AUDIO_MUTE] = {
- .name = "audio-mute",
- .activate = ledtrig_audio_mute_activate,
- },
- [LED_AUDIO_MICMUTE] = {
- .name = "audio-micmute",
- .activate = ledtrig_audio_micmute_activate,
- },
-};
-
-enum led_brightness ledtrig_audio_get(enum led_audio type)
-{
- return audio_state[type];
-}
-EXPORT_SYMBOL_GPL(ledtrig_audio_get);
-
-void ledtrig_audio_set(enum led_audio type, enum led_brightness state)
-{
- audio_state[type] = state;
- led_trigger_event(&ledtrig_audio[type], state);
-}
-EXPORT_SYMBOL_GPL(ledtrig_audio_set);
-
-static int __init ledtrig_audio_init(void)
-{
- led_trigger_register(&ledtrig_audio[LED_AUDIO_MUTE]);
- led_trigger_register(&ledtrig_audio[LED_AUDIO_MICMUTE]);
- return 0;
-}
-module_init(ledtrig_audio_init);
-
-static void __exit ledtrig_audio_exit(void)
-{
- led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MUTE]);
- led_trigger_unregister(&ledtrig_audio[LED_AUDIO_MICMUTE]);
-}
-module_exit(ledtrig_audio_exit);
-
-MODULE_DESCRIPTION("LED trigger for audio mute control");
-MODULE_LICENSE("GPL v2");
-MODULE_ALIAS("ledtrig:audio-mute");
-MODULE_ALIAS("ledtrig:audio-micmute");
diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c
new file mode 100644
index 000000000000..1c79731562c2
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-input-events.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Input Events LED trigger
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include "../leds.h"
+
+static unsigned long led_off_delay_ms = 5000;
+module_param(led_off_delay_ms, ulong, 0644);
+MODULE_PARM_DESC(led_off_delay_ms,
+ "Specify delay in ms for turning LEDs off after last input event");
+
+static struct input_events_data {
+ struct delayed_work work;
+ spinlock_t lock;
+ /* To avoid repeatedly setting the brightness while there are events */
+ bool led_on;
+ unsigned long led_off_time;
+} input_events_data;
+
+static struct led_trigger *input_events_led_trigger;
+
+static void led_input_events_work(struct work_struct *work)
+{
+ struct input_events_data *data =
+ container_of(work, struct input_events_data, work.work);
+
+ spin_lock_irq(&data->lock);
+
+ /*
+ * This time_after_eq() check avoids a race where this work starts
+ * running before a new event pushed led_off_time back.
+ */
+ if (time_after_eq(jiffies, data->led_off_time)) {
+ led_trigger_event(input_events_led_trigger, LED_OFF);
+ data->led_on = false;
+ }
+
+ spin_unlock_irq(&data->lock);
+}
+
+static void input_events_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int val)
+{
+ struct input_events_data *data = &input_events_data;
+ unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms);
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->lock, flags);
+
+ if (!data->led_on) {
+ led_trigger_event(input_events_led_trigger, LED_FULL);
+ data->led_on = true;
+ }
+ data->led_off_time = jiffies + led_off_delay;
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ mod_delayed_work(system_wq, &data->work, led_off_delay);
+}
+
+static int input_events_connect(struct input_handler *handler, struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int ret;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = KBUILD_MODNAME;
+
+ ret = input_register_handle(handle);
+ if (ret)
+ goto err_free_handle;
+
+ ret = input_open_device(handle);
+ if (ret)
+ goto err_unregister_handle;
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(handle);
+err_free_handle:
+ kfree(handle);
+ return ret;
+}
+
+static void input_events_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id input_events_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_REL) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_ABS) },
+ },
+ { }
+};
+
+static struct input_handler input_events_handler = {
+ .name = KBUILD_MODNAME,
+ .event = input_events_event,
+ .connect = input_events_connect,
+ .disconnect = input_events_disconnect,
+ .id_table = input_events_ids,
+};
+
+static int __init input_events_init(void)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
+ spin_lock_init(&input_events_data.lock);
+
+ led_trigger_register_simple("input-events", &input_events_led_trigger);
+
+ ret = input_register_handler(&input_events_handler);
+ if (ret) {
+ led_trigger_unregister_simple(input_events_led_trigger);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit input_events_exit(void)
+{
+ input_unregister_handler(&input_events_handler);
+ cancel_delayed_work_sync(&input_events_data.work);
+ led_trigger_unregister_simple(input_events_led_trigger);
+}
+
+module_init(input_events_init);
+module_exit(input_events_exit);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Input Events LED trigger");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ledtrig:input-events");
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
index ea00f6c70882..4e048e08c4fd 100644
--- a/drivers/leds/trigger/ledtrig-netdev.c
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -39,6 +39,8 @@
* (has carrier) or not
* tx - LED blinks on transmitted data
* rx - LED blinks on receive data
+ * tx_err - LED blinks on transmit error
+ * rx_err - LED blinks on receive error
*
* Note: If the user selects a mode that is not supported by hw, default
* behavior is to fall back to software control of the LED. However not every
@@ -66,6 +68,7 @@ struct led_netdev_data {
unsigned int last_activity;
unsigned long mode;
+ unsigned long blink_delay;
int link_speed;
__ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes);
u8 duplex;
@@ -84,6 +87,10 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
/* Already validated, hw control is possible with the requested mode */
if (trigger_data->hw_control) {
led_cdev->hw_control_set(led_cdev, trigger_data->mode);
+ if (led_cdev->blink_set) {
+ led_cdev->blink_set(led_cdev, &trigger_data->blink_delay,
+ &trigger_data->blink_delay);
+ }
return;
}
@@ -144,7 +151,9 @@ static void set_baseline_state(struct led_netdev_data *trigger_data)
* checking stats
*/
if (test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ||
- test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
schedule_delayed_work(&trigger_data->work, 0);
}
}
@@ -337,6 +346,8 @@ static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
bit = attr;
break;
default:
@@ -371,6 +382,8 @@ static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
case TRIGGER_NETDEV_FULL_DUPLEX:
case TRIGGER_NETDEV_TX:
case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
bit = attr;
break;
default:
@@ -429,6 +442,8 @@ DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR);
+DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR);
static ssize_t interval_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -444,10 +459,11 @@ static ssize_t interval_store(struct device *dev,
size_t size)
{
struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
unsigned long value;
int ret;
- if (trigger_data->hw_control)
+ if (trigger_data->hw_control && !led_cdev->blink_set)
return -EINVAL;
ret = kstrtoul(buf, 0, &value);
@@ -456,9 +472,13 @@ static ssize_t interval_store(struct device *dev,
/* impose some basic bounds on the timer interval */
if (value >= 5 && value <= 10000) {
- cancel_delayed_work_sync(&trigger_data->work);
+ if (trigger_data->hw_control) {
+ trigger_data->blink_delay = value;
+ } else {
+ cancel_delayed_work_sync(&trigger_data->work);
- atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
+ }
set_baseline_state(trigger_data); /* resets timer */
}
@@ -538,6 +558,8 @@ static struct attribute *netdev_trig_attrs[] = {
&dev_attr_half_duplex.attr,
&dev_attr_rx.attr,
&dev_attr_tx.attr,
+ &dev_attr_rx_err.attr,
+ &dev_attr_tx_err.attr,
&dev_attr_interval.attr,
&dev_attr_offloaded.attr,
NULL
@@ -593,6 +615,8 @@ static int netdev_trig_notify(struct notifier_block *nb,
trigger_data->net_dev = NULL;
break;
case NETDEV_UP:
+ trigger_data->hw_control = can_hw_control(trigger_data);
+ fallthrough;
case NETDEV_CHANGE:
get_device_state(trigger_data);
/* Refresh link_speed visibility */
@@ -628,7 +652,9 @@ static void netdev_trig_work(struct work_struct *work)
/* If we are not looking for RX/TX then return */
if (!test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) &&
- !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode))
+ !test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) &&
+ !test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode))
return;
dev_stats = dev_get_stats(trigger_data->net_dev, &temp);
@@ -636,7 +662,11 @@ static void netdev_trig_work(struct work_struct *work)
(test_bit(TRIGGER_NETDEV_TX, &trigger_data->mode) ?
dev_stats->tx_packets : 0) +
(test_bit(TRIGGER_NETDEV_RX, &trigger_data->mode) ?
- dev_stats->rx_packets : 0);
+ dev_stats->rx_packets : 0) +
+ (test_bit(TRIGGER_NETDEV_TX_ERR, &trigger_data->mode) ?
+ dev_stats->tx_errors : 0) +
+ (test_bit(TRIGGER_NETDEV_RX_ERR, &trigger_data->mode) ?
+ dev_stats->rx_errors : 0);
if (trigger_data->last_activity != new_activity) {
led_stop_software_blink(trigger_data->led_cdev);
@@ -724,8 +754,6 @@ static void netdev_trig_deactivate(struct led_classdev *led_cdev)
cancel_delayed_work_sync(&trigger_data->work);
- led_set_brightness(led_cdev, LED_OFF);
-
dev_put(trigger_data->net_dev);
kfree(trigger_data);
diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c
index fadd87dbe993..06d052957d37 100644
--- a/drivers/leds/trigger/ledtrig-pattern.c
+++ b/drivers/leds/trigger/ledtrig-pattern.c
@@ -13,6 +13,7 @@
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/timer.h>
+#include <linux/hrtimer.h>
#define MAX_PATTERNS 1024
/*
@@ -21,6 +22,12 @@
*/
#define UPDATE_INTERVAL 50
+enum pattern_type {
+ PATTERN_TYPE_SW, /* Use standard timer for software pattern */
+ PATTERN_TYPE_HR, /* Use hrtimer for software pattern */
+ PATTERN_TYPE_HW, /* Hardware pattern */
+};
+
struct pattern_trig_data {
struct led_classdev *led_cdev;
struct led_pattern patterns[MAX_PATTERNS];
@@ -32,8 +39,9 @@ struct pattern_trig_data {
int last_repeat;
int delta_t;
bool is_indefinite;
- bool is_hw_pattern;
+ enum pattern_type type;
struct timer_list timer;
+ struct hrtimer hrtimer;
};
static void pattern_trig_update_patterns(struct pattern_trig_data *data)
@@ -71,10 +79,35 @@ static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
return data->curr->brightness - step_brightness;
}
-static void pattern_trig_timer_function(struct timer_list *t)
+static void pattern_trig_timer_start(struct pattern_trig_data *data)
{
- struct pattern_trig_data *data = from_timer(data, t, timer);
+ if (data->type == PATTERN_TYPE_HR) {
+ hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL);
+ } else {
+ data->timer.expires = jiffies;
+ add_timer(&data->timer);
+ }
+}
+static void pattern_trig_timer_cancel(struct pattern_trig_data *data)
+{
+ if (data->type == PATTERN_TYPE_HR)
+ hrtimer_cancel(&data->hrtimer);
+ else
+ timer_delete_sync(&data->timer);
+}
+
+static void pattern_trig_timer_restart(struct pattern_trig_data *data,
+ unsigned long interval)
+{
+ if (data->type == PATTERN_TYPE_HR)
+ hrtimer_forward_now(&data->hrtimer, ms_to_ktime(interval));
+ else
+ mod_timer(&data->timer, jiffies + msecs_to_jiffies(interval));
+}
+
+static void pattern_trig_timer_common_function(struct pattern_trig_data *data)
+{
for (;;) {
if (!data->is_indefinite && !data->repeat)
break;
@@ -83,8 +116,7 @@ static void pattern_trig_timer_function(struct timer_list *t)
/* Step change of brightness */
led_set_brightness(data->led_cdev,
data->curr->brightness);
- mod_timer(&data->timer,
- jiffies + msecs_to_jiffies(data->curr->delta_t));
+ pattern_trig_timer_restart(data, data->curr->delta_t);
if (!data->next->delta_t) {
/* Skip the tuple with zero duration */
pattern_trig_update_patterns(data);
@@ -106,8 +138,7 @@ static void pattern_trig_timer_function(struct timer_list *t)
led_set_brightness(data->led_cdev,
pattern_trig_compute_brightness(data));
- mod_timer(&data->timer,
- jiffies + msecs_to_jiffies(UPDATE_INTERVAL));
+ pattern_trig_timer_restart(data, UPDATE_INTERVAL);
/* Accumulate the gradual dimming time */
data->delta_t += UPDATE_INTERVAL;
@@ -117,6 +148,25 @@ static void pattern_trig_timer_function(struct timer_list *t)
}
}
+static void pattern_trig_timer_function(struct timer_list *t)
+{
+ struct pattern_trig_data *data = from_timer(data, t, timer);
+
+ return pattern_trig_timer_common_function(data);
+}
+
+static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t)
+{
+ struct pattern_trig_data *data =
+ container_of(t, struct pattern_trig_data, hrtimer);
+
+ pattern_trig_timer_common_function(data);
+ if (!data->is_indefinite && !data->repeat)
+ return HRTIMER_NORESTART;
+
+ return HRTIMER_RESTART;
+}
+
static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
{
struct pattern_trig_data *data = led_cdev->trigger_data;
@@ -124,7 +174,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
if (!data->npatterns)
return 0;
- if (data->is_hw_pattern) {
+ if (data->type == PATTERN_TYPE_HW) {
return led_cdev->pattern_set(led_cdev, data->patterns,
data->npatterns, data->repeat);
}
@@ -136,8 +186,7 @@ static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
data->delta_t = 0;
data->curr = data->patterns;
data->next = data->patterns + 1;
- data->timer.expires = jiffies;
- add_timer(&data->timer);
+ pattern_trig_timer_start(data);
return 0;
}
@@ -175,9 +224,9 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
mutex_lock(&data->lock);
- del_timer_sync(&data->timer);
+ pattern_trig_timer_cancel(data);
- if (data->is_hw_pattern)
+ if (data->type == PATTERN_TYPE_HW)
led_cdev->pattern_clear(led_cdev);
data->last_repeat = data->repeat = res;
@@ -196,14 +245,14 @@ static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
static DEVICE_ATTR_RW(repeat);
static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
- char *buf, bool hw_pattern)
+ char *buf, enum pattern_type type)
{
ssize_t count = 0;
int i;
mutex_lock(&data->lock);
- if (!data->npatterns || (data->is_hw_pattern ^ hw_pattern))
+ if (!data->npatterns || data->type != type)
goto out;
for (i = 0; i < data->npatterns; i++) {
@@ -260,19 +309,19 @@ static int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
const char *buf, const u32 *buf_int,
- size_t count, bool hw_pattern)
+ size_t count, enum pattern_type type)
{
struct pattern_trig_data *data = led_cdev->trigger_data;
int err = 0;
mutex_lock(&data->lock);
- del_timer_sync(&data->timer);
+ pattern_trig_timer_cancel(data);
- if (data->is_hw_pattern)
+ if (data->type == PATTERN_TYPE_HW)
led_cdev->pattern_clear(led_cdev);
- data->is_hw_pattern = hw_pattern;
+ data->type = type;
data->npatterns = 0;
if (buf)
@@ -297,7 +346,7 @@ static ssize_t pattern_show(struct device *dev, struct device_attribute *attr,
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct pattern_trig_data *data = led_cdev->trigger_data;
- return pattern_trig_show_patterns(data, buf, false);
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_SW);
}
static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
@@ -305,7 +354,8 @@ static ssize_t pattern_store(struct device *dev, struct device_attribute *attr,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- return pattern_trig_store_patterns(led_cdev, buf, NULL, count, false);
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_SW);
}
static DEVICE_ATTR_RW(pattern);
@@ -316,7 +366,7 @@ static ssize_t hw_pattern_show(struct device *dev,
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct pattern_trig_data *data = led_cdev->trigger_data;
- return pattern_trig_show_patterns(data, buf, true);
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HW);
}
static ssize_t hw_pattern_store(struct device *dev,
@@ -325,11 +375,33 @@ static ssize_t hw_pattern_store(struct device *dev,
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- return pattern_trig_store_patterns(led_cdev, buf, NULL, count, true);
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_HW);
}
static DEVICE_ATTR_RW(hw_pattern);
+static ssize_t hr_pattern_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+
+ return pattern_trig_show_patterns(data, buf, PATTERN_TYPE_HR);
+}
+
+static ssize_t hr_pattern_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ return pattern_trig_store_patterns(led_cdev, buf, NULL, count,
+ PATTERN_TYPE_HR);
+}
+
+static DEVICE_ATTR_RW(hr_pattern);
+
static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
struct attribute *attr, int index)
{
@@ -338,6 +410,8 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
if (attr == &dev_attr_repeat.attr || attr == &dev_attr_pattern.attr)
return attr->mode;
+ else if (attr == &dev_attr_hr_pattern.attr)
+ return attr->mode;
else if (attr == &dev_attr_hw_pattern.attr && led_cdev->pattern_set)
return attr->mode;
@@ -347,6 +421,7 @@ static umode_t pattern_trig_attrs_mode(struct kobject *kobj,
static struct attribute *pattern_trig_attrs[] = {
&dev_attr_pattern.attr,
&dev_attr_hw_pattern.attr,
+ &dev_attr_hr_pattern.attr,
&dev_attr_repeat.attr,
NULL
};
@@ -376,7 +451,8 @@ static void pattern_init(struct led_classdev *led_cdev)
goto out;
}
- err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size, false);
+ err = pattern_trig_store_patterns(led_cdev, NULL, pattern, size,
+ PATTERN_TYPE_SW);
if (err < 0)
dev_warn(led_cdev->dev,
"Pattern initialization failed with error %d\n", err);
@@ -400,12 +476,15 @@ static int pattern_trig_activate(struct led_classdev *led_cdev)
led_cdev->pattern_clear = NULL;
}
+ data->type = PATTERN_TYPE_SW;
data->is_indefinite = true;
data->last_repeat = -1;
mutex_init(&data->lock);
data->led_cdev = led_cdev;
led_set_trigger_data(led_cdev, data);
timer_setup(&data->timer, pattern_trig_timer_function, 0);
+ hrtimer_setup(&data->hrtimer, pattern_trig_hrtimer_function, CLOCK_MONOTONIC,
+ HRTIMER_MODE_REL);
led_cdev->activated = true;
if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
@@ -431,6 +510,7 @@ static void pattern_trig_deactivate(struct led_classdev *led_cdev)
led_cdev->pattern_clear(led_cdev);
timer_shutdown_sync(&data->timer);
+ hrtimer_cancel(&data->hrtimer);
led_set_brightness(led_cdev, LED_OFF);
kfree(data);
diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c
index b4688d1d9d2b..1d213c999d40 100644
--- a/drivers/leds/trigger/ledtrig-timer.c
+++ b/drivers/leds/trigger/ledtrig-timer.c
@@ -110,11 +110,6 @@ static int timer_trig_activate(struct led_classdev *led_cdev)
led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
}
- /*
- * If "set brightness to 0" is pending in workqueue, we don't
- * want that to be reordered after blink_set()
- */
- flush_work(&led_cdev->set_brightness_work);
led_blink_set(led_cdev, &led_cdev->blink_delay_on,
&led_cdev->blink_delay_off);
diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c
index f111fa7635e5..e103c7ed830b 100644
--- a/drivers/leds/trigger/ledtrig-transient.c
+++ b/drivers/leds/trigger/ledtrig-transient.c
@@ -66,7 +66,7 @@ static ssize_t transient_activate_store(struct device *dev,
/* cancel the running timer */
if (state == 0 && transient_data->activate == 1) {
- del_timer(&transient_data->timer);
+ timer_delete(&transient_data->timer);
transient_data->activate = state;
led_set_brightness_nosleep(led_cdev,
transient_data->restore_state);
diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c
index 3d361c920030..374a841f18c3 100644
--- a/drivers/leds/uleds.c
+++ b/drivers/leds/uleds.c
@@ -200,7 +200,6 @@ static const struct file_operations uleds_fops = {
.read = uleds_read,
.write = uleds_write,
.poll = uleds_poll,
- .llseek = no_llseek,
};
static struct miscdevice uleds_misc = {