Fixed MTP to work with TWRP

This commit is contained in:
awab228 2018-06-19 23:16:04 +02:00
commit f6dfaef42e
50820 changed files with 20846062 additions and 0 deletions

572
drivers/leds/Kconfig Normal file
View 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
View 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
View 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
View 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
View 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
View 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");

View 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
View 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
View 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
View 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
View 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");

View 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);

View 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");

View 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
View 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
View 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");

View 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
View 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");

View 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
View 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
View 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");

View 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, &micro_led);
if (ret) {
dev_err(&pdev->dev, "registering led failed: %d\n", ret);
return ret;
}
dev_info(&pdev->dev, "iPAQ micro notification LED driver\n");
return 0;
}
static int micro_leds_remove(struct platform_device *pdev)
{
led_classdev_unregister(&micro_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

File diff suppressed because it is too large Load diff

388
drivers/leds/leds-ktd2692.c Executable file
View 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
View 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
View 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
View 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
View 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");

View 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
View 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
View 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
View 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
View 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");

View 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");

View 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
View 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
View 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
View 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
View 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
View 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");

View 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");

View 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
View 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
View 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
View 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
View 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
View 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
View 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", &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
View 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
View 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");

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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", &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");

View 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");

View 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
View 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
View 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
View 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 */

View 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

View 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

View 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");

View 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");

View 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");

View 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");

View 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");

View 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");

View 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");

View 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");

View 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");

View 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");