diff options
Diffstat (limited to 'drivers/leds')
63 files changed, 4535 insertions, 2703 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index b58bc8a14b9..a1b044e7eaa 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -154,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 @@ -173,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 @@ -193,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. @@ -205,6 +214,7 @@ config LEDS_LP5521 config LEDS_LP5523 tristate "LED Support for TI/National LP5523/55231 LED driver chip" depends on LEDS_CLASS && I2C + select LEDS_LP55XX_COMMON help If you say yes here you get support for TI/National Semiconductor LP5523/55231 LED driver. @@ -212,6 +222,28 @@ config LEDS_LP5523 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 @@ -259,13 +291,14 @@ config LEDS_PCA955X LED driver chips accessed via the I2C bus. Supported devices include PCA9550, PCA9551, PCA9552, and PCA9553. -config LEDS_PCA9633 - tristate "LED support for PCA9633 I2C chip" +config LEDS_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 PCA9633 - LED driver chip accessed via the I2C bus. + 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" @@ -310,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 @@ -342,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 @@ -368,20 +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 || \ - MACH_NETSPACE_V2_DT || MACH_INETSPACE_V2_DT || \ - MACH_NETSPACE_MAX_V2_DT || MACH_NETSPACE_MINI_V2_DT + depends on ARCH_KIRKWOOD || MACH_KIRKWOOD default y help This option enable support for the dual-GPIO LED found on the @@ -390,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 @@ -409,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=y && 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 @@ -446,11 +464,13 @@ config LEDS_LM355x 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. +comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)" + config LEDS_BLINKM tristate "LED support for the BlinkM I2C RGB LED" depends on LEDS_CLASS @@ -459,106 +479,15 @@ config LEDS_BLINKM This option enables support for the BlinkM RGB LED connected through I2C. Say Y to enable support for the BlinkM LED. -config LEDS_TRIGGERS - bool "LED Trigger support" +config LEDS_VERSATILE + tristate "LED support for the ARM Versatile and RealView" + depends on ARCH_REALVIEW || ARCH_VERSATILE depends on LEDS_CLASS help - This option enables trigger support for the leds class. - These triggers allow kernel events to drive the LEDs and can - be configured via sysfs. If unsure, say Y. + This option enabled support for the LEDs on the ARM Versatile + and RealView boards. Say Y to enabled these. 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_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. +source "drivers/leds/trigger/Kconfig" endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 3fb9641b619..79c5155199a 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -23,8 +23,11 @@ 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 @@ -32,7 +35,7 @@ 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_PCA9633) += leds-pca9633.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 @@ -47,21 +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_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_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 a20752f562b..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,7 +40,7 @@ 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); @@ -57,6 +57,7 @@ static ssize_t led_brightness_store(struct device *dev, return size; } +static DEVICE_ATTR_RW(brightness); static ssize_t led_max_brightness_show(struct device *dev, struct device_attribute *attr, char *buf) @@ -65,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); -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), #ifdef CONFIG_LEDS_TRIGGERS - __ATTR(trigger, 0644, led_trigger_show, led_trigger_store), +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 - __ATTR_NULL, + +static struct attribute *led_class_attrs[] = { + &dev_attr_brightness.attr, + &dev_attr_max_brightness.attr, + NULL, +}; + +static const struct attribute_group led_group = { + .attrs = led_class_attrs, +}; + +static const struct attribute_group *led_groups[] = { + &led_group, +#ifdef CONFIG_LEDS_TRIGGERS + &led_trigger_group, +#endif + NULL, }; static void led_timer_function(unsigned long data) @@ -156,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); @@ -176,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. @@ -252,9 +279,8 @@ 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 ce8921a753a..71b40d3bf77 100644 --- a/drivers/leds/led-core.c +++ b/drivers/leds/led-core.c @@ -39,9 +39,11 @@ static void led_set_software_blink(struct led_classdev *led_cdev, led_cdev->blink_delay_on = delay_on; led_cdev->blink_delay_off = delay_off; - /* never on - don't blink */ - if (!delay_on) + /* never on - just set to off */ + if (!delay_on) { + __led_set_brightness(led_cdev, LED_OFF); return; + } /* never off - just set to brightness */ if (!delay_off) { diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 3c972b2f989..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> @@ -220,9 +219,12 @@ void led_trigger_unregister(struct led_trigger *trig) { struct led_classdev *led_cdev; + if (list_empty_careful(&trig->next_trig)) + return; + /* Remove from the list of led triggers */ down_write(&triggers_list_lock); - list_del(&trig->next_trig); + list_del_init(&trig->next_trig); up_write(&triggers_list_lock); /* Remove anyone actively using this trigger */ @@ -242,18 +244,14 @@ EXPORT_SYMBOL_GPL(led_trigger_unregister); void led_trigger_event(struct led_trigger *trig, enum led_brightness brightness) { - struct list_head *entry; + struct led_classdev *led_cdev; if (!trig) return; read_lock(&trig->leddev_list_lock); - list_for_each(entry, &trig->led_cdevs) { - struct led_classdev *led_cdev; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) led_set_brightness(led_cdev, brightness); - } read_unlock(&trig->leddev_list_lock); } EXPORT_SYMBOL_GPL(led_trigger_event); @@ -264,16 +262,13 @@ static void led_trigger_blink_setup(struct led_trigger *trig, int oneshot, int invert) { - struct list_head *entry; + struct led_classdev *led_cdev; if (!trig) return; read_lock(&trig->leddev_list_lock); - list_for_each(entry, &trig->led_cdevs) { - struct led_classdev *led_cdev; - - led_cdev = list_entry(entry, struct led_classdev, trig_list); + list_for_each_entry(led_cdev, &trig->led_cdevs, trig_list) { if (oneshot) led_blink_set_oneshot(led_cdev, delay_on, delay_off, invert); diff --git a/drivers/leds/leds-88pm860x.c b/drivers/leds/leds-88pm860x.c index 6be2edd4117..c2def5551ce 100644 --- a/drivers/leds/leds-88pm860x.c +++ b/drivers/leds/leds-88pm860x.c @@ -11,7 +11,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/i2c.h> @@ -128,11 +127,12 @@ static void pm860x_led_set(struct led_classdev *cdev, static int pm860x_led_dt_init(struct platform_device *pdev, struct pm860x_led *data) { - struct device_node *nproot = pdev->dev.parent->of_node, *np; + struct device_node *nproot, *np; int iset = 0; - if (!nproot) + + if (!pdev->dev.parent->of_node) return -ENODEV; - nproot = of_find_node_by_name(nproot, "leds"); + 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; @@ -145,6 +145,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev, break; } } + of_node_put(nproot); return 0; } #else @@ -154,7 +155,7 @@ static int pm860x_led_dt_init(struct platform_device *pdev, static int pm860x_led_probe(struct platform_device *pdev) { struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); - struct pm860x_led_pdata *pdata = pdev->dev.platform_data; + struct pm860x_led_pdata *pdata = dev_get_platdata(&pdev->dev); struct pm860x_led *data; struct resource *res; int ret = 0; @@ -201,7 +202,7 @@ static int pm860x_led_probe(struct platform_device *pdev) sprintf(data->name, "led1-blue"); break; } - dev_set_drvdata(&pdev->dev, data); + platform_set_drvdata(pdev, data); data->chip = chip; data->i2c = (chip->id == CHIP_PM8606) ? chip->client : chip->companion; data->port = pdev->id; diff --git a/drivers/leds/leds-adp5520.c b/drivers/leds/leds-adp5520.c index e8072abe76e..5036d7b4f82 100644 --- a/drivers/leds/leds-adp5520.c +++ b/drivers/leds/leds-adp5520.c @@ -15,7 +15,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> @@ -87,7 +86,7 @@ static int adp5520_led_setup(struct adp5520_led *led) static int adp5520_led_prepare(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct device *dev = pdev->dev.parent; int ret = 0; @@ -103,7 +102,7 @@ static int adp5520_led_prepare(struct platform_device *pdev) static int adp5520_led_probe(struct platform_device *pdev) { - struct adp5520_leds_platform_data *pdata = pdev->dev.platform_data; + struct adp5520_leds_platform_data *pdata = dev_get_platdata(&pdev->dev); struct adp5520_led *led, *led_dat; struct led_info *cur_led; int ret, i; @@ -121,13 +120,10 @@ static int adp5520_led_probe(struct platform_device *pdev) led = devm_kzalloc(&pdev->dev, sizeof(*led) * pdata->num_leds, GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "failed to alloc memory\n"); + if (!led) return -ENOMEM; - } ret = adp5520_led_prepare(pdev); - if (ret) { dev_err(&pdev->dev, "failed to write\n"); return ret; @@ -185,7 +181,7 @@ err: 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; diff --git a/drivers/leds/leds-asic3.c b/drivers/leds/leds-asic3.c index b474745e001..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> @@ -94,7 +93,7 @@ static int blink_set(struct led_classdev *cdev, 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); @@ -127,13 +126,14 @@ out: 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); 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); @@ -159,11 +159,9 @@ 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, diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c index 386773532d9..56cec8d6a2a 100644 --- a/drivers/leds/leds-atmel-pwm.c +++ b/drivers/leds/leds-atmel-pwm.c @@ -42,7 +42,7 @@ static int pwmled_probe(struct platform_device *pdev) int i; int status; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); if (!pdata || pdata->num_leds < 1) return -ENODEV; @@ -113,13 +113,13 @@ err: 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,7 +129,6 @@ static int __exit pwmled_remove(struct platform_device *pdev) pwm_channel_free(&led->pwmc); } - platform_set_drvdata(pdev, NULL); return 0; } @@ -140,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 851517030cc..6078c15d345 100644 --- a/drivers/leds/leds-bd2802.c +++ b/drivers/leds/leds-bd2802.c @@ -678,13 +678,11 @@ static int bd2802_probe(struct i2c_client *client, int ret, i; led = devm_kzalloc(&client->dev, sizeof(struct bd2802_led), GFP_KERNEL); - if (!led) { - dev_err(&client->dev, "failed to allocate driver data\n"); + if (!led) return -ENOMEM; - } led->client = client; - pdata = led->pdata = client->dev.platform_data; + pdata = led->pdata = dev_get_platdata(&client->dev); i2c_set_clientdata(client, led); /* Configure RESET GPIO (L: RESET, H: RESET cancel) */ @@ -732,7 +730,7 @@ failed_unregister_dev_file: 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; @@ -747,8 +745,7 @@ static int __exit bd2802_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM - +#ifdef CONFIG_PM_SLEEP static void bd2802_restore_state(struct bd2802_led *led) { int i; @@ -785,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 }, @@ -801,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 index a502678cc7f..d0452b099ae 100644 --- a/drivers/leds/leds-blinkm.c +++ b/drivers/leds/leds-blinkm.c @@ -18,7 +18,6 @@ */ #include <linux/module.h> -#include <linux/init.h> #include <linux/slab.h> #include <linux/jiffies.h> #include <linux/i2c.h> @@ -161,13 +160,10 @@ static ssize_t show_color_common(struct device *dev, char *buf, int color) switch (color) { case RED: return scnprintf(buf, PAGE_SIZE, "%02X\n", data->red); - break; case GREEN: return scnprintf(buf, PAGE_SIZE, "%02X\n", data->green); - break; case BLUE: return scnprintf(buf, PAGE_SIZE, "%02X\n", data->blue); - break; default: return -EINVAL; } @@ -447,7 +443,7 @@ static void led_work(struct work_struct *work) { int ret; struct blinkm_led *led; - struct blinkm_data *data ; + struct blinkm_data *data; struct blinkm_work *blm_work = work_to_blmwork(work); led = blm_work->blinkm_led; diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c index 6a8405df76a..f58a354428e 100644 --- a/drivers/leds/leds-clevo-mail.c +++ b/drivers/leds/leds-clevo-mail.c @@ -19,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"); @@ -40,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 clevo_mail_led_dmi_table[] = { +static struct dmi_system_id clevo_mail_led_dmi_table[] __initdata = { { .callback = clevo_mail_led_dmi_callback, .ident = "Clevo D410J", @@ -153,7 +153,7 @@ static struct led_classdev clevo_mail_led = { .flags = LED_CORE_SUSPENDRESUME, }; -static int clevo_mail_led_probe(struct platform_device *pdev) +static int __init clevo_mail_led_probe(struct platform_device *pdev) { return led_classdev_register(&pdev->dev, &clevo_mail_led); } @@ -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, diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c index 8abcb66db01..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> diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c index c263a21db82..54b8b5216b8 100644 --- a/drivers/leds/leds-da903x.c +++ b/drivers/leds/leds-da903x.c @@ -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> @@ -93,7 +92,7 @@ static void da903x_led_set(struct led_classdev *led_cdev, static int da903x_led_probe(struct platform_device *pdev) { - struct led_info *pdata = pdev->dev.platform_data; + struct led_info *pdata = dev_get_platdata(&pdev->dev); struct da903x_led *led; int id, ret; @@ -109,10 +108,8 @@ static int da903x_led_probe(struct platform_device *pdev) } led = devm_kzalloc(&pdev->dev, sizeof(struct da903x_led), GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id); + if (!led) return -ENOMEM; - } led->cdev.name = pdata->name; led->cdev.default_trigger = pdata->default_trigger; diff --git a/drivers/leds/leds-da9052.c b/drivers/leds/leds-da9052.c index efec43344e9..e4da1f460ac 100644 --- a/drivers/leds/leds-da9052.c +++ b/drivers/leds/leds-da9052.c @@ -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> @@ -112,7 +111,7 @@ static int da9052_led_probe(struct platform_device *pdev) int i; da9052 = dev_get_drvdata(pdev->dev.parent); - pdata = da9052->dev->platform_data; + pdata = dev_get_platdata(da9052->dev); if (pdata == NULL) { dev_err(&pdev->dev, "No platform data\n"); goto err; @@ -127,8 +126,7 @@ static int da9052_led_probe(struct platform_device *pdev) led = devm_kzalloc(&pdev->dev, sizeof(struct da9052_led) * pled->num_leds, GFP_KERNEL); - if (led == NULL) { - dev_err(&pdev->dev, "Failed to alloc memory\n"); + if (!led) { error = -ENOMEM; goto err; } @@ -185,7 +183,7 @@ static int da9052_led_remove(struct platform_device *pdev) int i; da9052 = dev_get_drvdata(pdev->dev.parent); - pdata = da9052->dev->platform_data; + pdata = dev_get_platdata(da9052->dev); pled = pdata->pled; for (i = 0; i < pled->num_leds; i++) { diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c index 1f9d8e62d37..db3ba8b4251 100644 --- a/drivers/leds/leds-dac124s085.c +++ b/drivers/leds/leds-dac124s085.c @@ -101,7 +101,6 @@ eledcr: while (i--) led_classdev_unregister(&dac->leds[i].ldev); - spi_set_drvdata(spi, NULL); return ret; } @@ -115,8 +114,6 @@ static int dac124s085_remove(struct spi_device *spi) cancel_work_sync(&dac->leds[i].work); } - spi_set_drvdata(spi, NULL); - return 0; } diff --git a/drivers/leds/leds-fsg.c b/drivers/leds/leds-fsg.c index b4d5a44cc41..2b4dc738dcd 100644 --- a/drivers/leds/leds-fsg.c +++ b/drivers/leds/leds-fsg.c @@ -16,7 +16,6 @@ */ #include <linux/kernel.h> -#include <linux/init.h> #include <linux/platform_device.h> #include <linux/leds.h> #include <linux/module.h> diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index a0d931bcb37..57ff20fecf5 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -11,16 +11,15 @@ * */ #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 <linux/pinctrl/consumer.h> #include <linux/err.h> struct gpio_led_data { @@ -107,6 +106,10 @@ static int create_gpio_led(const struct gpio_led *template, return 0; } + ret = devm_gpio_request(parent, template->gpio, template->name); + if (ret < 0) + return ret; + led_dat->cdev.name = template->name; led_dat->cdev.default_trigger = template->default_trigger; led_dat->gpio = template->gpio; @@ -126,10 +129,7 @@ static int create_gpio_led(const struct gpio_led *template, if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = devm_gpio_request_one(parent, template->gpio, - (led_dat->active_low ^ state) ? - GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, - template->name); + ret = gpio_direction_output(led_dat->gpio, led_dat->active_low ^ state); if (ret < 0) return ret; @@ -170,11 +170,11 @@ static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) int count, ret; /* count LEDs in this device, so we know how much to allocate */ - count = of_get_child_count(np); + count = of_get_available_child_count(np); if (!count) return ERR_PTR(-ENODEV); - for_each_child_of_node(np, child) + for_each_available_child_of_node(np, child) if (of_get_gpio(child, 0) == -EPROBE_DEFER) return ERR_PTR(-EPROBE_DEFER); @@ -183,7 +183,7 @@ static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) if (!priv) return ERR_PTR(-ENOMEM); - for_each_child_of_node(np, child) { + for_each_available_child_of_node(np, child) { struct gpio_led led = {}; enum of_gpio_flags flags; const char *state; @@ -203,6 +203,9 @@ static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) 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) { @@ -223,6 +226,8 @@ static const struct of_device_id of_gpio_leds_match[] = { { .compatible = "gpio-leds", }, {}, }; + +MODULE_DEVICE_TABLE(of, of_gpio_leds_match); #else /* CONFIG_OF_GPIO */ static struct gpio_leds_priv *gpio_leds_create_of(struct platform_device *pdev) { @@ -233,15 +238,10 @@ static struct gpio_leds_priv *gpio_leds_create_of(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; - struct pinctrl *pinctrl; int i, ret = 0; - pinctrl = devm_pinctrl_get_select_default(&pdev->dev); - if (IS_ERR(pinctrl)) - dev_warn(&pdev->dev, - "pins are not configured from the driver\n"); if (pdata && pdata->num_leds) { priv = devm_kzalloc(&pdev->dev, @@ -281,8 +281,6 @@ static int gpio_led_remove(struct platform_device *pdev) for (i = 0; i < priv->num_leds; i++) delete_gpio_led(&priv->leds[i]); - platform_set_drvdata(pdev, NULL); - return 0; } 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 21414548383..652368c2ea9 100644 --- a/drivers/leds/leds-lm3530.c +++ b/drivers/leds/leds-lm3530.c @@ -187,6 +187,40 @@ static void lm3530_als_configure(struct lm3530_platform_data *pdata, (pdata->als2_resistor_sel << LM3530_ALS2_IMP_SHIFT); } +static int lm3530_led_enable(struct lm3530_data *drvdata) +{ + int ret; + + if (drvdata->enable) + return 0; + + ret = regulator_enable(drvdata->regulator); + if (ret) { + dev_err(drvdata->led_dev.dev, "Failed to enable vin:%d\n", ret); + return ret; + } + + drvdata->enable = true; + return 0; +} + +static void lm3530_led_disable(struct lm3530_data *drvdata) +{ + int ret; + + if (!drvdata->enable) + return; + + ret = regulator_disable(drvdata->regulator); + if (ret) { + dev_err(drvdata->led_dev.dev, "Failed to disable vin:%d\n", + ret); + return; + } + + drvdata->enable = false; +} + static int lm3530_init_registers(struct lm3530_data *drvdata) { int ret = 0; @@ -245,15 +279,9 @@ static int lm3530_init_registers(struct lm3530_data *drvdata) reg_val[12] = LM3530_DEF_ZT_3; /* LM3530_ALS_Z3T_REG */ reg_val[13] = 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; - } + 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 */ @@ -305,13 +333,8 @@ static void lm3530_brightness_set(struct led_classdev *led_cdev, else 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; @@ -380,7 +403,7 @@ static DEVICE_ATTR(mode, 0644, lm3530_mode_get, lm3530_mode_set); static int lm3530_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm3530_platform_data *pdata = client->dev.platform_data; + struct lm3530_platform_data *pdata = dev_get_platdata(&client->dev); struct lm3530_data *drvdata; int err = 0; @@ -458,8 +481,7 @@ static int lm3530_remove(struct i2c_client *client) device_remove_file(drvdata->led_dev.dev, &dev_attr_mode); - if (drvdata->enable) - regulator_disable(drvdata->regulator); + lm3530_led_disable(drvdata); led_classdev_unregister(&drvdata->led_dev); return 0; } diff --git a/drivers/leds/leds-lm3533.c b/drivers/leds/leds-lm3533.c index bbf24d038a7..e2c642c1169 100644 --- a/drivers/leds/leds-lm3533.c +++ b/drivers/leds/leds-lm3533.c @@ -12,7 +12,6 @@ */ #include <linux/module.h> -#include <linux/init.h> #include <linux/leds.h> #include <linux/mfd/core.h> #include <linux/mutex.h> @@ -671,7 +670,7 @@ static int lm3533_led_probe(struct platform_device *pdev) if (!lm3533) return -EINVAL; - pdata = pdev->dev.platform_data; + pdata = dev_get_platdata(&pdev->dev); if (!pdata) { dev_err(&pdev->dev, "no platform data\n"); return -EINVAL; diff --git a/drivers/leds/leds-lm355x.c b/drivers/leds/leds-lm355x.c index 65d79284c48..591eb5e58ae 100644 --- a/drivers/leds/leds-lm355x.c +++ b/drivers/leds/leds-lm355x.c @@ -380,7 +380,7 @@ static void lm355x_indicator_brightness_set(struct led_classdev *cdev, /* indicator pattern only for lm3556*/ static ssize_t lm3556_indicator_pattern_store(struct device *dev, - struct device_attribute *devAttr, + struct device_attribute *attr, const char *buf, size_t size) { ssize_t ret; @@ -423,7 +423,7 @@ static const struct regmap_config lm355x_regmap = { static int lm355x_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm355x_platform_data *pdata = client->dev.platform_data; + struct lm355x_platform_data *pdata = dev_get_platdata(&client->dev); struct lm355x_chip_data *chip; int err; @@ -477,6 +477,7 @@ static int lm355x_probe(struct i2c_client *client, 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) @@ -486,6 +487,7 @@ static int lm355x_probe(struct i2c_client *client, 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) diff --git a/drivers/leds/leds-lm3642.c b/drivers/leds/leds-lm3642.c index 07b3dde9061..ceb6b3cde6f 100644 --- a/drivers/leds/leds-lm3642.c +++ b/drivers/leds/leds-lm3642.c @@ -176,7 +176,7 @@ out: /* torch pin config for lm3642*/ static ssize_t lm3642_torch_pin_store(struct device *dev, - struct device_attribute *devAttr, + struct device_attribute *attr, const char *buf, size_t size) { ssize_t ret; @@ -233,7 +233,7 @@ static void lm3642_torch_brightness_set(struct led_classdev *cdev, /* strobe pin config for lm3642*/ static ssize_t lm3642_strobe_pin_store(struct device *dev, - struct device_attribute *devAttr, + struct device_attribute *attr, const char *buf, size_t size) { ssize_t ret; @@ -316,7 +316,7 @@ static const struct regmap_config lm3642_regmap = { static int lm3642_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lm3642_platform_data *pdata = client->dev.platform_data; + struct lm3642_platform_data *pdata = dev_get_platdata(&client->dev); struct lm3642_chip_data *chip; int err; @@ -363,6 +363,7 @@ static int lm3642_probe(struct i2c_client *client, 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) { @@ -380,6 +381,7 @@ static int lm3642_probe(struct i2c_client *client, 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) { diff --git a/drivers/leds/leds-lp3944.c b/drivers/leds/leds-lp3944.c index 0c4386e656c..8e1abdcd4c9 100644 --- a/drivers/leds/leds-lp3944.c +++ b/drivers/leds/leds-lp3944.c @@ -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); } @@ -377,7 +377,8 @@ exit: 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; @@ -413,7 +414,7 @@ static int lp3944_probe(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; diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index cb8a5220200..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 @@ -86,357 +68,330 @@ #define LP5521_ENABLE_RUN_PROGRAM \ (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) +/* CONFIG register */ +#define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ +#define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ +#define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ +#define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ +#define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ +#define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ +#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_INT 0x01 /* Internal clock */ +#define LP5521_DEFAULT_CFG \ + (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO) + /* Status */ #define LP5521_EXT_CLK_USED 0x08 /* default R channel current register value */ #define LP5521_REG_R_CURR_DEFAULT 0xAF -/* Pattern Mode */ -#define PATTERN_OFF 0 +/* Reset register value */ +#define LP5521_RESET 0xFF -struct lp5521_engine { - int id; - u8 mode; - u8 prog_page; - u8 engine_mask; -}; +/* 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 -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; -}; +#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) -struct lp5521_chip { - struct lp5521_platform_data *pdata; - struct mutex lock; /* Serialize control */ - struct i2c_client *client; - struct lp5521_engine engines[LP5521_MAX_ENGINES]; - struct lp5521_led leds[LP5521_MAX_LEDS]; - u8 num_channels; - u8 num_leds; -}; +#define 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 struct lp5521_led *cdev_to_led(struct led_classdev *cdev) +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 inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +static void lp5521_load_engine(struct lp55xx_chip *chip) { - return i2c_smbus_write_byte_data(client, reg, value); -} + 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, + }; -static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) -{ - s32 ret; + u8 val[] = { + [LP55XX_ENGINE_1] = LP5521_LOAD_R, + [LP55XX_ENGINE_2] = LP5521_LOAD_G, + [LP55XX_ENGINE_3] = LP5521_LOAD_B, + }; - ret = i2c_smbus_read_byte_data(client, reg); - if (ret < 0) - return ret; + lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]); - *buf = ret; - return 0; + lp5521_wait_opmode_done(); } -static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode) +static void lp5521_stop_all_engines(struct lp55xx_chip *chip) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; - int ret; - u8 engine_state; + lp55xx_write(chip, LP5521_REG_OP_MODE, 0); + lp5521_wait_opmode_done(); +} - /* Only transition between RUN and DIRECT mode are handled here */ - if (mode == LP5521_CMD_LOAD) - return 0; +static void lp5521_stop_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5521_MODE_R_M, + [LP55XX_ENGINE_2] = LP5521_MODE_G_M, + [LP55XX_ENGINE_3] = LP5521_MODE_B_M, + }; - if (mode == LP5521_CMD_DISABLED) - mode = LP5521_CMD_DIRECT; + lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0); - ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state); - if (ret < 0) - return ret; - - /* 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); + lp5521_wait_opmode_done(); } -static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) +static void lp5521_run_engine(struct lp55xx_chip *chip, bool start) { - struct lp5521_chip *chip = engine_to_lp5521(eng); - struct i2c_client *client = chip->client; int ret; - int addr; u8 mode; + u8 exec; - /* move current engine to direct mode and remember the state */ - ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); + /* stop engine */ + if (!start) { + lp5521_stop_engine(chip); + lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + lp5521_wait_opmode_done(); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode); if (ret) - return ret; + return; - /* 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); + ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec); if (ret) - return ret; + return; - /* 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); - - return lp5521_write(client, LP5521_REG_OP_MODE, mode); -} + /* change operation mode to RUN only when each engine is loading */ + if (LP5521_R_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R; + exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R; + } -static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) -{ - return lp5521_write(chip->client, - LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, - curr); -} + if (LP5521_G_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G; + exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G; + } -static void lp5521_init_engine(struct lp5521_chip *chip) -{ - 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; + if (LP5521_B_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B; + exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B; } + + lp55xx_write(chip, LP5521_REG_OP_MODE, mode); + lp5521_wait_opmode_done(); + + lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec); + lp5521_wait_enable_done(); } -static int lp5521_configure(struct i2c_client *client) +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; - u8 cfg; + int offset = 0; + int i = 0; - lp5521_init_engine(chip); + 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; - /* Set all PWMs to direct control mode */ - ret = lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; - cfg = chip->pdata->update_config ? - : (LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); - ret |= lp5521_write(client, LP5521_REG_CONFIG, cfg); + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } - /* 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); + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; - /* Set engines are set to run state when OP_MODE enables engines */ - ret |= lp5521_write(client, LP5521_REG_ENABLE, - LP5521_ENABLE_RUN_PROGRAM); - /* enable takes 500us. 1 - 2 ms leaves some margin */ - usleep_range(1000, 2000); + for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) { + ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); + if (ret) + return -EINVAL; + } - return ret; + return size; + +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_ENABLE_DEFAULT); + 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_ENABLE_DEFAULT) - 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) @@ -445,222 +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 (kstrtoul(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"); -} -static void lp5521_clear_program_memory(struct i2c_client *cl) -{ - int i; - u8 rgb_mem[] = { - LP5521_REG_R_PROG_MEM, - LP5521_REG_G_PROG_MEM, - LP5521_REG_B_PROG_MEM, - }; - - for (i = 0; i < ARRAY_SIZE(rgb_mem); i++) { - lp5521_write(cl, rgb_mem[i], 0); - lp5521_write(cl, rgb_mem[i] + 1, 0); - } + return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK"); } -static void lp5521_write_program_memory(struct i2c_client *cl, - u8 base, u8 *rgb, int size) -{ - int i; - - if (!rgb || size <= 0) - return; - - for (i = 0; i < size; i++) - lp5521_write(cl, base + i, *(rgb + i)); - - lp5521_write(cl, base + i, 0); - lp5521_write(cl, base + i + 1, 0); -} - -static inline struct lp5521_led_pattern *lp5521_get_pattern - (struct lp5521_chip *chip, u8 offset) -{ - struct lp5521_led_pattern *ptn; - ptn = chip->pdata->patterns + (offset - 1); - return ptn; -} - -static void lp5521_run_led_pattern(int mode, struct lp5521_chip *chip) -{ - struct lp5521_led_pattern *ptn; - struct i2c_client *cl = chip->client; - int num_patterns = chip->pdata->num_patterns; - - if (mode > num_patterns || !(chip->pdata->patterns)) - return; - - if (mode == PATTERN_OFF) { - lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_DEFAULT); - usleep_range(1000, 2000); - lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); - } else { - ptn = lp5521_get_pattern(chip, mode); - if (!ptn) - return; - - lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); - usleep_range(1000, 2000); - - lp5521_clear_program_memory(cl); - - lp5521_write_program_memory(cl, LP5521_REG_R_PROG_MEM, - ptn->r, ptn->size_r); - lp5521_write_program_memory(cl, LP5521_REG_G_PROG_MEM, - ptn->g, ptn->size_g); - lp5521_write_program_memory(cl, LP5521_REG_B_PROG_MEM, - ptn->b, ptn->size_b); - - lp5521_write(cl, LP5521_REG_OP_MODE, LP5521_CMD_RUN); - usleep_range(1000, 2000); - lp5521_write(cl, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); - } -} - -static ssize_t store_led_pattern(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct lp5521_chip *chip = i2c_get_clientdata(to_i2c_client(dev)); - unsigned long val; - int ret; - - ret = kstrtoul(buf, 16, &val); - if (ret) - return ret; - - lp5521_run_led_pattern(val, chip); - - 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 *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 -}; - /* 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 DEVICE_ATTR(led_pattern, S_IWUSR, NULL, store_led_pattern); +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_led_pattern.attr, + &dev_attr_selftest.attr, NULL }; @@ -668,217 +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 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; - } - - led->cdev.brightness_set = lp5521_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 ?: client->name, chan); - 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 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 = 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"); - return -EINVAL; - } + 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) - return ret; - } - - 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. */ - } - - lp5521_write(client, LP5521_REG_RESET, 0xff); - usleep_range(10000, 20000); /* - * Exact value is not available. 10 - 20ms - * appears to be enough for reset. - */ + i2c_set_clientdata(client, led); - /* - * 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 = lp5521_read(client, LP5521_REG_R_CURRENT, &buf); - if (ret) { - dev_err(&client->dev, "error in resetting chip\n"); - goto fail2; - } - if (buf != LP5521_REG_R_CURR_DEFAULT) { - dev_err(&client->dev, - "unexpected data in register (expected 0x%x got 0x%x)\n", - LP5521_REG_R_CURR_DEFAULT, buf); - ret = -EINVAL; - 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 fail1; - } - - /* 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 fail2; - } - 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 fail2; + goto err_register_sysfs; } - return ret; -fail2: - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); - } -fail1: - if (pdata->enable) - pdata->enable(0); - if (pdata->release_resources) - pdata->release_resources(); + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: return ret; } static int lp5521_remove(struct i2c_client *client) { - struct lp5521_chip *chip = i2c_get_clientdata(client); - int i; + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; - lp5521_run_led_pattern(PATTERN_OFF, 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(); return 0; } @@ -888,9 +591,18 @@ static const struct i2c_device_id lp5521_id[] = { }; MODULE_DEVICE_TABLE(i2c, lp5521_id); +#ifdef CONFIG_OF +static const struct of_device_id of_lp5521_leds_match[] = { + { .compatible = "national,lp5521", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5521_leds_match); +#endif static struct i2c_driver lp5521_driver = { .driver = { .name = "lp5521", + .of_match_table = of_match_ptr(of_lp5521_leds_match), }, .probe = lp5521_probe, .remove = lp5521_remove, @@ -900,5 +612,6 @@ static struct i2c_driver lp5521_driver = { 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 7f5be8948cd..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,334 +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) enum lp5523_chip_id { LP5523, LP55231, }; -struct lp5523_engine { - int id; - u8 mode; - u8 prog_page; - u8 mux_page; - u16 led_mux; - u8 engine_mask; -}; +static int lp5523_init_program_engine(struct lp55xx_chip *chip); -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; -}; - -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 inline void lp5523_wait_opmode_done(void) +{ + usleep_range(1000, 2000); +} -static inline struct lp5523_led *cdev_to_led(struct led_classdev *cdev) +static void lp5523_set_led_current(struct lp55xx_led *led, u8 led_current) { - return container_of(cdev, struct lp5523_led, cdev); + led->led_current = led_current; + lp55xx_write(led->chip, LP5523_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); } -static inline struct lp5523_chip *engine_to_lp5523(struct lp5523_engine *engine) +static int lp5523_post_init_device(struct lp55xx_chip *chip) { - return container_of(engine, struct lp5523_chip, - engines[engine->id - 1]); + 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 inline struct lp5523_chip *led_to_lp5523(struct lp5523_led *led) +static void lp5523_load_engine(struct lp55xx_chip *chip) { - return container_of(led, struct lp5523_chip, - leds[led->id]); + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP5523_LOAD_ENG1, + [LP55XX_ENGINE_2] = LP5523_LOAD_ENG2, + [LP55XX_ENGINE_3] = LP5523_LOAD_ENG3, + }; + + lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], val[idx]); + + lp5523_wait_opmode_done(); } -static void 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, const u8 *pattern); +static void lp5523_load_engine_and_select_page(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 page_sel[] = { + [LP55XX_ENGINE_1] = LP5523_PAGE_ENG1, + [LP55XX_ENGINE_2] = LP5523_PAGE_ENG2, + [LP55XX_ENGINE_3] = LP5523_PAGE_ENG3, + }; + + lp5523_load_engine(chip); -static void lp5523_led_brightness_work(struct work_struct *work); + lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, page_sel[idx]); +} -static int lp5523_write(struct i2c_client *client, u8 reg, u8 value) +static void lp5523_stop_all_engines(struct lp55xx_chip *chip) { - return i2c_smbus_write_byte_data(client, reg, value); + lp55xx_write(chip, LP5523_REG_OP_MODE, 0); + lp5523_wait_opmode_done(); } -static int lp5523_read(struct i2c_client *client, u8 reg, u8 *buf) +static void lp5523_stop_engine(struct lp55xx_chip *chip) { - s32 ret = i2c_smbus_read_byte_data(client, reg); + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5523_MODE_ENG1_M, + [LP55XX_ENGINE_2] = LP5523_MODE_ENG2_M, + [LP55XX_ENGINE_3] = LP5523_MODE_ENG3_M, + }; - if (ret < 0) - return ret; + lp55xx_update_bits(chip, LP5523_REG_OP_MODE, mask[idx], 0); - *buf = ret; - return 0; + lp5523_wait_opmode_done(); +} + +static void lp5523_turn_off_channels(struct lp55xx_chip *chip) +{ + int i; + + for (i = 0; i < LP5523_MAX_LEDS; i++) + lp55xx_write(chip, LP5523_REG_LED_PWM_BASE + i, 0); } -static int lp5523_detect(struct i2c_client *client) +static void lp5523_run_engine(struct lp55xx_chip *chip, bool start) { int ret; - u8 buf; + u8 mode; + u8 exec; - ret = lp5523_write(client, LP5523_REG_ENABLE, LP5523_ENABLE); + /* stop engine */ + if (!start) { + lp5523_stop_engine(chip); + lp5523_turn_off_channels(chip); + return; + } + + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ + + ret = lp55xx_read(chip, LP5523_REG_OP_MODE, &mode); if (ret) - return ret; - 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 */ + /* 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); - ret = lp5523_read(client, LP5523_REG_STATUS, &status); - if (ret < 0) - return ret; - + 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, const 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; + int i; + + len = min_t(int, len, LP5523_MAX_LEDS); - len = min_t(int, len, LP5523_LEDS); for (i = 0; i < len; i++) { switch (buf[i]) { case '1': @@ -370,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; @@ -417,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; @@ -429,93 +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) @@ -530,249 +666,40 @@ 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}; - - if (engine->mode != LP5523_CMD_LOAD) - return -EINVAL; - - while ((offset < len - 1) && (i < LP5523_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto fail; - pattern[i] = (u8)cmd; - - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto fail; - - mutex_lock(&chip->lock); - ret = lp5523_load_program(engine, pattern); - mutex_unlock(&chip->lock); - - if (ret) { - dev_err(&client->dev, "failed loading pattern\n"); - return ret; - } - - return len; -fail: - dev_err(&client->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - return lp5523_do_store_load(&chip->engines[nr - 1], buf, len); -} - -#define store_load(nr) \ -static ssize_t store_engine##nr##_load(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_load(dev, attr, buf, len, nr); \ -} -store_load(1) -store_load(2) -store_load(3) - -static ssize_t show_engine_mode(struct device *dev, - struct device_attribute *attr, - char *buf, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - switch (chip->engines[nr - 1].mode) { - case LP5523_CMD_RUN: - return sprintf(buf, "run\n"); - case LP5523_CMD_LOAD: - return sprintf(buf, "load\n"); - case LP5523_CMD_DISABLED: - return sprintf(buf, "disabled\n"); - default: - return sprintf(buf, "disabled\n"); - } -} - -#define show_mode(nr) \ -static ssize_t show_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_engine_mode(dev, attr, buf, nr); \ -} -show_mode(1) -show_mode(2) -show_mode(3) - -static ssize_t store_engine_mode(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5523_chip *chip = i2c_get_clientdata(client); - struct lp5523_engine *engine = &chip->engines[nr - 1]; - mutex_lock(&chip->lock); - - if (!strncmp(buf, "run", 3)) - lp5523_set_mode(engine, LP5523_CMD_RUN); - else if (!strncmp(buf, "load", 4)) - lp5523_set_mode(engine, LP5523_CMD_LOAD); - else if (!strncmp(buf, "disabled", 8)) - lp5523_set_mode(engine, LP5523_CMD_DISABLED); - - mutex_unlock(&chip->lock); - return len; -} - -#define store_mode(nr) \ -static ssize_t store_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_mode(dev, attr, buf, len, nr); \ -} -store_mode(1) -store_mode(2) -store_mode(3) - -static ssize_t show_max_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->max_current); -} - -static ssize_t show_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->led_current); -} - -static ssize_t store_current(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5523_led *led = cdev_to_led(led_cdev); - struct lp5523_chip *chip = led_to_lp5523(led); - ssize_t ret; - unsigned long curr; - - if (kstrtoul(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, }; @@ -780,252 +707,99 @@ 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 void lp5523_set_mode(struct lp5523_engine *engine, u8 mode) -{ - /* if in that mode already do nothing, except for run */ - if (mode == engine->mode && mode != LP5523_CMD_RUN) - return; - - switch (mode) { - case LP5523_CMD_RUN: - lp5523_run_program(engine); - break; - case LP5523_CMD_LOAD: - lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); - lp5523_set_engine_mode(engine, LP5523_CMD_LOAD); - break; - case LP5523_CMD_DISABLED: - lp5523_set_engine_mode(engine, LP5523_CMD_DISABLED); - break; - default: - return; - } - - engine->mode = mode; -} - -/*--------------------------------------------------------------*/ -/* 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 lp5523_init_led(struct lp5523_led *led, struct device *dev, - int chan, struct lp5523_platform_data *pdata, - const char *chip_name) +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); - return -EINVAL; - } - - if (pdata->led_config[chan].name) { - led->cdev.name = pdata->led_config[chan].name; + 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 { - snprintf(name, sizeof(name), "%s:channel%d", - pdata->label ? : chip_name, 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; + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; } - } else { - led->led_current = 0; } - return 0; -} - -static int lp5523_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct lp5523_chip *chip; - struct lp5523_platform_data *pdata; - int ret, i, led; + pdata = dev_get_platdata(&client->dev); chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - i2c_set_clientdata(client, chip); - chip->client = client; + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; - pdata = client->dev.platform_data; - - if (!pdata) { - dev_err(&client->dev, "no platform data\n"); - return -EINVAL; - } + 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) - return ret; - } - - 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 fail1; + goto err_init; 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 fail1; - } - } - ret = lp5523_configure(client); - if (ret < 0) { - dev_err(&client->dev, "error configuring chip\n"); - goto fail1; - } - - /* 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; - - INIT_WORK(&chip->leds[led].brightness_work, - lp5523_led_brightness_work); - - ret = lp5523_init_led(&chip->leds[led], &client->dev, i, pdata, - id->name); - if (ret) { - dev_err(&client->dev, "error initializing leds\n"); - goto fail2; - } - 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); - - 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 fail2; - } - return ret; -fail2: - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - flush_work(&chip->leds[i].brightness_work); + goto err_register_sysfs; } -fail1: - if (pdata->enable) - pdata->enable(0); - if (pdata->release_resources) - pdata->release_resources(); + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: return ret; } static int lp5523_remove(struct i2c_client *client) { - struct lp5523_chip *chip = i2c_get_clientdata(client); - int i; + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; - /* Disable engine mode */ - lp5523_write(client, LP5523_REG_OP_MODE, LP5523_CMD_DISABLED); + lp5523_stop_all_engines(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); - lp5523_unregister_sysfs(client); - - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - flush_work(&chip->leds[i].brightness_work); - } - - if (chip->pdata->enable) - chip->pdata->enable(0); - if (chip->pdata->release_resources) - chip->pdata->release_resources(); return 0; } @@ -1037,9 +811,20 @@ static const struct i2c_device_id lp5523_id[] = { MODULE_DEVICE_TABLE(i2c, lp5523_id); +#ifdef CONFIG_OF +static const struct of_device_id of_lp5523_leds_match[] = { + { .compatible = "national,lp5523", }, + { .compatible = "ti,lp55231", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5523_leds_match); +#endif + static struct i2c_driver lp5523_driver = { .driver = { .name = "lp5523x", + .of_match_table = of_match_ptr(of_lp5523_leds_match), }, .probe = lp5523_probe, .remove = lp5523_remove, @@ -1049,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 index 4353942c5fd..7c2cb384e7a 100644 --- a/drivers/leds/leds-lp8788.c +++ b/drivers/leds/leds-lp8788.c @@ -130,9 +130,10 @@ 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(lp->dev, sizeof(struct lp8788_led), GFP_KERNEL); + led = devm_kzalloc(dev, sizeof(struct lp8788_led), GFP_KERNEL); if (!led) return -ENOMEM; @@ -154,13 +155,13 @@ static int lp8788_led_probe(struct platform_device *pdev) ret = lp8788_led_init_device(led, led_pdata); if (ret) { - dev_err(lp->dev, "led init device err: %d\n", ret); + dev_err(dev, "led init device err: %d\n", ret); return ret; } - ret = led_classdev_register(lp->dev, &led->led_dev); + ret = led_classdev_register(dev, &led->led_dev); if (ret) { - dev_err(lp->dev, "led register err: %d\n", ret); + dev_err(dev, "led register err: %d\n", ret); return ret; } diff --git a/drivers/leds/leds-lt3593.c b/drivers/leds/leds-lt3593.c index c9b9e1fec58..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> @@ -106,8 +105,9 @@ static int create_lt3593_led(const struct gpio_led *template, if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; - ret = devm_gpio_request_one(parent, template->gpio, - GPIOF_DIR_OUT | state, template->name); + ret = devm_gpio_request_one(parent, template->gpio, state ? + GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW, + template->name); if (ret < 0) return ret; @@ -134,7 +134,7 @@ static void delete_lt3593_led(struct lt3593_led_data *led) 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; @@ -168,7 +168,7 @@ err: 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); diff --git a/drivers/leds/leds-mc13783.c b/drivers/leds/leds-mc13783.c index e942adaa750..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 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,229 +77,261 @@ static int 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 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 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 = devm_kzalloc(&pdev->dev, pdata->num_leds * sizeof(*led), - 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"); - return ret; + 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); + } return ret; } -static int 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, +}; - platform_set_drvdata(pdev, NULL); - 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 = 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-netxbig.c b/drivers/leds/leds-netxbig.c index c61c5ebcc08..e97f443a6e0 100644 --- a/drivers/leds/leds-netxbig.c +++ b/drivers/leds/leds-netxbig.c @@ -21,7 +21,6 @@ */ #include <linux/module.h> -#include <linux/init.h> #include <linux/irq.h> #include <linux/slab.h> #include <linux/spinlock.h> @@ -306,7 +305,7 @@ 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); @@ -354,7 +353,7 @@ create_netxbig_led(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; @@ -391,7 +390,7 @@ err_free_leds: 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; diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c index d978171c25b..efa625883c8 100644 --- a/drivers/leds/leds-ns2.c +++ b/drivers/leds/leds-ns2.c @@ -23,13 +23,13 @@ */ #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 <linux/platform_data/leds-kirkwood-ns2.h> +#include <linux/of.h> #include <linux/of_gpio.h> /* @@ -193,7 +193,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, enum ns2_led_modes mode; ret = devm_gpio_request_one(&pdev->dev, template->cmd, - GPIOF_DIR_OUT | gpio_get_value(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", @@ -202,7 +203,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, } ret = devm_gpio_request_one(&pdev->dev, template->slow, - GPIOF_DIR_OUT | gpio_get_value(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", @@ -306,10 +308,21 @@ static const struct of_device_id of_ns2_leds_match[] = { }; #endif /* CONFIG_OF_GPIO */ +struct ns2_led_priv { + int num_leds; + struct ns2_led_data leds_data[]; +}; + +static inline int sizeof_ns2_led_priv(int num_leds) +{ + return sizeof(struct ns2_led_priv) + + (sizeof(struct ns2_led_data) * num_leds); +} + static int ns2_led_probe(struct platform_device *pdev) { - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; + struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev); + struct ns2_led_priv *priv; int i; int ret; @@ -330,21 +343,23 @@ static int ns2_led_probe(struct platform_device *pdev) return -EINVAL; #endif /* CONFIG_OF_GPIO */ - leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) * - pdata->num_leds, GFP_KERNEL); - if (!leds_data) + 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 < pdata->num_leds; i++) { - ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); + 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(&leds_data[i]); + delete_ns2_led(&priv->leds_data[i]); return ret; } } - platform_set_drvdata(pdev, leds_data); + platform_set_drvdata(pdev, priv); return 0; } @@ -352,15 +367,12 @@ static int ns2_led_probe(struct platform_device *pdev) static int ns2_led_remove(struct platform_device *pdev) { int i; - struct ns2_led_platform_data *pdata = pdev->dev.platform_data; - struct ns2_led_data *leds_data; - - leds_data = platform_get_drvdata(pdev); + struct ns2_led_priv *priv; - for (i = 0; i < pdata->num_leds; i++) - delete_ns2_led(&leds_data[i]); + priv = platform_get_drvdata(pdev); - platform_set_drvdata(pdev, NULL); + for (i = 0; i < priv->num_leds; i++) + delete_ns2_led(&priv->leds_data[i]); return 0; } diff --git a/drivers/leds/leds-ot200.c b/drivers/leds/leds-ot200.c index ee14662ed5c..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), } }; diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c index cee8a5b483a..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,7 +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; + struct pca9532_platform_data *pca9532_pdata = + dev_get_platdata(&client->dev); if (!pca9532_pdata) return -EIO; diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c index edf485b773c..c3a08b60535 100644 --- a/drivers/leds/leds-pca955x.c +++ b/drivers/leds/leds-pca955x.c @@ -267,7 +267,7 @@ static int 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)) != diff --git a/drivers/leds/leds-pca9633.c b/drivers/leds/leds-pca9633.c deleted file mode 100644 index 9aae5679ffb..00000000000 --- a/drivers/leds/leds-pca9633.c +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright 2011 bct electronic GmbH - * - * Author: Peter Meerwald <p.meerwald@bct-electronic.com> - * - * Based on leds-pca955x.c - * - * This file is subject to the terms and conditions of version 2 of - * the GNU General Public License. See the file COPYING in the main - * directory of this archive for more details. - * - * LED driver for the PCA9633 I2C LED driver (7-bit slave address 0x62) - * - */ - -#include <linux/module.h> -#include <linux/delay.h> -#include <linux/string.h> -#include <linux/ctype.h> -#include <linux/leds.h> -#include <linux/err.h> -#include <linux/i2c.h> -#include <linux/workqueue.h> -#include <linux/slab.h> -#include <linux/platform_data/leds-pca9633.h> - -/* LED select registers determine the source that drives LED outputs */ -#define PCA9633_LED_OFF 0x0 /* LED driver off */ -#define PCA9633_LED_ON 0x1 /* LED driver on */ -#define PCA9633_LED_PWM 0x2 /* Controlled through PWM */ -#define PCA9633_LED_GRP_PWM 0x3 /* Controlled through PWM/GRPPWM */ - -#define PCA9633_MODE1 0x00 -#define PCA9633_MODE2 0x01 -#define PCA9633_PWM_BASE 0x02 -#define PCA9633_LEDOUT 0x08 - -static const struct i2c_device_id pca9633_id[] = { - { "pca9633", 0 }, - { } -}; -MODULE_DEVICE_TABLE(i2c, pca9633_id); - -struct pca9633_led { - struct i2c_client *client; - struct work_struct work; - enum led_brightness brightness; - struct led_classdev led_cdev; - int led_num; /* 0 .. 3 potentially */ - char name[32]; -}; - -static void pca9633_led_work(struct work_struct *work) -{ - struct pca9633_led *pca9633 = container_of(work, - struct pca9633_led, work); - u8 ledout = i2c_smbus_read_byte_data(pca9633->client, PCA9633_LEDOUT); - int shift = 2 * pca9633->led_num; - u8 mask = 0x3 << shift; - - switch (pca9633->brightness) { - case LED_FULL: - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, - (ledout & ~mask) | (PCA9633_LED_ON << shift)); - break; - case LED_OFF: - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, - ledout & ~mask); - break; - default: - i2c_smbus_write_byte_data(pca9633->client, - PCA9633_PWM_BASE + pca9633->led_num, - pca9633->brightness); - i2c_smbus_write_byte_data(pca9633->client, PCA9633_LEDOUT, - (ledout & ~mask) | (PCA9633_LED_PWM << shift)); - break; - } -} - -static void pca9633_led_set(struct led_classdev *led_cdev, - enum led_brightness value) -{ - struct pca9633_led *pca9633; - - pca9633 = container_of(led_cdev, struct pca9633_led, led_cdev); - - pca9633->brightness = value; - - /* - * Must use workqueue for the actual I/O since I2C operations - * can sleep. - */ - schedule_work(&pca9633->work); -} - -static int pca9633_probe(struct i2c_client *client, - const struct i2c_device_id *id) -{ - struct pca9633_led *pca9633; - struct pca9633_platform_data *pdata; - int i, err; - - pdata = client->dev.platform_data; - - if (pdata) { - if (pdata->leds.num_leds <= 0 || pdata->leds.num_leds > 4) { - dev_err(&client->dev, "board info must claim at most 4 LEDs"); - return -EINVAL; - } - } - - pca9633 = devm_kzalloc(&client->dev, 4 * sizeof(*pca9633), GFP_KERNEL); - if (!pca9633) - return -ENOMEM; - - i2c_set_clientdata(client, pca9633); - - for (i = 0; i < 4; i++) { - pca9633[i].client = client; - pca9633[i].led_num = i; - - /* Platform data can specify LED names and default triggers */ - if (pdata && i < pdata->leds.num_leds) { - if (pdata->leds.leds[i].name) - snprintf(pca9633[i].name, - sizeof(pca9633[i].name), "pca9633:%s", - pdata->leds.leds[i].name); - if (pdata->leds.leds[i].default_trigger) - pca9633[i].led_cdev.default_trigger = - pdata->leds.leds[i].default_trigger; - } else { - snprintf(pca9633[i].name, sizeof(pca9633[i].name), - "pca9633:%d", i); - } - - pca9633[i].led_cdev.name = pca9633[i].name; - pca9633[i].led_cdev.brightness_set = pca9633_led_set; - - INIT_WORK(&pca9633[i].work, pca9633_led_work); - - err = led_classdev_register(&client->dev, &pca9633[i].led_cdev); - if (err < 0) - goto exit; - } - - /* Disable LED all-call address and set normal mode */ - i2c_smbus_write_byte_data(client, PCA9633_MODE1, 0x00); - - /* Configure output: open-drain or totem pole (push-pull) */ - if (pdata && pdata->outdrv == PCA9633_OPEN_DRAIN) - i2c_smbus_write_byte_data(client, PCA9633_MODE2, 0x01); - - /* Turn off LEDs */ - i2c_smbus_write_byte_data(client, PCA9633_LEDOUT, 0x00); - - return 0; - -exit: - while (i--) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } - - return err; -} - -static int pca9633_remove(struct i2c_client *client) -{ - struct pca9633_led *pca9633 = i2c_get_clientdata(client); - int i; - - for (i = 0; i < 4; i++) { - led_classdev_unregister(&pca9633[i].led_cdev); - cancel_work_sync(&pca9633[i].work); - } - - return 0; -} - -static struct i2c_driver pca9633_driver = { - .driver = { - .name = "leds-pca9633", - .owner = THIS_MODULE, - }, - .probe = pca9633_probe, - .remove = pca9633_remove, - .id_table = pca9633_id, -}; - -module_i2c_driver(pca9633_driver); - -MODULE_AUTHOR("Peter Meerwald <p.meerwald@bct-electronic.com>"); -MODULE_DESCRIPTION("PCA9633 LED driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c new file mode 100644 index 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 2157524f277..d672bb4480f 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -14,121 +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; + struct work_struct work; unsigned int active_low; unsigned int period; + int duty; + bool can_sleep; }; +struct led_pwm_priv { + int num_leds; + struct led_pwm_data leds[0]; +}; + +static void __led_pwm_set(struct led_pwm_data *led_dat) +{ + int new_duty = led_dat->duty; + + pwm_config(led_dat->pwm, new_duty, led_dat->period); + + if (new_duty == 0) + pwm_disable(led_dat->pwm); + else + pwm_enable(led_dat->pwm); +} + +static void led_pwm_work(struct work_struct *work) +{ + struct led_pwm_data *led_dat = + container_of(work, struct led_pwm_data, work); + + __led_pwm_set(led_dat); +} + static void led_pwm_set(struct led_classdev *led_cdev, enum led_brightness brightness) { struct led_pwm_data *led_dat = container_of(led_cdev, struct led_pwm_data, cdev); unsigned int max = led_dat->cdev.max_brightness; - unsigned int period = led_dat->period; + unsigned long long duty = led_dat->period; - if (brightness == 0) { - pwm_config(led_dat->pwm, 0, period); - pwm_disable(led_dat->pwm); - } else { - pwm_config(led_dat->pwm, brightness * period / max, period); - pwm_enable(led_dat->pwm); - } + duty *= brightness; + do_div(duty, max); + + if (led_dat->active_low) + duty = led_dat->period - duty; + + led_dat->duty = duty; + + if (led_dat->can_sleep) + schedule_work(&led_dat->work); + else + __led_pwm_set(led_dat); } -static int led_pwm_probe(struct platform_device *pdev) +static inline size_t sizeof_pwm_leds_priv(int num_leds) { - struct led_pwm_platform_data *pdata = pdev->dev.platform_data; - struct led_pwm *cur_led; - struct led_pwm_data *leds_data, *led_dat; - int i, ret = 0; - - if (!pdata) - return -EBUSY; - - leds_data = devm_kzalloc(&pdev->dev, - sizeof(struct led_pwm_data) * pdata->num_leds, - GFP_KERNEL); - if (!leds_data) - return -ENOMEM; + return sizeof(struct led_pwm_priv) + + (sizeof(struct led_pwm_data) * num_leds); +} - 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; - } +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); + } +} - 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; - } +static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, + struct led_pwm *led, struct device_node *child) +{ + struct led_pwm_data *led_data = &priv->leds[priv->num_leds]; + int ret; + + led_data->active_low = led->active_low; + led_data->cdev.name = led->name; + led_data->cdev.default_trigger = led->default_trigger; + led_data->cdev.brightness_set = led_pwm_set; + led_data->cdev.brightness = LED_OFF; + led_data->cdev.max_brightness = led->max_brightness; + led_data->cdev.flags = LED_CORE_SUSPENDRESUME; + + if (child) + led_data->pwm = devm_of_pwm_get(dev, child, NULL); + else + led_data->pwm = devm_pwm_get(dev, led->name); + if (IS_ERR(led_data->pwm)) { + ret = PTR_ERR(led_data->pwm); + dev_err(dev, "unable to request PWM for %s: %d\n", + led->name, ret); + return ret; } - platform_set_drvdata(pdev, leds_data); + if (child) + led_data->period = pwm_get_period(led_data->pwm); - return 0; + led_data->can_sleep = pwm_can_sleep(led_data->pwm); + if (led_data->can_sleep) + INIT_WORK(&led_data->work, led_pwm_work); + + led_data->period = pwm_get_period(led_data->pwm); + if (!led_data->period && (led->pwm_period_ns > 0)) + led_data->period = led->pwm_period_ns; + + ret = led_classdev_register(dev, &led_data->cdev); + if (ret == 0) { + priv->num_leds++; + } else { + dev_err(dev, "failed to register PWM led for %s: %d\n", + led->name, ret); + } -err: - if (i > 0) { - for (i = i - 1; i >= 0; i--) { - led_classdev_unregister(&leds_data[i].cdev); - pwm_free(leds_data[i].pwm); + return ret; +} + +static int led_pwm_create_of(struct device *dev, struct led_pwm_priv *priv) +{ + struct device_node *child; + struct led_pwm led; + int ret = 0; + + memset(&led, 0, sizeof(led)); + + for_each_child_of_node(dev->of_node, child) { + led.name = of_get_property(child, "label", NULL) ? : + child->name; + + led.default_trigger = of_get_property(child, + "linux,default-trigger", NULL); + led.active_low = of_property_read_bool(child, "active-low"); + of_property_read_u32(child, "max-brightness", + &led.max_brightness); + + ret = led_pwm_add(dev, priv, &led, child); + if (ret) { + of_node_put(child); + break; } } return ret; } -static int led_pwm_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; } + platform_set_drvdata(pdev, priv); + return 0; } +static int led_pwm_remove(struct platform_device *pdev) +{ + struct led_pwm_priv *priv = platform_get_drvdata(pdev); + + led_pwm_cleanup(priv); + + return 0; +} + +static const struct of_device_id of_pwm_leds_match[] = { + { .compatible = "pwm-leds", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_pwm_leds_match); + static struct platform_driver led_pwm_driver = { .probe = led_pwm_probe, .remove = led_pwm_remove, .driver = { .name = "leds_pwm", .owner = THIS_MODULE, + .of_match_table = of_pwm_leds_match, }, }; diff --git a/drivers/leds/leds-regulator.c b/drivers/leds/leds-regulator.c index 4253a9b03db..358430db6e6 100644 --- a/drivers/leds/leds-regulator.c +++ b/drivers/leds/leds-regulator.c @@ -142,7 +142,8 @@ static void regulator_led_brightness_set(struct led_classdev *led_cdev, 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; diff --git a/drivers/leds/leds-renesas-tpu.c b/drivers/leds/leds-renesas-tpu.c deleted file mode 100644 index e0fff1ca592..00000000000 --- a/drivers/leds/leds-renesas-tpu.c +++ /dev/null @@ -1,336 +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_one(cfg->pin_gpio, GPIOF_DIR_OUT | !!brightness, - cfg->name); - - if (new_state == R_TPU_PIN_GPIO_FN) - gpio_request(cfg->pin_gpio_fn, cfg->name); - - p->pin_state = new_state; -} - -static void r_tpu_work(struct work_struct *work) -{ - struct r_tpu_priv *p = container_of(work, struct r_tpu_priv, work); - enum led_brightness brightness = p->new_brightness; - - r_tpu_disable(p); - - /* off and maximum are handled as GPIO pins, in between PWM */ - if ((brightness == 0) || (brightness == p->ldev.max_brightness)) - r_tpu_set_pin(p, R_TPU_PIN_GPIO, brightness); - else { - r_tpu_set_pin(p, R_TPU_PIN_GPIO_FN, 0); - r_tpu_enable(p, brightness); - } -} - -static void r_tpu_set_brightness(struct led_classdev *ldev, - enum led_brightness brightness) -{ - struct r_tpu_priv *p = container_of(ldev, struct r_tpu_priv, ldev); - p->new_brightness = brightness; - schedule_work(&p->work); -} - -static int r_tpu_probe(struct platform_device *pdev) -{ - struct led_renesas_tpu_config *cfg = pdev->dev.platform_data; - struct r_tpu_priv *p; - struct resource *res; - int ret; - - if (!cfg) { - dev_err(&pdev->dev, "missing platform data\n"); - return -ENODEV; - } - - p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); - if (p == NULL) { - dev_err(&pdev->dev, "failed to allocate driver data\n"); - return -ENOMEM; - } - - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!res) { - dev_err(&pdev->dev, "failed to get I/O memory\n"); - return -ENXIO; - } - - /* map memory, let mapbase point to our channel */ - p->mapbase = devm_ioremap_nocache(&pdev->dev, res->start, - resource_size(res)); - if (p->mapbase == NULL) { - dev_err(&pdev->dev, "failed to remap I/O memory\n"); - return -ENXIO; - } - - /* get hold of clock */ - p->clk = devm_clk_get(&pdev->dev, NULL); - if (IS_ERR(p->clk)) { - dev_err(&pdev->dev, "cannot get clock\n"); - return PTR_ERR(p->clk); - } - - p->pdev = pdev; - p->pin_state = R_TPU_PIN_UNUSED; - p->timer_state = R_TPU_TIMER_UNUSED; - p->refresh_rate = cfg->refresh_rate ? cfg->refresh_rate : 100; - r_tpu_set_pin(p, R_TPU_PIN_GPIO, LED_OFF); - platform_set_drvdata(pdev, p); - - INIT_WORK(&p->work, r_tpu_work); - - p->ldev.name = cfg->name; - p->ldev.brightness = LED_OFF; - p->ldev.max_brightness = cfg->max_brightness; - p->ldev.brightness_set = r_tpu_set_brightness; - p->ldev.flags |= LED_CORE_SUSPENDRESUME; - ret = led_classdev_register(&pdev->dev, &p->ldev); - if (ret < 0) - goto err0; - - /* max_brightness may be updated by the LED core code */ - p->min_rate = p->ldev.max_brightness * p->refresh_rate; - - pm_runtime_enable(&pdev->dev); - return 0; - - err0: - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - return ret; -} - -static int r_tpu_remove(struct platform_device *pdev) -{ - struct r_tpu_priv *p = platform_get_drvdata(pdev); - - r_tpu_set_brightness(&p->ldev, LED_OFF); - led_classdev_unregister(&p->ldev); - cancel_work_sync(&p->work); - r_tpu_disable(p); - r_tpu_set_pin(p, R_TPU_PIN_UNUSED, LED_OFF); - - pm_runtime_disable(&pdev->dev); - - return 0; -} - -static struct platform_driver r_tpu_device_driver = { - .probe = r_tpu_probe, - .remove = r_tpu_remove, - .driver = { - .name = "leds-renesas-tpu", - } -}; - -module_platform_driver(r_tpu_device_driver); - -MODULE_AUTHOR("Magnus Damm"); -MODULE_DESCRIPTION("Renesas TPU LED Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c index e1a0df63a37..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 <linux/platform_data/leds-s3c24xx.h> +#include <plat/gpio-cfg.h> /* our context */ @@ -71,16 +70,14 @@ static int s3c24xx_led_remove(struct platform_device *dev) 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 = devm_kzalloc(&dev->dev, sizeof(struct s3c24xx_gpio_led), GFP_KERNEL); - if (led == NULL) { - dev_err(&dev->dev, "No memory for device\n"); + if (!led) return -ENOMEM; - } platform_set_drvdata(dev, led); diff --git a/drivers/leds/leds-ss4200.c b/drivers/leds/leds-ss4200.c index ec9b287ecfb..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; diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c index 07ff5a3a6ce..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"); @@ -130,16 +129,14 @@ static int sunfire_led_generic_probe(struct platform_device *pdev, 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); return -EINVAL; } p = devm_kzalloc(&pdev->dev, sizeof(*p), GFP_KERNEL); - if (!p) { - printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata\n"); + if (!p) return -ENOMEM; - } for (i = 0; i < NUM_LEDS_PER_BOARD; i++) { struct led_classdev *lp = &p->leds[i].led_cdev; @@ -152,7 +149,7 @@ static int 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); for (i--; i >= 0; i--) led_classdev_unregister(&p->leds[i].led_cdev); @@ -160,14 +157,14 @@ static int sunfire_led_generic_probe(struct platform_device *pdev, } } - dev_set_drvdata(&pdev->dev, p); + platform_set_drvdata(pdev, p); return 0; } 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++) @@ -188,7 +185,7 @@ static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = { { .name = "clockboard-right", .handler = clockboard_right_set, - .default_trigger= "heartbeat", + .default_trigger = "heartbeat", }, }; @@ -209,7 +206,7 @@ static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = { { .name = "fhc-right", .handler = fhc_right_set, - .default_trigger= "heartbeat", + .default_trigger = "heartbeat", }, }; @@ -244,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 b26a63bae16..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 */ +#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) + const struct i2c_device_id *id) { struct tca6507_chip *tca; struct i2c_adapter *adapter; @@ -677,15 +758,18 @@ static int 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); + } } tca = devm_kzalloc(&client->dev, sizeof(*tca), GFP_KERNEL); if (!tca) @@ -750,6 +834,7 @@ static struct i2c_driver tca6507_driver = { .driver = { .name = "leds-tca6507", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(of_tca6507_leds_match), }, .probe = tca6507_probe, .remove = tca6507_remove, 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 ed15157c8f6..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,7 +202,7 @@ static int wm8350_led_probe(struct platform_device *pdev) { struct regulator *isink, *dcdc; struct wm8350_led *led; - struct wm8350_led_platform_data *pdata = pdev->dev.platform_data; + struct wm8350_led_platform_data *pdata = dev_get_platdata(&pdev->dev); int i; if (pdata == NULL) { 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 027a2b15d7d..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; } 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/ledtrig-cpu.c b/drivers/leds/trigger/ledtrig-cpu.c index 4239b3955ff..aec0f02b6b3 100644 --- a/drivers/leds/ledtrig-cpu.c +++ b/drivers/leds/trigger/ledtrig-cpu.c @@ -26,7 +26,8 @@ #include <linux/percpu.h> #include <linux/syscore_ops.h> #include <linux/rwsem.h> -#include "leds.h" +#include <linux/cpu.h> +#include "../leds.h" #define MAX_NAME_LEN 8 @@ -46,7 +47,7 @@ static DEFINE_PER_CPU(struct led_trigger_cpu, cpu_trig); */ void ledtrig_cpu(enum cpu_led_event ledevt) { - struct led_trigger_cpu *trig = &__get_cpu_var(cpu_trig); + struct led_trigger_cpu *trig = this_cpu_ptr(&cpu_trig); /* Locate the correct CPU LED */ switch (ledevt) { @@ -92,6 +93,26 @@ static struct syscore_ops ledtrig_cpu_syscore_ops = { .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; @@ -113,6 +134,7 @@ static int __init ledtrig_cpu_init(void) } register_syscore_ops(&ledtrig_cpu_syscore_ops); + register_cpu_notifier(&ledtrig_cpu_nb); pr_info("ledtrig-cpu: registered to indicate activity on CPUs\n"); @@ -124,6 +146,8 @@ 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); diff --git a/drivers/leds/ledtrig-default-on.c b/drivers/leds/trigger/ledtrig-default-on.c index eac1f1b1ada..81a91be8e18 100644 --- a/drivers/leds/ledtrig-default-on.c +++ b/drivers/leds/trigger/ledtrig-default-on.c @@ -15,7 +15,7 @@ #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) { diff --git a/drivers/leds/ledtrig-gpio.c b/drivers/leds/trigger/ledtrig-gpio.c index 72e3ebfc281..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; diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/trigger/ledtrig-heartbeat.c index 1edc7463ce8..5c8464a3317 100644 --- a/drivers/leds/ledtrig-heartbeat.c +++ b/drivers/leds/trigger/ledtrig-heartbeat.c @@ -19,7 +19,7 @@ #include <linux/sched.h> #include <linux/leds.h> #include <linux/reboot.h> -#include "leds.h" +#include "../leds.h" static int panic_heartbeats; diff --git a/drivers/leds/ledtrig-ide-disk.c b/drivers/leds/trigger/ledtrig-ide-disk.c index 2cd7c0cf592..2cd7c0cf592 100644 --- a/drivers/leds/ledtrig-ide-disk.c +++ b/drivers/leds/trigger/ledtrig-ide-disk.c diff --git a/drivers/leds/ledtrig-oneshot.c b/drivers/leds/trigger/ledtrig-oneshot.c index 2c029aa5c4f..cb4c7466692 100644 --- a/drivers/leds/ledtrig-oneshot.c +++ b/drivers/leds/trigger/ledtrig-oneshot.c @@ -18,7 +18,7 @@ #include <linux/ctype.h> #include <linux/slab.h> #include <linux/leds.h> -#include "leds.h" +#include "../leds.h" #define DEFAULT_DELAY 100 diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/trigger/ledtrig-timer.c index f774d059220..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) diff --git a/drivers/leds/ledtrig-transient.c b/drivers/leds/trigger/ledtrig-transient.c index 398f1042c43..e5abc00bb00 100644 --- a/drivers/leds/ledtrig-transient.c +++ b/drivers/leds/trigger/ledtrig-transient.c @@ -25,7 +25,7 @@ #include <linux/slab.h> #include <linux/timer.h> #include <linux/leds.h> -#include "leds.h" +#include "../leds.h" struct transient_trig_data { int activate; |
