diff options
Diffstat (limited to 'drivers/leds')
70 files changed, 9170 insertions, 3460 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9ca28fced2b..a1b044e7eaa 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -17,7 +17,7 @@ menuconfig NEW_LEDS if NEW_LEDS config LEDS_CLASS - bool "LED Class Support" + tristate "LED Class Support" help This option enables the led sysfs class in /sys/class/leds. You'll need this to do anything useful with LEDs. If unsure, say N. @@ -50,6 +50,30 @@ config LEDS_LM3530 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 @@ -69,18 +93,11 @@ config LEDS_MIKROTIK_RB532 config LEDS_S3C24XX tristate "LED Support for Samsung S3C24XX GPIO LEDs" depends on LEDS_CLASS - depends on ARCH_S3C2410 + depends on ARCH_S3C24XX help This option enables support for LEDs connected to GPIO lines on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440. -config LEDS_AMS_DELTA - tristate "LED Support for the Amstrad Delta (E3)" - depends on LEDS_CLASS - depends on MACH_AMS_DELTA - help - This option enables support for the LEDs on Amstrad Delta (E3). - config LEDS_NET48XX tristate "LED Support for Soekris net48xx series Error LED" depends on LEDS_CLASS @@ -89,16 +106,6 @@ config LEDS_NET48XX This option enables support for the Soekris net4801 and net4826 error LED. -config LEDS_NET5501 - tristate "LED Support for Soekris net5501 series Error LED" - depends on LEDS_TRIGGERS - depends on X86 && GPIO_CS5535 - select LEDS_TRIGGER_DEFAULT_ON - default n - help - Add support for the Soekris net5501 board (detection, error led - and GPIO). - config LEDS_FSG tristate "LED Support for the Freecom FSG-3" depends on LEDS_CLASS @@ -147,7 +154,7 @@ config LEDS_HP6XX config LEDS_PCA9532 tristate "LED driver for PCA9532 dimmer" depends on LEDS_CLASS - depends on I2C && INPUT && EXPERIMENTAL + depends on I2C && INPUT help This option enables support for NXP pca9532 LED controller. It is generally only useful @@ -166,7 +173,7 @@ config LEDS_PCA9532_GPIO config LEDS_GPIO tristate "LED Support for GPIO connected LEDs" depends on LEDS_CLASS - depends on GENERIC_GPIO + depends on GPIOLIB help This option enables support for the LEDs connected to GPIO outputs. To be useful the particular board must have LEDs @@ -186,9 +193,18 @@ config LEDS_LP3944 To compile this driver as a module, choose M here: the module will be called leds-lp3944. +config LEDS_LP55XX_COMMON + tristate "Common Driver for TI/National LP5521/5523/55231/5562/8501" + depends on LEDS_LP5521 || LEDS_LP5523 || LEDS_LP5562 || LEDS_LP8501 + select FW_LOADER + help + This option supports common operations for LP5521/5523/55231/5562/8501 + devices. + config LEDS_LP5521 tristate "LED Support for N.S. LP5521 LED driver chip" depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON help If you say yes here you get support for the National Semiconductor LP5521 LED driver. It is 3 channel chip with programmable engines. @@ -196,14 +212,45 @@ config LEDS_LP5521 programming the engines. config LEDS_LP5523 - tristate "LED Support for N.S. LP5523 LED driver chip" + tristate "LED Support for TI/National LP5523/55231 LED driver chip" depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON help - If you say yes here you get support for the National Semiconductor - LP5523 LED driver. It is 9 channel chip with programmable engines. + If you say yes here you get support for TI/National Semiconductor + LP5523/55231 LED driver. + It is 9 channel chip with programmable engines. Driver provides direct control via LED class and interface for programming the engines. +config LEDS_LP5562 + tristate "LED Support for TI LP5562 LED driver chip" + depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP5562 LED driver. + It is 4 channels chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + +config LEDS_LP8501 + tristate "LED Support for TI LP8501 LED driver chip" + depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON + help + If you say yes here you get support for TI LP8501 LED driver. + It is 9 channel chip with programmable engines. + Driver provides direct control via LED class and interface for + programming the engines. + It is similar as LP5523, but output power selection is available. + And register layout and engine program schemes are different. + +config LEDS_LP8788 + tristate "LED support for the TI LP8788 PMIC" + depends on LEDS_CLASS + depends on MFD_LP8788 + help + This option enables support for the Keyboard LEDs on the LP8788 PMIC. + config LEDS_CLEVO_MAIL tristate "Mail LED on Clevo notebook" depends on LEDS_CLASS @@ -244,6 +291,15 @@ config LEDS_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 @@ -268,6 +324,14 @@ config LEDS_DA903X 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 @@ -279,7 +343,7 @@ config LEDS_DAC124S085 config LEDS_PWM tristate "PWM driven LED Support" depends on LEDS_CLASS - depends on HAVE_PWM + depends on PWM help This option enables support for pwm driven LEDs @@ -311,7 +375,7 @@ config LEDS_INTEL_SS4200 config LEDS_LT3593 tristate "LED driver for LT3593 controllers" depends on LEDS_CLASS - depends on GENERIC_GPIO + 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 @@ -337,18 +401,17 @@ config LEDS_DELL_NETBOOKS notebooks that have an external LED. config LEDS_MC13783 - tristate "LED Support for MC13783 PMIC" + tristate "LED Support for MC13XXX PMIC" depends on LEDS_CLASS - depends on MFD_MC13783 + depends on MFD_MC13XXX help This option enable support for on-chip LED drivers found - on Freescale Semiconductor MC13783 PMIC. + on Freescale Semiconductor MC13783/MC13892/MC34708 PMIC. config LEDS_NS2 tristate "LED support for Network Space v2 GPIO LEDs" depends on LEDS_CLASS - depends on MACH_NETSPACE_V2 || MACH_INETSPACE_V2 || \ - MACH_NETSPACE_MAX_V2 || MACH_D2NET_V2 + depends on ARCH_KIRKWOOD || MACH_KIRKWOOD default y help This option enable support for the dual-GPIO LED found on the @@ -357,8 +420,8 @@ config LEDS_NS2 config LEDS_NETXBIG tristate "LED support for Big Network series LEDs" - depends on MACH_NET2BIG_V2 || MACH_NET5BIG_V2 depends on LEDS_CLASS + depends on ARCH_KIRKWOOD || MACH_KIRKWOOD default y help This option enable support for LEDs found on the LaCie 2Big @@ -367,7 +430,7 @@ config LEDS_NETXBIG config LEDS_ASIC3 bool "LED support for the HTC ASIC3" - depends on LEDS_CLASS + depends on LEDS_CLASS=y depends on MFD_ASIC3 default y help @@ -376,18 +439,6 @@ config LEDS_ASIC3 cannot be used. This driver supports hardware blinking with an on+off period from 62ms to 125s. Say Y to enable LEDs on the HP iPAQ hx4700. -config LEDS_RENESAS_TPU - bool "LED support for Renesas TPU" - depends on LEDS_CLASS && HAVE_CLK && GENERIC_GPIO - help - This option enables build of the LED TPU platform driver, - suitable to drive any TPU channel on newer Renesas SoCs. - The driver controls the GPIO pin connected to the LED via - the GPIO framework and expects the LED to be connected to - a pin that can be driven in both GPIO mode and using TPU - pin function. The latter to support brightness control. - Brightness control is supported but hardware blinking is not. - config LEDS_TCA6507 tristate "LED Support for TCA6507 I2C chip" depends on LEDS_CLASS && I2C @@ -403,81 +454,40 @@ config LEDS_MAX8997 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 + 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_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. - -comment "LED 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_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. +comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" -config LEDS_TRIGGER_GPIO - tristate "LED GPIO Trigger" - depends on LEDS_TRIGGERS - depends on GPIOLIB +config LEDS_BLINKM + tristate "LED support for the BlinkM I2C RGB LED" + depends on LEDS_CLASS + depends on I2C help - This allows LEDs to be controlled by gpio events. It's good - when using gpios as switches and triggering the needed LEDs - from there. One use case is n810's keypad LEDs that could - be triggered by this trigger when user slides up to show - keypad. - - If unsure, say N. + This option enables support for the BlinkM RGB LED connected + through I2C. Say Y to enable support for the BlinkM LED. -config LEDS_TRIGGER_DEFAULT_ON - tristate "LED Default ON Trigger" - depends on LEDS_TRIGGERS +config LEDS_VERSATILE + tristate "LED support for the ARM Versatile and RealView" + depends on ARCH_REALVIEW || ARCH_VERSATILE + depends on LEDS_CLASS help - This allows LEDs to be initialised in the ON state. - If unsure, say Y. + This option enabled support for the LEDs on the ARM Versatile + and RealView boards. Say Y to enabled these. -comment "iptables trigger is under Netfilter config (LED target)" - depends on LEDS_TRIGGERS +comment "LED Triggers" +source "drivers/leds/trigger/Kconfig" endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 1fc6875a8b2..79c5155199a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -10,11 +10,11 @@ obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o obj-$(CONFIG_LEDS_BD2802) += leds-bd2802.o obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o obj-$(CONFIG_LEDS_LM3530) += leds-lm3530.o +obj-$(CONFIG_LEDS_LM3533) += leds-lm3533.o +obj-$(CONFIG_LEDS_LM3642) += leds-lm3642.o obj-$(CONFIG_LEDS_MIKROTIK_RB532) += leds-rb532.o obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o -obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o -obj-$(CONFIG_LEDS_NET5501) += leds-net5501.o obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o @@ -23,15 +23,21 @@ obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o obj-$(CONFIG_LEDS_GPIO_REGISTER) += leds-gpio-register.o obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o obj-$(CONFIG_LEDS_LP3944) += leds-lp3944.o +obj-$(CONFIG_LEDS_LP55XX_COMMON) += leds-lp55xx-common.o obj-$(CONFIG_LEDS_LP5521) += leds-lp5521.o obj-$(CONFIG_LEDS_LP5523) += leds-lp5523.o +obj-$(CONFIG_LEDS_LP5562) += leds-lp5562.o +obj-$(CONFIG_LEDS_LP8501) += leds-lp8501.o +obj-$(CONFIG_LEDS_LP8788) += leds-lp8788.o obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o obj-$(CONFIG_LEDS_OT200) += leds-ot200.o obj-$(CONFIG_LEDS_FSG) += leds-fsg.o obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o +obj-$(CONFIG_LEDS_PCA963X) += leds-pca963x.o obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o +obj-$(CONFIG_LEDS_DA9052) += leds-da9052.o obj-$(CONFIG_LEDS_WM831X_STATUS) += leds-wm831x-status.o obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o obj-$(CONFIG_LEDS_PWM) += leds-pwm.o @@ -44,16 +50,13 @@ obj-$(CONFIG_LEDS_MC13783) += leds-mc13783.o obj-$(CONFIG_LEDS_NS2) += leds-ns2.o obj-$(CONFIG_LEDS_NETXBIG) += leds-netxbig.o obj-$(CONFIG_LEDS_ASIC3) += leds-asic3.o -obj-$(CONFIG_LEDS_RENESAS_TPU) += leds-renesas-tpu.o obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o +obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o +obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o +obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o # LED Triggers -obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o -obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o -obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o -obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o -obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o -obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGERS) += trigger/ diff --git a/drivers/leds/dell-led.c b/drivers/leds/dell-led.c index e5c57389efd..c36acaf566a 100644 --- a/drivers/leds/dell-led.c +++ b/drivers/leds/dell-led.c @@ -15,12 +15,15 @@ #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: */ @@ -39,6 +42,149 @@ MODULE_ALIAS("wmi:" DELL_LED_BIOS_GUID); #define CMD_LED_OFF 17 #define CMD_LED_BLINK 18 +struct app_wmi_args { + u16 class; + u16 selector; + u32 arg1; + u32 arg2; + u32 arg3; + u32 arg4; + u32 res1; + u32 res2; + u32 res3; + u32 res4; + char dummy[92]; +}; + +#define GLOBAL_MIC_MUTE_ENABLE 0x364 +#define GLOBAL_MIC_MUTE_DISABLE 0x365 + +struct dell_bios_data_token { + u16 tokenid; + u16 location; + u16 value; +}; + +struct __attribute__ ((__packed__)) dell_bios_calling_interface { + struct dmi_header header; + u16 cmd_io_addr; + u8 cmd_io_code; + u32 supported_cmds; + struct dell_bios_data_token damap[]; +}; + +static struct dell_bios_data_token dell_mic_tokens[2]; + +static int dell_wmi_perform_query(struct app_wmi_args *args) +{ + struct app_wmi_args *bios_return; + union acpi_object *obj; + struct acpi_buffer input; + struct acpi_buffer output = { ACPI_ALLOCATE_BUFFER, NULL }; + acpi_status status; + u32 rc = -EINVAL; + + input.length = 128; + input.pointer = args; + + status = wmi_evaluate_method(DELL_APP_GUID, 0, 1, &input, &output); + if (!ACPI_SUCCESS(status)) + goto err_out0; + + obj = output.pointer; + if (!obj) + goto err_out0; + + if (obj->type != ACPI_TYPE_BUFFER) + goto err_out1; + + bios_return = (struct app_wmi_args *)obj->buffer.pointer; + rc = bios_return->res1; + if (rc) + goto err_out1; + + memcpy(args, bios_return, sizeof(struct app_wmi_args)); + rc = 0; + + err_out1: + kfree(obj); + err_out0: + return rc; +} + +static void __init find_micmute_tokens(const struct dmi_header *dm, void *dummy) +{ + struct dell_bios_calling_interface *calling_interface; + struct dell_bios_data_token *token; + int token_size = sizeof(struct dell_bios_data_token); + int i = 0; + + if (dm->type == 0xda && dm->length > 17) { + calling_interface = container_of(dm, + struct dell_bios_calling_interface, header); + + token = &calling_interface->damap[i]; + while (token->tokenid != 0xffff) { + if (token->tokenid == GLOBAL_MIC_MUTE_DISABLE) + memcpy(&dell_mic_tokens[0], token, token_size); + else if (token->tokenid == GLOBAL_MIC_MUTE_ENABLE) + memcpy(&dell_mic_tokens[1], token, token_size); + + i++; + token = &calling_interface->damap[i]; + } + } +} + +static int dell_micmute_led_set(int state) +{ + struct app_wmi_args args; + struct dell_bios_data_token *token; + + if (!wmi_has_guid(DELL_APP_GUID)) + return -ENODEV; + + if (state == 0 || state == 1) + token = &dell_mic_tokens[state]; + else + return -EINVAL; + + memset(&args, 0, sizeof(struct app_wmi_args)); + + args.class = 1; + args.arg1 = token->location; + args.arg2 = token->value; + + dell_wmi_perform_query(&args); + + return state; +} + +int dell_app_wmi_led_set(int whichled, int on) +{ + int state = 0; + + switch (whichled) { + case DELL_LED_MICMUTE: + state = dell_micmute_led_set(on); + break; + default: + pr_warn("led type %x is not supported\n", whichled); + break; + } + + return state; +} +EXPORT_SYMBOL_GPL(dell_app_wmi_led_set); + +static int __init dell_micmute_led_init(void) +{ + memset(dell_mic_tokens, 0, sizeof(struct dell_bios_data_token) * 2); + dmi_walk(find_micmute_tokens, NULL); + + return 0; +} + struct bios_args { unsigned char length; unsigned char result_code; @@ -181,21 +327,32 @@ static int __init dell_led_init(void) { int error = 0; - if (!wmi_has_guid(DELL_LED_BIOS_GUID)) + if (!wmi_has_guid(DELL_LED_BIOS_GUID) && !wmi_has_guid(DELL_APP_GUID)) return -ENODEV; - error = led_off(); - if (error != 0) - return -ENODEV; + if (wmi_has_guid(DELL_APP_GUID)) + error = dell_micmute_led_init(); - return led_classdev_register(NULL, &dell_led); + if (wmi_has_guid(DELL_LED_BIOS_GUID)) { + error = led_off(); + if (error != 0) + return -ENODEV; + + error = led_classdev_register(NULL, &dell_led); + } + + return error; } static void __exit dell_led_exit(void) { - led_classdev_unregister(&dell_led); + int error = 0; - led_off(); + if (wmi_has_guid(DELL_LED_BIOS_GUID)) { + error = led_off(); + if (error == 0) + led_classdev_unregister(&dell_led); + } } module_init(dell_led_init); diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 0c8739c448b..f37d63cf726 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -29,7 +29,7 @@ static void led_update_brightness(struct led_classdev *led_cdev) led_cdev->brightness = led_cdev->brightness_get(led_cdev); } -static ssize_t led_brightness_show(struct device *dev, +static ssize_t brightness_show(struct device *dev, struct device_attribute *attr, char *buf) { struct led_classdev *led_cdev = dev_get_drvdata(dev); @@ -40,28 +40,24 @@ static ssize_t led_brightness_show(struct device *dev, return sprintf(buf, "%u\n", led_cdev->brightness); } -static ssize_t led_brightness_store(struct device *dev, +static ssize_t brightness_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned long state; ssize_t ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - if (isspace(*after)) - count++; + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; - if (count == size) { - ret = count; + if (state == LED_OFF) + led_trigger_remove(led_cdev); + __led_set_brightness(led_cdev, state); - if (state == LED_OFF) - led_trigger_remove(led_cdev); - led_set_brightness(led_cdev, state); - } - - return ret; + return size; } +static DEVICE_ATTR_RW(brightness); static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -70,14 +66,35 @@ static ssize_t led_max_brightness_show(struct device *dev, return sprintf(buf, "%u\n", led_cdev->max_brightness); } +static DEVICE_ATTR(max_brightness, 0444, led_max_brightness_show, NULL); + +#ifdef CONFIG_LEDS_TRIGGERS +static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); +static struct attribute *led_trigger_attrs[] = { + &dev_attr_trigger.attr, + NULL, +}; +static const struct attribute_group led_trigger_group = { + .attrs = led_trigger_attrs, +}; +#endif + +static struct attribute *led_class_attrs[] = { + &dev_attr_brightness.attr, + &dev_attr_max_brightness.attr, + NULL, +}; + +static const struct attribute_group led_group = { + .attrs = led_class_attrs, +}; -static struct device_attribute led_class_attrs[] = { - __ATTR(brightness, 0644, led_brightness_show, led_brightness_store), - __ATTR(max_brightness, 0444, led_max_brightness_show, NULL), +static const struct attribute_group *led_groups[] = { + &led_group, #ifdef CONFIG_LEDS_TRIGGERS - __ATTR(trigger, 0644, led_trigger_show, led_trigger_store), + &led_trigger_group, #endif - __ATTR_NULL, + NULL, }; static void led_timer_function(unsigned long data) @@ -87,7 +104,12 @@ static void led_timer_function(unsigned long data) unsigned long delay; if (!led_cdev->blink_delay_on || !led_cdev->blink_delay_off) { - led_set_brightness(led_cdev, LED_OFF); + __led_set_brightness(led_cdev, LED_OFF); + return; + } + + if (led_cdev->flags & LED_BLINK_ONESHOT_STOP) { + led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; return; } @@ -105,55 +127,35 @@ static void led_timer_function(unsigned long data) delay = led_cdev->blink_delay_off; } - led_set_brightness(led_cdev, brightness); + __led_set_brightness(led_cdev, brightness); + + /* Return in next iteration if led is in one-shot mode and we are in + * the final blink state so that the led is toggled each delay_on + + * delay_off milliseconds in worst case. + */ + if (led_cdev->flags & LED_BLINK_ONESHOT) { + if (led_cdev->flags & LED_BLINK_INVERT) { + if (brightness) + led_cdev->flags |= LED_BLINK_ONESHOT_STOP; + } else { + if (!brightness) + led_cdev->flags |= LED_BLINK_ONESHOT_STOP; + } + } mod_timer(&led_cdev->blink_timer, jiffies + msecs_to_jiffies(delay)); } -static void led_stop_software_blink(struct led_classdev *led_cdev) +static void set_brightness_delayed(struct work_struct *ws) { - /* deactivate previous settings */ - del_timer_sync(&led_cdev->blink_timer); - led_cdev->blink_delay_on = 0; - led_cdev->blink_delay_off = 0; -} - -static void led_set_software_blink(struct led_classdev *led_cdev, - unsigned long delay_on, - unsigned long delay_off) -{ - int current_brightness; - - current_brightness = led_get_brightness(led_cdev); - if (current_brightness) - led_cdev->blink_brightness = current_brightness; - if (!led_cdev->blink_brightness) - led_cdev->blink_brightness = led_cdev->max_brightness; - - if (led_get_trigger_data(led_cdev) && - delay_on == led_cdev->blink_delay_on && - delay_off == led_cdev->blink_delay_off) - return; + struct led_classdev *led_cdev = + container_of(ws, struct led_classdev, set_brightness_work); led_stop_software_blink(led_cdev); - led_cdev->blink_delay_on = delay_on; - led_cdev->blink_delay_off = delay_off; - - /* never on - don't blink */ - if (!delay_on) - return; - - /* never off - just set to brightness */ - if (!delay_off) { - led_set_brightness(led_cdev, led_cdev->blink_brightness); - return; - } - - mod_timer(&led_cdev->blink_timer, jiffies + 1); + __led_set_brightness(led_cdev, led_cdev->delayed_set_value); } - /** * led_classdev_suspend - suspend an led_classdev. * @led_cdev: the led_classdev to suspend. @@ -176,7 +178,7 @@ void led_classdev_resume(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_classdev_resume); -static int led_suspend(struct device *dev, pm_message_t state) +static int led_suspend(struct device *dev) { struct led_classdev *led_cdev = dev_get_drvdata(dev); @@ -196,6 +198,11 @@ static int led_resume(struct device *dev) return 0; } +static const struct dev_pm_ops leds_class_dev_pm_ops = { + .suspend = led_suspend, + .resume = led_resume, +}; + /** * led_classdev_register - register a new object of led_classdev class. * @parent: The device to register. @@ -221,6 +228,8 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) led_update_brightness(led_cdev); + INIT_WORK(&led_cdev->set_brightness_work, set_brightness_delayed); + init_timer(&led_cdev->blink_timer); led_cdev->blink_timer.function = led_timer_function; led_cdev->blink_timer.data = (unsigned long)led_cdev; @@ -229,7 +238,7 @@ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) led_trigger_set_default(led_cdev); #endif - printk(KERN_DEBUG "Registered led device: %s\n", + dev_dbg(parent, "Registered led device: %s\n", led_cdev->name); return 0; @@ -251,8 +260,11 @@ void led_classdev_unregister(struct led_classdev *led_cdev) up_write(&led_cdev->trigger_lock); #endif + cancel_work_sync(&led_cdev->set_brightness_work); + /* Stop blinking */ - led_brightness_set(led_cdev, LED_OFF); + led_stop_software_blink(led_cdev); + led_set_brightness(led_cdev, LED_OFF); device_unregister(led_cdev->dev); @@ -262,40 +274,13 @@ void led_classdev_unregister(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_classdev_unregister); -void led_blink_set(struct led_classdev *led_cdev, - unsigned long *delay_on, - unsigned long *delay_off) -{ - del_timer_sync(&led_cdev->blink_timer); - - if (led_cdev->blink_set && - !led_cdev->blink_set(led_cdev, delay_on, delay_off)) - return; - - /* blink with 1 Hz as default if nothing specified */ - if (!*delay_on && !*delay_off) - *delay_on = *delay_off = 500; - - led_set_software_blink(led_cdev, *delay_on, *delay_off); -} -EXPORT_SYMBOL(led_blink_set); - -void led_brightness_set(struct led_classdev *led_cdev, - enum led_brightness brightness) -{ - led_stop_software_blink(led_cdev); - led_cdev->brightness_set(led_cdev, brightness); -} -EXPORT_SYMBOL(led_brightness_set); - static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); if (IS_ERR(leds_class)) return PTR_ERR(leds_class); - leds_class->suspend = led_suspend; - leds_class->resume = led_resume; - leds_class->dev_attrs = led_class_attrs; + leds_class->pm = &leds_class_dev_pm_ops; + leds_class->dev_groups = led_groups; return 0; } diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c index 016d19f5486..71b40d3bf77 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -23,3 +23,106 @@ EXPORT_SYMBOL_GPL(leds_list_lock); LIST_HEAD(leds_list); EXPORT_SYMBOL_GPL(leds_list); + +static void led_set_software_blink(struct led_classdev *led_cdev, + unsigned long delay_on, + unsigned long delay_off) +{ + int current_brightness; + + current_brightness = led_get_brightness(led_cdev); + if (current_brightness) + led_cdev->blink_brightness = current_brightness; + if (!led_cdev->blink_brightness) + led_cdev->blink_brightness = led_cdev->max_brightness; + + led_cdev->blink_delay_on = delay_on; + led_cdev->blink_delay_off = delay_off; + + /* never on - just set to off */ + if (!delay_on) { + __led_set_brightness(led_cdev, LED_OFF); + return; + } + + /* never off - just set to brightness */ + if (!delay_off) { + __led_set_brightness(led_cdev, led_cdev->blink_brightness); + return; + } + + mod_timer(&led_cdev->blink_timer, jiffies + 1); +} + + +static void led_blink_setup(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + if (!(led_cdev->flags & LED_BLINK_ONESHOT) && + led_cdev->blink_set && + !led_cdev->blink_set(led_cdev, delay_on, delay_off)) + return; + + /* blink with 1 Hz as default if nothing specified */ + if (!*delay_on && !*delay_off) + *delay_on = *delay_off = 500; + + led_set_software_blink(led_cdev, *delay_on, *delay_off); +} + +void led_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + del_timer_sync(&led_cdev->blink_timer); + + led_cdev->flags &= ~LED_BLINK_ONESHOT; + led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; + + led_blink_setup(led_cdev, delay_on, delay_off); +} +EXPORT_SYMBOL(led_blink_set); + +void led_blink_set_oneshot(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off, + int invert) +{ + if ((led_cdev->flags & LED_BLINK_ONESHOT) && + timer_pending(&led_cdev->blink_timer)) + return; + + led_cdev->flags |= LED_BLINK_ONESHOT; + led_cdev->flags &= ~LED_BLINK_ONESHOT_STOP; + + if (invert) + led_cdev->flags |= LED_BLINK_INVERT; + else + led_cdev->flags &= ~LED_BLINK_INVERT; + + led_blink_setup(led_cdev, delay_on, delay_off); +} +EXPORT_SYMBOL(led_blink_set_oneshot); + +void led_stop_software_blink(struct led_classdev *led_cdev) +{ + del_timer_sync(&led_cdev->blink_timer); + led_cdev->blink_delay_on = 0; + led_cdev->blink_delay_off = 0; +} +EXPORT_SYMBOL_GPL(led_stop_software_blink); + +void led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + /* delay brightness setting if need to stop soft-blink timer */ + if (led_cdev->blink_delay_on || led_cdev->blink_delay_off) { + led_cdev->delayed_set_value = brightness; + schedule_work(&led_cdev->set_brightness_work); + return; + } + + __led_set_brightness(led_cdev, brightness); +} +EXPORT_SYMBOL(led_set_brightness); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 46b4c766335..c3734f10fdd 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -13,7 +13,6 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/list.h> #include <linux/spinlock.h> #include <linux/device.h> @@ -99,9 +98,15 @@ ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, EXPORT_SYMBOL_GPL(led_trigger_show); /* Caller must ensure led_cdev->trigger_lock held */ -void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) +void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) { unsigned long flags; + char *event = NULL; + char *envp[2]; + const char *name; + + name = trig ? trig->name : "none"; + event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name); /* Remove any existing trigger */ if (led_cdev->trigger) { @@ -109,18 +114,27 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) list_del(&led_cdev->trig_list); write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, flags); + cancel_work_sync(&led_cdev->set_brightness_work); + led_stop_software_blink(led_cdev); if (led_cdev->trigger->deactivate) led_cdev->trigger->deactivate(led_cdev); led_cdev->trigger = NULL; - led_brightness_set(led_cdev, LED_OFF); + led_set_brightness(led_cdev, LED_OFF); + } + if (trig) { + write_lock_irqsave(&trig->leddev_list_lock, flags); + list_add_tail(&led_cdev->trig_list, &trig->led_cdevs); + write_unlock_irqrestore(&trig->leddev_list_lock, flags); + led_cdev->trigger = trig; + if (trig->activate) + trig->activate(led_cdev); } - if (trigger) { - write_lock_irqsave(&trigger->leddev_list_lock, flags); - list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); - write_unlock_irqrestore(&trigger->leddev_list_lock, flags); - led_cdev->trigger = trigger; - if (trigger->activate) - trigger->activate(led_cdev); + + if (event) { + envp[0] = event; + envp[1] = NULL; + kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp); + kfree(event); } } EXPORT_SYMBOL_GPL(led_trigger_set); @@ -151,26 +165,39 @@ void led_trigger_set_default(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_trigger_set_default); +void led_trigger_rename_static(const char *name, struct led_trigger *trig) +{ + /* new name must be on a temporary string to prevent races */ + BUG_ON(name == trig->name); + + down_write(&triggers_list_lock); + /* this assumes that trig->name was originaly allocated to + * non constant storage */ + strcpy((char *)trig->name, name); + up_write(&triggers_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_rename_static); + /* LED Trigger Interface */ -int led_trigger_register(struct led_trigger *trigger) +int led_trigger_register(struct led_trigger *trig) { struct led_classdev *led_cdev; - struct led_trigger *trig; + struct led_trigger *_trig; - rwlock_init(&trigger->leddev_list_lock); - INIT_LIST_HEAD(&trigger->led_cdevs); + rwlock_init(&trig->leddev_list_lock); + INIT_LIST_HEAD(&trig->led_cdevs); down_write(&triggers_list_lock); /* Make sure the trigger's name isn't already in use */ - list_for_each_entry(trig, &trigger_list, next_trig) { - if (!strcmp(trig->name, trigger->name)) { + list_for_each_entry(_trig, &trigger_list, next_trig) { + if (!strcmp(_trig->name, trig->name)) { up_write(&triggers_list_lock); return -EEXIST; } } /* Add to the list of led triggers */ - list_add_tail(&trigger->next_trig, &trigger_list); + list_add_tail(&trig->next_trig, &trigger_list); up_write(&triggers_list_lock); /* Register with any LEDs that have this as a default trigger */ @@ -178,8 +205,8 @@ int led_trigger_register(struct led_trigger *trigger) list_for_each_entry(led_cdev, &leds_list, node) { down_write(&led_cdev->trigger_lock); if (!led_cdev->trigger && led_cdev->default_trigger && - !strcmp(led_cdev->default_trigger, trigger->name)) - led_trigger_set(led_cdev, trigger); + !strcmp(led_cdev->default_trigger, trig->name)) + led_trigger_set(led_cdev, trig); up_write(&led_cdev->trigger_lock); } up_read(&leds_list_lock); @@ -188,20 +215,23 @@ int led_trigger_register(struct led_trigger *trigger) } EXPORT_SYMBOL_GPL(led_trigger_register); -void led_trigger_unregister(struct led_trigger *trigger) +void led_trigger_unregister(struct led_trigger *trig) { struct led_classdev *led_cdev; + if (list_empty_careful(&trig->next_trig)) + return; + /* Remove from the list of led triggers */ down_write(&triggers_list_lock); - list_del(&trigger->next_trig); + list_del_init(&trig->next_trig); up_write(&triggers_list_lock); /* Remove anyone actively using this trigger */ down_read(&leds_list_lock); list_for_each_entry(led_cdev, &leds_list, node) { down_write(&led_cdev->trigger_lock); - if (led_cdev->trigger == trigger) + if (led_cdev->trigger == trig) led_trigger_set(led_cdev, NULL); up_write(&led_cdev->trigger_lock); } @@ -211,74 +241,89 @@ EXPORT_SYMBOL_GPL(led_trigger_unregister); /* Simple LED Tigger Interface */ -void led_trigger_event(struct led_trigger *trigger, +void led_trigger_event(struct led_trigger *trig, enum led_brightness brightness) { - struct list_head *entry; + struct led_classdev *led_cdev; - if (!trigger) + if (!trig) return; - read_lock(&trigger->leddev_list_lock); - list_for_each(entry, &trigger->led_cdevs) { - struct led_classdev *led_cdev; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); + read_lock(&trig->leddev_list_lock); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) led_set_brightness(led_cdev, brightness); - } - read_unlock(&trigger->leddev_list_lock); + read_unlock(&trig->leddev_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_event); -void led_trigger_blink(struct led_trigger *trigger, - unsigned long *delay_on, - unsigned long *delay_off) +static void led_trigger_blink_setup(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off, + int oneshot, + int invert) { - struct list_head *entry; + struct led_classdev *led_cdev; - if (!trigger) + if (!trig) return; - read_lock(&trigger->leddev_list_lock); - list_for_each(entry, &trigger->led_cdevs) { - struct led_classdev *led_cdev; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); - led_blink_set(led_cdev, delay_on, delay_off); + 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(&trigger->leddev_list_lock); + read_unlock(&trig->leddev_list_lock); +} + +void led_trigger_blink(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off) +{ + led_trigger_blink_setup(trig, delay_on, delay_off, 0, 0); } EXPORT_SYMBOL_GPL(led_trigger_blink); +void led_trigger_blink_oneshot(struct led_trigger *trig, + unsigned long *delay_on, + unsigned long *delay_off, + int invert) +{ + led_trigger_blink_setup(trig, delay_on, delay_off, 1, invert); +} +EXPORT_SYMBOL_GPL(led_trigger_blink_oneshot); + void led_trigger_register_simple(const char *name, struct led_trigger **tp) { - struct led_trigger *trigger; + struct led_trigger *trig; int err; - trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); + trig = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); - if (trigger) { - trigger->name = name; - err = led_trigger_register(trigger); + if (trig) { + trig->name = name; + err = led_trigger_register(trig); if (err < 0) { - kfree(trigger); - trigger = NULL; - printk(KERN_WARNING "LED trigger %s failed to register" - " (%d)\n", name, err); + kfree(trig); + trig = NULL; + pr_warn("LED trigger %s failed to register (%d)\n", + name, err); } - } else - printk(KERN_WARNING "LED trigger %s failed to register" - " (no memory)\n", name); - - *tp = trigger; + } else { + pr_warn("LED trigger %s failed to register (no memory)\n", + name); + } + *tp = trig; } EXPORT_SYMBOL_GPL(led_trigger_register_simple); -void led_trigger_unregister_simple(struct led_trigger *trigger) +void led_trigger_unregister_simple(struct led_trigger *trig) { - if (trigger) - led_trigger_unregister(trigger); - kfree(trigger); + if (trig) + led_trigger_unregister(trig); + kfree(trig); } EXPORT_SYMBOL_GPL(led_trigger_unregister_simple); diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index 4ca00624bd1..c2def5551ce 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -11,7 +11,7 @@ */ #include <linux/kernel.h> -#include <linux/init.h> +#include <linux/of.h> #include <linux/platform_device.h> #include <linux/i2c.h> #include <linux/leds.h> @@ -20,18 +20,12 @@ #include <linux/mfd/88pm860x.h> #include <linux/module.h> -#define LED_PWM_SHIFT (3) #define LED_PWM_MASK (0x1F) #define LED_CURRENT_MASK (0x07 << 5) -#define LED_BLINK_ON_MASK (0x07) #define LED_BLINK_MASK (0x7F) -#define LED_BLINK_ON(x) ((x & 0x7) * 66 + 66) -#define LED_BLINK_ON_MIN LED_BLINK_ON(0) -#define LED_BLINK_ON_MAX LED_BLINK_ON(0x7) #define LED_ON_CONTINUOUS (0x0F << 3) -#define LED_TO_ON(x) ((x - 66) / 66) #define LED1_BLINK_EN (1 << 1) #define LED2_BLINK_EN (1 << 2) @@ -49,66 +43,27 @@ struct pm860x_led { unsigned char brightness; unsigned char current_brightness; - int blink_data; - int blink_time; - int blink_on; - int blink_off; + int reg_control; + int reg_blink; + int blink_mask; }; -/* return offset of color register */ -static inline int __led_off(int port) +static int led_power_set(struct pm860x_chip *chip, int port, int on) { int ret = -EINVAL; switch (port) { - case PM8606_LED1_RED: - case PM8606_LED1_GREEN: - case PM8606_LED1_BLUE: - ret = port - PM8606_LED1_RED + PM8606_RGB1B; + case 0: + case 1: + case 2: + ret = on ? pm8606_osc_enable(chip, RGB1_ENABLE) : + pm8606_osc_disable(chip, RGB1_ENABLE); break; - case PM8606_LED2_RED: - case PM8606_LED2_GREEN: - case PM8606_LED2_BLUE: - ret = port - PM8606_LED2_RED + PM8606_RGB2B; - break; - } - return ret; -} - -/* return offset of blink register */ -static inline int __blink_off(int port) -{ - int ret = -EINVAL; - - switch (port) { - case PM8606_LED1_RED: - case PM8606_LED1_GREEN: - case PM8606_LED1_BLUE: - ret = PM8606_RGB1A; - break; - case PM8606_LED2_RED: - case PM8606_LED2_GREEN: - case PM8606_LED2_BLUE: - ret = PM8606_RGB2A; - break; - } - return ret; -} - -static inline int __blink_ctl_mask(int port) -{ - int ret = -EINVAL; - - switch (port) { - case PM8606_LED1_RED: - case PM8606_LED1_GREEN: - case PM8606_LED1_BLUE: - ret = LED1_BLINK_EN; - break; - case PM8606_LED2_RED: - case PM8606_LED2_GREEN: - case PM8606_LED2_BLUE: - ret = LED2_BLINK_EN; + case 3: + case 4: + case 5: + ret = on ? pm8606_osc_enable(chip, RGB2_ENABLE) : + pm8606_osc_disable(chip, RGB2_ENABLE); break; } return ret; @@ -120,40 +75,42 @@ static void pm860x_led_work(struct work_struct *work) struct pm860x_led *led; struct pm860x_chip *chip; unsigned char buf[3]; - int mask, ret; + int ret; led = container_of(work, struct pm860x_led, work); chip = led->chip; mutex_lock(&led->lock); if ((led->current_brightness == 0) && led->brightness) { + led_power_set(chip, led->port, 1); if (led->iset) { - pm860x_set_bits(led->i2c, __led_off(led->port), + pm860x_set_bits(led->i2c, led->reg_control, LED_CURRENT_MASK, led->iset); } - pm860x_set_bits(led->i2c, __blink_off(led->port), + pm860x_set_bits(led->i2c, led->reg_blink, LED_BLINK_MASK, LED_ON_CONTINUOUS); - mask = __blink_ctl_mask(led->port); - pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, mask); + pm860x_set_bits(led->i2c, PM8606_WLED3B, led->blink_mask, + led->blink_mask); } - pm860x_set_bits(led->i2c, __led_off(led->port), LED_PWM_MASK, + pm860x_set_bits(led->i2c, led->reg_control, LED_PWM_MASK, led->brightness); if (led->brightness == 0) { - pm860x_bulk_read(led->i2c, __led_off(led->port), 3, buf); + 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_off(led->port), + pm860x_set_bits(led->i2c, led->reg_control, LED_CURRENT_MASK, 0); - mask = __blink_ctl_mask(led->port); - pm860x_set_bits(led->i2c, PM8606_WLED3B, mask, 0); + pm860x_set_bits(led->i2c, PM8606_WLED3B, + led->blink_mask, 0); + led_power_set(chip, led->port, 0); } } led->current_brightness = led->brightness; dev_dbg(chip->dev, "Update LED. (reg:%d, brightness:%d)\n", - __led_off(led->port), led->brightness); + led->reg_control, led->brightness); mutex_unlock(&led->lock); } @@ -166,40 +123,92 @@ static void pm860x_led_set(struct led_classdev *cdev, 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; + struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev); struct pm860x_led *data; struct resource *res; - int ret; - - res = platform_get_resource(pdev, IORESOURCE_IO, 0); - if (res == NULL) { - dev_err(&pdev->dev, "No I/O resource!\n"); - return -EINVAL; - } - - pdata = pdev->dev.platform_data; - if (pdata == NULL) { - dev_err(&pdev->dev, "No platform data!\n"); - return -EINVAL; - } + int ret = 0; - data = kzalloc(sizeof(struct pm860x_led), GFP_KERNEL); + data = devm_kzalloc(&pdev->dev, sizeof(struct pm860x_led), GFP_KERNEL); if (data == NULL) return -ENOMEM; - strncpy(data->name, res->name, MFD_NAME_SIZE - 1); - dev_set_drvdata(&pdev->dev, data); + res = platform_get_resource_byname(pdev, IORESOURCE_REG, "control"); + if (!res) { + dev_err(&pdev->dev, "No REG resource for control\n"); + return -ENXIO; + } + data->reg_control = res->start; + res = platform_get_resource_byname(pdev, IORESOURCE_REG, "blink"); + if (!res) { + dev_err(&pdev->dev, "No REG resource for blink\n"); + return -ENXIO; + } + data->reg_blink = res->start; + memset(data->name, 0, MFD_NAME_SIZE); + switch (pdev->id) { + case 0: + data->blink_mask = LED1_BLINK_EN; + sprintf(data->name, "led0-red"); + break; + case 1: + data->blink_mask = LED1_BLINK_EN; + sprintf(data->name, "led0-green"); + break; + case 2: + data->blink_mask = LED1_BLINK_EN; + sprintf(data->name, "led0-blue"); + break; + case 3: + data->blink_mask = LED2_BLINK_EN; + sprintf(data->name, "led1-red"); + break; + case 4: + data->blink_mask = LED2_BLINK_EN; + sprintf(data->name, "led1-green"); + break; + case 5: + data->blink_mask = LED2_BLINK_EN; + sprintf(data->name, "led1-blue"); + break; + } + platform_set_drvdata(pdev, data); data->chip = chip; data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; - data->iset = pdata->iset; - data->port = pdata->flags; - if (data->port < 0) { - dev_err(&pdev->dev, "check device failed\n"); - kfree(data); - return -EINVAL; - } + data->port = pdev->id; + if (pm860x_led_dt_init(pdev, data)) + if (pdata) + data->iset = pdata->iset; data->current_brightness = 0; data->cdev.name = data->name; @@ -210,13 +219,10 @@ static int pm860x_led_probe(struct platform_device *pdev) ret = led_classdev_register(chip->dev, &data->cdev); if (ret < 0) { dev_err(&pdev->dev, "Failed to register LED: %d\n", ret); - goto out; + return ret; } pm860x_led_set(&data->cdev, 0); return 0; -out: - kfree(data); - return ret; } static int pm860x_led_remove(struct platform_device *pdev) @@ -224,7 +230,6 @@ static int pm860x_led_remove(struct platform_device *pdev) struct pm860x_led *data = platform_get_drvdata(pdev); led_classdev_unregister(&data->cdev); - kfree(data); return 0; } diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c index b1400db3f83..5036d7b4f82 100644 --- a/drivers/leds/leds-adp5520.c +++ b/drivers/leds/leds-adp5520.c @@ -5,17 +5,16 @@ * * Loosely derived from leds-da903x: * Copyright (C) 2008 Compulab, Ltd. - * Mike Rapoport <mike@compulab.co.il> + * Mike Rapoport <mike@compulab.co.il> * * Copyright (C) 2006-2008 Marvell International Ltd. - * Eric Miao <eric.miao@marvell.com> + * Eric Miao <eric.miao@marvell.com> * * Licensed under the GPL-2 or later. */ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/workqueue.h> @@ -85,9 +84,9 @@ static int adp5520_led_setup(struct adp5520_led *led) return ret; } -static int __devinit adp5520_led_prepare(struct platform_device *pdev) +static int adp5520_led_prepare(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = pdev->dev.parent; int ret = 0; @@ -101,9 +100,9 @@ static int __devinit adp5520_led_prepare(struct platform_device *pdev) return ret; } -static int __devinit adp5520_led_probe(struct platform_device *pdev) +static int adp5520_led_probe(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led, *led_dat; struct led_info *cur_led; int ret, i; @@ -119,17 +118,15 @@ static int __devinit adp5520_led_probe(struct platform_device *pdev) return -EFAULT; } - led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "failed to alloc memory\n"); + led = devm_kzalloc(&pdev->dev, sizeof(*led) * pdata->num_leds, + GFP_KERNEL); + if (!led) return -ENOMEM; - } ret = adp5520_led_prepare(pdev); - if (ret) { dev_err(&pdev->dev, "failed to write\n"); - goto err_free; + return ret; } for (i = 0; i < pdata->num_leds; ++i) { @@ -179,14 +176,12 @@ err: } } -err_free: - kfree(led); return ret; } -static int __devexit adp5520_led_remove(struct platform_device *pdev) +static int adp5520_led_remove(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led; int i; @@ -200,7 +195,6 @@ static int __devexit adp5520_led_remove(struct platform_device *pdev) cancel_work_sync(&led[i].work); } - kfree(led); return 0; } @@ -210,7 +204,7 @@ static struct platform_driver adp5520_led_driver = { .owner = THIS_MODULE, }, .probe = adp5520_led_probe, - .remove = __devexit_p(adp5520_led_remove), + .remove = adp5520_led_remove, }; module_platform_driver(adp5520_led_driver); diff --git a/drivers/leds/leds-ams-delta.c b/drivers/leds/leds-ams-delta.c deleted file mode 100644 index 07428357c83..00000000000 --- a/drivers/leds/leds-ams-delta.c +++ /dev/null @@ -1,126 +0,0 @@ -/* - * LEDs driver for Amstrad Delta (E3) - * - * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/leds.h> -#include <plat/board-ams-delta.h> - -/* - * Our context - */ -struct ams_delta_led { - struct led_classdev cdev; - u8 bitmask; -}; - -static void ams_delta_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct ams_delta_led *led_dev = - container_of(led_cdev, struct ams_delta_led, cdev); - - if (value) - ams_delta_latch1_write(led_dev->bitmask, led_dev->bitmask); - else - ams_delta_latch1_write(led_dev->bitmask, 0); -} - -static struct ams_delta_led ams_delta_leds[] = { - { - .cdev = { - .name = "ams-delta::camera", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_CAMERA, - }, - { - .cdev = { - .name = "ams-delta::advert", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_ADVERT, - }, - { - .cdev = { - .name = "ams-delta::email", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_EMAIL, - }, - { - .cdev = { - .name = "ams-delta::handsfree", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_HANDSFREE, - }, - { - .cdev = { - .name = "ams-delta::voicemail", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_VOICEMAIL, - }, - { - .cdev = { - .name = "ams-delta::voice", - .brightness_set = ams_delta_led_set, - }, - .bitmask = AMS_DELTA_LATCH1_LED_VOICE, - }, -}; - -static int ams_delta_led_probe(struct platform_device *pdev) -{ - int i, ret; - - for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) { - ams_delta_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = led_classdev_register(&pdev->dev, - &ams_delta_leds[i].cdev); - if (ret < 0) - goto fail; - } - - return 0; -fail: - while (--i >= 0) - led_classdev_unregister(&ams_delta_leds[i].cdev); - return ret; -} - -static int ams_delta_led_remove(struct platform_device *pdev) -{ - int i; - - for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) - led_classdev_unregister(&ams_delta_leds[i].cdev); - - return 0; -} - -static struct platform_driver ams_delta_led_driver = { - .probe = ams_delta_led_probe, - .remove = ams_delta_led_remove, - .driver = { - .name = "ams-delta-led", - .owner = THIS_MODULE, - }, -}; - -module_platform_driver(ams_delta_led_driver); - -MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>"); -MODULE_DESCRIPTION("Amstrad Delta LED driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:ams-delta-led"); diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c index 525a9249283..70c74a7f0df 100644 --- a/drivers/leds/leds-asic3.c +++ b/drivers/leds/leds-asic3.c @@ -7,7 +7,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/slab.h> @@ -92,19 +91,20 @@ static int blink_set(struct led_classdev *cdev, return 0; } -static int __devinit asic3_led_probe(struct platform_device *pdev) +static int asic3_led_probe(struct platform_device *pdev) { - struct asic3_led *led = pdev->dev.platform_data; + struct asic3_led *led = dev_get_platdata(&pdev->dev); int ret; ret = mfd_cell_enable(pdev); if (ret < 0) - goto ret0; + return ret; - led->cdev = kzalloc(sizeof(struct led_classdev), GFP_KERNEL); + led->cdev = devm_kzalloc(&pdev->dev, sizeof(struct led_classdev), + GFP_KERNEL); if (!led->cdev) { ret = -ENOMEM; - goto ret1; + goto out; } led->cdev->name = led->name; @@ -115,29 +115,25 @@ static int __devinit asic3_led_probe(struct platform_device *pdev) ret = led_classdev_register(&pdev->dev, led->cdev); if (ret < 0) - goto ret2; + goto out; return 0; -ret2: - kfree(led->cdev); -ret1: +out: (void) mfd_cell_disable(pdev); -ret0: return ret; } -static int __devexit asic3_led_remove(struct platform_device *pdev) +static int asic3_led_remove(struct platform_device *pdev) { - struct asic3_led *led = pdev->dev.platform_data; + struct asic3_led *led = dev_get_platdata(&pdev->dev); led_classdev_unregister(led->cdev); - kfree(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); @@ -163,15 +159,13 @@ static int asic3_led_resume(struct device *dev) return ret; } +#endif -static const struct dev_pm_ops asic3_led_pm_ops = { - .suspend = asic3_led_suspend, - .resume = asic3_led_resume, -}; +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 = __devexit_p(asic3_led_remove), + .remove = asic3_led_remove, .driver = { .name = "leds-asic3", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c index 800243b6037..56cec8d6a2a 100644 --- a/drivers/leds/leds-atmel-pwm.c +++ b/drivers/leds/leds-atmel-pwm.c @@ -35,18 +35,19 @@ static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b) * NOTE: we reuse the platform_data structure of GPIO leds, * but repurpose its "gpio" number as a PWM channel number. */ -static int __init pwmled_probe(struct platform_device *pdev) +static int pwmled_probe(struct platform_device *pdev) { const struct gpio_led_platform_data *pdata; struct pwmled *leds; int i; int status; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); if (!pdata || pdata->num_leds < 1) return -ENODEV; - leds = kcalloc(pdata->num_leds, sizeof(*leds), GFP_KERNEL); + leds = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*leds), + GFP_KERNEL); if (!leds) return -ENOMEM; @@ -108,18 +109,17 @@ err: pwm_channel_free(&leds[i].pwmc); } } - kfree(leds); return status; } -static int __exit pwmled_remove(struct platform_device *pdev) +static int pwmled_remove(struct platform_device *pdev) { const struct gpio_led_platform_data *pdata; struct pwmled *leds; unsigned i; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); leds = platform_get_drvdata(pdev); for (i = 0; i < pdata->num_leds; i++) { @@ -129,8 +129,6 @@ static int __exit pwmled_remove(struct platform_device *pdev) pwm_channel_free(&led->pwmc); } - kfree(leds); - platform_set_drvdata(pdev, NULL); return 0; } @@ -141,7 +139,7 @@ static struct platform_driver pwmled_driver = { }, /* REVISIT add suspend() and resume() methods */ .probe = pwmled_probe, - .remove = __exit_p(pwmled_remove), + .remove = pwmled_remove, }; module_platform_driver(pwmled_driver); diff --git a/drivers/leds/leds-bd2802.c b/drivers/leds/leds-bd2802.c index 591cbdf5a04..6078c15d345 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -26,8 +26,8 @@ #define BD2802_LED_OFFSET 0xa #define BD2802_COLOR_OFFSET 0x3 -#define BD2802_REG_CLKSETUP 0x00 -#define BD2802_REG_CONTROL 0x01 +#define BD2802_REG_CLKSETUP 0x00 +#define BD2802_REG_CONTROL 0x01 #define BD2802_REG_HOURSETUP 0x02 #define BD2802_REG_CURRENT1SETUP 0x03 #define BD2802_REG_CURRENT2SETUP 0x04 @@ -93,7 +93,7 @@ struct bd2802_led { * In ADF mode, user can set registers of BD2802GU directly, * therefore BD2802GU doesn't enter reset state. */ - int adf_on; + int adf_on; enum led_ids led_id; enum led_colors color; @@ -328,7 +328,7 @@ static ssize_t bd2802_store_reg##reg_addr(struct device *dev, \ int ret; \ if (!count) \ return -EINVAL; \ - ret = strict_strtoul(buf, 16, &val); \ + ret = kstrtoul(buf, 16, &val); \ if (ret) \ return ret; \ down_write(&led->rwsem); \ @@ -492,7 +492,7 @@ static ssize_t bd2802_store_##attr_name(struct device *dev, \ int ret; \ if (!count) \ return -EINVAL; \ - ret = strict_strtoul(buf, 16, &val); \ + ret = kstrtoul(buf, 16, &val); \ if (ret) \ return ret; \ down_write(&led->rwsem); \ @@ -670,21 +670,19 @@ static void bd2802_unregister_led_classdev(struct bd2802_led *led) led_classdev_unregister(&led->cdev_led1r); } -static int __devinit bd2802_probe(struct i2c_client *client, +static int bd2802_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct bd2802_led *led; struct bd2802_led_platform_data *pdata; int ret, i; - led = kzalloc(sizeof(struct bd2802_led), GFP_KERNEL); - if (!led) { - dev_err(&client->dev, "failed to allocate driver data\n"); + led = devm_kzalloc(&client->dev, sizeof(struct bd2802_led), GFP_KERNEL); + if (!led) return -ENOMEM; - } led->client = client; - pdata = led->pdata = client->dev.platform_data; + pdata = led->pdata = dev_get_platdata(&client->dev); i2c_set_clientdata(client, led); /* Configure RESET GPIO (L: RESET, H: RESET cancel) */ @@ -697,7 +695,7 @@ static int __devinit bd2802_probe(struct i2c_client *client, ret = bd2802_write_byte(client, BD2802_REG_CLKSETUP, 0x00); if (ret < 0) { dev_err(&client->dev, "failed to detect device\n"); - goto failed_free; + return ret; } else dev_info(&client->dev, "return 0x%02x\n", ret); @@ -729,13 +727,10 @@ static int __devinit bd2802_probe(struct i2c_client *client, failed_unregister_dev_file: for (i--; i >= 0; i--) device_remove_file(&led->client->dev, bd2802_attributes[i]); -failed_free: - kfree(led); - return ret; } -static int __exit bd2802_remove(struct i2c_client *client) +static int bd2802_remove(struct i2c_client *client) { struct bd2802_led *led = i2c_get_clientdata(client); int i; @@ -746,13 +741,11 @@ static int __exit bd2802_remove(struct i2c_client *client) bd2802_disable_adv_conf(led); for (i = 0; i < ARRAY_SIZE(bd2802_attributes); i++) device_remove_file(&led->client->dev, bd2802_attributes[i]); - kfree(led); return 0; } -#ifdef CONFIG_PM - +#ifdef CONFIG_PM_SLEEP static void bd2802_restore_state(struct bd2802_led *led) { int i; @@ -789,12 +782,9 @@ static int bd2802_resume(struct device *dev) return 0; } +#endif static SIMPLE_DEV_PM_OPS(bd2802_pm, bd2802_suspend, bd2802_resume); -#define BD2802_PM (&bd2802_pm) -#else /* CONFIG_PM */ -#define BD2802_PM NULL -#endif static const struct i2c_device_id bd2802_id[] = { { "BD2802", 0 }, @@ -805,10 +795,10 @@ MODULE_DEVICE_TABLE(i2c, bd2802_id); static struct i2c_driver bd2802_i2c_driver = { .driver = { .name = "BD2802", - .pm = BD2802_PM, + .pm = &bd2802_pm, }, .probe = bd2802_probe, - .remove = __exit_p(bd2802_remove), + .remove = bd2802_remove, .id_table = bd2802_id, }; diff --git a/drivers/leds/leds-blinkm.c b/drivers/leds/leds-blinkm.c new file mode 100644 index 00000000000..d0452b099ae --- /dev/null +++ b/drivers/leds/leds-blinkm.c @@ -0,0 +1,811 @@ +/* + * leds-blinkm.c + * (c) Jan-Simon Möller (dl9pf@gmx.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/sysfs.h> +#include <linux/printk.h> +#include <linux/pm_runtime.h> +#include <linux/leds.h> +#include <linux/delay.h> + +/* Addresses to scan - BlinkM is on 0x09 by default*/ +static const unsigned short normal_i2c[] = { 0x09, I2C_CLIENT_END }; + +static int blinkm_transfer_hw(struct i2c_client *client, int cmd); +static int blinkm_test_run(struct i2c_client *client); + +struct blinkm_led { + struct i2c_client *i2c_client; + struct led_classdev led_cdev; + int id; + atomic_t active; +}; + +struct blinkm_work { + struct blinkm_led *blinkm_led; + struct work_struct work; +}; + +#define cdev_to_blmled(c) container_of(c, struct blinkm_led, led_cdev) +#define work_to_blmwork(c) container_of(c, struct blinkm_work, work) + +struct blinkm_data { + struct i2c_client *i2c_client; + struct mutex update_lock; + /* used for led class interface */ + struct blinkm_led blinkm_leds[3]; + /* used for "blinkm" sysfs interface */ + u8 red; /* color red */ + u8 green; /* color green */ + u8 blue; /* color blue */ + /* next values to use for transfer */ + u8 next_red; /* color red */ + u8 next_green; /* color green */ + u8 next_blue; /* color blue */ + /* internal use */ + u8 args[7]; /* set of args for transmission */ + u8 i2c_addr; /* i2c addr */ + u8 fw_ver; /* firmware version */ + /* used, but not from userspace */ + u8 hue; /* HSB hue */ + u8 saturation; /* HSB saturation */ + u8 brightness; /* HSB brightness */ + u8 next_hue; /* HSB hue */ + u8 next_saturation; /* HSB saturation */ + u8 next_brightness; /* HSB brightness */ + /* currently unused / todo */ + u8 fade_speed; /* fade speed 1 - 255 */ + s8 time_adjust; /* time adjust -128 - 127 */ + u8 fade:1; /* fade on = 1, off = 0 */ + u8 rand:1; /* rand fade mode on = 1 */ + u8 script_id; /* script ID */ + u8 script_repeats; /* repeats of script */ + u8 script_startline; /* line to start */ +}; + +/* Colors */ +#define RED 0 +#define GREEN 1 +#define BLUE 2 + +/* mapping command names to cmd chars - see datasheet */ +#define BLM_GO_RGB 0 +#define BLM_FADE_RGB 1 +#define BLM_FADE_HSB 2 +#define BLM_FADE_RAND_RGB 3 +#define BLM_FADE_RAND_HSB 4 +#define BLM_PLAY_SCRIPT 5 +#define BLM_STOP_SCRIPT 6 +#define BLM_SET_FADE_SPEED 7 +#define BLM_SET_TIME_ADJ 8 +#define BLM_GET_CUR_RGB 9 +#define BLM_WRITE_SCRIPT_LINE 10 +#define BLM_READ_SCRIPT_LINE 11 +#define BLM_SET_SCRIPT_LR 12 /* Length & Repeats */ +#define BLM_SET_ADDR 13 +#define BLM_GET_ADDR 14 +#define BLM_GET_FW_VER 15 +#define BLM_SET_STARTUP_PARAM 16 + +/* BlinkM Commands + * as extracted out of the datasheet: + * + * cmdchar = command (ascii) + * cmdbyte = command in hex + * nr_args = number of arguments (to send) + * nr_ret = number of return values (to read) + * dir = direction (0 = read, 1 = write, 2 = both) + * + */ +static const struct { + char cmdchar; + u8 cmdbyte; + u8 nr_args; + u8 nr_ret; + u8 dir:2; +} blinkm_cmds[17] = { + /* cmdchar, cmdbyte, nr_args, nr_ret, dir */ + { 'n', 0x6e, 3, 0, 1}, + { 'c', 0x63, 3, 0, 1}, + { 'h', 0x68, 3, 0, 1}, + { 'C', 0x43, 3, 0, 1}, + { 'H', 0x48, 3, 0, 1}, + { 'p', 0x70, 3, 0, 1}, + { 'o', 0x6f, 0, 0, 1}, + { 'f', 0x66, 1, 0, 1}, + { 't', 0x74, 1, 0, 1}, + { 'g', 0x67, 0, 3, 0}, + { 'W', 0x57, 7, 0, 1}, + { 'R', 0x52, 2, 5, 2}, + { 'L', 0x4c, 3, 0, 1}, + { 'A', 0x41, 4, 0, 1}, + { 'a', 0x61, 0, 1, 0}, + { 'Z', 0x5a, 0, 1, 0}, + { 'B', 0x42, 5, 0, 1}, +}; + +static ssize_t show_color_common(struct device *dev, char *buf, int color) +{ + struct i2c_client *client; + struct blinkm_data *data; + int ret; + + client = to_i2c_client(dev); + data = i2c_get_clientdata(client); + + ret = blinkm_transfer_hw(client, BLM_GET_CUR_RGB); + if (ret < 0) + return ret; + switch (color) { + case RED: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red); + case GREEN: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green); + case BLUE: + return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue); + default: + return -EINVAL; + } + return -EINVAL; +} + +static int store_color_common(struct device *dev, const char *buf, int color) +{ + struct i2c_client *client; + struct blinkm_data *data; + int ret; + u8 value; + + client = to_i2c_client(dev); + data = i2c_get_clientdata(client); + + ret = kstrtou8(buf, 10, &value); + if (ret < 0) { + dev_err(dev, "BlinkM: value too large!\n"); + return ret; + } + + switch (color) { + case RED: + data->next_red = value; + break; + case GREEN: + data->next_green = value; + break; + case BLUE: + data->next_blue = value; + break; + default: + return -EINVAL; + } + + dev_dbg(dev, "next_red = %d, next_green = %d, next_blue = %d\n", + data->next_red, data->next_green, data->next_blue); + + /* if mode ... */ + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) { + dev_err(dev, "BlinkM: can't set RGB\n"); + return ret; + } + return 0; +} + +static ssize_t show_red(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, RED); +} + +static ssize_t store_red(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = store_color_common(dev, buf, RED); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(red, S_IRUGO | S_IWUSR, show_red, store_red); + +static ssize_t show_green(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, GREEN); +} + +static ssize_t store_green(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + int ret; + + ret = store_color_common(dev, buf, GREEN); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(green, S_IRUGO | S_IWUSR, show_green, store_green); + +static ssize_t show_blue(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return show_color_common(dev, buf, BLUE); +} + +static ssize_t store_blue(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + + ret = store_color_common(dev, buf, BLUE); + if (ret < 0) + return ret; + return count; +} + +static DEVICE_ATTR(blue, S_IRUGO | S_IWUSR, show_blue, store_blue); + +static ssize_t show_test(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return scnprintf(buf, PAGE_SIZE, + "#Write into test to start test sequence!#\n"); +} + +static ssize_t store_test(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + + struct i2c_client *client; + int ret; + client = to_i2c_client(dev); + + /*test */ + ret = blinkm_test_run(client); + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR(test, S_IRUGO | S_IWUSR, show_test, store_test); + +/* TODO: HSB, fade, timeadj, script ... */ + +static struct attribute *blinkm_attrs[] = { + &dev_attr_red.attr, + &dev_attr_green.attr, + &dev_attr_blue.attr, + &dev_attr_test.attr, + NULL, +}; + +static struct attribute_group blinkm_group = { + .name = "blinkm", + .attrs = blinkm_attrs, +}; + +static int blinkm_write(struct i2c_client *client, int cmd, u8 *arg) +{ + int result; + int i; + int arglen = blinkm_cmds[cmd].nr_args; + /* write out cmd to blinkm - always / default step */ + result = i2c_smbus_write_byte(client, blinkm_cmds[cmd].cmdbyte); + if (result < 0) + return result; + /* no args to write out */ + if (arglen == 0) + return 0; + + for (i = 0; i < arglen; i++) { + /* repeat for arglen */ + result = i2c_smbus_write_byte(client, arg[i]); + if (result < 0) + return result; + } + return 0; +} + +static int blinkm_read(struct i2c_client *client, int cmd, u8 *arg) +{ + int result; + int i; + int retlen = blinkm_cmds[cmd].nr_ret; + for (i = 0; i < retlen; i++) { + /* repeat for retlen */ + result = i2c_smbus_read_byte(client); + if (result < 0) + return result; + arg[i] = result; + } + + return 0; +} + +static int blinkm_transfer_hw(struct i2c_client *client, int cmd) +{ + /* the protocol is simple but non-standard: + * e.g. cmd 'g' (= 0x67) for "get device address" + * - which defaults to 0x09 - would be the sequence: + * a) write 0x67 to the device (byte write) + * b) read the value (0x09) back right after (byte read) + * + * Watch out for "unfinished" sequences (i.e. not enough reads + * or writes after a command. It will make the blinkM misbehave. + * Sequence is key here. + */ + + /* args / return are in private data struct */ + struct blinkm_data *data = i2c_get_clientdata(client); + + /* We start hardware transfers which are not to be + * mixed with other commands. Aquire a lock now. */ + if (mutex_lock_interruptible(&data->update_lock) < 0) + return -EAGAIN; + + /* switch cmd - usually write before reads */ + switch (cmd) { + case BLM_FADE_RAND_RGB: + case BLM_GO_RGB: + case BLM_FADE_RGB: + data->args[0] = data->next_red; + data->args[1] = data->next_green; + data->args[2] = data->next_blue; + blinkm_write(client, cmd, data->args); + data->red = data->args[0]; + data->green = data->args[1]; + data->blue = data->args[2]; + break; + case BLM_FADE_HSB: + case BLM_FADE_RAND_HSB: + data->args[0] = data->next_hue; + data->args[1] = data->next_saturation; + data->args[2] = data->next_brightness; + blinkm_write(client, cmd, data->args); + data->hue = data->next_hue; + data->saturation = data->next_saturation; + data->brightness = data->next_brightness; + break; + case BLM_PLAY_SCRIPT: + data->args[0] = data->script_id; + data->args[1] = data->script_repeats; + data->args[2] = data->script_startline; + blinkm_write(client, cmd, data->args); + break; + case BLM_STOP_SCRIPT: + blinkm_write(client, cmd, NULL); + break; + case BLM_GET_CUR_RGB: + data->args[0] = data->red; + data->args[1] = data->green; + data->args[2] = data->blue; + blinkm_write(client, cmd, NULL); + blinkm_read(client, cmd, data->args); + data->red = data->args[0]; + data->green = data->args[1]; + data->blue = data->args[2]; + break; + case BLM_GET_ADDR: + data->args[0] = data->i2c_addr; + blinkm_write(client, cmd, NULL); + blinkm_read(client, cmd, data->args); + data->i2c_addr = data->args[0]; + break; + case BLM_SET_TIME_ADJ: + case BLM_SET_FADE_SPEED: + case BLM_READ_SCRIPT_LINE: + case BLM_WRITE_SCRIPT_LINE: + case BLM_SET_SCRIPT_LR: + case BLM_SET_ADDR: + case BLM_GET_FW_VER: + case BLM_SET_STARTUP_PARAM: + dev_err(&client->dev, + "BlinkM: cmd %d not implemented yet.\n", cmd); + break; + default: + dev_err(&client->dev, "BlinkM: unknown command %d\n", cmd); + mutex_unlock(&data->update_lock); + return -EINVAL; + } /* end switch(cmd) */ + + /* transfers done, unlock */ + mutex_unlock(&data->update_lock); + return 0; +} + +static void led_work(struct work_struct *work) +{ + int ret; + struct blinkm_led *led; + struct blinkm_data *data; + struct blinkm_work *blm_work = work_to_blmwork(work); + + led = blm_work->blinkm_led; + data = i2c_get_clientdata(led->i2c_client); + ret = blinkm_transfer_hw(led->i2c_client, BLM_GO_RGB); + atomic_dec(&led->active); + dev_dbg(&led->i2c_client->dev, + "# DONE # next_red = %d, next_green = %d," + " next_blue = %d, active = %d\n", + data->next_red, data->next_green, + data->next_blue, atomic_read(&led->active)); + kfree(blm_work); +} + +static int blinkm_led_common_set(struct led_classdev *led_cdev, + enum led_brightness value, int color) +{ + /* led_brightness is 0, 127 or 255 - we just use it here as-is */ + struct blinkm_led *led = cdev_to_blmled(led_cdev); + struct blinkm_data *data = i2c_get_clientdata(led->i2c_client); + struct blinkm_work *bl_work; + + switch (color) { + case RED: + /* bail out if there's no change */ + if (data->next_red == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * think of network led trigger - we cannot blink that fast, so + * in case we already have a off->on->off transition queued up, + * we refuse to queue up more. + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_red = (u8) value; + break; + case GREEN: + /* bail out if there's no change */ + if (data->next_green == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_green = (u8) value; + break; + case BLUE: + /* bail out if there's no change */ + if (data->next_blue == (u8) value) + return 0; + /* we assume a quite fast sequence here ([off]->on->off) + * Revisit: fast-changing brightness. */ + if (atomic_read(&led->active) > 1) + return 0; + data->next_blue = (u8) value; + break; + + default: + dev_err(&led->i2c_client->dev, "BlinkM: unknown color.\n"); + return -EINVAL; + } + + bl_work = kzalloc(sizeof(*bl_work), GFP_ATOMIC); + if (!bl_work) + return -ENOMEM; + + atomic_inc(&led->active); + dev_dbg(&led->i2c_client->dev, + "#TO_SCHED# next_red = %d, next_green = %d," + " next_blue = %d, active = %d\n", + data->next_red, data->next_green, + data->next_blue, atomic_read(&led->active)); + + /* a fresh work _item_ for each change */ + bl_work->blinkm_led = led; + INIT_WORK(&bl_work->work, led_work); + /* queue work in own queue for easy sync on exit*/ + schedule_work(&bl_work->work); + + return 0; +} + +static void blinkm_led_red_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, RED); +} + +static void blinkm_led_green_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, GREEN); +} + +static void blinkm_led_blue_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + blinkm_led_common_set(led_cdev, value, BLUE); +} + +static void blinkm_init_hw(struct i2c_client *client) +{ + int ret; + ret = blinkm_transfer_hw(client, BLM_STOP_SCRIPT); + ret = blinkm_transfer_hw(client, BLM_GO_RGB); +} + +static int blinkm_test_run(struct i2c_client *client) +{ + int ret; + struct blinkm_data *data = i2c_get_clientdata(client); + + data->next_red = 0x01; + data->next_green = 0x05; + data->next_blue = 0x10; + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) + return ret; + msleep(2000); + + data->next_red = 0x25; + data->next_green = 0x10; + data->next_blue = 0x31; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + return ret; + msleep(2000); + + data->next_hue = 0x50; + data->next_saturation = 0x10; + data->next_brightness = 0x20; + ret = blinkm_transfer_hw(client, BLM_FADE_HSB); + if (ret < 0) + return ret; + msleep(2000); + + return 0; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int blinkm_detect(struct i2c_client *client, struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int ret; + int count = 99; + u8 tmpargs[7]; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + /* Now, we do the remaining detection. Simple for now. */ + /* We might need more guards to protect other i2c slaves */ + + /* make sure the blinkM is balanced (read/writes) */ + while (count > 0) { + ret = blinkm_write(client, BLM_GET_ADDR, NULL); + usleep_range(5000, 10000); + ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); + usleep_range(5000, 10000); + if (tmpargs[0] == 0x09) + count = 0; + count--; + } + + /* Step 1: Read BlinkM address back - cmd_char 'a' */ + ret = blinkm_write(client, BLM_GET_ADDR, NULL); + if (ret < 0) + return ret; + usleep_range(20000, 30000); /* allow a small delay */ + ret = blinkm_read(client, BLM_GET_ADDR, tmpargs); + if (ret < 0) + return ret; + + if (tmpargs[0] != 0x09) { + dev_err(&client->dev, "enodev DEV ADDR = 0x%02X\n", tmpargs[0]); + return -ENODEV; + } + + strlcpy(info->type, "blinkm", I2C_NAME_SIZE); + return 0; +} + +static int blinkm_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct blinkm_data *data; + struct blinkm_led *led[3]; + int err, i; + char blinkm_led_name[28]; + + data = devm_kzalloc(&client->dev, + sizeof(struct blinkm_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + data->i2c_addr = 0x09; + data->i2c_addr = 0x08; + /* i2c addr - use fake addr of 0x08 initially (real is 0x09) */ + data->fw_ver = 0xfe; + /* firmware version - use fake until we read real value + * (currently broken - BlinkM confused!) */ + data->script_id = 0x01; + data->i2c_client = client; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &blinkm_group); + if (err < 0) { + dev_err(&client->dev, "couldn't register sysfs group\n"); + goto exit; + } + + for (i = 0; i < 3; i++) { + /* RED = 0, GREEN = 1, BLUE = 2 */ + led[i] = &data->blinkm_leds[i]; + led[i]->i2c_client = client; + led[i]->id = i; + led[i]->led_cdev.max_brightness = 255; + led[i]->led_cdev.flags = LED_CORE_SUSPENDRESUME; + atomic_set(&led[i]->active, 0); + switch (i) { + case RED: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-red", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_red_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failred; + } + break; + case GREEN: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-green", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_green_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failgreen; + } + break; + case BLUE: + snprintf(blinkm_led_name, sizeof(blinkm_led_name), + "blinkm-%d-%d-blue", + client->adapter->nr, + client->addr); + led[i]->led_cdev.name = blinkm_led_name; + led[i]->led_cdev.brightness_set = blinkm_led_blue_set; + err = led_classdev_register(&client->dev, + &led[i]->led_cdev); + if (err < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led[i]->led_cdev.name); + goto failblue; + } + break; + } /* end switch */ + } /* end for */ + + /* Initialize the blinkm */ + blinkm_init_hw(client); + + return 0; + +failblue: + led_classdev_unregister(&led[GREEN]->led_cdev); + +failgreen: + led_classdev_unregister(&led[RED]->led_cdev); + +failred: + sysfs_remove_group(&client->dev.kobj, &blinkm_group); +exit: + return err; +} + +static int blinkm_remove(struct i2c_client *client) +{ + struct blinkm_data *data = i2c_get_clientdata(client); + int ret = 0; + int i; + + /* make sure no workqueue entries are pending */ + for (i = 0; i < 3; i++) { + flush_scheduled_work(); + led_classdev_unregister(&data->blinkm_leds[i].led_cdev); + } + + /* reset rgb */ + data->next_red = 0x00; + data->next_green = 0x00; + data->next_blue = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* reset hsb */ + data->next_hue = 0x00; + data->next_saturation = 0x00; + data->next_brightness = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_HSB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* red fade to off */ + data->next_red = 0xff; + ret = blinkm_transfer_hw(client, BLM_GO_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + /* off */ + data->next_red = 0x00; + ret = blinkm_transfer_hw(client, BLM_FADE_RGB); + if (ret < 0) + dev_err(&client->dev, "Failure in blinkm_remove ignored. Continuing.\n"); + + sysfs_remove_group(&client->dev.kobj, &blinkm_group); + return 0; +} + +static const struct i2c_device_id blinkm_id[] = { + {"blinkm", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, blinkm_id); + + /* This is the driver that will be inserted */ +static struct i2c_driver blinkm_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "blinkm", + }, + .probe = blinkm_probe, + .remove = blinkm_remove, + .id_table = blinkm_id, + .detect = blinkm_detect, + .address_list = normal_i2c, +}; + +module_i2c_driver(blinkm_driver); + +MODULE_AUTHOR("Jan-Simon Moeller <dl9pf@gmx.de>"); +MODULE_DESCRIPTION("BlinkM RGB LED driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c index 1ed1677c916..f58a354428e 100644 --- a/drivers/leds/leds-clevo-mail.c +++ b/drivers/leds/leds-clevo-mail.c @@ -1,3 +1,4 @@ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/module.h> @@ -18,7 +19,7 @@ MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>"); MODULE_DESCRIPTION("Clevo mail LED driver"); MODULE_LICENSE("GPL"); -static bool __initdata nodetect; +static bool nodetect; module_param_named(nodetect, nodetect, bool, 0); MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection"); @@ -26,12 +27,12 @@ static struct platform_device *pdev; static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id) { - printk(KERN_INFO KBUILD_MODNAME ": '%s' found\n", id->ident); + pr_info("'%s' found\n", id->ident); return 1; } /* - * struct mail_led_whitelist - List of known good models + * struct clevo_mail_led_dmi_table - List of known good models * * Contains the known good models this driver is compatible with. * When adding a new model try to be as strict as possible. This @@ -39,7 +40,7 @@ static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id) * detected as working, but in reality it is not) as low as * possible. */ -static struct dmi_system_id __initdata mail_led_whitelist[] = { +static struct dmi_system_id clevo_mail_led_dmi_table[] __initdata = { { .callback = clevo_mail_led_dmi_callback, .ident = "Clevo D410J", @@ -59,11 +60,10 @@ static struct dmi_system_id __initdata mail_led_whitelist[] = { }, { .callback = clevo_mail_led_dmi_callback, - .ident = "Positivo Mobile", + .ident = "Clevo M5x0V", .matches = { DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "), DMI_MATCH(DMI_BOARD_NAME, "M5X0V "), - DMI_MATCH(DMI_PRODUCT_NAME, "Positivo Mobile"), DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198") } }, @@ -89,6 +89,7 @@ static struct dmi_system_id __initdata mail_led_whitelist[] = { }, { } }; +MODULE_DEVICE_TABLE(dmi, clevo_mail_led_dmi_table); static void clevo_mail_led_set(struct led_classdev *led_cdev, enum led_brightness value) @@ -135,8 +136,7 @@ static int clevo_mail_led_blink(struct led_classdev *led_cdev, status = 0; } else { - printk(KERN_DEBUG KBUILD_MODNAME - ": clevo_mail_led_blink(..., %lu, %lu)," + pr_debug("clevo_mail_led_blink(..., %lu, %lu)," " returning -EINVAL (unsupported)\n", *delay_on, *delay_off); } @@ -153,7 +153,7 @@ static struct led_classdev clevo_mail_led = { .flags = LED_CORE_SUSPENDRESUME, }; -static int __devinit clevo_mail_led_probe(struct platform_device *pdev) +static int __init clevo_mail_led_probe(struct platform_device *pdev) { return led_classdev_register(&pdev->dev, &clevo_mail_led); } @@ -165,7 +165,6 @@ static int clevo_mail_led_remove(struct platform_device *pdev) } static struct platform_driver clevo_mail_led_driver = { - .probe = clevo_mail_led_probe, .remove = clevo_mail_led_remove, .driver = { .name = KBUILD_MODNAME, @@ -180,10 +179,10 @@ static int __init clevo_mail_led_init(void) /* Check with the help of DMI if we are running on supported hardware */ if (!nodetect) { - count = dmi_check_system(mail_led_whitelist); + count = dmi_check_system(clevo_mail_led_dmi_table); } else { count = 1; - printk(KERN_ERR KBUILD_MODNAME ": Skipping DMI detection. " + pr_err("Skipping DMI detection. " "If the driver works on your hardware please " "report model and the output of dmidecode in tracker " "at http://sourceforge.net/projects/clevo-mailled/\n"); @@ -197,8 +196,7 @@ static int __init clevo_mail_led_init(void) error = platform_driver_probe(&clevo_mail_led_driver, clevo_mail_led_probe); if (error) { - printk(KERN_ERR KBUILD_MODNAME - ": Can't probe platform driver\n"); + pr_err("Can't probe platform driver\n"); platform_device_unregister(pdev); } } else diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c index 6a8725cc7b4..910339d86ed 100644 --- a/drivers/leds/leds-cobalt-qube.c +++ b/drivers/leds/leds-cobalt-qube.c @@ -3,7 +3,6 @@ * * Control the Cobalt Qube/RaQ front LED */ -#include <linux/init.h> #include <linux/io.h> #include <linux/ioport.h> #include <linux/leds.h> @@ -34,7 +33,7 @@ static struct led_classdev qube_front_led = { .default_trigger = "default-on", }; -static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) +static int cobalt_qube_led_probe(struct platform_device *pdev) { struct resource *res; int retval; @@ -43,7 +42,7 @@ static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) if (!res) return -EBUSY; - led_port = ioremap(res->start, resource_size(res)); + led_port = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!led_port) return -ENOMEM; @@ -52,32 +51,29 @@ static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) retval = led_classdev_register(&pdev->dev, &qube_front_led); if (retval) - goto err_iounmap; + goto err_null; return 0; -err_iounmap: - iounmap(led_port); +err_null: led_port = NULL; return retval; } -static int __devexit cobalt_qube_led_remove(struct platform_device *pdev) +static int cobalt_qube_led_remove(struct platform_device *pdev) { led_classdev_unregister(&qube_front_led); - if (led_port) { - iounmap(led_port); + if (led_port) led_port = NULL; - } return 0; } static struct platform_driver cobalt_qube_led_driver = { .probe = cobalt_qube_led_probe, - .remove = __devexit_p(cobalt_qube_led_remove), + .remove = cobalt_qube_led_remove, .driver = { .name = "cobalt-qube-leds", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c index aac1c073fe7..001088b3137 100644 --- a/drivers/leds/leds-cobalt-raq.c +++ b/drivers/leds/leds-cobalt-raq.c @@ -76,7 +76,7 @@ static struct led_classdev raq_power_off_led = { .default_trigger = "power-off", }; -static int __devinit cobalt_raq_led_probe(struct platform_device *pdev) +static int cobalt_raq_led_probe(struct platform_device *pdev) { struct resource *res; int retval; @@ -85,13 +85,13 @@ static int __devinit cobalt_raq_led_probe(struct platform_device *pdev) if (!res) return -EBUSY; - led_port = ioremap(res->start, resource_size(res)); + led_port = devm_ioremap(&pdev->dev, res->start, resource_size(res)); if (!led_port) return -ENOMEM; retval = led_classdev_register(&pdev->dev, &raq_power_off_led); if (retval) - goto err_iounmap; + goto err_null; retval = led_classdev_register(&pdev->dev, &raq_web_led); if (retval) @@ -102,29 +102,26 @@ static int __devinit cobalt_raq_led_probe(struct platform_device *pdev) err_unregister: led_classdev_unregister(&raq_power_off_led); -err_iounmap: - iounmap(led_port); +err_null: led_port = NULL; return retval; } -static int __devexit cobalt_raq_led_remove(struct platform_device *pdev) +static int cobalt_raq_led_remove(struct platform_device *pdev) { led_classdev_unregister(&raq_power_off_led); led_classdev_unregister(&raq_web_led); - if (led_port) { - iounmap(led_port); + if (led_port) led_port = NULL; - } return 0; } static struct platform_driver cobalt_raq_led_driver = { .probe = cobalt_raq_led_probe, - .remove = __devexit_p(cobalt_raq_led_remove), + .remove = cobalt_raq_led_remove, .driver = { .name = "cobalt-raq-leds", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c index d9cd73ebd6c..54b8b5216b8 100644 --- a/drivers/leds/leds-da903x.c +++ b/drivers/leds/leds-da903x.c @@ -2,10 +2,10 @@ * LEDs driver for Dialog Semiconductor DA9030/DA9034 * * Copyright (C) 2008 Compulab, Ltd. - * Mike Rapoport <mike@compulab.co.il> + * Mike Rapoport <mike@compulab.co.il> * * Copyright (C) 2006-2008 Marvell International Ltd. - * Eric Miao <eric.miao@marvell.com> + * Eric Miao <eric.miao@marvell.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -14,7 +14,6 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/workqueue.h> @@ -85,15 +84,15 @@ static void da903x_led_set(struct led_classdev *led_cdev, enum led_brightness value) { struct da903x_led *led; - + led = container_of(led_cdev, struct da903x_led, cdev); led->new_brightness = value; schedule_work(&led->work); } -static int __devinit da903x_led_probe(struct platform_device *pdev) +static int da903x_led_probe(struct platform_device *pdev) { - struct led_info *pdata = pdev->dev.platform_data; + struct led_info *pdata = dev_get_platdata(&pdev->dev); struct da903x_led *led; int id, ret; @@ -108,11 +107,9 @@ static int __devinit da903x_led_probe(struct platform_device *pdev) return -EINVAL; } - led = kzalloc(sizeof(struct da903x_led), GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id); + led = devm_kzalloc(&pdev->dev, sizeof(struct da903x_led), GFP_KERNEL); + if (!led) return -ENOMEM; - } led->cdev.name = pdata->name; led->cdev.default_trigger = pdata->default_trigger; @@ -129,23 +126,18 @@ static int __devinit da903x_led_probe(struct platform_device *pdev) ret = led_classdev_register(led->master, &led->cdev); if (ret) { dev_err(&pdev->dev, "failed to register LED %d\n", id); - goto err; + return ret; } platform_set_drvdata(pdev, led); return 0; - -err: - kfree(led); - return ret; } -static int __devexit da903x_led_remove(struct platform_device *pdev) +static int da903x_led_remove(struct platform_device *pdev) { struct da903x_led *led = platform_get_drvdata(pdev); led_classdev_unregister(&led->cdev); - kfree(led); return 0; } @@ -155,13 +147,13 @@ static struct platform_driver da903x_led_driver = { .owner = THIS_MODULE, }, .probe = da903x_led_probe, - .remove = __devexit_p(da903x_led_remove), + .remove = da903x_led_remove, }; module_platform_driver(da903x_led_driver); MODULE_DESCRIPTION("LEDs driver for Dialog Semiconductor DA9030/DA9034"); -MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" - "Mike Rapoport <mike@compulab.co.il>"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>"); +MODULE_AUTHOR("Mike Rapoport <mike@compulab.co.il>"); MODULE_LICENSE("GPL"); MODULE_ALIAS("platform:da903x-led"); diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c new file mode 100644 index 00000000000..e4da1f460ac --- /dev/null +++ b/drivers/leds/leds-da9052.c @@ -0,0 +1,212 @@ +/* + * LED Driver for Dialog DA9052 PMICs. + * + * Copyright(c) 2012 Dialog Semiconductor Ltd. + * + * Author: David Dajun Chen <dchen@diasemi.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/slab.h> + +#include <linux/mfd/da9052/reg.h> +#include <linux/mfd/da9052/da9052.h> +#include <linux/mfd/da9052/pdata.h> + +#define DA9052_OPENDRAIN_OUTPUT 2 +#define DA9052_SET_HIGH_LVL_OUTPUT (1 << 3) +#define DA9052_MASK_UPPER_NIBBLE 0xF0 +#define DA9052_MASK_LOWER_NIBBLE 0x0F +#define DA9052_NIBBLE_SHIFT 4 +#define DA9052_MAX_BRIGHTNESS 0x5f + +struct da9052_led { + struct led_classdev cdev; + struct work_struct work; + struct da9052 *da9052; + unsigned char led_index; + unsigned char id; + int brightness; +}; + +static unsigned char led_reg[] = { + DA9052_LED_CONT_4_REG, + DA9052_LED_CONT_5_REG, +}; + +static int da9052_set_led_brightness(struct da9052_led *led) +{ + u8 val; + int error; + + val = (led->brightness & 0x7f) | DA9052_LED_CONT_DIM; + + error = da9052_reg_write(led->da9052, led_reg[led->led_index], val); + if (error < 0) + dev_err(led->da9052->dev, "Failed to set led brightness, %d\n", + error); + return error; +} + +static void da9052_led_work(struct work_struct *work) +{ + struct da9052_led *led = container_of(work, struct da9052_led, work); + + da9052_set_led_brightness(led); +} + +static void da9052_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct da9052_led *led; + + led = container_of(led_cdev, struct da9052_led, cdev); + led->brightness = value; + schedule_work(&led->work); +} + +static int da9052_configure_leds(struct da9052 *da9052) +{ + int error; + unsigned char register_value = DA9052_OPENDRAIN_OUTPUT + | DA9052_SET_HIGH_LVL_OUTPUT; + + error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, + DA9052_MASK_LOWER_NIBBLE, + register_value); + + if (error < 0) { + dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n", + error); + return error; + } + + error = da9052_reg_update(da9052, DA9052_GPIO_14_15_REG, + DA9052_MASK_UPPER_NIBBLE, + register_value << DA9052_NIBBLE_SHIFT); + if (error < 0) + dev_err(da9052->dev, "Failed to write GPIO 14-15 reg, %d\n", + error); + + return error; +} + +static int da9052_led_probe(struct platform_device *pdev) +{ + struct da9052_pdata *pdata; + struct da9052 *da9052; + struct led_platform_data *pled; + struct da9052_led *led = NULL; + int error = -ENODEV; + int i; + + da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = dev_get_platdata(da9052->dev); + if (pdata == NULL) { + dev_err(&pdev->dev, "No platform data\n"); + goto err; + } + + pled = pdata->pled; + if (pled == NULL) { + dev_err(&pdev->dev, "No platform data for LED\n"); + goto err; + } + + led = devm_kzalloc(&pdev->dev, + sizeof(struct da9052_led) * pled->num_leds, + GFP_KERNEL); + if (!led) { + error = -ENOMEM; + goto err; + } + + for (i = 0; i < pled->num_leds; i++) { + led[i].cdev.name = pled->leds[i].name; + led[i].cdev.brightness_set = da9052_led_set; + led[i].cdev.brightness = LED_OFF; + led[i].cdev.max_brightness = DA9052_MAX_BRIGHTNESS; + led[i].brightness = LED_OFF; + led[i].led_index = pled->leds[i].flags; + led[i].da9052 = dev_get_drvdata(pdev->dev.parent); + INIT_WORK(&led[i].work, da9052_led_work); + + error = led_classdev_register(pdev->dev.parent, &led[i].cdev); + if (error) { + dev_err(&pdev->dev, "Failed to register led %d\n", + led[i].led_index); + goto err_register; + } + + error = da9052_set_led_brightness(&led[i]); + if (error) { + dev_err(&pdev->dev, "Unable to init led %d\n", + led[i].led_index); + continue; + } + } + error = da9052_configure_leds(led->da9052); + if (error) { + dev_err(&pdev->dev, "Failed to configure GPIO LED%d\n", error); + goto err_register; + } + + platform_set_drvdata(pdev, led); + + return 0; + +err_register: + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } +err: + return error; +} + +static int da9052_led_remove(struct platform_device *pdev) +{ + struct da9052_led *led = platform_get_drvdata(pdev); + struct da9052_pdata *pdata; + struct da9052 *da9052; + struct led_platform_data *pled; + int i; + + da9052 = dev_get_drvdata(pdev->dev.parent); + pdata = dev_get_platdata(da9052->dev); + pled = pdata->pled; + + for (i = 0; i < pled->num_leds; i++) { + led[i].brightness = 0; + da9052_set_led_brightness(&led[i]); + led_classdev_unregister(&led[i].cdev); + cancel_work_sync(&led[i].work); + } + + return 0; +} + +static struct platform_driver da9052_led_driver = { + .driver = { + .name = "da9052-leds", + .owner = THIS_MODULE, + }, + .probe = da9052_led_probe, + .remove = da9052_led_remove, +}; + +module_platform_driver(da9052_led_driver); + +MODULE_AUTHOR("Dialog Semiconductor Ltd <dchen@diasemi.com>"); +MODULE_DESCRIPTION("LED driver for Dialog DA9052 PMIC"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c index d56c14269ff..db3ba8b4251 100644 --- a/drivers/leds/leds-dac124s085.c +++ b/drivers/leds/leds-dac124s085.c @@ -69,7 +69,7 @@ static int dac124s085_probe(struct spi_device *spi) struct dac124s085_led *led; int i, ret; - dac = kzalloc(sizeof(*dac), GFP_KERNEL); + dac = devm_kzalloc(&spi->dev, sizeof(*dac), GFP_KERNEL); if (!dac) return -ENOMEM; @@ -101,8 +101,6 @@ eledcr: while (i--) led_classdev_unregister(&dac->leds[i].ldev); - spi_set_drvdata(spi, NULL); - kfree(dac); return ret; } @@ -116,9 +114,6 @@ static int dac124s085_remove(struct spi_device *spi) cancel_work_sync(&dac->leds[i].work); } - spi_set_drvdata(spi, NULL); - kfree(dac); - return 0; } diff --git a/drivers/leds/leds-fsg.c b/drivers/leds/leds-fsg.c index b9053fa6e25..2b4dc738dcd 100644 --- a/drivers/leds/leds-fsg.c +++ b/drivers/leds/leds-fsg.c @@ -16,12 +16,11 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/module.h> +#include <linux/io.h> #include <mach/hardware.h> -#include <asm/io.h> #define FSG_LED_WLAN_BIT 0 #define FSG_LED_WAN_BIT 1 @@ -149,11 +148,10 @@ static int fsg_led_probe(struct platform_device *pdev) int ret; /* Map the LED chip select address space */ - latch_address = (unsigned short *) ioremap(IXP4XX_EXP_BUS_BASE(2), 512); - if (!latch_address) { - ret = -ENOMEM; - goto failremap; - } + latch_address = (unsigned short *) devm_ioremap(&pdev->dev, + IXP4XX_EXP_BUS_BASE(2), 512); + if (!latch_address) + return -ENOMEM; latch_value = 0xffff; *latch_address = latch_value; @@ -195,8 +193,6 @@ static int fsg_led_probe(struct platform_device *pdev) failwan: led_classdev_unregister(&fsg_wlan_led); failwlan: - iounmap(latch_address); - failremap: return ret; } @@ -210,8 +206,6 @@ static int fsg_led_remove(struct platform_device *pdev) led_classdev_unregister(&fsg_sync_led); led_classdev_unregister(&fsg_ring_led); - iounmap(latch_address); - return 0; } diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 7df74cb97e7..57ff20fecf5 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -11,16 +11,16 @@ * */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> +#include <linux/gpio.h> #include <linux/leds.h> +#include <linux/of.h> #include <linux/of_platform.h> #include <linux/of_gpio.h> #include <linux/slab.h> #include <linux/workqueue.h> #include <linux/module.h> - -#include <asm/gpio.h> +#include <linux/err.h> struct gpio_led_data { struct led_classdev cdev; @@ -91,7 +91,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev, delay_on, delay_off); } -static int __devinit create_gpio_led(const struct gpio_led *template, +static int create_gpio_led(const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, int (*blink_set)(unsigned, int, unsigned long *, unsigned long *)) { @@ -101,12 +101,12 @@ static int __devinit create_gpio_led(const struct gpio_led *template, /* skip leds that aren't available */ if (!gpio_is_valid(template->gpio)) { - printk(KERN_INFO "Skipping unavailable LED gpio %d (%s)\n", + dev_info(parent, "Skipping unavailable LED gpio %d (%s)\n", template->gpio, template->name); return 0; } - ret = gpio_request(template->gpio, template->name); + ret = devm_gpio_request(parent, template->gpio, template->name); if (ret < 0) return ret; @@ -131,18 +131,15 @@ static int __devinit create_gpio_led(const struct gpio_led *template, ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state); if (ret < 0) - goto err; - + return ret; + INIT_WORK(&led_dat->work, gpio_led_work); ret = led_classdev_register(parent, &led_dat->cdev); if (ret < 0) - goto err; + return ret; return 0; -err: - gpio_free(led_dat->gpio); - return ret; } static void delete_gpio_led(struct gpio_led_data *led) @@ -151,7 +148,6 @@ static void delete_gpio_led(struct gpio_led_data *led) return; led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); - gpio_free(led->gpio); } struct gpio_leds_priv { @@ -167,23 +163,27 @@ static inline int sizeof_gpio_leds_priv(int num_leds) /* Code to create from OpenFirmware platform devices */ #ifdef CONFIG_OF_GPIO -static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_device *pdev) +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 = 0, ret; + int count, ret; /* count LEDs in this device, so we know how much to allocate */ - for_each_child_of_node(np, child) - count++; + count = of_get_available_child_count(np); if (!count) - return NULL; + 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 = kzalloc(sizeof_gpio_leds_priv(count), GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(count), + GFP_KERNEL); if (!priv) - return NULL; + return ERR_PTR(-ENOMEM); - for_each_child_of_node(np, child) { + for_each_available_child_of_node(np, child) { struct gpio_led led = {}; enum of_gpio_flags flags; const char *state; @@ -203,6 +203,9 @@ static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_dev 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) { @@ -216,32 +219,34 @@ static struct gpio_leds_priv * __devinit gpio_leds_create_of(struct platform_dev err: for (count = priv->num_leds - 2; count >= 0; count--) delete_gpio_led(&priv->leds[count]); - kfree(priv); - return NULL; + 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 * __devinit gpio_leds_create_of(struct platform_device *pdev) +static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) { - return NULL; + return ERR_PTR(-ENODEV); } -#define of_gpio_leds_match NULL #endif /* CONFIG_OF_GPIO */ -static int __devinit gpio_led_probe(struct platform_device *pdev) +static int gpio_led_probe(struct platform_device *pdev) { - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct gpio_leds_priv *priv; int i, ret = 0; + if (pdata && pdata->num_leds) { - priv = kzalloc(sizeof_gpio_leds_priv(pdata->num_leds), - GFP_KERNEL); + priv = devm_kzalloc(&pdev->dev, + sizeof_gpio_leds_priv(pdata->num_leds), + GFP_KERNEL); if (!priv) return -ENOMEM; @@ -254,14 +259,13 @@ static int __devinit gpio_led_probe(struct platform_device *pdev) /* On failure: unwind the led creations */ for (i = i - 1; i >= 0; i--) delete_gpio_led(&priv->leds[i]); - kfree(priv); return ret; } } } else { priv = gpio_leds_create_of(pdev); - if (!priv) - return -ENODEV; + if (IS_ERR(priv)) + return PTR_ERR(priv); } platform_set_drvdata(pdev, priv); @@ -269,27 +273,24 @@ static int __devinit gpio_led_probe(struct platform_device *pdev) return 0; } -static int __devexit gpio_led_remove(struct platform_device *pdev) +static int gpio_led_remove(struct platform_device *pdev) { - struct gpio_leds_priv *priv = dev_get_drvdata(&pdev->dev); + 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]); - dev_set_drvdata(&pdev->dev, NULL); - kfree(priv); - return 0; } static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, - .remove = __devexit_p(gpio_led_remove), + .remove = gpio_led_remove, .driver = { .name = "leds-gpio", .owner = THIS_MODULE, - .of_match_table = of_gpio_leds_match, + .of_match_table = of_match_ptr(of_gpio_leds_match), }, }; diff --git a/drivers/leds/leds-hp6xx.c b/drivers/leds/leds-hp6xx.c index 366b6055e33..d61a98896c7 100644 --- a/drivers/leds/leds-hp6xx.c +++ b/drivers/leds/leds-hp6xx.c @@ -12,7 +12,6 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <asm/hd64461.h> diff --git a/drivers/leds/leds-lm3530.c b/drivers/leds/leds-lm3530.c index e59c166a0ce..652368c2ea9 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -26,7 +26,6 @@ #define LM3530_GEN_CONFIG 0x10 #define LM3530_ALS_CONFIG 0x20 #define LM3530_BRT_RAMP_RATE 0x30 -#define LM3530_ALS_ZONE_REG 0x40 #define LM3530_ALS_IMP_SELECT 0x41 #define LM3530_BRT_CTRL_REG 0xA0 #define LM3530_ALS_ZB0_REG 0x60 @@ -38,7 +37,7 @@ #define LM3530_ALS_Z2T_REG 0x72 #define LM3530_ALS_Z3T_REG 0x73 #define LM3530_ALS_Z4T_REG 0x74 -#define LM3530_REG_MAX 15 +#define LM3530_REG_MAX 14 /* General Control Register */ #define LM3530_EN_I2C_SHIFT (0) @@ -80,6 +79,9 @@ #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; @@ -111,11 +113,22 @@ struct lm3530_data { 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_ZONE_REG, LM3530_ALS_IMP_SELECT, LM3530_BRT_CTRL_REG, LM3530_ALS_ZB0_REG, @@ -137,103 +150,149 @@ static int lm3530_get_mode_from_str(const char *str) if (sysfs_streq(str, mode_map[i].mode)) return mode_map[i].mode_val; - return -1; + return -EINVAL; } -static int lm3530_init_registers(struct lm3530_data *drvdata) +static void lm3530_als_configure(struct lm3530_platform_data *pdata, + struct lm3530_als_data *als) { - int ret = 0; int i; - u8 gen_config; - u8 als_config = 0; - u8 brt_ramp; - u8 als_imp_sel = 0; - u8 brightness; - u8 reg_val[LM3530_REG_MAX]; - u8 zones[LM3530_ALS_ZB_MAX]; u32 als_vmin, als_vmax, als_vstep; - struct lm3530_platform_data *pltfm = drvdata->pdata; - struct i2c_client *client = drvdata->client; - gen_config = (pltfm->brt_ramp_law << LM3530_RAMP_LAW_SHIFT) | - ((pltfm->max_current & 7) << LM3530_MAX_CURR_SHIFT); + if (pdata->als_vmax == 0) { + pdata->als_vmin = 0; + pdata->als_vmax = LM3530_ALS_WINDOW_mV; + } - if (drvdata->mode == LM3530_BL_MODE_MANUAL || - drvdata->mode == LM3530_BL_MODE_ALS) - gen_config |= (LM3530_ENABLE_I2C); + als_vmin = pdata->als_vmin; + als_vmax = pdata->als_vmax; - if (drvdata->mode == LM3530_BL_MODE_ALS) { - if (pltfm->als_vmax == 0) { - pltfm->als_vmin = 0; - pltfm->als_vmax = LM3530_ALS_WINDOW_mV; - } + if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV) + pdata->als_vmax = als_vmax = als_vmin + LM3530_ALS_WINDOW_mV; - als_vmin = pltfm->als_vmin; - als_vmax = pltfm->als_vmax; + /* n zone boundary makes n+1 zones */ + als_vstep = (als_vmax - als_vmin) / (LM3530_ALS_ZB_MAX + 1); - if ((als_vmax - als_vmin) > LM3530_ALS_WINDOW_mV) - pltfm->als_vmax = als_vmax = - als_vmin + LM3530_ALS_WINDOW_mV; + 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; - /* n zone boundary makes n+1 zones */ - als_vstep = (als_vmax - als_vmin) / (LM3530_ALS_ZB_MAX + 1); + als->config = + (pdata->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | + (LM3530_ENABLE_ALS) | + (pdata->als_input_mode << LM3530_ALS_SEL_SHIFT); - for (i = 0; i < LM3530_ALS_ZB_MAX; i++) - zones[i] = (((als_vmin + LM3530_ALS_OFFSET_mV) + - als_vstep + (i * als_vstep)) * LED_FULL) - / 1000; + als->imp_sel = + (pdata->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | + (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); +} - als_config = - (pltfm->als_avrg_time << LM3530_ALS_AVG_TIME_SHIFT) | - (LM3530_ENABLE_ALS) | - (pltfm->als_input_mode << LM3530_ALS_SEL_SHIFT); +static int lm3530_led_enable(struct lm3530_data *drvdata) +{ + int ret; - als_imp_sel = - (pltfm->als1_resistor_sel << LM3530_ALS1_IMP_SHIFT) | - (pltfm->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); + 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; } - if (drvdata->mode == LM3530_BL_MODE_PWM) - gen_config |= (LM3530_ENABLE_PWM) | - (pltfm->pwm_pol_hi << LM3530_PWM_POL_SHIFT) | - (LM3530_ENABLE_PWM_SIMPLE); + 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)); - brt_ramp = (pltfm->brt_ramp_fall << LM3530_BRT_RAMP_FALL_SHIFT) | - (pltfm->brt_ramp_rise << LM3530_BRT_RAMP_RISE_SHIFT); + 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 = pltfm->brt_val; + 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[1] = als.config; /* LM3530_ALS_CONFIG */ reg_val[2] = brt_ramp; /* LM3530_BRT_RAMP_RATE */ - reg_val[3] = 0x00; /* LM3530_ALS_ZONE_REG */ - reg_val[4] = als_imp_sel; /* LM3530_ALS_IMP_SELECT */ - reg_val[5] = brightness; /* LM3530_BRT_CTRL_REG */ - reg_val[6] = zones[0]; /* LM3530_ALS_ZB0_REG */ - reg_val[7] = zones[1]; /* LM3530_ALS_ZB1_REG */ - reg_val[8] = zones[2]; /* LM3530_ALS_ZB2_REG */ - reg_val[9] = zones[3]; /* LM3530_ALS_ZB3_REG */ - reg_val[10] = LM3530_DEF_ZT_0; /* LM3530_ALS_Z0T_REG */ - reg_val[11] = LM3530_DEF_ZT_1; /* LM3530_ALS_Z1T_REG */ - reg_val[12] = LM3530_DEF_ZT_2; /* LM3530_ALS_Z2T_REG */ - reg_val[13] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */ - reg_val[14] = LM3530_DEF_ZT_4; /* LM3530_ALS_Z4T_REG */ - - if (!drvdata->enable) { - ret = regulator_enable(drvdata->regulator); - if (ret) { - dev_err(&drvdata->client->dev, - "Enable regulator failed\n"); - return ret; - } - drvdata->enable = true; - } + 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) @@ -249,6 +308,9 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, 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: @@ -264,24 +326,21 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, /* set the brightness in brightness control register*/ err = i2c_smbus_write_byte_data(drvdata->client, - LM3530_BRT_CTRL_REG, brt_val / 2); + 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 / 2; + drvdata->brightness = brt_val; - if (brt_val == 0) { - err = regulator_disable(drvdata->regulator); - if (err) - dev_err(&drvdata->client->dev, - "Disable regulator failed\n"); - drvdata->enable = false; - } + 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; @@ -291,11 +350,11 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, static ssize_t lm3530_mode_get(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = container_of( - dev->parent, struct i2c_client, dev); - struct lm3530_data *drvdata = i2c_get_clientdata(client); + 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); @@ -310,26 +369,26 @@ static ssize_t lm3530_mode_get(struct device *dev, static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { - int err; - struct i2c_client *client = container_of( - dev->parent, struct i2c_client, dev); - struct lm3530_data *drvdata = i2c_get_clientdata(client); - int mode; + 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 -EINVAL; + return mode; } - if (mode == LM3530_BL_MODE_MANUAL) - drvdata->mode = LM3530_BL_MODE_MANUAL; - else if (mode == LM3530_BL_MODE_ALS) - drvdata->mode = LM3530_BL_MODE_ALS; - else if (mode == LM3530_BL_MODE_PWM) { - dev_err(dev, "PWM mode not supported\n"); - return -EINVAL; - } + 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) { @@ -341,37 +400,33 @@ static ssize_t lm3530_mode_set(struct device *dev, struct device_attribute } static DEVICE_ATTR(mode, 0644, lm3530_mode_get, lm3530_mode_set); -static int __devinit lm3530_probe(struct i2c_client *client, +static int lm3530_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm3530_platform_data *pdata = client->dev.platform_data; + struct lm3530_platform_data *pdata = dev_get_platdata(&client->dev); struct lm3530_data *drvdata; int err = 0; if (pdata == NULL) { dev_err(&client->dev, "platform data required\n"); - err = -ENODEV; - goto err_out; + return -ENODEV; } /* BL mode */ if (pdata->mode > LM3530_BL_MODE_PWM) { dev_err(&client->dev, "Illegal Mode request\n"); - err = -EINVAL; - goto err_out; + return -EINVAL; } if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { dev_err(&client->dev, "I2C_FUNC_I2C not supported\n"); - err = -EIO; - goto err_out; + return -EIO; } - drvdata = kzalloc(sizeof(struct lm3530_data), GFP_KERNEL); - if (drvdata == NULL) { - err = -ENOMEM; - goto err_out; - } + drvdata = devm_kzalloc(&client->dev, sizeof(struct lm3530_data), + GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; drvdata->mode = pdata->mode; drvdata->client = client; @@ -380,15 +435,16 @@ static int __devinit lm3530_probe(struct i2c_client *client, drvdata->enable = false; drvdata->led_dev.name = LM3530_LED_DEV; drvdata->led_dev.brightness_set = lm3530_brightness_set; + drvdata->led_dev.max_brightness = MAX_BRIGHTNESS; i2c_set_clientdata(client, drvdata); - drvdata->regulator = regulator_get(&client->dev, "vin"); + 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; - goto err_regulator_get; + return err; } if (drvdata->pdata->brt_val) { @@ -396,15 +452,13 @@ static int __devinit lm3530_probe(struct i2c_client *client, if (err < 0) { dev_err(&client->dev, "Register Init failed: %d\n", err); - err = -ENODEV; - goto err_reg_init; + 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); - err = -ENODEV; - goto err_class_register; + return err; } err = device_create_file(drvdata->led_dev.dev, &dev_attr_mode); @@ -418,26 +472,17 @@ static int __devinit lm3530_probe(struct i2c_client *client, err_create_file: led_classdev_unregister(&drvdata->led_dev); -err_class_register: -err_reg_init: - regulator_put(drvdata->regulator); -err_regulator_get: - kfree(drvdata); -err_out: return err; } -static int __devexit lm3530_remove(struct i2c_client *client) +static int lm3530_remove(struct i2c_client *client) { struct lm3530_data *drvdata = i2c_get_clientdata(client); device_remove_file(drvdata->led_dev.dev, &dev_attr_mode); - if (drvdata->enable) - regulator_disable(drvdata->regulator); - regulator_put(drvdata->regulator); + lm3530_led_disable(drvdata); led_classdev_unregister(&drvdata->led_dev); - kfree(drvdata); return 0; } @@ -449,7 +494,7 @@ MODULE_DEVICE_TABLE(i2c, lm3530_id); static struct i2c_driver lm3530_i2c_driver = { .probe = lm3530_probe, - .remove = __devexit_p(lm3530_remove), + .remove = lm3530_remove, .id_table = lm3530_id, .driver = { .name = LM3530_NAME, diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c new file mode 100644 index 00000000000..e2c642c1169 --- /dev/null +++ b/drivers/leds/leds-lm3533.c @@ -0,0 +1,784 @@ +/* + * leds-lm3533.c -- LM3533 LED driver + * + * Copyright (C) 2011-2012 Texas Instruments + * + * Author: Johan Hovold <jhovold@gmail.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include <linux/module.h> +#include <linux/leds.h> +#include <linux/mfd/core.h> +#include <linux/mutex.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +#include <linux/mfd/lm3533.h> + + +#define LM3533_LVCTRLBANK_MIN 2 +#define LM3533_LVCTRLBANK_MAX 5 +#define LM3533_LVCTRLBANK_COUNT 4 +#define LM3533_RISEFALLTIME_MAX 7 +#define LM3533_ALS_CHANNEL_LV_MIN 1 +#define LM3533_ALS_CHANNEL_LV_MAX 2 + +#define LM3533_REG_CTRLBANK_BCONF_BASE 0x1b +#define LM3533_REG_PATTERN_ENABLE 0x28 +#define LM3533_REG_PATTERN_LOW_TIME_BASE 0x71 +#define LM3533_REG_PATTERN_HIGH_TIME_BASE 0x72 +#define LM3533_REG_PATTERN_RISETIME_BASE 0x74 +#define LM3533_REG_PATTERN_FALLTIME_BASE 0x75 + +#define LM3533_REG_PATTERN_STEP 0x10 + +#define LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK 0x04 +#define LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK 0x02 +#define LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK 0x01 + +#define LM3533_LED_FLAG_PATTERN_ENABLE 1 + + +struct lm3533_led { + struct lm3533 *lm3533; + struct lm3533_ctrlbank cb; + struct led_classdev cdev; + int id; + + struct mutex mutex; + unsigned long flags; + + struct work_struct work; + u8 new_brightness; +}; + + +static inline struct lm3533_led *to_lm3533_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct lm3533_led, cdev); +} + +static inline int lm3533_led_get_ctrlbank_id(struct lm3533_led *led) +{ + return led->id + 2; +} + +static inline u8 lm3533_led_get_lv_reg(struct lm3533_led *led, u8 base) +{ + return base + led->id; +} + +static inline u8 lm3533_led_get_pattern(struct lm3533_led *led) +{ + return led->id; +} + +static inline u8 lm3533_led_get_pattern_reg(struct lm3533_led *led, + u8 base) +{ + return base + lm3533_led_get_pattern(led) * LM3533_REG_PATTERN_STEP; +} + +static int lm3533_led_pattern_enable(struct lm3533_led *led, int enable) +{ + u8 mask; + u8 val; + int pattern; + int state; + int ret = 0; + + dev_dbg(led->cdev.dev, "%s - %d\n", __func__, enable); + + mutex_lock(&led->mutex); + + state = test_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags); + if ((enable && state) || (!enable && !state)) + goto out; + + pattern = lm3533_led_get_pattern(led); + mask = 1 << (2 * pattern); + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, LM3533_REG_PATTERN_ENABLE, val, mask); + if (ret) { + dev_err(led->cdev.dev, "failed to enable pattern %d (%d)\n", + pattern, enable); + goto out; + } + + __change_bit(LM3533_LED_FLAG_PATTERN_ENABLE, &led->flags); +out: + mutex_unlock(&led->mutex); + + return ret; +} + +static void lm3533_led_work(struct work_struct *work) +{ + struct lm3533_led *led = container_of(work, struct lm3533_led, work); + + dev_dbg(led->cdev.dev, "%s - %u\n", __func__, led->new_brightness); + + if (led->new_brightness == 0) + lm3533_led_pattern_enable(led, 0); /* disable blink */ + + lm3533_ctrlbank_set_brightness(&led->cb, led->new_brightness); +} + +static void lm3533_led_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + + dev_dbg(led->cdev.dev, "%s - %d\n", __func__, value); + + led->new_brightness = value; + schedule_work(&led->work); +} + +static enum led_brightness lm3533_led_get(struct led_classdev *cdev) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + u8 val; + int ret; + + ret = lm3533_ctrlbank_get_brightness(&led->cb, &val); + if (ret) + return ret; + + dev_dbg(led->cdev.dev, "%s - %u\n", __func__, val); + + return val; +} + +/* Pattern generator defines (delays in us). */ +#define LM3533_LED_DELAY1_VMIN 0x00 +#define LM3533_LED_DELAY2_VMIN 0x3d +#define LM3533_LED_DELAY3_VMIN 0x80 + +#define LM3533_LED_DELAY1_VMAX (LM3533_LED_DELAY2_VMIN - 1) +#define LM3533_LED_DELAY2_VMAX (LM3533_LED_DELAY3_VMIN - 1) +#define LM3533_LED_DELAY3_VMAX 0xff + +#define LM3533_LED_DELAY1_TMIN 16384U +#define LM3533_LED_DELAY2_TMIN 1130496U +#define LM3533_LED_DELAY3_TMIN 10305536U + +#define LM3533_LED_DELAY1_TMAX 999424U +#define LM3533_LED_DELAY2_TMAX 9781248U +#define LM3533_LED_DELAY3_TMAX 76890112U + +/* t_step = (t_max - t_min) / (v_max - v_min) */ +#define LM3533_LED_DELAY1_TSTEP 16384 +#define LM3533_LED_DELAY2_TSTEP 131072 +#define LM3533_LED_DELAY3_TSTEP 524288 + +/* Delay limits for hardware accelerated blinking (in ms). */ +#define LM3533_LED_DELAY_ON_MAX \ + ((LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY2_TSTEP / 2) / 1000) +#define LM3533_LED_DELAY_OFF_MAX \ + ((LM3533_LED_DELAY3_TMAX + LM3533_LED_DELAY3_TSTEP / 2) / 1000) + +/* + * Returns linear map of *t from [t_min,t_max] to [v_min,v_max] with a step + * size of t_step, where + * + * t_step = (t_max - t_min) / (v_max - v_min) + * + * and updates *t to reflect the mapped value. + */ +static u8 time_to_val(unsigned *t, unsigned t_min, unsigned t_step, + u8 v_min, u8 v_max) +{ + unsigned val; + + val = (*t + t_step / 2 - t_min) / t_step + v_min; + + *t = t_step * (val - v_min) + t_min; + + return (u8)val; +} + +/* + * Returns time code corresponding to *delay (in ms) and updates *delay to + * reflect actual hardware delay. + * + * Hardware supports 256 discrete delay times, divided into three groups with + * the following ranges and step-sizes: + * + * [ 16, 999] [0x00, 0x3e] step 16 ms + * [ 1130, 9781] [0x3d, 0x7f] step 131 ms + * [10306, 76890] [0x80, 0xff] step 524 ms + * + * Note that delay group 3 is only available for delay_off. + */ +static u8 lm3533_led_get_hw_delay(unsigned *delay) +{ + unsigned t; + u8 val; + + t = *delay * 1000; + + if (t >= (LM3533_LED_DELAY2_TMAX + LM3533_LED_DELAY3_TMIN) / 2) { + t = clamp(t, LM3533_LED_DELAY3_TMIN, LM3533_LED_DELAY3_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY3_TMIN, + LM3533_LED_DELAY3_TSTEP, + LM3533_LED_DELAY3_VMIN, + LM3533_LED_DELAY3_VMAX); + } else if (t >= (LM3533_LED_DELAY1_TMAX + LM3533_LED_DELAY2_TMIN) / 2) { + t = clamp(t, LM3533_LED_DELAY2_TMIN, LM3533_LED_DELAY2_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY2_TMIN, + LM3533_LED_DELAY2_TSTEP, + LM3533_LED_DELAY2_VMIN, + LM3533_LED_DELAY2_VMAX); + } else { + t = clamp(t, LM3533_LED_DELAY1_TMIN, LM3533_LED_DELAY1_TMAX); + val = time_to_val(&t, LM3533_LED_DELAY1_TMIN, + LM3533_LED_DELAY1_TSTEP, + LM3533_LED_DELAY1_VMIN, + LM3533_LED_DELAY1_VMAX); + } + + *delay = (t + 500) / 1000; + + return val; +} + +/* + * Set delay register base to *delay (in ms) and update *delay to reflect + * actual hardware delay used. + */ +static u8 lm3533_led_delay_set(struct lm3533_led *led, u8 base, + unsigned long *delay) +{ + unsigned t; + u8 val; + u8 reg; + int ret; + + t = (unsigned)*delay; + + /* Delay group 3 is only available for low time (delay off). */ + if (base != LM3533_REG_PATTERN_LOW_TIME_BASE) + t = min(t, LM3533_LED_DELAY2_TMAX / 1000); + + val = lm3533_led_get_hw_delay(&t); + + dev_dbg(led->cdev.dev, "%s - %lu: %u (0x%02x)\n", __func__, + *delay, t, val); + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_write(led->lm3533, reg, val); + if (ret) + dev_err(led->cdev.dev, "failed to set delay (%02x)\n", reg); + + *delay = t; + + return ret; +} + +static int lm3533_led_delay_on_set(struct lm3533_led *led, unsigned long *t) +{ + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_HIGH_TIME_BASE, t); +} + +static int lm3533_led_delay_off_set(struct lm3533_led *led, unsigned long *t) +{ + return lm3533_led_delay_set(led, LM3533_REG_PATTERN_LOW_TIME_BASE, t); +} + +static int lm3533_led_blink_set(struct led_classdev *cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct lm3533_led *led = to_lm3533_led(cdev); + int ret; + + dev_dbg(led->cdev.dev, "%s - on = %lu, off = %lu\n", __func__, + *delay_on, *delay_off); + + if (*delay_on > LM3533_LED_DELAY_ON_MAX || + *delay_off > LM3533_LED_DELAY_OFF_MAX) + return -EINVAL; + + if (*delay_on == 0 && *delay_off == 0) { + *delay_on = 500; + *delay_off = 500; + } + + ret = lm3533_led_delay_on_set(led, delay_on); + if (ret) + return ret; + + ret = lm3533_led_delay_off_set(led, delay_off); + if (ret) + return ret; + + return lm3533_led_pattern_enable(led, 1); +} + +static ssize_t show_id(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", led->id); +} + +/* + * Pattern generator rise/fall times: + * + * 0 - 2048 us (default) + * 1 - 262 ms + * 2 - 524 ms + * 3 - 1.049 s + * 4 - 2.097 s + * 5 - 4.194 s + * 6 - 8.389 s + * 7 - 16.78 s + */ +static ssize_t show_risefalltime(struct device *dev, + struct device_attribute *attr, + char *buf, u8 base) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + ssize_t ret; + u8 reg; + u8 val; + + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%x\n", val); +} + +static ssize_t show_risetime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_risefalltime(dev, attr, buf, + LM3533_REG_PATTERN_RISETIME_BASE); +} + +static ssize_t show_falltime(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return show_risefalltime(dev, attr, buf, + LM3533_REG_PATTERN_FALLTIME_BASE); +} + +static ssize_t store_risefalltime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, u8 base) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + u8 reg; + int ret; + + if (kstrtou8(buf, 0, &val) || val > LM3533_RISEFALLTIME_MAX) + return -EINVAL; + + reg = lm3533_led_get_pattern_reg(led, base); + ret = lm3533_write(led->lm3533, reg, val); + if (ret) + return ret; + + return len; +} + +static ssize_t store_risetime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return store_risefalltime(dev, attr, buf, len, + LM3533_REG_PATTERN_RISETIME_BASE); +} + +static ssize_t store_falltime(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + return store_risefalltime(dev, attr, buf, len, + LM3533_REG_PATTERN_FALLTIME_BASE); +} + +static ssize_t show_als_channel(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned channel; + u8 reg; + u8 val; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + channel = (val & LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK) + 1; + + return scnprintf(buf, PAGE_SIZE, "%u\n", channel); +} + +static ssize_t store_als_channel(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned channel; + u8 reg; + u8 val; + u8 mask; + int ret; + + if (kstrtouint(buf, 0, &channel)) + return -EINVAL; + + if (channel < LM3533_ALS_CHANNEL_LV_MIN || + channel > LM3533_ALS_CHANNEL_LV_MAX) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_ALS_CHANNEL_MASK; + val = channel - 1; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_als_en(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + bool enable; + u8 reg; + u8 val; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + enable = val & LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK; + + return scnprintf(buf, PAGE_SIZE, "%d\n", enable); +} + +static ssize_t store_als_en(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned enable; + u8 reg; + u8 mask; + u8 val; + int ret; + + if (kstrtouint(buf, 0, &enable)) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_ALS_EN_MASK; + + if (enable) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_linear(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 reg; + u8 val; + int linear; + int ret; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + ret = lm3533_read(led->lm3533, reg, &val); + if (ret) + return ret; + + if (val & LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK) + linear = 1; + else + linear = 0; + + return scnprintf(buf, PAGE_SIZE, "%x\n", linear); +} + +static ssize_t store_linear(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + unsigned long linear; + u8 reg; + u8 mask; + u8 val; + int ret; + + if (kstrtoul(buf, 0, &linear)) + return -EINVAL; + + reg = lm3533_led_get_lv_reg(led, LM3533_REG_CTRLBANK_BCONF_BASE); + mask = LM3533_REG_CTRLBANK_BCONF_MAPPING_MASK; + + if (linear) + val = mask; + else + val = 0; + + ret = lm3533_update(led->lm3533, reg, val, mask); + if (ret) + return ret; + + return len; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + int ret; + + ret = lm3533_ctrlbank_get_pwm(&led->cb, &val); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%u\n", val); +} + +static ssize_t store_pwm(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + u8 val; + int ret; + + if (kstrtou8(buf, 0, &val)) + return -EINVAL; + + ret = lm3533_ctrlbank_set_pwm(&led->cb, val); + if (ret) + return ret; + + return len; +} + +static LM3533_ATTR_RW(als_channel); +static LM3533_ATTR_RW(als_en); +static LM3533_ATTR_RW(falltime); +static LM3533_ATTR_RO(id); +static LM3533_ATTR_RW(linear); +static LM3533_ATTR_RW(pwm); +static LM3533_ATTR_RW(risetime); + +static struct attribute *lm3533_led_attributes[] = { + &dev_attr_als_channel.attr, + &dev_attr_als_en.attr, + &dev_attr_falltime.attr, + &dev_attr_id.attr, + &dev_attr_linear.attr, + &dev_attr_pwm.attr, + &dev_attr_risetime.attr, + NULL, +}; + +static umode_t lm3533_led_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3533_led *led = to_lm3533_led(led_cdev); + umode_t mode = attr->mode; + + if (attr == &dev_attr_als_channel.attr || + attr == &dev_attr_als_en.attr) { + if (!led->lm3533->have_als) + mode = 0; + } + + return mode; +}; + +static struct attribute_group lm3533_led_attribute_group = { + .is_visible = lm3533_led_attr_is_visible, + .attrs = lm3533_led_attributes +}; + +static int lm3533_led_setup(struct lm3533_led *led, + struct lm3533_led_platform_data *pdata) +{ + int ret; + + ret = lm3533_ctrlbank_set_max_current(&led->cb, pdata->max_current); + if (ret) + return ret; + + return lm3533_ctrlbank_set_pwm(&led->cb, pdata->pwm); +} + +static int lm3533_led_probe(struct platform_device *pdev) +{ + struct lm3533 *lm3533; + struct lm3533_led_platform_data *pdata; + struct lm3533_led *led; + int ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533 = dev_get_drvdata(pdev->dev.parent); + if (!lm3533) + return -EINVAL; + + pdata = dev_get_platdata(&pdev->dev); + if (!pdata) { + dev_err(&pdev->dev, "no platform data\n"); + return -EINVAL; + } + + if (pdev->id < 0 || pdev->id >= LM3533_LVCTRLBANK_COUNT) { + dev_err(&pdev->dev, "illegal LED id %d\n", pdev->id); + return -EINVAL; + } + + led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->lm3533 = lm3533; + led->cdev.name = pdata->name; + led->cdev.default_trigger = pdata->default_trigger; + led->cdev.brightness_set = lm3533_led_set; + led->cdev.brightness_get = lm3533_led_get; + led->cdev.blink_set = lm3533_led_blink_set; + led->cdev.brightness = LED_OFF; + led->id = pdev->id; + + mutex_init(&led->mutex); + INIT_WORK(&led->work, lm3533_led_work); + + /* The class framework makes a callback to get brightness during + * registration so use parent device (for error reporting) until + * registered. + */ + led->cb.lm3533 = lm3533; + led->cb.id = lm3533_led_get_ctrlbank_id(led); + led->cb.dev = lm3533->dev; + + platform_set_drvdata(pdev, led); + + ret = led_classdev_register(pdev->dev.parent, &led->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register LED %d\n", pdev->id); + return ret; + } + + led->cb.dev = led->cdev.dev; + + ret = sysfs_create_group(&led->cdev.dev->kobj, + &lm3533_led_attribute_group); + if (ret < 0) { + dev_err(&pdev->dev, "failed to create sysfs attributes\n"); + goto err_unregister; + } + + ret = lm3533_led_setup(led, pdata); + if (ret) + goto err_sysfs_remove; + + ret = lm3533_ctrlbank_enable(&led->cb); + if (ret) + goto err_sysfs_remove; + + return 0; + +err_sysfs_remove: + sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group); +err_unregister: + led_classdev_unregister(&led->cdev); + flush_work(&led->work); + + return ret; +} + +static int lm3533_led_remove(struct platform_device *pdev) +{ + struct lm3533_led *led = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533_ctrlbank_disable(&led->cb); + sysfs_remove_group(&led->cdev.dev->kobj, &lm3533_led_attribute_group); + led_classdev_unregister(&led->cdev); + flush_work(&led->work); + + return 0; +} + +static void lm3533_led_shutdown(struct platform_device *pdev) +{ + + struct lm3533_led *led = platform_get_drvdata(pdev); + + dev_dbg(&pdev->dev, "%s\n", __func__); + + lm3533_ctrlbank_disable(&led->cb); + lm3533_led_set(&led->cdev, LED_OFF); /* disable blink */ + flush_work(&led->work); +} + +static struct platform_driver lm3533_led_driver = { + .driver = { + .name = "lm3533-leds", + .owner = THIS_MODULE, + }, + .probe = lm3533_led_probe, + .remove = lm3533_led_remove, + .shutdown = lm3533_led_shutdown, +}; +module_platform_driver(lm3533_led_driver); + +MODULE_AUTHOR("Johan Hovold <jhovold@gmail.com>"); +MODULE_DESCRIPTION("LM3533 LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lm3533-leds"); diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c new file mode 100644 index 00000000000..591eb5e58ae --- /dev/null +++ b/drivers/leds/leds-lm355x.c @@ -0,0 +1,574 @@ +/* +* Simple driver for Texas Instruments LM355x LED Flash driver chip +* Copyright (C) 2012 Texas Instruments +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/gpio.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> +#include <linux/platform_data/leds-lm355x.h> + +enum lm355x_type { + CHIP_LM3554 = 0, + CHIP_LM3556, +}; + +enum lm355x_regs { + REG_FLAG = 0, + REG_TORCH_CFG, + REG_TORCH_CTRL, + REG_STROBE_CFG, + REG_FLASH_CTRL, + REG_INDI_CFG, + REG_INDI_CTRL, + REG_OPMODE, + REG_MAX, +}; + +/* operation mode */ +enum lm355x_mode { + MODE_SHDN = 0, + MODE_INDIC, + MODE_TORCH, + MODE_FLASH +}; + +/* register map info. */ +struct lm355x_reg_data { + u8 regno; + u8 mask; + u8 shift; +}; + +struct lm355x_chip_data { + struct device *dev; + enum lm355x_type type; + + struct led_classdev cdev_flash; + struct led_classdev cdev_torch; + struct led_classdev cdev_indicator; + + struct work_struct work_flash; + struct work_struct work_torch; + struct work_struct work_indicator; + + u8 br_flash; + u8 br_torch; + u8 br_indicator; + + struct lm355x_platform_data *pdata; + struct regmap *regmap; + struct mutex lock; + + unsigned int last_flag; + struct lm355x_reg_data *regs; +}; + +/* specific indicator function for lm3556 */ +enum lm3556_indic_pulse_time { + PULSE_TIME_0_MS = 0, + PULSE_TIME_32_MS, + PULSE_TIME_64_MS, + PULSE_TIME_92_MS, + PULSE_TIME_128_MS, + PULSE_TIME_160_MS, + PULSE_TIME_196_MS, + PULSE_TIME_224_MS, + PULSE_TIME_256_MS, + PULSE_TIME_288_MS, + PULSE_TIME_320_MS, + PULSE_TIME_352_MS, + PULSE_TIME_384_MS, + PULSE_TIME_416_MS, + PULSE_TIME_448_MS, + PULSE_TIME_480_MS, +}; + +enum lm3556_indic_n_blank { + INDIC_N_BLANK_0 = 0, + INDIC_N_BLANK_1, + INDIC_N_BLANK_2, + INDIC_N_BLANK_3, + INDIC_N_BLANK_4, + INDIC_N_BLANK_5, + INDIC_N_BLANK_6, + INDIC_N_BLANK_7, + INDIC_N_BLANK_8, + INDIC_N_BLANK_9, + INDIC_N_BLANK_10, + INDIC_N_BLANK_11, + INDIC_N_BLANK_12, + INDIC_N_BLANK_13, + INDIC_N_BLANK_14, + INDIC_N_BLANK_15, +}; + +enum lm3556_indic_period { + INDIC_PERIOD_0 = 0, + INDIC_PERIOD_1, + INDIC_PERIOD_2, + INDIC_PERIOD_3, + INDIC_PERIOD_4, + INDIC_PERIOD_5, + INDIC_PERIOD_6, + INDIC_PERIOD_7, +}; + +#define INDIC_PATTERN_SIZE 4 + +struct indicator { + u8 blinking; + u8 period_cnt; +}; + +/* indicator pattern data only for lm3556 */ +static struct indicator indicator_pattern[INDIC_PATTERN_SIZE] = { + [0] = {(INDIC_N_BLANK_1 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_1}, + [1] = {(INDIC_N_BLANK_15 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_2}, + [2] = {(INDIC_N_BLANK_10 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_4}, + [3] = {(INDIC_N_BLANK_5 << 4) | PULSE_TIME_32_MS, INDIC_PERIOD_7}, +}; + +static struct lm355x_reg_data lm3554_regs[REG_MAX] = { + [REG_FLAG] = {0xD0, 0xBF, 0}, + [REG_TORCH_CFG] = {0xE0, 0x80, 7}, + [REG_TORCH_CTRL] = {0xA0, 0x38, 3}, + [REG_STROBE_CFG] = {0xE0, 0x04, 2}, + [REG_FLASH_CTRL] = {0xB0, 0x78, 3}, + [REG_INDI_CFG] = {0xE0, 0x08, 3}, + [REG_INDI_CTRL] = {0xA0, 0xC0, 6}, + [REG_OPMODE] = {0xA0, 0x03, 0}, +}; + +static struct lm355x_reg_data lm3556_regs[REG_MAX] = { + [REG_FLAG] = {0x0B, 0xFF, 0}, + [REG_TORCH_CFG] = {0x0A, 0x10, 4}, + [REG_TORCH_CTRL] = {0x09, 0x70, 4}, + [REG_STROBE_CFG] = {0x0A, 0x20, 5}, + [REG_FLASH_CTRL] = {0x09, 0x0F, 0}, + [REG_INDI_CFG] = {0xFF, 0xFF, 0}, + [REG_INDI_CTRL] = {0x09, 0x70, 4}, + [REG_OPMODE] = {0x0A, 0x03, 0}, +}; + +static char lm355x_name[][I2C_NAME_SIZE] = { + [CHIP_LM3554] = LM3554_NAME, + [CHIP_LM3556] = LM3556_NAME, +}; + +/* chip initialize */ +static int lm355x_chip_init(struct lm355x_chip_data *chip) +{ + int ret; + unsigned int reg_val; + struct lm355x_platform_data *pdata = chip->pdata; + + /* input and output pins configuration */ + switch (chip->type) { + case CHIP_LM3554: + reg_val = pdata->pin_tx2 | pdata->ntc_pin; + ret = regmap_update_bits(chip->regmap, 0xE0, 0x28, reg_val); + if (ret < 0) + goto out; + reg_val = pdata->pass_mode; + ret = regmap_update_bits(chip->regmap, 0xA0, 0x04, reg_val); + if (ret < 0) + goto out; + break; + + case CHIP_LM3556: + reg_val = pdata->pin_tx2 | pdata->ntc_pin | pdata->pass_mode; + ret = regmap_update_bits(chip->regmap, 0x0A, 0xC4, reg_val); + if (ret < 0) + goto out; + break; + default: + return -ENODATA; + } + + return ret; +out: + dev_err(chip->dev, "%s:i2c access fail to register\n", __func__); + return ret; +} + +/* chip control */ +static void lm355x_control(struct lm355x_chip_data *chip, + u8 brightness, enum lm355x_mode opmode) +{ + int ret; + unsigned int reg_val; + struct lm355x_platform_data *pdata = chip->pdata; + struct lm355x_reg_data *preg = chip->regs; + + ret = regmap_read(chip->regmap, preg[REG_FLAG].regno, &chip->last_flag); + if (ret < 0) + goto out; + if (chip->last_flag & preg[REG_FLAG].mask) + dev_info(chip->dev, "%s Last FLAG is 0x%x\n", + lm355x_name[chip->type], + chip->last_flag & preg[REG_FLAG].mask); + /* brightness 0 means shutdown */ + if (!brightness) + opmode = MODE_SHDN; + + switch (opmode) { + case MODE_TORCH: + ret = + regmap_update_bits(chip->regmap, preg[REG_TORCH_CTRL].regno, + preg[REG_TORCH_CTRL].mask, + (brightness - 1) + << preg[REG_TORCH_CTRL].shift); + if (ret < 0) + goto out; + + if (pdata->pin_tx1 != LM355x_PIN_TORCH_DISABLE) { + ret = + regmap_update_bits(chip->regmap, + preg[REG_TORCH_CFG].regno, + preg[REG_TORCH_CFG].mask, + 0x01 << + preg[REG_TORCH_CFG].shift); + if (ret < 0) + goto out; + opmode = MODE_SHDN; + dev_info(chip->dev, + "torch brt is set - ext. torch pin mode\n"); + } + break; + + case MODE_FLASH: + + ret = + regmap_update_bits(chip->regmap, preg[REG_FLASH_CTRL].regno, + preg[REG_FLASH_CTRL].mask, + (brightness - 1) + << preg[REG_FLASH_CTRL].shift); + if (ret < 0) + goto out; + + if (pdata->pin_strobe != LM355x_PIN_STROBE_DISABLE) { + if (chip->type == CHIP_LM3554) + reg_val = 0x00; + else + reg_val = 0x01; + ret = + regmap_update_bits(chip->regmap, + preg[REG_STROBE_CFG].regno, + preg[REG_STROBE_CFG].mask, + reg_val << + preg[REG_STROBE_CFG].shift); + if (ret < 0) + goto out; + opmode = MODE_SHDN; + dev_info(chip->dev, + "flash brt is set - ext. strobe pin mode\n"); + } + break; + + case MODE_INDIC: + ret = + regmap_update_bits(chip->regmap, preg[REG_INDI_CTRL].regno, + preg[REG_INDI_CTRL].mask, + (brightness - 1) + << preg[REG_INDI_CTRL].shift); + if (ret < 0) + goto out; + + if (pdata->pin_tx2 != LM355x_PIN_TX_DISABLE) { + ret = + regmap_update_bits(chip->regmap, + preg[REG_INDI_CFG].regno, + preg[REG_INDI_CFG].mask, + 0x01 << + preg[REG_INDI_CFG].shift); + if (ret < 0) + goto out; + opmode = MODE_SHDN; + } + break; + case MODE_SHDN: + break; + default: + return; + } + /* operation mode control */ + ret = regmap_update_bits(chip->regmap, preg[REG_OPMODE].regno, + preg[REG_OPMODE].mask, + opmode << preg[REG_OPMODE].shift); + if (ret < 0) + goto out; + return; +out: + dev_err(chip->dev, "%s:i2c access fail to register\n", __func__); + return; +} + +/* torch */ +static void lm355x_deferred_torch_brightness_set(struct work_struct *work) +{ + struct lm355x_chip_data *chip = + container_of(work, struct lm355x_chip_data, work_torch); + + mutex_lock(&chip->lock); + lm355x_control(chip, chip->br_torch, MODE_TORCH); + mutex_unlock(&chip->lock); +} + +static void lm355x_torch_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm355x_chip_data *chip = + container_of(cdev, struct lm355x_chip_data, cdev_torch); + + chip->br_torch = brightness; + schedule_work(&chip->work_torch); +} + +/* flash */ +static void lm355x_deferred_strobe_brightness_set(struct work_struct *work) +{ + struct lm355x_chip_data *chip = + container_of(work, struct lm355x_chip_data, work_flash); + + mutex_lock(&chip->lock); + lm355x_control(chip, chip->br_flash, MODE_FLASH); + mutex_unlock(&chip->lock); +} + +static void lm355x_strobe_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm355x_chip_data *chip = + container_of(cdev, struct lm355x_chip_data, cdev_flash); + + chip->br_flash = brightness; + schedule_work(&chip->work_flash); +} + +/* indicator */ +static void lm355x_deferred_indicator_brightness_set(struct work_struct *work) +{ + struct lm355x_chip_data *chip = + container_of(work, struct lm355x_chip_data, work_indicator); + + mutex_lock(&chip->lock); + lm355x_control(chip, chip->br_indicator, MODE_INDIC); + mutex_unlock(&chip->lock); +} + +static void lm355x_indicator_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm355x_chip_data *chip = + container_of(cdev, struct lm355x_chip_data, cdev_indicator); + + chip->br_indicator = brightness; + schedule_work(&chip->work_indicator); +} + +/* indicator pattern only for lm3556*/ +static ssize_t lm3556_indicator_pattern_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t ret; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm355x_chip_data *chip = + container_of(led_cdev, struct lm355x_chip_data, cdev_indicator); + unsigned int state; + + ret = kstrtouint(buf, 10, &state); + if (ret) + goto out; + if (state > INDIC_PATTERN_SIZE - 1) + state = INDIC_PATTERN_SIZE - 1; + + ret = regmap_write(chip->regmap, 0x04, + indicator_pattern[state].blinking); + if (ret < 0) + goto out; + + ret = regmap_write(chip->regmap, 0x05, + indicator_pattern[state].period_cnt); + if (ret < 0) + goto out; + + return size; +out: + dev_err(chip->dev, "%s:i2c access fail to register\n", __func__); + return ret; +} + +static DEVICE_ATTR(pattern, S_IWUSR, NULL, lm3556_indicator_pattern_store); + +static const struct regmap_config lm355x_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = 0xFF, +}; + +/* module initialize */ +static int lm355x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm355x_platform_data *pdata = dev_get_platdata(&client->dev); + struct lm355x_chip_data *chip; + + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c functionality check fail.\n"); + return -EOPNOTSUPP; + } + + if (pdata == NULL) { + dev_err(&client->dev, "needs Platform Data.\n"); + return -ENODATA; + } + + chip = devm_kzalloc(&client->dev, + sizeof(struct lm355x_chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &client->dev; + chip->type = id->driver_data; + switch (id->driver_data) { + case CHIP_LM3554: + chip->regs = lm3554_regs; + break; + case CHIP_LM3556: + chip->regs = lm3556_regs; + break; + default: + return -ENOSYS; + } + chip->pdata = pdata; + + chip->regmap = devm_regmap_init_i2c(client, &lm355x_regmap); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(&client->dev, + "Failed to allocate register map: %d\n", err); + return err; + } + + mutex_init(&chip->lock); + i2c_set_clientdata(client, chip); + + err = lm355x_chip_init(chip); + if (err < 0) + goto err_out; + + /* flash */ + INIT_WORK(&chip->work_flash, lm355x_deferred_strobe_brightness_set); + chip->cdev_flash.name = "flash"; + chip->cdev_flash.max_brightness = 16; + chip->cdev_flash.brightness_set = lm355x_strobe_brightness_set; + chip->cdev_flash.default_trigger = "flash"; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_flash); + if (err < 0) + goto err_out; + /* torch */ + INIT_WORK(&chip->work_torch, lm355x_deferred_torch_brightness_set); + chip->cdev_torch.name = "torch"; + chip->cdev_torch.max_brightness = 8; + chip->cdev_torch.brightness_set = lm355x_torch_brightness_set; + chip->cdev_torch.default_trigger = "torch"; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_torch); + if (err < 0) + goto err_create_torch_file; + /* indicator */ + INIT_WORK(&chip->work_indicator, + lm355x_deferred_indicator_brightness_set); + chip->cdev_indicator.name = "indicator"; + if (id->driver_data == CHIP_LM3554) + chip->cdev_indicator.max_brightness = 4; + else + chip->cdev_indicator.max_brightness = 8; + chip->cdev_indicator.brightness_set = lm355x_indicator_brightness_set; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_indicator); + if (err < 0) + goto err_create_indicator_file; + /* indicator pattern control only for LM3554 */ + if (id->driver_data == CHIP_LM3556) { + err = + device_create_file(chip->cdev_indicator.dev, + &dev_attr_pattern); + if (err < 0) + goto err_create_pattern_file; + } + + dev_info(&client->dev, "%s is initialized\n", + lm355x_name[id->driver_data]); + return 0; + +err_create_pattern_file: + led_classdev_unregister(&chip->cdev_indicator); +err_create_indicator_file: + led_classdev_unregister(&chip->cdev_torch); +err_create_torch_file: + led_classdev_unregister(&chip->cdev_flash); +err_out: + return err; +} + +static int lm355x_remove(struct i2c_client *client) +{ + struct lm355x_chip_data *chip = i2c_get_clientdata(client); + struct lm355x_reg_data *preg = chip->regs; + + regmap_write(chip->regmap, preg[REG_OPMODE].regno, 0); + if (chip->type == CHIP_LM3556) + device_remove_file(chip->cdev_indicator.dev, &dev_attr_pattern); + led_classdev_unregister(&chip->cdev_indicator); + flush_work(&chip->work_indicator); + led_classdev_unregister(&chip->cdev_torch); + flush_work(&chip->work_torch); + led_classdev_unregister(&chip->cdev_flash); + flush_work(&chip->work_flash); + dev_info(&client->dev, "%s is removed\n", lm355x_name[chip->type]); + + return 0; +} + +static const struct i2c_device_id lm355x_id[] = { + {LM3554_NAME, CHIP_LM3554}, + {LM3556_NAME, CHIP_LM3556}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm355x_id); + +static struct i2c_driver lm355x_i2c_driver = { + .driver = { + .name = LM355x_NAME, + .owner = THIS_MODULE, + .pm = NULL, + }, + .probe = lm355x_probe, + .remove = lm355x_remove, + .id_table = lm355x_id, +}; + +module_i2c_driver(lm355x_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM355x"); +MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>"); +MODULE_AUTHOR("G.Shark Jeong <gshark.jeong@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c new file mode 100644 index 00000000000..ceb6b3cde6f --- /dev/null +++ b/drivers/leds/leds-lm3642.c @@ -0,0 +1,464 @@ +/* +* Simple driver for Texas Instruments LM3642 LED Flash driver chip +* Copyright (C) 2012 Texas Instruments +* +* This program is free software; you can redistribute it and/or modify +* it under the terms of the GNU General Public License version 2 as +* published by the Free Software Foundation. +* +*/ +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/fs.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> +#include <linux/platform_data/leds-lm3642.h> + +#define REG_FILT_TIME (0x0) +#define REG_IVFM_MODE (0x1) +#define REG_TORCH_TIME (0x6) +#define REG_FLASH (0x8) +#define REG_I_CTRL (0x9) +#define REG_ENABLE (0xA) +#define REG_FLAG (0xB) +#define REG_MAX (0xB) + +#define UVLO_EN_SHIFT (7) +#define IVM_D_TH_SHIFT (2) +#define TORCH_RAMP_UP_TIME_SHIFT (3) +#define TORCH_RAMP_DN_TIME_SHIFT (0) +#define INDUCTOR_I_LIMIT_SHIFT (6) +#define FLASH_RAMP_TIME_SHIFT (3) +#define FLASH_TOUT_TIME_SHIFT (0) +#define TORCH_I_SHIFT (4) +#define FLASH_I_SHIFT (0) +#define IVFM_SHIFT (7) +#define TX_PIN_EN_SHIFT (6) +#define STROBE_PIN_EN_SHIFT (5) +#define TORCH_PIN_EN_SHIFT (4) +#define MODE_BITS_SHIFT (0) + +#define UVLO_EN_MASK (0x1) +#define IVM_D_TH_MASK (0x7) +#define TORCH_RAMP_UP_TIME_MASK (0x7) +#define TORCH_RAMP_DN_TIME_MASK (0x7) +#define INDUCTOR_I_LIMIT_MASK (0x1) +#define FLASH_RAMP_TIME_MASK (0x7) +#define FLASH_TOUT_TIME_MASK (0x7) +#define TORCH_I_MASK (0x7) +#define FLASH_I_MASK (0xF) +#define IVFM_MASK (0x1) +#define TX_PIN_EN_MASK (0x1) +#define STROBE_PIN_EN_MASK (0x1) +#define TORCH_PIN_EN_MASK (0x1) +#define MODE_BITS_MASK (0x73) +#define EX_PIN_CONTROL_MASK (0x71) +#define EX_PIN_ENABLE_MASK (0x70) + +enum lm3642_mode { + MODES_STASNDBY = 0, + MODES_INDIC, + MODES_TORCH, + MODES_FLASH +}; + +struct lm3642_chip_data { + struct device *dev; + + struct led_classdev cdev_flash; + struct led_classdev cdev_torch; + struct led_classdev cdev_indicator; + + struct work_struct work_flash; + struct work_struct work_torch; + struct work_struct work_indicator; + + u8 br_flash; + u8 br_torch; + u8 br_indicator; + + enum lm3642_torch_pin_enable torch_pin; + enum lm3642_strobe_pin_enable strobe_pin; + enum lm3642_tx_pin_enable tx_pin; + + struct lm3642_platform_data *pdata; + struct regmap *regmap; + struct mutex lock; + + unsigned int last_flag; +}; + +/* chip initialize */ +static int lm3642_chip_init(struct lm3642_chip_data *chip) +{ + int ret; + struct lm3642_platform_data *pdata = chip->pdata; + + /* set enable register */ + ret = regmap_update_bits(chip->regmap, REG_ENABLE, EX_PIN_ENABLE_MASK, + pdata->tx_pin); + if (ret < 0) + dev_err(chip->dev, "Failed to update REG_ENABLE Register\n"); + return ret; +} + +/* chip control */ +static int lm3642_control(struct lm3642_chip_data *chip, + u8 brightness, enum lm3642_mode opmode) +{ + int ret; + + ret = regmap_read(chip->regmap, REG_FLAG, &chip->last_flag); + if (ret < 0) { + dev_err(chip->dev, "Failed to read REG_FLAG Register\n"); + goto out; + } + + if (chip->last_flag) + dev_info(chip->dev, "Last FLAG is 0x%x\n", chip->last_flag); + + /* brightness 0 means off-state */ + if (!brightness) + opmode = MODES_STASNDBY; + + switch (opmode) { + case MODES_TORCH: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + TORCH_I_MASK << TORCH_I_SHIFT, + (brightness - 1) << TORCH_I_SHIFT); + + if (chip->torch_pin) + opmode |= (TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT); + break; + + case MODES_FLASH: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + FLASH_I_MASK << FLASH_I_SHIFT, + (brightness - 1) << FLASH_I_SHIFT); + + if (chip->strobe_pin) + opmode |= (STROBE_PIN_EN_MASK << STROBE_PIN_EN_SHIFT); + break; + + case MODES_INDIC: + ret = regmap_update_bits(chip->regmap, REG_I_CTRL, + TORCH_I_MASK << TORCH_I_SHIFT, + (brightness - 1) << TORCH_I_SHIFT); + break; + + case MODES_STASNDBY: + + break; + + default: + return ret; + } + if (ret < 0) { + dev_err(chip->dev, "Failed to write REG_I_CTRL Register\n"); + goto out; + } + + if (chip->tx_pin) + opmode |= (TX_PIN_EN_MASK << TX_PIN_EN_SHIFT); + + ret = regmap_update_bits(chip->regmap, REG_ENABLE, + MODE_BITS_MASK << MODE_BITS_SHIFT, + opmode << MODE_BITS_SHIFT); +out: + return ret; +} + +/* torch */ + +/* torch pin config for lm3642*/ +static ssize_t lm3642_torch_pin_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t ret; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3642_chip_data *chip = + container_of(led_cdev, struct lm3642_chip_data, cdev_indicator); + unsigned int state; + + ret = kstrtouint(buf, 10, &state); + if (ret) + goto out_strtoint; + if (state != 0) + state = 0x01 << TORCH_PIN_EN_SHIFT; + + chip->torch_pin = state; + ret = regmap_update_bits(chip->regmap, REG_ENABLE, + TORCH_PIN_EN_MASK << TORCH_PIN_EN_SHIFT, + state); + if (ret < 0) + goto out; + + return size; +out: + dev_err(chip->dev, "%s:i2c access fail to register\n", __func__); + return ret; +out_strtoint: + dev_err(chip->dev, "%s: fail to change str to int\n", __func__); + return ret; +} + +static DEVICE_ATTR(torch_pin, S_IWUSR, NULL, lm3642_torch_pin_store); + +static void lm3642_deferred_torch_brightness_set(struct work_struct *work) +{ + struct lm3642_chip_data *chip = + container_of(work, struct lm3642_chip_data, work_torch); + + mutex_lock(&chip->lock); + lm3642_control(chip, chip->br_torch, MODES_TORCH); + mutex_unlock(&chip->lock); +} + +static void lm3642_torch_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3642_chip_data *chip = + container_of(cdev, struct lm3642_chip_data, cdev_torch); + + chip->br_torch = brightness; + schedule_work(&chip->work_torch); +} + +/* flash */ + +/* strobe pin config for lm3642*/ +static ssize_t lm3642_strobe_pin_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + ssize_t ret; + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct lm3642_chip_data *chip = + container_of(led_cdev, struct lm3642_chip_data, cdev_indicator); + unsigned int state; + + ret = kstrtouint(buf, 10, &state); + if (ret) + goto out_strtoint; + if (state != 0) + state = 0x01 << STROBE_PIN_EN_SHIFT; + + chip->strobe_pin = state; + ret = regmap_update_bits(chip->regmap, REG_ENABLE, + STROBE_PIN_EN_MASK << STROBE_PIN_EN_SHIFT, + state); + if (ret < 0) + goto out; + + return size; +out: + dev_err(chip->dev, "%s:i2c access fail to register\n", __func__); + return ret; +out_strtoint: + dev_err(chip->dev, "%s: fail to change str to int\n", __func__); + return ret; +} + +static DEVICE_ATTR(strobe_pin, S_IWUSR, NULL, lm3642_strobe_pin_store); + +static void lm3642_deferred_strobe_brightness_set(struct work_struct *work) +{ + struct lm3642_chip_data *chip = + container_of(work, struct lm3642_chip_data, work_flash); + + mutex_lock(&chip->lock); + lm3642_control(chip, chip->br_flash, MODES_FLASH); + mutex_unlock(&chip->lock); +} + +static void lm3642_strobe_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3642_chip_data *chip = + container_of(cdev, struct lm3642_chip_data, cdev_flash); + + chip->br_flash = brightness; + schedule_work(&chip->work_flash); +} + +/* indicator */ +static void lm3642_deferred_indicator_brightness_set(struct work_struct *work) +{ + struct lm3642_chip_data *chip = + container_of(work, struct lm3642_chip_data, work_indicator); + + mutex_lock(&chip->lock); + lm3642_control(chip, chip->br_indicator, MODES_INDIC); + mutex_unlock(&chip->lock); +} + +static void lm3642_indicator_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lm3642_chip_data *chip = + container_of(cdev, struct lm3642_chip_data, cdev_indicator); + + chip->br_indicator = brightness; + schedule_work(&chip->work_indicator); +} + +static const struct regmap_config lm3642_regmap = { + .reg_bits = 8, + .val_bits = 8, + .max_register = REG_MAX, +}; + +static int lm3642_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct lm3642_platform_data *pdata = dev_get_platdata(&client->dev); + struct lm3642_chip_data *chip; + + int err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { + dev_err(&client->dev, "i2c functionality check fail.\n"); + return -EOPNOTSUPP; + } + + if (pdata == NULL) { + dev_err(&client->dev, "needs Platform Data.\n"); + return -ENODATA; + } + + chip = devm_kzalloc(&client->dev, + sizeof(struct lm3642_chip_data), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->dev = &client->dev; + chip->pdata = pdata; + + chip->tx_pin = pdata->tx_pin; + chip->torch_pin = pdata->torch_pin; + chip->strobe_pin = pdata->strobe_pin; + + chip->regmap = devm_regmap_init_i2c(client, &lm3642_regmap); + if (IS_ERR(chip->regmap)) { + err = PTR_ERR(chip->regmap); + dev_err(&client->dev, "Failed to allocate register map: %d\n", + err); + return err; + } + + mutex_init(&chip->lock); + i2c_set_clientdata(client, chip); + + err = lm3642_chip_init(chip); + if (err < 0) + goto err_out; + + /* flash */ + INIT_WORK(&chip->work_flash, lm3642_deferred_strobe_brightness_set); + chip->cdev_flash.name = "flash"; + chip->cdev_flash.max_brightness = 16; + chip->cdev_flash.brightness_set = lm3642_strobe_brightness_set; + chip->cdev_flash.default_trigger = "flash"; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_flash); + if (err < 0) { + dev_err(chip->dev, "failed to register flash\n"); + goto err_out; + } + err = device_create_file(chip->cdev_flash.dev, &dev_attr_strobe_pin); + if (err < 0) { + dev_err(chip->dev, "failed to create strobe-pin file\n"); + goto err_create_flash_pin_file; + } + + /* torch */ + INIT_WORK(&chip->work_torch, lm3642_deferred_torch_brightness_set); + chip->cdev_torch.name = "torch"; + chip->cdev_torch.max_brightness = 8; + chip->cdev_torch.brightness_set = lm3642_torch_brightness_set; + chip->cdev_torch.default_trigger = "torch"; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_torch); + if (err < 0) { + dev_err(chip->dev, "failed to register torch\n"); + goto err_create_torch_file; + } + err = device_create_file(chip->cdev_torch.dev, &dev_attr_torch_pin); + if (err < 0) { + dev_err(chip->dev, "failed to create torch-pin file\n"); + goto err_create_torch_pin_file; + } + + /* indicator */ + INIT_WORK(&chip->work_indicator, + lm3642_deferred_indicator_brightness_set); + chip->cdev_indicator.name = "indicator"; + chip->cdev_indicator.max_brightness = 8; + chip->cdev_indicator.brightness_set = lm3642_indicator_brightness_set; + err = led_classdev_register((struct device *) + &client->dev, &chip->cdev_indicator); + if (err < 0) { + dev_err(chip->dev, "failed to register indicator\n"); + goto err_create_indicator_file; + } + + dev_info(&client->dev, "LM3642 is initialized\n"); + return 0; + +err_create_indicator_file: + device_remove_file(chip->cdev_torch.dev, &dev_attr_torch_pin); +err_create_torch_pin_file: + led_classdev_unregister(&chip->cdev_torch); +err_create_torch_file: + device_remove_file(chip->cdev_flash.dev, &dev_attr_strobe_pin); +err_create_flash_pin_file: + led_classdev_unregister(&chip->cdev_flash); +err_out: + return err; +} + +static int lm3642_remove(struct i2c_client *client) +{ + struct lm3642_chip_data *chip = i2c_get_clientdata(client); + + led_classdev_unregister(&chip->cdev_indicator); + flush_work(&chip->work_indicator); + device_remove_file(chip->cdev_torch.dev, &dev_attr_torch_pin); + led_classdev_unregister(&chip->cdev_torch); + flush_work(&chip->work_torch); + device_remove_file(chip->cdev_flash.dev, &dev_attr_strobe_pin); + led_classdev_unregister(&chip->cdev_flash); + flush_work(&chip->work_flash); + regmap_write(chip->regmap, REG_ENABLE, 0); + return 0; +} + +static const struct i2c_device_id lm3642_id[] = { + {LM3642_NAME, 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, lm3642_id); + +static struct i2c_driver lm3642_i2c_driver = { + .driver = { + .name = LM3642_NAME, + .owner = THIS_MODULE, + .pm = NULL, + }, + .probe = lm3642_probe, + .remove = lm3642_remove, + .id_table = lm3642_id, +}; + +module_i2c_driver(lm3642_i2c_driver); + +MODULE_DESCRIPTION("Texas Instruments Flash Lighting driver for LM3642"); +MODULE_AUTHOR("Daniel Jeong <daniel.jeong@ti.com>"); +MODULE_AUTHOR("G.Shark Jeong <gshark.jeong@gmail.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index b8f9f0a5d43..8e1abdcd4c9 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -86,7 +86,7 @@ static int lp3944_reg_read(struct i2c_client *client, u8 reg, u8 *value) tmp = i2c_smbus_read_byte_data(client, reg); if (tmp < 0) - return -EINVAL; + return tmp; *value = tmp; @@ -289,7 +289,7 @@ static void lp3944_led_set_brightness(struct led_classdev *led_cdev, dev_dbg(&led->client->dev, "%s: %s, %d\n", __func__, led_cdev->name, brightness); - led->status = brightness; + led->status = !!brightness; schedule_work(&led->work); } @@ -374,10 +374,11 @@ exit: return err; } -static int __devinit lp3944_probe(struct i2c_client *client, +static int lp3944_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lp3944_platform_data *lp3944_pdata = client->dev.platform_data; + struct lp3944_platform_data *lp3944_pdata = + dev_get_platdata(&client->dev); struct lp3944_data *data; int err; @@ -393,7 +394,8 @@ static int __devinit lp3944_probe(struct i2c_client *client, return -ENODEV; } - data = kzalloc(sizeof(struct lp3944_data), GFP_KERNEL); + data = devm_kzalloc(&client->dev, sizeof(struct lp3944_data), + GFP_KERNEL); if (!data) return -ENOMEM; @@ -403,18 +405,16 @@ static int __devinit lp3944_probe(struct i2c_client *client, mutex_init(&data->lock); err = lp3944_configure(client, data, lp3944_pdata); - if (err < 0) { - kfree(data); + if (err < 0) return err; - } dev_info(&client->dev, "lp3944 enabled\n"); return 0; } -static int __devexit lp3944_remove(struct i2c_client *client) +static int lp3944_remove(struct i2c_client *client) { - struct lp3944_platform_data *pdata = client->dev.platform_data; + struct lp3944_platform_data *pdata = dev_get_platdata(&client->dev); struct lp3944_data *data = i2c_get_clientdata(client); int i; @@ -431,8 +431,6 @@ static int __devexit lp3944_remove(struct i2c_client *client) break; } - kfree(data); - return 0; } @@ -449,7 +447,7 @@ static struct i2c_driver lp3944_driver = { .name = "lp3944", }, .probe = lp3944_probe, - .remove = __devexit_p(lp3944_remove), + .remove = lp3944_remove, .id_table = lp3944_id, }; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index d62a7982a5e..8ca197af286 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -2,8 +2,10 @@ * LP5521 LED chip driver. * * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 Texas Instruments * * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * Milo(Woogyom) Kim <milo.kim@ti.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,33 +22,21 @@ * 02110-1301 USA */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/i2c.h> -#include <linux/mutex.h> -#include <linux/gpio.h> -#include <linux/interrupt.h> #include <linux/delay.h> -#include <linux/ctype.h> -#include <linux/spinlock.h> -#include <linux/wait.h> +#include <linux/firmware.h> +#include <linux/i2c.h> #include <linux/leds.h> -#include <linux/leds-lp5521.h> -#include <linux/workqueue.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_data/leds-lp55xx.h> #include <linux/slab.h> +#include <linux/of.h> -#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ - -#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */ -#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */ - -#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */ -#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */ +#include "leds-lp55xx-common.h" -#define LP5521_CMD_LOAD 0x15 /* 00010101 */ -#define LP5521_CMD_RUN 0x2a /* 00101010 */ -#define LP5521_CMD_DIRECT 0x3f /* 00111111 */ -#define LP5521_CMD_DISABLED 0x00 /* 00000000 */ +#define LP5521_PROGRAM_LENGTH 32 +#define LP5521_MAX_LEDS 3 +#define LP5521_CMD_DIRECT 0x3F /* Registers */ #define LP5521_REG_ENABLE 0x00 @@ -58,22 +48,14 @@ #define LP5521_REG_G_CURRENT 0x06 #define LP5521_REG_B_CURRENT 0x07 #define LP5521_REG_CONFIG 0x08 -#define LP5521_REG_R_CHANNEL_PC 0x09 -#define LP5521_REG_G_CHANNEL_PC 0x0A -#define LP5521_REG_B_CHANNEL_PC 0x0B #define LP5521_REG_STATUS 0x0C #define LP5521_REG_RESET 0x0D -#define LP5521_REG_GPO 0x0E #define LP5521_REG_R_PROG_MEM 0x10 #define LP5521_REG_G_PROG_MEM 0x30 #define LP5521_REG_B_PROG_MEM 0x50 -#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM -#define LP5521_PROG_MEM_SIZE 0x20 - /* Base register to set LED current */ #define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT - /* Base register to set the brightness */ #define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM @@ -81,18 +63,22 @@ #define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ #define LP5521_EXEC_RUN 0x2A +#define LP5521_ENABLE_DEFAULT \ + (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) +#define LP5521_ENABLE_RUN_PROGRAM \ + (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) -/* Bits in CONFIG register */ +/* CONFIG register */ #define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ #define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ #define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ #define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ #define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ #define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ +#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_INT 0x01 /* Internal clock */ +#define LP5521_DEFAULT_CFG \ + (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO) /* Status */ #define LP5521_EXT_CLK_USED 0x08 @@ -100,345 +86,312 @@ /* default R channel current register value */ #define LP5521_REG_R_CURR_DEFAULT 0xAF -struct lp5521_engine { - int id; - u8 mode; - u8 prog_page; - u8 engine_mask; -}; - -struct lp5521_led { - int id; - u8 chan_nr; - u8 led_current; - u8 max_current; - struct led_classdev cdev; - struct work_struct brightness_work; - u8 brightness; -}; - -struct lp5521_chip { - struct lp5521_platform_data *pdata; - struct mutex lock; /* Serialize control */ - struct i2c_client *client; - struct lp5521_engine engines[LP5521_MAX_ENGINES]; - struct lp5521_led leds[LP5521_MAX_LEDS]; - u8 num_channels; - u8 num_leds; -}; - -static inline struct lp5521_led *cdev_to_led(struct led_classdev *cdev) +/* Reset register value */ +#define LP5521_RESET 0xFF + +/* Program Memory Operations */ +#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */ +#define LP5521_MODE_G_M 0x0C +#define LP5521_MODE_B_M 0x03 +#define LP5521_LOAD_R 0x10 +#define LP5521_LOAD_G 0x04 +#define LP5521_LOAD_B 0x01 + +#define LP5521_R_IS_LOADING(mode) \ + ((mode & LP5521_MODE_R_M) == LP5521_LOAD_R) +#define LP5521_G_IS_LOADING(mode) \ + ((mode & LP5521_MODE_G_M) == LP5521_LOAD_G) +#define LP5521_B_IS_LOADING(mode) \ + ((mode & LP5521_MODE_B_M) == LP5521_LOAD_B) + +#define LP5521_EXEC_R_M 0x30 /* Enable Register */ +#define LP5521_EXEC_G_M 0x0C +#define LP5521_EXEC_B_M 0x03 +#define LP5521_EXEC_M 0x3F +#define LP5521_RUN_R 0x20 +#define LP5521_RUN_G 0x08 +#define LP5521_RUN_B 0x02 + +static inline void lp5521_wait_opmode_done(void) { - return container_of(cdev, struct lp5521_led, cdev); + /* operation mode change needs to be longer than 153 us */ + usleep_range(200, 300); } -static inline struct lp5521_chip *engine_to_lp5521(struct lp5521_engine *engine) +static inline void lp5521_wait_enable_done(void) { - return container_of(engine, struct lp5521_chip, - engines[engine->id - 1]); + /* it takes more 488 us to update ENABLE register */ + usleep_range(500, 600); } -static inline struct lp5521_chip *led_to_lp5521(struct lp5521_led *led) +static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current) { - return container_of(led, struct lp5521_chip, - leds[led->id]); + led->led_current = led_current; + lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); } -static void lp5521_led_brightness_work(struct work_struct *work); +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 inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +static void lp5521_stop_all_engines(struct lp55xx_chip *chip) { - return i2c_smbus_write_byte_data(client, reg, value); + lp55xx_write(chip, LP5521_REG_OP_MODE, 0); + lp5521_wait_opmode_done(); } -static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) +static void lp5521_stop_engine(struct lp55xx_chip *chip) { - s32 ret; + 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, + }; - ret = i2c_smbus_read_byte_data(client, reg); - if (ret < 0) - return -EIO; + lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0); - *buf = ret; - return 0; + lp5521_wait_opmode_done(); } -static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode) +static void lp5521_run_engine(struct lp55xx_chip *chip, bool start) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; int ret; - u8 engine_state; + 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; + } - /* Only transition between RUN and DIRECT mode are handled here */ - if (mode == LP5521_CMD_LOAD) - return 0; + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ - if (mode == LP5521_CMD_DISABLED) - mode = LP5521_CMD_DIRECT; + ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode); + if (ret) + return; - ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state); - if (ret < 0) - return ret; + ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec); + if (ret) + return; - /* set mode only for this engine */ - engine_state &= ~(engine->engine_mask); - mode &= engine->engine_mask; - engine_state |= mode; - return lp5521_write(client, LP5521_REG_OP_MODE, engine_state); -} + /* change operation mode to RUN only when each engine is loading */ + if (LP5521_R_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R; + exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R; + } -static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) -{ - struct lp5521_chip *chip = engine_to_lp5521(eng); - struct i2c_client *client = chip->client; - int ret; - int addr; - u8 mode; + if (LP5521_G_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G; + exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G; + } - /* move current engine to direct mode and remember the state */ - ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); - /* Mode change requires min 500 us delay. 1 - 2 ms with margin */ - usleep_range(1000, 2000); - ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode); - - /* For loading, all the engines to load mode */ - lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); - /* Mode change requires min 500 us delay. 1 - 2 ms with margin */ - usleep_range(1000, 2000); - lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); - /* Mode change requires min 500 us delay. 1 - 2 ms with margin */ - usleep_range(1000, 2000); - - addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE; - i2c_smbus_write_i2c_block_data(client, - addr, - LP5521_PROG_MEM_SIZE, - pattern); - - ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode); - return ret; -} + if (LP5521_B_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B; + exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B; + } -static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) -{ - return lp5521_write(chip->client, - LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, - curr); -} + lp55xx_write(chip, LP5521_REG_OP_MODE, mode); + lp5521_wait_opmode_done(); -static void lp5521_init_engine(struct lp5521_chip *chip) -{ - int i; - for (i = 0; i < ARRAY_SIZE(chip->engines); i++) { - chip->engines[i].id = i + 1; - chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2); - chip->engines[i].prog_page = i; - } + lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec); + lp5521_wait_enable_done(); } -static int lp5521_configure(struct i2c_client *client) +static int lp5521_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) { - struct lp5521_chip *chip = i2c_get_clientdata(client); + enum lp55xx_engine_index idx = chip->engine_idx; + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; + u8 addr[] = { + [LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM, + [LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM, + [LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM, + }; + unsigned cmd; + char c[3]; + int nrchars; int ret; + int offset = 0; + int i = 0; + + 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; - lp5521_init_engine(chip); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; - /* Set all PWMs to direct control mode */ - ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } - /* Enable auto-powersave, set charge pump to auto, red to battery */ - ret |= lp5521_write(client, LP5521_REG_CONFIG, - LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; - /* Initialize all channels PWM to zero -> leds off */ - ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); - ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); - ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) { + ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); + if (ret) + return -EINVAL; + } - /* Set engines are set to run state when OP_MODE enables engines */ - ret |= lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM | - LP5521_EXEC_RUN); - /* enable takes 500us. 1 - 2 ms leaves some margin */ - usleep_range(1000, 2000); + return size; - return ret; +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; } -static int lp5521_run_selftest(struct lp5521_chip *chip, char *buf) +static void lp5521_firmware_loaded(struct lp55xx_chip *chip) { - int ret; - u8 status; + const struct firmware *fw = chip->fw; - ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status); - if (ret < 0) - return ret; + if (fw->size > LP5521_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } - /* Check that ext clock is really in use if requested */ - if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT) - if ((status & LP5521_EXT_CLK_USED) == 0) - return -EIO; - return 0; -} + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ -static void lp5521_set_brightness(struct led_classdev *cdev, - enum led_brightness brightness) -{ - struct lp5521_led *led = cdev_to_led(cdev); - led->brightness = (u8)brightness; - schedule_work(&led->brightness_work); + lp5521_load_engine(chip); + lp5521_update_program_memory(chip, fw->data, fw->size); } -static void lp5521_led_brightness_work(struct work_struct *work) +static int lp5521_post_init_device(struct lp55xx_chip *chip) { - struct lp5521_led *led = container_of(work, - struct lp5521_led, - brightness_work); - struct lp5521_chip *chip = led_to_lp5521(led); - struct i2c_client *client = chip->client; + int ret; + u8 val; - mutex_lock(&chip->lock); - lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr, - led->brightness); - mutex_unlock(&chip->lock); -} + /* + * 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); -/* Detect the chip by setting its ENABLE register and reading it back. */ -static int lp5521_detect(struct i2c_client *client) -{ - int ret; - u8 buf; + /* 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 = lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM); + ret = lp55xx_write(chip, LP5521_REG_CONFIG, val); if (ret) return ret; - /* enable takes 500us. 1 - 2 ms leaves some margin */ - usleep_range(1000, 2000); - ret = lp5521_read(client, LP5521_REG_ENABLE, &buf); + + /* Initialize all channels PWM to zero -> leds off */ + lp55xx_write(chip, LP5521_REG_R_PWM, 0); + lp55xx_write(chip, LP5521_REG_G_PWM, 0); + lp55xx_write(chip, LP5521_REG_B_PWM, 0); + + /* Set engines are set to run state when OP_MODE enables engines */ + ret = lp55xx_write(chip, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); if (ret) return ret; - if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)) - return -ENODEV; + + lp5521_wait_enable_done(); return 0; } -/* Set engine mode and create appropriate sysfs attributes, if required. */ -static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode) +static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf) { - int ret = 0; + struct lp55xx_platform_data *pdata = chip->pdata; + int ret; + u8 status; - /* if in that mode already do nothing, except for run */ - if (mode == engine->mode && mode != LP5521_CMD_RUN) - return 0; + ret = lp55xx_read(chip, LP5521_REG_STATUS, &status); + if (ret < 0) + return ret; - if (mode == LP5521_CMD_RUN) { - ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN); - } else if (mode == LP5521_CMD_LOAD) { - lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); - lp5521_set_engine_mode(engine, LP5521_CMD_LOAD); - } else if (mode == LP5521_CMD_DISABLED) { - lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); - } + if (pdata->clock_mode != LP55XX_CLOCK_EXT) + return 0; - engine->mode = mode; + /* Check that ext clock is really in use if requested */ + if ((status & LP5521_EXT_CLK_USED) == 0) + return -EIO; - return ret; + return 0; } -static int lp5521_do_store_load(struct lp5521_engine *engine, - const char *buf, size_t len) +static void lp5521_led_brightness_work(struct work_struct *work) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; - int ret, nrchars, offset = 0, i = 0; - char c[3]; - unsigned cmd; - u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; - - while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); - if (ret != 2) - goto fail; - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto fail; - pattern[i] = (u8)cmd; - - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto fail; + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; mutex_lock(&chip->lock); - if (engine->mode == LP5521_CMD_LOAD) - ret = lp5521_load_program(engine, pattern); - else - ret = -EINVAL; + lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); mutex_unlock(&chip->lock); - - if (ret) { - dev_err(&client->dev, "failed loading pattern\n"); - return ret; - } - - return len; -fail: - dev_err(&client->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - return lp5521_do_store_load(&chip->engines[nr - 1], buf, len); -} - -#define store_load(nr) \ -static ssize_t store_engine##nr##_load(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_load(dev, attr, buf, len, nr); \ } -store_load(1) -store_load(2) -store_load(3) static ssize_t show_engine_mode(struct device *dev, struct device_attribute *attr, char *buf, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - switch (chip->engines[nr - 1].mode) { - case LP5521_CMD_RUN: + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; + + switch (mode) { + case LP55XX_ENGINE_RUN: return sprintf(buf, "run\n"); - case LP5521_CMD_LOAD: + case LP55XX_ENGINE_LOAD: return sprintf(buf, "load\n"); - case LP5521_CMD_DISABLED: - return sprintf(buf, "disabled\n"); + case LP55XX_ENGINE_DISABLED: default: return sprintf(buf, "disabled\n"); } } - -#define show_mode(nr) \ -static ssize_t show_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_engine_mode(dev, attr, buf, nr); \ -} show_mode(1) show_mode(2) show_mode(3) @@ -447,129 +400,88 @@ static ssize_t store_engine_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t len, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - struct lp5521_engine *engine = &chip->engines[nr - 1]; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + mutex_lock(&chip->lock); - if (!strncmp(buf, "run", 3)) - lp5521_set_mode(engine, LP5521_CMD_RUN); - else if (!strncmp(buf, "load", 4)) - lp5521_set_mode(engine, LP5521_CMD_LOAD); - else if (!strncmp(buf, "disabled", 8)) - lp5521_set_mode(engine, LP5521_CMD_DISABLED); + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + lp5521_run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp5521_stop_engine(chip); + lp5521_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp5521_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } mutex_unlock(&chip->lock); - return len; -} -#define store_mode(nr) \ -static ssize_t store_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_mode(dev, attr, buf, len, nr); \ + return len; } store_mode(1) store_mode(2) store_mode(3) -static ssize_t show_max_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->max_current); -} - -static ssize_t show_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->led_current); -} - -static ssize_t store_current(struct device *dev, +static ssize_t store_engine_load(struct device *dev, struct device_attribute *attr, - const char *buf, size_t len) + const char *buf, size_t len, int nr) { - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - struct lp5521_chip *chip = led_to_lp5521(led); - ssize_t ret; - unsigned long curr; - - if (strict_strtoul(buf, 0, &curr)) - return -EINVAL; - - if (curr > led->max_current) - return -EINVAL; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + int ret; mutex_lock(&chip->lock); - ret = lp5521_set_led_current(chip, led->id, curr); - mutex_unlock(&chip->lock); - if (ret < 0) - return ret; + chip->engine_idx = nr; + lp5521_load_engine(chip); + ret = lp5521_update_program_memory(chip, buf, len); - led->led_current = (u8)curr; + mutex_unlock(&chip->lock); - return len; + return ret; } +store_load(1) +store_load(2) +store_load(3) static ssize_t lp5521_selftest(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; int ret; mutex_lock(&chip->lock); ret = lp5521_run_selftest(chip, buf); mutex_unlock(&chip->lock); - return sprintf(buf, "%s\n", ret ? "FAIL" : "OK"); -} - -/* led class device attributes */ -static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current); -static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); - -static struct attribute *lp5521_led_attributes[] = { - &dev_attr_led_current.attr, - &dev_attr_max_current.attr, - NULL, -}; -static struct attribute_group lp5521_led_attribute_group = { - .attrs = lp5521_led_attributes -}; + return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK"); +} /* device attributes */ -static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUSR, - show_engine1_mode, store_engine1_mode); -static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUSR, - show_engine2_mode, store_engine2_mode); -static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUSR, - show_engine3_mode, store_engine3_mode); -static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load); -static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load); -static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load); -static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); +static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); +static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); +static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); +static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); +static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); +static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest); static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_mode.attr, &dev_attr_engine2_mode.attr, &dev_attr_engine3_mode.attr, - &dev_attr_selftest.attr, &dev_attr_engine1_load.attr, &dev_attr_engine2_load.attr, &dev_attr_engine3_load.attr, + &dev_attr_selftest.attr, NULL }; @@ -577,208 +489,99 @@ static const struct attribute_group lp5521_group = { .attrs = lp5521_attributes, }; -static int lp5521_register_sysfs(struct i2c_client *client) -{ - struct device *dev = &client->dev; - return sysfs_create_group(&dev->kobj, &lp5521_group); -} - -static void lp5521_unregister_sysfs(struct i2c_client *client) -{ - struct lp5521_chip *chip = i2c_get_clientdata(client); - struct device *dev = &client->dev; - int i; - - sysfs_remove_group(&dev->kobj, &lp5521_group); - - for (i = 0; i < chip->num_leds; i++) - sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, - &lp5521_led_attribute_group); -} - -static int __devinit lp5521_init_led(struct lp5521_led *led, - struct i2c_client *client, - int chan, struct lp5521_platform_data *pdata) -{ - struct device *dev = &client->dev; - char name[32]; - int res; - - if (chan >= LP5521_MAX_LEDS) - return -EINVAL; - - if (pdata->led_config[chan].led_current == 0) - return 0; - - led->led_current = pdata->led_config[chan].led_current; - led->max_current = pdata->led_config[chan].max_current; - led->chan_nr = pdata->led_config[chan].chan_nr; - - if (led->chan_nr >= LP5521_MAX_LEDS) { - dev_err(dev, "Use channel numbers between 0 and %d\n", - LP5521_MAX_LEDS - 1); - return -EINVAL; - } - - snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ?: client->name, chan); - led->cdev.brightness_set = lp5521_set_brightness; - led->cdev.name = name; - res = led_classdev_register(dev, &led->cdev); - if (res < 0) { - dev_err(dev, "couldn't register led on channel %d\n", chan); - return res; - } - - res = sysfs_create_group(&led->cdev.dev->kobj, - &lp5521_led_attribute_group); - if (res < 0) { - dev_err(dev, "couldn't register current attribute\n"); - led_classdev_unregister(&led->cdev); - return res; - } - return 0; -} +/* 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 __devinit lp5521_probe(struct i2c_client *client, +static int lp5521_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lp5521_chip *chip; - struct lp5521_platform_data *pdata; - int ret, i, led; - u8 buf; + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata; + struct device_node *np = client->dev.of_node; + + if (!dev_get_platdata(&client->dev)) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = dev_get_platdata(&client->dev); - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - i2c_set_clientdata(client, chip); - chip->client = client; - - pdata = client->dev.platform_data; + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; - if (!pdata) { - dev_err(&client->dev, "no platform data\n"); - ret = -EINVAL; - goto fail1; - } + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp5521_cfg; mutex_init(&chip->lock); - chip->pdata = pdata; - - if (pdata->setup_resources) { - ret = pdata->setup_resources(); - if (ret < 0) - goto fail1; - } - - if (pdata->enable) { - pdata->enable(0); - usleep_range(1000, 2000); /* Keep enable down at least 1ms */ - pdata->enable(1); - usleep_range(1000, 2000); /* 500us abs min. */ - } + i2c_set_clientdata(client, led); - lp5521_write(client, LP5521_REG_RESET, 0xff); - usleep_range(10000, 20000); /* - * Exact value is not available. 10 - 20ms - * appears to be enough for reset. - */ - - /* - * 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! - */ - lp5521_read(client, LP5521_REG_R_CURRENT, &buf); - if (buf != LP5521_REG_R_CURR_DEFAULT) { - dev_err(&client->dev, "error in reseting chip\n"); - goto fail2; - } - usleep_range(10000, 20000); - - ret = lp5521_detect(client); - - if (ret) { - dev_err(&client->dev, "Chip not found\n"); - goto fail2; - } + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; dev_info(&client->dev, "%s programmable led chip found\n", id->name); - ret = lp5521_configure(client); - if (ret < 0) { - dev_err(&client->dev, "error configuring chip\n"); - goto fail2; - } - - /* Initialize leds */ - chip->num_channels = pdata->num_channels; - chip->num_leds = 0; - led = 0; - for (i = 0; i < pdata->num_channels; i++) { - /* Do not initialize channels that are not connected */ - if (pdata->led_config[i].led_current == 0) - continue; - - ret = lp5521_init_led(&chip->leds[led], client, i, pdata); - if (ret) { - dev_err(&client->dev, "error initializing leds\n"); - goto fail3; - } - chip->num_leds++; - - chip->leds[led].id = led; - /* Set initial LED current */ - lp5521_set_led_current(chip, led, - chip->leds[led].led_current); - - INIT_WORK(&(chip->leds[led].brightness_work), - lp5521_led_brightness_work); - - led++; - } + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; - ret = lp5521_register_sysfs(client); + ret = lp55xx_register_sysfs(chip); if (ret) { dev_err(&client->dev, "registering sysfs failed\n"); - goto fail3; - } - return ret; -fail3: - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); + goto err_register_sysfs; } -fail2: - if (pdata->enable) - pdata->enable(0); - if (pdata->release_resources) - pdata->release_resources(); -fail1: - kfree(chip); + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: return ret; } -static int __devexit lp5521_remove(struct i2c_client *client) +static int lp5521_remove(struct i2c_client *client) { - struct lp5521_chip *chip = i2c_get_clientdata(client); - int i; + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; - lp5521_unregister_sysfs(client); + lp5521_stop_all_engines(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); - } - - if (chip->pdata->enable) - chip->pdata->enable(0); - if (chip->pdata->release_resources) - chip->pdata->release_resources(); - kfree(chip); return 0; } @@ -788,17 +591,27 @@ static const struct i2c_device_id lp5521_id[] = { }; MODULE_DEVICE_TABLE(i2c, lp5521_id); +#ifdef CONFIG_OF +static const struct of_device_id of_lp5521_leds_match[] = { + { .compatible = "national,lp5521", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5521_leds_match); +#endif static struct i2c_driver lp5521_driver = { .driver = { .name = "lp5521", + .of_match_table = of_match_ptr(of_lp5521_leds_match), }, .probe = lp5521_probe, - .remove = __devexit_p(lp5521_remove), + .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"); diff --git a/drivers/leds/leds-lp5523.c b/drivers/leds/leds-lp5523.c index 73e791ae725..9e1716f8098 100644 --- a/drivers/leds/leds-lp5523.c +++ b/drivers/leds/leds-lp5523.c @@ -1,9 +1,11 @@ /* - * lp5523.c - LP5523 LED Driver + * lp5523.c - LP5523, LP55231 LED Driver * * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 Texas Instruments * * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * Milo(Woogyom) Kim <milo.kim@ti.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,325 +22,436 @@ * 02110-1301 USA */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/i2c.h> -#include <linux/mutex.h> -#include <linux/gpio.h> -#include <linux/interrupt.h> #include <linux/delay.h> -#include <linux/ctype.h> -#include <linux/spinlock.h> -#include <linux/wait.h> +#include <linux/firmware.h> +#include <linux/i2c.h> #include <linux/leds.h> -#include <linux/leds-lp5523.h> -#include <linux/workqueue.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_data/leds-lp55xx.h> #include <linux/slab.h> +#include "leds-lp55xx-common.h" + +#define LP5523_PROGRAM_LENGTH 32 /* bytes */ +/* Memory is used like this: + 0x00 engine 1 program + 0x10 engine 2 program + 0x20 engine 3 program + 0x30 engine 1 muxing info + 0x40 engine 2 muxing info + 0x50 engine 3 muxing info +*/ +#define LP5523_MAX_LEDS 9 + +/* Registers */ #define LP5523_REG_ENABLE 0x00 #define LP5523_REG_OP_MODE 0x01 -#define LP5523_REG_RATIOMETRIC_MSB 0x02 -#define LP5523_REG_RATIOMETRIC_LSB 0x03 #define LP5523_REG_ENABLE_LEDS_MSB 0x04 #define LP5523_REG_ENABLE_LEDS_LSB 0x05 -#define LP5523_REG_LED_CNTRL_BASE 0x06 #define LP5523_REG_LED_PWM_BASE 0x16 #define LP5523_REG_LED_CURRENT_BASE 0x26 #define LP5523_REG_CONFIG 0x36 -#define LP5523_REG_CHANNEL1_PC 0x37 -#define LP5523_REG_CHANNEL2_PC 0x38 -#define LP5523_REG_CHANNEL3_PC 0x39 -#define LP5523_REG_STATUS 0x3a -#define LP5523_REG_GPO 0x3b -#define LP5523_REG_VARIABLE 0x3c -#define LP5523_REG_RESET 0x3d -#define LP5523_REG_TEMP_CTRL 0x3e -#define LP5523_REG_TEMP_READ 0x3f -#define LP5523_REG_TEMP_WRITE 0x40 +#define LP5523_REG_STATUS 0x3A +#define LP5523_REG_RESET 0x3D #define LP5523_REG_LED_TEST_CTRL 0x41 #define LP5523_REG_LED_TEST_ADC 0x42 -#define LP5523_REG_ENG1_VARIABLE 0x45 -#define LP5523_REG_ENG2_VARIABLE 0x46 -#define LP5523_REG_ENG3_VARIABLE 0x47 -#define LP5523_REG_MASTER_FADER1 0x48 -#define LP5523_REG_MASTER_FADER2 0x49 -#define LP5523_REG_MASTER_FADER3 0x4a -#define LP5523_REG_CH1_PROG_START 0x4c -#define LP5523_REG_CH2_PROG_START 0x4d -#define LP5523_REG_CH3_PROG_START 0x4e -#define LP5523_REG_PROG_PAGE_SEL 0x4f +#define LP5523_REG_CH1_PROG_START 0x4C +#define LP5523_REG_CH2_PROG_START 0x4D +#define LP5523_REG_CH3_PROG_START 0x4E +#define LP5523_REG_PROG_PAGE_SEL 0x4F #define LP5523_REG_PROG_MEM 0x50 -#define LP5523_CMD_LOAD 0x15 /* 00010101 */ -#define LP5523_CMD_RUN 0x2a /* 00101010 */ -#define LP5523_CMD_DISABLED 0x00 /* 00000000 */ - +/* Bit description in registers */ #define LP5523_ENABLE 0x40 #define LP5523_AUTO_INC 0x40 #define LP5523_PWR_SAVE 0x20 #define LP5523_PWM_PWR_SAVE 0x04 -#define LP5523_CP_1 0x08 -#define LP5523_CP_1_5 0x10 #define LP5523_CP_AUTO 0x18 -#define LP5523_INT_CLK 0x01 #define LP5523_AUTO_CLK 0x02 + #define LP5523_EN_LEDTEST 0x80 #define LP5523_LEDTEST_DONE 0x80 - -#define LP5523_DEFAULT_CURRENT 50 /* microAmps */ -#define LP5523_PROGRAM_LENGTH 32 /* in bytes */ -#define LP5523_PROGRAM_PAGES 6 +#define LP5523_RESET 0xFF #define LP5523_ADC_SHORTCIRC_LIM 80 - -#define LP5523_LEDS 9 -#define LP5523_ENGINES 3 - -#define LP5523_ENG_MASK_BASE 0x30 /* 00110000 */ - -#define LP5523_ENG_STATUS_MASK 0x07 /* 00000111 */ - -#define LP5523_IRQ_FLAGS IRQF_TRIGGER_FALLING - #define LP5523_EXT_CLK_USED 0x08 +#define LP5523_ENG_STATUS_MASK 0x07 + +/* Memory Page Selection */ +#define LP5523_PAGE_ENG1 0 +#define LP5523_PAGE_ENG2 1 +#define LP5523_PAGE_ENG3 2 +#define LP5523_PAGE_MUX1 3 +#define LP5523_PAGE_MUX2 4 +#define LP5523_PAGE_MUX3 5 + +/* Program Memory Operations */ +#define LP5523_MODE_ENG1_M 0x30 /* Operation Mode Register */ +#define LP5523_MODE_ENG2_M 0x0C +#define LP5523_MODE_ENG3_M 0x03 +#define LP5523_LOAD_ENG1 0x10 +#define LP5523_LOAD_ENG2 0x04 +#define LP5523_LOAD_ENG3 0x01 + +#define LP5523_ENG1_IS_LOADING(mode) \ + ((mode & LP5523_MODE_ENG1_M) == LP5523_LOAD_ENG1) +#define LP5523_ENG2_IS_LOADING(mode) \ + ((mode & LP5523_MODE_ENG2_M) == LP5523_LOAD_ENG2) +#define LP5523_ENG3_IS_LOADING(mode) \ + ((mode & LP5523_MODE_ENG3_M) == LP5523_LOAD_ENG3) + +#define LP5523_EXEC_ENG1_M 0x30 /* Enable Register */ +#define LP5523_EXEC_ENG2_M 0x0C +#define LP5523_EXEC_ENG3_M 0x03 +#define LP5523_EXEC_M 0x3F +#define LP5523_RUN_ENG1 0x20 +#define LP5523_RUN_ENG2 0x08 +#define LP5523_RUN_ENG3 0x02 #define LED_ACTIVE(mux, led) (!!(mux & (0x0001 << led))) -#define SHIFT_MASK(id) (((id) - 1) * 2) - -struct lp5523_engine { - int id; - u8 mode; - u8 prog_page; - u8 mux_page; - u16 led_mux; - u8 engine_mask; -}; -struct lp5523_led { - int id; - u8 chan_nr; - u8 led_current; - u8 max_current; - struct led_classdev cdev; - struct work_struct brightness_work; - u8 brightness; +enum lp5523_chip_id { + LP5523, + LP55231, }; -struct lp5523_chip { - struct mutex lock; /* Serialize control */ - struct i2c_client *client; - struct lp5523_engine engines[LP5523_ENGINES]; - struct lp5523_led leds[LP5523_LEDS]; - struct lp5523_platform_data *pdata; - u8 num_channels; - u8 num_leds; -}; +static int lp5523_init_program_engine(struct lp55xx_chip *chip); -static inline struct lp5523_led *cdev_to_led(struct led_classdev *cdev) +static inline void lp5523_wait_opmode_done(void) { - return container_of(cdev, struct lp5523_led, cdev); + usleep_range(1000, 2000); } -static inline struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine) +static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current) { - return container_of(engine, struct lp5523_chip, - engines[engine->id - 1]); + led->led_current = led_current; + lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); } -static inline struct lp5523_chip *led_to_lp5523(struct lp5523_led *led) +static int lp5523_post_init_device(struct lp55xx_chip *chip) { - return container_of(led, struct lp5523_chip, - leds[led->id]); + 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 int lp5523_set_mode(struct lp5523_engine *engine, u8 mode); -static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode); -static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern); +static void lp5523_load_engine(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, + }; -static void lp5523_led_brightness_work(struct work_struct *work); + u8 val[] = { + [LP55XX_ENGINE_1] = LP5523_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP5523_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3, + }; + + lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]); + + lp5523_wait_opmode_done(); +} -static int lp5523_write(struct i2c_client *client, u8 reg, u8 value) +static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip) { - return i2c_smbus_write_byte_data(client, reg, value); + 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 int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf) +static void lp5523_stop_all_engines(struct lp55xx_chip *chip) { - s32 ret = i2c_smbus_read_byte_data(client, reg); + lp55xx_write(chip, LP5523_REG_OP_MODE, 0); + lp5523_wait_opmode_done(); +} - if (ret < 0) - return -EIO; +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, + }; - *buf = ret; - return 0; + lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0); + + lp5523_wait_opmode_done(); } -static int lp5523_detect(struct i2c_client *client) +static void lp5523_turn_off_channels(struct lp55xx_chip *chip) +{ + int i; + + for (i = 0; i < LP5523_MAX_LEDS; i++) + lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0); +} + +static void lp5523_run_engine(struct lp55xx_chip *chip, bool start) { int ret; - u8 buf; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp5523_stop_engine(chip); + lp5523_turn_off_channels(chip); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ - ret = lp5523_write(client, LP5523_REG_ENABLE, 0x40); + ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode); if (ret) - return ret; - ret = lp5523_read(client, LP5523_REG_ENABLE, &buf); + return; + + ret = lp55xx_read(chip, LP5523_REG_ENABLE, &exec); if (ret) - return ret; - if (buf == 0x40) - return 0; - else - return -ENODEV; + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP5523_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP5523_MODE_ENG1_M) | LP5523_RUN_ENG1; + exec = (exec & ~LP5523_EXEC_ENG1_M) | LP5523_RUN_ENG1; + } + + if (LP5523_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP5523_MODE_ENG2_M) | LP5523_RUN_ENG2; + exec = (exec & ~LP5523_EXEC_ENG2_M) | LP5523_RUN_ENG2; + } + + if (LP5523_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP5523_MODE_ENG3_M) | LP5523_RUN_ENG3; + exec = (exec & ~LP5523_EXEC_ENG3_M) | LP5523_RUN_ENG3; + } + + lp55xx_write(chip, LP5523_REG_OP_MODE, mode); + lp5523_wait_opmode_done(); + + lp55xx_update_bits(chip, LP5523_REG_ENABLE, LP5523_EXEC_M, exec); } -static int lp5523_configure(struct i2c_client *client) +static int lp5523_init_program_engine(struct lp55xx_chip *chip) { - struct lp5523_chip *chip = i2c_get_clientdata(client); - int ret = 0; + int i; + int j; + int ret; u8 status; - - /* one pattern per engine setting led mux start and stop addresses */ - u8 pattern[][LP5523_PROGRAM_LENGTH] = { + /* one pattern per engine setting LED MUX start and stop addresses */ + static const u8 pattern[][LP5523_PROGRAM_LENGTH] = { { 0x9c, 0x30, 0x9c, 0xb0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x40, 0x9c, 0xc0, 0x9d, 0x80, 0xd8, 0x00, 0}, { 0x9c, 0x50, 0x9c, 0xd0, 0x9d, 0x80, 0xd8, 0x00, 0}, }; - ret |= lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE); - /* Chip startup time is 500 us, 1 - 2 ms gives some margin */ - usleep_range(1000, 2000); - - ret |= lp5523_write(client, LP5523_REG_CONFIG, - LP5523_AUTO_INC | LP5523_PWR_SAVE | - LP5523_CP_AUTO | LP5523_AUTO_CLK | - LP5523_PWM_PWR_SAVE); - - /* turn on all leds */ - ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_MSB, 0x01); - ret |= lp5523_write(client, LP5523_REG_ENABLE_LEDS_LSB, 0xff); - /* hardcode 32 bytes of memory for each engine from program memory */ - ret |= lp5523_write(client, LP5523_REG_CH1_PROG_START, 0x00); - ret |= lp5523_write(client, LP5523_REG_CH2_PROG_START, 0x10); - ret |= lp5523_write(client, LP5523_REG_CH3_PROG_START, 0x20); - - /* write led mux address space for each channel */ - ret |= lp5523_load_program(&chip->engines[0], pattern[0]); - ret |= lp5523_load_program(&chip->engines[1], pattern[1]); - ret |= lp5523_load_program(&chip->engines[2], pattern[2]); + ret = lp55xx_write(chip, LP5523_REG_CH1_PROG_START, 0x00); + if (ret) + return ret; - if (ret) { - dev_err(&client->dev, "could not load mux programs\n"); - return -1; - } + ret = lp55xx_write(chip, LP5523_REG_CH2_PROG_START, 0x10); + if (ret) + return ret; - /* set all engines exec state and mode to run 00101010 */ - ret |= lp5523_write(client, LP5523_REG_ENABLE, - (LP5523_CMD_RUN | LP5523_ENABLE)); + ret = lp55xx_write(chip, LP5523_REG_CH3_PROG_START, 0x20); + if (ret) + return ret; - ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_RUN); + /* write LED MUX address space for each engine */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + chip->engine_idx = i; + lp5523_load_engine_and_select_page(chip); - if (ret) { - dev_err(&client->dev, "could not start mux programs\n"); - return -1; + for (j = 0; j < LP5523_PROGRAM_LENGTH; j++) { + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + j, + pattern[i - 1][j]); + if (ret) + goto out; + } } + lp5523_run_engine(chip, true); + /* Let the programs run for couple of ms and check the engine status */ usleep_range(3000, 6000); - lp5523_read(client, LP5523_REG_STATUS, &status); + lp55xx_read(chip, LP5523_REG_STATUS, &status); status &= LP5523_ENG_STATUS_MASK; - if (status == LP5523_ENG_STATUS_MASK) { - dev_dbg(&client->dev, "all engines configured\n"); - } else { - dev_info(&client->dev, "status == %x\n", status); - dev_err(&client->dev, "cound not configure LED engine\n"); - return -1; + if (status != LP5523_ENG_STATUS_MASK) { + dev_err(&chip->cl->dev, + "cound not configure LED engine, status = 0x%.2x\n", + status); + ret = -1; } - dev_info(&client->dev, "disabling engines\n"); - - ret |= lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED); - +out: + lp5523_stop_all_engines(chip); return ret; } -static int lp5523_set_engine_mode(struct lp5523_engine *engine, u8 mode) +static int lp5523_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) { - struct lp5523_chip *chip = engine_to_lp5523(engine); - struct i2c_client *client = chip->client; + u8 pattern[LP5523_PROGRAM_LENGTH] = {0}; + unsigned cmd; + char c[3]; + int nrchars; int ret; - u8 engine_state; + int offset = 0; + int i = 0; - ret = lp5523_read(client, LP5523_REG_OP_MODE, &engine_state); - if (ret) - goto fail; + while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; - engine_state &= ~(engine->engine_mask); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; - /* set mode only for this engine */ - mode &= engine->engine_mask; + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } - engine_state |= mode; + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; - ret |= lp5523_write(client, LP5523_REG_OP_MODE, engine_state); -fail: - return ret; + for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) { + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]); + if (ret) + return -EINVAL; + } + + return size; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; } -static int lp5523_load_mux(struct lp5523_engine *engine, u16 mux) +static void lp5523_firmware_loaded(struct lp55xx_chip *chip) { - struct lp5523_chip *chip = engine_to_lp5523(engine); - struct i2c_client *client = chip->client; - int ret = 0; + const struct firmware *fw = chip->fw; - ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); + if (fw->size > LP5523_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } - ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, engine->mux_page); - ret |= lp5523_write(client, LP5523_REG_PROG_MEM, - (u8)(mux >> 8)); - ret |= lp5523_write(client, LP5523_REG_PROG_MEM + 1, (u8)(mux)); - engine->led_mux = mux; + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ - return ret; + lp5523_load_engine_and_select_page(chip); + lp5523_update_program_memory(chip, fw->data, fw->size); } -static int lp5523_load_program(struct lp5523_engine *engine, u8 *pattern) +static ssize_t show_engine_mode(struct device *dev, + struct device_attribute *attr, + char *buf, int nr) { - struct lp5523_chip *chip = engine_to_lp5523(engine); - struct i2c_client *client = chip->client; - - int ret = 0; - - ret |= lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); - - ret |= lp5523_write(client, LP5523_REG_PROG_PAGE_SEL, - engine->prog_page); - ret |= i2c_smbus_write_i2c_block_data(client, LP5523_REG_PROG_MEM, - LP5523_PROGRAM_LENGTH, pattern); + 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; - return ret; + switch (mode) { + case LP55XX_ENGINE_RUN: + return sprintf(buf, "run\n"); + case LP55XX_ENGINE_LOAD: + return sprintf(buf, "load\n"); + case LP55XX_ENGINE_DISABLED: + default: + return sprintf(buf, "disabled\n"); + } } +show_mode(1) +show_mode(2) +show_mode(3) -static int lp5523_run_program(struct lp5523_engine *engine) +static ssize_t store_engine_mode(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) { - struct lp5523_chip *chip = engine_to_lp5523(engine); - struct i2c_client *client = chip->client; - int ret; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; - ret = lp5523_write(client, LP5523_REG_ENABLE, - LP5523_CMD_RUN | LP5523_ENABLE); - if (ret) - goto fail; + mutex_lock(&chip->lock); - ret = lp5523_set_engine_mode(engine, LP5523_CMD_RUN); -fail: - return ret; + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + lp5523_run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp5523_stop_engine(chip); + lp5523_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp5523_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } + + mutex_unlock(&chip->lock); + + return len; } +store_mode(1) +store_mode(2) +store_mode(3) static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len) { - int i; u16 tmp_mux = 0; - len = len < LP5523_LEDS ? len : LP5523_LEDS; + int i; + + len = min_t(int, len, LP5523_MAX_LEDS); + for (i = 0; i < len; i++) { switch (buf[i]) { case '1': @@ -361,46 +474,63 @@ static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len) static void lp5523_mux_to_array(u16 led_mux, char *array) { int i, pos = 0; - for (i = 0; i < LP5523_LEDS; i++) + for (i = 0; i < LP5523_MAX_LEDS; i++) pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i)); array[pos] = '\0'; } -/*--------------------------------------------------------------*/ -/* Sysfs interface */ -/*--------------------------------------------------------------*/ - static ssize_t show_engine_leds(struct device *dev, struct device_attribute *attr, char *buf, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - char mux[LP5523_LEDS + 1]; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + char mux[LP5523_MAX_LEDS + 1]; lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux); return sprintf(buf, "%s\n", mux); } - -#define show_leds(nr) \ -static ssize_t show_engine##nr##_leds(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_engine_leds(dev, attr, buf, nr); \ -} show_leds(1) show_leds(2) show_leds(3) +static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr) +{ + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + int ret; + u8 mux_page[] = { + [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1, + [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2, + [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3, + }; + + lp5523_load_engine(chip); + + ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8)); + if (ret) + return ret; + + ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux)); + if (ret) + return ret; + + engine->led_mux = mux; + return 0; +} + static ssize_t store_engine_leds(struct device *dev, struct device_attribute *attr, const char *buf, size_t len, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; u16 mux = 0; ssize_t ret; @@ -408,11 +538,14 @@ static ssize_t store_engine_leds(struct device *dev, return -EINVAL; mutex_lock(&chip->lock); + + chip->engine_idx = nr; ret = -EINVAL; - if (chip->engines[nr - 1].mode != LP5523_CMD_LOAD) + + if (engine->mode != LP55XX_ENGINE_LOAD) goto leave; - if (lp5523_load_mux(&chip->engines[nr - 1], mux)) + if (lp5523_load_mux(chip, mux, nr)) goto leave; ret = len; @@ -420,82 +553,105 @@ leave: mutex_unlock(&chip->lock); return ret; } - -#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); \ -} store_leds(1) store_leds(2) store_leds(3) +static ssize_t store_engine_load(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len, int nr) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + int ret; + + mutex_lock(&chip->lock); + + chip->engine_idx = nr; + lp5523_load_engine_and_select_page(chip); + ret = lp5523_update_program_memory(chip, buf, len); + + mutex_unlock(&chip->lock); + + return ret; +} +store_load(1) +store_load(2) +store_load(3) + static ssize_t lp5523_selftest(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_platform_data *pdata = chip->pdata; int i, ret, pos = 0; - int led = 0; u8 status, adc, vdd; mutex_lock(&chip->lock); - ret = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + ret = lp55xx_read(chip, LP5523_REG_STATUS, &status); if (ret < 0) goto fail; /* Check that ext clock is really in use if requested */ - if ((chip->pdata) && (chip->pdata->clock_mode == LP5523_CLOCK_EXT)) + if (pdata->clock_mode == LP55XX_CLOCK_EXT) { if ((status & LP5523_EXT_CLK_USED) == 0) goto fail; + } /* Measure VDD (i.e. VBAT) first (channel 16 corresponds to VDD) */ - lp5523_write(chip->client, LP5523_REG_LED_TEST_CTRL, - LP5523_EN_LEDTEST | 16); + 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 = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + 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 |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &vdd); + 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_LEDS; i++) { + for (i = 0; i < LP5523_MAX_LEDS; i++) { /* Skip non-existing channels */ - if (chip->pdata->led_config[i].led_current == 0) + if (pdata->led_config[i].led_current == 0) continue; /* Set default current */ - lp5523_write(chip->client, - LP5523_REG_LED_CURRENT_BASE + i, - chip->pdata->led_config[i].led_current); + lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i, + pdata->led_config[i].led_current); - lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0xff); + lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0xff); /* let current stabilize 2 - 4ms before measurements start */ usleep_range(2000, 4000); - lp5523_write(chip->client, - LP5523_REG_LED_TEST_CTRL, + 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 = lp5523_read(chip->client, LP5523_REG_STATUS, &status); + 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 |= lp5523_read(chip->client, LP5523_REG_LED_TEST_ADC, &adc); + + ret = lp55xx_read(chip, LP5523_REG_LED_TEST_ADC, &adc); + if (ret < 0) + goto fail; if (adc >= vdd || adc < LP5523_ADC_SHORTCIRC_LIM) pos += sprintf(buf + pos, "LED %d FAIL\n", i); - lp5523_write(chip->client, LP5523_REG_LED_PWM_BASE + i, 0x00); + lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0x00); /* Restore current */ - lp5523_write(chip->client, - LP5523_REG_LED_CURRENT_BASE + i, - chip->leds[led].led_current); + lp55xx_write(chip, LP5523_REG_LED_CURRENT_BASE + i, + led->led_current); led++; } if (pos == 0) @@ -510,509 +666,165 @@ release_lock: return pos; } -static void lp5523_set_brightness(struct led_classdev *cdev, - enum led_brightness brightness) -{ - struct lp5523_led *led = cdev_to_led(cdev); - - led->brightness = (u8)brightness; - - schedule_work(&led->brightness_work); -} - static void lp5523_led_brightness_work(struct work_struct *work) { - struct lp5523_led *led = container_of(work, - struct lp5523_led, + struct lp55xx_led *led = container_of(work, struct lp55xx_led, brightness_work); - struct lp5523_chip *chip = led_to_lp5523(led); - struct i2c_client *client = chip->client; + struct lp55xx_chip *chip = led->chip; mutex_lock(&chip->lock); - - lp5523_write(client, LP5523_REG_LED_PWM_BASE + led->chan_nr, + lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + led->chan_nr, led->brightness); - - mutex_unlock(&chip->lock); -} - -static int lp5523_do_store_load(struct lp5523_engine *engine, - const char *buf, size_t len) -{ - struct lp5523_chip *chip = engine_to_lp5523(engine); - struct i2c_client *client = chip->client; - int ret, nrchars, offset = 0, i = 0; - char c[3]; - unsigned cmd; - u8 pattern[LP5523_PROGRAM_LENGTH] = {0}; - - while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto fail; - pattern[i] = (u8)cmd; - - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto fail; - - mutex_lock(&chip->lock); - - if (engine->mode == LP5523_CMD_LOAD) - ret = lp5523_load_program(engine, pattern); - else - ret = -EINVAL; - - mutex_unlock(&chip->lock); - - if (ret) { - dev_err(&client->dev, "failed loading pattern\n"); - return ret; - } - - return len; -fail: - dev_err(&client->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - return lp5523_do_store_load(&chip->engines[nr - 1], buf, len); -} - -#define store_load(nr) \ -static ssize_t store_engine##nr##_load(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_load(dev, attr, buf, len, nr); \ -} -store_load(1) -store_load(2) -store_load(3) - -static ssize_t show_engine_mode(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - switch (chip->engines[nr - 1].mode) { - case LP5523_CMD_RUN: - return sprintf(buf, "run\n"); - case LP5523_CMD_LOAD: - return sprintf(buf, "load\n"); - case LP5523_CMD_DISABLED: - return sprintf(buf, "disabled\n"); - default: - return sprintf(buf, "disabled\n"); - } -} - -#define show_mode(nr) \ -static ssize_t show_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_engine_mode(dev, attr, buf, nr); \ -} -show_mode(1) -show_mode(2) -show_mode(3) - -static ssize_t store_engine_mode(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - struct lp5523_engine *engine = &chip->engines[nr - 1]; - mutex_lock(&chip->lock); - - if (!strncmp(buf, "run", 3)) - lp5523_set_mode(engine, LP5523_CMD_RUN); - else if (!strncmp(buf, "load", 4)) - lp5523_set_mode(engine, LP5523_CMD_LOAD); - else if (!strncmp(buf, "disabled", 8)) - lp5523_set_mode(engine, LP5523_CMD_DISABLED); - - mutex_unlock(&chip->lock); - return len; -} - -#define store_mode(nr) \ -static ssize_t store_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_mode(dev, attr, buf, len, nr); \ -} -store_mode(1) -store_mode(2) -store_mode(3) - -static ssize_t show_max_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->max_current); -} - -static ssize_t show_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->led_current); -} - -static ssize_t store_current(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - struct lp5523_chip *chip = led_to_lp5523(led); - ssize_t ret; - unsigned long curr; - - if (strict_strtoul(buf, 0, &curr)) - return -EINVAL; - - if (curr > led->max_current) - return -EINVAL; - - mutex_lock(&chip->lock); - ret = lp5523_write(chip->client, - LP5523_REG_LED_CURRENT_BASE + led->chan_nr, - (u8)curr); mutex_unlock(&chip->lock); - - if (ret < 0) - return ret; - - led->led_current = (u8)curr; - - return len; } -/* led class device attributes */ -static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, show_current, store_current); -static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); - -static struct attribute *lp5523_led_attributes[] = { - &dev_attr_led_current.attr, - &dev_attr_max_current.attr, - NULL, -}; - -static struct attribute_group lp5523_led_attribute_group = { - .attrs = lp5523_led_attributes -}; - -/* device attributes */ -static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUSR, - show_engine1_mode, store_engine1_mode); -static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUSR, - show_engine2_mode, store_engine2_mode); -static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUSR, - show_engine3_mode, store_engine3_mode); -static DEVICE_ATTR(engine1_leds, S_IRUGO | S_IWUSR, - show_engine1_leds, store_engine1_leds); -static DEVICE_ATTR(engine2_leds, S_IRUGO | S_IWUSR, - show_engine2_leds, store_engine2_leds); -static DEVICE_ATTR(engine3_leds, S_IRUGO | S_IWUSR, - show_engine3_leds, store_engine3_leds); -static DEVICE_ATTR(engine1_load, S_IWUSR, NULL, store_engine1_load); -static DEVICE_ATTR(engine2_load, S_IWUSR, NULL, store_engine2_load); -static DEVICE_ATTR(engine3_load, S_IWUSR, NULL, store_engine3_load); -static DEVICE_ATTR(selftest, S_IRUGO, lp5523_selftest, NULL); +static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); +static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); +static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); +static LP55XX_DEV_ATTR_RW(engine1_leds, show_engine1_leds, store_engine1_leds); +static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds); +static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds); +static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); +static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); +static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +static LP55XX_DEV_ATTR_RO(selftest, lp5523_selftest); static struct attribute *lp5523_attributes[] = { &dev_attr_engine1_mode.attr, &dev_attr_engine2_mode.attr, &dev_attr_engine3_mode.attr, - &dev_attr_selftest.attr, &dev_attr_engine1_load.attr, - &dev_attr_engine1_leds.attr, &dev_attr_engine2_load.attr, - &dev_attr_engine2_leds.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, }; -static int lp5523_register_sysfs(struct i2c_client *client) -{ - struct device *dev = &client->dev; - int ret; - - ret = sysfs_create_group(&dev->kobj, &lp5523_group); - if (ret < 0) - return ret; - - return 0; -} - -static void lp5523_unregister_sysfs(struct i2c_client *client) -{ - struct lp5523_chip *chip = i2c_get_clientdata(client); - struct device *dev = &client->dev; - int i; - - sysfs_remove_group(&dev->kobj, &lp5523_group); - - for (i = 0; i < chip->num_leds; i++) - sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, - &lp5523_led_attribute_group); -} - -/*--------------------------------------------------------------*/ -/* Set chip operating mode */ -/*--------------------------------------------------------------*/ -static int lp5523_set_mode(struct lp5523_engine *engine, u8 mode) -{ - int ret = 0; - - /* if in that mode already do nothing, except for run */ - if (mode == engine->mode && mode != LP5523_CMD_RUN) - return 0; - - if (mode == LP5523_CMD_RUN) { - ret = lp5523_run_program(engine); - } else if (mode == LP5523_CMD_LOAD) { - lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); - lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); - } else if (mode == LP5523_CMD_DISABLED) { - lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); - } - - engine->mode = mode; - - return ret; -} - -/*--------------------------------------------------------------*/ -/* Probe, Attach, Remove */ -/*--------------------------------------------------------------*/ -static int __init lp5523_init_engine(struct lp5523_engine *engine, int id) -{ - if (id < 1 || id > LP5523_ENGINES) - return -1; - engine->id = id; - engine->engine_mask = LP5523_ENG_MASK_BASE >> SHIFT_MASK(id); - engine->prog_page = id - 1; - engine->mux_page = id + 2; - - return 0; -} +/* 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 __devinit lp5523_init_led(struct lp5523_led *led, struct device *dev, - int chan, struct lp5523_platform_data *pdata) +static int lp5523_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - char name[32]; - int res; - - if (chan >= LP5523_LEDS) - return -EINVAL; - - if (pdata->led_config[chan].led_current) { - led->led_current = pdata->led_config[chan].led_current; - led->max_current = pdata->led_config[chan].max_current; - led->chan_nr = pdata->led_config[chan].chan_nr; - - if (led->chan_nr >= LP5523_LEDS) { - dev_err(dev, "Use channel numbers between 0 and %d\n", - LP5523_LEDS - 1); + 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; } - - snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ?: "lp5523", chan); - - led->cdev.name = name; - led->cdev.brightness_set = lp5523_set_brightness; - res = led_classdev_register(dev, &led->cdev); - if (res < 0) { - dev_err(dev, "couldn't register led on channel %d\n", - chan); - return res; - } - res = sysfs_create_group(&led->cdev.dev->kobj, - &lp5523_led_attribute_group); - if (res < 0) { - dev_err(dev, "couldn't register current attribute\n"); - led_classdev_unregister(&led->cdev); - return res; - } - } else { - led->led_current = 0; } - return 0; -} + pdata = dev_get_platdata(&client->dev); -static int __devinit lp5523_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct lp5523_chip *chip; - struct lp5523_platform_data *pdata; - int ret, i, led; - - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - i2c_set_clientdata(client, chip); - chip->client = client; - - pdata = client->dev.platform_data; + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; - if (!pdata) { - dev_err(&client->dev, "no platform data\n"); - ret = -EINVAL; - goto fail1; - } + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp5523_cfg; mutex_init(&chip->lock); - chip->pdata = pdata; - - if (pdata->setup_resources) { - ret = pdata->setup_resources(); - if (ret < 0) - goto fail1; - } - - if (pdata->enable) { - pdata->enable(0); - usleep_range(1000, 2000); /* Keep enable down at least 1ms */ - pdata->enable(1); - usleep_range(1000, 2000); /* 500us abs min. */ - } + i2c_set_clientdata(client, led); - lp5523_write(client, LP5523_REG_RESET, 0xff); - usleep_range(10000, 20000); /* - * Exact value is not available. 10 - 20ms - * appears to be enough for reset. - */ - ret = lp5523_detect(client); + ret = lp55xx_init_device(chip); if (ret) - goto fail2; + goto err_init; - dev_info(&client->dev, "LP5523 Programmable led chip found\n"); + dev_info(&client->dev, "%s Programmable led chip found\n", id->name); - /* Initialize engines */ - for (i = 0; i < ARRAY_SIZE(chip->engines); i++) { - ret = lp5523_init_engine(&chip->engines[i], i + 1); - if (ret) { - dev_err(&client->dev, "error initializing engine\n"); - goto fail2; - } - } - ret = lp5523_configure(client); - if (ret < 0) { - dev_err(&client->dev, "error configuring chip\n"); - goto fail2; - } - - /* Initialize leds */ - chip->num_channels = pdata->num_channels; - chip->num_leds = 0; - led = 0; - for (i = 0; i < pdata->num_channels; i++) { - /* Do not initialize channels that are not connected */ - if (pdata->led_config[i].led_current == 0) - continue; - - ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata); - if (ret) { - dev_err(&client->dev, "error initializing leds\n"); - goto fail3; - } - chip->num_leds++; - - chip->leds[led].id = led; - /* Set LED current */ - lp5523_write(client, - LP5523_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, - chip->leds[led].led_current); - - INIT_WORK(&(chip->leds[led].brightness_work), - lp5523_led_brightness_work); - - led++; - } + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; - ret = lp5523_register_sysfs(client); + ret = lp55xx_register_sysfs(chip); if (ret) { dev_err(&client->dev, "registering sysfs failed\n"); - goto fail3; - } - return ret; -fail3: - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); + goto err_register_sysfs; } -fail2: - if (pdata->enable) - pdata->enable(0); - if (pdata->release_resources) - pdata->release_resources(); -fail1: - kfree(chip); + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: return ret; } static int lp5523_remove(struct i2c_client *client) { - struct lp5523_chip *chip = i2c_get_clientdata(client); - int i; - - lp5523_unregister_sysfs(client); + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); - } + lp5523_stop_all_engines(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); - if (chip->pdata->enable) - chip->pdata->enable(0); - if (chip->pdata->release_resources) - chip->pdata->release_resources(); - kfree(chip); return 0; } static const struct i2c_device_id lp5523_id[] = { - { "lp5523", 0 }, + { "lp5523", LP5523 }, + { "lp55231", LP55231 }, { } }; MODULE_DEVICE_TABLE(i2c, lp5523_id); +#ifdef CONFIG_OF +static const struct of_device_id of_lp5523_leds_match[] = { + { .compatible = "national,lp5523", }, + { .compatible = "ti,lp55231", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5523_leds_match); +#endif + static struct i2c_driver lp5523_driver = { .driver = { - .name = "lp5523", + .name = "lp5523x", + .of_match_table = of_match_ptr(of_lp5523_leds_match), }, .probe = lp5523_probe, .remove = lp5523_remove, @@ -1022,5 +834,6 @@ static struct i2c_driver lp5523_driver = { module_i2c_driver(lp5523_driver); MODULE_AUTHOR("Mathias Nyman <mathias.nyman@nokia.com>"); +MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); MODULE_DESCRIPTION("LP5523 LED engine"); MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp5562.c b/drivers/leds/leds-lp5562.c new file mode 100644 index 00000000000..ca85724ab13 --- /dev/null +++ b/drivers/leds/leds-lp5562.c @@ -0,0 +1,617 @@ +/* + * LP5562 LED driver + * + * Copyright (C) 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_data/leds-lp55xx.h> +#include <linux/slab.h> + +#include "leds-lp55xx-common.h" + +#define LP5562_PROGRAM_LENGTH 32 +#define LP5562_MAX_LEDS 4 + +/* ENABLE Register 00h */ +#define LP5562_REG_ENABLE 0x00 +#define LP5562_EXEC_ENG1_M 0x30 +#define LP5562_EXEC_ENG2_M 0x0C +#define LP5562_EXEC_ENG3_M 0x03 +#define LP5562_EXEC_M 0x3F +#define LP5562_MASTER_ENABLE 0x40 /* Chip master enable */ +#define LP5562_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ +#define LP5562_EXEC_RUN 0x2A +#define LP5562_ENABLE_DEFAULT \ + (LP5562_MASTER_ENABLE | LP5562_LOGARITHMIC_PWM) +#define LP5562_ENABLE_RUN_PROGRAM \ + (LP5562_ENABLE_DEFAULT | LP5562_EXEC_RUN) + +/* OPMODE Register 01h */ +#define LP5562_REG_OP_MODE 0x01 +#define LP5562_MODE_ENG1_M 0x30 +#define LP5562_MODE_ENG2_M 0x0C +#define LP5562_MODE_ENG3_M 0x03 +#define LP5562_LOAD_ENG1 0x10 +#define LP5562_LOAD_ENG2 0x04 +#define LP5562_LOAD_ENG3 0x01 +#define LP5562_RUN_ENG1 0x20 +#define LP5562_RUN_ENG2 0x08 +#define LP5562_RUN_ENG3 0x02 +#define LP5562_ENG1_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG1_M) == LP5562_LOAD_ENG1) +#define LP5562_ENG2_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG2_M) == LP5562_LOAD_ENG2) +#define LP5562_ENG3_IS_LOADING(mode) \ + ((mode & LP5562_MODE_ENG3_M) == LP5562_LOAD_ENG3) + +/* BRIGHTNESS Registers */ +#define LP5562_REG_R_PWM 0x04 +#define LP5562_REG_G_PWM 0x03 +#define LP5562_REG_B_PWM 0x02 +#define LP5562_REG_W_PWM 0x0E + +/* CURRENT Registers */ +#define LP5562_REG_R_CURRENT 0x07 +#define LP5562_REG_G_CURRENT 0x06 +#define LP5562_REG_B_CURRENT 0x05 +#define LP5562_REG_W_CURRENT 0x0F + +/* CONFIG Register 08h */ +#define LP5562_REG_CONFIG 0x08 +#define LP5562_PWM_HF 0x40 +#define LP5562_PWRSAVE_EN 0x20 +#define LP5562_CLK_INT 0x01 /* Internal clock */ +#define LP5562_DEFAULT_CFG (LP5562_PWM_HF | LP5562_PWRSAVE_EN) + +/* RESET Register 0Dh */ +#define LP5562_REG_RESET 0x0D +#define LP5562_RESET 0xFF + +/* PROGRAM ENGINE Registers */ +#define LP5562_REG_PROG_MEM_ENG1 0x10 +#define LP5562_REG_PROG_MEM_ENG2 0x30 +#define LP5562_REG_PROG_MEM_ENG3 0x50 + +/* LEDMAP Register 70h */ +#define LP5562_REG_ENG_SEL 0x70 +#define LP5562_ENG_SEL_PWM 0 +#define LP5562_ENG_FOR_RGB_M 0x3F +#define LP5562_ENG_SEL_RGB 0x1B /* R:ENG1, G:ENG2, B:ENG3 */ +#define LP5562_ENG_FOR_W_M 0xC0 +#define LP5562_ENG1_FOR_W 0x40 /* W:ENG1 */ +#define LP5562_ENG2_FOR_W 0x80 /* W:ENG2 */ +#define LP5562_ENG3_FOR_W 0xC0 /* W:ENG3 */ + +/* Program Commands */ +#define LP5562_CMD_DISABLE 0x00 +#define LP5562_CMD_LOAD 0x15 +#define LP5562_CMD_RUN 0x2A +#define LP5562_CMD_DIRECT 0x3F +#define LP5562_PATTERN_OFF 0 + +static inline void lp5562_wait_opmode_done(void) +{ + /* operation mode change needs to be longer than 153 us */ + usleep_range(200, 300); +} + +static inline void lp5562_wait_enable_done(void) +{ + /* it takes more 488 us to update ENABLE register */ + usleep_range(500, 600); +} + +static void lp5562_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + u8 addr[] = { + LP5562_REG_R_CURRENT, + LP5562_REG_G_CURRENT, + LP5562_REG_B_CURRENT, + LP5562_REG_W_CURRENT, + }; + + led->led_current = led_current; + lp55xx_write(led->chip, addr[led->chan_nr], led_current); +} + +static void lp5562_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5562_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP5562_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP5562_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP5562_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP5562_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP5562_LOAD_ENG3, + }; + + lp55xx_update_bits(chip, LP5562_REG_OP_MODE, mask[idx], val[idx]); + + lp5562_wait_opmode_done(); +} + +static void lp5562_stop_engine(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DISABLE); + lp5562_wait_opmode_done(); +} + +static void lp5562_run_engine(struct lp55xx_chip *chip, bool start) +{ + int ret; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp55xx_write(chip, LP5562_REG_ENABLE, LP5562_ENABLE_DEFAULT); + lp5562_wait_enable_done(); + lp5562_stop_engine(chip); + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM); + lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); + lp5562_wait_opmode_done(); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP5562_REG_OP_MODE, &mode); + if (ret) + return; + + ret = lp55xx_read(chip, LP5562_REG_ENABLE, &exec); + if (ret) + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP5562_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG1_M) | LP5562_RUN_ENG1; + exec = (exec & ~LP5562_EXEC_ENG1_M) | LP5562_RUN_ENG1; + } + + if (LP5562_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG2_M) | LP5562_RUN_ENG2; + exec = (exec & ~LP5562_EXEC_ENG2_M) | LP5562_RUN_ENG2; + } + + if (LP5562_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP5562_MODE_ENG3_M) | LP5562_RUN_ENG3; + exec = (exec & ~LP5562_EXEC_ENG3_M) | LP5562_RUN_ENG3; + } + + lp55xx_write(chip, LP5562_REG_OP_MODE, mode); + lp5562_wait_opmode_done(); + + lp55xx_update_bits(chip, LP5562_REG_ENABLE, LP5562_EXEC_M, exec); + lp5562_wait_enable_done(); +} + +static int lp5562_update_firmware(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 pattern[LP5562_PROGRAM_LENGTH] = {0}; + u8 addr[] = { + [LP55XX_ENGINE_1] = LP5562_REG_PROG_MEM_ENG1, + [LP55XX_ENGINE_2] = LP5562_REG_PROG_MEM_ENG2, + [LP55XX_ENGINE_3] = LP5562_REG_PROG_MEM_ENG3, + }; + unsigned cmd; + char c[3]; + int program_size; + int nrchars; + int offset = 0; + int ret; + int i; + + /* clear program memory before updating */ + for (i = 0; i < LP5562_PROGRAM_LENGTH; i++) + lp55xx_write(chip, addr[idx] + i, 0); + + i = 0; + while ((offset < size - 1) && (i < LP5562_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; + + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; + + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } + + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; + + program_size = i; + for (i = 0; i < program_size; i++) + lp55xx_write(chip, addr[idx] + i, pattern[i]); + + return 0; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static void lp5562_firmware_loaded(struct lp55xx_chip *chip) +{ + const struct firmware *fw = chip->fw; + + if (fw->size > LP5562_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } + + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ + + lp5562_load_engine(chip); + lp5562_update_firmware(chip, fw->data, fw->size); +} + +static int lp5562_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 cfg = LP5562_DEFAULT_CFG; + + /* Set all PWMs to direct control mode */ + ret = lp55xx_write(chip, LP5562_REG_OP_MODE, LP5562_CMD_DIRECT); + if (ret) + return ret; + + lp5562_wait_opmode_done(); + + /* Update configuration for the clock setting */ + if (!lp55xx_is_extclk_used(chip)) + cfg |= LP5562_CLK_INT; + + ret = lp55xx_write(chip, LP5562_REG_CONFIG, cfg); + if (ret) + return ret; + + /* Initialize all channels PWM to zero -> leds off */ + lp55xx_write(chip, LP5562_REG_R_PWM, 0); + lp55xx_write(chip, LP5562_REG_G_PWM, 0); + lp55xx_write(chip, LP5562_REG_B_PWM, 0); + lp55xx_write(chip, LP5562_REG_W_PWM, 0); + + /* Set LED map as register PWM by default */ + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_PWM); + + return 0; +} + +static void lp5562_led_brightness_work(struct work_struct *work) +{ + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; + u8 addr[] = { + LP5562_REG_R_PWM, + LP5562_REG_G_PWM, + LP5562_REG_B_PWM, + LP5562_REG_W_PWM, + }; + + mutex_lock(&chip->lock); + lp55xx_write(chip, addr[led->chan_nr], led->brightness); + mutex_unlock(&chip->lock); +} + +static void lp5562_write_program_memory(struct lp55xx_chip *chip, + u8 base, const u8 *rgb, int size) +{ + int i; + + if (!rgb || size <= 0) + return; + + for (i = 0; i < size; i++) + lp55xx_write(chip, base + i, *(rgb + i)); + + lp55xx_write(chip, base + i, 0); + lp55xx_write(chip, base + i + 1, 0); +} + +/* check the size of program count */ +static inline bool _is_pc_overflow(struct lp55xx_predef_pattern *ptn) +{ + return ptn->size_r >= LP5562_PROGRAM_LENGTH || + ptn->size_g >= LP5562_PROGRAM_LENGTH || + ptn->size_b >= LP5562_PROGRAM_LENGTH; +} + +static int lp5562_run_predef_led_pattern(struct lp55xx_chip *chip, int mode) +{ + struct lp55xx_predef_pattern *ptn; + int i; + + if (mode == LP5562_PATTERN_OFF) { + lp5562_run_engine(chip, false); + return 0; + } + + ptn = chip->pdata->patterns + (mode - 1); + if (!ptn || _is_pc_overflow(ptn)) { + dev_err(&chip->cl->dev, "invalid pattern data\n"); + return -EINVAL; + } + + lp5562_stop_engine(chip); + + /* Set LED map as RGB */ + lp55xx_write(chip, LP5562_REG_ENG_SEL, LP5562_ENG_SEL_RGB); + + /* Load engines */ + for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) { + chip->engine_idx = i; + lp5562_load_engine(chip); + } + + /* Clear program registers */ + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG1 + 1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG2 + 1, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3, 0); + lp55xx_write(chip, LP5562_REG_PROG_MEM_ENG3 + 1, 0); + + /* Program engines */ + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG1, + ptn->r, ptn->size_r); + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG2, + ptn->g, ptn->size_g); + lp5562_write_program_memory(chip, LP5562_REG_PROG_MEM_ENG3, + ptn->b, ptn->size_b); + + /* Run engines */ + lp5562_run_engine(chip, true); + + return 0; +} + +static ssize_t lp5562_store_pattern(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_predef_pattern *ptn = chip->pdata->patterns; + int num_patterns = chip->pdata->num_patterns; + unsigned long mode; + int ret; + + ret = kstrtoul(buf, 0, &mode); + if (ret) + return ret; + + if (mode > num_patterns || !ptn) + return -EINVAL; + + mutex_lock(&chip->lock); + ret = lp5562_run_predef_led_pattern(chip, mode); + mutex_unlock(&chip->lock); + + if (ret) + return ret; + + return len; +} + +static ssize_t lp5562_store_engine_mux(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + u8 mask; + u8 val; + + /* LED map + * R ... Engine 1 (fixed) + * G ... Engine 2 (fixed) + * B ... Engine 3 (fixed) + * W ... Engine 1 or 2 or 3 + */ + + if (sysfs_streq(buf, "RGB")) { + mask = LP5562_ENG_FOR_RGB_M; + val = LP5562_ENG_SEL_RGB; + } else if (sysfs_streq(buf, "W")) { + enum lp55xx_engine_index idx = chip->engine_idx; + + mask = LP5562_ENG_FOR_W_M; + switch (idx) { + case LP55XX_ENGINE_1: + val = LP5562_ENG1_FOR_W; + break; + case LP55XX_ENGINE_2: + val = LP5562_ENG2_FOR_W; + break; + case LP55XX_ENGINE_3: + val = LP5562_ENG3_FOR_W; + break; + default: + return -EINVAL; + } + + } else { + dev_err(dev, "choose RGB or W\n"); + return -EINVAL; + } + + mutex_lock(&chip->lock); + lp55xx_update_bits(chip, LP5562_REG_ENG_SEL, mask, val); + mutex_unlock(&chip->lock); + + return len; +} + +static LP55XX_DEV_ATTR_WO(led_pattern, lp5562_store_pattern); +static LP55XX_DEV_ATTR_WO(engine_mux, lp5562_store_engine_mux); + +static struct attribute *lp5562_attributes[] = { + &dev_attr_led_pattern.attr, + &dev_attr_engine_mux.attr, + NULL, +}; + +static const struct attribute_group lp5562_group = { + .attrs = lp5562_attributes, +}; + +/* Chip specific configurations */ +static struct lp55xx_device_config lp5562_cfg = { + .max_channel = LP5562_MAX_LEDS, + .reset = { + .addr = LP5562_REG_RESET, + .val = LP5562_RESET, + }, + .enable = { + .addr = LP5562_REG_ENABLE, + .val = LP5562_ENABLE_DEFAULT, + }, + .post_init_device = lp5562_post_init_device, + .set_led_current = lp5562_set_led_current, + .brightness_work_fn = lp5562_led_brightness_work, + .run_engine = lp5562_run_engine, + .firmware_cb = lp5562_firmware_loaded, + .dev_attr_group = &lp5562_group, +}; + +static int lp5562_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata; + struct device_node *np = client->dev.of_node; + + if (!dev_get_platdata(&client->dev)) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = dev_get_platdata(&client->dev); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; + + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp5562_cfg; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; + + ret = lp55xx_register_sysfs(chip); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto err_register_sysfs; + } + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: + return ret; +} + +static int lp5562_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp5562_stop_engine(chip); + + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); + + return 0; +} + +static const struct i2c_device_id lp5562_id[] = { + { "lp5562", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp5562_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp5562_leds_match[] = { + { .compatible = "ti,lp5562", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5562_leds_match); +#endif + +static struct i2c_driver lp5562_driver = { + .driver = { + .name = "lp5562", + .of_match_table = of_match_ptr(of_lp5562_leds_match), + }, + .probe = lp5562_probe, + .remove = lp5562_remove, + .id_table = lp5562_id, +}; + +module_i2c_driver(lp5562_driver); + +MODULE_DESCRIPTION("Texas Instruments LP5562 LED Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c new file mode 100644 index 00000000000..88317b4f7bf --- /dev/null +++ b/drivers/leds/leds-lp55xx-common.c @@ -0,0 +1,613 @@ +/* + * LP5521/LP5523/LP55231/LP5562 Common Driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Derived from leds-lp5521.c, leds-lp5523.c + */ + +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_data/leds-lp55xx.h> +#include <linux/slab.h> +#include <linux/gpio.h> +#include <linux/of_gpio.h> + +#include "leds-lp55xx-common.h" + +/* External clock rate */ +#define LP55XX_CLK_32K 32768 + +static struct lp55xx_led *cdev_to_lp55xx_led(struct led_classdev *cdev) +{ + return container_of(cdev, struct lp55xx_led, cdev); +} + +static struct lp55xx_led *dev_to_lp55xx_led(struct device *dev) +{ + return cdev_to_lp55xx_led(dev_get_drvdata(dev)); +} + +static void lp55xx_reset_device(struct lp55xx_chip *chip) +{ + struct lp55xx_device_config *cfg = chip->cfg; + u8 addr = cfg->reset.addr; + u8 val = cfg->reset.val; + + /* no error checking here because no ACK from the device after reset */ + lp55xx_write(chip, addr, val); +} + +static int lp55xx_detect_device(struct lp55xx_chip *chip) +{ + struct lp55xx_device_config *cfg = chip->cfg; + u8 addr = cfg->enable.addr; + u8 val = cfg->enable.val; + int ret; + + ret = lp55xx_write(chip, addr, val); + if (ret) + return ret; + + usleep_range(1000, 2000); + + ret = lp55xx_read(chip, addr, &val); + if (ret) + return ret; + + if (val != cfg->enable.val) + return -ENODEV; + + return 0; +} + +static int lp55xx_post_init_device(struct lp55xx_chip *chip) +{ + struct lp55xx_device_config *cfg = chip->cfg; + + if (!cfg->post_init_device) + return 0; + + return cfg->post_init_device(chip); +} + +static ssize_t lp55xx_show_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp55xx_led *led = dev_to_lp55xx_led(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", led->led_current); +} + +static ssize_t lp55xx_store_current(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = dev_to_lp55xx_led(dev); + struct lp55xx_chip *chip = led->chip; + unsigned long curr; + + if (kstrtoul(buf, 0, &curr)) + return -EINVAL; + + if (curr > led->max_current) + return -EINVAL; + + if (!chip->cfg->set_led_current) + return len; + + mutex_lock(&chip->lock); + chip->cfg->set_led_current(led, (u8)curr); + mutex_unlock(&chip->lock); + + return len; +} + +static ssize_t lp55xx_show_max_current(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp55xx_led *led = dev_to_lp55xx_led(dev); + + return scnprintf(buf, PAGE_SIZE, "%d\n", led->max_current); +} + +static DEVICE_ATTR(led_current, S_IRUGO | S_IWUSR, lp55xx_show_current, + lp55xx_store_current); +static DEVICE_ATTR(max_current, S_IRUGO , lp55xx_show_max_current, NULL); + +static struct attribute *lp55xx_led_attributes[] = { + &dev_attr_led_current.attr, + &dev_attr_max_current.attr, + NULL, +}; + +static struct attribute_group lp55xx_led_attr_group = { + .attrs = lp55xx_led_attributes +}; + +static void lp55xx_set_brightness(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct lp55xx_led *led = cdev_to_lp55xx_led(cdev); + + led->brightness = (u8)brightness; + schedule_work(&led->brightness_work); +} + +static int lp55xx_init_led(struct lp55xx_led *led, + struct lp55xx_chip *chip, int chan) +{ + struct lp55xx_platform_data *pdata = chip->pdata; + struct lp55xx_device_config *cfg = chip->cfg; + struct device *dev = &chip->cl->dev; + char name[32]; + int ret; + int max_channel = cfg->max_channel; + + if (chan >= max_channel) { + dev_err(dev, "invalid channel: %d / %d\n", chan, max_channel); + return -EINVAL; + } + + if (pdata->led_config[chan].led_current == 0) + return 0; + + led->led_current = pdata->led_config[chan].led_current; + led->max_current = pdata->led_config[chan].max_current; + led->chan_nr = pdata->led_config[chan].chan_nr; + led->cdev.default_trigger = pdata->led_config[chan].default_trigger; + + if (led->chan_nr >= max_channel) { + dev_err(dev, "Use channel numbers between 0 and %d\n", + max_channel - 1); + return -EINVAL; + } + + led->cdev.brightness_set = lp55xx_set_brightness; + + if (pdata->led_config[chan].name) { + led->cdev.name = pdata->led_config[chan].name; + } else { + snprintf(name, sizeof(name), "%s:channel%d", + pdata->label ? : chip->cl->name, chan); + led->cdev.name = name; + } + + /* + * register led class device for each channel and + * add device attributes + */ + + ret = led_classdev_register(dev, &led->cdev); + if (ret) { + dev_err(dev, "led register err: %d\n", ret); + return ret; + } + + ret = sysfs_create_group(&led->cdev.dev->kobj, &lp55xx_led_attr_group); + if (ret) { + dev_err(dev, "led sysfs err: %d\n", ret); + led_classdev_unregister(&led->cdev); + return ret; + } + + return 0; +} + +static void lp55xx_firmware_loaded(const struct firmware *fw, void *context) +{ + struct lp55xx_chip *chip = context; + struct device *dev = &chip->cl->dev; + enum lp55xx_engine_index idx = chip->engine_idx; + + if (!fw) { + dev_err(dev, "firmware request failed\n"); + goto out; + } + + /* handling firmware data is chip dependent */ + mutex_lock(&chip->lock); + + chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD; + chip->fw = fw; + if (chip->cfg->firmware_cb) + chip->cfg->firmware_cb(chip); + + mutex_unlock(&chip->lock); + +out: + /* firmware should be released for other channel use */ + release_firmware(chip->fw); +} + +static int lp55xx_request_firmware(struct lp55xx_chip *chip) +{ + const char *name = chip->cl->name; + struct device *dev = &chip->cl->dev; + + return request_firmware_nowait(THIS_MODULE, true, name, dev, + GFP_KERNEL, chip, lp55xx_firmware_loaded); +} + +static ssize_t lp55xx_show_engine_select(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + + return sprintf(buf, "%d\n", chip->engine_idx); +} + +static ssize_t lp55xx_store_engine_select(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + unsigned long val; + int ret; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + /* select the engine to be run */ + + switch (val) { + case LP55XX_ENGINE_1: + case LP55XX_ENGINE_2: + case LP55XX_ENGINE_3: + mutex_lock(&chip->lock); + chip->engine_idx = val; + ret = lp55xx_request_firmware(chip); + mutex_unlock(&chip->lock); + break; + default: + dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val); + return -EINVAL; + } + + if (ret) { + dev_err(dev, "request firmware err: %d\n", ret); + return ret; + } + + return len; +} + +static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start) +{ + if (chip->cfg->run_engine) + chip->cfg->run_engine(chip, start); +} + +static ssize_t lp55xx_store_engine_run(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t len) +{ + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + unsigned long val; + + if (kstrtoul(buf, 0, &val)) + return -EINVAL; + + /* run or stop the selected engine */ + + if (val <= 0) { + lp55xx_run_engine(chip, false); + return len; + } + + mutex_lock(&chip->lock); + lp55xx_run_engine(chip, true); + mutex_unlock(&chip->lock); + + return len; +} + +static DEVICE_ATTR(select_engine, S_IRUGO | S_IWUSR, + lp55xx_show_engine_select, lp55xx_store_engine_select); +static DEVICE_ATTR(run_engine, S_IWUSR, NULL, lp55xx_store_engine_run); + +static struct attribute *lp55xx_engine_attributes[] = { + &dev_attr_select_engine.attr, + &dev_attr_run_engine.attr, + NULL, +}; + +static const struct attribute_group lp55xx_engine_attr_group = { + .attrs = lp55xx_engine_attributes, +}; + +int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val) +{ + return i2c_smbus_write_byte_data(chip->cl, reg, val); +} +EXPORT_SYMBOL_GPL(lp55xx_write); + +int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(chip->cl, reg); + if (ret < 0) + return ret; + + *val = ret; + return 0; +} +EXPORT_SYMBOL_GPL(lp55xx_read); + +int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, u8 mask, u8 val) +{ + int ret; + u8 tmp; + + ret = lp55xx_read(chip, reg, &tmp); + if (ret) + return ret; + + tmp &= ~mask; + tmp |= val & mask; + + return lp55xx_write(chip, reg, tmp); +} +EXPORT_SYMBOL_GPL(lp55xx_update_bits); + +bool lp55xx_is_extclk_used(struct lp55xx_chip *chip) +{ + struct clk *clk; + int err; + + clk = devm_clk_get(&chip->cl->dev, "32k_clk"); + if (IS_ERR(clk)) + goto use_internal_clk; + + err = clk_prepare_enable(clk); + if (err) + goto use_internal_clk; + + if (clk_get_rate(clk) != LP55XX_CLK_32K) { + clk_disable_unprepare(clk); + goto use_internal_clk; + } + + dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K); + + chip->clk = clk; + return true; + +use_internal_clk: + dev_info(&chip->cl->dev, "internal clock used\n"); + return false; +} +EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used); + +int lp55xx_init_device(struct lp55xx_chip *chip) +{ + struct lp55xx_platform_data *pdata; + struct lp55xx_device_config *cfg; + struct device *dev = &chip->cl->dev; + int ret = 0; + + WARN_ON(!chip); + + pdata = chip->pdata; + cfg = chip->cfg; + + if (!pdata || !cfg) + return -EINVAL; + + if (gpio_is_valid(pdata->enable_gpio)) { + ret = devm_gpio_request_one(dev, pdata->enable_gpio, + GPIOF_DIR_OUT, "lp5523_enable"); + if (ret < 0) { + dev_err(dev, "could not acquire enable gpio (err=%d)\n", + ret); + goto err; + } + + gpio_set_value(pdata->enable_gpio, 0); + usleep_range(1000, 2000); /* Keep enable down at least 1ms */ + gpio_set_value(pdata->enable_gpio, 1); + usleep_range(1000, 2000); /* 500us abs min. */ + } + + lp55xx_reset_device(chip); + + /* + * Exact value is not available. 10 - 20ms + * appears to be enough for reset. + */ + usleep_range(10000, 20000); + + ret = lp55xx_detect_device(chip); + if (ret) { + dev_err(dev, "device detection err: %d\n", ret); + goto err; + } + + /* chip specific initialization */ + ret = lp55xx_post_init_device(chip); + if (ret) { + dev_err(dev, "post init device err: %d\n", ret); + goto err_post_init; + } + + return 0; + +err_post_init: + lp55xx_deinit_device(chip); +err: + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_init_device); + +void lp55xx_deinit_device(struct lp55xx_chip *chip) +{ + struct lp55xx_platform_data *pdata = chip->pdata; + + if (chip->clk) + clk_disable_unprepare(chip->clk); + + if (gpio_is_valid(pdata->enable_gpio)) + gpio_set_value(pdata->enable_gpio, 0); +} +EXPORT_SYMBOL_GPL(lp55xx_deinit_device); + +int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) +{ + struct lp55xx_platform_data *pdata = chip->pdata; + struct lp55xx_device_config *cfg = chip->cfg; + int num_channels = pdata->num_channels; + struct lp55xx_led *each; + u8 led_current; + int ret; + int i; + + if (!cfg->brightness_work_fn) { + dev_err(&chip->cl->dev, "empty brightness configuration\n"); + return -EINVAL; + } + + for (i = 0; i < num_channels; i++) { + + /* do not initialize channels that are not connected */ + if (pdata->led_config[i].led_current == 0) + continue; + + led_current = pdata->led_config[i].led_current; + each = led + i; + ret = lp55xx_init_led(each, chip, i); + if (ret) + goto err_init_led; + + INIT_WORK(&each->brightness_work, cfg->brightness_work_fn); + + chip->num_leds++; + each->chip = chip; + + /* setting led current at each channel */ + if (cfg->set_led_current) + cfg->set_led_current(each, led_current); + } + + return 0; + +err_init_led: + lp55xx_unregister_leds(led, chip); + return ret; +} +EXPORT_SYMBOL_GPL(lp55xx_register_leds); + +void lp55xx_unregister_leds(struct lp55xx_led *led, struct lp55xx_chip *chip) +{ + int i; + struct lp55xx_led *each; + + for (i = 0; i < chip->num_leds; i++) { + each = led + i; + led_classdev_unregister(&each->cdev); + flush_work(&each->brightness_work); + } +} +EXPORT_SYMBOL_GPL(lp55xx_unregister_leds); + +int lp55xx_register_sysfs(struct lp55xx_chip *chip) +{ + struct device *dev = &chip->cl->dev; + struct lp55xx_device_config *cfg = chip->cfg; + int ret; + + if (!cfg->run_engine || !cfg->firmware_cb) + goto dev_specific_attrs; + + ret = sysfs_create_group(&dev->kobj, &lp55xx_engine_attr_group); + if (ret) + return ret; + +dev_specific_attrs: + return cfg->dev_attr_group ? + sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0; +} +EXPORT_SYMBOL_GPL(lp55xx_register_sysfs); + +void lp55xx_unregister_sysfs(struct lp55xx_chip *chip) +{ + struct device *dev = &chip->cl->dev; + struct lp55xx_device_config *cfg = chip->cfg; + + if (cfg->dev_attr_group) + sysfs_remove_group(&dev->kobj, cfg->dev_attr_group); + + sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group); +} +EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs); + +int lp55xx_of_populate_pdata(struct device *dev, struct device_node *np) +{ + struct device_node *child; + struct lp55xx_platform_data *pdata; + struct lp55xx_led_config *cfg; + int num_channels; + int i = 0; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + num_channels = of_get_child_count(np); + if (num_channels == 0) { + dev_err(dev, "no LED channels\n"); + return -EINVAL; + } + + cfg = devm_kzalloc(dev, sizeof(*cfg) * num_channels, GFP_KERNEL); + if (!cfg) + return -ENOMEM; + + pdata->led_config = &cfg[0]; + pdata->num_channels = num_channels; + + for_each_child_of_node(np, child) { + cfg[i].chan_nr = i; + + of_property_read_string(child, "chan-name", &cfg[i].name); + of_property_read_u8(child, "led-cur", &cfg[i].led_current); + of_property_read_u8(child, "max-cur", &cfg[i].max_current); + cfg[i].default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + + i++; + } + + of_property_read_string(np, "label", &pdata->label); + of_property_read_u8(np, "clock-mode", &pdata->clock_mode); + + pdata->enable_gpio = of_get_named_gpio(np, "enable-gpio", 0); + + /* LP8501 specific */ + of_property_read_u8(np, "pwr-sel", (u8 *)&pdata->pwr_sel); + + dev->platform_data = pdata; + + return 0; +} +EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata); + +MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); +MODULE_DESCRIPTION("LP55xx Common Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp55xx-common.h b/drivers/leds/leds-lp55xx-common.h new file mode 100644 index 00000000000..cceab483edd --- /dev/null +++ b/drivers/leds/leds-lp55xx-common.h @@ -0,0 +1,208 @@ +/* + * LP55XX Common Driver Header + * + * Copyright (C) 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * Derived from leds-lp5521.c, leds-lp5523.c + */ + +#ifndef _LEDS_LP55XX_COMMON_H +#define _LEDS_LP55XX_COMMON_H + +enum lp55xx_engine_index { + LP55XX_ENGINE_INVALID, + LP55XX_ENGINE_1, + LP55XX_ENGINE_2, + LP55XX_ENGINE_3, + LP55XX_ENGINE_MAX = LP55XX_ENGINE_3, +}; + +enum lp55xx_engine_mode { + LP55XX_ENGINE_DISABLED, + LP55XX_ENGINE_LOAD, + LP55XX_ENGINE_RUN, +}; + +#define LP55XX_DEV_ATTR_RW(name, show, store) \ + DEVICE_ATTR(name, S_IRUGO | S_IWUSR, show, store) +#define LP55XX_DEV_ATTR_RO(name, show) \ + DEVICE_ATTR(name, S_IRUGO, show, NULL) +#define LP55XX_DEV_ATTR_WO(name, store) \ + DEVICE_ATTR(name, S_IWUSR, NULL, store) + +#define show_mode(nr) \ +static ssize_t show_engine##nr##_mode(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return show_engine_mode(dev, attr, buf, nr); \ +} + +#define store_mode(nr) \ +static ssize_t store_engine##nr##_mode(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_mode(dev, attr, buf, len, nr); \ +} + +#define show_leds(nr) \ +static ssize_t show_engine##nr##_leds(struct device *dev, \ + struct device_attribute *attr, \ + char *buf) \ +{ \ + return show_engine_leds(dev, attr, buf, nr); \ +} + +#define store_leds(nr) \ +static ssize_t store_engine##nr##_leds(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_leds(dev, attr, buf, len, nr); \ +} + +#define store_load(nr) \ +static ssize_t store_engine##nr##_load(struct device *dev, \ + struct device_attribute *attr, \ + const char *buf, size_t len) \ +{ \ + return store_engine_load(dev, attr, buf, len, nr); \ +} + +struct lp55xx_led; +struct lp55xx_chip; + +/* + * struct lp55xx_reg + * @addr : Register address + * @val : Register value + */ +struct lp55xx_reg { + u8 addr; + u8 val; +}; + +/* + * struct lp55xx_device_config + * @reset : Chip specific reset command + * @enable : Chip specific enable command + * @max_channel : Maximum number of channels + * @post_init_device : Chip specific initialization code + * @brightness_work_fn : Brightness work function + * @set_led_current : LED current set function + * @firmware_cb : Call function when the firmware is loaded + * @run_engine : Run internal engine for pattern + * @dev_attr_group : Device specific attributes + */ +struct lp55xx_device_config { + const struct lp55xx_reg reset; + const struct lp55xx_reg enable; + const int max_channel; + + /* define if the device has specific initialization process */ + int (*post_init_device) (struct lp55xx_chip *chip); + + /* access brightness register */ + void (*brightness_work_fn)(struct work_struct *work); + + /* current setting function */ + void (*set_led_current) (struct lp55xx_led *led, u8 led_current); + + /* access program memory when the firmware is loaded */ + void (*firmware_cb)(struct lp55xx_chip *chip); + + /* used for running firmware LED patterns */ + void (*run_engine) (struct lp55xx_chip *chip, bool start); + + /* additional device specific attributes */ + const struct attribute_group *dev_attr_group; +}; + +/* + * struct lp55xx_engine + * @mode : Engine mode + * @led_mux : Mux bits for LED selection. Only used in LP5523 + */ +struct lp55xx_engine { + enum lp55xx_engine_mode mode; + u16 led_mux; +}; + +/* + * struct lp55xx_chip + * @cl : I2C communication for access registers + * @pdata : Platform specific data + * @lock : Lock for user-space interface + * @num_leds : Number of registered LEDs + * @cfg : Device specific configuration data + * @engine_idx : Selected engine number + * @engines : Engine structure for the device attribute R/W interface + * @fw : Firmware data for running a LED pattern + */ +struct lp55xx_chip { + struct i2c_client *cl; + struct clk *clk; + struct lp55xx_platform_data *pdata; + struct mutex lock; /* lock for user-space interface */ + int num_leds; + struct lp55xx_device_config *cfg; + enum lp55xx_engine_index engine_idx; + struct lp55xx_engine engines[LP55XX_ENGINE_MAX]; + const struct firmware *fw; +}; + +/* + * struct lp55xx_led + * @chan_nr : Channel number + * @cdev : LED class device + * @led_current : Current setting at each led channel + * @max_current : Maximun current at each led channel + * @brightness_work : Workqueue for brightness control + * @brightness : Brightness value + * @chip : The lp55xx chip data + */ +struct lp55xx_led { + int chan_nr; + struct led_classdev cdev; + u8 led_current; + u8 max_current; + struct work_struct brightness_work; + u8 brightness; + struct lp55xx_chip *chip; +}; + +/* register access */ +extern int lp55xx_write(struct lp55xx_chip *chip, u8 reg, u8 val); +extern int lp55xx_read(struct lp55xx_chip *chip, u8 reg, u8 *val); +extern int lp55xx_update_bits(struct lp55xx_chip *chip, u8 reg, + u8 mask, u8 val); + +/* external clock detection */ +extern bool lp55xx_is_extclk_used(struct lp55xx_chip *chip); + +/* common device init/deinit functions */ +extern int lp55xx_init_device(struct lp55xx_chip *chip); +extern void lp55xx_deinit_device(struct lp55xx_chip *chip); + +/* common LED class device functions */ +extern int lp55xx_register_leds(struct lp55xx_led *led, + struct lp55xx_chip *chip); +extern void lp55xx_unregister_leds(struct lp55xx_led *led, + struct lp55xx_chip *chip); + +/* common device attributes functions */ +extern int lp55xx_register_sysfs(struct lp55xx_chip *chip); +extern void lp55xx_unregister_sysfs(struct lp55xx_chip *chip); + +/* common device tree population function */ +extern int lp55xx_of_populate_pdata(struct device *dev, + struct device_node *np); + +#endif /* _LEDS_LP55XX_COMMON_H */ diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c new file mode 100644 index 00000000000..00f068b0fa6 --- /dev/null +++ b/drivers/leds/leds-lp8501.c @@ -0,0 +1,411 @@ +/* + * TI LP8501 9 channel LED Driver + * + * Copyright (C) 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + */ + +#include <linux/delay.h> +#include <linux/firmware.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_data/leds-lp55xx.h> +#include <linux/slab.h> + +#include "leds-lp55xx-common.h" + +#define LP8501_PROGRAM_LENGTH 32 +#define LP8501_MAX_LEDS 9 + +/* Registers */ +#define LP8501_REG_ENABLE 0x00 +#define LP8501_ENABLE BIT(6) +#define LP8501_EXEC_M 0x3F +#define LP8501_EXEC_ENG1_M 0x30 +#define LP8501_EXEC_ENG2_M 0x0C +#define LP8501_EXEC_ENG3_M 0x03 +#define LP8501_RUN_ENG1 0x20 +#define LP8501_RUN_ENG2 0x08 +#define LP8501_RUN_ENG3 0x02 + +#define LP8501_REG_OP_MODE 0x01 +#define LP8501_MODE_ENG1_M 0x30 +#define LP8501_MODE_ENG2_M 0x0C +#define LP8501_MODE_ENG3_M 0x03 +#define LP8501_LOAD_ENG1 0x10 +#define LP8501_LOAD_ENG2 0x04 +#define LP8501_LOAD_ENG3 0x01 + +#define LP8501_REG_PWR_CONFIG 0x05 +#define LP8501_PWR_CONFIG_M 0x03 + +#define LP8501_REG_LED_PWM_BASE 0x16 + +#define LP8501_REG_LED_CURRENT_BASE 0x26 + +#define LP8501_REG_CONFIG 0x36 +#define LP8501_PWM_PSAVE BIT(7) +#define LP8501_AUTO_INC BIT(6) +#define LP8501_PWR_SAVE BIT(5) +#define LP8501_CP_AUTO 0x18 +#define LP8501_INT_CLK BIT(0) +#define LP8501_DEFAULT_CFG \ + (LP8501_PWM_PSAVE | LP8501_AUTO_INC | LP8501_PWR_SAVE | LP8501_CP_AUTO) + +#define LP8501_REG_RESET 0x3D +#define LP8501_RESET 0xFF + +#define LP8501_REG_PROG_PAGE_SEL 0x4F +#define LP8501_PAGE_ENG1 0 +#define LP8501_PAGE_ENG2 1 +#define LP8501_PAGE_ENG3 2 + +#define LP8501_REG_PROG_MEM 0x50 + +#define LP8501_ENG1_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG1_M) == LP8501_LOAD_ENG1) +#define LP8501_ENG2_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG2_M) == LP8501_LOAD_ENG2) +#define LP8501_ENG3_IS_LOADING(mode) \ + ((mode & LP8501_MODE_ENG3_M) == LP8501_LOAD_ENG3) + +static inline void lp8501_wait_opmode_done(void) +{ + usleep_range(1000, 2000); +} + +static void lp8501_set_led_current(struct lp55xx_led *led, u8 led_current) +{ + led->led_current = led_current; + lp55xx_write(led->chip, LP8501_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); +} + +static int lp8501_post_init_device(struct lp55xx_chip *chip) +{ + int ret; + u8 val = LP8501_DEFAULT_CFG; + + ret = lp55xx_write(chip, LP8501_REG_ENABLE, LP8501_ENABLE); + if (ret) + return ret; + + /* Chip startup time is 500 us, 1 - 2 ms gives some margin */ + usleep_range(1000, 2000); + + if (chip->pdata->clock_mode != LP55XX_CLOCK_EXT) + val |= LP8501_INT_CLK; + + ret = lp55xx_write(chip, LP8501_REG_CONFIG, val); + if (ret) + return ret; + + /* Power selection for each output */ + return lp55xx_update_bits(chip, LP8501_REG_PWR_CONFIG, + LP8501_PWR_CONFIG_M, chip->pdata->pwr_sel); +} + +static void lp8501_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP8501_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP8501_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP8501_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP8501_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP8501_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP8501_LOAD_ENG3, + }; + + u8 page_sel[] = { + [LP55XX_ENGINE_1] = LP8501_PAGE_ENG1, + [LP55XX_ENGINE_2] = LP8501_PAGE_ENG2, + [LP55XX_ENGINE_3] = LP8501_PAGE_ENG3, + }; + + lp55xx_update_bits(chip, LP8501_REG_OP_MODE, mask[idx], val[idx]); + + lp8501_wait_opmode_done(); + + lp55xx_write(chip, LP8501_REG_PROG_PAGE_SEL, page_sel[idx]); +} + +static void lp8501_stop_engine(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP8501_REG_OP_MODE, 0); + lp8501_wait_opmode_done(); +} + +static void lp8501_turn_off_channels(struct lp55xx_chip *chip) +{ + int i; + + for (i = 0; i < LP8501_MAX_LEDS; i++) + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + i, 0); +} + +static void lp8501_run_engine(struct lp55xx_chip *chip, bool start) +{ + int ret; + u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp8501_stop_engine(chip); + lp8501_turn_off_channels(chip); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP8501_REG_OP_MODE, &mode); + if (ret) + return; + + ret = lp55xx_read(chip, LP8501_REG_ENABLE, &exec); + if (ret) + return; + + /* change operation mode to RUN only when each engine is loading */ + if (LP8501_ENG1_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG1_M) | LP8501_RUN_ENG1; + exec = (exec & ~LP8501_EXEC_ENG1_M) | LP8501_RUN_ENG1; + } + + if (LP8501_ENG2_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG2_M) | LP8501_RUN_ENG2; + exec = (exec & ~LP8501_EXEC_ENG2_M) | LP8501_RUN_ENG2; + } + + if (LP8501_ENG3_IS_LOADING(mode)) { + mode = (mode & ~LP8501_MODE_ENG3_M) | LP8501_RUN_ENG3; + exec = (exec & ~LP8501_EXEC_ENG3_M) | LP8501_RUN_ENG3; + } + + lp55xx_write(chip, LP8501_REG_OP_MODE, mode); + lp8501_wait_opmode_done(); + + lp55xx_update_bits(chip, LP8501_REG_ENABLE, LP8501_EXEC_M, exec); +} + +static int lp8501_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) +{ + u8 pattern[LP8501_PROGRAM_LENGTH] = {0}; + unsigned cmd; + char c[3]; + int update_size; + int nrchars; + int offset = 0; + int ret; + int i; + + /* clear program memory before updating */ + for (i = 0; i < LP8501_PROGRAM_LENGTH; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, 0); + + i = 0; + while ((offset < size - 1) && (i < LP8501_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; + + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; + + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } + + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; + + update_size = i; + for (i = 0; i < update_size; i++) + lp55xx_write(chip, LP8501_REG_PROG_MEM + i, pattern[i]); + + return 0; + +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; +} + +static void lp8501_firmware_loaded(struct lp55xx_chip *chip) +{ + const struct firmware *fw = chip->fw; + + if (fw->size > LP8501_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } + + /* + * Program memory sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ + + lp8501_load_engine(chip); + lp8501_update_program_memory(chip, fw->data, fw->size); +} + +static void lp8501_led_brightness_work(struct work_struct *work) +{ + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; + + mutex_lock(&chip->lock); + lp55xx_write(chip, LP8501_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); + mutex_unlock(&chip->lock); +} + +/* Chip specific configurations */ +static struct lp55xx_device_config lp8501_cfg = { + .reset = { + .addr = LP8501_REG_RESET, + .val = LP8501_RESET, + }, + .enable = { + .addr = LP8501_REG_ENABLE, + .val = LP8501_ENABLE, + }, + .max_channel = LP8501_MAX_LEDS, + .post_init_device = lp8501_post_init_device, + .brightness_work_fn = lp8501_led_brightness_work, + .set_led_current = lp8501_set_led_current, + .firmware_cb = lp8501_firmware_loaded, + .run_engine = lp8501_run_engine, +}; + +static int lp8501_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata; + struct device_node *np = client->dev.of_node; + + if (!dev_get_platdata(&client->dev)) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = dev_get_platdata(&client->dev); + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; + + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp8501_cfg; + + mutex_init(&chip->lock); + + i2c_set_clientdata(client, led); + + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; + + dev_info(&client->dev, "%s Programmable led chip found\n", id->name); + + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; + + ret = lp55xx_register_sysfs(chip); + if (ret) { + dev_err(&client->dev, "registering sysfs failed\n"); + goto err_register_sysfs; + } + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: + return ret; +} + +static int lp8501_remove(struct i2c_client *client) +{ + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; + + lp8501_stop_engine(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); + + return 0; +} + +static const struct i2c_device_id lp8501_id[] = { + { "lp8501", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, lp8501_id); + +#ifdef CONFIG_OF +static const struct of_device_id of_lp8501_leds_match[] = { + { .compatible = "ti,lp8501", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp8501_leds_match); +#endif + +static struct i2c_driver lp8501_driver = { + .driver = { + .name = "lp8501", + .of_match_table = of_match_ptr(of_lp8501_leds_match), + }, + .probe = lp8501_probe, + .remove = lp8501_remove, + .id_table = lp8501_id, +}; + +module_i2c_driver(lp8501_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8501 LED drvier"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp8788.c b/drivers/leds/leds-lp8788.c new file mode 100644 index 00000000000..7c2cb384e7a --- /dev/null +++ b/drivers/leds/leds-lp8788.c @@ -0,0 +1,194 @@ +/* + * TI LP8788 MFD - keyled driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/mutex.h> +#include <linux/mfd/lp8788.h> +#include <linux/mfd/lp8788-isink.h> + +#define MAX_BRIGHTNESS LP8788_ISINK_MAX_PWM +#define DEFAULT_LED_NAME "keyboard-backlight" + +struct lp8788_led { + struct lp8788 *lp; + struct mutex lock; + struct work_struct work; + struct led_classdev led_dev; + enum lp8788_isink_number isink_num; + enum led_brightness brightness; + int on; +}; + +struct lp8788_led_config { + enum lp8788_isink_scale scale; + enum lp8788_isink_number num; + int iout; +}; + +static struct lp8788_led_config default_led_config = { + .scale = LP8788_ISINK_SCALE_100mA, + .num = LP8788_ISINK_3, + .iout = 0, +}; + +static int lp8788_led_init_device(struct lp8788_led *led, + struct lp8788_led_platform_data *pdata) +{ + struct lp8788_led_config *cfg = &default_led_config; + u8 addr, mask, val; + int ret; + + if (pdata) { + cfg->scale = pdata->scale; + cfg->num = pdata->num; + cfg->iout = pdata->iout_code; + } + + led->isink_num = cfg->num; + + /* scale configuration */ + addr = LP8788_ISINK_CTRL; + mask = 1 << (cfg->num + LP8788_ISINK_SCALE_OFFSET); + val = cfg->scale << (cfg->num + LP8788_ISINK_SCALE_OFFSET); + ret = lp8788_update_bits(led->lp, addr, mask, val); + if (ret) + return ret; + + /* current configuration */ + addr = lp8788_iout_addr[cfg->num]; + mask = lp8788_iout_mask[cfg->num]; + val = cfg->iout; + + return lp8788_update_bits(led->lp, addr, mask, val); +} + +static void lp8788_led_enable(struct lp8788_led *led, + enum lp8788_isink_number num, int on) +{ + u8 mask = 1 << num; + u8 val = on << num; + + if (lp8788_update_bits(led->lp, LP8788_ISINK_CTRL, mask, val)) + return; + + led->on = on; +} + +static void lp8788_led_work(struct work_struct *work) +{ + struct lp8788_led *led = container_of(work, struct lp8788_led, work); + enum lp8788_isink_number num = led->isink_num; + int enable; + u8 val = led->brightness; + + mutex_lock(&led->lock); + + switch (num) { + case LP8788_ISINK_1: + case LP8788_ISINK_2: + case LP8788_ISINK_3: + lp8788_write_byte(led->lp, lp8788_pwm_addr[num], val); + break; + default: + mutex_unlock(&led->lock); + return; + } + + enable = (val > 0) ? 1 : 0; + if (enable != led->on) + lp8788_led_enable(led, num, enable); + + mutex_unlock(&led->lock); +} + +static void lp8788_brightness_set(struct led_classdev *led_cdev, + enum led_brightness brt_val) +{ + struct lp8788_led *led = + container_of(led_cdev, struct lp8788_led, led_dev); + + led->brightness = brt_val; + schedule_work(&led->work); +} + +static int lp8788_led_probe(struct platform_device *pdev) +{ + struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent); + struct lp8788_led_platform_data *led_pdata; + struct lp8788_led *led; + struct device *dev = &pdev->dev; + int ret; + + led = devm_kzalloc(dev, sizeof(struct lp8788_led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->lp = lp; + led->led_dev.max_brightness = MAX_BRIGHTNESS; + led->led_dev.brightness_set = lp8788_brightness_set; + + led_pdata = lp->pdata ? lp->pdata->led_pdata : NULL; + + if (!led_pdata || !led_pdata->name) + led->led_dev.name = DEFAULT_LED_NAME; + else + led->led_dev.name = led_pdata->name; + + mutex_init(&led->lock); + INIT_WORK(&led->work, lp8788_led_work); + + platform_set_drvdata(pdev, led); + + ret = lp8788_led_init_device(led, led_pdata); + if (ret) { + dev_err(dev, "led init device err: %d\n", ret); + return ret; + } + + ret = led_classdev_register(dev, &led->led_dev); + if (ret) { + dev_err(dev, "led register err: %d\n", ret); + return ret; + } + + return 0; +} + +static int lp8788_led_remove(struct platform_device *pdev) +{ + struct lp8788_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->led_dev); + flush_work(&led->work); + + return 0; +} + +static struct platform_driver lp8788_led_driver = { + .probe = lp8788_led_probe, + .remove = lp8788_led_remove, + .driver = { + .name = LP8788_DEV_KEYLED, + .owner = THIS_MODULE, + }, +}; +module_platform_driver(lp8788_led_driver); + +MODULE_DESCRIPTION("Texas Instruments LP8788 Keyboard LED Driver"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:lp8788-keyled"); diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c index e311a96c446..059f5b1f355 100644 --- a/drivers/leds/leds-lt3593.c +++ b/drivers/leds/leds-lt3593.c @@ -17,7 +17,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/workqueue.h> @@ -82,22 +81,18 @@ static void lt3593_led_set(struct led_classdev *led_cdev, schedule_work(&led_dat->work); } -static int __devinit create_lt3593_led(const struct gpio_led *template, +static int create_lt3593_led(const struct gpio_led *template, struct lt3593_led_data *led_dat, struct device *parent) { int ret, state; /* skip leds on GPIOs that aren't available */ if (!gpio_is_valid(template->gpio)) { - printk(KERN_INFO "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n", + dev_info(parent, "%s: skipping unavailable LT3593 LED at gpio %d (%s)\n", KBUILD_MODNAME, template->gpio, template->name); return 0; } - ret = gpio_request(template->gpio, template->name); - if (ret < 0) - return ret; - led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; led_dat->gpio = template->gpio; @@ -110,24 +105,22 @@ static int __devinit create_lt3593_led(const struct gpio_led *template, if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = gpio_direction_output(led_dat->gpio, state); + ret = devm_gpio_request_one(parent, template->gpio, state ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + template->name); if (ret < 0) - goto err; + return ret; INIT_WORK(&led_dat->work, lt3593_led_work); ret = led_classdev_register(parent, &led_dat->cdev); if (ret < 0) - goto err; + return ret; - printk(KERN_INFO "%s: registered LT3593 LED '%s' at GPIO %d\n", + dev_info(parent, "%s: registered LT3593 LED '%s' at GPIO %d\n", KBUILD_MODNAME, template->name, template->gpio); return 0; - -err: - gpio_free(led_dat->gpio); - return ret; } static void delete_lt3593_led(struct lt3593_led_data *led) @@ -137,20 +130,20 @@ static void delete_lt3593_led(struct lt3593_led_data *led) led_classdev_unregister(&led->cdev); cancel_work_sync(&led->work); - gpio_free(led->gpio); } -static int __devinit lt3593_led_probe(struct platform_device *pdev) +static int lt3593_led_probe(struct platform_device *pdev) { - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct lt3593_led_data *leds_data; int i, ret = 0; if (!pdata) return -EBUSY; - leds_data = kzalloc(sizeof(struct lt3593_led_data) * pdata->num_leds, - GFP_KERNEL); + leds_data = devm_kzalloc(&pdev->dev, + sizeof(struct lt3593_led_data) * pdata->num_leds, + GFP_KERNEL); if (!leds_data) return -ENOMEM; @@ -169,15 +162,13 @@ err: for (i = i - 1; i >= 0; i--) delete_lt3593_led(&leds_data[i]); - kfree(leds_data); - return ret; } -static int __devexit lt3593_led_remove(struct platform_device *pdev) +static int lt3593_led_remove(struct platform_device *pdev) { int i; - struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct lt3593_led_data *leds_data; leds_data = platform_get_drvdata(pdev); @@ -185,14 +176,12 @@ static int __devexit lt3593_led_remove(struct platform_device *pdev) for (i = 0; i < pdata->num_leds; i++) delete_lt3593_led(&leds_data[i]); - kfree(leds_data); - return 0; } static struct platform_driver lt3593_led_driver = { .probe = lt3593_led_probe, - .remove = __devexit_p(lt3593_led_remove), + .remove = lt3593_led_remove, .driver = { .name = "leds-lt3593", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-max8997.c b/drivers/leds/leds-max8997.c index f4c0e37fad1..f449a8bdddc 100644 --- a/drivers/leds/leds-max8997.c +++ b/drivers/leds/leds-max8997.c @@ -49,71 +49,37 @@ struct max8997_led { struct mutex mutex; }; -static void max8997_led_clear_mode(struct max8997_led *led, - enum max8997_led_mode mode) -{ - struct i2c_client *client = led->iodev->i2c; - u8 val = 0, mask = 0; - int ret; - - switch (mode) { - case MAX8997_FLASH_MODE: - mask = led->id ? - MAX8997_LED1_FLASH_MASK : MAX8997_LED0_FLASH_MASK; - break; - case MAX8997_MOVIE_MODE: - mask = led->id ? - MAX8997_LED1_MOVIE_MASK : MAX8997_LED0_MOVIE_MASK; - break; - case MAX8997_FLASH_PIN_CONTROL_MODE: - mask = led->id ? - MAX8997_LED1_FLASH_PIN_MASK : MAX8997_LED0_FLASH_PIN_MASK; - break; - case MAX8997_MOVIE_PIN_CONTROL_MODE: - mask = led->id ? - MAX8997_LED1_MOVIE_PIN_MASK : MAX8997_LED0_MOVIE_PIN_MASK; - break; - default: - 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); - } -} - 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; - - /* First, clear the previous mode */ - max8997_led_clear_mode(led, led->led_mode); + u8 mask = 0, val; switch (mode) { case MAX8997_FLASH_MODE: - mask = led->id ? + 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 = led->id ? + 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 = led->id ? + 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 = led->id ? + 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; @@ -123,8 +89,8 @@ static void max8997_led_set_mode(struct max8997_led *led, } if (mask) { - ret = max8997_update_reg(client, - MAX8997_REG_LEN_CNTL, mask, 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); @@ -263,7 +229,7 @@ static ssize_t max8997_led_store_mode(struct device *dev, static DEVICE_ATTR(mode, 0644, max8997_led_show_mode, max8997_led_store_mode); -static int __devinit max8997_led_probe(struct platform_device *pdev) +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); @@ -276,11 +242,9 @@ static int __devinit max8997_led_probe(struct platform_device *pdev) return -ENODEV; } - led = kzalloc(sizeof(*led), GFP_KERNEL); - if (led == NULL) { - ret = -ENOMEM; - goto err_mem; - } + 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); @@ -315,32 +279,25 @@ static int __devinit max8997_led_probe(struct platform_device *pdev) ret = led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) - goto err_led; + return ret; ret = device_create_file(led->cdev.dev, &dev_attr_mode); if (ret != 0) { dev_err(&pdev->dev, "failed to create file: %d\n", ret); - goto err_file; + led_classdev_unregister(&led->cdev); + return ret; } return 0; - -err_file: - led_classdev_unregister(&led->cdev); -err_led: - kfree(led); -err_mem: - return ret; } -static int __devexit max8997_led_remove(struct platform_device *pdev) +static int max8997_led_remove(struct platform_device *pdev) { struct max8997_led *led = platform_get_drvdata(pdev); device_remove_file(led->cdev.dev, &dev_attr_mode); led_classdev_unregister(&led->cdev); - kfree(led); return 0; } @@ -351,20 +308,10 @@ static struct platform_driver max8997_led_driver = { .owner = THIS_MODULE, }, .probe = max8997_led_probe, - .remove = __devexit_p(max8997_led_remove), + .remove = max8997_led_remove, }; -static int __init max8997_led_init(void) -{ - return platform_driver_register(&max8997_led_driver); -} -module_init(max8997_led_init); - -static void __exit max8997_led_exit(void) -{ - platform_driver_unregister(&max8997_led_driver); -} -module_exit(max8997_led_exit); +module_platform_driver(max8997_led_driver); MODULE_AUTHOR("Donggeun Kim <dg77.kim@samsung.com>"); MODULE_DESCRIPTION("MAX8997 LED driver"); diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index 8bc49154155..f1db88e2513 100644 --- a/drivers/leds/leds-mc13783.c +++ b/drivers/leds/leds-mc13783.c @@ -1,5 +1,5 @@ /* - * LEDs driver for Freescale MC13783 + * LEDs driver for Freescale MC13783/MC13892/MC34708 * * Copyright (C) 2010 Philippe Rétornaz * @@ -17,143 +17,56 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> +#include <linux/of.h> #include <linux/workqueue.h> #include <linux/mfd/mc13xxx.h> -#include <linux/slab.h> -struct mc13783_led { +struct mc13xxx_led_devtype { + int led_min; + int led_max; + int num_regs; + u32 ledctrl_base; +}; + +struct mc13xxx_led { struct led_classdev cdev; struct work_struct work; - struct mc13xxx *master; enum led_brightness new_brightness; int id; + struct mc13xxx_leds *leds; }; -#define MC13783_REG_LED_CONTROL_0 51 -#define MC13783_LED_C0_ENABLE_BIT (1 << 0) -#define MC13783_LED_C0_TRIODE_MD_BIT (1 << 7) -#define MC13783_LED_C0_TRIODE_AD_BIT (1 << 8) -#define MC13783_LED_C0_TRIODE_KP_BIT (1 << 9) -#define MC13783_LED_C0_BOOST_BIT (1 << 10) -#define MC13783_LED_C0_ABMODE_MASK 0x7 -#define MC13783_LED_C0_ABMODE 11 -#define MC13783_LED_C0_ABREF_MASK 0x3 -#define MC13783_LED_C0_ABREF 14 - -#define MC13783_REG_LED_CONTROL_1 52 -#define MC13783_LED_C1_TC1HALF_BIT (1 << 18) - -#define MC13783_REG_LED_CONTROL_2 53 -#define MC13783_LED_C2_BL_P_MASK 0xf -#define MC13783_LED_C2_MD_P 9 -#define MC13783_LED_C2_AD_P 13 -#define MC13783_LED_C2_KP_P 17 -#define MC13783_LED_C2_BL_C_MASK 0x7 -#define MC13783_LED_C2_MD_C 0 -#define MC13783_LED_C2_AD_C 3 -#define MC13783_LED_C2_KP_C 6 - -#define MC13783_REG_LED_CONTROL_3 54 -#define MC13783_LED_C3_TC_P 6 -#define MC13783_LED_C3_TC_P_MASK 0x1f - -#define MC13783_REG_LED_CONTROL_4 55 -#define MC13783_REG_LED_CONTROL_5 56 - -#define MC13783_LED_Cx_PERIOD 21 -#define MC13783_LED_Cx_PERIOD_MASK 0x3 -#define MC13783_LED_Cx_SLEWLIM_BIT (1 << 23) -#define MC13783_LED_Cx_TRIODE_TC_BIT (1 << 23) -#define MC13783_LED_Cx_TC_C_MASK 0x3 - -static void mc13783_led_work(struct work_struct *work) -{ - struct mc13783_led *led = container_of(work, struct mc13783_led, work); - int reg = 0; - int mask = 0; - int value = 0; - int bank, off, shift; - - switch (led->id) { - case MC13783_LED_MD: - reg = MC13783_REG_LED_CONTROL_2; - mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_MD_P; - value = (led->new_brightness >> 4) << MC13783_LED_C2_MD_P; - break; - case MC13783_LED_AD: - reg = MC13783_REG_LED_CONTROL_2; - mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_AD_P; - value = (led->new_brightness >> 4) << MC13783_LED_C2_AD_P; - break; - case MC13783_LED_KP: - reg = MC13783_REG_LED_CONTROL_2; - mask = MC13783_LED_C2_BL_P_MASK << MC13783_LED_C2_KP_P; - value = (led->new_brightness >> 4) << MC13783_LED_C2_KP_P; - break; - case MC13783_LED_R1: - case MC13783_LED_G1: - case MC13783_LED_B1: - case MC13783_LED_R2: - case MC13783_LED_G2: - case MC13783_LED_B2: - case MC13783_LED_R3: - case MC13783_LED_G3: - case MC13783_LED_B3: - off = led->id - MC13783_LED_R1; - bank = off/3; - reg = MC13783_REG_LED_CONTROL_3 + off/3; - shift = (off - bank * 3) * 5 + MC13783_LED_C3_TC_P; - value = (led->new_brightness >> 3) << shift; - mask = MC13783_LED_C3_TC_P_MASK << shift; - break; - } - - mc13xxx_lock(led->master); - - mc13xxx_reg_rmw(led->master, reg, mask, value); - - mc13xxx_unlock(led->master); -} +struct mc13xxx_leds { + struct mc13xxx *master; + struct mc13xxx_led_devtype *devtype; + int num_leds; + struct mc13xxx_led *led; +}; -static void mc13783_led_set(struct led_classdev *led_cdev, - enum led_brightness value) +static unsigned int mc13xxx_max_brightness(int id) { - struct mc13783_led *led; + if (id >= MC13783_LED_MD && id <= MC13783_LED_KP) + return 0x0f; + else if (id >= MC13783_LED_R1 && id <= MC13783_LED_B3) + return 0x1f; - led = container_of(led_cdev, struct mc13783_led, cdev); - led->new_brightness = value; - schedule_work(&led->work); + return 0x3f; } -static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current) +static void mc13xxx_led_work(struct work_struct *work) { - int shift = 0; - int mask = 0; - int value = 0; - int reg = 0; - int ret, bank; + struct mc13xxx_led *led = container_of(work, struct mc13xxx_led, work); + struct mc13xxx_leds *leds = led->leds; + unsigned int reg, bank, off, shift; switch (led->id) { case MC13783_LED_MD: - shift = MC13783_LED_C2_MD_C; - mask = MC13783_LED_C2_BL_C_MASK; - value = max_current & MC13783_LED_C2_BL_C_MASK; - reg = MC13783_REG_LED_CONTROL_2; - break; case MC13783_LED_AD: - shift = MC13783_LED_C2_AD_C; - mask = MC13783_LED_C2_BL_C_MASK; - value = max_current & MC13783_LED_C2_BL_C_MASK; - reg = MC13783_REG_LED_CONTROL_2; - break; case MC13783_LED_KP: - shift = MC13783_LED_C2_KP_C; - mask = MC13783_LED_C2_BL_C_MASK; - value = max_current & MC13783_LED_C2_BL_C_MASK; - reg = MC13783_REG_LED_CONTROL_2; + reg = 2; + shift = 9 + (led->id - MC13783_LED_MD) * 4; break; case MC13783_LED_R1: case MC13783_LED_G1: @@ -164,230 +77,261 @@ static int __devinit mc13783_led_setup(struct mc13783_led *led, int max_current) case MC13783_LED_R3: case MC13783_LED_G3: case MC13783_LED_B3: - bank = (led->id - MC13783_LED_R1)/3; - reg = MC13783_REG_LED_CONTROL_3 + bank; - shift = ((led->id - MC13783_LED_R1) - bank * 3) * 2; - mask = MC13783_LED_Cx_TC_C_MASK; - value = max_current & MC13783_LED_Cx_TC_C_MASK; + off = led->id - MC13783_LED_R1; + bank = off / 3; + reg = 3 + bank; + shift = (off - bank * 3) * 5 + 6; + break; + case MC13892_LED_MD: + case MC13892_LED_AD: + case MC13892_LED_KP: + reg = (led->id - MC13892_LED_MD) / 2; + shift = 3 + (led->id - MC13892_LED_MD) * 12; + break; + case MC13892_LED_R: + case MC13892_LED_G: + case MC13892_LED_B: + off = led->id - MC13892_LED_R; + bank = off / 2; + reg = 2 + bank; + shift = (off - bank * 2) * 12 + 3; break; + case MC34708_LED_R: + case MC34708_LED_G: + reg = 0; + shift = 3 + (led->id - MC34708_LED_R) * 12; + break; + default: + BUG(); } - mc13xxx_lock(led->master); + mc13xxx_reg_rmw(leds->master, leds->devtype->ledctrl_base + reg, + mc13xxx_max_brightness(led->id) << shift, + led->new_brightness << shift); +} - ret = mc13xxx_reg_rmw(led->master, reg, mask << shift, - value << shift); +static void mc13xxx_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct mc13xxx_led *led = + container_of(led_cdev, struct mc13xxx_led, cdev); - mc13xxx_unlock(led->master); - return ret; + led->new_brightness = value; + schedule_work(&led->work); } -static int __devinit mc13783_leds_prepare(struct platform_device *pdev) +#ifdef CONFIG_OF +static struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( + struct platform_device *pdev) { - struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct mc13xxx *dev = dev_get_drvdata(pdev->dev.parent); - int ret = 0; - int reg = 0; + struct mc13xxx_leds *leds = platform_get_drvdata(pdev); + struct mc13xxx_leds_platform_data *pdata; + struct device_node *parent, *child; + struct device *dev = &pdev->dev; + int i = 0, ret = -ENODATA; - mc13xxx_lock(dev); + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); - if (pdata->flags & MC13783_LED_TC1HALF) - reg |= MC13783_LED_C1_TC1HALF_BIT; + of_node_get(dev->parent->of_node); - if (pdata->flags & MC13783_LED_SLEWLIMTC) - reg |= MC13783_LED_Cx_SLEWLIM_BIT; + parent = of_find_node_by_name(dev->parent->of_node, "leds"); + if (!parent) + goto out_node_put; - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_1, reg); + ret = of_property_read_u32_array(parent, "led-control", + pdata->led_control, + leds->devtype->num_regs); if (ret) - goto out; + goto out_node_put; - reg = (pdata->bl_period & MC13783_LED_Cx_PERIOD_MASK) << - MC13783_LED_Cx_PERIOD; + pdata->num_leds = of_get_child_count(parent); - if (pdata->flags & MC13783_LED_SLEWLIMBL) - reg |= MC13783_LED_Cx_SLEWLIM_BIT; + pdata->led = devm_kzalloc(dev, pdata->num_leds * sizeof(*pdata->led), + GFP_KERNEL); + if (!pdata->led) { + ret = -ENOMEM; + goto out_node_put; + } - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_2, reg); - if (ret) - goto out; + for_each_child_of_node(parent, child) { + const char *str; + u32 tmp; - reg = (pdata->tc1_period & MC13783_LED_Cx_PERIOD_MASK) << - MC13783_LED_Cx_PERIOD; + if (of_property_read_u32(child, "reg", &tmp)) + continue; + pdata->led[i].id = leds->devtype->led_min + tmp; - if (pdata->flags & MC13783_LED_TRIODE_TC1) - reg |= MC13783_LED_Cx_TRIODE_TC_BIT; + if (!of_property_read_string(child, "label", &str)) + pdata->led[i].name = str; + if (!of_property_read_string(child, "linux,default-trigger", + &str)) + pdata->led[i].default_trigger = str; - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_3, reg); - if (ret) - goto out; + i++; + } - reg = (pdata->tc2_period & MC13783_LED_Cx_PERIOD_MASK) << - MC13783_LED_Cx_PERIOD; + pdata->num_leds = i; + ret = i > 0 ? 0 : -ENODATA; - if (pdata->flags & MC13783_LED_TRIODE_TC2) - reg |= MC13783_LED_Cx_TRIODE_TC_BIT; +out_node_put: + of_node_put(parent); - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_4, reg); - if (ret) - goto out; + return ret ? ERR_PTR(ret) : pdata; +} +#else +static inline struct mc13xxx_leds_platform_data __init *mc13xxx_led_probe_dt( + struct platform_device *pdev) +{ + return ERR_PTR(-ENOSYS); +} +#endif - reg = (pdata->tc3_period & MC13783_LED_Cx_PERIOD_MASK) << - MC13783_LED_Cx_PERIOD; +static int __init mc13xxx_led_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(dev); + struct mc13xxx *mcdev = dev_get_drvdata(dev->parent); + struct mc13xxx_led_devtype *devtype = + (struct mc13xxx_led_devtype *)pdev->id_entry->driver_data; + struct mc13xxx_leds *leds; + int i, id, ret = -ENODATA; + u32 init_led = 0; + + leds = devm_kzalloc(dev, sizeof(*leds), GFP_KERNEL); + if (!leds) + return -ENOMEM; - if (pdata->flags & MC13783_LED_TRIODE_TC3) - reg |= MC13783_LED_Cx_TRIODE_TC_BIT; + leds->devtype = devtype; + leds->master = mcdev; + platform_set_drvdata(pdev, leds); - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_5, reg); - if (ret) - goto out; - - reg = MC13783_LED_C0_ENABLE_BIT; - if (pdata->flags & MC13783_LED_TRIODE_MD) - reg |= MC13783_LED_C0_TRIODE_MD_BIT; - if (pdata->flags & MC13783_LED_TRIODE_AD) - reg |= MC13783_LED_C0_TRIODE_AD_BIT; - if (pdata->flags & MC13783_LED_TRIODE_KP) - reg |= MC13783_LED_C0_TRIODE_KP_BIT; - if (pdata->flags & MC13783_LED_BOOST_EN) - reg |= MC13783_LED_C0_BOOST_BIT; - - reg |= (pdata->abmode & MC13783_LED_C0_ABMODE_MASK) << - MC13783_LED_C0_ABMODE; - reg |= (pdata->abref & MC13783_LED_C0_ABREF_MASK) << - MC13783_LED_C0_ABREF; - - ret = mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_0, reg); - -out: - mc13xxx_unlock(dev); - return ret; -} + if (dev->parent->of_node) { + pdata = mc13xxx_led_probe_dt(pdev); + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + } else if (!pdata) + return -ENODATA; -static int __devinit mc13783_led_probe(struct platform_device *pdev) -{ - struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct mc13xxx_led_platform_data *led_cur; - struct mc13783_led *led, *led_dat; - int ret, i; - int init_led = 0; - - if (pdata == NULL) { - dev_err(&pdev->dev, "missing platform data\n"); - return -ENODEV; - } + leds->num_leds = pdata->num_leds; - if (pdata->num_leds < 1 || pdata->num_leds > (MC13783_LED_MAX + 1)) { - dev_err(&pdev->dev, "Invalid led count %d\n", pdata->num_leds); + if ((leds->num_leds < 1) || + (leds->num_leds > (devtype->led_max - devtype->led_min + 1))) { + dev_err(dev, "Invalid LED count %d\n", leds->num_leds); return -EINVAL; } - led = kzalloc(sizeof(*led) * pdata->num_leds, GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "failed to alloc memory\n"); + leds->led = devm_kzalloc(dev, leds->num_leds * sizeof(*leds->led), + GFP_KERNEL); + if (!leds->led) return -ENOMEM; - } - ret = mc13783_leds_prepare(pdev); - if (ret) { - dev_err(&pdev->dev, "unable to init led driver\n"); - goto err_free; + for (i = 0; i < devtype->num_regs; i++) { + ret = mc13xxx_reg_write(mcdev, leds->devtype->ledctrl_base + i, + pdata->led_control[i]); + if (ret) + return ret; } - for (i = 0; i < pdata->num_leds; i++) { - led_dat = &led[i]; - led_cur = &pdata->led[i]; + for (i = 0; i < leds->num_leds; i++) { + const char *name, *trig; - if (led_cur->id > MC13783_LED_MAX || led_cur->id < 0) { - dev_err(&pdev->dev, "invalid id %d\n", led_cur->id); - ret = -EINVAL; - goto err_register; - } + ret = -EINVAL; - if (init_led & (1 << led_cur->id)) { - dev_err(&pdev->dev, "led %d already initialized\n", - led_cur->id); - ret = -EINVAL; - goto err_register; + 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; } - init_led |= 1 << led_cur->id; - led_dat->cdev.name = led_cur->name; - led_dat->cdev.default_trigger = led_cur->default_trigger; - led_dat->cdev.brightness_set = mc13783_led_set; - led_dat->cdev.brightness = LED_OFF; - led_dat->id = led_cur->id; - led_dat->master = dev_get_drvdata(pdev->dev.parent); + if (init_led & (1 << id)) { + dev_warn(dev, "LED %i already initialized\n", id); + break; + } - INIT_WORK(&led_dat->work, mc13783_led_work); + init_led |= 1 << id; + leds->led[i].id = id; + leds->led[i].leds = leds; + leds->led[i].cdev.name = name; + leds->led[i].cdev.default_trigger = trig; + leds->led[i].cdev.flags = LED_CORE_SUSPENDRESUME; + leds->led[i].cdev.brightness_set = mc13xxx_led_set; + leds->led[i].cdev.max_brightness = mc13xxx_max_brightness(id); - ret = led_classdev_register(pdev->dev.parent, &led_dat->cdev); - if (ret) { - dev_err(&pdev->dev, "failed to register led %d\n", - led_dat->id); - goto err_register; - } + INIT_WORK(&leds->led[i].work, mc13xxx_led_work); - ret = mc13783_led_setup(led_dat, led_cur->max_current); + ret = led_classdev_register(dev->parent, &leds->led[i].cdev); if (ret) { - dev_err(&pdev->dev, "unable to init led %d\n", - led_dat->id); - i++; - goto err_register; + dev_err(dev, "Failed to register LED %i\n", id); + break; } } - platform_set_drvdata(pdev, led); - return 0; - -err_register: - for (i = i - 1; i >= 0; i--) { - led_classdev_unregister(&led[i].cdev); - cancel_work_sync(&led[i].work); - } + if (ret) + while (--i >= 0) { + led_classdev_unregister(&leds->led[i].cdev); + cancel_work_sync(&leds->led[i].work); + } -err_free: - kfree(led); return ret; } -static int __devexit mc13783_led_remove(struct platform_device *pdev) +static int mc13xxx_led_remove(struct platform_device *pdev) { - struct mc13xxx_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); - struct mc13783_led *led = platform_get_drvdata(pdev); - struct mc13xxx *dev = dev_get_drvdata(pdev->dev.parent); + struct mc13xxx_leds *leds = platform_get_drvdata(pdev); int i; - for (i = 0; i < pdata->num_leds; i++) { - led_classdev_unregister(&led[i].cdev); - cancel_work_sync(&led[i].work); + for (i = 0; i < leds->num_leds; i++) { + led_classdev_unregister(&leds->led[i].cdev); + cancel_work_sync(&leds->led[i].work); } - mc13xxx_lock(dev); + return 0; +} - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_0, 0); - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_1, 0); - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_2, 0); - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_3, 0); - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_4, 0); - mc13xxx_reg_write(dev, MC13783_REG_LED_CONTROL_5, 0); +static const struct mc13xxx_led_devtype mc13783_led_devtype = { + .led_min = MC13783_LED_MD, + .led_max = MC13783_LED_B3, + .num_regs = 6, + .ledctrl_base = 51, +}; - mc13xxx_unlock(dev); +static const struct mc13xxx_led_devtype mc13892_led_devtype = { + .led_min = MC13892_LED_MD, + .led_max = MC13892_LED_B, + .num_regs = 4, + .ledctrl_base = 51, +}; - kfree(led); - return 0; -} +static const struct mc13xxx_led_devtype mc34708_led_devtype = { + .led_min = MC34708_LED_R, + .led_max = MC34708_LED_G, + .num_regs = 1, + .ledctrl_base = 54, +}; + +static 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 mc13783_led_driver = { +static struct platform_driver mc13xxx_led_driver = { .driver = { - .name = "mc13783-led", + .name = "mc13xxx-led", .owner = THIS_MODULE, }, - .probe = mc13783_led_probe, - .remove = __devexit_p(mc13783_led_remove), + .remove = mc13xxx_led_remove, + .id_table = mc13xxx_led_id_table, }; +module_platform_driver_probe(mc13xxx_led_driver, mc13xxx_led_probe); -module_platform_driver(mc13783_led_driver); - -MODULE_DESCRIPTION("LEDs driver for Freescale MC13783 PMIC"); +MODULE_DESCRIPTION("LEDs driver for Freescale MC13XXX PMIC"); MODULE_AUTHOR("Philippe Retornaz <philippe.retornaz@epfl.ch>"); MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:mc13783-led"); diff --git a/drivers/leds/leds-net48xx.c b/drivers/leds/leds-net48xx.c index f117f7326c5..27d06c52824 100644 --- a/drivers/leds/leds-net48xx.c +++ b/drivers/leds/leds-net48xx.c @@ -15,7 +15,7 @@ #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/err.h> -#include <asm/io.h> +#include <linux/io.h> #include <linux/nsc_gpio.h> #include <linux/scx200_gpio.h> #include <linux/module.h> diff --git a/drivers/leds/leds-net5501.c b/drivers/leds/leds-net5501.c deleted file mode 100644 index 0555d4709a7..00000000000 --- a/drivers/leds/leds-net5501.c +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Soekris board support code - * - * Copyright (C) 2008-2009 Tower Technologies - * Written by Alessandro Zummo <a.zummo@towertech.it> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 - * as published by the Free Software Foundation. - */ - -#include <linux/kernel.h> -#include <linux/init.h> -#include <linux/io.h> -#include <linux/string.h> -#include <linux/leds.h> -#include <linux/platform_device.h> -#include <linux/gpio.h> -#include <linux/module.h> - -#include <asm/geode.h> - -static const struct gpio_led net5501_leds[] = { - { - .name = "error", - .gpio = 6, - .default_trigger = "default-on", - }, -}; - -static struct gpio_led_platform_data net5501_leds_data = { - .num_leds = ARRAY_SIZE(net5501_leds), - .leds = net5501_leds, -}; - -static struct platform_device net5501_leds_dev = { - .name = "leds-gpio", - .id = -1, - .dev.platform_data = &net5501_leds_data, -}; - -static void __init init_net5501(void) -{ - platform_device_register(&net5501_leds_dev); -} - -struct soekris_board { - u16 offset; - char *sig; - u8 len; - void (*init)(void); -}; - -static struct soekris_board __initdata boards[] = { - { 0xb7b, "net5501", 7, init_net5501 }, /* net5501 v1.33/1.33c */ - { 0xb1f, "net5501", 7, init_net5501 }, /* net5501 v1.32i */ -}; - -static int __init soekris_init(void) -{ - int i; - unsigned char *rombase, *bios; - - if (!is_geode()) - return 0; - - rombase = ioremap(0xffff0000, 0xffff); - if (!rombase) { - printk(KERN_INFO "Soekris net5501 LED driver failed to get rombase"); - return 0; - } - - bios = rombase + 0x20; /* null terminated */ - - if (strncmp(bios, "comBIOS", 7)) - goto unmap; - - for (i = 0; i < ARRAY_SIZE(boards); i++) { - unsigned char *model = rombase + boards[i].offset; - - if (strncmp(model, boards[i].sig, boards[i].len) == 0) { - printk(KERN_INFO "Soekris %s: %s\n", model, bios); - - if (boards[i].init) - boards[i].init(); - break; - } - } - -unmap: - iounmap(rombase); - return 0; -} - -arch_initcall(soekris_init); - -MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-netxbig.c b/drivers/leds/leds-netxbig.c index d8433f2d53b..e97f443a6e0 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -21,14 +21,13 @@ */ #include <linux/module.h> -#include <linux/init.h> #include <linux/irq.h> #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/platform_device.h> #include <linux/gpio.h> #include <linux/leds.h> -#include <mach/leds-netxbig.h> +#include <linux/platform_data/leds-kirkwood-netxbig.h> /* * GPIO extension bus. @@ -71,7 +70,7 @@ static void gpio_ext_set_value(struct netxbig_gpio_ext *gpio_ext, spin_unlock_irqrestore(&gpio_ext_lock, flags); } -static int __devinit gpio_ext_init(struct netxbig_gpio_ext *gpio_ext) +static int gpio_ext_init(struct netxbig_gpio_ext *gpio_ext) { int err; int i; @@ -112,7 +111,7 @@ err_free_addr: return err; } -static void __devexit gpio_ext_free(struct netxbig_gpio_ext *gpio_ext) +static void gpio_ext_free(struct netxbig_gpio_ext *gpio_ext) { int i; @@ -243,7 +242,7 @@ static ssize_t netxbig_led_sata_store(struct device *dev, int mode_val; int ret; - ret = strict_strtoul(buff, 10, &enable); + ret = kstrtoul(buff, 10, &enable); if (ret < 0) return ret; @@ -294,19 +293,19 @@ static ssize_t netxbig_led_sata_show(struct device *dev, static DEVICE_ATTR(sata, 0644, netxbig_led_sata_show, netxbig_led_sata_store); -static void __devexit delete_netxbig_led(struct netxbig_led_data *led_dat) +static void delete_netxbig_led(struct netxbig_led_data *led_dat) { if (led_dat->mode_val[NETXBIG_LED_SATA] != NETXBIG_LED_INVALID_MODE) device_remove_file(led_dat->cdev.dev, &dev_attr_sata); led_classdev_unregister(&led_dat->cdev); } -static int __devinit +static int create_netxbig_led(struct platform_device *pdev, struct netxbig_led_data *led_dat, const struct netxbig_led *template) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); int ret; spin_lock_init(&led_dat->lock); @@ -352,9 +351,9 @@ create_netxbig_led(struct platform_device *pdev, return ret; } -static int __devinit netxbig_led_probe(struct platform_device *pdev) +static int netxbig_led_probe(struct platform_device *pdev) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct netxbig_led_data *leds_data; int i; int ret; @@ -362,14 +361,14 @@ static int __devinit netxbig_led_probe(struct platform_device *pdev) if (!pdata) return -EINVAL; - leds_data = kzalloc(sizeof(struct netxbig_led_data) * pdata->num_leds, - GFP_KERNEL); + leds_data = devm_kzalloc(&pdev->dev, + sizeof(struct netxbig_led_data) * pdata->num_leds, GFP_KERNEL); if (!leds_data) return -ENOMEM; ret = gpio_ext_init(pdata->gpio_ext); if (ret < 0) - goto err_free_data; + return ret; for (i = 0; i < pdata->num_leds; i++) { ret = create_netxbig_led(pdev, &leds_data[i], &pdata->leds[i]); @@ -386,15 +385,12 @@ err_free_leds: delete_netxbig_led(&leds_data[i]); gpio_ext_free(pdata->gpio_ext); -err_free_data: - kfree(leds_data); - return ret; } -static int __devexit netxbig_led_remove(struct platform_device *pdev) +static int netxbig_led_remove(struct platform_device *pdev) { - struct netxbig_led_platform_data *pdata = pdev->dev.platform_data; + struct netxbig_led_platform_data *pdata = dev_get_platdata(&pdev->dev); struct netxbig_led_data *leds_data; int i; @@ -404,14 +400,13 @@ static int __devexit netxbig_led_remove(struct platform_device *pdev) delete_netxbig_led(&leds_data[i]); gpio_ext_free(pdata->gpio_ext); - kfree(leds_data); return 0; } static struct platform_driver netxbig_led_driver = { .probe = netxbig_led_probe, - .remove = __devexit_p(netxbig_led_remove), + .remove = netxbig_led_remove, .driver = { .name = "leds-netxbig", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index 2f0a14421a7..efa625883c8 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -23,13 +23,14 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/gpio.h> #include <linux/leds.h> #include <linux/module.h> -#include <mach/leds-ns2.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 @@ -149,7 +150,7 @@ static ssize_t ns2_led_sata_store(struct device *dev, unsigned long enable; enum ns2_led_modes mode; - ret = strict_strtoul(buff, 10, &enable); + ret = kstrtoul(buff, 10, &enable); if (ret < 0) return ret; @@ -184,36 +185,31 @@ static ssize_t ns2_led_sata_show(struct device *dev, static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); -static int __devinit +static int create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, const struct ns2_led *template) { int ret; enum ns2_led_modes mode; - ret = gpio_request(template->cmd, template->name); - if (ret == 0) { - ret = gpio_direction_output(template->cmd, - gpio_get_value(template->cmd)); - if (ret) - gpio_free(template->cmd); - } + ret = devm_gpio_request_one(&pdev->dev, template->cmd, + gpio_get_value(template->cmd) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + template->name); if (ret) { dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", template->name); + return ret; } - ret = gpio_request(template->slow, template->name); - if (ret == 0) { - ret = gpio_direction_output(template->slow, - gpio_get_value(template->slow)); - if (ret) - gpio_free(template->slow); - } + ret = devm_gpio_request_one(&pdev->dev, template->slow, + gpio_get_value(template->slow) ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + template->name); if (ret) { dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", template->name); - goto err_free_cmd; + return ret; } rwlock_init(&led_dat->rw_lock); @@ -228,7 +224,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, ret = ns2_led_get_mode(led_dat, &mode); if (ret < 0) - goto err_free_slow; + return ret; /* Set LED initial state. */ led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; @@ -237,7 +233,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, ret = led_classdev_register(&pdev->dev, &led_dat->cdev); if (ret < 0) - goto err_free_slow; + return ret; ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); if (ret < 0) @@ -247,80 +243,147 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, err_free_cdev: led_classdev_unregister(&led_dat->cdev); -err_free_slow: - gpio_free(led_dat->slow); -err_free_cmd: - gpio_free(led_dat->cmd); - return ret; } -static void __devexit delete_ns2_led(struct ns2_led_data *led_dat) +static void delete_ns2_led(struct ns2_led_data *led_dat) { device_remove_file(led_dat->cdev.dev, &dev_attr_sata); led_classdev_unregister(&led_dat->cdev); - gpio_free(led_dat->cmd); - gpio_free(led_dat->slow); } -static int __devinit ns2_led_probe(struct platform_device *pdev) +#ifdef CONFIG_OF_GPIO +/* + * Translate OpenFirmware node properties into platform_data. + */ +static int +ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata) { - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; - int i; - int ret; - - if (!pdata) - return -EINVAL; - - leds_data = kzalloc(sizeof(struct ns2_led_data) * - pdata->num_leds, GFP_KERNEL); - if (!leds_data) + struct device_node *np = dev->of_node; + struct device_node *child; + struct ns2_led *leds; + int num_leds = 0; + int i = 0; + + num_leds = of_get_child_count(np); + if (!num_leds) + return -ENODEV; + + leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led), + GFP_KERNEL); + if (!leds) return -ENOMEM; - for (i = 0; i < pdata->num_leds; i++) { - ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); - if (ret < 0) - goto err; + for_each_child_of_node(np, child) { + const char *string; + int ret; + ret = of_get_named_gpio(child, "cmd-gpio", 0); + if (ret < 0) + return ret; + leds[i].cmd = ret; + ret = of_get_named_gpio(child, "slow-gpio", 0); + if (ret < 0) + return ret; + leds[i].slow = ret; + ret = of_property_read_string(child, "label", &string); + leds[i].name = (ret == 0) ? string : child->name; + ret = of_property_read_string(child, "linux,default-trigger", + &string); + if (ret == 0) + leds[i].default_trigger = string; + + i++; } - platform_set_drvdata(pdev, leds_data); + pdata->leds = leds; + pdata->num_leds = num_leds; return 0; +} -err: - for (i = i - 1; i >= 0; i--) - delete_ns2_led(&leds_data[i]); +static const struct of_device_id of_ns2_leds_match[] = { + { .compatible = "lacie,ns2-leds", }, + {}, +}; +#endif /* CONFIG_OF_GPIO */ - kfree(leds_data); +struct ns2_led_priv { + int num_leds; + struct ns2_led_data leds_data[]; +}; - return ret; +static inline int sizeof_ns2_led_priv(int num_leds) +{ + return sizeof(struct ns2_led_priv) + + (sizeof(struct ns2_led_data) * num_leds); } -static int __devexit ns2_led_remove(struct platform_device *pdev) +static int ns2_led_probe(struct platform_device *pdev) { + struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct ns2_led_priv *priv; int i; - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; + int ret; + +#ifdef CONFIG_OF_GPIO + if (!pdata) { + pdata = devm_kzalloc(&pdev->dev, + sizeof(struct ns2_led_platform_data), + GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + ret = ns2_leds_get_of_pdata(&pdev->dev, pdata); + if (ret) + return ret; + } +#else + if (!pdata) + return -EINVAL; +#endif /* CONFIG_OF_GPIO */ + + priv = devm_kzalloc(&pdev->dev, + sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL); + if (!priv) + return -ENOMEM; + priv->num_leds = pdata->num_leds; + + for (i = 0; i < priv->num_leds; i++) { + ret = create_ns2_led(pdev, &priv->leds_data[i], + &pdata->leds[i]); + if (ret < 0) { + for (i = i - 1; i >= 0; i--) + delete_ns2_led(&priv->leds_data[i]); + return ret; + } + } + + platform_set_drvdata(pdev, priv); - leds_data = platform_get_drvdata(pdev); + return 0; +} + +static int ns2_led_remove(struct platform_device *pdev) +{ + int i; + struct ns2_led_priv *priv; - for (i = 0; i < pdata->num_leds; i++) - delete_ns2_led(&leds_data[i]); + priv = platform_get_drvdata(pdev); - kfree(leds_data); - platform_set_drvdata(pdev, NULL); + for (i = 0; i < priv->num_leds; i++) + delete_ns2_led(&priv->leds_data[i]); return 0; } static struct platform_driver ns2_led_driver = { .probe = ns2_led_probe, - .remove = __devexit_p(ns2_led_remove), + .remove = ns2_led_remove, .driver = { - .name = "leds-ns2", - .owner = THIS_MODULE, + .name = "leds-ns2", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_ns2_leds_match), }, }; diff --git a/drivers/leds/leds-ot200.c b/drivers/leds/leds-ot200.c index c4646825a62..c9d90609846 100644 --- a/drivers/leds/leds-ot200.c +++ b/drivers/leds/leds-ot200.c @@ -8,7 +8,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/leds.h> @@ -47,37 +46,37 @@ static struct ot200_led leds[] = { { .name = "led_1", .port = 0x49, - .mask = BIT(7), + .mask = BIT(6), }, { .name = "led_2", .port = 0x49, - .mask = BIT(6), + .mask = BIT(5), }, { .name = "led_3", .port = 0x49, - .mask = BIT(5), + .mask = BIT(4), }, { .name = "led_4", .port = 0x49, - .mask = BIT(4), + .mask = BIT(3), }, { .name = "led_5", .port = 0x49, - .mask = BIT(3), + .mask = BIT(2), }, { .name = "led_6", .port = 0x49, - .mask = BIT(2), + .mask = BIT(1), }, { .name = "led_7", .port = 0x49, - .mask = BIT(1), + .mask = BIT(0), } }; @@ -115,7 +114,7 @@ static void ot200_led_brightness_set(struct led_classdev *led_cdev, spin_unlock_irqrestore(&value_lock, flags); } -static int __devinit ot200_led_probe(struct platform_device *pdev) +static int ot200_led_probe(struct platform_device *pdev) { int i; int ret; @@ -144,7 +143,7 @@ err: return ret; } -static int __devexit ot200_led_remove(struct platform_device *pdev) +static int ot200_led_remove(struct platform_device *pdev) { int i; @@ -156,7 +155,7 @@ static int __devexit ot200_led_remove(struct platform_device *pdev) static struct platform_driver ot200_led_driver = { .probe = ot200_led_probe, - .remove = __devexit_p(ot200_led_remove), + .remove = ot200_led_remove, .driver = { .name = "leds-ot200", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index ceccab44b5b..4a0e786b783 100644 --- a/drivers/leds/leds-pca9532.c +++ b/drivers/leds/leds-pca9532.c @@ -186,7 +186,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev, int err = 0; if (*delay_on == 0 && *delay_off == 0) { - /* led subsystem ask us for a blink rate */ + /* led subsystem ask us for a blink rate */ *delay_on = 1000; *delay_off = 1000; } @@ -311,7 +311,6 @@ static int pca9532_destroy_devices(struct pca9532_data *data, int n_devs) break; case PCA9532_TYPE_N2100_BEEP: if (data->idev != NULL) { - input_unregister_device(data->idev); cancel_work_sync(&data->work); data->idev = NULL; } @@ -382,7 +381,7 @@ static int pca9532_configure(struct i2c_client *client, BUG_ON(data->idev); led->state = PCA9532_PWM1; pca9532_setled(led); - data->idev = input_allocate_device(); + data->idev = devm_input_allocate_device(&client->dev); if (data->idev == NULL) { err = -ENOMEM; goto exit; @@ -401,7 +400,6 @@ static int pca9532_configure(struct i2c_client *client, INIT_WORK(&data->work, pca9532_input_work); err = input_register_device(data->idev); if (err) { - input_free_device(data->idev); cancel_work_sync(&data->work); data->idev = NULL; goto exit; @@ -448,8 +446,8 @@ static int pca9532_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct pca9532_data *data = i2c_get_clientdata(client); - struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data; - int err; + struct pca9532_platform_data *pca9532_pdata = + dev_get_platdata(&client->dev); if (!pca9532_pdata) return -EIO; @@ -458,7 +456,7 @@ static int pca9532_probe(struct i2c_client *client, I2C_FUNC_SMBUS_BYTE_DATA)) return -EIO; - data = kzalloc(sizeof(*data), GFP_KERNEL); + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); if (!data) return -ENOMEM; @@ -469,11 +467,7 @@ static int pca9532_probe(struct i2c_client *client, data->client = client; mutex_init(&data->update_lock); - err = pca9532_configure(client, data, pca9532_pdata); - if (err) - kfree(data); - - return err; + return pca9532_configure(client, data, pca9532_pdata); } static int pca9532_remove(struct i2c_client *client) @@ -485,7 +479,6 @@ static int pca9532_remove(struct i2c_client *client) if (err) return err; - kfree(data); return 0; } diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index dcc3bc3d38d..c3a08b60535 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -101,11 +101,16 @@ static const struct i2c_device_id pca955x_id[] = { }; MODULE_DEVICE_TABLE(i2c, pca955x_id); -struct pca955x_led { +struct pca955x { + struct mutex lock; + struct pca955x_led *leds; struct pca955x_chipdef *chipdef; struct i2c_client *client; +}; + +struct pca955x_led { + struct pca955x *pca955x; struct work_struct work; - spinlock_t lock; enum led_brightness brightness; struct led_classdev led_cdev; int led_num; /* 0 .. 15 potentially */ @@ -140,7 +145,7 @@ static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) */ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n, @@ -156,7 +161,7 @@ static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) */ static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n, @@ -169,7 +174,7 @@ static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) */ static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); i2c_smbus_write_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n, @@ -182,7 +187,7 @@ static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) */ static u8 pca955x_read_ls(struct i2c_client *client, int n) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); return (u8) i2c_smbus_read_byte_data(client, pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n); @@ -190,18 +195,23 @@ static u8 pca955x_read_ls(struct i2c_client *client, int n) static void pca955x_led_work(struct work_struct *work) { - struct pca955x_led *pca955x; + struct pca955x_led *pca955x_led; + struct pca955x *pca955x; u8 ls; int chip_ls; /* which LSx to use (0-3 potentially) */ int ls_led; /* which set of bits within LSx to use (0-3) */ - pca955x = container_of(work, struct pca955x_led, work); - chip_ls = pca955x->led_num / 4; - ls_led = pca955x->led_num % 4; + pca955x_led = container_of(work, struct pca955x_led, work); + pca955x = pca955x_led->pca955x; + + chip_ls = pca955x_led->led_num / 4; + ls_led = pca955x_led->led_num % 4; + + mutex_lock(&pca955x->lock); ls = pca955x_read_ls(pca955x->client, chip_ls); - switch (pca955x->brightness) { + switch (pca955x_led->brightness) { case LED_FULL: ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); break; @@ -219,12 +229,15 @@ static void pca955x_led_work(struct work_struct *work) * OFF, HALF, or FULL. But, this is probably better than * just turning off for all other values. */ - pca955x_write_pwm(pca955x->client, 1, 255-pca955x->brightness); + pca955x_write_pwm(pca955x->client, 1, + 255 - pca955x_led->brightness); ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); break; } pca955x_write_ls(pca955x->client, chip_ls, ls); + + mutex_unlock(&pca955x->lock); } static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value) @@ -233,7 +246,6 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v pca955x = container_of(led_cdev, struct pca955x_led, led_cdev); - spin_lock(&pca955x->lock); pca955x->brightness = value; /* @@ -241,14 +253,13 @@ static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness v * can sleep. */ schedule_work(&pca955x->work); - - spin_unlock(&pca955x->lock); } -static int __devinit pca955x_probe(struct i2c_client *client, +static int pca955x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct pca955x_led *pca955x; + struct pca955x *pca955x; + struct pca955x_led *pca955x_led; struct pca955x_chipdef *chip; struct i2c_adapter *adapter; struct led_platform_data *pdata; @@ -256,7 +267,7 @@ static int __devinit pca955x_probe(struct i2c_client *client, chip = &pca955x_chipdefs[id->driver_data]; adapter = to_i2c_adapter(client->dev.parent); - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); /* Make sure the slave address / chip type combo given is possible */ if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != @@ -266,7 +277,7 @@ static int __devinit pca955x_probe(struct i2c_client *client, return -ENODEV; } - printk(KERN_INFO "leds-pca955x: Using %s %d-bit LED driver at " + dev_info(&client->dev, "leds-pca955x: Using %s %d-bit LED driver at " "slave address 0x%02x\n", id->name, chip->bits, client->addr); @@ -282,39 +293,47 @@ static int __devinit pca955x_probe(struct i2c_client *client, } } - pca955x = kzalloc(sizeof(*pca955x) * chip->bits, GFP_KERNEL); + pca955x = devm_kzalloc(&client->dev, sizeof(*pca955x), GFP_KERNEL); if (!pca955x) return -ENOMEM; + pca955x->leds = devm_kzalloc(&client->dev, + sizeof(*pca955x_led) * chip->bits, GFP_KERNEL); + if (!pca955x->leds) + return -ENOMEM; + i2c_set_clientdata(client, pca955x); + mutex_init(&pca955x->lock); + pca955x->client = client; + pca955x->chipdef = chip; + for (i = 0; i < chip->bits; i++) { - pca955x[i].chipdef = chip; - pca955x[i].client = client; - pca955x[i].led_num = i; + pca955x_led = &pca955x->leds[i]; + pca955x_led->led_num = i; + pca955x_led->pca955x = pca955x; /* Platform data can specify LED names and default triggers */ if (pdata) { if (pdata->leds[i].name) - snprintf(pca955x[i].name, - sizeof(pca955x[i].name), "pca955x:%s", - pdata->leds[i].name); + snprintf(pca955x_led->name, + sizeof(pca955x_led->name), "pca955x:%s", + pdata->leds[i].name); if (pdata->leds[i].default_trigger) - pca955x[i].led_cdev.default_trigger = + pca955x_led->led_cdev.default_trigger = pdata->leds[i].default_trigger; } else { - snprintf(pca955x[i].name, sizeof(pca955x[i].name), + snprintf(pca955x_led->name, sizeof(pca955x_led->name), "pca955x:%d", i); } - spin_lock_init(&pca955x[i].lock); - - pca955x[i].led_cdev.name = pca955x[i].name; - pca955x[i].led_cdev.brightness_set = pca955x_led_set; + pca955x_led->led_cdev.name = pca955x_led->name; + pca955x_led->led_cdev.brightness_set = pca955x_led_set; - INIT_WORK(&pca955x[i].work, pca955x_led_work); + INIT_WORK(&pca955x_led->work, pca955x_led_work); - err = led_classdev_register(&client->dev, &pca955x[i].led_cdev); + err = led_classdev_register(&client->dev, + &pca955x_led->led_cdev); if (err < 0) goto exit; } @@ -337,27 +356,23 @@ static int __devinit pca955x_probe(struct i2c_client *client, exit: while (i--) { - led_classdev_unregister(&pca955x[i].led_cdev); - cancel_work_sync(&pca955x[i].work); + led_classdev_unregister(&pca955x->leds[i].led_cdev); + cancel_work_sync(&pca955x->leds[i].work); } - kfree(pca955x); - return err; } -static int __devexit pca955x_remove(struct i2c_client *client) +static int pca955x_remove(struct i2c_client *client) { - struct pca955x_led *pca955x = i2c_get_clientdata(client); + struct pca955x *pca955x = i2c_get_clientdata(client); int i; for (i = 0; i < pca955x->chipdef->bits; i++) { - led_classdev_unregister(&pca955x[i].led_cdev); - cancel_work_sync(&pca955x[i].work); + led_classdev_unregister(&pca955x->leds[i].led_cdev); + cancel_work_sync(&pca955x->leds[i].work); } - kfree(pca955x); - return 0; } @@ -367,7 +382,7 @@ static struct i2c_driver pca955x_driver = { .owner = THIS_MODULE, }, .probe = pca955x_probe, - .remove = __devexit_p(pca955x_remove), + .remove = pca955x_remove, .id_table = pca955x_id, }; diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c new file mode 100644 index 00000000000..82589c0a568 --- /dev/null +++ b/drivers/leds/leds-pca963x.c @@ -0,0 +1,461 @@ +/* + * Copyright 2011 bct electronic GmbH + * Copyright 2013 Qtechnology/AS + * + * Author: Peter Meerwald <p.meerwald@bct-electronic.com> + * Author: Ricardo Ribalda <ricardo.ribalda@gmail.com> + * + * Based on leds-pca955x.c + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) + * LED driver for the PCA9634 I2C LED driver (7-bit slave address set by hw.) + * + * Note that hardware blinking violates the leds infrastructure driver + * interface since the hardware only supports blinking all LEDs with the + * same delay_on/delay_off rates. That is, only the LEDs that are set to + * blink will actually blink but all LEDs that are set to blink will blink + * in identical fashion. The delay_on/delay_off values of the last LED + * that is set to blink will be used for all of the blinking LEDs. + * Hardware blinking is disabled by default but can be enabled by setting + * the 'blink_type' member in the platform_data struct to 'PCA963X_HW_BLINK' + * or by adding the 'nxp,hw-blink' property to the DTS. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/slab.h> +#include <linux/of.h> +#include <linux/platform_data/leds-pca963x.h> + +/* LED select registers determine the source that drives LED outputs */ +#define PCA963X_LED_OFF 0x0 /* LED driver off */ +#define PCA963X_LED_ON 0x1 /* LED driver on */ +#define PCA963X_LED_PWM 0x2 /* Controlled through PWM */ +#define PCA963X_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ + +#define PCA963X_MODE2_DMBLNK 0x20 /* Enable blinking */ + +#define PCA963X_MODE1 0x00 +#define PCA963X_MODE2 0x01 +#define PCA963X_PWM_BASE 0x02 + +enum pca963x_type { + pca9633, + pca9634, +}; + +struct pca963x_chipdef { + u8 grppwm; + u8 grpfreq; + u8 ledout_base; + int n_leds; +}; + +static struct pca963x_chipdef pca963x_chipdefs[] = { + [pca9633] = { + .grppwm = 0x6, + .grpfreq = 0x7, + .ledout_base = 0x8, + .n_leds = 4, + }, + [pca9634] = { + .grppwm = 0xa, + .grpfreq = 0xb, + .ledout_base = 0xc, + .n_leds = 8, + }, +}; + +/* Total blink period in milliseconds */ +#define PCA963X_BLINK_PERIOD_MIN 42 +#define PCA963X_BLINK_PERIOD_MAX 10667 + +static const struct i2c_device_id pca963x_id[] = { + { "pca9632", pca9633 }, + { "pca9633", pca9633 }, + { "pca9634", pca9634 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca963x_id); + +enum pca963x_cmd { + BRIGHTNESS_SET, + BLINK_SET, +}; + +struct pca963x_led; + +struct pca963x { + struct pca963x_chipdef *chipdef; + struct mutex mutex; + struct i2c_client *client; + struct pca963x_led *leds; +}; + +struct pca963x_led { + struct pca963x *chip; + struct work_struct work; + enum led_brightness brightness; + struct led_classdev led_cdev; + int led_num; /* 0 .. 7 potentially */ + enum pca963x_cmd cmd; + char name[32]; + u8 gdc; + u8 gfrq; +}; + +static void pca963x_brightness_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + switch (pca963x->brightness) { + case LED_FULL: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_ON << shift)); + break; + case LED_OFF: + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + ledout & ~mask); + break; + default: + i2c_smbus_write_byte_data(pca963x->chip->client, + PCA963X_PWM_BASE + pca963x->led_num, + pca963x->brightness); + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_PWM << shift)); + break; + } + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_blink_work(struct pca963x_led *pca963x) +{ + u8 ledout_addr = pca963x->chip->chipdef->ledout_base + + (pca963x->led_num / 4); + u8 ledout; + u8 mode2 = i2c_smbus_read_byte_data(pca963x->chip->client, + PCA963X_MODE2); + int shift = 2 * (pca963x->led_num % 4); + u8 mask = 0x3 << shift; + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grppwm, pca963x->gdc); + + i2c_smbus_write_byte_data(pca963x->chip->client, + pca963x->chip->chipdef->grpfreq, pca963x->gfrq); + + if (!(mode2 & PCA963X_MODE2_DMBLNK)) + i2c_smbus_write_byte_data(pca963x->chip->client, PCA963X_MODE2, + mode2 | PCA963X_MODE2_DMBLNK); + + mutex_lock(&pca963x->chip->mutex); + ledout = i2c_smbus_read_byte_data(pca963x->chip->client, ledout_addr); + if ((ledout & mask) != (PCA963X_LED_GRP_PWM << shift)) + i2c_smbus_write_byte_data(pca963x->chip->client, ledout_addr, + (ledout & ~mask) | (PCA963X_LED_GRP_PWM << shift)); + mutex_unlock(&pca963x->chip->mutex); +} + +static void pca963x_work(struct work_struct *work) +{ + struct pca963x_led *pca963x = container_of(work, + struct pca963x_led, work); + + switch (pca963x->cmd) { + case BRIGHTNESS_SET: + pca963x_brightness_work(pca963x); + break; + case BLINK_SET: + pca963x_blink_work(pca963x); + break; + } +} + +static void pca963x_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct pca963x_led *pca963x; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + pca963x->cmd = BRIGHTNESS_SET; + pca963x->brightness = value; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); +} + +static int pca963x_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca963x_led *pca963x; + unsigned long time_on, time_off, period; + u8 gdc, gfrq; + + pca963x = container_of(led_cdev, struct pca963x_led, led_cdev); + + time_on = *delay_on; + time_off = *delay_off; + + /* If both zero, pick reasonable defaults of 500ms each */ + if (!time_on && !time_off) { + time_on = 500; + time_off = 500; + } + + period = time_on + time_off; + + /* If period not supported by hardware, default to someting sane. */ + if ((period < PCA963X_BLINK_PERIOD_MIN) || + (period > PCA963X_BLINK_PERIOD_MAX)) { + time_on = 500; + time_off = 500; + period = time_on + time_off; + } + + /* + * From manual: duty cycle = (GDC / 256) -> + * (time_on / period) = (GDC / 256) -> + * GDC = ((time_on * 256) / period) + */ + gdc = (time_on * 256) / period; + + /* + * From manual: period = ((GFRQ + 1) / 24) in seconds. + * So, period (in ms) = (((GFRQ + 1) / 24) * 1000) -> + * GFRQ = ((period * 24 / 1000) - 1) + */ + gfrq = (period * 24 / 1000) - 1; + + pca963x->cmd = BLINK_SET; + pca963x->gdc = gdc; + pca963x->gfrq = gfrq; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca963x->work); + + *delay_on = time_on; + *delay_off = time_off; + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + struct device_node *np = client->dev.of_node, *child; + struct pca963x_platform_data *pdata; + struct led_info *pca963x_leds; + int count; + + count = of_get_child_count(np); + if (!count || count > chip->n_leds) + return ERR_PTR(-ENODEV); + + pca963x_leds = devm_kzalloc(&client->dev, + sizeof(struct led_info) * chip->n_leds, GFP_KERNEL); + if (!pca963x_leds) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(np, child) { + struct led_info led; + u32 reg; + int res; + + res = of_property_read_u32(child, "reg", ®); + if ((res != 0) || (reg >= chip->n_leds)) + continue; + led.name = + of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + pca963x_leds[reg] = led; + } + pdata = devm_kzalloc(&client->dev, + sizeof(struct pca963x_platform_data), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->leds.leds = pca963x_leds; + pdata->leds.num_leds = chip->n_leds; + + /* default to open-drain unless totem pole (push-pull) is specified */ + if (of_property_read_bool(np, "nxp,totem-pole")) + pdata->outdrv = PCA963X_TOTEM_POLE; + else + pdata->outdrv = PCA963X_OPEN_DRAIN; + + /* default to software blinking unless hardware blinking is specified */ + if (of_property_read_bool(np, "nxp,hw-blink")) + pdata->blink_type = PCA963X_HW_BLINK; + else + pdata->blink_type = PCA963X_SW_BLINK; + + return pdata; +} + +static const struct of_device_id of_pca963x_match[] = { + { .compatible = "nxp,pca9632", }, + { .compatible = "nxp,pca9633", }, + { .compatible = "nxp,pca9634", }, + {}, +}; +#else +static struct pca963x_platform_data * +pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) +{ + return ERR_PTR(-ENODEV); +} +#endif + +static int pca963x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca963x *pca963x_chip; + struct pca963x_led *pca963x; + struct pca963x_platform_data *pdata; + struct pca963x_chipdef *chip; + int i, err; + + chip = &pca963x_chipdefs[id->driver_data]; + pdata = dev_get_platdata(&client->dev); + + if (!pdata) { + pdata = pca963x_dt_init(client, chip); + if (IS_ERR(pdata)) { + dev_warn(&client->dev, "could not parse configuration\n"); + pdata = NULL; + } + } + + if (pdata && (pdata->leds.num_leds < 1 || + pdata->leds.num_leds > chip->n_leds)) { + dev_err(&client->dev, "board info must claim 1-%d LEDs", + chip->n_leds); + return -EINVAL; + } + + pca963x_chip = devm_kzalloc(&client->dev, sizeof(*pca963x_chip), + GFP_KERNEL); + if (!pca963x_chip) + return -ENOMEM; + pca963x = devm_kzalloc(&client->dev, chip->n_leds * sizeof(*pca963x), + GFP_KERNEL); + if (!pca963x) + return -ENOMEM; + + i2c_set_clientdata(client, pca963x_chip); + + mutex_init(&pca963x_chip->mutex); + pca963x_chip->chipdef = chip; + pca963x_chip->client = client; + pca963x_chip->leds = pca963x; + + /* Turn off LEDs by default*/ + i2c_smbus_write_byte_data(client, chip->ledout_base, 0x00); + if (chip->n_leds > 4) + i2c_smbus_write_byte_data(client, chip->ledout_base + 1, 0x00); + + for (i = 0; i < chip->n_leds; i++) { + pca963x[i].led_num = i; + pca963x[i].chip = pca963x_chip; + + /* Platform data can specify LED names and default triggers */ + if (pdata && i < pdata->leds.num_leds) { + if (pdata->leds.leds[i].name) + snprintf(pca963x[i].name, + sizeof(pca963x[i].name), "pca963x:%s", + pdata->leds.leds[i].name); + if (pdata->leds.leds[i].default_trigger) + pca963x[i].led_cdev.default_trigger = + pdata->leds.leds[i].default_trigger; + } + if (!pdata || i >= pdata->leds.num_leds || + !pdata->leds.leds[i].name) + snprintf(pca963x[i].name, sizeof(pca963x[i].name), + "pca963x:%d:%.2x:%d", client->adapter->nr, + client->addr, i); + + pca963x[i].led_cdev.name = pca963x[i].name; + pca963x[i].led_cdev.brightness_set = pca963x_led_set; + + if (pdata && pdata->blink_type == PCA963X_HW_BLINK) + pca963x[i].led_cdev.blink_set = pca963x_blink_set; + + INIT_WORK(&pca963x[i].work, pca963x_work); + + err = led_classdev_register(&client->dev, &pca963x[i].led_cdev); + if (err < 0) + goto exit; + } + + /* Disable LED all-call address and set normal mode */ + i2c_smbus_write_byte_data(client, PCA963X_MODE1, 0x00); + + /* Configure output: open-drain or totem pole (push-pull) */ + if (pdata && pdata->outdrv == PCA963X_OPEN_DRAIN) + i2c_smbus_write_byte_data(client, PCA963X_MODE2, 0x01); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca963x[i].led_cdev); + cancel_work_sync(&pca963x[i].work); + } + + return err; +} + +static int pca963x_remove(struct i2c_client *client) +{ + struct pca963x *pca963x = i2c_get_clientdata(client); + int i; + + for (i = 0; i < pca963x->chipdef->n_leds; i++) { + led_classdev_unregister(&pca963x->leds[i].led_cdev); + cancel_work_sync(&pca963x->leds[i].work); + } + + return 0; +} + +static struct i2c_driver pca963x_driver = { + .driver = { + .name = "leds-pca963x", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_pca963x_match), + }, + .probe = pca963x_probe, + .remove = pca963x_remove, + .id_table = pca963x_id, +}; + +module_i2c_driver(pca963x_driver); + +MODULE_AUTHOR("Peter Meerwald <p.meerwald@bct-electronic.com>"); +MODULE_DESCRIPTION("PCA963X LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index 3ed92f34bd4..d672bb4480f 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -14,124 +14,226 @@ #include <linux/module.h> #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> +#include <linux/of_platform.h> #include <linux/fb.h> #include <linux/leds.h> #include <linux/err.h> #include <linux/pwm.h> #include <linux/leds_pwm.h> #include <linux/slab.h> +#include <linux/workqueue.h> struct led_pwm_data { struct led_classdev cdev; struct pwm_device *pwm; - unsigned int active_low; + struct work_struct work; + unsigned int active_low; unsigned int period; + int duty; + bool can_sleep; }; +struct led_pwm_priv { + int num_leds; + struct led_pwm_data leds[0]; +}; + +static void __led_pwm_set(struct led_pwm_data *led_dat) +{ + int new_duty = led_dat->duty; + + pwm_config(led_dat->pwm, new_duty, led_dat->period); + + if (new_duty == 0) + pwm_disable(led_dat->pwm); + else + pwm_enable(led_dat->pwm); +} + +static void led_pwm_work(struct work_struct *work) +{ + struct led_pwm_data *led_dat = + container_of(work, struct led_pwm_data, work); + + __led_pwm_set(led_dat); +} + static void led_pwm_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct led_pwm_data *led_dat = container_of(led_cdev, struct led_pwm_data, cdev); unsigned int max = led_dat->cdev.max_brightness; - unsigned int period = led_dat->period; + unsigned long long duty = led_dat->period; - if (brightness == 0) { - pwm_config(led_dat->pwm, 0, period); - pwm_disable(led_dat->pwm); - } else { - pwm_config(led_dat->pwm, brightness * period / max, period); - pwm_enable(led_dat->pwm); + duty *= brightness; + do_div(duty, max); + + if (led_dat->active_low) + duty = led_dat->period - duty; + + led_dat->duty = duty; + + if (led_dat->can_sleep) + schedule_work(&led_dat->work); + else + __led_pwm_set(led_dat); +} + +static 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_probe(struct platform_device *pdev) +static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, + struct led_pwm *led, struct device_node *child) { - struct led_pwm_platform_data *pdata = pdev->dev.platform_data; - struct led_pwm *cur_led; - struct led_pwm_data *leds_data, *led_dat; - int i, ret = 0; + 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 (!pdata) - return -EBUSY; + if (child) + led_data->period = pwm_get_period(led_data->pwm); - leds_data = kzalloc(sizeof(struct led_pwm_data) * pdata->num_leds, - GFP_KERNEL); - if (!leds_data) - return -ENOMEM; + led_data->can_sleep = pwm_can_sleep(led_data->pwm); + if (led_data->can_sleep) + INIT_WORK(&led_data->work, led_pwm_work); - for (i = 0; i < pdata->num_leds; i++) { - cur_led = &pdata->leds[i]; - led_dat = &leds_data[i]; - - led_dat->pwm = pwm_request(cur_led->pwm_id, - cur_led->name); - if (IS_ERR(led_dat->pwm)) { - ret = PTR_ERR(led_dat->pwm); - dev_err(&pdev->dev, "unable to request PWM %d\n", - cur_led->pwm_id); - goto err; - } + led_data->period = pwm_get_period(led_data->pwm); + if (!led_data->period && (led->pwm_period_ns > 0)) + led_data->period = led->pwm_period_ns; - led_dat->cdev.name = cur_led->name; - led_dat->cdev.default_trigger = cur_led->default_trigger; - led_dat->active_low = cur_led->active_low; - led_dat->period = cur_led->pwm_period_ns; - led_dat->cdev.brightness_set = led_pwm_set; - led_dat->cdev.brightness = LED_OFF; - led_dat->cdev.max_brightness = cur_led->max_brightness; - led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; - - ret = led_classdev_register(&pdev->dev, &led_dat->cdev); - if (ret < 0) { - pwm_free(led_dat->pwm); - goto err; - } + 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); } - platform_set_drvdata(pdev, leds_data); - - return 0; + return ret; +} -err: - if (i > 0) { - for (i = i - 1; i >= 0; i--) { - led_classdev_unregister(&leds_data[i].cdev); - pwm_free(leds_data[i].pwm); +static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv) +{ + struct device_node *child; + struct led_pwm led; + int ret = 0; + + memset(&led, 0, sizeof(led)); + + for_each_child_of_node(dev->of_node, child) { + led.name = of_get_property(child, "label", NULL) ? : + child->name; + + led.default_trigger = of_get_property(child, + "linux,default-trigger", NULL); + led.active_low = of_property_read_bool(child, "active-low"); + of_property_read_u32(child, "max-brightness", + &led.max_brightness); + + ret = led_pwm_add(dev, priv, &led, child); + if (ret) { + of_node_put(child); + break; } } - kfree(leds_data); - return ret; } -static int __devexit led_pwm_remove(struct platform_device *pdev) +static int led_pwm_probe(struct platform_device *pdev) { - int i; - struct led_pwm_platform_data *pdata = pdev->dev.platform_data; - struct led_pwm_data *leds_data; + struct led_pwm_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct led_pwm_priv *priv; + int count, i; + int ret = 0; + + if (pdata) + count = pdata->num_leds; + else + count = of_get_child_count(pdev->dev.of_node); + + if (!count) + return -EINVAL; + + priv = devm_kzalloc(&pdev->dev, sizeof_pwm_leds_priv(count), + GFP_KERNEL); + if (!priv) + return -ENOMEM; - leds_data = platform_get_drvdata(pdev); + if (pdata) { + for (i = 0; i < count; i++) { + ret = led_pwm_add(&pdev->dev, priv, &pdata->leds[i], + NULL); + if (ret) + break; + } + } else { + ret = led_pwm_create_of(&pdev->dev, priv); + } - for (i = 0; i < pdata->num_leds; i++) { - led_classdev_unregister(&leds_data[i].cdev); - pwm_free(leds_data[i].pwm); + if (ret) { + led_pwm_cleanup(priv); + return ret; } - kfree(leds_data); + platform_set_drvdata(pdev, priv); + + return 0; +} + +static int led_pwm_remove(struct platform_device *pdev) +{ + struct led_pwm_priv *priv = platform_get_drvdata(pdev); + + led_pwm_cleanup(priv); return 0; } +static const struct of_device_id of_pwm_leds_match[] = { + { .compatible = "pwm-leds", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_leds_match); + static struct platform_driver led_pwm_driver = { .probe = led_pwm_probe, - .remove = __devexit_p(led_pwm_remove), + .remove = led_pwm_remove, .driver = { .name = "leds_pwm", .owner = THIS_MODULE, + .of_match_table = of_pwm_leds_match, }, }; diff --git a/drivers/leds/leds-rb532.c b/drivers/leds/leds-rb532.c index a7815b6cd85..2e746d257b0 100644 --- a/drivers/leds/leds-rb532.c +++ b/drivers/leds/leds-rb532.c @@ -16,7 +16,7 @@ #include <asm/mach-rc32434/rb.h> static void rb532_led_set(struct led_classdev *cdev, - enum led_brightness brightness) + enum led_brightness brightness) { if (brightness) set_latch_u5(LO_ULED, 0); @@ -37,12 +37,12 @@ static struct led_classdev rb532_uled = { .default_trigger = "nand-disk", }; -static int __devinit rb532_led_probe(struct platform_device *pdev) +static int rb532_led_probe(struct platform_device *pdev) { return led_classdev_register(&pdev->dev, &rb532_uled); } -static int __devexit rb532_led_remove(struct platform_device *pdev) +static int rb532_led_remove(struct platform_device *pdev) { led_classdev_unregister(&rb532_uled); return 0; @@ -50,7 +50,7 @@ static int __devexit rb532_led_remove(struct platform_device *pdev) static struct platform_driver rb532_led_driver = { .probe = rb532_led_probe, - .remove = __devexit_p(rb532_led_remove), + .remove = rb532_led_remove, .driver = { .name = "rb532-led", .owner = THIS_MODULE, diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index df7e963bddd..358430db6e6 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -140,9 +140,10 @@ static void regulator_led_brightness_set(struct led_classdev *led_cdev, schedule_work(&led->work); } -static int __devinit regulator_led_probe(struct platform_device *pdev) +static int regulator_led_probe(struct platform_device *pdev) { - struct led_regulator_platform_data *pdata = pdev->dev.platform_data; + struct led_regulator_platform_data *pdata = + dev_get_platdata(&pdev->dev); struct regulator_led *led; struct regulator *vcc; int ret = 0; @@ -158,7 +159,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev) return PTR_ERR(vcc); } - led = kzalloc(sizeof(*led), GFP_KERNEL); + led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); if (led == NULL) { ret = -ENOMEM; goto err_vcc; @@ -169,7 +170,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Invalid default brightness %d\n", pdata->brightness); ret = -EINVAL; - goto err_led; + goto err_vcc; } led->value = pdata->brightness; @@ -190,7 +191,7 @@ static int __devinit regulator_led_probe(struct platform_device *pdev) ret = led_classdev_register(&pdev->dev, &led->cdev); if (ret < 0) { cancel_work_sync(&led->work); - goto err_led; + goto err_vcc; } /* to expose the default value to userspace */ @@ -201,14 +202,12 @@ static int __devinit regulator_led_probe(struct platform_device *pdev) return 0; -err_led: - kfree(led); err_vcc: regulator_put(vcc); return ret; } -static int __devexit regulator_led_remove(struct platform_device *pdev) +static int regulator_led_remove(struct platform_device *pdev) { struct regulator_led *led = platform_get_drvdata(pdev); @@ -216,7 +215,6 @@ static int __devexit regulator_led_remove(struct platform_device *pdev) cancel_work_sync(&led->work); regulator_led_disable(led); regulator_put(led->vcc); - kfree(led); return 0; } @@ -226,7 +224,7 @@ static struct platform_driver regulator_led_driver = { .owner = THIS_MODULE, }, .probe = regulator_led_probe, - .remove = __devexit_p(regulator_led_remove), + .remove = regulator_led_remove, }; module_platform_driver(regulator_led_driver); diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c deleted file mode 100644 index 32fe337d5c6..00000000000 --- a/drivers/leds/leds-renesas-tpu.c +++ /dev/null @@ -1,346 +0,0 @@ -/* - * LED control using Renesas TPU - * - * Copyright (C) 2011 Magnus Damm - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#include <linux/module.h> -#include <linux/init.h> -#include <linux/platform_device.h> -#include <linux/spinlock.h> -#include <linux/printk.h> -#include <linux/ioport.h> -#include <linux/io.h> -#include <linux/clk.h> -#include <linux/leds.h> -#include <linux/platform_data/leds-renesas-tpu.h> -#include <linux/gpio.h> -#include <linux/err.h> -#include <linux/slab.h> -#include <linux/pm_runtime.h> -#include <linux/workqueue.h> - -enum r_tpu_pin { R_TPU_PIN_UNUSED, R_TPU_PIN_GPIO, R_TPU_PIN_GPIO_FN }; -enum r_tpu_timer { R_TPU_TIMER_UNUSED, R_TPU_TIMER_ON }; - -struct r_tpu_priv { - struct led_classdev ldev; - void __iomem *mapbase; - struct clk *clk; - struct platform_device *pdev; - enum r_tpu_pin pin_state; - enum r_tpu_timer timer_state; - unsigned long min_rate; - unsigned int refresh_rate; - struct work_struct work; - enum led_brightness new_brightness; -}; - -static DEFINE_SPINLOCK(r_tpu_lock); - -#define TSTR -1 /* Timer start register (shared register) */ -#define TCR 0 /* Timer control register (+0x00) */ -#define TMDR 1 /* Timer mode register (+0x04) */ -#define TIOR 2 /* Timer I/O control register (+0x08) */ -#define TIER 3 /* Timer interrupt enable register (+0x0c) */ -#define TSR 4 /* Timer status register (+0x10) */ -#define TCNT 5 /* Timer counter (+0x14) */ -#define TGRA 6 /* Timer general register A (+0x18) */ -#define TGRB 7 /* Timer general register B (+0x1c) */ -#define TGRC 8 /* Timer general register C (+0x20) */ -#define TGRD 9 /* Timer general register D (+0x24) */ - -static inline unsigned short r_tpu_read(struct r_tpu_priv *p, int reg_nr) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - void __iomem *base = p->mapbase; - unsigned long offs = reg_nr << 2; - - if (reg_nr == TSTR) - return ioread16(base - cfg->channel_offset); - - return ioread16(base + offs); -} - -static inline void r_tpu_write(struct r_tpu_priv *p, int reg_nr, - unsigned short value) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - void __iomem *base = p->mapbase; - unsigned long offs = reg_nr << 2; - - if (reg_nr == TSTR) { - iowrite16(value, base - cfg->channel_offset); - return; - } - - iowrite16(value, base + offs); -} - -static void r_tpu_start_stop_ch(struct r_tpu_priv *p, int start) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - unsigned long flags, value; - - /* start stop register shared by multiple timer channels */ - spin_lock_irqsave(&r_tpu_lock, flags); - value = r_tpu_read(p, TSTR); - - if (start) - value |= 1 << cfg->timer_bit; - else - value &= ~(1 << cfg->timer_bit); - - r_tpu_write(p, TSTR, value); - spin_unlock_irqrestore(&r_tpu_lock, flags); -} - -static int r_tpu_enable(struct r_tpu_priv *p, enum led_brightness brightness) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - int prescaler[] = { 1, 4, 16, 64 }; - int k, ret; - unsigned long rate, tmp; - - if (p->timer_state == R_TPU_TIMER_ON) - return 0; - - /* wake up device and enable clock */ - pm_runtime_get_sync(&p->pdev->dev); - ret = clk_enable(p->clk); - if (ret) { - dev_err(&p->pdev->dev, "cannot enable clock\n"); - return ret; - } - - /* make sure channel is disabled */ - r_tpu_start_stop_ch(p, 0); - - /* get clock rate after enabling it */ - rate = clk_get_rate(p->clk); - - /* pick the lowest acceptable rate */ - for (k = 0; k < ARRAY_SIZE(prescaler); k++) - if ((rate / prescaler[k]) < p->min_rate) - break; - - if (!k) { - dev_err(&p->pdev->dev, "clock rate mismatch\n"); - goto err0; - } - dev_dbg(&p->pdev->dev, "rate = %lu, prescaler %u\n", - rate, prescaler[k - 1]); - - /* clear TCNT on TGRB match, count on rising edge, set prescaler */ - r_tpu_write(p, TCR, 0x0040 | (k - 1)); - - /* output 0 until TGRA, output 1 until TGRB */ - r_tpu_write(p, TIOR, 0x0002); - - rate /= prescaler[k - 1] * p->refresh_rate; - r_tpu_write(p, TGRB, rate); - dev_dbg(&p->pdev->dev, "TRGB = 0x%04lx\n", rate); - - tmp = (cfg->max_brightness - brightness) * rate; - r_tpu_write(p, TGRA, tmp / cfg->max_brightness); - dev_dbg(&p->pdev->dev, "TRGA = 0x%04lx\n", tmp / cfg->max_brightness); - - /* PWM mode */ - r_tpu_write(p, TMDR, 0x0002); - - /* enable channel */ - r_tpu_start_stop_ch(p, 1); - - p->timer_state = R_TPU_TIMER_ON; - return 0; - err0: - clk_disable(p->clk); - pm_runtime_put_sync(&p->pdev->dev); - return -ENOTSUPP; -} - -static void r_tpu_disable(struct r_tpu_priv *p) -{ - if (p->timer_state == R_TPU_TIMER_UNUSED) - return; - - /* disable channel */ - r_tpu_start_stop_ch(p, 0); - - /* stop clock and mark device as idle */ - clk_disable(p->clk); - pm_runtime_put_sync(&p->pdev->dev); - - p->timer_state = R_TPU_TIMER_UNUSED; -} - -static void r_tpu_set_pin(struct r_tpu_priv *p, enum r_tpu_pin new_state, - enum led_brightness brightness) -{ - struct led_renesas_tpu_config *cfg = p->pdev->dev.platform_data; - - if (p->pin_state == new_state) { - if (p->pin_state == R_TPU_PIN_GPIO) - gpio_set_value(cfg->pin_gpio, brightness); - return; - } - - if (p->pin_state == R_TPU_PIN_GPIO) - gpio_free(cfg->pin_gpio); - - if (p->pin_state == R_TPU_PIN_GPIO_FN) - gpio_free(cfg->pin_gpio_fn); - - if (new_state == R_TPU_PIN_GPIO) { - gpio_request(cfg->pin_gpio, cfg->name); - gpio_direction_output(cfg->pin_gpio, !!brightness); - } - if (new_state == R_TPU_PIN_GPIO_FN) - gpio_request(cfg->pin_gpio_fn, cfg->name); - - p->pin_state = new_state; -} - -static void r_tpu_work(struct work_struct *work) -{ - struct r_tpu_priv *p = container_of(work, struct r_tpu_priv, work); - enum led_brightness brightness = p->new_brightness; - - r_tpu_disable(p); - - /* off and maximum are handled as GPIO pins, in between PWM */ - if ((brightness == 0) || (brightness == p->ldev.max_brightness)) - r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness); - else { - r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0); - r_tpu_enable(p, brightness); - } -} - -static void r_tpu_set_brightness(struct led_classdev *ldev, - enum led_brightness brightness) -{ - struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev); - p->new_brightness = brightness; - schedule_work(&p->work); -} - -static int __devinit r_tpu_probe(struct platform_device *pdev) -{ - struct led_renesas_tpu_config *cfg = pdev->dev.platform_data; - struct r_tpu_priv *p; - struct resource *res; - int ret = -ENXIO; - - if (!cfg) { - dev_err(&pdev->dev, "missing platform data\n"); - goto err0; - } - - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (p == NULL) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); - ret = -ENOMEM; - goto err0; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "failed to get I/O memory\n"); - goto err1; - } - - /* map memory, let mapbase point to our channel */ - p->mapbase = ioremap_nocache(res->start, resource_size(res)); - if (p->mapbase == NULL) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); - goto err1; - } - - /* get hold of clock */ - p->clk = clk_get(&pdev->dev, NULL); - if (IS_ERR(p->clk)) { - dev_err(&pdev->dev, "cannot get clock\n"); - ret = PTR_ERR(p->clk); - goto err2; - } - - p->pdev = pdev; - p->pin_state = R_TPU_PIN_UNUSED; - p->timer_state = R_TPU_TIMER_UNUSED; - p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100; - r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF); - platform_set_drvdata(pdev, p); - - INIT_WORK(&p->work, r_tpu_work); - - p->ldev.name = cfg->name; - p->ldev.brightness = LED_OFF; - p->ldev.max_brightness = cfg->max_brightness; - p->ldev.brightness_set = r_tpu_set_brightness; - p->ldev.flags |= LED_CORE_SUSPENDRESUME; - ret = led_classdev_register(&pdev->dev, &p->ldev); - if (ret < 0) - goto err3; - - /* max_brightness may be updated by the LED core code */ - p->min_rate = p->ldev.max_brightness * p->refresh_rate; - - pm_runtime_enable(&pdev->dev); - return 0; - - err3: - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - clk_put(p->clk); - err2: - iounmap(p->mapbase); - err1: - kfree(p); - err0: - return ret; -} - -static int __devexit r_tpu_remove(struct platform_device *pdev) -{ - struct r_tpu_priv *p = platform_get_drvdata(pdev); - - r_tpu_set_brightness(&p->ldev, LED_OFF); - led_classdev_unregister(&p->ldev); - cancel_work_sync(&p->work); - r_tpu_disable(p); - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - - pm_runtime_disable(&pdev->dev); - clk_put(p->clk); - - iounmap(p->mapbase); - kfree(p); - return 0; -} - -static struct platform_driver r_tpu_device_driver = { - .probe = r_tpu_probe, - .remove = __devexit_p(r_tpu_remove), - .driver = { - .name = "leds-renesas-tpu", - } -}; - -module_platform_driver(r_tpu_device_driver); - -MODULE_AUTHOR("Magnus Damm"); -MODULE_DESCRIPTION("Renesas TPU LED Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c index bd0a5ed49c4..785eb53a87f 100644 --- a/drivers/leds/leds-s3c24xx.c +++ b/drivers/leds/leds-s3c24xx.c @@ -12,16 +12,15 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/gpio.h> #include <linux/slab.h> #include <linux/module.h> +#include <linux/platform_data/leds-s3c24xx.h> -#include <mach/hardware.h> #include <mach/regs-gpio.h> -#include <mach/leds-gpio.h> +#include <plat/gpio-cfg.h> /* our context */ @@ -45,17 +44,19 @@ static void s3c24xx_led_set(struct led_classdev *led_cdev, { struct s3c24xx_gpio_led *led = to_gpio(led_cdev); struct s3c24xx_led_platdata *pd = led->pdata; + int state = (value ? 1 : 0) ^ (pd->flags & S3C24XX_LEDF_ACTLOW); /* there will be a short delay between setting the output and * going from output to input when using tristate. */ - s3c2410_gpio_setpin(pd->gpio, (value ? 1 : 0) ^ - (pd->flags & S3C24XX_LEDF_ACTLOW)); - - if (pd->flags & S3C24XX_LEDF_TRISTATE) - s3c2410_gpio_cfgpin(pd->gpio, - value ? S3C2410_GPIO_OUTPUT : S3C2410_GPIO_INPUT); + gpio_set_value(pd->gpio, state); + if (pd->flags & S3C24XX_LEDF_TRISTATE) { + if (value) + gpio_direction_output(pd->gpio, state); + else + gpio_direction_input(pd->gpio); + } } static int s3c24xx_led_remove(struct platform_device *dev) @@ -63,22 +64,20 @@ static int s3c24xx_led_remove(struct platform_device *dev) struct s3c24xx_gpio_led *led = pdev_to_gpio(dev); led_classdev_unregister(&led->cdev); - kfree(led); return 0; } static int s3c24xx_led_probe(struct platform_device *dev) { - struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; + struct s3c24xx_led_platdata *pdata = dev_get_platdata(&dev->dev); struct s3c24xx_gpio_led *led; int ret; - led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL); - if (led == NULL) { - dev_err(&dev->dev, "No memory for device\n"); + led = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led), + GFP_KERNEL); + if (!led) return -ENOMEM; - } platform_set_drvdata(dev, led); @@ -89,27 +88,27 @@ static int s3c24xx_led_probe(struct platform_device *dev) led->pdata = pdata; + ret = devm_gpio_request(&dev->dev, pdata->gpio, "S3C24XX_LED"); + if (ret < 0) + return ret; + /* no point in having a pull-up if we are always driving */ - if (pdata->flags & S3C24XX_LEDF_TRISTATE) { - s3c2410_gpio_setpin(pdata->gpio, 0); - s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT); - } else { - s3c2410_gpio_pullup(pdata->gpio, 0); - s3c2410_gpio_setpin(pdata->gpio, 0); - s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT); - } + s3c_gpio_setpull(pdata->gpio, S3C_GPIO_PULL_NONE); + + if (pdata->flags & S3C24XX_LEDF_TRISTATE) + gpio_direction_input(pdata->gpio); + else + gpio_direction_output(pdata->gpio, + pdata->flags & S3C24XX_LEDF_ACTLOW ? 1 : 0); /* register our new led device */ ret = led_classdev_register(&dev->dev, &led->cdev); - if (ret < 0) { + if (ret < 0) dev_err(&dev->dev, "led_classdev_register failed\n"); - kfree(led); - return ret; - } - return 0; + return ret; } static struct platform_driver s3c24xx_led_driver = { diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index 57371e1485a..2eb3ef62962 100644 --- a/drivers/leds/leds-ss4200.c +++ b/drivers/leds/leds-ss4200.c @@ -63,8 +63,7 @@ MODULE_LICENSE("GPL"); /* * PCI ID of the Intel ICH7 LPC Device within which the GPIO block lives. */ -static const struct pci_device_id ich7_lpc_pci_id[] = -{ +static const struct pci_device_id ich7_lpc_pci_id[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_0) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_1) }, { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_ICH7_30) }, @@ -79,7 +78,7 @@ static int __init ss4200_led_dmi_callback(const struct dmi_system_id *id) return 1; } -static bool __initdata nodetect; +static bool nodetect; module_param_named(nodetect, nodetect, bool, 0); MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection"); @@ -92,7 +91,7 @@ MODULE_PARM_DESC(nodetect, "Skip DMI-based hardware detection"); * detected as working, but in reality it is not) as low as * possible. */ -static struct dmi_system_id __initdata nas_led_whitelist[] = { +static struct dmi_system_id nas_led_whitelist[] __initdata = { { .callback = ss4200_led_dmi_callback, .ident = "Intel SS4200-E", @@ -198,7 +197,7 @@ static void nasgpio_led_set_attr(struct led_classdev *led_cdev, spin_unlock(&nasgpio_gpio_lock); } -u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) +static u32 nasgpio_led_get_attr(struct led_classdev *led_cdev, u32 port) { struct nasgpio_led *led = led_classdev_to_nasgpio_led(led_cdev); u32 gpio_in; @@ -263,7 +262,7 @@ static int nasgpio_led_set_blink(struct led_classdev *led_cdev, * already taken care of this, but we will do so in a non destructive manner * so that we have what we need whether the BIOS did it or not. */ -static int __devinit ich7_gpio_init(struct device *dev) +static int ich7_gpio_init(struct device *dev) { int i; u32 config_data = 0; @@ -342,7 +341,7 @@ static void ich7_lpc_cleanup(struct device *dev) * so we can retrive the required operational information and prepare the GPIO. */ static struct pci_dev *nas_gpio_pci_dev; -static int __devinit ich7_lpc_probe(struct pci_dev *dev, +static int ich7_lpc_probe(struct pci_dev *dev, const struct pci_device_id *id) { int status; @@ -459,7 +458,7 @@ static ssize_t nas_led_blink_store(struct device *dev, struct led_classdev *led = dev_get_drvdata(dev); unsigned long blink_state; - ret = strict_strtoul(buf, 10, &blink_state); + ret = kstrtoul(buf, 10, &blink_state); if (ret) return ret; diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c index 1757396b20b..0b8cc4a021a 100644 --- a/drivers/leds/leds-sunfire.c +++ b/drivers/leds/leds-sunfire.c @@ -3,6 +3,8 @@ * Copyright (C) 2008 David S. Miller <davem@davemloft.net> */ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> @@ -14,9 +16,6 @@ #include <asm/fhc.h> #include <asm/upa.h> -#define DRIVER_NAME "leds-sunfire" -#define PFX DRIVER_NAME ": " - MODULE_AUTHOR("David S. Miller (davem@davemloft.net)"); MODULE_DESCRIPTION("Sun Fire LED driver"); MODULE_LICENSE("GPL"); @@ -123,25 +122,21 @@ struct sunfire_drvdata { struct sunfire_led leds[NUM_LEDS_PER_BOARD]; }; -static int __devinit sunfire_led_generic_probe(struct platform_device *pdev, +static int sunfire_led_generic_probe(struct platform_device *pdev, struct led_type *types) { struct sunfire_drvdata *p; int i, err; if (pdev->num_resources != 1) { - printk(KERN_ERR PFX "Wrong number of resources %d, should be 1\n", + dev_err(&pdev->dev, "Wrong number of resources %d, should be 1\n", pdev->num_resources); - err = -EINVAL; - goto out; + return -EINVAL; } - p = kzalloc(sizeof(*p), GFP_KERNEL); - if (!p) { - printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata\n"); - err = -ENOMEM; - goto out; - } + p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); + if (!p) + return -ENOMEM; for (i = 0; i < NUM_LEDS_PER_BOARD; i++) { struct led_classdev *lp = &p->leds[i].led_cdev; @@ -154,34 +149,27 @@ static int __devinit sunfire_led_generic_probe(struct platform_device *pdev, err = led_classdev_register(&pdev->dev, lp); if (err) { - printk(KERN_ERR PFX "Could not register %s LED\n", + dev_err(&pdev->dev, "Could not register %s LED\n", lp->name); - goto out_unregister_led_cdevs; + for (i--; i >= 0; i--) + led_classdev_unregister(&p->leds[i].led_cdev); + return err; } } - dev_set_drvdata(&pdev->dev, p); + platform_set_drvdata(pdev, p); return 0; - -out_unregister_led_cdevs: - for (i--; i >= 0; i--) - led_classdev_unregister(&p->leds[i].led_cdev); - kfree(p); -out: - return err; } -static int __devexit sunfire_led_generic_remove(struct platform_device *pdev) +static int sunfire_led_generic_remove(struct platform_device *pdev) { - struct sunfire_drvdata *p = dev_get_drvdata(&pdev->dev); + struct sunfire_drvdata *p = platform_get_drvdata(pdev); int i; for (i = 0; i < NUM_LEDS_PER_BOARD; i++) led_classdev_unregister(&p->leds[i].led_cdev); - kfree(p); - return 0; } @@ -197,11 +185,11 @@ static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = { { .name = "clockboard-right", .handler = clockboard_right_set, - .default_trigger= "heartbeat", + .default_trigger = "heartbeat", }, }; -static int __devinit sunfire_clockboard_led_probe(struct platform_device *pdev) +static int sunfire_clockboard_led_probe(struct platform_device *pdev) { return sunfire_led_generic_probe(pdev, clockboard_led_types); } @@ -218,11 +206,11 @@ static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = { { .name = "fhc-right", .handler = fhc_right_set, - .default_trigger= "heartbeat", + .default_trigger = "heartbeat", }, }; -static int __devinit sunfire_fhc_led_probe(struct platform_device *pdev) +static int sunfire_fhc_led_probe(struct platform_device *pdev) { return sunfire_led_generic_probe(pdev, fhc_led_types); } @@ -232,7 +220,7 @@ MODULE_ALIAS("platform:sunfire-fhc-leds"); static struct platform_driver sunfire_clockboard_led_driver = { .probe = sunfire_clockboard_led_probe, - .remove = __devexit_p(sunfire_led_generic_remove), + .remove = sunfire_led_generic_remove, .driver = { .name = "sunfire-clockboard-leds", .owner = THIS_MODULE, @@ -241,7 +229,7 @@ static struct platform_driver sunfire_clockboard_led_driver = { static struct platform_driver sunfire_fhc_led_driver = { .probe = sunfire_fhc_led_probe, - .remove = __devexit_p(sunfire_led_generic_remove), + .remove = sunfire_led_generic_remove, .driver = { .name = "sunfire-fhc-leds", .owner = THIS_MODULE, @@ -253,13 +241,13 @@ static int __init sunfire_leds_init(void) int err = platform_driver_register(&sunfire_clockboard_led_driver); if (err) { - printk(KERN_ERR PFX "Could not register clock board LED driver\n"); + pr_err("Could not register clock board LED driver\n"); return err; } err = platform_driver_register(&sunfire_fhc_led_driver); if (err) { - printk(KERN_ERR PFX "Could not register FHC LED driver\n"); + pr_err("Could not register FHC LED driver\n"); platform_driver_unregister(&sunfire_clockboard_led_driver); } diff --git a/drivers/leds/leds-tca6507.c b/drivers/leds/leds-tca6507.c index 133f89fb707..3d9e267a56c 100644 --- a/drivers/leds/leds-tca6507.c +++ b/drivers/leds/leds-tca6507.c @@ -4,77 +4,87 @@ * The TCA6507 is a programmable LED controller that can drive 7 * separate lines either by holding them low, or by pulsing them * with modulated width. - * The modulation can be varied in a simple pattern to produce a blink or - * double-blink. + * The modulation can be varied in a simple pattern to produce a + * blink or double-blink. * - * This driver can configure each line either as a 'GPIO' which is out-only - * (no pull-up) or as an LED with variable brightness and hardware-assisted - * blinking. + * This driver can configure each line either as a 'GPIO' which is + * out-only (pull-up resistor required) or as an LED with variable + * brightness and hardware-assisted blinking. * - * Apart from OFF and ON there are three programmable brightness levels which - * can be programmed from 0 to 15 and indicate how many 500usec intervals in - * each 8msec that the led is 'on'. The levels are named MASTER, BANK0 and - * BANK1. + * Apart from OFF and ON there are three programmable brightness + * levels which can be programmed from 0 to 15 and indicate how many + * 500usec intervals in each 8msec that the led is 'on'. The levels + * are named MASTER, BANK0 and BANK1. * - * There are two different blink rates that can be programmed, each with - * separate time for rise, on, fall, off and second-off. Thus if 3 or more - * different non-trivial rates are required, software must be used for the extra - * rates. The two different blink rates must align with the two levels BANK0 and - * BANK1. - * This driver does not support double-blink so 'second-off' always matches - * 'off'. + * There are two different blink rates that can be programmed, each + * with separate time for rise, on, fall, off and second-off. Thus if + * 3 or more different non-trivial rates are required, software must + * be used for the extra rates. The two different blink rates must + * align with the two levels BANK0 and BANK1. This driver does not + * support double-blink so 'second-off' always matches 'off'. * - * Only 16 different times can be programmed in a roughly logarithmic scale from - * 64ms to 16320ms. To be precise the possible times are: + * Only 16 different times can be programmed in a roughly logarithmic + * scale from 64ms to 16320ms. To be precise the possible times are: * 0, 64, 128, 192, 256, 384, 512, 768, * 1024, 1536, 2048, 3072, 4096, 5760, 8128, 16320 * - * Times that cannot be closely matched with these must be - * handled in software. This driver allows 12.5% error in matching. + * Times that cannot be closely matched with these must be handled in + * software. This driver allows 12.5% error in matching. * - * This driver does not allow rise/fall rates to be set explicitly. When trying - * to match a given 'on' or 'off' period, an appropriate pair of 'change' and - * 'hold' times are chosen to get a close match. If the target delay is even, - * the 'change' number will be the smaller; if odd, the 'hold' number will be - * the smaller. - - * Choosing pairs of delays with 12.5% errors allows us to match delays in the - * ranges: 56-72, 112-144, 168-216, 224-27504, 28560-36720. - * 26% of the achievable sums can be matched by multiple pairings. For example - * 1536 == 1536+0, 1024+512, or 768+768. This driver will always choose the - * pairing with the least maximum - 768+768 in this case. Other pairings are - * not available. + * This driver does not allow rise/fall rates to be set explicitly. + * When trying to match a given 'on' or 'off' period, an appropriate + * pair of 'change' and 'hold' times are chosen to get a close match. + * If the target delay is even, the 'change' number will be the + * smaller; if odd, the 'hold' number will be the smaller. + + * Choosing pairs of delays with 12.5% errors allows us to match + * delays in the ranges: 56-72, 112-144, 168-216, 224-27504, + * 28560-36720. + * 26% of the achievable sums can be matched by multiple pairings. + * For example 1536 == 1536+0, 1024+512, or 768+768. + * This driver will always choose the pairing with the least + * maximum - 768+768 in this case. Other pairings are not available. * - * Access to the 3 levels and 2 blinks are on a first-come, first-served basis. - * Access can be shared by multiple leds if they have the same level and - * either same blink rates, or some don't blink. - * When a led changes, it relinquishes access and tries again, so it might - * lose access to hardware blink. - * If a blink engine cannot be allocated, software blink is used. - * If the desired brightness cannot be allocated, the closest available non-zero - * brightness is used. As 'full' is always available, the worst case would be - * to have two different blink rates at '1', with Max at '2', then other leds - * will have to choose between '2' and '16'. Hopefully this is not likely. + * Access to the 3 levels and 2 blinks are on a first-come, + * first-served basis. Access can be shared by multiple leds if they + * have the same level and either same blink rates, or some don't + * blink. When a led changes, it relinquishes access and tries again, + * so it might lose access to hardware blink. * - * Each bank (BANK0 and BANK1) has two usage counts - LEDs using the brightness - * and LEDs using the blink. It can only be reprogrammed when the appropriate - * counter is zero. The MASTER level has a single usage count. + * If a blink engine cannot be allocated, software blink is used. If + * the desired brightness cannot be allocated, the closest available + * non-zero brightness is used. As 'full' is always available, the + * worst case would be to have two different blink rates at '1', with + * Max at '2', then other leds will have to choose between '2' and + * '16'. Hopefully this is not likely. * - * Each Led has programmable 'on' and 'off' time as milliseconds. With each - * there is a flag saying if it was explicitly requested or defaulted. - * Similarly the banks know if each time was explicit or a default. Defaults - * are permitted to be changed freely - they are not recognised when matching. + * 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. This data - * lists for each output: the name, default trigger, and whether the signal - * is being used as a GPiO rather than an led. 'struct led_plaform_data' - * is used for this. If 'name' is NULL, the output isn't used. If 'flags' - * is TCA6507_MAKE_CPIO, the output is a GPO. - * The "struct led_platform_data" can be embedded in a - * "struct tca6507_platform_data" which adds a 'gpio_base' for the GPiOs, - * and a 'setup' callback which is called once the GPiOs are available. * + * 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> @@ -85,6 +95,7 @@ #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) */ @@ -191,17 +202,18 @@ MODULE_DEVICE_TABLE(i2c, tca6507_id); static int choose_times(int msec, int *c1p, int *c2p) { /* - * Choose two timecodes which add to 'msec' as near as possible. - * The first returned is the 'on' or 'off' time. The second is to be - * used as a 'fade-on' or 'fade-off' time. If 'msec' is even, - * the first will not be smaller than the second. If 'msec' is odd, - * the first will not be larger than the second. - * If we cannot get a sum within 1/8 of 'msec' fail with -EINVAL, - * otherwise return the sum that was achieved, plus 1 if the first is - * smaller. - * If two possibilities are equally good (e.g. 512+0, 256+256), choose - * the first pair so there is more change-time visible (i.e. it is - * softer). + * Choose two timecodes which add to 'msec' as near as + * possible. The first returned is the 'on' or 'off' time. + * The second is to be used as a 'fade-on' or 'fade-off' time. + * If 'msec' is even, the first will not be smaller than the + * second. If 'msec' is odd, the first will not be larger + * than the second. + * If we cannot get a sum within 1/8 of 'msec' fail with + * -EINVAL, otherwise return the sum that was achieved, plus 1 + * if the first is smaller. + * If two possibilities are equally good (e.g. 512+0, + * 256+256), choose the first pair so there is more + * change-time visible (i.e. it is softer). */ int c1, c2; int tmax = msec * 9 / 8; @@ -254,8 +266,8 @@ static int choose_times(int msec, int *c1p, int *c2p) } /* - * Update the register file with the appropriate 3-bit state for - * the given led. + * Update the register file with the appropriate 3-bit state for the + * given led. */ static void set_select(struct tca6507_chip *tca, int led, int val) { @@ -273,9 +285,9 @@ static void set_select(struct tca6507_chip *tca, int led, int val) } } -/* Update the register file with the appropriate 4-bit code for - * one bank or other. This can be used for timers, for levels, or - * for initialisation. +/* Update the register file with the appropriate 4-bit code for one + * bank or other. This can be used for timers, for levels, or for + * initialization. */ static void set_code(struct tca6507_chip *tca, int reg, int bank, int new) { @@ -308,7 +320,7 @@ static void set_level(struct tca6507_chip *tca, int bank, int level) tca->bank[bank].level = level; } -/* Record all relevant time code for a given bank */ +/* Record all relevant time codes for a given bank */ static void set_times(struct tca6507_chip *tca, int bank) { int c1, c2; @@ -316,7 +328,8 @@ static void set_times(struct tca6507_chip *tca, int bank) 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], + "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); @@ -324,7 +337,8 @@ static void set_times(struct tca6507_chip *tca, int bank) result = choose_times(tca->bank[bank].offtime, &c1, &c2); dev_dbg(&tca->client->dev, - "Chose off times %d(%d) %d(%d) for %dms\n", c1, time_codes[c1], + "Chose off times %d(%d) %d(%d) for %dms\n", + c1, time_codes[c1], c2, time_codes[c2], tca->bank[bank].offtime); set_code(tca, TCA6507_FADE_OFF, bank, c2); set_code(tca, TCA6507_FIRST_OFF, bank, c1); @@ -372,7 +386,8 @@ static void led_release(struct tca6507_led *led) static int led_prepare(struct tca6507_led *led) { - /* Assign this led to a bank, configuring that bank if necessary. */ + /* Assign this led to a bank, configuring that bank if + * necessary. */ int level = TO_LEVEL(led->led_cdev.brightness); struct tca6507_chip *tca = led->chip; int c1, c2; @@ -388,10 +403,10 @@ static int led_prepare(struct tca6507_led *led) if (led->ontime == 0 || led->offtime == 0) { /* - * Just set the brightness, choosing first usable bank. - * If none perfect, choose best. - * Count backwards so we check MASTER bank first - * to avoid wasting a timer. + * Just set the brightness, choosing first usable + * bank. If none perfect, choose best. Count + * backwards so we check MASTER bank first to avoid + * wasting a timer. */ int best = -1;/* full-on */ int diff = 15-level; @@ -432,9 +447,9 @@ static int led_prepare(struct tca6507_led *led) } /* - * We have on/off time so we need to try to allocate a timing bank. - * First check if times are compatible with hardware and give up if - * not. + * We have on/off time so we need to try to allocate a timing + * bank. First check if times are compatible with hardware + * and give up if not. */ if (choose_times(led->ontime, &c1, &c2) < 0) return -EINVAL; @@ -522,8 +537,8 @@ static int led_assign(struct tca6507_led *led) err = led_prepare(led); if (err) { /* - * Can only fail on timer setup. In that case we need to - * re-establish as steady level. + * Can only fail on timer setup. In that case we need + * to re-establish as steady level. */ led->ontime = 0; led->offtime = 0; @@ -593,8 +608,8 @@ static void tca6507_gpio_set_value(struct gpio_chip *gc, spin_lock_irqsave(&tca->lock, flags); /* - * 'OFF' is floating high, and 'ON' is pulled down, so it has the - * inverse sense of 'val'. + * 'OFF' is floating high, and 'ON' is pulled down, so it has + * the inverse sense of 'val'. */ set_select(tca, tca->gpio_map[offset], val ? TCA6507_LS_LED_OFF : TCA6507_LS_LED_ON); @@ -637,6 +652,9 @@ static int tca6507_probe_gpios(struct i2c_client *client, 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; @@ -667,8 +685,71 @@ static void tca6507_remove_gpio(struct tca6507_chip *tca) } #endif /* CONFIG_GPIOLIB */ -static int __devinit tca6507_probe(struct i2c_client *client, - const struct i2c_device_id *id) +#ifdef CONFIG_OF +static struct tca6507_platform_data * +tca6507_led_dt_init(struct i2c_client *client) +{ + struct device_node *np = client->dev.of_node, *child; + struct tca6507_platform_data *pdata; + struct led_info *tca_leds; + int count; + + count = of_get_child_count(np); + if (!count || count > NUM_LEDS) + return ERR_PTR(-ENODEV); + + tca_leds = devm_kzalloc(&client->dev, + sizeof(struct led_info) * NUM_LEDS, GFP_KERNEL); + if (!tca_leds) + return ERR_PTR(-ENOMEM); + + for_each_child_of_node(np, child) { + struct led_info led; + u32 reg; + int ret; + + led.name = + of_get_property(child, "label", NULL) ? : child->name; + led.default_trigger = + of_get_property(child, "linux,default-trigger", NULL); + led.flags = 0; + if (of_property_match_string(child, "compatible", "gpio") >= 0) + led.flags |= TCA6507_MAKE_GPIO; + ret = of_property_read_u32(child, "reg", ®); + if (ret != 0 || reg < 0 || reg >= NUM_LEDS) + continue; + + tca_leds[reg] = led; + } + pdata = devm_kzalloc(&client->dev, + sizeof(struct tca6507_platform_data), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->leds.leds = tca_leds; + pdata->leds.num_leds = NUM_LEDS; +#ifdef CONFIG_GPIOLIB + pdata->gpio_base = -1; +#endif + return pdata; +} + +static const struct of_device_id of_tca6507_leds_match[] = { + { .compatible = "ti,tca6507", }, + {}, +}; + +#else +static struct tca6507_platform_data * +tca6507_led_dt_init(struct i2c_client *client) +{ + return ERR_PTR(-ENODEV); +} + +#endif + +static int tca6507_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct tca6507_chip *tca; struct i2c_adapter *adapter; @@ -677,20 +758,22 @@ static int __devinit tca6507_probe(struct i2c_client *client, int i = 0; adapter = to_i2c_adapter(client->dev.parent); - pdata = client->dev.platform_data; + pdata = dev_get_platdata(&client->dev); if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) return -EIO; if (!pdata || pdata->leds.num_leds != NUM_LEDS) { - dev_err(&client->dev, "Need %d entries in platform-data list\n", - NUM_LEDS); - return -ENODEV; + 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); + } } - err = -ENOMEM; - tca = kzalloc(sizeof(*tca), GFP_KERNEL); + tca = devm_kzalloc(&client->dev, sizeof(*tca), GFP_KERNEL); if (!tca) - goto exit; + return -ENOMEM; tca->client = client; INIT_WORK(&tca->work, tca6507_work); @@ -724,16 +807,14 @@ static int __devinit tca6507_probe(struct i2c_client *client, return 0; exit: - while (i--) + while (i--) { if (tca->leds[i].led_cdev.name) led_classdev_unregister(&tca->leds[i].led_cdev); - cancel_work_sync(&tca->work); - i2c_set_clientdata(client, NULL); - kfree(tca); + } return err; } -static int __devexit tca6507_remove(struct i2c_client *client) +static int tca6507_remove(struct i2c_client *client) { int i; struct tca6507_chip *tca = i2c_get_clientdata(client); @@ -745,8 +826,6 @@ static int __devexit tca6507_remove(struct i2c_client *client) } tca6507_remove_gpio(tca); cancel_work_sync(&tca->work); - i2c_set_clientdata(client, NULL); - kfree(tca); return 0; } @@ -755,24 +834,14 @@ 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 = __devexit_p(tca6507_remove), + .remove = tca6507_remove, .id_table = tca6507_id, }; -static int __init tca6507_leds_init(void) -{ - return i2c_add_driver(&tca6507_driver); -} - -static void __exit tca6507_leds_exit(void) -{ - i2c_del_driver(&tca6507_driver); -} - -module_init(tca6507_leds_init); -module_exit(tca6507_leds_exit); +module_i2c_driver(tca6507_driver); MODULE_AUTHOR("NeilBrown <neilb@suse.de>"); MODULE_DESCRIPTION("TCA6507 LED/GPO driver"); diff --git a/drivers/leds/leds-versatile.c b/drivers/leds/leds-versatile.c new file mode 100644 index 00000000000..80553022d66 --- /dev/null +++ b/drivers/leds/leds-versatile.c @@ -0,0 +1,110 @@ +/* + * Driver for the 8 user LEDs found on the RealViews and Versatiles + * Based on DaVinci's DM365 board code + * + * License terms: GNU General Public License (GPL) version 2 + * Author: Linus Walleij <triad@df.lth.se> + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/io.h> +#include <linux/slab.h> +#include <linux/leds.h> +#include <linux/platform_device.h> + +struct versatile_led { + void __iomem *base; + struct led_classdev cdev; + u8 mask; +}; + +/* + * The triggers lines up below will only be used if the + * LED triggers are compiled in. + */ +static const struct { + const char *name; + const char *trigger; +} versatile_leds[] = { + { "versatile:0", "heartbeat", }, + { "versatile:1", "mmc0", }, + { "versatile:2", "cpu0" }, + { "versatile:3", "cpu1" }, + { "versatile:4", "cpu2" }, + { "versatile:5", "cpu3" }, + { "versatile:6", }, + { "versatile:7", }, +}; + +static void versatile_led_set(struct led_classdev *cdev, + enum led_brightness b) +{ + struct versatile_led *led = container_of(cdev, + struct versatile_led, cdev); + u32 reg = readl(led->base); + + if (b != LED_OFF) + reg |= led->mask; + else + reg &= ~led->mask; + writel(reg, led->base); +} + +static enum led_brightness versatile_led_get(struct led_classdev *cdev) +{ + struct versatile_led *led = container_of(cdev, + struct versatile_led, cdev); + u32 reg = readl(led->base); + + return (reg & led->mask) ? LED_FULL : LED_OFF; +} + +static int versatile_leds_probe(struct platform_device *dev) +{ + int i; + struct resource *res; + void __iomem *base; + + res = platform_get_resource(dev, IORESOURCE_MEM, 0); + base = devm_ioremap_resource(&dev->dev, res); + if (IS_ERR(base)) + return PTR_ERR(base); + + /* All off */ + writel(0, base); + for (i = 0; i < ARRAY_SIZE(versatile_leds); i++) { + struct versatile_led *led; + + led = kzalloc(sizeof(*led), GFP_KERNEL); + if (!led) + break; + + led->base = base; + led->cdev.name = versatile_leds[i].name; + led->cdev.brightness_set = versatile_led_set; + led->cdev.brightness_get = versatile_led_get; + led->cdev.default_trigger = versatile_leds[i].trigger; + led->mask = BIT(i); + + if (led_classdev_register(NULL, &led->cdev) < 0) { + kfree(led); + break; + } + } + + return 0; +} + +static struct platform_driver versatile_leds_driver = { + .driver = { + .name = "versatile-leds", + }, + .probe = versatile_leds_probe, +}; + +module_platform_driver(versatile_leds_driver); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_DESCRIPTION("ARM Versatile LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-wm831x-status.c b/drivers/leds/leds-wm831x-status.c index 74a24cf897c..e72c974142d 100644 --- a/drivers/leds/leds-wm831x-status.c +++ b/drivers/leds/leds-wm831x-status.c @@ -10,7 +10,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/slab.h> #include <linux/leds.h> @@ -157,7 +156,7 @@ static int wm831x_status_blink_set(struct led_classdev *led_cdev, return ret; } -static const char *led_src_texts[] = { +static const char * const led_src_texts[] = { "otp", "power", "charger", @@ -230,9 +229,9 @@ static int wm831x_status_probe(struct platform_device *pdev) int id = pdev->id % ARRAY_SIZE(chip_pdata->status); int ret; - res = platform_get_resource(pdev, IORESOURCE_IO, 0); + res = platform_get_resource(pdev, IORESOURCE_REG, 0); if (res == NULL) { - dev_err(&pdev->dev, "No I/O resource\n"); + dev_err(&pdev->dev, "No register resource\n"); ret = -EINVAL; goto err; } @@ -241,13 +240,13 @@ static int wm831x_status_probe(struct platform_device *pdev) GFP_KERNEL); if (!drvdata) return -ENOMEM; - dev_set_drvdata(&pdev->dev, drvdata); + platform_set_drvdata(pdev, drvdata); drvdata->wm831x = wm831x; drvdata->reg = res->start; - if (wm831x->dev->platform_data) - chip_pdata = wm831x->dev->platform_data; + if (dev_get_platdata(wm831x->dev)) + chip_pdata = dev_get_platdata(wm831x->dev); else chip_pdata = NULL; diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c index 918d4baff1c..4133ffe2901 100644 --- a/drivers/leds/leds-wm8350.c +++ b/drivers/leds/leds-wm8350.c @@ -10,7 +10,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/err.h> @@ -129,7 +128,10 @@ static void wm8350_led_disable(struct wm8350_led *led) ret = regulator_disable(led->isink); if (ret != 0) { dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret); - regulator_enable(led->dcdc); + ret = regulator_enable(led->dcdc); + if (ret != 0) + dev_err(led->cdev.dev, "Failed to reenable DCDC: %d\n", + ret); return; } @@ -200,8 +202,8 @@ static int wm8350_led_probe(struct platform_device *pdev) { struct regulator *isink, *dcdc; struct wm8350_led *led; - struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; - int ret, i; + struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); + int i; if (pdata == NULL) { dev_err(&pdev->dev, "no platform data\n"); @@ -214,24 +216,21 @@ static int wm8350_led_probe(struct platform_device *pdev) return -EINVAL; } - isink = regulator_get(&pdev->dev, "led_isink"); + isink = devm_regulator_get(&pdev->dev, "led_isink"); if (IS_ERR(isink)) { - printk(KERN_ERR "%s: can't get ISINK\n", __func__); + dev_err(&pdev->dev, "%s: can't get ISINK\n", __func__); return PTR_ERR(isink); } - dcdc = regulator_get(&pdev->dev, "led_vcc"); + dcdc = devm_regulator_get(&pdev->dev, "led_vcc"); if (IS_ERR(dcdc)) { - printk(KERN_ERR "%s: can't get DCDC\n", __func__); - ret = PTR_ERR(dcdc); - goto err_isink; + dev_err(&pdev->dev, "%s: can't get DCDC\n", __func__); + return PTR_ERR(dcdc); } led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); - if (led == NULL) { - ret = -ENOMEM; - goto err_dcdc; - } + if (led == NULL) + return -ENOMEM; led->cdev.brightness_set = wm8350_led_set; led->cdev.default_trigger = pdata->default_trigger; @@ -257,17 +256,7 @@ static int wm8350_led_probe(struct platform_device *pdev) led->value = LED_OFF; platform_set_drvdata(pdev, led); - ret = led_classdev_register(&pdev->dev, &led->cdev); - if (ret < 0) - goto err_dcdc; - - return 0; - - err_dcdc: - regulator_put(dcdc); - err_isink: - regulator_put(isink); - return ret; + return led_classdev_register(&pdev->dev, &led->cdev); } static int wm8350_led_remove(struct platform_device *pdev) @@ -275,10 +264,8 @@ static int wm8350_led_remove(struct platform_device *pdev) struct wm8350_led *led = platform_get_drvdata(pdev); led_classdev_unregister(&led->cdev); - flush_work_sync(&led->work); + flush_work(&led->work); wm8350_led_disable(led); - regulator_put(led->dcdc); - regulator_put(led->isink); return 0; } diff --git a/drivers/leds/leds-wrap.c b/drivers/leds/leds-wrap.c index 6e21e654bb0..b358cc05eff 100644 --- a/drivers/leds/leds-wrap.c +++ b/drivers/leds/leds-wrap.c @@ -15,7 +15,7 @@ #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/err.h> -#include <asm/io.h> +#include <linux/io.h> #include <linux/scx200_gpio.h> #include <linux/module.h> diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h index e77c7f8dcdd..4c50365344a 100644 --- a/drivers/leds/leds.h +++ b/drivers/leds/leds.h @@ -17,7 +17,7 @@ #include <linux/rwsem.h> #include <linux/leds.h> -static inline void led_set_brightness(struct led_classdev *led_cdev, +static inline void __led_set_brightness(struct led_classdev *led_cdev, enum led_brightness value) { if (value > led_cdev->max_brightness) @@ -32,6 +32,8 @@ static inline int led_get_brightness(struct led_classdev *led_cdev) return led_cdev->brightness; } +void led_stop_software_blink(struct led_classdev *led_cdev); + extern struct rw_semaphore leds_list_lock; extern struct list_head leds_list; diff --git a/drivers/leds/trigger/Kconfig b/drivers/leds/trigger/Kconfig new file mode 100644 index 00000000000..49794b47b51 --- /dev/null +++ b/drivers/leds/trigger/Kconfig @@ -0,0 +1,111 @@ +menuconfig LEDS_TRIGGERS + bool "LED Trigger support" + depends on LEDS_CLASS + help + This option enables trigger support for the leds class. + These triggers allow kernel events to drive the LEDs and can + be configured via sysfs. If unsure, say Y. + +if LEDS_TRIGGERS + +config LEDS_TRIGGER_TIMER + tristate "LED Timer Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by a programmable timer + via sysfs. Some LED hardware can be programmed to start + blinking the LED without any further software interaction. + For more details read Documentation/leds/leds-class.txt. + + If unsure, say Y. + +config LEDS_TRIGGER_ONESHOT + tristate "LED One-shot Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to blink in one-shot pulses with parameters + controlled via sysfs. It's useful to notify the user on + sporadic events, when there are no clear begin and end trap points, + or on dense events, where this blinks the LED at constant rate if + rearmed continuously. + + It also shows how to use the led_blink_set_oneshot() function. + + If unsure, say Y. + +config LEDS_TRIGGER_IDE_DISK + bool "LED IDE Disk Trigger" + depends on IDE_GD_ATA + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by IDE disk activity. + If unsure, say Y. + +config LEDS_TRIGGER_HEARTBEAT + tristate "LED Heartbeat Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by a CPU load average. + The flash frequency is a hyperbolic function of the 1-minute + load average. + If unsure, say Y. + +config LEDS_TRIGGER_BACKLIGHT + tristate "LED backlight Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled as a backlight device: they + turn off and on when the display is blanked and unblanked. + + If unsure, say N. + +config LEDS_TRIGGER_CPU + bool "LED CPU Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled by active CPUs. This shows + the active CPUs across an array of LEDs so you can see which + CPUs are active on the system at any given moment. + + If unsure, say N. + +config LEDS_TRIGGER_GPIO + tristate "LED GPIO Trigger" + depends on LEDS_TRIGGERS + depends on GPIOLIB + help + This allows LEDs to be controlled by gpio events. It's good + when using gpios as switches and triggering the needed LEDs + from there. One use case is n810's keypad LEDs that could + be triggered by this trigger when user slides up to show + keypad. + + If unsure, say N. + +config LEDS_TRIGGER_DEFAULT_ON + tristate "LED Default ON Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be initialised in the ON state. + If unsure, say Y. + +comment "iptables trigger is under Netfilter config (LED target)" + depends on LEDS_TRIGGERS + +config LEDS_TRIGGER_TRANSIENT + tristate "LED Transient Trigger" + depends on LEDS_TRIGGERS + help + This allows one time activation of a transient state on + GPIO/PWM based hardware. + If unsure, say Y. + +config LEDS_TRIGGER_CAMERA + tristate "LED Camera Flash/Torch Trigger" + depends on LEDS_TRIGGERS + help + This allows LEDs to be controlled as a camera flash/torch device. + This enables direct flash/torch on/off by the driver, kernel space. + If unsure, say Y. + +endif # LEDS_TRIGGERS diff --git a/drivers/leds/trigger/Makefile b/drivers/leds/trigger/Makefile new file mode 100644 index 00000000000..1abf48dacf7 --- /dev/null +++ b/drivers/leds/trigger/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o +obj-$(CONFIG_LEDS_TRIGGER_ONESHOT) += ledtrig-oneshot.o +obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o +obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o +obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o +obj-$(CONFIG_LEDS_TRIGGER_GPIO) += ledtrig-gpio.o +obj-$(CONFIG_LEDS_TRIGGER_CPU) += ledtrig-cpu.o +obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o +obj-$(CONFIG_LEDS_TRIGGER_TRANSIENT) += ledtrig-transient.o +obj-$(CONFIG_LEDS_TRIGGER_CAMERA) += ledtrig-camera.o diff --git a/drivers/leds/ledtrig-backlight.c b/drivers/leds/trigger/ledtrig-backlight.c index 2b513a2ad7d..47e55aa9eef 100644 --- a/drivers/leds/ledtrig-backlight.c +++ b/drivers/leds/trigger/ledtrig-backlight.c @@ -16,7 +16,7 @@ #include <linux/init.h> #include <linux/fb.h> #include <linux/leds.h> -#include "leds.h" +#include "../leds.h" #define BLANK 1 #define UNBLANK 0 @@ -36,26 +36,28 @@ static int fb_notifier_callback(struct notifier_block *p, struct bl_trig_notifier, notifier); struct led_classdev *led = n->led; struct fb_event *fb_event = data; - int *blank = fb_event->data; - int new_status = *blank ? BLANK : UNBLANK; + int *blank; + int new_status; - switch (event) { - case FB_EVENT_BLANK : - if (new_status == n->old_status) - break; + /* If we aren't interested in this event, skip it immediately ... */ + if (event != FB_EVENT_BLANK) + 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); - } + blank = fb_event->data; + new_status = *blank ? BLANK : UNBLANK; - n->old_status = new_status; + if (new_status == n->old_status) + return 0; - break; + 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; } @@ -76,7 +78,7 @@ static ssize_t bl_trig_invert_store(struct device *dev, unsigned long invert; int ret; - ret = strict_strtoul(buf, 10, &invert); + ret = kstrtoul(buf, 10, &invert); if (ret < 0) return ret; @@ -87,9 +89,9 @@ static ssize_t bl_trig_invert_store(struct device *dev, /* After inverting, we need to update the LED. */ if ((n->old_status == BLANK) ^ n->invert) - led_set_brightness(led, LED_OFF); + __led_set_brightness(led, LED_OFF); else - led_set_brightness(led, n->brightness); + __led_set_brightness(led, n->brightness); return num; } @@ -120,6 +122,7 @@ static void bl_trig_activate(struct led_classdev *led) ret = fb_register_client(&n->notifier); if (ret) dev_err(led->dev, "unable to register backlight trigger\n"); + led->activated = true; return; @@ -133,10 +136,11 @@ static void bl_trig_deactivate(struct led_classdev *led) struct bl_trig_notifier *n = (struct bl_trig_notifier *) led->trigger_data; - if (n) { + if (led->activated) { device_remove_file(led->dev, &dev_attr_inverted); fb_unregister_client(&n->notifier); kfree(n); + led->activated = false; } } diff --git a/drivers/leds/trigger/ledtrig-camera.c b/drivers/leds/trigger/ledtrig-camera.c new file mode 100644 index 00000000000..9bd73a8bad5 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-camera.c @@ -0,0 +1,57 @@ +/* + * Camera Flash and Torch On/Off Trigger + * + * based on ledtrig-ide-disk.c + * + * Copyright 2013 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/leds.h> + +DEFINE_LED_TRIGGER(ledtrig_flash); +DEFINE_LED_TRIGGER(ledtrig_torch); + +void ledtrig_flash_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_flash, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_flash_ctrl); + +void ledtrig_torch_ctrl(bool on) +{ + enum led_brightness brt = on ? LED_FULL : LED_OFF; + + led_trigger_event(ledtrig_torch, brt); +} +EXPORT_SYMBOL_GPL(ledtrig_torch_ctrl); + +static int __init ledtrig_camera_init(void) +{ + led_trigger_register_simple("flash", &ledtrig_flash); + led_trigger_register_simple("torch", &ledtrig_torch); + return 0; +} +module_init(ledtrig_camera_init); + +static void __exit ledtrig_camera_exit(void) +{ + led_trigger_unregister_simple(ledtrig_torch); + led_trigger_unregister_simple(ledtrig_flash); +} +module_exit(ledtrig_camera_exit); + +MODULE_DESCRIPTION("LED Trigger for Camera Flash/Torch Control"); +MODULE_AUTHOR("Milo Kim"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/trigger/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c new file mode 100644 index 00000000000..aec0f02b6b3 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -0,0 +1,166 @@ +/* + * ledtrig-cpu.c - LED trigger based on CPU activity + * + * This LED trigger will be registered for each possible CPU and named as + * cpu0, cpu1, cpu2, cpu3, etc. + * + * It can be bound to any LED just like other triggers using either a + * board file or via sysfs interface. + * + * An API named ledtrig_cpu is exported for any user, who want to add CPU + * activity indication in their code + * + * Copyright 2011 Linus Walleij <linus.walleij@linaro.org> + * Copyright 2011 - 2012 Bryan Wu <bryan.wu@canonical.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/percpu.h> +#include <linux/syscore_ops.h> +#include <linux/rwsem.h> +#include <linux/cpu.h> +#include "../leds.h" + +#define MAX_NAME_LEN 8 + +struct led_trigger_cpu { + char name[MAX_NAME_LEN]; + struct led_trigger *_trig; +}; + +static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); + +/** + * ledtrig_cpu - emit a CPU event as a trigger + * @evt: CPU event to be emitted + * + * Emit a CPU event on a CPU core, which will trigger a + * binded LED to turn on or turn off. + */ +void ledtrig_cpu(enum cpu_led_event ledevt) +{ + struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig); + + /* Locate the correct CPU LED */ + switch (ledevt) { + case CPU_LED_IDLE_END: + case CPU_LED_START: + /* Will turn the LED on, max brightness */ + led_trigger_event(trig->_trig, LED_FULL); + break; + + case CPU_LED_IDLE_START: + case CPU_LED_STOP: + case CPU_LED_HALTED: + /* Will turn the LED off */ + led_trigger_event(trig->_trig, LED_OFF); + break; + + default: + /* Will leave the LED as it is */ + break; + } +} +EXPORT_SYMBOL(ledtrig_cpu); + +static int ledtrig_cpu_syscore_suspend(void) +{ + ledtrig_cpu(CPU_LED_STOP); + return 0; +} + +static void ledtrig_cpu_syscore_resume(void) +{ + ledtrig_cpu(CPU_LED_START); +} + +static void ledtrig_cpu_syscore_shutdown(void) +{ + ledtrig_cpu(CPU_LED_HALTED); +} + +static struct syscore_ops ledtrig_cpu_syscore_ops = { + .shutdown = ledtrig_cpu_syscore_shutdown, + .suspend = ledtrig_cpu_syscore_suspend, + .resume = ledtrig_cpu_syscore_resume, +}; + +static int ledtrig_cpu_notify(struct notifier_block *self, + unsigned long action, void *hcpu) +{ + switch (action & ~CPU_TASKS_FROZEN) { + case CPU_STARTING: + ledtrig_cpu(CPU_LED_START); + break; + case CPU_DYING: + ledtrig_cpu(CPU_LED_STOP); + break; + } + + return NOTIFY_OK; +} + + +static struct notifier_block ledtrig_cpu_nb = { + .notifier_call = ledtrig_cpu_notify, +}; + +static int __init ledtrig_cpu_init(void) +{ + int cpu; + + /* Supports up to 9999 cpu cores */ + BUILD_BUG_ON(CONFIG_NR_CPUS > 9999); + + /* + * Registering CPU led trigger for each CPU core here + * ignores CPU hotplug, but after this CPU hotplug works + * fine with this trigger. + */ + for_each_possible_cpu(cpu) { + struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + + snprintf(trig->name, MAX_NAME_LEN, "cpu%d", cpu); + + led_trigger_register_simple(trig->name, &trig->_trig); + } + + register_syscore_ops(&ledtrig_cpu_syscore_ops); + register_cpu_notifier(&ledtrig_cpu_nb); + + pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); + + return 0; +} +module_init(ledtrig_cpu_init); + +static void __exit ledtrig_cpu_exit(void) +{ + int cpu; + + unregister_cpu_notifier(&ledtrig_cpu_nb); + + for_each_possible_cpu(cpu) { + struct led_trigger_cpu *trig = &per_cpu(cpu_trig, cpu); + + led_trigger_unregister_simple(trig->_trig); + trig->_trig = NULL; + memset(trig->name, 0, MAX_NAME_LEN); + } + + unregister_syscore_ops(&ledtrig_cpu_syscore_ops); +} +module_exit(ledtrig_cpu_exit); + +MODULE_AUTHOR("Linus Walleij <linus.walleij@linaro.org>"); +MODULE_AUTHOR("Bryan Wu <bryan.wu@canonical.com>"); +MODULE_DESCRIPTION("CPU LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c index a4ef54b9d50..81a91be8e18 100644 --- a/drivers/leds/ledtrig-default-on.c +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -15,11 +15,11 @@ #include <linux/kernel.h> #include <linux/init.h> #include <linux/leds.h> -#include "leds.h" +#include "../leds.h" static void defon_trig_activate(struct led_classdev *led_cdev) { - led_set_brightness(led_cdev, led_cdev->max_brightness); + __led_set_brightness(led_cdev, led_cdev->max_brightness); } static struct led_trigger defon_led_trigger = { diff --git a/drivers/leds/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index ecc4bf3f37a..35812e3a37f 100644 --- a/drivers/leds/ledtrig-gpio.c +++ b/drivers/leds/trigger/ledtrig-gpio.c @@ -17,7 +17,7 @@ #include <linux/workqueue.h> #include <linux/leds.h> #include <linux/slab.h> -#include "leds.h" +#include "../leds.h" struct gpio_trig_data { struct led_classdev *led; @@ -54,12 +54,12 @@ static void gpio_trig_work(struct work_struct *work) if (tmp) { if (gpio_data->desired_brightness) - led_set_brightness(gpio_data->led, + __led_set_brightness(gpio_data->led, gpio_data->desired_brightness); else - led_set_brightness(gpio_data->led, LED_FULL); + __led_set_brightness(gpio_data->led, LED_FULL); } else { - led_set_brightness(gpio_data->led, LED_OFF); + __led_set_brightness(gpio_data->led, LED_OFF); } } @@ -110,7 +110,7 @@ static ssize_t gpio_trig_inverted_store(struct device *dev, unsigned long inverted; int ret; - ret = strict_strtoul(buf, 10, &inverted); + ret = kstrtoul(buf, 10, &inverted); if (ret < 0) return ret; @@ -200,6 +200,7 @@ static void gpio_trig_activate(struct led_classdev *led) gpio_data->led = led; led->trigger_data = gpio_data; INIT_WORK(&gpio_data->work, gpio_trig_work); + led->activated = true; return; @@ -217,7 +218,7 @@ static void gpio_trig_deactivate(struct led_classdev *led) { struct gpio_trig_data *gpio_data = led->trigger_data; - if (gpio_data) { + if (led->activated) { device_remove_file(led->dev, &dev_attr_gpio); device_remove_file(led->dev, &dev_attr_inverted); device_remove_file(led->dev, &dev_attr_desired_brightness); @@ -225,6 +226,7 @@ static void gpio_trig_deactivate(struct led_classdev *led) if (gpio_data->gpio != 0) free_irq(gpio_to_irq(gpio_data->gpio), led); kfree(gpio_data); + led->activated = false; } } diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index 759c0bba4a8..5c8464a3317 100644 --- a/drivers/leds/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -18,7 +18,10 @@ #include <linux/timer.h> #include <linux/sched.h> #include <linux/leds.h> -#include "leds.h" +#include <linux/reboot.h> +#include "../leds.h" + +static int panic_heartbeats; struct heartbeat_trig_data { unsigned int phase; @@ -33,6 +36,11 @@ static void led_heartbeat_function(unsigned long data) unsigned long brightness = LED_OFF; unsigned long delay = 0; + if (unlikely(panic_heartbeats)) { + led_set_brightness(led_cdev, LED_OFF); + return; + } + /* acts like an actual heart beat -- ie thump-thump-pause... */ switch (heartbeat_data->phase) { case 0: @@ -66,7 +74,7 @@ static void led_heartbeat_function(unsigned long data) break; } - led_set_brightness(led_cdev, brightness); + __led_set_brightness(led_cdev, brightness); mod_timer(&heartbeat_data->timer, jiffies + delay); } @@ -83,15 +91,17 @@ static void heartbeat_trig_activate(struct led_classdev *led_cdev) led_heartbeat_function, (unsigned long) led_cdev); heartbeat_data->phase = 0; led_heartbeat_function(heartbeat_data->timer.data); + led_cdev->activated = true; } static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) { struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; - if (heartbeat_data) { + if (led_cdev->activated) { del_timer_sync(&heartbeat_data->timer); kfree(heartbeat_data); + led_cdev->activated = false; } } @@ -101,13 +111,45 @@ static struct led_trigger heartbeat_led_trigger = { .deactivate = heartbeat_trig_deactivate, }; +static int heartbeat_reboot_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + led_trigger_unregister(&heartbeat_led_trigger); + return NOTIFY_DONE; +} + +static int heartbeat_panic_notifier(struct notifier_block *nb, + unsigned long code, void *unused) +{ + panic_heartbeats = 1; + return NOTIFY_DONE; +} + +static struct notifier_block heartbeat_reboot_nb = { + .notifier_call = heartbeat_reboot_notifier, +}; + +static struct notifier_block heartbeat_panic_nb = { + .notifier_call = heartbeat_panic_notifier, +}; + static int __init heartbeat_trig_init(void) { - return led_trigger_register(&heartbeat_led_trigger); + int rc = led_trigger_register(&heartbeat_led_trigger); + + if (!rc) { + atomic_notifier_chain_register(&panic_notifier_list, + &heartbeat_panic_nb); + register_reboot_notifier(&heartbeat_reboot_nb); + } + return rc; } static void __exit heartbeat_trig_exit(void) { + unregister_reboot_notifier(&heartbeat_reboot_nb); + atomic_notifier_chain_unregister(&panic_notifier_list, + &heartbeat_panic_nb); led_trigger_unregister(&heartbeat_led_trigger); } diff --git a/drivers/leds/ledtrig-ide-disk.c b/drivers/leds/trigger/ledtrig-ide-disk.c index ec099fcbcb0..2cd7c0cf592 100644 --- a/drivers/leds/ledtrig-ide-disk.c +++ b/drivers/leds/trigger/ledtrig-ide-disk.c @@ -12,39 +12,22 @@ */ #include <linux/module.h> -#include <linux/jiffies.h> #include <linux/kernel.h> #include <linux/init.h> -#include <linux/timer.h> #include <linux/leds.h> -static void ledtrig_ide_timerfunc(unsigned long data); +#define BLINK_DELAY 30 DEFINE_LED_TRIGGER(ledtrig_ide); -static DEFINE_TIMER(ledtrig_ide_timer, ledtrig_ide_timerfunc, 0, 0); -static int ide_activity; -static int ide_lastactivity; +static unsigned long ide_blink_delay = BLINK_DELAY; void ledtrig_ide_activity(void) { - ide_activity++; - if (!timer_pending(&ledtrig_ide_timer)) - mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10)); + led_trigger_blink_oneshot(ledtrig_ide, + &ide_blink_delay, &ide_blink_delay, 0); } EXPORT_SYMBOL(ledtrig_ide_activity); -static void ledtrig_ide_timerfunc(unsigned long data) -{ - if (ide_lastactivity != ide_activity) { - ide_lastactivity = ide_activity; - /* INT_MAX will set each LED to its maximum brightness */ - led_trigger_event(ledtrig_ide, INT_MAX); - mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10)); - } else { - led_trigger_event(ledtrig_ide, LED_OFF); - } -} - static int __init ledtrig_ide_init(void) { led_trigger_register_simple("ide-disk", &ledtrig_ide); diff --git a/drivers/leds/trigger/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c new file mode 100644 index 00000000000..cb4c7466692 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-oneshot.c @@ -0,0 +1,204 @@ +/* + * One-shot LED Trigger + * + * Copyright 2012, Fabio Baltieri <fabio.baltieri@gmail.com> + * + * Based on ledtrig-timer.c by Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/ctype.h> +#include <linux/slab.h> +#include <linux/leds.h> +#include "../leds.h" + +#define DEFAULT_DELAY 100 + +struct oneshot_trig_data { + unsigned int invert; +}; + +static ssize_t led_shot(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + + led_blink_set_oneshot(led_cdev, + &led_cdev->blink_delay_on, &led_cdev->blink_delay_off, + oneshot_data->invert); + + /* content is ignored */ + return size; +} +static ssize_t led_invert_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + + return sprintf(buf, "%u\n", oneshot_data->invert); +} + +static ssize_t led_invert_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + oneshot_data->invert = !!state; + + if (oneshot_data->invert) + __led_set_brightness(led_cdev, LED_FULL); + else + __led_set_brightness(led_cdev, LED_OFF); + + return size; +} + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_on); +} + +static ssize_t led_delay_on_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_on = state; + + return size; +} +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + return sprintf(buf, "%lu\n", led_cdev->blink_delay_off); +} + +static ssize_t led_delay_off_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + unsigned long state; + int ret; + + ret = kstrtoul(buf, 0, &state); + if (ret) + return ret; + + led_cdev->blink_delay_off = state; + + return size; +} + +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); +static DEVICE_ATTR(invert, 0644, led_invert_show, led_invert_store); +static DEVICE_ATTR(shot, 0200, NULL, led_shot); + +static void oneshot_trig_activate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data; + int rc; + + oneshot_data = kzalloc(sizeof(*oneshot_data), GFP_KERNEL); + if (!oneshot_data) + return; + + led_cdev->trigger_data = oneshot_data; + + rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); + if (rc) + goto err_out_trig_data; + rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); + if (rc) + goto err_out_delayon; + rc = device_create_file(led_cdev->dev, &dev_attr_invert); + if (rc) + goto err_out_delayoff; + rc = device_create_file(led_cdev->dev, &dev_attr_shot); + if (rc) + goto err_out_invert; + + led_cdev->blink_delay_on = DEFAULT_DELAY; + led_cdev->blink_delay_off = DEFAULT_DELAY; + + led_cdev->activated = true; + + return; + +err_out_invert: + device_remove_file(led_cdev->dev, &dev_attr_invert); +err_out_delayoff: + device_remove_file(led_cdev->dev, &dev_attr_delay_off); +err_out_delayon: + device_remove_file(led_cdev->dev, &dev_attr_delay_on); +err_out_trig_data: + kfree(led_cdev->trigger_data); +} + +static void oneshot_trig_deactivate(struct led_classdev *led_cdev) +{ + struct oneshot_trig_data *oneshot_data = led_cdev->trigger_data; + + if (led_cdev->activated) { + device_remove_file(led_cdev->dev, &dev_attr_delay_on); + device_remove_file(led_cdev->dev, &dev_attr_delay_off); + device_remove_file(led_cdev->dev, &dev_attr_invert); + device_remove_file(led_cdev->dev, &dev_attr_shot); + kfree(oneshot_data); + led_cdev->activated = false; + } + + /* Stop blinking */ + led_set_brightness(led_cdev, LED_OFF); +} + +static struct led_trigger oneshot_led_trigger = { + .name = "oneshot", + .activate = oneshot_trig_activate, + .deactivate = oneshot_trig_deactivate, +}; + +static int __init oneshot_trig_init(void) +{ + return led_trigger_register(&oneshot_led_trigger); +} + +static void __exit oneshot_trig_exit(void) +{ + led_trigger_unregister(&oneshot_led_trigger); +} + +module_init(oneshot_trig_init); +module_exit(oneshot_trig_exit); + +MODULE_AUTHOR("Fabio Baltieri <fabio.baltieri@gmail.com>"); +MODULE_DESCRIPTION("One-shot LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c index 328c64c0841..8d09327b571 100644 --- a/drivers/leds/ledtrig-timer.c +++ b/drivers/leds/trigger/ledtrig-timer.c @@ -17,7 +17,6 @@ #include <linux/device.h> #include <linux/ctype.h> #include <linux/leds.h> -#include "leds.h" static ssize_t led_delay_on_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -31,21 +30,17 @@ static ssize_t led_delay_on_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); - int ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - - if (isspace(*after)) - count++; - - if (count == size) { - led_blink_set(led_cdev, &state, &led_cdev->blink_delay_off); - led_cdev->blink_delay_on = state; - ret = count; - } + unsigned long state; + ssize_t ret = -EINVAL; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return 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, @@ -60,21 +55,17 @@ static ssize_t led_delay_off_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t size) { struct led_classdev *led_cdev = dev_get_drvdata(dev); - int ret = -EINVAL; - char *after; - unsigned long state = simple_strtoul(buf, &after, 10); - size_t count = after - buf; - - if (isspace(*after)) - count++; - - if (count == size) { - led_blink_set(led_cdev, &led_cdev->blink_delay_on, &state); - led_cdev->blink_delay_off = state; - ret = count; - } + unsigned long state; + ssize_t ret = -EINVAL; - return ret; + 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); @@ -95,8 +86,7 @@ static void timer_trig_activate(struct led_classdev *led_cdev) led_blink_set(led_cdev, &led_cdev->blink_delay_on, &led_cdev->blink_delay_off); - - led_cdev->trigger_data = (void *)1; + led_cdev->activated = true; return; @@ -106,13 +96,14 @@ err_out_delayon: static void timer_trig_deactivate(struct led_classdev *led_cdev) { - if (led_cdev->trigger_data) { + if (led_cdev->activated) { device_remove_file(led_cdev->dev, &dev_attr_delay_on); device_remove_file(led_cdev->dev, &dev_attr_delay_off); + led_cdev->activated = false; } /* Stop blinking */ - led_brightness_set(led_cdev, LED_OFF); + led_set_brightness(led_cdev, LED_OFF); } static struct led_trigger timer_led_trigger = { diff --git a/drivers/leds/trigger/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c new file mode 100644 index 00000000000..e5abc00bb00 --- /dev/null +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -0,0 +1,237 @@ +/* + * LED Kernel Transient Trigger + * + * Copyright (C) 2012 Shuah Khan <shuahkhan@gmail.com> + * + * Based on Richard Purdie's ledtrig-timer.c and Atsushi Nemoto's + * ledtrig-heartbeat.c + * Design and use-case input from Jonas Bonn <jonas@southpole.se> and + * Neil Brown <neilb@suse.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +/* + * Transient trigger allows one shot timer activation. Please refer to + * Documentation/leds/ledtrig-transient.txt for details +*/ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <linux/timer.h> +#include <linux/leds.h> +#include "../leds.h" + +struct transient_trig_data { + int activate; + int state; + int restore_state; + unsigned long duration; + struct timer_list timer; +}; + +static void transient_timer_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *) data; + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + transient_data->activate = 0; + __led_set_brightness(led_cdev, transient_data->restore_state); +} + +static ssize_t transient_activate_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + return sprintf(buf, "%d\n", transient_data->activate); +} + +static ssize_t transient_activate_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + /* cancel the running timer */ + if (state == 0 && transient_data->activate == 1) { + del_timer(&transient_data->timer); + transient_data->activate = state; + __led_set_brightness(led_cdev, transient_data->restore_state); + return size; + } + + /* start timer if there is no active timer */ + if (state == 1 && transient_data->activate == 0 && + transient_data->duration != 0) { + transient_data->activate = state; + __led_set_brightness(led_cdev, transient_data->state); + transient_data->restore_state = + (transient_data->state == LED_FULL) ? LED_OFF : LED_FULL; + mod_timer(&transient_data->timer, + jiffies + transient_data->duration); + } + + /* state == 0 && transient_data->activate == 0 + timer is not active - just return */ + /* state == 1 && transient_data->activate == 1 + timer is already active - just return */ + + return size; +} + +static ssize_t transient_duration_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + return sprintf(buf, "%lu\n", transient_data->duration); +} + +static ssize_t transient_duration_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + transient_data->duration = state; + return size; +} + +static ssize_t transient_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + int state; + + state = (transient_data->state == LED_FULL) ? 1 : 0; + return sprintf(buf, "%d\n", state); +} + +static ssize_t transient_state_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct transient_trig_data *transient_data = led_cdev->trigger_data; + unsigned long state; + ssize_t ret; + + ret = kstrtoul(buf, 10, &state); + if (ret) + return ret; + + if (state != 1 && state != 0) + return -EINVAL; + + transient_data->state = (state == 1) ? LED_FULL : LED_OFF; + return size; +} + +static DEVICE_ATTR(activate, 0644, transient_activate_show, + transient_activate_store); +static DEVICE_ATTR(duration, 0644, transient_duration_show, + transient_duration_store); +static DEVICE_ATTR(state, 0644, transient_state_show, transient_state_store); + +static void transient_trig_activate(struct led_classdev *led_cdev) +{ + int rc; + struct transient_trig_data *tdata; + + tdata = kzalloc(sizeof(struct transient_trig_data), GFP_KERNEL); + if (!tdata) { + dev_err(led_cdev->dev, + "unable to allocate transient trigger\n"); + return; + } + led_cdev->trigger_data = tdata; + + rc = device_create_file(led_cdev->dev, &dev_attr_activate); + if (rc) + goto err_out; + + rc = device_create_file(led_cdev->dev, &dev_attr_duration); + if (rc) + goto err_out_duration; + + rc = device_create_file(led_cdev->dev, &dev_attr_state); + if (rc) + goto err_out_state; + + setup_timer(&tdata->timer, transient_timer_function, + (unsigned long) led_cdev); + led_cdev->activated = true; + + return; + +err_out_state: + device_remove_file(led_cdev->dev, &dev_attr_duration); +err_out_duration: + device_remove_file(led_cdev->dev, &dev_attr_activate); +err_out: + dev_err(led_cdev->dev, "unable to register transient trigger\n"); + led_cdev->trigger_data = NULL; + kfree(tdata); +} + +static void transient_trig_deactivate(struct led_classdev *led_cdev) +{ + struct transient_trig_data *transient_data = led_cdev->trigger_data; + + if (led_cdev->activated) { + del_timer_sync(&transient_data->timer); + __led_set_brightness(led_cdev, transient_data->restore_state); + device_remove_file(led_cdev->dev, &dev_attr_activate); + device_remove_file(led_cdev->dev, &dev_attr_duration); + device_remove_file(led_cdev->dev, &dev_attr_state); + led_cdev->trigger_data = NULL; + led_cdev->activated = false; + kfree(transient_data); + } +} + +static struct led_trigger transient_trigger = { + .name = "transient", + .activate = transient_trig_activate, + .deactivate = transient_trig_deactivate, +}; + +static int __init transient_trig_init(void) +{ + return led_trigger_register(&transient_trigger); +} + +static void __exit transient_trig_exit(void) +{ + led_trigger_unregister(&transient_trigger); +} + +module_init(transient_trig_init); +module_exit(transient_trig_exit); + +MODULE_AUTHOR("Shuah Khan <shuahkhan@gmail.com>"); +MODULE_DESCRIPTION("Transient LED trigger"); +MODULE_LICENSE("GPL"); |
