aboutsummaryrefslogtreecommitdiff
path: root/drivers/leds
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds')
-rw-r--r--drivers/leds/Kconfig313
-rw-r--r--drivers/leds/Makefile28
-rw-r--r--drivers/leds/dell-led.c172
-rw-r--r--drivers/leds/led-class.c167
-rw-r--r--drivers/leds/led-core.c103
-rw-r--r--drivers/leds/led-triggers.c167
-rw-r--r--drivers/leds/leds-88pm860x.c241
-rw-r--r--drivers/leds/leds-adp5520.c44
-rw-r--r--drivers/leds/leds-alix2.c239
-rw-r--r--drivers/leds/leds-ams-delta.c136
-rw-r--r--drivers/leds/leds-asic3.c181
-rw-r--r--drivers/leds/leds-atmel-pwm.c34
-rw-r--r--drivers/leds/leds-bd2802.c93
-rw-r--r--drivers/leds/leds-blinkm.c811
-rw-r--r--drivers/leds/leds-clevo-mail.c26
-rw-r--r--drivers/leds/leds-cobalt-qube.c35
-rw-r--r--drivers/leds/leds-cobalt-raq.c18
-rw-r--r--drivers/leds/leds-da903x.c44
-rw-r--r--drivers/leds/leds-da9052.c212
-rw-r--r--drivers/leds/leds-dac124s085.c20
-rw-r--r--drivers/leds/leds-fsg.c32
-rw-r--r--drivers/leds/leds-gpio-register.c42
-rw-r--r--drivers/leds/leds-gpio.c260
-rw-r--r--drivers/leds/leds-h1940.c170
-rw-r--r--drivers/leds/leds-hp6xx.c19
-rw-r--r--drivers/leds/leds-lm3530.c509
-rw-r--r--drivers/leds/leds-lm3533.c784
-rw-r--r--drivers/leds/leds-lm355x.c574
-rw-r--r--drivers/leds/leds-lm3642.c464
-rw-r--r--drivers/leds/leds-locomo.c1
-rw-r--r--drivers/leds/leds-lp3944.c37
-rw-r--r--drivers/leds/leds-lp5521.c946
-rw-r--r--drivers/leds/leds-lp5523.c1298
-rw-r--r--drivers/leds/leds-lp5562.c617
-rw-r--r--drivers/leds/leds-lp55xx-common.c613
-rw-r--r--drivers/leds/leds-lp55xx-common.h208
-rw-r--r--drivers/leds/leds-lp8501.c411
-rw-r--r--drivers/leds/leds-lp8788.c194
-rw-r--r--drivers/leds/leds-lt3593.c60
-rw-r--r--drivers/leds/leds-max8997.c319
-rw-r--r--drivers/leds/leds-mc13783.c512
-rw-r--r--drivers/leds/leds-net48xx.c3
-rw-r--r--drivers/leds/leds-net5501.c96
-rw-r--r--drivers/leds/leds-netxbig.c74
-rw-r--r--drivers/leds/leds-ns2.c203
-rw-r--r--drivers/leds/leds-ot200.c170
-rw-r--r--drivers/leds/leds-pca9532.c282
-rw-r--r--drivers/leds/leds-pca955x.c118
-rw-r--r--drivers/leds/leds-pca963x.c461
-rw-r--r--drivers/leds/leds-pwm.c246
-rw-r--r--drivers/leds/leds-rb532.c24
-rw-r--r--drivers/leds/leds-regulator.c34
-rw-r--r--drivers/leds/leds-s3c24xx.c69
-rw-r--r--drivers/leds/leds-ss4200.c16
-rw-r--r--drivers/leds/leds-sunfire.c60
-rw-r--r--drivers/leds/leds-tca6507.c848
-rw-r--r--drivers/leds/leds-versatile.c110
-rw-r--r--drivers/leds/leds-wm831x-status.c31
-rw-r--r--drivers/leds/leds-wm8350.c61
-rw-r--r--drivers/leds/leds-wrap.c3
-rw-r--r--drivers/leds/leds.h11
-rw-r--r--drivers/leds/trigger/Kconfig111
-rw-r--r--drivers/leds/trigger/Makefile10
-rw-r--r--drivers/leds/trigger/ledtrig-backlight.c (renamed from drivers/leds/ledtrig-backlight.c)87
-rw-r--r--drivers/leds/trigger/ledtrig-camera.c57
-rw-r--r--drivers/leds/trigger/ledtrig-cpu.c166
-rw-r--r--drivers/leds/trigger/ledtrig-default-on.c (renamed from drivers/leds/ledtrig-default-on.c)4
-rw-r--r--drivers/leds/trigger/ledtrig-gpio.c (renamed from drivers/leds/ledtrig-gpio.c)27
-rw-r--r--drivers/leds/trigger/ledtrig-heartbeat.c (renamed from drivers/leds/ledtrig-heartbeat.c)50
-rw-r--r--drivers/leds/trigger/ledtrig-ide-disk.c (renamed from drivers/leds/ledtrig-ide-disk.c)25
-rw-r--r--drivers/leds/trigger/ledtrig-oneshot.c204
-rw-r--r--drivers/leds/trigger/ledtrig-timer.c (renamed from drivers/leds/ledtrig-timer.c)48
-rw-r--r--drivers/leds/trigger/ledtrig-transient.c237
73 files changed, 11359 insertions, 3741 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 77b8fd20cd9..a1b044e7eaa 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -1,3 +1,10 @@
+config LEDS_GPIO_REGISTER
+ bool
+ help
+ This option provides the function gpio_led_register_device.
+ As this function is used by arch code it must not be compiled as a
+ module.
+
menuconfig NEW_LEDS
bool "LED Support"
help
@@ -10,17 +17,16 @@ menuconfig NEW_LEDS
if NEW_LEDS
config LEDS_CLASS
- bool "LED Class Support"
+ 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.
-if LEDS_CLASS
-
comment "LED drivers"
config LEDS_88PM860X
tristate "LED Support for Marvell 88PM860x PMIC"
+ depends on LEDS_CLASS
depends on MFD_88PM860X
help
This option enables support for on-chip LED drivers found on Marvell
@@ -28,13 +34,49 @@ config LEDS_88PM860X
config LEDS_ATMEL_PWM
tristate "LED Support using Atmel PWM outputs"
+ depends on LEDS_CLASS
depends on ATMEL_PWM
help
This option enables support for LEDs driven using outputs
of the dedicated PWM controller found on newer Atmel SOCs.
+config LEDS_LM3530
+ tristate "LCD Backlight driver for LM3530"
+ depends on LEDS_CLASS
+ depends on I2C
+ help
+ This option enables support for the LCD backlight using
+ LM3530 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
+ depends on MFD_LM3533
+ help
+ This option enables support for the LEDs on National Semiconductor /
+ TI LM3533 Lighting Power chips.
+
+ The LEDs can be controlled directly, through PWM input, or by the
+ ambient-light-sensor interface. The chip supports
+ hardware-accelerated blinking with maximum on and off periods of 9.8
+ and 77 seconds respectively.
+
+config LEDS_LM3642
+ tristate "LED support for LM3642 Chip"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
+ help
+ This option enables support for LEDs connected to LM3642.
+ The LM3642 is a 4MHz fixed-frequency synchronous boost
+ converter plus 1.5A constant current driver for a high-current
+ white LED.
+
+
config LEDS_LOCOMO
tristate "LED Support for Locomo device"
+ depends on LEDS_CLASS
depends on SHARP_LOCOMO
help
This option enables support for the LEDs on Sharp Locomo.
@@ -42,6 +84,7 @@ config LEDS_LOCOMO
config LEDS_MIKROTIK_RB532
tristate "LED Support for Mikrotik Routerboard 532"
+ depends on LEDS_CLASS
depends on MIKROTIK_RB532
help
This option enables support for the so called "User LED" of
@@ -49,61 +92,37 @@ config LEDS_MIKROTIK_RB532
config LEDS_S3C24XX
tristate "LED Support for Samsung S3C24XX GPIO LEDs"
- depends on ARCH_S3C2410
+ depends on LEDS_CLASS
+ depends on ARCH_S3C24XX
help
This option enables support for LEDs connected to GPIO lines
on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440.
-config LEDS_AMS_DELTA
- tristate "LED Support for the Amstrad Delta (E3)"
- depends on MACH_AMS_DELTA
- help
- This option enables support for the LEDs on Amstrad Delta (E3).
-
config LEDS_NET48XX
tristate "LED Support for Soekris net48xx series Error LED"
+ depends on LEDS_CLASS
depends on SCx200_GPIO
help
This option enables support for the Soekris net4801 and net4826 error
LED.
-config LEDS_NET5501
- tristate "LED Support for Soekris net5501 series Error LED"
- depends on LEDS_TRIGGERS
- depends on X86 && LEDS_GPIO_PLATFORM && GPIO_CS5535
- select LEDS_TRIGGER_DEFAULT_ON
- default n
- help
- Add support for the Soekris net5501 board (detection, error led
- and GPIO).
-
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
depends on SCx200_GPIO
help
This option enables support for the PCEngines WRAP programmable LEDs.
-config LEDS_ALIX2
- tristate "LED Support for ALIX.2 and ALIX.3 series"
- depends on X86 && !GPIO_CS5535 && !CS5535_GPIO
- help
- This option enables support for the PCEngines ALIX.2 and ALIX.3 LEDs.
- You have to set leds-alix2.force=1 for boards with Award BIOS.
-
-config LEDS_H1940
- tristate "LED Support for iPAQ H1940 device"
- depends on ARCH_H1940
- help
- This option enables support for the LEDs on the h1940.
-
config LEDS_COBALT_QUBE
tristate "LED Support for the Cobalt Qube series front LED"
+ depends on LEDS_CLASS
depends on MIPS_COBALT
help
This option enables support for the front LED on Cobalt Qube series
@@ -117,6 +136,7 @@ config LEDS_COBALT_RAQ
config LEDS_SUNFIRE
tristate "LED support for SunFire servers."
+ depends on LEDS_CLASS
depends on SPARC64
select LEDS_TRIGGERS
help
@@ -125,6 +145,7 @@ config LEDS_SUNFIRE
config LEDS_HP6XX
tristate "LED Support for the HP Jornada 6xx"
+ depends on LEDS_CLASS
depends on SH_HP6XX
help
This option enables LED support for the handheld
@@ -132,15 +153,27 @@ config LEDS_HP6XX
config LEDS_PCA9532
tristate "LED driver for PCA9532 dimmer"
- depends on I2C && INPUT && EXPERIMENTAL
+ depends on LEDS_CLASS
+ depends on I2C && INPUT
help
This option enables support for NXP pca9532
LED controller. It is generally only useful
as a platform driver
+config LEDS_PCA9532_GPIO
+ bool "Enable GPIO support for PCA9532"
+ depends on LEDS_PCA9532
+ depends on GPIOLIB
+ help
+ Allow unused pins on PCA9532 to be used as gpio.
+
+ To use a pin as gpio pca9532_type in pca9532_platform data needs to
+ set to PCA9532_TYPE_GPIO.
+
config LEDS_GPIO
tristate "LED Support for GPIO connected LEDs"
- depends on GENERIC_GPIO
+ depends on LEDS_CLASS
+ depends on GPIOLIB
help
This option enables support for the LEDs connected to GPIO
outputs. To be useful the particular board must have LEDs
@@ -148,25 +181,9 @@ config LEDS_GPIO
defined as platform devices and/or OpenFirmware platform devices.
The code to use these bindings can be selected below.
-config LEDS_GPIO_PLATFORM
- bool "Platform device bindings for GPIO LEDs"
- depends on LEDS_GPIO
- default y
- help
- Let the leds-gpio driver drive LEDs which have been defined as
- platform devices. If you don't know what this means, say yes.
-
-config LEDS_GPIO_OF
- bool "OpenFirmware platform device bindings for GPIO LEDs"
- depends on LEDS_GPIO && OF_DEVICE
- default y
- help
- Let the leds-gpio driver drive LEDs which have been defined as
- of_platform devices. For instance, LEDs which are listed in a "dts"
- file.
-
config LEDS_LP3944
tristate "LED Support for N.S. LP3944 (Fun Light) I2C chip"
+ depends on LEDS_CLASS
depends on I2C
help
This option enables support for LEDs connected to the National
@@ -176,9 +193,18 @@ config LEDS_LP3944
To compile this driver as a module, choose M here: the
module will be called leds-lp3944.
+config LEDS_LP55XX_COMMON
+ tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501"
+ depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501
+ select FW_LOADER
+ help
+ This option supports common operations for LP5521/5523/55231/5562/8501
+ devices.
+
config LEDS_LP5521
tristate "LED Support for N.S. LP5521 LED driver chip"
depends on LEDS_CLASS && I2C
+ select 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.
@@ -186,16 +212,48 @@ config LEDS_LP5521
programming the engines.
config LEDS_LP5523
- tristate "LED Support for N.S. LP5523 LED driver chip"
+ tristate "LED Support for TI/National LP5523/55231 LED driver chip"
depends on LEDS_CLASS && I2C
+ select LEDS_LP55XX_COMMON
help
- If you say yes here you get support for the National Semiconductor
- LP5523 LED driver. It is 9 channel chip with programmable engines.
+ If you say yes here you get support for TI/National Semiconductor
+ LP5523/55231 LED driver.
+ It is 9 channel chip with programmable engines.
Driver provides direct control via LED class and interface for
programming the engines.
+config LEDS_LP5562
+ tristate "LED Support for TI LP5562 LED driver chip"
+ depends on LEDS_CLASS && I2C
+ select 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_LP8501
+ tristate "LED Support for TI LP8501 LED driver chip"
+ depends on LEDS_CLASS && I2C
+ select 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
+ depends on MFD_LP8788
+ help
+ This option enables support for the Keyboard LEDs on the LP8788 PMIC.
+
config LEDS_CLEVO_MAIL
tristate "Mail LED on Clevo notebook"
+ depends on LEDS_CLASS
depends on X86 && SERIO_I8042 && DMI
help
This driver makes the mail LED accessible from userspace
@@ -226,14 +284,25 @@ config LEDS_CLEVO_MAIL
config LEDS_PCA955X
tristate "LED Support for PCA955x I2C chips"
+ depends on LEDS_CLASS
depends on I2C
help
This option enables support for LEDs connected to PCA955x
LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553.
+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_WM831X_STATUS
tristate "LED support for status LEDs on WM831x PMICs"
+ depends on LEDS_CLASS
depends on MFD_WM831X
help
This option enables support for the status LEDs of the WM831x
@@ -241,6 +310,7 @@ config LEDS_WM831X_STATUS
config LEDS_WM8350
tristate "LED Support for WM8350 AudioPlus PMIC"
+ depends on LEDS_CLASS
depends on MFD_WM8350
help
This option enables support for LEDs driven by the Wolfson
@@ -248,13 +318,23 @@ config LEDS_WM8350
config LEDS_DA903X
tristate "LED Support for DA9030/DA9034 PMIC"
+ depends on LEDS_CLASS
depends on PMIC_DA903X
help
This option enables support for on-chip LED drivers found
on Dialog Semiconductor DA9030/DA9034 PMICs.
+config LEDS_DA9052
+ tristate "Dialog DA9052/DA9053 LEDS"
+ depends on LEDS_CLASS
+ depends on PMIC_DA9052
+ help
+ This option enables support for on-chip LED drivers found
+ on Dialog Semiconductor DA9052-BC and DA9053-AA/Bx PMICs.
+
config LEDS_DAC124S085
tristate "LED Support for DAC124S085 SPI DAC"
+ depends on LEDS_CLASS
depends on SPI
help
This option enables support for DAC124S085 SPI DAC from NatSemi,
@@ -262,18 +342,21 @@ config LEDS_DAC124S085
config LEDS_PWM
tristate "PWM driven LED Support"
- depends on HAVE_PWM
+ depends on LEDS_CLASS
+ depends on PWM
help
This option enables support for pwm driven LEDs
config LEDS_REGULATOR
tristate "REGULATOR driven LED support"
+ depends on LEDS_CLASS
depends on REGULATOR
help
This option enables support for regulator driven LEDs.
config LEDS_BD2802
tristate "LED driver for BD2802 RGB LED"
+ depends on LEDS_CLASS
depends on I2C
help
This option enables support for BD2802GU RGB LED driver chips
@@ -281,6 +364,7 @@ config LEDS_BD2802
config LEDS_INTEL_SS4200
tristate "LED driver for Intel NAS SS4200 series"
+ depends on LEDS_CLASS
depends on PCI && DMI
help
This option enables support for the Intel SS4200 series of
@@ -290,7 +374,8 @@ config LEDS_INTEL_SS4200
config LEDS_LT3593
tristate "LED driver for LT3593 controllers"
- depends on GENERIC_GPIO
+ depends on LEDS_CLASS
+ depends on GPIOLIB
help
This option enables support for LEDs driven by a Linear Technology
LT3593 controller. This controller uses a special one-wire pulse
@@ -298,6 +383,7 @@ config LEDS_LT3593
config LEDS_ADP5520
tristate "LED Support for ADP5520/ADP5501 PMIC"
+ depends on LEDS_CLASS
depends on PMIC_ADP5520
help
This option enables support for on-chip LED drivers found
@@ -308,21 +394,24 @@ config LEDS_ADP5520
config LEDS_DELL_NETBOOKS
tristate "External LED on Dell Business Netbooks"
+ depends on LEDS_CLASS
depends on X86 && ACPI_WMI
help
This adds support for the Latitude 2100 and similar
notebooks that have an external LED.
config LEDS_MC13783
- tristate "LED Support for MC13783 PMIC"
- depends on MFD_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 PMIC.
+ on Freescale Semiconductor MC13783/MC13892/MC34708 PMIC.
config LEDS_NS2
tristate "LED support for Network Space v2 GPIO LEDs"
- depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || MACH_NETSPACE_MAX_V2 || D2NET_V2
+ depends on LEDS_CLASS
+ depends on ARCH_KIRKWOOD || MACH_KIRKWOOD
default y
help
This option enable support for the dual-GPIO LED found on the
@@ -331,80 +420,74 @@ config LEDS_NS2
config LEDS_NETXBIG
tristate "LED support for Big Network series LEDs"
- depends on MACH_NET2BIG_V2 || MACH_NET5BIG_V2
+ depends on LEDS_CLASS
+ depends on ARCH_KIRKWOOD || MACH_KIRKWOOD
default y
help
This option enable 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_TRIGGERS
- bool "LED Trigger support"
+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 trigger support for the leds class.
- These triggers allow kernel events to drive the LEDs and can
- be configured via sysfs. If unsure, say Y.
+ 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.
-if LEDS_TRIGGERS
-
-comment "LED Triggers"
-
-config LEDS_TRIGGER_TIMER
- tristate "LED Timer Trigger"
+config LEDS_TCA6507
+ tristate "LED Support for TCA6507 I2C chip"
+ depends on LEDS_CLASS && I2C
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-class.txt.
+ This option enables support for LEDs connected to TC6507
+ LED driver chips accessed via the I2C bus.
+ Driver support brightness control and hardware-assisted blinking.
- If unsure, say Y.
-
-config LEDS_TRIGGER_IDE_DISK
- bool "LED IDE Disk Trigger"
- depends on IDE_GD_ATA
+config LEDS_MAX8997
+ tristate "LED support for MAX8997 PMIC"
+ depends on LEDS_CLASS && MFD_MAX8997
help
- This allows LEDs to be controlled by IDE disk activity.
- If unsure, say Y.
+ This option enables support for on-chip LED drivers on
+ MAXIM MAX8997 PMIC.
-config LEDS_TRIGGER_HEARTBEAT
- tristate "LED Heartbeat Trigger"
+config LEDS_LM355x
+ tristate "LED support for LM355x Chips, LM3554 and LM3556"
+ depends on LEDS_CLASS && I2C
+ select REGMAP_I2C
help
- This allows LEDs to be controlled by a CPU load average.
- The flash frequency is a hyperbolic function of the 1-minute
- load average.
- If unsure, say Y.
+ This option enables support for LEDs connected to LM355x.
+ LM355x includes Torch, Flash and Indicator functions.
-config LEDS_TRIGGER_BACKLIGHT
- tristate "LED backlight Trigger"
+config LEDS_OT200
+ tristate "LED support for the Bachmann OT200"
+ depends on LEDS_CLASS && HAS_IOMEM && (X86_32 || COMPILE_TEST)
help
- This allows LEDs to be controlled as a backlight device: they
- turn off and on when the display is blanked and unblanked.
+ This option enables support for the LEDs on the Bachmann OT200.
+ Say Y to enable LEDs on the Bachmann OT200.
- If unsure, say N.
+comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
-config LEDS_TRIGGER_GPIO
- tristate "LED GPIO Trigger"
- depends on GPIOLIB
+config LEDS_BLINKM
+ tristate "LED support for the BlinkM I2C RGB LED"
+ depends on LEDS_CLASS
+ depends on I2C
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.
+ This option enables support for the BlinkM RGB LED connected
+ through I2C. Say Y to enable support for the BlinkM LED.
- If unsure, say N.
-
-config LEDS_TRIGGER_DEFAULT_ON
- tristate "LED Default ON Trigger"
+config LEDS_VERSATILE
+ tristate "LED support for the ARM Versatile and RealView"
+ depends on ARCH_REALVIEW || ARCH_VERSATILE
+ depends on LEDS_CLASS
help
- This allows LEDs to be initialised in the ON state.
- If unsure, say Y.
-
-comment "iptables trigger is under Netfilter config (LED target)"
- depends on LEDS_TRIGGERS
+ This option enabled support for the LEDs on the ARM Versatile
+ and RealView boards. Say Y to enabled these.
-endif # LEDS_TRIGGERS
-
-endif # LEDS_CLASS
+comment "LED Triggers"
+source "drivers/leds/trigger/Kconfig"
endif # NEW_LEDS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index aae6989ff6b..79c5155199a 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -9,27 +9,35 @@ obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.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_AMS_DELTA) += leds-ams-delta.o
obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
-obj-$(CONFIG_LEDS_NET5501) += leds-net5501.o
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
-obj-$(CONFIG_LEDS_ALIX2) += leds-alix2.o
-obj-$(CONFIG_LEDS_H1940) += leds-h1940.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_GPIO) += leds-gpio.o
obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o
+obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.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_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_OT200) += leds-ot200.o
obj-$(CONFIG_LEDS_FSG) += leds-fsg.o
obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o
+obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.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_PWM) += leds-pwm.o
@@ -41,14 +49,14 @@ 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_MAX8997) += leds-max8997.o
+obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
+obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
+obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
# LED Triggers
-obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
-obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.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_DEFAULT_ON) += ledtrig-default-on.o
+obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
diff --git a/drivers/leds/dell-led.c b/drivers/leds/dell-led.c
index 52590296af3..c36acaf566a 100644
--- a/drivers/leds/dell-led.c
+++ b/drivers/leds/dell-led.c
@@ -14,12 +14,16 @@
#include <linux/acpi.h>
#include <linux/leds.h>
#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/dmi.h>
+#include <linux/dell-led.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"
+#define DELL_APP_GUID "A80593CE-A997-11DA-B012-B622A1EF5492"
MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
/* Error Result Codes: */
@@ -38,6 +42,149 @@ MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID);
#define CMD_LED_OFF 17
#define CMD_LED_BLINK 18
+struct app_wmi_args {
+ u16 class;
+ u16 selector;
+ u32 arg1;
+ u32 arg2;
+ u32 arg3;
+ u32 arg4;
+ u32 res1;
+ u32 res2;
+ u32 res3;
+ u32 res4;
+ char dummy[92];
+};
+
+#define GLOBAL_MIC_MUTE_ENABLE 0x364
+#define GLOBAL_MIC_MUTE_DISABLE 0x365
+
+struct dell_bios_data_token {
+ u16 tokenid;
+ u16 location;
+ u16 value;
+};
+
+struct __attribute__ ((__packed__)) dell_bios_calling_interface {
+ struct dmi_header header;
+ u16 cmd_io_addr;
+ u8 cmd_io_code;
+ u32 supported_cmds;
+ struct dell_bios_data_token damap[];
+};
+
+static struct dell_bios_data_token dell_mic_tokens[2];
+
+static int dell_wmi_perform_query(struct app_wmi_args *args)
+{
+ struct app_wmi_args *bios_return;
+ union acpi_object *obj;
+ struct acpi_buffer input;
+ struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
+ acpi_status status;
+ u32 rc = -EINVAL;
+
+ input.length = 128;
+ input.pointer = args;
+
+ status = wmi_evaluate_method(DELL_APP_GUID, 0, 1, &input, &output);
+ if (!ACPI_SUCCESS(status))
+ goto err_out0;
+
+ obj = output.pointer;
+ if (!obj)
+ goto err_out0;
+
+ if (obj->type != ACPI_TYPE_BUFFER)
+ goto err_out1;
+
+ bios_return = (struct app_wmi_args *)obj->buffer.pointer;
+ rc = bios_return->res1;
+ if (rc)
+ goto err_out1;
+
+ memcpy(args, bios_return, sizeof(struct app_wmi_args));
+ rc = 0;
+
+ err_out1:
+ kfree(obj);
+ err_out0:
+ return rc;
+}
+
+static void __init find_micmute_tokens(const struct dmi_header *dm, void *dummy)
+{
+ struct dell_bios_calling_interface *calling_interface;
+ struct dell_bios_data_token *token;
+ int token_size = sizeof(struct dell_bios_data_token);
+ int i = 0;
+
+ if (dm->type == 0xda && dm->length > 17) {
+ calling_interface = container_of(dm,
+ struct dell_bios_calling_interface, header);
+
+ token = &calling_interface->damap[i];
+ while (token->tokenid != 0xffff) {
+ if (token->tokenid == GLOBAL_MIC_MUTE_DISABLE)
+ memcpy(&dell_mic_tokens[0], token, token_size);
+ else if (token->tokenid == GLOBAL_MIC_MUTE_ENABLE)
+ memcpy(&dell_mic_tokens[1], token, token_size);
+
+ i++;
+ token = &calling_interface->damap[i];
+ }
+ }
+}
+
+static int dell_micmute_led_set(int state)
+{
+ struct app_wmi_args args;
+ struct dell_bios_data_token *token;
+
+ if (!wmi_has_guid(DELL_APP_GUID))
+ return -ENODEV;
+
+ if (state == 0 || state == 1)
+ token = &dell_mic_tokens[state];
+ else
+ return -EINVAL;
+
+ memset(&args, 0, sizeof(struct app_wmi_args));
+
+ args.class = 1;
+ args.arg1 = token->location;
+ args.arg2 = token->value;
+
+ dell_wmi_perform_query(&args);
+
+ return state;
+}
+
+int dell_app_wmi_led_set(int whichled, int on)
+{
+ int state = 0;
+
+ switch (whichled) {
+ case DELL_LED_MICMUTE:
+ state = dell_micmute_led_set(on);
+ break;
+ default:
+ pr_warn("led type %x is not supported\n", whichled);
+ break;
+ }
+
+ return state;
+}
+EXPORT_SYMBOL_GPL(dell_app_wmi_led_set);
+
+static int __init dell_micmute_led_init(void)
+{
+ memset(dell_mic_tokens, 0, sizeof(struct dell_bios_data_token) * 2);
+ dmi_walk(find_micmute_tokens, NULL);
+
+ return 0;
+}
+
struct bios_args {
unsigned char length;
unsigned char result_code;
@@ -180,21 +327,32 @@ static int __init dell_led_init(void)
{
int error = 0;
- if (!wmi_has_guid(DELL_LED_BIOS_GUID))
+ if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID))
return -ENODEV;
- error = led_off();
- if (error != 0)
- return -ENODEV;
+ if (wmi_has_guid(DELL_APP_GUID))
+ error = dell_micmute_led_init();
- return led_classdev_register(NULL, &dell_led);
+ if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
+ error = led_off();
+ if (error != 0)
+ return -ENODEV;
+
+ error = led_classdev_register(NULL, &dell_led);
+ }
+
+ return error;
}
static void __exit dell_led_exit(void)
{
- led_classdev_unregister(&dell_led);
+ int error = 0;
- led_off();
+ if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
+ error = led_off();
+ if (error == 0)
+ led_classdev_unregister(&dell_led);
+ }
}
module_init(dell_led_init);
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 211e21f34bd..f37d63cf726 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -15,7 +15,6 @@
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
-#include <linux/sysdev.h>
#include <linux/timer.h>
#include <linux/err.h>
#include <linux/ctype.h>
@@ -30,7 +29,7 @@ static void led_update_brightness(struct led_classdev *led_cdev)
led_cdev->brightness = led_cdev->brightness_get(led_cdev);
}
-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);
@@ -41,28 +40,24 @@ static ssize_t led_brightness_show(struct device *dev,
return sprintf(buf, "%u\n", led_cdev->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;
- char *after;
- unsigned long state = simple_strtoul(buf, &after, 10);
- size_t count = after - buf;
- if (isspace(*after))
- count++;
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
- if (count == size) {
- ret = count;
+ if (state == LED_OFF)
+ led_trigger_remove(led_cdev);
+ __led_set_brightness(led_cdev, state);
- if (state == LED_OFF)
- led_trigger_remove(led_cdev);
- led_set_brightness(led_cdev, state);
- }
-
- return ret;
+ return size;
}
+static DEVICE_ATTR_RW(brightness);
static ssize_t led_max_brightness_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -71,14 +66,35 @@ static ssize_t led_max_brightness_show(struct device *dev,
return sprintf(buf, "%u\n", led_cdev->max_brightness);
}
+static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL);
+
+#ifdef CONFIG_LEDS_TRIGGERS
+static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store);
+static struct attribute *led_trigger_attrs[] = {
+ &dev_attr_trigger.attr,
+ NULL,
+};
+static const struct attribute_group led_trigger_group = {
+ .attrs = led_trigger_attrs,
+};
+#endif
+
+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 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 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)
@@ -88,7 +104,12 @@ static void led_timer_function(unsigned long data)
unsigned long delay;
if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) {
- led_set_brightness(led_cdev, LED_OFF);
+ __led_set_brightness(led_cdev, LED_OFF);
+ return;
+ }
+
+ if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) {
+ led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
return;
}
@@ -106,54 +127,35 @@ static void led_timer_function(unsigned long data)
delay = led_cdev->blink_delay_off;
}
- led_set_brightness(led_cdev, brightness);
+ __led_set_brightness(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 (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;
+ }
+ }
mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay));
}
-static void led_stop_software_blink(struct led_classdev *led_cdev)
+static void set_brightness_delayed(struct work_struct *ws)
{
- /* deactivate previous settings */
- del_timer_sync(&led_cdev->blink_timer);
- led_cdev->blink_delay_on = 0;
- led_cdev->blink_delay_off = 0;
-}
-
-static void led_set_software_blink(struct led_classdev *led_cdev,
- unsigned long delay_on,
- unsigned long delay_off)
-{
- int current_brightness;
-
- current_brightness = led_get_brightness(led_cdev);
- if (current_brightness)
- led_cdev->blink_brightness = current_brightness;
- if (!led_cdev->blink_brightness)
- led_cdev->blink_brightness = led_cdev->max_brightness;
-
- if (delay_on == led_cdev->blink_delay_on &&
- delay_off == led_cdev->blink_delay_off)
- return;
+ struct led_classdev *led_cdev =
+ container_of(ws, struct led_classdev, set_brightness_work);
led_stop_software_blink(led_cdev);
- led_cdev->blink_delay_on = delay_on;
- led_cdev->blink_delay_off = delay_off;
-
- /* never on - don't blink */
- if (!delay_on)
- return;
-
- /* never off - just set to brightness */
- if (!delay_off) {
- led_set_brightness(led_cdev, led_cdev->blink_brightness);
- return;
- }
-
- mod_timer(&led_cdev->blink_timer, jiffies + 1);
+ __led_set_brightness(led_cdev, led_cdev->delayed_set_value);
}
-
/**
* led_classdev_suspend - suspend an led_classdev.
* @led_cdev: the led_classdev to suspend.
@@ -176,7 +178,7 @@ void led_classdev_resume(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
-static int led_suspend(struct device *dev, pm_message_t state)
+static int led_suspend(struct device *dev)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
@@ -196,6 +198,11 @@ static int led_resume(struct device *dev)
return 0;
}
+static const struct dev_pm_ops leds_class_dev_pm_ops = {
+ .suspend = led_suspend,
+ .resume = led_resume,
+};
+
/**
* led_classdev_register - register a new object of led_classdev class.
* @parent: The device to register.
@@ -221,6 +228,8 @@ 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);
+
init_timer(&led_cdev->blink_timer);
led_cdev->blink_timer.function = led_timer_function;
led_cdev->blink_timer.data = (unsigned long)led_cdev;
@@ -229,7 +238,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
led_trigger_set_default(led_cdev);
#endif
- printk(KERN_DEBUG "Registered led device: %s\n",
+ dev_dbg(parent, "Registered led device: %s\n",
led_cdev->name);
return 0;
@@ -251,8 +260,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
up_write(&led_cdev->trigger_lock);
#endif
+ cancel_work_sync(&led_cdev->set_brightness_work);
+
/* Stop blinking */
- led_brightness_set(led_cdev, LED_OFF);
+ led_stop_software_blink(led_cdev);
+ led_set_brightness(led_cdev, LED_OFF);
device_unregister(led_cdev->dev);
@@ -262,38 +274,13 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_classdev_unregister);
-void led_blink_set(struct led_classdev *led_cdev,
- unsigned long *delay_on,
- unsigned long *delay_off)
-{
- if (led_cdev->blink_set &&
- led_cdev->blink_set(led_cdev, delay_on, delay_off))
- return;
-
- /* blink with 1 Hz as default if nothing specified */
- if (!*delay_on && !*delay_off)
- *delay_on = *delay_off = 500;
-
- led_set_software_blink(led_cdev, *delay_on, *delay_off);
-}
-EXPORT_SYMBOL(led_blink_set);
-
-void led_brightness_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
-{
- led_stop_software_blink(led_cdev);
- led_cdev->brightness_set(led_cdev, brightness);
-}
-EXPORT_SYMBOL(led_brightness_set);
-
static int __init leds_init(void)
{
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
- leds_class->suspend = led_suspend;
- leds_class->resume = led_resume;
- leds_class->dev_attrs = led_class_attrs;
+ leds_class->pm = &leds_class_dev_pm_ops;
+ leds_class->dev_groups = led_groups;
return 0;
}
diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c
index 016d19f5486..71b40d3bf77 100644
--- a/drivers/leds/led-core.c
+++ b/drivers/leds/led-core.c
@@ -23,3 +23,106 @@ EXPORT_SYMBOL_GPL(leds_list_lock);
LIST_HEAD(leds_list);
EXPORT_SYMBOL_GPL(leds_list);
+
+static void led_set_software_blink(struct led_classdev *led_cdev,
+ unsigned long delay_on,
+ unsigned long delay_off)
+{
+ int current_brightness;
+
+ current_brightness = led_get_brightness(led_cdev);
+ if (current_brightness)
+ led_cdev->blink_brightness = current_brightness;
+ if (!led_cdev->blink_brightness)
+ led_cdev->blink_brightness = led_cdev->max_brightness;
+
+ led_cdev->blink_delay_on = delay_on;
+ led_cdev->blink_delay_off = delay_off;
+
+ /* never on - just set to off */
+ if (!delay_on) {
+ __led_set_brightness(led_cdev, LED_OFF);
+ return;
+ }
+
+ /* never off - just set to brightness */
+ if (!delay_off) {
+ __led_set_brightness(led_cdev, led_cdev->blink_brightness);
+ return;
+ }
+
+ mod_timer(&led_cdev->blink_timer, jiffies + 1);
+}
+
+
+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) &&
+ led_cdev->blink_set &&
+ !led_cdev->blink_set(led_cdev, delay_on, delay_off))
+ return;
+
+ /* blink with 1 Hz as default if nothing specified */
+ if (!*delay_on && !*delay_off)
+ *delay_on = *delay_off = 500;
+
+ led_set_software_blink(led_cdev, *delay_on, *delay_off);
+}
+
+void led_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ del_timer_sync(&led_cdev->blink_timer);
+
+ led_cdev->flags &= ~LED_BLINK_ONESHOT;
+ led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
+
+ led_blink_setup(led_cdev, delay_on, delay_off);
+}
+EXPORT_SYMBOL(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) &&
+ timer_pending(&led_cdev->blink_timer))
+ return;
+
+ led_cdev->flags |= LED_BLINK_ONESHOT;
+ led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP;
+
+ if (invert)
+ led_cdev->flags |= LED_BLINK_INVERT;
+ else
+ led_cdev->flags &= ~LED_BLINK_INVERT;
+
+ led_blink_setup(led_cdev, delay_on, delay_off);
+}
+EXPORT_SYMBOL(led_blink_set_oneshot);
+
+void led_stop_software_blink(struct led_classdev *led_cdev)
+{
+ del_timer_sync(&led_cdev->blink_timer);
+ led_cdev->blink_delay_on = 0;
+ led_cdev->blink_delay_off = 0;
+}
+EXPORT_SYMBOL_GPL(led_stop_software_blink);
+
+void led_set_brightness(struct led_classdev *led_cdev,
+ enum led_brightness 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);
+ return;
+ }
+
+ __led_set_brightness(led_cdev, brightness);
+}
+EXPORT_SYMBOL(led_set_brightness);
diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c
index c41eb6180c9..c3734f10fdd 100644
--- a/drivers/leds/led-triggers.c
+++ b/drivers/leds/led-triggers.c
@@ -13,11 +13,9 @@
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/device.h>
-#include <linux/sysdev.h>
#include <linux/timer.h>
#include <linux/rwsem.h>
#include <linux/leds.h>
@@ -100,9 +98,15 @@ ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
EXPORT_SYMBOL_GPL(led_trigger_show);
/* Caller must ensure led_cdev->trigger_lock held */
-void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
+void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig)
{
unsigned long flags;
+ char *event = NULL;
+ char *envp[2];
+ const char *name;
+
+ name = trig ? trig->name : "none";
+ event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name);
/* Remove any existing trigger */
if (led_cdev->trigger) {
@@ -110,18 +114,27 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger)
list_del(&led_cdev->trig_list);
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
flags);
+ cancel_work_sync(&led_cdev->set_brightness_work);
+ led_stop_software_blink(led_cdev);
if (led_cdev->trigger->deactivate)
led_cdev->trigger->deactivate(led_cdev);
led_cdev->trigger = NULL;
- led_brightness_set(led_cdev, LED_OFF);
+ 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);
+ led_cdev->trigger = trig;
+ if (trig->activate)
+ trig->activate(led_cdev);
}
- if (trigger) {
- write_lock_irqsave(&trigger->leddev_list_lock, flags);
- list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs);
- write_unlock_irqrestore(&trigger->leddev_list_lock, flags);
- led_cdev->trigger = trigger;
- if (trigger->activate)
- trigger->activate(led_cdev);
+
+ if (event) {
+ envp[0] = event;
+ envp[1] = NULL;
+ kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp);
+ kfree(event);
}
}
EXPORT_SYMBOL_GPL(led_trigger_set);
@@ -152,26 +165,39 @@ void led_trigger_set_default(struct led_classdev *led_cdev)
}
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);
+}
+EXPORT_SYMBOL_GPL(led_trigger_rename_static);
+
/* LED Trigger Interface */
-int led_trigger_register(struct led_trigger *trigger)
+int led_trigger_register(struct led_trigger *trig)
{
struct led_classdev *led_cdev;
- struct led_trigger *trig;
+ struct led_trigger *_trig;
- rwlock_init(&trigger->leddev_list_lock);
- INIT_LIST_HEAD(&trigger->led_cdevs);
+ rwlock_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, trigger->name)) {
+ list_for_each_entry(_trig, &trigger_list, next_trig) {
+ if (!strcmp(_trig->name, trig->name)) {
up_write(&triggers_list_lock);
return -EEXIST;
}
}
/* Add to the list of led triggers */
- list_add_tail(&trigger->next_trig, &trigger_list);
+ list_add_tail(&trig->next_trig, &trigger_list);
up_write(&triggers_list_lock);
/* Register with any LEDs that have this as a default trigger */
@@ -179,8 +205,8 @@ int led_trigger_register(struct led_trigger *trigger)
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, trigger->name))
- led_trigger_set(led_cdev, trigger);
+ !strcmp(led_cdev->default_trigger, trig->name))
+ led_trigger_set(led_cdev, trig);
up_write(&led_cdev->trigger_lock);
}
up_read(&leds_list_lock);
@@ -189,20 +215,23 @@ int led_trigger_register(struct led_trigger *trigger)
}
EXPORT_SYMBOL_GPL(led_trigger_register);
-void led_trigger_unregister(struct led_trigger *trigger)
+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(&trigger->next_trig);
+ list_del_init(&trig->next_trig);
up_write(&triggers_list_lock);
/* Remove anyone actively using this trigger */
down_read(&leds_list_lock);
list_for_each_entry(led_cdev, &leds_list, node) {
down_write(&led_cdev->trigger_lock);
- if (led_cdev->trigger == trigger)
+ if (led_cdev->trigger == trig)
led_trigger_set(led_cdev, NULL);
up_write(&led_cdev->trigger_lock);
}
@@ -212,51 +241,89 @@ EXPORT_SYMBOL_GPL(led_trigger_unregister);
/* Simple LED Tigger Interface */
-void led_trigger_event(struct led_trigger *trigger,
+void led_trigger_event(struct led_trigger *trig,
enum led_brightness brightness)
{
- struct list_head *entry;
+ struct led_classdev *led_cdev;
- if (!trigger)
+ if (!trig)
return;
- read_lock(&trigger->leddev_list_lock);
- list_for_each(entry, &trigger->led_cdevs) {
- struct led_classdev *led_cdev;
-
- led_cdev = list_entry(entry, struct led_classdev, trig_list);
+ read_lock(&trig->leddev_list_lock);
+ list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list)
led_set_brightness(led_cdev, brightness);
- }
- read_unlock(&trigger->leddev_list_lock);
+ read_unlock(&trig->leddev_list_lock);
}
EXPORT_SYMBOL_GPL(led_trigger_event);
+static void led_trigger_blink_setup(struct led_trigger *trig,
+ unsigned long *delay_on,
+ unsigned long *delay_off,
+ int oneshot,
+ int invert)
+{
+ struct led_classdev *led_cdev;
+
+ if (!trig)
+ return;
+
+ read_lock(&trig->leddev_list_lock);
+ list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) {
+ if (oneshot)
+ led_blink_set_oneshot(led_cdev, delay_on, delay_off,
+ invert);
+ else
+ led_blink_set(led_cdev, delay_on, delay_off);
+ }
+ read_unlock(&trig->leddev_list_lock);
+}
+
+void led_trigger_blink(struct led_trigger *trig,
+ 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,
+ int invert)
+{
+ led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert);
+}
+EXPORT_SYMBOL_GPL(led_trigger_blink_oneshot);
+
void led_trigger_register_simple(const char *name, struct led_trigger **tp)
{
- struct led_trigger *trigger;
+ struct led_trigger *trig;
int err;
- trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
-
- if (trigger) {
- trigger->name = name;
- err = led_trigger_register(trigger);
- if (err < 0)
- printk(KERN_WARNING "LED trigger %s failed to register"
- " (%d)\n", name, err);
- } else
- printk(KERN_WARNING "LED trigger %s failed to register"
- " (no memory)\n", name);
+ trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
- *tp = trigger;
+ if (trig) {
+ trig->name = name;
+ err = led_trigger_register(trig);
+ if (err < 0) {
+ kfree(trig);
+ trig = NULL;
+ pr_warn("LED trigger %s failed to register (%d)\n",
+ name, err);
+ }
+ } else {
+ pr_warn("LED trigger %s failed to register (no memory)\n",
+ name);
+ }
+ *tp = trig;
}
EXPORT_SYMBOL_GPL(led_trigger_register_simple);
-void led_trigger_unregister_simple(struct led_trigger *trigger)
+void led_trigger_unregister_simple(struct led_trigger *trig)
{
- if (trigger)
- led_trigger_unregister(trigger);
- kfree(trigger);
+ if (trig)
+ led_trigger_unregister(trig);
+ kfree(trig);
}
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c
index e672b44ee17..c2def5551ce 100644
--- a/drivers/leds/leds-88pm860x.c
+++ b/drivers/leds/leds-88pm860x.c
@@ -11,26 +11,21 @@
*/
#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>
-#define LED_PWM_SHIFT (3)
#define LED_PWM_MASK (0x1F)
#define LED_CURRENT_MASK (0x07 << 5)
-#define LED_BLINK_ON_MASK (0x07)
#define LED_BLINK_MASK (0x7F)
-#define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66)
-#define LED_BLINK_ON_MIN LED_BLINK_ON(0)
-#define LED_BLINK_ON_MAX LED_BLINK_ON(0x7)
#define LED_ON_CONTINUOUS (0x0F << 3)
-#define LED_TO_ON(x) ((x - 66) / 66)
#define LED1_BLINK_EN (1 << 1)
#define LED2_BLINK_EN (1 << 2)
@@ -48,66 +43,27 @@ struct pm860x_led {
unsigned char brightness;
unsigned char current_brightness;
- int blink_data;
- int blink_time;
- int blink_on;
- int blink_off;
+ int reg_control;
+ int reg_blink;
+ int blink_mask;
};
-/* return offset of color register */
-static inline int __led_off(int port)
+static int led_power_set(struct pm860x_chip *chip, int port, int on)
{
int ret = -EINVAL;
switch (port) {
- case PM8606_LED1_RED:
- case PM8606_LED1_GREEN:
- case PM8606_LED1_BLUE:
- ret = port - PM8606_LED1_RED + PM8606_RGB1B;
+ case 0:
+ case 1:
+ case 2:
+ ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) :
+ pm8606_osc_disable(chip, RGB1_ENABLE);
break;
- case PM8606_LED2_RED:
- case PM8606_LED2_GREEN:
- case PM8606_LED2_BLUE:
- ret = port - PM8606_LED2_RED + PM8606_RGB2B;
- break;
- }
- return ret;
-}
-
-/* return offset of blink register */
-static inline int __blink_off(int port)
-{
- int ret = -EINVAL;
-
- switch (port) {
- case PM8606_LED1_RED:
- case PM8606_LED1_GREEN:
- case PM8606_LED1_BLUE:
- ret = PM8606_RGB1A;
- break;
- case PM8606_LED2_RED:
- case PM8606_LED2_GREEN:
- case PM8606_LED2_BLUE:
- ret = PM8606_RGB2A;
- break;
- }
- return ret;
-}
-
-static inline int __blink_ctl_mask(int port)
-{
- int ret = -EINVAL;
-
- switch (port) {
- case PM8606_LED1_RED:
- case PM8606_LED1_GREEN:
- case PM8606_LED1_BLUE:
- ret = LED1_BLINK_EN;
- break;
- case PM8606_LED2_RED:
- case PM8606_LED2_GREEN:
- case PM8606_LED2_BLUE:
- ret = LED2_BLINK_EN;
+ case 3:
+ case 4:
+ case 5:
+ ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) :
+ pm8606_osc_disable(chip, RGB2_ENABLE);
break;
}
return ret;
@@ -118,29 +74,43 @@ static void pm860x_led_work(struct work_struct *work)
struct pm860x_led *led;
struct pm860x_chip *chip;
- int mask;
+ unsigned char buf[3];
+ int ret;
led = container_of(work, struct pm860x_led, work);
chip = led->chip;
mutex_lock(&led->lock);
if ((led->current_brightness == 0) && led->brightness) {
+ led_power_set(chip, led->port, 1);
if (led->iset) {
- pm860x_set_bits(led->i2c, __led_off(led->port),
+ pm860x_set_bits(led->i2c, led->reg_control,
LED_CURRENT_MASK, led->iset);
}
- mask = __blink_ctl_mask(led->port);
- pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask);
- } else if (led->brightness == 0) {
- pm860x_set_bits(led->i2c, __led_off(led->port),
- LED_CURRENT_MASK, 0);
- mask = __blink_ctl_mask(led->port);
- pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0);
+ pm860x_set_bits(led->i2c, led->reg_blink,
+ LED_BLINK_MASK, LED_ON_CONTINUOUS);
+ pm860x_set_bits(led->i2c, PM8606_WLED3B, led->blink_mask,
+ led->blink_mask);
}
- pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK,
+ pm860x_set_bits(led->i2c, led->reg_control, LED_PWM_MASK,
led->brightness);
+
+ if (led->brightness == 0) {
+ pm860x_bulk_read(led->i2c, led->reg_control, 3, buf);
+ ret = buf[0] & LED_PWM_MASK;
+ ret |= buf[1] & LED_PWM_MASK;
+ ret |= buf[2] & LED_PWM_MASK;
+ if (ret == 0) {
+ /* unset current since no led is lighting */
+ pm860x_set_bits(led->i2c, led->reg_control,
+ LED_CURRENT_MASK, 0);
+ pm860x_set_bits(led->i2c, PM8606_WLED3B,
+ led->blink_mask, 0);
+ led_power_set(chip, led->port, 0);
+ }
+ }
led->current_brightness = led->brightness;
dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n",
- __led_off(led->port), led->brightness);
+ led->reg_control, led->brightness);
mutex_unlock(&led->lock);
}
@@ -153,62 +123,92 @@ static void pm860x_led_set(struct led_classdev *cdev,
schedule_work(&data->work);
}
-static int __check_device(struct pm860x_led_pdata *pdata, char *name)
+#ifdef CONFIG_OF
+static int pm860x_led_dt_init(struct platform_device *pdev,
+ struct pm860x_led *data)
{
- struct pm860x_led_pdata *p = pdata;
- int ret = -EINVAL;
-
- while (p && p->id) {
- if ((p->id != PM8606_ID_LED) || (p->flags < 0))
- break;
-
- if (!strncmp(name, pm860x_led_name[p->flags],
- MFD_NAME_SIZE)) {
- ret = (int)p->flags;
+ struct device_node *nproot, *np;
+ int iset = 0;
+
+ if (!pdev->dev.parent->of_node)
+ return -ENODEV;
+ nproot = of_get_child_by_name(pdev->dev.parent->of_node, "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)) {
+ of_property_read_u32(np, "marvell,88pm860x-iset",
+ &iset);
+ data->iset = PM8606_LED_CURRENT(iset);
break;
}
- p++;
}
- return ret;
+ of_node_put(nproot);
+ return 0;
}
+#else
+#define pm860x_led_dt_init(x, y) (-1)
+#endif
static int pm860x_led_probe(struct platform_device *pdev)
{
struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
- struct pm860x_platform_data *pm860x_pdata;
- struct pm860x_led_pdata *pdata;
+ struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev);
struct pm860x_led *data;
struct resource *res;
- int ret;
+ int ret = 0;
- res = platform_get_resource(pdev, IORESOURCE_IO, 0);
- if (res == NULL) {
- dev_err(&pdev->dev, "No I/O resource!\n");
- return -EINVAL;
- }
-
- if (pdev->dev.parent->platform_data) {
- pm860x_pdata = pdev->dev.parent->platform_data;
- pdata = pm860x_pdata->led;
- } else {
- dev_err(&pdev->dev, "No platform data!\n");
- return -EINVAL;
- }
-
- data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL);
+ data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL);
if (data == NULL)
return -ENOMEM;
- strncpy(data->name, res->name, MFD_NAME_SIZE);
- dev_set_drvdata(&pdev->dev, data);
+ res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control");
+ if (!res) {
+ dev_err(&pdev->dev, "No REG resource for control\n");
+ return -ENXIO;
+ }
+ data->reg_control = res->start;
+ res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink");
+ if (!res) {
+ dev_err(&pdev->dev, "No REG resource for blink\n");
+ return -ENXIO;
+ }
+ data->reg_blink = res->start;
+ memset(data->name, 0, MFD_NAME_SIZE);
+ switch (pdev->id) {
+ case 0:
+ data->blink_mask = LED1_BLINK_EN;
+ sprintf(data->name, "led0-red");
+ break;
+ case 1:
+ data->blink_mask = LED1_BLINK_EN;
+ sprintf(data->name, "led0-green");
+ break;
+ case 2:
+ data->blink_mask = LED1_BLINK_EN;
+ sprintf(data->name, "led0-blue");
+ break;
+ case 3:
+ data->blink_mask = LED2_BLINK_EN;
+ sprintf(data->name, "led1-red");
+ break;
+ case 4:
+ data->blink_mask = LED2_BLINK_EN;
+ sprintf(data->name, "led1-green");
+ break;
+ case 5:
+ data->blink_mask = LED2_BLINK_EN;
+ 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->iset = pdata->iset;
- data->port = __check_device(pdata, data->name);
- if (data->port < 0) {
- dev_err(&pdev->dev, "check device failed\n");
- kfree(data);
- return -EINVAL;
- }
+ data->port = pdev->id;
+ if (pm860x_led_dt_init(pdev, data))
+ if (pdata)
+ data->iset = pdata->iset;
data->current_brightness = 0;
data->cdev.name = data->name;
@@ -219,12 +219,10 @@ static int pm860x_led_probe(struct platform_device *pdev)
ret = led_classdev_register(chip->dev, &data->cdev);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
- goto out;
+ return ret;
}
+ pm860x_led_set(&data->cdev, 0);
return 0;
-out:
- kfree(data);
- return ret;
}
static int pm860x_led_remove(struct platform_device *pdev)
@@ -232,7 +230,6 @@ static int pm860x_led_remove(struct platform_device *pdev)
struct pm860x_led *data = platform_get_drvdata(pdev);
led_classdev_unregister(&data->cdev);
- kfree(data);
return 0;
}
@@ -246,17 +243,7 @@ static struct platform_driver pm860x_led_driver = {
.remove = pm860x_led_remove,
};
-static int __devinit pm860x_led_init(void)
-{
- return platform_driver_register(&pm860x_led_driver);
-}
-module_init(pm860x_led_init);
-
-static void __devexit pm860x_led_exit(void)
-{
- platform_driver_unregister(&pm860x_led_driver);
-}
-module_exit(pm860x_led_exit);
+module_platform_driver(pm860x_led_driver);
MODULE_DESCRIPTION("LED driver for Marvell PM860x");
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c
index 7ba4c7b5b97..5036d7b4f82 100644
--- a/drivers/leds/leds-adp5520.c
+++ b/drivers/leds/leds-adp5520.c
@@ -5,17 +5,16 @@
*
* Loosely derived from leds-da903x:
* Copyright (C) 2008 Compulab, Ltd.
- * Mike Rapoport <mike@compulab.co.il>
+ * Mike Rapoport <mike@compulab.co.il>
*
* Copyright (C) 2006-2008 Marvell International Ltd.
- * Eric Miao <eric.miao@marvell.com>
+ * 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>
@@ -85,9 +84,9 @@ static int adp5520_led_setup(struct adp5520_led *led)
return ret;
}
-static int __devinit adp5520_led_prepare(struct platform_device *pdev)
+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;
@@ -101,9 +100,9 @@ static int __devinit adp5520_led_prepare(struct platform_device *pdev)
return ret;
}
-static int __devinit adp5520_led_probe(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,17 +118,15 @@ static int __devinit adp5520_led_probe(struct platform_device *pdev)
return -EFAULT;
}
- led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "failed to alloc memory\n");
+ led = devm_kzalloc(&pdev->dev, sizeof(*led) * pdata->num_leds,
+ GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- }
ret = adp5520_led_prepare(pdev);
-
if (ret) {
dev_err(&pdev->dev, "failed to write\n");
- goto err_free;
+ return ret;
}
for (i = 0; i < pdata->num_leds; ++i) {
@@ -179,14 +176,12 @@ err:
}
}
-err_free:
- kfree(led);
return ret;
}
-static int __devexit adp5520_led_remove(struct platform_device *pdev)
+static int 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;
@@ -200,7 +195,6 @@ static int __devexit adp5520_led_remove(struct platform_device *pdev)
cancel_work_sync(&led[i].work);
}
- kfree(led);
return 0;
}
@@ -210,20 +204,10 @@ static struct platform_driver adp5520_led_driver = {
.owner = THIS_MODULE,
},
.probe = adp5520_led_probe,
- .remove = __devexit_p(adp5520_led_remove),
+ .remove = adp5520_led_remove,
};
-static int __init adp5520_led_init(void)
-{
- return platform_driver_register(&adp5520_led_driver);
-}
-module_init(adp5520_led_init);
-
-static void __exit adp5520_led_exit(void)
-{
- platform_driver_unregister(&adp5520_led_driver);
-}
-module_exit(adp5520_led_exit);
+module_platform_driver(adp5520_led_driver);
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
MODULE_DESCRIPTION("LEDS ADP5520(01) Driver");
diff --git a/drivers/leds/leds-alix2.c b/drivers/leds/leds-alix2.c
deleted file mode 100644
index f59ffadf512..00000000000
--- a/drivers/leds/leds-alix2.c
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * LEDs driver for PCEngines ALIX.2 and ALIX.3
- *
- * Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
- */
-
-#include <linux/err.h>
-#include <linux/io.h>
-#include <linux/kernel.h>
-#include <linux/leds.h>
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/string.h>
-#include <linux/pci.h>
-
-static int force = 0;
-module_param(force, bool, 0444);
-MODULE_PARM_DESC(force, "Assume system has ALIX.2/ALIX.3 style LEDs");
-
-#define MSR_LBAR_GPIO 0x5140000C
-#define CS5535_GPIO_SIZE 256
-
-static u32 gpio_base;
-
-static struct pci_device_id divil_pci[] = {
- { PCI_DEVICE(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_CS5535_ISA) },
- { PCI_DEVICE(PCI_VENDOR_ID_AMD, PCI_DEVICE_ID_AMD_CS5536_ISA) },
- { } /* NULL entry */
-};
-MODULE_DEVICE_TABLE(pci, divil_pci);
-
-struct alix_led {
- struct led_classdev cdev;
- unsigned short port;
- unsigned int on_value;
- unsigned int off_value;
-};
-
-static void alix_led_set(struct led_classdev *led_cdev,
- enum led_brightness brightness)
-{
- struct alix_led *led_dev =
- container_of(led_cdev, struct alix_led, cdev);
-
- if (brightness)
- outl(led_dev->on_value, gpio_base + led_dev->port);
- else
- outl(led_dev->off_value, gpio_base + led_dev->port);
-}
-
-static struct alix_led alix_leds[] = {
- {
- .cdev = {
- .name = "alix:1",
- .brightness_set = alix_led_set,
- },
- .port = 0x00,
- .on_value = 1 << 22,
- .off_value = 1 << 6,
- },
- {
- .cdev = {
- .name = "alix:2",
- .brightness_set = alix_led_set,
- },
- .port = 0x80,
- .on_value = 1 << 25,
- .off_value = 1 << 9,
- },
- {
- .cdev = {
- .name = "alix:3",
- .brightness_set = alix_led_set,
- },
- .port = 0x80,
- .on_value = 1 << 27,
- .off_value = 1 << 11,
- },
-};
-
-static int __init alix_led_probe(struct platform_device *pdev)
-{
- int i;
- int ret;
-
- for (i = 0; i < ARRAY_SIZE(alix_leds); i++) {
- alix_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
- ret = led_classdev_register(&pdev->dev, &alix_leds[i].cdev);
- if (ret < 0)
- goto fail;
- }
- return 0;
-
-fail:
- while (--i >= 0)
- led_classdev_unregister(&alix_leds[i].cdev);
- return ret;
-}
-
-static int alix_led_remove(struct platform_device *pdev)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
- led_classdev_unregister(&alix_leds[i].cdev);
- return 0;
-}
-
-static struct platform_driver alix_led_driver = {
- .remove = alix_led_remove,
- .driver = {
- .name = KBUILD_MODNAME,
- .owner = THIS_MODULE,
- },
-};
-
-static int __init alix_present(unsigned long bios_phys,
- const char *alix_sig,
- size_t alix_sig_len)
-{
- const size_t bios_len = 0x00010000;
- const char *bios_virt;
- const char *scan_end;
- const char *p;
- char name[64];
-
- if (force) {
- printk(KERN_NOTICE "%s: forced to skip BIOS test, "
- "assume system has ALIX.2 style LEDs\n",
- KBUILD_MODNAME);
- return 1;
- }
-
- bios_virt = phys_to_virt(bios_phys);
- scan_end = bios_virt + bios_len - (alix_sig_len + 2);
- for (p = bios_virt; p < scan_end; p++) {
- const char *tail;
- char *a;
-
- if (memcmp(p, alix_sig, alix_sig_len) != 0)
- continue;
-
- memcpy(name, p, sizeof(name));
-
- /* remove the first \0 character from string */
- a = strchr(name, '\0');
- if (a)
- *a = ' ';
-
- /* cut the string at a newline */
- a = strchr(name, '\r');
- if (a)
- *a = '\0';
-
- tail = p + alix_sig_len;
- if ((tail[0] == '2' || tail[0] == '3')) {
- printk(KERN_INFO
- "%s: system is recognized as \"%s\"\n",
- KBUILD_MODNAME, name);
- return 1;
- }
- }
-
- return 0;
-}
-
-static struct platform_device *pdev;
-
-static int __init alix_pci_led_init(void)
-{
- u32 low, hi;
-
- if (pci_dev_present(divil_pci) == 0) {
- printk(KERN_WARNING KBUILD_MODNAME": DIVIL not found\n");
- return -ENODEV;
- }
-
- /* Grab the GPIO I/O range */
- rdmsr(MSR_LBAR_GPIO, low, hi);
-
- /* Check the mask and whether GPIO is enabled (sanity check) */
- if (hi != 0x0000f001) {
- printk(KERN_WARNING KBUILD_MODNAME": GPIO not enabled\n");
- return -ENODEV;
- }
-
- /* Mask off the IO base address */
- gpio_base = low & 0x0000ff00;
-
- if (!request_region(gpio_base, CS5535_GPIO_SIZE, KBUILD_MODNAME)) {
- printk(KERN_ERR KBUILD_MODNAME": can't allocate I/O for GPIO\n");
- return -ENODEV;
- }
-
- /* Set GPIO function to output */
- outl(1 << 6, gpio_base + 0x04);
- outl(1 << 9, gpio_base + 0x84);
- outl(1 << 11, gpio_base + 0x84);
-
- return 0;
-}
-
-static int __init alix_led_init(void)
-{
- int ret = -ENODEV;
- const char tinybios_sig[] = "PC Engines ALIX.";
- const char coreboot_sig[] = "PC Engines\0ALIX.";
-
- if (alix_present(0xf0000, tinybios_sig, sizeof(tinybios_sig) - 1) ||
- alix_present(0x500, coreboot_sig, sizeof(coreboot_sig) - 1))
- ret = alix_pci_led_init();
-
- if (ret < 0)
- return ret;
-
- pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
- if (!IS_ERR(pdev)) {
- ret = platform_driver_probe(&alix_led_driver, alix_led_probe);
- if (ret)
- platform_device_unregister(pdev);
- } else
- ret = PTR_ERR(pdev);
-
- return ret;
-}
-
-static void __exit alix_led_exit(void)
-{
- platform_device_unregister(pdev);
- platform_driver_unregister(&alix_led_driver);
- release_region(gpio_base, CS5535_GPIO_SIZE);
-}
-
-module_init(alix_led_init);
-module_exit(alix_led_exit);
-
-MODULE_AUTHOR("Constantin Baranov <const@mimas.ru>");
-MODULE_DESCRIPTION("PCEngines ALIX.2 and ALIX.3 LED driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-ams-delta.c b/drivers/leds/leds-ams-delta.c
deleted file mode 100644
index b9826032450..00000000000
--- a/drivers/leds/leds-ams-delta.c
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * LEDs driver for Amstrad Delta (E3)
- *
- * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li>
- *
- * 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 <plat/board-ams-delta.h>
-
-/*
- * Our context
- */
-struct ams_delta_led {
- struct led_classdev cdev;
- u8 bitmask;
-};
-
-static void ams_delta_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
-{
- struct ams_delta_led *led_dev =
- container_of(led_cdev, struct ams_delta_led, cdev);
-
- if (value)
- ams_delta_latch1_write(led_dev->bitmask, led_dev->bitmask);
- else
- ams_delta_latch1_write(led_dev->bitmask, 0);
-}
-
-static struct ams_delta_led ams_delta_leds[] = {
- {
- .cdev = {
- .name = "ams-delta::camera",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_CAMERA,
- },
- {
- .cdev = {
- .name = "ams-delta::advert",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_ADVERT,
- },
- {
- .cdev = {
- .name = "ams-delta::email",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_EMAIL,
- },
- {
- .cdev = {
- .name = "ams-delta::handsfree",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_HANDSFREE,
- },
- {
- .cdev = {
- .name = "ams-delta::voicemail",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_VOICEMAIL,
- },
- {
- .cdev = {
- .name = "ams-delta::voice",
- .brightness_set = ams_delta_led_set,
- },
- .bitmask = AMS_DELTA_LATCH1_LED_VOICE,
- },
-};
-
-static int ams_delta_led_probe(struct platform_device *pdev)
-{
- int i, ret;
-
- for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) {
- ams_delta_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
- ret = led_classdev_register(&pdev->dev,
- &ams_delta_leds[i].cdev);
- if (ret < 0)
- goto fail;
- }
-
- return 0;
-fail:
- while (--i >= 0)
- led_classdev_unregister(&ams_delta_leds[i].cdev);
- return ret;
-}
-
-static int ams_delta_led_remove(struct platform_device *pdev)
-{
- int i;
-
- for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++)
- led_classdev_unregister(&ams_delta_leds[i].cdev);
-
- return 0;
-}
-
-static struct platform_driver ams_delta_led_driver = {
- .probe = ams_delta_led_probe,
- .remove = ams_delta_led_remove,
- .driver = {
- .name = "ams-delta-led",
- .owner = THIS_MODULE,
- },
-};
-
-static int __init ams_delta_led_init(void)
-{
- return platform_driver_register(&ams_delta_led_driver);
-}
-
-static void __exit ams_delta_led_exit(void)
-{
- platform_driver_unregister(&ams_delta_led_driver);
-}
-
-module_init(ams_delta_led_init);
-module_exit(ams_delta_led_exit);
-
-MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>");
-MODULE_DESCRIPTION("Amstrad Delta LED driver");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:ams-delta-led");
diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c
new file mode 100644
index 00000000000..70c74a7f0df
--- /dev/null
+++ b/drivers/leds/leds-asic3.c
@@ -0,0 +1,181 @@
+/*
+ * 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/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 = dev_get_platdata(&pdev->dev);
+ 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 = dev_get_platdata(&pdev->dev);
+
+ 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
index c941d906bba..56cec8d6a2a 100644
--- a/drivers/leds/leds-atmel-pwm.c
+++ b/drivers/leds/leds-atmel-pwm.c
@@ -4,6 +4,7 @@
#include <linux/io.h>
#include <linux/atmel_pwm.h>
#include <linux/slab.h>
+#include <linux/module.h>
struct pwmled {
@@ -34,18 +35,19 @@ static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b)
* NOTE: we reuse the platform_data structure of GPIO leds,
* but repurpose its "gpio" number as a PWM channel number.
*/
-static int __init pwmled_probe(struct platform_device *pdev)
+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;
+ pdata = dev_get_platdata(&pdev->dev);
if (!pdata || pdata->num_leds < 1)
return -ENODEV;
- leds = kcalloc(pdata->num_leds, sizeof(*leds), GFP_KERNEL);
+ leds = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*leds),
+ GFP_KERNEL);
if (!leds)
return -ENOMEM;
@@ -107,18 +109,17 @@ err:
pwm_channel_free(&leds[i].pwmc);
}
}
- kfree(leds);
return status;
}
-static int __exit pwmled_remove(struct platform_device *pdev)
+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;
+ pdata = dev_get_platdata(&pdev->dev);
leds = platform_get_drvdata(pdev);
for (i = 0; i < pdata->num_leds; i++) {
@@ -128,34 +129,21 @@ static int __exit pwmled_remove(struct platform_device *pdev)
pwm_channel_free(&led->pwmc);
}
- kfree(leds);
- platform_set_drvdata(pdev, NULL);
return 0;
}
-/* work with hotplug and coldplug */
-MODULE_ALIAS("platform:leds-atmel-pwm");
-
static struct platform_driver pwmled_driver = {
.driver = {
.name = "leds-atmel-pwm",
.owner = THIS_MODULE,
},
/* REVISIT add suspend() and resume() methods */
- .remove = __exit_p(pwmled_remove),
+ .probe = pwmled_probe,
+ .remove = pwmled_remove,
};
-static int __init modinit(void)
-{
- return platform_driver_probe(&pwmled_driver, pwmled_probe);
-}
-module_init(modinit);
-
-static void __exit modexit(void)
-{
- platform_driver_unregister(&pwmled_driver);
-}
-module_exit(modexit);
+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-bd2802.c b/drivers/leds/leds-bd2802.c
index 19dc4b61a10..6078c15d345 100644
--- a/drivers/leds/leds-bd2802.c
+++ b/drivers/leds/leds-bd2802.c
@@ -19,15 +19,15 @@
#include <linux/leds.h>
#include <linux/leds-bd2802.h>
#include <linux/slab.h>
-
+#include <linux/pm.h>
#define LED_CTL(rgb2en, rgb1en) ((rgb2en) << 4 | ((rgb1en) << 0))
#define BD2802_LED_OFFSET 0xa
#define BD2802_COLOR_OFFSET 0x3
-#define BD2802_REG_CLKSETUP 0x00
-#define BD2802_REG_CONTROL 0x01
+#define BD2802_REG_CLKSETUP 0x00
+#define BD2802_REG_CONTROL 0x01
#define BD2802_REG_HOURSETUP 0x02
#define BD2802_REG_CURRENT1SETUP 0x03
#define BD2802_REG_CURRENT2SETUP 0x04
@@ -93,7 +93,7 @@ struct bd2802_led {
* In ADF mode, user can set registers of BD2802GU directly,
* therefore BD2802GU doesn't enter reset state.
*/
- int adf_on;
+ int adf_on;
enum led_ids led_id;
enum led_colors color;
@@ -319,20 +319,6 @@ static void bd2802_turn_off(struct bd2802_led *led, enum led_ids id,
bd2802_update_state(led, id, color, BD2802_OFF);
}
-static void bd2802_restore_state(struct bd2802_led *led)
-{
- int i;
-
- for (i = 0; i < LED_NUM; i++) {
- if (led->led[i].r)
- bd2802_turn_on(led, i, RED, led->led[i].r);
- if (led->led[i].g)
- bd2802_turn_on(led, i, GREEN, led->led[i].g);
- if (led->led[i].b)
- bd2802_turn_on(led, i, BLUE, led->led[i].b);
- }
-}
-
#define BD2802_SET_REGISTER(reg_addr, reg_name) \
static ssize_t bd2802_store_reg##reg_addr(struct device *dev, \
struct device_attribute *attr, const char *buf, size_t count) \
@@ -342,7 +328,7 @@ static ssize_t bd2802_store_reg##reg_addr(struct device *dev, \
int ret; \
if (!count) \
return -EINVAL; \
- ret = strict_strtoul(buf, 16, &val); \
+ ret = kstrtoul(buf, 16, &val); \
if (ret) \
return ret; \
down_write(&led->rwsem); \
@@ -506,7 +492,7 @@ static ssize_t bd2802_store_##attr_name(struct device *dev, \
int ret; \
if (!count) \
return -EINVAL; \
- ret = strict_strtoul(buf, 16, &val); \
+ ret = kstrtoul(buf, 16, &val); \
if (ret) \
return ret; \
down_write(&led->rwsem); \
@@ -676,29 +662,31 @@ 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);
+ led_classdev_unregister(&led->cdev_led1b);
+ led_classdev_unregister(&led->cdev_led1g);
led_classdev_unregister(&led->cdev_led1r);
}
-static int __devinit bd2802_probe(struct i2c_client *client,
+static int bd2802_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct bd2802_led *led;
struct bd2802_led_platform_data *pdata;
int ret, i;
- led = kzalloc(sizeof(struct bd2802_led), GFP_KERNEL);
- if (!led) {
- dev_err(&client->dev, "failed to allocate driver data\n");
+ led = devm_kzalloc(&client->dev, sizeof(struct bd2802_led), GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- }
led->client = client;
- pdata = led->pdata = client->dev.platform_data;
+ pdata = led->pdata = dev_get_platdata(&client->dev);
i2c_set_clientdata(client, led);
/* Configure RESET GPIO (L: RESET, H: RESET cancel) */
- gpio_request(pdata->reset_gpio, "RGB_RESETB");
- gpio_direction_output(pdata->reset_gpio, 1);
+ gpio_request_one(pdata->reset_gpio, GPIOF_OUT_INIT_HIGH, "RGB_RESETB");
/* Tacss = min 0.1ms */
udelay(100);
@@ -707,7 +695,7 @@ static int __devinit bd2802_probe(struct i2c_client *client,
ret = bd2802_write_byte(client, BD2802_REG_CLKSETUP, 0x00);
if (ret < 0) {
dev_err(&client->dev, "failed to detect device\n");
- goto failed_free;
+ return ret;
} else
dev_info(&client->dev, "return 0x%02x\n", ret);
@@ -739,13 +727,10 @@ static int __devinit bd2802_probe(struct i2c_client *client,
failed_unregister_dev_file:
for (i--; i >= 0; i--)
device_remove_file(&led->client->dev, bd2802_attributes[i]);
-failed_free:
- kfree(led);
-
return ret;
}
-static int __exit bd2802_remove(struct i2c_client *client)
+static int bd2802_remove(struct i2c_client *client)
{
struct bd2802_led *led = i2c_get_clientdata(client);
int i;
@@ -756,13 +741,28 @@ static int __exit bd2802_remove(struct i2c_client *client)
bd2802_disable_adv_conf(led);
for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++)
device_remove_file(&led->client->dev, bd2802_attributes[i]);
- kfree(led);
return 0;
}
-static int bd2802_suspend(struct i2c_client *client, pm_message_t mesg)
+#ifdef CONFIG_PM_SLEEP
+static void bd2802_restore_state(struct bd2802_led *led)
+{
+ int i;
+
+ for (i = 0; i < LED_NUM; i++) {
+ if (led->led[i].r)
+ bd2802_turn_on(led, i, RED, led->led[i].r);
+ if (led->led[i].g)
+ bd2802_turn_on(led, i, GREEN, led->led[i].g);
+ if (led->led[i].b)
+ bd2802_turn_on(led, i, BLUE, led->led[i].b);
+ }
+}
+
+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);
@@ -770,8 +770,9 @@ static int bd2802_suspend(struct i2c_client *client, pm_message_t mesg)
return 0;
}
-static int bd2802_resume(struct i2c_client *client)
+static int bd2802_resume(struct device *dev)
{
+ struct i2c_client *client = to_i2c_client(dev);
struct bd2802_led *led = i2c_get_clientdata(client);
if (!bd2802_is_all_off(led) || led->adf_on) {
@@ -781,6 +782,9 @@ static int bd2802_resume(struct i2c_client *client)
return 0;
}
+#endif
+
+static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume);
static const struct i2c_device_id bd2802_id[] = {
{ "BD2802", 0 },
@@ -791,25 +795,14 @@ MODULE_DEVICE_TABLE(i2c, bd2802_id);
static struct i2c_driver bd2802_i2c_driver = {
.driver = {
.name = "BD2802",
+ .pm = &bd2802_pm,
},
.probe = bd2802_probe,
- .remove = __exit_p(bd2802_remove),
- .suspend = bd2802_suspend,
- .resume = bd2802_resume,
+ .remove = bd2802_remove,
.id_table = bd2802_id,
};
-static int __init bd2802_init(void)
-{
- return i2c_add_driver(&bd2802_i2c_driver);
-}
-module_init(bd2802_init);
-
-static void __exit bd2802_exit(void)
-{
- i2c_del_driver(&bd2802_i2c_driver);
-}
-module_exit(bd2802_exit);
+module_i2c_driver(bd2802_i2c_driver);
MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>");
MODULE_DESCRIPTION("BD2802 LED driver");
diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c
new file mode 100644
index 00000000000..d0452b099ae
--- /dev/null
+++ b/drivers/leds/leds-blinkm.c
@@ -0,0 +1,811 @@
+/*
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/jiffies.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+#include <linux/mutex.h>
+#include <linux/sysfs.h>
+#include <linux/printk.h>
+#include <linux/pm_runtime.h>
+#include <linux/leds.h>
+#include <linux/delay.h>
+
+/* Addresses to scan - BlinkM is on 0x09 by default*/
+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);
+
+struct blinkm_led {
+ struct i2c_client *i2c_client;
+ struct led_classdev led_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)
+
+struct blinkm_data {
+ struct i2c_client *i2c_client;
+ struct mutex update_lock;
+ /* used for led class interface */
+ struct blinkm_led blinkm_leds[3];
+ /* used for "blinkm" sysfs interface */
+ u8 red; /* color red */
+ u8 green; /* color green */
+ u8 blue; /* color blue */
+ /* next values to use for transfer */
+ u8 next_red; /* color red */
+ u8 next_green; /* color green */
+ u8 next_blue; /* color blue */
+ /* internal use */
+ u8 args[7]; /* set of args for transmission */
+ u8 i2c_addr; /* i2c addr */
+ u8 fw_ver; /* firmware version */
+ /* used, but not from userspace */
+ u8 hue; /* HSB hue */
+ u8 saturation; /* HSB saturation */
+ u8 brightness; /* HSB brightness */
+ u8 next_hue; /* HSB hue */
+ u8 next_saturation; /* HSB saturation */
+ u8 next_brightness; /* HSB brightness */
+ /* currently unused / todo */
+ u8 fade_speed; /* fade speed 1 - 255 */
+ s8 time_adjust; /* time adjust -128 - 127 */
+ u8 fade:1; /* fade on = 1, off = 0 */
+ u8 rand:1; /* rand fade mode on = 1 */
+ u8 script_id; /* script ID */
+ u8 script_repeats; /* repeats of script */
+ u8 script_startline; /* line to start */
+};
+
+/* Colors */
+#define RED 0
+#define GREEN 1
+#define BLUE 2
+
+/* mapping command names to cmd chars - see datasheet */
+#define BLM_GO_RGB 0
+#define BLM_FADE_RGB 1
+#define BLM_FADE_HSB 2
+#define BLM_FADE_RAND_RGB 3
+#define BLM_FADE_RAND_HSB 4
+#define BLM_PLAY_SCRIPT 5
+#define BLM_STOP_SCRIPT 6
+#define BLM_SET_FADE_SPEED 7
+#define BLM_SET_TIME_ADJ 8
+#define BLM_GET_CUR_RGB 9
+#define BLM_WRITE_SCRIPT_LINE 10
+#define BLM_READ_SCRIPT_LINE 11
+#define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */
+#define BLM_SET_ADDR 13
+#define BLM_GET_ADDR 14
+#define BLM_GET_FW_VER 15
+#define BLM_SET_STARTUP_PARAM 16
+
+/* BlinkM Commands
+ * as extracted out of the datasheet:
+ *
+ * cmdchar = command (ascii)
+ * cmdbyte = command in hex
+ * nr_args = number of arguments (to send)
+ * nr_ret = number of return values (to read)
+ * dir = direction (0 = read, 1 = write, 2 = both)
+ *
+ */
+static const struct {
+ char cmdchar;
+ u8 cmdbyte;
+ u8 nr_args;
+ u8 nr_ret;
+ u8 dir:2;
+} blinkm_cmds[17] = {
+ /* cmdchar, cmdbyte, nr_args, nr_ret, dir */
+ { 'n', 0x6e, 3, 0, 1},
+ { 'c', 0x63, 3, 0, 1},
+ { 'h', 0x68, 3, 0, 1},
+ { 'C', 0x43, 3, 0, 1},
+ { 'H', 0x48, 3, 0, 1},
+ { 'p', 0x70, 3, 0, 1},
+ { 'o', 0x6f, 0, 0, 1},
+ { 'f', 0x66, 1, 0, 1},
+ { 't', 0x74, 1, 0, 1},
+ { 'g', 0x67, 0, 3, 0},
+ { 'W', 0x57, 7, 0, 1},
+ { 'R', 0x52, 2, 5, 2},
+ { 'L', 0x4c, 3, 0, 1},
+ { 'A', 0x41, 4, 0, 1},
+ { 'a', 0x61, 0, 1, 0},
+ { 'Z', 0x5a, 0, 1, 0},
+ { 'B', 0x42, 5, 0, 1},
+};
+
+static ssize_t show_color_common(struct device *dev, char *buf, int color)
+{
+ struct i2c_client *client;
+ struct blinkm_data *data;
+ int ret;
+
+ client = to_i2c_client(dev);
+ data = i2c_get_clientdata(client);
+
+ ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB);
+ if (ret < 0)
+ return ret;
+ switch (color) {
+ case RED:
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red);
+ case GREEN:
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green);
+ case BLUE:
+ return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue);
+ default:
+ return -EINVAL;
+ }
+ return -EINVAL;
+}
+
+static int store_color_common(struct device *dev, const char *buf, int color)
+{
+ struct i2c_client *client;
+ struct blinkm_data *data;
+ int ret;
+ u8 value;
+
+ client = to_i2c_client(dev);
+ data = i2c_get_clientdata(client);
+
+ ret = kstrtou8(buf, 10, &value);
+ if (ret < 0) {
+ dev_err(dev, "BlinkM: value too large!\n");
+ return ret;
+ }
+
+ switch (color) {
+ case RED:
+ data->next_red = value;
+ break;
+ case GREEN:
+ data->next_green = value;
+ break;
+ case BLUE:
+ data->next_blue = value;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n",
+ data->next_red, data->next_green, data->next_blue);
+
+ /* if mode ... */
+ ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+ if (ret < 0) {
+ dev_err(dev, "BlinkM: can't set RGB\n");
+ return ret;
+ }
+ return 0;
+}
+
+static ssize_t show_red(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,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = store_color_common(dev, buf, RED);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+static DEVICE_ATTR(red, S_IRUGO | S_IWUSR, show_red, store_red);
+
+static ssize_t show_green(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,
+ const char *buf, size_t count)
+{
+
+ int ret;
+
+ ret = store_color_common(dev, buf, GREEN);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+static DEVICE_ATTR(green, S_IRUGO | S_IWUSR, show_green, store_green);
+
+static ssize_t show_blue(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,
+ const char *buf, size_t count)
+{
+ int ret;
+
+ ret = store_color_common(dev, buf, BLUE);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+static DEVICE_ATTR(blue, S_IRUGO | S_IWUSR, show_blue, store_blue);
+
+static ssize_t show_test(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ return scnprintf(buf, PAGE_SIZE,
+ "#Write into test to start test sequence!#\n");
+}
+
+static ssize_t store_test(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+
+ struct i2c_client *client;
+ int ret;
+ client = to_i2c_client(dev);
+
+ /*test */
+ ret = blinkm_test_run(client);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(test, S_IRUGO | S_IWUSR, show_test, store_test);
+
+/* TODO: HSB, fade, timeadj, script ... */
+
+static struct attribute *blinkm_attrs[] = {
+ &dev_attr_red.attr,
+ &dev_attr_green.attr,
+ &dev_attr_blue.attr,
+ &dev_attr_test.attr,
+ NULL,
+};
+
+static struct attribute_group blinkm_group = {
+ .name = "blinkm",
+ .attrs = blinkm_attrs,
+};
+
+static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg)
+{
+ int result;
+ int i;
+ int arglen = blinkm_cmds[cmd].nr_args;
+ /* write out cmd to blinkm - always / default step */
+ result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte);
+ if (result < 0)
+ return result;
+ /* no args to write out */
+ if (arglen == 0)
+ return 0;
+
+ for (i = 0; i < arglen; i++) {
+ /* repeat for arglen */
+ result = i2c_smbus_write_byte(client, arg[i]);
+ if (result < 0)
+ return result;
+ }
+ return 0;
+}
+
+static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg)
+{
+ int result;
+ int i;
+ int retlen = blinkm_cmds[cmd].nr_ret;
+ for (i = 0; i < retlen; i++) {
+ /* repeat for retlen */
+ result = i2c_smbus_read_byte(client);
+ if (result < 0)
+ return result;
+ arg[i] = result;
+ }
+
+ return 0;
+}
+
+static int blinkm_transfer_hw(struct i2c_client *client, int cmd)
+{
+ /* the protocol is simple but non-standard:
+ * e.g. cmd 'g' (= 0x67) for "get device address"
+ * - which defaults to 0x09 - would be the sequence:
+ * a) write 0x67 to the device (byte write)
+ * b) read the value (0x09) back right after (byte read)
+ *
+ * Watch out for "unfinished" sequences (i.e. not enough reads
+ * or writes after a command. It will make the blinkM misbehave.
+ * Sequence is key here.
+ */
+
+ /* args / return are in private data struct */
+ struct blinkm_data *data = i2c_get_clientdata(client);
+
+ /* We start hardware transfers which are not to be
+ * mixed with other commands. Aquire a lock now. */
+ if (mutex_lock_interruptible(&data->update_lock) < 0)
+ return -EAGAIN;
+
+ /* switch cmd - usually write before reads */
+ switch (cmd) {
+ case BLM_FADE_RAND_RGB:
+ case BLM_GO_RGB:
+ case BLM_FADE_RGB:
+ data->args[0] = data->next_red;
+ data->args[1] = data->next_green;
+ data->args[2] = data->next_blue;
+ blinkm_write(client, cmd, data->args);
+ data->red = data->args[0];
+ data->green = data->args[1];
+ data->blue = data->args[2];
+ break;
+ case BLM_FADE_HSB:
+ case BLM_FADE_RAND_HSB:
+ data->args[0] = data->next_hue;
+ data->args[1] = data->next_saturation;
+ data->args[2] = data->next_brightness;
+ blinkm_write(client, cmd, data->args);
+ data->hue = data->next_hue;
+ data->saturation = data->next_saturation;
+ data->brightness = data->next_brightness;
+ break;
+ case BLM_PLAY_SCRIPT:
+ data->args[0] = data->script_id;
+ data->args[1] = data->script_repeats;
+ data->args[2] = data->script_startline;
+ blinkm_write(client, cmd, data->args);
+ break;
+ case BLM_STOP_SCRIPT:
+ blinkm_write(client, cmd, NULL);
+ break;
+ case BLM_GET_CUR_RGB:
+ data->args[0] = data->red;
+ data->args[1] = data->green;
+ data->args[2] = data->blue;
+ blinkm_write(client, cmd, NULL);
+ blinkm_read(client, cmd, data->args);
+ data->red = data->args[0];
+ data->green = data->args[1];
+ data->blue = data->args[2];
+ break;
+ case BLM_GET_ADDR:
+ data->args[0] = data->i2c_addr;
+ blinkm_write(client, cmd, NULL);
+ blinkm_read(client, cmd, data->args);
+ data->i2c_addr = data->args[0];
+ break;
+ case BLM_SET_TIME_ADJ:
+ case BLM_SET_FADE_SPEED:
+ case BLM_READ_SCRIPT_LINE:
+ case BLM_WRITE_SCRIPT_LINE:
+ case BLM_SET_SCRIPT_LR:
+ case BLM_SET_ADDR:
+ case BLM_GET_FW_VER:
+ case BLM_SET_STARTUP_PARAM:
+ dev_err(&client->dev,
+ "BlinkM: cmd %d not implemented yet.\n", cmd);
+ break;
+ default:
+ dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd);
+ mutex_unlock(&data->update_lock);
+ return -EINVAL;
+ } /* end switch(cmd) */
+
+ /* transfers done, unlock */
+ mutex_unlock(&data->update_lock);
+ return 0;
+}
+
+static void led_work(struct work_struct *work)
+{
+ 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);
+}
+
+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_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;
+
+ default:
+ dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n");
+ return -EINVAL;
+ }
+
+ bl_work = kzalloc(sizeof(*bl_work), GFP_ATOMIC);
+ if (!bl_work)
+ return -ENOMEM;
+
+ atomic_inc(&led->active);
+ dev_dbg(&led->i2c_client->dev,
+ "#TO_SCHED# 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));
+
+ /* 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);
+
+ return 0;
+}
+
+static void blinkm_led_red_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ blinkm_led_common_set(led_cdev, value, RED);
+}
+
+static void blinkm_led_green_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ blinkm_led_common_set(led_cdev, value, GREEN);
+}
+
+static void blinkm_led_blue_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ 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);
+}
+
+static int blinkm_test_run(struct i2c_client *client)
+{
+ int ret;
+ struct blinkm_data *data = i2c_get_clientdata(client);
+
+ data->next_red = 0x01;
+ data->next_green = 0x05;
+ data->next_blue = 0x10;
+ ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+ if (ret < 0)
+ return ret;
+ msleep(2000);
+
+ data->next_red = 0x25;
+ data->next_green = 0x10;
+ data->next_blue = 0x31;
+ ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+ if (ret < 0)
+ return ret;
+ msleep(2000);
+
+ data->next_hue = 0x50;
+ data->next_saturation = 0x10;
+ data->next_brightness = 0x20;
+ ret = blinkm_transfer_hw(client, BLM_FADE_HSB);
+ if (ret < 0)
+ return ret;
+ msleep(2000);
+
+ return 0;
+}
+
+/* Return 0 if detection is successful, -ENODEV otherwise */
+static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info)
+{
+ struct i2c_adapter *adapter = client->adapter;
+ int ret;
+ int count = 99;
+ u8 tmpargs[7];
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA
+ | I2C_FUNC_SMBUS_WORD_DATA
+ | I2C_FUNC_SMBUS_WRITE_BYTE))
+ return -ENODEV;
+
+ /* Now, we do the remaining detection. Simple for now. */
+ /* We might need more guards to protect other i2c slaves */
+
+ /* make sure the blinkM is balanced (read/writes) */
+ while (count > 0) {
+ ret = blinkm_write(client, BLM_GET_ADDR, NULL);
+ usleep_range(5000, 10000);
+ ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);
+ usleep_range(5000, 10000);
+ if (tmpargs[0] == 0x09)
+ count = 0;
+ count--;
+ }
+
+ /* Step 1: Read BlinkM address back - cmd_char 'a' */
+ ret = blinkm_write(client, BLM_GET_ADDR, NULL);
+ if (ret < 0)
+ return ret;
+ usleep_range(20000, 30000); /* allow a small delay */
+ ret = blinkm_read(client, BLM_GET_ADDR, tmpargs);
+ if (ret < 0)
+ return ret;
+
+ if (tmpargs[0] != 0x09) {
+ dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]);
+ return -ENODEV;
+ }
+
+ strlcpy(info->type, "blinkm", I2C_NAME_SIZE);
+ return 0;
+}
+
+static int blinkm_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct blinkm_data *data;
+ struct blinkm_led *led[3];
+ int err, i;
+ 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++) {
+ /* 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);
+ switch (i) {
+ case RED:
+ snprintf(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;
+ err = led_classdev_register(&client->dev,
+ &led[i]->led_cdev);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't register LED %s\n",
+ led[i]->led_cdev.name);
+ goto failred;
+ }
+ break;
+ case GREEN:
+ snprintf(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;
+ err = led_classdev_register(&client->dev,
+ &led[i]->led_cdev);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't register LED %s\n",
+ led[i]->led_cdev.name);
+ goto failgreen;
+ }
+ break;
+ case BLUE:
+ snprintf(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;
+ err = led_classdev_register(&client->dev,
+ &led[i]->led_cdev);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "couldn't register LED %s\n",
+ led[i]->led_cdev.name);
+ goto failblue;
+ }
+ break;
+ } /* end switch */
+ } /* end for */
+
+ /* Initialize the blinkm */
+ blinkm_init_hw(client);
+
+ return 0;
+
+failblue:
+ led_classdev_unregister(&led[GREEN]->led_cdev);
+
+failgreen:
+ led_classdev_unregister(&led[RED]->led_cdev);
+
+failred:
+ sysfs_remove_group(&client->dev.kobj, &blinkm_group);
+exit:
+ return err;
+}
+
+static int 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);
+ }
+
+ /* reset rgb */
+ data->next_red = 0x00;
+ data->next_green = 0x00;
+ data->next_blue = 0x00;
+ ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+ if (ret < 0)
+ dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");
+
+ /* reset hsb */
+ data->next_hue = 0x00;
+ data->next_saturation = 0x00;
+ data->next_brightness = 0x00;
+ ret = blinkm_transfer_hw(client, BLM_FADE_HSB);
+ if (ret < 0)
+ dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");
+
+ /* red fade to off */
+ data->next_red = 0xff;
+ ret = blinkm_transfer_hw(client, BLM_GO_RGB);
+ if (ret < 0)
+ dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n");
+
+ /* off */
+ data->next_red = 0x00;
+ ret = blinkm_transfer_hw(client, BLM_FADE_RGB);
+ if (ret < 0)
+ 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},
+ {}
+};
+
+MODULE_DEVICE_TABLE(i2c, blinkm_id);
+
+ /* This is the driver that will be inserted */
+static struct i2c_driver blinkm_driver = {
+ .class = I2C_CLASS_HWMON,
+ .driver = {
+ .name = "blinkm",
+ },
+ .probe = blinkm_probe,
+ .remove = blinkm_remove,
+ .id_table = blinkm_id,
+ .detect = blinkm_detect,
+ .address_list = normal_i2c,
+};
+
+module_i2c_driver(blinkm_driver);
+
+MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>");
+MODULE_DESCRIPTION("BlinkM RGB LED driver");
+MODULE_LICENSE("GPL");
+
diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c
index a498135a4e8..f58a354428e 100644
--- a/drivers/leds/leds-clevo-mail.c
+++ b/drivers/leds/leds-clevo-mail.c
@@ -1,3 +1,4 @@
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
@@ -18,7 +19,7 @@ MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>");
MODULE_DESCRIPTION("Clevo mail LED driver");
MODULE_LICENSE("GPL");
-static unsigned int __initdata nodetect;
+static bool nodetect;
module_param_named(nodetect, nodetect, bool, 0);
MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection");
@@ -26,12 +27,12 @@ static struct platform_device *pdev;
static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id)
{
- printk(KERN_INFO KBUILD_MODNAME ": '%s' found\n", id->ident);
+ pr_info("'%s' found\n", id->ident);
return 1;
}
/*
- * struct mail_led_whitelist - List of known good models
+ * struct clevo_mail_led_dmi_table - List of known good models
*
* Contains the known good models this driver is compatible with.
* When adding a new model try to be as strict as possible. This
@@ -39,7 +40,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 mail_led_whitelist[] = {
+static struct dmi_system_id clevo_mail_led_dmi_table[] __initdata = {
{
.callback = clevo_mail_led_dmi_callback,
.ident = "Clevo D410J",
@@ -59,11 +60,10 @@ static struct dmi_system_id __initdata mail_led_whitelist[] = {
},
{
.callback = clevo_mail_led_dmi_callback,
- .ident = "Positivo Mobile",
+ .ident = "Clevo M5x0V",
.matches = {
DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "),
DMI_MATCH(DMI_BOARD_NAME, "M5X0V "),
- DMI_MATCH(DMI_PRODUCT_NAME, "Positivo Mobile"),
DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198")
}
},
@@ -89,6 +89,7 @@ static struct dmi_system_id __initdata mail_led_whitelist[] = {
},
{ }
};
+MODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table);
static void clevo_mail_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
@@ -135,8 +136,7 @@ static int clevo_mail_led_blink(struct led_classdev *led_cdev,
status = 0;
} else {
- printk(KERN_DEBUG KBUILD_MODNAME
- ": clevo_mail_led_blink(..., %lu, %lu),"
+ pr_debug("clevo_mail_led_blink(..., %lu, %lu),"
" returning -EINVAL (unsupported)\n",
*delay_on, *delay_off);
}
@@ -153,7 +153,7 @@ static struct led_classdev clevo_mail_led = {
.flags = LED_CORE_SUSPENDRESUME,
};
-static int __devinit 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);
}
@@ -165,7 +165,6 @@ static int clevo_mail_led_remove(struct platform_device *pdev)
}
static struct platform_driver clevo_mail_led_driver = {
- .probe = clevo_mail_led_probe,
.remove = clevo_mail_led_remove,
.driver = {
.name = KBUILD_MODNAME,
@@ -180,10 +179,10 @@ static int __init clevo_mail_led_init(void)
/* Check with the help of DMI if we are running on supported hardware */
if (!nodetect) {
- count = dmi_check_system(mail_led_whitelist);
+ count = dmi_check_system(clevo_mail_led_dmi_table);
} else {
count = 1;
- printk(KERN_ERR KBUILD_MODNAME ": Skipping DMI detection. "
+ pr_err("Skipping DMI detection. "
"If the driver works on your hardware please "
"report model and the output of dmidecode in tracker "
"at http://sourceforge.net/projects/clevo-mailled/\n");
@@ -197,8 +196,7 @@ static int __init clevo_mail_led_init(void)
error = platform_driver_probe(&clevo_mail_led_driver,
clevo_mail_led_probe);
if (error) {
- printk(KERN_ERR KBUILD_MODNAME
- ": Can't probe platform driver\n");
+ pr_err("Can't probe platform driver\n");
platform_device_unregister(pdev);
}
} else
diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c
index da5fb016b1a..910339d86ed 100644
--- a/drivers/leds/leds-cobalt-qube.c
+++ b/drivers/leds/leds-cobalt-qube.c
@@ -3,7 +3,6 @@
*
* Control the Cobalt Qube/RaQ front LED
*/
-#include <linux/init.h>
#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/leds.h>
@@ -34,7 +33,7 @@ static struct led_classdev qube_front_led = {
.default_trigger = "default-on",
};
-static int __devinit cobalt_qube_led_probe(struct platform_device *pdev)
+static int cobalt_qube_led_probe(struct platform_device *pdev)
{
struct resource *res;
int retval;
@@ -43,7 +42,7 @@ static int __devinit cobalt_qube_led_probe(struct platform_device *pdev)
if (!res)
return -EBUSY;
- led_port = ioremap(res->start, resource_size(res));
+ led_port = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!led_port)
return -ENOMEM;
@@ -52,54 +51,38 @@ static int __devinit cobalt_qube_led_probe(struct platform_device *pdev)
retval = led_classdev_register(&pdev->dev, &qube_front_led);
if (retval)
- goto err_iounmap;
+ goto err_null;
return 0;
-err_iounmap:
- iounmap(led_port);
+err_null:
led_port = NULL;
return retval;
}
-static int __devexit cobalt_qube_led_remove(struct platform_device *pdev)
+static int cobalt_qube_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&qube_front_led);
- if (led_port) {
- iounmap(led_port);
+ if (led_port)
led_port = NULL;
- }
return 0;
}
-/* work with hotplug and coldplug */
-MODULE_ALIAS("platform:cobalt-qube-leds");
-
static struct platform_driver cobalt_qube_led_driver = {
.probe = cobalt_qube_led_probe,
- .remove = __devexit_p(cobalt_qube_led_remove),
+ .remove = cobalt_qube_led_remove,
.driver = {
.name = "cobalt-qube-leds",
.owner = THIS_MODULE,
},
};
-static int __init cobalt_qube_led_init(void)
-{
- return platform_driver_register(&cobalt_qube_led_driver);
-}
-
-static void __exit cobalt_qube_led_exit(void)
-{
- platform_driver_unregister(&cobalt_qube_led_driver);
-}
-
-module_init(cobalt_qube_led_init);
-module_exit(cobalt_qube_led_exit);
+module_platform_driver(cobalt_qube_led_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Front LED support for Cobalt Server");
MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>");
+MODULE_ALIAS("platform:cobalt-qube-leds");
diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c
index 438d4838463..001088b3137 100644
--- a/drivers/leds/leds-cobalt-raq.c
+++ b/drivers/leds/leds-cobalt-raq.c
@@ -24,6 +24,7 @@
#include <linux/platform_device.h>
#include <linux/spinlock.h>
#include <linux/types.h>
+#include <linux/export.h>
#define LED_WEB 0x04
#define LED_POWER_OFF 0x08
@@ -75,7 +76,7 @@ static struct led_classdev raq_power_off_led = {
.default_trigger = "power-off",
};
-static int __devinit cobalt_raq_led_probe(struct platform_device *pdev)
+static int cobalt_raq_led_probe(struct platform_device *pdev)
{
struct resource *res;
int retval;
@@ -84,13 +85,13 @@ static int __devinit cobalt_raq_led_probe(struct platform_device *pdev)
if (!res)
return -EBUSY;
- led_port = ioremap(res->start, resource_size(res));
+ led_port = devm_ioremap(&pdev->dev, res->start, resource_size(res));
if (!led_port)
return -ENOMEM;
retval = led_classdev_register(&pdev->dev, &raq_power_off_led);
if (retval)
- goto err_iounmap;
+ goto err_null;
retval = led_classdev_register(&pdev->dev, &raq_web_led);
if (retval)
@@ -101,29 +102,26 @@ static int __devinit cobalt_raq_led_probe(struct platform_device *pdev)
err_unregister:
led_classdev_unregister(&raq_power_off_led);
-err_iounmap:
- iounmap(led_port);
+err_null:
led_port = NULL;
return retval;
}
-static int __devexit cobalt_raq_led_remove(struct platform_device *pdev)
+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) {
- iounmap(led_port);
+ if (led_port)
led_port = NULL;
- }
return 0;
}
static struct platform_driver cobalt_raq_led_driver = {
.probe = cobalt_raq_led_probe,
- .remove = __devexit_p(cobalt_raq_led_remove),
+ .remove = cobalt_raq_led_remove,
.driver = {
.name = "cobalt-raq-leds",
.owner = THIS_MODULE,
diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c
index f28931cf678..54b8b5216b8 100644
--- a/drivers/leds/leds-da903x.c
+++ b/drivers/leds/leds-da903x.c
@@ -2,10 +2,10 @@
* LEDs driver for Dialog Semiconductor DA9030/DA9034
*
* Copyright (C) 2008 Compulab, Ltd.
- * Mike Rapoport <mike@compulab.co.il>
+ * Mike Rapoport <mike@compulab.co.il>
*
* Copyright (C) 2006-2008 Marvell International Ltd.
- * Eric Miao <eric.miao@marvell.com>
+ * 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
@@ -14,7 +14,6 @@
#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>
@@ -85,15 +84,15 @@ 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);
}
-static int __devinit da903x_led_probe(struct platform_device *pdev)
+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;
@@ -108,11 +107,9 @@ static int __devinit da903x_led_probe(struct platform_device *pdev)
return -EINVAL;
}
- led = kzalloc(sizeof(struct da903x_led), GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id);
+ led = devm_kzalloc(&pdev->dev, sizeof(struct da903x_led), GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- }
led->cdev.name = pdata->name;
led->cdev.default_trigger = pdata->default_trigger;
@@ -129,23 +126,18 @@ static int __devinit da903x_led_probe(struct platform_device *pdev)
ret = led_classdev_register(led->master, &led->cdev);
if (ret) {
dev_err(&pdev->dev, "failed to register LED %d\n", id);
- goto err;
+ return ret;
}
platform_set_drvdata(pdev, led);
return 0;
-
-err:
- kfree(led);
- return ret;
}
-static int __devexit da903x_led_remove(struct platform_device *pdev)
+static int da903x_led_remove(struct platform_device *pdev)
{
struct da903x_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
- kfree(led);
return 0;
}
@@ -155,23 +147,13 @@ static struct platform_driver da903x_led_driver = {
.owner = THIS_MODULE,
},
.probe = da903x_led_probe,
- .remove = __devexit_p(da903x_led_remove),
+ .remove = da903x_led_remove,
};
-static int __init da903x_led_init(void)
-{
- return platform_driver_register(&da903x_led_driver);
-}
-module_init(da903x_led_init);
-
-static void __exit da903x_led_exit(void)
-{
- platform_driver_unregister(&da903x_led_driver);
-}
-module_exit(da903x_led_exit);
+module_platform_driver(da903x_led_driver);
MODULE_DESCRIPTION("LEDs driver for Dialog Semiconductor DA9030/DA9034");
-MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"
- "Mike Rapoport <mike@compulab.co.il>");
+MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
+MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:da903x-led");
diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c
new file mode 100644
index 00000000000..e4da1f460ac
--- /dev/null
+++ b/drivers/leds/leds-da9052.c
@@ -0,0 +1,212 @@
+/*
+ * 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/platform_device.h>
+#include <linux/leds.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/da9052/reg.h>
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/pdata.h>
+
+#define DA9052_OPENDRAIN_OUTPUT 2
+#define DA9052_SET_HIGH_LVL_OUTPUT (1 << 3)
+#define DA9052_MASK_UPPER_NIBBLE 0xF0
+#define DA9052_MASK_LOWER_NIBBLE 0x0F
+#define DA9052_NIBBLE_SHIFT 4
+#define DA9052_MAX_BRIGHTNESS 0x5f
+
+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[] = {
+ DA9052_LED_CONT_4_REG,
+ DA9052_LED_CONT_5_REG,
+};
+
+static int da9052_set_led_brightness(struct da9052_led *led)
+{
+ u8 val;
+ int error;
+
+ val = (led->brightness & 0x7f) | DA9052_LED_CONT_DIM;
+
+ error = da9052_reg_write(led->da9052, led_reg[led->led_index], val);
+ if (error < 0)
+ dev_err(led->da9052->dev, "Failed to set led brightness, %d\n",
+ error);
+ 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,
+ enum led_brightness value)
+{
+ struct da9052_led *led;
+
+ led = container_of(led_cdev, struct da9052_led, cdev);
+ led->brightness = value;
+ schedule_work(&led->work);
+}
+
+static int da9052_configure_leds(struct da9052 *da9052)
+{
+ int error;
+ unsigned char register_value = DA9052_OPENDRAIN_OUTPUT
+ | DA9052_SET_HIGH_LVL_OUTPUT;
+
+ error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG,
+ DA9052_MASK_LOWER_NIBBLE,
+ register_value);
+
+ if (error < 0) {
+ dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n",
+ error);
+ return error;
+ }
+
+ error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG,
+ DA9052_MASK_UPPER_NIBBLE,
+ register_value << DA9052_NIBBLE_SHIFT);
+ if (error < 0)
+ dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n",
+ error);
+
+ return error;
+}
+
+static int da9052_led_probe(struct platform_device *pdev)
+{
+ struct da9052_pdata *pdata;
+ struct da9052 *da9052;
+ struct led_platform_data *pled;
+ struct da9052_led *led = NULL;
+ int error = -ENODEV;
+ int i;
+
+ da9052 = dev_get_drvdata(pdev->dev.parent);
+ pdata = dev_get_platdata(da9052->dev);
+ if (pdata == NULL) {
+ dev_err(&pdev->dev, "No platform data\n");
+ goto err;
+ }
+
+ pled = pdata->pled;
+ if (pled == NULL) {
+ dev_err(&pdev->dev, "No platform data for LED\n");
+ goto err;
+ }
+
+ led = devm_kzalloc(&pdev->dev,
+ sizeof(struct da9052_led) * pled->num_leds,
+ GFP_KERNEL);
+ 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 = 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) {
+ dev_err(&pdev->dev, "Failed to register led %d\n",
+ led[i].led_index);
+ goto err_register;
+ }
+
+ error = da9052_set_led_brightness(&led[i]);
+ if (error) {
+ dev_err(&pdev->dev, "Unable to init led %d\n",
+ led[i].led_index);
+ continue;
+ }
+ }
+ error = da9052_configure_leds(led->da9052);
+ if (error) {
+ dev_err(&pdev->dev, "Failed to configure GPIO LED%d\n", error);
+ goto err_register;
+ }
+
+ platform_set_drvdata(pdev, led);
+
+ return 0;
+
+err_register:
+ 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)
+{
+ struct da9052_led *led = platform_get_drvdata(pdev);
+ struct da9052_pdata *pdata;
+ struct da9052 *da9052;
+ struct led_platform_data *pled;
+ int i;
+
+ da9052 = dev_get_drvdata(pdev->dev.parent);
+ 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]);
+ 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,
+};
+
+module_platform_driver(da9052_led_driver);
+
+MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>");
+MODULE_DESCRIPTION("LED driver for Dialog DA9052 PMIC");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
index 31cf0d60a9a..db3ba8b4251 100644
--- a/drivers/leds/leds-dac124s085.c
+++ b/drivers/leds/leds-dac124s085.c
@@ -69,7 +69,7 @@ static int dac124s085_probe(struct spi_device *spi)
struct dac124s085_led *led;
int i, ret;
- dac = kzalloc(sizeof(*dac), GFP_KERNEL);
+ dac = devm_kzalloc(&spi->dev, sizeof(*dac), GFP_KERNEL);
if (!dac)
return -ENOMEM;
@@ -101,8 +101,6 @@ eledcr:
while (i--)
led_classdev_unregister(&dac->leds[i].ldev);
- spi_set_drvdata(spi, NULL);
- kfree(dac);
return ret;
}
@@ -116,9 +114,6 @@ static int dac124s085_remove(struct spi_device *spi)
cancel_work_sync(&dac->leds[i].work);
}
- spi_set_drvdata(spi, NULL);
- kfree(dac);
-
return 0;
}
@@ -131,18 +126,7 @@ static struct spi_driver dac124s085_driver = {
},
};
-static int __init dac124s085_leds_init(void)
-{
- return spi_register_driver(&dac124s085_driver);
-}
-
-static void __exit dac124s085_leds_exit(void)
-{
- spi_unregister_driver(&dac124s085_driver);
-}
-
-module_init(dac124s085_leds_init);
-module_exit(dac124s085_leds_exit);
+module_spi_driver(dac124s085_driver);
MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>");
MODULE_DESCRIPTION("DAC124S085 LED driver");
diff --git a/drivers/leds/leds-fsg.c b/drivers/leds/leds-fsg.c
index d11d05be0de..2b4dc738dcd 100644
--- a/drivers/leds/leds-fsg.c
+++ b/drivers/leds/leds-fsg.c
@@ -16,11 +16,11 @@
*/
#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>
-#include <asm/io.h>
#define FSG_LED_WLAN_BIT 0
#define FSG_LED_WAN_BIT 1
@@ -148,11 +148,10 @@ static int fsg_led_probe(struct platform_device *pdev)
int ret;
/* Map the LED chip select address space */
- latch_address = (unsigned short *) ioremap(IXP4XX_EXP_BUS_BASE(2), 512);
- if (!latch_address) {
- ret = -ENOMEM;
- goto failremap;
- }
+ 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;
@@ -194,8 +193,6 @@ static int fsg_led_probe(struct platform_device *pdev)
failwan:
led_classdev_unregister(&fsg_wlan_led);
failwlan:
- iounmap(latch_address);
- failremap:
return ret;
}
@@ -209,8 +206,6 @@ static int fsg_led_remove(struct platform_device *pdev)
led_classdev_unregister(&fsg_sync_led);
led_classdev_unregister(&fsg_ring_led);
- iounmap(latch_address);
-
return 0;
}
@@ -223,20 +218,7 @@ static struct platform_driver fsg_led_driver = {
},
};
-
-static int __init fsg_led_init(void)
-{
- return platform_driver_register(&fsg_led_driver);
-}
-
-static void __exit fsg_led_exit(void)
-{
- platform_driver_unregister(&fsg_led_driver);
-}
-
-
-module_init(fsg_led_init);
-module_exit(fsg_led_exit);
+module_platform_driver(fsg_led_driver);
MODULE_AUTHOR("Rod Whitby <rod@whitby.id.au>");
MODULE_DESCRIPTION("Freecom FSG-3 LED driver");
diff --git a/drivers/leds/leds-gpio-register.c b/drivers/leds/leds-gpio-register.c
new file mode 100644
index 00000000000..1c4ed5510f3
--- /dev/null
+++ b/drivers/leds/leds-gpio-register.c
@@ -0,0 +1,42 @@
+/*
+ * 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/platform_device.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+
+/**
+ * gpio_led_register_device - register a gpio-led device
+ * @pdata: the platform data used for the new device
+ *
+ * Makes a copy of pdata and pdata->leds and registers a new leds-gpio device
+ * with the result. This allows to have pdata and pdata-leds in .init.rodata
+ * and so saves some bytes compared to a static struct platform_device with
+ * static platform data.
+ *
+ * Returns the registered device or an error pointer.
+ */
+struct platform_device *__init gpio_led_register_device(
+ int id, const struct gpio_led_platform_data *pdata)
+{
+ struct platform_device *ret;
+ struct gpio_led_platform_data _pdata = *pdata;
+
+ _pdata.leds = kmemdup(pdata->leds,
+ pdata->num_leds * sizeof(*pdata->leds), GFP_KERNEL);
+ if (!_pdata.leds)
+ return ERR_PTR(-ENOMEM);
+
+ ret = platform_device_register_resndata(NULL, "leds-gpio", id,
+ NULL, 0, &_pdata, sizeof(_pdata));
+ if (IS_ERR(ret))
+ kfree(_pdata.leds);
+
+ return ret;
+}
diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c
index 4d9fa38d9ff..57ff20fecf5 100644
--- a/drivers/leds/leds-gpio.c
+++ b/drivers/leds/leds-gpio.c
@@ -11,13 +11,16 @@
*
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
+#include <linux/gpio.h>
#include <linux/leds.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
-
-#include <asm/gpio.h>
+#include <linux/module.h>
+#include <linux/err.h>
struct gpio_led_data {
struct led_classdev cdev;
@@ -88,7 +91,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev,
delay_on, delay_off);
}
-static int __devinit create_gpio_led(const struct gpio_led *template,
+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 *))
{
@@ -98,12 +101,12 @@ static int __devinit create_gpio_led(const struct gpio_led *template,
/* skip leds that aren't available */
if (!gpio_is_valid(template->gpio)) {
- printk(KERN_INFO "Skipping unavailable LED gpio %d (%s)\n",
+ dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
template->gpio, template->name);
return 0;
}
- ret = gpio_request(template->gpio, template->name);
+ ret = devm_gpio_request(parent, template->gpio, template->name);
if (ret < 0)
return ret;
@@ -119,7 +122,7 @@ static int __devinit create_gpio_led(const struct gpio_led *template,
}
led_dat->cdev.brightness_set = gpio_led_set;
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
- state = !!gpio_get_value(led_dat->gpio) ^ led_dat->active_low;
+ state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
else
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
@@ -128,18 +131,15 @@ static int __devinit create_gpio_led(const struct gpio_led *template,
ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
if (ret < 0)
- goto err;
-
+ return ret;
+
INIT_WORK(&led_dat->work, gpio_led_work);
ret = led_classdev_register(parent, &led_dat->cdev);
if (ret < 0)
- goto err;
+ return ret;
return 0;
-err:
- gpio_free(led_dat->gpio);
- return ret;
}
static void delete_gpio_led(struct gpio_led_data *led)
@@ -148,101 +148,42 @@ static void delete_gpio_led(struct gpio_led_data *led)
return;
led_classdev_unregister(&led->cdev);
cancel_work_sync(&led->work);
- gpio_free(led->gpio);
}
-#ifdef CONFIG_LEDS_GPIO_PLATFORM
-static int __devinit gpio_led_probe(struct platform_device *pdev)
-{
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
- struct gpio_led_data *leds_data;
- int i, ret = 0;
-
- if (!pdata)
- return -EBUSY;
-
- leds_data = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds,
- GFP_KERNEL);
- if (!leds_data)
- return -ENOMEM;
-
- for (i = 0; i < pdata->num_leds; i++) {
- ret = create_gpio_led(&pdata->leds[i], &leds_data[i],
- &pdev->dev, pdata->gpio_blink_set);
- if (ret < 0)
- goto err;
- }
-
- platform_set_drvdata(pdev, leds_data);
-
- return 0;
-
-err:
- for (i = i - 1; i >= 0; i--)
- delete_gpio_led(&leds_data[i]);
-
- kfree(leds_data);
-
- return ret;
-}
+struct gpio_leds_priv {
+ int num_leds;
+ struct gpio_led_data leds[];
+};
-static int __devexit gpio_led_remove(struct platform_device *pdev)
+static inline int sizeof_gpio_leds_priv(int num_leds)
{
- int i;
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
- struct gpio_led_data *leds_data;
-
- leds_data = platform_get_drvdata(pdev);
-
- for (i = 0; i < pdata->num_leds; i++)
- delete_gpio_led(&leds_data[i]);
-
- kfree(leds_data);
-
- return 0;
+ return sizeof(struct gpio_leds_priv) +
+ (sizeof(struct gpio_led_data) * num_leds);
}
-static struct platform_driver gpio_led_driver = {
- .probe = gpio_led_probe,
- .remove = __devexit_p(gpio_led_remove),
- .driver = {
- .name = "leds-gpio",
- .owner = THIS_MODULE,
- },
-};
-
-MODULE_ALIAS("platform:leds-gpio");
-#endif /* CONFIG_LEDS_GPIO_PLATFORM */
-
/* Code to create from OpenFirmware platform devices */
-#ifdef CONFIG_LEDS_GPIO_OF
-#include <linux/of_platform.h>
-#include <linux/of_gpio.h>
-
-struct gpio_led_of_platform_data {
- int num_leds;
- struct gpio_led_data led_data[];
-};
-
-static int __devinit of_gpio_leds_probe(struct platform_device *ofdev,
- const struct of_device_id *match)
+#ifdef CONFIG_OF_GPIO
+static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
{
- struct device_node *np = ofdev->dev.of_node, *child;
- struct gpio_led_of_platform_data *pdata;
- int count = 0, ret;
+ struct device_node *np = pdev->dev.of_node, *child;
+ struct gpio_leds_priv *priv;
+ int count, ret;
- /* count LEDs defined by this device, so we know how much to allocate */
- for_each_child_of_node(np, child)
- count++;
+ /* count LEDs in this device, so we know how much to allocate */
+ count = of_get_available_child_count(np);
if (!count)
- return 0; /* or ENODEV? */
+ return ERR_PTR(-ENODEV);
+
+ for_each_available_child_of_node(np, child)
+ if (of_get_gpio(child, 0) == -EPROBE_DEFER)
+ return ERR_PTR(-EPROBE_DEFER);
- pdata = kzalloc(sizeof(*pdata) + sizeof(struct gpio_led_data) * count,
+ priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
GFP_KERNEL);
- if (!pdata)
- return -ENOMEM;
+ if (!priv)
+ return ERR_PTR(-ENOMEM);
- for_each_child_of_node(np, child) {
+ for_each_available_child_of_node(np, child) {
struct gpio_led led = {};
enum of_gpio_flags flags;
const char *state;
@@ -256,97 +197,106 @@ static int __devinit of_gpio_leds_probe(struct platform_device *ofdev,
if (state) {
if (!strcmp(state, "keep"))
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
- else if(!strcmp(state, "on"))
+ 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, &pdata->led_data[pdata->num_leds++],
- &ofdev->dev, NULL);
+ if (of_get_property(child, "retain-state-suspended", NULL))
+ led.retain_state_suspended = 1;
+
+ ret = create_gpio_led(&led, &priv->leds[priv->num_leds++],
+ &pdev->dev, NULL);
if (ret < 0) {
of_node_put(child);
goto err;
}
}
- dev_set_drvdata(&ofdev->dev, pdata);
-
- return 0;
+ return priv;
err:
- for (count = pdata->num_leds - 2; count >= 0; count--)
- delete_gpio_led(&pdata->led_data[count]);
+ for (count = priv->num_leds - 2; count >= 0; count--)
+ delete_gpio_led(&priv->leds[count]);
+ return ERR_PTR(-ENODEV);
+}
- kfree(pdata);
+static const struct of_device_id of_gpio_leds_match[] = {
+ { .compatible = "gpio-leds", },
+ {},
+};
- return ret;
+MODULE_DEVICE_TABLE(of, of_gpio_leds_match);
+#else /* CONFIG_OF_GPIO */
+static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
+{
+ return ERR_PTR(-ENODEV);
}
+#endif /* CONFIG_OF_GPIO */
-static int __devexit of_gpio_leds_remove(struct platform_device *ofdev)
+
+static int gpio_led_probe(struct platform_device *pdev)
{
- struct gpio_led_of_platform_data *pdata = dev_get_drvdata(&ofdev->dev);
- int i;
+ struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct gpio_leds_priv *priv;
+ int i, ret = 0;
- for (i = 0; i < pdata->num_leds; i++)
- delete_gpio_led(&pdata->led_data[i]);
- kfree(pdata);
+ if (pdata && pdata->num_leds) {
+ priv = devm_kzalloc(&pdev->dev,
+ sizeof_gpio_leds_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_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;
+ }
+ }
+ } else {
+ priv = gpio_leds_create_of(pdev);
+ if (IS_ERR(priv))
+ return PTR_ERR(priv);
+ }
- dev_set_drvdata(&ofdev->dev, NULL);
+ platform_set_drvdata(pdev, priv);
return 0;
}
-static const struct of_device_id of_gpio_leds_match[] = {
- { .compatible = "gpio-leds", },
- {},
-};
-
-static struct of_platform_driver of_gpio_leds_driver = {
- .driver = {
- .name = "of_gpio_leds",
- .owner = THIS_MODULE,
- .of_match_table = of_gpio_leds_match,
- },
- .probe = of_gpio_leds_probe,
- .remove = __devexit_p(of_gpio_leds_remove),
-};
-#endif
-
-static int __init gpio_led_init(void)
+static int gpio_led_remove(struct platform_device *pdev)
{
- int ret = 0;
+ struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
+ int i;
-#ifdef CONFIG_LEDS_GPIO_PLATFORM
- ret = platform_driver_register(&gpio_led_driver);
- if (ret)
- return ret;
-#endif
-#ifdef CONFIG_LEDS_GPIO_OF
- ret = of_register_platform_driver(&of_gpio_leds_driver);
-#endif
-#ifdef CONFIG_LEDS_GPIO_PLATFORM
- if (ret)
- platform_driver_unregister(&gpio_led_driver);
-#endif
-
- return ret;
-}
+ for (i = 0; i < priv->num_leds; i++)
+ delete_gpio_led(&priv->leds[i]);
-static void __exit gpio_led_exit(void)
-{
-#ifdef CONFIG_LEDS_GPIO_PLATFORM
- platform_driver_unregister(&gpio_led_driver);
-#endif
-#ifdef CONFIG_LEDS_GPIO_OF
- of_unregister_platform_driver(&of_gpio_leds_driver);
-#endif
+ return 0;
}
-module_init(gpio_led_init);
-module_exit(gpio_led_exit);
+static struct platform_driver gpio_led_driver = {
+ .probe = gpio_led_probe,
+ .remove = gpio_led_remove,
+ .driver = {
+ .name = "leds-gpio",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(of_gpio_leds_match),
+ },
+};
+
+module_platform_driver(gpio_led_driver);
MODULE_AUTHOR("Raphael Assenat <raph@8d.com>, Trent Piepho <tpiepho@freescale.com>");
MODULE_DESCRIPTION("GPIO LED driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-gpio");
diff --git a/drivers/leds/leds-h1940.c b/drivers/leds/leds-h1940.c
deleted file mode 100644
index 173d104d9ff..00000000000
--- a/drivers/leds/leds-h1940.c
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * drivers/leds/leds-h1940.c
- * Copyright (c) Arnaud Patard <arnaud.patard@rtp-net.org>
- *
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file COPYING in the main directory of this archive for
- * more details.
- *
- * H1940 leds driver
- *
- */
-
-#include <linux/module.h>
-#include <linux/platform_device.h>
-#include <linux/delay.h>
-#include <linux/string.h>
-#include <linux/ctype.h>
-#include <linux/leds.h>
-#include <linux/gpio.h>
-
-#include <mach/regs-gpio.h>
-#include <mach/hardware.h>
-#include <mach/h1940-latch.h>
-
-/*
- * Green led.
- */
-static void h1940_greenled_set(struct led_classdev *led_dev,
- enum led_brightness value)
-{
- switch (value) {
- case LED_HALF:
- h1940_latch_control(0, H1940_LATCH_LED_FLASH);
- s3c2410_gpio_setpin(S3C2410_GPA7, 1);
- break;
- case LED_FULL:
- h1940_latch_control(0, H1940_LATCH_LED_GREEN);
- s3c2410_gpio_setpin(S3C2410_GPA7, 1);
- break;
- default:
- case LED_OFF:
- h1940_latch_control(H1940_LATCH_LED_FLASH, 0);
- h1940_latch_control(H1940_LATCH_LED_GREEN, 0);
- s3c2410_gpio_setpin(S3C2410_GPA7, 0);
- break;
- }
-}
-
-static struct led_classdev h1940_greenled = {
- .name = "h1940:green",
- .brightness_set = h1940_greenled_set,
- .default_trigger = "h1940-charger",
-};
-
-/*
- * Red led.
- */
-static void h1940_redled_set(struct led_classdev *led_dev,
- enum led_brightness value)
-{
- switch (value) {
- case LED_HALF:
- h1940_latch_control(0, H1940_LATCH_LED_FLASH);
- s3c2410_gpio_setpin(S3C2410_GPA1, 1);
- break;
- case LED_FULL:
- h1940_latch_control(0, H1940_LATCH_LED_RED);
- s3c2410_gpio_setpin(S3C2410_GPA1, 1);
- break;
- default:
- case LED_OFF:
- h1940_latch_control(H1940_LATCH_LED_FLASH, 0);
- h1940_latch_control(H1940_LATCH_LED_RED, 0);
- s3c2410_gpio_setpin(S3C2410_GPA1, 0);
- break;
- }
-}
-
-static struct led_classdev h1940_redled = {
- .name = "h1940:red",
- .brightness_set = h1940_redled_set,
- .default_trigger = "h1940-charger",
-};
-
-/*
- * Blue led.
- * (it can only be blue flashing led)
- */
-static void h1940_blueled_set(struct led_classdev *led_dev,
- enum led_brightness value)
-{
- if (value) {
- /* flashing Blue */
- h1940_latch_control(0, H1940_LATCH_LED_FLASH);
- s3c2410_gpio_setpin(S3C2410_GPA3, 1);
- } else {
- h1940_latch_control(H1940_LATCH_LED_FLASH, 0);
- s3c2410_gpio_setpin(S3C2410_GPA3, 0);
- }
-
-}
-
-static struct led_classdev h1940_blueled = {
- .name = "h1940:blue",
- .brightness_set = h1940_blueled_set,
- .default_trigger = "h1940-bluetooth",
-};
-
-static int __devinit h1940leds_probe(struct platform_device *pdev)
-{
- int ret;
-
- ret = led_classdev_register(&pdev->dev, &h1940_greenled);
- if (ret)
- goto err_green;
-
- ret = led_classdev_register(&pdev->dev, &h1940_redled);
- if (ret)
- goto err_red;
-
- ret = led_classdev_register(&pdev->dev, &h1940_blueled);
- if (ret)
- goto err_blue;
-
- return 0;
-
-err_blue:
- led_classdev_unregister(&h1940_redled);
-err_red:
- led_classdev_unregister(&h1940_greenled);
-err_green:
- return ret;
-}
-
-static int h1940leds_remove(struct platform_device *pdev)
-{
- led_classdev_unregister(&h1940_greenled);
- led_classdev_unregister(&h1940_redled);
- led_classdev_unregister(&h1940_blueled);
- return 0;
-}
-
-
-static struct platform_driver h1940leds_driver = {
- .driver = {
- .name = "h1940-leds",
- .owner = THIS_MODULE,
- },
- .probe = h1940leds_probe,
- .remove = h1940leds_remove,
-};
-
-
-static int __init h1940leds_init(void)
-{
- return platform_driver_register(&h1940leds_driver);
-}
-
-static void __exit h1940leds_exit(void)
-{
- platform_driver_unregister(&h1940leds_driver);
-}
-
-module_init(h1940leds_init);
-module_exit(h1940leds_exit);
-
-MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>");
-MODULE_DESCRIPTION("LED driver for the iPAQ H1940");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:h1940-leds");
diff --git a/drivers/leds/leds-hp6xx.c b/drivers/leds/leds-hp6xx.c
index e4ce1fd4633..d61a98896c7 100644
--- a/drivers/leds/leds-hp6xx.c
+++ b/drivers/leds/leds-hp6xx.c
@@ -10,8 +10,8 @@
* 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>
@@ -78,9 +78,6 @@ static int hp6xxled_remove(struct platform_device *pdev)
return 0;
}
-/* work with hotplug and coldplug */
-MODULE_ALIAS("platform:hp6xx-led");
-
static struct platform_driver hp6xxled_driver = {
.probe = hp6xxled_probe,
.remove = hp6xxled_remove,
@@ -90,19 +87,9 @@ static struct platform_driver hp6xxled_driver = {
},
};
-static int __init hp6xxled_init(void)
-{
- return platform_driver_register(&hp6xxled_driver);
-}
-
-static void __exit hp6xxled_exit(void)
-{
- platform_driver_unregister(&hp6xxled_driver);
-}
-
-module_init(hp6xxled_init);
-module_exit(hp6xxled_exit);
+module_platform_driver(hp6xxled_driver);
MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>");
MODULE_DESCRIPTION("HP Jornada 6xx LED driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:hp6xx-led");
diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c
new file mode 100644
index 00000000000..652368c2ea9
--- /dev/null
+++ b/drivers/leds/leds-lm3530.c
@@ -0,0 +1,509 @@
+/*
+ * 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>
+ * based on leds-lm3530.c by Dan Murphy <D.Murphy@motorola.com>
+ */
+
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/input.h>
+#include <linux/led-lm3530.h>
+#include <linux/types.h>
+#include <linux/regulator/consumer.h>
+#include <linux/module.h>
+
+#define LM3530_LED_DEV "lcd-backlight"
+#define LM3530_NAME "lm3530-led"
+
+#define LM3530_GEN_CONFIG 0x10
+#define LM3530_ALS_CONFIG 0x20
+#define LM3530_BRT_RAMP_RATE 0x30
+#define LM3530_ALS_IMP_SELECT 0x41
+#define LM3530_BRT_CTRL_REG 0xA0
+#define LM3530_ALS_ZB0_REG 0x60
+#define LM3530_ALS_ZB1_REG 0x61
+#define LM3530_ALS_ZB2_REG 0x62
+#define LM3530_ALS_ZB3_REG 0x63
+#define LM3530_ALS_Z0T_REG 0x70
+#define LM3530_ALS_Z1T_REG 0x71
+#define LM3530_ALS_Z2T_REG 0x72
+#define LM3530_ALS_Z3T_REG 0x73
+#define LM3530_ALS_Z4T_REG 0x74
+#define LM3530_REG_MAX 14
+
+/* General Control Register */
+#define LM3530_EN_I2C_SHIFT (0)
+#define LM3530_RAMP_LAW_SHIFT (1)
+#define LM3530_MAX_CURR_SHIFT (2)
+#define LM3530_EN_PWM_SHIFT (5)
+#define LM3530_PWM_POL_SHIFT (6)
+#define LM3530_EN_PWM_SIMPLE_SHIFT (7)
+
+#define LM3530_ENABLE_I2C (1 << LM3530_EN_I2C_SHIFT)
+#define LM3530_ENABLE_PWM (1 << LM3530_EN_PWM_SHIFT)
+#define LM3530_POL_LOW (1 << LM3530_PWM_POL_SHIFT)
+#define LM3530_ENABLE_PWM_SIMPLE (1 << LM3530_EN_PWM_SIMPLE_SHIFT)
+
+/* ALS Config Register Options */
+#define LM3530_ALS_AVG_TIME_SHIFT (0)
+#define LM3530_EN_ALS_SHIFT (3)
+#define LM3530_ALS_SEL_SHIFT (5)
+
+#define LM3530_ENABLE_ALS (3 << LM3530_EN_ALS_SHIFT)
+
+/* Brightness Ramp Rate Register */
+#define LM3530_BRT_RAMP_FALL_SHIFT (0)
+#define LM3530_BRT_RAMP_RISE_SHIFT (3)
+
+/* ALS Resistor Select */
+#define LM3530_ALS1_IMP_SHIFT (0)
+#define LM3530_ALS2_IMP_SHIFT (4)
+
+/* Zone Boundary Register defaults */
+#define LM3530_ALS_ZB_MAX (4)
+#define LM3530_ALS_WINDOW_mV (1000)
+#define LM3530_ALS_OFFSET_mV (4)
+
+/* Zone Target Register defaults */
+#define LM3530_DEF_ZT_0 (0x7F)
+#define LM3530_DEF_ZT_1 (0x66)
+#define LM3530_DEF_ZT_2 (0x4C)
+#define LM3530_DEF_ZT_3 (0x33)
+#define LM3530_DEF_ZT_4 (0x19)
+
+/* 7 bits are used for the brightness : LM3530_BRT_CTRL_REG */
+#define MAX_BRIGHTNESS (127)
+
+struct lm3530_mode_map {
+ const char *mode;
+ enum lm3530_mode mode_val;
+};
+
+static struct lm3530_mode_map mode_map[] = {
+ { "man", LM3530_BL_MODE_MANUAL },
+ { "als", LM3530_BL_MODE_ALS },
+ { "pwm", LM3530_BL_MODE_PWM },
+};
+
+/**
+ * struct lm3530_data
+ * @led_dev: led class device
+ * @client: i2c client
+ * @pdata: LM3530 platform data
+ * @mode: mode of operation - manual, ALS, PWM
+ * @regulator: regulator
+ * @brighness: previous brightness value
+ * @enable: regulator is enabled
+ */
+struct lm3530_data {
+ struct led_classdev led_dev;
+ struct i2c_client *client;
+ struct lm3530_platform_data *pdata;
+ enum lm3530_mode mode;
+ struct regulator *regulator;
+ enum led_brightness brightness;
+ bool enable;
+};
+
+/*
+ * struct lm3530_als_data
+ * @config : value of ALS configuration register
+ * @imp_sel : value of ALS resistor select register
+ * @zone : values of ALS ZB(Zone Boundary) registers
+ */
+struct lm3530_als_data {
+ u8 config;
+ u8 imp_sel;
+ u8 zones[LM3530_ALS_ZB_MAX];
+};
+
+static const u8 lm3530_reg[LM3530_REG_MAX] = {
+ LM3530_GEN_CONFIG,
+ LM3530_ALS_CONFIG,
+ LM3530_BRT_RAMP_RATE,
+ LM3530_ALS_IMP_SELECT,
+ LM3530_BRT_CTRL_REG,
+ LM3530_ALS_ZB0_REG,
+ LM3530_ALS_ZB1_REG,
+ LM3530_ALS_ZB2_REG,
+ LM3530_ALS_ZB3_REG,
+ LM3530_ALS_Z0T_REG,
+ LM3530_ALS_Z1T_REG,
+ LM3530_ALS_Z2T_REG,
+ LM3530_ALS_Z3T_REG,
+ LM3530_ALS_Z4T_REG,
+};
+
+static int lm3530_get_mode_from_str(const char *str)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mode_map); i++)
+ if (sysfs_streq(str, mode_map[i].mode))
+ return mode_map[i].mode_val;
+
+ return -EINVAL;
+}
+
+static void lm3530_als_configure(struct lm3530_platform_data *pdata,
+ struct lm3530_als_data *als)
+{
+ int i;
+ u32 als_vmin, als_vmax, als_vstep;
+
+ if (pdata->als_vmax == 0) {
+ pdata->als_vmin = 0;
+ pdata->als_vmax = LM3530_ALS_WINDOW_mV;
+ }
+
+ als_vmin = pdata->als_vmin;
+ als_vmax = pdata->als_vmax;
+
+ if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV)
+ pdata->als_vmax = als_vmax = als_vmin + LM3530_ALS_WINDOW_mV;
+
+ /* n zone boundary makes n+1 zones */
+ als_vstep = (als_vmax - als_vmin) / (LM3530_ALS_ZB_MAX + 1);
+
+ for (i = 0; i < LM3530_ALS_ZB_MAX; i++)
+ als->zones[i] = (((als_vmin + LM3530_ALS_OFFSET_mV) +
+ als_vstep + (i * als_vstep)) * LED_FULL) / 1000;
+
+ als->config =
+ (pdata->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) |
+ (LM3530_ENABLE_ALS) |
+ (pdata->als_input_mode << LM3530_ALS_SEL_SHIFT);
+
+ als->imp_sel =
+ (pdata->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) |
+ (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT);
+}
+
+static int lm3530_led_enable(struct lm3530_data *drvdata)
+{
+ int ret;
+
+ if (drvdata->enable)
+ return 0;
+
+ ret = regulator_enable(drvdata->regulator);
+ if (ret) {
+ dev_err(drvdata->led_dev.dev, "Failed to enable vin:%d\n", ret);
+ return ret;
+ }
+
+ drvdata->enable = true;
+ return 0;
+}
+
+static void lm3530_led_disable(struct lm3530_data *drvdata)
+{
+ int ret;
+
+ if (!drvdata->enable)
+ return;
+
+ ret = regulator_disable(drvdata->regulator);
+ if (ret) {
+ dev_err(drvdata->led_dev.dev, "Failed to disable vin:%d\n",
+ ret);
+ return;
+ }
+
+ drvdata->enable = false;
+}
+
+static int lm3530_init_registers(struct lm3530_data *drvdata)
+{
+ int ret = 0;
+ int i;
+ u8 gen_config;
+ u8 brt_ramp;
+ u8 brightness;
+ u8 reg_val[LM3530_REG_MAX];
+ struct lm3530_platform_data *pdata = drvdata->pdata;
+ struct i2c_client *client = drvdata->client;
+ struct lm3530_pwm_data *pwm = &pdata->pwm_data;
+ struct lm3530_als_data als;
+
+ memset(&als, 0, sizeof(struct lm3530_als_data));
+
+ gen_config = (pdata->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) |
+ ((pdata->max_current & 7) << LM3530_MAX_CURR_SHIFT);
+
+ switch (drvdata->mode) {
+ case LM3530_BL_MODE_MANUAL:
+ gen_config |= LM3530_ENABLE_I2C;
+ break;
+ case LM3530_BL_MODE_ALS:
+ gen_config |= LM3530_ENABLE_I2C;
+ lm3530_als_configure(pdata, &als);
+ break;
+ case LM3530_BL_MODE_PWM:
+ gen_config |= LM3530_ENABLE_PWM | LM3530_ENABLE_PWM_SIMPLE |
+ (pdata->pwm_pol_hi << LM3530_PWM_POL_SHIFT);
+ break;
+ }
+
+ brt_ramp = (pdata->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) |
+ (pdata->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT);
+
+ if (drvdata->brightness)
+ brightness = drvdata->brightness;
+ else
+ brightness = drvdata->brightness = pdata->brt_val;
+
+ if (brightness > drvdata->led_dev.max_brightness)
+ brightness = drvdata->led_dev.max_brightness;
+
+ reg_val[0] = gen_config; /* LM3530_GEN_CONFIG */
+ reg_val[1] = als.config; /* LM3530_ALS_CONFIG */
+ reg_val[2] = brt_ramp; /* LM3530_BRT_RAMP_RATE */
+ reg_val[3] = als.imp_sel; /* LM3530_ALS_IMP_SELECT */
+ reg_val[4] = brightness; /* LM3530_BRT_CTRL_REG */
+ reg_val[5] = als.zones[0]; /* LM3530_ALS_ZB0_REG */
+ reg_val[6] = als.zones[1]; /* LM3530_ALS_ZB1_REG */
+ reg_val[7] = als.zones[2]; /* LM3530_ALS_ZB2_REG */
+ reg_val[8] = als.zones[3]; /* LM3530_ALS_ZB3_REG */
+ reg_val[9] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */
+ reg_val[10] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */
+ reg_val[11] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */
+ reg_val[12] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */
+ reg_val[13] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */
+
+ ret = lm3530_led_enable(drvdata);
+ if (ret)
+ return ret;
+
+ for (i = 0; i < LM3530_REG_MAX; i++) {
+ /* do not update brightness register when pwm mode */
+ if (lm3530_reg[i] == LM3530_BRT_CTRL_REG &&
+ drvdata->mode == LM3530_BL_MODE_PWM) {
+ if (pwm->pwm_set_intensity)
+ pwm->pwm_set_intensity(reg_val[i],
+ drvdata->led_dev.max_brightness);
+ continue;
+ }
+
+ ret = i2c_smbus_write_byte_data(client,
+ lm3530_reg[i], reg_val[i]);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static void lm3530_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brt_val)
+{
+ int err;
+ struct lm3530_data *drvdata =
+ container_of(led_cdev, struct lm3530_data, led_dev);
+ struct lm3530_platform_data *pdata = drvdata->pdata;
+ struct lm3530_pwm_data *pwm = &pdata->pwm_data;
+ u8 max_brightness = led_cdev->max_brightness;
+
+ switch (drvdata->mode) {
+ case LM3530_BL_MODE_MANUAL:
+
+ if (!drvdata->enable) {
+ err = lm3530_init_registers(drvdata);
+ if (err) {
+ dev_err(&drvdata->client->dev,
+ "Register Init failed: %d\n", err);
+ break;
+ }
+ }
+
+ /* set the brightness in brightness control register*/
+ err = i2c_smbus_write_byte_data(drvdata->client,
+ LM3530_BRT_CTRL_REG, brt_val);
+ if (err)
+ dev_err(&drvdata->client->dev,
+ "Unable to set brightness: %d\n", err);
+ else
+ drvdata->brightness = brt_val;
+
+ if (brt_val == 0)
+ lm3530_led_disable(drvdata);
+ break;
+ case LM3530_BL_MODE_ALS:
+ break;
+ case LM3530_BL_MODE_PWM:
+ if (pwm->pwm_set_intensity)
+ pwm->pwm_set_intensity(brt_val, max_brightness);
+ break;
+ default:
+ break;
+ }
+}
+
+static ssize_t lm3530_mode_get(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3530_data *drvdata;
+ int i, len = 0;
+
+ drvdata = container_of(led_cdev, struct lm3530_data, led_dev);
+ for (i = 0; i < ARRAY_SIZE(mode_map); i++)
+ if (drvdata->mode == mode_map[i].mode_val)
+ len += sprintf(buf + len, "[%s] ", mode_map[i].mode);
+ else
+ len += sprintf(buf + len, "%s ", mode_map[i].mode);
+
+ len += sprintf(buf + len, "\n");
+
+ return len;
+}
+
+static ssize_t lm3530_mode_set(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;
+ struct lm3530_pwm_data *pwm;
+ u8 max_brightness;
+ int mode, err;
+
+ drvdata = container_of(led_cdev, struct lm3530_data, led_dev);
+ pwm = &drvdata->pdata->pwm_data;
+ max_brightness = led_cdev->max_brightness;
+ mode = lm3530_get_mode_from_str(buf);
+ if (mode < 0) {
+ dev_err(dev, "Invalid mode\n");
+ return mode;
+ }
+
+ drvdata->mode = mode;
+
+ /* set pwm to low if unnecessary */
+ if (mode != LM3530_BL_MODE_PWM && pwm->pwm_set_intensity)
+ pwm->pwm_set_intensity(0, max_brightness);
+
+ err = lm3530_init_registers(drvdata);
+ if (err) {
+ dev_err(dev, "Setting %s Mode failed :%d\n", buf, err);
+ return err;
+ }
+
+ return sizeof(drvdata->mode);
+}
+static DEVICE_ATTR(mode, 0644, lm3530_mode_get, lm3530_mode_set);
+
+static int lm3530_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lm3530_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct lm3530_data *drvdata;
+ int err = 0;
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "platform data required\n");
+ return -ENODEV;
+ }
+
+ /* BL mode */
+ if (pdata->mode > LM3530_BL_MODE_PWM) {
+ dev_err(&client->dev, "Illegal Mode request\n");
+ return -EINVAL;
+ }
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "I2C_FUNC_I2C not supported\n");
+ return -EIO;
+ }
+
+ drvdata = devm_kzalloc(&client->dev, sizeof(struct lm3530_data),
+ GFP_KERNEL);
+ if (drvdata == NULL)
+ return -ENOMEM;
+
+ drvdata->mode = pdata->mode;
+ drvdata->client = client;
+ drvdata->pdata = pdata;
+ drvdata->brightness = LED_OFF;
+ drvdata->enable = false;
+ drvdata->led_dev.name = LM3530_LED_DEV;
+ drvdata->led_dev.brightness_set = lm3530_brightness_set;
+ drvdata->led_dev.max_brightness = MAX_BRIGHTNESS;
+
+ i2c_set_clientdata(client, drvdata);
+
+ drvdata->regulator = devm_regulator_get(&client->dev, "vin");
+ if (IS_ERR(drvdata->regulator)) {
+ dev_err(&client->dev, "regulator get failed\n");
+ err = PTR_ERR(drvdata->regulator);
+ drvdata->regulator = NULL;
+ return err;
+ }
+
+ if (drvdata->pdata->brt_val) {
+ err = lm3530_init_registers(drvdata);
+ if (err < 0) {
+ dev_err(&client->dev,
+ "Register Init failed: %d\n", err);
+ return err;
+ }
+ }
+ err = led_classdev_register(&client->dev, &drvdata->led_dev);
+ if (err < 0) {
+ dev_err(&client->dev, "Register led class failed: %d\n", err);
+ 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)
+{
+ 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},
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, lm3530_id);
+
+static struct i2c_driver lm3530_i2c_driver = {
+ .probe = lm3530_probe,
+ .remove = lm3530_remove,
+ .id_table = lm3530_id,
+ .driver = {
+ .name = LM3530_NAME,
+ .owner = THIS_MODULE,
+ },
+};
+
+module_i2c_driver(lm3530_i2c_driver);
+
+MODULE_DESCRIPTION("Back Light driver for LM3530");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Shreshtha Kumar SAHU <shreshthakumar.sahu@stericsson.com>");
diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c
new file mode 100644
index 00000000000..e2c642c1169
--- /dev/null
+++ b/drivers/leds/leds-lm3533.c
@@ -0,0 +1,784 @@
+/*
+ * 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/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>
+
+
+#define LM3533_LVCTRLBANK_MIN 2
+#define LM3533_LVCTRLBANK_MAX 5
+#define LM3533_LVCTRLBANK_COUNT 4
+#define LM3533_RISEFALLTIME_MAX 7
+#define LM3533_ALS_CHANNEL_LV_MIN 1
+#define LM3533_ALS_CHANNEL_LV_MAX 2
+
+#define LM3533_REG_CTRLBANK_BCONF_BASE 0x1b
+#define LM3533_REG_PATTERN_ENABLE 0x28
+#define LM3533_REG_PATTERN_LOW_TIME_BASE 0x71
+#define LM3533_REG_PATTERN_HIGH_TIME_BASE 0x72
+#define LM3533_REG_PATTERN_RISETIME_BASE 0x74
+#define LM3533_REG_PATTERN_FALLTIME_BASE 0x75
+
+#define LM3533_REG_PATTERN_STEP 0x10
+
+#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK 0x04
+#define LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK 0x02
+#define LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK 0x01
+
+#define LM3533_LED_FLAG_PATTERN_ENABLE 1
+
+
+struct lm3533_led {
+ struct lm3533 *lm3533;
+ struct lm3533_ctrlbank cb;
+ struct led_classdev cdev;
+ int id;
+
+ struct mutex mutex;
+ unsigned long flags;
+
+ struct work_struct work;
+ u8 new_brightness;
+};
+
+
+static inline struct lm3533_led *to_lm3533_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct lm3533_led, cdev);
+}
+
+static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led)
+{
+ return led->id + 2;
+}
+
+static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base)
+{
+ return base + led->id;
+}
+
+static inline u8 lm3533_led_get_pattern(struct lm3533_led *led)
+{
+ return led->id;
+}
+
+static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led,
+ u8 base)
+{
+ return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP;
+}
+
+static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable)
+{
+ u8 mask;
+ u8 val;
+ int pattern;
+ int state;
+ int ret = 0;
+
+ dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable);
+
+ mutex_lock(&led->mutex);
+
+ state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+ if ((enable && state) || (!enable && !state))
+ goto out;
+
+ pattern = lm3533_led_get_pattern(led);
+ mask = 1 << (2 * pattern);
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask);
+ if (ret) {
+ dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n",
+ pattern, enable);
+ goto out;
+ }
+
+ __change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags);
+out:
+ mutex_unlock(&led->mutex);
+
+ 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,
+ 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);
+}
+
+static enum led_brightness lm3533_led_get(struct led_classdev *cdev)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_brightness(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val);
+
+ return val;
+}
+
+/* Pattern generator defines (delays in us). */
+#define LM3533_LED_DELAY1_VMIN 0x00
+#define LM3533_LED_DELAY2_VMIN 0x3d
+#define LM3533_LED_DELAY3_VMIN 0x80
+
+#define LM3533_LED_DELAY1_VMAX (LM3533_LED_DELAY2_VMIN - 1)
+#define LM3533_LED_DELAY2_VMAX (LM3533_LED_DELAY3_VMIN - 1)
+#define LM3533_LED_DELAY3_VMAX 0xff
+
+#define LM3533_LED_DELAY1_TMIN 16384U
+#define LM3533_LED_DELAY2_TMIN 1130496U
+#define LM3533_LED_DELAY3_TMIN 10305536U
+
+#define LM3533_LED_DELAY1_TMAX 999424U
+#define LM3533_LED_DELAY2_TMAX 9781248U
+#define LM3533_LED_DELAY3_TMAX 76890112U
+
+/* t_step = (t_max - t_min) / (v_max - v_min) */
+#define LM3533_LED_DELAY1_TSTEP 16384
+#define LM3533_LED_DELAY2_TSTEP 131072
+#define LM3533_LED_DELAY3_TSTEP 524288
+
+/* Delay limits for hardware accelerated blinking (in ms). */
+#define LM3533_LED_DELAY_ON_MAX \
+ ((LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY2_TSTEP / 2) / 1000)
+#define LM3533_LED_DELAY_OFF_MAX \
+ ((LM3533_LED_DELAY3_TMAX + LM3533_LED_DELAY3_TSTEP / 2) / 1000)
+
+/*
+ * Returns linear map of *t from [t_min,t_max] to [v_min,v_max] with a step
+ * size of t_step, where
+ *
+ * t_step = (t_max - t_min) / (v_max - v_min)
+ *
+ * and updates *t to reflect the mapped value.
+ */
+static u8 time_to_val(unsigned *t, unsigned t_min, unsigned t_step,
+ u8 v_min, u8 v_max)
+{
+ unsigned val;
+
+ val = (*t + t_step / 2 - t_min) / t_step + v_min;
+
+ *t = t_step * (val - v_min) + t_min;
+
+ return (u8)val;
+}
+
+/*
+ * Returns time code corresponding to *delay (in ms) and updates *delay to
+ * reflect actual hardware delay.
+ *
+ * Hardware supports 256 discrete delay times, divided into three groups with
+ * the following ranges and step-sizes:
+ *
+ * [ 16, 999] [0x00, 0x3e] step 16 ms
+ * [ 1130, 9781] [0x3d, 0x7f] step 131 ms
+ * [10306, 76890] [0x80, 0xff] step 524 ms
+ *
+ * Note that delay group 3 is only available for delay_off.
+ */
+static u8 lm3533_led_get_hw_delay(unsigned *delay)
+{
+ unsigned t;
+ u8 val;
+
+ t = *delay * 1000;
+
+ if (t >= (LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY3_TMIN) / 2) {
+ t = clamp(t, LM3533_LED_DELAY3_TMIN, LM3533_LED_DELAY3_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY3_TMIN,
+ LM3533_LED_DELAY3_TSTEP,
+ LM3533_LED_DELAY3_VMIN,
+ LM3533_LED_DELAY3_VMAX);
+ } else if (t >= (LM3533_LED_DELAY1_TMAX + LM3533_LED_DELAY2_TMIN) / 2) {
+ t = clamp(t, LM3533_LED_DELAY2_TMIN, LM3533_LED_DELAY2_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY2_TMIN,
+ LM3533_LED_DELAY2_TSTEP,
+ LM3533_LED_DELAY2_VMIN,
+ LM3533_LED_DELAY2_VMAX);
+ } else {
+ t = clamp(t, LM3533_LED_DELAY1_TMIN, LM3533_LED_DELAY1_TMAX);
+ val = time_to_val(&t, LM3533_LED_DELAY1_TMIN,
+ LM3533_LED_DELAY1_TSTEP,
+ LM3533_LED_DELAY1_VMIN,
+ LM3533_LED_DELAY1_VMAX);
+ }
+
+ *delay = (t + 500) / 1000;
+
+ return val;
+}
+
+/*
+ * Set delay register base to *delay (in ms) and update *delay to reflect
+ * actual hardware delay used.
+ */
+static u8 lm3533_led_delay_set(struct lm3533_led *led, u8 base,
+ unsigned long *delay)
+{
+ unsigned t;
+ u8 val;
+ u8 reg;
+ int ret;
+
+ t = (unsigned)*delay;
+
+ /* Delay group 3 is only available for low time (delay off). */
+ if (base != LM3533_REG_PATTERN_LOW_TIME_BASE)
+ t = min(t, LM3533_LED_DELAY2_TMAX / 1000);
+
+ val = lm3533_led_get_hw_delay(&t);
+
+ dev_dbg(led->cdev.dev, "%s - %lu: %u (0x%02x)\n", __func__,
+ *delay, t, val);
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg);
+
+ *delay = t;
+
+ return ret;
+}
+
+static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t)
+{
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t);
+}
+
+static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t)
+{
+ return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t);
+}
+
+static int lm3533_led_blink_set(struct led_classdev *cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct lm3533_led *led = to_lm3533_led(cdev);
+ int ret;
+
+ dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__,
+ *delay_on, *delay_off);
+
+ if (*delay_on > LM3533_LED_DELAY_ON_MAX ||
+ *delay_off > LM3533_LED_DELAY_OFF_MAX)
+ return -EINVAL;
+
+ if (*delay_on == 0 && *delay_off == 0) {
+ *delay_on = 500;
+ *delay_off = 500;
+ }
+
+ ret = lm3533_led_delay_on_set(led, delay_on);
+ if (ret)
+ return ret;
+
+ ret = lm3533_led_delay_off_set(led, delay_off);
+ if (ret)
+ return ret;
+
+ return lm3533_led_pattern_enable(led, 1);
+}
+
+static ssize_t show_id(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ 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);
+}
+
+/*
+ * Pattern generator rise/fall times:
+ *
+ * 0 - 2048 us (default)
+ * 1 - 262 ms
+ * 2 - 524 ms
+ * 3 - 1.049 s
+ * 4 - 2.097 s
+ * 5 - 4.194 s
+ * 6 - 8.389 s
+ * 7 - 16.78 s
+ */
+static ssize_t show_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ ssize_t ret;
+ u8 reg;
+ u8 val;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", val);
+}
+
+static ssize_t show_risetime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t show_falltime(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return show_risefalltime(dev, attr, buf,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t store_risefalltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, u8 base)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ u8 reg;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX)
+ return -EINVAL;
+
+ reg = lm3533_led_get_pattern_reg(led, base);
+ ret = lm3533_write(led->lm3533, reg, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t store_risetime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_RISETIME_BASE);
+}
+
+static ssize_t store_falltime(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ return store_risefalltime(dev, attr, buf, len,
+ LM3533_REG_PATTERN_FALLTIME_BASE);
+}
+
+static ssize_t show_als_channel(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned channel;
+ u8 reg;
+ u8 val;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ channel = (val & LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK) + 1;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", channel);
+}
+
+static ssize_t store_als_channel(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned channel;
+ u8 reg;
+ u8 val;
+ u8 mask;
+ int ret;
+
+ if (kstrtouint(buf, 0, &channel))
+ return -EINVAL;
+
+ if (channel < LM3533_ALS_CHANNEL_LV_MIN ||
+ channel > LM3533_ALS_CHANNEL_LV_MAX)
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK;
+ val = channel - 1;
+
+ ret = lm3533_update(led->lm3533, reg, val, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_als_en(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ bool enable;
+ u8 reg;
+ u8 val;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ enable = val & LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", enable);
+}
+
+static ssize_t store_als_en(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned enable;
+ u8 reg;
+ u8 mask;
+ u8 val;
+ int ret;
+
+ if (kstrtouint(buf, 0, &enable))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK;
+
+ if (enable)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, reg, val, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_linear(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 reg;
+ u8 val;
+ int linear;
+ int ret;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ ret = lm3533_read(led->lm3533, reg, &val);
+ if (ret)
+ return ret;
+
+ if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK)
+ linear = 1;
+ else
+ linear = 0;
+
+ return scnprintf(buf, PAGE_SIZE, "%x\n", linear);
+}
+
+static ssize_t store_linear(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ unsigned long linear;
+ u8 reg;
+ u8 mask;
+ u8 val;
+ int ret;
+
+ if (kstrtoul(buf, 0, &linear))
+ return -EINVAL;
+
+ reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE);
+ mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK;
+
+ if (linear)
+ val = mask;
+ else
+ val = 0;
+
+ ret = lm3533_update(led->lm3533, reg, val, mask);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t show_pwm(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ ret = lm3533_ctrlbank_get_pwm(&led->cb, &val);
+ if (ret)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_pwm(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ u8 val;
+ int ret;
+
+ if (kstrtou8(buf, 0, &val))
+ return -EINVAL;
+
+ ret = lm3533_ctrlbank_set_pwm(&led->cb, val);
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static LM3533_ATTR_RW(als_channel);
+static LM3533_ATTR_RW(als_en);
+static LM3533_ATTR_RW(falltime);
+static LM3533_ATTR_RO(id);
+static LM3533_ATTR_RW(linear);
+static LM3533_ATTR_RW(pwm);
+static LM3533_ATTR_RW(risetime);
+
+static struct attribute *lm3533_led_attributes[] = {
+ &dev_attr_als_channel.attr,
+ &dev_attr_als_en.attr,
+ &dev_attr_falltime.attr,
+ &dev_attr_id.attr,
+ &dev_attr_linear.attr,
+ &dev_attr_pwm.attr,
+ &dev_attr_risetime.attr,
+ NULL,
+};
+
+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 led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3533_led *led = to_lm3533_led(led_cdev);
+ umode_t mode = attr->mode;
+
+ if (attr == &dev_attr_als_channel.attr ||
+ attr == &dev_attr_als_en.attr) {
+ if (!led->lm3533->have_als)
+ mode = 0;
+ }
+
+ return mode;
+};
+
+static struct attribute_group lm3533_led_attribute_group = {
+ .is_visible = lm3533_led_attr_is_visible,
+ .attrs = lm3533_led_attributes
+};
+
+static int lm3533_led_setup(struct lm3533_led *led,
+ struct lm3533_led_platform_data *pdata)
+{
+ int ret;
+
+ ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current);
+ if (ret)
+ return ret;
+
+ return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm);
+}
+
+static int lm3533_led_probe(struct platform_device *pdev)
+{
+ struct lm3533 *lm3533;
+ struct lm3533_led_platform_data *pdata;
+ struct lm3533_led *led;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ lm3533 = dev_get_drvdata(pdev->dev.parent);
+ if (!lm3533)
+ return -EINVAL;
+
+ pdata = dev_get_platdata(&pdev->dev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform data\n");
+ return -EINVAL;
+ }
+
+ if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) {
+ dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id);
+ return -EINVAL;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ 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_get = lm3533_led_get;
+ led->cdev.blink_set = lm3533_led_blink_set;
+ led->cdev.brightness = LED_OFF;
+ 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
+ * registered.
+ */
+ led->cb.lm3533 = lm3533;
+ led->cb.id = lm3533_led_get_ctrlbank_id(led);
+ led->cb.dev = lm3533->dev;
+
+ platform_set_drvdata(pdev, led);
+
+ ret = led_classdev_register(pdev->dev.parent, &led->cdev);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id);
+ return ret;
+ }
+
+ 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;
+
+ ret = lm3533_ctrlbank_enable(&led->cb);
+ if (ret)
+ goto err_sysfs_remove;
+
+ return 0;
+
+err_sysfs_remove:
+ sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group);
+err_unregister:
+ led_classdev_unregister(&led->cdev);
+ flush_work(&led->work);
+
+ return ret;
+}
+
+static int 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)
+{
+
+ struct lm3533_led *led = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ 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,
+ .shutdown = lm3533_led_shutdown,
+};
+module_platform_driver(lm3533_led_driver);
+
+MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>");
+MODULE_DESCRIPTION("LM3533 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lm3533-leds");
diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c
new file mode 100644
index 00000000000..591eb5e58ae
--- /dev/null
+++ b/drivers/leds/leds-lm355x.c
@@ -0,0 +1,574 @@
+/*
+* 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 {
+ CHIP_LM3554 = 0,
+ CHIP_LM3556,
+};
+
+enum lm355x_regs {
+ REG_FLAG = 0,
+ REG_TORCH_CFG,
+ REG_TORCH_CTRL,
+ REG_STROBE_CFG,
+ REG_FLASH_CTRL,
+ REG_INDI_CFG,
+ REG_INDI_CTRL,
+ REG_OPMODE,
+ REG_MAX,
+};
+
+/* operation mode */
+enum lm355x_mode {
+ MODE_SHDN = 0,
+ MODE_INDIC,
+ MODE_TORCH,
+ MODE_FLASH
+};
+
+/* register map info. */
+struct lm355x_reg_data {
+ u8 regno;
+ u8 mask;
+ u8 shift;
+};
+
+struct lm355x_chip_data {
+ struct device *dev;
+ enum lm355x_type type;
+
+ struct led_classdev cdev_flash;
+ 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;
+
+ unsigned int last_flag;
+ struct lm355x_reg_data *regs;
+};
+
+/* specific indicator function for lm3556 */
+enum lm3556_indic_pulse_time {
+ PULSE_TIME_0_MS = 0,
+ PULSE_TIME_32_MS,
+ PULSE_TIME_64_MS,
+ PULSE_TIME_92_MS,
+ PULSE_TIME_128_MS,
+ PULSE_TIME_160_MS,
+ PULSE_TIME_196_MS,
+ PULSE_TIME_224_MS,
+ PULSE_TIME_256_MS,
+ PULSE_TIME_288_MS,
+ PULSE_TIME_320_MS,
+ PULSE_TIME_352_MS,
+ PULSE_TIME_384_MS,
+ PULSE_TIME_416_MS,
+ PULSE_TIME_448_MS,
+ PULSE_TIME_480_MS,
+};
+
+enum lm3556_indic_n_blank {
+ INDIC_N_BLANK_0 = 0,
+ INDIC_N_BLANK_1,
+ INDIC_N_BLANK_2,
+ INDIC_N_BLANK_3,
+ INDIC_N_BLANK_4,
+ INDIC_N_BLANK_5,
+ INDIC_N_BLANK_6,
+ INDIC_N_BLANK_7,
+ INDIC_N_BLANK_8,
+ INDIC_N_BLANK_9,
+ INDIC_N_BLANK_10,
+ INDIC_N_BLANK_11,
+ INDIC_N_BLANK_12,
+ INDIC_N_BLANK_13,
+ INDIC_N_BLANK_14,
+ INDIC_N_BLANK_15,
+};
+
+enum lm3556_indic_period {
+ INDIC_PERIOD_0 = 0,
+ INDIC_PERIOD_1,
+ INDIC_PERIOD_2,
+ INDIC_PERIOD_3,
+ INDIC_PERIOD_4,
+ INDIC_PERIOD_5,
+ INDIC_PERIOD_6,
+ INDIC_PERIOD_7,
+};
+
+#define INDIC_PATTERN_SIZE 4
+
+struct indicator {
+ u8 blinking;
+ u8 period_cnt;
+};
+
+/* indicator pattern data only for lm3556 */
+static struct indicator indicator_pattern[INDIC_PATTERN_SIZE] = {
+ [0] = {(INDIC_N_BLANK_1 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_1},
+ [1] = {(INDIC_N_BLANK_15 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_2},
+ [2] = {(INDIC_N_BLANK_10 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_4},
+ [3] = {(INDIC_N_BLANK_5 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_7},
+};
+
+static struct lm355x_reg_data lm3554_regs[REG_MAX] = {
+ [REG_FLAG] = {0xD0, 0xBF, 0},
+ [REG_TORCH_CFG] = {0xE0, 0x80, 7},
+ [REG_TORCH_CTRL] = {0xA0, 0x38, 3},
+ [REG_STROBE_CFG] = {0xE0, 0x04, 2},
+ [REG_FLASH_CTRL] = {0xB0, 0x78, 3},
+ [REG_INDI_CFG] = {0xE0, 0x08, 3},
+ [REG_INDI_CTRL] = {0xA0, 0xC0, 6},
+ [REG_OPMODE] = {0xA0, 0x03, 0},
+};
+
+static struct lm355x_reg_data lm3556_regs[REG_MAX] = {
+ [REG_FLAG] = {0x0B, 0xFF, 0},
+ [REG_TORCH_CFG] = {0x0A, 0x10, 4},
+ [REG_TORCH_CTRL] = {0x09, 0x70, 4},
+ [REG_STROBE_CFG] = {0x0A, 0x20, 5},
+ [REG_FLASH_CTRL] = {0x09, 0x0F, 0},
+ [REG_INDI_CFG] = {0xFF, 0xFF, 0},
+ [REG_INDI_CTRL] = {0x09, 0x70, 4},
+ [REG_OPMODE] = {0x0A, 0x03, 0},
+};
+
+static char lm355x_name[][I2C_NAME_SIZE] = {
+ [CHIP_LM3554] = LM3554_NAME,
+ [CHIP_LM3556] = LM3556_NAME,
+};
+
+/* chip initialize */
+static int lm355x_chip_init(struct lm355x_chip_data *chip)
+{
+ int ret;
+ unsigned int reg_val;
+ struct lm355x_platform_data *pdata = chip->pdata;
+
+ /* input and output pins configuration */
+ switch (chip->type) {
+ case CHIP_LM3554:
+ reg_val = pdata->pin_tx2 | pdata->ntc_pin;
+ ret = regmap_update_bits(chip->regmap, 0xE0, 0x28, reg_val);
+ if (ret < 0)
+ goto out;
+ reg_val = 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;
+ ret = regmap_update_bits(chip->regmap, 0x0A, 0xC4, reg_val);
+ if (ret < 0)
+ goto out;
+ break;
+ default:
+ return -ENODATA;
+ }
+
+ return ret;
+out:
+ dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
+ return ret;
+}
+
+/* chip control */
+static void lm355x_control(struct lm355x_chip_data *chip,
+ u8 brightness, enum lm355x_mode opmode)
+{
+ int ret;
+ unsigned int reg_val;
+ struct lm355x_platform_data *pdata = chip->pdata;
+ struct lm355x_reg_data *preg = chip->regs;
+
+ ret = regmap_read(chip->regmap, preg[REG_FLAG].regno, &chip->last_flag);
+ if (ret < 0)
+ goto out;
+ if (chip->last_flag & preg[REG_FLAG].mask)
+ dev_info(chip->dev, "%s Last FLAG is 0x%x\n",
+ lm355x_name[chip->type],
+ chip->last_flag & preg[REG_FLAG].mask);
+ /* brightness 0 means shutdown */
+ if (!brightness)
+ opmode = MODE_SHDN;
+
+ switch (opmode) {
+ case MODE_TORCH:
+ ret =
+ regmap_update_bits(chip->regmap, preg[REG_TORCH_CTRL].regno,
+ preg[REG_TORCH_CTRL].mask,
+ (brightness - 1)
+ << preg[REG_TORCH_CTRL].shift);
+ if (ret < 0)
+ goto out;
+
+ if (pdata->pin_tx1 != LM355x_PIN_TORCH_DISABLE) {
+ ret =
+ regmap_update_bits(chip->regmap,
+ preg[REG_TORCH_CFG].regno,
+ preg[REG_TORCH_CFG].mask,
+ 0x01 <<
+ preg[REG_TORCH_CFG].shift);
+ if (ret < 0)
+ goto out;
+ opmode = MODE_SHDN;
+ dev_info(chip->dev,
+ "torch brt is set - ext. torch pin mode\n");
+ }
+ break;
+
+ case MODE_FLASH:
+
+ ret =
+ regmap_update_bits(chip->regmap, preg[REG_FLASH_CTRL].regno,
+ preg[REG_FLASH_CTRL].mask,
+ (brightness - 1)
+ << preg[REG_FLASH_CTRL].shift);
+ if (ret < 0)
+ goto out;
+
+ if (pdata->pin_strobe != LM355x_PIN_STROBE_DISABLE) {
+ if (chip->type == CHIP_LM3554)
+ reg_val = 0x00;
+ else
+ reg_val = 0x01;
+ ret =
+ regmap_update_bits(chip->regmap,
+ preg[REG_STROBE_CFG].regno,
+ preg[REG_STROBE_CFG].mask,
+ reg_val <<
+ preg[REG_STROBE_CFG].shift);
+ if (ret < 0)
+ goto out;
+ opmode = MODE_SHDN;
+ dev_info(chip->dev,
+ "flash brt is set - ext. strobe pin mode\n");
+ }
+ break;
+
+ case MODE_INDIC:
+ ret =
+ regmap_update_bits(chip->regmap, preg[REG_INDI_CTRL].regno,
+ preg[REG_INDI_CTRL].mask,
+ (brightness - 1)
+ << preg[REG_INDI_CTRL].shift);
+ if (ret < 0)
+ goto out;
+
+ if (pdata->pin_tx2 != LM355x_PIN_TX_DISABLE) {
+ ret =
+ regmap_update_bits(chip->regmap,
+ preg[REG_INDI_CFG].regno,
+ preg[REG_INDI_CFG].mask,
+ 0x01 <<
+ preg[REG_INDI_CFG].shift);
+ if (ret < 0)
+ goto out;
+ opmode = MODE_SHDN;
+ }
+ break;
+ case MODE_SHDN:
+ break;
+ default:
+ return;
+ }
+ /* operation mode control */
+ ret = regmap_update_bits(chip->regmap, preg[REG_OPMODE].regno,
+ preg[REG_OPMODE].mask,
+ opmode << preg[REG_OPMODE].shift);
+ if (ret < 0)
+ goto out;
+ return;
+out:
+ dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
+ return;
+}
+
+/* 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,
+ 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);
+
+ mutex_lock(&chip->lock);
+ lm355x_control(chip, chip->br_flash, MODE_FLASH);
+ mutex_unlock(&chip->lock);
+}
+
+static void 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);
+
+ mutex_lock(&chip->lock);
+ lm355x_control(chip, chip->br_indicator, MODE_INDIC);
+ mutex_unlock(&chip->lock);
+}
+
+static void 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);
+
+ chip->br_indicator = brightness;
+ schedule_work(&chip->work_indicator);
+}
+
+/* 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)
+{
+ ssize_t ret;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm355x_chip_data *chip =
+ container_of(led_cdev, struct lm355x_chip_data, cdev_indicator);
+ unsigned int state;
+
+ ret = kstrtouint(buf, 10, &state);
+ if (ret)
+ goto out;
+ if (state > INDIC_PATTERN_SIZE - 1)
+ state = INDIC_PATTERN_SIZE - 1;
+
+ ret = regmap_write(chip->regmap, 0x04,
+ indicator_pattern[state].blinking);
+ if (ret < 0)
+ goto out;
+
+ ret = regmap_write(chip->regmap, 0x05,
+ indicator_pattern[state].period_cnt);
+ if (ret < 0)
+ goto out;
+
+ return size;
+out:
+ dev_err(chip->dev, "%s:i2c access fail to register\n", __func__);
+ return ret;
+}
+
+static DEVICE_ATTR(pattern, S_IWUSR, NULL, lm3556_indicator_pattern_store);
+
+static const struct regmap_config lm355x_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = 0xFF,
+};
+
+/* module initialize */
+static int lm355x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lm355x_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct lm355x_chip_data *chip;
+
+ int err;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "i2c functionality check fail.\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "needs Platform Data.\n");
+ return -ENODATA;
+ }
+
+ chip = devm_kzalloc(&client->dev,
+ sizeof(struct lm355x_chip_data), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &client->dev;
+ chip->type = id->driver_data;
+ switch (id->driver_data) {
+ case CHIP_LM3554:
+ chip->regs = lm3554_regs;
+ break;
+ case CHIP_LM3556:
+ chip->regs = lm3556_regs;
+ break;
+ default:
+ return -ENOSYS;
+ }
+ chip->pdata = pdata;
+
+ chip->regmap = devm_regmap_init_i2c(client, &lm355x_regmap);
+ if (IS_ERR(chip->regmap)) {
+ err = PTR_ERR(chip->regmap);
+ dev_err(&client->dev,
+ "Failed to allocate register map: %d\n", err);
+ return err;
+ }
+
+ mutex_init(&chip->lock);
+ i2c_set_clientdata(client, chip);
+
+ err = lm355x_chip_init(chip);
+ if (err < 0)
+ 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.default_trigger = "flash";
+ err = led_classdev_register((struct device *)
+ &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.default_trigger = "torch";
+ err = led_classdev_register((struct device *)
+ &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);
+ 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:
+ led_classdev_unregister(&chip->cdev_flash);
+err_out:
+ return err;
+}
+
+static int 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[] = {
+ {LM3554_NAME, CHIP_LM3554},
+ {LM3556_NAME, CHIP_LM3556},
+ {}
+};
+
+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,
+ .remove = lm355x_remove,
+ .id_table = lm355x_id,
+};
+
+module_i2c_driver(lm355x_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM355x");
+MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_AUTHOR("G.Shark Jeong <gshark.jeong@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c
new file mode 100644
index 00000000000..ceb6b3cde6f
--- /dev/null
+++ b/drivers/leds/leds-lm3642.c
@@ -0,0 +1,464 @@
+/*
+* 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>
+#include <linux/i2c.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-lm3642.h>
+
+#define REG_FILT_TIME (0x0)
+#define REG_IVFM_MODE (0x1)
+#define REG_TORCH_TIME (0x6)
+#define REG_FLASH (0x8)
+#define REG_I_CTRL (0x9)
+#define REG_ENABLE (0xA)
+#define REG_FLAG (0xB)
+#define REG_MAX (0xB)
+
+#define UVLO_EN_SHIFT (7)
+#define IVM_D_TH_SHIFT (2)
+#define TORCH_RAMP_UP_TIME_SHIFT (3)
+#define TORCH_RAMP_DN_TIME_SHIFT (0)
+#define INDUCTOR_I_LIMIT_SHIFT (6)
+#define FLASH_RAMP_TIME_SHIFT (3)
+#define FLASH_TOUT_TIME_SHIFT (0)
+#define TORCH_I_SHIFT (4)
+#define FLASH_I_SHIFT (0)
+#define IVFM_SHIFT (7)
+#define TX_PIN_EN_SHIFT (6)
+#define STROBE_PIN_EN_SHIFT (5)
+#define TORCH_PIN_EN_SHIFT (4)
+#define MODE_BITS_SHIFT (0)
+
+#define UVLO_EN_MASK (0x1)
+#define IVM_D_TH_MASK (0x7)
+#define TORCH_RAMP_UP_TIME_MASK (0x7)
+#define TORCH_RAMP_DN_TIME_MASK (0x7)
+#define INDUCTOR_I_LIMIT_MASK (0x1)
+#define FLASH_RAMP_TIME_MASK (0x7)
+#define FLASH_TOUT_TIME_MASK (0x7)
+#define TORCH_I_MASK (0x7)
+#define FLASH_I_MASK (0xF)
+#define IVFM_MASK (0x1)
+#define TX_PIN_EN_MASK (0x1)
+#define STROBE_PIN_EN_MASK (0x1)
+#define TORCH_PIN_EN_MASK (0x1)
+#define MODE_BITS_MASK (0x73)
+#define EX_PIN_CONTROL_MASK (0x71)
+#define EX_PIN_ENABLE_MASK (0x70)
+
+enum lm3642_mode {
+ MODES_STASNDBY = 0,
+ MODES_INDIC,
+ MODES_TORCH,
+ MODES_FLASH
+};
+
+struct lm3642_chip_data {
+ struct device *dev;
+
+ struct led_classdev cdev_flash;
+ 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;
+
+ enum lm3642_torch_pin_enable torch_pin;
+ enum lm3642_strobe_pin_enable strobe_pin;
+ enum lm3642_tx_pin_enable tx_pin;
+
+ struct lm3642_platform_data *pdata;
+ struct regmap *regmap;
+ struct mutex lock;
+
+ unsigned int last_flag;
+};
+
+/* chip initialize */
+static int lm3642_chip_init(struct lm3642_chip_data *chip)
+{
+ int ret;
+ struct lm3642_platform_data *pdata = chip->pdata;
+
+ /* set enable register */
+ ret = regmap_update_bits(chip->regmap, REG_ENABLE, EX_PIN_ENABLE_MASK,
+ pdata->tx_pin);
+ if (ret < 0)
+ dev_err(chip->dev, "Failed to update REG_ENABLE Register\n");
+ return ret;
+}
+
+/* chip control */
+static int lm3642_control(struct lm3642_chip_data *chip,
+ u8 brightness, enum lm3642_mode opmode)
+{
+ int ret;
+
+ 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;
+ }
+
+ if (chip->last_flag)
+ dev_info(chip->dev, "Last FLAG is 0x%x\n", chip->last_flag);
+
+ /* brightness 0 means off-state */
+ if (!brightness)
+ opmode = MODES_STASNDBY;
+
+ switch (opmode) {
+ case MODES_TORCH:
+ ret = regmap_update_bits(chip->regmap, REG_I_CTRL,
+ TORCH_I_MASK << TORCH_I_SHIFT,
+ (brightness - 1) << TORCH_I_SHIFT);
+
+ if (chip->torch_pin)
+ opmode |= (TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT);
+ break;
+
+ case MODES_FLASH:
+ ret = regmap_update_bits(chip->regmap, REG_I_CTRL,
+ FLASH_I_MASK << FLASH_I_SHIFT,
+ (brightness - 1) << FLASH_I_SHIFT);
+
+ if (chip->strobe_pin)
+ opmode |= (STROBE_PIN_EN_MASK << STROBE_PIN_EN_SHIFT);
+ break;
+
+ case MODES_INDIC:
+ ret = regmap_update_bits(chip->regmap, REG_I_CTRL,
+ TORCH_I_MASK << TORCH_I_SHIFT,
+ (brightness - 1) << TORCH_I_SHIFT);
+ break;
+
+ case MODES_STASNDBY:
+
+ break;
+
+ default:
+ return ret;
+ }
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to write REG_I_CTRL Register\n");
+ goto out;
+ }
+
+ if (chip->tx_pin)
+ opmode |= (TX_PIN_EN_MASK << TX_PIN_EN_SHIFT);
+
+ 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)
+{
+ ssize_t ret;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3642_chip_data *chip =
+ container_of(led_cdev, struct lm3642_chip_data, cdev_indicator);
+ unsigned int state;
+
+ ret = kstrtouint(buf, 10, &state);
+ if (ret)
+ goto out_strtoint;
+ if (state != 0)
+ state = 0x01 << TORCH_PIN_EN_SHIFT;
+
+ chip->torch_pin = state;
+ ret = regmap_update_bits(chip->regmap, REG_ENABLE,
+ TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT,
+ state);
+ if (ret < 0)
+ goto out;
+
+ 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 void 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);
+
+ chip->br_torch = brightness;
+ schedule_work(&chip->work_torch);
+}
+
+/* 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)
+{
+ ssize_t ret;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct lm3642_chip_data *chip =
+ container_of(led_cdev, struct lm3642_chip_data, cdev_indicator);
+ unsigned int state;
+
+ ret = kstrtouint(buf, 10, &state);
+ if (ret)
+ goto out_strtoint;
+ if (state != 0)
+ state = 0x01 << STROBE_PIN_EN_SHIFT;
+
+ chip->strobe_pin = state;
+ ret = regmap_update_bits(chip->regmap, REG_ENABLE,
+ STROBE_PIN_EN_MASK << STROBE_PIN_EN_SHIFT,
+ state);
+ if (ret < 0)
+ goto out;
+
+ 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 void 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);
+
+ mutex_lock(&chip->lock);
+ lm3642_control(chip, chip->br_indicator, MODES_INDIC);
+ mutex_unlock(&chip->lock);
+}
+
+static void 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);
+
+ chip->br_indicator = brightness;
+ schedule_work(&chip->work_indicator);
+}
+
+static const struct regmap_config lm3642_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = REG_MAX,
+};
+
+static int lm3642_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct lm3642_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct lm3642_chip_data *chip;
+
+ int err;
+
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "i2c functionality check fail.\n");
+ return -EOPNOTSUPP;
+ }
+
+ if (pdata == NULL) {
+ dev_err(&client->dev, "needs Platform Data.\n");
+ return -ENODATA;
+ }
+
+ chip = devm_kzalloc(&client->dev,
+ sizeof(struct lm3642_chip_data), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->dev = &client->dev;
+ chip->pdata = pdata;
+
+ chip->tx_pin = pdata->tx_pin;
+ chip->torch_pin = pdata->torch_pin;
+ chip->strobe_pin = pdata->strobe_pin;
+
+ chip->regmap = devm_regmap_init_i2c(client, &lm3642_regmap);
+ if (IS_ERR(chip->regmap)) {
+ err = PTR_ERR(chip->regmap);
+ dev_err(&client->dev, "Failed to allocate register map: %d\n",
+ err);
+ return err;
+ }
+
+ mutex_init(&chip->lock);
+ i2c_set_clientdata(client, chip);
+
+ err = lm3642_chip_init(chip);
+ if (err < 0)
+ 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.default_trigger = "flash";
+ err = led_classdev_register((struct device *)
+ &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.default_trigger = "torch";
+ err = led_classdev_register((struct device *)
+ &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);
+ if (err < 0) {
+ dev_err(chip->dev, "failed to register indicator\n");
+ goto err_create_indicator_file;
+ }
+
+ dev_info(&client->dev, "LM3642 is initialized\n");
+ 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)
+{
+ 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},
+ {}
+};
+
+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,
+ .remove = lm3642_remove,
+ .id_table = lm3642_id,
+};
+
+module_i2c_driver(lm3642_i2c_driver);
+
+MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3642");
+MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_AUTHOR("G.Shark Jeong <gshark.jeong@gmail.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-locomo.c b/drivers/leds/leds-locomo.c
index 1f7c10f6b7f..80ba048889d 100644
--- a/drivers/leds/leds-locomo.c
+++ b/drivers/leds/leds-locomo.c
@@ -10,6 +10,7 @@
#include <linux/kernel.h>
#include <linux/init.h>
+#include <linux/module.h>
#include <linux/device.h>
#include <linux/leds.h>
diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c
index 9010c054615..8e1abdcd4c9 100644
--- a/drivers/leds/leds-lp3944.c
+++ b/drivers/leds/leds-lp3944.c
@@ -86,7 +86,7 @@ static int lp3944_reg_read(struct i2c_client *client, u8 reg, u8 *value)
tmp = i2c_smbus_read_byte_data(client, reg);
if (tmp < 0)
- return -EINVAL;
+ return tmp;
*value = tmp;
@@ -289,7 +289,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;
+ led->status = !!brightness;
schedule_work(&led->work);
}
@@ -374,10 +374,11 @@ exit:
return err;
}
-static int __devinit lp3944_probe(struct i2c_client *client,
+static int lp3944_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- 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;
@@ -393,7 +394,8 @@ static int __devinit lp3944_probe(struct i2c_client *client,
return -ENODEV;
}
- data = kzalloc(sizeof(struct lp3944_data), GFP_KERNEL);
+ data = devm_kzalloc(&client->dev, sizeof(struct lp3944_data),
+ GFP_KERNEL);
if (!data)
return -ENOMEM;
@@ -403,18 +405,16 @@ static int __devinit lp3944_probe(struct i2c_client *client,
mutex_init(&data->lock);
err = lp3944_configure(client, data, lp3944_pdata);
- if (err < 0) {
- kfree(data);
+ if (err < 0)
return err;
- }
dev_info(&client->dev, "lp3944 enabled\n");
return 0;
}
-static int __devexit lp3944_remove(struct i2c_client *client)
+static int 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;
@@ -431,8 +431,6 @@ static int __devexit lp3944_remove(struct i2c_client *client)
break;
}
- kfree(data);
-
return 0;
}
@@ -449,22 +447,11 @@ static struct i2c_driver lp3944_driver = {
.name = "lp3944",
},
.probe = lp3944_probe,
- .remove = __devexit_p(lp3944_remove),
+ .remove = lp3944_remove,
.id_table = lp3944_id,
};
-static int __init lp3944_module_init(void)
-{
- return i2c_add_driver(&lp3944_driver);
-}
-
-static void __exit lp3944_module_exit(void)
-{
- i2c_del_driver(&lp3944_driver);
-}
-
-module_init(lp3944_module_init);
-module_exit(lp3944_module_exit);
+module_i2c_driver(lp3944_driver);
MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
MODULE_DESCRIPTION("LP3944 Fun Light Chip");
diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c
index 3782f31f06d..8ca197af286 100644
--- a/drivers/leds/leds-lp5521.c
+++ b/drivers/leds/leds-lp5521.c
@@ -2,8 +2,10 @@
* LP5521 LED chip 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
@@ -20,33 +22,21 @@
* 02110-1301 USA
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/i2c.h>
-#include <linux/mutex.h>
-#include <linux/gpio.h>
-#include <linux/interrupt.h>
#include <linux/delay.h>
-#include <linux/ctype.h>
-#include <linux/spinlock.h>
-#include <linux/wait.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
#include <linux/leds.h>
-#include <linux/leds-lp5521.h>
-#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/platform_data/leds-lp55xx.h>
#include <linux/slab.h>
+#include <linux/of.h>
-#define LP5521_PROGRAM_LENGTH 32 /* in bytes */
-
-#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */
-#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */
+#include "leds-lp55xx-common.h"
-#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */
-#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */
-
-#define LP5521_CMD_LOAD 0x15 /* 00010101 */
-#define LP5521_CMD_RUN 0x2a /* 00101010 */
-#define LP5521_CMD_DIRECT 0x3f /* 00111111 */
-#define LP5521_CMD_DISABLED 0x00 /* 00000000 */
+#define LP5521_PROGRAM_LENGTH 32
+#define LP5521_MAX_LEDS 3
+#define LP5521_CMD_DIRECT 0x3F
/* Registers */
#define LP5521_REG_ENABLE 0x00
@@ -58,22 +48,14 @@
#define LP5521_REG_G_CURRENT 0x06
#define LP5521_REG_B_CURRENT 0x07
#define LP5521_REG_CONFIG 0x08
-#define LP5521_REG_R_CHANNEL_PC 0x09
-#define LP5521_REG_G_CHANNEL_PC 0x0A
-#define LP5521_REG_B_CHANNEL_PC 0x0B
#define LP5521_REG_STATUS 0x0C
#define LP5521_REG_RESET 0x0D
-#define LP5521_REG_GPO 0x0E
#define LP5521_REG_R_PROG_MEM 0x10
#define LP5521_REG_G_PROG_MEM 0x30
#define LP5521_REG_B_PROG_MEM 0x50
-#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM
-#define LP5521_PROG_MEM_SIZE 0x20
-
/* Base register to set LED current */
#define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT
-
/* Base register to set the brightness */
#define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM
@@ -81,360 +63,335 @@
#define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */
#define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */
#define LP5521_EXEC_RUN 0x2A
+#define LP5521_ENABLE_DEFAULT \
+ (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)
+#define LP5521_ENABLE_RUN_PROGRAM \
+ (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN)
-/* Bits in CONFIG register */
+/* 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_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */
-#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */
-#define LP5521_CLK_INT 1 /* Internal clock */
-#define LP5521_CLK_AUTO 2 /* Automatic clock selection */
+#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)
/* Status */
#define LP5521_EXT_CLK_USED 0x08
-struct lp5521_engine {
- const struct attribute_group *attributes;
- int id;
- u8 mode;
- u8 prog_page;
- u8 engine_mask;
-};
-
-struct lp5521_led {
- int id;
- u8 chan_nr;
- u8 led_current;
- u8 max_current;
- struct led_classdev cdev;
- struct work_struct brightness_work;
- u8 brightness;
-};
-
-struct lp5521_chip {
- struct lp5521_platform_data *pdata;
- struct mutex lock; /* Serialize control */
- struct i2c_client *client;
- struct lp5521_engine engines[LP5521_MAX_ENGINES];
- struct lp5521_led leds[LP5521_MAX_LEDS];
- u8 num_channels;
- u8 num_leds;
-};
-
-#define cdev_to_led(c) container_of(c, struct lp5521_led, cdev)
-#define engine_to_lp5521(eng) container_of((eng), struct lp5521_chip, \
- engines[(eng)->id - 1])
-#define led_to_lp5521(led) container_of((led), struct lp5521_chip, \
- leds[(led)->id])
-
-static void lp5521_led_brightness_work(struct work_struct *work);
-
-static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value)
+/* default R channel current register value */
+#define LP5521_REG_R_CURR_DEFAULT 0xAF
+
+/* 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)
{
- return i2c_smbus_write_byte_data(client, reg, value);
+ /* operation mode change needs to be longer than 153 us */
+ usleep_range(200, 300);
}
-static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf)
+static inline void lp5521_wait_enable_done(void)
{
- s32 ret;
-
- ret = i2c_smbus_read_byte_data(client, reg);
- if (ret < 0)
- return -EIO;
-
- *buf = ret;
- return 0;
+ /* it takes more 488 us to update ENABLE register */
+ usleep_range(500, 600);
}
-static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode)
+static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current)
{
- struct lp5521_chip *chip = engine_to_lp5521(engine);
- struct i2c_client *client = chip->client;
- int ret;
- u8 engine_state;
+ led->led_current = led_current;
+ lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
+ led_current);
+}
- /* Only transition between RUN and DIRECT mode are handled here */
- if (mode == LP5521_CMD_LOAD)
- return 0;
+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();
+}
- if (mode == LP5521_CMD_DISABLED)
- mode = LP5521_CMD_DIRECT;
+static void lp5521_stop_all_engines(struct lp55xx_chip *chip)
+{
+ lp55xx_write(chip, LP5521_REG_OP_MODE, 0);
+ lp5521_wait_opmode_done();
+}
- ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state);
+static void lp5521_stop_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,
+ };
- /* set mode only for this engine */
- engine_state &= ~(engine->engine_mask);
- mode &= engine->engine_mask;
- engine_state |= mode;
- ret |= lp5521_write(client, LP5521_REG_OP_MODE, engine_state);
+ lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0);
- return ret;
+ lp5521_wait_opmode_done();
}
-static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern)
+static void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
{
- struct lp5521_chip *chip = engine_to_lp5521(eng);
- struct i2c_client *client = chip->client;
int ret;
- int addr;
u8 mode;
+ u8 exec;
+
+ /* stop engine */
+ if (!start) {
+ lp5521_stop_engine(chip);
+ lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
+ lp5521_wait_opmode_done();
+ return;
+ }
- /* move current engine to direct mode and remember the state */
- ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT);
- usleep_range(1000, 10000);
- ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode);
+ /*
+ * To run the engine,
+ * operation mode and enable register should updated at the same time
+ */
- /* For loading, all the engines to load mode */
- lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
- usleep_range(1000, 10000);
- lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD);
- usleep_range(1000, 10000);
+ ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode);
+ if (ret)
+ return;
- addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE;
- i2c_smbus_write_i2c_block_data(client,
- addr,
- LP5521_PROG_MEM_SIZE,
- pattern);
+ ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec);
+ if (ret)
+ return;
- ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode);
- return ret;
-}
+ /* 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;
+ }
-static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr)
-{
- return lp5521_write(chip->client,
- LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
- curr);
-}
+ if (LP5521_G_IS_LOADING(mode)) {
+ mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
+ exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
+ }
-static void lp5521_init_engine(struct lp5521_chip *chip,
- const struct attribute_group *attr_group)
-{
- int i;
- for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
- chip->engines[i].id = i + 1;
- chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2);
- chip->engines[i].prog_page = i;
- chip->engines[i].attributes = &attr_group[i];
+ 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_configure(struct i2c_client *client,
- const struct attribute_group *attr_group)
+static int lp5521_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
{
- struct lp5521_chip *chip = i2c_get_clientdata(client);
+ 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 nrchars;
int ret;
+ int offset = 0;
+ int i = 0;
- lp5521_init_engine(chip, attr_group);
-
- lp5521_write(client, LP5521_REG_RESET, 0xff);
+ 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;
- usleep_range(10000, 20000);
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
- /* Set all PWMs to direct control mode */
- ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F);
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
- /* Enable auto-powersave, set charge pump to auto, red to battery */
- ret |= lp5521_write(client, LP5521_REG_CONFIG,
- LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT);
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
- /* Initialize all channels PWM to zero -> leds off */
- ret |= lp5521_write(client, LP5521_REG_R_PWM, 0);
- ret |= lp5521_write(client, LP5521_REG_G_PWM, 0);
- ret |= lp5521_write(client, LP5521_REG_B_PWM, 0);
+ for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) {
+ ret = lp55xx_write(chip, addr[idx] + i, pattern[i]);
+ if (ret)
+ return -EINVAL;
+ }
- /* Set engines are set to run state when OP_MODE enables engines */
- ret |= lp5521_write(client, LP5521_REG_ENABLE,
- LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM |
- LP5521_EXEC_RUN);
- /* enable takes 500us */
- usleep_range(500, 20000);
+ return size;
- return ret;
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
}
-static int lp5521_run_selftest(struct lp5521_chip *chip, char *buf)
+static void lp5521_firmware_loaded(struct lp55xx_chip *chip)
{
- int ret;
- u8 status;
+ const struct firmware *fw = chip->fw;
- ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status);
- if (ret < 0)
- return ret;
+ if (fw->size > LP5521_PROGRAM_LENGTH) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
- /* Check that ext clock is really in use if requested */
- if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT)
- if ((status & LP5521_EXT_CLK_USED) == 0)
- return -EIO;
- return 0;
-}
+ /*
+ * Program momery sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
-static void lp5521_set_brightness(struct led_classdev *cdev,
- enum led_brightness brightness)
-{
- struct lp5521_led *led = cdev_to_led(cdev);
- led->brightness = (u8)brightness;
- schedule_work(&led->brightness_work);
+ lp5521_load_engine(chip);
+ lp5521_update_program_memory(chip, fw->data, fw->size);
}
-static void lp5521_led_brightness_work(struct work_struct *work)
+static int lp5521_post_init_device(struct lp55xx_chip *chip)
{
- struct lp5521_led *led = container_of(work,
- struct lp5521_led,
- brightness_work);
- struct lp5521_chip *chip = led_to_lp5521(led);
- struct i2c_client *client = chip->client;
+ int ret;
+ u8 val;
+
+ /*
+ * Make sure that the chip is reset by reading back the r channel
+ * current reg. This is dummy read is required on some platforms -
+ * otherwise further access to the R G B channels in the
+ * LP5521_REG_ENABLE register will not have any effect - strange!
+ */
+ ret = lp55xx_read(chip, LP5521_REG_R_CURRENT, &val);
+ if (ret) {
+ dev_err(&chip->cl->dev, "error in resetting chip\n");
+ return ret;
+ }
+ if (val != LP5521_REG_R_CURR_DEFAULT) {
+ dev_err(&chip->cl->dev,
+ "unexpected data in register (expected 0x%x got 0x%x)\n",
+ LP5521_REG_R_CURR_DEFAULT, val);
+ ret = -EINVAL;
+ return ret;
+ }
+ usleep_range(10000, 20000);
- mutex_lock(&chip->lock);
- lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr,
- led->brightness);
- mutex_unlock(&chip->lock);
-}
+ /* Set all PWMs to direct control mode */
+ ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
-/* Detect the chip by setting its ENABLE register and reading it back. */
-static int lp5521_detect(struct i2c_client *client)
-{
- int ret;
- u8 buf;
+ /* Update configuration for the clock setting */
+ val = LP5521_DEFAULT_CFG;
+ if (!lp55xx_is_extclk_used(chip))
+ val |= LP5521_CLK_INT;
- ret = lp5521_write(client, LP5521_REG_ENABLE,
- LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM);
+ ret = lp55xx_write(chip, LP5521_REG_CONFIG, val);
if (ret)
return ret;
- usleep_range(1000, 10000);
- ret = lp5521_read(client, LP5521_REG_ENABLE, &buf);
+
+ /* Initialize all channels PWM to zero -> leds off */
+ lp55xx_write(chip, LP5521_REG_R_PWM, 0);
+ lp55xx_write(chip, LP5521_REG_G_PWM, 0);
+ lp55xx_write(chip, LP5521_REG_B_PWM, 0);
+
+ /* Set engines are set to run state when OP_MODE enables engines */
+ ret = lp55xx_write(chip, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM);
if (ret)
return ret;
- if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM))
- return -ENODEV;
+
+ lp5521_wait_enable_done();
return 0;
}
-/* Set engine mode and create appropriate sysfs attributes, if required. */
-static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode)
+static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
{
- struct lp5521_chip *chip = engine_to_lp5521(engine);
- struct i2c_client *client = chip->client;
- struct device *dev = &client->dev;
- int ret = 0;
-
- /* if in that mode already do nothing, except for run */
- if (mode == engine->mode && mode != LP5521_CMD_RUN)
- return 0;
-
- if (mode == LP5521_CMD_RUN) {
- ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN);
- } else if (mode == LP5521_CMD_LOAD) {
- lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
- lp5521_set_engine_mode(engine, LP5521_CMD_LOAD);
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ int ret;
+ u8 status;
- ret = sysfs_create_group(&dev->kobj, engine->attributes);
- if (ret)
- return ret;
- } else if (mode == LP5521_CMD_DISABLED) {
- lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED);
- }
+ ret = lp55xx_read(chip, LP5521_REG_STATUS, &status);
+ if (ret < 0)
+ return ret;
- /* remove load attribute from sysfs if not in load mode */
- if (engine->mode == LP5521_CMD_LOAD && mode != LP5521_CMD_LOAD)
- sysfs_remove_group(&dev->kobj, engine->attributes);
+ if (pdata->clock_mode != LP55XX_CLOCK_EXT)
+ return 0;
- engine->mode = mode;
+ /* Check that ext clock is really in use if requested */
+ if ((status & LP5521_EXT_CLK_USED) == 0)
+ return -EIO;
- return ret;
+ return 0;
}
-static int lp5521_do_store_load(struct lp5521_engine *engine,
- const char *buf, size_t len)
+static void lp5521_led_brightness_work(struct work_struct *work)
{
- struct lp5521_chip *chip = engine_to_lp5521(engine);
- struct i2c_client *client = chip->client;
- int ret, nrchars, offset = 0, i = 0;
- char c[3];
- unsigned cmd;
- u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
-
- while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto fail;
- pattern[i] = (u8)cmd;
-
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto fail;
+ struct lp55xx_led *led = container_of(work, struct lp55xx_led,
+ brightness_work);
+ struct lp55xx_chip *chip = led->chip;
mutex_lock(&chip->lock);
- ret = lp5521_load_program(engine, pattern);
+ lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
+ led->brightness);
mutex_unlock(&chip->lock);
-
- if (ret) {
- dev_err(&client->dev, "failed loading pattern\n");
- return ret;
- }
-
- return len;
-fail:
- dev_err(&client->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static ssize_t store_engine_load(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5521_chip *chip = i2c_get_clientdata(client);
- return lp5521_do_store_load(&chip->engines[nr - 1], buf, len);
}
-#define store_load(nr) \
-static ssize_t store_engine##nr##_load(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_load(dev, attr, buf, len, nr); \
-}
-store_load(1)
-store_load(2)
-store_load(3)
-
static ssize_t show_engine_mode(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5521_chip *chip = i2c_get_clientdata(client);
- switch (chip->engines[nr - 1].mode) {
- case LP5521_CMD_RUN:
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
+
+ switch (mode) {
+ case LP55XX_ENGINE_RUN:
return sprintf(buf, "run\n");
- case LP5521_CMD_LOAD:
+ case LP55XX_ENGINE_LOAD:
return sprintf(buf, "load\n");
- case LP5521_CMD_DISABLED:
- return sprintf(buf, "disabled\n");
+ case LP55XX_ENGINE_DISABLED:
default:
return sprintf(buf, "disabled\n");
}
}
-
-#define show_mode(nr) \
-static ssize_t show_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- return show_engine_mode(dev, attr, buf, nr); \
-}
show_mode(1)
show_mode(2)
show_mode(3)
@@ -443,141 +400,88 @@ static ssize_t store_engine_mode(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5521_chip *chip = i2c_get_clientdata(client);
- struct lp5521_engine *engine = &chip->engines[nr - 1];
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+
mutex_lock(&chip->lock);
- if (!strncmp(buf, "run", 3))
- lp5521_set_mode(engine, LP5521_CMD_RUN);
- else if (!strncmp(buf, "load", 4))
- lp5521_set_mode(engine, LP5521_CMD_LOAD);
- else if (!strncmp(buf, "disabled", 8))
- lp5521_set_mode(engine, LP5521_CMD_DISABLED);
+ chip->engine_idx = nr;
+
+ if (!strncmp(buf, "run", 3)) {
+ lp5521_run_engine(chip, true);
+ engine->mode = LP55XX_ENGINE_RUN;
+ } else if (!strncmp(buf, "load", 4)) {
+ lp5521_stop_engine(chip);
+ lp5521_load_engine(chip);
+ engine->mode = LP55XX_ENGINE_LOAD;
+ } else if (!strncmp(buf, "disabled", 8)) {
+ lp5521_stop_engine(chip);
+ engine->mode = LP55XX_ENGINE_DISABLED;
+ }
mutex_unlock(&chip->lock);
- return len;
-}
-#define store_mode(nr) \
-static ssize_t store_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_mode(dev, attr, buf, len, nr); \
+ return len;
}
store_mode(1)
store_mode(2)
store_mode(3)
-static ssize_t show_max_current(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5521_led *led = cdev_to_led(led_cdev);
-
- return sprintf(buf, "%d\n", led->max_current);
-}
-
-static ssize_t show_current(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5521_led *led = cdev_to_led(led_cdev);
-
- return sprintf(buf, "%d\n", led->led_current);
-}
-
-static ssize_t store_current(struct device *dev,
+static ssize_t store_engine_load(struct device *dev,
struct device_attribute *attr,
- const char *buf, size_t len)
+ const char *buf, size_t len, int nr)
{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5521_led *led = cdev_to_led(led_cdev);
- struct lp5521_chip *chip = led_to_lp5521(led);
- ssize_t ret;
- unsigned long curr;
-
- if (strict_strtoul(buf, 0, &curr))
- return -EINVAL;
-
- if (curr > led->max_current)
- return -EINVAL;
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
mutex_lock(&chip->lock);
- ret = lp5521_set_led_current(chip, led->id, curr);
- mutex_unlock(&chip->lock);
- if (ret < 0)
- return ret;
+ chip->engine_idx = nr;
+ lp5521_load_engine(chip);
+ ret = lp5521_update_program_memory(chip, buf, len);
- led->led_current = (u8)curr;
+ mutex_unlock(&chip->lock);
- return len;
+ return ret;
}
+store_load(1)
+store_load(2)
+store_load(3)
static ssize_t lp5521_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5521_chip *chip = i2c_get_clientdata(client);
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
int ret;
mutex_lock(&chip->lock);
ret = lp5521_run_selftest(chip, buf);
mutex_unlock(&chip->lock);
- return sprintf(buf, "%s\n", ret ? "FAIL" : "OK");
-}
-
-/* led class device attributes */
-static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
-static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
-static struct attribute *lp5521_led_attributes[] = {
- &dev_attr_led_current.attr,
- &dev_attr_max_current.attr,
- NULL,
-};
-
-static struct attribute_group lp5521_led_attribute_group = {
- .attrs = lp5521_led_attributes
-};
+ return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK");
+}
/* device attributes */
-static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO,
- show_engine1_mode, store_engine1_mode);
-static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO,
- show_engine2_mode, store_engine2_mode);
-static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO,
- show_engine3_mode, store_engine3_mode);
-static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load);
-static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load);
-static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load);
-static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL);
+static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
+static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
+static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
+static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
+static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
+static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
+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_selftest.attr,
- NULL
-};
-
-static struct attribute *lp5521_engine1_attributes[] = {
&dev_attr_engine1_load.attr,
- NULL
-};
-
-static struct attribute *lp5521_engine2_attributes[] = {
&dev_attr_engine2_load.attr,
- NULL
-};
-
-static struct attribute *lp5521_engine3_attributes[] = {
&dev_attr_engine3_load.attr,
+ &dev_attr_selftest.attr,
NULL
};
@@ -585,199 +489,99 @@ static const struct attribute_group lp5521_group = {
.attrs = lp5521_attributes,
};
-static const struct attribute_group lp5521_engine_group[] = {
- {.attrs = lp5521_engine1_attributes },
- {.attrs = lp5521_engine2_attributes },
- {.attrs = lp5521_engine3_attributes },
+/* Chip specific configurations */
+static struct lp55xx_device_config lp5521_cfg = {
+ .reset = {
+ .addr = LP5521_REG_RESET,
+ .val = LP5521_RESET,
+ },
+ .enable = {
+ .addr = LP5521_REG_ENABLE,
+ .val = LP5521_ENABLE_DEFAULT,
+ },
+ .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,
+ .run_engine = lp5521_run_engine,
+ .dev_attr_group = &lp5521_group,
};
-static int lp5521_register_sysfs(struct i2c_client *client)
-{
- struct device *dev = &client->dev;
- return sysfs_create_group(&dev->kobj, &lp5521_group);
-}
-
-static void lp5521_unregister_sysfs(struct i2c_client *client)
-{
- struct lp5521_chip *chip = i2c_get_clientdata(client);
- struct device *dev = &client->dev;
- int i;
-
- sysfs_remove_group(&dev->kobj, &lp5521_group);
-
- for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
- if (chip->engines[i].mode == LP5521_CMD_LOAD)
- sysfs_remove_group(&dev->kobj,
- chip->engines[i].attributes);
- }
-
- for (i = 0; i < chip->num_leds; i++)
- sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
- &lp5521_led_attribute_group);
-}
-
-static int __init lp5521_init_led(struct lp5521_led *led,
- struct i2c_client *client,
- int chan, struct lp5521_platform_data *pdata)
-{
- struct device *dev = &client->dev;
- char name[32];
- int res;
-
- if (chan >= LP5521_MAX_LEDS)
- return -EINVAL;
-
- if (pdata->led_config[chan].led_current == 0)
- return 0;
-
- 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;
-
- if (led->chan_nr >= LP5521_MAX_LEDS) {
- dev_err(dev, "Use channel numbers between 0 and %d\n",
- LP5521_MAX_LEDS - 1);
- return -EINVAL;
- }
-
- snprintf(name, sizeof(name), "%s:channel%d", client->name, chan);
- led->cdev.brightness_set = lp5521_set_brightness;
- led->cdev.name = name;
- res = led_classdev_register(dev, &led->cdev);
- if (res < 0) {
- dev_err(dev, "couldn't register led on channel %d\n", chan);
- return res;
- }
-
- res = sysfs_create_group(&led->cdev.dev->kobj,
- &lp5521_led_attribute_group);
- if (res < 0) {
- dev_err(dev, "couldn't register current attribute\n");
- led_classdev_unregister(&led->cdev);
- return res;
- }
- return 0;
-}
-
static int lp5521_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct lp5521_chip *chip;
- struct lp5521_platform_data *pdata;
- int ret, i, led;
+ int ret;
+ struct lp55xx_chip *chip;
+ struct lp55xx_led *led;
+ struct lp55xx_platform_data *pdata;
+ struct device_node *np = client->dev.of_node;
+
+ if (!dev_get_platdata(&client->dev)) {
+ 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 = dev_get_platdata(&client->dev);
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
- i2c_set_clientdata(client, chip);
- chip->client = client;
-
- pdata = client->dev.platform_data;
+ led = devm_kzalloc(&client->dev,
+ sizeof(*led) * pdata->num_channels, GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
- if (!pdata) {
- dev_err(&client->dev, "no platform data\n");
- ret = -EINVAL;
- goto fail1;
- }
+ chip->cl = client;
+ chip->pdata = pdata;
+ chip->cfg = &lp5521_cfg;
mutex_init(&chip->lock);
- chip->pdata = pdata;
-
- if (pdata->setup_resources) {
- ret = pdata->setup_resources();
- if (ret < 0)
- goto fail1;
- }
-
- if (pdata->enable) {
- pdata->enable(0);
- usleep_range(1000, 10000);
- pdata->enable(1);
- usleep_range(1000, 10000); /* Spec says min 500us */
- }
-
- ret = lp5521_detect(client);
+ i2c_set_clientdata(client, led);
- if (ret) {
- dev_err(&client->dev, "Chip not found\n");
- goto fail2;
- }
+ ret = lp55xx_init_device(chip);
+ if (ret)
+ goto err_init;
dev_info(&client->dev, "%s programmable led chip found\n", id->name);
- ret = lp5521_configure(client, lp5521_engine_group);
- if (ret < 0) {
- dev_err(&client->dev, "error configuring chip\n");
- goto fail2;
- }
-
- /* Initialize leds */
- chip->num_channels = pdata->num_channels;
- chip->num_leds = 0;
- led = 0;
- for (i = 0; i < pdata->num_channels; i++) {
- /* Do not initialize channels that are not connected */
- if (pdata->led_config[i].led_current == 0)
- continue;
-
- ret = lp5521_init_led(&chip->leds[led], client, i, pdata);
- if (ret) {
- dev_err(&client->dev, "error initializing leds\n");
- goto fail3;
- }
- chip->num_leds++;
-
- chip->leds[led].id = led;
- /* Set initial LED current */
- lp5521_set_led_current(chip, led,
- chip->leds[led].led_current);
-
- INIT_WORK(&(chip->leds[led].brightness_work),
- lp5521_led_brightness_work);
-
- led++;
- }
+ ret = lp55xx_register_leds(led, chip);
+ if (ret)
+ goto err_register_leds;
- ret = lp5521_register_sysfs(client);
+ ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
- goto fail3;
- }
- return ret;
-fail3:
- for (i = 0; i < chip->num_leds; i++) {
- led_classdev_unregister(&chip->leds[i].cdev);
- cancel_work_sync(&chip->leds[i].brightness_work);
+ goto err_register_sysfs;
}
-fail2:
- if (pdata->enable)
- pdata->enable(0);
- if (pdata->release_resources)
- pdata->release_resources();
-fail1:
- kfree(chip);
+
+ 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 lp5521_chip *chip = i2c_get_clientdata(client);
- int i;
+ struct lp55xx_led *led = i2c_get_clientdata(client);
+ struct lp55xx_chip *chip = led->chip;
- lp5521_unregister_sysfs(client);
+ lp5521_stop_all_engines(chip);
+ lp55xx_unregister_sysfs(chip);
+ lp55xx_unregister_leds(led, chip);
+ lp55xx_deinit_device(chip);
- for (i = 0; i < chip->num_leds; i++) {
- led_classdev_unregister(&chip->leds[i].cdev);
- cancel_work_sync(&chip->leds[i].brightness_work);
- }
-
- if (chip->pdata->enable)
- chip->pdata->enable(0);
- if (chip->pdata->release_resources)
- chip->pdata->release_resources();
- kfree(chip);
return 0;
}
@@ -787,35 +591,27 @@ static const struct i2c_device_id lp5521_id[] = {
};
MODULE_DEVICE_TABLE(i2c, lp5521_id);
+#ifdef CONFIG_OF
+static const struct of_device_id of_lp5521_leds_match[] = {
+ { .compatible = "national,lp5521", },
+ {},
+};
+
+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),
},
.probe = lp5521_probe,
.remove = lp5521_remove,
.id_table = lp5521_id,
};
-static int __init lp5521_init(void)
-{
- int ret;
-
- ret = i2c_add_driver(&lp5521_driver);
-
- if (ret < 0)
- printk(KERN_ALERT "Adding lp5521 driver failed\n");
-
- return ret;
-}
-
-static void __exit lp5521_exit(void)
-{
- i2c_del_driver(&lp5521_driver);
-}
-
-module_init(lp5521_init);
-module_exit(lp5521_exit);
+module_i2c_driver(lp5521_driver);
MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo");
+MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP5521 LED engine");
MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c
index 1e11fcc08b2..9e1716f8098 100644
--- a/drivers/leds/leds-lp5523.c
+++ b/drivers/leds/leds-lp5523.c
@@ -1,9 +1,11 @@
/*
- * 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
@@ -20,327 +22,436 @@
* 02110-1301 USA
*/
-#include <linux/module.h>
-#include <linux/init.h>
-#include <linux/i2c.h>
-#include <linux/mutex.h>
-#include <linux/gpio.h>
-#include <linux/interrupt.h>
#include <linux/delay.h>
-#include <linux/ctype.h>
-#include <linux/spinlock.h>
-#include <linux/wait.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
#include <linux/leds.h>
-#include <linux/leds-lp5523.h>
-#include <linux/workqueue.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 /* bytes */
+/* 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_MAX_LEDS 9
+
+/* Registers */
#define LP5523_REG_ENABLE 0x00
#define LP5523_REG_OP_MODE 0x01
-#define LP5523_REG_RATIOMETRIC_MSB 0x02
-#define LP5523_REG_RATIOMETRIC_LSB 0x03
#define LP5523_REG_ENABLE_LEDS_MSB 0x04
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
-#define LP5523_REG_LED_CNTRL_BASE 0x06
#define LP5523_REG_LED_PWM_BASE 0x16
#define LP5523_REG_LED_CURRENT_BASE 0x26
#define LP5523_REG_CONFIG 0x36
-#define LP5523_REG_CHANNEL1_PC 0x37
-#define LP5523_REG_CHANNEL2_PC 0x38
-#define LP5523_REG_CHANNEL3_PC 0x39
-#define LP5523_REG_STATUS 0x3a
-#define LP5523_REG_GPO 0x3b
-#define LP5523_REG_VARIABLE 0x3c
-#define LP5523_REG_RESET 0x3d
-#define LP5523_REG_TEMP_CTRL 0x3e
-#define LP5523_REG_TEMP_READ 0x3f
-#define LP5523_REG_TEMP_WRITE 0x40
+#define LP5523_REG_STATUS 0x3A
+#define LP5523_REG_RESET 0x3D
#define LP5523_REG_LED_TEST_CTRL 0x41
#define LP5523_REG_LED_TEST_ADC 0x42
-#define LP5523_REG_ENG1_VARIABLE 0x45
-#define LP5523_REG_ENG2_VARIABLE 0x46
-#define LP5523_REG_ENG3_VARIABLE 0x47
-#define LP5523_REG_MASTER_FADER1 0x48
-#define LP5523_REG_MASTER_FADER2 0x49
-#define LP5523_REG_MASTER_FADER3 0x4a
-#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_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
-#define LP5523_CMD_LOAD 0x15 /* 00010101 */
-#define LP5523_CMD_RUN 0x2a /* 00101010 */
-#define LP5523_CMD_DISABLED 0x00 /* 00000000 */
-
+/* Bit description in registers */
#define LP5523_ENABLE 0x40
#define LP5523_AUTO_INC 0x40
#define LP5523_PWR_SAVE 0x20
#define LP5523_PWM_PWR_SAVE 0x04
-#define LP5523_CP_1 0x08
-#define LP5523_CP_1_5 0x10
#define LP5523_CP_AUTO 0x18
-#define LP5523_INT_CLK 0x01
#define LP5523_AUTO_CLK 0x02
+
#define LP5523_EN_LEDTEST 0x80
#define LP5523_LEDTEST_DONE 0x80
-
-#define LP5523_DEFAULT_CURRENT 50 /* microAmps */
-#define LP5523_PROGRAM_LENGTH 32 /* in bytes */
-#define LP5523_PROGRAM_PAGES 6
+#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
+#define LP5523_PAGE_MUX1 3
+#define LP5523_PAGE_MUX2 4
+#define LP5523_PAGE_MUX3 5
+
+/* Program Memory Operations */
+#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */
+#define LP5523_MODE_ENG2_M 0x0C
+#define LP5523_MODE_ENG3_M 0x03
+#define LP5523_LOAD_ENG1 0x10
+#define LP5523_LOAD_ENG2 0x04
+#define LP5523_LOAD_ENG3 0x01
+
+#define LP5523_ENG1_IS_LOADING(mode) \
+ ((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1)
+#define LP5523_ENG2_IS_LOADING(mode) \
+ ((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2)
+#define LP5523_ENG3_IS_LOADING(mode) \
+ ((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3)
+
+#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */
+#define LP5523_EXEC_ENG2_M 0x0C
+#define LP5523_EXEC_ENG3_M 0x03
+#define LP5523_EXEC_M 0x3F
+#define LP5523_RUN_ENG1 0x20
+#define LP5523_RUN_ENG2 0x08
+#define LP5523_RUN_ENG3 0x02
+
+#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
-#define LP5523_LEDS 9
-#define LP5523_ENGINES 3
+enum lp5523_chip_id {
+ LP5523,
+ LP55231,
+};
-#define LP5523_ENG_MASK_BASE 0x30 /* 00110000 */
+static int lp5523_init_program_engine(struct lp55xx_chip *chip);
-#define LP5523_ENG_STATUS_MASK 0x07 /* 00000111 */
+static inline void lp5523_wait_opmode_done(void)
+{
+ usleep_range(1000, 2000);
+}
-#define LP5523_IRQ_FLAGS IRQF_TRIGGER_FALLING
+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);
+}
-#define LP5523_EXT_CLK_USED 0x08
+static int lp5523_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
-#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
-#define SHIFT_MASK(id) (((id) - 1) * 2)
-
-struct lp5523_engine {
- const struct attribute_group *attributes;
- int id;
- u8 mode;
- u8 prog_page;
- u8 mux_page;
- u16 led_mux;
- u8 engine_mask;
-};
+ ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE);
+ if (ret)
+ return ret;
-struct lp5523_led {
- int id;
- u8 chan_nr;
- u8 led_current;
- u8 max_current;
- struct led_classdev cdev;
- struct work_struct brightness_work;
- u8 brightness;
-};
+ /* Chip startup time is 500 us, 1 - 2 ms gives some margin */
+ usleep_range(1000, 2000);
-struct lp5523_chip {
- struct mutex lock; /* Serialize control */
- struct i2c_client *client;
- struct lp5523_engine engines[LP5523_ENGINES];
- struct lp5523_led leds[LP5523_LEDS];
- struct lp5523_platform_data *pdata;
- u8 num_channels;
- u8 num_leds;
-};
+ ret = lp55xx_write(chip, LP5523_REG_CONFIG,
+ LP5523_AUTO_INC | LP5523_PWR_SAVE |
+ LP5523_CP_AUTO | LP5523_AUTO_CLK |
+ LP5523_PWM_PWR_SAVE);
+ if (ret)
+ return ret;
+
+ /* turn on all leds */
+ ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
+ if (ret)
+ return ret;
-#define cdev_to_led(c) container_of(c, struct lp5523_led, cdev)
+ ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
+ if (ret)
+ return ret;
-static struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine)
-{
- return container_of(engine, struct lp5523_chip,
- engines[engine->id - 1]);
+ return lp5523_init_program_engine(chip);
}
-static struct lp5523_chip *led_to_lp5523(struct lp5523_led *led)
+static void lp5523_load_engine(struct lp55xx_chip *chip)
{
- return container_of(led, struct lp5523_chip,
- leds[led->id]);
+ 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,
+ };
+
+ lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
+
+ lp5523_wait_opmode_done();
}
-static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode);
-static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode);
-static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern);
+static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ u8 page_sel[] = {
+ [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
+ [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
+ [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
+ };
+
+ lp5523_load_engine(chip);
-static void lp5523_led_brightness_work(struct work_struct *work);
+ lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
+}
-static int lp5523_write(struct i2c_client *client, u8 reg, u8 value)
+static void lp5523_stop_all_engines(struct lp55xx_chip *chip)
{
- return i2c_smbus_write_byte_data(client, reg, value);
+ lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
+ lp5523_wait_opmode_done();
}
-static int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf)
+static void lp5523_stop_engine(struct lp55xx_chip *chip)
{
- s32 ret = i2c_smbus_read_byte_data(client, reg);
+ 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,
+ };
- if (ret < 0)
- return -EIO;
+ lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0);
- *buf = ret;
- return 0;
+ lp5523_wait_opmode_done();
}
-static int lp5523_detect(struct i2c_client *client)
+static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
+{
+ int i;
+
+ for (i = 0; i < LP5523_MAX_LEDS; i++)
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
+}
+
+static void lp5523_run_engine(struct lp55xx_chip *chip, bool start)
{
int ret;
- u8 buf;
+ u8 mode;
+ u8 exec;
+
+ /* stop engine */
+ if (!start) {
+ lp5523_stop_engine(chip);
+ lp5523_turn_off_channels(chip);
+ return;
+ }
- ret = lp5523_write(client, LP5523_REG_ENABLE, 0x40);
+ /*
+ * 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;
- ret = lp5523_read(client, LP5523_REG_ENABLE, &buf);
+ return;
+
+ ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec);
if (ret)
- return ret;
- if (buf == 0x40)
- return 0;
- else
- return -ENODEV;
+ 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);
}
-static int lp5523_configure(struct i2c_client *client)
+static int lp5523_init_program_engine(struct lp55xx_chip *chip)
{
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- int ret = 0;
+ int i;
+ int j;
+ int ret;
u8 status;
-
- /* one pattern per engine setting led mux start and stop addresses */
- u8 pattern[][LP5523_PROGRAM_LENGTH] = {
+ /* one pattern per engine setting LED MUX start and stop addresses */
+ static const u8 pattern[][LP5523_PROGRAM_LENGTH] = {
{ 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},
};
- lp5523_write(client, LP5523_REG_RESET, 0xff);
-
- usleep_range(10000, 100000);
-
- ret |= lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE);
- /* Chip startup time after reset is 500 us */
- usleep_range(1000, 10000);
-
- ret |= lp5523_write(client, LP5523_REG_CONFIG,
- LP5523_AUTO_INC | LP5523_PWR_SAVE |
- LP5523_CP_AUTO | LP5523_AUTO_CLK |
- LP5523_PWM_PWR_SAVE);
-
- /* turn on all leds */
- ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
- ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
-
/* hardcode 32 bytes of memory for each engine from program memory */
- ret |= lp5523_write(client, LP5523_REG_CH1_PROG_START, 0x00);
- ret |= lp5523_write(client, LP5523_REG_CH2_PROG_START, 0x10);
- ret |= lp5523_write(client, LP5523_REG_CH3_PROG_START, 0x20);
-
- /* write led mux address space for each channel */
- ret |= lp5523_load_program(&chip->engines[0], pattern[0]);
- ret |= lp5523_load_program(&chip->engines[1], pattern[1]);
- ret |= lp5523_load_program(&chip->engines[2], pattern[2]);
+ ret = lp55xx_write(chip, LP5523_REG_CH1_PROG_START, 0x00);
+ if (ret)
+ return ret;
- if (ret) {
- dev_err(&client->dev, "could not load mux programs\n");
- return -1;
- }
+ ret = lp55xx_write(chip, LP5523_REG_CH2_PROG_START, 0x10);
+ if (ret)
+ return ret;
- /* set all engines exec state and mode to run 00101010 */
- ret |= lp5523_write(client, LP5523_REG_ENABLE,
- (LP5523_CMD_RUN | LP5523_ENABLE));
+ ret = lp55xx_write(chip, LP5523_REG_CH3_PROG_START, 0x20);
+ if (ret)
+ return ret;
- ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_RUN);
+ /* write LED MUX address space for each engine */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ chip->engine_idx = i;
+ lp5523_load_engine_and_select_page(chip);
- if (ret) {
- dev_err(&client->dev, "could not start mux programs\n");
- return -1;
+ for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) {
+ ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j,
+ pattern[i - 1][j]);
+ if (ret)
+ goto out;
+ }
}
- /* Wait 3ms and check the engine status */
- usleep_range(3000, 20000);
- lp5523_read(client, LP5523_REG_STATUS, &status);
+ lp5523_run_engine(chip, true);
+
+ /* Let the programs run for couple of ms and check the engine status */
+ usleep_range(3000, 6000);
+ lp55xx_read(chip, LP5523_REG_STATUS, &status);
status &= LP5523_ENG_STATUS_MASK;
- if (status == LP5523_ENG_STATUS_MASK) {
- dev_dbg(&client->dev, "all engines configured\n");
- } else {
- dev_info(&client->dev, "status == %x\n", status);
- dev_err(&client->dev, "cound not configure LED engine\n");
- return -1;
+ if (status != LP5523_ENG_STATUS_MASK) {
+ dev_err(&chip->cl->dev,
+ "cound not configure LED engine, status = 0x%.2x\n",
+ status);
+ ret = -1;
}
- dev_info(&client->dev, "disabling engines\n");
-
- ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED);
-
+out:
+ lp5523_stop_all_engines(chip);
return ret;
}
-static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode)
+static int lp5523_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
{
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
+ u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
+ unsigned cmd;
+ char c[3];
+ int nrchars;
int ret;
- u8 engine_state;
+ int offset = 0;
+ int i = 0;
- ret = lp5523_read(client, LP5523_REG_OP_MODE, &engine_state);
- if (ret)
- goto fail;
+ 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;
- engine_state &= ~(engine->engine_mask);
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
- /* set mode only for this engine */
- mode &= engine->engine_mask;
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
- engine_state |= mode;
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
- ret |= lp5523_write(client, LP5523_REG_OP_MODE, engine_state);
-fail:
- return ret;
+ for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) {
+ ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
+ if (ret)
+ return -EINVAL;
+ }
+
+ return size;
+
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
}
-static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux)
+static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
{
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
- int ret = 0;
+ const struct firmware *fw = chip->fw;
- ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
+ if (fw->size > LP5523_PROGRAM_LENGTH) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
- ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, engine->mux_page);
- ret |= lp5523_write(client, LP5523_REG_PROG_MEM,
- (u8)(mux >> 8));
- ret |= lp5523_write(client, LP5523_REG_PROG_MEM + 1, (u8)(mux));
- engine->led_mux = mux;
+ /*
+ * Program momery sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
- return ret;
+ lp5523_load_engine_and_select_page(chip);
+ lp5523_update_program_memory(chip, fw->data, fw->size);
}
-static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern)
+static ssize_t show_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
{
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
+ 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;
- int ret = 0;
-
- ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
-
- ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL,
- engine->prog_page);
- ret |= i2c_smbus_write_i2c_block_data(client, LP5523_REG_PROG_MEM,
- LP5523_PROGRAM_LENGTH, pattern);
-
- return ret;
+ switch (mode) {
+ case LP55XX_ENGINE_RUN:
+ return sprintf(buf, "run\n");
+ case LP55XX_ENGINE_LOAD:
+ return sprintf(buf, "load\n");
+ case LP55XX_ENGINE_DISABLED:
+ default:
+ return sprintf(buf, "disabled\n");
+ }
}
+show_mode(1)
+show_mode(2)
+show_mode(3)
-static int lp5523_run_program(struct lp5523_engine *engine)
+static ssize_t store_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
{
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
- int ret;
+ 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];
- ret = lp5523_write(client, LP5523_REG_ENABLE,
- LP5523_CMD_RUN | LP5523_ENABLE);
- if (ret)
- goto fail;
+ mutex_lock(&chip->lock);
- ret = lp5523_set_engine_mode(engine, LP5523_CMD_RUN);
-fail:
- return ret;
+ chip->engine_idx = nr;
+
+ if (!strncmp(buf, "run", 3)) {
+ lp5523_run_engine(chip, true);
+ engine->mode = LP55XX_ENGINE_RUN;
+ } else if (!strncmp(buf, "load", 4)) {
+ lp5523_stop_engine(chip);
+ lp5523_load_engine(chip);
+ engine->mode = LP55XX_ENGINE_LOAD;
+ } else if (!strncmp(buf, "disabled", 8)) {
+ lp5523_stop_engine(chip);
+ engine->mode = LP55XX_ENGINE_DISABLED;
+ }
+
+ mutex_unlock(&chip->lock);
+
+ return len;
}
+store_mode(1)
+store_mode(2)
+store_mode(3)
static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
{
- int i;
u16 tmp_mux = 0;
- len = len < LP5523_LEDS ? len : LP5523_LEDS;
+ int i;
+
+ len = min_t(int, len, LP5523_MAX_LEDS);
+
for (i = 0; i < len; i++) {
switch (buf[i]) {
case '1':
@@ -363,132 +474,184 @@ static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
static void lp5523_mux_to_array(u16 led_mux, char *array)
{
int i, pos = 0;
- for (i = 0; i < LP5523_LEDS; i++)
+ for (i = 0; i < LP5523_MAX_LEDS; i++)
pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
array[pos] = '\0';
}
-/*--------------------------------------------------------------*/
-/* Sysfs interface */
-/*--------------------------------------------------------------*/
-
static ssize_t show_engine_leds(struct device *dev,
struct device_attribute *attr,
char *buf, int nr)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- char mux[LP5523_LEDS + 1];
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ char mux[LP5523_MAX_LEDS + 1];
lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
return sprintf(buf, "%s\n", mux);
}
-
-#define show_leds(nr) \
-static ssize_t show_engine##nr##_leds(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- return show_engine_leds(dev, attr, buf, nr); \
-}
show_leds(1)
show_leds(2)
show_leds(3)
+static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
+{
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+ int ret;
+ u8 mux_page[] = {
+ [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1,
+ [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2,
+ [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3,
+ };
+
+ lp5523_load_engine(chip);
+
+ ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8));
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux));
+ if (ret)
+ return ret;
+
+ engine->led_mux = mux;
+ return 0;
+}
+
static ssize_t store_engine_leds(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len, int nr)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
u16 mux = 0;
+ ssize_t ret;
if (lp5523_mux_parse(buf, &mux, len))
return -EINVAL;
- if (lp5523_load_mux(&chip->engines[nr - 1], mux))
- return -EINVAL;
+ mutex_lock(&chip->lock);
- return len;
-}
+ chip->engine_idx = nr;
+ ret = -EINVAL;
-#define store_leds(nr) \
-static ssize_t store_engine##nr##_leds(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_leds(dev, attr, buf, len, nr); \
+ if (engine->mode != LP55XX_ENGINE_LOAD)
+ goto leave;
+
+ if (lp5523_load_mux(chip, mux, nr))
+ goto leave;
+
+ ret = len;
+leave:
+ mutex_unlock(&chip->lock);
+ return ret;
}
store_leds(1)
store_leds(2)
store_leds(3)
+static ssize_t store_engine_load(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+
+ mutex_lock(&chip->lock);
+
+ chip->engine_idx = nr;
+ lp5523_load_engine_and_select_page(chip);
+ ret = lp5523_update_program_memory(chip, buf, len);
+
+ mutex_unlock(&chip->lock);
+
+ return ret;
+}
+store_load(1)
+store_load(2)
+store_load(3)
+
static ssize_t lp5523_selftest(struct device *dev,
struct device_attribute *attr,
char *buf)
{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
+ 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;
- int led = 0;
u8 status, adc, vdd;
mutex_lock(&chip->lock);
- ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+ ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
if (ret < 0)
goto fail;
/* Check that ext clock is really in use if requested */
- if ((chip->pdata) && (chip->pdata->clock_mode == LP5523_CLOCK_EXT))
+ if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
if ((status & LP5523_EXT_CLK_USED) == 0)
goto fail;
+ }
/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
- lp5523_write(chip->client, LP5523_REG_LED_TEST_CTRL,
- LP5523_EN_LEDTEST | 16);
- usleep_range(3000, 10000);
- ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+ lp55xx_write(chip, LP5523_REG_LED_TEST_CTRL, LP5523_EN_LEDTEST | 16);
+ 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;
+
if (!(status & LP5523_LEDTEST_DONE))
- usleep_range(3000, 10000);
+ 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;
- ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &vdd);
vdd--; /* There may be some fluctuation in measurement */
- for (i = 0; i < LP5523_LEDS; i++) {
+ for (i = 0; i < LP5523_MAX_LEDS; i++) {
/* Skip non-existing channels */
- if (chip->pdata->led_config[i].led_current == 0)
+ if (pdata->led_config[i].led_current == 0)
continue;
/* Set default current */
- lp5523_write(chip->client,
- LP5523_REG_LED_CURRENT_BASE + i,
- chip->pdata->led_config[i].led_current);
-
- lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0xff);
- /* let current stabilize 2ms before measurements start */
- usleep_range(2000, 10000);
- lp5523_write(chip->client,
- LP5523_REG_LED_TEST_CTRL,
+ lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
+ pdata->led_config[i].led_current);
+
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 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);
- /* ledtest takes 2.7ms */
- usleep_range(3000, 10000);
- ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status);
+ /* 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;
+
if (!(status & LP5523_LEDTEST_DONE))
- usleep_range(3000, 10000);
- ret |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &adc);
+ usleep_range(3000, 6000);/* Was not ready. Wait. */
+
+ ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc);
+ if (ret < 0)
+ goto fail;
if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM)
pos += sprintf(buf + pos, "LED %d FAIL\n", i);
- lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0x00);
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0x00);
/* Restore current */
- lp5523_write(chip->client,
- LP5523_REG_LED_CURRENT_BASE + i,
- chip->leds[led].led_current);
+ lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
+ led->led_current);
led++;
}
if (pos == 0)
@@ -503,563 +666,174 @@ release_lock:
return pos;
}
-static void lp5523_set_brightness(struct led_classdev *cdev,
- enum led_brightness brightness)
-{
- struct lp5523_led *led = cdev_to_led(cdev);
-
- led->brightness = (u8)brightness;
-
- schedule_work(&led->brightness_work);
-}
-
static void lp5523_led_brightness_work(struct work_struct *work)
{
- struct lp5523_led *led = container_of(work,
- struct lp5523_led,
+ struct lp55xx_led *led = container_of(work, struct lp55xx_led,
brightness_work);
- struct lp5523_chip *chip = led_to_lp5523(led);
- struct i2c_client *client = chip->client;
+ struct lp55xx_chip *chip = led->chip;
mutex_lock(&chip->lock);
-
- lp5523_write(client, LP5523_REG_LED_PWM_BASE + led->chan_nr,
+ lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
led->brightness);
-
- mutex_unlock(&chip->lock);
-}
-
-static int lp5523_do_store_load(struct lp5523_engine *engine,
- const char *buf, size_t len)
-{
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
- int ret, nrchars, offset = 0, i = 0;
- char c[3];
- unsigned cmd;
- u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
-
- while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) {
- /* separate sscanfs because length is working only for %s */
- ret = sscanf(buf + offset, "%2s%n ", c, &nrchars);
- ret = sscanf(c, "%2x", &cmd);
- if (ret != 1)
- goto fail;
- pattern[i] = (u8)cmd;
-
- offset += nrchars;
- i++;
- }
-
- /* Each instruction is 16bit long. Check that length is even */
- if (i % 2)
- goto fail;
-
- mutex_lock(&chip->lock);
-
- ret = lp5523_load_program(engine, pattern);
- mutex_unlock(&chip->lock);
-
- if (ret) {
- dev_err(&client->dev, "failed loading pattern\n");
- return ret;
- }
-
- return len;
-fail:
- dev_err(&client->dev, "wrong pattern format\n");
- return -EINVAL;
-}
-
-static ssize_t store_engine_load(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- return lp5523_do_store_load(&chip->engines[nr - 1], buf, len);
-}
-
-#define store_load(nr) \
-static ssize_t store_engine##nr##_load(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_load(dev, attr, buf, len, nr); \
-}
-store_load(1)
-store_load(2)
-store_load(3)
-
-static ssize_t show_engine_mode(struct device *dev,
- struct device_attribute *attr,
- char *buf, int nr)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- switch (chip->engines[nr - 1].mode) {
- case LP5523_CMD_RUN:
- return sprintf(buf, "run\n");
- case LP5523_CMD_LOAD:
- return sprintf(buf, "load\n");
- case LP5523_CMD_DISABLED:
- return sprintf(buf, "disabled\n");
- default:
- return sprintf(buf, "disabled\n");
- }
-}
-
-#define show_mode(nr) \
-static ssize_t show_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- char *buf) \
-{ \
- return show_engine_mode(dev, attr, buf, nr); \
-}
-show_mode(1)
-show_mode(2)
-show_mode(3)
-
-static ssize_t store_engine_mode(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len, int nr)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- struct lp5523_engine *engine = &chip->engines[nr - 1];
- mutex_lock(&chip->lock);
-
- if (!strncmp(buf, "run", 3))
- lp5523_set_mode(engine, LP5523_CMD_RUN);
- else if (!strncmp(buf, "load", 4))
- lp5523_set_mode(engine, LP5523_CMD_LOAD);
- else if (!strncmp(buf, "disabled", 8))
- lp5523_set_mode(engine, LP5523_CMD_DISABLED);
-
mutex_unlock(&chip->lock);
- return len;
-}
-
-#define store_mode(nr) \
-static ssize_t store_engine##nr##_mode(struct device *dev, \
- struct device_attribute *attr, \
- const char *buf, size_t len) \
-{ \
- return store_engine_mode(dev, attr, buf, len, nr); \
}
-store_mode(1)
-store_mode(2)
-store_mode(3)
-
-static ssize_t show_max_current(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5523_led *led = cdev_to_led(led_cdev);
-
- return sprintf(buf, "%d\n", led->max_current);
-}
-
-static ssize_t show_current(struct device *dev,
- struct device_attribute *attr,
- char *buf)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5523_led *led = cdev_to_led(led_cdev);
-
- return sprintf(buf, "%d\n", led->led_current);
-}
-
-static ssize_t store_current(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
-{
- struct led_classdev *led_cdev = dev_get_drvdata(dev);
- struct lp5523_led *led = cdev_to_led(led_cdev);
- struct lp5523_chip *chip = led_to_lp5523(led);
- ssize_t ret;
- unsigned long curr;
-
- if (strict_strtoul(buf, 0, &curr))
- return -EINVAL;
-
- if (curr > led->max_current)
- return -EINVAL;
-
- mutex_lock(&chip->lock);
- ret = lp5523_write(chip->client,
- LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
- (u8)curr);
- mutex_unlock(&chip->lock);
-
- if (ret < 0)
- return ret;
-
- led->led_current = (u8)curr;
-
- return len;
-}
-
-/* led class device attributes */
-static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current);
-static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL);
-
-static struct attribute *lp5523_led_attributes[] = {
- &dev_attr_led_current.attr,
- &dev_attr_max_current.attr,
- NULL,
-};
-
-static struct attribute_group lp5523_led_attribute_group = {
- .attrs = lp5523_led_attributes
-};
-/* device attributes */
-static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO,
- show_engine1_mode, store_engine1_mode);
-static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO,
- show_engine2_mode, store_engine2_mode);
-static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO,
- show_engine3_mode, store_engine3_mode);
-static DEVICE_ATTR(engine1_leds, S_IRUGO | S_IWUGO,
- show_engine1_leds, store_engine1_leds);
-static DEVICE_ATTR(engine2_leds, S_IRUGO | S_IWUGO,
- show_engine2_leds, store_engine2_leds);
-static DEVICE_ATTR(engine3_leds, S_IRUGO | S_IWUGO,
- show_engine3_leds, store_engine3_leds);
-static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load);
-static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load);
-static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load);
-static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL);
+static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
+static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
+static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
+static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds);
+static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds);
+static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds);
+static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
+static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
+static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
+static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest);
static struct attribute *lp5523_attributes[] = {
&dev_attr_engine1_mode.attr,
&dev_attr_engine2_mode.attr,
&dev_attr_engine3_mode.attr,
- &dev_attr_selftest.attr,
- NULL
-};
-
-static struct attribute *lp5523_engine1_attributes[] = {
&dev_attr_engine1_load.attr,
- &dev_attr_engine1_leds.attr,
- NULL
-};
-
-static struct attribute *lp5523_engine2_attributes[] = {
&dev_attr_engine2_load.attr,
- &dev_attr_engine2_leds.attr,
- NULL
-};
-
-static struct attribute *lp5523_engine3_attributes[] = {
&dev_attr_engine3_load.attr,
+ &dev_attr_engine1_leds.attr,
+ &dev_attr_engine2_leds.attr,
&dev_attr_engine3_leds.attr,
- NULL
+ &dev_attr_selftest.attr,
+ NULL,
};
static const struct attribute_group lp5523_group = {
.attrs = lp5523_attributes,
};
-static const struct attribute_group lp5523_engine_group[] = {
- {.attrs = lp5523_engine1_attributes },
- {.attrs = lp5523_engine2_attributes },
- {.attrs = lp5523_engine3_attributes },
+/* Chip specific configurations */
+static struct lp55xx_device_config lp5523_cfg = {
+ .reset = {
+ .addr = LP5523_REG_RESET,
+ .val = LP5523_RESET,
+ },
+ .enable = {
+ .addr = LP5523_REG_ENABLE,
+ .val = LP5523_ENABLE,
+ },
+ .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,
+ .run_engine = lp5523_run_engine,
+ .dev_attr_group = &lp5523_group,
};
-static int lp5523_register_sysfs(struct i2c_client *client)
+static int lp5523_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
{
- struct device *dev = &client->dev;
int ret;
-
- ret = sysfs_create_group(&dev->kobj, &lp5523_group);
- if (ret < 0)
- return ret;
-
- return 0;
-}
-
-static void lp5523_unregister_sysfs(struct i2c_client *client)
-{
- struct lp5523_chip *chip = i2c_get_clientdata(client);
- struct device *dev = &client->dev;
- int i;
-
- sysfs_remove_group(&dev->kobj, &lp5523_group);
-
- for (i = 0; i < ARRAY_SIZE(chip->engines); i++)
- if (chip->engines[i].mode == LP5523_CMD_LOAD)
- sysfs_remove_group(&dev->kobj, &lp5523_engine_group[i]);
-
- for (i = 0; i < chip->num_leds; i++)
- sysfs_remove_group(&chip->leds[i].cdev.dev->kobj,
- &lp5523_led_attribute_group);
-}
-
-/*--------------------------------------------------------------*/
-/* Set chip operating mode */
-/*--------------------------------------------------------------*/
-static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode)
-{
- /* engine to chip */
- struct lp5523_chip *chip = engine_to_lp5523(engine);
- struct i2c_client *client = chip->client;
- struct device *dev = &client->dev;
- int ret = 0;
-
- /* if in that mode already do nothing, except for run */
- if (mode == engine->mode && mode != LP5523_CMD_RUN)
- return 0;
-
- if (mode == LP5523_CMD_RUN) {
- ret = lp5523_run_program(engine);
- } else if (mode == LP5523_CMD_LOAD) {
- lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
- lp5523_set_engine_mode(engine, LP5523_CMD_LOAD);
-
- ret = sysfs_create_group(&dev->kobj, engine->attributes);
- if (ret)
- return ret;
- } else if (mode == LP5523_CMD_DISABLED) {
- lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED);
- }
-
- /* remove load attribute from sysfs if not in load mode */
- if (engine->mode == LP5523_CMD_LOAD && mode != LP5523_CMD_LOAD)
- sysfs_remove_group(&dev->kobj, engine->attributes);
-
- engine->mode = mode;
-
- return ret;
-}
-
-/*--------------------------------------------------------------*/
-/* Probe, Attach, Remove */
-/*--------------------------------------------------------------*/
-static int __init lp5523_init_engine(struct lp5523_engine *engine, int id)
-{
- if (id < 1 || id > LP5523_ENGINES)
- return -1;
- engine->id = id;
- engine->engine_mask = LP5523_ENG_MASK_BASE >> SHIFT_MASK(id);
- engine->prog_page = id - 1;
- engine->mux_page = id + 2;
- engine->attributes = &lp5523_engine_group[id - 1];
-
- return 0;
-}
-
-static int __init lp5523_init_led(struct lp5523_led *led, struct device *dev,
- int chan, struct lp5523_platform_data *pdata)
-{
- char name[32];
- int res;
-
- if (chan >= LP5523_LEDS)
- return -EINVAL;
-
- if (pdata->led_config[chan].led_current) {
- 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;
-
- if (led->chan_nr >= LP5523_LEDS) {
- dev_err(dev, "Use channel numbers between 0 and %d\n",
- LP5523_LEDS - 1);
+ struct lp55xx_chip *chip;
+ struct lp55xx_led *led;
+ struct lp55xx_platform_data *pdata;
+ struct device_node *np = client->dev.of_node;
+
+ if (!dev_get_platdata(&client->dev)) {
+ 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;
}
-
- snprintf(name, 32, "lp5523:channel%d", chan);
-
- led->cdev.name = name;
- led->cdev.brightness_set = lp5523_set_brightness;
- res = led_classdev_register(dev, &led->cdev);
- if (res < 0) {
- dev_err(dev, "couldn't register led on channel %d\n",
- chan);
- return res;
- }
- res = sysfs_create_group(&led->cdev.dev->kobj,
- &lp5523_led_attribute_group);
- if (res < 0) {
- dev_err(dev, "couldn't register current attribute\n");
- led_classdev_unregister(&led->cdev);
- return res;
- }
- } else {
- led->led_current = 0;
}
- return 0;
-}
-
-static struct i2c_driver lp5523_driver;
-
-static int lp5523_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- struct lp5523_chip *chip;
- struct lp5523_platform_data *pdata;
- int ret, i, led;
+ pdata = dev_get_platdata(&client->dev);
- chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
if (!chip)
return -ENOMEM;
- i2c_set_clientdata(client, chip);
- chip->client = client;
-
- pdata = client->dev.platform_data;
+ led = devm_kzalloc(&client->dev,
+ sizeof(*led) * pdata->num_channels, GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
- if (!pdata) {
- dev_err(&client->dev, "no platform data\n");
- ret = -EINVAL;
- goto fail1;
- }
+ chip->cl = client;
+ chip->pdata = pdata;
+ chip->cfg = &lp5523_cfg;
mutex_init(&chip->lock);
- chip->pdata = pdata;
-
- if (pdata->setup_resources) {
- ret = pdata->setup_resources();
- if (ret < 0)
- goto fail1;
- }
-
- if (pdata->enable) {
- pdata->enable(0);
- usleep_range(1000, 10000);
- pdata->enable(1);
- usleep_range(1000, 10000); /* Spec says min 500us */
- }
+ i2c_set_clientdata(client, led);
- ret = lp5523_detect(client);
+ ret = lp55xx_init_device(chip);
if (ret)
- goto fail2;
-
- dev_info(&client->dev, "LP5523 Programmable led chip found\n");
-
- /* Initialize engines */
- for (i = 0; i < ARRAY_SIZE(chip->engines); i++) {
- ret = lp5523_init_engine(&chip->engines[i], i + 1);
- if (ret) {
- dev_err(&client->dev, "error initializing engine\n");
- goto fail2;
- }
- }
- ret = lp5523_configure(client);
- if (ret < 0) {
- dev_err(&client->dev, "error configuring chip\n");
- goto fail2;
- }
-
- /* Initialize leds */
- chip->num_channels = pdata->num_channels;
- chip->num_leds = 0;
- led = 0;
- for (i = 0; i < pdata->num_channels; i++) {
- /* Do not initialize channels that are not connected */
- if (pdata->led_config[i].led_current == 0)
- continue;
-
- ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata);
- if (ret) {
- dev_err(&client->dev, "error initializing leds\n");
- goto fail3;
- }
- chip->num_leds++;
+ goto err_init;
- chip->leds[led].id = led;
- /* Set LED current */
- lp5523_write(client,
- LP5523_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr,
- chip->leds[led].led_current);
+ dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
- INIT_WORK(&(chip->leds[led].brightness_work),
- lp5523_led_brightness_work);
-
- led++;
- }
+ ret = lp55xx_register_leds(led, chip);
+ if (ret)
+ goto err_register_leds;
- ret = lp5523_register_sysfs(client);
+ ret = lp55xx_register_sysfs(chip);
if (ret) {
dev_err(&client->dev, "registering sysfs failed\n");
- goto fail3;
+ goto err_register_sysfs;
}
- return ret;
-fail3:
- for (i = 0; i < chip->num_leds; i++) {
- led_classdev_unregister(&chip->leds[i].cdev);
- cancel_work_sync(&chip->leds[i].brightness_work);
- }
-fail2:
- if (pdata->enable)
- pdata->enable(0);
- if (pdata->release_resources)
- pdata->release_resources();
-fail1:
- kfree(chip);
+
+ 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 lp5523_chip *chip = i2c_get_clientdata(client);
- int i;
+ struct lp55xx_led *led = i2c_get_clientdata(client);
+ struct lp55xx_chip *chip = led->chip;
- lp5523_unregister_sysfs(client);
+ lp5523_stop_all_engines(chip);
+ lp55xx_unregister_sysfs(chip);
+ lp55xx_unregister_leds(led, chip);
+ lp55xx_deinit_device(chip);
- for (i = 0; i < chip->num_leds; i++) {
- led_classdev_unregister(&chip->leds[i].cdev);
- cancel_work_sync(&chip->leds[i].brightness_work);
- }
-
- if (chip->pdata->enable)
- chip->pdata->enable(0);
- if (chip->pdata->release_resources)
- chip->pdata->release_resources();
- kfree(chip);
return 0;
}
static const struct i2c_device_id lp5523_id[] = {
- { "lp5523", 0 },
+ { "lp5523", LP5523 },
+ { "lp55231", LP55231 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5523_id);
+#ifdef CONFIG_OF
+static const struct of_device_id of_lp5523_leds_match[] = {
+ { .compatible = "national,lp5523", },
+ { .compatible = "ti,lp55231", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_lp5523_leds_match);
+#endif
+
static struct i2c_driver lp5523_driver = {
.driver = {
- .name = "lp5523",
+ .name = "lp5523x",
+ .of_match_table = of_match_ptr(of_lp5523_leds_match),
},
.probe = lp5523_probe,
.remove = lp5523_remove,
.id_table = lp5523_id,
};
-static int __init lp5523_init(void)
-{
- int ret;
-
- ret = i2c_add_driver(&lp5523_driver);
-
- if (ret < 0)
- printk(KERN_ALERT "Adding lp5523 driver failed\n");
-
- return ret;
-}
-
-static void __exit lp5523_exit(void)
-{
- i2c_del_driver(&lp5523_driver);
-}
-
-module_init(lp5523_init);
-module_exit(lp5523_exit);
+module_i2c_driver(lp5523_driver);
MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>");
+MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP5523 LED engine");
MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c
new file mode 100644
index 00000000000..ca85724ab13
--- /dev/null
+++ b/drivers/leds/leds-lp5562.c
@@ -0,0 +1,617 @@
+/*
+ * 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/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.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 */
+#define LP5562_REG_ENABLE 0x00
+#define LP5562_EXEC_ENG1_M 0x30
+#define LP5562_EXEC_ENG2_M 0x0C
+#define LP5562_EXEC_ENG3_M 0x03
+#define LP5562_EXEC_M 0x3F
+#define LP5562_MASTER_ENABLE 0x40 /* Chip master enable */
+#define LP5562_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */
+#define LP5562_EXEC_RUN 0x2A
+#define LP5562_ENABLE_DEFAULT \
+ (LP5562_MASTER_ENABLE | LP5562_LOGARITHMIC_PWM)
+#define LP5562_ENABLE_RUN_PROGRAM \
+ (LP5562_ENABLE_DEFAULT | LP5562_EXEC_RUN)
+
+/* 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
+#define LP5562_REG_G_PWM 0x03
+#define LP5562_REG_B_PWM 0x02
+#define LP5562_REG_W_PWM 0x0E
+
+/* CURRENT Registers */
+#define LP5562_REG_R_CURRENT 0x07
+#define LP5562_REG_G_CURRENT 0x06
+#define LP5562_REG_B_CURRENT 0x05
+#define LP5562_REG_W_CURRENT 0x0F
+
+/* CONFIG Register 08h */
+#define LP5562_REG_CONFIG 0x08
+#define LP5562_PWM_HF 0x40
+#define LP5562_PWRSAVE_EN 0x20
+#define LP5562_CLK_INT 0x01 /* Internal clock */
+#define LP5562_DEFAULT_CFG (LP5562_PWM_HF | LP5562_PWRSAVE_EN)
+
+/* RESET Register 0Dh */
+#define LP5562_REG_RESET 0x0D
+#define LP5562_RESET 0xFF
+
+/* PROGRAM ENGINE Registers */
+#define LP5562_REG_PROG_MEM_ENG1 0x10
+#define LP5562_REG_PROG_MEM_ENG2 0x30
+#define LP5562_REG_PROG_MEM_ENG3 0x50
+
+/* LEDMAP Register 70h */
+#define LP5562_REG_ENG_SEL 0x70
+#define LP5562_ENG_SEL_PWM 0
+#define LP5562_ENG_FOR_RGB_M 0x3F
+#define LP5562_ENG_SEL_RGB 0x1B /* R:ENG1, G:ENG2, B:ENG3 */
+#define LP5562_ENG_FOR_W_M 0xC0
+#define LP5562_ENG1_FOR_W 0x40 /* W:ENG1 */
+#define LP5562_ENG2_FOR_W 0x80 /* W:ENG2 */
+#define LP5562_ENG3_FOR_W 0xC0 /* W:ENG3 */
+
+/* Program Commands */
+#define LP5562_CMD_DISABLE 0x00
+#define LP5562_CMD_LOAD 0x15
+#define LP5562_CMD_RUN 0x2A
+#define LP5562_CMD_DIRECT 0x3F
+#define LP5562_PATTERN_OFF 0
+
+static inline void lp5562_wait_opmode_done(void)
+{
+ /* operation mode change needs to be longer than 153 us */
+ usleep_range(200, 300);
+}
+
+static inline void lp5562_wait_enable_done(void)
+{
+ /* it takes more 488 us to update ENABLE register */
+ usleep_range(500, 600);
+}
+
+static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current)
+{
+ u8 addr[] = {
+ LP5562_REG_R_CURRENT,
+ LP5562_REG_G_CURRENT,
+ LP5562_REG_B_CURRENT,
+ LP5562_REG_W_CURRENT,
+ };
+
+ led->led_current = 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_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);
+}
+
+static int lp5562_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
+ u8 cfg = LP5562_DEFAULT_CFG;
+
+ /* Set all PWMs to direct control mode */
+ ret = lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT);
+ if (ret)
+ return ret;
+
+ lp5562_wait_opmode_done();
+
+ /* Update configuration for the clock setting */
+ if (!lp55xx_is_extclk_used(chip))
+ cfg |= LP5562_CLK_INT;
+
+ ret = lp55xx_write(chip, LP5562_REG_CONFIG, cfg);
+ if (ret)
+ return ret;
+
+ /* Initialize all channels PWM to zero -> leds off */
+ lp55xx_write(chip, LP5562_REG_R_PWM, 0);
+ lp55xx_write(chip, LP5562_REG_G_PWM, 0);
+ lp55xx_write(chip, LP5562_REG_B_PWM, 0);
+ lp55xx_write(chip, LP5562_REG_W_PWM, 0);
+
+ /* Set LED map as register PWM by default */
+ lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM);
+
+ return 0;
+}
+
+static void lp5562_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;
+ u8 addr[] = {
+ LP5562_REG_R_PWM,
+ LP5562_REG_G_PWM,
+ LP5562_REG_B_PWM,
+ LP5562_REG_W_PWM,
+ };
+
+ mutex_lock(&chip->lock);
+ lp55xx_write(chip, addr[led->chan_nr], led->brightness);
+ mutex_unlock(&chip->lock);
+}
+
+static void lp5562_write_program_memory(struct lp55xx_chip *chip,
+ u8 base, const u8 *rgb, int size)
+{
+ int i;
+
+ if (!rgb || size <= 0)
+ return;
+
+ for (i = 0; i < size; i++)
+ lp55xx_write(chip, base + i, *(rgb + i));
+
+ lp55xx_write(chip, base + i, 0);
+ lp55xx_write(chip, base + i + 1, 0);
+}
+
+/* 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;
+}
+
+static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode)
+{
+ struct lp55xx_predef_pattern *ptn;
+ int i;
+
+ if (mode == LP5562_PATTERN_OFF) {
+ lp5562_run_engine(chip, false);
+ return 0;
+ }
+
+ ptn = chip->pdata->patterns + (mode - 1);
+ if (!ptn || _is_pc_overflow(ptn)) {
+ dev_err(&chip->cl->dev, "invalid pattern data\n");
+ return -EINVAL;
+ }
+
+ lp5562_stop_engine(chip);
+
+ /* Set LED map as RGB */
+ lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB);
+
+ /* Load engines */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ chip->engine_idx = i;
+ lp5562_load_engine(chip);
+ }
+
+ /* Clear program registers */
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1, 0);
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1 + 1, 0);
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2, 0);
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2 + 1, 0);
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3, 0);
+ lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3 + 1, 0);
+
+ /* Program engines */
+ lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG1,
+ ptn->r, ptn->size_r);
+ lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG2,
+ ptn->g, ptn->size_g);
+ lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG3,
+ ptn->b, ptn->size_b);
+
+ /* Run engines */
+ lp5562_run_engine(chip, true);
+
+ return 0;
+}
+
+static ssize_t lp5562_store_pattern(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;
+ struct lp55xx_predef_pattern *ptn = chip->pdata->patterns;
+ int num_patterns = chip->pdata->num_patterns;
+ unsigned long mode;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &mode);
+ if (ret)
+ return ret;
+
+ if (mode > num_patterns || !ptn)
+ return -EINVAL;
+
+ mutex_lock(&chip->lock);
+ ret = lp5562_run_predef_led_pattern(chip, mode);
+ mutex_unlock(&chip->lock);
+
+ if (ret)
+ return ret;
+
+ return len;
+}
+
+static ssize_t lp5562_store_engine_mux(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;
+ u8 mask;
+ u8 val;
+
+ /* LED map
+ * R ... Engine 1 (fixed)
+ * G ... Engine 2 (fixed)
+ * B ... Engine 3 (fixed)
+ * W ... Engine 1 or 2 or 3
+ */
+
+ if (sysfs_streq(buf, "RGB")) {
+ mask = LP5562_ENG_FOR_RGB_M;
+ val = LP5562_ENG_SEL_RGB;
+ } else if (sysfs_streq(buf, "W")) {
+ enum lp55xx_engine_index idx = chip->engine_idx;
+
+ mask = LP5562_ENG_FOR_W_M;
+ switch (idx) {
+ case LP55XX_ENGINE_1:
+ val = LP5562_ENG1_FOR_W;
+ break;
+ case LP55XX_ENGINE_2:
+ val = LP5562_ENG2_FOR_W;
+ break;
+ case LP55XX_ENGINE_3:
+ val = LP5562_ENG3_FOR_W;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ } else {
+ dev_err(dev, "choose RGB or W\n");
+ return -EINVAL;
+ }
+
+ mutex_lock(&chip->lock);
+ lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val);
+ mutex_unlock(&chip->lock);
+
+ return len;
+}
+
+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,
+ &dev_attr_engine_mux.attr,
+ NULL,
+};
+
+static const struct attribute_group lp5562_group = {
+ .attrs = lp5562_attributes,
+};
+
+/* Chip specific configurations */
+static struct lp55xx_device_config lp5562_cfg = {
+ .max_channel = LP5562_MAX_LEDS,
+ .reset = {
+ .addr = LP5562_REG_RESET,
+ .val = LP5562_RESET,
+ },
+ .enable = {
+ .addr = LP5562_REG_ENABLE,
+ .val = LP5562_ENABLE_DEFAULT,
+ },
+ .post_init_device = lp5562_post_init_device,
+ .set_led_current = lp5562_set_led_current,
+ .brightness_work_fn = lp5562_led_brightness_work,
+ .run_engine = lp5562_run_engine,
+ .firmware_cb = lp5562_firmware_loaded,
+ .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 (!dev_get_platdata(&client->dev)) {
+ 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 = dev_get_platdata(&client->dev);
+
+ 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 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp5562_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_lp5562_leds_match[] = {
+ { .compatible = "ti,lp5562", },
+ {},
+};
+
+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),
+ },
+ .probe = lp5562_probe,
+ .remove = lp5562_remove,
+ .id_table = lp5562_id,
+};
+
+module_i2c_driver(lp5562_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP5562 LED Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
new file mode 100644
index 00000000000..88317b4f7bf
--- /dev/null
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -0,0 +1,613 @@
+/*
+ * LP5521/LP5523/LP55231/LP5562 Common 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.
+ *
+ * Derived from leds-lp5521.c, leds-lp5523.c
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/platform_data/leds-lp55xx.h>
+#include <linux/slab.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+
+#include "leds-lp55xx-common.h"
+
+/* External clock rate */
+#define LP55XX_CLK_32K 32768
+
+static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev)
+{
+ return container_of(cdev, struct lp55xx_led, cdev);
+}
+
+static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev)
+{
+ return cdev_to_lp55xx_led(dev_get_drvdata(dev));
+}
+
+static void lp55xx_reset_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_device_config *cfg = chip->cfg;
+ u8 addr = cfg->reset.addr;
+ u8 val = cfg->reset.val;
+
+ /* no error checking here because no ACK from the device after reset */
+ lp55xx_write(chip, addr, val);
+}
+
+static int lp55xx_detect_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_device_config *cfg = chip->cfg;
+ u8 addr = cfg->enable.addr;
+ u8 val = cfg->enable.val;
+ int ret;
+
+ ret = lp55xx_write(chip, addr, val);
+ if (ret)
+ return ret;
+
+ usleep_range(1000, 2000);
+
+ ret = lp55xx_read(chip, addr, &val);
+ if (ret)
+ return ret;
+
+ if (val != cfg->enable.val)
+ return -ENODEV;
+
+ return 0;
+}
+
+static int lp55xx_post_init_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_device_config *cfg = chip->cfg;
+
+ if (!cfg->post_init_device)
+ return 0;
+
+ return cfg->post_init_device(chip);
+}
+
+static ssize_t lp55xx_show_current(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);
+}
+
+static ssize_t lp55xx_store_current(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lp55xx_led *led = dev_to_lp55xx_led(dev);
+ struct lp55xx_chip *chip = led->chip;
+ unsigned long curr;
+
+ if (kstrtoul(buf, 0, &curr))
+ return -EINVAL;
+
+ if (curr > led->max_current)
+ return -EINVAL;
+
+ if (!chip->cfg->set_led_current)
+ return len;
+
+ mutex_lock(&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,
+ 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);
+}
+
+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 struct attribute *lp55xx_led_attributes[] = {
+ &dev_attr_led_current.attr,
+ &dev_attr_max_current.attr,
+ NULL,
+};
+
+static struct attribute_group lp55xx_led_attr_group = {
+ .attrs = lp55xx_led_attributes
+};
+
+static void lp55xx_set_brightness(struct led_classdev *cdev,
+ enum led_brightness brightness)
+{
+ struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
+
+ led->brightness = (u8)brightness;
+ schedule_work(&led->brightness_work);
+}
+
+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;
+ struct device *dev = &chip->cl->dev;
+ char name[32];
+ int ret;
+ int max_channel = cfg->max_channel;
+
+ if (chan >= max_channel) {
+ dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel);
+ return -EINVAL;
+ }
+
+ if (pdata->led_config[chan].led_current == 0)
+ return 0;
+
+ 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;
+ led->cdev.default_trigger = pdata->led_config[chan].default_trigger;
+
+ if (led->chan_nr >= max_channel) {
+ dev_err(dev, "Use channel numbers between 0 and %d\n",
+ max_channel - 1);
+ return -EINVAL;
+ }
+
+ led->cdev.brightness_set = lp55xx_set_brightness;
+
+ 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;
+}
+
+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;
+ }
+
+ /* handling firmware data is chip dependent */
+ mutex_lock(&chip->lock);
+
+ chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
+ chip->fw = fw;
+ if (chip->cfg->firmware_cb)
+ chip->cfg->firmware_cb(chip);
+
+ mutex_unlock(&chip->lock);
+
+out:
+ /* firmware should be released for other channel use */
+ release_firmware(chip->fw);
+}
+
+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,
+ GFP_KERNEL, chip, lp55xx_firmware_loaded);
+}
+
+static ssize_t lp55xx_show_engine_select(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;
+
+ 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)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ unsigned long val;
+ int ret;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ /* select the engine to be run */
+
+ switch (val) {
+ 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);
+ break;
+ default:
+ dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
+ return -EINVAL;
+ }
+
+ if (ret) {
+ dev_err(dev, "request firmware err: %d\n", ret);
+ return ret;
+ }
+
+ return len;
+}
+
+static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
+{
+ if (chip->cfg->run_engine)
+ 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)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ /* run or stop the selected engine */
+
+ if (val <= 0) {
+ lp55xx_run_engine(chip, false);
+ return len;
+ }
+
+ mutex_lock(&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 struct attribute *lp55xx_engine_attributes[] = {
+ &dev_attr_select_engine.attr,
+ &dev_attr_run_engine.attr,
+ NULL,
+};
+
+static const struct attribute_group lp55xx_engine_attr_group = {
+ .attrs = lp55xx_engine_attributes,
+};
+
+int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val)
+{
+ return i2c_smbus_write_byte_data(chip->cl, reg, val);
+}
+EXPORT_SYMBOL_GPL(lp55xx_write);
+
+int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val)
+{
+ s32 ret;
+
+ ret = i2c_smbus_read_byte_data(chip->cl, reg);
+ if (ret < 0)
+ return ret;
+
+ *val = ret;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lp55xx_read);
+
+int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val)
+{
+ int ret;
+ u8 tmp;
+
+ ret = lp55xx_read(chip, reg, &tmp);
+ if (ret)
+ return ret;
+
+ tmp &= ~mask;
+ tmp |= val & mask;
+
+ return lp55xx_write(chip, reg, tmp);
+}
+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");
+ 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);
+ 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:
+ dev_info(&chip->cl->dev, "internal clock used\n");
+ return false;
+}
+EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used);
+
+int lp55xx_init_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_platform_data *pdata;
+ struct lp55xx_device_config *cfg;
+ struct device *dev = &chip->cl->dev;
+ int ret = 0;
+
+ WARN_ON(!chip);
+
+ pdata = chip->pdata;
+ cfg = chip->cfg;
+
+ if (!pdata || !cfg)
+ return -EINVAL;
+
+ if (gpio_is_valid(pdata->enable_gpio)) {
+ ret = devm_gpio_request_one(dev, pdata->enable_gpio,
+ GPIOF_DIR_OUT, "lp5523_enable");
+ if (ret < 0) {
+ dev_err(dev, "could not acquire enable gpio (err=%d)\n",
+ ret);
+ goto err;
+ }
+
+ gpio_set_value(pdata->enable_gpio, 0);
+ usleep_range(1000, 2000); /* Keep enable down at least 1ms */
+ gpio_set_value(pdata->enable_gpio, 1);
+ usleep_range(1000, 2000); /* 500us abs min. */
+ }
+
+ lp55xx_reset_device(chip);
+
+ /*
+ * Exact value is not available. 10 - 20ms
+ * appears to be enough for reset.
+ */
+ usleep_range(10000, 20000);
+
+ ret = lp55xx_detect_device(chip);
+ if (ret) {
+ dev_err(dev, "device detection err: %d\n", ret);
+ goto err;
+ }
+
+ /* chip specific initialization */
+ ret = lp55xx_post_init_device(chip);
+ if (ret) {
+ dev_err(dev, "post init device err: %d\n", ret);
+ goto err_post_init;
+ }
+
+ return 0;
+
+err_post_init:
+ lp55xx_deinit_device(chip);
+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 (gpio_is_valid(pdata->enable_gpio))
+ gpio_set_value(pdata->enable_gpio, 0);
+}
+EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
+
+int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
+{
+ struct lp55xx_platform_data *pdata = chip->pdata;
+ struct lp55xx_device_config *cfg = chip->cfg;
+ int num_channels = pdata->num_channels;
+ struct lp55xx_led *each;
+ u8 led_current;
+ int ret;
+ int i;
+
+ if (!cfg->brightness_work_fn) {
+ dev_err(&chip->cl->dev, "empty brightness configuration\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < num_channels; i++) {
+
+ /* do not initialize channels that are not connected */
+ if (pdata->led_config[i].led_current == 0)
+ continue;
+
+ led_current = pdata->led_config[i].led_current;
+ each = led + i;
+ ret = lp55xx_init_led(each, chip, i);
+ if (ret)
+ goto err_init_led;
+
+ INIT_WORK(&each->brightness_work, cfg->brightness_work_fn);
+
+ chip->num_leds++;
+ each->chip = chip;
+
+ /* setting led current at each channel */
+ if (cfg->set_led_current)
+ cfg->set_led_current(each, led_current);
+ }
+
+ 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)
+{
+ struct device *dev = &chip->cl->dev;
+ struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+
+ if (!cfg->run_engine || !cfg->firmware_cb)
+ goto dev_specific_attrs;
+
+ ret = sysfs_create_group(&dev->kobj, &lp55xx_engine_attr_group);
+ if (ret)
+ return ret;
+
+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)
+{
+ struct device *dev = &chip->cl->dev;
+ 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)
+{
+ struct device_node *child;
+ struct lp55xx_platform_data *pdata;
+ struct lp55xx_led_config *cfg;
+ int num_channels;
+ int i = 0;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return -ENOMEM;
+
+ num_channels = of_get_child_count(np);
+ if (num_channels == 0) {
+ dev_err(dev, "no LED channels\n");
+ return -EINVAL;
+ }
+
+ cfg = devm_kzalloc(dev, sizeof(*cfg) * num_channels, GFP_KERNEL);
+ if (!cfg)
+ return -ENOMEM;
+
+ pdata->led_config = &cfg[0];
+ pdata->num_channels = num_channels;
+
+ for_each_child_of_node(np, child) {
+ cfg[i].chan_nr = 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);
+ cfg[i].default_trigger =
+ of_get_property(child, "linux,default-trigger", NULL);
+
+ i++;
+ }
+
+ of_property_read_string(np, "label", &pdata->label);
+ of_property_read_u8(np, "clock-mode", &pdata->clock_mode);
+
+ pdata->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0);
+
+ /* LP8501 specific */
+ of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel);
+
+ dev->platform_data = pdata;
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata);
+
+MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
+MODULE_DESCRIPTION("LP55xx Common Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h
new file mode 100644
index 00000000000..cceab483edd
--- /dev/null
+++ b/drivers/leds/leds-lp55xx-common.h
@@ -0,0 +1,208 @@
+/*
+ * LP55XX Common Driver Header
+ *
+ * Copyright (C) 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.
+ *
+ * Derived from leds-lp5521.c, leds-lp5523.c
+ */
+
+#ifndef _LEDS_LP55XX_COMMON_H
+#define _LEDS_LP55XX_COMMON_H
+
+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 show_mode(nr) \
+static ssize_t show_engine##nr##_mode(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return show_engine_mode(dev, attr, buf, nr); \
+}
+
+#define store_mode(nr) \
+static ssize_t store_engine##nr##_mode(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return store_engine_mode(dev, attr, buf, len, nr); \
+}
+
+#define show_leds(nr) \
+static ssize_t show_engine##nr##_leds(struct device *dev, \
+ struct device_attribute *attr, \
+ char *buf) \
+{ \
+ return show_engine_leds(dev, attr, buf, nr); \
+}
+
+#define store_leds(nr) \
+static ssize_t store_engine##nr##_leds(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return store_engine_leds(dev, attr, buf, len, nr); \
+}
+
+#define store_load(nr) \
+static ssize_t store_engine##nr##_load(struct device *dev, \
+ struct device_attribute *attr, \
+ const char *buf, size_t len) \
+{ \
+ return store_engine_load(dev, attr, buf, len, nr); \
+}
+
+struct lp55xx_led;
+struct lp55xx_chip;
+
+/*
+ * struct lp55xx_reg
+ * @addr : Register address
+ * @val : Register value
+ */
+struct lp55xx_reg {
+ u8 addr;
+ u8 val;
+};
+
+/*
+ * struct lp55xx_device_config
+ * @reset : Chip specific reset command
+ * @enable : Chip specific enable command
+ * @max_channel : Maximum number of channels
+ * @post_init_device : Chip specific initialization code
+ * @brightness_work_fn : Brightness work 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 reset;
+ const struct lp55xx_reg enable;
+ 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);
+
+ /* current setting function */
+ void (*set_led_current) (struct lp55xx_led *led, u8 led_current);
+
+ /* access program memory when the firmware is loaded */
+ void (*firmware_cb)(struct lp55xx_chip *chip);
+
+ /* used for running firmware LED patterns */
+ void (*run_engine) (struct lp55xx_chip *chip, bool start);
+
+ /* additional device specific attributes */
+ const struct attribute_group *dev_attr_group;
+};
+
+/*
+ * 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
+ * @lock : Lock for user-space interface
+ * @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;
+ enum lp55xx_engine_index engine_idx;
+ struct lp55xx_engine engines[LP55XX_ENGINE_MAX];
+ const struct firmware *fw;
+};
+
+/*
+ * struct lp55xx_led
+ * @chan_nr : Channel number
+ * @cdev : LED class device
+ * @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;
+ u8 led_current;
+ u8 max_current;
+ struct work_struct brightness_work;
+ u8 brightness;
+ struct lp55xx_chip *chip;
+};
+
+/* register access */
+extern int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val);
+extern int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val);
+extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg,
+ u8 mask, u8 val);
+
+/* 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 device attributes functions */
+extern int lp55xx_register_sysfs(struct lp55xx_chip *chip);
+extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip);
+
+/* common device tree population function */
+extern int lp55xx_of_populate_pdata(struct device *dev,
+ struct device_node *np);
+
+#endif /* _LEDS_LP55XX_COMMON_H */
diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c
new file mode 100644
index 00000000000..00f068b0fa6
--- /dev/null
+++ b/drivers/leds/leds-lp8501.c
@@ -0,0 +1,411 @@
+/*
+ * TI LP8501 9 channel 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/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_PROGRAM_LENGTH 32
+#define LP8501_MAX_LEDS 9
+
+/* Registers */
+#define LP8501_REG_ENABLE 0x00
+#define LP8501_ENABLE BIT(6)
+#define LP8501_EXEC_M 0x3F
+#define LP8501_EXEC_ENG1_M 0x30
+#define LP8501_EXEC_ENG2_M 0x0C
+#define LP8501_EXEC_ENG3_M 0x03
+#define LP8501_RUN_ENG1 0x20
+#define LP8501_RUN_ENG2 0x08
+#define LP8501_RUN_ENG3 0x02
+
+#define LP8501_REG_OP_MODE 0x01
+#define LP8501_MODE_ENG1_M 0x30
+#define LP8501_MODE_ENG2_M 0x0C
+#define LP8501_MODE_ENG3_M 0x03
+#define LP8501_LOAD_ENG1 0x10
+#define LP8501_LOAD_ENG2 0x04
+#define LP8501_LOAD_ENG3 0x01
+
+#define LP8501_REG_PWR_CONFIG 0x05
+#define LP8501_PWR_CONFIG_M 0x03
+
+#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_AUTO 0x18
+#define LP8501_INT_CLK BIT(0)
+#define LP8501_DEFAULT_CFG \
+ (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO)
+
+#define LP8501_REG_RESET 0x3D
+#define LP8501_RESET 0xFF
+
+#define LP8501_REG_PROG_PAGE_SEL 0x4F
+#define LP8501_PAGE_ENG1 0
+#define LP8501_PAGE_ENG2 1
+#define LP8501_PAGE_ENG3 2
+
+#define LP8501_REG_PROG_MEM 0x50
+
+#define LP8501_ENG1_IS_LOADING(mode) \
+ ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1)
+#define LP8501_ENG2_IS_LOADING(mode) \
+ ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2)
+#define LP8501_ENG3_IS_LOADING(mode) \
+ ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3)
+
+static inline void lp8501_wait_opmode_done(void)
+{
+ usleep_range(1000, 2000);
+}
+
+static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current)
+{
+ led->led_current = led_current;
+ lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr,
+ led_current);
+}
+
+static int lp8501_post_init_device(struct lp55xx_chip *chip)
+{
+ int ret;
+ 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;
+
+ 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_load_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ u8 mask[] = {
+ [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M,
+ [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M,
+ [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M,
+ };
+
+ u8 val[] = {
+ [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1,
+ [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2,
+ [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3,
+ };
+
+ u8 page_sel[] = {
+ [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1,
+ [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2,
+ [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3,
+ };
+
+ lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]);
+
+ lp8501_wait_opmode_done();
+
+ lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]);
+}
+
+static void lp8501_stop_engine(struct lp55xx_chip *chip)
+{
+ lp55xx_write(chip, LP8501_REG_OP_MODE, 0);
+ lp8501_wait_opmode_done();
+}
+
+static void lp8501_turn_off_channels(struct lp55xx_chip *chip)
+{
+ int i;
+
+ for (i = 0; i < LP8501_MAX_LEDS; i++)
+ lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0);
+}
+
+static void lp8501_run_engine(struct lp55xx_chip *chip, bool start)
+{
+ int ret;
+ u8 mode;
+ u8 exec;
+
+ /* stop engine */
+ if (!start) {
+ lp8501_stop_engine(chip);
+ lp8501_turn_off_channels(chip);
+ return;
+ }
+
+ /*
+ * To run the engine,
+ * operation mode and enable register should updated at the same time
+ */
+
+ ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode);
+ if (ret)
+ return;
+
+ ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec);
+ if (ret)
+ return;
+
+ /* change operation mode to RUN only when each engine is loading */
+ if (LP8501_ENG1_IS_LOADING(mode)) {
+ mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1;
+ exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1;
+ }
+
+ if (LP8501_ENG2_IS_LOADING(mode)) {
+ mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2;
+ exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2;
+ }
+
+ if (LP8501_ENG3_IS_LOADING(mode)) {
+ mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3;
+ exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3;
+ }
+
+ lp55xx_write(chip, LP8501_REG_OP_MODE, mode);
+ lp8501_wait_opmode_done();
+
+ lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec);
+}
+
+static int lp8501_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
+{
+ u8 pattern[LP8501_PROGRAM_LENGTH] = {0};
+ unsigned cmd;
+ char c[3];
+ int update_size;
+ int nrchars;
+ int offset = 0;
+ int ret;
+ int i;
+
+ /* clear program memory before updating */
+ for (i = 0; i < LP8501_PROGRAM_LENGTH; i++)
+ lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0);
+
+ i = 0;
+ while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) {
+ /* separate sscanfs because length is working only for %s */
+ ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
+ if (ret != 1)
+ goto err;
+
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
+
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
+
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
+
+ update_size = i;
+ for (i = 0; i < update_size; i++)
+ lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]);
+
+ return 0;
+
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
+}
+
+static void lp8501_firmware_loaded(struct lp55xx_chip *chip)
+{
+ const struct firmware *fw = chip->fw;
+
+ if (fw->size > LP8501_PROGRAM_LENGTH) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
+
+ /*
+ * Program memory sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
+
+ lp8501_load_engine(chip);
+ lp8501_update_program_memory(chip, fw->data, fw->size);
+}
+
+static void lp8501_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, LP8501_REG_LED_PWM_BASE + led->chan_nr,
+ led->brightness);
+ mutex_unlock(&chip->lock);
+}
+
+/* Chip specific configurations */
+static struct lp55xx_device_config lp8501_cfg = {
+ .reset = {
+ .addr = LP8501_REG_RESET,
+ .val = LP8501_RESET,
+ },
+ .enable = {
+ .addr = LP8501_REG_ENABLE,
+ .val = LP8501_ENABLE,
+ },
+ .max_channel = LP8501_MAX_LEDS,
+ .post_init_device = lp8501_post_init_device,
+ .brightness_work_fn = lp8501_led_brightness_work,
+ .set_led_current = lp8501_set_led_current,
+ .firmware_cb = lp8501_firmware_loaded,
+ .run_engine = lp8501_run_engine,
+};
+
+static int lp8501_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 (!dev_get_platdata(&client->dev)) {
+ 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 = dev_get_platdata(&client->dev);
+
+ 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 = &lp8501_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 lp8501_remove(struct i2c_client *client)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(client);
+ struct lp55xx_chip *chip = led->chip;
+
+ lp8501_stop_engine(chip);
+ lp55xx_unregister_sysfs(chip);
+ lp55xx_unregister_leds(led, chip);
+ lp55xx_deinit_device(chip);
+
+ return 0;
+}
+
+static const struct i2c_device_id lp8501_id[] = {
+ { "lp8501", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp8501_id);
+
+#ifdef CONFIG_OF
+static const struct of_device_id of_lp8501_leds_match[] = {
+ { .compatible = "ti,lp8501", },
+ {},
+};
+
+MODULE_DEVICE_TABLE(of, of_lp8501_leds_match);
+#endif
+
+static struct i2c_driver lp8501_driver = {
+ .driver = {
+ .name = "lp8501",
+ .of_match_table = of_match_ptr(of_lp8501_leds_match),
+ },
+ .probe = lp8501_probe,
+ .remove = lp8501_remove,
+ .id_table = lp8501_id,
+};
+
+module_i2c_driver(lp8501_driver);
+
+MODULE_DESCRIPTION("Texas Instruments LP8501 LED drvier");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-lp8788.c b/drivers/leds/leds-lp8788.c
new file mode 100644
index 00000000000..7c2cb384e7a
--- /dev/null
+++ b/drivers/leds/leds-lp8788.c
@@ -0,0 +1,194 @@
+/*
+ * 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>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/mfd/lp8788.h>
+#include <linux/mfd/lp8788-isink.h>
+
+#define MAX_BRIGHTNESS LP8788_ISINK_MAX_PWM
+#define DEFAULT_LED_NAME "keyboard-backlight"
+
+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;
+};
+
+struct lp8788_led_config {
+ enum lp8788_isink_scale scale;
+ enum lp8788_isink_number num;
+ int iout;
+};
+
+static struct lp8788_led_config default_led_config = {
+ .scale = LP8788_ISINK_SCALE_100mA,
+ .num = LP8788_ISINK_3,
+ .iout = 0,
+};
+
+static int lp8788_led_init_device(struct lp8788_led *led,
+ struct lp8788_led_platform_data *pdata)
+{
+ struct lp8788_led_config *cfg = &default_led_config;
+ u8 addr, mask, val;
+ int ret;
+
+ if (pdata) {
+ cfg->scale = pdata->scale;
+ cfg->num = pdata->num;
+ cfg->iout = pdata->iout_code;
+ }
+
+ led->isink_num = cfg->num;
+
+ /* scale configuration */
+ addr = LP8788_ISINK_CTRL;
+ mask = 1 << (cfg->num + LP8788_ISINK_SCALE_OFFSET);
+ val = cfg->scale << (cfg->num + LP8788_ISINK_SCALE_OFFSET);
+ ret = lp8788_update_bits(led->lp, addr, mask, val);
+ if (ret)
+ return ret;
+
+ /* current configuration */
+ addr = lp8788_iout_addr[cfg->num];
+ mask = lp8788_iout_mask[cfg->num];
+ val = cfg->iout;
+
+ return lp8788_update_bits(led->lp, addr, mask, val);
+}
+
+static void lp8788_led_enable(struct lp8788_led *led,
+ enum lp8788_isink_number num, int on)
+{
+ u8 mask = 1 << num;
+ u8 val = on << num;
+
+ if (lp8788_update_bits(led->lp, LP8788_ISINK_CTRL, mask, val))
+ return;
+
+ led->on = on;
+}
+
+static void lp8788_led_work(struct work_struct *work)
+{
+ struct lp8788_led *led = container_of(work, struct lp8788_led, work);
+ enum lp8788_isink_number num = led->isink_num;
+ int enable;
+ u8 val = led->brightness;
+
+ mutex_lock(&led->lock);
+
+ switch (num) {
+ case LP8788_ISINK_1:
+ case LP8788_ISINK_2:
+ case LP8788_ISINK_3:
+ lp8788_write_byte(led->lp, lp8788_pwm_addr[num], val);
+ break;
+ default:
+ mutex_unlock(&led->lock);
+ return;
+ }
+
+ enable = (val > 0) ? 1 : 0;
+ if (enable != led->on)
+ lp8788_led_enable(led, num, enable);
+
+ 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);
+}
+
+static int lp8788_led_probe(struct platform_device *pdev)
+{
+ struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);
+ struct lp8788_led_platform_data *led_pdata;
+ struct lp8788_led *led;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ led = devm_kzalloc(dev, sizeof(struct lp8788_led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->lp = lp;
+ led->led_dev.max_brightness = MAX_BRIGHTNESS;
+ led->led_dev.brightness_set = lp8788_brightness_set;
+
+ led_pdata = lp->pdata ? lp->pdata->led_pdata : NULL;
+
+ if (!led_pdata || !led_pdata->name)
+ led->led_dev.name = DEFAULT_LED_NAME;
+ else
+ 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) {
+ dev_err(dev, "led init device err: %d\n", ret);
+ return ret;
+ }
+
+ ret = led_classdev_register(dev, &led->led_dev);
+ if (ret) {
+ dev_err(dev, "led register err: %d\n", ret);
+ return ret;
+ }
+
+ 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);
+
+MODULE_DESCRIPTION("Texas Instruments LP8788 Keyboard LED Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lp8788-keyled");
diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c
index 2579678f97a..059f5b1f355 100644
--- a/drivers/leds/leds-lt3593.c
+++ b/drivers/leds/leds-lt3593.c
@@ -17,13 +17,13 @@
*/
#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/slab.h>
+#include <linux/module.h>
struct lt3593_led_data {
struct led_classdev cdev;
@@ -81,22 +81,18 @@ static void lt3593_led_set(struct led_classdev *led_cdev,
schedule_work(&led_dat->work);
}
-static int __devinit create_lt3593_led(const struct gpio_led *template,
+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)) {
- printk(KERN_INFO "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n",
+ dev_info(parent, "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n",
KBUILD_MODNAME, template->gpio, template->name);
return 0;
}
- ret = gpio_request(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;
@@ -109,24 +105,22 @@ static int __devinit create_lt3593_led(const struct gpio_led *template,
if (!template->retain_state_suspended)
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
- ret = gpio_direction_output(led_dat->gpio, state);
+ ret = devm_gpio_request_one(parent, template->gpio, state ?
+ GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
+ template->name);
if (ret < 0)
- goto err;
+ return ret;
INIT_WORK(&led_dat->work, lt3593_led_work);
ret = led_classdev_register(parent, &led_dat->cdev);
if (ret < 0)
- goto err;
+ return ret;
- printk(KERN_INFO "%s: registered LT3593 LED '%s' at GPIO %d\n",
+ dev_info(parent, "%s: registered LT3593 LED '%s' at GPIO %d\n",
KBUILD_MODNAME, template->name, template->gpio);
return 0;
-
-err:
- gpio_free(led_dat->gpio);
- return ret;
}
static void delete_lt3593_led(struct lt3593_led_data *led)
@@ -136,20 +130,20 @@ static void delete_lt3593_led(struct lt3593_led_data *led)
led_classdev_unregister(&led->cdev);
cancel_work_sync(&led->work);
- gpio_free(led->gpio);
}
-static int __devinit lt3593_led_probe(struct platform_device *pdev)
+static int lt3593_led_probe(struct platform_device *pdev)
{
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct lt3593_led_data *leds_data;
int i, ret = 0;
if (!pdata)
return -EBUSY;
- leds_data = kzalloc(sizeof(struct lt3593_led_data) * pdata->num_leds,
- GFP_KERNEL);
+ leds_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct lt3593_led_data) * pdata->num_leds,
+ GFP_KERNEL);
if (!leds_data)
return -ENOMEM;
@@ -168,15 +162,13 @@ err:
for (i = i - 1; i >= 0; i--)
delete_lt3593_led(&leds_data[i]);
- kfree(leds_data);
-
return ret;
}
-static int __devexit lt3593_led_remove(struct platform_device *pdev)
+static int lt3593_led_remove(struct platform_device *pdev)
{
int i;
- struct gpio_led_platform_data *pdata = pdev->dev.platform_data;
+ struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct lt3593_led_data *leds_data;
leds_data = platform_get_drvdata(pdev);
@@ -184,35 +176,21 @@ static int __devexit lt3593_led_remove(struct platform_device *pdev)
for (i = 0; i < pdata->num_leds; i++)
delete_lt3593_led(&leds_data[i]);
- kfree(leds_data);
-
return 0;
}
static struct platform_driver lt3593_led_driver = {
.probe = lt3593_led_probe,
- .remove = __devexit_p(lt3593_led_remove),
+ .remove = lt3593_led_remove,
.driver = {
.name = "leds-lt3593",
.owner = THIS_MODULE,
},
};
-MODULE_ALIAS("platform:leds-lt3593");
-
-static int __init lt3593_led_init(void)
-{
- return platform_driver_register(&lt3593_led_driver);
-}
-
-static void __exit lt3593_led_exit(void)
-{
- platform_driver_unregister(&lt3593_led_driver);
-}
-
-module_init(lt3593_led_init);
-module_exit(lt3593_led_exit);
+module_platform_driver(lt3593_led_driver);
MODULE_AUTHOR("Daniel Mack <daniel@caiaq.de>");
MODULE_DESCRIPTION("LED driver for LT3593 controllers");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-lt3593");
diff --git a/drivers/leds/leds-max8997.c b/drivers/leds/leds-max8997.c
new file mode 100644
index 00000000000..f449a8bdddc
--- /dev/null
+++ b/drivers/leds/leds-max8997.c
@@ -0,0 +1,319 @@
+/*
+ * 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>
+#include <linux/platform_device.h>
+
+#define MAX8997_LED_FLASH_SHIFT 3
+#define MAX8997_LED_FLASH_CUR_MASK 0xf8
+#define MAX8997_LED_MOVIE_SHIFT 4
+#define MAX8997_LED_MOVIE_CUR_MASK 0xf0
+
+#define MAX8997_LED_FLASH_MAX_BRIGHTNESS 0x1f
+#define MAX8997_LED_MOVIE_MAX_BRIGHTNESS 0xf
+#define MAX8997_LED_NONE_MAX_BRIGHTNESS 0
+
+#define MAX8997_LED0_FLASH_MASK 0x1
+#define MAX8997_LED0_FLASH_PIN_MASK 0x5
+#define MAX8997_LED0_MOVIE_MASK 0x8
+#define MAX8997_LED0_MOVIE_PIN_MASK 0x28
+
+#define MAX8997_LED1_FLASH_MASK 0x2
+#define MAX8997_LED1_FLASH_PIN_MASK 0x6
+#define MAX8997_LED1_MOVIE_MASK 0x10
+#define MAX8997_LED1_MOVIE_PIN_MASK 0x30
+
+#define MAX8997_LED_BOOST_ENABLE_MASK (1 << 6)
+
+struct max8997_led {
+ struct max8997_dev *iodev;
+ struct led_classdev cdev;
+ bool enabled;
+ int id;
+ enum max8997_led_mode led_mode;
+ struct mutex mutex;
+};
+
+static void max8997_led_set_mode(struct max8997_led *led,
+ enum max8997_led_mode mode)
+{
+ int ret;
+ struct i2c_client *client = led->iodev->i2c;
+ u8 mask = 0, val;
+
+ switch (mode) {
+ case MAX8997_FLASH_MODE:
+ mask = MAX8997_LED1_FLASH_MASK | MAX8997_LED0_FLASH_MASK;
+ val = led->id ?
+ MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK;
+ led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS;
+ break;
+ case MAX8997_MOVIE_MODE:
+ mask = MAX8997_LED1_MOVIE_MASK | MAX8997_LED0_MOVIE_MASK;
+ val = led->id ?
+ MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK;
+ led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS;
+ break;
+ case MAX8997_FLASH_PIN_CONTROL_MODE:
+ mask = MAX8997_LED1_FLASH_PIN_MASK |
+ MAX8997_LED0_FLASH_PIN_MASK;
+ val = led->id ?
+ MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK;
+ led->cdev.max_brightness = MAX8997_LED_FLASH_MAX_BRIGHTNESS;
+ break;
+ case MAX8997_MOVIE_PIN_CONTROL_MODE:
+ mask = MAX8997_LED1_MOVIE_PIN_MASK |
+ MAX8997_LED0_MOVIE_PIN_MASK;
+ val = led->id ?
+ MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK;
+ led->cdev.max_brightness = MAX8997_LED_MOVIE_MAX_BRIGHTNESS;
+ break;
+ default:
+ led->cdev.max_brightness = MAX8997_LED_NONE_MAX_BRIGHTNESS;
+ break;
+ }
+
+ if (mask) {
+ ret = max8997_update_reg(client, MAX8997_REG_LEN_CNTL, val,
+ mask);
+ if (ret)
+ dev_err(led->iodev->dev,
+ "failed to update register(%d)\n", ret);
+ }
+
+ led->led_mode = mode;
+}
+
+static void max8997_led_enable(struct max8997_led *led, bool enable)
+{
+ int ret;
+ struct i2c_client *client = led->iodev->i2c;
+ u8 val = 0, mask = MAX8997_LED_BOOST_ENABLE_MASK;
+
+ if (led->enabled == enable)
+ return;
+
+ val = enable ? MAX8997_LED_BOOST_ENABLE_MASK : 0;
+
+ ret = max8997_update_reg(client, MAX8997_REG_BOOST_CNTL, val, mask);
+ if (ret)
+ dev_err(led->iodev->dev,
+ "failed to update register(%d)\n", ret);
+
+ led->enabled = enable;
+}
+
+static void max8997_led_set_current(struct max8997_led *led,
+ enum led_brightness value)
+{
+ int ret;
+ struct i2c_client *client = led->iodev->i2c;
+ u8 val = 0, mask = 0, reg = 0;
+
+ switch (led->led_mode) {
+ case MAX8997_FLASH_MODE:
+ case MAX8997_FLASH_PIN_CONTROL_MODE:
+ val = value << MAX8997_LED_FLASH_SHIFT;
+ mask = MAX8997_LED_FLASH_CUR_MASK;
+ reg = led->id ? MAX8997_REG_FLASH2_CUR : MAX8997_REG_FLASH1_CUR;
+ break;
+ case MAX8997_MOVIE_MODE:
+ case MAX8997_MOVIE_PIN_CONTROL_MODE:
+ val = value << MAX8997_LED_MOVIE_SHIFT;
+ mask = MAX8997_LED_MOVIE_CUR_MASK;
+ reg = MAX8997_REG_MOVIE_CUR;
+ break;
+ default:
+ break;
+ }
+
+ if (mask) {
+ ret = max8997_update_reg(client, reg, val, mask);
+ if (ret)
+ dev_err(led->iodev->dev,
+ "failed to update register(%d)\n", ret);
+ }
+}
+
+static void max8997_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct max8997_led *led =
+ container_of(led_cdev, struct max8997_led, cdev);
+
+ if (value) {
+ max8997_led_set_current(led, value);
+ max8997_led_enable(led, true);
+ } else {
+ max8997_led_set_current(led, value);
+ max8997_led_enable(led, false);
+ }
+}
+
+static ssize_t max8997_led_show_mode(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ struct max8997_led *led =
+ container_of(led_cdev, struct max8997_led, cdev);
+ ssize_t ret = 0;
+
+ mutex_lock(&led->mutex);
+
+ switch (led->led_mode) {
+ case MAX8997_FLASH_MODE:
+ ret += sprintf(buf, "FLASH\n");
+ break;
+ case MAX8997_MOVIE_MODE:
+ ret += sprintf(buf, "MOVIE\n");
+ break;
+ case MAX8997_FLASH_PIN_CONTROL_MODE:
+ ret += sprintf(buf, "FLASH_PIN_CONTROL\n");
+ break;
+ case MAX8997_MOVIE_PIN_CONTROL_MODE:
+ ret += sprintf(buf, "MOVIE_PIN_CONTROL\n");
+ break;
+ default:
+ ret += sprintf(buf, "NONE\n");
+ break;
+ }
+
+ mutex_unlock(&led->mutex);
+
+ return ret;
+}
+
+static ssize_t max8997_led_store_mode(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 =
+ container_of(led_cdev, struct max8997_led, cdev);
+ enum max8997_led_mode mode;
+
+ mutex_lock(&led->mutex);
+
+ if (!strncmp(buf, "FLASH_PIN_CONTROL", 17))
+ mode = MAX8997_FLASH_PIN_CONTROL_MODE;
+ else if (!strncmp(buf, "MOVIE_PIN_CONTROL", 17))
+ mode = MAX8997_MOVIE_PIN_CONTROL_MODE;
+ else if (!strncmp(buf, "FLASH", 5))
+ mode = MAX8997_FLASH_MODE;
+ else if (!strncmp(buf, "MOVIE", 5))
+ mode = MAX8997_MOVIE_MODE;
+ else
+ mode = MAX8997_NONE;
+
+ max8997_led_set_mode(led, mode);
+
+ mutex_unlock(&led->mutex);
+
+ return size;
+}
+
+static DEVICE_ATTR(mode, 0644, max8997_led_show_mode, max8997_led_store_mode);
+
+static int max8997_led_probe(struct platform_device *pdev)
+{
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct max8997_led *led;
+ 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;
+
+ led->id = pdev->id;
+ snprintf(name, sizeof(name), "max8997-led%d", pdev->id);
+
+ led->cdev.name = name;
+ led->cdev.brightness_set = max8997_led_brightness_set;
+ led->cdev.flags |= LED_CORE_SUSPENDRESUME;
+ led->cdev.brightness = 0;
+ led->iodev = iodev;
+
+ /* initialize mode and brightness according to platform_data */
+ if (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]);
+
+ if (brightness > led->cdev.max_brightness)
+ brightness = led->cdev.max_brightness;
+ max8997_led_set_current(led, brightness);
+ led->cdev.brightness = brightness;
+ } else {
+ max8997_led_set_mode(led, MAX8997_NONE);
+ max8997_led_set_current(led, 0);
+ }
+
+ mutex_init(&led->mutex);
+
+ platform_set_drvdata(pdev, led);
+
+ ret = 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);
+
+MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>");
+MODULE_DESCRIPTION("MAX8997 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:max8997-led");
diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c
index f05bb08d0f0..f1db88e2513 100644
--- a/drivers/leds/leds-mc13783.c
+++ b/drivers/leds/leds-mc13783.c
@@ -1,5 +1,5 @@
/*
- * LEDs driver for Freescale MC13783
+ * LEDs driver for Freescale MC13783/MC13892/MC34708
*
* Copyright (C) 2010 Philippe Rétornaz
*
@@ -17,143 +17,56 @@
#include <linux/module.h>
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
+#include <linux/of.h>
#include <linux/workqueue.h>
-#include <linux/mfd/mc13783.h>
-#include <linux/slab.h>
+#include <linux/mfd/mc13xxx.h>
-struct mc13783_led {
+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 mc13783 *master;
enum led_brightness new_brightness;
int id;
+ struct mc13xxx_leds *leds;
};
-#define MC13783_REG_LED_CONTROL_0 51
-#define MC13783_LED_C0_ENABLE_BIT (1 << 0)
-#define MC13783_LED_C0_TRIODE_MD_BIT (1 << 7)
-#define MC13783_LED_C0_TRIODE_AD_BIT (1 << 8)
-#define MC13783_LED_C0_TRIODE_KP_BIT (1 << 9)
-#define MC13783_LED_C0_BOOST_BIT (1 << 10)
-#define MC13783_LED_C0_ABMODE_MASK 0x7
-#define MC13783_LED_C0_ABMODE 11
-#define MC13783_LED_C0_ABREF_MASK 0x3
-#define MC13783_LED_C0_ABREF 14
-
-#define MC13783_REG_LED_CONTROL_1 52
-#define MC13783_LED_C1_TC1HALF_BIT (1 << 18)
-
-#define MC13783_REG_LED_CONTROL_2 53
-#define MC13783_LED_C2_BL_P_MASK 0xf
-#define MC13783_LED_C2_MD_P 9
-#define MC13783_LED_C2_AD_P 13
-#define MC13783_LED_C2_KP_P 17
-#define MC13783_LED_C2_BL_C_MASK 0x7
-#define MC13783_LED_C2_MD_C 0
-#define MC13783_LED_C2_AD_C 3
-#define MC13783_LED_C2_KP_C 6
-
-#define MC13783_REG_LED_CONTROL_3 54
-#define MC13783_LED_C3_TC_P 6
-#define MC13783_LED_C3_TC_P_MASK 0x1f
-
-#define MC13783_REG_LED_CONTROL_4 55
-#define MC13783_REG_LED_CONTROL_5 56
-
-#define MC13783_LED_Cx_PERIOD 21
-#define MC13783_LED_Cx_PERIOD_MASK 0x3
-#define MC13783_LED_Cx_SLEWLIM_BIT (1 << 23)
-#define MC13783_LED_Cx_TRIODE_TC_BIT (1 << 23)
-#define MC13783_LED_Cx_TC_C_MASK 0x3
-
-static void mc13783_led_work(struct work_struct *work)
-{
- struct mc13783_led *led = container_of(work, struct mc13783_led, work);
- int reg = 0;
- int mask = 0;
- int value = 0;
- int bank, off, shift;
-
- switch (led->id) {
- case MC13783_LED_MD:
- reg = MC13783_REG_LED_CONTROL_2;
- mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_MD_P;
- value = (led->new_brightness >> 4) << MC13783_LED_C2_MD_P;
- break;
- case MC13783_LED_AD:
- reg = MC13783_REG_LED_CONTROL_2;
- mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_AD_P;
- value = (led->new_brightness >> 4) << MC13783_LED_C2_AD_P;
- break;
- case MC13783_LED_KP:
- reg = MC13783_REG_LED_CONTROL_2;
- mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_KP_P;
- value = (led->new_brightness >> 4) << MC13783_LED_C2_KP_P;
- 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:
- off = led->id - MC13783_LED_R1;
- bank = off/3;
- reg = MC13783_REG_LED_CONTROL_3 + off/3;
- shift = (off - bank * 3) * 5 + MC13783_LED_C3_TC_P;
- value = (led->new_brightness >> 3) << shift;
- mask = MC13783_LED_C3_TC_P_MASK << shift;
- break;
- }
-
- mc13783_lock(led->master);
-
- mc13783_reg_rmw(led->master, reg, mask, value);
-
- mc13783_unlock(led->master);
-}
+struct mc13xxx_leds {
+ struct mc13xxx *master;
+ struct mc13xxx_led_devtype *devtype;
+ int num_leds;
+ struct mc13xxx_led *led;
+};
-static void mc13783_led_set(struct led_classdev *led_cdev,
- enum led_brightness value)
+static unsigned int mc13xxx_max_brightness(int id)
{
- struct mc13783_led *led;
+ if (id >= MC13783_LED_MD && id <= MC13783_LED_KP)
+ return 0x0f;
+ else if (id >= MC13783_LED_R1 && id <= MC13783_LED_B3)
+ return 0x1f;
- led = container_of(led_cdev, struct mc13783_led, cdev);
- led->new_brightness = value;
- schedule_work(&led->work);
+ return 0x3f;
}
-static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current)
+static void mc13xxx_led_work(struct work_struct *work)
{
- int shift = 0;
- int mask = 0;
- int value = 0;
- int reg = 0;
- int ret, bank;
+ struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work);
+ struct mc13xxx_leds *leds = led->leds;
+ unsigned int reg, bank, off, shift;
switch (led->id) {
case MC13783_LED_MD:
- shift = MC13783_LED_C2_MD_C;
- mask = MC13783_LED_C2_BL_C_MASK;
- value = max_current & MC13783_LED_C2_BL_C_MASK;
- reg = MC13783_REG_LED_CONTROL_2;
- break;
case MC13783_LED_AD:
- shift = MC13783_LED_C2_AD_C;
- mask = MC13783_LED_C2_BL_C_MASK;
- value = max_current & MC13783_LED_C2_BL_C_MASK;
- reg = MC13783_REG_LED_CONTROL_2;
- break;
case MC13783_LED_KP:
- shift = MC13783_LED_C2_KP_C;
- mask = MC13783_LED_C2_BL_C_MASK;
- value = max_current & MC13783_LED_C2_BL_C_MASK;
- reg = MC13783_REG_LED_CONTROL_2;
+ reg = 2;
+ shift = 9 + (led->id - MC13783_LED_MD) * 4;
break;
case MC13783_LED_R1:
case MC13783_LED_G1:
@@ -164,240 +77,261 @@ static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current)
case MC13783_LED_R3:
case MC13783_LED_G3:
case MC13783_LED_B3:
- bank = (led->id - MC13783_LED_R1)/3;
- reg = MC13783_REG_LED_CONTROL_3 + bank;
- shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2;
- mask = MC13783_LED_Cx_TC_C_MASK;
- value = max_current & MC13783_LED_Cx_TC_C_MASK;
+ off = led->id - MC13783_LED_R1;
+ bank = off / 3;
+ reg = 3 + bank;
+ shift = (off - bank * 3) * 5 + 6;
+ break;
+ case MC13892_LED_MD:
+ case MC13892_LED_AD:
+ case MC13892_LED_KP:
+ reg = (led->id - MC13892_LED_MD) / 2;
+ shift = 3 + (led->id - MC13892_LED_MD) * 12;
+ break;
+ case MC13892_LED_R:
+ case MC13892_LED_G:
+ case MC13892_LED_B:
+ off = led->id - MC13892_LED_R;
+ bank = off / 2;
+ reg = 2 + bank;
+ shift = (off - bank * 2) * 12 + 3;
+ break;
+ case MC34708_LED_R:
+ case MC34708_LED_G:
+ reg = 0;
+ shift = 3 + (led->id - MC34708_LED_R) * 12;
break;
+ default:
+ BUG();
}
- mc13783_lock(led->master);
+ mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg,
+ mc13xxx_max_brightness(led->id) << shift,
+ led->new_brightness << shift);
+}
- ret = mc13783_reg_rmw(led->master, reg, mask << shift,
- value << shift);
+static void mc13xxx_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct mc13xxx_led *led =
+ container_of(led_cdev, struct mc13xxx_led, cdev);
- mc13783_unlock(led->master);
- return ret;
+ led->new_brightness = value;
+ schedule_work(&led->work);
}
-static int __devinit mc13783_leds_prepare(struct platform_device *pdev)
+#ifdef CONFIG_OF
+static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
+ struct platform_device *pdev)
{
- struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent);
- int ret = 0;
- int reg = 0;
+ struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
+ struct mc13xxx_leds_platform_data *pdata;
+ struct device_node *parent, *child;
+ struct device *dev = &pdev->dev;
+ int i = 0, ret = -ENODATA;
- mc13783_lock(dev);
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
- if (pdata->flags & MC13783_LED_TC1HALF)
- reg |= MC13783_LED_C1_TC1HALF_BIT;
+ of_node_get(dev->parent->of_node);
- if (pdata->flags & MC13783_LED_SLEWLIMTC)
- reg |= MC13783_LED_Cx_SLEWLIM_BIT;
+ parent = of_find_node_by_name(dev->parent->of_node, "leds");
+ if (!parent)
+ goto out_node_put;
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_1, reg);
+ ret = of_property_read_u32_array(parent, "led-control",
+ pdata->led_control,
+ leds->devtype->num_regs);
if (ret)
- goto out;
+ goto out_node_put;
- reg = (pdata->bl_period & MC13783_LED_Cx_PERIOD_MASK) <<
- MC13783_LED_Cx_PERIOD;
+ pdata->num_leds = of_get_child_count(parent);
- if (pdata->flags & MC13783_LED_SLEWLIMBL)
- reg |= MC13783_LED_Cx_SLEWLIM_BIT;
+ pdata->led = devm_kzalloc(dev, pdata->num_leds * sizeof(*pdata->led),
+ GFP_KERNEL);
+ if (!pdata->led) {
+ ret = -ENOMEM;
+ goto out_node_put;
+ }
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_2, reg);
- if (ret)
- goto out;
+ for_each_child_of_node(parent, child) {
+ const char *str;
+ u32 tmp;
- reg = (pdata->tc1_period & MC13783_LED_Cx_PERIOD_MASK) <<
- MC13783_LED_Cx_PERIOD;
+ if (of_property_read_u32(child, "reg", &tmp))
+ continue;
+ pdata->led[i].id = leds->devtype->led_min + tmp;
- if (pdata->flags & MC13783_LED_TRIODE_TC1)
- reg |= MC13783_LED_Cx_TRIODE_TC_BIT;
+ 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;
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_3, reg);
- if (ret)
- goto out;
+ i++;
+ }
- reg = (pdata->tc2_period & MC13783_LED_Cx_PERIOD_MASK) <<
- MC13783_LED_Cx_PERIOD;
+ pdata->num_leds = i;
+ ret = i > 0 ? 0 : -ENODATA;
- if (pdata->flags & MC13783_LED_TRIODE_TC2)
- reg |= MC13783_LED_Cx_TRIODE_TC_BIT;
+out_node_put:
+ of_node_put(parent);
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_4, reg);
- if (ret)
- goto out;
+ return ret ? ERR_PTR(ret) : pdata;
+}
+#else
+static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
+ struct platform_device *pdev)
+{
+ return ERR_PTR(-ENOSYS);
+}
+#endif
- reg = (pdata->tc3_period & MC13783_LED_Cx_PERIOD_MASK) <<
- MC13783_LED_Cx_PERIOD;
+static int __init mc13xxx_led_probe(struct platform_device *pdev)
+{
+ 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, ret = -ENODATA;
+ u32 init_led = 0;
+
+ leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL);
+ if (!leds)
+ return -ENOMEM;
- if (pdata->flags & MC13783_LED_TRIODE_TC3)
- reg |= MC13783_LED_Cx_TRIODE_TC_BIT;;
+ leds->devtype = devtype;
+ leds->master = mcdev;
+ platform_set_drvdata(pdev, leds);
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_5, reg);
- if (ret)
- goto out;
-
- reg = MC13783_LED_C0_ENABLE_BIT;
- if (pdata->flags & MC13783_LED_TRIODE_MD)
- reg |= MC13783_LED_C0_TRIODE_MD_BIT;
- if (pdata->flags & MC13783_LED_TRIODE_AD)
- reg |= MC13783_LED_C0_TRIODE_AD_BIT;
- if (pdata->flags & MC13783_LED_TRIODE_KP)
- reg |= MC13783_LED_C0_TRIODE_KP_BIT;
- if (pdata->flags & MC13783_LED_BOOST_EN)
- reg |= MC13783_LED_C0_BOOST_BIT;
-
- reg |= (pdata->abmode & MC13783_LED_C0_ABMODE_MASK) <<
- MC13783_LED_C0_ABMODE;
- reg |= (pdata->abref & MC13783_LED_C0_ABREF_MASK) <<
- MC13783_LED_C0_ABREF;
-
- ret = mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_0, reg);
-
-out:
- mc13783_unlock(dev);
- return ret;
-}
+ if (dev->parent->of_node) {
+ pdata = mc13xxx_led_probe_dt(pdev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ } else if (!pdata)
+ return -ENODATA;
-static int __devinit mc13783_led_probe(struct platform_device *pdev)
-{
- struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct mc13783_led_platform_data *led_cur;
- struct mc13783_led *led, *led_dat;
- int ret, i;
- int init_led = 0;
-
- if (pdata == NULL) {
- dev_err(&pdev->dev, "missing platform data\n");
- return -ENODEV;
- }
+ leds->num_leds = pdata->num_leds;
- if (pdata->num_leds < 1 || pdata->num_leds > MC13783_LED_MAX) {
- dev_err(&pdev->dev, "Invalid led count %d\n", 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;
}
- led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL);
- if (led == NULL) {
- dev_err(&pdev->dev, "failed to alloc memory\n");
+ leds->led = devm_kzalloc(dev, leds->num_leds * sizeof(*leds->led),
+ GFP_KERNEL);
+ if (!leds->led)
return -ENOMEM;
- }
- ret = mc13783_leds_prepare(pdev);
- if (ret) {
- dev_err(&pdev->dev, "unable to init led driver\n");
- goto err_free;
+ for (i = 0; i < devtype->num_regs; i++) {
+ ret = mc13xxx_reg_write(mcdev, leds->devtype->ledctrl_base + i,
+ pdata->led_control[i]);
+ if (ret)
+ return ret;
}
- for (i = 0; i < pdata->num_leds; i++) {
- led_dat = &led[i];
- led_cur = &pdata->led[i];
+ for (i = 0; i < leds->num_leds; i++) {
+ const char *name, *trig;
- if (led_cur->id > MC13783_LED_MAX || led_cur->id < 0) {
- dev_err(&pdev->dev, "invalid id %d\n", led_cur->id);
- ret = -EINVAL;
- goto err_register;
- }
+ ret = -EINVAL;
+
+ id = pdata->led[i].id;
+ name = pdata->led[i].name;
+ trig = pdata->led[i].default_trigger;
- if (init_led & (1 << led_cur->id)) {
- dev_err(&pdev->dev, "led %d already initialized\n",
- led_cur->id);
- ret = -EINVAL;
- goto err_register;
+ if ((id > devtype->led_max) || (id < devtype->led_min)) {
+ dev_err(dev, "Invalid ID %i\n", id);
+ break;
}
- init_led |= 1 << led_cur->id;
- led_dat->cdev.name = led_cur->name;
- led_dat->cdev.default_trigger = led_cur->default_trigger;
- led_dat->cdev.brightness_set = mc13783_led_set;
- led_dat->cdev.brightness = LED_OFF;
- led_dat->id = led_cur->id;
- led_dat->master = dev_get_drvdata(pdev->dev.parent);
+ if (init_led & (1 << id)) {
+ dev_warn(dev, "LED %i already initialized\n", id);
+ break;
+ }
- INIT_WORK(&led_dat->work, mc13783_led_work);
+ init_led |= 1 << id;
+ leds->led[i].id = id;
+ leds->led[i].leds = leds;
+ leds->led[i].cdev.name = name;
+ leds->led[i].cdev.default_trigger = trig;
+ leds->led[i].cdev.flags = LED_CORE_SUSPENDRESUME;
+ leds->led[i].cdev.brightness_set = mc13xxx_led_set;
+ leds->led[i].cdev.max_brightness = mc13xxx_max_brightness(id);
- ret = led_classdev_register(pdev->dev.parent, &led_dat->cdev);
- if (ret) {
- dev_err(&pdev->dev, "failed to register led %d\n",
- led_dat->id);
- goto err_register;
- }
+ INIT_WORK(&leds->led[i].work, mc13xxx_led_work);
- ret = mc13783_led_setup(led_dat, led_cur->max_current);
+ ret = led_classdev_register(dev->parent, &leds->led[i].cdev);
if (ret) {
- dev_err(&pdev->dev, "unable to init led %d\n",
- led_dat->id);
- i++;
- goto err_register;
+ dev_err(dev, "Failed to register LED %i\n", id);
+ break;
}
}
- platform_set_drvdata(pdev, led);
- return 0;
-
-err_register:
- for (i = i - 1; i >= 0; i--) {
- led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
- }
+ if (ret)
+ while (--i >= 0) {
+ led_classdev_unregister(&leds->led[i].cdev);
+ cancel_work_sync(&leds->led[i].work);
+ }
-err_free:
- kfree(led);
return ret;
}
-static int __devexit mc13783_led_remove(struct platform_device *pdev)
+static int mc13xxx_led_remove(struct platform_device *pdev)
{
- struct mc13783_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
- struct mc13783_led *led = platform_get_drvdata(pdev);
- struct mc13783 *dev = dev_get_drvdata(pdev->dev.parent);
+ struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
int i;
- for (i = 0; i < pdata->num_leds; i++) {
- led_classdev_unregister(&led[i].cdev);
- cancel_work_sync(&led[i].work);
+ for (i = 0; i < leds->num_leds; i++) {
+ led_classdev_unregister(&leds->led[i].cdev);
+ cancel_work_sync(&leds->led[i].work);
}
- mc13783_lock(dev);
+ return 0;
+}
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_0, 0);
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_1, 0);
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_2, 0);
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_3, 0);
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_4, 0);
- mc13783_reg_write(dev, MC13783_REG_LED_CONTROL_5, 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,
+};
- mc13783_unlock(dev);
+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,
+};
- kfree(led);
- return 0;
-}
+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 struct platform_driver mc13783_led_driver = {
+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);
+
+static struct platform_driver mc13xxx_led_driver = {
.driver = {
- .name = "mc13783-led",
+ .name = "mc13xxx-led",
.owner = THIS_MODULE,
},
- .probe = mc13783_led_probe,
- .remove = __devexit_p(mc13783_led_remove),
+ .remove = mc13xxx_led_remove,
+ .id_table = mc13xxx_led_id_table,
};
+module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
-static int __init mc13783_led_init(void)
-{
- return platform_driver_register(&mc13783_led_driver);
-}
-module_init(mc13783_led_init);
-
-static void __exit mc13783_led_exit(void)
-{
- platform_driver_unregister(&mc13783_led_driver);
-}
-module_exit(mc13783_led_exit);
-
-MODULE_DESCRIPTION("LEDs driver for Freescale MC13783 PMIC");
+MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC");
MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>");
MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:mc13783-led");
diff --git a/drivers/leds/leds-net48xx.c b/drivers/leds/leds-net48xx.c
index 93987a12da4..27d06c52824 100644
--- a/drivers/leds/leds-net48xx.c
+++ b/drivers/leds/leds-net48xx.c
@@ -15,9 +15,10 @@
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
-#include <asm/io.h>
+#include <linux/io.h>
#include <linux/nsc_gpio.h>
#include <linux/scx200_gpio.h>
+#include <linux/module.h>
#define DRVNAME "net48xx-led"
#define NET48XX_ERROR_LED_GPIO 20
diff --git a/drivers/leds/leds-net5501.c b/drivers/leds/leds-net5501.c
deleted file mode 100644
index 1739557a903..00000000000
--- a/drivers/leds/leds-net5501.c
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Soekris board support code
- *
- * Copyright (C) 2008-2009 Tower Technologies
- * Written by Alessandro Zummo <a.zummo@towertech.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/kernel.h>
-#include <linux/init.h>
-#include <linux/io.h>
-#include <linux/string.h>
-#include <linux/leds.h>
-#include <linux/platform_device.h>
-#include <linux/gpio.h>
-
-#include <asm/geode.h>
-
-static struct gpio_led net5501_leds[] = {
- {
- .name = "error",
- .gpio = 6,
- .default_trigger = "default-on",
- },
-};
-
-static struct gpio_led_platform_data net5501_leds_data = {
- .num_leds = ARRAY_SIZE(net5501_leds),
- .leds = net5501_leds,
-};
-
-static struct platform_device net5501_leds_dev = {
- .name = "leds-gpio",
- .id = -1,
- .dev.platform_data = &net5501_leds_data,
-};
-
-static void __init init_net5501(void)
-{
- platform_device_register(&net5501_leds_dev);
-}
-
-struct soekris_board {
- u16 offset;
- char *sig;
- u8 len;
- void (*init)(void);
-};
-
-static struct soekris_board __initdata boards[] = {
- { 0xb7b, "net5501", 7, init_net5501 }, /* net5501 v1.33/1.33c */
- { 0xb1f, "net5501", 7, init_net5501 }, /* net5501 v1.32i */
-};
-
-static int __init soekris_init(void)
-{
- int i;
- unsigned char *rombase, *bios;
-
- if (!is_geode())
- return 0;
-
- rombase = ioremap(0xffff0000, 0xffff);
- if (!rombase) {
- printk(KERN_INFO "Soekris net5501 LED driver failed to get rombase");
- return 0;
- }
-
- bios = rombase + 0x20; /* null terminated */
-
- if (strncmp(bios, "comBIOS", 7))
- goto unmap;
-
- for (i = 0; i < ARRAY_SIZE(boards); i++) {
- unsigned char *model = rombase + boards[i].offset;
-
- if (strncmp(model, boards[i].sig, boards[i].len) == 0) {
- printk(KERN_INFO "Soekris %s: %s\n", model, bios);
-
- if (boards[i].init)
- boards[i].init();
- break;
- }
- }
-
-unmap:
- iounmap(rombase);
- return 0;
-}
-
-arch_initcall(soekris_init);
-
-MODULE_LICENSE("GPL");
diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c
index f2e51c13439..e97f443a6e0 100644
--- a/drivers/leds/leds-netxbig.c
+++ b/drivers/leds/leds-netxbig.c
@@ -21,14 +21,13 @@
*/
#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/leds.h>
-#include <mach/leds-netxbig.h>
+#include <linux/platform_data/leds-kirkwood-netxbig.h>
/*
* GPIO extension bus.
@@ -71,7 +70,7 @@ static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext,
spin_unlock_irqrestore(&gpio_ext_lock, flags);
}
-static int __devinit gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
+static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
{
int err;
int i;
@@ -81,35 +80,23 @@ static int __devinit gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
/* Configure address GPIOs. */
for (i = 0; i < gpio_ext->num_addr; i++) {
- err = gpio_request(gpio_ext->addr[i], "GPIO extension addr");
+ err = gpio_request_one(gpio_ext->addr[i], GPIOF_OUT_INIT_LOW,
+ "GPIO extension addr");
if (err)
goto err_free_addr;
- err = gpio_direction_output(gpio_ext->addr[i], 0);
- if (err) {
- gpio_free(gpio_ext->addr[i]);
- goto err_free_addr;
- }
}
/* Configure data GPIOs. */
for (i = 0; i < gpio_ext->num_data; i++) {
- err = gpio_request(gpio_ext->data[i], "GPIO extension data");
+ err = gpio_request_one(gpio_ext->data[i], GPIOF_OUT_INIT_LOW,
+ "GPIO extension data");
if (err)
goto err_free_data;
- err = gpio_direction_output(gpio_ext->data[i], 0);
- if (err) {
- gpio_free(gpio_ext->data[i]);
- goto err_free_data;
- }
}
/* Configure "enable select" GPIO. */
- err = gpio_request(gpio_ext->enable, "GPIO extension enable");
+ err = gpio_request_one(gpio_ext->enable, GPIOF_OUT_INIT_LOW,
+ "GPIO extension enable");
if (err)
goto err_free_data;
- err = gpio_direction_output(gpio_ext->enable, 0);
- if (err) {
- gpio_free(gpio_ext->enable);
- goto err_free_data;
- }
return 0;
@@ -124,7 +111,7 @@ err_free_addr:
return err;
}
-static void __devexit gpio_ext_free(struct netxbig_gpio_ext *gpio_ext)
+static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext)
{
int i;
@@ -255,7 +242,7 @@ static ssize_t netxbig_led_sata_store(struct device *dev,
int mode_val;
int ret;
- ret = strict_strtoul(buff, 10, &enable);
+ ret = kstrtoul(buff, 10, &enable);
if (ret < 0)
return ret;
@@ -306,19 +293,19 @@ static ssize_t netxbig_led_sata_show(struct device *dev,
static DEVICE_ATTR(sata, 0644, netxbig_led_sata_show, netxbig_led_sata_store);
-static void __devexit delete_netxbig_led(struct netxbig_led_data *led_dat)
+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 int __devinit
+static int
create_netxbig_led(struct platform_device *pdev,
struct netxbig_led_data *led_dat,
const struct netxbig_led *template)
{
- struct netxbig_led_platform_data *pdata = pdev->dev.platform_data;
+ struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
int ret;
spin_lock_init(&led_dat->lock);
@@ -364,9 +351,9 @@ create_netxbig_led(struct platform_device *pdev,
return ret;
}
-static int __devinit netxbig_led_probe(struct platform_device *pdev)
+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 = dev_get_platdata(&pdev->dev);
struct netxbig_led_data *leds_data;
int i;
int ret;
@@ -374,14 +361,14 @@ static int __devinit netxbig_led_probe(struct platform_device *pdev)
if (!pdata)
return -EINVAL;
- leds_data = kzalloc(sizeof(struct netxbig_led_data) * pdata->num_leds,
- GFP_KERNEL);
+ leds_data = devm_kzalloc(&pdev->dev,
+ sizeof(struct netxbig_led_data) * pdata->num_leds, GFP_KERNEL);
if (!leds_data)
return -ENOMEM;
ret = gpio_ext_init(pdata->gpio_ext);
if (ret < 0)
- goto err_free_data;
+ return ret;
for (i = 0; i < pdata->num_leds; i++) {
ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]);
@@ -398,15 +385,12 @@ err_free_leds:
delete_netxbig_led(&leds_data[i]);
gpio_ext_free(pdata->gpio_ext);
-err_free_data:
- kfree(leds_data);
-
return ret;
}
-static int __devexit netxbig_led_remove(struct platform_device *pdev)
+static int netxbig_led_remove(struct platform_device *pdev)
{
- struct netxbig_led_platform_data *pdata = pdev->dev.platform_data;
+ struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
struct netxbig_led_data *leds_data;
int i;
@@ -416,34 +400,22 @@ static int __devexit netxbig_led_remove(struct platform_device *pdev)
delete_netxbig_led(&leds_data[i]);
gpio_ext_free(pdata->gpio_ext);
- kfree(leds_data);
return 0;
}
static struct platform_driver netxbig_led_driver = {
.probe = netxbig_led_probe,
- .remove = __devexit_p(netxbig_led_remove),
+ .remove = netxbig_led_remove,
.driver = {
.name = "leds-netxbig",
.owner = THIS_MODULE,
},
};
-MODULE_ALIAS("platform:leds-netxbig");
-static int __init netxbig_led_init(void)
-{
- return platform_driver_register(&netxbig_led_driver);
-}
-
-static void __exit netxbig_led_exit(void)
-{
- platform_driver_unregister(&netxbig_led_driver);
-}
-
-module_init(netxbig_led_init);
-module_exit(netxbig_led_exit);
+module_platform_driver(netxbig_led_driver);
MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
MODULE_DESCRIPTION("LED driver for LaCie xBig Network boards");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-netxbig");
diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c
index f77d48d0b3e..efa625883c8 100644
--- a/drivers/leds/leds-ns2.c
+++ b/drivers/leds/leds-ns2.c
@@ -23,12 +23,14 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/leds.h>
-#include <mach/leds-ns2.h>
+#include <linux/module.h>
+#include <linux/platform_data/leds-kirkwood-ns2.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
/*
* The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
@@ -148,7 +150,7 @@ static ssize_t ns2_led_sata_store(struct device *dev,
unsigned long enable;
enum ns2_led_modes mode;
- ret = strict_strtoul(buff, 10, &enable);
+ ret = kstrtoul(buff, 10, &enable);
if (ret < 0)
return ret;
@@ -183,36 +185,31 @@ static ssize_t ns2_led_sata_show(struct device *dev,
static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
-static int __devinit
+static int
create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
const struct ns2_led *template)
{
int ret;
enum ns2_led_modes mode;
- ret = gpio_request(template->cmd, template->name);
- if (ret == 0) {
- ret = gpio_direction_output(template->cmd,
- gpio_get_value(template->cmd));
- if (ret)
- gpio_free(template->cmd);
- }
+ 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;
}
- ret = gpio_request(template->slow, template->name);
- if (ret == 0) {
- ret = gpio_direction_output(template->slow,
- gpio_get_value(template->slow));
- if (ret)
- gpio_free(template->slow);
- }
+ 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);
- goto err_free_cmd;
+ return ret;
}
rwlock_init(&led_dat->rw_lock);
@@ -227,7 +224,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
ret = ns2_led_get_mode(led_dat, &mode);
if (ret < 0)
- goto err_free_slow;
+ return ret;
/* Set LED initial state. */
led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
@@ -236,7 +233,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0)
- goto err_free_slow;
+ return ret;
ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
if (ret < 0)
@@ -246,97 +243,153 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
err_free_cdev:
led_classdev_unregister(&led_dat->cdev);
-err_free_slow:
- gpio_free(led_dat->slow);
-err_free_cmd:
- gpio_free(led_dat->cmd);
-
return ret;
}
-static void __devexit delete_ns2_led(struct ns2_led_data *led_dat)
+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);
- gpio_free(led_dat->cmd);
- gpio_free(led_dat->slow);
}
-static int __devinit ns2_led_probe(struct platform_device *pdev)
+#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)
{
- struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
- struct ns2_led_data *leds_data;
- int i;
- int ret;
-
- if (!pdata)
- return -EINVAL;
-
- leds_data = kzalloc(sizeof(struct ns2_led_data) *
- pdata->num_leds, GFP_KERNEL);
- if (!leds_data)
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct ns2_led *leds;
+ int num_leds = 0;
+ int i = 0;
+
+ num_leds = of_get_child_count(np);
+ if (!num_leds)
+ return -ENODEV;
+
+ leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
+ GFP_KERNEL);
+ if (!leds)
return -ENOMEM;
- for (i = 0; i < pdata->num_leds; i++) {
- ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]);
- if (ret < 0)
- goto err;
+ 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)
+ 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++;
}
- platform_set_drvdata(pdev, leds_data);
+ pdata->leds = leds;
+ pdata->num_leds = num_leds;
return 0;
+}
-err:
- for (i = i - 1; i >= 0; i--)
- delete_ns2_led(&leds_data[i]);
+static const struct of_device_id of_ns2_leds_match[] = {
+ { .compatible = "lacie,ns2-leds", },
+ {},
+};
+#endif /* CONFIG_OF_GPIO */
- kfree(leds_data);
+struct ns2_led_priv {
+ int num_leds;
+ struct ns2_led_data leds_data[];
+};
- return ret;
+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 __devexit ns2_led_remove(struct platform_device *pdev)
+static int ns2_led_probe(struct platform_device *pdev)
{
+ struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct ns2_led_priv *priv;
int i;
- struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
- struct ns2_led_data *leds_data;
+ 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);
- leds_data = platform_get_drvdata(pdev);
+ return 0;
+}
- for (i = 0; i < pdata->num_leds; i++)
- delete_ns2_led(&leds_data[i]);
+static int ns2_led_remove(struct platform_device *pdev)
+{
+ int i;
+ struct ns2_led_priv *priv;
- kfree(leds_data);
- platform_set_drvdata(pdev, NULL);
+ priv = platform_get_drvdata(pdev);
+
+ for (i = 0; i < priv->num_leds; i++)
+ delete_ns2_led(&priv->leds_data[i]);
return 0;
}
static struct platform_driver ns2_led_driver = {
.probe = ns2_led_probe,
- .remove = __devexit_p(ns2_led_remove),
+ .remove = ns2_led_remove,
.driver = {
- .name = "leds-ns2",
- .owner = THIS_MODULE,
+ .name = "leds-ns2",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(of_ns2_leds_match),
},
};
-MODULE_ALIAS("platform:leds-ns2");
-static int __init ns2_led_init(void)
-{
- return platform_driver_register(&ns2_led_driver);
-}
-
-static void __exit ns2_led_exit(void)
-{
- platform_driver_unregister(&ns2_led_driver);
-}
-
-module_init(ns2_led_init);
-module_exit(ns2_led_exit);
+module_platform_driver(ns2_led_driver);
MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
MODULE_DESCRIPTION("Network Space v2 LED driver");
MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-ns2");
diff --git a/drivers/leds/leds-ot200.c b/drivers/leds/leds-ot200.c
new file mode 100644
index 00000000000..c9d90609846
--- /dev/null
+++ b/drivers/leds/leds-ot200.c
@@ -0,0 +1,170 @@
+/*
+ * Bachmann ot200 leds driver.
+ *
+ * Author: Sebastian Andrzej Siewior <bigeasy@linutronix.de>
+ * Christian Gmeiner <christian.gmeiner@gmail.com>
+ *
+ * License: GPL as published by the FSF.
+ */
+
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/io.h>
+#include <linux/module.h>
+
+
+struct ot200_led {
+ struct led_classdev cdev;
+ const char *name;
+ unsigned long port;
+ u8 mask;
+};
+
+/*
+ * The device has three leds on the back panel (led_err, led_init and led_run)
+ * and can handle up to seven leds on the front panel.
+ */
+
+static struct ot200_led leds[] = {
+ {
+ .name = "led_run",
+ .port = 0x5a,
+ .mask = BIT(0),
+ },
+ {
+ .name = "led_init",
+ .port = 0x5a,
+ .mask = BIT(1),
+ },
+ {
+ .name = "led_err",
+ .port = 0x5a,
+ .mask = BIT(2),
+ },
+ {
+ .name = "led_1",
+ .port = 0x49,
+ .mask = BIT(6),
+ },
+ {
+ .name = "led_2",
+ .port = 0x49,
+ .mask = BIT(5),
+ },
+ {
+ .name = "led_3",
+ .port = 0x49,
+ .mask = BIT(4),
+ },
+ {
+ .name = "led_4",
+ .port = 0x49,
+ .mask = BIT(3),
+ },
+ {
+ .name = "led_5",
+ .port = 0x49,
+ .mask = BIT(2),
+ },
+ {
+ .name = "led_6",
+ .port = 0x49,
+ .mask = BIT(1),
+ },
+ {
+ .name = "led_7",
+ .port = 0x49,
+ .mask = BIT(0),
+ }
+};
+
+static DEFINE_SPINLOCK(value_lock);
+
+/*
+ * we need to store the current led states, as it is not
+ * possible to read the current led state via inb().
+ */
+static u8 leds_back;
+static u8 leds_front;
+
+static void ot200_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct ot200_led *led = container_of(led_cdev, struct ot200_led, cdev);
+ u8 *val;
+ unsigned long flags;
+
+ spin_lock_irqsave(&value_lock, flags);
+
+ if (led->port == 0x49)
+ val = &leds_front;
+ else if (led->port == 0x5a)
+ val = &leds_back;
+ else
+ BUG();
+
+ if (value == LED_OFF)
+ *val &= ~led->mask;
+ else
+ *val |= led->mask;
+
+ outb(*val, led->port);
+ spin_unlock_irqrestore(&value_lock, flags);
+}
+
+static int ot200_led_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(leds); i++) {
+
+ 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);
+ if (ret < 0)
+ goto err;
+ }
+
+ leds_front = 0; /* turn off all front leds */
+ leds_back = BIT(1); /* turn on init led */
+ outb(leds_front, 0x49);
+ 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,
+ },
+};
+
+module_platform_driver(ot200_led_driver);
+
+MODULE_AUTHOR("Sebastian A. Siewior <bigeasy@linutronix.de>");
+MODULE_DESCRIPTION("ot200 LED driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:leds-ot200");
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index 43d08756d82..4a0e786b783 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -1,13 +1,14 @@
/*
* 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/acrobat/datasheets/PCA9532_3.pdf
+ * Datasheet: http://www.nxp.com/documents/data_sheet/PCA9532.pdf
*
*/
@@ -19,21 +20,32 @@
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/leds-pca9532.h>
+#include <linux/gpio.h>
-#define PCA9532_REG_PSC(i) (0x2+(i)*2)
-#define PCA9532_REG_PWM(i) (0x3+(i)*2)
-#define PCA9532_REG_LS0 0x6
-#define LED_REG(led) ((led>>2)+PCA9532_REG_LS0)
-#define LED_NUM(led) (led & 0x3)
+/* m = num_leds*/
+#define PCA9532_REG_INPUT(i) ((i) >> 3)
+#define PCA9532_REG_OFFSET(m) ((m) >> 4)
+#define PCA9532_REG_PSC(m, i) (PCA9532_REG_OFFSET(m) + 0x1 + (i) * 2)
+#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 ldev_to_led(c) container_of(c, struct pca9532_led, ldev)
+struct pca9532_chip_info {
+ u8 num_leds;
+};
+
struct pca9532_data {
struct i2c_client *client;
struct pca9532_led leds[16];
struct mutex update_lock;
struct input_dev *idev;
struct work_struct work;
+#ifdef CONFIG_LEDS_PCA9532_GPIO
+ struct gpio_chip gpio;
+#endif
+ const struct pca9532_chip_info *chip_info;
u8 pwm[2];
u8 psc[2];
};
@@ -42,23 +54,48 @@ static int pca9532_probe(struct i2c_client *client,
const struct i2c_device_id *id);
static int pca9532_remove(struct i2c_client *client);
+enum {
+ pca9530,
+ pca9531,
+ pca9532,
+ pca9533,
+};
+
static const struct i2c_device_id pca9532_id[] = {
- { "pca9532", 0 },
+ { "pca9530", pca9530 },
+ { "pca9531", pca9531 },
+ { "pca9532", pca9532 },
+ { "pca9533", pca9533 },
{ }
};
MODULE_DEVICE_TABLE(i2c, pca9532_id);
+static const struct pca9532_chip_info pca9532_chip_info_tbl[] = {
+ [pca9530] = {
+ .num_leds = 2,
+ },
+ [pca9531] = {
+ .num_leds = 8,
+ },
+ [pca9532] = {
+ .num_leds = 16,
+ },
+ [pca9533] = {
+ .num_leds = 4,
+ },
+};
+
static struct i2c_driver pca9532_driver = {
.driver = {
- .name = "pca9532",
+ .name = "leds-pca953x",
},
.probe = pca9532_probe,
.remove = pca9532_remove,
.id_table = pca9532_id,
};
-/* We have two pwm/blinkers, but 16 possible leds to drive. Additionaly,
+/* We have two pwm/blinkers, but 16 possible leds to drive. Additionally,
* the clever Thecus people are using one pwm to drive the beeper. So,
* as a compromise we average one pwm to the values requested by all
* leds that are not ON/OFF.
@@ -68,7 +105,7 @@ static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
{
int a = 0, b = 0, i = 0;
struct pca9532_data *data = i2c_get_clientdata(client);
- for (i = 0; i < 16; i++) {
+ for (i = 0; i < data->chip_info->num_leds; i++) {
if (data->leds[i].type == PCA9532_TYPE_LED &&
data->leds[i].state == PCA9532_PWM0+pwm) {
a++;
@@ -92,10 +129,12 @@ static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
static int pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
+ u8 maxleds = data->chip_info->num_leds;
+
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(pwm),
+ i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, pwm),
data->pwm[pwm]);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(pwm),
+ i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
data->psc[pwm]);
mutex_unlock(&data->update_lock);
return 0;
@@ -106,15 +145,16 @@ static void pca9532_setled(struct pca9532_led *led)
{
struct i2c_client *client = led->client;
struct pca9532_data *data = i2c_get_clientdata(client);
+ u8 maxleds = data->chip_info->num_leds;
char reg;
mutex_lock(&data->update_lock);
- reg = i2c_smbus_read_byte_data(client, LED_REG(led->id));
+ reg = i2c_smbus_read_byte_data(client, LED_REG(maxleds, led->id));
/* zero led bits */
reg = reg & ~(0x3<<LED_NUM(led->id)*2);
/* set the new value */
reg = reg | (led->state << LED_NUM(led->id)*2);
- i2c_smbus_write_byte_data(client, LED_REG(led->id), reg);
+ i2c_smbus_write_byte_data(client, LED_REG(maxleds, led->id), reg);
mutex_unlock(&data->update_lock);
}
@@ -146,7 +186,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
int err = 0;
if (*delay_on == 0 && *delay_off == 0) {
- /* led subsystem ask us for a blink rate */
+ /* led subsystem ask us for a blink rate */
*delay_on = 1000;
*delay_off = 1000;
}
@@ -183,10 +223,12 @@ static int pca9532_event(struct input_dev *dev, unsigned int type,
static void pca9532_input_work(struct work_struct *work)
{
- struct pca9532_data *data;
- data = container_of(work, struct pca9532_data, work);
+ struct pca9532_data *data =
+ container_of(work, struct pca9532_data, work);
+ u8 maxleds = data->chip_info->num_leds;
+
mutex_lock(&data->update_lock);
- i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(1),
+ i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(maxleds, 1),
data->pwm[1]);
mutex_unlock(&data->update_lock);
}
@@ -200,21 +242,113 @@ static void pca9532_led_work(struct work_struct *work)
pca9532_setled(led);
}
+#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_led *led = &data->leds[offset];
+
+ if (led->type == PCA9532_TYPE_GPIO)
+ return 0;
+
+ return -EBUSY;
+}
+
+static void pca9532_gpio_set_value(struct gpio_chip *gc, unsigned offset, int val)
+{
+ struct pca9532_data *data = container_of(gc, struct pca9532_data, gpio);
+ struct pca9532_led *led = &data->leds[offset];
+
+ if (val)
+ led->state = PCA9532_ON;
+ else
+ led->state = PCA9532_OFF;
+
+ pca9532_setled(led);
+}
+
+static int pca9532_gpio_get_value(struct gpio_chip *gc, unsigned offset)
+{
+ struct pca9532_data *data = container_of(gc, struct pca9532_data, gpio);
+ unsigned char reg;
+
+ reg = i2c_smbus_read_byte_data(data->client, PCA9532_REG_INPUT(offset));
+
+ return !!(reg & (1 << (offset % 8)));
+}
+
+static int pca9532_gpio_direction_input(struct gpio_chip *gc, unsigned offset)
+{
+ /* To use as input ensure pin is not driven */
+ pca9532_gpio_set_value(gc, offset, 0);
+
+ return 0;
+}
+
+static int pca9532_gpio_direction_output(struct gpio_chip *gc, unsigned offset, int val)
+{
+ pca9532_gpio_set_value(gc, offset, val);
+
+ return 0;
+}
+#endif /* CONFIG_LEDS_PCA9532_GPIO */
+
+static int 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:
+ case PCA9532_TYPE_GPIO:
+ 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) {
+ cancel_work_sync(&data->work);
+ data->idev = NULL;
+ }
+ break;
+ }
+ }
+
+#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;
+ }
+ }
+#endif
+
+ return 0;
+}
+
static int pca9532_configure(struct i2c_client *client,
struct pca9532_data *data, struct pca9532_platform_data *pdata)
{
int i, err = 0;
+ int gpios = 0;
+ u8 maxleds = data->chip_info->num_leds;
for (i = 0; i < 2; i++) {
data->pwm[i] = pdata->pwm[i];
data->psc[i] = pdata->psc[i];
- i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(i),
+ i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(maxleds, i),
data->pwm[i]);
- i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(i),
+ i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
data->psc[i]);
}
- for (i = 0; i < 16; i++) {
+ for (i = 0; i < data->chip_info->num_leds; i++) {
struct pca9532_led *led = &data->leds[i];
struct pca9532_led *pled = &pdata->leds[i];
led->client = client;
@@ -223,6 +357,9 @@ static int pca9532_configure(struct i2c_client *client,
switch (led->type) {
case PCA9532_TYPE_NONE:
break;
+ case PCA9532_TYPE_GPIO:
+ gpios++;
+ break;
case PCA9532_TYPE_LED:
led->state = pled->state;
led->name = pled->name;
@@ -244,7 +381,7 @@ static int pca9532_configure(struct i2c_client *client,
BUG_ON(data->idev);
led->state = PCA9532_PWM1;
pca9532_setled(led);
- data->idev = input_allocate_device();
+ data->idev = devm_input_allocate_device(&client->dev);
if (data->idev == NULL) {
err = -ENOMEM;
goto exit;
@@ -263,7 +400,6 @@ static int pca9532_configure(struct i2c_client *client,
INIT_WORK(&data->work, pca9532_input_work);
err = input_register_device(data->idev);
if (err) {
- input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
goto exit;
@@ -271,28 +407,38 @@ static int pca9532_configure(struct i2c_client *client,
break;
}
}
+
+#ifdef CONFIG_LEDS_PCA9532_GPIO
+ if (gpios) {
+ data->gpio.label = "gpio-pca9532";
+ data->gpio.direction_input = pca9532_gpio_direction_input;
+ data->gpio.direction_output = pca9532_gpio_direction_output;
+ data->gpio.set = pca9532_gpio_set_value;
+ data->gpio.get = pca9532_gpio_get_value;
+ data->gpio.request = pca9532_gpio_request_pin;
+ 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.owner = THIS_MODULE;
+
+ err = gpiochip_add(&data->gpio);
+ if (err) {
+ /* Use data->gpio.dev as a flag for freeing gpiochip */
+ data->gpio.dev = NULL;
+ dev_warn(&client->dev, "could not add gpiochip\n");
+ } else {
+ dev_info(&client->dev, "gpios %i...%i\n",
+ data->gpio.base, data->gpio.base +
+ data->gpio.ngpio - 1);
+ }
+ }
+#endif
+
return 0;
exit:
- if (i > 0)
- for (i = i - 1; i >= 0; i--)
- switch (data->leds[i].type) {
- case PCA9532_TYPE_NONE:
- 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) {
- input_unregister_device(data->idev);
- input_free_device(data->idev);
- cancel_work_sync(&data->work);
- data->idev = NULL;
- }
- break;
- }
-
+ pca9532_destroy_devices(data, i);
return err;
}
@@ -300,8 +446,8 @@ static int pca9532_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct pca9532_data *data = i2c_get_clientdata(client);
- struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data;
- int err;
+ struct pca9532_platform_data *pca9532_pdata =
+ dev_get_platdata(&client->dev);
if (!pca9532_pdata)
return -EIO;
@@ -310,62 +456,34 @@ static int pca9532_probe(struct i2c_client *client,
I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
- data = kzalloc(sizeof(*data), GFP_KERNEL);
+ data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
+ data->chip_info = &pca9532_chip_info_tbl[id->driver_data];
+
dev_info(&client->dev, "setting platform data\n");
i2c_set_clientdata(client, data);
data->client = client;
mutex_init(&data->update_lock);
- err = pca9532_configure(client, data, pca9532_pdata);
- if (err)
- kfree(data);
-
- return err;
+ return pca9532_configure(client, data, pca9532_pdata);
}
static int pca9532_remove(struct i2c_client *client)
{
struct pca9532_data *data = i2c_get_clientdata(client);
- int i;
- for (i = 0; i < 16; i++)
- switch (data->leds[i].type) {
- case PCA9532_TYPE_NONE:
- 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) {
- input_unregister_device(data->idev);
- input_free_device(data->idev);
- cancel_work_sync(&data->work);
- data->idev = NULL;
- }
- break;
- }
+ int err;
- kfree(data);
- return 0;
-}
+ err = pca9532_destroy_devices(data, data->chip_info->num_leds);
+ if (err)
+ return err;
-static int __init pca9532_init(void)
-{
- return i2c_add_driver(&pca9532_driver);
+ return 0;
}
-static void __exit pca9532_exit(void)
-{
- i2c_del_driver(&pca9532_driver);
-}
+module_i2c_driver(pca9532_driver);
MODULE_AUTHOR("Riku Voipio");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("PCA 9532 LED dimmer");
-
-module_init(pca9532_init);
-module_exit(pca9532_exit);
-
diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c
index 66aa3e8e786..c3a08b60535 100644
--- a/drivers/leds/leds-pca955x.c
+++ b/drivers/leds/leds-pca955x.c
@@ -101,11 +101,16 @@ static const struct i2c_device_id pca955x_id[] = {
};
MODULE_DEVICE_TABLE(i2c, pca955x_id);
-struct pca955x_led {
+struct pca955x {
+ struct mutex lock;
+ struct pca955x_led *leds;
struct pca955x_chipdef *chipdef;
struct i2c_client *client;
+};
+
+struct pca955x_led {
+ struct pca955x *pca955x;
struct work_struct work;
- spinlock_t lock;
enum led_brightness brightness;
struct led_classdev led_cdev;
int led_num; /* 0 .. 15 potentially */
@@ -140,7 +145,7 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
*/
static void pca955x_write_psc(struct i2c_client *client, int n, u8 val)
{
- struct pca955x_led *pca955x = i2c_get_clientdata(client);
+ struct pca955x *pca955x = i2c_get_clientdata(client);
i2c_smbus_write_byte_data(client,
pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n,
@@ -156,7 +161,7 @@ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val)
*/
static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
{
- struct pca955x_led *pca955x = i2c_get_clientdata(client);
+ struct pca955x *pca955x = i2c_get_clientdata(client);
i2c_smbus_write_byte_data(client,
pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n,
@@ -169,7 +174,7 @@ static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
*/
static void pca955x_write_ls(struct i2c_client *client, int n, u8 val)
{
- struct pca955x_led *pca955x = i2c_get_clientdata(client);
+ struct pca955x *pca955x = i2c_get_clientdata(client);
i2c_smbus_write_byte_data(client,
pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n,
@@ -182,7 +187,7 @@ static void pca955x_write_ls(struct i2c_client *client, int n, u8 val)
*/
static u8 pca955x_read_ls(struct i2c_client *client, int n)
{
- struct pca955x_led *pca955x = i2c_get_clientdata(client);
+ struct pca955x *pca955x = i2c_get_clientdata(client);
return (u8) i2c_smbus_read_byte_data(client,
pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n);
@@ -190,18 +195,23 @@ static u8 pca955x_read_ls(struct i2c_client *client, int n)
static void pca955x_led_work(struct work_struct *work)
{
- struct pca955x_led *pca955x;
+ struct pca955x_led *pca955x_led;
+ struct pca955x *pca955x;
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) */
- pca955x = container_of(work, struct pca955x_led, work);
- chip_ls = pca955x->led_num / 4;
- ls_led = pca955x->led_num % 4;
+ pca955x_led = container_of(work, struct pca955x_led, work);
+ pca955x = pca955x_led->pca955x;
+
+ chip_ls = pca955x_led->led_num / 4;
+ ls_led = pca955x_led->led_num % 4;
+
+ mutex_lock(&pca955x->lock);
ls = pca955x_read_ls(pca955x->client, chip_ls);
- switch (pca955x->brightness) {
+ switch (pca955x_led->brightness) {
case LED_FULL:
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
break;
@@ -219,12 +229,15 @@ static void pca955x_led_work(struct work_struct *work)
* 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->brightness);
+ pca955x_write_pwm(pca955x->client, 1,
+ 255 - pca955x_led->brightness);
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
break;
}
pca955x_write_ls(pca955x->client, chip_ls, ls);
+
+ mutex_unlock(&pca955x->lock);
}
static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value)
@@ -233,7 +246,6 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v
pca955x = container_of(led_cdev, struct pca955x_led, led_cdev);
- spin_lock(&pca955x->lock);
pca955x->brightness = value;
/*
@@ -241,14 +253,13 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v
* can sleep.
*/
schedule_work(&pca955x->work);
-
- spin_unlock(&pca955x->lock);
}
-static int __devinit pca955x_probe(struct i2c_client *client,
+static int pca955x_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct pca955x_led *pca955x;
+ struct pca955x *pca955x;
+ struct pca955x_led *pca955x_led;
struct pca955x_chipdef *chip;
struct i2c_adapter *adapter;
struct led_platform_data *pdata;
@@ -256,7 +267,7 @@ static int __devinit pca955x_probe(struct i2c_client *client,
chip = &pca955x_chipdefs[id->driver_data];
adapter = to_i2c_adapter(client->dev.parent);
- pdata = client->dev.platform_data;
+ pdata = dev_get_platdata(&client->dev);
/* Make sure the slave address / chip type combo given is possible */
if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) !=
@@ -266,7 +277,7 @@ static int __devinit pca955x_probe(struct i2c_client *client,
return -ENODEV;
}
- printk(KERN_INFO "leds-pca955x: Using %s %d-bit LED driver at "
+ dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
"slave address 0x%02x\n",
id->name, chip->bits, client->addr);
@@ -282,39 +293,47 @@ static int __devinit pca955x_probe(struct i2c_client *client,
}
}
- pca955x = kzalloc(sizeof(*pca955x) * chip->bits, GFP_KERNEL);
+ 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);
+ if (!pca955x->leds)
+ return -ENOMEM;
+
i2c_set_clientdata(client, pca955x);
+ mutex_init(&pca955x->lock);
+ pca955x->client = client;
+ pca955x->chipdef = chip;
+
for (i = 0; i < chip->bits; i++) {
- pca955x[i].chipdef = chip;
- pca955x[i].client = client;
- pca955x[i].led_num = 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[i].name,
- sizeof(pca955x[i].name), "pca955x:%s",
- 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[i].led_cdev.default_trigger =
+ pca955x_led->led_cdev.default_trigger =
pdata->leds[i].default_trigger;
} else {
- snprintf(pca955x[i].name, sizeof(pca955x[i].name),
+ snprintf(pca955x_led->name, sizeof(pca955x_led->name),
"pca955x:%d", i);
}
- spin_lock_init(&pca955x[i].lock);
-
- pca955x[i].led_cdev.name = pca955x[i].name;
- pca955x[i].led_cdev.brightness_set = pca955x_led_set;
+ pca955x_led->led_cdev.name = pca955x_led->name;
+ pca955x_led->led_cdev.brightness_set = pca955x_led_set;
- INIT_WORK(&pca955x[i].work, pca955x_led_work);
+ INIT_WORK(&pca955x_led->work, pca955x_led_work);
- err = led_classdev_register(&client->dev, &pca955x[i].led_cdev);
+ err = led_classdev_register(&client->dev,
+ &pca955x_led->led_cdev);
if (err < 0)
goto exit;
}
@@ -337,27 +356,23 @@ static int __devinit pca955x_probe(struct i2c_client *client,
exit:
while (i--) {
- led_classdev_unregister(&pca955x[i].led_cdev);
- cancel_work_sync(&pca955x[i].work);
+ led_classdev_unregister(&pca955x->leds[i].led_cdev);
+ cancel_work_sync(&pca955x->leds[i].work);
}
- kfree(pca955x);
-
return err;
}
-static int __devexit pca955x_remove(struct i2c_client *client)
+static int pca955x_remove(struct i2c_client *client)
{
- struct pca955x_led *pca955x = i2c_get_clientdata(client);
+ struct pca955x *pca955x = i2c_get_clientdata(client);
int i;
for (i = 0; i < pca955x->chipdef->bits; i++) {
- led_classdev_unregister(&pca955x[i].led_cdev);
- cancel_work_sync(&pca955x[i].work);
+ led_classdev_unregister(&pca955x->leds[i].led_cdev);
+ cancel_work_sync(&pca955x->leds[i].work);
}
- kfree(pca955x);
-
return 0;
}
@@ -367,22 +382,11 @@ static struct i2c_driver pca955x_driver = {
.owner = THIS_MODULE,
},
.probe = pca955x_probe,
- .remove = __devexit_p(pca955x_remove),
+ .remove = pca955x_remove,
.id_table = pca955x_id,
};
-static int __init pca955x_leds_init(void)
-{
- return i2c_add_driver(&pca955x_driver);
-}
-
-static void __exit pca955x_leds_exit(void)
-{
- i2c_del_driver(&pca955x_driver);
-}
-
-module_init(pca955x_leds_init);
-module_exit(pca955x_leds_exit);
+module_i2c_driver(pca955x_driver);
MODULE_AUTHOR("Nate Case <ncase@xes-inc.com>");
MODULE_DESCRIPTION("PCA955x LED driver");
diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c
new file mode 100644
index 00000000000..82589c0a568
--- /dev/null
+++ b/drivers/leds/leds-pca963x.c
@@ -0,0 +1,461 @@
+/*
+ * Copyright 2011 bct electronic GmbH
+ * Copyright 2013 Qtechnology/AS
+ *
+ * Author: Peter Meerwald <p.meerwald@bct-electronic.com>
+ * Author: Ricardo Ribalda <ricardo.ribalda@gmail.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)
+ * LED driver for the PCA9634 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/workqueue.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/platform_data/leds-pca963x.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_MODE2_DMBLNK 0x20 /* Enable blinking */
+
+#define PCA963X_MODE1 0x00
+#define PCA963X_MODE2 0x01
+#define PCA963X_PWM_BASE 0x02
+
+enum pca963x_type {
+ pca9633,
+ pca9634,
+};
+
+struct pca963x_chipdef {
+ u8 grppwm;
+ u8 grpfreq;
+ u8 ledout_base;
+ int n_leds;
+};
+
+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,
+ },
+};
+
+/* 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 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, pca963x_id);
+
+enum pca963x_cmd {
+ BRIGHTNESS_SET,
+ BLINK_SET,
+};
+
+struct pca963x_led;
+
+struct pca963x {
+ struct pca963x_chipdef *chipdef;
+ struct mutex mutex;
+ struct i2c_client *client;
+ struct pca963x_led *leds;
+};
+
+struct pca963x_led {
+ struct pca963x *chip;
+ struct work_struct work;
+ enum led_brightness brightness;
+ struct led_classdev led_cdev;
+ int led_num; /* 0 .. 7 potentially */
+ enum pca963x_cmd cmd;
+ char name[32];
+ u8 gdc;
+ u8 gfrq;
+};
+
+static void pca963x_brightness_work(struct pca963x_led *pca963x)
+{
+ u8 ledout_addr = pca963x->chip->chipdef->ledout_base
+ + (pca963x->led_num / 4);
+ u8 ledout;
+ int shift = 2 * (pca963x->led_num % 4);
+ u8 mask = 0x3 << shift;
+
+ mutex_lock(&pca963x->chip->mutex);
+ ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr);
+ switch (pca963x->brightness) {
+ case LED_FULL:
+ i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr,
+ (ledout & ~mask) | (PCA963X_LED_ON << shift));
+ break;
+ case LED_OFF:
+ i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr,
+ ledout & ~mask);
+ break;
+ default:
+ i2c_smbus_write_byte_data(pca963x->chip->client,
+ PCA963X_PWM_BASE + pca963x->led_num,
+ pca963x->brightness);
+ i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr,
+ (ledout & ~mask) | (PCA963X_LED_PWM << shift));
+ break;
+ }
+ mutex_unlock(&pca963x->chip->mutex);
+}
+
+static void pca963x_blink_work(struct pca963x_led *pca963x)
+{
+ u8 ledout_addr = pca963x->chip->chipdef->ledout_base +
+ (pca963x->led_num / 4);
+ u8 ledout;
+ u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client,
+ PCA963X_MODE2);
+ int shift = 2 * (pca963x->led_num % 4);
+ u8 mask = 0x3 << shift;
+
+ i2c_smbus_write_byte_data(pca963x->chip->client,
+ pca963x->chip->chipdef->grppwm, pca963x->gdc);
+
+ i2c_smbus_write_byte_data(pca963x->chip->client,
+ pca963x->chip->chipdef->grpfreq, pca963x->gfrq);
+
+ if (!(mode2 & PCA963X_MODE2_DMBLNK))
+ i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2,
+ mode2 | PCA963X_MODE2_DMBLNK);
+
+ mutex_lock(&pca963x->chip->mutex);
+ ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr);
+ if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift))
+ i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr,
+ (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift));
+ mutex_unlock(&pca963x->chip->mutex);
+}
+
+static void pca963x_work(struct work_struct *work)
+{
+ struct pca963x_led *pca963x = container_of(work,
+ struct pca963x_led, work);
+
+ switch (pca963x->cmd) {
+ case BRIGHTNESS_SET:
+ pca963x_brightness_work(pca963x);
+ break;
+ case BLINK_SET:
+ pca963x_blink_work(pca963x);
+ break;
+ }
+}
+
+static void pca963x_led_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct pca963x_led *pca963x;
+
+ pca963x = container_of(led_cdev, struct pca963x_led, led_cdev);
+
+ pca963x->cmd = BRIGHTNESS_SET;
+ pca963x->brightness = value;
+
+ /*
+ * Must use workqueue for the actual I/O since I2C operations
+ * can sleep.
+ */
+ schedule_work(&pca963x->work);
+}
+
+static int pca963x_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on, unsigned long *delay_off)
+{
+ struct pca963x_led *pca963x;
+ unsigned long time_on, time_off, period;
+ u8 gdc, gfrq;
+
+ pca963x = 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 = 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 = time_on + time_off;
+ }
+
+ /*
+ * From manual: duty cycle = (GDC / 256) ->
+ * (time_on / period) = (GDC / 256) ->
+ * GDC = ((time_on * 256) / period)
+ */
+ gdc = (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;
+
+ pca963x->cmd = BLINK_SET;
+ pca963x->gdc = gdc;
+ pca963x->gfrq = gfrq;
+
+ /*
+ * Must use workqueue for the actual I/O since I2C operations
+ * can sleep.
+ */
+ schedule_work(&pca963x->work);
+
+ *delay_on = time_on;
+ *delay_off = time_off;
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_OF)
+static struct pca963x_platform_data *
+pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip)
+{
+ struct device_node *np = client->dev.of_node, *child;
+ struct pca963x_platform_data *pdata;
+ struct led_info *pca963x_leds;
+ int count;
+
+ count = of_get_child_count(np);
+ if (!count || count > chip->n_leds)
+ return ERR_PTR(-ENODEV);
+
+ pca963x_leds = devm_kzalloc(&client->dev,
+ sizeof(struct led_info) * chip->n_leds, GFP_KERNEL);
+ if (!pca963x_leds)
+ return ERR_PTR(-ENOMEM);
+
+ for_each_child_of_node(np, child) {
+ struct led_info led;
+ u32 reg;
+ int res;
+
+ res = of_property_read_u32(child, "reg", &reg);
+ if ((res != 0) || (reg >= chip->n_leds))
+ continue;
+ led.name =
+ of_get_property(child, "label", NULL) ? : child->name;
+ led.default_trigger =
+ of_get_property(child, "linux,default-trigger", NULL);
+ pca963x_leds[reg] = led;
+ }
+ pdata = devm_kzalloc(&client->dev,
+ sizeof(struct pca963x_platform_data), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->leds.leds = pca963x_leds;
+ pdata->leds.num_leds = chip->n_leds;
+
+ /* default to open-drain unless totem pole (push-pull) is specified */
+ if (of_property_read_bool(np, "nxp,totem-pole"))
+ pdata->outdrv = PCA963X_TOTEM_POLE;
+ else
+ pdata->outdrv = PCA963X_OPEN_DRAIN;
+
+ /* default to software blinking unless hardware blinking is specified */
+ if (of_property_read_bool(np, "nxp,hw-blink"))
+ pdata->blink_type = PCA963X_HW_BLINK;
+ else
+ pdata->blink_type = PCA963X_SW_BLINK;
+
+ return pdata;
+}
+
+static const struct of_device_id of_pca963x_match[] = {
+ { .compatible = "nxp,pca9632", },
+ { .compatible = "nxp,pca9633", },
+ { .compatible = "nxp,pca9634", },
+ {},
+};
+#else
+static struct pca963x_platform_data *
+pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip)
+{
+ return ERR_PTR(-ENODEV);
+}
+#endif
+
+static int pca963x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct pca963x *pca963x_chip;
+ struct pca963x_led *pca963x;
+ struct pca963x_platform_data *pdata;
+ struct pca963x_chipdef *chip;
+ int i, err;
+
+ chip = &pca963x_chipdefs[id->driver_data];
+ pdata = dev_get_platdata(&client->dev);
+
+ if (!pdata) {
+ pdata = pca963x_dt_init(client, chip);
+ if (IS_ERR(pdata)) {
+ dev_warn(&client->dev, "could not parse configuration\n");
+ pdata = NULL;
+ }
+ }
+
+ if (pdata && (pdata->leds.num_leds < 1 ||
+ pdata->leds.num_leds > chip->n_leds)) {
+ dev_err(&client->dev, "board info must claim 1-%d LEDs",
+ chip->n_leds);
+ return -EINVAL;
+ }
+
+ pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip),
+ GFP_KERNEL);
+ if (!pca963x_chip)
+ return -ENOMEM;
+ pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x),
+ GFP_KERNEL);
+ if (!pca963x)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, pca963x_chip);
+
+ mutex_init(&pca963x_chip->mutex);
+ pca963x_chip->chipdef = chip;
+ pca963x_chip->client = client;
+ pca963x_chip->leds = pca963x;
+
+ /* Turn off LEDs by default*/
+ i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00);
+ if (chip->n_leds > 4)
+ i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00);
+
+ for (i = 0; i < chip->n_leds; i++) {
+ pca963x[i].led_num = i;
+ pca963x[i].chip = pca963x_chip;
+
+ /* Platform data can specify LED names and default triggers */
+ if (pdata && i < pdata->leds.num_leds) {
+ if (pdata->leds.leds[i].name)
+ snprintf(pca963x[i].name,
+ sizeof(pca963x[i].name), "pca963x:%s",
+ pdata->leds.leds[i].name);
+ if (pdata->leds.leds[i].default_trigger)
+ pca963x[i].led_cdev.default_trigger =
+ pdata->leds.leds[i].default_trigger;
+ }
+ if (!pdata || i >= pdata->leds.num_leds ||
+ !pdata->leds.leds[i].name)
+ snprintf(pca963x[i].name, sizeof(pca963x[i].name),
+ "pca963x:%d:%.2x:%d", client->adapter->nr,
+ client->addr, i);
+
+ pca963x[i].led_cdev.name = pca963x[i].name;
+ pca963x[i].led_cdev.brightness_set = pca963x_led_set;
+
+ if (pdata && pdata->blink_type == PCA963X_HW_BLINK)
+ pca963x[i].led_cdev.blink_set = pca963x_blink_set;
+
+ INIT_WORK(&pca963x[i].work, pca963x_work);
+
+ err = led_classdev_register(&client->dev, &pca963x[i].led_cdev);
+ if (err < 0)
+ goto exit;
+ }
+
+ /* Disable LED all-call address and set normal mode */
+ i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00);
+
+ /* Configure output: open-drain or totem pole (push-pull) */
+ if (pdata && pdata->outdrv == PCA963X_OPEN_DRAIN)
+ i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x01);
+
+ return 0;
+
+exit:
+ while (i--) {
+ led_classdev_unregister(&pca963x[i].led_cdev);
+ cancel_work_sync(&pca963x[i].work);
+ }
+
+ return err;
+}
+
+static int pca963x_remove(struct i2c_client *client)
+{
+ struct pca963x *pca963x = i2c_get_clientdata(client);
+ int i;
+
+ for (i = 0; i < pca963x->chipdef->n_leds; i++) {
+ led_classdev_unregister(&pca963x->leds[i].led_cdev);
+ cancel_work_sync(&pca963x->leds[i].work);
+ }
+
+ return 0;
+}
+
+static struct i2c_driver pca963x_driver = {
+ .driver = {
+ .name = "leds-pca963x",
+ .owner = THIS_MODULE,
+ .of_match_table = of_match_ptr(of_pca963x_match),
+ },
+ .probe = pca963x_probe,
+ .remove = pca963x_remove,
+ .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-pwm.c b/drivers/leds/leds-pwm.c
index da3fa8dcdf5..d672bb4480f 100644
--- a/drivers/leds/leds-pwm.c
+++ b/drivers/leds/leds-pwm.c
@@ -14,138 +14,230 @@
#include <linux/module.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/pwm.h>
#include <linux/leds_pwm.h>
#include <linux/slab.h>
+#include <linux/workqueue.h>
struct led_pwm_data {
struct led_classdev cdev;
struct pwm_device *pwm;
- unsigned int active_low;
+ struct work_struct work;
+ 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];
+};
+
+static void __led_pwm_set(struct led_pwm_data *led_dat)
+{
+ int new_duty = led_dat->duty;
+
+ pwm_config(led_dat->pwm, new_duty, led_dat->period);
+
+ if (new_duty == 0)
+ pwm_disable(led_dat->pwm);
+ else
+ pwm_enable(led_dat->pwm);
+}
+
+static void led_pwm_work(struct work_struct *work)
+{
+ struct led_pwm_data *led_dat =
+ container_of(work, struct led_pwm_data, work);
+
+ __led_pwm_set(led_dat);
+}
+
static void led_pwm_set(struct led_classdev *led_cdev,
enum led_brightness 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 long long duty = led_dat->period;
- if (brightness == 0) {
- pwm_config(led_dat->pwm, 0, period);
- pwm_disable(led_dat->pwm);
- } else {
- pwm_config(led_dat->pwm, brightness * period / max, period);
- pwm_enable(led_dat->pwm);
- }
+ duty *= brightness;
+ do_div(duty, max);
+
+ if (led_dat->active_low)
+ duty = led_dat->period - duty;
+
+ led_dat->duty = duty;
+
+ if (led_dat->can_sleep)
+ schedule_work(&led_dat->work);
+ else
+ __led_pwm_set(led_dat);
}
-static int led_pwm_probe(struct platform_device *pdev)
+static inline size_t sizeof_pwm_leds_priv(int num_leds)
{
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm *cur_led;
- struct led_pwm_data *leds_data, *led_dat;
- int i, ret = 0;
+ return sizeof(struct led_pwm_priv) +
+ (sizeof(struct led_pwm_data) * num_leds);
+}
- if (!pdata)
- return -EBUSY;
+static void led_pwm_cleanup(struct led_pwm_priv *priv)
+{
+ while (priv->num_leds--) {
+ led_classdev_unregister(&priv->leds[priv->num_leds].cdev);
+ if (priv->leds[priv->num_leds].can_sleep)
+ cancel_work_sync(&priv->leds[priv->num_leds].work);
+ }
+}
- leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds,
- GFP_KERNEL);
- if (!leds_data)
- return -ENOMEM;
+static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
+ struct led_pwm *led, struct device_node *child)
+{
+ struct led_pwm_data *led_data = &priv->leds[priv->num_leds];
+ int ret;
+
+ led_data->active_low = led->active_low;
+ led_data->cdev.name = led->name;
+ led_data->cdev.default_trigger = led->default_trigger;
+ led_data->cdev.brightness_set = led_pwm_set;
+ led_data->cdev.brightness = LED_OFF;
+ led_data->cdev.max_brightness = led->max_brightness;
+ led_data->cdev.flags = LED_CORE_SUSPENDRESUME;
+
+ if (child)
+ led_data->pwm = devm_of_pwm_get(dev, child, NULL);
+ else
+ led_data->pwm = devm_pwm_get(dev, led->name);
+ if (IS_ERR(led_data->pwm)) {
+ ret = PTR_ERR(led_data->pwm);
+ dev_err(dev, "unable to request PWM for %s: %d\n",
+ led->name, ret);
+ return ret;
+ }
- for (i = 0; i < pdata->num_leds; i++) {
- cur_led = &pdata->leds[i];
- led_dat = &leds_data[i];
+ if (child)
+ led_data->period = pwm_get_period(led_data->pwm);
- led_dat->pwm = pwm_request(cur_led->pwm_id,
- cur_led->name);
- if (IS_ERR(led_dat->pwm)) {
- dev_err(&pdev->dev, "unable to request PWM %d\n",
- cur_led->pwm_id);
- goto err;
- }
+ led_data->can_sleep = pwm_can_sleep(led_data->pwm);
+ if (led_data->can_sleep)
+ INIT_WORK(&led_data->work, led_pwm_work);
- 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;
-
- ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
- if (ret < 0) {
- pwm_free(led_dat->pwm);
- goto err;
- }
- }
+ led_data->period = pwm_get_period(led_data->pwm);
+ if (!led_data->period && (led->pwm_period_ns > 0))
+ led_data->period = led->pwm_period_ns;
- platform_set_drvdata(pdev, leds_data);
+ ret = led_classdev_register(dev, &led_data->cdev);
+ if (ret == 0) {
+ priv->num_leds++;
+ } else {
+ dev_err(dev, "failed to register PWM led for %s: %d\n",
+ led->name, ret);
+ }
- return 0;
+ return ret;
+}
-err:
- if (i > 0) {
- for (i = i - 1; i >= 0; i--) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
+static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv)
+{
+ struct device_node *child;
+ struct led_pwm led;
+ int ret = 0;
+
+ memset(&led, 0, sizeof(led));
+
+ for_each_child_of_node(dev->of_node, child) {
+ led.name = of_get_property(child, "label", NULL) ? :
+ child->name;
+
+ led.default_trigger = of_get_property(child,
+ "linux,default-trigger", NULL);
+ led.active_low = of_property_read_bool(child, "active-low");
+ of_property_read_u32(child, "max-brightness",
+ &led.max_brightness);
+
+ ret = led_pwm_add(dev, priv, &led, child);
+ if (ret) {
+ of_node_put(child);
+ break;
}
}
- kfree(leds_data);
-
return ret;
}
-static int __devexit led_pwm_remove(struct platform_device *pdev)
+static int led_pwm_probe(struct platform_device *pdev)
{
- int i;
- struct led_pwm_platform_data *pdata = pdev->dev.platform_data;
- struct led_pwm_data *leds_data;
+ struct led_pwm_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ struct led_pwm_priv *priv;
+ int count, i;
+ int ret = 0;
+
+ if (pdata)
+ count = pdata->num_leds;
+ else
+ count = of_get_child_count(pdev->dev.of_node);
+
+ if (!count)
+ return -EINVAL;
+
+ priv = devm_kzalloc(&pdev->dev, sizeof_pwm_leds_priv(count),
+ GFP_KERNEL);
+ if (!priv)
+ return -ENOMEM;
- leds_data = platform_get_drvdata(pdev);
+ if (pdata) {
+ for (i = 0; i < count; i++) {
+ ret = led_pwm_add(&pdev->dev, priv, &pdata->leds[i],
+ NULL);
+ if (ret)
+ break;
+ }
+ } else {
+ ret = led_pwm_create_of(&pdev->dev, priv);
+ }
- for (i = 0; i < pdata->num_leds; i++) {
- led_classdev_unregister(&leds_data[i].cdev);
- pwm_free(leds_data[i].pwm);
+ if (ret) {
+ led_pwm_cleanup(priv);
+ return ret;
}
- kfree(leds_data);
+ platform_set_drvdata(pdev, priv);
return 0;
}
+static int led_pwm_remove(struct platform_device *pdev)
+{
+ struct led_pwm_priv *priv = platform_get_drvdata(pdev);
+
+ led_pwm_cleanup(priv);
+
+ return 0;
+}
+
+static const struct of_device_id of_pwm_leds_match[] = {
+ { .compatible = "pwm-leds", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, of_pwm_leds_match);
+
static struct platform_driver led_pwm_driver = {
.probe = led_pwm_probe,
- .remove = __devexit_p(led_pwm_remove),
+ .remove = led_pwm_remove,
.driver = {
.name = "leds_pwm",
.owner = THIS_MODULE,
+ .of_match_table = of_pwm_leds_match,
},
};
-static int __init led_pwm_init(void)
-{
- return platform_driver_register(&led_pwm_driver);
-}
-
-static void __exit led_pwm_exit(void)
-{
- platform_driver_unregister(&led_pwm_driver);
-}
-
-module_init(led_pwm_init);
-module_exit(led_pwm_exit);
+module_platform_driver(led_pwm_driver);
MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
MODULE_DESCRIPTION("PWM LED driver for PXA");
diff --git a/drivers/leds/leds-rb532.c b/drivers/leds/leds-rb532.c
index c3525f37f73..2e746d257b0 100644
--- a/drivers/leds/leds-rb532.c
+++ b/drivers/leds/leds-rb532.c
@@ -16,7 +16,7 @@
#include <asm/mach-rc32434/rb.h>
static void rb532_led_set(struct led_classdev *cdev,
- enum led_brightness brightness)
+ enum led_brightness brightness)
{
if (brightness)
set_latch_u5(LO_ULED, 0);
@@ -37,12 +37,12 @@ static struct led_classdev rb532_uled = {
.default_trigger = "nand-disk",
};
-static int __devinit rb532_led_probe(struct platform_device *pdev)
+static int rb532_led_probe(struct platform_device *pdev)
{
return led_classdev_register(&pdev->dev, &rb532_uled);
}
-static int __devexit rb532_led_remove(struct platform_device *pdev)
+static int rb532_led_remove(struct platform_device *pdev)
{
led_classdev_unregister(&rb532_uled);
return 0;
@@ -50,28 +50,16 @@ static int __devexit rb532_led_remove(struct platform_device *pdev)
static struct platform_driver rb532_led_driver = {
.probe = rb532_led_probe,
- .remove = __devexit_p(rb532_led_remove),
+ .remove = rb532_led_remove,
.driver = {
.name = "rb532-led",
.owner = THIS_MODULE,
},
};
-static int __init rb532_led_init(void)
-{
- return platform_driver_register(&rb532_led_driver);
-}
-
-static void __exit rb532_led_exit(void)
-{
- platform_driver_unregister(&rb532_led_driver);
-}
-
-module_init(rb532_led_init);
-module_exit(rb532_led_exit);
-
-MODULE_ALIAS("platform:rb532-led");
+module_platform_driver(rb532_led_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("User LED support for Routerboard532");
MODULE_AUTHOR("Phil Sutter <n0-1@freewrt.org>");
+MODULE_ALIAS("platform:rb532-led");
diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c
index 3790816643b..358430db6e6 100644
--- a/drivers/leds/leds-regulator.c
+++ b/drivers/leds/leds-regulator.c
@@ -140,9 +140,10 @@ static void regulator_led_brightness_set(struct led_classdev *led_cdev,
schedule_work(&led->work);
}
-static int __devinit regulator_led_probe(struct platform_device *pdev)
+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 regulator_led *led;
struct regulator *vcc;
int ret = 0;
@@ -158,7 +159,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev)
return PTR_ERR(vcc);
}
- led = kzalloc(sizeof(*led), GFP_KERNEL);
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
if (led == NULL) {
ret = -ENOMEM;
goto err_vcc;
@@ -169,7 +170,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev)
dev_err(&pdev->dev, "Invalid default brightness %d\n",
pdata->brightness);
ret = -EINVAL;
- goto err_led;
+ goto err_vcc;
}
led->value = pdata->brightness;
@@ -178,6 +179,10 @@ static int __devinit regulator_led_probe(struct platform_device *pdev)
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->vcc = vcc;
+ /* to handle correctly an already enabled regulator */
+ if (regulator_is_enabled(led->vcc))
+ led->enabled = 1;
+
mutex_init(&led->mutex);
INIT_WORK(&led->work, led_work);
@@ -186,7 +191,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev)
ret = led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0) {
cancel_work_sync(&led->work);
- goto err_led;
+ goto err_vcc;
}
/* to expose the default value to userspace */
@@ -197,14 +202,12 @@ static int __devinit regulator_led_probe(struct platform_device *pdev)
return 0;
-err_led:
- kfree(led);
err_vcc:
regulator_put(vcc);
return ret;
}
-static int __devexit regulator_led_remove(struct platform_device *pdev)
+static int regulator_led_remove(struct platform_device *pdev)
{
struct regulator_led *led = platform_get_drvdata(pdev);
@@ -212,7 +215,6 @@ static int __devexit regulator_led_remove(struct platform_device *pdev)
cancel_work_sync(&led->work);
regulator_led_disable(led);
regulator_put(led->vcc);
- kfree(led);
return 0;
}
@@ -222,20 +224,10 @@ static struct platform_driver regulator_led_driver = {
.owner = THIS_MODULE,
},
.probe = regulator_led_probe,
- .remove = __devexit_p(regulator_led_remove),
+ .remove = regulator_led_remove,
};
-static int __init regulator_led_init(void)
-{
- return platform_driver_register(&regulator_led_driver);
-}
-module_init(regulator_led_init);
-
-static void __exit regulator_led_exit(void)
-{
- platform_driver_unregister(&regulator_led_driver);
-}
-module_exit(regulator_led_exit);
+module_platform_driver(regulator_led_driver);
MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
MODULE_DESCRIPTION("Regulator driven LED driver");
diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c
index a77771dc2e9..785eb53a87f 100644
--- a/drivers/leds/leds-s3c24xx.c
+++ b/drivers/leds/leds-s3c24xx.c
@@ -12,15 +12,15 @@
*/
#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 <linux/platform_data/leds-s3c24xx.h>
-#include <mach/hardware.h>
#include <mach/regs-gpio.h>
-#include <mach/leds-gpio.h>
+#include <plat/gpio-cfg.h>
/* our context */
@@ -44,17 +44,19 @@ static void s3c24xx_led_set(struct led_classdev *led_cdev,
{
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. */
- s3c2410_gpio_setpin(pd->gpio, (value ? 1 : 0) ^
- (pd->flags & S3C24XX_LEDF_ACTLOW));
-
- if (pd->flags & S3C24XX_LEDF_TRISTATE)
- s3c2410_gpio_cfgpin(pd->gpio,
- value ? S3C2410_GPIO_OUTPUT : S3C2410_GPIO_INPUT);
+ 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)
@@ -62,22 +64,20 @@ static int s3c24xx_led_remove(struct platform_device *dev)
struct s3c24xx_gpio_led *led = pdev_to_gpio(dev);
led_classdev_unregister(&led->cdev);
- kfree(led);
return 0;
}
static int s3c24xx_led_probe(struct platform_device *dev)
{
- struct s3c24xx_led_platdata *pdata = dev->dev.platform_data;
+ struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev);
struct s3c24xx_gpio_led *led;
int ret;
- led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL);
- if (led == NULL) {
- dev_err(&dev->dev, "No memory for device\n");
+ led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
+ GFP_KERNEL);
+ if (!led)
return -ENOMEM;
- }
platform_set_drvdata(dev, led);
@@ -88,27 +88,27 @@ static int s3c24xx_led_probe(struct platform_device *dev)
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 */
- if (pdata->flags & S3C24XX_LEDF_TRISTATE) {
- s3c2410_gpio_setpin(pdata->gpio, 0);
- s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT);
- } else {
- s3c2410_gpio_pullup(pdata->gpio, 0);
- s3c2410_gpio_setpin(pdata->gpio, 0);
- s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT);
- }
+ 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) {
+ if (ret < 0)
dev_err(&dev->dev, "led_classdev_register failed\n");
- kfree(led);
- return ret;
- }
- return 0;
+ return ret;
}
static struct platform_driver s3c24xx_led_driver = {
@@ -120,18 +120,7 @@ static struct platform_driver s3c24xx_led_driver = {
},
};
-static int __init s3c24xx_led_init(void)
-{
- return platform_driver_register(&s3c24xx_led_driver);
-}
-
-static void __exit s3c24xx_led_exit(void)
-{
- platform_driver_unregister(&s3c24xx_led_driver);
-}
-
-module_init(s3c24xx_led_init);
-module_exit(s3c24xx_led_exit);
+module_platform_driver(s3c24xx_led_driver);
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
MODULE_DESCRIPTION("S3C24XX LED driver");
diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c
index a688293abd0..2eb3ef62962 100644
--- a/drivers/leds/leds-ss4200.c
+++ b/drivers/leds/leds-ss4200.c
@@ -63,8 +63,7 @@ MODULE_LICENSE("GPL");
/*
* PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives.
*/
-static const struct pci_device_id 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) },
@@ -79,7 +78,7 @@ static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id)
return 1;
}
-static unsigned int __initdata nodetect;
+static bool nodetect;
module_param_named(nodetect, nodetect, bool, 0);
MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection");
@@ -92,7 +91,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 struct dmi_system_id nas_led_whitelist[] __initdata = {
{
.callback = ss4200_led_dmi_callback,
.ident = "Intel SS4200-E",
@@ -102,6 +101,7 @@ static struct dmi_system_id __initdata nas_led_whitelist[] = {
DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00")
}
},
+ {}
};
/*
@@ -197,7 +197,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;
@@ -262,7 +262,7 @@ static int nasgpio_led_set_blink(struct led_classdev *led_cdev,
* already taken care of this, but we will do so in a non destructive manner
* so that we have what we need whether the BIOS did it or not.
*/
-static int __devinit ich7_gpio_init(struct device *dev)
+static int ich7_gpio_init(struct device *dev)
{
int i;
u32 config_data = 0;
@@ -341,7 +341,7 @@ static void ich7_lpc_cleanup(struct device *dev)
* so we can retrive the required operational information and prepare the GPIO.
*/
static struct pci_dev *nas_gpio_pci_dev;
-static int __devinit ich7_lpc_probe(struct pci_dev *dev,
+static int ich7_lpc_probe(struct pci_dev *dev,
const struct pci_device_id *id)
{
int status;
@@ -458,7 +458,7 @@ static ssize_t nas_led_blink_store(struct device *dev,
struct led_classdev *led = dev_get_drvdata(dev);
unsigned long blink_state;
- ret = strict_strtoul(buf, 10, &blink_state);
+ ret = kstrtoul(buf, 10, &blink_state);
if (ret)
return ret;
diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c
index ab6d18f5c39..0b8cc4a021a 100644
--- a/drivers/leds/leds-sunfire.c
+++ b/drivers/leds/leds-sunfire.c
@@ -3,6 +3,8 @@
* Copyright (C) 2008 David S. Miller <davem@davemloft.net>
*/
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
@@ -14,9 +16,6 @@
#include <asm/fhc.h>
#include <asm/upa.h>
-#define DRIVER_NAME "leds-sunfire"
-#define PFX DRIVER_NAME ": "
-
MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
MODULE_DESCRIPTION("Sun Fire LED driver");
MODULE_LICENSE("GPL");
@@ -123,23 +122,21 @@ struct sunfire_drvdata {
struct sunfire_led leds[NUM_LEDS_PER_BOARD];
};
-static int __devinit sunfire_led_generic_probe(struct platform_device *pdev,
+static int sunfire_led_generic_probe(struct platform_device *pdev,
struct led_type *types)
{
struct sunfire_drvdata *p;
- int i, err = -EINVAL;
+ int i, err;
if (pdev->num_resources != 1) {
- printk(KERN_ERR PFX "Wrong number of resources %d, should be 1\n",
+ dev_err(&pdev->dev, "Wrong number of resources %d, should be 1\n",
pdev->num_resources);
- goto out;
+ return -EINVAL;
}
- p = kzalloc(sizeof(*p), GFP_KERNEL);
- if (!p) {
- printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata\n");
- goto out;
- }
+ p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL);
+ if (!p)
+ return -ENOMEM;
for (i = 0; i < NUM_LEDS_PER_BOARD; i++) {
struct led_classdev *lp = &p->leds[i].led_cdev;
@@ -152,34 +149,27 @@ static int __devinit sunfire_led_generic_probe(struct platform_device *pdev,
err = led_classdev_register(&pdev->dev, lp);
if (err) {
- printk(KERN_ERR PFX "Could not register %s LED\n",
+ dev_err(&pdev->dev, "Could not register %s LED\n",
lp->name);
- goto out_unregister_led_cdevs;
+ for (i--; i >= 0; i--)
+ led_classdev_unregister(&p->leds[i].led_cdev);
+ return err;
}
}
- dev_set_drvdata(&pdev->dev, p);
-
- err = 0;
-out:
- return err;
+ platform_set_drvdata(pdev, p);
-out_unregister_led_cdevs:
- for (i--; i >= 0; i--)
- led_classdev_unregister(&p->leds[i].led_cdev);
- goto out;
+ return 0;
}
-static int __devexit sunfire_led_generic_remove(struct platform_device *pdev)
+static int sunfire_led_generic_remove(struct platform_device *pdev)
{
- struct sunfire_drvdata *p = dev_get_drvdata(&pdev->dev);
+ 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);
- kfree(p);
-
return 0;
}
@@ -195,11 +185,11 @@ static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "clockboard-right",
.handler = clockboard_right_set,
- .default_trigger= "heartbeat",
+ .default_trigger = "heartbeat",
},
};
-static int __devinit sunfire_clockboard_led_probe(struct platform_device *pdev)
+static int sunfire_clockboard_led_probe(struct platform_device *pdev)
{
return sunfire_led_generic_probe(pdev, clockboard_led_types);
}
@@ -216,11 +206,11 @@ static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = {
{
.name = "fhc-right",
.handler = fhc_right_set,
- .default_trigger= "heartbeat",
+ .default_trigger = "heartbeat",
},
};
-static int __devinit sunfire_fhc_led_probe(struct platform_device *pdev)
+static int sunfire_fhc_led_probe(struct platform_device *pdev)
{
return sunfire_led_generic_probe(pdev, fhc_led_types);
}
@@ -230,7 +220,7 @@ MODULE_ALIAS("platform:sunfire-fhc-leds");
static struct platform_driver sunfire_clockboard_led_driver = {
.probe = sunfire_clockboard_led_probe,
- .remove = __devexit_p(sunfire_led_generic_remove),
+ .remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-clockboard-leds",
.owner = THIS_MODULE,
@@ -239,7 +229,7 @@ static struct platform_driver sunfire_clockboard_led_driver = {
static struct platform_driver sunfire_fhc_led_driver = {
.probe = sunfire_fhc_led_probe,
- .remove = __devexit_p(sunfire_led_generic_remove),
+ .remove = sunfire_led_generic_remove,
.driver = {
.name = "sunfire-fhc-leds",
.owner = THIS_MODULE,
@@ -251,13 +241,13 @@ static int __init sunfire_leds_init(void)
int err = platform_driver_register(&sunfire_clockboard_led_driver);
if (err) {
- printk(KERN_ERR PFX "Could not register clock board LED driver\n");
+ pr_err("Could not register clock board LED driver\n");
return err;
}
err = platform_driver_register(&sunfire_fhc_led_driver);
if (err) {
- printk(KERN_ERR PFX "Could not register FHC LED driver\n");
+ pr_err("Could not register FHC LED driver\n");
platform_driver_unregister(&sunfire_clockboard_led_driver);
}
diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c
new file mode 100644
index 00000000000..3d9e267a56c
--- /dev/null
+++ b/drivers/leds/leds-tca6507.c
@@ -0,0 +1,848 @@
+/*
+ * 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.
+ *
+ * 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.
+ *
+ * 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:
+ * 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.
+ *
+ * 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.
+ *
+ * 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.
+ *
+ *
+ * An led-tca6507 device must be provided with platform data or
+ * configured via devicetree.
+ *
+ * The platform-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_GPIO, 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.
+ *
+ * When configured via devicetree there is one child for each output.
+ * The "reg" determines the output number and "compatible" determines
+ * whether it is an LED or a GPIO. "linux,default-trigger" can set a
+ * default trigger.
+ */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/gpio.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) */
+#define TCA6507_LS_LED_OFF1 0x1 /* Output HI-Z (off) - not used */
+#define TCA6507_LS_LED_PWM0 0x2 /* Output LOW with Bank0 rate */
+#define TCA6507_LS_LED_PWM1 0x3 /* Output LOW with Bank1 rate */
+#define TCA6507_LS_LED_ON 0x4 /* Output LOW (on) */
+#define TCA6507_LS_LED_MIR 0x5 /* Output LOW with Master Intensity */
+#define TCA6507_LS_BLINK0 0x6 /* Blink at Bank0 rate */
+#define TCA6507_LS_BLINK1 0x7 /* Blink at Bank1 rate */
+
+enum {
+ BANK0,
+ BANK1,
+ MASTER,
+};
+static int bank_source[3] = {
+ TCA6507_LS_LED_PWM0,
+ TCA6507_LS_LED_PWM1,
+ TCA6507_LS_LED_MIR,
+};
+static int blink_source[2] = {
+ TCA6507_LS_BLINK0,
+ TCA6507_LS_BLINK1,
+};
+
+/* PWM registers */
+#define TCA6507_REG_CNT 11
+
+/*
+ * 0x00, 0x01, 0x02 encode the TCA6507_LS_* values, each output
+ * owns one bit in each register
+ */
+#define TCA6507_FADE_ON 0x03
+#define TCA6507_FULL_ON 0x04
+#define TCA6507_FADE_OFF 0x05
+#define TCA6507_FIRST_OFF 0x06
+#define TCA6507_SECOND_OFF 0x07
+#define TCA6507_MAX_INTENSITY 0x08
+#define TCA6507_MASTER_INTENSITY 0x09
+#define TCA6507_INITIALIZE 0x0A
+
+#define INIT_CODE 0x8
+
+#define TIMECODES 16
+static int time_codes[TIMECODES] = {
+ 0, 64, 128, 192, 256, 384, 512, 768,
+ 1024, 1536, 2048, 3072, 4096, 5760, 8128, 16320
+};
+
+/* Convert an led.brightness level (0..255) to a TCA6507 level (0..15) */
+static inline int TO_LEVEL(int brightness)
+{
+ return brightness >> 4;
+}
+
+/* ...and convert back */
+static inline int TO_BRIGHT(int level)
+{
+ if (level)
+ return (level << 4) | 0xf;
+ return 0;
+}
+
+#define NUM_LEDS 7
+struct tca6507_chip {
+ int reg_set; /* One bit per register where
+ * a '1' means the register
+ * should be written */
+ u8 reg_file[TCA6507_REG_CNT];
+ /* Bank 2 is Master Intensity and doesn't use times */
+ struct bank {
+ int level;
+ int ontime, offtime;
+ int on_dflt, off_dflt;
+ int time_use, level_use;
+ } bank[3];
+ struct i2c_client *client;
+ struct work_struct work;
+ spinlock_t lock;
+
+ struct tca6507_led {
+ struct tca6507_chip *chip;
+ struct led_classdev led_cdev;
+ int num;
+ int ontime, offtime;
+ int on_dflt, off_dflt;
+ int bank; /* Bank used, or -1 */
+ int blink; /* Set if hardware-blinking */
+ } leds[NUM_LEDS];
+#ifdef CONFIG_GPIOLIB
+ struct gpio_chip gpio;
+ const char *gpio_name[NUM_LEDS];
+ int gpio_map[NUM_LEDS];
+#endif
+};
+
+static const struct i2c_device_id tca6507_id[] = {
+ { "tca6507" },
+ { }
+};
+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).
+ */
+ int c1, c2;
+ int tmax = msec * 9 / 8;
+ int tmin = msec * 7 / 8;
+ int diff = 65536;
+
+ /* We start at '1' to ensure we never even think of choosing a
+ * total time of '0'.
+ */
+ for (c1 = 1; c1 < TIMECODES; c1++) {
+ int t = time_codes[c1];
+ if (t*2 < tmin)
+ continue;
+ if (t > tmax)
+ break;
+ for (c2 = 0; c2 <= c1; c2++) {
+ int tt = t + time_codes[c2];
+ int d;
+ if (tt < tmin)
+ continue;
+ if (tt > tmax)
+ break;
+ /* This works! */
+ d = abs(msec - tt);
+ if (d >= diff)
+ continue;
+ /* Best yet */
+ *c1p = c1;
+ *c2p = c2;
+ diff = d;
+ if (d == 0)
+ return msec;
+ }
+ }
+ if (diff < 65536) {
+ int actual;
+ if (msec & 1) {
+ c1 = *c2p;
+ *c2p = *c1p;
+ *c1p = c1;
+ }
+ actual = time_codes[*c1p] + time_codes[*c2p];
+ if (*c1p < *c2p)
+ return actual + 1;
+ else
+ return actual;
+ }
+ /* No close match */
+ return -EINVAL;
+}
+
+/*
+ * 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)
+{
+ int mask = (1 << led);
+ int bit;
+
+ for (bit = 0; bit < 3; bit++) {
+ int n = tca->reg_file[bit] & ~mask;
+ if (val & (1 << bit))
+ n |= mask;
+ if (tca->reg_file[bit] != n) {
+ tca->reg_file[bit] = n;
+ tca->reg_set |= (1 << bit);
+ }
+ }
+}
+
+/* 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)
+{
+ int mask = 0xF;
+ int n;
+ if (bank) {
+ mask <<= 4;
+ new <<= 4;
+ }
+ n = tca->reg_file[reg] & ~mask;
+ n |= new;
+ if (tca->reg_file[reg] != n) {
+ tca->reg_file[reg] = n;
+ tca->reg_set |= 1 << reg;
+ }
+}
+
+/* Update brightness level. */
+static void set_level(struct tca6507_chip *tca, int bank, int level)
+{
+ switch (bank) {
+ case BANK0:
+ case BANK1:
+ set_code(tca, TCA6507_MAX_INTENSITY, bank, level);
+ break;
+ case MASTER:
+ set_code(tca, TCA6507_MASTER_INTENSITY, 0, level);
+ break;
+ }
+ tca->bank[bank].level = level;
+}
+
+/* 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);
+ dev_dbg(&tca->client->dev,
+ "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);
+ tca->bank[bank].ontime = result;
+
+ 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],
+ c2, time_codes[c2], tca->bank[bank].offtime);
+ set_code(tca, TCA6507_FADE_OFF, bank, c2);
+ set_code(tca, TCA6507_FIRST_OFF, bank, c1);
+ set_code(tca, TCA6507_SECOND_OFF, bank, c1);
+ tca->bank[bank].offtime = result;
+
+ set_code(tca, TCA6507_INITIALIZE, bank, INIT_CODE);
+}
+
+/* Write all needed register of tca6507 */
+
+static void tca6507_work(struct work_struct *work)
+{
+ struct tca6507_chip *tca = container_of(work, struct tca6507_chip,
+ work);
+ struct i2c_client *cl = tca->client;
+ int set;
+ u8 file[TCA6507_REG_CNT];
+ int r;
+
+ spin_lock_irq(&tca->lock);
+ set = tca->reg_set;
+ memcpy(file, tca->reg_file, TCA6507_REG_CNT);
+ tca->reg_set = 0;
+ spin_unlock_irq(&tca->lock);
+
+ for (r = 0; r < TCA6507_REG_CNT; r++)
+ if (set & (1<<r))
+ i2c_smbus_write_byte_data(cl, r, file[r]);
+}
+
+static void led_release(struct tca6507_led *led)
+{
+ /* If led owns any resource, release it. */
+ struct tca6507_chip *tca = led->chip;
+ if (led->bank >= 0) {
+ struct bank *b = tca->bank + led->bank;
+ if (led->blink)
+ b->time_use--;
+ b->level_use--;
+ }
+ led->blink = 0;
+ led->bank = -1;
+}
+
+static int led_prepare(struct tca6507_led *led)
+{
+ /* 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;
+ int i;
+ struct bank *b;
+ int need_init = 0;
+
+ led->led_cdev.brightness = TO_BRIGHT(level);
+ if (level == 0) {
+ set_select(tca, led->num, TCA6507_LS_LED_OFF);
+ return 0;
+ }
+
+ 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.
+ */
+ int best = -1;/* full-on */
+ int diff = 15-level;
+
+ if (level == 15) {
+ set_select(tca, led->num, TCA6507_LS_LED_ON);
+ return 0;
+ }
+
+ for (i = MASTER; i >= BANK0; i--) {
+ int d;
+ if (tca->bank[i].level == level ||
+ tca->bank[i].level_use == 0) {
+ best = i;
+ break;
+ }
+ d = abs(level - tca->bank[i].level);
+ if (d < diff) {
+ diff = d;
+ best = i;
+ }
+ }
+ if (best == -1) {
+ /* Best brightness is full-on */
+ set_select(tca, led->num, TCA6507_LS_LED_ON);
+ led->led_cdev.brightness = LED_FULL;
+ return 0;
+ }
+
+ if (!tca->bank[best].level_use)
+ set_level(tca, best, level);
+
+ tca->bank[best].level_use++;
+ led->bank = best;
+ set_select(tca, led->num, bank_source[best]);
+ led->led_cdev.brightness = TO_BRIGHT(tca->bank[best].level);
+ return 0;
+ }
+
+ /*
+ * 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;
+ if (choose_times(led->offtime, &c1, &c2) < 0)
+ return -EINVAL;
+
+ for (i = BANK0; i <= BANK1; i++) {
+ if (tca->bank[i].level_use == 0)
+ /* not in use - it is ours! */
+ break;
+ if (tca->bank[i].level != level)
+ /* Incompatible level - skip */
+ /* FIX: if timer matches we maybe should consider
+ * this anyway...
+ */
+ continue;
+
+ if (tca->bank[i].time_use == 0)
+ /* Timer not in use, and level matches - use it */
+ break;
+
+ if (!(tca->bank[i].on_dflt ||
+ led->on_dflt ||
+ tca->bank[i].ontime == led->ontime))
+ /* on time is incompatible */
+ continue;
+
+ if (!(tca->bank[i].off_dflt ||
+ led->off_dflt ||
+ tca->bank[i].offtime == led->offtime))
+ /* off time is incompatible */
+ continue;
+
+ /* looks like a suitable match */
+ break;
+ }
+
+ if (i > BANK1)
+ /* Nothing matches - how sad */
+ return -EINVAL;
+
+ b = &tca->bank[i];
+ if (b->level_use == 0)
+ set_level(tca, i, level);
+ b->level_use++;
+ led->bank = i;
+
+ if (b->on_dflt ||
+ !led->on_dflt ||
+ b->time_use == 0) {
+ b->ontime = led->ontime;
+ b->on_dflt = led->on_dflt;
+ need_init = 1;
+ }
+
+ if (b->off_dflt ||
+ !led->off_dflt ||
+ b->time_use == 0) {
+ b->offtime = led->offtime;
+ b->off_dflt = led->off_dflt;
+ need_init = 1;
+ }
+
+ if (need_init)
+ set_times(tca, i);
+
+ led->ontime = b->ontime;
+ led->offtime = b->offtime;
+
+ b->time_use++;
+ led->blink = 1;
+ led->led_cdev.brightness = TO_BRIGHT(b->level);
+ set_select(tca, led->num, blink_source[i]);
+ return 0;
+}
+
+static int led_assign(struct tca6507_led *led)
+{
+ struct tca6507_chip *tca = led->chip;
+ int err;
+ unsigned long flags;
+
+ spin_lock_irqsave(&tca->lock, flags);
+ led_release(led);
+ err = led_prepare(led);
+ if (err) {
+ /*
+ * Can only fail on timer setup. In that case we need
+ * to re-establish as steady level.
+ */
+ led->ontime = 0;
+ led->offtime = 0;
+ led_prepare(led);
+ }
+ spin_unlock_irqrestore(&tca->lock, flags);
+
+ if (tca->reg_set)
+ schedule_work(&tca->work);
+ return err;
+}
+
+static void tca6507_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness brightness)
+{
+ struct tca6507_led *led = container_of(led_cdev, struct tca6507_led,
+ led_cdev);
+ led->led_cdev.brightness = brightness;
+ led->ontime = 0;
+ led->offtime = 0;
+ led_assign(led);
+}
+
+static int tca6507_blink_set(struct led_classdev *led_cdev,
+ unsigned long *delay_on,
+ unsigned long *delay_off)
+{
+ struct tca6507_led *led = container_of(led_cdev, struct tca6507_led,
+ led_cdev);
+
+ if (*delay_on == 0)
+ led->on_dflt = 1;
+ else if (delay_on != &led_cdev->blink_delay_on)
+ led->on_dflt = 0;
+ led->ontime = *delay_on;
+
+ if (*delay_off == 0)
+ led->off_dflt = 1;
+ else if (delay_off != &led_cdev->blink_delay_off)
+ led->off_dflt = 0;
+ led->offtime = *delay_off;
+
+ if (led->ontime == 0)
+ led->ontime = 512;
+ if (led->offtime == 0)
+ led->offtime = 512;
+
+ if (led->led_cdev.brightness == LED_OFF)
+ led->led_cdev.brightness = LED_FULL;
+ if (led_assign(led) < 0) {
+ led->ontime = 0;
+ led->offtime = 0;
+ led->led_cdev.brightness = LED_OFF;
+ return -EINVAL;
+ }
+ *delay_on = led->ontime;
+ *delay_off = led->offtime;
+ return 0;
+}
+
+#ifdef CONFIG_GPIOLIB
+static void tca6507_gpio_set_value(struct gpio_chip *gc,
+ unsigned offset, int val)
+{
+ struct tca6507_chip *tca = container_of(gc, struct tca6507_chip, gpio);
+ 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'.
+ */
+ 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);
+}
+
+static int tca6507_gpio_direction_output(struct gpio_chip *gc,
+ unsigned offset, int val)
+{
+ tca6507_gpio_set_value(gc, offset, val);
+ return 0;
+}
+
+static int tca6507_probe_gpios(struct i2c_client *client,
+ struct tca6507_chip *tca,
+ struct tca6507_platform_data *pdata)
+{
+ int err;
+ int i = 0;
+ int gpios = 0;
+
+ 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++;
+ }
+
+ if (!gpios)
+ 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.owner = THIS_MODULE;
+ tca->gpio.direction_output = tca6507_gpio_direction_output;
+ tca->gpio.set = tca6507_gpio_set_value;
+ tca->gpio.dev = &client->dev;
+#ifdef CONFIG_OF_GPIO
+ tca->gpio.of_node = of_node_get(client->dev.of_node);
+#endif
+ err = gpiochip_add(&tca->gpio);
+ 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,
+ 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)
+{
+ 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);
+ if (!count || count > NUM_LEDS)
+ return ERR_PTR(-ENODEV);
+
+ tca_leds = devm_kzalloc(&client->dev,
+ sizeof(struct led_info) * NUM_LEDS, GFP_KERNEL);
+ if (!tca_leds)
+ return ERR_PTR(-ENOMEM);
+
+ for_each_child_of_node(np, 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);
+ led.flags = 0;
+ if (of_property_match_string(child, "compatible", "gpio") >= 0)
+ led.flags |= TCA6507_MAKE_GPIO;
+ ret = of_property_read_u32(child, "reg", &reg);
+ if (ret != 0 || reg < 0 || reg >= NUM_LEDS)
+ continue;
+
+ tca_leds[reg] = led;
+ }
+ pdata = devm_kzalloc(&client->dev,
+ sizeof(struct tca6507_platform_data), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->leds.leds = tca_leds;
+ pdata->leds.num_leds = NUM_LEDS;
+#ifdef CONFIG_GPIOLIB
+ pdata->gpio_base = -1;
+#endif
+ return pdata;
+}
+
+static const struct of_device_id of_tca6507_leds_match[] = {
+ { .compatible = "ti,tca6507", },
+ {},
+};
+
+#else
+static struct tca6507_platform_data *
+tca6507_led_dt_init(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 i2c_adapter *adapter;
+ struct tca6507_platform_data *pdata;
+ int err;
+ int i = 0;
+
+ adapter = to_i2c_adapter(client->dev.parent);
+ pdata = dev_get_platdata(&client->dev);
+
+ 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);
+ }
+ }
+ tca = devm_kzalloc(&client->dev, sizeof(*tca), GFP_KERNEL);
+ if (!tca)
+ return -ENOMEM;
+
+ tca->client = client;
+ INIT_WORK(&tca->work, tca6507_work);
+ spin_lock_init(&tca->lock);
+ i2c_set_clientdata(client, tca);
+
+ for (i = 0; i < NUM_LEDS; i++) {
+ struct tca6507_led *l = tca->leds + i;
+
+ l->chip = tca;
+ l->num = i;
+ if (pdata->leds.leds[i].name && !pdata->leds.leds[i].flags) {
+ l->led_cdev.name = pdata->leds.leds[i].name;
+ l->led_cdev.default_trigger
+ = pdata->leds.leds[i].default_trigger;
+ 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);
+ if (err < 0)
+ goto exit;
+ }
+ }
+ err = tca6507_probe_gpios(client, tca, pdata);
+ if (err)
+ goto exit;
+ /* 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)
+{
+ 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,
+ .remove = tca6507_remove,
+ .id_table = tca6507_id,
+};
+
+module_i2c_driver(tca6507_driver);
+
+MODULE_AUTHOR("NeilBrown <neilb@suse.de>");
+MODULE_DESCRIPTION("TCA6507 LED/GPO driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-versatile.c b/drivers/leds/leds-versatile.c
new file mode 100644
index 00000000000..80553022d66
--- /dev/null
+++ b/drivers/leds/leds-versatile.c
@@ -0,0 +1,110 @@
+/*
+ * Driver for the 8 user LEDs found on the RealViews and Versatiles
+ * Based on DaVinci's DM365 board code
+ *
+ * License terms: GNU General Public License (GPL) version 2
+ * Author: Linus Walleij <triad@df.lth.se>
+ */
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/io.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include <linux/platform_device.h>
+
+struct versatile_led {
+ void __iomem *base;
+ struct led_classdev cdev;
+ u8 mask;
+};
+
+/*
+ * The triggers lines up below will only be used if the
+ * LED triggers are compiled in.
+ */
+static const struct {
+ const char *name;
+ const char *trigger;
+} versatile_leds[] = {
+ { "versatile:0", "heartbeat", },
+ { "versatile:1", "mmc0", },
+ { "versatile:2", "cpu0" },
+ { "versatile:3", "cpu1" },
+ { "versatile:4", "cpu2" },
+ { "versatile:5", "cpu3" },
+ { "versatile:6", },
+ { "versatile:7", },
+};
+
+static void versatile_led_set(struct led_classdev *cdev,
+ enum led_brightness b)
+{
+ struct versatile_led *led = container_of(cdev,
+ struct versatile_led, cdev);
+ u32 reg = readl(led->base);
+
+ if (b != LED_OFF)
+ reg |= led->mask;
+ else
+ reg &= ~led->mask;
+ writel(reg, led->base);
+}
+
+static enum led_brightness versatile_led_get(struct led_classdev *cdev)
+{
+ struct versatile_led *led = container_of(cdev,
+ struct versatile_led, cdev);
+ u32 reg = readl(led->base);
+
+ return (reg & led->mask) ? LED_FULL : LED_OFF;
+}
+
+static int versatile_leds_probe(struct platform_device *dev)
+{
+ int i;
+ struct resource *res;
+ void __iomem *base;
+
+ res = platform_get_resource(dev, IORESOURCE_MEM, 0);
+ base = devm_ioremap_resource(&dev->dev, res);
+ if (IS_ERR(base))
+ return PTR_ERR(base);
+
+ /* All off */
+ writel(0, base);
+ for (i = 0; i < ARRAY_SIZE(versatile_leds); i++) {
+ struct versatile_led *led;
+
+ led = kzalloc(sizeof(*led), GFP_KERNEL);
+ if (!led)
+ break;
+
+ led->base = base;
+ led->cdev.name = versatile_leds[i].name;
+ led->cdev.brightness_set = versatile_led_set;
+ led->cdev.brightness_get = versatile_led_get;
+ led->cdev.default_trigger = versatile_leds[i].trigger;
+ led->mask = BIT(i);
+
+ if (led_classdev_register(NULL, &led->cdev) < 0) {
+ kfree(led);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static struct platform_driver versatile_leds_driver = {
+ .driver = {
+ .name = "versatile-leds",
+ },
+ .probe = versatile_leds_probe,
+};
+
+module_platform_driver(versatile_leds_driver);
+
+MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>");
+MODULE_DESCRIPTION("ARM Versatile LED driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c
index ef5c24140a4..e72c974142d 100644
--- a/drivers/leds/leds-wm831x-status.c
+++ b/drivers/leds/leds-wm831x-status.c
@@ -10,7 +10,6 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/leds.h>
@@ -18,6 +17,7 @@
#include <linux/mfd/wm831x/core.h>
#include <linux/mfd/wm831x/pdata.h>
#include <linux/mfd/wm831x/status.h>
+#include <linux/module.h>
struct wm831x_status {
@@ -156,7 +156,7 @@ static int wm831x_status_blink_set(struct led_classdev *led_cdev,
return ret;
}
-static const char *led_src_texts[] = {
+static const char * const led_src_texts[] = {
"otp",
"power",
"charger",
@@ -229,23 +229,24 @@ 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");
+ dev_err(&pdev->dev, "No register resource\n");
ret = -EINVAL;
goto err;
}
- drvdata = kzalloc(sizeof(struct wm831x_status), GFP_KERNEL);
+ drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status),
+ GFP_KERNEL);
if (!drvdata)
return -ENOMEM;
- dev_set_drvdata(&pdev->dev, drvdata);
+ 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;
@@ -299,7 +300,6 @@ static int wm831x_status_probe(struct platform_device *pdev)
err_led:
led_classdev_unregister(&drvdata->cdev);
- kfree(drvdata);
err:
return ret;
}
@@ -310,7 +310,6 @@ static int wm831x_status_remove(struct platform_device *pdev)
device_remove_file(drvdata->cdev.dev, &dev_attr_src);
led_classdev_unregister(&drvdata->cdev);
- kfree(drvdata);
return 0;
}
@@ -324,17 +323,7 @@ static struct platform_driver wm831x_status_driver = {
.remove = wm831x_status_remove,
};
-static int __devinit wm831x_status_init(void)
-{
- return platform_driver_register(&wm831x_status_driver);
-}
-module_init(wm831x_status_init);
-
-static void wm831x_status_exit(void)
-{
- platform_driver_unregister(&wm831x_status_driver);
-}
-module_exit(wm831x_status_exit);
+module_platform_driver(wm831x_status_driver);
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
MODULE_DESCRIPTION("WM831x status LED driver");
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c
index 5aab32ce4f4..4133ffe2901 100644
--- a/drivers/leds/leds-wm8350.c
+++ b/drivers/leds/leds-wm8350.c
@@ -10,13 +10,13 @@
*/
#include <linux/kernel.h>
-#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/mfd/wm8350/pmic.h>
#include <linux/regulator/consumer.h>
#include <linux/slab.h>
+#include <linux/module.h>
/* Microamps */
static const int isink_cur[] = {
@@ -128,7 +128,10 @@ static void wm8350_led_disable(struct wm8350_led *led)
ret = regulator_disable(led->isink);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
- regulator_enable(led->dcdc);
+ ret = regulator_enable(led->dcdc);
+ if (ret != 0)
+ dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n",
+ ret);
return;
}
@@ -199,8 +202,8 @@ 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;
- int ret, i;
+ struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
+ int i;
if (pdata == NULL) {
dev_err(&pdev->dev, "no platform data\n");
@@ -213,24 +216,21 @@ static int wm8350_led_probe(struct platform_device *pdev)
return -EINVAL;
}
- isink = regulator_get(&pdev->dev, "led_isink");
+ isink = devm_regulator_get(&pdev->dev, "led_isink");
if (IS_ERR(isink)) {
- printk(KERN_ERR "%s: cant get ISINK\n", __func__);
+ dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__);
return PTR_ERR(isink);
}
- dcdc = regulator_get(&pdev->dev, "led_vcc");
+ dcdc = devm_regulator_get(&pdev->dev, "led_vcc");
if (IS_ERR(dcdc)) {
- printk(KERN_ERR "%s: cant get DCDC\n", __func__);
- ret = PTR_ERR(dcdc);
- goto err_isink;
+ dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__);
+ return PTR_ERR(dcdc);
}
- led = kzalloc(sizeof(*led), GFP_KERNEL);
- if (led == NULL) {
- ret = -ENOMEM;
- goto err_dcdc;
- }
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (led == NULL)
+ return -ENOMEM;
led->cdev.brightness_set = wm8350_led_set;
led->cdev.default_trigger = pdata->default_trigger;
@@ -256,19 +256,7 @@ static int wm8350_led_probe(struct platform_device *pdev)
led->value = LED_OFF;
platform_set_drvdata(pdev, led);
- ret = led_classdev_register(&pdev->dev, &led->cdev);
- if (ret < 0)
- goto err_led;
-
- return 0;
-
- err_led:
- kfree(led);
- err_dcdc:
- regulator_put(dcdc);
- err_isink:
- regulator_put(isink);
- return ret;
+ return led_classdev_register(&pdev->dev, &led->cdev);
}
static int wm8350_led_remove(struct platform_device *pdev)
@@ -276,11 +264,8 @@ static int wm8350_led_remove(struct platform_device *pdev)
struct wm8350_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
- flush_scheduled_work();
+ flush_work(&led->work);
wm8350_led_disable(led);
- regulator_put(led->dcdc);
- regulator_put(led->isink);
- kfree(led);
return 0;
}
@@ -294,17 +279,7 @@ static struct platform_driver wm8350_led_driver = {
.shutdown = wm8350_led_shutdown,
};
-static int __devinit wm8350_led_init(void)
-{
- return platform_driver_register(&wm8350_led_driver);
-}
-module_init(wm8350_led_init);
-
-static void wm8350_led_exit(void)
-{
- platform_driver_unregister(&wm8350_led_driver);
-}
-module_exit(wm8350_led_exit);
+module_platform_driver(wm8350_led_driver);
MODULE_AUTHOR("Mark Brown");
MODULE_DESCRIPTION("WM8350 LED driver");
diff --git a/drivers/leds/leds-wrap.c b/drivers/leds/leds-wrap.c
index 2982c86ac4c..b358cc05eff 100644
--- a/drivers/leds/leds-wrap.c
+++ b/drivers/leds/leds-wrap.c
@@ -15,8 +15,9 @@
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
-#include <asm/io.h>
+#include <linux/io.h>
#include <linux/scx200_gpio.h>
+#include <linux/module.h>
#define DRVNAME "wrap-led"
#define WRAP_POWER_LED_GPIO 2
diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h
index 2dd8ecbfdc3..4c50365344a 100644
--- a/drivers/leds/leds.h
+++ b/drivers/leds/leds.h
@@ -17,7 +17,7 @@
#include <linux/rwsem.h>
#include <linux/leds.h>
-static inline void led_set_brightness(struct led_classdev *led_cdev,
+static inline void __led_set_brightness(struct led_classdev *led_cdev,
enum led_brightness value)
{
if (value > led_cdev->max_brightness)
@@ -32,6 +32,8 @@ static inline int led_get_brightness(struct led_classdev *led_cdev)
return led_cdev->brightness;
}
+void led_stop_software_blink(struct led_classdev *led_cdev);
+
extern struct rw_semaphore leds_list_lock;
extern struct list_head leds_list;
@@ -40,10 +42,17 @@ 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,
diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig
new file mode 100644
index 00000000000..49794b47b51
--- /dev/null
+++ b/drivers/leds/trigger/Kconfig
@@ -0,0 +1,111 @@
+menuconfig LEDS_TRIGGERS
+ bool "LED Trigger support"
+ depends on LEDS_CLASS
+ help
+ This option enables trigger support for the leds class.
+ These triggers allow kernel events to drive the LEDs and can
+ be configured via sysfs. If unsure, say Y.
+
+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.
+
+ 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
+ sporadic events, when there are no clear begin and end trap points,
+ or on dense events, where this blinks the LED at constant rate if
+ rearmed continuously.
+
+ It also shows how to use the led_blink_set_oneshot() function.
+
+ If unsure, say Y.
+
+config LEDS_TRIGGER_IDE_DISK
+ bool "LED IDE Disk Trigger"
+ depends on IDE_GD_ATA
+ depends on LEDS_TRIGGERS
+ help
+ This allows LEDs to be controlled by IDE disk activity.
+ If unsure, say Y.
+
+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
+ load average.
+ If unsure, say Y.
+
+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.
+
+ If unsure, say N.
+
+config LEDS_TRIGGER_CPU
+ bool "LED CPU Trigger"
+ depends on LEDS_TRIGGERS
+ 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
+ CPUs are active on the system at any given moment.
+
+ If unsure, say N.
+
+config LEDS_TRIGGER_GPIO
+ tristate "LED GPIO Trigger"
+ depends on LEDS_TRIGGERS
+ depends on GPIOLIB
+ 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.
+
+ 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.
+
+comment "iptables trigger is under Netfilter config (LED target)"
+ depends on LEDS_TRIGGERS
+
+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.
+ If unsure, say Y.
+
+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.
+
+endif # LEDS_TRIGGERS
diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile
new file mode 100644
index 00000000000..1abf48dacf7
--- /dev/null
+++ b/drivers/leds/trigger/Makefile
@@ -0,0 +1,10 @@
+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_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_DEFAULT_ON) += ledtrig-default-on.o
+obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o
+obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o
diff --git a/drivers/leds/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c
index f948e57bd9b..47e55aa9eef 100644
--- a/drivers/leds/ledtrig-backlight.c
+++ b/drivers/leds/trigger/ledtrig-backlight.c
@@ -16,7 +16,7 @@
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/leds.h>
-#include "leds.h"
+#include "../leds.h"
#define BLANK 1
#define UNBLANK 0
@@ -26,6 +26,7 @@ struct bl_trig_notifier {
int brightness;
int old_status;
struct notifier_block notifier;
+ unsigned invert;
};
static int fb_notifier_callback(struct notifier_block *p,
@@ -35,24 +36,67 @@ static int fb_notifier_callback(struct notifier_block *p,
struct bl_trig_notifier, notifier);
struct led_classdev *led = n->led;
struct fb_event *fb_event = data;
- int *blank = fb_event->data;
-
- switch (event) {
- case FB_EVENT_BLANK :
- if (*blank && n->old_status == UNBLANK) {
- n->brightness = led->brightness;
- led_set_brightness(led, LED_OFF);
- n->old_status = BLANK;
- } else if (!*blank && n->old_status == BLANK) {
- led_set_brightness(led, n->brightness);
- n->old_status = UNBLANK;
- }
- break;
+ int *blank;
+ int new_status;
+
+ /* If we aren't interested in this event, skip it immediately ... */
+ if (event != FB_EVENT_BLANK)
+ return 0;
+
+ blank = fb_event->data;
+ new_status = *blank ? BLANK : UNBLANK;
+
+ if (new_status == n->old_status)
+ return 0;
+
+ if ((n->old_status == UNBLANK) ^ n->invert) {
+ n->brightness = led->brightness;
+ __led_set_brightness(led, LED_OFF);
+ } else {
+ __led_set_brightness(led, n->brightness);
}
+ n->old_status = new_status;
+
return 0;
}
+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;
+
+ return sprintf(buf, "%u\n", n->invert);
+}
+
+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;
+ unsigned long invert;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &invert);
+ if (ret < 0)
+ return ret;
+
+ if (invert > 1)
+ return -EINVAL;
+
+ n->invert = invert;
+
+ /* After inverting, we need to update the LED. */
+ if ((n->old_status == BLANK) ^ n->invert)
+ __led_set_brightness(led, LED_OFF);
+ else
+ __led_set_brightness(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;
@@ -66,6 +110,10 @@ static void bl_trig_activate(struct led_classdev *led)
return;
}
+ ret = device_create_file(led->dev, &dev_attr_inverted);
+ if (ret)
+ goto err_invert;
+
n->led = led;
n->brightness = led->brightness;
n->old_status = UNBLANK;
@@ -74,6 +122,13 @@ static void bl_trig_activate(struct led_classdev *led)
ret = fb_register_client(&n->notifier);
if (ret)
dev_err(led->dev, "unable to register backlight trigger\n");
+ led->activated = true;
+
+ return;
+
+err_invert:
+ led->trigger_data = NULL;
+ kfree(n);
}
static void bl_trig_deactivate(struct led_classdev *led)
@@ -81,9 +136,11 @@ static void bl_trig_deactivate(struct led_classdev *led)
struct bl_trig_notifier *n =
(struct bl_trig_notifier *) led->trigger_data;
- if (n) {
+ if (led->activated) {
+ device_remove_file(led->dev, &dev_attr_inverted);
fb_unregister_client(&n->notifier);
kfree(n);
+ led->activated = false;
}
}
diff --git a/drivers/leds/trigger/ledtrig-camera.c b/drivers/leds/trigger/ledtrig-camera.c
new file mode 100644
index 00000000000..9bd73a8bad5
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-camera.c
@@ -0,0 +1,57 @@
+/*
+ * Camera Flash and Torch On/Off Trigger
+ *
+ * based on ledtrig-ide-disk.c
+ *
+ * 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>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/leds.h>
+
+DEFINE_LED_TRIGGER(ledtrig_flash);
+DEFINE_LED_TRIGGER(ledtrig_torch);
+
+void ledtrig_flash_ctrl(bool on)
+{
+ enum led_brightness brt = on ? LED_FULL : LED_OFF;
+
+ led_trigger_event(ledtrig_flash, brt);
+}
+EXPORT_SYMBOL_GPL(ledtrig_flash_ctrl);
+
+void ledtrig_torch_ctrl(bool on)
+{
+ enum led_brightness brt = on ? LED_FULL : LED_OFF;
+
+ led_trigger_event(ledtrig_torch, brt);
+}
+EXPORT_SYMBOL_GPL(ledtrig_torch_ctrl);
+
+static int __init ledtrig_camera_init(void)
+{
+ led_trigger_register_simple("flash", &ledtrig_flash);
+ led_trigger_register_simple("torch", &ledtrig_torch);
+ return 0;
+}
+module_init(ledtrig_camera_init);
+
+static void __exit ledtrig_camera_exit(void)
+{
+ led_trigger_unregister_simple(ledtrig_torch);
+ led_trigger_unregister_simple(ledtrig_flash);
+}
+module_exit(ledtrig_camera_exit);
+
+MODULE_DESCRIPTION("LED Trigger for Camera Flash/Torch Control");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c
new file mode 100644
index 00000000000..aec0f02b6b3
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-cpu.c
@@ -0,0 +1,166 @@
+/*
+ * 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.
+ *
+ * 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
+ *
+ * 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 {
+ char name[MAX_NAME_LEN];
+ struct led_trigger *_trig;
+};
+
+static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig);
+
+/**
+ * ledtrig_cpu - emit a CPU event as a trigger
+ * @evt: 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.
+ */
+void ledtrig_cpu(enum cpu_led_event ledevt)
+{
+ struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig);
+
+ /* 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);
+ 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);
+ break;
+
+ default:
+ /* Will leave the LED as it is */
+ break;
+ }
+}
+EXPORT_SYMBOL(ledtrig_cpu);
+
+static int ledtrig_cpu_syscore_suspend(void)
+{
+ ledtrig_cpu(CPU_LED_STOP);
+ return 0;
+}
+
+static void ledtrig_cpu_syscore_resume(void)
+{
+ ledtrig_cpu(CPU_LED_START);
+}
+
+static void ledtrig_cpu_syscore_shutdown(void)
+{
+ ledtrig_cpu(CPU_LED_HALTED);
+}
+
+static struct syscore_ops ledtrig_cpu_syscore_ops = {
+ .shutdown = ledtrig_cpu_syscore_shutdown,
+ .suspend = ledtrig_cpu_syscore_suspend,
+ .resume = ledtrig_cpu_syscore_resume,
+};
+
+static int ledtrig_cpu_notify(struct notifier_block *self,
+ unsigned long action, void *hcpu)
+{
+ switch (action & ~CPU_TASKS_FROZEN) {
+ case CPU_STARTING:
+ ledtrig_cpu(CPU_LED_START);
+ break;
+ case CPU_DYING:
+ ledtrig_cpu(CPU_LED_STOP);
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+
+static struct notifier_block ledtrig_cpu_nb = {
+ .notifier_call = ledtrig_cpu_notify,
+};
+
+static int __init ledtrig_cpu_init(void)
+{
+ int cpu;
+
+ /* Supports up to 9999 cpu cores */
+ BUILD_BUG_ON(CONFIG_NR_CPUS > 9999);
+
+ /*
+ * Registering CPU led trigger for each CPU core here
+ * ignores CPU hotplug, but after this CPU hotplug works
+ * fine with this trigger.
+ */
+ for_each_possible_cpu(cpu) {
+ struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu);
+
+ snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu);
+
+ led_trigger_register_simple(trig->name, &trig->_trig);
+ }
+
+ register_syscore_ops(&ledtrig_cpu_syscore_ops);
+ register_cpu_notifier(&ledtrig_cpu_nb);
+
+ 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;
+
+ unregister_cpu_notifier(&ledtrig_cpu_nb);
+
+ 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");
diff --git a/drivers/leds/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c
index a4ef54b9d50..81a91be8e18 100644
--- a/drivers/leds/ledtrig-default-on.c
+++ b/drivers/leds/trigger/ledtrig-default-on.c
@@ -15,11 +15,11 @@
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/leds.h>
-#include "leds.h"
+#include "../leds.h"
static void defon_trig_activate(struct led_classdev *led_cdev)
{
- led_set_brightness(led_cdev, led_cdev->max_brightness);
+ __led_set_brightness(led_cdev, led_cdev->max_brightness);
}
static struct led_trigger defon_led_trigger = {
diff --git a/drivers/leds/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c
index 991d93be0f4..35812e3a37f 100644
--- a/drivers/leds/ledtrig-gpio.c
+++ b/drivers/leds/trigger/ledtrig-gpio.c
@@ -17,7 +17,7 @@
#include <linux/workqueue.h>
#include <linux/leds.h>
#include <linux/slab.h>
-#include "leds.h"
+#include "../leds.h"
struct gpio_trig_data {
struct led_classdev *led;
@@ -54,12 +54,12 @@ static void gpio_trig_work(struct work_struct *work)
if (tmp) {
if (gpio_data->desired_brightness)
- led_set_brightness(gpio_data->led,
+ __led_set_brightness(gpio_data->led,
gpio_data->desired_brightness);
else
- led_set_brightness(gpio_data->led, LED_FULL);
+ __led_set_brightness(gpio_data->led, LED_FULL);
} else {
- led_set_brightness(gpio_data->led, LED_OFF);
+ __led_set_brightness(gpio_data->led, LED_OFF);
}
}
@@ -99,7 +99,7 @@ static ssize_t gpio_trig_inverted_show(struct device *dev,
struct led_classdev *led = dev_get_drvdata(dev);
struct gpio_trig_data *gpio_data = led->trigger_data;
- return sprintf(buf, "%s\n", gpio_data->inverted ? "yes" : "no");
+ return sprintf(buf, "%u\n", gpio_data->inverted);
}
static ssize_t gpio_trig_inverted_store(struct device *dev,
@@ -107,16 +107,17 @@ static ssize_t gpio_trig_inverted_store(struct device *dev,
{
struct led_classdev *led = dev_get_drvdata(dev);
struct gpio_trig_data *gpio_data = led->trigger_data;
- unsigned inverted;
+ unsigned long inverted;
int ret;
- ret = sscanf(buf, "%u", &inverted);
- if (ret < 1) {
- dev_err(dev, "invalid value\n");
+ ret = kstrtoul(buf, 10, &inverted);
+ if (ret < 0)
+ return ret;
+
+ if (inverted > 1)
return -EINVAL;
- }
- gpio_data->inverted = !!inverted;
+ gpio_data->inverted = inverted;
/* After inverting, we need to update the LED. */
schedule_work(&gpio_data->work);
@@ -199,6 +200,7 @@ static void gpio_trig_activate(struct led_classdev *led)
gpio_data->led = led;
led->trigger_data = gpio_data;
INIT_WORK(&gpio_data->work, gpio_trig_work);
+ led->activated = true;
return;
@@ -216,7 +218,7 @@ static void gpio_trig_deactivate(struct led_classdev *led)
{
struct gpio_trig_data *gpio_data = led->trigger_data;
- if (gpio_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);
@@ -224,6 +226,7 @@ static void gpio_trig_deactivate(struct led_classdev *led)
if (gpio_data->gpio != 0)
free_irq(gpio_to_irq(gpio_data->gpio), led);
kfree(gpio_data);
+ led->activated = false;
}
}
diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c
index 759c0bba4a8..5c8464a3317 100644
--- a/drivers/leds/ledtrig-heartbeat.c
+++ b/drivers/leds/trigger/ledtrig-heartbeat.c
@@ -18,7 +18,10 @@
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/leds.h>
-#include "leds.h"
+#include <linux/reboot.h>
+#include "../leds.h"
+
+static int panic_heartbeats;
struct heartbeat_trig_data {
unsigned int phase;
@@ -33,6 +36,11 @@ static void led_heartbeat_function(unsigned long data)
unsigned long brightness = LED_OFF;
unsigned long delay = 0;
+ if (unlikely(panic_heartbeats)) {
+ led_set_brightness(led_cdev, LED_OFF);
+ return;
+ }
+
/* acts like an actual heart beat -- ie thump-thump-pause... */
switch (heartbeat_data->phase) {
case 0:
@@ -66,7 +74,7 @@ static void led_heartbeat_function(unsigned long data)
break;
}
- led_set_brightness(led_cdev, brightness);
+ __led_set_brightness(led_cdev, brightness);
mod_timer(&heartbeat_data->timer, jiffies + delay);
}
@@ -83,15 +91,17 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev)
led_heartbeat_function, (unsigned long) led_cdev);
heartbeat_data->phase = 0;
led_heartbeat_function(heartbeat_data->timer.data);
+ led_cdev->activated = true;
}
static void heartbeat_trig_deactivate(struct led_classdev *led_cdev)
{
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data;
- if (heartbeat_data) {
+ if (led_cdev->activated) {
del_timer_sync(&heartbeat_data->timer);
kfree(heartbeat_data);
+ led_cdev->activated = false;
}
}
@@ -101,13 +111,45 @@ static struct led_trigger heartbeat_led_trigger = {
.deactivate = heartbeat_trig_deactivate,
};
+static int heartbeat_reboot_notifier(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ led_trigger_unregister(&heartbeat_led_trigger);
+ return NOTIFY_DONE;
+}
+
+static int heartbeat_panic_notifier(struct notifier_block *nb,
+ unsigned long code, void *unused)
+{
+ panic_heartbeats = 1;
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block heartbeat_reboot_nb = {
+ .notifier_call = heartbeat_reboot_notifier,
+};
+
+static struct notifier_block heartbeat_panic_nb = {
+ .notifier_call = heartbeat_panic_notifier,
+};
+
static int __init heartbeat_trig_init(void)
{
- return led_trigger_register(&heartbeat_led_trigger);
+ int rc = led_trigger_register(&heartbeat_led_trigger);
+
+ if (!rc) {
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &heartbeat_panic_nb);
+ register_reboot_notifier(&heartbeat_reboot_nb);
+ }
+ return rc;
}
static void __exit heartbeat_trig_exit(void)
{
+ unregister_reboot_notifier(&heartbeat_reboot_nb);
+ atomic_notifier_chain_unregister(&panic_notifier_list,
+ &heartbeat_panic_nb);
led_trigger_unregister(&heartbeat_led_trigger);
}
diff --git a/drivers/leds/ledtrig-ide-disk.c b/drivers/leds/trigger/ledtrig-ide-disk.c
index ec099fcbcb0..2cd7c0cf592 100644
--- a/drivers/leds/ledtrig-ide-disk.c
+++ b/drivers/leds/trigger/ledtrig-ide-disk.c
@@ -12,39 +12,22 @@
*/
#include <linux/module.h>
-#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/init.h>
-#include <linux/timer.h>
#include <linux/leds.h>
-static void ledtrig_ide_timerfunc(unsigned long data);
+#define BLINK_DELAY 30
DEFINE_LED_TRIGGER(ledtrig_ide);
-static DEFINE_TIMER(ledtrig_ide_timer, ledtrig_ide_timerfunc, 0, 0);
-static int ide_activity;
-static int ide_lastactivity;
+static unsigned long ide_blink_delay = BLINK_DELAY;
void ledtrig_ide_activity(void)
{
- ide_activity++;
- if (!timer_pending(&ledtrig_ide_timer))
- mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10));
+ led_trigger_blink_oneshot(ledtrig_ide,
+ &ide_blink_delay, &ide_blink_delay, 0);
}
EXPORT_SYMBOL(ledtrig_ide_activity);
-static void ledtrig_ide_timerfunc(unsigned long data)
-{
- if (ide_lastactivity != ide_activity) {
- ide_lastactivity = ide_activity;
- /* INT_MAX will set each LED to its maximum brightness */
- led_trigger_event(ledtrig_ide, INT_MAX);
- mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10));
- } else {
- led_trigger_event(ledtrig_ide, LED_OFF);
- }
-}
-
static int __init ledtrig_ide_init(void)
{
led_trigger_register_simple("ide-disk", &ledtrig_ide);
diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c
new file mode 100644
index 00000000000..cb4c7466692
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-oneshot.c
@@ -0,0 +1,204 @@
+/*
+ * 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>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/ctype.h>
+#include <linux/slab.h>
+#include <linux/leds.h>
+#include "../leds.h"
+
+#define DEFAULT_DELAY 100
+
+struct oneshot_trig_data {
+ unsigned int invert;
+};
+
+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;
+
+ led_blink_set_oneshot(led_cdev,
+ &led_cdev->blink_delay_on, &led_cdev->blink_delay_off,
+ oneshot_data->invert);
+
+ /* content is ignored */
+ return size;
+}
+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;
+
+ return sprintf(buf, "%u\n", oneshot_data->invert);
+}
+
+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;
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ oneshot_data->invert = !!state;
+
+ if (oneshot_data->invert)
+ __led_set_brightness(led_cdev, LED_FULL);
+ else
+ __led_set_brightness(led_cdev, LED_OFF);
+
+ return size;
+}
+
+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);
+
+ return sprintf(buf, "%lu\n", led_cdev->blink_delay_on);
+}
+
+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);
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ led_cdev->blink_delay_on = state;
+
+ 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);
+
+ return sprintf(buf, "%lu\n", led_cdev->blink_delay_off);
+}
+
+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);
+ unsigned long state;
+ int ret;
+
+ ret = kstrtoul(buf, 0, &state);
+ if (ret)
+ return ret;
+
+ led_cdev->blink_delay_off = state;
+
+ return size;
+}
+
+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 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)
+{
+ struct oneshot_trig_data *oneshot_data;
+ int rc;
+
+ 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 = DEFAULT_DELAY;
+ led_cdev->blink_delay_off = DEFAULT_DELAY;
+
+ led_cdev->activated = true;
+
+ return;
+
+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);
+}
+
+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;
+ }
+
+ /* Stop blinking */
+ led_set_brightness(led_cdev, LED_OFF);
+}
+
+static struct led_trigger oneshot_led_trigger = {
+ .name = "oneshot",
+ .activate = oneshot_trig_activate,
+ .deactivate = oneshot_trig_deactivate,
+};
+
+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_AUTHOR("Fabio Baltieri <fabio.baltieri@gmail.com>");
+MODULE_DESCRIPTION("One-shot LED trigger");
+MODULE_LICENSE("GPL");
diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c
index b09bcbeade9..8d09327b571 100644
--- a/drivers/leds/ledtrig-timer.c
+++ b/drivers/leds/trigger/ledtrig-timer.c
@@ -17,7 +17,6 @@
#include <linux/device.h>
#include <linux/ctype.h>
#include <linux/leds.h>
-#include "leds.h"
static ssize_t led_delay_on_show(struct device *dev,
struct device_attribute *attr, char *buf)
@@ -31,20 +30,17 @@ 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);
- int ret = -EINVAL;
- char *after;
- unsigned long state = simple_strtoul(buf, &after, 10);
- size_t count = after - buf;
+ unsigned long state;
+ ssize_t ret = -EINVAL;
- if (isspace(*after))
- count++;
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
- if (count == size) {
- led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
- ret = count;
- }
+ led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
+ led_cdev->blink_delay_on = state;
- return ret;
+ return size;
}
static ssize_t led_delay_off_show(struct device *dev,
@@ -59,20 +55,17 @@ 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);
- int ret = -EINVAL;
- char *after;
- unsigned long state = simple_strtoul(buf, &after, 10);
- size_t count = after - buf;
+ unsigned long state;
+ ssize_t ret = -EINVAL;
- if (isspace(*after))
- count++;
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
- if (count == size) {
- led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state);
- ret = count;
- }
+ led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state);
+ led_cdev->blink_delay_off = state;
- return ret;
+ return size;
}
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);
@@ -91,7 +84,9 @@ static void timer_trig_activate(struct led_classdev *led_cdev)
if (rc)
goto err_out_delayon;
- led_cdev->trigger_data = (void *)1;
+ led_blink_set(led_cdev, &led_cdev->blink_delay_on,
+ &led_cdev->blink_delay_off);
+ led_cdev->activated = true;
return;
@@ -101,13 +96,14 @@ err_out_delayon:
static void timer_trig_deactivate(struct led_classdev *led_cdev)
{
- if (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);
+ led_cdev->activated = false;
}
/* Stop blinking */
- led_brightness_set(led_cdev, LED_OFF);
+ led_set_brightness(led_cdev, LED_OFF);
}
static struct led_trigger timer_led_trigger = {
diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c
new file mode 100644
index 00000000000..e5abc00bb00
--- /dev/null
+++ b/drivers/leds/trigger/ledtrig-transient.c
@@ -0,0 +1,237 @@
+/*
+ * 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
+*/
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/timer.h>
+#include <linux/leds.h>
+#include "../leds.h"
+
+struct transient_trig_data {
+ int activate;
+ int state;
+ int restore_state;
+ unsigned long duration;
+ struct timer_list timer;
+};
+
+static void transient_timer_function(unsigned long data)
+{
+ struct led_classdev *led_cdev = (struct led_classdev *) data;
+ struct transient_trig_data *transient_data = led_cdev->trigger_data;
+
+ transient_data->activate = 0;
+ __led_set_brightness(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;
+
+ return sprintf(buf, "%d\n", transient_data->activate);
+}
+
+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;
+ unsigned long state;
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ if (state != 1 && state != 0)
+ return -EINVAL;
+
+ /* cancel the running timer */
+ if (state == 0 && transient_data->activate == 1) {
+ del_timer(&transient_data->timer);
+ transient_data->activate = state;
+ __led_set_brightness(led_cdev, transient_data->restore_state);
+ return size;
+ }
+
+ /* start timer if there is no active timer */
+ if (state == 1 && transient_data->activate == 0 &&
+ transient_data->duration != 0) {
+ transient_data->activate = state;
+ __led_set_brightness(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);
+ }
+
+ /* state == 0 && transient_data->activate == 0
+ timer is not active - just return */
+ /* state == 1 && transient_data->activate == 1
+ timer is already active - just return */
+
+ return size;
+}
+
+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;
+
+ return sprintf(buf, "%lu\n", transient_data->duration);
+}
+
+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;
+ unsigned long state;
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ transient_data->duration = state;
+ return size;
+}
+
+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;
+ int state;
+
+ state = (transient_data->state == LED_FULL) ? 1 : 0;
+ return sprintf(buf, "%d\n", state);
+}
+
+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;
+ unsigned long state;
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ if (state != 1 && state != 0)
+ return -EINVAL;
+
+ transient_data->state = (state == 1) ? LED_FULL : LED_OFF;
+ return size;
+}
+
+static DEVICE_ATTR(activate, 0644, transient_activate_show,
+ transient_activate_store);
+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)
+{
+ 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);
+}
+
+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);
+ }
+}
+
+static struct led_trigger transient_trigger = {
+ .name = "transient",
+ .activate = transient_trig_activate,
+ .deactivate = transient_trig_deactivate,
+};
+
+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_AUTHOR("Shuah Khan <shuahkhan@gmail.com>");
+MODULE_DESCRIPTION("Transient LED trigger");
+MODULE_LICENSE("GPL");