mirror of
https://github.com/AetherDroid/android_kernel_samsung_on5xelte.git
synced 2025-10-28 23:08:52 +01:00
Fixed MTP to work with TWRP
This commit is contained in:
commit
f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions
572
drivers/leds/Kconfig
Normal file
572
drivers/leds/Kconfig
Normal file
|
|
@ -0,0 +1,572 @@
|
|||
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
|
||||
Say Y to enable Linux LED support. This allows control of supported
|
||||
LEDs from both userspace and optionally, by kernel events (triggers).
|
||||
|
||||
This is not related to standard keyboard LEDs which are controlled
|
||||
via the input system.
|
||||
|
||||
if NEW_LEDS
|
||||
|
||||
config LEDS_CLASS
|
||||
tristate "LED Class Support"
|
||||
help
|
||||
This option enables the led sysfs class in /sys/class/leds. You'll
|
||||
need this to do anything useful with LEDs. If unsure, say N.
|
||||
|
||||
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
|
||||
Semiconductor 88PM8606 PMIC.
|
||||
|
||||
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.
|
||||
Zaurus models SL-5500 and SL-5600.
|
||||
|
||||
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
|
||||
Mikrotik's Routerboard 532.
|
||||
|
||||
config LEDS_S3C24XX
|
||||
tristate "LED Support for Samsung S3C24XX GPIO LEDs"
|
||||
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_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_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_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
|
||||
|
||||
config LEDS_COBALT_RAQ
|
||||
bool "LED Support for the Cobalt Raq series"
|
||||
depends on LEDS_CLASS=y && MIPS_COBALT
|
||||
select LEDS_TRIGGERS
|
||||
help
|
||||
This option enables support for the Cobalt Raq series LEDs.
|
||||
|
||||
config LEDS_SUNFIRE
|
||||
tristate "LED support for SunFire servers."
|
||||
depends on LEDS_CLASS
|
||||
depends on SPARC64
|
||||
select LEDS_TRIGGERS
|
||||
help
|
||||
This option enables support for the Left, Middle, and Right
|
||||
LEDs on the I/O and CPU boards of SunFire UltraSPARC servers.
|
||||
|
||||
config LEDS_IPAQ_MICRO
|
||||
tristate "LED Support for the Compaq iPAQ h3xxx"
|
||||
depends on MFD_IPAQ_MICRO
|
||||
help
|
||||
Choose this option if you want to use the notification LED on
|
||||
Compaq/HP iPAQ h3100 and h3600.
|
||||
|
||||
config LEDS_HP6XX
|
||||
tristate "LED Support for the HP Jornada 6xx"
|
||||
depends on LEDS_CLASS
|
||||
depends on SH_HP6XX
|
||||
help
|
||||
This option enables LED support for the handheld
|
||||
HP Jornada 620/660/680/690.
|
||||
|
||||
config LEDS_PCA9532
|
||||
tristate "LED driver for PCA9532 dimmer"
|
||||
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 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
|
||||
and they must be connected to the GPIO lines. The LEDs must be
|
||||
defined as platform devices and/or OpenFirmware platform devices.
|
||||
The code to use these bindings can be selected below.
|
||||
|
||||
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
|
||||
Semiconductor LP3944 Lighting Management Unit (LMU) also known as
|
||||
Fun Light Chip.
|
||||
|
||||
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.
|
||||
Driver provides direct control via LED class and interface for
|
||||
programming the engines.
|
||||
|
||||
config LEDS_LP5523
|
||||
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 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
|
||||
programs through the leds subsystem. This LED have three
|
||||
known mode: off, blink at 0.5Hz and blink at 1Hz.
|
||||
|
||||
The driver supports two kinds of interface: using ledtrig-timer
|
||||
or through /sys/class/leds/clevo::mail/brightness. As this LED
|
||||
cannot change it's brightness it blinks instead. The brightness
|
||||
value 0 means off, 1..127 means blink at 0.5Hz and 128..255 means
|
||||
blink at 1Hz.
|
||||
|
||||
This module can drive the mail LED for the following notebooks:
|
||||
|
||||
Clevo D400P
|
||||
Clevo D410J
|
||||
Clevo D410V
|
||||
Clevo D400V/D470V (not tested, but might work)
|
||||
Clevo M540N
|
||||
Clevo M5x0N (not tested, but might work)
|
||||
Positivo Mobile (Clevo M5x0V)
|
||||
|
||||
If your model is not listed here you can try the "nodetect"
|
||||
module parameter.
|
||||
|
||||
To compile this driver as a module, choose M here: the
|
||||
module will be called 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
|
||||
series of PMICs.
|
||||
|
||||
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
|
||||
Microelectronics WM8350 AudioPlus PMIC.
|
||||
|
||||
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,
|
||||
which can be used to control up to four LEDs.
|
||||
|
||||
config LEDS_PWM
|
||||
tristate "PWM driven LED Support"
|
||||
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
|
||||
accessed via the I2C bus.
|
||||
|
||||
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
|
||||
Network Attached Storage servers. You may control the hard
|
||||
drive or power LEDs on the front panel. Using this driver
|
||||
can stop the front LED from blinking after startup.
|
||||
|
||||
config LEDS_LT3593
|
||||
tristate "LED driver for LT3593 controllers"
|
||||
depends on LEDS_CLASS
|
||||
depends on GPIOLIB
|
||||
help
|
||||
This option enables support for LEDs driven by a Linear Technology
|
||||
LT3593 controller. This controller uses a special one-wire pulse
|
||||
coding protocol to set the brightness.
|
||||
|
||||
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
|
||||
on Analog Devices ADP5520/ADP5501 PMICs.
|
||||
|
||||
To compile this driver as a module, choose M here: the module will
|
||||
be called leds-adp5520.
|
||||
|
||||
config LEDS_DELL_NETBOOKS
|
||||
tristate "External LED on Dell Business Netbooks"
|
||||
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 MC13XXX PMIC"
|
||||
depends on LEDS_CLASS
|
||||
depends on MFD_MC13XXX
|
||||
help
|
||||
This option enable support for on-chip LED drivers found
|
||||
on Freescale Semiconductor MC13783/MC13892/MC34708 PMIC.
|
||||
|
||||
config LEDS_NS2
|
||||
tristate "LED support for Network Space v2 GPIO LEDs"
|
||||
depends on LEDS_CLASS
|
||||
depends on MACH_KIRKWOOD
|
||||
default y
|
||||
help
|
||||
This option enable support for the dual-GPIO LED found on the
|
||||
Network Space v2 board (and parents). This include Internet Space v2,
|
||||
Network Space (Max) v2 and d2 Network v2 boards.
|
||||
|
||||
config LEDS_NETXBIG
|
||||
tristate "LED support for Big Network series LEDs"
|
||||
depends on LEDS_CLASS
|
||||
depends on 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_ASIC3
|
||||
bool "LED support for the HTC ASIC3"
|
||||
depends on LEDS_CLASS=y
|
||||
depends on MFD_ASIC3
|
||||
default y
|
||||
help
|
||||
This option enables support for the LEDs on the HTC ASIC3. The HTC
|
||||
ASIC3 LED GPIOs are inputs, not outputs, thus the leds-gpio driver
|
||||
cannot be used. This driver supports hardware blinking with an on+off
|
||||
period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700.
|
||||
|
||||
config LEDS_TCA6507
|
||||
tristate "LED Support for TCA6507 I2C chip"
|
||||
depends on LEDS_CLASS && I2C
|
||||
help
|
||||
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.
|
||||
|
||||
config LEDS_MAX8997
|
||||
tristate "LED support for MAX8997 PMIC"
|
||||
depends on LEDS_CLASS && MFD_MAX8997
|
||||
help
|
||||
This option enables support for on-chip LED drivers on
|
||||
MAXIM MAX8997 PMIC.
|
||||
|
||||
config LEDS_LM355x
|
||||
tristate "LED support for LM355x Chips, LM3554 and LM3556"
|
||||
depends on LEDS_CLASS && I2C
|
||||
select REGMAP_I2C
|
||||
help
|
||||
This option enables support for LEDs connected to LM355x.
|
||||
LM355x includes Torch, Flash and Indicator functions.
|
||||
|
||||
config LEDS_OT200
|
||||
tristate "LED support for the Bachmann OT200"
|
||||
depends on LEDS_CLASS && HAS_IOMEM && (X86_32 || COMPILE_TEST)
|
||||
help
|
||||
This option enables support for the LEDs on the Bachmann OT200.
|
||||
Say Y to enable LEDs on the Bachmann OT200.
|
||||
|
||||
config LEDS_MENF21BMC
|
||||
tristate "LED support for the MEN 14F021P00 BMC"
|
||||
depends on LEDS_CLASS && MFD_MENF21BMC
|
||||
help
|
||||
Say Y here to include support for the MEN 14F021P00 BMC LEDs.
|
||||
|
||||
This driver can also be built as a module. If so the module
|
||||
will be called leds-menf21bmc.
|
||||
|
||||
comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
|
||||
|
||||
config LEDS_BLINKM
|
||||
tristate "LED support for the BlinkM I2C RGB LED"
|
||||
depends on LEDS_CLASS
|
||||
depends on I2C
|
||||
help
|
||||
This option enables support for the BlinkM RGB LED connected
|
||||
through I2C. Say Y to enable support for the BlinkM LED.
|
||||
|
||||
config LEDS_SYSCON
|
||||
bool "LED support for LEDs on system controllers"
|
||||
depends on LEDS_CLASS=y
|
||||
depends on MFD_SYSCON
|
||||
depends on OF
|
||||
help
|
||||
This option enabled support for the LEDs on syscon type
|
||||
devices. This will only work with device tree enabled
|
||||
devices.
|
||||
|
||||
config LEDS_VERSATILE
|
||||
tristate "LED support for the ARM Versatile and RealView"
|
||||
depends on ARCH_REALVIEW || ARCH_VERSATILE
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enabled support for the LEDs on the ARM Versatile
|
||||
and RealView boards. Say Y to enabled these.
|
||||
|
||||
config LEDS_S2MPB02
|
||||
bool "LED support for the S2MPB02"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enables support for the LEDs on the S2MPB02.
|
||||
|
||||
config LEDS_S2MU003
|
||||
bool "LED support for the S2MU003"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enables support for the LEDs on the S2MU003.
|
||||
|
||||
config S2MU003_LEDS_I2C
|
||||
bool "LED support for the S2MU003"
|
||||
depends on LEDS_S2MU003
|
||||
help
|
||||
This option enables support for the LEDs on the S2MU003.
|
||||
|
||||
config LEDS_S2MU005_FLASH
|
||||
bool "LED support for the S2MU005"
|
||||
depends on LEDS_CLASS
|
||||
help
|
||||
This option enables support for the LEDs on the S2MU005.
|
||||
|
||||
config S2MU005_LEDS_I2C
|
||||
bool "LED support for the S2MU005"
|
||||
help
|
||||
This option enables support for the LEDs on the S2MU003.
|
||||
|
||||
comment "LED Triggers"
|
||||
source "drivers/leds/trigger/Kconfig"
|
||||
|
||||
config LEDS_KTD2026
|
||||
tristate "LED driver for KTD2026 LED"
|
||||
depends on I2C
|
||||
help
|
||||
This option enables support for RGB LED driver
|
||||
accessed via the I2C bus.
|
||||
KTD2026 is a fully programmable, constant current
|
||||
three channel LED driver with flexible control interface.
|
||||
|
||||
config LEDS_KTD2026_PANEL
|
||||
tristate "LED driver for KTD2026 LED to make a different brightness"
|
||||
depends on LEDS_KTD2026
|
||||
help
|
||||
If you say yes here you will get support for
|
||||
different brightness in panels
|
||||
|
||||
config LEDS_KTD2692
|
||||
bool "LED support for the KTD2692"
|
||||
help
|
||||
If you say yes here you will get support for
|
||||
for the KTD2692 FLASH led chip.
|
||||
|
||||
config USE_LIGHT_SENSOR
|
||||
bool "Light sensor use or not in LED driver for KTD2026 LED"
|
||||
depends on LEDS_KTD2026
|
||||
default n
|
||||
|
||||
config LEDS_SUPPORT_FRONT_FLASH
|
||||
bool "LED support for Front Flash"
|
||||
help
|
||||
This option enables support for the LEDs.
|
||||
|
||||
endif # NEW_LEDS
|
||||
69
drivers/leds/Makefile
Normal file
69
drivers/leds/Makefile
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
|
||||
# LED Core
|
||||
obj-$(CONFIG_NEW_LEDS) += led-core.o
|
||||
obj-$(CONFIG_LEDS_CLASS) += led-class.o
|
||||
obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o
|
||||
|
||||
# LED Platform Drivers
|
||||
obj-$(CONFIG_LEDS_88PM860X) += leds-88pm860x.o
|
||||
obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o
|
||||
obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o
|
||||
obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o
|
||||
obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o
|
||||
obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o
|
||||
obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o
|
||||
obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o
|
||||
obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o
|
||||
obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o
|
||||
obj-$(CONFIG_LEDS_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_IPAQ_MICRO) += leds-ipaq-micro.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
|
||||
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
|
||||
obj-$(CONFIG_LEDS_INTEL_SS4200) += leds-ss4200.o
|
||||
obj-$(CONFIG_LEDS_LT3593) += leds-lt3593.o
|
||||
obj-$(CONFIG_LEDS_ADP5520) += leds-adp5520.o
|
||||
obj-$(CONFIG_LEDS_DELL_NETBOOKS) += dell-led.o
|
||||
obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o
|
||||
obj-$(CONFIG_LEDS_NS2) += leds-ns2.o
|
||||
obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o
|
||||
obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o
|
||||
obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
|
||||
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
|
||||
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
|
||||
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
|
||||
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
|
||||
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
|
||||
obj-$(CONFIG_LEDS_S2MU003) += leds-s2mu003.o
|
||||
obj-$(CONFIG_LEDS_S2MPB02) += leds-s2mpb02.o
|
||||
obj-$(CONFIG_LEDS_S2MU005_FLASH) += leds-s2mu005.o
|
||||
obj-$(CONFIG_LEDS_KTD2026) += leds-ktd2026.o
|
||||
obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
|
||||
|
||||
# LED SPI Drivers
|
||||
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
|
||||
|
||||
# LED Triggers
|
||||
obj-$(CONFIG_LEDS_TRIGGERS) += trigger/
|
||||
359
drivers/leds/dell-led.c
Normal file
359
drivers/leds/dell-led.c
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
/*
|
||||
* dell_led.c - Dell LED Driver
|
||||
*
|
||||
* Copyright (C) 2010 Dell Inc.
|
||||
* Louis Davis <louis_davis@dell.com>
|
||||
* Jim Dailey <jim_dailey@dell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#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: */
|
||||
#define INVALID_DEVICE_ID 250
|
||||
#define INVALID_PARAMETER 251
|
||||
#define INVALID_BUFFER 252
|
||||
#define INTERFACE_ERROR 253
|
||||
#define UNSUPPORTED_COMMAND 254
|
||||
#define UNSPECIFIED_ERROR 255
|
||||
|
||||
/* Device ID */
|
||||
#define DEVICE_ID_PANEL_BACK 1
|
||||
|
||||
/* LED Commands */
|
||||
#define CMD_LED_ON 16
|
||||
#define CMD_LED_OFF 17
|
||||
#define CMD_LED_BLINK 18
|
||||
|
||||
struct 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;
|
||||
unsigned char device_id;
|
||||
unsigned char command;
|
||||
unsigned char on_time;
|
||||
unsigned char off_time;
|
||||
};
|
||||
|
||||
static int dell_led_perform_fn(u8 length,
|
||||
u8 result_code,
|
||||
u8 device_id,
|
||||
u8 command,
|
||||
u8 on_time,
|
||||
u8 off_time)
|
||||
{
|
||||
struct bios_args *bios_return;
|
||||
u8 return_code;
|
||||
union acpi_object *obj;
|
||||
struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL };
|
||||
struct acpi_buffer input;
|
||||
acpi_status status;
|
||||
|
||||
struct bios_args args;
|
||||
args.length = length;
|
||||
args.result_code = result_code;
|
||||
args.device_id = device_id;
|
||||
args.command = command;
|
||||
args.on_time = on_time;
|
||||
args.off_time = off_time;
|
||||
|
||||
input.length = sizeof(struct bios_args);
|
||||
input.pointer = &args;
|
||||
|
||||
status = wmi_evaluate_method(DELL_LED_BIOS_GUID,
|
||||
1,
|
||||
1,
|
||||
&input,
|
||||
&output);
|
||||
|
||||
if (ACPI_FAILURE(status))
|
||||
return status;
|
||||
|
||||
obj = output.pointer;
|
||||
|
||||
if (!obj)
|
||||
return -EINVAL;
|
||||
else if (obj->type != ACPI_TYPE_BUFFER) {
|
||||
kfree(obj);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
bios_return = ((struct bios_args *)obj->buffer.pointer);
|
||||
return_code = bios_return->result_code;
|
||||
|
||||
kfree(obj);
|
||||
|
||||
return return_code;
|
||||
}
|
||||
|
||||
static int led_on(void)
|
||||
{
|
||||
return dell_led_perform_fn(3, /* Length of command */
|
||||
INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
|
||||
DEVICE_ID_PANEL_BACK, /* Device ID */
|
||||
CMD_LED_ON, /* Command */
|
||||
0, /* not used */
|
||||
0); /* not used */
|
||||
}
|
||||
|
||||
static int led_off(void)
|
||||
{
|
||||
return dell_led_perform_fn(3, /* Length of command */
|
||||
INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
|
||||
DEVICE_ID_PANEL_BACK, /* Device ID */
|
||||
CMD_LED_OFF, /* Command */
|
||||
0, /* not used */
|
||||
0); /* not used */
|
||||
}
|
||||
|
||||
static int led_blink(unsigned char on_eighths,
|
||||
unsigned char off_eighths)
|
||||
{
|
||||
return dell_led_perform_fn(5, /* Length of command */
|
||||
INTERFACE_ERROR, /* Init to INTERFACE_ERROR */
|
||||
DEVICE_ID_PANEL_BACK, /* Device ID */
|
||||
CMD_LED_BLINK, /* Command */
|
||||
on_eighths, /* blink on in eigths of a second */
|
||||
off_eighths); /* blink off in eights of a second */
|
||||
}
|
||||
|
||||
static void dell_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value == LED_OFF)
|
||||
led_off();
|
||||
else
|
||||
led_on();
|
||||
}
|
||||
|
||||
static int dell_led_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
unsigned long on_eighths;
|
||||
unsigned long off_eighths;
|
||||
|
||||
/* The Dell LED delay is based on 125ms intervals.
|
||||
Need to round up to next interval. */
|
||||
|
||||
on_eighths = (*delay_on + 124) / 125;
|
||||
if (0 == on_eighths)
|
||||
on_eighths = 1;
|
||||
if (on_eighths > 255)
|
||||
on_eighths = 255;
|
||||
*delay_on = on_eighths * 125;
|
||||
|
||||
off_eighths = (*delay_off + 124) / 125;
|
||||
if (0 == off_eighths)
|
||||
off_eighths = 1;
|
||||
if (off_eighths > 255)
|
||||
off_eighths = 255;
|
||||
*delay_off = off_eighths * 125;
|
||||
|
||||
led_blink(on_eighths, off_eighths);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct led_classdev dell_led = {
|
||||
.name = "dell::lid",
|
||||
.brightness = LED_OFF,
|
||||
.max_brightness = 1,
|
||||
.brightness_set = dell_led_set,
|
||||
.blink_set = dell_led_blink,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int __init dell_led_init(void)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID))
|
||||
return -ENODEV;
|
||||
|
||||
if (wmi_has_guid(DELL_APP_GUID))
|
||||
error = dell_micmute_led_init();
|
||||
|
||||
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)
|
||||
{
|
||||
int error = 0;
|
||||
|
||||
if (wmi_has_guid(DELL_LED_BIOS_GUID)) {
|
||||
error = led_off();
|
||||
if (error == 0)
|
||||
led_classdev_unregister(&dell_led);
|
||||
}
|
||||
}
|
||||
|
||||
module_init(dell_led_init);
|
||||
module_exit(dell_led_exit);
|
||||
293
drivers/leds/led-class.c
Normal file
293
drivers/leds/led-class.c
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
/*
|
||||
* LED Class Core
|
||||
*
|
||||
* Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
|
||||
* Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/timer.h>
|
||||
#include "leds.h"
|
||||
|
||||
static struct class *leds_class;
|
||||
|
||||
static ssize_t brightness_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
/* no lock needed for this */
|
||||
led_update_brightness(led_cdev);
|
||||
|
||||
return sprintf(buf, "%u\n", led_cdev->brightness);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
ret = kstrtoul(buf, 10, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (state == LED_OFF)
|
||||
led_trigger_remove(led_cdev);
|
||||
__led_set_brightness(led_cdev, state);
|
||||
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_RW(brightness);
|
||||
|
||||
static ssize_t max_brightness_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%u\n", led_cdev->max_brightness);
|
||||
}
|
||||
static DEVICE_ATTR_RO(max_brightness);
|
||||
|
||||
#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 const struct attribute_group *led_groups[] = {
|
||||
&led_group,
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
&led_trigger_group,
|
||||
#endif
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void led_timer_function(unsigned long data)
|
||||
{
|
||||
struct led_classdev *led_cdev = (void *)data;
|
||||
unsigned long brightness;
|
||||
unsigned long delay;
|
||||
|
||||
if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_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;
|
||||
}
|
||||
|
||||
brightness = led_get_brightness(led_cdev);
|
||||
if (!brightness) {
|
||||
/* Time to switch the LED on. */
|
||||
brightness = led_cdev->blink_brightness;
|
||||
delay = led_cdev->blink_delay_on;
|
||||
} else {
|
||||
/* Store the current brightness value to be able
|
||||
* to restore it when the delay_off period is over.
|
||||
*/
|
||||
led_cdev->blink_brightness = brightness;
|
||||
brightness = LED_OFF;
|
||||
delay = led_cdev->blink_delay_off;
|
||||
}
|
||||
|
||||
__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 set_brightness_delayed(struct work_struct *ws)
|
||||
{
|
||||
struct led_classdev *led_cdev =
|
||||
container_of(ws, struct led_classdev, set_brightness_work);
|
||||
|
||||
led_stop_software_blink(led_cdev);
|
||||
|
||||
__led_set_brightness(led_cdev, led_cdev->delayed_set_value);
|
||||
}
|
||||
|
||||
/**
|
||||
* led_classdev_suspend - suspend an led_classdev.
|
||||
* @led_cdev: the led_classdev to suspend.
|
||||
*/
|
||||
void led_classdev_suspend(struct led_classdev *led_cdev)
|
||||
{
|
||||
led_cdev->flags |= LED_SUSPENDED;
|
||||
led_cdev->brightness_set(led_cdev, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_classdev_suspend);
|
||||
|
||||
/**
|
||||
* led_classdev_resume - resume an led_classdev.
|
||||
* @led_cdev: the led_classdev to resume.
|
||||
*/
|
||||
void led_classdev_resume(struct led_classdev *led_cdev)
|
||||
{
|
||||
led_cdev->brightness_set(led_cdev, led_cdev->brightness);
|
||||
led_cdev->flags &= ~LED_SUSPENDED;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_classdev_resume);
|
||||
|
||||
static int led_suspend(struct device *dev)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
|
||||
led_classdev_suspend(led_cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int led_resume(struct device *dev)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
|
||||
led_classdev_resume(led_cdev);
|
||||
|
||||
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.
|
||||
* @led_cdev: the led_classdev structure for this device.
|
||||
*/
|
||||
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
|
||||
{
|
||||
led_cdev->dev = device_create_with_groups(leds_class, parent, 0,
|
||||
led_cdev, led_cdev->groups,
|
||||
"%s", led_cdev->name);
|
||||
if (IS_ERR(led_cdev->dev))
|
||||
return PTR_ERR(led_cdev->dev);
|
||||
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
init_rwsem(&led_cdev->trigger_lock);
|
||||
#endif
|
||||
/* add to the list of leds */
|
||||
down_write(&leds_list_lock);
|
||||
list_add_tail(&led_cdev->node, &leds_list);
|
||||
up_write(&leds_list_lock);
|
||||
|
||||
if (!led_cdev->max_brightness)
|
||||
led_cdev->max_brightness = LED_FULL;
|
||||
|
||||
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;
|
||||
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
led_trigger_set_default(led_cdev);
|
||||
#endif
|
||||
|
||||
dev_dbg(parent, "Registered led device: %s\n",
|
||||
led_cdev->name);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_classdev_register);
|
||||
|
||||
/**
|
||||
* led_classdev_unregister - unregisters a object of led_properties class.
|
||||
* @led_cdev: the led device to unregister
|
||||
*
|
||||
* Unregisters a previously registered via led_classdev_register object.
|
||||
*/
|
||||
void led_classdev_unregister(struct led_classdev *led_cdev)
|
||||
{
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
down_write(&led_cdev->trigger_lock);
|
||||
if (led_cdev->trigger)
|
||||
led_trigger_set(led_cdev, NULL);
|
||||
up_write(&led_cdev->trigger_lock);
|
||||
#endif
|
||||
|
||||
cancel_work_sync(&led_cdev->set_brightness_work);
|
||||
|
||||
/* Stop blinking */
|
||||
led_stop_software_blink(led_cdev);
|
||||
led_set_brightness(led_cdev, LED_OFF);
|
||||
|
||||
device_unregister(led_cdev->dev);
|
||||
|
||||
down_write(&leds_list_lock);
|
||||
list_del(&led_cdev->node);
|
||||
up_write(&leds_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_classdev_unregister);
|
||||
|
||||
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->pm = &leds_class_dev_pm_ops;
|
||||
leds_class->dev_groups = led_groups;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit leds_exit(void)
|
||||
{
|
||||
class_destroy(leds_class);
|
||||
}
|
||||
|
||||
subsys_initcall(leds_init);
|
||||
module_exit(leds_exit);
|
||||
|
||||
MODULE_AUTHOR("John Lenz, Richard Purdie");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("LED Class Interface");
|
||||
145
drivers/leds/led-core.c
Normal file
145
drivers/leds/led-core.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* LED Class Core
|
||||
*
|
||||
* Copyright 2005-2006 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include "leds.h"
|
||||
|
||||
DECLARE_RWSEM(leds_list_lock);
|
||||
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);
|
||||
|
||||
int led_update_brightness(struct led_classdev *led_cdev)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (led_cdev->brightness_get) {
|
||||
ret = led_cdev->brightness_get(led_cdev);
|
||||
if (ret >= 0) {
|
||||
led_cdev->brightness = ret;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(led_update_brightness);
|
||||
332
drivers/leds/led-triggers.c
Normal file
332
drivers/leds/led-triggers.c
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
/*
|
||||
* LED Triggers Core
|
||||
*
|
||||
* Copyright 2005-2007 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/slab.h>
|
||||
#include "leds.h"
|
||||
|
||||
/*
|
||||
* Nests outside led_cdev->trigger_lock
|
||||
*/
|
||||
static DECLARE_RWSEM(triggers_list_lock);
|
||||
static LIST_HEAD(trigger_list);
|
||||
|
||||
/* Used by LED Class */
|
||||
|
||||
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
char trigger_name[TRIG_NAME_MAX];
|
||||
struct led_trigger *trig;
|
||||
size_t len;
|
||||
|
||||
trigger_name[sizeof(trigger_name) - 1] = '\0';
|
||||
strncpy(trigger_name, buf, sizeof(trigger_name) - 1);
|
||||
len = strlen(trigger_name);
|
||||
|
||||
if (len && trigger_name[len - 1] == '\n')
|
||||
trigger_name[len - 1] = '\0';
|
||||
|
||||
if (!strcmp(trigger_name, "none")) {
|
||||
led_trigger_remove(led_cdev);
|
||||
return count;
|
||||
}
|
||||
|
||||
down_read(&triggers_list_lock);
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
if (!strcmp(trigger_name, trig->name)) {
|
||||
down_write(&led_cdev->trigger_lock);
|
||||
led_trigger_set(led_cdev, trig);
|
||||
up_write(&led_cdev->trigger_lock);
|
||||
|
||||
up_read(&triggers_list_lock);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
up_read(&triggers_list_lock);
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_store);
|
||||
|
||||
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct led_trigger *trig;
|
||||
int len = 0;
|
||||
|
||||
down_read(&triggers_list_lock);
|
||||
down_read(&led_cdev->trigger_lock);
|
||||
|
||||
if (!led_cdev->trigger)
|
||||
len += sprintf(buf+len, "[none] ");
|
||||
else
|
||||
len += sprintf(buf+len, "none ");
|
||||
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
if (led_cdev->trigger && !strcmp(led_cdev->trigger->name,
|
||||
trig->name))
|
||||
len += sprintf(buf+len, "[%s] ", trig->name);
|
||||
else
|
||||
len += sprintf(buf+len, "%s ", trig->name);
|
||||
}
|
||||
up_read(&led_cdev->trigger_lock);
|
||||
up_read(&triggers_list_lock);
|
||||
|
||||
len += sprintf(len+buf, "\n");
|
||||
return len;
|
||||
}
|
||||
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 *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) {
|
||||
write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags);
|
||||
list_del(&led_cdev->trig_list);
|
||||
write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock,
|
||||
flags);
|
||||
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_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 (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);
|
||||
|
||||
void led_trigger_remove(struct led_classdev *led_cdev)
|
||||
{
|
||||
down_write(&led_cdev->trigger_lock);
|
||||
led_trigger_set(led_cdev, NULL);
|
||||
up_write(&led_cdev->trigger_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_remove);
|
||||
|
||||
void led_trigger_set_default(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct led_trigger *trig;
|
||||
|
||||
if (!led_cdev->default_trigger)
|
||||
return;
|
||||
|
||||
down_read(&triggers_list_lock);
|
||||
down_write(&led_cdev->trigger_lock);
|
||||
list_for_each_entry(trig, &trigger_list, next_trig) {
|
||||
if (!strcmp(led_cdev->default_trigger, trig->name))
|
||||
led_trigger_set(led_cdev, trig);
|
||||
}
|
||||
up_write(&led_cdev->trigger_lock);
|
||||
up_read(&triggers_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_set_default);
|
||||
|
||||
void led_trigger_rename_static(const char *name, struct led_trigger *trig)
|
||||
{
|
||||
/* new name must be on a temporary string to prevent races */
|
||||
BUG_ON(name == trig->name);
|
||||
|
||||
down_write(&triggers_list_lock);
|
||||
/* this assumes that trig->name was originaly allocated to
|
||||
* non constant storage */
|
||||
strcpy((char *)trig->name, name);
|
||||
up_write(&triggers_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_rename_static);
|
||||
|
||||
/* LED Trigger Interface */
|
||||
|
||||
int led_trigger_register(struct led_trigger *trig)
|
||||
{
|
||||
struct led_classdev *led_cdev;
|
||||
struct led_trigger *_trig;
|
||||
|
||||
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, trig->name)) {
|
||||
up_write(&triggers_list_lock);
|
||||
return -EEXIST;
|
||||
}
|
||||
}
|
||||
/* Add to the list of led triggers */
|
||||
list_add_tail(&trig->next_trig, &trigger_list);
|
||||
up_write(&triggers_list_lock);
|
||||
|
||||
/* Register with any LEDs that have this as a default 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 && led_cdev->default_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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_register);
|
||||
|
||||
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_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 == trig)
|
||||
led_trigger_set(led_cdev, NULL);
|
||||
up_write(&led_cdev->trigger_lock);
|
||||
}
|
||||
up_read(&leds_list_lock);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_unregister);
|
||||
|
||||
/* Simple LED Tigger Interface */
|
||||
|
||||
void led_trigger_event(struct led_trigger *trig,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
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)
|
||||
led_set_brightness(led_cdev, brightness);
|
||||
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 *trig;
|
||||
int err;
|
||||
|
||||
trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL);
|
||||
|
||||
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 *trig)
|
||||
{
|
||||
if (trig)
|
||||
led_trigger_unregister(trig);
|
||||
kfree(trig);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(led_trigger_unregister_simple);
|
||||
|
||||
MODULE_AUTHOR("Richard Purdie");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("LED Triggers Core");
|
||||
251
drivers/leds/leds-88pm860x.c
Normal file
251
drivers/leds/leds-88pm860x.c
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/*
|
||||
* LED driver for Marvell 88PM860x
|
||||
*
|
||||
* Copyright (C) 2009 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/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_MASK (0x1F)
|
||||
#define LED_CURRENT_MASK (0x07 << 5)
|
||||
|
||||
#define LED_BLINK_MASK (0x7F)
|
||||
|
||||
#define LED_ON_CONTINUOUS (0x0F << 3)
|
||||
|
||||
#define LED1_BLINK_EN (1 << 1)
|
||||
#define LED2_BLINK_EN (1 << 2)
|
||||
|
||||
struct pm860x_led {
|
||||
struct led_classdev cdev;
|
||||
struct i2c_client *i2c;
|
||||
struct work_struct work;
|
||||
struct pm860x_chip *chip;
|
||||
struct mutex lock;
|
||||
char name[MFD_NAME_SIZE];
|
||||
|
||||
int port;
|
||||
int iset;
|
||||
unsigned char brightness;
|
||||
unsigned char current_brightness;
|
||||
|
||||
int reg_control;
|
||||
int reg_blink;
|
||||
int blink_mask;
|
||||
};
|
||||
|
||||
static int led_power_set(struct pm860x_chip *chip, int port, int on)
|
||||
{
|
||||
int ret = -EINVAL;
|
||||
|
||||
switch (port) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) :
|
||||
pm8606_osc_disable(chip, RGB1_ENABLE);
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) :
|
||||
pm8606_osc_disable(chip, RGB2_ENABLE);
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void pm860x_led_work(struct work_struct *work)
|
||||
{
|
||||
|
||||
struct pm860x_led *led;
|
||||
struct pm860x_chip *chip;
|
||||
unsigned char buf[3];
|
||||
int ret;
|
||||
|
||||
led = container_of(work, struct pm860x_led, work);
|
||||
chip = led->chip;
|
||||
mutex_lock(&led->lock);
|
||||
if ((led->current_brightness == 0) && led->brightness) {
|
||||
led_power_set(chip, led->port, 1);
|
||||
if (led->iset) {
|
||||
pm860x_set_bits(led->i2c, led->reg_control,
|
||||
LED_CURRENT_MASK, led->iset);
|
||||
}
|
||||
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->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->reg_control, led->brightness);
|
||||
mutex_unlock(&led->lock);
|
||||
}
|
||||
|
||||
static void pm860x_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct pm860x_led *data = container_of(cdev, struct pm860x_led, cdev);
|
||||
|
||||
data->brightness = value >> 3;
|
||||
schedule_work(&data->work);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static int pm860x_led_dt_init(struct platform_device *pdev,
|
||||
struct pm860x_led *data)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
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_led_pdata *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct pm860x_led *data;
|
||||
struct resource *res;
|
||||
int ret = 0;
|
||||
|
||||
data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL);
|
||||
if (data == NULL)
|
||||
return -ENOMEM;
|
||||
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->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;
|
||||
data->cdev.brightness_set = pm860x_led_set;
|
||||
mutex_init(&data->lock);
|
||||
INIT_WORK(&data->work, pm860x_led_work);
|
||||
|
||||
ret = led_classdev_register(chip->dev, &data->cdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
pm860x_led_set(&data->cdev, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pm860x_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct pm860x_led *data = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&data->cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver pm860x_led_driver = {
|
||||
.driver = {
|
||||
.name = "88pm860x-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pm860x_led_probe,
|
||||
.remove = pm860x_led_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(pm860x_led_driver);
|
||||
|
||||
MODULE_DESCRIPTION("LED driver for Marvell PM860x");
|
||||
MODULE_AUTHOR("Haojian Zhuang <haojian.zhuang@marvell.com>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:88pm860x-led");
|
||||
215
drivers/leds/leds-adp5520.c
Normal file
215
drivers/leds/leds-adp5520.c
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
/*
|
||||
* LEDs driver for Analog Devices ADP5520/ADP5501 MFD PMICs
|
||||
*
|
||||
* Copyright 2009 Analog Devices Inc.
|
||||
*
|
||||
* Loosely derived from leds-da903x:
|
||||
* Copyright (C) 2008 Compulab, Ltd.
|
||||
* Mike Rapoport <mike@compulab.co.il>
|
||||
*
|
||||
* Copyright (C) 2006-2008 Marvell International Ltd.
|
||||
* Eric Miao <eric.miao@marvell.com>
|
||||
*
|
||||
* Licensed under the GPL-2 or later.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mfd/adp5520.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
struct adp5520_led {
|
||||
struct led_classdev cdev;
|
||||
struct work_struct work;
|
||||
struct device *master;
|
||||
enum led_brightness new_brightness;
|
||||
int id;
|
||||
int flags;
|
||||
};
|
||||
|
||||
static void adp5520_led_work(struct work_struct *work)
|
||||
{
|
||||
struct adp5520_led *led = container_of(work, struct adp5520_led, work);
|
||||
adp5520_write(led->master, ADP5520_LED1_CURRENT + led->id - 1,
|
||||
led->new_brightness >> 2);
|
||||
}
|
||||
|
||||
static void adp5520_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct adp5520_led *led;
|
||||
|
||||
led = container_of(led_cdev, struct adp5520_led, cdev);
|
||||
led->new_brightness = value;
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
static int adp5520_led_setup(struct adp5520_led *led)
|
||||
{
|
||||
struct device *dev = led->master;
|
||||
int flags = led->flags;
|
||||
int ret = 0;
|
||||
|
||||
switch (led->id) {
|
||||
case FLAG_ID_ADP5520_LED1_ADP5501_LED0:
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_TIME,
|
||||
(flags >> ADP5520_FLAG_OFFT_SHIFT) &
|
||||
ADP5520_FLAG_OFFT_MASK);
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
|
||||
ADP5520_LED1_EN);
|
||||
break;
|
||||
case FLAG_ID_ADP5520_LED2_ADP5501_LED1:
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_TIME,
|
||||
((flags >> ADP5520_FLAG_OFFT_SHIFT) &
|
||||
ADP5520_FLAG_OFFT_MASK) << 2);
|
||||
ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL,
|
||||
ADP5520_R3_MODE);
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
|
||||
ADP5520_LED2_EN);
|
||||
break;
|
||||
case FLAG_ID_ADP5520_LED3_ADP5501_LED2:
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_TIME,
|
||||
((flags >> ADP5520_FLAG_OFFT_SHIFT) &
|
||||
ADP5520_FLAG_OFFT_MASK) << 4);
|
||||
ret |= adp5520_clr_bits(dev, ADP5520_LED_CONTROL,
|
||||
ADP5520_C3_MODE);
|
||||
ret |= adp5520_set_bits(dev, ADP5520_LED_CONTROL,
|
||||
ADP5520_LED3_EN);
|
||||
break;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adp5520_led_prepare(struct platform_device *pdev)
|
||||
{
|
||||
struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct device *dev = pdev->dev.parent;
|
||||
int ret = 0;
|
||||
|
||||
ret |= adp5520_write(dev, ADP5520_LED1_CURRENT, 0);
|
||||
ret |= adp5520_write(dev, ADP5520_LED2_CURRENT, 0);
|
||||
ret |= adp5520_write(dev, ADP5520_LED3_CURRENT, 0);
|
||||
ret |= adp5520_write(dev, ADP5520_LED_TIME, pdata->led_on_time << 6);
|
||||
ret |= adp5520_write(dev, ADP5520_LED_FADE, FADE_VAL(pdata->fade_in,
|
||||
pdata->fade_out));
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adp5520_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
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;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "missing platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (pdata->num_leds > ADP5520_01_MAXLEDS) {
|
||||
dev_err(&pdev->dev, "can't handle more than %d LEDS\n",
|
||||
ADP5520_01_MAXLEDS);
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
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");
|
||||
return ret;
|
||||
}
|
||||
|
||||
for (i = 0; i < pdata->num_leds; ++i) {
|
||||
cur_led = &pdata->leds[i];
|
||||
led_dat = &led[i];
|
||||
|
||||
led_dat->cdev.name = cur_led->name;
|
||||
led_dat->cdev.default_trigger = cur_led->default_trigger;
|
||||
led_dat->cdev.brightness_set = adp5520_led_set;
|
||||
led_dat->cdev.brightness = LED_OFF;
|
||||
|
||||
if (cur_led->flags & ADP5520_FLAG_LED_MASK)
|
||||
led_dat->flags = cur_led->flags;
|
||||
else
|
||||
led_dat->flags = i + 1;
|
||||
|
||||
led_dat->id = led_dat->flags & ADP5520_FLAG_LED_MASK;
|
||||
|
||||
led_dat->master = pdev->dev.parent;
|
||||
led_dat->new_brightness = LED_OFF;
|
||||
|
||||
INIT_WORK(&led_dat->work, adp5520_led_work);
|
||||
|
||||
ret = led_classdev_register(led_dat->master, &led_dat->cdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register LED %d\n",
|
||||
led_dat->id);
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = adp5520_led_setup(led_dat);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to write\n");
|
||||
i++;
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led);
|
||||
return 0;
|
||||
|
||||
err:
|
||||
if (i > 0) {
|
||||
for (i = i - 1; i >= 0; i--) {
|
||||
led_classdev_unregister(&led[i].cdev);
|
||||
cancel_work_sync(&led[i].work);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int adp5520_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct adp5520_led *led;
|
||||
int i;
|
||||
|
||||
led = platform_get_drvdata(pdev);
|
||||
|
||||
adp5520_clr_bits(led->master, ADP5520_LED_CONTROL,
|
||||
ADP5520_LED1_EN | ADP5520_LED2_EN | ADP5520_LED3_EN);
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
led_classdev_unregister(&led[i].cdev);
|
||||
cancel_work_sync(&led[i].work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver adp5520_led_driver = {
|
||||
.driver = {
|
||||
.name = "adp5520-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = adp5520_led_probe,
|
||||
.remove = adp5520_led_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(adp5520_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>");
|
||||
MODULE_DESCRIPTION("LEDS ADP5520(01) Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:adp5520-led");
|
||||
181
drivers/leds/leds-asic3.c
Normal file
181
drivers/leds/leds-asic3.c
Normal file
|
|
@ -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");
|
||||
809
drivers/leds/leds-bd2802.c
Normal file
809
drivers/leds/leds-bd2802.c
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
/*
|
||||
* leds-bd2802.c - RGB LED Driver
|
||||
*
|
||||
* Copyright (C) 2009 Samsung Electronics
|
||||
* Kim Kyuwon <q1.kim@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Datasheet: http://www.rohm.com/products/databook/driver/pdf/bd2802gu-e.pdf
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/delay.h>
|
||||
#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_HOURSETUP 0x02
|
||||
#define BD2802_REG_CURRENT1SETUP 0x03
|
||||
#define BD2802_REG_CURRENT2SETUP 0x04
|
||||
#define BD2802_REG_WAVEPATTERN 0x05
|
||||
|
||||
#define BD2802_CURRENT_032 0x10 /* 3.2mA */
|
||||
#define BD2802_CURRENT_000 0x00 /* 0.0mA */
|
||||
|
||||
#define BD2802_PATTERN_FULL 0x07
|
||||
#define BD2802_PATTERN_HALF 0x03
|
||||
|
||||
enum led_ids {
|
||||
LED1,
|
||||
LED2,
|
||||
LED_NUM,
|
||||
};
|
||||
|
||||
enum led_colors {
|
||||
RED,
|
||||
GREEN,
|
||||
BLUE,
|
||||
};
|
||||
|
||||
enum led_bits {
|
||||
BD2802_OFF,
|
||||
BD2802_BLINK,
|
||||
BD2802_ON,
|
||||
};
|
||||
|
||||
/*
|
||||
* State '0' : 'off'
|
||||
* State '1' : 'blink'
|
||||
* State '2' : 'on'.
|
||||
*/
|
||||
struct led_state {
|
||||
unsigned r:2;
|
||||
unsigned g:2;
|
||||
unsigned b:2;
|
||||
};
|
||||
|
||||
struct bd2802_led {
|
||||
struct bd2802_led_platform_data *pdata;
|
||||
struct i2c_client *client;
|
||||
struct rw_semaphore rwsem;
|
||||
struct work_struct work;
|
||||
|
||||
struct led_state led[2];
|
||||
|
||||
/*
|
||||
* Making led_classdev as array is not recommended, because array
|
||||
* members prevent using 'container_of' macro. So repetitive works
|
||||
* are needed.
|
||||
*/
|
||||
struct led_classdev cdev_led1r;
|
||||
struct led_classdev cdev_led1g;
|
||||
struct led_classdev cdev_led1b;
|
||||
struct led_classdev cdev_led2r;
|
||||
struct led_classdev cdev_led2g;
|
||||
struct led_classdev cdev_led2b;
|
||||
|
||||
/*
|
||||
* Advanced Configuration Function(ADF) mode:
|
||||
* In ADF mode, user can set registers of BD2802GU directly,
|
||||
* therefore BD2802GU doesn't enter reset state.
|
||||
*/
|
||||
int adf_on;
|
||||
|
||||
enum led_ids led_id;
|
||||
enum led_colors color;
|
||||
enum led_bits state;
|
||||
|
||||
/* General attributes of RGB LEDs */
|
||||
int wave_pattern;
|
||||
int rgb_current;
|
||||
};
|
||||
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* BD2802GU helper functions */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
static inline int bd2802_is_rgb_off(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color)
|
||||
{
|
||||
switch (color) {
|
||||
case RED:
|
||||
return !led->led[id].r;
|
||||
case GREEN:
|
||||
return !led->led[id].g;
|
||||
case BLUE:
|
||||
return !led->led[id].b;
|
||||
default:
|
||||
dev_err(&led->client->dev, "%s: Invalid color\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
static inline int bd2802_is_led_off(struct bd2802_led *led, enum led_ids id)
|
||||
{
|
||||
if (led->led[id].r || led->led[id].g || led->led[id].b)
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline int bd2802_is_all_off(struct bd2802_led *led)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LED_NUM; i++)
|
||||
if (!bd2802_is_led_off(led, i))
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static inline u8 bd2802_get_base_offset(enum led_ids id, enum led_colors color)
|
||||
{
|
||||
return id * BD2802_LED_OFFSET + color * BD2802_COLOR_OFFSET;
|
||||
}
|
||||
|
||||
static inline u8 bd2802_get_reg_addr(enum led_ids id, enum led_colors color,
|
||||
u8 reg_offset)
|
||||
{
|
||||
return reg_offset + bd2802_get_base_offset(id, color);
|
||||
}
|
||||
|
||||
|
||||
/*--------------------------------------------------------------*/
|
||||
/* BD2802GU core functions */
|
||||
/*--------------------------------------------------------------*/
|
||||
|
||||
static int bd2802_write_byte(struct i2c_client *client, u8 reg, u8 val)
|
||||
{
|
||||
int ret = i2c_smbus_write_byte_data(client, reg, val);
|
||||
if (ret >= 0)
|
||||
return 0;
|
||||
|
||||
dev_err(&client->dev, "%s: reg 0x%x, val 0x%x, err %d\n",
|
||||
__func__, reg, val, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void bd2802_update_state(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color, enum led_bits led_bit)
|
||||
{
|
||||
int i;
|
||||
u8 value;
|
||||
|
||||
for (i = 0; i < LED_NUM; i++) {
|
||||
if (i == id) {
|
||||
switch (color) {
|
||||
case RED:
|
||||
led->led[i].r = led_bit;
|
||||
break;
|
||||
case GREEN:
|
||||
led->led[i].g = led_bit;
|
||||
break;
|
||||
case BLUE:
|
||||
led->led[i].b = led_bit;
|
||||
break;
|
||||
default:
|
||||
dev_err(&led->client->dev,
|
||||
"%s: Invalid color\n", __func__);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (led_bit == BD2802_BLINK || led_bit == BD2802_ON)
|
||||
return;
|
||||
|
||||
if (!bd2802_is_led_off(led, id))
|
||||
return;
|
||||
|
||||
if (bd2802_is_all_off(led) && !led->adf_on) {
|
||||
gpio_set_value(led->pdata->reset_gpio, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* In this case, other led is turned on, and current led is turned
|
||||
* off. So set RGB LED Control register to stop the current RGB LED
|
||||
*/
|
||||
value = (id == LED1) ? LED_CTL(1, 0) : LED_CTL(0, 1);
|
||||
bd2802_write_byte(led->client, BD2802_REG_CONTROL, value);
|
||||
}
|
||||
|
||||
static void bd2802_configure(struct bd2802_led *led)
|
||||
{
|
||||
struct bd2802_led_platform_data *pdata = led->pdata;
|
||||
u8 reg;
|
||||
|
||||
reg = bd2802_get_reg_addr(LED1, RED, BD2802_REG_HOURSETUP);
|
||||
bd2802_write_byte(led->client, reg, pdata->rgb_time);
|
||||
|
||||
reg = bd2802_get_reg_addr(LED2, RED, BD2802_REG_HOURSETUP);
|
||||
bd2802_write_byte(led->client, reg, pdata->rgb_time);
|
||||
}
|
||||
|
||||
static void bd2802_reset_cancel(struct bd2802_led *led)
|
||||
{
|
||||
gpio_set_value(led->pdata->reset_gpio, 1);
|
||||
udelay(100);
|
||||
bd2802_configure(led);
|
||||
}
|
||||
|
||||
static void bd2802_enable(struct bd2802_led *led, enum led_ids id)
|
||||
{
|
||||
enum led_ids other_led = (id == LED1) ? LED2 : LED1;
|
||||
u8 value, other_led_on;
|
||||
|
||||
other_led_on = !bd2802_is_led_off(led, other_led);
|
||||
if (id == LED1)
|
||||
value = LED_CTL(other_led_on, 1);
|
||||
else
|
||||
value = LED_CTL(1 , other_led_on);
|
||||
|
||||
bd2802_write_byte(led->client, BD2802_REG_CONTROL, value);
|
||||
}
|
||||
|
||||
static void bd2802_set_on(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
if (bd2802_is_all_off(led) && !led->adf_on)
|
||||
bd2802_reset_cancel(led);
|
||||
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP);
|
||||
bd2802_write_byte(led->client, reg, led->rgb_current);
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP);
|
||||
bd2802_write_byte(led->client, reg, BD2802_CURRENT_000);
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN);
|
||||
bd2802_write_byte(led->client, reg, BD2802_PATTERN_FULL);
|
||||
|
||||
bd2802_enable(led, id);
|
||||
bd2802_update_state(led, id, color, BD2802_ON);
|
||||
}
|
||||
|
||||
static void bd2802_set_blink(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
if (bd2802_is_all_off(led) && !led->adf_on)
|
||||
bd2802_reset_cancel(led);
|
||||
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP);
|
||||
bd2802_write_byte(led->client, reg, BD2802_CURRENT_000);
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP);
|
||||
bd2802_write_byte(led->client, reg, led->rgb_current);
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_WAVEPATTERN);
|
||||
bd2802_write_byte(led->client, reg, led->wave_pattern);
|
||||
|
||||
bd2802_enable(led, id);
|
||||
bd2802_update_state(led, id, color, BD2802_BLINK);
|
||||
}
|
||||
|
||||
static void bd2802_turn_on(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color, enum led_bits led_bit)
|
||||
{
|
||||
if (led_bit == BD2802_OFF) {
|
||||
dev_err(&led->client->dev,
|
||||
"Only 'blink' and 'on' are allowed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (led_bit == BD2802_BLINK)
|
||||
bd2802_set_blink(led, id, color);
|
||||
else
|
||||
bd2802_set_on(led, id, color);
|
||||
}
|
||||
|
||||
static void bd2802_turn_off(struct bd2802_led *led, enum led_ids id,
|
||||
enum led_colors color)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
if (bd2802_is_rgb_off(led, id, color))
|
||||
return;
|
||||
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT1SETUP);
|
||||
bd2802_write_byte(led->client, reg, BD2802_CURRENT_000);
|
||||
reg = bd2802_get_reg_addr(id, color, BD2802_REG_CURRENT2SETUP);
|
||||
bd2802_write_byte(led->client, reg, BD2802_CURRENT_000);
|
||||
|
||||
bd2802_update_state(led, id, color, BD2802_OFF);
|
||||
}
|
||||
|
||||
#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) \
|
||||
{ \
|
||||
struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\
|
||||
unsigned long val; \
|
||||
int ret; \
|
||||
if (!count) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtoul(buf, 16, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
down_write(&led->rwsem); \
|
||||
bd2802_write_byte(led->client, reg_addr, (u8) val); \
|
||||
up_write(&led->rwsem); \
|
||||
return count; \
|
||||
} \
|
||||
static struct device_attribute bd2802_reg##reg_addr##_attr = { \
|
||||
.attr = {.name = reg_name, .mode = 0644}, \
|
||||
.store = bd2802_store_reg##reg_addr, \
|
||||
};
|
||||
|
||||
BD2802_SET_REGISTER(0x00, "0x00");
|
||||
BD2802_SET_REGISTER(0x01, "0x01");
|
||||
BD2802_SET_REGISTER(0x02, "0x02");
|
||||
BD2802_SET_REGISTER(0x03, "0x03");
|
||||
BD2802_SET_REGISTER(0x04, "0x04");
|
||||
BD2802_SET_REGISTER(0x05, "0x05");
|
||||
BD2802_SET_REGISTER(0x06, "0x06");
|
||||
BD2802_SET_REGISTER(0x07, "0x07");
|
||||
BD2802_SET_REGISTER(0x08, "0x08");
|
||||
BD2802_SET_REGISTER(0x09, "0x09");
|
||||
BD2802_SET_REGISTER(0x0a, "0x0a");
|
||||
BD2802_SET_REGISTER(0x0b, "0x0b");
|
||||
BD2802_SET_REGISTER(0x0c, "0x0c");
|
||||
BD2802_SET_REGISTER(0x0d, "0x0d");
|
||||
BD2802_SET_REGISTER(0x0e, "0x0e");
|
||||
BD2802_SET_REGISTER(0x0f, "0x0f");
|
||||
BD2802_SET_REGISTER(0x10, "0x10");
|
||||
BD2802_SET_REGISTER(0x11, "0x11");
|
||||
BD2802_SET_REGISTER(0x12, "0x12");
|
||||
BD2802_SET_REGISTER(0x13, "0x13");
|
||||
BD2802_SET_REGISTER(0x14, "0x14");
|
||||
BD2802_SET_REGISTER(0x15, "0x15");
|
||||
|
||||
static struct device_attribute *bd2802_addr_attributes[] = {
|
||||
&bd2802_reg0x00_attr,
|
||||
&bd2802_reg0x01_attr,
|
||||
&bd2802_reg0x02_attr,
|
||||
&bd2802_reg0x03_attr,
|
||||
&bd2802_reg0x04_attr,
|
||||
&bd2802_reg0x05_attr,
|
||||
&bd2802_reg0x06_attr,
|
||||
&bd2802_reg0x07_attr,
|
||||
&bd2802_reg0x08_attr,
|
||||
&bd2802_reg0x09_attr,
|
||||
&bd2802_reg0x0a_attr,
|
||||
&bd2802_reg0x0b_attr,
|
||||
&bd2802_reg0x0c_attr,
|
||||
&bd2802_reg0x0d_attr,
|
||||
&bd2802_reg0x0e_attr,
|
||||
&bd2802_reg0x0f_attr,
|
||||
&bd2802_reg0x10_attr,
|
||||
&bd2802_reg0x11_attr,
|
||||
&bd2802_reg0x12_attr,
|
||||
&bd2802_reg0x13_attr,
|
||||
&bd2802_reg0x14_attr,
|
||||
&bd2802_reg0x15_attr,
|
||||
};
|
||||
|
||||
static void bd2802_enable_adv_conf(struct bd2802_led *led)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++) {
|
||||
ret = device_create_file(&led->client->dev,
|
||||
bd2802_addr_attributes[i]);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "failed: sysfs file %s\n",
|
||||
bd2802_addr_attributes[i]->attr.name);
|
||||
goto failed_remove_files;
|
||||
}
|
||||
}
|
||||
|
||||
if (bd2802_is_all_off(led))
|
||||
bd2802_reset_cancel(led);
|
||||
|
||||
led->adf_on = 1;
|
||||
|
||||
return;
|
||||
|
||||
failed_remove_files:
|
||||
for (i--; i >= 0; i--)
|
||||
device_remove_file(&led->client->dev,
|
||||
bd2802_addr_attributes[i]);
|
||||
}
|
||||
|
||||
static void bd2802_disable_adv_conf(struct bd2802_led *led)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bd2802_addr_attributes); i++)
|
||||
device_remove_file(&led->client->dev,
|
||||
bd2802_addr_attributes[i]);
|
||||
|
||||
if (bd2802_is_all_off(led))
|
||||
gpio_set_value(led->pdata->reset_gpio, 0);
|
||||
|
||||
led->adf_on = 0;
|
||||
}
|
||||
|
||||
static ssize_t bd2802_show_adv_conf(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
ssize_t ret;
|
||||
|
||||
down_read(&led->rwsem);
|
||||
if (led->adf_on)
|
||||
ret = sprintf(buf, "on\n");
|
||||
else
|
||||
ret = sprintf(buf, "off\n");
|
||||
up_read(&led->rwsem);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t bd2802_store_adv_conf(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t count)
|
||||
{
|
||||
struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
|
||||
if (!count)
|
||||
return -EINVAL;
|
||||
|
||||
down_write(&led->rwsem);
|
||||
if (!led->adf_on && !strncmp(buf, "on", 2))
|
||||
bd2802_enable_adv_conf(led);
|
||||
else if (led->adf_on && !strncmp(buf, "off", 3))
|
||||
bd2802_disable_adv_conf(led);
|
||||
up_write(&led->rwsem);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static struct device_attribute bd2802_adv_conf_attr = {
|
||||
.attr = {
|
||||
.name = "advanced_configuration",
|
||||
.mode = 0644,
|
||||
},
|
||||
.show = bd2802_show_adv_conf,
|
||||
.store = bd2802_store_adv_conf,
|
||||
};
|
||||
|
||||
#define BD2802_CONTROL_ATTR(attr_name, name_str) \
|
||||
static ssize_t bd2802_show_##attr_name(struct device *dev, \
|
||||
struct device_attribute *attr, char *buf) \
|
||||
{ \
|
||||
struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\
|
||||
ssize_t ret; \
|
||||
down_read(&led->rwsem); \
|
||||
ret = sprintf(buf, "0x%02x\n", led->attr_name); \
|
||||
up_read(&led->rwsem); \
|
||||
return ret; \
|
||||
} \
|
||||
static ssize_t bd2802_store_##attr_name(struct device *dev, \
|
||||
struct device_attribute *attr, const char *buf, size_t count) \
|
||||
{ \
|
||||
struct bd2802_led *led = i2c_get_clientdata(to_i2c_client(dev));\
|
||||
unsigned long val; \
|
||||
int ret; \
|
||||
if (!count) \
|
||||
return -EINVAL; \
|
||||
ret = kstrtoul(buf, 16, &val); \
|
||||
if (ret) \
|
||||
return ret; \
|
||||
down_write(&led->rwsem); \
|
||||
led->attr_name = val; \
|
||||
up_write(&led->rwsem); \
|
||||
return count; \
|
||||
} \
|
||||
static struct device_attribute bd2802_##attr_name##_attr = { \
|
||||
.attr = { \
|
||||
.name = name_str, \
|
||||
.mode = 0644, \
|
||||
}, \
|
||||
.show = bd2802_show_##attr_name, \
|
||||
.store = bd2802_store_##attr_name, \
|
||||
};
|
||||
|
||||
BD2802_CONTROL_ATTR(wave_pattern, "wave_pattern");
|
||||
BD2802_CONTROL_ATTR(rgb_current, "rgb_current");
|
||||
|
||||
static struct device_attribute *bd2802_attributes[] = {
|
||||
&bd2802_adv_conf_attr,
|
||||
&bd2802_wave_pattern_attr,
|
||||
&bd2802_rgb_current_attr,
|
||||
};
|
||||
|
||||
static void bd2802_led_work(struct work_struct *work)
|
||||
{
|
||||
struct bd2802_led *led = container_of(work, struct bd2802_led, work);
|
||||
|
||||
if (led->state)
|
||||
bd2802_turn_on(led, led->led_id, led->color, led->state);
|
||||
else
|
||||
bd2802_turn_off(led, led->led_id, led->color);
|
||||
}
|
||||
|
||||
#define BD2802_CONTROL_RGBS(name, id, clr) \
|
||||
static void bd2802_set_##name##_brightness(struct led_classdev *led_cdev,\
|
||||
enum led_brightness value) \
|
||||
{ \
|
||||
struct bd2802_led *led = \
|
||||
container_of(led_cdev, struct bd2802_led, cdev_##name); \
|
||||
led->led_id = id; \
|
||||
led->color = clr; \
|
||||
if (value == LED_OFF) \
|
||||
led->state = BD2802_OFF; \
|
||||
else \
|
||||
led->state = BD2802_ON; \
|
||||
schedule_work(&led->work); \
|
||||
} \
|
||||
static int bd2802_set_##name##_blink(struct led_classdev *led_cdev, \
|
||||
unsigned long *delay_on, unsigned long *delay_off) \
|
||||
{ \
|
||||
struct bd2802_led *led = \
|
||||
container_of(led_cdev, struct bd2802_led, cdev_##name); \
|
||||
if (*delay_on == 0 || *delay_off == 0) \
|
||||
return -EINVAL; \
|
||||
led->led_id = id; \
|
||||
led->color = clr; \
|
||||
led->state = BD2802_BLINK; \
|
||||
schedule_work(&led->work); \
|
||||
return 0; \
|
||||
}
|
||||
|
||||
BD2802_CONTROL_RGBS(led1r, LED1, RED);
|
||||
BD2802_CONTROL_RGBS(led1g, LED1, GREEN);
|
||||
BD2802_CONTROL_RGBS(led1b, LED1, BLUE);
|
||||
BD2802_CONTROL_RGBS(led2r, LED2, RED);
|
||||
BD2802_CONTROL_RGBS(led2g, LED2, GREEN);
|
||||
BD2802_CONTROL_RGBS(led2b, LED2, BLUE);
|
||||
|
||||
static int bd2802_register_led_classdev(struct bd2802_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
INIT_WORK(&led->work, bd2802_led_work);
|
||||
|
||||
led->cdev_led1r.name = "led1_R";
|
||||
led->cdev_led1r.brightness = LED_OFF;
|
||||
led->cdev_led1r.brightness_set = bd2802_set_led1r_brightness;
|
||||
led->cdev_led1r.blink_set = bd2802_set_led1r_blink;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led1r);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led1r.name);
|
||||
goto failed_unregister_led1_R;
|
||||
}
|
||||
|
||||
led->cdev_led1g.name = "led1_G";
|
||||
led->cdev_led1g.brightness = LED_OFF;
|
||||
led->cdev_led1g.brightness_set = bd2802_set_led1g_brightness;
|
||||
led->cdev_led1g.blink_set = bd2802_set_led1g_blink;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led1g);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led1g.name);
|
||||
goto failed_unregister_led1_G;
|
||||
}
|
||||
|
||||
led->cdev_led1b.name = "led1_B";
|
||||
led->cdev_led1b.brightness = LED_OFF;
|
||||
led->cdev_led1b.brightness_set = bd2802_set_led1b_brightness;
|
||||
led->cdev_led1b.blink_set = bd2802_set_led1b_blink;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led1b);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led1b.name);
|
||||
goto failed_unregister_led1_B;
|
||||
}
|
||||
|
||||
led->cdev_led2r.name = "led2_R";
|
||||
led->cdev_led2r.brightness = LED_OFF;
|
||||
led->cdev_led2r.brightness_set = bd2802_set_led2r_brightness;
|
||||
led->cdev_led2r.blink_set = bd2802_set_led2r_blink;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led2r);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led2r.name);
|
||||
goto failed_unregister_led2_R;
|
||||
}
|
||||
|
||||
led->cdev_led2g.name = "led2_G";
|
||||
led->cdev_led2g.brightness = LED_OFF;
|
||||
led->cdev_led2g.brightness_set = bd2802_set_led2g_brightness;
|
||||
led->cdev_led2g.blink_set = bd2802_set_led2g_blink;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led2g);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led2g.name);
|
||||
goto failed_unregister_led2_G;
|
||||
}
|
||||
|
||||
led->cdev_led2b.name = "led2_B";
|
||||
led->cdev_led2b.brightness = LED_OFF;
|
||||
led->cdev_led2b.brightness_set = bd2802_set_led2b_brightness;
|
||||
led->cdev_led2b.blink_set = bd2802_set_led2b_blink;
|
||||
led->cdev_led2b.flags |= LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = led_classdev_register(&led->client->dev, &led->cdev_led2b);
|
||||
if (ret < 0) {
|
||||
dev_err(&led->client->dev, "couldn't register LED %s\n",
|
||||
led->cdev_led2b.name);
|
||||
goto failed_unregister_led2_B;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
failed_unregister_led2_B:
|
||||
led_classdev_unregister(&led->cdev_led2g);
|
||||
failed_unregister_led2_G:
|
||||
led_classdev_unregister(&led->cdev_led2r);
|
||||
failed_unregister_led2_R:
|
||||
led_classdev_unregister(&led->cdev_led1b);
|
||||
failed_unregister_led1_B:
|
||||
led_classdev_unregister(&led->cdev_led1g);
|
||||
failed_unregister_led1_G:
|
||||
led_classdev_unregister(&led->cdev_led1r);
|
||||
failed_unregister_led1_R:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 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 = devm_kzalloc(&client->dev, sizeof(struct bd2802_led), GFP_KERNEL);
|
||||
if (!led)
|
||||
return -ENOMEM;
|
||||
|
||||
led->client = client;
|
||||
pdata = led->pdata = dev_get_platdata(&client->dev);
|
||||
i2c_set_clientdata(client, led);
|
||||
|
||||
/* Configure RESET GPIO (L: RESET, H: RESET cancel) */
|
||||
gpio_request_one(pdata->reset_gpio, GPIOF_OUT_INIT_HIGH, "RGB_RESETB");
|
||||
|
||||
/* Tacss = min 0.1ms */
|
||||
udelay(100);
|
||||
|
||||
/* Detect BD2802GU */
|
||||
ret = bd2802_write_byte(client, BD2802_REG_CLKSETUP, 0x00);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "failed to detect device\n");
|
||||
return ret;
|
||||
} else
|
||||
dev_info(&client->dev, "return 0x%02x\n", ret);
|
||||
|
||||
/* To save the power, reset BD2802 after detecting */
|
||||
gpio_set_value(led->pdata->reset_gpio, 0);
|
||||
|
||||
/* Default attributes */
|
||||
led->wave_pattern = BD2802_PATTERN_HALF;
|
||||
led->rgb_current = BD2802_CURRENT_032;
|
||||
|
||||
init_rwsem(&led->rwsem);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++) {
|
||||
ret = device_create_file(&led->client->dev,
|
||||
bd2802_attributes[i]);
|
||||
if (ret) {
|
||||
dev_err(&led->client->dev, "failed: sysfs file %s\n",
|
||||
bd2802_attributes[i]->attr.name);
|
||||
goto failed_unregister_dev_file;
|
||||
}
|
||||
}
|
||||
|
||||
ret = bd2802_register_led_classdev(led);
|
||||
if (ret < 0)
|
||||
goto failed_unregister_dev_file;
|
||||
|
||||
return 0;
|
||||
|
||||
failed_unregister_dev_file:
|
||||
for (i--; i >= 0; i--)
|
||||
device_remove_file(&led->client->dev, bd2802_attributes[i]);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int bd2802_remove(struct i2c_client *client)
|
||||
{
|
||||
struct bd2802_led *led = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
gpio_set_value(led->pdata->reset_gpio, 0);
|
||||
bd2802_unregister_led_classdev(led);
|
||||
if (led->adf_on)
|
||||
bd2802_disable_adv_conf(led);
|
||||
for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++)
|
||||
device_remove_file(&led->client->dev, bd2802_attributes[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
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);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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) {
|
||||
bd2802_reset_cancel(led);
|
||||
bd2802_restore_state(led);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume);
|
||||
|
||||
static const struct i2c_device_id bd2802_id[] = {
|
||||
{ "BD2802", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, bd2802_id);
|
||||
|
||||
static struct i2c_driver bd2802_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "BD2802",
|
||||
.pm = &bd2802_pm,
|
||||
},
|
||||
.probe = bd2802_probe,
|
||||
.remove = bd2802_remove,
|
||||
.id_table = bd2802_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(bd2802_i2c_driver);
|
||||
|
||||
MODULE_AUTHOR("Kim Kyuwon <q1.kim@samsung.com>");
|
||||
MODULE_DESCRIPTION("BD2802 LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
811
drivers/leds/leds-blinkm.c
Normal file
811
drivers/leds/leds-blinkm.c
Normal file
|
|
@ -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");
|
||||
|
||||
217
drivers/leds/leds-clevo-mail.c
Normal file
217
drivers/leds/leds-clevo-mail.c
Normal file
|
|
@ -0,0 +1,217 @@
|
|||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/module.h>
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/dmi.h>
|
||||
|
||||
#include <linux/i8042.h>
|
||||
|
||||
#define CLEVO_MAIL_LED_OFF 0x0084
|
||||
#define CLEVO_MAIL_LED_BLINK_1HZ 0x008A
|
||||
#define CLEVO_MAIL_LED_BLINK_0_5HZ 0x0083
|
||||
|
||||
MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>");
|
||||
MODULE_DESCRIPTION("Clevo mail LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
static bool nodetect;
|
||||
module_param_named(nodetect, nodetect, bool, 0);
|
||||
MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection");
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id)
|
||||
{
|
||||
pr_info("'%s' found\n", id->ident);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
* makes it possible to keep the false positives (the model is
|
||||
* detected as working, but in reality it is not) as low as
|
||||
* possible.
|
||||
*/
|
||||
static struct dmi_system_id clevo_mail_led_dmi_table[] __initdata = {
|
||||
{
|
||||
.callback = clevo_mail_led_dmi_callback,
|
||||
.ident = "Clevo D410J",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "VIA"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "K8N800"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "VT8204B")
|
||||
}
|
||||
},
|
||||
{
|
||||
.callback = clevo_mail_led_dmi_callback,
|
||||
.ident = "Clevo M5x0N",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "CLEVO Co."),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "M5x0N")
|
||||
}
|
||||
},
|
||||
{
|
||||
.callback = clevo_mail_led_dmi_callback,
|
||||
.ident = "Clevo M5x0V",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "M5X0V "),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198")
|
||||
}
|
||||
},
|
||||
{
|
||||
.callback = clevo_mail_led_dmi_callback,
|
||||
.ident = "Clevo D400P",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Clevo"),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "D400P"),
|
||||
DMI_MATCH(DMI_BOARD_VERSION, "Rev.A"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "0106")
|
||||
}
|
||||
},
|
||||
{
|
||||
.callback = clevo_mail_led_dmi_callback,
|
||||
.ident = "Clevo D410V",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_BOARD_VENDOR, "Clevo, Co."),
|
||||
DMI_MATCH(DMI_BOARD_NAME, "D400V/D470V"),
|
||||
DMI_MATCH(DMI_BOARD_VERSION, "SS78B"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "Rev. A1")
|
||||
}
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table);
|
||||
|
||||
static void clevo_mail_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
i8042_lock_chip();
|
||||
|
||||
if (value == LED_OFF)
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_OFF);
|
||||
else if (value <= LED_HALF)
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
|
||||
else
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);
|
||||
|
||||
i8042_unlock_chip();
|
||||
|
||||
}
|
||||
|
||||
static int clevo_mail_led_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
int status = -EINVAL;
|
||||
|
||||
i8042_lock_chip();
|
||||
|
||||
if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) {
|
||||
/* Special case: the leds subsystem requested us to
|
||||
* chose one user friendly blinking of the LED, and
|
||||
* start it. Let's blink the led slowly (0.5Hz).
|
||||
*/
|
||||
*delay_on = 1000; /* ms */
|
||||
*delay_off = 1000; /* ms */
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
|
||||
status = 0;
|
||||
|
||||
} else if (*delay_on == 500 /* ms */ && *delay_off == 500 /* ms */) {
|
||||
/* blink the led with 1Hz */
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ);
|
||||
status = 0;
|
||||
|
||||
} else if (*delay_on == 1000 /* ms */ && *delay_off == 1000 /* ms */) {
|
||||
/* blink the led with 0.5Hz */
|
||||
i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ);
|
||||
status = 0;
|
||||
|
||||
} else {
|
||||
pr_debug("clevo_mail_led_blink(..., %lu, %lu),"
|
||||
" returning -EINVAL (unsupported)\n",
|
||||
*delay_on, *delay_off);
|
||||
}
|
||||
|
||||
i8042_unlock_chip();
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
static struct led_classdev clevo_mail_led = {
|
||||
.name = "clevo::mail",
|
||||
.brightness_set = clevo_mail_led_set,
|
||||
.blink_set = clevo_mail_led_blink,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int __init clevo_mail_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
return led_classdev_register(&pdev->dev, &clevo_mail_led);
|
||||
}
|
||||
|
||||
static int clevo_mail_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&clevo_mail_led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver clevo_mail_led_driver = {
|
||||
.remove = clevo_mail_led_remove,
|
||||
.driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init clevo_mail_led_init(void)
|
||||
{
|
||||
int error = 0;
|
||||
int count = 0;
|
||||
|
||||
/* Check with the help of DMI if we are running on supported hardware */
|
||||
if (!nodetect) {
|
||||
count = dmi_check_system(clevo_mail_led_dmi_table);
|
||||
} else {
|
||||
count = 1;
|
||||
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");
|
||||
}
|
||||
|
||||
if (!count)
|
||||
return -ENODEV;
|
||||
|
||||
pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
|
||||
if (!IS_ERR(pdev)) {
|
||||
error = platform_driver_probe(&clevo_mail_led_driver,
|
||||
clevo_mail_led_probe);
|
||||
if (error) {
|
||||
pr_err("Can't probe platform driver\n");
|
||||
platform_device_unregister(pdev);
|
||||
}
|
||||
} else
|
||||
error = PTR_ERR(pdev);
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
static void __exit clevo_mail_led_exit(void)
|
||||
{
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&clevo_mail_led_driver);
|
||||
|
||||
clevo_mail_led_set(NULL, LED_OFF);
|
||||
}
|
||||
|
||||
module_init(clevo_mail_led_init);
|
||||
module_exit(clevo_mail_led_exit);
|
||||
88
drivers/leds/leds-cobalt-qube.c
Normal file
88
drivers/leds/leds-cobalt-qube.c
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright 2006 - Florian Fainelli <florian@openwrt.org>
|
||||
*
|
||||
* Control the Cobalt Qube/RaQ front LED
|
||||
*/
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define LED_FRONT_LEFT 0x01
|
||||
#define LED_FRONT_RIGHT 0x02
|
||||
|
||||
static void __iomem *led_port;
|
||||
static u8 led_value;
|
||||
|
||||
static void qube_front_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
if (brightness)
|
||||
led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT;
|
||||
else
|
||||
led_value = ~(LED_FRONT_LEFT | LED_FRONT_RIGHT);
|
||||
writeb(led_value, led_port);
|
||||
}
|
||||
|
||||
static struct led_classdev qube_front_led = {
|
||||
.name = "qube::front",
|
||||
.brightness = LED_FULL,
|
||||
.brightness_set = qube_front_led_set,
|
||||
.default_trigger = "default-on",
|
||||
};
|
||||
|
||||
static int cobalt_qube_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int retval;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
|
||||
led_port = devm_ioremap(&pdev->dev, res->start, resource_size(res));
|
||||
if (!led_port)
|
||||
return -ENOMEM;
|
||||
|
||||
led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT;
|
||||
writeb(led_value, led_port);
|
||||
|
||||
retval = led_classdev_register(&pdev->dev, &qube_front_led);
|
||||
if (retval)
|
||||
goto err_null;
|
||||
|
||||
return 0;
|
||||
|
||||
err_null:
|
||||
led_port = NULL;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int cobalt_qube_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&qube_front_led);
|
||||
|
||||
if (led_port)
|
||||
led_port = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver cobalt_qube_led_driver = {
|
||||
.probe = cobalt_qube_led_probe,
|
||||
.remove = cobalt_qube_led_remove,
|
||||
.driver = {
|
||||
.name = "cobalt-qube-leds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
136
drivers/leds/leds-cobalt-raq.c
Normal file
136
drivers/leds/leds-cobalt-raq.c
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* LEDs driver for the Cobalt Raq series.
|
||||
*
|
||||
* Copyright (C) 2007 Yoichi Yuasa <yuasa@linux-mips.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/leds.h>
|
||||
#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
|
||||
|
||||
static void __iomem *led_port;
|
||||
static u8 led_value;
|
||||
static DEFINE_SPINLOCK(led_value_lock);
|
||||
|
||||
static void raq_web_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led_value_lock, flags);
|
||||
|
||||
if (brightness)
|
||||
led_value |= LED_WEB;
|
||||
else
|
||||
led_value &= ~LED_WEB;
|
||||
writeb(led_value, led_port);
|
||||
|
||||
spin_unlock_irqrestore(&led_value_lock, flags);
|
||||
}
|
||||
|
||||
static struct led_classdev raq_web_led = {
|
||||
.name = "raq::web",
|
||||
.brightness_set = raq_web_led_set,
|
||||
};
|
||||
|
||||
static void raq_power_off_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led_value_lock, flags);
|
||||
|
||||
if (brightness)
|
||||
led_value |= LED_POWER_OFF;
|
||||
else
|
||||
led_value &= ~LED_POWER_OFF;
|
||||
writeb(led_value, led_port);
|
||||
|
||||
spin_unlock_irqrestore(&led_value_lock, flags);
|
||||
}
|
||||
|
||||
static struct led_classdev raq_power_off_led = {
|
||||
.name = "raq::power-off",
|
||||
.brightness_set = raq_power_off_led_set,
|
||||
.default_trigger = "power-off",
|
||||
};
|
||||
|
||||
static int cobalt_raq_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct resource *res;
|
||||
int retval;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!res)
|
||||
return -EBUSY;
|
||||
|
||||
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_null;
|
||||
|
||||
retval = led_classdev_register(&pdev->dev, &raq_web_led);
|
||||
if (retval)
|
||||
goto err_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister:
|
||||
led_classdev_unregister(&raq_power_off_led);
|
||||
|
||||
err_null:
|
||||
led_port = NULL;
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static int cobalt_raq_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&raq_power_off_led);
|
||||
led_classdev_unregister(&raq_web_led);
|
||||
|
||||
if (led_port)
|
||||
led_port = NULL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver cobalt_raq_led_driver = {
|
||||
.probe = cobalt_raq_led_probe,
|
||||
.remove = cobalt_raq_led_remove,
|
||||
.driver = {
|
||||
.name = "cobalt-raq-leds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init cobalt_raq_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&cobalt_raq_led_driver);
|
||||
}
|
||||
|
||||
module_init(cobalt_raq_led_init);
|
||||
159
drivers/leds/leds-da903x.c
Normal file
159
drivers/leds/leds-da903x.c
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* LEDs driver for Dialog Semiconductor DA9030/DA9034
|
||||
*
|
||||
* Copyright (C) 2008 Compulab, Ltd.
|
||||
* Mike Rapoport <mike@compulab.co.il>
|
||||
*
|
||||
* Copyright (C) 2006-2008 Marvell International Ltd.
|
||||
* Eric Miao <eric.miao@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mfd/da903x.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define DA9030_LED1_CONTROL 0x20
|
||||
#define DA9030_LED2_CONTROL 0x21
|
||||
#define DA9030_LED3_CONTROL 0x22
|
||||
#define DA9030_LED4_CONTROL 0x23
|
||||
#define DA9030_LEDPC_CONTROL 0x24
|
||||
#define DA9030_MISC_CONTROL_A 0x26 /* Vibrator Control */
|
||||
|
||||
#define DA9034_LED1_CONTROL 0x35
|
||||
#define DA9034_LED2_CONTROL 0x36
|
||||
#define DA9034_VIBRA 0x40
|
||||
|
||||
struct da903x_led {
|
||||
struct led_classdev cdev;
|
||||
struct work_struct work;
|
||||
struct device *master;
|
||||
enum led_brightness new_brightness;
|
||||
int id;
|
||||
int flags;
|
||||
};
|
||||
|
||||
#define DA9030_LED_OFFSET(id) ((id) - DA9030_ID_LED_1)
|
||||
#define DA9034_LED_OFFSET(id) ((id) - DA9034_ID_LED_1)
|
||||
|
||||
static void da903x_led_work(struct work_struct *work)
|
||||
{
|
||||
struct da903x_led *led = container_of(work, struct da903x_led, work);
|
||||
uint8_t val;
|
||||
int offset;
|
||||
|
||||
switch (led->id) {
|
||||
case DA9030_ID_LED_1:
|
||||
case DA9030_ID_LED_2:
|
||||
case DA9030_ID_LED_3:
|
||||
case DA9030_ID_LED_4:
|
||||
case DA9030_ID_LED_PC:
|
||||
offset = DA9030_LED_OFFSET(led->id);
|
||||
val = led->flags & ~0x87;
|
||||
val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
|
||||
val |= (0x7 - (led->new_brightness >> 5)) & 0x7; /* PWM<2:0> */
|
||||
da903x_write(led->master, DA9030_LED1_CONTROL + offset, val);
|
||||
break;
|
||||
case DA9030_ID_VIBRA:
|
||||
val = led->flags & ~0x80;
|
||||
val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */
|
||||
da903x_write(led->master, DA9030_MISC_CONTROL_A, val);
|
||||
break;
|
||||
case DA9034_ID_LED_1:
|
||||
case DA9034_ID_LED_2:
|
||||
offset = DA9034_LED_OFFSET(led->id);
|
||||
val = (led->new_brightness * 0x5f / LED_FULL) & 0x7f;
|
||||
val |= (led->flags & DA9034_LED_RAMP) ? 0x80 : 0;
|
||||
da903x_write(led->master, DA9034_LED1_CONTROL + offset, val);
|
||||
break;
|
||||
case DA9034_ID_VIBRA:
|
||||
val = led->new_brightness & 0xfe;
|
||||
da903x_write(led->master, DA9034_VIBRA, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void da903x_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct da903x_led *led;
|
||||
|
||||
led = container_of(led_cdev, struct da903x_led, cdev);
|
||||
led->new_brightness = value;
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
static int da903x_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct led_info *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct da903x_led *led;
|
||||
int id, ret;
|
||||
|
||||
if (pdata == NULL)
|
||||
return 0;
|
||||
|
||||
id = pdev->id;
|
||||
|
||||
if (!((id >= DA9030_ID_LED_1 && id <= DA9030_ID_VIBRA) ||
|
||||
(id >= DA9034_ID_LED_1 && id <= DA9034_ID_VIBRA))) {
|
||||
dev_err(&pdev->dev, "invalid LED ID (%d) specified\n", id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
led->cdev.brightness_set = da903x_led_set;
|
||||
led->cdev.brightness = LED_OFF;
|
||||
|
||||
led->id = id;
|
||||
led->flags = pdata->flags;
|
||||
led->master = pdev->dev.parent;
|
||||
led->new_brightness = LED_OFF;
|
||||
|
||||
INIT_WORK(&led->work, da903x_led_work);
|
||||
|
||||
ret = led_classdev_register(led->master, &led->cdev);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "failed to register LED %d\n", id);
|
||||
return ret;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int da903x_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct da903x_led *led = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&led->cdev);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver da903x_led_driver = {
|
||||
.driver = {
|
||||
.name = "da903x-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = da903x_led_probe,
|
||||
.remove = da903x_led_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(da903x_led_driver);
|
||||
|
||||
MODULE_DESCRIPTION("LEDs driver for Dialog Semiconductor DA9030/DA9034");
|
||||
MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>");
|
||||
MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:da903x-led");
|
||||
212
drivers/leds/leds-da9052.c
Normal file
212
drivers/leds/leds-da9052.c
Normal file
|
|
@ -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");
|
||||
134
drivers/leds/leds-dac124s085.c
Normal file
134
drivers/leds/leds-dac124s085.c
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
/*
|
||||
* Copyright 2008
|
||||
* Guennadi Liakhovetski, DENX Software Engineering, <lg@denx.de>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* LED driver for the DAC124S085 SPI DAC
|
||||
*/
|
||||
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
struct dac124s085_led {
|
||||
struct led_classdev ldev;
|
||||
struct spi_device *spi;
|
||||
int id;
|
||||
int brightness;
|
||||
char name[sizeof("dac124s085-3")];
|
||||
|
||||
struct mutex mutex;
|
||||
struct work_struct work;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
struct dac124s085 {
|
||||
struct dac124s085_led leds[4];
|
||||
};
|
||||
|
||||
#define REG_WRITE (0 << 12)
|
||||
#define REG_WRITE_UPDATE (1 << 12)
|
||||
#define ALL_WRITE_UPDATE (2 << 12)
|
||||
#define POWER_DOWN_OUTPUT (3 << 12)
|
||||
|
||||
static void dac124s085_led_work(struct work_struct *work)
|
||||
{
|
||||
struct dac124s085_led *led = container_of(work, struct dac124s085_led,
|
||||
work);
|
||||
u16 word;
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE |
|
||||
(led->brightness & 0xfff));
|
||||
spi_write(led->spi, (const u8 *)&word, sizeof(word));
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
|
||||
static void dac124s085_set_brightness(struct led_classdev *ldev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
|
||||
ldev);
|
||||
|
||||
spin_lock(&led->lock);
|
||||
led->brightness = brightness;
|
||||
schedule_work(&led->work);
|
||||
spin_unlock(&led->lock);
|
||||
}
|
||||
|
||||
static int dac124s085_probe(struct spi_device *spi)
|
||||
{
|
||||
struct dac124s085 *dac;
|
||||
struct dac124s085_led *led;
|
||||
int i, ret;
|
||||
|
||||
dac = devm_kzalloc(&spi->dev, sizeof(*dac), GFP_KERNEL);
|
||||
if (!dac)
|
||||
return -ENOMEM;
|
||||
|
||||
spi->bits_per_word = 16;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
|
||||
led = dac->leds + i;
|
||||
led->id = i;
|
||||
led->brightness = LED_OFF;
|
||||
led->spi = spi;
|
||||
snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
|
||||
spin_lock_init(&led->lock);
|
||||
INIT_WORK(&led->work, dac124s085_led_work);
|
||||
mutex_init(&led->mutex);
|
||||
led->ldev.name = led->name;
|
||||
led->ldev.brightness = LED_OFF;
|
||||
led->ldev.max_brightness = 0xfff;
|
||||
led->ldev.brightness_set = dac124s085_set_brightness;
|
||||
ret = led_classdev_register(&spi->dev, &led->ldev);
|
||||
if (ret < 0)
|
||||
goto eledcr;
|
||||
}
|
||||
|
||||
spi_set_drvdata(spi, dac);
|
||||
|
||||
return 0;
|
||||
|
||||
eledcr:
|
||||
while (i--)
|
||||
led_classdev_unregister(&dac->leds[i].ldev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int dac124s085_remove(struct spi_device *spi)
|
||||
{
|
||||
struct dac124s085 *dac = spi_get_drvdata(spi);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
|
||||
led_classdev_unregister(&dac->leds[i].ldev);
|
||||
cancel_work_sync(&dac->leds[i].work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct spi_driver dac124s085_driver = {
|
||||
.probe = dac124s085_probe,
|
||||
.remove = dac124s085_remove,
|
||||
.driver = {
|
||||
.name = "dac124s085",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_spi_driver(dac124s085_driver);
|
||||
|
||||
MODULE_AUTHOR("Guennadi Liakhovetski <lg@denx.de>");
|
||||
MODULE_DESCRIPTION("DAC124S085 LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("spi:dac124s085");
|
||||
225
drivers/leds/leds-fsg.c
Normal file
225
drivers/leds/leds-fsg.c
Normal file
|
|
@ -0,0 +1,225 @@
|
|||
/*
|
||||
* LED Driver for the Freecom FSG-3
|
||||
*
|
||||
* Copyright (c) 2008 Rod Whitby <rod@whitby.id.au>
|
||||
*
|
||||
* Author: Rod Whitby <rod@whitby.id.au>
|
||||
*
|
||||
* Based on leds-spitz.c
|
||||
* Copyright 2005-2006 Openedhand Ltd.
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/io.h>
|
||||
#include <mach/hardware.h>
|
||||
|
||||
#define FSG_LED_WLAN_BIT 0
|
||||
#define FSG_LED_WAN_BIT 1
|
||||
#define FSG_LED_SATA_BIT 2
|
||||
#define FSG_LED_USB_BIT 4
|
||||
#define FSG_LED_RING_BIT 5
|
||||
#define FSG_LED_SYNC_BIT 7
|
||||
|
||||
static short __iomem *latch_address;
|
||||
static unsigned short latch_value;
|
||||
|
||||
|
||||
static void fsg_led_wlan_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_WLAN_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_WLAN_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fsg_led_wan_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_WAN_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_WAN_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fsg_led_sata_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_SATA_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_SATA_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fsg_led_usb_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_USB_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_USB_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fsg_led_sync_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_SYNC_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_SYNC_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
static void fsg_led_ring_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value) {
|
||||
latch_value &= ~(1 << FSG_LED_RING_BIT);
|
||||
*latch_address = latch_value;
|
||||
} else {
|
||||
latch_value |= (1 << FSG_LED_RING_BIT);
|
||||
*latch_address = latch_value;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static struct led_classdev fsg_wlan_led = {
|
||||
.name = "fsg:blue:wlan",
|
||||
.brightness_set = fsg_led_wlan_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev fsg_wan_led = {
|
||||
.name = "fsg:blue:wan",
|
||||
.brightness_set = fsg_led_wan_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev fsg_sata_led = {
|
||||
.name = "fsg:blue:sata",
|
||||
.brightness_set = fsg_led_sata_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev fsg_usb_led = {
|
||||
.name = "fsg:blue:usb",
|
||||
.brightness_set = fsg_led_usb_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev fsg_sync_led = {
|
||||
.name = "fsg:blue:sync",
|
||||
.brightness_set = fsg_led_sync_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev fsg_ring_led = {
|
||||
.name = "fsg:blue:ring",
|
||||
.brightness_set = fsg_led_ring_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
|
||||
static int fsg_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Map the LED chip select address space */
|
||||
latch_address = (unsigned short *) devm_ioremap(&pdev->dev,
|
||||
IXP4XX_EXP_BUS_BASE(2), 512);
|
||||
if (!latch_address)
|
||||
return -ENOMEM;
|
||||
|
||||
latch_value = 0xffff;
|
||||
*latch_address = latch_value;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_wlan_led);
|
||||
if (ret < 0)
|
||||
goto failwlan;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_wan_led);
|
||||
if (ret < 0)
|
||||
goto failwan;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_sata_led);
|
||||
if (ret < 0)
|
||||
goto failsata;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_usb_led);
|
||||
if (ret < 0)
|
||||
goto failusb;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_sync_led);
|
||||
if (ret < 0)
|
||||
goto failsync;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &fsg_ring_led);
|
||||
if (ret < 0)
|
||||
goto failring;
|
||||
|
||||
return ret;
|
||||
|
||||
failring:
|
||||
led_classdev_unregister(&fsg_sync_led);
|
||||
failsync:
|
||||
led_classdev_unregister(&fsg_usb_led);
|
||||
failusb:
|
||||
led_classdev_unregister(&fsg_sata_led);
|
||||
failsata:
|
||||
led_classdev_unregister(&fsg_wan_led);
|
||||
failwan:
|
||||
led_classdev_unregister(&fsg_wlan_led);
|
||||
failwlan:
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int fsg_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&fsg_wlan_led);
|
||||
led_classdev_unregister(&fsg_wan_led);
|
||||
led_classdev_unregister(&fsg_sata_led);
|
||||
led_classdev_unregister(&fsg_usb_led);
|
||||
led_classdev_unregister(&fsg_sync_led);
|
||||
led_classdev_unregister(&fsg_ring_led);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static struct platform_driver fsg_led_driver = {
|
||||
.probe = fsg_led_probe,
|
||||
.remove = fsg_led_remove,
|
||||
.driver = {
|
||||
.name = "fsg-led",
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(fsg_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Rod Whitby <rod@whitby.id.au>");
|
||||
MODULE_DESCRIPTION("Freecom FSG-3 LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
45
drivers/leds/leds-gpio-register.c
Normal file
45
drivers/leds/leds-gpio-register.c
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright (C) 2011 Pengutronix
|
||||
* Uwe Kleine-Koenig <u.kleine-koenig@pengutronix.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License version 2 as published by the
|
||||
* Free Software Foundation.
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
if (!pdata->num_leds)
|
||||
return ERR_PTR(-EINVAL);
|
||||
|
||||
_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;
|
||||
}
|
||||
300
drivers/leds/leds-gpio.c
Normal file
300
drivers/leds/leds-gpio.c
Normal file
|
|
@ -0,0 +1,300 @@
|
|||
/*
|
||||
* LEDs driver for GPIOs
|
||||
*
|
||||
* Copyright (C) 2007 8D Technologies inc.
|
||||
* Raphael Assenat <raph@8d.com>
|
||||
* Copyright (C) 2008 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/of_platform.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct gpio_led_data {
|
||||
struct led_classdev cdev;
|
||||
unsigned gpio;
|
||||
struct work_struct work;
|
||||
u8 new_level;
|
||||
u8 can_sleep;
|
||||
u8 active_low;
|
||||
u8 blinking;
|
||||
int (*platform_gpio_blink_set)(unsigned gpio, int state,
|
||||
unsigned long *delay_on, unsigned long *delay_off);
|
||||
};
|
||||
|
||||
static void gpio_led_work(struct work_struct *work)
|
||||
{
|
||||
struct gpio_led_data *led_dat =
|
||||
container_of(work, struct gpio_led_data, work);
|
||||
|
||||
if (led_dat->blinking) {
|
||||
led_dat->platform_gpio_blink_set(led_dat->gpio,
|
||||
led_dat->new_level,
|
||||
NULL, NULL);
|
||||
led_dat->blinking = 0;
|
||||
} else
|
||||
gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level);
|
||||
}
|
||||
|
||||
static void gpio_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct gpio_led_data *led_dat =
|
||||
container_of(led_cdev, struct gpio_led_data, cdev);
|
||||
int level;
|
||||
|
||||
if (value == LED_OFF)
|
||||
level = 0;
|
||||
else
|
||||
level = 1;
|
||||
|
||||
if (led_dat->active_low)
|
||||
level = !level;
|
||||
|
||||
/* Setting GPIOs with I2C/etc requires a task context, and we don't
|
||||
* seem to have a reliable way to know if we're already in one; so
|
||||
* let's just assume the worst.
|
||||
*/
|
||||
if (led_dat->can_sleep) {
|
||||
led_dat->new_level = level;
|
||||
schedule_work(&led_dat->work);
|
||||
} else {
|
||||
if (led_dat->blinking) {
|
||||
led_dat->platform_gpio_blink_set(led_dat->gpio, level,
|
||||
NULL, NULL);
|
||||
led_dat->blinking = 0;
|
||||
} else
|
||||
gpio_set_value(led_dat->gpio, level);
|
||||
}
|
||||
}
|
||||
|
||||
static int gpio_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on, unsigned long *delay_off)
|
||||
{
|
||||
struct gpio_led_data *led_dat =
|
||||
container_of(led_cdev, struct gpio_led_data, cdev);
|
||||
|
||||
led_dat->blinking = 1;
|
||||
return led_dat->platform_gpio_blink_set(led_dat->gpio, GPIO_LED_BLINK,
|
||||
delay_on, delay_off);
|
||||
}
|
||||
|
||||
static int create_gpio_led(const struct gpio_led *template,
|
||||
struct gpio_led_data *led_dat, struct device *parent,
|
||||
int (*blink_set)(unsigned, int, unsigned long *, unsigned long *))
|
||||
{
|
||||
int ret, state;
|
||||
|
||||
led_dat->gpio = -1;
|
||||
|
||||
/* skip leds that aren't available */
|
||||
if (!gpio_is_valid(template->gpio)) {
|
||||
dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n",
|
||||
template->gpio, template->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = devm_gpio_request(parent, template->gpio, template->name);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
led_dat->cdev.name = template->name;
|
||||
led_dat->cdev.default_trigger = template->default_trigger;
|
||||
led_dat->gpio = template->gpio;
|
||||
led_dat->can_sleep = gpio_cansleep(template->gpio);
|
||||
led_dat->active_low = template->active_low;
|
||||
led_dat->blinking = 0;
|
||||
if (blink_set) {
|
||||
led_dat->platform_gpio_blink_set = blink_set;
|
||||
led_dat->cdev.blink_set = gpio_blink_set;
|
||||
}
|
||||
led_dat->cdev.brightness_set = gpio_led_set;
|
||||
if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP)
|
||||
state = !!gpio_get_value_cansleep(led_dat->gpio) ^ led_dat->active_low;
|
||||
else
|
||||
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
|
||||
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
|
||||
if (!template->retain_state_suspended)
|
||||
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
INIT_WORK(&led_dat->work, gpio_led_work);
|
||||
|
||||
ret = led_classdev_register(parent, &led_dat->cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void delete_gpio_led(struct gpio_led_data *led)
|
||||
{
|
||||
if (!gpio_is_valid(led->gpio))
|
||||
return;
|
||||
led_classdev_unregister(&led->cdev);
|
||||
cancel_work_sync(&led->work);
|
||||
}
|
||||
|
||||
struct gpio_leds_priv {
|
||||
int num_leds;
|
||||
struct gpio_led_data leds[];
|
||||
};
|
||||
|
||||
static inline int sizeof_gpio_leds_priv(int num_leds)
|
||||
{
|
||||
return sizeof(struct gpio_leds_priv) +
|
||||
(sizeof(struct gpio_led_data) * num_leds);
|
||||
}
|
||||
|
||||
/* Code to create from OpenFirmware platform devices */
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev)
|
||||
{
|
||||
struct device_node *np = pdev->dev.of_node, *child;
|
||||
struct gpio_leds_priv *priv;
|
||||
int count, ret;
|
||||
|
||||
/* count LEDs in this device, so we know how much to allocate */
|
||||
count = of_get_available_child_count(np);
|
||||
if (!count)
|
||||
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);
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count),
|
||||
GFP_KERNEL);
|
||||
if (!priv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
struct gpio_led led = {};
|
||||
enum of_gpio_flags flags;
|
||||
const char *state;
|
||||
|
||||
led.gpio = of_get_gpio_flags(child, 0, &flags);
|
||||
led.active_low = flags & OF_GPIO_ACTIVE_LOW;
|
||||
led.name = of_get_property(child, "label", NULL) ? : child->name;
|
||||
led.default_trigger =
|
||||
of_get_property(child, "linux,default-trigger", NULL);
|
||||
state = of_get_property(child, "default-state", NULL);
|
||||
if (state) {
|
||||
if (!strcmp(state, "keep"))
|
||||
led.default_state = LEDS_GPIO_DEFSTATE_KEEP;
|
||||
else if (!strcmp(state, "on"))
|
||||
led.default_state = LEDS_GPIO_DEFSTATE_ON;
|
||||
else
|
||||
led.default_state = LEDS_GPIO_DEFSTATE_OFF;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return priv;
|
||||
|
||||
err:
|
||||
for (count = priv->num_leds - 2; count >= 0; count--)
|
||||
delete_gpio_led(&priv->leds[count]);
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
static const struct of_device_id of_gpio_leds_match[] = {
|
||||
{ .compatible = "gpio-leds", },
|
||||
{},
|
||||
};
|
||||
|
||||
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 gpio_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct gpio_leds_priv *priv;
|
||||
int i, ret = 0;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gpio_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < priv->num_leds; i++)
|
||||
delete_gpio_led(&priv->leds[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
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");
|
||||
95
drivers/leds/leds-hp6xx.c
Normal file
95
drivers/leds/leds-hp6xx.c
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* LED Triggers Core
|
||||
* For the HP Jornada 620/660/680/690 handhelds
|
||||
*
|
||||
* Copyright 2008 Kristoffer Ericson <kristoffer.ericson@gmail.com>
|
||||
* this driver is based on leds-spitz.c by Richard Purdie.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <asm/hd64461.h>
|
||||
#include <mach/hp6xx.h>
|
||||
|
||||
static void hp6xxled_green_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
u8 v8;
|
||||
|
||||
v8 = inb(PKDR);
|
||||
if (value)
|
||||
outb(v8 & (~PKDR_LED_GREEN), PKDR);
|
||||
else
|
||||
outb(v8 | PKDR_LED_GREEN, PKDR);
|
||||
}
|
||||
|
||||
static void hp6xxled_red_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
u16 v16;
|
||||
|
||||
v16 = inw(HD64461_GPBDR);
|
||||
if (value)
|
||||
outw(v16 & (~HD64461_GPBDR_LED_RED), HD64461_GPBDR);
|
||||
else
|
||||
outw(v16 | HD64461_GPBDR_LED_RED, HD64461_GPBDR);
|
||||
}
|
||||
|
||||
static struct led_classdev hp6xx_red_led = {
|
||||
.name = "hp6xx:red",
|
||||
.default_trigger = "hp6xx-charge",
|
||||
.brightness_set = hp6xxled_red_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev hp6xx_green_led = {
|
||||
.name = "hp6xx:green",
|
||||
.default_trigger = "ide-disk",
|
||||
.brightness_set = hp6xxled_green_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int hp6xxled_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &hp6xx_red_led);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &hp6xx_green_led);
|
||||
if (ret < 0)
|
||||
led_classdev_unregister(&hp6xx_red_led);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int hp6xxled_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&hp6xx_red_led);
|
||||
led_classdev_unregister(&hp6xx_green_led);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver hp6xxled_driver = {
|
||||
.probe = hp6xxled_probe,
|
||||
.remove = hp6xxled_remove,
|
||||
.driver = {
|
||||
.name = "hp6xx-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
141
drivers/leds/leds-ipaq-micro.c
Normal file
141
drivers/leds/leds-ipaq-micro.c
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
* h3xxx atmel micro companion support, notification LED subdevice
|
||||
*
|
||||
* Author : Linus Walleij <linus.walleij@linaro.org>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/mfd/ipaq-micro.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#define LED_YELLOW 0x00
|
||||
#define LED_GREEN 0x01
|
||||
|
||||
#define LED_EN (1 << 4) /* LED ON/OFF 0:off, 1:on */
|
||||
#define LED_AUTOSTOP (1 << 5) /* LED ON/OFF auto stop set 0:disable, 1:enable */
|
||||
#define LED_ALWAYS (1 << 6) /* LED Interrupt Mask 0:No mask, 1:mask */
|
||||
|
||||
static void micro_leds_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct ipaq_micro *micro = dev_get_drvdata(led_cdev->dev->parent->parent);
|
||||
/*
|
||||
* In this message:
|
||||
* Byte 0 = LED color: 0 = yellow, 1 = green
|
||||
* yellow LED is always ~30 blinks per minute
|
||||
* Byte 1 = duration (flags?) appears to be ignored
|
||||
* Byte 2 = green ontime in 1/10 sec (deciseconds)
|
||||
* 1 = 1/10 second
|
||||
* 0 = 256/10 second
|
||||
* Byte 3 = green offtime in 1/10 sec (deciseconds)
|
||||
* 1 = 1/10 second
|
||||
* 0 = 256/10 seconds
|
||||
*/
|
||||
struct ipaq_micro_msg msg = {
|
||||
.id = MSG_NOTIFY_LED,
|
||||
.tx_len = 4,
|
||||
};
|
||||
|
||||
msg.tx_data[0] = LED_GREEN;
|
||||
msg.tx_data[1] = 0;
|
||||
if (value) {
|
||||
msg.tx_data[2] = 0; /* Duty cycle 256 */
|
||||
msg.tx_data[3] = 1;
|
||||
} else {
|
||||
msg.tx_data[2] = 1;
|
||||
msg.tx_data[3] = 0; /* Duty cycle 256 */
|
||||
}
|
||||
ipaq_micro_tx_msg_sync(micro, &msg);
|
||||
}
|
||||
|
||||
/* Maximum duty cycle in ms 256/10 sec = 25600 ms */
|
||||
#define IPAQ_LED_MAX_DUTY 25600
|
||||
|
||||
static int micro_leds_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct ipaq_micro *micro = dev_get_drvdata(led_cdev->dev->parent->parent);
|
||||
/*
|
||||
* In this message:
|
||||
* Byte 0 = LED color: 0 = yellow, 1 = green
|
||||
* yellow LED is always ~30 blinks per minute
|
||||
* Byte 1 = duration (flags?) appears to be ignored
|
||||
* Byte 2 = green ontime in 1/10 sec (deciseconds)
|
||||
* 1 = 1/10 second
|
||||
* 0 = 256/10 second
|
||||
* Byte 3 = green offtime in 1/10 sec (deciseconds)
|
||||
* 1 = 1/10 second
|
||||
* 0 = 256/10 seconds
|
||||
*/
|
||||
struct ipaq_micro_msg msg = {
|
||||
.id = MSG_NOTIFY_LED,
|
||||
.tx_len = 4,
|
||||
};
|
||||
|
||||
msg.tx_data[0] = LED_GREEN;
|
||||
if (*delay_on > IPAQ_LED_MAX_DUTY ||
|
||||
*delay_off > IPAQ_LED_MAX_DUTY)
|
||||
return -EINVAL;
|
||||
|
||||
if (*delay_on == 0 && *delay_off == 0) {
|
||||
*delay_on = 100;
|
||||
*delay_off = 100;
|
||||
}
|
||||
|
||||
msg.tx_data[1] = 0;
|
||||
if (*delay_on >= IPAQ_LED_MAX_DUTY)
|
||||
msg.tx_data[2] = 0;
|
||||
else
|
||||
msg.tx_data[2] = (u8) DIV_ROUND_CLOSEST(*delay_on, 100);
|
||||
if (*delay_off >= IPAQ_LED_MAX_DUTY)
|
||||
msg.tx_data[3] = 0;
|
||||
else
|
||||
msg.tx_data[3] = (u8) DIV_ROUND_CLOSEST(*delay_off, 100);
|
||||
return ipaq_micro_tx_msg_sync(micro, &msg);
|
||||
}
|
||||
|
||||
static struct led_classdev micro_led = {
|
||||
.name = "led-ipaq-micro",
|
||||
.brightness_set = micro_leds_brightness_set,
|
||||
.blink_set = micro_leds_blink_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int micro_leds_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, µ_led);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "registering led failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
dev_info(&pdev->dev, "iPAQ micro notification LED driver\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int micro_leds_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(µ_led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver micro_leds_device_driver = {
|
||||
.driver = {
|
||||
.name = "ipaq-micro-leds",
|
||||
},
|
||||
.probe = micro_leds_probe,
|
||||
.remove = micro_leds_remove,
|
||||
};
|
||||
module_platform_driver(micro_leds_device_driver);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("driver for iPAQ Atmel micro leds");
|
||||
MODULE_ALIAS("platform:ipaq-micro-leds");
|
||||
1055
drivers/leds/leds-ktd2026.c
Normal file
1055
drivers/leds/leds-ktd2026.c
Normal file
File diff suppressed because it is too large
Load diff
388
drivers/leds/leds-ktd2692.c
Executable file
388
drivers/leds/leds-ktd2692.c
Executable file
|
|
@ -0,0 +1,388 @@
|
|||
/*
|
||||
* LED driver - leds-ktd2692.c
|
||||
*
|
||||
* Copyright (C) 2013 Sunggeun Yim <sunggeun.yim@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/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/leds-ktd2692.h>
|
||||
#ifdef CONFIG_OF
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#endif
|
||||
|
||||
extern struct class *camera_class; /*sys/class/camera*/
|
||||
struct device *ktd2692_dev;
|
||||
|
||||
struct ktd2692_platform_data *global_ktd2692data = NULL;
|
||||
struct device *global_dev;
|
||||
|
||||
void ktd2692_setGpio(int onoff)
|
||||
{
|
||||
if (onoff) {
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 1);
|
||||
} else {
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void ktd2692_set_low_bit(void)
|
||||
{
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 0);
|
||||
ndelay(T_L_LB*1000); /* 12ms */
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 1);
|
||||
ndelay(T_H_LB*1000); /* 4ms */
|
||||
}
|
||||
|
||||
void ktd2692_set_high_bit(void)
|
||||
{
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 0);
|
||||
ndelay(T_L_HB*1000); /* 4ms */
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 1);
|
||||
ndelay(T_H_HB*1000); /* 12ms */
|
||||
}
|
||||
|
||||
static int ktd2692_set_bit(unsigned int bit)
|
||||
{
|
||||
if (bit) {
|
||||
ktd2692_set_high_bit();
|
||||
} else {
|
||||
ktd2692_set_low_bit();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ktd2692_write_data(unsigned data)
|
||||
{
|
||||
int err = 0;
|
||||
unsigned int bit = 0;
|
||||
|
||||
/* Data Start Condition */
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 1);
|
||||
ndelay(T_SOD*1000); //15us
|
||||
|
||||
/* BIT 7*/
|
||||
bit = ((data>> 7) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 6 */
|
||||
bit = ((data>> 6) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 5*/
|
||||
bit = ((data>> 5) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 4 */
|
||||
bit = ((data>> 4) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 3*/
|
||||
bit = ((data>> 3) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 2 */
|
||||
bit = ((data>> 2) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 1*/
|
||||
bit = ((data>> 1) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
/* BIT 0 */
|
||||
bit = ((data>> 0) & 0x01);
|
||||
ktd2692_set_bit(bit);
|
||||
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 0);
|
||||
ndelay(T_EOD_L*1000); //4us
|
||||
|
||||
/* Data End Condition */
|
||||
__gpio_set_value(global_ktd2692data->flash_control, 1);
|
||||
udelay(T_EOD_H);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_LEDS_SUPPORT_FRONT_FLASH)
|
||||
int ktd2692_led_mode_ctrl(int mode)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned long flags = 0;
|
||||
struct pinctrl *pinctrl;
|
||||
|
||||
LED_INFO("KTD2692 - mode = %d\n", mode);
|
||||
|
||||
if(global_ktd2692data == NULL){
|
||||
LED_INFO("KTD2692 global_ktd2692data is not initialized.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
switch(mode) {
|
||||
case 1 : //CAM2_FLASH_MODE_OFF
|
||||
ret = gpio_request(global_ktd2692data->flash_control, "ktd2692_led_control");
|
||||
if (ret) {
|
||||
LED_ERROR("Failed to requeset ktd2692_led_control\n");
|
||||
} else {
|
||||
LED_INFO("KTD2692-TORCH OFF. : E(%d)\n", mode);
|
||||
|
||||
global_ktd2692data->mode_status = KTD2692_DISABLES_MOVIE_FLASH_MODE;
|
||||
spin_lock_irqsave(&global_ktd2692data->int_lock, flags);
|
||||
ktd2692_write_data(global_ktd2692data->mode_status|
|
||||
KTD2692_ADDR_MOVIE_FLASHMODE_CONTROL);
|
||||
spin_unlock_irqrestore(&global_ktd2692data->int_lock, flags);
|
||||
|
||||
ktd2692_setGpio(0);
|
||||
gpio_free(global_ktd2692data->flash_control);
|
||||
LED_INFO("KTD2692-TORCH OFF. : X(%d)\n", mode);
|
||||
}
|
||||
|
||||
pinctrl = devm_pinctrl_get_select(global_dev, "is");
|
||||
if (IS_ERR(pinctrl))
|
||||
pr_err("%s: flash %s pins are not configured\n", __func__, "is");
|
||||
break;
|
||||
case 2 : //CAM2_FLASH_MODE_SINGLE
|
||||
pinctrl = devm_pinctrl_get_select(global_dev, "host");
|
||||
if (IS_ERR(pinctrl))
|
||||
pr_err("%s: flash %s pins are not configured\n", __func__, "host");
|
||||
|
||||
ret = gpio_request(global_ktd2692data->flash_control, "ktd2692_led_control");
|
||||
if (ret) {
|
||||
LED_ERROR("Failed to requeset ktd2692_led_control\n");
|
||||
} else {
|
||||
LED_INFO("KTD2692-TORCH ON. : E(%d)\n", mode);
|
||||
|
||||
global_ktd2692data->mode_status = KTD2692_ENABLE_MOVIE_MODE;
|
||||
spin_lock_irqsave(&global_ktd2692data->int_lock, flags);
|
||||
ktd2692_write_data(global_ktd2692data->LVP_Voltage|
|
||||
KTD2692_ADDR_LVP_SETTING);
|
||||
ktd2692_write_data(global_ktd2692data->movie_current_value|
|
||||
KTD2692_ADDR_MOVIE_CURRENT_SETTING);
|
||||
ktd2692_write_data(global_ktd2692data->mode_status|
|
||||
KTD2692_ADDR_MOVIE_FLASHMODE_CONTROL);
|
||||
spin_unlock_irqrestore(&global_ktd2692data->int_lock, flags);
|
||||
|
||||
gpio_free(global_ktd2692data->flash_control);
|
||||
LED_INFO("KTD2692-TORCH ON. : X(%d)\n", mode);
|
||||
}
|
||||
break;
|
||||
default :
|
||||
break;
|
||||
}
|
||||
if (!IS_ERR(pinctrl))
|
||||
devm_pinctrl_put(pinctrl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifndef CONFIG_LEDS_SUPPORT_FRONT_FLASH
|
||||
ssize_t ktd2692_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
int value = 0;
|
||||
int ret = 0;
|
||||
unsigned long flags = 0;
|
||||
struct pinctrl *pinctrl;
|
||||
|
||||
if ((buf == NULL) || kstrtouint(buf, 10, &value)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
global_ktd2692data->sysfs_input_data = value;
|
||||
|
||||
if (value <= 0) {
|
||||
ret = gpio_request(global_ktd2692data->flash_control, "ktd2692_led_control");
|
||||
if (ret) {
|
||||
LED_ERROR("Failed to requeset ktd2692_led_control\n");
|
||||
} else {
|
||||
LED_INFO("KTD2692-TORCH OFF. : E(%d)\n", value);
|
||||
|
||||
global_ktd2692data->mode_status = KTD2692_DISABLES_MOVIE_FLASH_MODE;
|
||||
spin_lock_irqsave(&global_ktd2692data->int_lock, flags);
|
||||
ktd2692_write_data(global_ktd2692data->mode_status|
|
||||
KTD2692_ADDR_MOVIE_FLASHMODE_CONTROL);
|
||||
spin_unlock_irqrestore(&global_ktd2692data->int_lock, flags);
|
||||
|
||||
ktd2692_setGpio(0);
|
||||
gpio_free(global_ktd2692data->flash_control);
|
||||
LED_INFO("KTD2692-TORCH OFF. : X(%d)\n", value);
|
||||
}
|
||||
|
||||
pinctrl = devm_pinctrl_get_select(global_dev, "is");
|
||||
if (IS_ERR(pinctrl))
|
||||
pr_err("%s: flash %s pins are not configured\n", __func__, "is");
|
||||
|
||||
} else {
|
||||
pinctrl = devm_pinctrl_get_select(global_dev, "host");
|
||||
if (IS_ERR(pinctrl))
|
||||
pr_err("%s: flash %s pins are not configured\n", __func__, "host");
|
||||
|
||||
ret = gpio_request(global_ktd2692data->flash_control, "ktd2692_led_control");
|
||||
if (ret) {
|
||||
LED_ERROR("Failed to requeset ktd2692_led_control\n");
|
||||
} else {
|
||||
LED_INFO("KTD2692-TORCH ON. : E(%d)\n", value);
|
||||
|
||||
global_ktd2692data->mode_status = KTD2692_ENABLE_MOVIE_MODE;
|
||||
spin_lock_irqsave(&global_ktd2692data->int_lock, flags);
|
||||
ktd2692_write_data(global_ktd2692data->LVP_Voltage|
|
||||
KTD2692_ADDR_LVP_SETTING);
|
||||
#if 0 /* use the internel defualt setting */
|
||||
ktd2692_write_data(global_ktd2692data->flash_timeout|
|
||||
KTD2692_ADDR_FLASH_TIMEOUT_SETTING);
|
||||
#endif
|
||||
ktd2692_write_data(global_ktd2692data->movie_current_value|
|
||||
KTD2692_ADDR_MOVIE_CURRENT_SETTING);
|
||||
ktd2692_write_data(global_ktd2692data->mode_status|
|
||||
KTD2692_ADDR_MOVIE_FLASHMODE_CONTROL);
|
||||
spin_unlock_irqrestore(&global_ktd2692data->int_lock, flags);
|
||||
|
||||
gpio_free(global_ktd2692data->flash_control);
|
||||
LED_INFO("KTD2692-TORCH ON. : X(%d)\n", value);
|
||||
}
|
||||
}
|
||||
|
||||
if (!IS_ERR(pinctrl))
|
||||
devm_pinctrl_put(pinctrl);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
ssize_t ktd2692_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
return sprintf(buf, "%d\n", global_ktd2692data->sysfs_input_data);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(rear_flash, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH,
|
||||
ktd2692_show, ktd2692_store);
|
||||
#endif
|
||||
|
||||
static int ktd2692_parse_dt(struct device *dev,
|
||||
struct ktd2692_platform_data *pdata)
|
||||
{
|
||||
struct device_node *dnode = dev->of_node;
|
||||
int ret = 0;
|
||||
|
||||
/* Defulat Value */
|
||||
pdata->LVP_Voltage = KTD2692_DISABLE_LVP;
|
||||
pdata->flash_timeout = KTD2692_TIMER_1049ms; /* default */
|
||||
pdata->min_current_value = KTD2692_MIN_CURRENT_240mA;
|
||||
pdata->movie_current_value = KTD2692_MOVIE_CURRENT5;
|
||||
pdata->flash_current_value = KTD2692_FLASH_CURRENT16;
|
||||
pdata->mode_status = KTD2692_DISABLES_MOVIE_FLASH_MODE;
|
||||
|
||||
/* get gpio */
|
||||
pdata->flash_control = of_get_named_gpio(dnode, "flash_control", 0);
|
||||
if (!gpio_is_valid(pdata->flash_control)) {
|
||||
dev_err(dev, "failed to get flash_control\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ktd2692_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ktd2692_platform_data *pdata;
|
||||
int ret = 0;
|
||||
|
||||
LED_INFO("KTD2692_LED Probe\n");
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "Failed to allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = ktd2692_parse_dt(&pdev->dev, pdata);
|
||||
if (ret < 0) {
|
||||
return -EFAULT;
|
||||
}
|
||||
} else {
|
||||
pdata = pdev->dev.platform_data;
|
||||
if (pdata == NULL) {
|
||||
return -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
global_ktd2692data = pdata;
|
||||
global_dev = &pdev->dev;
|
||||
|
||||
LED_INFO("KTD2692_LED Probed\n");
|
||||
|
||||
#ifndef CONFIG_LEDS_SUPPORT_FRONT_FLASH
|
||||
ktd2692_dev = device_create(camera_class, NULL, 0, NULL, "flash");
|
||||
if (IS_ERR(ktd2692_dev)) {
|
||||
LED_ERROR("Failed to create device(flash)!\n");
|
||||
}
|
||||
|
||||
if (device_create_file(ktd2692_dev, &dev_attr_rear_flash) < 0) {
|
||||
LED_ERROR("failed to create device file, %s\n",
|
||||
dev_attr_rear_flash.attr.name);
|
||||
}
|
||||
#endif
|
||||
spin_lock_init(&pdata->int_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
static int __devexit ktd2692_remove(struct platform_device *pdev)
|
||||
{
|
||||
#ifndef CONFIG_LEDS_SUPPORT_FRONT_FLASH
|
||||
device_remove_file(ktd2692_dev, &dev_attr_rear_flash);
|
||||
device_destroy(camera_class, 0);
|
||||
class_destroy(camera_class);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct of_device_id ktd2692_dt_ids[] = {
|
||||
{ .compatible = "ktd2692",},
|
||||
{},
|
||||
};
|
||||
/*MODULE_DEVICE_TABLE(of, ktd2692_dt_ids);*/
|
||||
#endif
|
||||
|
||||
static struct platform_driver ktd2692_driver = {
|
||||
.driver = {
|
||||
.name = ktd2692_NAME,
|
||||
.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_OF
|
||||
.of_match_table = ktd2692_dt_ids,
|
||||
#endif
|
||||
},
|
||||
.probe = ktd2692_probe,
|
||||
.remove = ktd2692_remove,
|
||||
};
|
||||
|
||||
static int __init ktd2692_init(void)
|
||||
{
|
||||
return platform_driver_register(&ktd2692_driver);
|
||||
}
|
||||
|
||||
static void __exit ktd2692_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&ktd2692_driver);
|
||||
}
|
||||
|
||||
module_init(ktd2692_init);
|
||||
module_exit(ktd2692_exit);
|
||||
|
||||
MODULE_AUTHOR("sunggeun yim <sunggeun.yim@samsung.com.com>");
|
||||
MODULE_DESCRIPTION("KTD2692 driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
|
||||
503
drivers/leds/leds-lm3530.c
Normal file
503
drivers/leds/leds-lm3530.c
Normal file
|
|
@ -0,0 +1,503 @@
|
|||
/*
|
||||
* 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 struct attribute *lm3530_attrs[] = {
|
||||
&dev_attr_mode.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(lm3530);
|
||||
|
||||
static int lm3530_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
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;
|
||||
drvdata->led_dev.groups = lm3530_groups;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lm3530_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lm3530_data *drvdata = i2c_get_clientdata(client);
|
||||
|
||||
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>");
|
||||
780
drivers/leds/leds-lm3533.c
Normal file
780
drivers/leds/leds-lm3533.c
Normal file
|
|
@ -0,0 +1,780 @@
|
|||
/*
|
||||
* 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 const struct attribute_group *lm3533_led_attribute_groups[] = {
|
||||
&lm3533_led_attribute_group,
|
||||
NULL
|
||||
};
|
||||
|
||||
static int lm3533_led_setup(struct lm3533_led *led,
|
||||
struct lm3533_led_platform_data *pdata)
|
||||
{
|
||||
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->cdev.groups = lm3533_led_attribute_groups,
|
||||
led->id = pdev->id;
|
||||
|
||||
mutex_init(&led->mutex);
|
||||
INIT_WORK(&led->work, lm3533_led_work);
|
||||
|
||||
/* The class framework makes a callback to get brightness during
|
||||
* registration so use parent device (for error reporting) until
|
||||
* 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 = lm3533_led_setup(led, pdata);
|
||||
if (ret)
|
||||
goto err_unregister;
|
||||
|
||||
ret = lm3533_ctrlbank_enable(&led->cb);
|
||||
if (ret)
|
||||
goto err_unregister;
|
||||
|
||||
return 0;
|
||||
|
||||
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);
|
||||
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");
|
||||
571
drivers/leds/leds-lm355x.c
Normal file
571
drivers/leds/leds-lm355x.c
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
/*
|
||||
* 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 struct attribute *lm355x_indicator_attrs[] = {
|
||||
&dev_attr_pattern.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(lm355x_indicator);
|
||||
|
||||
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;
|
||||
/* indicator pattern control only for LM3556 */
|
||||
if (id->driver_data == CHIP_LM3556)
|
||||
chip->cdev_indicator.groups = lm355x_indicator_groups;
|
||||
err = led_classdev_register((struct device *)
|
||||
&client->dev, &chip->cdev_indicator);
|
||||
if (err < 0)
|
||||
goto err_create_indicator_file;
|
||||
|
||||
dev_info(&client->dev, "%s is initialized\n",
|
||||
lm355x_name[id->driver_data]);
|
||||
return 0;
|
||||
|
||||
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);
|
||||
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");
|
||||
462
drivers/leds/leds-lm3642.c
Normal file
462
drivers/leds/leds-lm3642.c
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
* 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 struct attribute *lm3642_flash_attrs[] = {
|
||||
&dev_attr_strobe_pin.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(lm3642_flash);
|
||||
|
||||
static struct attribute *lm3642_torch_attrs[] = {
|
||||
&dev_attr_torch_pin.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(lm3642_torch);
|
||||
|
||||
static int lm3642_probe(struct i2c_client *client,
|
||||
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";
|
||||
chip->cdev_flash.groups = lm3642_flash_groups,
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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";
|
||||
chip->cdev_torch.groups = lm3642_torch_groups,
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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:
|
||||
led_classdev_unregister(&chip->cdev_torch);
|
||||
err_create_torch_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);
|
||||
led_classdev_unregister(&chip->cdev_torch);
|
||||
flush_work(&chip->work_torch);
|
||||
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");
|
||||
97
drivers/leds/leds-locomo.c
Normal file
97
drivers/leds/leds-locomo.c
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* linux/drivers/leds/leds-locomo.c
|
||||
*
|
||||
* Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#include <mach/hardware.h>
|
||||
#include <asm/hardware/locomo.h>
|
||||
|
||||
static void locomoled_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value, int offset)
|
||||
{
|
||||
struct locomo_dev *locomo_dev = LOCOMO_DEV(led_cdev->dev->parent);
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_save(flags);
|
||||
if (value)
|
||||
locomo_writel(LOCOMO_LPT_TOFH, locomo_dev->mapbase + offset);
|
||||
else
|
||||
locomo_writel(LOCOMO_LPT_TOFL, locomo_dev->mapbase + offset);
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void locomoled_brightness_set0(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
locomoled_brightness_set(led_cdev, value, LOCOMO_LPT0);
|
||||
}
|
||||
|
||||
static void locomoled_brightness_set1(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
locomoled_brightness_set(led_cdev, value, LOCOMO_LPT1);
|
||||
}
|
||||
|
||||
static struct led_classdev locomo_led0 = {
|
||||
.name = "locomo:amber:charge",
|
||||
.default_trigger = "main-battery-charging",
|
||||
.brightness_set = locomoled_brightness_set0,
|
||||
};
|
||||
|
||||
static struct led_classdev locomo_led1 = {
|
||||
.name = "locomo:green:mail",
|
||||
.default_trigger = "nand-disk",
|
||||
.brightness_set = locomoled_brightness_set1,
|
||||
};
|
||||
|
||||
static int locomoled_probe(struct locomo_dev *ldev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = led_classdev_register(&ldev->dev, &locomo_led0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = led_classdev_register(&ldev->dev, &locomo_led1);
|
||||
if (ret < 0)
|
||||
led_classdev_unregister(&locomo_led0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int locomoled_remove(struct locomo_dev *dev)
|
||||
{
|
||||
led_classdev_unregister(&locomo_led0);
|
||||
led_classdev_unregister(&locomo_led1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct locomo_driver locomoled_driver = {
|
||||
.drv = {
|
||||
.name = "locomoled"
|
||||
},
|
||||
.devid = LOCOMO_DEVID_LED,
|
||||
.probe = locomoled_probe,
|
||||
.remove = locomoled_remove,
|
||||
};
|
||||
|
||||
static int __init locomoled_init(void)
|
||||
{
|
||||
return locomo_driver_register(&locomoled_driver);
|
||||
}
|
||||
module_init(locomoled_init);
|
||||
|
||||
MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>");
|
||||
MODULE_DESCRIPTION("Locomo LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
459
drivers/leds/leds-lp3944.c
Normal file
459
drivers/leds/leds-lp3944.c
Normal file
|
|
@ -0,0 +1,459 @@
|
|||
/*
|
||||
* leds-lp3944.c - driver for National Semiconductor LP3944 Funlight Chip
|
||||
*
|
||||
* Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* I2C driver for National Semiconductor LP3944 Funlight Chip
|
||||
* http://www.national.com/pf/LP/LP3944.html
|
||||
*
|
||||
* This helper chip can drive up to 8 leds, with two programmable DIM modes;
|
||||
* it could even be used as a gpio expander but this driver assumes it is used
|
||||
* as a led controller.
|
||||
*
|
||||
* The DIM modes are used to set _blink_ patterns for leds, the pattern is
|
||||
* specified supplying two parameters:
|
||||
* - period: from 0s to 1.6s
|
||||
* - duty cycle: percentage of the period the led is on, from 0 to 100
|
||||
*
|
||||
* LP3944 can be found on Motorola A910 smartphone, where it drives the rgb
|
||||
* leds, the camera flash light and the displays backlights.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds-lp3944.h>
|
||||
|
||||
/* Read Only Registers */
|
||||
#define LP3944_REG_INPUT1 0x00 /* LEDs 0-7 InputRegister (Read Only) */
|
||||
#define LP3944_REG_REGISTER1 0x01 /* None (Read Only) */
|
||||
|
||||
#define LP3944_REG_PSC0 0x02 /* Frequency Prescaler 0 (R/W) */
|
||||
#define LP3944_REG_PWM0 0x03 /* PWM Register 0 (R/W) */
|
||||
#define LP3944_REG_PSC1 0x04 /* Frequency Prescaler 1 (R/W) */
|
||||
#define LP3944_REG_PWM1 0x05 /* PWM Register 1 (R/W) */
|
||||
#define LP3944_REG_LS0 0x06 /* LEDs 0-3 Selector (R/W) */
|
||||
#define LP3944_REG_LS1 0x07 /* LEDs 4-7 Selector (R/W) */
|
||||
|
||||
/* These registers are not used to control leds in LP3944, they can store
|
||||
* arbitrary values which the chip will ignore.
|
||||
*/
|
||||
#define LP3944_REG_REGISTER8 0x08
|
||||
#define LP3944_REG_REGISTER9 0x09
|
||||
|
||||
#define LP3944_DIM0 0
|
||||
#define LP3944_DIM1 1
|
||||
|
||||
/* period in ms */
|
||||
#define LP3944_PERIOD_MIN 0
|
||||
#define LP3944_PERIOD_MAX 1600
|
||||
|
||||
/* duty cycle is a percentage */
|
||||
#define LP3944_DUTY_CYCLE_MIN 0
|
||||
#define LP3944_DUTY_CYCLE_MAX 100
|
||||
|
||||
#define ldev_to_led(c) container_of(c, struct lp3944_led_data, ldev)
|
||||
|
||||
/* Saved data */
|
||||
struct lp3944_led_data {
|
||||
u8 id;
|
||||
enum lp3944_type type;
|
||||
enum lp3944_status status;
|
||||
struct led_classdev ldev;
|
||||
struct i2c_client *client;
|
||||
struct work_struct work;
|
||||
};
|
||||
|
||||
struct lp3944_data {
|
||||
struct mutex lock;
|
||||
struct i2c_client *client;
|
||||
struct lp3944_led_data leds[LP3944_LEDS_MAX];
|
||||
};
|
||||
|
||||
static int lp3944_reg_read(struct i2c_client *client, u8 reg, u8 *value)
|
||||
{
|
||||
int tmp;
|
||||
|
||||
tmp = i2c_smbus_read_byte_data(client, reg);
|
||||
if (tmp < 0)
|
||||
return tmp;
|
||||
|
||||
*value = tmp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lp3944_reg_write(struct i2c_client *client, u8 reg, u8 value)
|
||||
{
|
||||
return i2c_smbus_write_byte_data(client, reg, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the period for DIM status
|
||||
*
|
||||
* @client: the i2c client
|
||||
* @dim: either LP3944_DIM0 or LP3944_DIM1
|
||||
* @period: period of a blink, that is a on/off cycle, expressed in ms.
|
||||
*/
|
||||
static int lp3944_dim_set_period(struct i2c_client *client, u8 dim, u16 period)
|
||||
{
|
||||
u8 psc_reg;
|
||||
u8 psc_value;
|
||||
int err;
|
||||
|
||||
if (dim == LP3944_DIM0)
|
||||
psc_reg = LP3944_REG_PSC0;
|
||||
else if (dim == LP3944_DIM1)
|
||||
psc_reg = LP3944_REG_PSC1;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* Convert period to Prescaler value */
|
||||
if (period > LP3944_PERIOD_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
psc_value = (period * 255) / LP3944_PERIOD_MAX;
|
||||
|
||||
err = lp3944_reg_write(client, psc_reg, psc_value);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the duty cycle for DIM status
|
||||
*
|
||||
* @client: the i2c client
|
||||
* @dim: either LP3944_DIM0 or LP3944_DIM1
|
||||
* @duty_cycle: percentage of a period during which a led is ON
|
||||
*/
|
||||
static int lp3944_dim_set_dutycycle(struct i2c_client *client, u8 dim,
|
||||
u8 duty_cycle)
|
||||
{
|
||||
u8 pwm_reg;
|
||||
u8 pwm_value;
|
||||
int err;
|
||||
|
||||
if (dim == LP3944_DIM0)
|
||||
pwm_reg = LP3944_REG_PWM0;
|
||||
else if (dim == LP3944_DIM1)
|
||||
pwm_reg = LP3944_REG_PWM1;
|
||||
else
|
||||
return -EINVAL;
|
||||
|
||||
/* Convert duty cycle to PWM value */
|
||||
if (duty_cycle > LP3944_DUTY_CYCLE_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
pwm_value = (duty_cycle * 255) / LP3944_DUTY_CYCLE_MAX;
|
||||
|
||||
err = lp3944_reg_write(client, pwm_reg, pwm_value);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the led status
|
||||
*
|
||||
* @led: a lp3944_led_data structure
|
||||
* @status: one of LP3944_LED_STATUS_OFF
|
||||
* LP3944_LED_STATUS_ON
|
||||
* LP3944_LED_STATUS_DIM0
|
||||
* LP3944_LED_STATUS_DIM1
|
||||
*/
|
||||
static int lp3944_led_set(struct lp3944_led_data *led, u8 status)
|
||||
{
|
||||
struct lp3944_data *data = i2c_get_clientdata(led->client);
|
||||
u8 id = led->id;
|
||||
u8 reg;
|
||||
u8 val = 0;
|
||||
int err;
|
||||
|
||||
dev_dbg(&led->client->dev, "%s: %s, status before normalization:%d\n",
|
||||
__func__, led->ldev.name, status);
|
||||
|
||||
switch (id) {
|
||||
case LP3944_LED0:
|
||||
case LP3944_LED1:
|
||||
case LP3944_LED2:
|
||||
case LP3944_LED3:
|
||||
reg = LP3944_REG_LS0;
|
||||
break;
|
||||
case LP3944_LED4:
|
||||
case LP3944_LED5:
|
||||
case LP3944_LED6:
|
||||
case LP3944_LED7:
|
||||
id -= LP3944_LED4;
|
||||
reg = LP3944_REG_LS1;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (status > LP3944_LED_STATUS_DIM1)
|
||||
return -EINVAL;
|
||||
|
||||
/* invert only 0 and 1, leave unchanged the other values,
|
||||
* remember we are abusing status to set blink patterns
|
||||
*/
|
||||
if (led->type == LP3944_LED_TYPE_LED_INVERTED && status < 2)
|
||||
status = 1 - status;
|
||||
|
||||
mutex_lock(&data->lock);
|
||||
lp3944_reg_read(led->client, reg, &val);
|
||||
|
||||
val &= ~(LP3944_LED_STATUS_MASK << (id << 1));
|
||||
val |= (status << (id << 1));
|
||||
|
||||
dev_dbg(&led->client->dev, "%s: %s, reg:%d id:%d status:%d val:%#x\n",
|
||||
__func__, led->ldev.name, reg, id, status, val);
|
||||
|
||||
/* set led status */
|
||||
err = lp3944_reg_write(led->client, reg, val);
|
||||
mutex_unlock(&data->lock);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lp3944_led_set_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct lp3944_led_data *led = ldev_to_led(led_cdev);
|
||||
u16 period;
|
||||
u8 duty_cycle;
|
||||
int err;
|
||||
|
||||
/* units are in ms */
|
||||
if (*delay_on + *delay_off > LP3944_PERIOD_MAX)
|
||||
return -EINVAL;
|
||||
|
||||
if (*delay_on == 0 && *delay_off == 0) {
|
||||
/* Special case: the leds subsystem requires a default user
|
||||
* friendly blink pattern for the LED. Let's blink the led
|
||||
* slowly (1Hz).
|
||||
*/
|
||||
*delay_on = 500;
|
||||
*delay_off = 500;
|
||||
}
|
||||
|
||||
period = (*delay_on) + (*delay_off);
|
||||
|
||||
/* duty_cycle is the percentage of period during which the led is ON */
|
||||
duty_cycle = 100 * (*delay_on) / period;
|
||||
|
||||
/* invert duty cycle for inverted leds, this has the same effect of
|
||||
* swapping delay_on and delay_off
|
||||
*/
|
||||
if (led->type == LP3944_LED_TYPE_LED_INVERTED)
|
||||
duty_cycle = 100 - duty_cycle;
|
||||
|
||||
/* NOTE: using always the first DIM mode, this means that all leds
|
||||
* will have the same blinking pattern.
|
||||
*
|
||||
* We could find a way later to have two leds blinking in hardware
|
||||
* with different patterns at the same time, falling back to software
|
||||
* control for the other ones.
|
||||
*/
|
||||
err = lp3944_dim_set_period(led->client, LP3944_DIM0, period);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
err = lp3944_dim_set_dutycycle(led->client, LP3944_DIM0, duty_cycle);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
dev_dbg(&led->client->dev, "%s: OK hardware accelerated blink!\n",
|
||||
__func__);
|
||||
|
||||
led->status = LP3944_LED_STATUS_DIM0;
|
||||
schedule_work(&led->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lp3944_led_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
struct lp3944_led_data *led = ldev_to_led(led_cdev);
|
||||
|
||||
dev_dbg(&led->client->dev, "%s: %s, %d\n",
|
||||
__func__, led_cdev->name, brightness);
|
||||
|
||||
led->status = !!brightness;
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
static void lp3944_led_work(struct work_struct *work)
|
||||
{
|
||||
struct lp3944_led_data *led;
|
||||
|
||||
led = container_of(work, struct lp3944_led_data, work);
|
||||
lp3944_led_set(led, led->status);
|
||||
}
|
||||
|
||||
static int lp3944_configure(struct i2c_client *client,
|
||||
struct lp3944_data *data,
|
||||
struct lp3944_platform_data *pdata)
|
||||
{
|
||||
int i, err = 0;
|
||||
|
||||
for (i = 0; i < pdata->leds_size; i++) {
|
||||
struct lp3944_led *pled = &pdata->leds[i];
|
||||
struct lp3944_led_data *led = &data->leds[i];
|
||||
led->client = client;
|
||||
led->id = i;
|
||||
|
||||
switch (pled->type) {
|
||||
|
||||
case LP3944_LED_TYPE_LED:
|
||||
case LP3944_LED_TYPE_LED_INVERTED:
|
||||
led->type = pled->type;
|
||||
led->status = pled->status;
|
||||
led->ldev.name = pled->name;
|
||||
led->ldev.max_brightness = 1;
|
||||
led->ldev.brightness_set = lp3944_led_set_brightness;
|
||||
led->ldev.blink_set = lp3944_led_set_blink;
|
||||
led->ldev.flags = LED_CORE_SUSPENDRESUME;
|
||||
|
||||
INIT_WORK(&led->work, lp3944_led_work);
|
||||
err = led_classdev_register(&client->dev, &led->ldev);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"couldn't register LED %s\n",
|
||||
led->ldev.name);
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* to expose the default value to userspace */
|
||||
led->ldev.brightness =
|
||||
(enum led_brightness) led->status;
|
||||
|
||||
/* Set the default led status */
|
||||
err = lp3944_led_set(led, led->status);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"%s couldn't set STATUS %d\n",
|
||||
led->ldev.name, led->status);
|
||||
goto exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case LP3944_LED_TYPE_NONE:
|
||||
default:
|
||||
break;
|
||||
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
if (i > 0)
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
switch (pdata->leds[i].type) {
|
||||
|
||||
case LP3944_LED_TYPE_LED:
|
||||
case LP3944_LED_TYPE_LED_INVERTED:
|
||||
led_classdev_unregister(&data->leds[i].ldev);
|
||||
cancel_work_sync(&data->leds[i].work);
|
||||
break;
|
||||
|
||||
case LP3944_LED_TYPE_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int lp3944_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct lp3944_platform_data *lp3944_pdata =
|
||||
dev_get_platdata(&client->dev);
|
||||
struct lp3944_data *data;
|
||||
int err;
|
||||
|
||||
if (lp3944_pdata == NULL) {
|
||||
dev_err(&client->dev, "no platform data\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Let's see whether this adapter can support what we need. */
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(&client->dev, "insufficient functionality!\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
data = devm_kzalloc(&client->dev, sizeof(struct lp3944_data),
|
||||
GFP_KERNEL);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
|
||||
data->client = client;
|
||||
i2c_set_clientdata(client, data);
|
||||
|
||||
mutex_init(&data->lock);
|
||||
|
||||
err = lp3944_configure(client, data, lp3944_pdata);
|
||||
if (err < 0)
|
||||
return err;
|
||||
|
||||
dev_info(&client->dev, "lp3944 enabled\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lp3944_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lp3944_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
struct lp3944_data *data = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pdata->leds_size; i++)
|
||||
switch (data->leds[i].type) {
|
||||
case LP3944_LED_TYPE_LED:
|
||||
case LP3944_LED_TYPE_LED_INVERTED:
|
||||
led_classdev_unregister(&data->leds[i].ldev);
|
||||
cancel_work_sync(&data->leds[i].work);
|
||||
break;
|
||||
|
||||
case LP3944_LED_TYPE_NONE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* lp3944 i2c driver struct */
|
||||
static const struct i2c_device_id lp3944_id[] = {
|
||||
{"lp3944", 0},
|
||||
{}
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(i2c, lp3944_id);
|
||||
|
||||
static struct i2c_driver lp3944_driver = {
|
||||
.driver = {
|
||||
.name = "lp3944",
|
||||
},
|
||||
.probe = lp3944_probe,
|
||||
.remove = lp3944_remove,
|
||||
.id_table = lp3944_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(lp3944_driver);
|
||||
|
||||
MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
|
||||
MODULE_DESCRIPTION("LP3944 Fun Light Chip");
|
||||
MODULE_LICENSE("GPL");
|
||||
617
drivers/leds/leds-lp5521.c
Normal file
617
drivers/leds/leds-lp5521.c
Normal file
|
|
@ -0,0 +1,617 @@
|
|||
/*
|
||||
* 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
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/firmware.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/platform_data/leds-lp55xx.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include "leds-lp55xx-common.h"
|
||||
|
||||
#define LP5521_PROGRAM_LENGTH 32
|
||||
#define LP5521_MAX_LEDS 3
|
||||
#define LP5521_CMD_DIRECT 0x3F
|
||||
|
||||
/* Registers */
|
||||
#define LP5521_REG_ENABLE 0x00
|
||||
#define LP5521_REG_OP_MODE 0x01
|
||||
#define LP5521_REG_R_PWM 0x02
|
||||
#define LP5521_REG_G_PWM 0x03
|
||||
#define LP5521_REG_B_PWM 0x04
|
||||
#define LP5521_REG_R_CURRENT 0x05
|
||||
#define LP5521_REG_G_CURRENT 0x06
|
||||
#define LP5521_REG_B_CURRENT 0x07
|
||||
#define LP5521_REG_CONFIG 0x08
|
||||
#define LP5521_REG_STATUS 0x0C
|
||||
#define LP5521_REG_RESET 0x0D
|
||||
#define LP5521_REG_R_PROG_MEM 0x10
|
||||
#define LP5521_REG_G_PROG_MEM 0x30
|
||||
#define LP5521_REG_B_PROG_MEM 0x50
|
||||
|
||||
/* 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
|
||||
|
||||
/* Bits in ENABLE register */
|
||||
#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)
|
||||
|
||||
/* 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 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
|
||||
|
||||
/* 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)
|
||||
{
|
||||
/* operation mode change needs to be longer than 153 us */
|
||||
usleep_range(200, 300);
|
||||
}
|
||||
|
||||
static inline void lp5521_wait_enable_done(void)
|
||||
{
|
||||
/* it takes more 488 us to update ENABLE register */
|
||||
usleep_range(500, 600);
|
||||
}
|
||||
|
||||
static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current)
|
||||
{
|
||||
led->led_current = led_current;
|
||||
lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr,
|
||||
led_current);
|
||||
}
|
||||
|
||||
static void lp5521_load_engine(struct lp55xx_chip *chip)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 mask[] = {
|
||||
[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
|
||||
[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
|
||||
[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
|
||||
};
|
||||
|
||||
u8 val[] = {
|
||||
[LP55XX_ENGINE_1] = LP5521_LOAD_R,
|
||||
[LP55XX_ENGINE_2] = LP5521_LOAD_G,
|
||||
[LP55XX_ENGINE_3] = LP5521_LOAD_B,
|
||||
};
|
||||
|
||||
lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]);
|
||||
|
||||
lp5521_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5521_stop_all_engines(struct lp55xx_chip *chip)
|
||||
{
|
||||
lp55xx_write(chip, LP5521_REG_OP_MODE, 0);
|
||||
lp5521_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5521_stop_engine(struct lp55xx_chip *chip)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 mask[] = {
|
||||
[LP55XX_ENGINE_1] = LP5521_MODE_R_M,
|
||||
[LP55XX_ENGINE_2] = LP5521_MODE_G_M,
|
||||
[LP55XX_ENGINE_3] = LP5521_MODE_B_M,
|
||||
};
|
||||
|
||||
lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0);
|
||||
|
||||
lp5521_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5521_run_engine(struct lp55xx_chip *chip, bool start)
|
||||
{
|
||||
int ret;
|
||||
u8 mode;
|
||||
u8 exec;
|
||||
|
||||
/* stop engine */
|
||||
if (!start) {
|
||||
lp5521_stop_engine(chip);
|
||||
lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
|
||||
lp5521_wait_opmode_done();
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* To run the engine,
|
||||
* operation mode and enable register should updated at the same time
|
||||
*/
|
||||
|
||||
ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
/* change operation mode to RUN only when each engine is loading */
|
||||
if (LP5521_R_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R;
|
||||
exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R;
|
||||
}
|
||||
|
||||
if (LP5521_G_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G;
|
||||
exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G;
|
||||
}
|
||||
|
||||
if (LP5521_B_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B;
|
||||
exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B;
|
||||
}
|
||||
|
||||
lp55xx_write(chip, LP5521_REG_OP_MODE, mode);
|
||||
lp5521_wait_opmode_done();
|
||||
|
||||
lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec);
|
||||
lp5521_wait_enable_done();
|
||||
}
|
||||
|
||||
static int lp5521_update_program_memory(struct lp55xx_chip *chip,
|
||||
const u8 *data, size_t size)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 pattern[LP5521_PROGRAM_LENGTH] = {0};
|
||||
u8 addr[] = {
|
||||
[LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM,
|
||||
[LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM,
|
||||
[LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM,
|
||||
};
|
||||
unsigned cmd;
|
||||
char c[3];
|
||||
int nrchars;
|
||||
int ret;
|
||||
int offset = 0;
|
||||
int i = 0;
|
||||
|
||||
while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) {
|
||||
/* separate sscanfs because length is working only for %s */
|
||||
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
|
||||
if (ret != 1)
|
||||
goto err;
|
||||
|
||||
ret = sscanf(c, "%2x", &cmd);
|
||||
if (ret != 1)
|
||||
goto err;
|
||||
|
||||
pattern[i] = (u8)cmd;
|
||||
offset += nrchars;
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Each instruction is 16bit long. Check that length is even */
|
||||
if (i % 2)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) {
|
||||
ret = lp55xx_write(chip, addr[idx] + i, pattern[i]);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
err:
|
||||
dev_err(&chip->cl->dev, "wrong pattern format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void lp5521_firmware_loaded(struct lp55xx_chip *chip)
|
||||
{
|
||||
const struct firmware *fw = chip->fw;
|
||||
|
||||
if (fw->size > LP5521_PROGRAM_LENGTH) {
|
||||
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
|
||||
fw->size);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program momery sequence
|
||||
* 1) set engine mode to "LOAD"
|
||||
* 2) write firmware data into program memory
|
||||
*/
|
||||
|
||||
lp5521_load_engine(chip);
|
||||
lp5521_update_program_memory(chip, fw->data, fw->size);
|
||||
}
|
||||
|
||||
static int lp5521_post_init_device(struct lp55xx_chip *chip)
|
||||
{
|
||||
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);
|
||||
|
||||
/* Set all PWMs to direct control mode */
|
||||
ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT);
|
||||
|
||||
/* Update configuration for the clock setting */
|
||||
val = LP5521_DEFAULT_CFG;
|
||||
if (!lp55xx_is_extclk_used(chip))
|
||||
val |= LP5521_CLK_INT;
|
||||
|
||||
ret = lp55xx_write(chip, LP5521_REG_CONFIG, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* 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;
|
||||
|
||||
lp5521_wait_enable_done();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf)
|
||||
{
|
||||
struct lp55xx_platform_data *pdata = chip->pdata;
|
||||
int ret;
|
||||
u8 status;
|
||||
|
||||
ret = lp55xx_read(chip, LP5521_REG_STATUS, &status);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (pdata->clock_mode != LP55XX_CLOCK_EXT)
|
||||
return 0;
|
||||
|
||||
/* Check that ext clock is really in use if requested */
|
||||
if ((status & LP5521_EXT_CLK_USED) == 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lp5521_led_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
|
||||
brightness_work);
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr,
|
||||
led->brightness);
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
static ssize_t show_engine_mode(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
|
||||
|
||||
switch (mode) {
|
||||
case LP55XX_ENGINE_RUN:
|
||||
return sprintf(buf, "run\n");
|
||||
case LP55XX_ENGINE_LOAD:
|
||||
return sprintf(buf, "load\n");
|
||||
case LP55XX_ENGINE_DISABLED:
|
||||
default:
|
||||
return sprintf(buf, "disabled\n");
|
||||
}
|
||||
}
|
||||
show_mode(1)
|
||||
show_mode(2)
|
||||
show_mode(3)
|
||||
|
||||
static ssize_t store_engine_mode(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
struct lp55xx_engine *engine = &chip->engines[nr - 1];
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
chip->engine_idx = nr;
|
||||
|
||||
if (!strncmp(buf, "run", 3)) {
|
||||
lp5521_run_engine(chip, true);
|
||||
engine->mode = LP55XX_ENGINE_RUN;
|
||||
} else if (!strncmp(buf, "load", 4)) {
|
||||
lp5521_stop_engine(chip);
|
||||
lp5521_load_engine(chip);
|
||||
engine->mode = LP55XX_ENGINE_LOAD;
|
||||
} else if (!strncmp(buf, "disabled", 8)) {
|
||||
lp5521_stop_engine(chip);
|
||||
engine->mode = LP55XX_ENGINE_DISABLED;
|
||||
}
|
||||
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
store_mode(1)
|
||||
store_mode(2)
|
||||
store_mode(3)
|
||||
|
||||
static ssize_t store_engine_load(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
chip->engine_idx = nr;
|
||||
lp5521_load_engine(chip);
|
||||
ret = lp5521_update_program_memory(chip, buf, len);
|
||||
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
store_load(1)
|
||||
store_load(2)
|
||||
store_load(3)
|
||||
|
||||
static ssize_t lp5521_selftest(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
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 scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK");
|
||||
}
|
||||
|
||||
/* device attributes */
|
||||
static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode);
|
||||
static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode);
|
||||
static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode);
|
||||
static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load);
|
||||
static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load);
|
||||
static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load);
|
||||
static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest);
|
||||
|
||||
static struct attribute *lp5521_attributes[] = {
|
||||
&dev_attr_engine1_mode.attr,
|
||||
&dev_attr_engine2_mode.attr,
|
||||
&dev_attr_engine3_mode.attr,
|
||||
&dev_attr_engine1_load.attr,
|
||||
&dev_attr_engine2_load.attr,
|
||||
&dev_attr_engine3_load.attr,
|
||||
&dev_attr_selftest.attr,
|
||||
NULL
|
||||
};
|
||||
|
||||
static const struct attribute_group lp5521_group = {
|
||||
.attrs = lp5521_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_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 = &lp5521_cfg;
|
||||
|
||||
mutex_init(&chip->lock);
|
||||
|
||||
i2c_set_clientdata(client, led);
|
||||
|
||||
ret = lp55xx_init_device(chip);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
dev_info(&client->dev, "%s programmable led chip found\n", id->name);
|
||||
|
||||
ret = lp55xx_register_leds(led, chip);
|
||||
if (ret)
|
||||
goto err_register_leds;
|
||||
|
||||
ret = lp55xx_register_sysfs(chip);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "registering sysfs failed\n");
|
||||
goto err_register_sysfs;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_register_sysfs:
|
||||
lp55xx_unregister_leds(led, chip);
|
||||
err_register_leds:
|
||||
lp55xx_deinit_device(chip);
|
||||
err_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lp5521_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(client);
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
|
||||
lp5521_stop_all_engines(chip);
|
||||
lp55xx_unregister_sysfs(chip);
|
||||
lp55xx_unregister_leds(led, chip);
|
||||
lp55xx_deinit_device(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lp5521_id[] = {
|
||||
{ "lp5521", 0 }, /* Three channel chip */
|
||||
{ }
|
||||
};
|
||||
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,
|
||||
};
|
||||
|
||||
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");
|
||||
839
drivers/leds/leds-lp5523.c
Normal file
839
drivers/leds/leds-lp5523.c
Normal file
|
|
@ -0,0 +1,839 @@
|
|||
/*
|
||||
* lp5523.c - LP5523, LP55231 LED Driver
|
||||
*
|
||||
* Copyright (C) 2010 Nokia Corporation
|
||||
* Copyright (C) 2012 Texas Instruments
|
||||
*
|
||||
* Contact: Samu Onkalo <samu.p.onkalo@nokia.com>
|
||||
* Milo(Woogyom) Kim <milo.kim@ti.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* version 2 as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
|
||||
* 02110-1301 USA
|
||||
*/
|
||||
|
||||
#include <linux/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 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_ENABLE_LEDS_MSB 0x04
|
||||
#define LP5523_REG_ENABLE_LEDS_LSB 0x05
|
||||
#define LP5523_REG_LED_PWM_BASE 0x16
|
||||
#define LP5523_REG_LED_CURRENT_BASE 0x26
|
||||
#define LP5523_REG_CONFIG 0x36
|
||||
#define LP5523_REG_STATUS 0x3A
|
||||
#define LP5523_REG_RESET 0x3D
|
||||
#define LP5523_REG_LED_TEST_CTRL 0x41
|
||||
#define LP5523_REG_LED_TEST_ADC 0x42
|
||||
#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
|
||||
|
||||
/* 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_AUTO 0x18
|
||||
#define LP5523_AUTO_CLK 0x02
|
||||
|
||||
#define LP5523_EN_LEDTEST 0x80
|
||||
#define LP5523_LEDTEST_DONE 0x80
|
||||
#define LP5523_RESET 0xFF
|
||||
#define LP5523_ADC_SHORTCIRC_LIM 80
|
||||
#define LP5523_EXT_CLK_USED 0x08
|
||||
#define LP5523_ENG_STATUS_MASK 0x07
|
||||
|
||||
/* Memory Page Selection */
|
||||
#define LP5523_PAGE_ENG1 0
|
||||
#define LP5523_PAGE_ENG2 1
|
||||
#define LP5523_PAGE_ENG3 2
|
||||
#define LP5523_PAGE_MUX1 3
|
||||
#define LP5523_PAGE_MUX2 4
|
||||
#define LP5523_PAGE_MUX3 5
|
||||
|
||||
/* Program Memory Operations */
|
||||
#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */
|
||||
#define LP5523_MODE_ENG2_M 0x0C
|
||||
#define LP5523_MODE_ENG3_M 0x03
|
||||
#define LP5523_LOAD_ENG1 0x10
|
||||
#define LP5523_LOAD_ENG2 0x04
|
||||
#define LP5523_LOAD_ENG3 0x01
|
||||
|
||||
#define LP5523_ENG1_IS_LOADING(mode) \
|
||||
((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1)
|
||||
#define LP5523_ENG2_IS_LOADING(mode) \
|
||||
((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2)
|
||||
#define LP5523_ENG3_IS_LOADING(mode) \
|
||||
((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3)
|
||||
|
||||
#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */
|
||||
#define LP5523_EXEC_ENG2_M 0x0C
|
||||
#define LP5523_EXEC_ENG3_M 0x03
|
||||
#define LP5523_EXEC_M 0x3F
|
||||
#define LP5523_RUN_ENG1 0x20
|
||||
#define LP5523_RUN_ENG2 0x08
|
||||
#define LP5523_RUN_ENG3 0x02
|
||||
|
||||
#define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led)))
|
||||
|
||||
enum lp5523_chip_id {
|
||||
LP5523,
|
||||
LP55231,
|
||||
};
|
||||
|
||||
static int lp5523_init_program_engine(struct lp55xx_chip *chip);
|
||||
|
||||
static inline void lp5523_wait_opmode_done(void)
|
||||
{
|
||||
usleep_range(1000, 2000);
|
||||
}
|
||||
|
||||
static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current)
|
||||
{
|
||||
led->led_current = led_current;
|
||||
lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr,
|
||||
led_current);
|
||||
}
|
||||
|
||||
static int lp5523_post_init_device(struct lp55xx_chip *chip)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_ENABLE, LP5523_ENABLE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Chip startup time is 500 us, 1 - 2 ms gives some margin */
|
||||
usleep_range(1000, 2000);
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_CONFIG,
|
||||
LP5523_AUTO_INC | LP5523_PWR_SAVE |
|
||||
LP5523_CP_AUTO | LP5523_AUTO_CLK |
|
||||
LP5523_PWM_PWR_SAVE);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* turn on all leds */
|
||||
ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_MSB, 0x01);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_ENABLE_LEDS_LSB, 0xff);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return lp5523_init_program_engine(chip);
|
||||
}
|
||||
|
||||
static void lp5523_load_engine(struct lp55xx_chip *chip)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 mask[] = {
|
||||
[LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
|
||||
[LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
|
||||
[LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
|
||||
};
|
||||
|
||||
u8 val[] = {
|
||||
[LP55XX_ENGINE_1] = LP5523_LOAD_ENG1,
|
||||
[LP55XX_ENGINE_2] = LP5523_LOAD_ENG2,
|
||||
[LP55XX_ENGINE_3] = LP5523_LOAD_ENG3,
|
||||
};
|
||||
|
||||
lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]);
|
||||
|
||||
lp5523_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 page_sel[] = {
|
||||
[LP55XX_ENGINE_1] = LP5523_PAGE_ENG1,
|
||||
[LP55XX_ENGINE_2] = LP5523_PAGE_ENG2,
|
||||
[LP55XX_ENGINE_3] = LP5523_PAGE_ENG3,
|
||||
};
|
||||
|
||||
lp5523_load_engine(chip);
|
||||
|
||||
lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]);
|
||||
}
|
||||
|
||||
static void lp5523_stop_all_engines(struct lp55xx_chip *chip)
|
||||
{
|
||||
lp55xx_write(chip, LP5523_REG_OP_MODE, 0);
|
||||
lp5523_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5523_stop_engine(struct lp55xx_chip *chip)
|
||||
{
|
||||
enum lp55xx_engine_index idx = chip->engine_idx;
|
||||
u8 mask[] = {
|
||||
[LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M,
|
||||
[LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M,
|
||||
[LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M,
|
||||
};
|
||||
|
||||
lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0);
|
||||
|
||||
lp5523_wait_opmode_done();
|
||||
}
|
||||
|
||||
static void lp5523_turn_off_channels(struct lp55xx_chip *chip)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < LP5523_MAX_LEDS; i++)
|
||||
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0);
|
||||
}
|
||||
|
||||
static void lp5523_run_engine(struct lp55xx_chip *chip, bool start)
|
||||
{
|
||||
int ret;
|
||||
u8 mode;
|
||||
u8 exec;
|
||||
|
||||
/* stop engine */
|
||||
if (!start) {
|
||||
lp5523_stop_engine(chip);
|
||||
lp5523_turn_off_channels(chip);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* To run the engine,
|
||||
* operation mode and enable register should updated at the same time
|
||||
*/
|
||||
|
||||
ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
/* change operation mode to RUN only when each engine is loading */
|
||||
if (LP5523_ENG1_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1;
|
||||
exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1;
|
||||
}
|
||||
|
||||
if (LP5523_ENG2_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2;
|
||||
exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2;
|
||||
}
|
||||
|
||||
if (LP5523_ENG3_IS_LOADING(mode)) {
|
||||
mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3;
|
||||
exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3;
|
||||
}
|
||||
|
||||
lp55xx_write(chip, LP5523_REG_OP_MODE, mode);
|
||||
lp5523_wait_opmode_done();
|
||||
|
||||
lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec);
|
||||
}
|
||||
|
||||
static int lp5523_init_program_engine(struct lp55xx_chip *chip)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
int ret;
|
||||
u8 status;
|
||||
/* 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},
|
||||
};
|
||||
|
||||
/* hardcode 32 bytes of memory for each engine from program memory */
|
||||
ret = lp55xx_write(chip, LP5523_REG_CH1_PROG_START, 0x00);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_CH2_PROG_START, 0x10);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_CH3_PROG_START, 0x20);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* 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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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_err(&chip->cl->dev,
|
||||
"cound not configure LED engine, status = 0x%.2x\n",
|
||||
status);
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
out:
|
||||
lp5523_stop_all_engines(chip);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lp5523_update_program_memory(struct lp55xx_chip *chip,
|
||||
const u8 *data, size_t size)
|
||||
{
|
||||
u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
|
||||
unsigned cmd;
|
||||
char c[3];
|
||||
int nrchars;
|
||||
int ret;
|
||||
int offset = 0;
|
||||
int i = 0;
|
||||
|
||||
while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) {
|
||||
/* separate sscanfs because length is working only for %s */
|
||||
ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
|
||||
if (ret != 1)
|
||||
goto err;
|
||||
|
||||
ret = sscanf(c, "%2x", &cmd);
|
||||
if (ret != 1)
|
||||
goto err;
|
||||
|
||||
pattern[i] = (u8)cmd;
|
||||
offset += nrchars;
|
||||
i++;
|
||||
}
|
||||
|
||||
/* Each instruction is 16bit long. Check that length is even */
|
||||
if (i % 2)
|
||||
goto err;
|
||||
|
||||
for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) {
|
||||
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
|
||||
if (ret)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return size;
|
||||
|
||||
err:
|
||||
dev_err(&chip->cl->dev, "wrong pattern format\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
|
||||
{
|
||||
const struct firmware *fw = chip->fw;
|
||||
|
||||
if (fw->size > LP5523_PROGRAM_LENGTH) {
|
||||
dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
|
||||
fw->size);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Program momery sequence
|
||||
* 1) set engine mode to "LOAD"
|
||||
* 2) write firmware data into program memory
|
||||
*/
|
||||
|
||||
lp5523_load_engine_and_select_page(chip);
|
||||
lp5523_update_program_memory(chip, fw->data, fw->size);
|
||||
}
|
||||
|
||||
static ssize_t show_engine_mode(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode;
|
||||
|
||||
switch (mode) {
|
||||
case LP55XX_ENGINE_RUN:
|
||||
return sprintf(buf, "run\n");
|
||||
case LP55XX_ENGINE_LOAD:
|
||||
return sprintf(buf, "load\n");
|
||||
case LP55XX_ENGINE_DISABLED:
|
||||
default:
|
||||
return sprintf(buf, "disabled\n");
|
||||
}
|
||||
}
|
||||
show_mode(1)
|
||||
show_mode(2)
|
||||
show_mode(3)
|
||||
|
||||
static ssize_t store_engine_mode(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
struct lp55xx_engine *engine = &chip->engines[nr - 1];
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
chip->engine_idx = nr;
|
||||
|
||||
if (!strncmp(buf, "run", 3)) {
|
||||
lp5523_run_engine(chip, true);
|
||||
engine->mode = LP55XX_ENGINE_RUN;
|
||||
} else if (!strncmp(buf, "load", 4)) {
|
||||
lp5523_stop_engine(chip);
|
||||
lp5523_load_engine(chip);
|
||||
engine->mode = LP55XX_ENGINE_LOAD;
|
||||
} else if (!strncmp(buf, "disabled", 8)) {
|
||||
lp5523_stop_engine(chip);
|
||||
engine->mode = LP55XX_ENGINE_DISABLED;
|
||||
}
|
||||
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return len;
|
||||
}
|
||||
store_mode(1)
|
||||
store_mode(2)
|
||||
store_mode(3)
|
||||
|
||||
static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
|
||||
{
|
||||
u16 tmp_mux = 0;
|
||||
int i;
|
||||
|
||||
len = min_t(int, len, LP5523_MAX_LEDS);
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
switch (buf[i]) {
|
||||
case '1':
|
||||
tmp_mux |= (1 << i);
|
||||
break;
|
||||
case '0':
|
||||
break;
|
||||
case '\n':
|
||||
i = len;
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
*mux = tmp_mux;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void lp5523_mux_to_array(u16 led_mux, char *array)
|
||||
{
|
||||
int i, pos = 0;
|
||||
for (i = 0; i < LP5523_MAX_LEDS; i++)
|
||||
pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
|
||||
|
||||
array[pos] = '\0';
|
||||
}
|
||||
|
||||
static ssize_t show_engine_leds(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
char *buf, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
char mux[LP5523_MAX_LEDS + 1];
|
||||
|
||||
lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
|
||||
|
||||
return sprintf(buf, "%s\n", mux);
|
||||
}
|
||||
show_leds(1)
|
||||
show_leds(2)
|
||||
show_leds(3)
|
||||
|
||||
static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
|
||||
{
|
||||
struct lp55xx_engine *engine = &chip->engines[nr - 1];
|
||||
int ret;
|
||||
u8 mux_page[] = {
|
||||
[LP55XX_ENGINE_1] = LP5523_PAGE_MUX1,
|
||||
[LP55XX_ENGINE_2] = LP5523_PAGE_MUX2,
|
||||
[LP55XX_ENGINE_3] = LP5523_PAGE_MUX3,
|
||||
};
|
||||
|
||||
lp5523_load_engine(chip);
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
engine->led_mux = mux;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t store_engine_leds(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t len, int nr)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
struct lp55xx_engine *engine = &chip->engines[nr - 1];
|
||||
u16 mux = 0;
|
||||
ssize_t ret;
|
||||
|
||||
if (lp5523_mux_parse(buf, &mux, len))
|
||||
return -EINVAL;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
chip->engine_idx = nr;
|
||||
ret = -EINVAL;
|
||||
|
||||
if (engine->mode != LP55XX_ENGINE_LOAD)
|
||||
goto leave;
|
||||
|
||||
if (lp5523_load_mux(chip, mux, nr))
|
||||
goto leave;
|
||||
|
||||
ret = len;
|
||||
leave:
|
||||
mutex_unlock(&chip->lock);
|
||||
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 lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
struct lp55xx_platform_data *pdata = chip->pdata;
|
||||
int i, ret, pos = 0;
|
||||
u8 status, adc, vdd;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
|
||||
ret = lp55xx_read(chip, LP5523_REG_STATUS, &status);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
/* Check that ext clock is really in use if requested */
|
||||
if (pdata->clock_mode == LP55XX_CLOCK_EXT) {
|
||||
if ((status & LP5523_EXT_CLK_USED) == 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
/* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */
|
||||
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, 6000); /* Was not ready. Wait little bit */
|
||||
|
||||
ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &vdd);
|
||||
if (ret < 0)
|
||||
goto fail;
|
||||
|
||||
vdd--; /* There may be some fluctuation in measurement */
|
||||
|
||||
for (i = 0; i < LP5523_MAX_LEDS; i++) {
|
||||
/* Skip non-existing channels */
|
||||
if (pdata->led_config[i].led_current == 0)
|
||||
continue;
|
||||
|
||||
/* Set default current */
|
||||
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);
|
||||
/* 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, 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);
|
||||
|
||||
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0x00);
|
||||
|
||||
/* Restore current */
|
||||
lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i,
|
||||
led->led_current);
|
||||
led++;
|
||||
}
|
||||
if (pos == 0)
|
||||
pos = sprintf(buf, "OK\n");
|
||||
goto release_lock;
|
||||
fail:
|
||||
pos = sprintf(buf, "FAIL\n");
|
||||
|
||||
release_lock:
|
||||
mutex_unlock(&chip->lock);
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
static void lp5523_led_brightness_work(struct work_struct *work)
|
||||
{
|
||||
struct lp55xx_led *led = container_of(work, struct lp55xx_led,
|
||||
brightness_work);
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
|
||||
mutex_lock(&chip->lock);
|
||||
lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr,
|
||||
led->brightness);
|
||||
mutex_unlock(&chip->lock);
|
||||
}
|
||||
|
||||
static 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_engine1_load.attr,
|
||||
&dev_attr_engine2_load.attr,
|
||||
&dev_attr_engine3_load.attr,
|
||||
&dev_attr_engine1_leds.attr,
|
||||
&dev_attr_engine2_leds.attr,
|
||||
&dev_attr_engine3_leds.attr,
|
||||
&dev_attr_selftest.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group lp5523_group = {
|
||||
.attrs = lp5523_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_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 = &lp5523_cfg;
|
||||
|
||||
mutex_init(&chip->lock);
|
||||
|
||||
i2c_set_clientdata(client, led);
|
||||
|
||||
ret = lp55xx_init_device(chip);
|
||||
if (ret)
|
||||
goto err_init;
|
||||
|
||||
dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
|
||||
|
||||
ret = lp55xx_register_leds(led, chip);
|
||||
if (ret)
|
||||
goto err_register_leds;
|
||||
|
||||
ret = lp55xx_register_sysfs(chip);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "registering sysfs failed\n");
|
||||
goto err_register_sysfs;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_register_sysfs:
|
||||
lp55xx_unregister_leds(led, chip);
|
||||
err_register_leds:
|
||||
lp55xx_deinit_device(chip);
|
||||
err_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lp5523_remove(struct i2c_client *client)
|
||||
{
|
||||
struct lp55xx_led *led = i2c_get_clientdata(client);
|
||||
struct lp55xx_chip *chip = led->chip;
|
||||
|
||||
lp5523_stop_all_engines(chip);
|
||||
lp55xx_unregister_sysfs(chip);
|
||||
lp55xx_unregister_leds(led, chip);
|
||||
lp55xx_deinit_device(chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id lp5523_id[] = {
|
||||
{ "lp5523", LP5523 },
|
||||
{ "lp55231", LP55231 },
|
||||
{ }
|
||||
};
|
||||
|
||||
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 = "lp5523x",
|
||||
.of_match_table = of_match_ptr(of_lp5523_leds_match),
|
||||
},
|
||||
.probe = lp5523_probe,
|
||||
.remove = lp5523_remove,
|
||||
.id_table = lp5523_id,
|
||||
};
|
||||
|
||||
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");
|
||||
617
drivers/leds/leds-lp5562.c
Normal file
617
drivers/leds/leds-lp5562.c
Normal file
|
|
@ -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");
|
||||
599
drivers/leds/leds-lp55xx-common.c
Normal file
599
drivers/leds/leds-lp55xx-common.c
Normal file
|
|
@ -0,0 +1,599 @@
|
|||
/*
|
||||
* 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_attrs[] = {
|
||||
&dev_attr_led_current.attr,
|
||||
&dev_attr_max_current.attr,
|
||||
NULL,
|
||||
};
|
||||
ATTRIBUTE_GROUPS(lp55xx_led);
|
||||
|
||||
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;
|
||||
led->cdev.groups = lp55xx_led_groups;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
ret = led_classdev_register(dev, &led->cdev);
|
||||
if (ret) {
|
||||
dev_err(dev, "led register err: %d\n", ret);
|
||||
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");
|
||||
208
drivers/leds/leds-lp55xx-common.h
Normal file
208
drivers/leds/leds-lp55xx-common.h
Normal file
|
|
@ -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 */
|
||||
411
drivers/leds/leds-lp8501.c
Normal file
411
drivers/leds/leds-lp8501.c
Normal file
|
|
@ -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");
|
||||
194
drivers/leds/leds-lp8788.c
Normal file
194
drivers/leds/leds-lp8788.c
Normal file
|
|
@ -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");
|
||||
196
drivers/leds/leds-lt3593.c
Normal file
196
drivers/leds/leds-lt3593.c
Normal file
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* LEDs driver for LT3593 controllers
|
||||
*
|
||||
* See the datasheet at http://cds.linear.com/docs/Datasheet/3593f.pdf
|
||||
*
|
||||
* Copyright (c) 2009 Daniel Mack <daniel@caiaq.de>
|
||||
*
|
||||
* Based on leds-gpio.c,
|
||||
*
|
||||
* Copyright (C) 2007 8D Technologies inc.
|
||||
* Raphael Assenat <raph@8d.com>
|
||||
* Copyright (C) 2008 Freescale Semiconductor, Inc.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.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;
|
||||
unsigned gpio;
|
||||
struct work_struct work;
|
||||
u8 new_level;
|
||||
};
|
||||
|
||||
static void lt3593_led_work(struct work_struct *work)
|
||||
{
|
||||
int pulses;
|
||||
struct lt3593_led_data *led_dat =
|
||||
container_of(work, struct lt3593_led_data, work);
|
||||
|
||||
/*
|
||||
* The LT3593 resets its internal current level register to the maximum
|
||||
* level on the first falling edge on the control pin. Each following
|
||||
* falling edge decreases the current level by 625uA. Up to 32 pulses
|
||||
* can be sent, so the maximum power reduction is 20mA.
|
||||
* After a timeout of 128us, the value is taken from the register and
|
||||
* applied is to the output driver.
|
||||
*/
|
||||
|
||||
if (led_dat->new_level == 0) {
|
||||
gpio_set_value_cansleep(led_dat->gpio, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
pulses = 32 - (led_dat->new_level * 32) / 255;
|
||||
|
||||
if (pulses == 0) {
|
||||
gpio_set_value_cansleep(led_dat->gpio, 0);
|
||||
mdelay(1);
|
||||
gpio_set_value_cansleep(led_dat->gpio, 1);
|
||||
return;
|
||||
}
|
||||
|
||||
gpio_set_value_cansleep(led_dat->gpio, 1);
|
||||
|
||||
while (pulses--) {
|
||||
gpio_set_value_cansleep(led_dat->gpio, 0);
|
||||
udelay(1);
|
||||
gpio_set_value_cansleep(led_dat->gpio, 1);
|
||||
udelay(1);
|
||||
}
|
||||
}
|
||||
|
||||
static void lt3593_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct lt3593_led_data *led_dat =
|
||||
container_of(led_cdev, struct lt3593_led_data, cdev);
|
||||
|
||||
led_dat->new_level = value;
|
||||
schedule_work(&led_dat->work);
|
||||
}
|
||||
|
||||
static int create_lt3593_led(const struct gpio_led *template,
|
||||
struct lt3593_led_data *led_dat, struct device *parent)
|
||||
{
|
||||
int ret, state;
|
||||
|
||||
/* skip leds on GPIOs that aren't available */
|
||||
if (!gpio_is_valid(template->gpio)) {
|
||||
dev_info(parent, "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n",
|
||||
KBUILD_MODNAME, template->gpio, template->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
led_dat->cdev.name = template->name;
|
||||
led_dat->cdev.default_trigger = template->default_trigger;
|
||||
led_dat->gpio = template->gpio;
|
||||
|
||||
led_dat->cdev.brightness_set = lt3593_led_set;
|
||||
|
||||
state = (template->default_state == LEDS_GPIO_DEFSTATE_ON);
|
||||
led_dat->cdev.brightness = state ? LED_FULL : LED_OFF;
|
||||
|
||||
if (!template->retain_state_suspended)
|
||||
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
|
||||
ret = devm_gpio_request_one(parent, template->gpio, state ?
|
||||
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
|
||||
template->name);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
INIT_WORK(&led_dat->work, lt3593_led_work);
|
||||
|
||||
ret = led_classdev_register(parent, &led_dat->cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_info(parent, "%s: registered LT3593 LED '%s' at GPIO %d\n",
|
||||
KBUILD_MODNAME, template->name, template->gpio);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void delete_lt3593_led(struct lt3593_led_data *led)
|
||||
{
|
||||
if (!gpio_is_valid(led->gpio))
|
||||
return;
|
||||
|
||||
led_classdev_unregister(&led->cdev);
|
||||
cancel_work_sync(&led->work);
|
||||
}
|
||||
|
||||
static int lt3593_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct lt3593_led_data *leds_data;
|
||||
int i, ret = 0;
|
||||
|
||||
if (!pdata)
|
||||
return -EBUSY;
|
||||
|
||||
leds_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct lt3593_led_data) * pdata->num_leds,
|
||||
GFP_KERNEL);
|
||||
if (!leds_data)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
ret = create_lt3593_led(&pdata->leds[i], &leds_data[i],
|
||||
&pdev->dev);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, leds_data);
|
||||
|
||||
return 0;
|
||||
|
||||
err:
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
delete_lt3593_led(&leds_data[i]);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int lt3593_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct lt3593_led_data *leds_data;
|
||||
|
||||
leds_data = platform_get_drvdata(pdev);
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++)
|
||||
delete_lt3593_led(&leds_data[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver lt3593_led_driver = {
|
||||
.probe = lt3593_led_probe,
|
||||
.remove = lt3593_led_remove,
|
||||
.driver = {
|
||||
.name = "leds-lt3593",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
317
drivers/leds/leds-max8997.c
Normal file
317
drivers/leds/leds-max8997.c
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* 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 struct attribute *max8997_attrs[] = {
|
||||
&dev_attr_mode.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(max8997);
|
||||
|
||||
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->cdev.groups = max8997_groups;
|
||||
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;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max8997_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct max8997_led *led = platform_get_drvdata(pdev);
|
||||
|
||||
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");
|
||||
337
drivers/leds/leds-mc13783.c
Normal file
337
drivers/leds/leds-mc13783.c
Normal file
|
|
@ -0,0 +1,337 @@
|
|||
/*
|
||||
* LEDs driver for Freescale MC13783/MC13892/MC34708
|
||||
*
|
||||
* Copyright (C) 2010 Philippe Rétornaz
|
||||
*
|
||||
* Based on leds-da903x:
|
||||
* Copyright (C) 2008 Compulab, Ltd.
|
||||
* Mike Rapoport <mike@compulab.co.il>
|
||||
*
|
||||
* Copyright (C) 2006-2008 Marvell International Ltd.
|
||||
* Eric Miao <eric.miao@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/mfd/mc13xxx.h>
|
||||
|
||||
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;
|
||||
enum led_brightness new_brightness;
|
||||
int id;
|
||||
struct mc13xxx_leds *leds;
|
||||
};
|
||||
|
||||
struct mc13xxx_leds {
|
||||
struct mc13xxx *master;
|
||||
struct mc13xxx_led_devtype *devtype;
|
||||
int num_leds;
|
||||
struct mc13xxx_led *led;
|
||||
};
|
||||
|
||||
static unsigned int mc13xxx_max_brightness(int id)
|
||||
{
|
||||
if (id >= MC13783_LED_MD && id <= MC13783_LED_KP)
|
||||
return 0x0f;
|
||||
else if (id >= MC13783_LED_R1 && id <= MC13783_LED_B3)
|
||||
return 0x1f;
|
||||
|
||||
return 0x3f;
|
||||
}
|
||||
|
||||
static void mc13xxx_led_work(struct work_struct *work)
|
||||
{
|
||||
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:
|
||||
case MC13783_LED_AD:
|
||||
case MC13783_LED_KP:
|
||||
reg = 2;
|
||||
shift = 9 + (led->id - MC13783_LED_MD) * 4;
|
||||
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 = 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();
|
||||
}
|
||||
|
||||
mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg,
|
||||
mc13xxx_max_brightness(led->id) << shift,
|
||||
led->new_brightness << 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);
|
||||
|
||||
led->new_brightness = value;
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt(
|
||||
struct platform_device *pdev)
|
||||
{
|
||||
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;
|
||||
|
||||
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
of_node_get(dev->parent->of_node);
|
||||
|
||||
parent = of_find_node_by_name(dev->parent->of_node, "leds");
|
||||
if (!parent)
|
||||
goto out_node_put;
|
||||
|
||||
ret = of_property_read_u32_array(parent, "led-control",
|
||||
pdata->led_control,
|
||||
leds->devtype->num_regs);
|
||||
if (ret)
|
||||
goto out_node_put;
|
||||
|
||||
pdata->num_leds = of_get_child_count(parent);
|
||||
|
||||
pdata->led = devm_kzalloc(dev, pdata->num_leds * sizeof(*pdata->led),
|
||||
GFP_KERNEL);
|
||||
if (!pdata->led) {
|
||||
ret = -ENOMEM;
|
||||
goto out_node_put;
|
||||
}
|
||||
|
||||
for_each_child_of_node(parent, child) {
|
||||
const char *str;
|
||||
u32 tmp;
|
||||
|
||||
if (of_property_read_u32(child, "reg", &tmp))
|
||||
continue;
|
||||
pdata->led[i].id = leds->devtype->led_min + tmp;
|
||||
|
||||
if (!of_property_read_string(child, "label", &str))
|
||||
pdata->led[i].name = str;
|
||||
if (!of_property_read_string(child, "linux,default-trigger",
|
||||
&str))
|
||||
pdata->led[i].default_trigger = str;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
pdata->num_leds = i;
|
||||
ret = i > 0 ? 0 : -ENODATA;
|
||||
|
||||
out_node_put:
|
||||
of_node_put(parent);
|
||||
|
||||
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
|
||||
|
||||
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;
|
||||
|
||||
leds->devtype = devtype;
|
||||
leds->master = mcdev;
|
||||
platform_set_drvdata(pdev, leds);
|
||||
|
||||
if (dev->parent->of_node) {
|
||||
pdata = mc13xxx_led_probe_dt(pdev);
|
||||
if (IS_ERR(pdata))
|
||||
return PTR_ERR(pdata);
|
||||
} else if (!pdata)
|
||||
return -ENODATA;
|
||||
|
||||
leds->num_leds = pdata->num_leds;
|
||||
|
||||
if ((leds->num_leds < 1) ||
|
||||
(leds->num_leds > (devtype->led_max - devtype->led_min + 1))) {
|
||||
dev_err(dev, "Invalid LED count %d\n", leds->num_leds);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
leds->led = devm_kzalloc(dev, leds->num_leds * sizeof(*leds->led),
|
||||
GFP_KERNEL);
|
||||
if (!leds->led)
|
||||
return -ENOMEM;
|
||||
|
||||
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 < leds->num_leds; i++) {
|
||||
const char *name, *trig;
|
||||
|
||||
ret = -EINVAL;
|
||||
|
||||
id = pdata->led[i].id;
|
||||
name = pdata->led[i].name;
|
||||
trig = pdata->led[i].default_trigger;
|
||||
|
||||
if ((id > devtype->led_max) || (id < devtype->led_min)) {
|
||||
dev_err(dev, "Invalid ID %i\n", id);
|
||||
break;
|
||||
}
|
||||
|
||||
if (init_led & (1 << id)) {
|
||||
dev_warn(dev, "LED %i already initialized\n", id);
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
INIT_WORK(&leds->led[i].work, mc13xxx_led_work);
|
||||
|
||||
ret = led_classdev_register(dev->parent, &leds->led[i].cdev);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register LED %i\n", id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret)
|
||||
while (--i >= 0) {
|
||||
led_classdev_unregister(&leds->led[i].cdev);
|
||||
cancel_work_sync(&leds->led[i].work);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int mc13xxx_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct mc13xxx_leds *leds = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < leds->num_leds; i++) {
|
||||
led_classdev_unregister(&leds->led[i].cdev);
|
||||
cancel_work_sync(&leds->led[i].work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct mc13xxx_led_devtype mc13783_led_devtype = {
|
||||
.led_min = MC13783_LED_MD,
|
||||
.led_max = MC13783_LED_B3,
|
||||
.num_regs = 6,
|
||||
.ledctrl_base = 51,
|
||||
};
|
||||
|
||||
static const struct mc13xxx_led_devtype mc13892_led_devtype = {
|
||||
.led_min = MC13892_LED_MD,
|
||||
.led_max = MC13892_LED_B,
|
||||
.num_regs = 4,
|
||||
.ledctrl_base = 51,
|
||||
};
|
||||
|
||||
static const struct mc13xxx_led_devtype mc34708_led_devtype = {
|
||||
.led_min = MC34708_LED_R,
|
||||
.led_max = MC34708_LED_G,
|
||||
.num_regs = 1,
|
||||
.ledctrl_base = 54,
|
||||
};
|
||||
|
||||
static const struct platform_device_id mc13xxx_led_id_table[] = {
|
||||
{ "mc13783-led", (kernel_ulong_t)&mc13783_led_devtype, },
|
||||
{ "mc13892-led", (kernel_ulong_t)&mc13892_led_devtype, },
|
||||
{ "mc34708-led", (kernel_ulong_t)&mc34708_led_devtype, },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(platform, mc13xxx_led_id_table);
|
||||
|
||||
static struct platform_driver mc13xxx_led_driver = {
|
||||
.driver = {
|
||||
.name = "mc13xxx-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.remove = mc13xxx_led_remove,
|
||||
.id_table = mc13xxx_led_id_table,
|
||||
};
|
||||
module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe);
|
||||
|
||||
MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC");
|
||||
MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>");
|
||||
MODULE_LICENSE("GPL");
|
||||
131
drivers/leds/leds-menf21bmc.c
Normal file
131
drivers/leds/leds-menf21bmc.c
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
* MEN 14F021P00 Board Management Controller (BMC) LEDs Driver.
|
||||
*
|
||||
* This is the core LED driver of the MEN 14F021P00 BMC.
|
||||
* There are four LEDs available which can be switched on and off.
|
||||
* STATUS LED, HOT SWAP LED, USER LED 1, USER LED 2
|
||||
*
|
||||
* Copyright (C) 2014 MEN Mikro Elektronik Nuernberg GmbH
|
||||
*
|
||||
* 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/i2c.h>
|
||||
|
||||
#define BMC_CMD_LED_GET_SET 0xA0
|
||||
#define BMC_BIT_LED_STATUS BIT(0)
|
||||
#define BMC_BIT_LED_HOTSWAP BIT(1)
|
||||
#define BMC_BIT_LED_USER1 BIT(2)
|
||||
#define BMC_BIT_LED_USER2 BIT(3)
|
||||
|
||||
struct menf21bmc_led {
|
||||
struct led_classdev cdev;
|
||||
u8 led_bit;
|
||||
const char *name;
|
||||
struct i2c_client *i2c_client;
|
||||
};
|
||||
|
||||
static struct menf21bmc_led leds[] = {
|
||||
{
|
||||
.name = "menf21bmc:led_status",
|
||||
.led_bit = BMC_BIT_LED_STATUS,
|
||||
},
|
||||
{
|
||||
.name = "menf21bmc:led_hotswap",
|
||||
.led_bit = BMC_BIT_LED_HOTSWAP,
|
||||
},
|
||||
{
|
||||
.name = "menf21bmc:led_user1",
|
||||
.led_bit = BMC_BIT_LED_USER1,
|
||||
},
|
||||
{
|
||||
.name = "menf21bmc:led_user2",
|
||||
.led_bit = BMC_BIT_LED_USER2,
|
||||
}
|
||||
};
|
||||
|
||||
static DEFINE_MUTEX(led_lock);
|
||||
|
||||
static void
|
||||
menf21bmc_led_set(struct led_classdev *led_cdev, enum led_brightness value)
|
||||
{
|
||||
int led_val;
|
||||
struct menf21bmc_led *led = container_of(led_cdev,
|
||||
struct menf21bmc_led, cdev);
|
||||
|
||||
mutex_lock(&led_lock);
|
||||
led_val = i2c_smbus_read_byte_data(led->i2c_client,
|
||||
BMC_CMD_LED_GET_SET);
|
||||
if (led_val < 0)
|
||||
goto err_out;
|
||||
|
||||
if (value == LED_OFF)
|
||||
led_val &= ~led->led_bit;
|
||||
else
|
||||
led_val |= led->led_bit;
|
||||
|
||||
i2c_smbus_write_byte_data(led->i2c_client,
|
||||
BMC_CMD_LED_GET_SET, led_val);
|
||||
err_out:
|
||||
mutex_unlock(&led_lock);
|
||||
}
|
||||
|
||||
static int menf21bmc_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
int ret;
|
||||
struct i2c_client *i2c_client = to_i2c_client(pdev->dev.parent);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(leds); i++) {
|
||||
leds[i].cdev.name = leds[i].name;
|
||||
leds[i].cdev.brightness_set = menf21bmc_led_set;
|
||||
leds[i].i2c_client = i2c_client;
|
||||
ret = led_classdev_register(&pdev->dev, &leds[i].cdev);
|
||||
if (ret < 0)
|
||||
goto err_free_leds;
|
||||
}
|
||||
dev_info(&pdev->dev, "MEN 140F21P00 BMC LED device enabled\n");
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_leds:
|
||||
dev_err(&pdev->dev, "failed to register LED device\n");
|
||||
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
led_classdev_unregister(&leds[i].cdev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int menf21bmc_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 menf21bmc_led = {
|
||||
.probe = menf21bmc_led_probe,
|
||||
.remove = menf21bmc_led_remove,
|
||||
.driver = {
|
||||
.name = "menf21bmc_led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(menf21bmc_led);
|
||||
|
||||
MODULE_AUTHOR("Andreas Werner <andreas.werner@men.de>");
|
||||
MODULE_DESCRIPTION("MEN 14F021P00 BMC led driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
MODULE_ALIAS("platform:menf21bmc_led");
|
||||
97
drivers/leds/leds-net48xx.c
Normal file
97
drivers/leds/leds-net48xx.c
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* LEDs driver for Soekris net48xx
|
||||
*
|
||||
* Copyright (C) 2006 Chris Boot <bootc@bootc.net>
|
||||
*
|
||||
* Based on leds-ams-delta.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/err.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
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static void net48xx_error_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
scx200_gpio_ops.gpio_set(NET48XX_ERROR_LED_GPIO, value ? 1 : 0);
|
||||
}
|
||||
|
||||
static struct led_classdev net48xx_error_led = {
|
||||
.name = "net48xx::error",
|
||||
.brightness_set = net48xx_error_led_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int net48xx_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
return led_classdev_register(&pdev->dev, &net48xx_error_led);
|
||||
}
|
||||
|
||||
static int net48xx_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&net48xx_error_led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver net48xx_led_driver = {
|
||||
.probe = net48xx_led_probe,
|
||||
.remove = net48xx_led_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init net48xx_led_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* small hack, but scx200_gpio doesn't set .dev if the probe fails */
|
||||
if (!scx200_gpio_ops.dev) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&net48xx_led_driver);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
ret = PTR_ERR(pdev);
|
||||
platform_driver_unregister(&net48xx_led_driver);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit net48xx_led_exit(void)
|
||||
{
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&net48xx_led_driver);
|
||||
}
|
||||
|
||||
module_init(net48xx_led_init);
|
||||
module_exit(net48xx_led_exit);
|
||||
|
||||
MODULE_AUTHOR("Chris Boot <bootc@bootc.net>");
|
||||
MODULE_DESCRIPTION("Soekris net48xx LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
416
drivers/leds/leds-netxbig.c
Normal file
416
drivers/leds/leds-netxbig.c
Normal file
|
|
@ -0,0 +1,416 @@
|
|||
/*
|
||||
* leds-netxbig.c - Driver for the 2Big and 5Big Network series LEDs
|
||||
*
|
||||
* Copyright (C) 2010 LaCie
|
||||
*
|
||||
* Author: Simon Guinot <sguinot@lacie.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/platform_data/leds-kirkwood-netxbig.h>
|
||||
|
||||
/*
|
||||
* GPIO extension bus.
|
||||
*/
|
||||
|
||||
static DEFINE_SPINLOCK(gpio_ext_lock);
|
||||
|
||||
static void gpio_ext_set_addr(struct netxbig_gpio_ext *gpio_ext, int addr)
|
||||
{
|
||||
int pin;
|
||||
|
||||
for (pin = 0; pin < gpio_ext->num_addr; pin++)
|
||||
gpio_set_value(gpio_ext->addr[pin], (addr >> pin) & 1);
|
||||
}
|
||||
|
||||
static void gpio_ext_set_data(struct netxbig_gpio_ext *gpio_ext, int data)
|
||||
{
|
||||
int pin;
|
||||
|
||||
for (pin = 0; pin < gpio_ext->num_data; pin++)
|
||||
gpio_set_value(gpio_ext->data[pin], (data >> pin) & 1);
|
||||
}
|
||||
|
||||
static void gpio_ext_enable_select(struct netxbig_gpio_ext *gpio_ext)
|
||||
{
|
||||
/* Enable select is done on the raising edge. */
|
||||
gpio_set_value(gpio_ext->enable, 0);
|
||||
gpio_set_value(gpio_ext->enable, 1);
|
||||
}
|
||||
|
||||
static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext,
|
||||
int addr, int value)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&gpio_ext_lock, flags);
|
||||
gpio_ext_set_addr(gpio_ext, addr);
|
||||
gpio_ext_set_data(gpio_ext, value);
|
||||
gpio_ext_enable_select(gpio_ext);
|
||||
spin_unlock_irqrestore(&gpio_ext_lock, flags);
|
||||
}
|
||||
|
||||
static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext)
|
||||
{
|
||||
int err;
|
||||
int i;
|
||||
|
||||
if (unlikely(!gpio_ext))
|
||||
return -EINVAL;
|
||||
|
||||
/* Configure address GPIOs. */
|
||||
for (i = 0; i < gpio_ext->num_addr; i++) {
|
||||
err = gpio_request_one(gpio_ext->addr[i], GPIOF_OUT_INIT_LOW,
|
||||
"GPIO extension addr");
|
||||
if (err)
|
||||
goto err_free_addr;
|
||||
}
|
||||
/* Configure data GPIOs. */
|
||||
for (i = 0; i < gpio_ext->num_data; i++) {
|
||||
err = gpio_request_one(gpio_ext->data[i], GPIOF_OUT_INIT_LOW,
|
||||
"GPIO extension data");
|
||||
if (err)
|
||||
goto err_free_data;
|
||||
}
|
||||
/* Configure "enable select" GPIO. */
|
||||
err = gpio_request_one(gpio_ext->enable, GPIOF_OUT_INIT_LOW,
|
||||
"GPIO extension enable");
|
||||
if (err)
|
||||
goto err_free_data;
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_data:
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
gpio_free(gpio_ext->data[i]);
|
||||
i = gpio_ext->num_addr;
|
||||
err_free_addr:
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
gpio_free(gpio_ext->addr[i]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext)
|
||||
{
|
||||
int i;
|
||||
|
||||
gpio_free(gpio_ext->enable);
|
||||
for (i = gpio_ext->num_addr - 1; i >= 0; i--)
|
||||
gpio_free(gpio_ext->addr[i]);
|
||||
for (i = gpio_ext->num_data - 1; i >= 0; i--)
|
||||
gpio_free(gpio_ext->data[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Class LED driver.
|
||||
*/
|
||||
|
||||
struct netxbig_led_data {
|
||||
struct netxbig_gpio_ext *gpio_ext;
|
||||
struct led_classdev cdev;
|
||||
int mode_addr;
|
||||
int *mode_val;
|
||||
int bright_addr;
|
||||
int bright_max;
|
||||
struct netxbig_led_timer *timer;
|
||||
int num_timer;
|
||||
enum netxbig_led_mode mode;
|
||||
int sata;
|
||||
spinlock_t lock;
|
||||
};
|
||||
|
||||
static int netxbig_led_get_timer_mode(enum netxbig_led_mode *mode,
|
||||
unsigned long delay_on,
|
||||
unsigned long delay_off,
|
||||
struct netxbig_led_timer *timer,
|
||||
int num_timer)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num_timer; i++) {
|
||||
if (timer[i].delay_on == delay_on &&
|
||||
timer[i].delay_off == delay_off) {
|
||||
*mode = timer[i].mode;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static int netxbig_led_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct netxbig_led_data *led_dat =
|
||||
container_of(led_cdev, struct netxbig_led_data, cdev);
|
||||
enum netxbig_led_mode mode;
|
||||
int mode_val;
|
||||
int ret;
|
||||
|
||||
/* Look for a LED mode with the requested timer frequency. */
|
||||
ret = netxbig_led_get_timer_mode(&mode, *delay_on, *delay_off,
|
||||
led_dat->timer, led_dat->num_timer);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
mode_val = led_dat->mode_val[mode];
|
||||
if (mode_val == NETXBIG_LED_INVALID_MODE)
|
||||
return -EINVAL;
|
||||
|
||||
spin_lock_irq(&led_dat->lock);
|
||||
|
||||
gpio_ext_set_value(led_dat->gpio_ext, led_dat->mode_addr, mode_val);
|
||||
led_dat->mode = mode;
|
||||
|
||||
spin_unlock_irq(&led_dat->lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void netxbig_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct netxbig_led_data *led_dat =
|
||||
container_of(led_cdev, struct netxbig_led_data, cdev);
|
||||
enum netxbig_led_mode mode;
|
||||
int mode_val, bright_val;
|
||||
int set_brightness = 1;
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led_dat->lock, flags);
|
||||
|
||||
if (value == LED_OFF) {
|
||||
mode = NETXBIG_LED_OFF;
|
||||
set_brightness = 0;
|
||||
} else {
|
||||
if (led_dat->sata)
|
||||
mode = NETXBIG_LED_SATA;
|
||||
else if (led_dat->mode == NETXBIG_LED_OFF)
|
||||
mode = NETXBIG_LED_ON;
|
||||
else /* Keep 'timer' mode. */
|
||||
mode = led_dat->mode;
|
||||
}
|
||||
mode_val = led_dat->mode_val[mode];
|
||||
|
||||
gpio_ext_set_value(led_dat->gpio_ext, led_dat->mode_addr, mode_val);
|
||||
led_dat->mode = mode;
|
||||
/*
|
||||
* Note that the brightness register is shared between all the
|
||||
* SATA LEDs. So, change the brightness setting for a single
|
||||
* SATA LED will affect all the others.
|
||||
*/
|
||||
if (set_brightness) {
|
||||
bright_val = DIV_ROUND_UP(value * led_dat->bright_max,
|
||||
LED_FULL);
|
||||
gpio_ext_set_value(led_dat->gpio_ext,
|
||||
led_dat->bright_addr, bright_val);
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&led_dat->lock, flags);
|
||||
}
|
||||
|
||||
static ssize_t netxbig_led_sata_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buff, size_t count)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct netxbig_led_data *led_dat =
|
||||
container_of(led_cdev, struct netxbig_led_data, cdev);
|
||||
unsigned long enable;
|
||||
enum netxbig_led_mode mode;
|
||||
int mode_val;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buff, 10, &enable);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
enable = !!enable;
|
||||
|
||||
spin_lock_irq(&led_dat->lock);
|
||||
|
||||
if (led_dat->sata == enable) {
|
||||
ret = count;
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
if (led_dat->mode != NETXBIG_LED_ON &&
|
||||
led_dat->mode != NETXBIG_LED_SATA)
|
||||
mode = led_dat->mode; /* Keep modes 'off' and 'timer'. */
|
||||
else if (enable)
|
||||
mode = NETXBIG_LED_SATA;
|
||||
else
|
||||
mode = NETXBIG_LED_ON;
|
||||
|
||||
mode_val = led_dat->mode_val[mode];
|
||||
if (mode_val == NETXBIG_LED_INVALID_MODE) {
|
||||
ret = -EINVAL;
|
||||
goto exit_unlock;
|
||||
}
|
||||
|
||||
gpio_ext_set_value(led_dat->gpio_ext, led_dat->mode_addr, mode_val);
|
||||
led_dat->mode = mode;
|
||||
led_dat->sata = enable;
|
||||
|
||||
ret = count;
|
||||
|
||||
exit_unlock:
|
||||
spin_unlock_irq(&led_dat->lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t netxbig_led_sata_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct netxbig_led_data *led_dat =
|
||||
container_of(led_cdev, struct netxbig_led_data, cdev);
|
||||
|
||||
return sprintf(buf, "%d\n", led_dat->sata);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(sata, 0644, netxbig_led_sata_show, netxbig_led_sata_store);
|
||||
|
||||
static struct attribute *netxbig_led_attrs[] = {
|
||||
&dev_attr_sata.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(netxbig_led);
|
||||
|
||||
static void delete_netxbig_led(struct netxbig_led_data *led_dat)
|
||||
{
|
||||
led_classdev_unregister(&led_dat->cdev);
|
||||
}
|
||||
|
||||
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 = dev_get_platdata(&pdev->dev);
|
||||
|
||||
spin_lock_init(&led_dat->lock);
|
||||
led_dat->gpio_ext = pdata->gpio_ext;
|
||||
led_dat->cdev.name = template->name;
|
||||
led_dat->cdev.default_trigger = template->default_trigger;
|
||||
led_dat->cdev.blink_set = netxbig_led_blink_set;
|
||||
led_dat->cdev.brightness_set = netxbig_led_set;
|
||||
/*
|
||||
* Because the GPIO extension bus don't allow to read registers
|
||||
* value, there is no way to probe the LED initial state.
|
||||
* So, the initial sysfs LED value for the "brightness" and "sata"
|
||||
* attributes are inconsistent.
|
||||
*
|
||||
* Note that the initial LED state can't be reconfigured.
|
||||
* The reason is that the LED behaviour must stay uniform during
|
||||
* the whole boot process (bootloader+linux).
|
||||
*/
|
||||
led_dat->sata = 0;
|
||||
led_dat->cdev.brightness = LED_OFF;
|
||||
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
led_dat->mode_addr = template->mode_addr;
|
||||
led_dat->mode_val = template->mode_val;
|
||||
led_dat->bright_addr = template->bright_addr;
|
||||
led_dat->bright_max = (1 << pdata->gpio_ext->num_data) - 1;
|
||||
led_dat->timer = pdata->timer;
|
||||
led_dat->num_timer = pdata->num_timer;
|
||||
/*
|
||||
* If available, expose the SATA activity blink capability through
|
||||
* a "sata" sysfs attribute.
|
||||
*/
|
||||
if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE)
|
||||
led_dat->cdev.groups = netxbig_led_groups;
|
||||
|
||||
return led_classdev_register(&pdev->dev, &led_dat->cdev);
|
||||
}
|
||||
|
||||
static int netxbig_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct netxbig_led_data *leds_data;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
|
||||
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)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++) {
|
||||
ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]);
|
||||
if (ret < 0)
|
||||
goto err_free_leds;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, leds_data);
|
||||
|
||||
return 0;
|
||||
|
||||
err_free_leds:
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
delete_netxbig_led(&leds_data[i]);
|
||||
|
||||
gpio_ext_free(pdata->gpio_ext);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int netxbig_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct netxbig_led_data *leds_data;
|
||||
int i;
|
||||
|
||||
leds_data = platform_get_drvdata(pdev);
|
||||
|
||||
for (i = 0; i < pdata->num_leds; i++)
|
||||
delete_netxbig_led(&leds_data[i]);
|
||||
|
||||
gpio_ext_free(pdata->gpio_ext);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver netxbig_led_driver = {
|
||||
.probe = netxbig_led_probe,
|
||||
.remove = netxbig_led_remove,
|
||||
.driver = {
|
||||
.name = "leds-netxbig",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
393
drivers/leds/leds-ns2.c
Normal file
393
drivers/leds/leds-ns2.c
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
|
||||
*
|
||||
* Copyright (C) 2010 LaCie
|
||||
*
|
||||
* Author: Simon Guinot <sguinot@lacie.com>
|
||||
*
|
||||
* Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/leds.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
|
||||
* relation with the SATA activity. This capability is exposed through the
|
||||
* "sata" sysfs attribute.
|
||||
*
|
||||
* The following array detail the different LED registers and the combination
|
||||
* of their possible values:
|
||||
*
|
||||
* cmd_led | slow_led | /SATA active | LED state
|
||||
* | | |
|
||||
* 1 | 0 | x | off
|
||||
* - | 1 | x | on
|
||||
* 0 | 0 | 1 | on
|
||||
* 0 | 0 | 0 | blink (rate 300ms)
|
||||
*/
|
||||
|
||||
enum ns2_led_modes {
|
||||
NS_V2_LED_OFF,
|
||||
NS_V2_LED_ON,
|
||||
NS_V2_LED_SATA,
|
||||
};
|
||||
|
||||
struct ns2_led_mode_value {
|
||||
enum ns2_led_modes mode;
|
||||
int cmd_level;
|
||||
int slow_level;
|
||||
};
|
||||
|
||||
static struct ns2_led_mode_value ns2_led_modval[] = {
|
||||
{ NS_V2_LED_OFF , 1, 0 },
|
||||
{ NS_V2_LED_ON , 0, 1 },
|
||||
{ NS_V2_LED_ON , 1, 1 },
|
||||
{ NS_V2_LED_SATA, 0, 0 },
|
||||
};
|
||||
|
||||
struct ns2_led_data {
|
||||
struct led_classdev cdev;
|
||||
unsigned cmd;
|
||||
unsigned slow;
|
||||
unsigned char sata; /* True when SATA mode active. */
|
||||
rwlock_t rw_lock; /* Lock GPIOs. */
|
||||
};
|
||||
|
||||
static int ns2_led_get_mode(struct ns2_led_data *led_dat,
|
||||
enum ns2_led_modes *mode)
|
||||
{
|
||||
int i;
|
||||
int ret = -EINVAL;
|
||||
int cmd_level;
|
||||
int slow_level;
|
||||
|
||||
read_lock_irq(&led_dat->rw_lock);
|
||||
|
||||
cmd_level = gpio_get_value(led_dat->cmd);
|
||||
slow_level = gpio_get_value(led_dat->slow);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
|
||||
if (cmd_level == ns2_led_modval[i].cmd_level &&
|
||||
slow_level == ns2_led_modval[i].slow_level) {
|
||||
*mode = ns2_led_modval[i].mode;
|
||||
ret = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
read_unlock_irq(&led_dat->rw_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ns2_led_set_mode(struct ns2_led_data *led_dat,
|
||||
enum ns2_led_modes mode)
|
||||
{
|
||||
int i;
|
||||
unsigned long flags;
|
||||
|
||||
write_lock_irqsave(&led_dat->rw_lock, flags);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
|
||||
if (mode == ns2_led_modval[i].mode) {
|
||||
gpio_set_value(led_dat->cmd,
|
||||
ns2_led_modval[i].cmd_level);
|
||||
gpio_set_value(led_dat->slow,
|
||||
ns2_led_modval[i].slow_level);
|
||||
}
|
||||
}
|
||||
|
||||
write_unlock_irqrestore(&led_dat->rw_lock, flags);
|
||||
}
|
||||
|
||||
static void ns2_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct ns2_led_data *led_dat =
|
||||
container_of(led_cdev, struct ns2_led_data, cdev);
|
||||
enum ns2_led_modes mode;
|
||||
|
||||
if (value == LED_OFF)
|
||||
mode = NS_V2_LED_OFF;
|
||||
else if (led_dat->sata)
|
||||
mode = NS_V2_LED_SATA;
|
||||
else
|
||||
mode = NS_V2_LED_ON;
|
||||
|
||||
ns2_led_set_mode(led_dat, mode);
|
||||
}
|
||||
|
||||
static ssize_t ns2_led_sata_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buff, size_t count)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct ns2_led_data *led_dat =
|
||||
container_of(led_cdev, struct ns2_led_data, cdev);
|
||||
int ret;
|
||||
unsigned long enable;
|
||||
enum ns2_led_modes mode;
|
||||
|
||||
ret = kstrtoul(buff, 10, &enable);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
enable = !!enable;
|
||||
|
||||
if (led_dat->sata == enable)
|
||||
return count;
|
||||
|
||||
ret = ns2_led_get_mode(led_dat, &mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (enable && mode == NS_V2_LED_ON)
|
||||
ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
|
||||
if (!enable && mode == NS_V2_LED_SATA)
|
||||
ns2_led_set_mode(led_dat, NS_V2_LED_ON);
|
||||
|
||||
led_dat->sata = enable;
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t ns2_led_sata_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct ns2_led_data *led_dat =
|
||||
container_of(led_cdev, struct ns2_led_data, cdev);
|
||||
|
||||
return sprintf(buf, "%d\n", led_dat->sata);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);
|
||||
|
||||
static struct attribute *ns2_led_attrs[] = {
|
||||
&dev_attr_sata.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(ns2_led);
|
||||
|
||||
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 = 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 = devm_gpio_request_one(&pdev->dev, template->slow,
|
||||
gpio_get_value(template->slow) ?
|
||||
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
|
||||
template->name);
|
||||
if (ret) {
|
||||
dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
|
||||
template->name);
|
||||
return ret;
|
||||
}
|
||||
|
||||
rwlock_init(&led_dat->rw_lock);
|
||||
|
||||
led_dat->cdev.name = template->name;
|
||||
led_dat->cdev.default_trigger = template->default_trigger;
|
||||
led_dat->cdev.blink_set = NULL;
|
||||
led_dat->cdev.brightness_set = ns2_led_set;
|
||||
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
led_dat->cdev.groups = ns2_led_groups;
|
||||
led_dat->cmd = template->cmd;
|
||||
led_dat->slow = template->slow;
|
||||
|
||||
ret = ns2_led_get_mode(led_dat, &mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Set LED initial state. */
|
||||
led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
|
||||
led_dat->cdev.brightness =
|
||||
(mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void delete_ns2_led(struct ns2_led_data *led_dat)
|
||||
{
|
||||
led_classdev_unregister(&led_dat->cdev);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
/*
|
||||
* Translate OpenFirmware node properties into platform_data.
|
||||
*/
|
||||
static int
|
||||
ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
|
||||
{
|
||||
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_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++;
|
||||
}
|
||||
|
||||
pdata->leds = leds;
|
||||
pdata->num_leds = num_leds;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id of_ns2_leds_match[] = {
|
||||
{ .compatible = "lacie,ns2-leds", },
|
||||
{},
|
||||
};
|
||||
#endif /* CONFIG_OF_GPIO */
|
||||
|
||||
struct ns2_led_priv {
|
||||
int num_leds;
|
||||
struct ns2_led_data leds_data[];
|
||||
};
|
||||
|
||||
static inline int sizeof_ns2_led_priv(int num_leds)
|
||||
{
|
||||
return sizeof(struct ns2_led_priv) +
|
||||
(sizeof(struct ns2_led_data) * num_leds);
|
||||
}
|
||||
|
||||
static int ns2_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
struct ns2_led_priv *priv;
|
||||
int i;
|
||||
int ret;
|
||||
|
||||
#ifdef CONFIG_OF_GPIO
|
||||
if (!pdata) {
|
||||
pdata = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct ns2_led_platform_data),
|
||||
GFP_KERNEL);
|
||||
if (!pdata)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
if (!pdata)
|
||||
return -EINVAL;
|
||||
#endif /* CONFIG_OF_GPIO */
|
||||
|
||||
priv = devm_kzalloc(&pdev->dev,
|
||||
sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
priv->num_leds = pdata->num_leds;
|
||||
|
||||
for (i = 0; i < priv->num_leds; i++) {
|
||||
ret = create_ns2_led(pdev, &priv->leds_data[i],
|
||||
&pdata->leds[i]);
|
||||
if (ret < 0) {
|
||||
for (i = i - 1; i >= 0; i--)
|
||||
delete_ns2_led(&priv->leds_data[i]);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ns2_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
int i;
|
||||
struct ns2_led_priv *priv;
|
||||
|
||||
priv = platform_get_drvdata(pdev);
|
||||
|
||||
for (i = 0; i < priv->num_leds; i++)
|
||||
delete_ns2_led(&priv->leds_data[i]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver ns2_led_driver = {
|
||||
.probe = ns2_led_probe,
|
||||
.remove = ns2_led_remove,
|
||||
.driver = {
|
||||
.name = "leds-ns2",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(of_ns2_leds_match),
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
170
drivers/leds/leds-ot200.c
Normal file
170
drivers/leds/leds-ot200.c
Normal file
|
|
@ -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");
|
||||
483
drivers/leds/leds-pca9532.c
Normal file
483
drivers/leds/leds-pca9532.c
Normal file
|
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* pca9532.c - 16-bit Led dimmer
|
||||
*
|
||||
* Copyright (C) 2011 Jan Weitzel
|
||||
* Copyright (C) 2008 Riku Voipio
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; version 2 of the License.
|
||||
*
|
||||
* Datasheet: http://www.nxp.com/documents/data_sheet/PCA9532.pdf
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/input.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds-pca9532.h>
|
||||
#include <linux/gpio.h>
|
||||
|
||||
/* 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];
|
||||
};
|
||||
|
||||
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[] = {
|
||||
{ "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 = "leds-pca953x",
|
||||
},
|
||||
.probe = pca9532_probe,
|
||||
.remove = pca9532_remove,
|
||||
.id_table = pca9532_id,
|
||||
};
|
||||
|
||||
/* 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.
|
||||
* */
|
||||
static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
|
||||
enum led_brightness value)
|
||||
{
|
||||
int a = 0, b = 0, i = 0;
|
||||
struct pca9532_data *data = i2c_get_clientdata(client);
|
||||
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++;
|
||||
b += data->leds[i].ldev.brightness;
|
||||
}
|
||||
}
|
||||
if (a == 0) {
|
||||
dev_err(&client->dev,
|
||||
"fear of division by zero %d/%d, wanted %d\n",
|
||||
b, a, value);
|
||||
return -EINVAL;
|
||||
}
|
||||
b = b/a;
|
||||
if (b > 0xFF)
|
||||
return -EINVAL;
|
||||
data->pwm[pwm] = b;
|
||||
data->psc[pwm] = blink;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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(maxleds, pwm),
|
||||
data->pwm[pwm]);
|
||||
i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, pwm),
|
||||
data->psc[pwm]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Set LED routing */
|
||||
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(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(maxleds, led->id), reg);
|
||||
mutex_unlock(&data->update_lock);
|
||||
}
|
||||
|
||||
static void pca9532_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
int err = 0;
|
||||
struct pca9532_led *led = ldev_to_led(led_cdev);
|
||||
|
||||
if (value == LED_OFF)
|
||||
led->state = PCA9532_OFF;
|
||||
else if (value == LED_FULL)
|
||||
led->state = PCA9532_ON;
|
||||
else {
|
||||
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
|
||||
err = pca9532_calcpwm(led->client, 0, 0, value);
|
||||
if (err)
|
||||
return; /* XXX: led api doesn't allow error code? */
|
||||
}
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
static int pca9532_set_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on, unsigned long *delay_off)
|
||||
{
|
||||
struct pca9532_led *led = ldev_to_led(led_cdev);
|
||||
struct i2c_client *client = led->client;
|
||||
int psc;
|
||||
int err = 0;
|
||||
|
||||
if (*delay_on == 0 && *delay_off == 0) {
|
||||
/* led subsystem ask us for a blink rate */
|
||||
*delay_on = 1000;
|
||||
*delay_off = 1000;
|
||||
}
|
||||
if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6)
|
||||
return -EINVAL;
|
||||
|
||||
/* Thecus specific: only use PSC/PWM 0 */
|
||||
psc = (*delay_on * 152-1)/1000;
|
||||
err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness);
|
||||
if (err)
|
||||
return err;
|
||||
schedule_work(&led->work);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pca9532_event(struct input_dev *dev, unsigned int type,
|
||||
unsigned int code, int value)
|
||||
{
|
||||
struct pca9532_data *data = input_get_drvdata(dev);
|
||||
|
||||
if (!(type == EV_SND && (code == SND_BELL || code == SND_TONE)))
|
||||
return -1;
|
||||
|
||||
/* XXX: allow different kind of beeps with psc/pwm modifications */
|
||||
if (value > 1 && value < 32767)
|
||||
data->pwm[1] = 127;
|
||||
else
|
||||
data->pwm[1] = 0;
|
||||
|
||||
schedule_work(&data->work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pca9532_input_work(struct work_struct *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(maxleds, 1),
|
||||
data->pwm[1]);
|
||||
mutex_unlock(&data->update_lock);
|
||||
}
|
||||
|
||||
static void pca9532_led_work(struct work_struct *work)
|
||||
{
|
||||
struct pca9532_led *led;
|
||||
led = container_of(work, struct pca9532_led, work);
|
||||
if (led->state == PCA9532_PWM0)
|
||||
pca9532_setpwm(led->client, 0);
|
||||
pca9532_setled(led);
|
||||
}
|
||||
|
||||
#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)
|
||||
gpiochip_remove(&data->gpio);
|
||||
#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(maxleds, i),
|
||||
data->pwm[i]);
|
||||
i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(maxleds, i),
|
||||
data->psc[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;
|
||||
led->id = i;
|
||||
led->type = pled->type;
|
||||
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;
|
||||
led->ldev.name = led->name;
|
||||
led->ldev.brightness = LED_OFF;
|
||||
led->ldev.brightness_set = pca9532_set_brightness;
|
||||
led->ldev.blink_set = pca9532_set_blink;
|
||||
INIT_WORK(&led->work, pca9532_led_work);
|
||||
err = led_classdev_register(&client->dev, &led->ldev);
|
||||
if (err < 0) {
|
||||
dev_err(&client->dev,
|
||||
"couldn't register LED %s\n",
|
||||
led->name);
|
||||
goto exit;
|
||||
}
|
||||
pca9532_setled(led);
|
||||
break;
|
||||
case PCA9532_TYPE_N2100_BEEP:
|
||||
BUG_ON(data->idev);
|
||||
led->state = PCA9532_PWM1;
|
||||
pca9532_setled(led);
|
||||
data->idev = devm_input_allocate_device(&client->dev);
|
||||
if (data->idev == NULL) {
|
||||
err = -ENOMEM;
|
||||
goto exit;
|
||||
}
|
||||
data->idev->name = pled->name;
|
||||
data->idev->phys = "i2c/pca9532";
|
||||
data->idev->id.bustype = BUS_HOST;
|
||||
data->idev->id.vendor = 0x001f;
|
||||
data->idev->id.product = 0x0001;
|
||||
data->idev->id.version = 0x0100;
|
||||
data->idev->evbit[0] = BIT_MASK(EV_SND);
|
||||
data->idev->sndbit[0] = BIT_MASK(SND_BELL) |
|
||||
BIT_MASK(SND_TONE);
|
||||
data->idev->event = pca9532_event;
|
||||
input_set_drvdata(data->idev, data);
|
||||
INIT_WORK(&data->work, pca9532_input_work);
|
||||
err = input_register_device(data->idev);
|
||||
if (err) {
|
||||
cancel_work_sync(&data->work);
|
||||
data->idev = NULL;
|
||||
goto exit;
|
||||
}
|
||||
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:
|
||||
pca9532_destroy_devices(data, i);
|
||||
return err;
|
||||
}
|
||||
|
||||
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 =
|
||||
dev_get_platdata(&client->dev);
|
||||
|
||||
if (!pca9532_pdata)
|
||||
return -EIO;
|
||||
|
||||
if (!i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA))
|
||||
return -EIO;
|
||||
|
||||
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);
|
||||
|
||||
return pca9532_configure(client, data, pca9532_pdata);
|
||||
}
|
||||
|
||||
static int pca9532_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pca9532_data *data = i2c_get_clientdata(client);
|
||||
int err;
|
||||
|
||||
err = pca9532_destroy_devices(data, data->chip_info->num_leds);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
module_i2c_driver(pca9532_driver);
|
||||
|
||||
MODULE_AUTHOR("Riku Voipio");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_DESCRIPTION("PCA 9532 LED dimmer");
|
||||
393
drivers/leds/leds-pca955x.c
Normal file
393
drivers/leds/leds-pca955x.c
Normal file
|
|
@ -0,0 +1,393 @@
|
|||
/*
|
||||
* Copyright 2007-2008 Extreme Engineering Solutions, Inc.
|
||||
*
|
||||
* Author: Nate Case <ncase@xes-inc.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of version 2 of
|
||||
* the GNU General Public License. See the file COPYING in the main
|
||||
* directory of this archive for more details.
|
||||
*
|
||||
* LED driver for various PCA955x I2C LED drivers
|
||||
*
|
||||
* Supported devices:
|
||||
*
|
||||
* Device Description 7-bit slave address
|
||||
* ------ ----------- -------------------
|
||||
* PCA9550 2-bit driver 0x60 .. 0x61
|
||||
* PCA9551 8-bit driver 0x60 .. 0x67
|
||||
* PCA9552 16-bit driver 0x60 .. 0x67
|
||||
* PCA9553/01 4-bit driver 0x62
|
||||
* PCA9553/02 4-bit driver 0x63
|
||||
*
|
||||
* Philips PCA955x LED driver chips follow a register map as shown below:
|
||||
*
|
||||
* Control Register Description
|
||||
* ---------------- -----------
|
||||
* 0x0 Input register 0
|
||||
* ..
|
||||
* NUM_INPUT_REGS - 1 Last Input register X
|
||||
*
|
||||
* NUM_INPUT_REGS Frequency prescaler 0
|
||||
* NUM_INPUT_REGS + 1 PWM register 0
|
||||
* NUM_INPUT_REGS + 2 Frequency prescaler 1
|
||||
* NUM_INPUT_REGS + 3 PWM register 1
|
||||
*
|
||||
* NUM_INPUT_REGS + 4 LED selector 0
|
||||
* NUM_INPUT_REGS + 4
|
||||
* + NUM_LED_REGS - 1 Last LED selector
|
||||
*
|
||||
* where NUM_INPUT_REGS and NUM_LED_REGS vary depending on how many
|
||||
* bits the chip supports.
|
||||
*/
|
||||
|
||||
#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>
|
||||
|
||||
/* LED select registers determine the source that drives LED outputs */
|
||||
#define PCA955X_LS_LED_ON 0x0 /* Output LOW */
|
||||
#define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */
|
||||
#define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */
|
||||
#define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 rate */
|
||||
|
||||
enum pca955x_type {
|
||||
pca9550,
|
||||
pca9551,
|
||||
pca9552,
|
||||
pca9553,
|
||||
};
|
||||
|
||||
struct pca955x_chipdef {
|
||||
int bits;
|
||||
u8 slv_addr; /* 7-bit slave address mask */
|
||||
int slv_addr_shift; /* Number of bits to ignore */
|
||||
};
|
||||
|
||||
static struct pca955x_chipdef pca955x_chipdefs[] = {
|
||||
[pca9550] = {
|
||||
.bits = 2,
|
||||
.slv_addr = /* 110000x */ 0x60,
|
||||
.slv_addr_shift = 1,
|
||||
},
|
||||
[pca9551] = {
|
||||
.bits = 8,
|
||||
.slv_addr = /* 1100xxx */ 0x60,
|
||||
.slv_addr_shift = 3,
|
||||
},
|
||||
[pca9552] = {
|
||||
.bits = 16,
|
||||
.slv_addr = /* 1100xxx */ 0x60,
|
||||
.slv_addr_shift = 3,
|
||||
},
|
||||
[pca9553] = {
|
||||
.bits = 4,
|
||||
.slv_addr = /* 110001x */ 0x62,
|
||||
.slv_addr_shift = 1,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct i2c_device_id pca955x_id[] = {
|
||||
{ "pca9550", pca9550 },
|
||||
{ "pca9551", pca9551 },
|
||||
{ "pca9552", pca9552 },
|
||||
{ "pca9553", pca9553 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pca955x_id);
|
||||
|
||||
struct pca955x {
|
||||
struct mutex lock;
|
||||
struct pca955x_led *leds;
|
||||
struct pca955x_chipdef *chipdef;
|
||||
struct i2c_client *client;
|
||||
};
|
||||
|
||||
struct pca955x_led {
|
||||
struct pca955x *pca955x;
|
||||
struct work_struct work;
|
||||
enum led_brightness brightness;
|
||||
struct led_classdev led_cdev;
|
||||
int led_num; /* 0 .. 15 potentially */
|
||||
char name[32];
|
||||
};
|
||||
|
||||
/* 8 bits per input register */
|
||||
static inline int pca95xx_num_input_regs(int bits)
|
||||
{
|
||||
return (bits + 7) / 8;
|
||||
}
|
||||
|
||||
/* 4 bits per LED selector register */
|
||||
static inline int pca95xx_num_led_regs(int bits)
|
||||
{
|
||||
return (bits + 3) / 4;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return an LED selector register value based on an existing one, with
|
||||
* the appropriate 2-bit state value set for the given LED number (0-3).
|
||||
*/
|
||||
static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state)
|
||||
{
|
||||
return (oldval & (~(0x3 << (led_num << 1)))) |
|
||||
((state & 0x3) << (led_num << 1));
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to frequency prescaler register, used to program the
|
||||
* period of the PWM output. period = (PSCx + 1) / 38
|
||||
*/
|
||||
static void pca955x_write_psc(struct i2c_client *client, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
|
||||
i2c_smbus_write_byte_data(client,
|
||||
pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n,
|
||||
val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to PWM register, which determines the duty cycle of the
|
||||
* output. LED is OFF when the count is less than the value of this
|
||||
* register, and ON when it is greater. If PWMx == 0, LED is always OFF.
|
||||
*
|
||||
* Duty cycle is (256 - PWMx) / 256
|
||||
*/
|
||||
static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
|
||||
i2c_smbus_write_byte_data(client,
|
||||
pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n,
|
||||
val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Write to LED selector register, which determines the source that
|
||||
* drives the LED output.
|
||||
*/
|
||||
static void pca955x_write_ls(struct i2c_client *client, int n, u8 val)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
|
||||
i2c_smbus_write_byte_data(client,
|
||||
pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n,
|
||||
val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read the LED selector register, which determines the source that
|
||||
* drives the LED output.
|
||||
*/
|
||||
static u8 pca955x_read_ls(struct i2c_client *client, int n)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
|
||||
return (u8) i2c_smbus_read_byte_data(client,
|
||||
pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n);
|
||||
}
|
||||
|
||||
static void pca955x_led_work(struct work_struct *work)
|
||||
{
|
||||
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_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_led->brightness) {
|
||||
case LED_FULL:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON);
|
||||
break;
|
||||
case LED_OFF:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF);
|
||||
break;
|
||||
case LED_HALF:
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0);
|
||||
break;
|
||||
default:
|
||||
/*
|
||||
* Use PWM1 for all other values. This has the unwanted
|
||||
* side effect of making all LEDs on the chip share the
|
||||
* same brightness level if set to a value other than
|
||||
* OFF, HALF, or FULL. But, this is probably better than
|
||||
* just turning off for all other values.
|
||||
*/
|
||||
pca955x_write_pwm(pca955x->client, 1,
|
||||
255 - pca955x_led->brightness);
|
||||
ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1);
|
||||
break;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
struct pca955x_led *pca955x;
|
||||
|
||||
pca955x = container_of(led_cdev, struct pca955x_led, led_cdev);
|
||||
|
||||
pca955x->brightness = value;
|
||||
|
||||
/*
|
||||
* Must use workqueue for the actual I/O since I2C operations
|
||||
* can sleep.
|
||||
*/
|
||||
schedule_work(&pca955x->work);
|
||||
}
|
||||
|
||||
static int pca955x_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct pca955x *pca955x;
|
||||
struct pca955x_led *pca955x_led;
|
||||
struct pca955x_chipdef *chip;
|
||||
struct i2c_adapter *adapter;
|
||||
struct led_platform_data *pdata;
|
||||
int i, err;
|
||||
|
||||
chip = &pca955x_chipdefs[id->driver_data];
|
||||
adapter = to_i2c_adapter(client->dev.parent);
|
||||
pdata = 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)) !=
|
||||
chip->slv_addr) {
|
||||
dev_err(&client->dev, "invalid slave address %02x\n",
|
||||
client->addr);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at "
|
||||
"slave address 0x%02x\n",
|
||||
id->name, chip->bits, client->addr);
|
||||
|
||||
if (!i2c_check_functionality(adapter, I2C_FUNC_I2C))
|
||||
return -EIO;
|
||||
|
||||
if (pdata) {
|
||||
if (pdata->num_leds != chip->bits) {
|
||||
dev_err(&client->dev, "board info claims %d LEDs"
|
||||
" on a %d-bit chip\n",
|
||||
pdata->num_leds, chip->bits);
|
||||
return -ENODEV;
|
||||
}
|
||||
}
|
||||
|
||||
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_led = &pca955x->leds[i];
|
||||
pca955x_led->led_num = i;
|
||||
pca955x_led->pca955x = pca955x;
|
||||
|
||||
/* Platform data can specify LED names and default triggers */
|
||||
if (pdata) {
|
||||
if (pdata->leds[i].name)
|
||||
snprintf(pca955x_led->name,
|
||||
sizeof(pca955x_led->name), "pca955x:%s",
|
||||
pdata->leds[i].name);
|
||||
if (pdata->leds[i].default_trigger)
|
||||
pca955x_led->led_cdev.default_trigger =
|
||||
pdata->leds[i].default_trigger;
|
||||
} else {
|
||||
snprintf(pca955x_led->name, sizeof(pca955x_led->name),
|
||||
"pca955x:%d", i);
|
||||
}
|
||||
|
||||
pca955x_led->led_cdev.name = pca955x_led->name;
|
||||
pca955x_led->led_cdev.brightness_set = pca955x_led_set;
|
||||
|
||||
INIT_WORK(&pca955x_led->work, pca955x_led_work);
|
||||
|
||||
err = led_classdev_register(&client->dev,
|
||||
&pca955x_led->led_cdev);
|
||||
if (err < 0)
|
||||
goto exit;
|
||||
}
|
||||
|
||||
/* Turn off LEDs */
|
||||
for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++)
|
||||
pca955x_write_ls(client, i, 0x55);
|
||||
|
||||
/* PWM0 is used for half brightness or 50% duty cycle */
|
||||
pca955x_write_pwm(client, 0, 255-LED_HALF);
|
||||
|
||||
/* PWM1 is used for variable brightness, default to OFF */
|
||||
pca955x_write_pwm(client, 1, 0);
|
||||
|
||||
/* Set to fast frequency so we do not see flashing */
|
||||
pca955x_write_psc(client, 0, 0);
|
||||
pca955x_write_psc(client, 1, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
exit:
|
||||
while (i--) {
|
||||
led_classdev_unregister(&pca955x->leds[i].led_cdev);
|
||||
cancel_work_sync(&pca955x->leds[i].work);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int pca955x_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pca955x *pca955x = i2c_get_clientdata(client);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < pca955x->chipdef->bits; i++) {
|
||||
led_classdev_unregister(&pca955x->leds[i].led_cdev);
|
||||
cancel_work_sync(&pca955x->leds[i].work);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pca955x_driver = {
|
||||
.driver = {
|
||||
.name = "leds-pca955x",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = pca955x_probe,
|
||||
.remove = pca955x_remove,
|
||||
.id_table = pca955x_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(pca955x_driver);
|
||||
|
||||
MODULE_AUTHOR("Nate Case <ncase@xes-inc.com>");
|
||||
MODULE_DESCRIPTION("PCA955x LED driver");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
473
drivers/leds/leds-pca963x.c
Normal file
473
drivers/leds/leds-pca963x.c
Normal file
|
|
@ -0,0 +1,473 @@
|
|||
/*
|
||||
* 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/5 I2C LED driver (7-bit slave address set by hw.)
|
||||
*
|
||||
* Note that hardware blinking violates the leds infrastructure driver
|
||||
* interface since the hardware only supports blinking all LEDs with the
|
||||
* same delay_on/delay_off rates. That is, only the LEDs that are set to
|
||||
* blink will actually blink but all LEDs that are set to blink will blink
|
||||
* in identical fashion. The delay_on/delay_off values of the last LED
|
||||
* that is set to blink will be used for all of the blinking LEDs.
|
||||
* Hardware blinking is disabled by default but can be enabled by setting
|
||||
* the 'blink_type' member in the platform_data struct to 'PCA963X_HW_BLINK'
|
||||
* or by adding the 'nxp,hw-blink' property to the DTS.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/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,
|
||||
pca9635,
|
||||
};
|
||||
|
||||
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,
|
||||
},
|
||||
[pca9635] = {
|
||||
.grppwm = 0x12,
|
||||
.grpfreq = 0x13,
|
||||
.ledout_base = 0x14,
|
||||
.n_leds = 16,
|
||||
},
|
||||
};
|
||||
|
||||
/* Total blink period in milliseconds */
|
||||
#define PCA963X_BLINK_PERIOD_MIN 42
|
||||
#define PCA963X_BLINK_PERIOD_MAX 10667
|
||||
|
||||
static const struct i2c_device_id pca963x_id[] = {
|
||||
{ "pca9632", pca9633 },
|
||||
{ "pca9633", pca9633 },
|
||||
{ "pca9634", pca9634 },
|
||||
{ "pca9635", pca9635 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pca963x_id);
|
||||
|
||||
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 .. 15 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", ®);
|
||||
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", },
|
||||
{ .compatible = "nxp,pca9635", },
|
||||
{},
|
||||
};
|
||||
#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*/
|
||||
for (i = 0; i < chip->n_leds / 4; i++)
|
||||
i2c_smbus_write_byte_data(client, chip->ledout_base + i, 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);
|
||||
|
||||
if (pdata) {
|
||||
/* Configure output: open-drain or totem pole (push-pull) */
|
||||
if (pdata->outdrv == PCA963X_OPEN_DRAIN)
|
||||
i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x01);
|
||||
else
|
||||
i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x05);
|
||||
}
|
||||
|
||||
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");
|
||||
245
drivers/leds/leds-pwm.c
Normal file
245
drivers/leds/leds-pwm.c
Normal file
|
|
@ -0,0 +1,245 @@
|
|||
/*
|
||||
* linux/drivers/leds-pwm.c
|
||||
*
|
||||
* simple PWM based LED control
|
||||
*
|
||||
* Copyright 2009 Luotao Fu @ Pengutronix (l.fu@pengutronix.de)
|
||||
*
|
||||
* based on leds-gpio.c by Raphael Assenat <raph@8d.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.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;
|
||||
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 long long duty = led_dat->period;
|
||||
|
||||
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 inline size_t sizeof_pwm_leds_priv(int num_leds)
|
||||
{
|
||||
return sizeof(struct led_pwm_priv) +
|
||||
(sizeof(struct led_pwm_data) * num_leds);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (child)
|
||||
led_data->period = pwm_get_period(led_data->pwm);
|
||||
|
||||
led_data->can_sleep = pwm_can_sleep(led_data->pwm);
|
||||
if (led_data->can_sleep)
|
||||
INIT_WORK(&led_data->work, led_pwm_work);
|
||||
|
||||
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;
|
||||
|
||||
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 ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int led_pwm_probe(struct platform_device *pdev)
|
||||
{
|
||||
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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
led_pwm_cleanup(priv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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 = led_pwm_remove,
|
||||
.driver = {
|
||||
.name = "leds_pwm",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_pwm_leds_match,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(led_pwm_driver);
|
||||
|
||||
MODULE_AUTHOR("Luotao Fu <l.fu@pengutronix.de>");
|
||||
MODULE_DESCRIPTION("PWM LED driver for PXA");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:leds-pwm");
|
||||
65
drivers/leds/leds-rb532.c
Normal file
65
drivers/leds/leds-rb532.c
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* LEDs driver for the "User LED" on Routerboard532
|
||||
*
|
||||
* Copyright (C) 2009 Phil Sutter <n0-1@freewrt.org>
|
||||
*
|
||||
* Based on leds-cobalt-qube.c by Florian Fainelly and
|
||||
* rb-diag.c (my own standalone driver for both LED and
|
||||
* button of Routerboard532).
|
||||
*/
|
||||
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include <asm/mach-rc32434/gpio.h>
|
||||
#include <asm/mach-rc32434/rb.h>
|
||||
|
||||
static void rb532_led_set(struct led_classdev *cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
if (brightness)
|
||||
set_latch_u5(LO_ULED, 0);
|
||||
|
||||
else
|
||||
set_latch_u5(0, LO_ULED);
|
||||
}
|
||||
|
||||
static enum led_brightness rb532_led_get(struct led_classdev *cdev)
|
||||
{
|
||||
return (get_latch_u5() & LO_ULED) ? LED_FULL : LED_OFF;
|
||||
}
|
||||
|
||||
static struct led_classdev rb532_uled = {
|
||||
.name = "uled",
|
||||
.brightness_set = rb532_led_set,
|
||||
.brightness_get = rb532_led_get,
|
||||
.default_trigger = "nand-disk",
|
||||
};
|
||||
|
||||
static int rb532_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
return led_classdev_register(&pdev->dev, &rb532_uled);
|
||||
}
|
||||
|
||||
static int rb532_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&rb532_uled);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver rb532_led_driver = {
|
||||
.probe = rb532_led_probe,
|
||||
.remove = rb532_led_remove,
|
||||
.driver = {
|
||||
.name = "rb532-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
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");
|
||||
235
drivers/leds/leds-regulator.c
Normal file
235
drivers/leds/leds-regulator.c
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
/*
|
||||
* leds-regulator.c - LED class driver for regulator driven LEDs.
|
||||
*
|
||||
* Copyright (C) 2009 Antonio Ospite <ospite@studenti.unina.it>
|
||||
*
|
||||
* Inspired by leds-wm8350 driver.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/leds-regulator.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#define to_regulator_led(led_cdev) \
|
||||
container_of(led_cdev, struct regulator_led, cdev)
|
||||
|
||||
struct regulator_led {
|
||||
struct led_classdev cdev;
|
||||
enum led_brightness value;
|
||||
int enabled;
|
||||
struct mutex mutex;
|
||||
struct work_struct work;
|
||||
|
||||
struct regulator *vcc;
|
||||
};
|
||||
|
||||
static inline int led_regulator_get_max_brightness(struct regulator *supply)
|
||||
{
|
||||
int ret;
|
||||
int voltage = regulator_list_voltage(supply, 0);
|
||||
|
||||
if (voltage <= 0)
|
||||
return 1;
|
||||
|
||||
/* even if regulator can't change voltages,
|
||||
* we still assume it can change status
|
||||
* and the LED can be turned on and off.
|
||||
*/
|
||||
ret = regulator_set_voltage(supply, voltage, voltage);
|
||||
if (ret < 0)
|
||||
return 1;
|
||||
|
||||
return regulator_count_voltages(supply);
|
||||
}
|
||||
|
||||
static int led_regulator_get_voltage(struct regulator *supply,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
if (brightness == 0)
|
||||
return -EINVAL;
|
||||
|
||||
return regulator_list_voltage(supply, brightness - 1);
|
||||
}
|
||||
|
||||
|
||||
static void regulator_led_enable(struct regulator_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (led->enabled)
|
||||
return;
|
||||
|
||||
ret = regulator_enable(led->vcc);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to enable vcc: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
led->enabled = 1;
|
||||
}
|
||||
|
||||
static void regulator_led_disable(struct regulator_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!led->enabled)
|
||||
return;
|
||||
|
||||
ret = regulator_disable(led->vcc);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to disable vcc: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
led->enabled = 0;
|
||||
}
|
||||
|
||||
static void regulator_led_set_value(struct regulator_led *led)
|
||||
{
|
||||
int voltage;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
|
||||
if (led->value == LED_OFF) {
|
||||
regulator_led_disable(led);
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (led->cdev.max_brightness > 1) {
|
||||
voltage = led_regulator_get_voltage(led->vcc, led->value);
|
||||
dev_dbg(led->cdev.dev, "brightness: %d voltage: %d\n",
|
||||
led->value, voltage);
|
||||
|
||||
ret = regulator_set_voltage(led->vcc, voltage, voltage);
|
||||
if (ret != 0)
|
||||
dev_err(led->cdev.dev, "Failed to set voltage %d: %d\n",
|
||||
voltage, ret);
|
||||
}
|
||||
|
||||
regulator_led_enable(led);
|
||||
|
||||
out:
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
|
||||
static void led_work(struct work_struct *work)
|
||||
{
|
||||
struct regulator_led *led;
|
||||
|
||||
led = container_of(work, struct regulator_led, work);
|
||||
regulator_led_set_value(led);
|
||||
}
|
||||
|
||||
static void regulator_led_brightness_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct regulator_led *led = to_regulator_led(led_cdev);
|
||||
|
||||
led->value = value;
|
||||
schedule_work(&led->work);
|
||||
}
|
||||
|
||||
static int regulator_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct led_regulator_platform_data *pdata =
|
||||
dev_get_platdata(&pdev->dev);
|
||||
struct regulator_led *led;
|
||||
struct regulator *vcc;
|
||||
int ret = 0;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
vcc = regulator_get_exclusive(&pdev->dev, "vled");
|
||||
if (IS_ERR(vcc)) {
|
||||
dev_err(&pdev->dev, "Cannot get vcc for %s\n", pdata->name);
|
||||
return PTR_ERR(vcc);
|
||||
}
|
||||
|
||||
led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
|
||||
if (led == NULL) {
|
||||
ret = -ENOMEM;
|
||||
goto err_vcc;
|
||||
}
|
||||
|
||||
led->cdev.max_brightness = led_regulator_get_max_brightness(vcc);
|
||||
if (pdata->brightness > led->cdev.max_brightness) {
|
||||
dev_err(&pdev->dev, "Invalid default brightness %d\n",
|
||||
pdata->brightness);
|
||||
ret = -EINVAL;
|
||||
goto err_vcc;
|
||||
}
|
||||
led->value = pdata->brightness;
|
||||
|
||||
led->cdev.brightness_set = regulator_led_brightness_set;
|
||||
led->cdev.name = pdata->name;
|
||||
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);
|
||||
|
||||
platform_set_drvdata(pdev, led);
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &led->cdev);
|
||||
if (ret < 0) {
|
||||
cancel_work_sync(&led->work);
|
||||
goto err_vcc;
|
||||
}
|
||||
|
||||
/* to expose the default value to userspace */
|
||||
led->cdev.brightness = led->value;
|
||||
|
||||
/* Set the default led status */
|
||||
regulator_led_set_value(led);
|
||||
|
||||
return 0;
|
||||
|
||||
err_vcc:
|
||||
regulator_put(vcc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int regulator_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator_led *led = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&led->cdev);
|
||||
cancel_work_sync(&led->work);
|
||||
regulator_led_disable(led);
|
||||
regulator_put(led->vcc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver regulator_led_driver = {
|
||||
.driver = {
|
||||
.name = "leds-regulator",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = regulator_led_probe,
|
||||
.remove = regulator_led_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(regulator_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Antonio Ospite <ospite@studenti.unina.it>");
|
||||
MODULE_DESCRIPTION("Regulator driven LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:leds-regulator");
|
||||
525
drivers/leds/leds-s2mpb02.c
Normal file
525
drivers/leds/leds-s2mpb02.c
Normal file
|
|
@ -0,0 +1,525 @@
|
|||
/*
|
||||
* LED driver for Samsung S2MPB02
|
||||
*
|
||||
* Copyright (C) 2014 Samsung Electronics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This driver is based on leds-max77804.c
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/s2mpb02.h>
|
||||
#include <linux/mfd/s2mpb02-private.h>
|
||||
#include <linux/leds-s2mpb02.h>
|
||||
#include <linux/ctype.h>
|
||||
|
||||
extern struct class *camera_class; /*sys/class/camera*/
|
||||
struct device *s2mpb02_led_dev;
|
||||
struct s2mpb02_led_data *global_led_datas[S2MPB02_LED_MAX];
|
||||
|
||||
struct s2mpb02_led_data {
|
||||
struct led_classdev led;
|
||||
struct s2mpb02_dev *s2mpb02;
|
||||
struct s2mpb02_led *data;
|
||||
struct i2c_client *i2c;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
spinlock_t value_lock;
|
||||
int brightness;
|
||||
int test_brightness;
|
||||
};
|
||||
|
||||
static u8 leds_mask[S2MPB02_LED_MAX] = {
|
||||
S2MPB02_FLASH_MASK,
|
||||
S2MPB02_TORCH_MASK,
|
||||
};
|
||||
|
||||
static u8 leds_shift[S2MPB02_LED_MAX] = {
|
||||
4,
|
||||
0,
|
||||
};
|
||||
|
||||
u32 original_brightness;
|
||||
|
||||
static int s2mpb02_set_bits(struct i2c_client *client, const u8 reg,
|
||||
const u8 mask, const u8 inval)
|
||||
{
|
||||
int ret;
|
||||
u8 value;
|
||||
|
||||
ret = s2mpb02_read_reg(client, reg, &value);
|
||||
if (unlikely(ret < 0))
|
||||
return ret;
|
||||
|
||||
value = (value & ~mask) | (inval & mask);
|
||||
|
||||
ret = s2mpb02_write_reg(client, reg, value);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s2mpb02_led_get_en_value(struct s2mpb02_led_data *led_data, int on)
|
||||
{
|
||||
if (on) {
|
||||
if (led_data->data->id == S2MPB02_FLASH_LED_1)
|
||||
return ((S2MPB02_FLED_ENABLE
|
||||
<< S2MPB02_FLED_ENABLE_SHIFT) |
|
||||
(S2MPB02_FLED_FLASH_MODE
|
||||
<< S2MPB02_FLED_MODE_SHIFT));
|
||||
/* Turn on FLASH by I2C */
|
||||
else
|
||||
return ((S2MPB02_FLED_ENABLE
|
||||
<< S2MPB02_FLED_ENABLE_SHIFT) |
|
||||
(S2MPB02_FLED_TORCH_MODE
|
||||
<< S2MPB02_FLED_MODE_SHIFT));
|
||||
/* Turn on TORCH by I2C */
|
||||
} else
|
||||
return (S2MPB02_FLED_DISABLE
|
||||
<< S2MPB02_FLED_ENABLE_SHIFT);
|
||||
/* controlled by GPIO */
|
||||
}
|
||||
|
||||
static void s2mpb02_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct s2mpb02_led_data *led_data
|
||||
= container_of(led_cdev, struct s2mpb02_led_data, led);
|
||||
|
||||
pr_debug("[LED] %s\n", __func__);
|
||||
|
||||
spin_lock_irqsave(&led_data->value_lock, flags);
|
||||
led_data->data->brightness = min_t(int, (int)value,
|
||||
S2MPB02_FLASH_TORCH_CURRENT_MAX);
|
||||
spin_unlock_irqrestore(&led_data->value_lock, flags);
|
||||
|
||||
schedule_work(&led_data->work);
|
||||
}
|
||||
|
||||
static void led_set(struct s2mpb02_led_data *led_data)
|
||||
{
|
||||
int ret;
|
||||
struct s2mpb02_led *data = led_data->data;
|
||||
int id = data->id;
|
||||
int value;
|
||||
|
||||
if (led_data->data->brightness == LED_OFF) {
|
||||
value = s2mpb02_led_get_en_value(led_data, 0);
|
||||
ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CTRL1,
|
||||
S2MPB02_FLED_ENABLE_MODE_MASK, value);
|
||||
if (unlikely(ret))
|
||||
goto error_set_bits;
|
||||
|
||||
/* set current */
|
||||
ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1,
|
||||
leds_mask[id],
|
||||
data->brightness << leds_shift[id]);
|
||||
if (unlikely(ret))
|
||||
goto error_set_bits;
|
||||
} else {
|
||||
|
||||
/* set current */
|
||||
ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CUR1,
|
||||
leds_mask[id], data->brightness << leds_shift[id]);
|
||||
if (unlikely(ret))
|
||||
goto error_set_bits;
|
||||
|
||||
/* Turn on LED by I2C */
|
||||
value = s2mpb02_led_get_en_value(led_data, 1);
|
||||
ret = s2mpb02_set_bits(led_data->i2c, S2MPB02_REG_FLED_CTRL1,
|
||||
S2MPB02_FLED_ENABLE_MODE_MASK, value);
|
||||
if (unlikely(ret))
|
||||
goto error_set_bits;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
error_set_bits:
|
||||
pr_err("%s: can't set led level %d\n", __func__, ret);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
static void s2mpb02_led_work(struct work_struct *work)
|
||||
{
|
||||
struct s2mpb02_led_data *led_data
|
||||
= container_of(work, struct s2mpb02_led_data, work);
|
||||
|
||||
pr_debug("[LED] %s\n", __func__);
|
||||
|
||||
mutex_lock(&led_data->lock);
|
||||
led_set(led_data);
|
||||
mutex_unlock(&led_data->lock);
|
||||
}
|
||||
|
||||
static int s2mpb02_led_setup(struct s2mpb02_led_data *led_data)
|
||||
{
|
||||
int ret = 0;
|
||||
struct s2mpb02_led *data = led_data->data;
|
||||
int id = data->id;
|
||||
int value;
|
||||
|
||||
/* set Low Voltage operating mode disable */
|
||||
ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1,
|
||||
S2MPB02_FLED_CTRL1_LV_DISABLE,
|
||||
S2MPB02_FLED_CTRL1_LV_EN_MASK);
|
||||
|
||||
/* set current & timeout */
|
||||
ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CUR1,
|
||||
data->brightness << leds_shift[id], leds_mask[id]);
|
||||
ret |= s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_TIME1,
|
||||
data->timeout << leds_shift[id], leds_mask[id]);
|
||||
|
||||
value = s2mpb02_led_get_en_value(led_data, 0);
|
||||
ret = s2mpb02_update_reg(led_data->i2c, S2MPB02_REG_FLED_CTRL1,
|
||||
value, S2MPB02_FLED_ENABLE_MODE_MASK);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void s2mpb02_led_get_status(struct led_classdev *led_cdev,
|
||||
bool status, bool onoff)
|
||||
{
|
||||
int ret = 0;
|
||||
u8 value[6] = {0, };
|
||||
struct s2mpb02_led_data *led_data
|
||||
= container_of(led_cdev, struct s2mpb02_led_data, led);
|
||||
|
||||
ret = s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_CTRL1, &value[0]); /* Fled_ctrl1 */
|
||||
ret |= s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_CTRL2, &value[1]); /* Fled_ctrl2 */
|
||||
ret |= s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_CUR1, &value[2]); /* Fled_cur1 */
|
||||
ret |= s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_TIME1, &value[3]); /* Fled_time1 */
|
||||
ret |= s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_CUR2, &value[4]); /* Fled_cur2 */
|
||||
ret |= s2mpb02_read_reg(led_data->i2c,
|
||||
S2MPB02_REG_FLED_TIME2, &value[5]); /* Fled_time2 */
|
||||
if (unlikely(ret < 0))
|
||||
pr_info("%s : error to get dt node\n", __func__);
|
||||
|
||||
pr_info("%s[%d, %d] : Fled_ctrl1 = 0x%12x, Fled_ctrl2 = 0x%13x, Fled_cur1 = 0x%14x, Fled_time1 = 0x%15x, Fled_cur2 = 0x%16x, Fled_time2 = 0x%17x\n",
|
||||
__func__, status, onoff,
|
||||
value[0], value[1], value[2], value[3], value[4], value[5]);
|
||||
}
|
||||
|
||||
ssize_t s2mpb02_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf,
|
||||
size_t count)
|
||||
{
|
||||
int value = 0;
|
||||
|
||||
if ((buf == NULL) || kstrtouint(buf, 10, &value))
|
||||
return -1;
|
||||
|
||||
if (global_led_datas[S2MPB02_TORCH_LED_1] == NULL) {
|
||||
pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pr_info("[LED]%s , value:%d\n", __func__, value);
|
||||
if (value > 0) {
|
||||
if (value >= S2MPB02_TORCH_OUT_I_MAX) {
|
||||
if (value == 100)
|
||||
value = LED_FULL;
|
||||
else
|
||||
value = S2MPB02_TORCH_OUT_I_MAX - 1;
|
||||
}
|
||||
/* turn on torch */
|
||||
global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness = value;
|
||||
} else {
|
||||
/* turn off torch */
|
||||
global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness =
|
||||
LED_OFF;
|
||||
}
|
||||
|
||||
led_set(global_led_datas[S2MPB02_TORCH_LED_1]);
|
||||
if (value <= 0) {
|
||||
s2mpb02_set_bits(global_led_datas[S2MPB02_TORCH_LED_1]->i2c,
|
||||
S2MPB02_REG_FLED_CUR1,
|
||||
leds_mask[global_led_datas[S2MPB02_TORCH_LED_1]->data->id],
|
||||
original_brightness <<
|
||||
leds_shift[global_led_datas[S2MPB02_TORCH_LED_1]->data->id]);
|
||||
global_led_datas[S2MPB02_TORCH_LED_1]->data->brightness =
|
||||
original_brightness;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
ssize_t s2mpb02_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
if (global_led_datas[S2MPB02_TORCH_LED_1] == NULL) {
|
||||
pr_err("<%s> global_led_datas[S2MPB02_TORCH_LED_1] is NULL\n",
|
||||
__func__);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pr_info("[LED] %s , MAX STEP TORCH_LED:%d\n", __func__,
|
||||
S2MPB02_TORCH_OUT_I_MAX - 1);
|
||||
return sprintf(buf, "%d\n", S2MPB02_TORCH_OUT_I_MAX - 1);
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(rear_flash, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH,
|
||||
s2mpb02_show, s2mpb02_store);
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static int of_s2mpb02_torch_dt(struct s2mpb02_dev *iodev,
|
||||
struct s2mpb02_led_platform_data *pdata)
|
||||
{
|
||||
struct device_node *led_np, *np, *c_np;
|
||||
int ret;
|
||||
u32 temp;
|
||||
const char *temp_str;
|
||||
int index;
|
||||
|
||||
led_np = iodev->dev->of_node;
|
||||
if (!led_np) {
|
||||
pr_info("<%s> could not find led sub-node\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
np = of_find_node_by_name(led_np, "torch");
|
||||
if (!np) {
|
||||
pr_info("<%s> could not find led sub-node\n",
|
||||
__func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
pdata->num_leds = of_get_child_count(np);
|
||||
|
||||
for_each_child_of_node(np, c_np) {
|
||||
ret = of_property_read_u32(c_np, "id", &temp);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
|
||||
index = temp;
|
||||
pdata->leds[index].id = temp;
|
||||
|
||||
ret = of_property_read_string(c_np, "ledname", &temp_str);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
|
||||
pdata->leds[index].name = temp_str;
|
||||
ret = of_property_read_u32(c_np, "brightness", &temp);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
|
||||
if (temp > S2MPB02_FLASH_TORCH_CURRENT_MAX)
|
||||
temp = S2MPB02_FLASH_TORCH_CURRENT_MAX;
|
||||
pdata->leds[index].brightness = temp;
|
||||
original_brightness = temp;
|
||||
|
||||
ret = of_property_read_u32(c_np, "timeout", &temp);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
|
||||
if (temp > S2MPB02_TIMEOUT_MAX)
|
||||
temp = S2MPB02_TIMEOUT_MAX;
|
||||
pdata->leds[index].timeout = temp;
|
||||
}
|
||||
of_node_put(led_np);
|
||||
|
||||
return 0;
|
||||
dt_err:
|
||||
pr_err("%s failed to get a dt info\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static int s2mpb02_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0;
|
||||
int i;
|
||||
struct s2mpb02_dev *s2mpb02 = dev_get_drvdata(pdev->dev.parent);
|
||||
#ifndef CONFIG_OF
|
||||
struct s2mpb02_platform_data *s2mpb02_pdata
|
||||
= dev_get_platdata(s2mpb02->dev);
|
||||
#endif
|
||||
struct s2mpb02_led_platform_data *pdata;
|
||||
struct s2mpb02_led_data *led_data;
|
||||
struct s2mpb02_led *data;
|
||||
struct s2mpb02_led_data **led_datas;
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
pdata = kzalloc(sizeof(struct s2mpb02_led_platform_data), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
pr_err("%s: failed to allocate driver data\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
ret = of_s2mpb02_torch_dt(s2mpb02, pdata);
|
||||
if (ret < 0) {
|
||||
pr_err("s2mpb02-torch : %s not found torch dt! ret[%d]\n",
|
||||
__func__, ret);
|
||||
kfree(pdata);
|
||||
return -1;
|
||||
}
|
||||
#else
|
||||
pdata = s2mpb02_pdata->led_data;
|
||||
if (pdata == NULL) {
|
||||
pr_err("[LED] no platform data for this led is found\n");
|
||||
return -EFAULT;
|
||||
}
|
||||
#endif
|
||||
led_datas = kzalloc(sizeof(struct s2mpb02_led_data *)
|
||||
* S2MPB02_LED_MAX, GFP_KERNEL);
|
||||
if (unlikely(!led_datas)) {
|
||||
pr_err("[LED] memory allocation error %s", __func__);
|
||||
kfree(pdata);
|
||||
return -ENOMEM;
|
||||
}
|
||||
platform_set_drvdata(pdev, led_datas);
|
||||
|
||||
pr_info("[LED] %s %d leds\n", __func__, pdata->num_leds);
|
||||
|
||||
for (i = 0; i != pdata->num_leds; ++i) {
|
||||
pr_info("%s led%d setup ...\n", __func__, i);
|
||||
|
||||
data = kzalloc(sizeof(struct s2mpb02_led), GFP_KERNEL);
|
||||
if (unlikely(!data)) {
|
||||
pr_err("[LED] memory allocation error %s\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(data, &(pdata->leds[i]), sizeof(struct s2mpb02_led));
|
||||
|
||||
led_data = kzalloc(sizeof(struct s2mpb02_led_data),
|
||||
GFP_KERNEL);
|
||||
|
||||
global_led_datas[i] = led_data;
|
||||
led_datas[i] = led_data;
|
||||
if (unlikely(!led_data)) {
|
||||
pr_err("[LED] memory allocation error %s\n", __func__);
|
||||
ret = -ENOMEM;
|
||||
kfree(data);
|
||||
continue;
|
||||
}
|
||||
|
||||
led_data->s2mpb02 = s2mpb02;
|
||||
led_data->i2c = s2mpb02->i2c;
|
||||
led_data->data = data;
|
||||
led_data->led.name = data->name;
|
||||
led_data->led.brightness_set = s2mpb02_led_set;
|
||||
led_data->led.brightness = LED_OFF;
|
||||
led_data->brightness = data->brightness;
|
||||
led_data->led.flags = 0;
|
||||
led_data->led.max_brightness = S2MPB02_FLASH_TORCH_CURRENT_MAX;
|
||||
|
||||
mutex_init(&led_data->lock);
|
||||
spin_lock_init(&led_data->value_lock);
|
||||
INIT_WORK(&led_data->work, s2mpb02_led_work);
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &led_data->led);
|
||||
if (unlikely(ret)) {
|
||||
pr_err("unable to register LED\n");
|
||||
cancel_work_sync(&led_data->work);
|
||||
mutex_destroy(&led_data->lock);
|
||||
kfree(data);
|
||||
kfree(led_data);
|
||||
led_datas[i] = NULL;
|
||||
global_led_datas[i] = NULL;
|
||||
ret = -EFAULT;
|
||||
continue;
|
||||
}
|
||||
|
||||
ret = s2mpb02_led_setup(led_data);
|
||||
if (unlikely(ret)) {
|
||||
pr_err("unable to register LED\n");
|
||||
cancel_work_sync(&led_data->work);
|
||||
mutex_destroy(&led_data->lock);
|
||||
led_classdev_unregister(&led_data->led);
|
||||
kfree(data);
|
||||
kfree(led_data);
|
||||
led_datas[i] = NULL;
|
||||
global_led_datas[i] = NULL;
|
||||
ret = -EFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
kfree(pdata);
|
||||
#endif
|
||||
|
||||
s2mpb02_led_dev = device_create(camera_class, NULL, 3, NULL, "flash");
|
||||
if (IS_ERR(s2mpb02_led_dev)) {
|
||||
pr_err("<%s> Failed to create device(flash)!\n", __func__);
|
||||
} else {
|
||||
if (device_create_file(s2mpb02_led_dev,
|
||||
&dev_attr_rear_flash) < 0) {
|
||||
pr_err("<%s> failed to create device file, %s\n",
|
||||
__func__ , dev_attr_rear_flash.attr.name);
|
||||
}
|
||||
}
|
||||
|
||||
pr_err("<%s> end\n", __func__);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s2mpb02_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct s2mpb02_led_data **led_datas = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i != S2MPB02_LED_MAX; ++i) {
|
||||
if (led_datas[i] == NULL)
|
||||
continue;
|
||||
|
||||
cancel_work_sync(&led_datas[i]->work);
|
||||
mutex_destroy(&led_datas[i]->lock);
|
||||
led_classdev_unregister(&led_datas[i]->led);
|
||||
kfree(led_datas[i]->data);
|
||||
kfree(led_datas[i]);
|
||||
led_datas[i] = NULL;
|
||||
global_led_datas[i] = NULL;
|
||||
}
|
||||
kfree(led_datas);
|
||||
|
||||
if (s2mpb02_led_dev)
|
||||
device_remove_file(s2mpb02_led_dev, &dev_attr_rear_flash);
|
||||
|
||||
if (camera_class && s2mpb02_led_dev)
|
||||
device_destroy(camera_class, s2mpb02_led_dev->devt);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver s2mpb02_led_driver = {
|
||||
.probe = s2mpb02_led_probe,
|
||||
.remove = s2mpb02_led_remove,
|
||||
.driver = {
|
||||
.name = "s2mpb02-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init s2mpb02_led_init(void)
|
||||
{
|
||||
return platform_driver_register(&s2mpb02_led_driver);
|
||||
}
|
||||
module_init(s2mpb02_led_init);
|
||||
|
||||
static void __exit s2mpb02_led_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s2mpb02_led_driver);
|
||||
}
|
||||
module_exit(s2mpb02_led_exit);
|
||||
|
||||
MODULE_DESCRIPTION("S2MPB02 LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
715
drivers/leds/leds-s2mu003.c
Normal file
715
drivers/leds/leds-s2mu003.c
Normal file
|
|
@ -0,0 +1,715 @@
|
|||
/*
|
||||
* leds-s2mu003.c - LED class driver for S2MU003 LEDs.
|
||||
*
|
||||
* Copyright (C) 2014 Samsung Electronics
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mfd/samsung/s2mu003.h>
|
||||
#include <linux/mfd/samsung/s2mu003-private.h>
|
||||
#include <linux/leds-s2mu003.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct device *s2mu003_dev;
|
||||
struct s2mu003_led_data *global_led_datas[S2MU003_LED_MAX];
|
||||
|
||||
struct s2mu003_led_data {
|
||||
struct s2mu003_mfd_chip *iodev;
|
||||
struct led_classdev cdev;
|
||||
struct s2mu003_led *data;
|
||||
struct notifier_block batt_nb;
|
||||
struct i2c_client *i2c;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
spinlock_t value_lock;
|
||||
int brightness;
|
||||
int test_brightness;
|
||||
int attach_ta;
|
||||
int attach_sdp;
|
||||
bool enable;
|
||||
int torch_pin;
|
||||
int flash_pin;
|
||||
};
|
||||
|
||||
u32 original_brightness;
|
||||
#ifdef CONFIG_MUIC_NOTIFIER
|
||||
static void attach_cable_check(muic_attached_dev_t attached_dev,
|
||||
int *attach_ta, int *attach_sdp)
|
||||
{
|
||||
if (attached_dev == ATTACHED_DEV_USB_MUIC)
|
||||
*attach_sdp = 1;
|
||||
else
|
||||
*attach_sdp = 0;
|
||||
|
||||
switch (attached_dev) {
|
||||
case ATTACHED_DEV_TA_MUIC:
|
||||
case ATTACHED_DEV_SMARTDOCK_TA_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_TA_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC:
|
||||
case ATTACHED_DEV_CDP_MUIC:
|
||||
case ATTACHED_DEV_USB_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC:
|
||||
*attach_ta = 1;
|
||||
break;
|
||||
default:
|
||||
*attach_ta = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int ta_notification(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data;
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
u8 temp;
|
||||
#endif
|
||||
int ret = 0;
|
||||
struct s2mu003_led_data *led_data =
|
||||
container_of(nb, struct s2mu003_led_data, batt_nb);
|
||||
|
||||
switch (action) {
|
||||
case MUIC_NOTIFY_CMD_DETACH:
|
||||
case MUIC_NOTIFY_CMD_LOGICALLY_DETACH:
|
||||
if (!led_data->attach_ta)
|
||||
goto err;
|
||||
|
||||
led_data->attach_ta = 0;
|
||||
|
||||
if (!led_data->data->id) {
|
||||
pr_info("%s : flash mode\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
if (gpio_is_valid(led_data->torch_pin)) {
|
||||
ret = devm_gpio_request(led_data->cdev.dev,
|
||||
led_data->torch_pin, "s2mu003_gpio");
|
||||
if (ret) {
|
||||
pr_err("%s : fail to assignment gpio\n", __func__);
|
||||
goto gpio_free_data;
|
||||
}
|
||||
}
|
||||
if (gpio_get_value(led_data->torch_pin)) {
|
||||
gpio_direction_output(led_data->torch_pin, 0);
|
||||
gpio_direction_output(led_data->torch_pin, 1);
|
||||
goto gpio_free_data;
|
||||
}
|
||||
#else
|
||||
temp = s2mu003_reg_read(led_data->i2c,
|
||||
S2MU003_FLED_CH1_CTRL4);
|
||||
if ((temp & 0x0C) == 0x0C) {
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CH1_CTRL4,
|
||||
S2MU003_TORCH_ENABLE_MASK,
|
||||
S2MU003_FLASH_TORCH_OFF);
|
||||
|
||||
pr_info("%s : LED OFF\n", __func__);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CH1_CTRL4,
|
||||
S2MU003_TORCH_ENABLE_MASK,
|
||||
S2MU003_TORCH_ON_I2C);
|
||||
|
||||
pr_info("%s : LED ON\n", __func__);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case MUIC_NOTIFY_CMD_ATTACH:
|
||||
case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH:
|
||||
led_data->attach_ta = 0;
|
||||
attach_cable_check(attached_dev, &led_data->attach_ta, &led_data->attach_sdp);
|
||||
return 0;
|
||||
default:
|
||||
goto err;
|
||||
break;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
gpio_free_data:
|
||||
devm_gpio_free(led_data->cdev.dev, led_data->torch_pin);
|
||||
pr_info("%s : gpio free\n", __func__);
|
||||
#endif
|
||||
pr_info("%s : complete detached\n", __func__);
|
||||
return 0;
|
||||
err:
|
||||
pr_err("%s : abandond access %d\n", __func__, led_data->attach_ta);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void s2mu003_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct s2mu003_led_data *led_data =
|
||||
container_of(led_cdev, struct s2mu003_led_data, cdev);
|
||||
u8 max;
|
||||
|
||||
max = led_cdev->max_brightness;
|
||||
|
||||
spin_lock_irqsave(&led_data->value_lock, flags);
|
||||
led_data->data->brightness = min_t(int, (int)value, (int)max);
|
||||
|
||||
pr_info("%s value = %d, max = %d\n", __func__, led_data->data->brightness, max);
|
||||
spin_unlock_irqrestore(&led_data->value_lock, flags);
|
||||
|
||||
schedule_work(&led_data->work);
|
||||
return;
|
||||
}
|
||||
|
||||
static void led_set(struct s2mu003_led_data *led_data)
|
||||
{
|
||||
int ret;
|
||||
struct s2mu003_led *data = led_data->data;
|
||||
int id = data->id;
|
||||
u8 mask = 0, reg = 0;
|
||||
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
u8 enable_mask, value;
|
||||
#else
|
||||
int gpio_pin;
|
||||
#endif
|
||||
if (id == S2MU003_FLASH_LED) {
|
||||
pr_info("%s led mode is flash\n", __func__);
|
||||
reg = S2MU003_FLED_CH1_CTRL0;
|
||||
mask = S2MU003_FLASH_IOUT_MASK;
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
pr_info("%s gpio_flash mode\n", __func__);
|
||||
gpio_pin = led_data->flash_pin;
|
||||
#endif
|
||||
} else {
|
||||
pr_info("%s led mode is torch\n", __func__);
|
||||
reg = S2MU003_FLED_CH1_CTRL1;
|
||||
mask = S2MU003_TORCH_IOUT_MASK;
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
pr_info("%s gpio_torch mode\n", __func__);
|
||||
gpio_pin = led_data->torch_pin;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
if (gpio_is_valid(gpio_pin)) {
|
||||
ret = devm_gpio_request(led_data->cdev.dev, gpio_pin,
|
||||
"s2mu003_gpio");
|
||||
if (ret) {
|
||||
pr_err("%s : fail to assignment gpio\n", __func__);
|
||||
goto error_set_bits;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
pr_info("%s start led_set\n", __func__);
|
||||
|
||||
if (led_data->data->brightness == LED_OFF) {
|
||||
ret = s2mu003_assign_bits(led_data->i2c, reg,
|
||||
mask, led_data->data->brightness);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
|
||||
pr_info("%s led off\n", __func__);
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
value = S2MU003_FLASH_TORCH_OFF;
|
||||
#else
|
||||
gpio_direction_output(gpio_pin, 0);
|
||||
#endif
|
||||
/* torch mode off sequence */
|
||||
if (id && led_data->attach_ta) {
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CTRL1, 0x80, 0x00);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
}
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
goto gpio_free_data;
|
||||
#endif
|
||||
} else {
|
||||
pr_info("%s led on\n", __func__);
|
||||
/* torch mode on sequence */
|
||||
if (id && led_data->attach_ta) {
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CTRL1, 0x80, 0x80);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
/* ta attach & sdp mode : brightness limit 300mA */
|
||||
if (led_data->attach_sdp) {
|
||||
led_data->data->brightness =
|
||||
(led_data->data->brightness > 0x5) ?
|
||||
0x5 : led_data->data->brightness;
|
||||
}
|
||||
}
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
reg, mask, led_data->data->brightness);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
value = id ? S2MU003_TORCH_ON_I2C : S2MU003_FLASH_ON_I2C;
|
||||
#else
|
||||
gpio_direction_output(gpio_pin, 1);
|
||||
goto gpio_free_data;
|
||||
|
||||
#endif
|
||||
}
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
enable_mask = id ? S2MU003_TORCH_ENABLE_MASK :
|
||||
S2MU003_FLASH_ENABLE_MASK;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CH1_CTRL4,
|
||||
enable_mask, value);
|
||||
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
#endif
|
||||
return;
|
||||
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
gpio_free_data:
|
||||
devm_gpio_free(led_data->cdev.dev, gpio_pin);
|
||||
pr_info("%s : gpio free\n", __func__);
|
||||
return;
|
||||
#endif
|
||||
error_set_bits:
|
||||
pr_err("%s: can't set led level %d\n", __func__, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
static void s2mu003_led_work(struct work_struct *work)
|
||||
{
|
||||
struct s2mu003_led_data *led_data
|
||||
= container_of(work, struct s2mu003_led_data, work);
|
||||
|
||||
pr_debug("%s [led]\n", __func__);
|
||||
|
||||
mutex_lock(&led_data->lock);
|
||||
led_set(led_data);
|
||||
mutex_unlock(&led_data->lock);
|
||||
}
|
||||
|
||||
static int s2mu003_led_setup(struct s2mu003_led_data *led_data)
|
||||
{
|
||||
int ret = 0;
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
int mask, value;
|
||||
#endif
|
||||
ret = s2mu003_assign_bits(led_data->i2c, 0x89, 0x0f, 0x0f);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CTRL0,
|
||||
0x07, 0x07);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CTRL2,
|
||||
S2MU003_EN_CHANNEL_SHARE_MASK, 0x80);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CTRL2,
|
||||
S2MU003_BOOST_VOUT_FLASH_MASK, 0x23);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c,
|
||||
S2MU003_FLED_CH1_CTRL3, 0x80, 0x80);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CH1_CTRL1,
|
||||
S2MU003_TORCH_IOUT_MASK, S2MU003_TORCH_OUT_I_50MA);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CH1_CTRL3,
|
||||
S2MU003_TIMEOUT_MAX, S2MU003_FLASH_TIMEOUT_992MS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CH1_CTRL2,
|
||||
S2MU003_TIMEOUT_MAX, S2MU003_TORCH_TIMEOUT_15728MS);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
#ifdef CONFIG_S2MU003_LEDS_I2C
|
||||
value = S2MU003_FLASH_TORCH_OFF;
|
||||
mask = S2MU003_TORCH_ENABLE_MASK | S2MU003_FLASH_ENABLE_MASK;
|
||||
ret = s2mu003_assign_bits(led_data->i2c, S2MU003_FLED_CH1_CTRL4,
|
||||
mask, value);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
#endif
|
||||
pr_info("%s : led setup complete\n", __func__);
|
||||
return ret;
|
||||
|
||||
out:
|
||||
pr_err("%s : led setup fail\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t rear_flash_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct s2mu003_led_data *led_data =
|
||||
container_of(led_cdev, struct s2mu003_led_data, cdev);
|
||||
char *str;
|
||||
|
||||
switch (led_data->data->id) {
|
||||
case S2MU003_FLASH_LED:
|
||||
str = "FLASH";
|
||||
break;
|
||||
case S2MU003_TORCH_LED:
|
||||
str = "TORCH";
|
||||
break;
|
||||
default:
|
||||
str = "NONE";
|
||||
break;
|
||||
}
|
||||
|
||||
return snprintf(buf, 20, "%s\n", str);
|
||||
}
|
||||
|
||||
static ssize_t rear_flash_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct s2mu003_led_data *led_data =
|
||||
container_of(led_cdev, struct s2mu003_led_data, cdev);
|
||||
|
||||
enum led_brightness value;
|
||||
|
||||
mutex_lock(&led_data->lock);
|
||||
|
||||
if (led_data->data->id == S2MU003_FLASH_LED) {
|
||||
pr_info("%s : flash is not controlled by sysfs", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (!strncmp(buf, "0", 1)) {
|
||||
value = LED_OFF;
|
||||
pr_info("%s : led off\n", __func__);
|
||||
} else if (!strncmp(buf, "1", 1))
|
||||
value = 0x2;
|
||||
else if (!strncmp(buf, "2", 1))
|
||||
value = 0x8;
|
||||
else if (!strncmp(buf, "3", 1))
|
||||
value = 0xf;
|
||||
else {
|
||||
pr_info("%s : incorrect number\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (led_cdev->flags & LED_SUSPENDED) {
|
||||
pr_info("%s : led suspended\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
s2mu003_led_set(led_cdev, value);
|
||||
|
||||
mutex_unlock(&led_data->lock);
|
||||
return size;
|
||||
|
||||
err:
|
||||
pr_err("%s : led abnormal end\n", __func__);
|
||||
|
||||
mutex_unlock(&led_data->lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(rear_flash, 0644, rear_flash_show, rear_flash_store);
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static int s2mu003_led_dt_parse_pdata(struct s2mu003_mfd_chip *iodev,
|
||||
struct s2mu003_fled_platform_data *pdata)
|
||||
{
|
||||
struct device_node *led_np, *np, *c_np;
|
||||
int ret;
|
||||
u32 temp;
|
||||
const char *temp_str;
|
||||
int index;
|
||||
|
||||
led_np = iodev->dev->of_node;
|
||||
if (!led_np) {
|
||||
pr_err("<%s> could not find led sub-node led_np\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
np = of_find_node_by_name(led_np, "leds");
|
||||
if (!np) {
|
||||
pr_err("%s : could not find led sub-node np\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pdata->torch_pin = of_get_named_gpio(np, "torch-gpio", 0);
|
||||
if (ret < 0) {
|
||||
pr_err("%s : can't get torch-gpio\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pdata->flash_pin = of_get_named_gpio(np, "flash-gpio", 0);
|
||||
if (ret < 0) {
|
||||
pr_err("%s : can't get flash-gpio\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pdata->num_leds = of_get_child_count(np);
|
||||
|
||||
for_each_child_of_node(np, c_np) {
|
||||
ret = of_property_read_u32(c_np, "id", &temp);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
index = temp;
|
||||
pdata->leds[index].id = temp;
|
||||
|
||||
ret = of_property_read_string(c_np, "ledname", &temp_str);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
pdata->leds[index].name = temp_str;
|
||||
|
||||
ret = of_property_read_u32(c_np, "brightness", &temp);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
if (temp > leds_cur_max[index])
|
||||
temp = leds_cur_max[index];
|
||||
pdata->leds[index].brightness = temp;
|
||||
original_brightness = temp;
|
||||
ret = of_property_read_u32(c_np, "timeout", &temp);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
if (temp > leds_time_max[index])
|
||||
temp = leds_time_max[index];
|
||||
pdata->leds[index].timeout = temp;
|
||||
|
||||
}
|
||||
return 0;
|
||||
dt_err:
|
||||
pr_err("%s failed to get a timeout\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static struct of_device_id s2mu003_fled_match_table[] = {
|
||||
{ .compatible = "samsung,s2mu003led",},
|
||||
{},
|
||||
};
|
||||
|
||||
static int s2mu003_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0, i = 0;
|
||||
|
||||
struct s2mu003_mfd_chip *s2mu003 = dev_get_drvdata(pdev->dev.parent);
|
||||
#ifndef CONFIG_OF
|
||||
struct s2mu003_mfd_platform_data *s2mu003_pdata = s2mu003->pdata;
|
||||
#endif
|
||||
struct s2mu003_fled_platform_data *pdata;
|
||||
struct s2mu003_led_data *led_data;
|
||||
struct s2mu003_led *data;
|
||||
struct s2mu003_led_data **led_datas;
|
||||
|
||||
pr_info("[%s] s2mu003_fled start\n", __func__);
|
||||
|
||||
if (!s2mu003) {
|
||||
dev_err(&pdev->dev, "drvdata->dev.parent not supplied\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
#ifdef CONFIG_OF
|
||||
if (pdev->dev.parent->of_node) {
|
||||
pdev->dev.of_node = of_find_compatible_node(
|
||||
of_node_get(pdev->dev.parent->of_node), NULL,
|
||||
s2mu003_fled_match_table[0].compatible);
|
||||
}
|
||||
|
||||
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
dev_err(&pdev->dev, "Failed to allocate memory\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (pdev->dev.of_node) {
|
||||
ret = s2mu003_led_dt_parse_pdata(s2mu003, pdata);
|
||||
if (ret < 0) {
|
||||
pr_err("[%s] not found leds dt! ret[%d]\n",
|
||||
__func__, ret);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!s2mu003_pdata) {
|
||||
dev_err(&pdev->dev, "platform data not supplied\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pdata = s2mu003_pdata->fled_platform_data;
|
||||
if (!pdata) {
|
||||
pr_err("[%s] no platform data for this led is found\n",
|
||||
__func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
#endif
|
||||
|
||||
led_datas = kzalloc(sizeof(struct s2mu003_led_data *) * S2MU003_LED_MAX, GFP_KERNEL);
|
||||
|
||||
if (!led_datas) {
|
||||
pr_err("[%s] memory allocation error led_datas", __func__);
|
||||
devm_kfree(&pdev->dev, pdata);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led_datas);
|
||||
|
||||
pr_info("%s %d leds\n", __func__, pdata->num_leds);
|
||||
|
||||
for (i = 0; i != pdata->num_leds; ++i) {
|
||||
pr_info("%s led%d setup ...\n", __func__, i);
|
||||
|
||||
data = kzalloc(sizeof(struct s2mu003_led), GFP_KERNEL);
|
||||
global_led_datas[i] = led_data;
|
||||
if (!data) {
|
||||
pr_err("[%s] memory allocation error data\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(data, &(pdata->leds[i]), sizeof(struct s2mu003_led));
|
||||
led_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct s2mu003_led_data), GFP_KERNEL);
|
||||
|
||||
led_datas[i] = led_data;
|
||||
|
||||
if (!led_data) {
|
||||
pr_err("[%s] memory allocation error led_data\n",
|
||||
__func__);
|
||||
kfree(data);
|
||||
ret = -ENOMEM;
|
||||
continue;
|
||||
}
|
||||
|
||||
led_data->iodev = s2mu003;
|
||||
led_data->i2c = s2mu003->i2c_client;
|
||||
led_data->data = data;
|
||||
led_data->cdev.name = data->name;
|
||||
led_data->cdev.brightness_set = s2mu003_led_set;
|
||||
led_data->cdev.flags = 0;
|
||||
led_data->cdev.brightness = data->brightness;
|
||||
led_data->cdev.max_brightness = led_data->data->id ?
|
||||
S2MU003_TORCH_OUT_I_400MA : S2MU003_FLASH_OUT_I_900MA;
|
||||
|
||||
mutex_init(&led_data->lock);
|
||||
spin_lock_init(&led_data->value_lock);
|
||||
INIT_WORK(&led_data->work, s2mu003_led_work);
|
||||
ret = led_classdev_register(&pdev->dev, &led_data->cdev);
|
||||
if (ret < 0) {
|
||||
pr_err("unable to register LED\n");
|
||||
cancel_work_sync(&led_data->work);
|
||||
mutex_destroy(&led_data->lock);
|
||||
kfree(data);
|
||||
kfree(led_data);
|
||||
global_led_datas[i] = NULL;
|
||||
led_datas[i] = NULL;
|
||||
ret = -EFAULT;
|
||||
continue;
|
||||
}
|
||||
if (led_data->data->id == S2MU003_TORCH_LED) {
|
||||
ret = device_create_file(led_data->cdev.dev,
|
||||
&dev_attr_rear_flash);
|
||||
if (ret < 0)
|
||||
pr_err("%s :unable to create file\n", __func__);
|
||||
}
|
||||
#ifndef CONFIG_S2MU003_LEDS_I2C
|
||||
if (gpio_is_valid(pdata->torch_pin) &&
|
||||
gpio_is_valid(pdata->flash_pin)) {
|
||||
if (ret < 0) {
|
||||
pr_err("%s : s2mu003 fled gpio allocation error\n",
|
||||
__func__);
|
||||
} else {
|
||||
led_data->torch_pin = pdata->torch_pin;
|
||||
led_data->flash_pin = pdata->flash_pin;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_MUIC_NOTIFIER
|
||||
muic_notifier_register(&led_data->batt_nb,
|
||||
ta_notification,
|
||||
MUIC_NOTIFY_DEV_CHARGER);
|
||||
#endif
|
||||
ret = s2mu003_led_setup(led_data);
|
||||
if (ret < 0)
|
||||
pr_err("%s : failed s2mu003 led reg init\n", __func__);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s2mu003_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct s2mu003_led_data **led_datas = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i != S2MU003_LED_MAX; ++i) {
|
||||
if (led_datas[i] == NULL)
|
||||
continue;
|
||||
|
||||
if (led_datas[i]->data->id == S2MU003_TORCH_LED)
|
||||
device_remove_file(led_datas[i]->cdev.dev,
|
||||
&dev_attr_rear_flash);
|
||||
cancel_work_sync(&led_datas[i]->work);
|
||||
mutex_destroy(&led_datas[i]->lock);
|
||||
led_classdev_unregister(&led_datas[i]->cdev);
|
||||
kfree(led_datas[i]->data);
|
||||
kfree(led_datas[i]);
|
||||
kfree(global_led_datas[i]);
|
||||
}
|
||||
kfree(led_datas);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id s2mu003_leds_id[] = {
|
||||
{"s2mu003-leds", 0},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver s2mu003_led_driver = {
|
||||
.driver = {
|
||||
.name = "s2mu003-leds",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = s2mu003_fled_match_table,
|
||||
},
|
||||
.probe = s2mu003_led_probe,
|
||||
.remove = s2mu003_led_remove,
|
||||
.id_table = s2mu003_leds_id,
|
||||
};
|
||||
|
||||
static int __init s2mu003_led_driver_init(void)
|
||||
{
|
||||
return platform_driver_register(&s2mu003_led_driver);
|
||||
}
|
||||
module_init(s2mu003_led_driver_init);
|
||||
|
||||
static void __exit s2mu003_led_driver_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s2mu003_led_driver);
|
||||
}
|
||||
module_exit(s2mu003_led_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("SUJI LEE <suji0908.lee@samsung.com>");
|
||||
MODULE_DESCRIPTION("SAMSUNG s2mu003 LED Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
758
drivers/leds/leds-s2mu005.c
Normal file
758
drivers/leds/leds-s2mu005.c
Normal file
|
|
@ -0,0 +1,758 @@
|
|||
/*
|
||||
* leds-s2mu005.c - LED class driver for S2MU005 LEDs.
|
||||
*
|
||||
* Copyright (C) 2015 Samsung Electronics
|
||||
*
|
||||
* 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/module.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/of_gpio.h>
|
||||
#include <linux/mfd/samsung/s2mu005.h>
|
||||
#include <linux/mfd/samsung/s2mu005-private.h>
|
||||
#include <linux/leds-s2mu005.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
struct s2mu005_led_data *g_led_datas[S2MU005_LED_MAX];
|
||||
|
||||
static u8 leds_cur_max[] = {
|
||||
S2MU005_FLASH_OUT_I_1200MA,
|
||||
S2MU005_TORCH_OUT_I_400MA,
|
||||
};
|
||||
|
||||
static u8 leds_time_max[] = {
|
||||
S2MU005_FLASH_TIMEOUT_992MS,
|
||||
S2MU005_TORCH_TIMEOUT_15728MS,
|
||||
};
|
||||
|
||||
struct s2mu005_led_data {
|
||||
struct led_classdev cdev;
|
||||
struct s2mu005_led *data;
|
||||
struct notifier_block batt_nb;
|
||||
struct i2c_client *i2c;
|
||||
struct work_struct work;
|
||||
struct mutex lock;
|
||||
spinlock_t value_lock;
|
||||
int brightness;
|
||||
int test_brightness;
|
||||
int attach_ta;
|
||||
int attach_sdp;
|
||||
bool enable;
|
||||
int torch_pin;
|
||||
int flash_pin;
|
||||
};
|
||||
|
||||
u8 CH_FLASH_TORCH_EN = S2MU005_REG_FLED_RSVD;
|
||||
|
||||
#ifdef CONFIG_MUIC_NOTIFIER
|
||||
static void attach_cable_check(muic_attached_dev_t attached_dev,
|
||||
int *attach_ta, int *attach_sdp)
|
||||
{
|
||||
if (attached_dev == ATTACHED_DEV_USB_MUIC)
|
||||
*attach_sdp = 1;
|
||||
else
|
||||
*attach_sdp = 0;
|
||||
|
||||
switch (attached_dev) {
|
||||
case ATTACHED_DEV_TA_MUIC:
|
||||
case ATTACHED_DEV_SMARTDOCK_TA_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_TA_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_ID_TA_MUIC:
|
||||
case ATTACHED_DEV_CDP_MUIC:
|
||||
case ATTACHED_DEV_USB_MUIC:
|
||||
case ATTACHED_DEV_UNOFFICIAL_ID_CDP_MUIC:
|
||||
*attach_ta = 1;
|
||||
break;
|
||||
default:
|
||||
*attach_ta = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int ta_notification(struct notifier_block *nb,
|
||||
unsigned long action, void *data)
|
||||
{
|
||||
muic_attached_dev_t attached_dev = *(muic_attached_dev_t *)data;
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
u8 temp;
|
||||
#endif
|
||||
int ret = 0;
|
||||
struct s2mu005_led_data *led_data =
|
||||
container_of(nb, struct s2mu005_led_data, batt_nb);
|
||||
|
||||
switch (action) {
|
||||
case MUIC_NOTIFY_CMD_DETACH:
|
||||
case MUIC_NOTIFY_CMD_LOGICALLY_DETACH:
|
||||
if (!led_data->attach_ta)
|
||||
goto err;
|
||||
|
||||
led_data->attach_ta = 0;
|
||||
|
||||
if (!led_data->data->id) {
|
||||
pr_info("%s : flash mode\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
if (gpio_is_valid(led_data->torch_pin)) {
|
||||
ret = devm_gpio_request(led_data->cdev.dev,
|
||||
led_data->torch_pin, "s2mu005_gpio");
|
||||
if (ret) {
|
||||
pr_err("%s : fail to assignment gpio\n",
|
||||
__func__);
|
||||
goto gpio_free_data;
|
||||
}
|
||||
}
|
||||
if (gpio_get_value(led_data->torch_pin)) {
|
||||
gpio_direction_output(led_data->torch_pin, 0);
|
||||
gpio_direction_output(led_data->torch_pin, 1);
|
||||
goto gpio_free_data;
|
||||
}
|
||||
#else
|
||||
s2mu005_read_reg(led_data->i2c,
|
||||
CH_FLASH_TORCH_EN, &temp);
|
||||
if ((temp & S2MU005_TORCH_ON_I2C) == S2MU005_TORCH_ON_I2C) {
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
CH_FLASH_TORCH_EN,
|
||||
S2MU005_FLASH_TORCH_OFF,
|
||||
S2MU005_TORCH_ENABLE_MASK);
|
||||
|
||||
pr_info("%s : LED OFF\n", __func__);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_LED_CTRL4,
|
||||
S2MU005_TORCH_ON_I2C,
|
||||
S2MU005_TORCH_ENABLE_MASK);
|
||||
|
||||
pr_info("%s : LED ON\n", __func__);
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
case MUIC_NOTIFY_CMD_ATTACH:
|
||||
case MUIC_NOTIFY_CMD_LOGICALLY_ATTACH:
|
||||
led_data->attach_ta = 0;
|
||||
attach_cable_check(attached_dev, &led_data->attach_ta,
|
||||
&led_data->attach_sdp);
|
||||
return 0;
|
||||
default:
|
||||
goto err;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
gpio_free_data:
|
||||
gpio_free(led_data->torch_pin);
|
||||
pr_info("%s : gpio free\n", __func__);
|
||||
#endif
|
||||
pr_info("%s : complete detached\n", __func__);
|
||||
return 0;
|
||||
err:
|
||||
pr_err("%s : abandond access %d\n", __func__, led_data->attach_ta);
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void led_set(struct s2mu005_led_data *led_data)
|
||||
{
|
||||
int ret;
|
||||
struct s2mu005_led *data = led_data->data;
|
||||
int id = data->id;
|
||||
u8 mask = 0, reg = 0;
|
||||
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
u8 enable_mask, value;
|
||||
#else
|
||||
int gpio_pin;
|
||||
#endif
|
||||
if (id == S2MU005_FLASH_LED) {
|
||||
pr_info("%s led mode is flash\n", __func__);
|
||||
reg = S2MU005_REG_FLED_CH1_CTRL0;
|
||||
mask = S2MU005_FLASH_IOUT_MASK;
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
pr_info("%s gpio_flash mode\n", __func__);
|
||||
gpio_pin = led_data->flash_pin;
|
||||
#endif
|
||||
} else {
|
||||
pr_info("%s led mode is torch\n", __func__);
|
||||
reg = S2MU005_REG_FLED_CH1_CTRL1;
|
||||
mask = S2MU005_TORCH_IOUT_MASK;
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
pr_info("%s gpio_torch mode\n", __func__);
|
||||
gpio_pin = led_data->torch_pin;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
if (gpio_is_valid(gpio_pin)) {
|
||||
ret = devm_gpio_request(led_data->cdev.dev, gpio_pin,
|
||||
"s2mu005_gpio");
|
||||
if (ret) {
|
||||
pr_err("%s : fail to assignment gpio\n", __func__);
|
||||
goto gpio_free_data;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
pr_info("%s start led_set\n", __func__);
|
||||
|
||||
if (led_data->test_brightness == LED_OFF) {
|
||||
ret = s2mu005_update_reg(led_data->i2c, reg,
|
||||
led_data->test_brightness, mask);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
value = S2MU005_FLASH_TORCH_OFF;
|
||||
#else
|
||||
gpio_direction_output(gpio_pin, 0);
|
||||
#endif
|
||||
/* torch mode off sequence */
|
||||
if (id && led_data->attach_ta) {
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_FLED_CTRL1, 0x00, 0x80);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
}
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
goto gpio_free_data;
|
||||
#endif
|
||||
} else {
|
||||
pr_info("%s led on\n", __func__);
|
||||
/* torch mode on sequence */
|
||||
if (id && led_data->attach_ta) {
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_FLED_CTRL1, 0x80, 0x80);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
/* ta attach & sdp mode : brightness limit 300mA */
|
||||
if (led_data->attach_sdp)
|
||||
led_data->test_brightness =
|
||||
(led_data->test_brightness > S2MU005_TORCH_OUT_I_300MA) ?
|
||||
S2MU005_TORCH_OUT_I_300MA : led_data->test_brightness;
|
||||
}
|
||||
pr_info("%s led brightness = %d\n", __func__,
|
||||
led_data->test_brightness);
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
reg, led_data->test_brightness, mask);
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
value = id ? S2MU005_TORCH_ON_I2C : S2MU005_FLASH_ON_I2C;
|
||||
#else
|
||||
gpio_direction_output(gpio_pin, 1);
|
||||
goto gpio_free_data;
|
||||
|
||||
#endif
|
||||
}
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
enable_mask = id ? S2MU005_TORCH_ENABLE_MASK :
|
||||
S2MU005_FLASH_ENABLE_MASK;
|
||||
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
CH_FLASH_TORCH_EN,
|
||||
value, enable_mask);
|
||||
|
||||
if (ret < 0)
|
||||
goto error_set_bits;
|
||||
#endif
|
||||
return;
|
||||
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
gpio_free_data:
|
||||
gpio_free(gpio_pin);
|
||||
pr_info("%s : gpio free\n", __func__);
|
||||
return;
|
||||
#endif
|
||||
error_set_bits:
|
||||
pr_err("%s: can't set led level %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
static void s2mu005_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct s2mu005_led_data *led_data =
|
||||
container_of(led_cdev, struct s2mu005_led_data, cdev);
|
||||
u8 max;
|
||||
|
||||
max = led_cdev->max_brightness;
|
||||
|
||||
pr_info("%s value = %d, max = %d\n", __func__, value, max);
|
||||
|
||||
spin_lock_irqsave(&led_data->value_lock, flags);
|
||||
led_data->test_brightness = min_t(int, (int)value, (int)max);
|
||||
spin_unlock_irqrestore(&led_data->value_lock, flags);
|
||||
|
||||
led_set(led_data);
|
||||
}
|
||||
|
||||
static void s2mu005_led_work(struct work_struct *work)
|
||||
{
|
||||
struct s2mu005_led_data *led_data
|
||||
= container_of(work, struct s2mu005_led_data, work);
|
||||
|
||||
pr_debug("%s [led]\n", __func__);
|
||||
|
||||
mutex_lock(&led_data->lock);
|
||||
led_set(led_data);
|
||||
mutex_unlock(&led_data->lock);
|
||||
}
|
||||
|
||||
static int s2mu005_led_setup(struct s2mu005_led_data *led_data)
|
||||
{
|
||||
int ret = 0;
|
||||
int mask, value;
|
||||
u8 temp;
|
||||
|
||||
/* EVT0 0x73[3:0] == 0x0 */
|
||||
ret = s2mu005_read_reg(led_data->i2c, 0x73, &temp);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if ((temp & 0xf) == 0x00) {
|
||||
/* forced BATID recognition 0x89[1:0] = 0x3 */
|
||||
ret = s2mu005_update_reg(led_data->i2c, 0x89, 0x03, 0x03);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = s2mu005_update_reg(led_data->i2c, 0x92, 0x80, 0x80);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Controlled Channel1, Channel2 independently */
|
||||
ret = s2mu005_update_reg(led_data->i2c, S2MU005_REG_FLED_CTRL2,
|
||||
0x00, S2MU005_EN_CHANNEL_SHARE_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Boost vout flash 4.5V */
|
||||
ret = s2mu005_update_reg(led_data->i2c, S2MU005_REG_FLED_CTRL2,
|
||||
0x0A, S2MU005_BOOST_VOUT_FLASH_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* FLED_BOOST_EN */
|
||||
ret = s2mu005_update_reg(led_data->i2c, S2MU005_REG_FLED_CTRL1,
|
||||
0x40, S2MU005_FLASH_BOOST_EN_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* Flash timer Maximum mode */
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_FLED_CH1_CTRL3, 0x80, 0x80);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
if (led_data->data->id == S2MU005_FLASH_LED) {
|
||||
/* flash timer Maximum set */
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_FLED_CH1_CTRL3, led_data->data->timeout,
|
||||
S2MU005_TIMEOUT_MAX);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
} else {
|
||||
/* torch timer Maximum set */
|
||||
ret = s2mu005_update_reg(led_data->i2c,
|
||||
S2MU005_REG_FLED_CH1_CTRL2, led_data->data->timeout,
|
||||
S2MU005_TIMEOUT_MAX);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* flash brightness set */
|
||||
ret = s2mu005_update_reg(led_data->i2c, S2MU005_REG_FLED_CH1_CTRL0,
|
||||
S2MU005_FLASH_OUT_I_1000MA, S2MU005_FLASH_IOUT_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/* torch brightness set */
|
||||
ret = s2mu005_update_reg(led_data->i2c, S2MU005_REG_FLED_CH1_CTRL1,
|
||||
S2MU005_TORCH_OUT_I_75MA, S2MU005_TORCH_IOUT_MASK);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
#ifdef CONFIG_S2MU005_LEDS_I2C
|
||||
value = S2MU005_FLASH_TORCH_OFF;
|
||||
#else
|
||||
value = S2MU005_FLASH_ON_GPIO | S2MU005_TORCH_ON_GPIO;
|
||||
#endif
|
||||
mask = S2MU005_TORCH_ENABLE_MASK | S2MU005_FLASH_ENABLE_MASK;
|
||||
ret = s2mu005_update_reg(led_data->i2c, CH_FLASH_TORCH_EN,
|
||||
value, mask);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
pr_info("%s : led setup complete\n", __func__);
|
||||
return ret;
|
||||
|
||||
out:
|
||||
pr_err("%s : led setup fail\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t rear_flash_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct s2mu005_led_data *led_data = g_led_datas[S2MU005_TORCH_LED];
|
||||
char *str;
|
||||
|
||||
switch (led_data->data->id) {
|
||||
case S2MU005_FLASH_LED:
|
||||
str = "FLASH";
|
||||
break;
|
||||
case S2MU005_TORCH_LED:
|
||||
str = "TORCH";
|
||||
break;
|
||||
default:
|
||||
str = "NONE";
|
||||
break;
|
||||
}
|
||||
|
||||
return snprintf(buf, 20, "%s\n", str);
|
||||
}
|
||||
|
||||
static ssize_t rear_flash_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct s2mu005_led_data *led_data = g_led_datas[S2MU005_TORCH_LED];
|
||||
struct led_classdev *led_cdev = &led_data->cdev;
|
||||
int value = 0;
|
||||
int brightness = 0;
|
||||
|
||||
if ((buf == NULL) || kstrtouint(buf, 10, &value)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
pr_info("[LED]%s , value:%d\n", __func__, value);
|
||||
mutex_lock(&led_data->lock);
|
||||
|
||||
if (led_data->data->id == S2MU005_FLASH_LED) {
|
||||
pr_info("%s : flash is not controlled by sysfs", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (value == 0) {
|
||||
/* Turn off Torch */
|
||||
brightness = LED_OFF;
|
||||
} else if (value == 1) {
|
||||
/* Turn on Torch */
|
||||
brightness = value;
|
||||
} else {
|
||||
pr_info("[LED]%s , Invalid value:%d\n", __func__, value);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (led_cdev->flags & LED_SUSPENDED) {
|
||||
pr_info("%s : led suspended\n", __func__);
|
||||
goto err;
|
||||
}
|
||||
|
||||
s2mu005_led_set(led_cdev, brightness);
|
||||
|
||||
mutex_unlock(&led_data->lock);
|
||||
return size;
|
||||
|
||||
err:
|
||||
pr_err("%s : led abnormal end\n", __func__);
|
||||
|
||||
mutex_unlock(&led_data->lock);
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(rear_flash, 0644, rear_flash_show, rear_flash_store);
|
||||
|
||||
#if defined(CONFIG_OF)
|
||||
static int s2mu005_led_dt_parse_pdata(struct s2mu005_dev *iodev,
|
||||
struct s2mu005_fled_platform_data *pdata)
|
||||
{
|
||||
struct device_node *led_np, *np, *c_np;
|
||||
int ret;
|
||||
u32 temp;
|
||||
const char *temp_str;
|
||||
int index;
|
||||
|
||||
led_np = iodev->dev->of_node;
|
||||
|
||||
if (!led_np) {
|
||||
pr_err("<%s> could not find led sub-node led_np\n", __func__);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
np = of_find_node_by_name(led_np, "leds");
|
||||
if (!np) {
|
||||
pr_err("%s : could not find led sub-node np\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = pdata->torch_pin = of_get_named_gpio(np, "torch-gpio", 0);
|
||||
if (ret < 0) {
|
||||
pr_err("%s : can't get torch-gpio\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = pdata->flash_pin = of_get_named_gpio(np, "flash-gpio", 0);
|
||||
if (ret < 0) {
|
||||
pr_err("%s : can't get flash-gpio\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pdata->num_leds = of_get_child_count(np);
|
||||
|
||||
for_each_child_of_node(np, c_np) {
|
||||
ret = of_property_read_u32(c_np, "id", &temp);
|
||||
if (ret < 0)
|
||||
goto dt_err;
|
||||
index = temp;
|
||||
pdata->leds[index].id = temp;
|
||||
|
||||
ret = of_property_read_string(c_np, "ledname", &temp_str);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
pdata->leds[index].name = temp_str;
|
||||
|
||||
ret = of_property_read_u32(c_np, "brightness", &temp);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
if (temp > leds_cur_max[index])
|
||||
temp = leds_cur_max[index];
|
||||
pdata->leds[index].brightness = temp;
|
||||
|
||||
ret = of_property_read_u32(c_np, "timeout", &temp);
|
||||
if (ret)
|
||||
goto dt_err;
|
||||
if (temp > leds_time_max[index])
|
||||
temp = leds_time_max[index];
|
||||
pdata->leds[index].timeout = temp;
|
||||
}
|
||||
return 0;
|
||||
dt_err:
|
||||
pr_err("%s failed to get a timeout\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
#endif /* CONFIG_OF */
|
||||
|
||||
static int s2mu005_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret = 0, i = 0;
|
||||
u8 temp = 0;
|
||||
|
||||
struct s2mu005_dev *s2mu005 = dev_get_drvdata(pdev->dev.parent);
|
||||
#ifndef CONFIG_OF
|
||||
struct s2mu005_mfd_platform_data *s2mu005_pdata = s2mu005->pdata;
|
||||
#endif
|
||||
struct s2mu005_fled_platform_data *pdata;
|
||||
struct s2mu005_led_data *led_data;
|
||||
struct s2mu005_led *data;
|
||||
struct s2mu005_led_data **led_datas;
|
||||
|
||||
pr_info("[%s] s2mu005_fled start\n", __func__);
|
||||
|
||||
if (!s2mu005) {
|
||||
dev_err(&pdev->dev, "drvdata->dev.parent not supplied\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
#ifdef CONFIG_OF
|
||||
pdata = kzalloc(sizeof(struct s2mu005_fled_platform_data), GFP_KERNEL);
|
||||
if (!pdata) {
|
||||
pr_err("[%s] failed to allocate driver data\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
if (s2mu005->dev->of_node) {
|
||||
ret = s2mu005_led_dt_parse_pdata(s2mu005, pdata);
|
||||
if (ret < 0) {
|
||||
pr_err("[%s] not found leds dt! ret[%d]\n",
|
||||
__func__, ret);
|
||||
kfree(pdata);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!s2mu005_pdata) {
|
||||
dev_err(&pdev->dev, "platform data not supplied\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
pdata = s2mu005_pdata->fled_platform_data;
|
||||
if (!pdata) {
|
||||
pr_err("[%s] no platform data for this led is found\n",
|
||||
__func__);
|
||||
return -EFAULT;
|
||||
}
|
||||
#endif
|
||||
|
||||
led_datas = devm_kzalloc(s2mu005->dev,
|
||||
sizeof(struct s2mu005_led_data *) *
|
||||
S2MU005_LED_MAX, GFP_KERNEL);
|
||||
if (!led_datas) {
|
||||
pr_err("[%s] memory allocation error led_datas", __func__);
|
||||
kfree(pdata);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, led_datas);
|
||||
|
||||
pr_info("%s %d leds\n", __func__, pdata->num_leds);
|
||||
|
||||
for (i = 0; i != pdata->num_leds; ++i) {
|
||||
pr_info("%s led%d setup ...\n", __func__, i);
|
||||
|
||||
data = devm_kzalloc(s2mu005->dev, sizeof(struct s2mu005_led),
|
||||
GFP_KERNEL);
|
||||
if (!data) {
|
||||
pr_err("[%s] memory allocation error data\n",
|
||||
__func__);
|
||||
ret = -ENOMEM;
|
||||
continue;
|
||||
}
|
||||
|
||||
memcpy(data, &(pdata->leds[i]), sizeof(struct s2mu005_led));
|
||||
led_data = devm_kzalloc(&pdev->dev,
|
||||
sizeof(struct s2mu005_led_data), GFP_KERNEL);
|
||||
|
||||
g_led_datas[i] = led_data;
|
||||
led_datas[i] = led_data;
|
||||
|
||||
if (!led_data) {
|
||||
pr_err("[%s] memory allocation error led_data\n",
|
||||
__func__);
|
||||
kfree(data);
|
||||
ret = -ENOMEM;
|
||||
continue;
|
||||
}
|
||||
|
||||
led_data->i2c = s2mu005->i2c;
|
||||
led_data->data = data;
|
||||
led_data->cdev.name = data->name;
|
||||
led_data->cdev.brightness_set = s2mu005_led_set;
|
||||
led_data->cdev.flags = 0;
|
||||
led_data->cdev.brightness = data->brightness;
|
||||
led_data->cdev.max_brightness = led_data->data->id ?
|
||||
S2MU005_TORCH_OUT_I_400MA : S2MU005_FLASH_OUT_I_1200MA;
|
||||
|
||||
mutex_init(&led_data->lock);
|
||||
spin_lock_init(&led_data->value_lock);
|
||||
INIT_WORK(&led_data->work, s2mu005_led_work);
|
||||
ret = led_classdev_register(&pdev->dev, &led_data->cdev);
|
||||
if (ret < 0) {
|
||||
pr_err("unable to register LED\n");
|
||||
cancel_work_sync(&led_data->work);
|
||||
mutex_destroy(&led_data->lock);
|
||||
kfree(data);
|
||||
kfree(led_data);
|
||||
led_datas[i] = NULL;
|
||||
g_led_datas[i] = NULL;
|
||||
ret = -EFAULT;
|
||||
continue;
|
||||
}
|
||||
if (led_data->data->id == S2MU005_TORCH_LED) {
|
||||
ret = device_create_file(led_data->cdev.dev,
|
||||
&dev_attr_rear_flash);
|
||||
if (ret < 0)
|
||||
pr_err("%s :unable to create file\n", __func__);
|
||||
}
|
||||
#ifndef CONFIG_S2MU005_LEDS_I2C
|
||||
if (gpio_is_valid(pdata->torch_pin) &&
|
||||
gpio_is_valid(pdata->flash_pin)) {
|
||||
if (ret < 0) {
|
||||
pr_err("%s : s2mu005 fled gpio allocation error\n",
|
||||
__func__);
|
||||
} else {
|
||||
led_data->torch_pin = pdata->torch_pin;
|
||||
led_data->flash_pin = pdata->flash_pin;
|
||||
gpio_request_one(pdata->torch_pin,
|
||||
GPIOF_OUT_INIT_LOW, "LED_GPIO_OUTPUT_LOW");
|
||||
gpio_request_one(pdata->flash_pin,
|
||||
GPIOF_OUT_INIT_LOW, "LED_GPIO_OUTPUT_LOW");
|
||||
gpio_free(pdata->torch_pin);
|
||||
gpio_free(pdata->flash_pin);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
/* EVT0 0x73[3:0] == 0x0 */
|
||||
ret = s2mu005_read_reg(led_data->i2c, 0x73, &temp);
|
||||
if (ret < 0)
|
||||
pr_err("%s : s2mu005 reg fled read fail\n", __func__);
|
||||
|
||||
if ((temp & 0xf) == 0x00) {
|
||||
/* FLED_CTRL4 = 0x3A */
|
||||
CH_FLASH_TORCH_EN = S2MU005_REG_FLED_CTRL4;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MUIC_NOTIFIER
|
||||
muic_notifier_register(&led_data->batt_nb,
|
||||
ta_notification,
|
||||
MUIC_NOTIFY_DEV_CHARGER);
|
||||
#endif
|
||||
ret = s2mu005_led_setup(led_data);
|
||||
if (ret < 0)
|
||||
pr_err("%s : failed s2mu005 led reg init\n", __func__);
|
||||
}
|
||||
#ifdef CONFIG_OF
|
||||
kfree(pdata);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s2mu005_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct s2mu005_led_data **led_datas = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i != S2MU005_LED_MAX; ++i) {
|
||||
if (led_datas[i] == NULL)
|
||||
continue;
|
||||
|
||||
cancel_work_sync(&led_datas[i]->work);
|
||||
mutex_destroy(&led_datas[i]->lock);
|
||||
led_classdev_unregister(&led_datas[i]->cdev);
|
||||
kfree(led_datas[i]->data);
|
||||
kfree(led_datas[i]);
|
||||
g_led_datas[i] = NULL;
|
||||
}
|
||||
kfree(led_datas);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct platform_device_id s2mu005_leds_id[] = {
|
||||
{"s2mu005-flash", 0},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct platform_driver s2mu005_led_driver = {
|
||||
.driver = {
|
||||
.name = "s2mu005-flash",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = s2mu005_led_probe,
|
||||
.remove = s2mu005_led_remove,
|
||||
.id_table = s2mu005_leds_id,
|
||||
};
|
||||
|
||||
static int __init s2mu005_led_driver_init(void)
|
||||
{
|
||||
return platform_driver_register(&s2mu005_led_driver);
|
||||
}
|
||||
module_init(s2mu005_led_driver_init);
|
||||
|
||||
static void __exit s2mu005_led_driver_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&s2mu005_led_driver);
|
||||
}
|
||||
module_exit(s2mu005_led_driver_exit);
|
||||
|
||||
MODULE_AUTHOR("SUJI LEE <suji0908.lee@samsung.com>");
|
||||
MODULE_DESCRIPTION("SAMSUNG s2mu005 LED Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
128
drivers/leds/leds-s3c24xx.c
Normal file
128
drivers/leds/leds-s3c24xx.c
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/* drivers/leds/leds-s3c24xx.c
|
||||
*
|
||||
* (c) 2006 Simtec Electronics
|
||||
* http://armlinux.simtec.co.uk/
|
||||
* Ben Dooks <ben@simtec.co.uk>
|
||||
*
|
||||
* S3C24XX - LEDs GPIO driver
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/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/regs-gpio.h>
|
||||
#include <plat/gpio-cfg.h>
|
||||
|
||||
/* our context */
|
||||
|
||||
struct s3c24xx_gpio_led {
|
||||
struct led_classdev cdev;
|
||||
struct s3c24xx_led_platdata *pdata;
|
||||
};
|
||||
|
||||
static inline struct s3c24xx_gpio_led *pdev_to_gpio(struct platform_device *dev)
|
||||
{
|
||||
return platform_get_drvdata(dev);
|
||||
}
|
||||
|
||||
static inline struct s3c24xx_gpio_led *to_gpio(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct s3c24xx_gpio_led, cdev);
|
||||
}
|
||||
|
||||
static void s3c24xx_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct s3c24xx_gpio_led *led = to_gpio(led_cdev);
|
||||
struct s3c24xx_led_platdata *pd = led->pdata;
|
||||
int state = (value ? 1 : 0) ^ (pd->flags & S3C24XX_LEDF_ACTLOW);
|
||||
|
||||
/* there will be a short delay between setting the output and
|
||||
* going from output to input when using tristate. */
|
||||
|
||||
gpio_set_value(pd->gpio, state);
|
||||
|
||||
if (pd->flags & S3C24XX_LEDF_TRISTATE) {
|
||||
if (value)
|
||||
gpio_direction_output(pd->gpio, state);
|
||||
else
|
||||
gpio_direction_input(pd->gpio);
|
||||
}
|
||||
}
|
||||
|
||||
static int s3c24xx_led_remove(struct platform_device *dev)
|
||||
{
|
||||
struct s3c24xx_gpio_led *led = pdev_to_gpio(dev);
|
||||
|
||||
led_classdev_unregister(&led->cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int s3c24xx_led_probe(struct platform_device *dev)
|
||||
{
|
||||
struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev);
|
||||
struct s3c24xx_gpio_led *led;
|
||||
int ret;
|
||||
|
||||
led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led),
|
||||
GFP_KERNEL);
|
||||
if (!led)
|
||||
return -ENOMEM;
|
||||
|
||||
platform_set_drvdata(dev, led);
|
||||
|
||||
led->cdev.brightness_set = s3c24xx_led_set;
|
||||
led->cdev.default_trigger = pdata->def_trigger;
|
||||
led->cdev.name = pdata->name;
|
||||
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
|
||||
led->pdata = pdata;
|
||||
|
||||
ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* no point in having a pull-up if we are always driving */
|
||||
|
||||
s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE);
|
||||
|
||||
if (pdata->flags & S3C24XX_LEDF_TRISTATE)
|
||||
gpio_direction_input(pdata->gpio);
|
||||
else
|
||||
gpio_direction_output(pdata->gpio,
|
||||
pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0);
|
||||
|
||||
/* register our new led device */
|
||||
|
||||
ret = led_classdev_register(&dev->dev, &led->cdev);
|
||||
if (ret < 0)
|
||||
dev_err(&dev->dev, "led_classdev_register failed\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static struct platform_driver s3c24xx_led_driver = {
|
||||
.probe = s3c24xx_led_probe,
|
||||
.remove = s3c24xx_led_remove,
|
||||
.driver = {
|
||||
.name = "s3c24xx_led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(s3c24xx_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>");
|
||||
MODULE_DESCRIPTION("S3C24XX LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:s3c24xx_led");
|
||||
560
drivers/leds/leds-ss4200.c
Normal file
560
drivers/leds/leds-ss4200.c
Normal file
|
|
@ -0,0 +1,560 @@
|
|||
/*
|
||||
* SS4200-E Hardware API
|
||||
* Copyright (c) 2009, Intel Corporation.
|
||||
* Copyright IBM Corporation, 2009
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms and conditions of the GNU General Public License,
|
||||
* version 2, as published by the Free Software Foundation.
|
||||
*
|
||||
* This program is distributed in the hope it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Author: Dave Hansen <dave@sr71.net>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
|
||||
|
||||
#include <linux/dmi.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/ioport.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
MODULE_AUTHOR("Rodney Girod <rgirod@confocus.com>, Dave Hansen <dave@sr71.net>");
|
||||
MODULE_DESCRIPTION("Intel NAS/Home Server ICH7 GPIO Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
/*
|
||||
* ICH7 LPC/GPIO PCI Config register offsets
|
||||
*/
|
||||
#define PMBASE 0x040
|
||||
#define GPIO_BASE 0x048
|
||||
#define GPIO_CTRL 0x04c
|
||||
#define GPIO_EN 0x010
|
||||
|
||||
/*
|
||||
* The ICH7 GPIO register block is 64 bytes in size.
|
||||
*/
|
||||
#define ICH7_GPIO_SIZE 64
|
||||
|
||||
/*
|
||||
* Define register offsets within the ICH7 register block.
|
||||
*/
|
||||
#define GPIO_USE_SEL 0x000
|
||||
#define GP_IO_SEL 0x004
|
||||
#define GP_LVL 0x00c
|
||||
#define GPO_BLINK 0x018
|
||||
#define GPI_INV 0x030
|
||||
#define GPIO_USE_SEL2 0x034
|
||||
#define GP_IO_SEL2 0x038
|
||||
#define GP_LVL2 0x03c
|
||||
|
||||
/*
|
||||
* PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives.
|
||||
*/
|
||||
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) },
|
||||
{ } /* NULL entry */
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, ich7_lpc_pci_id);
|
||||
|
||||
static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id)
|
||||
{
|
||||
pr_info("detected '%s'\n", id->ident);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static bool nodetect;
|
||||
module_param_named(nodetect, nodetect, bool, 0);
|
||||
MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection");
|
||||
|
||||
/*
|
||||
* struct nas_led_whitelist - 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
|
||||
* makes it possible to keep the false positives (the model is
|
||||
* detected as working, but in reality it is not) as low as
|
||||
* possible.
|
||||
*/
|
||||
static struct dmi_system_id nas_led_whitelist[] __initdata = {
|
||||
{
|
||||
.callback = ss4200_led_dmi_callback,
|
||||
.ident = "Intel SS4200-E",
|
||||
.matches = {
|
||||
DMI_MATCH(DMI_SYS_VENDOR, "Intel"),
|
||||
DMI_MATCH(DMI_PRODUCT_NAME, "SS4200-E"),
|
||||
DMI_MATCH(DMI_PRODUCT_VERSION, "1.00.00")
|
||||
}
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
/*
|
||||
* Base I/O address assigned to the Power Management register block
|
||||
*/
|
||||
static u32 g_pm_io_base;
|
||||
|
||||
/*
|
||||
* Base I/O address assigned to the ICH7 GPIO register block
|
||||
*/
|
||||
static u32 nas_gpio_io_base;
|
||||
|
||||
/*
|
||||
* When we successfully register a region, we are returned a resource.
|
||||
* We use these to identify which regions we need to release on our way
|
||||
* back out.
|
||||
*/
|
||||
static struct resource *gp_gpio_resource;
|
||||
|
||||
struct nasgpio_led {
|
||||
char *name;
|
||||
u32 gpio_bit;
|
||||
struct led_classdev led_cdev;
|
||||
};
|
||||
|
||||
/*
|
||||
* gpio_bit(s) are the ICH7 GPIO bit assignments
|
||||
*/
|
||||
static struct nasgpio_led nasgpio_leds[] = {
|
||||
{ .name = "hdd1:blue:sata", .gpio_bit = 0 },
|
||||
{ .name = "hdd1:amber:sata", .gpio_bit = 1 },
|
||||
{ .name = "hdd2:blue:sata", .gpio_bit = 2 },
|
||||
{ .name = "hdd2:amber:sata", .gpio_bit = 3 },
|
||||
{ .name = "hdd3:blue:sata", .gpio_bit = 4 },
|
||||
{ .name = "hdd3:amber:sata", .gpio_bit = 5 },
|
||||
{ .name = "hdd4:blue:sata", .gpio_bit = 6 },
|
||||
{ .name = "hdd4:amber:sata", .gpio_bit = 7 },
|
||||
{ .name = "power:blue:power", .gpio_bit = 27},
|
||||
{ .name = "power:amber:power", .gpio_bit = 28},
|
||||
};
|
||||
|
||||
#define NAS_RECOVERY 0x00000400 /* GPIO10 */
|
||||
|
||||
static struct nasgpio_led *
|
||||
led_classdev_to_nasgpio_led(struct led_classdev *led_cdev)
|
||||
{
|
||||
return container_of(led_cdev, struct nasgpio_led, led_cdev);
|
||||
}
|
||||
|
||||
static struct nasgpio_led *get_led_named(char *name)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) {
|
||||
if (strcmp(nasgpio_leds[i].name, name))
|
||||
continue;
|
||||
return &nasgpio_leds[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* This protects access to the gpio ports.
|
||||
*/
|
||||
static DEFINE_SPINLOCK(nasgpio_gpio_lock);
|
||||
|
||||
/*
|
||||
* There are two gpio ports, one for blinking and the other
|
||||
* for power. @port tells us if we're doing blinking or
|
||||
* power control.
|
||||
*
|
||||
* Caller must hold nasgpio_gpio_lock
|
||||
*/
|
||||
static void __nasgpio_led_set_attr(struct led_classdev *led_cdev,
|
||||
u32 port, u32 value)
|
||||
{
|
||||
struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev);
|
||||
u32 gpio_out;
|
||||
|
||||
gpio_out = inl(nas_gpio_io_base + port);
|
||||
if (value)
|
||||
gpio_out |= (1<<led->gpio_bit);
|
||||
else
|
||||
gpio_out &= ~(1<<led->gpio_bit);
|
||||
|
||||
outl(gpio_out, nas_gpio_io_base + port);
|
||||
}
|
||||
|
||||
static void nasgpio_led_set_attr(struct led_classdev *led_cdev,
|
||||
u32 port, u32 value)
|
||||
{
|
||||
spin_lock(&nasgpio_gpio_lock);
|
||||
__nasgpio_led_set_attr(led_cdev, port, value);
|
||||
spin_unlock(&nasgpio_gpio_lock);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
spin_lock(&nasgpio_gpio_lock);
|
||||
gpio_in = inl(nas_gpio_io_base + port);
|
||||
spin_unlock(&nasgpio_gpio_lock);
|
||||
if (gpio_in & (1<<led->gpio_bit))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* There is actual brightness control in the hardware,
|
||||
* but it is via smbus commands and not implemented
|
||||
* in this driver.
|
||||
*/
|
||||
static void nasgpio_led_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness brightness)
|
||||
{
|
||||
u32 setting = 0;
|
||||
if (brightness >= LED_HALF)
|
||||
setting = 1;
|
||||
/*
|
||||
* Hold the lock across both operations. This ensures
|
||||
* consistency so that both the "turn off blinking"
|
||||
* and "turn light off" operations complete as a set.
|
||||
*/
|
||||
spin_lock(&nasgpio_gpio_lock);
|
||||
/*
|
||||
* LED class documentation asks that past blink state
|
||||
* be disabled when brightness is turned to zero.
|
||||
*/
|
||||
if (brightness == 0)
|
||||
__nasgpio_led_set_attr(led_cdev, GPO_BLINK, 0);
|
||||
__nasgpio_led_set_attr(led_cdev, GP_LVL, setting);
|
||||
spin_unlock(&nasgpio_gpio_lock);
|
||||
}
|
||||
|
||||
static int nasgpio_led_set_blink(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
u32 setting = 1;
|
||||
if (!(*delay_on == 0 && *delay_off == 0) &&
|
||||
!(*delay_on == 500 && *delay_off == 500))
|
||||
return -EINVAL;
|
||||
/*
|
||||
* These are very approximate.
|
||||
*/
|
||||
*delay_on = 500;
|
||||
*delay_off = 500;
|
||||
|
||||
nasgpio_led_set_attr(led_cdev, GPO_BLINK, setting);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Initialize the ICH7 GPIO registers for NAS usage. The BIOS should have
|
||||
* 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 ich7_gpio_init(struct device *dev)
|
||||
{
|
||||
int i;
|
||||
u32 config_data = 0;
|
||||
u32 all_nas_led = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++)
|
||||
all_nas_led |= (1<<nasgpio_leds[i].gpio_bit);
|
||||
|
||||
spin_lock(&nasgpio_gpio_lock);
|
||||
/*
|
||||
* We need to enable all of the GPIO lines used by the NAS box,
|
||||
* so we will read the current Use Selection and add our usage
|
||||
* to it. This should be benign with regard to the original
|
||||
* BIOS configuration.
|
||||
*/
|
||||
config_data = inl(nas_gpio_io_base + GPIO_USE_SEL);
|
||||
dev_dbg(dev, ": Data read from GPIO_USE_SEL = 0x%08x\n", config_data);
|
||||
config_data |= all_nas_led + NAS_RECOVERY;
|
||||
outl(config_data, nas_gpio_io_base + GPIO_USE_SEL);
|
||||
config_data = inl(nas_gpio_io_base + GPIO_USE_SEL);
|
||||
dev_dbg(dev, ": GPIO_USE_SEL = 0x%08x\n\n", config_data);
|
||||
|
||||
/*
|
||||
* The LED GPIO outputs need to be configured for output, so we
|
||||
* will ensure that all LED lines are cleared for output and the
|
||||
* RECOVERY line ready for input. This too should be benign with
|
||||
* regard to BIOS configuration.
|
||||
*/
|
||||
config_data = inl(nas_gpio_io_base + GP_IO_SEL);
|
||||
dev_dbg(dev, ": Data read from GP_IO_SEL = 0x%08x\n",
|
||||
config_data);
|
||||
config_data &= ~all_nas_led;
|
||||
config_data |= NAS_RECOVERY;
|
||||
outl(config_data, nas_gpio_io_base + GP_IO_SEL);
|
||||
config_data = inl(nas_gpio_io_base + GP_IO_SEL);
|
||||
dev_dbg(dev, ": GP_IO_SEL = 0x%08x\n", config_data);
|
||||
|
||||
/*
|
||||
* In our final system, the BIOS will initialize the state of all
|
||||
* of the LEDs. For now, we turn them all off (or Low).
|
||||
*/
|
||||
config_data = inl(nas_gpio_io_base + GP_LVL);
|
||||
dev_dbg(dev, ": Data read from GP_LVL = 0x%08x\n", config_data);
|
||||
/*
|
||||
* In our final system, the BIOS will initialize the blink state of all
|
||||
* of the LEDs. For now, we turn blink off for all of them.
|
||||
*/
|
||||
config_data = inl(nas_gpio_io_base + GPO_BLINK);
|
||||
dev_dbg(dev, ": Data read from GPO_BLINK = 0x%08x\n", config_data);
|
||||
|
||||
/*
|
||||
* At this moment, I am unsure if anything needs to happen with GPI_INV
|
||||
*/
|
||||
config_data = inl(nas_gpio_io_base + GPI_INV);
|
||||
dev_dbg(dev, ": Data read from GPI_INV = 0x%08x\n", config_data);
|
||||
|
||||
spin_unlock(&nasgpio_gpio_lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ich7_lpc_cleanup(struct device *dev)
|
||||
{
|
||||
/*
|
||||
* If we were given exclusive use of the GPIO
|
||||
* I/O Address range, we must return it.
|
||||
*/
|
||||
if (gp_gpio_resource) {
|
||||
dev_dbg(dev, ": Releasing GPIO I/O addresses\n");
|
||||
release_region(nas_gpio_io_base, ICH7_GPIO_SIZE);
|
||||
gp_gpio_resource = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The OS has determined that the LPC of the Intel ICH7 Southbridge is present
|
||||
* so we can retrive the required operational information and prepare the GPIO.
|
||||
*/
|
||||
static struct pci_dev *nas_gpio_pci_dev;
|
||||
static int ich7_lpc_probe(struct pci_dev *dev,
|
||||
const struct pci_device_id *id)
|
||||
{
|
||||
int status;
|
||||
u32 gc = 0;
|
||||
|
||||
status = pci_enable_device(dev);
|
||||
if (status) {
|
||||
dev_err(&dev->dev, "pci_enable_device failed\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
nas_gpio_pci_dev = dev;
|
||||
status = pci_read_config_dword(dev, PMBASE, &g_pm_io_base);
|
||||
if (status)
|
||||
goto out;
|
||||
g_pm_io_base &= 0x00000ff80;
|
||||
|
||||
status = pci_read_config_dword(dev, GPIO_CTRL, &gc);
|
||||
if (!(GPIO_EN & gc)) {
|
||||
status = -EEXIST;
|
||||
dev_info(&dev->dev,
|
||||
"ERROR: The LPC GPIO Block has not been enabled.\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
status = pci_read_config_dword(dev, GPIO_BASE, &nas_gpio_io_base);
|
||||
if (0 > status) {
|
||||
dev_info(&dev->dev, "Unable to read GPIOBASE.\n");
|
||||
goto out;
|
||||
}
|
||||
dev_dbg(&dev->dev, ": GPIOBASE = 0x%08x\n", nas_gpio_io_base);
|
||||
nas_gpio_io_base &= 0x00000ffc0;
|
||||
|
||||
/*
|
||||
* Insure that we have exclusive access to the GPIO I/O address range.
|
||||
*/
|
||||
gp_gpio_resource = request_region(nas_gpio_io_base, ICH7_GPIO_SIZE,
|
||||
KBUILD_MODNAME);
|
||||
if (NULL == gp_gpio_resource) {
|
||||
dev_info(&dev->dev,
|
||||
"ERROR Unable to register GPIO I/O addresses.\n");
|
||||
status = -1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Initialize the GPIO for NAS/Home Server Use
|
||||
*/
|
||||
ich7_gpio_init(&dev->dev);
|
||||
|
||||
out:
|
||||
if (status) {
|
||||
ich7_lpc_cleanup(&dev->dev);
|
||||
pci_disable_device(dev);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
static void ich7_lpc_remove(struct pci_dev *dev)
|
||||
{
|
||||
ich7_lpc_cleanup(&dev->dev);
|
||||
pci_disable_device(dev);
|
||||
}
|
||||
|
||||
/*
|
||||
* pci_driver structure passed to the PCI modules
|
||||
*/
|
||||
static struct pci_driver nas_gpio_pci_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.id_table = ich7_lpc_pci_id,
|
||||
.probe = ich7_lpc_probe,
|
||||
.remove = ich7_lpc_remove,
|
||||
};
|
||||
|
||||
static struct led_classdev *get_classdev_for_led_nr(int nr)
|
||||
{
|
||||
struct nasgpio_led *nas_led = &nasgpio_leds[nr];
|
||||
struct led_classdev *led = &nas_led->led_cdev;
|
||||
return led;
|
||||
}
|
||||
|
||||
|
||||
static void set_power_light_amber_noblink(void)
|
||||
{
|
||||
struct nasgpio_led *amber = get_led_named("power:amber:power");
|
||||
struct nasgpio_led *blue = get_led_named("power:blue:power");
|
||||
|
||||
if (!amber || !blue)
|
||||
return;
|
||||
/*
|
||||
* LED_OFF implies disabling future blinking
|
||||
*/
|
||||
pr_debug("setting blue off and amber on\n");
|
||||
|
||||
nasgpio_led_set_brightness(&blue->led_cdev, LED_OFF);
|
||||
nasgpio_led_set_brightness(&amber->led_cdev, LED_FULL);
|
||||
}
|
||||
|
||||
static ssize_t nas_led_blink_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
int blinking = 0;
|
||||
if (nasgpio_led_get_attr(led, GPO_BLINK))
|
||||
blinking = 1;
|
||||
return sprintf(buf, "%u\n", blinking);
|
||||
}
|
||||
|
||||
static ssize_t nas_led_blink_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
int ret;
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
unsigned long blink_state;
|
||||
|
||||
ret = kstrtoul(buf, 10, &blink_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
nasgpio_led_set_attr(led, GPO_BLINK, blink_state);
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(blink, 0644, nas_led_blink_show, nas_led_blink_store);
|
||||
|
||||
static struct attribute *nasgpio_led_attrs[] = {
|
||||
&dev_attr_blink.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(nasgpio_led);
|
||||
|
||||
static int register_nasgpio_led(int led_nr)
|
||||
{
|
||||
int ret;
|
||||
struct nasgpio_led *nas_led = &nasgpio_leds[led_nr];
|
||||
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
|
||||
|
||||
led->name = nas_led->name;
|
||||
led->brightness = LED_OFF;
|
||||
if (nasgpio_led_get_attr(led, GP_LVL))
|
||||
led->brightness = LED_FULL;
|
||||
led->brightness_set = nasgpio_led_set_brightness;
|
||||
led->blink_set = nasgpio_led_set_blink;
|
||||
led->groups = nasgpio_led_groups;
|
||||
ret = led_classdev_register(&nas_gpio_pci_dev->dev, led);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void unregister_nasgpio_led(int led_nr)
|
||||
{
|
||||
struct led_classdev *led = get_classdev_for_led_nr(led_nr);
|
||||
led_classdev_unregister(led);
|
||||
}
|
||||
/*
|
||||
* module load/initialization
|
||||
*/
|
||||
static int __init nas_gpio_init(void)
|
||||
{
|
||||
int i;
|
||||
int ret = 0;
|
||||
int nr_devices = 0;
|
||||
|
||||
nr_devices = dmi_check_system(nas_led_whitelist);
|
||||
if (nodetect) {
|
||||
pr_info("skipping hardware autodetection\n");
|
||||
pr_info("Please send 'dmidecode' output to dave@sr71.net\n");
|
||||
nr_devices++;
|
||||
}
|
||||
|
||||
if (nr_devices <= 0) {
|
||||
pr_info("no LED devices found\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
pr_info("registering PCI driver\n");
|
||||
ret = pci_register_driver(&nas_gpio_pci_driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++) {
|
||||
ret = register_nasgpio_led(i);
|
||||
if (ret)
|
||||
goto out_err;
|
||||
}
|
||||
/*
|
||||
* When the system powers on, the BIOS leaves the power
|
||||
* light blue and blinking. This will turn it solid
|
||||
* amber once the driver is loaded.
|
||||
*/
|
||||
set_power_light_amber_noblink();
|
||||
return 0;
|
||||
out_err:
|
||||
for (i--; i >= 0; i--)
|
||||
unregister_nasgpio_led(i);
|
||||
pci_unregister_driver(&nas_gpio_pci_driver);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* module unload
|
||||
*/
|
||||
static void __exit nas_gpio_exit(void)
|
||||
{
|
||||
int i;
|
||||
pr_info("Unregistering driver\n");
|
||||
for (i = 0; i < ARRAY_SIZE(nasgpio_leds); i++)
|
||||
unregister_nasgpio_led(i);
|
||||
pci_unregister_driver(&nas_gpio_pci_driver);
|
||||
}
|
||||
|
||||
module_init(nas_gpio_init);
|
||||
module_exit(nas_gpio_exit);
|
||||
264
drivers/leds/leds-sunfire.c
Normal file
264
drivers/leds/leds-sunfire.c
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
/* leds-sunfire.c: SUNW,Ultra-Enterprise LED driver.
|
||||
*
|
||||
* 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>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <asm/fhc.h>
|
||||
#include <asm/upa.h>
|
||||
|
||||
MODULE_AUTHOR("David S. Miller (davem@davemloft.net)");
|
||||
MODULE_DESCRIPTION("Sun Fire LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
struct sunfire_led {
|
||||
struct led_classdev led_cdev;
|
||||
void __iomem *reg;
|
||||
};
|
||||
#define to_sunfire_led(d) container_of(d, struct sunfire_led, led_cdev)
|
||||
|
||||
static void __clockboard_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val, u8 bit)
|
||||
{
|
||||
struct sunfire_led *p = to_sunfire_led(led_cdev);
|
||||
u8 reg = upa_readb(p->reg);
|
||||
|
||||
switch (bit) {
|
||||
case CLOCK_CTRL_LLED:
|
||||
if (led_val)
|
||||
reg &= ~bit;
|
||||
else
|
||||
reg |= bit;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (led_val)
|
||||
reg |= bit;
|
||||
else
|
||||
reg &= ~bit;
|
||||
break;
|
||||
}
|
||||
upa_writeb(reg, p->reg);
|
||||
}
|
||||
|
||||
static void clockboard_left_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__clockboard_set(led_cdev, led_val, CLOCK_CTRL_LLED);
|
||||
}
|
||||
|
||||
static void clockboard_middle_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__clockboard_set(led_cdev, led_val, CLOCK_CTRL_MLED);
|
||||
}
|
||||
|
||||
static void clockboard_right_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__clockboard_set(led_cdev, led_val, CLOCK_CTRL_RLED);
|
||||
}
|
||||
|
||||
static void __fhc_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val, u32 bit)
|
||||
{
|
||||
struct sunfire_led *p = to_sunfire_led(led_cdev);
|
||||
u32 reg = upa_readl(p->reg);
|
||||
|
||||
switch (bit) {
|
||||
case FHC_CONTROL_LLED:
|
||||
if (led_val)
|
||||
reg &= ~bit;
|
||||
else
|
||||
reg |= bit;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (led_val)
|
||||
reg |= bit;
|
||||
else
|
||||
reg &= ~bit;
|
||||
break;
|
||||
}
|
||||
upa_writel(reg, p->reg);
|
||||
}
|
||||
|
||||
static void fhc_left_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__fhc_set(led_cdev, led_val, FHC_CONTROL_LLED);
|
||||
}
|
||||
|
||||
static void fhc_middle_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__fhc_set(led_cdev, led_val, FHC_CONTROL_MLED);
|
||||
}
|
||||
|
||||
static void fhc_right_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness led_val)
|
||||
{
|
||||
__fhc_set(led_cdev, led_val, FHC_CONTROL_RLED);
|
||||
}
|
||||
|
||||
typedef void (*set_handler)(struct led_classdev *, enum led_brightness);
|
||||
struct led_type {
|
||||
const char *name;
|
||||
set_handler handler;
|
||||
const char *default_trigger;
|
||||
};
|
||||
|
||||
#define NUM_LEDS_PER_BOARD 3
|
||||
struct sunfire_drvdata {
|
||||
struct sunfire_led leds[NUM_LEDS_PER_BOARD];
|
||||
};
|
||||
|
||||
static int sunfire_led_generic_probe(struct platform_device *pdev,
|
||||
struct led_type *types)
|
||||
{
|
||||
struct sunfire_drvdata *p;
|
||||
int i, err;
|
||||
|
||||
if (pdev->num_resources != 1) {
|
||||
dev_err(&pdev->dev, "Wrong number of resources %d, should be 1\n",
|
||||
pdev->num_resources);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
p->leds[i].reg = (void __iomem *) pdev->resource[0].start;
|
||||
lp->name = types[i].name;
|
||||
lp->brightness = LED_FULL;
|
||||
lp->brightness_set = types[i].handler;
|
||||
lp->default_trigger = types[i].default_trigger;
|
||||
|
||||
err = led_classdev_register(&pdev->dev, lp);
|
||||
if (err) {
|
||||
dev_err(&pdev->dev, "Could not register %s LED\n",
|
||||
lp->name);
|
||||
for (i--; i >= 0; i--)
|
||||
led_classdev_unregister(&p->leds[i].led_cdev);
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sunfire_led_generic_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct sunfire_drvdata *p = platform_get_drvdata(pdev);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < NUM_LEDS_PER_BOARD; i++)
|
||||
led_classdev_unregister(&p->leds[i].led_cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = {
|
||||
{
|
||||
.name = "clockboard-left",
|
||||
.handler = clockboard_left_set,
|
||||
},
|
||||
{
|
||||
.name = "clockboard-middle",
|
||||
.handler = clockboard_middle_set,
|
||||
},
|
||||
{
|
||||
.name = "clockboard-right",
|
||||
.handler = clockboard_right_set,
|
||||
.default_trigger = "heartbeat",
|
||||
},
|
||||
};
|
||||
|
||||
static int sunfire_clockboard_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
return sunfire_led_generic_probe(pdev, clockboard_led_types);
|
||||
}
|
||||
|
||||
static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = {
|
||||
{
|
||||
.name = "fhc-left",
|
||||
.handler = fhc_left_set,
|
||||
},
|
||||
{
|
||||
.name = "fhc-middle",
|
||||
.handler = fhc_middle_set,
|
||||
},
|
||||
{
|
||||
.name = "fhc-right",
|
||||
.handler = fhc_right_set,
|
||||
.default_trigger = "heartbeat",
|
||||
},
|
||||
};
|
||||
|
||||
static int sunfire_fhc_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
return sunfire_led_generic_probe(pdev, fhc_led_types);
|
||||
}
|
||||
|
||||
MODULE_ALIAS("platform:sunfire-clockboard-leds");
|
||||
MODULE_ALIAS("platform:sunfire-fhc-leds");
|
||||
|
||||
static struct platform_driver sunfire_clockboard_led_driver = {
|
||||
.probe = sunfire_clockboard_led_probe,
|
||||
.remove = sunfire_led_generic_remove,
|
||||
.driver = {
|
||||
.name = "sunfire-clockboard-leds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static struct platform_driver sunfire_fhc_led_driver = {
|
||||
.probe = sunfire_fhc_led_probe,
|
||||
.remove = sunfire_led_generic_remove,
|
||||
.driver = {
|
||||
.name = "sunfire-fhc-leds",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init sunfire_leds_init(void)
|
||||
{
|
||||
int err = platform_driver_register(&sunfire_clockboard_led_driver);
|
||||
|
||||
if (err) {
|
||||
pr_err("Could not register clock board LED driver\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
err = platform_driver_register(&sunfire_fhc_led_driver);
|
||||
if (err) {
|
||||
pr_err("Could not register FHC LED driver\n");
|
||||
platform_driver_unregister(&sunfire_clockboard_led_driver);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit sunfire_leds_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&sunfire_clockboard_led_driver);
|
||||
platform_driver_unregister(&sunfire_fhc_led_driver);
|
||||
}
|
||||
|
||||
module_init(sunfire_leds_init);
|
||||
module_exit(sunfire_leds_exit);
|
||||
166
drivers/leds/leds-syscon.c
Normal file
166
drivers/leds/leds-syscon.c
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* Generic Syscon LEDs Driver
|
||||
*
|
||||
* Copyright (c) 2014, Linaro Limited
|
||||
* Author: Linus Walleij <linus.walleij@linaro.org>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston,
|
||||
* MA 02111-1307 USA
|
||||
*
|
||||
* This driver provides system reboot functionality for APM X-Gene SoC.
|
||||
* For system shutdown, this is board specify. If a board designer
|
||||
* implements GPIO shutdown, use the gpio-poweroff.c driver.
|
||||
*/
|
||||
#include <linux/io.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_address.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/stat.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
/**
|
||||
* struct syscon_led - state container for syscon based LEDs
|
||||
* @cdev: LED class device for this LED
|
||||
* @map: regmap to access the syscon device backing this LED
|
||||
* @offset: the offset into the syscon regmap for the LED register
|
||||
* @mask: the bit in the register corresponding to the LED
|
||||
* @state: current state of the LED
|
||||
*/
|
||||
struct syscon_led {
|
||||
struct led_classdev cdev;
|
||||
struct regmap *map;
|
||||
u32 offset;
|
||||
u32 mask;
|
||||
bool state;
|
||||
};
|
||||
|
||||
static void syscon_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct syscon_led *sled =
|
||||
container_of(led_cdev, struct syscon_led, cdev);
|
||||
u32 val;
|
||||
int ret;
|
||||
|
||||
if (value == LED_OFF) {
|
||||
val = 0;
|
||||
sled->state = false;
|
||||
} else {
|
||||
val = sled->mask;
|
||||
sled->state = true;
|
||||
}
|
||||
|
||||
ret = regmap_update_bits(sled->map, sled->offset, sled->mask, val);
|
||||
if (ret < 0)
|
||||
dev_err(sled->cdev.dev, "error updating LED status\n");
|
||||
}
|
||||
|
||||
static const struct of_device_id syscon_match[] = {
|
||||
{ .compatible = "syscon", },
|
||||
{},
|
||||
};
|
||||
|
||||
static int __init syscon_leds_init(void)
|
||||
{
|
||||
const struct of_device_id *devid;
|
||||
struct device_node *np;
|
||||
struct device_node *child;
|
||||
struct regmap *map;
|
||||
struct platform_device *pdev;
|
||||
struct device *dev;
|
||||
int ret;
|
||||
|
||||
np = of_find_matching_node_and_match(NULL, syscon_match,
|
||||
&devid);
|
||||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
map = syscon_node_to_regmap(np);
|
||||
if (IS_ERR(map))
|
||||
return PTR_ERR(map);
|
||||
|
||||
/*
|
||||
* If the map is there, the device should be there, we allocate
|
||||
* memory on the syscon device's behalf here.
|
||||
*/
|
||||
pdev = of_find_device_by_node(np);
|
||||
if (!pdev)
|
||||
return -ENODEV;
|
||||
dev = &pdev->dev;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
struct syscon_led *sled;
|
||||
const char *state;
|
||||
|
||||
/* Only check for register-bit-leds */
|
||||
if (of_property_match_string(child, "compatible",
|
||||
"register-bit-led") < 0)
|
||||
continue;
|
||||
|
||||
sled = devm_kzalloc(dev, sizeof(*sled), GFP_KERNEL);
|
||||
if (!sled)
|
||||
return -ENOMEM;
|
||||
|
||||
sled->map = map;
|
||||
|
||||
if (of_property_read_u32(child, "offset", &sled->offset))
|
||||
return -EINVAL;
|
||||
if (of_property_read_u32(child, "mask", &sled->mask))
|
||||
return -EINVAL;
|
||||
sled->cdev.name =
|
||||
of_get_property(child, "label", NULL) ? : child->name;
|
||||
sled->cdev.default_trigger =
|
||||
of_get_property(child, "linux,default-trigger", NULL);
|
||||
|
||||
state = of_get_property(child, "default-state", NULL);
|
||||
if (state) {
|
||||
if (!strcmp(state, "keep")) {
|
||||
u32 val;
|
||||
|
||||
ret = regmap_read(map, sled->offset, &val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
sled->state = !!(val & sled->mask);
|
||||
} else if (!strcmp(state, "on")) {
|
||||
sled->state = true;
|
||||
ret = regmap_update_bits(map, sled->offset,
|
||||
sled->mask,
|
||||
sled->mask);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else {
|
||||
sled->state = false;
|
||||
ret = regmap_update_bits(map, sled->offset,
|
||||
sled->mask, 0);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
sled->cdev.brightness_set = syscon_led_set;
|
||||
|
||||
ret = led_classdev_register(dev, &sled->cdev);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
dev_info(dev, "registered LED %s\n", sled->cdev.name);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
device_initcall(syscon_leds_init);
|
||||
845
drivers/leds/leds-tca6507.c
Normal file
845
drivers/leds/leds-tca6507.c
Normal file
|
|
@ -0,0 +1,845 @@
|
|||
/*
|
||||
* 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)
|
||||
gpiochip_remove(&tca->gpio);
|
||||
}
|
||||
#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", ®);
|
||||
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");
|
||||
110
drivers/leds/leds-versatile.c
Normal file
110
drivers/leds/leds-versatile.c
Normal file
|
|
@ -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");
|
||||
326
drivers/leds/leds-wm831x-status.c
Normal file
326
drivers/leds/leds-wm831x-status.c
Normal file
|
|
@ -0,0 +1,326 @@
|
|||
/*
|
||||
* LED driver for WM831x status LEDs
|
||||
*
|
||||
* Copyright(C) 2009 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/err.h>
|
||||
#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 {
|
||||
struct led_classdev cdev;
|
||||
struct wm831x *wm831x;
|
||||
struct work_struct work;
|
||||
struct mutex mutex;
|
||||
|
||||
spinlock_t value_lock;
|
||||
int reg; /* Control register */
|
||||
int reg_val; /* Control register value */
|
||||
|
||||
int blink;
|
||||
int blink_time;
|
||||
int blink_cyc;
|
||||
int src;
|
||||
enum led_brightness brightness;
|
||||
};
|
||||
|
||||
#define to_wm831x_status(led_cdev) \
|
||||
container_of(led_cdev, struct wm831x_status, cdev)
|
||||
|
||||
static void wm831x_status_work(struct work_struct *work)
|
||||
{
|
||||
struct wm831x_status *led = container_of(work, struct wm831x_status,
|
||||
work);
|
||||
unsigned long flags;
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
|
||||
led->reg_val &= ~(WM831X_LED_SRC_MASK | WM831X_LED_MODE_MASK |
|
||||
WM831X_LED_DUTY_CYC_MASK | WM831X_LED_DUR_MASK);
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
|
||||
led->reg_val |= led->src << WM831X_LED_SRC_SHIFT;
|
||||
if (led->blink) {
|
||||
led->reg_val |= 2 << WM831X_LED_MODE_SHIFT;
|
||||
led->reg_val |= led->blink_time << WM831X_LED_DUR_SHIFT;
|
||||
led->reg_val |= led->blink_cyc;
|
||||
} else {
|
||||
if (led->brightness != LED_OFF)
|
||||
led->reg_val |= 1 << WM831X_LED_MODE_SHIFT;
|
||||
}
|
||||
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
|
||||
wm831x_reg_write(led->wm831x, led->reg, led->reg_val);
|
||||
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
|
||||
static void wm831x_status_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct wm831x_status *led = to_wm831x_status(led_cdev);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
led->brightness = value;
|
||||
if (value == LED_OFF)
|
||||
led->blink = 0;
|
||||
schedule_work(&led->work);
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
}
|
||||
|
||||
static int wm831x_status_blink_set(struct led_classdev *led_cdev,
|
||||
unsigned long *delay_on,
|
||||
unsigned long *delay_off)
|
||||
{
|
||||
struct wm831x_status *led = to_wm831x_status(led_cdev);
|
||||
unsigned long flags;
|
||||
int ret = 0;
|
||||
|
||||
/* Pick some defaults if we've not been given times */
|
||||
if (*delay_on == 0 && *delay_off == 0) {
|
||||
*delay_on = 250;
|
||||
*delay_off = 250;
|
||||
}
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
|
||||
/* We only have a limited selection of settings, see if we can
|
||||
* support the configuration we're being given */
|
||||
switch (*delay_on) {
|
||||
case 1000:
|
||||
led->blink_time = 0;
|
||||
break;
|
||||
case 250:
|
||||
led->blink_time = 1;
|
||||
break;
|
||||
case 125:
|
||||
led->blink_time = 2;
|
||||
break;
|
||||
case 62:
|
||||
case 63:
|
||||
/* Actually 62.5ms */
|
||||
led->blink_time = 3;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
switch (*delay_off / *delay_on) {
|
||||
case 1:
|
||||
led->blink_cyc = 0;
|
||||
break;
|
||||
case 3:
|
||||
led->blink_cyc = 1;
|
||||
break;
|
||||
case 4:
|
||||
led->blink_cyc = 2;
|
||||
break;
|
||||
case 8:
|
||||
led->blink_cyc = 3;
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ret == 0)
|
||||
led->blink = 1;
|
||||
else
|
||||
led->blink = 0;
|
||||
|
||||
/* Always update; if we fail turn off blinking since we expect
|
||||
* a software fallback. */
|
||||
schedule_work(&led->work);
|
||||
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char * const led_src_texts[] = {
|
||||
"otp",
|
||||
"power",
|
||||
"charger",
|
||||
"soft",
|
||||
};
|
||||
|
||||
static ssize_t wm831x_status_src_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct wm831x_status *led = to_wm831x_status(led_cdev);
|
||||
int i;
|
||||
ssize_t ret = 0;
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(led_src_texts); i++)
|
||||
if (i == led->src)
|
||||
ret += sprintf(&buf[ret], "[%s] ", led_src_texts[i]);
|
||||
else
|
||||
ret += sprintf(&buf[ret], "%s ", led_src_texts[i]);
|
||||
|
||||
mutex_unlock(&led->mutex);
|
||||
|
||||
ret += sprintf(&buf[ret], "\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t wm831x_status_src_store(struct device *dev,
|
||||
struct device_attribute *attr,
|
||||
const char *buf, size_t size)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
struct wm831x_status *led = to_wm831x_status(led_cdev);
|
||||
char name[20];
|
||||
int i;
|
||||
size_t len;
|
||||
|
||||
name[sizeof(name) - 1] = '\0';
|
||||
strncpy(name, buf, sizeof(name) - 1);
|
||||
len = strlen(name);
|
||||
|
||||
if (len && name[len - 1] == '\n')
|
||||
name[len - 1] = '\0';
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(led_src_texts); i++) {
|
||||
if (!strcmp(name, led_src_texts[i])) {
|
||||
mutex_lock(&led->mutex);
|
||||
|
||||
led->src = i;
|
||||
schedule_work(&led->work);
|
||||
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
static DEVICE_ATTR(src, 0644, wm831x_status_src_show, wm831x_status_src_store);
|
||||
|
||||
static struct attribute *wm831x_status_attrs[] = {
|
||||
&dev_attr_src.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(wm831x_status);
|
||||
|
||||
static int wm831x_status_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
|
||||
struct wm831x_pdata *chip_pdata;
|
||||
struct wm831x_status_pdata pdata;
|
||||
struct wm831x_status *drvdata;
|
||||
struct resource *res;
|
||||
int id = pdev->id % ARRAY_SIZE(chip_pdata->status);
|
||||
int ret;
|
||||
|
||||
res = platform_get_resource(pdev, IORESOURCE_REG, 0);
|
||||
if (res == NULL) {
|
||||
dev_err(&pdev->dev, "No register resource\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
drvdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_status),
|
||||
GFP_KERNEL);
|
||||
if (!drvdata)
|
||||
return -ENOMEM;
|
||||
platform_set_drvdata(pdev, drvdata);
|
||||
|
||||
drvdata->wm831x = wm831x;
|
||||
drvdata->reg = res->start;
|
||||
|
||||
if (dev_get_platdata(wm831x->dev))
|
||||
chip_pdata = dev_get_platdata(wm831x->dev);
|
||||
else
|
||||
chip_pdata = NULL;
|
||||
|
||||
memset(&pdata, 0, sizeof(pdata));
|
||||
if (chip_pdata && chip_pdata->status[id])
|
||||
memcpy(&pdata, chip_pdata->status[id], sizeof(pdata));
|
||||
else
|
||||
pdata.name = dev_name(&pdev->dev);
|
||||
|
||||
mutex_init(&drvdata->mutex);
|
||||
INIT_WORK(&drvdata->work, wm831x_status_work);
|
||||
spin_lock_init(&drvdata->value_lock);
|
||||
|
||||
/* We cache the configuration register and read startup values
|
||||
* from it. */
|
||||
drvdata->reg_val = wm831x_reg_read(wm831x, drvdata->reg);
|
||||
|
||||
if (drvdata->reg_val & WM831X_LED_MODE_MASK)
|
||||
drvdata->brightness = LED_FULL;
|
||||
else
|
||||
drvdata->brightness = LED_OFF;
|
||||
|
||||
/* Set a default source if configured, otherwise leave the
|
||||
* current hardware setting.
|
||||
*/
|
||||
if (pdata.default_src == WM831X_STATUS_PRESERVE) {
|
||||
drvdata->src = drvdata->reg_val;
|
||||
drvdata->src &= WM831X_LED_SRC_MASK;
|
||||
drvdata->src >>= WM831X_LED_SRC_SHIFT;
|
||||
} else {
|
||||
drvdata->src = pdata.default_src - 1;
|
||||
}
|
||||
|
||||
drvdata->cdev.name = pdata.name;
|
||||
drvdata->cdev.default_trigger = pdata.default_trigger;
|
||||
drvdata->cdev.brightness_set = wm831x_status_set;
|
||||
drvdata->cdev.blink_set = wm831x_status_blink_set;
|
||||
drvdata->cdev.groups = wm831x_status_groups;
|
||||
|
||||
ret = led_classdev_register(wm831x->dev, &drvdata->cdev);
|
||||
if (ret < 0) {
|
||||
dev_err(&pdev->dev, "Failed to register LED: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm831x_status_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wm831x_status *drvdata = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&drvdata->cdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver wm831x_status_driver = {
|
||||
.driver = {
|
||||
.name = "wm831x-status",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm831x_status_probe,
|
||||
.remove = wm831x_status_remove,
|
||||
};
|
||||
|
||||
module_platform_driver(wm831x_status_driver);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
|
||||
MODULE_DESCRIPTION("WM831x status LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:wm831x-status");
|
||||
287
drivers/leds/leds-wm8350.c
Normal file
287
drivers/leds/leds-wm8350.c
Normal file
|
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* LED driver for WM8350 driven LEDS.
|
||||
*
|
||||
* Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/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[] = {
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
10,
|
||||
11,
|
||||
14,
|
||||
16,
|
||||
19,
|
||||
23,
|
||||
27,
|
||||
32,
|
||||
39,
|
||||
46,
|
||||
54,
|
||||
65,
|
||||
77,
|
||||
92,
|
||||
109,
|
||||
130,
|
||||
154,
|
||||
183,
|
||||
218,
|
||||
259,
|
||||
308,
|
||||
367,
|
||||
436,
|
||||
518,
|
||||
616,
|
||||
733,
|
||||
872,
|
||||
1037,
|
||||
1233,
|
||||
1466,
|
||||
1744,
|
||||
2073,
|
||||
2466,
|
||||
2933,
|
||||
3487,
|
||||
4147,
|
||||
4932,
|
||||
5865,
|
||||
6975,
|
||||
8294,
|
||||
9864,
|
||||
11730,
|
||||
13949,
|
||||
16589,
|
||||
19728,
|
||||
23460,
|
||||
27899,
|
||||
33178,
|
||||
39455,
|
||||
46920,
|
||||
55798,
|
||||
66355,
|
||||
78910,
|
||||
93840,
|
||||
111596,
|
||||
132710,
|
||||
157820,
|
||||
187681,
|
||||
223191
|
||||
};
|
||||
|
||||
#define to_wm8350_led(led_cdev) \
|
||||
container_of(led_cdev, struct wm8350_led, cdev)
|
||||
|
||||
static void wm8350_led_enable(struct wm8350_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (led->enabled)
|
||||
return;
|
||||
|
||||
ret = regulator_enable(led->isink);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = regulator_enable(led->dcdc);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
|
||||
regulator_disable(led->isink);
|
||||
return;
|
||||
}
|
||||
|
||||
led->enabled = 1;
|
||||
}
|
||||
|
||||
static void wm8350_led_disable(struct wm8350_led *led)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!led->enabled)
|
||||
return;
|
||||
|
||||
ret = regulator_disable(led->dcdc);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
|
||||
return;
|
||||
}
|
||||
|
||||
ret = regulator_disable(led->isink);
|
||||
if (ret != 0) {
|
||||
dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
|
||||
ret = regulator_enable(led->dcdc);
|
||||
if (ret != 0)
|
||||
dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n",
|
||||
ret);
|
||||
return;
|
||||
}
|
||||
|
||||
led->enabled = 0;
|
||||
}
|
||||
|
||||
static void led_work(struct work_struct *work)
|
||||
{
|
||||
struct wm8350_led *led = container_of(work, struct wm8350_led, work);
|
||||
int ret;
|
||||
int uA;
|
||||
unsigned long flags;
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
|
||||
if (led->value == LED_OFF) {
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
wm8350_led_disable(led);
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* This scales linearly into the index of valid current
|
||||
* settings which results in a linear scaling of perceived
|
||||
* brightness due to the non-linear current settings provided
|
||||
* by the hardware.
|
||||
*/
|
||||
uA = (led->max_uA_index * led->value) / LED_FULL;
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
BUG_ON(uA >= ARRAY_SIZE(isink_cur));
|
||||
|
||||
ret = regulator_set_current_limit(led->isink, isink_cur[uA],
|
||||
isink_cur[uA]);
|
||||
if (ret != 0)
|
||||
dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
|
||||
isink_cur[uA], ret);
|
||||
|
||||
wm8350_led_enable(led);
|
||||
|
||||
out:
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
|
||||
static void wm8350_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
struct wm8350_led *led = to_wm8350_led(led_cdev);
|
||||
unsigned long flags;
|
||||
|
||||
spin_lock_irqsave(&led->value_lock, flags);
|
||||
led->value = value;
|
||||
schedule_work(&led->work);
|
||||
spin_unlock_irqrestore(&led->value_lock, flags);
|
||||
}
|
||||
|
||||
static void wm8350_led_shutdown(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8350_led *led = platform_get_drvdata(pdev);
|
||||
|
||||
mutex_lock(&led->mutex);
|
||||
led->value = LED_OFF;
|
||||
wm8350_led_disable(led);
|
||||
mutex_unlock(&led->mutex);
|
||||
}
|
||||
|
||||
static int wm8350_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct regulator *isink, *dcdc;
|
||||
struct wm8350_led *led;
|
||||
struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
|
||||
int i;
|
||||
|
||||
if (pdata == NULL) {
|
||||
dev_err(&pdev->dev, "no platform data\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
if (pdata->max_uA < isink_cur[0]) {
|
||||
dev_err(&pdev->dev, "Invalid maximum current %duA\n",
|
||||
pdata->max_uA);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
isink = devm_regulator_get(&pdev->dev, "led_isink");
|
||||
if (IS_ERR(isink)) {
|
||||
dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__);
|
||||
return PTR_ERR(isink);
|
||||
}
|
||||
|
||||
dcdc = devm_regulator_get(&pdev->dev, "led_vcc");
|
||||
if (IS_ERR(dcdc)) {
|
||||
dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__);
|
||||
return PTR_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;
|
||||
led->cdev.name = pdata->name;
|
||||
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
|
||||
led->enabled = regulator_is_enabled(isink);
|
||||
led->isink = isink;
|
||||
led->dcdc = dcdc;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
|
||||
if (isink_cur[i] >= pdata->max_uA)
|
||||
break;
|
||||
led->max_uA_index = i;
|
||||
if (pdata->max_uA != isink_cur[i])
|
||||
dev_warn(&pdev->dev,
|
||||
"Maximum current %duA is not directly supported,"
|
||||
" check platform data\n",
|
||||
pdata->max_uA);
|
||||
|
||||
spin_lock_init(&led->value_lock);
|
||||
mutex_init(&led->mutex);
|
||||
INIT_WORK(&led->work, led_work);
|
||||
led->value = LED_OFF;
|
||||
platform_set_drvdata(pdev, led);
|
||||
|
||||
return led_classdev_register(&pdev->dev, &led->cdev);
|
||||
}
|
||||
|
||||
static int wm8350_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct wm8350_led *led = platform_get_drvdata(pdev);
|
||||
|
||||
led_classdev_unregister(&led->cdev);
|
||||
flush_work(&led->work);
|
||||
wm8350_led_disable(led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver wm8350_led_driver = {
|
||||
.driver = {
|
||||
.name = "wm8350-led",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = wm8350_led_probe,
|
||||
.remove = wm8350_led_remove,
|
||||
.shutdown = wm8350_led_shutdown,
|
||||
};
|
||||
|
||||
module_platform_driver(wm8350_led_driver);
|
||||
|
||||
MODULE_AUTHOR("Mark Brown");
|
||||
MODULE_DESCRIPTION("WM8350 LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_ALIAS("platform:wm8350-led");
|
||||
154
drivers/leds/leds-wrap.c
Normal file
154
drivers/leds/leds-wrap.c
Normal file
|
|
@ -0,0 +1,154 @@
|
|||
/*
|
||||
* LEDs driver for PCEngines WRAP
|
||||
*
|
||||
* Copyright (C) 2006 Kristian Kielhofner <kris@krisk.org>
|
||||
*
|
||||
* Based on leds-net48xx.c
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/scx200_gpio.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
#define DRVNAME "wrap-led"
|
||||
#define WRAP_POWER_LED_GPIO 2
|
||||
#define WRAP_ERROR_LED_GPIO 3
|
||||
#define WRAP_EXTRA_LED_GPIO 18
|
||||
|
||||
static struct platform_device *pdev;
|
||||
|
||||
static void wrap_power_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value)
|
||||
scx200_gpio_set_low(WRAP_POWER_LED_GPIO);
|
||||
else
|
||||
scx200_gpio_set_high(WRAP_POWER_LED_GPIO);
|
||||
}
|
||||
|
||||
static void wrap_error_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value)
|
||||
scx200_gpio_set_low(WRAP_ERROR_LED_GPIO);
|
||||
else
|
||||
scx200_gpio_set_high(WRAP_ERROR_LED_GPIO);
|
||||
}
|
||||
|
||||
static void wrap_extra_led_set(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value)
|
||||
scx200_gpio_set_low(WRAP_EXTRA_LED_GPIO);
|
||||
else
|
||||
scx200_gpio_set_high(WRAP_EXTRA_LED_GPIO);
|
||||
}
|
||||
|
||||
static struct led_classdev wrap_power_led = {
|
||||
.name = "wrap::power",
|
||||
.brightness_set = wrap_power_led_set,
|
||||
.default_trigger = "default-on",
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev wrap_error_led = {
|
||||
.name = "wrap::error",
|
||||
.brightness_set = wrap_error_led_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static struct led_classdev wrap_extra_led = {
|
||||
.name = "wrap::extra",
|
||||
.brightness_set = wrap_extra_led_set,
|
||||
.flags = LED_CORE_SUSPENDRESUME,
|
||||
};
|
||||
|
||||
static int wrap_led_probe(struct platform_device *pdev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &wrap_power_led);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &wrap_error_led);
|
||||
if (ret < 0)
|
||||
goto err1;
|
||||
|
||||
ret = led_classdev_register(&pdev->dev, &wrap_extra_led);
|
||||
if (ret < 0)
|
||||
goto err2;
|
||||
|
||||
return ret;
|
||||
|
||||
err2:
|
||||
led_classdev_unregister(&wrap_error_led);
|
||||
err1:
|
||||
led_classdev_unregister(&wrap_power_led);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wrap_led_remove(struct platform_device *pdev)
|
||||
{
|
||||
led_classdev_unregister(&wrap_power_led);
|
||||
led_classdev_unregister(&wrap_error_led);
|
||||
led_classdev_unregister(&wrap_extra_led);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct platform_driver wrap_led_driver = {
|
||||
.probe = wrap_led_probe,
|
||||
.remove = wrap_led_remove,
|
||||
.driver = {
|
||||
.name = DRVNAME,
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init wrap_led_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!scx200_gpio_present()) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = platform_driver_register(&wrap_led_driver);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0);
|
||||
if (IS_ERR(pdev)) {
|
||||
ret = PTR_ERR(pdev);
|
||||
platform_driver_unregister(&wrap_led_driver);
|
||||
goto out;
|
||||
}
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit wrap_led_exit(void)
|
||||
{
|
||||
platform_device_unregister(pdev);
|
||||
platform_driver_unregister(&wrap_led_driver);
|
||||
}
|
||||
|
||||
module_init(wrap_led_init);
|
||||
module_exit(wrap_led_exit);
|
||||
|
||||
MODULE_AUTHOR("Kristian Kielhofner <kris@krisk.org>");
|
||||
MODULE_DESCRIPTION("PCEngines WRAP LED driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
63
drivers/leds/leds.h
Normal file
63
drivers/leds/leds.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* LED Core
|
||||
*
|
||||
* Copyright 2005 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#ifndef __LEDS_H_INCLUDED
|
||||
#define __LEDS_H_INCLUDED
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/rwsem.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
static inline void __led_set_brightness(struct led_classdev *led_cdev,
|
||||
enum led_brightness value)
|
||||
{
|
||||
if (value > led_cdev->max_brightness)
|
||||
value = led_cdev->max_brightness;
|
||||
led_cdev->brightness = value;
|
||||
if (!(led_cdev->flags & LED_SUSPENDED))
|
||||
led_cdev->brightness_set(led_cdev, value);
|
||||
}
|
||||
|
||||
static inline int led_get_brightness(struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->brightness;
|
||||
}
|
||||
|
||||
void led_stop_software_blink(struct led_classdev *led_cdev);
|
||||
|
||||
extern struct rw_semaphore leds_list_lock;
|
||||
extern struct list_head leds_list;
|
||||
|
||||
#ifdef CONFIG_LEDS_TRIGGERS
|
||||
void led_trigger_set_default(struct led_classdev *led_cdev);
|
||||
void led_trigger_set(struct led_classdev *led_cdev,
|
||||
struct led_trigger *trigger);
|
||||
void led_trigger_remove(struct led_classdev *led_cdev);
|
||||
|
||||
static inline void *led_get_trigger_data(struct led_classdev *led_cdev)
|
||||
{
|
||||
return led_cdev->trigger_data;
|
||||
}
|
||||
|
||||
#else
|
||||
#define led_trigger_set_default(x) do {} while (0)
|
||||
#define led_trigger_set(x, y) do {} while (0)
|
||||
#define led_trigger_remove(x) do {} while (0)
|
||||
#define led_get_trigger_data(x) (NULL)
|
||||
#endif
|
||||
|
||||
ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t count);
|
||||
ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf);
|
||||
|
||||
#endif /* __LEDS_H_INCLUDED */
|
||||
111
drivers/leds/trigger/Kconfig
Normal file
111
drivers/leds/trigger/Kconfig
Normal file
|
|
@ -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
|
||||
10
drivers/leds/trigger/Makefile
Normal file
10
drivers/leds/trigger/Makefile
Normal file
|
|
@ -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
|
||||
168
drivers/leds/trigger/ledtrig-backlight.c
Normal file
168
drivers/leds/trigger/ledtrig-backlight.c
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Backlight emulation LED trigger
|
||||
*
|
||||
* Copyright 2008 (C) Rodolfo Giometti <giometti@linux.it>
|
||||
* Copyright 2008 (C) Eurotech S.p.A. <info@eurotech.it>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/fb.h>
|
||||
#include <linux/leds.h>
|
||||
#include "../leds.h"
|
||||
|
||||
#define BLANK 1
|
||||
#define UNBLANK 0
|
||||
|
||||
struct bl_trig_notifier {
|
||||
struct led_classdev *led;
|
||||
int brightness;
|
||||
int old_status;
|
||||
struct notifier_block notifier;
|
||||
unsigned invert;
|
||||
};
|
||||
|
||||
static int fb_notifier_callback(struct notifier_block *p,
|
||||
unsigned long event, void *data)
|
||||
{
|
||||
struct bl_trig_notifier *n = container_of(p,
|
||||
struct bl_trig_notifier, notifier);
|
||||
struct led_classdev *led = n->led;
|
||||
struct fb_event *fb_event = data;
|
||||
int *blank;
|
||||
int new_status;
|
||||
|
||||
/* If we aren't interested in this event, skip it immediately ... */
|
||||
if (event != FB_EVENT_BLANK)
|
||||
return 0;
|
||||
|
||||
blank = fb_event->data;
|
||||
new_status = *blank ? BLANK : UNBLANK;
|
||||
|
||||
if (new_status == n->old_status)
|
||||
return 0;
|
||||
|
||||
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;
|
||||
|
||||
struct bl_trig_notifier *n;
|
||||
|
||||
n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL);
|
||||
led->trigger_data = n;
|
||||
if (!n) {
|
||||
dev_err(led->dev, "unable to allocate backlight trigger\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = device_create_file(led->dev, &dev_attr_inverted);
|
||||
if (ret)
|
||||
goto err_invert;
|
||||
|
||||
n->led = led;
|
||||
n->brightness = led->brightness;
|
||||
n->old_status = UNBLANK;
|
||||
n->notifier.notifier_call = fb_notifier_callback;
|
||||
|
||||
ret = fb_register_client(&n->notifier);
|
||||
if (ret)
|
||||
dev_err(led->dev, "unable to register backlight trigger\n");
|
||||
led->activated = true;
|
||||
|
||||
return;
|
||||
|
||||
err_invert:
|
||||
led->trigger_data = NULL;
|
||||
kfree(n);
|
||||
}
|
||||
|
||||
static void bl_trig_deactivate(struct led_classdev *led)
|
||||
{
|
||||
struct bl_trig_notifier *n =
|
||||
(struct bl_trig_notifier *) led->trigger_data;
|
||||
|
||||
if (led->activated) {
|
||||
device_remove_file(led->dev, &dev_attr_inverted);
|
||||
fb_unregister_client(&n->notifier);
|
||||
kfree(n);
|
||||
led->activated = false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct led_trigger bl_led_trigger = {
|
||||
.name = "backlight",
|
||||
.activate = bl_trig_activate,
|
||||
.deactivate = bl_trig_deactivate
|
||||
};
|
||||
|
||||
static int __init bl_trig_init(void)
|
||||
{
|
||||
return led_trigger_register(&bl_led_trigger);
|
||||
}
|
||||
|
||||
static void __exit bl_trig_exit(void)
|
||||
{
|
||||
led_trigger_unregister(&bl_led_trigger);
|
||||
}
|
||||
|
||||
module_init(bl_trig_init);
|
||||
module_exit(bl_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
|
||||
MODULE_DESCRIPTION("Backlight emulation LED trigger");
|
||||
MODULE_LICENSE("GPL v2");
|
||||
57
drivers/leds/trigger/ledtrig-camera.c
Normal file
57
drivers/leds/trigger/ledtrig-camera.c
Normal file
|
|
@ -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");
|
||||
166
drivers/leds/trigger/ledtrig-cpu.c
Normal file
166
drivers/leds/trigger/ledtrig-cpu.c
Normal file
|
|
@ -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");
|
||||
45
drivers/leds/trigger/ledtrig-default-on.c
Normal file
45
drivers/leds/trigger/ledtrig-default-on.c
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* LED Kernel Default ON Trigger
|
||||
*
|
||||
* Copyright 2008 Nick Forbes <nick.forbes@incepta.com>
|
||||
*
|
||||
* Based on Richard Purdie's ledtrig-timer.c.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/leds.h>
|
||||
#include "../leds.h"
|
||||
|
||||
static void defon_trig_activate(struct led_classdev *led_cdev)
|
||||
{
|
||||
__led_set_brightness(led_cdev, led_cdev->max_brightness);
|
||||
}
|
||||
|
||||
static struct led_trigger defon_led_trigger = {
|
||||
.name = "default-on",
|
||||
.activate = defon_trig_activate,
|
||||
};
|
||||
|
||||
static int __init defon_trig_init(void)
|
||||
{
|
||||
return led_trigger_register(&defon_led_trigger);
|
||||
}
|
||||
|
||||
static void __exit defon_trig_exit(void)
|
||||
{
|
||||
led_trigger_unregister(&defon_led_trigger);
|
||||
}
|
||||
|
||||
module_init(defon_trig_init);
|
||||
module_exit(defon_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>");
|
||||
MODULE_DESCRIPTION("Default-ON LED trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
253
drivers/leds/trigger/ledtrig-gpio.c
Normal file
253
drivers/leds/trigger/ledtrig-gpio.c
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
/*
|
||||
* ledtrig-gio.c - LED Trigger Based on GPIO events
|
||||
*
|
||||
* Copyright 2009 Felipe Balbi <me@felipebalbi.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/slab.h>
|
||||
#include "../leds.h"
|
||||
|
||||
struct gpio_trig_data {
|
||||
struct led_classdev *led;
|
||||
struct work_struct work;
|
||||
|
||||
unsigned desired_brightness; /* desired brightness when led is on */
|
||||
unsigned inverted; /* true when gpio is inverted */
|
||||
unsigned gpio; /* gpio that triggers the leds */
|
||||
};
|
||||
|
||||
static irqreturn_t gpio_trig_irq(int irq, void *_led)
|
||||
{
|
||||
struct led_classdev *led = _led;
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
|
||||
/* just schedule_work since gpio_get_value can sleep */
|
||||
schedule_work(&gpio_data->work);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
};
|
||||
|
||||
static void gpio_trig_work(struct work_struct *work)
|
||||
{
|
||||
struct gpio_trig_data *gpio_data = container_of(work,
|
||||
struct gpio_trig_data, work);
|
||||
int tmp;
|
||||
|
||||
if (!gpio_data->gpio)
|
||||
return;
|
||||
|
||||
tmp = gpio_get_value_cansleep(gpio_data->gpio);
|
||||
if (gpio_data->inverted)
|
||||
tmp = !tmp;
|
||||
|
||||
if (tmp) {
|
||||
if (gpio_data->desired_brightness)
|
||||
__led_set_brightness(gpio_data->led,
|
||||
gpio_data->desired_brightness);
|
||||
else
|
||||
__led_set_brightness(gpio_data->led, LED_FULL);
|
||||
} else {
|
||||
__led_set_brightness(gpio_data->led, LED_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t gpio_trig_brightness_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
|
||||
return sprintf(buf, "%u\n", gpio_data->desired_brightness);
|
||||
}
|
||||
|
||||
static ssize_t gpio_trig_brightness_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t n)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
unsigned desired_brightness;
|
||||
int ret;
|
||||
|
||||
ret = sscanf(buf, "%u", &desired_brightness);
|
||||
if (ret < 1 || desired_brightness > 255) {
|
||||
dev_err(dev, "invalid value\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
gpio_data->desired_brightness = desired_brightness;
|
||||
|
||||
return n;
|
||||
}
|
||||
static DEVICE_ATTR(desired_brightness, 0644, gpio_trig_brightness_show,
|
||||
gpio_trig_brightness_store);
|
||||
|
||||
static ssize_t gpio_trig_inverted_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
|
||||
return sprintf(buf, "%u\n", gpio_data->inverted);
|
||||
}
|
||||
|
||||
static ssize_t gpio_trig_inverted_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t n)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
unsigned long inverted;
|
||||
int ret;
|
||||
|
||||
ret = kstrtoul(buf, 10, &inverted);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
if (inverted > 1)
|
||||
return -EINVAL;
|
||||
|
||||
gpio_data->inverted = inverted;
|
||||
|
||||
/* After inverting, we need to update the LED. */
|
||||
schedule_work(&gpio_data->work);
|
||||
|
||||
return n;
|
||||
}
|
||||
static DEVICE_ATTR(inverted, 0644, gpio_trig_inverted_show,
|
||||
gpio_trig_inverted_store);
|
||||
|
||||
static ssize_t gpio_trig_gpio_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
|
||||
return sprintf(buf, "%u\n", gpio_data->gpio);
|
||||
}
|
||||
|
||||
static ssize_t gpio_trig_gpio_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t n)
|
||||
{
|
||||
struct led_classdev *led = dev_get_drvdata(dev);
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
unsigned gpio;
|
||||
int ret;
|
||||
|
||||
ret = sscanf(buf, "%u", &gpio);
|
||||
if (ret < 1) {
|
||||
dev_err(dev, "couldn't read gpio number\n");
|
||||
flush_work(&gpio_data->work);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (gpio_data->gpio == gpio)
|
||||
return n;
|
||||
|
||||
if (!gpio) {
|
||||
if (gpio_data->gpio != 0)
|
||||
free_irq(gpio_to_irq(gpio_data->gpio), led);
|
||||
gpio_data->gpio = 0;
|
||||
return n;
|
||||
}
|
||||
|
||||
ret = request_irq(gpio_to_irq(gpio), gpio_trig_irq,
|
||||
IRQF_SHARED | IRQF_TRIGGER_RISING
|
||||
| IRQF_TRIGGER_FALLING, "ledtrig-gpio", led);
|
||||
if (ret) {
|
||||
dev_err(dev, "request_irq failed with error %d\n", ret);
|
||||
} else {
|
||||
if (gpio_data->gpio != 0)
|
||||
free_irq(gpio_to_irq(gpio_data->gpio), led);
|
||||
gpio_data->gpio = gpio;
|
||||
}
|
||||
|
||||
return ret ? ret : n;
|
||||
}
|
||||
static DEVICE_ATTR(gpio, 0644, gpio_trig_gpio_show, gpio_trig_gpio_store);
|
||||
|
||||
static void gpio_trig_activate(struct led_classdev *led)
|
||||
{
|
||||
struct gpio_trig_data *gpio_data;
|
||||
int ret;
|
||||
|
||||
gpio_data = kzalloc(sizeof(*gpio_data), GFP_KERNEL);
|
||||
if (!gpio_data)
|
||||
return;
|
||||
|
||||
ret = device_create_file(led->dev, &dev_attr_gpio);
|
||||
if (ret)
|
||||
goto err_gpio;
|
||||
|
||||
ret = device_create_file(led->dev, &dev_attr_inverted);
|
||||
if (ret)
|
||||
goto err_inverted;
|
||||
|
||||
ret = device_create_file(led->dev, &dev_attr_desired_brightness);
|
||||
if (ret)
|
||||
goto err_brightness;
|
||||
|
||||
gpio_data->led = led;
|
||||
led->trigger_data = gpio_data;
|
||||
INIT_WORK(&gpio_data->work, gpio_trig_work);
|
||||
led->activated = true;
|
||||
|
||||
return;
|
||||
|
||||
err_brightness:
|
||||
device_remove_file(led->dev, &dev_attr_inverted);
|
||||
|
||||
err_inverted:
|
||||
device_remove_file(led->dev, &dev_attr_gpio);
|
||||
|
||||
err_gpio:
|
||||
kfree(gpio_data);
|
||||
}
|
||||
|
||||
static void gpio_trig_deactivate(struct led_classdev *led)
|
||||
{
|
||||
struct gpio_trig_data *gpio_data = led->trigger_data;
|
||||
|
||||
if (led->activated) {
|
||||
device_remove_file(led->dev, &dev_attr_gpio);
|
||||
device_remove_file(led->dev, &dev_attr_inverted);
|
||||
device_remove_file(led->dev, &dev_attr_desired_brightness);
|
||||
flush_work(&gpio_data->work);
|
||||
if (gpio_data->gpio != 0)
|
||||
free_irq(gpio_to_irq(gpio_data->gpio), led);
|
||||
kfree(gpio_data);
|
||||
led->activated = false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct led_trigger gpio_led_trigger = {
|
||||
.name = "gpio",
|
||||
.activate = gpio_trig_activate,
|
||||
.deactivate = gpio_trig_deactivate,
|
||||
};
|
||||
|
||||
static int __init gpio_trig_init(void)
|
||||
{
|
||||
return led_trigger_register(&gpio_led_trigger);
|
||||
}
|
||||
module_init(gpio_trig_init);
|
||||
|
||||
static void __exit gpio_trig_exit(void)
|
||||
{
|
||||
led_trigger_unregister(&gpio_led_trigger);
|
||||
}
|
||||
module_exit(gpio_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Felipe Balbi <me@felipebalbi.com>");
|
||||
MODULE_DESCRIPTION("GPIO LED trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
161
drivers/leds/trigger/ledtrig-heartbeat.c
Normal file
161
drivers/leds/trigger/ledtrig-heartbeat.c
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* LED Heartbeat Trigger
|
||||
*
|
||||
* Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp>
|
||||
*
|
||||
* Based on Richard Purdie's ledtrig-timer.c and some arch's
|
||||
* CONFIG_HEARTBEAT code.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/timer.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/leds.h>
|
||||
#include <linux/reboot.h>
|
||||
#include "../leds.h"
|
||||
|
||||
static int panic_heartbeats;
|
||||
|
||||
struct heartbeat_trig_data {
|
||||
unsigned int phase;
|
||||
unsigned int period;
|
||||
struct timer_list timer;
|
||||
};
|
||||
|
||||
static void led_heartbeat_function(unsigned long data)
|
||||
{
|
||||
struct led_classdev *led_cdev = (struct led_classdev *) data;
|
||||
struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_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:
|
||||
/*
|
||||
* The hyperbolic function below modifies the
|
||||
* heartbeat period length in dependency of the
|
||||
* current (1min) load. It goes through the points
|
||||
* f(0)=1260, f(1)=860, f(5)=510, f(inf)->300.
|
||||
*/
|
||||
heartbeat_data->period = 300 +
|
||||
(6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT));
|
||||
heartbeat_data->period =
|
||||
msecs_to_jiffies(heartbeat_data->period);
|
||||
delay = msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
brightness = led_cdev->max_brightness;
|
||||
break;
|
||||
case 1:
|
||||
delay = heartbeat_data->period / 4 - msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
break;
|
||||
case 2:
|
||||
delay = msecs_to_jiffies(70);
|
||||
heartbeat_data->phase++;
|
||||
brightness = led_cdev->max_brightness;
|
||||
break;
|
||||
default:
|
||||
delay = heartbeat_data->period - heartbeat_data->period / 4 -
|
||||
msecs_to_jiffies(70);
|
||||
heartbeat_data->phase = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
__led_set_brightness(led_cdev, brightness);
|
||||
mod_timer(&heartbeat_data->timer, jiffies + delay);
|
||||
}
|
||||
|
||||
static void heartbeat_trig_activate(struct led_classdev *led_cdev)
|
||||
{
|
||||
struct heartbeat_trig_data *heartbeat_data;
|
||||
|
||||
heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL);
|
||||
if (!heartbeat_data)
|
||||
return;
|
||||
|
||||
led_cdev->trigger_data = heartbeat_data;
|
||||
setup_timer(&heartbeat_data->timer,
|
||||
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 (led_cdev->activated) {
|
||||
del_timer_sync(&heartbeat_data->timer);
|
||||
kfree(heartbeat_data);
|
||||
led_cdev->activated = false;
|
||||
}
|
||||
}
|
||||
|
||||
static struct led_trigger heartbeat_led_trigger = {
|
||||
.name = "heartbeat",
|
||||
.activate = heartbeat_trig_activate,
|
||||
.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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
module_init(heartbeat_trig_init);
|
||||
module_exit(heartbeat_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>");
|
||||
MODULE_DESCRIPTION("Heartbeat LED trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
47
drivers/leds/trigger/ledtrig-ide-disk.c
Normal file
47
drivers/leds/trigger/ledtrig-ide-disk.c
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* LED IDE-Disk Activity Trigger
|
||||
*
|
||||
* Copyright 2006 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
#define BLINK_DELAY 30
|
||||
|
||||
DEFINE_LED_TRIGGER(ledtrig_ide);
|
||||
static unsigned long ide_blink_delay = BLINK_DELAY;
|
||||
|
||||
void ledtrig_ide_activity(void)
|
||||
{
|
||||
led_trigger_blink_oneshot(ledtrig_ide,
|
||||
&ide_blink_delay, &ide_blink_delay, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(ledtrig_ide_activity);
|
||||
|
||||
static int __init ledtrig_ide_init(void)
|
||||
{
|
||||
led_trigger_register_simple("ide-disk", &ledtrig_ide);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __exit ledtrig_ide_exit(void)
|
||||
{
|
||||
led_trigger_unregister_simple(ledtrig_ide);
|
||||
}
|
||||
|
||||
module_init(ledtrig_ide_init);
|
||||
module_exit(ledtrig_ide_exit);
|
||||
|
||||
MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
|
||||
MODULE_DESCRIPTION("LED IDE Disk Activity Trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
204
drivers/leds/trigger/ledtrig-oneshot.c
Normal file
204
drivers/leds/trigger/ledtrig-oneshot.c
Normal file
|
|
@ -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");
|
||||
130
drivers/leds/trigger/ledtrig-timer.c
Normal file
130
drivers/leds/trigger/ledtrig-timer.c
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/*
|
||||
* LED Kernel Timer Trigger
|
||||
*
|
||||
* Copyright 2005-2006 Openedhand Ltd.
|
||||
*
|
||||
* Author: Richard Purdie <rpurdie@openedhand.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/leds.h>
|
||||
|
||||
static ssize_t led_delay_on_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct led_classdev *led_cdev = dev_get_drvdata(dev);
|
||||
|
||||
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;
|
||||
ssize_t ret = -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 10, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off);
|
||||
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;
|
||||
ssize_t ret = -EINVAL;
|
||||
|
||||
ret = kstrtoul(buf, 10, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state);
|
||||
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 void timer_trig_activate(struct led_classdev *led_cdev)
|
||||
{
|
||||
int rc;
|
||||
|
||||
led_cdev->trigger_data = NULL;
|
||||
|
||||
rc = device_create_file(led_cdev->dev, &dev_attr_delay_on);
|
||||
if (rc)
|
||||
return;
|
||||
rc = device_create_file(led_cdev->dev, &dev_attr_delay_off);
|
||||
if (rc)
|
||||
goto err_out_delayon;
|
||||
|
||||
led_blink_set(led_cdev, &led_cdev->blink_delay_on,
|
||||
&led_cdev->blink_delay_off);
|
||||
led_cdev->activated = true;
|
||||
|
||||
return;
|
||||
|
||||
err_out_delayon:
|
||||
device_remove_file(led_cdev->dev, &dev_attr_delay_on);
|
||||
}
|
||||
|
||||
static void timer_trig_deactivate(struct led_classdev *led_cdev)
|
||||
{
|
||||
if (led_cdev->activated) {
|
||||
device_remove_file(led_cdev->dev, &dev_attr_delay_on);
|
||||
device_remove_file(led_cdev->dev, &dev_attr_delay_off);
|
||||
led_cdev->activated = false;
|
||||
}
|
||||
|
||||
/* Stop blinking */
|
||||
led_set_brightness(led_cdev, LED_OFF);
|
||||
}
|
||||
|
||||
static struct led_trigger timer_led_trigger = {
|
||||
.name = "timer",
|
||||
.activate = timer_trig_activate,
|
||||
.deactivate = timer_trig_deactivate,
|
||||
};
|
||||
|
||||
static int __init timer_trig_init(void)
|
||||
{
|
||||
return led_trigger_register(&timer_led_trigger);
|
||||
}
|
||||
|
||||
static void __exit timer_trig_exit(void)
|
||||
{
|
||||
led_trigger_unregister(&timer_led_trigger);
|
||||
}
|
||||
|
||||
module_init(timer_trig_init);
|
||||
module_exit(timer_trig_exit);
|
||||
|
||||
MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>");
|
||||
MODULE_DESCRIPTION("Timer LED trigger");
|
||||
MODULE_LICENSE("GPL");
|
||||
237
drivers/leds/trigger/ledtrig-transient.c
Normal file
237
drivers/leds/trigger/ledtrig-transient.c
Normal file
|
|
@ -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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue