summaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/.kunitconfig4
-rw-r--r--drivers/leds/Kconfig742
-rw-r--r--drivers/leds/Makefile136
-rw-r--r--drivers/leds/TODO75
-rw-r--r--drivers/leds/blink/Kconfig31
-rw-r--r--drivers/leds/blink/Makefile3
-rw-r--r--drivers/leds/blink/leds-bcm63138.c313
-rw-r--r--drivers/leds/blink/leds-lgm-sso.c876
-rw-r--r--drivers/leds/dell-led.c202
-rw-r--r--drivers/leds/flash/Kconfig147
-rw-r--r--drivers/leds/flash/Makefile15
-rw-r--r--drivers/leds/flash/leds-aat1290.c550
-rw-r--r--drivers/leds/flash/leds-as3645a.c770
-rw-r--r--drivers/leds/flash/leds-ktd2692.c355
-rw-r--r--drivers/leds/flash/leds-lm3601x.c494
-rw-r--r--drivers/leds/flash/leds-max77693.c1057
-rw-r--r--drivers/leds/flash/leds-mt6360.c895
-rw-r--r--drivers/leds/flash/leds-mt6370-flash.c568
-rw-r--r--drivers/leds/flash/leds-qcom-flash.c989
-rw-r--r--drivers/leds/flash/leds-rt4505.c430
-rw-r--r--drivers/leds/flash/leds-rt8515.c397
-rw-r--r--drivers/leds/flash/leds-sgm3140.c313
-rw-r--r--drivers/leds/flash/leds-sy7802.c539
-rw-r--r--drivers/leds/flash/leds-tps6131x.c815
-rw-r--r--drivers/leds/led-class-flash.c460
-rw-r--r--drivers/leds/led-class-multicolor.c205
-rw-r--r--drivers/leds/led-class.c638
-rw-r--r--drivers/leds/led-core.c548
-rw-r--r--drivers/leds/led-test.c132
-rw-r--r--drivers/leds/led-triggers.c373
-rw-r--r--drivers/leds/leds-88pm860x.c52
-rw-r--r--drivers/leds/leds-acer-a500.c129
-rw-r--r--drivers/leds/leds-adp5520.c48
-rw-r--r--drivers/leds/leds-an30259a.c352
-rw-r--r--drivers/leds/leds-apu.c221
-rw-r--r--drivers/leds/leds-ariel.c133
-rw-r--r--drivers/leds/leds-asic3.c182
-rw-r--r--drivers/leds/leds-atmel-pwm.c149
-rw-r--r--drivers/leds/leds-aw200xx.c669
-rw-r--r--drivers/leds/leds-aw2013.c441
-rw-r--r--drivers/leds/leds-bcm6328.c483
-rw-r--r--drivers/leds/leds-bcm6358.c226
-rw-r--r--drivers/leds/leds-bd2606mvv.c157
-rw-r--r--drivers/leds/leds-bd2802.c87
-rw-r--r--drivers/leds/leds-blinkm.c364
-rw-r--r--drivers/leds/leds-cht-wcove.c472
-rw-r--r--drivers/leds/leds-clevo-mail.c12
-rw-r--r--drivers/leds/leds-cobalt-qube.c26
-rw-r--r--drivers/leds/leds-cobalt-raq.c35
-rw-r--r--drivers/leds/leds-cpcap.c226
-rw-r--r--drivers/leds/leds-cr0014114.c296
-rw-r--r--drivers/leds/leds-cros_ec.c263
-rw-r--r--drivers/leds/leds-da903x.c63
-rw-r--r--drivers/leds/leds-da9052.c63
-rw-r--r--drivers/leds/leds-dac124s085.c51
-rw-r--r--drivers/leds/leds-el15203000.c340
-rw-r--r--drivers/leds/leds-expresswire.c72
-rw-r--r--drivers/leds/leds-fsg.c226
-rw-r--r--drivers/leds/leds-gpio-register.c11
-rw-r--r--drivers/leds/leds-gpio.c352
-rw-r--r--drivers/leds/leds-hp6xx.c26
-rw-r--r--drivers/leds/leds-ip30.c81
-rw-r--r--drivers/leds/leds-ipaq-micro.c132
-rw-r--r--drivers/leds/leds-is31fl319x.c596
-rw-r--r--drivers/leds/leds-is31fl32xx.c528
-rw-r--r--drivers/leds/leds-lm3530.c46
-rw-r--r--drivers/leds/leds-lm3532.c742
-rw-r--r--drivers/leds/leds-lm3533.c82
-rw-r--r--drivers/leds/leds-lm355x.c147
-rw-r--r--drivers/leds/leds-lm36274.c172
-rw-r--r--drivers/leds/leds-lm3642.c182
-rw-r--r--drivers/leds/leds-lm3692x.c529
-rw-r--r--drivers/leds/leds-lm3697.c381
-rw-r--r--drivers/leds/leds-locomo.c21
-rw-r--r--drivers/leds/leds-lp3944.c66
-rw-r--r--drivers/leds/leds-lp3952.c286
-rw-r--r--drivers/leds/leds-lp50xx.c637
-rw-r--r--drivers/leds/leds-lp5521.c345
-rw-r--r--drivers/leds/leds-lp5523.c504
-rw-r--r--drivers/leds/leds-lp5562.c329
-rw-r--r--drivers/leds/leds-lp5569.c544
-rw-r--r--drivers/leds/leds-lp55xx-common.c1053
-rw-r--r--drivers/leds/leds-lp55xx-common.h195
-rw-r--r--drivers/leds/leds-lp8501.c159
-rw-r--r--drivers/leds/leds-lp8788.c69
-rw-r--r--drivers/leds/leds-lp8860.c372
-rw-r--r--drivers/leds/leds-lp8864.c296
-rw-r--r--drivers/leds/leds-lt3593.c191
-rw-r--r--drivers/leds/leds-max5970.c112
-rw-r--r--drivers/leds/leds-max77650.c142
-rw-r--r--drivers/leds/leds-max77705.c275
-rw-r--r--drivers/leds/leds-max8997.c59
-rw-r--r--drivers/leds/leds-mc13783.c332
-rw-r--r--drivers/leds/leds-menf21bmc.c110
-rw-r--r--drivers/leds/leds-mlxcpld.c434
-rw-r--r--drivers/leds/leds-mlxreg.c293
-rw-r--r--drivers/leds/leds-mt6323.c727
-rw-r--r--drivers/leds/leds-net48xx.c15
-rw-r--r--drivers/leds/leds-netxbig.c512
-rw-r--r--drivers/leds/leds-nic78bx.c209
-rw-r--r--drivers/leds/leds-ns2.c394
-rw-r--r--drivers/leds/leds-ot200.c23
-rw-r--r--drivers/leds/leds-pca9532.c264
-rw-r--r--drivers/leds/leds-pca955x.c746
-rw-r--r--drivers/leds/leds-pca9633.c194
-rw-r--r--drivers/leds/leds-pca963x.c465
-rw-r--r--drivers/leds/leds-pca995x.c218
-rw-r--r--drivers/leds/leds-pm8058.c182
-rw-r--r--drivers/leds/leds-powernv.c338
-rw-r--r--drivers/leds/leds-pwm.c333
-rw-r--r--drivers/leds/leds-qnap-mcu.c392
-rw-r--r--drivers/leds/leds-rb532.c6
-rw-r--r--drivers/leds/leds-regulator.c116
-rw-r--r--drivers/leds/leds-renesas-tpu.c337
-rw-r--r--drivers/leds/leds-s3c24xx.c131
-rw-r--r--drivers/leds/leds-sc27xx-bltc.c355
-rw-r--r--drivers/leds/leds-spi-byte.c136
-rw-r--r--drivers/leds/leds-ss4200.c74
-rw-r--r--drivers/leds/leds-st1202.c416
-rw-r--r--drivers/leds/leds-sun50i-a100.c573
-rw-r--r--drivers/leds/leds-sunfire.c36
-rw-r--r--drivers/leds/leds-syscon.c137
-rw-r--r--drivers/leds/leds-tca6507.c333
-rw-r--r--drivers/leds/leds-ti-lmu-common.c153
-rw-r--r--drivers/leds/leds-tlc591xx.c236
-rw-r--r--drivers/leds/leds-tps6105x.c89
-rw-r--r--drivers/leds/leds-turris-omnia.c555
-rw-r--r--drivers/leds/leds-upboard.c126
-rw-r--r--drivers/leds/leds-wm831x-status.c101
-rw-r--r--drivers/leds/leds-wm8350.c77
-rw-r--r--drivers/leds/leds-wrap.c34
-rw-r--r--drivers/leds/leds.h49
-rw-r--r--drivers/leds/rgb/Kconfig78
-rw-r--r--drivers/leds/rgb/Makefile8
-rw-r--r--drivers/leds/rgb/leds-group-multicolor.c173
-rw-r--r--drivers/leds/rgb/leds-ktd202x.c635
-rw-r--r--drivers/leds/rgb/leds-mt6370-rgb.c995
-rw-r--r--drivers/leds/rgb/leds-ncp5623.c270
-rw-r--r--drivers/leds/rgb/leds-pwm-multicolor.c198
-rw-r--r--drivers/leds/rgb/leds-qcom-lpg.c1847
-rw-r--r--drivers/leds/simatic/Kconfig51
-rw-r--r--drivers/leds/simatic/Makefile5
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c67
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-core.c108
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c57
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c107
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds-gpio.h22
-rw-r--r--drivers/leds/simatic/simatic-ipc-leds.c134
-rw-r--r--drivers/leds/trigger/Kconfig91
-rw-r--r--drivers/leds/trigger/Makefile10
-rw-r--r--drivers/leds/trigger/ledtrig-activity.c270
-rw-r--r--drivers/leds/trigger/ledtrig-backlight.c132
-rw-r--r--drivers/leds/trigger/ledtrig-camera.c8
-rw-r--r--drivers/leds/trigger/ledtrig-cpu.c120
-rw-r--r--drivers/leds/trigger/ledtrig-default-on.c28
-rw-r--r--drivers/leds/trigger/ledtrig-disk.c40
-rw-r--r--drivers/leds/trigger/ledtrig-gpio.c240
-rw-r--r--drivers/leds/trigger/ledtrig-heartbeat.c104
-rw-r--r--drivers/leds/trigger/ledtrig-ide-disk.c47
-rw-r--r--drivers/leds/trigger/ledtrig-input-events.c165
-rw-r--r--drivers/leds/trigger/ledtrig-mtd.c37
-rw-r--r--drivers/leds/trigger/ledtrig-netdev.c765
-rw-r--r--drivers/leds/trigger/ledtrig-oneshot.c140
-rw-r--r--drivers/leds/trigger/ledtrig-panic.c68
-rw-r--r--drivers/leds/trigger/ledtrig-pattern.c543
-rw-r--r--drivers/leds/trigger/ledtrig-timer.c95
-rw-r--r--drivers/leds/trigger/ledtrig-transient.c161
-rw-r--r--drivers/leds/trigger/ledtrig-tty.c357
-rw-r--r--drivers/leds/uleds.c215
169 files changed, 40965 insertions, 6249 deletions
diff --git a/drivers/leds/.kunitconfig b/drivers/leds/.kunitconfig
new file mode 100644
index 000000000000..5180f77910a1
--- /dev/null
+++ b/drivers/leds/.kunitconfig
@@ -0,0 +1,4 @@
+CONFIG_KUNIT=y
+CONFIG_NEW_LEDS=y
+CONFIG_LEDS_CLASS=y
+CONFIG_LEDS_KUNIT_TEST=y
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index e43402dd1dea..11e7282dc297 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
config LEDS_GPIO_REGISTER
bool
help
@@ -5,22 +6,61 @@ config LEDS_GPIO_REGISTER
As this function is used by arch code it must not be compiled as a
module.
+# This library does not depend on NEW_LEDS and must be independent so it can be
+# selected from other subsystems (specifically backlight).
+config LEDS_EXPRESSWIRE
+ bool
+ depends on GPIOLIB
+
menuconfig NEW_LEDS
bool "LED Support"
help
Say Y to enable Linux LED support. This allows control of supported
LEDs from both userspace and optionally, by kernel events (triggers).
- This is not related to standard keyboard LEDs which are controlled
- via the input system.
-
if NEW_LEDS
config LEDS_CLASS
tristate "LED Class Support"
help
- This option enables the led sysfs class in /sys/class/leds. You'll
- need this to do anything useful with LEDs. If unsure, say N.
+ This option enables the LED sysfs class in /sys/class/leds. You'll
+ need this to do anything useful with LEDs. If unsure, say Y.
+
+config LEDS_CLASS_FLASH
+ tristate "LED Flash Class Support"
+ depends on LEDS_CLASS
+ help
+ This option enables the flash LED sysfs class in /sys/class/leds.
+ It wraps LED Class and adds flash LEDs specific sysfs attributes
+ and kernel internal API to it. You'll need this to provide support
+ for the flash related features of a LED device. It can be built
+ as a module.
+
+config LEDS_CLASS_MULTICOLOR
+ tristate "LED Multicolor Class Support"
+ depends on LEDS_CLASS
+ help
+ This option enables the multicolor LED sysfs class in /sys/class/leds.
+ It wraps LED class and adds multicolor LED specific sysfs attributes
+ and kernel internal API to it. You'll need this to provide support
+ for multicolor LEDs that are grouped together. This class is not
+ intended for single color LEDs. It can be built as a module.
+
+config LEDS_BRIGHTNESS_HW_CHANGED
+ bool "LED Class brightness_hw_changed attribute support"
+ depends on LEDS_CLASS
+ help
+ This option enables support for the brightness_hw_changed attribute
+ for LED sysfs class devices under /sys/class/leds.
+
+ See Documentation/ABI/testing/sysfs-class-led for details.
+
+config LEDS_KUNIT_TEST
+ tristate "KUnit tests for LEDs"
+ depends on KUNIT && LEDS_CLASS
+ default KUNIT_ALL_TESTS
+ help
+ Say Y here to enable KUnit testing for the LEDs framework.
comment "LED drivers"
@@ -32,13 +72,161 @@ config LEDS_88PM860X
This option enables support for on-chip LED drivers found on Marvell
Semiconductor 88PM8606 PMIC.
-config LEDS_ATMEL_PWM
- tristate "LED Support using Atmel PWM outputs"
+config LEDS_AN30259A
+ tristate "LED support for Panasonic AN30259A"
+ depends on LEDS_CLASS && I2C && OF
+ help
+ This option enables support for the AN30259A 3-channel
+ LED driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-an30259a.
+
+config LEDS_APU
+ tristate "Front panel LED support for PC Engines APU/APU2/APU3 boards"
+ depends on LEDS_CLASS
+ depends on X86 && DMI
+ help
+ This driver makes the PC Engines APU1 front panel LEDs
+ accessible from userspace programs through the LED subsystem.
+
+ If you're looking for APU2/3, use the pcengines-apu2 driver.
+ (symbol CONFIG_PCENGINES_APU2)
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-apu.
+
+config LEDS_ARIEL
+ tristate "Dell Wyse 3020 status LED support"
+ depends on LEDS_CLASS
+ depends on (MACH_MMP3_DT && MFD_ENE_KB3930) || COMPILE_TEST
+ help
+ This driver adds support for controlling the front panel status
+ LEDs on Dell Wyse 3020 (Ariel) board via the KB3930 Embedded
+ Controller.
+
+ Say Y to if your machine is a Dell Wyse 3020 thin client.
+
+config LEDS_AW200XX
+ tristate "LED support for Awinic AW20036/AW20054/AW20072/AW20108"
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for the Awinic AW200XX LED controllers.
+ It is a matrix LED driver programmed via an I2C interface. Devices have
+ a set of individually controlled LEDs and support 3 pattern controllers
+ for auto breathing or group dimming control. Supported devices:
+ - AW20036 (3x12) 36 LEDs
+ - AW20054 (6x9) 54 LEDs
+ - AW20072 (6x12) 72 LEDs
+ - AW20108 (9x12) 108 LEDs
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-aw200xx.
+
+config LEDS_AW2013
+ tristate "LED support for Awinic AW2013"
+ depends on LEDS_CLASS && I2C && OF
+ select REGMAP_I2C
+ help
+ This option enables support for the AW2013 3-channel
+ LED driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-aw2013.
+
+config LEDS_BCM6328
+ tristate "LED Support for Broadcom BCM6328"
+ depends on LEDS_CLASS
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ This option enables support for LEDs connected to the BCM6328
+ LED HW controller accessed via MMIO registers.
+
+config LEDS_BCM6358
+ tristate "LED Support for Broadcom BCM6358"
+ depends on LEDS_CLASS
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ This option enables support for LEDs connected to the BCM6358
+ LED HW controller accessed via MMIO registers.
+
+config LEDS_CHT_WCOVE
+ tristate "LED support for Intel Cherry Trail Whiskey Cove PMIC"
+ depends on LEDS_CLASS
+ depends on INTEL_SOC_PMIC_CHTWC
+ help
+ This option enables support for charger and general purpose LEDs
+ connected to the Intel Cherrytrail Whiskey Cove PMIC.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-cht-wcove.
+
+config LEDS_CPCAP
+ tristate "LED Support for Motorola CPCAP"
+ depends on LEDS_CLASS
+ depends on MFD_CPCAP
+ depends on OF
+ help
+ This option enables support for LEDs offered by Motorola's
+ CPCAP PMIC.
+
+config LEDS_CR0014114
+ tristate "LED Support for Crane CR0014114"
+ depends on LEDS_CLASS
+ depends on SPI
+ depends on OF
+ help
+ This option enables support for CR0014114 LED Board which
+ is widely used in vending machines produced by
+ Crane Merchandising Systems.
+
+ 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
- depends on ATMEL_PWM
+ depends on SPI
+ depends on OF
+ help
+ This option enables support for EL15203000 LED Board
+ (aka RED LED board) which is widely used in coffee vending
+ machines produced by Crane Merchandising Systems.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-el15203000.
+
+config LEDS_TURRIS_OMNIA
+ tristate "LED support for CZ.NIC's Turris Omnia"
+ depends on LEDS_CLASS_MULTICOLOR
+ 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 support for LEDs driven using outputs
- of the dedicated PWM controller found on newer Atmel SOCs.
+ This option enables basic support for the LEDs found on the front
+ side of CZ.NIC's Turris Omnia router. There are 12 RGB LEDs on the
+ front panel.
config LEDS_LM3530
tristate "LCD Backlight driver for LM3530"
@@ -50,6 +238,17 @@ config LEDS_LM3530
controlled manually or using PWM input or using ambient
light automatically.
+config LEDS_LM3532
+ tristate "LCD Backlight driver for LM3532"
+ select REGMAP_I2C
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for the LCD backlight using
+ LM3532 ambient light sensor chip. This ALS chip can be
+ controlled manually or using PWM input or using ambient
+ light automatically.
+
config LEDS_LM3533
tristate "LED support for LM3533"
depends on LEDS_CLASS
@@ -73,6 +272,13 @@ config LEDS_LM3642
converter plus 1.5A constant current driver for a high-current
white LED.
+config LEDS_LM3692X
+ tristate "LED support for LM3692x Chips"
+ depends on LEDS_CLASS && I2C && OF
+ select REGMAP_I2C
+ help
+ This option enables support for the TI LM3692x family
+ of white LED string drivers used for backlighting.
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
@@ -90,13 +296,13 @@ config LEDS_MIKROTIK_RB532
This option enables support for the so called "User LED" of
Mikrotik's Routerboard 532.
-config LEDS_S3C24XX
- tristate "LED Support for Samsung S3C24XX GPIO LEDs"
+config LEDS_MT6323
+ tristate "LED Support for Mediatek MT6323 PMIC"
depends on LEDS_CLASS
- depends on ARCH_S3C24XX
+ depends on MFD_MT6397
help
- This option enables support for LEDs connected to GPIO lines
- on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440.
+ This option enables support for on-chip LED drivers found on
+ Mediatek MT6323 PMIC.
config LEDS_NET48XX
tristate "LED Support for Soekris net48xx series Error LED"
@@ -106,13 +312,6 @@ config LEDS_NET48XX
This option enables support for the Soekris net4801 and net4826 error
LED.
-config LEDS_FSG
- tristate "LED Support for the Freecom FSG-3"
- depends on LEDS_CLASS
- depends on MACH_FSG
- help
- This option enables support for the LEDs on the Freecom FSG-3.
-
config LEDS_WRAP
tristate "LED Support for the WRAP series LEDs"
depends on LEDS_CLASS
@@ -123,17 +322,26 @@ config LEDS_WRAP
config LEDS_COBALT_QUBE
tristate "LED Support for the Cobalt Qube series front LED"
depends on LEDS_CLASS
- depends on MIPS_COBALT
+ depends on MIPS_COBALT || COMPILE_TEST
help
This option enables support for the front LED on Cobalt Qube series
config LEDS_COBALT_RAQ
bool "LED Support for the Cobalt Raq series"
- depends on LEDS_CLASS=y && MIPS_COBALT
+ depends on LEDS_CLASS=y && (MIPS_COBALT || COMPILE_TEST)
select LEDS_TRIGGERS
help
This option enables support for the Cobalt Raq series LEDs.
+config LEDS_SUN50I_A100
+ tristate "LED support for Allwinner A100 RGB LED controller"
+ depends on LEDS_CLASS_MULTICOLOR
+ depends on ARCH_SUNXI || COMPILE_TEST
+ help
+ This option enables support for the RGB LED controller found
+ in some Allwinner sunxi SoCs, including A100, R329, and D1.
+ It uses a one-wire interface to control up to 1024 LEDs.
+
config LEDS_SUNFIRE
tristate "LED support for SunFire servers."
depends on LEDS_CLASS
@@ -143,6 +351,14 @@ config LEDS_SUNFIRE
This option enables support for the Left, Middle, and Right
LEDs on the I/O and CPU boards of SunFire UltraSPARC servers.
+config LEDS_IPAQ_MICRO
+ tristate "LED Support for the Compaq iPAQ h3xxx"
+ depends on LEDS_CLASS
+ depends on MFD_IPAQ_MICRO
+ help
+ Choose this option if you want to use the notification LED on
+ Compaq/HP iPAQ h3100 and h3600.
+
config LEDS_HP6XX
tristate "LED Support for the HP Jornada 6xx"
depends on LEDS_CLASS
@@ -173,7 +389,7 @@ config LEDS_PCA9532_GPIO
config LEDS_GPIO
tristate "LED Support for GPIO connected LEDs"
depends on LEDS_CLASS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This option enables support for the LEDs connected to GPIO
outputs. To be useful the particular board must have LEDs
@@ -193,18 +409,46 @@ config LEDS_LP3944
To compile this driver as a module, choose M here: the
module will be called leds-lp3944.
+config LEDS_LP3952
+ tristate "LED Support for TI LP3952 2 channel LED driver"
+ depends on LEDS_CLASS
+ depends on I2C
+ depends on GPIOLIB
+ select REGMAP_I2C
+ help
+ This option enables support for LEDs connected to the Texas
+ Instruments LP3952 LED driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-lp3952.
+
+config LEDS_LP50XX
+ tristate "LED Support for TI LP5036/30/24/18/12/09 LED driver chip"
+ depends on LEDS_CLASS && REGMAP_I2C
+ depends on LEDS_CLASS_MULTICOLOR
+ help
+ If you say yes here you get support for the Texas Instruments
+ LP5036, LP5030, LP5024, LP5018, LP5012 and LP5009 LED driver.
+
+ To compile this driver as a module, choose M here: the
+ module will be called leds-lp50xx.
+
config LEDS_LP55XX_COMMON
- tristate "Common Driver for TI/National LP5521, LP5523/55231 and LP5562"
- depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562
- select FW_LOADER
+ tristate "Common Driver for TI/National LP5521/5523/55231/5562/5569/8501"
+ depends on LEDS_CLASS
+ depends on LEDS_CLASS_MULTICOLOR
+ depends on OF
+ depends on I2C
+ imply FW_LOADER
+ imply FW_LOADER_USER_HELPER
help
- This option supports common operations for LP5521 and LP5523/55231
- 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"
depends on LEDS_CLASS && I2C
- select LEDS_LP55XX_COMMON
+ depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for the National Semiconductor
LP5521 LED driver. It is 3 channel chip with programmable engines.
@@ -214,7 +458,7 @@ config LEDS_LP5521
config LEDS_LP5523
tristate "LED Support for TI/National LP5523/55231 LED driver chip"
depends on LEDS_CLASS && I2C
- select LEDS_LP55XX_COMMON
+ depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI/National Semiconductor
LP5523/55231 LED driver.
@@ -225,13 +469,35 @@ config LEDS_LP5523
config LEDS_LP5562
tristate "LED Support for TI LP5562 LED driver chip"
depends on LEDS_CLASS && I2C
- select LEDS_LP55XX_COMMON
+ depends on LEDS_LP55XX_COMMON
help
If you say yes here you get support for TI LP5562 LED driver.
It is 4 channels chip with programmable engines.
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
+ depends on LEDS_LP55XX_COMMON
+ help
+ If you say yes here you get support for TI LP8501 LED driver.
+ It is 9 channel chip with programmable engines.
+ Driver provides direct control via LED class and interface for
+ programming the engines.
+ It is similar as LP5523, but output power selection is available.
+ And register layout and engine program schemes are different.
+
config LEDS_LP8788
tristate "LED support for the TI LP8788 PMIC"
depends on LEDS_CLASS
@@ -239,30 +505,53 @@ config LEDS_LP8788
help
This option enables support for the Keyboard LEDs on the LP8788 PMIC.
+config LEDS_LP8860
+ tristate "LED support for the TI LP8860 4 channel LED driver"
+ depends on LEDS_CLASS && I2C && OF
+ select REGMAP_I2C
+ help
+ If you say yes here you get support for the TI LP8860 4 channel
+ LED driver.
+ This option enables support for the display cluster LEDs
+ 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
+ depends on LEDS_CLASS && BROKEN
depends on X86 && SERIO_I8042 && DMI
help
This driver makes the mail LED accessible from userspace
- programs through the leds subsystem. This LED have three
- known mode: off, blink at 0.5Hz and blink at 1Hz.
+ programs through the LEDs subsystem. This LED has three
+ known modes: off, blink at 0.5Hz and blink at 1Hz.
The driver supports two kinds of interface: using ledtrig-timer
or through /sys/class/leds/clevo::mail/brightness. As this LED
- cannot change it's brightness it blinks instead. The brightness
+ cannot change its brightness it blinks instead. The brightness
value 0 means off, 1..127 means blink at 0.5Hz and 128..255 means
blink at 1Hz.
This module can drive the mail LED for the following notebooks:
- Clevo D400P
- Clevo D410J
- Clevo D410V
- Clevo D400V/D470V (not tested, but might work)
- Clevo M540N
- Clevo M5x0N (not tested, but might work)
- Positivo Mobile (Clevo M5x0V)
+ Clevo D400P
+ Clevo D410J
+ Clevo D410V
+ Clevo D400V/D470V (not tested, but might work)
+ Clevo M540N
+ Clevo M5x0N (not tested, but might work)
+ Positivo Mobile (Clevo M5x0V)
If your model is not listed here you can try the "nodetect"
module parameter.
@@ -279,13 +568,45 @@ config LEDS_PCA955X
LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553.
-config LEDS_PCA9633
- tristate "LED support for PCA9633 I2C chip"
+config LEDS_PCA955X_GPIO
+ bool "Enable GPIO support for PCA955X"
+ depends on LEDS_PCA955X
+ depends on GPIOLIB
+ help
+ Allow unused pins on PCA955X to be used as gpio.
+
+ To use a pin as gpio the pin type should be set to
+ PCA955X_TYPE_GPIO in the device tree.
+
+
+config LEDS_PCA963X
+ tristate "LED support for PCA963x I2C chip"
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for LEDs connected to the PCA963x
+ LED driver chip accessed via the I2C bus. Supported
+ devices include PCA9633 and PCA9634
+
+config LEDS_PCA995X
+ tristate "LED Support for PCA995x I2C chips"
depends on LEDS_CLASS
depends on I2C
help
- This option enables support for LEDs connected to the PCA9633
- LED driver chip accessed via the I2C bus.
+ This option enables support for LEDs connected to PCA995x
+ LED driver chips accessed via the I2C bus. Supported
+ devices include PCA9955BTW, PCA9952TW and PCA9955TW.
+
+config LEDS_QNAP_MCU
+ tristate "LED Support for QNAP MCU controllers"
+ depends on LEDS_CLASS
+ depends on MFD_QNAP_MCU
+ help
+ This option enables support for LEDs available on embedded
+ controllers used in QNAP NAS devices.
+
+ This driver can also be built as a module. If so, the module
+ will be called qnap-mcu-leds.
config LEDS_WM831X_STATUS
tristate "LED support for status LEDs on WM831x PMICs"
@@ -293,7 +614,7 @@ config LEDS_WM831X_STATUS
depends on MFD_WM831X
help
This option enables support for the status LEDs of the WM831x
- series of PMICs.
+ series of PMICs.
config LEDS_WM8350
tristate "LED Support for WM8350 AudioPlus PMIC"
@@ -341,6 +662,20 @@ config LEDS_REGULATOR
help
This option enables support for regulator driven LEDs.
+config LEDS_BD2606MVV
+ tristate "LED driver for BD2606MVV"
+ depends on LEDS_CLASS
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This option enables support for BD2606MVV LED driver chips
+ accessed via the I2C bus. It supports setting brightness, with
+ the limitation that there are groups of two channels sharing
+ a brightness setting, but not the on/off setting.
+
+ To compile this driver as a module, choose M here: the module will
+ be called leds-bd2606mvv.
+
config LEDS_BD2802
tristate "LED driver for BD2802 RGB LED"
depends on LEDS_CLASS
@@ -353,16 +688,17 @@ config LEDS_INTEL_SS4200
tristate "LED driver for Intel NAS SS4200 series"
depends on LEDS_CLASS
depends on PCI && DMI
+ depends on X86
help
This option enables support for the Intel SS4200 series of
- Network Attached Storage servers. You may control the hard
- drive or power LEDs on the front panel. Using this driver
+ Network Attached Storage servers. You may control the hard
+ drive or power LEDs on the front panel. Using this driver
can stop the front LED from blinking after startup.
config LEDS_LT3593
tristate "LED driver for LT3593 controllers"
depends on LEDS_CLASS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This option enables support for LEDs driven by a Linear Technology
LT3593 controller. This controller uses a special one-wire pulse
@@ -379,68 +715,50 @@ config LEDS_ADP5520
To compile this driver as a module, choose M here: the module will
be called leds-adp5520.
-config LEDS_DELL_NETBOOKS
- tristate "External LED on Dell Business Netbooks"
+config LEDS_MAX5970
+ tristate "LED Support for Maxim 5970"
depends on LEDS_CLASS
- depends on X86 && ACPI_WMI
+ depends on MFD_MAX5970
help
- This adds support for the Latitude 2100 and similar
- notebooks that have an external LED.
+ This option enables support for the Maxim MAX5970 & MAX5978 smart
+ switch indication LEDs via the I2C bus.
+
+ To compile this driver as a module, choose M here: the module will
+ be called leds-max5970.
config LEDS_MC13783
tristate "LED Support for MC13XXX PMIC"
depends on LEDS_CLASS
depends on MFD_MC13XXX
help
- This option enable support for on-chip LED drivers found
- on Freescale Semiconductor MC13783/MC13892 PMIC.
+ This option enables support for on-chip LED drivers found
+ on Freescale Semiconductor MC13783/MC13892/MC34708 PMIC.
config LEDS_NS2
tristate "LED support for Network Space v2 GPIO LEDs"
depends on LEDS_CLASS
- depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || \
- MACH_NETSPACE_MAX_V2 || MACH_D2NET_V2 || \
- MACH_NETSPACE_V2_DT || MACH_INETSPACE_V2_DT || \
- MACH_NETSPACE_MAX_V2_DT || MACH_NETSPACE_MINI_V2_DT
- default y
+ depends on MACH_KIRKWOOD || MACH_ARMADA_370 || COMPILE_TEST
+ default y if MACH_KIRKWOOD || MACH_ARMADA_370
help
- This option enable support for the dual-GPIO LED found on the
- Network Space v2 board (and parents). This include Internet Space v2,
- Network Space (Max) v2 and d2 Network v2 boards.
+ This option enables support for the dual-GPIO LEDs found on the
+ following LaCie/Seagate boards:
+
+ Network Space v2 (and parents: Max, Mini)
+ Internet Space v2
+ d2 Network v2
+ n090401 (Seagate NAS 4-Bay)
config LEDS_NETXBIG
tristate "LED support for Big Network series LEDs"
- depends on MACH_NET2BIG_V2 || MACH_NET5BIG_V2
depends on LEDS_CLASS
- default y
+ depends on MACH_KIRKWOOD || COMPILE_TEST
+ depends on OF_GPIO
+ default MACH_KIRKWOOD
help
- This option enable support for LEDs found on the LaCie 2Big
+ This option enables support for LEDs found on the LaCie 2Big
and 5Big Network v2 boards. The LEDs are wired to a CPLD and are
controlled through a GPIO extension bus.
-config LEDS_ASIC3
- bool "LED support for the HTC ASIC3"
- depends on LEDS_CLASS=y
- depends on MFD_ASIC3
- default y
- help
- This option enables support for the LEDs on the HTC ASIC3. The HTC
- ASIC3 LED GPIOs are inputs, not outputs, thus the leds-gpio driver
- cannot be used. This driver supports hardware blinking with an on+off
- period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700.
-
-config LEDS_RENESAS_TPU
- bool "LED support for Renesas TPU"
- depends on LEDS_CLASS=y && HAVE_CLK && GPIOLIB
- help
- This option enables build of the LED TPU platform driver,
- suitable to drive any TPU channel on newer Renesas SoCs.
- The driver controls the GPIO pin connected to the LED via
- the GPIO framework and expects the LED to be connected to
- a pin that can be driven in both GPIO mode and using TPU
- pin function. The latter to support brightness control.
- Brightness control is supported but hardware blinking is not.
-
config LEDS_TCA6507
tristate "LED Support for TCA6507 I2C chip"
depends on LEDS_CLASS && I2C
@@ -449,6 +767,28 @@ config LEDS_TCA6507
LED driver chips accessed via the I2C bus.
Driver support brightness control and hardware-assisted blinking.
+config LEDS_TLC591XX
+ tristate "LED driver for TLC59108 and TLC59116 controllers"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for Texas Instruments TLC59108
+ and TLC59116 LED controllers.
+
+config LEDS_MAX77650
+ tristate "LED support for Maxim MAX77650 PMIC"
+ depends on LEDS_CLASS && MFD_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
@@ -457,20 +797,71 @@ config LEDS_MAX8997
MAXIM MAX8997 PMIC.
config LEDS_LM355x
- tristate "LED support for LM355x Chips, LM3554 and LM3556"
+ tristate "LED support for LM3554 and LM3556 chips"
depends on LEDS_CLASS && I2C
select REGMAP_I2C
help
- This option enables support for LEDs connected to LM355x.
- LM355x includes Torch, Flash and Indicator functions.
+ This option enables support for LEDs connected to LM3554
+ and LM3556. It includes Torch, Flash and Indicator functions.
config LEDS_OT200
tristate "LED support for the Bachmann OT200"
- depends on LEDS_CLASS && HAS_IOMEM
+ depends on LEDS_CLASS && HAS_IOPORT && (X86_32 || COMPILE_TEST)
help
This option enables support for the LEDs on the Bachmann OT200.
Say Y to enable LEDs on the Bachmann OT200.
+config LEDS_MENF21BMC
+ tristate "LED support for the MEN 14F021P00 BMC"
+ depends on LEDS_CLASS && MFD_MENF21BMC
+ help
+ Say Y here to include support for the MEN 14F021P00 BMC LEDs.
+
+ This driver can also be built as a module. If so the module
+ will be called leds-menf21bmc.
+
+config LEDS_IS31FL319X
+ tristate "LED Support for ISSI IS31FL319x I2C LED controller family"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for LEDs connected to ISSI IS31FL319x
+ fancy LED driver chips accessed via the I2C bus.
+ Driver supports individual PWM brightness control for each channel.
+
+ This driver can also be built as a module. If so the module will be
+ called leds-is31fl319x.
+
+config LEDS_IS31FL32XX
+ tristate "LED support for ISSI IS31FL32XX I2C LED controller family"
+ depends on LEDS_CLASS && I2C && OF
+ help
+ Say Y here to include support for ISSI IS31FL32XX and Si-En SN32xx
+ LED controllers. They are I2C devices with multiple constant-current
+ channels, each with independent 256-level PWM control.
+
+config LEDS_SC27XX_BLTC
+ tristate "LED support for the SC27xx breathing light controller"
+ depends on LEDS_CLASS && MFD_SC27XX_PMIC
+ depends on OF
+ help
+ Say Y here to include support for the SC27xx breathing light controller
+ LEDs.
+
+ 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
tristate "LED support for the BlinkM I2C RGB LED"
depends on LEDS_CLASS
@@ -479,7 +870,164 @@ 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
+ depends on PPC_POWERNV
+ depends on OF
+ help
+ This option enables support for the system LEDs present on
+ PowerNV platforms. Say 'y' to enable this support in kernel.
+ To compile this driver as a module, choose 'm' here: the module
+ will be called leds-powernv.
+
+config LEDS_SYSCON
+ bool "LED support for LEDs on system controllers"
+ depends on LEDS_CLASS=y
+ depends on MFD_SYSCON
+ depends on OF
+ help
+ This option enables support for the LEDs on syscon type
+ devices. This will only work with device tree enabled
+ devices.
+
+config LEDS_PM8058
+ tristate "LED Support for the Qualcomm PM8058 PMIC"
+ depends on MFD_PM8XXX
+ depends on LEDS_CLASS
+ help
+ Choose this option if you want to use the LED drivers in
+ the Qualcomm PM8058 PMIC.
+
+config LEDS_MLXCPLD
+ tristate "LED support for the Mellanox boards"
+ depends on X86 && DMI
+ depends on LEDS_CLASS
+ help
+ This option enables support for the LEDs on the Mellanox
+ boards. Say Y to enable these.
+
+config LEDS_MLXREG
+ tristate "LED support for the Mellanox switches management control"
+ depends on LEDS_CLASS
+ help
+ This option enables support for the LEDs on the Mellanox Ethernet and
+ InfiniBand switches. The driver can be activated by the platform device
+ device add call. Say Y to enable these. To compile this driver as a
+ module, choose 'M' here: the module will be called leds-mlxreg.
+
+config LEDS_USER
+ tristate "Userspace LED support"
+ depends on LEDS_CLASS
+ help
+ This option enables support for userspace LEDs. Say 'y' to enable this
+ support in kernel. To compile this driver as a module, choose 'm' here:
+ the module will be called uleds.
+
+config LEDS_NIC78BX
+ tristate "LED support for NI PXI NIC78bx devices"
+ depends on LEDS_CLASS
+ 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.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-nic78bx.
+
+config LEDS_SPI_BYTE
+ tristate "LED support for SPI LED controller with a single byte"
+ depends on LEDS_CLASS
+ depends on SPI
+ help
+ This option enables support for LED controller which use a single byte
+ for controlling the brightness. Currently the following controller is
+ supported: Ubiquiti airCube ISP microcontroller based LED controller.
+
+config LEDS_TI_LMU_COMMON
+ tristate "LED driver for TI LMU" if COMPILE_TEST
+ select REGMAP
+ help
+ Say Y to enable the LED driver for TI LMU devices.
+ This supports common features between the TI LM3532, LM3631, LM3632,
+ LM3633, LM3695 and LM3697.
+
+config LEDS_LM3697
+ tristate "LED driver for LM3697"
+ depends on LEDS_CLASS && I2C && OF
+ select LEDS_TI_LMU_COMMON
+ help
+ Say Y to enable the LM3697 LED driver for TI LMU devices.
+ This supports the LED device LM3697.
+
+config LEDS_LM36274
+ tristate "LED driver for LM36274"
+ depends on LEDS_CLASS && MFD_TI_LMU
+ select LEDS_TI_LMU_COMMON
+ help
+ 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
+ depends on TPS6105X
+ default y if TPS6105X
+ help
+ This driver supports TPS61050/TPS61052 LED chips.
+ It is a single boost converter primarily for white LEDs and
+ audio amplifiers.
+
+config LEDS_IP30
+ tristate "LED support for SGI Octane machines"
+ depends on LEDS_CLASS
+ depends on SGI_MFD_IOC3 || COMPILE_TEST
+ help
+ This option enables support for the Red and White LEDs of
+ SGI Octane machines.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-ip30.
+
+config LEDS_ACER_A500
+ tristate "Power button LED support for Acer Iconia Tab A500"
+ depends on LEDS_CLASS && MFD_ACER_A500_EC
+ help
+ This option enables support for the Power Button LED of
+ Acer Iconia Tab A500.
+
+source "drivers/leds/blink/Kconfig"
+
+comment "Flash and Torch LED drivers"
+source "drivers/leds/flash/Kconfig"
+
+comment "RGB LED drivers"
+source "drivers/leds/rgb/Kconfig"
+
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"
+comment "Simatic LED drivers"
+source "drivers/leds/simatic/Kconfig"
+
endif # NEW_LEDS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index ac2897732b02..9a0333ec1a86 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -1,61 +1,127 @@
+# SPDX-License-Identifier: GPL-2.0
# LED Core
obj-$(CONFIG_NEW_LEDS) += led-core.o
obj-$(CONFIG_LEDS_CLASS) += led-class.o
+obj-$(CONFIG_LEDS_CLASS_FLASH) += led-class-flash.o
+obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += led-class-multicolor.o
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
+obj-$(CONFIG_LEDS_KUNIT_TEST) += led-test.o
-# LED Platform Drivers
+# LED Platform Drivers (keep this sorted, M-| sort)
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
-obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o
+obj-$(CONFIG_LEDS_ACER_A500) += leds-acer-a500.o
+obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
+obj-$(CONFIG_LEDS_AN30259A) += leds-an30259a.o
+obj-$(CONFIG_LEDS_APU) += leds-apu.o
+obj-$(CONFIG_LEDS_ARIEL) += leds-ariel.o
+obj-$(CONFIG_LEDS_AW200XX) += leds-aw200xx.o
+obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
+obj-$(CONFIG_LEDS_BCM6328) += leds-bcm6328.o
+obj-$(CONFIG_LEDS_BCM6358) += leds-bcm6358.o
+obj-$(CONFIG_LEDS_BD2606MVV) += leds-bd2606mvv.o
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
-obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
-obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
-obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
-obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o
-obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
-obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
-obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
-obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
+obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
+obj-$(CONFIG_LEDS_CHT_WCOVE) += leds-cht-wcove.o
+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_SUNFIRE) += leds-sunfire.o
-obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
-obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.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
+obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o
+obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o
+obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
+obj-$(CONFIG_LEDS_IP30) += leds-ip30.o
+obj-$(CONFIG_LEDS_IPAQ_MICRO) += leds-ipaq-micro.o
+obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o
+obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o
+obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
+obj-$(CONFIG_LEDS_LM3532) += leds-lm3532.o
+obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
+obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
+obj-$(CONFIG_LEDS_LM36274) += leds-lm36274.o
+obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o
+obj-$(CONFIG_LEDS_LM3692X) += leds-lm3692x.o
+obj-$(CONFIG_LEDS_LM3697) += leds-lm3697.o
+obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
-obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o
+obj-$(CONFIG_LEDS_LP3952) += leds-lp3952.o
+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_TCA6507) += leds-tca6507.o
-obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o
-obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.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
+obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
+obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o
+obj-$(CONFIG_LEDS_MLXREG) += leds-mlxreg.o
+obj-$(CONFIG_LEDS_MT6323) += leds-mt6323.o
+obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
+obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
+obj-$(CONFIG_LEDS_NIC78BX) += leds-nic78bx.o
+obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
obj-$(CONFIG_LEDS_OT200) += leds-ot200.o
-obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
+obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o
obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
-obj-$(CONFIG_LEDS_PCA9633) += leds-pca9633.o
-obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
-obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o
-obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
-obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
+obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA995X) += leds-pca995x.o
+obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o
+obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
+obj-$(CONFIG_LEDS_QNAP_MCU) += leds-qnap-mcu.o
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
-obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
-obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
-obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
-obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o
-obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
-obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
-obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
-obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
-obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o
-obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
-obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
-obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
+obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
+obj-$(CONFIG_LEDS_ST1202) += leds-st1202.o
+obj-$(CONFIG_LEDS_SUN50I_A100) += leds-sun50i-a100.o
+obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
+obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
+obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
+obj-$(CONFIG_LEDS_TI_LMU_COMMON) += leds-ti-lmu-common.o
+obj-$(CONFIG_LEDS_TLC591XX) += leds-tlc591xx.o
+obj-$(CONFIG_LEDS_TPS6105X) += leds-tps6105x.o
+obj-$(CONFIG_LEDS_TURRIS_OMNIA) += leds-turris-omnia.o
+obj-$(CONFIG_LEDS_UPBOARD) += leds-upboard.o
+obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o
+obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
+obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
+
+# Kinetic ExpressWire Protocol
+obj-$(CONFIG_LEDS_EXPRESSWIRE) += leds-expresswire.o
# LED SPI Drivers
+obj-$(CONFIG_LEDS_CR0014114) += leds-cr0014114.o
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
+obj-$(CONFIG_LEDS_EL15203000) += leds-el15203000.o
+obj-$(CONFIG_LEDS_SPI_BYTE) += leds-spi-byte.o
+
+# LED Userspace Drivers
+obj-$(CONFIG_LEDS_USER) += uleds.o
+
+# Flash and Torch LED Drivers
+obj-$(CONFIG_LEDS_CLASS_FLASH) += flash/
+
+# RGB LED Drivers
+obj-$(CONFIG_LEDS_CLASS_MULTICOLOR) += rgb/
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
+
+# LED Blink
+obj-y += blink/
+
+# Simatic LED drivers
+obj-y += simatic/
diff --git a/drivers/leds/TODO b/drivers/leds/TODO
new file mode 100644
index 000000000000..bfa60fa1d812
--- /dev/null
+++ b/drivers/leds/TODO
@@ -0,0 +1,75 @@
+-*- org -*-
+
+* On/off LEDs should have max_brightness of 1
+* Get rid of enum led_brightness
+
+It is really an integer, as maximum is configurable. Get rid of it, or
+make it into typedef or something.
+
+* Review atomicity requirements in LED subsystem
+
+Calls that may and that may not block are mixed in same structure, and
+semantics is sometimes non-intuitive. (For example blink callback may
+not sleep.) Review the requirements for any bugs and document them
+clearly.
+
+* LED names are still a mess
+
+No two LEDs have same name, so the names are probably unusable for the
+userland. Nudge authors into creating common LED names for common
+functionality.
+
+? Perhaps check for known LED names during boot, and warn if there are
+LEDs not on the list?
+
+* Split drivers into subdirectories
+
+The number of drivers is getting big, and driver for on/off LED on a
+i/o port is really quite different from camera flash LED, which is
+really different from driver for RGB color LED that can run its own
+microcode. Split the drivers somehow.
+
+* Figure out what to do with RGB leds
+
+Multicolor is a bit too abstract. Yes, we can have
+Green-Magenta-Ultraviolet LED, but so far all the LEDs we support are
+RGB, and not even RGB-White or RGB-Yellow variants emerged.
+
+Multicolor is not a good fit for RGB LED. It does not really know
+about LED color. In particular, there's no way to make LED "white".
+
+Userspace is interested in knowing "this LED can produce arbitrary
+color", which not all multicolor LEDs can.
+
+ Proposal: let's add "rgb" to led_colors in drivers/leds/led-core.c,
+ add corresponding device tree defines, and use that, instead of
+ multicolor for RGB LEDs.
+
+ We really need to do that now; "white" stuff can wait.
+
+RGB LEDs are quite common, and it would be good to be able to turn LED
+white and to turn it into any arbitrary color. It is essential that
+userspace is able to set arbitrary colors, and it might be good to
+have that ability from kernel, too... to allow full-color triggers.
+
+* Command line utility to manipulate the LEDs?
+
+/sys interface is not really suitable to use by hand, should we have
+an utility to perform LED control?
+
+In particular, LED names are still a mess (see above) and utility
+could help there by presenting both old and new names while we clean
+them up.
+
+In future, I'd like utility to accept both old and new names while we
+clean them up.
+
+It would be also nice to have useful listing mode -- name, type,
+current brightness/trigger...
+
+In future, it would be good to be able to set rgb led to particular
+color.
+
+And probably user-friendly interface to access LEDs for particular
+ethernet interface would be nice.
+
diff --git a/drivers/leds/blink/Kconfig b/drivers/leds/blink/Kconfig
new file mode 100644
index 000000000000..bdcb7377cd4e
--- /dev/null
+++ b/drivers/leds/blink/Kconfig
@@ -0,0 +1,31 @@
+config LEDS_BCM63138
+ tristate "LED Support for Broadcom BCM63138 SoC"
+ depends on LEDS_CLASS
+ depends on ARCH_BCMBCA || ARCH_BCM_5301X || BCM63XX || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ default ARCH_BCMBCA
+ help
+ This option enables support for LED controller that is part of
+ BCM63138 SoC. The same hardware block is known to be also used
+ in BCM4908, BCM6848, BCM6858, BCM63148, BCM63381 and BCM68360.
+
+ If compiled as module it will be called leds-bcm63138.
+
+config LEDS_LGM
+ tristate "LED support for LGM SoC series"
+ depends on X86 || COMPILE_TEST
+ depends on GPIOLIB && LEDS_CLASS && MFD_SYSCON && OF
+ help
+ This option enables support for LEDs connected to GPIO lines on
+ Lightning Mountain (LGM) SoC. Lightning Mountain is a AnyWAN
+ gateway-on-a-chip SoC to be shipped on mid and high end home
+ gateways and routers.
+
+ These LEDs are driven by a Serial Shift Output (SSO) controller.
+ The driver supports hardware blinking and the LEDs can be configured
+ to be triggered by software/CPU or by hardware.
+
+ Say 'Y' here if you are working on LGM SoC based platform. Otherwise,
+ say 'N'. To compile this driver as a module, choose M here: the module
+ will be called leds-lgm-sso.
diff --git a/drivers/leds/blink/Makefile b/drivers/leds/blink/Makefile
new file mode 100644
index 000000000000..447029f4153a
--- /dev/null
+++ b/drivers/leds/blink/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_LEDS_BCM63138) += leds-bcm63138.o
+obj-$(CONFIG_LEDS_LGM) += leds-lgm-sso.o
diff --git a/drivers/leds/blink/leds-bcm63138.c b/drivers/leds/blink/leds-bcm63138.c
new file mode 100644
index 000000000000..ef2e511438cc
--- /dev/null
+++ b/drivers/leds/blink/leds-bcm63138.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * 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>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM63138_MAX_LEDS 32
+#define BCM63138_MAX_BRIGHTNESS 9
+
+#define BCM63138_LED_BITS 4 /* how many bits control a single LED */
+#define BCM63138_LED_MASK ((1 << BCM63138_LED_BITS) - 1) /* 0xf */
+#define BCM63138_LEDS_PER_REG (32 / BCM63138_LED_BITS) /* 8 */
+
+#define BCM63138_GLB_CTRL 0x00
+#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
+#define BCM63138_FLASH_RATE_CTRL1 0x10
+#define BCM63138_FLASH_RATE_CTRL2 0x14
+#define BCM63138_FLASH_RATE_CTRL3 0x18
+#define BCM63138_FLASH_RATE_CTRL4 0x1c
+#define BCM63138_BRIGHT_CTRL1 0x20
+#define BCM63138_BRIGHT_CTRL2 0x24
+#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
+#define BCM63138_PARALLEL_LED_POLARITY 0xc0
+#define BCM63138_SERIAL_LED_POLARITY 0xc4
+#define BCM63138_HW_LED_STATUS 0xc8
+#define BCM63138_FLASH_CTRL_STATUS 0xcc
+#define BCM63138_FLASH_BRT_CTRL 0xd0
+#define BCM63138_FLASH_P_LED_OUT_STATUS 0xd4
+#define BCM63138_FLASH_S_LED_OUT_STATUS 0xd8
+
+struct bcm63138_leds {
+ struct device *dev;
+ void __iomem *base;
+ spinlock_t lock;
+};
+
+struct bcm63138_led {
+ struct bcm63138_leds *leds;
+ struct led_classdev cdev;
+ u32 pin;
+ bool active_low;
+};
+
+/*
+ * I/O access
+ */
+
+static void bcm63138_leds_write(struct bcm63138_leds *leds, unsigned int reg,
+ u32 data)
+{
+ writel(data, leds->base + reg);
+}
+
+static unsigned long bcm63138_leds_read(struct bcm63138_leds *leds,
+ unsigned int reg)
+{
+ return readl(leds->base + reg);
+}
+
+static void bcm63138_leds_update_bits(struct bcm63138_leds *leds,
+ unsigned int reg, u32 mask, u32 val)
+{
+ WARN_ON(val & ~mask);
+
+ bcm63138_leds_write(leds, reg, (bcm63138_leds_read(leds, reg) & ~mask) | (val & mask));
+}
+
+/*
+ * Helpers
+ */
+
+static void bcm63138_leds_set_flash_rate(struct bcm63138_leds *leds,
+ struct bcm63138_led *led,
+ u8 value)
+{
+ int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
+ int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
+
+ bcm63138_leds_update_bits(leds, BCM63138_FLASH_RATE_CTRL1 + reg_offset,
+ BCM63138_LED_MASK << shift, value << shift);
+}
+
+static void bcm63138_leds_set_bright(struct bcm63138_leds *leds,
+ struct bcm63138_led *led,
+ u8 value)
+{
+ int reg_offset = (led->pin >> fls((BCM63138_LEDS_PER_REG - 1))) * 4;
+ int shift = (led->pin & (BCM63138_LEDS_PER_REG - 1)) * BCM63138_LED_BITS;
+
+ bcm63138_leds_update_bits(leds, BCM63138_BRIGHT_CTRL1 + reg_offset,
+ BCM63138_LED_MASK << shift, value << shift);
+}
+
+static void bcm63138_leds_enable_led(struct bcm63138_leds *leds,
+ struct bcm63138_led *led,
+ enum led_brightness value)
+{
+ u32 bit = BIT(led->pin);
+
+ bcm63138_leds_update_bits(leds, BCM63138_SW_DATA, bit, value ? bit : 0);
+}
+
+/*
+ * API callbacks
+ */
+
+static void bcm63138_leds_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
+ struct bcm63138_leds *leds = led->leds;
+
+ 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);
+}
+
+static int bcm63138_leds_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct bcm63138_led *led = container_of(led_cdev, struct bcm63138_led, cdev);
+ struct bcm63138_leds *leds = led->leds;
+ u8 value;
+
+ if (!*delay_on && !*delay_off) {
+ *delay_on = 640;
+ *delay_off = 640;
+ }
+
+ if (*delay_on != *delay_off) {
+ dev_dbg(led_cdev->dev, "Blinking at unequal delays is not supported\n");
+ return -EINVAL;
+ }
+
+ switch (*delay_on) {
+ case 1152 ... 1408: /* 1280 ms ± 10% */
+ value = 0x7;
+ break;
+ case 576 ... 704: /* 640 ms ± 10% */
+ value = 0x6;
+ break;
+ case 288 ... 352: /* 320 ms ± 10% */
+ value = 0x5;
+ break;
+ case 126 ... 154: /* 140 ms ± 10% */
+ value = 0x4;
+ break;
+ case 59 ... 72: /* 65 ms ± 10% */
+ value = 0x3;
+ break;
+ default:
+ dev_dbg(led_cdev->dev, "Blinking delay value %lu is unsupported\n",
+ *delay_on);
+ return -EINVAL;
+ }
+
+ guard(spinlock_irqsave)(&leds->lock);
+
+ bcm63138_leds_enable_led(leds, led, BCM63138_MAX_BRIGHTNESS);
+ bcm63138_leds_set_flash_rate(leds, led, value);
+
+ return 0;
+}
+
+/*
+ * LED driver
+ */
+
+static void bcm63138_leds_create_led(struct bcm63138_leds *leds,
+ struct device_node *np)
+{
+ struct led_init_data init_data = {
+ .fwnode = of_fwnode_handle(np),
+ };
+ struct device *dev = leds->dev;
+ struct bcm63138_led *led;
+ struct pinctrl *pinctrl;
+ u32 bit;
+ int err;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ dev_err(dev, "Failed to alloc LED\n");
+ return;
+ }
+
+ led->leds = leds;
+
+ if (of_property_read_u32(np, "reg", &led->pin)) {
+ dev_err(dev, "Missing \"reg\" property in %pOF\n", np);
+ goto err_free;
+ }
+
+ if (led->pin >= BCM63138_MAX_LEDS) {
+ dev_err(dev, "Invalid \"reg\" value %d\n", led->pin);
+ goto err_free;
+ }
+
+ led->active_low = of_property_read_bool(np, "active-low");
+
+ led->cdev.max_brightness = BCM63138_MAX_BRIGHTNESS;
+ led->cdev.brightness_set = bcm63138_leds_brightness_set;
+ led->cdev.blink_set = bcm63138_leds_blink_set;
+
+ err = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (err) {
+ dev_err(dev, "Failed to register LED %pOF: %d\n", np, err);
+ goto err_free;
+ }
+
+ pinctrl = devm_pinctrl_get_select_default(led->cdev.dev);
+ if (IS_ERR(pinctrl) && PTR_ERR(pinctrl) != -ENODEV) {
+ dev_warn(led->cdev.dev, "Failed to select %pOF pinctrl: %ld\n",
+ np, PTR_ERR(pinctrl));
+ }
+
+ bit = BIT(led->pin);
+ bcm63138_leds_update_bits(leds, BCM63138_PARALLEL_LED_POLARITY, bit,
+ led->active_low ? 0 : bit);
+ bcm63138_leds_update_bits(leds, BCM63138_HW_LED_EN, bit, 0);
+ bcm63138_leds_set_flash_rate(leds, led, 0);
+ bcm63138_leds_enable_led(leds, led, led->cdev.brightness);
+
+ return;
+
+err_free:
+ devm_kfree(dev, led);
+}
+
+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;
+ u32 shift_bits;
+
+ leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds->dev = dev;
+
+ leds->base = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(leds->base))
+ return PTR_ERR(leds->base);
+
+ 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);
+ bcm63138_leds_write(leds, BCM63138_HW_LED_EN, 0);
+ bcm63138_leds_write(leds, BCM63138_SERIAL_LED_POLARITY, 0);
+ bcm63138_leds_write(leds, BCM63138_PARALLEL_LED_POLARITY, 0);
+
+ for_each_available_child_of_node_scoped(np, child) {
+ bcm63138_leds_create_led(leds, child);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm63138_leds_of_match_table[] = {
+ { .compatible = "brcm,bcm63138-leds", },
+ { },
+};
+
+static struct platform_driver bcm63138_leds_driver = {
+ .probe = bcm63138_leds_probe,
+ .driver = {
+ .name = "leds-bcm63xxx",
+ .of_match_table = bcm63138_leds_of_match_table,
+ },
+};
+
+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
new file mode 100644
index 000000000000..8923d2df4704
--- /dev/null
+++ b/drivers/leds/blink/leds-lgm-sso.c
@@ -0,0 +1,876 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Intel Lightning Mountain SoC LED Serial Shift Output Controller driver
+ *
+ * Copyright (c) 2020 Intel Corporation.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/sizes.h>
+#include <linux/uaccess.h>
+
+#define SSO_DEV_NAME "lgm-sso"
+
+#define LED_BLINK_H8_0 0x0
+#define LED_BLINK_H8_1 0x4
+#define GET_FREQ_OFFSET(pin, src) (((pin) * 6) + ((src) * 2))
+#define GET_SRC_OFFSET(pinc) (((pin) * 6) + 4)
+
+#define DUTY_CYCLE(x) (0x8 + ((x) * 4))
+#define SSO_CON0 0x2B0
+#define SSO_CON0_RZFL BIT(26)
+#define SSO_CON0_BLINK_R BIT(30)
+#define SSO_CON0_SWU BIT(31)
+
+#define SSO_CON1 0x2B4
+#define SSO_CON1_FCDSC GENMASK(21, 20) /* Fixed Divider Shift Clock */
+#define SSO_CON1_FPID GENMASK(24, 23)
+#define SSO_CON1_GPTD GENMASK(26, 25)
+#define SSO_CON1_US GENMASK(31, 30)
+
+#define SSO_CPU 0x2B8
+#define SSO_CON2 0x2C4
+#define SSO_CON3 0x2C8
+
+/* Driver MACRO */
+#define MAX_PIN_NUM_PER_BANK SZ_32
+#define MAX_GROUP_NUM SZ_4
+#define PINS_PER_GROUP SZ_8
+#define FPID_FREQ_RANK_MAX SZ_4
+#define SSO_LED_MAX_NUM SZ_32
+#define MAX_FREQ_RANK 10
+#define DEF_GPTC_CLK_RATE 200000000
+#define SSO_DEF_BRIGHTNESS LED_HALF
+#define DATA_CLK_EDGE 0 /* 0-rising, 1-falling */
+
+static const u32 freq_div_tbl[] = {4000, 2000, 1000, 800};
+static const int freq_tbl[] = {2, 4, 8, 10, 50000, 100000, 200000, 250000};
+static const int shift_clk_freq_tbl[] = {25000000, 12500000, 6250000, 3125000};
+
+/*
+ * Update Source to update the SOUTs
+ * SW - Software has to update the SWU bit
+ * GPTC - General Purpose timer is used as clock source
+ * FPID - Divided FSC clock (FPID) is used as clock source
+ */
+enum {
+ US_SW = 0,
+ US_GPTC = 1,
+ US_FPID = 2
+};
+
+enum {
+ MAX_FPID_FREQ_RANK = 5, /* 1 to 4 */
+ MAX_GPTC_FREQ_RANK = 9, /* 5 to 8 */
+ MAX_GPTC_HS_FREQ_RANK = 10, /* 9 to 10 */
+};
+
+enum {
+ LED_GRP0_PIN_MAX = 24,
+ LED_GRP1_PIN_MAX = 29,
+ LED_GRP2_PIN_MAX = 32,
+};
+
+enum {
+ LED_GRP0_0_23,
+ LED_GRP1_24_28,
+ LED_GRP2_29_31,
+ LED_GROUP_MAX,
+};
+
+enum {
+ CLK_SRC_FPID = 0,
+ CLK_SRC_GPTC = 1,
+ CLK_SRC_GPTC_HS = 2,
+};
+
+struct sso_led_priv;
+
+struct sso_led_desc {
+ const char *name;
+ const char *default_trigger;
+ unsigned int brightness;
+ unsigned int blink_rate;
+ unsigned int retain_state_suspended:1;
+ unsigned int retain_state_shutdown:1;
+ unsigned int panic_indicator:1;
+ unsigned int hw_blink:1;
+ unsigned int hw_trig:1;
+ unsigned int blinking:1;
+ int freq_idx;
+ u32 pin;
+};
+
+struct sso_led {
+ struct list_head list;
+ struct led_classdev cdev;
+ struct gpio_desc *gpiod;
+ struct sso_led_desc desc;
+ struct sso_led_priv *priv;
+};
+
+struct sso_gpio {
+ struct gpio_chip chip;
+ int shift_clk_freq;
+ int edge;
+ int freq;
+ u32 pins;
+ u32 alloc_bitmap;
+};
+
+struct sso_led_priv {
+ struct regmap *mmap;
+ struct device *dev;
+ struct platform_device *pdev;
+ struct clk_bulk_data clocks[2];
+ u32 fpid_clkrate;
+ u32 gptc_clkrate;
+ u32 freq[MAX_FREQ_RANK];
+ struct list_head led_list;
+ struct sso_gpio gpio;
+};
+
+static int sso_get_blink_rate_idx(struct sso_led_priv *priv, u32 rate)
+{
+ int i;
+
+ for (i = 0; i < MAX_FREQ_RANK; i++) {
+ if (rate <= priv->freq[i])
+ return i;
+ }
+
+ return -1;
+}
+
+static unsigned int sso_led_pin_to_group(u32 pin)
+{
+ if (pin < LED_GRP0_PIN_MAX)
+ return LED_GRP0_0_23;
+ else if (pin < LED_GRP1_PIN_MAX)
+ return LED_GRP1_24_28;
+ else
+ return LED_GRP2_29_31;
+}
+
+static u32 sso_led_get_freq_src(int freq_idx)
+{
+ if (freq_idx < MAX_FPID_FREQ_RANK)
+ return CLK_SRC_FPID;
+ else if (freq_idx < MAX_GPTC_FREQ_RANK)
+ return CLK_SRC_GPTC;
+ else
+ return CLK_SRC_GPTC_HS;
+}
+
+static u32 sso_led_pin_blink_off(u32 pin, unsigned int group)
+{
+ if (group == LED_GRP2_29_31)
+ return pin - LED_GRP1_PIN_MAX;
+ else if (group == LED_GRP1_24_28)
+ return pin - LED_GRP0_PIN_MAX;
+ else /* led 0 - 23 in led 32 location */
+ return SSO_LED_MAX_NUM - LED_GRP1_PIN_MAX;
+}
+
+static struct sso_led
+*cdev_to_sso_led_data(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct sso_led, cdev);
+}
+
+static void sso_led_freq_set(struct sso_led_priv *priv, u32 pin, int freq_idx)
+{
+ u32 reg, off, freq_src, val_freq;
+ u32 low, high, val;
+ unsigned int group;
+
+ if (!freq_idx)
+ return;
+
+ group = sso_led_pin_to_group(pin);
+ freq_src = sso_led_get_freq_src(freq_idx);
+ off = sso_led_pin_blink_off(pin, group);
+
+ if (group == LED_GRP0_0_23)
+ return;
+ else if (group == LED_GRP1_24_28)
+ reg = LED_BLINK_H8_0;
+ else
+ reg = LED_BLINK_H8_1;
+
+ if (freq_src == CLK_SRC_FPID)
+ val_freq = freq_idx - 1;
+ else if (freq_src == CLK_SRC_GPTC)
+ val_freq = freq_idx - MAX_FPID_FREQ_RANK;
+
+ /* set blink rate idx */
+ if (freq_src != CLK_SRC_GPTC_HS) {
+ low = GET_FREQ_OFFSET(off, freq_src);
+ high = low + 2;
+ val = val_freq << high;
+ regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val);
+ }
+
+ /* select clock source */
+ low = GET_SRC_OFFSET(off);
+ high = low + 2;
+ val = freq_src << high;
+ regmap_update_bits(priv->mmap, reg, GENMASK(high, low), val);
+}
+
+static void sso_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct sso_led_priv *priv;
+ struct sso_led_desc *desc;
+ struct sso_led *led;
+ int val;
+
+ led = cdev_to_sso_led_data(led_cdev);
+ priv = led->priv;
+ desc = &led->desc;
+
+ desc->brightness = brightness;
+ regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), brightness);
+
+ if (brightness == LED_OFF)
+ val = 0;
+ else
+ val = 1;
+
+ /* HW blink off */
+ if (desc->hw_blink && !val && desc->blinking) {
+ desc->blinking = 0;
+ regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin), 0);
+ } else if (desc->hw_blink && val && !desc->blinking) {
+ desc->blinking = 1;
+ regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin),
+ 1 << desc->pin);
+ }
+
+ if (!desc->hw_trig)
+ gpiod_set_value(led->gpiod, val);
+}
+
+static enum led_brightness sso_led_brightness_get(struct led_classdev *led_cdev)
+{
+ struct sso_led *led = cdev_to_sso_led_data(led_cdev);
+
+ return (enum led_brightness)led->desc.brightness;
+}
+
+static int
+delay_to_freq_idx(struct sso_led *led, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct sso_led_priv *priv = led->priv;
+ unsigned long delay;
+ int freq_idx;
+ u32 freq;
+
+ if (!*delay_on && !*delay_off) {
+ *delay_on = *delay_off = (1000 / priv->freq[0]) / 2;
+ return 0;
+ }
+
+ delay = *delay_on + *delay_off;
+ freq = 1000 / delay;
+
+ freq_idx = sso_get_blink_rate_idx(priv, freq);
+ if (freq_idx == -1)
+ freq_idx = MAX_FREQ_RANK - 1;
+
+ delay = 1000 / priv->freq[freq_idx];
+ *delay_on = *delay_off = delay / 2;
+
+ if (!*delay_on)
+ *delay_on = *delay_off = 1;
+
+ return freq_idx;
+}
+
+static int
+sso_led_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct sso_led_priv *priv;
+ struct sso_led *led;
+ int freq_idx;
+
+ led = cdev_to_sso_led_data(led_cdev);
+ priv = led->priv;
+ freq_idx = delay_to_freq_idx(led, delay_on, delay_off);
+
+ sso_led_freq_set(priv, led->desc.pin, freq_idx);
+ regmap_update_bits(priv->mmap, SSO_CON2, BIT(led->desc.pin),
+ 1 << led->desc.pin);
+ led->desc.freq_idx = freq_idx;
+ led->desc.blink_rate = priv->freq[freq_idx];
+ led->desc.blinking = 1;
+
+ return 1;
+}
+
+static void sso_led_hw_cfg(struct sso_led_priv *priv, struct sso_led *led)
+{
+ struct sso_led_desc *desc = &led->desc;
+
+ /* set freq */
+ if (desc->hw_blink) {
+ sso_led_freq_set(priv, desc->pin, desc->freq_idx);
+ regmap_update_bits(priv->mmap, SSO_CON2, BIT(desc->pin),
+ 1 << desc->pin);
+ }
+
+ if (desc->hw_trig)
+ regmap_update_bits(priv->mmap, SSO_CON3, BIT(desc->pin),
+ 1 << desc->pin);
+
+ /* set brightness */
+ regmap_write(priv->mmap, DUTY_CYCLE(desc->pin), desc->brightness);
+
+ /* enable output */
+ if (!desc->hw_trig && desc->brightness)
+ gpiod_set_value(led->gpiod, 1);
+}
+
+static int sso_create_led(struct sso_led_priv *priv, struct sso_led *led,
+ struct fwnode_handle *child)
+{
+ struct sso_led_desc *desc = &led->desc;
+ struct led_init_data init_data;
+ int err;
+
+ init_data.fwnode = child;
+ init_data.devicename = SSO_DEV_NAME;
+ init_data.default_label = ":";
+
+ led->cdev.default_trigger = desc->default_trigger;
+ led->cdev.brightness_set = sso_led_brightness_set;
+ led->cdev.brightness_get = sso_led_brightness_get;
+ led->cdev.brightness = desc->brightness;
+ led->cdev.max_brightness = LED_FULL;
+
+ if (desc->retain_state_shutdown)
+ led->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
+ if (desc->retain_state_suspended)
+ led->cdev.flags |= LED_CORE_SUSPENDRESUME;
+ if (desc->panic_indicator)
+ led->cdev.flags |= LED_PANIC_INDICATOR;
+
+ if (desc->hw_blink)
+ led->cdev.blink_set = sso_led_blink_set;
+
+ sso_led_hw_cfg(priv, led);
+
+ err = devm_led_classdev_register_ext(priv->dev, &led->cdev, &init_data);
+ if (err)
+ return err;
+
+ list_add(&led->list, &priv->led_list);
+
+ return 0;
+}
+
+static void sso_init_freq(struct sso_led_priv *priv)
+{
+ int i;
+
+ priv->freq[0] = 0;
+ for (i = 1; i < MAX_FREQ_RANK; i++) {
+ if (i < MAX_FPID_FREQ_RANK) {
+ priv->freq[i] = priv->fpid_clkrate / freq_div_tbl[i - 1];
+ } else if (i < MAX_GPTC_FREQ_RANK) {
+ priv->freq[i] = priv->gptc_clkrate /
+ freq_div_tbl[i - MAX_FPID_FREQ_RANK];
+ } else if (i < MAX_GPTC_HS_FREQ_RANK) {
+ priv->freq[i] = priv->gptc_clkrate;
+ }
+ }
+}
+
+static int sso_gpio_request(struct gpio_chip *chip, unsigned int offset)
+{
+ struct sso_led_priv *priv = gpiochip_get_data(chip);
+
+ if (priv->gpio.alloc_bitmap & BIT(offset))
+ return -EINVAL;
+
+ priv->gpio.alloc_bitmap |= BIT(offset);
+ regmap_write(priv->mmap, DUTY_CYCLE(offset), 0xFF);
+
+ return 0;
+}
+
+static void sso_gpio_free(struct gpio_chip *chip, unsigned int offset)
+{
+ struct sso_led_priv *priv = gpiochip_get_data(chip);
+
+ priv->gpio.alloc_bitmap &= ~BIT(offset);
+ regmap_write(priv->mmap, DUTY_CYCLE(offset), 0x0);
+}
+
+static int sso_gpio_get_dir(struct gpio_chip *chip, unsigned int offset)
+{
+ return GPIO_LINE_DIRECTION_OUT;
+}
+
+static int
+sso_gpio_dir_out(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct sso_led_priv *priv = gpiochip_get_data(chip);
+ bool bit = !!value;
+
+ regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), bit << offset);
+ if (!priv->gpio.freq)
+ regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
+ SSO_CON0_SWU);
+
+ return 0;
+}
+
+static int sso_gpio_get(struct gpio_chip *chip, unsigned int offset)
+{
+ struct sso_led_priv *priv = gpiochip_get_data(chip);
+ u32 reg_val;
+
+ regmap_read(priv->mmap, SSO_CPU, &reg_val);
+
+ return !!(reg_val & BIT(offset));
+}
+
+static int sso_gpio_set(struct gpio_chip *chip, unsigned int offset, int value)
+{
+ struct sso_led_priv *priv = gpiochip_get_data(chip);
+
+ regmap_update_bits(priv->mmap, SSO_CPU, BIT(offset), value << offset);
+ if (!priv->gpio.freq)
+ regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_SWU,
+ SSO_CON0_SWU);
+
+ return 0;
+}
+
+static int sso_gpio_gc_init(struct device *dev, struct sso_led_priv *priv)
+{
+ struct gpio_chip *gc = &priv->gpio.chip;
+
+ gc->request = sso_gpio_request;
+ gc->free = sso_gpio_free;
+ gc->get_direction = sso_gpio_get_dir;
+ gc->direction_output = sso_gpio_dir_out;
+ gc->get = sso_gpio_get;
+ gc->set = sso_gpio_set;
+
+ gc->label = "lgm-sso";
+ gc->base = -1;
+ /* To exclude pins from control, use "gpio-reserved-ranges" */
+ gc->ngpio = priv->gpio.pins;
+ gc->parent = dev;
+ gc->owner = THIS_MODULE;
+
+ return devm_gpiochip_add_data(dev, gc, priv);
+}
+
+static int sso_gpio_get_freq_idx(int freq)
+{
+ int idx;
+
+ for (idx = 0; idx < ARRAY_SIZE(freq_tbl); idx++) {
+ if (freq <= freq_tbl[idx])
+ return idx;
+ }
+
+ return -1;
+}
+
+static void sso_register_shift_clk(struct sso_led_priv *priv)
+{
+ int idx, size = ARRAY_SIZE(shift_clk_freq_tbl);
+ u32 val = 0;
+
+ for (idx = 0; idx < size; idx++) {
+ if (shift_clk_freq_tbl[idx] <= priv->gpio.shift_clk_freq) {
+ val = idx;
+ break;
+ }
+ }
+
+ if (idx == size)
+ dev_warn(priv->dev, "%s: Invalid freq %d\n",
+ __func__, priv->gpio.shift_clk_freq);
+
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FCDSC,
+ FIELD_PREP(SSO_CON1_FCDSC, val));
+}
+
+static int sso_gpio_freq_set(struct sso_led_priv *priv)
+{
+ int freq_idx;
+ u32 val;
+
+ freq_idx = sso_gpio_get_freq_idx(priv->gpio.freq);
+ if (freq_idx == -1)
+ freq_idx = ARRAY_SIZE(freq_tbl) - 1;
+
+ val = freq_idx % FPID_FREQ_RANK_MAX;
+
+ if (!priv->gpio.freq) {
+ regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R, 0);
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
+ FIELD_PREP(SSO_CON1_US, US_SW));
+ } else if (freq_idx < FPID_FREQ_RANK_MAX) {
+ regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R,
+ SSO_CON0_BLINK_R);
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
+ FIELD_PREP(SSO_CON1_US, US_FPID));
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_FPID,
+ FIELD_PREP(SSO_CON1_FPID, val));
+ } else {
+ regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_BLINK_R,
+ SSO_CON0_BLINK_R);
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_US,
+ FIELD_PREP(SSO_CON1_US, US_GPTC));
+ regmap_update_bits(priv->mmap, SSO_CON1, SSO_CON1_GPTD,
+ FIELD_PREP(SSO_CON1_GPTD, val));
+ }
+
+ return 0;
+}
+
+static int sso_gpio_hw_init(struct sso_led_priv *priv)
+{
+ u32 activate;
+ int i, err;
+
+ /* Clear all duty cycles */
+ for (i = 0; i < priv->gpio.pins; i++) {
+ err = regmap_write(priv->mmap, DUTY_CYCLE(i), 0);
+ if (err)
+ return err;
+ }
+
+ /* 4 groups for total 32 pins */
+ for (i = 1; i <= MAX_GROUP_NUM; i++) {
+ activate = !!(i * PINS_PER_GROUP <= priv->gpio.pins ||
+ priv->gpio.pins > (i - 1) * PINS_PER_GROUP);
+ err = regmap_update_bits(priv->mmap, SSO_CON1, BIT(i - 1),
+ activate << (i - 1));
+ if (err)
+ return err;
+ }
+
+ /* NO HW directly controlled pin by default */
+ err = regmap_write(priv->mmap, SSO_CON3, 0);
+ if (err)
+ return err;
+
+ /* NO BLINK for all pins */
+ err = regmap_write(priv->mmap, SSO_CON2, 0);
+ if (err)
+ return err;
+
+ /* OUTPUT 0 by default */
+ err = regmap_write(priv->mmap, SSO_CPU, 0);
+ if (err)
+ return err;
+
+ /* update edge */
+ err = regmap_update_bits(priv->mmap, SSO_CON0, SSO_CON0_RZFL,
+ FIELD_PREP(SSO_CON0_RZFL, priv->gpio.edge));
+ if (err)
+ return err;
+
+ /* Set GPIO update rate */
+ sso_gpio_freq_set(priv);
+
+ /* Register shift clock */
+ sso_register_shift_clk(priv);
+
+ return 0;
+}
+
+static void sso_led_shutdown(struct sso_led *led)
+{
+ struct sso_led_priv *priv = led->priv;
+
+ /* unregister led */
+ devm_led_classdev_unregister(priv->dev, &led->cdev);
+
+ /* clear HW control bit */
+ if (led->desc.hw_trig)
+ regmap_update_bits(priv->mmap, SSO_CON3, BIT(led->desc.pin), 0);
+
+ led->priv = NULL;
+}
+
+static int
+__sso_led_dt_parse(struct sso_led_priv *priv, struct fwnode_handle *fw_ssoled)
+{
+ struct fwnode_handle *fwnode_child;
+ struct device *dev = priv->dev;
+ struct sso_led_desc *desc;
+ struct sso_led *led;
+ const char *tmp;
+ u32 prop;
+ int ret;
+
+ fwnode_for_each_child_node(fw_ssoled, fwnode_child) {
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ ret = -ENOMEM;
+ goto __dt_err;
+ }
+
+ INIT_LIST_HEAD(&led->list);
+ led->priv = priv;
+ desc = &led->desc;
+
+ led->gpiod = devm_fwnode_gpiod_get(dev, fwnode_child, NULL,
+ GPIOD_ASIS, NULL);
+ if (IS_ERR(led->gpiod)) {
+ ret = dev_err_probe(dev, PTR_ERR(led->gpiod), "led: get gpio fail!\n");
+ goto __dt_err;
+ }
+
+ fwnode_property_read_string(fwnode_child,
+ "linux,default-trigger",
+ &desc->default_trigger);
+
+ if (fwnode_property_present(fwnode_child,
+ "retain-state-suspended"))
+ desc->retain_state_suspended = 1;
+
+ if (fwnode_property_present(fwnode_child,
+ "retain-state-shutdown"))
+ desc->retain_state_shutdown = 1;
+
+ if (fwnode_property_present(fwnode_child, "panic-indicator"))
+ desc->panic_indicator = 1;
+
+ ret = fwnode_property_read_u32(fwnode_child, "reg", &prop);
+ if (ret)
+ goto __dt_err;
+ if (prop >= SSO_LED_MAX_NUM) {
+ dev_err(dev, "invalid LED pin:%u\n", prop);
+ ret = -EINVAL;
+ goto __dt_err;
+ }
+ desc->pin = prop;
+
+ if (fwnode_property_present(fwnode_child, "intel,sso-hw-blink"))
+ desc->hw_blink = 1;
+
+ desc->hw_trig = fwnode_property_read_bool(fwnode_child,
+ "intel,sso-hw-trigger");
+ if (desc->hw_trig) {
+ desc->default_trigger = NULL;
+ desc->retain_state_shutdown = 0;
+ desc->retain_state_suspended = 0;
+ desc->panic_indicator = 0;
+ desc->hw_blink = 0;
+ }
+
+ if (fwnode_property_read_u32(fwnode_child,
+ "intel,sso-blink-rate-hz", &prop)) {
+ /* default first freq rate */
+ desc->freq_idx = 0;
+ desc->blink_rate = priv->freq[desc->freq_idx];
+ } else {
+ desc->freq_idx = sso_get_blink_rate_idx(priv, prop);
+ if (desc->freq_idx == -1)
+ desc->freq_idx = MAX_FREQ_RANK - 1;
+
+ desc->blink_rate = priv->freq[desc->freq_idx];
+ }
+
+ if (!fwnode_property_read_string(fwnode_child, "default-state", &tmp)) {
+ if (!strcmp(tmp, "on"))
+ desc->brightness = LED_FULL;
+ }
+
+ ret = sso_create_led(priv, led, fwnode_child);
+ if (ret)
+ goto __dt_err;
+ }
+
+ return 0;
+
+__dt_err:
+ fwnode_handle_put(fwnode_child);
+ /* unregister leds */
+ list_for_each_entry(led, &priv->led_list, list)
+ sso_led_shutdown(led);
+
+ return ret;
+}
+
+static int sso_led_dt_parse(struct sso_led_priv *priv)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(priv->dev);
+ struct fwnode_handle *fw_ssoled;
+ struct device *dev = priv->dev;
+ int count;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count)
+ return 0;
+
+ fw_ssoled = fwnode_get_named_child_node(fwnode, "ssoled");
+ if (fw_ssoled) {
+ ret = __sso_led_dt_parse(priv, fw_ssoled);
+ fwnode_handle_put(fw_ssoled);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sso_probe_gpios(struct sso_led_priv *priv)
+{
+ struct device *dev = priv->dev;
+ int ret;
+
+ if (device_property_read_u32(dev, "ngpios", &priv->gpio.pins))
+ priv->gpio.pins = MAX_PIN_NUM_PER_BANK;
+
+ if (priv->gpio.pins > MAX_PIN_NUM_PER_BANK)
+ return -EINVAL;
+
+ if (device_property_read_u32(dev, "intel,sso-update-rate-hz",
+ &priv->gpio.freq))
+ priv->gpio.freq = 0;
+
+ priv->gpio.edge = DATA_CLK_EDGE;
+ priv->gpio.shift_clk_freq = -1;
+
+ ret = sso_gpio_hw_init(priv);
+ if (ret)
+ return ret;
+
+ return sso_gpio_gc_init(dev, priv);
+}
+
+static void sso_clock_disable_unprepare(void *data)
+{
+ struct sso_led_priv *priv = data;
+
+ clk_bulk_disable_unprepare(ARRAY_SIZE(priv->clocks), priv->clocks);
+}
+
+static int intel_sso_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct sso_led_priv *priv;
+ int ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->pdev = pdev;
+ priv->dev = dev;
+
+ /* gate clock */
+ priv->clocks[0].id = "sso";
+
+ /* fpid clock */
+ priv->clocks[1].id = "fpid";
+
+ ret = devm_clk_bulk_get(dev, ARRAY_SIZE(priv->clocks), priv->clocks);
+ if (ret) {
+ dev_err(dev, "Getting clocks failed!\n");
+ return ret;
+ }
+
+ ret = clk_bulk_prepare_enable(ARRAY_SIZE(priv->clocks), priv->clocks);
+ if (ret) {
+ dev_err(dev, "Failed to prepare and enable clocks!\n");
+ return ret;
+ }
+
+ ret = devm_add_action_or_reset(dev, sso_clock_disable_unprepare, priv);
+ if (ret)
+ return ret;
+
+ priv->fpid_clkrate = clk_get_rate(priv->clocks[1].clk);
+
+ priv->mmap = syscon_node_to_regmap(dev->of_node);
+
+ priv->mmap = syscon_node_to_regmap(dev->of_node);
+ if (IS_ERR(priv->mmap)) {
+ dev_err(dev, "Failed to map iomem!\n");
+ return PTR_ERR(priv->mmap);
+ }
+
+ ret = sso_probe_gpios(priv);
+ if (ret) {
+ regmap_exit(priv->mmap);
+ return ret;
+ }
+
+ INIT_LIST_HEAD(&priv->led_list);
+
+ platform_set_drvdata(pdev, priv);
+ sso_init_freq(priv);
+
+ priv->gptc_clkrate = DEF_GPTC_CLK_RATE;
+
+ ret = sso_led_dt_parse(priv);
+ if (ret) {
+ regmap_exit(priv->mmap);
+ return ret;
+ }
+ dev_info(priv->dev, "sso LED init success!\n");
+
+ return 0;
+}
+
+static void intel_sso_led_remove(struct platform_device *pdev)
+{
+ struct sso_led_priv *priv;
+ struct sso_led *led, *n;
+
+ priv = platform_get_drvdata(pdev);
+
+ list_for_each_entry_safe(led, n, &priv->led_list, list) {
+ list_del(&led->list);
+ sso_led_shutdown(led);
+ }
+
+ regmap_exit(priv->mmap);
+}
+
+static const struct of_device_id of_sso_led_match[] = {
+ { .compatible = "intel,lgm-ssoled" },
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, of_sso_led_match);
+
+static struct platform_driver intel_sso_led_driver = {
+ .probe = intel_sso_led_probe,
+ .remove = intel_sso_led_remove,
+ .driver = {
+ .name = "lgm-ssoled",
+ .of_match_table = of_sso_led_match,
+ },
+};
+
+module_platform_driver(intel_sso_led_driver);
+
+MODULE_DESCRIPTION("Intel SSO LED/GPIO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/dell-led.c b/drivers/leds/dell-led.c
deleted file mode 100644
index e5c57389efd6..000000000000
--- a/drivers/leds/dell-led.c
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * dell_led.c - Dell LED Driver
- *
- * Copyright (C) 2010 Dell Inc.
- * Louis Davis <louis_davis@dell.com>
- * Jim Dailey <jim_dailey@dell.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as
- * published by the Free Software Foundation.
- *
- */
-
-#include <linux/acpi.h>
-#include <linux/leds.h>
-#include <linux/slab.h>
-#include <linux/module.h>
-
-MODULE_AUTHOR("Louis Davis/Jim Dailey");
-MODULE_DESCRIPTION("Dell LED Control Driver");
-MODULE_LICENSE("GPL");
-
-#define DELL_LED_BIOS_GUID "F6E4FE6E-909D-47cb-8BAB-C9F6F2F8D396"
-MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
-
-/* Error Result Codes: */
-#define INVALID_DEVICE_ID 250
-#define INVALID_PARAMETER 251
-#define INVALID_BUFFER 252
-#define INTERFACE_ERROR 253
-#define UNSUPPORTED_COMMAND 254
-#define UNSPECIFIED_ERROR 255
-
-/* Device ID */
-#define DEVICE_ID_PANEL_BACK 1
-
-/* LED Commands */
-#define CMD_LED_ON 16
-#define CMD_LED_OFF 17
-#define CMD_LED_BLINK 18
-
-struct bios_args {
- unsigned char length;
- unsigned char result_code;
- unsigned char device_id;
- unsigned char command;
- unsigned char on_time;
- unsigned char off_time;
-};
-
-static int dell_led_perform_fn(u8 length,
- u8 result_code,
- u8 device_id,
- u8 command,
- u8 on_time,
- u8 off_time)
-{
- struct bios_args *bios_return;
- u8 return_code;
- union acpi_object *obj;
- struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
- struct acpi_buffer input;
- acpi_status status;
-
- struct bios_args args;
- args.length = length;
- args.result_code = result_code;
- args.device_id = device_id;
- args.command = command;
- args.on_time = on_time;
- args.off_time = off_time;
-
- input.length = sizeof(struct bios_args);
- input.pointer = &args;
-
- status = wmi_evaluate_method(DELL_LED_BIOS_GUID,
- 1,
- 1,
- &input,
- &output);
-
- if (ACPI_FAILURE(status))
- return status;
-
- obj = output.pointer;
-
- if (!obj)
- return -EINVAL;
- else if (obj->type != ACPI_TYPE_BUFFER) {
- kfree(obj);
- return -EINVAL;
- }
-
- bios_return = ((struct bios_args *)obj->buffer.pointer);
- return_code = bios_return->result_code;
-
- kfree(obj);
-
- return return_code;
-}
-
-static int led_on(void)
-{
- return dell_led_perform_fn(3, /* Length of command */
- INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
- DEVICE_ID_PANEL_BACK, /* Device ID */
- CMD_LED_ON, /* Command */
- 0, /* not used */
- 0); /* not used */
-}
-
-static int led_off(void)
-{
- return dell_led_perform_fn(3, /* Length of command */
- INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
- DEVICE_ID_PANEL_BACK, /* Device ID */
- CMD_LED_OFF, /* Command */
- 0, /* not used */
- 0); /* not used */
-}
-
-static int led_blink(unsigned char on_eighths,
- unsigned char off_eighths)
-{
- return dell_led_perform_fn(5, /* Length of command */
- INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
- DEVICE_ID_PANEL_BACK, /* Device ID */
- CMD_LED_BLINK, /* Command */
- on_eighths, /* blink on in eigths of a second */
- off_eighths); /* blink off in eights of a second */
-}
-
-static void dell_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value == LED_OFF)
- led_off();
- else
- led_on();
-}
-
-static int dell_led_blink(struct led_classdev *led_cdev,
- unsigned long *delay_on,
- unsigned long *delay_off)
-{
- unsigned long on_eighths;
- unsigned long off_eighths;
-
- /* The Dell LED delay is based on 125ms intervals.
- Need to round up to next interval. */
-
- on_eighths = (*delay_on + 124) / 125;
- if (0 == on_eighths)
- on_eighths = 1;
- if (on_eighths > 255)
- on_eighths = 255;
- *delay_on = on_eighths * 125;
-
- off_eighths = (*delay_off + 124) / 125;
- if (0 == off_eighths)
- off_eighths = 1;
- if (off_eighths > 255)
- off_eighths = 255;
- *delay_off = off_eighths * 125;
-
- led_blink(on_eighths, off_eighths);
-
- return 0;
-}
-
-static struct led_classdev dell_led = {
- .name = "dell::lid",
- .brightness = LED_OFF,
- .max_brightness = 1,
- .brightness_set = dell_led_set,
- .blink_set = dell_led_blink,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static int __init dell_led_init(void)
-{
- int error = 0;
-
- if (!wmi_has_guid(DELL_LED_BIOS_GUID))
- return -ENODEV;
-
- error = led_off();
- if (error != 0)
- return -ENODEV;
-
- return led_classdev_register(NULL, &dell_led);
-}
-
-static void __exit dell_led_exit(void)
-{
- led_classdev_unregister(&dell_led);
-
- led_off();
-}
-
-module_init(dell_led_init);
-module_exit(dell_led_exit);
diff --git a/drivers/leds/flash/Kconfig b/drivers/leds/flash/Kconfig
new file mode 100644
index 000000000000..5e08102a6784
--- /dev/null
+++ b/drivers/leds/flash/Kconfig
@@ -0,0 +1,147 @@
+# SPDX-License-Identifier: GPL-2.0
+
+if LEDS_CLASS_FLASH
+
+config LEDS_AAT1290
+ tristate "LED support for the AAT1290"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on GPIOLIB || COMPILE_TEST
+ depends on OF
+ depends on PINCTRL
+ help
+ This option enables support for the LEDs on the AAT1290.
+
+config LEDS_AS3645A
+ tristate "AS3645A and LM3555 LED flash controllers support"
+ depends on I2C
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ Enable LED flash class support for AS3645A LED flash
+ controller. V4L2 flash API is provided as well if
+ CONFIG_V4L2_FLASH_API is enabled.
+
+config LEDS_KTD2692
+ tristate "LED support for Kinetic KTD2692 flash LED controller"
+ depends on OF
+ depends on GPIOLIB
+ select LEDS_EXPRESSWIRE
+ help
+ This option enables support for Kinetic KTD2692 LED flash connected
+ through ExpressWire interface.
+
+ Say Y to enable this driver.
+
+config LEDS_LM3601X
+ tristate "LED support for LM3601x Chips"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for the TI LM3601x family
+ of flash, torch and indicator classes.
+
+config LEDS_MAX77693
+ tristate "LED support for MAX77693 Flash"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MAX77693
+ depends on OF
+ help
+ This option enables support for the flash part of the MAX77693
+ multifunction device. It has build in control for two leds in flash
+ and torch mode.
+
+config LEDS_MT6360
+ tristate "LED Support for Mediatek MT6360 PMIC"
+ depends on LEDS_CLASS && OF
+ depends on LEDS_CLASS_FLASH
+ depends on LEDS_CLASS_MULTICOLOR
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MT6360
+ help
+ This option enables support for dual Flash LED drivers found on
+ Mediatek MT6360 PMIC.
+ Independent current sources supply for each flash LED support torch
+ and strobe mode.
+
+config LEDS_MT6370_FLASH
+ tristate "Flash LED Support for MediaTek MT6370 PMIC"
+ depends on LEDS_CLASS
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ depends on MFD_MT6370
+ help
+ Support 2 channels and torch/strobe mode.
+ Say Y here to enable support for
+ MT6370_FLASH_LED device.
+
+ This driver can also be built as a module. If so, the module
+ will be called "leds-mt6370-flash".
+
+config LEDS_QCOM_FLASH
+ tristate "LED support for flash module inside Qualcomm Technologies, Inc. PMIC"
+ depends on MFD_SPMI_PMIC || COMPILE_TEST
+ depends on LEDS_CLASS && OF
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP
+ help
+ This option enables support for the flash module found in Qualcomm
+ Technologies, Inc. PMICs. The flash module can have 3 or 4 flash LED
+ channels and each channel is programmable to support up to 1.5 A full
+ scale current. It also supports connecting two channels' output together
+ to supply one LED component to achieve current up to 2 A. In such case,
+ the total LED current will be split symmetrically on each channel and
+ they will be enabled/disabled at the same time.
+
+ This driver can be built as a module, it will be called "leds-qcom-flash".
+
+config LEDS_RT4505
+ tristate "LED support for RT4505 flashlight controller"
+ depends on I2C && OF
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP_I2C
+ help
+ This option enables support for the RT4505 flash LED controller.
+ RT4505 includes torch and flash functions with programmable current.
+ And it's commonly used to compensate the illuminance for the camera
+ inside the mobile product like as phones or tablets.
+
+config LEDS_RT8515
+ tristate "LED support for Richtek RT8515 flash/torch LED"
+ depends on GPIOLIB
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ This option enables support for the Richtek RT8515 flash
+ and torch LEDs found on some mobile phones.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-rt8515.
+
+config LEDS_SGM3140
+ tristate "LED support for the SGM3140"
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ help
+ This option enables support for the SGM3140 500mA Buck/Boost Charge
+ Pump LED Driver.
+
+config LEDS_SY7802
+ tristate "LED support for the Silergy SY7802"
+ depends on I2C && OF
+ depends on GPIOLIB
+ select REGMAP_I2C
+ help
+ This option enables support for the SY7802 flash LED controller.
+ SY7802 includes torch and flash functions with programmable current.
+
+ This driver can be built as a module, it will be called "leds-sy7802".
+
+config LEDS_TPS6131X
+ tristate "LED support for TI TPS6131x flash LED driver"
+ depends on I2C && OF
+ depends on GPIOLIB
+ depends on V4L2_FLASH_LED_CLASS || !V4L2_FLASH_LED_CLASS
+ select REGMAP_I2C
+ help
+ This option enables support for Texas Instruments TPS61310/TPS61311
+ flash LED driver.
+
+ This driver can be built as a module, it will be called "leds-tps6131x".
+
+endif # LEDS_CLASS_FLASH
diff --git a/drivers/leds/flash/Makefile b/drivers/leds/flash/Makefile
new file mode 100644
index 000000000000..712fb737a428
--- /dev/null
+++ b/drivers/leds/flash/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_LEDS_MT6360) += leds-mt6360.o
+obj-$(CONFIG_LEDS_MT6370_FLASH) += leds-mt6370-flash.o
+obj-$(CONFIG_LEDS_AAT1290) += leds-aat1290.o
+obj-$(CONFIG_LEDS_AS3645A) += leds-as3645a.o
+obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
+obj-$(CONFIG_LEDS_LM3601X) += leds-lm3601x.o
+obj-$(CONFIG_LEDS_MAX77693) += leds-max77693.o
+obj-$(CONFIG_LEDS_QCOM_FLASH) += leds-qcom-flash.o
+obj-$(CONFIG_LEDS_RT4505) += leds-rt4505.o
+obj-$(CONFIG_LEDS_RT8515) += leds-rt8515.o
+obj-$(CONFIG_LEDS_SGM3140) += leds-sgm3140.o
+obj-$(CONFIG_LEDS_SY7802) += leds-sy7802.o
+obj-$(CONFIG_LEDS_TPS6131X) += leds-tps6131x.o
diff --git a/drivers/leds/flash/leds-aat1290.c b/drivers/leds/flash/leds-aat1290.c
new file mode 100644
index 000000000000..49251cfd3350
--- /dev/null
+++ b/drivers/leds/flash/leds-aat1290.c
@@ -0,0 +1,550 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Flash class driver for the AAT1290
+ * 1.5A Step-Up Current Regulator for Flash LEDs
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ * 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>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define AAT1290_MOVIE_MODE_CURRENT_ADDR 17
+#define AAT1290_MAX_MM_CURR_PERCENT_0 16
+#define AAT1290_MAX_MM_CURR_PERCENT_100 1
+
+#define AAT1290_FLASH_SAFETY_TIMER_ADDR 18
+
+#define AAT1290_MOVIE_MODE_CONFIG_ADDR 19
+#define AAT1290_MOVIE_MODE_OFF 1
+#define AAT1290_MOVIE_MODE_ON 3
+
+#define AAT1290_MM_CURRENT_RATIO_ADDR 20
+#define AAT1290_MM_TO_FL_1_92 1
+
+#define AAT1290_MM_TO_FL_RATIO 1000 / 1920
+#define AAT1290_MAX_MM_CURRENT(fl_max) (fl_max * AAT1290_MM_TO_FL_RATIO)
+
+#define AAT1290_LATCH_TIME_MIN_US 500
+#define AAT1290_LATCH_TIME_MAX_US 1000
+#define AAT1290_EN_SET_TICK_TIME_US 1
+#define AAT1290_FLEN_OFF_DELAY_TIME_US 10
+#define AAT1290_FLASH_TM_NUM_LEVELS 16
+#define AAT1290_MM_CURRENT_SCALE_SIZE 15
+
+#define AAT1290_NAME "aat1290"
+
+
+struct aat1290_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 max_mm_current;
+ /* maximum LED current in flash mode */
+ u32 max_flash_current;
+ /* maximum flash timeout */
+ u32 max_flash_tm;
+ /* external strobe capability */
+ bool has_external_strobe;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+struct aat1290_led {
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* FLEN pin */
+ struct gpio_desc *gpio_fl_en;
+ /* EN|SET pin */
+ struct gpio_desc *gpio_en_set;
+ /* movie mode current scale */
+ int *mm_current_scale;
+ /* device mode */
+ bool movie_mode;
+};
+
+static struct aat1290_led *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct aat1290_led, fled_cdev);
+}
+
+static struct led_classdev_flash *led_cdev_to_fled_cdev(
+ struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct led_classdev_flash, led_cdev);
+}
+
+static void aat1290_as2cwire_write(struct aat1290_led *led, int addr, int value)
+{
+ int i;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ udelay(AAT1290_FLEN_OFF_DELAY_TIME_US);
+
+ /* write address */
+ for (i = 0; i < addr; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+
+ /* write data */
+ for (i = 0; i < value; ++i) {
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ udelay(AAT1290_EN_SET_TICK_TIME_US);
+ gpiod_direction_output(led->gpio_en_set, 1);
+ }
+
+ usleep_range(AAT1290_LATCH_TIME_MIN_US, AAT1290_LATCH_TIME_MAX_US);
+}
+
+static void aat1290_set_flash_safety_timer(struct aat1290_led *led,
+ unsigned int micro_sec)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *flash_tm = &fled_cdev->timeout;
+ int flash_tm_reg = AAT1290_FLASH_TM_NUM_LEVELS -
+ (micro_sec / flash_tm->step) + 1;
+
+ aat1290_as2cwire_write(led, AAT1290_FLASH_SAFETY_TIMER_ADDR,
+ flash_tm_reg);
+}
+
+/* LED subsystem callbacks */
+
+static int aat1290_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = led_cdev_to_fled_cdev(led_cdev);
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ if (brightness == 0) {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ led->movie_mode = false;
+ } else {
+ if (!led->movie_mode) {
+ aat1290_as2cwire_write(led,
+ AAT1290_MM_CURRENT_RATIO_ADDR,
+ AAT1290_MM_TO_FL_1_92);
+ led->movie_mode = true;
+ }
+
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CURRENT_ADDR,
+ AAT1290_MAX_MM_CURR_PERCENT_0 - brightness);
+ aat1290_as2cwire_write(led, AAT1290_MOVIE_MODE_CONFIG_ADDR,
+ AAT1290_MOVIE_MODE_ON);
+ }
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+
+{
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ aat1290_set_flash_safety_timer(led, timeout->val);
+ gpiod_direction_output(led->gpio_fl_en, 1);
+ } else {
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+ }
+
+ /*
+ * To reenter movie mode after a flash event the part must be cycled
+ * off and back on to reset the movie mode and reprogrammed via the
+ * AS2Cwire. Therefore the brightness and movie_mode properties needs
+ * to be updated here to reflect the actual state.
+ */
+ led_cdev->brightness = 0;
+ led->movie_mode = false;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int aat1290_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ /*
+ * Don't do anything - flash timeout is cached in the led-class-flash
+ * core and will be applied in the strobe_set op, as writing the
+ * safety timer register spuriously turns the torch mode on.
+ */
+
+ return 0;
+}
+
+static int aat1290_led_parse_dt(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ struct device *dev = &led->pdev->dev;
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ struct pinctrl *pinctrl;
+#endif
+ int ret = 0;
+
+ led->gpio_fl_en = devm_gpiod_get(dev, "flen", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_fl_en)) {
+ ret = PTR_ERR(led->gpio_fl_en);
+ dev_err(dev, "Unable to claim gpio \"flen\".\n");
+ return ret;
+ }
+
+ led->gpio_en_set = devm_gpiod_get(dev, "enset", GPIOD_ASIS);
+ if (IS_ERR(led->gpio_en_set)) {
+ ret = PTR_ERR(led->gpio_en_set);
+ dev_err(dev, "Unable to claim gpio \"enset\".\n");
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+ pinctrl = devm_pinctrl_get_select_default(&led->pdev->dev);
+ if (IS_ERR(pinctrl)) {
+ cfg->has_external_strobe = false;
+ dev_info(dev,
+ "No support for external strobe detected.\n");
+ } else {
+ cfg->has_external_strobe = true;
+ }
+#endif
+
+ 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;
+ }
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->max_mm_current);
+ /*
+ * led-max-microamp will default to 1/20 of flash-max-microamp
+ * in case it is missing.
+ */
+ if (ret < 0)
+ dev_warn(dev,
+ "led-max-microamp DT property missing\n");
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->max_flash_current);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-microamp DT property missing\n");
+ return ret;
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->max_flash_tm);
+ if (ret < 0) {
+ dev_err(dev,
+ "flash-max-timeout-us DT property missing\n");
+ return ret;
+ }
+
+ *sub_node = child_node;
+
+ return 0;
+}
+
+static void aat1290_led_validate_mm_current(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ int i, b = 0, e = AAT1290_MM_CURRENT_SCALE_SIZE;
+
+ while (e - b > 1) {
+ i = b + (e - b) / 2;
+ if (cfg->max_mm_current < led->mm_current_scale[i])
+ e = i;
+ else
+ b = i;
+ }
+
+ cfg->max_mm_current = led->mm_current_scale[b];
+ cfg->max_brightness = b + 1;
+}
+
+static int init_mm_current_scale(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ static const int max_mm_current_percent[] = {
+ 20, 22, 25, 28, 32, 36, 40, 45, 50, 56,
+ 63, 71, 79, 89, 100
+ };
+ int i, max_mm_current =
+ AAT1290_MAX_MM_CURRENT(cfg->max_flash_current);
+
+ led->mm_current_scale = devm_kzalloc(&led->pdev->dev,
+ sizeof(max_mm_current_percent),
+ GFP_KERNEL);
+ if (!led->mm_current_scale)
+ return -ENOMEM;
+
+ for (i = 0; i < AAT1290_MM_CURRENT_SCALE_SIZE; ++i)
+ led->mm_current_scale[i] = max_mm_current *
+ max_mm_current_percent[i] / 100;
+
+ return 0;
+}
+
+static int aat1290_led_get_configuration(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg,
+ struct device_node **sub_node)
+{
+ int ret;
+
+ ret = aat1290_led_parse_dt(led, cfg, sub_node);
+ if (ret < 0)
+ return ret;
+ /*
+ * Init non-linear movie mode current scale basing
+ * on the max flash current from led configuration.
+ */
+ ret = init_mm_current_scale(led, cfg);
+ if (ret < 0)
+ return ret;
+
+ aat1290_led_validate_mm_current(led, cfg);
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+#else
+ devm_kfree(&led->pdev->dev, led->mm_current_scale);
+#endif
+
+ return 0;
+}
+
+static void aat1290_init_flash_timeout(struct aat1290_led *led,
+ struct aat1290_led_config_data *cfg)
+{
+ struct led_classdev_flash *fled_cdev = &led->fled_cdev;
+ struct led_flash_setting *setting;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = cfg->max_flash_tm / AAT1290_FLASH_TM_NUM_LEVELS;
+ setting->max = cfg->max_flash_tm;
+ setting->step = setting->min;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static enum led_brightness aat1290_intensity_to_brightness(
+ struct v4l2_flash *v4l2_flash,
+ s32 intensity)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+ int i;
+
+ for (i = AAT1290_MM_CURRENT_SCALE_SIZE - 1; i >= 0; --i)
+ if (intensity >= led->mm_current_scale[i])
+ return i + 1;
+
+ return 1;
+}
+
+static s32 aat1290_brightness_to_intensity(struct v4l2_flash *v4l2_flash,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct aat1290_led *led = fled_cdev_to_led(fled_cdev);
+
+ return led->mm_current_scale[brightness - 1];
+}
+
+static int aat1290_led_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct aat1290_led *led = fled_cdev_to_led(v4l2_flash->fled_cdev);
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct pinctrl *pinctrl;
+
+ gpiod_direction_output(led->gpio_fl_en, 0);
+ gpiod_direction_output(led->gpio_en_set, 0);
+
+ led->movie_mode = false;
+ led_cdev->brightness = 0;
+
+ pinctrl = devm_pinctrl_get_select(&led->pdev->dev,
+ enable ? "isp" : "host");
+ if (IS_ERR(pinctrl)) {
+ dev_warn(&led->pdev->dev, "Unable to switch strobe source.\n");
+ return PTR_ERR(pinctrl);
+ }
+
+ return 0;
+}
+
+static void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led_cdev = &led->fled_cdev.led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ s = &v4l2_sd_cfg->intensity;
+ s->min = led->mm_current_scale[0];
+ s->max = led_cfg->max_mm_current;
+ s->step = 1;
+ s->val = s->max;
+
+ v4l2_sd_cfg->has_external_strobe = led_cfg->has_external_strobe;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = aat1290_led_external_strobe_set,
+ .intensity_to_led_brightness = aat1290_intensity_to_brightness,
+ .led_brightness_to_intensity = aat1290_brightness_to_intensity,
+};
+#else
+static inline void aat1290_init_v4l2_flash_config(struct aat1290_led *led,
+ struct aat1290_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = aat1290_led_flash_strobe_set,
+ .timeout_set = aat1290_led_flash_timeout_set,
+};
+
+static int aat1290_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *sub_node = NULL;
+ struct aat1290_led *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct led_init_data init_data = {};
+ struct aat1290_led_config_data led_cfg = {};
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ platform_set_drvdata(pdev, led);
+
+ fled_cdev = &led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ ret = aat1290_led_get_configuration(led, &led_cfg, &sub_node);
+ if (ret < 0)
+ return ret;
+
+ mutex_init(&led->lock);
+
+ /* Initialize LED Flash class device */
+ led_cdev->brightness_set_blocking = aat1290_led_brightness_set;
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ aat1290_init_flash_timeout(led, &led_cfg);
+
+ init_data.fwnode = of_fwnode_handle(sub_node);
+ init_data.devicename = AAT1290_NAME;
+
+ /* Register LED Flash class device */
+ ret = led_classdev_flash_register_ext(&pdev->dev, fled_cdev,
+ &init_data);
+ if (ret < 0)
+ goto err_flash_register;
+
+ aat1290_init_v4l2_flash_config(led, &led_cfg, &v4l2_sd_cfg);
+
+ /* Create V4L2 Flash subdev. */
+ led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node),
+ fled_cdev, &v4l2_flash_ops,
+ &v4l2_sd_cfg);
+ if (IS_ERR(led->v4l2_flash)) {
+ ret = PTR_ERR(led->v4l2_flash);
+ goto error_v4l2_flash_init;
+ }
+
+ return 0;
+
+error_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+err_flash_register:
+ mutex_destroy(&led->lock);
+
+ return ret;
+}
+
+static void aat1290_led_remove(struct platform_device *pdev)
+{
+ struct aat1290_led *led = platform_get_drvdata(pdev);
+
+ v4l2_flash_release(led->v4l2_flash);
+ led_classdev_flash_unregister(&led->fled_cdev);
+
+ mutex_destroy(&led->lock);
+}
+
+static const struct of_device_id aat1290_led_dt_match[] = {
+ { .compatible = "skyworks,aat1290" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, aat1290_led_dt_match);
+
+static struct platform_driver aat1290_led_driver = {
+ .probe = aat1290_led_probe,
+ .remove = aat1290_led_remove,
+ .driver = {
+ .name = "aat1290",
+ .of_match_table = aat1290_led_dt_match,
+ },
+};
+
+module_platform_driver(aat1290_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("Skyworks Current Regulator for Flash LEDs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-as3645a.c b/drivers/leds/flash/leds-as3645a.c
new file mode 100644
index 000000000000..2f2d783c62c3
--- /dev/null
+++ b/drivers/leds/flash/leds-as3645a.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * drivers/leds/leds-as3645a.c - AS3645A and LM3555 flash controllers driver
+ *
+ * Copyright (C) 2008-2011 Nokia Corporation
+ * Copyright (c) 2011, 2017 Intel Corporation.
+ *
+ * Based on drivers/media/i2c/as3645a.c.
+ *
+ * Contact: Sakari Ailus <sakari.ailus@iki.fi>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+#define AS_TIMER_US_TO_CODE(t) (((t) / 1000 - 100) / 50)
+#define AS_TIMER_CODE_TO_US(c) ((50 * (c) + 100) * 1000)
+
+/* Register definitions */
+
+/* Read-only Design info register: Reset state: xxxx 0001 */
+#define AS_DESIGN_INFO_REG 0x00
+#define AS_DESIGN_INFO_FACTORY(x) (((x) >> 4))
+#define AS_DESIGN_INFO_MODEL(x) ((x) & 0x0f)
+
+/* Read-only Version control register: Reset state: 0000 0000
+ * for first engineering samples
+ */
+#define AS_VERSION_CONTROL_REG 0x01
+#define AS_VERSION_CONTROL_RFU(x) (((x) >> 4))
+#define AS_VERSION_CONTROL_VERSION(x) ((x) & 0x0f)
+
+/* Read / Write (Indicator and timer register): Reset state: 0000 1111 */
+#define AS_INDICATOR_AND_TIMER_REG 0x02
+#define AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT 0
+#define AS_INDICATOR_AND_TIMER_VREF_SHIFT 4
+#define AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT 6
+
+/* Read / Write (Current set register): Reset state: 0110 1001 */
+#define AS_CURRENT_SET_REG 0x03
+#define AS_CURRENT_ASSIST_LIGHT_SHIFT 0
+#define AS_CURRENT_LED_DET_ON (1 << 3)
+#define AS_CURRENT_FLASH_CURRENT_SHIFT 4
+
+/* Read / Write (Control register): Reset state: 1011 0100 */
+#define AS_CONTROL_REG 0x04
+#define AS_CONTROL_MODE_SETTING_SHIFT 0
+#define AS_CONTROL_STROBE_ON (1 << 2)
+#define AS_CONTROL_OUT_ON (1 << 3)
+#define AS_CONTROL_EXT_TORCH_ON (1 << 4)
+#define AS_CONTROL_STROBE_TYPE_EDGE (0 << 5)
+#define AS_CONTROL_STROBE_TYPE_LEVEL (1 << 5)
+#define AS_CONTROL_COIL_PEAK_SHIFT 6
+
+/* Read only (D3 is read / write) (Fault and info): Reset state: 0000 x000 */
+#define AS_FAULT_INFO_REG 0x05
+#define AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT (1 << 1)
+#define AS_FAULT_INFO_INDICATOR_LED (1 << 2)
+#define AS_FAULT_INFO_LED_AMOUNT (1 << 3)
+#define AS_FAULT_INFO_TIMEOUT (1 << 4)
+#define AS_FAULT_INFO_OVER_TEMPERATURE (1 << 5)
+#define AS_FAULT_INFO_SHORT_CIRCUIT (1 << 6)
+#define AS_FAULT_INFO_OVER_VOLTAGE (1 << 7)
+
+/* Boost register */
+#define AS_BOOST_REG 0x0d
+#define AS_BOOST_CURRENT_DISABLE (0 << 0)
+#define AS_BOOST_CURRENT_ENABLE (1 << 0)
+
+/* Password register is used to unlock boost register writing */
+#define AS_PASSWORD_REG 0x0f
+#define AS_PASSWORD_UNLOCK_VALUE 0x55
+
+#define AS_NAME "as3645a"
+#define AS_I2C_ADDR (0x60 >> 1) /* W:0x60, R:0x61 */
+
+#define AS_FLASH_TIMEOUT_MIN 100000 /* us */
+#define AS_FLASH_TIMEOUT_MAX 850000
+#define AS_FLASH_TIMEOUT_STEP 50000
+
+#define AS_FLASH_INTENSITY_MIN 200000 /* uA */
+#define AS_FLASH_INTENSITY_MAX_1LED 500000
+#define AS_FLASH_INTENSITY_MAX_2LEDS 400000
+#define AS_FLASH_INTENSITY_STEP 20000
+
+#define AS_TORCH_INTENSITY_MIN 20000 /* uA */
+#define AS_TORCH_INTENSITY_MAX 160000
+#define AS_TORCH_INTENSITY_STEP 20000
+
+#define AS_INDICATOR_INTENSITY_MIN 0 /* uA */
+#define AS_INDICATOR_INTENSITY_MAX 10000
+#define AS_INDICATOR_INTENSITY_STEP 2500
+
+#define AS_PEAK_mA_MAX 2000
+#define AS_PEAK_mA_TO_REG(a) \
+ ((min_t(u32, AS_PEAK_mA_MAX, a) - 1250) / 250)
+
+/* LED numbers for Devicetree */
+#define AS_LED_FLASH 0
+#define AS_LED_INDICATOR 1
+
+enum as_mode {
+ AS_MODE_EXT_TORCH = 0 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_INDICATOR = 1 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_ASSIST = 2 << AS_CONTROL_MODE_SETTING_SHIFT,
+ AS_MODE_FLASH = 3 << AS_CONTROL_MODE_SETTING_SHIFT,
+};
+
+struct as3645a_config {
+ u32 flash_timeout_us;
+ u32 flash_max_ua;
+ u32 assist_max_ua;
+ u32 indicator_max_ua;
+ u32 voltage_reference;
+ u32 peak;
+};
+
+struct as3645a {
+ struct i2c_client *client;
+
+ struct mutex mutex;
+
+ struct led_classdev_flash fled;
+ struct led_classdev iled_cdev;
+
+ struct v4l2_flash *vf;
+ struct v4l2_flash *vfind;
+
+ struct fwnode_handle *flash_node;
+ struct fwnode_handle *indicator_node;
+
+ struct as3645a_config cfg;
+
+ enum as_mode mode;
+ unsigned int timeout;
+ unsigned int flash_current;
+ unsigned int assist_current;
+ unsigned int indicator_current;
+ enum v4l2_flash_strobe_source strobe_source;
+};
+
+#define fled_to_as3645a(__fled) container_of(__fled, struct as3645a, fled)
+#define iled_cdev_to_as3645a(__iled_cdev) \
+ container_of(__iled_cdev, struct as3645a, iled_cdev)
+
+/* Return negative errno else zero on success */
+static int as3645a_write(struct as3645a *flash, u8 addr, u8 val)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_write_byte_data(client, addr, val);
+
+ dev_dbg(&client->dev, "Write Addr:%02X Val:%02X %s\n", addr, val,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* Return negative errno else a data byte received from the device. */
+static int as3645a_read(struct as3645a *flash, u8 addr)
+{
+ struct i2c_client *client = flash->client;
+ int rval;
+
+ rval = i2c_smbus_read_byte_data(client, addr);
+
+ dev_dbg(&client->dev, "Read Addr:%02X Val:%02X %s\n", addr, rval,
+ rval < 0 ? "fail" : "ok");
+
+ return rval;
+}
+
+/* -----------------------------------------------------------------------------
+ * Hardware configuration and trigger
+ */
+
+/**
+ * as3645a_set_current - Set flash configuration registers
+ * @flash: The flash
+ *
+ * Configure the hardware with flash, assist and indicator currents, as well as
+ * flash timeout.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int as3645a_set_current(struct as3645a *flash)
+{
+ u8 val;
+
+ val = (flash->flash_current << AS_CURRENT_FLASH_CURRENT_SHIFT)
+ | (flash->assist_current << AS_CURRENT_ASSIST_LIGHT_SHIFT)
+ | AS_CURRENT_LED_DET_ON;
+
+ return as3645a_write(flash, AS_CURRENT_SET_REG, val);
+}
+
+static int as3645a_set_timeout(struct as3645a *flash)
+{
+ u8 val;
+
+ val = flash->timeout << AS_INDICATOR_AND_TIMER_TIMEOUT_SHIFT;
+
+ val |= (flash->cfg.voltage_reference
+ << AS_INDICATOR_AND_TIMER_VREF_SHIFT)
+ | ((flash->indicator_current ? flash->indicator_current - 1 : 0)
+ << AS_INDICATOR_AND_TIMER_INDICATOR_SHIFT);
+
+ return as3645a_write(flash, AS_INDICATOR_AND_TIMER_REG, val);
+}
+
+/**
+ * as3645a_set_control - Set flash control register
+ * @flash: The flash
+ * @mode: Desired output mode
+ * @on: Desired output state
+ *
+ * Configure the hardware with output mode and state.
+ *
+ * Return 0 on success, or a negative error code if an I2C communication error
+ * occurred.
+ */
+static int
+as3645a_set_control(struct as3645a *flash, enum as_mode mode, bool on)
+{
+ u8 reg;
+
+ /* Configure output parameters and operation mode. */
+ reg = (flash->cfg.peak << AS_CONTROL_COIL_PEAK_SHIFT)
+ | (on ? AS_CONTROL_OUT_ON : 0)
+ | mode;
+
+ if (mode == AS_MODE_FLASH &&
+ flash->strobe_source == V4L2_FLASH_STROBE_SOURCE_EXTERNAL)
+ reg |= AS_CONTROL_STROBE_TYPE_LEVEL
+ | AS_CONTROL_STROBE_ON;
+
+ return as3645a_write(flash, AS_CONTROL_REG, reg);
+}
+
+static int as3645a_get_fault(struct led_classdev_flash *fled, u32 *fault)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+ int rval;
+
+ /* NOTE: reading register clears fault status */
+ rval = as3645a_read(flash, AS_FAULT_INFO_REG);
+ if (rval < 0)
+ return rval;
+
+ if (rval & AS_FAULT_INFO_INDUCTOR_PEAK_LIMIT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+
+ if (rval & AS_FAULT_INFO_INDICATOR_LED)
+ *fault |= LED_FAULT_INDICATOR;
+
+ dev_dbg(&flash->client->dev, "%u connected LEDs\n",
+ rval & AS_FAULT_INFO_LED_AMOUNT ? 2 : 1);
+
+ if (rval & AS_FAULT_INFO_TIMEOUT)
+ *fault |= LED_FAULT_TIMEOUT;
+
+ if (rval & AS_FAULT_INFO_OVER_TEMPERATURE)
+ *fault |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (rval & AS_FAULT_INFO_SHORT_CIRCUIT)
+ *fault |= LED_FAULT_OVER_CURRENT;
+
+ if (rval & AS_FAULT_INFO_OVER_VOLTAGE)
+ *fault |= LED_FAULT_INPUT_VOLTAGE;
+
+ return rval;
+}
+
+static unsigned int __as3645a_current_to_reg(unsigned int min, unsigned int max,
+ unsigned int step,
+ unsigned int val)
+{
+ if (val < min)
+ val = min;
+
+ if (val > max)
+ val = max;
+
+ return (val - min) / step;
+}
+
+static unsigned int as3645a_current_to_reg(struct as3645a *flash, bool is_flash,
+ unsigned int ua)
+{
+ if (is_flash)
+ return __as3645a_current_to_reg(AS_TORCH_INTENSITY_MIN,
+ flash->cfg.assist_max_ua,
+ AS_TORCH_INTENSITY_STEP, ua);
+ else
+ return __as3645a_current_to_reg(AS_FLASH_INTENSITY_MIN,
+ flash->cfg.flash_max_ua,
+ AS_FLASH_INTENSITY_STEP, ua);
+}
+
+static int as3645a_set_indicator_brightness(struct led_classdev *iled_cdev,
+ enum led_brightness brightness)
+{
+ struct as3645a *flash = iled_cdev_to_as3645a(iled_cdev);
+ int rval;
+
+ flash->indicator_current = brightness;
+
+ rval = as3645a_set_timeout(flash);
+ if (rval)
+ return rval;
+
+ return as3645a_set_control(flash, AS_MODE_INDICATOR, brightness);
+}
+
+static int as3645a_set_assist_brightness(struct led_classdev *fled_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(fled_cdev);
+ struct as3645a *flash = fled_to_as3645a(fled);
+ int rval;
+
+ if (brightness) {
+ /* Register value 0 is 20 mA. */
+ flash->assist_current = brightness - 1;
+
+ rval = as3645a_set_current(flash);
+ if (rval)
+ return rval;
+ }
+
+ return as3645a_set_control(flash, AS_MODE_ASSIST, brightness);
+}
+
+static int as3645a_set_flash_brightness(struct led_classdev_flash *fled,
+ u32 brightness_ua)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ flash->flash_current = as3645a_current_to_reg(flash, true,
+ brightness_ua);
+
+ return as3645a_set_current(flash);
+}
+
+static int as3645a_set_flash_timeout(struct led_classdev_flash *fled,
+ u32 timeout_us)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ flash->timeout = AS_TIMER_US_TO_CODE(timeout_us);
+
+ return as3645a_set_timeout(flash);
+}
+
+static int as3645a_set_strobe(struct led_classdev_flash *fled, bool state)
+{
+ struct as3645a *flash = fled_to_as3645a(fled);
+
+ return as3645a_set_control(flash, AS_MODE_FLASH, state);
+}
+
+static const struct led_flash_ops as3645a_led_flash_ops = {
+ .flash_brightness_set = as3645a_set_flash_brightness,
+ .timeout_set = as3645a_set_flash_timeout,
+ .strobe_set = as3645a_set_strobe,
+ .fault_get = as3645a_get_fault,
+};
+
+static int as3645a_setup(struct as3645a *flash)
+{
+ struct device *dev = &flash->client->dev;
+ u32 fault = 0;
+ int rval;
+
+ /* clear errors */
+ rval = as3645a_read(flash, AS_FAULT_INFO_REG);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "Fault info: %02x\n", rval);
+
+ rval = as3645a_set_current(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_set_timeout(flash);
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_set_control(flash, AS_MODE_INDICATOR, false);
+ if (rval < 0)
+ return rval;
+
+ /* read status */
+ rval = as3645a_get_fault(&flash->fled, &fault);
+ if (rval < 0)
+ return rval;
+
+ dev_dbg(dev, "AS_INDICATOR_AND_TIMER_REG: %02x\n",
+ as3645a_read(flash, AS_INDICATOR_AND_TIMER_REG));
+ dev_dbg(dev, "AS_CURRENT_SET_REG: %02x\n",
+ as3645a_read(flash, AS_CURRENT_SET_REG));
+ dev_dbg(dev, "AS_CONTROL_REG: %02x\n",
+ as3645a_read(flash, AS_CONTROL_REG));
+
+ return rval & ~AS_FAULT_INFO_LED_AMOUNT ? -EIO : 0;
+}
+
+static int as3645a_detect(struct as3645a *flash)
+{
+ struct device *dev = &flash->client->dev;
+ int rval, man, model, rfu, version;
+ const char *vendor;
+
+ rval = as3645a_read(flash, AS_DESIGN_INFO_REG);
+ if (rval < 0) {
+ dev_err(dev, "can't read design info reg\n");
+ return rval;
+ }
+
+ man = AS_DESIGN_INFO_FACTORY(rval);
+ model = AS_DESIGN_INFO_MODEL(rval);
+
+ rval = as3645a_read(flash, AS_VERSION_CONTROL_REG);
+ if (rval < 0) {
+ dev_err(dev, "can't read version control reg\n");
+ return rval;
+ }
+
+ rfu = AS_VERSION_CONTROL_RFU(rval);
+ version = AS_VERSION_CONTROL_VERSION(rval);
+
+ /* Verify the chip model and version. */
+ if (model != 0x01 || rfu != 0x00) {
+ dev_err(dev, "AS3645A not detected (model %d rfu %d)\n",
+ model, rfu);
+ return -ENODEV;
+ }
+
+ switch (man) {
+ case 1:
+ vendor = "AMS, Austria Micro Systems";
+ break;
+ case 2:
+ vendor = "ADI, Analog Devices Inc.";
+ break;
+ case 3:
+ vendor = "NSC, National Semiconductor";
+ break;
+ case 4:
+ vendor = "NXP";
+ break;
+ case 5:
+ vendor = "TI, Texas Instrument";
+ break;
+ default:
+ vendor = "Unknown";
+ }
+
+ dev_info(dev, "Chip vendor: %s (%d) Version: %d\n", vendor,
+ man, version);
+
+ rval = as3645a_write(flash, AS_PASSWORD_REG, AS_PASSWORD_UNLOCK_VALUE);
+ if (rval < 0)
+ return rval;
+
+ return as3645a_write(flash, AS_BOOST_REG, AS_BOOST_CURRENT_DISABLE);
+}
+
+static int as3645a_parse_node(struct device *dev, struct as3645a *flash)
+{
+ struct as3645a_config *cfg = &flash->cfg;
+ int rval;
+
+ device_for_each_child_node_scoped(dev, child) {
+ u32 id = 0;
+
+ fwnode_property_read_u32(child, "reg", &id);
+
+ switch (id) {
+ case AS_LED_FLASH:
+ flash->flash_node = child;
+ fwnode_handle_get(child);
+ break;
+ case AS_LED_INDICATOR:
+ flash->indicator_node = child;
+ fwnode_handle_get(child);
+ break;
+ default:
+ dev_warn(&flash->client->dev,
+ "unknown LED %u encountered, ignoring\n", id);
+ break;
+ }
+ }
+
+ if (!flash->flash_node) {
+ dev_err(&flash->client->dev, "can't find flash node\n");
+ return -ENODEV;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-timeout-us",
+ &cfg->flash_timeout_us);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-timeout-us property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "flash-max-microamp",
+ &cfg->flash_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read flash-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ rval = fwnode_property_read_u32(flash->flash_node, "led-max-microamp",
+ &cfg->assist_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for flash\n");
+ goto out_err;
+ }
+
+ fwnode_property_read_u32(flash->flash_node, "voltage-reference",
+ &cfg->voltage_reference);
+
+ fwnode_property_read_u32(flash->flash_node, "ams,input-max-microamp",
+ &cfg->peak);
+ cfg->peak = AS_PEAK_mA_TO_REG(cfg->peak);
+
+ if (!flash->indicator_node) {
+ dev_warn(&flash->client->dev,
+ "can't find indicator node\n");
+ rval = -ENODEV;
+ goto out_err;
+ }
+
+
+ rval = fwnode_property_read_u32(flash->indicator_node,
+ "led-max-microamp",
+ &cfg->indicator_max_ua);
+ if (rval < 0) {
+ dev_err(&flash->client->dev,
+ "can't read led-max-microamp property for indicator\n");
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static int as3645a_led_class_setup(struct as3645a *flash)
+{
+ struct led_classdev *fled_cdev = &flash->fled.led_cdev;
+ struct led_classdev *iled_cdev = &flash->iled_cdev;
+ struct led_init_data init_data = {};
+ struct led_flash_setting *cfg;
+ int rval;
+
+ iled_cdev->brightness_set_blocking = as3645a_set_indicator_brightness;
+ iled_cdev->max_brightness =
+ flash->cfg.indicator_max_ua / AS_INDICATOR_INTENSITY_STEP;
+ iled_cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ init_data.fwnode = flash->indicator_node;
+ init_data.devicename = AS_NAME;
+ init_data.default_label = "indicator";
+
+ rval = led_classdev_register_ext(&flash->client->dev, iled_cdev,
+ &init_data);
+ if (rval < 0)
+ return rval;
+
+ cfg = &flash->fled.brightness;
+ cfg->min = AS_FLASH_INTENSITY_MIN;
+ cfg->max = flash->cfg.flash_max_ua;
+ cfg->step = AS_FLASH_INTENSITY_STEP;
+ cfg->val = flash->cfg.flash_max_ua;
+
+ cfg = &flash->fled.timeout;
+ cfg->min = AS_FLASH_TIMEOUT_MIN;
+ cfg->max = flash->cfg.flash_timeout_us;
+ cfg->step = AS_FLASH_TIMEOUT_STEP;
+ cfg->val = flash->cfg.flash_timeout_us;
+
+ flash->fled.ops = &as3645a_led_flash_ops;
+
+ fled_cdev->brightness_set_blocking = as3645a_set_assist_brightness;
+ /* Value 0 is off in LED class. */
+ fled_cdev->max_brightness =
+ as3645a_current_to_reg(flash, false,
+ flash->cfg.assist_max_ua) + 1;
+ fled_cdev->flags = LED_DEV_CAP_FLASH | LED_CORE_SUSPENDRESUME;
+
+ init_data.fwnode = flash->flash_node;
+ init_data.devicename = AS_NAME;
+ init_data.default_label = "flash";
+
+ rval = led_classdev_flash_register_ext(&flash->client->dev,
+ &flash->fled, &init_data);
+ if (rval)
+ goto out_err;
+
+ return rval;
+
+out_err:
+ led_classdev_unregister(iled_cdev);
+ dev_err(&flash->client->dev,
+ "led_classdev_flash_register() failed, error %d\n",
+ rval);
+ return rval;
+}
+
+static int as3645a_v4l2_setup(struct as3645a *flash)
+{
+ struct led_classdev_flash *fled = &flash->fled;
+ struct led_classdev *led = &fled->led_cdev;
+ struct v4l2_flash_config cfg = {
+ .intensity = {
+ .min = AS_TORCH_INTENSITY_MIN,
+ .max = flash->cfg.assist_max_ua,
+ .step = AS_TORCH_INTENSITY_STEP,
+ .val = flash->cfg.assist_max_ua,
+ },
+ };
+ struct v4l2_flash_config cfgind = {
+ .intensity = {
+ .min = AS_INDICATOR_INTENSITY_MIN,
+ .max = flash->cfg.indicator_max_ua,
+ .step = AS_INDICATOR_INTENSITY_STEP,
+ .val = flash->cfg.indicator_max_ua,
+ },
+ };
+
+ strscpy(cfg.dev_name, led->dev->kobj.name, sizeof(cfg.dev_name));
+ strscpy(cfgind.dev_name, flash->iled_cdev.dev->kobj.name,
+ sizeof(cfgind.dev_name));
+
+ flash->vf = v4l2_flash_init(
+ &flash->client->dev, flash->flash_node, &flash->fled, NULL,
+ &cfg);
+ if (IS_ERR(flash->vf))
+ return PTR_ERR(flash->vf);
+
+ flash->vfind = v4l2_flash_indicator_init(
+ &flash->client->dev, flash->indicator_node, &flash->iled_cdev,
+ &cfgind);
+ if (IS_ERR(flash->vfind)) {
+ v4l2_flash_release(flash->vf);
+ return PTR_ERR(flash->vfind);
+ }
+
+ return 0;
+}
+
+static int as3645a_probe(struct i2c_client *client)
+{
+ struct as3645a *flash;
+ int rval;
+
+ if (!dev_fwnode(&client->dev))
+ return -ENODEV;
+
+ flash = devm_kzalloc(&client->dev, sizeof(*flash), GFP_KERNEL);
+ if (flash == NULL)
+ return -ENOMEM;
+
+ flash->client = client;
+
+ rval = as3645a_parse_node(&client->dev, flash);
+ if (rval < 0)
+ return rval;
+
+ rval = as3645a_detect(flash);
+ if (rval < 0)
+ goto out_put_nodes;
+
+ mutex_init(&flash->mutex);
+ i2c_set_clientdata(client, flash);
+
+ rval = as3645a_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = as3645a_led_class_setup(flash);
+ if (rval)
+ goto out_mutex_destroy;
+
+ rval = as3645a_v4l2_setup(flash);
+ if (rval)
+ goto out_led_classdev_flash_unregister;
+
+ return 0;
+
+out_led_classdev_flash_unregister:
+ led_classdev_flash_unregister(&flash->fled);
+
+out_mutex_destroy:
+ mutex_destroy(&flash->mutex);
+
+out_put_nodes:
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+
+ return rval;
+}
+
+static void as3645a_remove(struct i2c_client *client)
+{
+ struct as3645a *flash = i2c_get_clientdata(client);
+
+ as3645a_set_control(flash, AS_MODE_EXT_TORCH, false);
+
+ v4l2_flash_release(flash->vf);
+ v4l2_flash_release(flash->vfind);
+
+ led_classdev_flash_unregister(&flash->fled);
+ led_classdev_unregister(&flash->iled_cdev);
+
+ mutex_destroy(&flash->mutex);
+
+ fwnode_handle_put(flash->flash_node);
+ fwnode_handle_put(flash->indicator_node);
+}
+
+static const struct i2c_device_id as3645a_id_table[] = {
+ { AS_NAME },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, as3645a_id_table);
+
+static const struct of_device_id as3645a_of_table[] = {
+ { .compatible = "ams,as3645a" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, as3645a_of_table);
+
+static struct i2c_driver as3645a_i2c_driver = {
+ .driver = {
+ .of_match_table = as3645a_of_table,
+ .name = AS_NAME,
+ },
+ .probe = as3645a_probe,
+ .remove = as3645a_remove,
+ .id_table = as3645a_id_table,
+};
+
+module_i2c_driver(as3645a_i2c_driver);
+
+MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
+MODULE_AUTHOR("Sakari Ailus <sakari.ailus@iki.fi>");
+MODULE_DESCRIPTION("LED flash driver for AS3645A, LM3555 and their clones");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-ktd2692.c b/drivers/leds/flash/leds-ktd2692.c
new file mode 100644
index 000000000000..0f16eefcfe4c
--- /dev/null
+++ b/drivers/leds/flash/leds-ktd2692.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver : leds-ktd2692.c
+ *
+ * Copyright (C) 2015 Samsung Electronics
+ * Ingi Kim <ingi2.kim@samsung.com>
+ */
+
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds-expresswire.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regulator/consumer.h>
+
+/* Value related the movie mode */
+#define KTD2692_MOVIE_MODE_CURRENT_LEVELS 16
+#define KTD2692_MM_TO_FL_RATIO(x) ((x) / 3)
+#define KTD2692_MM_MIN_CURR_THRESHOLD_SCALE 8
+
+/* Value related the flash mode */
+#define KTD2692_FLASH_MODE_TIMEOUT_LEVELS 8
+#define KTD2692_FLASH_MODE_TIMEOUT_DISABLE 0
+#define KTD2692_FLASH_MODE_CURR_PERCENT(x) (((x) * 16) / 100)
+
+/* Macro for getting offset of flash timeout */
+#define GET_TIMEOUT_OFFSET(timeout, step) ((timeout) / (step))
+
+/* Base register address */
+#define KTD2692_REG_LVP_BASE 0x00
+#define KTD2692_REG_FLASH_TIMEOUT_BASE 0x20
+#define KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE 0x40
+#define KTD2692_REG_MOVIE_CURRENT_BASE 0x60
+#define KTD2692_REG_FLASH_CURRENT_BASE 0x80
+#define KTD2692_REG_MODE_BASE 0xA0
+
+/* KTD2692 default length of name */
+#define KTD2692_NAME_LENGTH 20
+
+/* Movie / Flash Mode Control */
+enum ktd2692_led_mode {
+ KTD2692_MODE_DISABLE = 0, /* default */
+ KTD2692_MODE_MOVIE,
+ KTD2692_MODE_FLASH,
+};
+
+struct ktd2692_led_config_data {
+ /* maximum LED current in movie mode */
+ u32 movie_max_microamp;
+ /* maximum LED current in flash mode */
+ u32 flash_max_microamp;
+ /* maximum flash timeout */
+ u32 flash_max_timeout;
+ /* max LED brightness level */
+ enum led_brightness max_brightness;
+};
+
+const struct expresswire_timing ktd2692_timing = {
+ .poweroff_us = 700,
+ .data_start_us = 10,
+ .end_of_data_low_us = 10,
+ .end_of_data_high_us = 350,
+ .short_bitset_us = 4,
+ .long_bitset_us = 12
+};
+
+struct ktd2692_context {
+ /* Common ExpressWire properties (ctrl GPIO and timing) */
+ struct expresswire_common_props props;
+
+ /* Related LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+
+ /* secures access to the device */
+ struct mutex lock;
+ struct regulator *regulator;
+
+ struct gpio_desc *aux_gpio;
+
+ enum ktd2692_led_mode mode;
+ enum led_brightness torch_brightness;
+};
+
+static struct ktd2692_context *fled_cdev_to_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct ktd2692_context, fled_cdev);
+}
+
+static int ktd2692_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ if (brightness == LED_OFF) {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, 0);
+ } else {
+ expresswire_write_u8(&led->props, brightness |
+ KTD2692_REG_MOVIE_CURRENT_BASE);
+ led->mode = KTD2692_MODE_MOVIE;
+ }
+
+ expresswire_write_u8(&led->props, led->mode | KTD2692_REG_MODE_BASE);
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct ktd2692_context *led = fled_cdev_to_led(fled_cdev);
+ struct led_flash_setting *timeout = &fled_cdev->timeout;
+ u32 flash_tm_reg;
+
+ mutex_lock(&led->lock);
+
+ if (state) {
+ flash_tm_reg = GET_TIMEOUT_OFFSET(timeout->val, timeout->step);
+ expresswire_write_u8(&led->props, flash_tm_reg
+ | KTD2692_REG_FLASH_TIMEOUT_BASE);
+
+ led->mode = KTD2692_MODE_FLASH;
+ gpiod_direction_output(led->aux_gpio, 1);
+ } else {
+ led->mode = KTD2692_MODE_DISABLE;
+ gpiod_direction_output(led->aux_gpio, 0);
+ }
+
+ expresswire_write_u8(&led->props, led->mode | KTD2692_REG_MODE_BASE);
+
+ fled_cdev->led_cdev.brightness = LED_OFF;
+ led->mode = KTD2692_MODE_DISABLE;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int ktd2692_led_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ return 0;
+}
+
+static void ktd2692_init_movie_current_max(struct ktd2692_led_config_data *cfg)
+{
+ u32 offset, step;
+ u32 movie_current_microamp;
+
+ offset = KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+ step = KTD2692_MM_TO_FL_RATIO(cfg->flash_max_microamp)
+ / KTD2692_MOVIE_MODE_CURRENT_LEVELS;
+
+ do {
+ movie_current_microamp = step * offset;
+ offset--;
+ } while ((movie_current_microamp > cfg->movie_max_microamp) &&
+ (offset > 0));
+
+ cfg->max_brightness = offset;
+}
+
+static void ktd2692_init_flash_timeout(struct led_classdev_flash *fled_cdev,
+ struct ktd2692_led_config_data *cfg)
+{
+ struct led_flash_setting *setting;
+
+ setting = &fled_cdev->timeout;
+ setting->min = KTD2692_FLASH_MODE_TIMEOUT_DISABLE;
+ setting->max = cfg->flash_max_timeout;
+ setting->step = cfg->flash_max_timeout
+ / (KTD2692_FLASH_MODE_TIMEOUT_LEVELS - 1);
+ setting->val = cfg->flash_max_timeout;
+}
+
+static void ktd2692_setup(struct ktd2692_context *led)
+{
+ led->mode = KTD2692_MODE_DISABLE;
+ expresswire_power_off(&led->props);
+ gpiod_direction_output(led->aux_gpio, 0);
+
+ expresswire_write_u8(&led->props, (KTD2692_MM_MIN_CURR_THRESHOLD_SCALE - 1)
+ | KTD2692_REG_MM_MIN_CURR_THRESHOLD_BASE);
+ expresswire_write_u8(&led->props, KTD2692_FLASH_MODE_CURR_PERCENT(45)
+ | KTD2692_REG_FLASH_CURRENT_BASE);
+}
+
+static void regulator_disable_action(void *_data)
+{
+ struct device *dev = _data;
+ struct ktd2692_context *led = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(dev, "Failed to disable supply: %d\n", ret);
+}
+
+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);
+ int ret;
+
+ if (!np)
+ return -ENXIO;
+
+ led->props.ctrl_gpio = devm_gpiod_get(dev, "ctrl", GPIOD_ASIS);
+ ret = PTR_ERR_OR_ZERO(led->props.ctrl_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "cannot get ctrl-gpios\n");
+
+ led->aux_gpio = devm_gpiod_get_optional(dev, "aux", GPIOD_ASIS);
+ if (IS_ERR(led->aux_gpio))
+ return dev_err_probe(dev, PTR_ERR(led->aux_gpio), "cannot get aux-gpios\n");
+
+ led->regulator = devm_regulator_get(dev, "vin");
+ if (IS_ERR(led->regulator))
+ led->regulator = NULL;
+
+ if (led->regulator) {
+ ret = regulator_enable(led->regulator);
+ if (ret) {
+ dev_err(dev, "Failed to enable supply: %d\n", ret);
+ } else {
+ ret = devm_add_action_or_reset(dev,
+ regulator_disable_action, dev);
+ if (ret)
+ return ret;
+ }
+ }
+
+ 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;
+ }
+
+ led->fled_cdev.led_cdev.name =
+ of_get_property(child_node, "label", NULL) ? : child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->movie_max_microamp);
+ if (ret) {
+ dev_err(dev, "failed to parse led-max-microamp\n");
+ 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");
+ 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");
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .strobe_set = ktd2692_led_flash_strobe_set,
+ .timeout_set = ktd2692_led_flash_timeout_set,
+};
+
+static int ktd2692_probe(struct platform_device *pdev)
+{
+ struct ktd2692_context *led;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct ktd2692_led_config_data led_cfg;
+ int ret;
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ 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)
+ return ret;
+
+ ktd2692_init_flash_timeout(fled_cdev, &led_cfg);
+ ktd2692_init_movie_current_max(&led_cfg);
+
+ fled_cdev->ops = &flash_ops;
+
+ led_cdev->max_brightness = led_cfg.max_brightness;
+ led_cdev->brightness_set_blocking = ktd2692_led_brightness_set;
+ led_cdev->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+ mutex_init(&led->lock);
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_flash_register(&pdev->dev, fled_cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "can't register LED %s\n", led_cdev->name);
+ mutex_destroy(&led->lock);
+ return ret;
+ }
+
+ ktd2692_setup(led);
+
+ return 0;
+}
+
+static void ktd2692_remove(struct platform_device *pdev)
+{
+ struct ktd2692_context *led = platform_get_drvdata(pdev);
+
+ led_classdev_flash_unregister(&led->fled_cdev);
+
+ mutex_destroy(&led->lock);
+}
+
+static const struct of_device_id ktd2692_match[] = {
+ { .compatible = "kinetic,ktd2692", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, ktd2692_match);
+
+static struct platform_driver ktd2692_driver = {
+ .driver = {
+ .name = "ktd2692",
+ .of_match_table = ktd2692_match,
+ },
+ .probe = ktd2692_probe,
+ .remove = ktd2692_remove,
+};
+
+module_platform_driver(ktd2692_driver);
+
+MODULE_IMPORT_NS("EXPRESSWIRE");
+MODULE_AUTHOR("Ingi Kim <ingi2.kim@samsung.com>");
+MODULE_DESCRIPTION("Kinetic KTD2692 LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-lm3601x.c b/drivers/leds/flash/leds-lm3601x.c
new file mode 100644
index 000000000000..abf6b96ade3d
--- /dev/null
+++ b/drivers/leds/flash/leds-lm3601x.c
@@ -0,0 +1,494 @@
+// SPDX-License-Identifier: GPL-2.0
+// Flash and torch driver for Texas Instruments LM3601X LED
+// Flash driver chip family
+// Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define LM3601X_LED_IR 0x0
+#define LM3601X_LED_TORCH 0x1
+
+/* Registers */
+#define LM3601X_ENABLE_REG 0x01
+#define LM3601X_CFG_REG 0x02
+#define LM3601X_LED_FLASH_REG 0x03
+#define LM3601X_LED_TORCH_REG 0x04
+#define LM3601X_FLAGS_REG 0x05
+#define LM3601X_DEV_ID_REG 0x06
+
+#define LM3601X_SW_RESET BIT(7)
+
+/* Enable Mode bits */
+#define LM3601X_MODE_STANDBY 0x00
+#define LM3601X_MODE_IR_DRV BIT(0)
+#define LM3601X_MODE_TORCH BIT(1)
+#define LM3601X_MODE_STROBE (BIT(0) | BIT(1))
+#define LM3601X_STRB_EN BIT(2)
+#define LM3601X_STRB_EDGE_TRIG BIT(3)
+#define LM3601X_IVFM_EN BIT(4)
+
+#define LM36010_BOOST_LIMIT_28 BIT(5)
+#define LM36010_BOOST_FREQ_4MHZ BIT(6)
+#define LM36010_BOOST_MODE_PASS BIT(7)
+
+/* Flag Mask */
+#define LM3601X_FLASH_TIME_OUT BIT(0)
+#define LM3601X_UVLO_FAULT BIT(1)
+#define LM3601X_THERM_SHUTDOWN BIT(2)
+#define LM3601X_THERM_CURR BIT(3)
+#define LM36010_CURR_LIMIT BIT(4)
+#define LM3601X_SHORT_FAULT BIT(5)
+#define LM3601X_IVFM_TRIP BIT(6)
+#define LM36010_OVP_FAULT BIT(7)
+
+#define LM3601X_MAX_TORCH_I_UA 376000
+#define LM3601X_MIN_TORCH_I_UA 2400
+#define LM3601X_TORCH_REG_DIV 2965
+
+#define LM3601X_MAX_STROBE_I_UA 1500000
+#define LM3601X_MIN_STROBE_I_UA 11000
+#define LM3601X_STROBE_REG_DIV 11800
+
+#define LM3601X_TIMEOUT_MASK 0x1e
+#define LM3601X_ENABLE_MASK (LM3601X_MODE_IR_DRV | LM3601X_MODE_TORCH)
+
+#define LM3601X_LOWER_STEP_US 40000
+#define LM3601X_UPPER_STEP_US 200000
+#define LM3601X_MIN_TIMEOUT_US 40000
+#define LM3601X_MAX_TIMEOUT_US 1600000
+#define LM3601X_TIMEOUT_XOVER_US 400000
+
+enum lm3601x_type {
+ CHIP_LM36010 = 0,
+ CHIP_LM36011,
+};
+
+/**
+ * struct lm3601x_led - private lm3601x LED data
+ * @fled_cdev: flash LED class device pointer
+ * @client: Pointer to the I2C client
+ * @regmap: Devices register map
+ * @lock: Lock for reading/writing the device
+ * @flash_timeout: the timeout for the flash
+ * @last_flag: last known flags register value
+ * @torch_current_max: maximum current for the torch
+ * @flash_current_max: maximum current for the flash
+ * @max_flash_timeout: maximum timeout for the flash
+ * @led_mode: The mode to enable either IR or Torch
+ */
+struct lm3601x_led {
+ struct led_classdev_flash fled_cdev;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct mutex lock;
+
+ unsigned int flash_timeout;
+ unsigned int last_flag;
+
+ u32 torch_current_max;
+ u32 flash_current_max;
+ u32 max_flash_timeout;
+
+ u32 led_mode;
+};
+
+static const struct reg_default lm3601x_regmap_defs[] = {
+ { LM3601X_ENABLE_REG, 0x20 },
+ { LM3601X_CFG_REG, 0x15 },
+ { LM3601X_LED_FLASH_REG, 0x00 },
+ { LM3601X_LED_TORCH_REG, 0x00 },
+};
+
+static bool lm3601x_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case LM3601X_FLAGS_REG:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config lm3601x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LM3601X_DEV_ID_REG,
+ .reg_defaults = lm3601x_regmap_defs,
+ .num_reg_defaults = ARRAY_SIZE(lm3601x_regmap_defs),
+ .cache_type = REGCACHE_MAPLE,
+ .volatile_reg = lm3601x_volatile_reg,
+};
+
+static struct lm3601x_led *fled_cdev_to_led(struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct lm3601x_led, fled_cdev);
+}
+
+static int lm3601x_read_faults(struct lm3601x_led *led)
+{
+ int flags_val;
+ int ret;
+
+ ret = regmap_read(led->regmap, LM3601X_FLAGS_REG, &flags_val);
+ if (ret < 0)
+ return -EIO;
+
+ led->last_flag = 0;
+
+ if (flags_val & LM36010_OVP_FAULT)
+ led->last_flag |= LED_FAULT_OVER_VOLTAGE;
+
+ if (flags_val & (LM3601X_THERM_SHUTDOWN | LM3601X_THERM_CURR))
+ led->last_flag |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (flags_val & LM3601X_SHORT_FAULT)
+ led->last_flag |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (flags_val & LM36010_CURR_LIMIT)
+ led->last_flag |= LED_FAULT_OVER_CURRENT;
+
+ if (flags_val & LM3601X_UVLO_FAULT)
+ led->last_flag |= LED_FAULT_UNDER_VOLTAGE;
+
+ if (flags_val & LM3601X_IVFM_TRIP)
+ led->last_flag |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (flags_val & LM3601X_THERM_SHUTDOWN)
+ led->last_flag |= LED_FAULT_LED_OVER_TEMPERATURE;
+
+ return led->last_flag;
+}
+
+static int lm3601x_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int ret, led_mode_val;
+
+ mutex_lock(&led->lock);
+
+ ret = lm3601x_read_faults(led);
+ if (ret < 0)
+ goto out;
+
+ if (led->led_mode == LM3601X_LED_TORCH)
+ led_mode_val = LM3601X_MODE_TORCH;
+ else
+ led_mode_val = LM3601X_MODE_IR_DRV;
+
+ if (brightness == LED_OFF) {
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ led_mode_val, LED_OFF);
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LM3601X_LED_TORCH_REG, brightness - 1);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV,
+ led_mode_val);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int timeout_reg_val;
+ int current_timeout;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_read(led->regmap, LM3601X_CFG_REG, &current_timeout);
+ if (ret < 0)
+ goto out;
+
+ if (led->flash_timeout >= LM3601X_TIMEOUT_XOVER_US)
+ timeout_reg_val = led->flash_timeout / LM3601X_UPPER_STEP_US + 0x07;
+ else
+ timeout_reg_val = led->flash_timeout / LM3601X_LOWER_STEP_US - 0x01;
+
+ if (led->flash_timeout != current_timeout)
+ ret = regmap_update_bits(led->regmap, LM3601X_CFG_REG,
+ LM3601X_TIMEOUT_MASK, timeout_reg_val);
+
+ if (state)
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_TORCH | LM3601X_MODE_IR_DRV,
+ LM3601X_MODE_STROBE);
+ else
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_STROBE, LED_OFF);
+
+ ret = lm3601x_read_faults(led);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_brightness_set(struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ u8 brightness_val;
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = lm3601x_read_faults(led);
+ if (ret < 0)
+ goto out;
+
+ if (brightness == LED_OFF) {
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_MODE_STROBE, LED_OFF);
+ goto out;
+ }
+
+ brightness_val = brightness / LM3601X_STROBE_REG_DIV;
+
+ ret = regmap_write(led->regmap, LM3601X_LED_FLASH_REG, brightness_val);
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+
+ mutex_lock(&led->lock);
+
+ led->flash_timeout = timeout;
+
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int lm3601x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+ int strobe_state;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ ret = regmap_read(led->regmap, LM3601X_ENABLE_REG, &strobe_state);
+ if (ret < 0)
+ goto out;
+
+ *state = strobe_state & LM3601X_MODE_STROBE;
+
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int lm3601x_flash_fault_get(struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct lm3601x_led *led = fled_cdev_to_led(fled_cdev);
+
+ lm3601x_read_faults(led);
+
+ *fault = led->last_flag;
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = lm3601x_flash_brightness_set,
+ .strobe_set = lm3601x_strobe_set,
+ .strobe_get = lm3601x_strobe_get,
+ .timeout_set = lm3601x_flash_timeout_set,
+ .fault_get = lm3601x_flash_fault_get,
+};
+
+static int lm3601x_register_leds(struct lm3601x_led *led,
+ struct fwnode_handle *fwnode)
+{
+ struct led_classdev *led_cdev;
+ struct led_flash_setting *setting;
+ struct led_init_data init_data = {};
+
+ led->fled_cdev.ops = &flash_ops;
+
+ setting = &led->fled_cdev.timeout;
+ setting->min = LM3601X_MIN_TIMEOUT_US;
+ setting->max = led->max_flash_timeout;
+ setting->step = LM3601X_LOWER_STEP_US;
+ setting->val = led->max_flash_timeout;
+
+ setting = &led->fled_cdev.brightness;
+ setting->min = LM3601X_MIN_STROBE_I_UA;
+ setting->max = led->flash_current_max;
+ setting->step = LM3601X_TORCH_REG_DIV;
+ setting->val = led->flash_current_max;
+
+ 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_MIN_TORCH_I_UA + 1,
+ LM3601X_TORCH_REG_DIV);
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ init_data.fwnode = fwnode;
+ init_data.devicename = led->client->name;
+ init_data.default_label = (led->led_mode == LM3601X_LED_TORCH) ?
+ "torch" : "infrared";
+ return devm_led_classdev_flash_register_ext(&led->client->dev,
+ &led->fled_cdev, &init_data);
+}
+
+static int lm3601x_parse_node(struct lm3601x_led *led,
+ struct fwnode_handle **fwnode)
+{
+ struct fwnode_handle *child = NULL;
+ int ret = -ENODEV;
+
+ child = device_get_next_child_node(&led->client->dev, child);
+ if (!child) {
+ dev_err(&led->client->dev, "No LED Child node\n");
+ return ret;
+ }
+
+ ret = fwnode_property_read_u32(child, "reg", &led->led_mode);
+ if (ret) {
+ dev_err(&led->client->dev, "reg DT property missing\n");
+ goto out_err;
+ }
+
+ if (led->led_mode > LM3601X_LED_TORCH ||
+ led->led_mode < LM3601X_LED_IR) {
+ dev_warn(&led->client->dev, "Invalid led mode requested\n");
+ ret = -EINVAL;
+ goto out_err;
+ }
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp",
+ &led->torch_current_max);
+ if (ret) {
+ dev_warn(&led->client->dev,
+ "led-max-microamp DT property missing\n");
+ 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) {
+ dev_warn(&led->client->dev,
+ "flash-max-microamp DT property missing\n");
+ goto out_err;
+ }
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us",
+ &led->max_flash_timeout);
+ if (ret) {
+ dev_warn(&led->client->dev,
+ "flash-max-timeout-us DT property missing\n");
+ goto out_err;
+ }
+
+ *fwnode = child;
+
+out_err:
+ fwnode_handle_put(child);
+ return ret;
+}
+
+static int lm3601x_probe(struct i2c_client *client)
+{
+ struct lm3601x_led *led;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->client = client;
+ i2c_set_clientdata(client, led);
+
+ ret = lm3601x_parse_node(led, &fwnode);
+ if (ret)
+ return -ENODEV;
+
+ led->regmap = devm_regmap_init_i2c(client, &lm3601x_regmap);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev,
+ "Failed to allocate register map: %d\n", ret);
+ 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);
+}
+
+static void lm3601x_remove(struct i2c_client *client)
+{
+ struct lm3601x_led *led = i2c_get_clientdata(client);
+ int ret;
+
+ ret = regmap_update_bits(led->regmap, LM3601X_ENABLE_REG,
+ LM3601X_ENABLE_MASK, LM3601X_MODE_STANDBY);
+ if (ret)
+ dev_warn(&client->dev,
+ "Failed to put into standby (%pe)\n", ERR_PTR(ret));
+}
+
+static const struct i2c_device_id lm3601x_id[] = {
+ { "LM36010", CHIP_LM36010 },
+ { "LM36011", CHIP_LM36011 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm3601x_id);
+
+static const struct of_device_id of_lm3601x_leds_match[] = {
+ { .compatible = "ti,lm36010", },
+ { .compatible = "ti,lm36011", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_lm3601x_leds_match);
+
+static struct i2c_driver lm3601x_i2c_driver = {
+ .driver = {
+ .name = "lm3601x",
+ .of_match_table = of_lm3601x_leds_match,
+ },
+ .probe = lm3601x_probe,
+ .remove = lm3601x_remove,
+ .id_table = lm3601x_id,
+};
+module_i2c_driver(lm3601x_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3601X");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-max77693.c b/drivers/leds/flash/leds-max77693.c
new file mode 100644
index 000000000000..daee10986108
--- /dev/null
+++ b/drivers/leds/flash/leds-max77693.c
@@ -0,0 +1,1057 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Flash class driver for the flash cell of max77693 mfd.
+ *
+ * Copyright (C) 2015, Samsung Electronics Co., Ltd.
+ *
+ * Authors: Jacek Anaszewski <j.anaszewski@samsung.com>
+ * Andrzej Hajda <a.hajda@samsung.com>
+ */
+
+#include <linux/led-class-flash.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77693-private.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define MODE_OFF 0
+#define MODE_FLASH(a) (1 << (a))
+#define MODE_TORCH(a) (1 << (2 + (a)))
+#define MODE_FLASH_EXTERNAL(a) (1 << (4 + (a)))
+
+#define MODE_FLASH_MASK (MODE_FLASH(FLED1) | MODE_FLASH(FLED2) | \
+ MODE_FLASH_EXTERNAL(FLED1) | \
+ MODE_FLASH_EXTERNAL(FLED2))
+#define MODE_TORCH_MASK (MODE_TORCH(FLED1) | MODE_TORCH(FLED2))
+
+#define FLED1_IOUT (1 << 0)
+#define FLED2_IOUT (1 << 1)
+
+enum max77693_fled {
+ FLED1,
+ FLED2,
+};
+
+enum max77693_led_mode {
+ FLASH,
+ TORCH,
+};
+
+struct max77693_led_config_data {
+ const char *label[2];
+ u32 iout_torch_max[2];
+ u32 iout_flash_max[2];
+ u32 flash_timeout_max[2];
+ u32 num_leds;
+ u32 boost_mode;
+ u32 boost_vout;
+ u32 low_vsys;
+};
+
+struct max77693_sub_led {
+ /* corresponding FLED output identifier */
+ int fled_id;
+ /* corresponding LED Flash class device */
+ struct led_classdev_flash fled_cdev;
+ /* V4L2 Flash device */
+ struct v4l2_flash *v4l2_flash;
+
+ /* brightness cache */
+ unsigned int torch_brightness;
+ /* flash timeout cache */
+ unsigned int flash_timeout;
+ /* flash faults that may have occurred */
+ u32 flash_faults;
+};
+
+struct max77693_led_device {
+ /* parent mfd regmap */
+ struct regmap *regmap;
+ /* platform device data */
+ struct platform_device *pdev;
+ /* secures access to the device */
+ struct mutex lock;
+
+ /* sub led data */
+ struct max77693_sub_led sub_leds[2];
+
+ /* maximum torch current values for FLED outputs */
+ u32 iout_torch_max[2];
+ /* maximum flash current values for FLED outputs */
+ u32 iout_flash_max[2];
+
+ /* current flash timeout cache */
+ unsigned int current_flash_timeout;
+ /* ITORCH register cache */
+ u8 torch_iout_reg;
+ /* mode of fled outputs */
+ unsigned int mode_flags;
+ /* recently strobed fled */
+ int strobing_sub_led_id;
+ /* bitmask of FLED outputs use state (bit 0. - FLED1, bit 1. - FLED2) */
+ u8 fled_mask;
+ /* FLED modes that can be set */
+ u8 allowed_modes;
+
+ /* arrangement of current outputs */
+ bool iout_joint;
+};
+
+static u8 max77693_led_iout_to_reg(u32 ua)
+{
+ if (ua < FLASH_IOUT_MIN)
+ ua = FLASH_IOUT_MIN;
+ return (ua - FLASH_IOUT_MIN) / FLASH_IOUT_STEP;
+}
+
+static u8 max77693_flash_timeout_to_reg(u32 us)
+{
+ return (us - FLASH_TIMEOUT_MIN) / FLASH_TIMEOUT_STEP;
+}
+
+static inline struct max77693_sub_led *flcdev_to_sub_led(
+ struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct max77693_sub_led, fled_cdev);
+}
+
+static inline struct max77693_led_device *sub_led_to_led(
+ struct max77693_sub_led *sub_led)
+{
+ return container_of(sub_led, struct max77693_led_device,
+ sub_leds[sub_led->fled_id]);
+}
+
+static inline u8 max77693_led_vsys_to_reg(u32 mv)
+{
+ return ((mv - MAX_FLASH1_VSYS_MIN) / MAX_FLASH1_VSYS_STEP) << 2;
+}
+
+static inline u8 max77693_led_vout_to_reg(u32 mv)
+{
+ return (mv - FLASH_VOUT_MIN) / FLASH_VOUT_STEP + FLASH_VOUT_RMIN;
+}
+
+static inline bool max77693_fled_used(struct max77693_led_device *led,
+ int fled_id)
+{
+ u8 fled_bit = (fled_id == FLED1) ? FLED1_IOUT : FLED2_IOUT;
+
+ return led->fled_mask & fled_bit;
+}
+
+static int max77693_set_mode_reg(struct max77693_led_device *led, u8 mode)
+{
+ struct regmap *rmap = led->regmap;
+ int ret, v = 0, i;
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (mode & MODE_TORCH(i))
+ v |= FLASH_EN_ON << TORCH_EN_SHIFT(i);
+
+ if (mode & MODE_FLASH(i)) {
+ v |= FLASH_EN_ON << FLASH_EN_SHIFT(i);
+ } else if (mode & MODE_FLASH_EXTERNAL(i)) {
+ v |= FLASH_EN_FLASH << FLASH_EN_SHIFT(i);
+ /*
+ * Enable hw triggering also for torch mode, as some
+ * camera sensors use torch led to fathom ambient light
+ * conditions before strobing the flash.
+ */
+ v |= FLASH_EN_TORCH << TORCH_EN_SHIFT(i);
+ }
+ }
+
+ /* Reset the register only prior setting flash modes */
+ if (mode & ~(MODE_TORCH(FLED1) | MODE_TORCH(FLED2))) {
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ return regmap_write(rmap, MAX77693_LED_REG_FLASH_EN, v);
+}
+
+static int max77693_add_mode(struct max77693_led_device *led, u8 mode)
+{
+ u8 new_mode_flags;
+ int i, ret;
+
+ if (led->iout_joint)
+ /* Span the mode on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ /*
+ * FLASH_EXTERNAL mode activates FLASHEN and TORCHEN pins in the device.
+ * Corresponding register bit fields interfere with SW triggered modes,
+ * thus clear them to ensure proper device configuration.
+ */
+ for (i = FLED1; i <= FLED2; ++i)
+ if (mode & MODE_FLASH_EXTERNAL(i))
+ led->mode_flags &= (~MODE_TORCH(i) & ~MODE_FLASH(i));
+
+ new_mode_flags = mode | led->mode_flags;
+ new_mode_flags &= led->allowed_modes;
+
+ if (new_mode_flags ^ led->mode_flags)
+ led->mode_flags = new_mode_flags;
+ else
+ return 0;
+
+ ret = max77693_set_mode_reg(led, led->mode_flags);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Clear flash mode flag after setting the mode to avoid spurious flash
+ * strobing on each subsequent torch mode setting.
+ */
+ if (mode & MODE_FLASH_MASK)
+ led->mode_flags &= ~mode;
+
+ return ret;
+}
+
+static int max77693_clear_mode(struct max77693_led_device *led,
+ u8 mode)
+{
+ if (led->iout_joint)
+ /* Clear mode also on FLED2 for joint iouts case */
+ mode |= (mode << 1);
+
+ led->mode_flags &= ~mode;
+
+ return max77693_set_mode_reg(led, led->mode_flags);
+}
+
+static void max77693_add_allowed_modes(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode)
+{
+ if (mode == FLASH)
+ led->allowed_modes |= (MODE_FLASH(fled_id) |
+ MODE_FLASH_EXTERNAL(fled_id));
+ else
+ led->allowed_modes |= MODE_TORCH(fled_id);
+}
+
+static void max77693_distribute_currents(struct max77693_led_device *led,
+ int fled_id, enum max77693_led_mode mode,
+ u32 micro_amp, u32 iout_max[2], u32 iout[2])
+{
+ if (!led->iout_joint) {
+ iout[fled_id] = micro_amp;
+ max77693_add_allowed_modes(led, fled_id, mode);
+ return;
+ }
+
+ iout[FLED1] = min(micro_amp, iout_max[FLED1]);
+ iout[FLED2] = micro_amp - iout[FLED1];
+
+ if (mode == FLASH)
+ led->allowed_modes &= ~MODE_FLASH_MASK;
+ else
+ led->allowed_modes &= ~MODE_TORCH_MASK;
+
+ max77693_add_allowed_modes(led, FLED1, mode);
+
+ if (iout[FLED2])
+ max77693_add_allowed_modes(led, FLED2, mode);
+}
+
+static int max77693_set_torch_current(struct max77693_led_device *led,
+ int fled_id, u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg = 0, iout2_reg = 0;
+ u32 iout[2];
+
+ max77693_distribute_currents(led, fled_id, TORCH, micro_amp,
+ led->iout_torch_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT2_SHIFT);
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ led->torch_iout_reg &= TORCH_IOUT_MASK(TORCH_IOUT1_SHIFT);
+ }
+
+ led->torch_iout_reg |= ((iout1_reg << TORCH_IOUT1_SHIFT) |
+ (iout2_reg << TORCH_IOUT2_SHIFT));
+
+ return regmap_write(rmap, MAX77693_LED_REG_ITORCH,
+ led->torch_iout_reg);
+}
+
+static int max77693_set_flash_current(struct max77693_led_device *led,
+ int fled_id,
+ u32 micro_amp)
+{
+ struct regmap *rmap = led->regmap;
+ u8 iout1_reg, iout2_reg;
+ u32 iout[2];
+ int ret = -EINVAL;
+
+ max77693_distribute_currents(led, fled_id, FLASH, micro_amp,
+ led->iout_flash_max, iout);
+
+ if (fled_id == FLED1 || led->iout_joint) {
+ iout1_reg = max77693_led_iout_to_reg(iout[FLED1]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH1,
+ iout1_reg);
+ if (ret < 0)
+ return ret;
+ }
+ if (fled_id == FLED2 || led->iout_joint) {
+ iout2_reg = max77693_led_iout_to_reg(iout[FLED2]);
+ ret = regmap_write(rmap, MAX77693_LED_REG_IFLASH2,
+ iout2_reg);
+ }
+
+ return ret;
+}
+
+static int max77693_set_timeout(struct max77693_led_device *led, u32 microsec)
+{
+ struct regmap *rmap = led->regmap;
+ u8 v;
+ int ret;
+
+ v = max77693_flash_timeout_to_reg(microsec) | FLASH_TMR_LEVEL;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_FLASH_TIMER, v);
+ if (ret < 0)
+ return ret;
+
+ led->current_flash_timeout = microsec;
+
+ return 0;
+}
+
+static int max77693_get_strobe_status(struct max77693_led_device *led,
+ bool *state)
+{
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ int ret;
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_STATUS, &v);
+ if (ret < 0)
+ return ret;
+
+ *state = v & FLASH_STATUS_FLASH_ON;
+
+ return ret;
+}
+
+static int max77693_get_flash_faults(struct max77693_sub_led *sub_led)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct regmap *rmap = led->regmap;
+ unsigned int v;
+ u8 fault_open_mask, fault_short_mask;
+ int ret;
+
+ sub_led->flash_faults = 0;
+
+ if (led->iout_joint) {
+ fault_open_mask = FLASH_INT_FLED1_OPEN | FLASH_INT_FLED2_OPEN;
+ fault_short_mask = FLASH_INT_FLED1_SHORT |
+ FLASH_INT_FLED2_SHORT;
+ } else {
+ fault_open_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_OPEN :
+ FLASH_INT_FLED2_OPEN;
+ fault_short_mask = (sub_led->fled_id == FLED1) ?
+ FLASH_INT_FLED1_SHORT :
+ FLASH_INT_FLED2_SHORT;
+ }
+
+ ret = regmap_read(rmap, MAX77693_LED_REG_FLASH_INT, &v);
+ if (ret < 0)
+ return ret;
+
+ if (v & fault_open_mask)
+ sub_led->flash_faults |= LED_FAULT_OVER_VOLTAGE;
+ if (v & fault_short_mask)
+ sub_led->flash_faults |= LED_FAULT_SHORT_CIRCUIT;
+ if (v & FLASH_INT_OVER_CURRENT)
+ sub_led->flash_faults |= LED_FAULT_OVER_CURRENT;
+
+ return 0;
+}
+
+static int max77693_setup(struct max77693_led_device *led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct regmap *rmap = led->regmap;
+ int i, first_led, last_led, ret;
+ u32 max_flash_curr[2];
+ u8 v;
+
+ /*
+ * Initialize only flash current. Torch current doesn't
+ * require initialization as ITORCH register is written with
+ * new value each time brightness_set op is called.
+ */
+ if (led->iout_joint) {
+ first_led = FLED1;
+ last_led = FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2];
+ } else {
+ first_led = max77693_fled_used(led, FLED1) ? FLED1 : FLED2;
+ last_led = max77693_fled_used(led, FLED2) ? FLED2 : FLED1;
+ max_flash_curr[FLED1] = led_cfg->iout_flash_max[FLED1];
+ max_flash_curr[FLED2] = led_cfg->iout_flash_max[FLED2];
+ }
+
+ for (i = first_led; i <= last_led; ++i) {
+ ret = max77693_set_flash_current(led, i,
+ max_flash_curr[i]);
+ if (ret < 0)
+ return ret;
+ }
+
+ v = TORCH_TMR_NO_TIMER | MAX77693_LED_TRIG_TYPE_LEVEL;
+ ret = regmap_write(rmap, MAX77693_LED_REG_ITORCHTIMER, v);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->low_vsys > 0)
+ v = max77693_led_vsys_to_reg(led_cfg->low_vsys) |
+ MAX_FLASH1_MAX_FL_EN;
+ else
+ v = 0;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH1, v);
+ if (ret < 0)
+ return ret;
+ ret = regmap_write(rmap, MAX77693_LED_REG_MAX_FLASH2, 0);
+ if (ret < 0)
+ return ret;
+
+ if (led_cfg->boost_mode == MAX77693_LED_BOOST_FIXED)
+ v = FLASH_BOOST_FIXED;
+ else
+ v = led_cfg->boost_mode | led_cfg->boost_mode << 1;
+
+ if (max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ v |= FLASH_BOOST_LEDNUM_2;
+
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_CNTL, v);
+ if (ret < 0)
+ return ret;
+
+ v = max77693_led_vout_to_reg(led_cfg->boost_vout);
+ ret = regmap_write(rmap, MAX77693_LED_REG_VOUT_FLASH1, v);
+ if (ret < 0)
+ return ret;
+
+ return max77693_set_mode_reg(led, MODE_OFF);
+}
+
+/* LED subsystem callbacks */
+static int max77693_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id, ret;
+
+ mutex_lock(&led->lock);
+
+ if (value == 0) {
+ ret = max77693_clear_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to clear torch mode (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_set_torch_current(led, fled_id, value * TORCH_IOUT_STEP);
+ if (ret < 0) {
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch current (%d)\n",
+ ret);
+ goto unlock;
+ }
+
+ ret = max77693_add_mode(led, MODE_TORCH(fled_id));
+ if (ret < 0)
+ dev_dbg(&led->pdev->dev,
+ "Failed to set torch mode (%d)\n",
+ ret);
+unlock:
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_brightness_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ mutex_lock(&led->lock);
+ ret = max77693_set_flash_current(led, sub_led->fled_id, brightness);
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_strobe_set(
+ struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (!state) {
+ ret = max77693_clear_mode(led, MODE_FLASH(fled_id));
+ goto unlock;
+ }
+
+ if (sub_led->flash_timeout != led->current_flash_timeout) {
+ ret = max77693_set_timeout(led, sub_led->flash_timeout);
+ if (ret < 0)
+ goto unlock;
+ }
+
+ led->strobing_sub_led_id = fled_id;
+
+ ret = max77693_add_mode(led, MODE_FLASH(fled_id));
+ if (ret < 0)
+ goto unlock;
+
+ ret = max77693_get_flash_faults(sub_led);
+
+unlock:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static int max77693_led_flash_fault_get(
+ struct led_classdev_flash *fled_cdev,
+ u32 *fault)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+
+ *fault = sub_led->flash_faults;
+
+ return 0;
+}
+
+static int max77693_led_flash_strobe_get(
+ struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int ret;
+
+ if (!state)
+ return -EINVAL;
+
+ mutex_lock(&led->lock);
+
+ ret = max77693_get_strobe_status(led, state);
+
+ *state = !!(*state && (led->strobing_sub_led_id == sub_led->fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static int max77693_led_flash_timeout_set(
+ struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct max77693_sub_led *sub_led = flcdev_to_sub_led(fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+
+ mutex_lock(&led->lock);
+ sub_led->flash_timeout = timeout;
+ mutex_unlock(&led->lock);
+
+ return 0;
+}
+
+static int max77693_led_parse_dt(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ struct device *dev = &led->pdev->dev;
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+ struct device_node *node = dev_of_node(dev);
+ struct property *prop;
+ u32 led_sources[2];
+ int i, ret, fled_id;
+
+ of_property_read_u32(node, "maxim,boost-mode", &cfg->boost_mode);
+ 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_scoped(node, child_node) {
+ prop = of_find_property(child_node, "led-sources", NULL);
+ if (prop) {
+ const __be32 *srcs = NULL;
+
+ for (i = 0; i < ARRAY_SIZE(led_sources); ++i) {
+ srcs = of_prop_next_u32(prop, srcs,
+ &led_sources[i]);
+ if (!srcs)
+ break;
+ }
+ } else {
+ dev_err(dev,
+ "led-sources DT property missing\n");
+ return -EINVAL;
+ }
+
+ if (i == 2) {
+ fled_id = FLED1;
+ led->fled_mask = FLED1_IOUT | FLED2_IOUT;
+ } else if (led_sources[0] == FLED1) {
+ fled_id = FLED1;
+ led->fled_mask |= FLED1_IOUT;
+ } else if (led_sources[0] == FLED2) {
+ fled_id = FLED2;
+ led->fled_mask |= FLED2_IOUT;
+ } else {
+ dev_err(dev,
+ "Wrong led-sources DT property value.\n");
+ return -EINVAL;
+ }
+
+ if (sub_nodes[fled_id]) {
+ dev_err(dev,
+ "Conflicting \"led-sources\" DT properties\n");
+ return -EINVAL;
+ }
+
+ sub_nodes[fled_id] = of_node_get(child_node);
+ sub_leds[fled_id].fled_id = fled_id;
+
+ cfg->label[fled_id] =
+ of_get_property(child_node, "label", NULL) ? :
+ child_node->name;
+
+ ret = of_property_read_u32(child_node, "led-max-microamp",
+ &cfg->iout_torch_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_torch_max[fled_id] = TORCH_IOUT_MIN;
+ dev_warn(dev, "led-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-microamp",
+ &cfg->iout_flash_max[fled_id]);
+ if (ret < 0) {
+ cfg->iout_flash_max[fled_id] = FLASH_IOUT_MIN;
+ dev_warn(dev,
+ "flash-max-microamp DT property missing\n");
+ }
+
+ ret = of_property_read_u32(child_node, "flash-max-timeout-us",
+ &cfg->flash_timeout_max[fled_id]);
+ if (ret < 0) {
+ cfg->flash_timeout_max[fled_id] = FLASH_TIMEOUT_MIN;
+ dev_warn(dev,
+ "flash-max-timeout-us DT property missing\n");
+ }
+
+ if (++cfg->num_leds == 2 ||
+ (max77693_fled_used(led, FLED1) &&
+ max77693_fled_used(led, FLED2)))
+ break;
+ }
+
+ if (cfg->num_leds == 0) {
+ dev_err(dev, "No DT child node found for connected LED(s).\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void clamp_align(u32 *v, u32 min, u32 max, u32 step)
+{
+ *v = clamp_val(*v, min, max);
+ if (step > 1)
+ *v = (*v - min) / step * step + min;
+}
+
+static void max77693_align_iout_current(struct max77693_led_device *led,
+ u32 *iout, u32 min, u32 max, u32 step)
+{
+ int i;
+
+ if (led->iout_joint) {
+ if (iout[FLED1] > min) {
+ iout[FLED1] /= 2;
+ iout[FLED2] = iout[FLED1];
+ } else {
+ iout[FLED1] = min;
+ iout[FLED2] = 0;
+ return;
+ }
+ }
+
+ for (i = FLED1; i <= FLED2; ++i)
+ if (max77693_fled_used(led, i))
+ clamp_align(&iout[i], min, max, step);
+ else
+ iout[i] = 0;
+}
+
+static void max77693_led_validate_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg)
+{
+ u32 flash_iout_max = cfg->boost_mode ? FLASH_IOUT_MAX_2LEDS :
+ FLASH_IOUT_MAX_1LED;
+ int i;
+
+ if (cfg->num_leds == 1 &&
+ max77693_fled_used(led, FLED1) && max77693_fled_used(led, FLED2))
+ led->iout_joint = true;
+
+ cfg->boost_mode = clamp_val(cfg->boost_mode, MAX77693_LED_BOOST_NONE,
+ MAX77693_LED_BOOST_FIXED);
+
+ /* Boost must be enabled if both current outputs are used */
+ if ((cfg->boost_mode == MAX77693_LED_BOOST_NONE) && led->iout_joint)
+ cfg->boost_mode = MAX77693_LED_BOOST_FIXED;
+
+ max77693_align_iout_current(led, cfg->iout_torch_max,
+ TORCH_IOUT_MIN, TORCH_IOUT_MAX, TORCH_IOUT_STEP);
+
+ max77693_align_iout_current(led, cfg->iout_flash_max,
+ FLASH_IOUT_MIN, flash_iout_max, FLASH_IOUT_STEP);
+
+ for (i = 0; i < ARRAY_SIZE(cfg->flash_timeout_max); ++i)
+ clamp_align(&cfg->flash_timeout_max[i], FLASH_TIMEOUT_MIN,
+ FLASH_TIMEOUT_MAX, FLASH_TIMEOUT_STEP);
+
+ clamp_align(&cfg->boost_vout, FLASH_VOUT_MIN, FLASH_VOUT_MAX,
+ FLASH_VOUT_STEP);
+
+ if (cfg->low_vsys)
+ clamp_align(&cfg->low_vsys, MAX_FLASH1_VSYS_MIN,
+ MAX_FLASH1_VSYS_MAX, MAX_FLASH1_VSYS_STEP);
+}
+
+static int max77693_led_get_configuration(struct max77693_led_device *led,
+ struct max77693_led_config_data *cfg,
+ struct device_node **sub_nodes)
+{
+ int ret;
+
+ ret = max77693_led_parse_dt(led, cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ max77693_led_validate_configuration(led, cfg);
+
+ memcpy(led->iout_torch_max, cfg->iout_torch_max,
+ sizeof(led->iout_torch_max));
+ memcpy(led->iout_flash_max, cfg->iout_flash_max,
+ sizeof(led->iout_flash_max));
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = max77693_led_flash_brightness_set,
+ .strobe_set = max77693_led_flash_strobe_set,
+ .strobe_get = max77693_led_flash_strobe_get,
+ .timeout_set = max77693_led_flash_timeout_set,
+ .fault_get = max77693_led_flash_fault_get,
+};
+
+static void max77693_init_flash_settings(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_flash_setting *setting;
+
+ /* Init flash intensity setting */
+ setting = &fled_cdev->brightness;
+ setting->min = FLASH_IOUT_MIN;
+ setting->max = led->iout_joint ?
+ led_cfg->iout_flash_max[FLED1] +
+ led_cfg->iout_flash_max[FLED2] :
+ led_cfg->iout_flash_max[fled_id];
+ setting->step = FLASH_IOUT_STEP;
+ setting->val = setting->max;
+
+ /* Init flash timeout setting */
+ setting = &fled_cdev->timeout;
+ setting->min = FLASH_TIMEOUT_MIN;
+ setting->max = led_cfg->flash_timeout_max[fled_id];
+ setting->step = FLASH_TIMEOUT_STEP;
+ setting->val = setting->max;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+
+static int max77693_led_external_strobe_set(
+ struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct max77693_sub_led *sub_led =
+ flcdev_to_sub_led(v4l2_flash->fled_cdev);
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ int ret;
+
+ mutex_lock(&led->lock);
+
+ if (enable)
+ ret = max77693_add_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+ else
+ ret = max77693_clear_mode(led, MODE_FLASH_EXTERNAL(fled_id));
+
+ mutex_unlock(&led->lock);
+
+ return ret;
+}
+
+static void max77693_init_v4l2_flash_config(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct device *dev = &led->pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct i2c_client *i2c = iodev->i2c;
+ struct led_flash_setting *s;
+
+ snprintf(v4l2_sd_cfg->dev_name, sizeof(v4l2_sd_cfg->dev_name),
+ "%s %d-%04x", sub_led->fled_cdev.led_cdev.name,
+ i2c_adapter_id(i2c->adapter), i2c->addr);
+
+ s = &v4l2_sd_cfg->intensity;
+ s->min = TORCH_IOUT_MIN;
+ s->max = sub_led->fled_cdev.led_cdev.max_brightness * TORCH_IOUT_STEP;
+ s->step = TORCH_IOUT_STEP;
+ s->val = s->max;
+
+ /* Init flash faults config */
+ v4l2_sd_cfg->flash_faults = LED_FAULT_OVER_VOLTAGE |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_OVER_CURRENT;
+
+ v4l2_sd_cfg->has_external_strobe = true;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = max77693_led_external_strobe_set,
+};
+#else
+static inline void max77693_init_v4l2_flash_config(
+ struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+static const struct v4l2_flash_ops v4l2_flash_ops;
+#endif
+
+static void max77693_init_fled_cdev(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ int fled_id = sub_led->fled_id;
+ struct led_classdev_flash *fled_cdev;
+ struct led_classdev *led_cdev;
+
+ /* Initialize LED Flash class device */
+ fled_cdev = &sub_led->fled_cdev;
+ fled_cdev->ops = &flash_ops;
+ led_cdev = &fled_cdev->led_cdev;
+
+ led_cdev->name = led_cfg->label[fled_id];
+
+ led_cdev->brightness_set_blocking = max77693_led_brightness_set;
+ led_cdev->max_brightness = (led->iout_joint ?
+ led_cfg->iout_torch_max[FLED1] +
+ led_cfg->iout_torch_max[FLED2] :
+ led_cfg->iout_torch_max[fled_id]) /
+ TORCH_IOUT_STEP;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ max77693_init_flash_settings(sub_led, led_cfg);
+
+ /* Init flash timeout cache */
+ sub_led->flash_timeout = fled_cdev->timeout.val;
+}
+
+static int max77693_register_led(struct max77693_sub_led *sub_led,
+ struct max77693_led_config_data *led_cfg,
+ struct device_node *sub_node)
+{
+ struct max77693_led_device *led = sub_led_to_led(sub_led);
+ struct led_classdev_flash *fled_cdev = &sub_led->fled_cdev;
+ struct device *dev = &led->pdev->dev;
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ /* Register in the LED subsystem */
+ ret = led_classdev_flash_register(dev, fled_cdev);
+ if (ret < 0)
+ return ret;
+
+ max77693_init_v4l2_flash_config(sub_led, led_cfg, &v4l2_sd_cfg);
+
+ /* Register in the V4L2 subsystem. */
+ sub_led->v4l2_flash = v4l2_flash_init(dev, of_fwnode_handle(sub_node),
+ fled_cdev, &v4l2_flash_ops,
+ &v4l2_sd_cfg);
+ if (IS_ERR(sub_led->v4l2_flash)) {
+ ret = PTR_ERR(sub_led->v4l2_flash);
+ goto err_v4l2_flash_init;
+ }
+
+ return 0;
+
+err_v4l2_flash_init:
+ led_classdev_flash_unregister(fled_cdev);
+ return ret;
+}
+
+static int max77693_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct max77693_dev *iodev = dev_get_drvdata(dev->parent);
+ struct max77693_led_device *led;
+ struct max77693_sub_led *sub_leds;
+ struct device_node *sub_nodes[2] = {};
+ struct max77693_led_config_data led_cfg = {};
+ int init_fled_cdev[2], i, ret;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pdev = pdev;
+ led->regmap = iodev->regmap;
+ led->allowed_modes = MODE_FLASH_MASK;
+ sub_leds = led->sub_leds;
+
+ platform_set_drvdata(pdev, led);
+ ret = max77693_led_get_configuration(led, &led_cfg, sub_nodes);
+ if (ret < 0)
+ return ret;
+
+ ret = max77693_setup(led, &led_cfg);
+ if (ret < 0)
+ goto err_setup;
+
+ mutex_init(&led->lock);
+
+ init_fled_cdev[FLED1] =
+ led->iout_joint || max77693_fled_used(led, FLED1);
+ init_fled_cdev[FLED2] =
+ !led->iout_joint && max77693_fled_used(led, FLED2);
+
+ for (i = FLED1; i <= FLED2; ++i) {
+ if (!init_fled_cdev[i])
+ continue;
+
+ /* Initialize LED Flash class device */
+ max77693_init_fled_cdev(&sub_leds[i], &led_cfg);
+
+ /*
+ * Register LED Flash class device and corresponding
+ * V4L2 Flash device.
+ */
+ ret = max77693_register_led(&sub_leds[i], &led_cfg,
+ sub_nodes[i]);
+ if (ret < 0) {
+ /*
+ * At this moment FLED1 might have been already
+ * registered and it needs to be released.
+ */
+ if (i == FLED2)
+ goto err_register_led2;
+ else
+ goto err_register_led1;
+ }
+ of_node_put(sub_nodes[i]);
+ sub_nodes[i] = NULL;
+ }
+
+ return 0;
+
+err_register_led2:
+ /* It is possible than only FLED2 was to be registered */
+ if (!init_fled_cdev[FLED1])
+ goto err_register_led1;
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+err_register_led1:
+ mutex_destroy(&led->lock);
+
+err_setup:
+ for (i = FLED1; i <= FLED2; i++)
+ of_node_put(sub_nodes[i]);
+ return ret;
+}
+
+static void max77693_led_remove(struct platform_device *pdev)
+{
+ struct max77693_led_device *led = platform_get_drvdata(pdev);
+ struct max77693_sub_led *sub_leds = led->sub_leds;
+
+ if (led->iout_joint || max77693_fled_used(led, FLED1)) {
+ v4l2_flash_release(sub_leds[FLED1].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED1].fled_cdev);
+ }
+
+ if (!led->iout_joint && max77693_fled_used(led, FLED2)) {
+ v4l2_flash_release(sub_leds[FLED2].v4l2_flash);
+ led_classdev_flash_unregister(&sub_leds[FLED2].fled_cdev);
+ }
+
+ mutex_destroy(&led->lock);
+}
+
+static const struct of_device_id max77693_led_dt_match[] = {
+ { .compatible = "maxim,max77693-led" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, max77693_led_dt_match);
+
+static struct platform_driver max77693_led_driver = {
+ .probe = max77693_led_probe,
+ .remove = max77693_led_remove,
+ .driver = {
+ .name = "max77693-led",
+ .of_match_table = max77693_led_dt_match,
+ },
+};
+
+module_platform_driver(max77693_led_driver);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_AUTHOR("Andrzej Hajda <a.hajda@samsung.com>");
+MODULE_DESCRIPTION("Maxim MAX77693 led flash driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-mt6360.c b/drivers/leds/flash/leds-mt6360.c
new file mode 100644
index 000000000000..462a902f54e0
--- /dev/null
+++ b/drivers/leds/flash/leds-mt6360.c
@@ -0,0 +1,895 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+enum {
+ MT6360_LED_ISNK1 = 0,
+ MT6360_LED_ISNK2,
+ MT6360_LED_ISNK3,
+ MT6360_LED_ISNKML,
+ MT6360_LED_FLASH1,
+ MT6360_LED_FLASH2,
+ MT6360_MAX_LEDS
+};
+
+#define MT6360_REG_RGBEN 0x380
+#define MT6360_REG_ISNK(_led_no) (0x381 + (_led_no))
+#define MT6360_ISNK_ENMASK(_led_no) BIT(7 - (_led_no))
+#define MT6360_ISNK_MASK GENMASK(4, 0)
+#define MT6360_CHRINDSEL_MASK BIT(3)
+
+/* Virtual definition for multicolor */
+#define MT6360_VIRTUAL_MULTICOLOR (MT6360_MAX_LEDS + 1)
+#define MULTICOLOR_NUM_CHANNELS 3
+
+#define MT6360_REG_FLEDEN 0x37E
+#define MT6360_REG_STRBTO 0x373
+#define MT6360_REG_FLEDBASE(_id) (0x372 + 4 * (_id - MT6360_LED_FLASH1))
+#define MT6360_REG_FLEDISTRB(_id) (MT6360_REG_FLEDBASE(_id) + 2)
+#define MT6360_REG_FLEDITOR(_id) (MT6360_REG_FLEDBASE(_id) + 3)
+#define MT6360_REG_CHGSTAT2 0x3E1
+#define MT6360_REG_FLEDSTAT1 0x3E9
+#define MT6360_ITORCH_MASK GENMASK(4, 0)
+#define MT6360_ISTROBE_MASK GENMASK(6, 0)
+#define MT6360_STRBTO_MASK GENMASK(6, 0)
+#define MT6360_TORCHEN_MASK BIT(3)
+#define MT6360_STROBEN_MASK BIT(2)
+#define MT6360_FLCSEN_MASK(_id) BIT(MT6360_LED_FLASH2 - _id)
+#define MT6360_FLEDCHGVINOVP_MASK BIT(3)
+#define MT6360_FLED1STRBTO_MASK BIT(11)
+#define MT6360_FLED2STRBTO_MASK BIT(10)
+#define MT6360_FLED1STRB_MASK BIT(9)
+#define MT6360_FLED2STRB_MASK BIT(8)
+#define MT6360_FLED1SHORT_MASK BIT(7)
+#define MT6360_FLED2SHORT_MASK BIT(6)
+#define MT6360_FLEDLVF_MASK BIT(3)
+
+#define MT6360_ISNKRGB_STEPUA 2000
+#define MT6360_ISNKRGB_MAXUA 24000
+#define MT6360_ISNKML_STEPUA 5000
+#define MT6360_ISNKML_MAXUA 150000
+
+#define MT6360_ITORCH_MINUA 25000
+#define MT6360_ITORCH_STEPUA 12500
+#define MT6360_ITORCH_MAXUA 400000
+#define MT6360_ISTRB_MINUA 50000
+#define MT6360_ISTRB_STEPUA 12500
+#define MT6360_ISTRB_MAXUA 1500000
+#define MT6360_STRBTO_MINUS 64000
+#define MT6360_STRBTO_STEPUS 32000
+#define MT6360_STRBTO_MAXUS 2432000
+
+struct mt6360_led {
+ union {
+ struct led_classdev isnk;
+ struct led_classdev_mc mc;
+ struct led_classdev_flash flash;
+ };
+ struct v4l2_flash *v4l2_flash;
+ struct mt6360_priv *priv;
+ u32 led_no;
+ enum led_default_state default_state;
+};
+
+struct mt6360_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+ unsigned int leds_active;
+ unsigned int leds_count;
+ struct mt6360_led leds[] __counted_by(leds_count);
+};
+
+static int mt6360_mc_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+ struct mt6360_led *led = container_of(mccdev, struct mt6360_led, mc);
+ struct mt6360_priv *priv = led->priv;
+ u32 real_bright, enable_mask = 0, enable = 0;
+ int i, ret;
+
+ mutex_lock(&priv->lock);
+
+ led_mc_calc_color_components(mccdev, level);
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ struct mc_subled *subled = mccdev->subled_info + i;
+
+ real_bright = min(lcdev->max_brightness, subled->brightness);
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(i),
+ MT6360_ISNK_MASK, real_bright);
+ if (ret)
+ goto out;
+
+ enable_mask |= MT6360_ISNK_ENMASK(subled->channel);
+ if (real_bright)
+ enable |= MT6360_ISNK_ENMASK(subled->channel);
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN, enable_mask,
+ enable);
+
+out:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_isnk_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct mt6360_led *led = container_of(lcdev, struct mt6360_led, isnk);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_ISNK_ENMASK(led->led_no);
+ u32 val = level ? MT6360_ISNK_ENMASK(led->led_no) : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_ISNK(led->led_no),
+ MT6360_ISNK_MASK, level);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN, enable_mask,
+ val);
+
+out:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_torch_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct mt6360_led *led =
+ container_of(lcdev, struct mt6360_led, flash.led_cdev);
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = level ? MT6360_FLCSEN_MASK(led->led_no) : 0;
+ u32 prev = priv->fled_torch_used, curr;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * Only one set of flash control logic, use the flag to avoid strobe is
+ * currently used.
+ */
+ if (priv->fled_strobe_used) {
+ dev_warn(lcdev->dev, "Please disable strobe first [%d]\n",
+ priv->fled_strobe_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (level)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6360_TORCHEN_MASK;
+
+ if (level) {
+ ret = regmap_update_bits(priv->regmap,
+ MT6360_REG_FLEDITOR(led->led_no),
+ MT6360_ITORCH_MASK, level - 1);
+ if (ret)
+ goto unlock;
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
+ val);
+ if (ret)
+ goto unlock;
+
+ priv->fled_torch_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_flash_brightness_set(struct led_classdev_flash *fl_cdev,
+ u32 brightness)
+{
+ /*
+ * Due to the current spike when turning on flash, let brightness to be
+ * kept by framework.
+ * This empty function is used to prevent led_classdev_flash register
+ * ops check failure.
+ */
+ return 0;
+}
+
+static int _mt6360_flash_brightness_set(struct led_classdev_flash *fl_cdev,
+ u32 brightness)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+
+ return regmap_update_bits(priv->regmap,
+ MT6360_REG_FLEDISTRB(led->led_no),
+ MT6360_ISTROBE_MASK, val);
+}
+
+static int mt6360_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_classdev *lcdev = &fl_cdev->led_cdev;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ 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 = 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
+ */
+ if (priv->fled_torch_used) {
+ dev_warn(lcdev->dev, "Please disable torch first [0x%x]\n",
+ priv->fled_torch_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (state)
+ curr = prev | BIT(led->led_no);
+ else
+ curr = prev & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6360_STROBEN_MASK;
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, enable_mask,
+ val);
+ if (ret) {
+ dev_err(lcdev->dev, "[%d] control current source %d fail\n",
+ led->led_no, state);
+ goto unlock;
+ }
+
+ /*
+ * If the flash need to be on, config the flash current ramping up to
+ * the setting value.
+ * Else, always recover back to the minimum one
+ */
+ ret = _mt6360_flash_brightness_set(fl_cdev, state ? s->val : s->min);
+ if (ret)
+ goto unlock;
+
+ /*
+ * For the flash turn on/off, HW rampping up/down time is 5ms/500us,
+ * respectively.
+ */
+ if (!prev && curr)
+ usleep_range(5000, 6000);
+ else if (prev && !curr)
+ udelay(500);
+
+ priv->fled_strobe_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6360_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+
+ mutex_lock(&priv->lock);
+ *state = !!(priv->fled_strobe_used & BIT(led->led_no));
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int mt6360_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_STRBTO,
+ MT6360_STRBTO_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6360_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct mt6360_led *led =
+ container_of(fl_cdev, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ u16 fled_stat;
+ unsigned int chg_stat, strobe_timeout_mask, fled_short_mask;
+ u32 rfault = 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_read(priv->regmap, MT6360_REG_CHGSTAT2, &chg_stat);
+ if (ret)
+ goto unlock;
+
+ ret = regmap_raw_read(priv->regmap, MT6360_REG_FLEDSTAT1, &fled_stat,
+ sizeof(fled_stat));
+ if (ret)
+ goto unlock;
+
+ if (led->led_no == MT6360_LED_FLASH1) {
+ strobe_timeout_mask = MT6360_FLED1STRBTO_MASK;
+ fled_short_mask = MT6360_FLED1SHORT_MASK;
+ } else {
+ strobe_timeout_mask = MT6360_FLED2STRBTO_MASK;
+ fled_short_mask = MT6360_FLED2SHORT_MASK;
+ }
+
+ if (chg_stat & MT6360_FLEDCHGVINOVP_MASK)
+ rfault |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (fled_stat & strobe_timeout_mask)
+ rfault |= LED_FAULT_TIMEOUT;
+
+ if (fled_stat & fled_short_mask)
+ rfault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (fled_stat & MT6360_FLEDLVF_MASK)
+ rfault |= LED_FAULT_UNDER_VOLTAGE;
+
+ *fault = rfault;
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct led_flash_ops mt6360_flash_ops = {
+ .flash_brightness_set = mt6360_flash_brightness_set,
+ .strobe_set = mt6360_strobe_set,
+ .strobe_get = mt6360_strobe_get,
+ .timeout_set = mt6360_timeout_set,
+ .fault_get = mt6360_fault_get,
+};
+
+static int mt6360_isnk_init_default_state(struct mt6360_led *led)
+{
+ struct mt6360_priv *priv = led->priv;
+ unsigned int regval;
+ u32 level;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_ISNK(led->led_no), &regval);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ISNK_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_RGBEN, &regval);
+ if (ret)
+ return ret;
+
+ if (!(regval & MT6360_ISNK_ENMASK(led->led_no)))
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led->isnk.brightness = led->isnk.max_brightness;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ led->isnk.brightness = min(level, led->isnk.max_brightness);
+ break;
+ default:
+ led->isnk.brightness = LED_OFF;
+ }
+
+ return mt6360_isnk_brightness_set(&led->isnk, led->isnk.brightness);
+}
+
+static int mt6360_flash_init_default_state(struct mt6360_led *led)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct mt6360_priv *priv = led->priv;
+ u32 enable_mask = MT6360_TORCHEN_MASK | MT6360_FLCSEN_MASK(led->led_no);
+ u32 level;
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDITOR(led->led_no),
+ &regval);
+ if (ret)
+ return ret;
+ level = regval & MT6360_ITORCH_MASK;
+
+ ret = regmap_read(priv->regmap, MT6360_REG_FLEDEN, &regval);
+ if (ret)
+ return ret;
+
+ if ((regval & enable_mask) == enable_mask)
+ level += 1;
+ else
+ level = LED_OFF;
+
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ flash->led_cdev.brightness = flash->led_cdev.max_brightness;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ flash->led_cdev.brightness =
+ min(level, flash->led_cdev.max_brightness);
+ break;
+ default:
+ flash->led_cdev.brightness = LED_OFF;
+ }
+
+ return mt6360_torch_brightness_set(&flash->led_cdev,
+ flash->led_cdev.brightness);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int mt6360_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct mt6360_led *led = container_of(flash, struct mt6360_led, flash);
+ struct mt6360_priv *priv = led->priv;
+ u32 mask = MT6360_FLCSEN_MASK(led->led_no);
+ u32 val = enable ? mask : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_FLEDEN, mask, val);
+ if (ret)
+ goto unlock;
+
+ if (enable)
+ priv->fled_strobe_used |= BIT(led->led_no);
+ else
+ priv->fled_strobe_used &= ~BIT(led->led_no);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = mt6360_flash_external_strobe_set,
+};
+
+static void mt6360_init_v4l2_flash_config(struct mt6360_led *led,
+ struct v4l2_flash_config *config)
+{
+ struct led_classdev *lcdev;
+ struct led_flash_setting *s = &config->intensity;
+
+ lcdev = &led->flash.led_cdev;
+
+ s->min = MT6360_ITORCH_MINUA;
+ s->step = MT6360_ITORCH_STEPUA;
+ s->val = s->max = s->min + (lcdev->max_brightness - 1) * s->step;
+
+ config->has_external_strobe = 1;
+ strscpy(config->dev_name, lcdev->dev->kobj.name,
+ sizeof(config->dev_name));
+
+ config->flash_faults = LED_FAULT_SHORT_CIRCUIT | LED_FAULT_TIMEOUT |
+ LED_FAULT_INPUT_VOLTAGE |
+ LED_FAULT_UNDER_VOLTAGE;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+static void mt6360_init_v4l2_flash_config(struct mt6360_led *led,
+ struct v4l2_flash_config *config)
+{
+}
+#endif
+
+static int mt6360_led_register(struct device *parent, struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct mt6360_priv *priv = led->priv;
+ struct v4l2_flash_config v4l2_config = {0};
+ int ret;
+
+ if ((led->led_no == MT6360_LED_ISNK1 ||
+ led->led_no == MT6360_VIRTUAL_MULTICOLOR) &&
+ (priv->leds_active & BIT(MT6360_LED_ISNK1))) {
+ /*
+ * Change isink1 to SW control mode, disconnect it with
+ * charger state
+ */
+ ret = regmap_update_bits(priv->regmap, MT6360_REG_RGBEN,
+ MT6360_CHRINDSEL_MASK,
+ MT6360_CHRINDSEL_MASK);
+ if (ret) {
+ dev_err(parent, "Failed to config ISNK1 to SW mode\n");
+ return ret;
+ }
+ }
+
+ switch (led->led_no) {
+ case MT6360_VIRTUAL_MULTICOLOR:
+ ret = mt6360_mc_brightness_set(&led->mc.led_cdev, LED_OFF);
+ if (ret) {
+ dev_err(parent,
+ "Failed to init multicolor brightness\n");
+ return ret;
+ }
+
+ ret = devm_led_classdev_multicolor_register_ext(parent,
+ &led->mc, init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register multicolor\n");
+ return ret;
+ }
+ break;
+ case MT6360_LED_ISNK1 ... MT6360_LED_ISNKML:
+ ret = mt6360_isnk_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d isnk state\n",
+ led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_register_ext(parent, &led->isnk,
+ init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register isink %d\n",
+ led->led_no);
+ return ret;
+ }
+ break;
+ default:
+ ret = mt6360_flash_init_default_state(led);
+ if (ret) {
+ dev_err(parent, "Failed to init %d flash state\n",
+ led->led_no);
+ return ret;
+ }
+
+ ret = devm_led_classdev_flash_register_ext(parent, &led->flash,
+ init_data);
+ if (ret) {
+ dev_err(parent, "Couldn't register flash %d\n",
+ led->led_no);
+ return ret;
+ }
+
+ mt6360_init_v4l2_flash_config(led, &v4l2_config);
+ led->v4l2_flash = v4l2_flash_init(parent, init_data->fwnode,
+ &led->flash,
+ &v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(led->v4l2_flash)) {
+ dev_err(parent, "Failed to register %d v4l2 sd\n",
+ led->led_no);
+ return PTR_ERR(led->v4l2_flash);
+ }
+ }
+
+ return 0;
+}
+
+static u32 clamp_align(u32 val, u32 min, u32 max, u32 step)
+{
+ u32 retval;
+
+ retval = clamp_val(val, min, max);
+ if (step > 1)
+ retval = rounddown(retval - min, step) + min;
+
+ return retval;
+}
+
+static int mt6360_init_isnk_properties(struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct led_classdev *lcdev;
+ struct mt6360_priv *priv = led->priv;
+ struct fwnode_handle *child;
+ u32 step_uA = MT6360_ISNKRGB_STEPUA, max_uA = MT6360_ISNKRGB_MAXUA;
+ u32 val;
+ int num_color = 0, ret;
+
+ if (led->led_no == MT6360_VIRTUAL_MULTICOLOR) {
+ struct mc_subled *sub_led;
+
+ sub_led = devm_kzalloc(priv->dev,
+ sizeof(*sub_led) * MULTICOLOR_NUM_CHANNELS, GFP_KERNEL);
+ if (!sub_led)
+ return -ENOMEM;
+
+ fwnode_for_each_child_node(init_data->fwnode, child) {
+ u32 reg, color;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg > MT6360_LED_ISNK3 ||
+ 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;
+ }
+
+ priv->leds_active |= BIT(reg);
+ sub_led[num_color].color_index = color;
+ sub_led[num_color].channel = reg;
+ num_color++;
+ }
+
+ if (num_color < 2) {
+ dev_err(priv->dev,
+ "Multicolor must include 2 or more led channel\n");
+ return -EINVAL;
+ }
+
+ led->mc.num_colors = num_color;
+ led->mc.subled_info = sub_led;
+
+ lcdev = &led->mc.led_cdev;
+ lcdev->brightness_set_blocking = mt6360_mc_brightness_set;
+ } else {
+ if (led->led_no == MT6360_LED_ISNKML) {
+ step_uA = MT6360_ISNKML_STEPUA;
+ max_uA = MT6360_ISNKML_MAXUA;
+ }
+
+ lcdev = &led->isnk;
+ lcdev->brightness_set_blocking = mt6360_isnk_brightness_set;
+ }
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified led-max-microamp, config to the minimum\n");
+ val = step_uA;
+ } else
+ val = clamp_align(val, 0, max_uA, step_uA);
+
+ lcdev->max_brightness = val / step_uA;
+
+ fwnode_property_read_string(init_data->fwnode, "linux,default-trigger",
+ &lcdev->default_trigger);
+
+ return 0;
+}
+
+static int mt6360_init_flash_properties(struct mt6360_led *led,
+ struct led_init_data *init_data)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct mt6360_priv *priv = led->priv;
+ struct led_flash_setting *s;
+ u32 val;
+ int ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified led-max-microamp, config to the minimum\n");
+ val = MT6360_ITORCH_MINUA;
+ } else
+ val = clamp_align(val, MT6360_ITORCH_MINUA, MT6360_ITORCH_MAXUA,
+ MT6360_ITORCH_STEPUA);
+
+ lcdev->max_brightness =
+ (val - MT6360_ITORCH_MINUA) / MT6360_ITORCH_STEPUA + 1;
+ lcdev->brightness_set_blocking = mt6360_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "flash-max-microamp",
+ &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified flash-max-microamp, config to the minimum\n");
+ val = MT6360_ISTRB_MINUA;
+ } else
+ val = clamp_align(val, MT6360_ISTRB_MINUA, MT6360_ISTRB_MAXUA,
+ MT6360_ISTRB_STEPUA);
+
+ s = &flash->brightness;
+ s->min = MT6360_ISTRB_MINUA;
+ s->step = MT6360_ISTRB_STEPUA;
+ s->val = s->max = val;
+
+ /*
+ * Always configure as min level when off to prevent flash current
+ * spike.
+ */
+ ret = _mt6360_flash_brightness_set(flash, s->min);
+ if (ret)
+ return ret;
+
+ ret = fwnode_property_read_u32(init_data->fwnode,
+ "flash-max-timeout-us", &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "Not specified flash-max-timeout-us, config to the minimum\n");
+ val = MT6360_STRBTO_MINUS;
+ } else
+ val = clamp_align(val, MT6360_STRBTO_MINUS, MT6360_STRBTO_MAXUS,
+ MT6360_STRBTO_STEPUS);
+
+ s = &flash->timeout;
+ s->min = MT6360_STRBTO_MINUS;
+ s->step = MT6360_STRBTO_STEPUS;
+ s->val = s->max = val;
+
+ flash->ops = &mt6360_flash_ops;
+
+ return 0;
+}
+
+static void mt6360_v4l2_flash_release(struct mt6360_priv *priv)
+{
+ int i;
+
+ for (i = 0; i < priv->leds_count; i++) {
+ struct mt6360_led *led = priv->leds + i;
+
+ if (led->v4l2_flash)
+ v4l2_flash_release(led->v4l2_flash);
+ }
+}
+
+static int mt6360_led_probe(struct platform_device *pdev)
+{
+ struct mt6360_priv *priv;
+ size_t count;
+ int i = 0, ret;
+
+ count = device_get_child_node_count(&pdev->dev);
+ if (!count || count > MT6360_MAX_LEDS) {
+ dev_err(&pdev->dev,
+ "No child node or node count over max led number %zu\n",
+ count);
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev,
+ struct_size(priv, leds, count), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->leds_count = count;
+ priv->dev = &pdev->dev;
+ mutex_init(&priv->lock);
+
+ priv->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!priv->regmap) {
+ dev_err(&pdev->dev, "Failed to get parent regmap\n");
+ return -ENODEV;
+ }
+
+ 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;
+
+ ret = fwnode_property_read_u32(child, "color", &led_color);
+ if (ret)
+ goto out_flash_release;
+
+ if (led_color == LED_COLOR_ID_RGB ||
+ led_color == LED_COLOR_ID_MULTI)
+ reg = MT6360_VIRTUAL_MULTICOLOR;
+ else {
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ goto out_flash_release;
+
+ if (reg >= MT6360_MAX_LEDS) {
+ ret = -EINVAL;
+ goto out_flash_release;
+ }
+ }
+
+ if (priv->leds_active & BIT(reg)) {
+ ret = -EINVAL;
+ goto out_flash_release;
+ }
+ priv->leds_active |= BIT(reg);
+
+ led->led_no = reg;
+ led->priv = priv;
+ led->default_state = led_init_default_state_get(child);
+
+ if (reg == MT6360_VIRTUAL_MULTICOLOR ||
+ reg <= MT6360_LED_ISNKML)
+ ret = mt6360_init_isnk_properties(led, &init_data);
+ else
+ ret = mt6360_init_flash_properties(led, &init_data);
+
+ if (ret)
+ goto out_flash_release;
+
+ ret = mt6360_led_register(&pdev->dev, led, &init_data);
+ if (ret)
+ goto out_flash_release;
+
+ i++;
+ }
+
+ platform_set_drvdata(pdev, priv);
+ return 0;
+
+out_flash_release:
+ mt6360_v4l2_flash_release(priv);
+ return ret;
+}
+
+static void mt6360_led_remove(struct platform_device *pdev)
+{
+ struct mt6360_priv *priv = platform_get_drvdata(pdev);
+
+ mt6360_v4l2_flash_release(priv);
+}
+
+static const struct of_device_id __maybe_unused mt6360_led_of_id[] = {
+ { .compatible = "mediatek,mt6360-led", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mt6360_led_of_id);
+
+static struct platform_driver mt6360_led_driver = {
+ .driver = {
+ .name = "mt6360-led",
+ .of_match_table = mt6360_led_of_id,
+ },
+ .probe = mt6360_led_probe,
+ .remove = mt6360_led_remove,
+};
+module_platform_driver(mt6360_led_driver);
+
+MODULE_AUTHOR("Gene Chen <gene_chen@richtek.com>");
+MODULE_DESCRIPTION("MT6360 LED Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-mt6370-flash.c b/drivers/leds/flash/leds-mt6370-flash.c
new file mode 100644
index 000000000000..dbdbe92309db
--- /dev/null
+++ b/drivers/leds/flash/leds-mt6370-flash.c
@@ -0,0 +1,568 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Richtek Technology Corp.
+ *
+ * Authors:
+ * Alice Chen <alice_chen@richtek.com>
+ * ChiYuan Huang <cy_huang@richtek.com>
+ */
+
+#include <linux/bitops.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+enum {
+ MT6370_LED_FLASH1 = 0,
+ MT6370_LED_FLASH2,
+ MT6370_MAX_LEDS
+};
+
+/* Virtual definition for multicolor */
+
+#define MT6370_REG_FLEDEN 0x17E
+#define MT6370_REG_STRBTO 0x173
+#define MT6370_REG_CHGSTAT2 0x1D1
+#define MT6370_REG_FLEDSTAT1 0x1D9
+#define MT6370_REG_FLEDISTRB(_id) (0x174 + 4 * (_id))
+#define MT6370_REG_FLEDITOR(_id) (0x175 + 4 * (_id))
+#define MT6370_ITORCH_MASK GENMASK(4, 0)
+#define MT6370_ISTROBE_MASK GENMASK(6, 0)
+#define MT6370_STRBTO_MASK GENMASK(6, 0)
+#define MT6370_TORCHEN_MASK BIT(3)
+#define MT6370_STROBEN_MASK BIT(2)
+#define MT6370_FLCSEN_MASK(_id) BIT(MT6370_LED_FLASH2 - (_id))
+#define MT6370_FLCSEN_MASK_ALL GENMASK(1, 0)
+#define MT6370_FLEDCHGVINOVP_MASK BIT(3)
+#define MT6370_FLED1STRBTO_MASK BIT(11)
+#define MT6370_FLED2STRBTO_MASK BIT(10)
+#define MT6370_FLED1STRB_MASK BIT(9)
+#define MT6370_FLED2STRB_MASK BIT(8)
+#define MT6370_FLED1SHORT_MASK BIT(7)
+#define MT6370_FLED2SHORT_MASK BIT(6)
+#define MT6370_FLEDLVF_MASK BIT(3)
+
+#define MT6370_LED_JOINT 2
+#define MT6370_RANGE_FLED_REG 4
+#define MT6370_ITORCH_MIN_uA 25000
+#define MT6370_ITORCH_STEP_uA 12500
+#define MT6370_ITORCH_MAX_uA 400000
+#define MT6370_ITORCH_DOUBLE_MAX_uA 800000
+#define MT6370_ISTRB_MIN_uA 50000
+#define MT6370_ISTRB_STEP_uA 12500
+#define MT6370_ISTRB_MAX_uA 1500000
+#define MT6370_ISTRB_DOUBLE_MAX_uA 3000000
+#define MT6370_STRBTO_MIN_US 64000
+#define MT6370_STRBTO_STEP_US 32000
+#define MT6370_STRBTO_MAX_US 2432000
+
+#define to_mt6370_led(ptr, member) container_of(ptr, struct mt6370_led, member)
+
+struct mt6370_led {
+ struct led_classdev_flash flash;
+ struct v4l2_flash *v4l2_flash;
+ struct mt6370_priv *priv;
+ u8 led_no;
+};
+
+struct mt6370_priv {
+ struct regmap *regmap;
+ struct mutex lock;
+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+ unsigned int leds_active;
+ unsigned int leds_count;
+ struct mt6370_led leds[] __counted_by(leds_count);
+};
+
+static int mt6370_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness level)
+{
+ struct mt6370_led *led = to_mt6370_led(lcdev, flash.led_cdev);
+ struct mt6370_priv *priv = led->priv;
+ u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
+ MT6370_FLCSEN_MASK(led->led_no);
+ u32 enable_mask = MT6370_TORCHEN_MASK | led_enable_mask;
+ u32 val = level ? led_enable_mask : 0;
+ u32 curr;
+ int ret, i;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * There is only one set of flash control logic, and this flag is used to check if 'strobe'
+ * is currently being used.
+ */
+ if (priv->fled_strobe_used) {
+ dev_warn(lcdev->dev, "Please disable strobe first [%d]\n", priv->fled_strobe_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (level)
+ curr = priv->fled_torch_used | BIT(led->led_no);
+ else
+ curr = priv->fled_torch_used & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6370_TORCHEN_MASK;
+
+ if (level) {
+ level -= 1;
+ if (led->led_no == MT6370_LED_JOINT) {
+ u32 flevel[MT6370_MAX_LEDS];
+
+ /*
+ * There're two flash channels in MT6370. If joint flash output is used,
+ * torch current will be averaged output from both channels.
+ */
+ flevel[0] = level / 2;
+ flevel[1] = level - flevel[0];
+ for (i = 0; i < MT6370_MAX_LEDS; i++) {
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(i),
+ MT6370_ITORCH_MASK, flevel[i]);
+ if (ret)
+ goto unlock;
+ }
+ } else {
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDITOR(led->led_no),
+ MT6370_ITORCH_MASK, level);
+ if (ret)
+ goto unlock;
+ }
+ }
+
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, enable_mask, val);
+ if (ret)
+ goto unlock;
+
+ priv->fled_torch_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
+{
+ /*
+ * Because of the current spikes when turning on the flash, the brightness should be kept
+ * by the LED framework. This empty function is used to prevent checking failure when
+ * led_classdev_flash registers ops.
+ */
+ return 0;
+}
+
+static int _mt6370_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
+{
+ struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
+ struct mt6370_priv *priv = led->priv;
+ struct led_flash_setting *setting = &fl_cdev->brightness;
+ u32 val = (brightness - setting->min) / setting->step;
+ int ret, i;
+
+ if (led->led_no == MT6370_LED_JOINT) {
+ u32 flevel[MT6370_MAX_LEDS];
+
+ /*
+ * There're two flash channels in MT6370. If joint flash output is used, storbe
+ * current will be averaged output from both channels.
+ */
+ flevel[0] = val / 2;
+ flevel[1] = val - flevel[0];
+ for (i = 0; i < MT6370_MAX_LEDS; i++) {
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(i),
+ MT6370_ISTROBE_MASK, flevel[i]);
+ if (ret)
+ break;
+ }
+ } else {
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDISTRB(led->led_no),
+ MT6370_ISTROBE_MASK, val);
+ }
+
+ return ret;
+}
+
+static int mt6370_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
+ struct mt6370_priv *priv = led->priv;
+ struct led_classdev *lcdev = &fl_cdev->led_cdev;
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 led_enable_mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
+ MT6370_FLCSEN_MASK(led->led_no);
+ u32 enable_mask = MT6370_STROBEN_MASK | led_enable_mask;
+ u32 val = state ? led_enable_mask : 0;
+ u32 curr;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ /*
+ * There is only one set of flash control logic, and this flag is used to check if 'torch'
+ * is currently being used.
+ */
+ if (priv->fled_torch_used) {
+ dev_warn(lcdev->dev, "Please disable torch first [0x%x]\n", priv->fled_torch_used);
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (state)
+ curr = priv->fled_strobe_used | BIT(led->led_no);
+ else
+ curr = priv->fled_strobe_used & ~BIT(led->led_no);
+
+ if (curr)
+ val |= MT6370_STROBEN_MASK;
+
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, enable_mask, val);
+ if (ret) {
+ dev_err(lcdev->dev, "[%d] control current source %d fail\n", led->led_no, state);
+ goto unlock;
+ }
+
+ /*
+ * If the flash needs to turn on, configure the flash current to ramp up to the setting
+ * value. Otherwise, always revert to the minimum one.
+ */
+ ret = _mt6370_flash_brightness_set(fl_cdev, state ? s->val : s->min);
+ if (ret) {
+ dev_err(lcdev->dev, "[%d] Failed to set brightness\n", led->led_no);
+ goto unlock;
+ }
+
+ /*
+ * For the flash to turn on/off, we must wait for HW ramping up/down time 5ms/500us to
+ * prevent the unexpected problem.
+ */
+ if (!priv->fled_strobe_used && curr)
+ usleep_range(5000, 6000);
+ else if (priv->fled_strobe_used && !curr)
+ usleep_range(500, 600);
+
+ priv->fled_strobe_used = curr;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int mt6370_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
+ struct mt6370_priv *priv = led->priv;
+
+ mutex_lock(&priv->lock);
+ *state = !!(priv->fled_strobe_used & BIT(led->led_no));
+ mutex_unlock(&priv->lock);
+
+ return 0;
+}
+
+static int mt6370_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
+ struct mt6370_priv *priv = led->priv;
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+
+ return regmap_update_bits(priv->regmap, MT6370_REG_STRBTO, MT6370_STRBTO_MASK, val);
+}
+
+static int mt6370_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct mt6370_led *led = to_mt6370_led(fl_cdev, flash);
+ struct mt6370_priv *priv = led->priv;
+ u16 fled_stat;
+ unsigned int chg_stat, strobe_timeout_mask, fled_short_mask;
+ u32 rfault = 0;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6370_REG_CHGSTAT2, &chg_stat);
+ if (ret)
+ return ret;
+
+ ret = regmap_raw_read(priv->regmap, MT6370_REG_FLEDSTAT1, &fled_stat, sizeof(fled_stat));
+ if (ret)
+ return ret;
+
+ switch (led->led_no) {
+ case MT6370_LED_FLASH1:
+ strobe_timeout_mask = MT6370_FLED1STRBTO_MASK;
+ fled_short_mask = MT6370_FLED1SHORT_MASK;
+ break;
+
+ case MT6370_LED_FLASH2:
+ strobe_timeout_mask = MT6370_FLED2STRBTO_MASK;
+ fled_short_mask = MT6370_FLED2SHORT_MASK;
+ break;
+
+ case MT6370_LED_JOINT:
+ strobe_timeout_mask = MT6370_FLED1STRBTO_MASK | MT6370_FLED2STRBTO_MASK;
+ fled_short_mask = MT6370_FLED1SHORT_MASK | MT6370_FLED2SHORT_MASK;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (chg_stat & MT6370_FLEDCHGVINOVP_MASK)
+ rfault |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (fled_stat & strobe_timeout_mask)
+ rfault |= LED_FAULT_TIMEOUT;
+
+ if (fled_stat & fled_short_mask)
+ rfault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (fled_stat & MT6370_FLEDLVF_MASK)
+ rfault |= LED_FAULT_UNDER_VOLTAGE;
+
+ *fault = rfault;
+ return ret;
+}
+
+static const struct led_flash_ops mt6370_flash_ops = {
+ .flash_brightness_set = mt6370_flash_brightness_set,
+ .strobe_set = mt6370_strobe_set,
+ .strobe_get = mt6370_strobe_get,
+ .timeout_set = mt6370_timeout_set,
+ .fault_get = mt6370_fault_get,
+};
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int mt6370_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct mt6370_led *led = to_mt6370_led(flash, flash);
+ struct mt6370_priv *priv = led->priv;
+ u32 mask = led->led_no == MT6370_LED_JOINT ? MT6370_FLCSEN_MASK_ALL :
+ MT6370_FLCSEN_MASK(led->led_no);
+ u32 val = enable ? mask : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_update_bits(priv->regmap, MT6370_REG_FLEDEN, mask, val);
+ if (ret)
+ goto unlock;
+
+ if (enable)
+ priv->fled_strobe_used |= BIT(led->led_no);
+ else
+ priv->fled_strobe_used &= ~BIT(led->led_no);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = mt6370_flash_external_strobe_set,
+};
+
+static void mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg)
+{
+ struct led_classdev *lcdev;
+ struct led_flash_setting *s = &cfg->intensity;
+
+ lcdev = &led->flash.led_cdev;
+
+ s->min = MT6370_ITORCH_MIN_uA;
+ s->step = MT6370_ITORCH_STEP_uA;
+ s->val = s->max = s->min + (lcdev->max_brightness - 1) * s->step;
+
+ cfg->has_external_strobe = 1;
+ strscpy(cfg->dev_name, dev_name(lcdev->dev), sizeof(cfg->dev_name));
+
+ cfg->flash_faults = LED_FAULT_SHORT_CIRCUIT | LED_FAULT_TIMEOUT |
+ LED_FAULT_INPUT_VOLTAGE | LED_FAULT_UNDER_VOLTAGE;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+static void mt6370_init_v4l2_flash_config(struct mt6370_led *led, struct v4l2_flash_config *cfg)
+{
+}
+#endif
+
+static void mt6370_v4l2_flash_release(void *v4l2_flash)
+{
+ v4l2_flash_release(v4l2_flash);
+}
+
+static int mt6370_led_register(struct device *parent, struct mt6370_led *led,
+ struct fwnode_handle *fwnode)
+{
+ struct led_init_data init_data = { .fwnode = fwnode };
+ struct v4l2_flash_config v4l2_config = {};
+ int ret;
+
+ ret = devm_led_classdev_flash_register_ext(parent, &led->flash, &init_data);
+ if (ret)
+ return dev_err_probe(parent, ret, "Couldn't register flash %d\n", led->led_no);
+
+ mt6370_init_v4l2_flash_config(led, &v4l2_config);
+ led->v4l2_flash = v4l2_flash_init(parent, fwnode, &led->flash, &v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(led->v4l2_flash))
+ return dev_err_probe(parent, PTR_ERR(led->v4l2_flash),
+ "Failed to register %d v4l2 sd\n", led->led_no);
+
+ return devm_add_action_or_reset(parent, mt6370_v4l2_flash_release, led->v4l2_flash);
+}
+
+static u32 mt6370_clamp(u32 val, u32 min, u32 max, u32 step)
+{
+ u32 retval;
+
+ retval = clamp_val(val, min, max);
+ if (step > 1)
+ retval = rounddown(retval - min, step) + min;
+
+ return retval;
+}
+
+static int mt6370_init_flash_properties(struct device *dev, struct mt6370_led *led,
+ struct fwnode_handle *fwnode)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct mt6370_priv *priv = led->priv;
+ struct led_flash_setting *s;
+ u32 sources[MT6370_MAX_LEDS];
+ u32 max_ua, val;
+ int i, ret, num;
+
+ num = fwnode_property_count_u32(fwnode, "led-sources");
+ if (num < 1)
+ return dev_err_probe(dev, -EINVAL,
+ "Not specified or wrong number of led-sources\n");
+
+ ret = fwnode_property_read_u32_array(fwnode, "led-sources", sources, num);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num; i++) {
+ if (sources[i] >= MT6370_MAX_LEDS)
+ return -EINVAL;
+ if (priv->leds_active & BIT(sources[i]))
+ return -EINVAL;
+ priv->leds_active |= BIT(sources[i]);
+ }
+
+ /* If both channels are specified in 'led-sources', joint flash output mode is used */
+ led->led_no = num == 2 ? MT6370_LED_JOINT : sources[0];
+
+ max_ua = num == 2 ? MT6370_ITORCH_DOUBLE_MAX_uA : MT6370_ITORCH_MAX_uA;
+ val = MT6370_ITORCH_MIN_uA;
+ ret = fwnode_property_read_u32(fwnode, "led-max-microamp", &val);
+ if (!ret)
+ val = mt6370_clamp(val, MT6370_ITORCH_MIN_uA, max_ua, MT6370_ITORCH_STEP_uA);
+
+ lcdev->max_brightness = (val - MT6370_ITORCH_MIN_uA) / MT6370_ITORCH_STEP_uA + 1;
+ lcdev->brightness_set_blocking = mt6370_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ max_ua = num == 2 ? MT6370_ISTRB_DOUBLE_MAX_uA : MT6370_ISTRB_MAX_uA;
+ val = MT6370_ISTRB_MIN_uA;
+ ret = fwnode_property_read_u32(fwnode, "flash-max-microamp", &val);
+ if (!ret)
+ val = mt6370_clamp(val, MT6370_ISTRB_MIN_uA, max_ua, MT6370_ISTRB_STEP_uA);
+
+ s = &flash->brightness;
+ s->min = MT6370_ISTRB_MIN_uA;
+ s->step = MT6370_ISTRB_STEP_uA;
+ s->val = s->max = val;
+
+ /* Always configure to the minimum level when off to prevent flash current spikes. */
+ ret = _mt6370_flash_brightness_set(flash, s->min);
+ if (ret)
+ return ret;
+
+ val = MT6370_STRBTO_MIN_US;
+ ret = fwnode_property_read_u32(fwnode, "flash-max-timeout-us", &val);
+ if (!ret)
+ val = mt6370_clamp(val, MT6370_STRBTO_MIN_US, MT6370_STRBTO_MAX_US,
+ MT6370_STRBTO_STEP_US);
+
+ s = &flash->timeout;
+ s->min = MT6370_STRBTO_MIN_US;
+ s->step = MT6370_STRBTO_STEP_US;
+ s->val = s->max = val;
+
+ flash->ops = &mt6370_flash_ops;
+
+ return 0;
+}
+
+static int mt6370_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mt6370_priv *priv;
+ size_t count;
+ int i = 0, ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > MT6370_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL,
+ "No child node or node count over max led number %zu\n", count);
+
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->leds_count = count;
+ mutex_init(&priv->lock);
+
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap)
+ return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n");
+
+ 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)
+ return ret;
+
+ ret = mt6370_led_register(dev, led, child);
+ if (ret)
+ return ret;
+
+ i++;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mt6370_led_of_id[] = {
+ { .compatible = "mediatek,mt6370-flashlight" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mt6370_led_of_id);
+
+static struct platform_driver mt6370_led_driver = {
+ .driver = {
+ .name = "mt6370-flashlight",
+ .of_match_table = mt6370_led_of_id,
+ },
+ .probe = mt6370_led_probe,
+};
+module_platform_driver(mt6370_led_driver);
+
+MODULE_AUTHOR("Alice Chen <alice_chen@richtek.com>");
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("MT6370 FLASH LED Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/flash/leds-qcom-flash.c b/drivers/leds/flash/leds-qcom-flash.c
new file mode 100644
index 000000000000..b03a6833e3e3
--- /dev/null
+++ b/drivers/leds/flash/leds-qcom-flash.c
@@ -0,0 +1,989 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022, 2024-2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/leds.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#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
+
+#define FLASH_SUBTYPE_REG 0x05
+#define FLASH_SUBTYPE_3CH_PM8150_VAL 0x04
+#define FLASH_SUBTYPE_3CH_PMI8998_VAL 0x03
+#define FLASH_SUBTYPE_4CH_VAL 0x07
+
+#define FLASH_STS_3CH_OTST1 BIT(0)
+#define FLASH_STS_3CH_OTST2 BIT(1)
+#define FLASH_STS_3CH_OTST3 BIT(2)
+#define FLASH_STS_3CH_BOB_THM_OVERLOAD BIT(3)
+#define FLASH_STS_3CH_VPH_DROOP BIT(4)
+#define FLASH_STS_3CH_BOB_ILIM_S1 BIT(5)
+#define FLASH_STS_3CH_BOB_ILIM_S2 BIT(6)
+#define FLASH_STS_3CH_BCL_IBAT BIT(7)
+
+#define FLASH_STS_4CH_VPH_LOW BIT(0)
+#define FLASH_STS_4CH_BCL_IBAT BIT(1)
+#define FLASH_STS_4CH_BOB_ILIM_S1 BIT(2)
+#define FLASH_STS_4CH_BOB_ILIM_S2 BIT(3)
+#define FLASH_STS_4CH_OTST2 BIT(4)
+#define FLASH_STS_4CH_OTST1 BIT(5)
+#define FLASH_STS_4CHG_BOB_THM_OVERLOAD BIT(6)
+
+#define FLASH_TIMER_EN_BIT BIT(7)
+#define FLASH_TIMER_VAL_MASK GENMASK(6, 0)
+#define FLASH_TIMER_STEP_MS 10
+
+#define FLASH_STROBE_HW_SW_SEL_BIT BIT(2)
+#define SW_STROBE_VAL 0
+#define HW_STROBE_VAL 1
+#define FLASH_HW_STROBE_TRIGGER_SEL_BIT BIT(1)
+#define STROBE_LEVEL_TRIGGER_VAL 0
+#define STROBE_EDGE_TRIGGER_VAL 1
+#define FLASH_STROBE_POLARITY_BIT BIT(0)
+#define STROBE_ACTIVE_HIGH_VAL 1
+
+#define FLASH_IRES_MASK_4CH BIT(0)
+#define FLASH_IRES_MASK_3CH GENMASK(1, 0)
+#define FLASH_IRES_12P5MA_VAL 0
+#define FLASH_IRES_5MA_VAL_4CH 1
+#define FLASH_IRES_5MA_VAL_3CH 3
+
+/* constants */
+#define FLASH_CURRENT_MAX_UA 1500000
+#define TORCH_CURRENT_MAX_UA 500000
+#define FLASH_TOTAL_CURRENT_MAX_UA 2000000
+#define FLASH_CURRENT_DEFAULT_UA 1000000
+#define TORCH_CURRENT_DEFAULT_UA 200000
+
+#define TORCH_IRES_UA 5000
+#define FLASH_IRES_UA 12500
+
+#define FLASH_TIMEOUT_MAX_US 1280000
+#define FLASH_TIMEOUT_STEP_US 10000
+
+#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,
+};
+
+enum led_mode {
+ FLASH_MODE,
+ TORCH_MODE,
+};
+
+enum led_strobe {
+ SW_STROBE,
+ HW_STROBE,
+};
+
+enum {
+ REG_STATUS1,
+ REG_STATUS2,
+ REG_STATUS3,
+ REG_CHAN_TIMER,
+ REG_ITARGET,
+ REG_MODULE_EN,
+ REG_IRESOLUTION,
+ REG_CHAN_STROBE,
+ REG_CHAN_EN,
+ REG_THERM_THRSH1,
+ REG_THERM_THRSH2,
+ REG_THERM_THRSH3,
+ REG_TORCH_CLAMP,
+ REG_MAX_COUNT,
+};
+
+static const struct reg_field mvflash_3ch_pmi8998_regs[REG_MAX_COUNT] = {
+ [REG_STATUS1] = REG_FIELD(0x08, 0, 5),
+ [REG_STATUS2] = REG_FIELD(0x09, 0, 7),
+ [REG_STATUS3] = REG_FIELD(0x0a, 0, 7),
+ [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1),
+ [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1),
+ [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7),
+ [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5),
+ [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1),
+ [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2),
+ [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2),
+ [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2),
+ [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2),
+ [REG_TORCH_CLAMP] = REG_FIELD(0xea, 0, 6),
+};
+
+static const struct reg_field mvflash_3ch_regs[REG_MAX_COUNT] = {
+ [REG_STATUS1] = REG_FIELD(0x08, 0, 7),
+ [REG_STATUS2] = REG_FIELD(0x09, 0, 7),
+ [REG_STATUS3] = REG_FIELD(0x0a, 0, 7),
+ [REG_CHAN_TIMER] = REG_FIELD_ID(0x40, 0, 7, 3, 1),
+ [REG_ITARGET] = REG_FIELD_ID(0x43, 0, 6, 3, 1),
+ [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7),
+ [REG_IRESOLUTION] = REG_FIELD(0x47, 0, 5),
+ [REG_CHAN_STROBE] = REG_FIELD_ID(0x49, 0, 2, 3, 1),
+ [REG_CHAN_EN] = REG_FIELD(0x4c, 0, 2),
+ [REG_THERM_THRSH1] = REG_FIELD(0x56, 0, 2),
+ [REG_THERM_THRSH2] = REG_FIELD(0x57, 0, 2),
+ [REG_THERM_THRSH3] = REG_FIELD(0x58, 0, 2),
+ [REG_TORCH_CLAMP] = REG_FIELD(0xec, 0, 6),
+};
+
+static const struct reg_field mvflash_4ch_regs[REG_MAX_COUNT] = {
+ [REG_STATUS1] = REG_FIELD(0x06, 0, 7),
+ [REG_STATUS2] = REG_FIELD(0x07, 0, 6),
+ [REG_STATUS3] = REG_FIELD(0x09, 0, 7),
+ [REG_CHAN_TIMER] = REG_FIELD_ID(0x3e, 0, 7, 4, 1),
+ [REG_ITARGET] = REG_FIELD_ID(0x42, 0, 6, 4, 1),
+ [REG_MODULE_EN] = REG_FIELD(0x46, 7, 7),
+ [REG_IRESOLUTION] = REG_FIELD(0x49, 0, 3),
+ [REG_CHAN_STROBE] = REG_FIELD_ID(0x4a, 0, 6, 4, 1),
+ [REG_CHAN_EN] = REG_FIELD(0x4e, 0, 3),
+ [REG_THERM_THRSH1] = REG_FIELD(0x7a, 0, 2),
+ [REG_THERM_THRSH2] = REG_FIELD(0x78, 0, 2),
+ [REG_TORCH_CLAMP] = REG_FIELD(0xed, 0, 6),
+};
+
+struct qcom_flash_data {
+ struct v4l2_flash **v4l2_flash;
+ struct regmap_field *r_fields[REG_MAX_COUNT];
+ struct mutex lock;
+ enum hw_type hw_type;
+ u32 total_ma;
+ u8 leds_count;
+ u8 max_channels;
+ u8 chan_en_bits;
+ u8 revision;
+ u8 torch_clamp;
+};
+
+struct qcom_flash_led {
+ struct qcom_flash_data *flash_data;
+ struct led_classdev_flash flash;
+ u32 max_flash_current_ma;
+ u32 max_torch_current_ma;
+ 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;
+};
+
+static int set_flash_module_en(struct qcom_flash_led *led, bool en)
+{
+ struct qcom_flash_data *flash_data = led->flash_data;
+ u8 led_mask = 0, enable;
+ int i, rc;
+
+ for (i = 0; i < led->chan_count; i++)
+ led_mask |= BIT(led->chan_id[i]);
+
+ mutex_lock(&flash_data->lock);
+ if (en)
+ flash_data->chan_en_bits |= led_mask;
+ else
+ flash_data->chan_en_bits &= ~led_mask;
+
+ enable = !!flash_data->chan_en_bits;
+ rc = regmap_field_write(flash_data->r_fields[REG_MODULE_EN], enable);
+ if (rc)
+ dev_err(led->flash.led_cdev.dev, "write module_en failed, rc=%d\n", rc);
+ mutex_unlock(&flash_data->lock);
+
+ 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;
+ u32 itarg_ua, ires_ua;
+ u8 shift, ires_mask = 0, ires_val = 0, chan_id;
+ int i, rc;
+
+ /*
+ * Split the current across the channels and set the
+ * IRESOLUTION and ITARGET registers accordingly.
+ */
+ itarg_ua = (current_ma * UA_PER_MA) / led->chan_count + 1;
+ ires_ua = (mode == FLASH_MODE) ? FLASH_IRES_UA : TORCH_IRES_UA;
+
+ for (i = 0; i < led->chan_count; i++) {
+ u8 itarget = 0;
+
+ if (itarg_ua > ires_ua)
+ itarget = itarg_ua / ires_ua - 1;
+
+ chan_id = led->chan_id[i];
+
+ rc = regmap_fields_write(flash_data->r_fields[REG_ITARGET], chan_id, itarget);
+ if (rc)
+ return rc;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ shift = chan_id * 2;
+ ires_mask |= FLASH_IRES_MASK_3CH << shift;
+ ires_val |= ((mode == FLASH_MODE) ?
+ (FLASH_IRES_12P5MA_VAL << shift) :
+ (FLASH_IRES_5MA_VAL_3CH << shift));
+ } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
+ shift = chan_id;
+ ires_mask |= FLASH_IRES_MASK_4CH << shift;
+ ires_val |= ((mode == FLASH_MODE) ?
+ (FLASH_IRES_12P5MA_VAL << shift) :
+ (FLASH_IRES_5MA_VAL_4CH << shift));
+ } else {
+ dev_err(led->flash.led_cdev.dev,
+ "HW type %d is not supported\n", flash_data->hw_type);
+ return -EOPNOTSUPP;
+ }
+ }
+
+ return regmap_field_update_bits(flash_data->r_fields[REG_IRESOLUTION], ires_mask, ires_val);
+}
+
+static int set_flash_timeout(struct qcom_flash_led *led, u32 timeout_ms)
+{
+ struct qcom_flash_data *flash_data = led->flash_data;
+ u8 timer, chan_id;
+ int rc, i;
+
+ /* set SAFETY_TIMER for all the channels connected to the same LED */
+ timeout_ms = min_t(u32, timeout_ms, led->max_timeout_ms);
+
+ for (i = 0; i < led->chan_count; i++) {
+ chan_id = led->chan_id[i];
+
+ timer = timeout_ms / FLASH_TIMER_STEP_MS;
+ timer = clamp_t(u8, timer, 0, FLASH_TIMER_VAL_MASK);
+
+ if (timeout_ms)
+ timer |= FLASH_TIMER_EN_BIT;
+
+ rc = regmap_fields_write(flash_data->r_fields[REG_CHAN_TIMER], chan_id, timer);
+ if (rc)
+ return rc;
+ }
+
+ return 0;
+}
+
+static int set_flash_strobe(struct qcom_flash_led *led, enum led_strobe strobe, bool state)
+{
+ struct qcom_flash_data *flash_data = led->flash_data;
+ u8 strobe_sel, chan_en, chan_id, chan_mask = 0;
+ int rc, i;
+
+ /* Set SW strobe config for all channels connected to the LED */
+ for (i = 0; i < led->chan_count; i++) {
+ chan_id = led->chan_id[i];
+
+ if (strobe == SW_STROBE)
+ strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, SW_STROBE_VAL);
+ else
+ strobe_sel = FIELD_PREP(FLASH_STROBE_HW_SW_SEL_BIT, HW_STROBE_VAL);
+
+ strobe_sel |=
+ FIELD_PREP(FLASH_HW_STROBE_TRIGGER_SEL_BIT, STROBE_LEVEL_TRIGGER_VAL) |
+ FIELD_PREP(FLASH_STROBE_POLARITY_BIT, STROBE_ACTIVE_HIGH_VAL);
+
+ rc = regmap_fields_write(
+ flash_data->r_fields[REG_CHAN_STROBE], chan_id, strobe_sel);
+ if (rc)
+ return rc;
+
+ chan_mask |= BIT(chan_id);
+ }
+
+ /* Enable/disable flash channels */
+ chan_en = state ? chan_mask : 0;
+ rc = regmap_field_update_bits(flash_data->r_fields[REG_CHAN_EN], chan_mask, chan_en);
+ if (rc)
+ return rc;
+
+ led->enabled = state;
+ return 0;
+}
+
+static inline struct qcom_flash_led *flcdev_to_qcom_fled(struct led_classdev_flash *flcdev)
+{
+ return container_of(flcdev, struct qcom_flash_led, flash);
+}
+
+static int qcom_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
+{
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+
+ led->flash_current_ma = min_t(u32, led->max_flash_current_ma, brightness / UA_PER_MA);
+ return 0;
+}
+
+static int qcom_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout)
+{
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+
+ led->flash_timeout_ms = timeout / USEC_PER_MSEC;
+ return 0;
+}
+
+static int qcom_flash_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
+{
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+ int rc;
+
+ rc = set_flash_strobe(led, SW_STROBE, false);
+ if (rc)
+ return rc;
+
+ rc = update_allowed_flash_current(led, &led->flash_current_ma, state);
+ if (rc < 0)
+ return rc;
+
+ rc = set_flash_current(led, led->flash_current_ma, FLASH_MODE);
+ if (rc)
+ return rc;
+
+ rc = set_flash_timeout(led, led->flash_timeout_ms);
+ if (rc)
+ return rc;
+
+ rc = set_flash_module_en(led, state);
+ if (rc)
+ return rc;
+
+ return set_flash_strobe(led, SW_STROBE, state);
+}
+
+static int qcom_flash_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+
+ *state = led->enabled;
+ return 0;
+}
+
+static int qcom_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+ struct qcom_flash_data *flash_data = led->flash_data;
+ u8 shift, chan_id, chan_mask = 0;
+ u8 ot_mask = 0, oc_mask = 0, uv_mask = 0;
+ u32 val, fault_sts = 0;
+ int i, rc;
+
+ rc = regmap_field_read(flash_data->r_fields[REG_STATUS1], &val);
+ if (rc)
+ return rc;
+
+ for (i = 0; i < led->chan_count; i++) {
+ chan_id = led->chan_id[i];
+ shift = chan_id * 2;
+
+ if (val & BIT(shift))
+ fault_sts |= LED_FAULT_SHORT_CIRCUIT;
+
+ chan_mask |= BIT(chan_id);
+ }
+
+ rc = regmap_field_read(flash_data->r_fields[REG_STATUS2], &val);
+ if (rc)
+ return rc;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ ot_mask = FLASH_STS_3CH_OTST1 |
+ FLASH_STS_3CH_OTST2 |
+ FLASH_STS_3CH_OTST3 |
+ FLASH_STS_3CH_BOB_THM_OVERLOAD;
+ oc_mask = FLASH_STS_3CH_BOB_ILIM_S1 |
+ FLASH_STS_3CH_BOB_ILIM_S2 |
+ FLASH_STS_3CH_BCL_IBAT;
+ uv_mask = FLASH_STS_3CH_VPH_DROOP;
+ } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
+ ot_mask = FLASH_STS_4CH_OTST2 |
+ FLASH_STS_4CH_OTST1 |
+ FLASH_STS_4CHG_BOB_THM_OVERLOAD;
+ oc_mask = FLASH_STS_4CH_BCL_IBAT |
+ FLASH_STS_4CH_BOB_ILIM_S1 |
+ FLASH_STS_4CH_BOB_ILIM_S2;
+ uv_mask = FLASH_STS_4CH_VPH_LOW;
+ }
+
+ if (val & ot_mask)
+ fault_sts |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (val & oc_mask)
+ fault_sts |= LED_FAULT_OVER_CURRENT;
+
+ if (val & uv_mask)
+ fault_sts |= LED_FAULT_INPUT_VOLTAGE;
+
+ rc = regmap_field_read(flash_data->r_fields[REG_STATUS3], &val);
+ if (rc)
+ return rc;
+
+ if (flash_data->hw_type == QCOM_MVFLASH_3CH) {
+ if (val & chan_mask)
+ fault_sts |= LED_FAULT_TIMEOUT;
+ } else if (flash_data->hw_type == QCOM_MVFLASH_4CH) {
+ for (i = 0; i < led->chan_count; i++) {
+ chan_id = led->chan_id[i];
+ shift = chan_id * 2;
+
+ if (val & BIT(shift))
+ fault_sts |= LED_FAULT_TIMEOUT;
+ }
+ }
+
+ *fault = fault_sts;
+ return 0;
+}
+
+static int qcom_flash_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+ u32 current_ma = brightness * led->max_torch_current_ma / LED_FULL;
+ bool enable = !!brightness;
+ int rc;
+
+ rc = set_flash_strobe(led, SW_STROBE, false);
+ if (rc)
+ return rc;
+
+ rc = set_flash_module_en(led, false);
+ 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;
+
+ /* Disable flash timeout for torch LED */
+ rc = set_flash_timeout(led, 0);
+ if (rc)
+ return rc;
+
+ rc = set_flash_module_en(led, enable);
+ if (rc)
+ return rc;
+
+ return set_flash_strobe(led, SW_STROBE, enable);
+}
+
+static const struct led_flash_ops qcom_flash_ops = {
+ .flash_brightness_set = qcom_flash_brightness_set,
+ .strobe_set = qcom_flash_strobe_set,
+ .strobe_get = qcom_flash_strobe_get,
+ .timeout_set = qcom_flash_timeout_set,
+ .fault_get = qcom_flash_fault_get,
+};
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int qcom_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+ int rc;
+
+ rc = set_flash_module_en(led, enable);
+ if (rc)
+ return rc;
+
+ if (enable)
+ return set_flash_strobe(led, HW_STROBE, true);
+ else
+ return set_flash_strobe(led, SW_STROBE, false);
+}
+
+static enum led_brightness
+qcom_flash_intensity_to_led_brightness(struct v4l2_flash *v4l2_flash, s32 intensity)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+ u32 current_ma = intensity / UA_PER_MA;
+
+ current_ma = min_t(u32, current_ma, led->max_torch_current_ma);
+ if (!current_ma)
+ return LED_OFF;
+
+ return (current_ma * LED_FULL) / led->max_torch_current_ma;
+}
+
+static s32 qcom_flash_brightness_to_led_intensity(struct v4l2_flash *v4l2_flash,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct qcom_flash_led *led = flcdev_to_qcom_fled(fled_cdev);
+
+ return (brightness * led->max_torch_current_ma * UA_PER_MA) / LED_FULL;
+}
+
+static const struct v4l2_flash_ops qcom_v4l2_flash_ops = {
+ .external_strobe_set = qcom_flash_external_strobe_set,
+ .intensity_to_led_brightness = qcom_flash_intensity_to_led_brightness,
+ .led_brightness_to_intensity = qcom_flash_brightness_to_led_intensity,
+};
+
+static int
+qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
+{
+ 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;
+
+ intensity->min = intensity->step = TORCH_IRES_UA * led->chan_count;
+ intensity->max = led->max_torch_current_ma * UA_PER_MA;
+ intensity->val = min_t(u32, intensity->max, TORCH_CURRENT_DEFAULT_UA);
+
+ strscpy(v4l2_cfg.dev_name, led->flash.led_cdev.dev->kobj.name,
+ sizeof(v4l2_cfg.dev_name));
+
+ v4l2_cfg.has_external_strobe = true;
+ v4l2_cfg.flash_faults = LED_FAULT_INPUT_VOLTAGE |
+ LED_FAULT_OVER_CURRENT |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_OVER_TEMPERATURE |
+ LED_FAULT_TIMEOUT;
+
+ 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
+qcom_flash_v4l2_init(struct device *dev, struct qcom_flash_led *led, struct fwnode_handle *fwnode)
+{
+ return 0;
+}
+#endif
+
+static int qcom_flash_register_led_device(struct device *dev,
+ struct fwnode_handle *node, struct qcom_flash_led *led)
+{
+ struct qcom_flash_data *flash_data = led->flash_data;
+ struct led_init_data init_data;
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_flash_setting *brightness, *timeout;
+ u32 current_ua, timeout_us;
+ u32 channels[4];
+ int i, rc, count;
+ u8 torch_clamp;
+
+ count = fwnode_property_count_u32(node, "led-sources");
+ if (count <= 0) {
+ dev_err(dev, "No led-sources specified\n");
+ return -ENODEV;
+ }
+
+ if (count > flash_data->max_channels) {
+ dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
+ count, flash_data->max_channels);
+ return -EINVAL;
+ }
+
+ rc = fwnode_property_read_u32_array(node, "led-sources", channels, count);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read led-sources property, rc=%d\n", rc);
+ return rc;
+ }
+
+ led->chan_count = count;
+ led->chan_id = devm_kcalloc(dev, count, sizeof(u8), GFP_KERNEL);
+ if (!led->chan_id)
+ return -ENOMEM;
+
+ for (i = 0; i < count; i++) {
+ if ((channels[i] == 0) || (channels[i] > flash_data->max_channels)) {
+ dev_err(dev, "led-source out of HW support range [1-%u]\n",
+ flash_data->max_channels);
+ return -EINVAL;
+ }
+
+ /* Make chan_id indexing from 0 */
+ led->chan_id[i] = channels[i] - 1;
+ }
+
+ rc = fwnode_property_read_u32(node, "led-max-microamp", &current_ua);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read led-max-microamp property, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (current_ua == 0) {
+ dev_err(dev, "led-max-microamp shouldn't be 0\n");
+ return -EINVAL;
+ }
+
+ current_ua = min_t(u32, current_ua, TORCH_CURRENT_MAX_UA * led->chan_count);
+ led->max_torch_current_ma = current_ua / UA_PER_MA;
+
+ torch_clamp = (current_ua / led->chan_count) / TORCH_IRES_UA;
+ if (torch_clamp != 0)
+ torch_clamp--;
+
+ flash_data->torch_clamp = max_t(u8, flash_data->torch_clamp, torch_clamp);
+
+ if (fwnode_property_present(node, "flash-max-microamp")) {
+ flash->led_cdev.flags |= LED_DEV_CAP_FLASH;
+
+ rc = fwnode_property_read_u32(node, "flash-max-microamp", &current_ua);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read flash-max-microamp property, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ current_ua = min_t(u32, current_ua, FLASH_CURRENT_MAX_UA * led->chan_count);
+ current_ua = min_t(u32, current_ua, FLASH_TOTAL_CURRENT_MAX_UA);
+
+ /* Initialize flash class LED device brightness settings */
+ brightness = &flash->brightness;
+ brightness->min = brightness->step = FLASH_IRES_UA * led->chan_count;
+ brightness->max = current_ua;
+ brightness->val = min_t(u32, current_ua, FLASH_CURRENT_DEFAULT_UA);
+
+ led->max_flash_current_ma = current_ua / UA_PER_MA;
+ led->flash_current_ma = brightness->val / UA_PER_MA;
+
+ rc = fwnode_property_read_u32(node, "flash-max-timeout-us", &timeout_us);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read flash-max-timeout-us property, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ timeout_us = min_t(u32, timeout_us, FLASH_TIMEOUT_MAX_US);
+
+ /* Initialize flash class LED device timeout settings */
+ timeout = &flash->timeout;
+ timeout->min = timeout->step = FLASH_TIMEOUT_STEP_US;
+ timeout->val = timeout->max = timeout_us;
+
+ led->max_timeout_ms = led->flash_timeout_ms = timeout_us / USEC_PER_MSEC;
+
+ flash->ops = &qcom_flash_ops;
+ }
+
+ flash->led_cdev.brightness_set_blocking = qcom_flash_led_brightness_set;
+
+ init_data.fwnode = node;
+ init_data.devicename = NULL;
+ init_data.default_label = NULL;
+ init_data.devname_mandatory = false;
+
+ rc = devm_led_classdev_flash_register_ext(dev, flash, &init_data);
+ if (rc < 0) {
+ dev_err(dev, "Register flash LED classdev failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ return qcom_flash_v4l2_init(dev, led, node);
+}
+
+static int qcom_flash_led_probe(struct platform_device *pdev)
+{
+ struct qcom_flash_data *flash_data;
+ struct qcom_flash_led *led;
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ struct reg_field *regs;
+ int count, i, rc;
+ u32 val, reg_base;
+
+ flash_data = devm_kzalloc(dev, sizeof(*flash_data), GFP_KERNEL);
+ if (!flash_data)
+ return -ENOMEM;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap) {
+ dev_err(dev, "Failed to get parent regmap\n");
+ return -EINVAL;
+ }
+
+ rc = fwnode_property_read_u32(dev->fwnode, "reg", &reg_base);
+ if (rc < 0) {
+ dev_err(dev, "Failed to get register base address, rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = regmap_read(regmap, reg_base + FLASH_TYPE_REG, &val);
+ if (rc < 0) {
+ dev_err(dev, "Read flash LED module type failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (val != FLASH_TYPE_VAL) {
+ dev_err(dev, "type %#x is not a flash LED module\n", val);
+ return -ENODEV;
+ }
+
+ rc = regmap_read(regmap, reg_base + FLASH_SUBTYPE_REG, &val);
+ if (rc < 0) {
+ dev_err(dev, "Read flash LED module subtype failed, rc=%d\n", rc);
+ return rc;
+ }
+
+ if (val == FLASH_SUBTYPE_3CH_PM8150_VAL) {
+ flash_data->hw_type = QCOM_MVFLASH_3CH;
+ flash_data->max_channels = 3;
+ regs = devm_kmemdup(dev, mvflash_3ch_regs, sizeof(mvflash_3ch_regs),
+ GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
+ } else if (val == FLASH_SUBTYPE_3CH_PMI8998_VAL) {
+ flash_data->hw_type = QCOM_MVFLASH_3CH;
+ flash_data->max_channels = 3;
+ regs = devm_kmemdup(dev, mvflash_3ch_pmi8998_regs,
+ sizeof(mvflash_3ch_pmi8998_regs), GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
+ } else if (val == FLASH_SUBTYPE_4CH_VAL) {
+ flash_data->hw_type = QCOM_MVFLASH_4CH;
+ flash_data->max_channels = 4;
+ regs = devm_kmemdup(dev, mvflash_4ch_regs, sizeof(mvflash_4ch_regs),
+ GFP_KERNEL);
+ if (!regs)
+ return -ENOMEM;
+
+ rc = regmap_read(regmap, reg_base + FLASH_REVISION_REG, &val);
+ if (rc < 0) {
+ dev_err(dev, "Failed to read flash LED module revision, rc=%d\n", rc);
+ return rc;
+ }
+
+ flash_data->revision = val;
+ } else {
+ dev_err(dev, "flash LED subtype %#x is not yet supported\n", val);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < REG_MAX_COUNT; i++)
+ regs[i].reg += reg_base;
+
+ rc = devm_regmap_field_bulk_alloc(dev, regmap, flash_data->r_fields, regs, REG_MAX_COUNT);
+ if (rc < 0) {
+ dev_err(dev, "Failed to allocate regmap field, rc=%d\n", rc);
+ return rc;
+ }
+ devm_kfree(dev, regs); /* devm_regmap_field_bulk_alloc() makes copies */
+
+ platform_set_drvdata(pdev, flash_data);
+ mutex_init(&flash_data->lock);
+
+ count = device_get_child_node_count(dev);
+ if (count == 0 || count > flash_data->max_channels) {
+ dev_err(dev, "No child or child count exceeds %d\n", flash_data->max_channels);
+ return -EINVAL;
+ }
+
+ flash_data->v4l2_flash = devm_kcalloc(dev, count,
+ sizeof(*flash_data->v4l2_flash), GFP_KERNEL);
+ if (!flash_data->v4l2_flash)
+ return -ENOMEM;
+
+ device_for_each_child_node_scoped(dev, child) {
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led) {
+ rc = -ENOMEM;
+ goto release;
+ }
+
+ led->flash_data = flash_data;
+ rc = qcom_flash_register_led_device(dev, child, led);
+ if (rc < 0)
+ goto release;
+
+ flash_data->leds_count++;
+ }
+
+ return regmap_field_write(flash_data->r_fields[REG_TORCH_CLAMP], flash_data->torch_clamp);
+release:
+ while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
+ v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
+ return rc;
+}
+
+static void qcom_flash_led_remove(struct platform_device *pdev)
+{
+ struct qcom_flash_data *flash_data = platform_get_drvdata(pdev);
+
+ while (flash_data->v4l2_flash[flash_data->leds_count] && flash_data->leds_count)
+ v4l2_flash_release(flash_data->v4l2_flash[flash_data->leds_count--]);
+
+ mutex_destroy(&flash_data->lock);
+}
+
+static const struct of_device_id qcom_flash_led_match_table[] = {
+ { .compatible = "qcom,spmi-flash-led" },
+ { }
+};
+
+MODULE_DEVICE_TABLE(of, qcom_flash_led_match_table);
+static struct platform_driver qcom_flash_led_driver = {
+ .driver = {
+ .name = "leds-qcom-flash",
+ .of_match_table = qcom_flash_led_match_table,
+ },
+ .probe = qcom_flash_led_probe,
+ .remove = qcom_flash_led_remove,
+};
+
+module_platform_driver(qcom_flash_led_driver);
+
+MODULE_DESCRIPTION("QCOM Flash LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/flash/leds-rt4505.c b/drivers/leds/flash/leds-rt4505.c
new file mode 100644
index 000000000000..18fd5b7e528f
--- /dev/null
+++ b/drivers/leds/flash/leds-rt4505.c
@@ -0,0 +1,430 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/bitops.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/property.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define RT4505_REG_RESET 0x0
+#define RT4505_REG_CONFIG 0x8
+#define RT4505_REG_ILED 0x9
+#define RT4505_REG_ENABLE 0xA
+#define RT4505_REG_FLAGS 0xB
+
+#define RT4505_RESET_MASK BIT(7)
+#define RT4505_FLASHTO_MASK GENMASK(2, 0)
+#define RT4505_ITORCH_MASK GENMASK(7, 5)
+#define RT4505_ITORCH_SHIFT 5
+#define RT4505_IFLASH_MASK GENMASK(4, 0)
+#define RT4505_ENABLE_MASK GENMASK(5, 0)
+#define RT4505_TORCH_SET (BIT(0) | BIT(4))
+#define RT4505_FLASH_SET (BIT(0) | BIT(1) | BIT(2) | BIT(4))
+#define RT4505_EXT_FLASH_SET (BIT(0) | BIT(1) | BIT(4) | BIT(5))
+#define RT4505_FLASH_GET (BIT(0) | BIT(1) | BIT(4))
+#define RT4505_OVP_MASK BIT(3)
+#define RT4505_SHORT_MASK BIT(2)
+#define RT4505_OTP_MASK BIT(1)
+#define RT4505_TIMEOUT_MASK BIT(0)
+
+#define RT4505_ITORCH_MINUA 46000
+#define RT4505_ITORCH_MAXUA 375000
+#define RT4505_ITORCH_STPUA 47000
+#define RT4505_IFLASH_MINUA 93750
+#define RT4505_IFLASH_MAXUA 1500000
+#define RT4505_IFLASH_STPUA 93750
+#define RT4505_FLASHTO_MINUS 100000
+#define RT4505_FLASHTO_MAXUS 800000
+#define RT4505_FLASHTO_STPUS 100000
+
+struct rt4505_priv {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex lock;
+ struct led_classdev_flash flash;
+ struct v4l2_flash *v4l2_flash;
+};
+
+static int rt4505_torch_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct rt4505_priv *priv =
+ container_of(lcdev, struct rt4505_priv, flash.led_cdev);
+ u32 val = 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ if (level != LED_OFF) {
+ ret = regmap_update_bits(priv->regmap,
+ RT4505_REG_ILED, RT4505_ITORCH_MASK,
+ (level - 1) << RT4505_ITORCH_SHIFT);
+ if (ret)
+ goto unlock;
+
+ val = RT4505_TORCH_SET;
+ }
+
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static enum led_brightness rt4505_torch_brightness_get(
+ struct led_classdev *lcdev)
+{
+ struct rt4505_priv *priv =
+ container_of(lcdev, struct rt4505_priv, flash.led_cdev);
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val);
+ if (ret) {
+ dev_err(lcdev->dev, "Failed to get LED enable\n");
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ if ((val & RT4505_ENABLE_MASK) != RT4505_TORCH_SET) {
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ILED, &val);
+ if (ret) {
+ dev_err(lcdev->dev, "Failed to get LED brightness\n");
+ ret = LED_OFF;
+ goto unlock;
+ }
+
+ ret = ((val & RT4505_ITORCH_MASK) >> RT4505_ITORCH_SHIFT) + 1;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rt4505_flash_brightness_set(struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ struct led_flash_setting *s = &fled_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ILED,
+ RT4505_IFLASH_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_flash_strobe_set(struct led_classdev_flash *fled_cdev,
+ bool state)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val = state ? RT4505_FLASH_SET : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_flash_strobe_get(struct led_classdev_flash *fled_cdev,
+ bool *state)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_read(priv->regmap, RT4505_REG_ENABLE, &val);
+ if (ret)
+ goto unlock;
+
+ *state = (val & RT4505_FLASH_GET) == RT4505_FLASH_GET;
+
+unlock:
+ mutex_unlock(&priv->lock);
+ return ret;
+}
+
+static int rt4505_flash_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ struct led_flash_setting *s = &fled_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_CONFIG,
+ RT4505_FLASHTO_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int rt4505_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ struct rt4505_priv *priv =
+ container_of(fled_cdev, struct rt4505_priv, flash);
+ u32 val, led_faults = 0;
+ int ret;
+
+ ret = regmap_read(priv->regmap, RT4505_REG_FLAGS, &val);
+ if (ret)
+ return ret;
+
+ if (val & RT4505_OVP_MASK)
+ led_faults |= LED_FAULT_OVER_VOLTAGE;
+
+ if (val & RT4505_SHORT_MASK)
+ led_faults |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (val & RT4505_OTP_MASK)
+ led_faults |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (val & RT4505_TIMEOUT_MASK)
+ led_faults |= LED_FAULT_TIMEOUT;
+
+ *fault = led_faults;
+ return 0;
+}
+
+static const struct led_flash_ops rt4505_flash_ops = {
+ .flash_brightness_set = rt4505_flash_brightness_set,
+ .strobe_set = rt4505_flash_strobe_set,
+ .strobe_get = rt4505_flash_strobe_get,
+ .timeout_set = rt4505_flash_timeout_set,
+ .fault_get = rt4505_fault_get,
+};
+
+static bool rt4505_is_accessible_reg(struct device *dev, unsigned int reg)
+{
+ if (reg == RT4505_REG_RESET ||
+ (reg >= RT4505_REG_CONFIG && reg <= RT4505_REG_FLAGS))
+ return true;
+ return false;
+}
+
+static const struct regmap_config rt4505_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = RT4505_REG_FLAGS,
+
+ .readable_reg = rt4505_is_accessible_reg,
+ .writeable_reg = rt4505_is_accessible_reg,
+};
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static int rt4505_flash_external_strobe_set(struct v4l2_flash *v4l2_flash,
+ bool enable)
+{
+ struct led_classdev_flash *flash = v4l2_flash->fled_cdev;
+ struct rt4505_priv *priv =
+ container_of(flash, struct rt4505_priv, flash);
+ u32 val = enable ? RT4505_EXT_FLASH_SET : 0;
+ int ret;
+
+ mutex_lock(&priv->lock);
+ ret = regmap_update_bits(priv->regmap, RT4505_REG_ENABLE,
+ RT4505_ENABLE_MASK, val);
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static const struct v4l2_flash_ops v4l2_flash_ops = {
+ .external_strobe_set = rt4505_flash_external_strobe_set,
+};
+
+static void rt4505_init_v4l2_config(struct rt4505_priv *priv,
+ struct v4l2_flash_config *config)
+{
+ struct led_classdev_flash *flash = &priv->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(config->dev_name, lcdev->dev->kobj.name,
+ sizeof(config->dev_name));
+
+ s = &config->intensity;
+ s->min = RT4505_ITORCH_MINUA;
+ s->step = RT4505_ITORCH_STPUA;
+ s->max = s->val = s->min + (lcdev->max_brightness - 1) * s->step;
+
+ config->flash_faults = LED_FAULT_OVER_VOLTAGE |
+ LED_FAULT_SHORT_CIRCUIT |
+ LED_FAULT_LED_OVER_TEMPERATURE |
+ LED_FAULT_TIMEOUT;
+ config->has_external_strobe = 1;
+}
+#else
+static const struct v4l2_flash_ops v4l2_flash_ops;
+static void rt4505_init_v4l2_config(struct rt4505_priv *priv,
+ struct v4l2_flash_config *config)
+{
+}
+#endif
+
+static void rt4505_init_flash_properties(struct rt4505_priv *priv,
+ struct fwnode_handle *child)
+{
+ struct led_classdev_flash *flash = &priv->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ struct led_flash_setting *s;
+ u32 val;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp", &val);
+ if (ret) {
+ dev_warn(priv->dev, "led-max-microamp DT property missing\n");
+ val = RT4505_ITORCH_MINUA;
+ } else
+ val = clamp_val(val, RT4505_ITORCH_MINUA, RT4505_ITORCH_MAXUA);
+
+ lcdev->max_brightness =
+ (val - RT4505_ITORCH_MINUA) / RT4505_ITORCH_STPUA + 1;
+ lcdev->brightness_set_blocking = rt4505_torch_brightness_set;
+ lcdev->brightness_get = rt4505_torch_brightness_get;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ ret = fwnode_property_read_u32(child, "flash-max-microamp", &val);
+ if (ret) {
+ dev_warn(priv->dev, "flash-max-microamp DT property missing\n");
+ val = RT4505_IFLASH_MINUA;
+ } else
+ val = clamp_val(val, RT4505_IFLASH_MINUA, RT4505_IFLASH_MAXUA);
+
+ s = &flash->brightness;
+ s->min = RT4505_IFLASH_MINUA;
+ s->step = RT4505_IFLASH_STPUA;
+ s->max = s->val = val;
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us", &val);
+ if (ret) {
+ dev_warn(priv->dev,
+ "flash-max-timeout-us DT property missing\n");
+ val = RT4505_FLASHTO_MINUS;
+ } else
+ val = clamp_val(val, RT4505_FLASHTO_MINUS,
+ RT4505_FLASHTO_MAXUS);
+
+ s = &flash->timeout;
+ s->min = RT4505_FLASHTO_MINUS;
+ s->step = RT4505_FLASHTO_STPUS;
+ s->max = s->val = val;
+
+ flash->ops = &rt4505_flash_ops;
+}
+
+static int rt4505_probe(struct i2c_client *client)
+{
+ struct rt4505_priv *priv;
+ struct fwnode_handle *child;
+ struct led_init_data init_data = {};
+ struct v4l2_flash_config v4l2_config = {};
+ int ret;
+
+ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = &client->dev;
+ mutex_init(&priv->lock);
+
+ priv->regmap = devm_regmap_init_i2c(client, &rt4505_regmap_config);
+ if (IS_ERR(priv->regmap)) {
+ dev_err(priv->dev, "Failed to allocate register map\n");
+ return PTR_ERR(priv->regmap);
+ }
+
+ ret = regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK);
+ if (ret) {
+ dev_err(priv->dev, "Failed to reset registers\n");
+ return ret;
+ }
+
+ child = device_get_next_child_node(&client->dev, NULL);
+ if (!child) {
+ dev_err(priv->dev, "Failed to get child node\n");
+ return -EINVAL;
+ }
+ init_data.fwnode = child;
+
+ rt4505_init_flash_properties(priv, child);
+ ret = devm_led_classdev_flash_register_ext(priv->dev, &priv->flash,
+ &init_data);
+ if (ret) {
+ dev_err(priv->dev, "Failed to register flash\n");
+ return ret;
+ }
+
+ rt4505_init_v4l2_config(priv, &v4l2_config);
+ priv->v4l2_flash = v4l2_flash_init(priv->dev, init_data.fwnode,
+ &priv->flash, &v4l2_flash_ops,
+ &v4l2_config);
+ if (IS_ERR(priv->v4l2_flash)) {
+ dev_err(priv->dev, "Failed to register v4l2 flash\n");
+ return PTR_ERR(priv->v4l2_flash);
+ }
+
+ i2c_set_clientdata(client, priv);
+ return 0;
+}
+
+static void rt4505_remove(struct i2c_client *client)
+{
+ struct rt4505_priv *priv = i2c_get_clientdata(client);
+
+ v4l2_flash_release(priv->v4l2_flash);
+}
+
+static void rt4505_shutdown(struct i2c_client *client)
+{
+ struct rt4505_priv *priv = i2c_get_clientdata(client);
+
+ /* Reset registers to make sure all off before shutdown */
+ regmap_write(priv->regmap, RT4505_REG_RESET, RT4505_RESET_MASK);
+}
+
+static const struct of_device_id __maybe_unused rt4505_leds_match[] = {
+ { .compatible = "richtek,rt4505", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, rt4505_leds_match);
+
+static struct i2c_driver rt4505_driver = {
+ .driver = {
+ .name = "rt4505",
+ .of_match_table = of_match_ptr(rt4505_leds_match),
+ },
+ .probe = rt4505_probe,
+ .remove = rt4505_remove,
+ .shutdown = rt4505_shutdown,
+};
+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
new file mode 100644
index 000000000000..f6b439674c03
--- /dev/null
+++ b/drivers/leds/flash/leds-rt8515.c
@@ -0,0 +1,397 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LED driver for Richtek RT8515 flash/torch white LEDs
+ * found on some Samsung mobile phones.
+ *
+ * This is a 1.5A Boost dual channel driver produced around 2011.
+ *
+ * The component lacks a datasheet, but in the schematic picture
+ * from the LG P970 service manual you can see the connections
+ * from the RT8515 to the LED, with two resistors connected
+ * from the pins "RFS" and "RTS" to ground.
+ *
+ * On the LG P970:
+ * RFS (resistance flash setting?) is 20 kOhm
+ * RTS (resistance torch setting?) is 39 kOhm
+ *
+ * Some sleuthing finds us the RT9387A which we have a datasheet for:
+ * https://static5.arrow.com/pdfs/2014/7/27/8/21/12/794/rtt_/manual/94download_ds.jspprt9387a.jspprt9387a.pdf
+ * This apparently works the same way so in theory this driver
+ * should cover RT9387A as well. This has not been tested, please
+ * update the compatibles if you add RT9387A support.
+ *
+ * Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+/* We can provide 15-700 mA out to the LED */
+#define RT8515_MIN_IOUT_MA 15
+#define RT8515_MAX_IOUT_MA 700
+/* The maximum intensity is 1-16 for flash and 1-100 for torch */
+#define RT8515_FLASH_MAX 16
+#define RT8515_TORCH_MAX 100
+
+#define RT8515_TIMEOUT_US 250000U
+#define RT8515_MAX_TIMEOUT_US 300000U
+
+struct rt8515 {
+ struct led_classdev_flash fled;
+ struct device *dev;
+ struct v4l2_flash *v4l2_flash;
+ struct mutex lock;
+ struct regulator *reg;
+ struct gpio_desc *enable_torch;
+ struct gpio_desc *enable_flash;
+ struct timer_list powerdown_timer;
+ u32 max_timeout; /* Flash max timeout */
+ int flash_max_intensity;
+ int torch_max_intensity;
+};
+
+static struct rt8515 *to_rt8515(struct led_classdev_flash *fled)
+{
+ return container_of(fled, struct rt8515, fled);
+}
+
+static void rt8515_gpio_led_off(struct rt8515 *rt)
+{
+ gpiod_set_value(rt->enable_flash, 0);
+ gpiod_set_value(rt->enable_torch, 0);
+}
+
+static void rt8515_gpio_brightness_commit(struct gpio_desc *gpiod,
+ int brightness)
+{
+ int i;
+
+ /*
+ * Toggling a GPIO line with a small delay increases the
+ * brightness one step at a time.
+ */
+ for (i = 0; i < brightness; i++) {
+ gpiod_set_value(gpiod, 0);
+ udelay(1);
+ gpiod_set_value(gpiod, 1);
+ udelay(1);
+ }
+}
+
+/* This is setting the torch light level */
+static int rt8515_led_brightness_set(struct led_classdev *led,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled = lcdev_to_flcdev(led);
+ struct rt8515 *rt = to_rt8515(fled);
+
+ mutex_lock(&rt->lock);
+
+ if (brightness == LED_OFF) {
+ /* Off */
+ rt8515_gpio_led_off(rt);
+ } else if (brightness < RT8515_TORCH_MAX) {
+ /* Step it up to movie mode brightness using the flash pin */
+ rt8515_gpio_brightness_commit(rt->enable_torch, brightness);
+ } else {
+ /* Max torch brightness requested */
+ gpiod_set_value(rt->enable_torch, 1);
+ }
+
+ mutex_unlock(&rt->lock);
+
+ return 0;
+}
+
+static int rt8515_led_flash_strobe_set(struct led_classdev_flash *fled,
+ bool state)
+{
+ struct rt8515 *rt = to_rt8515(fled);
+ struct led_flash_setting *timeout = &fled->timeout;
+ int brightness = rt->flash_max_intensity;
+
+ mutex_lock(&rt->lock);
+
+ if (state) {
+ /* Enable LED flash mode and set brightness */
+ rt8515_gpio_brightness_commit(rt->enable_flash, brightness);
+ /* Set timeout */
+ mod_timer(&rt->powerdown_timer,
+ jiffies + usecs_to_jiffies(timeout->val));
+ } else {
+ timer_delete_sync(&rt->powerdown_timer);
+ /* Turn the LED off */
+ rt8515_gpio_led_off(rt);
+ }
+
+ fled->led_cdev.brightness = LED_OFF;
+ /* After this the torch LED will be disabled */
+
+ mutex_unlock(&rt->lock);
+
+ return 0;
+}
+
+static int rt8515_led_flash_strobe_get(struct led_classdev_flash *fled,
+ bool *state)
+{
+ struct rt8515 *rt = to_rt8515(fled);
+
+ *state = timer_pending(&rt->powerdown_timer);
+
+ return 0;
+}
+
+static int rt8515_led_flash_timeout_set(struct led_classdev_flash *fled,
+ u32 timeout)
+{
+ /* The timeout is stored in the led-class-flash core */
+ return 0;
+}
+
+static const struct led_flash_ops rt8515_flash_ops = {
+ .strobe_set = rt8515_led_flash_strobe_set,
+ .strobe_get = rt8515_led_flash_strobe_get,
+ .timeout_set = rt8515_led_flash_timeout_set,
+};
+
+static void rt8515_powerdown_timer(struct timer_list *t)
+{
+ struct rt8515 *rt = timer_container_of(rt, t, powerdown_timer);
+
+ /* Turn the LED off */
+ rt8515_gpio_led_off(rt);
+}
+
+static void rt8515_init_flash_timeout(struct rt8515 *rt)
+{
+ struct led_classdev_flash *fled = &rt->fled;
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fled->timeout;
+ s->min = 1;
+ s->max = rt->max_timeout;
+ s->step = 1;
+ /*
+ * Set default timeout to RT8515_TIMEOUT_US except if
+ * max_timeout from DT is lower.
+ */
+ s->val = min(rt->max_timeout, RT8515_TIMEOUT_US);
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+/* Configure the V2L2 flash subdevice */
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led = &rt->fled.led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(v4l2_sd_cfg->dev_name, led->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ /*
+ * Init flash intensity setting: this is a linear scale
+ * capped from the device tree max intensity setting
+ * 1..flash_max_intensity
+ */
+ s = &v4l2_sd_cfg->intensity;
+ s->min = 1;
+ s->max = rt->flash_max_intensity;
+ s->step = 1;
+ s->val = s->max;
+}
+
+static void rt8515_v4l2_flash_release(struct rt8515 *rt)
+{
+ v4l2_flash_release(rt->v4l2_flash);
+}
+
+#else
+static void rt8515_init_v4l2_flash_config(struct rt8515 *rt,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+
+static void rt8515_v4l2_flash_release(struct rt8515 *rt)
+{
+}
+#endif
+
+static void rt8515_determine_max_intensity(struct rt8515 *rt,
+ struct fwnode_handle *led,
+ const char *resistance,
+ const char *max_ua_prop, int hw_max,
+ int *max_intensity_setting)
+{
+ u32 res = 0; /* Can't be 0 so 0 is undefined */
+ u32 ua;
+ u32 max_ma;
+ int max_intensity;
+ int ret;
+
+ fwnode_property_read_u32(rt->dev->fwnode, resistance, &res);
+ ret = fwnode_property_read_u32(led, max_ua_prop, &ua);
+
+ /* Missing info in DT, OK go with hardware maxima */
+ if (ret || res == 0) {
+ dev_err(rt->dev,
+ "either %s or %s missing from DT, using HW max\n",
+ resistance, max_ua_prop);
+ max_ma = RT8515_MAX_IOUT_MA;
+ max_intensity = hw_max;
+ goto out_assign_max;
+ }
+
+ /*
+ * Formula from the datasheet, this is the maximum current
+ * defined by the hardware.
+ */
+ max_ma = (5500 * 1000) / res;
+ /*
+ * Calculate max intensity (linear scaling)
+ * Formula is ((ua / 1000) / max_ma) * 100, then simplified
+ */
+ max_intensity = (ua / 10) / max_ma;
+
+ dev_info(rt->dev,
+ "current restricted from %u to %u mA, max intensity %d/100\n",
+ max_ma, (ua / 1000), max_intensity);
+
+out_assign_max:
+ dev_info(rt->dev, "max intensity %d/%d = %d mA\n",
+ max_intensity, hw_max, max_ma);
+ *max_intensity_setting = max_intensity;
+}
+
+static int rt8515_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct fwnode_handle *child;
+ struct rt8515 *rt;
+ struct led_classdev *led;
+ struct led_classdev_flash *fled;
+ struct led_init_data init_data = {};
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ rt = devm_kzalloc(dev, sizeof(*rt), GFP_KERNEL);
+ if (!rt)
+ return -ENOMEM;
+
+ rt->dev = dev;
+ fled = &rt->fled;
+ led = &fled->led_cdev;
+
+ /* ENF - Enable Flash line */
+ rt->enable_flash = devm_gpiod_get(dev, "enf", GPIOD_OUT_LOW);
+ if (IS_ERR(rt->enable_flash))
+ return dev_err_probe(dev, PTR_ERR(rt->enable_flash),
+ "cannot get ENF (enable flash) GPIO\n");
+
+ /* ENT - Enable Torch line */
+ rt->enable_torch = devm_gpiod_get(dev, "ent", GPIOD_OUT_LOW);
+ if (IS_ERR(rt->enable_torch))
+ return dev_err_probe(dev, PTR_ERR(rt->enable_torch),
+ "cannot get ENT (enable torch) GPIO\n");
+
+ child = device_get_next_child_node(dev, NULL);
+ if (!child) {
+ dev_err(dev,
+ "No fwnode child node found for connected LED.\n");
+ return -EINVAL;
+ }
+ init_data.fwnode = child;
+
+ rt8515_determine_max_intensity(rt, child, "richtek,rfs-ohms",
+ "flash-max-microamp",
+ RT8515_FLASH_MAX,
+ &rt->flash_max_intensity);
+ rt8515_determine_max_intensity(rt, child, "richtek,rts-ohms",
+ "led-max-microamp",
+ RT8515_TORCH_MAX,
+ &rt->torch_max_intensity);
+
+ ret = fwnode_property_read_u32(child, "flash-max-timeout-us",
+ &rt->max_timeout);
+ if (ret) {
+ rt->max_timeout = RT8515_MAX_TIMEOUT_US;
+ dev_warn(dev,
+ "flash-max-timeout-us property missing\n");
+ }
+ timer_setup(&rt->powerdown_timer, rt8515_powerdown_timer, 0);
+ rt8515_init_flash_timeout(rt);
+
+ fled->ops = &rt8515_flash_ops;
+
+ led->max_brightness = rt->torch_max_intensity;
+ led->brightness_set_blocking = rt8515_led_brightness_set;
+ led->flags |= LED_CORE_SUSPENDRESUME | LED_DEV_CAP_FLASH;
+
+ mutex_init(&rt->lock);
+
+ platform_set_drvdata(pdev, rt);
+
+ ret = devm_led_classdev_flash_register_ext(dev, fled, &init_data);
+ if (ret) {
+ fwnode_handle_put(child);
+ mutex_destroy(&rt->lock);
+ dev_err(dev, "can't register LED %s\n", led->name);
+ return ret;
+ }
+
+ rt8515_init_v4l2_flash_config(rt, &v4l2_sd_cfg);
+
+ /* Create a V4L2 Flash device if V4L2 flash is enabled */
+ rt->v4l2_flash = v4l2_flash_init(dev, child, fled, NULL, &v4l2_sd_cfg);
+ if (IS_ERR(rt->v4l2_flash)) {
+ ret = PTR_ERR(rt->v4l2_flash);
+ dev_err(dev, "failed to register V4L2 flash device (%d)\n",
+ ret);
+ /*
+ * Continue without the V4L2 flash
+ * (we still have the classdev)
+ */
+ }
+
+ fwnode_handle_put(child);
+ return 0;
+}
+
+static void rt8515_remove(struct platform_device *pdev)
+{
+ struct rt8515 *rt = platform_get_drvdata(pdev);
+
+ rt8515_v4l2_flash_release(rt);
+ timer_delete_sync(&rt->powerdown_timer);
+ mutex_destroy(&rt->lock);
+}
+
+static const struct of_device_id rt8515_match[] = {
+ { .compatible = "richtek,rt8515", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, rt8515_match);
+
+static struct platform_driver rt8515_driver = {
+ .driver = {
+ .name = "rt8515",
+ .of_match_table = rt8515_match,
+ },
+ .probe = rt8515_probe,
+ .remove = rt8515_remove,
+};
+module_platform_driver(rt8515_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("Richtek RT8515 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/flash/leds-sgm3140.c b/drivers/leds/flash/leds-sgm3140.c
new file mode 100644
index 000000000000..dc6840357370
--- /dev/null
+++ b/drivers/leds/flash/leds-sgm3140.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2020 Luca Weiss <luca@z3ntu.xyz>
+
+#include <linux/gpio/consumer.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/platform_device.h>
+
+#include <media/v4l2-flash-led-class.h>
+
+#define FLASH_TIMEOUT_DEFAULT 250000U /* 250ms */
+#define FLASH_MAX_TIMEOUT_DEFAULT 300000U /* 300ms */
+
+struct sgm3140 {
+ struct led_classdev_flash fled_cdev;
+ struct v4l2_flash *v4l2_flash;
+
+ struct timer_list powerdown_timer;
+
+ struct gpio_desc *flash_gpio;
+ struct gpio_desc *enable_gpio;
+ struct regulator *vin_regulator;
+
+ bool enabled;
+
+ /* current timeout in us */
+ u32 timeout;
+ /* maximum timeout in us */
+ u32 max_timeout;
+};
+
+static struct sgm3140 *flcdev_to_sgm3140(struct led_classdev_flash *flcdev)
+{
+ return container_of(flcdev, struct sgm3140, fled_cdev);
+}
+
+static int sgm3140_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+ int ret;
+
+ if (priv->enabled == state)
+ return 0;
+
+ if (state) {
+ ret = regulator_enable(priv->vin_regulator);
+ if (ret) {
+ dev_err(fled_cdev->led_cdev.dev,
+ "failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+ gpiod_set_value_cansleep(priv->flash_gpio, 1);
+ gpiod_set_value_cansleep(priv->enable_gpio, 1);
+ mod_timer(&priv->powerdown_timer,
+ jiffies + usecs_to_jiffies(priv->timeout));
+ } else {
+ 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);
+ if (ret) {
+ dev_err(fled_cdev->led_cdev.dev,
+ "failed to disable regulator: %d\n", ret);
+ return ret;
+ }
+ }
+
+ priv->enabled = state;
+
+ return 0;
+}
+
+static int sgm3140_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+
+ *state = timer_pending(&priv->powerdown_timer);
+
+ return 0;
+}
+
+static int sgm3140_timeout_set(struct led_classdev_flash *fled_cdev,
+ u32 timeout)
+{
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+
+ priv->timeout = timeout;
+
+ return 0;
+}
+
+static const struct led_flash_ops sgm3140_flash_ops = {
+ .strobe_set = sgm3140_strobe_set,
+ .strobe_get = sgm3140_strobe_get,
+ .timeout_set = sgm3140_timeout_set,
+};
+
+static int sgm3140_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ struct sgm3140 *priv = flcdev_to_sgm3140(fled_cdev);
+ bool enable = brightness == LED_ON;
+ int ret;
+
+ if (priv->enabled == enable)
+ return 0;
+
+ if (enable) {
+ ret = regulator_enable(priv->vin_regulator);
+ if (ret) {
+ dev_err(led_cdev->dev,
+ "failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+ gpiod_set_value_cansleep(priv->flash_gpio, 0);
+ gpiod_set_value_cansleep(priv->enable_gpio, 1);
+ } else {
+ timer_delete_sync(&priv->powerdown_timer);
+ gpiod_set_value_cansleep(priv->flash_gpio, 0);
+ gpiod_set_value_cansleep(priv->enable_gpio, 0);
+ ret = regulator_disable(priv->vin_regulator);
+ if (ret) {
+ dev_err(led_cdev->dev,
+ "failed to disable regulator: %d\n", ret);
+ return ret;
+ }
+ }
+
+ priv->enabled = enable;
+
+ return 0;
+}
+
+static void sgm3140_powerdown_timer(struct timer_list *t)
+{
+ struct sgm3140 *priv = timer_container_of(priv, t, powerdown_timer);
+
+ gpiod_set_value(priv->enable_gpio, 0);
+ gpiod_set_value(priv->flash_gpio, 0);
+ regulator_disable(priv->vin_regulator);
+
+ priv->enabled = false;
+}
+
+static void sgm3140_init_flash_timeout(struct sgm3140 *priv)
+{
+ struct led_classdev_flash *fled_cdev = &priv->fled_cdev;
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fled_cdev->timeout;
+ s->min = 1;
+ s->max = priv->max_timeout;
+ s->step = 1;
+ s->val = FLASH_TIMEOUT_DEFAULT;
+}
+
+#if IS_ENABLED(CONFIG_V4L2_FLASH_LED_CLASS)
+static void sgm3140_init_v4l2_flash_config(struct sgm3140 *priv,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+ struct led_classdev *led_cdev = &priv->fled_cdev.led_cdev;
+ struct led_flash_setting *s;
+
+ strscpy(v4l2_sd_cfg->dev_name, led_cdev->dev->kobj.name,
+ sizeof(v4l2_sd_cfg->dev_name));
+
+ /* Init flash intensity setting */
+ s = &v4l2_sd_cfg->intensity;
+ s->min = 0;
+ s->max = 1;
+ s->step = 1;
+ s->val = 1;
+}
+
+#else
+static void sgm3140_init_v4l2_flash_config(struct sgm3140 *priv,
+ struct v4l2_flash_config *v4l2_sd_cfg)
+{
+}
+#endif
+
+static int sgm3140_probe(struct platform_device *pdev)
+{
+ struct sgm3140 *priv;
+ struct led_classdev *led_cdev;
+ struct led_classdev_flash *fled_cdev;
+ struct led_init_data init_data = {};
+ struct fwnode_handle *child_node;
+ struct v4l2_flash_config v4l2_sd_cfg = {};
+ int ret;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->flash_gpio = devm_gpiod_get(&pdev->dev, "flash", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(priv->flash_gpio);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request flash gpio\n");
+
+ priv->enable_gpio = devm_gpiod_get(&pdev->dev, "enable", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(priv->enable_gpio);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request enable gpio\n");
+
+ priv->vin_regulator = devm_regulator_get(&pdev->dev, "vin");
+ ret = PTR_ERR_OR_ZERO(priv->vin_regulator);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "Failed to request regulator\n");
+
+ child_node = device_get_next_child_node(&pdev->dev, NULL);
+ if (!child_node) {
+ dev_err(&pdev->dev,
+ "No fwnode child node found for connected LED.\n");
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(child_node, "flash-max-timeout-us",
+ &priv->max_timeout);
+ if (ret) {
+ priv->max_timeout = FLASH_MAX_TIMEOUT_DEFAULT;
+ dev_warn(&pdev->dev,
+ "flash-max-timeout-us property missing\n");
+ }
+
+ /*
+ * Set default timeout to FLASH_DEFAULT_TIMEOUT except if max_timeout
+ * from DT is lower.
+ */
+ priv->timeout = min(priv->max_timeout, FLASH_TIMEOUT_DEFAULT);
+
+ timer_setup(&priv->powerdown_timer, sgm3140_powerdown_timer, 0);
+
+ fled_cdev = &priv->fled_cdev;
+ led_cdev = &fled_cdev->led_cdev;
+
+ fled_cdev->ops = &sgm3140_flash_ops;
+
+ led_cdev->brightness_set_blocking = sgm3140_brightness_set;
+ led_cdev->max_brightness = LED_ON;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ sgm3140_init_flash_timeout(priv);
+
+ init_data.fwnode = child_node;
+
+ platform_set_drvdata(pdev, priv);
+
+ /* Register in the LED subsystem */
+ ret = devm_led_classdev_flash_register_ext(&pdev->dev,
+ fled_cdev, &init_data);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to register flash device: %d\n",
+ ret);
+ goto err;
+ }
+
+ sgm3140_init_v4l2_flash_config(priv, &v4l2_sd_cfg);
+
+ /* Create V4L2 Flash subdev */
+ priv->v4l2_flash = v4l2_flash_init(&pdev->dev,
+ child_node,
+ fled_cdev, NULL,
+ &v4l2_sd_cfg);
+ if (IS_ERR(priv->v4l2_flash)) {
+ ret = PTR_ERR(priv->v4l2_flash);
+ goto err;
+ }
+
+ return ret;
+
+err:
+ fwnode_handle_put(child_node);
+ return ret;
+}
+
+static void sgm3140_remove(struct platform_device *pdev)
+{
+ struct sgm3140 *priv = platform_get_drvdata(pdev);
+
+ timer_delete_sync(&priv->powerdown_timer);
+
+ v4l2_flash_release(priv->v4l2_flash);
+}
+
+static const struct of_device_id sgm3140_dt_match[] = {
+ { .compatible = "ocs,ocp8110" },
+ { .compatible = "richtek,rt5033-led" },
+ { .compatible = "sgmicro,sgm3140" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, sgm3140_dt_match);
+
+static struct platform_driver sgm3140_driver = {
+ .probe = sgm3140_probe,
+ .remove = sgm3140_remove,
+ .driver = {
+ .name = "sgm3140",
+ .of_match_table = sgm3140_dt_match,
+ },
+};
+
+module_platform_driver(sgm3140_driver);
+
+MODULE_AUTHOR("Luca Weiss <luca@z3ntu.xyz>");
+MODULE_DESCRIPTION("SG Micro SGM3140 charge pump LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/flash/leds-sy7802.c b/drivers/leds/flash/leds-sy7802.c
new file mode 100644
index 000000000000..ddac836762af
--- /dev/null
+++ b/drivers/leds/flash/leds-sy7802.c
@@ -0,0 +1,539 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Silergy SY7802 flash LED driver with an I2C interface
+ *
+ * Copyright 2024 André Apitzsch <git@apitzsch.eu>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/led-class-flash.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define SY7802_MAX_LEDS 2
+#define SY7802_LED_JOINT 2
+
+#define SY7802_REG_ENABLE 0x10
+#define SY7802_REG_TORCH_BRIGHTNESS 0xa0
+#define SY7802_REG_FLASH_BRIGHTNESS 0xb0
+#define SY7802_REG_FLASH_DURATION 0xc0
+#define SY7802_REG_FLAGS 0xd0
+#define SY7802_REG_CONFIG_1 0xe0
+#define SY7802_REG_CONFIG_2 0xf0
+#define SY7802_REG_VIN_MONITOR 0x80
+#define SY7802_REG_LAST_FLASH 0x81
+#define SY7802_REG_VLED_MONITOR 0x30
+#define SY7802_REG_ADC_DELAY 0x31
+#define SY7802_REG_DEV_ID 0xff
+
+#define SY7802_MODE_OFF 0
+#define SY7802_MODE_TORCH 2
+#define SY7802_MODE_FLASH 3
+#define SY7802_MODE_MASK GENMASK(1, 0)
+
+#define SY7802_LEDS_SHIFT 3
+#define SY7802_LEDS_MASK(_id) (BIT(_id) << SY7802_LEDS_SHIFT)
+#define SY7802_LEDS_MASK_ALL (SY7802_LEDS_MASK(0) | SY7802_LEDS_MASK(1))
+
+#define SY7802_TORCH_CURRENT_SHIFT 3
+#define SY7802_TORCH_CURRENT_MASK(_id) \
+ (GENMASK(2, 0) << (SY7802_TORCH_CURRENT_SHIFT * (_id)))
+#define SY7802_TORCH_CURRENT_MASK_ALL \
+ (SY7802_TORCH_CURRENT_MASK(0) | SY7802_TORCH_CURRENT_MASK(1))
+
+#define SY7802_FLASH_CURRENT_SHIFT 4
+#define SY7802_FLASH_CURRENT_MASK(_id) \
+ (GENMASK(3, 0) << (SY7802_FLASH_CURRENT_SHIFT * (_id)))
+#define SY7802_FLASH_CURRENT_MASK_ALL \
+ (SY7802_FLASH_CURRENT_MASK(0) | SY7802_FLASH_CURRENT_MASK(1))
+
+#define SY7802_TIMEOUT_DEFAULT_US 512000U
+#define SY7802_TIMEOUT_MIN_US 32000U
+#define SY7802_TIMEOUT_MAX_US 1024000U
+#define SY7802_TIMEOUT_STEPSIZE_US 32000U
+
+#define SY7802_TORCH_BRIGHTNESS_MAX 8
+
+#define SY7802_FLASH_BRIGHTNESS_DEFAULT 14
+#define SY7802_FLASH_BRIGHTNESS_MIN 0
+#define SY7802_FLASH_BRIGHTNESS_MAX 15
+#define SY7802_FLASH_BRIGHTNESS_STEP 1
+
+#define SY7802_FLAG_TIMEOUT BIT(0)
+#define SY7802_FLAG_THERMAL_SHUTDOWN BIT(1)
+#define SY7802_FLAG_LED_FAULT BIT(2)
+#define SY7802_FLAG_TX1_INTERRUPT BIT(3)
+#define SY7802_FLAG_TX2_INTERRUPT BIT(4)
+#define SY7802_FLAG_LED_THERMAL_FAULT BIT(5)
+#define SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW BIT(6)
+#define SY7802_FLAG_INPUT_VOLTAGE_LOW BIT(7)
+
+#define SY7802_CHIP_ID 0x51
+
+static const struct reg_default sy7802_regmap_defs[] = {
+ { SY7802_REG_ENABLE, SY7802_LEDS_MASK_ALL },
+ { SY7802_REG_TORCH_BRIGHTNESS, 0x92 },
+ { SY7802_REG_FLASH_BRIGHTNESS, SY7802_FLASH_BRIGHTNESS_DEFAULT |
+ SY7802_FLASH_BRIGHTNESS_DEFAULT << SY7802_FLASH_CURRENT_SHIFT },
+ { SY7802_REG_FLASH_DURATION, 0x6f },
+ { SY7802_REG_FLAGS, 0x0 },
+ { SY7802_REG_CONFIG_1, 0x68 },
+ { SY7802_REG_CONFIG_2, 0xf0 },
+};
+
+struct sy7802_led {
+ struct led_classdev_flash flash;
+ struct sy7802 *chip;
+ u8 led_id;
+};
+
+struct sy7802 {
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex mutex;
+
+ struct gpio_desc *enable_gpio;
+ struct regulator *vin_regulator;
+
+ unsigned int fled_strobe_used;
+ unsigned int fled_torch_used;
+ unsigned int leds_active;
+ int num_leds;
+ struct sy7802_led leds[] __counted_by(num_leds);
+};
+
+static int sy7802_torch_brightness_set(struct led_classdev *lcdev, enum led_brightness brightness)
+{
+ struct sy7802_led *led = container_of(lcdev, struct sy7802_led, flash.led_cdev);
+ struct sy7802 *chip = led->chip;
+ u32 fled_torch_used_tmp;
+ u32 led_enable_mask;
+ u32 enable_mask;
+ u32 torch_mask;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ if (chip->fled_strobe_used) {
+ dev_warn(chip->dev, "Cannot set torch brightness whilst strobe is enabled\n");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (brightness)
+ fled_torch_used_tmp = chip->fled_torch_used | BIT(led->led_id);
+ else
+ fled_torch_used_tmp = chip->fled_torch_used & ~BIT(led->led_id);
+
+ led_enable_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_LEDS_MASK_ALL :
+ SY7802_LEDS_MASK(led->led_id);
+
+ val = brightness ? led_enable_mask : SY7802_MODE_OFF;
+ if (fled_torch_used_tmp)
+ val |= SY7802_MODE_TORCH;
+
+ /* Disable torch to apply brightness */
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_MASK,
+ SY7802_MODE_OFF);
+ if (ret)
+ goto unlock;
+
+ torch_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_TORCH_CURRENT_MASK_ALL :
+ SY7802_TORCH_CURRENT_MASK(led->led_id);
+
+ /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */
+ if (brightness)
+ brightness -= 1;
+
+ brightness |= (brightness << SY7802_TORCH_CURRENT_SHIFT);
+
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, torch_mask, brightness);
+ if (ret)
+ goto unlock;
+
+ enable_mask = SY7802_MODE_MASK | led_enable_mask;
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
+ if (ret)
+ goto unlock;
+
+ chip->fled_torch_used = fled_torch_used_tmp;
+
+unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static int sy7802_flash_brightness_set(struct led_classdev_flash *fl_cdev, u32 brightness)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct led_flash_setting *s = &fl_cdev->brightness;
+ u32 val = (brightness - s->min) / s->step;
+ struct sy7802 *chip = led->chip;
+ u32 flash_mask;
+ int ret;
+
+ val |= (val << SY7802_FLASH_CURRENT_SHIFT);
+ flash_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_FLASH_CURRENT_MASK_ALL :
+ SY7802_FLASH_CURRENT_MASK(led->led_id);
+
+ mutex_lock(&chip->mutex);
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_FLASH_BRIGHTNESS, flash_mask, val);
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static int sy7802_strobe_set(struct led_classdev_flash *fl_cdev, bool state)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+ u32 fled_strobe_used_tmp;
+ u32 led_enable_mask;
+ u32 enable_mask;
+ u32 val;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ if (chip->fled_torch_used) {
+ dev_warn(chip->dev, "Cannot set strobe brightness whilst torch is enabled\n");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ if (state)
+ fled_strobe_used_tmp = chip->fled_strobe_used | BIT(led->led_id);
+ else
+ fled_strobe_used_tmp = chip->fled_strobe_used & ~BIT(led->led_id);
+
+ led_enable_mask = led->led_id == SY7802_LED_JOINT ?
+ SY7802_LEDS_MASK_ALL :
+ SY7802_LEDS_MASK(led->led_id);
+
+ val = state ? led_enable_mask : SY7802_MODE_OFF;
+ if (fled_strobe_used_tmp)
+ val |= SY7802_MODE_FLASH;
+
+ enable_mask = SY7802_MODE_MASK | led_enable_mask;
+ ret = regmap_update_bits(chip->regmap, SY7802_REG_ENABLE, enable_mask, val);
+
+ if (ret)
+ goto unlock;
+
+ chip->fled_strobe_used = fled_strobe_used_tmp;
+
+unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static int sy7802_strobe_get(struct led_classdev_flash *fl_cdev, bool *state)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+
+ mutex_lock(&chip->mutex);
+ *state = !!(chip->fled_strobe_used & BIT(led->led_id));
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+}
+
+static int sy7802_timeout_set(struct led_classdev_flash *fl_cdev, u32 timeout)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct led_flash_setting *s = &fl_cdev->timeout;
+ u32 val = (timeout - s->min) / s->step;
+ struct sy7802 *chip = led->chip;
+
+ return regmap_write(chip->regmap, SY7802_REG_FLASH_DURATION, val);
+}
+
+static int sy7802_fault_get(struct led_classdev_flash *fl_cdev, u32 *fault)
+{
+ struct sy7802_led *led = container_of(fl_cdev, struct sy7802_led, flash);
+ struct sy7802 *chip = led->chip;
+ u32 val, led_faults = 0;
+ int ret;
+
+ /* NOTE: reading register clears fault status */
+ ret = regmap_read(chip->regmap, SY7802_REG_FLAGS, &val);
+ if (ret)
+ return ret;
+
+ if (val & (SY7802_FLAG_FLASH_INPUT_VOLTAGE_LOW | SY7802_FLAG_INPUT_VOLTAGE_LOW))
+ led_faults |= LED_FAULT_INPUT_VOLTAGE;
+
+ if (val & SY7802_FLAG_THERMAL_SHUTDOWN)
+ led_faults |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (val & SY7802_FLAG_TIMEOUT)
+ led_faults |= LED_FAULT_TIMEOUT;
+
+ *fault = led_faults;
+ return 0;
+}
+
+static const struct led_flash_ops sy7802_flash_ops = {
+ .flash_brightness_set = sy7802_flash_brightness_set,
+ .strobe_set = sy7802_strobe_set,
+ .strobe_get = sy7802_strobe_get,
+ .timeout_set = sy7802_timeout_set,
+ .fault_get = sy7802_fault_get,
+};
+
+static void sy7802_init_flash_brightness(struct led_classdev_flash *fl_cdev)
+{
+ struct led_flash_setting *s;
+
+ /* Init flash brightness setting */
+ s = &fl_cdev->brightness;
+ s->min = SY7802_FLASH_BRIGHTNESS_MIN;
+ s->max = SY7802_FLASH_BRIGHTNESS_MAX;
+ s->step = SY7802_FLASH_BRIGHTNESS_STEP;
+ s->val = SY7802_FLASH_BRIGHTNESS_DEFAULT;
+}
+
+static void sy7802_init_flash_timeout(struct led_classdev_flash *fl_cdev)
+{
+ struct led_flash_setting *s;
+
+ /* Init flash timeout setting */
+ s = &fl_cdev->timeout;
+ s->min = SY7802_TIMEOUT_MIN_US;
+ s->max = SY7802_TIMEOUT_MAX_US;
+ s->step = SY7802_TIMEOUT_STEPSIZE_US;
+ s->val = SY7802_TIMEOUT_DEFAULT_US;
+}
+
+static int sy7802_led_register(struct device *dev, struct sy7802_led *led,
+ struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ int ret;
+
+ init_data.fwnode = of_fwnode_handle(np);
+
+ ret = devm_led_classdev_flash_register_ext(dev, &led->flash, &init_data);
+ if (ret) {
+ dev_err(dev, "Couldn't register flash %d\n", led->led_id);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sy7802_init_flash_properties(struct device *dev, struct sy7802_led *led,
+ struct device_node *np)
+{
+ struct led_classdev_flash *flash = &led->flash;
+ struct led_classdev *lcdev = &flash->led_cdev;
+ u32 sources[SY7802_MAX_LEDS];
+ int i, num, ret;
+
+ num = of_property_count_u32_elems(np, "led-sources");
+ if (num < 1) {
+ dev_err(dev, "Not specified or wrong number of led-sources\n");
+ return -EINVAL;
+ }
+
+ ret = of_property_read_u32_array(np, "led-sources", sources, num);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < num; i++) {
+ if (sources[i] >= SY7802_MAX_LEDS)
+ return -EINVAL;
+ if (led->chip->leds_active & BIT(sources[i]))
+ return -EINVAL;
+ led->chip->leds_active |= BIT(sources[i]);
+ }
+
+ /* If both channels are specified in 'led-sources', joint flash output mode is used */
+ led->led_id = num == 2 ? SY7802_LED_JOINT : sources[0];
+
+ lcdev->max_brightness = SY7802_TORCH_BRIGHTNESS_MAX;
+ lcdev->brightness_set_blocking = sy7802_torch_brightness_set;
+ lcdev->flags |= LED_DEV_CAP_FLASH;
+
+ flash->ops = &sy7802_flash_ops;
+
+ sy7802_init_flash_brightness(flash);
+ sy7802_init_flash_timeout(flash);
+
+ return 0;
+}
+
+static int sy7802_chip_check(struct sy7802 *chip)
+{
+ struct device *dev = chip->dev;
+ u32 chipid;
+ int ret;
+
+ ret = regmap_read(chip->regmap, SY7802_REG_DEV_ID, &chipid);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read chip ID\n");
+
+ if (chipid != SY7802_CHIP_ID)
+ return dev_err_probe(dev, -ENODEV, "Unsupported chip detected: %x\n", chipid);
+
+ return 0;
+}
+
+static void sy7802_enable(struct sy7802 *chip)
+{
+ gpiod_set_value_cansleep(chip->enable_gpio, 1);
+ usleep_range(200, 300);
+}
+
+static void sy7802_disable(struct sy7802 *chip)
+{
+ gpiod_set_value_cansleep(chip->enable_gpio, 0);
+}
+
+static int sy7802_probe_dt(struct sy7802 *chip)
+{
+ struct device_node *np = dev_of_node(chip->dev);
+ int child_num;
+ int ret;
+
+ regmap_write(chip->regmap, SY7802_REG_ENABLE, SY7802_MODE_OFF);
+ regmap_write(chip->regmap, SY7802_REG_TORCH_BRIGHTNESS, LED_OFF);
+
+ child_num = 0;
+ for_each_available_child_of_node_scoped(np, child) {
+ struct sy7802_led *led = chip->leds + child_num;
+
+ led->chip = chip;
+ led->led_id = child_num;
+
+ ret = sy7802_init_flash_properties(chip->dev, led, child);
+ if (ret)
+ return ret;
+
+ ret = sy7802_led_register(chip->dev, led, child);
+ if (ret)
+ return ret;
+
+ child_num++;
+ }
+ return 0;
+}
+
+static void sy7802_chip_disable_action(void *data)
+{
+ struct sy7802 *chip = data;
+
+ sy7802_disable(chip);
+}
+
+static void sy7802_regulator_disable_action(void *data)
+{
+ struct sy7802 *chip = data;
+
+ regulator_disable(chip->vin_regulator);
+}
+
+static const struct regmap_config sy7802_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xff,
+ .cache_type = REGCACHE_MAPLE,
+ .reg_defaults = sy7802_regmap_defs,
+ .num_reg_defaults = ARRAY_SIZE(sy7802_regmap_defs),
+};
+
+static int sy7802_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct sy7802 *chip;
+ size_t count;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > SY7802_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL, "Invalid amount of LED nodes %zu\n", count);
+
+ chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->num_leds = count;
+
+ chip->dev = dev;
+ i2c_set_clientdata(client, chip);
+
+ chip->enable_gpio = devm_gpiod_get(dev, "enable", GPIOD_OUT_LOW);
+ ret = PTR_ERR_OR_ZERO(chip->enable_gpio);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request enable gpio\n");
+
+ chip->vin_regulator = devm_regulator_get(dev, "vin");
+ ret = PTR_ERR_OR_ZERO(chip->vin_regulator);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to request regulator\n");
+
+ ret = regulator_enable(chip->vin_regulator);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to enable regulator\n");
+
+ ret = devm_add_action_or_reset(dev, sy7802_regulator_disable_action, chip);
+ if (ret)
+ return ret;
+
+ ret = devm_mutex_init(dev, &chip->mutex);
+ if (ret)
+ return ret;
+
+ mutex_lock(&chip->mutex);
+
+ chip->regmap = devm_regmap_init_i2c(client, &sy7802_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err_probe(dev, ret, "Failed to allocate register map\n");
+ goto error;
+ }
+
+ ret = sy7802_probe_dt(chip);
+ if (ret < 0)
+ goto error;
+
+ sy7802_enable(chip);
+
+ ret = devm_add_action_or_reset(dev, sy7802_chip_disable_action, chip);
+ if (ret)
+ goto error;
+
+ ret = sy7802_chip_check(chip);
+
+error:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static const struct of_device_id __maybe_unused sy7802_leds_match[] = {
+ { .compatible = "silergy,sy7802", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sy7802_leds_match);
+
+static struct i2c_driver sy7802_driver = {
+ .driver = {
+ .name = "sy7802",
+ .of_match_table = of_match_ptr(sy7802_leds_match),
+ },
+ .probe = sy7802_probe,
+};
+module_i2c_driver(sy7802_driver);
+
+MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>");
+MODULE_DESCRIPTION("Silergy SY7802 flash LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/flash/leds-tps6131x.c b/drivers/leds/flash/leds-tps6131x.c
new file mode 100644
index 000000000000..f0f1f2b77d5a
--- /dev/null
+++ b/drivers/leds/flash/leds-tps6131x.c
@@ -0,0 +1,815 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Texas Instruments TPS61310/TPS61311 flash LED driver with I2C interface
+ *
+ * Copyright 2025 Matthias Fend <matthias.fend@emfend.at>
+ */
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <media/v4l2-flash-led-class.h>
+
+#define TPS6131X_REG_0 0x00
+#define TPS6131X_REG_0_RESET BIT(7)
+#define TPS6131X_REG_0_DCLC13 GENMASK(5, 3)
+#define TPS6131X_REG_0_DCLC13_SHIFT 3
+#define TPS6131X_REG_0_DCLC2 GENMASK(2, 0)
+#define TPS6131X_REG_0_DCLC2_SHIFT 0
+
+#define TPS6131X_REG_1 0x01
+#define TPS6131X_REG_1_MODE GENMASK(7, 6)
+#define TPS6131X_REG_1_MODE_SHIFT 6
+#define TPS6131X_REG_1_FC2 GENMASK(5, 0)
+#define TPS6131X_REG_1_FC2_SHIFT 0
+
+#define TPS6131X_REG_2 0x02
+#define TPS6131X_REG_2_MODE GENMASK(7, 6)
+#define TPS6131X_REG_2_MODE_SHIFT 6
+#define TPS6131X_REG_2_ENVM BIT(5)
+#define TPS6131X_REG_2_FC13 GENMASK(4, 0)
+#define TPS6131X_REG_2_FC13_SHIFT 0
+
+#define TPS6131X_REG_3 0x03
+#define TPS6131X_REG_3_STIM GENMASK(7, 5)
+#define TPS6131X_REG_3_STIM_SHIFT 5
+#define TPS6131X_REG_3_HPFL BIT(4)
+#define TPS6131X_REG_3_SELSTIM_TO BIT(3)
+#define TPS6131X_REG_3_STT BIT(2)
+#define TPS6131X_REG_3_SFT BIT(1)
+#define TPS6131X_REG_3_TXMASK BIT(0)
+
+#define TPS6131X_REG_4 0x04
+#define TPS6131X_REG_4_PG BIT(7)
+#define TPS6131X_REG_4_HOTDIE_HI BIT(6)
+#define TPS6131X_REG_4_HOTDIE_LO BIT(5)
+#define TPS6131X_REG_4_ILIM BIT(4)
+#define TPS6131X_REG_4_INDC GENMASK(3, 0)
+#define TPS6131X_REG_4_INDC_SHIFT 0
+
+#define TPS6131X_REG_5 0x05
+#define TPS6131X_REG_5_SELFCAL BIT(7)
+#define TPS6131X_REG_5_ENPSM BIT(6)
+#define TPS6131X_REG_5_STSTRB1_DIR BIT(5)
+#define TPS6131X_REG_5_GPIO BIT(4)
+#define TPS6131X_REG_5_GPIOTYPE BIT(3)
+#define TPS6131X_REG_5_ENLED3 BIT(2)
+#define TPS6131X_REG_5_ENLED2 BIT(1)
+#define TPS6131X_REG_5_ENLED1 BIT(0)
+
+#define TPS6131X_REG_6 0x06
+#define TPS6131X_REG_6_ENTS BIT(7)
+#define TPS6131X_REG_6_LEDHOT BIT(6)
+#define TPS6131X_REG_6_LEDWARN BIT(5)
+#define TPS6131X_REG_6_LEDHDR BIT(4)
+#define TPS6131X_REG_6_OV GENMASK(3, 0)
+#define TPS6131X_REG_6_OV_SHIFT 0
+
+#define TPS6131X_REG_7 0x07
+#define TPS6131X_REG_7_ENBATMON BIT(7)
+#define TPS6131X_REG_7_BATDROOP GENMASK(6, 4)
+#define TPS6131X_REG_7_BATDROOP_SHIFT 4
+#define TPS6131X_REG_7_REVID GENMASK(2, 0)
+#define TPS6131X_REG_7_REVID_SHIFT 0
+
+#define TPS6131X_MAX_CHANNELS 3
+
+#define TPS6131X_FLASH_MAX_I_CHAN13_MA 400
+#define TPS6131X_FLASH_MAX_I_CHAN2_MA 800
+#define TPS6131X_FLASH_STEP_I_MA 25
+
+#define TPS6131X_TORCH_MAX_I_CHAN13_MA 175
+#define TPS6131X_TORCH_MAX_I_CHAN2_MA 175
+#define TPS6131X_TORCH_STEP_I_MA 25
+
+/* The torch watchdog timer must be refreshed within an interval of 13 seconds. */
+#define TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES msecs_to_jiffies(10000)
+
+#define UA_TO_MA(UA) ((UA) / 1000)
+
+enum tps6131x_mode {
+ TPS6131X_MODE_SHUTDOWN = 0x0,
+ TPS6131X_MODE_TORCH = 0x1,
+ TPS6131X_MODE_FLASH = 0x2,
+};
+
+struct tps6131x {
+ struct device *dev;
+ struct regmap *regmap;
+ struct gpio_desc *reset_gpio;
+ /*
+ * Registers 0, 1, 2, and 3 control parts of the controller that are not completely
+ * independent of each other. Since some operations require the registers to be written in
+ * a specific order to avoid unwanted side effects, they are synchronized with a lock.
+ */
+ struct mutex lock; /* Hardware access lock for register 0, 1, 2 and 3 */
+ struct delayed_work torch_refresh_work;
+ bool valley_current_limit;
+ bool chan1_en;
+ bool chan2_en;
+ bool chan3_en;
+ struct fwnode_handle *led_node;
+ u32 max_flash_current_ma;
+ u32 step_flash_current_ma;
+ u32 max_torch_current_ma;
+ u32 step_torch_current_ma;
+ u32 max_timeout_us;
+ struct led_classdev_flash fled_cdev;
+ struct v4l2_flash *v4l2_flash;
+};
+
+static struct tps6131x *fled_cdev_to_tps6131x(struct led_classdev_flash *fled_cdev)
+{
+ return container_of(fled_cdev, struct tps6131x, fled_cdev);
+}
+
+/*
+ * Register contents after a power on/reset. These values cannot be changed.
+ */
+
+#define TPS6131X_DCLC2_50MA 2
+#define TPS6131X_DCLC13_25MA 1
+#define TPS6131X_FC2_400MA 16
+#define TPS6131X_FC13_200MA 8
+#define TPS6131X_STIM_0_579MS_1_37MS 6
+#define TPS6131X_SELSTIM_RANGE0 0
+#define TPS6131X_INDC_OFF 0
+#define TPS6131X_OV_4950MV 9
+#define TPS6131X_BATDROOP_150MV 4
+
+static const struct reg_default tps6131x_regmap_defaults[] = {
+ { TPS6131X_REG_0, (TPS6131X_DCLC13_25MA << TPS6131X_REG_0_DCLC13_SHIFT) |
+ (TPS6131X_DCLC2_50MA << TPS6131X_REG_0_DCLC2_SHIFT) },
+ { TPS6131X_REG_1, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_1_MODE_SHIFT) |
+ (TPS6131X_FC2_400MA << TPS6131X_REG_1_FC2_SHIFT) },
+ { TPS6131X_REG_2, (TPS6131X_MODE_SHUTDOWN << TPS6131X_REG_2_MODE_SHIFT) |
+ (TPS6131X_FC13_200MA << TPS6131X_REG_2_FC13_SHIFT) },
+ { TPS6131X_REG_3, (TPS6131X_STIM_0_579MS_1_37MS << TPS6131X_REG_3_STIM_SHIFT) |
+ (TPS6131X_SELSTIM_RANGE0 << TPS6131X_REG_3_SELSTIM_TO) |
+ TPS6131X_REG_3_TXMASK },
+ { TPS6131X_REG_4, (TPS6131X_INDC_OFF << TPS6131X_REG_4_INDC_SHIFT) },
+ { TPS6131X_REG_5, TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR |
+ TPS6131X_REG_5_GPIOTYPE | TPS6131X_REG_5_ENLED2 },
+ { TPS6131X_REG_6, (TPS6131X_OV_4950MV << TPS6131X_REG_6_OV_SHIFT) },
+ { TPS6131X_REG_7, (TPS6131X_BATDROOP_150MV << TPS6131X_REG_7_BATDROOP_SHIFT) },
+};
+
+/*
+ * These registers contain flags that are reset when read.
+ */
+static bool tps6131x_regmap_precious(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case TPS6131X_REG_3:
+ case TPS6131X_REG_4:
+ case TPS6131X_REG_6:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static const struct regmap_config tps6131x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = TPS6131X_REG_7,
+ .reg_defaults = tps6131x_regmap_defaults,
+ .num_reg_defaults = ARRAY_SIZE(tps6131x_regmap_defaults),
+ .cache_type = REGCACHE_FLAT,
+ .precious_reg = &tps6131x_regmap_precious,
+};
+
+struct tps6131x_timer_config {
+ u8 val;
+ u8 range;
+ u32 time_us;
+};
+
+static const struct tps6131x_timer_config tps6131x_timer_configs[] = {
+ { .val = 0, .range = 1, .time_us = 5300 },
+ { .val = 1, .range = 1, .time_us = 10700 },
+ { .val = 2, .range = 1, .time_us = 16000 },
+ { .val = 3, .range = 1, .time_us = 21300 },
+ { .val = 4, .range = 1, .time_us = 26600 },
+ { .val = 5, .range = 1, .time_us = 32000 },
+ { .val = 6, .range = 1, .time_us = 37300 },
+ { .val = 0, .range = 0, .time_us = 68200 },
+ { .val = 7, .range = 1, .time_us = 71500 },
+ { .val = 1, .range = 0, .time_us = 102200 },
+ { .val = 2, .range = 0, .time_us = 136300 },
+ { .val = 3, .range = 0, .time_us = 170400 },
+ { .val = 4, .range = 0, .time_us = 204500 },
+ { .val = 5, .range = 0, .time_us = 340800 },
+ { .val = 6, .range = 0, .time_us = 579300 },
+ { .val = 7, .range = 0, .time_us = 852000 },
+};
+
+static const struct tps6131x_timer_config *tps6131x_find_closest_timer_config(u32 timeout_us)
+{
+ const struct tps6131x_timer_config *timer_config = &tps6131x_timer_configs[0];
+ u32 diff, min_diff = U32_MAX;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(tps6131x_timer_configs); i++) {
+ diff = abs(tps6131x_timer_configs[i].time_us - timeout_us);
+ if (diff < min_diff) {
+ timer_config = &tps6131x_timer_configs[i];
+ min_diff = diff;
+ if (!min_diff)
+ break;
+ }
+ }
+
+ return timer_config;
+}
+
+static int tps6131x_reset_chip(struct tps6131x *tps6131x)
+{
+ int ret;
+
+ if (tps6131x->reset_gpio) {
+ gpiod_set_value_cansleep(tps6131x->reset_gpio, 1);
+ fsleep(10);
+ gpiod_set_value_cansleep(tps6131x->reset_gpio, 0);
+ fsleep(100);
+ } else {
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET,
+ TPS6131X_REG_0_RESET);
+ if (ret)
+ return ret;
+
+ fsleep(100);
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0, TPS6131X_REG_0_RESET, 0);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps6131x_init_chip(struct tps6131x *tps6131x)
+{
+ u32 val;
+ int ret;
+
+ val = tps6131x->valley_current_limit ? TPS6131X_REG_4_ILIM : 0;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_4, val);
+ if (ret)
+ return ret;
+
+ val = TPS6131X_REG_5_ENPSM | TPS6131X_REG_5_STSTRB1_DIR | TPS6131X_REG_5_GPIOTYPE;
+
+ if (tps6131x->chan1_en)
+ val |= TPS6131X_REG_5_ENLED1;
+
+ if (tps6131x->chan2_en)
+ val |= TPS6131X_REG_5_ENLED2;
+
+ if (tps6131x->chan3_en)
+ val |= TPS6131X_REG_5_ENLED3;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_5, val);
+ if (ret)
+ return ret;
+
+ val = TPS6131X_REG_6_ENTS;
+
+ ret = regmap_write(tps6131x->regmap, TPS6131X_REG_6, val);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_set_mode(struct tps6131x *tps6131x, enum tps6131x_mode mode, bool force)
+{
+ u8 val = mode << TPS6131X_REG_1_MODE_SHIFT;
+
+ return regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_MODE, val,
+ NULL, false, force);
+}
+
+static void tps6131x_torch_refresh_handler(struct work_struct *work)
+{
+ struct tps6131x *tps6131x = container_of(work, struct tps6131x, torch_refresh_work.work);
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = tps6131x_set_mode(tps6131x, TPS6131X_MODE_TORCH, true);
+ if (ret < 0) {
+ dev_err(tps6131x->dev, "Failed to refresh torch watchdog timer\n");
+ return;
+ }
+
+ schedule_delayed_work(&tps6131x->torch_refresh_work,
+ TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
+}
+
+static int tps6131x_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(cdev);
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u32 num_chans, steps_chan13, steps_chan2, steps_remaining;
+ u8 reg0;
+ int ret;
+
+ cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
+
+ /*
+ * The brightness parameter uses the number of current steps as the unit (not the current
+ * value itself). Since the reported step size can vary depending on the configuration,
+ * this value must be converted into actual register steps.
+ */
+ steps_remaining = (brightness * tps6131x->step_torch_current_ma) / TPS6131X_TORCH_STEP_I_MA;
+
+ num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
+
+ /*
+ * The currents are distributed as evenly as possible across the activated channels.
+ * Since channels 1 and 3 share the same register setting, they always use the same current
+ * value. Channel 2 supports higher currents and thus takes over the remaining additional
+ * portion that cannot be covered by the other channels.
+ */
+ steps_chan13 = min_t(u32, steps_remaining / num_chans,
+ TPS6131X_TORCH_MAX_I_CHAN13_MA / TPS6131X_TORCH_STEP_I_MA);
+ if (tps6131x->chan1_en)
+ steps_remaining -= steps_chan13;
+ if (tps6131x->chan3_en)
+ steps_remaining -= steps_chan13;
+
+ steps_chan2 = min_t(u32, steps_remaining,
+ TPS6131X_TORCH_MAX_I_CHAN2_MA / TPS6131X_TORCH_STEP_I_MA);
+
+ guard(mutex)(&tps6131x->lock);
+
+ reg0 = (steps_chan13 << TPS6131X_REG_0_DCLC13_SHIFT) |
+ (steps_chan2 << TPS6131X_REG_0_DCLC2_SHIFT);
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_0,
+ TPS6131X_REG_0_DCLC13 | TPS6131X_REG_0_DCLC2, reg0);
+ if (ret < 0)
+ return ret;
+
+ ret = tps6131x_set_mode(tps6131x, brightness ? TPS6131X_MODE_TORCH : TPS6131X_MODE_SHUTDOWN,
+ true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * In order to use both the flash and the video light functions purely via the I2C
+ * interface, STRB1 must be low. If STRB1 is low, then the video light watchdog timer
+ * is also active, which puts the device into the shutdown state after around 13 seconds.
+ * To prevent this, the mode must be refreshed within the watchdog timeout.
+ */
+ if (brightness)
+ schedule_delayed_work(&tps6131x->torch_refresh_work,
+ TPS6131X_TORCH_REFRESH_INTERVAL_JIFFIES);
+
+ return 0;
+}
+
+static int tps6131x_strobe_set(struct led_classdev_flash *fled_cdev, bool state)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = tps6131x_set_mode(tps6131x, state ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
+ true);
+ if (ret < 0)
+ return ret;
+
+ if (state) {
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT,
+ TPS6131X_REG_3_SFT, NULL, false, true);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_3, TPS6131X_REG_3_SFT, 0, NULL,
+ false, true);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_flash_brightness_set(struct led_classdev_flash *fled_cdev, u32 brightness)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u32 num_chans;
+ u32 steps_chan13, steps_chan2;
+ u32 steps_remaining;
+ int ret;
+
+ steps_remaining = brightness / TPS6131X_FLASH_STEP_I_MA;
+ num_chans = tps6131x->chan1_en + tps6131x->chan2_en + tps6131x->chan3_en;
+ steps_chan13 = min_t(u32, steps_remaining / num_chans,
+ TPS6131X_FLASH_MAX_I_CHAN13_MA / TPS6131X_FLASH_STEP_I_MA);
+ if (tps6131x->chan1_en)
+ steps_remaining -= steps_chan13;
+ if (tps6131x->chan3_en)
+ steps_remaining -= steps_chan13;
+ steps_chan2 = min_t(u32, steps_remaining,
+ TPS6131X_FLASH_MAX_I_CHAN2_MA / TPS6131X_FLASH_STEP_I_MA);
+
+ guard(mutex)(&tps6131x->lock);
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_2, TPS6131X_REG_2_FC13,
+ steps_chan13 << TPS6131X_REG_2_FC13_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_1, TPS6131X_REG_1_FC2,
+ steps_chan2 << TPS6131X_REG_1_FC2_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ fled_cdev->brightness.val = brightness;
+
+ return 0;
+}
+
+static int tps6131x_flash_timeout_set(struct led_classdev_flash *fled_cdev, u32 timeout_us)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ u8 reg3;
+ int ret;
+
+ guard(mutex)(&tps6131x->lock);
+
+ timer_config = tps6131x_find_closest_timer_config(timeout_us);
+
+ reg3 = timer_config->val << TPS6131X_REG_3_STIM_SHIFT;
+ if (timer_config->range)
+ reg3 |= TPS6131X_REG_3_SELSTIM_TO;
+
+ ret = regmap_update_bits(tps6131x->regmap, TPS6131X_REG_3,
+ TPS6131X_REG_3_STIM | TPS6131X_REG_3_SELSTIM_TO, reg3);
+ if (ret < 0)
+ return ret;
+
+ fled_cdev->timeout.val = timer_config->time_us;
+
+ return 0;
+}
+
+static int tps6131x_strobe_get(struct led_classdev_flash *fled_cdev, bool *state)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ unsigned int reg3;
+ int ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, &reg3);
+ if (ret)
+ return ret;
+
+ *state = !!(reg3 & TPS6131X_REG_3_SFT);
+
+ return 0;
+}
+
+static int tps6131x_flash_fault_get(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+ unsigned int reg3, reg4, reg6;
+ int ret;
+
+ *fault = 0;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_3, &reg3);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_4, &reg4);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read_bypassed(tps6131x->regmap, TPS6131X_REG_6, &reg6);
+ if (ret < 0)
+ return ret;
+
+ if (reg3 & TPS6131X_REG_3_HPFL)
+ *fault |= LED_FAULT_SHORT_CIRCUIT;
+
+ if (reg3 & TPS6131X_REG_3_SELSTIM_TO)
+ *fault |= LED_FAULT_TIMEOUT;
+
+ if (reg4 & TPS6131X_REG_4_HOTDIE_HI)
+ *fault |= LED_FAULT_OVER_TEMPERATURE;
+
+ if (reg6 & (TPS6131X_REG_6_LEDHOT | TPS6131X_REG_6_LEDWARN))
+ *fault |= LED_FAULT_LED_OVER_TEMPERATURE;
+
+ if (!(reg6 & TPS6131X_REG_6_LEDHDR))
+ *fault |= LED_FAULT_UNDER_VOLTAGE;
+
+ if (reg6 & TPS6131X_REG_6_LEDHOT) {
+ ret = regmap_update_bits_base(tps6131x->regmap, TPS6131X_REG_6,
+ TPS6131X_REG_6_LEDHOT, 0, NULL, false, true);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct led_flash_ops flash_ops = {
+ .flash_brightness_set = tps6131x_flash_brightness_set,
+ .strobe_set = tps6131x_strobe_set,
+ .strobe_get = tps6131x_strobe_get,
+ .timeout_set = tps6131x_flash_timeout_set,
+ .fault_get = tps6131x_flash_fault_get,
+};
+
+static int tps6131x_parse_node(struct tps6131x *tps6131x)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct device *dev = tps6131x->dev;
+ u32 channels[TPS6131X_MAX_CHANNELS];
+ u32 current_step_multiplier;
+ u32 current_ua;
+ u32 max_current_flash_ma, max_current_torch_ma;
+ u32 timeout_us;
+ int num_channels;
+ int i;
+ int ret;
+
+ tps6131x->valley_current_limit = device_property_read_bool(dev, "ti,valley-current-limit");
+
+ tps6131x->led_node = device_get_next_child_node(dev, NULL);
+ if (!tps6131x->led_node) {
+ dev_err(dev, "Missing LED node\n");
+ return -EINVAL;
+ }
+
+ num_channels = fwnode_property_count_u32(tps6131x->led_node, "led-sources");
+ if (num_channels <= 0) {
+ dev_err(dev, "Failed to read led-sources property\n");
+ return -EINVAL;
+ }
+
+ if (num_channels > TPS6131X_MAX_CHANNELS) {
+ dev_err(dev, "led-sources count %u exceeds maximum channel count %u\n",
+ num_channels, TPS6131X_MAX_CHANNELS);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32_array(tps6131x->led_node, "led-sources", channels,
+ num_channels);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read led-sources property\n");
+ return ret;
+ }
+
+ max_current_flash_ma = 0;
+ max_current_torch_ma = 0;
+ for (i = 0; i < num_channels; i++) {
+ switch (channels[i]) {
+ case 1:
+ tps6131x->chan1_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
+ break;
+ case 2:
+ tps6131x->chan2_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN2_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN2_MA;
+ break;
+ case 3:
+ tps6131x->chan3_en = true;
+ max_current_flash_ma += TPS6131X_FLASH_MAX_I_CHAN13_MA;
+ max_current_torch_ma += TPS6131X_TORCH_MAX_I_CHAN13_MA;
+ break;
+ default:
+ dev_err(dev, "led-source out of range [1-3]\n");
+ return -EINVAL;
+ }
+ }
+
+ /*
+ * If only channels 1 and 3 are used, the step size is doubled because the two channels
+ * share the same current control register.
+ */
+ current_step_multiplier =
+ (tps6131x->chan1_en && tps6131x->chan3_en && !tps6131x->chan2_en) ? 2 : 1;
+ tps6131x->step_flash_current_ma = current_step_multiplier * TPS6131X_FLASH_STEP_I_MA;
+ tps6131x->step_torch_current_ma = current_step_multiplier * TPS6131X_TORCH_STEP_I_MA;
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "led-max-microamp", &current_ua);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read led-max-microamp property\n");
+ return ret;
+ }
+
+ tps6131x->max_torch_current_ma = UA_TO_MA(current_ua);
+
+ if (!tps6131x->max_torch_current_ma ||
+ tps6131x->max_torch_current_ma > max_current_torch_ma ||
+ (tps6131x->max_torch_current_ma % tps6131x->step_torch_current_ma)) {
+ dev_err(dev, "led-max-microamp out of range or not a multiple of %u\n",
+ tps6131x->step_torch_current_ma);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-microamp", &current_ua);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read flash-max-microamp property\n");
+ return ret;
+ }
+
+ tps6131x->max_flash_current_ma = UA_TO_MA(current_ua);
+
+ if (!tps6131x->max_flash_current_ma ||
+ tps6131x->max_flash_current_ma > max_current_flash_ma ||
+ (tps6131x->max_flash_current_ma % tps6131x->step_flash_current_ma)) {
+ dev_err(dev, "flash-max-microamp out of range or not a multiple of %u\n",
+ tps6131x->step_flash_current_ma);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(tps6131x->led_node, "flash-max-timeout-us", &timeout_us);
+ if (ret < 0) {
+ dev_err(dev, "Failed to read flash-max-timeout-us property\n");
+ return ret;
+ }
+
+ timer_config = tps6131x_find_closest_timer_config(timeout_us);
+ tps6131x->max_timeout_us = timer_config->time_us;
+
+ if (tps6131x->max_timeout_us != timeout_us)
+ dev_warn(dev, "flash-max-timeout-us %u not supported (using %u)\n", timeout_us,
+ tps6131x->max_timeout_us);
+
+ return 0;
+}
+
+static int tps6131x_led_class_setup(struct tps6131x *tps6131x)
+{
+ const struct tps6131x_timer_config *timer_config;
+ struct led_classdev *led_cdev;
+ struct led_flash_setting *setting;
+ struct led_init_data init_data = {};
+ int ret;
+
+ tps6131x->fled_cdev.ops = &flash_ops;
+
+ setting = &tps6131x->fled_cdev.timeout;
+ timer_config = tps6131x_find_closest_timer_config(0);
+ setting->min = timer_config->time_us;
+ setting->max = tps6131x->max_timeout_us;
+ setting->step = 1; /* Only some specific time periods are supported. No fixed step size. */
+ setting->val = setting->min;
+
+ setting = &tps6131x->fled_cdev.brightness;
+ setting->min = tps6131x->step_flash_current_ma;
+ setting->max = tps6131x->max_flash_current_ma;
+ setting->step = tps6131x->step_flash_current_ma;
+ setting->val = setting->min;
+
+ led_cdev = &tps6131x->fled_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = tps6131x_brightness_set;
+ led_cdev->max_brightness = tps6131x->max_torch_current_ma;
+ led_cdev->flags |= LED_DEV_CAP_FLASH;
+
+ init_data.fwnode = tps6131x->led_node;
+ init_data.devicename = NULL;
+ init_data.default_label = NULL;
+ init_data.devname_mandatory = false;
+
+ ret = devm_led_classdev_flash_register_ext(tps6131x->dev, &tps6131x->fled_cdev,
+ &init_data);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int tps6131x_flash_external_strobe_set(struct v4l2_flash *v4l2_flash, bool enable)
+{
+ struct led_classdev_flash *fled_cdev = v4l2_flash->fled_cdev;
+ struct tps6131x *tps6131x = fled_cdev_to_tps6131x(fled_cdev);
+
+ guard(mutex)(&tps6131x->lock);
+
+ return tps6131x_set_mode(tps6131x, enable ? TPS6131X_MODE_FLASH : TPS6131X_MODE_SHUTDOWN,
+ false);
+}
+
+static const struct v4l2_flash_ops tps6131x_v4l2_flash_ops = {
+ .external_strobe_set = tps6131x_flash_external_strobe_set,
+};
+
+static int tps6131x_v4l2_setup(struct tps6131x *tps6131x)
+{
+ struct v4l2_flash_config v4l2_cfg = { 0 };
+ struct led_flash_setting *intensity = &v4l2_cfg.intensity;
+
+ intensity->min = tps6131x->step_torch_current_ma;
+ intensity->max = tps6131x->max_torch_current_ma;
+ intensity->step = tps6131x->step_torch_current_ma;
+ intensity->val = intensity->min;
+
+ strscpy(v4l2_cfg.dev_name, tps6131x->fled_cdev.led_cdev.dev->kobj.name,
+ sizeof(v4l2_cfg.dev_name));
+
+ v4l2_cfg.has_external_strobe = true;
+ v4l2_cfg.flash_faults = LED_FAULT_TIMEOUT | LED_FAULT_OVER_TEMPERATURE |
+ LED_FAULT_SHORT_CIRCUIT | LED_FAULT_UNDER_VOLTAGE |
+ LED_FAULT_LED_OVER_TEMPERATURE;
+
+ tps6131x->v4l2_flash = v4l2_flash_init(tps6131x->dev, tps6131x->led_node,
+ &tps6131x->fled_cdev, &tps6131x_v4l2_flash_ops,
+ &v4l2_cfg);
+ if (IS_ERR(tps6131x->v4l2_flash)) {
+ dev_err(tps6131x->dev, "Failed to initialize v4l2 flash LED\n");
+ return PTR_ERR(tps6131x->v4l2_flash);
+ }
+
+ return 0;
+}
+
+static int tps6131x_probe(struct i2c_client *client)
+{
+ struct tps6131x *tps6131x;
+ int ret;
+
+ tps6131x = devm_kzalloc(&client->dev, sizeof(*tps6131x), GFP_KERNEL);
+ if (!tps6131x)
+ return -ENOMEM;
+
+ tps6131x->dev = &client->dev;
+ i2c_set_clientdata(client, tps6131x);
+ mutex_init(&tps6131x->lock);
+ INIT_DELAYED_WORK(&tps6131x->torch_refresh_work, tps6131x_torch_refresh_handler);
+
+ ret = tps6131x_parse_node(tps6131x);
+ if (ret)
+ return ret;
+
+ tps6131x->regmap = devm_regmap_init_i2c(client, &tps6131x_regmap);
+ if (IS_ERR(tps6131x->regmap)) {
+ ret = PTR_ERR(tps6131x->regmap);
+ return dev_err_probe(&client->dev, ret, "Failed to allocate register map\n");
+ }
+
+ tps6131x->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset", GPIOD_OUT_HIGH);
+ if (IS_ERR(tps6131x->reset_gpio)) {
+ ret = PTR_ERR(tps6131x->reset_gpio);
+ return dev_err_probe(&client->dev, ret, "Failed to get reset GPIO\n");
+ }
+
+ ret = tps6131x_reset_chip(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to reset LED controller\n");
+
+ ret = tps6131x_init_chip(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to initialize LED controller\n");
+
+ ret = tps6131x_led_class_setup(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to setup LED class\n");
+
+ ret = tps6131x_v4l2_setup(tps6131x);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to setup v4l2 flash\n");
+
+ return 0;
+}
+
+static void tps6131x_remove(struct i2c_client *client)
+{
+ struct tps6131x *tps6131x = i2c_get_clientdata(client);
+
+ v4l2_flash_release(tps6131x->v4l2_flash);
+
+ cancel_delayed_work_sync(&tps6131x->torch_refresh_work);
+}
+
+static const struct of_device_id of_tps6131x_leds_match[] = {
+ { .compatible = "ti,tps61310" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_tps6131x_leds_match);
+
+static struct i2c_driver tps6131x_i2c_driver = {
+ .driver = {
+ .name = "tps6131x",
+ .of_match_table = of_tps6131x_leds_match,
+ },
+ .probe = tps6131x_probe,
+ .remove = tps6131x_remove,
+};
+module_i2c_driver(tps6131x_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments TPS6131X flash LED driver");
+MODULE_AUTHOR("Matthias Fend <matthias.fend@emfend.at>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c
new file mode 100644
index 000000000000..165035a8826c
--- /dev/null
+++ b/drivers/leds/led-class-flash.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Flash class interface
+ *
+ * Copyright (C) 2015 Samsung Electronics Co., Ltd.
+ * Author: Jacek Anaszewski <j.anaszewski@samsung.com>
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/led-class-flash.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+
+#define has_flash_op(fled_cdev, op) \
+ (fled_cdev && fled_cdev->ops->op)
+
+#define call_flash_op(fled_cdev, op, args...) \
+ ((has_flash_op(fled_cdev, op)) ? \
+ (fled_cdev->ops->op(fled_cdev, args)) : \
+ -EINVAL)
+
+static const char * const led_flash_fault_names[] = {
+ "led-over-voltage",
+ "flash-timeout-exceeded",
+ "controller-over-temperature",
+ "controller-short-circuit",
+ "led-power-supply-over-current",
+ "indicator-led-fault",
+ "led-under-voltage",
+ "controller-under-voltage",
+ "led-over-temperature",
+};
+
+static ssize_t flash_brightness_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ unsigned long state;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_brightness(fled_cdev, state);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+
+ /* no lock needed for this */
+ led_update_flash_brightness(fled_cdev);
+
+ return sprintf(buf, "%u\n", fled_cdev->brightness.val);
+}
+static DEVICE_ATTR_RW(flash_brightness);
+
+static ssize_t max_flash_brightness_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+
+ return sprintf(buf, "%u\n", fled_cdev->brightness.max);
+}
+static DEVICE_ATTR_RO(max_flash_brightness);
+
+static ssize_t flash_strobe_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ unsigned long state;
+ ssize_t ret = -EBUSY;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev))
+ goto unlock;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ goto unlock;
+
+ if (state > 1) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+
+ ret = led_set_flash_strobe(fled_cdev, state);
+ if (ret < 0)
+ goto unlock;
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_strobe_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ bool state;
+ int ret;
+
+ /* no lock needed for this */
+ ret = led_get_flash_strobe(fled_cdev, &state);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%u\n", state);
+}
+static DEVICE_ATTR_RW(flash_strobe);
+
+static ssize_t flash_timeout_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ unsigned long flash_timeout;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ ret = kstrtoul(buf, 10, &flash_timeout);
+ if (ret)
+ goto unlock;
+
+ ret = led_set_flash_timeout(fled_cdev, flash_timeout);
+ if (ret < 0)
+ goto unlock;
+
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t flash_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+
+ return sprintf(buf, "%u\n", fled_cdev->timeout.val);
+}
+static DEVICE_ATTR_RW(flash_timeout);
+
+static ssize_t max_flash_timeout_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+
+ return sprintf(buf, "%u\n", fled_cdev->timeout.max);
+}
+static DEVICE_ATTR_RO(max_flash_timeout);
+
+static ssize_t flash_fault_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+ u32 fault, mask = 0x1;
+ char *pbuf = buf;
+ int i, ret, buf_len;
+
+ ret = led_get_flash_fault(fled_cdev, &fault);
+ if (ret < 0)
+ return -EINVAL;
+
+ *buf = '\0';
+
+ for (i = 0; i < LED_NUM_FLASH_FAULTS; ++i) {
+ if (fault & mask) {
+ buf_len = sprintf(pbuf, "%s ",
+ led_flash_fault_names[i]);
+ pbuf += buf_len;
+ }
+ mask <<= 1;
+ }
+
+ return strlen(strcat(buf, "\n"));
+}
+static DEVICE_ATTR_RO(flash_fault);
+
+static struct attribute *led_flash_strobe_attrs[] = {
+ &dev_attr_flash_strobe.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_timeout_attrs[] = {
+ &dev_attr_flash_timeout.attr,
+ &dev_attr_max_flash_timeout.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_brightness_attrs[] = {
+ &dev_attr_flash_brightness.attr,
+ &dev_attr_max_flash_brightness.attr,
+ NULL,
+};
+
+static struct attribute *led_flash_fault_attrs[] = {
+ &dev_attr_flash_fault.attr,
+ NULL,
+};
+
+static const struct attribute_group led_flash_strobe_group = {
+ .attrs = led_flash_strobe_attrs,
+};
+
+static const struct attribute_group led_flash_timeout_group = {
+ .attrs = led_flash_timeout_attrs,
+};
+
+static const struct attribute_group led_flash_brightness_group = {
+ .attrs = led_flash_brightness_attrs,
+};
+
+static const struct attribute_group led_flash_fault_group = {
+ .attrs = led_flash_fault_attrs,
+};
+
+static void led_flash_resume(struct led_classdev *led_cdev)
+{
+ struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev);
+
+ call_flash_op(fled_cdev, flash_brightness_set,
+ fled_cdev->brightness.val);
+ call_flash_op(fled_cdev, timeout_set, fled_cdev->timeout.val);
+}
+
+static void led_flash_init_sysfs_groups(struct led_classdev_flash *fled_cdev)
+{
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ const struct led_flash_ops *ops = fled_cdev->ops;
+ const struct attribute_group **flash_groups = fled_cdev->sysfs_groups;
+
+ int num_sysfs_groups = 0;
+
+ flash_groups[num_sysfs_groups++] = &led_flash_strobe_group;
+
+ if (ops->flash_brightness_set)
+ flash_groups[num_sysfs_groups++] = &led_flash_brightness_group;
+
+ if (ops->timeout_set)
+ flash_groups[num_sysfs_groups++] = &led_flash_timeout_group;
+
+ if (ops->fault_get)
+ flash_groups[num_sysfs_groups++] = &led_flash_fault_group;
+
+ led_cdev->groups = flash_groups;
+}
+
+int led_classdev_flash_register_ext(struct device *parent,
+ struct led_classdev_flash *fled_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev *led_cdev;
+ const struct led_flash_ops *ops;
+ int ret;
+
+ if (!fled_cdev)
+ return -EINVAL;
+
+ led_cdev = &fled_cdev->led_cdev;
+
+ if (led_cdev->flags & LED_DEV_CAP_FLASH) {
+ if (!led_cdev->brightness_set_blocking)
+ return -EINVAL;
+
+ ops = fled_cdev->ops;
+ if (!ops || !ops->strobe_set)
+ return -EINVAL;
+
+ led_cdev->flash_resume = led_flash_resume;
+
+ /* Select the sysfs attributes to be created for the device */
+ led_flash_init_sysfs_groups(fled_cdev);
+ }
+
+ /* Register led class device */
+ ret = led_classdev_register_ext(parent, led_cdev, init_data);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_register_ext);
+
+void led_classdev_flash_unregister(struct led_classdev_flash *fled_cdev)
+{
+ if (!fled_cdev)
+ return;
+
+ led_classdev_unregister(&fled_cdev->led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_flash_unregister);
+
+static void devm_led_classdev_flash_release(struct device *dev, void *res)
+{
+ led_classdev_flash_unregister(*(struct led_classdev_flash **)res);
+}
+
+int devm_led_classdev_flash_register_ext(struct device *parent,
+ struct led_classdev_flash *fled_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev_flash **dr;
+ int ret;
+
+ dr = devres_alloc(devm_led_classdev_flash_release, sizeof(*dr),
+ GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ ret = led_classdev_flash_register_ext(parent, fled_cdev, init_data);
+ if (ret) {
+ devres_free(dr);
+ return ret;
+ }
+
+ *dr = fled_cdev;
+ devres_add(parent, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_flash_register_ext);
+
+static int devm_led_classdev_flash_match(struct device *dev,
+ void *res, void *data)
+{
+ struct led_classdev_flash **p = res;
+
+ if (WARN_ON(!p || !*p))
+ return 0;
+
+ return *p == data;
+}
+
+void devm_led_classdev_flash_unregister(struct device *dev,
+ struct led_classdev_flash *fled_cdev)
+{
+ WARN_ON(devres_release(dev,
+ devm_led_classdev_flash_release,
+ devm_led_classdev_flash_match, fled_cdev));
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_flash_unregister);
+
+static void led_clamp_align(struct led_flash_setting *s)
+{
+ u32 v, offset;
+
+ v = s->val + s->step / 2;
+ v = clamp(v, s->min, s->max);
+ offset = v - s->min;
+ offset = s->step * (offset / s->step);
+ s->val = s->min + offset;
+}
+
+int led_set_flash_timeout(struct led_classdev_flash *fled_cdev, u32 timeout)
+{
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *s = &fled_cdev->timeout;
+
+ s->val = timeout;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(fled_cdev, timeout_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_timeout);
+
+int led_get_flash_fault(struct led_classdev_flash *fled_cdev, u32 *fault)
+{
+ return call_flash_op(fled_cdev, fault_get, fault);
+}
+EXPORT_SYMBOL_GPL(led_get_flash_fault);
+
+int led_set_flash_brightness(struct led_classdev_flash *fled_cdev,
+ u32 brightness)
+{
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *s = &fled_cdev->brightness;
+
+ s->val = brightness;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(fled_cdev, flash_brightness_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_brightness);
+
+int led_update_flash_brightness(struct led_classdev_flash *fled_cdev)
+{
+ struct led_flash_setting *s = &fled_cdev->brightness;
+ u32 brightness;
+
+ if (has_flash_op(fled_cdev, flash_brightness_get)) {
+ int ret = call_flash_op(fled_cdev, flash_brightness_get,
+ &brightness);
+ if (ret < 0)
+ return ret;
+
+ s->val = brightness;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_update_flash_brightness);
+
+int led_set_flash_duration(struct led_classdev_flash *fled_cdev, u32 duration)
+{
+ struct led_classdev *led_cdev = &fled_cdev->led_cdev;
+ struct led_flash_setting *s = &fled_cdev->duration;
+
+ s->val = duration;
+ led_clamp_align(s);
+
+ if (!(led_cdev->flags & LED_SUSPENDED))
+ return call_flash_op(fled_cdev, duration_set, s->val);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_set_flash_duration);
+
+MODULE_AUTHOR("Jacek Anaszewski <j.anaszewski@samsung.com>");
+MODULE_DESCRIPTION("LED Flash class interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/led-class-multicolor.c b/drivers/leds/led-class-multicolor.c
new file mode 100644
index 000000000000..fd66d2bdeace
--- /dev/null
+++ b/drivers/leds/led-class-multicolor.c
@@ -0,0 +1,205 @@
+// SPDX-License-Identifier: GPL-2.0
+// LED Multicolor class interface
+// Copyright (C) 2019-20 Texas Instruments Incorporated - http://www.ti.com/
+// Author: Dan Murphy <dmurphy@ti.com>
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+int led_mc_calc_color_components(struct led_classdev_mc *mcled_cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev *led_cdev = &mcled_cdev->led_cdev;
+ int i;
+
+ for (i = 0; i < mcled_cdev->num_colors; i++)
+ mcled_cdev->subled_info[i].brightness =
+ DIV_ROUND_CLOSEST(brightness *
+ mcled_cdev->subled_info[i].intensity,
+ led_cdev->max_brightness);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_mc_calc_color_components);
+
+static ssize_t multi_intensity_store(struct device *dev,
+ struct device_attribute *intensity_attr,
+ const char *buf, size_t size)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
+ int nrchars, offset = 0;
+ int intensity_value[LED_COLOR_ID_MAX];
+ int i;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ for (i = 0; i < mcled_cdev->num_colors; i++) {
+ ret = sscanf(buf + offset, "%i%n",
+ &intensity_value[i], &nrchars);
+ if (ret != 1) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+ offset += nrchars;
+ }
+
+ offset++;
+ if (offset < size) {
+ ret = -EINVAL;
+ goto err_out;
+ }
+
+ for (i = 0; i < mcled_cdev->num_colors; i++)
+ mcled_cdev->subled_info[i].intensity = intensity_value[i];
+
+ if (!test_bit(LED_BLINK_SW, &led_cdev->work_flags))
+ led_set_brightness(led_cdev, led_cdev->brightness);
+ ret = size;
+err_out:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+}
+
+static ssize_t multi_intensity_show(struct device *dev,
+ struct device_attribute *intensity_attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
+ int len = 0;
+ int i;
+
+ for (i = 0; i < mcled_cdev->num_colors; i++) {
+ len += sprintf(buf + len, "%d",
+ mcled_cdev->subled_info[i].intensity);
+ if (i < mcled_cdev->num_colors - 1)
+ len += sprintf(buf + len, " ");
+ }
+
+ buf[len++] = '\n';
+ return len;
+}
+static DEVICE_ATTR_RW(multi_intensity);
+
+static ssize_t multi_index_show(struct device *dev,
+ struct device_attribute *multi_index_attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev_mc *mcled_cdev = lcdev_to_mccdev(led_cdev);
+ int len = 0;
+ int index;
+ int i;
+
+ for (i = 0; i < mcled_cdev->num_colors; i++) {
+ index = mcled_cdev->subled_info[i].color_index;
+ len += sprintf(buf + len, "%s", led_get_color_name(index));
+ if (i < mcled_cdev->num_colors - 1)
+ len += sprintf(buf + len, " ");
+ }
+
+ buf[len++] = '\n';
+ return len;
+}
+static DEVICE_ATTR_RO(multi_index);
+
+static struct attribute *led_multicolor_attrs[] = {
+ &dev_attr_multi_intensity.attr,
+ &dev_attr_multi_index.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(led_multicolor);
+
+int led_classdev_multicolor_register_ext(struct device *parent,
+ struct led_classdev_mc *mcled_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev *led_cdev;
+
+ if (!mcled_cdev)
+ return -EINVAL;
+
+ if (mcled_cdev->num_colors <= 0)
+ return -EINVAL;
+
+ if (mcled_cdev->num_colors > LED_COLOR_ID_MAX)
+ 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);
+}
+EXPORT_SYMBOL_GPL(led_classdev_multicolor_register_ext);
+
+void led_classdev_multicolor_unregister(struct led_classdev_mc *mcled_cdev)
+{
+ if (!mcled_cdev)
+ return;
+
+ led_classdev_unregister(&mcled_cdev->led_cdev);
+}
+EXPORT_SYMBOL_GPL(led_classdev_multicolor_unregister);
+
+static void devm_led_classdev_multicolor_release(struct device *dev, void *res)
+{
+ led_classdev_multicolor_unregister(*(struct led_classdev_mc **)res);
+}
+
+int devm_led_classdev_multicolor_register_ext(struct device *parent,
+ struct led_classdev_mc *mcled_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev_mc **dr;
+ int ret;
+
+ dr = devres_alloc(devm_led_classdev_multicolor_release,
+ sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ ret = led_classdev_multicolor_register_ext(parent, mcled_cdev,
+ init_data);
+ if (ret) {
+ devres_free(dr);
+ return ret;
+ }
+
+ *dr = mcled_cdev;
+ devres_add(parent, dr);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_register_ext);
+
+static int devm_led_classdev_multicolor_match(struct device *dev,
+ void *res, void *data)
+{
+ struct led_classdev_mc **p = res;
+
+ if (WARN_ON(!p || !*p))
+ return 0;
+
+ return *p == data;
+}
+
+void devm_led_classdev_multicolor_unregister(struct device *dev,
+ struct led_classdev_mc *mcled_cdev)
+{
+ WARN_ON(devres_release(dev,
+ devm_led_classdev_multicolor_release,
+ devm_led_classdev_multicolor_match, mcled_cdev));
+}
+EXPORT_SYMBOL_GPL(devm_led_classdev_multicolor_unregister);
+
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_DESCRIPTION("Multicolor LED class interface");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 4336e37a97f4..885399ed0776 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -1,138 +1,178 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Class Core
*
* Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
* Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
-#include <linux/module.h>
-#include <linux/kernel.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/err.h>
#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/slab.h>
#include <linux/spinlock.h>
-#include <linux/device.h>
#include <linux/timer.h>
-#include <linux/err.h>
-#include <linux/ctype.h>
-#include <linux/leds.h>
+#include <uapi/linux/uleds.h>
+#include <linux/of.h>
#include "leds.h"
-static struct class *leds_class;
+static DEFINE_MUTEX(leds_lookup_lock);
+static LIST_HEAD(leds_lookup_list);
-static void led_update_brightness(struct led_classdev *led_cdev)
-{
- if (led_cdev->brightness_get)
- led_cdev->brightness = led_cdev->brightness_get(led_cdev);
-}
+static struct workqueue_struct *leds_wq;
-static ssize_t led_brightness_show(struct device *dev,
+static ssize_t brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned int brightness;
- /* no lock needed for this */
+ mutex_lock(&led_cdev->led_access);
led_update_brightness(led_cdev);
+ brightness = led_cdev->brightness;
+ mutex_unlock(&led_cdev->led_access);
- return sprintf(buf, "%u\n", led_cdev->brightness);
+ return sysfs_emit(buf, "%u\n", brightness);
}
-static ssize_t led_brightness_store(struct device *dev,
+static ssize_t brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
unsigned long state;
- ssize_t ret = -EINVAL;
+ ssize_t ret;
+
+ mutex_lock(&led_cdev->led_access);
+
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
ret = kstrtoul(buf, 10, &state);
if (ret)
- return ret;
+ goto unlock;
if (state == LED_OFF)
led_trigger_remove(led_cdev);
- __led_set_brightness(led_cdev, state);
+ led_set_brightness(led_cdev, state);
- return size;
+ ret = size;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
}
+static DEVICE_ATTR_RW(brightness);
-static ssize_t led_max_brightness_show(struct device *dev,
+static ssize_t max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ unsigned int max_brightness;
- return sprintf(buf, "%u\n", led_cdev->max_brightness);
+ mutex_lock(&led_cdev->led_access);
+ max_brightness = led_cdev->max_brightness;
+ mutex_unlock(&led_cdev->led_access);
+
+ return sysfs_emit(buf, "%u\n", max_brightness);
}
+static DEVICE_ATTR_RO(max_brightness);
+
+#ifdef CONFIG_LEDS_TRIGGERS
+static 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,
+};
+#endif
-static struct device_attribute led_class_attrs[] = {
- __ATTR(brightness, 0644, led_brightness_show, led_brightness_store),
- __ATTR(max_brightness, 0444, led_max_brightness_show, NULL),
+static struct attribute *led_class_attrs[] = {
+ &dev_attr_brightness.attr,
+ &dev_attr_max_brightness.attr,
+ NULL,
+};
+
+static const struct attribute_group led_group = {
+ .attrs = led_class_attrs,
+};
+
+static const struct attribute_group *led_groups[] = {
+ &led_group,
#ifdef CONFIG_LEDS_TRIGGERS
- __ATTR(trigger, 0644, led_trigger_show, led_trigger_store),
+ &led_trigger_group,
#endif
- __ATTR_NULL,
+ NULL,
};
-static void led_timer_function(unsigned long data)
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+static ssize_t brightness_hw_changed_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = (void *)data;
- unsigned long brightness;
- unsigned long delay;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
- if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
- __led_set_brightness(led_cdev, LED_OFF);
- return;
- }
+ if (led_cdev->brightness_hw_changed == -1)
+ return -ENODATA;
- if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
- led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
- return;
- }
+ return sysfs_emit(buf, "%u\n", led_cdev->brightness_hw_changed);
+}
- brightness = led_get_brightness(led_cdev);
- if (!brightness) {
- /* Time to switch the LED on. */
- brightness = led_cdev->blink_brightness;
- delay = led_cdev->blink_delay_on;
- } else {
- /* Store the current brightness value to be able
- * to restore it when the delay_off period is over.
- */
- led_cdev->blink_brightness = brightness;
- brightness = LED_OFF;
- delay = led_cdev->blink_delay_off;
- }
+static DEVICE_ATTR_RO(brightness_hw_changed);
- __led_set_brightness(led_cdev, brightness);
+static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+ struct device *dev = led_cdev->dev;
+ int ret;
- /* Return in next iteration if led is in one-shot mode and we are in
- * the final blink state so that the led is toggled each delay_on +
- * delay_off milliseconds in worst case.
- */
- if (led_cdev->flags & LED_BLINK_ONESHOT) {
- if (led_cdev->flags & LED_BLINK_INVERT) {
- if (brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- } else {
- if (!brightness)
- led_cdev->flags |= LED_BLINK_ONESHOT_STOP;
- }
+ ret = device_create_file(dev, &dev_attr_brightness_hw_changed);
+ if (ret) {
+ dev_err(dev, "Error creating brightness_hw_changed\n");
+ return ret;
+ }
+
+ led_cdev->brightness_hw_changed_kn =
+ sysfs_get_dirent(dev->kobj.sd, "brightness_hw_changed");
+ if (!led_cdev->brightness_hw_changed_kn) {
+ dev_err(dev, "Error getting brightness_hw_changed kn\n");
+ device_remove_file(dev, &dev_attr_brightness_hw_changed);
+ return -ENXIO;
}
- mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
+ return 0;
}
-static void set_brightness_delayed(struct work_struct *ws)
+static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
{
- struct led_classdev *led_cdev =
- container_of(ws, struct led_classdev, set_brightness_work);
+ sysfs_put(led_cdev->brightness_hw_changed_kn);
+ device_remove_file(led_cdev->dev, &dev_attr_brightness_hw_changed);
+}
- led_stop_software_blink(led_cdev);
+void led_classdev_notify_brightness_hw_changed(struct led_classdev *led_cdev, unsigned int brightness)
+{
+ if (WARN_ON(!led_cdev->brightness_hw_changed_kn))
+ return;
- __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
+ led_cdev->brightness_hw_changed = brightness;
+ sysfs_notify_dirent(led_cdev->brightness_hw_changed_kn);
+}
+EXPORT_SYMBOL_GPL(led_classdev_notify_brightness_hw_changed);
+#else
+static int led_add_brightness_hw_changed(struct led_classdev *led_cdev)
+{
+ return 0;
+}
+static void led_remove_brightness_hw_changed(struct led_classdev *led_cdev)
+{
}
+#endif
/**
* led_classdev_suspend - suspend an led_classdev.
@@ -141,7 +181,8 @@ static void set_brightness_delayed(struct work_struct *ws)
void led_classdev_suspend(struct led_classdev *led_cdev)
{
led_cdev->flags |= LED_SUSPENDED;
- led_cdev->brightness_set(led_cdev, 0);
+ led_set_brightness_nopm(led_cdev, 0);
+ flush_work(&led_cdev->set_brightness_work);
}
EXPORT_SYMBOL_GPL(led_classdev_suspend);
@@ -151,11 +192,16 @@ EXPORT_SYMBOL_GPL(led_classdev_suspend);
*/
void led_classdev_resume(struct led_classdev *led_cdev)
{
- led_cdev->brightness_set(led_cdev, led_cdev->brightness);
+ led_set_brightness_nopm(led_cdev, led_cdev->brightness);
+
+ if (led_cdev->flash_resume)
+ led_cdev->flash_resume(led_cdev);
+
led_cdev->flags &= ~LED_SUSPENDED;
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
+#ifdef CONFIG_PM_SLEEP
static int led_suspend(struct device *dev)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -175,27 +221,345 @@ static int led_resume(struct device *dev)
return 0;
}
+#endif
+
+static SIMPLE_DEV_PM_OPS(leds_class_dev_pm_ops, led_suspend, led_resume);
+
+static struct led_classdev *led_module_get(struct device *led_dev)
+{
+ struct led_classdev *led_cdev;
+
+ if (!led_dev)
+ return ERR_PTR(-EPROBE_DEFER);
+
+ led_cdev = dev_get_drvdata(led_dev);
-static const struct dev_pm_ops leds_class_dev_pm_ops = {
- .suspend = led_suspend,
- .resume = led_resume,
+ if (!try_module_get(led_cdev->dev->parent->driver->owner)) {
+ put_device(led_cdev->dev);
+ return ERR_PTR(-ENODEV);
+ }
+
+ return led_cdev;
+}
+
+static const struct class leds_class = {
+ .name = "leds",
+ .dev_groups = led_groups,
+ .pm = &leds_class_dev_pm_ops,
};
/**
- * led_classdev_register - register a new object of led_classdev class.
- * @parent: The device to register.
+ * of_led_get() - request a LED device via the LED framework
+ * @np: device node to get the LED device from
+ * @index: the index of the LED
+ * @name: the name of the LED used to map it to its function, if present
+ *
+ * Returns the LED device parsed from the phandle specified in the "leds"
+ * property of a device tree node or a negative error-code on failure.
+ */
+static struct led_classdev *of_led_get(struct device_node *np, int index,
+ const char *name)
+{
+ struct device *led_dev;
+ struct device_node *led_node;
+
+ /*
+ * For named LEDs, first look up the name in the "led-names" property.
+ * If it cannot be found, then of_parse_phandle() will propagate the error.
+ */
+ if (name)
+ index = of_property_match_string(np, "led-names", name);
+ led_node = of_parse_phandle(np, "leds", index);
+ if (!led_node)
+ return ERR_PTR(-ENOENT);
+
+ led_dev = class_find_device_by_of_node(&leds_class, led_node);
+ of_node_put(led_node);
+
+ return led_module_get(led_dev);
+}
+
+/**
+ * led_put() - release a LED device
+ * @led_cdev: LED device
+ */
+void led_put(struct led_classdev *led_cdev)
+{
+ module_put(led_cdev->dev->parent->driver->owner);
+ put_device(led_cdev->dev);
+}
+EXPORT_SYMBOL_GPL(led_put);
+
+static void devm_led_release(struct device *dev, void *res)
+{
+ struct led_classdev **p = res;
+
+ led_put(*p);
+}
+
+static struct led_classdev *__devm_led_get(struct device *dev, struct led_classdev *led)
+{
+ struct led_classdev **dr;
+
+ dr = devres_alloc(devm_led_release, sizeof(struct led_classdev *), GFP_KERNEL);
+ if (!dr) {
+ led_put(led);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ *dr = led;
+ devres_add(dev, dr);
+
+ return led;
+}
+
+/**
+ * devm_of_led_get - Resource-managed request of a LED device
+ * @dev: LED consumer
+ * @index: index of the LED to obtain in the consumer
+ *
+ * The device node of the device is parse to find the request LED device.
+ * The LED device returned from this function is automatically released
+ * on driver detach.
+ *
+ * @return a pointer to a LED device or ERR_PTR(errno) on failure.
+ */
+struct led_classdev *__must_check devm_of_led_get(struct device *dev,
+ int index)
+{
+ struct led_classdev *led;
+
+ if (!dev)
+ return ERR_PTR(-EINVAL);
+
+ led = of_led_get(dev->of_node, index, NULL);
+ if (IS_ERR(led))
+ return led;
+
+ return __devm_led_get(dev, led);
+}
+EXPORT_SYMBOL_GPL(devm_of_led_get);
+
+/**
+ * led_get() - request a LED device via the LED framework
+ * @dev: device for which to get the LED device
+ * @con_id: name of the LED from the device's point of view
+ *
+ * @return a pointer to a LED device or ERR_PTR(errno) on failure.
+ */
+struct led_classdev *led_get(struct device *dev, char *con_id)
+{
+ struct led_lookup_data *lookup;
+ struct led_classdev *led_cdev;
+ const char *provider = NULL;
+ struct device *led_dev;
+
+ led_cdev = of_led_get(dev->of_node, -1, con_id);
+ if (!IS_ERR(led_cdev) || PTR_ERR(led_cdev) != -ENOENT)
+ return led_cdev;
+
+ mutex_lock(&leds_lookup_lock);
+ list_for_each_entry(lookup, &leds_lookup_list, list) {
+ if (!strcmp(lookup->dev_id, dev_name(dev)) &&
+ !strcmp(lookup->con_id, con_id)) {
+ provider = kstrdup_const(lookup->provider, GFP_KERNEL);
+ break;
+ }
+ }
+ mutex_unlock(&leds_lookup_lock);
+
+ if (!provider)
+ return ERR_PTR(-ENOENT);
+
+ led_dev = class_find_device_by_name(&leds_class, provider);
+ kfree_const(provider);
+
+ return led_module_get(led_dev);
+}
+EXPORT_SYMBOL_GPL(led_get);
+
+/**
+ * devm_led_get() - request a LED device via the LED framework
+ * @dev: device for which to get the LED device
+ * @con_id: name of the LED from the device's point of view
+ *
+ * The LED device returned from this function is automatically released
+ * on driver detach.
+ *
+ * @return a pointer to a LED device or ERR_PTR(errno) on failure.
+ */
+struct led_classdev *devm_led_get(struct device *dev, char *con_id)
+{
+ struct led_classdev *led;
+
+ led = led_get(dev, con_id);
+ if (IS_ERR(led))
+ return led;
+
+ return __devm_led_get(dev, led);
+}
+EXPORT_SYMBOL_GPL(devm_led_get);
+
+/**
+ * led_add_lookup() - Add a LED lookup table entry
+ * @led_lookup: the lookup table entry to add
+ *
+ * Add a LED lookup table entry. On systems without devicetree the lookup table
+ * is used by led_get() to find LEDs.
+ */
+void led_add_lookup(struct led_lookup_data *led_lookup)
+{
+ mutex_lock(&leds_lookup_lock);
+ list_add_tail(&led_lookup->list, &leds_lookup_list);
+ mutex_unlock(&leds_lookup_lock);
+}
+EXPORT_SYMBOL_GPL(led_add_lookup);
+
+/**
+ * led_remove_lookup() - Remove a LED lookup table entry
+ * @led_lookup: the lookup table entry to remove
+ */
+void led_remove_lookup(struct led_lookup_data *led_lookup)
+{
+ mutex_lock(&leds_lookup_lock);
+ list_del(&led_lookup->list);
+ mutex_unlock(&leds_lookup_lock);
+}
+EXPORT_SYMBOL_GPL(led_remove_lookup);
+
+/**
+ * devm_of_led_get_optional - Resource-managed request of an optional LED device
+ * @dev: LED consumer
+ * @index: index of the LED to obtain in the consumer
+ *
+ * The device node of the device is parsed to find the requested LED device.
+ * The LED device returned from this function is automatically released
+ * on driver detach.
+ *
+ * @return a pointer to a LED device, ERR_PTR(errno) on failure and NULL if the
+ * led was not found.
+ */
+struct led_classdev *__must_check devm_of_led_get_optional(struct device *dev,
+ int index)
+{
+ struct led_classdev *led;
+
+ led = devm_of_led_get(dev, index);
+ if (IS_ERR(led) && PTR_ERR(led) == -ENOENT)
+ return NULL;
+
+ return led;
+}
+EXPORT_SYMBOL_GPL(devm_of_led_get_optional);
+
+static int led_classdev_next_name(const char *init_name, char *name,
+ size_t len)
+{
+ unsigned int i = 0;
+ int ret = 0;
+ struct device *dev;
+
+ strscpy(name, init_name, len);
+
+ while ((ret < len) &&
+ (dev = class_find_device_by_name(&leds_class, name))) {
+ put_device(dev);
+ ret = snprintf(name, len, "%s_%u", init_name, ++i);
+ }
+
+ if (ret >= len)
+ return -ENOMEM;
+
+ return i;
+}
+
+/**
+ * led_classdev_register_ext - register a new object of led_classdev class
+ * with init data.
+ *
+ * @parent: parent of LED device
* @led_cdev: the led_classdev structure for this device.
+ * @init_data: LED class device initialization data
*/
-int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
+int led_classdev_register_ext(struct device *parent,
+ struct led_classdev *led_cdev,
+ struct led_init_data *init_data)
{
- led_cdev->dev = device_create(leds_class, parent, 0, led_cdev,
- "%s", led_cdev->name);
- if (IS_ERR(led_cdev->dev))
+ char composed_name[LED_MAX_NAME_SIZE];
+ char final_name[LED_MAX_NAME_SIZE];
+ const char *proposed_name = composed_name;
+ int ret;
+
+ if (init_data) {
+ if (init_data->devname_mandatory && !init_data->devicename) {
+ dev_err(parent, "Mandatory device name is missing");
+ return -EINVAL;
+ }
+ ret = led_compose_name(parent, init_data, composed_name);
+ if (ret < 0)
+ return ret;
+
+ if (init_data->fwnode) {
+ fwnode_property_read_string(init_data->fwnode,
+ "linux,default-trigger",
+ &led_cdev->default_trigger);
+
+ if (fwnode_property_present(init_data->fwnode,
+ "retain-state-shutdown"))
+ led_cdev->flags |= LED_RETAIN_AT_SHUTDOWN;
+
+ fwnode_property_read_u32(init_data->fwnode,
+ "max-brightness",
+ &led_cdev->max_brightness);
+
+ if (fwnode_property_present(init_data->fwnode, "color"))
+ fwnode_property_read_u32(init_data->fwnode, "color",
+ &led_cdev->color);
+ }
+ } else {
+ proposed_name = led_cdev->name;
+ }
+
+ ret = led_classdev_next_name(proposed_name, final_name, sizeof(final_name));
+ if (ret < 0)
+ return ret;
+ else if (ret && led_cdev->flags & LED_REJECT_NAME_CONFLICT)
+ return -EEXIST;
+ else if (ret)
+ dev_warn(parent, "Led %s renamed to %s due to name collision\n",
+ proposed_name, final_name);
+
+ if (led_cdev->color >= LED_COLOR_ID_MAX)
+ dev_warn(parent, "LED %s color identifier out of range\n", final_name);
+
+ mutex_init(&led_cdev->led_access);
+ mutex_lock(&led_cdev->led_access);
+ led_cdev->dev = device_create_with_groups(&leds_class, parent, 0,
+ led_cdev, led_cdev->groups, "%s", final_name);
+ if (IS_ERR(led_cdev->dev)) {
+ mutex_unlock(&led_cdev->led_access);
return PTR_ERR(led_cdev->dev);
+ }
+ if (init_data && init_data->fwnode)
+ device_set_node(led_cdev->dev, init_data->fwnode);
+
+ if (led_cdev->flags & LED_BRIGHT_HW_CHANGED) {
+ ret = led_add_brightness_hw_changed(led_cdev);
+ if (ret) {
+ device_unregister(led_cdev->dev);
+ led_cdev->dev = NULL;
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
+ }
+ }
+ led_cdev->work_flags = 0;
#ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock);
#endif
+#ifdef CONFIG_LEDS_BRIGHTNESS_HW_CHANGED
+ led_cdev->brightness_hw_changed = -1;
+#endif
/* add to the list of leds */
down_write(&leds_list_lock);
list_add_tail(&led_cdev->node, &leds_list);
@@ -206,22 +570,22 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
led_update_brightness(led_cdev);
- INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
+ led_cdev->wq = leds_wq;
- init_timer(&led_cdev->blink_timer);
- led_cdev->blink_timer.function = led_timer_function;
- led_cdev->blink_timer.data = (unsigned long)led_cdev;
+ led_init_core(led_cdev);
#ifdef CONFIG_LEDS_TRIGGERS
led_trigger_set_default(led_cdev);
#endif
+ mutex_unlock(&led_cdev->led_access);
+
dev_dbg(parent, "Registered led device: %s\n",
led_cdev->name);
return 0;
}
-EXPORT_SYMBOL_GPL(led_classdev_register);
+EXPORT_SYMBOL_GPL(led_classdev_register_ext);
/**
* led_classdev_unregister - unregisters a object of led_properties class.
@@ -231,6 +595,9 @@ EXPORT_SYMBOL_GPL(led_classdev_register);
*/
void led_classdev_unregister(struct led_classdev *led_cdev)
{
+ if (IS_ERR_OR_NULL(led_cdev->dev))
+ return;
+
#ifdef CONFIG_LEDS_TRIGGERS
down_write(&led_cdev->trigger_lock);
if (led_cdev->trigger)
@@ -238,33 +605,104 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
up_write(&led_cdev->trigger_lock);
#endif
- cancel_work_sync(&led_cdev->set_brightness_work);
+ led_cdev->flags |= LED_UNREGISTERING;
/* Stop blinking */
led_stop_software_blink(led_cdev);
- led_set_brightness(led_cdev, LED_OFF);
+
+ if (!(led_cdev->flags & LED_RETAIN_AT_SHUTDOWN))
+ led_set_brightness(led_cdev, LED_OFF);
+
+ flush_work(&led_cdev->set_brightness_work);
+
+ if (led_cdev->flags & LED_BRIGHT_HW_CHANGED)
+ led_remove_brightness_hw_changed(led_cdev);
device_unregister(led_cdev->dev);
down_write(&leds_list_lock);
list_del(&led_cdev->node);
up_write(&leds_list_lock);
+
+ mutex_destroy(&led_cdev->led_access);
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);
-static int __init leds_init(void)
+static void devm_led_classdev_release(struct device *dev, void *res)
{
- leds_class = class_create(THIS_MODULE, "leds");
- if (IS_ERR(leds_class))
- return PTR_ERR(leds_class);
- leds_class->pm = &leds_class_dev_pm_ops;
- leds_class->dev_attrs = led_class_attrs;
+ led_classdev_unregister(*(struct led_classdev **)res);
+}
+
+/**
+ * devm_led_classdev_register_ext - resource managed led_classdev_register_ext()
+ *
+ * @parent: parent of LED device
+ * @led_cdev: the led_classdev structure for this device.
+ * @init_data: LED class device initialization data
+ */
+int devm_led_classdev_register_ext(struct device *parent,
+ struct led_classdev *led_cdev,
+ struct led_init_data *init_data)
+{
+ struct led_classdev **dr;
+ int rc;
+
+ dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ rc = led_classdev_register_ext(parent, led_cdev, init_data);
+ if (rc) {
+ devres_free(dr);
+ return rc;
+ }
+
+ *dr = led_cdev;
+ devres_add(parent, dr);
+
return 0;
}
+EXPORT_SYMBOL_GPL(devm_led_classdev_register_ext);
+
+static int devm_led_classdev_match(struct device *dev, void *res, void *data)
+{
+ struct led_classdev **p = res;
+
+ if (WARN_ON(!p || !*p))
+ return 0;
+
+ return *p == data;
+}
+
+/**
+ * devm_led_classdev_unregister() - resource managed led_classdev_unregister()
+ * @dev: The device to unregister.
+ * @led_cdev: the led_classdev structure for this device.
+ */
+void devm_led_classdev_unregister(struct device *dev,
+ struct led_classdev *led_cdev)
+{
+ WARN_ON(devres_release(dev,
+ devm_led_classdev_release,
+ devm_led_classdev_match, led_cdev));
+}
+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_destroy(leds_class);
+ class_unregister(&leds_class);
+ destroy_workqueue(leds_wq);
}
subsys_initcall(leds_init);
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index ce8921a753a3..59473f286b31 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -1,21 +1,23 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Class Core
*
* Copyright 2005-2006 Openedhand Ltd.
*
* Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
#include <linux/list.h>
#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/property.h>
#include <linux/rwsem.h>
-#include <linux/leds.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
#include "leds.h"
DECLARE_RWSEM(leds_list_lock);
@@ -24,6 +26,165 @@ EXPORT_SYMBOL_GPL(leds_list_lock);
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
+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",
+ [LED_COLOR_ID_BLUE] = "blue",
+ [LED_COLOR_ID_AMBER] = "amber",
+ [LED_COLOR_ID_VIOLET] = "violet",
+ [LED_COLOR_ID_YELLOW] = "yellow",
+ [LED_COLOR_ID_IR] = "ir",
+ [LED_COLOR_ID_MULTI] = "multicolor",
+ [LED_COLOR_ID_RGB] = "rgb",
+ [LED_COLOR_ID_PURPLE] = "purple",
+ [LED_COLOR_ID_ORANGE] = "orange",
+ [LED_COLOR_ID_PINK] = "pink",
+ [LED_COLOR_ID_CYAN] = "cyan",
+ [LED_COLOR_ID_LIME] = "lime",
+};
+
+static int __led_set_brightness(struct led_classdev *led_cdev, unsigned int value)
+{
+ if (!led_cdev->brightness_set)
+ return -ENOTSUPP;
+
+ led_cdev->brightness_set(led_cdev, value);
+
+ return 0;
+}
+
+static int __led_set_brightness_blocking(struct led_classdev *led_cdev, unsigned int value)
+{
+ if (!led_cdev->brightness_set_blocking)
+ return -ENOTSUPP;
+
+ return led_cdev->brightness_set_blocking(led_cdev, value);
+}
+
+static void led_timer_function(struct timer_list *t)
+{
+ struct led_classdev *led_cdev = timer_container_of(led_cdev, t,
+ blink_timer);
+ unsigned long brightness;
+ unsigned long delay;
+
+ if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
+ led_set_brightness_nosleep(led_cdev, LED_OFF);
+ clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
+ return;
+ }
+
+ if (test_and_clear_bit(LED_BLINK_ONESHOT_STOP,
+ &led_cdev->work_flags)) {
+ clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
+ return;
+ }
+
+ brightness = led_get_brightness(led_cdev);
+ if (!brightness) {
+ /* Time to switch the LED on. */
+ if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE,
+ &led_cdev->work_flags))
+ brightness = led_cdev->new_blink_brightness;
+ else
+ brightness = led_cdev->blink_brightness;
+ delay = led_cdev->blink_delay_on;
+ } else {
+ /* Store the current brightness value to be able
+ * to restore it when the delay_off period is over.
+ */
+ led_cdev->blink_brightness = brightness;
+ brightness = LED_OFF;
+ delay = led_cdev->blink_delay_off;
+ }
+
+ led_set_brightness_nosleep(led_cdev, brightness);
+
+ /* Return in next iteration if led is in one-shot mode and we are in
+ * the final blink state so that the led is toggled each delay_on +
+ * delay_off milliseconds in worst case.
+ */
+ if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags)) {
+ if (test_bit(LED_BLINK_INVERT, &led_cdev->work_flags)) {
+ if (brightness)
+ set_bit(LED_BLINK_ONESHOT_STOP,
+ &led_cdev->work_flags);
+ } else {
+ if (!brightness)
+ set_bit(LED_BLINK_ONESHOT_STOP,
+ &led_cdev->work_flags);
+ }
+ }
+
+ mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
+}
+
+static void set_brightness_delayed_set_brightness(struct led_classdev *led_cdev,
+ unsigned int value)
+{
+ int ret;
+
+ ret = __led_set_brightness(led_cdev, value);
+ if (ret == -ENOTSUPP) {
+ ret = __led_set_brightness_blocking(led_cdev, value);
+ 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);
+}
+
+static void set_brightness_delayed(struct work_struct *ws)
+{
+ struct led_classdev *led_cdev =
+ container_of(ws, struct led_classdev, set_brightness_work);
+
+ if (test_and_clear_bit(LED_BLINK_DISABLE, &led_cdev->work_flags)) {
+ led_stop_software_blink(led_cdev);
+ set_bit(LED_SET_BRIGHTNESS_OFF, &led_cdev->work_flags);
+ }
+
+ /*
+ * Triggers may call led_set_brightness(LED_OFF),
+ * led_set_brightness(LED_FULL) in quick succession to disable blinking
+ * and turn the LED on. Both actions may have been scheduled to run
+ * 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)) {
+ 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);
+
+ if (test_and_clear_bit(LED_SET_BLINK, &led_cdev->work_flags)) {
+ unsigned long delay_on = led_cdev->delayed_delay_on;
+ unsigned long delay_off = led_cdev->delayed_delay_off;
+
+ led_blink_set(led_cdev, &delay_on, &delay_off);
+ }
+}
+
static void led_set_software_blink(struct led_classdev *led_cdev,
unsigned long delay_on,
unsigned long delay_off)
@@ -39,16 +200,20 @@ static void led_set_software_blink(struct led_classdev *led_cdev,
led_cdev->blink_delay_on = delay_on;
led_cdev->blink_delay_off = delay_off;
- /* never on - don't blink */
- if (!delay_on)
+ /* never on - just set to off */
+ if (!delay_on) {
+ led_set_brightness_nosleep(led_cdev, LED_OFF);
return;
+ }
/* never off - just set to brightness */
if (!delay_off) {
- __led_set_brightness(led_cdev, led_cdev->blink_brightness);
+ led_set_brightness_nosleep(led_cdev,
+ led_cdev->blink_brightness);
return;
}
+ set_bit(LED_BLINK_SW, &led_cdev->work_flags);
mod_timer(&led_cdev->blink_timer, jiffies + 1);
}
@@ -57,7 +222,7 @@ static void led_blink_setup(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off)
{
- if (!(led_cdev->flags & LED_BLINK_ONESHOT) &&
+ if (!test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
led_cdev->blink_set &&
!led_cdev->blink_set(led_cdev, delay_on, delay_off))
return;
@@ -69,58 +234,383 @@ static void led_blink_setup(struct led_classdev *led_cdev,
led_set_software_blink(led_cdev, *delay_on, *delay_off);
}
+void led_init_core(struct led_classdev *led_cdev)
+{
+ INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed);
+
+ timer_setup(&led_cdev->blink_timer, led_timer_function, 0);
+}
+EXPORT_SYMBOL_GPL(led_init_core);
+
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);
- led_cdev->flags &= ~LED_BLINK_ONESHOT;
- led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
+ clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
+ clear_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
+ clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off);
}
-EXPORT_SYMBOL(led_blink_set);
+EXPORT_SYMBOL_GPL(led_blink_set);
void led_blink_set_oneshot(struct led_classdev *led_cdev,
unsigned long *delay_on,
unsigned long *delay_off,
int invert)
{
- if ((led_cdev->flags & LED_BLINK_ONESHOT) &&
+ if (test_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags) &&
timer_pending(&led_cdev->blink_timer))
return;
- led_cdev->flags |= LED_BLINK_ONESHOT;
- led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
+ set_bit(LED_BLINK_ONESHOT, &led_cdev->work_flags);
+ clear_bit(LED_BLINK_ONESHOT_STOP, &led_cdev->work_flags);
if (invert)
- led_cdev->flags |= LED_BLINK_INVERT;
+ set_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
else
- led_cdev->flags &= ~LED_BLINK_INVERT;
+ clear_bit(LED_BLINK_INVERT, &led_cdev->work_flags);
led_blink_setup(led_cdev, delay_on, delay_off);
}
-EXPORT_SYMBOL(led_blink_set_oneshot);
+EXPORT_SYMBOL_GPL(led_blink_set_oneshot);
+
+void led_blink_set_nosleep(struct led_classdev *led_cdev, unsigned long delay_on,
+ unsigned long delay_off)
+{
+ /* If necessary delegate to a work queue task. */
+ if (led_cdev->blink_set && led_cdev->brightness_set_blocking) {
+ led_cdev->delayed_delay_on = delay_on;
+ led_cdev->delayed_delay_off = delay_off;
+ set_bit(LED_SET_BLINK, &led_cdev->work_flags);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
+ return;
+ }
+
+ led_blink_set(led_cdev, &delay_on, &delay_off);
+}
+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);
}
EXPORT_SYMBOL_GPL(led_stop_software_blink);
-void led_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+void led_set_brightness(struct led_classdev *led_cdev, unsigned int brightness)
{
- /* delay brightness setting if need to stop soft-blink timer */
- if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) {
- led_cdev->delayed_set_value = brightness;
- schedule_work(&led_cdev->set_brightness_work);
+ /*
+ * If software blink is active, delay brightness setting
+ * until the next timer tick.
+ */
+ if (test_bit(LED_BLINK_SW, &led_cdev->work_flags)) {
+ /*
+ * If we need to disable soft blinking delegate this to the
+ * work queue task to avoid problems in case we are called
+ * from hard irq context.
+ */
+ if (!brightness) {
+ set_bit(LED_BLINK_DISABLE, &led_cdev->work_flags);
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
+ } else {
+ set_bit(LED_BLINK_BRIGHTNESS_CHANGE,
+ &led_cdev->work_flags);
+ led_cdev->new_blink_brightness = brightness;
+ }
return;
}
- __led_set_brightness(led_cdev, brightness);
+ led_set_brightness_nosleep(led_cdev, brightness);
+}
+EXPORT_SYMBOL_GPL(led_set_brightness);
+
+void led_set_brightness_nopm(struct led_classdev *led_cdev, unsigned int value)
+{
+ /* Use brightness_set op if available, it is guaranteed not to sleep */
+ if (!__led_set_brightness(led_cdev, value))
+ return;
+
+ /*
+ * Brightness setting can sleep, delegate it to a work queue task.
+ * value 0 / LED_OFF is special, since it also disables hw-blinking
+ * (sw-blink disable is handled in led_set_brightness()).
+ * To avoid a hw-blink-disable getting lost when a second brightness
+ * change is done immediately afterwards (before the work runs),
+ * it uses a separate work_flag.
+ */
+ 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 {
+ 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);
+ }
+
+ queue_work(led_cdev->wq, &led_cdev->set_brightness_work);
+}
+EXPORT_SYMBOL_GPL(led_set_brightness_nopm);
+
+void led_set_brightness_nosleep(struct led_classdev *led_cdev, unsigned int value)
+{
+ led_cdev->brightness = min(value, led_cdev->max_brightness);
+
+ if (led_cdev->flags & LED_SUSPENDED)
+ return;
+
+ led_set_brightness_nopm(led_cdev, led_cdev->brightness);
+}
+EXPORT_SYMBOL_GPL(led_set_brightness_nosleep);
+
+int led_set_brightness_sync(struct led_classdev *led_cdev, unsigned int value)
+{
+ if (led_cdev->blink_delay_on || led_cdev->blink_delay_off)
+ return -EBUSY;
+
+ led_cdev->brightness = min(value, led_cdev->max_brightness);
+
+ if (led_cdev->flags & LED_SUSPENDED)
+ return 0;
+
+ return __led_set_brightness_blocking(led_cdev, led_cdev->brightness);
+}
+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;
+
+ if (led_cdev->brightness_get) {
+ ret = led_cdev->brightness_get(led_cdev);
+ if (ret < 0)
+ return ret;
+
+ led_cdev->brightness = ret;
+ }
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_update_brightness);
+
+u32 *led_get_default_pattern(struct led_classdev *led_cdev, unsigned int *size)
+{
+ struct fwnode_handle *fwnode = led_cdev->dev->fwnode;
+ u32 *pattern;
+ int count;
+
+ count = fwnode_property_count_u32(fwnode, "led-pattern");
+ if (count < 0)
+ return NULL;
+
+ pattern = kcalloc(count, sizeof(*pattern), GFP_KERNEL);
+ if (!pattern)
+ return NULL;
+
+ if (fwnode_property_read_u32_array(fwnode, "led-pattern", pattern, count)) {
+ kfree(pattern);
+ return NULL;
+ }
+
+ *size = count;
+
+ return pattern;
+}
+EXPORT_SYMBOL_GPL(led_get_default_pattern);
+
+/* Caller must ensure led_cdev->led_access held */
+void led_sysfs_disable(struct led_classdev *led_cdev)
+{
+ lockdep_assert_held(&led_cdev->led_access);
+
+ led_cdev->flags |= LED_SYSFS_DISABLE;
+}
+EXPORT_SYMBOL_GPL(led_sysfs_disable);
+
+/* Caller must ensure led_cdev->led_access held */
+void led_sysfs_enable(struct led_classdev *led_cdev)
+{
+ lockdep_assert_held(&led_cdev->led_access);
+
+ led_cdev->flags &= ~LED_SYSFS_DISABLE;
+}
+EXPORT_SYMBOL_GPL(led_sysfs_enable);
+
+static void led_parse_fwnode_props(struct device *dev,
+ struct fwnode_handle *fwnode,
+ struct led_properties *props)
+{
+ int ret;
+
+ if (!fwnode)
+ return;
+
+ if (fwnode_property_present(fwnode, "label")) {
+ ret = fwnode_property_read_string(fwnode, "label", &props->label);
+ if (ret)
+ dev_err(dev, "Error parsing 'label' property (%d)\n", ret);
+ return;
+ }
+
+ if (fwnode_property_present(fwnode, "color")) {
+ ret = fwnode_property_read_u32(fwnode, "color", &props->color);
+ if (ret)
+ dev_err(dev, "Error parsing 'color' property (%d)\n", ret);
+ else if (props->color >= LED_COLOR_ID_MAX)
+ dev_err(dev, "LED color identifier out of range\n");
+ else
+ props->color_present = true;
+ }
+
+
+ if (!fwnode_property_present(fwnode, "function"))
+ return;
+
+ ret = fwnode_property_read_string(fwnode, "function", &props->function);
+ if (ret) {
+ dev_err(dev,
+ "Error parsing 'function' property (%d)\n",
+ ret);
+ }
+
+ if (!fwnode_property_present(fwnode, "function-enumerator"))
+ return;
+
+ ret = fwnode_property_read_u32(fwnode, "function-enumerator",
+ &props->func_enum);
+ if (ret) {
+ dev_err(dev,
+ "Error parsing 'function-enumerator' property (%d)\n",
+ ret);
+ } else {
+ props->func_enum_present = true;
+ }
+}
+
+int led_compose_name(struct device *dev, struct led_init_data *init_data,
+ char *led_classdev_name)
+{
+ struct led_properties props = {};
+ struct fwnode_handle *fwnode = init_data->fwnode;
+ const char *devicename = init_data->devicename;
+ int n;
+
+ if (!led_classdev_name)
+ return -EINVAL;
+
+ led_parse_fwnode_props(dev, fwnode, &props);
+
+ if (props.label) {
+ /*
+ * If init_data.devicename is NULL, then it indicates that
+ * DT label should be used as-is for LED class device name.
+ * Otherwise the label is prepended with devicename to compose
+ * the final LED class device name.
+ */
+ if (devicename) {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, props.label);
+ } else {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", props.label);
+ }
+ } else if (props.function || props.color_present) {
+ char tmp_buf[LED_MAX_NAME_SIZE];
+
+ if (props.func_enum_present) {
+ n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s-%d",
+ props.color_present ? led_colors[props.color] : "",
+ props.function ?: "", props.func_enum);
+ } else {
+ n = snprintf(tmp_buf, LED_MAX_NAME_SIZE, "%s:%s",
+ props.color_present ? led_colors[props.color] : "",
+ props.function ?: "");
+ }
+ if (n >= LED_MAX_NAME_SIZE)
+ return -E2BIG;
+
+ if (init_data->devname_mandatory) {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, tmp_buf);
+ } else {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s", tmp_buf);
+ }
+ } else if (init_data->default_label) {
+ if (!devicename) {
+ dev_err(dev, "Legacy LED naming requires devicename segment");
+ return -EINVAL;
+ }
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s:%s",
+ devicename, init_data->default_label);
+ } else if (is_of_node(fwnode)) {
+ n = snprintf(led_classdev_name, LED_MAX_NAME_SIZE, "%s",
+ to_of_node(fwnode)->name);
+ } else
+ return -EINVAL;
+
+ if (n >= LED_MAX_NAME_SIZE)
+ return -E2BIG;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(led_compose_name);
+
+const char *led_get_color_name(u8 color_id)
+{
+ if (color_id >= ARRAY_SIZE(led_colors))
+ return NULL;
+
+ return led_colors[color_id];
+}
+EXPORT_SYMBOL_GPL(led_get_color_name);
+
+enum led_default_state led_init_default_state_get(struct fwnode_handle *fwnode)
+{
+ const char *state = NULL;
+
+ if (!fwnode_property_read_string(fwnode, "default-state", &state)) {
+ if (!strcmp(state, "keep"))
+ return LEDS_DEFSTATE_KEEP;
+ if (!strcmp(state, "on"))
+ return LEDS_DEFSTATE_ON;
+ }
+
+ return LEDS_DEFSTATE_OFF;
}
-EXPORT_SYMBOL(led_set_brightness);
+EXPORT_SYMBOL_GPL(led_init_default_state_get);
diff --git a/drivers/leds/led-test.c b/drivers/leds/led-test.c
new file mode 100644
index 000000000000..ddf9aa967a6a
--- /dev/null
+++ b/drivers/leds/led-test.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2025 Google LLC
+ *
+ * Author: Lee Jones <lee@kernel.org>
+ */
+
+#include <kunit/device.h>
+#include <kunit/test.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
+#define LED_TEST_POST_REG_BRIGHTNESS 10
+
+struct led_test_ddata {
+ struct led_classdev cdev;
+ struct device *dev;
+};
+
+static enum led_brightness led_test_brightness_get(struct led_classdev *cdev)
+{
+ return LED_TEST_POST_REG_BRIGHTNESS;
+}
+
+static void led_test_class_register(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+ struct led_classdev *cdev_clash, *cdev = &ddata->cdev;
+ struct device *dev = ddata->dev;
+ int ret;
+
+ /* Register a LED class device */
+ cdev->name = "led-test";
+ cdev->brightness_get = led_test_brightness_get;
+ cdev->brightness = 0;
+
+ ret = devm_led_classdev_register(dev, cdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_EQ(test, cdev->max_brightness, LED_FULL);
+ KUNIT_EXPECT_EQ(test, cdev->brightness, LED_TEST_POST_REG_BRIGHTNESS);
+ KUNIT_EXPECT_STREQ(test, cdev->dev->kobj.name, "led-test");
+
+ /* Register again with the same name - expect it to pass with the LED renamed */
+ cdev_clash = devm_kmemdup(dev, cdev, sizeof(*cdev), GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, cdev_clash);
+
+ ret = devm_led_classdev_register(dev, cdev_clash);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ KUNIT_EXPECT_STREQ(test, cdev_clash->dev->kobj.name, "led-test_1");
+ KUNIT_EXPECT_STREQ(test, cdev_clash->name, "led-test");
+
+ /* Enable name conflict rejection and register with the same name again - expect failure */
+ cdev_clash->flags |= LED_REJECT_NAME_CONFLICT;
+ ret = devm_led_classdev_register(dev, cdev_clash);
+ KUNIT_EXPECT_EQ(test, ret, -EEXIST);
+}
+
+static void led_test_class_add_lookup_and_get(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+ struct led_classdev *cdev = &ddata->cdev, *cdev_get;
+ struct device *dev = ddata->dev;
+ struct led_lookup_data lookup;
+ int ret;
+
+ /* First, register a LED class device */
+ cdev->name = "led-test";
+ ret = devm_led_classdev_register(dev, cdev);
+ KUNIT_ASSERT_EQ(test, ret, 0);
+
+ /* Then make the LED available for lookup */
+ lookup.provider = cdev->name;
+ lookup.dev_id = dev_name(dev);
+ lookup.con_id = "led-test-1";
+ led_add_lookup(&lookup);
+
+ /* Finally, attempt to look it up via the API - imagine this was an orthogonal driver */
+ cdev_get = devm_led_get(dev, "led-test-1");
+ KUNIT_ASSERT_FALSE(test, IS_ERR(cdev_get));
+
+ KUNIT_EXPECT_STREQ(test, cdev_get->name, cdev->name);
+
+ led_remove_lookup(&lookup);
+}
+
+static struct kunit_case led_test_cases[] = {
+ KUNIT_CASE(led_test_class_register),
+ KUNIT_CASE(led_test_class_add_lookup_and_get),
+ { }
+};
+
+static int led_test_init(struct kunit *test)
+{
+ struct led_test_ddata *ddata;
+ struct device *dev;
+
+ ddata = kunit_kzalloc(test, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata)
+ return -ENOMEM;
+
+ test->priv = ddata;
+
+ dev = kunit_device_register(test, "led_test");
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+
+ ddata->dev = get_device(dev);
+
+ return 0;
+}
+
+static void led_test_exit(struct kunit *test)
+{
+ struct led_test_ddata *ddata = test->priv;
+
+ if (ddata && ddata->dev)
+ put_device(ddata->dev);
+}
+
+static struct kunit_suite led_test_suite = {
+ .name = "led",
+ .init = led_test_init,
+ .exit = led_test_exit,
+ .test_cases = led_test_cases,
+};
+kunit_test_suite(led_test_suite);
+
+MODULE_AUTHOR("Lee Jones <lee@kernel.org>");
+MODULE_DESCRIPTION("KUnit tests for the LED framework");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index 3c972b2f9893..3799dcc1cf07 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -1,19 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Triggers Core
*
* Copyright 2005-2007 Openedhand Ltd.
*
* Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
-#include <linux/module.h>
+#include <linux/export.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
@@ -21,6 +16,7 @@
#include <linux/rwsem.h>
#include <linux/leds.h>
#include <linux/slab.h>
+#include <linux/mm.h>
#include "leds.h"
/*
@@ -31,112 +27,236 @@ static LIST_HEAD(trigger_list);
/* Used by LED Class */
-ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count)
+static inline bool
+trigger_relevant(struct led_classdev *led_cdev, struct led_trigger *trig)
{
+ return !trig->trigger_type || trig->trigger_type == led_cdev->trigger_type;
+}
+
+ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *bin_attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- char trigger_name[TRIG_NAME_MAX];
struct led_trigger *trig;
- size_t len;
+ int ret = count;
- trigger_name[sizeof(trigger_name) - 1] = '\0';
- strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
- len = strlen(trigger_name);
+ mutex_lock(&led_cdev->led_access);
- if (len && trigger_name[len - 1] == '\n')
- trigger_name[len - 1] = '\0';
+ if (led_sysfs_is_disabled(led_cdev)) {
+ ret = -EBUSY;
+ goto unlock;
+ }
- if (!strcmp(trigger_name, "none")) {
+ if (sysfs_streq(buf, "none")) {
led_trigger_remove(led_cdev);
- return count;
+ goto unlock;
+ }
+
+ if (sysfs_streq(buf, "default")) {
+ led_trigger_set_default(led_cdev);
+ goto unlock;
}
down_read(&triggers_list_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
- if (!strcmp(trigger_name, trig->name)) {
+ if (sysfs_streq(buf, trig->name) && trigger_relevant(led_cdev, trig)) {
down_write(&led_cdev->trigger_lock);
led_trigger_set(led_cdev, trig);
up_write(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
- return count;
+ goto unlock;
}
}
+ /* we come here only if buf matches no trigger */
+ ret = -EINVAL;
up_read(&triggers_list_lock);
- return -EINVAL;
+unlock:
+ mutex_unlock(&led_cdev->led_access);
+ return ret;
}
-EXPORT_SYMBOL_GPL(led_trigger_store);
+EXPORT_SYMBOL_GPL(led_trigger_write);
-ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
- char *buf)
+__printf(3, 4)
+static int led_trigger_snprintf(char *buf, ssize_t size, const char *fmt, ...)
+{
+ va_list args;
+ int i;
+
+ va_start(args, fmt);
+ if (size <= 0)
+ i = vsnprintf(NULL, 0, fmt, args);
+ else
+ i = vscnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return i;
+}
+
+static int led_trigger_format(char *buf, size_t size,
+ struct led_classdev *led_cdev)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct led_trigger *trig;
- int len = 0;
+ int len = led_trigger_snprintf(buf, size, "%s",
+ led_cdev->trigger ? "none" : "[none]");
+
+ if (led_cdev->default_trigger)
+ len += led_trigger_snprintf(buf + len, size - len, " default");
+
+ list_for_each_entry(trig, &trigger_list, next_trig) {
+ bool hit;
+
+ if (!trigger_relevant(led_cdev, trig))
+ continue;
+
+ hit = led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name);
+
+ len += led_trigger_snprintf(buf + len, size - len,
+ " %s%s%s", hit ? "[" : "",
+ trig->name, hit ? "]" : "");
+ }
+
+ len += led_trigger_snprintf(buf + len, size - len, "\n");
+
+ return len;
+}
+
+/*
+ * It was stupid to create 10000 cpu triggers, but we are stuck with it now.
+ * Don't make that mistake again. We work around it here by creating binary
+ * attribute, which is not limited by length. This is _not_ good design, do not
+ * copy it.
+ */
+ssize_t led_trigger_read(struct file *filp, struct kobject *kobj,
+ const struct bin_attribute *attr, char *buf,
+ loff_t pos, size_t count)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ void *data;
+ int len;
down_read(&triggers_list_lock);
down_read(&led_cdev->trigger_lock);
- if (!led_cdev->trigger)
- len += sprintf(buf+len, "[none] ");
- else
- len += sprintf(buf+len, "none ");
-
- list_for_each_entry(trig, &trigger_list, next_trig) {
- if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
- trig->name))
- len += sprintf(buf+len, "[%s] ", trig->name);
- else
- len += sprintf(buf+len, "%s ", trig->name);
+ len = led_trigger_format(NULL, 0, led_cdev);
+ data = kvmalloc(len + 1, GFP_KERNEL);
+ if (!data) {
+ up_read(&led_cdev->trigger_lock);
+ up_read(&triggers_list_lock);
+ return -ENOMEM;
}
+ len = led_trigger_format(data, len + 1, led_cdev);
+
up_read(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
- len += sprintf(len+buf, "\n");
+ len = memory_read_from_buffer(buf, count, &pos, data, len);
+
+ kvfree(data);
+
return len;
}
-EXPORT_SYMBOL_GPL(led_trigger_show);
+EXPORT_SYMBOL_GPL(led_trigger_read);
/* Caller must ensure led_cdev->trigger_lock held */
-void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
+int led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
- unsigned long flags;
char *event = NULL;
char *envp[2];
const char *name;
+ int ret;
+
+ if (!led_cdev->trigger && !trig)
+ return 0;
name = trig ? trig->name : "none";
event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
/* Remove any existing trigger */
if (led_cdev->trigger) {
- write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
- list_del(&led_cdev->trig_list);
- write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
- flags);
+ spin_lock(&led_cdev->trigger->leddev_list_lock);
+ list_del_rcu(&led_cdev->trig_list);
+ spin_unlock(&led_cdev->trigger->leddev_list_lock);
+
+ /* ensure it's no longer visible on the led_cdevs list */
+ synchronize_rcu();
+
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);
led_cdev->trigger = NULL;
+ led_cdev->trigger_data = NULL;
+ led_cdev->activated = false;
+ led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
led_set_brightness(led_cdev, LED_OFF);
}
if (trig) {
- write_lock_irqsave(&trig->leddev_list_lock, flags);
- list_add_tail(&led_cdev->trig_list, &trig->led_cdevs);
- write_unlock_irqrestore(&trig->leddev_list_lock, flags);
+ spin_lock(&trig->leddev_list_lock);
+ list_add_tail_rcu(&led_cdev->trig_list, &trig->led_cdevs);
+ 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)
- trig->activate(led_cdev);
+ ret = trig->activate(led_cdev);
+ else
+ led_set_brightness(led_cdev, trig->brightness);
+ if (ret)
+ goto err_activate;
+
+ ret = device_add_groups(led_cdev->dev, trig->groups);
+ if (ret) {
+ dev_err(led_cdev->dev, "Failed to add trigger attributes\n");
+ goto err_add_groups;
+ }
}
if (event) {
envp[0] = event;
envp[1] = NULL;
- kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp);
+ if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp))
+ dev_err(led_cdev->dev,
+ "%s: Error sending uevent\n", __func__);
kfree(event);
}
+
+ return 0;
+
+err_add_groups:
+
+ if (trig->deactivate)
+ trig->deactivate(led_cdev);
+err_activate:
+
+ spin_lock(&led_cdev->trigger->leddev_list_lock);
+ list_del_rcu(&led_cdev->trig_list);
+ spin_unlock(&led_cdev->trigger->leddev_list_lock);
+ synchronize_rcu();
+ led_cdev->trigger = NULL;
+ led_cdev->trigger_data = NULL;
+ led_set_brightness(led_cdev, LED_OFF);
+ kfree(event);
+
+ return ret;
}
EXPORT_SYMBOL_GPL(led_trigger_set);
@@ -148,36 +268,50 @@ void led_trigger_remove(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_trigger_remove);
+static bool led_match_default_trigger(struct led_classdev *led_cdev,
+ struct led_trigger *trig)
+{
+ if (!strcmp(led_cdev->default_trigger, trig->name) &&
+ trigger_relevant(led_cdev, trig)) {
+ led_cdev->flags |= LED_INIT_DEFAULT_TRIGGER;
+ led_trigger_set(led_cdev, trig);
+ return true;
+ }
+
+ return false;
+}
+
void led_trigger_set_default(struct led_classdev *led_cdev)
{
struct led_trigger *trig;
+ bool found = false;
if (!led_cdev->default_trigger)
return;
+ if (!strcmp(led_cdev->default_trigger, "none")) {
+ led_trigger_remove(led_cdev);
+ return;
+ }
+
down_read(&triggers_list_lock);
down_write(&led_cdev->trigger_lock);
list_for_each_entry(trig, &trigger_list, next_trig) {
- if (!strcmp(led_cdev->default_trigger, trig->name))
- led_trigger_set(led_cdev, trig);
+ found = led_match_default_trigger(led_cdev, trig);
+ if (found)
+ break;
}
up_write(&led_cdev->trigger_lock);
up_read(&triggers_list_lock);
-}
-EXPORT_SYMBOL_GPL(led_trigger_set_default);
-
-void led_trigger_rename_static(const char *name, struct led_trigger *trig)
-{
- /* new name must be on a temporary string to prevent races */
- BUG_ON(name == trig->name);
- down_write(&triggers_list_lock);
- /* this assumes that trig->name was originaly allocated to
- * non constant storage */
- strcpy((char *)trig->name, name);
- up_write(&triggers_list_lock);
+ /*
+ * If default trigger wasn't found, maybe trigger module isn't loaded yet.
+ * Once loaded it will re-probe with all led_cdev's.
+ */
+ if (!found)
+ request_module_nowait("ledtrig:%s", led_cdev->default_trigger);
}
-EXPORT_SYMBOL_GPL(led_trigger_rename_static);
+EXPORT_SYMBOL_GPL(led_trigger_set_default);
/* LED Trigger Interface */
@@ -186,13 +320,15 @@ int led_trigger_register(struct led_trigger *trig)
struct led_classdev *led_cdev;
struct led_trigger *_trig;
- rwlock_init(&trig->leddev_list_lock);
+ spin_lock_init(&trig->leddev_list_lock);
INIT_LIST_HEAD(&trig->led_cdevs);
down_write(&triggers_list_lock);
/* Make sure the trigger's name isn't already in use */
list_for_each_entry(_trig, &trigger_list, next_trig) {
- if (!strcmp(_trig->name, trig->name)) {
+ if (!strcmp(_trig->name, trig->name) &&
+ (trig->trigger_type == _trig->trigger_type ||
+ !trig->trigger_type || !_trig->trigger_type)) {
up_write(&triggers_list_lock);
return -EEXIST;
}
@@ -205,9 +341,8 @@ int led_trigger_register(struct led_trigger *trig)
down_read(&leds_list_lock);
list_for_each_entry(led_cdev, &leds_list, node) {
down_write(&led_cdev->trigger_lock);
- if (!led_cdev->trigger && led_cdev->default_trigger &&
- !strcmp(led_cdev->default_trigger, trig->name))
- led_trigger_set(led_cdev, trig);
+ if (!led_cdev->trigger && led_cdev->default_trigger)
+ led_match_default_trigger(led_cdev, trig);
up_write(&led_cdev->trigger_lock);
}
up_read(&leds_list_lock);
@@ -220,9 +355,12 @@ void led_trigger_unregister(struct led_trigger *trig)
{
struct led_classdev *led_cdev;
+ if (list_empty_careful(&trig->next_trig))
+ return;
+
/* Remove from the list of led triggers */
down_write(&triggers_list_lock);
- list_del(&trig->next_trig);
+ list_del_init(&trig->next_trig);
up_write(&triggers_list_lock);
/* Remove anyone actively using this trigger */
@@ -237,63 +375,106 @@ void led_trigger_unregister(struct led_trigger *trig)
}
EXPORT_SYMBOL_GPL(led_trigger_unregister);
-/* Simple LED Tigger Interface */
+static void devm_led_trigger_release(struct device *dev, void *res)
+{
+ led_trigger_unregister(*(struct led_trigger **)res);
+}
+
+int devm_led_trigger_register(struct device *dev,
+ struct led_trigger *trig)
+{
+ struct led_trigger **dr;
+ int rc;
+
+ dr = devres_alloc(devm_led_trigger_release, sizeof(*dr),
+ GFP_KERNEL);
+ if (!dr)
+ return -ENOMEM;
+
+ *dr = trig;
+
+ rc = led_trigger_register(trig);
+ if (rc)
+ devres_free(dr);
+ else
+ devres_add(dev, dr);
+
+ return rc;
+}
+EXPORT_SYMBOL_GPL(devm_led_trigger_register);
+
+/* Simple LED Trigger Interface */
void led_trigger_event(struct led_trigger *trig,
enum led_brightness brightness)
{
- struct list_head *entry;
+ struct led_classdev *led_cdev;
if (!trig)
return;
- read_lock(&trig->leddev_list_lock);
- list_for_each(entry, &trig->led_cdevs) {
- struct led_classdev *led_cdev;
+ trig->brightness = brightness;
- led_cdev = list_entry(entry, struct led_classdev, trig_list);
+ rcu_read_lock();
+ list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list)
led_set_brightness(led_cdev, brightness);
- }
- read_unlock(&trig->leddev_list_lock);
+ rcu_read_unlock();
}
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,
+ unsigned long delay_on,
+ unsigned long delay_off,
int oneshot,
int invert)
{
- struct list_head *entry;
+ struct led_classdev *led_cdev;
if (!trig)
return;
- read_lock(&trig->leddev_list_lock);
- list_for_each(entry, &trig->led_cdevs) {
- struct led_classdev *led_cdev;
-
- led_cdev = list_entry(entry, struct led_classdev, trig_list);
+ rcu_read_lock();
+ list_for_each_entry_rcu(led_cdev, &trig->led_cdevs, trig_list) {
if (oneshot)
- led_blink_set_oneshot(led_cdev, delay_on, delay_off,
+ led_blink_set_oneshot(led_cdev, &delay_on, &delay_off,
invert);
else
- led_blink_set(led_cdev, delay_on, delay_off);
+ led_blink_set_nosleep(led_cdev, delay_on, delay_off);
}
- read_unlock(&trig->leddev_list_lock);
+ rcu_read_unlock();
}
void led_trigger_blink(struct led_trigger *trig,
- unsigned long *delay_on,
- unsigned long *delay_off)
+ unsigned long delay_on,
+ unsigned long delay_off)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0);
}
EXPORT_SYMBOL_GPL(led_trigger_blink);
void led_trigger_blink_oneshot(struct led_trigger *trig,
- unsigned long *delay_on,
- unsigned long *delay_off,
+ unsigned long delay_on,
+ unsigned long delay_off,
int invert)
{
led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert);
@@ -331,7 +512,3 @@ void led_trigger_unregister_simple(struct led_trigger *trig)
kfree(trig);
}
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
-
-MODULE_AUTHOR("Richard Purdie");
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("LED Triggers Core");
diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c
index 232b3ce902e5..ef5c6c4667ab 100644
--- a/drivers/leds/leds-88pm860x.c
+++ b/drivers/leds/leds-88pm860x.c
@@ -1,23 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED driver for Marvell 88PM860x
*
* Copyright (C) 2009 Marvell International Ltd.
* Haojian Zhuang <haojian.zhuang@marvell.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/slab.h>
-#include <linux/workqueue.h>
#include <linux/mfd/88pm860x.h>
#include <linux/module.h>
@@ -34,7 +28,6 @@
struct pm860x_led {
struct led_classdev cdev;
struct i2c_client *i2c;
- struct work_struct work;
struct pm860x_chip *chip;
struct mutex lock;
char name[MFD_NAME_SIZE];
@@ -70,17 +63,18 @@ static int led_power_set(struct pm860x_chip *chip, int port, int on)
return ret;
}
-static void pm860x_led_work(struct work_struct *work)
+static int pm860x_led_set(struct led_classdev *cdev,
+ enum led_brightness value)
{
-
- struct pm860x_led *led;
+ struct pm860x_led *led = container_of(cdev, struct pm860x_led, cdev);
struct pm860x_chip *chip;
unsigned char buf[3];
int ret;
- led = container_of(work, struct pm860x_led, work);
chip = led->chip;
mutex_lock(&led->lock);
+ led->brightness = value >> 3;
+
if ((led->current_brightness == 0) && led->brightness) {
led_power_set(chip, led->port, 1);
if (led->iset) {
@@ -113,34 +107,26 @@ static void pm860x_led_work(struct work_struct *work)
dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
led->reg_control, led->brightness);
mutex_unlock(&led->lock);
-}
-
-static void pm860x_led_set(struct led_classdev *cdev,
- enum led_brightness value)
-{
- struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
- data->brightness = value >> 3;
- schedule_work(&data->work);
+ return 0;
}
#ifdef CONFIG_OF
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;
- nproot = of_node_get(pdev->dev.parent->of_node);
- if (!nproot)
+ if (!dev_of_node(pdev->dev.parent))
return -ENODEV;
- nproot = of_find_node_by_name(nproot, "leds");
+ nproot = of_get_child_by_name(dev_of_node(pdev->dev.parent), "leds");
if (!nproot) {
dev_err(&pdev->dev, "failed to find leds node\n");
return -ENODEV;
}
- for_each_child_of_node(nproot, np) {
- if (!of_node_cmp(np->name, data->name)) {
+ 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);
@@ -157,7 +143,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev,
static int pm860x_led_probe(struct platform_device *pdev)
{
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
- struct pm860x_led_pdata *pdata = pdev->dev.platform_data;
+ struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev);
struct pm860x_led *data;
struct resource *res;
int ret = 0;
@@ -204,7 +190,6 @@ static int pm860x_led_probe(struct platform_device *pdev)
sprintf(data->name, "led1-blue");
break;
}
- platform_set_drvdata(pdev, data);
data->chip = chip;
data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion;
data->port = pdev->id;
@@ -214,9 +199,8 @@ static int pm860x_led_probe(struct platform_device *pdev)
data->current_brightness = 0;
data->cdev.name = data->name;
- data->cdev.brightness_set = pm860x_led_set;
+ data->cdev.brightness_set_blocking = pm860x_led_set;
mutex_init(&data->lock);
- INIT_WORK(&data->work, pm860x_led_work);
ret = led_classdev_register(chip->dev, &data->cdev);
if (ret < 0) {
@@ -224,22 +208,22 @@ static int pm860x_led_probe(struct platform_device *pdev)
return ret;
}
pm860x_led_set(&data->cdev, 0);
+
+ platform_set_drvdata(pdev, data);
+
return 0;
}
-static int pm860x_led_remove(struct platform_device *pdev)
+static void pm860x_led_remove(struct platform_device *pdev)
{
struct pm860x_led *data = platform_get_drvdata(pdev);
led_classdev_unregister(&data->cdev);
-
- return 0;
}
static struct platform_driver pm860x_led_driver = {
.driver = {
.name = "88pm860x-led",
- .owner = THIS_MODULE,
},
.probe = pm860x_led_probe,
.remove = pm860x_led_remove,
diff --git a/drivers/leds/leds-acer-a500.c b/drivers/leds/leds-acer-a500.c
new file mode 100644
index 000000000000..8cf0b11f4390
--- /dev/null
+++ b/drivers/leds/leds-acer-a500.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define A500_EC_LED_DELAY_USEC (100 * 1000)
+
+enum {
+ REG_RESET_LEDS = 0x40,
+ REG_POWER_LED_ON = 0x42,
+ REG_CHARGE_LED_ON = 0x43,
+ REG_ANDROID_LEDS_OFF = 0x5a,
+};
+
+struct a500_led {
+ struct led_classdev cdev;
+ const struct reg_sequence *enable_seq;
+ struct a500_led *other;
+ struct regmap *rmap;
+};
+
+static const struct reg_sequence a500_ec_leds_reset_seq[] = {
+ REG_SEQ(REG_RESET_LEDS, 0x0, A500_EC_LED_DELAY_USEC),
+ REG_SEQ(REG_ANDROID_LEDS_OFF, 0x0, A500_EC_LED_DELAY_USEC),
+};
+
+static const struct reg_sequence a500_ec_white_led_enable_seq[] = {
+ REG_SEQ(REG_POWER_LED_ON, 0x0, A500_EC_LED_DELAY_USEC),
+};
+
+static const struct reg_sequence a500_ec_orange_led_enable_seq[] = {
+ REG_SEQ(REG_CHARGE_LED_ON, 0x0, A500_EC_LED_DELAY_USEC),
+};
+
+static int a500_ec_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct a500_led *led = container_of(led_cdev, struct a500_led, cdev);
+ struct reg_sequence control_seq[2];
+ unsigned int num_regs = 1;
+
+ if (value) {
+ control_seq[0] = led->enable_seq[0];
+ } else {
+ /*
+ * There is no separate controls which can disable LEDs
+ * individually, there is only RESET_LEDS command that turns
+ * off both LEDs.
+ *
+ * RESET_LEDS turns off both LEDs, thus restore other LED if
+ * it's turned ON.
+ */
+ if (led->other->cdev.brightness)
+ num_regs = 2;
+
+ control_seq[0] = a500_ec_leds_reset_seq[0];
+ control_seq[1] = led->other->enable_seq[0];
+ }
+
+ return regmap_multi_reg_write(led->rmap, control_seq, num_regs);
+}
+
+static int a500_ec_leds_probe(struct platform_device *pdev)
+{
+ struct a500_led *white_led, *orange_led;
+ struct regmap *rmap;
+ int err;
+
+ rmap = dev_get_regmap(pdev->dev.parent, "KB930");
+ if (!rmap)
+ return -EINVAL;
+
+ /* reset and turn off LEDs */
+ regmap_multi_reg_write(rmap, a500_ec_leds_reset_seq, 2);
+
+ white_led = devm_kzalloc(&pdev->dev, sizeof(*white_led), GFP_KERNEL);
+ if (!white_led)
+ return -ENOMEM;
+
+ white_led->cdev.name = "power:white";
+ white_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set;
+ white_led->cdev.flags = LED_CORE_SUSPENDRESUME;
+ white_led->cdev.max_brightness = 1;
+ white_led->enable_seq = a500_ec_white_led_enable_seq;
+ white_led->rmap = rmap;
+
+ orange_led = devm_kzalloc(&pdev->dev, sizeof(*orange_led), GFP_KERNEL);
+ if (!orange_led)
+ return -ENOMEM;
+
+ orange_led->cdev.name = "power:orange";
+ orange_led->cdev.brightness_set_blocking = a500_ec_led_brightness_set;
+ orange_led->cdev.flags = LED_CORE_SUSPENDRESUME;
+ orange_led->cdev.max_brightness = 1;
+ orange_led->enable_seq = a500_ec_orange_led_enable_seq;
+ orange_led->rmap = rmap;
+
+ white_led->other = orange_led;
+ orange_led->other = white_led;
+
+ err = devm_led_classdev_register(&pdev->dev, &white_led->cdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register white LED\n");
+ return err;
+ }
+
+ err = devm_led_classdev_register(&pdev->dev, &orange_led->cdev);
+ if (err) {
+ dev_err(&pdev->dev, "failed to register orange LED\n");
+ return err;
+ }
+
+ return 0;
+}
+
+static struct platform_driver a500_ec_leds_driver = {
+ .driver = {
+ .name = "acer-a500-iconia-leds",
+ },
+ .probe = a500_ec_leds_probe,
+};
+module_platform_driver(a500_ec_leds_driver);
+
+MODULE_DESCRIPTION("LED driver for Acer Iconia Tab A500 Power Button");
+MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
+MODULE_ALIAS("platform:acer-a500-iconia-leds");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c
index e8072abe76e5..13e5bc80e56e 100644
--- a/drivers/leds/leds-adp5520.c
+++ b/drivers/leds/leds-adp5520.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LEDs driver for Analog Devices ADP5520/ADP5501 MFD PMICs
*
@@ -9,43 +10,30 @@
*
* Copyright (C) 2006-2008 Marvell International Ltd.
* Eric Miao <eric.miao@marvell.com>
- *
- * Licensed under the GPL-2 or later.
*/
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
-#include <linux/workqueue.h>
#include <linux/mfd/adp5520.h>
#include <linux/slab.h>
struct adp5520_led {
struct led_classdev cdev;
- struct work_struct work;
struct device *master;
- enum led_brightness new_brightness;
int id;
int flags;
};
-static void adp5520_led_work(struct work_struct *work)
-{
- struct adp5520_led *led = container_of(work, struct adp5520_led, work);
- adp5520_write(led->master, ADP5520_LED1_CURRENT + led->id - 1,
- led->new_brightness >> 2);
-}
-
-static void adp5520_led_set(struct led_classdev *led_cdev,
+static int adp5520_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct adp5520_led *led;
led = container_of(led_cdev, struct adp5520_led, cdev);
- led->new_brightness = value;
- schedule_work(&led->work);
+ return adp5520_write(led->master, ADP5520_LED1_CURRENT + led->id - 1,
+ value >> 2);
}
static int adp5520_led_setup(struct adp5520_led *led)
@@ -87,7 +75,7 @@ static int adp5520_led_setup(struct adp5520_led *led)
static int adp5520_led_prepare(struct platform_device *pdev)
{
- struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data;
+ struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct device *dev = pdev->dev.parent;
int ret = 0;
@@ -103,7 +91,7 @@ static int adp5520_led_prepare(struct platform_device *pdev)
static int adp5520_led_probe(struct platform_device *pdev)
{
- struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data;
+ struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct adp5520_led *led, *led_dat;
struct led_info *cur_led;
int ret, i;
@@ -119,15 +107,12 @@ static int adp5520_led_probe(struct platform_device *pdev)
return -EFAULT;
}
- led = devm_kzalloc(&pdev->dev, sizeof(*led) * pdata->num_leds,
+ led = devm_kcalloc(&pdev->dev, pdata->num_leds, sizeof(*led),
GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "failed to alloc memory\n");
+ if (!led)
return -ENOMEM;
- }
ret = adp5520_led_prepare(pdev);
-
if (ret) {
dev_err(&pdev->dev, "failed to write\n");
return ret;
@@ -139,7 +124,7 @@ static int adp5520_led_probe(struct platform_device *pdev)
led_dat->cdev.name = cur_led->name;
led_dat->cdev.default_trigger = cur_led->default_trigger;
- led_dat->cdev.brightness_set = adp5520_led_set;
+ led_dat->cdev.brightness_set_blocking = adp5520_led_set;
led_dat->cdev.brightness = LED_OFF;
if (cur_led->flags & ADP5520_FLAG_LED_MASK)
@@ -150,9 +135,6 @@ static int adp5520_led_probe(struct platform_device *pdev)
led_dat->id = led_dat->flags & ADP5520_FLAG_LED_MASK;
led_dat->master = pdev->dev.parent;
- led_dat->new_brightness = LED_OFF;
-
- INIT_WORK(&led_dat->work, adp5520_led_work);
ret = led_classdev_register(led_dat->master, &led_dat->cdev);
if (ret) {
@@ -174,18 +156,16 @@ static int adp5520_led_probe(struct platform_device *pdev)
err:
if (i > 0) {
- for (i = i - 1; i >= 0; i--) {
+ for (i = i - 1; i >= 0; i--)
led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
- }
}
return ret;
}
-static int adp5520_led_remove(struct platform_device *pdev)
+static void adp5520_led_remove(struct platform_device *pdev)
{
- struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data;
+ struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct adp5520_led *led;
int i;
@@ -196,16 +176,12 @@ static int adp5520_led_remove(struct platform_device *pdev)
for (i = 0; i < pdata->num_leds; i++) {
led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
}
-
- return 0;
}
static struct platform_driver adp5520_led_driver = {
.driver = {
.name = "adp5520-led",
- .owner = THIS_MODULE,
},
.probe = adp5520_led_probe,
.remove = adp5520_led_remove,
diff --git a/drivers/leds/leds-an30259a.c b/drivers/leds/leds-an30259a.c
new file mode 100644
index 000000000000..a42cc4bc6917
--- /dev/null
+++ b/drivers/leds/leds-an30259a.c
@@ -0,0 +1,352 @@
+// SPDX-License-Identifier: GPL-2.0+
+//
+// Driver for Panasonic AN30259A 3-channel LED driver
+//
+// Copyright (c) 2018 Simon Shields <simon@lineageos.org>
+//
+// Datasheet:
+// https://www.alliedelec.com/m/d/a9d2b3ee87c2d1a535a41dd747b1c247.pdf
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define AN30259A_MAX_LEDS 3
+
+#define AN30259A_REG_SRESET 0x00
+#define AN30259A_LED_SRESET BIT(0)
+
+/* LED power registers */
+#define AN30259A_REG_LED_ON 0x01
+#define AN30259A_LED_EN(x) BIT((x) - 1)
+#define AN30259A_LED_SLOPE(x) BIT(((x) - 1) + 4)
+
+#define AN30259A_REG_LEDCC(x) (0x03 + ((x) - 1))
+
+/* slope control registers */
+#define AN30259A_REG_SLOPE(x) (0x06 + ((x) - 1))
+#define AN30259A_LED_SLOPETIME1(x) (x)
+#define AN30259A_LED_SLOPETIME2(x) ((x) << 4)
+
+#define AN30259A_REG_LEDCNT1(x) (0x09 + (4 * ((x) - 1)))
+#define AN30259A_LED_DUTYMAX(x) ((x) << 4)
+#define AN30259A_LED_DUTYMID(x) (x)
+
+#define AN30259A_REG_LEDCNT2(x) (0x0A + (4 * ((x) - 1)))
+#define AN30259A_LED_DELAY(x) ((x) << 4)
+#define AN30259A_LED_DUTYMIN(x) (x)
+
+/* detention time control (length of each slope step) */
+#define AN30259A_REG_LEDCNT3(x) (0x0B + (4 * ((x) - 1)))
+#define AN30259A_LED_DT1(x) (x)
+#define AN30259A_LED_DT2(x) ((x) << 4)
+
+#define AN30259A_REG_LEDCNT4(x) (0x0C + (4 * ((x) - 1)))
+#define AN30259A_LED_DT3(x) (x)
+#define AN30259A_LED_DT4(x) ((x) << 4)
+
+#define AN30259A_REG_MAX 0x14
+
+#define AN30259A_BLINK_MAX_TIME 7500 /* ms */
+#define AN30259A_SLOPE_RESOLUTION 500 /* ms */
+
+#define AN30259A_NAME "an30259a"
+
+struct an30259a;
+
+struct an30259a_led {
+ struct an30259a *chip;
+ struct fwnode_handle *fwnode;
+ struct led_classdev cdev;
+ u32 num;
+ enum led_default_state default_state;
+ bool sloping;
+};
+
+struct an30259a {
+ struct mutex mutex; /* held when writing to registers */
+ struct i2c_client *client;
+ struct an30259a_led leds[AN30259A_MAX_LEDS];
+ struct regmap *regmap;
+ int num_leds;
+};
+
+static int an30259a_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct an30259a_led *led;
+ int ret;
+ unsigned int led_on;
+
+ led = container_of(cdev, struct an30259a_led, cdev);
+ mutex_lock(&led->chip->mutex);
+
+ ret = regmap_read(led->chip->regmap, AN30259A_REG_LED_ON, &led_on);
+ if (ret)
+ goto error;
+
+ switch (brightness) {
+ case LED_OFF:
+ led_on &= ~AN30259A_LED_EN(led->num);
+ led_on &= ~AN30259A_LED_SLOPE(led->num);
+ led->sloping = false;
+ break;
+ default:
+ led_on |= AN30259A_LED_EN(led->num);
+ if (led->sloping)
+ led_on |= AN30259A_LED_SLOPE(led->num);
+ ret = regmap_write(led->chip->regmap,
+ AN30259A_REG_LEDCNT1(led->num),
+ AN30259A_LED_DUTYMAX(0xf) |
+ AN30259A_LED_DUTYMID(0xf));
+ if (ret)
+ goto error;
+ break;
+ }
+
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LED_ON, led_on);
+ if (ret)
+ goto error;
+
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCC(led->num),
+ brightness);
+
+error:
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int an30259a_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_off, unsigned long *delay_on)
+{
+ struct an30259a_led *led;
+ int ret, num;
+ unsigned int led_on;
+ unsigned long off = *delay_off, on = *delay_on;
+
+ led = container_of(cdev, struct an30259a_led, cdev);
+
+ mutex_lock(&led->chip->mutex);
+ num = led->num;
+
+ /* slope time can only be a multiple of 500ms. */
+ if (off % AN30259A_SLOPE_RESOLUTION || on % AN30259A_SLOPE_RESOLUTION) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* up to a maximum of 7500ms. */
+ if (off > AN30259A_BLINK_MAX_TIME || on > AN30259A_BLINK_MAX_TIME) {
+ ret = -EINVAL;
+ goto error;
+ }
+
+ /* if no blink specified, default to 1 Hz. */
+ if (!off && !on) {
+ *delay_off = off = 500;
+ *delay_on = on = 500;
+ }
+
+ /* convert into values the HW will understand. */
+ off /= AN30259A_SLOPE_RESOLUTION;
+ on /= AN30259A_SLOPE_RESOLUTION;
+
+ /* duty min should be zero (=off), delay should be zero. */
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT2(num),
+ AN30259A_LED_DELAY(0) | AN30259A_LED_DUTYMIN(0));
+ if (ret)
+ goto error;
+
+ /* reset detention time (no "breathing" effect). */
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT3(num),
+ AN30259A_LED_DT1(0) | AN30259A_LED_DT2(0));
+ if (ret)
+ goto error;
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LEDCNT4(num),
+ AN30259A_LED_DT3(0) | AN30259A_LED_DT4(0));
+ if (ret)
+ goto error;
+
+ /* slope time controls on/off cycle length. */
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_SLOPE(num),
+ AN30259A_LED_SLOPETIME1(off) |
+ AN30259A_LED_SLOPETIME2(on));
+ if (ret)
+ goto error;
+
+ /* Finally, enable slope mode. */
+ ret = regmap_read(led->chip->regmap, AN30259A_REG_LED_ON, &led_on);
+ if (ret)
+ goto error;
+
+ led_on |= AN30259A_LED_SLOPE(num) | AN30259A_LED_EN(led->num);
+
+ ret = regmap_write(led->chip->regmap, AN30259A_REG_LED_ON, led_on);
+
+ if (!ret)
+ led->sloping = true;
+error:
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int an30259a_dt_init(struct i2c_client *client,
+ struct an30259a *chip)
+{
+ struct device_node *np = dev_of_node(&client->dev), *child;
+ int count, ret;
+ int i = 0;
+ struct an30259a_led *led;
+
+ count = of_get_available_child_count(np);
+ if (!count || count > AN30259A_MAX_LEDS)
+ return -EINVAL;
+
+ for_each_available_child_of_node(np, child) {
+ u32 source;
+
+ ret = of_property_read_u32(child, "reg", &source);
+ if (ret != 0 || !source || source > AN30259A_MAX_LEDS) {
+ dev_err(&client->dev, "Couldn't read LED address: %d\n",
+ ret);
+ count--;
+ continue;
+ }
+
+ led = &chip->leds[i];
+
+ led->num = source;
+ led->chip = chip;
+ led->fwnode = of_fwnode_handle(child);
+ led->default_state = led_init_default_state_get(led->fwnode);
+
+ i++;
+ }
+
+ if (!count)
+ return -EINVAL;
+
+ chip->num_leds = i;
+
+ return 0;
+}
+
+static const struct regmap_config an30259a_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = AN30259A_REG_MAX,
+};
+
+static void an30259a_init_default_state(struct an30259a_led *led)
+{
+ struct an30259a *chip = led->chip;
+ int led_on, err;
+
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led->cdev.brightness = LED_FULL;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ err = regmap_read(chip->regmap, AN30259A_REG_LED_ON, &led_on);
+ if (err)
+ break;
+
+ if (!(led_on & AN30259A_LED_EN(led->num))) {
+ led->cdev.brightness = LED_OFF;
+ break;
+ }
+ regmap_read(chip->regmap, AN30259A_REG_LEDCC(led->num),
+ &led->cdev.brightness);
+ break;
+ default:
+ led->cdev.brightness = LED_OFF;
+ }
+
+ an30259a_brightness_set(&led->cdev, led->cdev.brightness);
+}
+
+static int an30259a_probe(struct i2c_client *client)
+{
+ struct an30259a *chip;
+ int i, err;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ err = an30259a_dt_init(client, chip);
+ if (err < 0)
+ return err;
+
+ err = devm_mutex_init(&client->dev, &chip->mutex);
+ if (err)
+ return err;
+
+ chip->client = client;
+ i2c_set_clientdata(client, chip);
+
+ chip->regmap = devm_regmap_init_i2c(client, &an30259a_regmap_config);
+
+ if (IS_ERR(chip->regmap)) {
+ err = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ err);
+ goto exit;
+ }
+
+ for (i = 0; i < chip->num_leds; i++) {
+ struct led_init_data init_data = {};
+
+ an30259a_init_default_state(&chip->leds[i]);
+ chip->leds[i].cdev.brightness_set_blocking =
+ an30259a_brightness_set;
+ chip->leds[i].cdev.blink_set = an30259a_blink_set;
+
+ init_data.fwnode = chip->leds[i].fwnode;
+ init_data.devicename = AN30259A_NAME;
+ init_data.default_label = ":";
+
+ err = devm_led_classdev_register_ext(&client->dev,
+ &chip->leds[i].cdev,
+ &init_data);
+ if (err < 0)
+ goto exit;
+ }
+ return 0;
+
+exit:
+ return err;
+}
+
+static const struct of_device_id an30259a_match_table[] = {
+ { .compatible = "panasonic,an30259a", },
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, an30259a_match_table);
+
+static const struct i2c_device_id an30259a_id[] = {
+ { "an30259a" },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(i2c, an30259a_id);
+
+static struct i2c_driver an30259a_driver = {
+ .driver = {
+ .name = "leds-an30259a",
+ .of_match_table = an30259a_match_table,
+ },
+ .probe = an30259a_probe,
+ .id_table = an30259a_id,
+};
+
+module_i2c_driver(an30259a_driver);
+
+MODULE_AUTHOR("Simon Shields <simon@lineageos.org>");
+MODULE_DESCRIPTION("AN30259A LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-apu.c b/drivers/leds/leds-apu.c
new file mode 100644
index 000000000000..1c116aaa9b6e
--- /dev/null
+++ b/drivers/leds/leds-apu.c
@@ -0,0 +1,221 @@
+/*
+ * drivers/leds/leds-apu.c
+ * Copyright (C) 2017 Alan Mizrahi, alan at mizrahi dot com dot ve
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/dmi.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+
+#define APU1_FCH_ACPI_MMIO_BASE 0xFED80000
+#define APU1_FCH_GPIO_BASE (APU1_FCH_ACPI_MMIO_BASE + 0x01BD)
+#define APU1_LEDON 0x08
+#define APU1_LEDOFF 0xC8
+#define APU1_NUM_GPIO 3
+#define APU1_IOSIZE sizeof(u8)
+
+/* LED access parameters */
+struct apu_param {
+ void __iomem *addr; /* for ioread/iowrite */
+};
+
+/* LED private data */
+struct apu_led_priv {
+ struct led_classdev cdev;
+ struct apu_param param;
+};
+#define cdev_to_priv(c) container_of(c, struct apu_led_priv, cdev)
+
+/* LED profile */
+struct apu_led_profile {
+ const char *name;
+ enum led_brightness brightness;
+ unsigned long offset; /* for devm_ioremap */
+};
+
+struct apu_led_pdata {
+ struct platform_device *pdev;
+ struct apu_led_priv *pled;
+ spinlock_t lock;
+};
+
+static struct apu_led_pdata *apu_led;
+
+static const struct apu_led_profile apu1_led_profile[] = {
+ { "apu:green:1", LED_ON, APU1_FCH_GPIO_BASE + 0 * APU1_IOSIZE },
+ { "apu:green:2", LED_OFF, APU1_FCH_GPIO_BASE + 1 * APU1_IOSIZE },
+ { "apu:green:3", LED_OFF, APU1_FCH_GPIO_BASE + 2 * APU1_IOSIZE },
+};
+
+static const struct dmi_system_id apu_led_dmi_table[] __initconst = {
+ /* PC Engines APU with factory bios "SageBios_PCEngines_APU-45" */
+ {
+ .ident = "apu",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "APU")
+ }
+ },
+ /* PC Engines APU with "Mainline" bios >= 4.6.8 */
+ {
+ .ident = "apu",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "PC Engines"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "apu1")
+ }
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(dmi, apu_led_dmi_table);
+
+static void apu1_led_brightness_set(struct led_classdev *led, enum led_brightness value)
+{
+ struct apu_led_priv *pled = cdev_to_priv(led);
+
+ spin_lock(&apu_led->lock);
+ iowrite8(value ? APU1_LEDON : APU1_LEDOFF, pled->param.addr);
+ spin_unlock(&apu_led->lock);
+}
+
+static int apu_led_config(struct device *dev, struct apu_led_pdata *apuld)
+{
+ int i;
+ int err;
+
+ apu_led->pled = devm_kcalloc(dev,
+ ARRAY_SIZE(apu1_led_profile), sizeof(struct apu_led_priv),
+ GFP_KERNEL);
+
+ if (!apu_led->pled)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++) {
+ struct apu_led_priv *pled = &apu_led->pled[i];
+ struct led_classdev *led_cdev = &pled->cdev;
+
+ led_cdev->name = apu1_led_profile[i].name;
+ led_cdev->brightness = apu1_led_profile[i].brightness;
+ led_cdev->max_brightness = 1;
+ led_cdev->flags = LED_CORE_SUSPENDRESUME;
+ led_cdev->brightness_set = apu1_led_brightness_set;
+
+ pled->param.addr = devm_ioremap(dev,
+ apu1_led_profile[i].offset, APU1_IOSIZE);
+ if (!pled->param.addr) {
+ err = -ENOMEM;
+ goto error;
+ }
+
+ err = led_classdev_register(dev, led_cdev);
+ if (err)
+ goto error;
+
+ apu1_led_brightness_set(led_cdev, apu1_led_profile[i].brightness);
+ }
+
+ return 0;
+
+error:
+ while (i-- > 0)
+ led_classdev_unregister(&apu_led->pled[i].cdev);
+
+ return err;
+}
+
+static int __init apu_led_probe(struct platform_device *pdev)
+{
+ apu_led = devm_kzalloc(&pdev->dev, sizeof(*apu_led), GFP_KERNEL);
+
+ if (!apu_led)
+ return -ENOMEM;
+
+ apu_led->pdev = pdev;
+
+ spin_lock_init(&apu_led->lock);
+ return apu_led_config(&pdev->dev, apu_led);
+}
+
+static struct platform_driver apu_led_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+static int __init apu_led_init(void)
+{
+ struct platform_device *pdev;
+ int err;
+
+ 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;
+ }
+
+ pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ pr_err("Device allocation failed\n");
+ return PTR_ERR(pdev);
+ }
+
+ err = platform_driver_probe(&apu_led_driver, apu_led_probe);
+ if (err) {
+ pr_err("Probe platform driver failed\n");
+ platform_device_unregister(pdev);
+ }
+
+ return err;
+}
+
+static void __exit apu_led_exit(void)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(apu1_led_profile); i++)
+ led_classdev_unregister(&apu_led->pled[i].cdev);
+
+ platform_device_unregister(apu_led->pdev);
+ platform_driver_unregister(&apu_led_driver);
+}
+
+module_init(apu_led_init);
+module_exit(apu_led_exit);
+
+MODULE_AUTHOR("Alan Mizrahi");
+MODULE_DESCRIPTION("PC Engines APU1 front LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds_apu");
diff --git a/drivers/leds/leds-ariel.c b/drivers/leds/leds-ariel.c
new file mode 100644
index 000000000000..dd319c7e385f
--- /dev/null
+++ b/drivers/leds/leds-ariel.c
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: BSD-2-Clause OR GPL-2.0-or-later
+/*
+ * Dell Wyse 3020 a.k.a. "Ariel" Embedded Controller LED Driver
+ *
+ * Copyright (C) 2020 Lubomir Rintel
+ */
+
+#include <linux/module.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+enum ec_index {
+ EC_BLUE_LED = 0x01,
+ EC_AMBER_LED = 0x02,
+ EC_GREEN_LED = 0x03,
+};
+
+enum {
+ EC_LED_OFF = 0x00,
+ EC_LED_STILL = 0x01,
+ EC_LED_FADE = 0x02,
+ EC_LED_BLINK = 0x03,
+};
+
+struct ariel_led {
+ struct regmap *ec_ram;
+ enum ec_index ec_index;
+ struct led_classdev led_cdev;
+};
+
+#define led_cdev_to_ariel_led(c) container_of(c, struct ariel_led, led_cdev)
+
+static enum led_brightness ariel_led_get(struct led_classdev *led_cdev)
+{
+ struct ariel_led *led = led_cdev_to_ariel_led(led_cdev);
+ unsigned int led_status = 0;
+
+ if (regmap_read(led->ec_ram, led->ec_index, &led_status))
+ return LED_OFF;
+
+ if (led_status == EC_LED_STILL)
+ return LED_FULL;
+ else
+ return LED_OFF;
+}
+
+static void ariel_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct ariel_led *led = led_cdev_to_ariel_led(led_cdev);
+
+ if (brightness == LED_OFF)
+ regmap_write(led->ec_ram, led->ec_index, EC_LED_OFF);
+ else
+ regmap_write(led->ec_ram, led->ec_index, EC_LED_STILL);
+}
+
+static int ariel_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct ariel_led *led = led_cdev_to_ariel_led(led_cdev);
+
+ if (*delay_on == 0 && *delay_off == 0)
+ return -EINVAL;
+
+ if (*delay_on == 0) {
+ regmap_write(led->ec_ram, led->ec_index, EC_LED_OFF);
+ } else if (*delay_off == 0) {
+ regmap_write(led->ec_ram, led->ec_index, EC_LED_STILL);
+ } else {
+ *delay_on = 500;
+ *delay_off = 500;
+ regmap_write(led->ec_ram, led->ec_index, EC_LED_BLINK);
+ }
+
+ return 0;
+}
+
+#define NLEDS 3
+
+static int ariel_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ariel_led *leds;
+ struct regmap *ec_ram;
+ int ret;
+ int i;
+
+ ec_ram = dev_get_regmap(dev->parent, "ec_ram");
+ if (!ec_ram)
+ return -ENODEV;
+
+ leds = devm_kcalloc(dev, NLEDS, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds[0].ec_index = EC_BLUE_LED;
+ leds[0].led_cdev.name = "blue:power";
+ leds[0].led_cdev.default_trigger = "default-on";
+
+ leds[1].ec_index = EC_AMBER_LED;
+ leds[1].led_cdev.name = "amber:status";
+
+ leds[2].ec_index = EC_GREEN_LED;
+ leds[2].led_cdev.name = "green:status";
+ leds[2].led_cdev.default_trigger = "default-on";
+
+ for (i = 0; i < NLEDS; i++) {
+ leds[i].ec_ram = ec_ram;
+ leds[i].led_cdev.brightness_get = ariel_led_get;
+ leds[i].led_cdev.brightness_set = ariel_led_set;
+ leds[i].led_cdev.blink_set = ariel_blink_set;
+
+ ret = devm_led_classdev_register(dev, &leds[i].led_cdev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver ariel_led_driver = {
+ .probe = ariel_led_probe,
+ .driver = {
+ .name = "dell-wyse-ariel-led",
+ },
+};
+module_platform_driver(ariel_led_driver);
+
+MODULE_AUTHOR("Lubomir Rintel <lkundrak@v3.sk>");
+MODULE_DESCRIPTION("Dell Wyse 3020 Status LEDs Driver");
+MODULE_LICENSE("Dual BSD/GPL");
diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c
deleted file mode 100644
index cf9efe421c2b..000000000000
--- a/drivers/leds/leds-asic3.c
+++ /dev/null
@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2011 Paul Parsons <lost.distance@yahoo.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
-
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/leds.h>
-#include <linux/slab.h>
-
-#include <linux/mfd/asic3.h>
-#include <linux/mfd/core.h>
-#include <linux/module.h>
-
-/*
- * The HTC ASIC3 LED GPIOs are inputs, not outputs.
- * Hence we turn the LEDs on/off via the TimeBase register.
- */
-
-/*
- * When TimeBase is 4 the clock resolution is about 32Hz.
- * This driver supports hardware blinking with an on+off
- * period from 62ms (2 clocks) to 125s (4000 clocks).
- */
-#define MS_TO_CLK(ms) DIV_ROUND_CLOSEST(((ms)*1024), 32000)
-#define CLK_TO_MS(clk) (((clk)*32000)/1024)
-#define MAX_CLK 4000 /* Fits into 12-bit Time registers */
-#define MAX_MS CLK_TO_MS(MAX_CLK)
-
-static const unsigned int led_n_base[ASIC3_NUM_LEDS] = {
- [0] = ASIC3_LED_0_Base,
- [1] = ASIC3_LED_1_Base,
- [2] = ASIC3_LED_2_Base,
-};
-
-static void brightness_set(struct led_classdev *cdev,
- enum led_brightness value)
-{
- struct platform_device *pdev = to_platform_device(cdev->dev->parent);
- const struct mfd_cell *cell = mfd_get_cell(pdev);
- struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
- u32 timebase;
- unsigned int base;
-
- timebase = (value == LED_OFF) ? 0 : (LED_EN|0x4);
-
- base = led_n_base[cell->id];
- asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), 32);
- asic3_write_register(asic, (base + ASIC3_LED_DutyTime), 32);
- asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
- asic3_write_register(asic, (base + ASIC3_LED_TimeBase), timebase);
-}
-
-static int blink_set(struct led_classdev *cdev,
- unsigned long *delay_on,
- unsigned long *delay_off)
-{
- struct platform_device *pdev = to_platform_device(cdev->dev->parent);
- const struct mfd_cell *cell = mfd_get_cell(pdev);
- struct asic3 *asic = dev_get_drvdata(pdev->dev.parent);
- u32 on;
- u32 off;
- unsigned int base;
-
- if (*delay_on > MAX_MS || *delay_off > MAX_MS)
- return -EINVAL;
-
- if (*delay_on == 0 && *delay_off == 0) {
- /* If both are zero then a sensible default should be chosen */
- on = MS_TO_CLK(500);
- off = MS_TO_CLK(500);
- } else {
- on = MS_TO_CLK(*delay_on);
- off = MS_TO_CLK(*delay_off);
- if ((on + off) > MAX_CLK)
- return -EINVAL;
- }
-
- base = led_n_base[cell->id];
- asic3_write_register(asic, (base + ASIC3_LED_PeriodTime), (on + off));
- asic3_write_register(asic, (base + ASIC3_LED_DutyTime), on);
- asic3_write_register(asic, (base + ASIC3_LED_AutoStopCount), 0);
- asic3_write_register(asic, (base + ASIC3_LED_TimeBase), (LED_EN|0x4));
-
- *delay_on = CLK_TO_MS(on);
- *delay_off = CLK_TO_MS(off);
-
- return 0;
-}
-
-static int asic3_led_probe(struct platform_device *pdev)
-{
- struct asic3_led *led = pdev->dev.platform_data;
- int ret;
-
- ret = mfd_cell_enable(pdev);
- if (ret < 0)
- return ret;
-
- led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev),
- GFP_KERNEL);
- if (!led->cdev) {
- ret = -ENOMEM;
- goto out;
- }
-
- led->cdev->name = led->name;
- led->cdev->flags = LED_CORE_SUSPENDRESUME;
- led->cdev->brightness_set = brightness_set;
- led->cdev->blink_set = blink_set;
- led->cdev->default_trigger = led->default_trigger;
-
- ret = led_classdev_register(&pdev->dev, led->cdev);
- if (ret < 0)
- goto out;
-
- return 0;
-
-out:
- (void) mfd_cell_disable(pdev);
- return ret;
-}
-
-static int asic3_led_remove(struct platform_device *pdev)
-{
- struct asic3_led *led = pdev->dev.platform_data;
-
- led_classdev_unregister(led->cdev);
-
- return mfd_cell_disable(pdev);
-}
-
-#ifdef CONFIG_PM_SLEEP
-static int asic3_led_suspend(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- const struct mfd_cell *cell = mfd_get_cell(pdev);
- int ret;
-
- ret = 0;
- if (cell->suspend)
- ret = (*cell->suspend)(pdev);
-
- return ret;
-}
-
-static int asic3_led_resume(struct device *dev)
-{
- struct platform_device *pdev = to_platform_device(dev);
- const struct mfd_cell *cell = mfd_get_cell(pdev);
- int ret;
-
- ret = 0;
- if (cell->resume)
- ret = (*cell->resume)(pdev);
-
- return ret;
-}
-#endif
-
-static SIMPLE_DEV_PM_OPS(asic3_led_pm_ops, asic3_led_suspend, asic3_led_resume);
-
-static struct platform_driver asic3_led_driver = {
- .probe = asic3_led_probe,
- .remove = asic3_led_remove,
- .driver = {
- .name = "leds-asic3",
- .owner = THIS_MODULE,
- .pm = &asic3_led_pm_ops,
- },
-};
-
-module_platform_driver(asic3_led_driver);
-
-MODULE_AUTHOR("Paul Parsons <lost.distance@yahoo.com>");
-MODULE_DESCRIPTION("HTC ASIC3 LED driver");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:leds-asic3");
diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c
deleted file mode 100644
index 90518f84b9c0..000000000000
--- a/drivers/leds/leds-atmel-pwm.c
+++ /dev/null
@@ -1,149 +0,0 @@
-#include <linux/kernel.h>
-#include <linux/platform_device.h>
-#include <linux/leds.h>
-#include <linux/io.h>
-#include <linux/atmel_pwm.h>
-#include <linux/slab.h>
-#include <linux/module.h>
-
-
-struct pwmled {
- struct led_classdev cdev;
- struct pwm_channel pwmc;
- struct gpio_led *desc;
- u32 mult;
- u8 active_low;
-};
-
-
-/*
- * For simplicity, we use "brightness" as if it were a linear function
- * of PWM duty cycle. However, a logarithmic function of duty cycle is
- * probably a better match for perceived brightness: two is half as bright
- * as four, four is half as bright as eight, etc
- */
-static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b)
-{
- struct pwmled *led;
-
- /* update the duty cycle for the *next* period */
- led = container_of(cdev, struct pwmled, cdev);
- pwm_channel_writel(&led->pwmc, PWM_CUPD, led->mult * (unsigned) b);
-}
-
-/*
- * NOTE: we reuse the platform_data structure of GPIO leds,
- * but repurpose its "gpio" number as a PWM channel number.
- */
-static int pwmled_probe(struct platform_device *pdev)
-{
- const struct gpio_led_platform_data *pdata;
- struct pwmled *leds;
- int i;
- int status;
-
- pdata = pdev->dev.platform_data;
- if (!pdata || pdata->num_leds < 1)
- return -ENODEV;
-
- leds = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*leds),
- GFP_KERNEL);
- if (!leds)
- return -ENOMEM;
-
- for (i = 0; i < pdata->num_leds; i++) {
- struct pwmled *led = leds + i;
- const struct gpio_led *dat = pdata->leds + i;
- u32 tmp;
-
- led->cdev.name = dat->name;
- led->cdev.brightness = LED_OFF;
- led->cdev.brightness_set = pwmled_brightness;
- led->cdev.default_trigger = dat->default_trigger;
-
- led->active_low = dat->active_low;
-
- status = pwm_channel_alloc(dat->gpio, &led->pwmc);
- if (status < 0)
- goto err;
-
- /*
- * Prescale clock by 2^x, so PWM counts in low MHz.
- * Start each cycle with the LED active, so increasing
- * the duty cycle gives us more time on (== brighter).
- */
- tmp = 5;
- if (!led->active_low)
- tmp |= PWM_CPR_CPOL;
- pwm_channel_writel(&led->pwmc, PWM_CMR, tmp);
-
- /*
- * Pick a period so PWM cycles at 100+ Hz; and a multiplier
- * for scaling duty cycle: brightness * mult.
- */
- tmp = (led->pwmc.mck / (1 << 5)) / 100;
- tmp /= 255;
- led->mult = tmp;
- pwm_channel_writel(&led->pwmc, PWM_CDTY,
- led->cdev.brightness * 255);
- pwm_channel_writel(&led->pwmc, PWM_CPRD,
- LED_FULL * tmp);
-
- pwm_channel_enable(&led->pwmc);
-
- /* Hand it over to the LED framework */
- status = led_classdev_register(&pdev->dev, &led->cdev);
- if (status < 0) {
- pwm_channel_free(&led->pwmc);
- goto err;
- }
- }
-
- platform_set_drvdata(pdev, leds);
- return 0;
-
-err:
- if (i > 0) {
- for (i = i - 1; i >= 0; i--) {
- led_classdev_unregister(&leds[i].cdev);
- pwm_channel_free(&leds[i].pwmc);
- }
- }
-
- return status;
-}
-
-static int pwmled_remove(struct platform_device *pdev)
-{
- const struct gpio_led_platform_data *pdata;
- struct pwmled *leds;
- unsigned i;
-
- pdata = pdev->dev.platform_data;
- leds = platform_get_drvdata(pdev);
-
- for (i = 0; i < pdata->num_leds; i++) {
- struct pwmled *led = leds + i;
-
- led_classdev_unregister(&led->cdev);
- pwm_channel_free(&led->pwmc);
- }
-
- return 0;
-}
-
-static struct platform_driver pwmled_driver = {
- .driver = {
- .name = "leds-atmel-pwm",
- .owner = THIS_MODULE,
- },
- /* REVISIT add suspend() and resume() methods */
- .probe = pwmled_probe,
- .remove = pwmled_remove,
-};
-
-module_platform_driver(pwmled_driver);
-
-MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:leds-atmel-pwm");
diff --git a/drivers/leds/leds-aw200xx.c b/drivers/leds/leds-aw200xx.c
new file mode 100644
index 000000000000..fe223d363a5d
--- /dev/null
+++ b/drivers/leds/leds-aw200xx.c
@@ -0,0 +1,669 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Awinic AW20036/AW20054/AW20072/AW20108 LED driver
+ *
+ * Copyright (c) 2023, SberDevices. All Rights Reserved.
+ *
+ * Author: Martin Kurbanov <mmkurbanov@sberdevices.ru>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/time.h>
+#include <linux/units.h>
+
+#define AW200XX_DIM_MAX (BIT(6) - 1)
+#define AW200XX_FADE_MAX (BIT(8) - 1)
+#define AW200XX_IMAX_DEFAULT_uA 60000
+#define AW200XX_IMAX_MAX_uA 160000
+#define AW200XX_IMAX_MIN_uA 3300
+
+/* Page 0 */
+#define AW200XX_REG_PAGE0_BASE 0xc000
+
+/* Select page register */
+#define AW200XX_REG_PAGE 0xF0
+#define AW200XX_PAGE_MASK (GENMASK(7, 6) | GENMASK(2, 0))
+#define AW200XX_PAGE_SHIFT 0
+#define AW200XX_NUM_PAGES 6
+#define AW200XX_PAGE_SIZE 256
+#define AW200XX_REG(page, reg) \
+ (AW200XX_REG_PAGE0_BASE + (page) * AW200XX_PAGE_SIZE + (reg))
+#define AW200XX_REG_MAX \
+ AW200XX_REG(AW200XX_NUM_PAGES - 1, AW200XX_PAGE_SIZE - 1)
+#define AW200XX_PAGE0 0
+#define AW200XX_PAGE1 1
+#define AW200XX_PAGE2 2
+#define AW200XX_PAGE3 3
+#define AW200XX_PAGE4 4
+#define AW200XX_PAGE5 5
+
+/* Chip ID register */
+#define AW200XX_REG_IDR AW200XX_REG(AW200XX_PAGE0, 0x00)
+#define AW200XX_IDR_CHIPID 0x18
+
+/* Sleep mode register */
+#define AW200XX_REG_SLPCR AW200XX_REG(AW200XX_PAGE0, 0x01)
+#define AW200XX_SLPCR_ACTIVE 0x00
+
+/* Reset register */
+#define AW200XX_REG_RSTR AW200XX_REG(AW200XX_PAGE0, 0x02)
+#define AW200XX_RSTR_RESET 0x01
+
+/* Global current configuration register */
+#define AW200XX_REG_GCCR AW200XX_REG(AW200XX_PAGE0, 0x03)
+#define AW200XX_GCCR_IMAX_MASK GENMASK(7, 4)
+#define AW200XX_GCCR_IMAX(x) ((x) << 4)
+#define AW200XX_GCCR_ALLON BIT(3)
+
+/* Fast clear display control register */
+#define AW200XX_REG_FCD AW200XX_REG(AW200XX_PAGE0, 0x04)
+#define AW200XX_FCD_CLEAR 0x01
+
+/* Display size configuration */
+#define AW200XX_REG_DSIZE AW200XX_REG(AW200XX_PAGE0, 0x80)
+#define AW200XX_DSIZE_COLUMNS_MAX 12
+
+#define AW200XX_LED2REG(x, columns) \
+ ((x) + (((x) / (columns)) * (AW200XX_DSIZE_COLUMNS_MAX - (columns))))
+
+/* DIM current configuration register on page 1 */
+#define AW200XX_REG_DIM_PAGE1(x, columns) \
+ AW200XX_REG(AW200XX_PAGE1, AW200XX_LED2REG(x, columns))
+
+/*
+ * DIM current configuration register (page 4).
+ * The even address for current DIM configuration.
+ * The odd address for current FADE configuration
+ */
+#define AW200XX_REG_DIM(x, columns) \
+ AW200XX_REG(AW200XX_PAGE4, AW200XX_LED2REG(x, columns) * 2)
+#define AW200XX_REG_DIM2FADE(x) ((x) + 1)
+#define AW200XX_REG_FADE2DIM(fade) \
+ DIV_ROUND_UP((fade) * AW200XX_DIM_MAX, AW200XX_FADE_MAX)
+
+/*
+ * Duty ratio of display scan (see p.15 of datasheet for formula):
+ * duty = (592us / 600.5us) * (1 / (display_rows + 1))
+ *
+ * Multiply to 1000 (MILLI) to improve the accuracy of calculations.
+ */
+#define AW200XX_DUTY_RATIO(rows) \
+ (((592UL * USEC_PER_SEC) / 600500UL) * (MILLI / (rows)) / MILLI)
+
+struct aw200xx_chipdef {
+ u32 channels;
+ u32 display_size_rows_max;
+ u32 display_size_columns;
+};
+
+struct aw200xx_led {
+ struct led_classdev cdev;
+ struct aw200xx *chip;
+ int dim;
+ u32 num;
+};
+
+struct aw200xx {
+ const struct aw200xx_chipdef *cdef;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct mutex mutex;
+ u32 num_leds;
+ u32 display_rows;
+ struct gpio_desc *hwen;
+ struct aw200xx_led leds[] __counted_by(num_leds);
+};
+
+static ssize_t dim_show(struct device *dev, struct device_attribute *devattr,
+ char *buf)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
+ int dim = led->dim;
+
+ if (dim < 0)
+ return sysfs_emit(buf, "auto\n");
+
+ return sysfs_emit(buf, "%d\n", dim);
+}
+
+static ssize_t dim_store(struct device *dev, struct device_attribute *devattr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
+ struct aw200xx *chip = led->chip;
+ u32 columns = chip->cdef->display_size_columns;
+ int dim;
+ ssize_t ret;
+
+ if (sysfs_streq(buf, "auto")) {
+ dim = -1;
+ } else {
+ ret = kstrtoint(buf, 0, &dim);
+ if (ret)
+ return ret;
+
+ if (dim > AW200XX_DIM_MAX)
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->mutex);
+
+ if (dim >= 0) {
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_DIM_PAGE1(led->num, columns),
+ dim);
+ if (ret)
+ goto out_unlock;
+ }
+
+ led->dim = dim;
+ ret = count;
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+static DEVICE_ATTR_RW(dim);
+
+static struct attribute *dim_attrs[] = {
+ &dev_attr_dim.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(dim);
+
+static int aw200xx_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct aw200xx_led *led = container_of(cdev, struct aw200xx_led, cdev);
+ struct aw200xx *chip = led->chip;
+ int dim;
+ u32 reg;
+ int ret;
+
+ mutex_lock(&chip->mutex);
+
+ reg = AW200XX_REG_DIM(led->num, chip->cdef->display_size_columns);
+
+ dim = led->dim;
+ if (dim < 0)
+ dim = AW200XX_REG_FADE2DIM(brightness);
+
+ ret = regmap_write(chip->regmap, reg, dim);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_write(chip->regmap,
+ AW200XX_REG_DIM2FADE(reg), brightness);
+
+out_unlock:
+ mutex_unlock(&chip->mutex);
+
+ return ret;
+}
+
+static u32 aw200xx_imax_from_global(const struct aw200xx *const chip,
+ u32 global_imax_uA)
+{
+ u64 led_imax_uA;
+
+ /*
+ * The output current of each LED (see p.14 of datasheet for formula):
+ * Iled = Imax * (dim / 63) * ((fade + 1) / 256) * duty
+ *
+ * The value of duty is determined by the following formula:
+ * duty = (592us / 600.5us) * (1 / (display_rows + 1))
+ *
+ * Calculated for the maximum values of fade and dim.
+ * We divide by 1000 because we earlier multiplied by 1000 to improve
+ * accuracy when calculating the duty.
+ */
+ led_imax_uA = global_imax_uA * AW200XX_DUTY_RATIO(chip->display_rows);
+ do_div(led_imax_uA, MILLI);
+
+ return led_imax_uA;
+}
+
+static u32 aw200xx_imax_to_global(const struct aw200xx *const chip,
+ u32 led_imax_uA)
+{
+ u32 duty = AW200XX_DUTY_RATIO(chip->display_rows);
+
+ /* The output current of each LED (see p.14 of datasheet for formula) */
+ return (led_imax_uA * 1000U) / duty;
+}
+
+#define AW200XX_IMAX_MULTIPLIER1 10000
+#define AW200XX_IMAX_MULTIPLIER2 3333
+#define AW200XX_IMAX_BASE_VAL1 0
+#define AW200XX_IMAX_BASE_VAL2 8
+
+/*
+ * The AW200XX has a 4-bit register (GCCR) to configure the global current,
+ * which ranges from 3.3mA to 160mA. The following table indicates the values
+ * of the global current, divided into two parts:
+ *
+ * +-----------+-----------------+-----------+-----------------+
+ * | reg value | global max (mA) | reg value | global max (mA) |
+ * +-----------+-----------------+-----------+-----------------+
+ * | 0 | 10 | 8 | 3.3 |
+ * | 1 | 20 | 9 | 6.7 |
+ * | 2 | 30 | 10 | 10 |
+ * | 3 | 40 | 11 | 13.3 |
+ * | 4 | 60 | 12 | 20 |
+ * | 5 | 80 | 13 | 26.7 |
+ * | 6 | 120 | 14 | 40 |
+ * | 7 | 160 | 15 | 53.3 |
+ * +-----------+-----------------+-----------+-----------------+
+ *
+ * The left part with a multiplier of 10, and the right part with a multiplier
+ * of 3.3.
+ * So we have two formulas to calculate the global current:
+ * for the left part of the table:
+ * imax = coefficient * 10
+ *
+ * for the right part of the table:
+ * imax = coefficient * 3.3
+ *
+ * The coefficient table consists of the following values:
+ * 1, 2, 3, 4, 6, 8, 12, 16.
+ */
+static int aw200xx_set_imax(const struct aw200xx *const chip,
+ u32 led_imax_uA)
+{
+ u32 g_imax_uA = aw200xx_imax_to_global(chip, led_imax_uA);
+ static const u32 coeff_table[] = {1, 2, 3, 4, 6, 8, 12, 16};
+ u32 gccr_imax = UINT_MAX;
+ u32 cur_imax = 0;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(coeff_table); i++) {
+ u32 imax;
+
+ /* select closest ones */
+ imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER1;
+ if (g_imax_uA >= imax && imax > cur_imax) {
+ cur_imax = imax;
+ gccr_imax = i + AW200XX_IMAX_BASE_VAL1;
+ }
+
+ imax = coeff_table[i] * AW200XX_IMAX_MULTIPLIER2;
+ imax = DIV_ROUND_CLOSEST(imax, 100) * 100;
+ if (g_imax_uA >= imax && imax > cur_imax) {
+ cur_imax = imax;
+ gccr_imax = i + AW200XX_IMAX_BASE_VAL2;
+ }
+ }
+
+ if (gccr_imax == UINT_MAX)
+ return -EINVAL;
+
+ return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
+ AW200XX_GCCR_IMAX_MASK,
+ AW200XX_GCCR_IMAX(gccr_imax));
+}
+
+static int aw200xx_chip_reset(const struct aw200xx *const chip)
+{
+ int ret;
+
+ ret = regmap_write(chip->regmap, AW200XX_REG_RSTR, AW200XX_RSTR_RESET);
+ if (ret)
+ return ret;
+
+ /* According to the datasheet software reset takes at least 1ms */
+ fsleep(1000);
+
+ regcache_mark_dirty(chip->regmap);
+ return regmap_write(chip->regmap, AW200XX_REG_FCD, AW200XX_FCD_CLEAR);
+}
+
+static int aw200xx_chip_init(const struct aw200xx *const chip)
+{
+ int ret;
+
+ ret = regmap_write(chip->regmap, AW200XX_REG_DSIZE,
+ chip->display_rows - 1);
+ if (ret)
+ return ret;
+
+ ret = regmap_write(chip->regmap, AW200XX_REG_SLPCR,
+ AW200XX_SLPCR_ACTIVE);
+ if (ret)
+ return ret;
+
+ return regmap_update_bits(chip->regmap, AW200XX_REG_GCCR,
+ AW200XX_GCCR_ALLON, AW200XX_GCCR_ALLON);
+}
+
+static int aw200xx_chip_check(const struct aw200xx *const chip)
+{
+ struct device *dev = &chip->client->dev;
+ u32 chipid;
+ int ret;
+
+ ret = regmap_read(chip->regmap, AW200XX_REG_IDR, &chipid);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read chip ID\n");
+
+ if (chipid != AW200XX_IDR_CHIPID)
+ return dev_err_probe(dev, -ENODEV,
+ "Chip reported wrong ID: %x\n", chipid);
+
+ return 0;
+}
+
+static void aw200xx_enable(const struct aw200xx *const chip)
+{
+ gpiod_set_value_cansleep(chip->hwen, 1);
+
+ /*
+ * After HWEN pin set high the chip begins to load the OTP information,
+ * which takes 200us to complete. About 200us wait time is needed for
+ * internal oscillator startup and display SRAM initialization. After
+ * display SRAM initialization, the registers in page1 to page5 can be
+ * configured via i2c interface.
+ */
+ fsleep(400);
+}
+
+static void aw200xx_disable(const struct aw200xx *const chip)
+{
+ gpiod_set_value_cansleep(chip->hwen, 0);
+}
+
+static int aw200xx_probe_get_display_rows(struct device *dev,
+ struct aw200xx *chip)
+{
+ struct fwnode_handle *child;
+ u32 max_source = 0;
+
+ device_for_each_child_node(dev, child) {
+ u32 source;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "reg", &source);
+ if (ret || source >= chip->cdef->channels)
+ continue;
+
+ max_source = max(max_source, source);
+ }
+
+ if (max_source == 0)
+ return -EINVAL;
+
+ chip->display_rows = max_source / chip->cdef->display_size_columns + 1;
+
+ return 0;
+}
+
+static int aw200xx_probe_fw(struct device *dev, struct aw200xx *chip)
+{
+ u32 current_min, current_max, min_uA;
+ int ret;
+ int i;
+
+ ret = aw200xx_probe_get_display_rows(dev, chip);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "No valid led definitions found\n");
+
+ current_max = aw200xx_imax_from_global(chip, AW200XX_IMAX_MAX_uA);
+ current_min = aw200xx_imax_from_global(chip, AW200XX_IMAX_MIN_uA);
+ min_uA = UINT_MAX;
+ i = 0;
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct led_init_data init_data = {};
+ struct aw200xx_led *led;
+ u32 source, imax;
+
+ ret = fwnode_property_read_u32(child, "reg", &source);
+ if (ret) {
+ dev_err(dev, "Missing reg property\n");
+ chip->num_leds--;
+ continue;
+ }
+
+ if (source >= chip->cdef->channels) {
+ dev_err(dev, "LED reg %u out of range (max %u)\n",
+ source, chip->cdef->channels);
+ chip->num_leds--;
+ continue;
+ }
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp",
+ &imax);
+ if (ret) {
+ dev_info(&chip->client->dev,
+ "DT property led-max-microamp is missing\n");
+ } else if (imax < current_min || imax > current_max) {
+ dev_err(dev, "Invalid value %u for led-max-microamp\n",
+ imax);
+ chip->num_leds--;
+ continue;
+ } else {
+ min_uA = min(min_uA, imax);
+ }
+
+ led = &chip->leds[i];
+ led->dim = -1;
+ led->num = source;
+ led->chip = chip;
+ led->cdev.brightness_set_blocking = aw200xx_brightness_set;
+ led->cdev.max_brightness = AW200XX_FADE_MAX;
+ led->cdev.groups = dim_groups;
+ init_data.fwnode = child;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev,
+ &init_data);
+ if (ret)
+ break;
+
+ i++;
+ }
+
+ if (!chip->num_leds)
+ return -EINVAL;
+
+ if (min_uA == UINT_MAX) {
+ min_uA = aw200xx_imax_from_global(chip,
+ AW200XX_IMAX_DEFAULT_uA);
+ }
+
+ return aw200xx_set_imax(chip, min_uA);
+}
+
+static const struct regmap_range_cfg aw200xx_ranges[] = {
+ {
+ .name = "aw200xx",
+ .range_min = 0,
+ .range_max = AW200XX_REG_MAX,
+ .selector_reg = AW200XX_REG_PAGE,
+ .selector_mask = AW200XX_PAGE_MASK,
+ .selector_shift = AW200XX_PAGE_SHIFT,
+ .window_start = 0,
+ .window_len = AW200XX_PAGE_SIZE,
+ },
+};
+
+static const struct regmap_range aw200xx_writeonly_ranges[] = {
+ regmap_reg_range(AW200XX_REG(AW200XX_PAGE1, 0x00), AW200XX_REG_MAX),
+};
+
+static const struct regmap_access_table aw200xx_readable_table = {
+ .no_ranges = aw200xx_writeonly_ranges,
+ .n_no_ranges = ARRAY_SIZE(aw200xx_writeonly_ranges),
+};
+
+static const struct regmap_range aw200xx_readonly_ranges[] = {
+ regmap_reg_range(AW200XX_REG_IDR, AW200XX_REG_IDR),
+};
+
+static const struct regmap_access_table aw200xx_writeable_table = {
+ .no_ranges = aw200xx_readonly_ranges,
+ .n_no_ranges = ARRAY_SIZE(aw200xx_readonly_ranges),
+};
+
+static const struct regmap_config aw200xx_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = AW200XX_REG_MAX,
+ .ranges = aw200xx_ranges,
+ .num_ranges = ARRAY_SIZE(aw200xx_ranges),
+ .rd_table = &aw200xx_readable_table,
+ .wr_table = &aw200xx_writeable_table,
+ .cache_type = REGCACHE_MAPLE,
+ .disable_locking = true,
+};
+
+static void aw200xx_chip_reset_action(void *data)
+{
+ aw200xx_chip_reset(data);
+}
+
+static void aw200xx_disable_action(void *data)
+{
+ aw200xx_disable(data);
+}
+
+static int aw200xx_probe(struct i2c_client *client)
+{
+ const struct aw200xx_chipdef *cdef;
+ struct aw200xx *chip;
+ int count;
+ int ret;
+
+ cdef = device_get_match_data(&client->dev);
+ if (!cdef)
+ return -ENODEV;
+
+ count = device_get_child_node_count(&client->dev);
+ if (!count || count > cdef->channels)
+ return dev_err_probe(&client->dev, -EINVAL,
+ "Incorrect number of leds (%d)", count);
+
+ chip = devm_kzalloc(&client->dev, struct_size(chip, leds, count),
+ GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->cdef = cdef;
+ chip->num_leds = count;
+ chip->client = client;
+ i2c_set_clientdata(client, chip);
+
+ chip->regmap = devm_regmap_init_i2c(client, &aw200xx_regmap_config);
+ if (IS_ERR(chip->regmap))
+ return PTR_ERR(chip->regmap);
+
+ chip->hwen = devm_gpiod_get_optional(&client->dev, "enable",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(chip->hwen))
+ return dev_err_probe(&client->dev, PTR_ERR(chip->hwen),
+ "Cannot get enable GPIO");
+
+ aw200xx_enable(chip);
+
+ ret = devm_add_action(&client->dev, aw200xx_disable_action, chip);
+ if (ret)
+ return ret;
+
+ ret = aw200xx_chip_check(chip);
+ if (ret)
+ return ret;
+
+ 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);
+
+ ret = aw200xx_chip_reset(chip);
+ 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;
+
+ ret = aw200xx_chip_init(chip);
+
+out_unlock:
+ if (ret)
+ aw200xx_disable(chip);
+
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static const struct aw200xx_chipdef aw20036_cdef = {
+ .channels = 36,
+ .display_size_rows_max = 3,
+ .display_size_columns = 12,
+};
+
+static const struct aw200xx_chipdef aw20054_cdef = {
+ .channels = 54,
+ .display_size_rows_max = 6,
+ .display_size_columns = 9,
+};
+
+static const struct aw200xx_chipdef aw20072_cdef = {
+ .channels = 72,
+ .display_size_rows_max = 6,
+ .display_size_columns = 12,
+};
+
+static const struct aw200xx_chipdef aw20108_cdef = {
+ .channels = 108,
+ .display_size_rows_max = 9,
+ .display_size_columns = 12,
+};
+
+static const struct i2c_device_id aw200xx_id[] = {
+ { "aw20036" },
+ { "aw20054" },
+ { "aw20072" },
+ { "aw20108" },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, aw200xx_id);
+
+static const struct of_device_id aw200xx_match_table[] = {
+ { .compatible = "awinic,aw20036", .data = &aw20036_cdef, },
+ { .compatible = "awinic,aw20054", .data = &aw20054_cdef, },
+ { .compatible = "awinic,aw20072", .data = &aw20072_cdef, },
+ { .compatible = "awinic,aw20108", .data = &aw20108_cdef, },
+ {}
+};
+MODULE_DEVICE_TABLE(of, aw200xx_match_table);
+
+static struct i2c_driver aw200xx_driver = {
+ .driver = {
+ .name = "aw200xx",
+ .of_match_table = aw200xx_match_table,
+ },
+ .probe = aw200xx_probe,
+ .id_table = aw200xx_id,
+};
+module_i2c_driver(aw200xx_driver);
+
+MODULE_AUTHOR("Martin Kurbanov <mmkurbanov@sberdevices.ru>");
+MODULE_DESCRIPTION("AW200XX LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-aw2013.c b/drivers/leds/leds-aw2013.c
new file mode 100644
index 000000000000..216755d6010f
--- /dev/null
+++ b/drivers/leds/leds-aw2013.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0+
+// Driver for Awinic AW2013 3-channel LED driver
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/regulator/consumer.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+#define AW2013_MAX_LEDS 3
+
+/* Reset and ID register */
+#define AW2013_RSTR 0x00
+#define AW2013_RSTR_RESET 0x55
+#define AW2013_RSTR_CHIP_ID 0x33
+
+/* Global control register */
+#define AW2013_GCR 0x01
+#define AW2013_GCR_ENABLE BIT(0)
+
+/* LED channel enable register */
+#define AW2013_LCTR 0x30
+#define AW2013_LCTR_LE(x) BIT((x))
+
+/* LED channel control registers */
+#define AW2013_LCFG(x) (0x31 + (x))
+#define AW2013_LCFG_IMAX_MASK (BIT(0) | BIT(1)) // Should be 0-3
+#define AW2013_LCFG_MD BIT(4)
+#define AW2013_LCFG_FI BIT(5)
+#define AW2013_LCFG_FO BIT(6)
+
+/* LED channel PWM registers */
+#define AW2013_REG_PWM(x) (0x34 + (x))
+
+/* LED channel timing registers */
+#define AW2013_LEDT0(x) (0x37 + (x) * 3)
+#define AW2013_LEDT0_T1(x) ((x) << 4) // Should be 0-7
+#define AW2013_LEDT0_T2(x) (x) // Should be 0-5
+
+#define AW2013_LEDT1(x) (0x38 + (x) * 3)
+#define AW2013_LEDT1_T3(x) ((x) << 4) // Should be 0-7
+#define AW2013_LEDT1_T4(x) (x) // Should be 0-7
+
+#define AW2013_LEDT2(x) (0x39 + (x) * 3)
+#define AW2013_LEDT2_T0(x) ((x) << 4) // Should be 0-8
+#define AW2013_LEDT2_REPEAT(x) (x) // Should be 0-15
+
+#define AW2013_REG_MAX 0x77
+
+#define AW2013_TIME_STEP 130 /* ms */
+
+struct aw2013;
+
+struct aw2013_led {
+ struct aw2013 *chip;
+ struct led_classdev cdev;
+ u32 num;
+ unsigned int imax;
+};
+
+struct aw2013 {
+ struct mutex mutex; /* held when writing to registers */
+ struct regulator_bulk_data regulators[2];
+ struct i2c_client *client;
+ struct aw2013_led leds[AW2013_MAX_LEDS];
+ struct regmap *regmap;
+ int num_leds;
+ bool enabled;
+};
+
+static int aw2013_chip_init(struct aw2013 *chip)
+{
+ int i, ret;
+
+ ret = regmap_write(chip->regmap, AW2013_GCR, AW2013_GCR_ENABLE);
+ if (ret) {
+ dev_err(&chip->client->dev, "Failed to enable the chip: %d\n",
+ ret);
+ return ret;
+ }
+
+ for (i = 0; i < chip->num_leds; i++) {
+ ret = regmap_update_bits(chip->regmap,
+ AW2013_LCFG(chip->leds[i].num),
+ AW2013_LCFG_IMAX_MASK,
+ chip->leds[i].imax);
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Failed to set maximum current for led %d: %d\n",
+ chip->leds[i].num, ret);
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+static void aw2013_chip_disable(struct aw2013 *chip)
+{
+ int ret;
+
+ if (!chip->enabled)
+ return;
+
+ regmap_write(chip->regmap, AW2013_GCR, 0);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Failed to disable regulators: %d\n", ret);
+ return;
+ }
+
+ chip->enabled = false;
+}
+
+static int aw2013_chip_enable(struct aw2013 *chip)
+{
+ int ret;
+
+ if (chip->enabled)
+ return 0;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+ if (ret) {
+ dev_err(&chip->client->dev,
+ "Failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+ chip->enabled = true;
+
+ ret = aw2013_chip_init(chip);
+ if (ret)
+ aw2013_chip_disable(chip);
+
+ return ret;
+}
+
+static bool aw2013_chip_in_use(struct aw2013 *chip)
+{
+ int i;
+
+ for (i = 0; i < chip->num_leds; i++)
+ if (chip->leds[i].cdev.brightness)
+ return true;
+
+ return false;
+}
+
+static int aw2013_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct aw2013_led *led = container_of(cdev, struct aw2013_led, cdev);
+ int ret, num;
+
+ mutex_lock(&led->chip->mutex);
+
+ if (aw2013_chip_in_use(led->chip)) {
+ ret = aw2013_chip_enable(led->chip);
+ if (ret)
+ goto error;
+ }
+
+ num = led->num;
+
+ ret = regmap_write(led->chip->regmap, AW2013_REG_PWM(num), brightness);
+ if (ret)
+ goto error;
+
+ if (brightness) {
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCTR,
+ AW2013_LCTR_LE(num), 0xFF);
+ } else {
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCTR,
+ AW2013_LCTR_LE(num), 0);
+ if (ret)
+ goto error;
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCFG(num),
+ AW2013_LCFG_MD, 0);
+ }
+ if (ret)
+ goto error;
+
+ if (!aw2013_chip_in_use(led->chip))
+ aw2013_chip_disable(led->chip);
+
+error:
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int aw2013_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct aw2013_led *led = container_of(cdev, struct aw2013_led, cdev);
+ int ret, num = led->num;
+ unsigned long off = 0, on = 0;
+
+ /* If no blink specified, default to 1 Hz. */
+ if (!*delay_off && !*delay_on) {
+ *delay_off = 500;
+ *delay_on = 500;
+ }
+
+ if (!led->cdev.brightness) {
+ led->cdev.brightness = LED_FULL;
+ ret = aw2013_brightness_set(&led->cdev, led->cdev.brightness);
+ if (ret)
+ return ret;
+ }
+
+ /* Never on - just set to off */
+ if (!*delay_on) {
+ led->cdev.brightness = LED_OFF;
+ return aw2013_brightness_set(&led->cdev, LED_OFF);
+ }
+
+ mutex_lock(&led->chip->mutex);
+
+ /* Never off - brightness is already set, disable blinking */
+ if (!*delay_off) {
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCFG(num),
+ AW2013_LCFG_MD, 0);
+ goto out;
+ }
+
+ /* Convert into values the HW will understand. */
+ off = min(5, ilog2((*delay_off - 1) / AW2013_TIME_STEP) + 1);
+ on = min(7, ilog2((*delay_on - 1) / AW2013_TIME_STEP) + 1);
+
+ *delay_off = BIT(off) * AW2013_TIME_STEP;
+ *delay_on = BIT(on) * AW2013_TIME_STEP;
+
+ /* Set timings */
+ ret = regmap_write(led->chip->regmap,
+ AW2013_LEDT0(num), AW2013_LEDT0_T2(on));
+ if (ret)
+ goto out;
+ ret = regmap_write(led->chip->regmap,
+ AW2013_LEDT1(num), AW2013_LEDT1_T4(off));
+ if (ret)
+ goto out;
+
+ /* Finally, enable the LED */
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCFG(num),
+ AW2013_LCFG_MD, 0xFF);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(led->chip->regmap, AW2013_LCTR,
+ AW2013_LCTR_LE(num), 0xFF);
+
+out:
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int aw2013_probe_dt(struct aw2013 *chip)
+{
+ struct device_node *np = dev_of_node(&chip->client->dev);
+ int count, ret = 0, i = 0;
+ struct aw2013_led *led;
+
+ count = of_get_available_child_count(np);
+ if (!count || count > AW2013_MAX_LEDS)
+ return -EINVAL;
+
+ regmap_write(chip->regmap, AW2013_RSTR, AW2013_RSTR_RESET);
+
+ for_each_available_child_of_node_scoped(np, child) {
+ struct led_init_data init_data = {};
+ u32 source;
+ u32 imax;
+
+ ret = of_property_read_u32(child, "reg", &source);
+ if (ret != 0 || source >= AW2013_MAX_LEDS) {
+ dev_err(&chip->client->dev,
+ "Couldn't read LED address: %d\n", ret);
+ count--;
+ continue;
+ }
+
+ led = &chip->leds[i];
+ led->num = source;
+ led->chip = chip;
+ init_data.fwnode = of_fwnode_handle(child);
+
+ if (!of_property_read_u32(child, "led-max-microamp", &imax)) {
+ led->imax = min_t(u32, imax / 5000, 3);
+ } else {
+ led->imax = 1; // 5mA
+ dev_info(&chip->client->dev,
+ "DT property led-max-microamp is missing\n");
+ }
+
+ led->cdev.brightness_set_blocking = aw2013_brightness_set;
+ led->cdev.blink_set = aw2013_blink_set;
+
+ ret = devm_led_classdev_register_ext(&chip->client->dev,
+ &led->cdev, &init_data);
+ if (ret < 0)
+ return ret;
+
+ i++;
+ }
+
+ if (!count)
+ return -EINVAL;
+
+ chip->num_leds = i;
+
+ 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,
+ .max_register = AW2013_REG_MAX,
+};
+
+static int aw2013_probe(struct i2c_client *client)
+{
+ struct aw2013 *chip;
+ int ret;
+ unsigned int chipid;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ ret = devm_mutex_init(&client->dev, &chip->mutex);
+ if (ret)
+ return ret;
+
+ mutex_lock(&chip->mutex);
+
+ chip->client = client;
+ i2c_set_clientdata(client, chip);
+
+ chip->regmap = devm_regmap_init_i2c(client, &aw2013_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ goto error;
+ }
+
+ chip->regulators[0].supply = "vcc";
+ chip->regulators[1].supply = "vio";
+ ret = devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+ if (ret < 0) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(&client->dev,
+ "Failed to request regulators: %d\n", ret);
+ goto error;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to enable regulators: %d\n", ret);
+ goto error;
+ }
+
+ ret = regmap_read(chip->regmap, AW2013_RSTR, &chipid);
+ if (ret) {
+ dev_err(&client->dev, "Failed to read chip ID: %d\n",
+ ret);
+ goto error_reg;
+ }
+
+ if (chipid != AW2013_RSTR_CHIP_ID) {
+ dev_err(&client->dev, "Chip reported wrong ID: %x\n",
+ chipid);
+ ret = -ENODEV;
+ goto error_reg;
+ }
+
+ ret = devm_add_action(&client->dev, aw2013_chip_disable_action, chip);
+ if (ret)
+ goto error_reg;
+
+ ret = aw2013_probe_dt(chip);
+ if (ret < 0)
+ goto error_reg;
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to disable regulators: %d\n", ret);
+ goto error;
+ }
+
+ mutex_unlock(&chip->mutex);
+
+ return 0;
+
+error_reg:
+ regulator_bulk_disable(ARRAY_SIZE(chip->regulators),
+ chip->regulators);
+
+error:
+ mutex_unlock(&chip->mutex);
+ return ret;
+}
+
+static const struct of_device_id aw2013_match_table[] = {
+ { .compatible = "awinic,aw2013", },
+ { /* sentinel */ },
+};
+
+MODULE_DEVICE_TABLE(of, aw2013_match_table);
+
+static struct i2c_driver aw2013_driver = {
+ .driver = {
+ .name = "leds-aw2013",
+ .of_match_table = aw2013_match_table,
+ },
+ .probe = aw2013_probe,
+};
+
+module_i2c_driver(aw2013_driver);
+
+MODULE_AUTHOR("Nikita Travkin <nikitos.tr@gmail.com>");
+MODULE_DESCRIPTION("AW2013 LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-bcm6328.c b/drivers/leds/leds-bcm6328.c
new file mode 100644
index 000000000000..592bbf4b7e35
--- /dev/null
+++ b/drivers/leds/leds-bcm6328.c
@@ -0,0 +1,483 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for BCM6328 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ * Copyright 2015 Jonas Gorski <jogo@openwrt.org>
+ */
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6328_REG_INIT 0x00
+#define BCM6328_REG_MODE_HI 0x04
+#define BCM6328_REG_MODE_LO 0x08
+#define BCM6328_REG_HWDIS 0x0c
+#define BCM6328_REG_STROBE 0x10
+#define BCM6328_REG_LNKACTSEL_HI 0x14
+#define BCM6328_REG_LNKACTSEL_LO 0x18
+#define BCM6328_REG_RBACK 0x1c
+#define BCM6328_REG_SERMUX 0x20
+
+#define BCM6328_LED_MAX_COUNT 24
+#define BCM6328_LED_DEF_DELAY 500
+
+#define BCM6328_LED_BLINK_DELAYS 2
+#define BCM6328_LED_BLINK_MS 20
+
+#define BCM6328_LED_BLINK_MASK 0x3f
+#define BCM6328_LED_BLINK1_SHIFT 0
+#define BCM6328_LED_BLINK1_MASK (BCM6328_LED_BLINK_MASK << \
+ BCM6328_LED_BLINK1_SHIFT)
+#define BCM6328_LED_BLINK2_SHIFT 6
+#define BCM6328_LED_BLINK2_MASK (BCM6328_LED_BLINK_MASK << \
+ BCM6328_LED_BLINK2_SHIFT)
+#define BCM6328_SERIAL_LED_EN BIT(12)
+#define BCM6328_SERIAL_LED_MUX BIT(13)
+#define BCM6328_SERIAL_LED_CLK_NPOL BIT(14)
+#define BCM6328_SERIAL_LED_DATA_PPOL BIT(15)
+#define BCM6328_SERIAL_LED_SHIFT_DIR BIT(16)
+#define BCM6328_LED_SHIFT_TEST BIT(30)
+#define BCM6328_LED_TEST BIT(31)
+#define BCM6328_INIT_MASK (BCM6328_SERIAL_LED_EN | \
+ BCM6328_SERIAL_LED_MUX | \
+ BCM6328_SERIAL_LED_CLK_NPOL | \
+ BCM6328_SERIAL_LED_DATA_PPOL | \
+ BCM6328_SERIAL_LED_SHIFT_DIR)
+
+#define BCM6328_LED_MODE_MASK 3
+#define BCM6328_LED_MODE_ON 0
+#define BCM6328_LED_MODE_BLINK1 1
+#define BCM6328_LED_MODE_BLINK2 2
+#define BCM6328_LED_MODE_OFF 3
+#define BCM6328_LED_SHIFT(X) ((X) << 1)
+
+/**
+ * struct bcm6328_led - state container for bcm6328 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @blink_leds: blinking LEDs
+ * @blink_delay: blinking delay
+ * @active_low: LED is active low
+ */
+struct bcm6328_led {
+ struct led_classdev cdev;
+ void __iomem *mem;
+ spinlock_t *lock;
+ unsigned long pin;
+ unsigned long *blink_leds;
+ unsigned long *blink_delay;
+ bool active_low;
+};
+
+static void bcm6328_led_write(void __iomem *reg, unsigned long data)
+{
+#ifdef CONFIG_CPU_BIG_ENDIAN
+ iowrite32be(data, reg);
+#else
+ writel(data, reg);
+#endif
+}
+
+static unsigned long bcm6328_led_read(void __iomem *reg)
+{
+#ifdef CONFIG_CPU_BIG_ENDIAN
+ return ioread32be(reg);
+#else
+ return readl(reg);
+#endif
+}
+
+/*
+ * LEDMode 64 bits / 24 LEDs
+ * bits [31:0] -> LEDs 8-23
+ * bits [47:32] -> LEDs 0-7
+ * bits [63:48] -> unused
+ */
+static unsigned long bcm6328_pin2shift(unsigned long pin)
+{
+ if (pin < 8)
+ return pin + 16; /* LEDs 0-7 (bits 47:32) */
+ else
+ return pin - 8; /* LEDs 8-23 (bits 31:0) */
+}
+
+static void bcm6328_led_mode(struct bcm6328_led *led, unsigned long value)
+{
+ void __iomem *mode;
+ unsigned long val, shift;
+
+ shift = bcm6328_pin2shift(led->pin);
+ if (shift >= 16)
+ mode = led->mem + BCM6328_REG_MODE_HI;
+ else
+ mode = led->mem + BCM6328_REG_MODE_LO;
+
+ val = bcm6328_led_read(mode);
+ val &= ~(BCM6328_LED_MODE_MASK << BCM6328_LED_SHIFT(shift % 16));
+ val |= (value << BCM6328_LED_SHIFT(shift % 16));
+ bcm6328_led_write(mode, val);
+}
+
+static void bcm6328_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm6328_led *led =
+ container_of(led_cdev, struct bcm6328_led, cdev);
+ unsigned long flags;
+
+ spin_lock_irqsave(led->lock, flags);
+
+ /* Remove LED from cached HW blinking intervals */
+ led->blink_leds[0] &= ~BIT(led->pin);
+ led->blink_leds[1] &= ~BIT(led->pin);
+
+ /* Set LED on/off */
+ if ((led->active_low && value == LED_OFF) ||
+ (!led->active_low && value != LED_OFF))
+ bcm6328_led_mode(led, BCM6328_LED_MODE_ON);
+ else
+ bcm6328_led_mode(led, BCM6328_LED_MODE_OFF);
+
+ spin_unlock_irqrestore(led->lock, flags);
+}
+
+static unsigned long bcm6328_blink_delay(unsigned long delay)
+{
+ unsigned long bcm6328_delay;
+
+ bcm6328_delay = delay + BCM6328_LED_BLINK_MS / 2;
+ bcm6328_delay = bcm6328_delay / BCM6328_LED_BLINK_MS;
+ if (bcm6328_delay == 0)
+ bcm6328_delay = 1;
+
+ return bcm6328_delay;
+}
+
+static int bcm6328_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct bcm6328_led *led =
+ container_of(led_cdev, struct bcm6328_led, cdev);
+ unsigned long delay, flags;
+ int rc;
+
+ if (!*delay_on)
+ *delay_on = BCM6328_LED_DEF_DELAY;
+ if (!*delay_off)
+ *delay_off = BCM6328_LED_DEF_DELAY;
+
+ delay = bcm6328_blink_delay(*delay_on);
+ if (delay != bcm6328_blink_delay(*delay_off)) {
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay_on != delay_off)\n");
+ return -EINVAL;
+ }
+
+ if (delay > BCM6328_LED_BLINK_MASK) {
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay > %ums)\n",
+ BCM6328_LED_BLINK_MASK * BCM6328_LED_BLINK_MS);
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(led->lock, flags);
+ /*
+ * Check if any of the two configurable HW blinking intervals is
+ * available:
+ * 1. No LEDs assigned to the HW blinking interval.
+ * 2. Only this LED is assigned to the HW blinking interval.
+ * 3. LEDs with the same delay assigned.
+ */
+ if (led->blink_leds[0] == 0 ||
+ led->blink_leds[0] == BIT(led->pin) ||
+ led->blink_delay[0] == delay) {
+ unsigned long val;
+
+ /* Add LED to the first HW blinking interval cache */
+ led->blink_leds[0] |= BIT(led->pin);
+
+ /* Remove LED from the second HW blinking interval cache */
+ led->blink_leds[1] &= ~BIT(led->pin);
+
+ /* Cache first HW blinking interval delay */
+ led->blink_delay[0] = delay;
+
+ /* Update the delay for the first HW blinking interval */
+ val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
+ val &= ~BCM6328_LED_BLINK1_MASK;
+ val |= (delay << BCM6328_LED_BLINK1_SHIFT);
+ bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
+
+ /* Set the LED to first HW blinking interval */
+ bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK1);
+
+ rc = 0;
+ } else if (led->blink_leds[1] == 0 ||
+ led->blink_leds[1] == BIT(led->pin) ||
+ led->blink_delay[1] == delay) {
+ unsigned long val;
+
+ /* Remove LED from the first HW blinking interval */
+ led->blink_leds[0] &= ~BIT(led->pin);
+
+ /* Add LED to the second HW blinking interval */
+ led->blink_leds[1] |= BIT(led->pin);
+
+ /* Cache second HW blinking interval delay */
+ led->blink_delay[1] = delay;
+
+ /* Update the delay for the second HW blinking interval */
+ val = bcm6328_led_read(led->mem + BCM6328_REG_INIT);
+ val &= ~BCM6328_LED_BLINK2_MASK;
+ val |= (delay << BCM6328_LED_BLINK2_SHIFT);
+ bcm6328_led_write(led->mem + BCM6328_REG_INIT, val);
+
+ /* Set the LED to second HW blinking interval */
+ bcm6328_led_mode(led, BCM6328_LED_MODE_BLINK2);
+
+ rc = 0;
+ } else {
+ dev_dbg(led_cdev->dev,
+ "fallback to soft blinking (delay already set)\n");
+ rc = -EINVAL;
+ }
+ spin_unlock_irqrestore(led->lock, flags);
+
+ return rc;
+}
+
+static int bcm6328_hwled(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock)
+{
+ int i, cnt;
+ unsigned long flags, val;
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(mem + BCM6328_REG_HWDIS);
+ val &= ~BIT(reg);
+ bcm6328_led_write(mem + BCM6328_REG_HWDIS, val);
+ spin_unlock_irqrestore(lock, flags);
+
+ /* Only LEDs 0-7 can be activity/link controlled */
+ if (reg >= 8)
+ return 0;
+
+ cnt = of_property_count_elems_of_size(nc, "brcm,link-signal-sources",
+ sizeof(u32));
+ for (i = 0; i < cnt; i++) {
+ u32 sel;
+ void __iomem *addr;
+
+ if (reg < 4)
+ addr = mem + BCM6328_REG_LNKACTSEL_LO;
+ else
+ addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+ of_property_read_u32_index(nc, "brcm,link-signal-sources", i,
+ &sel);
+
+ if (reg / 4 != sel / 4) {
+ dev_warn(dev, "invalid link signal source\n");
+ continue;
+ }
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(addr);
+ val |= (BIT(reg % 4) << (((sel % 4) * 4) + 16));
+ bcm6328_led_write(addr, val);
+ spin_unlock_irqrestore(lock, flags);
+ }
+
+ cnt = of_property_count_elems_of_size(nc,
+ "brcm,activity-signal-sources",
+ sizeof(u32));
+ for (i = 0; i < cnt; i++) {
+ u32 sel;
+ void __iomem *addr;
+
+ if (reg < 4)
+ addr = mem + BCM6328_REG_LNKACTSEL_LO;
+ else
+ addr = mem + BCM6328_REG_LNKACTSEL_HI;
+
+ of_property_read_u32_index(nc, "brcm,activity-signal-sources",
+ i, &sel);
+
+ if (reg / 4 != sel / 4) {
+ dev_warn(dev, "invalid activity signal source\n");
+ continue;
+ }
+
+ spin_lock_irqsave(lock, flags);
+ val = bcm6328_led_read(addr);
+ val |= (BIT(reg % 4) << ((sel % 4) * 4));
+ bcm6328_led_write(addr, val);
+ spin_unlock_irqrestore(lock, flags);
+ }
+
+ return 0;
+}
+
+static int bcm6328_led(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock,
+ unsigned long *blink_leds, unsigned long *blink_delay)
+{
+ struct led_init_data init_data = {};
+ struct bcm6328_led *led;
+ enum led_default_state state;
+ unsigned long val, shift;
+ void __iomem *mode;
+ int rc;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pin = reg;
+ led->mem = mem;
+ led->lock = lock;
+ led->blink_leds = blink_leds;
+ led->blink_delay = blink_delay;
+
+ if (of_property_read_bool(nc, "active-low"))
+ led->active_low = true;
+
+ init_data.fwnode = of_fwnode_handle(nc);
+
+ state = led_init_default_state_get(init_data.fwnode);
+ switch (state) {
+ case LEDS_DEFSTATE_ON:
+ led->cdev.brightness = LED_FULL;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ shift = bcm6328_pin2shift(led->pin);
+ if (shift >= 16)
+ mode = mem + BCM6328_REG_MODE_HI;
+ else
+ mode = mem + BCM6328_REG_MODE_LO;
+
+ val = bcm6328_led_read(mode) >> BCM6328_LED_SHIFT(shift % 16);
+ val &= BCM6328_LED_MODE_MASK;
+ if ((led->active_low && val == BCM6328_LED_MODE_OFF) ||
+ (!led->active_low && val == BCM6328_LED_MODE_ON))
+ led->cdev.brightness = LED_FULL;
+ else
+ led->cdev.brightness = LED_OFF;
+ break;
+ default:
+ led->cdev.brightness = LED_OFF;
+ }
+
+ bcm6328_led_set(&led->cdev, led->cdev.brightness);
+
+ led->cdev.brightness_set = bcm6328_led_set;
+ led->cdev.blink_set = bcm6328_blink_set;
+
+ rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (rc < 0)
+ return rc;
+
+ dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+ return 0;
+}
+
+static int bcm6328_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(&pdev->dev);
+ void __iomem *mem;
+ spinlock_t *lock; /* memory lock */
+ unsigned long val, *blink_leds, *blink_delay;
+
+ mem = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ blink_leds = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
+ sizeof(*blink_leds), GFP_KERNEL);
+ if (!blink_leds)
+ return -ENOMEM;
+
+ blink_delay = devm_kcalloc(dev, BCM6328_LED_BLINK_DELAYS,
+ sizeof(*blink_delay), GFP_KERNEL);
+ if (!blink_delay)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ bcm6328_led_write(mem + BCM6328_REG_HWDIS, ~0);
+ bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_HI, 0);
+ bcm6328_led_write(mem + BCM6328_REG_LNKACTSEL_LO, 0);
+
+ val = bcm6328_led_read(mem + BCM6328_REG_INIT);
+ val &= ~(BCM6328_INIT_MASK);
+ if (of_property_read_bool(np, "brcm,serial-leds"))
+ val |= BCM6328_SERIAL_LED_EN;
+ if (of_property_read_bool(np, "brcm,serial-mux"))
+ val |= BCM6328_SERIAL_LED_MUX;
+ if (of_property_read_bool(np, "brcm,serial-clk-low"))
+ val |= BCM6328_SERIAL_LED_CLK_NPOL;
+ if (!of_property_read_bool(np, "brcm,serial-dat-low"))
+ val |= BCM6328_SERIAL_LED_DATA_PPOL;
+ if (!of_property_read_bool(np, "brcm,serial-shift-inv"))
+ val |= BCM6328_SERIAL_LED_SHIFT_DIR;
+ bcm6328_led_write(mem + BCM6328_REG_INIT, val);
+
+ for_each_available_child_of_node_scoped(np, child) {
+ int rc;
+ u32 reg;
+
+ if (of_property_read_u32(child, "reg", &reg))
+ continue;
+
+ if (reg >= BCM6328_LED_MAX_COUNT) {
+ dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+ BCM6328_LED_MAX_COUNT);
+ continue;
+ }
+
+ if (of_property_read_bool(child, "brcm,hardware-controlled"))
+ rc = bcm6328_hwled(dev, child, reg, mem, lock);
+ else
+ rc = bcm6328_led(dev, child, reg, mem, lock,
+ blink_leds, blink_delay);
+
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm6328_leds_of_match[] = {
+ { .compatible = "brcm,bcm6328-leds", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm6328_leds_of_match);
+
+static struct platform_driver bcm6328_leds_driver = {
+ .probe = bcm6328_leds_probe,
+ .driver = {
+ .name = "leds-bcm6328",
+ .of_match_table = bcm6328_leds_of_match,
+ },
+};
+
+module_platform_driver(bcm6328_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_AUTHOR("Jonas Gorski <jogo@openwrt.org>");
+MODULE_DESCRIPTION("LED driver for BCM6328 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6328");
diff --git a/drivers/leds/leds-bcm6358.c b/drivers/leds/leds-bcm6358.c
new file mode 100644
index 000000000000..51fcff2a64fd
--- /dev/null
+++ b/drivers/leds/leds-bcm6358.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Driver for BCM6358 memory-mapped LEDs, based on leds-syscon.c
+ *
+ * Copyright 2015 Álvaro Fernández Rojas <noltari@gmail.com>
+ */
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+
+#define BCM6358_REG_MODE 0x0
+#define BCM6358_REG_CTRL 0x4
+
+#define BCM6358_SLED_CLKDIV_MASK 3
+#define BCM6358_SLED_CLKDIV_1 0
+#define BCM6358_SLED_CLKDIV_2 1
+#define BCM6358_SLED_CLKDIV_4 2
+#define BCM6358_SLED_CLKDIV_8 3
+
+#define BCM6358_SLED_POLARITY BIT(2)
+#define BCM6358_SLED_BUSY BIT(3)
+
+#define BCM6358_SLED_MAX_COUNT 32
+#define BCM6358_SLED_WAIT 100
+
+/**
+ * struct bcm6358_led - state container for bcm6358 based LEDs
+ * @cdev: LED class device for this LED
+ * @mem: memory resource
+ * @lock: memory lock
+ * @pin: LED pin number
+ * @active_low: LED is active low
+ */
+struct bcm6358_led {
+ struct led_classdev cdev;
+ void __iomem *mem;
+ spinlock_t *lock;
+ unsigned long pin;
+ bool active_low;
+};
+
+static void bcm6358_led_write(void __iomem *reg, unsigned long data)
+{
+#ifdef CONFIG_CPU_BIG_ENDIAN
+ iowrite32be(data, reg);
+#else
+ writel(data, reg);
+#endif
+}
+
+static unsigned long bcm6358_led_read(void __iomem *reg)
+{
+#ifdef CONFIG_CPU_BIG_ENDIAN
+ return ioread32be(reg);
+#else
+ return readl(reg);
+#endif
+}
+
+static unsigned long bcm6358_led_busy(void __iomem *mem)
+{
+ unsigned long val;
+
+ while ((val = bcm6358_led_read(mem + BCM6358_REG_CTRL)) &
+ BCM6358_SLED_BUSY)
+ udelay(BCM6358_SLED_WAIT);
+
+ return val;
+}
+
+static void bcm6358_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct bcm6358_led *led =
+ container_of(led_cdev, struct bcm6358_led, cdev);
+ unsigned long flags, val;
+
+ spin_lock_irqsave(led->lock, flags);
+ bcm6358_led_busy(led->mem);
+ val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+ if ((led->active_low && value == LED_OFF) ||
+ (!led->active_low && value != LED_OFF))
+ val |= BIT(led->pin);
+ else
+ val &= ~(BIT(led->pin));
+ bcm6358_led_write(led->mem + BCM6358_REG_MODE, val);
+ spin_unlock_irqrestore(led->lock, flags);
+}
+
+static int bcm6358_led(struct device *dev, struct device_node *nc, u32 reg,
+ void __iomem *mem, spinlock_t *lock)
+{
+ struct led_init_data init_data = {};
+ struct bcm6358_led *led;
+ enum led_default_state state;
+ unsigned long val;
+ int rc;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->pin = reg;
+ led->mem = mem;
+ led->lock = lock;
+
+ if (of_property_read_bool(nc, "active-low"))
+ led->active_low = true;
+
+ init_data.fwnode = of_fwnode_handle(nc);
+
+ state = led_init_default_state_get(init_data.fwnode);
+ switch (state) {
+ case LEDS_DEFSTATE_ON:
+ led->cdev.brightness = LED_FULL;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ val = bcm6358_led_read(led->mem + BCM6358_REG_MODE);
+ val &= BIT(led->pin);
+ if ((led->active_low && !val) || (!led->active_low && val))
+ led->cdev.brightness = LED_FULL;
+ else
+ led->cdev.brightness = LED_OFF;
+ break;
+ default:
+ led->cdev.brightness = LED_OFF;
+ }
+
+ bcm6358_led_set(&led->cdev, led->cdev.brightness);
+
+ led->cdev.brightness_set = bcm6358_led_set;
+
+ rc = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (rc < 0)
+ return rc;
+
+ dev_dbg(dev, "registered LED %s\n", led->cdev.name);
+
+ return 0;
+}
+
+static int bcm6358_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(&pdev->dev);
+ void __iomem *mem;
+ spinlock_t *lock; /* memory lock */
+ unsigned long val;
+ u32 clk_div;
+
+ mem = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(mem))
+ return PTR_ERR(mem);
+
+ lock = devm_kzalloc(dev, sizeof(*lock), GFP_KERNEL);
+ if (!lock)
+ return -ENOMEM;
+
+ spin_lock_init(lock);
+
+ val = bcm6358_led_busy(mem);
+ val &= ~(BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK);
+ if (of_property_read_bool(np, "brcm,clk-dat-low"))
+ val |= BCM6358_SLED_POLARITY;
+ of_property_read_u32(np, "brcm,clk-div", &clk_div);
+ switch (clk_div) {
+ case 8:
+ val |= BCM6358_SLED_CLKDIV_8;
+ break;
+ case 4:
+ val |= BCM6358_SLED_CLKDIV_4;
+ break;
+ case 2:
+ val |= BCM6358_SLED_CLKDIV_2;
+ break;
+ default:
+ val |= BCM6358_SLED_CLKDIV_1;
+ break;
+ }
+ bcm6358_led_write(mem + BCM6358_REG_CTRL, val);
+
+ for_each_available_child_of_node_scoped(np, child) {
+ int rc;
+ u32 reg;
+
+ if (of_property_read_u32(child, "reg", &reg))
+ continue;
+
+ if (reg >= BCM6358_SLED_MAX_COUNT) {
+ dev_err(dev, "invalid LED (%u >= %d)\n", reg,
+ BCM6358_SLED_MAX_COUNT);
+ continue;
+ }
+
+ rc = bcm6358_led(dev, child, reg, mem, lock);
+ if (rc < 0)
+ return rc;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id bcm6358_leds_of_match[] = {
+ { .compatible = "brcm,bcm6358-leds", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bcm6358_leds_of_match);
+
+static struct platform_driver bcm6358_leds_driver = {
+ .probe = bcm6358_leds_probe,
+ .driver = {
+ .name = "leds-bcm6358",
+ .of_match_table = bcm6358_leds_of_match,
+ },
+};
+
+module_platform_driver(bcm6358_leds_driver);
+
+MODULE_AUTHOR("Álvaro Fernández Rojas <noltari@gmail.com>");
+MODULE_DESCRIPTION("LED driver for BCM6358 controllers");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-bcm6358");
diff --git a/drivers/leds/leds-bd2606mvv.c b/drivers/leds/leds-bd2606mvv.c
new file mode 100644
index 000000000000..c1181a35d0f7
--- /dev/null
+++ b/drivers/leds/leds-bd2606mvv.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Andreas Kemnade
+ *
+ * Datasheet:
+ * https://fscdn.rohm.com/en/products/databook/datasheet/ic/power/led_driver/bd2606mvv_1-e.pdf
+ *
+ * If LED brightness cannot be controlled independently due to shared
+ * brightness registers, max_brightness is set to 1 and only on/off
+ * is possible for the affected LED pair.
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define BD2606_MAX_LEDS 6
+#define BD2606_MAX_BRIGHTNESS 63
+#define BD2606_REG_PWRCNT 3
+#define ldev_to_led(c) container_of(c, struct bd2606mvv_led, ldev)
+
+struct bd2606mvv_led {
+ unsigned int led_no;
+ struct led_classdev ldev;
+ struct bd2606mvv_priv *priv;
+};
+
+struct bd2606mvv_priv {
+ struct bd2606mvv_led leds[BD2606_MAX_LEDS];
+ struct regmap *regmap;
+};
+
+static int
+bd2606mvv_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct bd2606mvv_led *led = ldev_to_led(led_cdev);
+ struct bd2606mvv_priv *priv = led->priv;
+ int err;
+
+ if (brightness == 0)
+ return regmap_update_bits(priv->regmap,
+ BD2606_REG_PWRCNT,
+ 1 << led->led_no,
+ 0);
+
+ /* shared brightness register */
+ err = regmap_write(priv->regmap, led->led_no / 2,
+ led_cdev->max_brightness == 1 ?
+ BD2606_MAX_BRIGHTNESS : brightness);
+ if (err)
+ return err;
+
+ return regmap_update_bits(priv->regmap,
+ BD2606_REG_PWRCNT,
+ 1 << led->led_no,
+ 1 << led->led_no);
+}
+
+static const struct regmap_config bd2606mvv_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x3,
+};
+
+static int bd2606mvv_probe(struct i2c_client *client)
+{
+ 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, j;
+
+ if (!dev_fwnode(dev))
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(client, &bd2606mvv_regmap);
+ if (IS_ERR(priv->regmap)) {
+ err = PTR_ERR(priv->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", err);
+ return err;
+ }
+
+ i2c_set_clientdata(client, priv);
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct bd2606mvv_led *led;
+
+ err = fwnode_property_read_u32(child, "reg", &reg);
+ if (err)
+ return err;
+
+ if (reg < 0 || reg >= BD2606_MAX_LEDS || led_fwnodes[reg])
+ return -EINVAL;
+
+ led = &priv->leds[reg];
+ led_fwnodes[reg] = fwnode_handle_get(child);
+ active_pairs[reg / 2]++;
+ led->priv = priv;
+ led->led_no = reg;
+ led->ldev.brightness_set_blocking = bd2606mvv_brightness_set;
+ led->ldev.max_brightness = BD2606_MAX_BRIGHTNESS;
+ }
+
+ for (i = 0; i < BD2606_MAX_LEDS; i++) {
+ struct led_init_data init_data = {};
+
+ if (!led_fwnodes[i])
+ continue;
+
+ init_data.fwnode = led_fwnodes[i];
+ /* Check whether brightness can be independently adjusted. */
+ if (active_pairs[i / 2] == 2)
+ priv->leds[i].ldev.max_brightness = 1;
+
+ err = devm_led_classdev_register_ext(dev,
+ &priv->leds[i].ldev,
+ &init_data);
+ if (err < 0) {
+ 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);
+ }
+ }
+ return 0;
+}
+
+static const struct of_device_id __maybe_unused of_bd2606mvv_leds_match[] = {
+ { .compatible = "rohm,bd2606mvv", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_bd2606mvv_leds_match);
+
+static struct i2c_driver bd2606mvv_driver = {
+ .driver = {
+ .name = "leds-bd2606mvv",
+ .of_match_table = of_match_ptr(of_bd2606mvv_leds_match),
+ },
+ .probe = bd2606mvv_probe,
+};
+
+module_i2c_driver(bd2606mvv_driver);
+
+MODULE_AUTHOR("Andreas Kemnade <andreas@kemnade.info>");
+MODULE_DESCRIPTION("BD2606 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c
index 2db04231a792..2a08c5f27608 100644
--- a/drivers/leds/leds-bd2802.c
+++ b/drivers/leds/leds-bd2802.c
@@ -1,20 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* leds-bd2802.c - RGB LED Driver
*
* Copyright (C) 2009 Samsung Electronics
* Kim Kyuwon <q1.kim@samsung.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
* Datasheet: http://www.rohm.com/products/databook/driver/pdf/bd2802gu-e.pdf
- *
*/
#include <linux/module.h>
#include <linux/i2c.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/leds.h>
#include <linux/leds-bd2802.h>
@@ -71,8 +67,8 @@ struct led_state {
struct bd2802_led {
struct bd2802_led_platform_data *pdata;
struct i2c_client *client;
+ struct gpio_desc *reset;
struct rw_semaphore rwsem;
- struct work_struct work;
struct led_state led[2];
@@ -205,7 +201,7 @@ static void bd2802_update_state(struct bd2802_led *led, enum led_ids id,
return;
if (bd2802_is_all_off(led) && !led->adf_on) {
- gpio_set_value(led->pdata->reset_gpio, 0);
+ gpiod_set_value(led->reset, 1);
return;
}
@@ -231,7 +227,7 @@ static void bd2802_configure(struct bd2802_led *led)
static void bd2802_reset_cancel(struct bd2802_led *led)
{
- gpio_set_value(led->pdata->reset_gpio, 1);
+ gpiod_set_value(led->reset, 0);
udelay(100);
bd2802_configure(led);
}
@@ -425,7 +421,7 @@ static void bd2802_disable_adv_conf(struct bd2802_led *led)
bd2802_addr_attributes[i]);
if (bd2802_is_all_off(led))
- gpio_set_value(led->pdata->reset_gpio, 0);
+ gpiod_set_value(led->reset, 1);
led->adf_on = 0;
}
@@ -518,29 +514,22 @@ static struct device_attribute *bd2802_attributes[] = {
&bd2802_rgb_current_attr,
};
-static void bd2802_led_work(struct work_struct *work)
-{
- struct bd2802_led *led = container_of(work, struct bd2802_led, work);
-
- if (led->state)
- bd2802_turn_on(led, led->led_id, led->color, led->state);
- else
- bd2802_turn_off(led, led->led_id, led->color);
-}
-
#define BD2802_CONTROL_RGBS(name, id, clr) \
-static void bd2802_set_##name##_brightness(struct led_classdev *led_cdev,\
+static int bd2802_set_##name##_brightness(struct led_classdev *led_cdev,\
enum led_brightness value) \
{ \
struct bd2802_led *led = \
container_of(led_cdev, struct bd2802_led, cdev_##name); \
led->led_id = id; \
led->color = clr; \
- if (value == LED_OFF) \
+ if (value == LED_OFF) { \
led->state = BD2802_OFF; \
- else \
+ bd2802_turn_off(led, led->led_id, led->color); \
+ } else { \
led->state = BD2802_ON; \
- schedule_work(&led->work); \
+ bd2802_turn_on(led, led->led_id, led->color, BD2802_ON);\
+ } \
+ return 0; \
} \
static int bd2802_set_##name##_blink(struct led_classdev *led_cdev, \
unsigned long *delay_on, unsigned long *delay_off) \
@@ -552,7 +541,7 @@ static int bd2802_set_##name##_blink(struct led_classdev *led_cdev, \
led->led_id = id; \
led->color = clr; \
led->state = BD2802_BLINK; \
- schedule_work(&led->work); \
+ bd2802_turn_on(led, led->led_id, led->color, BD2802_BLINK); \
return 0; \
}
@@ -567,11 +556,9 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
{
int ret;
- INIT_WORK(&led->work, bd2802_led_work);
-
led->cdev_led1r.name = "led1_R";
led->cdev_led1r.brightness = LED_OFF;
- led->cdev_led1r.brightness_set = bd2802_set_led1r_brightness;
+ led->cdev_led1r.brightness_set_blocking = bd2802_set_led1r_brightness;
led->cdev_led1r.blink_set = bd2802_set_led1r_blink;
ret = led_classdev_register(&led->client->dev, &led->cdev_led1r);
@@ -583,7 +570,7 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
led->cdev_led1g.name = "led1_G";
led->cdev_led1g.brightness = LED_OFF;
- led->cdev_led1g.brightness_set = bd2802_set_led1g_brightness;
+ led->cdev_led1g.brightness_set_blocking = bd2802_set_led1g_brightness;
led->cdev_led1g.blink_set = bd2802_set_led1g_blink;
ret = led_classdev_register(&led->client->dev, &led->cdev_led1g);
@@ -595,7 +582,7 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
led->cdev_led1b.name = "led1_B";
led->cdev_led1b.brightness = LED_OFF;
- led->cdev_led1b.brightness_set = bd2802_set_led1b_brightness;
+ led->cdev_led1b.brightness_set_blocking = bd2802_set_led1b_brightness;
led->cdev_led1b.blink_set = bd2802_set_led1b_blink;
ret = led_classdev_register(&led->client->dev, &led->cdev_led1b);
@@ -607,7 +594,7 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
led->cdev_led2r.name = "led2_R";
led->cdev_led2r.brightness = LED_OFF;
- led->cdev_led2r.brightness_set = bd2802_set_led2r_brightness;
+ led->cdev_led2r.brightness_set_blocking = bd2802_set_led2r_brightness;
led->cdev_led2r.blink_set = bd2802_set_led2r_blink;
ret = led_classdev_register(&led->client->dev, &led->cdev_led2r);
@@ -619,7 +606,7 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
led->cdev_led2g.name = "led2_G";
led->cdev_led2g.brightness = LED_OFF;
- led->cdev_led2g.brightness_set = bd2802_set_led2g_brightness;
+ led->cdev_led2g.brightness_set_blocking = bd2802_set_led2g_brightness;
led->cdev_led2g.blink_set = bd2802_set_led2g_blink;
ret = led_classdev_register(&led->client->dev, &led->cdev_led2g);
@@ -631,7 +618,7 @@ static int bd2802_register_led_classdev(struct bd2802_led *led)
led->cdev_led2b.name = "led2_B";
led->cdev_led2b.brightness = LED_OFF;
- led->cdev_led2b.brightness_set = bd2802_set_led2b_brightness;
+ led->cdev_led2b.brightness_set_blocking = bd2802_set_led2b_brightness;
led->cdev_led2b.blink_set = bd2802_set_led2b_blink;
led->cdev_led2b.flags |= LED_CORE_SUSPENDRESUME;
@@ -661,7 +648,6 @@ failed_unregister_led1_R:
static void bd2802_unregister_led_classdev(struct bd2802_led *led)
{
- cancel_work_sync(&led->work);
led_classdev_unregister(&led->cdev_led2b);
led_classdev_unregister(&led->cdev_led2g);
led_classdev_unregister(&led->cdev_led2r);
@@ -670,25 +656,28 @@ static void bd2802_unregister_led_classdev(struct bd2802_led *led)
led_classdev_unregister(&led->cdev_led1r);
}
-static int bd2802_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int bd2802_probe(struct i2c_client *client)
{
struct bd2802_led *led;
- struct bd2802_led_platform_data *pdata;
int ret, i;
led = devm_kzalloc(&client->dev, sizeof(struct bd2802_led), GFP_KERNEL);
- if (!led) {
- dev_err(&client->dev, "failed to allocate driver data\n");
+ if (!led)
return -ENOMEM;
- }
led->client = client;
- pdata = led->pdata = client->dev.platform_data;
i2c_set_clientdata(client, led);
- /* Configure RESET GPIO (L: RESET, H: RESET cancel) */
- gpio_request_one(pdata->reset_gpio, GPIOF_OUT_INIT_HIGH, "RGB_RESETB");
+ /*
+ * Configure RESET GPIO (L: RESET, H: RESET cancel)
+ *
+ * We request the reset GPIO as OUT_LOW which means de-asserted,
+ * board files specifying this GPIO line in a machine descriptor
+ * table should take care to specify GPIO_ACTIVE_LOW for this line.
+ */
+ led->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_LOW);
+ if (IS_ERR(led->reset))
+ return PTR_ERR(led->reset);
/* Tacss = min 0.1ms */
udelay(100);
@@ -702,7 +691,7 @@ static int bd2802_probe(struct i2c_client *client,
dev_info(&client->dev, "return 0x%02x\n", ret);
/* To save the power, reset BD2802 after detecting */
- gpio_set_value(led->pdata->reset_gpio, 0);
+ gpiod_set_value(led->reset, 1);
/* Default attributes */
led->wave_pattern = BD2802_PATTERN_HALF;
@@ -732,19 +721,17 @@ failed_unregister_dev_file:
return ret;
}
-static int bd2802_remove(struct i2c_client *client)
+static void bd2802_remove(struct i2c_client *client)
{
struct bd2802_led *led = i2c_get_clientdata(client);
int i;
- gpio_set_value(led->pdata->reset_gpio, 0);
+ gpiod_set_value(led->reset, 1);
bd2802_unregister_led_classdev(led);
if (led->adf_on)
bd2802_disable_adv_conf(led);
for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++)
device_remove_file(&led->client->dev, bd2802_attributes[i]);
-
- return 0;
}
#ifdef CONFIG_PM_SLEEP
@@ -767,7 +754,7 @@ static int bd2802_suspend(struct device *dev)
struct i2c_client *client = to_i2c_client(dev);
struct bd2802_led *led = i2c_get_clientdata(client);
- gpio_set_value(led->pdata->reset_gpio, 0);
+ gpiod_set_value(led->reset, 1);
return 0;
}
@@ -789,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 a502678cc7f5..577497b9d426 100644
--- a/drivers/leds/leds-blinkm.c
+++ b/drivers/leds/leds-blinkm.c
@@ -1,24 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* leds-blinkm.c
* (c) Jan-Simon Möller (dl9pf@gmx.de)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ * (c) Joseph Strauss (jstrauss@mailbox.org)
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
@@ -29,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 };
@@ -36,26 +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;
- atomic_t active;
};
-struct blinkm_work {
- struct blinkm_led *blinkm_led;
- struct work_struct work;
-};
-
-#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev)
-#define work_to_blmwork(c) container_of(c, struct blinkm_work, work)
+#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 */
@@ -160,14 +150,11 @@ static ssize_t show_color_common(struct device *dev, char *buf, int color)
return ret;
switch (color) {
case RED:
- return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red);
- break;
+ return sysfs_emit(buf, "%02X\n", data->red);
case GREEN:
- return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green);
- break;
+ return sysfs_emit(buf, "%02X\n", data->green);
case BLUE:
- return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue);
- break;
+ return sysfs_emit(buf, "%02X\n", data->blue);
default:
return -EINVAL;
}
@@ -216,13 +203,13 @@ static int store_color_common(struct device *dev, const char *buf, int color)
return 0;
}
-static ssize_t show_red(struct device *dev, struct device_attribute *attr,
+static ssize_t red_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return show_color_common(dev, buf, RED);
}
-static ssize_t store_red(struct device *dev, struct device_attribute *attr,
+static ssize_t red_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
@@ -233,15 +220,15 @@ static ssize_t store_red(struct device *dev, struct device_attribute *attr,
return count;
}
-static DEVICE_ATTR(red, S_IRUGO | S_IWUSR, show_red, store_red);
+static DEVICE_ATTR_RW(red);
-static ssize_t show_green(struct device *dev, struct device_attribute *attr,
+static ssize_t green_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return show_color_common(dev, buf, GREEN);
}
-static ssize_t store_green(struct device *dev, struct device_attribute *attr,
+static ssize_t green_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -253,15 +240,15 @@ static ssize_t store_green(struct device *dev, struct device_attribute *attr,
return count;
}
-static DEVICE_ATTR(green, S_IRUGO | S_IWUSR, show_green, store_green);
+static DEVICE_ATTR_RW(green);
-static ssize_t show_blue(struct device *dev, struct device_attribute *attr,
+static ssize_t blue_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
return show_color_common(dev, buf, BLUE);
}
-static ssize_t store_blue(struct device *dev, struct device_attribute *attr,
+static ssize_t blue_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
int ret;
@@ -272,16 +259,16 @@ static ssize_t store_blue(struct device *dev, struct device_attribute *attr,
return count;
}
-static DEVICE_ATTR(blue, S_IRUGO | S_IWUSR, show_blue, store_blue);
+static DEVICE_ATTR_RW(blue);
-static ssize_t show_test(struct device *dev, struct device_attribute *attr,
+static ssize_t test_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- return scnprintf(buf, PAGE_SIZE,
+ return sysfs_emit(buf,
"#Write into test to start test sequence!#\n");
}
-static ssize_t store_test(struct device *dev, struct device_attribute *attr,
+static ssize_t test_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
@@ -297,7 +284,7 @@ static ssize_t store_test(struct device *dev, struct device_attribute *attr,
return count;
}
-static DEVICE_ATTR(test, S_IRUGO | S_IWUSR, show_test, store_test);
+static DEVICE_ATTR_RW(test);
/* TODO: HSB, fade, timeadj, script ... */
@@ -309,7 +296,7 @@ static struct attribute *blinkm_attrs[] = {
NULL,
};
-static struct attribute_group blinkm_group = {
+static const struct attribute_group blinkm_group = {
.name = "blinkm",
.attrs = blinkm_attrs,
};
@@ -443,65 +430,48 @@ static int blinkm_transfer_hw(struct i2c_client *client, int cmd)
return 0;
}
-static void led_work(struct work_struct *work)
+static int blinkm_set_mc_brightness(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
- int ret;
- struct blinkm_led *led;
- struct blinkm_data *data ;
- struct blinkm_work *blm_work = work_to_blmwork(work);
-
- led = blm_work->blinkm_led;
- data = i2c_get_clientdata(led->i2c_client);
- ret = blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);
- atomic_dec(&led->active);
- dev_dbg(&led->i2c_client->dev,
- "# DONE # next_red = %d, next_green = %d,"
- " next_blue = %d, active = %d\n",
- data->next_red, data->next_green,
- data->next_blue, atomic_read(&led->active));
- kfree(blm_work);
+ 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);
- struct blinkm_work *bl_work;
switch (color) {
case RED:
/* bail out if there's no change */
if (data->next_red == (u8) value)
return 0;
- /* we assume a quite fast sequence here ([off]->on->off)
- * think of network led trigger - we cannot blink that fast, so
- * in case we already have a off->on->off transition queued up,
- * we refuse to queue up more.
- * Revisit: fast-changing brightness. */
- if (atomic_read(&led->active) > 1)
- return 0;
data->next_red = (u8) value;
break;
case GREEN:
/* bail out if there's no change */
if (data->next_green == (u8) value)
return 0;
- /* we assume a quite fast sequence here ([off]->on->off)
- * Revisit: fast-changing brightness. */
- if (atomic_read(&led->active) > 1)
- return 0;
data->next_green = (u8) value;
break;
case BLUE:
/* bail out if there's no change */
if (data->next_blue == (u8) value)
return 0;
- /* we assume a quite fast sequence here ([off]->on->off)
- * Revisit: fast-changing brightness. */
- if (atomic_read(&led->active) > 1)
- return 0;
data->next_blue = (u8) value;
break;
@@ -510,49 +480,37 @@ static int blinkm_led_common_set(struct led_classdev *led_cdev,
return -EINVAL;
}
- bl_work = kzalloc(sizeof(*bl_work), GFP_ATOMIC);
- if (!bl_work)
- return -ENOMEM;
-
- atomic_inc(&led->active);
+ blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB);
dev_dbg(&led->i2c_client->dev,
- "#TO_SCHED# next_red = %d, next_green = %d,"
- " next_blue = %d, active = %d\n",
+ "# DONE # next_red = %d, next_green = %d,"
+ " next_blue = %d\n",
data->next_red, data->next_green,
- data->next_blue, atomic_read(&led->active));
-
- /* a fresh work _item_ for each change */
- bl_work->blinkm_led = led;
- INIT_WORK(&bl_work->work, led_work);
- /* queue work in own queue for easy sync on exit*/
- schedule_work(&bl_work->work);
-
+ data->next_blue);
return 0;
}
-static void blinkm_led_red_set(struct led_classdev *led_cdev,
+static int blinkm_led_red_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- blinkm_led_common_set(led_cdev, value, RED);
+ return blinkm_led_common_set(led_cdev, value, RED);
}
-static void blinkm_led_green_set(struct led_classdev *led_cdev,
+static int blinkm_led_green_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- blinkm_led_common_set(led_cdev, value, GREEN);
+ return blinkm_led_common_set(led_cdev, value, GREEN);
}
-static void blinkm_led_blue_set(struct led_classdev *led_cdev,
+static int blinkm_led_blue_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- blinkm_led_common_set(led_cdev, value, BLUE);
+ return blinkm_led_common_set(led_cdev, value, BLUE);
}
static void blinkm_init_hw(struct i2c_client *client)
{
- int ret;
- ret = blinkm_transfer_hw(client, BLM_STOP_SCRIPT);
- ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+ blinkm_transfer_hw(client, BLM_STOP_SCRIPT);
+ blinkm_transfer_hw(client, BLM_GO_RGB);
}
static int blinkm_test_run(struct i2c_client *client)
@@ -606,8 +564,12 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
/* make sure the blinkM is balanced (read/writes) */
while (count > 0) {
ret = blinkm_write(client, BLM_GET_ADDR, NULL);
+ if (ret)
+ return ret;
usleep_range(5000, 10000);
ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);
+ if (ret)
+ return ret;
usleep_range(5000, 10000);
if (tmpargs[0] == 0x09)
count = 0;
@@ -628,132 +590,188 @@ static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
return -ENODEV;
}
- strlcpy(info->type, "blinkm", I2C_NAME_SIZE);
+ strscpy(info->type, "blinkm", I2C_NAME_SIZE);
return 0;
}
-static int blinkm_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+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 = 0x09;
- 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;
- atomic_set(&led[i]->active, 0);
+ 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 = blinkm_led_red_set;
+ 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 = blinkm_led_green_set;
+ 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 = blinkm_led_blue_set;
+ 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 blinkm_remove(struct i2c_client *client)
+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);
int ret = 0;
int i;
/* make sure no workqueue entries are pending */
- for (i = 0; i < 3; i++) {
- flush_scheduled_work();
- 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;
@@ -784,11 +802,10 @@ static int blinkm_remove(struct i2c_client *client)
dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");
sysfs_remove_group(&client->dev.kobj, &blinkm_group);
- return 0;
}
static const struct i2c_device_id blinkm_id[] = {
- {"blinkm", 0},
+ { "blinkm" },
{}
};
@@ -810,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
new file mode 100644
index 000000000000..9a609dd5acdc
--- /dev/null
+++ b/drivers/leds/leds-cht-wcove.c
@@ -0,0 +1,472 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Driver for LEDs connected to the Intel Cherry Trail Whiskey Cove PMIC
+ *
+ * Copyright 2019 Yauhen Kharuzhy <jekhor@gmail.com>
+ * Copyright 2023 Hans de Goede <hansg@kernel.org>
+ *
+ * Register info comes from the Lenovo Yoga Book Android opensource code
+ * available from Lenovo. File lenovo_yb1_x90f_l_osc_201803.7z path in the 7z:
+ * YB1_source_code/kernel/cht/drivers/misc/charger_gp_led.c
+ */
+
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/mfd/intel_soc_pmic.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/suspend.h>
+
+#define CHT_WC_LED1_CTRL 0x5e1f
+#define CHT_WC_LED1_FSM 0x5e20
+#define CHT_WC_LED1_PWM 0x5e21
+
+#define CHT_WC_LED2_CTRL 0x4fdf
+#define CHT_WC_LED2_FSM 0x4fe0
+#define CHT_WC_LED2_PWM 0x4fe1
+
+#define CHT_WC_LED1_SWCTL BIT(0) /* HW or SW control of charging led */
+#define CHT_WC_LED1_ON BIT(1)
+
+#define CHT_WC_LED2_ON BIT(0)
+#define CHT_WC_LED_I_MA2_5 (2 << 2) /* LED current limit */
+#define CHT_WC_LED_I_MASK GENMASK(3, 2) /* LED current limit mask */
+
+#define CHT_WC_LED_F_1_4_HZ (0 << 4)
+#define CHT_WC_LED_F_1_2_HZ (1 << 4)
+#define CHT_WC_LED_F_1_HZ (2 << 4)
+#define CHT_WC_LED_F_2_HZ (3 << 4)
+#define CHT_WC_LED_F_MASK GENMASK(5, 4)
+
+#define CHT_WC_LED_EFF_OFF (0 << 1)
+#define CHT_WC_LED_EFF_ON (1 << 1)
+#define CHT_WC_LED_EFF_BLINKING (2 << 1)
+#define CHT_WC_LED_EFF_BREATHING (3 << 1)
+#define CHT_WC_LED_EFF_MASK GENMASK(2, 1)
+
+#define CHT_WC_LED_COUNT 2
+
+struct cht_wc_led_regs {
+ /* Register addresses */
+ u16 ctrl;
+ u16 fsm;
+ u16 pwm;
+ /* Mask + values for turning the LED on/off */
+ u8 on_off_mask;
+ u8 on_val;
+ u8 off_val;
+};
+
+struct cht_wc_led_saved_regs {
+ unsigned int ctrl;
+ unsigned int fsm;
+ unsigned int pwm;
+};
+
+struct cht_wc_led {
+ struct led_classdev cdev;
+ const struct cht_wc_led_regs *regs;
+ struct regmap *regmap;
+ struct mutex mutex;
+ struct cht_wc_led_saved_regs saved_regs;
+};
+
+struct cht_wc_leds {
+ struct cht_wc_led leds[CHT_WC_LED_COUNT];
+ /* Saved LED1 initial register values */
+ struct cht_wc_led_saved_regs led1_initial_regs;
+};
+
+static const struct cht_wc_led_regs cht_wc_led_regs[CHT_WC_LED_COUNT] = {
+ {
+ .ctrl = CHT_WC_LED1_CTRL,
+ .fsm = CHT_WC_LED1_FSM,
+ .pwm = CHT_WC_LED1_PWM,
+ .on_off_mask = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
+ .on_val = CHT_WC_LED1_SWCTL | CHT_WC_LED1_ON,
+ .off_val = CHT_WC_LED1_SWCTL,
+ },
+ {
+ .ctrl = CHT_WC_LED2_CTRL,
+ .fsm = CHT_WC_LED2_FSM,
+ .pwm = CHT_WC_LED2_PWM,
+ .on_off_mask = CHT_WC_LED2_ON,
+ .on_val = CHT_WC_LED2_ON,
+ .off_val = 0,
+ },
+};
+
+static const char * const cht_wc_leds_names[CHT_WC_LED_COUNT] = {
+ "platform::" LED_FUNCTION_CHARGING,
+ "platform::" LED_FUNCTION_INDICATOR,
+};
+
+static int cht_wc_leds_brightness_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
+ int ret;
+
+ mutex_lock(&led->mutex);
+
+ if (!value) {
+ ret = regmap_update_bits(led->regmap, led->regs->ctrl,
+ led->regs->on_off_mask, led->regs->off_val);
+ if (ret < 0) {
+ dev_err(cdev->dev, "Failed to turn off: %d\n", ret);
+ goto out;
+ }
+
+ /* Disable HW blinking */
+ ret = regmap_update_bits(led->regmap, led->regs->fsm,
+ CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
+ if (ret < 0)
+ dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
+ } else {
+ ret = regmap_write(led->regmap, led->regs->pwm, value);
+ if (ret < 0) {
+ dev_err(cdev->dev, "Failed to set brightness: %d\n", ret);
+ goto out;
+ }
+
+ ret = regmap_update_bits(led->regmap, led->regs->ctrl,
+ led->regs->on_off_mask, led->regs->on_val);
+ if (ret < 0)
+ dev_err(cdev->dev, "Failed to turn on: %d\n", ret);
+ }
+out:
+ mutex_unlock(&led->mutex);
+ return ret;
+}
+
+static enum led_brightness cht_wc_leds_brightness_get(struct led_classdev *cdev)
+{
+ struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
+ unsigned int val;
+ int ret;
+
+ mutex_lock(&led->mutex);
+
+ ret = regmap_read(led->regmap, led->regs->ctrl, &val);
+ if (ret < 0) {
+ dev_err(cdev->dev, "Failed to read LED CTRL reg: %d\n", ret);
+ ret = 0;
+ goto done;
+ }
+
+ val &= led->regs->on_off_mask;
+ if (val != led->regs->on_val) {
+ ret = 0;
+ goto done;
+ }
+
+ ret = regmap_read(led->regmap, led->regs->pwm, &val);
+ if (ret < 0) {
+ dev_err(cdev->dev, "Failed to read LED PWM reg: %d\n", ret);
+ ret = 0;
+ goto done;
+ }
+
+ ret = val;
+done:
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+/* Return blinking period for given CTRL reg value */
+static unsigned long cht_wc_leds_get_period(int ctrl)
+{
+ ctrl &= CHT_WC_LED_F_MASK;
+
+ switch (ctrl) {
+ case CHT_WC_LED_F_1_4_HZ:
+ return 1000 * 4;
+ case CHT_WC_LED_F_1_2_HZ:
+ return 1000 * 2;
+ case CHT_WC_LED_F_1_HZ:
+ return 1000;
+ case CHT_WC_LED_F_2_HZ:
+ return 1000 / 2;
+ }
+
+ return 0;
+}
+
+/*
+ * Find suitable hardware blink mode for given period.
+ * period < 750 ms - select 2 HZ
+ * 750 ms <= period < 1500 ms - select 1 HZ
+ * 1500 ms <= period < 3000 ms - select 1/2 HZ
+ * 3000 ms <= period < 5000 ms - select 1/4 HZ
+ * 5000 ms <= period - return -1
+ */
+static int cht_wc_leds_find_freq(unsigned long period)
+{
+ if (period < 750)
+ return CHT_WC_LED_F_2_HZ;
+ else if (period < 1500)
+ return CHT_WC_LED_F_1_HZ;
+ else if (period < 3000)
+ return CHT_WC_LED_F_1_2_HZ;
+ else if (period < 5000)
+ return CHT_WC_LED_F_1_4_HZ;
+ else
+ return -1;
+}
+
+static int cht_wc_leds_set_effect(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off,
+ u8 effect)
+{
+ struct cht_wc_led *led = container_of(cdev, struct cht_wc_led, cdev);
+ int ctrl, ret;
+
+ mutex_lock(&led->mutex);
+
+ /* Blink with 1 Hz as default if nothing specified */
+ if (!*delay_on && !*delay_off)
+ *delay_on = *delay_off = 500;
+
+ ctrl = cht_wc_leds_find_freq(*delay_on + *delay_off);
+ if (ctrl < 0) {
+ /* Disable HW blinking */
+ ret = regmap_update_bits(led->regmap, led->regs->fsm,
+ CHT_WC_LED_EFF_MASK, CHT_WC_LED_EFF_ON);
+ if (ret < 0)
+ dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
+
+ /* Fallback to software timer */
+ *delay_on = *delay_off = 0;
+ ret = -EINVAL;
+ goto done;
+ }
+
+ ret = regmap_update_bits(led->regmap, led->regs->fsm,
+ CHT_WC_LED_EFF_MASK, effect);
+ if (ret < 0)
+ dev_err(cdev->dev, "Failed to update LED FSM reg: %d\n", ret);
+
+ /* Set the frequency and make sure the LED is on */
+ ret = regmap_update_bits(led->regmap, led->regs->ctrl,
+ CHT_WC_LED_F_MASK | led->regs->on_off_mask,
+ ctrl | led->regs->on_val);
+ if (ret < 0)
+ dev_err(cdev->dev, "Failed to update LED CTRL reg: %d\n", ret);
+
+ *delay_off = *delay_on = cht_wc_leds_get_period(ctrl) / 2;
+
+done:
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+static int cht_wc_leds_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ u8 effect = CHT_WC_LED_EFF_BLINKING;
+
+ /*
+ * The desired default behavior of LED1 / the charge LED is breathing
+ * while charging and on/solid when full. Since triggers cannot select
+ * breathing, blink_set() gets called when charging. Use slow breathing
+ * when the default "charging-blink-full-solid" trigger is used to
+ * achieve the desired default behavior.
+ */
+ if (cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
+ *delay_on = *delay_off = 1000;
+ effect = CHT_WC_LED_EFF_BREATHING;
+ }
+
+ return cht_wc_leds_set_effect(cdev, delay_on, delay_off, effect);
+}
+
+static int cht_wc_leds_pattern_set(struct led_classdev *cdev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ unsigned long delay_off, delay_on;
+
+ if (repeat > 0 || len != 2 ||
+ pattern[0].brightness != 0 || pattern[1].brightness != 1 ||
+ pattern[0].delta_t != pattern[1].delta_t ||
+ (pattern[0].delta_t != 250 && pattern[0].delta_t != 500 &&
+ pattern[0].delta_t != 1000 && pattern[0].delta_t != 2000))
+ return -EINVAL;
+
+ delay_off = pattern[0].delta_t;
+ delay_on = pattern[1].delta_t;
+
+ return cht_wc_leds_set_effect(cdev, &delay_on, &delay_off, CHT_WC_LED_EFF_BREATHING);
+}
+
+static int cht_wc_leds_pattern_clear(struct led_classdev *cdev)
+{
+ return cht_wc_leds_brightness_set(cdev, 0);
+}
+
+static int cht_wc_led_save_regs(struct cht_wc_led *led,
+ struct cht_wc_led_saved_regs *saved_regs)
+{
+ int ret;
+
+ ret = regmap_read(led->regmap, led->regs->ctrl, &saved_regs->ctrl);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(led->regmap, led->regs->fsm, &saved_regs->fsm);
+ if (ret < 0)
+ return ret;
+
+ return regmap_read(led->regmap, led->regs->pwm, &saved_regs->pwm);
+}
+
+static void cht_wc_led_restore_regs(struct cht_wc_led *led,
+ const struct cht_wc_led_saved_regs *saved_regs)
+{
+ regmap_write(led->regmap, led->regs->ctrl, saved_regs->ctrl);
+ regmap_write(led->regmap, led->regs->fsm, saved_regs->fsm);
+ regmap_write(led->regmap, led->regs->pwm, saved_regs->pwm);
+}
+
+static int cht_wc_leds_probe(struct platform_device *pdev)
+{
+ struct intel_soc_pmic *pmic = dev_get_drvdata(pdev->dev.parent);
+ struct cht_wc_leds *leds;
+ int ret;
+ int i;
+
+ /*
+ * On the Lenovo Yoga Tab 3 the LED1 driver output is actually
+ * connected to a haptic feedback motor rather then a LED.
+ * So do not register a LED classdev there (LED2 is unused).
+ */
+ if (pmic->cht_wc_model == INTEL_CHT_WC_LENOVO_YT3_X90)
+ return -ENODEV;
+
+ leds = devm_kzalloc(&pdev->dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ /*
+ * LED1 might be in hw-controlled mode when this driver gets loaded; and
+ * since the PMIC is always powered by the battery any changes made are
+ * permanent. Save LED1 regs to restore them on remove() or shutdown().
+ */
+ leds->leds[0].regs = &cht_wc_led_regs[0];
+ leds->leds[0].regmap = pmic->regmap;
+ ret = cht_wc_led_save_regs(&leds->leds[0], &leds->led1_initial_regs);
+ if (ret < 0)
+ return ret;
+
+ /* Set LED1 default trigger based on machine model */
+ switch (pmic->cht_wc_model) {
+ case INTEL_CHT_WC_GPD_WIN_POCKET:
+ leds->leds[0].cdev.default_trigger = "max170xx_battery-charging-blink-full-solid";
+ break;
+ case INTEL_CHT_WC_XIAOMI_MIPAD2:
+ leds->leds[0].cdev.default_trigger = "bq27520-0-charging-blink-full-solid";
+ break;
+ case INTEL_CHT_WC_LENOVO_YOGABOOK1:
+ leds->leds[0].cdev.default_trigger = "bq27542-0-charging-blink-full-solid";
+ break;
+ default:
+ dev_warn(&pdev->dev, "Unknown model, no default charging trigger\n");
+ break;
+ }
+
+ for (i = 0; i < CHT_WC_LED_COUNT; i++) {
+ struct cht_wc_led *led = &leds->leds[i];
+
+ led->regs = &cht_wc_led_regs[i];
+ led->regmap = pmic->regmap;
+ mutex_init(&led->mutex);
+ led->cdev.name = cht_wc_leds_names[i];
+ led->cdev.brightness_set_blocking = cht_wc_leds_brightness_set;
+ led->cdev.brightness_get = cht_wc_leds_brightness_get;
+ led->cdev.blink_set = cht_wc_leds_blink_set;
+ led->cdev.pattern_set = cht_wc_leds_pattern_set;
+ led->cdev.pattern_clear = cht_wc_leds_pattern_clear;
+ led->cdev.max_brightness = 255;
+
+ ret = devm_led_classdev_register(&pdev->dev, &led->cdev);
+ if (ret < 0)
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, leds);
+ return 0;
+}
+
+static void cht_wc_leds_remove(struct platform_device *pdev)
+{
+ struct cht_wc_leds *leds = platform_get_drvdata(pdev);
+
+ /* Restore LED1 regs if hw-control was active else leave LED1 off */
+ if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
+ cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
+}
+
+static void cht_wc_leds_disable(struct platform_device *pdev)
+{
+ struct cht_wc_leds *leds = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < CHT_WC_LED_COUNT; i++)
+ cht_wc_leds_brightness_set(&leds->leds[i].cdev, 0);
+
+ /* Restore LED1 regs if hw-control was active else leave LED1 off */
+ if (!(leds->led1_initial_regs.ctrl & CHT_WC_LED1_SWCTL))
+ cht_wc_led_restore_regs(&leds->leds[0], &leds->led1_initial_regs);
+}
+
+/* On suspend save current settings and turn LEDs off */
+static int cht_wc_leds_suspend(struct device *dev)
+{
+ struct cht_wc_leds *leds = dev_get_drvdata(dev);
+ int i, ret;
+
+ for (i = 0; i < CHT_WC_LED_COUNT; i++) {
+ ret = cht_wc_led_save_regs(&leds->leds[i], &leds->leds[i].saved_regs);
+ if (ret < 0)
+ return ret;
+ }
+
+ cht_wc_leds_disable(to_platform_device(dev));
+ return 0;
+}
+
+/* On resume restore the saved settings */
+static int cht_wc_leds_resume(struct device *dev)
+{
+ struct cht_wc_leds *leds = dev_get_drvdata(dev);
+ int i;
+
+ for (i = 0; i < CHT_WC_LED_COUNT; i++)
+ cht_wc_led_restore_regs(&leds->leds[i], &leds->leds[i].saved_regs);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(cht_wc_leds_pm, cht_wc_leds_suspend, cht_wc_leds_resume);
+
+static struct platform_driver cht_wc_leds_driver = {
+ .probe = cht_wc_leds_probe,
+ .remove = cht_wc_leds_remove,
+ .shutdown = cht_wc_leds_disable,
+ .driver = {
+ .name = "cht_wcove_leds",
+ .pm = pm_sleep_ptr(&cht_wc_leds_pm),
+ },
+};
+module_platform_driver(cht_wc_leds_driver);
+
+MODULE_ALIAS("platform:cht_wcove_leds");
+MODULE_DESCRIPTION("Intel Cherry Trail Whiskey Cove PMIC LEDs driver");
+MODULE_AUTHOR("Yauhen Kharuzhy <jekhor@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c
index 6a8405df76a3..f00b16ac1586 100644
--- a/drivers/leds/leds-clevo-mail.c
+++ b/drivers/leds/leds-clevo-mail.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
@@ -19,7 +20,7 @@ MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>");
MODULE_DESCRIPTION("Clevo mail LED driver");
MODULE_LICENSE("GPL");
-static bool __initdata nodetect;
+static bool nodetect;
module_param_named(nodetect, nodetect, bool, 0);
MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection");
@@ -40,7 +41,7 @@ static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id)
* detected as working, but in reality it is not) as low as
* possible.
*/
-static struct dmi_system_id __initdata clevo_mail_led_dmi_table[] = {
+static const struct dmi_system_id clevo_mail_led_dmi_table[] __initconst = {
{
.callback = clevo_mail_led_dmi_callback,
.ident = "Clevo D410J",
@@ -153,23 +154,20 @@ static struct led_classdev clevo_mail_led = {
.flags = LED_CORE_SUSPENDRESUME,
};
-static int clevo_mail_led_probe(struct platform_device *pdev)
+static int __init clevo_mail_led_probe(struct platform_device *pdev)
{
return led_classdev_register(&pdev->dev, &clevo_mail_led);
}
-static int clevo_mail_led_remove(struct platform_device *pdev)
+static void clevo_mail_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&clevo_mail_led);
- return 0;
}
static struct platform_driver clevo_mail_led_driver = {
- .probe = clevo_mail_led_probe,
.remove = clevo_mail_led_remove,
.driver = {
.name = KBUILD_MODNAME,
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c
index 8abcb66db01c..ef22e1e94048 100644
--- a/drivers/leds/leds-cobalt-qube.c
+++ b/drivers/leds/leds-cobalt-qube.c
@@ -1,9 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2006 - Florian Fainelli <florian@openwrt.org>
*
* Control the Cobalt Qube/RaQ front LED
*/
-#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
@@ -37,7 +37,6 @@ static struct led_classdev qube_front_led = {
static int cobalt_qube_led_probe(struct platform_device *pdev)
{
struct resource *res;
- int retval;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
@@ -50,34 +49,13 @@ static int cobalt_qube_led_probe(struct platform_device *pdev)
led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT;
writeb(led_value, led_port);
- retval = led_classdev_register(&pdev->dev, &qube_front_led);
- if (retval)
- goto err_null;
-
- return 0;
-
-err_null:
- led_port = NULL;
-
- return retval;
-}
-
-static int cobalt_qube_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&qube_front_led);
-
- if (led_port)
- led_port = NULL;
-
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &qube_front_led);
}
static struct platform_driver cobalt_qube_led_driver = {
.probe = cobalt_qube_led_probe,
- .remove = cobalt_qube_led_remove,
.driver = {
.name = "cobalt-qube-leds",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c
index 001088b31373..045c239c7328 100644
--- a/drivers/leds/leds-cobalt-raq.c
+++ b/drivers/leds/leds-cobalt-raq.c
@@ -1,21 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LEDs driver for the Cobalt Raq series.
*
* Copyright (C) 2007 Yoichi Yuasa <yuasa@linux-mips.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <linux/init.h>
#include <linux/io.h>
@@ -108,29 +95,11 @@ err_null:
return retval;
}
-static int cobalt_raq_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&raq_power_off_led);
- led_classdev_unregister(&raq_web_led);
-
- if (led_port)
- led_port = NULL;
-
- return 0;
-}
-
static struct platform_driver cobalt_raq_led_driver = {
.probe = cobalt_raq_led_probe,
- .remove = cobalt_raq_led_remove,
.driver = {
.name = "cobalt-raq-leds",
- .owner = THIS_MODULE,
},
};
-static int __init cobalt_raq_led_init(void)
-{
- return platform_driver_register(&cobalt_raq_led_driver);
-}
-
-module_init(cobalt_raq_led_init);
+builtin_platform_driver(cobalt_raq_led_driver);
diff --git a/drivers/leds/leds-cpcap.c b/drivers/leds/leds-cpcap.c
new file mode 100644
index 000000000000..87354f17644b
--- /dev/null
+++ b/drivers/leds/leds-cpcap.c
@@ -0,0 +1,226 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2017 Sebastian Reichel <sre@kernel.org>
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/motorola-cpcap.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define CPCAP_LED_NO_CURRENT 0x0001
+
+struct cpcap_led_info {
+ u16 reg;
+ u16 mask;
+ u16 limit;
+ u16 init_mask;
+ u16 init_val;
+};
+
+static const struct cpcap_led_info cpcap_led_red = {
+ .reg = CPCAP_REG_REDC,
+ .mask = 0x03FF,
+ .limit = 31,
+};
+
+static const struct cpcap_led_info cpcap_led_green = {
+ .reg = CPCAP_REG_GREENC,
+ .mask = 0x03FF,
+ .limit = 31,
+};
+
+static const struct cpcap_led_info cpcap_led_blue = {
+ .reg = CPCAP_REG_BLUEC,
+ .mask = 0x03FF,
+ .limit = 31,
+};
+
+/* aux display light */
+static const struct cpcap_led_info cpcap_led_adl = {
+ .reg = CPCAP_REG_ADLC,
+ .mask = 0x000F,
+ .limit = 1,
+ .init_mask = 0x7FFF,
+ .init_val = 0x5FF0,
+};
+
+/* camera privacy led */
+static const struct cpcap_led_info cpcap_led_cp = {
+ .reg = CPCAP_REG_CLEDC,
+ .mask = 0x0007,
+ .limit = 1,
+ .init_mask = 0x03FF,
+ .init_val = 0x0008,
+};
+
+struct cpcap_led {
+ struct led_classdev led;
+ const struct cpcap_led_info *info;
+ struct device *dev;
+ struct regmap *regmap;
+ struct mutex update_lock;
+ struct regulator *vdd;
+ bool powered;
+
+ u32 current_limit;
+};
+
+static u16 cpcap_led_val(u8 current_limit, u8 duty_cycle)
+{
+ current_limit &= 0x1f; /* 5 bit */
+ duty_cycle &= 0x0f; /* 4 bit */
+
+ return current_limit << 4 | duty_cycle;
+}
+
+static int cpcap_led_set_power(struct cpcap_led *led, bool status)
+{
+ int err;
+
+ if (status == led->powered)
+ return 0;
+
+ if (status)
+ err = regulator_enable(led->vdd);
+ else
+ err = regulator_disable(led->vdd);
+
+ if (err) {
+ dev_err(led->dev, "regulator failure: %d", err);
+ return err;
+ }
+
+ led->powered = status;
+
+ return 0;
+}
+
+static int cpcap_led_set(struct led_classdev *ledc, enum led_brightness value)
+{
+ struct cpcap_led *led = container_of(ledc, struct cpcap_led, led);
+ int brightness;
+ int err;
+
+ mutex_lock(&led->update_lock);
+
+ if (value > LED_OFF) {
+ err = cpcap_led_set_power(led, true);
+ if (err)
+ goto exit;
+ }
+
+ if (value == LED_OFF) {
+ /* Avoid HW issue by turning off current before duty cycle */
+ err = regmap_update_bits(led->regmap,
+ led->info->reg, led->info->mask, CPCAP_LED_NO_CURRENT);
+ if (err) {
+ dev_err(led->dev, "regmap failed: %d", err);
+ goto exit;
+ }
+
+ brightness = cpcap_led_val(value, LED_OFF);
+ } else {
+ brightness = cpcap_led_val(value, LED_ON);
+ }
+
+ err = regmap_update_bits(led->regmap, led->info->reg, led->info->mask,
+ brightness);
+ if (err) {
+ dev_err(led->dev, "regmap failed: %d", err);
+ goto exit;
+ }
+
+ if (value == LED_OFF) {
+ err = cpcap_led_set_power(led, false);
+ if (err)
+ goto exit;
+ }
+
+exit:
+ mutex_unlock(&led->update_lock);
+ return err;
+}
+
+static const struct of_device_id cpcap_led_of_match[] = {
+ { .compatible = "motorola,cpcap-led-red", .data = &cpcap_led_red },
+ { .compatible = "motorola,cpcap-led-green", .data = &cpcap_led_green },
+ { .compatible = "motorola,cpcap-led-blue", .data = &cpcap_led_blue },
+ { .compatible = "motorola,cpcap-led-adl", .data = &cpcap_led_adl },
+ { .compatible = "motorola,cpcap-led-cp", .data = &cpcap_led_cp },
+ {},
+};
+MODULE_DEVICE_TABLE(of, cpcap_led_of_match);
+
+static int cpcap_led_probe(struct platform_device *pdev)
+{
+ struct cpcap_led *led;
+ int err;
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+ platform_set_drvdata(pdev, led);
+ led->info = device_get_match_data(&pdev->dev);
+ led->dev = &pdev->dev;
+
+ if (led->info->reg == 0x0000) {
+ dev_err(led->dev, "Unsupported LED");
+ return -ENODEV;
+ }
+
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap)
+ return -ENODEV;
+
+ led->vdd = devm_regulator_get(&pdev->dev, "vdd");
+ if (IS_ERR(led->vdd)) {
+ err = PTR_ERR(led->vdd);
+ dev_err(led->dev, "Couldn't get regulator: %d", err);
+ return err;
+ }
+
+ err = device_property_read_string(&pdev->dev, "label", &led->led.name);
+ if (err) {
+ dev_err(led->dev, "Couldn't read LED label: %d", err);
+ return err;
+ }
+
+ if (led->info->init_mask) {
+ err = regmap_update_bits(led->regmap, led->info->reg,
+ led->info->init_mask, led->info->init_val);
+ if (err) {
+ dev_err(led->dev, "regmap failed: %d", err);
+ return err;
+ }
+ }
+
+ mutex_init(&led->update_lock);
+
+ led->led.max_brightness = led->info->limit;
+ led->led.brightness_set_blocking = cpcap_led_set;
+ err = devm_led_classdev_register(&pdev->dev, &led->led);
+ if (err) {
+ dev_err(led->dev, "Couldn't register LED: %d", err);
+ return err;
+ }
+
+ return 0;
+}
+
+static struct platform_driver cpcap_led_driver = {
+ .probe = cpcap_led_probe,
+ .driver = {
+ .name = "cpcap-led",
+ .of_match_table = cpcap_led_of_match,
+ },
+};
+module_platform_driver(cpcap_led_driver);
+
+MODULE_DESCRIPTION("CPCAP LED driver");
+MODULE_AUTHOR("Sebastian Reichel <sre@kernel.org>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-cr0014114.c b/drivers/leds/leds-cr0014114.c
new file mode 100644
index 000000000000..7e51c374edd4
--- /dev/null
+++ b/drivers/leds/leds-cr0014114.c
@@ -0,0 +1,296 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2018 Crane Merchandising Systems. All rights reserved.
+// Copyright (C) 2018 Oleh Kravchenko <oleg@kaa.org.ua>
+
+#include <linux/delay.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/spi/spi.h>
+#include <linux/workqueue.h>
+
+/*
+ * CR0014114 SPI protocol descrtiption:
+ * +----+-----------------------------------+----+
+ * | CMD| BRIGHTNESS |CRC |
+ * +----+-----------------------------------+----+
+ * | | LED0| LED1| LED2| LED3| LED4| LED5| |
+ * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | |R|G|B|R|G|B|R|G|B|R|G|B|R|G|B|R|G|B| |
+ * | 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ 1 |
+ * | |1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1|1| |
+ * | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ |
+ * | | 18 | |
+ * +----+-----------------------------------+----+
+ * | 20 |
+ * +---------------------------------------------+
+ *
+ * PS: Boards can be connected to the chain:
+ * SPI -> board0 -> board1 -> board2 ..
+ */
+
+/* CR0014114 SPI commands */
+#define CR_SET_BRIGHTNESS 0x80
+#define CR_INIT_REENUMERATE 0x81
+#define CR_NEXT_REENUMERATE 0x82
+
+/* CR0014114 default settings */
+#define CR_MAX_BRIGHTNESS GENMASK(6, 0)
+#define CR_FW_DELAY_MSEC 10
+#define CR_RECOUNT_DELAY (HZ * 3600)
+
+#define CR_DEV_NAME "cr0014114"
+
+struct cr0014114_led {
+ struct cr0014114 *priv;
+ struct led_classdev ldev;
+ u8 brightness;
+};
+
+struct cr0014114 {
+ bool do_recount;
+ size_t count;
+ struct delayed_work work;
+ struct device *dev;
+ struct mutex lock;
+ struct spi_device *spi;
+ u8 *buf;
+ unsigned long delay;
+ struct cr0014114_led leds[] __counted_by(count);
+};
+
+static void cr0014114_calc_crc(u8 *buf, const size_t len)
+{
+ size_t i;
+ u8 crc;
+
+ for (i = 1, crc = 1; i < len - 1; i++)
+ crc += buf[i];
+ crc |= BIT(7);
+
+ /* special case when CRC matches the SPI commands */
+ if (crc == CR_SET_BRIGHTNESS ||
+ crc == CR_INIT_REENUMERATE ||
+ crc == CR_NEXT_REENUMERATE)
+ crc = 0xfe;
+
+ buf[len - 1] = crc;
+}
+
+static int cr0014114_recount(struct cr0014114 *priv)
+{
+ int ret;
+ size_t i;
+ u8 cmd;
+
+ dev_dbg(priv->dev, "LEDs recount is started\n");
+
+ cmd = CR_INIT_REENUMERATE;
+ ret = spi_write(priv->spi, &cmd, sizeof(cmd));
+ if (ret)
+ goto err;
+
+ cmd = CR_NEXT_REENUMERATE;
+ for (i = 0; i < priv->count; i++) {
+ msleep(CR_FW_DELAY_MSEC);
+
+ ret = spi_write(priv->spi, &cmd, sizeof(cmd));
+ if (ret)
+ goto err;
+ }
+
+err:
+ dev_dbg(priv->dev, "LEDs recount is finished\n");
+
+ if (ret)
+ dev_err(priv->dev, "with error %d", ret);
+
+ return ret;
+}
+
+static int cr0014114_sync(struct cr0014114 *priv)
+{
+ int ret;
+ size_t i;
+ unsigned long udelay, now = jiffies;
+
+ /* to avoid SPI mistiming with firmware we should wait some time */
+ if (time_after(priv->delay, now)) {
+ udelay = jiffies_to_usecs(priv->delay - now);
+ usleep_range(udelay, udelay + 1);
+ }
+
+ if (unlikely(priv->do_recount)) {
+ ret = cr0014114_recount(priv);
+ if (ret)
+ goto err;
+
+ priv->do_recount = false;
+ msleep(CR_FW_DELAY_MSEC);
+ }
+
+ priv->buf[0] = CR_SET_BRIGHTNESS;
+ for (i = 0; i < priv->count; i++)
+ priv->buf[i + 1] = priv->leds[i].brightness;
+ cr0014114_calc_crc(priv->buf, priv->count + 2);
+ ret = spi_write(priv->spi, priv->buf, priv->count + 2);
+
+err:
+ priv->delay = jiffies + msecs_to_jiffies(CR_FW_DELAY_MSEC);
+
+ return ret;
+}
+
+static void cr0014114_recount_work(struct work_struct *work)
+{
+ int ret;
+ struct cr0014114 *priv = container_of(work,
+ struct cr0014114,
+ work.work);
+
+ mutex_lock(&priv->lock);
+ priv->do_recount = true;
+ ret = cr0014114_sync(priv);
+ mutex_unlock(&priv->lock);
+
+ if (ret)
+ dev_warn(priv->dev, "sync of LEDs failed %d\n", ret);
+
+ schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY);
+}
+
+static int cr0014114_set_sync(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ int ret;
+ struct cr0014114_led *led = container_of(ldev,
+ struct cr0014114_led,
+ ldev);
+
+ dev_dbg(led->priv->dev, "Set brightness to %d\n", brightness);
+
+ mutex_lock(&led->priv->lock);
+ led->brightness = (u8)brightness;
+ ret = cr0014114_sync(led->priv);
+ mutex_unlock(&led->priv->lock);
+
+ return ret;
+}
+
+static int cr0014114_probe_dt(struct cr0014114 *priv)
+{
+ size_t i = 0;
+ struct cr0014114_led *led;
+ struct led_init_data init_data = {};
+ int ret;
+
+ device_for_each_child_node_scoped(priv->dev, child) {
+ led = &priv->leds[i];
+
+ led->priv = priv;
+ led->ldev.max_brightness = CR_MAX_BRIGHTNESS;
+ led->ldev.brightness_set_blocking = cr0014114_set_sync;
+
+ init_data.fwnode = child;
+ init_data.devicename = CR_DEV_NAME;
+ init_data.default_label = ":";
+
+ ret = devm_led_classdev_register_ext(priv->dev, &led->ldev,
+ &init_data);
+ if (ret) {
+ dev_err(priv->dev,
+ "failed to register LED device, err %d", ret);
+ return ret;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int cr0014114_probe(struct spi_device *spi)
+{
+ struct cr0014114 *priv;
+ size_t count;
+ int ret;
+
+ count = device_get_child_node_count(&spi->dev);
+ if (!count) {
+ dev_err(&spi->dev, "LEDs are not defined in device tree!");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->buf = devm_kzalloc(&spi->dev, count + 2, GFP_KERNEL);
+ if (!priv->buf)
+ return -ENOMEM;
+
+ mutex_init(&priv->lock);
+ INIT_DELAYED_WORK(&priv->work, cr0014114_recount_work);
+ priv->count = count;
+ priv->dev = &spi->dev;
+ priv->spi = spi;
+ priv->delay = jiffies -
+ msecs_to_jiffies(CR_FW_DELAY_MSEC);
+
+ priv->do_recount = true;
+ ret = cr0014114_sync(priv);
+ if (ret) {
+ dev_err(priv->dev, "first recount failed %d\n", ret);
+ return ret;
+ }
+
+ priv->do_recount = true;
+ ret = cr0014114_sync(priv);
+ if (ret) {
+ dev_err(priv->dev, "second recount failed %d\n", ret);
+ return ret;
+ }
+
+ ret = cr0014114_probe_dt(priv);
+ if (ret)
+ return ret;
+
+ /* setup recount work to workaround buggy firmware */
+ schedule_delayed_work(&priv->work, CR_RECOUNT_DELAY);
+
+ spi_set_drvdata(spi, priv);
+
+ return 0;
+}
+
+static void cr0014114_remove(struct spi_device *spi)
+{
+ struct cr0014114 *priv = spi_get_drvdata(spi);
+
+ cancel_delayed_work_sync(&priv->work);
+ mutex_destroy(&priv->lock);
+}
+
+static const struct of_device_id cr0014114_dt_ids[] = {
+ { .compatible = "crane,cr0014114", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, cr0014114_dt_ids);
+
+static struct spi_driver cr0014114_driver = {
+ .probe = cr0014114_probe,
+ .remove = cr0014114_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = cr0014114_dt_ids,
+ },
+};
+
+module_spi_driver(cr0014114_driver);
+
+MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>");
+MODULE_DESCRIPTION("cr0014114 LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:cr0014114");
diff --git a/drivers/leds/leds-cros_ec.c b/drivers/leds/leds-cros_ec.c
new file mode 100644
index 000000000000..bea3cc3fbfd2
--- /dev/null
+++ b/drivers/leds/leds-cros_ec.c
@@ -0,0 +1,263 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * ChromeOS EC LED Driver
+ *
+ * Copyright (C) 2024 Thomas Weißschuh <linux@weissschuh.net>
+ */
+
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/cros_ec_commands.h>
+#include <linux/platform_data/cros_ec_proto.h>
+
+static const char * const cros_ec_led_functions[] = {
+ [EC_LED_ID_BATTERY_LED] = LED_FUNCTION_CHARGING,
+ [EC_LED_ID_POWER_LED] = LED_FUNCTION_POWER,
+ [EC_LED_ID_ADAPTER_LED] = "adapter",
+ [EC_LED_ID_LEFT_LED] = "left",
+ [EC_LED_ID_RIGHT_LED] = "right",
+ [EC_LED_ID_RECOVERY_HW_REINIT_LED] = "recovery-hw-reinit",
+ [EC_LED_ID_SYSRQ_DEBUG_LED] = "sysrq-debug",
+};
+
+static_assert(ARRAY_SIZE(cros_ec_led_functions) == EC_LED_ID_COUNT);
+
+static const int cros_ec_led_to_linux_id[] = {
+ [EC_LED_COLOR_RED] = LED_COLOR_ID_RED,
+ [EC_LED_COLOR_GREEN] = LED_COLOR_ID_GREEN,
+ [EC_LED_COLOR_BLUE] = LED_COLOR_ID_BLUE,
+ [EC_LED_COLOR_YELLOW] = LED_COLOR_ID_YELLOW,
+ [EC_LED_COLOR_WHITE] = LED_COLOR_ID_WHITE,
+ [EC_LED_COLOR_AMBER] = LED_COLOR_ID_AMBER,
+};
+
+static_assert(ARRAY_SIZE(cros_ec_led_to_linux_id) == EC_LED_COLOR_COUNT);
+
+static const int cros_ec_linux_to_ec_id[] = {
+ [LED_COLOR_ID_RED] = EC_LED_COLOR_RED,
+ [LED_COLOR_ID_GREEN] = EC_LED_COLOR_GREEN,
+ [LED_COLOR_ID_BLUE] = EC_LED_COLOR_BLUE,
+ [LED_COLOR_ID_YELLOW] = EC_LED_COLOR_YELLOW,
+ [LED_COLOR_ID_WHITE] = EC_LED_COLOR_WHITE,
+ [LED_COLOR_ID_AMBER] = EC_LED_COLOR_AMBER,
+};
+
+struct cros_ec_led_priv {
+ struct led_classdev_mc led_mc_cdev;
+ struct cros_ec_device *cros_ec;
+ enum ec_led_id led_id;
+};
+
+static inline struct cros_ec_led_priv *cros_ec_led_cdev_to_priv(struct led_classdev *led_cdev)
+{
+ return container_of(lcdev_to_mccdev(led_cdev), struct cros_ec_led_priv, led_mc_cdev);
+}
+
+union cros_ec_led_cmd_data {
+ struct ec_params_led_control req;
+ struct ec_response_led_control resp;
+};
+
+static int cros_ec_led_send_cmd(struct cros_ec_device *cros_ec,
+ union cros_ec_led_cmd_data *arg)
+{
+ int ret;
+
+ ret = cros_ec_cmd(cros_ec, 1, EC_CMD_LED_CONTROL, &arg->req,
+ sizeof(arg->req), &arg->resp, sizeof(arg->resp));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int cros_ec_led_trigger_activate(struct led_classdev *led_cdev)
+{
+ struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev);
+ union cros_ec_led_cmd_data arg = {};
+
+ arg.req.led_id = priv->led_id;
+ arg.req.flags = EC_LED_FLAGS_AUTO;
+
+ return cros_ec_led_send_cmd(priv->cros_ec, &arg);
+}
+
+static struct led_hw_trigger_type cros_ec_led_trigger_type;
+
+static struct led_trigger cros_ec_led_trigger = {
+ .name = "chromeos-auto",
+ .trigger_type = &cros_ec_led_trigger_type,
+ .activate = cros_ec_led_trigger_activate,
+};
+
+static int cros_ec_led_brightness_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct cros_ec_led_priv *priv = cros_ec_led_cdev_to_priv(led_cdev);
+ union cros_ec_led_cmd_data arg = {};
+ enum ec_led_colors led_color;
+ struct mc_subled *subled;
+ size_t i;
+
+ led_mc_calc_color_components(&priv->led_mc_cdev, brightness);
+
+ arg.req.led_id = priv->led_id;
+
+ for (i = 0; i < priv->led_mc_cdev.num_colors; i++) {
+ subled = &priv->led_mc_cdev.subled_info[i];
+ led_color = cros_ec_linux_to_ec_id[subled->color_index];
+ arg.req.brightness[led_color] = subled->brightness;
+ }
+
+ return cros_ec_led_send_cmd(priv->cros_ec, &arg);
+}
+
+static int cros_ec_led_count_subleds(struct device *dev,
+ struct ec_response_led_control *resp,
+ unsigned int *max_brightness)
+{
+ unsigned int range, common_range = 0;
+ int num_subleds = 0;
+ size_t i;
+
+ for (i = 0; i < EC_LED_COLOR_COUNT; i++) {
+ range = resp->brightness_range[i];
+
+ if (!range)
+ continue;
+
+ num_subleds++;
+
+ if (!common_range)
+ common_range = range;
+
+ if (common_range != range) {
+ /* The multicolor LED API expects a uniform max_brightness */
+ dev_err(dev, "Inconsistent LED brightness values\n");
+ return -EINVAL;
+ }
+ }
+
+ *max_brightness = common_range;
+ return num_subleds;
+}
+
+static const char *cros_ec_led_get_color_name(struct led_classdev_mc *led_mc_cdev)
+{
+ int color;
+
+ if (led_mc_cdev->num_colors == 1)
+ color = led_mc_cdev->subled_info[0].color_index;
+ else
+ color = LED_COLOR_ID_MULTI;
+
+ return led_get_color_name(color);
+}
+
+static int cros_ec_led_probe_one(struct device *dev, struct cros_ec_device *cros_ec,
+ enum ec_led_id id)
+{
+ union cros_ec_led_cmd_data arg = {};
+ struct cros_ec_led_priv *priv;
+ struct led_classdev *led_cdev;
+ struct mc_subled *subleds;
+ int i, ret, num_subleds;
+ size_t subled;
+
+ arg.req.led_id = id;
+ arg.req.flags = EC_LED_FLAGS_QUERY;
+ ret = cros_ec_led_send_cmd(cros_ec, &arg);
+ if (ret == -EINVAL)
+ return 0; /* Unknown LED, skip */
+ if (ret == -EOPNOTSUPP)
+ return -ENODEV;
+ if (ret < 0)
+ return ret;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ num_subleds = cros_ec_led_count_subleds(dev, &arg.resp,
+ &priv->led_mc_cdev.led_cdev.max_brightness);
+ if (num_subleds < 0)
+ return num_subleds;
+ if (num_subleds == 0)
+ return 0; /* LED without any colors, skip */
+
+ priv->cros_ec = cros_ec;
+ priv->led_id = id;
+
+ subleds = devm_kcalloc(dev, num_subleds, sizeof(*subleds), GFP_KERNEL);
+ if (!subleds)
+ return -ENOMEM;
+
+ subled = 0;
+ for (i = 0; i < EC_LED_COLOR_COUNT; i++) {
+ if (!arg.resp.brightness_range[i])
+ continue;
+
+ subleds[subled].color_index = cros_ec_led_to_linux_id[i];
+ if (subled == 0)
+ subleds[subled].intensity = 100;
+ subled++;
+ }
+
+ priv->led_mc_cdev.subled_info = subleds;
+ priv->led_mc_cdev.num_colors = num_subleds;
+
+ led_cdev = &priv->led_mc_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = cros_ec_led_brightness_set_blocking;
+ led_cdev->trigger_type = &cros_ec_led_trigger_type;
+ led_cdev->default_trigger = cros_ec_led_trigger.name;
+ led_cdev->hw_control_trigger = cros_ec_led_trigger.name;
+
+ led_cdev->name = devm_kasprintf(dev, GFP_KERNEL, "chromeos:%s:%s",
+ cros_ec_led_get_color_name(&priv->led_mc_cdev),
+ cros_ec_led_functions[id]);
+ if (!led_cdev->name)
+ return -ENOMEM;
+
+ return devm_led_classdev_multicolor_register(dev, &priv->led_mc_cdev);
+}
+
+static int cros_ec_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(dev->parent);
+ struct cros_ec_device *cros_ec = ec_dev->ec_dev;
+ int i, ret = 0;
+
+ ret = devm_led_trigger_register(dev, &cros_ec_led_trigger);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < EC_LED_ID_COUNT; i++) {
+ ret = cros_ec_led_probe_one(dev, cros_ec, i);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static const struct platform_device_id cros_ec_led_id[] = {
+ { "cros-ec-led", 0 },
+ {}
+};
+
+static struct platform_driver cros_ec_led_driver = {
+ .driver.name = "cros-ec-led",
+ .probe = cros_ec_led_probe,
+ .id_table = cros_ec_led_id,
+};
+module_platform_driver(cros_ec_led_driver);
+
+MODULE_DEVICE_TABLE(platform, cros_ec_led_id);
+MODULE_DESCRIPTION("ChromeOS EC LED Driver");
+MODULE_AUTHOR("Thomas Weißschuh <linux@weissschuh.net");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c
index c263a21db829..71209b3c8f1e 100644
--- a/drivers/leds/leds-da903x.c
+++ b/drivers/leds/leds-da903x.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LEDs driver for Dialog Semiconductor DA9030/DA9034
*
@@ -6,18 +7,12 @@
*
* Copyright (C) 2006-2008 Marvell International Ltd.
* Eric Miao <eric.miao@marvell.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
-#include <linux/workqueue.h>
#include <linux/mfd/da903x.h>
#include <linux/slab.h>
@@ -34,9 +29,7 @@
struct da903x_led {
struct led_classdev cdev;
- struct work_struct work;
struct device *master;
- enum led_brightness new_brightness;
int id;
int flags;
};
@@ -44,11 +37,13 @@ struct da903x_led {
#define DA9030_LED_OFFSET(id) ((id) - DA9030_ID_LED_1)
#define DA9034_LED_OFFSET(id) ((id) - DA9034_ID_LED_1)
-static void da903x_led_work(struct work_struct *work)
+static int da903x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
- struct da903x_led *led = container_of(work, struct da903x_led, work);
+ struct da903x_led *led =
+ container_of(led_cdev, struct da903x_led, cdev);
uint8_t val;
- int offset;
+ int offset, ret = -EINVAL;
switch (led->id) {
case DA9030_ID_LED_1:
@@ -58,42 +53,36 @@ static void da903x_led_work(struct work_struct *work)
case DA9030_ID_LED_PC:
offset = DA9030_LED_OFFSET(led->id);
val = led->flags & ~0x87;
- val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
- val |= (0x7 - (led->new_brightness >> 5)) & 0x7; /* PWM<2:0> */
- da903x_write(led->master, DA9030_LED1_CONTROL + offset, val);
+ val |= value ? 0x80 : 0; /* EN bit */
+ val |= (0x7 - (value >> 5)) & 0x7; /* PWM<2:0> */
+ ret = da903x_write(led->master, DA9030_LED1_CONTROL + offset,
+ val);
break;
case DA9030_ID_VIBRA:
val = led->flags & ~0x80;
- val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
- da903x_write(led->master, DA9030_MISC_CONTROL_A, val);
+ val |= value ? 0x80 : 0; /* EN bit */
+ ret = da903x_write(led->master, DA9030_MISC_CONTROL_A, val);
break;
case DA9034_ID_LED_1:
case DA9034_ID_LED_2:
offset = DA9034_LED_OFFSET(led->id);
- val = (led->new_brightness * 0x5f / LED_FULL) & 0x7f;
+ val = (value * 0x5f / LED_FULL) & 0x7f;
val |= (led->flags & DA9034_LED_RAMP) ? 0x80 : 0;
- da903x_write(led->master, DA9034_LED1_CONTROL + offset, val);
+ ret = da903x_write(led->master, DA9034_LED1_CONTROL + offset,
+ val);
break;
case DA9034_ID_VIBRA:
- val = led->new_brightness & 0xfe;
- da903x_write(led->master, DA9034_VIBRA, val);
+ val = value & 0xfe;
+ ret = da903x_write(led->master, DA9034_VIBRA, val);
break;
}
-}
-static void da903x_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct da903x_led *led;
-
- led = container_of(led_cdev, struct da903x_led, cdev);
- led->new_brightness = value;
- schedule_work(&led->work);
+ return ret;
}
static int da903x_led_probe(struct platform_device *pdev)
{
- struct led_info *pdata = pdev->dev.platform_data;
+ struct led_info *pdata = dev_get_platdata(&pdev->dev);
struct da903x_led *led;
int id, ret;
@@ -109,22 +98,17 @@ static int da903x_led_probe(struct platform_device *pdev)
}
led = devm_kzalloc(&pdev->dev, sizeof(struct da903x_led), GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id);
+ if (!led)
return -ENOMEM;
- }
led->cdev.name = pdata->name;
led->cdev.default_trigger = pdata->default_trigger;
- led->cdev.brightness_set = da903x_led_set;
+ led->cdev.brightness_set_blocking = da903x_led_set;
led->cdev.brightness = LED_OFF;
led->id = id;
led->flags = pdata->flags;
led->master = pdev->dev.parent;
- led->new_brightness = LED_OFF;
-
- INIT_WORK(&led->work, da903x_led_work);
ret = led_classdev_register(led->master, &led->cdev);
if (ret) {
@@ -133,21 +117,20 @@ static int da903x_led_probe(struct platform_device *pdev)
}
platform_set_drvdata(pdev, led);
+
return 0;
}
-static int da903x_led_remove(struct platform_device *pdev)
+static void da903x_led_remove(struct platform_device *pdev)
{
struct da903x_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
- return 0;
}
static struct platform_driver da903x_led_driver = {
.driver = {
.name = "da903x-led",
- .owner = THIS_MODULE,
},
.probe = da903x_led_probe,
.remove = da903x_led_remove,
diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c
index efec43344e9f..7c9051184a59 100644
--- a/drivers/leds/leds-da9052.c
+++ b/drivers/leds/leds-da9052.c
@@ -1,23 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* LED Driver for Dialog DA9052 PMICs.
*
* Copyright(c) 2012 Dialog Semiconductor Ltd.
*
* Author: David Dajun Chen <dchen@diasemi.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
- *
*/
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
-#include <linux/workqueue.h>
#include <linux/slab.h>
#include <linux/mfd/da9052/reg.h>
@@ -33,11 +26,9 @@
struct da9052_led {
struct led_classdev cdev;
- struct work_struct work;
struct da9052 *da9052;
unsigned char led_index;
unsigned char id;
- int brightness;
};
static unsigned char led_reg[] = {
@@ -45,12 +36,13 @@ static unsigned char led_reg[] = {
DA9052_LED_CONT_5_REG,
};
-static int da9052_set_led_brightness(struct da9052_led *led)
+static int da9052_set_led_brightness(struct da9052_led *led,
+ enum led_brightness brightness)
{
u8 val;
int error;
- val = (led->brightness & 0x7f) | DA9052_LED_CONT_DIM;
+ val = (brightness & 0x7f) | DA9052_LED_CONT_DIM;
error = da9052_reg_write(led->da9052, led_reg[led->led_index], val);
if (error < 0)
@@ -59,21 +51,13 @@ static int da9052_set_led_brightness(struct da9052_led *led)
return error;
}
-static void da9052_led_work(struct work_struct *work)
-{
- struct da9052_led *led = container_of(work, struct da9052_led, work);
-
- da9052_set_led_brightness(led);
-}
-
-static void da9052_led_set(struct led_classdev *led_cdev,
+static int da9052_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- struct da9052_led *led;
+ struct da9052_led *led =
+ container_of(led_cdev, struct da9052_led, cdev);
- led = container_of(led_cdev, struct da9052_led, cdev);
- led->brightness = value;
- schedule_work(&led->work);
+ return da9052_set_led_brightness(led, value);
}
static int da9052_configure_leds(struct da9052 *da9052)
@@ -112,7 +96,7 @@ static int da9052_led_probe(struct platform_device *pdev)
int i;
da9052 = dev_get_drvdata(pdev->dev.parent);
- pdata = da9052->dev->platform_data;
+ pdata = dev_get_platdata(da9052->dev);
if (pdata == NULL) {
dev_err(&pdev->dev, "No platform data\n");
goto err;
@@ -124,24 +108,21 @@ static int da9052_led_probe(struct platform_device *pdev)
goto err;
}
- led = devm_kzalloc(&pdev->dev,
- sizeof(struct da9052_led) * pled->num_leds,
+ led = devm_kcalloc(&pdev->dev,
+ pled->num_leds, sizeof(struct da9052_led),
GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "Failed to alloc memory\n");
+ if (!led) {
error = -ENOMEM;
goto err;
}
for (i = 0; i < pled->num_leds; i++) {
led[i].cdev.name = pled->leds[i].name;
- led[i].cdev.brightness_set = da9052_led_set;
+ led[i].cdev.brightness_set_blocking = da9052_led_set;
led[i].cdev.brightness = LED_OFF;
led[i].cdev.max_brightness = DA9052_MAX_BRIGHTNESS;
- led[i].brightness = LED_OFF;
led[i].led_index = pled->leds[i].flags;
led[i].da9052 = dev_get_drvdata(pdev->dev.parent);
- INIT_WORK(&led[i].work, da9052_led_work);
error = led_classdev_register(pdev->dev.parent, &led[i].cdev);
if (error) {
@@ -150,7 +131,8 @@ static int da9052_led_probe(struct platform_device *pdev)
goto err_register;
}
- error = da9052_set_led_brightness(&led[i]);
+ error = da9052_set_led_brightness(&led[i],
+ led[i].cdev.brightness);
if (error) {
dev_err(&pdev->dev, "Unable to init led %d\n",
led[i].led_index);
@@ -168,15 +150,13 @@ static int da9052_led_probe(struct platform_device *pdev)
return 0;
err_register:
- for (i = i - 1; i >= 0; i--) {
+ for (i = i - 1; i >= 0; i--)
led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
- }
err:
return error;
}
-static int da9052_led_remove(struct platform_device *pdev)
+static void da9052_led_remove(struct platform_device *pdev)
{
struct da9052_led *led = platform_get_drvdata(pdev);
struct da9052_pdata *pdata;
@@ -185,23 +165,18 @@ static int da9052_led_remove(struct platform_device *pdev)
int i;
da9052 = dev_get_drvdata(pdev->dev.parent);
- pdata = da9052->dev->platform_data;
+ pdata = dev_get_platdata(da9052->dev);
pled = pdata->pled;
for (i = 0; i < pled->num_leds; i++) {
- led[i].brightness = 0;
- da9052_set_led_brightness(&led[i]);
+ da9052_set_led_brightness(&led[i], LED_OFF);
led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
}
-
- return 0;
}
static struct platform_driver da9052_led_driver = {
.driver = {
.name = "da9052-leds",
- .owner = THIS_MODULE,
},
.probe = da9052_led_probe,
.remove = da9052_led_remove,
diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
index 1f9d8e62d37e..cf5fb1195f87 100644
--- a/drivers/leds/leds-dac124s085.c
+++ b/drivers/leds/leds-dac124s085.c
@@ -1,11 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2008
* Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de>
*
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
- *
* LED driver for the DAC124S085 SPI DAC
*/
@@ -13,20 +10,15 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
-#include <linux/spinlock.h>
-#include <linux/workqueue.h>
#include <linux/spi/spi.h>
struct dac124s085_led {
struct led_classdev ldev;
struct spi_device *spi;
int id;
- int brightness;
char name[sizeof("dac124s085-3")];
struct mutex mutex;
- struct work_struct work;
- spinlock_t lock;
};
struct dac124s085 {
@@ -38,29 +30,21 @@ struct dac124s085 {
#define ALL_WRITE_UPDATE (2 << 12)
#define POWER_DOWN_OUTPUT (3 << 12)
-static void dac124s085_led_work(struct work_struct *work)
+static int dac124s085_set_brightness(struct led_classdev *ldev,
+ enum led_brightness brightness)
{
- struct dac124s085_led *led = container_of(work, struct dac124s085_led,
- work);
+ struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
+ ldev);
u16 word;
+ int ret;
mutex_lock(&led->mutex);
word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE |
- (led->brightness & 0xfff));
- spi_write(led->spi, (const u8 *)&word, sizeof(word));
+ (brightness & 0xfff));
+ ret = spi_write(led->spi, (const u8 *)&word, sizeof(word));
mutex_unlock(&led->mutex);
-}
-
-static void dac124s085_set_brightness(struct led_classdev *ldev,
- enum led_brightness brightness)
-{
- struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
- ldev);
- spin_lock(&led->lock);
- led->brightness = brightness;
- schedule_work(&led->work);
- spin_unlock(&led->lock);
+ return ret;
}
static int dac124s085_probe(struct spi_device *spi)
@@ -78,16 +62,13 @@ static int dac124s085_probe(struct spi_device *spi)
for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
led = dac->leds + i;
led->id = i;
- led->brightness = LED_OFF;
led->spi = spi;
snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
- spin_lock_init(&led->lock);
- INIT_WORK(&led->work, dac124s085_led_work);
mutex_init(&led->mutex);
led->ldev.name = led->name;
led->ldev.brightness = LED_OFF;
led->ldev.max_brightness = 0xfff;
- led->ldev.brightness_set = dac124s085_set_brightness;
+ led->ldev.brightness_set_blocking = dac124s085_set_brightness;
ret = led_classdev_register(&spi->dev, &led->ldev);
if (ret < 0)
goto eledcr;
@@ -101,23 +82,16 @@ eledcr:
while (i--)
led_classdev_unregister(&dac->leds[i].ldev);
- spi_set_drvdata(spi, NULL);
return ret;
}
-static int dac124s085_remove(struct spi_device *spi)
+static void dac124s085_remove(struct spi_device *spi)
{
struct dac124s085 *dac = spi_get_drvdata(spi);
int i;
- for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
+ for (i = 0; i < ARRAY_SIZE(dac->leds); i++)
led_classdev_unregister(&dac->leds[i].ldev);
- cancel_work_sync(&dac->leds[i].work);
- }
-
- spi_set_drvdata(spi, NULL);
-
- return 0;
}
static struct spi_driver dac124s085_driver = {
@@ -125,7 +99,6 @@ static struct spi_driver dac124s085_driver = {
.remove = dac124s085_remove,
.driver = {
.name = "dac124s085",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-el15203000.c b/drivers/leds/leds-el15203000.c
new file mode 100644
index 000000000000..e26d1654bd0d
--- /dev/null
+++ b/drivers/leds/leds-el15203000.c
@@ -0,0 +1,340 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019 Crane Merchandising Systems. All rights reserved.
+// Copyright (C) 2019 Oleh Kravchenko <oleg@kaa.org.ua>
+
+#include <linux/delay.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+
+/*
+ * EL15203000 SPI protocol description:
+ * +-----+---------+
+ * | LED | COMMAND |
+ * +-----+---------+
+ * | 1 | 1 |
+ * +-----+---------+
+ * (*) LEDs MCU board expects 20 msec delay per byte.
+ *
+ * LEDs:
+ * +----------+--------------+-------------------------------------------+
+ * | ID | NAME | DESCRIPTION |
+ * +----------+--------------+-------------------------------------------+
+ * | 'P' 0x50 | Pipe | Consists from 5 LEDs, controlled by board |
+ * +----------+--------------+-------------------------------------------+
+ * | 'S' 0x53 | Screen frame | Light tube around the screen |
+ * +----------+--------------+-------------------------------------------+
+ * | 'V' 0x56 | Vending area | Highlights a cup of coffee |
+ * +----------+--------------+-------------------------------------------+
+ *
+ * COMMAND:
+ * +----------+-----------------+--------------+--------------+
+ * | VALUES | PIPE | SCREEN FRAME | VENDING AREA |
+ * +----------+-----------------+--------------+--------------+
+ * | '0' 0x30 | Off |
+ * +----------+-----------------------------------------------+
+ * | '1' 0x31 | On |
+ * +----------+-----------------+--------------+--------------+
+ * | '2' 0x32 | Cascade | Breathing |
+ * +----------+-----------------+--------------+
+ * | '3' 0x33 | Inverse cascade |
+ * +----------+-----------------+
+ * | '4' 0x34 | Bounce |
+ * +----------+-----------------+
+ * | '5' 0x35 | Inverse bounce |
+ * +----------+-----------------+
+ */
+
+/* EL15203000 default settings */
+#define EL_FW_DELAY_USEC 20000ul
+#define EL_PATTERN_DELAY_MSEC 800u
+#define EL_PATTERN_LEN 10u
+#define EL_PATTERN_HALF_LEN (EL_PATTERN_LEN / 2)
+
+enum el15203000_command {
+ /* for all LEDs */
+ EL_OFF = '0',
+ EL_ON = '1',
+
+ /* for Screen LED */
+ EL_SCREEN_BREATHING = '2',
+
+ /* for Pipe LED */
+ EL_PIPE_CASCADE = '2',
+ EL_PIPE_INV_CASCADE = '3',
+ EL_PIPE_BOUNCE = '4',
+ EL_PIPE_INV_BOUNCE = '5',
+};
+
+struct el15203000_led {
+ struct led_classdev ldev;
+ struct el15203000 *priv;
+ u32 reg;
+};
+
+struct el15203000 {
+ struct device *dev;
+ struct mutex lock;
+ struct spi_device *spi;
+ unsigned long delay;
+ size_t count;
+ struct el15203000_led leds[] __counted_by(count);
+};
+
+#define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev)
+
+static int el15203000_cmd(struct el15203000_led *led, u8 brightness)
+{
+ int ret;
+ u8 cmd[2];
+ size_t i;
+
+ mutex_lock(&led->priv->lock);
+
+ dev_dbg(led->priv->dev, "Set brightness of 0x%02x(%c) to 0x%02x(%c)",
+ led->reg, led->reg, brightness, brightness);
+
+ /* to avoid SPI mistiming with firmware we should wait some time */
+ if (time_after(led->priv->delay, jiffies)) {
+ dev_dbg(led->priv->dev, "Wait %luus to sync",
+ EL_FW_DELAY_USEC);
+
+ usleep_range(EL_FW_DELAY_USEC,
+ EL_FW_DELAY_USEC + 1);
+ }
+
+ cmd[0] = led->reg;
+ cmd[1] = brightness;
+
+ for (i = 0; i < ARRAY_SIZE(cmd); i++) {
+ if (i)
+ usleep_range(EL_FW_DELAY_USEC,
+ EL_FW_DELAY_USEC + 1);
+
+ ret = spi_write(led->priv->spi, &cmd[i], sizeof(cmd[i]));
+ if (ret) {
+ dev_err(led->priv->dev,
+ "spi_write() error %d", ret);
+ break;
+ }
+ }
+
+ led->priv->delay = jiffies + usecs_to_jiffies(EL_FW_DELAY_USEC);
+
+ mutex_unlock(&led->priv->lock);
+
+ return ret;
+}
+
+static int el15203000_set_blocking(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ struct el15203000_led *led = to_el15203000_led(ldev);
+
+ return el15203000_cmd(led, brightness == LED_OFF ? EL_OFF : EL_ON);
+}
+
+static int el15203000_pattern_set_S(struct led_classdev *ldev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct el15203000_led *led = to_el15203000_led(ldev);
+
+ if (repeat > 0 || len != 2 ||
+ pattern[0].delta_t != 4000 || pattern[0].brightness != 0 ||
+ pattern[1].delta_t != 4000 || pattern[1].brightness != 1)
+ return -EINVAL;
+
+ dev_dbg(led->priv->dev, "Breathing mode for 0x%02x(%c)",
+ led->reg, led->reg);
+
+ return el15203000_cmd(led, EL_SCREEN_BREATHING);
+}
+
+static bool is_cascade(const struct led_pattern *pattern, u32 len,
+ bool inv, bool right)
+{
+ int val, t;
+ u32 i;
+
+ if (len != EL_PATTERN_HALF_LEN)
+ return false;
+
+ val = right ? BIT(4) : BIT(0);
+
+ for (i = 0; i < len; i++) {
+ t = inv ? ~val & GENMASK(4, 0) : val;
+
+ if (pattern[i].delta_t != EL_PATTERN_DELAY_MSEC ||
+ pattern[i].brightness != t)
+ return false;
+
+ val = right ? val >> 1 : val << 1;
+ }
+
+ return true;
+}
+
+static bool is_bounce(const struct led_pattern *pattern, u32 len, bool inv)
+{
+ if (len != EL_PATTERN_LEN)
+ return false;
+
+ return is_cascade(pattern, EL_PATTERN_HALF_LEN, inv, false) &&
+ is_cascade(pattern + EL_PATTERN_HALF_LEN,
+ EL_PATTERN_HALF_LEN, inv, true);
+}
+
+static int el15203000_pattern_set_P(struct led_classdev *ldev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct el15203000_led *led = to_el15203000_led(ldev);
+ u8 cmd;
+
+ if (repeat > 0)
+ return -EINVAL;
+
+ if (is_cascade(pattern, len, false, false)) {
+ dev_dbg(led->priv->dev, "Cascade mode for 0x%02x(%c)",
+ led->reg, led->reg);
+
+ cmd = EL_PIPE_CASCADE;
+ } else if (is_cascade(pattern, len, true, false)) {
+ dev_dbg(led->priv->dev, "Inverse cascade mode for 0x%02x(%c)",
+ led->reg, led->reg);
+
+ cmd = EL_PIPE_INV_CASCADE;
+ } else if (is_bounce(pattern, len, false)) {
+ dev_dbg(led->priv->dev, "Bounce mode for 0x%02x(%c)",
+ led->reg, led->reg);
+
+ cmd = EL_PIPE_BOUNCE;
+ } else if (is_bounce(pattern, len, true)) {
+ dev_dbg(led->priv->dev, "Inverse bounce mode for 0x%02x(%c)",
+ led->reg, led->reg);
+
+ cmd = EL_PIPE_INV_BOUNCE;
+ } else {
+ dev_err(led->priv->dev, "Invalid hw_pattern for 0x%02x(%c)!",
+ led->reg, led->reg);
+
+ return -EINVAL;
+ }
+
+ return el15203000_cmd(led, cmd);
+}
+
+static int el15203000_pattern_clear(struct led_classdev *ldev)
+{
+ struct el15203000_led *led = to_el15203000_led(ldev);
+
+ return el15203000_cmd(led, EL_OFF);
+}
+
+static int el15203000_probe_dt(struct el15203000 *priv)
+{
+ struct el15203000_led *led = priv->leds;
+ int ret;
+
+ 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");
+ return ret;
+ }
+
+ if (led->reg > U8_MAX) {
+ dev_err(priv->dev, "LED value %d is invalid", led->reg);
+ return -EINVAL;
+ }
+
+ led->priv = priv;
+ led->ldev.max_brightness = LED_ON;
+ led->ldev.brightness_set_blocking = el15203000_set_blocking;
+
+ if (led->reg == 'S') {
+ led->ldev.pattern_set = el15203000_pattern_set_S;
+ led->ldev.pattern_clear = el15203000_pattern_clear;
+ } else if (led->reg == 'P') {
+ led->ldev.pattern_set = el15203000_pattern_set_P;
+ led->ldev.pattern_clear = el15203000_pattern_clear;
+ }
+
+ init_data.fwnode = child;
+ ret = devm_led_classdev_register_ext(priv->dev, &led->ldev,
+ &init_data);
+ if (ret) {
+ dev_err(priv->dev,
+ "failed to register LED device %s, err %d",
+ led->ldev.name, ret);
+ return ret;
+ }
+
+ led++;
+ }
+
+ return 0;
+}
+
+static int el15203000_probe(struct spi_device *spi)
+{
+ struct el15203000 *priv;
+ size_t count;
+
+ count = device_get_child_node_count(&spi->dev);
+ if (!count) {
+ dev_err(&spi->dev, "LEDs are not defined in device tree!");
+ return -ENODEV;
+ }
+
+ priv = devm_kzalloc(&spi->dev, struct_size(priv, leds, count),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ mutex_init(&priv->lock);
+ priv->count = count;
+ priv->dev = &spi->dev;
+ priv->spi = spi;
+ priv->delay = jiffies -
+ usecs_to_jiffies(EL_FW_DELAY_USEC);
+
+ spi_set_drvdata(spi, priv);
+
+ return el15203000_probe_dt(priv);
+}
+
+static void el15203000_remove(struct spi_device *spi)
+{
+ struct el15203000 *priv = spi_get_drvdata(spi);
+
+ mutex_destroy(&priv->lock);
+}
+
+static const struct of_device_id el15203000_dt_ids[] = {
+ { .compatible = "crane,el15203000", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, el15203000_dt_ids);
+
+static struct spi_driver el15203000_driver = {
+ .probe = el15203000_probe,
+ .remove = el15203000_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .of_match_table = el15203000_dt_ids,
+ },
+};
+
+module_spi_driver(el15203000_driver);
+
+MODULE_AUTHOR("Oleh Kravchenko <oleg@kaa.org.ua>");
+MODULE_DESCRIPTION("el15203000 LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:el15203000");
diff --git a/drivers/leds/leds-expresswire.c b/drivers/leds/leds-expresswire.c
new file mode 100644
index 000000000000..bb69be228a6d
--- /dev/null
+++ b/drivers/leds/leds-expresswire.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Shared library for Kinetic's ExpressWire protocol.
+ * This protocol works by pulsing the ExpressWire IC's control GPIO.
+ * ktd2692 and ktd2801 are known to use this protocol.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/export.h>
+#include <linux/gpio/consumer.h>
+#include <linux/types.h>
+
+#include <linux/leds-expresswire.h>
+
+void expresswire_power_off(struct expresswire_common_props *props)
+{
+ gpiod_set_value_cansleep(props->ctrl_gpio, 0);
+ usleep_range(props->timing.poweroff_us, props->timing.poweroff_us * 2);
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_power_off, "EXPRESSWIRE");
+
+void expresswire_enable(struct expresswire_common_props *props)
+{
+ gpiod_set_value(props->ctrl_gpio, 1);
+ udelay(props->timing.detect_delay_us);
+ gpiod_set_value(props->ctrl_gpio, 0);
+ udelay(props->timing.detect_us);
+ gpiod_set_value(props->ctrl_gpio, 1);
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_enable, "EXPRESSWIRE");
+
+void expresswire_start(struct expresswire_common_props *props)
+{
+ gpiod_set_value(props->ctrl_gpio, 1);
+ udelay(props->timing.data_start_us);
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_start, "EXPRESSWIRE");
+
+void expresswire_end(struct expresswire_common_props *props)
+{
+ gpiod_set_value(props->ctrl_gpio, 0);
+ udelay(props->timing.end_of_data_low_us);
+ gpiod_set_value(props->ctrl_gpio, 1);
+ udelay(props->timing.end_of_data_high_us);
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_end, "EXPRESSWIRE");
+
+void expresswire_set_bit(struct expresswire_common_props *props, bool bit)
+{
+ if (bit) {
+ gpiod_set_value(props->ctrl_gpio, 0);
+ udelay(props->timing.short_bitset_us);
+ gpiod_set_value(props->ctrl_gpio, 1);
+ udelay(props->timing.long_bitset_us);
+ } else {
+ gpiod_set_value(props->ctrl_gpio, 0);
+ udelay(props->timing.long_bitset_us);
+ gpiod_set_value(props->ctrl_gpio, 1);
+ udelay(props->timing.short_bitset_us);
+ }
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_set_bit, "EXPRESSWIRE");
+
+void expresswire_write_u8(struct expresswire_common_props *props, u8 val)
+{
+ expresswire_start(props);
+ for (int i = 7; i >= 0; i--)
+ expresswire_set_bit(props, val & BIT(i));
+ expresswire_end(props);
+}
+EXPORT_SYMBOL_NS_GPL(expresswire_write_u8, "EXPRESSWIRE");
diff --git a/drivers/leds/leds-fsg.c b/drivers/leds/leds-fsg.c
deleted file mode 100644
index b4d5a44cc41b..000000000000
--- a/drivers/leds/leds-fsg.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * LED Driver for the Freecom FSG-3
- *
- * Copyright (c) 2008 Rod Whitby <rod@whitby.id.au>
- *
- * Author: Rod Whitby <rod@whitby.id.au>
- *
- * Based on leds-spitz.c
- * Copyright 2005-2006 Openedhand Ltd.
- * Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
-
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include <linux/io.h>
-#include <mach/hardware.h>
-
-#define FSG_LED_WLAN_BIT 0
-#define FSG_LED_WAN_BIT 1
-#define FSG_LED_SATA_BIT 2
-#define FSG_LED_USB_BIT 4
-#define FSG_LED_RING_BIT 5
-#define FSG_LED_SYNC_BIT 7
-
-static short __iomem *latch_address;
-static unsigned short latch_value;
-
-
-static void fsg_led_wlan_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_WLAN_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_WLAN_BIT);
- *latch_address = latch_value;
- }
-}
-
-static void fsg_led_wan_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_WAN_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_WAN_BIT);
- *latch_address = latch_value;
- }
-}
-
-static void fsg_led_sata_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_SATA_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_SATA_BIT);
- *latch_address = latch_value;
- }
-}
-
-static void fsg_led_usb_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_USB_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_USB_BIT);
- *latch_address = latch_value;
- }
-}
-
-static void fsg_led_sync_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_SYNC_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_SYNC_BIT);
- *latch_address = latch_value;
- }
-}
-
-static void fsg_led_ring_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value) {
- latch_value &= ~(1 << FSG_LED_RING_BIT);
- *latch_address = latch_value;
- } else {
- latch_value |= (1 << FSG_LED_RING_BIT);
- *latch_address = latch_value;
- }
-}
-
-
-static struct led_classdev fsg_wlan_led = {
- .name = "fsg:blue:wlan",
- .brightness_set = fsg_led_wlan_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static struct led_classdev fsg_wan_led = {
- .name = "fsg:blue:wan",
- .brightness_set = fsg_led_wan_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static struct led_classdev fsg_sata_led = {
- .name = "fsg:blue:sata",
- .brightness_set = fsg_led_sata_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static struct led_classdev fsg_usb_led = {
- .name = "fsg:blue:usb",
- .brightness_set = fsg_led_usb_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static struct led_classdev fsg_sync_led = {
- .name = "fsg:blue:sync",
- .brightness_set = fsg_led_sync_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-static struct led_classdev fsg_ring_led = {
- .name = "fsg:blue:ring",
- .brightness_set = fsg_led_ring_set,
- .flags = LED_CORE_SUSPENDRESUME,
-};
-
-
-static int fsg_led_probe(struct platform_device *pdev)
-{
- int ret;
-
- /* Map the LED chip select address space */
- latch_address = (unsigned short *) devm_ioremap(&pdev->dev,
- IXP4XX_EXP_BUS_BASE(2), 512);
- if (!latch_address)
- return -ENOMEM;
-
- latch_value = 0xffff;
- *latch_address = latch_value;
-
- ret = led_classdev_register(&pdev->dev, &fsg_wlan_led);
- if (ret < 0)
- goto failwlan;
-
- ret = led_classdev_register(&pdev->dev, &fsg_wan_led);
- if (ret < 0)
- goto failwan;
-
- ret = led_classdev_register(&pdev->dev, &fsg_sata_led);
- if (ret < 0)
- goto failsata;
-
- ret = led_classdev_register(&pdev->dev, &fsg_usb_led);
- if (ret < 0)
- goto failusb;
-
- ret = led_classdev_register(&pdev->dev, &fsg_sync_led);
- if (ret < 0)
- goto failsync;
-
- ret = led_classdev_register(&pdev->dev, &fsg_ring_led);
- if (ret < 0)
- goto failring;
-
- return ret;
-
- failring:
- led_classdev_unregister(&fsg_sync_led);
- failsync:
- led_classdev_unregister(&fsg_usb_led);
- failusb:
- led_classdev_unregister(&fsg_sata_led);
- failsata:
- led_classdev_unregister(&fsg_wan_led);
- failwan:
- led_classdev_unregister(&fsg_wlan_led);
- failwlan:
-
- return ret;
-}
-
-static int fsg_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&fsg_wlan_led);
- led_classdev_unregister(&fsg_wan_led);
- led_classdev_unregister(&fsg_sata_led);
- led_classdev_unregister(&fsg_usb_led);
- led_classdev_unregister(&fsg_sync_led);
- led_classdev_unregister(&fsg_ring_led);
-
- return 0;
-}
-
-
-static struct platform_driver fsg_led_driver = {
- .probe = fsg_led_probe,
- .remove = fsg_led_remove,
- .driver = {
- .name = "fsg-led",
- },
-};
-
-module_platform_driver(fsg_led_driver);
-
-MODULE_AUTHOR("Rod Whitby <rod@whitby.id.au>");
-MODULE_DESCRIPTION("Freecom FSG-3 LED driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-gpio-register.c b/drivers/leds/leds-gpio-register.c
index 1c4ed5510f35..ccc01fa72e6f 100644
--- a/drivers/leds/leds-gpio-register.c
+++ b/drivers/leds/leds-gpio-register.c
@@ -1,18 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2011 Pengutronix
* Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
- *
- * This program is free software; you can redistribute it and/or modify it under
- * the terms of the GNU General Public License version 2 as published by the
- * Free Software Foundation.
*/
#include <linux/err.h>
+#include <linux/leds.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/leds.h>
/**
* gpio_led_register_device - register a gpio-led 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
@@ -28,6 +26,9 @@ struct platform_device *__init gpio_led_register_device(
struct platform_device *ret;
struct gpio_led_platform_data _pdata = *pdata;
+ if (!pdata->num_leds)
+ return ERR_PTR(-EINVAL);
+
_pdata.leds = kmemdup(pdata->leds,
pdata->num_leds * sizeof(*pdata->leds), GFP_KERNEL);
if (!_pdata.leds)
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c
index 84d74c373cae..a3428b22de3a 100644
--- a/drivers/leds/leds-gpio.c
+++ b/drivers/leds/leds-gpio.c
@@ -1,58 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LEDs driver for GPIOs
*
* Copyright (C) 2007 8D Technologies inc.
* Raphael Assenat <raph@8d.com>
* Copyright (C) 2008 Freescale Semiconductor, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/err.h>
#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/leds.h>
-#include <linux/of_platform.h>
-#include <linux/of_gpio.h>
-#include <linux/slab.h>
-#include <linux/workqueue.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
-#include <linux/err.h>
+#include <linux/overflow.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/types.h>
struct gpio_led_data {
struct led_classdev cdev;
- unsigned gpio;
- struct work_struct work;
- u8 new_level;
+ struct gpio_desc *gpiod;
u8 can_sleep;
- u8 active_low;
u8 blinking;
- int (*platform_gpio_blink_set)(unsigned gpio, int state,
- unsigned long *delay_on, unsigned long *delay_off);
+ gpio_blink_set_t platform_gpio_blink_set;
};
-static void gpio_led_work(struct work_struct *work)
+static inline struct gpio_led_data *
+ cdev_to_gpio_led_data(struct led_classdev *led_cdev)
{
- struct gpio_led_data *led_dat =
- container_of(work, struct gpio_led_data, work);
-
- if (led_dat->blinking) {
- led_dat->platform_gpio_blink_set(led_dat->gpio,
- led_dat->new_level,
- NULL, NULL);
- led_dat->blinking = 0;
- } else
- gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level);
+ return container_of(led_cdev, struct gpio_led_data, cdev);
}
static void gpio_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- struct gpio_led_data *led_dat =
- container_of(led_cdev, struct gpio_led_data, cdev);
+ struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
int level;
if (value == LED_OFF)
@@ -60,205 +46,248 @@ static void gpio_led_set(struct led_classdev *led_cdev,
else
level = 1;
- if (led_dat->active_low)
- level = !level;
-
- /* Setting GPIOs with I2C/etc requires a task context, and we don't
- * seem to have a reliable way to know if we're already in one; so
- * let's just assume the worst.
- */
- if (led_dat->can_sleep) {
- led_dat->new_level = level;
- schedule_work(&led_dat->work);
+ if (led_dat->blinking) {
+ led_dat->platform_gpio_blink_set(led_dat->gpiod, level,
+ NULL, NULL);
+ led_dat->blinking = 0;
} else {
- if (led_dat->blinking) {
- led_dat->platform_gpio_blink_set(led_dat->gpio, level,
- NULL, NULL);
- led_dat->blinking = 0;
- } else
- gpio_set_value(led_dat->gpio, level);
+ if (led_dat->can_sleep)
+ gpiod_set_value_cansleep(led_dat->gpiod, level);
+ else
+ gpiod_set_value(led_dat->gpiod, level);
}
}
+static int gpio_led_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ gpio_led_set(led_cdev, value);
+ return 0;
+}
+
static int gpio_blink_set(struct led_classdev *led_cdev,
unsigned long *delay_on, unsigned long *delay_off)
{
- struct gpio_led_data *led_dat =
- container_of(led_cdev, struct gpio_led_data, cdev);
+ struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev);
led_dat->blinking = 1;
- return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK,
+ return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK,
delay_on, delay_off);
}
static int create_gpio_led(const struct gpio_led *template,
struct gpio_led_data *led_dat, struct device *parent,
- int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
+ struct fwnode_handle *fwnode, gpio_blink_set_t blink_set)
{
+ struct led_init_data init_data = {};
+ struct pinctrl *pinctrl;
int ret, state;
- led_dat->gpio = -1;
-
- /* skip leds that aren't available */
- if (!gpio_is_valid(template->gpio)) {
- dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
- template->gpio, template->name);
- return 0;
- }
-
- ret = devm_gpio_request(parent, template->gpio, template->name);
- if (ret < 0)
- return ret;
-
- led_dat->cdev.name = template->name;
led_dat->cdev.default_trigger = template->default_trigger;
- led_dat->gpio = template->gpio;
- led_dat->can_sleep = gpio_cansleep(template->gpio);
- led_dat->active_low = template->active_low;
+ led_dat->can_sleep = gpiod_cansleep(led_dat->gpiod);
+ if (!led_dat->can_sleep)
+ led_dat->cdev.brightness_set = gpio_led_set;
+ else
+ led_dat->cdev.brightness_set_blocking = gpio_led_set_blocking;
led_dat->blinking = 0;
if (blink_set) {
led_dat->platform_gpio_blink_set = blink_set;
led_dat->cdev.blink_set = gpio_blink_set;
}
- led_dat->cdev.brightness_set = gpio_led_set;
- if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
- state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
- else
+ if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) {
+ state = gpiod_get_value_cansleep(led_dat->gpiod);
+ if (state < 0)
+ return state;
+ } else {
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
- led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
+ }
+ led_dat->cdev.brightness = state;
+ led_dat->cdev.max_brightness = 1;
if (!template->retain_state_suspended)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
+ if (template->panic_indicator)
+ led_dat->cdev.flags |= LED_PANIC_INDICATOR;
+ if (template->retain_state_shutdown)
+ led_dat->cdev.flags |= LED_RETAIN_AT_SHUTDOWN;
- ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
+ ret = gpiod_direction_output(led_dat->gpiod, state);
if (ret < 0)
return ret;
- INIT_WORK(&led_dat->work, gpio_led_work);
+ if (template->name) {
+ led_dat->cdev.name = template->name;
+ ret = devm_led_classdev_register(parent, &led_dat->cdev);
+ } else {
+ init_data.fwnode = fwnode;
+ ret = devm_led_classdev_register_ext(parent, &led_dat->cdev,
+ &init_data);
+ }
- ret = led_classdev_register(parent, &led_dat->cdev);
- if (ret < 0)
+ if (ret)
return ret;
- return 0;
-}
+ pinctrl = devm_pinctrl_get_select_default(led_dat->cdev.dev);
+ ret = PTR_ERR_OR_ZERO(pinctrl);
+ /* pinctrl-%d not present, not an error */
+ if (ret == -ENODEV)
+ ret = 0;
+ if (ret) {
+ dev_warn(led_dat->cdev.dev, "Failed to select %pfw pinctrl: %d\n",
+ fwnode, ret);
+ }
-static void delete_gpio_led(struct gpio_led_data *led)
-{
- if (!gpio_is_valid(led->gpio))
- return;
- led_classdev_unregister(&led->cdev);
- cancel_work_sync(&led->work);
+ return ret;
}
struct gpio_leds_priv {
int num_leds;
- struct gpio_led_data leds[];
+ struct gpio_led_data leds[] __counted_by(num_leds);
};
-static inline int sizeof_gpio_leds_priv(int num_leds)
-{
- return sizeof(struct gpio_leds_priv) +
- (sizeof(struct gpio_led_data) * num_leds);
-}
-
-/* Code to create from OpenFirmware platform devices */
-#ifdef CONFIG_OF_GPIO
-static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
+static struct gpio_leds_priv *gpio_leds_create(struct device *dev)
{
- struct device_node *np = pdev->dev.of_node, *child;
struct gpio_leds_priv *priv;
- int count, ret;
+ int count, used, ret;
- /* count LEDs in this device, so we know how much to allocate */
- count = of_get_child_count(np);
+ count = device_get_child_node_count(dev);
if (!count)
return ERR_PTR(-ENODEV);
- for_each_child_of_node(np, child)
- if (of_get_gpio(child, 0) == -EPROBE_DEFER)
- return ERR_PTR(-EPROBE_DEFER);
-
- priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
- GFP_KERNEL);
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
if (!priv)
return ERR_PTR(-ENOMEM);
+ priv->num_leds = count;
+ used = 0;
- for_each_child_of_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
+ struct gpio_led_data *led_dat = &priv->leds[used];
struct gpio_led led = {};
- enum of_gpio_flags flags;
- const char *state;
-
- led.gpio = of_get_gpio_flags(child, 0, &flags);
- led.active_low = flags & OF_GPIO_ACTIVE_LOW;
- led.name = of_get_property(child, "label", NULL) ? : child->name;
- led.default_trigger =
- of_get_property(child, "linux,default-trigger", NULL);
- state = of_get_property(child, "default-state", NULL);
- if (state) {
- if (!strcmp(state, "keep"))
- led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
- else if (!strcmp(state, "on"))
- led.default_state = LEDS_GPIO_DEFSTATE_ON;
- else
- led.default_state = LEDS_GPIO_DEFSTATE_OFF;
- }
- ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
- &pdev->dev, NULL);
- if (ret < 0) {
- of_node_put(child);
- goto err;
+ /*
+ * Acquire gpiod from DT with uninitialized label, which
+ * will be updated after LED class device is registered,
+ * Only then the final LED name is known.
+ */
+ led.gpiod = devm_fwnode_gpiod_get(dev, child, NULL, GPIOD_ASIS,
+ NULL);
+ if (IS_ERR(led.gpiod)) {
+ dev_err_probe(dev, PTR_ERR(led.gpiod), "Failed to get GPIO '%pfw'\n",
+ child);
+ return ERR_CAST(led.gpiod);
}
+
+ led_dat->gpiod = led.gpiod;
+
+ led.default_state = led_init_default_state_get(child);
+
+ if (fwnode_property_present(child, "retain-state-suspended"))
+ led.retain_state_suspended = 1;
+ if (fwnode_property_present(child, "retain-state-shutdown"))
+ led.retain_state_shutdown = 1;
+ if (fwnode_property_present(child, "panic-indicator"))
+ led.panic_indicator = 1;
+
+ ret = create_gpio_led(&led, led_dat, dev, child, NULL);
+ 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);
+ used++;
}
+ priv->num_leds = used;
return priv;
-
-err:
- for (count = priv->num_leds - 2; count >= 0; count--)
- delete_gpio_led(&priv->leds[count]);
- return ERR_PTR(-ENODEV);
}
static const struct of_device_id of_gpio_leds_match[] = {
{ .compatible = "gpio-leds", },
{},
};
-#else /* CONFIG_OF_GPIO */
-static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
+
+MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
+
+static struct gpio_desc *gpio_led_get_gpiod(struct device *dev, int idx,
+ const struct gpio_led *template)
{
- return ERR_PTR(-ENODEV);
-}
-#endif /* CONFIG_OF_GPIO */
+ struct gpio_desc *gpiod;
+ int ret;
+
+ /*
+ * This means the LED does not come from the device tree
+ * or ACPI, so let's try just getting it by index from the
+ * device, this will hit the board file, if any and get
+ * the GPIO from there.
+ */
+ gpiod = devm_gpiod_get_index_optional(dev, NULL, idx, GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ return gpiod;
+ if (gpiod) {
+ gpiod_set_consumer_name(gpiod, template->name);
+ return gpiod;
+ }
+
+ /*
+ * This is the legacy code path for platform code that
+ * still uses GPIO numbers. Ultimately we would like to get
+ * rid of this block completely.
+ */
+
+ /* skip leds that aren't available */
+ if (!gpio_is_valid(template->gpio))
+ return ERR_PTR(-ENOENT);
+
+ ret = devm_gpio_request_one(dev, template->gpio, GPIOF_OUT_INIT_LOW,
+ template->name);
+ if (ret < 0)
+ return ERR_PTR(ret);
+
+ gpiod = gpio_to_desc(template->gpio);
+ if (!gpiod)
+ return ERR_PTR(-EINVAL);
+
+ if (template->active_low ^ gpiod_is_active_low(gpiod))
+ gpiod_toggle_active_low(gpiod);
+ return gpiod;
+}
static int gpio_led_probe(struct platform_device *pdev)
{
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct gpio_led_platform_data *pdata = dev_get_platdata(dev);
struct gpio_leds_priv *priv;
- int i, ret = 0;
-
+ int i, ret;
if (pdata && pdata->num_leds) {
- priv = devm_kzalloc(&pdev->dev,
- sizeof_gpio_leds_priv(pdata->num_leds),
- GFP_KERNEL);
+ priv = devm_kzalloc(dev, struct_size(priv, leds, pdata->num_leds), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->num_leds = pdata->num_leds;
for (i = 0; i < priv->num_leds; i++) {
- ret = create_gpio_led(&pdata->leds[i],
- &priv->leds[i],
- &pdev->dev, pdata->gpio_blink_set);
- if (ret < 0) {
- /* On failure: unwind the led creations */
- for (i = i - 1; i >= 0; i--)
- delete_gpio_led(&priv->leds[i]);
- return ret;
+ const struct gpio_led *template = &pdata->leds[i];
+ struct gpio_led_data *led_dat = &priv->leds[i];
+
+ if (template->gpiod)
+ led_dat->gpiod = template->gpiod;
+ else
+ led_dat->gpiod =
+ gpio_led_get_gpiod(dev, i, template);
+ if (IS_ERR(led_dat->gpiod)) {
+ dev_info(dev, "Skipping unavailable LED gpio %d (%s)\n",
+ template->gpio, template->name);
+ continue;
}
+
+ ret = create_gpio_led(template, led_dat, dev, NULL,
+ pdata->gpio_blink_set);
+ if (ret < 0)
+ return ret;
}
} else {
- priv = gpio_leds_create_of(pdev);
+ priv = gpio_leds_create(dev);
if (IS_ERR(priv))
return PTR_ERR(priv);
}
@@ -268,24 +297,25 @@ static int gpio_led_probe(struct platform_device *pdev)
return 0;
}
-static int gpio_led_remove(struct platform_device *pdev)
+static void gpio_led_shutdown(struct platform_device *pdev)
{
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
int i;
- for (i = 0; i < priv->num_leds; i++)
- delete_gpio_led(&priv->leds[i]);
+ for (i = 0; i < priv->num_leds; i++) {
+ struct gpio_led_data *led = &priv->leds[i];
- return 0;
+ if (!(led->cdev.flags & LED_RETAIN_AT_SHUTDOWN))
+ gpio_led_set(&led->cdev, LED_OFF);
+ }
}
static struct platform_driver gpio_led_driver = {
.probe = gpio_led_probe,
- .remove = gpio_led_remove,
+ .shutdown = gpio_led_shutdown,
.driver = {
.name = "leds-gpio",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(of_gpio_leds_match),
+ .of_match_table = of_gpio_leds_match,
},
};
diff --git a/drivers/leds/leds-hp6xx.c b/drivers/leds/leds-hp6xx.c
index 366b6055e330..54af9e63c09c 100644
--- a/drivers/leds/leds-hp6xx.c
+++ b/drivers/leds/leds-hp6xx.c
@@ -1,18 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Triggers Core
* For the HP Jornada 620/660/680/690 handhelds
*
* Copyright 2008 Kristoffer Ericson <kristoffer.ericson@gmail.com>
* this driver is based on leds-spitz.c by Richard Purdie.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <asm/hd64461.h>
@@ -51,7 +47,7 @@ static struct led_classdev hp6xx_red_led = {
static struct led_classdev hp6xx_green_led = {
.name = "hp6xx:green",
- .default_trigger = "ide-disk",
+ .default_trigger = "disk-activity",
.brightness_set = hp6xxled_green_set,
.flags = LED_CORE_SUSPENDRESUME,
};
@@ -60,31 +56,17 @@ static int hp6xxled_probe(struct platform_device *pdev)
{
int ret;
- ret = led_classdev_register(&pdev->dev, &hp6xx_red_led);
+ ret = devm_led_classdev_register(&pdev->dev, &hp6xx_red_led);
if (ret < 0)
return ret;
- ret = led_classdev_register(&pdev->dev, &hp6xx_green_led);
- if (ret < 0)
- led_classdev_unregister(&hp6xx_red_led);
-
- return ret;
-}
-
-static int hp6xxled_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&hp6xx_red_led);
- led_classdev_unregister(&hp6xx_green_led);
-
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &hp6xx_green_led);
}
static struct platform_driver hp6xxled_driver = {
.probe = hp6xxled_probe,
- .remove = hp6xxled_remove,
.driver = {
.name = "hp6xx-led",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-ip30.c b/drivers/leds/leds-ip30.c
new file mode 100644
index 000000000000..2df24c303366
--- /dev/null
+++ b/drivers/leds/leds-ip30.c
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * LED Driver for SGI Octane machines
+ */
+
+#include <asm/io.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+
+#define IP30_LED_SYSTEM 0
+#define IP30_LED_FAULT 1
+
+struct ip30_led {
+ struct led_classdev cdev;
+ u32 __iomem *reg;
+};
+
+static void ip30led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct ip30_led *led = container_of(led_cdev, struct ip30_led, cdev);
+
+ writel(value, led->reg);
+}
+
+static int ip30led_create(struct platform_device *pdev, int num)
+{
+ struct ip30_led *data;
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->reg = devm_platform_ioremap_resource(pdev, num);
+ if (IS_ERR(data->reg))
+ return PTR_ERR(data->reg);
+
+ switch (num) {
+ case IP30_LED_SYSTEM:
+ data->cdev.name = "white:power";
+ break;
+ case IP30_LED_FAULT:
+ data->cdev.name = "red:fault";
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ data->cdev.brightness = readl(data->reg);
+ data->cdev.max_brightness = 1;
+ data->cdev.brightness_set = ip30led_set;
+
+ return devm_led_classdev_register(&pdev->dev, &data->cdev);
+}
+
+static int ip30led_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = ip30led_create(pdev, IP30_LED_SYSTEM);
+ if (ret < 0)
+ return ret;
+
+ return ip30led_create(pdev, IP30_LED_FAULT);
+}
+
+static struct platform_driver ip30led_driver = {
+ .probe = ip30led_probe,
+ .driver = {
+ .name = "ip30-leds",
+ },
+};
+
+module_platform_driver(ip30led_driver);
+
+MODULE_AUTHOR("Thomas Bogendoerfer <tbogendoerfer@suse.de>");
+MODULE_DESCRIPTION("SGI Octane LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:ip30-leds");
diff --git a/drivers/leds/leds-ipaq-micro.c b/drivers/leds/leds-ipaq-micro.c
new file mode 100644
index 000000000000..504a95b6ef45
--- /dev/null
+++ b/drivers/leds/leds-ipaq-micro.c
@@ -0,0 +1,132 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ *
+ * h3xxx atmel micro companion support, notification LED subdevice
+ *
+ * Author : Linus Walleij <linus.walleij@linaro.org>
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/ipaq-micro.h>
+#include <linux/leds.h>
+
+#define LED_YELLOW 0x00
+#define LED_GREEN 0x01
+
+#define LED_EN (1 << 4) /* LED ON/OFF 0:off, 1:on */
+#define LED_AUTOSTOP (1 << 5) /* LED ON/OFF auto stop set 0:disable, 1:enable */
+#define LED_ALWAYS (1 << 6) /* LED Interrupt Mask 0:No mask, 1:mask */
+
+static int micro_leds_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct ipaq_micro *micro = dev_get_drvdata(led_cdev->dev->parent->parent);
+ /*
+ * In this message:
+ * Byte 0 = LED color: 0 = yellow, 1 = green
+ * yellow LED is always ~30 blinks per minute
+ * Byte 1 = duration (flags?) appears to be ignored
+ * Byte 2 = green ontime in 1/10 sec (deciseconds)
+ * 1 = 1/10 second
+ * 0 = 256/10 second
+ * Byte 3 = green offtime in 1/10 sec (deciseconds)
+ * 1 = 1/10 second
+ * 0 = 256/10 seconds
+ */
+ struct ipaq_micro_msg msg = {
+ .id = MSG_NOTIFY_LED,
+ .tx_len = 4,
+ };
+
+ msg.tx_data[0] = LED_GREEN;
+ msg.tx_data[1] = 0;
+ if (value) {
+ msg.tx_data[2] = 0; /* Duty cycle 256 */
+ msg.tx_data[3] = 1;
+ } else {
+ msg.tx_data[2] = 1;
+ msg.tx_data[3] = 0; /* Duty cycle 256 */
+ }
+ return ipaq_micro_tx_msg_sync(micro, &msg);
+}
+
+/* Maximum duty cycle in ms 256/10 sec = 25600 ms */
+#define IPAQ_LED_MAX_DUTY 25600
+
+static int micro_leds_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct ipaq_micro *micro = dev_get_drvdata(led_cdev->dev->parent->parent);
+ /*
+ * In this message:
+ * Byte 0 = LED color: 0 = yellow, 1 = green
+ * yellow LED is always ~30 blinks per minute
+ * Byte 1 = duration (flags?) appears to be ignored
+ * Byte 2 = green ontime in 1/10 sec (deciseconds)
+ * 1 = 1/10 second
+ * 0 = 256/10 second
+ * Byte 3 = green offtime in 1/10 sec (deciseconds)
+ * 1 = 1/10 second
+ * 0 = 256/10 seconds
+ */
+ struct ipaq_micro_msg msg = {
+ .id = MSG_NOTIFY_LED,
+ .tx_len = 4,
+ };
+
+ msg.tx_data[0] = LED_GREEN;
+ if (*delay_on > IPAQ_LED_MAX_DUTY ||
+ *delay_off > IPAQ_LED_MAX_DUTY)
+ return -EINVAL;
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ *delay_on = 100;
+ *delay_off = 100;
+ }
+
+ msg.tx_data[1] = 0;
+ if (*delay_on >= IPAQ_LED_MAX_DUTY)
+ msg.tx_data[2] = 0;
+ else
+ msg.tx_data[2] = (u8) DIV_ROUND_CLOSEST(*delay_on, 100);
+ if (*delay_off >= IPAQ_LED_MAX_DUTY)
+ msg.tx_data[3] = 0;
+ else
+ msg.tx_data[3] = (u8) DIV_ROUND_CLOSEST(*delay_off, 100);
+ return ipaq_micro_tx_msg_sync(micro, &msg);
+}
+
+static struct led_classdev micro_led = {
+ .name = "led-ipaq-micro",
+ .brightness_set_blocking = micro_leds_brightness_set,
+ .blink_set = micro_leds_blink_set,
+ .flags = LED_CORE_SUSPENDRESUME,
+};
+
+static int micro_leds_probe(struct platform_device *pdev)
+{
+ int ret;
+
+ ret = devm_led_classdev_register(&pdev->dev, &micro_led);
+ if (ret) {
+ dev_err(&pdev->dev, "registering led failed: %d\n", ret);
+ return ret;
+ }
+ dev_info(&pdev->dev, "iPAQ micro notification LED driver\n");
+
+ return 0;
+}
+
+static struct platform_driver micro_leds_device_driver = {
+ .driver = {
+ .name = "ipaq-micro-leds",
+ },
+ .probe = micro_leds_probe,
+};
+module_platform_driver(micro_leds_device_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("driver for iPAQ Atmel micro leds");
+MODULE_ALIAS("platform:ipaq-micro-leds");
diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c
new file mode 100644
index 000000000000..e411cee06dab
--- /dev/null
+++ b/drivers/leds/leds-is31fl319x.c
@@ -0,0 +1,596 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2015-16 Golden Delicious Computers
+ *
+ * Author: Nikolaus Schaller <hns@goldelico.com>
+ *
+ * LED driver for the IS31FL319{0,1,3,6,9} to drive 1, 3, 6 or 9 light
+ * effect LEDs.
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+
+/* register numbers */
+#define IS31FL319X_SHUTDOWN 0x00
+
+/* registers for 3190, 3191 and 3193 */
+#define IS31FL3190_BREATHING 0x01
+#define IS31FL3190_LEDMODE 0x02
+#define IS31FL3190_CURRENT 0x03
+#define IS31FL3190_PWM(channel) (0x04 + channel)
+#define IS31FL3190_DATA_UPDATE 0x07
+#define IS31FL3190_T0(channel) (0x0a + channel)
+#define IS31FL3190_T1T2(channel) (0x10 + channel)
+#define IS31FL3190_T3T4(channel) (0x16 + channel)
+#define IS31FL3190_TIME_UPDATE 0x1c
+#define IS31FL3190_LEDCONTROL 0x1d
+#define IS31FL3190_RESET 0x2f
+
+#define IS31FL3190_CURRENT_uA_MIN 5000
+#define IS31FL3190_CURRENT_uA_DEFAULT 42000
+#define IS31FL3190_CURRENT_uA_MAX 42000
+#define IS31FL3190_CURRENT_SHIFT 2
+#define IS31FL3190_CURRENT_MASK GENMASK(4, 2)
+#define IS31FL3190_CURRENT_5_mA 0x02
+#define IS31FL3190_CURRENT_10_mA 0x01
+#define IS31FL3190_CURRENT_17dot5_mA 0x04
+#define IS31FL3190_CURRENT_30_mA 0x03
+#define IS31FL3190_CURRENT_42_mA 0x00
+
+/* registers for 3196 and 3199 */
+#define IS31FL3196_CTRL1 0x01
+#define IS31FL3196_CTRL2 0x02
+#define IS31FL3196_CONFIG1 0x03
+#define IS31FL3196_CONFIG2 0x04
+#define IS31FL3196_RAMP_MODE 0x05
+#define IS31FL3196_BREATH_MARK 0x06
+#define IS31FL3196_PWM(channel) (0x07 + channel)
+#define IS31FL3196_DATA_UPDATE 0x10
+#define IS31FL3196_T0(channel) (0x11 + channel)
+#define IS31FL3196_T123_1 0x1a
+#define IS31FL3196_T123_2 0x1b
+#define IS31FL3196_T123_3 0x1c
+#define IS31FL3196_T4(channel) (0x1d + channel)
+#define IS31FL3196_TIME_UPDATE 0x26
+#define IS31FL3196_RESET 0xff
+
+#define IS31FL3196_REG_CNT (IS31FL3196_RESET + 1)
+
+#define IS31FL319X_MAX_LEDS 9
+
+/* CS (Current Setting) in CONFIG2 register */
+#define IS31FL3196_CONFIG2_CS_SHIFT 4
+#define IS31FL3196_CONFIG2_CS_MASK GENMASK(2, 0)
+#define IS31FL3196_CONFIG2_CS_STEP_REF 12
+
+#define IS31FL3196_CURRENT_uA_MIN 5000
+#define IS31FL3196_CURRENT_uA_MAX 40000
+#define IS31FL3196_CURRENT_uA_STEP 5000
+#define IS31FL3196_CURRENT_uA_DEFAULT 20000
+
+/* Audio gain in CONFIG2 register */
+#define IS31FL3196_AUDIO_GAIN_DB_MAX ((u32)21)
+#define IS31FL3196_AUDIO_GAIN_DB_STEP 3
+
+/*
+ * regmap is used as a cache of chip's register space,
+ * to avoid reading back brightness values from chip,
+ * which is known to hang.
+ */
+struct is31fl319x_chip {
+ const struct is31fl319x_chipdef *cdef;
+ struct i2c_client *client;
+ struct gpio_desc *shutdown_gpio;
+ struct regmap *regmap;
+ struct mutex lock;
+ u32 audio_gain_db;
+
+ struct is31fl319x_led {
+ struct is31fl319x_chip *chip;
+ struct led_classdev cdev;
+ u32 max_microamp;
+ bool configured;
+ } leds[IS31FL319X_MAX_LEDS];
+};
+
+struct is31fl319x_chipdef {
+ int num_leds;
+ u8 reset_reg;
+ const struct regmap_config *is31fl319x_regmap_config;
+ int (*brightness_set)(struct led_classdev *cdev, enum led_brightness brightness);
+ u32 current_default;
+ u32 current_min;
+ u32 current_max;
+ bool is_3196or3199;
+};
+
+static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg)
+{
+ /* we have no readable registers */
+ return false;
+}
+
+static bool is31fl3190_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /* volatile registers are not cached */
+ switch (reg) {
+ case IS31FL3190_DATA_UPDATE:
+ case IS31FL3190_TIME_UPDATE:
+ case IS31FL3190_RESET:
+ return true; /* always write-through */
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default is31fl3190_reg_defaults[] = {
+ { IS31FL3190_LEDMODE, 0x00 },
+ { IS31FL3190_CURRENT, 0x00 },
+ { IS31FL3190_PWM(0), 0x00 },
+ { IS31FL3190_PWM(1), 0x00 },
+ { IS31FL3190_PWM(2), 0x00 },
+};
+
+static const struct regmap_config is31fl3190_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = IS31FL3190_RESET,
+ .cache_type = REGCACHE_FLAT,
+ .readable_reg = is31fl319x_readable_reg,
+ .volatile_reg = is31fl3190_volatile_reg,
+ .reg_defaults = is31fl3190_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(is31fl3190_reg_defaults),
+};
+
+static bool is31fl3196_volatile_reg(struct device *dev, unsigned int reg)
+{
+ /* volatile registers are not cached */
+ switch (reg) {
+ case IS31FL3196_DATA_UPDATE:
+ case IS31FL3196_TIME_UPDATE:
+ case IS31FL3196_RESET:
+ return true; /* always write-through */
+ default:
+ return false;
+ }
+}
+
+static const struct reg_default is31fl3196_reg_defaults[] = {
+ { IS31FL3196_CONFIG1, 0x00 },
+ { IS31FL3196_CONFIG2, 0x00 },
+ { IS31FL3196_PWM(0), 0x00 },
+ { IS31FL3196_PWM(1), 0x00 },
+ { IS31FL3196_PWM(2), 0x00 },
+ { IS31FL3196_PWM(3), 0x00 },
+ { IS31FL3196_PWM(4), 0x00 },
+ { IS31FL3196_PWM(5), 0x00 },
+ { IS31FL3196_PWM(6), 0x00 },
+ { IS31FL3196_PWM(7), 0x00 },
+ { IS31FL3196_PWM(8), 0x00 },
+};
+
+static const struct regmap_config is31fl3196_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = IS31FL3196_REG_CNT,
+ .cache_type = REGCACHE_FLAT,
+ .readable_reg = is31fl319x_readable_reg,
+ .volatile_reg = is31fl3196_volatile_reg,
+ .reg_defaults = is31fl3196_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(is31fl3196_reg_defaults),
+};
+
+static int is31fl3190_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev);
+ struct is31fl319x_chip *is31 = led->chip;
+ int chan = led - is31->leds;
+ int ret;
+ int i;
+ u8 ctrl = 0;
+
+ dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness);
+
+ mutex_lock(&is31->lock);
+
+ /* update PWM register */
+ ret = regmap_write(is31->regmap, IS31FL3190_PWM(chan), brightness);
+ if (ret < 0)
+ goto out;
+
+ /* read current brightness of all PWM channels */
+ for (i = 0; i < is31->cdef->num_leds; i++) {
+ unsigned int pwm_value;
+ bool on;
+
+ /*
+ * since neither cdev nor the chip can provide
+ * the current setting, we read from the regmap cache
+ */
+
+ ret = regmap_read(is31->regmap, IS31FL3190_PWM(i), &pwm_value);
+ on = ret >= 0 && pwm_value > LED_OFF;
+
+ ctrl |= on << i;
+ }
+
+ if (ctrl > 0) {
+ dev_dbg(&is31->client->dev, "power up %02x\n", ctrl);
+ regmap_write(is31->regmap, IS31FL3190_LEDCONTROL, ctrl);
+ /* update PWMs */
+ regmap_write(is31->regmap, IS31FL3190_DATA_UPDATE, 0x00);
+ /* enable chip from shut down and enable all channels */
+ ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x20);
+ } else {
+ dev_dbg(&is31->client->dev, "power down\n");
+ /* shut down (no need to clear LEDCONTROL) */
+ ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01);
+ }
+
+out:
+ mutex_unlock(&is31->lock);
+
+ return ret;
+}
+
+static int is31fl3196_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, cdev);
+ struct is31fl319x_chip *is31 = led->chip;
+ int chan = led - is31->leds;
+ int ret;
+ int i;
+ u8 ctrl1 = 0, ctrl2 = 0;
+
+ dev_dbg(&is31->client->dev, "channel %d: %d\n", chan, brightness);
+
+ mutex_lock(&is31->lock);
+
+ /* update PWM register */
+ ret = regmap_write(is31->regmap, IS31FL3196_PWM(chan), brightness);
+ if (ret < 0)
+ goto out;
+
+ /* read current brightness of all PWM channels */
+ for (i = 0; i < is31->cdef->num_leds; i++) {
+ unsigned int pwm_value;
+ bool on;
+
+ /*
+ * since neither cdev nor the chip can provide
+ * the current setting, we read from the regmap cache
+ */
+
+ ret = regmap_read(is31->regmap, IS31FL3196_PWM(i), &pwm_value);
+ on = ret >= 0 && pwm_value > LED_OFF;
+
+ if (i < 3)
+ ctrl1 |= on << i; /* 0..2 => bit 0..2 */
+ else if (i < 6)
+ ctrl1 |= on << (i + 1); /* 3..5 => bit 4..6 */
+ else
+ ctrl2 |= on << (i - 6); /* 6..8 => bit 0..2 */
+ }
+
+ if (ctrl1 > 0 || ctrl2 > 0) {
+ dev_dbg(&is31->client->dev, "power up %02x %02x\n",
+ ctrl1, ctrl2);
+ regmap_write(is31->regmap, IS31FL3196_CTRL1, ctrl1);
+ regmap_write(is31->regmap, IS31FL3196_CTRL2, ctrl2);
+ /* update PWMs */
+ regmap_write(is31->regmap, IS31FL3196_DATA_UPDATE, 0x00);
+ /* enable chip from shut down */
+ ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01);
+ } else {
+ dev_dbg(&is31->client->dev, "power down\n");
+ /* shut down (no need to clear CTRL1/2) */
+ ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x00);
+ }
+
+out:
+ mutex_unlock(&is31->lock);
+
+ return ret;
+}
+
+static const struct is31fl319x_chipdef is31fl3190_cdef = {
+ .num_leds = 1,
+ .reset_reg = IS31FL3190_RESET,
+ .is31fl319x_regmap_config = &is31fl3190_regmap_config,
+ .brightness_set = is31fl3190_brightness_set,
+ .current_default = IS31FL3190_CURRENT_uA_DEFAULT,
+ .current_min = IS31FL3190_CURRENT_uA_MIN,
+ .current_max = IS31FL3190_CURRENT_uA_MAX,
+ .is_3196or3199 = false,
+};
+
+static const struct is31fl319x_chipdef is31fl3193_cdef = {
+ .num_leds = 3,
+ .reset_reg = IS31FL3190_RESET,
+ .is31fl319x_regmap_config = &is31fl3190_regmap_config,
+ .brightness_set = is31fl3190_brightness_set,
+ .current_default = IS31FL3190_CURRENT_uA_DEFAULT,
+ .current_min = IS31FL3190_CURRENT_uA_MIN,
+ .current_max = IS31FL3190_CURRENT_uA_MAX,
+ .is_3196or3199 = false,
+};
+
+static const struct is31fl319x_chipdef is31fl3196_cdef = {
+ .num_leds = 6,
+ .reset_reg = IS31FL3196_RESET,
+ .is31fl319x_regmap_config = &is31fl3196_regmap_config,
+ .brightness_set = is31fl3196_brightness_set,
+ .current_default = IS31FL3196_CURRENT_uA_DEFAULT,
+ .current_min = IS31FL3196_CURRENT_uA_MIN,
+ .current_max = IS31FL3196_CURRENT_uA_MAX,
+ .is_3196or3199 = true,
+};
+
+static const struct is31fl319x_chipdef is31fl3199_cdef = {
+ .num_leds = 9,
+ .reset_reg = IS31FL3196_RESET,
+ .is31fl319x_regmap_config = &is31fl3196_regmap_config,
+ .brightness_set = is31fl3196_brightness_set,
+ .current_default = IS31FL3196_CURRENT_uA_DEFAULT,
+ .current_min = IS31FL3196_CURRENT_uA_MIN,
+ .current_max = IS31FL3196_CURRENT_uA_MAX,
+ .is_3196or3199 = true,
+};
+
+static const struct of_device_id of_is31fl319x_match[] = {
+ { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, },
+ { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, },
+ { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, },
+ { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, },
+ { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, },
+ { .compatible = "si-en,sn3190", .data = &is31fl3190_cdef, },
+ { .compatible = "si-en,sn3191", .data = &is31fl3190_cdef, },
+ { .compatible = "si-en,sn3193", .data = &is31fl3193_cdef, },
+ { .compatible = "si-en,sn3196", .data = &is31fl3196_cdef, },
+ { .compatible = "si-en,sn3199", .data = &is31fl3199_cdef, },
+ { }
+};
+MODULE_DEVICE_TABLE(of, of_is31fl319x_match);
+
+static int is31fl319x_parse_child_fw(const struct device *dev,
+ const struct fwnode_handle *child,
+ struct is31fl319x_led *led,
+ struct is31fl319x_chip *is31)
+{
+ struct led_classdev *cdev = &led->cdev;
+ int ret;
+
+ if (fwnode_property_read_string(child, "label", &cdev->name))
+ cdev->name = fwnode_get_name(child);
+
+ ret = fwnode_property_read_string(child, "linux,default-trigger", &cdev->default_trigger);
+ if (ret < 0 && ret != -EINVAL) /* is optional */
+ return ret;
+
+ led->max_microamp = is31->cdef->current_default;
+ ret = fwnode_property_read_u32(child, "led-max-microamp", &led->max_microamp);
+ if (!ret) {
+ if (led->max_microamp < is31->cdef->current_min)
+ return -EINVAL; /* not supported */
+ led->max_microamp = min(led->max_microamp,
+ is31->cdef->current_max);
+ }
+
+ return 0;
+}
+
+static int is31fl319x_parse_fw(struct device *dev, struct is31fl319x_chip *is31)
+{
+ struct fwnode_handle *fwnode = dev_fwnode(dev);
+ int count;
+ int ret;
+
+ is31->shutdown_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);
+ if (IS_ERR(is31->shutdown_gpio))
+ return dev_err_probe(dev, PTR_ERR(is31->shutdown_gpio),
+ "Failed to get shutdown gpio\n");
+
+ is31->cdef = device_get_match_data(dev);
+
+ count = 0;
+ device_for_each_child_node_scoped(dev, child)
+ count++;
+
+ dev_dbg(dev, "probing with %d leds defined in DT\n", count);
+
+ if (!count || count > is31->cdef->num_leds)
+ return dev_err_probe(dev, -ENODEV,
+ "Number of leds defined must be between 1 and %u\n",
+ is31->cdef->num_leds);
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct is31fl319x_led *led;
+ u32 reg;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to read led 'reg' property\n");
+
+ 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)
+ return dev_err_probe(dev, -EINVAL, "led %u is already configured\n", reg);
+
+ ret = is31fl319x_parse_child_fw(dev, child, led, is31);
+ if (ret)
+ return dev_err_probe(dev, ret, "led %u DT parsing failed\n", reg);
+
+ led->configured = true;
+ }
+
+ is31->audio_gain_db = 0;
+ if (is31->cdef->is_3196or3199) {
+ ret = fwnode_property_read_u32(fwnode, "audio-gain-db", &is31->audio_gain_db);
+ if (!ret)
+ is31->audio_gain_db = min(is31->audio_gain_db,
+ IS31FL3196_AUDIO_GAIN_DB_MAX);
+ }
+
+ return 0;
+}
+
+static inline int is31fl3190_microamp_to_cs(struct device *dev, u32 microamp)
+{
+ switch (microamp) {
+ case 5000:
+ return IS31FL3190_CURRENT_5_mA;
+ case 10000:
+ return IS31FL3190_CURRENT_10_mA;
+ case 17500:
+ return IS31FL3190_CURRENT_17dot5_mA;
+ case 30000:
+ return IS31FL3190_CURRENT_30_mA;
+ case 42000:
+ return IS31FL3190_CURRENT_42_mA;
+ default:
+ dev_warn(dev, "Unsupported current value: %d, using 5000 µA!\n", microamp);
+ return IS31FL3190_CURRENT_5_mA;
+ }
+}
+
+static inline int is31fl3196_microamp_to_cs(struct device *dev, u32 microamp)
+{
+ /* round down to nearest supported value (range check done by caller) */
+ u32 step = microamp / IS31FL3196_CURRENT_uA_STEP;
+
+ return ((IS31FL3196_CONFIG2_CS_STEP_REF - step) &
+ IS31FL3196_CONFIG2_CS_MASK) <<
+ IS31FL3196_CONFIG2_CS_SHIFT; /* CS encoding */
+}
+
+static inline int is31fl3196_db_to_gain(u32 dezibel)
+{
+ /* round down to nearest supported value (range check done by caller) */
+ return dezibel / IS31FL3196_AUDIO_GAIN_DB_STEP;
+}
+
+static int is31fl319x_probe(struct i2c_client *client)
+{
+ struct is31fl319x_chip *is31;
+ struct device *dev = &client->dev;
+ int err;
+ int i = 0;
+ u32 aggregated_led_microamp;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C))
+ return -EIO;
+
+ is31 = devm_kzalloc(&client->dev, sizeof(*is31), GFP_KERNEL);
+ if (!is31)
+ return -ENOMEM;
+
+ err = devm_mutex_init(dev, &is31->lock);
+ if (err)
+ return err;
+
+ err = is31fl319x_parse_fw(&client->dev, is31);
+ if (err)
+ return err;
+
+ if (is31->shutdown_gpio) {
+ gpiod_direction_output(is31->shutdown_gpio, 0);
+ mdelay(5);
+ gpiod_direction_output(is31->shutdown_gpio, 1);
+ }
+
+ is31->client = client;
+ is31->regmap = devm_regmap_init_i2c(client, is31->cdef->is31fl319x_regmap_config);
+ if (IS_ERR(is31->regmap))
+ return dev_err_probe(dev, PTR_ERR(is31->regmap), "failed to allocate register map\n");
+
+ i2c_set_clientdata(client, is31);
+
+ /* check for write-reply from chip (we can't read any registers) */
+ err = regmap_write(is31->regmap, is31->cdef->reset_reg, 0x00);
+ if (err < 0)
+ return dev_err_probe(dev, err, "no response from chip write\n");
+
+ /*
+ * Kernel conventions require per-LED led-max-microamp property.
+ * But the chip does not allow to limit individual LEDs.
+ * So we take minimum from all subnodes for safety of hardware.
+ */
+ aggregated_led_microamp = is31->cdef->current_max;
+ for (i = 0; i < is31->cdef->num_leds; i++)
+ if (is31->leds[i].configured &&
+ is31->leds[i].max_microamp < aggregated_led_microamp)
+ aggregated_led_microamp = is31->leds[i].max_microamp;
+
+ if (is31->cdef->is_3196or3199)
+ regmap_write(is31->regmap, IS31FL3196_CONFIG2,
+ is31fl3196_microamp_to_cs(dev, aggregated_led_microamp) |
+ is31fl3196_db_to_gain(is31->audio_gain_db));
+ else
+ regmap_update_bits(is31->regmap, IS31FL3190_CURRENT, IS31FL3190_CURRENT_MASK,
+ is31fl3190_microamp_to_cs(dev, aggregated_led_microamp) << IS31FL3190_CURRENT_SHIFT);
+
+ for (i = 0; i < is31->cdef->num_leds; i++) {
+ struct is31fl319x_led *led = &is31->leds[i];
+
+ if (!led->configured)
+ continue;
+
+ led->chip = is31;
+ led->cdev.brightness_set_blocking = is31->cdef->brightness_set;
+
+ err = devm_led_classdev_register(&client->dev, &led->cdev);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * i2c-core (and modalias) requires that id_table be properly filled,
+ * even though it is not used for DeviceTree based instantiation.
+ */
+static const struct i2c_device_id is31fl319x_id[] = {
+ { "is31fl3190" },
+ { "is31fl3191" },
+ { "is31fl3193" },
+ { "is31fl3196" },
+ { "is31fl3199" },
+ { "sn3190" },
+ { "sn3191" },
+ { "sn3193" },
+ { "sn3196" },
+ { "sn3199" },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, is31fl319x_id);
+
+static struct i2c_driver is31fl319x_driver = {
+ .driver = {
+ .name = "leds-is31fl319x",
+ .of_match_table = of_is31fl319x_match,
+ },
+ .probe = is31fl319x_probe,
+ .id_table = is31fl319x_id,
+};
+
+module_i2c_driver(is31fl319x_driver);
+
+MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>");
+MODULE_AUTHOR("Andrey Utkin <andrey_utkin@fastmail.com>");
+MODULE_DESCRIPTION("IS31FL319X LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-is31fl32xx.c b/drivers/leds/leds-is31fl32xx.c
new file mode 100644
index 000000000000..dc9349f9d350
--- /dev/null
+++ b/drivers/leds/leds-is31fl32xx.c
@@ -0,0 +1,528 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for ISSI IS31FL32xx family of I2C LED controllers
+ *
+ * Copyright 2015 Allworx Corp.
+ *
+ * Datasheets:
+ * http://www.issi.com/US/product-analog-fxled-driver.shtml
+ * http://www.si-en.com/product.asp?parentid=890
+ */
+
+#include <linux/device.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+
+/* Used to indicate a device has no such register */
+#define IS31FL32XX_REG_NONE 0xFF
+
+/* Software Shutdown bit in Shutdown Register */
+#define IS31FL32XX_SHUTDOWN_SSD_ENABLE 0
+#define IS31FL32XX_SHUTDOWN_SSD_DISABLE BIT(0)
+
+/* IS31FL3216 has a number of unique registers */
+#define IS31FL3216_CONFIG_REG 0x00
+#define IS31FL3216_LIGHTING_EFFECT_REG 0x03
+#define IS31FL3216_CHANNEL_CONFIG_REG 0x04
+
+/* Software Shutdown bit in 3216 Config Register */
+#define IS31FL3216_CONFIG_SSD_ENABLE BIT(7)
+#define IS31FL3216_CONFIG_SSD_DISABLE 0
+
+#define IS31FL32XX_PWM_FREQUENCY_22KHZ 0x01
+
+struct is31fl32xx_priv;
+struct is31fl32xx_led_data {
+ struct led_classdev cdev;
+ u8 channel; /* 1-based, max priv->cdef->channels */
+ struct is31fl32xx_priv *priv;
+};
+
+struct is31fl32xx_priv {
+ const struct is31fl32xx_chipdef *cdef;
+ struct i2c_client *client;
+ unsigned int num_leds;
+ struct is31fl32xx_led_data leds[];
+};
+
+/**
+ * struct is31fl32xx_chipdef - chip-specific attributes
+ * @channels : Number of LED channels
+ * @shutdown_reg : address of Shutdown register (optional)
+ * @pwm_update_reg : address of PWM Update register
+ * @global_control_reg : address of Global Control register (optional)
+ * @reset_reg : address of Reset register (optional)
+ * @output_frequency_setting_reg: address of output frequency register (optional)
+ * @pwm_register_base : address of first PWM register
+ * @pwm_registers_reversed: : true if PWM registers count down instead of up
+ * @led_control_register_base : address of first LED control register (optional)
+ * @enable_bits_per_led_control_register: number of LEDs enable bits in each
+ * @reset_func : pointer to reset function
+ * @sw_shutdown_func : pointer to software shutdown function
+ *
+ * For all optional register addresses, the sentinel value %IS31FL32XX_REG_NONE
+ * indicates that this chip has no such register.
+ *
+ * If non-NULL, @reset_func will be called during probing to set all
+ * necessary registers to a known initialization state. This is needed
+ * for chips that do not have a @reset_reg.
+ *
+ * @enable_bits_per_led_control_register must be >=1 if
+ * @led_control_register_base != %IS31FL32XX_REG_NONE.
+ */
+struct is31fl32xx_chipdef {
+ u8 channels;
+ u8 shutdown_reg;
+ u8 pwm_update_reg;
+ u8 global_control_reg;
+ u8 reset_reg;
+ u8 output_frequency_setting_reg;
+ u8 pwm_register_base;
+ bool pwm_registers_reversed;
+ u8 led_control_register_base;
+ u8 enable_bits_per_led_control_register;
+ int (*reset_func)(struct is31fl32xx_priv *priv);
+ int (*sw_shutdown_func)(struct is31fl32xx_priv *priv, bool enable);
+};
+
+static const struct is31fl32xx_chipdef is31fl3236_cdef = {
+ .channels = 36,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x26,
+ .enable_bits_per_led_control_register = 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3236a_cdef = {
+ .channels = 36,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = 0x4b,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x26,
+ .enable_bits_per_led_control_register = 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3235_cdef = {
+ .channels = 28,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x25,
+ .global_control_reg = 0x4a,
+ .reset_reg = 0x4f,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x05,
+ .led_control_register_base = 0x2a,
+ .enable_bits_per_led_control_register = 1,
+};
+
+static const struct is31fl32xx_chipdef is31fl3218_cdef = {
+ .channels = 18,
+ .shutdown_reg = 0x00,
+ .pwm_update_reg = 0x16,
+ .global_control_reg = IS31FL32XX_REG_NONE,
+ .reset_reg = 0x17,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x01,
+ .led_control_register_base = 0x13,
+ .enable_bits_per_led_control_register = 6,
+};
+
+static int is31fl3216_reset(struct is31fl32xx_priv *priv);
+static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,
+ bool enable);
+static const struct is31fl32xx_chipdef is31fl3216_cdef = {
+ .channels = 16,
+ .shutdown_reg = IS31FL32XX_REG_NONE,
+ .pwm_update_reg = 0xB0,
+ .global_control_reg = IS31FL32XX_REG_NONE,
+ .reset_reg = IS31FL32XX_REG_NONE,
+ .output_frequency_setting_reg = IS31FL32XX_REG_NONE,
+ .pwm_register_base = 0x10,
+ .pwm_registers_reversed = true,
+ .led_control_register_base = 0x01,
+ .enable_bits_per_led_control_register = 8,
+ .reset_func = is31fl3216_reset,
+ .sw_shutdown_func = is31fl3216_software_shutdown,
+};
+
+static int is31fl32xx_write(struct is31fl32xx_priv *priv, u8 reg, u8 val)
+{
+ int ret;
+
+ dev_dbg(&priv->client->dev, "writing register 0x%02X=0x%02X", reg, val);
+
+ ret = i2c_smbus_write_byte_data(priv->client, reg, val);
+ if (ret) {
+ dev_err(&priv->client->dev,
+ "register write to 0x%02X failed (error %d)",
+ reg, ret);
+ }
+ return ret;
+}
+
+/*
+ * Custom reset function for IS31FL3216 because it does not have a RESET
+ * register the way that the other IS31FL32xx chips do. We don't bother
+ * writing the GPIO and animation registers, because the registers we
+ * do write ensure those will have no effect.
+ */
+static int is31fl3216_reset(struct is31fl32xx_priv *priv)
+{
+ unsigned int i;
+ int ret;
+
+ ret = is31fl32xx_write(priv, IS31FL3216_CONFIG_REG,
+ IS31FL3216_CONFIG_SSD_ENABLE);
+ if (ret)
+ return ret;
+ for (i = 0; i < priv->cdef->channels; i++) {
+ ret = is31fl32xx_write(priv, priv->cdef->pwm_register_base+i,
+ 0x00);
+ if (ret)
+ return ret;
+ }
+ ret = is31fl32xx_write(priv, priv->cdef->pwm_update_reg, 0);
+ if (ret)
+ return ret;
+ ret = is31fl32xx_write(priv, IS31FL3216_LIGHTING_EFFECT_REG, 0x00);
+ if (ret)
+ return ret;
+ ret = is31fl32xx_write(priv, IS31FL3216_CHANNEL_CONFIG_REG, 0x00);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+/*
+ * Custom Software-Shutdown function for IS31FL3216 because it does not have
+ * a SHUTDOWN register the way that the other IS31FL32xx chips do.
+ * We don't bother doing a read/modify/write on the CONFIG register because
+ * we only ever use a value of '0' for the other fields in that register.
+ */
+static int is31fl3216_software_shutdown(struct is31fl32xx_priv *priv,
+ bool enable)
+{
+ u8 value = enable ? IS31FL3216_CONFIG_SSD_ENABLE :
+ IS31FL3216_CONFIG_SSD_DISABLE;
+
+ return is31fl32xx_write(priv, IS31FL3216_CONFIG_REG, value);
+}
+
+/*
+ * NOTE: A mutex is not needed in this function because:
+ * - All referenced data is read-only after probe()
+ * - The I2C core has a mutex on to protect the bus
+ * - There are no read/modify/write operations
+ * - Intervening operations between the write of the PWM register
+ * and the Update register are harmless.
+ *
+ * Example:
+ * PWM_REG_1 write 16
+ * UPDATE_REG write 0
+ * PWM_REG_2 write 128
+ * UPDATE_REG write 0
+ * vs:
+ * PWM_REG_1 write 16
+ * PWM_REG_2 write 128
+ * UPDATE_REG write 0
+ * UPDATE_REG write 0
+ * are equivalent. Poking the Update register merely applies all PWM
+ * register writes up to that point.
+ */
+static int is31fl32xx_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ const struct is31fl32xx_led_data *led_data =
+ container_of(led_cdev, struct is31fl32xx_led_data, cdev);
+ const struct is31fl32xx_chipdef *cdef = led_data->priv->cdef;
+ u8 pwm_register_offset;
+ int ret;
+
+ dev_dbg(led_cdev->dev, "%s: %d\n", __func__, brightness);
+
+ /* NOTE: led_data->channel is 1-based */
+ if (cdef->pwm_registers_reversed)
+ pwm_register_offset = cdef->channels - led_data->channel;
+ else
+ pwm_register_offset = led_data->channel - 1;
+
+ ret = is31fl32xx_write(led_data->priv,
+ cdef->pwm_register_base + pwm_register_offset,
+ brightness);
+ if (ret)
+ return ret;
+
+ return is31fl32xx_write(led_data->priv, cdef->pwm_update_reg, 0);
+}
+
+static int is31fl32xx_reset_regs(struct is31fl32xx_priv *priv)
+{
+ const struct is31fl32xx_chipdef *cdef = priv->cdef;
+ int ret;
+
+ if (cdef->reset_reg != IS31FL32XX_REG_NONE) {
+ ret = is31fl32xx_write(priv, cdef->reset_reg, 0);
+ if (ret)
+ return ret;
+ }
+
+ if (cdef->reset_func)
+ return cdef->reset_func(priv);
+
+ return 0;
+}
+
+static int is31fl32xx_software_shutdown(struct is31fl32xx_priv *priv,
+ bool enable)
+{
+ const struct is31fl32xx_chipdef *cdef = priv->cdef;
+ int ret;
+
+ if (cdef->shutdown_reg != IS31FL32XX_REG_NONE) {
+ u8 value = enable ? IS31FL32XX_SHUTDOWN_SSD_ENABLE :
+ IS31FL32XX_SHUTDOWN_SSD_DISABLE;
+ ret = is31fl32xx_write(priv, cdef->shutdown_reg, value);
+ if (ret)
+ return ret;
+ }
+
+ if (cdef->sw_shutdown_func)
+ return cdef->sw_shutdown_func(priv, enable);
+
+ return 0;
+}
+
+static int is31fl32xx_init_regs(struct is31fl32xx_priv *priv)
+{
+ const struct is31fl32xx_chipdef *cdef = priv->cdef;
+ int ret;
+
+ ret = is31fl32xx_reset_regs(priv);
+ if (ret)
+ return ret;
+
+ /*
+ * Set enable bit for all channels.
+ * We will control state with PWM registers alone.
+ */
+ if (cdef->led_control_register_base != IS31FL32XX_REG_NONE) {
+ u8 value =
+ GENMASK(cdef->enable_bits_per_led_control_register-1, 0);
+ u8 num_regs = cdef->channels /
+ cdef->enable_bits_per_led_control_register;
+ int i;
+
+ for (i = 0; i < num_regs; i++) {
+ ret = is31fl32xx_write(priv,
+ cdef->led_control_register_base+i,
+ value);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = is31fl32xx_software_shutdown(priv, false);
+ if (ret)
+ return ret;
+
+ if (cdef->global_control_reg != IS31FL32XX_REG_NONE) {
+ ret = is31fl32xx_write(priv, cdef->global_control_reg, 0x00);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int is31fl32xx_parse_child_dt(const struct device *dev,
+ const struct device_node *child,
+ struct is31fl32xx_led_data *led_data)
+{
+ struct led_classdev *cdev = &led_data->cdev;
+ int ret = 0;
+ u32 reg;
+
+ ret = of_property_read_u32(child, "reg", &reg);
+ if (ret || reg < 1 || reg > led_data->priv->cdef->channels) {
+ dev_err(dev,
+ "Child node %pOF does not have a valid reg property\n",
+ child);
+ return -EINVAL;
+ }
+ led_data->channel = reg;
+
+ cdev->brightness_set_blocking = is31fl32xx_brightness_set;
+
+ return 0;
+}
+
+static struct is31fl32xx_led_data *is31fl32xx_find_led_data(
+ struct is31fl32xx_priv *priv,
+ u8 channel)
+{
+ size_t i;
+
+ for (i = 0; i < priv->num_leds; i++) {
+ if (priv->leds[i].channel == channel)
+ return &priv->leds[i];
+ }
+
+ return NULL;
+}
+
+static int is31fl32xx_parse_dt(struct device *dev,
+ struct is31fl32xx_priv *priv)
+{
+ const struct is31fl32xx_chipdef *cdef = priv->cdef;
+ int ret = 0;
+
+ if ((cdef->output_frequency_setting_reg != IS31FL32XX_REG_NONE) &&
+ of_property_read_bool(dev_of_node(dev), "issi,22khz-pwm")) {
+
+ ret = is31fl32xx_write(priv, cdef->output_frequency_setting_reg,
+ IS31FL32XX_PWM_FREQUENCY_22KHZ);
+
+ if (ret) {
+ dev_err(dev, "Failed to write output PWM frequency register\n");
+ return ret;
+ }
+ }
+
+ for_each_available_child_of_node_scoped(dev_of_node(dev), child) {
+ struct led_init_data init_data = {};
+ struct is31fl32xx_led_data *led_data =
+ &priv->leds[priv->num_leds];
+ const struct is31fl32xx_led_data *other_led_data;
+
+ led_data->priv = priv;
+
+ ret = is31fl32xx_parse_child_dt(dev, child, led_data);
+ if (ret)
+ return ret;
+
+ /* Detect if channel is already in use by another child */
+ other_led_data = is31fl32xx_find_led_data(priv,
+ led_data->channel);
+ if (other_led_data) {
+ dev_err(dev,
+ "Node %pOF 'reg' conflicts with another LED\n",
+ child);
+ return -EINVAL;
+ }
+
+ init_data.fwnode = of_fwnode_handle(child);
+
+ ret = devm_led_classdev_register_ext(dev, &led_data->cdev,
+ &init_data);
+ if (ret) {
+ dev_err(dev, "Failed to register LED for %pOF: %d\n",
+ child, ret);
+ return ret;
+ }
+
+ priv->num_leds++;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_is31fl32xx_match[] = {
+ { .compatible = "issi,is31fl3236", .data = &is31fl3236_cdef, },
+ { .compatible = "issi,is31fl3236a", .data = &is31fl3236a_cdef, },
+ { .compatible = "issi,is31fl3235", .data = &is31fl3235_cdef, },
+ { .compatible = "issi,is31fl3218", .data = &is31fl3218_cdef, },
+ { .compatible = "si-en,sn3218", .data = &is31fl3218_cdef, },
+ { .compatible = "issi,is31fl3216", .data = &is31fl3216_cdef, },
+ { .compatible = "si-en,sn3216", .data = &is31fl3216_cdef, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_is31fl32xx_match);
+
+static int is31fl32xx_probe(struct i2c_client *client)
+{
+ const struct is31fl32xx_chipdef *cdef;
+ struct device *dev = &client->dev;
+ struct is31fl32xx_priv *priv;
+ int count;
+ int ret = 0;
+
+ cdef = device_get_match_data(dev);
+
+ count = of_get_available_child_count(dev_of_node(dev));
+ if (!count)
+ return -EINVAL;
+
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+ priv->cdef = cdef;
+ i2c_set_clientdata(client, priv);
+
+ ret = is31fl32xx_init_regs(priv);
+ if (ret)
+ return ret;
+
+ ret = is31fl32xx_parse_dt(dev, priv);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void is31fl32xx_remove(struct i2c_client *client)
+{
+ struct is31fl32xx_priv *priv = i2c_get_clientdata(client);
+ int ret;
+
+ ret = is31fl32xx_reset_regs(priv);
+ if (ret)
+ dev_err(&client->dev, "Failed to reset registers on removal (%pe)\n",
+ ERR_PTR(ret));
+}
+
+/*
+ * i2c-core (and modalias) requires that id_table be properly filled,
+ * even though it is not used for DeviceTree based instantiation.
+ */
+static const struct i2c_device_id is31fl32xx_id[] = {
+ { "is31fl3236" },
+ { "is31fl3236a" },
+ { "is31fl3235" },
+ { "is31fl3218" },
+ { "sn3218" },
+ { "is31fl3216" },
+ { "sn3216" },
+ {},
+};
+
+MODULE_DEVICE_TABLE(i2c, is31fl32xx_id);
+
+static struct i2c_driver is31fl32xx_driver = {
+ .driver = {
+ .name = "is31fl32xx",
+ .of_match_table = of_is31fl32xx_match,
+ },
+ .probe = is31fl32xx_probe,
+ .remove = is31fl32xx_remove,
+ .id_table = is31fl32xx_id,
+};
+
+module_i2c_driver(is31fl32xx_driver);
+
+MODULE_AUTHOR("David Rivshin <drivshin@allworx.com>");
+MODULE_DESCRIPTION("ISSI IS31FL32xx LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c
index a036a19040fe..e44a3db106c3 100644
--- a/drivers/leds/leds-lm3530.c
+++ b/drivers/leds/leds-lm3530.c
@@ -1,9 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2011 ST-Ericsson SA.
* Copyright (C) 2009 Motorola, Inc.
*
- * License Terms: GNU General Public License v2
- *
* Simple driver for National Semiconductor LM3530 Backlight driver chip
*
* Author: Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>
@@ -100,7 +99,7 @@ static struct lm3530_mode_map mode_map[] = {
* @pdata: LM3530 platform data
* @mode: mode of operation - manual, ALS, PWM
* @regulator: regulator
- * @brighness: previous brightness value
+ * @brightness: previous brightness value
* @enable: regulator is enabled
*/
struct lm3530_data {
@@ -347,8 +346,8 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev,
}
}
-static ssize_t lm3530_mode_get(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lm3530_data *drvdata;
@@ -366,8 +365,8 @@ static ssize_t lm3530_mode_get(struct device *dev,
return len;
}
-static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute
- *attr, const char *buf, size_t size)
+static ssize_t mode_store(struct device *dev, struct device_attribute
+ *attr, const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lm3530_data *drvdata;
@@ -398,12 +397,17 @@ static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute
return sizeof(drvdata->mode);
}
-static DEVICE_ATTR(mode, 0644, lm3530_mode_get, lm3530_mode_set);
+static DEVICE_ATTR_RW(mode);
+
+static struct attribute *lm3530_attrs[] = {
+ &dev_attr_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(lm3530);
-static int lm3530_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int lm3530_probe(struct i2c_client *client)
{
- struct lm3530_platform_data *pdata = client->dev.platform_data;
+ struct lm3530_platform_data *pdata = dev_get_platdata(&client->dev);
struct lm3530_data *drvdata;
int err = 0;
@@ -436,6 +440,7 @@ static int lm3530_probe(struct i2c_client *client,
drvdata->led_dev.name = LM3530_LED_DEV;
drvdata->led_dev.brightness_set = lm3530_brightness_set;
drvdata->led_dev.max_brightness = MAX_BRIGHTNESS;
+ drvdata->led_dev.groups = lm3530_groups;
i2c_set_clientdata(client, drvdata);
@@ -461,33 +466,19 @@ static int lm3530_probe(struct i2c_client *client,
return err;
}
- err = device_create_file(drvdata->led_dev.dev, &dev_attr_mode);
- if (err < 0) {
- dev_err(&client->dev, "File device creation failed: %d\n", err);
- err = -ENODEV;
- goto err_create_file;
- }
-
return 0;
-
-err_create_file:
- led_classdev_unregister(&drvdata->led_dev);
- return err;
}
-static int lm3530_remove(struct i2c_client *client)
+static void lm3530_remove(struct i2c_client *client)
{
struct lm3530_data *drvdata = i2c_get_clientdata(client);
- device_remove_file(drvdata->led_dev.dev, &dev_attr_mode);
-
lm3530_led_disable(drvdata);
led_classdev_unregister(&drvdata->led_dev);
- return 0;
}
static const struct i2c_device_id lm3530_id[] = {
- {LM3530_NAME, 0},
+ { LM3530_NAME },
{}
};
MODULE_DEVICE_TABLE(i2c, lm3530_id);
@@ -498,7 +489,6 @@ static struct i2c_driver lm3530_i2c_driver = {
.id_table = lm3530_id,
.driver = {
.name = LM3530_NAME,
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-lm3532.c b/drivers/leds/leds-lm3532.c
new file mode 100644
index 000000000000..24dc8ad27bb3
--- /dev/null
+++ b/drivers/leds/leds-lm3532.c
@@ -0,0 +1,742 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LM3532 LED driver
+// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
+// https://www.ti.com/lit/ds/symlink/lm3532.pdf
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <uapi/linux/uleds.h>
+#include <linux/gpio/consumer.h>
+
+#define LM3532_NAME "lm3532-led"
+#define LM3532_BL_MODE_MANUAL 0x00
+#define LM3532_BL_MODE_ALS 0x01
+
+#define LM3532_REG_OUTPUT_CFG 0x10
+#define LM3532_REG_STARTSHUT_RAMP 0x11
+#define LM3532_REG_RT_RAMP 0x12
+#define LM3532_REG_PWM_A_CFG 0x13
+#define LM3532_REG_PWM_B_CFG 0x14
+#define LM3532_REG_PWM_C_CFG 0x15
+#define LM3532_REG_ZONE_CFG_A 0x16
+#define LM3532_REG_CTRL_A_FS_CURR 0x17
+#define LM3532_REG_ZONE_CFG_B 0x18
+#define LM3532_REG_CTRL_B_FS_CURR 0x19
+#define LM3532_REG_ZONE_CFG_C 0x1a
+#define LM3532_REG_CTRL_C_FS_CURR 0x1b
+#define LM3532_REG_ENABLE 0x1d
+#define LM3532_ALS_CONFIG 0x23
+#define LM3532_REG_ZN_0_HI 0x60
+#define LM3532_REG_ZN_0_LO 0x61
+#define LM3532_REG_ZN_1_HI 0x62
+#define LM3532_REG_ZN_1_LO 0x63
+#define LM3532_REG_ZN_2_HI 0x64
+#define LM3532_REG_ZN_2_LO 0x65
+#define LM3532_REG_ZN_3_HI 0x66
+#define LM3532_REG_ZN_3_LO 0x67
+#define LM3532_REG_ZONE_TRGT_A 0x70
+#define LM3532_REG_ZONE_TRGT_B 0x75
+#define LM3532_REG_ZONE_TRGT_C 0x7a
+#define LM3532_REG_MAX 0x7e
+
+/* Control Enable */
+#define LM3532_CTRL_A_ENABLE BIT(0)
+#define LM3532_CTRL_B_ENABLE BIT(1)
+#define LM3532_CTRL_C_ENABLE BIT(2)
+
+/* PWM Zone Control */
+#define LM3532_PWM_ZONE_MASK 0x7c
+#define LM3532_PWM_ZONE_0_EN BIT(2)
+#define LM3532_PWM_ZONE_1_EN BIT(3)
+#define LM3532_PWM_ZONE_2_EN BIT(4)
+#define LM3532_PWM_ZONE_3_EN BIT(5)
+#define LM3532_PWM_ZONE_4_EN BIT(6)
+
+/* Brightness Configuration */
+#define LM3532_I2C_CTRL BIT(0)
+#define LM3532_ALS_CTRL 0
+#define LM3532_LINEAR_MAP BIT(1)
+#define LM3532_ZONE_MASK (BIT(2) | BIT(3) | BIT(4))
+#define LM3532_ZONE_0 0
+#define LM3532_ZONE_1 BIT(2)
+#define LM3532_ZONE_2 BIT(3)
+#define LM3532_ZONE_3 (BIT(2) | BIT(3))
+#define LM3532_ZONE_4 BIT(4)
+
+#define LM3532_ENABLE_ALS BIT(3)
+#define LM3532_ALS_SEL_SHIFT 6
+
+/* Zone Boundary Register */
+#define LM3532_ALS_WINDOW_mV 2000
+#define LM3532_ALS_ZB_MAX 4
+#define LM3532_ALS_OFFSET_mV 2
+
+#define LM3532_CONTROL_A 0
+#define LM3532_CONTROL_B 1
+#define LM3532_CONTROL_C 2
+#define LM3532_MAX_CONTROL_BANKS 3
+#define LM3532_MAX_LED_STRINGS 3
+
+#define LM3532_OUTPUT_CFG_MASK 0x3
+#define LM3532_BRT_VAL_ADJUST 8
+#define LM3532_RAMP_DOWN_SHIFT 3
+
+#define LM3532_NUM_RAMP_VALS 8
+#define LM3532_NUM_AVG_VALS 8
+#define LM3532_NUM_IMP_VALS 32
+
+#define LM3532_FS_CURR_MIN 5000
+#define LM3532_FS_CURR_MAX 29800
+#define LM3532_FS_CURR_STEP 800
+
+/*
+ * struct lm3532_als_data
+ * @config: value of ALS configuration register
+ * @als1_imp_sel: value of ALS1 resistor select register
+ * @als2_imp_sel: value of ALS2 resistor select register
+ * @als_avrg_time: ALS averaging time
+ * @als_input_mode: ALS input mode for brightness control
+ * @als_vmin: Minimum ALS voltage
+ * @als_vmax: Maximum ALS voltage
+ * @zone_lo: values of ALS lo ZB(Zone Boundary) registers
+ * @zone_hi: values of ALS hi ZB(Zone Boundary) registers
+ */
+struct lm3532_als_data {
+ u8 config;
+ u8 als1_imp_sel;
+ u8 als2_imp_sel;
+ u8 als_avrg_time;
+ u8 als_input_mode;
+ u32 als_vmin;
+ u32 als_vmax;
+ u8 zones_lo[LM3532_ALS_ZB_MAX];
+ u8 zones_hi[LM3532_ALS_ZB_MAX];
+};
+
+/**
+ * struct lm3532_led
+ * @led_dev: led class device
+ * @priv: Pointer the device data structure
+ * @control_bank: Control bank the LED is associated to
+ * @mode: Mode of the LED string
+ * @ctrl_brt_pointer: Zone target register that controls the sink
+ * @num_leds: Number of LED strings are supported in this array
+ * @full_scale_current: The full-scale current setting for the current sink.
+ * @led_strings: The LED strings supported in this array
+ * @enabled: Enabled status
+ */
+struct lm3532_led {
+ struct led_classdev led_dev;
+ struct lm3532_data *priv;
+
+ int control_bank;
+ int mode;
+ int ctrl_brt_pointer;
+ int num_leds;
+ int full_scale_current;
+ unsigned int enabled:1;
+ u32 led_strings[LM3532_MAX_CONTROL_BANKS];
+};
+
+/**
+ * struct lm3532_data
+ * @enable_gpio: Hardware enable gpio
+ * @regulator: regulator
+ * @client: i2c client
+ * @regmap: Devices register map
+ * @dev: Pointer to the devices device struct
+ * @lock: Lock for reading/writing the device
+ * @als_data: Pointer to the als data struct
+ * @runtime_ramp_up: Runtime ramp up setting
+ * @runtime_ramp_down: Runtime ramp down setting
+ * @leds: Array of LED strings
+ */
+struct lm3532_data {
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+
+ struct lm3532_als_data *als_data;
+
+ u32 runtime_ramp_up;
+ u32 runtime_ramp_down;
+
+ struct lm3532_led leds[];
+};
+
+static const struct reg_default lm3532_reg_defs[] = {
+ {LM3532_REG_OUTPUT_CFG, 0xe4},
+ {LM3532_REG_STARTSHUT_RAMP, 0xc0},
+ {LM3532_REG_RT_RAMP, 0xc0},
+ {LM3532_REG_PWM_A_CFG, 0x82},
+ {LM3532_REG_PWM_B_CFG, 0x82},
+ {LM3532_REG_PWM_C_CFG, 0x82},
+ {LM3532_REG_ZONE_CFG_A, 0xf1},
+ {LM3532_REG_CTRL_A_FS_CURR, 0xf3},
+ {LM3532_REG_ZONE_CFG_B, 0xf1},
+ {LM3532_REG_CTRL_B_FS_CURR, 0xf3},
+ {LM3532_REG_ZONE_CFG_C, 0xf1},
+ {LM3532_REG_CTRL_C_FS_CURR, 0xf3},
+ {LM3532_REG_ENABLE, 0xf8},
+ {LM3532_ALS_CONFIG, 0x44},
+ {LM3532_REG_ZN_0_HI, 0x35},
+ {LM3532_REG_ZN_0_LO, 0x33},
+ {LM3532_REG_ZN_1_HI, 0x6a},
+ {LM3532_REG_ZN_1_LO, 0x66},
+ {LM3532_REG_ZN_2_HI, 0xa1},
+ {LM3532_REG_ZN_2_LO, 0x99},
+ {LM3532_REG_ZN_3_HI, 0xdc},
+ {LM3532_REG_ZN_3_LO, 0xcc},
+};
+
+static const struct regmap_config lm3532_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LM3532_REG_MAX,
+ .reg_defaults = lm3532_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lm3532_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const int als_imp_table[LM3532_NUM_IMP_VALS] = {37000, 18500, 12330,
+ 92500, 7400, 6170, 5290,
+ 4630, 4110, 3700, 3360,
+ 3080, 2850, 2640, 2440,
+ 2310, 2180, 2060, 1950,
+ 1850, 1760, 1680, 1610,
+ 1540, 1480, 1420, 1370,
+ 1320, 1280, 1230, 1190};
+static int lm3532_get_als_imp_index(int als_imped)
+{
+ int i;
+
+ if (als_imped > als_imp_table[1])
+ return 0;
+
+ if (als_imped < als_imp_table[LM3532_NUM_IMP_VALS - 1])
+ return LM3532_NUM_IMP_VALS - 1;
+
+ for (i = 1; i < LM3532_NUM_IMP_VALS; i++) {
+ if (als_imped == als_imp_table[i])
+ return i;
+
+ /* Find an approximate index by looking up the table */
+ if (als_imped < als_imp_table[i - 1] &&
+ als_imped > als_imp_table[i]) {
+ if (als_imped - als_imp_table[i - 1] <
+ als_imp_table[i] - als_imped)
+ return i + 1;
+ else
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int lm3532_get_index(const int table[], int size, int value)
+{
+ int i;
+
+ for (i = 1; i < size; i++) {
+ if (value == table[i])
+ return i;
+
+ /* Find an approximate index by looking up the table */
+ if (value > table[i - 1] &&
+ value < table[i]) {
+ if (value - table[i - 1] < table[i] - value)
+ return i - 1;
+ else
+ return i;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static const int als_avrg_table[LM3532_NUM_AVG_VALS] = {17920, 35840, 71680,
+ 1433360, 286720, 573440,
+ 1146880, 2293760};
+static int lm3532_get_als_avg_index(int avg_time)
+{
+ if (avg_time <= als_avrg_table[0])
+ return 0;
+
+ if (avg_time > als_avrg_table[LM3532_NUM_AVG_VALS - 1])
+ return LM3532_NUM_AVG_VALS - 1;
+
+ return lm3532_get_index(&als_avrg_table[0], LM3532_NUM_AVG_VALS,
+ avg_time);
+}
+
+static const int ramp_table[LM3532_NUM_RAMP_VALS] = { 8, 1024, 2048, 4096, 8192,
+ 16384, 32768, 65536};
+static int lm3532_get_ramp_index(int ramp_time)
+{
+ if (ramp_time <= ramp_table[0])
+ return 0;
+
+ if (ramp_time > ramp_table[LM3532_NUM_RAMP_VALS - 1])
+ return LM3532_NUM_RAMP_VALS - 1;
+
+ return lm3532_get_index(&ramp_table[0], LM3532_NUM_RAMP_VALS,
+ ramp_time);
+}
+
+/* Caller must take care of locking */
+static int lm3532_led_enable(struct lm3532_led *led_data)
+{
+ int ctrl_en_val = BIT(led_data->control_bank);
+ int ret;
+
+ if (led_data->enabled)
+ return 0;
+
+ ret = regmap_update_bits(led_data->priv->regmap, LM3532_REG_ENABLE,
+ ctrl_en_val, ctrl_en_val);
+ if (ret) {
+ dev_err(led_data->priv->dev, "Failed to set ctrl:%d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_enable(led_data->priv->regulator);
+ if (ret < 0)
+ return ret;
+
+ led_data->enabled = 1;
+
+ return 0;
+}
+
+/* Caller must take care of locking */
+static int lm3532_led_disable(struct lm3532_led *led_data)
+{
+ int ctrl_en_val = BIT(led_data->control_bank);
+ int ret;
+
+ if (!led_data->enabled)
+ return 0;
+
+ ret = regmap_update_bits(led_data->priv->regmap, LM3532_REG_ENABLE,
+ ctrl_en_val, 0);
+ if (ret) {
+ dev_err(led_data->priv->dev, "Failed to set ctrl:%d\n", ret);
+ return ret;
+ }
+
+ ret = regulator_disable(led_data->priv->regulator);
+ if (ret < 0)
+ return ret;
+
+ led_data->enabled = 0;
+
+ return 0;
+}
+
+static int lm3532_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lm3532_led *led =
+ container_of(led_cdev, struct lm3532_led, led_dev);
+ u8 brightness_reg;
+ int ret;
+
+ mutex_lock(&led->priv->lock);
+
+ if (led->mode == LM3532_ALS_CTRL) {
+ if (brt_val > LED_OFF)
+ ret = lm3532_led_enable(led);
+ else
+ ret = lm3532_led_disable(led);
+
+ goto unlock;
+ }
+
+ if (brt_val == LED_OFF) {
+ ret = lm3532_led_disable(led);
+ goto unlock;
+ }
+
+ ret = lm3532_led_enable(led);
+ if (ret)
+ goto unlock;
+
+ brightness_reg = LM3532_REG_ZONE_TRGT_A + led->control_bank * 5 +
+ (led->ctrl_brt_pointer >> 2);
+
+ ret = regmap_write(led->priv->regmap, brightness_reg, brt_val);
+
+unlock:
+ mutex_unlock(&led->priv->lock);
+ return ret;
+}
+
+static int lm3532_init_registers(struct lm3532_led *led)
+{
+ struct lm3532_data *drvdata = led->priv;
+ unsigned int runtime_ramp_val;
+ unsigned int output_cfg_val = 0;
+ unsigned int output_cfg_shift = 0;
+ unsigned int output_cfg_mask = 0;
+ unsigned int brightness_config_reg;
+ unsigned int brightness_config_val;
+ int fs_current_reg;
+ int fs_current_val;
+ int ret, i;
+
+ if (drvdata->enable_gpio)
+ gpiod_direction_output(drvdata->enable_gpio, 1);
+
+ brightness_config_reg = LM3532_REG_ZONE_CFG_A + led->control_bank * 2;
+ /*
+ * This could be hard coded to the default value but the control
+ * brightness register may have changed during boot.
+ */
+ ret = regmap_read(drvdata->regmap, brightness_config_reg,
+ &led->ctrl_brt_pointer);
+ if (ret)
+ return ret;
+
+ led->ctrl_brt_pointer &= LM3532_ZONE_MASK;
+ brightness_config_val = led->ctrl_brt_pointer | led->mode;
+ ret = regmap_write(drvdata->regmap, brightness_config_reg,
+ brightness_config_val);
+ if (ret)
+ return ret;
+
+ if (led->full_scale_current) {
+ fs_current_reg = LM3532_REG_CTRL_A_FS_CURR + led->control_bank * 2;
+ fs_current_val = (led->full_scale_current - LM3532_FS_CURR_MIN) /
+ LM3532_FS_CURR_STEP;
+
+ ret = regmap_write(drvdata->regmap, fs_current_reg,
+ fs_current_val);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < led->num_leds; i++) {
+ output_cfg_shift = led->led_strings[i] * 2;
+ output_cfg_val |= (led->control_bank << output_cfg_shift);
+ output_cfg_mask |= LM3532_OUTPUT_CFG_MASK << output_cfg_shift;
+ }
+
+ ret = regmap_update_bits(drvdata->regmap, LM3532_REG_OUTPUT_CFG,
+ output_cfg_mask, output_cfg_val);
+ if (ret)
+ return ret;
+
+ runtime_ramp_val = drvdata->runtime_ramp_up |
+ (drvdata->runtime_ramp_down << LM3532_RAMP_DOWN_SHIFT);
+
+ return regmap_write(drvdata->regmap, LM3532_REG_RT_RAMP,
+ runtime_ramp_val);
+}
+
+static int lm3532_als_configure(struct lm3532_data *priv,
+ struct lm3532_led *led)
+{
+ struct lm3532_als_data *als = priv->als_data;
+ u32 als_vmin, als_vmax, als_vstep;
+ int zone_reg = LM3532_REG_ZN_0_HI;
+ int ret;
+ int i;
+
+ als_vmin = als->als_vmin;
+ als_vmax = als->als_vmax;
+
+ als_vstep = (als_vmax - als_vmin) / ((LM3532_ALS_ZB_MAX + 1) * 2);
+
+ for (i = 0; i < LM3532_ALS_ZB_MAX; i++) {
+ als->zones_lo[i] = ((als_vmin + als_vstep + (i * als_vstep)) *
+ LED_FULL) / 1000;
+ als->zones_hi[i] = ((als_vmin + LM3532_ALS_OFFSET_mV +
+ als_vstep + (i * als_vstep)) * LED_FULL) / 1000;
+
+ zone_reg = LM3532_REG_ZN_0_HI + i * 2;
+ ret = regmap_write(priv->regmap, zone_reg, als->zones_lo[i]);
+ if (ret)
+ return ret;
+
+ zone_reg += 1;
+ ret = regmap_write(priv->regmap, zone_reg, als->zones_hi[i]);
+ if (ret)
+ return ret;
+ }
+
+ als->config = (als->als_avrg_time | (LM3532_ENABLE_ALS) |
+ (als->als_input_mode << LM3532_ALS_SEL_SHIFT));
+
+ return regmap_write(priv->regmap, LM3532_ALS_CONFIG, als->config);
+}
+
+static int lm3532_parse_als(struct lm3532_data *priv)
+{
+ struct lm3532_als_data *als;
+ int als_avg_time;
+ int als_impedance;
+ int ret;
+
+ als = devm_kzalloc(priv->dev, sizeof(*als), GFP_KERNEL);
+ if (als == NULL)
+ return -ENOMEM;
+
+ ret = device_property_read_u32(&priv->client->dev, "ti,als-vmin",
+ &als->als_vmin);
+ if (ret)
+ als->als_vmin = 0;
+
+ ret = device_property_read_u32(&priv->client->dev, "ti,als-vmax",
+ &als->als_vmax);
+ if (ret)
+ als->als_vmax = LM3532_ALS_WINDOW_mV;
+
+ if (als->als_vmax > LM3532_ALS_WINDOW_mV) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ ret = device_property_read_u32(&priv->client->dev, "ti,als1-imp-sel",
+ &als_impedance);
+ if (ret)
+ als->als1_imp_sel = 0;
+ else
+ als->als1_imp_sel = lm3532_get_als_imp_index(als_impedance);
+
+ ret = device_property_read_u32(&priv->client->dev, "ti,als2-imp-sel",
+ &als_impedance);
+ if (ret)
+ als->als2_imp_sel = 0;
+ else
+ als->als2_imp_sel = lm3532_get_als_imp_index(als_impedance);
+
+ ret = device_property_read_u32(&priv->client->dev, "ti,als-avrg-time-us",
+ &als_avg_time);
+ if (ret)
+ als->als_avrg_time = 0;
+ else
+ als->als_avrg_time = lm3532_get_als_avg_index(als_avg_time);
+
+ ret = device_property_read_u8(&priv->client->dev, "ti,als-input-mode",
+ &als->als_input_mode);
+ if (ret)
+ als->als_input_mode = 0;
+
+ if (als->als_input_mode > LM3532_BL_MODE_ALS) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ priv->als_data = als;
+
+ 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 lm3532_led *led;
+ int control_bank;
+ u32 ramp_time;
+ size_t i = 0;
+ int ret;
+
+ priv->enable_gpio = devm_gpiod_get_optional(&priv->client->dev,
+ "enable", GPIOD_OUT_LOW);
+ 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;
+
+ ret = device_property_read_u32(&priv->client->dev, "ramp-up-us",
+ &ramp_time);
+ if (ret)
+ dev_info(&priv->client->dev, "ramp-up-ms property missing\n");
+ else
+ priv->runtime_ramp_up = lm3532_get_ramp_index(ramp_time);
+
+ ret = device_property_read_u32(&priv->client->dev, "ramp-down-us",
+ &ramp_time);
+ if (ret)
+ dev_info(&priv->client->dev, "ramp-down-ms property missing\n");
+ else
+ priv->runtime_ramp_down = lm3532_get_ramp_index(ramp_time);
+
+ device_for_each_child_node_scoped(priv->dev, child) {
+ struct led_init_data idata = {
+ .fwnode = child,
+ .default_label = ":",
+ .devicename = priv->client->name,
+ };
+
+ led = &priv->leds[i];
+
+ ret = fwnode_property_read_u32(child, "reg", &control_bank);
+ if (ret) {
+ dev_err(&priv->client->dev, "reg property missing\n");
+ return ret;
+ }
+
+ if (control_bank > LM3532_CONTROL_C) {
+ dev_err(&priv->client->dev, "Control bank invalid\n");
+ continue;
+ }
+
+ led->control_bank = control_bank;
+
+ ret = fwnode_property_read_u32(child, "ti,led-mode",
+ &led->mode);
+ if (ret) {
+ dev_err(&priv->client->dev, "ti,led-mode property missing\n");
+ return ret;
+ }
+
+ if (fwnode_property_present(child, "led-max-microamp") &&
+ fwnode_property_read_u32(child, "led-max-microamp",
+ &led->full_scale_current))
+ dev_err(&priv->client->dev,
+ "Failed getting led-max-microamp\n");
+ else
+ led->full_scale_current = min(led->full_scale_current,
+ LM3532_FS_CURR_MAX);
+
+ if (led->mode == LM3532_BL_MODE_ALS) {
+ led->mode = LM3532_ALS_CTRL;
+ ret = lm3532_parse_als(priv);
+ if (ret)
+ dev_err(&priv->client->dev, "Failed to parse als\n");
+ else
+ lm3532_als_configure(priv, led);
+ } else {
+ led->mode = LM3532_I2C_CTRL;
+ }
+
+ led->num_leds = fwnode_property_count_u32(child, "led-sources");
+ if (led->num_leds > LM3532_MAX_LED_STRINGS) {
+ dev_err(&priv->client->dev, "Too many LED string defined\n");
+ continue;
+ }
+
+ ret = fwnode_property_read_u32_array(child, "led-sources",
+ led->led_strings,
+ led->num_leds);
+ if (ret) {
+ dev_err(&priv->client->dev, "led-sources property missing\n");
+ return ret;
+ }
+
+ led->priv = priv;
+ led->led_dev.brightness_set_blocking = lm3532_brightness_set;
+
+ ret = devm_led_classdev_register_ext(priv->dev, &led->led_dev, &idata);
+ if (ret) {
+ dev_err(&priv->client->dev, "led register err: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = lm3532_init_registers(led);
+ if (ret) {
+ dev_err(&priv->client->dev, "register init err: %d\n",
+ ret);
+ return ret;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int lm3532_probe(struct i2c_client *client)
+{
+ struct lm3532_data *drvdata;
+ int ret = 0;
+ int count;
+
+ count = device_get_child_node_count(&client->dev);
+ if (!count) {
+ dev_err(&client->dev, "LEDs are not defined in device tree!");
+ return -ENODEV;
+ }
+
+ drvdata = devm_kzalloc(&client->dev, struct_size(drvdata, leds, count),
+ GFP_KERNEL);
+ if (drvdata == NULL)
+ return -ENOMEM;
+
+ drvdata->client = client;
+ drvdata->dev = &client->dev;
+
+ drvdata->regmap = devm_regmap_init_i2c(client, &lm3532_regmap_config);
+ if (IS_ERR(drvdata->regmap)) {
+ ret = PTR_ERR(drvdata->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = devm_mutex_init(&client->dev, &drvdata->lock);
+ if (ret)
+ return ret;
+
+ i2c_set_clientdata(client, drvdata);
+
+ ret = lm3532_parse_node(drvdata);
+ if (ret) {
+ dev_err(&client->dev, "Failed to parse node\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id of_lm3532_leds_match[] = {
+ { .compatible = "ti,lm3532", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lm3532_leds_match);
+
+static const struct i2c_device_id lm3532_id[] = {
+ { LM3532_NAME },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lm3532_id);
+
+static struct i2c_driver lm3532_i2c_driver = {
+ .probe = lm3532_probe,
+ .id_table = lm3532_id,
+ .driver = {
+ .name = LM3532_NAME,
+ .of_match_table = of_lm3532_leds_match,
+ },
+};
+module_i2c_driver(lm3532_i2c_driver);
+
+MODULE_DESCRIPTION("Back Light driver for LM3532");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
index bbf24d038a7f..45795f2a1042 100644
--- a/drivers/leds/leds-lm3533.c
+++ b/drivers/leds/leds-lm3533.c
@@ -1,24 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* leds-lm3533.c -- LM3533 LED driver
*
* Copyright (C) 2011-2012 Texas Instruments
*
* Author: Johan Hovold <jhovold@gmail.com>
- *
- * This program is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License as published by the
- * Free Software Foundation; either version 2 of the License, or (at your
- * option) any later version.
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/leds.h>
#include <linux/mfd/core.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/workqueue.h>
#include <linux/mfd/lm3533.h>
@@ -54,9 +48,6 @@ struct lm3533_led {
struct mutex mutex;
unsigned long flags;
-
- struct work_struct work;
- u8 new_brightness;
};
@@ -124,27 +115,17 @@ out:
return ret;
}
-static void lm3533_led_work(struct work_struct *work)
-{
- struct lm3533_led *led = container_of(work, struct lm3533_led, work);
-
- dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness);
-
- if (led->new_brightness == 0)
- lm3533_led_pattern_enable(led, 0); /* disable blink */
-
- lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness);
-}
-
-static void lm3533_led_set(struct led_classdev *cdev,
+static int lm3533_led_set(struct led_classdev *cdev,
enum led_brightness value)
{
struct lm3533_led *led = to_lm3533_led(cdev);
dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value);
- led->new_brightness = value;
- schedule_work(&led->work);
+ if (value == 0)
+ lm3533_led_pattern_enable(led, 0); /* disable blink */
+
+ return lm3533_ctrlbank_set_brightness(&led->cb, value);
}
static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
@@ -333,7 +314,7 @@ static ssize_t show_id(struct device *dev,
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lm3533_led *led = to_lm3533_led(led_cdev);
- return scnprintf(buf, PAGE_SIZE, "%d\n", led->id);
+ return sysfs_emit(buf, "%d\n", led->id);
}
/*
@@ -363,7 +344,7 @@ static ssize_t show_risefalltime(struct device *dev,
if (ret)
return ret;
- return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+ return sysfs_emit(buf, "%x\n", val);
}
static ssize_t show_risetime(struct device *dev,
@@ -434,7 +415,7 @@ static ssize_t show_als_channel(struct device *dev,
channel = (val & LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK) + 1;
- return scnprintf(buf, PAGE_SIZE, "%u\n", channel);
+ return sysfs_emit(buf, "%u\n", channel);
}
static ssize_t store_als_channel(struct device *dev,
@@ -484,7 +465,7 @@ static ssize_t show_als_en(struct device *dev,
enable = val & LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK;
- return scnprintf(buf, PAGE_SIZE, "%d\n", enable);
+ return sysfs_emit(buf, "%d\n", enable);
}
static ssize_t store_als_en(struct device *dev,
@@ -537,7 +518,7 @@ static ssize_t show_linear(struct device *dev,
else
linear = 0;
- return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+ return sysfs_emit(buf, "%x\n", linear);
}
static ssize_t store_linear(struct device *dev,
@@ -583,7 +564,7 @@ static ssize_t show_pwm(struct device *dev,
if (ret)
return ret;
- return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+ return sysfs_emit(buf, "%u\n", val);
}
static ssize_t store_pwm(struct device *dev,
@@ -627,7 +608,7 @@ static struct attribute *lm3533_led_attributes[] = {
static umode_t lm3533_led_attr_is_visible(struct kobject *kobj,
struct attribute *attr, int n)
{
- struct device *dev = container_of(kobj, struct device, kobj);
+ struct device *dev = kobj_to_dev(kobj);
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct lm3533_led *led = to_lm3533_led(led_cdev);
umode_t mode = attr->mode;
@@ -641,11 +622,16 @@ static umode_t lm3533_led_attr_is_visible(struct kobject *kobj,
return mode;
};
-static struct attribute_group lm3533_led_attribute_group = {
+static const struct attribute_group lm3533_led_attribute_group = {
.is_visible = lm3533_led_attr_is_visible,
.attrs = lm3533_led_attributes
};
+static const struct attribute_group *lm3533_led_attribute_groups[] = {
+ &lm3533_led_attribute_group,
+ NULL
+};
+
static int lm3533_led_setup(struct lm3533_led *led,
struct lm3533_led_platform_data *pdata)
{
@@ -671,7 +657,7 @@ static int lm3533_led_probe(struct platform_device *pdev)
if (!lm3533)
return -EINVAL;
- pdata = pdev->dev.platform_data;
+ pdata = dev_get_platdata(&pdev->dev);
if (!pdata) {
dev_err(&pdev->dev, "no platform data\n");
return -EINVAL;
@@ -689,14 +675,14 @@ static int lm3533_led_probe(struct platform_device *pdev)
led->lm3533 = lm3533;
led->cdev.name = pdata->name;
led->cdev.default_trigger = pdata->default_trigger;
- led->cdev.brightness_set = lm3533_led_set;
+ led->cdev.brightness_set_blocking = lm3533_led_set;
led->cdev.brightness_get = lm3533_led_get;
led->cdev.blink_set = lm3533_led_blink_set;
led->cdev.brightness = LED_OFF;
+ led->cdev.groups = lm3533_led_attribute_groups;
led->id = pdev->id;
mutex_init(&led->mutex);
- INIT_WORK(&led->work, lm3533_led_work);
/* The class framework makes a callback to get brightness during
* registration so use parent device (for error reporting) until
@@ -716,44 +702,30 @@ static int lm3533_led_probe(struct platform_device *pdev)
led->cb.dev = led->cdev.dev;
- ret = sysfs_create_group(&led->cdev.dev->kobj,
- &lm3533_led_attribute_group);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to create sysfs attributes\n");
- goto err_unregister;
- }
-
ret = lm3533_led_setup(led, pdata);
if (ret)
- goto err_sysfs_remove;
+ goto err_deregister;
ret = lm3533_ctrlbank_enable(&led->cb);
if (ret)
- goto err_sysfs_remove;
+ goto err_deregister;
return 0;
-err_sysfs_remove:
- sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
-err_unregister:
+err_deregister:
led_classdev_unregister(&led->cdev);
- flush_work(&led->work);
return ret;
}
-static int lm3533_led_remove(struct platform_device *pdev)
+static void lm3533_led_remove(struct platform_device *pdev)
{
struct lm3533_led *led = platform_get_drvdata(pdev);
dev_dbg(&pdev->dev, "%s\n", __func__);
lm3533_ctrlbank_disable(&led->cb);
- sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
led_classdev_unregister(&led->cdev);
- flush_work(&led->work);
-
- return 0;
}
static void lm3533_led_shutdown(struct platform_device *pdev)
@@ -765,13 +737,11 @@ static void lm3533_led_shutdown(struct platform_device *pdev)
lm3533_ctrlbank_disable(&led->cb);
lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */
- flush_work(&led->work);
}
static struct platform_driver lm3533_led_driver = {
.driver = {
.name = "lm3533-leds",
- .owner = THIS_MODULE,
},
.probe = lm3533_led_probe,
.remove = lm3533_led_remove,
diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c
index d81a8e7afd6c..f68771b9eac6 100644
--- a/drivers/leds/leds-lm355x.c
+++ b/drivers/leds/leds-lm355x.c
@@ -1,22 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Simple driver for Texas Instruments LM355x LED Flash driver chip
* Copyright (C) 2012 Texas Instruments
-*
-* This program is free software; you can redistribute it and/or modify
-* it under the terms of the GNU General Public License version 2 as
-* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/i2c.h>
-#include <linux/gpio.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/regmap.h>
-#include <linux/workqueue.h>
#include <linux/platform_data/leds-lm355x.h>
enum lm355x_type {
@@ -59,14 +54,6 @@ struct lm355x_chip_data {
struct led_classdev cdev_torch;
struct led_classdev cdev_indicator;
- struct work_struct work_flash;
- struct work_struct work_torch;
- struct work_struct work_indicator;
-
- u8 br_flash;
- u8 br_torch;
- u8 br_indicator;
-
struct lm355x_platform_data *pdata;
struct regmap *regmap;
struct mutex lock;
@@ -177,18 +164,19 @@ static int lm355x_chip_init(struct lm355x_chip_data *chip)
/* input and output pins configuration */
switch (chip->type) {
case CHIP_LM3554:
- reg_val = pdata->pin_tx2 | pdata->ntc_pin;
+ reg_val = (u32)pdata->pin_tx2 | (u32)pdata->ntc_pin;
ret = regmap_update_bits(chip->regmap, 0xE0, 0x28, reg_val);
if (ret < 0)
goto out;
- reg_val = pdata->pass_mode;
+ reg_val = (u32)pdata->pass_mode;
ret = regmap_update_bits(chip->regmap, 0xA0, 0x04, reg_val);
if (ret < 0)
goto out;
break;
case CHIP_LM3556:
- reg_val = pdata->pin_tx2 | pdata->ntc_pin | pdata->pass_mode;
+ reg_val = (u32)pdata->pin_tx2 | (u32)pdata->ntc_pin |
+ (u32)pdata->pass_mode;
ret = regmap_update_bits(chip->regmap, 0x0A, 0xC4, reg_val);
if (ret < 0)
goto out;
@@ -204,7 +192,7 @@ out:
}
/* chip control */
-static void lm355x_control(struct lm355x_chip_data *chip,
+static int lm355x_control(struct lm355x_chip_data *chip,
u8 brightness, enum lm355x_mode opmode)
{
int ret;
@@ -301,7 +289,7 @@ static void lm355x_control(struct lm355x_chip_data *chip,
case MODE_SHDN:
break;
default:
- return;
+ return -EINVAL;
}
/* operation mode control */
ret = regmap_update_bits(chip->regmap, preg[REG_OPMODE].regno,
@@ -309,79 +297,61 @@ static void lm355x_control(struct lm355x_chip_data *chip,
opmode << preg[REG_OPMODE].shift);
if (ret < 0)
goto out;
- return;
+ return ret;
out:
dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
- return;
+ return ret;
}
/* torch */
-static void lm355x_deferred_torch_brightness_set(struct work_struct *work)
-{
- struct lm355x_chip_data *chip =
- container_of(work, struct lm355x_chip_data, work_torch);
-
- mutex_lock(&chip->lock);
- lm355x_control(chip, chip->br_torch, MODE_TORCH);
- mutex_unlock(&chip->lock);
-}
-static void lm355x_torch_brightness_set(struct led_classdev *cdev,
+static int lm355x_torch_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm355x_chip_data *chip =
container_of(cdev, struct lm355x_chip_data, cdev_torch);
-
- chip->br_torch = brightness;
- schedule_work(&chip->work_torch);
-}
-
-/* flash */
-static void lm355x_deferred_strobe_brightness_set(struct work_struct *work)
-{
- struct lm355x_chip_data *chip =
- container_of(work, struct lm355x_chip_data, work_flash);
+ int ret;
mutex_lock(&chip->lock);
- lm355x_control(chip, chip->br_flash, MODE_FLASH);
+ ret = lm355x_control(chip, brightness, MODE_TORCH);
mutex_unlock(&chip->lock);
+ return ret;
}
-static void lm355x_strobe_brightness_set(struct led_classdev *cdev,
+/* flash */
+
+static int lm355x_strobe_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm355x_chip_data *chip =
container_of(cdev, struct lm355x_chip_data, cdev_flash);
-
- chip->br_flash = brightness;
- schedule_work(&chip->work_flash);
-}
-
-/* indicator */
-static void lm355x_deferred_indicator_brightness_set(struct work_struct *work)
-{
- struct lm355x_chip_data *chip =
- container_of(work, struct lm355x_chip_data, work_indicator);
+ int ret;
mutex_lock(&chip->lock);
- lm355x_control(chip, chip->br_indicator, MODE_INDIC);
+ ret = lm355x_control(chip, brightness, MODE_FLASH);
mutex_unlock(&chip->lock);
+ return ret;
}
-static void lm355x_indicator_brightness_set(struct led_classdev *cdev,
+/* indicator */
+
+static int lm355x_indicator_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm355x_chip_data *chip =
container_of(cdev, struct lm355x_chip_data, cdev_indicator);
+ int ret;
- chip->br_indicator = brightness;
- schedule_work(&chip->work_indicator);
+ mutex_lock(&chip->lock);
+ ret = lm355x_control(chip, brightness, MODE_INDIC);
+ mutex_unlock(&chip->lock);
+ return ret;
}
/* indicator pattern only for lm3556*/
-static ssize_t lm3556_indicator_pattern_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t pattern_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
ssize_t ret;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -411,7 +381,13 @@ out:
return ret;
}
-static DEVICE_ATTR(pattern, S_IWUSR, NULL, lm3556_indicator_pattern_store);
+static DEVICE_ATTR_WO(pattern);
+
+static struct attribute *lm355x_indicator_attrs[] = {
+ &dev_attr_pattern.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(lm355x_indicator);
static const struct regmap_config lm355x_regmap = {
.reg_bits = 8,
@@ -420,10 +396,10 @@ static const struct regmap_config lm355x_regmap = {
};
/* module initialize */
-static int lm355x_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int lm355x_probe(struct i2c_client *client)
{
- struct lm355x_platform_data *pdata = client->dev.platform_data;
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct lm355x_platform_data *pdata = dev_get_platdata(&client->dev);
struct lm355x_chip_data *chip;
int err;
@@ -473,53 +449,40 @@ static int lm355x_probe(struct i2c_client *client,
goto err_out;
/* flash */
- INIT_WORK(&chip->work_flash, lm355x_deferred_strobe_brightness_set);
chip->cdev_flash.name = "flash";
chip->cdev_flash.max_brightness = 16;
- chip->cdev_flash.brightness_set = lm355x_strobe_brightness_set;
+ chip->cdev_flash.brightness_set_blocking = lm355x_strobe_brightness_set;
chip->cdev_flash.default_trigger = "flash";
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_flash);
+ err = led_classdev_register(&client->dev, &chip->cdev_flash);
if (err < 0)
goto err_out;
/* torch */
- INIT_WORK(&chip->work_torch, lm355x_deferred_torch_brightness_set);
chip->cdev_torch.name = "torch";
chip->cdev_torch.max_brightness = 8;
- chip->cdev_torch.brightness_set = lm355x_torch_brightness_set;
+ chip->cdev_torch.brightness_set_blocking = lm355x_torch_brightness_set;
chip->cdev_torch.default_trigger = "torch";
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_torch);
+ err = led_classdev_register(&client->dev, &chip->cdev_torch);
if (err < 0)
goto err_create_torch_file;
/* indicator */
- INIT_WORK(&chip->work_indicator,
- lm355x_deferred_indicator_brightness_set);
chip->cdev_indicator.name = "indicator";
if (id->driver_data == CHIP_LM3554)
chip->cdev_indicator.max_brightness = 4;
else
chip->cdev_indicator.max_brightness = 8;
- chip->cdev_indicator.brightness_set = lm355x_indicator_brightness_set;
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_indicator);
+ chip->cdev_indicator.brightness_set_blocking =
+ lm355x_indicator_brightness_set;
+ /* indicator pattern control only for LM3556 */
+ if (id->driver_data == CHIP_LM3556)
+ chip->cdev_indicator.groups = lm355x_indicator_groups;
+ err = led_classdev_register(&client->dev, &chip->cdev_indicator);
if (err < 0)
goto err_create_indicator_file;
- /* indicator pattern control only for LM3554 */
- if (id->driver_data == CHIP_LM3556) {
- err =
- device_create_file(chip->cdev_indicator.dev,
- &dev_attr_pattern);
- if (err < 0)
- goto err_create_pattern_file;
- }
dev_info(&client->dev, "%s is initialized\n",
lm355x_name[id->driver_data]);
return 0;
-err_create_pattern_file:
- led_classdev_unregister(&chip->cdev_indicator);
err_create_indicator_file:
led_classdev_unregister(&chip->cdev_torch);
err_create_torch_file:
@@ -528,23 +491,16 @@ err_out:
return err;
}
-static int lm355x_remove(struct i2c_client *client)
+static void lm355x_remove(struct i2c_client *client)
{
struct lm355x_chip_data *chip = i2c_get_clientdata(client);
struct lm355x_reg_data *preg = chip->regs;
regmap_write(chip->regmap, preg[REG_OPMODE].regno, 0);
- if (chip->type == CHIP_LM3556)
- device_remove_file(chip->cdev_indicator.dev, &dev_attr_pattern);
led_classdev_unregister(&chip->cdev_indicator);
- flush_work(&chip->work_indicator);
led_classdev_unregister(&chip->cdev_torch);
- flush_work(&chip->work_torch);
led_classdev_unregister(&chip->cdev_flash);
- flush_work(&chip->work_flash);
dev_info(&client->dev, "%s is removed\n", lm355x_name[chip->type]);
-
- return 0;
}
static const struct i2c_device_id lm355x_id[] = {
@@ -558,7 +514,6 @@ MODULE_DEVICE_TABLE(i2c, lm355x_id);
static struct i2c_driver lm355x_i2c_driver = {
.driver = {
.name = LM355x_NAME,
- .owner = THIS_MODULE,
.pm = NULL,
},
.probe = lm355x_probe,
diff --git a/drivers/leds/leds-lm36274.c b/drivers/leds/leds-lm36274.c
new file mode 100644
index 000000000000..e009b6d17915
--- /dev/null
+++ b/drivers/leds/leds-lm36274.c
@@ -0,0 +1,172 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LM36274 LED chip family driver
+// Copyright (C) 2019 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/leds-ti-lmu-common.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+#include <linux/mfd/ti-lmu.h>
+#include <linux/mfd/ti-lmu-register.h>
+
+#include <uapi/linux/uleds.h>
+
+#define LM36274_MAX_STRINGS 4
+#define LM36274_BL_EN BIT(4)
+
+/**
+ * struct lm36274
+ * @pdev: platform device
+ * @led_dev: led class device
+ * @lmu_data: Register and setting values for common code
+ * @regmap: Devices register map
+ * @dev: Pointer to the devices device struct
+ * @led_sources: The LED strings supported in this array
+ * @num_leds: Number of LED strings are supported in this array
+ */
+struct lm36274 {
+ struct platform_device *pdev;
+ struct led_classdev led_dev;
+ struct ti_lmu_bank lmu_data;
+ struct regmap *regmap;
+ struct device *dev;
+
+ u32 led_sources[LM36274_MAX_STRINGS];
+ int num_leds;
+};
+
+static int lm36274_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lm36274 *chip = container_of(led_cdev, struct lm36274, led_dev);
+
+ return ti_lmu_common_set_brightness(&chip->lmu_data, brt_val);
+}
+
+static int lm36274_init(struct lm36274 *chip)
+{
+ int enable_val = 0;
+ int i;
+
+ for (i = 0; i < chip->num_leds; i++)
+ enable_val |= (1 << chip->led_sources[i]);
+
+ if (!enable_val) {
+ dev_err(chip->dev, "No LEDs were enabled\n");
+ return -EINVAL;
+ }
+
+ enable_val |= LM36274_BL_EN;
+
+ return regmap_write(chip->regmap, LM36274_REG_BL_EN, enable_val);
+}
+
+static int lm36274_parse_dt(struct lm36274 *chip,
+ struct led_init_data *init_data)
+{
+ struct device *dev = chip->dev;
+ struct fwnode_handle *child;
+ int ret;
+
+ /* There should only be 1 node */
+ if (device_get_child_node_count(dev) != 1)
+ return -EINVAL;
+
+ child = device_get_next_child_node(dev, NULL);
+
+ init_data->fwnode = child;
+ init_data->devicename = chip->pdev->name;
+ /* for backwards compatibility when `label` property is not present */
+ init_data->default_label = ":";
+
+ chip->num_leds = fwnode_property_count_u32(child, "led-sources");
+ if (chip->num_leds <= 0) {
+ ret = -ENODEV;
+ goto err;
+ }
+
+ ret = fwnode_property_read_u32_array(child, "led-sources",
+ chip->led_sources, chip->num_leds);
+ if (ret) {
+ dev_err(dev, "led-sources property missing\n");
+ goto err;
+ }
+
+ return 0;
+err:
+ fwnode_handle_put(child);
+ return ret;
+}
+
+static int lm36274_probe(struct platform_device *pdev)
+{
+ struct ti_lmu *lmu = dev_get_drvdata(pdev->dev.parent);
+ struct led_init_data init_data = {};
+ struct lm36274 *chip;
+ int ret;
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->pdev = pdev;
+ chip->dev = &pdev->dev;
+ chip->regmap = lmu->regmap;
+ platform_set_drvdata(pdev, chip);
+
+ ret = lm36274_parse_dt(chip, &init_data);
+ if (ret) {
+ dev_err(chip->dev, "Failed to parse DT node\n");
+ return ret;
+ }
+
+ ret = lm36274_init(chip);
+ if (ret) {
+ fwnode_handle_put(init_data.fwnode);
+ dev_err(chip->dev, "Failed to init the device\n");
+ return ret;
+ }
+
+ chip->lmu_data.regmap = chip->regmap;
+ chip->lmu_data.max_brightness = MAX_BRIGHTNESS_11BIT;
+ chip->lmu_data.msb_brightness_reg = LM36274_REG_BRT_MSB;
+ chip->lmu_data.lsb_brightness_reg = LM36274_REG_BRT_LSB;
+
+ chip->led_dev.max_brightness = MAX_BRIGHTNESS_11BIT;
+ chip->led_dev.brightness_set_blocking = lm36274_brightness_set;
+
+ ret = devm_led_classdev_register_ext(chip->dev, &chip->led_dev,
+ &init_data);
+ if (ret)
+ dev_err(chip->dev, "Failed to register LED for node %pfw\n",
+ init_data.fwnode);
+
+ fwnode_handle_put(init_data.fwnode);
+
+ return ret;
+}
+
+static const struct of_device_id of_lm36274_leds_match[] = {
+ { .compatible = "ti,lm36274-backlight", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lm36274_leds_match);
+
+static struct platform_driver lm36274_driver = {
+ .probe = lm36274_probe,
+ .driver = {
+ .name = "lm36274-leds",
+ .of_match_table = of_lm36274_leds_match,
+ },
+};
+module_platform_driver(lm36274_driver)
+
+MODULE_DESCRIPTION("Texas Instruments LM36274 LED driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c
index f361bbef2dec..61629d5d6703 100644
--- a/drivers/leds/leds-lm3642.c
+++ b/drivers/leds/leds-lm3642.c
@@ -1,11 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Simple driver for Texas Instruments LM3642 LED Flash driver chip
* Copyright (C) 2012 Texas Instruments
-*
-* This program is free software; you can redistribute it and/or modify
-* it under the terms of the GNU General Public License version 2 as
-* published by the Free Software Foundation.
-*
*/
#include <linux/module.h>
#include <linux/delay.h>
@@ -15,7 +11,6 @@
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/regmap.h>
-#include <linux/workqueue.h>
#include <linux/platform_data/leds-lm3642.h>
#define REG_FILT_TIME (0x0)
@@ -73,10 +68,6 @@ struct lm3642_chip_data {
struct led_classdev cdev_torch;
struct led_classdev cdev_indicator;
- struct work_struct work_flash;
- struct work_struct work_torch;
- struct work_struct work_indicator;
-
u8 br_flash;
u8 br_torch;
u8 br_indicator;
@@ -115,7 +106,7 @@ static int lm3642_control(struct lm3642_chip_data *chip,
ret = regmap_read(chip->regmap, REG_FLAG, &chip->last_flag);
if (ret < 0) {
dev_err(chip->dev, "Failed to read REG_FLAG Register\n");
- goto out;
+ return ret;
}
if (chip->last_flag)
@@ -155,11 +146,11 @@ static int lm3642_control(struct lm3642_chip_data *chip,
break;
default:
- return ret;
+ return -EINVAL;
}
if (ret < 0) {
dev_err(chip->dev, "Failed to write REG_I_CTRL Register\n");
- goto out;
+ return ret;
}
if (chip->tx_pin)
@@ -168,16 +159,15 @@ static int lm3642_control(struct lm3642_chip_data *chip,
ret = regmap_update_bits(chip->regmap, REG_ENABLE,
MODE_BITS_MASK << MODE_BITS_SHIFT,
opmode << MODE_BITS_SHIFT);
-out:
return ret;
}
/* torch */
-/* torch pin config for lm3642*/
-static ssize_t lm3642_torch_pin_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+/* torch pin config for lm3642 */
+static ssize_t torch_pin_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
ssize_t ret;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -187,7 +177,7 @@ static ssize_t lm3642_torch_pin_store(struct device *dev,
ret = kstrtouint(buf, 10, &state);
if (ret)
- goto out_strtoint;
+ return ret;
if (state != 0)
state = 0x01 << TORCH_PIN_EN_SHIFT;
@@ -195,46 +185,36 @@ static ssize_t lm3642_torch_pin_store(struct device *dev,
ret = regmap_update_bits(chip->regmap, REG_ENABLE,
TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT,
state);
- if (ret < 0)
- goto out;
+ if (ret < 0) {
+ dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
+ return ret;
+ }
return size;
-out:
- dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
- return ret;
-out_strtoint:
- dev_err(chip->dev, "%s: fail to change str to int\n", __func__);
- return ret;
}
-static DEVICE_ATTR(torch_pin, S_IWUSR, NULL, lm3642_torch_pin_store);
-
-static void lm3642_deferred_torch_brightness_set(struct work_struct *work)
-{
- struct lm3642_chip_data *chip =
- container_of(work, struct lm3642_chip_data, work_torch);
-
- mutex_lock(&chip->lock);
- lm3642_control(chip, chip->br_torch, MODES_TORCH);
- mutex_unlock(&chip->lock);
-}
+static DEVICE_ATTR_WO(torch_pin);
-static void lm3642_torch_brightness_set(struct led_classdev *cdev,
+static int lm3642_torch_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm3642_chip_data *chip =
container_of(cdev, struct lm3642_chip_data, cdev_torch);
+ int ret;
+ mutex_lock(&chip->lock);
chip->br_torch = brightness;
- schedule_work(&chip->work_torch);
+ ret = lm3642_control(chip, chip->br_torch, MODES_TORCH);
+ mutex_unlock(&chip->lock);
+ return ret;
}
/* flash */
/* strobe pin config for lm3642*/
-static ssize_t lm3642_strobe_pin_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t strobe_pin_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
ssize_t ret;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -244,7 +224,7 @@ static ssize_t lm3642_strobe_pin_store(struct device *dev,
ret = kstrtouint(buf, 10, &state);
if (ret)
- goto out_strtoint;
+ return ret;
if (state != 0)
state = 0x01 << STROBE_PIN_EN_SHIFT;
@@ -252,59 +232,43 @@ static ssize_t lm3642_strobe_pin_store(struct device *dev,
ret = regmap_update_bits(chip->regmap, REG_ENABLE,
STROBE_PIN_EN_MASK << STROBE_PIN_EN_SHIFT,
state);
- if (ret < 0)
- goto out;
+ if (ret < 0) {
+ dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
+ return ret;
+ }
return size;
-out:
- dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
- return ret;
-out_strtoint:
- dev_err(chip->dev, "%s: fail to change str to int\n", __func__);
- return ret;
}
-static DEVICE_ATTR(strobe_pin, S_IWUSR, NULL, lm3642_strobe_pin_store);
-
-static void lm3642_deferred_strobe_brightness_set(struct work_struct *work)
-{
- struct lm3642_chip_data *chip =
- container_of(work, struct lm3642_chip_data, work_flash);
-
- mutex_lock(&chip->lock);
- lm3642_control(chip, chip->br_flash, MODES_FLASH);
- mutex_unlock(&chip->lock);
-}
+static DEVICE_ATTR_WO(strobe_pin);
-static void lm3642_strobe_brightness_set(struct led_classdev *cdev,
+static int lm3642_strobe_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm3642_chip_data *chip =
container_of(cdev, struct lm3642_chip_data, cdev_flash);
-
- chip->br_flash = brightness;
- schedule_work(&chip->work_flash);
-}
-
-/* indicator */
-static void lm3642_deferred_indicator_brightness_set(struct work_struct *work)
-{
- struct lm3642_chip_data *chip =
- container_of(work, struct lm3642_chip_data, work_indicator);
+ int ret;
mutex_lock(&chip->lock);
- lm3642_control(chip, chip->br_indicator, MODES_INDIC);
+ chip->br_flash = brightness;
+ ret = lm3642_control(chip, chip->br_flash, MODES_FLASH);
mutex_unlock(&chip->lock);
+ return ret;
}
-static void lm3642_indicator_brightness_set(struct led_classdev *cdev,
+/* indicator */
+static int lm3642_indicator_brightness_set(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lm3642_chip_data *chip =
container_of(cdev, struct lm3642_chip_data, cdev_indicator);
+ int ret;
+ mutex_lock(&chip->lock);
chip->br_indicator = brightness;
- schedule_work(&chip->work_indicator);
+ ret = lm3642_control(chip, chip->br_indicator, MODES_INDIC);
+ mutex_unlock(&chip->lock);
+ return ret;
}
static const struct regmap_config lm3642_regmap = {
@@ -313,10 +277,21 @@ static const struct regmap_config lm3642_regmap = {
.max_register = REG_MAX,
};
-static int lm3642_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static struct attribute *lm3642_flash_attrs[] = {
+ &dev_attr_strobe_pin.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(lm3642_flash);
+
+static struct attribute *lm3642_torch_attrs[] = {
+ &dev_attr_torch_pin.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(lm3642_torch);
+
+static int lm3642_probe(struct i2c_client *client)
{
- struct lm3642_platform_data *pdata = client->dev.platform_data;
+ struct lm3642_platform_data *pdata = dev_get_platdata(&client->dev);
struct lm3642_chip_data *chip;
int err;
@@ -359,49 +334,35 @@ static int lm3642_probe(struct i2c_client *client,
goto err_out;
/* flash */
- INIT_WORK(&chip->work_flash, lm3642_deferred_strobe_brightness_set);
chip->cdev_flash.name = "flash";
chip->cdev_flash.max_brightness = 16;
- chip->cdev_flash.brightness_set = lm3642_strobe_brightness_set;
+ chip->cdev_flash.brightness_set_blocking = lm3642_strobe_brightness_set;
chip->cdev_flash.default_trigger = "flash";
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_flash);
+ chip->cdev_flash.groups = lm3642_flash_groups;
+ err = led_classdev_register(&client->dev, &chip->cdev_flash);
if (err < 0) {
dev_err(chip->dev, "failed to register flash\n");
goto err_out;
}
- err = device_create_file(chip->cdev_flash.dev, &dev_attr_strobe_pin);
- if (err < 0) {
- dev_err(chip->dev, "failed to create strobe-pin file\n");
- goto err_create_flash_pin_file;
- }
/* torch */
- INIT_WORK(&chip->work_torch, lm3642_deferred_torch_brightness_set);
chip->cdev_torch.name = "torch";
chip->cdev_torch.max_brightness = 8;
- chip->cdev_torch.brightness_set = lm3642_torch_brightness_set;
+ chip->cdev_torch.brightness_set_blocking = lm3642_torch_brightness_set;
chip->cdev_torch.default_trigger = "torch";
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_torch);
+ chip->cdev_torch.groups = lm3642_torch_groups;
+ err = led_classdev_register(&client->dev, &chip->cdev_torch);
if (err < 0) {
dev_err(chip->dev, "failed to register torch\n");
goto err_create_torch_file;
}
- err = device_create_file(chip->cdev_torch.dev, &dev_attr_torch_pin);
- if (err < 0) {
- dev_err(chip->dev, "failed to create torch-pin file\n");
- goto err_create_torch_pin_file;
- }
/* indicator */
- INIT_WORK(&chip->work_indicator,
- lm3642_deferred_indicator_brightness_set);
chip->cdev_indicator.name = "indicator";
chip->cdev_indicator.max_brightness = 8;
- chip->cdev_indicator.brightness_set = lm3642_indicator_brightness_set;
- err = led_classdev_register((struct device *)
- &client->dev, &chip->cdev_indicator);
+ chip->cdev_indicator.brightness_set_blocking =
+ lm3642_indicator_brightness_set;
+ err = led_classdev_register(&client->dev, &chip->cdev_indicator);
if (err < 0) {
dev_err(chip->dev, "failed to register indicator\n");
goto err_create_indicator_file;
@@ -411,35 +372,25 @@ static int lm3642_probe(struct i2c_client *client,
return 0;
err_create_indicator_file:
- device_remove_file(chip->cdev_torch.dev, &dev_attr_torch_pin);
-err_create_torch_pin_file:
led_classdev_unregister(&chip->cdev_torch);
err_create_torch_file:
- device_remove_file(chip->cdev_flash.dev, &dev_attr_strobe_pin);
-err_create_flash_pin_file:
led_classdev_unregister(&chip->cdev_flash);
err_out:
return err;
}
-static int lm3642_remove(struct i2c_client *client)
+static void lm3642_remove(struct i2c_client *client)
{
struct lm3642_chip_data *chip = i2c_get_clientdata(client);
led_classdev_unregister(&chip->cdev_indicator);
- flush_work(&chip->work_indicator);
- device_remove_file(chip->cdev_torch.dev, &dev_attr_torch_pin);
led_classdev_unregister(&chip->cdev_torch);
- flush_work(&chip->work_torch);
- device_remove_file(chip->cdev_flash.dev, &dev_attr_strobe_pin);
led_classdev_unregister(&chip->cdev_flash);
- flush_work(&chip->work_flash);
regmap_write(chip->regmap, REG_ENABLE, 0);
- return 0;
}
static const struct i2c_device_id lm3642_id[] = {
- {LM3642_NAME, 0},
+ { LM3642_NAME },
{}
};
@@ -448,7 +399,6 @@ MODULE_DEVICE_TABLE(i2c, lm3642_id);
static struct i2c_driver lm3642_i2c_driver = {
.driver = {
.name = LM3642_NAME,
- .owner = THIS_MODULE,
.pm = NULL,
},
.probe = lm3642_probe,
diff --git a/drivers/leds/leds-lm3692x.c b/drivers/leds/leds-lm3692x.c
new file mode 100644
index 000000000000..c319ff4d70b2
--- /dev/null
+++ b/drivers/leds/leds-lm3692x.c
@@ -0,0 +1,529 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LM3692x LED chip family driver
+// Copyright (C) 2017-18 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/log2.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+
+#define LM36922_MODEL 0
+#define LM36923_MODEL 1
+
+#define LM3692X_REV 0x0
+#define LM3692X_RESET 0x1
+#define LM3692X_EN 0x10
+#define LM3692X_BRT_CTRL 0x11
+#define LM3692X_PWM_CTRL 0x12
+#define LM3692X_BOOST_CTRL 0x13
+#define LM3692X_AUTO_FREQ_HI 0x15
+#define LM3692X_AUTO_FREQ_LO 0x16
+#define LM3692X_BL_ADJ_THRESH 0x17
+#define LM3692X_BRT_LSB 0x18
+#define LM3692X_BRT_MSB 0x19
+#define LM3692X_FAULT_CTRL 0x1e
+#define LM3692X_FAULT_FLAGS 0x1f
+
+#define LM3692X_SW_RESET BIT(0)
+#define LM3692X_DEVICE_EN BIT(0)
+#define LM3692X_LED1_EN BIT(1)
+#define LM3692X_LED2_EN BIT(2)
+#define LM36923_LED3_EN BIT(3)
+#define LM3692X_ENABLE_MASK (LM3692X_DEVICE_EN | LM3692X_LED1_EN | \
+ LM3692X_LED2_EN | LM36923_LED3_EN)
+
+/* Brightness Control Bits */
+#define LM3692X_BL_ADJ_POL BIT(0)
+#define LM3692X_RAMP_RATE_125us 0x00
+#define LM3692X_RAMP_RATE_250us BIT(1)
+#define LM3692X_RAMP_RATE_500us BIT(2)
+#define LM3692X_RAMP_RATE_1ms (BIT(1) | BIT(2))
+#define LM3692X_RAMP_RATE_2ms BIT(3)
+#define LM3692X_RAMP_RATE_4ms (BIT(3) | BIT(1))
+#define LM3692X_RAMP_RATE_8ms (BIT(2) | BIT(3))
+#define LM3692X_RAMP_RATE_16ms (BIT(1) | BIT(2) | BIT(3))
+#define LM3692X_RAMP_EN BIT(4)
+#define LM3692X_BRHT_MODE_REG 0x00
+#define LM3692X_BRHT_MODE_PWM BIT(5)
+#define LM3692X_BRHT_MODE_MULTI_RAMP BIT(6)
+#define LM3692X_BRHT_MODE_RAMP_MULTI (BIT(5) | BIT(6))
+#define LM3692X_MAP_MODE_EXP BIT(7)
+
+/* PWM Register Bits */
+#define LM3692X_PWM_FILTER_100 BIT(0)
+#define LM3692X_PWM_FILTER_150 BIT(1)
+#define LM3692X_PWM_FILTER_200 (BIT(0) | BIT(1))
+#define LM3692X_PWM_HYSTER_1LSB BIT(2)
+#define LM3692X_PWM_HYSTER_2LSB BIT(3)
+#define LM3692X_PWM_HYSTER_3LSB (BIT(3) | BIT(2))
+#define LM3692X_PWM_HYSTER_4LSB BIT(4)
+#define LM3692X_PWM_HYSTER_5LSB (BIT(4) | BIT(2))
+#define LM3692X_PWM_HYSTER_6LSB (BIT(4) | BIT(3))
+#define LM3692X_PWM_POLARITY BIT(5)
+#define LM3692X_PWM_SAMP_4MHZ BIT(6)
+#define LM3692X_PWM_SAMP_24MHZ BIT(7)
+
+/* Boost Control Bits */
+#define LM3692X_OCP_PROT_1A BIT(0)
+#define LM3692X_OCP_PROT_1_25A BIT(1)
+#define LM3692X_OCP_PROT_1_5A (BIT(0) | BIT(1))
+#define LM3692X_OVP_21V BIT(2)
+#define LM3692X_OVP_25V BIT(3)
+#define LM3692X_OVP_29V (BIT(2) | BIT(3))
+#define LM3692X_MIN_IND_22UH BIT(4)
+#define LM3692X_BOOST_SW_1MHZ BIT(5)
+#define LM3692X_BOOST_SW_NO_SHIFT BIT(6)
+
+/* Fault Control Bits */
+#define LM3692X_FAULT_CTRL_OVP BIT(0)
+#define LM3692X_FAULT_CTRL_OCP BIT(1)
+#define LM3692X_FAULT_CTRL_TSD BIT(2)
+#define LM3692X_FAULT_CTRL_OPEN BIT(3)
+
+/* Fault Flag Bits */
+#define LM3692X_FAULT_FLAG_OVP BIT(0)
+#define LM3692X_FAULT_FLAG_OCP BIT(1)
+#define LM3692X_FAULT_FLAG_TSD BIT(2)
+#define LM3692X_FAULT_FLAG_SHRT BIT(3)
+#define LM3692X_FAULT_FLAG_OPEN BIT(4)
+
+/**
+ * struct lm3692x_led
+ * @lock: Lock for reading/writing the device
+ * @client: Pointer to the I2C client
+ * @led_dev: LED class device pointer
+ * @regmap: Devices register map
+ * @enable_gpio: VDDIO/EN gpio to enable communication interface
+ * @regulator: LED supply regulator pointer
+ * @led_enable: LED sync to be enabled
+ * @model_id: Current device model ID enumerated
+ */
+struct lm3692x_led {
+ struct mutex lock;
+ struct i2c_client *client;
+ struct led_classdev led_dev;
+ struct regmap *regmap;
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ int led_enable;
+ int model_id;
+
+ u8 boost_ctrl, brightness_ctrl;
+ bool enabled;
+};
+
+static const struct reg_default lm3692x_reg_defs[] = {
+ {LM3692X_EN, 0xf},
+ {LM3692X_BRT_CTRL, 0x61},
+ {LM3692X_PWM_CTRL, 0x73},
+ {LM3692X_BOOST_CTRL, 0x6f},
+ {LM3692X_AUTO_FREQ_HI, 0x0},
+ {LM3692X_AUTO_FREQ_LO, 0x0},
+ {LM3692X_BL_ADJ_THRESH, 0x0},
+ {LM3692X_BRT_LSB, 0x7},
+ {LM3692X_BRT_MSB, 0xff},
+ {LM3692X_FAULT_CTRL, 0x7},
+};
+
+static const struct regmap_config lm3692x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LM3692X_FAULT_FLAGS,
+ .reg_defaults = lm3692x_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lm3692x_reg_defs),
+ .cache_type = REGCACHE_MAPLE,
+};
+
+static int lm3692x_fault_check(struct lm3692x_led *led)
+{
+ int ret;
+ unsigned int read_buf;
+
+ ret = regmap_read(led->regmap, LM3692X_FAULT_FLAGS, &read_buf);
+ if (ret)
+ return ret;
+
+ if (read_buf)
+ dev_err(&led->client->dev, "Detected a fault 0x%X\n", read_buf);
+
+ /* The first read may clear the fault. Check again to see if the fault
+ * still exits and return that value.
+ */
+ regmap_read(led->regmap, LM3692X_FAULT_FLAGS, &read_buf);
+ if (read_buf)
+ dev_err(&led->client->dev, "Second read of fault flags 0x%X\n",
+ read_buf);
+
+ return read_buf;
+}
+
+static int lm3692x_leds_enable(struct lm3692x_led *led)
+{
+ int enable_state;
+ int ret, reg_ret;
+
+ if (led->enabled)
+ return 0;
+
+ if (led->regulator) {
+ ret = regulator_enable(led->regulator);
+ if (ret) {
+ dev_err(&led->client->dev,
+ "Failed to enable regulator: %d\n", ret);
+ return ret;
+ }
+ }
+
+ if (led->enable_gpio)
+ gpiod_direction_output(led->enable_gpio, 1);
+
+ ret = lm3692x_fault_check(led);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot read/clear faults: %d\n",
+ ret);
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LM3692X_BRT_CTRL, 0x00);
+ if (ret)
+ goto out;
+
+ /*
+ * For glitch free operation, the following data should
+ * only be written while LEDx enable bits are 0 and the device enable
+ * bit is set to 1.
+ * per Section 7.5.14 of the data sheet
+ */
+ ret = regmap_write(led->regmap, LM3692X_EN, LM3692X_DEVICE_EN);
+ if (ret)
+ goto out;
+
+ /* Set the brightness to 0 so when enabled the LEDs do not come
+ * on with full brightness.
+ */
+ ret = regmap_write(led->regmap, LM3692X_BRT_MSB, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_BRT_LSB, 0);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_PWM_CTRL,
+ LM3692X_PWM_FILTER_100 | LM3692X_PWM_SAMP_24MHZ);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_BOOST_CTRL, led->boost_ctrl);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_AUTO_FREQ_HI, 0x00);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_AUTO_FREQ_LO, 0x00);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_BL_ADJ_THRESH, 0x00);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap, LM3692X_BRT_CTRL,
+ LM3692X_BL_ADJ_POL | LM3692X_RAMP_EN);
+ if (ret)
+ goto out;
+
+ switch (led->led_enable) {
+ case 0:
+ default:
+ if (led->model_id == LM36923_MODEL)
+ enable_state = LM3692X_LED1_EN | LM3692X_LED2_EN |
+ LM36923_LED3_EN;
+ else
+ enable_state = LM3692X_LED1_EN | LM3692X_LED2_EN;
+
+ break;
+ case 1:
+ enable_state = LM3692X_LED1_EN;
+ break;
+ case 2:
+ enable_state = LM3692X_LED2_EN;
+ break;
+
+ case 3:
+ if (led->model_id == LM36923_MODEL) {
+ enable_state = LM36923_LED3_EN;
+ break;
+ }
+
+ ret = -EINVAL;
+ dev_err(&led->client->dev,
+ "LED3 sync not available on this device\n");
+ goto out;
+ }
+
+ ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_ENABLE_MASK,
+ enable_state | LM3692X_DEVICE_EN);
+
+ led->enabled = true;
+ return ret;
+out:
+ dev_err(&led->client->dev, "Fail writing initialization values\n");
+
+ if (led->enable_gpio)
+ gpiod_direction_output(led->enable_gpio, 0);
+
+ if (led->regulator) {
+ reg_ret = regulator_disable(led->regulator);
+ if (reg_ret)
+ dev_err(&led->client->dev,
+ "Failed to disable regulator: %d\n", reg_ret);
+ }
+
+ return ret;
+}
+
+static int lm3692x_leds_disable(struct lm3692x_led *led)
+{
+ int ret;
+
+ if (!led->enabled)
+ return 0;
+
+ ret = regmap_update_bits(led->regmap, LM3692X_EN, LM3692X_DEVICE_EN, 0);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed to disable regulator: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (led->enable_gpio)
+ gpiod_direction_output(led->enable_gpio, 0);
+
+ if (led->regulator) {
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(&led->client->dev,
+ "Failed to disable regulator: %d\n", ret);
+ }
+
+ led->enabled = false;
+ return ret;
+}
+
+static int lm3692x_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lm3692x_led *led =
+ container_of(led_cdev, struct lm3692x_led, led_dev);
+ int ret;
+ int led_brightness_lsb = (brt_val >> 5);
+
+ mutex_lock(&led->lock);
+
+ if (brt_val == 0) {
+ ret = lm3692x_leds_disable(led);
+ goto out;
+ } else {
+ lm3692x_leds_enable(led);
+ }
+
+ ret = lm3692x_fault_check(led);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot read/clear faults: %d\n",
+ ret);
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LM3692X_BRT_MSB, brt_val);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot write MSB: %d\n", ret);
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LM3692X_BRT_LSB, led_brightness_lsb);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot write LSB: %d\n", ret);
+ goto out;
+ }
+out:
+ mutex_unlock(&led->lock);
+ return ret;
+}
+
+static enum led_brightness lm3692x_max_brightness(struct lm3692x_led *led,
+ u32 max_cur)
+{
+ u32 max_code;
+
+ /* see p.12 of LM36922 data sheet for brightness formula */
+ max_code = ((max_cur * 1000) - 37806) / 12195;
+ if (max_code > 0x7FF)
+ max_code = 0x7FF;
+
+ return max_code >> 3;
+}
+
+static int lm3692x_probe_dt(struct lm3692x_led *led)
+{
+ struct fwnode_handle *child = NULL;
+ struct led_init_data init_data = {};
+ u32 ovp, max_cur;
+ int ret;
+
+ led->enable_gpio = devm_gpiod_get_optional(&led->client->dev,
+ "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(led->enable_gpio)) {
+ ret = PTR_ERR(led->enable_gpio);
+ dev_err(&led->client->dev, "Failed to get enable gpio: %d\n",
+ ret);
+ return ret;
+ }
+
+ led->regulator = devm_regulator_get_optional(&led->client->dev, "vled");
+ if (IS_ERR(led->regulator)) {
+ ret = PTR_ERR(led->regulator);
+ if (ret != -ENODEV)
+ return dev_err_probe(&led->client->dev, ret,
+ "Failed to get vled regulator\n");
+
+ led->regulator = NULL;
+ }
+
+ led->boost_ctrl = LM3692X_BOOST_SW_1MHZ |
+ LM3692X_BOOST_SW_NO_SHIFT |
+ LM3692X_OCP_PROT_1_5A;
+ ret = device_property_read_u32(&led->client->dev,
+ "ti,ovp-microvolt", &ovp);
+ if (ret) {
+ led->boost_ctrl |= LM3692X_OVP_29V;
+ } else {
+ switch (ovp) {
+ case 17000000:
+ break;
+ case 21000000:
+ led->boost_ctrl |= LM3692X_OVP_21V;
+ break;
+ case 25000000:
+ led->boost_ctrl |= LM3692X_OVP_25V;
+ break;
+ case 29000000:
+ led->boost_ctrl |= LM3692X_OVP_29V;
+ break;
+ default:
+ dev_err(&led->client->dev, "Invalid OVP %d\n", ovp);
+ return -EINVAL;
+ }
+ }
+
+ child = device_get_next_child_node(&led->client->dev, child);
+ if (!child) {
+ dev_err(&led->client->dev, "No LED Child node\n");
+ return -ENODEV;
+ }
+
+ ret = fwnode_property_read_u32(child, "reg", &led->led_enable);
+ if (ret) {
+ fwnode_handle_put(child);
+ dev_err(&led->client->dev, "reg DT property missing\n");
+ return ret;
+ }
+
+ ret = fwnode_property_read_u32(child, "led-max-microamp", &max_cur);
+ led->led_dev.max_brightness = ret ? LED_FULL :
+ lm3692x_max_brightness(led, max_cur);
+
+ init_data.fwnode = child;
+ init_data.devicename = led->client->name;
+ init_data.default_label = ":";
+
+ ret = devm_led_classdev_register_ext(&led->client->dev, &led->led_dev,
+ &init_data);
+ if (ret)
+ dev_err(&led->client->dev, "led register err: %d\n", ret);
+
+ fwnode_handle_put(init_data.fwnode);
+ return ret;
+}
+
+static int lm3692x_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct lm3692x_led *led;
+ int ret;
+
+ led = devm_kzalloc(&client->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ mutex_init(&led->lock);
+ led->client = client;
+ led->led_dev.brightness_set_blocking = lm3692x_brightness_set;
+ led->model_id = id->driver_data;
+ i2c_set_clientdata(client, led);
+
+ led->regmap = devm_regmap_init_i2c(client, &lm3692x_regmap_config);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = lm3692x_probe_dt(led);
+ if (ret)
+ return ret;
+
+ ret = lm3692x_leds_enable(led);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static void lm3692x_remove(struct i2c_client *client)
+{
+ struct lm3692x_led *led = i2c_get_clientdata(client);
+
+ lm3692x_leds_disable(led);
+ mutex_destroy(&led->lock);
+}
+
+static const struct i2c_device_id lm3692x_id[] = {
+ { "lm36922", LM36922_MODEL },
+ { "lm36923", LM36923_MODEL },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm3692x_id);
+
+static const struct of_device_id of_lm3692x_leds_match[] = {
+ { .compatible = "ti,lm36922", },
+ { .compatible = "ti,lm36923", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lm3692x_leds_match);
+
+static struct i2c_driver lm3692x_driver = {
+ .driver = {
+ .name = "lm3692x",
+ .of_match_table = of_lm3692x_leds_match,
+ },
+ .probe = lm3692x_probe,
+ .remove = lm3692x_remove,
+ .id_table = lm3692x_id,
+};
+module_i2c_driver(lm3692x_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LM3692X LED driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lm3697.c b/drivers/leds/leds-lm3697.c
new file mode 100644
index 000000000000..7ad232780a31
--- /dev/null
+++ b/drivers/leds/leds-lm3697.c
@@ -0,0 +1,381 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LM3697 LED chip family driver
+// Copyright (C) 2018 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/bits.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/types.h>
+
+#include <linux/leds-ti-lmu-common.h>
+
+#define LM3697_REV 0x0
+#define LM3697_RESET 0x1
+#define LM3697_OUTPUT_CONFIG 0x10
+#define LM3697_CTRL_A_RAMP 0x11
+#define LM3697_CTRL_B_RAMP 0x12
+#define LM3697_CTRL_A_B_RT_RAMP 0x13
+#define LM3697_CTRL_A_B_RAMP_CFG 0x14
+#define LM3697_CTRL_A_B_BRT_CFG 0x16
+#define LM3697_CTRL_A_FS_CURR_CFG 0x17
+#define LM3697_CTRL_B_FS_CURR_CFG 0x18
+#define LM3697_PWM_CFG 0x1c
+#define LM3697_CTRL_A_BRT_LSB 0x20
+#define LM3697_CTRL_A_BRT_MSB 0x21
+#define LM3697_CTRL_B_BRT_LSB 0x22
+#define LM3697_CTRL_B_BRT_MSB 0x23
+#define LM3697_CTRL_ENABLE 0x24
+
+#define LM3697_SW_RESET BIT(0)
+
+#define LM3697_CTRL_A_EN BIT(0)
+#define LM3697_CTRL_B_EN BIT(1)
+#define LM3697_CTRL_A_B_EN (LM3697_CTRL_A_EN | LM3697_CTRL_B_EN)
+
+#define LM3697_MAX_LED_STRINGS 3
+
+#define LM3697_CONTROL_A 0
+#define LM3697_CONTROL_B 1
+#define LM3697_MAX_CONTROL_BANKS 2
+
+/**
+ * struct lm3697_led -
+ * @hvled_strings: Array of LED strings associated with a control bank
+ * @label: LED label
+ * @led_dev: LED class device
+ * @priv: Pointer to the device struct
+ * @lmu_data: Register and setting values for common code
+ * @control_bank: Control bank the LED is associated to. 0 is control bank A
+ * 1 is control bank B
+ * @enabled: LED brightness level (or LED_OFF)
+ * @num_leds: Number of LEDs available
+ */
+struct lm3697_led {
+ u32 hvled_strings[LM3697_MAX_LED_STRINGS];
+ char label[LED_MAX_NAME_SIZE];
+ struct led_classdev led_dev;
+ struct lm3697 *priv;
+ struct ti_lmu_bank lmu_data;
+ int control_bank;
+ int enabled;
+ int num_leds;
+};
+
+/**
+ * struct lm3697 -
+ * @enable_gpio: Hardware enable gpio
+ * @regulator: LED supply regulator pointer
+ * @client: Pointer to the I2C client
+ * @regmap: Devices register map
+ * @dev: Pointer to the devices device struct
+ * @lock: Lock for reading/writing the device
+ * @leds: Array of LED strings
+ * @bank_cfg: OUTPUT_CONFIG register values
+ * @num_banks: Number of control banks
+ */
+struct lm3697 {
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+
+ int bank_cfg;
+ int num_banks;
+
+ struct lm3697_led leds[] __counted_by(num_banks);
+};
+
+static const struct reg_default lm3697_reg_defs[] = {
+ {LM3697_OUTPUT_CONFIG, 0x6},
+ {LM3697_CTRL_A_RAMP, 0x0},
+ {LM3697_CTRL_B_RAMP, 0x0},
+ {LM3697_CTRL_A_B_RT_RAMP, 0x0},
+ {LM3697_CTRL_A_B_RAMP_CFG, 0x0},
+ {LM3697_CTRL_A_B_BRT_CFG, 0x0},
+ {LM3697_CTRL_A_FS_CURR_CFG, 0x13},
+ {LM3697_CTRL_B_FS_CURR_CFG, 0x13},
+ {LM3697_PWM_CFG, 0xc},
+ {LM3697_CTRL_A_BRT_LSB, 0x0},
+ {LM3697_CTRL_A_BRT_MSB, 0x0},
+ {LM3697_CTRL_B_BRT_LSB, 0x0},
+ {LM3697_CTRL_B_BRT_MSB, 0x0},
+ {LM3697_CTRL_ENABLE, 0x0},
+};
+
+static const struct regmap_config lm3697_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LM3697_CTRL_ENABLE,
+ .reg_defaults = lm3697_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lm3697_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static int lm3697_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lm3697_led *led = container_of(led_cdev, struct lm3697_led,
+ led_dev);
+ int ctrl_en_val = (1 << led->control_bank);
+ struct device *dev = led->priv->dev;
+ int ret;
+
+ mutex_lock(&led->priv->lock);
+
+ if (brt_val == LED_OFF) {
+ ret = regmap_update_bits(led->priv->regmap, LM3697_CTRL_ENABLE,
+ ctrl_en_val, ~ctrl_en_val);
+ if (ret) {
+ dev_err(dev, "Cannot write ctrl register\n");
+ goto brightness_out;
+ }
+
+ led->enabled = LED_OFF;
+ } else {
+ ret = ti_lmu_common_set_brightness(&led->lmu_data, brt_val);
+ if (ret) {
+ dev_err(dev, "Cannot write brightness\n");
+ goto brightness_out;
+ }
+
+ if (!led->enabled) {
+ ret = regmap_update_bits(led->priv->regmap,
+ LM3697_CTRL_ENABLE,
+ ctrl_en_val, ctrl_en_val);
+ if (ret) {
+ dev_err(dev, "Cannot enable the device\n");
+ goto brightness_out;
+ }
+
+ led->enabled = brt_val;
+ }
+ }
+
+brightness_out:
+ mutex_unlock(&led->priv->lock);
+ return ret;
+}
+
+static int lm3697_init(struct lm3697 *priv)
+{
+ struct device *dev = priv->dev;
+ struct lm3697_led *led;
+ int i, ret;
+
+ if (priv->enable_gpio) {
+ gpiod_direction_output(priv->enable_gpio, 1);
+ } else {
+ ret = regmap_write(priv->regmap, LM3697_RESET, LM3697_SW_RESET);
+ if (ret) {
+ dev_err(dev, "Cannot reset the device\n");
+ goto out;
+ }
+ }
+
+ ret = regmap_write(priv->regmap, LM3697_CTRL_ENABLE, 0x0);
+ if (ret) {
+ dev_err(dev, "Cannot write ctrl enable\n");
+ goto out;
+ }
+
+ ret = regmap_write(priv->regmap, LM3697_OUTPUT_CONFIG, priv->bank_cfg);
+ if (ret)
+ dev_err(dev, "Cannot write OUTPUT config\n");
+
+ for (i = 0; i < priv->num_banks; i++) {
+ led = &priv->leds[i];
+ ret = ti_lmu_common_set_ramp(&led->lmu_data);
+ if (ret)
+ dev_err(dev, "Setting the ramp rate failed\n");
+ }
+out:
+ return ret;
+}
+
+static int lm3697_probe_dt(struct lm3697 *priv)
+{
+ struct device *dev = priv->dev;
+ struct lm3697_led *led;
+ int ret = -EINVAL;
+ int control_bank;
+ size_t i = 0;
+ int j;
+
+ priv->enable_gpio = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_OUT_LOW);
+ if (IS_ERR(priv->enable_gpio))
+ return dev_err_probe(dev, PTR_ERR(priv->enable_gpio),
+ "Failed to get enable GPIO\n");
+
+ priv->regulator = devm_regulator_get(dev, "vled");
+ if (IS_ERR(priv->regulator))
+ priv->regulator = NULL;
+
+ 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");
+ return ret;
+ }
+
+ if (control_bank > LM3697_CONTROL_B) {
+ dev_err(dev, "reg property is invalid\n");
+ return -EINVAL;
+ }
+
+ led = &priv->leds[i];
+
+ ret = ti_lmu_common_get_brt_res(dev, child, &led->lmu_data);
+ if (ret)
+ dev_warn(dev,
+ "brightness resolution property missing\n");
+
+ led->control_bank = control_bank;
+ led->lmu_data.regmap = priv->regmap;
+ led->lmu_data.runtime_ramp_reg = LM3697_CTRL_A_RAMP +
+ control_bank;
+ led->lmu_data.msb_brightness_reg = LM3697_CTRL_A_BRT_MSB +
+ led->control_bank * 2;
+ led->lmu_data.lsb_brightness_reg = LM3697_CTRL_A_BRT_LSB +
+ led->control_bank * 2;
+
+ led->num_leds = fwnode_property_count_u32(child, "led-sources");
+ if (led->num_leds > LM3697_MAX_LED_STRINGS) {
+ dev_err(dev, "Too many LED strings defined\n");
+ continue;
+ }
+
+ ret = fwnode_property_read_u32_array(child, "led-sources",
+ led->hvled_strings,
+ led->num_leds);
+ if (ret) {
+ dev_err(dev, "led-sources property missing\n");
+ return ret;
+ }
+
+ for (j = 0; j < led->num_leds; j++)
+ priv->bank_cfg |=
+ (led->control_bank << led->hvled_strings[j]);
+
+ ret = ti_lmu_common_get_ramp_params(dev, child, &led->lmu_data);
+ if (ret)
+ dev_warn(dev, "runtime-ramp properties missing\n");
+
+ init_data.fwnode = child;
+ init_data.devicename = priv->client->name;
+ /* for backwards compatibility if `label` is not present */
+ init_data.default_label = ":";
+
+ led->priv = priv;
+ led->led_dev.max_brightness = led->lmu_data.max_brightness;
+ led->led_dev.brightness_set_blocking = lm3697_brightness_set;
+
+ ret = devm_led_classdev_register_ext(dev, &led->led_dev,
+ &init_data);
+ if (ret) {
+ dev_err(dev, "led register err: %d\n", ret);
+ return ret;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int lm3697_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct lm3697 *led;
+ int count;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > LM3697_MAX_CONTROL_BANKS) {
+ dev_err(dev, "Strange device tree!");
+ return -ENODEV;
+ }
+
+ led = devm_kzalloc(dev, struct_size(led, leds, count), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ mutex_init(&led->lock);
+ i2c_set_clientdata(client, led);
+
+ led->client = client;
+ led->dev = dev;
+ led->num_banks = count;
+ led->regmap = devm_regmap_init_i2c(client, &lm3697_regmap_config);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", ret);
+ return ret;
+ }
+
+ ret = lm3697_probe_dt(led);
+ if (ret)
+ return ret;
+
+ return lm3697_init(led);
+}
+
+static void lm3697_remove(struct i2c_client *client)
+{
+ struct lm3697 *led = i2c_get_clientdata(client);
+ struct device *dev = &led->client->dev;
+ int ret;
+
+ ret = regmap_update_bits(led->regmap, LM3697_CTRL_ENABLE,
+ LM3697_CTRL_A_B_EN, 0);
+ if (ret)
+ dev_err(dev, "Failed to disable the device\n");
+
+ if (led->enable_gpio)
+ gpiod_direction_output(led->enable_gpio, 0);
+
+ if (led->regulator) {
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(dev, "Failed to disable regulator\n");
+ }
+
+ mutex_destroy(&led->lock);
+}
+
+static const struct i2c_device_id lm3697_id[] = {
+ { "lm3697" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lm3697_id);
+
+static const struct of_device_id of_lm3697_leds_match[] = {
+ { .compatible = "ti,lm3697", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lm3697_leds_match);
+
+static struct i2c_driver lm3697_driver = {
+ .driver = {
+ .name = "lm3697",
+ .of_match_table = of_lm3697_leds_match,
+ },
+ .probe = lm3697_probe,
+ .remove = lm3697_remove,
+ .id_table = lm3697_id,
+};
+module_i2c_driver(lm3697_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LM3697 LED driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-locomo.c b/drivers/leds/leds-locomo.c
index 80ba048889d6..9aa3fccd71fb 100644
--- a/drivers/leds/leds-locomo.c
+++ b/drivers/leds/leds-locomo.c
@@ -1,11 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/drivers/leds/leds-locomo.c
*
* Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/kernel.h>
@@ -14,7 +11,6 @@
#include <linux/device.h>
#include <linux/leds.h>
-#include <mach/hardware.h>
#include <asm/hardware/locomo.h>
static void locomoled_brightness_set(struct led_classdev *led_cdev,
@@ -59,23 +55,13 @@ static int locomoled_probe(struct locomo_dev *ldev)
{
int ret;
- ret = led_classdev_register(&ldev->dev, &locomo_led0);
+ ret = devm_led_classdev_register(&ldev->dev, &locomo_led0);
if (ret < 0)
return ret;
- ret = led_classdev_register(&ldev->dev, &locomo_led1);
- if (ret < 0)
- led_classdev_unregister(&locomo_led0);
-
- return ret;
+ return devm_led_classdev_register(&ldev->dev, &locomo_led1);
}
-static int locomoled_remove(struct locomo_dev *dev)
-{
- led_classdev_unregister(&locomo_led0);
- led_classdev_unregister(&locomo_led1);
- return 0;
-}
static struct locomo_driver locomoled_driver = {
.drv = {
@@ -83,7 +69,6 @@ static struct locomo_driver locomoled_driver = {
},
.devid = LOCOMO_DEVID_LED,
.probe = locomoled_probe,
- .remove = locomoled_remove,
};
static int __init locomoled_init(void)
diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c
index 0c4386e656c1..ccfeee49ea78 100644
--- a/drivers/leds/leds-lp3944.c
+++ b/drivers/leds/leds-lp3944.c
@@ -1,12 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* leds-lp3944.c - driver for National Semiconductor LP3944 Funlight Chip
*
* Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
/*
@@ -31,7 +27,6 @@
#include <linux/slab.h>
#include <linux/leds.h>
#include <linux/mutex.h>
-#include <linux/workqueue.h>
#include <linux/leds-lp3944.h>
/* Read Only Registers */
@@ -68,10 +63,8 @@
struct lp3944_led_data {
u8 id;
enum lp3944_type type;
- enum lp3944_status status;
struct led_classdev ldev;
struct i2c_client *client;
- struct work_struct work;
};
struct lp3944_data {
@@ -99,7 +92,7 @@ static int lp3944_reg_write(struct i2c_client *client, u8 reg, u8 value)
}
/**
- * Set the period for DIM status
+ * lp3944_dim_set_period() - Set the period for DIM status
*
* @client: the i2c client
* @dim: either LP3944_DIM0 or LP3944_DIM1
@@ -130,7 +123,7 @@ static int lp3944_dim_set_period(struct i2c_client *client, u8 dim, u16 period)
}
/**
- * Set the duty cycle for DIM status
+ * lp3944_dim_set_dutycycle - Set the duty cycle for DIM status
*
* @client: the i2c client
* @dim: either LP3944_DIM0 or LP3944_DIM1
@@ -162,7 +155,7 @@ static int lp3944_dim_set_dutycycle(struct i2c_client *client, u8 dim,
}
/**
- * Set the led status
+ * lp3944_led_set() - Set the led status
*
* @led: a lp3944_led_data structure
* @status: one of LP3944_LED_STATUS_OFF
@@ -202,8 +195,11 @@ static int lp3944_led_set(struct lp3944_led_data *led, u8 status)
if (status > LP3944_LED_STATUS_DIM1)
return -EINVAL;
- /* invert only 0 and 1, leave unchanged the other values,
- * remember we are abusing status to set blink patterns
+ /*
+ * Invert status only when it's < 2 (i.e. 0 or 1) which means it's
+ * controlling the on/off state directly.
+ * When, instead, status is >= 2 don't invert it because it would mean
+ * to mess with the hardware blinking mode.
*/
if (led->type == LP3944_LED_TYPE_LED_INVERTED && status < 2)
status = 1 - status;
@@ -275,13 +271,12 @@ static int lp3944_led_set_blink(struct led_classdev *led_cdev,
dev_dbg(&led->client->dev, "%s: OK hardware accelerated blink!\n",
__func__);
- led->status = LP3944_LED_STATUS_DIM0;
- schedule_work(&led->work);
+ lp3944_led_set(led, LP3944_LED_STATUS_DIM0);
return 0;
}
-static void lp3944_led_set_brightness(struct led_classdev *led_cdev,
+static int lp3944_led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct lp3944_led_data *led = ldev_to_led(led_cdev);
@@ -289,16 +284,7 @@ static void lp3944_led_set_brightness(struct led_classdev *led_cdev,
dev_dbg(&led->client->dev, "%s: %s, %d\n",
__func__, led_cdev->name, brightness);
- led->status = brightness;
- schedule_work(&led->work);
-}
-
-static void lp3944_led_work(struct work_struct *work)
-{
- struct lp3944_led_data *led;
-
- led = container_of(work, struct lp3944_led_data, work);
- lp3944_led_set(led, led->status);
+ return lp3944_led_set(led, !!brightness);
}
static int lp3944_configure(struct i2c_client *client,
@@ -318,14 +304,13 @@ static int lp3944_configure(struct i2c_client *client,
case LP3944_LED_TYPE_LED:
case LP3944_LED_TYPE_LED_INVERTED:
led->type = pled->type;
- led->status = pled->status;
led->ldev.name = pled->name;
led->ldev.max_brightness = 1;
- led->ldev.brightness_set = lp3944_led_set_brightness;
+ led->ldev.brightness_set_blocking =
+ lp3944_led_set_brightness;
led->ldev.blink_set = lp3944_led_set_blink;
led->ldev.flags = LED_CORE_SUSPENDRESUME;
- INIT_WORK(&led->work, lp3944_led_work);
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
@@ -335,14 +320,15 @@ static int lp3944_configure(struct i2c_client *client,
}
/* to expose the default value to userspace */
- led->ldev.brightness = led->status;
+ led->ldev.brightness =
+ (enum led_brightness) pled->status;
/* Set the default led status */
- err = lp3944_led_set(led, led->status);
+ err = lp3944_led_set(led, pled->status);
if (err < 0) {
dev_err(&client->dev,
"%s couldn't set STATUS %d\n",
- led->ldev.name, led->status);
+ led->ldev.name, pled->status);
goto exit;
}
break;
@@ -363,7 +349,6 @@ exit:
case LP3944_LED_TYPE_LED:
case LP3944_LED_TYPE_LED_INVERTED:
led_classdev_unregister(&data->leds[i].ldev);
- cancel_work_sync(&data->leds[i].work);
break;
case LP3944_LED_TYPE_NONE:
@@ -374,10 +359,10 @@ exit:
return err;
}
-static int lp3944_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int lp3944_probe(struct i2c_client *client)
{
- struct lp3944_platform_data *lp3944_pdata = client->dev.platform_data;
+ struct lp3944_platform_data *lp3944_pdata =
+ dev_get_platdata(&client->dev);
struct lp3944_data *data;
int err;
@@ -411,9 +396,9 @@ static int lp3944_probe(struct i2c_client *client,
return 0;
}
-static int lp3944_remove(struct i2c_client *client)
+static void lp3944_remove(struct i2c_client *client)
{
- struct lp3944_platform_data *pdata = client->dev.platform_data;
+ struct lp3944_platform_data *pdata = dev_get_platdata(&client->dev);
struct lp3944_data *data = i2c_get_clientdata(client);
int i;
@@ -422,20 +407,17 @@ static int lp3944_remove(struct i2c_client *client)
case LP3944_LED_TYPE_LED:
case LP3944_LED_TYPE_LED_INVERTED:
led_classdev_unregister(&data->leds[i].ldev);
- cancel_work_sync(&data->leds[i].work);
break;
case LP3944_LED_TYPE_NONE:
default:
break;
}
-
- return 0;
}
/* 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
new file mode 100644
index 000000000000..17219a582704
--- /dev/null
+++ b/drivers/leds/leds-lp3952.c
@@ -0,0 +1,286 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for TI lp3952 controller
+ *
+ * Copyright (C) 2016, DAQRI, LLC.
+ * Author: Tony Makkiel <tony.makkiel@daqri.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/leds-lp3952.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/reboot.h>
+#include <linux/regmap.h>
+
+static int lp3952_register_write(struct i2c_client *client, u8 reg, u8 val)
+{
+ int ret;
+ struct lp3952_led_array *priv = i2c_get_clientdata(client);
+
+ ret = regmap_write(priv->regmap, reg, val);
+
+ if (ret)
+ dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
+ __func__, reg, val, ret);
+ return ret;
+}
+
+static void lp3952_on_off(struct lp3952_led_array *priv,
+ enum lp3952_leds led_id, bool on)
+{
+ int ret, val;
+
+ dev_dbg(&priv->client->dev, "%s LED %d to %d\n", __func__, led_id, on);
+
+ val = 1 << led_id;
+ if (led_id == LP3952_LED_ALL)
+ val = LP3952_LED_MASK_ALL;
+
+ ret = regmap_update_bits(priv->regmap, LP3952_REG_LED_CTRL, val,
+ on ? val : 0);
+ if (ret)
+ dev_err(&priv->client->dev, "%s, Error %d\n", __func__, ret);
+}
+
+/*
+ * Using Imax to control brightness. There are 4 possible
+ * setting 25, 50, 75 and 100 % of Imax. Possible values are
+ * values 0-4. 0 meaning turn off.
+ */
+static int lp3952_set_brightness(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ unsigned int reg, shift_val;
+ struct lp3952_ctrl_hdl *led = container_of(cdev,
+ struct lp3952_ctrl_hdl,
+ cdev);
+ struct lp3952_led_array *priv = (struct lp3952_led_array *)led->priv;
+
+ dev_dbg(cdev->dev, "Brightness request: %d on %d\n", value,
+ led->channel);
+
+ if (value == LED_OFF) {
+ lp3952_on_off(priv, led->channel, false);
+ return 0;
+ }
+
+ if (led->channel > LP3952_RED_1) {
+ dev_err(cdev->dev, " %s Invalid LED requested", __func__);
+ return -EINVAL;
+ }
+
+ if (led->channel >= LP3952_BLUE_1) {
+ reg = LP3952_REG_RGB1_MAX_I_CTRL;
+ shift_val = (led->channel - LP3952_BLUE_1) * 2;
+ } else {
+ reg = LP3952_REG_RGB2_MAX_I_CTRL;
+ shift_val = led->channel * 2;
+ }
+
+ /* Enable the LED in case it is not enabled already */
+ lp3952_on_off(priv, led->channel, true);
+
+ return regmap_update_bits(priv->regmap, reg, 3 << shift_val,
+ --value << shift_val);
+}
+
+static int lp3952_get_label(struct device *dev, const char *label, char *dest)
+{
+ int ret;
+ const char *str;
+
+ ret = device_property_read_string(dev, label, &str);
+ if (ret)
+ return ret;
+
+ strscpy(dest, str, LP3952_LABEL_MAX_LEN);
+ return 0;
+}
+
+static int lp3952_register_led_classdev(struct lp3952_led_array *priv)
+{
+ int i, acpi_ret, ret = -ENODEV;
+ static const char *led_name_hdl[LP3952_LED_ALL] = {
+ "blue2",
+ "green2",
+ "red2",
+ "blue1",
+ "green1",
+ "red1"
+ };
+
+ for (i = 0; i < LP3952_LED_ALL; i++) {
+ acpi_ret = lp3952_get_label(&priv->client->dev, led_name_hdl[i],
+ priv->leds[i].name);
+ if (acpi_ret)
+ continue;
+
+ priv->leds[i].cdev.name = priv->leds[i].name;
+ priv->leds[i].cdev.brightness = LED_OFF;
+ priv->leds[i].cdev.max_brightness = LP3952_BRIGHT_MAX;
+ priv->leds[i].cdev.brightness_set_blocking =
+ lp3952_set_brightness;
+ priv->leds[i].channel = i;
+ priv->leds[i].priv = priv;
+
+ ret = devm_led_classdev_register(&priv->client->dev,
+ &priv->leds[i].cdev);
+ if (ret < 0) {
+ dev_err(&priv->client->dev,
+ "couldn't register LED %s\n",
+ priv->leds[i].cdev.name);
+ break;
+ }
+ }
+ return ret;
+}
+
+static int lp3952_set_pattern_gen_cmd(struct lp3952_led_array *priv,
+ u8 cmd_index, u8 r, u8 g, u8 b,
+ enum lp3952_tt tt, enum lp3952_cet cet)
+{
+ int ret;
+ struct ptrn_gen_cmd line = {
+ {
+ {
+ .r = r,
+ .g = g,
+ .b = b,
+ .cet = cet,
+ .tt = tt
+ }
+ }
+ };
+
+ if (cmd_index >= LP3952_CMD_REG_COUNT)
+ return -EINVAL;
+
+ ret = lp3952_register_write(priv->client,
+ LP3952_REG_CMD_0 + cmd_index * 2,
+ line.bytes.msb);
+ if (ret)
+ return ret;
+
+ return lp3952_register_write(priv->client,
+ LP3952_REG_CMD_0 + cmd_index * 2 + 1,
+ line.bytes.lsb);
+}
+
+static int lp3952_configure(struct lp3952_led_array *priv)
+{
+ int ret;
+
+ /* Disable any LEDs on from any previous conf. */
+ ret = lp3952_register_write(priv->client, LP3952_REG_LED_CTRL, 0);
+ if (ret)
+ return ret;
+
+ /* enable rgb patter, loop */
+ ret = lp3952_register_write(priv->client, LP3952_REG_PAT_GEN_CTRL,
+ LP3952_PATRN_LOOP | LP3952_PATRN_GEN_EN);
+ if (ret)
+ return ret;
+
+ /* Update Bit 6 (Active mode), Select both Led sets, Bit [1:0] */
+ ret = lp3952_register_write(priv->client, LP3952_REG_ENABLES,
+ LP3952_ACTIVE_MODE | LP3952_INT_B00ST_LDR);
+ if (ret)
+ return ret;
+
+ /* Set Cmd1 for RGB intensity,cmd and transition time */
+ return lp3952_set_pattern_gen_cmd(priv, 0, I46, I71, I100, TT0,
+ CET197);
+}
+
+static const struct regmap_config lp3952_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = REG_MAX,
+ .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;
+ struct lp3952_led_array *priv;
+
+ priv = devm_kzalloc(&client->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->client = client;
+
+ priv->enable_gpio = devm_gpiod_get(&client->dev, "nrst",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(priv->enable_gpio)) {
+ status = PTR_ERR(priv->enable_gpio);
+ dev_err(&client->dev, "Failed to enable gpio: %d\n", status);
+ 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);
+
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ err);
+ return err;
+ }
+
+ i2c_set_clientdata(client, priv);
+
+ status = lp3952_configure(priv);
+ if (status) {
+ dev_err(&client->dev, "Probe failed. Device not found (%d)\n",
+ status);
+ return status;
+ }
+
+ status = lp3952_register_led_classdev(priv);
+ if (status) {
+ dev_err(&client->dev, "Unable to register led_classdev: %d\n",
+ status);
+ return status;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id lp3952_id[] = {
+ { LP3952_NAME },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lp3952_id);
+
+static struct i2c_driver lp3952_i2c_driver = {
+ .driver = {
+ .name = LP3952_NAME,
+ },
+ .probe = lp3952_probe,
+ .id_table = lp3952_id,
+};
+
+module_i2c_driver(lp3952_i2c_driver);
+
+MODULE_AUTHOR("Tony Makkiel <tony.makkiel@daqri.com>");
+MODULE_DESCRIPTION("lp3952 I2C LED controller driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lp50xx.c b/drivers/leds/leds-lp50xx.c
new file mode 100644
index 000000000000..e2a9c8592953
--- /dev/null
+++ b/drivers/leds/leds-lp50xx.c
@@ -0,0 +1,637 @@
+// SPDX-License-Identifier: GPL-2.0
+// TI LP50XX LED chip family driver
+// Copyright (C) 2018-20 Texas Instruments Incorporated - https://www.ti.com/
+
+#include <linux/gpio/consumer.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+#include <linux/led-class-multicolor.h>
+
+#define LP50XX_DEV_CFG0 0x00
+#define LP50XX_DEV_CFG1 0x01
+#define LP50XX_LED_CFG0 0x02
+
+/* LP5009 and LP5012 registers */
+#define LP5012_BNK_BRT 0x03
+#define LP5012_BNKA_CLR 0x04
+#define LP5012_BNKB_CLR 0x05
+#define LP5012_BNKC_CLR 0x06
+#define LP5012_LED0_BRT 0x07
+#define LP5012_OUT0_CLR 0x0b
+#define LP5012_RESET 0x17
+
+/* LP5018 and LP5024 registers */
+#define LP5024_BNK_BRT 0x03
+#define LP5024_BNKA_CLR 0x04
+#define LP5024_BNKB_CLR 0x05
+#define LP5024_BNKC_CLR 0x06
+#define LP5024_LED0_BRT 0x07
+#define LP5024_OUT0_CLR 0x0f
+#define LP5024_RESET 0x27
+
+/* LP5030 and LP5036 registers */
+#define LP5036_LED_CFG1 0x03
+#define LP5036_BNK_BRT 0x04
+#define LP5036_BNKA_CLR 0x05
+#define LP5036_BNKB_CLR 0x06
+#define LP5036_BNKC_CLR 0x07
+#define LP5036_LED0_BRT 0x08
+#define LP5036_OUT0_CLR 0x14
+#define LP5036_RESET 0x38
+
+#define LP50XX_SW_RESET 0xff
+#define LP50XX_CHIP_EN BIT(6)
+#define LP50XX_CHIP_DISABLE 0x00
+#define LP50XX_START_TIME_US 500
+#define LP50XX_RESET_TIME_US 3
+
+#define LP50XX_EN_GPIO_LOW 0
+#define LP50XX_EN_GPIO_HIGH 1
+
+/* There are 3 LED outputs per bank */
+#define LP50XX_LEDS_PER_MODULE 3
+
+#define LP5009_MAX_LED_MODULES 3
+#define LP5012_MAX_LED_MODULES 4
+#define LP5018_MAX_LED_MODULES 6
+#define LP5024_MAX_LED_MODULES 8
+#define LP5030_MAX_LED_MODULES 10
+#define LP5036_MAX_LED_MODULES 12
+
+static const struct reg_default lp5012_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5012_BNK_BRT, 0xff},
+ {LP5012_BNKA_CLR, 0x0f},
+ {LP5012_BNKB_CLR, 0x0f},
+ {LP5012_BNKC_CLR, 0x0f},
+ {LP5012_LED0_BRT, 0x0f},
+ /* LEDX_BRT registers are all 0xff for defaults */
+ {0x08, 0xff}, {0x09, 0xff}, {0x0a, 0xff},
+ {LP5012_OUT0_CLR, 0x0f},
+ /* OUTX_CLR registers are all 0x0 for defaults */
+ {0x0c, 0x00}, {0x0d, 0x00}, {0x0e, 0x00}, {0x0f, 0x00}, {0x10, 0x00},
+ {0x11, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x14, 0x00}, {0x15, 0x00},
+ {0x16, 0x00},
+ {LP5012_RESET, 0x00}
+};
+
+static const struct reg_default lp5024_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5024_BNK_BRT, 0xff},
+ {LP5024_BNKA_CLR, 0x0f},
+ {LP5024_BNKB_CLR, 0x0f},
+ {LP5024_BNKC_CLR, 0x0f},
+ {LP5024_LED0_BRT, 0x0f},
+ /* LEDX_BRT registers are all 0xff for defaults */
+ {0x08, 0xff}, {0x09, 0xff}, {0x0a, 0xff}, {0x0b, 0xff}, {0x0c, 0xff},
+ {0x0d, 0xff}, {0x0e, 0xff},
+ {LP5024_OUT0_CLR, 0x0f},
+ /* OUTX_CLR registers are all 0x0 for defaults */
+ {0x10, 0x00}, {0x11, 0x00}, {0x12, 0x00}, {0x13, 0x00}, {0x14, 0x00},
+ {0x15, 0x00}, {0x16, 0x00}, {0x17, 0x00}, {0x18, 0x00}, {0x19, 0x00},
+ {0x1a, 0x00}, {0x1b, 0x00}, {0x1c, 0x00}, {0x1d, 0x00}, {0x1e, 0x00},
+ {0x1f, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00},
+ {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00},
+ {LP5024_RESET, 0x00}
+};
+
+static const struct reg_default lp5036_reg_defs[] = {
+ {LP50XX_DEV_CFG0, 0x0},
+ {LP50XX_DEV_CFG1, 0x3c},
+ {LP50XX_LED_CFG0, 0x0},
+ {LP5036_LED_CFG1, 0x0},
+ {LP5036_BNK_BRT, 0xff},
+ {LP5036_BNKA_CLR, 0x0f},
+ {LP5036_BNKB_CLR, 0x0f},
+ {LP5036_BNKC_CLR, 0x0f},
+ {LP5036_LED0_BRT, 0x0f},
+ /* LEDX_BRT registers are all 0xff for defaults */
+ {0x08, 0xff}, {0x09, 0xff}, {0x0a, 0xff}, {0x0b, 0xff}, {0x0c, 0xff},
+ {0x0d, 0xff}, {0x0e, 0xff}, {0x0f, 0xff}, {0x10, 0xff}, {0x11, 0xff},
+ {0x12, 0xff}, {0x13, 0xff},
+ {LP5036_OUT0_CLR, 0x0f},
+ /* OUTX_CLR registers are all 0x0 for defaults */
+ {0x15, 0x00}, {0x16, 0x00}, {0x17, 0x00}, {0x18, 0x00}, {0x19, 0x00},
+ {0x1a, 0x00}, {0x1b, 0x00}, {0x1c, 0x00}, {0x1d, 0x00}, {0x1e, 0x00},
+ {0x1f, 0x00}, {0x20, 0x00}, {0x21, 0x00}, {0x22, 0x00}, {0x23, 0x00},
+ {0x24, 0x00}, {0x25, 0x00}, {0x26, 0x00}, {0x27, 0x00}, {0x28, 0x00},
+ {0x29, 0x00}, {0x2a, 0x00}, {0x2b, 0x00}, {0x2c, 0x00}, {0x2d, 0x00},
+ {0x2e, 0x00}, {0x2f, 0x00}, {0x30, 0x00}, {0x31, 0x00}, {0x32, 0x00},
+ {0x33, 0x00}, {0x34, 0x00}, {0x35, 0x00}, {0x36, 0x00}, {0x37, 0x00},
+ {LP5036_RESET, 0x00}
+};
+
+static const struct regmap_config lp5012_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5012_RESET,
+ .reg_defaults = lp5012_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5012_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config lp5024_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5024_RESET,
+ .reg_defaults = lp5024_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5024_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+static const struct regmap_config lp5036_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP5036_RESET,
+ .reg_defaults = lp5036_reg_defs,
+ .num_reg_defaults = ARRAY_SIZE(lp5036_reg_defs),
+ .cache_type = REGCACHE_FLAT,
+};
+
+enum lp50xx_model {
+ LP5009,
+ LP5012,
+ LP5018,
+ LP5024,
+ LP5030,
+ LP5036,
+};
+
+/**
+ * struct lp50xx_chip_info -
+ * @lp50xx_regmap_config: regmap register configuration
+ * @model_id: LED device model
+ * @max_modules: total number of supported LED modules
+ * @num_leds: number of LED outputs available on the device
+ * @led_brightness0_reg: first brightness register of the device
+ * @mix_out0_reg: first color mix register of the device
+ * @bank_brt_reg: bank brightness register
+ * @bank_mix_reg: color mix register
+ * @reset_reg: device reset register
+ */
+struct lp50xx_chip_info {
+ const struct regmap_config *lp50xx_regmap_config;
+ int model_id;
+ u8 max_modules;
+ u8 num_leds;
+ u8 led_brightness0_reg;
+ u8 mix_out0_reg;
+ u8 bank_brt_reg;
+ u8 bank_mix_reg;
+ u8 reset_reg;
+};
+
+static const struct lp50xx_chip_info lp50xx_chip_info_tbl[] = {
+ [LP5009] = {
+ .model_id = LP5009,
+ .max_modules = LP5009_MAX_LED_MODULES,
+ .num_leds = LP5009_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5012_LED0_BRT,
+ .mix_out0_reg = LP5012_OUT0_CLR,
+ .bank_brt_reg = LP5012_BNK_BRT,
+ .bank_mix_reg = LP5012_BNKA_CLR,
+ .reset_reg = LP5012_RESET,
+ .lp50xx_regmap_config = &lp5012_regmap_config,
+ },
+ [LP5012] = {
+ .model_id = LP5012,
+ .max_modules = LP5012_MAX_LED_MODULES,
+ .num_leds = LP5012_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5012_LED0_BRT,
+ .mix_out0_reg = LP5012_OUT0_CLR,
+ .bank_brt_reg = LP5012_BNK_BRT,
+ .bank_mix_reg = LP5012_BNKA_CLR,
+ .reset_reg = LP5012_RESET,
+ .lp50xx_regmap_config = &lp5012_regmap_config,
+ },
+ [LP5018] = {
+ .model_id = LP5018,
+ .max_modules = LP5018_MAX_LED_MODULES,
+ .num_leds = LP5018_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5024_LED0_BRT,
+ .mix_out0_reg = LP5024_OUT0_CLR,
+ .bank_brt_reg = LP5024_BNK_BRT,
+ .bank_mix_reg = LP5024_BNKA_CLR,
+ .reset_reg = LP5024_RESET,
+ .lp50xx_regmap_config = &lp5024_regmap_config,
+ },
+ [LP5024] = {
+ .model_id = LP5024,
+ .max_modules = LP5024_MAX_LED_MODULES,
+ .num_leds = LP5024_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5024_LED0_BRT,
+ .mix_out0_reg = LP5024_OUT0_CLR,
+ .bank_brt_reg = LP5024_BNK_BRT,
+ .bank_mix_reg = LP5024_BNKA_CLR,
+ .reset_reg = LP5024_RESET,
+ .lp50xx_regmap_config = &lp5024_regmap_config,
+ },
+ [LP5030] = {
+ .model_id = LP5030,
+ .max_modules = LP5030_MAX_LED_MODULES,
+ .num_leds = LP5030_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5036_LED0_BRT,
+ .mix_out0_reg = LP5036_OUT0_CLR,
+ .bank_brt_reg = LP5036_BNK_BRT,
+ .bank_mix_reg = LP5036_BNKA_CLR,
+ .reset_reg = LP5036_RESET,
+ .lp50xx_regmap_config = &lp5036_regmap_config,
+ },
+ [LP5036] = {
+ .model_id = LP5036,
+ .max_modules = LP5036_MAX_LED_MODULES,
+ .num_leds = LP5036_MAX_LED_MODULES * LP50XX_LEDS_PER_MODULE,
+ .led_brightness0_reg = LP5036_LED0_BRT,
+ .mix_out0_reg = LP5036_OUT0_CLR,
+ .bank_brt_reg = LP5036_BNK_BRT,
+ .bank_mix_reg = LP5036_BNKA_CLR,
+ .reset_reg = LP5036_RESET,
+ .lp50xx_regmap_config = &lp5036_regmap_config,
+ },
+};
+
+struct lp50xx_led {
+ struct led_classdev_mc mc_cdev;
+ struct lp50xx *priv;
+ u8 ctrl_bank_enabled;
+ int led_number;
+};
+
+/**
+ * struct lp50xx -
+ * @enable_gpio: hardware enable gpio
+ * @regulator: LED supply regulator pointer
+ * @client: pointer to the I2C client
+ * @regmap: device register map
+ * @dev: pointer to the devices device struct
+ * @lock: lock for reading/writing the device
+ * @chip_info: chip specific information (ie num_leds)
+ * @leds: array of LED strings
+ */
+struct lp50xx {
+ struct gpio_desc *enable_gpio;
+ struct regulator *regulator;
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct device *dev;
+ struct mutex lock;
+ const struct lp50xx_chip_info *chip_info;
+
+ /* This needs to be at the end of the struct */
+ struct lp50xx_led leds[];
+};
+
+static struct lp50xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
+{
+ return container_of(mc_cdev, struct lp50xx_led, mc_cdev);
+}
+
+static int lp50xx_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
+ struct lp50xx_led *led = mcled_cdev_to_led(mc_dev);
+ const struct lp50xx_chip_info *led_chip = led->priv->chip_info;
+ u8 led_offset, reg_val;
+ int ret = 0;
+ int i;
+
+ mutex_lock(&led->priv->lock);
+ if (led->ctrl_bank_enabled)
+ reg_val = led_chip->bank_brt_reg;
+ else
+ reg_val = led_chip->led_brightness0_reg +
+ led->led_number;
+
+ ret = regmap_write(led->priv->regmap, reg_val, brightness);
+ if (ret) {
+ dev_err(led->priv->dev,
+ "Cannot write brightness value %d\n", ret);
+ goto out;
+ }
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ if (led->ctrl_bank_enabled) {
+ reg_val = led_chip->bank_mix_reg + i;
+ } else {
+ led_offset = (led->led_number * 3) + i;
+ reg_val = led_chip->mix_out0_reg + led_offset;
+ }
+
+ ret = regmap_write(led->priv->regmap, reg_val,
+ mc_dev->subled_info[i].intensity);
+ if (ret) {
+ dev_err(led->priv->dev,
+ "Cannot write intensity value %d\n", ret);
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&led->priv->lock);
+ return ret;
+}
+
+static int lp50xx_set_banks(struct lp50xx *priv, u32 led_banks[], int num_leds)
+{
+ u8 led_config_lo, led_config_hi;
+ u32 bank_enable_mask = 0;
+ int ret;
+ int i;
+
+ for (i = 0; i < num_leds; i++)
+ bank_enable_mask |= (1 << led_banks[i]);
+
+ led_config_lo = bank_enable_mask;
+ led_config_hi = bank_enable_mask >> 8;
+
+ ret = regmap_write(priv->regmap, LP50XX_LED_CFG0, led_config_lo);
+ if (ret)
+ return ret;
+
+ if (priv->chip_info->model_id >= LP5030)
+ ret = regmap_write(priv->regmap, LP5036_LED_CFG1, led_config_hi);
+
+ return ret;
+}
+
+static int lp50xx_reset(struct lp50xx *priv)
+{
+ return regmap_write(priv->regmap, priv->chip_info->reset_reg, LP50XX_SW_RESET);
+}
+
+static int lp50xx_enable(struct lp50xx *priv)
+{
+ int ret;
+
+ if (priv->enable_gpio) {
+ ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_HIGH);
+ if (ret)
+ return ret;
+
+ udelay(LP50XX_START_TIME_US);
+ }
+
+ ret = lp50xx_reset(priv);
+ if (ret)
+ return ret;
+
+ return regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_EN);
+}
+
+static int lp50xx_disable(struct lp50xx *priv)
+{
+ int ret;
+
+ ret = regmap_write(priv->regmap, LP50XX_DEV_CFG0, LP50XX_CHIP_DISABLE);
+ if (ret)
+ return ret;
+
+ if (priv->enable_gpio) {
+ ret = gpiod_direction_output(priv->enable_gpio, LP50XX_EN_GPIO_LOW);
+ if (ret)
+ return ret;
+
+ udelay(LP50XX_RESET_TIME_US);
+ }
+
+ return 0;
+}
+
+static int lp50xx_probe_leds(struct fwnode_handle *child, struct lp50xx *priv,
+ struct lp50xx_led *led, int num_leds)
+{
+ u32 led_banks[LP5036_MAX_LED_MODULES] = {0};
+ int led_number;
+ int ret;
+
+ if (num_leds > 1) {
+ if (num_leds > priv->chip_info->max_modules) {
+ dev_err(priv->dev, "reg property is invalid\n");
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32_array(child, "reg", led_banks, num_leds);
+ if (ret) {
+ dev_err(priv->dev, "reg property is missing\n");
+ return ret;
+ }
+
+ ret = lp50xx_set_banks(priv, led_banks, num_leds);
+ if (ret) {
+ dev_err(priv->dev, "Cannot setup banked LEDs\n");
+ return ret;
+ }
+
+ led->ctrl_bank_enabled = 1;
+ } else {
+ ret = fwnode_property_read_u32(child, "reg", &led_number);
+ if (ret) {
+ dev_err(priv->dev, "led reg property missing\n");
+ return ret;
+ }
+
+ if (led_number > priv->chip_info->num_leds) {
+ dev_err(priv->dev, "led-sources property is invalid\n");
+ return -EINVAL;
+ }
+
+ led->led_number = led_number;
+ }
+
+ return 0;
+}
+
+static int lp50xx_probe_dt(struct lp50xx *priv)
+{
+ struct fwnode_handle *led_node = NULL;
+ struct led_init_data init_data = {};
+ struct led_classdev *led_cdev;
+ struct mc_subled *mc_led_info;
+ struct lp50xx_led *led;
+ int ret = -EINVAL;
+ int num_colors;
+ u32 color_id;
+ int i = 0;
+
+ priv->enable_gpio = devm_gpiod_get_optional(priv->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(priv->enable_gpio))
+ return dev_err_probe(priv->dev, PTR_ERR(priv->enable_gpio),
+ "Failed to get enable GPIO\n");
+
+ ret = lp50xx_enable(priv);
+ if (ret)
+ return ret;
+
+ priv->regulator = devm_regulator_get(priv->dev, "vled");
+ if (IS_ERR(priv->regulator))
+ priv->regulator = NULL;
+
+ device_for_each_child_node_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");
+ return ret;
+ }
+
+ ret = lp50xx_probe_leds(child, priv, led, ret);
+ if (ret)
+ return ret;
+
+ init_data.fwnode = child;
+ num_colors = 0;
+
+ /*
+ * There are only 3 LEDs per module otherwise they should be
+ * banked which also is presented as 3 LEDs.
+ */
+ mc_led_info = devm_kcalloc(priv->dev, LP50XX_LEDS_PER_MODULE,
+ sizeof(*mc_led_info), GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ fwnode_for_each_child_node(child, led_node) {
+ int multi_index;
+ ret = fwnode_property_read_u32(led_node, "color",
+ &color_id);
+ if (ret) {
+ fwnode_handle_put(led_node);
+ dev_err(priv->dev, "Cannot read color\n");
+ return ret;
+ }
+ ret = fwnode_property_read_u32(led_node, "reg", &multi_index);
+ if (ret != 0) {
+ dev_err(priv->dev, "reg must be set\n");
+ return -EINVAL;
+ } else if (multi_index >= LP50XX_LEDS_PER_MODULE) {
+ dev_err(priv->dev, "reg %i out of range\n", multi_index);
+ return -EINVAL;
+ }
+
+ mc_led_info[multi_index].color_index = color_id;
+ num_colors++;
+ }
+
+ led->priv = priv;
+ led->mc_cdev.num_colors = num_colors;
+ led->mc_cdev.subled_info = mc_led_info;
+ led_cdev = &led->mc_cdev.led_cdev;
+ led_cdev->brightness_set_blocking = lp50xx_brightness_set;
+
+ ret = devm_led_classdev_multicolor_register_ext(priv->dev,
+ &led->mc_cdev,
+ &init_data);
+ if (ret) {
+ dev_err(priv->dev, "led register err: %d\n", ret);
+ return ret;
+ }
+ i++;
+ }
+
+ return 0;
+}
+
+static int lp50xx_probe(struct i2c_client *client)
+{
+ struct lp50xx *led;
+ int count;
+ int ret;
+
+ count = device_get_child_node_count(&client->dev);
+ if (!count) {
+ dev_err(&client->dev, "LEDs are not defined in device tree!");
+ return -ENODEV;
+ }
+
+ led = devm_kzalloc(&client->dev, struct_size(led, leds, count),
+ GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ mutex_init(&led->lock);
+ led->client = client;
+ led->dev = &client->dev;
+ led->chip_info = device_get_match_data(&client->dev);
+ i2c_set_clientdata(client, led);
+ led->regmap = devm_regmap_init_i2c(client,
+ led->chip_info->lp50xx_regmap_config);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ return lp50xx_probe_dt(led);
+}
+
+static void lp50xx_remove(struct i2c_client *client)
+{
+ struct lp50xx *led = i2c_get_clientdata(client);
+ int ret;
+
+ ret = lp50xx_disable(led);
+ if (ret)
+ dev_err(led->dev, "Failed to disable chip\n");
+
+ if (led->regulator) {
+ ret = regulator_disable(led->regulator);
+ if (ret)
+ dev_err(led->dev, "Failed to disable regulator\n");
+ }
+
+ mutex_destroy(&led->lock);
+}
+
+static const struct i2c_device_id lp50xx_id[] = {
+ { "lp5009", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5009] },
+ { "lp5012", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5012] },
+ { "lp5018", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5018] },
+ { "lp5024", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5024] },
+ { "lp5030", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5030] },
+ { "lp5036", (kernel_ulong_t)&lp50xx_chip_info_tbl[LP5036] },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp50xx_id);
+
+static const struct of_device_id of_lp50xx_leds_match[] = {
+ { .compatible = "ti,lp5009", .data = &lp50xx_chip_info_tbl[LP5009] },
+ { .compatible = "ti,lp5012", .data = &lp50xx_chip_info_tbl[LP5012] },
+ { .compatible = "ti,lp5018", .data = &lp50xx_chip_info_tbl[LP5018] },
+ { .compatible = "ti,lp5024", .data = &lp50xx_chip_info_tbl[LP5024] },
+ { .compatible = "ti,lp5030", .data = &lp50xx_chip_info_tbl[LP5030] },
+ { .compatible = "ti,lp5036", .data = &lp50xx_chip_info_tbl[LP5036] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_lp50xx_leds_match);
+
+static struct i2c_driver lp50xx_driver = {
+ .driver = {
+ .name = "lp50xx",
+ .of_match_table = of_lp50xx_leds_match,
+ },
+ .probe = lp50xx_probe,
+ .remove = lp50xx_remove,
+ .id_table = lp50xx_id,
+};
+module_i2c_driver(lp50xx_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP50XX LED driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c
index 1392feb1bcf7..7564b9953408 100644
--- a/drivers/leds/leds-lp5521.c
+++ b/drivers/leds/leds-lp5521.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LP5521 LED chip driver.
*
@@ -6,26 +7,12 @@
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
* Milo(Woogyom) Kim <milo.kim@ti.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
-#include <linux/init.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
@@ -35,7 +22,6 @@
#include "leds-lp55xx-common.h"
-#define LP5521_PROGRAM_LENGTH 32
#define LP5521_MAX_LEDS 3
#define LP5521_CMD_DIRECT 0x3F
@@ -72,14 +58,11 @@
/* CONFIG register */
#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */
#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */
-#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */
-#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */
-#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */
-#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */
+#define LP5521_CP_MODE_MASK 0x18 /* Charge pump mode */
+#define LP5521_CP_MODE_SHIFT 3
#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */
#define LP5521_CLK_INT 0x01 /* Internal clock */
-#define LP5521_DEFAULT_CFG \
- (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO)
+#define LP5521_DEFAULT_CFG (LP5521_PWM_HF | LP5521_PWRSAVE_EN)
/* Status */
#define LP5521_EXT_CLK_USED 0x08
@@ -90,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 */
@@ -125,160 +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;
- u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5521_MODE_R_M,
- [LP55XX_ENGINE_2] = LP5521_MODE_G_M,
- [LP55XX_ENGINE_3] = LP5521_MODE_B_M,
- };
-
- 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_engine(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP5521_REG_OP_MODE, 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};
- 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 program_size;
- int nrchars;
- int offset = 0;
- int ret;
- int i;
-
- /* clear program memory before updating */
- for (i = 0; i < LP5521_PROGRAM_LENGTH; i++)
- lp55xx_write(chip, addr[idx] + i, 0);
-
- 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;
-
- 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 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 momery 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)
@@ -308,12 +129,16 @@ static int lp5521_post_init_device(struct lp55xx_chip *chip)
/* Set all PWMs to direct control mode */
ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
+ if (ret)
+ return ret;
/* Update configuration for the clock setting */
val = LP5521_DEFAULT_CFG;
if (!lp55xx_is_extclk_used(chip))
val |= LP5521_CLK_INT;
+ val |= (chip->pdata->charge_pump_mode << LP5521_CP_MODE_SHIFT) & LP5521_CP_MODE_MASK;
+
ret = lp55xx_write(chip, LP5521_REG_CONFIG, val);
if (ret)
return ret;
@@ -353,18 +178,6 @@ static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
return 0;
}
-static void lp5521_led_brightness_work(struct work_struct *work)
-{
- struct lp55xx_led *led = container_of(work, struct lp55xx_led,
- brightness_work);
- struct lp55xx_chip *chip = led->chip;
-
- mutex_lock(&chip->lock);
- lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
-}
-
static ssize_t lp5521_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -373,17 +186,29 @@ 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 scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK");
+ return sysfs_emit(buf, "%s\n", ret ? "FAIL" : "OK");
}
/* device attributes */
-static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL);
+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[] = {
+ &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_selftest.attr,
NULL
};
@@ -394,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,
@@ -402,113 +233,45 @@ static struct lp55xx_device_config lp5521_cfg = {
.addr = LP5521_REG_ENABLE,
.val = LP5521_ENABLE_DEFAULT,
},
+ .prog_mem_base = {
+ .addr = LP5521_REG_R_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5521_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5521_REG_LED_CURRENT_BASE,
+ },
.max_channel = LP5521_MAX_LEDS,
.post_init_device = lp5521_post_init_device,
- .brightness_work_fn = lp5521_led_brightness_work,
- .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)
-{
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
- struct device_node *np = client->dev.of_node;
-
- if (!client->dev.platform_data) {
- if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
- pdata = client->dev.platform_data;
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- led = devm_kzalloc(&client->dev,
- sizeof(*led) * pdata->num_channels, GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
- chip->cfg = &lp5521_cfg;
-
- 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_register_leds;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_register_sysfs;
- }
-
- return 0;
-
-err_register_sysfs:
- lp55xx_unregister_leds(led, chip);
-err_register_leds:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static int lp5521_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp5521_stop_engine(chip);
- lp55xx_unregister_sysfs(chip);
- lp55xx_unregister_leds(led, chip);
- lp55xx_deinit_device(chip);
-
- return 0;
-}
-
static const struct i2c_device_id lp5521_id[] = {
- { "lp5521", 0 }, /* Three channel chip */
+ { "lp5521", .driver_data = (kernel_ulong_t)&lp5521_cfg, }, /* Three channel chip */
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5521_id);
-#ifdef CONFIG_OF
static const struct of_device_id of_lp5521_leds_match[] = {
- { .compatible = "national,lp5521", },
+ { .compatible = "national,lp5521", .data = &lp5521_cfg, },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5521_leds_match);
-#endif
+
static struct i2c_driver lp5521_driver = {
.driver = {
.name = "lp5521",
- .of_match_table = of_match_ptr(of_lp5521_leds_match),
+ .of_match_table = of_lp5521_leds_match,
},
- .probe = lp5521_probe,
- .remove = lp5521_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5521_id,
};
diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
index 3979428f3100..4ed3e735260c 100644
--- a/drivers/leds/leds-lp5523.c
+++ b/drivers/leds/leds-lp5523.c
@@ -1,40 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
- * lp5523.c - LP5523 LED Driver
+ * lp5523.c - LP5523, LP55231 LED Driver
*
* Copyright (C) 2010 Nokia Corporation
* Copyright (C) 2012 Texas Instruments
*
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
* Milo(Woogyom) Kim <milo.kim@ti.com>
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.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/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#include "leds-lp55xx-common.h"
-#define LP5523_PROGRAM_LENGTH 32
+/* Memory is used like this:
+ * 0x00 engine 1 program
+ * 0x10 engine 2 program
+ * 0x20 engine 3 program
+ * 0x30 engine 1 muxing info
+ * 0x40 engine 2 muxing info
+ * 0x50 engine 3 muxing info
+ */
+#define LP5523_PAGES_PER_ENGINE 1
#define LP5523_MAX_LEDS 9
/* Registers */
@@ -42,13 +38,21 @@
#define LP5523_REG_OP_MODE 0x01
#define LP5523_REG_ENABLE_LEDS_MSB 0x04
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
+#define LP5523_REG_LED_CTRL_BASE 0x06
#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
+#define LP5523_REG_MASTER_FADER_BASE 0x48
+#define LP5523_REG_CH1_PROG_START 0x4C
+#define LP5523_REG_CH2_PROG_START 0x4D
+#define LP5523_REG_CH3_PROG_START 0x4E
#define LP5523_REG_PROG_PAGE_SEL 0x4F
#define LP5523_REG_PROG_MEM 0x50
@@ -57,63 +61,25 @@
#define LP5523_AUTO_INC 0x40
#define LP5523_PWR_SAVE 0x20
#define LP5523_PWM_PWR_SAVE 0x04
-#define LP5523_CP_AUTO 0x18
+#define LP5523_CP_MODE_MASK 0x18
+#define LP5523_CP_MODE_SHIFT 3
#define LP5523_AUTO_CLK 0x02
+#define LP5523_DEFAULT_CONFIG \
+ (LP5523_AUTO_INC | LP5523_PWR_SAVE | LP5523_AUTO_CLK | LP5523_PWM_PWR_SAVE)
#define LP5523_EN_LEDTEST 0x80
#define LP5523_LEDTEST_DONE 0x80
#define LP5523_RESET 0xFF
#define LP5523_ADC_SHORTCIRC_LIM 80
#define LP5523_EXT_CLK_USED 0x08
+#define LP5523_ENG_STATUS_MASK 0x07
-/* Memory Page Selection */
-#define LP5523_PAGE_ENG1 0
-#define LP5523_PAGE_ENG2 1
-#define LP5523_PAGE_ENG3 2
-
-/* 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
-
-enum lp5523_chip_id {
- LP5523,
- LP55231,
-};
-
-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_init_program_engine(struct lp55xx_chip *chip);
static int lp5523_post_init_device(struct lp55xx_chip *chip)
{
int ret;
+ int val;
ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE);
if (ret)
@@ -122,10 +88,10 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip)
/* Chip startup time is 500 us, 1 - 2 ms gives some margin */
usleep_range(1000, 2000);
- ret = lp55xx_write(chip, LP5523_REG_CONFIG,
- LP5523_AUTO_INC | LP5523_PWR_SAVE |
- LP5523_CP_AUTO | LP5523_AUTO_CLK |
- LP5523_PWM_PWR_SAVE);
+ val = LP5523_DEFAULT_CONFIG;
+ val |= (chip->pdata->charge_pump_mode << LP5523_CP_MODE_SHIFT) & LP5523_CP_MODE_MASK;
+
+ ret = lp55xx_write(chip, LP5523_REG_CONFIG, val);
if (ret)
return ret;
@@ -134,164 +100,83 @@ static int lp5523_post_init_device(struct lp55xx_chip *chip)
if (ret)
return ret;
- return lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
-}
-
-static void lp5523_load_engine(struct lp55xx_chip *chip)
-{
- enum lp55xx_engine_index idx = chip->engine_idx;
- u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
- };
-
- u8 val[] = {
- [LP55XX_ENGINE_1] = LP5523_LOAD_ENG1,
- [LP55XX_ENGINE_2] = LP5523_LOAD_ENG2,
- [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3,
- };
-
- u8 page_sel[] = {
- [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
- [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
- [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
- };
-
- lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
-
- lp5523_wait_opmode_done();
-
- lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
-}
-
-static void lp5523_stop_engine(struct lp55xx_chip *chip)
-{
- lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
- lp5523_wait_opmode_done();
-}
-
-static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
-{
- int i;
+ ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
+ if (ret)
+ return ret;
- for (i = 0; i < LP5523_MAX_LEDS; i++)
- lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
+ return lp5523_init_program_engine(chip);
}
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_update_program_memory(struct lp55xx_chip *chip,
- const u8 *data, size_t size)
+static int lp5523_init_program_engine(struct lp55xx_chip *chip)
{
- u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
- unsigned cmd;
- char c[3];
- int update_size;
- int nrchars;
- int offset = 0;
- int ret;
int i;
+ int j;
+ int ret;
+ u8 status;
+ /* one pattern per engine setting LED MUX start and stop addresses */
+ 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},
+ };
- /* clear program memory before updating */
- for (i = 0; i < LP5523_PROGRAM_LENGTH; i++)
- lp55xx_write(chip, LP5523_REG_PROG_MEM + i, 0);
-
- 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++;
- }
+ /* hardcode 32 bytes of memory for each engine from program memory */
+ ret = lp55xx_write(chip, LP5523_REG_CH1_PROG_START, 0x00);
+ if (ret)
+ return ret;
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto err;
+ ret = lp55xx_write(chip, LP5523_REG_CH2_PROG_START, 0x10);
+ if (ret)
+ return ret;
- update_size = i;
- for (i = 0; i < update_size; i++)
- lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
+ ret = lp55xx_write(chip, LP5523_REG_CH3_PROG_START, 0x20);
+ if (ret)
+ return ret;
- return 0;
+ /* write 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);
-err:
- dev_err(&chip->cl->dev, "wrong pattern format\n");
- return -EINVAL;
-}
+ for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
+ ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j,
+ pattern[i - 1][j]);
+ if (ret)
+ goto out;
+ }
+ }
-static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
-{
- const struct firmware *fw = chip->fw;
+ lp5523_run_engine(chip, true);
- if (fw->size > LP5523_PROGRAM_LENGTH) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
- return;
+ /* Let the programs run for couple of ms and check the engine status */
+ usleep_range(3000, 6000);
+ ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
+ if (ret)
+ goto out;
+ status &= LP5523_ENG_STATUS_MASK;
+
+ if (status != LP5523_ENG_STATUS_MASK) {
+ dev_err(&chip->cl->dev,
+ "could not configure LED engine, status = 0x%.2x\n",
+ status);
+ ret = -1;
}
- /*
- * Program momery sequence
- * 1) set engine mode to "LOAD"
- * 2) write firmware data into program memory
- */
-
- lp5523_load_engine(chip);
- lp5523_update_program_memory(chip, fw->data, fw->size);
+out:
+ lp55xx_stop_all_engine(chip);
+ return ret;
}
static ssize_t lp5523_selftest(struct device *dev,
@@ -301,19 +186,19 @@ static ssize_t lp5523_selftest(struct device *dev,
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
struct lp55xx_platform_data *pdata = chip->pdata;
- int i, ret, pos = 0;
- u8 status, adc, vdd;
+ 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) */
@@ -321,82 +206,92 @@ 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 */
- for (i = 0; i < LP5523_MAX_LEDS; i++) {
- /* Skip non-existing channels */
+ for (i = 0; i < pdata->num_channels; i++) {
+ /* Skip disabled channels */
if (pdata->led_config[i].led_current == 0)
continue;
/* Set default current */
- lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
+ lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
pdata->led_config[i].led_current);
- lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0xff);
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
+ 0xff);
/* let current stabilize 2 - 4ms before measurements start */
usleep_range(2000, 4000);
lp55xx_write(chip, LP5523_REG_LED_TEST_CTRL,
- LP5523_EN_LEDTEST | i);
+ LP5523_EN_LEDTEST | led->chan_nr);
/* ADC conversion time is 2.7 ms typically */
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. */
+ 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", i);
+ pos += sysfs_emit_at(buf, pos, "LED %d FAIL\n",
+ led->chan_nr);
- lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0x00);
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
+ 0x00);
/* Restore current */
- lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
- led->led_current);
+ lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
+ 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;
+ return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
}
-static void lp5523_led_brightness_work(struct work_struct *work)
-{
- struct lp55xx_led *led = container_of(work, struct lp55xx_led,
- brightness_work);
- struct lp55xx_chip *chip = led->chip;
-
- mutex_lock(&chip->lock);
- lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
-}
-
-static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL);
+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);
+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,
+ &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,
};
@@ -406,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,
@@ -414,116 +319,55 @@ static struct lp55xx_device_config lp5523_cfg = {
.addr = LP5523_REG_ENABLE,
.val = LP5523_ENABLE,
},
+ .prog_mem_base = {
+ .addr = LP5523_REG_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5523_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5523_REG_LED_CURRENT_BASE,
+ },
+ .reg_master_fader_base = {
+ .addr = LP5523_REG_MASTER_FADER_BASE,
+ },
+ .reg_led_ctrl_base = {
+ .addr = LP5523_REG_LED_CTRL_BASE,
+ },
+ .pages_per_engine = LP5523_PAGES_PER_ENGINE,
.max_channel = LP5523_MAX_LEDS,
.post_init_device = lp5523_post_init_device,
- .brightness_work_fn = lp5523_led_brightness_work,
- .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)
-{
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
- struct device_node *np = client->dev.of_node;
-
- if (!client->dev.platform_data) {
- if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
- pdata = client->dev.platform_data;
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- led = devm_kzalloc(&client->dev,
- sizeof(*led) * pdata->num_channels, GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
- chip->cfg = &lp5523_cfg;
-
- 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_register_leds;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_register_sysfs;
- }
-
- return 0;
-
-err_register_sysfs:
- lp55xx_unregister_leds(led, chip);
-err_register_leds:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static int lp5523_remove(struct i2c_client *client)
-{
- struct lp55xx_led *led = i2c_get_clientdata(client);
- struct lp55xx_chip *chip = led->chip;
-
- lp5523_stop_engine(chip);
- lp55xx_unregister_sysfs(chip);
- lp55xx_unregister_leds(led, chip);
- lp55xx_deinit_device(chip);
-
- return 0;
-}
-
static const struct i2c_device_id lp5523_id[] = {
- { "lp5523", LP5523 },
- { "lp55231", LP55231 },
+ { "lp5523", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
+ { "lp55231", .driver_data = (kernel_ulong_t)&lp5523_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5523_id);
-#ifdef CONFIG_OF
static const struct of_device_id of_lp5523_leds_match[] = {
- { .compatible = "national,lp5523", },
+ { .compatible = "national,lp5523", .data = &lp5523_cfg, },
+ { .compatible = "ti,lp55231", .data = &lp5523_cfg, },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5523_leds_match);
-#endif
static struct i2c_driver lp5523_driver = {
.driver = {
.name = "lp5523x",
- .of_match_table = of_match_ptr(of_lp5523_leds_match),
+ .of_match_table = of_lp5523_leds_match,
},
- .probe = lp5523_probe,
- .remove = lp5523_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5523_id,
};
diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c
index cbd856dac150..14a4af361b26 100644
--- a/drivers/leds/leds-lp5562.c
+++ b/drivers/leds/leds-lp5562.c
@@ -1,28 +1,25 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LP5562 LED driver
*
* Copyright (C) 2013 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
+#include <linux/cleanup.h>
#include <linux/delay.h>
#include <linux/firmware.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/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
#include "leds-lp55xx-common.h"
-#define LP5562_PROGRAM_LENGTH 32
#define LP5562_MAX_LEDS 4
/* ENABLE Register 00h */
@@ -41,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
@@ -116,7 +98,7 @@ static inline void lp5562_wait_enable_done(void)
static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current)
{
- u8 addr[] = {
+ static const u8 addr[] = {
LP5562_REG_R_CURRENT,
LP5562_REG_G_CURRENT,
LP5562_REG_B_CURRENT,
@@ -127,156 +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;
- u8 mask[] = {
- [LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M,
- [LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M,
- [LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M,
- };
-
- 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};
- 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;
-
- if (fw->size > LP5562_PROGRAM_LENGTH) {
- dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
- fw->size);
- return;
- }
-
- /*
- * Program momery 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)
@@ -311,21 +161,46 @@ static int lp5562_post_init_device(struct lp55xx_chip *chip)
return 0;
}
-static void lp5562_led_brightness_work(struct work_struct *work)
+static int lp5562_multicolor_brightness(struct lp55xx_led *led)
{
- struct lp55xx_led *led = container_of(work, struct lp55xx_led,
- brightness_work);
struct lp55xx_chip *chip = led->chip;
- u8 addr[] = {
+ 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;
+ static const u8 addr[] = {
+ LP5562_REG_R_PWM,
+ LP5562_REG_G_PWM,
+ LP5562_REG_B_PWM,
+ LP5562_REG_W_PWM,
+ };
+ int ret;
+
+ guard(mutex)(&chip->lock);
- mutex_lock(&chip->lock);
- lp55xx_write(chip, addr[led->chan_nr], led->brightness);
- mutex_unlock(&chip->lock);
+ ret = lp55xx_write(chip, addr[led->chan_nr], led->brightness);
+
+ return ret;
}
static void lp5562_write_program_memory(struct lp55xx_chip *chip,
@@ -346,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)
@@ -367,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);
@@ -375,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 */
@@ -418,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;
@@ -470,15 +345,15 @@ 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;
}
-static DEVICE_ATTR(led_pattern, S_IWUSR, NULL, lp5562_store_pattern);
-static DEVICE_ATTR(engine_mux, S_IWUSR, NULL, lp5562_store_engine_mux);
+static LP55XX_DEV_ATTR_WO(led_pattern, lp5562_store_pattern);
+static LP55XX_DEV_ATTR_WO(engine_mux, lp5562_store_engine_mux);
static struct attribute *lp5562_attributes[] = {
&dev_attr_led_pattern.attr,
@@ -493,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,
@@ -501,112 +382,38 @@ static struct lp55xx_device_config lp5562_cfg = {
.addr = LP5562_REG_ENABLE,
.val = LP5562_ENABLE_DEFAULT,
},
+ .prog_mem_base = {
+ .addr = LP5562_REG_PROG_MEM_ENG1,
+ },
.post_init_device = lp5562_post_init_device,
.set_led_current = lp5562_set_led_current,
- .brightness_work_fn = lp5562_led_brightness_work,
+ .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,
- const struct i2c_device_id *id)
-{
- int ret;
- struct lp55xx_chip *chip;
- struct lp55xx_led *led;
- struct lp55xx_platform_data *pdata;
- struct device_node *np = client->dev.of_node;
-
- if (!client->dev.platform_data) {
- if (np) {
- ret = lp55xx_of_populate_pdata(&client->dev, np);
- if (ret < 0)
- return ret;
- } else {
- dev_err(&client->dev, "no platform data\n");
- return -EINVAL;
- }
- }
- pdata = client->dev.platform_data;
-
- chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
- if (!chip)
- return -ENOMEM;
-
- led = devm_kzalloc(&client->dev,
- sizeof(*led) * pdata->num_channels, GFP_KERNEL);
- if (!led)
- return -ENOMEM;
-
- chip->cl = client;
- chip->pdata = pdata;
- chip->cfg = &lp5562_cfg;
-
- 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_register_leds;
-
- ret = lp55xx_register_sysfs(chip);
- if (ret) {
- dev_err(&client->dev, "registering sysfs failed\n");
- goto err_register_sysfs;
- }
-
- return 0;
-
-err_register_sysfs:
- lp55xx_unregister_leds(led, chip);
-err_register_leds:
- lp55xx_deinit_device(chip);
-err_init:
- return ret;
-}
-
-static int 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_unregister_leds(led, chip);
- lp55xx_deinit_device(chip);
-
- return 0;
-}
-
static const struct i2c_device_id lp5562_id[] = {
- { "lp5562", 0 },
+ { "lp5562", .driver_data = (kernel_ulong_t)&lp5562_cfg, },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5562_id);
-#ifdef CONFIG_OF
static const struct of_device_id of_lp5562_leds_match[] = {
- { .compatible = "ti,lp5562", },
+ { .compatible = "ti,lp5562", .data = &lp5562_cfg, },
{},
};
MODULE_DEVICE_TABLE(of, of_lp5562_leds_match);
-#endif
static struct i2c_driver lp5562_driver = {
.driver = {
.name = "lp5562",
- .of_match_table = of_match_ptr(of_lp5562_leds_match),
+ .of_match_table = of_lp5562_leds_match,
},
- .probe = lp5562_probe,
- .remove = lp5562_remove,
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
.id_table = lp5562_id,
};
diff --git a/drivers/leds/leds-lp5569.c b/drivers/leds/leds-lp5569.c
new file mode 100644
index 000000000000..786f2aa35319
--- /dev/null
+++ b/drivers/leds/leds-lp5569.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2024 Christian Marangi <ansuelsmth@gmail.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/iopoll.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_data/leds-lp55xx.h>
+#include <linux/slab.h>
+#include <dt-bindings/leds/leds-lp55xx.h>
+
+#include "leds-lp55xx-common.h"
+
+#define LP5569_MAX_LEDS 9
+
+/* Memory is used like this:
+ * 0x00 engine 1 program (4 pages)
+ * 0x40 engine 2 program (4 pages)
+ * 0x80 engine 3 program (4 pages)
+ * 0xc0 engine 1 muxing info (1 page)
+ * 0xd0 engine 2 muxing info (1 page)
+ * 0xe0 engine 3 muxing info (1 page)
+ */
+#define LP5569_PAGES_PER_ENGINE 4
+
+#define LP5569_REG_ENABLE 0x00
+#define LP5569_ENABLE BIT(6)
+
+#define LP5569_REG_EXEC_CTRL 0x01
+#define LP5569_MODE_ENG_SHIFT 2
+
+#define LP5569_REG_OP_MODE 0x02
+#define LP5569_EXEC_ENG_SHIFT 2
+
+#define LP5569_REG_ENABLE_LEDS_MSB 0x04
+#define LP5569_REG_ENABLE_LEDS_LSB 0x05
+#define LP5569_REG_LED_CTRL_BASE 0x07
+#define LP5569_FADER_MAPPING_MASK GENMASK(7, 5)
+#define LP5569_REG_LED_PWM_BASE 0x16
+#define LP5569_REG_LED_CURRENT_BASE 0x22
+#define LP5569_REG_MISC 0x2F
+#define LP5569_AUTO_INC BIT(6)
+#define LP5569_PWR_SAVE BIT(5)
+#define LP5569_CP_MODE_MASK GENMASK(4, 3)
+#define LP5569_PWM_PWR_SAVE BIT(2)
+#define LP5569_INTERNAL_CLK BIT(0)
+#define LP5569_REG_MISC2 0x33
+#define LP5569_LED_SHORT_TEST BIT(4)
+#define LP5569_LED_OPEN_TEST BIT(3)
+#define LP5569_REG_STATUS 0x3C
+#define LP5569_MASK_BUSY BIT(7)
+#define LP5569_STARTUP_BUSY BIT(6)
+#define LP5569_ENGINE_BUSY BIT(5)
+#define LP5569_ENGINE1_INT BIT(2)
+#define LP5569_ENGINE2_INT BIT(1)
+#define LP5569_ENGINE3_INT BIT(0)
+#define LP5569_ENG_STATUS_MASK (LP5569_ENGINE1_INT | LP5569_ENGINE2_INT | \
+ LP5569_ENGINE3_INT)
+#define LP5569_REG_IO_CONTROL 0x3D
+#define LP5569_CLK_OUTPUT BIT(3)
+#define LP5569_REG_RESET 0x3F
+#define LP5569_RESET 0xFF
+#define LP5569_REG_MASTER_FADER_BASE 0x46
+#define LP5569_REG_CH1_PROG_START 0x4B
+#define LP5569_REG_CH2_PROG_START 0x4C
+#define LP5569_REG_CH3_PROG_START 0x4D
+#define LP5569_REG_PROG_PAGE_SEL 0x4F
+#define LP5569_REG_PROG_MEM 0x50
+#define LP5569_REG_LED_FAULT1 0x81
+#define LP5569_LED_FAULT8 BIT(0)
+#define LP5569_REG_LED_FAULT2 0x82
+#define LP5569_LED_FAULT7 BIT(7)
+#define LP5569_LED_FAULT6 BIT(6)
+#define LP5569_LED_FAULT5 BIT(5)
+#define LP5569_LED_FAULT4 BIT(4)
+#define LP5569_LED_FAULT3 BIT(3)
+#define LP5569_LED_FAULT2 BIT(2)
+#define LP5569_LED_FAULT1 BIT(1)
+#define LP5569_LED_FAULT0 BIT(0)
+
+#define LP5569_ENG1_PROG_ADDR 0x0
+#define LP5569_ENG2_PROG_ADDR 0x40
+#define LP5569_ENG3_PROG_ADDR 0x80
+#define LP5569_ENG1_MUX_ADDR 0xc0
+#define LP5569_ENG2_MUX_ADDR 0xd0
+#define LP5569_ENG3_MUX_ADDR 0xe0
+
+#define LP5569_STARTUP_SLEEP 500
+
+#define LEDn_STATUS_FAULT(n, status) ((status) >> (n) & BIT(0))
+
+#define LP5569_DEFAULT_CONFIG \
+ (LP5569_AUTO_INC | LP5569_PWR_SAVE | LP5569_PWM_PWR_SAVE)
+
+static void lp5569_run_engine(struct lp55xx_chip *chip, bool start)
+{
+ if (!start) {
+ lp55xx_stop_engine(chip);
+ lp55xx_turn_off_channels(chip);
+ return;
+ }
+
+ lp55xx_run_engine_common(chip);
+}
+
+static int lp5569_init_program_engine(struct lp55xx_chip *chip)
+{
+ int i;
+ int j;
+ int ret;
+ u8 status;
+ /* Precompiled pattern per ENGINE setting LED MUX start and stop addresses */
+ static const u8 pattern[][LP55xx_BYTES_PER_PAGE] = {
+ { 0x9c, LP5569_ENG1_MUX_ADDR, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ { 0x9c, LP5569_ENG2_MUX_ADDR, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ { 0x9c, LP5569_ENG3_MUX_ADDR, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0},
+ };
+
+ /* Setup each ENGINE program start address */
+ ret = lp55xx_write(chip, LP5569_REG_CH1_PROG_START, LP5569_ENG1_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5569_REG_CH2_PROG_START, LP5569_ENG2_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5569_REG_CH3_PROG_START, LP5569_ENG3_PROG_ADDR);
+ if (ret)
+ return ret;
+
+ /* Write precompiled pattern for LED MUX address space for each ENGINE */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ chip->engine_idx = i;
+ lp55xx_load_engine(chip);
+
+ for (j = 0; j < LP55xx_BYTES_PER_PAGE; j++) {
+ ret = lp55xx_write(chip, LP5569_REG_PROG_MEM + j,
+ pattern[i - 1][j]);
+ if (ret)
+ goto out;
+ }
+ }
+
+ lp5569_run_engine(chip, true);
+
+ /* Let the programs run for couple of ms and check the engine status */
+ usleep_range(3000, 6000);
+ lp55xx_read(chip, LP5569_REG_STATUS, &status);
+ status = FIELD_GET(LP5569_ENG_STATUS_MASK, status);
+
+ if (status != LP5569_ENG_STATUS_MASK) {
+ dev_err(&chip->cl->dev,
+ "could not configure LED engine, status = 0x%.2x\n",
+ status);
+ ret = -EINVAL;
+ }
+
+out:
+ lp55xx_stop_all_engine(chip);
+ return ret;
+}
+
+static int lp5569_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
+ u8 val;
+
+ val = LP5569_DEFAULT_CONFIG;
+ val |= FIELD_PREP(LP5569_CP_MODE_MASK, chip->pdata->charge_pump_mode);
+ ret = lp55xx_write(chip, LP5569_REG_MISC, val);
+ if (ret)
+ return ret;
+
+ if (chip->pdata->clock_mode == LP55XX_CLOCK_INT) {
+ /* Internal clock MUST be configured before CLK output */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ LP5569_INTERNAL_CLK,
+ LP5569_INTERNAL_CLK);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_update_bits(chip, LP5569_REG_IO_CONTROL,
+ LP5569_CLK_OUTPUT,
+ LP5569_CLK_OUTPUT);
+ if (ret)
+ return ret;
+ }
+
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ return ret;
+
+ read_poll_timeout(lp55xx_read, ret, !(val & LP5569_STARTUP_BUSY),
+ LP5569_STARTUP_SLEEP, LP5569_STARTUP_SLEEP * 10, false,
+ chip, LP5569_REG_STATUS, &val);
+
+ return lp5569_init_program_engine(chip);
+}
+
+static ssize_t lp5569_led_open_test(struct lp55xx_led *led, char *buf)
+{
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ bool leds_fault[LP5569_MAX_LEDS];
+ struct lp55xx_led *led_tmp = led;
+ int i, ret, pos = 0;
+ u8 status;
+
+ /* Set in STANDBY state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
+ if (ret)
+ goto exit;
+
+ /* Wait 1ms for device to enter STANDBY state */
+ usleep_range(1000, 2000);
+
+ /* Set Charge Pump to 1.5x */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BOOST),
+ LP5569_CP_MODE_MASK);
+ if (ret)
+ goto exit;
+
+ /* Enable LED Open Test */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
+ LP5569_LED_OPEN_TEST);
+ if (ret)
+ goto exit;
+
+ /* Put Device in NORMAL state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to enter NORMAL state */
+ usleep_range(500, 750);
+
+ /* Enable LED and set to 100% brightness */
+ for (i = 0; i < pdata->num_channels; i++) {
+ ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ led_tmp++;
+ }
+
+ /* Wait 500 us for device to fill status regs */
+ usleep_range(500, 750);
+
+ /* Parse status led fault 1 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 8; i++)
+ leds_fault[i] = !!((status >> i) & 0x1);
+
+ /* Parse status led fault 2 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 1; i++)
+ leds_fault[i + 8] = !!((status >> i) & 0x1);
+
+ /* Report LED fault */
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ if (leds_fault[led_tmp->chan_nr])
+ pos += sysfs_emit_at(buf, pos, "LED %d OPEN FAIL\n",
+ led_tmp->chan_nr);
+
+ led_tmp++;
+ }
+
+ ret = pos;
+
+exit:
+ /* Disable LED Open Test */
+ lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST, 0);
+
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
+
+ led_tmp++;
+ }
+
+ return ret;
+}
+
+static ssize_t lp5569_led_short_test(struct lp55xx_led *led, char *buf)
+{
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ bool leds_fault[LP5569_MAX_LEDS];
+ struct lp55xx_led *led_tmp = led;
+ int i, ret, pos = 0;
+ u8 status;
+
+ /* Set in STANDBY state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, 0);
+ if (ret)
+ goto exit;
+
+ /* Wait 1ms for device to enter STANDBY state */
+ usleep_range(1000, 2000);
+
+ /* Set Charge Pump to 1x */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC,
+ FIELD_PREP(LP5569_CP_MODE_MASK, LP55XX_CP_BYPASS),
+ LP5569_CP_MODE_MASK);
+ if (ret)
+ goto exit;
+
+ /* Enable LED and set to 100% brightness and current to 100% (25.5mA) */
+ for (i = 0; i < pdata->num_channels; i++) {
+ ret = lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ ret = lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led_tmp->chan_nr,
+ LED_FULL);
+ if (ret)
+ goto exit;
+
+ led_tmp++;
+ }
+
+ /* Put Device in NORMAL state */
+ ret = lp55xx_write(chip, LP5569_REG_ENABLE, LP5569_ENABLE);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to enter NORMAL state */
+ usleep_range(500, 750);
+
+ /* Enable LED Shorted Test */
+ ret = lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_OPEN_TEST,
+ LP5569_LED_SHORT_TEST);
+ if (ret)
+ goto exit;
+
+ /* Wait 500 us for device to fill status regs */
+ usleep_range(500, 750);
+
+ /* Parse status led fault 1 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT1, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 8; i++)
+ leds_fault[i] = !!LEDn_STATUS_FAULT(i, status);
+
+ /* Parse status led fault 2 regs */
+ ret = lp55xx_read(chip, LP5569_REG_LED_FAULT2, &status);
+ if (ret < 0)
+ goto exit;
+
+ for (i = 0; i < 1; i++)
+ leds_fault[i + 8] = !!LEDn_STATUS_FAULT(i, status);
+
+ /* Report LED fault */
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ if (leds_fault[led_tmp->chan_nr])
+ pos += sysfs_emit_at(buf, pos, "LED %d SHORTED FAIL\n",
+ led_tmp->chan_nr);
+
+ led_tmp++;
+ }
+
+ ret = pos;
+
+exit:
+ /* Disable LED Shorted Test */
+ lp55xx_update_bits(chip, LP5569_REG_MISC2, LP5569_LED_SHORT_TEST, 0);
+
+ led_tmp = led;
+ for (i = 0; i < pdata->num_channels; i++) {
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led_tmp->chan_nr, 0);
+
+ led_tmp++;
+ }
+
+ return ret;
+}
+
+static ssize_t lp5569_selftest(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int i, pos = 0;
+
+ guard(mutex)(&chip->lock);
+
+ /* Test LED Open */
+ pos = lp5569_led_open_test(led, buf);
+ if (pos < 0)
+ return sprintf(buf, "FAIL\n");
+
+ /* Test LED Shorted */
+ pos += lp5569_led_short_test(led, buf);
+ if (pos < 0)
+ return sprintf(buf, "FAIL\n");
+
+ for (i = 0; i < chip->pdata->num_channels; i++) {
+ /* Restore current */
+ lp55xx_write(chip, LP5569_REG_LED_CURRENT_BASE + led->chan_nr,
+ led->led_current);
+
+ /* Restore brightness */
+ lp55xx_write(chip, LP5569_REG_LED_PWM_BASE + led->chan_nr,
+ led->brightness);
+ led++;
+ }
+
+ return pos == 0 ? sysfs_emit(buf, "OK\n") : pos;
+}
+
+LP55XX_DEV_ATTR_ENGINE_MODE(1);
+LP55XX_DEV_ATTR_ENGINE_MODE(2);
+LP55XX_DEV_ATTR_ENGINE_MODE(3);
+LP55XX_DEV_ATTR_ENGINE_LEDS(1);
+LP55XX_DEV_ATTR_ENGINE_LEDS(2);
+LP55XX_DEV_ATTR_ENGINE_LEDS(3);
+LP55XX_DEV_ATTR_ENGINE_LOAD(1);
+LP55XX_DEV_ATTR_ENGINE_LOAD(2);
+LP55XX_DEV_ATTR_ENGINE_LOAD(3);
+static LP55XX_DEV_ATTR_RO(selftest, lp5569_selftest);
+LP55XX_DEV_ATTR_MASTER_FADER(1);
+LP55XX_DEV_ATTR_MASTER_FADER(2);
+LP55XX_DEV_ATTR_MASTER_FADER(3);
+static LP55XX_DEV_ATTR_RW(master_fader_leds, lp55xx_show_master_fader_leds,
+ lp55xx_store_master_fader_leds);
+
+static struct attribute *lp5569_attributes[] = {
+ &dev_attr_engine1_mode.attr,
+ &dev_attr_engine2_mode.attr,
+ &dev_attr_engine3_mode.attr,
+ &dev_attr_engine1_load.attr,
+ &dev_attr_engine2_load.attr,
+ &dev_attr_engine3_load.attr,
+ &dev_attr_engine1_leds.attr,
+ &dev_attr_engine2_leds.attr,
+ &dev_attr_engine3_leds.attr,
+ &dev_attr_selftest.attr,
+ &dev_attr_master_fader1.attr,
+ &dev_attr_master_fader2.attr,
+ &dev_attr_master_fader3.attr,
+ &dev_attr_master_fader_leds.attr,
+ NULL,
+};
+
+static const struct attribute_group lp5569_group = {
+ .attrs = lp5569_attributes,
+};
+
+/* Chip specific configurations */
+static struct lp55xx_device_config lp5569_cfg = {
+ .reg_op_mode = {
+ .addr = LP5569_REG_OP_MODE,
+ .shift = LP5569_MODE_ENG_SHIFT,
+ },
+ .reg_exec = {
+ .addr = LP5569_REG_EXEC_CTRL,
+ .shift = LP5569_EXEC_ENG_SHIFT,
+ },
+ .reset = {
+ .addr = LP5569_REG_RESET,
+ .val = LP5569_RESET,
+ },
+ .enable = {
+ .addr = LP5569_REG_ENABLE,
+ .val = LP5569_ENABLE,
+ },
+ .prog_mem_base = {
+ .addr = LP5569_REG_PROG_MEM,
+ },
+ .reg_led_pwm_base = {
+ .addr = LP5569_REG_LED_PWM_BASE,
+ },
+ .reg_led_current_base = {
+ .addr = LP5569_REG_LED_CURRENT_BASE,
+ },
+ .reg_master_fader_base = {
+ .addr = LP5569_REG_MASTER_FADER_BASE,
+ },
+ .reg_led_ctrl_base = {
+ .addr = LP5569_REG_LED_CTRL_BASE,
+ },
+ .pages_per_engine = LP5569_PAGES_PER_ENGINE,
+ .max_channel = LP5569_MAX_LEDS,
+ .post_init_device = lp5569_post_init_device,
+ .brightness_fn = lp55xx_led_brightness,
+ .multicolor_brightness_fn = lp55xx_multicolor_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
+ .run_engine = lp5569_run_engine,
+ .dev_attr_group = &lp5569_group,
+};
+
+static const struct i2c_device_id lp5569_id[] = {
+ { "lp5569", .driver_data = (kernel_ulong_t)&lp5569_cfg, },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, lp5569_id);
+
+static const struct of_device_id of_lp5569_leds_match[] = {
+ { .compatible = "ti,lp5569", .data = &lp5569_cfg, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_lp5569_leds_match);
+
+static struct i2c_driver lp5569_driver = {
+ .driver = {
+ .name = "lp5569",
+ .of_match_table = of_lp5569_leds_match,
+ },
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
+ .id_table = lp5569_id,
+};
+
+module_i2c_driver(lp5569_driver);
+
+MODULE_AUTHOR("Christian Marangi <ansuelsmth@gmail.com>");
+MODULE_DESCRIPTION("LP5569 LED engine");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index c2fecd4d391c..fd447eb7eb15 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LP5521/LP5523/LP55231/LP5562 Common Driver
*
@@ -5,24 +6,69 @@
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
* 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>
#include <linux/slab.h>
+#include <linux/gpio/consumer.h>
+#include <dt-bindings/leds/leds-lp55xx.h>
#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
@@ -36,9 +82,264 @@ static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
return cdev_to_lp55xx_led(dev_get_drvdata(dev));
}
+static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
+{
+ return container_of(mc_cdev, struct lp55xx_led, mc_cdev);
+}
+
+static void lp55xx_wait_opmode_done(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int __always_unused ret;
+ u8 val;
+
+ /*
+ * Recent chip supports BUSY bit for engine.
+ * Check support by checking if val is not 0.
+ * For legacy device, sleep at least 153 us.
+ */
+ if (cfg->engine_busy.val) {
+ read_poll_timeout(lp55xx_read, ret, !(val & cfg->engine_busy.mask),
+ LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 10, false,
+ chip, cfg->engine_busy.addr, &val);
+ } else {
+ usleep_range(LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 2);
+ }
+}
+
+void lp55xx_stop_all_engine(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, LP55xx_MODE_DISABLE_ALL_ENG);
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_all_engine);
+
+void lp55xx_load_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask, val;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ val = LP55xx_MODE_LOAD_ENG << LP55xx_MODE_ENGn_SHIFT(idx, cfg->reg_op_mode.shift);
+
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, val);
+ lp55xx_wait_opmode_done(chip);
+
+ /* Setup PAGE if supported (pages_per_engine not 0)*/
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine));
+}
+EXPORT_SYMBOL_GPL(lp55xx_load_engine);
+
+int lp55xx_run_engine_common(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mode, exec;
+ int i, ret;
+
+ /* To run the engine, both OP MODE and EXEC needs to be put in RUN mode */
+ ret = lp55xx_read(chip, cfg->reg_op_mode.addr, &mode);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_read(chip, cfg->reg_exec.addr, &exec);
+ if (ret)
+ return ret;
+
+ /* Switch to RUN only for engine that were put in LOAD previously */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ if (LP55xx_MODE_ENGn_GET(i, mode, cfg->reg_op_mode.shift) != LP55xx_MODE_LOAD_ENG)
+ continue;
+
+ mode &= ~LP55xx_MODE_ENGn_MASK(i, cfg->reg_op_mode.shift);
+ mode |= LP55xx_MODE_RUN_ENG << LP55xx_MODE_ENGn_SHIFT(i, cfg->reg_op_mode.shift);
+ exec &= ~LP55xx_EXEC_ENGn_MASK(i, cfg->reg_exec.shift);
+ exec |= LP55xx_EXEC_RUN_ENG << LP55xx_EXEC_ENGn_SHIFT(i, cfg->reg_exec.shift);
+ }
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, mode);
+ lp55xx_wait_opmode_done(chip);
+ lp55xx_write(chip, cfg->reg_exec.addr, exec);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lp55xx_run_engine_common);
+
+int lp55xx_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 pattern[LP55xx_MAX_PROGRAM_LENGTH] = { };
+ u8 start_addr = cfg->prog_mem_base.addr;
+ int page, i = 0, offset = 0;
+ int program_length, ret;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ while ((offset < size - 1) && (i < program_length)) {
+ unsigned int cmd;
+ int nrchars;
+ char c[3];
+
+ /* separate sscanfs because length is working only for %s */
+ ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
+ if (ret != 1)
+ goto err;
+
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
+
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
+
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
+
+ /*
+ * For legacy LED chip with no page support, engine base address are
+ * one after another at offset of 32.
+ * For LED chip that support page, PAGE is already set in load_engine.
+ */
+ if (!cfg->pages_per_engine)
+ start_addr += LP55xx_BYTES_PER_PAGE * (idx - 1);
+
+ for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) {
+ /* Write to the next page each 32 bytes (if supported) */
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine) + page);
+
+ for (i = 0; i < LP55xx_BYTES_PER_PAGE; i++) {
+ ret = lp55xx_write(chip, start_addr + i,
+ pattern[i + (page * LP55xx_BYTES_PER_PAGE)]);
+ if (ret)
+ return -EINVAL;
+ }
+ }
+
+ return size;
+
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(lp55xx_update_program_memory);
+
+void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ const struct firmware *fw = chip->fw;
+ int program_length;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ /*
+ * the firmware is encoded in ascii hex character, with 2 chars
+ * per byte
+ */
+ if (fw->size > program_length * 2) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
+
+ /*
+ * Program memory sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
+
+ lp55xx_load_engine(chip);
+ lp55xx_update_program_memory(chip, fw->data, fw->size);
+}
+EXPORT_SYMBOL_GPL(lp55xx_firmware_loaded_cb);
+
+int lp55xx_led_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_write(chip, cfg->reg_led_pwm_base.addr + led->chan_nr,
+ led->brightness);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_led_brightness);
+
+int lp55xx_multicolor_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ int i;
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ ret = lp55xx_write(chip,
+ cfg->reg_led_pwm_base.addr +
+ led->mc_cdev.subled_info[i].channel,
+ led->mc_cdev.subled_info[i].brightness);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_multicolor_brightness);
+
+void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ led->led_current = led_current;
+ lp55xx_write(led->chip, cfg->reg_led_current_base.addr + led->chan_nr,
+ led_current);
+}
+EXPORT_SYMBOL_GPL(lp55xx_set_led_current);
+
+void lp55xx_turn_off_channels(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i;
+
+ for (i = 0; i < cfg->max_channel; i++)
+ lp55xx_write(chip, cfg->reg_led_pwm_base.addr + i, 0);
+}
+EXPORT_SYMBOL_GPL(lp55xx_turn_off_channels);
+
+void lp55xx_stop_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, 0);
+
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_engine);
+
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->reset.addr;
u8 val = cfg->reset.val;
@@ -48,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;
@@ -71,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;
@@ -79,16 +380,16 @@ static int lp55xx_post_init_device(struct lp55xx_chip *chip)
return cfg->post_init_device(chip);
}
-static ssize_t lp55xx_show_current(struct device *dev,
+static ssize_t led_current_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = dev_to_lp55xx_led(dev);
- return scnprintf(buf, PAGE_SIZE, "%d\n", led->led_current);
+ return sysfs_emit(buf, "%d\n", led->led_current);
}
-static ssize_t lp55xx_store_current(struct device *dev,
+static ssize_t led_current_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
@@ -105,54 +406,66 @@ static ssize_t lp55xx_store_current(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;
}
-static ssize_t lp55xx_show_max_current(struct device *dev,
+static ssize_t max_current_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct lp55xx_led *led = dev_to_lp55xx_led(dev);
- return scnprintf(buf, PAGE_SIZE, "%d\n", led->max_current);
+ return sysfs_emit(buf, "%d\n", led->max_current);
}
-static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current,
- lp55xx_store_current);
-static DEVICE_ATTR(max_current, S_IRUGO , lp55xx_show_max_current, NULL);
+static DEVICE_ATTR_RW(led_current);
+static DEVICE_ATTR_RO(max_current);
-static struct attribute *lp55xx_led_attributes[] = {
+static struct attribute *lp55xx_led_attrs[] = {
&dev_attr_led_current.attr,
&dev_attr_max_current.attr,
NULL,
};
+ATTRIBUTE_GROUPS(lp55xx_led);
-static struct attribute_group lp55xx_led_attr_group = {
- .attrs = lp55xx_led_attributes
-};
+static int lp55xx_set_mc_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
+ struct lp55xx_led *led = mcled_cdev_to_led(mc_dev);
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
+
+ led_mc_calc_color_components(&led->mc_cdev, brightness);
+ return cfg->multicolor_brightness_fn(led);
-static void lp55xx_set_brightness(struct led_classdev *cdev,
+}
+
+static int lp55xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
led->brightness = (u8)brightness;
- schedule_work(&led->brightness_work);
+ return cfg->brightness_fn(led);
}
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;
+ struct led_classdev *led_cdev;
char name[32];
+ int i;
int ret;
- int max_channel = cfg->max_channel;
if (chan >= max_channel) {
dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
@@ -162,6 +475,39 @@ static int lp55xx_init_led(struct lp55xx_led *led,
if (pdata->led_config[chan].led_current == 0)
return 0;
+ if (pdata->led_config[chan].name) {
+ led->cdev.name = pdata->led_config[chan].name;
+ } else {
+ snprintf(name, sizeof(name), "%s:channel%d",
+ pdata->label ? : chip->cl->name, chan);
+ led->cdev.name = name;
+ }
+
+ if (pdata->led_config[chan].num_colors > 1) {
+ mc_led_info = devm_kcalloc(dev,
+ pdata->led_config[chan].num_colors,
+ sizeof(*mc_led_info), GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ led_cdev = &led->mc_cdev.led_cdev;
+ led_cdev->name = led->cdev.name;
+ led_cdev->brightness_set_blocking = lp55xx_set_mc_brightness;
+ led->mc_cdev.num_colors = pdata->led_config[chan].num_colors;
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ mc_led_info[i].color_index =
+ pdata->led_config[chan].color_id[i];
+ mc_led_info[i].channel =
+ pdata->led_config[chan].output_num[i];
+ }
+
+ led->mc_cdev.subled_info = mc_led_info;
+ } else {
+ led->cdev.brightness_set_blocking = lp55xx_set_brightness;
+ }
+
+ led->cdev.groups = lp55xx_led_groups;
+ led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
led->led_current = pdata->led_config[chan].led_current;
led->max_current = pdata->led_config[chan].max_current;
led->chan_nr = pdata->led_config[chan].chan_nr;
@@ -172,34 +518,16 @@ static int lp55xx_init_led(struct lp55xx_led *led,
return -EINVAL;
}
- led->cdev.brightness_set = lp55xx_set_brightness;
+ if (pdata->led_config[chan].num_colors > 1)
+ ret = devm_led_classdev_multicolor_register(dev, &led->mc_cdev);
+ else
+ ret = devm_led_classdev_register(dev, &led->cdev);
- if (pdata->led_config[chan].name) {
- led->cdev.name = pdata->led_config[chan].name;
- } else {
- snprintf(name, sizeof(name), "%s:channel%d",
- pdata->label ? : chip->cl->name, chan);
- led->cdev.name = name;
- }
-
- /*
- * register led class device for each channel and
- * add device attributes
- */
-
- ret = led_classdev_register(dev, &led->cdev);
if (ret) {
dev_err(dev, "led register err: %d\n", ret);
return ret;
}
- ret = sysfs_create_group(&led->cdev.dev->kobj, &lp55xx_led_attr_group);
- if (ret) {
- dev_err(dev, "led sysfs err: %d\n", ret);
- led_classdev_unregister(&led->cdev);
- return ret;
- }
-
return 0;
}
@@ -207,24 +535,24 @@ static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
{
struct lp55xx_chip *chip = context;
struct device *dev = &chip->cl->dev;
+ enum lp55xx_engine_index idx = chip->engine_idx;
if (!fw) {
dev_err(dev, "firmware request failed\n");
- goto out;
+ return;
}
/* handling firmware data is chip dependent */
- mutex_lock(&chip->lock);
-
- 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);
+ }
-out:
/* firmware should be released for other channel use */
release_firmware(chip->fw);
+ chip->fw = NULL;
}
static int lp55xx_request_firmware(struct lp55xx_chip *chip)
@@ -232,13 +560,13 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
const char *name = chip->cl->name;
struct device *dev = &chip->cl->dev;
- return request_firmware_nowait(THIS_MODULE, true, name, dev,
+ return request_firmware_nowait(THIS_MODULE, false, name, dev,
GFP_KERNEL, chip, lp55xx_firmware_loaded);
}
-static ssize_t lp55xx_show_engine_select(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static ssize_t select_engine_show(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;
@@ -246,9 +574,9 @@ static ssize_t lp55xx_show_engine_select(struct device *dev,
return sprintf(buf, "%d\n", chip->engine_idx);
}
-static ssize_t lp55xx_store_engine_select(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+static ssize_t select_engine_store(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;
@@ -264,10 +592,10 @@ static ssize_t lp55xx_store_engine_select(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);
@@ -288,9 +616,9 @@ static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
chip->cfg->run_engine(chip, start);
}
-static ssize_t lp55xx_store_engine_run(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+static ssize_t run_engine_store(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;
@@ -306,16 +634,288 @@ static ssize_t lp55xx_store_engine_run(struct device *dev,
return len;
}
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
lp55xx_run_engine(chip, true);
- mutex_unlock(&chip->lock);
return len;
}
-static DEVICE_ATTR(select_engine, S_IRUGO | S_IWUSR,
- lp55xx_show_engine_select, lp55xx_store_engine_select);
-static DEVICE_ATTR(run_engine, S_IWUSR, NULL, lp55xx_store_engine_run);
+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,
@@ -365,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:
@@ -391,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;
@@ -406,18 +1006,13 @@ int lp55xx_init_device(struct lp55xx_chip *chip)
if (!pdata || !cfg)
return -EINVAL;
- if (pdata->setup_resources) {
- ret = pdata->setup_resources();
- if (ret < 0) {
- dev_err(dev, "setup resoure err: %d\n", ret);
- goto err;
- }
- }
+ if (pdata->enable_gpiod) {
+ gpiod_direction_output(pdata->enable_gpiod, 0);
- if (pdata->enable) {
- pdata->enable(0);
+ gpiod_set_consumer_name(pdata->enable_gpiod, "LP55xx enable");
+ gpiod_set_value_cansleep(pdata->enable_gpiod, 0);
usleep_range(1000, 2000); /* Keep enable down at least 1ms */
- pdata->enable(1);
+ gpiod_set_value_cansleep(pdata->enable_gpiod, 1);
usleep_range(1000, 2000); /* 500us abs min. */
}
@@ -449,34 +1044,18 @@ err_post_init:
err:
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_init_device);
-
-void lp55xx_deinit_device(struct lp55xx_chip *chip)
-{
- struct lp55xx_platform_data *pdata = chip->pdata;
-
- if (chip->clk)
- clk_disable_unprepare(chip->clk);
-
- if (pdata->enable)
- pdata->enable(0);
-
- if (pdata->release_resources)
- pdata->release_resources();
-}
-EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
-int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
+static 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;
int ret;
int i;
- if (!cfg->brightness_work_fn) {
+ if (!cfg->brightness_fn) {
dev_err(&chip->cl->dev, "empty brightness configuration\n");
return -EINVAL;
}
@@ -493,8 +1072,6 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
if (ret)
goto err_init_led;
- INIT_WORK(&each->brightness_work, cfg->brightness_work_fn);
-
chip->num_leds++;
each->chip = chip;
@@ -506,28 +1083,13 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
return 0;
err_init_led:
- lp55xx_unregister_leds(led, chip);
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_register_leds);
-void lp55xx_unregister_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
-{
- int i;
- struct lp55xx_led *each;
-
- for (i = 0; i < chip->num_leds; i++) {
- each = led + i;
- led_classdev_unregister(&each->cdev);
- flush_work(&each->brightness_work);
- }
-}
-EXPORT_SYMBOL_GPL(lp55xx_unregister_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)
@@ -541,63 +1103,248 @@ 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);
-int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np)
+static int lp55xx_parse_common_child(struct device_node *np,
+ struct lp55xx_led_config *cfg,
+ int led_number, int *chan_nr)
+{
+ int ret;
+
+ of_property_read_string(np, "chan-name",
+ &cfg[led_number].name);
+ of_property_read_u8(np, "led-cur",
+ &cfg[led_number].led_current);
+ of_property_read_u8(np, "max-cur",
+ &cfg[led_number].max_current);
+
+ ret = of_property_read_u32(np, "reg", chan_nr);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int lp55xx_parse_multi_led_child(struct device_node *child,
+ struct lp55xx_led_config *cfg,
+ int child_number, int color_number)
+{
+ int chan_nr, color_id, ret;
+
+ ret = lp55xx_parse_common_child(child, cfg, child_number, &chan_nr);
+ if (ret)
+ return ret;
+
+ ret = of_property_read_u32(child, "color", &color_id);
+ if (ret)
+ return ret;
+
+ cfg[child_number].color_id[color_number] = color_id;
+ cfg[child_number].output_num[color_number] = chan_nr;
+
+ return 0;
+}
+
+static int lp55xx_parse_multi_led(struct device_node *np,
+ struct lp55xx_led_config *cfg,
+ int child_number)
+{
+ int num_colors = 0, ret;
+
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = lp55xx_parse_multi_led_child(child, cfg, child_number,
+ num_colors);
+ if (ret)
+ return ret;
+ num_colors++;
+ }
+
+ cfg[child_number].num_colors = num_colors;
+
+ return 0;
+}
+
+static int lp55xx_parse_logical_led(struct device_node *np,
+ struct lp55xx_led_config *cfg,
+ int child_number)
+{
+ int led_color, ret;
+ int chan_nr = 0;
+
+ cfg[child_number].default_trigger =
+ of_get_property(np, "linux,default-trigger", NULL);
+
+ ret = of_property_read_u32(np, "color", &led_color);
+ if (ret)
+ return ret;
+
+ if (led_color == LED_COLOR_ID_RGB)
+ return lp55xx_parse_multi_led(np, cfg, child_number);
+
+ ret = lp55xx_parse_common_child(np, cfg, child_number, &chan_nr);
+ if (ret < 0)
+ return ret;
+
+ cfg[child_number].chan_nr = chan_nr;
+
+ return ret;
+}
+
+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;
struct lp55xx_led_config *cfg;
int num_channels;
int i = 0;
+ int ret;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
- num_channels = of_get_child_count(np);
+ num_channels = of_get_available_child_count(np);
if (num_channels == 0) {
dev_err(dev, "no LED channels\n");
- return -EINVAL;
+ return ERR_PTR(-EINVAL);
}
- cfg = devm_kzalloc(dev, sizeof(*cfg) * num_channels, GFP_KERNEL);
+ cfg = devm_kcalloc(dev, num_channels, sizeof(*cfg), GFP_KERNEL);
if (!cfg)
- return -ENOMEM;
+ return ERR_PTR(-ENOMEM);
pdata->led_config = &cfg[0];
pdata->num_channels = num_channels;
+ cfg->max_channel = chip->cfg->max_channel;
- for_each_child_of_node(np, child) {
- cfg[i].chan_nr = i;
+ for_each_available_child_of_node(np, child) {
+ ret = lp55xx_parse_logical_led(child, cfg, i);
+ if (ret) {
+ of_node_put(child);
+ return ERR_PTR(-EINVAL);
+ }
+ i++;
+ }
- of_property_read_string(child, "chan-name", &cfg[i].name);
- of_property_read_u8(child, "led-cur", &cfg[i].led_current);
- of_property_read_u8(child, "max-cur", &cfg[i].max_current);
+ if (of_property_read_u32(np, "ti,charge-pump-mode", &pdata->charge_pump_mode))
+ pdata->charge_pump_mode = LP55XX_CP_AUTO;
- i++;
+ if (pdata->charge_pump_mode > LP55XX_CP_AUTO) {
+ dev_err(dev, "invalid charge pump mode %d\n", pdata->charge_pump_mode);
+ return ERR_PTR(-EINVAL);
}
of_property_read_string(np, "label", &pdata->label);
of_property_read_u8(np, "clock-mode", &pdata->clock_mode);
- dev->platform_data = pdata;
+ pdata->enable_gpiod = devm_gpiod_get_optional(dev, "enable",
+ GPIOD_ASIS);
+ if (IS_ERR(pdata->enable_gpiod))
+ return ERR_CAST(pdata->enable_gpiod);
+
+ /* LP8501 specific */
+ of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel);
+
+ return 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_of_populate_pdata);
+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 dbbf86df0f1f..8fd64ec40919 100644
--- a/drivers/leds/leds-lp55xx-common.h
+++ b/drivers/leds/leds-lp55xx-common.h
@@ -1,3 +1,4 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
/*
* LP55XX Common Driver Header
*
@@ -5,58 +6,156 @@
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * version 2 as published by the Free Software Foundation.
- *
* Derived from leds-lp5521.c, leds-lp5523.c
*/
#ifndef _LEDS_LP55XX_COMMON_H
#define _LEDS_LP55XX_COMMON_H
+#include <linux/led-class-multicolor.h>
+
+#define LP55xx_BYTES_PER_PAGE 32 /* bytes */
+
enum lp55xx_engine_index {
LP55XX_ENGINE_INVALID,
LP55XX_ENGINE_1,
LP55XX_ENGINE_2,
LP55XX_ENGINE_3,
+ LP55XX_ENGINE_MAX = LP55XX_ENGINE_3,
+};
+
+enum lp55xx_engine_mode {
+ LP55XX_ENGINE_DISABLED,
+ LP55XX_ENGINE_LOAD,
+ LP55XX_ENGINE_RUN,
};
+#define LP55XX_DEV_ATTR_RW(name, show, store) \
+ DEVICE_ATTR(name, S_IRUGO | S_IWUSR, show, store)
+#define LP55XX_DEV_ATTR_RO(name, show) \
+ DEVICE_ATTR(name, S_IRUGO, show, NULL)
+#define LP55XX_DEV_ATTR_WO(name, store) \
+ DEVICE_ATTR(name, S_IWUSR, NULL, store)
+
+#define LP55XX_DEV_ATTR_ENGINE_MODE(nr) \
+static ssize_t show_engine##nr##_mode(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ 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) \
+{ \
+ 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 LP55XX_DEV_ATTR_ENGINE_LEDS(nr) \
+static ssize_t show_engine##nr##_leds(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ 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, \
+ char *buf) \
+{ \
+ 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;
/*
* 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_work_fn : Brightness work function
+ * @brightness_fn : Brightness function
+ * @multicolor_brightness_fn : Multicolor brightness function
* @set_led_current : LED current set function
* @firmware_cb : Call function when the firmware is loaded
* @run_engine : Run internal engine for pattern
* @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 */
int (*post_init_device) (struct lp55xx_chip *chip);
- /* access brightness register */
- void (*brightness_work_fn)(struct work_struct *work);
+ /* set LED brightness */
+ int (*brightness_fn)(struct lp55xx_led *led);
+
+ /* set multicolor LED brightness */
+ int (*multicolor_brightness_fn)(struct lp55xx_led *led);
/* current setting function */
void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
@@ -72,6 +171,16 @@ struct lp55xx_device_config {
};
/*
+ * struct lp55xx_engine
+ * @mode : Engine mode
+ * @led_mux : Mux bits for LED selection. Only used in LP5523
+ */
+struct lp55xx_engine {
+ enum lp55xx_engine_mode mode;
+ u16 led_mux;
+};
+
+/*
* struct lp55xx_chip
* @cl : I2C communication for access registers
* @pdata : Platform specific data
@@ -79,16 +188,17 @@ struct lp55xx_device_config {
* @num_leds : Number of registered LEDs
* @cfg : Device specific configuration data
* @engine_idx : Selected engine number
+ * @engines : Engine structure for the device attribute R/W interface
* @fw : Firmware data for running a LED pattern
*/
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;
};
@@ -96,18 +206,19 @@ struct lp55xx_chip {
* struct lp55xx_led
* @chan_nr : Channel number
* @cdev : LED class device
+ * @mc_cdev : Multi color class device
+ * @color_components: Multi color LED map information
* @led_current : Current setting at each led channel
* @max_current : Maximun current at each led channel
- * @brightness_work : Workqueue for brightness control
* @brightness : Brightness value
* @chip : The lp55xx chip data
*/
struct lp55xx_led {
int chan_nr;
struct led_classdev cdev;
+ struct led_classdev_mc mc_cdev;
u8 led_current;
u8 max_current;
- struct work_struct brightness_work;
u8 brightness;
struct lp55xx_chip *chip;
};
@@ -121,22 +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);
-extern void lp55xx_unregister_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 int lp55xx_of_populate_pdata(struct device *dev,
- struct device_node *np);
+/* 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
new file mode 100644
index 000000000000..ee4ff4586bc0
--- /dev/null
+++ b/drivers/leds/leds-lp8501.c
@@ -0,0 +1,159 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI LP8501 9 channel LED Driver
+ *
+ * Copyright (C) 2013 Texas Instruments
+ *
+ * Author: Milo(Woogyom) Kim <milo.kim@ti.com>
+ */
+
+#include <linux/delay.h>
+#include <linux/firmware.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/platform_data/leds-lp55xx.h>
+#include <linux/slab.h>
+
+#include "leds-lp55xx-common.h"
+
+#define LP8501_PAGES_PER_ENGINE 1
+#define LP8501_MAX_LEDS 9
+
+/* Registers */
+#define LP8501_REG_ENABLE 0x00
+#define LP8501_ENABLE BIT(6)
+
+#define LP8501_REG_OP_MODE 0x01
+
+#define LP8501_REG_PWR_CONFIG 0x05
+#define LP8501_PWR_CONFIG_M 0x03
+
+#define LP8501_REG_LED_PWM_BASE 0x16
+
+#define LP8501_REG_LED_CURRENT_BASE 0x26
+
+#define LP8501_REG_CONFIG 0x36
+#define LP8501_PWM_PSAVE BIT(7)
+#define LP8501_AUTO_INC BIT(6)
+#define LP8501_PWR_SAVE BIT(5)
+#define LP8501_CP_MODE_MASK 0x18
+#define LP8501_CP_MODE_SHIFT 3
+#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_MEM 0x50
+
+static int lp8501_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
+ u8 val = LP8501_DEFAULT_CFG;
+
+ ret = lp55xx_write(chip, LP8501_REG_ENABLE, LP8501_ENABLE);
+ if (ret)
+ return ret;
+
+ /* Chip startup time is 500 us, 1 - 2 ms gives some margin */
+ usleep_range(1000, 2000);
+
+ if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT)
+ val |= LP8501_INT_CLK;
+
+ val |= (chip->pdata->charge_pump_mode << LP8501_CP_MODE_SHIFT) & LP8501_CP_MODE_MASK;
+
+ ret = lp55xx_write(chip, LP8501_REG_CONFIG, val);
+ if (ret)
+ return ret;
+
+ /* Power selection for each output */
+ return lp55xx_update_bits(chip, LP8501_REG_PWR_CONFIG,
+ LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel);
+}
+
+static void lp8501_run_engine(struct lp55xx_chip *chip, bool start)
+{
+ /* stop engine */
+ if (!start) {
+ lp55xx_stop_all_engine(chip);
+ lp55xx_turn_off_channels(chip);
+ return;
+ }
+
+ 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,
+ },
+ .enable = {
+ .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 = lp55xx_led_brightness,
+ .set_led_current = lp55xx_set_led_current,
+ .firmware_cb = lp55xx_firmware_loaded_cb,
+ .run_engine = lp8501_run_engine,
+};
+
+static const struct i2c_device_id lp8501_id[] = {
+ { "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", .data = &lp8501_cfg, },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_lp8501_leds_match);
+
+static struct i2c_driver lp8501_driver = {
+ .driver = {
+ .name = "lp8501",
+ .of_match_table = of_lp8501_leds_match,
+ },
+ .probe = lp55xx_probe,
+ .remove = lp55xx_remove,
+ .id_table = lp8501_id,
+};
+
+module_i2c_driver(lp8501_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP8501 LED driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp8788.c b/drivers/leds/leds-lp8788.c
index 7c2cb384e7ae..9b9525ccca15 100644
--- a/drivers/leds/leds-lp8788.c
+++ b/drivers/leds/leds-lp8788.c
@@ -1,14 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* TI LP8788 MFD - keyled driver
*
* Copyright 2012 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
@@ -26,10 +22,8 @@
struct lp8788_led {
struct lp8788 *lp;
struct mutex lock;
- struct work_struct work;
struct led_classdev led_dev;
enum lp8788_isink_number isink_num;
- enum led_brightness brightness;
int on;
};
@@ -76,24 +70,29 @@ static int lp8788_led_init_device(struct lp8788_led *led,
return lp8788_update_bits(led->lp, addr, mask, val);
}
-static void lp8788_led_enable(struct lp8788_led *led,
+static int lp8788_led_enable(struct lp8788_led *led,
enum lp8788_isink_number num, int on)
{
+ int ret;
+
u8 mask = 1 << num;
u8 val = on << num;
- if (lp8788_update_bits(led->lp, LP8788_ISINK_CTRL, mask, val))
- return;
+ ret = lp8788_update_bits(led->lp, LP8788_ISINK_CTRL, mask, val);
+ if (ret == 0)
+ led->on = on;
- led->on = on;
+ return ret;
}
-static void lp8788_led_work(struct work_struct *work)
+static int lp8788_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness val)
{
- struct lp8788_led *led = container_of(work, struct lp8788_led, work);
+ struct lp8788_led *led =
+ container_of(led_cdev, struct lp8788_led, led_dev);
+
enum lp8788_isink_number num = led->isink_num;
- int enable;
- u8 val = led->brightness;
+ int enable, ret;
mutex_lock(&led->lock);
@@ -101,28 +100,21 @@ static void lp8788_led_work(struct work_struct *work)
case LP8788_ISINK_1:
case LP8788_ISINK_2:
case LP8788_ISINK_3:
- lp8788_write_byte(led->lp, lp8788_pwm_addr[num], val);
+ ret = lp8788_write_byte(led->lp, lp8788_pwm_addr[num], val);
+ if (ret < 0)
+ goto unlock;
break;
default:
mutex_unlock(&led->lock);
- return;
+ return -EINVAL;
}
enable = (val > 0) ? 1 : 0;
if (enable != led->on)
- lp8788_led_enable(led, num, enable);
-
+ ret = lp8788_led_enable(led, num, enable);
+unlock:
mutex_unlock(&led->lock);
-}
-
-static void lp8788_brightness_set(struct led_classdev *led_cdev,
- enum led_brightness brt_val)
-{
- struct lp8788_led *led =
- container_of(led_cdev, struct lp8788_led, led_dev);
-
- led->brightness = brt_val;
- schedule_work(&led->work);
+ return ret;
}
static int lp8788_led_probe(struct platform_device *pdev)
@@ -139,7 +131,7 @@ static int lp8788_led_probe(struct platform_device *pdev)
led->lp = lp;
led->led_dev.max_brightness = MAX_BRIGHTNESS;
- led->led_dev.brightness_set = lp8788_brightness_set;
+ led->led_dev.brightness_set_blocking = lp8788_brightness_set;
led_pdata = lp->pdata ? lp->pdata->led_pdata : NULL;
@@ -149,9 +141,6 @@ static int lp8788_led_probe(struct platform_device *pdev)
led->led_dev.name = led_pdata->name;
mutex_init(&led->lock);
- INIT_WORK(&led->work, lp8788_led_work);
-
- platform_set_drvdata(pdev, led);
ret = lp8788_led_init_device(led, led_pdata);
if (ret) {
@@ -159,7 +148,7 @@ static int lp8788_led_probe(struct platform_device *pdev)
return ret;
}
- ret = led_classdev_register(dev, &led->led_dev);
+ ret = devm_led_classdev_register(dev, &led->led_dev);
if (ret) {
dev_err(dev, "led register err: %d\n", ret);
return ret;
@@ -168,22 +157,10 @@ static int lp8788_led_probe(struct platform_device *pdev)
return 0;
}
-static int lp8788_led_remove(struct platform_device *pdev)
-{
- struct lp8788_led *led = platform_get_drvdata(pdev);
-
- led_classdev_unregister(&led->led_dev);
- flush_work(&led->work);
-
- return 0;
-}
-
static struct platform_driver lp8788_led_driver = {
.probe = lp8788_led_probe,
- .remove = lp8788_led_remove,
.driver = {
.name = LP8788_DEV_KEYLED,
- .owner = THIS_MODULE,
},
};
module_platform_driver(lp8788_led_driver);
diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c
new file mode 100644
index 000000000000..0962c00c215a
--- /dev/null
+++ b/drivers/leds/leds-lp8860.c
@@ -0,0 +1,372 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * TI LP8860 4-Channel LED Driver
+ *
+ * Copyright (C) 2014 Texas Instruments
+ *
+ * Author: Dan Murphy <dmurphy@ti.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/slab.h>
+
+#define LP8860_DISP_CL1_BRT_MSB 0x00
+#define LP8860_DISP_CL1_BRT_LSB 0x01
+#define LP8860_DISP_CL1_CURR_MSB 0x02
+#define LP8860_DISP_CL1_CURR_LSB 0x03
+#define LP8860_CL2_BRT_MSB 0x04
+#define LP8860_CL2_BRT_LSB 0x05
+#define LP8860_CL2_CURRENT 0x06
+#define LP8860_CL3_BRT_MSB 0x07
+#define LP8860_CL3_BRT_LSB 0x08
+#define LP8860_CL3_CURRENT 0x09
+#define LP8860_CL4_BRT_MSB 0x0a
+#define LP8860_CL4_BRT_LSB 0x0b
+#define LP8860_CL4_CURRENT 0x0c
+#define LP8860_CONFIG 0x0d
+#define LP8860_STATUS 0x0e
+#define LP8860_FAULT 0x0f
+#define LP8860_LED_FAULT 0x10
+#define LP8860_FAULT_CLEAR 0x11
+#define LP8860_ID 0x12
+#define LP8860_TEMP_MSB 0x13
+#define LP8860_TEMP_LSB 0x14
+#define LP8860_DISP_LED_CURR_MSB 0x15
+#define LP8860_DISP_LED_CURR_LSB 0x16
+#define LP8860_DISP_LED_PWM_MSB 0x17
+#define LP8860_DISP_LED_PWM_LSB 0x18
+#define LP8860_EEPROM_CNTRL 0x19
+#define LP8860_EEPROM_UNLOCK 0x1a
+
+#define LP8860_EEPROM_REG_0 0x60
+#define LP8860_EEPROM_REG_1 0x61
+#define LP8860_EEPROM_REG_2 0x62
+#define LP8860_EEPROM_REG_3 0x63
+#define LP8860_EEPROM_REG_4 0x64
+#define LP8860_EEPROM_REG_5 0x65
+#define LP8860_EEPROM_REG_6 0x66
+#define LP8860_EEPROM_REG_7 0x67
+#define LP8860_EEPROM_REG_8 0x68
+#define LP8860_EEPROM_REG_9 0x69
+#define LP8860_EEPROM_REG_10 0x6a
+#define LP8860_EEPROM_REG_11 0x6b
+#define LP8860_EEPROM_REG_12 0x6c
+#define LP8860_EEPROM_REG_13 0x6d
+#define LP8860_EEPROM_REG_14 0x6e
+#define LP8860_EEPROM_REG_15 0x6f
+#define LP8860_EEPROM_REG_16 0x70
+#define LP8860_EEPROM_REG_17 0x71
+#define LP8860_EEPROM_REG_18 0x72
+#define LP8860_EEPROM_REG_19 0x73
+#define LP8860_EEPROM_REG_20 0x74
+#define LP8860_EEPROM_REG_21 0x75
+#define LP8860_EEPROM_REG_22 0x76
+#define LP8860_EEPROM_REG_23 0x77
+#define LP8860_EEPROM_REG_24 0x78
+
+#define LP8860_LOCK_EEPROM 0x00
+#define LP8860_UNLOCK_EEPROM 0x01
+#define LP8860_PROGRAM_EEPROM 0x02
+#define LP8860_EEPROM_CODE_1 0x08
+#define LP8860_EEPROM_CODE_2 0xba
+#define LP8860_EEPROM_CODE_3 0xef
+
+#define LP8860_CLEAR_FAULTS 0x01
+
+#define LP8860_NAME "lp8860"
+
+/**
+ * struct lp8860_led
+ * @lock: Lock for reading/writing the device
+ * @client: Pointer to the I2C client
+ * @led_dev: led class device pointer
+ * @regmap: Devices register map
+ * @eeprom_regmap: EEPROM register map
+ */
+struct lp8860_led {
+ struct mutex lock;
+ struct i2c_client *client;
+ struct led_classdev led_dev;
+ struct regmap *regmap;
+ struct regmap *eeprom_regmap;
+};
+
+static const struct reg_sequence lp8860_eeprom_disp_regs[] = {
+ { LP8860_EEPROM_REG_0, 0xed },
+ { LP8860_EEPROM_REG_1, 0xdf },
+ { LP8860_EEPROM_REG_2, 0xdc },
+ { LP8860_EEPROM_REG_3, 0xf0 },
+ { LP8860_EEPROM_REG_4, 0xdf },
+ { LP8860_EEPROM_REG_5, 0xe5 },
+ { LP8860_EEPROM_REG_6, 0xf2 },
+ { LP8860_EEPROM_REG_7, 0x77 },
+ { LP8860_EEPROM_REG_8, 0x77 },
+ { LP8860_EEPROM_REG_9, 0x71 },
+ { LP8860_EEPROM_REG_10, 0x3f },
+ { LP8860_EEPROM_REG_11, 0xb7 },
+ { LP8860_EEPROM_REG_12, 0x17 },
+ { LP8860_EEPROM_REG_13, 0xef },
+ { LP8860_EEPROM_REG_14, 0xb0 },
+ { LP8860_EEPROM_REG_15, 0x87 },
+ { LP8860_EEPROM_REG_16, 0xce },
+ { LP8860_EEPROM_REG_17, 0x72 },
+ { LP8860_EEPROM_REG_18, 0xe5 },
+ { LP8860_EEPROM_REG_19, 0xdf },
+ { LP8860_EEPROM_REG_20, 0x35 },
+ { LP8860_EEPROM_REG_21, 0x06 },
+ { LP8860_EEPROM_REG_22, 0xdc },
+ { LP8860_EEPROM_REG_23, 0x88 },
+ { LP8860_EEPROM_REG_24, 0x3E },
+};
+
+static int lp8860_unlock_eeprom(struct lp8860_led *led)
+{
+ int ret;
+
+ guard(mutex)(&led->lock);
+
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_1);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
+ }
+
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_2);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
+ }
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_EEPROM_CODE_3);
+ if (ret) {
+ dev_err(&led->client->dev, "EEPROM Unlock failed\n");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int lp8860_fault_check(struct lp8860_led *led)
+{
+ int ret, fault;
+ unsigned int read_buf;
+
+ ret = regmap_read(led->regmap, LP8860_LED_FAULT, &read_buf);
+ if (ret)
+ goto out;
+
+ fault = read_buf;
+
+ ret = regmap_read(led->regmap, LP8860_FAULT, &read_buf);
+ if (ret)
+ goto out;
+
+ fault |= read_buf;
+
+ /* Attempt to clear any faults */
+ if (fault)
+ ret = regmap_write(led->regmap, LP8860_FAULT_CLEAR,
+ LP8860_CLEAR_FAULTS);
+out:
+ return ret;
+}
+
+static int lp8860_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ struct lp8860_led *led =
+ container_of(led_cdev, struct lp8860_led, led_dev);
+ int disp_brightness = brt_val * 255;
+ int ret;
+
+ guard(mutex)(&led->lock);
+
+ ret = lp8860_fault_check(led);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot read/clear faults\n");
+ return ret;
+ }
+
+ ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_MSB,
+ (disp_brightness & 0xff00) >> 8);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot write CL1 MSB\n");
+ return ret;
+ }
+
+ ret = regmap_write(led->regmap, LP8860_DISP_CL1_BRT_LSB,
+ disp_brightness & 0xff);
+ if (ret) {
+ dev_err(&led->client->dev, "Cannot write CL1 LSB\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lp8860_init(struct lp8860_led *led)
+{
+ unsigned int read_buf;
+ int ret, reg_count;
+
+ ret = lp8860_fault_check(led);
+ if (ret)
+ goto out;
+
+ ret = regmap_read(led->regmap, LP8860_STATUS, &read_buf);
+ if (ret)
+ goto out;
+
+ ret = lp8860_unlock_eeprom(led);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed unlocking EEPROM\n");
+ goto out;
+ }
+
+ reg_count = ARRAY_SIZE(lp8860_eeprom_disp_regs);
+ ret = regmap_multi_reg_write(led->eeprom_regmap, lp8860_eeprom_disp_regs, reg_count);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed writing EEPROM\n");
+ goto out;
+ }
+
+ ret = regmap_write(led->regmap, LP8860_EEPROM_UNLOCK, LP8860_LOCK_EEPROM);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->regmap,
+ LP8860_EEPROM_CNTRL,
+ LP8860_PROGRAM_EEPROM);
+ if (ret) {
+ dev_err(&led->client->dev, "Failed programming EEPROM\n");
+ goto out;
+ }
+
+ return ret;
+
+out:
+ return ret;
+}
+
+static const struct regmap_config lp8860_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP8860_EEPROM_UNLOCK,
+};
+
+static const struct regmap_config lp8860_eeprom_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = LP8860_EEPROM_REG_24,
+};
+
+static void lp8860_disable_gpio(void *data)
+{
+ struct gpio_desc *gpio = data;
+
+ gpiod_set_value(gpio, 0);
+}
+
+static int lp8860_probe(struct i2c_client *client)
+{
+ int ret;
+ struct lp8860_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)
+ return -EINVAL;
+
+ enable_gpio = devm_gpiod_get_optional(&client->dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(enable_gpio))
+ return dev_err_probe(&client->dev, PTR_ERR(enable_gpio),
+ "Failed to get enable GPIO\n");
+ devm_add_action_or_reset(&client->dev, lp8860_disable_gpio, enable_gpio);
+
+ ret = devm_regulator_get_enable_optional(&client->dev, "vled");
+ if (ret && ret != -ENODEV)
+ return dev_err_probe(&client->dev, ret,
+ "Failed to enable vled regulator\n");
+
+ led->client = client;
+ led->led_dev.brightness_set_blocking = lp8860_brightness_set;
+
+ ret = devm_mutex_init(&client->dev, &led->lock);
+ if (ret)
+ return dev_err_probe(&client->dev, ret, "Failed to initialize lock\n");
+
+ led->regmap = devm_regmap_init_i2c(client, &lp8860_regmap_config);
+ if (IS_ERR(led->regmap)) {
+ ret = PTR_ERR(led->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ led->eeprom_regmap = devm_regmap_init_i2c(client, &lp8860_eeprom_regmap_config);
+ if (IS_ERR(led->eeprom_regmap)) {
+ ret = PTR_ERR(led->eeprom_regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ ret);
+ return ret;
+ }
+
+ ret = lp8860_init(led);
+ if (ret)
+ return ret;
+
+ init_data.fwnode = of_fwnode_handle(child_node);
+ init_data.devicename = LP8860_NAME;
+ 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, "led register err: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id lp8860_id[] = {
+ { "lp8860" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp8860_id);
+
+static const struct of_device_id of_lp8860_leds_match[] = {
+ { .compatible = "ti,lp8860", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lp8860_leds_match);
+
+static struct i2c_driver lp8860_driver = {
+ .driver = {
+ .name = "lp8860",
+ .of_match_table = of_lp8860_leds_match,
+ },
+ .probe = lp8860_probe,
+ .id_table = lp8860_id,
+};
+module_i2c_driver(lp8860_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP8860 LED driver");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
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-lt3593.c b/drivers/leds/leds-lt3593.c
index ca48a7d5502d..d0160fde0f94 100644
--- a/drivers/leds/leds-lt3593.c
+++ b/drivers/leds/leds-lt3593.c
@@ -1,43 +1,29 @@
-/*
- * LEDs driver for LT3593 controllers
- *
- * See the datasheet at http://cds.linear.com/docs/Datasheet/3593f.pdf
- *
- * Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
- *
- * Based on leds-gpio.c,
- *
- * Copyright (C) 2007 8D Technologies inc.
- * Raphael Assenat <raph@8d.com>
- * Copyright (C) 2008 Freescale Semiconductor, Inc.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- */
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2009,2018 Daniel Mack <daniel@zonque.org>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
-#include <linux/workqueue.h>
#include <linux/delay.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/slab.h>
+#include <linux/mod_devicetable.h>
#include <linux/module.h>
+#include <linux/property.h>
+
+#define LED_LT3593_NAME "lt3593"
struct lt3593_led_data {
struct led_classdev cdev;
- unsigned gpio;
- struct work_struct work;
- u8 new_level;
+ struct gpio_desc *gpiod;
};
-static void lt3593_led_work(struct work_struct *work)
+static int lt3593_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
- int pulses;
struct lt3593_led_data *led_dat =
- container_of(work, struct lt3593_led_data, work);
+ container_of(led_cdev, struct lt3593_led_data, cdev);
+ int pulses;
/*
* The LT3593 resets its internal current level register to the maximum
@@ -48,150 +34,95 @@ static void lt3593_led_work(struct work_struct *work)
* applied is to the output driver.
*/
- if (led_dat->new_level == 0) {
- gpio_set_value_cansleep(led_dat->gpio, 0);
- return;
+ if (value == 0) {
+ gpiod_set_value_cansleep(led_dat->gpiod, 0);
+ return 0;
}
- pulses = 32 - (led_dat->new_level * 32) / 255;
+ pulses = 32 - (value * 32) / 255;
if (pulses == 0) {
- gpio_set_value_cansleep(led_dat->gpio, 0);
+ gpiod_set_value_cansleep(led_dat->gpiod, 0);
mdelay(1);
- gpio_set_value_cansleep(led_dat->gpio, 1);
- return;
+ gpiod_set_value_cansleep(led_dat->gpiod, 1);
+ return 0;
}
- gpio_set_value_cansleep(led_dat->gpio, 1);
+ gpiod_set_value_cansleep(led_dat->gpiod, 1);
while (pulses--) {
- gpio_set_value_cansleep(led_dat->gpio, 0);
+ gpiod_set_value_cansleep(led_dat->gpiod, 0);
udelay(1);
- gpio_set_value_cansleep(led_dat->gpio, 1);
+ gpiod_set_value_cansleep(led_dat->gpiod, 1);
udelay(1);
}
-}
-
-static void lt3593_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct lt3593_led_data *led_dat =
- container_of(led_cdev, struct lt3593_led_data, cdev);
-
- led_dat->new_level = value;
- schedule_work(&led_dat->work);
-}
-
-static int create_lt3593_led(const struct gpio_led *template,
- struct lt3593_led_data *led_dat, struct device *parent)
-{
- int ret, state;
-
- /* skip leds on GPIOs that aren't available */
- if (!gpio_is_valid(template->gpio)) {
- dev_info(parent, "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n",
- KBUILD_MODNAME, template->gpio, template->name);
- return 0;
- }
-
- led_dat->cdev.name = template->name;
- led_dat->cdev.default_trigger = template->default_trigger;
- led_dat->gpio = template->gpio;
-
- led_dat->cdev.brightness_set = lt3593_led_set;
-
- state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
- led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
-
- if (!template->retain_state_suspended)
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- ret = devm_gpio_request_one(parent, template->gpio, state ?
- GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
- template->name);
- if (ret < 0)
- return ret;
-
- INIT_WORK(&led_dat->work, lt3593_led_work);
-
- ret = led_classdev_register(parent, &led_dat->cdev);
- if (ret < 0)
- return ret;
-
- dev_info(parent, "%s: registered LT3593 LED '%s' at GPIO %d\n",
- KBUILD_MODNAME, template->name, template->gpio);
return 0;
}
-static void delete_lt3593_led(struct lt3593_led_data *led)
-{
- if (!gpio_is_valid(led->gpio))
- return;
-
- led_classdev_unregister(&led->cdev);
- cancel_work_sync(&led->work);
-}
-
static int lt3593_led_probe(struct platform_device *pdev)
{
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
- struct lt3593_led_data *leds_data;
- int i, ret = 0;
-
- if (!pdata)
- return -EBUSY;
-
- leds_data = devm_kzalloc(&pdev->dev,
- sizeof(struct lt3593_led_data) * pdata->num_leds,
- GFP_KERNEL);
- if (!leds_data)
+ struct device *dev = &pdev->dev;
+ struct lt3593_led_data *led_data;
+ struct fwnode_handle *child;
+ int ret, state = LEDS_GPIO_DEFSTATE_OFF;
+ struct led_init_data init_data = {};
+ const char *tmp;
+
+ led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
+ if (!led_data)
return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++) {
- ret = create_lt3593_led(&pdata->leds[i], &leds_data[i],
- &pdev->dev);
- if (ret < 0)
- goto err;
+ if (device_get_child_node_count(dev) != 1) {
+ dev_err(dev, "Device must have exactly one LED sub-node.");
+ return -EINVAL;
}
- platform_set_drvdata(pdev, leds_data);
+ led_data->gpiod = devm_gpiod_get(dev, "lltc,ctrl", 0);
+ if (IS_ERR(led_data->gpiod))
+ return PTR_ERR(led_data->gpiod);
- return 0;
+ child = device_get_next_child_node(dev, NULL);
-err:
- for (i = i - 1; i >= 0; i--)
- delete_lt3593_led(&leds_data[i]);
+ if (!fwnode_property_read_string(child, "default-state", &tmp)) {
+ if (!strcmp(tmp, "on"))
+ state = LEDS_GPIO_DEFSTATE_ON;
+ }
- return ret;
-}
+ led_data->cdev.brightness_set_blocking = lt3593_led_set;
+ led_data->cdev.brightness = state ? LED_FULL : LED_OFF;
-static int lt3593_led_remove(struct platform_device *pdev)
-{
- int i;
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
- struct lt3593_led_data *leds_data;
+ init_data.fwnode = child;
+ init_data.devicename = LED_LT3593_NAME;
+ init_data.default_label = ":";
- leds_data = platform_get_drvdata(pdev);
+ ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data);
+ fwnode_handle_put(child);
+ if (ret < 0)
+ return ret;
- for (i = 0; i < pdata->num_leds; i++)
- delete_lt3593_led(&leds_data[i]);
+ platform_set_drvdata(pdev, led_data);
return 0;
}
+static const struct of_device_id of_lt3593_leds_match[] = {
+ { .compatible = "lltc,lt3593", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_lt3593_leds_match);
+
static struct platform_driver lt3593_led_driver = {
.probe = lt3593_led_probe,
- .remove = lt3593_led_remove,
.driver = {
.name = "leds-lt3593",
- .owner = THIS_MODULE,
+ .of_match_table = of_lt3593_leds_match,
},
};
module_platform_driver(lt3593_led_driver);
-MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
+MODULE_AUTHOR("Daniel Mack <daniel@zonque.org>");
MODULE_DESCRIPTION("LED driver for LT3593 controllers");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-lt3593");
diff --git a/drivers/leds/leds-max5970.c b/drivers/leds/leds-max5970.c
new file mode 100644
index 000000000000..a1e91a06249c
--- /dev/null
+++ b/drivers/leds/leds-max5970.c
@@ -0,0 +1,112 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Device driver for leds in MAX5970 and MAX5978 IC
+ *
+ * Copyright (c) 2022 9elements GmbH
+ *
+ * Author: Patrick Rudolph <patrick.rudolph@9elements.com>
+ */
+
+#include <linux/bits.h>
+#include <linux/container_of.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+#include <linux/mfd/max5970.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+#define ldev_to_maxled(c) container_of(c, struct max5970_led, cdev)
+
+struct max5970_led {
+ struct device *dev;
+ struct regmap *regmap;
+ struct led_classdev cdev;
+ unsigned int index;
+};
+
+static int max5970_led_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct max5970_led *ddata = ldev_to_maxled(cdev);
+ int ret, val;
+
+ /* Set/clear corresponding bit for given led index */
+ val = !brightness ? BIT(ddata->index) : 0;
+
+ ret = regmap_update_bits(ddata->regmap, MAX5970_REG_LED_FLASH, BIT(ddata->index), val);
+ if (ret < 0)
+ dev_err(cdev->dev, "failed to set brightness %d", ret);
+
+ return ret;
+}
+
+static int max5970_led_probe(struct platform_device *pdev)
+{
+ struct fwnode_handle *child;
+ struct device *dev = &pdev->dev;
+ struct regmap *regmap;
+ struct max5970_led *ddata;
+ int ret = -ENODEV;
+
+ regmap = dev_get_regmap(dev->parent, NULL);
+ if (!regmap)
+ return -ENODEV;
+
+ struct fwnode_handle *led_node __free(fwnode_handle) =
+ device_get_named_child_node(dev->parent, "leds");
+ if (!led_node)
+ return -ENODEV;
+
+ fwnode_for_each_child_node(led_node, child) {
+ u32 reg;
+
+ if (fwnode_property_read_u32(child, "reg", &reg))
+ continue;
+
+ if (reg >= MAX5970_NUM_LEDS) {
+ dev_err_probe(dev, -EINVAL, "invalid LED (%u >= %d)\n", reg, MAX5970_NUM_LEDS);
+ continue;
+ }
+
+ ddata = devm_kzalloc(dev, sizeof(*ddata), GFP_KERNEL);
+ if (!ddata) {
+ fwnode_handle_put(child);
+ return -ENOMEM;
+ }
+
+ ddata->index = reg;
+ ddata->regmap = regmap;
+ ddata->dev = dev;
+
+ if (fwnode_property_read_string(child, "label", &ddata->cdev.name))
+ ddata->cdev.name = fwnode_get_name(child);
+
+ ddata->cdev.max_brightness = 1;
+ ddata->cdev.brightness_set_blocking = max5970_led_set_brightness;
+ ddata->cdev.default_trigger = "none";
+
+ ret = devm_led_classdev_register(dev, &ddata->cdev);
+ if (ret < 0) {
+ fwnode_handle_put(child);
+ return dev_err_probe(dev, ret, "Failed to initialize LED %u\n", reg);
+ }
+ }
+
+ return ret;
+}
+
+static struct platform_driver max5970_led_driver = {
+ .driver = {
+ .name = "max5970-led",
+ },
+ .probe = max5970_led_probe,
+};
+module_platform_driver(max5970_led_driver);
+
+MODULE_AUTHOR("Patrick Rudolph <patrick.rudolph@9elements.com>");
+MODULE_AUTHOR("Naresh Solanki <Naresh.Solanki@9elements.com>");
+MODULE_DESCRIPTION("MAX5970_hot-swap controller LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-max77650.c b/drivers/leds/leds-max77650.c
new file mode 100644
index 000000000000..f8c47078a3bb
--- /dev/null
+++ b/drivers/leds/leds-max77650.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0
+//
+// Copyright (C) 2018 BayLibre SAS
+// Author: Bartosz Golaszewski <bgolaszewski@baylibre.com>
+//
+// LED driver for MAXIM 77650/77651 charger/power-supply.
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/mfd/max77650.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77650_LED_NUM_LEDS 3
+
+#define MAX77650_LED_A_BASE 0x40
+#define MAX77650_LED_B_BASE 0x43
+
+#define MAX77650_LED_BR_MASK GENMASK(4, 0)
+#define MAX77650_LED_EN_MASK GENMASK(7, 6)
+
+#define MAX77650_LED_MAX_BRIGHTNESS MAX77650_LED_BR_MASK
+
+/* Enable EN_LED_MSTR. */
+#define MAX77650_LED_TOP_DEFAULT BIT(0)
+
+#define MAX77650_LED_ENABLE GENMASK(7, 6)
+#define MAX77650_LED_DISABLE 0x00
+
+#define MAX77650_LED_A_DEFAULT MAX77650_LED_DISABLE
+/* 100% on duty */
+#define MAX77650_LED_B_DEFAULT GENMASK(3, 0)
+
+struct max77650_led {
+ struct led_classdev cdev;
+ struct regmap *map;
+ unsigned int regA;
+ unsigned int regB;
+};
+
+static struct max77650_led *max77650_to_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct max77650_led, cdev);
+}
+
+static int max77650_led_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct max77650_led *led = max77650_to_led(cdev);
+ int val, mask;
+
+ mask = MAX77650_LED_BR_MASK | MAX77650_LED_EN_MASK;
+
+ if (brightness == LED_OFF)
+ val = MAX77650_LED_DISABLE;
+ else
+ val = MAX77650_LED_ENABLE | brightness;
+
+ return regmap_update_bits(led->map, led->regA, mask, val);
+}
+
+static int max77650_led_probe(struct platform_device *pdev)
+{
+ struct max77650_led *leds, *led;
+ struct device *dev;
+ struct regmap *map;
+ int rv, num_leds;
+ u32 reg;
+
+ dev = &pdev->dev;
+
+ leds = devm_kcalloc(dev, sizeof(*leds),
+ MAX77650_LED_NUM_LEDS, GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ map = dev_get_regmap(dev->parent, NULL);
+ if (!map)
+ return -ENODEV;
+
+ num_leds = device_get_child_node_count(dev);
+ if (!num_leds || num_leds > MAX77650_LED_NUM_LEDS)
+ return -ENODEV;
+
+ 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)
+ return -EINVAL;
+
+ led = &leds[reg];
+ led->map = map;
+ led->regA = MAX77650_LED_A_BASE + reg;
+ led->regB = MAX77650_LED_B_BASE + reg;
+ led->cdev.brightness_set_blocking = max77650_led_brightness_set;
+ led->cdev.max_brightness = MAX77650_LED_MAX_BRIGHTNESS;
+
+ init_data.fwnode = child;
+ init_data.devicename = "max77650";
+ /* for backwards compatibility if `label` is not present */
+ init_data.default_label = ":";
+
+ rv = devm_led_classdev_register_ext(dev, &led->cdev,
+ &init_data);
+ if (rv)
+ return rv;
+
+ rv = regmap_write(map, led->regA, MAX77650_LED_A_DEFAULT);
+ if (rv)
+ return rv;
+
+ rv = regmap_write(map, led->regB, MAX77650_LED_B_DEFAULT);
+ if (rv)
+ return rv;
+ }
+
+ return regmap_write(map,
+ MAX77650_REG_CNFG_LED_TOP,
+ MAX77650_LED_TOP_DEFAULT);
+}
+
+static const struct of_device_id max77650_led_of_match[] = {
+ { .compatible = "maxim,max77650-led" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77650_led_of_match);
+
+static struct platform_driver max77650_led_driver = {
+ .driver = {
+ .name = "max77650-led",
+ .of_match_table = max77650_led_of_match,
+ },
+ .probe = max77650_led_probe,
+};
+module_platform_driver(max77650_led_driver);
+
+MODULE_DESCRIPTION("MAXIM 77650/77651 LED driver");
+MODULE_AUTHOR("Bartosz Golaszewski <bgolaszewski@baylibre.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:max77650-led");
diff --git a/drivers/leds/leds-max77705.c b/drivers/leds/leds-max77705.c
new file mode 100644
index 000000000000..1e2054c1bf80
--- /dev/null
+++ b/drivers/leds/leds-max77705.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Based on leds-max77650 driver
+ *
+ * LED driver for MAXIM 77705 PMIC.
+ * Copyright (C) 2025 Dzmitry Sankouski <dsankouski@gmail.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mfd/max77705-private.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define MAX77705_LED_NUM_LEDS 4
+#define MAX77705_LED_EN_MASK GENMASK(1, 0)
+#define MAX77705_LED_MAX_BRIGHTNESS 0xff
+#define MAX77705_LED_EN_SHIFT(reg) (reg * MAX77705_RGBLED_EN_WIDTH)
+#define MAX77705_LED_REG_BRIGHTNESS(reg) (reg + MAX77705_RGBLED_REG_LED0BRT)
+
+struct max77705_led {
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+ struct regmap *regmap;
+
+ struct mc_subled *subled_info;
+};
+
+static const struct regmap_config max77705_leds_regmap_config = {
+ .reg_base = MAX77705_RGBLED_REG_BASE,
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = MAX77705_LED_REG_END,
+};
+
+static int max77705_rgb_blink(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+ int value, on_value, off_value;
+
+ if (*delay_on < MAX77705_RGB_DELAY_100_STEP)
+ on_value = 0;
+ else if (*delay_on < MAX77705_RGB_DELAY_100_STEP_LIM)
+ on_value = *delay_on / MAX77705_RGB_DELAY_100_STEP - 1;
+ else if (*delay_on < MAX77705_RGB_DELAY_250_STEP_LIM)
+ on_value = (*delay_on - MAX77705_RGB_DELAY_100_STEP_LIM) /
+ MAX77705_RGB_DELAY_250_STEP +
+ MAX77705_RGB_DELAY_100_STEP_COUNT;
+ else
+ on_value = 15;
+
+ on_value <<= 4;
+
+ if (*delay_off < 1)
+ off_value = 0;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP)
+ off_value = 1;
+ else if (*delay_off < MAX77705_RGB_DELAY_500_STEP_LIM)
+ off_value = *delay_off / MAX77705_RGB_DELAY_500_STEP;
+ else if (*delay_off < MAX77705_RGB_DELAY_1000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_1000_STEP_LIM) /
+ MAX77705_RGB_DELAY_1000_STEP +
+ MAX77705_RGB_DELAY_500_STEP_COUNT;
+ else if (*delay_off < MAX77705_RGB_DELAY_2000_STEP_LIM)
+ off_value = (*delay_off - MAX77705_RGB_DELAY_2000_STEP_LIM) /
+ MAX77705_RGB_DELAY_2000_STEP +
+ MAX77705_RGB_DELAY_1000_STEP_COUNT;
+ else
+ off_value = 15;
+
+ value = on_value | off_value;
+ return regmap_write(led->regmap, MAX77705_RGBLED_REG_LEDBLNK, value);
+}
+
+static int max77705_led_brightness_set(struct regmap *regmap, struct mc_subled *subled,
+ int num_colors)
+{
+ int ret;
+
+ for (int i = 0; i < num_colors; i++) {
+ unsigned int channel, brightness;
+
+ channel = subled[i].channel;
+ brightness = subled[i].brightness;
+
+ if (brightness == LED_OFF) {
+ /* Flash OFF */
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel), 0);
+ } else {
+ /* Set current */
+ ret = regmap_write(regmap, MAX77705_LED_REG_BRIGHTNESS(channel),
+ brightness);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(regmap,
+ MAX77705_RGBLED_REG_LEDEN,
+ LED_ON << MAX77705_LED_EN_SHIFT(channel),
+ MAX77705_LED_EN_MASK << MAX77705_LED_EN_SHIFT(channel));
+ }
+ }
+
+ return ret;
+}
+
+static int max77705_led_brightness_set_single(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct max77705_led *led = container_of(cdev, struct max77705_led, cdev);
+
+ led->subled_info->brightness = brightness;
+
+ return max77705_led_brightness_set(led->regmap, led->subled_info, 1);
+}
+
+static int max77705_led_brightness_set_multi(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mcdev = lcdev_to_mccdev(cdev);
+ struct max77705_led *led = container_of(mcdev, struct max77705_led, mcdev);
+
+ led_mc_calc_color_components(mcdev, brightness);
+
+ return max77705_led_brightness_set(led->regmap, led->mcdev.subled_info, mcdev->num_colors);
+}
+
+static int max77705_parse_subled(struct device *dev, struct fwnode_handle *np,
+ struct mc_subled *info)
+{
+ u32 color = LED_COLOR_ID_GREEN;
+ u32 reg;
+ int ret;
+
+ ret = fwnode_property_read_u32(np, "reg", &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)
+ return -EINVAL;
+
+ info = devm_kcalloc(dev, num_channels, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ cdev = &led->mcdev.led_cdev;
+ cdev->max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+ cdev->brightness_set_blocking = max77705_led_brightness_set_multi;
+ cdev->blink_set = max77705_rgb_blink;
+
+ fwnode_for_each_child_node(np, child) {
+ ret = max77705_parse_subled(dev, child, &info[i]);
+ if (ret < 0)
+ return ret;
+
+ info[i].intensity = 0;
+ i++;
+ }
+
+ led->mcdev.subled_info = info;
+ led->mcdev.num_colors = num_channels;
+ led->cdev = *cdev;
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mcdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_multi(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ } else {
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ max77705_parse_subled(dev, np, info);
+
+ led->subled_info = info;
+ led->cdev.brightness_set_blocking = max77705_led_brightness_set_single;
+ led->cdev.blink_set = max77705_rgb_blink;
+ led->cdev.max_brightness = MAX77705_LED_MAX_BRIGHTNESS;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret)
+ return ret;
+
+ ret = max77705_led_brightness_set_single(&led->cdev, LED_OFF);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int max77705_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct i2c_client *i2c = to_i2c_client(pdev->dev.parent);
+ struct regmap *regmap;
+ int ret;
+
+ regmap = devm_regmap_init_i2c(i2c, &max77705_leds_regmap_config);
+ if (IS_ERR(regmap))
+ return dev_err_probe(dev, PTR_ERR(regmap), "Failed to register LEDs regmap\n");
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = max77705_add_led(dev, regmap, child);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id max77705_led_of_match[] = {
+ { .compatible = "maxim,max77705-rgb" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77705_led_of_match);
+
+static struct platform_driver max77705_led_driver = {
+ .driver = {
+ .name = "max77705-led",
+ .of_match_table = max77705_led_of_match,
+ },
+ .probe = max77705_led_probe,
+};
+module_platform_driver(max77705_led_driver);
+
+MODULE_DESCRIPTION("Maxim MAX77705 LED driver");
+MODULE_AUTHOR("Dzmitry Sankouski <dsankouski@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-max8997.c b/drivers/leds/leds-max8997.c
index f449a8bdddc7..c8d7f55c9dec 100644
--- a/drivers/leds/leds-max8997.c
+++ b/drivers/leds/leds-max8997.c
@@ -1,19 +1,14 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* leds-max8997.c - LED class driver for MAX8997 LEDs.
*
* Copyright (C) 2011 Samsung Electronics
* Donggeun Kim <dg77.kim@samsung.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/slab.h>
-#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/mfd/max8997.h>
#include <linux/mfd/max8997-private.h>
@@ -165,8 +160,8 @@ static void max8997_led_brightness_set(struct led_classdev *led_cdev,
}
}
-static ssize_t max8997_led_show_mode(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct max8997_led *led =
@@ -198,9 +193,9 @@ static ssize_t max8997_led_show_mode(struct device *dev,
return ret;
}
-static ssize_t max8997_led_store_mode(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t mode_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct max8997_led *led =
@@ -227,7 +222,13 @@ static ssize_t max8997_led_store_mode(struct device *dev,
return size;
}
-static DEVICE_ATTR(mode, 0644, max8997_led_show_mode, max8997_led_store_mode);
+static DEVICE_ATTR_RW(mode);
+
+static struct attribute *max8997_attrs[] = {
+ &dev_attr_mode.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(max8997);
static int max8997_led_probe(struct platform_device *pdev)
{
@@ -237,11 +238,6 @@ static int max8997_led_probe(struct platform_device *pdev)
char name[20];
int ret = 0;
- if (pdata == NULL) {
- dev_err(&pdev->dev, "no platform data\n");
- return -ENODEV;
- }
-
led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (led == NULL)
return -ENOMEM;
@@ -253,16 +249,17 @@ static int max8997_led_probe(struct platform_device *pdev)
led->cdev.brightness_set = max8997_led_brightness_set;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->cdev.brightness = 0;
+ led->cdev.groups = max8997_groups;
led->iodev = iodev;
/* initialize mode and brightness according to platform_data */
- if (pdata->led_pdata) {
+ if (pdata && pdata->led_pdata) {
u8 mode = 0, brightness = 0;
mode = pdata->led_pdata->mode[led->id];
brightness = pdata->led_pdata->brightness[led->id];
- max8997_led_set_mode(led, pdata->led_pdata->mode[led->id]);
+ max8997_led_set_mode(led, mode);
if (brightness > led->cdev.max_brightness)
brightness = led->cdev.max_brightness;
@@ -275,40 +272,18 @@ static int max8997_led_probe(struct platform_device *pdev)
mutex_init(&led->mutex);
- platform_set_drvdata(pdev, led);
-
- ret = led_classdev_register(&pdev->dev, &led->cdev);
+ ret = devm_led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0)
return ret;
- ret = device_create_file(led->cdev.dev, &dev_attr_mode);
- if (ret != 0) {
- dev_err(&pdev->dev,
- "failed to create file: %d\n", ret);
- led_classdev_unregister(&led->cdev);
- return ret;
- }
-
- return 0;
-}
-
-static int max8997_led_remove(struct platform_device *pdev)
-{
- struct max8997_led *led = platform_get_drvdata(pdev);
-
- device_remove_file(led->cdev.dev, &dev_attr_mode);
- led_classdev_unregister(&led->cdev);
-
return 0;
}
static struct platform_driver max8997_led_driver = {
.driver = {
.name = "max8997-led",
- .owner = THIS_MODULE,
},
.probe = max8997_led_probe,
- .remove = max8997_led_remove,
};
module_platform_driver(max8997_led_driver);
diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c
index fa9b439323bd..e22f09d13798 100644
--- a/drivers/leds/leds-mc13783.c
+++ b/drivers/leds/leds-mc13783.c
@@ -1,5 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
- * LEDs driver for Freescale MC13783/MC13892
+ * LEDs driver for Freescale MC13783/MC13892/MC34708
*
* Copyright (C) 2010 Philippe Rétornaz
*
@@ -9,65 +10,60 @@
*
* Copyright (C) 2006-2008 Marvell International Ltd.
* Eric Miao <eric.miao@marvell.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
+#include <linux/cleanup.h>
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
-#include <linux/workqueue.h>
+#include <linux/of.h>
#include <linux/mfd/mc13xxx.h>
-#define MC13XXX_REG_LED_CONTROL(x) (51 + (x))
-
struct mc13xxx_led_devtype {
int led_min;
int led_max;
int num_regs;
+ u32 ledctrl_base;
};
struct mc13xxx_led {
struct led_classdev cdev;
- struct work_struct work;
- struct mc13xxx *master;
- enum led_brightness new_brightness;
int id;
+ struct mc13xxx_leds *leds;
};
struct mc13xxx_leds {
+ struct mc13xxx *master;
struct mc13xxx_led_devtype *devtype;
int num_leds;
- struct mc13xxx_led led[0];
+ struct mc13xxx_led *led;
};
-static void mc13xxx_led_work(struct work_struct *work)
+static unsigned int mc13xxx_max_brightness(int id)
{
- struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work);
- int reg, mask, value, bank, off, shift;
+ if (id >= MC13783_LED_MD && id <= MC13783_LED_KP)
+ return 0x0f;
+ else if (id >= MC13783_LED_R1 && id <= MC13783_LED_B3)
+ return 0x1f;
+
+ return 0x3f;
+}
+
+static int mc13xxx_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct mc13xxx_led *led =
+ container_of(led_cdev, struct mc13xxx_led, cdev);
+ struct mc13xxx_leds *leds = led->leds;
+ unsigned int reg, bank, off, shift;
switch (led->id) {
case MC13783_LED_MD:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 9;
- mask = 0x0f;
- value = led->new_brightness >> 4;
- break;
case MC13783_LED_AD:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 13;
- mask = 0x0f;
- value = led->new_brightness >> 4;
- break;
case MC13783_LED_KP:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 17;
- mask = 0x0f;
- value = led->new_brightness >> 4;
+ reg = 2;
+ shift = 9 + (led->id - MC13783_LED_MD) * 4;
break;
case MC13783_LED_R1:
case MC13783_LED_G1:
@@ -80,262 +76,223 @@ static void mc13xxx_led_work(struct work_struct *work)
case MC13783_LED_B3:
off = led->id - MC13783_LED_R1;
bank = off / 3;
- reg = MC13XXX_REG_LED_CONTROL(3) + bank;
+ reg = 3 + bank;
shift = (off - bank * 3) * 5 + 6;
- value = led->new_brightness >> 3;
- mask = 0x1f;
break;
case MC13892_LED_MD:
- reg = MC13XXX_REG_LED_CONTROL(0);
- shift = 3;
- mask = 0x3f;
- value = led->new_brightness >> 2;
- break;
case MC13892_LED_AD:
- reg = MC13XXX_REG_LED_CONTROL(0);
- shift = 15;
- mask = 0x3f;
- value = led->new_brightness >> 2;
- break;
case MC13892_LED_KP:
- reg = MC13XXX_REG_LED_CONTROL(1);
- shift = 3;
- mask = 0x3f;
- value = led->new_brightness >> 2;
+ off = led->id - MC13892_LED_MD;
+ reg = off / 2;
+ shift = 3 + (off - reg * 2) * 12;
break;
case MC13892_LED_R:
case MC13892_LED_G:
case MC13892_LED_B:
off = led->id - MC13892_LED_R;
bank = off / 2;
- reg = MC13XXX_REG_LED_CONTROL(2) + bank;
+ reg = 2 + bank;
shift = (off - bank * 2) * 12 + 3;
- value = led->new_brightness >> 2;
- mask = 0x3f;
+ break;
+ case MC34708_LED_R:
+ case MC34708_LED_G:
+ reg = 0;
+ shift = 3 + (led->id - MC34708_LED_R) * 12;
break;
default:
BUG();
}
- mc13xxx_lock(led->master);
- mc13xxx_reg_rmw(led->master, reg, mask << shift, value << shift);
- mc13xxx_unlock(led->master);
+ return mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg,
+ mc13xxx_max_brightness(led->id) << shift,
+ value << shift);
}
-static void mc13xxx_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
+#ifdef CONFIG_OF
+static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
+ struct platform_device *pdev)
{
- struct mc13xxx_led *led =
- container_of(led_cdev, struct mc13xxx_led, cdev);
+ struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
+ struct mc13xxx_leds_platform_data *pdata;
+ struct device_node *child;
+ struct device *dev = &pdev->dev;
+ int i = 0, ret = -ENODATA;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ struct device_node *parent __free(device_node) =
+ of_get_child_by_name(dev_of_node(dev->parent), "leds");
+ if (!parent)
+ return ERR_PTR(-ENODATA);
+
+ ret = of_property_read_u32_array(parent, "led-control",
+ pdata->led_control,
+ leds->devtype->num_regs);
+ if (ret)
+ return ERR_PTR(ret);
- led->new_brightness = value;
- schedule_work(&led->work);
-}
+ pdata->num_leds = of_get_available_child_count(parent);
-static int __init mc13xxx_led_setup(struct mc13xxx_led *led, int max_current)
-{
- int shift, mask, reg, ret, bank;
+ pdata->led = devm_kcalloc(dev, pdata->num_leds, sizeof(*pdata->led),
+ GFP_KERNEL);
+ if (!pdata->led)
+ return ERR_PTR(-ENOMEM);
- switch (led->id) {
- case MC13783_LED_MD:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 0;
- mask = 0x07;
- break;
- case MC13783_LED_AD:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 3;
- mask = 0x07;
- break;
- case MC13783_LED_KP:
- reg = MC13XXX_REG_LED_CONTROL(2);
- shift = 6;
- mask = 0x07;
- break;
- case MC13783_LED_R1:
- case MC13783_LED_G1:
- case MC13783_LED_B1:
- case MC13783_LED_R2:
- case MC13783_LED_G2:
- case MC13783_LED_B2:
- case MC13783_LED_R3:
- case MC13783_LED_G3:
- case MC13783_LED_B3:
- bank = (led->id - MC13783_LED_R1) / 3;
- reg = MC13XXX_REG_LED_CONTROL(3) + bank;
- shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2;
- mask = 0x03;
- break;
- case MC13892_LED_MD:
- reg = MC13XXX_REG_LED_CONTROL(0);
- shift = 9;
- mask = 0x07;
- break;
- case MC13892_LED_AD:
- reg = MC13XXX_REG_LED_CONTROL(0);
- shift = 21;
- mask = 0x07;
- break;
- case MC13892_LED_KP:
- reg = MC13XXX_REG_LED_CONTROL(1);
- shift = 9;
- mask = 0x07;
- break;
- case MC13892_LED_R:
- case MC13892_LED_G:
- case MC13892_LED_B:
- bank = (led->id - MC13892_LED_R) / 2;
- reg = MC13XXX_REG_LED_CONTROL(2) + bank;
- shift = ((led->id - MC13892_LED_R) - bank * 2) * 12 + 9;
- mask = 0x07;
- break;
- default:
- BUG();
+ for_each_available_child_of_node(parent, child) {
+ const char *str;
+ u32 tmp;
+
+ if (of_property_read_u32(child, "reg", &tmp))
+ continue;
+ pdata->led[i].id = leds->devtype->led_min + tmp;
+
+ if (!of_property_read_string(child, "label", &str))
+ pdata->led[i].name = str;
+ if (!of_property_read_string(child, "linux,default-trigger",
+ &str))
+ pdata->led[i].default_trigger = str;
+
+ i++;
}
- mc13xxx_lock(led->master);
- ret = mc13xxx_reg_rmw(led->master, reg, mask << shift,
- max_current << shift);
- mc13xxx_unlock(led->master);
+ pdata->num_leds = i;
+ if (i <= 0)
+ return ERR_PTR(-ENODATA);
- return ret;
+ return pdata;
}
+#else
+static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
+ struct platform_device *pdev)
+{
+ return ERR_PTR(-ENOSYS);
+}
+#endif
static int __init mc13xxx_led_probe(struct platform_device *pdev)
{
- struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(dev);
+ struct mc13xxx *mcdev = dev_get_drvdata(dev->parent);
struct mc13xxx_led_devtype *devtype =
(struct mc13xxx_led_devtype *)pdev->id_entry->driver_data;
struct mc13xxx_leds *leds;
- int i, id, num_leds, ret = -ENODATA;
- u32 reg, init_led = 0;
+ int i, id, ret = -ENODATA;
+ u32 init_led = 0;
- if (!pdata) {
- dev_err(&pdev->dev, "Missing platform data\n");
- return -ENODEV;
- }
+ leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ leds->devtype = devtype;
+ leds->master = mcdev;
+ platform_set_drvdata(pdev, leds);
- num_leds = pdata->num_leds;
+ if (dev_of_node(dev->parent)) {
+ pdata = mc13xxx_led_probe_dt(pdev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ } else if (!pdata)
+ return -ENODATA;
- if ((num_leds < 1) ||
- (num_leds > (devtype->led_max - devtype->led_min + 1))) {
- dev_err(&pdev->dev, "Invalid LED count %d\n", num_leds);
+ leds->num_leds = pdata->num_leds;
+
+ if ((leds->num_leds < 1) ||
+ (leds->num_leds > (devtype->led_max - devtype->led_min + 1))) {
+ dev_err(dev, "Invalid LED count %d\n", leds->num_leds);
return -EINVAL;
}
- leds = devm_kzalloc(&pdev->dev, num_leds * sizeof(struct mc13xxx_led) +
- sizeof(struct mc13xxx_leds), GFP_KERNEL);
- if (!leds)
+ leds->led = devm_kcalloc(dev, leds->num_leds, sizeof(*leds->led),
+ GFP_KERNEL);
+ if (!leds->led)
return -ENOMEM;
- leds->devtype = devtype;
- leds->num_leds = num_leds;
- platform_set_drvdata(pdev, leds);
-
- mc13xxx_lock(mcdev);
for (i = 0; i < devtype->num_regs; i++) {
- reg = pdata->led_control[i];
- WARN_ON(reg >= (1 << 24));
- ret = mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), reg);
+ ret = mc13xxx_reg_write(mcdev, leds->devtype->ledctrl_base + i,
+ pdata->led_control[i]);
if (ret)
- break;
- }
- mc13xxx_unlock(mcdev);
-
- if (ret) {
- dev_err(&pdev->dev, "Unable to init LED driver\n");
- return ret;
+ return ret;
}
- for (i = 0; i < num_leds; i++) {
+ for (i = 0; i < leds->num_leds; i++) {
const char *name, *trig;
- char max_current;
ret = -EINVAL;
id = pdata->led[i].id;
name = pdata->led[i].name;
trig = pdata->led[i].default_trigger;
- max_current = pdata->led[i].max_current;
if ((id > devtype->led_max) || (id < devtype->led_min)) {
- dev_err(&pdev->dev, "Invalid ID %i\n", id);
+ dev_err(dev, "Invalid ID %i\n", id);
break;
}
if (init_led & (1 << id)) {
- dev_warn(&pdev->dev,
- "LED %i already initialized\n", id);
+ dev_warn(dev, "LED %i already initialized\n", id);
break;
}
init_led |= 1 << id;
leds->led[i].id = id;
- leds->led[i].master = mcdev;
+ leds->led[i].leds = leds;
leds->led[i].cdev.name = name;
leds->led[i].cdev.default_trigger = trig;
- leds->led[i].cdev.brightness_set = mc13xxx_led_set;
- leds->led[i].cdev.brightness = LED_OFF;
-
- INIT_WORK(&leds->led[i].work, mc13xxx_led_work);
+ leds->led[i].cdev.flags = LED_CORE_SUSPENDRESUME;
+ leds->led[i].cdev.brightness_set_blocking = mc13xxx_led_set;
+ leds->led[i].cdev.max_brightness = mc13xxx_max_brightness(id);
- ret = mc13xxx_led_setup(&leds->led[i], max_current);
+ ret = led_classdev_register(dev->parent, &leds->led[i].cdev);
if (ret) {
- dev_err(&pdev->dev, "Unable to setup LED %i\n", id);
- break;
- }
- ret = led_classdev_register(pdev->dev.parent,
- &leds->led[i].cdev);
- if (ret) {
- dev_err(&pdev->dev, "Failed to register LED %i\n", id);
+ dev_err(dev, "Failed to register LED %i\n", id);
break;
}
}
if (ret)
- while (--i >= 0) {
+ while (--i >= 0)
led_classdev_unregister(&leds->led[i].cdev);
- cancel_work_sync(&leds->led[i].work);
- }
return ret;
}
-static int mc13xxx_led_remove(struct platform_device *pdev)
+static void mc13xxx_led_remove(struct platform_device *pdev)
{
- struct mc13xxx *mcdev = dev_get_drvdata(pdev->dev.parent);
struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
int i;
- for (i = 0; i < leds->num_leds; i++) {
+ for (i = 0; i < leds->num_leds; i++)
led_classdev_unregister(&leds->led[i].cdev);
- cancel_work_sync(&leds->led[i].work);
- }
-
- mc13xxx_lock(mcdev);
- for (i = 0; i < leds->devtype->num_regs; i++)
- mc13xxx_reg_write(mcdev, MC13XXX_REG_LED_CONTROL(i), 0);
- mc13xxx_unlock(mcdev);
-
- return 0;
}
static const struct mc13xxx_led_devtype mc13783_led_devtype = {
.led_min = MC13783_LED_MD,
.led_max = MC13783_LED_B3,
.num_regs = 6,
+ .ledctrl_base = 51,
};
static const struct mc13xxx_led_devtype mc13892_led_devtype = {
.led_min = MC13892_LED_MD,
.led_max = MC13892_LED_B,
.num_regs = 4,
+ .ledctrl_base = 51,
+};
+
+static const struct mc13xxx_led_devtype mc34708_led_devtype = {
+ .led_min = MC34708_LED_R,
+ .led_max = MC34708_LED_G,
+ .num_regs = 1,
+ .ledctrl_base = 54,
};
static const struct platform_device_id mc13xxx_led_id_table[] = {
{ "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, },
{ "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, },
+ { "mc34708-led", (kernel_ulong_t)&mc34708_led_devtype, },
{ }
};
MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
@@ -343,7 +300,6 @@ MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
static struct platform_driver mc13xxx_led_driver = {
.driver = {
.name = "mc13xxx-led",
- .owner = THIS_MODULE,
},
.remove = mc13xxx_led_remove,
.id_table = mc13xxx_led_id_table,
diff --git a/drivers/leds/leds-menf21bmc.c b/drivers/leds/leds-menf21bmc.c
new file mode 100644
index 000000000000..6b1b47160602
--- /dev/null
+++ b/drivers/leds/leds-menf21bmc.c
@@ -0,0 +1,110 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * MEN 14F021P00 Board Management Controller (BMC) LEDs Driver.
+ *
+ * This is the core LED driver of the MEN 14F021P00 BMC.
+ * There are four LEDs available which can be switched on and off.
+ * STATUS LED, HOT SWAP LED, USER LED 1, USER LED 2
+ *
+ * Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/i2c.h>
+
+#define BMC_CMD_LED_GET_SET 0xA0
+#define BMC_BIT_LED_STATUS BIT(0)
+#define BMC_BIT_LED_HOTSWAP BIT(1)
+#define BMC_BIT_LED_USER1 BIT(2)
+#define BMC_BIT_LED_USER2 BIT(3)
+
+struct menf21bmc_led {
+ struct led_classdev cdev;
+ u8 led_bit;
+ const char *name;
+ struct i2c_client *i2c_client;
+};
+
+static struct menf21bmc_led leds[] = {
+ {
+ .name = "menf21bmc:led_status",
+ .led_bit = BMC_BIT_LED_STATUS,
+ },
+ {
+ .name = "menf21bmc:led_hotswap",
+ .led_bit = BMC_BIT_LED_HOTSWAP,
+ },
+ {
+ .name = "menf21bmc:led_user1",
+ .led_bit = BMC_BIT_LED_USER1,
+ },
+ {
+ .name = "menf21bmc:led_user2",
+ .led_bit = BMC_BIT_LED_USER2,
+ }
+};
+
+static DEFINE_MUTEX(led_lock);
+
+static void
+menf21bmc_led_set(struct led_classdev *led_cdev, enum led_brightness value)
+{
+ int led_val;
+ struct menf21bmc_led *led = container_of(led_cdev,
+ struct menf21bmc_led, cdev);
+
+ mutex_lock(&led_lock);
+ led_val = i2c_smbus_read_byte_data(led->i2c_client,
+ BMC_CMD_LED_GET_SET);
+ if (led_val < 0)
+ goto err_out;
+
+ if (value == LED_OFF)
+ led_val &= ~led->led_bit;
+ else
+ led_val |= led->led_bit;
+
+ i2c_smbus_write_byte_data(led->i2c_client,
+ BMC_CMD_LED_GET_SET, led_val);
+err_out:
+ mutex_unlock(&led_lock);
+}
+
+static int menf21bmc_led_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+ struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent);
+
+ for (i = 0; i < ARRAY_SIZE(leds); i++) {
+ leds[i].cdev.name = leds[i].name;
+ leds[i].cdev.brightness_set = menf21bmc_led_set;
+ leds[i].i2c_client = i2c_client;
+ ret = devm_led_classdev_register(&pdev->dev, &leds[i].cdev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to register LED device\n");
+ return ret;
+ }
+ }
+ dev_info(&pdev->dev, "MEN 140F21P00 BMC LED device enabled\n");
+
+ return 0;
+
+}
+
+static struct platform_driver menf21bmc_led = {
+ .probe = menf21bmc_led_probe,
+ .driver = {
+ .name = "menf21bmc_led",
+ },
+};
+
+module_platform_driver(menf21bmc_led);
+
+MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
+MODULE_DESCRIPTION("MEN 14F021P00 BMC led driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:menf21bmc_led");
diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c
new file mode 100644
index 000000000000..f25f68789281
--- /dev/null
+++ b/drivers/leds/leds-mlxcpld.c
@@ -0,0 +1,434 @@
+/*
+ * drivers/leds/leds-mlxcpld.c
+ * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
+ * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the names of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <linux/device.h>
+#include <linux/dmi.h>
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+
+#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */
+
+/* Color codes for LEDs */
+#define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */
+#define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */
+#define MLXCPLD_LED_IS_OFF 0x00 /* Off */
+#define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */
+#define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \
+ MLXCPLD_LED_OFFSET_HALF)
+#define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \
+ MLXCPLD_LED_OFFSET_FULL)
+#define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */
+#define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \
+ MLXCPLD_LED_OFFSET_HALF)
+#define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \
+ MLXCPLD_LED_OFFSET_FULL)
+#define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */
+#define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */
+
+/**
+ * struct mlxcpld_param - LED access parameters:
+ * @offset: offset for LED access in CPLD device
+ * @mask: mask for LED access in CPLD device
+ * @base_color: base color code for LED
+**/
+struct mlxcpld_param {
+ u8 offset;
+ u8 mask;
+ u8 base_color;
+};
+
+/**
+ * struct mlxcpld_led_priv - LED private data:
+ * @cdev: LED class device instance
+ * @param: LED CPLD access parameters
+**/
+struct mlxcpld_led_priv {
+ struct led_classdev cdev;
+ struct mlxcpld_param param;
+};
+
+#define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev)
+
+/**
+ * struct mlxcpld_led_profile - system LED profile (defined per system class):
+ * @offset: offset for LED access in CPLD device
+ * @mask: mask for LED access in CPLD device
+ * @base_color: base color code
+ * @brightness: default brightness setting (on/off)
+ * @name: LED name
+**/
+struct mlxcpld_led_profile {
+ u8 offset;
+ u8 mask;
+ u8 base_color;
+ enum led_brightness brightness;
+ const char *name;
+};
+
+/**
+ * struct mlxcpld_led_pdata - system LED private data
+ * @pdev: platform device pointer
+ * @pled: LED class device instance
+ * @profile: system configuration profile
+ * @num_led_instances: number of LED instances
+ * @lock: device access lock
+**/
+struct mlxcpld_led_pdata {
+ struct platform_device *pdev;
+ struct mlxcpld_led_priv *pled;
+ struct mlxcpld_led_profile *profile;
+ int num_led_instances;
+ spinlock_t lock;
+};
+
+static struct mlxcpld_led_pdata *mlxcpld_led;
+
+/* Default profile fit the next Mellanox systems:
+ * "msx6710", "msx6720", "msb7700", "msn2700", "msx1410",
+ * "msn2410", "msb7800", "msn2740"
+ */
+static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = {
+ {
+ 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:fan1:green",
+ },
+ {
+ 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:fan1:red",
+ },
+ {
+ 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:fan2:green",
+ },
+ {
+ 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:fan2:red",
+ },
+ {
+ 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:fan3:green",
+ },
+ {
+ 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:fan3:red",
+ },
+ {
+ 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:fan4:green",
+ },
+ {
+ 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:fan4:red",
+ },
+ {
+ 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:psu:green",
+ },
+ {
+ 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:psu:red",
+ },
+ {
+ 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:status:green",
+ },
+ {
+ 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:status:red",
+ },
+};
+
+/* Profile fit the Mellanox systems based on "msn2100" */
+static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = {
+ {
+ 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:fan:green",
+ },
+ {
+ 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:fan:red",
+ },
+ {
+ 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:psu1:green",
+ },
+ {
+ 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:psu1:red",
+ },
+ {
+ 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:psu2:green",
+ },
+ {
+ 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:psu2:red",
+ },
+ {
+ 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1,
+ "mlxcpld:status:green",
+ },
+ {
+ 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF,
+ "mlxcpld:status:red",
+ },
+ {
+ 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF,
+ "mlxcpld:uid:blue",
+ },
+};
+
+enum mlxcpld_led_platform_types {
+ MLXCPLD_LED_PLATFORM_DEFAULT,
+ MLXCPLD_LED_PLATFORM_MSN2100,
+};
+
+static const char *mlx_product_names[] = {
+ "DEFAULT",
+ "MSN2100",
+};
+
+static enum
+mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void)
+{
+ const char *mlx_product_name;
+ int i;
+
+ mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME);
+ if (!mlx_product_name)
+ return MLXCPLD_LED_PLATFORM_DEFAULT;
+
+ for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) {
+ if (strstr(mlx_product_name, mlx_product_names[i]))
+ return i;
+ }
+
+ return MLXCPLD_LED_PLATFORM_DEFAULT;
+}
+
+static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag,
+ u8 *data)
+{
+ u32 addr = base + offset;
+
+ if (rw_flag == 0)
+ outb(*data, addr);
+ else
+ *data = inb(addr);
+}
+
+static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset)
+{
+ u8 nib, val;
+
+ /*
+ * Each LED is controlled through low or high nibble of the relevant
+ * CPLD register. Register offset is specified by off parameter.
+ * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
+ * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
+ * green.
+ * Parameter mask specifies which nibble is used for specific LED: mask
+ * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
+ * higher nibble (bits from 4 to 7).
+ */
+ spin_lock(&mlxcpld_led->lock);
+ mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1,
+ &val);
+ nib = (mask == 0xf0) ? vset : (vset << 4);
+ val = (val & mask) | nib;
+ mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0,
+ &val);
+ spin_unlock(&mlxcpld_led->lock);
+}
+
+static void mlxcpld_led_brightness_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct mlxcpld_led_priv *pled = cdev_to_priv(led);
+
+ if (value) {
+ mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
+ pled->param.base_color);
+ return;
+ }
+
+ mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
+ MLXCPLD_LED_IS_OFF);
+}
+
+static int mlxcpld_led_blink_set(struct led_classdev *led,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct mlxcpld_led_priv *pled = cdev_to_priv(led);
+
+ /*
+ * HW supports two types of blinking: full (6Hz) and half (3Hz).
+ * For delay on/off zero default setting 3Hz is used.
+ */
+ if (!(*delay_on == 0 && *delay_off == 0) &&
+ !(*delay_on == MLXCPLD_LED_BLINK_3HZ &&
+ *delay_off == MLXCPLD_LED_BLINK_3HZ) &&
+ !(*delay_on == MLXCPLD_LED_BLINK_6HZ &&
+ *delay_off == MLXCPLD_LED_BLINK_6HZ))
+ return -EINVAL;
+
+ if (*delay_on == MLXCPLD_LED_BLINK_6HZ)
+ mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
+ pled->param.base_color +
+ MLXCPLD_LED_OFFSET_FULL);
+ else
+ mlxcpld_led_store_hw(pled->param.mask, pled->param.offset,
+ pled->param.base_color +
+ MLXCPLD_LED_OFFSET_HALF);
+
+ return 0;
+}
+
+static int mlxcpld_led_config(struct device *dev,
+ struct mlxcpld_led_pdata *cpld)
+{
+ int i;
+ int err;
+
+ cpld->pled = devm_kcalloc(dev,
+ cpld->num_led_instances,
+ sizeof(struct mlxcpld_led_priv),
+ GFP_KERNEL);
+ if (!cpld->pled)
+ return -ENOMEM;
+
+ for (i = 0; i < cpld->num_led_instances; i++) {
+ cpld->pled[i].cdev.name = cpld->profile[i].name;
+ cpld->pled[i].cdev.brightness = cpld->profile[i].brightness;
+ cpld->pled[i].cdev.max_brightness = 1;
+ cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set;
+ cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set;
+ cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME;
+ err = devm_led_classdev_register(dev, &cpld->pled[i].cdev);
+ if (err)
+ return err;
+
+ cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset;
+ cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask;
+ cpld->pled[i].param.base_color =
+ mlxcpld_led->profile[i].base_color;
+
+ if (mlxcpld_led->profile[i].brightness)
+ mlxcpld_led_brightness_set(&cpld->pled[i].cdev,
+ mlxcpld_led->profile[i].brightness);
+ }
+
+ return 0;
+}
+
+static int __init mlxcpld_led_probe(struct platform_device *pdev)
+{
+ enum mlxcpld_led_platform_types mlxcpld_led_plat =
+ mlxcpld_led_platform_check_sys_type();
+
+ mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led),
+ GFP_KERNEL);
+ if (!mlxcpld_led)
+ return -ENOMEM;
+
+ mlxcpld_led->pdev = pdev;
+
+ switch (mlxcpld_led_plat) {
+ case MLXCPLD_LED_PLATFORM_MSN2100:
+ mlxcpld_led->profile = mlxcpld_led_msn2100_profile;
+ mlxcpld_led->num_led_instances =
+ ARRAY_SIZE(mlxcpld_led_msn2100_profile);
+ break;
+
+ default:
+ mlxcpld_led->profile = mlxcpld_led_default_profile;
+ mlxcpld_led->num_led_instances =
+ ARRAY_SIZE(mlxcpld_led_default_profile);
+ break;
+ }
+
+ spin_lock_init(&mlxcpld_led->lock);
+
+ return mlxcpld_led_config(&pdev->dev, mlxcpld_led);
+}
+
+static struct platform_driver mlxcpld_led_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+
+static int __init mlxcpld_led_init(void)
+{
+ struct platform_device *pdev;
+ int err;
+
+ if (!dmi_match(DMI_CHASSIS_VENDOR, "Mellanox Technologies Ltd."))
+ return -ENODEV;
+
+ pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
+ if (IS_ERR(pdev)) {
+ pr_err("Device allocation failed\n");
+ return PTR_ERR(pdev);
+ }
+
+ err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe);
+ if (err) {
+ pr_err("Probe platform driver failed\n");
+ platform_device_unregister(pdev);
+ }
+
+ return err;
+}
+
+static void __exit mlxcpld_led_exit(void)
+{
+ platform_device_unregister(mlxcpld_led->pdev);
+ platform_driver_unregister(&mlxcpld_led_driver);
+}
+
+module_init(mlxcpld_led_init);
+module_exit(mlxcpld_led_exit);
+
+MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox board LED driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:leds_mlxcpld");
diff --git a/drivers/leds/leds-mlxreg.c b/drivers/leds/leds-mlxreg.c
new file mode 100644
index 000000000000..1b70de72376c
--- /dev/null
+++ b/drivers/leds/leds-mlxreg.c
@@ -0,0 +1,293 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+//
+// Copyright (c) 2018 Mellanox Technologies. All rights reserved.
+// Copyright (c) 2018 Vadim Pasternak <vadimp@mellanox.com>
+
+#include <linux/bitops.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_data/mlxreg.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* Codes for LEDs. */
+#define MLXREG_LED_OFFSET_BLINK_3HZ 0x01 /* Offset from solid: 3Hz blink */
+#define MLXREG_LED_OFFSET_BLINK_6HZ 0x02 /* Offset from solid: 6Hz blink */
+#define MLXREG_LED_IS_OFF 0x00 /* Off */
+#define MLXREG_LED_RED_SOLID 0x05 /* Solid red */
+#define MLXREG_LED_GREEN_SOLID 0x0D /* Solid green */
+#define MLXREG_LED_AMBER_SOLID 0x09 /* Solid amber */
+#define MLXREG_LED_BLINK_3HZ 167 /* ~167 msec off/on - HW support */
+#define MLXREG_LED_BLINK_6HZ 83 /* ~83 msec off/on - HW support */
+#define MLXREG_LED_CAPABILITY_CLEAR GENMASK(31, 8) /* Clear mask */
+
+/**
+ * struct mlxreg_led_data - led control data:
+ *
+ * @data: led configuration data;
+ * @led_cdev: led class data;
+ * @base_color: base led color (other colors have constant offset from base);
+ * @data_parent: pointer to private device control data of parent;
+ * @led_cdev_name: class device name
+ */
+struct mlxreg_led_data {
+ struct mlxreg_core_data *data;
+ struct led_classdev led_cdev;
+ u8 base_color;
+ void *data_parent;
+ char led_cdev_name[MLXREG_CORE_LABEL_MAX_SIZE];
+};
+
+#define cdev_to_priv(c) container_of(c, struct mlxreg_led_data, led_cdev)
+
+/**
+ * struct mlxreg_led_priv_data - platform private data:
+ *
+ * @pdev: platform device;
+ * @pdata: platform data;
+ * @access_lock: mutex for attribute IO access;
+ */
+struct mlxreg_led_priv_data {
+ struct platform_device *pdev;
+ struct mlxreg_core_platform_data *pdata;
+ struct mutex access_lock; /* protect IO operations */
+};
+
+static int
+mlxreg_led_store_hw(struct mlxreg_led_data *led_data, u8 vset)
+{
+ struct mlxreg_led_priv_data *priv = led_data->data_parent;
+ struct mlxreg_core_platform_data *led_pdata = priv->pdata;
+ struct mlxreg_core_data *data = led_data->data;
+ u32 regval;
+ u32 nib;
+ int ret;
+
+ /*
+ * Each LED is controlled through low or high nibble of the relevant
+ * register byte. Register offset is specified by off parameter.
+ * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
+ * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
+ * green.
+ * Parameter mask specifies which nibble is used for specific LED: mask
+ * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
+ * higher nibble (bits from 4 to 7).
+ */
+ mutex_lock(&priv->access_lock);
+
+ ret = regmap_read(led_pdata->regmap, data->reg, &regval);
+ if (ret)
+ goto access_error;
+
+ nib = (ror32(data->mask, data->bit) == 0xf0) ? rol32(vset, data->bit) :
+ rol32(vset, data->bit + 4);
+ regval = (regval & data->mask) | nib;
+
+ ret = regmap_write(led_pdata->regmap, data->reg, regval);
+
+access_error:
+ mutex_unlock(&priv->access_lock);
+
+ return ret;
+}
+
+static enum led_brightness
+mlxreg_led_get_hw(struct mlxreg_led_data *led_data)
+{
+ struct mlxreg_led_priv_data *priv = led_data->data_parent;
+ struct mlxreg_core_platform_data *led_pdata = priv->pdata;
+ struct mlxreg_core_data *data = led_data->data;
+ u32 regval;
+ int err;
+
+ /*
+ * Each LED is controlled through low or high nibble of the relevant
+ * register byte. Register offset is specified by off parameter.
+ * Parameter vset provides color code: 0x0 for off, 0x5 for solid red,
+ * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink
+ * green.
+ * Parameter mask specifies which nibble is used for specific LED: mask
+ * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f -
+ * higher nibble (bits from 4 to 7).
+ */
+ err = regmap_read(led_pdata->regmap, data->reg, &regval);
+ if (err < 0) {
+ dev_warn(led_data->led_cdev.dev, "Failed to get current brightness, error: %d\n",
+ err);
+ /* Assume the LED is OFF */
+ return LED_OFF;
+ }
+
+ regval = regval & ~data->mask;
+ regval = (ror32(data->mask, data->bit) == 0xf0) ? ror32(regval,
+ data->bit) : ror32(regval, data->bit + 4);
+ if (regval >= led_data->base_color &&
+ regval <= (led_data->base_color + MLXREG_LED_OFFSET_BLINK_6HZ))
+ return LED_FULL;
+
+ return LED_OFF;
+}
+
+static int
+mlxreg_led_brightness_set(struct led_classdev *cled, enum led_brightness value)
+{
+ struct mlxreg_led_data *led_data = cdev_to_priv(cled);
+
+ if (value)
+ return mlxreg_led_store_hw(led_data, led_data->base_color);
+ else
+ return mlxreg_led_store_hw(led_data, MLXREG_LED_IS_OFF);
+}
+
+static enum led_brightness
+mlxreg_led_brightness_get(struct led_classdev *cled)
+{
+ struct mlxreg_led_data *led_data = cdev_to_priv(cled);
+
+ return mlxreg_led_get_hw(led_data);
+}
+
+static int
+mlxreg_led_blink_set(struct led_classdev *cled, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct mlxreg_led_data *led_data = cdev_to_priv(cled);
+ int err;
+
+ /*
+ * HW supports two types of blinking: full (6Hz) and half (3Hz).
+ * For delay on/off zero LED is setting to solid color. For others
+ * combination blinking is to be controlled by the software timer.
+ */
+ if (!(*delay_on == 0 && *delay_off == 0) &&
+ !(*delay_on == MLXREG_LED_BLINK_3HZ &&
+ *delay_off == MLXREG_LED_BLINK_3HZ) &&
+ !(*delay_on == MLXREG_LED_BLINK_6HZ &&
+ *delay_off == MLXREG_LED_BLINK_6HZ))
+ return -EINVAL;
+
+ if (*delay_on == MLXREG_LED_BLINK_6HZ)
+ err = mlxreg_led_store_hw(led_data, led_data->base_color +
+ MLXREG_LED_OFFSET_BLINK_6HZ);
+ else if (*delay_on == MLXREG_LED_BLINK_3HZ)
+ err = mlxreg_led_store_hw(led_data, led_data->base_color +
+ MLXREG_LED_OFFSET_BLINK_3HZ);
+ else
+ err = mlxreg_led_store_hw(led_data, led_data->base_color);
+
+ return err;
+}
+
+static int mlxreg_led_config(struct mlxreg_led_priv_data *priv)
+{
+ struct mlxreg_core_platform_data *led_pdata = priv->pdata;
+ struct mlxreg_core_data *data = led_pdata->data;
+ struct mlxreg_led_data *led_data;
+ struct led_classdev *led_cdev;
+ enum led_brightness brightness;
+ u32 regval;
+ int i;
+ int err;
+
+ for (i = 0; i < led_pdata->counter; i++, data++) {
+ led_data = devm_kzalloc(&priv->pdev->dev, sizeof(*led_data),
+ GFP_KERNEL);
+ if (!led_data)
+ return -ENOMEM;
+
+ if (data->capability) {
+ err = regmap_read(led_pdata->regmap, data->capability,
+ &regval);
+ if (err) {
+ dev_err(&priv->pdev->dev, "Failed to query capability register\n");
+ return err;
+ }
+ if (!(regval & data->bit))
+ continue;
+ /*
+ * Field "bit" can contain one capability bit in 0 byte
+ * and offset bit in 1-3 bytes. Clear capability bit and
+ * keep only offset bit.
+ */
+ data->bit &= MLXREG_LED_CAPABILITY_CLEAR;
+ }
+
+ led_cdev = &led_data->led_cdev;
+ led_data->data_parent = priv;
+ if (strstr(data->label, "red") ||
+ strstr(data->label, "orange")) {
+ brightness = LED_OFF;
+ led_data->base_color = MLXREG_LED_RED_SOLID;
+ } else if (strstr(data->label, "amber")) {
+ brightness = LED_OFF;
+ led_data->base_color = MLXREG_LED_AMBER_SOLID;
+ } else {
+ brightness = LED_OFF;
+ led_data->base_color = MLXREG_LED_GREEN_SOLID;
+ }
+ snprintf(led_data->led_cdev_name, sizeof(led_data->led_cdev_name),
+ "mlxreg:%s", data->label);
+ led_cdev->name = led_data->led_cdev_name;
+ led_cdev->brightness = brightness;
+ led_cdev->max_brightness = LED_ON;
+ led_cdev->brightness_set_blocking =
+ mlxreg_led_brightness_set;
+ led_cdev->brightness_get = mlxreg_led_brightness_get;
+ led_cdev->blink_set = mlxreg_led_blink_set;
+ led_cdev->flags = LED_CORE_SUSPENDRESUME;
+ led_data->data = data;
+ err = devm_led_classdev_register(&priv->pdev->dev, led_cdev);
+ if (err)
+ return err;
+
+ if (led_cdev->brightness)
+ mlxreg_led_brightness_set(led_cdev,
+ led_cdev->brightness);
+ dev_info(led_cdev->dev, "label: %s, mask: 0x%02x, offset:0x%02x\n",
+ data->label, data->mask, data->reg);
+ }
+
+ return 0;
+}
+
+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) {
+ dev_err(&pdev->dev, "Failed to get platform data.\n");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ 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 struct platform_driver mlxreg_led_driver = {
+ .driver = {
+ .name = "leds-mlxreg",
+ },
+ .probe = mlxreg_led_probe,
+};
+
+module_platform_driver(mlxreg_led_driver);
+
+MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>");
+MODULE_DESCRIPTION("Mellanox LED regmap driver");
+MODULE_LICENSE("Dual BSD/GPL");
+MODULE_ALIAS("platform:leds-mlxreg");
diff --git a/drivers/leds/leds-mt6323.c b/drivers/leds/leds-mt6323.c
new file mode 100644
index 000000000000..dbdc221c3828
--- /dev/null
+++ b/drivers/leds/leds-mt6323.c
@@ -0,0 +1,727 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * LED driver for Mediatek MT6323 PMIC
+ *
+ * Copyright (C) 2017 Sean Wang <sean.wang@mediatek.com>
+ */
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/mfd/mt6323/registers.h>
+#include <linux/mfd/mt6397/core.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/*
+ * Register field for TOP_CKPDN0 to enable
+ * 32K clock common for LED device.
+ */
+#define RG_DRV_32K_CK_PDN BIT(11)
+#define RG_DRV_32K_CK_PDN_MASK BIT(11)
+
+/* 32K/1M/6M clock common for WLED device */
+#define RG_VWLED_1M_CK_PDN BIT(0)
+#define RG_VWLED_32K_CK_PDN BIT(12)
+#define RG_VWLED_6M_CK_PDN BIT(13)
+
+/*
+ * Register field for TOP_CKPDN2 to enable
+ * individual clock for LED device.
+ */
+#define RG_ISINK_CK_PDN(i) BIT(i)
+#define RG_ISINK_CK_PDN_MASK(i) BIT(i)
+
+/*
+ * Register field for TOP_CKCON1 to select
+ * clock source.
+ */
+#define RG_ISINK_CK_SEL_MASK(i) (BIT(10) << (i))
+
+#define ISINK_CON(r, i) (r + 0x8 * (i))
+
+/* ISINK_CON0: Register to setup the duty cycle of the blink. */
+#define ISINK_DIM_DUTY_MASK (0x1f << 8)
+#define ISINK_DIM_DUTY(i) (((i) << 8) & ISINK_DIM_DUTY_MASK)
+
+/* ISINK_CON1: Register to setup the period of the blink. */
+#define ISINK_DIM_FSEL_MASK (0xffff)
+#define ISINK_DIM_FSEL(i) ((i) & ISINK_DIM_FSEL_MASK)
+
+/* ISINK_CON2: Register to control the brightness. */
+#define ISINK_CH_STEP_SHIFT 12
+#define ISINK_CH_STEP_MASK (0x7 << 12)
+#define ISINK_CH_STEP(i) (((i) << 12) & ISINK_CH_STEP_MASK)
+#define ISINK_SFSTR0_TC_MASK (0x3 << 1)
+#define ISINK_SFSTR0_TC(i) (((i) << 1) & ISINK_SFSTR0_TC_MASK)
+#define ISINK_SFSTR0_EN_MASK BIT(0)
+#define ISINK_SFSTR0_EN BIT(0)
+
+/* Register to LED channel enablement. */
+#define ISINK_CH_EN_MASK(i) BIT(i)
+#define ISINK_CH_EN(i) BIT(i)
+
+#define MAX_SUPPORTED_LEDS 8
+
+struct mt6323_leds;
+
+/**
+ * struct mt6323_led - state container for the LED device
+ * @id: the identifier in MT6323 LED device
+ * @parent: the pointer to MT6323 LED controller
+ * @cdev: LED class device for this LED device
+ * @current_brightness: current state of the LED device
+ */
+struct mt6323_led {
+ int id;
+ struct mt6323_leds *parent;
+ struct led_classdev cdev;
+ enum led_brightness current_brightness;
+};
+
+/**
+ * struct mt6323_regs - register spec for the LED device
+ * @top_ckpdn: Offset to ISINK_CKPDN[0..x] registers
+ * @num_top_ckpdn: Number of ISINK_CKPDN registers
+ * @top_ckcon: Offset to ISINK_CKCON[0..x] registers
+ * @num_top_ckcon: Number of ISINK_CKCON registers
+ * @isink_con: Offset to ISINKx_CON[0..x] registers
+ * @num_isink_con: Number of ISINKx_CON registers
+ * @isink_max_regs: Number of ISINK[0..x] registers
+ * @isink_en_ctrl: Offset to ISINK_EN_CTRL register
+ * @iwled_en_ctrl: Offset to IWLED_EN_CTRL register
+ */
+struct mt6323_regs {
+ const u16 *top_ckpdn;
+ u8 num_top_ckpdn;
+ const u16 *top_ckcon;
+ u8 num_top_ckcon;
+ const u16 *isink_con;
+ u8 num_isink_con;
+ u8 isink_max_regs;
+ u16 isink_en_ctrl;
+ u16 iwled_en_ctrl;
+};
+
+/**
+ * struct mt6323_hwspec - hardware specific parameters
+ * @max_period: Maximum period for all LEDs
+ * @max_leds: Maximum number of supported LEDs
+ * @max_wleds: Maximum number of WLEDs
+ * @max_brightness: Maximum brightness for all LEDs
+ * @unit_duty: Steps of duty per period
+ */
+struct mt6323_hwspec {
+ u16 max_period;
+ u8 max_leds;
+ u8 max_wleds;
+ u16 max_brightness;
+ u16 unit_duty;
+};
+
+/**
+ * struct mt6323_data - device specific data
+ * @regs: Register spec for this device
+ * @spec: Hardware specific parameters
+ */
+struct mt6323_data {
+ const struct mt6323_regs *regs;
+ const struct mt6323_hwspec *spec;
+};
+
+/**
+ * struct mt6323_leds - state container for holding LED controller
+ * of the driver
+ * @dev: the device pointer
+ * @hw: the underlying hardware providing shared
+ * bus for the register operations
+ * @pdata: device specific data
+ * @lock: the lock among process context
+ * @led: the array that contains the state of individual
+ * LED device
+ */
+struct mt6323_leds {
+ struct device *dev;
+ struct mt6397_chip *hw;
+ const struct mt6323_data *pdata;
+ /* protect among process context */
+ struct mutex lock;
+ struct mt6323_led *led[MAX_SUPPORTED_LEDS];
+};
+
+static int mt6323_led_hw_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ u32 con2_mask = 0, con2_val = 0;
+ int ret;
+
+ /*
+ * Setup current output for the corresponding
+ * brightness level.
+ */
+ con2_mask |= ISINK_CH_STEP_MASK |
+ ISINK_SFSTR0_TC_MASK |
+ ISINK_SFSTR0_EN_MASK;
+ con2_val |= ISINK_CH_STEP(brightness - 1) |
+ ISINK_SFSTR0_TC(2) |
+ ISINK_SFSTR0_EN;
+
+ ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[2], led->id),
+ con2_mask, con2_val);
+ return ret;
+}
+
+static int mt6323_led_hw_off(struct led_classdev *cdev)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ unsigned int status;
+ int ret;
+
+ status = ISINK_CH_EN(led->id);
+ ret = regmap_update_bits(regmap, regs->isink_en_ctrl,
+ ISINK_CH_EN_MASK(led->id), ~status);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(100, 300);
+ ret = regmap_update_bits(regmap, regs->top_ckpdn[2],
+ RG_ISINK_CK_PDN_MASK(led->id),
+ RG_ISINK_CK_PDN(led->id));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static enum led_brightness
+mt6323_get_led_hw_brightness(struct led_classdev *cdev)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(regmap, regs->top_ckpdn[2], &status);
+ if (ret < 0)
+ return ret;
+
+ if (status & RG_ISINK_CK_PDN_MASK(led->id))
+ return 0;
+
+ ret = regmap_read(regmap, regs->isink_en_ctrl, &status);
+ if (ret < 0)
+ return ret;
+
+ if (!(status & ISINK_CH_EN(led->id)))
+ return 0;
+
+ ret = regmap_read(regmap, ISINK_CON(regs->isink_con[2], led->id), &status);
+ if (ret < 0)
+ return ret;
+
+ return ((status & ISINK_CH_STEP_MASK)
+ >> ISINK_CH_STEP_SHIFT) + 1;
+}
+
+static int mt6323_led_hw_on(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ unsigned int status;
+ int ret;
+
+ /*
+ * Setup required clock source, enable the corresponding
+ * clock and channel and let work with continuous blink as
+ * the default.
+ */
+ ret = regmap_update_bits(regmap, regs->top_ckcon[1],
+ RG_ISINK_CK_SEL_MASK(led->id), 0);
+ if (ret < 0)
+ return ret;
+
+ status = RG_ISINK_CK_PDN(led->id);
+ ret = regmap_update_bits(regmap, regs->top_ckpdn[2],
+ RG_ISINK_CK_PDN_MASK(led->id),
+ ~status);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(100, 300);
+
+ ret = regmap_update_bits(regmap, regs->isink_en_ctrl,
+ ISINK_CH_EN_MASK(led->id),
+ ISINK_CH_EN(led->id));
+ if (ret < 0)
+ return ret;
+
+ ret = mt6323_led_hw_brightness(cdev, brightness);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
+ ISINK_DIM_DUTY_MASK,
+ ISINK_DIM_DUTY(31));
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),
+ ISINK_DIM_FSEL_MASK,
+ ISINK_DIM_FSEL(1000));
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static int mt6323_led_set_blink(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ const struct mt6323_hwspec *spec = leds->pdata->spec;
+ struct regmap *regmap = leds->hw->regmap;
+ unsigned long period;
+ u8 duty_hw;
+ int ret;
+
+ /*
+ * LED subsystem requires a default user
+ * friendly blink pattern for the LED so using
+ * 1Hz duty cycle 50% here if without specific
+ * value delay_on and delay off being assigned.
+ */
+ if (!*delay_on && !*delay_off) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ /*
+ * Units are in ms, if over the hardware able
+ * to support, fallback into software blink
+ */
+ period = *delay_on + *delay_off;
+
+ if (period > spec->max_period)
+ return -EINVAL;
+
+ /*
+ * Calculate duty_hw based on the percentage of period during
+ * which the led is ON.
+ */
+ duty_hw = DIV_ROUND_CLOSEST(*delay_on * 100000ul, period * spec->unit_duty);
+
+ /* hardware doesn't support zero duty cycle. */
+ if (!duty_hw)
+ return -EINVAL;
+
+ mutex_lock(&leds->lock);
+ /*
+ * Set max_brightness as the software blink behavior
+ * when no blink brightness.
+ */
+ if (!led->current_brightness) {
+ ret = mt6323_led_hw_on(cdev, cdev->max_brightness);
+ if (ret < 0)
+ goto out;
+ led->current_brightness = cdev->max_brightness;
+ }
+
+ ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[0], led->id),
+ ISINK_DIM_DUTY_MASK,
+ ISINK_DIM_DUTY(duty_hw - 1));
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_update_bits(regmap, ISINK_CON(regs->isink_con[1], led->id),
+ ISINK_DIM_FSEL_MASK,
+ ISINK_DIM_FSEL(period - 1));
+out:
+ mutex_unlock(&leds->lock);
+
+ return ret;
+}
+
+static int mt6323_led_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ int ret;
+
+ mutex_lock(&leds->lock);
+
+ if (!led->current_brightness && brightness) {
+ ret = mt6323_led_hw_on(cdev, brightness);
+ if (ret < 0)
+ goto out;
+ } else if (brightness) {
+ ret = mt6323_led_hw_brightness(cdev, brightness);
+ if (ret < 0)
+ goto out;
+ } else {
+ ret = mt6323_led_hw_off(cdev);
+ if (ret < 0)
+ goto out;
+ }
+
+ led->current_brightness = brightness;
+out:
+ mutex_unlock(&leds->lock);
+
+ return ret;
+}
+
+static int mtk_wled_hw_on(struct led_classdev *cdev)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ int ret;
+
+ ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);
+ if (ret)
+ return ret;
+
+ usleep_range(5000, 6000);
+
+ /* Enable WLED channel pair */
+ ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static int mtk_wled_hw_off(struct led_classdev *cdev)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ int ret;
+
+ ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id + 1));
+ if (ret)
+ return ret;
+
+ ret = regmap_clear_bits(regmap, regs->iwled_en_ctrl, BIT(led->id));
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_32K_CK_PDN);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_6M_CK_PDN);
+ if (ret)
+ return ret;
+
+ ret = regmap_set_bits(regmap, regs->top_ckpdn[0], RG_VWLED_1M_CK_PDN);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static enum led_brightness mt6323_get_wled_brightness(struct led_classdev *cdev)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ struct regmap *regmap = leds->hw->regmap;
+ unsigned int status;
+ int ret;
+
+ ret = regmap_read(regmap, regs->iwled_en_ctrl, &status);
+ if (ret)
+ return 0;
+
+ /* Always two channels per WLED */
+ status &= BIT(led->id) | BIT(led->id + 1);
+
+ return status ? led->current_brightness : 0;
+}
+
+static int mt6323_wled_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ struct mt6323_leds *leds = led->parent;
+ int ret = 0;
+
+ mutex_lock(&leds->lock);
+
+ if (brightness) {
+ if (!led->current_brightness)
+ ret = mtk_wled_hw_on(cdev);
+ if (ret)
+ goto out;
+ } else {
+ ret = mtk_wled_hw_off(cdev);
+ if (ret)
+ goto out;
+ }
+
+ led->current_brightness = brightness;
+out:
+ mutex_unlock(&leds->lock);
+
+ return ret;
+}
+
+static int mt6323_led_set_dt_default(struct led_classdev *cdev,
+ struct device_node *np)
+{
+ struct mt6323_led *led = container_of(cdev, struct mt6323_led, cdev);
+ enum led_default_state state;
+ int ret = 0;
+
+ state = led_init_default_state_get(of_fwnode_handle(np));
+ switch (state) {
+ case LEDS_DEFSTATE_ON:
+ ret = mt6323_led_set_brightness(cdev, cdev->max_brightness);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ ret = mt6323_get_led_hw_brightness(cdev);
+ if (ret < 0)
+ return ret;
+ led->current_brightness = ret;
+ ret = 0;
+ break;
+ default:
+ ret = mt6323_led_set_brightness(cdev, LED_OFF);
+ }
+
+ return ret;
+}
+
+static int mt6323_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct mt6397_chip *hw = dev_get_drvdata(dev->parent);
+ struct mt6323_leds *leds;
+ struct mt6323_led *led;
+ const struct mt6323_regs *regs;
+ const struct mt6323_hwspec *spec;
+ int ret;
+ unsigned int status;
+ u32 reg;
+ u8 max_leds;
+
+ leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, leds);
+ leds->dev = dev;
+ leds->pdata = device_get_match_data(dev);
+ regs = leds->pdata->regs;
+ spec = leds->pdata->spec;
+ max_leds = spec->max_leds + spec->max_wleds;
+
+ /*
+ * leds->hw points to the underlying bus for the register
+ * controlled.
+ */
+ leds->hw = hw;
+ mutex_init(&leds->lock);
+
+ status = RG_DRV_32K_CK_PDN;
+ ret = regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],
+ RG_DRV_32K_CK_PDN_MASK, ~status);
+ if (ret < 0) {
+ dev_err(leds->dev,
+ "Failed to update TOP_CKPDN0 Register\n");
+ return ret;
+ }
+
+ 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");
+ return ret;
+ }
+
+ if (reg >= max_leds || reg >= MAX_SUPPORTED_LEDS ||
+ leds->led[reg]) {
+ dev_err(dev, "Invalid led reg %u\n", reg);
+ return -EINVAL;
+ }
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ is_wled = of_property_read_bool(child, "mediatek,is-wled");
+
+ leds->led[reg] = led;
+ leds->led[reg]->id = reg;
+ leds->led[reg]->cdev.max_brightness = spec->max_brightness;
+
+ if (is_wled) {
+ leds->led[reg]->cdev.brightness_set_blocking =
+ mt6323_wled_set_brightness;
+ leds->led[reg]->cdev.brightness_get =
+ mt6323_get_wled_brightness;
+ } else {
+ leds->led[reg]->cdev.brightness_set_blocking =
+ mt6323_led_set_brightness;
+ leds->led[reg]->cdev.blink_set = mt6323_led_set_blink;
+ leds->led[reg]->cdev.brightness_get =
+ mt6323_get_led_hw_brightness;
+ }
+ leds->led[reg]->parent = leds;
+
+ ret = mt6323_led_set_dt_default(&leds->led[reg]->cdev, child);
+ if (ret < 0) {
+ dev_err(leds->dev,
+ "Failed to LED set default from devicetree\n");
+ return ret;
+ }
+
+ init_data.fwnode = of_fwnode_handle(child);
+
+ ret = devm_led_classdev_register_ext(dev, &leds->led[reg]->cdev,
+ &init_data);
+ if (ret) {
+ dev_err(dev, "Failed to register LED: %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void mt6323_led_remove(struct platform_device *pdev)
+{
+ struct mt6323_leds *leds = platform_get_drvdata(pdev);
+ const struct mt6323_regs *regs = leds->pdata->regs;
+ int i;
+
+ /* Turn the LEDs off on driver removal. */
+ for (i = 0 ; leds->led[i] ; i++)
+ mt6323_led_hw_off(&leds->led[i]->cdev);
+
+ regmap_update_bits(leds->hw->regmap, regs->top_ckpdn[0],
+ RG_DRV_32K_CK_PDN_MASK,
+ RG_DRV_32K_CK_PDN);
+
+ mutex_destroy(&leds->lock);
+}
+
+static const struct mt6323_regs mt6323_registers = {
+ .top_ckpdn = (const u16[]){ 0x102, 0x106, 0x10e },
+ .num_top_ckpdn = 3,
+ .top_ckcon = (const u16[]){ 0x120, 0x126 },
+ .num_top_ckcon = 2,
+ .isink_con = (const u16[]){ 0x330, 0x332, 0x334 },
+ .num_isink_con = 3,
+ .isink_max_regs = 4, /* ISINK[0..3] */
+ .isink_en_ctrl = 0x356,
+};
+
+static const struct mt6323_regs mt6331_registers = {
+ .top_ckpdn = (const u16[]){ 0x138, 0x13e, 0x144 },
+ .num_top_ckpdn = 3,
+ .top_ckcon = (const u16[]){ 0x14c, 0x14a },
+ .num_top_ckcon = 2,
+ .isink_con = (const u16[]){ 0x40c, 0x40e, 0x410, 0x412, 0x414 },
+ .num_isink_con = 5,
+ .isink_max_regs = 4, /* ISINK[0..3] */
+ .isink_en_ctrl = 0x43a,
+};
+
+static const struct mt6323_regs mt6332_registers = {
+ .top_ckpdn = (const u16[]){ 0x8094, 0x809a, 0x80a0 },
+ .num_top_ckpdn = 3,
+ .top_ckcon = (const u16[]){ 0x80a6, 0x80ac },
+ .num_top_ckcon = 2,
+ .isink_con = (const u16[]){ 0x8cd4 },
+ .num_isink_con = 1,
+ .isink_max_regs = 12, /* IWLED[0..2, 3..9] */
+ .iwled_en_ctrl = 0x8cda,
+};
+
+static const struct mt6323_hwspec mt6323_spec = {
+ .max_period = 10000,
+ .max_leds = 4,
+ .max_brightness = 6,
+ .unit_duty = 3125,
+};
+
+static const struct mt6323_hwspec mt6332_spec = {
+ /* There are no LEDs in MT6332. Only WLEDs are present. */
+ .max_leds = 0,
+ .max_wleds = 1,
+ .max_brightness = 1024,
+};
+
+static const struct mt6323_data mt6323_pdata = {
+ .regs = &mt6323_registers,
+ .spec = &mt6323_spec,
+};
+
+static const struct mt6323_data mt6331_pdata = {
+ .regs = &mt6331_registers,
+ .spec = &mt6323_spec,
+};
+
+static const struct mt6323_data mt6332_pdata = {
+ .regs = &mt6332_registers,
+ .spec = &mt6332_spec,
+};
+
+static const struct of_device_id mt6323_led_dt_match[] = {
+ { .compatible = "mediatek,mt6323-led", .data = &mt6323_pdata},
+ { .compatible = "mediatek,mt6331-led", .data = &mt6331_pdata },
+ { .compatible = "mediatek,mt6332-led", .data = &mt6332_pdata },
+ {},
+};
+MODULE_DEVICE_TABLE(of, mt6323_led_dt_match);
+
+static struct platform_driver mt6323_led_driver = {
+ .probe = mt6323_led_probe,
+ .remove = mt6323_led_remove,
+ .driver = {
+ .name = "mt6323-led",
+ .of_match_table = mt6323_led_dt_match,
+ },
+};
+
+module_platform_driver(mt6323_led_driver);
+
+MODULE_DESCRIPTION("LED driver for Mediatek MT6323 PMIC");
+MODULE_AUTHOR("Sean Wang <sean.wang@mediatek.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-net48xx.c b/drivers/leds/leds-net48xx.c
index 27d06c528246..a93468c13772 100644
--- a/drivers/leds/leds-net48xx.c
+++ b/drivers/leds/leds-net48xx.c
@@ -1,13 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LEDs driver for Soekris net48xx
*
* Copyright (C) 2006 Chris Boot <bootc@bootc.net>
*
* Based on leds-ams-delta.c
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/kernel.h>
@@ -39,21 +36,13 @@ static struct led_classdev net48xx_error_led = {
static int net48xx_led_probe(struct platform_device *pdev)
{
- return led_classdev_register(&pdev->dev, &net48xx_error_led);
-}
-
-static int net48xx_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&net48xx_error_led);
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &net48xx_error_led);
}
static struct platform_driver net48xx_led_driver = {
.probe = net48xx_led_probe,
- .remove = net48xx_led_remove,
.driver = {
.name = DRVNAME,
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c
index c61c5ebcc08e..99df46f2d9f5 100644
--- a/drivers/leds/leds-netxbig.c
+++ b/drivers/leds/leds-netxbig.c
@@ -1,34 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* leds-netxbig.c - Driver for the 2Big and 5Big Network series LEDs
*
* Copyright (C) 2010 LaCie
*
* Author: Simon Guinot <sguinot@lacie.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
-#include <linux/init.h>
#include <linux/irq.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/platform_device.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/leds.h>
-#include <linux/platform_data/leds-kirkwood-netxbig.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+struct netxbig_gpio_ext {
+ struct gpio_desc **addr;
+ int num_addr;
+ struct gpio_desc **data;
+ int num_data;
+ struct gpio_desc *enable;
+};
+
+enum netxbig_led_mode {
+ NETXBIG_LED_OFF,
+ NETXBIG_LED_ON,
+ NETXBIG_LED_SATA,
+ NETXBIG_LED_TIMER1,
+ NETXBIG_LED_TIMER2,
+ NETXBIG_LED_MODE_NUM,
+};
+
+#define NETXBIG_LED_INVALID_MODE NETXBIG_LED_MODE_NUM
+
+struct netxbig_led_timer {
+ unsigned long delay_on;
+ unsigned long delay_off;
+ enum netxbig_led_mode mode;
+};
+
+struct netxbig_led {
+ const char *name;
+ const char *default_trigger;
+ int mode_addr;
+ int *mode_val;
+ int bright_addr;
+ int bright_max;
+};
+
+struct netxbig_led_platform_data {
+ struct netxbig_gpio_ext *gpio_ext;
+ struct netxbig_led_timer *timer;
+ int num_timer;
+ struct netxbig_led *leds;
+ int num_leds;
+};
/*
* GPIO extension bus.
@@ -41,7 +70,7 @@ static void gpio_ext_set_addr(struct netxbig_gpio_ext *gpio_ext, int addr)
int pin;
for (pin = 0; pin < gpio_ext->num_addr; pin++)
- gpio_set_value(gpio_ext->addr[pin], (addr >> pin) & 1);
+ gpiod_set_value(gpio_ext->addr[pin], (addr >> pin) & 1);
}
static void gpio_ext_set_data(struct netxbig_gpio_ext *gpio_ext, int data)
@@ -49,14 +78,14 @@ static void gpio_ext_set_data(struct netxbig_gpio_ext *gpio_ext, int data)
int pin;
for (pin = 0; pin < gpio_ext->num_data; pin++)
- gpio_set_value(gpio_ext->data[pin], (data >> pin) & 1);
+ gpiod_set_value(gpio_ext->data[pin], (data >> pin) & 1);
}
static void gpio_ext_enable_select(struct netxbig_gpio_ext *gpio_ext)
{
/* Enable select is done on the raising edge. */
- gpio_set_value(gpio_ext->enable, 0);
- gpio_set_value(gpio_ext->enable, 1);
+ gpiod_set_value(gpio_ext->enable, 0);
+ gpiod_set_value(gpio_ext->enable, 1);
}
static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext,
@@ -71,58 +100,6 @@ static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext,
spin_unlock_irqrestore(&gpio_ext_lock, flags);
}
-static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
-{
- int err;
- int i;
-
- if (unlikely(!gpio_ext))
- return -EINVAL;
-
- /* Configure address GPIOs. */
- for (i = 0; i < gpio_ext->num_addr; i++) {
- err = gpio_request_one(gpio_ext->addr[i], GPIOF_OUT_INIT_LOW,
- "GPIO extension addr");
- if (err)
- goto err_free_addr;
- }
- /* Configure data GPIOs. */
- for (i = 0; i < gpio_ext->num_data; i++) {
- err = gpio_request_one(gpio_ext->data[i], GPIOF_OUT_INIT_LOW,
- "GPIO extension data");
- if (err)
- goto err_free_data;
- }
- /* Configure "enable select" GPIO. */
- err = gpio_request_one(gpio_ext->enable, GPIOF_OUT_INIT_LOW,
- "GPIO extension enable");
- if (err)
- goto err_free_data;
-
- return 0;
-
-err_free_data:
- for (i = i - 1; i >= 0; i--)
- gpio_free(gpio_ext->data[i]);
- i = gpio_ext->num_addr;
-err_free_addr:
- for (i = i - 1; i >= 0; i--)
- gpio_free(gpio_ext->addr[i]);
-
- return err;
-}
-
-static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext)
-{
- int i;
-
- gpio_free(gpio_ext->enable);
- for (i = gpio_ext->num_addr - 1; i >= 0; i--)
- gpio_free(gpio_ext->addr[i]);
- for (i = gpio_ext->num_data - 1; i >= 0; i--)
- gpio_free(gpio_ext->data[i]);
-}
-
/*
* Class LED driver.
*/
@@ -133,7 +110,6 @@ struct netxbig_led_data {
int mode_addr;
int *mode_val;
int bright_addr;
- int bright_max;
struct netxbig_led_timer *timer;
int num_timer;
enum netxbig_led_mode mode;
@@ -195,7 +171,7 @@ static void netxbig_led_set(struct led_classdev *led_cdev,
struct netxbig_led_data *led_dat =
container_of(led_cdev, struct netxbig_led_data, cdev);
enum netxbig_led_mode mode;
- int mode_val, bright_val;
+ int mode_val;
int set_brightness = 1;
unsigned long flags;
@@ -221,19 +197,16 @@ static void netxbig_led_set(struct led_classdev *led_cdev,
* SATA LEDs. So, change the brightness setting for a single
* SATA LED will affect all the others.
*/
- if (set_brightness) {
- bright_val = DIV_ROUND_UP(value * led_dat->bright_max,
- LED_FULL);
+ if (set_brightness)
gpio_ext_set_value(led_dat->gpio_ext,
- led_dat->bright_addr, bright_val);
- }
+ led_dat->bright_addr, value);
spin_unlock_irqrestore(&led_dat->lock, flags);
}
-static ssize_t netxbig_led_sata_store(struct device *dev,
- struct device_attribute *attr,
- const char *buff, size_t count)
+static ssize_t sata_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buff, size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct netxbig_led_data *led_dat =
@@ -282,8 +255,8 @@ exit_unlock:
return ret;
}
-static ssize_t netxbig_led_sata_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t sata_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct netxbig_led_data *led_dat =
@@ -292,23 +265,19 @@ static ssize_t netxbig_led_sata_show(struct device *dev,
return sprintf(buf, "%d\n", led_dat->sata);
}
-static DEVICE_ATTR(sata, 0644, netxbig_led_sata_show, netxbig_led_sata_store);
+static DEVICE_ATTR_RW(sata);
-static void delete_netxbig_led(struct netxbig_led_data *led_dat)
-{
- if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE)
- device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
- led_classdev_unregister(&led_dat->cdev);
-}
+static struct attribute *netxbig_led_attrs[] = {
+ &dev_attr_sata.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(netxbig_led);
-static int
-create_netxbig_led(struct platform_device *pdev,
- struct netxbig_led_data *led_dat,
- const struct netxbig_led *template)
+static int create_netxbig_led(struct platform_device *pdev,
+ struct netxbig_led_platform_data *pdata,
+ struct netxbig_led_data *led_dat,
+ const struct netxbig_led *template)
{
- struct netxbig_led_platform_data *pdata = pdev->dev.platform_data;
- int ret;
-
spin_lock_init(&led_dat->lock);
led_dat->gpio_ext = pdata->gpio_ext;
led_dat->cdev.name = template->name;
@@ -327,90 +296,355 @@ create_netxbig_led(struct platform_device *pdev,
*/
led_dat->sata = 0;
led_dat->cdev.brightness = LED_OFF;
+ led_dat->cdev.max_brightness = template->bright_max;
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
led_dat->mode_addr = template->mode_addr;
led_dat->mode_val = template->mode_val;
led_dat->bright_addr = template->bright_addr;
- led_dat->bright_max = (1 << pdata->gpio_ext->num_data) - 1;
led_dat->timer = pdata->timer;
led_dat->num_timer = pdata->num_timer;
+ /*
+ * If available, expose the SATA activity blink capability through
+ * a "sata" sysfs attribute.
+ */
+ if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE)
+ led_dat->cdev.groups = netxbig_led_groups;
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0)
+ return devm_led_classdev_register(&pdev->dev, &led_dat->cdev);
+}
+
+/**
+ * netxbig_gpio_ext_remove() - Clean up GPIO extension data
+ * @data: managed resource data to clean up
+ *
+ * Since we pick GPIO descriptors from another device than the device our
+ * driver is probing to, we need to register a specific callback to free
+ * these up using managed resources.
+ */
+static void netxbig_gpio_ext_remove(void *data)
+{
+ struct netxbig_gpio_ext *gpio_ext = data;
+ int i;
+
+ for (i = 0; i < gpio_ext->num_addr; i++)
+ gpiod_put(gpio_ext->addr[i]);
+ for (i = 0; i < gpio_ext->num_data; i++)
+ gpiod_put(gpio_ext->data[i]);
+ gpiod_put(gpio_ext->enable);
+}
+
+/**
+ * netxbig_gpio_ext_get() - Obtain GPIO extension device data
+ * @dev: main LED device
+ * @gpio_ext_dev: the GPIO extension device
+ * @gpio_ext: the data structure holding the GPIO extension data
+ *
+ * This function walks the subdevice that only contain GPIO line
+ * handles in the device tree and obtains the GPIO descriptors from that
+ * device.
+ */
+static int netxbig_gpio_ext_get(struct device *dev,
+ struct device *gpio_ext_dev,
+ struct netxbig_gpio_ext *gpio_ext)
+{
+ struct gpio_desc **addr, **data;
+ int num_addr, num_data;
+ struct gpio_desc *gpiod;
+ int ret;
+ int i;
+
+ ret = gpiod_count(gpio_ext_dev, "addr");
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to count GPIOs in DT property addr-gpios\n");
return ret;
+ }
+ num_addr = ret;
+ addr = devm_kcalloc(dev, num_addr, sizeof(*addr), GFP_KERNEL);
+ if (!addr)
+ return -ENOMEM;
+
+ gpio_ext->addr = addr;
+ gpio_ext->num_addr = 0;
/*
- * If available, expose the SATA activity blink capability through
- * a "sata" sysfs attribute.
+ * We cannot use devm_ managed resources with these GPIO descriptors
+ * since they are associated with the "GPIO extension device" which
+ * does not probe any driver. The device tree parser will however
+ * populate a platform device for it so we can anyway obtain the
+ * GPIO descriptors from the device.
*/
- if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE) {
- ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
- if (ret)
- led_classdev_unregister(&led_dat->cdev);
+ for (i = 0; i < num_addr; i++) {
+ gpiod = gpiod_get_index(gpio_ext_dev, "addr", i,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ goto err_set_code;
+ gpiod_set_consumer_name(gpiod, "GPIO extension addr");
+ addr[i] = gpiod;
+ gpio_ext->num_addr++;
}
+ ret = gpiod_count(gpio_ext_dev, "data");
+ if (ret < 0) {
+ dev_err(dev,
+ "Failed to count GPIOs in DT property data-gpios\n");
+ goto err_free_addr;
+ }
+ num_data = ret;
+ data = devm_kcalloc(dev, num_data, sizeof(*data), GFP_KERNEL);
+ if (!data) {
+ ret = -ENOMEM;
+ goto err_free_addr;
+ }
+
+ gpio_ext->data = data;
+ gpio_ext->num_data = 0;
+
+ for (i = 0; i < num_data; i++) {
+ gpiod = gpiod_get_index(gpio_ext_dev, "data", i,
+ GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod))
+ goto err_free_data;
+ gpiod_set_consumer_name(gpiod, "GPIO extension data");
+ data[i] = gpiod;
+ gpio_ext->num_data++;
+ }
+
+ gpiod = gpiod_get(gpio_ext_dev, "enable", GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod)) {
+ dev_err(dev,
+ "Failed to get GPIO from DT property enable-gpio\n");
+ goto err_free_data;
+ }
+ gpiod_set_consumer_name(gpiod, "GPIO extension enable");
+ gpio_ext->enable = gpiod;
+
+ return devm_add_action_or_reset(dev, netxbig_gpio_ext_remove, gpio_ext);
+
+err_free_data:
+ for (i = 0; i < gpio_ext->num_data; i++)
+ gpiod_put(gpio_ext->data[i]);
+err_set_code:
+ ret = PTR_ERR(gpiod);
+err_free_addr:
+ for (i = 0; i < gpio_ext->num_addr; i++)
+ gpiod_put(gpio_ext->addr[i]);
return ret;
}
-static int netxbig_led_probe(struct platform_device *pdev)
+static int netxbig_leds_get_of_pdata(struct device *dev,
+ struct netxbig_led_platform_data *pdata)
{
- struct netxbig_led_platform_data *pdata = pdev->dev.platform_data;
- struct netxbig_led_data *leds_data;
- int i;
+ struct device_node *np = dev_of_node(dev);
+ struct device_node *gpio_ext_np;
+ struct platform_device *gpio_ext_pdev;
+ struct device *gpio_ext_dev;
+ struct netxbig_gpio_ext *gpio_ext;
+ struct netxbig_led_timer *timers;
+ struct netxbig_led *leds, *led;
+ int num_timers;
+ int num_leds = 0;
int ret;
+ int i;
- if (!pdata)
+ /* GPIO extension */
+ gpio_ext_np = of_parse_phandle(np, "gpio-ext", 0);
+ if (!gpio_ext_np) {
+ dev_err(dev, "Failed to get DT handle gpio-ext\n");
return -EINVAL;
+ }
+ 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;
+ }
+ gpio_ext_dev = &gpio_ext_pdev->dev;
- leds_data = devm_kzalloc(&pdev->dev,
- sizeof(struct netxbig_led_data) * pdata->num_leds, GFP_KERNEL);
- if (!leds_data)
- return -ENOMEM;
+ gpio_ext = devm_kzalloc(dev, sizeof(*gpio_ext), GFP_KERNEL);
+ if (!gpio_ext) {
+ of_node_put(gpio_ext_np);
+ ret = -ENOMEM;
+ goto put_device;
+ }
+ ret = netxbig_gpio_ext_get(dev, gpio_ext_dev, gpio_ext);
+ of_node_put(gpio_ext_np);
+ if (ret)
+ goto put_device;
+ pdata->gpio_ext = gpio_ext;
+
+ /* Timers (optional) */
+ ret = of_property_count_u32_elems(np, "timers");
+ if (ret > 0) {
+ if (ret % 3) {
+ ret = -EINVAL;
+ goto put_device;
+ }
- ret = gpio_ext_init(pdata->gpio_ext);
- if (ret < 0)
- return ret;
+ num_timers = ret / 3;
+ timers = devm_kcalloc(dev, num_timers, sizeof(*timers),
+ GFP_KERNEL);
+ if (!timers) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
+ for (i = 0; i < num_timers; i++) {
+ u32 tmp;
+
+ of_property_read_u32_index(np, "timers", 3 * i,
+ &timers[i].mode);
+ if (timers[i].mode >= NETXBIG_LED_MODE_NUM) {
+ ret = -EINVAL;
+ goto put_device;
+ }
+ of_property_read_u32_index(np, "timers",
+ 3 * i + 1, &tmp);
+ timers[i].delay_on = tmp;
+ of_property_read_u32_index(np, "timers",
+ 3 * i + 2, &tmp);
+ timers[i].delay_off = tmp;
+ }
+ pdata->timer = timers;
+ pdata->num_timer = num_timers;
+ }
- for (i = 0; i < pdata->num_leds; i++) {
- ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]);
- if (ret < 0)
- goto err_free_leds;
+ /* LEDs */
+ num_leds = of_get_available_child_count(np);
+ if (!num_leds) {
+ dev_err(dev, "No LED subnodes found in DT\n");
+ ret = -ENODEV;
+ goto put_device;
}
- platform_set_drvdata(pdev, leds_data);
+ leds = devm_kcalloc(dev, num_leds, sizeof(*leds), GFP_KERNEL);
+ if (!leds) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
- return 0;
+ led = leds;
+ for_each_available_child_of_node_scoped(np, child) {
+ const char *string;
+ int *mode_val;
+ int num_modes;
+
+ ret = of_property_read_u32(child, "mode-addr",
+ &led->mode_addr);
+ if (ret)
+ goto put_device;
+
+ ret = of_property_read_u32(child, "bright-addr",
+ &led->bright_addr);
+ if (ret)
+ goto put_device;
+
+ ret = of_property_read_u32(child, "max-brightness",
+ &led->bright_max);
+ if (ret)
+ goto put_device;
+
+ mode_val =
+ devm_kcalloc(dev,
+ NETXBIG_LED_MODE_NUM, sizeof(*mode_val),
+ GFP_KERNEL);
+ if (!mode_val) {
+ ret = -ENOMEM;
+ goto put_device;
+ }
+
+ for (i = 0; i < NETXBIG_LED_MODE_NUM; i++)
+ mode_val[i] = NETXBIG_LED_INVALID_MODE;
+
+ ret = of_property_count_u32_elems(child, "mode-val");
+ if (ret < 0 || ret % 2) {
+ ret = -EINVAL;
+ goto put_device;
+ }
+ num_modes = ret / 2;
+ if (num_modes > NETXBIG_LED_MODE_NUM) {
+ ret = -EINVAL;
+ goto put_device;
+ }
+
+ for (i = 0; i < num_modes; i++) {
+ int mode;
+ int val;
+
+ of_property_read_u32_index(child,
+ "mode-val", 2 * i, &mode);
+ of_property_read_u32_index(child,
+ "mode-val", 2 * i + 1, &val);
+ if (mode >= NETXBIG_LED_MODE_NUM) {
+ ret = -EINVAL;
+ goto put_device;
+ }
+ mode_val[mode] = val;
+ }
+ led->mode_val = mode_val;
+
+ if (!of_property_read_string(child, "label", &string))
+ led->name = string;
+ else
+ led->name = child->name;
-err_free_leds:
- for (i = i - 1; i >= 0; i--)
- delete_netxbig_led(&leds_data[i]);
+ if (!of_property_read_string(child,
+ "linux,default-trigger", &string))
+ led->default_trigger = string;
- gpio_ext_free(pdata->gpio_ext);
+ led++;
+ }
+
+ pdata->leds = leds;
+ pdata->num_leds = num_leds;
+
+ return 0;
+
+put_device:
+ put_device(gpio_ext_dev);
return ret;
}
-static int netxbig_led_remove(struct platform_device *pdev)
+static const struct of_device_id of_netxbig_leds_match[] = {
+ { .compatible = "lacie,netxbig-leds", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_netxbig_leds_match);
+
+static int netxbig_led_probe(struct platform_device *pdev)
{
- struct netxbig_led_platform_data *pdata = pdev->dev.platform_data;
+ struct netxbig_led_platform_data *pdata;
struct netxbig_led_data *leds_data;
int i;
+ int ret;
- leds_data = platform_get_drvdata(pdev);
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+ ret = netxbig_leds_get_of_pdata(&pdev->dev, pdata);
+ if (ret)
+ return ret;
- for (i = 0; i < pdata->num_leds; i++)
- delete_netxbig_led(&leds_data[i]);
+ leds_data = devm_kcalloc(&pdev->dev,
+ pdata->num_leds, sizeof(*leds_data),
+ GFP_KERNEL);
+ if (!leds_data)
+ return -ENOMEM;
- gpio_ext_free(pdata->gpio_ext);
+ for (i = 0; i < pdata->num_leds; i++) {
+ ret = create_netxbig_led(pdev, pdata,
+ &leds_data[i], &pdata->leds[i]);
+ if (ret < 0)
+ return ret;
+ }
return 0;
}
static struct platform_driver netxbig_led_driver = {
.probe = netxbig_led_probe,
- .remove = netxbig_led_remove,
.driver = {
- .name = "leds-netxbig",
- .owner = THIS_MODULE,
+ .name = "leds-netxbig",
+ .of_match_table = of_netxbig_leds_match,
},
};
diff --git a/drivers/leds/leds-nic78bx.c b/drivers/leds/leds-nic78bx.c
new file mode 100644
index 000000000000..f3161266b8ad
--- /dev/null
+++ b/drivers/leds/leds-nic78bx.c
@@ -0,0 +1,209 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2016 National Instruments Corp.
+ */
+
+#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)
+#define NIC78BX_USER1_YELLOW_LED BIT(1)
+
+#define NIC78BX_USER2_LED_MASK 0xC
+#define NIC78BX_USER2_GREEN_LED BIT(2)
+#define NIC78BX_USER2_YELLOW_LED BIT(3)
+
+#define NIC78BX_LOCK_REG_OFFSET 1
+#define NIC78BX_LOCK_VALUE 0xA5
+#define NIC78BX_UNLOCK_VALUE 0x5A
+
+#define NIC78BX_USER_LED_IO_SIZE 2
+
+struct nic78bx_led_data {
+ u16 io_base;
+ spinlock_t lock;
+ struct platform_device *pdev;
+};
+
+struct nic78bx_led {
+ u8 bit;
+ u8 mask;
+ struct nic78bx_led_data *data;
+ struct led_classdev cdev;
+};
+
+static inline struct nic78bx_led *to_nic78bx_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct nic78bx_led, cdev);
+}
+
+static void nic78bx_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct nic78bx_led *nled = to_nic78bx_led(cdev);
+ unsigned long flags;
+ u8 value;
+
+ spin_lock_irqsave(&nled->data->lock, flags);
+ value = inb(nled->data->io_base);
+
+ if (brightness) {
+ value &= ~nled->mask;
+ value |= nled->bit;
+ } else {
+ value &= ~nled->bit;
+ }
+
+ outb(value, nled->data->io_base);
+ spin_unlock_irqrestore(&nled->data->lock, flags);
+}
+
+static enum led_brightness nic78bx_brightness_get(struct led_classdev *cdev)
+{
+ struct nic78bx_led *nled = to_nic78bx_led(cdev);
+ unsigned long flags;
+ u8 value;
+
+ spin_lock_irqsave(&nled->data->lock, flags);
+ value = inb(nled->data->io_base);
+ spin_unlock_irqrestore(&nled->data->lock, flags);
+
+ return (value & nled->bit) ? 1 : LED_OFF;
+}
+
+static struct nic78bx_led nic78bx_leds[] = {
+ {
+ .bit = NIC78BX_USER1_GREEN_LED,
+ .mask = NIC78BX_USER1_LED_MASK,
+ .cdev = {
+ .name = "nilrt:green:user1",
+ .max_brightness = 1,
+ .brightness_set = nic78bx_brightness_set,
+ .brightness_get = nic78bx_brightness_get,
+ }
+ },
+ {
+ .bit = NIC78BX_USER1_YELLOW_LED,
+ .mask = NIC78BX_USER1_LED_MASK,
+ .cdev = {
+ .name = "nilrt:yellow:user1",
+ .max_brightness = 1,
+ .brightness_set = nic78bx_brightness_set,
+ .brightness_get = nic78bx_brightness_get,
+ }
+ },
+ {
+ .bit = NIC78BX_USER2_GREEN_LED,
+ .mask = NIC78BX_USER2_LED_MASK,
+ .cdev = {
+ .name = "nilrt:green:user2",
+ .max_brightness = 1,
+ .brightness_set = nic78bx_brightness_set,
+ .brightness_get = nic78bx_brightness_get,
+ }
+ },
+ {
+ .bit = NIC78BX_USER2_YELLOW_LED,
+ .mask = NIC78BX_USER2_LED_MASK,
+ .cdev = {
+ .name = "nilrt:yellow:user2",
+ .max_brightness = 1,
+ .brightness_set = nic78bx_brightness_set,
+ .brightness_get = nic78bx_brightness_get,
+ }
+ }
+};
+
+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;
+ struct nic78bx_led_data *led_data;
+ struct resource *io_rc;
+ int ret, i;
+
+ led_data = devm_kzalloc(dev, sizeof(*led_data), GFP_KERNEL);
+ if (!led_data)
+ return -ENOMEM;
+
+ led_data->pdev = pdev;
+ platform_set_drvdata(pdev, led_data);
+
+ io_rc = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ if (!io_rc) {
+ dev_err(dev, "missing IO resources\n");
+ return -EINVAL;
+ }
+
+ if (resource_size(io_rc) < NIC78BX_USER_LED_IO_SIZE) {
+ dev_err(dev, "IO region too small\n");
+ return -EINVAL;
+ }
+
+ if (!devm_request_region(dev, io_rc->start, resource_size(io_rc),
+ KBUILD_MODNAME)) {
+ dev_err(dev, "failed to get IO region\n");
+ return -EBUSY;
+ }
+
+ 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;
+
+ ret = devm_led_classdev_register(dev, &nic78bx_leds[i].cdev);
+ if (ret)
+ return ret;
+ }
+
+ /* Unlock LED register */
+ outb(NIC78BX_UNLOCK_VALUE,
+ led_data->io_base + NIC78BX_LOCK_REG_OFFSET);
+
+ return ret;
+}
+
+static const struct acpi_device_id led_device_ids[] = {
+ { "NIC78B3" },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, led_device_ids);
+
+static struct platform_driver led_driver = {
+ .probe = nic78bx_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ .acpi_match_table = led_device_ids,
+ },
+};
+
+module_platform_driver(led_driver);
+
+MODULE_DESCRIPTION("National Instruments PXI User LEDs driver");
+MODULE_AUTHOR("Hui Chun Ong <hui.chun.ong@ni.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c
index e7df9875c400..4c6f04a5bd87 100644
--- a/drivers/leds/leds-ns2.c
+++ b/drivers/leds/leds-ns2.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
/*
* leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
*
@@ -6,47 +7,16 @@
* Author: Simon Guinot <sguinot@lacie.com>
*
* Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/leds.h>
#include <linux/module.h>
-#include <linux/platform_data/leds-kirkwood-ns2.h>
-#include <linux/of_gpio.h>
-
-/*
- * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
- * relation with the SATA activity. This capability is exposed through the
- * "sata" sysfs attribute.
- *
- * The following array detail the different LED registers and the combination
- * of their possible values:
- *
- * cmd_led | slow_led | /SATA active | LED state
- * | | |
- * 1 | 0 | x | off
- * - | 1 | x | on
- * 0 | 0 | 1 | on
- * 0 | 0 | 0 | blink (rate 300ms)
- */
+#include <linux/of.h>
+#include "leds.h"
enum ns2_led_modes {
NS_V2_LED_OFF,
@@ -54,89 +24,103 @@ enum ns2_led_modes {
NS_V2_LED_SATA,
};
-struct ns2_led_mode_value {
- enum ns2_led_modes mode;
- int cmd_level;
- int slow_level;
-};
+/*
+ * If the size of this structure or types of its members is changed,
+ * the filling of array modval in function ns2_led_register must be changed
+ * accordingly.
+ */
+struct ns2_led_modval {
+ u32 mode;
+ u32 cmd_level;
+ u32 slow_level;
+} __packed;
-static struct ns2_led_mode_value ns2_led_modval[] = {
- { NS_V2_LED_OFF , 1, 0 },
- { NS_V2_LED_ON , 0, 1 },
- { NS_V2_LED_ON , 1, 1 },
- { NS_V2_LED_SATA, 0, 0 },
-};
+/*
+ * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
+ * modes are available: off, on and SATA activity blinking. The LED modes are
+ * controlled through two GPIOs (command and slow): each combination of values
+ * for the command/slow GPIOs corresponds to a LED mode.
+ */
-struct ns2_led_data {
+struct ns2_led {
struct led_classdev cdev;
- unsigned cmd;
- unsigned slow;
+ struct gpio_desc *cmd;
+ struct gpio_desc *slow;
+ bool can_sleep;
unsigned char sata; /* True when SATA mode active. */
rwlock_t rw_lock; /* Lock GPIOs. */
+ int num_modes;
+ struct ns2_led_modval *modval;
};
-static int ns2_led_get_mode(struct ns2_led_data *led_dat,
- enum ns2_led_modes *mode)
+static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode)
{
int i;
- int ret = -EINVAL;
int cmd_level;
int slow_level;
- read_lock_irq(&led_dat->rw_lock);
-
- cmd_level = gpio_get_value(led_dat->cmd);
- slow_level = gpio_get_value(led_dat->slow);
+ cmd_level = gpiod_get_value_cansleep(led->cmd);
+ slow_level = gpiod_get_value_cansleep(led->slow);
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (cmd_level == ns2_led_modval[i].cmd_level &&
- slow_level == ns2_led_modval[i].slow_level) {
- *mode = ns2_led_modval[i].mode;
- ret = 0;
- break;
+ for (i = 0; i < led->num_modes; i++) {
+ if (cmd_level == led->modval[i].cmd_level &&
+ slow_level == led->modval[i].slow_level) {
+ *mode = led->modval[i].mode;
+ return 0;
}
}
- read_unlock_irq(&led_dat->rw_lock);
-
- return ret;
+ return -EINVAL;
}
-static void ns2_led_set_mode(struct ns2_led_data *led_dat,
- enum ns2_led_modes mode)
+static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode)
{
int i;
unsigned long flags;
- write_lock_irqsave(&led_dat->rw_lock, flags);
+ for (i = 0; i < led->num_modes; i++)
+ if (mode == led->modval[i].mode)
+ break;
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (mode == ns2_led_modval[i].mode) {
- gpio_set_value(led_dat->cmd,
- ns2_led_modval[i].cmd_level);
- gpio_set_value(led_dat->slow,
- ns2_led_modval[i].slow_level);
- }
+ if (i == led->num_modes)
+ return;
+
+ write_lock_irqsave(&led->rw_lock, flags);
+
+ if (!led->can_sleep) {
+ gpiod_set_value(led->cmd, led->modval[i].cmd_level);
+ gpiod_set_value(led->slow, led->modval[i].slow_level);
+ goto exit_unlock;
}
- write_unlock_irqrestore(&led_dat->rw_lock, flags);
+ gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level);
+ gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level);
+
+exit_unlock:
+ write_unlock_irqrestore(&led->rw_lock, flags);
}
static void ns2_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
- struct ns2_led_data *led_dat =
- container_of(led_cdev, struct ns2_led_data, cdev);
+ struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
enum ns2_led_modes mode;
if (value == LED_OFF)
mode = NS_V2_LED_OFF;
- else if (led_dat->sata)
+ else if (led->sata)
mode = NS_V2_LED_SATA;
else
mode = NS_V2_LED_ON;
- ns2_led_set_mode(led_dat, mode);
+ ns2_led_set_mode(led, mode);
+}
+
+static int ns2_led_set_blocking(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ ns2_led_set(led_cdev, value);
+ return 0;
}
static ssize_t ns2_led_sata_store(struct device *dev,
@@ -144,11 +128,9 @@ static ssize_t ns2_led_sata_store(struct device *dev,
const char *buff, size_t count)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct ns2_led_data *led_dat =
- container_of(led_cdev, struct ns2_led_data, cdev);
+ struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
int ret;
unsigned long enable;
- enum ns2_led_modes mode;
ret = kstrtoul(buff, 10, &enable);
if (ret < 0)
@@ -156,20 +138,20 @@ static ssize_t ns2_led_sata_store(struct device *dev,
enable = !!enable;
- if (led_dat->sata == enable)
- return count;
+ if (led->sata == enable)
+ goto exit;
- ret = ns2_led_get_mode(led_dat, &mode);
- if (ret < 0)
- return ret;
+ led->sata = enable;
- if (enable && mode == NS_V2_LED_ON)
- ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
- if (!enable && mode == NS_V2_LED_SATA)
- ns2_led_set_mode(led_dat, NS_V2_LED_ON);
+ if (!led_get_brightness(led_cdev))
+ goto exit;
- led_dat->sata = enable;
+ if (enable)
+ ns2_led_set_mode(led, NS_V2_LED_SATA);
+ else
+ ns2_led_set_mode(led, NS_V2_LED_ON);
+exit:
return count;
}
@@ -177,128 +159,103 @@ static ssize_t ns2_led_sata_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct ns2_led_data *led_dat =
- container_of(led_cdev, struct ns2_led_data, cdev);
+ struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);
- return sprintf(buf, "%d\n", led_dat->sata);
+ return sprintf(buf, "%d\n", led->sata);
}
static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
-static int
-create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
- const struct ns2_led *template)
+static struct attribute *ns2_led_attrs[] = {
+ &dev_attr_sata.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ns2_led);
+
+static int ns2_led_register(struct device *dev, struct fwnode_handle *node,
+ struct ns2_led *led)
{
- int ret;
+ struct led_init_data init_data = {};
+ struct ns2_led_modval *modval;
enum ns2_led_modes mode;
-
- ret = devm_gpio_request_one(&pdev->dev, template->cmd,
- gpio_get_value(template->cmd) ?
- GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
- template->name);
- if (ret) {
- dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
- template->name);
- return ret;
+ int nmodes, ret;
+
+ led->cmd = devm_fwnode_gpiod_get_index(dev, node, "cmd", 0, GPIOD_ASIS,
+ fwnode_get_name(node));
+ if (IS_ERR(led->cmd))
+ return PTR_ERR(led->cmd);
+
+ led->slow = devm_fwnode_gpiod_get_index(dev, node, "slow", 0,
+ GPIOD_ASIS,
+ fwnode_get_name(node));
+ if (IS_ERR(led->slow))
+ return PTR_ERR(led->slow);
+
+ ret = fwnode_property_count_u32(node, "modes-map");
+ if (ret < 0 || ret % 3) {
+ dev_err(dev, "Missing or malformed modes-map for %pfw\n", node);
+ return -EINVAL;
}
- ret = devm_gpio_request_one(&pdev->dev, template->slow,
- gpio_get_value(template->slow) ?
- GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
- template->name);
- if (ret) {
- dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
- template->name);
- return ret;
- }
+ nmodes = ret / 3;
+ modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL);
+ if (!modval)
+ return -ENOMEM;
- rwlock_init(&led_dat->rw_lock);
+ fwnode_property_read_u32_array(node, "modes-map", (void *)modval,
+ nmodes * 3);
- led_dat->cdev.name = template->name;
- led_dat->cdev.default_trigger = template->default_trigger;
- led_dat->cdev.blink_set = NULL;
- led_dat->cdev.brightness_set = ns2_led_set;
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
- led_dat->cmd = template->cmd;
- led_dat->slow = template->slow;
+ rwlock_init(&led->rw_lock);
- ret = ns2_led_get_mode(led_dat, &mode);
+ led->cdev.blink_set = NULL;
+ led->cdev.flags |= LED_CORE_SUSPENDRESUME;
+ led->cdev.groups = ns2_led_groups;
+ led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow);
+ if (led->can_sleep)
+ led->cdev.brightness_set_blocking = ns2_led_set_blocking;
+ else
+ led->cdev.brightness_set = ns2_led_set;
+ led->num_modes = nmodes;
+ led->modval = modval;
+
+ ret = ns2_led_get_mode(led, &mode);
if (ret < 0)
return ret;
/* Set LED initial state. */
- led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
- led_dat->cdev.brightness =
- (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0)
- return ret;
+ led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
+ led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
- ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
- if (ret < 0)
- goto err_free_cdev;
+ init_data.fwnode = node;
- return 0;
+ ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret)
+ dev_err(dev, "Failed to register LED for node %pfw\n", node);
-err_free_cdev:
- led_classdev_unregister(&led_dat->cdev);
return ret;
}
-static void delete_ns2_led(struct ns2_led_data *led_dat)
-{
- device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
- led_classdev_unregister(&led_dat->cdev);
-}
-
-#ifdef CONFIG_OF_GPIO
-/*
- * Translate OpenFirmware node properties into platform_data.
- */
-static int
-ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
+static int ns2_led_probe(struct platform_device *pdev)
{
- struct device_node *np = dev->of_node;
- struct device_node *child;
+ struct device *dev = &pdev->dev;
struct ns2_led *leds;
- int num_leds = 0;
- int i = 0;
+ int count;
+ int ret;
- num_leds = of_get_child_count(np);
- if (!num_leds)
+ count = device_get_child_node_count(dev);
+ if (!count)
return -ENODEV;
- leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
- GFP_KERNEL);
+ leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL);
if (!leds)
return -ENOMEM;
- for_each_child_of_node(np, child) {
- const char *string;
- int ret;
-
- ret = of_get_named_gpio(child, "cmd-gpio", 0);
- if (ret < 0)
- return ret;
- leds[i].cmd = ret;
- ret = of_get_named_gpio(child, "slow-gpio", 0);
- if (ret < 0)
+ device_for_each_child_node_scoped(dev, child) {
+ ret = ns2_led_register(dev, child, leds++);
+ if (ret)
return ret;
- leds[i].slow = ret;
- ret = of_property_read_string(child, "label", &string);
- leds[i].name = (ret == 0) ? string : child->name;
- ret = of_property_read_string(child, "linux,default-trigger",
- &string);
- if (ret == 0)
- leds[i].default_trigger = string;
-
- i++;
}
- pdata->leds = leds;
- pdata->num_leds = num_leds;
-
return 0;
}
@@ -306,84 +263,13 @@ static const struct of_device_id of_ns2_leds_match[] = {
{ .compatible = "lacie,ns2-leds", },
{},
};
-#endif /* CONFIG_OF_GPIO */
-
-struct ns2_led_priv {
- int num_leds;
- struct ns2_led_data leds_data[];
-};
-
-static inline int sizeof_ns2_led_priv(int num_leds)
-{
- return sizeof(struct ns2_led_priv) +
- (sizeof(struct ns2_led_data) * num_leds);
-}
-
-static int ns2_led_probe(struct platform_device *pdev)
-{
- struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
- struct ns2_led_priv *priv;
- int i;
- int ret;
-
-#ifdef CONFIG_OF_GPIO
- if (!pdata) {
- pdata = devm_kzalloc(&pdev->dev,
- sizeof(struct ns2_led_platform_data),
- GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
-
- ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
- if (ret)
- return ret;
- }
-#else
- if (!pdata)
- return -EINVAL;
-#endif /* CONFIG_OF_GPIO */
-
- priv = devm_kzalloc(&pdev->dev,
- sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
- priv->num_leds = pdata->num_leds;
-
- for (i = 0; i < priv->num_leds; i++) {
- ret = create_ns2_led(pdev, &priv->leds_data[i],
- &pdata->leds[i]);
- if (ret < 0) {
- for (i = i - 1; i >= 0; i--)
- delete_ns2_led(&priv->leds_data[i]);
- return ret;
- }
- }
-
- platform_set_drvdata(pdev, priv);
-
- return 0;
-}
-
-static int ns2_led_remove(struct platform_device *pdev)
-{
- int i;
- struct ns2_led_priv *priv;
-
- priv = platform_get_drvdata(pdev);
-
- for (i = 0; i < priv->num_leds; i++)
- delete_ns2_led(&priv->leds_data[i]);
-
- return 0;
-}
+MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
static struct platform_driver ns2_led_driver = {
.probe = ns2_led_probe,
- .remove = ns2_led_remove,
.driver = {
.name = "leds-ns2",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(of_ns2_leds_match),
+ .of_match_table = of_ns2_leds_match,
},
};
diff --git a/drivers/leds/leds-ot200.c b/drivers/leds/leds-ot200.c
index 98cae529373f..12af1127d9b7 100644
--- a/drivers/leds/leds-ot200.c
+++ b/drivers/leds/leds-ot200.c
@@ -8,7 +8,6 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/leds.h>
@@ -125,9 +124,9 @@ static int ot200_led_probe(struct platform_device *pdev)
leds[i].cdev.name = leds[i].name;
leds[i].cdev.brightness_set = ot200_led_brightness_set;
- ret = led_classdev_register(&pdev->dev, &leds[i].cdev);
+ ret = devm_led_classdev_register(&pdev->dev, &leds[i].cdev);
if (ret < 0)
- goto err;
+ return ret;
}
leds_front = 0; /* turn off all front leds */
@@ -136,30 +135,12 @@ static int ot200_led_probe(struct platform_device *pdev)
outb(leds_back, 0x5a);
return 0;
-
-err:
- for (i = i - 1; i >= 0; i--)
- led_classdev_unregister(&leds[i].cdev);
-
- return ret;
-}
-
-static int ot200_led_remove(struct platform_device *pdev)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(leds); i++)
- led_classdev_unregister(&leds[i].cdev);
-
- return 0;
}
static struct platform_driver ot200_led_driver = {
.probe = ot200_led_probe,
- .remove = ot200_led_remove,
.driver = {
.name = "leds-ot200",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index 0c597bdd23f9..0344189bb991 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -1,15 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* pca9532.c - 16-bit Led dimmer
*
* Copyright (C) 2011 Jan Weitzel
* Copyright (C) 2008 Riku Voipio
*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
* Datasheet: http://www.nxp.com/documents/data_sheet/PCA9532.pdf
- *
*/
#include <linux/module.h>
@@ -20,7 +16,8 @@
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/leds-pca9532.h>
-#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/of.h>
/* m = num_leds*/
#define PCA9532_REG_INPUT(i) ((i) >> 3)
@@ -29,6 +26,11 @@
#define PCA9532_REG_PWM(m, i) (PCA9532_REG_OFFSET(m) + 0x2 + (i) * 2)
#define LED_REG(m, led) (PCA9532_REG_OFFSET(m) + 0x5 + (led >> 2))
#define LED_NUM(led) (led & 0x3)
+#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)
@@ -46,13 +48,16 @@ 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,
- const struct i2c_device_id *id);
-static int pca9532_remove(struct i2c_client *client);
+static int pca9532_probe(struct i2c_client *client);
+static void pca9532_remove(struct i2c_client *client);
enum {
pca9530,
@@ -86,9 +91,22 @@ static const struct pca9532_chip_info pca9532_chip_info_tbl[] = {
},
};
+#ifdef CONFIG_OF
+static const struct of_device_id of_pca9532_leds_match[] = {
+ { .compatible = "nxp,pca9530", .data = (void *)pca9530 },
+ { .compatible = "nxp,pca9531", .data = (void *)pca9531 },
+ { .compatible = "nxp,pca9532", .data = (void *)pca9532 },
+ { .compatible = "nxp,pca9533", .data = (void *)pca9533 },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_pca9532_leds_match);
+#endif
+
static struct i2c_driver pca9532_driver = {
.driver = {
.name = "leds-pca953x",
+ .of_match_table = of_match_ptr(of_pca9532_leds_match),
},
.probe = pca9532_probe,
.remove = pca9532_remove,
@@ -151,14 +169,14 @@ static void pca9532_setled(struct pca9532_led *led)
mutex_lock(&data->update_lock);
reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
/* zero led bits */
- reg = reg & ~(0x3<<LED_NUM(led->id)*2);
+ reg = reg & ~LED_MASK(led->id);
/* set the new value */
- reg = reg | (led->state << LED_NUM(led->id)*2);
+ reg = reg | (led->state << LED_SHIFT(led->id));
i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
mutex_unlock(&data->update_lock);
}
-static void pca9532_set_brightness(struct led_classdev *led_cdev,
+static int pca9532_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
int err = 0;
@@ -170,11 +188,50 @@ static void 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; /* XXX: led api doesn't allow error code? */
+ return err;
+ }
+ if (led->state == PCA9532_PWM0)
+ 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;
}
- schedule_work(&led->work);
+
+ 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,
@@ -182,23 +239,24 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
{
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;
- schedule_work(&led->work);
+
+ pca9532_setled(led);
+
return 0;
}
@@ -212,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);
@@ -229,23 +287,29 @@ 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);
}
-static void pca9532_led_work(struct work_struct *work)
+static enum pca9532_state pca9532_getled(struct pca9532_led *led)
{
- struct pca9532_led *led;
- led = container_of(work, struct pca9532_led, work);
- if (led->state == PCA9532_PWM0)
- pca9532_setpwm(led->client, 0);
- pca9532_setled(led);
+ struct i2c_client *client = led->client;
+ struct pca9532_data *data = i2c_get_clientdata(client);
+ u8 maxleds = data->chip_info->num_leds;
+ char reg;
+ enum pca9532_state ret;
+
+ mutex_lock(&data->update_lock);
+ reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
+ ret = (reg & LED_MASK(led->id)) >> LED_SHIFT(led->id);
+ mutex_unlock(&data->update_lock);
+ return ret;
}
#ifdef CONFIG_LEDS_PCA9532_GPIO
static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset)
{
- struct pca9532_data *data = container_of(gc, struct pca9532_data, gpio);
+ struct pca9532_data *data = gpiochip_get_data(gc);
struct pca9532_led *led = &data->leds[offset];
if (led->type == PCA9532_TYPE_GPIO)
@@ -254,9 +318,10 @@ static int pca9532_gpio_request_pin(struct gpio_chip *gc, unsigned offset)
return -EBUSY;
}
-static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val)
+static int pca9532_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
- struct pca9532_data *data = container_of(gc, struct pca9532_data, gpio);
+ struct pca9532_data *data = gpiochip_get_data(gc);
struct pca9532_led *led = &data->leds[offset];
if (val)
@@ -265,11 +330,13 @@ static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int va
led->state = PCA9532_OFF;
pca9532_setled(led);
+
+ return 0;
}
static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
{
- struct pca9532_data *data = container_of(gc, struct pca9532_data, gpio);
+ struct pca9532_data *data = gpiochip_get_data(gc);
unsigned char reg;
reg = i2c_smbus_read_byte_data(data->client, PCA9532_REG_INPUT(offset));
@@ -287,19 +354,14 @@ static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
{
- pca9532_gpio_set_value(gc, offset, val);
-
- return 0;
+ return pca9532_gpio_set_value(gc, offset, val);
}
#endif /* CONFIG_LEDS_PCA9532_GPIO */
-static int pca9532_destroy_devices(struct pca9532_data *data, int n_devs)
+static void pca9532_destroy_devices(struct pca9532_data *data, int n_devs)
{
int i = n_devs;
- if (!data)
- return -EINVAL;
-
while (--i >= 0) {
switch (data->leds[i].type) {
case PCA9532_TYPE_NONE:
@@ -307,7 +369,6 @@ static int pca9532_destroy_devices(struct pca9532_data *data, int n_devs)
break;
case PCA9532_TYPE_LED:
led_classdev_unregister(&data->leds[i].ldev);
- cancel_work_sync(&data->leds[i].work);
break;
case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) {
@@ -319,17 +380,9 @@ static int pca9532_destroy_devices(struct pca9532_data *data, int n_devs)
}
#ifdef CONFIG_LEDS_PCA9532_GPIO
- if (data->gpio.dev) {
- int err = gpiochip_remove(&data->gpio);
- if (err) {
- dev_err(&data->client->dev, "%s failed, %d\n",
- "gpiochip_remove()", err);
- return err;
- }
- }
+ if (data->gpio.parent)
+ gpiochip_remove(&data->gpio);
#endif
-
- return 0;
}
static int pca9532_configure(struct i2c_client *client,
@@ -348,6 +401,7 @@ static int pca9532_configure(struct i2c_client *client,
data->psc[i]);
}
+ data->hw_blink = true;
for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
@@ -361,13 +415,17 @@ static int pca9532_configure(struct i2c_client *client,
gpios++;
break;
case PCA9532_TYPE_LED:
- led->state = pled->state;
+ if (pled->state == PCA9532_KEEP)
+ led->state = pca9532_getled(led);
+ else
+ led->state = pled->state;
led->name = pled->name;
led->ldev.name = led->name;
+ led->ldev.default_trigger = pled->default_trigger;
led->ldev.brightness = LED_OFF;
- led->ldev.brightness_set = pca9532_set_brightness;
+ led->ldev.brightness_set_blocking =
+ pca9532_set_brightness;
led->ldev.blink_set = pca9532_set_blink;
- INIT_WORK(&led->work, pca9532_led_work);
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
@@ -378,6 +436,8 @@ static int pca9532_configure(struct i2c_client *client,
pca9532_setled(led);
break;
case PCA9532_TYPE_N2100_BEEP:
+ /* PWM1 is reserved for beeper so blink will not use hardware */
+ data->hw_blink = false;
BUG_ON(data->idev);
led->state = PCA9532_PWM1;
pca9532_setled(led);
@@ -419,13 +479,13 @@ static int pca9532_configure(struct i2c_client *client,
data->gpio.can_sleep = 1;
data->gpio.base = pdata->gpio_base;
data->gpio.ngpio = data->chip_info->num_leds;
- data->gpio.dev = &client->dev;
+ data->gpio.parent = &client->dev;
data->gpio.owner = THIS_MODULE;
- err = gpiochip_add(&data->gpio);
+ err = gpiochip_add_data(&data->gpio, data);
if (err) {
/* Use data->gpio.dev as a flag for freeing gpiochip */
- data->gpio.dev = NULL;
+ data->gpio.parent = NULL;
dev_warn(&client->dev, "could not add gpiochip\n");
} else {
dev_info(&client->dev, "gpios %i...%i\n",
@@ -442,14 +502,71 @@ exit:
return err;
}
-static int pca9532_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static struct pca9532_platform_data *
+pca9532_of_populate_pdata(struct device *dev, struct device_node *np)
{
- struct pca9532_data *data = i2c_get_clientdata(client);
- struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data;
+ struct pca9532_platform_data *pdata;
+ int devid, maxleds;
+ int i = 0;
+ const char *state;
+
+ devid = (int)(uintptr_t)of_device_get_match_data(dev);
+ maxleds = pca9532_chip_info_tbl[devid].num_leds;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->gpio_base = -1;
+
+ 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[PCA9532_PWM_ID_0],
+ ARRAY_SIZE(pdata->psc));
+
+ 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;
+ of_property_read_u32(child, "type", &pdata->leds[i].type);
+ of_property_read_string(child, "linux,default-trigger",
+ &pdata->leds[i].default_trigger);
+ if (!of_property_read_string(child, "default-state", &state)) {
+ if (!strcmp(state, "on"))
+ pdata->leds[i].state = PCA9532_ON;
+ else if (!strcmp(state, "keep"))
+ pdata->leds[i].state = PCA9532_KEEP;
+ }
+ if (++i >= maxleds)
+ break;
+ }
- if (!pca9532_pdata)
- return -EIO;
+ return pdata;
+}
+
+static int pca9532_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ int devid;
+ struct pca9532_data *data = i2c_get_clientdata(client);
+ struct pca9532_platform_data *pca9532_pdata =
+ dev_get_platdata(&client->dev);
+ struct device_node *np = dev_of_node(&client->dev);
+
+ if (!pca9532_pdata) {
+ if (np) {
+ pca9532_pdata =
+ pca9532_of_populate_pdata(&client->dev, np);
+ if (IS_ERR(pca9532_pdata))
+ return PTR_ERR(pca9532_pdata);
+ } else {
+ dev_err(&client->dev, "no platform data\n");
+ return -EINVAL;
+ }
+ devid = (int)(uintptr_t)of_device_get_match_data(&client->dev);
+ } else {
+ devid = id->driver_data;
+ }
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
@@ -459,7 +576,7 @@ static int pca9532_probe(struct i2c_client *client,
if (!data)
return -ENOMEM;
- data->chip_info = &pca9532_chip_info_tbl[id->driver_data];
+ data->chip_info = &pca9532_chip_info_tbl[devid];
dev_info(&client->dev, "setting platform data\n");
i2c_set_clientdata(client, data);
@@ -469,16 +586,11 @@ static int pca9532_probe(struct i2c_client *client,
return pca9532_configure(client, data, pca9532_pdata);
}
-static int pca9532_remove(struct i2c_client *client)
+static void pca9532_remove(struct i2c_client *client)
{
struct pca9532_data *data = i2c_get_clientdata(client);
- int err;
- err = pca9532_destroy_devices(data, data->chip_info->num_leds);
- if (err)
- return err;
-
- return 0;
+ pca9532_destroy_devices(data, data->chip_info->num_leds);
}
module_i2c_driver(pca9532_driver);
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c
index edf485b773c8..2007fe6217ec 100644
--- a/drivers/leds/leds-pca955x.c
+++ b/drivers/leds/leds-pca955x.c
@@ -1,12 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright 2007-2008 Extreme Engineering Solutions, Inc.
*
* Author: Nate Case <ncase@xes-inc.com>
*
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
- *
* LED driver for various PCA955x I2C LED drivers
*
* Supported devices:
@@ -40,15 +37,20 @@
* bits the chip supports.
*/
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/string.h>
+#include <linux/bitops.h>
#include <linux/ctype.h>
-#include <linux/leds.h>
+#include <linux/delay.h>
#include <linux/err.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
-#include <linux/workqueue.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/property.h>
#include <linux/slab.h>
+#include <linux/string.h>
+
+#include <dt-bindings/leds/leds-pca955x.h>
/* LED select registers determine the source that drives LED outputs */
#define PCA955X_LS_LED_ON 0x0 /* Output LOW */
@@ -56,75 +58,97 @@
#define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */
#define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 rate */
+#define PCA955X_GPIO_INPUT LED_OFF
+#define PCA955X_GPIO_HIGH LED_OFF
+#define PCA955X_GPIO_LOW LED_FULL
+
+#define PCA955X_BLINK_DEFAULT_MS 1000
+
enum pca955x_type {
pca9550,
pca9551,
pca9552,
+ ibm_pca9552,
pca9553,
};
struct pca955x_chipdef {
- int bits;
+ u8 bits;
u8 slv_addr; /* 7-bit slave address mask */
int slv_addr_shift; /* Number of bits to ignore */
+ int blink_div; /* PSC divider */
};
-static struct pca955x_chipdef pca955x_chipdefs[] = {
+static const struct pca955x_chipdef pca955x_chipdefs[] = {
[pca9550] = {
.bits = 2,
.slv_addr = /* 110000x */ 0x60,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
[pca9551] = {
.bits = 8,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 38,
},
[pca9552] = {
.bits = 16,
.slv_addr = /* 1100xxx */ 0x60,
.slv_addr_shift = 3,
+ .blink_div = 44,
+ },
+ [ibm_pca9552] = {
+ .bits = 16,
+ .slv_addr = /* 0110xxx */ 0x30,
+ .slv_addr_shift = 3,
+ .blink_div = 44,
},
[pca9553] = {
.bits = 4,
.slv_addr = /* 110001x */ 0x62,
.slv_addr_shift = 1,
+ .blink_div = 44,
},
};
-static const struct i2c_device_id pca955x_id[] = {
- { "pca9550", pca9550 },
- { "pca9551", pca9551 },
- { "pca9552", pca9552 },
- { "pca9553", pca9553 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, pca955x_id);
-
struct pca955x {
struct mutex lock;
struct pca955x_led *leds;
- struct pca955x_chipdef *chipdef;
+ const struct pca955x_chipdef *chipdef;
struct i2c_client *client;
+ unsigned long active_blink;
+ unsigned long active_pins;
+ unsigned long blink_period;
+#ifdef CONFIG_LEDS_PCA955X_GPIO
+ struct gpio_chip gpio;
+#endif
};
struct pca955x_led {
struct pca955x *pca955x;
- struct work_struct work;
- enum led_brightness brightness;
struct led_classdev led_cdev;
int led_num; /* 0 .. 15 potentially */
- char name[32];
+ u32 type;
+ enum led_default_state default_state;
+ struct fwnode_handle *fwnode;
+};
+
+#define led_to_pca955x(l) container_of(l, struct pca955x_led, led_cdev)
+
+struct pca955x_platform_data {
+ struct pca955x_led *leds;
+ int num_leds;
};
/* 8 bits per input register */
-static inline int pca95xx_num_input_regs(int bits)
+static inline u8 pca955x_num_input_regs(u8 bits)
{
return (bits + 7) / 8;
}
/* 4 bits per LED selector register */
-static inline int pca95xx_num_led_regs(int bits)
+static inline u8 pca955x_num_led_regs(u8 bits)
{
return (bits + 3) / 4;
}
@@ -139,17 +163,26 @@ 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 void 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);
-
- i2c_smbus_write_byte_data(client,
- pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n,
- val);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + (2 * n);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
+ if (ret < 0)
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
+ return ret;
}
/*
@@ -159,146 +192,443 @@ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val)
*
* Duty cycle is (256 - PWMx) / 256
*/
-static void 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);
-
- i2c_smbus_write_byte_data(client,
- pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n,
- val);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
+ if (ret < 0)
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
+ return ret;
}
/*
* Write to LED selector register, which determines the source that
* drives the LED output.
*/
-static void 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);
-
- i2c_smbus_write_byte_data(client,
- pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n,
- val);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(pca955x->client, cmd, val);
+ if (ret < 0)
+ dev_err(&pca955x->client->dev, "%s: reg 0x%x, val 0x%x, err %d\n", __func__, n,
+ val, ret);
+ return ret;
}
/*
* Read the LED selector register, which determines the source that
* drives the LED output.
*/
-static u8 pca955x_read_ls(struct i2c_client *client, int n)
+static int pca955x_read_ls(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x *pca955x = i2c_get_clientdata(client);
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 4 + n;
+ int ret;
- return (u8) i2c_smbus_read_byte_data(client,
- pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + 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;
+ return 0;
}
-static void pca955x_led_work(struct work_struct *work)
+static int pca955x_read_pwm(struct pca955x *pca955x, int n, u8 *val)
{
- struct pca955x_led *pca955x_led;
- struct pca955x *pca955x;
+ u8 cmd = pca955x_num_input_regs(pca955x->chipdef->bits) + 1 + (2 * n);
+ int ret;
+
+ 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;
+ 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;
+ return 0;
+}
+
+static enum led_brightness pca955x_led_get(struct led_classdev *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, pca955x_led->led_num / 4, &ls);
+ if (ret)
+ return ret;
+
+ 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_BLINK1:
+ ret = pca955x_read_pwm(pca955x, 1, &pwm);
+ if (ret)
+ return ret;
+ ret = 255 - pwm;
+ break;
+ }
+
+ return ret;
+}
+
+static int pca955x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ 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;
+
+ mutex_lock(&pca955x->lock);
+
+ ret = pca955x_read_ls(pca955x, reg, &ls);
+ if (ret)
+ goto out;
+
+ 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;
+ }
+ } else {
+ switch (value) {
+ case LED_FULL:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_ON);
+ break;
+ case LED_OFF:
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_LED_OFF);
+ break;
+ default:
+ /*
+ * Use PWM1 for all other values. This has the unwanted
+ * side effect of making all LEDs on the chip share the
+ * same brightness level if set to a value other than
+ * OFF or FULL. But, this is probably better than just
+ * turning off for all other values.
+ */
+ ret = pca955x_write_pwm(pca955x, 1, 255 - value);
+ if (ret)
+ goto out;
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK1);
+ break;
+ }
+ }
+
+ ret = pca955x_write_ls(pca955x, reg, ls);
+
+out:
+ mutex_unlock(&pca955x->lock);
- pca955x_led = container_of(work, struct pca955x_led, work);
- pca955x = pca955x_led->pca955x;
+ return ret;
+}
- chip_ls = pca955x_led->led_num / 4;
- ls_led = pca955x_led->led_num % 4;
+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);
- ls = pca955x_read_ls(pca955x->client, chip_ls);
+ if (period) {
+ if (*delay_on != *delay_off) {
+ ret = -EINVAL;
+ goto out;
+ }
- switch (pca955x_led->brightness) {
- 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.
- */
- pca955x_write_pwm(pca955x->client, 1,
- 255 - pca955x_led->brightness);
- ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
- break;
+ 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;
}
- pca955x_write_ls(pca955x->client, chip_ls, ls);
+ if (!pca955x->active_blink ||
+ pca955x->active_blink == BIT(pca955x_led->led_num) ||
+ pca955x->blink_period == period) {
+ u8 psc = pca955x_period_to_psc(pca955x, period);
+
+ if (!test_and_set_bit(pca955x_led->led_num,
+ &pca955x->active_blink)) {
+ u8 ls;
+ int reg = pca955x_led->led_num / 4;
+ int bit = pca955x_led->led_num % 4;
+
+ ret = pca955x_read_ls(pca955x, reg, &ls);
+ if (ret)
+ goto out;
+
+ ls = pca955x_ledsel(ls, bit, PCA955X_LS_BLINK0);
+ ret = pca955x_write_ls(pca955x, reg, ls);
+ if (ret)
+ goto out;
+
+ /*
+ * Force 50% duty cycle to maintain the specified
+ * blink rate.
+ */
+ ret = pca955x_write_pwm(pca955x, 0, 128);
+ if (ret)
+ goto out;
+ }
+
+ if (pca955x->blink_period != period) {
+ pca955x->blink_period = period;
+ ret = pca955x_write_psc(pca955x, 0, psc);
+ if (ret)
+ goto out;
+ }
+
+ period = pca955x_psc_to_period(pca955x, psc);
+ period /= 2;
+ *delay_on = period;
+ *delay_off = period;
+ } else {
+ ret = -EBUSY;
+ }
+out:
mutex_unlock(&pca955x->lock);
+
+ return ret;
+}
+
+#ifdef CONFIG_LEDS_PCA955X_GPIO
+/*
+ * Read the INPUT register, which contains the state of LEDs.
+ */
+static int pca955x_read_input(struct i2c_client *client, int n, u8 *val)
+{
+ int ret = i2c_smbus_read_byte_data(client, n);
+
+ if (ret < 0) {
+ dev_err(&client->dev, "%s: reg 0x%x, err %d\n",
+ __func__, n, ret);
+ return ret;
+ }
+ *val = (u8)ret;
+ return 0;
+
+}
+
+static int pca955x_gpio_request_pin(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pca955x *pca955x = gpiochip_get_data(gc);
+
+ return test_and_set_bit(offset, &pca955x->active_pins) ? -EBUSY : 0;
+}
+
+static void pca955x_gpio_free_pin(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pca955x *pca955x = gpiochip_get_data(gc);
+
+ clear_bit(offset, &pca955x->active_pins);
}
-static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value)
+static int pca955x_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
- struct pca955x_led *pca955x;
+ struct pca955x *pca955x = gpiochip_get_data(gc);
+ struct pca955x_led *led = &pca955x->leds[offset];
- pca955x = container_of(led_cdev, struct pca955x_led, led_cdev);
+ if (val)
+ return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_HIGH);
- pca955x->brightness = value;
+ return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_LOW);
+}
- /*
- * Must use workqueue for the actual I/O since I2C operations
- * can sleep.
- */
- schedule_work(&pca955x->work);
+static int pca955x_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
+{
+ return pca955x_set_value(gc, offset, val);
}
-static int pca955x_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
+static int pca955x_gpio_get_value(struct gpio_chip *gc, unsigned int offset)
+{
+ struct pca955x *pca955x = gpiochip_get_data(gc);
+ struct pca955x_led *led = &pca955x->leds[offset];
+ u8 reg = 0;
+
+ /* There is nothing we can do about errors */
+ pca955x_read_input(pca955x->client, led->led_num / 8, &reg);
+
+ return !!(reg & (1 << (led->led_num % 8)));
+}
+
+static int pca955x_gpio_direction_input(struct gpio_chip *gc,
+ unsigned int offset)
+{
+ struct pca955x *pca955x = gpiochip_get_data(gc);
+ struct pca955x_led *led = &pca955x->leds[offset];
+
+ /* To use as input ensure pin is not driven. */
+ return pca955x_led_set(&led->led_cdev, PCA955X_GPIO_INPUT);
+}
+
+static int pca955x_gpio_direction_output(struct gpio_chip *gc,
+ unsigned int offset, int val)
+{
+ return pca955x_set_value(gc, offset, val);
+}
+#endif /* CONFIG_LEDS_PCA955X_GPIO */
+
+static struct pca955x_platform_data *
+pca955x_get_pdata(struct i2c_client *client, const struct pca955x_chipdef *chip)
+{
+ struct pca955x_platform_data *pdata;
+ struct pca955x_led *led;
+ struct fwnode_handle *child;
+ int count;
+
+ count = device_get_child_node_count(&client->dev);
+ if (count > chip->bits)
+ return ERR_PTR(-ENODEV);
+
+ pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->leds = devm_kcalloc(&client->dev,
+ chip->bits, sizeof(struct pca955x_led),
+ GFP_KERNEL);
+ if (!pdata->leds)
+ return ERR_PTR(-ENOMEM);
+
+ device_for_each_child_node(&client->dev, child) {
+ u32 reg;
+ int res;
+
+ res = fwnode_property_read_u32(child, "reg", &reg);
+ if ((res != 0) || (reg >= chip->bits))
+ continue;
+
+ led = &pdata->leds[reg];
+ led->type = PCA955X_TYPE_LED;
+ led->fwnode = child;
+ led->default_state = led_init_default_state_get(child);
+
+ fwnode_property_read_u32(child, "type", &led->type);
+ }
+
+ pdata->num_leds = chip->bits;
+
+ return pdata;
+}
+
+static int pca955x_probe(struct i2c_client *client)
{
struct pca955x *pca955x;
struct pca955x_led *pca955x_led;
- struct pca955x_chipdef *chip;
+ const struct pca955x_chipdef *chip;
+ struct led_classdev *led;
+ struct led_init_data init_data;
struct i2c_adapter *adapter;
- struct led_platform_data *pdata;
- int i, err;
-
- chip = &pca955x_chipdefs[id->driver_data];
- adapter = to_i2c_adapter(client->dev.parent);
- pdata = client->dev.platform_data;
+ u8 i, nls, psc0;
+ u8 ls1[4];
+ u8 ls2[4];
+ struct pca955x_platform_data *pdata;
+ bool keep_psc0 = false;
+ bool set_default_label = false;
+ char default_label[4];
+ int bit, err, reg;
+
+ chip = i2c_get_match_data(client);
+ if (!chip)
+ return dev_err_probe(&client->dev, -ENODEV, "unknown chip\n");
+
+ adapter = client->adapter;
+ pdata = dev_get_platdata(&client->dev);
+ if (!pdata) {
+ pdata = pca955x_get_pdata(client, chip);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ }
/* Make sure the slave address / chip type combo given is possible */
if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
chip->slv_addr) {
dev_err(&client->dev, "invalid slave address %02x\n",
- client->addr);
+ client->addr);
return -ENODEV;
}
- dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
- "slave address 0x%02x\n",
- id->name, chip->bits, client->addr);
+ dev_info(&client->dev, "Using %s %u-bit LED driver at slave address 0x%02x\n",
+ client->name, chip->bits, client->addr);
- if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
- if (pdata) {
- if (pdata->num_leds != chip->bits) {
- dev_err(&client->dev, "board info claims %d LEDs"
- " on a %d-bit chip\n",
- pdata->num_leds, chip->bits);
- return -ENODEV;
- }
+ if (pdata->num_leds != chip->bits) {
+ dev_err(&client->dev,
+ "board info claims %d LEDs on a %u-bit chip\n",
+ pdata->num_leds, chip->bits);
+ return -ENODEV;
}
pca955x = devm_kzalloc(&client->dev, sizeof(*pca955x), GFP_KERNEL);
if (!pca955x)
return -ENOMEM;
- pca955x->leds = devm_kzalloc(&client->dev,
- sizeof(*pca955x_led) * chip->bits, GFP_KERNEL);
+ pca955x->leds = devm_kcalloc(&client->dev, chip->bits,
+ sizeof(*pca955x_led), GFP_KERNEL);
if (!pca955x->leds)
return -ENOMEM;
@@ -307,82 +637,158 @@ 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;
pca955x_led->pca955x = pca955x;
-
- /* Platform data can specify LED names and default triggers */
- if (pdata) {
- if (pdata->leds[i].name)
- snprintf(pca955x_led->name,
- sizeof(pca955x_led->name), "pca955x:%s",
- pdata->leds[i].name);
- if (pdata->leds[i].default_trigger)
- pca955x_led->led_cdev.default_trigger =
- pdata->leds[i].default_trigger;
- } else {
- snprintf(pca955x_led->name, sizeof(pca955x_led->name),
- "pca955x:%d", i);
+ pca955x_led->type = pdata->leds[i].type;
+
+ switch (pca955x_led->type) {
+ case PCA955X_TYPE_NONE:
+ 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;
+ 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;
+
+ if (is_of_node(init_data.fwnode)) {
+ if (to_of_node(init_data.fwnode)->name[0] ==
+ '\0')
+ set_default_label = true;
+ else
+ set_default_label = false;
+ } else {
+ set_default_label = true;
+ }
+
+ if (set_default_label) {
+ snprintf(default_label, sizeof(default_label), "%hhu", i);
+ init_data.default_label = default_label;
+ } else {
+ init_data.default_label = NULL;
+ }
+
+ err = devm_led_classdev_register_ext(&client->dev, led,
+ &init_data);
+ if (err)
+ return err;
+
+ set_bit(i, &pca955x->active_pins);
}
-
- pca955x_led->led_cdev.name = pca955x_led->name;
- pca955x_led->led_cdev.brightness_set = pca955x_led_set;
-
- INIT_WORK(&pca955x_led->work, pca955x_led_work);
-
- err = led_classdev_register(&client->dev,
- &pca955x_led->led_cdev);
- if (err < 0)
- goto exit;
}
- /* Turn off LEDs */
- for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++)
- pca955x_write_ls(client, i, 0x55);
-
- /* PWM0 is used for half brightness or 50% duty cycle */
- pca955x_write_pwm(client, 0, 255-LED_HALF);
-
- /* PWM1 is used for variable brightness, default to OFF */
- pca955x_write_pwm(client, 1, 0);
-
- /* Set to fast frequency so we do not see flashing */
- pca955x_write_psc(client, 0, 0);
- pca955x_write_psc(client, 1, 0);
-
- return 0;
-
-exit:
- while (i--) {
- led_classdev_unregister(&pca955x->leds[i].led_cdev);
- cancel_work_sync(&pca955x->leds[i].work);
+ for (i = 0; i < nls; i++) {
+ if (ls1[i] != ls2[i]) {
+ err = pca955x_write_ls(pca955x, i, ls2[i]);
+ if (err)
+ return err;
+ }
}
- return err;
-}
-
-static int pca955x_remove(struct i2c_client *client)
-{
- struct pca955x *pca955x = i2c_get_clientdata(client);
- int i;
+ 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);
+ }
- for (i = 0; i < pca955x->chipdef->bits; i++) {
- led_classdev_unregister(&pca955x->leds[i].led_cdev);
- cancel_work_sync(&pca955x->leds[i].work);
+ if (err)
+ return err;
+
+ 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;
+
+#ifdef CONFIG_LEDS_PCA955X_GPIO
+ pca955x->gpio.label = "gpio-pca955x";
+ pca955x->gpio.direction_input = pca955x_gpio_direction_input;
+ pca955x->gpio.direction_output = pca955x_gpio_direction_output;
+ pca955x->gpio.set = pca955x_gpio_set_value;
+ pca955x->gpio.get = pca955x_gpio_get_value;
+ pca955x->gpio.request = pca955x_gpio_request_pin;
+ pca955x->gpio.free = pca955x_gpio_free_pin;
+ pca955x->gpio.can_sleep = 1;
+ pca955x->gpio.base = -1;
+ pca955x->gpio.ngpio = chip->bits;
+ pca955x->gpio.parent = &client->dev;
+ pca955x->gpio.owner = THIS_MODULE;
+
+ err = devm_gpiochip_add_data(&client->dev, &pca955x->gpio,
+ pca955x);
+ if (err) {
+ /* Use data->gpio.dev as a flag for freeing gpiochip */
+ pca955x->gpio.parent = NULL;
+ dev_warn(&client->dev, "could not add gpiochip\n");
+ return err;
}
+ dev_info(&client->dev, "gpios %i...%i\n",
+ pca955x->gpio.base, pca955x->gpio.base +
+ pca955x->gpio.ngpio - 1);
+#endif
return 0;
}
+static const struct i2c_device_id pca955x_id[] = {
+ { "pca9550", (kernel_ulong_t)&pca955x_chipdefs[pca9550] },
+ { "pca9551", (kernel_ulong_t)&pca955x_chipdefs[pca9551] },
+ { "pca9552", (kernel_ulong_t)&pca955x_chipdefs[pca9552] },
+ { "ibm-pca9552", (kernel_ulong_t)&pca955x_chipdefs[ibm_pca9552] },
+ { "pca9553", (kernel_ulong_t)&pca955x_chipdefs[pca9553] },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pca955x_id);
+
+static const struct of_device_id of_pca955x_match[] = {
+ { .compatible = "nxp,pca9550", .data = &pca955x_chipdefs[pca9550] },
+ { .compatible = "nxp,pca9551", .data = &pca955x_chipdefs[pca9551] },
+ { .compatible = "nxp,pca9552", .data = &pca955x_chipdefs[pca9552] },
+ { .compatible = "ibm,pca9552", .data = &pca955x_chipdefs[ibm_pca9552] },
+ { .compatible = "nxp,pca9553", .data = &pca955x_chipdefs[pca9553] },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_pca955x_match);
+
static struct i2c_driver pca955x_driver = {
.driver = {
.name = "leds-pca955x",
- .owner = THIS_MODULE,
+ .of_match_table = of_pca955x_match,
},
- .probe = pca955x_probe,
- .remove = pca955x_remove,
+ .probe = pca955x_probe,
.id_table = pca955x_id,
};
diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c
deleted file mode 100644
index 9aae5679ffb2..000000000000
--- a/drivers/leds/leds-pca9633.c
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright 2011 bct electronic GmbH
- *
- * Author: Peter Meerwald <p.meerwald@bct-electronic.com>
- *
- * Based on leds-pca955x.c
- *
- * This file is subject to the terms and conditions of version 2 of
- * the GNU General Public License. See the file COPYING in the main
- * directory of this archive for more details.
- *
- * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62)
- *
- */
-
-#include <linux/module.h>
-#include <linux/delay.h>
-#include <linux/string.h>
-#include <linux/ctype.h>
-#include <linux/leds.h>
-#include <linux/err.h>
-#include <linux/i2c.h>
-#include <linux/workqueue.h>
-#include <linux/slab.h>
-#include <linux/platform_data/leds-pca9633.h>
-
-/* LED select registers determine the source that drives LED outputs */
-#define PCA9633_LED_OFF 0x0 /* LED driver off */
-#define PCA9633_LED_ON 0x1 /* LED driver on */
-#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */
-#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */
-
-#define PCA9633_MODE1 0x00
-#define PCA9633_MODE2 0x01
-#define PCA9633_PWM_BASE 0x02
-#define PCA9633_LEDOUT 0x08
-
-static const struct i2c_device_id pca9633_id[] = {
- { "pca9633", 0 },
- { }
-};
-MODULE_DEVICE_TABLE(i2c, pca9633_id);
-
-struct pca9633_led {
- struct i2c_client *client;
- struct work_struct work;
- enum led_brightness brightness;
- struct led_classdev led_cdev;
- int led_num; /* 0 .. 3 potentially */
- char name[32];
-};
-
-static void pca9633_led_work(struct work_struct *work)
-{
- struct pca9633_led *pca9633 = container_of(work,
- struct pca9633_led, work);
- u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT);
- int shift = 2 * pca9633->led_num;
- u8 mask = 0x3 << shift;
-
- switch (pca9633->brightness) {
- case LED_FULL:
- i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT,
- (ledout & ~mask) | (PCA9633_LED_ON << shift));
- break;
- case LED_OFF:
- i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT,
- ledout & ~mask);
- break;
- default:
- i2c_smbus_write_byte_data(pca9633->client,
- PCA9633_PWM_BASE + pca9633->led_num,
- pca9633->brightness);
- i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT,
- (ledout & ~mask) | (PCA9633_LED_PWM << shift));
- break;
- }
-}
-
-static void pca9633_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct pca9633_led *pca9633;
-
- pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev);
-
- pca9633->brightness = value;
-
- /*
- * Must use workqueue for the actual I/O since I2C operations
- * can sleep.
- */
- schedule_work(&pca9633->work);
-}
-
-static int pca9633_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- struct pca9633_led *pca9633;
- struct pca9633_platform_data *pdata;
- int i, err;
-
- pdata = client->dev.platform_data;
-
- if (pdata) {
- if (pdata->leds.num_leds <= 0 || pdata->leds.num_leds > 4) {
- dev_err(&client->dev, "board info must claim at most 4 LEDs");
- return -EINVAL;
- }
- }
-
- pca9633 = devm_kzalloc(&client->dev, 4 * sizeof(*pca9633), GFP_KERNEL);
- if (!pca9633)
- return -ENOMEM;
-
- i2c_set_clientdata(client, pca9633);
-
- for (i = 0; i < 4; i++) {
- pca9633[i].client = client;
- pca9633[i].led_num = i;
-
- /* Platform data can specify LED names and default triggers */
- if (pdata && i < pdata->leds.num_leds) {
- if (pdata->leds.leds[i].name)
- snprintf(pca9633[i].name,
- sizeof(pca9633[i].name), "pca9633:%s",
- pdata->leds.leds[i].name);
- if (pdata->leds.leds[i].default_trigger)
- pca9633[i].led_cdev.default_trigger =
- pdata->leds.leds[i].default_trigger;
- } else {
- snprintf(pca9633[i].name, sizeof(pca9633[i].name),
- "pca9633:%d", i);
- }
-
- pca9633[i].led_cdev.name = pca9633[i].name;
- pca9633[i].led_cdev.brightness_set = pca9633_led_set;
-
- INIT_WORK(&pca9633[i].work, pca9633_led_work);
-
- err = led_classdev_register(&client->dev, &pca9633[i].led_cdev);
- if (err < 0)
- goto exit;
- }
-
- /* Disable LED all-call address and set normal mode */
- i2c_smbus_write_byte_data(client, PCA9633_MODE1, 0x00);
-
- /* Configure output: open-drain or totem pole (push-pull) */
- if (pdata && pdata->outdrv == PCA9633_OPEN_DRAIN)
- i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01);
-
- /* Turn off LEDs */
- i2c_smbus_write_byte_data(client, PCA9633_LEDOUT, 0x00);
-
- return 0;
-
-exit:
- while (i--) {
- led_classdev_unregister(&pca9633[i].led_cdev);
- cancel_work_sync(&pca9633[i].work);
- }
-
- return err;
-}
-
-static int pca9633_remove(struct i2c_client *client)
-{
- struct pca9633_led *pca9633 = i2c_get_clientdata(client);
- int i;
-
- for (i = 0; i < 4; i++) {
- led_classdev_unregister(&pca9633[i].led_cdev);
- cancel_work_sync(&pca9633[i].work);
- }
-
- return 0;
-}
-
-static struct i2c_driver pca9633_driver = {
- .driver = {
- .name = "leds-pca9633",
- .owner = THIS_MODULE,
- },
- .probe = pca9633_probe,
- .remove = pca9633_remove,
- .id_table = pca9633_id,
-};
-
-module_i2c_driver(pca9633_driver);
-
-MODULE_AUTHOR("Peter Meerwald <p.meerwald@bct-electronic.com>");
-MODULE_DESCRIPTION("PCA9633 LED driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c
new file mode 100644
index 000000000000..050e93b04884
--- /dev/null
+++ b/drivers/leds/leds-pca963x.c
@@ -0,0 +1,465 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2011 bct electronic GmbH
+ * Copyright 2013 Qtechnology/AS
+ *
+ * Author: Peter Meerwald <p.meerwald@bct-electronic.com>
+ * Author: Ricardo Ribalda <ribalda@kernel.org>
+ *
+ * Based on leds-pca955x.c
+ *
+ * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62)
+ * LED driver for the PCA9634/5 I2C LED driver (7-bit slave address set by hw.)
+ *
+ * Note that hardware blinking violates the leds infrastructure driver
+ * interface since the hardware only supports blinking all LEDs with the
+ * same delay_on/delay_off rates. That is, only the LEDs that are set to
+ * blink will actually blink but all LEDs that are set to blink will blink
+ * in identical fashion. The delay_on/delay_off values of the last LED
+ * that is set to blink will be used for all of the blinking LEDs.
+ * Hardware blinking is disabled by default but can be enabled by setting
+ * the 'blink_type' member in the platform_data struct to 'PCA963X_HW_BLINK'
+ * or by adding the 'nxp,hw-blink' property to the DTS.
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/string.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+/* LED select registers determine the source that drives LED outputs */
+#define PCA963X_LED_OFF 0x0 /* LED driver off */
+#define PCA963X_LED_ON 0x1 /* LED driver on */
+#define PCA963X_LED_PWM 0x2 /* Controlled through PWM */
+#define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */
+
+#define PCA963X_MODE1_SLEEP 0x04 /* Normal mode or Low Power mode, oscillator off */
+#define PCA963X_MODE2_OUTDRV 0x04 /* Open-drain or totem pole */
+#define PCA963X_MODE2_INVRT 0x10 /* Normal or inverted direction */
+#define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */
+
+#define PCA963X_MODE1 0x00
+#define PCA963X_MODE2 0x01
+#define PCA963X_PWM_BASE 0x02
+
+enum pca963x_type {
+ pca9633,
+ pca9634,
+ pca9635,
+};
+
+struct pca963x_chipdef {
+ u8 grppwm;
+ u8 grpfreq;
+ u8 ledout_base;
+ int n_leds;
+ unsigned int scaling;
+};
+
+static struct pca963x_chipdef pca963x_chipdefs[] = {
+ [pca9633] = {
+ .grppwm = 0x6,
+ .grpfreq = 0x7,
+ .ledout_base = 0x8,
+ .n_leds = 4,
+ },
+ [pca9634] = {
+ .grppwm = 0xa,
+ .grpfreq = 0xb,
+ .ledout_base = 0xc,
+ .n_leds = 8,
+ },
+ [pca9635] = {
+ .grppwm = 0x12,
+ .grpfreq = 0x13,
+ .ledout_base = 0x14,
+ .n_leds = 16,
+ },
+};
+
+/* Total blink period in milliseconds */
+#define PCA963X_BLINK_PERIOD_MIN 42
+#define PCA963X_BLINK_PERIOD_MAX 10667
+
+static const struct i2c_device_id pca963x_id[] = {
+ { "pca9632", pca9633 },
+ { "pca9633", pca9633 },
+ { "pca9634", pca9634 },
+ { "pca9635", pca9635 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pca963x_id);
+
+struct pca963x;
+
+struct pca963x_led {
+ struct pca963x *chip;
+ struct led_classdev led_cdev;
+ int led_num; /* 0 .. 15 potentially */
+ bool blinking;
+ u8 gdc;
+ u8 gfrq;
+};
+
+struct pca963x {
+ struct pca963x_chipdef *chipdef;
+ struct mutex mutex;
+ struct i2c_client *client;
+ unsigned long leds_on;
+ struct pca963x_led leds[];
+};
+
+static int pca963x_brightness(struct pca963x_led *led,
+ enum led_brightness brightness)
+{
+ struct i2c_client *client = led->chip->client;
+ struct pca963x_chipdef *chipdef = led->chip->chipdef;
+ u8 ledout_addr, ledout, mask, val;
+ int shift;
+ int ret;
+
+ ledout_addr = chipdef->ledout_base + (led->led_num / 4);
+ shift = 2 * (led->led_num % 4);
+ mask = 0x3 << shift;
+ ledout = i2c_smbus_read_byte_data(client, ledout_addr);
+
+ switch (brightness) {
+ case LED_FULL:
+ if (led->blinking) {
+ val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift);
+ ret = i2c_smbus_write_byte_data(client,
+ PCA963X_PWM_BASE +
+ led->led_num,
+ LED_FULL);
+ } else {
+ val = (ledout & ~mask) | (PCA963X_LED_ON << shift);
+ }
+ ret = i2c_smbus_write_byte_data(client, ledout_addr, val);
+ break;
+ case LED_OFF:
+ val = ledout & ~mask;
+ ret = i2c_smbus_write_byte_data(client, ledout_addr, val);
+ led->blinking = false;
+ break;
+ default:
+ ret = i2c_smbus_write_byte_data(client,
+ PCA963X_PWM_BASE +
+ led->led_num,
+ brightness);
+ if (ret < 0)
+ return ret;
+
+ if (led->blinking)
+ val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift);
+ else
+ val = (ledout & ~mask) | (PCA963X_LED_PWM << shift);
+
+ ret = i2c_smbus_write_byte_data(client, ledout_addr, val);
+ break;
+ }
+
+ return ret;
+}
+
+static void pca963x_blink(struct pca963x_led *led)
+{
+ struct i2c_client *client = led->chip->client;
+ struct pca963x_chipdef *chipdef = led->chip->chipdef;
+ u8 ledout_addr, ledout, mask, val, mode2;
+ int shift;
+
+ ledout_addr = chipdef->ledout_base + (led->led_num / 4);
+ shift = 2 * (led->led_num % 4);
+ mask = 0x3 << shift;
+ mode2 = i2c_smbus_read_byte_data(client, PCA963X_MODE2);
+
+ i2c_smbus_write_byte_data(client, chipdef->grppwm, led->gdc);
+
+ i2c_smbus_write_byte_data(client, chipdef->grpfreq, led->gfrq);
+
+ if (!(mode2 & PCA963X_MODE2_DMBLNK))
+ i2c_smbus_write_byte_data(client, PCA963X_MODE2,
+ mode2 | PCA963X_MODE2_DMBLNK);
+
+ mutex_lock(&led->chip->mutex);
+
+ ledout = i2c_smbus_read_byte_data(client, ledout_addr);
+ if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift)) {
+ val = (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift);
+ i2c_smbus_write_byte_data(client, ledout_addr, val);
+ }
+
+ mutex_unlock(&led->chip->mutex);
+ led->blinking = true;
+}
+
+static int pca963x_power_state(struct pca963x_led *led)
+{
+ struct i2c_client *client = led->chip->client;
+ unsigned long *leds_on = &led->chip->leds_on;
+ unsigned long cached_leds = *leds_on;
+
+ if (led->led_cdev.brightness)
+ set_bit(led->led_num, leds_on);
+ else
+ clear_bit(led->led_num, leds_on);
+
+ if (!(*leds_on) != !cached_leds)
+ return i2c_smbus_write_byte_data(client, PCA963X_MODE1,
+ *leds_on ? 0 : BIT(4));
+
+ return 0;
+}
+
+static int pca963x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct pca963x_led *led;
+ int ret;
+
+ led = container_of(led_cdev, struct pca963x_led, led_cdev);
+
+ mutex_lock(&led->chip->mutex);
+
+ ret = pca963x_brightness(led, value);
+ if (ret < 0)
+ goto unlock;
+ ret = pca963x_power_state(led);
+
+unlock:
+ mutex_unlock(&led->chip->mutex);
+ return ret;
+}
+
+static unsigned int pca963x_period_scale(struct pca963x_led *led,
+ unsigned int val)
+{
+ unsigned int scaling = led->chip->chipdef->scaling;
+
+ return scaling ? DIV_ROUND_CLOSEST(val * scaling, 1000) : val;
+}
+
+static int pca963x_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ unsigned long time_on, time_off, period;
+ struct pca963x_led *led;
+ u8 gdc, gfrq;
+
+ led = container_of(led_cdev, struct pca963x_led, led_cdev);
+
+ time_on = *delay_on;
+ time_off = *delay_off;
+
+ /* If both zero, pick reasonable defaults of 500ms each */
+ if (!time_on && !time_off) {
+ time_on = 500;
+ time_off = 500;
+ }
+
+ period = pca963x_period_scale(led, time_on + time_off);
+
+ /* If period not supported by hardware, default to someting sane. */
+ if ((period < PCA963X_BLINK_PERIOD_MIN) ||
+ (period > PCA963X_BLINK_PERIOD_MAX)) {
+ time_on = 500;
+ time_off = 500;
+ period = pca963x_period_scale(led, 1000);
+ }
+
+ /*
+ * From manual: duty cycle = (GDC / 256) ->
+ * (time_on / period) = (GDC / 256) ->
+ * GDC = ((time_on * 256) / period)
+ */
+ gdc = (pca963x_period_scale(led, time_on) * 256) / period;
+
+ /*
+ * From manual: period = ((GFRQ + 1) / 24) in seconds.
+ * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) ->
+ * GFRQ = ((period * 24 / 1000) - 1)
+ */
+ gfrq = (period * 24 / 1000) - 1;
+
+ led->gdc = gdc;
+ led->gfrq = gfrq;
+
+ pca963x_blink(led);
+ led->led_cdev.brightness = LED_FULL;
+ pca963x_led_set(led_cdev, LED_FULL);
+
+ *delay_on = time_on;
+ *delay_off = time_off;
+
+ return 0;
+}
+
+static int pca963x_register_leds(struct i2c_client *client,
+ struct pca963x *chip)
+{
+ struct pca963x_chipdef *chipdef = chip->chipdef;
+ struct pca963x_led *led = chip->leds;
+ struct device *dev = &client->dev;
+ bool hw_blink;
+ s32 mode2;
+ u32 reg;
+ int ret;
+
+ if (device_property_read_u32(dev, "nxp,period-scale",
+ &chipdef->scaling))
+ chipdef->scaling = 1000;
+
+ hw_blink = device_property_read_bool(dev, "nxp,hw-blink");
+
+ mode2 = i2c_smbus_read_byte_data(client, PCA963X_MODE2);
+ if (mode2 < 0)
+ return mode2;
+
+ /* default to open-drain unless totem pole (push-pull) is specified */
+ if (device_property_read_bool(dev, "nxp,totem-pole"))
+ mode2 |= PCA963X_MODE2_OUTDRV;
+ else
+ mode2 &= ~PCA963X_MODE2_OUTDRV;
+
+ /* default to non-inverted output, unless inverted is specified */
+ if (device_property_read_bool(dev, "nxp,inverted-out"))
+ mode2 |= PCA963X_MODE2_INVRT;
+ else
+ mode2 &= ~PCA963X_MODE2_INVRT;
+
+ ret = i2c_smbus_write_byte_data(client, PCA963X_MODE2, mode2);
+ if (ret < 0)
+ return ret;
+
+ device_for_each_child_node_scoped(dev, child) {
+ struct led_init_data init_data = {};
+ char default_label[32];
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg >= chipdef->n_leds) {
+ dev_err(dev, "Invalid 'reg' property for node %pfw\n",
+ child);
+ return -EINVAL;
+ }
+
+ led->led_num = reg;
+ led->chip = chip;
+ led->led_cdev.brightness_set_blocking = pca963x_led_set;
+ if (hw_blink)
+ led->led_cdev.blink_set = pca963x_blink_set;
+ led->blinking = false;
+
+ init_data.fwnode = child;
+ /* for backwards compatibility */
+ init_data.devicename = "pca963x";
+ snprintf(default_label, sizeof(default_label), "%d:%.2x:%u",
+ client->adapter->nr, client->addr, reg);
+ init_data.default_label = default_label;
+
+ ret = devm_led_classdev_register_ext(dev, &led->led_cdev,
+ &init_data);
+ if (ret) {
+ dev_err(dev, "Failed to register LED for node %pfw\n",
+ child);
+ return ret;
+ }
+
+ ++led;
+ }
+
+ return 0;
+}
+
+static int pca963x_suspend(struct device *dev)
+{
+ struct pca963x *chip = dev_get_drvdata(dev);
+ u8 reg;
+
+ reg = i2c_smbus_read_byte_data(chip->client, PCA963X_MODE1);
+ reg = reg | BIT(PCA963X_MODE1_SLEEP);
+ i2c_smbus_write_byte_data(chip->client, PCA963X_MODE1, reg);
+
+ return 0;
+}
+
+static int pca963x_resume(struct device *dev)
+{
+ struct pca963x *chip = dev_get_drvdata(dev);
+ u8 reg;
+
+ reg = i2c_smbus_read_byte_data(chip->client, PCA963X_MODE1);
+ reg = reg & ~BIT(PCA963X_MODE1_SLEEP);
+ i2c_smbus_write_byte_data(chip->client, PCA963X_MODE1, reg);
+
+ return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(pca963x_pm, pca963x_suspend, pca963x_resume);
+
+static const struct of_device_id of_pca963x_match[] = {
+ { .compatible = "nxp,pca9632", },
+ { .compatible = "nxp,pca9633", },
+ { .compatible = "nxp,pca9634", },
+ { .compatible = "nxp,pca9635", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_pca963x_match);
+
+static int pca963x_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ struct device *dev = &client->dev;
+ struct pca963x_chipdef *chipdef;
+ struct pca963x *chip;
+ int i, count;
+
+ chipdef = &pca963x_chipdefs[id->driver_data];
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > chipdef->n_leds) {
+ dev_err(dev, "Node %pfw must define between 1 and %d LEDs\n",
+ dev_fwnode(dev), chipdef->n_leds);
+ return -EINVAL;
+ }
+
+ chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, chip);
+
+ mutex_init(&chip->mutex);
+ chip->chipdef = chipdef;
+ chip->client = client;
+
+ /* Turn off LEDs by default*/
+ for (i = 0; i < chipdef->n_leds / 4; i++)
+ i2c_smbus_write_byte_data(client, chipdef->ledout_base + i, 0x00);
+
+ /* Disable LED all-call address, and power down initially */
+ i2c_smbus_write_byte_data(client, PCA963X_MODE1, BIT(4));
+
+ return pca963x_register_leds(client, chip);
+}
+
+static struct i2c_driver pca963x_driver = {
+ .driver = {
+ .name = "leds-pca963x",
+ .of_match_table = of_pca963x_match,
+ .pm = pm_sleep_ptr(&pca963x_pm)
+ },
+ .probe = pca963x_probe,
+ .id_table = pca963x_id,
+};
+
+module_i2c_driver(pca963x_driver);
+
+MODULE_AUTHOR("Peter Meerwald <p.meerwald@bct-electronic.com>");
+MODULE_DESCRIPTION("PCA963X LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-pca995x.c b/drivers/leds/leds-pca995x.c
new file mode 100644
index 000000000000..6ad06ce2bf64
--- /dev/null
+++ b/drivers/leds/leds-pca995x.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED driver for PCA995x I2C LED drivers
+ *
+ * Copyright 2011 bct electronic GmbH
+ * Copyright 2013 Qtechnology/AS
+ * Copyright 2022 NXP
+ * Copyright 2023 Marek Vasut
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+
+/* Register definition */
+#define PCA995X_MODE1 0x00
+#define PCA995X_MODE2 0x01
+#define PCA995X_LEDOUT0 0x02
+
+/* Auto-increment disabled. Normal mode */
+#define PCA995X_MODE1_CFG 0x00
+
+/* LED select registers determine the source that drives LED outputs */
+#define PCA995X_LED_OFF 0x0
+#define PCA995X_LED_ON 0x1
+#define PCA995X_LED_PWM_MODE 0x2
+#define PCA995X_LDRX_MASK 0x3
+#define PCA995X_LDRX_BITS 2
+
+#define PCA995X_MAX_OUTPUTS 24
+#define PCA995X_OUTPUTS_PER_REG 4
+
+#define PCA995X_IREFALL_FULL_CFG 0xFF
+#define PCA995X_IREFALL_HALF_CFG (PCA995X_IREFALL_FULL_CFG / 2)
+
+#define ldev_to_led(c) container_of(c, struct pca995x_led, ldev)
+
+struct pca995x_chipdef {
+ unsigned int num_leds;
+ u8 pwm_base;
+ u8 irefall;
+};
+
+static const struct pca995x_chipdef pca9952_chipdef = {
+ .num_leds = 16,
+ .pwm_base = 0x0a,
+ .irefall = 0x43,
+};
+
+static const struct pca995x_chipdef pca9955b_chipdef = {
+ .num_leds = 16,
+ .pwm_base = 0x08,
+ .irefall = 0x45,
+};
+
+static const struct pca995x_chipdef pca9956b_chipdef = {
+ .num_leds = 24,
+ .pwm_base = 0x0a,
+ .irefall = 0x40,
+};
+
+struct pca995x_led {
+ unsigned int led_no;
+ struct led_classdev ldev;
+ struct pca995x_chip *chip;
+};
+
+struct pca995x_chip {
+ struct regmap *regmap;
+ struct pca995x_led leds[PCA995X_MAX_OUTPUTS];
+ const struct pca995x_chipdef *chipdef;
+};
+
+static int pca995x_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct pca995x_led *led = ldev_to_led(led_cdev);
+ struct pca995x_chip *chip = led->chip;
+ const struct pca995x_chipdef *chipdef = chip->chipdef;
+ u8 ledout_addr, pwmout_addr;
+ int shift, ret;
+
+ pwmout_addr = chipdef->pwm_base + led->led_no;
+ ledout_addr = PCA995X_LEDOUT0 + (led->led_no / PCA995X_OUTPUTS_PER_REG);
+ shift = PCA995X_LDRX_BITS * (led->led_no % PCA995X_OUTPUTS_PER_REG);
+
+ switch (brightness) {
+ case LED_FULL:
+ return regmap_update_bits(chip->regmap, ledout_addr,
+ PCA995X_LDRX_MASK << shift,
+ PCA995X_LED_ON << shift);
+ case LED_OFF:
+ return regmap_update_bits(chip->regmap, ledout_addr,
+ PCA995X_LDRX_MASK << shift, 0);
+ default:
+ /* Adjust brightness as per user input by changing individual PWM */
+ ret = regmap_write(chip->regmap, pwmout_addr, brightness);
+ if (ret)
+ return ret;
+
+ /*
+ * Change LDRx configuration to individual brightness via PWM.
+ * LED will stop blinking if it's doing so.
+ */
+ return regmap_update_bits(chip->regmap, ledout_addr,
+ PCA995X_LDRX_MASK << shift,
+ PCA995X_LED_PWM_MODE << shift);
+ }
+}
+
+static const struct regmap_config pca995x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x49,
+};
+
+static int pca995x_probe(struct i2c_client *client)
+{
+ struct fwnode_handle *led_fwnodes[PCA995X_MAX_OUTPUTS] = { 0 };
+ struct device *dev = &client->dev;
+ const struct pca995x_chipdef *chipdef;
+ struct pca995x_chip *chip;
+ struct pca995x_led *led;
+ int i, j, reg, ret;
+
+ chipdef = device_get_match_data(&client->dev);
+
+ if (!dev_fwnode(dev))
+ return -ENODEV;
+
+ chip = devm_kzalloc(dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->chipdef = chipdef;
+ chip->regmap = devm_regmap_init_i2c(client, &pca995x_regmap);
+ if (IS_ERR(chip->regmap))
+ return PTR_ERR(chip->regmap);
+
+ i2c_set_clientdata(client, chip);
+
+ device_for_each_child_node_scoped(dev, child) {
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret)
+ return ret;
+
+ if (reg < 0 || reg >= PCA995X_MAX_OUTPUTS || led_fwnodes[reg])
+ return -EINVAL;
+
+ led = &chip->leds[reg];
+ led_fwnodes[reg] = fwnode_handle_get(child);
+ led->chip = chip;
+ led->led_no = reg;
+ led->ldev.brightness_set_blocking = pca995x_brightness_set;
+ led->ldev.max_brightness = 255;
+ }
+
+ for (i = 0; i < PCA995X_MAX_OUTPUTS; i++) {
+ struct led_init_data init_data = {};
+
+ if (!led_fwnodes[i])
+ continue;
+
+ init_data.fwnode = led_fwnodes[i];
+
+ ret = devm_led_classdev_register_ext(dev,
+ &chip->leds[i].ldev,
+ &init_data);
+ if (ret < 0) {
+ for (j = i; j < PCA995X_MAX_OUTPUTS; j++)
+ fwnode_handle_put(led_fwnodes[j]);
+ return dev_err_probe(dev, ret,
+ "Could not register LED %s\n",
+ chip->leds[i].ldev.name);
+ }
+ }
+
+ /* Disable LED all-call address and set normal mode */
+ ret = regmap_write(chip->regmap, PCA995X_MODE1, PCA995X_MODE1_CFG);
+ if (ret)
+ return ret;
+
+ /* IREF Output current value for all LEDn outputs */
+ return regmap_write(chip->regmap, chipdef->irefall, PCA995X_IREFALL_HALF_CFG);
+}
+
+static const struct i2c_device_id pca995x_id[] = {
+ { "pca9952", .driver_data = (kernel_ulong_t)&pca9952_chipdef },
+ { "pca9955b", .driver_data = (kernel_ulong_t)&pca9955b_chipdef },
+ { "pca9956b", .driver_data = (kernel_ulong_t)&pca9956b_chipdef },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, pca995x_id);
+
+static const struct of_device_id pca995x_of_match[] = {
+ { .compatible = "nxp,pca9952", .data = &pca9952_chipdef },
+ { .compatible = "nxp,pca9955b", .data = &pca9955b_chipdef },
+ { .compatible = "nxp,pca9956b", .data = &pca9956b_chipdef },
+ {},
+};
+MODULE_DEVICE_TABLE(of, pca995x_of_match);
+
+static struct i2c_driver pca995x_driver = {
+ .driver = {
+ .name = "leds-pca995x",
+ .of_match_table = pca995x_of_match,
+ },
+ .probe = pca995x_probe,
+ .id_table = pca995x_id,
+};
+module_i2c_driver(pca995x_driver);
+
+MODULE_AUTHOR("Isai Gaspar <isaiezequiel.gaspar@nxp.com>");
+MODULE_DESCRIPTION("PCA995x LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-pm8058.c b/drivers/leds/leds-pm8058.c
new file mode 100644
index 000000000000..3f49a5181892
--- /dev/null
+++ b/drivers/leds/leds-pm8058.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright (c) 2010, 2011, 2016 The Linux Foundation. All rights reserved.
+ */
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/regmap.h>
+
+#define PM8058_LED_TYPE_COMMON 0x00
+#define PM8058_LED_TYPE_KEYPAD 0x01
+#define PM8058_LED_TYPE_FLASH 0x02
+
+#define PM8058_LED_TYPE_COMMON_MASK 0xf8
+#define PM8058_LED_TYPE_KEYPAD_MASK 0xf0
+#define PM8058_LED_TYPE_COMMON_SHIFT 3
+#define PM8058_LED_TYPE_KEYPAD_SHIFT 4
+
+struct pm8058_led {
+ struct regmap *map;
+ u32 reg;
+ u32 ledtype;
+ struct led_classdev cdev;
+};
+
+static void pm8058_led_set(struct led_classdev *cled,
+ enum led_brightness value)
+{
+ struct pm8058_led *led;
+ int ret = 0;
+ unsigned int mask = 0;
+ unsigned int val = 0;
+
+ led = container_of(cled, struct pm8058_led, cdev);
+ switch (led->ledtype) {
+ case PM8058_LED_TYPE_COMMON:
+ mask = PM8058_LED_TYPE_COMMON_MASK;
+ val = value << PM8058_LED_TYPE_COMMON_SHIFT;
+ break;
+ case PM8058_LED_TYPE_KEYPAD:
+ case PM8058_LED_TYPE_FLASH:
+ mask = PM8058_LED_TYPE_KEYPAD_MASK;
+ val = value << PM8058_LED_TYPE_KEYPAD_SHIFT;
+ break;
+ default:
+ break;
+ }
+
+ ret = regmap_update_bits(led->map, led->reg, mask, val);
+ if (ret)
+ pr_err("Failed to set LED brightness\n");
+}
+
+static enum led_brightness pm8058_led_get(struct led_classdev *cled)
+{
+ struct pm8058_led *led;
+ int ret;
+ unsigned int val;
+
+ led = container_of(cled, struct pm8058_led, cdev);
+
+ ret = regmap_read(led->map, led->reg, &val);
+ if (ret) {
+ pr_err("Failed to get LED brightness\n");
+ return LED_OFF;
+ }
+
+ switch (led->ledtype) {
+ case PM8058_LED_TYPE_COMMON:
+ val &= PM8058_LED_TYPE_COMMON_MASK;
+ val >>= PM8058_LED_TYPE_COMMON_SHIFT;
+ break;
+ case PM8058_LED_TYPE_KEYPAD:
+ case PM8058_LED_TYPE_FLASH:
+ val &= PM8058_LED_TYPE_KEYPAD_MASK;
+ val >>= PM8058_LED_TYPE_KEYPAD_SHIFT;
+ break;
+ default:
+ val = LED_OFF;
+ break;
+ }
+
+ return val;
+}
+
+static int pm8058_led_probe(struct platform_device *pdev)
+{
+ struct led_init_data init_data = {};
+ struct device *dev = &pdev->dev;
+ struct pm8058_led *led;
+ struct device_node *np;
+ int ret;
+ struct regmap *map;
+ enum led_brightness maxbright;
+ enum led_default_state state;
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->ledtype = (u32)(unsigned long)of_device_get_match_data(dev);
+
+ map = dev_get_regmap(dev->parent, NULL);
+ if (!map) {
+ dev_err(dev, "Parent regmap unavailable.\n");
+ return -ENXIO;
+ }
+ led->map = map;
+
+ np = dev_of_node(dev);
+
+ ret = of_property_read_u32(np, "reg", &led->reg);
+ if (ret) {
+ dev_err(dev, "no register offset specified\n");
+ return -EINVAL;
+ }
+
+ led->cdev.brightness_set = pm8058_led_set;
+ led->cdev.brightness_get = pm8058_led_get;
+ if (led->ledtype == PM8058_LED_TYPE_COMMON)
+ maxbright = 31; /* 5 bits */
+ else
+ maxbright = 15; /* 4 bits */
+ led->cdev.max_brightness = maxbright;
+
+ init_data.fwnode = of_fwnode_handle(np);
+
+ state = led_init_default_state_get(init_data.fwnode);
+ switch (state) {
+ case LEDS_DEFSTATE_ON:
+ led->cdev.brightness = maxbright;
+ pm8058_led_set(&led->cdev, maxbright);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ led->cdev.brightness = pm8058_led_get(&led->cdev);
+ break;
+ default:
+ led->cdev.brightness = LED_OFF;
+ pm8058_led_set(&led->cdev, LED_OFF);
+ }
+
+ if (led->ledtype == PM8058_LED_TYPE_KEYPAD ||
+ led->ledtype == PM8058_LED_TYPE_FLASH)
+ led->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret)
+ dev_err(dev, "Failed to register LED for %pOF\n", np);
+
+ return ret;
+}
+
+static const struct of_device_id pm8058_leds_id_table[] = {
+ {
+ .compatible = "qcom,pm8058-led",
+ .data = (void *)PM8058_LED_TYPE_COMMON
+ },
+ {
+ .compatible = "qcom,pm8058-keypad-led",
+ .data = (void *)PM8058_LED_TYPE_KEYPAD
+ },
+ {
+ .compatible = "qcom,pm8058-flash-led",
+ .data = (void *)PM8058_LED_TYPE_FLASH
+ },
+ { },
+};
+MODULE_DEVICE_TABLE(of, pm8058_leds_id_table);
+
+static struct platform_driver pm8058_led_driver = {
+ .probe = pm8058_led_probe,
+ .driver = {
+ .name = "pm8058-leds",
+ .of_match_table = pm8058_leds_id_table,
+ },
+};
+module_platform_driver(pm8058_led_driver);
+
+MODULE_DESCRIPTION("PM8058 LEDs driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:pm8058-leds");
diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c
new file mode 100644
index 000000000000..3a38578ce8e4
--- /dev/null
+++ b/drivers/leds/leds-powernv.c
@@ -0,0 +1,338 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * PowerNV LED Driver
+ *
+ * Copyright IBM Corp. 2015
+ *
+ * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
+ * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
+ */
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include <asm/opal.h>
+
+/* Map LED type to description. */
+struct led_type_map {
+ const int type;
+ const char *desc;
+};
+static const struct led_type_map led_type_map[] = {
+ {OPAL_SLOT_LED_TYPE_ID, "identify"},
+ {OPAL_SLOT_LED_TYPE_FAULT, "fault"},
+ {OPAL_SLOT_LED_TYPE_ATTN, "attention"},
+ {-1, NULL},
+};
+
+struct powernv_led_common {
+ /*
+ * By default unload path resets all the LEDs. But on PowerNV
+ * platform we want to retain LED state across reboot as these
+ * are controlled by firmware. Also service processor can modify
+ * the LEDs independent of OS. Hence avoid resetting LEDs in
+ * unload path.
+ */
+ bool led_disabled;
+
+ /* Max supported LED type */
+ __be64 max_led_type;
+
+ /* glabal lock */
+ struct mutex lock;
+};
+
+/* PowerNV LED data */
+struct powernv_led_data {
+ struct led_classdev cdev;
+ char *loc_code; /* LED location code */
+ int led_type; /* OPAL_SLOT_LED_TYPE_* */
+
+ struct powernv_led_common *common;
+};
+
+
+/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
+static int powernv_get_led_type(const char *led_type_desc)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(led_type_map); i++)
+ if (!strcmp(led_type_map[i].desc, led_type_desc))
+ return led_type_map[i].type;
+
+ return -1;
+}
+
+/*
+ * This commits the state change of the requested LED through an OPAL call.
+ * This function is called from work queue task context when ever it gets
+ * scheduled. This function can sleep at opal_async_wait_response call.
+ */
+static int powernv_led_set(struct powernv_led_data *powernv_led,
+ enum led_brightness value)
+{
+ int rc, token;
+ u64 led_mask, led_value = 0;
+ __be64 max_type;
+ struct opal_msg msg;
+ struct device *dev = powernv_led->cdev.dev;
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+
+ /* Prepare for the OPAL call */
+ max_type = powernv_led_common->max_led_type;
+ led_mask = OPAL_SLOT_LED_STATE_ON << powernv_led->led_type;
+ if (value)
+ led_value = led_mask;
+
+ /* OPAL async call */
+ token = opal_async_get_token_interruptible();
+ if (token < 0) {
+ if (token != -ERESTARTSYS)
+ dev_err(dev, "%s: Couldn't get OPAL async token\n",
+ __func__);
+ return token;
+ }
+
+ rc = opal_leds_set_ind(token, powernv_led->loc_code,
+ led_mask, led_value, &max_type);
+ if (rc != OPAL_ASYNC_COMPLETION) {
+ dev_err(dev, "%s: OPAL set LED call failed for %s [rc=%d]\n",
+ __func__, powernv_led->loc_code, rc);
+ goto out_token;
+ }
+
+ rc = opal_async_wait_response(token, &msg);
+ if (rc) {
+ dev_err(dev,
+ "%s: Failed to wait for the async response [rc=%d]\n",
+ __func__, rc);
+ goto out_token;
+ }
+
+ rc = opal_get_async_rc(msg);
+ if (rc != OPAL_SUCCESS)
+ dev_err(dev, "%s : OAPL async call returned failed [rc=%d]\n",
+ __func__, rc);
+
+out_token:
+ opal_async_release_token(token);
+ return rc;
+}
+
+/*
+ * This function fetches the LED state for a given LED type for
+ * mentioned LED classdev structure.
+ */
+static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led)
+{
+ int rc;
+ __be64 mask, value, max_type;
+ u64 led_mask, led_value;
+ struct device *dev = powernv_led->cdev.dev;
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+
+ /* Fetch all LED status */
+ mask = cpu_to_be64(0);
+ value = cpu_to_be64(0);
+ max_type = powernv_led_common->max_led_type;
+
+ rc = opal_leds_get_ind(powernv_led->loc_code,
+ &mask, &value, &max_type);
+ if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) {
+ dev_err(dev, "%s: OPAL get led call failed [rc=%d]\n",
+ __func__, rc);
+ return LED_OFF;
+ }
+
+ led_mask = be64_to_cpu(mask);
+ led_value = be64_to_cpu(value);
+
+ /* LED status available */
+ if (!((led_mask >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)) {
+ dev_err(dev, "%s: LED status not available for %s\n",
+ __func__, powernv_led->cdev.name);
+ return LED_OFF;
+ }
+
+ /* LED status value */
+ if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)
+ return LED_FULL;
+
+ return LED_OFF;
+}
+
+/*
+ * LED classdev 'brightness_get' function. This schedules work
+ * to update LED state.
+ */
+static int powernv_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct powernv_led_data *powernv_led =
+ container_of(led_cdev, struct powernv_led_data, cdev);
+ struct powernv_led_common *powernv_led_common = powernv_led->common;
+ int rc;
+
+ /* Do not modify LED in unload path */
+ if (powernv_led_common->led_disabled)
+ return 0;
+
+ mutex_lock(&powernv_led_common->lock);
+ rc = powernv_led_set(powernv_led, value);
+ mutex_unlock(&powernv_led_common->lock);
+
+ return rc;
+}
+
+/* LED classdev 'brightness_get' function */
+static enum led_brightness powernv_brightness_get(struct led_classdev *led_cdev)
+{
+ struct powernv_led_data *powernv_led =
+ container_of(led_cdev, struct powernv_led_data, cdev);
+
+ return powernv_led_get(powernv_led);
+}
+
+/*
+ * This function registers classdev structure for any given type of LED on
+ * a given child LED device node.
+ */
+static int powernv_led_create(struct device *dev,
+ struct powernv_led_data *powernv_led,
+ const char *led_type_desc)
+{
+ int rc;
+
+ /* Make sure LED type is supported */
+ powernv_led->led_type = powernv_get_led_type(led_type_desc);
+ if (powernv_led->led_type == -1) {
+ dev_warn(dev, "%s: No support for led type : %s\n",
+ __func__, led_type_desc);
+ return -EINVAL;
+ }
+
+ /* Create the name for classdev */
+ powernv_led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
+ powernv_led->loc_code,
+ led_type_desc);
+ if (!powernv_led->cdev.name)
+ return -ENOMEM;
+
+ powernv_led->cdev.brightness_set_blocking = powernv_brightness_set;
+ powernv_led->cdev.brightness_get = powernv_brightness_get;
+ powernv_led->cdev.brightness = LED_OFF;
+ powernv_led->cdev.max_brightness = LED_FULL;
+
+ /* Register the classdev */
+ rc = devm_led_classdev_register(dev, &powernv_led->cdev);
+ if (rc) {
+ dev_err(dev, "%s: Classdev registration failed for %s\n",
+ __func__, powernv_led->cdev.name);
+ }
+
+ return rc;
+}
+
+/* Go through LED device tree node and register LED classdev structure */
+static int powernv_led_classdev(struct platform_device *pdev,
+ struct device_node *led_node,
+ struct powernv_led_common *powernv_led_common)
+{
+ const char *cur = NULL;
+ int rc = -1;
+ struct property *p;
+ struct powernv_led_data *powernv_led;
+ struct device *dev = &pdev->dev;
+
+ 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)
+ 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)
+ return rc;
+
+ } /* while end */
+ }
+
+ return rc;
+}
+
+/* Platform driver probe */
+static int powernv_led_probe(struct platform_device *pdev)
+{
+ struct powernv_led_common *powernv_led_common;
+ struct device *dev = &pdev->dev;
+ struct device_node *led_node
+ __free(device_node) = of_find_node_by_path("/ibm,opal/leds");
+
+ if (!led_node) {
+ dev_err(dev, "%s: LED parent device node not found\n",
+ __func__);
+ return -EINVAL;
+ }
+
+ powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
+ GFP_KERNEL);
+ 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);
+
+ return powernv_led_classdev(pdev, led_node, powernv_led_common);
+}
+
+/* Platform driver remove */
+static void powernv_led_remove(struct platform_device *pdev)
+{
+ struct powernv_led_common *powernv_led_common;
+
+ /* Disable LED operation */
+ powernv_led_common = platform_get_drvdata(pdev);
+ powernv_led_common->led_disabled = true;
+
+ /* Destroy lock */
+ mutex_destroy(&powernv_led_common->lock);
+
+ dev_info(&pdev->dev, "PowerNV led module unregistered\n");
+}
+
+/* Platform driver property match */
+static const struct of_device_id powernv_led_match[] = {
+ {
+ .compatible = "ibm,opal-v3-led",
+ },
+ {},
+};
+MODULE_DEVICE_TABLE(of, powernv_led_match);
+
+static struct platform_driver powernv_led_driver = {
+ .probe = powernv_led_probe,
+ .remove = powernv_led_remove,
+ .driver = {
+ .name = "powernv-led-driver",
+ .of_match_table = powernv_led_match,
+ },
+};
+
+module_platform_driver(powernv_led_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("PowerNV LED driver");
+MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");
diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c
index faf52c005e8c..6c1f2f50ff85 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* linux/drivers/leds-pwm.c
*
@@ -6,216 +7,222 @@
* Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
*
* based on leds-gpio.c by Raphael Assenat <raph@8d.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
-#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/gpio/consumer.h>
#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/of_platform.h>
-#include <linux/fb.h>
#include <linux/leds.h>
-#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
#include <linux/pwm.h>
-#include <linux/leds_pwm.h>
#include <linux/slab.h>
-#include <linux/workqueue.h>
+
+struct led_pwm {
+ const char *name;
+ u8 active_low;
+ u8 default_state;
+ unsigned int max_brightness;
+};
struct led_pwm_data {
+ struct gpio_desc *enable_gpio;
struct led_classdev cdev;
struct pwm_device *pwm;
- struct work_struct work;
+ struct pwm_state pwmstate;
unsigned int active_low;
- unsigned int period;
- int duty;
- bool can_sleep;
};
struct led_pwm_priv {
int num_leds;
- struct led_pwm_data leds[0];
+ struct led_pwm_data leds[];
};
-static void __led_pwm_set(struct led_pwm_data *led_dat)
+static int led_pwm_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
{
- int new_duty = led_dat->duty;
+ struct led_pwm_data *led_dat =
+ container_of(led_cdev, struct led_pwm_data, cdev);
+ unsigned int max = led_dat->cdev.max_brightness;
+ unsigned long long duty = led_dat->pwmstate.period;
- pwm_config(led_dat->pwm, new_duty, led_dat->period);
+ duty *= brightness;
+ do_div(duty, max);
- if (new_duty == 0)
- pwm_disable(led_dat->pwm);
- else
- pwm_enable(led_dat->pwm);
-}
+ if (led_dat->active_low)
+ duty = led_dat->pwmstate.period - duty;
-static void led_pwm_work(struct work_struct *work)
-{
- struct led_pwm_data *led_dat =
- container_of(work, struct led_pwm_data, work);
+ gpiod_set_value_cansleep(led_dat->enable_gpio, !!brightness);
- __led_pwm_set(led_dat);
+ led_dat->pwmstate.duty_cycle = duty;
+ /*
+ * 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 void led_pwm_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
+static int led_pwm_default_brightness_get(struct fwnode_handle *fwnode,
+ int max_brightness)
{
- struct led_pwm_data *led_dat =
- container_of(led_cdev, struct led_pwm_data, cdev);
- unsigned int max = led_dat->cdev.max_brightness;
- unsigned int period = led_dat->period;
+ unsigned int default_brightness;
+ int ret;
- led_dat->duty = brightness * period / max;
+ ret = fwnode_property_read_u32(fwnode, "default-brightness",
+ &default_brightness);
+ if (ret < 0 || default_brightness > max_brightness)
+ default_brightness = max_brightness;
- if (led_dat->can_sleep)
- schedule_work(&led_dat->work);
- else
- __led_pwm_set(led_dat);
+ return default_brightness;
}
-static inline size_t sizeof_pwm_leds_priv(int num_leds)
+__attribute__((nonnull))
+static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
+ struct led_pwm *led, struct fwnode_handle *fwnode)
{
- return sizeof(struct led_pwm_priv) +
- (sizeof(struct led_pwm_data) * num_leds);
+ struct led_pwm_data *led_data = &priv->leds[priv->num_leds];
+ struct led_init_data init_data = { .fwnode = fwnode };
+ int ret;
+
+ led_data->active_low = led->active_low;
+ led_data->cdev.name = led->name;
+ led_data->cdev.brightness = LED_OFF;
+ led_data->cdev.max_brightness = led->max_brightness;
+ led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ led_data->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
+ if (IS_ERR(led_data->pwm))
+ return dev_err_probe(dev, PTR_ERR(led_data->pwm),
+ "unable to request PWM for %s\n",
+ led->name);
+
+ led_data->cdev.brightness_set_blocking = led_pwm_set;
+
+ /* init PWM state */
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_KEEP:
+ pwm_get_state(led_data->pwm, &led_data->pwmstate);
+ if (led_data->pwmstate.period)
+ break;
+ led->default_state = LEDS_DEFSTATE_OFF;
+ dev_warn(dev,
+ "failed to read period for %s, default to off",
+ led->name);
+ fallthrough;
+ default:
+ pwm_init_state(led_data->pwm, &led_data->pwmstate);
+ break;
+ }
+
+ /* set brightness */
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led_data->cdev.brightness =
+ led_pwm_default_brightness_get(fwnode, led->max_brightness);
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ {
+ uint64_t brightness;
+
+ brightness = led->max_brightness;
+ brightness *= led_data->pwmstate.duty_cycle;
+ do_div(brightness, led_data->pwmstate.period);
+ led_data->cdev.brightness = brightness;
+ }
+ break;
+ }
+
+ /*
+ * Claim the GPIO as GPIOD_ASIS and set the value
+ * later on to honor the different default states
+ */
+ led_data->enable_gpio = devm_fwnode_gpiod_get(dev, fwnode, "enable", GPIOD_ASIS, NULL);
+ if (IS_ERR(led_data->enable_gpio)) {
+ if (PTR_ERR(led_data->enable_gpio) == -ENOENT)
+ /* Enable GPIO is optional */
+ led_data->enable_gpio = NULL;
+ else
+ return PTR_ERR(led_data->enable_gpio);
+ }
+
+ gpiod_direction_output(led_data->enable_gpio, !!led_data->cdev.brightness);
+
+ ret = devm_led_classdev_register_ext(dev, &led_data->cdev, &init_data);
+ if (ret) {
+ dev_err(dev, "failed to register PWM led for %s: %d\n",
+ led->name, ret);
+ return ret;
+ }
+
+ if (led->default_state != LEDS_DEFSTATE_KEEP) {
+ ret = led_pwm_set(&led_data->cdev, led_data->cdev.brightness);
+ if (ret) {
+ dev_err(dev, "failed to set led PWM value for %s: %d",
+ led->name, ret);
+ return ret;
+ }
+ }
+
+ priv->num_leds++;
+ return 0;
}
-static struct led_pwm_priv *led_pwm_create_of(struct platform_device *pdev)
+static int led_pwm_create_fwnode(struct device *dev, struct led_pwm_priv *priv)
{
- struct device_node *node = pdev->dev.of_node;
- struct device_node *child;
- struct led_pwm_priv *priv;
- int count, ret;
+ struct led_pwm led;
+ int ret;
- /* count LEDs in this device, so we know how much to allocate */
- count = of_get_child_count(node);
- if (!count)
- return NULL;
+ device_for_each_child_node_scoped(dev, fwnode) {
+ memset(&led, 0, sizeof(led));
- priv = devm_kzalloc(&pdev->dev, sizeof_pwm_leds_priv(count),
- GFP_KERNEL);
- if (!priv)
- return NULL;
+ ret = fwnode_property_read_string(fwnode, "label", &led.name);
+ if (ret && is_of_node(fwnode))
+ led.name = to_of_node(fwnode)->name;
- for_each_child_of_node(node, child) {
- struct led_pwm_data *led_dat = &priv->leds[priv->num_leds];
+ if (!led.name)
+ return -EINVAL;
- led_dat->cdev.name = of_get_property(child, "label",
- NULL) ? : child->name;
+ led.active_low = fwnode_property_read_bool(fwnode,
+ "active-low");
+ fwnode_property_read_u32(fwnode, "max-brightness",
+ &led.max_brightness);
- led_dat->pwm = devm_of_pwm_get(&pdev->dev, child, NULL);
- if (IS_ERR(led_dat->pwm)) {
- dev_err(&pdev->dev, "unable to request PWM for %s\n",
- led_dat->cdev.name);
- goto err;
- }
- /* Get the period from PWM core when n*/
- led_dat->period = pwm_get_period(led_dat->pwm);
-
- led_dat->cdev.default_trigger = of_get_property(child,
- "linux,default-trigger", NULL);
- of_property_read_u32(child, "max-brightness",
- &led_dat->cdev.max_brightness);
-
- led_dat->cdev.brightness_set = led_pwm_set;
- led_dat->cdev.brightness = LED_OFF;
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- led_dat->can_sleep = pwm_can_sleep(led_dat->pwm);
- if (led_dat->can_sleep)
- INIT_WORK(&led_dat->work, led_pwm_work);
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0) {
- dev_err(&pdev->dev, "failed to register for %s\n",
- led_dat->cdev.name);
- of_node_put(child);
- goto err;
- }
- priv->num_leds++;
- }
+ led.default_state = led_init_default_state_get(fwnode);
- return priv;
-err:
- while (priv->num_leds--)
- led_classdev_unregister(&priv->leds[priv->num_leds].cdev);
+ ret = led_pwm_add(dev, priv, &led, fwnode);
+ if (ret)
+ return ret;
+ }
- return NULL;
+ return 0;
}
static int led_pwm_probe(struct platform_device *pdev)
{
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
struct led_pwm_priv *priv;
- int i, ret = 0;
-
- if (pdata && pdata->num_leds) {
- priv = devm_kzalloc(&pdev->dev,
- sizeof_pwm_leds_priv(pdata->num_leds),
- GFP_KERNEL);
- if (!priv)
- return -ENOMEM;
-
- for (i = 0; i < pdata->num_leds; i++) {
- struct led_pwm *cur_led = &pdata->leds[i];
- struct led_pwm_data *led_dat = &priv->leds[i];
-
- led_dat->pwm = devm_pwm_get(&pdev->dev, cur_led->name);
- if (IS_ERR(led_dat->pwm)) {
- ret = PTR_ERR(led_dat->pwm);
- dev_err(&pdev->dev,
- "unable to request PWM for %s\n",
- cur_led->name);
- goto err;
- }
-
- led_dat->cdev.name = cur_led->name;
- led_dat->cdev.default_trigger = cur_led->default_trigger;
- led_dat->active_low = cur_led->active_low;
- led_dat->period = cur_led->pwm_period_ns;
- led_dat->cdev.brightness_set = led_pwm_set;
- led_dat->cdev.brightness = LED_OFF;
- led_dat->cdev.max_brightness = cur_led->max_brightness;
- led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- led_dat->can_sleep = pwm_can_sleep(led_dat->pwm);
- if (led_dat->can_sleep)
- INIT_WORK(&led_dat->work, led_pwm_work);
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0)
- goto err;
- }
- priv->num_leds = pdata->num_leds;
- } else {
- priv = led_pwm_create_of(pdev);
- if (!priv)
- return -ENODEV;
- }
+ int ret = 0;
+ int count;
- platform_set_drvdata(pdev, priv);
+ count = device_get_child_node_count(&pdev->dev);
- return 0;
+ if (!count)
+ return -EINVAL;
-err:
- while (i--)
- led_classdev_unregister(&priv->leds[i].cdev);
+ priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
- return ret;
-}
+ ret = led_pwm_create_fwnode(&pdev->dev, priv);
-static int led_pwm_remove(struct platform_device *pdev)
-{
- struct led_pwm_priv *priv = platform_get_drvdata(pdev);
- int i;
+ if (ret)
+ return ret;
- for (i = 0; i < priv->num_leds; i++) {
- led_classdev_unregister(&priv->leds[i].cdev);
- if (priv->leds[i].can_sleep)
- cancel_work_sync(&priv->leds[i].work);
- }
+ platform_set_drvdata(pdev, priv);
return 0;
}
@@ -228,17 +235,15 @@ MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
static struct platform_driver led_pwm_driver = {
.probe = led_pwm_probe,
- .remove = led_pwm_remove,
.driver = {
.name = "leds_pwm",
- .owner = THIS_MODULE,
- .of_match_table = of_match_ptr(of_pwm_leds_match),
+ .of_match_table = of_pwm_leds_match,
},
};
module_platform_driver(led_pwm_driver);
MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
-MODULE_DESCRIPTION("PWM LED driver for PXA");
-MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("generic PWM LED driver");
+MODULE_LICENSE("GPL v2");
MODULE_ALIAS("platform:leds-pwm");
diff --git a/drivers/leds/leds-qnap-mcu.c b/drivers/leds/leds-qnap-mcu.c
new file mode 100644
index 000000000000..6df110e33ac9
--- /dev/null
+++ b/drivers/leds/leds-qnap-mcu.c
@@ -0,0 +1,392 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Driver for LEDs found on QNAP MCU devices
+ *
+ * Copyright (C) 2024 Heiko Stuebner <heiko@sntech.de>
+ */
+
+#include <linux/leds.h>
+#include <linux/mfd/qnap-mcu.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <uapi/linux/uleds.h>
+
+enum qnap_mcu_err_led_mode {
+ QNAP_MCU_ERR_LED_ON = 0,
+ QNAP_MCU_ERR_LED_OFF = 1,
+ QNAP_MCU_ERR_LED_BLINK_FAST = 2,
+ QNAP_MCU_ERR_LED_BLINK_SLOW = 3,
+};
+
+struct qnap_mcu_err_led {
+ struct qnap_mcu *mcu;
+ struct led_classdev cdev;
+ char name[LED_MAX_NAME_SIZE];
+ u8 num;
+ u8 mode;
+};
+
+static inline struct qnap_mcu_err_led *
+ cdev_to_qnap_mcu_err_led(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct qnap_mcu_err_led, cdev);
+}
+
+static int qnap_mcu_err_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
+ u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
+
+ /* Don't disturb a possible set blink-mode if LED stays on */
+ if (brightness != 0 && err_led->mode >= QNAP_MCU_ERR_LED_BLINK_FAST)
+ return 0;
+
+ err_led->mode = brightness ? QNAP_MCU_ERR_LED_ON : QNAP_MCU_ERR_LED_OFF;
+ cmd[3] = '0' + err_led->mode;
+
+ return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_err_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct qnap_mcu_err_led *err_led = cdev_to_qnap_mcu_err_led(led_cdev);
+ u8 cmd[] = { '@', 'R', '0' + err_led->num, '0' };
+
+ /* LED is off, nothing to do */
+ if (err_led->mode == QNAP_MCU_ERR_LED_OFF)
+ return 0;
+
+ if (*delay_on < 500) {
+ *delay_on = 100;
+ *delay_off = 100;
+ err_led->mode = QNAP_MCU_ERR_LED_BLINK_FAST;
+ } else {
+ *delay_on = 500;
+ *delay_off = 500;
+ err_led->mode = QNAP_MCU_ERR_LED_BLINK_SLOW;
+ }
+
+ cmd[3] = '0' + err_led->mode;
+
+ return qnap_mcu_exec_with_ack(err_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_register_err_led(struct device *dev, struct qnap_mcu *mcu, int num_err_led)
+{
+ struct qnap_mcu_err_led *err_led;
+ int ret;
+
+ err_led = devm_kzalloc(dev, sizeof(*err_led), GFP_KERNEL);
+ if (!err_led)
+ return -ENOMEM;
+
+ err_led->mcu = mcu;
+ err_led->num = num_err_led;
+ err_led->mode = QNAP_MCU_ERR_LED_OFF;
+
+ scnprintf(err_led->name, LED_MAX_NAME_SIZE, "hdd%d:red:status", num_err_led + 1);
+ err_led->cdev.name = err_led->name;
+
+ err_led->cdev.brightness_set_blocking = qnap_mcu_err_led_set;
+ err_led->cdev.blink_set = qnap_mcu_err_led_blink_set;
+ err_led->cdev.brightness = 0;
+ err_led->cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, &err_led->cdev);
+ if (ret)
+ return ret;
+
+ return qnap_mcu_err_led_set(&err_led->cdev, 0);
+}
+
+enum qnap_mcu_usb_led_mode {
+ QNAP_MCU_USB_LED_ON = 0,
+ QNAP_MCU_USB_LED_OFF = 2,
+ QNAP_MCU_USB_LED_BLINK = 1,
+};
+
+struct qnap_mcu_usb_led {
+ struct qnap_mcu *mcu;
+ struct led_classdev cdev;
+ u8 mode;
+};
+
+static inline struct qnap_mcu_usb_led *
+ cdev_to_qnap_mcu_usb_led(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct qnap_mcu_usb_led, cdev);
+}
+
+static int qnap_mcu_usb_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
+ u8 cmd[] = { '@', 'C', 0 };
+
+ /* Don't disturb a possible set blink-mode if LED stays on */
+ if (brightness != 0 && usb_led->mode == QNAP_MCU_USB_LED_BLINK)
+ return 0;
+
+ usb_led->mode = brightness ? QNAP_MCU_USB_LED_ON : QNAP_MCU_USB_LED_OFF;
+
+ /*
+ * Byte 3 is shared between the usb led target on/off/blink
+ * and also the buzzer control (in the input driver)
+ */
+ cmd[2] = 'E' + usb_led->mode;
+
+ return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_usb_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct qnap_mcu_usb_led *usb_led = cdev_to_qnap_mcu_usb_led(led_cdev);
+ u8 cmd[] = { '@', 'C', 0 };
+
+ /* LED is off, nothing to do */
+ if (usb_led->mode == QNAP_MCU_USB_LED_OFF)
+ return 0;
+
+ *delay_on = 250;
+ *delay_off = 250;
+ usb_led->mode = QNAP_MCU_USB_LED_BLINK;
+
+ /*
+ * Byte 3 is shared between the USB LED target on/off/blink
+ * and also the buzzer control (in the input driver)
+ */
+ cmd[2] = 'E' + usb_led->mode;
+
+ return qnap_mcu_exec_with_ack(usb_led->mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_register_usb_led(struct device *dev, struct qnap_mcu *mcu)
+{
+ struct qnap_mcu_usb_led *usb_led;
+ int ret;
+
+ usb_led = devm_kzalloc(dev, sizeof(*usb_led), GFP_KERNEL);
+ if (!usb_led)
+ return -ENOMEM;
+
+ usb_led->mcu = mcu;
+ usb_led->mode = QNAP_MCU_USB_LED_OFF;
+ usb_led->cdev.name = "usb:blue:disk";
+ usb_led->cdev.brightness_set_blocking = qnap_mcu_usb_led_set;
+ usb_led->cdev.blink_set = qnap_mcu_usb_led_blink_set;
+ usb_led->cdev.brightness = 0;
+ usb_led->cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, &usb_led->cdev);
+ if (ret)
+ return ret;
+
+ return qnap_mcu_usb_led_set(&usb_led->cdev, 0);
+}
+
+enum qnap_mcu_status_led_mode {
+ QNAP_MCU_STATUS_LED_OFF = 0,
+ QNAP_MCU_STATUS_LED_ON = 1,
+ QNAP_MCU_STATUS_LED_BLINK_FAST = 2, /* 500ms / 500ms */
+ QNAP_MCU_STATUS_LED_BLINK_SLOW = 3, /* 1s / 1s */
+};
+
+struct qnap_mcu_status_led {
+ struct led_classdev cdev;
+ struct qnap_mcu_status_led *red;
+ u8 mode;
+};
+
+struct qnap_mcu_status {
+ struct qnap_mcu *mcu;
+ struct qnap_mcu_status_led red;
+ struct qnap_mcu_status_led green;
+};
+
+static inline struct qnap_mcu_status_led *cdev_to_qnap_mcu_status_led(struct led_classdev *led_cdev)
+{
+ return container_of(led_cdev, struct qnap_mcu_status_led, cdev);
+}
+
+static inline struct qnap_mcu_status *statusled_to_qnap_mcu_status(struct qnap_mcu_status_led *led)
+{
+ return container_of(led->red, struct qnap_mcu_status, red);
+}
+
+static u8 qnap_mcu_status_led_encode(struct qnap_mcu_status *status)
+{
+ if (status->red.mode == QNAP_MCU_STATUS_LED_OFF) {
+ switch (status->green.mode) {
+ case QNAP_MCU_STATUS_LED_OFF:
+ return '9';
+ case QNAP_MCU_STATUS_LED_ON:
+ return '6';
+ case QNAP_MCU_STATUS_LED_BLINK_FAST:
+ return '5';
+ case QNAP_MCU_STATUS_LED_BLINK_SLOW:
+ return 'A';
+ }
+ } else if (status->green.mode == QNAP_MCU_STATUS_LED_OFF) {
+ switch (status->red.mode) {
+ case QNAP_MCU_STATUS_LED_OFF:
+ return '9';
+ case QNAP_MCU_STATUS_LED_ON:
+ return '7';
+ case QNAP_MCU_STATUS_LED_BLINK_FAST:
+ return '4';
+ case QNAP_MCU_STATUS_LED_BLINK_SLOW:
+ return 'B';
+ }
+ } else if (status->green.mode == QNAP_MCU_STATUS_LED_ON &&
+ status->red.mode == QNAP_MCU_STATUS_LED_ON) {
+ return 'D';
+ } else if (status->green.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW &&
+ status->red.mode == QNAP_MCU_STATUS_LED_BLINK_SLOW) {
+ return 'C';
+ }
+
+ /*
+ * Here both LEDs are on in some fashion, either both blinking fast,
+ * or in different speeds, so default to fast blinking for both.
+ */
+ return '8';
+}
+
+static int qnap_mcu_status_led_update(struct qnap_mcu *mcu,
+ struct qnap_mcu_status *status)
+{
+ u8 cmd[] = { '@', 'C', 0 };
+
+ cmd[2] = qnap_mcu_status_led_encode(status);
+
+ return qnap_mcu_exec_with_ack(mcu, cmd, sizeof(cmd));
+}
+
+static int qnap_mcu_status_led_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev);
+ struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led);
+
+ /* Don't disturb a possible set blink-mode if LED stays on */
+ if (brightness != 0 && status_led->mode >= QNAP_MCU_STATUS_LED_BLINK_FAST)
+ return 0;
+
+ status_led->mode = brightness ? QNAP_MCU_STATUS_LED_ON :
+ QNAP_MCU_STATUS_LED_OFF;
+
+ return qnap_mcu_status_led_update(base->mcu, base);
+}
+
+static int qnap_mcu_status_led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct qnap_mcu_status_led *status_led = cdev_to_qnap_mcu_status_led(led_cdev);
+ struct qnap_mcu_status *base = statusled_to_qnap_mcu_status(status_led);
+
+ if (status_led->mode == QNAP_MCU_STATUS_LED_OFF)
+ return 0;
+
+ if (*delay_on <= 500) {
+ *delay_on = 500;
+ *delay_off = 500;
+ status_led->mode = QNAP_MCU_STATUS_LED_BLINK_FAST;
+ } else {
+ *delay_on = 1000;
+ *delay_off = 1000;
+ status_led->mode = QNAP_MCU_STATUS_LED_BLINK_SLOW;
+ }
+
+ return qnap_mcu_status_led_update(base->mcu, base);
+}
+
+static int qnap_mcu_register_status_leds(struct device *dev, struct qnap_mcu *mcu)
+{
+ struct qnap_mcu_status *status;
+ int ret;
+
+ status = devm_kzalloc(dev, sizeof(*status), GFP_KERNEL);
+ if (!status)
+ return -ENOMEM;
+
+ status->mcu = mcu;
+
+ /*
+ * point to the red led, so that statusled_to_qnap_mcu_status
+ * can resolve the main status struct containing both leds
+ */
+ status->red.red = &status->red;
+ status->green.red = &status->red;
+
+ status->red.mode = QNAP_MCU_STATUS_LED_OFF;
+ status->red.cdev.name = "red:status";
+ status->red.cdev.brightness_set_blocking = qnap_mcu_status_led_set;
+ status->red.cdev.blink_set = qnap_mcu_status_led_blink_set;
+ status->red.cdev.brightness = 0;
+ status->red.cdev.max_brightness = 1;
+
+ status->green.mode = QNAP_MCU_STATUS_LED_OFF;
+ status->green.cdev.name = "green:status";
+ status->green.cdev.brightness_set_blocking = qnap_mcu_status_led_set;
+ status->green.cdev.blink_set = qnap_mcu_status_led_blink_set;
+ status->green.cdev.brightness = 0;
+ status->green.cdev.max_brightness = 1;
+
+ ret = devm_led_classdev_register(dev, &status->red.cdev);
+ if (ret)
+ return ret;
+
+ ret = devm_led_classdev_register(dev, &status->green.cdev);
+ if (ret)
+ return ret;
+
+ return qnap_mcu_status_led_update(status->mcu, status);
+}
+
+static int qnap_mcu_leds_probe(struct platform_device *pdev)
+{
+ struct qnap_mcu *mcu = dev_get_drvdata(pdev->dev.parent);
+ const struct qnap_mcu_variant *variant = pdev->dev.platform_data;
+ int ret;
+
+ for (int i = 0; i < variant->num_drives; i++) {
+ ret = qnap_mcu_register_err_led(&pdev->dev, mcu, i);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register error LED %d\n", i);
+ }
+
+ if (variant->usb_led) {
+ ret = qnap_mcu_register_usb_led(&pdev->dev, mcu);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register USB LED\n");
+ }
+
+ ret = qnap_mcu_register_status_leds(&pdev->dev, mcu);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to register status LEDs\n");
+
+ return 0;
+}
+
+static struct platform_driver qnap_mcu_leds_driver = {
+ .probe = qnap_mcu_leds_probe,
+ .driver = {
+ .name = "qnap-mcu-leds",
+ },
+};
+module_platform_driver(qnap_mcu_leds_driver);
+
+MODULE_ALIAS("platform:qnap-mcu-leds");
+MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
+MODULE_DESCRIPTION("QNAP MCU LEDs driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-rb532.c b/drivers/leds/leds-rb532.c
index 2e746d257b02..782e1c11ee44 100644
--- a/drivers/leds/leds-rb532.c
+++ b/drivers/leds/leds-rb532.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LEDs driver for the "User LED" on Routerboard532
*
@@ -20,7 +21,6 @@ static void rb532_led_set(struct led_classdev *cdev,
{
if (brightness)
set_latch_u5(LO_ULED, 0);
-
else
set_latch_u5(0, LO_ULED);
}
@@ -42,10 +42,9 @@ static int rb532_led_probe(struct platform_device *pdev)
return led_classdev_register(&pdev->dev, &rb532_uled);
}
-static int rb532_led_remove(struct platform_device *pdev)
+static void rb532_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&rb532_uled);
- return 0;
}
static struct platform_driver rb532_led_driver = {
@@ -53,7 +52,6 @@ static struct platform_driver rb532_led_driver = {
.remove = rb532_led_remove,
.driver = {
.name = "rb532-led",
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c
index 4253a9b03dbf..ade64629431a 100644
--- a/drivers/leds/leds-regulator.c
+++ b/drivers/leds/leds-regulator.c
@@ -1,20 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* leds-regulator.c - LED class driver for regulator driven LEDs.
*
* Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
*
* Inspired by leds-wm8350 driver.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
+#include <linux/mod_devicetable.h>
#include <linux/err.h>
#include <linux/slab.h>
-#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/leds-regulator.h>
#include <linux/platform_device.h>
@@ -25,10 +21,8 @@
struct regulator_led {
struct led_classdev cdev;
- enum led_brightness value;
int enabled;
struct mutex mutex;
- struct work_struct work;
struct regulator *vcc;
};
@@ -94,22 +88,24 @@ static void regulator_led_disable(struct regulator_led *led)
led->enabled = 0;
}
-static void regulator_led_set_value(struct regulator_led *led)
+static int regulator_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
+ struct regulator_led *led = to_regulator_led(led_cdev);
int voltage;
- int ret;
+ int ret = 0;
mutex_lock(&led->mutex);
- if (led->value == LED_OFF) {
+ if (value == LED_OFF) {
regulator_led_disable(led);
goto out;
}
if (led->cdev.max_brightness > 1) {
- voltage = led_regulator_get_voltage(led->vcc, led->value);
+ voltage = led_regulator_get_voltage(led->vcc, value);
dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n",
- led->value, voltage);
+ value, voltage);
ret = regulator_set_voltage(led->vcc, voltage, voltage);
if (ret != 0)
@@ -121,60 +117,44 @@ static void regulator_led_set_value(struct regulator_led *led)
out:
mutex_unlock(&led->mutex);
-}
-
-static void led_work(struct work_struct *work)
-{
- struct regulator_led *led;
-
- led = container_of(work, struct regulator_led, work);
- regulator_led_set_value(led);
-}
-
-static void regulator_led_brightness_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct regulator_led *led = to_regulator_led(led_cdev);
-
- led->value = value;
- schedule_work(&led->work);
+ return ret;
}
static int regulator_led_probe(struct platform_device *pdev)
{
- struct led_regulator_platform_data *pdata = pdev->dev.platform_data;
+ struct led_regulator_platform_data *pdata =
+ dev_get_platdata(&pdev->dev);
+ struct device *dev = &pdev->dev;
+ struct led_init_data init_data = {};
struct regulator_led *led;
struct regulator *vcc;
int ret = 0;
- if (pdata == NULL) {
- dev_err(&pdev->dev, "no platform data\n");
- return -ENODEV;
- }
-
- vcc = regulator_get_exclusive(&pdev->dev, "vled");
+ vcc = devm_regulator_get_exclusive(dev, "vled");
if (IS_ERR(vcc)) {
- dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name);
+ dev_err(dev, "Cannot get vcc\n");
return PTR_ERR(vcc);
}
- led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
- if (led == NULL) {
- ret = -ENOMEM;
- goto err_vcc;
- }
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (led == NULL)
+ return -ENOMEM;
+
+ init_data.fwnode = dev->fwnode;
led->cdev.max_brightness = led_regulator_get_max_brightness(vcc);
- if (pdata->brightness > led->cdev.max_brightness) {
- dev_err(&pdev->dev, "Invalid default brightness %d\n",
+ /* Legacy platform data label assignment */
+ if (pdata) {
+ if (pdata->brightness > led->cdev.max_brightness) {
+ dev_err(dev, "Invalid default brightness %d\n",
pdata->brightness);
- ret = -EINVAL;
- goto err_vcc;
+ return -EINVAL;
+ }
+ led->cdev.brightness = pdata->brightness;
+ init_data.default_label = pdata->name;
}
- led->value = pdata->brightness;
- led->cdev.brightness_set = regulator_led_brightness_set;
- led->cdev.name = pdata->name;
+ led->cdev.brightness_set_blocking = regulator_led_brightness_set;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->vcc = vcc;
@@ -183,45 +163,35 @@ static int regulator_led_probe(struct platform_device *pdev)
led->enabled = 1;
mutex_init(&led->mutex);
- INIT_WORK(&led->work, led_work);
platform_set_drvdata(pdev, led);
- ret = led_classdev_register(&pdev->dev, &led->cdev);
- if (ret < 0) {
- cancel_work_sync(&led->work);
- goto err_vcc;
- }
-
- /* to expose the default value to userspace */
- led->cdev.brightness = led->value;
-
- /* Set the default led status */
- regulator_led_set_value(led);
+ ret = led_classdev_register_ext(dev, &led->cdev, &init_data);
+ if (ret < 0)
+ return ret;
return 0;
-
-err_vcc:
- regulator_put(vcc);
- return ret;
}
-static int regulator_led_remove(struct platform_device *pdev)
+static void regulator_led_remove(struct platform_device *pdev)
{
struct regulator_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
- cancel_work_sync(&led->work);
regulator_led_disable(led);
- regulator_put(led->vcc);
- return 0;
}
+static const struct of_device_id regulator_led_of_match[] = {
+ { .compatible = "regulator-led", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, regulator_led_of_match);
+
static struct platform_driver regulator_led_driver = {
.driver = {
- .name = "leds-regulator",
- .owner = THIS_MODULE,
- },
+ .name = "leds-regulator",
+ .of_match_table = regulator_led_of_match,
+ },
.probe = regulator_led_probe,
.remove = regulator_led_remove,
};
diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c
deleted file mode 100644
index adebf4931e1e..000000000000
--- a/drivers/leds/leds-renesas-tpu.c
+++ /dev/null
@@ -1,337 +0,0 @@
-/*
- * LED control using Renesas TPU
- *
- * Copyright (C) 2011 Magnus Damm
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- */
-
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/spinlock.h>
-#include <linux/printk.h>
-#include <linux/ioport.h>
-#include <linux/io.h>
-#include <linux/clk.h>
-#include <linux/leds.h>
-#include <linux/platform_data/leds-renesas-tpu.h>
-#include <linux/gpio.h>
-#include <linux/err.h>
-#include <linux/slab.h>
-#include <linux/pm_runtime.h>
-#include <linux/workqueue.h>
-
-enum r_tpu_pin { R_TPU_PIN_UNUSED, R_TPU_PIN_GPIO, R_TPU_PIN_GPIO_FN };
-enum r_tpu_timer { R_TPU_TIMER_UNUSED, R_TPU_TIMER_ON };
-
-struct r_tpu_priv {
- struct led_classdev ldev;
- void __iomem *mapbase;
- struct clk *clk;
- struct platform_device *pdev;
- enum r_tpu_pin pin_state;
- enum r_tpu_timer timer_state;
- unsigned long min_rate;
- unsigned int refresh_rate;
- struct work_struct work;
- enum led_brightness new_brightness;
-};
-
-static DEFINE_SPINLOCK(r_tpu_lock);
-
-#define TSTR -1 /* Timer start register (shared register) */
-#define TCR 0 /* Timer control register (+0x00) */
-#define TMDR 1 /* Timer mode register (+0x04) */
-#define TIOR 2 /* Timer I/O control register (+0x08) */
-#define TIER 3 /* Timer interrupt enable register (+0x0c) */
-#define TSR 4 /* Timer status register (+0x10) */
-#define TCNT 5 /* Timer counter (+0x14) */
-#define TGRA 6 /* Timer general register A (+0x18) */
-#define TGRB 7 /* Timer general register B (+0x1c) */
-#define TGRC 8 /* Timer general register C (+0x20) */
-#define TGRD 9 /* Timer general register D (+0x24) */
-
-static inline u16 r_tpu_read(struct r_tpu_priv *p, int reg_nr)
-{
- struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
- void __iomem *base = p->mapbase;
- unsigned long offs = reg_nr << 2;
-
- if (reg_nr == TSTR)
- return ioread16(base - cfg->channel_offset);
-
- return ioread16(base + offs);
-}
-
-static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, u16 value)
-{
- struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
- void __iomem *base = p->mapbase;
- unsigned long offs = reg_nr << 2;
-
- if (reg_nr == TSTR) {
- iowrite16(value, base - cfg->channel_offset);
- return;
- }
-
- iowrite16(value, base + offs);
-}
-
-static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start)
-{
- struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
- unsigned long flags;
- u16 value;
-
- /* start stop register shared by multiple timer channels */
- spin_lock_irqsave(&r_tpu_lock, flags);
- value = r_tpu_read(p, TSTR);
-
- if (start)
- value |= 1 << cfg->timer_bit;
- else
- value &= ~(1 << cfg->timer_bit);
-
- r_tpu_write(p, TSTR, value);
- spin_unlock_irqrestore(&r_tpu_lock, flags);
-}
-
-static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness)
-{
- struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
- int prescaler[] = { 1, 4, 16, 64 };
- int k, ret;
- unsigned long rate, tmp;
-
- if (p->timer_state == R_TPU_TIMER_ON)
- return 0;
-
- /* wake up device and enable clock */
- pm_runtime_get_sync(&p->pdev->dev);
- ret = clk_enable(p->clk);
- if (ret) {
- dev_err(&p->pdev->dev, "cannot enable clock\n");
- return ret;
- }
-
- /* make sure channel is disabled */
- r_tpu_start_stop_ch(p, 0);
-
- /* get clock rate after enabling it */
- rate = clk_get_rate(p->clk);
-
- /* pick the lowest acceptable rate */
- for (k = ARRAY_SIZE(prescaler) - 1; k >= 0; k--)
- if ((rate / prescaler[k]) >= p->min_rate)
- break;
-
- if (k < 0) {
- dev_err(&p->pdev->dev, "clock rate mismatch\n");
- goto err0;
- }
- dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n",
- rate, prescaler[k]);
-
- /* clear TCNT on TGRB match, count on rising edge, set prescaler */
- r_tpu_write(p, TCR, 0x0040 | k);
-
- /* output 0 until TGRA, output 1 until TGRB */
- r_tpu_write(p, TIOR, 0x0002);
-
- rate /= prescaler[k] * p->refresh_rate;
- r_tpu_write(p, TGRB, rate);
- dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate);
-
- tmp = (cfg->max_brightness - brightness) * rate;
- r_tpu_write(p, TGRA, tmp / cfg->max_brightness);
- dev_dbg(&p->pdev->dev, "TRGA = 0x%04lx\n", tmp / cfg->max_brightness);
-
- /* PWM mode */
- r_tpu_write(p, TMDR, 0x0002);
-
- /* enable channel */
- r_tpu_start_stop_ch(p, 1);
-
- p->timer_state = R_TPU_TIMER_ON;
- return 0;
- err0:
- clk_disable(p->clk);
- pm_runtime_put_sync(&p->pdev->dev);
- return -ENOTSUPP;
-}
-
-static void r_tpu_disable(struct r_tpu_priv *p)
-{
- if (p->timer_state == R_TPU_TIMER_UNUSED)
- return;
-
- /* disable channel */
- r_tpu_start_stop_ch(p, 0);
-
- /* stop clock and mark device as idle */
- clk_disable(p->clk);
- pm_runtime_put_sync(&p->pdev->dev);
-
- p->timer_state = R_TPU_TIMER_UNUSED;
-}
-
-static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state,
- enum led_brightness brightness)
-{
- struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data;
-
- if (p->pin_state == new_state) {
- if (p->pin_state == R_TPU_PIN_GPIO)
- gpio_set_value(cfg->pin_gpio, brightness);
- return;
- }
-
- if (p->pin_state == R_TPU_PIN_GPIO)
- gpio_free(cfg->pin_gpio);
-
- if (p->pin_state == R_TPU_PIN_GPIO_FN)
- gpio_free(cfg->pin_gpio_fn);
-
- if (new_state == R_TPU_PIN_GPIO)
- gpio_request_one(cfg->pin_gpio, !!brightness ?
- GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
- cfg->name);
-
- if (new_state == R_TPU_PIN_GPIO_FN)
- gpio_request(cfg->pin_gpio_fn, cfg->name);
-
- p->pin_state = new_state;
-}
-
-static void r_tpu_work(struct work_struct *work)
-{
- struct r_tpu_priv *p = container_of(work, struct r_tpu_priv, work);
- enum led_brightness brightness = p->new_brightness;
-
- r_tpu_disable(p);
-
- /* off and maximum are handled as GPIO pins, in between PWM */
- if ((brightness == 0) || (brightness == p->ldev.max_brightness))
- r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness);
- else {
- r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0);
- r_tpu_enable(p, brightness);
- }
-}
-
-static void r_tpu_set_brightness(struct led_classdev *ldev,
- enum led_brightness brightness)
-{
- struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev);
- p->new_brightness = brightness;
- schedule_work(&p->work);
-}
-
-static int r_tpu_probe(struct platform_device *pdev)
-{
- struct led_renesas_tpu_config *cfg = pdev->dev.platform_data;
- struct r_tpu_priv *p;
- struct resource *res;
- int ret;
-
- if (!cfg) {
- dev_err(&pdev->dev, "missing platform data\n");
- return -ENODEV;
- }
-
- p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
- if (p == NULL) {
- dev_err(&pdev->dev, "failed to allocate driver data\n");
- return -ENOMEM;
- }
-
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
- if (!res) {
- dev_err(&pdev->dev, "failed to get I/O memory\n");
- return -ENXIO;
- }
-
- /* map memory, let mapbase point to our channel */
- p->mapbase = devm_ioremap_nocache(&pdev->dev, res->start,
- resource_size(res));
- if (p->mapbase == NULL) {
- dev_err(&pdev->dev, "failed to remap I/O memory\n");
- return -ENXIO;
- }
-
- /* get hold of clock */
- p->clk = devm_clk_get(&pdev->dev, NULL);
- if (IS_ERR(p->clk)) {
- dev_err(&pdev->dev, "cannot get clock\n");
- return PTR_ERR(p->clk);
- }
-
- p->pdev = pdev;
- p->pin_state = R_TPU_PIN_UNUSED;
- p->timer_state = R_TPU_TIMER_UNUSED;
- p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100;
- r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF);
- platform_set_drvdata(pdev, p);
-
- INIT_WORK(&p->work, r_tpu_work);
-
- p->ldev.name = cfg->name;
- p->ldev.brightness = LED_OFF;
- p->ldev.max_brightness = cfg->max_brightness;
- p->ldev.brightness_set = r_tpu_set_brightness;
- p->ldev.flags |= LED_CORE_SUSPENDRESUME;
- ret = led_classdev_register(&pdev->dev, &p->ldev);
- if (ret < 0)
- goto err0;
-
- /* max_brightness may be updated by the LED core code */
- p->min_rate = p->ldev.max_brightness * p->refresh_rate;
-
- pm_runtime_enable(&pdev->dev);
- return 0;
-
- err0:
- r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF);
- return ret;
-}
-
-static int r_tpu_remove(struct platform_device *pdev)
-{
- struct r_tpu_priv *p = platform_get_drvdata(pdev);
-
- r_tpu_set_brightness(&p->ldev, LED_OFF);
- led_classdev_unregister(&p->ldev);
- cancel_work_sync(&p->work);
- r_tpu_disable(p);
- r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF);
-
- pm_runtime_disable(&pdev->dev);
-
- return 0;
-}
-
-static struct platform_driver r_tpu_device_driver = {
- .probe = r_tpu_probe,
- .remove = r_tpu_remove,
- .driver = {
- .name = "leds-renesas-tpu",
- }
-};
-
-module_platform_driver(r_tpu_device_driver);
-
-MODULE_AUTHOR("Magnus Damm");
-MODULE_DESCRIPTION("Renesas TPU LED Driver");
-MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c
deleted file mode 100644
index e1a0df63a37f..000000000000
--- a/drivers/leds/leds-s3c24xx.c
+++ /dev/null
@@ -1,131 +0,0 @@
-/* drivers/leds/leds-s3c24xx.c
- *
- * (c) 2006 Simtec Electronics
- * http://armlinux.simtec.co.uk/
- * Ben Dooks <ben@simtec.co.uk>
- *
- * S3C24XX - LEDs GPIO driver
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
-*/
-
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/platform_device.h>
-#include <linux/leds.h>
-#include <linux/gpio.h>
-#include <linux/slab.h>
-#include <linux/module.h>
-
-#include <mach/hardware.h>
-#include <mach/regs-gpio.h>
-#include <linux/platform_data/leds-s3c24xx.h>
-
-/* our context */
-
-struct s3c24xx_gpio_led {
- struct led_classdev cdev;
- struct s3c24xx_led_platdata *pdata;
-};
-
-static inline struct s3c24xx_gpio_led *pdev_to_gpio(struct platform_device *dev)
-{
- return platform_get_drvdata(dev);
-}
-
-static inline struct s3c24xx_gpio_led *to_gpio(struct led_classdev *led_cdev)
-{
- return container_of(led_cdev, struct s3c24xx_gpio_led, cdev);
-}
-
-static void s3c24xx_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct s3c24xx_gpio_led *led = to_gpio(led_cdev);
- struct s3c24xx_led_platdata *pd = led->pdata;
- int state = (value ? 1 : 0) ^ (pd->flags & S3C24XX_LEDF_ACTLOW);
-
- /* there will be a short delay between setting the output and
- * going from output to input when using tristate. */
-
- gpio_set_value(pd->gpio, state);
-
- if (pd->flags & S3C24XX_LEDF_TRISTATE) {
- if (value)
- gpio_direction_output(pd->gpio, state);
- else
- gpio_direction_input(pd->gpio);
- }
-}
-
-static int s3c24xx_led_remove(struct platform_device *dev)
-{
- struct s3c24xx_gpio_led *led = pdev_to_gpio(dev);
-
- led_classdev_unregister(&led->cdev);
-
- return 0;
-}
-
-static int s3c24xx_led_probe(struct platform_device *dev)
-{
- struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
- struct s3c24xx_gpio_led *led;
- int ret;
-
- led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
- GFP_KERNEL);
- if (led == NULL) {
- dev_err(&dev->dev, "No memory for device\n");
- return -ENOMEM;
- }
-
- platform_set_drvdata(dev, led);
-
- led->cdev.brightness_set = s3c24xx_led_set;
- led->cdev.default_trigger = pdata->def_trigger;
- led->cdev.name = pdata->name;
- led->cdev.flags |= LED_CORE_SUSPENDRESUME;
-
- led->pdata = pdata;
-
- ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
- if (ret < 0)
- return ret;
-
- /* no point in having a pull-up if we are always driving */
-
- s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
-
- if (pdata->flags & S3C24XX_LEDF_TRISTATE)
- gpio_direction_input(pdata->gpio);
- else
- gpio_direction_output(pdata->gpio,
- pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0);
-
- /* register our new led device */
-
- ret = led_classdev_register(&dev->dev, &led->cdev);
- if (ret < 0)
- dev_err(&dev->dev, "led_classdev_register failed\n");
-
- return ret;
-}
-
-static struct platform_driver s3c24xx_led_driver = {
- .probe = s3c24xx_led_probe,
- .remove = s3c24xx_led_remove,
- .driver = {
- .name = "s3c24xx_led",
- .owner = THIS_MODULE,
- },
-};
-
-module_platform_driver(s3c24xx_led_driver);
-
-MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
-MODULE_DESCRIPTION("S3C24XX LED driver");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:s3c24xx_led");
diff --git a/drivers/leds/leds-sc27xx-bltc.c b/drivers/leds/leds-sc27xx-bltc.c
new file mode 100644
index 000000000000..0c5169773949
--- /dev/null
+++ b/drivers/leds/leds-sc27xx-bltc.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Spreadtrum Communications Inc.
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+/* PMIC global control register definition */
+#define SC27XX_MODULE_EN0 0xc08
+#define SC27XX_CLK_EN0 0xc18
+#define SC27XX_RGB_CTRL 0xebc
+
+#define SC27XX_BLTC_EN BIT(9)
+#define SC27XX_RTC_EN BIT(7)
+#define SC27XX_RGB_PD BIT(0)
+
+/* Breathing light controller register definition */
+#define SC27XX_LEDS_CTRL 0x00
+#define SC27XX_LEDS_PRESCALE 0x04
+#define SC27XX_LEDS_DUTY 0x08
+#define SC27XX_LEDS_CURVE0 0x0c
+#define SC27XX_LEDS_CURVE1 0x10
+
+#define SC27XX_CTRL_SHIFT 4
+#define SC27XX_LED_RUN BIT(0)
+#define SC27XX_LED_TYPE BIT(1)
+
+#define SC27XX_DUTY_SHIFT 8
+#define SC27XX_DUTY_MASK GENMASK(15, 0)
+#define SC27XX_MOD_MASK GENMASK(7, 0)
+
+#define SC27XX_CURVE_SHIFT 8
+#define SC27XX_CURVE_L_MASK GENMASK(7, 0)
+#define SC27XX_CURVE_H_MASK GENMASK(15, 8)
+
+#define SC27XX_LEDS_OFFSET 0x10
+#define SC27XX_LEDS_MAX 3
+#define SC27XX_LEDS_PATTERN_CNT 4
+/* Stage duration step, in milliseconds */
+#define SC27XX_LEDS_STEP 125
+/* Minimum and maximum duration, in milliseconds */
+#define SC27XX_DELTA_T_MIN SC27XX_LEDS_STEP
+#define SC27XX_DELTA_T_MAX (SC27XX_LEDS_STEP * 255)
+
+struct sc27xx_led {
+ struct fwnode_handle *fwnode;
+ struct led_classdev ldev;
+ struct sc27xx_led_priv *priv;
+ u8 line;
+ bool active;
+};
+
+struct sc27xx_led_priv {
+ struct sc27xx_led leds[SC27XX_LEDS_MAX];
+ struct regmap *regmap;
+ struct mutex lock;
+ u32 base;
+};
+
+#define to_sc27xx_led(ldev) \
+ container_of(ldev, struct sc27xx_led, ldev)
+
+static int sc27xx_led_init(struct regmap *regmap)
+{
+ int err;
+
+ err = regmap_update_bits(regmap, SC27XX_MODULE_EN0, SC27XX_BLTC_EN,
+ SC27XX_BLTC_EN);
+ if (err)
+ return err;
+
+ err = regmap_update_bits(regmap, SC27XX_CLK_EN0, SC27XX_RTC_EN,
+ SC27XX_RTC_EN);
+ if (err)
+ return err;
+
+ return regmap_update_bits(regmap, SC27XX_RGB_CTRL, SC27XX_RGB_PD, 0);
+}
+
+static u32 sc27xx_led_get_offset(struct sc27xx_led *leds)
+{
+ return leds->priv->base + SC27XX_LEDS_OFFSET * leds->line;
+}
+
+static int sc27xx_led_enable(struct sc27xx_led *leds, enum led_brightness value)
+{
+ u32 base = sc27xx_led_get_offset(leds);
+ u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
+ u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
+ struct regmap *regmap = leds->priv->regmap;
+ int err;
+
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,
+ SC27XX_DUTY_MASK,
+ (value << SC27XX_DUTY_SHIFT) |
+ SC27XX_MOD_MASK);
+ if (err)
+ return err;
+
+ return regmap_update_bits(regmap, ctrl_base,
+ (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift,
+ (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift);
+}
+
+static int sc27xx_led_disable(struct sc27xx_led *leds)
+{
+ struct regmap *regmap = leds->priv->regmap;
+ u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
+ u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
+
+ return regmap_update_bits(regmap, ctrl_base,
+ (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);
+}
+
+static int sc27xx_led_set(struct led_classdev *ldev, enum led_brightness value)
+{
+ struct sc27xx_led *leds = to_sc27xx_led(ldev);
+ int err;
+
+ mutex_lock(&leds->priv->lock);
+
+ if (value == LED_OFF)
+ err = sc27xx_led_disable(leds);
+ else
+ err = sc27xx_led_enable(leds, value);
+
+ mutex_unlock(&leds->priv->lock);
+
+ return err;
+}
+
+static void sc27xx_led_clamp_align_delta_t(u32 *delta_t)
+{
+ u32 v, offset, t = *delta_t;
+
+ v = t + SC27XX_LEDS_STEP / 2;
+ v = clamp_t(u32, v, SC27XX_DELTA_T_MIN, SC27XX_DELTA_T_MAX);
+ offset = v - SC27XX_DELTA_T_MIN;
+ offset = SC27XX_LEDS_STEP * (offset / SC27XX_LEDS_STEP);
+
+ *delta_t = SC27XX_DELTA_T_MIN + offset;
+}
+
+static int sc27xx_led_pattern_clear(struct led_classdev *ldev)
+{
+ struct sc27xx_led *leds = to_sc27xx_led(ldev);
+ struct regmap *regmap = leds->priv->regmap;
+ u32 base = sc27xx_led_get_offset(leds);
+ u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
+ u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
+ int err;
+
+ mutex_lock(&leds->priv->lock);
+
+ /* Reset the rise, high, fall and low time to zero. */
+ regmap_write(regmap, base + SC27XX_LEDS_CURVE0, 0);
+ regmap_write(regmap, base + SC27XX_LEDS_CURVE1, 0);
+
+ err = regmap_update_bits(regmap, ctrl_base,
+ (SC27XX_LED_RUN | SC27XX_LED_TYPE) << ctrl_shift, 0);
+
+ ldev->brightness = LED_OFF;
+
+ mutex_unlock(&leds->priv->lock);
+
+ return err;
+}
+
+static int sc27xx_led_pattern_set(struct led_classdev *ldev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct sc27xx_led *leds = to_sc27xx_led(ldev);
+ u32 base = sc27xx_led_get_offset(leds);
+ u32 ctrl_base = leds->priv->base + SC27XX_LEDS_CTRL;
+ u8 ctrl_shift = SC27XX_CTRL_SHIFT * leds->line;
+ struct regmap *regmap = leds->priv->regmap;
+ int err;
+
+ /*
+ * Must contain 4 tuples to configure the rise time, high time, fall
+ * time and low time to enable the breathing mode.
+ */
+ if (len != SC27XX_LEDS_PATTERN_CNT)
+ return -EINVAL;
+
+ mutex_lock(&leds->priv->lock);
+
+ sc27xx_led_clamp_align_delta_t(&pattern[0].delta_t);
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
+ SC27XX_CURVE_L_MASK,
+ pattern[0].delta_t / SC27XX_LEDS_STEP);
+ if (err)
+ goto out;
+
+ sc27xx_led_clamp_align_delta_t(&pattern[1].delta_t);
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
+ SC27XX_CURVE_L_MASK,
+ pattern[1].delta_t / SC27XX_LEDS_STEP);
+ if (err)
+ goto out;
+
+ sc27xx_led_clamp_align_delta_t(&pattern[2].delta_t);
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE0,
+ SC27XX_CURVE_H_MASK,
+ (pattern[2].delta_t / SC27XX_LEDS_STEP) <<
+ SC27XX_CURVE_SHIFT);
+ if (err)
+ goto out;
+
+ sc27xx_led_clamp_align_delta_t(&pattern[3].delta_t);
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_CURVE1,
+ SC27XX_CURVE_H_MASK,
+ (pattern[3].delta_t / SC27XX_LEDS_STEP) <<
+ SC27XX_CURVE_SHIFT);
+ if (err)
+ goto out;
+
+ err = regmap_update_bits(regmap, base + SC27XX_LEDS_DUTY,
+ SC27XX_DUTY_MASK,
+ (pattern[1].brightness << SC27XX_DUTY_SHIFT) |
+ SC27XX_MOD_MASK);
+ if (err)
+ goto out;
+
+ /* Enable the LED breathing mode */
+ err = regmap_update_bits(regmap, ctrl_base,
+ SC27XX_LED_RUN << ctrl_shift,
+ SC27XX_LED_RUN << ctrl_shift);
+ if (!err)
+ ldev->brightness = pattern[1].brightness;
+
+out:
+ mutex_unlock(&leds->priv->lock);
+
+ return err;
+}
+
+static int sc27xx_led_register(struct device *dev, struct sc27xx_led_priv *priv)
+{
+ int i, err;
+
+ err = sc27xx_led_init(priv->regmap);
+ if (err)
+ return err;
+
+ for (i = 0; i < SC27XX_LEDS_MAX; i++) {
+ struct sc27xx_led *led = &priv->leds[i];
+ struct led_init_data init_data = {};
+
+ if (!led->active)
+ continue;
+
+ led->line = i;
+ led->priv = priv;
+ led->ldev.brightness_set_blocking = sc27xx_led_set;
+ led->ldev.pattern_set = sc27xx_led_pattern_set;
+ led->ldev.pattern_clear = sc27xx_led_pattern_clear;
+ led->ldev.default_trigger = "pattern";
+
+ init_data.fwnode = led->fwnode;
+ init_data.devicename = "sc27xx";
+ init_data.default_label = ":";
+
+ err = devm_led_classdev_register_ext(dev, &led->ldev,
+ &init_data);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int sc27xx_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct sc27xx_led_priv *priv;
+ u32 base, count, reg;
+ int err;
+
+ count = of_get_available_child_count(np);
+ if (!count || count > SC27XX_LEDS_MAX)
+ return -EINVAL;
+
+ err = of_property_read_u32(np, "reg", &base);
+ if (err) {
+ dev_err(dev, "fail to get reg of property\n");
+ return err;
+ }
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, priv);
+ priv->base = base;
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap) {
+ err = -ENODEV;
+ dev_err(dev, "failed to get regmap: %d\n", err);
+ return err;
+ }
+
+ for_each_available_child_of_node_scoped(np, child) {
+ err = of_property_read_u32(child, "reg", &reg);
+ if (err)
+ return err;
+
+ if (reg >= SC27XX_LEDS_MAX || priv->leds[reg].active)
+ return -EINVAL;
+
+ priv->leds[reg].fwnode = of_fwnode_handle(child);
+ priv->leds[reg].active = true;
+ }
+
+ mutex_init(&priv->lock);
+
+ err = sc27xx_led_register(dev, priv);
+ if (err)
+ mutex_destroy(&priv->lock);
+
+ return err;
+}
+
+static void sc27xx_led_remove(struct platform_device *pdev)
+{
+ struct sc27xx_led_priv *priv = platform_get_drvdata(pdev);
+
+ mutex_destroy(&priv->lock);
+}
+
+static const struct of_device_id sc27xx_led_of_match[] = {
+ { .compatible = "sprd,sc2731-bltc", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sc27xx_led_of_match);
+
+static struct platform_driver sc27xx_led_driver = {
+ .driver = {
+ .name = "sprd-bltc",
+ .of_match_table = sc27xx_led_of_match,
+ },
+ .probe = sc27xx_led_probe,
+ .remove = sc27xx_led_remove,
+};
+
+module_platform_driver(sc27xx_led_driver);
+
+MODULE_DESCRIPTION("Spreadtrum SC27xx breathing light controller driver");
+MODULE_AUTHOR("Xiaotong Lu <xiaotong.lu@spreadtrum.com>");
+MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-spi-byte.c b/drivers/leds/leds-spi-byte.c
new file mode 100644
index 000000000000..d24d0ddf347c
--- /dev/null
+++ b/drivers/leds/leds-spi-byte.c
@@ -0,0 +1,136 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2019 Christian Mauderer <oss@c-mauderer.de>
+
+/*
+ * The driver supports controllers with a very simple SPI protocol:
+ * - one LED is controlled by a single byte on MOSI
+ * - the value of the byte gives the brightness between two values (lowest to
+ * highest)
+ * - no return value is necessary (no MISO signal)
+ *
+ * The value for minimum and maximum brightness depends on the device
+ * (compatible string).
+ *
+ * Supported devices:
+ * - "ubnt,acb-spi-led": Microcontroller (SONiX 8F26E611LA) based device used
+ * for example in Ubiquiti airCube ISP. Reverse engineered protocol for this
+ * controller:
+ * * Higher two bits set a mode. Lower six bits are a parameter.
+ * * Mode: 00 -> set brightness between 0x00 (min) and 0x3F (max)
+ * * Mode: 01 -> pulsing pattern (min -> max -> min) with an interval. From
+ * some tests, the period is about (50ms + 102ms * parameter). There is a
+ * slightly different pattern starting from 0x10 (longer gap between the
+ * pulses) but the time still follows that calculation.
+ * * Mode: 10 -> same as 01 but with only a ramp from min to max. Again a
+ * slight jump in the pattern at 0x10.
+ * * Mode: 11 -> blinking (off -> 25% -> off -> 25% -> ...) with a period of
+ * (105ms * parameter)
+ * NOTE: This driver currently only supports mode 00.
+ */
+
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/property.h>
+#include <linux/spi/spi.h>
+#include <uapi/linux/uleds.h>
+
+struct spi_byte_chipdef {
+ /* SPI byte that will be send to switch the LED off */
+ u8 off_value;
+ /* SPI byte that will be send to switch the LED to maximum brightness */
+ u8 max_value;
+};
+
+struct spi_byte_led {
+ struct led_classdev ldev;
+ struct spi_device *spi;
+ char name[LED_MAX_NAME_SIZE];
+ struct mutex mutex;
+ const struct spi_byte_chipdef *cdef;
+};
+
+static const struct spi_byte_chipdef ubnt_acb_spi_led_cdef = {
+ .off_value = 0x0,
+ .max_value = 0x3F,
+};
+
+static int spi_byte_brightness_set_blocking(struct led_classdev *dev,
+ enum led_brightness brightness)
+{
+ struct spi_byte_led *led = container_of(dev, struct spi_byte_led, ldev);
+ u8 value;
+ int ret;
+
+ value = (u8) brightness + led->cdef->off_value;
+
+ mutex_lock(&led->mutex);
+ ret = spi_write(led->spi, &value, sizeof(value));
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+static int spi_byte_probe(struct spi_device *spi)
+{
+ struct fwnode_handle *child __free(fwnode_handle) = NULL;
+ struct device *dev = &spi->dev;
+ struct spi_byte_led *led;
+ struct led_init_data init_data = {};
+ enum led_default_state state;
+ int ret;
+
+ if (device_get_child_node_count(dev) != 1) {
+ dev_err(dev, "Device must have exactly one LED sub-node.");
+ return -EINVAL;
+ }
+
+ 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;
+ 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;
+
+ 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 = child;
+ init_data.devicename = "leds-spi-byte";
+ init_data.default_label = ":";
+
+ return devm_led_classdev_register_ext(dev, &led->ldev, &init_data);
+}
+
+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,
+ .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>");
+MODULE_DESCRIPTION("single byte SPI LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("spi:leds-spi-byte");
diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c
index 64e204e714f6..f24ca75c7cb1 100644
--- a/drivers/leds/leds-ss4200.c
+++ b/drivers/leds/leds-ss4200.c
@@ -1,21 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* SS4200-E Hardware API
* Copyright (c) 2009, Intel Corporation.
* Copyright IBM Corporation, 2009
*
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
- *
- * This program is distributed in the hope it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
- * more details.
- *
- * You should have received a copy of the GNU General Public License along with
- * this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
- *
* Author: Dave Hansen <dave@sr71.net>
*/
@@ -63,7 +51,7 @@ MODULE_LICENSE("GPL");
/*
* PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives.
*/
-static DEFINE_PCI_DEVICE_TABLE(ich7_lpc_pci_id) = {
+static const struct pci_device_id ich7_lpc_pci_id[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) },
{ PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) },
@@ -78,7 +66,7 @@ static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id)
return 1;
}
-static bool __initdata nodetect;
+static bool nodetect;
module_param_named(nodetect, nodetect, bool, 0);
MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection");
@@ -91,7 +79,7 @@ MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection");
* detected as working, but in reality it is not) as low as
* possible.
*/
-static struct dmi_system_id __initdata nas_led_whitelist[] = {
+static const struct dmi_system_id nas_led_whitelist[] __initconst = {
{
.callback = ss4200_led_dmi_callback,
.ident = "Intel SS4200-E",
@@ -101,6 +89,19 @@ static struct dmi_system_id __initdata nas_led_whitelist[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00")
}
},
+ {
+ /*
+ * FUJITSU SIEMENS SCALEO Home Server/SS4200-E
+ * BIOS V090L 12/19/2007
+ */
+ .callback = ss4200_led_dmi_callback,
+ .ident = "Fujitsu Siemens SCALEO Home Server",
+ .matches = {
+ DMI_MATCH(DMI_SYS_VENDOR, "FUJITSU SIEMENS"),
+ DMI_MATCH(DMI_PRODUCT_NAME, "SCALEO Home Server"),
+ DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00")
+ }
+ },
{}
};
@@ -197,7 +198,7 @@ static void nasgpio_led_set_attr(struct led_classdev *led_cdev,
spin_unlock(&nasgpio_gpio_lock);
}
-u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port)
+static u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port)
{
struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev);
u32 gpio_in;
@@ -355,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);
@@ -368,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);
@@ -440,19 +444,19 @@ static void set_power_light_amber_noblink(void)
nasgpio_led_set_brightness(&amber->led_cdev, LED_FULL);
}
-static ssize_t nas_led_blink_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t blink_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct led_classdev *led = dev_get_drvdata(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 nas_led_blink_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t blink_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
int ret;
struct led_classdev *led = dev_get_drvdata(dev);
@@ -467,11 +471,16 @@ static ssize_t nas_led_blink_store(struct device *dev,
return size;
}
-static DEVICE_ATTR(blink, 0644, nas_led_blink_show, nas_led_blink_store);
+static DEVICE_ATTR_RW(blink);
+
+static struct attribute *nasgpio_led_attrs[] = {
+ &dev_attr_blink.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(nasgpio_led);
static int register_nasgpio_led(int led_nr)
{
- int ret;
struct nasgpio_led *nas_led = &nasgpio_leds[led_nr];
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
@@ -481,20 +490,15 @@ static int register_nasgpio_led(int led_nr)
led->brightness = LED_FULL;
led->brightness_set = nasgpio_led_set_brightness;
led->blink_set = nasgpio_led_set_blink;
- ret = led_classdev_register(&nas_gpio_pci_dev->dev, led);
- if (ret)
- return ret;
- ret = device_create_file(led->dev, &dev_attr_blink);
- if (ret)
- led_classdev_unregister(led);
- return ret;
+ led->groups = nasgpio_led_groups;
+
+ return led_classdev_register(&nas_gpio_pci_dev->dev, led);
}
static void unregister_nasgpio_led(int led_nr)
{
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
led_classdev_unregister(led);
- device_remove_file(led->dev, &dev_attr_blink);
}
/*
* module load/initialization
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
new file mode 100644
index 000000000000..2c9bd360ab81
--- /dev/null
+++ b/drivers/leds/leds-sun50i-a100.c
@@ -0,0 +1,573 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2021-2023 Samuel Holland <samuel@sholland.org>
+ *
+ * Partly based on drivers/leds/leds-turris-omnia.c, which is:
+ * Copyright (c) 2020 by Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm.h>
+#include <linux/property.h>
+#include <linux/reset.h>
+#include <linux/spinlock.h>
+
+#define LEDC_CTRL_REG 0x0000
+#define LEDC_CTRL_REG_DATA_LENGTH GENMASK(28, 16)
+#define LEDC_CTRL_REG_RGB_MODE GENMASK(8, 6)
+#define LEDC_CTRL_REG_LEDC_EN BIT(0)
+#define LEDC_T01_TIMING_CTRL_REG 0x0004
+#define LEDC_T01_TIMING_CTRL_REG_T1H GENMASK(26, 21)
+#define LEDC_T01_TIMING_CTRL_REG_T1L GENMASK(20, 16)
+#define LEDC_T01_TIMING_CTRL_REG_T0H GENMASK(10, 6)
+#define LEDC_T01_TIMING_CTRL_REG_T0L GENMASK(5, 0)
+#define LEDC_RESET_TIMING_CTRL_REG 0x000c
+#define LEDC_RESET_TIMING_CTRL_REG_TR GENMASK(28, 16)
+#define LEDC_RESET_TIMING_CTRL_REG_LED_NUM GENMASK(9, 0)
+#define LEDC_DATA_REG 0x0014
+#define LEDC_DMA_CTRL_REG 0x0018
+#define LEDC_DMA_CTRL_REG_DMA_EN BIT(5)
+#define LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL GENMASK(4, 0)
+#define LEDC_INT_CTRL_REG 0x001c
+#define LEDC_INT_CTRL_REG_GLOBAL_INT_EN BIT(5)
+#define LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN BIT(1)
+#define LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN BIT(0)
+#define LEDC_INT_STS_REG 0x0020
+#define LEDC_INT_STS_REG_FIFO_WLW GENMASK(15, 10)
+#define LEDC_INT_STS_REG_FIFO_CPUREQ_INT BIT(1)
+#define LEDC_INT_STS_REG_TRANS_FINISH_INT BIT(0)
+
+#define LEDC_FIFO_DEPTH 32U
+#define LEDC_MAX_LEDS 1024
+#define LEDC_CHANNELS_PER_LED 3 /* RGB */
+
+#define LEDS_TO_BYTES(n) ((n) * sizeof(u32))
+
+struct sun50i_a100_ledc_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[LEDC_CHANNELS_PER_LED];
+ u32 addr;
+};
+
+#define to_ledc_led(mc) container_of(mc, struct sun50i_a100_ledc_led, mc_cdev)
+
+struct sun50i_a100_ledc_timing {
+ u32 t0h_ns;
+ u32 t0l_ns;
+ u32 t1h_ns;
+ u32 t1l_ns;
+ u32 treset_ns;
+};
+
+struct sun50i_a100_ledc {
+ struct device *dev;
+ void __iomem *base;
+ struct clk *bus_clk;
+ struct clk *mod_clk;
+ struct reset_control *reset;
+
+ u32 *buffer;
+ struct dma_chan *dma_chan;
+ dma_addr_t dma_handle;
+ unsigned int pio_length;
+ unsigned int pio_offset;
+
+ spinlock_t lock;
+ unsigned int next_length;
+ bool xfer_active;
+
+ u32 format;
+ struct sun50i_a100_ledc_timing timing;
+
+ u32 max_addr;
+ u32 num_leds;
+ struct sun50i_a100_ledc_led leds[] __counted_by(num_leds);
+};
+
+static int sun50i_a100_ledc_dma_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
+{
+ struct dma_async_tx_descriptor *desc;
+ dma_cookie_t cookie;
+
+ desc = dmaengine_prep_slave_single(priv->dma_chan, priv->dma_handle,
+ LEDS_TO_BYTES(length), DMA_MEM_TO_DEV, 0);
+ if (!desc)
+ return -ENOMEM;
+
+ cookie = dmaengine_submit(desc);
+ if (dma_submit_error(cookie))
+ return -EIO;
+
+ dma_async_issue_pending(priv->dma_chan);
+
+ return 0;
+}
+
+static void sun50i_a100_ledc_pio_xfer(struct sun50i_a100_ledc *priv, unsigned int fifo_used)
+{
+ unsigned int burst, length, offset;
+ u32 control;
+
+ length = priv->pio_length;
+ offset = priv->pio_offset;
+ burst = min(length, LEDC_FIFO_DEPTH - fifo_used);
+
+ iowrite32_rep(priv->base + LEDC_DATA_REG, priv->buffer + offset, burst);
+
+ if (burst < length) {
+ priv->pio_length = length - burst;
+ priv->pio_offset = offset + burst;
+
+ if (!offset) {
+ control = readl(priv->base + LEDC_INT_CTRL_REG);
+ control |= LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
+ writel(control, priv->base + LEDC_INT_CTRL_REG);
+ }
+ } else {
+ /* Disable the request IRQ once all data is written. */
+ control = readl(priv->base + LEDC_INT_CTRL_REG);
+ control &= ~LEDC_INT_CTRL_REG_FIFO_CPUREQ_INT_EN;
+ writel(control, priv->base + LEDC_INT_CTRL_REG);
+ }
+}
+
+static void sun50i_a100_ledc_start_xfer(struct sun50i_a100_ledc *priv, unsigned int length)
+{
+ bool use_dma = false;
+ u32 control;
+
+ if (priv->dma_chan && length > LEDC_FIFO_DEPTH) {
+ int ret;
+
+ ret = sun50i_a100_ledc_dma_xfer(priv, length);
+ if (ret)
+ dev_warn(priv->dev, "Failed to set up DMA (%d), using PIO\n", ret);
+ else
+ use_dma = true;
+ }
+
+ /* The DMA trigger level must be at least the burst length. */
+ control = FIELD_PREP(LEDC_DMA_CTRL_REG_DMA_EN, use_dma) |
+ FIELD_PREP_CONST(LEDC_DMA_CTRL_REG_FIFO_TRIG_LEVEL, LEDC_FIFO_DEPTH / 2);
+ writel(control, priv->base + LEDC_DMA_CTRL_REG);
+
+ control = readl(priv->base + LEDC_CTRL_REG);
+ control &= ~LEDC_CTRL_REG_DATA_LENGTH;
+ control |= FIELD_PREP(LEDC_CTRL_REG_DATA_LENGTH, length) | LEDC_CTRL_REG_LEDC_EN;
+ writel(control, priv->base + LEDC_CTRL_REG);
+
+ if (!use_dma) {
+ /* The FIFO is empty when starting a new transfer. */
+ unsigned int fifo_used = 0;
+
+ priv->pio_length = length;
+ priv->pio_offset = 0;
+
+ sun50i_a100_ledc_pio_xfer(priv, fifo_used);
+ }
+}
+
+static irqreturn_t sun50i_a100_ledc_irq(int irq, void *data)
+{
+ struct sun50i_a100_ledc *priv = data;
+ u32 status;
+
+ status = readl(priv->base + LEDC_INT_STS_REG);
+
+ if (status & LEDC_INT_STS_REG_TRANS_FINISH_INT) {
+ unsigned int next_length;
+
+ spin_lock(&priv->lock);
+
+ /* If another transfer is queued, dequeue and start it. */
+ next_length = priv->next_length;
+ if (next_length)
+ priv->next_length = 0;
+ else
+ priv->xfer_active = false;
+
+ spin_unlock(&priv->lock);
+
+ if (next_length)
+ sun50i_a100_ledc_start_xfer(priv, next_length);
+ } else if (status & LEDC_INT_STS_REG_FIFO_CPUREQ_INT) {
+ /* Continue the current transfer. */
+ sun50i_a100_ledc_pio_xfer(priv, FIELD_GET(LEDC_INT_STS_REG_FIFO_WLW, status));
+ }
+
+ /* Clear the W1C status bits. */
+ writel(status, priv->base + LEDC_INT_STS_REG);
+
+ return IRQ_HANDLED;
+}
+
+static void sun50i_a100_ledc_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct sun50i_a100_ledc *priv = dev_get_drvdata(cdev->dev->parent);
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct sun50i_a100_ledc_led *led = to_ledc_led(mc_cdev);
+ unsigned int next_length;
+ unsigned long flags;
+ bool xfer_active;
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ priv->buffer[led->addr] = led->subled_info[0].brightness << 16 |
+ led->subled_info[1].brightness << 8 |
+ led->subled_info[2].brightness;
+
+ spin_lock_irqsave(&priv->lock, flags);
+
+ /* Start, enqueue, or extend an enqueued transfer, as appropriate. */
+ next_length = max(priv->next_length, led->addr + 1);
+ xfer_active = priv->xfer_active;
+ if (xfer_active)
+ priv->next_length = next_length;
+ else
+ priv->xfer_active = true;
+
+ spin_unlock_irqrestore(&priv->lock, flags);
+
+ if (!xfer_active)
+ sun50i_a100_ledc_start_xfer(priv, next_length);
+}
+
+static const char *const sun50i_a100_ledc_formats[] = {
+ "rgb", "rbg", "grb", "gbr", "brg", "bgr",
+};
+
+static int sun50i_a100_ledc_parse_format(struct device *dev,
+ struct sun50i_a100_ledc *priv)
+{
+ const char *format = "grb";
+ int i;
+
+ device_property_read_string(dev, "allwinner,pixel-format", &format);
+
+ i = match_string(sun50i_a100_ledc_formats, ARRAY_SIZE(sun50i_a100_ledc_formats), format);
+ if (i < 0)
+ return dev_err_probe(dev, i, "Bad pixel format '%s'\n", format);
+
+ priv->format = i;
+ return 0;
+}
+
+static void sun50i_a100_ledc_set_format(struct sun50i_a100_ledc *priv)
+{
+ u32 control;
+
+ control = readl(priv->base + LEDC_CTRL_REG);
+ control &= ~LEDC_CTRL_REG_RGB_MODE;
+ control |= FIELD_PREP(LEDC_CTRL_REG_RGB_MODE, priv->format);
+ writel(control, priv->base + LEDC_CTRL_REG);
+}
+
+static const struct sun50i_a100_ledc_timing sun50i_a100_ledc_default_timing = {
+ .t0h_ns = 336,
+ .t0l_ns = 840,
+ .t1h_ns = 882,
+ .t1l_ns = 294,
+ .treset_ns = 300000,
+};
+
+static int sun50i_a100_ledc_parse_timing(struct device *dev,
+ struct sun50i_a100_ledc *priv)
+{
+ struct sun50i_a100_ledc_timing *timing = &priv->timing;
+
+ *timing = sun50i_a100_ledc_default_timing;
+
+ device_property_read_u32(dev, "allwinner,t0h-ns", &timing->t0h_ns);
+ device_property_read_u32(dev, "allwinner,t0l-ns", &timing->t0l_ns);
+ device_property_read_u32(dev, "allwinner,t1h-ns", &timing->t1h_ns);
+ device_property_read_u32(dev, "allwinner,t1l-ns", &timing->t1l_ns);
+ device_property_read_u32(dev, "allwinner,treset-ns", &timing->treset_ns);
+
+ return 0;
+}
+
+static void sun50i_a100_ledc_set_timing(struct sun50i_a100_ledc *priv)
+{
+ const struct sun50i_a100_ledc_timing *timing = &priv->timing;
+ unsigned long mod_freq = clk_get_rate(priv->mod_clk);
+ u32 cycle_ns;
+ u32 control;
+
+ if (!mod_freq)
+ return;
+
+ cycle_ns = NSEC_PER_SEC / mod_freq;
+ control = FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1H, timing->t1h_ns / cycle_ns) |
+ FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T1L, timing->t1l_ns / cycle_ns) |
+ FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0H, timing->t0h_ns / cycle_ns) |
+ FIELD_PREP(LEDC_T01_TIMING_CTRL_REG_T0L, timing->t0l_ns / cycle_ns);
+ writel(control, priv->base + LEDC_T01_TIMING_CTRL_REG);
+
+ control = FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_TR, timing->treset_ns / cycle_ns) |
+ FIELD_PREP(LEDC_RESET_TIMING_CTRL_REG_LED_NUM, priv->max_addr);
+ writel(control, priv->base + LEDC_RESET_TIMING_CTRL_REG);
+}
+
+static int sun50i_a100_ledc_resume(struct device *dev)
+{
+ struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
+ int ret;
+
+ ret = reset_control_deassert(priv->reset);
+ if (ret)
+ return ret;
+
+ ret = clk_prepare_enable(priv->bus_clk);
+ if (ret)
+ goto err_assert_reset;
+
+ ret = clk_prepare_enable(priv->mod_clk);
+ if (ret)
+ goto err_disable_bus_clk;
+
+ sun50i_a100_ledc_set_format(priv);
+ sun50i_a100_ledc_set_timing(priv);
+
+ writel(LEDC_INT_CTRL_REG_GLOBAL_INT_EN | LEDC_INT_CTRL_REG_TRANS_FINISH_INT_EN,
+ priv->base + LEDC_INT_CTRL_REG);
+
+ return 0;
+
+err_disable_bus_clk:
+ clk_disable_unprepare(priv->bus_clk);
+err_assert_reset:
+ reset_control_assert(priv->reset);
+
+ return ret;
+}
+
+static int sun50i_a100_ledc_suspend(struct device *dev)
+{
+ struct sun50i_a100_ledc *priv = dev_get_drvdata(dev);
+
+ /* Wait for all transfers to complete. */
+ for (;;) {
+ unsigned long flags;
+ bool xfer_active;
+
+ spin_lock_irqsave(&priv->lock, flags);
+ xfer_active = priv->xfer_active;
+ spin_unlock_irqrestore(&priv->lock, flags);
+ if (!xfer_active)
+ break;
+
+ usleep_range(1000, 1100);
+ }
+
+ clk_disable_unprepare(priv->mod_clk);
+ clk_disable_unprepare(priv->bus_clk);
+ reset_control_assert(priv->reset);
+
+ return 0;
+}
+
+static void sun50i_a100_ledc_dma_cleanup(void *data)
+{
+ struct sun50i_a100_ledc *priv = data;
+
+ dma_release_channel(priv->dma_chan);
+}
+
+static int sun50i_a100_ledc_probe(struct platform_device *pdev)
+{
+ struct dma_slave_config dma_cfg = {};
+ struct led_init_data init_data = {};
+ struct sun50i_a100_ledc_led *led;
+ struct device *dev = &pdev->dev;
+ struct sun50i_a100_ledc *priv;
+ struct resource *mem;
+ u32 max_addr = 0;
+ u32 num_leds = 0;
+ int irq, ret;
+
+ /*
+ * The maximum LED address must be known in sun50i_a100_ledc_resume() before
+ * class device registration, so parse and validate the subnodes up front.
+ */
+ device_for_each_child_node_scoped(dev, child) {
+ u32 addr, color;
+
+ ret = fwnode_property_read_u32(child, "reg", &addr);
+ if (ret || addr >= LEDC_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL, "'reg' must be between 0 and %d\n",
+ LEDC_MAX_LEDS - 1);
+
+ ret = fwnode_property_read_u32(child, "color", &color);
+ if (ret || color != LED_COLOR_ID_RGB)
+ return dev_err_probe(dev, -EINVAL, "'color' must be LED_COLOR_ID_RGB\n");
+
+ max_addr = max(max_addr, addr);
+ num_leds++;
+ }
+
+ if (!num_leds)
+ return -ENODEV;
+
+ priv = devm_kzalloc(dev, struct_size(priv, leds, num_leds), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->dev = dev;
+ priv->max_addr = max_addr;
+ priv->num_leds = num_leds;
+ spin_lock_init(&priv->lock);
+ dev_set_drvdata(dev, priv);
+
+ ret = sun50i_a100_ledc_parse_format(dev, priv);
+ if (ret)
+ return ret;
+
+ ret = sun50i_a100_ledc_parse_timing(dev, priv);
+ if (ret)
+ return ret;
+
+ priv->base = devm_platform_get_and_ioremap_resource(pdev, 0, &mem);
+ if (IS_ERR(priv->base))
+ return PTR_ERR(priv->base);
+
+ priv->bus_clk = devm_clk_get(dev, "bus");
+ if (IS_ERR(priv->bus_clk))
+ return PTR_ERR(priv->bus_clk);
+
+ priv->mod_clk = devm_clk_get(dev, "mod");
+ if (IS_ERR(priv->mod_clk))
+ return PTR_ERR(priv->mod_clk);
+
+ priv->reset = devm_reset_control_get_exclusive(dev, NULL);
+ if (IS_ERR(priv->reset))
+ return PTR_ERR(priv->reset);
+
+ priv->dma_chan = dma_request_chan(dev, "tx");
+ if (IS_ERR(priv->dma_chan)) {
+ if (PTR_ERR(priv->dma_chan) != -ENODEV)
+ return PTR_ERR(priv->dma_chan);
+
+ priv->dma_chan = NULL;
+
+ priv->buffer = devm_kzalloc(dev, LEDS_TO_BYTES(LEDC_MAX_LEDS), GFP_KERNEL);
+ if (!priv->buffer)
+ return -ENOMEM;
+ } else {
+ ret = devm_add_action_or_reset(dev, sun50i_a100_ledc_dma_cleanup, priv);
+ if (ret)
+ return ret;
+
+ dma_cfg.dst_addr = mem->start + LEDC_DATA_REG;
+ dma_cfg.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+ dma_cfg.dst_maxburst = LEDC_FIFO_DEPTH / 2;
+
+ ret = dmaengine_slave_config(priv->dma_chan, &dma_cfg);
+ if (ret)
+ return ret;
+
+ priv->buffer = dmam_alloc_attrs(dmaengine_get_dma_device(priv->dma_chan),
+ LEDS_TO_BYTES(LEDC_MAX_LEDS), &priv->dma_handle,
+ GFP_KERNEL, DMA_ATTR_WRITE_COMBINE);
+ if (!priv->buffer)
+ return -ENOMEM;
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_irq(dev, irq, sun50i_a100_ledc_irq, 0, dev_name(dev), priv);
+ if (ret)
+ return ret;
+
+ ret = sun50i_a100_ledc_resume(dev);
+ if (ret)
+ return ret;
+
+ led = priv->leds;
+ device_for_each_child_node_scoped(dev, child) {
+ struct led_classdev *cdev;
+
+ /* The node was already validated above. */
+ fwnode_property_read_u32(child, "reg", &led->addr);
+
+ led->subled_info[0].color_index = LED_COLOR_ID_RED;
+ led->subled_info[0].channel = 0;
+ led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ led->subled_info[1].channel = 1;
+ led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+ led->subled_info[2].channel = 2;
+
+ led->mc_cdev.num_colors = ARRAY_SIZE(led->subled_info);
+ led->mc_cdev.subled_info = led->subled_info;
+
+ cdev = &led->mc_cdev.led_cdev;
+ cdev->max_brightness = U8_MAX;
+ cdev->brightness_set = sun50i_a100_ledc_brightness_set;
+
+ init_data.fwnode = child;
+
+ ret = led_classdev_multicolor_register_ext(dev, &led->mc_cdev, &init_data);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to register multicolor LED %u", led->addr);
+ while (led-- > priv->leds)
+ led_classdev_multicolor_unregister(&led->mc_cdev);
+ sun50i_a100_ledc_suspend(&pdev->dev);
+
+ return ret;
+ }
+
+ led++;
+ }
+
+ dev_info(dev, "Registered %u LEDs\n", num_leds);
+
+ return 0;
+}
+
+static void sun50i_a100_ledc_remove(struct platform_device *pdev)
+{
+ struct sun50i_a100_ledc *priv = platform_get_drvdata(pdev);
+
+ for (u32 i = 0; i < priv->num_leds; i++)
+ led_classdev_multicolor_unregister(&priv->leds[i].mc_cdev);
+ sun50i_a100_ledc_suspend(&pdev->dev);
+}
+
+static const struct of_device_id sun50i_a100_ledc_of_match[] = {
+ { .compatible = "allwinner,sun50i-a100-ledc" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, sun50i_a100_ledc_of_match);
+
+static DEFINE_SIMPLE_DEV_PM_OPS(sun50i_a100_ledc_pm,
+ sun50i_a100_ledc_suspend,
+ sun50i_a100_ledc_resume);
+
+static struct platform_driver sun50i_a100_ledc_driver = {
+ .probe = sun50i_a100_ledc_probe,
+ .remove = sun50i_a100_ledc_remove,
+ .shutdown = sun50i_a100_ledc_remove,
+ .driver = {
+ .name = "sun50i-a100-ledc",
+ .of_match_table = sun50i_a100_ledc_of_match,
+ .pm = pm_ptr(&sun50i_a100_ledc_pm),
+ },
+};
+module_platform_driver(sun50i_a100_ledc_driver);
+
+MODULE_AUTHOR("Samuel Holland <samuel@sholland.org>");
+MODULE_DESCRIPTION("Allwinner A100 LED controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c
index 388632d23d44..bd24e7f5947a 100644
--- a/drivers/leds/leds-sunfire.c
+++ b/drivers/leds/leds-sunfire.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/* leds-sunfire.c: SUNW,Ultra-Enterprise LED driver.
*
* Copyright (C) 2008 David S. Miller <davem@davemloft.net>
@@ -16,7 +17,7 @@
#include <asm/fhc.h>
#include <asm/upa.h>
-MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
+MODULE_AUTHOR("David S. Miller <davem@davemloft.net>");
MODULE_DESCRIPTION("Sun Fire LED driver");
MODULE_LICENSE("GPL");
@@ -135,10 +136,8 @@ static int sunfire_led_generic_probe(struct platform_device *pdev,
}
p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
- if (!p) {
- dev_err(&pdev->dev, "Could not allocate struct sunfire_drvdata\n");
+ if (!p)
return -ENOMEM;
- }
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) {
struct led_classdev *lp = &p->leds[i].led_cdev;
@@ -164,15 +163,13 @@ static int sunfire_led_generic_probe(struct platform_device *pdev,
return 0;
}
-static int sunfire_led_generic_remove(struct platform_device *pdev)
+static void sunfire_led_generic_remove(struct platform_device *pdev)
{
struct sunfire_drvdata *p = platform_get_drvdata(pdev);
int i;
for (i = 0; i < NUM_LEDS_PER_BOARD; i++)
led_classdev_unregister(&p->leds[i].led_cdev);
-
- return 0;
}
static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = {
@@ -225,7 +222,6 @@ static struct platform_driver sunfire_clockboard_led_driver = {
.remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-clockboard-leds",
- .owner = THIS_MODULE,
},
};
@@ -234,32 +230,22 @@ static struct platform_driver sunfire_fhc_led_driver = {
.remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-fhc-leds",
- .owner = THIS_MODULE,
},
};
+static struct platform_driver * const drivers[] = {
+ &sunfire_clockboard_led_driver,
+ &sunfire_fhc_led_driver,
+};
+
static int __init sunfire_leds_init(void)
{
- int err = platform_driver_register(&sunfire_clockboard_led_driver);
-
- if (err) {
- pr_err("Could not register clock board LED driver\n");
- return err;
- }
-
- err = platform_driver_register(&sunfire_fhc_led_driver);
- if (err) {
- pr_err("Could not register FHC LED driver\n");
- platform_driver_unregister(&sunfire_clockboard_led_driver);
- }
-
- return err;
+ return platform_register_drivers(drivers, ARRAY_SIZE(drivers));
}
static void __exit sunfire_leds_exit(void)
{
- platform_driver_unregister(&sunfire_clockboard_led_driver);
- platform_driver_unregister(&sunfire_fhc_led_driver);
+ platform_unregister_drivers(drivers, ARRAY_SIZE(drivers));
}
module_init(sunfire_leds_init);
diff --git a/drivers/leds/leds-syscon.c b/drivers/leds/leds-syscon.c
new file mode 100644
index 000000000000..d633ad519d0c
--- /dev/null
+++ b/drivers/leds/leds-syscon.c
@@ -0,0 +1,137 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic Syscon LEDs Driver
+ *
+ * Copyright (c) 2014, Linaro Limited
+ * Author: Linus Walleij <linus.walleij@linaro.org>
+ */
+#include <linux/io.h>
+#include <linux/init.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/stat.h>
+#include <linux/slab.h>
+#include <linux/mfd/syscon.h>
+#include <linux/regmap.h>
+#include <linux/leds.h>
+
+/**
+ * struct syscon_led - state container for syscon based LEDs
+ * @cdev: LED class device for this LED
+ * @map: regmap to access the syscon device backing this LED
+ * @offset: the offset into the syscon regmap for the LED register
+ * @mask: the bit in the register corresponding to the LED
+ * @state: current state of the LED
+ */
+struct syscon_led {
+ struct led_classdev cdev;
+ struct regmap *map;
+ u32 offset;
+ u32 mask;
+ bool state;
+};
+
+static void syscon_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct syscon_led *sled =
+ container_of(led_cdev, struct syscon_led, cdev);
+ u32 val;
+ int ret;
+
+ if (value == LED_OFF) {
+ val = 0;
+ sled->state = false;
+ } else {
+ val = sled->mask;
+ sled->state = true;
+ }
+
+ ret = regmap_update_bits(sled->map, sled->offset, sled->mask, val);
+ if (ret < 0)
+ dev_err(sled->cdev.dev, "error updating LED status\n");
+}
+
+static int syscon_led_probe(struct platform_device *pdev)
+{
+ struct led_init_data init_data = {};
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct device *parent;
+ struct regmap *map;
+ struct syscon_led *sled;
+ enum led_default_state state;
+ u32 value;
+ int ret;
+
+ parent = dev->parent;
+ if (!parent) {
+ dev_err(dev, "no parent for syscon LED\n");
+ return -ENODEV;
+ }
+ map = syscon_node_to_regmap(dev_of_node(parent));
+ if (IS_ERR(map)) {
+ dev_err(dev, "no regmap for syscon LED parent\n");
+ return PTR_ERR(map);
+ }
+
+ sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
+ if (!sled)
+ return -ENOMEM;
+
+ sled->map = map;
+
+ if (of_property_read_u32(np, "reg", &sled->offset) &&
+ of_property_read_u32(np, "offset", &sled->offset))
+ return -EINVAL;
+ if (of_property_read_u32(np, "mask", &sled->mask))
+ return -EINVAL;
+
+ init_data.fwnode = of_fwnode_handle(np);
+
+ state = led_init_default_state_get(init_data.fwnode);
+ switch (state) {
+ case LEDS_DEFSTATE_ON:
+ ret = regmap_update_bits(map, sled->offset, sled->mask, sled->mask);
+ if (ret < 0)
+ return ret;
+ sled->state = true;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ ret = regmap_read(map, sled->offset, &value);
+ if (ret < 0)
+ return ret;
+ sled->state = !!(value & sled->mask);
+ break;
+ default:
+ ret = regmap_update_bits(map, sled->offset, sled->mask, 0);
+ if (ret < 0)
+ return ret;
+ sled->state = false;
+ }
+ sled->cdev.brightness_set = syscon_led_set;
+
+ ret = devm_led_classdev_register_ext(dev, &sled->cdev, &init_data);
+ if (ret < 0)
+ return ret;
+
+ platform_set_drvdata(pdev, sled);
+ dev_info(dev, "registered LED %s\n", sled->cdev.name);
+
+ return 0;
+}
+
+static const struct of_device_id of_syscon_leds_match[] = {
+ { .compatible = "register-bit-led", },
+ {},
+};
+
+static struct platform_driver syscon_led_driver = {
+ .probe = syscon_led_probe,
+ .driver = {
+ .name = "leds-syscon",
+ .of_match_table = of_syscon_leds_match,
+ .suppress_bind_attrs = true,
+ },
+};
+builtin_platform_driver(syscon_led_driver);
diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
index 98fe021ba276..fd0e8bab9a4b 100644
--- a/drivers/leds/leds-tca6507.c
+++ b/drivers/leds/leds-tca6507.c
@@ -1,80 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* leds-tca6507
*
* The TCA6507 is a programmable LED controller that can drive 7
* separate lines either by holding them low, or by pulsing them
* with modulated width.
- * The modulation can be varied in a simple pattern to produce a blink or
- * double-blink.
+ * The modulation can be varied in a simple pattern to produce a
+ * blink or double-blink.
*
- * This driver can configure each line either as a 'GPIO' which is out-only
- * (no pull-up) or as an LED with variable brightness and hardware-assisted
- * blinking.
+ * This driver can configure each line either as a 'GPIO' which is
+ * out-only (pull-up resistor required) or as an LED with variable
+ * brightness and hardware-assisted blinking.
*
- * Apart from OFF and ON there are three programmable brightness levels which
- * can be programmed from 0 to 15 and indicate how many 500usec intervals in
- * each 8msec that the led is 'on'. The levels are named MASTER, BANK0 and
- * BANK1.
+ * Apart from OFF and ON there are three programmable brightness
+ * levels which can be programmed from 0 to 15 and indicate how many
+ * 500usec intervals in each 8msec that the led is 'on'. The levels
+ * are named MASTER, BANK0 and BANK1.
*
- * There are two different blink rates that can be programmed, each with
- * separate time for rise, on, fall, off and second-off. Thus if 3 or more
- * different non-trivial rates are required, software must be used for the extra
- * rates. The two different blink rates must align with the two levels BANK0 and
- * BANK1.
- * This driver does not support double-blink so 'second-off' always matches
- * 'off'.
+ * There are two different blink rates that can be programmed, each
+ * with separate time for rise, on, fall, off and second-off. Thus if
+ * 3 or more different non-trivial rates are required, software must
+ * be used for the extra rates. The two different blink rates must
+ * align with the two levels BANK0 and BANK1. This driver does not
+ * support double-blink so 'second-off' always matches 'off'.
*
- * Only 16 different times can be programmed in a roughly logarithmic scale from
- * 64ms to 16320ms. To be precise the possible times are:
+ * Only 16 different times can be programmed in a roughly logarithmic
+ * scale from 64ms to 16320ms. To be precise the possible times are:
* 0, 64, 128, 192, 256, 384, 512, 768,
* 1024, 1536, 2048, 3072, 4096, 5760, 8128, 16320
*
- * Times that cannot be closely matched with these must be
- * handled in software. This driver allows 12.5% error in matching.
+ * Times that cannot be closely matched with these must be handled in
+ * software. This driver allows 12.5% error in matching.
*
- * This driver does not allow rise/fall rates to be set explicitly. When trying
- * to match a given 'on' or 'off' period, an appropriate pair of 'change' and
- * 'hold' times are chosen to get a close match. If the target delay is even,
- * the 'change' number will be the smaller; if odd, the 'hold' number will be
- * the smaller.
-
- * Choosing pairs of delays with 12.5% errors allows us to match delays in the
- * ranges: 56-72, 112-144, 168-216, 224-27504, 28560-36720.
- * 26% of the achievable sums can be matched by multiple pairings. For example
- * 1536 == 1536+0, 1024+512, or 768+768. This driver will always choose the
- * pairing with the least maximum - 768+768 in this case. Other pairings are
- * not available.
+ * This driver does not allow rise/fall rates to be set explicitly.
+ * When trying to match a given 'on' or 'off' period, an appropriate
+ * pair of 'change' and 'hold' times are chosen to get a close match.
+ * If the target delay is even, the 'change' number will be the
+ * smaller; if odd, the 'hold' number will be the smaller.
+
+ * Choosing pairs of delays with 12.5% errors allows us to match
+ * delays in the ranges: 56-72, 112-144, 168-216, 224-27504,
+ * 28560-36720.
+ * 26% of the achievable sums can be matched by multiple pairings.
+ * For example 1536 == 1536+0, 1024+512, or 768+768.
+ * This driver will always choose the pairing with the least
+ * maximum - 768+768 in this case. Other pairings are not available.
*
- * Access to the 3 levels and 2 blinks are on a first-come, first-served basis.
- * Access can be shared by multiple leds if they have the same level and
- * either same blink rates, or some don't blink.
- * When a led changes, it relinquishes access and tries again, so it might
- * lose access to hardware blink.
- * If a blink engine cannot be allocated, software blink is used.
- * If the desired brightness cannot be allocated, the closest available non-zero
- * brightness is used. As 'full' is always available, the worst case would be
- * to have two different blink rates at '1', with Max at '2', then other leds
- * will have to choose between '2' and '16'. Hopefully this is not likely.
+ * Access to the 3 levels and 2 blinks are on a first-come,
+ * first-served basis. Access can be shared by multiple leds if they
+ * have the same level and either same blink rates, or some don't
+ * blink. When a led changes, it relinquishes access and tries again,
+ * so it might lose access to hardware blink.
*
- * Each bank (BANK0 and BANK1) has two usage counts - LEDs using the brightness
- * and LEDs using the blink. It can only be reprogrammed when the appropriate
- * counter is zero. The MASTER level has a single usage count.
+ * If a blink engine cannot be allocated, software blink is used. If
+ * the desired brightness cannot be allocated, the closest available
+ * non-zero brightness is used. As 'full' is always available, the
+ * worst case would be to have two different blink rates at '1', with
+ * Max at '2', then other leds will have to choose between '2' and
+ * '16'. Hopefully this is not likely.
*
- * Each Led has programmable 'on' and 'off' time as milliseconds. With each
- * there is a flag saying if it was explicitly requested or defaulted.
- * Similarly the banks know if each time was explicit or a default. Defaults
- * are permitted to be changed freely - they are not recognised when matching.
- *
- *
- * An led-tca6507 device must be provided with platform data. This data
- * lists for each output: the name, default trigger, and whether the signal
- * is being used as a GPiO rather than an led. 'struct led_plaform_data'
- * is used for this. If 'name' is NULL, the output isn't used. If 'flags'
- * is TCA6507_MAKE_CPIO, the output is a GPO.
- * The "struct led_platform_data" can be embedded in a
- * "struct tca6507_platform_data" which adds a 'gpio_base' for the GPiOs,
- * and a 'setup' callback which is called once the GPiOs are available.
+ * Each bank (BANK0 and BANK1) has two usage counts - LEDs using the
+ * brightness and LEDs using the blink. It can only be reprogrammed
+ * when the appropriate counter is zero. The MASTER level has a
+ * single usage count.
*
+ * Each LED has programmable 'on' and 'off' time as milliseconds.
+ * With each there is a flag saying if it was explicitly requested or
+ * defaulted. Similarly the banks know if each time was explicit or a
+ * default. Defaults are permitted to be changed freely - they are
+ * not recognised when matching.
*/
#include <linux/module.h>
@@ -82,10 +76,9 @@
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/i2c.h>
-#include <linux/gpio.h>
+#include <linux/gpio/driver.h>
+#include <linux/property.h>
#include <linux/workqueue.h>
-#include <linux/leds-tca6507.h>
-#include <linux/of.h>
/* LED select registers determine the source that drives LED outputs */
#define TCA6507_LS_LED_OFF 0x0 /* Output HI-Z (off) */
@@ -97,6 +90,12 @@
#define TCA6507_LS_BLINK0 0x6 /* Blink at Bank0 rate */
#define TCA6507_LS_BLINK1 0x7 /* Blink at Bank1 rate */
+struct tca6507_platform_data {
+ struct led_platform_data leds;
+};
+
+#define TCA6507_MAKE_GPIO 1
+
enum {
BANK0,
BANK1,
@@ -178,7 +177,6 @@ struct tca6507_chip {
} leds[NUM_LEDS];
#ifdef CONFIG_GPIOLIB
struct gpio_chip gpio;
- const char *gpio_name[NUM_LEDS];
int gpio_map[NUM_LEDS];
#endif
};
@@ -192,17 +190,18 @@ MODULE_DEVICE_TABLE(i2c, tca6507_id);
static int choose_times(int msec, int *c1p, int *c2p)
{
/*
- * Choose two timecodes which add to 'msec' as near as possible.
- * The first returned is the 'on' or 'off' time. The second is to be
- * used as a 'fade-on' or 'fade-off' time. If 'msec' is even,
- * the first will not be smaller than the second. If 'msec' is odd,
- * the first will not be larger than the second.
- * If we cannot get a sum within 1/8 of 'msec' fail with -EINVAL,
- * otherwise return the sum that was achieved, plus 1 if the first is
- * smaller.
- * If two possibilities are equally good (e.g. 512+0, 256+256), choose
- * the first pair so there is more change-time visible (i.e. it is
- * softer).
+ * Choose two timecodes which add to 'msec' as near as
+ * possible. The first returned is the 'on' or 'off' time.
+ * The second is to be used as a 'fade-on' or 'fade-off' time.
+ * If 'msec' is even, the first will not be smaller than the
+ * second. If 'msec' is odd, the first will not be larger
+ * than the second.
+ * If we cannot get a sum within 1/8 of 'msec' fail with
+ * -EINVAL, otherwise return the sum that was achieved, plus 1
+ * if the first is smaller.
+ * If two possibilities are equally good (e.g. 512+0,
+ * 256+256), choose the first pair so there is more
+ * change-time visible (i.e. it is softer).
*/
int c1, c2;
int tmax = msec * 9 / 8;
@@ -240,9 +239,7 @@ static int choose_times(int msec, int *c1p, int *c2p)
if (diff < 65536) {
int actual;
if (msec & 1) {
- c1 = *c2p;
- *c2p = *c1p;
- *c1p = c1;
+ swap(*c2p, *c1p);
}
actual = time_codes[*c1p] + time_codes[*c2p];
if (*c1p < *c2p)
@@ -255,8 +252,8 @@ static int choose_times(int msec, int *c1p, int *c2p)
}
/*
- * Update the register file with the appropriate 3-bit state for
- * the given led.
+ * Update the register file with the appropriate 3-bit state for the
+ * given led.
*/
static void set_select(struct tca6507_chip *tca, int led, int val)
{
@@ -274,9 +271,9 @@ static void set_select(struct tca6507_chip *tca, int led, int val)
}
}
-/* Update the register file with the appropriate 4-bit code for
- * one bank or other. This can be used for timers, for levels, or
- * for initialisation.
+/* Update the register file with the appropriate 4-bit code for one
+ * bank or other. This can be used for timers, for levels, or for
+ * initialization.
*/
static void set_code(struct tca6507_chip *tca, int reg, int bank, int new)
{
@@ -309,15 +306,18 @@ static void set_level(struct tca6507_chip *tca, int bank, int level)
tca->bank[bank].level = level;
}
-/* Record all relevant time code for a given bank */
+/* Record all relevant time codes for a given bank */
static void set_times(struct tca6507_chip *tca, int bank)
{
int c1, c2;
int result;
result = choose_times(tca->bank[bank].ontime, &c1, &c2);
+ if (result < 0)
+ return;
dev_dbg(&tca->client->dev,
- "Chose on times %d(%d) %d(%d) for %dms\n", c1, time_codes[c1],
+ "Chose on times %d(%d) %d(%d) for %dms\n",
+ c1, time_codes[c1],
c2, time_codes[c2], tca->bank[bank].ontime);
set_code(tca, TCA6507_FADE_ON, bank, c2);
set_code(tca, TCA6507_FULL_ON, bank, c1);
@@ -325,7 +325,8 @@ static void set_times(struct tca6507_chip *tca, int bank)
result = choose_times(tca->bank[bank].offtime, &c1, &c2);
dev_dbg(&tca->client->dev,
- "Chose off times %d(%d) %d(%d) for %dms\n", c1, time_codes[c1],
+ "Chose off times %d(%d) %d(%d) for %dms\n",
+ c1, time_codes[c1],
c2, time_codes[c2], tca->bank[bank].offtime);
set_code(tca, TCA6507_FADE_OFF, bank, c2);
set_code(tca, TCA6507_FIRST_OFF, bank, c1);
@@ -373,7 +374,8 @@ static void led_release(struct tca6507_led *led)
static int led_prepare(struct tca6507_led *led)
{
- /* Assign this led to a bank, configuring that bank if necessary. */
+ /* Assign this led to a bank, configuring that bank if
+ * necessary. */
int level = TO_LEVEL(led->led_cdev.brightness);
struct tca6507_chip *tca = led->chip;
int c1, c2;
@@ -389,10 +391,10 @@ static int led_prepare(struct tca6507_led *led)
if (led->ontime == 0 || led->offtime == 0) {
/*
- * Just set the brightness, choosing first usable bank.
- * If none perfect, choose best.
- * Count backwards so we check MASTER bank first
- * to avoid wasting a timer.
+ * Just set the brightness, choosing first usable
+ * bank. If none perfect, choose best. Count
+ * backwards so we check MASTER bank first to avoid
+ * wasting a timer.
*/
int best = -1;/* full-on */
int diff = 15-level;
@@ -433,9 +435,9 @@ static int led_prepare(struct tca6507_led *led)
}
/*
- * We have on/off time so we need to try to allocate a timing bank.
- * First check if times are compatible with hardware and give up if
- * not.
+ * We have on/off time so we need to try to allocate a timing
+ * bank. First check if times are compatible with hardware
+ * and give up if not.
*/
if (choose_times(led->ontime, &c1, &c2) < 0)
return -EINVAL;
@@ -523,8 +525,8 @@ static int led_assign(struct tca6507_led *led)
err = led_prepare(led);
if (err) {
/*
- * Can only fail on timer setup. In that case we need to
- * re-establish as steady level.
+ * Can only fail on timer setup. In that case we need
+ * to re-establish as steady level.
*/
led->ontime = 0;
led->offtime = 0;
@@ -586,32 +588,33 @@ static int tca6507_blink_set(struct led_classdev *led_cdev,
}
#ifdef CONFIG_GPIOLIB
-static void tca6507_gpio_set_value(struct gpio_chip *gc,
- unsigned offset, int val)
+static int tca6507_gpio_set_value(struct gpio_chip *gc, unsigned int offset,
+ int val)
{
- struct tca6507_chip *tca = container_of(gc, struct tca6507_chip, gpio);
+ struct tca6507_chip *tca = gpiochip_get_data(gc);
unsigned long flags;
spin_lock_irqsave(&tca->lock, flags);
/*
- * 'OFF' is floating high, and 'ON' is pulled down, so it has the
- * inverse sense of 'val'.
+ * 'OFF' is floating high, and 'ON' is pulled down, so it has
+ * the inverse sense of 'val'.
*/
set_select(tca, tca->gpio_map[offset],
val ? TCA6507_LS_LED_OFF : TCA6507_LS_LED_ON);
spin_unlock_irqrestore(&tca->lock, flags);
if (tca->reg_set)
schedule_work(&tca->work);
+
+ return 0;
}
static int tca6507_gpio_direction_output(struct gpio_chip *gc,
unsigned offset, int val)
{
- tca6507_gpio_set_value(gc, offset, val);
- return 0;
+ return tca6507_gpio_set_value(gc, offset, val);
}
-static int tca6507_probe_gpios(struct i2c_client *client,
+static int tca6507_probe_gpios(struct device *dev,
struct tca6507_chip *tca,
struct tca6507_platform_data *pdata)
{
@@ -622,7 +625,6 @@ static int tca6507_probe_gpios(struct i2c_client *client,
for (i = 0; i < NUM_LEDS; i++)
if (pdata->leds.leds[i].name && pdata->leds.leds[i].flags) {
/* Configure as a gpio */
- tca->gpio_name[gpios] = pdata->leds.leds[i].name;
tca->gpio_map[gpios] = i;
gpios++;
}
@@ -631,126 +633,104 @@ static int tca6507_probe_gpios(struct i2c_client *client,
return 0;
tca->gpio.label = "gpio-tca6507";
- tca->gpio.names = tca->gpio_name;
tca->gpio.ngpio = gpios;
- tca->gpio.base = pdata->gpio_base;
+ tca->gpio.base = -1;
tca->gpio.owner = THIS_MODULE;
tca->gpio.direction_output = tca6507_gpio_direction_output;
tca->gpio.set = tca6507_gpio_set_value;
- tca->gpio.dev = &client->dev;
- err = gpiochip_add(&tca->gpio);
+ tca->gpio.parent = dev;
+ err = devm_gpiochip_add_data(dev, &tca->gpio, tca);
if (err) {
tca->gpio.ngpio = 0;
return err;
}
- if (pdata->setup)
- pdata->setup(tca->gpio.base, tca->gpio.ngpio);
return 0;
}
-
-static void tca6507_remove_gpio(struct tca6507_chip *tca)
-{
- if (tca->gpio.ngpio) {
- int err = gpiochip_remove(&tca->gpio);
- dev_err(&tca->client->dev, "%s failed, %d\n",
- "gpiochip_remove()", err);
- }
-}
#else /* CONFIG_GPIOLIB */
-static int tca6507_probe_gpios(struct i2c_client *client,
+static int tca6507_probe_gpios(struct device *dev,
struct tca6507_chip *tca,
struct tca6507_platform_data *pdata)
{
return 0;
}
-static void tca6507_remove_gpio(struct tca6507_chip *tca)
-{
-}
#endif /* CONFIG_GPIOLIB */
-#ifdef CONFIG_OF
static struct tca6507_platform_data *
-tca6507_led_dt_init(struct i2c_client *client)
+tca6507_led_dt_init(struct device *dev)
{
- struct device_node *np = client->dev.of_node, *child;
struct tca6507_platform_data *pdata;
struct led_info *tca_leds;
int count;
- count = of_get_child_count(np);
+ count = device_get_child_node_count(dev);
if (!count || count > NUM_LEDS)
return ERR_PTR(-ENODEV);
- tca_leds = devm_kzalloc(&client->dev,
- sizeof(struct led_info) * count, GFP_KERNEL);
+ tca_leds = devm_kcalloc(dev, NUM_LEDS, sizeof(struct led_info),
+ GFP_KERNEL);
if (!tca_leds)
return ERR_PTR(-ENOMEM);
- for_each_child_of_node(np, child) {
+ device_for_each_child_node_scoped(dev, child) {
struct led_info led;
u32 reg;
int ret;
- led.name =
- of_get_property(child, "label", NULL) ? : child->name;
- led.default_trigger =
- of_get_property(child, "linux,default-trigger", NULL);
+ if (fwnode_property_read_string(child, "label", &led.name))
+ led.name = fwnode_get_name(child);
- ret = of_property_read_u32(child, "reg", &reg);
- if (ret != 0)
- continue;
+ if (fwnode_property_read_string(child, "linux,default-trigger",
+ &led.default_trigger))
+ led.default_trigger = NULL;
+
+ led.flags = 0;
+ if (fwnode_device_is_compatible(child, "gpio"))
+ led.flags |= TCA6507_MAKE_GPIO;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg >= NUM_LEDS)
+ return ERR_PTR(ret ? : -EINVAL);
tca_leds[reg] = led;
}
- pdata = devm_kzalloc(&client->dev,
- sizeof(struct tca6507_platform_data), GFP_KERNEL);
+
+ pdata = devm_kzalloc(dev, sizeof(struct tca6507_platform_data),
+ GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
pdata->leds.leds = tca_leds;
- pdata->leds.num_leds = count;
+ pdata->leds.num_leds = NUM_LEDS;
return pdata;
}
-static const struct of_device_id of_tca6507_leds_match[] = {
+static const struct of_device_id __maybe_unused of_tca6507_leds_match[] = {
{ .compatible = "ti,tca6507", },
{},
};
+MODULE_DEVICE_TABLE(of, of_tca6507_leds_match);
-#else
-static struct tca6507_platform_data *
-tca6507_led_dt_init(struct i2c_client *client)
+static int tca6507_probe(struct i2c_client *client)
{
- return ERR_PTR(-ENODEV);
-}
-
-#endif
-
-static int tca6507_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- struct tca6507_chip *tca;
+ struct device *dev = &client->dev;
struct i2c_adapter *adapter;
+ struct tca6507_chip *tca;
struct tca6507_platform_data *pdata;
int err;
int i = 0;
- adapter = to_i2c_adapter(client->dev.parent);
- pdata = client->dev.platform_data;
+ adapter = client->adapter;
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
return -EIO;
- if (!pdata || pdata->leds.num_leds != NUM_LEDS) {
- pdata = tca6507_led_dt_init(client);
- if (IS_ERR(pdata)) {
- dev_err(&client->dev, "Need %d entries in platform-data list\n",
- NUM_LEDS);
- return PTR_ERR(pdata);
- }
+ pdata = tca6507_led_dt_init(dev);
+ if (IS_ERR(pdata)) {
+ dev_err(dev, "Need %d entries in platform-data list\n", NUM_LEDS);
+ return PTR_ERR(pdata);
}
- tca = devm_kzalloc(&client->dev, sizeof(*tca), GFP_KERNEL);
+ tca = devm_kzalloc(dev, sizeof(*tca), GFP_KERNEL);
if (!tca)
return -ENOMEM;
@@ -771,48 +751,31 @@ static int tca6507_probe(struct i2c_client *client,
l->led_cdev.brightness_set = tca6507_brightness_set;
l->led_cdev.blink_set = tca6507_blink_set;
l->bank = -1;
- err = led_classdev_register(&client->dev,
- &l->led_cdev);
+ err = devm_led_classdev_register(dev, &l->led_cdev);
if (err < 0)
- goto exit;
+ return err;
}
}
- err = tca6507_probe_gpios(client, tca, pdata);
+ err = tca6507_probe_gpios(dev, tca, pdata);
if (err)
- goto exit;
+ return err;
/* set all registers to known state - zero */
tca->reg_set = 0x7f;
schedule_work(&tca->work);
return 0;
-exit:
- while (i--) {
- if (tca->leds[i].led_cdev.name)
- led_classdev_unregister(&tca->leds[i].led_cdev);
- }
- return err;
}
-static int tca6507_remove(struct i2c_client *client)
+static void tca6507_remove(struct i2c_client *client)
{
- int i;
struct tca6507_chip *tca = i2c_get_clientdata(client);
- struct tca6507_led *tca_leds = tca->leds;
- for (i = 0; i < NUM_LEDS; i++) {
- if (tca_leds[i].led_cdev.name)
- led_classdev_unregister(&tca_leds[i].led_cdev);
- }
- tca6507_remove_gpio(tca);
cancel_work_sync(&tca->work);
-
- return 0;
}
static struct i2c_driver tca6507_driver = {
.driver = {
.name = "leds-tca6507",
- .owner = THIS_MODULE,
.of_match_table = of_match_ptr(of_tca6507_leds_match),
},
.probe = tca6507_probe,
diff --git a/drivers/leds/leds-ti-lmu-common.c b/drivers/leds/leds-ti-lmu-common.c
new file mode 100644
index 000000000000..b2491666b5dc
--- /dev/null
+++ b/drivers/leds/leds-ti-lmu-common.c
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2015 Texas Instruments
+// Copyright 2018 Sebastian Reichel
+// Copyright 2018 Pavel Machek <pavel@ucw.cz>
+// TI LMU LED common framework, based on previous work from
+// Milo Kim <milo.kim@ti.com>
+
+#include <linux/bitops.h>
+#include <linux/err.h>
+#include <linux/property.h>
+
+#include <linux/leds-ti-lmu-common.h>
+
+static const unsigned int ramp_table[16] = {2048, 262000, 524000, 1049000,
+ 2090000, 4194000, 8389000, 16780000, 33550000,
+ 41940000, 50330000, 58720000, 67110000,
+ 83880000, 100660000, 117440000};
+
+static int ti_lmu_common_update_brightness(struct ti_lmu_bank *lmu_bank,
+ int brightness)
+{
+ struct regmap *regmap = lmu_bank->regmap;
+ u8 reg, val;
+ int ret;
+
+ /*
+ * Brightness register update
+ *
+ * 11 bit dimming: update LSB bits and write MSB byte.
+ * MSB brightness should be shifted.
+ * 8 bit dimming: write MSB byte.
+ */
+ if (lmu_bank->max_brightness == MAX_BRIGHTNESS_11BIT) {
+ reg = lmu_bank->lsb_brightness_reg;
+ ret = regmap_update_bits(regmap, reg,
+ LMU_11BIT_LSB_MASK,
+ brightness);
+ if (ret)
+ return ret;
+
+ val = brightness >> LMU_11BIT_MSB_SHIFT;
+ } else {
+ val = brightness;
+ }
+
+ reg = lmu_bank->msb_brightness_reg;
+
+ return regmap_write(regmap, reg, val);
+}
+
+int ti_lmu_common_set_brightness(struct ti_lmu_bank *lmu_bank, int brightness)
+{
+ return ti_lmu_common_update_brightness(lmu_bank, brightness);
+}
+EXPORT_SYMBOL(ti_lmu_common_set_brightness);
+
+static unsigned int ti_lmu_common_convert_ramp_to_index(unsigned int usec)
+{
+ int size = ARRAY_SIZE(ramp_table);
+ int i;
+
+ if (usec <= ramp_table[0])
+ return 0;
+
+ if (usec > ramp_table[size - 1])
+ return size - 1;
+
+ for (i = 1; i < size; i++) {
+ if (usec == ramp_table[i])
+ return i;
+
+ /* Find an approximate index by looking up the table */
+ if (usec > ramp_table[i - 1] && usec < ramp_table[i]) {
+ if (usec - ramp_table[i - 1] < ramp_table[i] - usec)
+ return i - 1;
+ else
+ return i;
+ }
+ }
+
+ return 0;
+}
+
+int ti_lmu_common_set_ramp(struct ti_lmu_bank *lmu_bank)
+{
+ struct regmap *regmap = lmu_bank->regmap;
+ u8 ramp, ramp_up, ramp_down;
+
+ if (lmu_bank->ramp_up_usec == 0 && lmu_bank->ramp_down_usec == 0) {
+ ramp_up = 0;
+ ramp_down = 0;
+ } else {
+ ramp_up = ti_lmu_common_convert_ramp_to_index(lmu_bank->ramp_up_usec);
+ ramp_down = ti_lmu_common_convert_ramp_to_index(lmu_bank->ramp_down_usec);
+ }
+
+ ramp = (ramp_up << 4) | ramp_down;
+
+ return regmap_write(regmap, lmu_bank->runtime_ramp_reg, ramp);
+
+}
+EXPORT_SYMBOL(ti_lmu_common_set_ramp);
+
+int ti_lmu_common_get_ramp_params(struct device *dev,
+ struct fwnode_handle *child,
+ struct ti_lmu_bank *lmu_data)
+{
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "ramp-up-us",
+ &lmu_data->ramp_up_usec);
+ if (ret)
+ dev_warn(dev, "ramp-up-us property missing\n");
+
+
+ ret = fwnode_property_read_u32(child, "ramp-down-us",
+ &lmu_data->ramp_down_usec);
+ if (ret)
+ dev_warn(dev, "ramp-down-us property missing\n");
+
+ return 0;
+}
+EXPORT_SYMBOL(ti_lmu_common_get_ramp_params);
+
+int ti_lmu_common_get_brt_res(struct device *dev, struct fwnode_handle *child,
+ struct ti_lmu_bank *lmu_data)
+{
+ int ret;
+
+ ret = device_property_read_u32(dev, "ti,brightness-resolution",
+ &lmu_data->max_brightness);
+ if (ret)
+ ret = fwnode_property_read_u32(child,
+ "ti,brightness-resolution",
+ &lmu_data->max_brightness);
+ if (lmu_data->max_brightness <= 0) {
+ lmu_data->max_brightness = MAX_BRIGHTNESS_8BIT;
+ return ret;
+ }
+
+ if (lmu_data->max_brightness > MAX_BRIGHTNESS_11BIT)
+ lmu_data->max_brightness = MAX_BRIGHTNESS_11BIT;
+
+
+ return 0;
+}
+EXPORT_SYMBOL(ti_lmu_common_get_brt_res);
+
+MODULE_DESCRIPTION("TI LMU common LED framework");
+MODULE_AUTHOR("Sebastian Reichel");
+MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ti-lmu-led-common");
diff --git a/drivers/leds/leds-tlc591xx.c b/drivers/leds/leds-tlc591xx.c
new file mode 100644
index 000000000000..6605e08a042a
--- /dev/null
+++ b/drivers/leds/leds-tlc591xx.c
@@ -0,0 +1,236 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright 2014 Belkin Inc.
+ * Copyright 2015 Andrew Lunn <andrew@lunn.ch>
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define TLC591XX_MAX_LEDS 16
+#define TLC591XX_MAX_BRIGHTNESS 256
+
+#define TLC591XX_REG_MODE1 0x00
+#define MODE1_RESPON_ADDR_MASK 0xF0
+#define MODE1_NORMAL_MODE (0 << 4)
+#define MODE1_SPEED_MODE (1 << 4)
+
+#define TLC591XX_REG_MODE2 0x01
+#define MODE2_DIM (0 << 5)
+#define MODE2_BLINK (1 << 5)
+#define MODE2_OCH_STOP (0 << 3)
+#define MODE2_OCH_ACK (1 << 3)
+
+#define TLC591XX_REG_PWM(x) (0x02 + (x))
+
+#define TLC591XX_REG_GRPPWM 0x12
+#define TLC591XX_REG_GRPFREQ 0x13
+
+/* LED Driver Output State, determine the source that drives LED outputs */
+#define LEDOUT_OFF 0x0 /* Output LOW */
+#define LEDOUT_ON 0x1 /* Output HI-Z */
+#define LEDOUT_DIM 0x2 /* Dimming */
+#define LEDOUT_BLINK 0x3 /* Blinking */
+#define LEDOUT_MASK 0x3
+
+#define ldev_to_led(c) container_of(c, struct tlc591xx_led, ldev)
+
+struct tlc591xx_led {
+ bool active;
+ unsigned int led_no;
+ struct led_classdev ldev;
+ struct tlc591xx_priv *priv;
+};
+
+struct tlc591xx_priv {
+ struct tlc591xx_led leds[TLC591XX_MAX_LEDS];
+ struct regmap *regmap;
+ unsigned int reg_ledout_offset;
+};
+
+struct tlc591xx {
+ unsigned int max_leds;
+ unsigned int reg_ledout_offset;
+};
+
+static const struct tlc591xx tlc59116 = {
+ .max_leds = 16,
+ .reg_ledout_offset = 0x14,
+};
+
+static const struct tlc591xx tlc59108 = {
+ .max_leds = 8,
+ .reg_ledout_offset = 0x0c,
+};
+
+static int
+tlc591xx_set_mode(struct regmap *regmap, u8 mode)
+{
+ int err;
+ u8 val;
+
+ err = regmap_write(regmap, TLC591XX_REG_MODE1, MODE1_NORMAL_MODE);
+ if (err)
+ return err;
+
+ val = MODE2_OCH_STOP | mode;
+
+ return regmap_write(regmap, TLC591XX_REG_MODE2, val);
+}
+
+static int
+tlc591xx_set_ledout(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+ u8 val)
+{
+ unsigned int i = (led->led_no % 4) * 2;
+ unsigned int mask = LEDOUT_MASK << i;
+ unsigned int addr = priv->reg_ledout_offset + (led->led_no >> 2);
+
+ val = val << i;
+
+ return regmap_update_bits(priv->regmap, addr, mask, val);
+}
+
+static int
+tlc591xx_set_pwm(struct tlc591xx_priv *priv, struct tlc591xx_led *led,
+ u8 brightness)
+{
+ u8 pwm = TLC591XX_REG_PWM(led->led_no);
+
+ return regmap_write(priv->regmap, pwm, brightness);
+}
+
+static int
+tlc591xx_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct tlc591xx_led *led = ldev_to_led(led_cdev);
+ struct tlc591xx_priv *priv = led->priv;
+ int err;
+
+ switch ((int)brightness) {
+ case 0:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_OFF);
+ break;
+ case TLC591XX_MAX_BRIGHTNESS:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_ON);
+ break;
+ default:
+ err = tlc591xx_set_ledout(priv, led, LEDOUT_DIM);
+ if (!err)
+ err = tlc591xx_set_pwm(priv, led, brightness);
+ }
+
+ return err;
+}
+
+static const struct regmap_config tlc591xx_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x1e,
+};
+
+static const struct of_device_id of_tlc591xx_leds_match[] __maybe_unused = {
+ { .compatible = "ti,tlc59116",
+ .data = &tlc59116 },
+ { .compatible = "ti,tlc59108",
+ .data = &tlc59108 },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_tlc591xx_leds_match);
+
+static int
+tlc591xx_probe(struct i2c_client *client)
+{
+ struct device_node *np;
+ struct device *dev = &client->dev;
+ const struct tlc591xx *tlc591xx;
+ struct tlc591xx_priv *priv;
+ int err, count, reg;
+
+ np = dev_of_node(dev);
+ if (!np)
+ return -ENODEV;
+
+ tlc591xx = device_get_match_data(dev);
+ if (!tlc591xx)
+ return -ENODEV;
+
+ count = of_get_available_child_count(np);
+ if (!count || count > tlc591xx->max_leds)
+ return -EINVAL;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->regmap = devm_regmap_init_i2c(client, &tlc591xx_regmap);
+ if (IS_ERR(priv->regmap)) {
+ err = PTR_ERR(priv->regmap);
+ dev_err(dev, "Failed to allocate register map: %d\n", err);
+ return err;
+ }
+ priv->reg_ledout_offset = tlc591xx->reg_ledout_offset;
+
+ i2c_set_clientdata(client, priv);
+
+ err = tlc591xx_set_mode(priv->regmap, MODE2_DIM);
+ if (err < 0)
+ return err;
+
+ 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)
+ return err;
+
+ if (reg < 0 || reg >= tlc591xx->max_leds ||
+ priv->leds[reg].active)
+ return -EINVAL;
+
+ led = &priv->leds[reg];
+
+ led->active = true;
+ led->priv = priv;
+ led->led_no = reg;
+ led->ldev.brightness_set_blocking = tlc591xx_brightness_set;
+ led->ldev.max_brightness = TLC591XX_MAX_BRIGHTNESS;
+ err = devm_led_classdev_register_ext(dev, &led->ldev,
+ &init_data);
+ if (err < 0)
+ return dev_err_probe(dev, err,
+ "couldn't register LED %s\n",
+ led->ldev.name);
+ }
+ return 0;
+}
+
+static const struct i2c_device_id tlc591xx_id[] = {
+ { "tlc59116" },
+ { "tlc59108" },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, tlc591xx_id);
+
+static struct i2c_driver tlc591xx_driver = {
+ .driver = {
+ .name = "tlc591xx",
+ .of_match_table = of_match_ptr(of_tlc591xx_leds_match),
+ },
+ .probe = tlc591xx_probe,
+ .id_table = tlc591xx_id,
+};
+
+module_i2c_driver(tlc591xx_driver);
+
+MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("TLC591XX LED driver");
diff --git a/drivers/leds/leds-tps6105x.c b/drivers/leds/leds-tps6105x.c
new file mode 100644
index 000000000000..09fd88a6c8f0
--- /dev/null
+++ b/drivers/leds/leds-tps6105x.c
@@ -0,0 +1,89 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2019 Sven Van Asbroeck
+ */
+
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/tps6105x.h>
+#include <linux/regmap.h>
+
+struct tps6105x_priv {
+ struct regmap *regmap;
+ struct led_classdev cdev;
+ struct fwnode_handle *fwnode;
+};
+
+static void tps6105x_handle_put(void *data)
+{
+ struct tps6105x_priv *priv = data;
+
+ fwnode_handle_put(priv->fwnode);
+}
+
+static int tps6105x_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct tps6105x_priv *priv = container_of(cdev, struct tps6105x_priv,
+ cdev);
+
+ return regmap_update_bits(priv->regmap, TPS6105X_REG_0,
+ TPS6105X_REG0_TORCHC_MASK,
+ brightness << TPS6105X_REG0_TORCHC_SHIFT);
+}
+
+static int tps6105x_led_probe(struct platform_device *pdev)
+{
+ struct tps6105x *tps6105x = dev_get_platdata(&pdev->dev);
+ struct tps6105x_platform_data *pdata = tps6105x->pdata;
+ struct led_init_data init_data = { };
+ struct tps6105x_priv *priv;
+ int ret;
+
+ /* This instance is not set for torch mode so bail out */
+ if (pdata->mode != TPS6105X_MODE_TORCH) {
+ dev_info(&pdev->dev,
+ "chip not in torch mode, exit probe");
+ return -EINVAL;
+ }
+
+ priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+ /* fwnode/devicetree is optional. NULL is allowed for priv->fwnode */
+ priv->fwnode = device_get_next_child_node(pdev->dev.parent, NULL);
+ ret = devm_add_action_or_reset(&pdev->dev, tps6105x_handle_put, priv);
+ if (ret)
+ return ret;
+ priv->regmap = tps6105x->regmap;
+ priv->cdev.brightness_set_blocking = tps6105x_brightness_set;
+ priv->cdev.max_brightness = 7;
+ init_data.devicename = "tps6105x";
+ init_data.default_label = ":torch";
+ init_data.fwnode = priv->fwnode;
+
+ ret = regmap_update_bits(tps6105x->regmap, TPS6105X_REG_0,
+ TPS6105X_REG0_MODE_MASK |
+ TPS6105X_REG0_TORCHC_MASK,
+ TPS6105X_REG0_MODE_TORCH <<
+ TPS6105X_REG0_MODE_SHIFT);
+ if (ret)
+ return ret;
+
+ return devm_led_classdev_register_ext(&pdev->dev, &priv->cdev,
+ &init_data);
+}
+
+static struct platform_driver led_driver = {
+ .probe = tps6105x_led_probe,
+ .driver = {
+ .name = "tps6105x-leds",
+ },
+};
+
+module_platform_driver(led_driver);
+
+MODULE_DESCRIPTION("TPS6105x LED driver");
+MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-turris-omnia.c b/drivers/leds/leds-turris-omnia.c
new file mode 100644
index 000000000000..25ee5c1eb820
--- /dev/null
+++ b/drivers/leds/leds-turris-omnia.c
@@ -0,0 +1,555 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * CZ.NIC's Turris Omnia LEDs driver
+ *
+ * 2020, 2023, 2024 by Marek Behún <kabel@kernel.org>
+ */
+
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/turris-omnia-mcu-interface.h>
+
+#define OMNIA_BOARD_LEDS 12
+#define OMNIA_LED_NUM_CHANNELS 3
+
+/* MCU controller I2C address 0x2a, needed for detecting MCU features */
+#define OMNIA_MCU_I2C_ADDR 0x2a
+
+/**
+ * struct omnia_led - per-LED part of driver private data structure
+ * @mc_cdev: multi-color LED class device
+ * @subled_info: per-channel information
+ * @cached_channels: cached values of per-channel brightness that was sent to the MCU
+ * @on: whether the LED was set on
+ * @hwtrig: whether the LED blinking was offloaded to the MCU
+ * @reg: LED identifier to the MCU
+ */
+struct omnia_led {
+ struct led_classdev_mc mc_cdev;
+ struct mc_subled subled_info[OMNIA_LED_NUM_CHANNELS];
+ u8 cached_channels[OMNIA_LED_NUM_CHANNELS];
+ bool on, hwtrig;
+ int reg;
+};
+
+#define to_omnia_led(l) container_of(l, struct omnia_led, mc_cdev)
+
+/**
+ * struct omnia_leds - driver private data structure
+ * @client: I2C client device
+ * @lock: mutex to protect cached state
+ * @has_gamma_correction: whether the MCU firmware supports gamma correction
+ * @brightness_knode: kernel node of the "brightness" device sysfs attribute (this is the
+ * driver specific global brightness, not the LED classdev brightness)
+ * @leds: flexible array of per-LED data
+ */
+struct omnia_leds {
+ struct i2c_client *client;
+ struct mutex lock;
+ bool has_gamma_correction;
+ struct kernfs_node *brightness_knode;
+ struct omnia_led leds[];
+};
+
+static int omnia_cmd_set_color(const struct i2c_client *client, u8 led, u8 r, u8 g, u8 b)
+{
+ u8 buf[5] = { OMNIA_CMD_LED_COLOR, led, r, g, b };
+
+ return omnia_cmd_write(client, buf, sizeof(buf));
+}
+
+static int omnia_led_send_color_cmd(const struct i2c_client *client,
+ struct omnia_led *led)
+{
+ int ret;
+
+ /* Send the color change command */
+ ret = omnia_cmd_set_color(client, led->reg, led->subled_info[0].brightness,
+ led->subled_info[1].brightness, led->subled_info[2].brightness);
+ if (ret < 0)
+ return ret;
+
+ /* Cache the RGB channel brightnesses */
+ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
+ led->cached_channels[i] = led->subled_info[i].brightness;
+
+ return 0;
+}
+
+/* Determine if the computed RGB channels are different from the cached ones */
+static bool omnia_led_channels_changed(struct omnia_led *led)
+{
+ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i)
+ if (led->subled_info[i].brightness != led->cached_channels[i])
+ return true;
+
+ return false;
+}
+
+static int omnia_led_brightness_set_blocking(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
+ struct omnia_led *led = to_omnia_led(mc_cdev);
+ int err = 0;
+
+ mutex_lock(&leds->lock);
+
+ /*
+ * Only recalculate RGB brightnesses from intensities if brightness is
+ * non-zero (if it is zero and the LED is in HW blinking mode, we use
+ * max_brightness as brightness). Otherwise we won't be using them and
+ * we can save ourselves some software divisions (Omnia's CPU does not
+ * implement the division instruction).
+ */
+ if (brightness || led->hwtrig) {
+ led_mc_calc_color_components(mc_cdev, brightness ?:
+ cdev->max_brightness);
+
+ /*
+ * Send color command only if brightness is non-zero and the RGB
+ * channel brightnesses changed.
+ */
+ if (omnia_led_channels_changed(led))
+ err = omnia_led_send_color_cmd(leds->client, led);
+ }
+
+ /*
+ * Send on/off state change only if (bool)brightness changed and the LED
+ * is not being blinked by HW.
+ */
+ if (!err && !led->hwtrig && !brightness != !led->on) {
+ u8 state = OMNIA_CMD_LED_STATE_LED(led->reg);
+
+ if (brightness)
+ state |= OMNIA_CMD_LED_STATE_ON;
+
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_STATE, state);
+ if (!err)
+ led->on = !!brightness;
+ }
+
+ mutex_unlock(&leds->lock);
+
+ return err;
+}
+
+static struct led_hw_trigger_type omnia_hw_trigger_type;
+
+static int omnia_hwtrig_activate(struct led_classdev *cdev)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
+ struct omnia_led *led = to_omnia_led(mc_cdev);
+ int err = 0;
+
+ mutex_lock(&leds->lock);
+
+ if (!led->on) {
+ /*
+ * If the LED is off (brightness was set to 0), the last
+ * configured color was not necessarily sent to the MCU.
+ * Recompute with max_brightness and send if needed.
+ */
+ led_mc_calc_color_components(mc_cdev, cdev->max_brightness);
+
+ if (omnia_led_channels_changed(led))
+ err = omnia_led_send_color_cmd(leds->client, led);
+ }
+
+ if (!err) {
+ /* Put the LED into MCU controlled mode */
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE,
+ OMNIA_CMD_LED_MODE_LED(led->reg));
+ if (!err)
+ led->hwtrig = true;
+ }
+
+ mutex_unlock(&leds->lock);
+
+ return err;
+}
+
+static void omnia_hwtrig_deactivate(struct led_classdev *cdev)
+{
+ struct omnia_leds *leds = dev_get_drvdata(cdev->dev->parent);
+ struct omnia_led *led = to_omnia_led(lcdev_to_mccdev(cdev));
+ int err;
+
+ mutex_lock(&leds->lock);
+
+ led->hwtrig = false;
+
+ /* Put the LED into software mode */
+ err = omnia_cmd_write_u8(leds->client, OMNIA_CMD_LED_MODE,
+ OMNIA_CMD_LED_MODE_LED(led->reg) | OMNIA_CMD_LED_MODE_USER);
+
+ mutex_unlock(&leds->lock);
+
+ if (err)
+ dev_err(cdev->dev, "Cannot put LED to software mode: %i\n",
+ err);
+}
+
+static struct led_trigger omnia_hw_trigger = {
+ .name = "omnia-mcu",
+ .activate = omnia_hwtrig_activate,
+ .deactivate = omnia_hwtrig_deactivate,
+ .trigger_type = &omnia_hw_trigger_type,
+};
+
+static int omnia_led_register(struct i2c_client *client, struct omnia_led *led,
+ struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ struct device *dev = &client->dev;
+ struct led_classdev *cdev;
+ int ret, color;
+
+ ret = of_property_read_u32(np, "reg", &led->reg);
+ if (ret || led->reg >= OMNIA_BOARD_LEDS) {
+ dev_warn(dev,
+ "Node %pOF: must contain 'reg' property with values between 0 and %i\n",
+ np, OMNIA_BOARD_LEDS - 1);
+ return 0;
+ }
+
+ ret = of_property_read_u32(np, "color", &color);
+ if (ret || color != LED_COLOR_ID_RGB) {
+ dev_warn(dev,
+ "Node %pOF: must contain 'color' property with value LED_COLOR_ID_RGB\n",
+ np);
+ return 0;
+ }
+
+ led->subled_info[0].color_index = LED_COLOR_ID_RED;
+ led->subled_info[1].color_index = LED_COLOR_ID_GREEN;
+ led->subled_info[2].color_index = LED_COLOR_ID_BLUE;
+
+ /* Initial color is white */
+ for (int i = 0; i < OMNIA_LED_NUM_CHANNELS; ++i) {
+ led->subled_info[i].intensity = 255;
+ led->subled_info[i].brightness = 255;
+ led->subled_info[i].channel = i;
+ }
+
+ led->mc_cdev.subled_info = led->subled_info;
+ led->mc_cdev.num_colors = OMNIA_LED_NUM_CHANNELS;
+
+ init_data.fwnode = &np->fwnode;
+
+ cdev = &led->mc_cdev.led_cdev;
+ cdev->max_brightness = 255;
+ cdev->brightness_set_blocking = omnia_led_brightness_set_blocking;
+ cdev->trigger_type = &omnia_hw_trigger_type;
+ /*
+ * Use the omnia-mcu trigger as the default trigger. It may be rewritten
+ * by LED class from the linux,default-trigger property.
+ */
+ cdev->default_trigger = omnia_hw_trigger.name;
+
+ /* Put the LED into software mode */
+ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(led->reg) |
+ OMNIA_CMD_LED_MODE_USER);
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF to software mode\n", np);
+
+ /* Disable the LED */
+ ret = omnia_cmd_write_u8(client, OMNIA_CMD_LED_STATE, OMNIA_CMD_LED_STATE_LED(led->reg));
+ if (ret)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF brightness\n", np);
+
+ /* Set initial color and cache it */
+ ret = omnia_led_send_color_cmd(client, led);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot set LED %pOF initial color\n", np);
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc_cdev,
+ &init_data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot register LED %pOF\n", np);
+
+ return 1;
+}
+
+/*
+ * On the front panel of the Turris Omnia router there is also a button which
+ * can be used to control the intensity of all the LEDs at once, so that if they
+ * are too bright, user can dim them.
+ * The microcontroller cycles between 8 levels of this global brightness (from
+ * 100% to 0%), but this setting can have any integer value between 0 and 100.
+ * It is therefore convenient to be able to change this setting from software.
+ * We expose this setting via a sysfs attribute file called "brightness". This
+ * file lives in the device directory of the LED controller, not an individual
+ * LED, so it should not confuse users.
+ */
+static ssize_t brightness_show(struct device *dev, struct device_attribute *a,
+ char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ u8 reply;
+ int err;
+
+ err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_BRIGHTNESS, &reply);
+ if (err < 0)
+ return err;
+
+ return sysfs_emit(buf, "%d\n", reply);
+}
+
+static ssize_t brightness_store(struct device *dev, struct device_attribute *a,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ unsigned long brightness;
+ int err;
+
+ if (kstrtoul(buf, 10, &brightness))
+ return -EINVAL;
+
+ if (brightness > 100)
+ return -EINVAL;
+
+ err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_BRIGHTNESS, brightness);
+
+ return err ?: count;
+}
+static DEVICE_ATTR_RW(brightness);
+
+static ssize_t gamma_correction_show(struct device *dev,
+ struct device_attribute *a, char *buf)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct omnia_leds *leds = i2c_get_clientdata(client);
+ u8 reply = 0;
+ int err;
+
+ if (leds->has_gamma_correction) {
+ err = omnia_cmd_read_u8(client, OMNIA_CMD_GET_GAMMA_CORRECTION, &reply);
+ if (err < 0)
+ return err;
+ }
+
+ return sysfs_emit(buf, "%d\n", !!reply);
+}
+
+static ssize_t gamma_correction_store(struct device *dev,
+ struct device_attribute *a,
+ const char *buf, size_t count)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct omnia_leds *leds = i2c_get_clientdata(client);
+ bool val;
+ int err;
+
+ if (!leds->has_gamma_correction)
+ return -EOPNOTSUPP;
+
+ if (kstrtobool(buf, &val) < 0)
+ return -EINVAL;
+
+ err = omnia_cmd_write_u8(client, OMNIA_CMD_SET_GAMMA_CORRECTION, val);
+
+ return err ?: count;
+}
+static DEVICE_ATTR_RW(gamma_correction);
+
+static struct attribute *omnia_led_controller_attrs[] = {
+ &dev_attr_brightness.attr,
+ &dev_attr_gamma_correction.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(omnia_led_controller);
+
+static irqreturn_t omnia_brightness_changed_threaded_fn(int irq, void *data)
+{
+ struct omnia_leds *leds = data;
+
+ if (unlikely(!leds->brightness_knode)) {
+ /*
+ * Note that sysfs_get_dirent() may sleep. This is okay, because we are in threaded
+ * context.
+ */
+ leds->brightness_knode = sysfs_get_dirent(leds->client->dev.kobj.sd, "brightness");
+ if (!leds->brightness_knode)
+ return IRQ_NONE;
+ }
+
+ sysfs_notify_dirent(leds->brightness_knode);
+
+ return IRQ_HANDLED;
+}
+
+static void omnia_brightness_knode_put(void *data)
+{
+ struct omnia_leds *leds = data;
+
+ if (leds->brightness_knode)
+ sysfs_put(leds->brightness_knode);
+}
+
+static int omnia_request_brightness_irq(struct omnia_leds *leds)
+{
+ struct device *dev = &leds->client->dev;
+ int ret;
+
+ if (!leds->client->irq) {
+ dev_info(dev,
+ "Brightness change interrupt supported by MCU firmware but not described in device-tree\n");
+
+ return 0;
+ }
+
+ /*
+ * Registering the brightness_knode destructor before requesting the IRQ ensures that on
+ * removal the brightness_knode sysfs node is put only after the IRQ is freed.
+ * This is needed because the interrupt handler uses the knode.
+ */
+ ret = devm_add_action(dev, omnia_brightness_knode_put, leds);
+ if (ret < 0)
+ return ret;
+
+ return devm_request_threaded_irq(dev, leds->client->irq, NULL,
+ omnia_brightness_changed_threaded_fn, IRQF_ONESHOT,
+ "leds-turris-omnia", leds);
+}
+
+static int omnia_mcu_get_features(const struct i2c_client *mcu_client)
+{
+ u16 reply;
+ int err;
+
+ err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_STATUS_WORD, &reply);
+ if (err)
+ return err;
+
+ /* Check whether MCU firmware supports the OMNIA_CMD_GET_FEAUTRES command */
+ if (!(reply & OMNIA_STS_FEATURES_SUPPORTED))
+ return 0;
+
+ err = omnia_cmd_read_u16(mcu_client, OMNIA_CMD_GET_FEATURES, &reply);
+ if (err)
+ return err;
+
+ return reply;
+}
+
+static int omnia_match_mcu_client(struct device *dev, const void *data)
+{
+ struct i2c_client *client;
+
+ client = i2c_verify_client(dev);
+ if (!client)
+ return 0;
+
+ return client->addr == OMNIA_MCU_I2C_ADDR;
+}
+
+static int omnia_find_mcu_and_get_features(struct device *dev)
+{
+ struct device *mcu_dev;
+ int ret;
+
+ mcu_dev = device_find_child(dev->parent, NULL, omnia_match_mcu_client);
+ if (!mcu_dev)
+ return -ENODEV;
+
+ ret = omnia_mcu_get_features(i2c_verify_client(mcu_dev));
+
+ put_device(mcu_dev);
+
+ return ret;
+}
+
+static int omnia_leds_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct device_node *np = dev_of_node(dev);
+ struct omnia_leds *leds;
+ struct omnia_led *led;
+ int ret, count;
+
+ count = of_get_available_child_count(np);
+ 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)
+ return -ENOMEM;
+
+ leds->client = client;
+ i2c_set_clientdata(client, leds);
+
+ ret = omnia_find_mcu_and_get_features(dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot determine MCU supported features\n");
+
+ leds->has_gamma_correction = ret & OMNIA_FEAT_LED_GAMMA_CORRECTION;
+
+ if (ret & OMNIA_FEAT_BRIGHTNESS_INT) {
+ ret = omnia_request_brightness_irq(leds);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot request brightness IRQ\n");
+ }
+
+ mutex_init(&leds->lock);
+
+ ret = devm_led_trigger_register(dev, &omnia_hw_trigger);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Cannot register private LED trigger\n");
+
+ led = &leds->leds[0];
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = omnia_led_register(client, led, child);
+ if (ret < 0)
+ return ret;
+
+ led += ret;
+ }
+
+ return 0;
+}
+
+static void omnia_leds_remove(struct i2c_client *client)
+{
+ /* Put all LEDs into default (HW triggered) mode */
+ omnia_cmd_write_u8(client, OMNIA_CMD_LED_MODE, OMNIA_CMD_LED_MODE_LED(OMNIA_BOARD_LEDS));
+
+ /* Set all LEDs color to [255, 255, 255] */
+ 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" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, omnia_id);
+
+static struct i2c_driver omnia_leds_driver = {
+ .probe = omnia_leds_probe,
+ .remove = omnia_leds_remove,
+ .id_table = omnia_id,
+ .driver = {
+ .name = "leds-turris-omnia",
+ .of_match_table = of_omnia_leds_match,
+ .dev_groups = omnia_led_controller_groups,
+ },
+};
+
+module_i2c_driver(omnia_leds_driver);
+
+MODULE_AUTHOR("Marek Behun <kabel@kernel.org>");
+MODULE_DESCRIPTION("CZ.NIC's Turris Omnia LEDs");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-upboard.c b/drivers/leds/leds-upboard.c
new file mode 100644
index 000000000000..12989b2f1953
--- /dev/null
+++ b/drivers/leds/leds-upboard.c
@@ -0,0 +1,126 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * UP board LED driver.
+ *
+ * Copyright (c) AAEON. All rights reserved.
+ * Copyright (C) 2024 Bootlin
+ *
+ * Author: Gary Wang <garywang@aaeon.com.tw>
+ * Author: Thomas Richard <thomas.richard@bootlin.com>
+ */
+
+#include <linux/device.h>
+#include <linux/container_of.h>
+#include <linux/leds.h>
+#include <linux/mfd/upboard-fpga.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+#define led_cdev_to_led_upboard(c) container_of(c, struct upboard_led, cdev)
+
+struct upboard_led {
+ struct regmap_field *field;
+ struct led_classdev cdev;
+};
+
+struct upboard_led_profile {
+ const char *name;
+ unsigned int bit;
+};
+
+static struct upboard_led_profile upboard_up_led_profile[] = {
+ { "upboard:yellow:" LED_FUNCTION_STATUS, 0 },
+ { "upboard:green:" LED_FUNCTION_STATUS, 1 },
+ { "upboard:red:" LED_FUNCTION_STATUS, 2 },
+};
+
+static struct upboard_led_profile upboard_up2_led_profile[] = {
+ { "upboard:blue:" LED_FUNCTION_STATUS, 0 },
+ { "upboard:yellow:" LED_FUNCTION_STATUS, 1 },
+ { "upboard:green:" LED_FUNCTION_STATUS, 2 },
+ { "upboard:red:" LED_FUNCTION_STATUS, 3 },
+};
+
+static enum led_brightness upboard_led_brightness_get(struct led_classdev *cdev)
+{
+ struct upboard_led *led = led_cdev_to_led_upboard(cdev);
+ int brightness, ret;
+
+ ret = regmap_field_read(led->field, &brightness);
+
+ return ret ? LED_OFF : brightness;
+};
+
+static int upboard_led_brightness_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct upboard_led *led = led_cdev_to_led_upboard(cdev);
+
+ return regmap_field_write(led->field, brightness != LED_OFF);
+};
+
+static int upboard_led_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct upboard_fpga *fpga = dev_get_drvdata(dev->parent);
+ struct upboard_led_profile *led_profile;
+ struct upboard_led *led;
+ int led_instances, ret, i;
+
+ switch (fpga->fpga_data->type) {
+ case UPBOARD_UP_FPGA:
+ led_profile = upboard_up_led_profile;
+ led_instances = ARRAY_SIZE(upboard_up_led_profile);
+ break;
+ case UPBOARD_UP2_FPGA:
+ led_profile = upboard_up2_led_profile;
+ led_instances = ARRAY_SIZE(upboard_up2_led_profile);
+ break;
+ default:
+ return dev_err_probe(dev, -EINVAL, "Unknown device type %d\n",
+ fpga->fpga_data->type);
+ }
+
+ for (i = 0; i < led_instances; i++) {
+ const struct reg_field fldconf = {
+ .reg = UPBOARD_REG_FUNC_EN0,
+ .lsb = led_profile[i].bit,
+ .msb = led_profile[i].bit,
+ };
+
+ led = devm_kzalloc(dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->field = devm_regmap_field_alloc(&pdev->dev, fpga->regmap, fldconf);
+ if (IS_ERR(led->field))
+ return PTR_ERR(led->field);
+
+ led->cdev.brightness_get = upboard_led_brightness_get;
+ led->cdev.brightness_set_blocking = upboard_led_brightness_set;
+ led->cdev.max_brightness = LED_ON;
+
+ led->cdev.name = led_profile[i].name;
+
+ ret = devm_led_classdev_register(dev, &led->cdev);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static struct platform_driver upboard_led_driver = {
+ .driver = {
+ .name = "upboard-leds",
+ },
+ .probe = upboard_led_probe,
+};
+
+module_platform_driver(upboard_led_driver);
+
+MODULE_AUTHOR("Gary Wang <garywang@aaeon.com.tw>");
+MODULE_AUTHOR("Thomas Richard <thomas.richard@bootlin.com>");
+MODULE_DESCRIPTION("UP Board LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:upboard-leds");
diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c
index 120815a42701..05930e9e8887 100644
--- a/drivers/leds/leds-wm831x-status.c
+++ b/drivers/leds/leds-wm831x-status.c
@@ -1,16 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED driver for WM831x status LEDs
*
* Copyright(C) 2009 Wolfson Microelectronics PLC.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/leds.h>
@@ -24,7 +19,6 @@
struct wm831x_status {
struct led_classdev cdev;
struct wm831x *wm831x;
- struct work_struct work;
struct mutex mutex;
spinlock_t value_lock;
@@ -41,10 +35,8 @@ struct wm831x_status {
#define to_wm831x_status(led_cdev) \
container_of(led_cdev, struct wm831x_status, cdev)
-static void wm831x_status_work(struct work_struct *work)
+static void wm831x_status_set(struct wm831x_status *led)
{
- struct wm831x_status *led = container_of(work, struct wm831x_status,
- work);
unsigned long flags;
mutex_lock(&led->mutex);
@@ -71,8 +63,8 @@ static void wm831x_status_work(struct work_struct *work)
mutex_unlock(&led->mutex);
}
-static void wm831x_status_set(struct led_classdev *led_cdev,
- enum led_brightness value)
+static int wm831x_status_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
struct wm831x_status *led = to_wm831x_status(led_cdev);
unsigned long flags;
@@ -81,8 +73,10 @@ static void wm831x_status_set(struct led_classdev *led_cdev,
led->brightness = value;
if (value == LED_OFF)
led->blink = 0;
- schedule_work(&led->work);
spin_unlock_irqrestore(&led->value_lock, flags);
+ wm831x_status_set(led);
+
+ return 0;
}
static int wm831x_status_blink_set(struct led_classdev *led_cdev,
@@ -148,11 +142,8 @@ static int wm831x_status_blink_set(struct led_classdev *led_cdev,
else
led->blink = 0;
- /* Always update; if we fail turn off blinking since we expect
- * a software fallback. */
- schedule_work(&led->work);
-
spin_unlock_irqrestore(&led->value_lock, flags);
+ wm831x_status_set(led);
return ret;
}
@@ -164,8 +155,8 @@ static const char * const led_src_texts[] = {
"soft",
};
-static ssize_t wm831x_status_src_show(struct device *dev,
- struct device_attribute *attr, char *buf)
+static ssize_t src_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct wm831x_status *led = to_wm831x_status(led_cdev);
@@ -187,38 +178,32 @@ static ssize_t wm831x_status_src_show(struct device *dev,
return ret;
}
-static ssize_t wm831x_status_src_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t size)
+static ssize_t src_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct wm831x_status *led = to_wm831x_status(led_cdev);
- char name[20];
int i;
- size_t len;
- name[sizeof(name) - 1] = '\0';
- strncpy(name, buf, sizeof(name) - 1);
- len = strlen(name);
-
- if (len && name[len - 1] == '\n')
- name[len - 1] = '\0';
-
- for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) {
- if (!strcmp(name, led_src_texts[i])) {
- mutex_lock(&led->mutex);
-
- led->src = i;
- schedule_work(&led->work);
-
- mutex_unlock(&led->mutex);
- }
+ i = sysfs_match_string(led_src_texts, buf);
+ if (i >= 0) {
+ mutex_lock(&led->mutex);
+ led->src = i;
+ mutex_unlock(&led->mutex);
+ wm831x_status_set(led);
}
return size;
}
-static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);
+static DEVICE_ATTR_RW(src);
+
+static struct attribute *wm831x_status_attrs[] = {
+ &dev_attr_src.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(wm831x_status);
static int wm831x_status_probe(struct platform_device *pdev)
{
@@ -230,24 +215,22 @@ static int wm831x_status_probe(struct platform_device *pdev)
int id = pdev->id % ARRAY_SIZE(chip_pdata->status);
int ret;
- res = platform_get_resource(pdev, IORESOURCE_IO, 0);
+ res = platform_get_resource(pdev, IORESOURCE_REG, 0);
if (res == NULL) {
- dev_err(&pdev->dev, "No I/O resource\n");
- ret = -EINVAL;
- goto err;
+ dev_err(&pdev->dev, "No register resource\n");
+ return -EINVAL;
}
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status),
GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
- platform_set_drvdata(pdev, drvdata);
drvdata->wm831x = wm831x;
drvdata->reg = res->start;
- if (wm831x->dev->platform_data)
- chip_pdata = wm831x->dev->platform_data;
+ if (dev_get_platdata(wm831x->dev))
+ chip_pdata = dev_get_platdata(wm831x->dev);
else
chip_pdata = NULL;
@@ -258,7 +241,6 @@ static int wm831x_status_probe(struct platform_device *pdev)
pdata.name = dev_name(&pdev->dev);
mutex_init(&drvdata->mutex);
- INIT_WORK(&drvdata->work, wm831x_status_work);
spin_lock_init(&drvdata->value_lock);
/* We cache the configuration register and read startup values
@@ -283,42 +265,31 @@ static int wm831x_status_probe(struct platform_device *pdev)
drvdata->cdev.name = pdata.name;
drvdata->cdev.default_trigger = pdata.default_trigger;
- drvdata->cdev.brightness_set = wm831x_status_set;
+ drvdata->cdev.brightness_set_blocking = wm831x_status_brightness_set;
drvdata->cdev.blink_set = wm831x_status_blink_set;
+ drvdata->cdev.groups = wm831x_status_groups;
ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
- goto err_led;
+ return ret;
}
- ret = device_create_file(drvdata->cdev.dev, &dev_attr_src);
- if (ret != 0)
- dev_err(&pdev->dev,
- "No source control for LED: %d\n", ret);
+ platform_set_drvdata(pdev, drvdata);
return 0;
-
-err_led:
- led_classdev_unregister(&drvdata->cdev);
-err:
- return ret;
}
-static int wm831x_status_remove(struct platform_device *pdev)
+static void wm831x_status_remove(struct platform_device *pdev)
{
struct wm831x_status *drvdata = platform_get_drvdata(pdev);
- device_remove_file(drvdata->cdev.dev, &dev_attr_src);
led_classdev_unregister(&drvdata->cdev);
-
- return 0;
}
static struct platform_driver wm831x_status_driver = {
.driver = {
.name = "wm831x-status",
- .owner = THIS_MODULE,
},
.probe = wm831x_status_probe,
.remove = wm831x_status_remove,
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c
index 8a181d56602d..87e60ea927b9 100644
--- a/drivers/leds/leds-wm8350.c
+++ b/drivers/leds/leds-wm8350.c
@@ -1,16 +1,11 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED driver for WM8350 driven LEDS.
*
* Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
@@ -90,40 +85,42 @@ static const int isink_cur[] = {
#define to_wm8350_led(led_cdev) \
container_of(led_cdev, struct wm8350_led, cdev)
-static void wm8350_led_enable(struct wm8350_led *led)
+static int wm8350_led_enable(struct wm8350_led *led)
{
- int ret;
+ int ret = 0;
if (led->enabled)
- return;
+ return ret;
ret = regulator_enable(led->isink);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
- return;
+ return ret;
}
ret = regulator_enable(led->dcdc);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
regulator_disable(led->isink);
- return;
+ return ret;
}
led->enabled = 1;
+
+ return ret;
}
-static void wm8350_led_disable(struct wm8350_led *led)
+static int wm8350_led_disable(struct wm8350_led *led)
{
- int ret;
+ int ret = 0;
if (!led->enabled)
- return;
+ return ret;
ret = regulator_disable(led->dcdc);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
- return;
+ return ret;
}
ret = regulator_disable(led->isink);
@@ -133,27 +130,29 @@ static void wm8350_led_disable(struct wm8350_led *led)
if (ret != 0)
dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n",
ret);
- return;
+ return ret;
}
led->enabled = 0;
+
+ return ret;
}
-static void led_work(struct work_struct *work)
+static int wm8350_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
{
- struct wm8350_led *led = container_of(work, struct wm8350_led, work);
+ struct wm8350_led *led = to_wm8350_led(led_cdev);
+ unsigned long flags;
int ret;
int uA;
- unsigned long flags;
- mutex_lock(&led->mutex);
+ led->value = value;
spin_lock_irqsave(&led->value_lock, flags);
if (led->value == LED_OFF) {
spin_unlock_irqrestore(&led->value_lock, flags);
- wm8350_led_disable(led);
- goto out;
+ return wm8350_led_disable(led);
}
/* This scales linearly into the index of valid current
@@ -167,43 +166,28 @@ static void led_work(struct work_struct *work)
ret = regulator_set_current_limit(led->isink, isink_cur[uA],
isink_cur[uA]);
- if (ret != 0)
+ if (ret != 0) {
dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
isink_cur[uA], ret);
+ return ret;
+ }
- wm8350_led_enable(led);
-
-out:
- mutex_unlock(&led->mutex);
-}
-
-static void wm8350_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct wm8350_led *led = to_wm8350_led(led_cdev);
- unsigned long flags;
-
- spin_lock_irqsave(&led->value_lock, flags);
- led->value = value;
- schedule_work(&led->work);
- spin_unlock_irqrestore(&led->value_lock, flags);
+ return wm8350_led_enable(led);
}
static void wm8350_led_shutdown(struct platform_device *pdev)
{
struct wm8350_led *led = platform_get_drvdata(pdev);
- mutex_lock(&led->mutex);
led->value = LED_OFF;
wm8350_led_disable(led);
- mutex_unlock(&led->mutex);
}
static int wm8350_led_probe(struct platform_device *pdev)
{
struct regulator *isink, *dcdc;
struct wm8350_led *led;
- struct wm8350_led_platform_data *pdata = pdev->dev.platform_data;
+ struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
int i;
if (pdata == NULL) {
@@ -233,7 +217,7 @@ static int wm8350_led_probe(struct platform_device *pdev)
if (led == NULL)
return -ENOMEM;
- led->cdev.brightness_set = wm8350_led_set;
+ led->cdev.brightness_set_blocking = wm8350_led_set;
led->cdev.default_trigger = pdata->default_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
@@ -252,28 +236,23 @@ static int wm8350_led_probe(struct platform_device *pdev)
pdata->max_uA);
spin_lock_init(&led->value_lock);
- mutex_init(&led->mutex);
- INIT_WORK(&led->work, led_work);
led->value = LED_OFF;
platform_set_drvdata(pdev, led);
return led_classdev_register(&pdev->dev, &led->cdev);
}
-static int wm8350_led_remove(struct platform_device *pdev)
+static void wm8350_led_remove(struct platform_device *pdev)
{
struct wm8350_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
- flush_work(&led->work);
wm8350_led_disable(led);
- return 0;
}
static struct platform_driver wm8350_led_driver = {
.driver = {
.name = "wm8350-led",
- .owner = THIS_MODULE,
},
.probe = wm8350_led_probe,
.remove = wm8350_led_remove,
diff --git a/drivers/leds/leds-wrap.c b/drivers/leds/leds-wrap.c
index b358cc05eff5..794697e16068 100644
--- a/drivers/leds/leds-wrap.c
+++ b/drivers/leds/leds-wrap.c
@@ -1,13 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LEDs driver for PCEngines WRAP
*
* Copyright (C) 2006 Kristian Kielhofner <kris@krisk.org>
*
* Based on leds-net48xx.c
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
*/
#include <linux/kernel.h>
@@ -76,42 +73,21 @@ static int wrap_led_probe(struct platform_device *pdev)
{
int ret;
- ret = led_classdev_register(&pdev->dev, &wrap_power_led);
+ ret = devm_led_classdev_register(&pdev->dev, &wrap_power_led);
if (ret < 0)
return ret;
- ret = led_classdev_register(&pdev->dev, &wrap_error_led);
- if (ret < 0)
- goto err1;
-
- ret = led_classdev_register(&pdev->dev, &wrap_extra_led);
+ ret = devm_led_classdev_register(&pdev->dev, &wrap_error_led);
if (ret < 0)
- goto err2;
-
- return ret;
-
-err2:
- led_classdev_unregister(&wrap_error_led);
-err1:
- led_classdev_unregister(&wrap_power_led);
-
- return ret;
-}
+ return ret;
-static int wrap_led_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&wrap_power_led);
- led_classdev_unregister(&wrap_error_led);
- led_classdev_unregister(&wrap_extra_led);
- return 0;
+ return devm_led_classdev_register(&pdev->dev, &wrap_extra_led);
}
static struct platform_driver wrap_led_driver = {
.probe = wrap_led_probe,
- .remove = wrap_led_remove,
.driver = {
.name = DRVNAME,
- .owner = THIS_MODULE,
},
};
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 4c50365344a9..bee46651e068 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -1,63 +1,34 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
/*
* LED Core
*
* Copyright 2005 Openedhand Ltd.
*
* Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#ifndef __LEDS_H_INCLUDED
#define __LEDS_H_INCLUDED
-#include <linux/device.h>
#include <linux/rwsem.h>
#include <linux/leds.h>
-static inline void __led_set_brightness(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- if (value > led_cdev->max_brightness)
- value = led_cdev->max_brightness;
- led_cdev->brightness = value;
- if (!(led_cdev->flags & LED_SUSPENDED))
- led_cdev->brightness_set(led_cdev, value);
-}
-
static inline int led_get_brightness(struct led_classdev *led_cdev)
{
return led_cdev->brightness;
}
+void led_init_core(struct led_classdev *led_cdev);
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,
+ const struct bin_attribute *attr, char *buf,
+ loff_t pos, size_t count);
+ssize_t led_trigger_write(struct file *filp, struct kobject *kobj,
+ 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;
-#ifdef CONFIG_LEDS_TRIGGERS
-void led_trigger_set_default(struct led_classdev *led_cdev);
-void led_trigger_set(struct led_classdev *led_cdev,
- struct led_trigger *trigger);
-void led_trigger_remove(struct led_classdev *led_cdev);
-
-static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
-{
- return led_cdev->trigger_data;
-}
-
-#else
-#define led_trigger_set_default(x) do {} while (0)
-#define led_trigger_set(x, y) do {} while (0)
-#define led_trigger_remove(x) do {} while (0)
-#define led_get_trigger_data(x) (NULL)
-#endif
-
-ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
- const char *buf, size_t count);
-ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
- char *buf);
-
#endif /* __LEDS_H_INCLUDED */
diff --git a/drivers/leds/rgb/Kconfig b/drivers/leds/rgb/Kconfig
new file mode 100644
index 000000000000..222d943d826a
--- /dev/null
+++ b/drivers/leds/rgb/Kconfig
@@ -0,0 +1,78 @@
+# SPDX-License-Identifier: GPL-2.0
+
+if LEDS_CLASS_MULTICOLOR
+
+config LEDS_GROUP_MULTICOLOR
+ tristate "LEDs group multi-color support"
+ depends on OF
+ help
+ This option enables support for monochrome LEDs that are grouped
+ into multicolor LEDs which is useful in the case where LEDs of
+ different colors are physically grouped in a single multi-color LED
+ and driven by a controller that doesn't have multi-color support.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-group-multicolor.
+
+config LEDS_KTD202X
+ tristate "LED support for KTD202x Chips"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ This option enables support for the Kinetic KTD2026/KTD2027
+ RGB/White LED driver found in different BQ mobile phones.
+ It is a 3 or 4 channel LED driver programmed via an I2C interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-ktd202x.
+
+config LEDS_NCP5623
+ tristate "LED support for NCP5623"
+ depends on I2C
+ depends on OF
+ help
+ This option enables support for ON semiconductor NCP5623
+ Triple Output I2C Controlled RGB LED Driver.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-ncp5623.
+
+config LEDS_PWM_MULTICOLOR
+ tristate "PWM driven multi-color LED Support"
+ depends on PWM
+ help
+ This option enables support for PWM driven monochrome LEDs that are
+ grouped into multicolor LEDs.
+
+ To compile this driver as a module, choose M here: the module
+ will be called leds-pwm-multicolor.
+
+config LEDS_QCOM_LPG
+ tristate "LED support for Qualcomm LPG"
+ depends on OF
+ depends on PWM
+ depends on QCOM_PBS || !QCOM_PBS
+ depends on SPMI
+ help
+ This option enables support for the Light Pulse Generator found in a
+ wide variety of Qualcomm PMICs. The LPG consists of a number of PWM
+ channels and typically a shared pattern lookup table and a current
+ sink, intended to drive RGB LEDs. Each channel can either be used as
+ a LED, grouped to represent a RGB LED or exposed as PWM channels.
+
+ If compiled as a module, the module will be named leds-qcom-lpg.
+
+config LEDS_MT6370_RGB
+ tristate "LED Support for MediaTek MT6370 PMIC"
+ depends on MFD_MT6370
+ select LINEAR_RANGES
+ help
+ Say Y here to enable support for MT6370_RGB LED device.
+ In MT6370, there are four channel current-sink LED drivers that
+ support hardware pattern for constant current, PWM, and breath mode.
+ Isink4 channel can also be used as a CHG_VIN power good indicator.
+
+ This driver can also be built as a module. If so, the module
+ will be called "leds-mt6370-rgb".
+
+endif # LEDS_CLASS_MULTICOLOR
diff --git a/drivers/leds/rgb/Makefile b/drivers/leds/rgb/Makefile
new file mode 100644
index 000000000000..a501fd27f179
--- /dev/null
+++ b/drivers/leds/rgb/Makefile
@@ -0,0 +1,8 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_LEDS_GROUP_MULTICOLOR) += leds-group-multicolor.o
+obj-$(CONFIG_LEDS_KTD202X) += leds-ktd202x.o
+obj-$(CONFIG_LEDS_NCP5623) += leds-ncp5623.o
+obj-$(CONFIG_LEDS_PWM_MULTICOLOR) += leds-pwm-multicolor.o
+obj-$(CONFIG_LEDS_QCOM_LPG) += leds-qcom-lpg.o
+obj-$(CONFIG_LEDS_MT6370_RGB) += leds-mt6370-rgb.o
diff --git a/drivers/leds/rgb/leds-group-multicolor.c b/drivers/leds/rgb/leds-group-multicolor.c
new file mode 100644
index 000000000000..548c7dd63ba1
--- /dev/null
+++ b/drivers/leds/rgb/leds-group-multicolor.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Multi-color LED built with monochromatic LED devices
+ *
+ * This driver groups several monochromatic LED devices in a single multicolor LED device.
+ *
+ * Compared to handling this grouping in user-space, the benefits are:
+ * - The state of the monochromatic LED relative to each other is always consistent.
+ * - The sysfs interface of the LEDs can be used for the group as a whole.
+ *
+ * Copyright 2023 Jean-Jacques Hiblot <jjhiblot@traphandler.com>
+ */
+
+#include <linux/err.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/math.h>
+#include <linux/module.h>
+#include <linux/mod_devicetable.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+
+struct leds_multicolor {
+ struct led_classdev_mc mc_cdev;
+ struct led_classdev **monochromatics;
+};
+
+static int leds_gmc_set(struct led_classdev *cdev, enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct leds_multicolor *priv = container_of(mc_cdev, struct leds_multicolor, mc_cdev);
+ const unsigned int group_max_brightness = mc_cdev->led_cdev.max_brightness;
+ int i;
+
+ for (i = 0; i < mc_cdev->num_colors; i++) {
+ struct led_classdev *mono = priv->monochromatics[i];
+ const unsigned int mono_max_brightness = mono->max_brightness;
+ unsigned int intensity = mc_cdev->subled_info[i].intensity;
+ int mono_brightness;
+
+ /*
+ * Scale the brightness according to relative intensity of the
+ * color AND the max brightness of the monochromatic LED.
+ */
+ mono_brightness = DIV_ROUND_CLOSEST(brightness * intensity * mono_max_brightness,
+ group_max_brightness * group_max_brightness);
+
+ led_set_brightness(mono, mono_brightness);
+ }
+
+ return 0;
+}
+
+static void restore_sysfs_write_access(void *data)
+{
+ struct led_classdev *led_cdev = data;
+
+ /* Restore the write access to the LED */
+ mutex_lock(&led_cdev->led_access);
+ led_sysfs_enable(led_cdev);
+ mutex_unlock(&led_cdev->led_access);
+}
+
+static int leds_gmc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct mc_subled *subled;
+ struct leds_multicolor *priv;
+ unsigned int max_brightness = 0;
+ int i, ret, count = 0, common_flags = 0;
+
+ priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ for (;;) {
+ struct led_classdev *led_cdev;
+
+ led_cdev = devm_of_led_get_optional(dev, count);
+ if (IS_ERR(led_cdev))
+ return dev_err_probe(dev, PTR_ERR(led_cdev), "Unable to get LED #%d",
+ count);
+ if (!led_cdev)
+ break;
+
+ priv->monochromatics = devm_krealloc_array(dev, priv->monochromatics,
+ count + 1, sizeof(*priv->monochromatics),
+ GFP_KERNEL);
+ if (!priv->monochromatics)
+ return -ENOMEM;
+
+ common_flags |= led_cdev->flags;
+ priv->monochromatics[count] = led_cdev;
+
+ max_brightness = max(max_brightness, led_cdev->max_brightness);
+
+ count++;
+ }
+
+ subled = devm_kcalloc(dev, count, sizeof(*subled), GFP_KERNEL);
+ if (!subled)
+ return -ENOMEM;
+ priv->mc_cdev.subled_info = subled;
+
+ for (i = 0; i < count; i++) {
+ struct led_classdev *led_cdev = priv->monochromatics[i];
+
+ subled[i].color_index = led_cdev->color;
+
+ /* Configure the LED intensity to its maximum */
+ subled[i].intensity = max_brightness;
+ }
+
+ /* Initialise the multicolor's LED class device */
+ cdev = &priv->mc_cdev.led_cdev;
+ cdev->brightness_set_blocking = leds_gmc_set;
+ cdev->max_brightness = max_brightness;
+ cdev->color = LED_COLOR_ID_MULTI;
+ priv->mc_cdev.num_colors = count;
+
+ /* we only need suspend/resume if a sub-led requests it */
+ if (common_flags & LED_CORE_SUSPENDRESUME)
+ cdev->flags = LED_CORE_SUSPENDRESUME;
+
+ init_data.fwnode = dev_fwnode(dev);
+ ret = devm_led_classdev_multicolor_register_ext(dev, &priv->mc_cdev, &init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register multicolor LED for %s.\n",
+ cdev->name);
+
+ ret = leds_gmc_set(cdev, cdev->brightness);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set LED value for %s.", cdev->name);
+
+ for (i = 0; i < count; i++) {
+ struct led_classdev *led_cdev = priv->monochromatics[i];
+
+ /*
+ * Make the individual LED sysfs interface read-only to prevent the user
+ * to change the brightness of the individual LEDs of the group.
+ */
+ mutex_lock(&led_cdev->led_access);
+ led_sysfs_disable(led_cdev);
+ mutex_unlock(&led_cdev->led_access);
+
+ /* Restore the write access to the LED sysfs when the group is destroyed */
+ devm_add_action_or_reset(dev, restore_sysfs_write_access, led_cdev);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id of_leds_group_multicolor_match[] = {
+ { .compatible = "leds-group-multicolor" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_leds_group_multicolor_match);
+
+static struct platform_driver leds_group_multicolor_driver = {
+ .probe = leds_gmc_probe,
+ .driver = {
+ .name = "leds_group_multicolor",
+ .of_match_table = of_leds_group_multicolor_match,
+ }
+};
+module_platform_driver(leds_group_multicolor_driver);
+
+MODULE_AUTHOR("Jean-Jacques Hiblot <jjhiblot@traphandler.com>");
+MODULE_DESCRIPTION("LEDs group multicolor driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-group-multicolor");
diff --git a/drivers/leds/rgb/leds-ktd202x.c b/drivers/leds/rgb/leds-ktd202x.c
new file mode 100644
index 000000000000..e4f0f25a5e45
--- /dev/null
+++ b/drivers/leds/rgb/leds-ktd202x.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Kinetic KTD2026/7 RGB/White LED driver with I2C interface
+ *
+ * Copyright 2023 André Apitzsch <git@apitzsch.eu>
+ *
+ * Datasheet: https://www.kinet-ic.com/uploads/KTD2026-7-04h.pdf
+ */
+
+#include <linux/i2c.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+
+#define KTD2026_NUM_LEDS 3
+#define KTD2027_NUM_LEDS 4
+#define KTD202X_MAX_LEDS 4
+
+/* Register bank */
+#define KTD202X_REG_RESET_CONTROL 0x00
+#define KTD202X_REG_FLASH_PERIOD 0x01
+#define KTD202X_REG_PWM1_TIMER 0x02
+#define KTD202X_REG_PWM2_TIMER 0x03
+#define KTD202X_REG_CHANNEL_CTRL 0x04
+#define KTD202X_REG_TRISE_FALL 0x05
+#define KTD202X_REG_LED_IOUT(x) (0x06 + (x))
+
+/* Register 0 */
+#define KTD202X_TIMER_SLOT_CONTROL_TSLOT1 0x00
+#define KTD202X_TIMER_SLOT_CONTROL_TSLOT2 0x01
+#define KTD202X_TIMER_SLOT_CONTROL_TSLOT3 0x02
+#define KTD202X_TIMER_SLOT_CONTROL_TSLOT4 0x03
+#define KTD202X_RSTR_RESET 0x07
+
+#define KTD202X_ENABLE_CTRL_WAKE 0x00 /* SCL High & SDA High */
+#define KTD202X_ENABLE_CTRL_SLEEP 0x08 /* SCL High & SDA Toggling */
+
+#define KTD202X_TRISE_FALL_SCALE_NORMAL 0x00
+#define KTD202X_TRISE_FALL_SCALE_SLOW_X2 0x20
+#define KTD202X_TRISE_FALL_SCALE_SLOW_X4 0x40
+#define KTD202X_TRISE_FALL_SCALE_FAST_X8 0x60
+
+/* Register 1 */
+#define KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP 0x00
+
+/* Register 2-3 */
+#define KTD202X_FLASH_ON_TIME_0_4_PERCENT 0x01
+
+/* Register 4 */
+#define KTD202X_CHANNEL_CTRL_MASK(x) (BIT(2 * (x)) | BIT(2 * (x) + 1))
+#define KTD202X_CHANNEL_CTRL_OFF 0x00
+#define KTD202X_CHANNEL_CTRL_ON(x) BIT(2 * (x))
+#define KTD202X_CHANNEL_CTRL_PWM1(x) BIT(2 * (x) + 1)
+#define KTD202X_CHANNEL_CTRL_PWM2(x) (BIT(2 * (x)) | BIT(2 * (x) + 1))
+
+/* Register 5 */
+#define KTD202X_RAMP_TIMES_2_MS 0x00
+
+/* Register 6-9 */
+#define KTD202X_LED_CURRENT_10_mA 0x4f
+
+#define KTD202X_FLASH_PERIOD_MIN_MS 256
+#define KTD202X_FLASH_PERIOD_STEP_MS 128
+#define KTD202X_FLASH_PERIOD_MAX_STEPS 126
+#define KTD202X_FLASH_ON_MAX 256
+
+#define KTD202X_MAX_BRIGHTNESS 192
+
+static const struct reg_default ktd202x_reg_defaults[] = {
+ { KTD202X_REG_RESET_CONTROL, KTD202X_TIMER_SLOT_CONTROL_TSLOT1 |
+ KTD202X_ENABLE_CTRL_WAKE | KTD202X_TRISE_FALL_SCALE_NORMAL },
+ { KTD202X_REG_FLASH_PERIOD, KTD202X_FLASH_PERIOD_256_MS_LOG_RAMP },
+ { KTD202X_REG_PWM1_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT },
+ { KTD202X_REG_PWM2_TIMER, KTD202X_FLASH_ON_TIME_0_4_PERCENT },
+ { KTD202X_REG_CHANNEL_CTRL, KTD202X_CHANNEL_CTRL_OFF },
+ { KTD202X_REG_TRISE_FALL, KTD202X_RAMP_TIMES_2_MS },
+ { KTD202X_REG_LED_IOUT(0), KTD202X_LED_CURRENT_10_mA },
+ { KTD202X_REG_LED_IOUT(1), KTD202X_LED_CURRENT_10_mA },
+ { KTD202X_REG_LED_IOUT(2), KTD202X_LED_CURRENT_10_mA },
+ { KTD202X_REG_LED_IOUT(3), KTD202X_LED_CURRENT_10_mA },
+};
+
+struct ktd202x_led {
+ struct ktd202x *chip;
+ union {
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+ };
+ u32 index;
+};
+
+struct ktd202x {
+ struct mutex mutex;
+ struct regulator_bulk_data regulators[2];
+ struct device *dev;
+ struct regmap *regmap;
+ bool enabled;
+ unsigned long num_leds;
+ struct ktd202x_led leds[] __counted_by(num_leds);
+};
+
+static int ktd202x_chip_disable(struct ktd202x *chip)
+{
+ int ret;
+
+ if (!chip->enabled)
+ return 0;
+
+ regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_SLEEP);
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
+ if (ret) {
+ dev_err(chip->dev, "Failed to disable regulators: %d\n", ret);
+ return ret;
+ }
+
+ chip->enabled = false;
+ return 0;
+}
+
+static int ktd202x_chip_enable(struct ktd202x *chip)
+{
+ int ret;
+
+ if (chip->enabled)
+ return 0;
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators);
+ if (ret) {
+ dev_err(chip->dev, "Failed to enable regulators: %d\n", ret);
+ return ret;
+ }
+ chip->enabled = true;
+
+ ret = regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_ENABLE_CTRL_WAKE);
+
+ if (ret) {
+ dev_err(chip->dev, "Failed to enable the chip: %d\n", ret);
+ ktd202x_chip_disable(chip);
+ }
+
+ return ret;
+}
+
+static bool ktd202x_chip_in_use(struct ktd202x *chip)
+{
+ int i;
+
+ for (i = 0; i < chip->num_leds; i++) {
+ if (chip->leds[i].cdev.brightness)
+ return true;
+ }
+
+ return false;
+}
+
+static int ktd202x_brightness_set(struct ktd202x_led *led,
+ struct mc_subled *subleds,
+ unsigned int num_channels)
+{
+ bool mode_blink = false;
+ int channel;
+ int state;
+ int ret;
+ int i;
+
+ if (ktd202x_chip_in_use(led->chip)) {
+ ret = ktd202x_chip_enable(led->chip);
+ if (ret)
+ return ret;
+ }
+
+ ret = regmap_read(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL, &state);
+ if (ret)
+ return ret;
+
+ /*
+ * In multicolor case, assume blink mode if PWM is set for at least one
+ * channel because another channel cannot be in state ON at the same time
+ */
+ for (i = 0; i < num_channels; i++) {
+ int channel_state;
+
+ channel = subleds[i].channel;
+ channel_state = (state >> 2 * channel) & KTD202X_CHANNEL_CTRL_MASK(0);
+ if (channel_state == KTD202X_CHANNEL_CTRL_OFF)
+ continue;
+ mode_blink = channel_state == KTD202X_CHANNEL_CTRL_PWM1(0);
+ break;
+ }
+
+ for (i = 0; i < num_channels; i++) {
+ enum led_brightness brightness;
+ int mode;
+
+ brightness = subleds[i].brightness;
+ channel = subleds[i].channel;
+
+ if (brightness) {
+ /* Register expects brightness between 0 and MAX_BRIGHTNESS - 1 */
+ ret = regmap_write(led->chip->regmap, KTD202X_REG_LED_IOUT(channel),
+ brightness - 1);
+ if (ret)
+ return ret;
+
+ if (mode_blink)
+ mode = KTD202X_CHANNEL_CTRL_PWM1(channel);
+ else
+ mode = KTD202X_CHANNEL_CTRL_ON(channel);
+ } else {
+ mode = KTD202X_CHANNEL_CTRL_OFF;
+ }
+ ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
+ KTD202X_CHANNEL_CTRL_MASK(channel), mode);
+ if (ret)
+ return ret;
+ }
+
+ if (!ktd202x_chip_in_use(led->chip))
+ return ktd202x_chip_disable(led->chip);
+
+ return 0;
+}
+
+static int ktd202x_brightness_single_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev);
+ struct mc_subled info;
+ int ret;
+
+ cdev->brightness = value;
+
+ mutex_lock(&led->chip->mutex);
+
+ info.brightness = value;
+ info.channel = led->index;
+ ret = ktd202x_brightness_set(led, &info, 1);
+
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int ktd202x_brightness_mc_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev);
+ int ret;
+
+ cdev->brightness = value;
+
+ mutex_lock(&led->chip->mutex);
+
+ led_mc_calc_color_components(mc, value);
+ ret = ktd202x_brightness_set(led, mc->subled_info, mc->num_colors);
+
+ mutex_unlock(&led->chip->mutex);
+
+ return ret;
+}
+
+static int ktd202x_blink_set(struct ktd202x_led *led, unsigned long *delay_on,
+ unsigned long *delay_off, struct mc_subled *subleds,
+ unsigned int num_channels)
+{
+ unsigned long delay_total_ms;
+ int ret, num_steps, on;
+ u8 ctrl_mask = 0;
+ u8 ctrl_pwm1 = 0;
+ u8 ctrl_on = 0;
+ int i;
+
+ mutex_lock(&led->chip->mutex);
+
+ for (i = 0; i < num_channels; i++) {
+ int channel = subleds[i].channel;
+
+ ctrl_mask |= KTD202X_CHANNEL_CTRL_MASK(channel);
+ ctrl_on |= KTD202X_CHANNEL_CTRL_ON(channel);
+ ctrl_pwm1 |= KTD202X_CHANNEL_CTRL_PWM1(channel);
+ }
+
+ /* Never off - brightness is already set, disable blinking */
+ if (!*delay_off) {
+ ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
+ ctrl_mask, ctrl_on);
+ goto out;
+ }
+
+ /* Convert into values the HW will understand. */
+
+ /* Integer representation of time of flash period */
+ num_steps = (*delay_on + *delay_off - KTD202X_FLASH_PERIOD_MIN_MS) /
+ KTD202X_FLASH_PERIOD_STEP_MS;
+ num_steps = clamp(num_steps, 0, KTD202X_FLASH_PERIOD_MAX_STEPS);
+
+ /* Integer representation of percentage of LED ON time */
+ on = (*delay_on * KTD202X_FLASH_ON_MAX) / (*delay_on + *delay_off);
+
+ /* Actually used delay_{on,off} values */
+ delay_total_ms = num_steps * KTD202X_FLASH_PERIOD_STEP_MS + KTD202X_FLASH_PERIOD_MIN_MS;
+ *delay_on = (delay_total_ms * on) / KTD202X_FLASH_ON_MAX;
+ *delay_off = delay_total_ms - *delay_on;
+
+ /* Set timings */
+ ret = regmap_write(led->chip->regmap, KTD202X_REG_FLASH_PERIOD, num_steps);
+ if (ret)
+ goto out;
+
+ ret = regmap_write(led->chip->regmap, KTD202X_REG_PWM1_TIMER, on);
+ if (ret)
+ goto out;
+
+ ret = regmap_update_bits(led->chip->regmap, KTD202X_REG_CHANNEL_CTRL,
+ ctrl_mask, ctrl_pwm1);
+out:
+ mutex_unlock(&led->chip->mutex);
+ return ret;
+}
+
+static int ktd202x_blink_single_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct ktd202x_led *led = container_of(cdev, struct ktd202x_led, cdev);
+ struct mc_subled info;
+ int ret;
+
+ if (!cdev->brightness) {
+ ret = ktd202x_brightness_single_set(cdev, KTD202X_MAX_BRIGHTNESS);
+ if (ret)
+ return ret;
+ }
+
+ /* If no blink specified, default to 1 Hz. */
+ if (!*delay_off && !*delay_on) {
+ *delay_off = 500;
+ *delay_on = 500;
+ }
+
+ /* Never on - just set to off */
+ if (!*delay_on)
+ return ktd202x_brightness_single_set(cdev, LED_OFF);
+
+ info.channel = led->index;
+
+ return ktd202x_blink_set(led, delay_on, delay_off, &info, 1);
+}
+
+static int ktd202x_blink_mc_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct ktd202x_led *led = container_of(mc, struct ktd202x_led, mcdev);
+ int ret;
+
+ if (!cdev->brightness) {
+ ret = ktd202x_brightness_mc_set(cdev, KTD202X_MAX_BRIGHTNESS);
+ if (ret)
+ return ret;
+ }
+
+ /* If no blink specified, default to 1 Hz. */
+ if (!*delay_off && !*delay_on) {
+ *delay_off = 500;
+ *delay_on = 500;
+ }
+
+ /* Never on - just set to off */
+ if (!*delay_on)
+ return ktd202x_brightness_mc_set(cdev, LED_OFF);
+
+ return ktd202x_blink_set(led, delay_on, delay_off, mc->subled_info,
+ mc->num_colors);
+}
+
+static int ktd202x_setup_led_rgb(struct ktd202x *chip, struct fwnode_handle *fwnode,
+ struct ktd202x_led *led, struct led_init_data *init_data)
+{
+ struct fwnode_handle *child;
+ struct led_classdev *cdev;
+ struct mc_subled *info;
+ int num_channels;
+ int i = 0;
+
+ num_channels = 0;
+ fwnode_for_each_child_node(fwnode, child)
+ num_channels++;
+
+ if (!num_channels || num_channels > chip->num_leds)
+ return -EINVAL;
+
+ info = devm_kcalloc(chip->dev, num_channels, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ fwnode_for_each_child_node(fwnode, child) {
+ u32 mono_color;
+ u32 reg;
+ int ret;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret != 0 || reg >= chip->num_leds) {
+ dev_err(chip->dev, "invalid 'reg' of %pfw\n", child);
+ fwnode_handle_put(child);
+ return ret;
+ }
+
+ ret = fwnode_property_read_u32(child, "color", &mono_color);
+ if (ret < 0 && ret != -EINVAL) {
+ dev_err(chip->dev, "failed to parse 'color' of %pfw\n", child);
+ fwnode_handle_put(child);
+ return ret;
+ }
+
+ info[i].color_index = mono_color;
+ info[i].channel = reg;
+ info[i].intensity = KTD202X_MAX_BRIGHTNESS;
+ i++;
+ }
+
+ led->mcdev.subled_info = info;
+ led->mcdev.num_colors = num_channels;
+
+ cdev = &led->mcdev.led_cdev;
+ cdev->brightness_set_blocking = ktd202x_brightness_mc_set;
+ cdev->blink_set = ktd202x_blink_mc_set;
+
+ return devm_led_classdev_multicolor_register_ext(chip->dev, &led->mcdev, init_data);
+}
+
+static int ktd202x_setup_led_single(struct ktd202x *chip, struct fwnode_handle *fwnode,
+ struct ktd202x_led *led, struct led_init_data *init_data)
+{
+ struct led_classdev *cdev;
+ u32 reg;
+ int ret;
+
+ ret = fwnode_property_read_u32(fwnode, "reg", &reg);
+ if (ret != 0 || reg >= chip->num_leds) {
+ dev_err(chip->dev, "invalid 'reg' of %pfw\n", fwnode);
+ return -EINVAL;
+ }
+ led->index = reg;
+
+ cdev = &led->cdev;
+ cdev->brightness_set_blocking = ktd202x_brightness_single_set;
+ cdev->blink_set = ktd202x_blink_single_set;
+
+ return devm_led_classdev_register_ext(chip->dev, &led->cdev, init_data);
+}
+
+static int ktd202x_add_led(struct ktd202x *chip, struct fwnode_handle *fwnode, unsigned int index)
+{
+ struct ktd202x_led *led = &chip->leds[index];
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ u32 color;
+ int ret;
+
+ /* Color property is optional in single color case */
+ ret = fwnode_property_read_u32(fwnode, "color", &color);
+ if (ret < 0 && ret != -EINVAL) {
+ dev_err(chip->dev, "failed to parse 'color' of %pfw\n", fwnode);
+ return ret;
+ }
+
+ led->chip = chip;
+ init_data.fwnode = fwnode;
+
+ if (color == LED_COLOR_ID_RGB) {
+ cdev = &led->mcdev.led_cdev;
+ ret = ktd202x_setup_led_rgb(chip, fwnode, led, &init_data);
+ } else {
+ cdev = &led->cdev;
+ ret = ktd202x_setup_led_single(chip, fwnode, led, &init_data);
+ }
+
+ if (ret) {
+ dev_err(chip->dev, "unable to register %s\n", cdev->name);
+ return ret;
+ }
+
+ cdev->max_brightness = KTD202X_MAX_BRIGHTNESS;
+
+ return 0;
+}
+
+static int ktd202x_probe_fw(struct ktd202x *chip)
+{
+ struct device *dev = chip->dev;
+ int count;
+ int i = 0;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > chip->num_leds)
+ return -EINVAL;
+
+ regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
+
+ /* Allow the device to execute the complete reset */
+ usleep_range(200, 300);
+
+ device_for_each_child_node_scoped(dev, child) {
+ int ret = ktd202x_add_led(chip, child, i);
+
+ if (ret)
+ return ret;
+
+ i++;
+ }
+
+ return 0;
+}
+
+static const struct regmap_config ktd202x_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0x09,
+ .cache_type = REGCACHE_FLAT,
+ .reg_defaults = ktd202x_reg_defaults,
+ .num_reg_defaults = ARRAY_SIZE(ktd202x_reg_defaults),
+};
+
+static int ktd202x_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct ktd202x *chip;
+ int count;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > KTD202X_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL, "Incorrect number of leds (%d)", count);
+
+ chip = devm_kzalloc(dev, struct_size(chip, leds, count), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = dev;
+ i2c_set_clientdata(client, chip);
+
+ chip->regmap = devm_regmap_init_i2c(client, &ktd202x_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ ret = dev_err_probe(dev, PTR_ERR(chip->regmap),
+ "Failed to allocate register map.\n");
+ return ret;
+ }
+
+ ret = devm_mutex_init(dev, &chip->mutex);
+ if (ret)
+ return ret;
+
+ chip->num_leds = (unsigned long)i2c_get_match_data(client);
+
+ chip->regulators[0].supply = "vin";
+ chip->regulators[1].supply = "vio";
+ ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(chip->regulators), chip->regulators);
+ if (ret < 0) {
+ dev_err_probe(dev, ret, "Failed to request regulators.\n");
+ return ret;
+ }
+
+ ret = regulator_bulk_enable(ARRAY_SIZE(chip->regulators), chip->regulators);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to enable regulators.\n");
+ return ret;
+ }
+
+ ret = ktd202x_probe_fw(chip);
+ if (ret < 0) {
+ regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
+ return ret;
+ }
+
+ ret = regulator_bulk_disable(ARRAY_SIZE(chip->regulators), chip->regulators);
+ if (ret) {
+ dev_err_probe(dev, ret, "Failed to disable regulators.\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void ktd202x_remove(struct i2c_client *client)
+{
+ struct ktd202x *chip = i2c_get_clientdata(client);
+
+ ktd202x_chip_disable(chip);
+}
+
+static void ktd202x_shutdown(struct i2c_client *client)
+{
+ struct ktd202x *chip = i2c_get_clientdata(client);
+
+ /* Reset registers to make sure all LEDs are off before shutdown */
+ regmap_write(chip->regmap, KTD202X_REG_RESET_CONTROL, KTD202X_RSTR_RESET);
+}
+
+static const struct i2c_device_id ktd202x_id[] = {
+ {"ktd2026", KTD2026_NUM_LEDS},
+ {"ktd2027", KTD2027_NUM_LEDS},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, ktd202x_id);
+
+static const struct of_device_id ktd202x_match_table[] = {
+ { .compatible = "kinetic,ktd2026", .data = (void *)KTD2026_NUM_LEDS },
+ { .compatible = "kinetic,ktd2027", .data = (void *)KTD2027_NUM_LEDS },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ktd202x_match_table);
+
+static struct i2c_driver ktd202x_driver = {
+ .driver = {
+ .name = "leds-ktd202x",
+ .of_match_table = ktd202x_match_table,
+ },
+ .probe = ktd202x_probe,
+ .remove = ktd202x_remove,
+ .shutdown = ktd202x_shutdown,
+ .id_table = ktd202x_id,
+};
+module_i2c_driver(ktd202x_driver);
+
+MODULE_AUTHOR("André Apitzsch <git@apitzsch.eu>");
+MODULE_DESCRIPTION("Kinetic KTD2026/7 LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/rgb/leds-mt6370-rgb.c b/drivers/leds/rgb/leds-mt6370-rgb.c
new file mode 100644
index 000000000000..c5927d0eb830
--- /dev/null
+++ b/drivers/leds/rgb/leds-mt6370-rgb.c
@@ -0,0 +1,995 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2023 Richtek Technology Corp.
+ *
+ * Authors:
+ * ChiYuan Huang <cy_huang@richtek.com>
+ * Alice Chen <alice_chen@richtek.com>
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bitops.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/linear_range.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regmap.h>
+#include <linux/util_macros.h>
+
+#include <linux/unaligned.h>
+
+enum {
+ MT6370_LED_ISNK1 = 0,
+ MT6370_LED_ISNK2,
+ MT6370_LED_ISNK3,
+ MT6370_LED_ISNK4,
+ MT6370_MAX_LEDS
+};
+
+enum mt6370_led_mode {
+ MT6370_LED_PWM_MODE = 0,
+ MT6370_LED_BREATH_MODE,
+ MT6370_LED_REG_MODE,
+ MT6370_LED_MAX_MODE
+};
+
+enum mt6370_led_field {
+ F_RGB_EN = 0,
+ F_CHGIND_EN,
+ F_LED1_CURR,
+ F_LED2_CURR,
+ F_LED3_CURR,
+ F_LED4_CURR,
+ F_LED1_MODE,
+ F_LED2_MODE,
+ F_LED3_MODE,
+ F_LED4_MODE,
+ F_LED1_DUTY,
+ F_LED2_DUTY,
+ F_LED3_DUTY,
+ F_LED4_DUTY,
+ F_LED1_FREQ,
+ F_LED2_FREQ,
+ F_LED3_FREQ,
+ F_LED4_FREQ,
+ F_MAX_FIELDS
+};
+
+enum mt6370_led_ranges {
+ R_LED123_CURR = 0,
+ R_LED4_CURR,
+ R_LED_TRFON,
+ R_LED_TOFF,
+ R_MAX_RANGES
+};
+
+enum mt6370_pattern {
+ P_LED_TR1 = 0,
+ P_LED_TR2,
+ P_LED_TF1,
+ P_LED_TF2,
+ P_LED_TON,
+ P_LED_TOFF,
+ P_MAX_PATTERNS
+};
+
+#define MT6370_REG_DEV_INFO 0x100
+#define MT6370_REG_RGB1_DIM 0x182
+#define MT6370_REG_RGB2_DIM 0x183
+#define MT6370_REG_RGB3_DIM 0x184
+#define MT6370_REG_RGB_EN 0x185
+#define MT6370_REG_RGB1_ISNK 0x186
+#define MT6370_REG_RGB2_ISNK 0x187
+#define MT6370_REG_RGB3_ISNK 0x188
+#define MT6370_REG_RGB1_TR 0x189
+#define MT6370_REG_RGB_CHRIND_DIM 0x192
+#define MT6370_REG_RGB_CHRIND_CTRL 0x193
+#define MT6370_REG_RGB_CHRIND_TR 0x194
+
+#define MT6372_REG_RGB_EN 0x182
+#define MT6372_REG_RGB1_ISNK 0x183
+#define MT6372_REG_RGB2_ISNK 0x184
+#define MT6372_REG_RGB3_ISNK 0x185
+#define MT6372_REG_RGB4_ISNK 0x186
+#define MT6372_REG_RGB1_DIM 0x187
+#define MT6372_REG_RGB2_DIM 0x188
+#define MT6372_REG_RGB3_DIM 0x189
+#define MT6372_REG_RGB4_DIM 0x18A
+#define MT6372_REG_RGB12_FREQ 0x18B
+#define MT6372_REG_RGB34_FREQ 0x18C
+#define MT6372_REG_RGB1_TR 0x18D
+
+#define MT6370_VENDOR_ID_MASK GENMASK(7, 4)
+#define MT6372_VENDOR_ID 0x9
+#define MT6372C_VENDOR_ID 0xb
+#define MT6370_CHEN_BIT(id) BIT(MT6370_LED_ISNK4 - id)
+#define MT6370_VIRTUAL_MULTICOLOR 5
+#define MC_CHANNEL_NUM 3
+#define MT6370_PWM_DUTY (BIT(5) - 1)
+#define MT6372_PWM_DUTY (BIT(8) - 1)
+
+struct mt6370_led {
+ /*
+ * If the color of the LED in DT is set to
+ * - 'LED_COLOR_ID_RGB'
+ * - 'LED_COLOR_ID_MULTI'
+ * The member 'index' of this struct will be set to
+ * 'MT6370_VIRTUAL_MULTICOLOR'.
+ * If so, this LED will choose 'struct led_classdev_mc mc' to use.
+ * Instead, if the member 'index' of this struct is set to
+ * 'MT6370_LED_ISNK1' ~ 'MT6370_LED_ISNK4', then this LED will choose
+ * 'struct led_classdev isink' to use.
+ */
+ union {
+ struct led_classdev isink;
+ struct led_classdev_mc mc;
+ };
+ struct mt6370_priv *priv;
+ enum led_default_state default_state;
+ u32 index;
+};
+
+struct mt6370_pdata {
+ const unsigned int *tfreq;
+ unsigned int tfreq_len;
+ u16 reg_rgb1_tr;
+ s16 reg_rgb_chrind_tr;
+ u8 pwm_duty;
+};
+
+struct mt6370_priv {
+ /* Per LED access lock */
+ struct mutex lock;
+ struct regmap *regmap;
+ struct regmap_field *fields[F_MAX_FIELDS];
+ const struct reg_field *reg_fields;
+ const struct linear_range *ranges;
+ const struct mt6370_pdata *pdata;
+ unsigned int leds_count;
+ unsigned int leds_active;
+ struct mt6370_led leds[] __counted_by(leds_count);
+};
+
+static const struct reg_field common_reg_fields[F_MAX_FIELDS] = {
+ [F_RGB_EN] = REG_FIELD(MT6370_REG_RGB_EN, 4, 7),
+ [F_CHGIND_EN] = REG_FIELD(MT6370_REG_RGB_CHRIND_DIM, 7, 7),
+ [F_LED1_CURR] = REG_FIELD(MT6370_REG_RGB1_ISNK, 0, 2),
+ [F_LED2_CURR] = REG_FIELD(MT6370_REG_RGB2_ISNK, 0, 2),
+ [F_LED3_CURR] = REG_FIELD(MT6370_REG_RGB3_ISNK, 0, 2),
+ [F_LED4_CURR] = REG_FIELD(MT6370_REG_RGB_CHRIND_CTRL, 0, 1),
+ [F_LED1_MODE] = REG_FIELD(MT6370_REG_RGB1_DIM, 5, 6),
+ [F_LED2_MODE] = REG_FIELD(MT6370_REG_RGB2_DIM, 5, 6),
+ [F_LED3_MODE] = REG_FIELD(MT6370_REG_RGB3_DIM, 5, 6),
+ [F_LED4_MODE] = REG_FIELD(MT6370_REG_RGB_CHRIND_DIM, 5, 6),
+ [F_LED1_DUTY] = REG_FIELD(MT6370_REG_RGB1_DIM, 0, 4),
+ [F_LED2_DUTY] = REG_FIELD(MT6370_REG_RGB2_DIM, 0, 4),
+ [F_LED3_DUTY] = REG_FIELD(MT6370_REG_RGB3_DIM, 0, 4),
+ [F_LED4_DUTY] = REG_FIELD(MT6370_REG_RGB_CHRIND_DIM, 0, 4),
+ [F_LED1_FREQ] = REG_FIELD(MT6370_REG_RGB1_ISNK, 3, 5),
+ [F_LED2_FREQ] = REG_FIELD(MT6370_REG_RGB2_ISNK, 3, 5),
+ [F_LED3_FREQ] = REG_FIELD(MT6370_REG_RGB3_ISNK, 3, 5),
+ [F_LED4_FREQ] = REG_FIELD(MT6370_REG_RGB_CHRIND_CTRL, 2, 4),
+};
+
+static const struct reg_field mt6372_reg_fields[F_MAX_FIELDS] = {
+ [F_RGB_EN] = REG_FIELD(MT6372_REG_RGB_EN, 4, 7),
+ [F_CHGIND_EN] = REG_FIELD(MT6372_REG_RGB_EN, 3, 3),
+ [F_LED1_CURR] = REG_FIELD(MT6372_REG_RGB1_ISNK, 0, 3),
+ [F_LED2_CURR] = REG_FIELD(MT6372_REG_RGB2_ISNK, 0, 3),
+ [F_LED3_CURR] = REG_FIELD(MT6372_REG_RGB3_ISNK, 0, 3),
+ [F_LED4_CURR] = REG_FIELD(MT6372_REG_RGB4_ISNK, 0, 3),
+ [F_LED1_MODE] = REG_FIELD(MT6372_REG_RGB1_ISNK, 6, 7),
+ [F_LED2_MODE] = REG_FIELD(MT6372_REG_RGB2_ISNK, 6, 7),
+ [F_LED3_MODE] = REG_FIELD(MT6372_REG_RGB3_ISNK, 6, 7),
+ [F_LED4_MODE] = REG_FIELD(MT6372_REG_RGB4_ISNK, 6, 7),
+ [F_LED1_DUTY] = REG_FIELD(MT6372_REG_RGB1_DIM, 0, 7),
+ [F_LED2_DUTY] = REG_FIELD(MT6372_REG_RGB2_DIM, 0, 7),
+ [F_LED3_DUTY] = REG_FIELD(MT6372_REG_RGB3_DIM, 0, 7),
+ [F_LED4_DUTY] = REG_FIELD(MT6372_REG_RGB4_DIM, 0, 7),
+ [F_LED1_FREQ] = REG_FIELD(MT6372_REG_RGB12_FREQ, 5, 7),
+ [F_LED2_FREQ] = REG_FIELD(MT6372_REG_RGB12_FREQ, 2, 4),
+ [F_LED3_FREQ] = REG_FIELD(MT6372_REG_RGB34_FREQ, 5, 7),
+ [F_LED4_FREQ] = REG_FIELD(MT6372_REG_RGB34_FREQ, 2, 4),
+};
+
+/* Current unit: microamp, time unit: millisecond */
+static const struct linear_range common_led_ranges[R_MAX_RANGES] = {
+ [R_LED123_CURR] = LINEAR_RANGE(4000, 1, 6, 4000),
+ [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 3, 2000),
+ [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 200),
+ [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 400),
+};
+
+static const struct linear_range mt6372_led_ranges[R_MAX_RANGES] = {
+ [R_LED123_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
+ [R_LED4_CURR] = LINEAR_RANGE(2000, 1, 14, 2000),
+ [R_LED_TRFON] = LINEAR_RANGE(125, 0, 15, 250),
+ [R_LED_TOFF] = LINEAR_RANGE(250, 0, 15, 500),
+};
+
+static const unsigned int common_tfreqs[] = {
+ 10000, 5000, 2000, 1000, 500, 200, 5, 1,
+};
+
+static const unsigned int mt6372_tfreqs[] = {
+ 8000, 4000, 2000, 1000, 500, 250, 8, 4,
+};
+
+static const struct mt6370_pdata common_pdata = {
+ .tfreq = common_tfreqs,
+ .tfreq_len = ARRAY_SIZE(common_tfreqs),
+ .pwm_duty = MT6370_PWM_DUTY,
+ .reg_rgb1_tr = MT6370_REG_RGB1_TR,
+ .reg_rgb_chrind_tr = MT6370_REG_RGB_CHRIND_TR,
+};
+
+static const struct mt6370_pdata mt6372_pdata = {
+ .tfreq = mt6372_tfreqs,
+ .tfreq_len = ARRAY_SIZE(mt6372_tfreqs),
+ .pwm_duty = MT6372_PWM_DUTY,
+ .reg_rgb1_tr = MT6372_REG_RGB1_TR,
+ .reg_rgb_chrind_tr = -1,
+};
+
+static enum mt6370_led_field mt6370_get_led_current_field(unsigned int led_no)
+{
+ switch (led_no) {
+ case MT6370_LED_ISNK1:
+ return F_LED1_CURR;
+ case MT6370_LED_ISNK2:
+ return F_LED2_CURR;
+ case MT6370_LED_ISNK3:
+ return F_LED3_CURR;
+ default:
+ return F_LED4_CURR;
+ }
+}
+
+static int mt6370_set_led_brightness(struct mt6370_priv *priv, unsigned int led_no,
+ unsigned int level)
+{
+ enum mt6370_led_field sel_field;
+
+ sel_field = mt6370_get_led_current_field(led_no);
+
+ return regmap_field_write(priv->fields[sel_field], level);
+}
+
+static int mt6370_get_led_brightness(struct mt6370_priv *priv, unsigned int led_no,
+ unsigned int *level)
+{
+ enum mt6370_led_field sel_field;
+
+ sel_field = mt6370_get_led_current_field(led_no);
+
+ return regmap_field_read(priv->fields[sel_field], level);
+}
+
+static int mt6370_set_led_duty(struct mt6370_priv *priv, unsigned int led_no, unsigned int ton,
+ unsigned int toff)
+{
+ const struct mt6370_pdata *pdata = priv->pdata;
+ enum mt6370_led_field sel_field;
+ unsigned int divisor, ratio;
+
+ divisor = pdata->pwm_duty;
+ ratio = ton * divisor / (ton + toff);
+
+ switch (led_no) {
+ case MT6370_LED_ISNK1:
+ sel_field = F_LED1_DUTY;
+ break;
+ case MT6370_LED_ISNK2:
+ sel_field = F_LED2_DUTY;
+ break;
+ case MT6370_LED_ISNK3:
+ sel_field = F_LED3_DUTY;
+ break;
+ default:
+ sel_field = F_LED4_DUTY;
+ break;
+ }
+
+ return regmap_field_write(priv->fields[sel_field], ratio);
+}
+
+static int mt6370_set_led_freq(struct mt6370_priv *priv, unsigned int led_no, unsigned int ton,
+ unsigned int toff)
+{
+ const struct mt6370_pdata *pdata = priv->pdata;
+ enum mt6370_led_field sel_field;
+ unsigned int tfreq_len = pdata->tfreq_len;
+ unsigned int tsum, sel;
+
+ tsum = ton + toff;
+
+ if (tsum > pdata->tfreq[0] || tsum < pdata->tfreq[tfreq_len - 1])
+ return -EOPNOTSUPP;
+
+ sel = find_closest_descending(tsum, pdata->tfreq, tfreq_len);
+
+ switch (led_no) {
+ case MT6370_LED_ISNK1:
+ sel_field = F_LED1_FREQ;
+ break;
+ case MT6370_LED_ISNK2:
+ sel_field = F_LED2_FREQ;
+ break;
+ case MT6370_LED_ISNK3:
+ sel_field = F_LED3_FREQ;
+ break;
+ default:
+ sel_field = F_LED4_FREQ;
+ break;
+ }
+
+ return regmap_field_write(priv->fields[sel_field], sel);
+}
+
+static void mt6370_get_breath_reg_base(struct mt6370_priv *priv, unsigned int led_no,
+ unsigned int *base)
+{
+ const struct mt6370_pdata *pdata = priv->pdata;
+
+ if (pdata->reg_rgb_chrind_tr < 0) {
+ *base = pdata->reg_rgb1_tr + led_no * 3;
+ return;
+ }
+
+ switch (led_no) {
+ case MT6370_LED_ISNK1:
+ case MT6370_LED_ISNK2:
+ case MT6370_LED_ISNK3:
+ *base = pdata->reg_rgb1_tr + led_no * 3;
+ break;
+ default:
+ *base = pdata->reg_rgb_chrind_tr;
+ break;
+ }
+}
+
+static int mt6370_gen_breath_pattern(struct mt6370_priv *priv, struct led_pattern *pattern, u32 len,
+ u8 *pattern_val, u32 val_len)
+{
+ enum mt6370_led_ranges sel_range;
+ struct led_pattern *curr;
+ unsigned int sel;
+ u32 val = 0;
+ int i;
+
+ if (len < P_MAX_PATTERNS && val_len < P_MAX_PATTERNS / 2)
+ return -EINVAL;
+
+ /*
+ * Pattern list
+ * tr1: byte 0, b'[7:4]
+ * tr2: byte 0, b'[3:0]
+ * tf1: byte 1, b'[7:4]
+ * tf2: byte 1, b'[3:0]
+ * ton: byte 2, b'[7:4]
+ * toff: byte 2, b'[3:0]
+ */
+ for (i = 0; i < P_MAX_PATTERNS; i++) {
+ curr = pattern + i;
+
+ sel_range = i == P_LED_TOFF ? R_LED_TOFF : R_LED_TRFON;
+
+ linear_range_get_selector_within(priv->ranges + sel_range, curr->delta_t, &sel);
+
+ if (i % 2) {
+ val |= sel;
+ } else {
+ val <<= 8;
+ val |= sel << 4;
+ }
+ }
+
+ put_unaligned_be24(val, pattern_val);
+
+ return 0;
+}
+
+static int mt6370_set_led_mode(struct mt6370_priv *priv, unsigned int led_no,
+ enum mt6370_led_mode mode)
+{
+ enum mt6370_led_field sel_field;
+
+ switch (led_no) {
+ case MT6370_LED_ISNK1:
+ sel_field = F_LED1_MODE;
+ break;
+ case MT6370_LED_ISNK2:
+ sel_field = F_LED2_MODE;
+ break;
+ case MT6370_LED_ISNK3:
+ sel_field = F_LED3_MODE;
+ break;
+ default:
+ sel_field = F_LED4_MODE;
+ break;
+ }
+
+ return regmap_field_write(priv->fields[sel_field], mode);
+}
+
+static int mt6370_mc_brightness_set(struct led_classdev *lcdev, enum led_brightness level)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+ struct mt6370_led *led = container_of(mccdev, struct mt6370_led, mc);
+ struct mt6370_priv *priv = led->priv;
+ struct mc_subled *subled;
+ unsigned int enable, disable;
+ int i, ret;
+
+ mutex_lock(&priv->lock);
+
+ led_mc_calc_color_components(mccdev, level);
+
+ ret = regmap_field_read(priv->fields[F_RGB_EN], &enable);
+ if (ret)
+ goto out_unlock;
+
+ disable = enable;
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ u32 brightness;
+
+ subled = mccdev->subled_info + i;
+ brightness = min(subled->brightness, lcdev->max_brightness);
+ disable &= ~MT6370_CHEN_BIT(subled->channel);
+
+ if (level == 0) {
+ enable &= ~MT6370_CHEN_BIT(subled->channel);
+
+ ret = mt6370_set_led_mode(priv, subled->channel, MT6370_LED_REG_MODE);
+ if (ret)
+ goto out_unlock;
+
+ continue;
+ }
+
+ if (brightness == 0) {
+ enable &= ~MT6370_CHEN_BIT(subled->channel);
+ continue;
+ }
+
+ enable |= MT6370_CHEN_BIT(subled->channel);
+
+ ret = mt6370_set_led_brightness(priv, subled->channel, brightness);
+ if (ret)
+ goto out_unlock;
+ }
+
+ ret = regmap_field_write(priv->fields[F_RGB_EN], disable);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_field_write(priv->fields[F_RGB_EN], enable);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6370_mc_blink_set(struct led_classdev *lcdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+ struct mt6370_led *led = container_of(mccdev, struct mt6370_led, mc);
+ struct mt6370_priv *priv = led->priv;
+ struct mc_subled *subled;
+ unsigned int enable, disable;
+ int i, ret;
+
+ mutex_lock(&priv->lock);
+
+ if (!*delay_on && !*delay_off)
+ *delay_on = *delay_off = 500;
+
+ ret = regmap_field_read(priv->fields[F_RGB_EN], &enable);
+ if (ret)
+ goto out_unlock;
+
+ disable = enable;
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ subled = mccdev->subled_info + i;
+
+ disable &= ~MT6370_CHEN_BIT(subled->channel);
+
+ ret = mt6370_set_led_duty(priv, subled->channel, *delay_on, *delay_off);
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_freq(priv, subled->channel, *delay_on, *delay_off);
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_mode(priv, subled->channel, MT6370_LED_PWM_MODE);
+ if (ret)
+ goto out_unlock;
+ }
+
+ /* Toggle to make pattern timing the same */
+ ret = regmap_field_write(priv->fields[F_RGB_EN], disable);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_field_write(priv->fields[F_RGB_EN], enable);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6370_mc_pattern_set(struct led_classdev *lcdev, struct led_pattern *pattern, u32 len,
+ int repeat)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(lcdev);
+ struct mt6370_led *led = container_of(mccdev, struct mt6370_led, mc);
+ struct mt6370_priv *priv = led->priv;
+ struct mc_subled *subled;
+ unsigned int reg_base, enable, disable;
+ u8 params[P_MAX_PATTERNS / 2];
+ int i, ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = mt6370_gen_breath_pattern(priv, pattern, len, params, sizeof(params));
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_field_read(priv->fields[F_RGB_EN], &enable);
+ if (ret)
+ goto out_unlock;
+
+ disable = enable;
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ subled = mccdev->subled_info + i;
+
+ mt6370_get_breath_reg_base(priv, subled->channel, &reg_base);
+ disable &= ~MT6370_CHEN_BIT(subled->channel);
+
+ ret = regmap_raw_write(priv->regmap, reg_base, params, sizeof(params));
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_mode(priv, subled->channel, MT6370_LED_BREATH_MODE);
+ if (ret)
+ goto out_unlock;
+ }
+
+ /* Toggle to make pattern timing be the same */
+ ret = regmap_field_write(priv->fields[F_RGB_EN], disable);
+ if (ret)
+ goto out_unlock;
+
+ ret = regmap_field_write(priv->fields[F_RGB_EN], enable);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static inline int mt6370_mc_pattern_clear(struct led_classdev *lcdev)
+{
+ struct led_classdev_mc *mccdev = lcdev_to_mccdev(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 = 0;
+
+ mutex_lock(&led->priv->lock);
+
+ for (i = 0; i < mccdev->num_colors; i++) {
+ subled = mccdev->subled_info + i;
+
+ ret = mt6370_set_led_mode(priv, subled->channel, MT6370_LED_REG_MODE);
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&led->priv->lock);
+
+ return ret;
+}
+
+static int mt6370_isnk_brightness_set(struct led_classdev *lcdev,
+ enum led_brightness level)
+{
+ struct mt6370_led *led = container_of(lcdev, struct mt6370_led, isink);
+ struct mt6370_priv *priv = led->priv;
+ unsigned int enable;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = regmap_field_read(priv->fields[F_RGB_EN], &enable);
+ if (ret)
+ goto out_unlock;
+
+ if (level == 0) {
+ enable &= ~MT6370_CHEN_BIT(led->index);
+
+ ret = mt6370_set_led_mode(priv, led->index, MT6370_LED_REG_MODE);
+ if (ret)
+ goto out_unlock;
+ } else {
+ enable |= MT6370_CHEN_BIT(led->index);
+
+ ret = mt6370_set_led_brightness(priv, led->index, level);
+ if (ret)
+ goto out_unlock;
+ }
+
+ ret = regmap_field_write(priv->fields[F_RGB_EN], enable);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6370_isnk_blink_set(struct led_classdev *lcdev, unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct mt6370_led *led = container_of(lcdev, struct mt6370_led, isink);
+ struct mt6370_priv *priv = led->priv;
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ if (!*delay_on && !*delay_off)
+ *delay_on = *delay_off = 500;
+
+ ret = mt6370_set_led_duty(priv, led->index, *delay_on, *delay_off);
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_freq(priv, led->index, *delay_on, *delay_off);
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_mode(priv, led->index, MT6370_LED_PWM_MODE);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int mt6370_isnk_pattern_set(struct led_classdev *lcdev, struct led_pattern *pattern, u32 len,
+ int repeat)
+{
+ struct mt6370_led *led = container_of(lcdev, struct mt6370_led, isink);
+ struct mt6370_priv *priv = led->priv;
+ unsigned int reg_base;
+ u8 params[P_MAX_PATTERNS / 2];
+ int ret;
+
+ mutex_lock(&priv->lock);
+
+ ret = mt6370_gen_breath_pattern(priv, pattern, len, params, sizeof(params));
+ if (ret)
+ goto out_unlock;
+
+ mt6370_get_breath_reg_base(priv, led->index, &reg_base);
+
+ ret = regmap_raw_write(priv->regmap, reg_base, params, sizeof(params));
+ if (ret)
+ goto out_unlock;
+
+ ret = mt6370_set_led_mode(priv, led->index, MT6370_LED_BREATH_MODE);
+
+out_unlock:
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static inline int mt6370_isnk_pattern_clear(struct led_classdev *lcdev)
+{
+ struct mt6370_led *led = container_of(lcdev, struct mt6370_led, isink);
+ struct mt6370_priv *priv = led->priv;
+ int ret;
+
+ mutex_lock(&led->priv->lock);
+ ret = mt6370_set_led_mode(priv, led->index, MT6370_LED_REG_MODE);
+ mutex_unlock(&led->priv->lock);
+
+ return ret;
+}
+
+static int mt6370_assign_multicolor_info(struct device *dev, struct mt6370_led *led,
+ struct fwnode_handle *fwnode)
+{
+ struct mt6370_priv *priv = led->priv;
+ struct fwnode_handle *child;
+ struct mc_subled *sub_led;
+ u32 num_color = 0;
+ int ret;
+
+ sub_led = devm_kcalloc(dev, MC_CHANNEL_NUM, sizeof(*sub_led), GFP_KERNEL);
+ if (!sub_led)
+ return -ENOMEM;
+
+ fwnode_for_each_child_node(fwnode, child) {
+ u32 reg, color;
+
+ ret = fwnode_property_read_u32(child, "reg", &reg);
+ if (ret || reg > MT6370_LED_ISNK3 || priv->leds_active & BIT(reg)) {
+ fwnode_handle_put(child);
+ return -EINVAL;
+ }
+
+ ret = fwnode_property_read_u32(child, "color", &color);
+ if (ret) {
+ fwnode_handle_put(child);
+ return dev_err_probe(dev, ret, "LED %d, no color specified\n", led->index);
+ }
+
+ priv->leds_active |= BIT(reg);
+ sub_led[num_color].color_index = color;
+ sub_led[num_color].channel = reg;
+ sub_led[num_color].intensity = 0;
+ num_color++;
+ }
+
+ if (num_color < 2)
+ return dev_err_probe(dev, -EINVAL,
+ "Multicolor must include 2 or more LED channels\n");
+
+ led->mc.num_colors = num_color;
+ led->mc.subled_info = sub_led;
+
+ return 0;
+}
+
+static int mt6370_init_led_properties(struct device *dev, struct mt6370_led *led,
+ struct led_init_data *init_data)
+{
+ struct mt6370_priv *priv = led->priv;
+ struct led_classdev *lcdev;
+ enum mt6370_led_ranges sel_range;
+ u32 max_uA, max_level;
+ int ret;
+
+ if (led->index == MT6370_VIRTUAL_MULTICOLOR) {
+ ret = mt6370_assign_multicolor_info(dev, led, init_data->fwnode);
+ if (ret)
+ return ret;
+
+ lcdev = &led->mc.led_cdev;
+ lcdev->brightness_set_blocking = mt6370_mc_brightness_set;
+ lcdev->blink_set = mt6370_mc_blink_set;
+ lcdev->pattern_set = mt6370_mc_pattern_set;
+ lcdev->pattern_clear = mt6370_mc_pattern_clear;
+ } else {
+ lcdev = &led->isink;
+ lcdev->brightness_set_blocking = mt6370_isnk_brightness_set;
+ lcdev->blink_set = mt6370_isnk_blink_set;
+ lcdev->pattern_set = mt6370_isnk_pattern_set;
+ lcdev->pattern_clear = mt6370_isnk_pattern_clear;
+ }
+
+ ret = fwnode_property_read_u32(init_data->fwnode, "led-max-microamp", &max_uA);
+ if (ret) {
+ dev_warn(dev, "Not specified led-max-microamp, config to the minimum\n");
+ max_uA = 0;
+ }
+
+ if (led->index == MT6370_LED_ISNK4)
+ sel_range = R_LED4_CURR;
+ else
+ sel_range = R_LED123_CURR;
+
+ linear_range_get_selector_within(priv->ranges + sel_range, max_uA, &max_level);
+
+ lcdev->max_brightness = max_level;
+
+ led->default_state = led_init_default_state_get(init_data->fwnode);
+
+ return 0;
+}
+
+static int mt6370_isnk_init_default_state(struct mt6370_led *led)
+{
+ struct mt6370_priv *priv = led->priv;
+ unsigned int enable, level;
+ int ret;
+
+ ret = mt6370_get_led_brightness(priv, led->index, &level);
+ if (ret)
+ return ret;
+
+ ret = regmap_field_read(priv->fields[F_RGB_EN], &enable);
+ if (ret)
+ return ret;
+
+ if (!(enable & MT6370_CHEN_BIT(led->index)))
+ level = 0;
+
+ switch (led->default_state) {
+ case LEDS_DEFSTATE_ON:
+ led->isink.brightness = led->isink.max_brightness;
+ break;
+ case LEDS_DEFSTATE_KEEP:
+ led->isink.brightness = min(level, led->isink.max_brightness);
+ break;
+ default:
+ led->isink.brightness = 0;
+ break;
+ }
+
+ return mt6370_isnk_brightness_set(&led->isink, led->isink.brightness);
+}
+
+static int mt6370_multicolor_led_register(struct device *dev, struct mt6370_led *led,
+ struct led_init_data *init_data)
+{
+ int ret;
+
+ ret = mt6370_mc_brightness_set(&led->mc.led_cdev, 0);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't set multicolor brightness\n");
+
+ ret = devm_led_classdev_multicolor_register_ext(dev, &led->mc, init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't register multicolor\n");
+
+ return 0;
+}
+
+static int mt6370_led_register(struct device *dev, struct mt6370_led *led,
+ struct led_init_data *init_data)
+{
+ struct mt6370_priv *priv = led->priv;
+ int ret;
+
+ if (led->index == MT6370_VIRTUAL_MULTICOLOR)
+ return mt6370_multicolor_led_register(dev, led, init_data);
+
+ /* If ISNK4 is declared, change its mode from HW auto to SW control */
+ if (led->index == MT6370_LED_ISNK4) {
+ ret = regmap_field_write(priv->fields[F_CHGIND_EN], 1);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to set CHRIND to SW\n");
+ }
+
+ ret = mt6370_isnk_init_default_state(led);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to init %d isnk state\n", led->index);
+
+ ret = devm_led_classdev_register_ext(dev, &led->isink, init_data);
+ if (ret)
+ return dev_err_probe(dev, ret, "Couldn't register isink %d\n", led->index);
+
+ return 0;
+}
+
+static int mt6370_check_vendor_info(struct mt6370_priv *priv)
+{
+ unsigned int devinfo, vid;
+ int ret;
+
+ ret = regmap_read(priv->regmap, MT6370_REG_DEV_INFO, &devinfo);
+ if (ret)
+ return ret;
+
+ vid = FIELD_GET(MT6370_VENDOR_ID_MASK, devinfo);
+ if (vid == MT6372_VENDOR_ID || vid == MT6372C_VENDOR_ID) {
+ priv->reg_fields = mt6372_reg_fields;
+ priv->ranges = mt6372_led_ranges;
+ priv->pdata = &mt6372_pdata;
+ } else {
+ /* Common for MT6370/71 */
+ priv->reg_fields = common_reg_fields;
+ priv->ranges = common_led_ranges;
+ priv->pdata = &common_pdata;
+ }
+
+ return 0;
+}
+
+static int mt6370_leds_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mt6370_priv *priv;
+ size_t count;
+ unsigned int i = 0;
+ int ret;
+
+ count = device_get_child_node_count(dev);
+ if (!count || count > MT6370_MAX_LEDS)
+ return dev_err_probe(dev, -EINVAL,
+ "No child node or node count over max LED number %zu\n",
+ count);
+
+ priv = devm_kzalloc(dev, struct_size(priv, leds, count), GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
+
+ priv->leds_count = count;
+ mutex_init(&priv->lock);
+
+ priv->regmap = dev_get_regmap(dev->parent, NULL);
+ if (!priv->regmap)
+ return dev_err_probe(dev, -ENODEV, "Failed to get parent regmap\n");
+
+ ret = mt6370_check_vendor_info(priv);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to check vendor info\n");
+
+ ret = devm_regmap_field_bulk_alloc(dev, priv->regmap, priv->fields, priv->reg_fields,
+ F_MAX_FIELDS);
+ if (ret)
+ return dev_err_probe(dev, ret, "Failed to allocate regmap field\n");
+
+ 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_probe(dev, ret, "Failed to parse reg property\n");
+
+ 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)
+ 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))
+ return dev_err_probe(dev, -EINVAL, "Duplicate reg property\n");
+
+ priv->leds_active |= BIT(reg);
+
+ led->index = reg;
+ led->priv = priv;
+
+ ret = mt6370_init_led_properties(dev, led, &init_data);
+ if (ret)
+ return ret;
+
+ ret = mt6370_led_register(dev, led, &init_data);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct of_device_id mt6370_rgbled_device_table[] = {
+ { .compatible = "mediatek,mt6370-indicator" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, mt6370_rgbled_device_table);
+
+static struct platform_driver mt6370_rgbled_driver = {
+ .driver = {
+ .name = "mt6370-indicator",
+ .of_match_table = mt6370_rgbled_device_table,
+ },
+ .probe = mt6370_leds_probe,
+};
+module_platform_driver(mt6370_rgbled_driver);
+
+MODULE_AUTHOR("Alice Chen <alice_chen@richtek.com>");
+MODULE_AUTHOR("ChiYuan Huang <cy_huang@richtek.com>");
+MODULE_DESCRIPTION("MediaTek MT6370 RGB LED Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/rgb/leds-ncp5623.c b/drivers/leds/rgb/leds-ncp5623.c
new file mode 100644
index 000000000000..85d6be6fff2b
--- /dev/null
+++ b/drivers/leds/rgb/leds-ncp5623.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * NCP5623 Multi-LED Driver
+ *
+ * Author: Abdel Alkuor <alkuor@gmail.com>
+ * Datasheet: https://www.onsemi.com/pdf/datasheet/ncp5623-d.pdf
+ */
+
+#include <linux/i2c.h>
+#include <linux/module.h>
+
+#include <linux/led-class-multicolor.h>
+
+#define NCP5623_FUNCTION_OFFSET 0x5
+#define NCP5623_REG(x) ((x) << NCP5623_FUNCTION_OFFSET)
+
+#define NCP5623_SHUTDOWN_REG NCP5623_REG(0x0)
+#define NCP5623_ILED_REG NCP5623_REG(0x1)
+#define NCP5623_PWM_REG(index) NCP5623_REG(0x2 + (index))
+#define NCP5623_UPWARD_STEP_REG NCP5623_REG(0x5)
+#define NCP5623_DOWNWARD_STEP_REG NCP5623_REG(0x6)
+#define NCP5623_DIMMING_TIME_REG NCP5623_REG(0x7)
+
+#define NCP5623_MAX_BRIGHTNESS 0x1f
+#define NCP5623_MAX_DIM_TIME_MS 240
+#define NCP5623_DIM_STEP_MS 8
+
+struct ncp5623 {
+ struct i2c_client *client;
+ struct led_classdev_mc mc_dev;
+ struct mutex lock;
+
+ int current_brightness;
+ unsigned long delay;
+};
+
+static int ncp5623_write(struct i2c_client *client, u8 reg, u8 data)
+{
+ return i2c_smbus_write_byte_data(client, reg | data, 0);
+}
+
+static int ncp5623_brightness_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+ int ret;
+
+ guard(mutex)(&ncp->lock);
+
+ if (ncp->delay && time_is_after_jiffies(ncp->delay))
+ return -EBUSY;
+
+ ncp->delay = 0;
+
+ for (int i = 0; i < mc_cdev->num_colors; i++) {
+ ret = ncp5623_write(ncp->client,
+ NCP5623_PWM_REG(mc_cdev->subled_info[i].channel),
+ min(mc_cdev->subled_info[i].intensity,
+ NCP5623_MAX_BRIGHTNESS));
+ if (ret)
+ return ret;
+ }
+
+ ret = ncp5623_write(ncp->client, NCP5623_DIMMING_TIME_REG, 0);
+ if (ret)
+ return ret;
+
+ ret = ncp5623_write(ncp->client, NCP5623_ILED_REG, brightness);
+ if (ret)
+ return ret;
+
+ ncp->current_brightness = brightness;
+
+ return 0;
+}
+
+static int ncp5623_pattern_set(struct led_classdev *cdev,
+ struct led_pattern *pattern,
+ u32 len, int repeat)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct ncp5623 *ncp = container_of(mc_cdev, struct ncp5623, mc_dev);
+ int brightness_diff;
+ u8 reg;
+ int ret;
+
+ guard(mutex)(&ncp->lock);
+
+ if (ncp->delay && time_is_after_jiffies(ncp->delay))
+ return -EBUSY;
+
+ ncp->delay = 0;
+
+ if (pattern[0].delta_t > NCP5623_MAX_DIM_TIME_MS ||
+ (pattern[0].delta_t % NCP5623_DIM_STEP_MS) != 0)
+ return -EINVAL;
+
+ brightness_diff = pattern[0].brightness - ncp->current_brightness;
+
+ if (brightness_diff == 0)
+ return 0;
+
+ if (pattern[0].delta_t) {
+ if (brightness_diff > 0)
+ reg = NCP5623_UPWARD_STEP_REG;
+ else
+ reg = NCP5623_DOWNWARD_STEP_REG;
+ } else {
+ reg = NCP5623_ILED_REG;
+ }
+
+ ret = ncp5623_write(ncp->client, reg,
+ min(pattern[0].brightness, NCP5623_MAX_BRIGHTNESS));
+ if (ret)
+ return ret;
+
+ ret = ncp5623_write(ncp->client,
+ NCP5623_DIMMING_TIME_REG,
+ pattern[0].delta_t / NCP5623_DIM_STEP_MS);
+ if (ret)
+ return ret;
+
+ /*
+ * During testing, when the brightness difference is 1, for some
+ * unknown reason, the time factor it takes to change to the new
+ * value is the longest time possible. Otherwise, the time factor
+ * is simply the brightness difference.
+ *
+ * For example:
+ * current_brightness = 20 and new_brightness = 21 then the time it
+ * takes to set the new brightness increments to the maximum possible
+ * brightness from 20 then from 0 to 21.
+ * time_factor = max_brightness - 20 + 21
+ */
+ if (abs(brightness_diff) == 1)
+ ncp->delay = NCP5623_MAX_BRIGHTNESS + brightness_diff;
+ else
+ ncp->delay = abs(brightness_diff);
+
+ ncp->delay = msecs_to_jiffies(ncp->delay * pattern[0].delta_t) + jiffies;
+
+ ncp->current_brightness = pattern[0].brightness;
+
+ return 0;
+}
+
+static int ncp5623_pattern_clear(struct led_classdev *led_cdev)
+{
+ return 0;
+}
+
+static int ncp5623_probe(struct i2c_client *client)
+{
+ struct device *dev = &client->dev;
+ struct fwnode_handle *mc_node, *led_node;
+ struct led_init_data init_data = { };
+ struct ncp5623 *ncp;
+ struct mc_subled *subled_info;
+ unsigned int num_subleds;
+ u32 color_index;
+ u32 reg;
+ int ret;
+
+ ncp = devm_kzalloc(dev, sizeof(*ncp), GFP_KERNEL);
+ if (!ncp)
+ return -ENOMEM;
+
+ ncp->client = client;
+
+ mc_node = device_get_named_child_node(dev, "multi-led");
+ if (!mc_node)
+ return -EINVAL;
+
+ num_subleds = fwnode_get_child_node_count(mc_node);
+
+ subled_info = devm_kcalloc(dev, num_subleds, sizeof(*subled_info), GFP_KERNEL);
+ if (!subled_info) {
+ ret = -ENOMEM;
+ goto release_mc_node;
+ }
+
+ fwnode_for_each_child_node(mc_node, led_node) {
+ ret = fwnode_property_read_u32(led_node, "color", &color_index);
+ if (ret)
+ goto release_led_node;
+
+ ret = fwnode_property_read_u32(led_node, "reg", &reg);
+ if (ret)
+ goto release_led_node;
+
+ subled_info[ncp->mc_dev.num_colors].channel = reg;
+ subled_info[ncp->mc_dev.num_colors++].color_index = color_index;
+ }
+
+ init_data.fwnode = mc_node;
+
+ ncp->mc_dev.led_cdev.max_brightness = NCP5623_MAX_BRIGHTNESS;
+ ncp->mc_dev.subled_info = subled_info;
+ ncp->mc_dev.led_cdev.brightness_set_blocking = ncp5623_brightness_set;
+ ncp->mc_dev.led_cdev.pattern_set = ncp5623_pattern_set;
+ ncp->mc_dev.led_cdev.pattern_clear = ncp5623_pattern_clear;
+ ncp->mc_dev.led_cdev.default_trigger = "pattern";
+
+ mutex_init(&ncp->lock);
+ i2c_set_clientdata(client, ncp);
+
+ ret = led_classdev_multicolor_register_ext(dev, &ncp->mc_dev, &init_data);
+ if (ret)
+ goto destroy_lock;
+
+ return 0;
+
+destroy_lock:
+ mutex_destroy(&ncp->lock);
+
+release_mc_node:
+ fwnode_handle_put(mc_node);
+
+ return ret;
+
+release_led_node:
+ fwnode_handle_put(led_node);
+ goto release_mc_node;
+}
+
+static void ncp5623_remove(struct i2c_client *client)
+{
+ struct ncp5623 *ncp = i2c_get_clientdata(client);
+
+ mutex_lock(&ncp->lock);
+ ncp->delay = 0;
+ mutex_unlock(&ncp->lock);
+
+ ncp5623_write(client, NCP5623_DIMMING_TIME_REG, 0);
+ led_classdev_multicolor_unregister(&ncp->mc_dev);
+ mutex_destroy(&ncp->lock);
+}
+
+static void ncp5623_shutdown(struct i2c_client *client)
+{
+ struct ncp5623 *ncp = i2c_get_clientdata(client);
+
+ if (!(ncp->mc_dev.led_cdev.flags & LED_RETAIN_AT_SHUTDOWN))
+ ncp5623_write(client, NCP5623_SHUTDOWN_REG, 0);
+
+ mutex_destroy(&ncp->lock);
+}
+
+static const struct of_device_id ncp5623_id[] = {
+ { .compatible = "onnn,ncp5623" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, ncp5623_id);
+
+static struct i2c_driver ncp5623_i2c_driver = {
+ .driver = {
+ .name = "ncp5623",
+ .of_match_table = ncp5623_id,
+ },
+ .probe = ncp5623_probe,
+ .remove = ncp5623_remove,
+ .shutdown = ncp5623_shutdown,
+};
+
+module_i2c_driver(ncp5623_i2c_driver);
+
+MODULE_AUTHOR("Abdel Alkuor <alkuor@gmail.com>");
+MODULE_DESCRIPTION("NCP5623 Multi-LED driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/rgb/leds-pwm-multicolor.c b/drivers/leds/rgb/leds-pwm-multicolor.c
new file mode 100644
index 000000000000..e0d7d3c9215c
--- /dev/null
+++ b/drivers/leds/rgb/leds-pwm-multicolor.c
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * PWM-based multi-color LED control
+ *
+ * Copyright 2022 Sven Schwermer <sven.schwermer@disruptive-technologies.com>
+ */
+
+#include <linux/err.h>
+#include <linux/kernel.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/leds.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/pwm.h>
+
+struct pwm_led {
+ struct pwm_device *pwm;
+ struct pwm_state state;
+ bool active_low;
+};
+
+struct pwm_mc_led {
+ struct led_classdev_mc mc_cdev;
+ struct mutex lock;
+ struct pwm_led leds[];
+};
+
+static int led_pwm_mc_set(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct led_classdev_mc *mc_cdev = lcdev_to_mccdev(cdev);
+ struct pwm_mc_led *priv = container_of(mc_cdev, struct pwm_mc_led, mc_cdev);
+ unsigned long long duty;
+ int ret = 0;
+ int i;
+
+ led_mc_calc_color_components(mc_cdev, brightness);
+
+ mutex_lock(&priv->lock);
+
+ for (i = 0; i < mc_cdev->num_colors; i++) {
+ duty = priv->leds[i].state.period;
+ duty *= mc_cdev->subled_info[i].brightness;
+ do_div(duty, cdev->max_brightness);
+
+ if (priv->leds[i].active_low)
+ duty = priv->leds[i].state.period - duty;
+
+ priv->leds[i].state.duty_cycle = duty;
+ /*
+ * Disabling a PWM doesn't guarantee that it emits the inactive level.
+ * So keep it on. Only for suspending the PWM should be disabled because
+ * otherwise it refuses to suspend. The possible downside is that the
+ * LED might stay (or even go) on.
+ */
+ priv->leds[i].state.enabled = !(cdev->flags & LED_SUSPENDED);
+ ret = pwm_apply_might_sleep(priv->leds[i].pwm,
+ &priv->leds[i].state);
+ if (ret)
+ break;
+ }
+
+ mutex_unlock(&priv->lock);
+
+ return ret;
+}
+
+static int iterate_subleds(struct device *dev, struct pwm_mc_led *priv,
+ struct fwnode_handle *mcnode)
+{
+ struct mc_subled *subled = priv->mc_cdev.subled_info;
+ struct fwnode_handle *fwnode;
+ struct pwm_led *pwmled;
+ u32 color;
+ int ret;
+
+ /* iterate over the nodes inside the multi-led node */
+ fwnode_for_each_child_node(mcnode, fwnode) {
+ pwmled = &priv->leds[priv->mc_cdev.num_colors];
+ pwmled->pwm = devm_fwnode_pwm_get(dev, fwnode, NULL);
+ if (IS_ERR(pwmled->pwm)) {
+ ret = dev_err_probe(dev, PTR_ERR(pwmled->pwm), "unable to request PWM\n");
+ goto release_fwnode;
+ }
+ pwm_init_state(pwmled->pwm, &pwmled->state);
+ pwmled->active_low = fwnode_property_read_bool(fwnode, "active-low");
+
+ ret = fwnode_property_read_u32(fwnode, "color", &color);
+ if (ret) {
+ dev_err(dev, "cannot read color: %d\n", ret);
+ goto release_fwnode;
+ }
+
+ subled[priv->mc_cdev.num_colors].color_index = color;
+ priv->mc_cdev.num_colors++;
+ }
+
+ return 0;
+
+release_fwnode:
+ fwnode_handle_put(fwnode);
+ return ret;
+}
+
+static int led_pwm_mc_probe(struct platform_device *pdev)
+{
+ struct fwnode_handle *mcnode;
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct mc_subled *subled;
+ struct pwm_mc_led *priv;
+ unsigned int count;
+ int ret = 0;
+
+ mcnode = device_get_named_child_node(&pdev->dev, "multi-led");
+ if (!mcnode)
+ return dev_err_probe(&pdev->dev, -ENODEV,
+ "expected multi-led node\n");
+
+ /* count the nodes inside the multi-led node */
+ count = fwnode_get_child_node_count(mcnode);
+
+ priv = devm_kzalloc(&pdev->dev, struct_size(priv, leds, count),
+ GFP_KERNEL);
+ if (!priv) {
+ ret = -ENOMEM;
+ goto release_mcnode;
+ }
+ mutex_init(&priv->lock);
+
+ subled = devm_kcalloc(&pdev->dev, count, sizeof(*subled), GFP_KERNEL);
+ if (!subled) {
+ ret = -ENOMEM;
+ goto release_mcnode;
+ }
+ priv->mc_cdev.subled_info = subled;
+
+ /* init the multicolor's LED class device */
+ cdev = &priv->mc_cdev.led_cdev;
+ 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;
+
+ ret = iterate_subleds(&pdev->dev, priv, mcnode);
+ if (ret)
+ goto release_mcnode;
+
+ init_data.fwnode = mcnode;
+ ret = devm_led_classdev_multicolor_register_ext(&pdev->dev,
+ &priv->mc_cdev,
+ &init_data);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to register multicolor PWM led for %s: %d\n",
+ cdev->name, ret);
+ goto release_mcnode;
+ }
+
+ ret = led_pwm_mc_set(cdev, cdev->brightness);
+ if (ret)
+ return dev_err_probe(&pdev->dev, ret,
+ "failed to set led PWM value for %s\n",
+ cdev->name);
+
+ platform_set_drvdata(pdev, priv);
+ return 0;
+
+release_mcnode:
+ fwnode_handle_put(mcnode);
+ return ret;
+}
+
+static const struct of_device_id of_pwm_leds_mc_match[] = {
+ { .compatible = "pwm-leds-multicolor", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, of_pwm_leds_mc_match);
+
+static struct platform_driver led_pwm_mc_driver = {
+ .probe = led_pwm_mc_probe,
+ .driver = {
+ .name = "leds_pwm_multicolor",
+ .of_match_table = of_pwm_leds_mc_match,
+ },
+};
+module_platform_driver(led_pwm_mc_driver);
+
+MODULE_AUTHOR("Sven Schwermer <sven.schwermer@disruptive-technologies.com>");
+MODULE_DESCRIPTION("multi-color PWM LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:leds-pwm-multicolor");
diff --git a/drivers/leds/rgb/leds-qcom-lpg.c b/drivers/leds/rgb/leds-qcom-lpg.c
new file mode 100644
index 000000000000..72da0bf469ad
--- /dev/null
+++ b/drivers/leds/rgb/leds-qcom-lpg.c
@@ -0,0 +1,1847 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2022 Linaro Ltd
+ * Copyright (c) 2010-2012, The Linux Foundation. All rights reserved.
+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ */
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/led-class-multicolor.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pwm.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/qcom-pbs.h>
+
+#define LPG_SUBTYPE_REG 0x05
+#define LPG_SUBTYPE_LPG 0x2
+#define LPG_SUBTYPE_PWM 0xb
+#define LPG_SUBTYPE_HI_RES_PWM 0xc
+#define LPG_SUBTYPE_LPG_LITE 0x11
+#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
+#define PWM_FREQ_PRE_DIV_MASK GENMASK(6, 5)
+#define PWM_FREQ_EXP_MASK GENMASK(2, 0)
+#define PWM_TYPE_CONFIG_REG 0x43
+#define PWM_VALUE_REG 0x44
+#define PWM_ENABLE_CONTROL_REG 0x46
+#define PWM_SYNC_REG 0x47
+#define LPG_RAMP_DURATION_REG 0x50
+#define LPG_HI_PAUSE_REG 0x52
+#define LPG_LO_PAUSE_REG 0x54
+#define LPG_HI_IDX_REG 0x56
+#define LPG_LO_IDX_REG 0x57
+#define PWM_SEC_ACCESS_REG 0xd0
+#define PWM_DTEST_REG(x) (0xe2 + (x) - 1)
+
+#define SDAM_REG_PBS_SEQ_EN 0x42
+#define SDAM_PBS_TRIG_SET 0xe5
+#define SDAM_PBS_TRIG_CLR 0xe6
+
+#define TRI_LED_SRC_SEL 0x45
+#define TRI_LED_EN_CTL 0x46
+#define TRI_LED_ATC_CTL 0x47
+
+#define LPG_LUT_REG(x) (0x40 + (x) * 2)
+#define RAMP_CONTROL_REG 0xc8
+
+#define LPG_RESOLUTION_9BIT BIT(9)
+#define LPG_RESOLUTION_15BIT BIT(15)
+#define PPG_MAX_LED_BRIGHTNESS 255
+
+#define LPG_MAX_M 7
+#define LPG_MAX_PREDIV 6
+
+#define DEFAULT_TICK_DURATION_US 7800
+#define RAMP_STEP_DURATION(x) (((x) * 1000 / DEFAULT_TICK_DURATION_US) & 0xff)
+
+#define SDAM_MAX_DEVICES 2
+/* LPG common config settings for PPG */
+#define SDAM_START_BASE 0x40
+#define SDAM_REG_RAMP_STEP_DURATION 0x47
+
+#define SDAM_LUT_SDAM_LUT_PATTERN_OFFSET 0x45
+#define SDAM_LPG_SDAM_LUT_PATTERN_OFFSET 0x80
+
+/* LPG per channel config settings for PPG */
+#define SDAM_LUT_EN_OFFSET 0x0
+#define SDAM_PATTERN_CONFIG_OFFSET 0x1
+#define SDAM_END_INDEX_OFFSET 0x3
+#define SDAM_START_INDEX_OFFSET 0x4
+#define SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET 0x6
+#define SDAM_PAUSE_HI_MULTIPLIER_OFFSET 0x8
+#define SDAM_PAUSE_LO_MULTIPLIER_OFFSET 0x9
+
+struct lpg_channel;
+struct lpg_data;
+
+/**
+ * struct lpg - LPG device context
+ * @dev: pointer to LPG device
+ * @map: regmap for register access
+ * @lock: used to synchronize LED and pwm callback requests
+ * @pwm: PWM-chip object, if operating in PWM mode
+ * @data: reference to version specific data
+ * @lut_base: base address of the LUT block (optional)
+ * @lut_size: number of entries in the LUT block
+ * @lut_bitmap: allocation bitmap for LUT entries
+ * @pbs_dev: PBS device
+ * @lpg_chan_sdam: LPG SDAM peripheral device
+ * @lut_sdam: LUT SDAM peripheral device
+ * @pbs_en_bitmap: bitmap for tracking PBS triggers
+ * @triled_base: base address of the TRILED block (optional)
+ * @triled_src: power-source for the TRILED
+ * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register
+ * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register
+ * @channels: list of PWM channels
+ * @num_channels: number of @channels
+ */
+struct lpg {
+ struct device *dev;
+ struct regmap *map;
+
+ struct mutex lock;
+
+ struct pwm_chip *pwm;
+
+ const struct lpg_data *data;
+
+ u32 lut_base;
+ u32 lut_size;
+ unsigned long *lut_bitmap;
+
+ struct pbs_dev *pbs_dev;
+ struct nvmem_device *lpg_chan_sdam;
+ struct nvmem_device *lut_sdam;
+ unsigned long pbs_en_bitmap;
+
+ u32 triled_base;
+ u32 triled_src;
+ bool triled_has_atc_ctl;
+ bool triled_has_src_sel;
+
+ struct lpg_channel *channels;
+ unsigned int num_channels;
+};
+
+/**
+ * struct lpg_channel - per channel data
+ * @lpg: reference to parent lpg
+ * @base: base address of the PWM channel
+ * @triled_mask: mask in TRILED to enable this channel
+ * @lut_mask: mask in LUT to start pattern generator for this channel
+ * @subtype: PMIC hardware block subtype
+ * @sdam_offset: channel offset in LPG SDAM
+ * @in_use: channel is exposed to LED framework
+ * @color: color of the LED attached to this channel
+ * @dtest_line: DTEST line for output, or 0 if disabled
+ * @dtest_value: DTEST line configuration
+ * @pwm_value: duty (in microseconds) of the generated pulses, overridden by LUT
+ * @enabled: output enabled?
+ * @period: period (in nanoseconds) of the generated pulses
+ * @clk_sel: reference clock frequency selector
+ * @pre_div_sel: divider selector of the reference clock
+ * @pre_div_exp: exponential divider of the reference clock
+ * @pwm_resolution_sel: pwm resolution selector
+ * @ramp_enabled: duty cycle is driven by iterating over lookup table
+ * @ramp_ping_pong: reverse through pattern, rather than wrapping to start
+ * @ramp_oneshot: perform only a single pass over the pattern
+ * @ramp_reverse: iterate over pattern backwards
+ * @ramp_tick_ms: length (in milliseconds) of one step in the pattern
+ * @ramp_lo_pause_ms: pause (in milliseconds) before iterating over pattern
+ * @ramp_hi_pause_ms: pause (in milliseconds) after iterating over pattern
+ * @pattern_lo_idx: start index of associated pattern
+ * @pattern_hi_idx: last index of associated pattern
+ */
+struct lpg_channel {
+ struct lpg *lpg;
+
+ u32 base;
+ unsigned int triled_mask;
+ unsigned int lut_mask;
+ unsigned int subtype;
+ u32 sdam_offset;
+
+ bool in_use;
+
+ int color;
+
+ u32 dtest_line;
+ u32 dtest_value;
+
+ u16 pwm_value;
+ bool enabled;
+
+ u64 period;
+ unsigned int clk_sel;
+ unsigned int pre_div_sel;
+ unsigned int pre_div_exp;
+ unsigned int pwm_resolution_sel;
+
+ bool ramp_enabled;
+ bool ramp_ping_pong;
+ bool ramp_oneshot;
+ bool ramp_reverse;
+ unsigned short ramp_tick_ms;
+ unsigned long ramp_lo_pause_ms;
+ unsigned long ramp_hi_pause_ms;
+
+ unsigned int pattern_lo_idx;
+ unsigned int pattern_hi_idx;
+};
+
+/**
+ * struct lpg_led - logical LED object
+ * @lpg: lpg context reference
+ * @cdev: LED class device
+ * @mcdev: Multicolor LED class device
+ * @num_channels: number of @channels
+ * @channels: list of channels associated with the LED
+ */
+struct lpg_led {
+ struct lpg *lpg;
+
+ struct led_classdev cdev;
+ struct led_classdev_mc mcdev;
+
+ unsigned int num_channels;
+ struct lpg_channel *channels[] __counted_by(num_channels);
+};
+
+/**
+ * struct lpg_channel_data - per channel initialization data
+ * @sdam_offset: Channel offset in LPG SDAM
+ * @base: base address for PWM channel registers
+ * @triled_mask: bitmask for controlling this channel in TRILED
+ */
+struct lpg_channel_data {
+ unsigned int sdam_offset;
+ unsigned int base;
+ u8 triled_mask;
+};
+
+/**
+ * struct lpg_data - initialization data
+ * @lut_base: base address of LUT block
+ * @lut_size: number of entries in LUT
+ * @triled_base: base address of TRILED
+ * @triled_has_atc_ctl: true if there is TRI_LED_ATC_CTL register
+ * @triled_has_src_sel: true if there is TRI_LED_SRC_SEL register
+ * @num_channels: number of channels in LPG
+ * @channels: list of channel initialization data
+ */
+struct lpg_data {
+ unsigned int lut_base;
+ unsigned int lut_size;
+ unsigned int triled_base;
+ bool triled_has_atc_ctl;
+ bool triled_has_src_sel;
+ int num_channels;
+ const struct lpg_channel_data *channels;
+};
+
+#define PBS_SW_TRIG_BIT BIT(0)
+
+static int lpg_clear_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
+{
+ u8 val = 0;
+ int rc;
+
+ if (!lpg->lpg_chan_sdam)
+ return 0;
+
+ lpg->pbs_en_bitmap &= (~lut_mask);
+ if (!lpg->pbs_en_bitmap) {
+ rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
+ if (rc < 0)
+ return rc;
+
+ if (lpg->lut_sdam) {
+ val = PBS_SW_TRIG_BIT;
+ rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_CLR, 1, &val);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int lpg_set_pbs_trigger(struct lpg *lpg, unsigned int lut_mask)
+{
+ u8 val = PBS_SW_TRIG_BIT;
+ int rc;
+
+ if (!lpg->lpg_chan_sdam)
+ return 0;
+
+ if (!lpg->pbs_en_bitmap) {
+ rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_REG_PBS_SEQ_EN, 1, &val);
+ if (rc < 0)
+ return rc;
+
+ if (lpg->lut_sdam) {
+ rc = nvmem_device_write(lpg->lpg_chan_sdam, SDAM_PBS_TRIG_SET, 1, &val);
+ if (rc < 0)
+ return rc;
+ } else {
+ rc = qcom_pbs_trigger_event(lpg->pbs_dev, val);
+ if (rc < 0)
+ return rc;
+ }
+ }
+ lpg->pbs_en_bitmap |= lut_mask;
+
+ return 0;
+}
+
+static int lpg_sdam_configure_triggers(struct lpg_channel *chan, u8 set_trig)
+{
+ u32 addr = SDAM_LUT_EN_OFFSET + chan->sdam_offset;
+
+ if (!chan->lpg->lpg_chan_sdam)
+ return 0;
+
+ return nvmem_device_write(chan->lpg->lpg_chan_sdam, addr, 1, &set_trig);
+}
+
+static int triled_set(struct lpg *lpg, unsigned int mask, unsigned int enable)
+{
+ /* Skip if we don't have a triled block */
+ if (!lpg->triled_base)
+ return 0;
+
+ return regmap_update_bits(lpg->map, lpg->triled_base + TRI_LED_EN_CTL,
+ mask, enable);
+}
+
+static int lpg_lut_store_sdam(struct lpg *lpg, struct led_pattern *pattern,
+ size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
+{
+ unsigned int idx;
+ u8 brightness;
+ int i, rc;
+ u16 addr;
+
+ if (len > lpg->lut_size) {
+ dev_err(lpg->dev, "Pattern length (%zu) exceeds maximum pattern length (%d)\n",
+ len, lpg->lut_size);
+ return -EINVAL;
+ }
+
+ idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size, 0, len, 0);
+ if (idx >= lpg->lut_size)
+ return -ENOSPC;
+
+ for (i = 0; i < len; i++) {
+ brightness = pattern[i].brightness;
+
+ if (lpg->lut_sdam) {
+ addr = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET + i + idx;
+ rc = nvmem_device_write(lpg->lut_sdam, addr, 1, &brightness);
+ } else {
+ addr = SDAM_LPG_SDAM_LUT_PATTERN_OFFSET + i + idx;
+ rc = nvmem_device_write(lpg->lpg_chan_sdam, addr, 1, &brightness);
+ }
+
+ if (rc < 0)
+ return rc;
+ }
+
+ bitmap_set(lpg->lut_bitmap, idx, len);
+
+ *lo_idx = idx;
+ *hi_idx = idx + len - 1;
+
+ return 0;
+}
+
+static int lpg_lut_store(struct lpg *lpg, struct led_pattern *pattern,
+ size_t len, unsigned int *lo_idx, unsigned int *hi_idx)
+{
+ unsigned int idx;
+ u16 val;
+ int i;
+
+ idx = bitmap_find_next_zero_area(lpg->lut_bitmap, lpg->lut_size,
+ 0, len, 0);
+ if (idx >= lpg->lut_size)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i++) {
+ val = pattern[i].brightness;
+
+ regmap_bulk_write(lpg->map, lpg->lut_base + LPG_LUT_REG(idx + i),
+ &val, sizeof(val));
+ }
+
+ bitmap_set(lpg->lut_bitmap, idx, len);
+
+ *lo_idx = idx;
+ *hi_idx = idx + len - 1;
+
+ return 0;
+}
+
+static void lpg_lut_free(struct lpg *lpg, unsigned int lo_idx, unsigned int hi_idx)
+{
+ int len;
+
+ len = hi_idx - lo_idx + 1;
+ if (len == 1)
+ return;
+
+ bitmap_clear(lpg->lut_bitmap, lo_idx, len);
+}
+
+static int lpg_lut_sync(struct lpg *lpg, unsigned int mask)
+{
+ if (!lpg->lut_base)
+ return 0;
+
+ return regmap_write(lpg->map, lpg->lut_base + RAMP_CONTROL_REG, mask);
+}
+
+static const unsigned int lpg_clk_rates[] = {0, 1024, 32768, 19200000};
+static const unsigned int lpg_clk_rates_hi_res[] = {0, 1024, 32768, 19200000, 76800000};
+static const unsigned int lpg_pre_divs[] = {1, 3, 5, 6};
+static const unsigned int lpg_pwm_resolution[] = {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)
+{
+ unsigned int i, pwm_resolution_count, best_pwm_resolution_sel = 0;
+ const unsigned int *clk_rate_arr, *pwm_resolution_arr;
+ unsigned int clk_sel, clk_len, best_clk = 0;
+ unsigned int div, best_div = 0;
+ unsigned int m, best_m = 0;
+ unsigned int resolution;
+ unsigned int error;
+ unsigned int best_err = UINT_MAX;
+ u64 max_period, min_period;
+ u64 best_period = 0;
+ u64 max_res;
+
+ /*
+ * The PWM period is determined by:
+ *
+ * resolution * pre_div * 2^M
+ * period = --------------------------
+ * refclk
+ *
+ * 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 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.
+ */
+
+ if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
+ clk_rate_arr = lpg_clk_rates_hi_res;
+ clk_len = ARRAY_SIZE(lpg_clk_rates_hi_res);
+ pwm_resolution_arr = lpg_pwm_resolution_hi_res;
+ pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution_hi_res);
+ max_res = LPG_RESOLUTION_15BIT;
+ } else {
+ clk_rate_arr = lpg_clk_rates;
+ clk_len = ARRAY_SIZE(lpg_clk_rates);
+ pwm_resolution_arr = lpg_pwm_resolution;
+ pwm_resolution_count = ARRAY_SIZE(lpg_pwm_resolution);
+ max_res = LPG_RESOLUTION_9BIT;
+ }
+
+ 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;
+
+ /* Limit period to largest possible value, to avoid overflows */
+ max_period = div64_u64((u64)NSEC_PER_SEC * max_res * LPG_MAX_PREDIV * (1 << LPG_MAX_M),
+ 1024);
+ if (period > max_period)
+ period = max_period;
+
+ /*
+ * Search for the pre_div, refclk, resolution and M by solving the rewritten formula
+ * for each refclk, resolution and pre_div value:
+ *
+ * period * refclk
+ * M = log2 -------------------------------------
+ * NSEC_PER_SEC * pre_div * resolution
+ */
+
+ for (i = 0; i < pwm_resolution_count; 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];
+
+ for (div = 0; div < ARRAY_SIZE(lpg_pre_divs); div++) {
+ u64 denominator = (u64)NSEC_PER_SEC * lpg_pre_divs[div] *
+ resolution;
+ u64 actual;
+ u64 ratio;
+
+ if (numerator < denominator)
+ continue;
+
+ ratio = div64_u64(numerator, denominator);
+ m = ilog2(ratio);
+ if (m > LPG_MAX_M)
+ m = LPG_MAX_M;
+
+ actual = DIV_ROUND_UP_ULL(denominator * (1 << m),
+ clk_rate_arr[clk_sel]);
+ error = period - actual;
+ if (error < best_err) {
+ best_err = error;
+ best_div = div;
+ best_m = m;
+ best_clk = clk_sel;
+ best_period = actual;
+ best_pwm_resolution_sel = i;
+ }
+ }
+ }
+ }
+ chan->clk_sel = best_clk;
+ chan->pre_div_sel = best_div;
+ chan->pre_div_exp = best_m;
+ chan->period = best_period;
+ chan->pwm_resolution_sel = best_pwm_resolution_sel;
+ return 0;
+}
+
+static void lpg_calc_duty(struct lpg_channel *chan, uint64_t duty)
+{
+ unsigned int max;
+ unsigned int val;
+ unsigned int clk_rate;
+
+ if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
+ max = BIT(lpg_pwm_resolution_hi_res[chan->pwm_resolution_sel]) - 1;
+ clk_rate = lpg_clk_rates_hi_res[chan->clk_sel];
+ } else {
+ max = BIT(lpg_pwm_resolution[chan->pwm_resolution_sel]) - 1;
+ clk_rate = lpg_clk_rates[chan->clk_sel];
+ }
+
+ val = div64_u64(duty * clk_rate,
+ (u64)NSEC_PER_SEC * lpg_pre_divs[chan->pre_div_sel] * (1 << chan->pre_div_exp));
+
+ chan->pwm_value = min(val, max);
+}
+
+static void lpg_apply_freq(struct lpg_channel *chan)
+{
+ unsigned long val;
+ struct lpg *lpg = chan->lpg;
+
+ if (!chan->enabled)
+ return;
+
+ val = chan->clk_sel;
+
+ /* Specify resolution, based on the subtype of the channel */
+ switch (chan->subtype) {
+ case LPG_SUBTYPE_LPG:
+ val |= GENMASK(5, 4);
+ break;
+ case LPG_SUBTYPE_PWM:
+ 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);
+ break;
+ case LPG_SUBTYPE_LPG_LITE:
+ default:
+ val |= BIT(4);
+ break;
+ }
+
+ regmap_write(lpg->map, chan->base + LPG_SIZE_CLK_REG, val);
+
+ val = FIELD_PREP(PWM_FREQ_PRE_DIV_MASK, chan->pre_div_sel) |
+ FIELD_PREP(PWM_FREQ_EXP_MASK, chan->pre_div_exp);
+ regmap_write(lpg->map, chan->base + LPG_PREDIV_CLK_REG, val);
+}
+
+#define LPG_ENABLE_GLITCH_REMOVAL BIT(5)
+
+static void lpg_enable_glitch(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+
+ regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG,
+ LPG_ENABLE_GLITCH_REMOVAL, 0);
+}
+
+static void lpg_disable_glitch(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+
+ regmap_update_bits(lpg->map, chan->base + PWM_TYPE_CONFIG_REG,
+ LPG_ENABLE_GLITCH_REMOVAL,
+ LPG_ENABLE_GLITCH_REMOVAL);
+}
+
+static void lpg_apply_pwm_value(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+ u16 val = chan->pwm_value;
+
+ if (!chan->enabled)
+ return;
+
+ regmap_bulk_write(lpg->map, chan->base + PWM_VALUE_REG, &val, sizeof(val));
+}
+
+#define LPG_PATTERN_CONFIG_LO_TO_HI BIT(4)
+#define LPG_PATTERN_CONFIG_REPEAT BIT(3)
+#define LPG_PATTERN_CONFIG_TOGGLE BIT(2)
+#define LPG_PATTERN_CONFIG_PAUSE_HI BIT(1)
+#define LPG_PATTERN_CONFIG_PAUSE_LO BIT(0)
+
+static void lpg_sdam_apply_lut_control(struct lpg_channel *chan)
+{
+ struct nvmem_device *lpg_chan_sdam = chan->lpg->lpg_chan_sdam;
+ unsigned int lo_idx = chan->pattern_lo_idx;
+ unsigned int hi_idx = chan->pattern_hi_idx;
+ u8 val = 0, conf = 0, lut_offset = 0;
+ unsigned int hi_pause, lo_pause;
+ struct lpg *lpg = chan->lpg;
+
+ if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx)
+ return;
+
+ hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, chan->ramp_tick_ms);
+ lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, chan->ramp_tick_ms);
+
+ if (!chan->ramp_oneshot)
+ conf |= LPG_PATTERN_CONFIG_REPEAT;
+ if (chan->ramp_hi_pause_ms && lpg->lut_sdam)
+ conf |= LPG_PATTERN_CONFIG_PAUSE_HI;
+ if (chan->ramp_lo_pause_ms && lpg->lut_sdam)
+ conf |= LPG_PATTERN_CONFIG_PAUSE_LO;
+
+ if (lpg->lut_sdam) {
+ lut_offset = SDAM_LUT_SDAM_LUT_PATTERN_OFFSET - SDAM_START_BASE;
+ hi_idx += lut_offset;
+ lo_idx += lut_offset;
+ }
+
+ nvmem_device_write(lpg_chan_sdam, SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val);
+ nvmem_device_write(lpg_chan_sdam, SDAM_PATTERN_CONFIG_OFFSET + chan->sdam_offset, 1, &conf);
+ nvmem_device_write(lpg_chan_sdam, SDAM_END_INDEX_OFFSET + chan->sdam_offset, 1, &hi_idx);
+ nvmem_device_write(lpg_chan_sdam, SDAM_START_INDEX_OFFSET + chan->sdam_offset, 1, &lo_idx);
+
+ val = RAMP_STEP_DURATION(chan->ramp_tick_ms);
+ nvmem_device_write(lpg_chan_sdam, SDAM_REG_RAMP_STEP_DURATION, 1, &val);
+
+ if (lpg->lut_sdam) {
+ nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_HI_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &hi_pause);
+ nvmem_device_write(lpg_chan_sdam, SDAM_PAUSE_LO_MULTIPLIER_OFFSET + chan->sdam_offset, 1, &lo_pause);
+ }
+
+}
+
+static void lpg_apply_lut_control(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+ unsigned int hi_pause;
+ unsigned int lo_pause;
+ unsigned int conf = 0;
+ unsigned int lo_idx = chan->pattern_lo_idx;
+ unsigned int hi_idx = chan->pattern_hi_idx;
+ u16 step = chan->ramp_tick_ms;
+
+ if (!chan->ramp_enabled || chan->pattern_lo_idx == chan->pattern_hi_idx)
+ return;
+
+ hi_pause = DIV_ROUND_UP(chan->ramp_hi_pause_ms, step);
+ lo_pause = DIV_ROUND_UP(chan->ramp_lo_pause_ms, step);
+
+ if (!chan->ramp_reverse)
+ conf |= LPG_PATTERN_CONFIG_LO_TO_HI;
+ if (!chan->ramp_oneshot)
+ conf |= LPG_PATTERN_CONFIG_REPEAT;
+ if (chan->ramp_ping_pong)
+ conf |= LPG_PATTERN_CONFIG_TOGGLE;
+ if (chan->ramp_hi_pause_ms)
+ conf |= LPG_PATTERN_CONFIG_PAUSE_HI;
+ if (chan->ramp_lo_pause_ms)
+ conf |= LPG_PATTERN_CONFIG_PAUSE_LO;
+
+ regmap_write(lpg->map, chan->base + LPG_PATTERN_CONFIG_REG, conf);
+ regmap_write(lpg->map, chan->base + LPG_HI_IDX_REG, hi_idx);
+ regmap_write(lpg->map, chan->base + LPG_LO_IDX_REG, lo_idx);
+
+ regmap_bulk_write(lpg->map, chan->base + LPG_RAMP_DURATION_REG, &step, sizeof(step));
+ regmap_write(lpg->map, chan->base + LPG_HI_PAUSE_REG, hi_pause);
+ regmap_write(lpg->map, chan->base + LPG_LO_PAUSE_REG, lo_pause);
+}
+
+#define LPG_ENABLE_CONTROL_OUTPUT BIT(7)
+#define LPG_ENABLE_CONTROL_BUFFER_TRISTATE BIT(5)
+#define LPG_ENABLE_CONTROL_SRC_PWM BIT(2)
+#define LPG_ENABLE_CONTROL_RAMP_GEN BIT(1)
+
+static void lpg_apply_control(struct lpg_channel *chan)
+{
+ unsigned int ctrl;
+ struct lpg *lpg = chan->lpg;
+
+ ctrl = LPG_ENABLE_CONTROL_BUFFER_TRISTATE;
+
+ if (chan->enabled)
+ ctrl |= LPG_ENABLE_CONTROL_OUTPUT;
+
+ if (chan->pattern_lo_idx != chan->pattern_hi_idx)
+ ctrl |= LPG_ENABLE_CONTROL_RAMP_GEN;
+ else
+ ctrl |= LPG_ENABLE_CONTROL_SRC_PWM;
+
+ regmap_write(lpg->map, chan->base + PWM_ENABLE_CONTROL_REG, ctrl);
+
+ /*
+ * Due to LPG hardware bug, in the PWM mode, having enabled PWM,
+ * We have to write PWM values one more time.
+ */
+ if (chan->enabled)
+ lpg_apply_pwm_value(chan);
+}
+
+#define LPG_SYNC_PWM BIT(0)
+
+static void lpg_apply_sync(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+
+ regmap_write(lpg->map, chan->base + PWM_SYNC_REG, LPG_SYNC_PWM);
+}
+
+static int lpg_parse_dtest(struct lpg *lpg)
+{
+ struct lpg_channel *chan;
+ struct device_node *np = lpg->dev->of_node;
+ int count;
+ int ret;
+ int i;
+
+ count = of_property_count_u32_elems(np, "qcom,dtest");
+ if (count == -EINVAL) {
+ return 0;
+ } else if (count < 0) {
+ ret = count;
+ goto err_malformed;
+ } else if (count != lpg->data->num_channels * 2) {
+ return dev_err_probe(lpg->dev, -EINVAL,
+ "qcom,dtest needs to be %d items\n",
+ lpg->data->num_channels * 2);
+ }
+
+ for (i = 0; i < lpg->data->num_channels; i++) {
+ chan = &lpg->channels[i];
+
+ ret = of_property_read_u32_index(np, "qcom,dtest", i * 2,
+ &chan->dtest_line);
+ if (ret)
+ goto err_malformed;
+
+ ret = of_property_read_u32_index(np, "qcom,dtest", i * 2 + 1,
+ &chan->dtest_value);
+ if (ret)
+ goto err_malformed;
+ }
+
+ return 0;
+
+err_malformed:
+ return dev_err_probe(lpg->dev, ret, "malformed qcom,dtest\n");
+}
+
+static void lpg_apply_dtest(struct lpg_channel *chan)
+{
+ struct lpg *lpg = chan->lpg;
+
+ if (!chan->dtest_line)
+ return;
+
+ regmap_write(lpg->map, chan->base + PWM_SEC_ACCESS_REG, 0xa5);
+ regmap_write(lpg->map, chan->base + PWM_DTEST_REG(chan->dtest_line),
+ chan->dtest_value);
+}
+
+static void lpg_apply(struct lpg_channel *chan)
+{
+ lpg_disable_glitch(chan);
+ lpg_apply_freq(chan);
+ lpg_apply_pwm_value(chan);
+ lpg_apply_control(chan);
+ lpg_apply_sync(chan);
+ if (chan->lpg->lpg_chan_sdam)
+ lpg_sdam_apply_lut_control(chan);
+ else
+ lpg_apply_lut_control(chan);
+ lpg_enable_glitch(chan);
+}
+
+static void lpg_brightness_set(struct lpg_led *led, struct led_classdev *cdev,
+ struct mc_subled *subleds)
+{
+ enum led_brightness brightness;
+ struct lpg_channel *chan;
+ unsigned int triled_enabled = 0;
+ unsigned int triled_mask = 0;
+ unsigned int lut_mask = 0;
+ unsigned int duty;
+ struct lpg *lpg = led->lpg;
+ int i;
+
+ for (i = 0; i < led->num_channels; i++) {
+ chan = led->channels[i];
+ brightness = subleds[i].brightness;
+
+ if (brightness == LED_OFF) {
+ chan->enabled = false;
+ chan->ramp_enabled = false;
+ } else if (chan->pattern_lo_idx != chan->pattern_hi_idx) {
+ lpg_calc_freq(chan, NSEC_PER_MSEC);
+ lpg_sdam_configure_triggers(chan, 1);
+
+ chan->enabled = true;
+ chan->ramp_enabled = true;
+
+ lut_mask |= chan->lut_mask;
+ triled_enabled |= chan->triled_mask;
+ } else {
+ lpg_calc_freq(chan, NSEC_PER_MSEC);
+
+ duty = div_u64(brightness * chan->period, cdev->max_brightness);
+ lpg_calc_duty(chan, duty);
+ chan->enabled = true;
+ chan->ramp_enabled = false;
+
+ triled_enabled |= chan->triled_mask;
+ }
+
+ triled_mask |= chan->triled_mask;
+
+ lpg_apply(chan);
+ }
+
+ /* Toggle triled lines */
+ if (triled_mask)
+ triled_set(lpg, triled_mask, triled_enabled);
+
+ /* Trigger start of ramp generator(s) */
+ if (lut_mask) {
+ lpg_lut_sync(lpg, lut_mask);
+ lpg_set_pbs_trigger(lpg, lut_mask);
+ }
+}
+
+static int lpg_brightness_single_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
+ struct mc_subled info;
+
+ mutex_lock(&led->lpg->lock);
+
+ info.brightness = value;
+ lpg_brightness_set(led, cdev, &info);
+
+ mutex_unlock(&led->lpg->lock);
+
+ return 0;
+}
+
+static int lpg_brightness_mc_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
+
+ mutex_lock(&led->lpg->lock);
+
+ led_mc_calc_color_components(mc, value);
+ lpg_brightness_set(led, cdev, mc->subled_info);
+
+ mutex_unlock(&led->lpg->lock);
+
+ return 0;
+}
+
+static int lpg_blink_set(struct lpg_led *led,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct lpg_channel *chan;
+ unsigned int period;
+ unsigned int triled_mask = 0;
+ struct lpg *lpg = led->lpg;
+ u64 duty;
+ int i;
+
+ if (!*delay_on && !*delay_off) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ duty = *delay_on * NSEC_PER_MSEC;
+ period = (*delay_on + *delay_off) * NSEC_PER_MSEC;
+
+ for (i = 0; i < led->num_channels; i++) {
+ chan = led->channels[i];
+
+ lpg_calc_freq(chan, period);
+ lpg_calc_duty(chan, duty);
+
+ chan->enabled = true;
+ chan->ramp_enabled = false;
+
+ triled_mask |= chan->triled_mask;
+
+ lpg_apply(chan);
+ }
+
+ /* Enable triled lines */
+ triled_set(lpg, triled_mask, triled_mask);
+
+ chan = led->channels[0];
+ duty = div_u64(chan->pwm_value * chan->period, LPG_RESOLUTION_9BIT);
+ *delay_on = div_u64(duty, NSEC_PER_MSEC);
+ *delay_off = div_u64(chan->period - duty, NSEC_PER_MSEC);
+
+ return 0;
+}
+
+static int lpg_blink_single_set(struct led_classdev *cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
+ int ret;
+
+ mutex_lock(&led->lpg->lock);
+
+ ret = lpg_blink_set(led, delay_on, delay_off);
+
+ mutex_unlock(&led->lpg->lock);
+
+ return ret;
+}
+
+static int lpg_blink_mc_set(struct led_classdev *cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
+ int ret;
+
+ mutex_lock(&led->lpg->lock);
+
+ ret = lpg_blink_set(led, delay_on, delay_off);
+
+ mutex_unlock(&led->lpg->lock);
+
+ return ret;
+}
+
+static int lpg_pattern_set(struct lpg_led *led, struct led_pattern *led_pattern,
+ u32 len, int repeat)
+{
+ struct lpg_channel *chan;
+ struct lpg *lpg = led->lpg;
+ struct led_pattern *pattern;
+ unsigned int brightness_a;
+ unsigned int brightness_b;
+ unsigned int hi_pause = 0;
+ unsigned int lo_pause = 0;
+ unsigned int actual_len;
+ unsigned int delta_t;
+ unsigned int lo_idx;
+ unsigned int hi_idx;
+ unsigned int i;
+ bool ping_pong = true;
+ int ret = -EINVAL;
+
+ /* Hardware only support oneshot or indefinite loops */
+ if (repeat != -1 && repeat != 1)
+ return -EINVAL;
+
+ /*
+ * The standardized leds-trigger-pattern format defines that the
+ * brightness of the LED follows a linear transition from one entry
+ * in the pattern to the next, over the given delta_t time. It
+ * describes that the way to perform instant transitions a zero-length
+ * entry should be added following a pattern entry.
+ *
+ * The LPG hardware is only able to perform the latter (no linear
+ * transitions), so require each entry in the pattern to be followed by
+ * a zero-length transition.
+ */
+ if (len % 2)
+ return -EINVAL;
+
+ pattern = kcalloc(len / 2, sizeof(*pattern), GFP_KERNEL);
+ if (!pattern)
+ return -ENOMEM;
+
+ for (i = 0; i < len; i += 2) {
+ if (led_pattern[i].brightness != led_pattern[i + 1].brightness)
+ goto out_free_pattern;
+ if (led_pattern[i + 1].delta_t != 0)
+ goto out_free_pattern;
+
+ pattern[i / 2].brightness = led_pattern[i].brightness;
+ pattern[i / 2].delta_t = led_pattern[i].delta_t;
+ }
+
+ len /= 2;
+
+ /*
+ * Specifying a pattern of length 1 causes the hardware to iterate
+ * through the entire LUT, so prohibit this.
+ */
+ if (len < 2)
+ goto out_free_pattern;
+
+ /*
+ * The LPG plays patterns with at a fixed pace, a "low pause" can be
+ * used to stretch the first delay of the pattern and a "high pause"
+ * the last one.
+ *
+ * In order to save space the pattern can be played in "ping pong"
+ * mode, in which the pattern is first played forward, then "high
+ * pause" is applied, then the pattern is played backwards and finally
+ * the "low pause" is applied.
+ *
+ * The middle elements of the pattern are used to determine delta_t and
+ * the "low pause" and "high pause" multipliers are derrived from this.
+ *
+ * The first element in the pattern is used to determine "low pause".
+ *
+ * If the specified pattern is a palindrome the ping pong mode is
+ * enabled. In this scenario the delta_t of the middle entry (i.e. the
+ * last in the programmed pattern) determines the "high pause".
+ *
+ * SDAM-based devices do not support "ping pong", and only supports
+ * "low pause" and "high pause" with a dedicated SDAM LUT.
+ */
+
+ /* Detect palindromes and use "ping pong" to reduce LUT usage */
+ if (lpg->lut_base) {
+ for (i = 0; i < len / 2; i++) {
+ brightness_a = pattern[i].brightness;
+ brightness_b = pattern[len - i - 1].brightness;
+
+ if (brightness_a != brightness_b) {
+ ping_pong = false;
+ break;
+ }
+ }
+ } else
+ ping_pong = false;
+
+ /* The pattern length to be written to the LUT */
+ if (ping_pong)
+ actual_len = (len + 1) / 2;
+ else
+ actual_len = len;
+
+ /*
+ * Validate that all delta_t in the pattern are the same, with the
+ * exception of the middle element in case of ping_pong.
+ */
+ delta_t = pattern[1].delta_t;
+ for (i = 2; i < len; i++) {
+ if (pattern[i].delta_t != delta_t) {
+ /*
+ * Allow last entry in the full or shortened pattern to
+ * specify hi pause. Reject other variations.
+ */
+ if (i != actual_len - 1)
+ goto out_free_pattern;
+ }
+ }
+
+ /* LPG_RAMP_DURATION_REG is a 9bit */
+ if (delta_t >= BIT(9))
+ goto out_free_pattern;
+
+ /*
+ * Find "low pause" and "high pause" in the pattern in the LUT case.
+ * SDAM-based devices without dedicated LUT SDAM require equal
+ * duration of all steps.
+ */
+ if (lpg->lut_base || lpg->lut_sdam) {
+ lo_pause = pattern[0].delta_t;
+ hi_pause = pattern[actual_len - 1].delta_t;
+ } else {
+ if (delta_t != pattern[0].delta_t || delta_t != pattern[actual_len - 1].delta_t)
+ goto out_free_pattern;
+ }
+
+
+ mutex_lock(&lpg->lock);
+
+ if (lpg->lut_base)
+ ret = lpg_lut_store(lpg, pattern, actual_len, &lo_idx, &hi_idx);
+ else
+ ret = lpg_lut_store_sdam(lpg, pattern, actual_len, &lo_idx, &hi_idx);
+
+ if (ret < 0)
+ goto out_unlock;
+
+ for (i = 0; i < led->num_channels; i++) {
+ chan = led->channels[i];
+
+ chan->ramp_tick_ms = delta_t;
+ chan->ramp_ping_pong = ping_pong;
+ chan->ramp_oneshot = repeat != -1;
+
+ chan->ramp_lo_pause_ms = lo_pause;
+ chan->ramp_hi_pause_ms = hi_pause;
+
+ chan->pattern_lo_idx = lo_idx;
+ chan->pattern_hi_idx = hi_idx;
+ }
+
+out_unlock:
+ mutex_unlock(&lpg->lock);
+out_free_pattern:
+ kfree(pattern);
+
+ return ret;
+}
+
+static int lpg_pattern_single_set(struct led_classdev *cdev,
+ struct led_pattern *pattern, u32 len,
+ int repeat)
+{
+ struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
+ int ret;
+
+ ret = lpg_pattern_set(led, pattern, len, repeat);
+ if (ret < 0)
+ return ret;
+
+ lpg_brightness_single_set(cdev, LED_FULL);
+
+ return 0;
+}
+
+static int lpg_pattern_mc_set(struct led_classdev *cdev,
+ struct led_pattern *pattern, u32 len,
+ int repeat)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
+ unsigned int triled_mask = 0;
+ int ret, i;
+
+ for (i = 0; i < led->num_channels; i++)
+ triled_mask |= led->channels[i]->triled_mask;
+ triled_set(led->lpg, triled_mask, 0);
+
+ ret = lpg_pattern_set(led, pattern, len, repeat);
+ if (ret < 0)
+ return ret;
+
+ led_mc_calc_color_components(mc, LED_FULL);
+ lpg_brightness_set(led, cdev, mc->subled_info);
+
+ return 0;
+}
+
+static int lpg_pattern_clear(struct lpg_led *led)
+{
+ struct lpg_channel *chan;
+ struct lpg *lpg = led->lpg;
+ int i;
+
+ mutex_lock(&lpg->lock);
+
+ chan = led->channels[0];
+ lpg_lut_free(lpg, chan->pattern_lo_idx, chan->pattern_hi_idx);
+
+ for (i = 0; i < led->num_channels; i++) {
+ chan = led->channels[i];
+ lpg_sdam_configure_triggers(chan, 0);
+ lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask);
+ chan->pattern_lo_idx = 0;
+ chan->pattern_hi_idx = 0;
+ }
+
+ mutex_unlock(&lpg->lock);
+
+ return 0;
+}
+
+static int lpg_pattern_single_clear(struct led_classdev *cdev)
+{
+ struct lpg_led *led = container_of(cdev, struct lpg_led, cdev);
+
+ return lpg_pattern_clear(led);
+}
+
+static int lpg_pattern_mc_clear(struct led_classdev *cdev)
+{
+ struct led_classdev_mc *mc = lcdev_to_mccdev(cdev);
+ struct lpg_led *led = container_of(mc, struct lpg_led, mcdev);
+
+ return lpg_pattern_clear(led);
+}
+
+static inline struct lpg *lpg_pwm_from_chip(struct pwm_chip *chip)
+{
+ return pwmchip_get_drvdata(chip);
+}
+
+static int lpg_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
+{
+ struct lpg *lpg = lpg_pwm_from_chip(chip);
+ struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
+
+ return chan->in_use ? -EBUSY : 0;
+}
+
+/*
+ * Limitations:
+ * - Updating both duty and period is not done atomically, so the output signal
+ * will momentarily be a mix of the settings.
+ * - Changed parameters takes effect immediately.
+ * - A disabled channel outputs a logical 0.
+ */
+static int lpg_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
+ const struct pwm_state *state)
+{
+ struct lpg *lpg = lpg_pwm_from_chip(chip);
+ struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
+ int ret = 0;
+
+ if (state->polarity != PWM_POLARITY_NORMAL)
+ return -EINVAL;
+
+ mutex_lock(&lpg->lock);
+
+ if (state->enabled) {
+ ret = lpg_calc_freq(chan, state->period);
+ if (ret < 0)
+ goto out_unlock;
+
+ lpg_calc_duty(chan, state->duty_cycle);
+ }
+ chan->enabled = state->enabled;
+
+ lpg_apply(chan);
+
+out_unlock:
+ mutex_unlock(&lpg->lock);
+
+ return ret;
+}
+
+static int lpg_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
+ struct pwm_state *state)
+{
+ struct lpg *lpg = lpg_pwm_from_chip(chip);
+ struct lpg_channel *chan = &lpg->channels[pwm->hwpwm];
+ unsigned int resolution;
+ unsigned int pre_div;
+ unsigned int refclk;
+ unsigned int val;
+ unsigned int m;
+ u16 pwm_value;
+ int ret;
+
+ ret = regmap_read(lpg->map, chan->base + LPG_SIZE_CLK_REG, &val);
+ if (ret)
+ return ret;
+
+ if (chan->subtype == LPG_SUBTYPE_HI_RES_PWM) {
+ refclk = lpg_clk_rates_hi_res[FIELD_GET(PWM_CLK_SELECT_HI_RES_MASK, val)];
+ 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 = lpg_pwm_resolution[FIELD_GET(PWM_SIZE_SELECT_MASK, val)];
+ }
+
+ if (refclk) {
+ ret = regmap_read(lpg->map, chan->base + LPG_PREDIV_CLK_REG, &val);
+ if (ret)
+ return ret;
+
+ pre_div = lpg_pre_divs[FIELD_GET(PWM_FREQ_PRE_DIV_MASK, val)];
+ m = FIELD_GET(PWM_FREQ_EXP_MASK, val);
+
+ ret = regmap_bulk_read(lpg->map, chan->base + PWM_VALUE_REG, &pwm_value, sizeof(pwm_value));
+ if (ret)
+ return ret;
+
+ 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 {
+ state->period = 0;
+ state->duty_cycle = 0;
+ }
+
+ ret = regmap_read(lpg->map, chan->base + PWM_ENABLE_CONTROL_REG, &val);
+ if (ret)
+ return ret;
+
+ state->enabled = FIELD_GET(LPG_ENABLE_CONTROL_OUTPUT, val);
+ state->polarity = PWM_POLARITY_NORMAL;
+
+ if (state->duty_cycle > state->period)
+ state->duty_cycle = state->period;
+
+ return 0;
+}
+
+static const struct pwm_ops lpg_pwm_ops = {
+ .request = lpg_pwm_request,
+ .apply = lpg_pwm_apply,
+ .get_state = lpg_pwm_get_state,
+};
+
+static int lpg_add_pwm(struct lpg *lpg)
+{
+ struct pwm_chip *chip;
+ int ret;
+
+ lpg->pwm = chip = devm_pwmchip_alloc(lpg->dev, lpg->num_channels, 0);
+ if (IS_ERR(chip))
+ return PTR_ERR(chip);
+
+ chip->ops = &lpg_pwm_ops;
+ pwmchip_set_drvdata(chip, lpg);
+
+ ret = devm_pwmchip_add(lpg->dev, chip);
+ if (ret)
+ dev_err_probe(lpg->dev, ret, "failed to add PWM chip\n");
+
+ return ret;
+}
+
+static int lpg_parse_channel(struct lpg *lpg, struct device_node *np,
+ struct lpg_channel **channel)
+{
+ struct lpg_channel *chan;
+ u32 color = LED_COLOR_ID_GREEN;
+ u32 reg;
+ int ret;
+
+ ret = of_property_read_u32(np, "reg", &reg);
+ if (ret || !reg || reg > lpg->num_channels)
+ return dev_err_probe(lpg->dev, -EINVAL, "invalid \"reg\" of %pOFn\n", np);
+
+ chan = &lpg->channels[reg - 1];
+ chan->in_use = true;
+
+ ret = of_property_read_u32(np, "color", &color);
+ if (ret < 0 && ret != -EINVAL)
+ return dev_err_probe(lpg->dev, ret,
+ "failed to parse \"color\" of %pOF\n", np);
+
+ chan->color = color;
+
+ *channel = chan;
+
+ return 0;
+}
+
+static int lpg_add_led(struct lpg *lpg, struct device_node *np)
+{
+ struct led_init_data init_data = {};
+ struct led_classdev *cdev;
+ struct mc_subled *info;
+ struct lpg_led *led;
+ const char *state;
+ int num_channels;
+ u32 color = 0;
+ int ret;
+ int i;
+
+ ret = of_property_read_u32(np, "color", &color);
+ if (ret < 0 && ret != -EINVAL)
+ return dev_err_probe(lpg->dev, ret,
+ "failed to parse \"color\" of %pOF\n", np);
+
+ if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
+ num_channels = of_get_available_child_count(np);
+ else
+ num_channels = 1;
+
+ led = devm_kzalloc(lpg->dev, struct_size(led, channels, num_channels), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->lpg = lpg;
+ led->num_channels = num_channels;
+
+ if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI) {
+ info = devm_kcalloc(lpg->dev, num_channels, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ i = 0;
+ for_each_available_child_of_node_scoped(np, child) {
+ ret = lpg_parse_channel(lpg, child, &led->channels[i]);
+ if (ret < 0)
+ return ret;
+
+ info[i].color_index = led->channels[i]->color;
+ info[i].intensity = 0;
+ i++;
+ }
+
+ led->mcdev.subled_info = info;
+ led->mcdev.num_colors = num_channels;
+
+ cdev = &led->mcdev.led_cdev;
+ cdev->brightness_set_blocking = lpg_brightness_mc_set;
+ cdev->blink_set = lpg_blink_mc_set;
+
+ /* Register pattern accessors if we have a LUT block or when using PPG */
+ if (lpg->lut_base || lpg->lpg_chan_sdam) {
+ cdev->pattern_set = lpg_pattern_mc_set;
+ cdev->pattern_clear = lpg_pattern_mc_clear;
+ }
+ } else {
+ ret = lpg_parse_channel(lpg, np, &led->channels[0]);
+ if (ret < 0)
+ return ret;
+
+ cdev = &led->cdev;
+ cdev->brightness_set_blocking = lpg_brightness_single_set;
+ cdev->blink_set = lpg_blink_single_set;
+
+ /* Register pattern accessors if we have a LUT block or when using PPG */
+ if (lpg->lut_base || lpg->lpg_chan_sdam) {
+ cdev->pattern_set = lpg_pattern_single_set;
+ cdev->pattern_clear = lpg_pattern_single_clear;
+ }
+ }
+
+ cdev->default_trigger = of_get_property(np, "linux,default-trigger", NULL);
+
+ if (lpg->lpg_chan_sdam)
+ cdev->max_brightness = PPG_MAX_LED_BRIGHTNESS;
+ else
+ cdev->max_brightness = LPG_RESOLUTION_9BIT - 1;
+
+ if (!of_property_read_string(np, "default-state", &state) &&
+ !strcmp(state, "on"))
+ cdev->brightness = cdev->max_brightness;
+ else
+ cdev->brightness = LED_OFF;
+
+ cdev->brightness_set_blocking(cdev, cdev->brightness);
+
+ init_data.fwnode = of_fwnode_handle(np);
+
+ if (color == LED_COLOR_ID_RGB || color == LED_COLOR_ID_MULTI)
+ ret = devm_led_classdev_multicolor_register_ext(lpg->dev, &led->mcdev, &init_data);
+ else
+ ret = devm_led_classdev_register_ext(lpg->dev, &led->cdev, &init_data);
+ if (ret)
+ dev_err_probe(lpg->dev, ret, "unable to register %s\n", cdev->name);
+
+ return ret;
+}
+
+static int lpg_init_channels(struct lpg *lpg)
+{
+ const struct lpg_data *data = lpg->data;
+ struct lpg_channel *chan;
+ int i;
+
+ lpg->num_channels = data->num_channels;
+ lpg->channels = devm_kcalloc(lpg->dev, data->num_channels,
+ sizeof(struct lpg_channel), GFP_KERNEL);
+ if (!lpg->channels)
+ return -ENOMEM;
+
+ for (i = 0; i < data->num_channels; i++) {
+ chan = &lpg->channels[i];
+
+ chan->lpg = lpg;
+ chan->base = data->channels[i].base;
+ chan->triled_mask = data->channels[i].triled_mask;
+ chan->lut_mask = BIT(i);
+ chan->sdam_offset = data->channels[i].sdam_offset;
+
+ regmap_read(lpg->map, chan->base + LPG_SUBTYPE_REG, &chan->subtype);
+ }
+
+ return 0;
+}
+
+static int lpg_init_triled(struct lpg *lpg)
+{
+ struct device_node *np = lpg->dev->of_node;
+ int ret;
+
+ /* Skip initialization if we don't have a triled block */
+ if (!lpg->data->triled_base)
+ return 0;
+
+ lpg->triled_base = lpg->data->triled_base;
+ lpg->triled_has_atc_ctl = lpg->data->triled_has_atc_ctl;
+ lpg->triled_has_src_sel = lpg->data->triled_has_src_sel;
+
+ if (lpg->triled_has_src_sel) {
+ ret = of_property_read_u32(np, "qcom,power-source", &lpg->triled_src);
+ if (ret || lpg->triled_src == 2 || lpg->triled_src > 3)
+ return dev_err_probe(lpg->dev, -EINVAL,
+ "invalid power source\n");
+ }
+
+ /* Disable automatic trickle charge LED */
+ if (lpg->triled_has_atc_ctl)
+ regmap_write(lpg->map, lpg->triled_base + TRI_LED_ATC_CTL, 0);
+
+ /* Configure power source */
+ if (lpg->triled_has_src_sel)
+ regmap_write(lpg->map, lpg->triled_base + TRI_LED_SRC_SEL, lpg->triled_src);
+
+ /* Default all outputs to off */
+ regmap_write(lpg->map, lpg->triled_base + TRI_LED_EN_CTL, 0);
+
+ return 0;
+}
+
+static int lpg_init_lut(struct lpg *lpg)
+{
+ const struct lpg_data *data = lpg->data;
+
+ if (!data->lut_size)
+ return 0;
+
+ lpg->lut_size = data->lut_size;
+ if (data->lut_base)
+ lpg->lut_base = data->lut_base;
+
+ lpg->lut_bitmap = devm_bitmap_zalloc(lpg->dev, lpg->lut_size, GFP_KERNEL);
+ if (!lpg->lut_bitmap)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int lpg_init_sdam(struct lpg *lpg)
+{
+ int i, sdam_count, rc;
+ u8 val = 0;
+
+ sdam_count = of_property_count_strings(lpg->dev->of_node, "nvmem-names");
+ if (sdam_count <= 0)
+ return 0;
+ if (sdam_count > SDAM_MAX_DEVICES)
+ return -EINVAL;
+
+ /* Get the 1st SDAM device for LPG/LUT config */
+ lpg->lpg_chan_sdam = devm_nvmem_device_get(lpg->dev, "lpg_chan_sdam");
+ if (IS_ERR(lpg->lpg_chan_sdam))
+ return dev_err_probe(lpg->dev, PTR_ERR(lpg->lpg_chan_sdam),
+ "Failed to get LPG chan SDAM device\n");
+
+ if (sdam_count == 1) {
+ /* Get PBS device node if single SDAM device */
+ lpg->pbs_dev = get_pbs_client_device(lpg->dev);
+ if (IS_ERR(lpg->pbs_dev))
+ return dev_err_probe(lpg->dev, PTR_ERR(lpg->pbs_dev),
+ "Failed to get PBS client device\n");
+ } else if (sdam_count == 2) {
+ /* Get the 2nd SDAM device for LUT pattern */
+ lpg->lut_sdam = devm_nvmem_device_get(lpg->dev, "lut_sdam");
+ if (IS_ERR(lpg->lut_sdam))
+ return dev_err_probe(lpg->dev, PTR_ERR(lpg->lut_sdam),
+ "Failed to get LPG LUT SDAM device\n");
+ }
+
+ for (i = 0; i < lpg->num_channels; i++) {
+ struct lpg_channel *chan = &lpg->channels[i];
+
+ if (chan->sdam_offset) {
+ rc = nvmem_device_write(lpg->lpg_chan_sdam,
+ SDAM_PBS_SCRATCH_LUT_COUNTER_OFFSET + chan->sdam_offset, 1, &val);
+ if (rc < 0)
+ return rc;
+
+ rc = lpg_sdam_configure_triggers(chan, 0);
+ if (rc < 0)
+ return rc;
+
+ rc = lpg_clear_pbs_trigger(chan->lpg, chan->lut_mask);
+ if (rc < 0)
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int lpg_probe(struct platform_device *pdev)
+{
+ struct lpg *lpg;
+ int ret;
+ int i;
+
+ lpg = devm_kzalloc(&pdev->dev, sizeof(*lpg), GFP_KERNEL);
+ if (!lpg)
+ return -ENOMEM;
+
+ lpg->data = of_device_get_match_data(&pdev->dev);
+ if (!lpg->data)
+ return -EINVAL;
+
+ lpg->dev = &pdev->dev;
+ mutex_init(&lpg->lock);
+
+ lpg->map = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!lpg->map)
+ return dev_err_probe(&pdev->dev, -ENXIO, "parent regmap unavailable\n");
+
+ ret = lpg_init_channels(lpg);
+ if (ret < 0)
+ return ret;
+
+ ret = lpg_parse_dtest(lpg);
+ if (ret < 0)
+ return ret;
+
+ ret = lpg_init_triled(lpg);
+ if (ret < 0)
+ return ret;
+
+ ret = lpg_init_sdam(lpg);
+ if (ret < 0)
+ return ret;
+
+ ret = lpg_init_lut(lpg);
+ if (ret < 0)
+ return ret;
+
+ for_each_available_child_of_node_scoped(pdev->dev.of_node, np) {
+ ret = lpg_add_led(lpg, np);
+ if (ret)
+ return ret;
+ }
+
+ for (i = 0; i < lpg->num_channels; i++)
+ lpg_apply_dtest(&lpg->channels[i]);
+
+ return lpg_add_pwm(lpg);
+}
+
+static const struct lpg_data pm660l_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 49,
+
+ .triled_base = 0xd000,
+ .triled_has_atc_ctl = true,
+ .triled_has_src_sel = true,
+
+ .num_channels = 4,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100, .triled_mask = BIT(5) },
+ { .base = 0xb200, .triled_mask = BIT(6) },
+ { .base = 0xb300, .triled_mask = BIT(7) },
+ { .base = 0xb400 },
+ },
+};
+
+static const struct lpg_data pm8916_pwm_data = {
+ .num_channels = 1,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xbc00 },
+ },
+};
+
+static const struct lpg_data pm8941_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 64,
+
+ .triled_base = 0xd000,
+ .triled_has_atc_ctl = true,
+ .triled_has_src_sel = true,
+
+ .num_channels = 8,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100 },
+ { .base = 0xb200 },
+ { .base = 0xb300 },
+ { .base = 0xb400 },
+ { .base = 0xb500, .triled_mask = BIT(5) },
+ { .base = 0xb600, .triled_mask = BIT(6) },
+ { .base = 0xb700, .triled_mask = BIT(7) },
+ { .base = 0xb800 },
+ },
+};
+
+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,
+
+ .num_channels = 6,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100 },
+ { .base = 0xb200 },
+ { .base = 0xb300 },
+ { .base = 0xb400 },
+ { .base = 0xb500 },
+ { .base = 0xb600 },
+ },
+};
+
+/* PMI632 uses SDAM instead of LUT for pattern */
+static const struct lpg_data pmi632_lpg_data = {
+ .triled_base = 0xd000,
+
+ .lut_size = 64,
+
+ .num_channels = 5,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb300, .triled_mask = BIT(7), .sdam_offset = 0x48 },
+ { .base = 0xb400, .triled_mask = BIT(6), .sdam_offset = 0x56 },
+ { .base = 0xb500, .triled_mask = BIT(5), .sdam_offset = 0x64 },
+ { .base = 0xb600 },
+ { .base = 0xb700 },
+ },
+};
+
+static const struct lpg_data pmi8994_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 24,
+
+ .triled_base = 0xd000,
+ .triled_has_atc_ctl = true,
+ .triled_has_src_sel = true,
+
+ .num_channels = 4,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100, .triled_mask = BIT(5) },
+ { .base = 0xb200, .triled_mask = BIT(6) },
+ { .base = 0xb300, .triled_mask = BIT(7) },
+ { .base = 0xb400 },
+ },
+};
+
+static const struct lpg_data pmi8998_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 49,
+
+ .triled_base = 0xd000,
+
+ .num_channels = 6,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100 },
+ { .base = 0xb200 },
+ { .base = 0xb300, .triled_mask = BIT(5) },
+ { .base = 0xb400, .triled_mask = BIT(6) },
+ { .base = 0xb500, .triled_mask = BIT(7) },
+ { .base = 0xb600 },
+ },
+};
+
+static const struct lpg_data pm8150b_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 24,
+
+ .triled_base = 0xd000,
+
+ .num_channels = 2,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100, .triled_mask = BIT(7) },
+ { .base = 0xb200, .triled_mask = BIT(6) },
+ },
+};
+
+static const struct lpg_data pm8150l_lpg_data = {
+ .lut_base = 0xb000,
+ .lut_size = 48,
+
+ .triled_base = 0xd000,
+
+ .num_channels = 5,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xb100, .triled_mask = BIT(7) },
+ { .base = 0xb200, .triled_mask = BIT(6) },
+ { .base = 0xb300, .triled_mask = BIT(5) },
+ { .base = 0xbc00 },
+ { .base = 0xbd00 },
+
+ },
+};
+
+static const struct lpg_data pm8350c_pwm_data = {
+ .triled_base = 0xef00,
+
+ .lut_size = 122,
+
+ .num_channels = 4,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xe800, .triled_mask = BIT(7), .sdam_offset = 0x48 },
+ { .base = 0xe900, .triled_mask = BIT(6), .sdam_offset = 0x56 },
+ { .base = 0xea00, .triled_mask = BIT(5), .sdam_offset = 0x64 },
+ { .base = 0xeb00 },
+ },
+};
+
+static const struct lpg_data pmk8550_pwm_data = {
+ .num_channels = 2,
+ .channels = (const struct lpg_channel_data[]) {
+ { .base = 0xe800 },
+ { .base = 0xe900 },
+ },
+};
+
+static const struct of_device_id lpg_of_table[] = {
+ { .compatible = "qcom,pm660l-lpg", .data = &pm660l_lpg_data },
+ { .compatible = "qcom,pm8150b-lpg", .data = &pm8150b_lpg_data },
+ { .compatible = "qcom,pm8150l-lpg", .data = &pm8150l_lpg_data },
+ { .compatible = "qcom,pm8350c-pwm", .data = &pm8350c_pwm_data },
+ { .compatible = "qcom,pm8916-pwm", .data = &pm8916_pwm_data },
+ { .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 },
+ { .compatible = "qcom,pmk8550-pwm", .data = &pmk8550_pwm_data },
+ {}
+};
+MODULE_DEVICE_TABLE(of, lpg_of_table);
+
+static struct platform_driver lpg_driver = {
+ .probe = lpg_probe,
+ .driver = {
+ .name = "qcom-spmi-lpg",
+ .of_match_table = lpg_of_table,
+ },
+};
+module_platform_driver(lpg_driver);
+
+MODULE_DESCRIPTION("Qualcomm LPG LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/simatic/Kconfig b/drivers/leds/simatic/Kconfig
new file mode 100644
index 000000000000..e616cc6d6051
--- /dev/null
+++ b/drivers/leds/simatic/Kconfig
@@ -0,0 +1,51 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config LEDS_SIEMENS_SIMATIC_IPC
+ tristate "LED driver for Siemens Simatic IPCs"
+ depends on LEDS_CLASS
+ depends on SIEMENS_SIMATIC_IPC
+ default y
+ help
+ This option enables support for the LEDs of several Industrial PCs
+ from Siemens.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-leds.
+
+config LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE
+ tristate "LED driver for Siemens Simatic IPCs based on Intel Apollo Lake GPIO"
+ depends on LEDS_GPIO
+ depends on PINCTRL_BROXTON
+ depends on SIEMENS_SIMATIC_IPC
+ default LEDS_SIEMENS_SIMATIC_IPC
+ help
+ This option enables support for the LEDs of several Industrial PCs
+ from Siemens based on Apollo Lake GPIO i.e. IPC127E.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-leds-gpio-apollolake.
+
+config LEDS_SIEMENS_SIMATIC_IPC_F7188X
+ tristate "LED driver for Siemens Simatic IPCs based on Nuvoton GPIO"
+ depends on LEDS_GPIO
+ depends on GPIO_F7188X
+ depends on SIEMENS_SIMATIC_IPC
+ default LEDS_SIEMENS_SIMATIC_IPC
+ help
+ This option enables support for the LEDs of several Industrial PCs
+ from Siemens based on Nuvoton GPIO i.e. IPC227G.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-leds-gpio-f7188x.
+
+config LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE
+ tristate "LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO"
+ depends on LEDS_GPIO
+ depends on PINCTRL_ELKHARTLAKE
+ depends on SIEMENS_SIMATIC_IPC
+ default LEDS_SIEMENS_SIMATIC_IPC
+ help
+ This option enables support for the LEDs of several Industrial PCs
+ from Siemens based on Elkhart Lake GPIO i.e. BX-21A.
+
+ To compile this driver as a module, choose M here: the module
+ will be called simatic-ipc-leds-gpio-elkhartlake.
diff --git a/drivers/leds/simatic/Makefile b/drivers/leds/simatic/Makefile
new file mode 100644
index 000000000000..783578f11bb0
--- /dev/null
+++ b/drivers/leds/simatic/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC) += simatic-ipc-leds.o
+obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_APOLLOLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-apollolake.o
+obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_F7188X) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-f7188x.o
+obj-$(CONFIG_LEDS_SIEMENS_SIMATIC_IPC_ELKHARTLAKE) += simatic-ipc-leds-gpio-core.o simatic-ipc-leds-gpio-elkhartlake.o
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
new file mode 100644
index 000000000000..c98c370687c2
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-apollolake.c
@@ -0,0 +1,67 @@
+// 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("apollolake-pinctrl.0", 52, NULL, 0, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 53, NULL, 1, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 57, NULL, 2, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 58, NULL, 3, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 60, NULL, 4, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 51, 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("apollolake-pinctrl.0", 56, NULL, 6, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("apollolake-pinctrl.0", 59, NULL, 7, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static int simatic_ipc_leds_gpio_apollolake_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_apollolake_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_apollolake_driver = {
+ .probe = simatic_ipc_leds_gpio_apollolake_probe,
+ .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");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
new file mode 100644
index 000000000000..9bc5f361a06b
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-core.c
@@ -0,0 +1,108 @@
+// 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 platform_device *simatic_leds_pdev;
+
+static const struct gpio_led simatic_ipc_gpio_leds[] = {
+ { .name = "red:" LED_FUNCTION_STATUS "-1" },
+ { .name = "green:" LED_FUNCTION_STATUS "-1" },
+ { .name = "red:" LED_FUNCTION_STATUS "-2" },
+ { .name = "green:" LED_FUNCTION_STATUS "-2" },
+ { .name = "red:" LED_FUNCTION_STATUS "-3" },
+ { .name = "green:" LED_FUNCTION_STATUS "-3" },
+};
+
+static const struct gpio_led_platform_data simatic_ipc_gpio_leds_pdata = {
+ .num_leds = ARRAY_SIZE(simatic_ipc_gpio_leds),
+ .leds = simatic_ipc_gpio_leds,
+};
+
+void simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
+ struct gpiod_lookup_table *table,
+ struct gpiod_lookup_table *table_extra)
+{
+ gpiod_remove_lookup_table(table);
+ gpiod_remove_lookup_table(table_extra);
+ platform_device_unregister(simatic_leds_pdev);
+}
+EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_remove);
+
+int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
+ struct gpiod_lookup_table *table,
+ struct gpiod_lookup_table *table_extra)
+{
+ const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct gpio_desc *gpiod;
+ int err;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_127E:
+ case SIMATIC_IPC_DEVICE_227G:
+ case SIMATIC_IPC_DEVICE_BX_21A:
+ case SIMATIC_IPC_DEVICE_BX_59A:
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ gpiod_add_lookup_table(table);
+ simatic_leds_pdev = platform_device_register_resndata(NULL,
+ "leds-gpio", PLATFORM_DEVID_NONE, NULL, 0,
+ &simatic_ipc_gpio_leds_pdata,
+ sizeof(simatic_ipc_gpio_leds_pdata));
+ if (IS_ERR(simatic_leds_pdev)) {
+ err = PTR_ERR(simatic_leds_pdev);
+ goto out;
+ }
+
+ if (!table_extra)
+ return 0;
+
+ table_extra->dev_id = dev_name(dev);
+ gpiod_add_lookup_table(table_extra);
+
+ /* PM_BIOS_BOOT_N */
+ gpiod = gpiod_get_index(dev, NULL, 6, GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod)) {
+ err = PTR_ERR(gpiod);
+ goto out;
+ }
+ gpiod_put(gpiod);
+
+ /* PM_WDT_OUT */
+ gpiod = gpiod_get_index(dev, NULL, 7, GPIOD_OUT_LOW);
+ if (IS_ERR(gpiod)) {
+ err = PTR_ERR(gpiod);
+ goto out;
+ }
+ gpiod_put(gpiod);
+
+ return 0;
+out:
+ simatic_ipc_leds_gpio_remove(pdev, table, table_extra);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(simatic_ipc_leds_gpio_probe);
+
+MODULE_DESCRIPTION("Siemens SIMATIC IPC core driver for GPIO based LEDs");
+MODULE_LICENSE("GPL v2");
+MODULE_SOFTDEP("pre: platform:leds-gpio");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
new file mode 100644
index 000000000000..7f7cff275448
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-elkhartlake.c
@@ -0,0 +1,57 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for GPIO based LEDs
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Author:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+
+#include "simatic-ipc-leds-gpio.h"
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX("INTC1020:04", 72, NULL, 0, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 77, NULL, 1, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 78, NULL, 2, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 58, NULL, 3, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 60, NULL, 4, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("INTC1020:04", 62, NULL, 5, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static int simatic_ipc_leds_gpio_elkhartlake_probe(struct platform_device *pdev)
+{
+ return simatic_ipc_leds_gpio_probe(pdev, &simatic_ipc_led_gpio_table,
+ NULL);
+}
+
+static void simatic_ipc_leds_gpio_elkhartlake_remove(struct platform_device *pdev)
+{
+ simatic_ipc_leds_gpio_remove(pdev, &simatic_ipc_led_gpio_table, NULL);
+}
+
+static struct platform_driver simatic_ipc_led_gpio_elkhartlake_driver = {
+ .probe = simatic_ipc_leds_gpio_elkhartlake_probe,
+ .remove = simatic_ipc_leds_gpio_elkhartlake_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+module_platform_driver(simatic_ipc_led_gpio_elkhartlake_driver);
+
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Intel Elkhart Lake GPIO");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core platform:elkhartlake-pinctrl");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
new file mode 100644
index 000000000000..bc23d701bcb7
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio-f7188x.c
@@ -0,0 +1,107 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for GPIO based LEDs
+ *
+ * Copyright (c) Siemens AG, 2023
+ *
+ * Author:
+ * Henning Schild <henning.schild@siemens.com>
+ */
+
+#include <linux/gpio/machine.h>
+#include <linux/gpio/consumer.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+
+#include "simatic-ipc-leds-gpio.h"
+
+struct simatic_ipc_led_tables {
+ struct gpiod_lookup_table *led_lookup_table;
+ struct gpiod_lookup_table *led_lookup_table_extra;
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_227g = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 1, NULL, 1, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 2, NULL, 2, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 3, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 4, NULL, 4, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 5, NULL, 5, GPIO_ACTIVE_LOW),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_extra_227g = {
+ .dev_id = NULL, /* Filled during initialization */
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-3", 6, NULL, 6, GPIO_ACTIVE_HIGH),
+ GPIO_LOOKUP_IDX("gpio-f7188x-3", 7, NULL, 7, GPIO_ACTIVE_HIGH),
+ {} /* Terminating entry */
+ },
+};
+
+static struct gpiod_lookup_table simatic_ipc_led_gpio_table_bx_59a = {
+ .dev_id = "leds-gpio",
+ .table = {
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 0, NULL, 0, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-2", 3, NULL, 1, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-5", 3, NULL, 2, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-5", 2, NULL, 3, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 7, NULL, 4, GPIO_ACTIVE_LOW),
+ GPIO_LOOKUP_IDX("gpio-f7188x-7", 4, NULL, 5, GPIO_ACTIVE_LOW),
+ {} /* Terminating entry */
+ }
+};
+
+static int simatic_ipc_leds_gpio_f7188x_probe(struct platform_device *pdev)
+{
+ const struct simatic_ipc_platform *plat = dev_get_platdata(&pdev->dev);
+ struct simatic_ipc_led_tables *led_tables;
+
+ led_tables = devm_kzalloc(&pdev->dev, sizeof(*led_tables), GFP_KERNEL);
+ if (!led_tables)
+ return -ENOMEM;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_227G:
+ led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_227g;
+ led_tables->led_lookup_table_extra = &simatic_ipc_led_gpio_table_extra_227g;
+ break;
+ case SIMATIC_IPC_DEVICE_BX_59A:
+ led_tables->led_lookup_table = &simatic_ipc_led_gpio_table_bx_59a;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ platform_set_drvdata(pdev, led_tables);
+ return simatic_ipc_leds_gpio_probe(pdev, led_tables->led_lookup_table,
+ led_tables->led_lookup_table_extra);
+}
+
+static void simatic_ipc_leds_gpio_f7188x_remove(struct platform_device *pdev)
+{
+ struct simatic_ipc_led_tables *led_tables = platform_get_drvdata(pdev);
+
+ simatic_ipc_leds_gpio_remove(pdev, led_tables->led_lookup_table,
+ led_tables->led_lookup_table_extra);
+}
+
+static struct platform_driver simatic_ipc_led_gpio_driver = {
+ .probe = simatic_ipc_leds_gpio_f7188x_probe,
+ .remove = simatic_ipc_leds_gpio_f7188x_remove,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+};
+module_platform_driver(simatic_ipc_led_gpio_driver);
+
+MODULE_DESCRIPTION("LED driver for Siemens Simatic IPCs based on Nuvoton GPIO");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" KBUILD_MODNAME);
+MODULE_SOFTDEP("pre: simatic-ipc-leds-gpio-core gpio_f7188x");
+MODULE_AUTHOR("Henning Schild <henning.schild@siemens.com>");
diff --git a/drivers/leds/simatic/simatic-ipc-leds-gpio.h b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
new file mode 100644
index 000000000000..6b2519809cee
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds-gpio.h
@@ -0,0 +1,22 @@
+/* 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>
+ */
+
+#ifndef _SIMATIC_IPC_LEDS_GPIO_H
+#define _SIMATIC_IPC_LEDS_GPIO_H
+
+int simatic_ipc_leds_gpio_probe(struct platform_device *pdev,
+ struct gpiod_lookup_table *table,
+ struct gpiod_lookup_table *table_extra);
+
+void simatic_ipc_leds_gpio_remove(struct platform_device *pdev,
+ struct gpiod_lookup_table *table,
+ struct gpiod_lookup_table *table_extra);
+
+#endif /* _SIMATIC_IPC_LEDS_GPIO_H */
diff --git a/drivers/leds/simatic/simatic-ipc-leds.c b/drivers/leds/simatic/simatic-ipc-leds.c
new file mode 100644
index 000000000000..348679f0d1b7
--- /dev/null
+++ b/drivers/leds/simatic/simatic-ipc-leds.c
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Siemens SIMATIC IPC driver for LEDs
+ *
+ * Copyright (c) Siemens AG, 2018-2021
+ *
+ * Authors:
+ * Henning Schild <henning.schild@siemens.com>
+ * Jan Kiszka <jan.kiszka@siemens.com>
+ * Gerd Haeussler <gerd.haeussler.ext@siemens.com>
+ */
+
+#include <linux/ioport.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_data/x86/simatic-ipc-base.h>
+#include <linux/platform_device.h>
+#include <linux/sizes.h>
+#include <linux/spinlock.h>
+
+#define SIMATIC_IPC_LED_PORT_BASE 0x404E
+
+struct simatic_ipc_led {
+ unsigned int value; /* mask for io */
+ char *name;
+ struct led_classdev cdev;
+};
+
+static struct simatic_ipc_led simatic_ipc_leds_io[] = {
+ {1 << 15, "green:" LED_FUNCTION_STATUS "-1" },
+ {1 << 7, "yellow:" LED_FUNCTION_STATUS "-1" },
+ {1 << 14, "red:" LED_FUNCTION_STATUS "-2" },
+ {1 << 6, "yellow:" LED_FUNCTION_STATUS "-2" },
+ {1 << 13, "red:" LED_FUNCTION_STATUS "-3" },
+ {1 << 5, "yellow:" LED_FUNCTION_STATUS "-3" },
+ { }
+};
+
+static struct resource simatic_ipc_led_io_res =
+ DEFINE_RES_IO_NAMED(SIMATIC_IPC_LED_PORT_BASE, SZ_2, KBUILD_MODNAME);
+
+static DEFINE_SPINLOCK(reg_lock);
+
+static inline struct simatic_ipc_led *cdev_to_led(struct led_classdev *led_cd)
+{
+ return container_of(led_cd, struct simatic_ipc_led, cdev);
+}
+
+static void simatic_ipc_led_set_io(struct led_classdev *led_cd,
+ enum led_brightness brightness)
+{
+ struct simatic_ipc_led *led = cdev_to_led(led_cd);
+ unsigned long flags;
+ unsigned int val;
+
+ spin_lock_irqsave(&reg_lock, flags);
+
+ val = inw(SIMATIC_IPC_LED_PORT_BASE);
+ if (brightness == LED_OFF)
+ outw(val | led->value, SIMATIC_IPC_LED_PORT_BASE);
+ else
+ outw(val & ~led->value, SIMATIC_IPC_LED_PORT_BASE);
+
+ spin_unlock_irqrestore(&reg_lock, flags);
+}
+
+static enum led_brightness simatic_ipc_led_get_io(struct led_classdev *led_cd)
+{
+ struct simatic_ipc_led *led = cdev_to_led(led_cd);
+
+ return inw(SIMATIC_IPC_LED_PORT_BASE) & led->value ? LED_OFF : led_cd->max_brightness;
+}
+
+static int simatic_ipc_leds_probe(struct platform_device *pdev)
+{
+ const struct simatic_ipc_platform *plat = pdev->dev.platform_data;
+ struct device *dev = &pdev->dev;
+ struct simatic_ipc_led *ipcled;
+ struct led_classdev *cdev;
+ struct resource *res;
+ int err;
+
+ switch (plat->devmode) {
+ case SIMATIC_IPC_DEVICE_227D:
+ case SIMATIC_IPC_DEVICE_427E:
+ res = &simatic_ipc_led_io_res;
+ ipcled = simatic_ipc_leds_io;
+ /* on 227D the two bytes work the other way araound */
+ if (plat->devmode == SIMATIC_IPC_DEVICE_227D) {
+ while (ipcled->value) {
+ ipcled->value = swab16(ipcled->value);
+ ipcled++;
+ }
+ ipcled = simatic_ipc_leds_io;
+ }
+ if (!devm_request_region(dev, res->start, resource_size(res), KBUILD_MODNAME)) {
+ dev_err(dev, "Unable to register IO resource at %pR\n", res);
+ return -EBUSY;
+ }
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ while (ipcled->value) {
+ cdev = &ipcled->cdev;
+ cdev->brightness_set = simatic_ipc_led_set_io;
+ cdev->brightness_get = simatic_ipc_led_get_io;
+ cdev->max_brightness = LED_ON;
+ cdev->name = ipcled->name;
+
+ err = devm_led_classdev_register(dev, cdev);
+ if (err < 0)
+ return err;
+ ipcled++;
+ }
+
+ return 0;
+}
+
+static struct platform_driver simatic_ipc_led_driver = {
+ .probe = simatic_ipc_leds_probe,
+ .driver = {
+ .name = KBUILD_MODNAME,
+ }
+};
+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/trigger/Kconfig b/drivers/leds/trigger/Kconfig
index 49794b47b51c..c11282a74b5a 100644
--- a/drivers/leds/trigger/Kconfig
+++ b/drivers/leds/trigger/Kconfig
@@ -1,3 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-only
menuconfig LEDS_TRIGGERS
bool "LED Trigger support"
depends on LEDS_CLASS
@@ -10,18 +11,16 @@ if LEDS_TRIGGERS
config LEDS_TRIGGER_TIMER
tristate "LED Timer Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to be controlled by a programmable timer
via sysfs. Some LED hardware can be programmed to start
blinking the LED without any further software interaction.
- For more details read Documentation/leds/leds-class.txt.
+ For more details read Documentation/leds/leds-class.rst.
If unsure, say Y.
config LEDS_TRIGGER_ONESHOT
tristate "LED One-shot Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to blink in one-shot pulses with parameters
controlled via sysfs. It's useful to notify the user on
@@ -33,17 +32,22 @@ config LEDS_TRIGGER_ONESHOT
If unsure, say Y.
-config LEDS_TRIGGER_IDE_DISK
- bool "LED IDE Disk Trigger"
- depends on IDE_GD_ATA
- depends on LEDS_TRIGGERS
+config LEDS_TRIGGER_DISK
+ bool "LED Disk Trigger"
+ depends on ATA
help
- This allows LEDs to be controlled by IDE disk activity.
+ This allows LEDs to be controlled by disk activity.
If unsure, say Y.
+config LEDS_TRIGGER_MTD
+ bool "LED MTD (NAND/NOR) Trigger"
+ depends on MTD
+ help
+ This allows LEDs to be controlled by MTD activity.
+ If unsure, say N.
+
config LEDS_TRIGGER_HEARTBEAT
tristate "LED Heartbeat Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to be controlled by a CPU load average.
The flash frequency is a hyperbolic function of the 1-minute
@@ -52,7 +56,6 @@ config LEDS_TRIGGER_HEARTBEAT
config LEDS_TRIGGER_BACKLIGHT
tristate "LED backlight Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to be controlled as a backlight device: they
turn off and on when the display is blanked and unblanked.
@@ -61,7 +64,7 @@ config LEDS_TRIGGER_BACKLIGHT
config LEDS_TRIGGER_CPU
bool "LED CPU Trigger"
- depends on LEDS_TRIGGERS
+ depends on !PREEMPT_RT
help
This allows LEDs to be controlled by active CPUs. This shows
the active CPUs across an array of LEDs so you can see which
@@ -69,22 +72,26 @@ config LEDS_TRIGGER_CPU
If unsure, say N.
+config LEDS_TRIGGER_ACTIVITY
+ tristate "LED activity Trigger"
+ help
+ This allows LEDs to be controlled by an immediate CPU usage.
+ The flash frequency and duty cycle varies from faint flashes to
+ intense brightness depending on the instant CPU load.
+ If unsure, say N.
+
config LEDS_TRIGGER_GPIO
tristate "LED GPIO Trigger"
- depends on LEDS_TRIGGERS
- depends on GPIOLIB
+ depends on GPIOLIB || COMPILE_TEST
help
This allows LEDs to be controlled by gpio events. It's good
when using gpios as switches and triggering the needed LEDs
- from there. One use case is n810's keypad LEDs that could
- be triggered by this trigger when user slides up to show
- keypad.
+ from there. Triggers are defined as device properties.
If unsure, say N.
config LEDS_TRIGGER_DEFAULT_ON
tristate "LED Default ON Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to be initialised in the ON state.
If unsure, say Y.
@@ -94,7 +101,6 @@ comment "iptables trigger is under Netfilter config (LED target)"
config LEDS_TRIGGER_TRANSIENT
tristate "LED Transient Trigger"
- depends on LEDS_TRIGGERS
help
This allows one time activation of a transient state on
GPIO/PWM based hardware.
@@ -102,10 +108,57 @@ config LEDS_TRIGGER_TRANSIENT
config LEDS_TRIGGER_CAMERA
tristate "LED Camera Flash/Torch Trigger"
- depends on LEDS_TRIGGERS
help
This allows LEDs to be controlled as a camera flash/torch device.
This enables direct flash/torch on/off by the driver, kernel space.
If unsure, say Y.
+config LEDS_TRIGGER_PANIC
+ bool "LED Panic Trigger"
+ help
+ This allows LEDs to be configured to blink on a kernel panic.
+ Enabling this option will allow to mark certain LEDs as panic indicators,
+ allowing to blink them on a kernel panic, even if they are set to
+ a different trigger.
+ If unsure, say Y.
+
+config LEDS_TRIGGER_NETDEV
+ tristate "LED Netdev Trigger"
+ depends on NET
+ help
+ This allows LEDs to be controlled by network device activity.
+ If unsure, say Y.
+
+config LEDS_TRIGGER_PATTERN
+ tristate "LED Pattern Trigger"
+ help
+ This allows LEDs to be controlled by a software or hardware pattern
+ which is a series of tuples, of brightness and duration (ms).
+ If unsure, say N
+
+config LEDS_TRIGGER_TTY
+ tristate "LED Trigger for TTY devices"
+ depends on TTY
+ help
+ This allows LEDs to be controlled by activity on ttys which includes
+ serial devices like /dev/ttyS0.
+
+ 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 1abf48dacf7e..3b3628889f68 100644
--- a/drivers/leds/trigger/Makefile
+++ b/drivers/leds/trigger/Makefile
@@ -1,10 +1,18 @@
+# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o
-obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
+obj-$(CONFIG_LEDS_TRIGGER_DISK) += ledtrig-disk.o
+obj-$(CONFIG_LEDS_TRIGGER_MTD) += ledtrig-mtd.o
obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o
obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o
obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o
obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o
+obj-$(CONFIG_LEDS_TRIGGER_ACTIVITY) += ledtrig-activity.o
obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o
obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o
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_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
new file mode 100644
index 000000000000..c973246a57f9
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-activity.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Activity LED trigger
+ *
+ * Copyright (C) 2017 Willy Tarreau <w@1wt.eu>
+ * Partially based on Atsushi Nemoto's ledtrig-heartbeat.c.
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/kernel_stat.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/panic_notifier.h>
+#include <linux/reboot.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include "../leds.h"
+
+static int panic_detected;
+
+struct activity_data {
+ struct timer_list timer;
+ struct led_classdev *led_cdev;
+ u64 last_used;
+ u64 last_boot;
+ int time_left;
+ int state;
+ int invert;
+};
+
+static void led_activity_function(struct timer_list *t)
+{
+ struct activity_data *activity_data = timer_container_of(activity_data,
+ t, timer);
+ struct led_classdev *led_cdev = activity_data->led_cdev;
+ unsigned int target;
+ unsigned int usage;
+ int delay;
+ u64 curr_used;
+ u64 curr_boot;
+ s32 diff_used;
+ s32 diff_boot;
+ int cpus;
+ int i;
+
+ if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
+ led_cdev->blink_brightness = led_cdev->new_blink_brightness;
+
+ if (unlikely(panic_detected)) {
+ /* full brightness in case of panic */
+ led_set_brightness_nosleep(led_cdev, led_cdev->blink_brightness);
+ return;
+ }
+
+ cpus = 0;
+ curr_used = 0;
+
+ for_each_possible_cpu(i) {
+ struct kernel_cpustat kcpustat;
+
+ kcpustat_cpu_fetch(&kcpustat, i);
+
+ curr_used += kcpustat.cpustat[CPUTIME_USER]
+ + kcpustat.cpustat[CPUTIME_NICE]
+ + kcpustat.cpustat[CPUTIME_SYSTEM]
+ + kcpustat.cpustat[CPUTIME_SOFTIRQ]
+ + kcpustat.cpustat[CPUTIME_IRQ];
+ cpus++;
+ }
+
+ /* We come here every 100ms in the worst case, so that's 100M ns of
+ * cumulated time. By dividing by 2^16, we get the time resolution
+ * down to 16us, ensuring we won't overflow 32-bit computations below
+ * even up to 3k CPUs, while keeping divides cheap on smaller systems.
+ */
+ curr_boot = ktime_get_boottime_ns() * cpus;
+ diff_boot = (curr_boot - activity_data->last_boot) >> 16;
+ diff_used = (curr_used - activity_data->last_used) >> 16;
+ activity_data->last_boot = curr_boot;
+ activity_data->last_used = curr_used;
+
+ if (diff_boot <= 0 || diff_used < 0)
+ usage = 0;
+ else if (diff_used >= diff_boot)
+ usage = 100;
+ else
+ usage = 100 * diff_used / diff_boot;
+
+ /*
+ * Now we know the total boot_time multiplied by the number of CPUs, and
+ * the total idle+wait time for all CPUs. We'll compare how they evolved
+ * since last call. The % of overall CPU usage is :
+ *
+ * 1 - delta_idle / delta_boot
+ *
+ * What we want is that when the CPU usage is zero, the LED must blink
+ * slowly with very faint flashes that are detectable but not disturbing
+ * (typically 10ms every second, or 10ms ON, 990ms OFF). Then we want
+ * blinking frequency to increase up to the point where the load is
+ * enough to saturate one core in multi-core systems or 50% in single
+ * core systems. At this point it should reach 10 Hz with a 10/90 duty
+ * cycle (10ms ON, 90ms OFF). After this point, the blinking frequency
+ * remains stable (10 Hz) and only the duty cycle increases to report
+ * the activity, up to the point where we have 90ms ON, 10ms OFF when
+ * all cores are saturated. It's important that the LED never stays in
+ * a steady state so that it's easy to distinguish an idle or saturated
+ * machine from a hung one.
+ *
+ * This gives us :
+ * - a target CPU usage of min(50%, 100%/#CPU) for a 10% duty cycle
+ * (10ms ON, 90ms OFF)
+ * - below target :
+ * ON_ms = 10
+ * OFF_ms = 90 + (1 - usage/target) * 900
+ * - above target :
+ * ON_ms = 10 + (usage-target)/(100%-target) * 80
+ * OFF_ms = 90 - (usage-target)/(100%-target) * 80
+ *
+ * In order to keep a good responsiveness, we cap the sleep time to
+ * 100 ms and keep track of the sleep time left. This allows us to
+ * quickly change it if needed.
+ */
+
+ activity_data->time_left -= 100;
+ if (activity_data->time_left <= 0) {
+ activity_data->time_left = 0;
+ activity_data->state = !activity_data->state;
+ led_set_brightness_nosleep(led_cdev,
+ (activity_data->state ^ activity_data->invert) ?
+ led_cdev->blink_brightness : LED_OFF);
+ }
+
+ target = (cpus > 1) ? (100 / cpus) : 50;
+
+ if (usage < target)
+ delay = activity_data->state ?
+ 10 : /* ON */
+ 990 - 900 * usage / target; /* OFF */
+ else
+ delay = activity_data->state ?
+ 10 + 80 * (usage - target) / (100 - target) : /* ON */
+ 90 - 80 * (usage - target) / (100 - target); /* OFF */
+
+
+ if (!activity_data->time_left || delay <= activity_data->time_left)
+ activity_data->time_left = delay;
+
+ delay = min_t(int, activity_data->time_left, 100);
+ mod_timer(&activity_data->timer, jiffies + msecs_to_jiffies(delay));
+}
+
+static ssize_t led_invert_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct activity_data *activity_data = led_trigger_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", activity_data->invert);
+}
+
+static ssize_t led_invert_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct activity_data *activity_data = led_trigger_get_drvdata(dev);
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ activity_data->invert = !!state;
+
+ return size;
+}
+
+static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
+
+static struct attribute *activity_led_attrs[] = {
+ &dev_attr_invert.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(activity_led);
+
+static int activity_activate(struct led_classdev *led_cdev)
+{
+ struct activity_data *activity_data;
+
+ activity_data = kzalloc(sizeof(*activity_data), GFP_KERNEL);
+ if (!activity_data)
+ return -ENOMEM;
+
+ led_set_trigger_data(led_cdev, activity_data);
+
+ activity_data->led_cdev = led_cdev;
+ timer_setup(&activity_data->timer, led_activity_function, 0);
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+ led_activity_function(&activity_data->timer);
+ set_bit(LED_BLINK_SW, &led_cdev->work_flags);
+
+ return 0;
+}
+
+static void activity_deactivate(struct led_classdev *led_cdev)
+{
+ struct activity_data *activity_data = led_get_trigger_data(led_cdev);
+
+ timer_shutdown_sync(&activity_data->timer);
+ kfree(activity_data);
+ clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
+}
+
+static struct led_trigger activity_led_trigger = {
+ .name = "activity",
+ .activate = activity_activate,
+ .deactivate = activity_deactivate,
+ .groups = activity_led_groups,
+};
+
+static int activity_reboot_notifier(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ led_trigger_unregister(&activity_led_trigger);
+ return NOTIFY_DONE;
+}
+
+static int activity_panic_notifier(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ panic_detected = 1;
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block activity_reboot_nb = {
+ .notifier_call = activity_reboot_notifier,
+};
+
+static struct notifier_block activity_panic_nb = {
+ .notifier_call = activity_panic_notifier,
+};
+
+static int __init activity_init(void)
+{
+ int rc = led_trigger_register(&activity_led_trigger);
+
+ if (!rc) {
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &activity_panic_nb);
+ register_reboot_notifier(&activity_reboot_nb);
+ }
+ return rc;
+}
+
+static void __exit activity_exit(void)
+{
+ unregister_reboot_notifier(&activity_reboot_nb);
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &activity_panic_nb);
+ led_trigger_unregister(&activity_led_trigger);
+}
+
+module_init(activity_init);
+module_exit(activity_exit);
+
+MODULE_AUTHOR("Willy Tarreau <w@1wt.eu>");
+MODULE_DESCRIPTION("Activity LED trigger");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c
index 3c9c88a07eb8..c1f0f5becaee 100644
--- a/drivers/leds/trigger/ledtrig-backlight.c
+++ b/drivers/leds/trigger/ledtrig-backlight.c
@@ -1,20 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Backlight emulation LED trigger
*
* Copyright 2008 (C) Rodolfo Giometti <giometti@linux.it>
* Copyright 2008 (C) Eurotech S.p.A. <info@eurotech.it>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/init.h>
-#include <linux/fb.h>
#include <linux/leds.h>
#include "../leds.h"
@@ -25,45 +20,47 @@ struct bl_trig_notifier {
struct led_classdev *led;
int brightness;
int old_status;
- struct notifier_block notifier;
unsigned invert;
+
+ struct list_head entry;
};
-static int fb_notifier_callback(struct notifier_block *p,
- unsigned long event, void *data)
+static DEFINE_MUTEX(ledtrig_backlight_list_mutex);
+static LIST_HEAD(ledtrig_backlight_list);
+
+static void ledtrig_backlight_notify_blank(struct bl_trig_notifier *n, int new_status)
{
- struct bl_trig_notifier *n = container_of(p,
- struct bl_trig_notifier, notifier);
struct led_classdev *led = n->led;
- struct fb_event *fb_event = data;
- int *blank = fb_event->data;
- int new_status = *blank ? BLANK : UNBLANK;
- switch (event) {
- case FB_EVENT_BLANK:
- if (new_status == n->old_status)
- break;
+ if (new_status == n->old_status)
+ return;
- if ((n->old_status == UNBLANK) ^ n->invert) {
- n->brightness = led->brightness;
- __led_set_brightness(led, LED_OFF);
- } else {
- __led_set_brightness(led, n->brightness);
- }
+ if ((n->old_status == UNBLANK) ^ n->invert) {
+ n->brightness = led->brightness;
+ led_set_brightness_nosleep(led, LED_OFF);
+ } else {
+ led_set_brightness_nosleep(led, n->brightness);
+ }
- n->old_status = new_status;
+ n->old_status = new_status;
+}
- break;
- }
+void ledtrig_backlight_blank(bool blank)
+{
+ struct bl_trig_notifier *n;
+ int new_status = blank ? BLANK : UNBLANK;
- return 0;
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+
+ list_for_each_entry(n, &ledtrig_backlight_list, entry)
+ ledtrig_backlight_notify_blank(n, new_status);
}
+EXPORT_SYMBOL(ledtrig_backlight_blank);
static ssize_t bl_trig_invert_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct bl_trig_notifier *n = led->trigger_data;
+ struct bl_trig_notifier *n = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", n->invert);
}
@@ -71,8 +68,8 @@ static ssize_t bl_trig_invert_show(struct device *dev,
static ssize_t bl_trig_invert_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t num)
{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct bl_trig_notifier *n = led->trigger_data;
+ struct led_classdev *led = led_trigger_get_led(dev);
+ struct bl_trig_notifier *n = led_trigger_get_drvdata(dev);
unsigned long invert;
int ret;
@@ -87,79 +84,56 @@ static ssize_t bl_trig_invert_store(struct device *dev,
/* After inverting, we need to update the LED. */
if ((n->old_status == BLANK) ^ n->invert)
- __led_set_brightness(led, LED_OFF);
+ led_set_brightness_nosleep(led, LED_OFF);
else
- __led_set_brightness(led, n->brightness);
+ led_set_brightness_nosleep(led, n->brightness);
return num;
}
static DEVICE_ATTR(inverted, 0644, bl_trig_invert_show, bl_trig_invert_store);
-static void bl_trig_activate(struct led_classdev *led)
-{
- int ret;
+static struct attribute *bl_trig_attrs[] = {
+ &dev_attr_inverted.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(bl_trig);
+static int bl_trig_activate(struct led_classdev *led)
+{
struct bl_trig_notifier *n;
n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL);
- led->trigger_data = n;
- if (!n) {
- dev_err(led->dev, "unable to allocate backlight trigger\n");
- return;
- }
-
- ret = device_create_file(led->dev, &dev_attr_inverted);
- if (ret)
- goto err_invert;
+ if (!n)
+ return -ENOMEM;
+ led_set_trigger_data(led, n);
n->led = led;
n->brightness = led->brightness;
n->old_status = UNBLANK;
- n->notifier.notifier_call = fb_notifier_callback;
- ret = fb_register_client(&n->notifier);
- if (ret)
- dev_err(led->dev, "unable to register backlight trigger\n");
- led->activated = true;
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+ list_add(&n->entry, &ledtrig_backlight_list);
- return;
-
-err_invert:
- led->trigger_data = NULL;
- kfree(n);
+ return 0;
}
static void bl_trig_deactivate(struct led_classdev *led)
{
- struct bl_trig_notifier *n =
- (struct bl_trig_notifier *) led->trigger_data;
-
- if (led->activated) {
- device_remove_file(led->dev, &dev_attr_inverted);
- fb_unregister_client(&n->notifier);
- kfree(n);
- led->activated = false;
- }
+ struct bl_trig_notifier *n = led_get_trigger_data(led);
+
+ guard(mutex)(&ledtrig_backlight_list_mutex);
+ list_del(&n->entry);
+
+ kfree(n);
}
static struct led_trigger bl_led_trigger = {
.name = "backlight",
.activate = bl_trig_activate,
- .deactivate = bl_trig_deactivate
+ .deactivate = bl_trig_deactivate,
+ .groups = bl_trig_groups,
};
-
-static int __init bl_trig_init(void)
-{
- return led_trigger_register(&bl_led_trigger);
-}
-
-static void __exit bl_trig_exit(void)
-{
- led_trigger_unregister(&bl_led_trigger);
-}
-
-module_init(bl_trig_init);
-module_exit(bl_trig_exit);
+module_led_trigger(bl_led_trigger);
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
MODULE_DESCRIPTION("Backlight emulation LED trigger");
diff --git a/drivers/leds/trigger/ledtrig-camera.c b/drivers/leds/trigger/ledtrig-camera.c
index 9bd73a8bad5c..ab1c410872ff 100644
--- a/drivers/leds/trigger/ledtrig-camera.c
+++ b/drivers/leds/trigger/ledtrig-camera.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* Camera Flash and Torch On/Off Trigger
*
@@ -6,11 +7,6 @@
* Copyright 2013 Texas Instruments
*
* Author: Milo(Woogyom) Kim <milo.kim@ti.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
@@ -54,4 +50,4 @@ module_exit(ledtrig_camera_exit);
MODULE_DESCRIPTION("LED Trigger for Camera Flash/Torch Control");
MODULE_AUTHOR("Milo Kim");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c
index 118335eccc56..679323c2ccda 100644
--- a/drivers/leds/trigger/ledtrig-cpu.c
+++ b/drivers/leds/trigger/ledtrig-cpu.c
@@ -1,105 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* ledtrig-cpu.c - LED trigger based on CPU activity
*
- * This LED trigger will be registered for each possible CPU and named as
- * cpu0, cpu1, cpu2, cpu3, etc.
+ * This LED trigger will be registered for first 8 CPUs and named
+ * as cpu0..cpu7. There's additional trigger called cpu that
+ * is on when any CPU is active.
+ *
+ * If you want support for arbitrary number of CPUs, make it one trigger,
+ * with additional sysfs file selecting which CPU to watch.
*
* It can be bound to any LED just like other triggers using either a
* board file or via sysfs interface.
*
* An API named ledtrig_cpu is exported for any user, who want to add CPU
- * activity indication in their code
+ * activity indication in their code.
*
* Copyright 2011 Linus Walleij <linus.walleij@linaro.org>
* Copyright 2011 - 2012 Bryan Wu <bryan.wu@canonical.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
-#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/percpu.h>
#include <linux/syscore_ops.h>
#include <linux/rwsem.h>
+#include <linux/cpu.h>
#include "../leds.h"
#define MAX_NAME_LEN 8
struct led_trigger_cpu {
+ bool is_active;
char name[MAX_NAME_LEN];
struct led_trigger *_trig;
};
static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig);
+static struct led_trigger *trig_cpu_all;
+static atomic_t num_active_cpus = ATOMIC_INIT(0);
+
/**
* ledtrig_cpu - emit a CPU event as a trigger
- * @evt: CPU event to be emitted
+ * @ledevt: CPU event to be emitted
*
* Emit a CPU event on a CPU core, which will trigger a
- * binded LED to turn on or turn off.
+ * bound LED to turn on or turn off.
*/
void ledtrig_cpu(enum cpu_led_event ledevt)
{
- struct led_trigger_cpu *trig = &__get_cpu_var(cpu_trig);
+ struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig);
+ bool is_active = trig->is_active;
/* Locate the correct CPU LED */
switch (ledevt) {
case CPU_LED_IDLE_END:
case CPU_LED_START:
/* Will turn the LED on, max brightness */
- led_trigger_event(trig->_trig, LED_FULL);
+ is_active = true;
break;
case CPU_LED_IDLE_START:
case CPU_LED_STOP:
case CPU_LED_HALTED:
/* Will turn the LED off */
- led_trigger_event(trig->_trig, LED_OFF);
+ is_active = false;
break;
default:
/* Will leave the LED as it is */
break;
}
+
+ if (is_active != trig->is_active) {
+ unsigned int active_cpus;
+ unsigned int total_cpus;
+
+ /* Update trigger state */
+ trig->is_active = is_active;
+ atomic_add(is_active ? 1 : -1, &num_active_cpus);
+ active_cpus = atomic_read(&num_active_cpus);
+ total_cpus = num_present_cpus();
+
+ led_trigger_event(trig->_trig,
+ is_active ? LED_FULL : LED_OFF);
+
+
+ led_trigger_event(trig_cpu_all,
+ DIV_ROUND_UP(LED_FULL * active_cpus, total_cpus));
+
+ }
}
EXPORT_SYMBOL(ledtrig_cpu);
-static int ledtrig_cpu_syscore_suspend(void)
+static int ledtrig_cpu_syscore_suspend(void *data)
{
ledtrig_cpu(CPU_LED_STOP);
return 0;
}
-static void ledtrig_cpu_syscore_resume(void)
+static void ledtrig_cpu_syscore_resume(void *data)
{
ledtrig_cpu(CPU_LED_START);
}
-static void ledtrig_cpu_syscore_shutdown(void)
+static void ledtrig_cpu_syscore_shutdown(void *data)
{
ledtrig_cpu(CPU_LED_HALTED);
}
-static struct syscore_ops ledtrig_cpu_syscore_ops = {
+static const struct syscore_ops ledtrig_cpu_syscore_ops = {
.shutdown = ledtrig_cpu_syscore_shutdown,
.suspend = ledtrig_cpu_syscore_suspend,
.resume = ledtrig_cpu_syscore_resume,
};
+static struct syscore ledtrig_cpu_syscore = {
+ .ops = &ledtrig_cpu_syscore_ops,
+};
+
+static int ledtrig_online_cpu(unsigned int cpu)
+{
+ ledtrig_cpu(CPU_LED_START);
+ return 0;
+}
+
+static int ledtrig_prepare_down_cpu(unsigned int cpu)
+{
+ ledtrig_cpu(CPU_LED_STOP);
+ return 0;
+}
+
static int __init ledtrig_cpu_init(void)
{
- int cpu;
+ unsigned int cpu;
+ int ret;
/* Supports up to 9999 cpu cores */
BUILD_BUG_ON(CONFIG_NR_CPUS > 9999);
/*
+ * Registering a trigger for all CPUs.
+ */
+ led_trigger_register_simple("cpu", &trig_cpu_all);
+
+ /*
* Registering CPU led trigger for each CPU core here
* ignores CPU hotplug, but after this CPU hotplug works
* fine with this trigger.
@@ -107,36 +153,24 @@ static int __init ledtrig_cpu_init(void)
for_each_possible_cpu(cpu) {
struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu);
- snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu);
+ if (cpu >= 8)
+ continue;
+
+ snprintf(trig->name, MAX_NAME_LEN, "cpu%u", cpu);
led_trigger_register_simple(trig->name, &trig->_trig);
}
- register_syscore_ops(&ledtrig_cpu_syscore_ops);
+ register_syscore(&ledtrig_cpu_syscore);
+
+ ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "leds/trigger:starting",
+ ledtrig_online_cpu, ledtrig_prepare_down_cpu);
+ if (ret < 0)
+ pr_err("CPU hotplug notifier for ledtrig-cpu could not be registered: %d\n",
+ ret);
pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n");
return 0;
}
-module_init(ledtrig_cpu_init);
-
-static void __exit ledtrig_cpu_exit(void)
-{
- int cpu;
-
- for_each_possible_cpu(cpu) {
- struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu);
-
- led_trigger_unregister_simple(trig->_trig);
- trig->_trig = NULL;
- memset(trig->name, 0, MAX_NAME_LEN);
- }
-
- unregister_syscore_ops(&ledtrig_cpu_syscore_ops);
-}
-module_exit(ledtrig_cpu_exit);
-
-MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
-MODULE_AUTHOR("Bryan Wu <bryan.wu@canonical.com>");
-MODULE_DESCRIPTION("CPU LED trigger");
-MODULE_LICENSE("GPL");
+device_initcall(ledtrig_cpu_init);
diff --git a/drivers/leds/trigger/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c
index 81a91be8e18d..8678e64a5c33 100644
--- a/drivers/leds/trigger/ledtrig-default-on.c
+++ b/drivers/leds/trigger/ledtrig-default-on.c
@@ -1,14 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Kernel Default ON Trigger
*
* Copyright 2008 Nick Forbes <nick.forbes@incepta.com>
*
* Based on Richard Purdie's ledtrig-timer.c.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
@@ -17,29 +13,19 @@
#include <linux/leds.h>
#include "../leds.h"
-static void defon_trig_activate(struct led_classdev *led_cdev)
+static int defon_trig_activate(struct led_classdev *led_cdev)
{
- __led_set_brightness(led_cdev, led_cdev->max_brightness);
+ led_set_brightness_nosleep(led_cdev, led_cdev->max_brightness);
+ return 0;
}
static struct led_trigger defon_led_trigger = {
.name = "default-on",
.activate = defon_trig_activate,
};
-
-static int __init defon_trig_init(void)
-{
- return led_trigger_register(&defon_led_trigger);
-}
-
-static void __exit defon_trig_exit(void)
-{
- led_trigger_unregister(&defon_led_trigger);
-}
-
-module_init(defon_trig_init);
-module_exit(defon_trig_exit);
+module_led_trigger(defon_led_trigger);
MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>");
MODULE_DESCRIPTION("Default-ON LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ledtrig:default-on");
diff --git a/drivers/leds/trigger/ledtrig-disk.c b/drivers/leds/trigger/ledtrig-disk.c
new file mode 100644
index 000000000000..e9b87ee944f2
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-disk.c
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED Disk Activity Trigger
+ *
+ * Copyright 2006 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+
+#define BLINK_DELAY 30
+
+DEFINE_LED_TRIGGER(ledtrig_disk);
+DEFINE_LED_TRIGGER(ledtrig_disk_read);
+DEFINE_LED_TRIGGER(ledtrig_disk_write);
+
+void ledtrig_disk_activity(bool write)
+{
+ led_trigger_blink_oneshot(ledtrig_disk, BLINK_DELAY, BLINK_DELAY, 0);
+ if (write)
+ led_trigger_blink_oneshot(ledtrig_disk_write,
+ BLINK_DELAY, BLINK_DELAY, 0);
+ else
+ led_trigger_blink_oneshot(ledtrig_disk_read,
+ BLINK_DELAY, BLINK_DELAY, 0);
+}
+EXPORT_SYMBOL(ledtrig_disk_activity);
+
+static int __init ledtrig_disk_init(void)
+{
+ led_trigger_register_simple("disk-activity", &ledtrig_disk);
+ led_trigger_register_simple("disk-read", &ledtrig_disk_read);
+ led_trigger_register_simple("disk-write", &ledtrig_disk_write);
+
+ return 0;
+}
+device_initcall(ledtrig_disk_init);
diff --git a/drivers/leds/trigger/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c
index 35812e3a37f2..7f6a2352b0ac 100644
--- a/drivers/leds/trigger/ledtrig-gpio.c
+++ b/drivers/leds/trigger/ledtrig-gpio.c
@@ -1,253 +1,141 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* ledtrig-gio.c - LED Trigger Based on GPIO events
*
* Copyright 2009 Felipe Balbi <me@felipebalbi.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
+ * Copyright 2023 Linus Walleij <linus.walleij@linaro.org>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
-#include <linux/gpio.h>
+#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
-#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/slab.h>
#include "../leds.h"
struct gpio_trig_data {
struct led_classdev *led;
- struct work_struct work;
-
unsigned desired_brightness; /* desired brightness when led is on */
- unsigned inverted; /* true when gpio is inverted */
- unsigned gpio; /* gpio that triggers the leds */
+ struct gpio_desc *gpiod; /* gpio that triggers the led */
};
static irqreturn_t gpio_trig_irq(int irq, void *_led)
{
struct led_classdev *led = _led;
- struct gpio_trig_data *gpio_data = led->trigger_data;
-
- /* just schedule_work since gpio_get_value can sleep */
- schedule_work(&gpio_data->work);
-
- return IRQ_HANDLED;
-};
-
-static void gpio_trig_work(struct work_struct *work)
-{
- struct gpio_trig_data *gpio_data = container_of(work,
- struct gpio_trig_data, work);
+ struct gpio_trig_data *gpio_data = led_get_trigger_data(led);
int tmp;
- if (!gpio_data->gpio)
- return;
-
- tmp = gpio_get_value(gpio_data->gpio);
- if (gpio_data->inverted)
- tmp = !tmp;
-
+ tmp = gpiod_get_value_cansleep(gpio_data->gpiod);
if (tmp) {
if (gpio_data->desired_brightness)
- __led_set_brightness(gpio_data->led,
+ led_set_brightness_nosleep(gpio_data->led,
gpio_data->desired_brightness);
else
- __led_set_brightness(gpio_data->led, LED_FULL);
+ led_set_brightness_nosleep(gpio_data->led, LED_FULL);
} else {
- __led_set_brightness(gpio_data->led, LED_OFF);
+ led_set_brightness_nosleep(gpio_data->led, LED_OFF);
}
-}
-
-static ssize_t gpio_trig_brightness_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
-
- return sprintf(buf, "%u\n", gpio_data->desired_brightness);
-}
-
-static ssize_t gpio_trig_brightness_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t n)
-{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
- unsigned desired_brightness;
- int ret;
- ret = sscanf(buf, "%u", &desired_brightness);
- if (ret < 1 || desired_brightness > 255) {
- dev_err(dev, "invalid value\n");
- return -EINVAL;
- }
-
- gpio_data->desired_brightness = desired_brightness;
-
- return n;
+ return IRQ_HANDLED;
}
-static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
- gpio_trig_brightness_store);
-static ssize_t gpio_trig_inverted_show(struct device *dev,
+static ssize_t desired_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
+ struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
- return sprintf(buf, "%u\n", gpio_data->inverted);
+ return sysfs_emit(buf, "%u\n", gpio_data->desired_brightness);
}
-static ssize_t gpio_trig_inverted_store(struct device *dev,
+static ssize_t desired_brightness_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t n)
{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
- unsigned long inverted;
+ struct gpio_trig_data *gpio_data = led_trigger_get_drvdata(dev);
+ u8 desired_brightness;
int ret;
- ret = kstrtoul(buf, 10, &inverted);
- if (ret < 0)
+ ret = kstrtou8(buf, 10, &desired_brightness);
+ if (ret)
return ret;
- if (inverted > 1)
- return -EINVAL;
-
- gpio_data->inverted = inverted;
-
- /* After inverting, we need to update the LED. */
- schedule_work(&gpio_data->work);
+ gpio_data->desired_brightness = desired_brightness;
return n;
}
-static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show,
- gpio_trig_inverted_store);
-
-static ssize_t gpio_trig_gpio_show(struct device *dev,
- struct device_attribute *attr, char *buf)
-{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
+static DEVICE_ATTR_RW(desired_brightness);
- return sprintf(buf, "%u\n", gpio_data->gpio);
-}
+static struct attribute *gpio_trig_attrs[] = {
+ &dev_attr_desired_brightness.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(gpio_trig);
-static ssize_t gpio_trig_gpio_store(struct device *dev,
- struct device_attribute *attr, const char *buf, size_t n)
+static int gpio_trig_activate(struct led_classdev *led)
{
- struct led_classdev *led = dev_get_drvdata(dev);
- struct gpio_trig_data *gpio_data = led->trigger_data;
- unsigned gpio;
+ struct gpio_trig_data *gpio_data;
+ struct device *dev = led->dev;
int ret;
- ret = sscanf(buf, "%u", &gpio);
- if (ret < 1) {
- dev_err(dev, "couldn't read gpio number\n");
- flush_work(&gpio_data->work);
+ gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL);
+ if (!gpio_data)
+ return -ENOMEM;
+
+ /*
+ * The generic property "trigger-sources" is followed,
+ * and we hope that this is a GPIO.
+ */
+ gpio_data->gpiod = gpiod_get_optional(dev, "trigger-sources", GPIOD_IN);
+ if (IS_ERR(gpio_data->gpiod)) {
+ ret = PTR_ERR(gpio_data->gpiod);
+ kfree(gpio_data);
+ return ret;
+ }
+ if (!gpio_data->gpiod) {
+ dev_err(dev, "no valid GPIO for the trigger\n");
+ kfree(gpio_data);
return -EINVAL;
}
- if (gpio_data->gpio == gpio)
- return n;
+ gpiod_set_consumer_name(gpio_data->gpiod, "led-trigger");
- if (!gpio) {
- if (gpio_data->gpio != 0)
- free_irq(gpio_to_irq(gpio_data->gpio), led);
- gpio_data->gpio = 0;
- return n;
- }
+ gpio_data->led = led;
+ led_set_trigger_data(led, gpio_data);
- ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq,
- IRQF_SHARED | IRQF_TRIGGER_RISING
+ ret = request_threaded_irq(gpiod_to_irq(gpio_data->gpiod), NULL, gpio_trig_irq,
+ IRQF_ONESHOT | IRQF_SHARED | IRQF_TRIGGER_RISING
| IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
if (ret) {
dev_err(dev, "request_irq failed with error %d\n", ret);
- } else {
- if (gpio_data->gpio != 0)
- free_irq(gpio_to_irq(gpio_data->gpio), led);
- gpio_data->gpio = gpio;
+ gpiod_put(gpio_data->gpiod);
+ kfree(gpio_data);
+ return ret;
}
- return ret ? ret : n;
-}
-static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store);
-
-static void gpio_trig_activate(struct led_classdev *led)
-{
- struct gpio_trig_data *gpio_data;
- int ret;
-
- gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL);
- if (!gpio_data)
- return;
-
- ret = device_create_file(led->dev, &dev_attr_gpio);
- if (ret)
- goto err_gpio;
-
- ret = device_create_file(led->dev, &dev_attr_inverted);
- if (ret)
- goto err_inverted;
-
- ret = device_create_file(led->dev, &dev_attr_desired_brightness);
- if (ret)
- goto err_brightness;
-
- gpio_data->led = led;
- led->trigger_data = gpio_data;
- INIT_WORK(&gpio_data->work, gpio_trig_work);
- led->activated = true;
-
- return;
-
-err_brightness:
- device_remove_file(led->dev, &dev_attr_inverted);
+ /* Finally update the LED to initial status */
+ gpio_trig_irq(0, led);
-err_inverted:
- device_remove_file(led->dev, &dev_attr_gpio);
-
-err_gpio:
- kfree(gpio_data);
+ return 0;
}
static void gpio_trig_deactivate(struct led_classdev *led)
{
- struct gpio_trig_data *gpio_data = led->trigger_data;
-
- if (led->activated) {
- device_remove_file(led->dev, &dev_attr_gpio);
- device_remove_file(led->dev, &dev_attr_inverted);
- device_remove_file(led->dev, &dev_attr_desired_brightness);
- flush_work(&gpio_data->work);
- if (gpio_data->gpio != 0)
- free_irq(gpio_to_irq(gpio_data->gpio), led);
- kfree(gpio_data);
- led->activated = false;
- }
+ struct gpio_trig_data *gpio_data = led_get_trigger_data(led);
+
+ free_irq(gpiod_to_irq(gpio_data->gpiod), led);
+ gpiod_put(gpio_data->gpiod);
+ kfree(gpio_data);
}
static struct led_trigger gpio_led_trigger = {
.name = "gpio",
.activate = gpio_trig_activate,
.deactivate = gpio_trig_deactivate,
+ .groups = gpio_trig_groups,
};
-
-static int __init gpio_trig_init(void)
-{
- return led_trigger_register(&gpio_led_trigger);
-}
-module_init(gpio_trig_init);
-
-static void __exit gpio_trig_exit(void)
-{
- led_trigger_unregister(&gpio_led_trigger);
-}
-module_exit(gpio_trig_exit);
+module_led_trigger(gpio_led_trigger);
MODULE_AUTHOR("Felipe Balbi <me@felipebalbi.com>");
MODULE_DESCRIPTION("GPIO LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c
index 5c8464a33172..40eb61b6d54e 100644
--- a/drivers/leds/trigger/ledtrig-heartbeat.c
+++ b/drivers/leds/trigger/ledtrig-heartbeat.c
@@ -1,3 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Heartbeat Trigger
*
@@ -5,18 +6,16 @@
*
* Based on Richard Purdie's ledtrig-timer.c and some arch's
* CONFIG_HEARTBEAT code.
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
+
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/panic_notifier.h>
#include <linux/slab.h>
#include <linux/timer.h>
#include <linux/sched.h>
+#include <linux/sched/loadavg.h>
#include <linux/leds.h>
#include <linux/reboot.h>
#include "../leds.h"
@@ -24,23 +23,31 @@
static int panic_heartbeats;
struct heartbeat_trig_data {
+ struct led_classdev *led_cdev;
unsigned int phase;
unsigned int period;
struct timer_list timer;
+ unsigned int invert;
};
-static void led_heartbeat_function(unsigned long data)
+static void led_heartbeat_function(struct timer_list *t)
{
- struct led_classdev *led_cdev = (struct led_classdev *) data;
- struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
+ struct heartbeat_trig_data *heartbeat_data =
+ timer_container_of(heartbeat_data, t, timer);
+ struct led_classdev *led_cdev;
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
+ led_cdev = heartbeat_data->led_cdev;
+
if (unlikely(panic_heartbeats)) {
- led_set_brightness(led_cdev, LED_OFF);
+ led_set_brightness_nosleep(led_cdev, LED_OFF);
return;
}
+ if (test_and_clear_bit(LED_BLINK_BRIGHTNESS_CHANGE, &led_cdev->work_flags))
+ led_cdev->blink_brightness = led_cdev->new_blink_brightness;
+
/* acts like an actual heart beat -- ie thump-thump-pause... */
switch (heartbeat_data->phase) {
case 0:
@@ -56,59 +63,104 @@ static void led_heartbeat_function(unsigned long data)
msecs_to_jiffies(heartbeat_data->period);
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
- brightness = led_cdev->max_brightness;
+ if (!heartbeat_data->invert)
+ brightness = led_cdev->blink_brightness;
break;
case 1:
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
heartbeat_data->phase++;
+ if (heartbeat_data->invert)
+ brightness = led_cdev->blink_brightness;
break;
case 2:
delay = msecs_to_jiffies(70);
heartbeat_data->phase++;
- brightness = led_cdev->max_brightness;
+ if (!heartbeat_data->invert)
+ brightness = led_cdev->blink_brightness;
break;
default:
delay = heartbeat_data->period - heartbeat_data->period / 4 -
msecs_to_jiffies(70);
heartbeat_data->phase = 0;
+ if (heartbeat_data->invert)
+ brightness = led_cdev->blink_brightness;
break;
}
- __led_set_brightness(led_cdev, brightness);
+ led_set_brightness_nosleep(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
-static void heartbeat_trig_activate(struct led_classdev *led_cdev)
+static ssize_t led_invert_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct heartbeat_trig_data *heartbeat_data =
+ led_trigger_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n", heartbeat_data->invert);
+}
+
+static ssize_t led_invert_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t size)
+{
+ struct heartbeat_trig_data *heartbeat_data =
+ led_trigger_get_drvdata(dev);
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ heartbeat_data->invert = !!state;
+
+ return size;
+}
+
+static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
+
+static struct attribute *heartbeat_trig_attrs[] = {
+ &dev_attr_invert.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(heartbeat_trig);
+
+static int heartbeat_trig_activate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data;
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
if (!heartbeat_data)
- return;
+ return -ENOMEM;
+
+ led_set_trigger_data(led_cdev, heartbeat_data);
+ heartbeat_data->led_cdev = led_cdev;
- led_cdev->trigger_data = heartbeat_data;
- setup_timer(&heartbeat_data->timer,
- led_heartbeat_function, (unsigned long) led_cdev);
+ timer_setup(&heartbeat_data->timer, led_heartbeat_function, 0);
heartbeat_data->phase = 0;
- led_heartbeat_function(heartbeat_data->timer.data);
- led_cdev->activated = true;
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+ led_heartbeat_function(&heartbeat_data->timer);
+ set_bit(LED_BLINK_SW, &led_cdev->work_flags);
+
+ return 0;
}
static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
{
- struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
+ struct heartbeat_trig_data *heartbeat_data =
+ led_get_trigger_data(led_cdev);
- if (led_cdev->activated) {
- del_timer_sync(&heartbeat_data->timer);
- kfree(heartbeat_data);
- led_cdev->activated = false;
- }
+ timer_shutdown_sync(&heartbeat_data->timer);
+ kfree(heartbeat_data);
+ clear_bit(LED_BLINK_SW, &led_cdev->work_flags);
}
static struct led_trigger heartbeat_led_trigger = {
.name = "heartbeat",
.activate = heartbeat_trig_activate,
.deactivate = heartbeat_trig_deactivate,
+ .groups = heartbeat_trig_groups,
};
static int heartbeat_reboot_notifier(struct notifier_block *nb,
@@ -158,4 +210,4 @@ module_exit(heartbeat_trig_exit);
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
MODULE_DESCRIPTION("Heartbeat LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-ide-disk.c b/drivers/leds/trigger/ledtrig-ide-disk.c
deleted file mode 100644
index 2cd7c0cf5924..000000000000
--- a/drivers/leds/trigger/ledtrig-ide-disk.c
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * LED IDE-Disk Activity Trigger
- *
- * Copyright 2006 Openedhand Ltd.
- *
- * Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
-
-#include <linux/module.h>
-#include <linux/kernel.h>
-#include <linux/init.h>
-#include <linux/leds.h>
-
-#define BLINK_DELAY 30
-
-DEFINE_LED_TRIGGER(ledtrig_ide);
-static unsigned long ide_blink_delay = BLINK_DELAY;
-
-void ledtrig_ide_activity(void)
-{
- led_trigger_blink_oneshot(ledtrig_ide,
- &ide_blink_delay, &ide_blink_delay, 0);
-}
-EXPORT_SYMBOL(ledtrig_ide_activity);
-
-static int __init ledtrig_ide_init(void)
-{
- led_trigger_register_simple("ide-disk", &ledtrig_ide);
- return 0;
-}
-
-static void __exit ledtrig_ide_exit(void)
-{
- led_trigger_unregister_simple(ledtrig_ide);
-}
-
-module_init(ledtrig_ide_init);
-module_exit(ledtrig_ide_exit);
-
-MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
-MODULE_DESCRIPTION("LED IDE Disk Activity Trigger");
-MODULE_LICENSE("GPL");
diff --git a/drivers/leds/trigger/ledtrig-input-events.c b/drivers/leds/trigger/ledtrig-input-events.c
new file mode 100644
index 000000000000..3c6414259c27
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-input-events.c
@@ -0,0 +1,165 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Input Events LED trigger
+ *
+ * Copyright (C) 2024 Hans de Goede <hansg@kernel.org>
+ */
+
+#include <linux/input.h>
+#include <linux/jiffies.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include "../leds.h"
+
+static unsigned long led_off_delay_ms = 5000;
+module_param(led_off_delay_ms, ulong, 0644);
+MODULE_PARM_DESC(led_off_delay_ms,
+ "Specify delay in ms for turning LEDs off after last input event");
+
+static struct input_events_data {
+ struct delayed_work work;
+ spinlock_t lock;
+ /* To avoid repeatedly setting the brightness while there are events */
+ bool led_on;
+ unsigned long led_off_time;
+} input_events_data;
+
+static struct led_trigger *input_events_led_trigger;
+
+static void led_input_events_work(struct work_struct *work)
+{
+ struct input_events_data *data =
+ container_of(work, struct input_events_data, work.work);
+
+ spin_lock_irq(&data->lock);
+
+ /*
+ * This time_after_eq() check avoids a race where this work starts
+ * running before a new event pushed led_off_time back.
+ */
+ if (time_after_eq(jiffies, data->led_off_time)) {
+ led_trigger_event(input_events_led_trigger, LED_OFF);
+ data->led_on = false;
+ }
+
+ spin_unlock_irq(&data->lock);
+}
+
+static void input_events_event(struct input_handle *handle, unsigned int type,
+ unsigned int code, int val)
+{
+ struct input_events_data *data = &input_events_data;
+ unsigned long led_off_delay = msecs_to_jiffies(led_off_delay_ms);
+ unsigned long flags;
+
+ spin_lock_irqsave(&data->lock, flags);
+
+ if (!data->led_on) {
+ led_trigger_event(input_events_led_trigger, LED_FULL);
+ data->led_on = true;
+ }
+ data->led_off_time = jiffies + led_off_delay;
+
+ spin_unlock_irqrestore(&data->lock, flags);
+
+ mod_delayed_work(system_percpu_wq, &data->work, led_off_delay);
+}
+
+static int input_events_connect(struct input_handler *handler, struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct input_handle *handle;
+ int ret;
+
+ handle = kzalloc(sizeof(*handle), GFP_KERNEL);
+ if (!handle)
+ return -ENOMEM;
+
+ handle->dev = dev;
+ handle->handler = handler;
+ handle->name = KBUILD_MODNAME;
+
+ ret = input_register_handle(handle);
+ if (ret)
+ goto err_free_handle;
+
+ ret = input_open_device(handle);
+ if (ret)
+ goto err_unregister_handle;
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(handle);
+err_free_handle:
+ kfree(handle);
+ return ret;
+}
+
+static void input_events_disconnect(struct input_handle *handle)
+{
+ input_close_device(handle);
+ input_unregister_handle(handle);
+ kfree(handle);
+}
+
+static const struct input_device_id input_events_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_KEY) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_REL) },
+ },
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT,
+ .evbit = { BIT_MASK(EV_ABS) },
+ },
+ { }
+};
+
+static struct input_handler input_events_handler = {
+ .name = KBUILD_MODNAME,
+ .event = input_events_event,
+ .connect = input_events_connect,
+ .disconnect = input_events_disconnect,
+ .id_table = input_events_ids,
+};
+
+static int __init input_events_init(void)
+{
+ int ret;
+
+ INIT_DELAYED_WORK(&input_events_data.work, led_input_events_work);
+ spin_lock_init(&input_events_data.lock);
+
+ led_trigger_register_simple("input-events", &input_events_led_trigger);
+
+ ret = input_register_handler(&input_events_handler);
+ if (ret) {
+ led_trigger_unregister_simple(input_events_led_trigger);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void __exit input_events_exit(void)
+{
+ input_unregister_handler(&input_events_handler);
+ cancel_delayed_work_sync(&input_events_data.work);
+ led_trigger_unregister_simple(input_events_led_trigger);
+}
+
+module_init(input_events_init);
+module_exit(input_events_exit);
+
+MODULE_AUTHOR("Hans de Goede <hansg@kernel.org>");
+MODULE_DESCRIPTION("Input Events LED trigger");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ledtrig:input-events");
diff --git a/drivers/leds/trigger/ledtrig-mtd.c b/drivers/leds/trigger/ledtrig-mtd.c
new file mode 100644
index 000000000000..bbe6876a249d
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-mtd.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * LED MTD trigger
+ *
+ * Copyright 2016 Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+ *
+ * Based on LED IDE-Disk Activity Trigger
+ *
+ * Copyright 2006 Openedhand Ltd.
+ *
+ * Author: Richard Purdie <rpurdie@openedhand.com>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+
+#define BLINK_DELAY 30
+
+DEFINE_LED_TRIGGER(ledtrig_mtd);
+DEFINE_LED_TRIGGER(ledtrig_nand);
+
+void ledtrig_mtd_activity(void)
+{
+ led_trigger_blink_oneshot(ledtrig_mtd, BLINK_DELAY, BLINK_DELAY, 0);
+ led_trigger_blink_oneshot(ledtrig_nand, BLINK_DELAY, BLINK_DELAY, 0);
+}
+EXPORT_SYMBOL(ledtrig_mtd_activity);
+
+static int __init ledtrig_mtd_init(void)
+{
+ led_trigger_register_simple("mtd", &ledtrig_mtd);
+ led_trigger_register_simple("nand-disk", &ledtrig_nand);
+
+ return 0;
+}
+device_initcall(ledtrig_mtd_init);
diff --git a/drivers/leds/trigger/ledtrig-netdev.c b/drivers/leds/trigger/ledtrig-netdev.c
new file mode 100644
index 000000000000..c15efe3e5078
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-netdev.c
@@ -0,0 +1,765 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright 2017 Ben Whitten <ben.whitten@gmail.com>
+// Copyright 2007 Oliver Jowett <oliver@opencloud.com>
+//
+// LED Kernel Netdev Trigger
+//
+// Toggles the LED to reflect the link and traffic state of a named net device
+//
+// Derived from ledtrig-timer.c which is:
+// Copyright 2005-2006 Openedhand Ltd.
+// Author: Richard Purdie <rpurdie@openedhand.com>
+
+#include <linux/atomic.h>
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/ethtool.h>
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/linkmode.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/netdevice.h>
+#include <linux/mutex.h>
+#include <linux/phy.h>
+#include <linux/rtnetlink.h>
+#include <linux/timer.h>
+#include "../leds.h"
+
+#define NETDEV_LED_DEFAULT_INTERVAL 50
+
+/*
+ * Configurable sysfs attributes:
+ *
+ * device_name - network device name to monitor
+ * interval - duration of LED blink, in milliseconds
+ * link - LED's normal state reflects whether the link is up
+ * (has carrier) or not
+ * tx - LED blinks on transmitted data
+ * rx - LED blinks on receive data
+ * tx_err - LED blinks on transmit error
+ * rx_err - LED blinks on receive error
+ *
+ * Note: If the user selects a mode that is not supported by hw, default
+ * behavior is to fall back to software control of the LED. However not every
+ * hw supports software control. LED callbacks brightness_set() and
+ * brightness_set_blocking() are NULL in this case. hw_control_is_supported()
+ * should use available means supported by hw to inform the user that selected
+ * mode isn't supported by hw. This could be switching off the LED or any
+ * hw blink mode. If software control fallback isn't possible, we return
+ * -EOPNOTSUPP to the user, but still store the selected mode. This is needed
+ * in case an intermediate unsupported mode is necessary to switch from one
+ * supported mode to another.
+ */
+
+struct led_netdev_data {
+ struct mutex lock;
+
+ struct delayed_work work;
+ struct notifier_block notifier;
+
+ struct led_classdev *led_cdev;
+ struct net_device *net_dev;
+
+ char device_name[IFNAMSIZ];
+ atomic_t interval;
+ unsigned int last_activity;
+
+ unsigned long mode;
+ int link_speed;
+ __ETHTOOL_DECLARE_LINK_MODE_MASK(supported_link_modes);
+ u8 duplex;
+
+ bool carrier_link_up;
+ bool hw_control;
+};
+
+static const struct attribute_group netdev_trig_link_speed_attrs_group;
+
+static void set_baseline_state(struct led_netdev_data *trigger_data)
+{
+ int current_brightness;
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+
+ /* 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);
+
+ return;
+ }
+
+ current_brightness = led_cdev->brightness;
+ if (current_brightness)
+ led_cdev->blink_brightness = current_brightness;
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+
+ if (!trigger_data->carrier_link_up) {
+ led_set_brightness(led_cdev, LED_OFF);
+ } else {
+ bool blink_on = false;
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode))
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_10)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_100)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_1000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_2500)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_5000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) &&
+ trigger_data->link_speed == SPEED_10000)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) &&
+ trigger_data->duplex == DUPLEX_HALF)
+ blink_on = true;
+
+ if (test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode) &&
+ trigger_data->duplex == DUPLEX_FULL)
+ blink_on = true;
+
+ if (blink_on)
+ led_set_brightness(led_cdev,
+ led_cdev->blink_brightness);
+ else
+ led_set_brightness(led_cdev, LED_OFF);
+
+ /* If we are looking for RX/TX start periodically
+ * checking stats
+ */
+ if (test_bit(TRIGGER_NETDEV_TX, &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);
+ }
+}
+
+static bool supports_hw_control(struct led_classdev *led_cdev)
+{
+ if (!led_cdev->hw_control_get || !led_cdev->hw_control_set ||
+ !led_cdev->hw_control_is_supported)
+ return false;
+
+ return !strcmp(led_cdev->hw_control_trigger, led_cdev->trigger->name);
+}
+
+/*
+ * Validate the configured netdev is the same as the one associated with
+ * the LED driver in hw control.
+ */
+static bool validate_net_dev(struct led_classdev *led_cdev,
+ struct net_device *net_dev)
+{
+ struct device *dev = led_cdev->hw_control_get_device(led_cdev);
+ struct net_device *ndev;
+
+ if (!dev)
+ return false;
+
+ ndev = to_net_dev(dev);
+
+ return ndev == net_dev;
+}
+
+static bool can_hw_control(struct led_netdev_data *trigger_data)
+{
+ unsigned long default_interval = msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL);
+ unsigned int interval = atomic_read(&trigger_data->interval);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+ int ret;
+
+ if (!supports_hw_control(led_cdev))
+ return false;
+
+ /*
+ * Interval must be set to the default
+ * value. Any different value is rejected if in hw
+ * control.
+ */
+ if (interval != default_interval)
+ return false;
+
+ /*
+ * net_dev must be set with hw control, otherwise no
+ * blinking can be happening and there is nothing to
+ * offloaded. Additionally, for hw control to be
+ * valid, the configured netdev must be the same as
+ * netdev associated to the LED.
+ */
+ if (!validate_net_dev(led_cdev, trigger_data->net_dev))
+ return false;
+
+ /* Check if the requested mode is supported */
+ ret = led_cdev->hw_control_is_supported(led_cdev, trigger_data->mode);
+ /* Fall back to software blinking if not supported */
+ if (ret == -EOPNOTSUPP)
+ return false;
+ if (ret) {
+ dev_warn(led_cdev->dev,
+ "Current mode check failed with error %d\n", ret);
+ return false;
+ }
+
+ return true;
+}
+
+static void get_device_state(struct led_netdev_data *trigger_data)
+{
+ struct ethtool_link_ksettings cmd;
+
+ trigger_data->carrier_link_up = netif_carrier_ok(trigger_data->net_dev);
+
+ if (__ethtool_get_link_ksettings(trigger_data->net_dev, &cmd))
+ return;
+
+ if (trigger_data->carrier_link_up) {
+ trigger_data->link_speed = cmd.base.speed;
+ trigger_data->duplex = cmd.base.duplex;
+ }
+
+ /*
+ * Have a local copy of the link speed supported to avoid rtnl lock every time
+ * modes are refreshed on any change event
+ */
+ linkmode_copy(trigger_data->supported_link_modes, cmd.link_modes.supported);
+}
+
+static ssize_t device_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ ssize_t len;
+
+ mutex_lock(&trigger_data->lock);
+ len = sprintf(buf, "%s\n", trigger_data->device_name);
+ mutex_unlock(&trigger_data->lock);
+
+ return len;
+}
+
+static int set_device_name(struct led_netdev_data *trigger_data,
+ const char *name, size_t size)
+{
+ if (size >= IFNAMSIZ)
+ return -EINVAL;
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ /*
+ * Take RTNL lock before trigger_data lock to prevent potential
+ * deadlock with netdev notifier registration.
+ */
+ rtnl_lock();
+ mutex_lock(&trigger_data->lock);
+
+ if (trigger_data->net_dev) {
+ dev_put(trigger_data->net_dev);
+ trigger_data->net_dev = NULL;
+ }
+
+ memcpy(trigger_data->device_name, name, size);
+ trigger_data->device_name[size] = 0;
+ if (size > 0 && trigger_data->device_name[size - 1] == '\n')
+ trigger_data->device_name[size - 1] = 0;
+
+ if (trigger_data->device_name[0] != 0)
+ trigger_data->net_dev =
+ dev_get_by_name(&init_net, trigger_data->device_name);
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
+ trigger_data->duplex = DUPLEX_UNKNOWN;
+ if (trigger_data->net_dev)
+ get_device_state(trigger_data);
+
+ trigger_data->last_activity = 0;
+
+ /* Skip if we're called from netdev_trig_activate() and hw_control is true */
+ if (!trigger_data->hw_control || led_get_trigger_data(trigger_data->led_cdev))
+ set_baseline_state(trigger_data);
+
+ mutex_unlock(&trigger_data->lock);
+ rtnl_unlock();
+
+ return 0;
+}
+
+static ssize_t device_name_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ int ret;
+
+ ret = set_device_name(trigger_data, buf, size);
+
+ if (ret < 0)
+ return ret;
+
+ /* Refresh link_speed visibility */
+ sysfs_update_group(&dev->kobj, &netdev_trig_link_speed_attrs_group);
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(device_name);
+
+static ssize_t netdev_led_attr_show(struct device *dev, char *buf,
+ enum led_trigger_netdev_modes attr)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ int bit;
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_LINK_2500:
+ case TRIGGER_NETDEV_LINK_5000:
+ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
+ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return sprintf(buf, "%u\n", test_bit(bit, &trigger_data->mode));
+}
+
+static ssize_t netdev_led_attr_store(struct device *dev, const char *buf,
+ size_t size, enum led_trigger_netdev_modes attr)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+ unsigned long state, mode = trigger_data->mode;
+ int ret;
+ int bit;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ switch (attr) {
+ case TRIGGER_NETDEV_LINK:
+ case TRIGGER_NETDEV_LINK_10:
+ case TRIGGER_NETDEV_LINK_100:
+ case TRIGGER_NETDEV_LINK_1000:
+ case TRIGGER_NETDEV_LINK_2500:
+ case TRIGGER_NETDEV_LINK_5000:
+ case TRIGGER_NETDEV_LINK_10000:
+ case TRIGGER_NETDEV_HALF_DUPLEX:
+ case TRIGGER_NETDEV_FULL_DUPLEX:
+ case TRIGGER_NETDEV_TX:
+ case TRIGGER_NETDEV_RX:
+ case TRIGGER_NETDEV_TX_ERR:
+ case TRIGGER_NETDEV_RX_ERR:
+ bit = attr;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (state)
+ set_bit(bit, &mode);
+ else
+ clear_bit(bit, &mode);
+
+ if (test_bit(TRIGGER_NETDEV_LINK, &mode) &&
+ (test_bit(TRIGGER_NETDEV_LINK_10, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_5000, &mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10000, &mode)))
+ return -EINVAL;
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ trigger_data->mode = mode;
+ trigger_data->hw_control = can_hw_control(trigger_data);
+
+ if (!led_cdev->brightness_set && !led_cdev->brightness_set_blocking &&
+ !trigger_data->hw_control)
+ return -EOPNOTSUPP;
+
+ set_baseline_state(trigger_data);
+
+ return size;
+}
+
+#define DEFINE_NETDEV_TRIGGER(trigger_name, trigger) \
+ static ssize_t trigger_name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return netdev_led_attr_show(dev, buf, trigger); \
+ } \
+ static ssize_t trigger_name##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t size) \
+ { \
+ return netdev_led_attr_store(dev, buf, size, trigger); \
+ } \
+ static DEVICE_ATTR_RW(trigger_name)
+
+DEFINE_NETDEV_TRIGGER(link, TRIGGER_NETDEV_LINK);
+DEFINE_NETDEV_TRIGGER(link_10, TRIGGER_NETDEV_LINK_10);
+DEFINE_NETDEV_TRIGGER(link_100, TRIGGER_NETDEV_LINK_100);
+DEFINE_NETDEV_TRIGGER(link_1000, TRIGGER_NETDEV_LINK_1000);
+DEFINE_NETDEV_TRIGGER(link_2500, TRIGGER_NETDEV_LINK_2500);
+DEFINE_NETDEV_TRIGGER(link_5000, TRIGGER_NETDEV_LINK_5000);
+DEFINE_NETDEV_TRIGGER(link_10000, TRIGGER_NETDEV_LINK_10000);
+DEFINE_NETDEV_TRIGGER(half_duplex, TRIGGER_NETDEV_HALF_DUPLEX);
+DEFINE_NETDEV_TRIGGER(full_duplex, TRIGGER_NETDEV_FULL_DUPLEX);
+DEFINE_NETDEV_TRIGGER(tx, TRIGGER_NETDEV_TX);
+DEFINE_NETDEV_TRIGGER(rx, TRIGGER_NETDEV_RX);
+DEFINE_NETDEV_TRIGGER(tx_err, TRIGGER_NETDEV_TX_ERR);
+DEFINE_NETDEV_TRIGGER(rx_err, TRIGGER_NETDEV_RX_ERR);
+
+static ssize_t interval_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+
+ return sprintf(buf, "%u\n",
+ jiffies_to_msecs(atomic_read(&trigger_data->interval)));
+}
+
+static ssize_t interval_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+ unsigned long value;
+ int ret;
+
+ if (trigger_data->hw_control)
+ return -EINVAL;
+
+ ret = kstrtoul(buf, 0, &value);
+ if (ret)
+ return ret;
+
+ /* impose some basic bounds on the timer interval */
+ if (value >= 5 && value <= 10000) {
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(value));
+ set_baseline_state(trigger_data); /* resets timer */
+ }
+
+ return size;
+}
+
+static DEVICE_ATTR_RW(interval);
+
+static ssize_t offloaded_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_netdev_data *trigger_data = led_trigger_get_drvdata(dev);
+
+ return sprintf(buf, "%d\n", trigger_data->hw_control);
+}
+
+static DEVICE_ATTR_RO(offloaded);
+
+#define CHECK_LINK_MODE_ATTR(link_speed) \
+ do { \
+ if (attr == &dev_attr_link_##link_speed.attr && \
+ link_ksettings.base.speed == SPEED_##link_speed) \
+ return attr->mode; \
+ } while (0)
+
+static umode_t netdev_trig_link_speed_visible(struct kobject *kobj,
+ struct attribute *attr, int n)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_netdev_data *trigger_data;
+ unsigned long *supported_link_modes;
+ u32 mode;
+
+ trigger_data = led_trigger_get_drvdata(dev);
+ supported_link_modes = trigger_data->supported_link_modes;
+
+ /*
+ * Search in the supported link mode mask a matching supported mode.
+ * Stop at the first matching entry as we care only to check if a particular
+ * speed is supported and not the kind.
+ */
+ for_each_set_bit(mode, supported_link_modes, __ETHTOOL_LINK_MODE_MASK_NBITS) {
+ struct ethtool_link_ksettings link_ksettings;
+
+ ethtool_params_from_link_mode(&link_ksettings, mode);
+
+ CHECK_LINK_MODE_ATTR(10);
+ CHECK_LINK_MODE_ATTR(100);
+ CHECK_LINK_MODE_ATTR(1000);
+ CHECK_LINK_MODE_ATTR(2500);
+ CHECK_LINK_MODE_ATTR(5000);
+ CHECK_LINK_MODE_ATTR(10000);
+ }
+
+ return 0;
+}
+
+static struct attribute *netdev_trig_link_speed_attrs[] = {
+ &dev_attr_link_10.attr,
+ &dev_attr_link_100.attr,
+ &dev_attr_link_1000.attr,
+ &dev_attr_link_2500.attr,
+ &dev_attr_link_5000.attr,
+ &dev_attr_link_10000.attr,
+ NULL
+};
+
+static const struct attribute_group netdev_trig_link_speed_attrs_group = {
+ .attrs = netdev_trig_link_speed_attrs,
+ .is_visible = netdev_trig_link_speed_visible,
+};
+
+static struct attribute *netdev_trig_attrs[] = {
+ &dev_attr_device_name.attr,
+ &dev_attr_link.attr,
+ &dev_attr_full_duplex.attr,
+ &dev_attr_half_duplex.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_rx_err.attr,
+ &dev_attr_tx_err.attr,
+ &dev_attr_interval.attr,
+ &dev_attr_offloaded.attr,
+ NULL
+};
+
+static const struct attribute_group netdev_trig_attrs_group = {
+ .attrs = netdev_trig_attrs,
+};
+
+static const struct attribute_group *netdev_trig_groups[] = {
+ &netdev_trig_attrs_group,
+ &netdev_trig_link_speed_attrs_group,
+ NULL,
+};
+
+static int netdev_trig_notify(struct notifier_block *nb,
+ unsigned long evt, void *dv)
+{
+ struct net_device *dev =
+ netdev_notifier_info_to_dev((struct netdev_notifier_info *)dv);
+ struct led_netdev_data *trigger_data =
+ container_of(nb, struct led_netdev_data, notifier);
+ struct led_classdev *led_cdev = trigger_data->led_cdev;
+
+ if (evt != NETDEV_UP && evt != NETDEV_DOWN && evt != NETDEV_CHANGE
+ && evt != NETDEV_REGISTER && evt != NETDEV_UNREGISTER
+ && evt != NETDEV_CHANGENAME)
+ return NOTIFY_DONE;
+
+ if (!(dev == trigger_data->net_dev ||
+ (evt == NETDEV_CHANGENAME && !strcmp(dev->name, trigger_data->device_name)) ||
+ (evt == NETDEV_REGISTER && !strcmp(dev->name, trigger_data->device_name))))
+ return NOTIFY_DONE;
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ mutex_lock(&trigger_data->lock);
+
+ trigger_data->carrier_link_up = false;
+ trigger_data->link_speed = SPEED_UNKNOWN;
+ trigger_data->duplex = DUPLEX_UNKNOWN;
+ switch (evt) {
+ case NETDEV_CHANGENAME:
+ case NETDEV_REGISTER:
+ dev_put(trigger_data->net_dev);
+ dev_hold(dev);
+ trigger_data->net_dev = dev;
+ if (evt == NETDEV_CHANGENAME)
+ get_device_state(trigger_data);
+ break;
+ case NETDEV_UNREGISTER:
+ dev_put(trigger_data->net_dev);
+ trigger_data->net_dev = NULL;
+ break;
+ case NETDEV_UP:
+ trigger_data->hw_control = can_hw_control(trigger_data);
+ fallthrough;
+ case NETDEV_CHANGE:
+ get_device_state(trigger_data);
+ /* Refresh link_speed visibility */
+ if (evt == NETDEV_CHANGE)
+ sysfs_update_group(&led_cdev->dev->kobj,
+ &netdev_trig_link_speed_attrs_group);
+ break;
+ }
+
+ set_baseline_state(trigger_data);
+
+ mutex_unlock(&trigger_data->lock);
+
+ return NOTIFY_DONE;
+}
+
+/* here's the real work! */
+static void netdev_trig_work(struct work_struct *work)
+{
+ struct led_netdev_data *trigger_data =
+ container_of(work, struct led_netdev_data, work.work);
+ struct rtnl_link_stats64 *dev_stats;
+ unsigned int new_activity;
+ struct rtnl_link_stats64 temp;
+ unsigned long interval;
+ int invert;
+
+ /* If we dont have a device, insure we are off */
+ if (!trigger_data->net_dev) {
+ led_set_brightness(trigger_data->led_cdev, LED_OFF);
+ return;
+ }
+
+ /* 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_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);
+ new_activity =
+ (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) +
+ (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);
+
+ invert = test_bit(TRIGGER_NETDEV_LINK, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_100, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_1000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_2500, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_5000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_LINK_10000, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_HALF_DUPLEX, &trigger_data->mode) ||
+ test_bit(TRIGGER_NETDEV_FULL_DUPLEX, &trigger_data->mode);
+ interval = jiffies_to_msecs(
+ atomic_read(&trigger_data->interval));
+ /* base state is ON (link present) */
+ led_blink_set_oneshot(trigger_data->led_cdev,
+ &interval,
+ &interval,
+ invert);
+ trigger_data->last_activity = new_activity;
+ }
+
+ schedule_delayed_work(&trigger_data->work,
+ (atomic_read(&trigger_data->interval)*2));
+}
+
+static int netdev_trig_activate(struct led_classdev *led_cdev)
+{
+ struct led_netdev_data *trigger_data;
+ unsigned long mode = 0;
+ struct device *dev;
+ int rc;
+
+ trigger_data = kzalloc(sizeof(struct led_netdev_data), GFP_KERNEL);
+ if (!trigger_data)
+ return -ENOMEM;
+
+ mutex_init(&trigger_data->lock);
+
+ trigger_data->notifier.notifier_call = netdev_trig_notify;
+ trigger_data->notifier.priority = 10;
+
+ INIT_DELAYED_WORK(&trigger_data->work, netdev_trig_work);
+
+ trigger_data->led_cdev = led_cdev;
+ trigger_data->net_dev = NULL;
+ trigger_data->device_name[0] = 0;
+
+ trigger_data->mode = 0;
+ atomic_set(&trigger_data->interval, msecs_to_jiffies(NETDEV_LED_DEFAULT_INTERVAL));
+ trigger_data->last_activity = 0;
+
+ /* Check if hw control is active by default on the LED.
+ * Init already enabled mode in hw control.
+ */
+ if (supports_hw_control(led_cdev)) {
+ dev = led_cdev->hw_control_get_device(led_cdev);
+ if (dev) {
+ const char *name = dev_name(dev);
+
+ trigger_data->hw_control = true;
+ set_device_name(trigger_data, name, strlen(name));
+
+ rc = led_cdev->hw_control_get(led_cdev, &mode);
+ if (!rc)
+ trigger_data->mode = mode;
+ }
+ }
+
+ led_set_trigger_data(led_cdev, trigger_data);
+
+ rc = register_netdevice_notifier(&trigger_data->notifier);
+ if (rc)
+ kfree(trigger_data);
+
+ return rc;
+}
+
+static void netdev_trig_deactivate(struct led_classdev *led_cdev)
+{
+ struct led_netdev_data *trigger_data = led_get_trigger_data(led_cdev);
+
+ unregister_netdevice_notifier(&trigger_data->notifier);
+
+ cancel_delayed_work_sync(&trigger_data->work);
+
+ dev_put(trigger_data->net_dev);
+
+ kfree(trigger_data);
+}
+
+static struct led_trigger netdev_led_trigger = {
+ .name = "netdev",
+ .activate = netdev_trig_activate,
+ .deactivate = netdev_trig_deactivate,
+ .groups = netdev_trig_groups,
+};
+
+module_led_trigger(netdev_led_trigger);
+
+MODULE_AUTHOR("Ben Whitten <ben.whitten@gmail.com>");
+MODULE_AUTHOR("Oliver Jowett <oliver@opencloud.com>");
+MODULE_DESCRIPTION("Netdev LED trigger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("ledtrig:netdev");
diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c
index cb4c7466692a..bee3bd452abf 100644
--- a/drivers/leds/trigger/ledtrig-oneshot.c
+++ b/drivers/leds/trigger/ledtrig-oneshot.c
@@ -1,14 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* One-shot LED Trigger
*
* Copyright 2012, Fabio Baltieri <fabio.baltieri@gmail.com>
*
* Based on ledtrig-timer.c by Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
@@ -29,8 +25,8 @@ struct oneshot_trig_data {
static ssize_t led_shot(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
+ struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev);
led_blink_set_oneshot(led_cdev,
&led_cdev->blink_delay_on, &led_cdev->blink_delay_off,
@@ -42,8 +38,7 @@ static ssize_t led_shot(struct device *dev,
static ssize_t led_invert_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;
+ struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%u\n", oneshot_data->invert);
}
@@ -51,8 +46,8 @@ static ssize_t led_invert_show(struct device *dev,
static ssize_t led_invert_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
+ struct oneshot_trig_data *oneshot_data = led_trigger_get_drvdata(dev);
unsigned long state;
int ret;
@@ -63,9 +58,9 @@ static ssize_t led_invert_store(struct device *dev,
oneshot_data->invert = !!state;
if (oneshot_data->invert)
- __led_set_brightness(led_cdev, LED_FULL);
+ led_set_brightness_nosleep(led_cdev, LED_FULL);
else
- __led_set_brightness(led_cdev, LED_OFF);
+ led_set_brightness_nosleep(led_cdev, LED_OFF);
return size;
}
@@ -73,7 +68,7 @@ static ssize_t led_invert_store(struct device *dev,
static ssize_t led_delay_on_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
return sprintf(buf, "%lu\n", led_cdev->blink_delay_on);
}
@@ -81,7 +76,7 @@ static ssize_t led_delay_on_show(struct device *dev,
static ssize_t led_delay_on_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
unsigned long state;
int ret;
@@ -93,10 +88,11 @@ static ssize_t led_delay_on_store(struct device *dev,
return size;
}
+
static ssize_t led_delay_off_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
return sprintf(buf, "%lu\n", led_cdev->blink_delay_off);
}
@@ -104,7 +100,7 @@ static ssize_t led_delay_off_show(struct device *dev,
static ssize_t led_delay_off_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
unsigned long state;
int ret;
@@ -122,59 +118,70 @@ static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store);
static DEVICE_ATTR(shot, 0200, NULL, led_shot);
-static void oneshot_trig_activate(struct led_classdev *led_cdev)
+static struct attribute *oneshot_trig_attrs[] = {
+ &dev_attr_delay_on.attr,
+ &dev_attr_delay_off.attr,
+ &dev_attr_invert.attr,
+ &dev_attr_shot.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(oneshot_trig);
+
+static void pattern_init(struct led_classdev *led_cdev)
{
- struct oneshot_trig_data *oneshot_data;
- int rc;
+ u32 *pattern;
+ unsigned int size = 0;
+
+ pattern = led_get_default_pattern(led_cdev, &size);
+ if (!pattern)
+ goto out_default;
+
+ if (size != 2) {
+ dev_warn(led_cdev->dev,
+ "Expected 2 but got %u values for delays pattern\n",
+ size);
+ goto out_default;
+ }
- oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL);
- if (!oneshot_data)
- return;
-
- led_cdev->trigger_data = oneshot_data;
-
- rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
- if (rc)
- goto err_out_trig_data;
- rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
- if (rc)
- goto err_out_delayon;
- rc = device_create_file(led_cdev->dev, &dev_attr_invert);
- if (rc)
- goto err_out_delayoff;
- rc = device_create_file(led_cdev->dev, &dev_attr_shot);
- if (rc)
- goto err_out_invert;
+ led_cdev->blink_delay_on = pattern[0];
+ led_cdev->blink_delay_off = pattern[1];
+ kfree(pattern);
+
+ return;
+out_default:
+ kfree(pattern);
led_cdev->blink_delay_on = DEFAULT_DELAY;
led_cdev->blink_delay_off = DEFAULT_DELAY;
+}
- led_cdev->activated = true;
+static int oneshot_trig_activate(struct led_classdev *led_cdev)
+{
+ struct oneshot_trig_data *oneshot_data;
- return;
+ oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL);
+ if (!oneshot_data)
+ return -ENOMEM;
-err_out_invert:
- device_remove_file(led_cdev->dev, &dev_attr_invert);
-err_out_delayoff:
- device_remove_file(led_cdev->dev, &dev_attr_delay_off);
-err_out_delayon:
- device_remove_file(led_cdev->dev, &dev_attr_delay_on);
-err_out_trig_data:
- kfree(led_cdev->trigger_data);
+ led_set_trigger_data(led_cdev, oneshot_data);
+
+ if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
+ pattern_init(led_cdev);
+ /*
+ * Mark as initialized even on pattern_init() error because
+ * any consecutive call to it would produce the same error.
+ */
+ led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
+ }
+
+ return 0;
}
static void oneshot_trig_deactivate(struct led_classdev *led_cdev)
{
- struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data;
-
- if (led_cdev->activated) {
- device_remove_file(led_cdev->dev, &dev_attr_delay_on);
- device_remove_file(led_cdev->dev, &dev_attr_delay_off);
- device_remove_file(led_cdev->dev, &dev_attr_invert);
- device_remove_file(led_cdev->dev, &dev_attr_shot);
- kfree(oneshot_data);
- led_cdev->activated = false;
- }
+ struct oneshot_trig_data *oneshot_data = led_get_trigger_data(led_cdev);
+
+ kfree(oneshot_data);
/* Stop blinking */
led_set_brightness(led_cdev, LED_OFF);
@@ -184,21 +191,10 @@ static struct led_trigger oneshot_led_trigger = {
.name = "oneshot",
.activate = oneshot_trig_activate,
.deactivate = oneshot_trig_deactivate,
+ .groups = oneshot_trig_groups,
};
-
-static int __init oneshot_trig_init(void)
-{
- return led_trigger_register(&oneshot_led_trigger);
-}
-
-static void __exit oneshot_trig_exit(void)
-{
- led_trigger_unregister(&oneshot_led_trigger);
-}
-
-module_init(oneshot_trig_init);
-module_exit(oneshot_trig_exit);
+module_led_trigger(oneshot_led_trigger);
MODULE_AUTHOR("Fabio Baltieri <fabio.baltieri@gmail.com>");
MODULE_DESCRIPTION("One-shot LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-panic.c b/drivers/leds/trigger/ledtrig-panic.c
new file mode 100644
index 000000000000..1d49c1078091
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-panic.c
@@ -0,0 +1,68 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Kernel Panic LED Trigger
+ *
+ * Copyright 2016 Ezequiel Garcia <ezequiel@vanguardiasur.com.ar>
+ */
+
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/notifier.h>
+#include <linux/panic_notifier.h>
+#include <linux/leds.h>
+#include "../leds.h"
+
+static struct led_trigger *trigger;
+
+/*
+ * This is called in a special context by the atomic panic
+ * notifier. This means the trigger can be changed without
+ * worrying about locking.
+ */
+static void led_trigger_set_panic(struct led_classdev *led_cdev)
+{
+ if (led_cdev->trigger)
+ list_del(&led_cdev->trig_list);
+ list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
+
+ /* Avoid the delayed blink path */
+ led_cdev->blink_delay_on = 0;
+ led_cdev->blink_delay_off = 0;
+
+ led_cdev->trigger = trigger;
+}
+
+static int led_trigger_panic_notifier(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ struct led_classdev *led_cdev;
+
+ list_for_each_entry(led_cdev, &leds_list, node)
+ if (led_cdev->flags & LED_PANIC_INDICATOR)
+ led_trigger_set_panic(led_cdev);
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block led_trigger_panic_nb = {
+ .notifier_call = led_trigger_panic_notifier,
+};
+
+static long led_panic_blink(int state)
+{
+ led_trigger_event(trigger, state ? LED_FULL : LED_OFF);
+ return 0;
+}
+
+static int __init ledtrig_panic_init(void)
+{
+ led_trigger_register_simple("panic", &trigger);
+ if (!trigger)
+ return -ENOMEM;
+
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &led_trigger_panic_nb);
+
+ panic_blink = led_panic_blink;
+ return 0;
+}
+device_initcall(ledtrig_panic_init);
diff --git a/drivers/leds/trigger/ledtrig-pattern.c b/drivers/leds/trigger/ledtrig-pattern.c
new file mode 100644
index 000000000000..9af3c18f14f4
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-pattern.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0
+
+/*
+ * LED pattern trigger
+ *
+ * Idea discussed with Pavel Machek. Raphael Teysseyre implemented
+ * the first version, Baolin Wang simplified and improved the approach.
+ */
+
+#include <linux/kernel.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/hrtimer.h>
+
+#define MAX_PATTERNS 1024
+/*
+ * When doing gradual dimming, the led brightness will be updated
+ * every 50 milliseconds.
+ */
+#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];
+ struct led_pattern *curr;
+ struct led_pattern *next;
+ struct mutex lock;
+ u32 npatterns;
+ int repeat;
+ int last_repeat;
+ int delta_t;
+ bool is_indefinite;
+ enum pattern_type type;
+ struct timer_list timer;
+ struct hrtimer hrtimer;
+};
+
+static void pattern_trig_update_patterns(struct pattern_trig_data *data)
+{
+ data->curr = data->next;
+ if (!data->is_indefinite && data->curr == data->patterns)
+ data->repeat--;
+
+ if (data->next == data->patterns + data->npatterns - 1)
+ data->next = data->patterns;
+ else
+ data->next++;
+
+ data->delta_t = 0;
+}
+
+static int pattern_trig_compute_brightness(struct pattern_trig_data *data)
+{
+ int step_brightness;
+
+ /*
+ * If current tuple's duration is less than the dimming interval,
+ * we should treat it as a step change of brightness instead of
+ * doing gradual dimming.
+ */
+ if (data->delta_t == 0 || data->curr->delta_t < UPDATE_INTERVAL)
+ return data->curr->brightness;
+
+ step_brightness = abs(data->next->brightness - data->curr->brightness);
+ step_brightness = data->delta_t * step_brightness / data->curr->delta_t;
+
+ if (data->next->brightness > data->curr->brightness)
+ return data->curr->brightness + step_brightness;
+ else
+ return data->curr->brightness - step_brightness;
+}
+
+static void pattern_trig_timer_start(struct pattern_trig_data *data)
+{
+ if (data->type == PATTERN_TYPE_HR) {
+ hrtimer_start(&data->hrtimer, ns_to_ktime(0), HRTIMER_MODE_REL);
+ } else {
+ data->timer.expires = jiffies;
+ add_timer(&data->timer);
+ }
+}
+
+static void pattern_trig_timer_cancel(struct pattern_trig_data *data)
+{
+ if (data->type == PATTERN_TYPE_HR)
+ hrtimer_cancel(&data->hrtimer);
+ else
+ timer_delete_sync(&data->timer);
+}
+
+static void pattern_trig_timer_restart(struct pattern_trig_data *data,
+ unsigned long interval)
+{
+ 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;
+
+ if (data->curr->brightness == data->next->brightness) {
+ /* Step change of brightness */
+ led_set_brightness(data->led_cdev,
+ data->curr->brightness);
+ 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);
+ }
+ /* Select next tuple */
+ pattern_trig_update_patterns(data);
+ } else {
+ /* Gradual dimming */
+
+ /*
+ * If the accumulation time is larger than current
+ * tuple's duration, we should go next one and re-check
+ * if we repeated done.
+ */
+ if (data->delta_t > data->curr->delta_t) {
+ pattern_trig_update_patterns(data);
+ continue;
+ }
+
+ led_set_brightness(data->led_cdev,
+ pattern_trig_compute_brightness(data));
+ pattern_trig_timer_restart(data, UPDATE_INTERVAL);
+
+ /* Accumulate the gradual dimming time */
+ data->delta_t += UPDATE_INTERVAL;
+ }
+
+ break;
+ }
+}
+
+static void pattern_trig_timer_function(struct timer_list *t)
+{
+ struct pattern_trig_data *data = timer_container_of(data, t, timer);
+
+ return pattern_trig_timer_common_function(data);
+}
+
+static enum hrtimer_restart pattern_trig_hrtimer_function(struct hrtimer *t)
+{
+ struct pattern_trig_data *data =
+ container_of(t, struct pattern_trig_data, hrtimer);
+
+ pattern_trig_timer_common_function(data);
+ if (!data->is_indefinite && !data->repeat)
+ return HRTIMER_NORESTART;
+
+ return HRTIMER_RESTART;
+}
+
+static int pattern_trig_start_pattern(struct led_classdev *led_cdev)
+{
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+
+ if (!data->npatterns)
+ return 0;
+
+ if (data->type == PATTERN_TYPE_HW) {
+ return led_cdev->pattern_set(led_cdev, data->patterns,
+ data->npatterns, data->repeat);
+ }
+
+ /* At least 2 tuples for software pattern. */
+ if (data->npatterns < 2)
+ return -EINVAL;
+
+ data->delta_t = 0;
+ data->curr = data->patterns;
+ data->next = data->patterns + 1;
+ pattern_trig_timer_start(data);
+
+ return 0;
+}
+
+static ssize_t repeat_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;
+ int repeat;
+
+ mutex_lock(&data->lock);
+
+ repeat = data->last_repeat;
+
+ mutex_unlock(&data->lock);
+
+ return sysfs_emit(buf, "%d\n", repeat);
+}
+
+static ssize_t repeat_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+ int err, res;
+
+ err = kstrtos32(buf, 10, &res);
+ if (err)
+ return err;
+
+ /* Number 0 and negative numbers except -1 are invalid. */
+ if (res < -1 || res == 0)
+ return -EINVAL;
+
+ mutex_lock(&data->lock);
+
+ pattern_trig_timer_cancel(data);
+
+ if (data->type == PATTERN_TYPE_HW)
+ led_cdev->pattern_clear(led_cdev);
+
+ data->last_repeat = data->repeat = res;
+ /* -1 means repeat indefinitely */
+ if (data->repeat == -1)
+ data->is_indefinite = true;
+ else
+ data->is_indefinite = false;
+
+ err = pattern_trig_start_pattern(led_cdev);
+
+ mutex_unlock(&data->lock);
+ return err < 0 ? err : count;
+}
+
+static DEVICE_ATTR_RW(repeat);
+
+static ssize_t pattern_trig_show_patterns(struct pattern_trig_data *data,
+ char *buf, enum pattern_type type)
+{
+ ssize_t count = 0;
+ int i;
+
+ mutex_lock(&data->lock);
+
+ if (!data->npatterns || data->type != type)
+ goto out;
+
+ for (i = 0; i < data->npatterns; i++) {
+ count += scnprintf(buf + count, PAGE_SIZE - count,
+ "%d %u ",
+ data->patterns[i].brightness,
+ data->patterns[i].delta_t);
+ }
+
+ buf[count - 1] = '\n';
+
+out:
+ mutex_unlock(&data->lock);
+ return count;
+}
+
+static int pattern_trig_store_patterns_string(struct pattern_trig_data *data,
+ const char *buf, size_t count)
+{
+ int ccount, cr, offset = 0;
+
+ while (offset < count - 1 && data->npatterns < MAX_PATTERNS) {
+ cr = 0;
+ ccount = sscanf(buf + offset, "%u %u %n",
+ &data->patterns[data->npatterns].brightness,
+ &data->patterns[data->npatterns].delta_t, &cr);
+
+ if (ccount != 2 ||
+ data->patterns[data->npatterns].brightness > data->led_cdev->max_brightness) {
+ data->npatterns = 0;
+ return -EINVAL;
+ }
+
+ offset += cr;
+ data->npatterns++;
+ }
+
+ return 0;
+}
+
+static int pattern_trig_store_patterns_int(struct pattern_trig_data *data,
+ const u32 *buf, size_t count)
+{
+ unsigned int i;
+
+ for (i = 0; i < count; i += 2) {
+ data->patterns[data->npatterns].brightness = buf[i];
+ data->patterns[data->npatterns].delta_t = buf[i + 1];
+ data->npatterns++;
+ }
+
+ return 0;
+}
+
+static ssize_t pattern_trig_store_patterns(struct led_classdev *led_cdev,
+ const char *buf, const u32 *buf_int,
+ size_t count, enum pattern_type type)
+{
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+ int err = 0;
+
+ mutex_lock(&data->lock);
+
+ pattern_trig_timer_cancel(data);
+
+ if (data->type == PATTERN_TYPE_HW)
+ led_cdev->pattern_clear(led_cdev);
+
+ data->type = type;
+ data->npatterns = 0;
+
+ if (buf)
+ err = pattern_trig_store_patterns_string(data, buf, count);
+ else
+ err = pattern_trig_store_patterns_int(data, buf_int, count);
+ if (err)
+ goto out;
+
+ err = pattern_trig_start_pattern(led_cdev);
+ if (err)
+ data->npatterns = 0;
+
+out:
+ mutex_unlock(&data->lock);
+ return err < 0 ? err : count;
+}
+
+static ssize_t 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_SW);
+}
+
+static ssize_t 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_SW);
+}
+
+static DEVICE_ATTR_RW(pattern);
+
+static ssize_t hw_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_HW);
+}
+
+static ssize_t hw_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_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)
+{
+ struct device *dev = kobj_to_dev(kobj);
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+
+ 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;
+
+ return 0;
+}
+
+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
+};
+
+static const struct attribute_group pattern_trig_group = {
+ .attrs = pattern_trig_attrs,
+ .is_visible = pattern_trig_attrs_mode,
+};
+
+static const struct attribute_group *pattern_trig_groups[] = {
+ &pattern_trig_group,
+ NULL,
+};
+
+static void pattern_init(struct led_classdev *led_cdev)
+{
+ unsigned int size = 0;
+ u32 *pattern;
+ int err;
+
+ pattern = led_get_default_pattern(led_cdev, &size);
+ if (!pattern)
+ return;
+
+ if (size % 2) {
+ dev_warn(led_cdev->dev, "Expected pattern of tuples\n");
+ goto out;
+ }
+
+ 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);
+
+out:
+ kfree(pattern);
+}
+
+static int pattern_trig_activate(struct led_classdev *led_cdev)
+{
+ struct pattern_trig_data *data;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (!!led_cdev->pattern_set ^ !!led_cdev->pattern_clear) {
+ dev_warn(led_cdev->dev,
+ "Hardware pattern ops validation failed\n");
+ led_cdev->pattern_set = NULL;
+ 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) {
+ pattern_init(led_cdev);
+ /*
+ * Mark as initialized even on pattern_init() error because
+ * any consecutive call to it would produce the same error.
+ */
+ led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
+ }
+
+ return 0;
+}
+
+static void pattern_trig_deactivate(struct led_classdev *led_cdev)
+{
+ struct pattern_trig_data *data = led_cdev->trigger_data;
+
+ if (!led_cdev->activated)
+ return;
+
+ if (led_cdev->pattern_clear)
+ led_cdev->pattern_clear(led_cdev);
+
+ timer_shutdown_sync(&data->timer);
+ hrtimer_cancel(&data->hrtimer);
+
+ led_set_brightness(led_cdev, LED_OFF);
+ kfree(data);
+ led_cdev->activated = false;
+}
+
+static struct led_trigger pattern_led_trigger = {
+ .name = "pattern",
+ .activate = pattern_trig_activate,
+ .deactivate = pattern_trig_deactivate,
+ .groups = pattern_trig_groups,
+};
+
+static int __init pattern_trig_init(void)
+{
+ return led_trigger_register(&pattern_led_trigger);
+}
+
+static void __exit pattern_trig_exit(void)
+{
+ led_trigger_unregister(&pattern_led_trigger);
+}
+
+module_init(pattern_trig_init);
+module_exit(pattern_trig_exit);
+
+MODULE_AUTHOR("Raphael Teysseyre <rteysseyre@gmail.com>");
+MODULE_AUTHOR("Baolin Wang <baolin.wang@linaro.org>");
+MODULE_DESCRIPTION("LED Pattern trigger");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c
index 8d09327b5719..1d213c999d40 100644
--- a/drivers/leds/trigger/ledtrig-timer.c
+++ b/drivers/leds/trigger/ledtrig-timer.c
@@ -1,14 +1,10 @@
+// SPDX-License-Identifier: GPL-2.0-only
/*
* LED Kernel Timer Trigger
*
* Copyright 2005-2006 Openedhand Ltd.
*
* Author: Richard Purdie <rpurdie@openedhand.com>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
*/
#include <linux/module.h>
@@ -16,12 +12,13 @@
#include <linux/init.h>
#include <linux/device.h>
#include <linux/ctype.h>
+#include <linux/slab.h>
#include <linux/leds.h>
static ssize_t led_delay_on_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
return sprintf(buf, "%lu\n", led_cdev->blink_delay_on);
}
@@ -29,9 +26,9 @@ static ssize_t led_delay_on_show(struct device *dev,
static ssize_t led_delay_on_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
unsigned long state;
- ssize_t ret = -EINVAL;
+ ssize_t ret;
ret = kstrtoul(buf, 10, &state);
if (ret)
@@ -46,7 +43,7 @@ static ssize_t led_delay_on_store(struct device *dev,
static ssize_t led_delay_off_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
return sprintf(buf, "%lu\n", led_cdev->blink_delay_off);
}
@@ -54,9 +51,9 @@ static ssize_t led_delay_off_show(struct device *dev,
static ssize_t led_delay_off_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
unsigned long state;
- ssize_t ret = -EINVAL;
+ ssize_t ret;
ret = kstrtoul(buf, 10, &state);
if (ret)
@@ -71,37 +68,56 @@ static ssize_t led_delay_off_store(struct device *dev,
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);
static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store);
-static void timer_trig_activate(struct led_classdev *led_cdev)
-{
- int rc;
+static struct attribute *timer_trig_attrs[] = {
+ &dev_attr_delay_on.attr,
+ &dev_attr_delay_off.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(timer_trig);
- led_cdev->trigger_data = NULL;
+static void pattern_init(struct led_classdev *led_cdev)
+{
+ u32 *pattern;
+ unsigned int size = 0;
- rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
- if (rc)
+ pattern = led_get_default_pattern(led_cdev, &size);
+ if (!pattern)
return;
- rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
- if (rc)
- goto err_out_delayon;
- led_blink_set(led_cdev, &led_cdev->blink_delay_on,
- &led_cdev->blink_delay_off);
- led_cdev->activated = true;
+ if (size != 2) {
+ dev_warn(led_cdev->dev,
+ "Expected 2 but got %u values for delays pattern\n",
+ size);
+ goto out;
+ }
- return;
+ led_cdev->blink_delay_on = pattern[0];
+ led_cdev->blink_delay_off = pattern[1];
+ /* led_blink_set() called by caller */
-err_out_delayon:
- device_remove_file(led_cdev->dev, &dev_attr_delay_on);
+out:
+ kfree(pattern);
}
-static void timer_trig_deactivate(struct led_classdev *led_cdev)
+static int timer_trig_activate(struct led_classdev *led_cdev)
{
- if (led_cdev->activated) {
- device_remove_file(led_cdev->dev, &dev_attr_delay_on);
- device_remove_file(led_cdev->dev, &dev_attr_delay_off);
- led_cdev->activated = false;
+ if (led_cdev->flags & LED_INIT_DEFAULT_TRIGGER) {
+ pattern_init(led_cdev);
+ /*
+ * Mark as initialized even on pattern_init() error because
+ * any consecutive call to it would produce the same error.
+ */
+ led_cdev->flags &= ~LED_INIT_DEFAULT_TRIGGER;
}
+ led_blink_set(led_cdev, &led_cdev->blink_delay_on,
+ &led_cdev->blink_delay_off);
+
+ return 0;
+}
+
+static void timer_trig_deactivate(struct led_classdev *led_cdev)
+{
/* Stop blinking */
led_set_brightness(led_cdev, LED_OFF);
}
@@ -110,21 +126,10 @@ static struct led_trigger timer_led_trigger = {
.name = "timer",
.activate = timer_trig_activate,
.deactivate = timer_trig_deactivate,
+ .groups = timer_trig_groups,
};
-
-static int __init timer_trig_init(void)
-{
- return led_trigger_register(&timer_led_trigger);
-}
-
-static void __exit timer_trig_exit(void)
-{
- led_trigger_unregister(&timer_led_trigger);
-}
-
-module_init(timer_trig_init);
-module_exit(timer_trig_exit);
+module_led_trigger(timer_led_trigger);
MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
MODULE_DESCRIPTION("Timer LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c
index e5abc00bb00c..20f1351464b1 100644
--- a/drivers/leds/trigger/ledtrig-transient.c
+++ b/drivers/leds/trigger/ledtrig-transient.c
@@ -1,22 +1,15 @@
-/*
- * LED Kernel Transient Trigger
- *
- * Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com>
- *
- * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's
- * ledtrig-heartbeat.c
- * Design and use-case input from Jonas Bonn <jonas@southpole.se> and
- * Neil Brown <neilb@suse.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 as
- * published by the Free Software Foundation.
- *
- */
-/*
- * Transient trigger allows one shot timer activation. Please refer to
- * Documentation/leds/ledtrig-transient.txt for details
-*/
+// SPDX-License-Identifier: GPL-2.0
+//
+// LED Kernel Transient Trigger
+//
+// Transient trigger allows one shot timer activation. Please refer to
+// Documentation/leds/ledtrig-transient.rst for details
+// Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com>
+//
+// Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's
+// ledtrig-heartbeat.c
+// Design and use-case input from Jonas Bonn <jonas@southpole.se> and
+// Neil Brown <neilb@suse.de>
#include <linux/module.h>
#include <linux/kernel.h>
@@ -33,22 +26,24 @@ struct transient_trig_data {
int restore_state;
unsigned long duration;
struct timer_list timer;
+ struct led_classdev *led_cdev;
};
-static void transient_timer_function(unsigned long data)
+static void transient_timer_function(struct timer_list *t)
{
- struct led_classdev *led_cdev = (struct led_classdev *) data;
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data =
+ timer_container_of(transient_data, t, timer);
+ struct led_classdev *led_cdev = transient_data->led_cdev;
transient_data->activate = 0;
- __led_set_brightness(led_cdev, transient_data->restore_state);
+ led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
}
static ssize_t transient_activate_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data =
+ led_trigger_get_drvdata(dev);
return sprintf(buf, "%d\n", transient_data->activate);
}
@@ -56,8 +51,9 @@ static ssize_t transient_activate_show(struct device *dev,
static ssize_t transient_activate_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct led_classdev *led_cdev = led_trigger_get_led(dev);
+ struct transient_trig_data *transient_data =
+ led_trigger_get_drvdata(dev);
unsigned long state;
ssize_t ret;
@@ -70,9 +66,10 @@ 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(led_cdev, transient_data->restore_state);
+ led_set_brightness_nosleep(led_cdev,
+ transient_data->restore_state);
return size;
}
@@ -80,11 +77,11 @@ static ssize_t transient_activate_store(struct device *dev,
if (state == 1 && transient_data->activate == 0 &&
transient_data->duration != 0) {
transient_data->activate = state;
- __led_set_brightness(led_cdev, transient_data->state);
+ led_set_brightness_nosleep(led_cdev, transient_data->state);
transient_data->restore_state =
(transient_data->state == LED_FULL) ? LED_OFF : LED_FULL;
mod_timer(&transient_data->timer,
- jiffies + transient_data->duration);
+ jiffies + msecs_to_jiffies(transient_data->duration));
}
/* state == 0 && transient_data->activate == 0
@@ -98,8 +95,7 @@ static ssize_t transient_activate_store(struct device *dev,
static ssize_t transient_duration_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data = led_trigger_get_drvdata(dev);
return sprintf(buf, "%lu\n", transient_data->duration);
}
@@ -107,8 +103,8 @@ static ssize_t transient_duration_show(struct device *dev,
static ssize_t transient_duration_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data =
+ led_trigger_get_drvdata(dev);
unsigned long state;
ssize_t ret;
@@ -123,8 +119,8 @@ static ssize_t transient_duration_store(struct device *dev,
static ssize_t transient_state_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data =
+ led_trigger_get_drvdata(dev);
int state;
state = (transient_data->state == LED_FULL) ? 1 : 0;
@@ -134,8 +130,8 @@ static ssize_t transient_state_show(struct device *dev,
static ssize_t transient_state_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t size)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
+ struct transient_trig_data *transient_data =
+ led_trigger_get_drvdata(dev);
unsigned long state;
ssize_t ret;
@@ -156,82 +152,47 @@ static DEVICE_ATTR(duration, 0644, transient_duration_show,
transient_duration_store);
static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store);
-static void transient_trig_activate(struct led_classdev *led_cdev)
+static struct attribute *transient_trig_attrs[] = {
+ &dev_attr_activate.attr,
+ &dev_attr_duration.attr,
+ &dev_attr_state.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(transient_trig);
+
+static int transient_trig_activate(struct led_classdev *led_cdev)
{
- int rc;
struct transient_trig_data *tdata;
tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL);
- if (!tdata) {
- dev_err(led_cdev->dev,
- "unable to allocate transient trigger\n");
- return;
- }
- led_cdev->trigger_data = tdata;
-
- rc = device_create_file(led_cdev->dev, &dev_attr_activate);
- if (rc)
- goto err_out;
-
- rc = device_create_file(led_cdev->dev, &dev_attr_duration);
- if (rc)
- goto err_out_duration;
-
- rc = device_create_file(led_cdev->dev, &dev_attr_state);
- if (rc)
- goto err_out_state;
-
- setup_timer(&tdata->timer, transient_timer_function,
- (unsigned long) led_cdev);
- led_cdev->activated = true;
-
- return;
-
-err_out_state:
- device_remove_file(led_cdev->dev, &dev_attr_duration);
-err_out_duration:
- device_remove_file(led_cdev->dev, &dev_attr_activate);
-err_out:
- dev_err(led_cdev->dev, "unable to register transient trigger\n");
- led_cdev->trigger_data = NULL;
- kfree(tdata);
+ if (!tdata)
+ return -ENOMEM;
+
+ led_set_trigger_data(led_cdev, tdata);
+ tdata->led_cdev = led_cdev;
+
+ timer_setup(&tdata->timer, transient_timer_function, 0);
+
+ return 0;
}
static void transient_trig_deactivate(struct led_classdev *led_cdev)
{
- struct transient_trig_data *transient_data = led_cdev->trigger_data;
-
- if (led_cdev->activated) {
- del_timer_sync(&transient_data->timer);
- __led_set_brightness(led_cdev, transient_data->restore_state);
- device_remove_file(led_cdev->dev, &dev_attr_activate);
- device_remove_file(led_cdev->dev, &dev_attr_duration);
- device_remove_file(led_cdev->dev, &dev_attr_state);
- led_cdev->trigger_data = NULL;
- led_cdev->activated = false;
- kfree(transient_data);
- }
+ struct transient_trig_data *transient_data = led_get_trigger_data(led_cdev);
+
+ timer_shutdown_sync(&transient_data->timer);
+ led_set_brightness_nosleep(led_cdev, transient_data->restore_state);
+ kfree(transient_data);
}
static struct led_trigger transient_trigger = {
.name = "transient",
.activate = transient_trig_activate,
.deactivate = transient_trig_deactivate,
+ .groups = transient_trig_groups,
};
-
-static int __init transient_trig_init(void)
-{
- return led_trigger_register(&transient_trigger);
-}
-
-static void __exit transient_trig_exit(void)
-{
- led_trigger_unregister(&transient_trigger);
-}
-
-module_init(transient_trig_init);
-module_exit(transient_trig_exit);
+module_led_trigger(transient_trigger);
MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>");
MODULE_DESCRIPTION("Transient LED trigger");
-MODULE_LICENSE("GPL");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/trigger/ledtrig-tty.c b/drivers/leds/trigger/ledtrig-tty.c
new file mode 100644
index 000000000000..8cf1485e8165
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-tty.c
@@ -0,0 +1,357 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <uapi/linux/serial.h>
+
+#define LEDTRIG_TTY_INTERVAL 50
+
+struct ledtrig_tty_data {
+ struct led_classdev *led_cdev;
+ struct delayed_work dwork;
+ struct completion sysfs;
+ const char *ttyname;
+ struct tty_struct *tty;
+ int rx, tx;
+ bool mode_rx;
+ bool mode_tx;
+ bool mode_cts;
+ bool mode_dsr;
+ bool mode_dcd;
+ bool mode_rng;
+};
+
+/* Indicates which state the LED should now display */
+enum led_trigger_tty_state {
+ TTY_LED_BLINK,
+ TTY_LED_ENABLE,
+ TTY_LED_DISABLE,
+};
+
+enum led_trigger_tty_modes {
+ TRIGGER_TTY_RX = 0,
+ TRIGGER_TTY_TX,
+ TRIGGER_TTY_CTS,
+ TRIGGER_TTY_DSR,
+ TRIGGER_TTY_DCD,
+ TRIGGER_TTY_RNG,
+};
+
+static int ledtrig_tty_wait_for_completion(struct device *dev)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ int ret;
+
+ ret = wait_for_completion_timeout(&trigger_data->sysfs,
+ msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 20));
+ if (ret == 0)
+ return -ETIMEDOUT;
+
+ return ret;
+}
+
+static ssize_t ttyname_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ ssize_t len = 0;
+ int completion;
+
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_wait_for_completion(dev);
+ if (completion < 0)
+ return completion;
+
+ if (trigger_data->ttyname)
+ len = sprintf(buf, "%s\n", trigger_data->ttyname);
+
+ return len;
+}
+
+static ssize_t ttyname_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t size)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ char *ttyname;
+ ssize_t ret = size;
+ int completion;
+
+ if (size > 0 && buf[size - 1] == '\n')
+ size -= 1;
+
+ if (size) {
+ ttyname = kmemdup_nul(buf, size, GFP_KERNEL);
+ if (!ttyname)
+ return -ENOMEM;
+ } else {
+ ttyname = NULL;
+ }
+
+ reinit_completion(&trigger_data->sysfs);
+ completion = ledtrig_tty_wait_for_completion(dev);
+ if (completion < 0)
+ return completion;
+
+ kfree(trigger_data->ttyname);
+ tty_kref_put(trigger_data->tty);
+ trigger_data->tty = NULL;
+
+ trigger_data->ttyname = ttyname;
+
+ return ret;
+}
+static DEVICE_ATTR_RW(ttyname);
+
+static ssize_t ledtrig_tty_attr_show(struct device *dev, char *buf,
+ enum led_trigger_tty_modes attr)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ bool state;
+
+ switch (attr) {
+ case TRIGGER_TTY_RX:
+ state = trigger_data->mode_rx;
+ break;
+ case TRIGGER_TTY_TX:
+ state = trigger_data->mode_tx;
+ break;
+ case TRIGGER_TTY_CTS:
+ state = trigger_data->mode_cts;
+ break;
+ case TRIGGER_TTY_DSR:
+ state = trigger_data->mode_dsr;
+ break;
+ case TRIGGER_TTY_DCD:
+ state = trigger_data->mode_dcd;
+ break;
+ case TRIGGER_TTY_RNG:
+ state = trigger_data->mode_rng;
+ break;
+ }
+
+ return sysfs_emit(buf, "%u\n", state);
+}
+
+static ssize_t ledtrig_tty_attr_store(struct device *dev, const char *buf,
+ size_t size, enum led_trigger_tty_modes attr)
+{
+ struct ledtrig_tty_data *trigger_data = led_trigger_get_drvdata(dev);
+ bool state;
+ int ret;
+
+ ret = kstrtobool(buf, &state);
+ if (ret)
+ return ret;
+
+ switch (attr) {
+ case TRIGGER_TTY_RX:
+ trigger_data->mode_rx = state;
+ break;
+ case TRIGGER_TTY_TX:
+ trigger_data->mode_tx = state;
+ break;
+ case TRIGGER_TTY_CTS:
+ trigger_data->mode_cts = state;
+ break;
+ case TRIGGER_TTY_DSR:
+ trigger_data->mode_dsr = state;
+ break;
+ case TRIGGER_TTY_DCD:
+ trigger_data->mode_dcd = state;
+ break;
+ case TRIGGER_TTY_RNG:
+ trigger_data->mode_rng = state;
+ break;
+ }
+
+ return size;
+}
+
+#define DEFINE_TTY_TRIGGER(trigger_name, trigger) \
+ static ssize_t trigger_name##_show(struct device *dev, \
+ struct device_attribute *attr, char *buf) \
+ { \
+ return ledtrig_tty_attr_show(dev, buf, trigger); \
+ } \
+ static ssize_t trigger_name##_store(struct device *dev, \
+ struct device_attribute *attr, const char *buf, size_t size) \
+ { \
+ return ledtrig_tty_attr_store(dev, buf, size, trigger); \
+ } \
+ static DEVICE_ATTR_RW(trigger_name)
+
+DEFINE_TTY_TRIGGER(rx, TRIGGER_TTY_RX);
+DEFINE_TTY_TRIGGER(tx, TRIGGER_TTY_TX);
+DEFINE_TTY_TRIGGER(cts, TRIGGER_TTY_CTS);
+DEFINE_TTY_TRIGGER(dsr, TRIGGER_TTY_DSR);
+DEFINE_TTY_TRIGGER(dcd, TRIGGER_TTY_DCD);
+DEFINE_TTY_TRIGGER(rng, TRIGGER_TTY_RNG);
+
+static void ledtrig_tty_work(struct work_struct *work)
+{
+ struct ledtrig_tty_data *trigger_data =
+ container_of(work, struct ledtrig_tty_data, dwork.work);
+ enum led_trigger_tty_state state = TTY_LED_DISABLE;
+ unsigned long interval = LEDTRIG_TTY_INTERVAL;
+ bool invert = false;
+ int status;
+ int ret;
+
+ if (!trigger_data->ttyname)
+ goto out;
+
+ /* try to get the tty corresponding to $ttyname */
+ if (!trigger_data->tty) {
+ dev_t devno;
+ struct tty_struct *tty;
+ int ret;
+
+ ret = tty_dev_name_to_number(trigger_data->ttyname, &devno);
+ if (ret < 0)
+ /*
+ * A device with this name might appear later, so keep
+ * retrying.
+ */
+ goto out;
+
+ tty = tty_kopen_shared(devno);
+ if (IS_ERR(tty) || !tty)
+ /* What to do? retry or abort */
+ goto out;
+
+ trigger_data->tty = tty;
+ }
+
+ status = tty_get_tiocm(trigger_data->tty);
+ if (status > 0) {
+ if (trigger_data->mode_cts) {
+ if (status & TIOCM_CTS)
+ state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_dsr) {
+ if (status & TIOCM_DSR)
+ state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_dcd) {
+ if (status & TIOCM_CAR)
+ state = TTY_LED_ENABLE;
+ }
+
+ if (trigger_data->mode_rng) {
+ if (status & TIOCM_RNG)
+ state = TTY_LED_ENABLE;
+ }
+ }
+
+ /*
+ * The evaluation of rx/tx must be done after the evaluation
+ * of TIOCM_*, because rx/tx has priority.
+ */
+ if (trigger_data->mode_rx || trigger_data->mode_tx) {
+ struct serial_icounter_struct icount;
+
+ ret = tty_get_icount(trigger_data->tty, &icount);
+ if (ret)
+ goto out;
+
+ if (trigger_data->mode_tx && (icount.tx != trigger_data->tx)) {
+ trigger_data->tx = icount.tx;
+ invert = state == TTY_LED_ENABLE;
+ state = TTY_LED_BLINK;
+ }
+
+ if (trigger_data->mode_rx && (icount.rx != trigger_data->rx)) {
+ trigger_data->rx = icount.rx;
+ invert = state == TTY_LED_ENABLE;
+ state = TTY_LED_BLINK;
+ }
+ }
+
+out:
+ switch (state) {
+ case TTY_LED_BLINK:
+ led_blink_set_oneshot(trigger_data->led_cdev, &interval,
+ &interval, invert);
+ break;
+ case TTY_LED_ENABLE:
+ led_set_brightness(trigger_data->led_cdev,
+ trigger_data->led_cdev->blink_brightness);
+ break;
+ case TTY_LED_DISABLE:
+ fallthrough;
+ default:
+ led_set_brightness(trigger_data->led_cdev, LED_OFF);
+ break;
+ }
+
+ complete_all(&trigger_data->sysfs);
+ schedule_delayed_work(&trigger_data->dwork,
+ msecs_to_jiffies(LEDTRIG_TTY_INTERVAL * 2));
+}
+
+static struct attribute *ledtrig_tty_attrs[] = {
+ &dev_attr_ttyname.attr,
+ &dev_attr_rx.attr,
+ &dev_attr_tx.attr,
+ &dev_attr_cts.attr,
+ &dev_attr_dsr.attr,
+ &dev_attr_dcd.attr,
+ &dev_attr_rng.attr,
+ NULL
+};
+ATTRIBUTE_GROUPS(ledtrig_tty);
+
+static int ledtrig_tty_activate(struct led_classdev *led_cdev)
+{
+ struct ledtrig_tty_data *trigger_data;
+
+ trigger_data = kzalloc(sizeof(*trigger_data), GFP_KERNEL);
+ if (!trigger_data)
+ return -ENOMEM;
+
+ /* Enable default rx/tx mode */
+ trigger_data->mode_rx = true;
+ trigger_data->mode_tx = true;
+
+ led_set_trigger_data(led_cdev, trigger_data);
+
+ INIT_DELAYED_WORK(&trigger_data->dwork, ledtrig_tty_work);
+ trigger_data->led_cdev = led_cdev;
+ init_completion(&trigger_data->sysfs);
+
+ schedule_delayed_work(&trigger_data->dwork, 0);
+
+ return 0;
+}
+
+static void ledtrig_tty_deactivate(struct led_classdev *led_cdev)
+{
+ struct ledtrig_tty_data *trigger_data = led_get_trigger_data(led_cdev);
+
+ cancel_delayed_work_sync(&trigger_data->dwork);
+
+ kfree(trigger_data->ttyname);
+ tty_kref_put(trigger_data->tty);
+ trigger_data->tty = NULL;
+
+ kfree(trigger_data);
+}
+
+static struct led_trigger ledtrig_tty = {
+ .name = "tty",
+ .activate = ledtrig_tty_activate,
+ .deactivate = ledtrig_tty_deactivate,
+ .groups = ledtrig_tty_groups,
+};
+module_led_trigger(ledtrig_tty);
+
+MODULE_AUTHOR("Uwe Kleine-König <u.kleine-koenig@pengutronix.de>");
+MODULE_DESCRIPTION("UART LED trigger");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/uleds.c b/drivers/leds/uleds.c
new file mode 100644
index 000000000000..374a841f18c3
--- /dev/null
+++ b/drivers/leds/uleds.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Userspace driver for the LED subsystem
+ *
+ * Copyright (C) 2016 David Lechner <david@lechnology.com>
+ *
+ * Based on uinput.c: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
+ */
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+#include <linux/miscdevice.h>
+#include <linux/module.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+#include <uapi/linux/uleds.h>
+
+#define ULEDS_NAME "uleds"
+
+enum uleds_state {
+ ULEDS_STATE_UNKNOWN,
+ ULEDS_STATE_REGISTERED,
+};
+
+struct uleds_device {
+ struct uleds_user_dev user_dev;
+ struct led_classdev led_cdev;
+ struct mutex mutex;
+ enum uleds_state state;
+ wait_queue_head_t waitq;
+ int brightness;
+ bool new_data;
+};
+
+static struct miscdevice uleds_misc;
+
+static void uleds_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct uleds_device *udev = container_of(led_cdev, struct uleds_device,
+ led_cdev);
+
+ if (udev->brightness != brightness) {
+ udev->brightness = brightness;
+ udev->new_data = true;
+ wake_up_interruptible(&udev->waitq);
+ }
+}
+
+static int uleds_open(struct inode *inode, struct file *file)
+{
+ struct uleds_device *udev;
+
+ udev = kzalloc(sizeof(*udev), GFP_KERNEL);
+ if (!udev)
+ return -ENOMEM;
+
+ udev->led_cdev.name = udev->user_dev.name;
+ udev->led_cdev.brightness_set = uleds_brightness_set;
+
+ mutex_init(&udev->mutex);
+ init_waitqueue_head(&udev->waitq);
+ udev->state = ULEDS_STATE_UNKNOWN;
+
+ file->private_data = udev;
+ stream_open(inode, file);
+
+ return 0;
+}
+
+static ssize_t uleds_write(struct file *file, const char __user *buffer,
+ size_t count, loff_t *ppos)
+{
+ struct uleds_device *udev = file->private_data;
+ const char *name;
+ int ret;
+
+ if (count == 0)
+ return 0;
+
+ ret = mutex_lock_interruptible(&udev->mutex);
+ if (ret)
+ return ret;
+
+ if (udev->state == ULEDS_STATE_REGISTERED) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (count != sizeof(struct uleds_user_dev)) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (copy_from_user(&udev->user_dev, buffer,
+ sizeof(struct uleds_user_dev))) {
+ ret = -EFAULT;
+ goto out;
+ }
+
+ name = udev->user_dev.name;
+ if (!name[0] || !strcmp(name, ".") || !strcmp(name, "..") ||
+ strchr(name, '/')) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ if (udev->user_dev.max_brightness <= 0) {
+ ret = -EINVAL;
+ goto out;
+ }
+ udev->led_cdev.max_brightness = udev->user_dev.max_brightness;
+
+ ret = devm_led_classdev_register(uleds_misc.this_device,
+ &udev->led_cdev);
+ if (ret < 0)
+ goto out;
+
+ udev->new_data = true;
+ udev->state = ULEDS_STATE_REGISTERED;
+ ret = count;
+
+out:
+ mutex_unlock(&udev->mutex);
+
+ return ret;
+}
+
+static ssize_t uleds_read(struct file *file, char __user *buffer, size_t count,
+ loff_t *ppos)
+{
+ struct uleds_device *udev = file->private_data;
+ ssize_t retval;
+
+ if (count < sizeof(udev->brightness))
+ return 0;
+
+ do {
+ retval = mutex_lock_interruptible(&udev->mutex);
+ if (retval)
+ return retval;
+
+ if (udev->state != ULEDS_STATE_REGISTERED) {
+ retval = -ENODEV;
+ } else if (!udev->new_data && (file->f_flags & O_NONBLOCK)) {
+ retval = -EAGAIN;
+ } else if (udev->new_data) {
+ retval = copy_to_user(buffer, &udev->brightness,
+ sizeof(udev->brightness));
+ udev->new_data = false;
+ retval = sizeof(udev->brightness);
+ }
+
+ mutex_unlock(&udev->mutex);
+
+ if (retval)
+ break;
+
+ if (!(file->f_flags & O_NONBLOCK))
+ retval = wait_event_interruptible(udev->waitq,
+ udev->new_data ||
+ udev->state != ULEDS_STATE_REGISTERED);
+ } while (retval == 0);
+
+ return retval;
+}
+
+static __poll_t uleds_poll(struct file *file, poll_table *wait)
+{
+ struct uleds_device *udev = file->private_data;
+
+ poll_wait(file, &udev->waitq, wait);
+
+ if (udev->new_data)
+ return EPOLLIN | EPOLLRDNORM;
+
+ return 0;
+}
+
+static int uleds_release(struct inode *inode, struct file *file)
+{
+ struct uleds_device *udev = file->private_data;
+
+ if (udev->state == ULEDS_STATE_REGISTERED) {
+ udev->state = ULEDS_STATE_UNKNOWN;
+ devm_led_classdev_unregister(uleds_misc.this_device,
+ &udev->led_cdev);
+ }
+ kfree(udev);
+
+ return 0;
+}
+
+static const struct file_operations uleds_fops = {
+ .owner = THIS_MODULE,
+ .open = uleds_open,
+ .release = uleds_release,
+ .read = uleds_read,
+ .write = uleds_write,
+ .poll = uleds_poll,
+};
+
+static struct miscdevice uleds_misc = {
+ .fops = &uleds_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = ULEDS_NAME,
+};
+
+module_misc_device(uleds_misc);
+
+MODULE_AUTHOR("David Lechner <david@lechnology.com>");
+MODULE_DESCRIPTION("Userspace driver for the LED subsystem");
+MODULE_LICENSE("GPL");