diff options
Diffstat (limited to 'drivers/leds')
29 files changed, 4792 insertions, 0 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig new file mode 100644 index 0000000..e7fb7d2 --- /dev/null +++ b/drivers/leds/Kconfig @@ -0,0 +1,220 @@ +menuconfig NEW_LEDS + bool "LED Support" + help + Say Y to enable Linux LED support. This allows control of supported + LEDs from both userspace and optionally, by kernel events (triggers). + + This is not related to standard keyboard LEDs which are controlled + via the input system. + +if NEW_LEDS + +config LEDS_CLASS + tristate "LED Class Support" + help + This option enables the led sysfs class in /sys/class/leds. You'll + need this to do anything useful with LEDs. If unsure, say N. + +comment "LED drivers" + +config LEDS_ATMEL_PWM + tristate "LED Support using Atmel PWM outputs" + depends on LEDS_CLASS && ATMEL_PWM + help + This option enables support for LEDs driven using outputs + of the dedicated PWM controller found on newer Atmel SOCs. + +config LEDS_LOCOMO + tristate "LED Support for Locomo device" + depends on LEDS_CLASS && SHARP_LOCOMO + help + This option enables support for the LEDs on Sharp Locomo. + Zaurus models SL-5500 and SL-5600. + +config LEDS_S3C24XX + tristate "LED Support for Samsung S3C24XX GPIO LEDs" + depends on LEDS_CLASS && ARCH_S3C2410 + help + This option enables support for LEDs connected to GPIO lines + on Samsung S3C24XX series CPUs, such as the S3C2410 and S3C2440. + +config LEDS_AMS_DELTA + tristate "LED Support for the Amstrad Delta (E3)" + depends on LEDS_CLASS && MACH_AMS_DELTA + help + This option enables support for the LEDs on Amstrad Delta (E3). + +config LEDS_NET48XX + tristate "LED Support for Soekris net48xx series Error LED" + depends on LEDS_CLASS && SCx200_GPIO + help + This option enables support for the Soekris net4801 and net4826 error + LED. + +config LEDS_FSG + tristate "LED Support for the Freecom FSG-3" + depends on LEDS_CLASS && MACH_FSG + help + This option enables support for the LEDs on the Freecom FSG-3. + +config LEDS_WRAP + tristate "LED Support for the WRAP series LEDs" + depends on LEDS_CLASS && SCx200_GPIO + help + This option enables support for the PCEngines WRAP programmable LEDs. + +config LEDS_H1940 + tristate "LED Support for iPAQ H1940 device" + depends on LEDS_CLASS && ARCH_H1940 + help + This option enables support for the LEDs on the h1940. + +config LEDS_COBALT_QUBE + tristate "LED Support for the Cobalt Qube series front LED" + depends on LEDS_CLASS && MIPS_COBALT + help + This option enables support for the front LED on Cobalt Qube series + +config LEDS_COBALT_RAQ + bool "LED Support for the Cobalt Raq series" + depends on LEDS_CLASS && MIPS_COBALT + select LEDS_TRIGGERS + help + This option enables support for the Cobalt Raq series LEDs. + +config LEDS_SUNFIRE + tristate "LED support for SunFire servers." + depends on LEDS_CLASS && SPARC64 + select LEDS_TRIGGERS + help + This option enables support for the Left, Middle, and Right + LEDs on the I/O and CPU boards of SunFire UltraSPARC servers. + +config LEDS_HP6XX + tristate "LED Support for the HP Jornada 6xx" + depends on LEDS_CLASS && SH_HP6XX + help + This option enables led support for the handheld + HP Jornada 620/660/680/690. + +config LEDS_PCA9532 + tristate "LED driver for PCA9532 dimmer" + depends on LEDS_CLASS && I2C && INPUT && EXPERIMENTAL + help + This option enables support for NXP pca9532 + led controller. It is generally only usefull + as a platform driver + +config LEDS_GPIO + tristate "LED Support for GPIO connected LEDs" + depends on LEDS_CLASS && GENERIC_GPIO + help + This option enables support for the LEDs connected to GPIO + outputs. To be useful the particular board must have LEDs + and they must be connected to the GPIO lines. + +config LEDS_HP_DISK + tristate "LED Support for disk protection LED on HP notebooks" + depends on LEDS_CLASS && ACPI + help + This option enable support for disk protection LED, found on + newer HP notebooks. + +config LEDS_CLEVO_MAIL + tristate "Mail LED on Clevo notebook (EXPERIMENTAL)" + depends on LEDS_CLASS && X86 && SERIO_I8042 && DMI && EXPERIMENTAL + help + This driver makes the mail LED accessible from userspace + programs through the leds subsystem. This LED have three + known mode: off, blink at 0.5Hz and blink at 1Hz. + + The driver supports two kinds of interface: using ledtrig-timer + or through /sys/class/leds/clevo::mail/brightness. As this LED + cannot change it's brightness it blinks instead. The brightness + value 0 means off, 1..127 means blink at 0.5Hz and 128..255 means + blink at 1Hz. + + This module can drive the mail LED for the following notebooks: + + Clevo D400P + Clevo D410J + Clevo D410V + Clevo D400V/D470V (not tested, but might work) + Clevo M540N + Clevo M5x0N (not tested, but might work) + Positivo Mobile (Clevo M5x0V) + + If your model is not listed here you can try the "nodetect" + module paramter. + + To compile this driver as a module, choose M here: the + module will be called leds-clevo-mail. + +config LEDS_PCA955X + tristate "LED Support for PCA955x I2C chips" + depends on LEDS_CLASS && I2C + help + This option enables support for LEDs connected to PCA955x + LED driver chips accessed via the I2C bus. Supported + devices include PCA9550, PCA9551, PCA9552, and PCA9553. + +config LEDS_DA903X + tristate "LED Support for DA9030/DA9034 PMIC" + depends on LEDS_CLASS && PMIC_DA903X + help + This option enables support for on-chip LED drivers found + on Dialog Semiconductor DA9030/DA9034 PMICs. + +comment "LED Triggers" + +config LEDS_TRIGGERS + bool "LED Trigger support" + 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. + +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-class.txt. + + If unsure, say Y. + +config LEDS_TRIGGER_IDE_DISK + bool "LED IDE Disk Trigger" + depends on LEDS_TRIGGERS && IDE_GD_ATA + 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_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. + +endif # NEW_LEDS diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile new file mode 100644 index 0000000..e1967a2 --- /dev/null +++ b/drivers/leds/Makefile @@ -0,0 +1,32 @@ + +# LED Core +obj-$(CONFIG_NEW_LEDS) += led-core.o +obj-$(CONFIG_LEDS_CLASS) += led-class.o +obj-$(CONFIG_LEDS_TRIGGERS) += led-triggers.o + +# LED Platform Drivers +obj-$(CONFIG_LEDS_ATMEL_PWM) += leds-atmel-pwm.o +obj-$(CONFIG_LEDS_LOCOMO) += leds-locomo.o +obj-$(CONFIG_LEDS_S3C24XX) += leds-s3c24xx.o +obj-$(CONFIG_LEDS_AMS_DELTA) += leds-ams-delta.o +obj-$(CONFIG_LEDS_NET48XX) += leds-net48xx.o +obj-$(CONFIG_LEDS_WRAP) += leds-wrap.o +obj-$(CONFIG_LEDS_H1940) += leds-h1940.o +obj-$(CONFIG_LEDS_COBALT_QUBE) += leds-cobalt-qube.o +obj-$(CONFIG_LEDS_COBALT_RAQ) += leds-cobalt-raq.o +obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o +obj-$(CONFIG_LEDS_PCA9532) += leds-pca9532.o +obj-$(CONFIG_LEDS_GPIO) += leds-gpio.o +obj-$(CONFIG_LEDS_CLEVO_MAIL) += leds-clevo-mail.o +obj-$(CONFIG_LEDS_HP6XX) += leds-hp6xx.o +obj-$(CONFIG_LEDS_FSG) += leds-fsg.o +obj-$(CONFIG_LEDS_PCA955X) += leds-pca955x.o +obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o +obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o + +# LED Triggers +obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o +obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o +obj-$(CONFIG_LEDS_TRIGGER_HEARTBEAT) += ledtrig-heartbeat.o +obj-$(CONFIG_LEDS_TRIGGER_BACKLIGHT) += ledtrig-backlight.o +obj-$(CONFIG_LEDS_TRIGGER_DEFAULT_ON) += ledtrig-default-on.o diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c new file mode 100644 index 0000000..6c4a326 --- /dev/null +++ b/drivers/leds/led-class.c @@ -0,0 +1,190 @@ +/* + * LED Class Core + * + * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu> + * Copyright (C) 2005-2007 Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/timer.h> +#include <linux/err.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include "leds.h" + +static struct class *leds_class; + +static void led_update_brightness(struct led_classdev *led_cdev) +{ + if (led_cdev->brightness_get) + led_cdev->brightness = led_cdev->brightness_get(led_cdev); +} + +static ssize_t led_brightness_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + + /* no lock needed for this */ + led_update_brightness(led_cdev); + + return sprintf(buf, "%u\n", led_cdev->brightness); +} + +static ssize_t led_brightness_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + ssize_t ret = -EINVAL; + char *after; + unsigned long state = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + ret = count; + + if (state == LED_OFF) + led_trigger_remove(led_cdev); + led_set_brightness(led_cdev, state); + } + + return ret; +} + +static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store); +#ifdef CONFIG_LEDS_TRIGGERS +static DEVICE_ATTR(trigger, 0644, led_trigger_show, led_trigger_store); +#endif + +/** + * led_classdev_suspend - suspend an led_classdev. + * @led_cdev: the led_classdev to suspend. + */ +void led_classdev_suspend(struct led_classdev *led_cdev) +{ + led_cdev->flags |= LED_SUSPENDED; + led_cdev->brightness_set(led_cdev, 0); +} +EXPORT_SYMBOL_GPL(led_classdev_suspend); + +/** + * led_classdev_resume - resume an led_classdev. + * @led_cdev: the led_classdev to resume. + */ +void led_classdev_resume(struct led_classdev *led_cdev) +{ + led_cdev->brightness_set(led_cdev, led_cdev->brightness); + led_cdev->flags &= ~LED_SUSPENDED; +} +EXPORT_SYMBOL_GPL(led_classdev_resume); + +/** + * led_classdev_register - register a new object of led_classdev class. + * @dev: The device to register. + * @led_cdev: the led_classdev structure for this device. + */ +int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) +{ + int rc; + + led_cdev->dev = device_create(leds_class, parent, 0, led_cdev, + "%s", led_cdev->name); + if (IS_ERR(led_cdev->dev)) + return PTR_ERR(led_cdev->dev); + + /* register the attributes */ + rc = device_create_file(led_cdev->dev, &dev_attr_brightness); + if (rc) + goto err_out; + +#ifdef CONFIG_LEDS_TRIGGERS + init_rwsem(&led_cdev->trigger_lock); +#endif + /* add to the list of leds */ + down_write(&leds_list_lock); + list_add_tail(&led_cdev->node, &leds_list); + up_write(&leds_list_lock); + + led_update_brightness(led_cdev); + +#ifdef CONFIG_LEDS_TRIGGERS + rc = device_create_file(led_cdev->dev, &dev_attr_trigger); + if (rc) + goto err_out_led_list; + + led_trigger_set_default(led_cdev); +#endif + + printk(KERN_INFO "Registered led device: %s\n", + led_cdev->name); + + return 0; + +#ifdef CONFIG_LEDS_TRIGGERS +err_out_led_list: + device_remove_file(led_cdev->dev, &dev_attr_brightness); + list_del(&led_cdev->node); +#endif +err_out: + device_unregister(led_cdev->dev); + return rc; +} +EXPORT_SYMBOL_GPL(led_classdev_register); + +/** + * led_classdev_unregister - unregisters a object of led_properties class. + * @led_cdev: the led device to unregister + * + * Unregisters a previously registered via led_classdev_register object. + */ +void led_classdev_unregister(struct led_classdev *led_cdev) +{ + device_remove_file(led_cdev->dev, &dev_attr_brightness); +#ifdef CONFIG_LEDS_TRIGGERS + device_remove_file(led_cdev->dev, &dev_attr_trigger); + down_write(&led_cdev->trigger_lock); + if (led_cdev->trigger) + led_trigger_set(led_cdev, NULL); + up_write(&led_cdev->trigger_lock); +#endif + + device_unregister(led_cdev->dev); + + down_write(&leds_list_lock); + list_del(&led_cdev->node); + up_write(&leds_list_lock); +} +EXPORT_SYMBOL_GPL(led_classdev_unregister); + +static int __init leds_init(void) +{ + leds_class = class_create(THIS_MODULE, "leds"); + if (IS_ERR(leds_class)) + return PTR_ERR(leds_class); + return 0; +} + +static void __exit leds_exit(void) +{ + class_destroy(leds_class); +} + +subsys_initcall(leds_init); +module_exit(leds_exit); + +MODULE_AUTHOR("John Lenz, Richard Purdie"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LED Class Interface"); diff --git a/drivers/leds/led-core.c b/drivers/leds/led-core.c new file mode 100644 index 0000000..016d19f --- /dev/null +++ b/drivers/leds/led-core.c @@ -0,0 +1,25 @@ +/* + * LED Class Core + * + * Copyright 2005-2006 Openedhand Ltd. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/module.h> +#include <linux/rwsem.h> +#include <linux/leds.h> +#include "leds.h" + +DECLARE_RWSEM(leds_list_lock); +EXPORT_SYMBOL_GPL(leds_list_lock); + +LIST_HEAD(leds_list); +EXPORT_SYMBOL_GPL(leds_list); diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c new file mode 100644 index 0000000..f910eaf --- /dev/null +++ b/drivers/leds/led-triggers.c @@ -0,0 +1,256 @@ +/* + * LED Triggers Core + * + * Copyright 2005-2007 Openedhand Ltd. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/timer.h> +#include <linux/rwsem.h> +#include <linux/leds.h> +#include "leds.h" + +/* + * Nests outside led_cdev->trigger_lock + */ +static DECLARE_RWSEM(triggers_list_lock); +static LIST_HEAD(trigger_list); + + /* Used by LED Class */ + +ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + char trigger_name[TRIG_NAME_MAX]; + struct led_trigger *trig; + size_t len; + + trigger_name[sizeof(trigger_name) - 1] = '\0'; + strncpy(trigger_name, buf, sizeof(trigger_name) - 1); + len = strlen(trigger_name); + + if (len && trigger_name[len - 1] == '\n') + trigger_name[len - 1] = '\0'; + + if (!strcmp(trigger_name, "none")) { + led_trigger_remove(led_cdev); + return count; + } + + down_read(&triggers_list_lock); + list_for_each_entry(trig, &trigger_list, next_trig) { + if (!strcmp(trigger_name, trig->name)) { + down_write(&led_cdev->trigger_lock); + led_trigger_set(led_cdev, trig); + up_write(&led_cdev->trigger_lock); + + up_read(&triggers_list_lock); + return count; + } + } + up_read(&triggers_list_lock); + + return -EINVAL; +} +EXPORT_SYMBOL_GPL(led_trigger_store); + +ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct led_trigger *trig; + int len = 0; + + down_read(&triggers_list_lock); + down_read(&led_cdev->trigger_lock); + + if (!led_cdev->trigger) + len += sprintf(buf+len, "[none] "); + else + len += sprintf(buf+len, "none "); + + list_for_each_entry(trig, &trigger_list, next_trig) { + if (led_cdev->trigger && !strcmp(led_cdev->trigger->name, + trig->name)) + len += sprintf(buf+len, "[%s] ", trig->name); + else + len += sprintf(buf+len, "%s ", trig->name); + } + up_read(&led_cdev->trigger_lock); + up_read(&triggers_list_lock); + + len += sprintf(len+buf, "\n"); + return len; +} +EXPORT_SYMBOL_GPL(led_trigger_show); + +/* Caller must ensure led_cdev->trigger_lock held */ +void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trigger) +{ + unsigned long flags; + + /* Remove any existing trigger */ + if (led_cdev->trigger) { + write_lock_irqsave(&led_cdev->trigger->leddev_list_lock, flags); + list_del(&led_cdev->trig_list); + write_unlock_irqrestore(&led_cdev->trigger->leddev_list_lock, + flags); + if (led_cdev->trigger->deactivate) + led_cdev->trigger->deactivate(led_cdev); + led_cdev->trigger = NULL; + led_set_brightness(led_cdev, LED_OFF); + } + if (trigger) { + write_lock_irqsave(&trigger->leddev_list_lock, flags); + list_add_tail(&led_cdev->trig_list, &trigger->led_cdevs); + write_unlock_irqrestore(&trigger->leddev_list_lock, flags); + led_cdev->trigger = trigger; + if (trigger->activate) + trigger->activate(led_cdev); + } +} +EXPORT_SYMBOL_GPL(led_trigger_set); + +void led_trigger_remove(struct led_classdev *led_cdev) +{ + down_write(&led_cdev->trigger_lock); + led_trigger_set(led_cdev, NULL); + up_write(&led_cdev->trigger_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_remove); + +void led_trigger_set_default(struct led_classdev *led_cdev) +{ + struct led_trigger *trig; + + if (!led_cdev->default_trigger) + return; + + down_read(&triggers_list_lock); + down_write(&led_cdev->trigger_lock); + list_for_each_entry(trig, &trigger_list, next_trig) { + if (!strcmp(led_cdev->default_trigger, trig->name)) + led_trigger_set(led_cdev, trig); + } + up_write(&led_cdev->trigger_lock); + up_read(&triggers_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_set_default); + +/* LED Trigger Interface */ + +int led_trigger_register(struct led_trigger *trigger) +{ + struct led_classdev *led_cdev; + + rwlock_init(&trigger->leddev_list_lock); + INIT_LIST_HEAD(&trigger->led_cdevs); + + /* Add to the list of led triggers */ + down_write(&triggers_list_lock); + list_add_tail(&trigger->next_trig, &trigger_list); + up_write(&triggers_list_lock); + + /* Register with any LEDs that have this as a default trigger */ + down_read(&leds_list_lock); + list_for_each_entry(led_cdev, &leds_list, node) { + down_write(&led_cdev->trigger_lock); + if (!led_cdev->trigger && led_cdev->default_trigger && + !strcmp(led_cdev->default_trigger, trigger->name)) + led_trigger_set(led_cdev, trigger); + up_write(&led_cdev->trigger_lock); + } + up_read(&leds_list_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(led_trigger_register); + +void led_trigger_unregister(struct led_trigger *trigger) +{ + struct led_classdev *led_cdev; + + /* Remove from the list of led triggers */ + down_write(&triggers_list_lock); + list_del(&trigger->next_trig); + up_write(&triggers_list_lock); + + /* Remove anyone actively using this trigger */ + down_read(&leds_list_lock); + list_for_each_entry(led_cdev, &leds_list, node) { + down_write(&led_cdev->trigger_lock); + if (led_cdev->trigger == trigger) + led_trigger_set(led_cdev, NULL); + up_write(&led_cdev->trigger_lock); + } + up_read(&leds_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_unregister); + +/* Simple LED Tigger Interface */ + +void led_trigger_event(struct led_trigger *trigger, + enum led_brightness brightness) +{ + struct list_head *entry; + + if (!trigger) + return; + + read_lock(&trigger->leddev_list_lock); + list_for_each(entry, &trigger->led_cdevs) { + struct led_classdev *led_cdev; + + led_cdev = list_entry(entry, struct led_classdev, trig_list); + led_set_brightness(led_cdev, brightness); + } + read_unlock(&trigger->leddev_list_lock); +} +EXPORT_SYMBOL_GPL(led_trigger_event); + +void led_trigger_register_simple(const char *name, struct led_trigger **tp) +{ + struct led_trigger *trigger; + int err; + + trigger = kzalloc(sizeof(struct led_trigger), GFP_KERNEL); + + if (trigger) { + trigger->name = name; + err = led_trigger_register(trigger); + if (err < 0) + printk(KERN_WARNING "LED trigger %s failed to register" + " (%d)\n", name, err); + } else + printk(KERN_WARNING "LED trigger %s failed to register" + " (no memory)\n", name); + + *tp = trigger; +} +EXPORT_SYMBOL_GPL(led_trigger_register_simple); + +void led_trigger_unregister_simple(struct led_trigger *trigger) +{ + if (trigger) + led_trigger_unregister(trigger); + kfree(trigger); +} +EXPORT_SYMBOL_GPL(led_trigger_unregister_simple); + +MODULE_AUTHOR("Richard Purdie"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("LED Triggers Core"); diff --git a/drivers/leds/leds-ams-delta.c b/drivers/leds/leds-ams-delta.c new file mode 100644 index 0000000..1bd590b --- /dev/null +++ b/drivers/leds/leds-ams-delta.c @@ -0,0 +1,163 @@ +/* + * LEDs driver for Amstrad Delta (E3) + * + * Copyright (C) 2006 Jonathan McDowell <noodles@earth.li> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <mach/board-ams-delta.h> + +/* + * Our context + */ +struct ams_delta_led { + struct led_classdev cdev; + u8 bitmask; +}; + +static void ams_delta_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct ams_delta_led *led_dev = + container_of(led_cdev, struct ams_delta_led, cdev); + + if (value) + ams_delta_latch1_write(led_dev->bitmask, led_dev->bitmask); + else + ams_delta_latch1_write(led_dev->bitmask, 0); +} + +static struct ams_delta_led ams_delta_leds[] = { + { + .cdev = { + .name = "ams-delta::camera", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_CAMERA, + }, + { + .cdev = { + .name = "ams-delta::advert", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_ADVERT, + }, + { + .cdev = { + .name = "ams-delta::email", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_EMAIL, + }, + { + .cdev = { + .name = "ams-delta::handsfree", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_HANDSFREE, + }, + { + .cdev = { + .name = "ams-delta::voicemail", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_VOICEMAIL, + }, + { + .cdev = { + .name = "ams-delta::voice", + .brightness_set = ams_delta_led_set, + }, + .bitmask = AMS_DELTA_LATCH1_LED_VOICE, + }, +}; + +#ifdef CONFIG_PM +static int ams_delta_led_suspend(struct platform_device *dev, + pm_message_t state) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) + led_classdev_suspend(&ams_delta_leds[i].cdev); + + return 0; +} + +static int ams_delta_led_resume(struct platform_device *dev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) + led_classdev_resume(&ams_delta_leds[i].cdev); + + return 0; +} +#else +#define ams_delta_led_suspend NULL +#define ams_delta_led_resume NULL +#endif + +static int ams_delta_led_probe(struct platform_device *pdev) +{ + int i, ret; + + for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++) { + ret = led_classdev_register(&pdev->dev, + &ams_delta_leds[i].cdev); + if (ret < 0) + goto fail; + } + + return 0; +fail: + while (--i >= 0) + led_classdev_unregister(&ams_delta_leds[i].cdev); + return ret; +} + +static int ams_delta_led_remove(struct platform_device *pdev) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i--) + led_classdev_unregister(&ams_delta_leds[i].cdev); + + return 0; +} + +static struct platform_driver ams_delta_led_driver = { + .probe = ams_delta_led_probe, + .remove = ams_delta_led_remove, + .suspend = ams_delta_led_suspend, + .resume = ams_delta_led_resume, + .driver = { + .name = "ams-delta-led", + .owner = THIS_MODULE, + }, +}; + +static int __init ams_delta_led_init(void) +{ + return platform_driver_register(&ams_delta_led_driver); +} + +static void __exit ams_delta_led_exit(void) +{ + return platform_driver_unregister(&ams_delta_led_driver); +} + +module_init(ams_delta_led_init); +module_exit(ams_delta_led_exit); + +MODULE_AUTHOR("Jonathan McDowell <noodles@earth.li>"); +MODULE_DESCRIPTION("Amstrad Delta LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ams-delta-led"); diff --git a/drivers/leds/leds-atmel-pwm.c b/drivers/leds/leds-atmel-pwm.c new file mode 100644 index 0000000..52297c3 --- /dev/null +++ b/drivers/leds/leds-atmel-pwm.c @@ -0,0 +1,160 @@ +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/io.h> +#include <linux/atmel_pwm.h> + + +struct pwmled { + struct led_classdev cdev; + struct pwm_channel pwmc; + struct gpio_led *desc; + u32 mult; + u8 active_low; +}; + + +/* + * For simplicity, we use "brightness" as if it were a linear function + * of PWM duty cycle. However, a logarithmic function of duty cycle is + * probably a better match for perceived brightness: two is half as bright + * as four, four is half as bright as eight, etc + */ +static void pwmled_brightness(struct led_classdev *cdev, enum led_brightness b) +{ + struct pwmled *led; + + /* update the duty cycle for the *next* period */ + led = container_of(cdev, struct pwmled, cdev); + pwm_channel_writel(&led->pwmc, PWM_CUPD, led->mult * (unsigned) b); +} + +/* + * NOTE: we reuse the platform_data structure of GPIO leds, + * but repurpose its "gpio" number as a PWM channel number. + */ +static int __init pwmled_probe(struct platform_device *pdev) +{ + const struct gpio_led_platform_data *pdata; + struct pwmled *leds; + int i; + int status; + + pdata = pdev->dev.platform_data; + if (!pdata || pdata->num_leds < 1) + return -ENODEV; + + leds = kcalloc(pdata->num_leds, sizeof(*leds), GFP_KERNEL); + if (!leds) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) { + struct pwmled *led = leds + i; + const struct gpio_led *dat = pdata->leds + i; + u32 tmp; + + led->cdev.name = dat->name; + led->cdev.brightness = LED_OFF; + led->cdev.brightness_set = pwmled_brightness; + led->cdev.default_trigger = dat->default_trigger; + + led->active_low = dat->active_low; + + status = pwm_channel_alloc(dat->gpio, &led->pwmc); + if (status < 0) + goto err; + + /* + * Prescale clock by 2^x, so PWM counts in low MHz. + * Start each cycle with the LED active, so increasing + * the duty cycle gives us more time on (== brighter). + */ + tmp = 5; + if (!led->active_low) + tmp |= PWM_CPR_CPOL; + pwm_channel_writel(&led->pwmc, PWM_CMR, tmp); + + /* + * Pick a period so PWM cycles at 100+ Hz; and a multiplier + * for scaling duty cycle: brightness * mult. + */ + tmp = (led->pwmc.mck / (1 << 5)) / 100; + tmp /= 255; + led->mult = tmp; + pwm_channel_writel(&led->pwmc, PWM_CDTY, + led->cdev.brightness * 255); + pwm_channel_writel(&led->pwmc, PWM_CPRD, + LED_FULL * tmp); + + pwm_channel_enable(&led->pwmc); + + /* Hand it over to the LED framework */ + status = led_classdev_register(&pdev->dev, &led->cdev); + if (status < 0) { + pwm_channel_free(&led->pwmc); + goto err; + } + } + + platform_set_drvdata(pdev, leds); + return 0; + +err: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&leds[i].cdev); + pwm_channel_free(&leds[i].pwmc); + } + } + kfree(leds); + + return status; +} + +static int __exit pwmled_remove(struct platform_device *pdev) +{ + const struct gpio_led_platform_data *pdata; + struct pwmled *leds; + unsigned i; + + pdata = pdev->dev.platform_data; + leds = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + struct pwmled *led = leds + i; + + led_classdev_unregister(&led->cdev); + pwm_channel_free(&led->pwmc); + } + + kfree(leds); + platform_set_drvdata(pdev, NULL); + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:leds-atmel-pwm"); + +static struct platform_driver pwmled_driver = { + .driver = { + .name = "leds-atmel-pwm", + .owner = THIS_MODULE, + }, + /* REVISIT add suspend() and resume() methods */ + .remove = __exit_p(pwmled_remove), +}; + +static int __init modinit(void) +{ + return platform_driver_probe(&pwmled_driver, pwmled_probe); +} +module_init(modinit); + +static void __exit modexit(void) +{ + platform_driver_unregister(&pwmled_driver); +} +module_exit(modexit); + +MODULE_DESCRIPTION("Driver for LEDs with PWM-controlled brightness"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-clevo-mail.c b/drivers/leds/leds-clevo-mail.c new file mode 100644 index 0000000..eb3415e --- /dev/null +++ b/drivers/leds/leds-clevo-mail.c @@ -0,0 +1,230 @@ + +#include <linux/module.h> + +#include <linux/platform_device.h> +#include <linux/err.h> +#include <linux/leds.h> + +#include <linux/io.h> +#include <linux/dmi.h> + +#include <linux/i8042.h> + +#define CLEVO_MAIL_LED_OFF 0x0084 +#define CLEVO_MAIL_LED_BLINK_1HZ 0x008A +#define CLEVO_MAIL_LED_BLINK_0_5HZ 0x0083 + +MODULE_AUTHOR("Márton Németh <nm127@freemail.hu>"); +MODULE_DESCRIPTION("Clevo mail LED driver"); +MODULE_LICENSE("GPL"); + +static unsigned int __initdata nodetect; +module_param_named(nodetect, nodetect, bool, 0); +MODULE_PARM_DESC(nodetect, "Skip DMI hardware detection"); + +static struct platform_device *pdev; + +static int __init clevo_mail_led_dmi_callback(const struct dmi_system_id *id) +{ + printk(KERN_INFO KBUILD_MODNAME ": '%s' found\n", id->ident); + return 1; +} + +/* + * struct mail_led_whitelist - List of known good models + * + * Contains the known good models this driver is compatible with. + * When adding a new model try to be as strict as possible. This + * makes it possible to keep the false positives (the model is + * detected as working, but in reality it is not) as low as + * possible. + */ +static struct dmi_system_id __initdata mail_led_whitelist[] = { + { + .callback = clevo_mail_led_dmi_callback, + .ident = "Clevo D410J", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "VIA"), + DMI_MATCH(DMI_PRODUCT_NAME, "K8N800"), + DMI_MATCH(DMI_PRODUCT_VERSION, "VT8204B") + } + }, + { + .callback = clevo_mail_led_dmi_callback, + .ident = "Clevo M5x0N", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "CLEVO Co."), + DMI_MATCH(DMI_PRODUCT_NAME, "M5x0N") + } + }, + { + .callback = clevo_mail_led_dmi_callback, + .ident = "Positivo Mobile", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "CLEVO Co. "), + DMI_MATCH(DMI_BOARD_NAME, "M5X0V "), + DMI_MATCH(DMI_PRODUCT_NAME, "Positivo Mobile"), + DMI_MATCH(DMI_PRODUCT_VERSION, "VT6198") + } + }, + { + .callback = clevo_mail_led_dmi_callback, + .ident = "Clevo D400P", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Clevo"), + DMI_MATCH(DMI_BOARD_NAME, "D400P"), + DMI_MATCH(DMI_BOARD_VERSION, "Rev.A"), + DMI_MATCH(DMI_PRODUCT_VERSION, "0106") + } + }, + { + .callback = clevo_mail_led_dmi_callback, + .ident = "Clevo D410V", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Clevo, Co."), + DMI_MATCH(DMI_BOARD_NAME, "D400V/D470V"), + DMI_MATCH(DMI_BOARD_VERSION, "SS78B"), + DMI_MATCH(DMI_PRODUCT_VERSION, "Rev. A1") + } + }, + { } +}; + +static void clevo_mail_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value == LED_OFF) + i8042_command(NULL, CLEVO_MAIL_LED_OFF); + else if (value <= LED_HALF) + i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); + else + i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ); + +} + +static int clevo_mail_led_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, + unsigned long *delay_off) +{ + int status = -EINVAL; + + if (*delay_on == 0 /* ms */ && *delay_off == 0 /* ms */) { + /* Special case: the leds subsystem requested us to + * chose one user friendly blinking of the LED, and + * start it. Let's blink the led slowly (0.5Hz). + */ + *delay_on = 1000; /* ms */ + *delay_off = 1000; /* ms */ + i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); + status = 0; + + } else if (*delay_on == 500 /* ms */ && *delay_off == 500 /* ms */) { + /* blink the led with 1Hz */ + i8042_command(NULL, CLEVO_MAIL_LED_BLINK_1HZ); + status = 0; + + } else if (*delay_on == 1000 /* ms */ && *delay_off == 1000 /* ms */) { + /* blink the led with 0.5Hz */ + i8042_command(NULL, CLEVO_MAIL_LED_BLINK_0_5HZ); + status = 0; + + } else { + printk(KERN_DEBUG KBUILD_MODNAME + ": clevo_mail_led_blink(..., %lu, %lu)," + " returning -EINVAL (unsupported)\n", + *delay_on, *delay_off); + } + + return status; +} + +static struct led_classdev clevo_mail_led = { + .name = "clevo::mail", + .brightness_set = clevo_mail_led_set, + .blink_set = clevo_mail_led_blink, +}; + +static int __init clevo_mail_led_probe(struct platform_device *pdev) +{ + return led_classdev_register(&pdev->dev, &clevo_mail_led); +} + +static int clevo_mail_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&clevo_mail_led); + return 0; +} + +#ifdef CONFIG_PM +static int clevo_mail_led_suspend(struct platform_device *dev, + pm_message_t state) +{ + led_classdev_suspend(&clevo_mail_led); + return 0; +} + +static int clevo_mail_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&clevo_mail_led); + return 0; +} +#else +#define clevo_mail_led_suspend NULL +#define clevo_mail_led_resume NULL +#endif + +static struct platform_driver clevo_mail_led_driver = { + .probe = clevo_mail_led_probe, + .remove = clevo_mail_led_remove, + .suspend = clevo_mail_led_suspend, + .resume = clevo_mail_led_resume, + .driver = { + .name = KBUILD_MODNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init clevo_mail_led_init(void) +{ + int error = 0; + int count = 0; + + /* Check with the help of DMI if we are running on supported hardware */ + if (!nodetect) { + count = dmi_check_system(mail_led_whitelist); + } else { + count = 1; + printk(KERN_ERR KBUILD_MODNAME ": Skipping DMI detection. " + "If the driver works on your hardware please " + "report model and the output of dmidecode in tracker " + "at http://sourceforge.net/projects/clevo-mailled/\n"); + } + + if (!count) + return -ENODEV; + + pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); + if (!IS_ERR(pdev)) { + error = platform_driver_probe(&clevo_mail_led_driver, + clevo_mail_led_probe); + if (error) { + printk(KERN_ERR KBUILD_MODNAME + ": Can't probe platform driver\n"); + platform_device_unregister(pdev); + } + } else + error = PTR_ERR(pdev); + + return error; +} + +static void __exit clevo_mail_led_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&clevo_mail_led_driver); + + clevo_mail_led_set(NULL, LED_OFF); +} + +module_init(clevo_mail_led_init); +module_exit(clevo_mail_led_exit); diff --git a/drivers/leds/leds-cobalt-qube.c b/drivers/leds/leds-cobalt-qube.c new file mode 100644 index 0000000..059aa29 --- /dev/null +++ b/drivers/leds/leds-cobalt-qube.c @@ -0,0 +1,105 @@ +/* + * Copyright 2006 - Florian Fainelli <florian@openwrt.org> + * + * Control the Cobalt Qube/RaQ front LED + */ +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/types.h> + +#define LED_FRONT_LEFT 0x01 +#define LED_FRONT_RIGHT 0x02 + +static void __iomem *led_port; +static u8 led_value; + +static void qube_front_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + if (brightness) + led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT; + else + led_value = ~(LED_FRONT_LEFT | LED_FRONT_RIGHT); + writeb(led_value, led_port); +} + +static struct led_classdev qube_front_led = { + .name = "qube-front", + .brightness = LED_FULL, + .brightness_set = qube_front_led_set, + .default_trigger = "ide-disk", +}; + +static int __devinit cobalt_qube_led_probe(struct platform_device *pdev) +{ + struct resource *res; + int retval; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + led_port = ioremap(res->start, res->end - res->start + 1); + if (!led_port) + return -ENOMEM; + + led_value = LED_FRONT_LEFT | LED_FRONT_RIGHT; + writeb(led_value, led_port); + + retval = led_classdev_register(&pdev->dev, &qube_front_led); + if (retval) + goto err_iounmap; + + return 0; + +err_iounmap: + iounmap(led_port); + led_port = NULL; + + return retval; +} + +static int __devexit cobalt_qube_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&qube_front_led); + + if (led_port) { + iounmap(led_port); + led_port = NULL; + } + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:cobalt-qube-leds"); + +static struct platform_driver cobalt_qube_led_driver = { + .probe = cobalt_qube_led_probe, + .remove = __devexit_p(cobalt_qube_led_remove), + .driver = { + .name = "cobalt-qube-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init cobalt_qube_led_init(void) +{ + return platform_driver_register(&cobalt_qube_led_driver); +} + +static void __exit cobalt_qube_led_exit(void) +{ + platform_driver_unregister(&cobalt_qube_led_driver); +} + +module_init(cobalt_qube_led_init); +module_exit(cobalt_qube_led_exit); + +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Front LED support for Cobalt Server"); +MODULE_AUTHOR("Florian Fainelli <florian@openwrt.org>"); diff --git a/drivers/leds/leds-cobalt-raq.c b/drivers/leds/leds-cobalt-raq.c new file mode 100644 index 0000000..ff0e8c3 --- /dev/null +++ b/drivers/leds/leds-cobalt-raq.c @@ -0,0 +1,138 @@ +/* + * LEDs driver for the Cobalt Raq series. + * + * Copyright (C) 2007 Yoichi Yuasa <yoichi_yuasa@tripeaks.co.jp> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#include <linux/init.h> +#include <linux/io.h> +#include <linux/ioport.h> +#include <linux/leds.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +#define LED_WEB 0x04 +#define LED_POWER_OFF 0x08 + +static void __iomem *led_port; +static u8 led_value; +static DEFINE_SPINLOCK(led_value_lock); + +static void raq_web_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + unsigned long flags; + + spin_lock_irqsave(&led_value_lock, flags); + + if (brightness) + led_value |= LED_WEB; + else + led_value &= ~LED_WEB; + writeb(led_value, led_port); + + spin_unlock_irqrestore(&led_value_lock, flags); +} + +static struct led_classdev raq_web_led = { + .name = "raq-web", + .brightness_set = raq_web_led_set, +}; + +static void raq_power_off_led_set(struct led_classdev *led_cdev, + enum led_brightness brightness) +{ + unsigned long flags; + + spin_lock_irqsave(&led_value_lock, flags); + + if (brightness) + led_value |= LED_POWER_OFF; + else + led_value &= ~LED_POWER_OFF; + writeb(led_value, led_port); + + spin_unlock_irqrestore(&led_value_lock, flags); +} + +static struct led_classdev raq_power_off_led = { + .name = "raq-power-off", + .brightness_set = raq_power_off_led_set, + .default_trigger = "power-off", +}; + +static int __devinit cobalt_raq_led_probe(struct platform_device *pdev) +{ + struct resource *res; + int retval; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -EBUSY; + + led_port = ioremap(res->start, res->end - res->start + 1); + if (!led_port) + return -ENOMEM; + + retval = led_classdev_register(&pdev->dev, &raq_power_off_led); + if (retval) + goto err_iounmap; + + retval = led_classdev_register(&pdev->dev, &raq_web_led); + if (retval) + goto err_unregister; + + return 0; + +err_unregister: + led_classdev_unregister(&raq_power_off_led); + +err_iounmap: + iounmap(led_port); + led_port = NULL; + + return retval; +} + +static int __devexit cobalt_raq_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&raq_power_off_led); + led_classdev_unregister(&raq_web_led); + + if (led_port) { + iounmap(led_port); + led_port = NULL; + } + + return 0; +} + +static struct platform_driver cobalt_raq_led_driver = { + .probe = cobalt_raq_led_probe, + .remove = __devexit_p(cobalt_raq_led_remove), + .driver = { + .name = "cobalt-raq-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init cobalt_raq_led_init(void) +{ + return platform_driver_register(&cobalt_raq_led_driver); +} + +module_init(cobalt_raq_led_init); diff --git a/drivers/leds/leds-da903x.c b/drivers/leds/leds-da903x.c new file mode 100644 index 0000000..1f3cc51 --- /dev/null +++ b/drivers/leds/leds-da903x.c @@ -0,0 +1,176 @@ +/* + * LEDs driver for Dialog Semiconductor DA9030/DA9034 + * + * Copyright (C) 2008 Compulab, Ltd. + * Mike Rapoport <mike@compulab.co.il> + * + * Copyright (C) 2006-2008 Marvell International Ltd. + * Eric Miao <eric.miao@marvell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> +#include <linux/mfd/da903x.h> + +#define DA9030_LED1_CONTROL 0x20 +#define DA9030_LED2_CONTROL 0x21 +#define DA9030_LED3_CONTROL 0x22 +#define DA9030_LED4_CONTROL 0x23 +#define DA9030_LEDPC_CONTROL 0x24 +#define DA9030_MISC_CONTROL_A 0x26 /* Vibrator Control */ + +#define DA9034_LED1_CONTROL 0x35 +#define DA9034_LED2_CONTROL 0x36 +#define DA9034_VIBRA 0x40 + +struct da903x_led { + struct led_classdev cdev; + struct work_struct work; + struct device *master; + enum led_brightness new_brightness; + int id; + int flags; +}; + +#define DA9030_LED_OFFSET(id) ((id) - DA9030_ID_LED_1) +#define DA9034_LED_OFFSET(id) ((id) - DA9034_ID_LED_1) + +static void da903x_led_work(struct work_struct *work) +{ + struct da903x_led *led = container_of(work, struct da903x_led, work); + uint8_t val; + int offset; + + switch (led->id) { + case DA9030_ID_LED_1: + case DA9030_ID_LED_2: + case DA9030_ID_LED_3: + case DA9030_ID_LED_4: + case DA9030_ID_LED_PC: + offset = DA9030_LED_OFFSET(led->id); + val = led->flags & ~0x87; + val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */ + val |= (0x7 - (led->new_brightness >> 5)) & 0x7; /* PWM<2:0> */ + da903x_write(led->master, DA9030_LED1_CONTROL + offset, val); + break; + case DA9030_ID_VIBRA: + val = led->flags & ~0x80; + val |= (led->new_brightness) ? 0x80 : 0; /* EN bit */ + da903x_write(led->master, DA9030_MISC_CONTROL_A, val); + break; + case DA9034_ID_LED_1: + case DA9034_ID_LED_2: + offset = DA9034_LED_OFFSET(led->id); + val = (led->new_brightness * 0x5f / LED_FULL) & 0x7f; + val |= (led->flags & DA9034_LED_RAMP) ? 0x80 : 0; + da903x_write(led->master, DA9034_LED1_CONTROL + offset, val); + break; + case DA9034_ID_VIBRA: + val = led->new_brightness & 0xfe; + da903x_write(led->master, DA9034_VIBRA, val); + break; + } +} + +static void da903x_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct da903x_led *led; + + led = container_of(led_cdev, struct da903x_led, cdev); + led->new_brightness = value; + schedule_work(&led->work); +} + +static int __devinit da903x_led_probe(struct platform_device *pdev) +{ + struct led_info *pdata = pdev->dev.platform_data; + struct da903x_led *led; + int id, ret; + + if (pdata == NULL) + return 0; + + id = pdev->id; + + if (!((id >= DA9030_ID_LED_1 && id <= DA9030_ID_VIBRA) || + (id >= DA9034_ID_LED_1 && id <= DA9034_ID_VIBRA))) { + dev_err(&pdev->dev, "invalid LED ID (%d) specified\n", id); + return -EINVAL; + } + + led = kzalloc(sizeof(struct da903x_led), GFP_KERNEL); + if (led == NULL) { + dev_err(&pdev->dev, "failed to alloc memory for LED%d\n", id); + return -ENOMEM; + } + + led->cdev.name = pdata->name; + led->cdev.default_trigger = pdata->default_trigger; + led->cdev.brightness_set = da903x_led_set; + led->cdev.brightness = LED_OFF; + + led->id = id; + led->flags = pdata->flags; + led->master = pdev->dev.parent; + led->new_brightness = LED_OFF; + + INIT_WORK(&led->work, da903x_led_work); + + ret = led_classdev_register(led->master, &led->cdev); + if (ret) { + dev_err(&pdev->dev, "failed to register LED %d\n", id); + goto err; + } + + platform_set_drvdata(pdev, led); + return 0; + +err: + kfree(led); + return ret; +} + +static int __devexit da903x_led_remove(struct platform_device *pdev) +{ + struct da903x_led *led = platform_get_drvdata(pdev); + + led_classdev_unregister(&led->cdev); + kfree(led); + return 0; +} + +static struct platform_driver da903x_led_driver = { + .driver = { + .name = "da903x-led", + .owner = THIS_MODULE, + }, + .probe = da903x_led_probe, + .remove = __devexit_p(da903x_led_remove), +}; + +static int __init da903x_led_init(void) +{ + return platform_driver_register(&da903x_led_driver); +} +module_init(da903x_led_init); + +static void __exit da903x_led_exit(void) +{ + platform_driver_unregister(&da903x_led_driver); +} +module_exit(da903x_led_exit); + +MODULE_DESCRIPTION("LEDs driver for Dialog Semiconductor DA9030/DA9034"); +MODULE_AUTHOR("Eric Miao <eric.miao@marvell.com>" + "Mike Rapoport <mike@compulab.co.il>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:da903x-led"); diff --git a/drivers/leds/leds-fsg.c b/drivers/leds/leds-fsg.c new file mode 100644 index 0000000..3493515 --- /dev/null +++ b/drivers/leds/leds-fsg.c @@ -0,0 +1,261 @@ +/* + * LED Driver for the Freecom FSG-3 + * + * Copyright (c) 2008 Rod Whitby <rod@whitby.id.au> + * + * Author: Rod Whitby <rod@whitby.id.au> + * + * Based on leds-spitz.c + * Copyright 2005-2006 Openedhand Ltd. + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <mach/hardware.h> +#include <asm/io.h> + +static short __iomem *latch_address; +static unsigned short latch_value; + + +static void fsg_led_wlan_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_WLAN_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_WLAN_BIT); + *latch_address = latch_value; + } +} + +static void fsg_led_wan_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_WAN_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_WAN_BIT); + *latch_address = latch_value; + } +} + +static void fsg_led_sata_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_SATA_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_SATA_BIT); + *latch_address = latch_value; + } +} + +static void fsg_led_usb_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_USB_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_USB_BIT); + *latch_address = latch_value; + } +} + +static void fsg_led_sync_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_SYNC_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_SYNC_BIT); + *latch_address = latch_value; + } +} + +static void fsg_led_ring_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) { + latch_value &= ~(1 << FSG_LED_RING_BIT); + *latch_address = latch_value; + } else { + latch_value |= (1 << FSG_LED_RING_BIT); + *latch_address = latch_value; + } +} + + + +static struct led_classdev fsg_wlan_led = { + .name = "fsg:blue:wlan", + .brightness_set = fsg_led_wlan_set, +}; + +static struct led_classdev fsg_wan_led = { + .name = "fsg:blue:wan", + .brightness_set = fsg_led_wan_set, +}; + +static struct led_classdev fsg_sata_led = { + .name = "fsg:blue:sata", + .brightness_set = fsg_led_sata_set, +}; + +static struct led_classdev fsg_usb_led = { + .name = "fsg:blue:usb", + .brightness_set = fsg_led_usb_set, +}; + +static struct led_classdev fsg_sync_led = { + .name = "fsg:blue:sync", + .brightness_set = fsg_led_sync_set, +}; + +static struct led_classdev fsg_ring_led = { + .name = "fsg:blue:ring", + .brightness_set = fsg_led_ring_set, +}; + + + +#ifdef CONFIG_PM +static int fsg_led_suspend(struct platform_device *dev, pm_message_t state) +{ + led_classdev_suspend(&fsg_wlan_led); + led_classdev_suspend(&fsg_wan_led); + led_classdev_suspend(&fsg_sata_led); + led_classdev_suspend(&fsg_usb_led); + led_classdev_suspend(&fsg_sync_led); + led_classdev_suspend(&fsg_ring_led); + return 0; +} + +static int fsg_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&fsg_wlan_led); + led_classdev_resume(&fsg_wan_led); + led_classdev_resume(&fsg_sata_led); + led_classdev_resume(&fsg_usb_led); + led_classdev_resume(&fsg_sync_led); + led_classdev_resume(&fsg_ring_led); + return 0; +} +#endif + + +static int fsg_led_probe(struct platform_device *pdev) +{ + int ret; + + /* Map the LED chip select address space */ + latch_address = (unsigned short *) ioremap(IXP4XX_EXP_BUS_BASE(2), 512); + if (!latch_address) { + ret = -ENOMEM; + goto failremap; + } + + latch_value = 0xffff; + *latch_address = latch_value; + + ret = led_classdev_register(&pdev->dev, &fsg_wlan_led); + if (ret < 0) + goto failwlan; + + ret = led_classdev_register(&pdev->dev, &fsg_wan_led); + if (ret < 0) + goto failwan; + + ret = led_classdev_register(&pdev->dev, &fsg_sata_led); + if (ret < 0) + goto failsata; + + ret = led_classdev_register(&pdev->dev, &fsg_usb_led); + if (ret < 0) + goto failusb; + + ret = led_classdev_register(&pdev->dev, &fsg_sync_led); + if (ret < 0) + goto failsync; + + ret = led_classdev_register(&pdev->dev, &fsg_ring_led); + if (ret < 0) + goto failring; + + return ret; + + failring: + led_classdev_unregister(&fsg_sync_led); + failsync: + led_classdev_unregister(&fsg_usb_led); + failusb: + led_classdev_unregister(&fsg_sata_led); + failsata: + led_classdev_unregister(&fsg_wan_led); + failwan: + led_classdev_unregister(&fsg_wlan_led); + failwlan: + iounmap(latch_address); + failremap: + + return ret; +} + +static int fsg_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&fsg_wlan_led); + led_classdev_unregister(&fsg_wan_led); + led_classdev_unregister(&fsg_sata_led); + led_classdev_unregister(&fsg_usb_led); + led_classdev_unregister(&fsg_sync_led); + led_classdev_unregister(&fsg_ring_led); + + iounmap(latch_address); + + return 0; +} + + +static struct platform_driver fsg_led_driver = { + .probe = fsg_led_probe, + .remove = fsg_led_remove, +#ifdef CONFIG_PM + .suspend = fsg_led_suspend, + .resume = fsg_led_resume, +#endif + .driver = { + .name = "fsg-led", + }, +}; + + +static int __init fsg_led_init(void) +{ + return platform_driver_register(&fsg_led_driver); +} + +static void __exit fsg_led_exit(void) +{ + platform_driver_unregister(&fsg_led_driver); +} + + +module_init(fsg_led_init); +module_exit(fsg_led_exit); + +MODULE_AUTHOR("Rod Whitby <rod@whitby.id.au>"); +MODULE_DESCRIPTION("Freecom FSG-3 LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c new file mode 100644 index 0000000..b13bd29 --- /dev/null +++ b/drivers/leds/leds-gpio.c @@ -0,0 +1,217 @@ +/* + * LEDs driver for GPIOs + * + * Copyright (C) 2007 8D Technologies inc. + * Raphael Assenat <raph@8d.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/workqueue.h> + +#include <asm/gpio.h> + +struct gpio_led_data { + struct led_classdev cdev; + unsigned gpio; + struct work_struct work; + u8 new_level; + u8 can_sleep; + u8 active_low; + int (*platform_gpio_blink_set)(unsigned gpio, + unsigned long *delay_on, unsigned long *delay_off); +}; + +static void gpio_led_work(struct work_struct *work) +{ + struct gpio_led_data *led_dat = + container_of(work, struct gpio_led_data, work); + + gpio_set_value_cansleep(led_dat->gpio, led_dat->new_level); +} + +static void gpio_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct gpio_led_data *led_dat = + container_of(led_cdev, struct gpio_led_data, cdev); + int level; + + if (value == LED_OFF) + level = 0; + else + level = 1; + + if (led_dat->active_low) + level = !level; + + /* Setting GPIOs with I2C/etc requires a task context, and we don't + * seem to have a reliable way to know if we're already in one; so + * let's just assume the worst. + */ + if (led_dat->can_sleep) { + led_dat->new_level = level; + schedule_work(&led_dat->work); + } else + gpio_set_value(led_dat->gpio, level); +} + +static int gpio_blink_set(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct gpio_led_data *led_dat = + container_of(led_cdev, struct gpio_led_data, cdev); + + return led_dat->platform_gpio_blink_set(led_dat->gpio, delay_on, delay_off); +} + +static int gpio_led_probe(struct platform_device *pdev) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led *cur_led; + struct gpio_led_data *leds_data, *led_dat; + int i, ret = 0; + + if (!pdata) + return -EBUSY; + + leds_data = kzalloc(sizeof(struct gpio_led_data) * pdata->num_leds, + GFP_KERNEL); + if (!leds_data) + return -ENOMEM; + + for (i = 0; i < pdata->num_leds; i++) { + cur_led = &pdata->leds[i]; + led_dat = &leds_data[i]; + + ret = gpio_request(cur_led->gpio, cur_led->name); + if (ret < 0) + goto err; + + led_dat->cdev.name = cur_led->name; + led_dat->cdev.default_trigger = cur_led->default_trigger; + led_dat->gpio = cur_led->gpio; + led_dat->can_sleep = gpio_cansleep(cur_led->gpio); + led_dat->active_low = cur_led->active_low; + if (pdata->gpio_blink_set) { + led_dat->platform_gpio_blink_set = pdata->gpio_blink_set; + led_dat->cdev.blink_set = gpio_blink_set; + } + led_dat->cdev.brightness_set = gpio_led_set; + led_dat->cdev.brightness = LED_OFF; + + gpio_direction_output(led_dat->gpio, led_dat->active_low); + + INIT_WORK(&led_dat->work, gpio_led_work); + + ret = led_classdev_register(&pdev->dev, &led_dat->cdev); + if (ret < 0) { + gpio_free(led_dat->gpio); + goto err; + } + } + + platform_set_drvdata(pdev, leds_data); + + return 0; + +err: + if (i > 0) { + for (i = i - 1; i >= 0; i--) { + led_classdev_unregister(&leds_data[i].cdev); + cancel_work_sync(&leds_data[i].work); + gpio_free(leds_data[i].gpio); + } + } + + kfree(leds_data); + + return ret; +} + +static int __devexit gpio_led_remove(struct platform_device *pdev) +{ + int i; + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) { + led_classdev_unregister(&leds_data[i].cdev); + cancel_work_sync(&leds_data[i].work); + gpio_free(leds_data[i].gpio); + } + + kfree(leds_data); + + return 0; +} + +#ifdef CONFIG_PM +static int gpio_led_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + int i; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) + led_classdev_suspend(&leds_data[i].cdev); + + return 0; +} + +static int gpio_led_resume(struct platform_device *pdev) +{ + struct gpio_led_platform_data *pdata = pdev->dev.platform_data; + struct gpio_led_data *leds_data; + int i; + + leds_data = platform_get_drvdata(pdev); + + for (i = 0; i < pdata->num_leds; i++) + led_classdev_resume(&leds_data[i].cdev); + + return 0; +} +#else +#define gpio_led_suspend NULL +#define gpio_led_resume NULL +#endif + +static struct platform_driver gpio_led_driver = { + .probe = gpio_led_probe, + .remove = __devexit_p(gpio_led_remove), + .suspend = gpio_led_suspend, + .resume = gpio_led_resume, + .driver = { + .name = "leds-gpio", + .owner = THIS_MODULE, + }, +}; + +static int __init gpio_led_init(void) +{ + return platform_driver_register(&gpio_led_driver); +} + +static void __exit gpio_led_exit(void) +{ + platform_driver_unregister(&gpio_led_driver); +} + +module_init(gpio_led_init); +module_exit(gpio_led_exit); + +MODULE_AUTHOR("Raphael Assenat <raph@8d.com>"); +MODULE_DESCRIPTION("GPIO LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:leds-gpio"); diff --git a/drivers/leds/leds-h1940.c b/drivers/leds/leds-h1940.c new file mode 100644 index 0000000..11b77a7 --- /dev/null +++ b/drivers/leds/leds-h1940.c @@ -0,0 +1,168 @@ +/* + * drivers/leds/leds-h1940.c + * Copyright (c) Arnaud Patard <arnaud.patard@rtp-net.org> + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + * H1940 leds driver + * + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <mach/regs-gpio.h> +#include <mach/hardware.h> +#include <mach/h1940-latch.h> + +/* + * Green led. + */ +static void h1940_greenled_set(struct led_classdev *led_dev, + enum led_brightness value) +{ + switch (value) { + case LED_HALF: + h1940_latch_control(0, H1940_LATCH_LED_FLASH); + s3c2410_gpio_setpin(S3C2410_GPA7, 1); + break; + case LED_FULL: + h1940_latch_control(0, H1940_LATCH_LED_GREEN); + s3c2410_gpio_setpin(S3C2410_GPA7, 1); + break; + default: + case LED_OFF: + h1940_latch_control(H1940_LATCH_LED_FLASH, 0); + h1940_latch_control(H1940_LATCH_LED_GREEN, 0); + s3c2410_gpio_setpin(S3C2410_GPA7, 0); + break; + } +} + +static struct led_classdev h1940_greenled = { + .name = "h1940:green", + .brightness_set = h1940_greenled_set, + .default_trigger = "h1940-charger", +}; + +/* + * Red led. + */ +static void h1940_redled_set(struct led_classdev *led_dev, + enum led_brightness value) +{ + switch (value) { + case LED_HALF: + h1940_latch_control(0, H1940_LATCH_LED_FLASH); + s3c2410_gpio_setpin(S3C2410_GPA1, 1); + break; + case LED_FULL: + h1940_latch_control(0, H1940_LATCH_LED_RED); + s3c2410_gpio_setpin(S3C2410_GPA1, 1); + break; + default: + case LED_OFF: + h1940_latch_control(H1940_LATCH_LED_FLASH, 0); + h1940_latch_control(H1940_LATCH_LED_RED, 0); + s3c2410_gpio_setpin(S3C2410_GPA1, 0); + break; + } +} + +static struct led_classdev h1940_redled = { + .name = "h1940:red", + .brightness_set = h1940_redled_set, + .default_trigger = "h1940-charger", +}; + +/* + * Blue led. + * (it can only be blue flashing led) + */ +static void h1940_blueled_set(struct led_classdev *led_dev, + enum led_brightness value) +{ + if (value) { + /* flashing Blue */ + h1940_latch_control(0, H1940_LATCH_LED_FLASH); + s3c2410_gpio_setpin(S3C2410_GPA3, 1); + } else { + h1940_latch_control(H1940_LATCH_LED_FLASH, 0); + s3c2410_gpio_setpin(S3C2410_GPA3, 0); + } + +} + +static struct led_classdev h1940_blueled = { + .name = "h1940:blue", + .brightness_set = h1940_blueled_set, + .default_trigger = "h1940-bluetooth", +}; + +static int __init h1940leds_probe(struct platform_device *pdev) +{ + int ret; + + ret = led_classdev_register(&pdev->dev, &h1940_greenled); + if (ret) + goto err_green; + + ret = led_classdev_register(&pdev->dev, &h1940_redled); + if (ret) + goto err_red; + + ret = led_classdev_register(&pdev->dev, &h1940_blueled); + if (ret) + goto err_blue; + + return 0; + +err_blue: + led_classdev_unregister(&h1940_redled); +err_red: + led_classdev_unregister(&h1940_greenled); +err_green: + return ret; +} + +static int h1940leds_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&h1940_greenled); + led_classdev_unregister(&h1940_redled); + led_classdev_unregister(&h1940_blueled); + return 0; +} + + +static struct platform_driver h1940leds_driver = { + .driver = { + .name = "h1940-leds", + .owner = THIS_MODULE, + }, + .probe = h1940leds_probe, + .remove = h1940leds_remove, +}; + + +static int __init h1940leds_init(void) +{ + return platform_driver_register(&h1940leds_driver); +} + +static void __exit h1940leds_exit(void) +{ + platform_driver_unregister(&h1940leds_driver); +} + +module_init(h1940leds_init); +module_exit(h1940leds_exit); + +MODULE_AUTHOR("Arnaud Patard <arnaud.patard@rtp-net.org>"); +MODULE_DESCRIPTION("LED driver for the iPAQ H1940"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:h1940-leds"); diff --git a/drivers/leds/leds-hp-disk.c b/drivers/leds/leds-hp-disk.c new file mode 100644 index 0000000..44fa757 --- /dev/null +++ b/drivers/leds/leds-hp-disk.c @@ -0,0 +1,155 @@ +/* + * leds-hp-disk.c - driver for HP "hard disk protection" LED + * + * Copyright (C) 2008 Pavel Machek + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/dmi.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/input.h> +#include <linux/kthread.h> +#include <linux/leds.h> +#include <acpi/acpi_drivers.h> + +#define DRIVER_NAME "leds-hp-disk" +#define ACPI_MDPS_CLASS "led" + +/* For automatic insertion of the module */ +static struct acpi_device_id hpled_device_ids[] = { + {"HPQ0004", 0}, /* HP Mobile Data Protection System PNP */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, hpled_device_ids); + +struct acpi_hpled { + struct acpi_device *device; /* The ACPI device */ +}; + +static struct acpi_hpled adev; + +static acpi_status hpled_acpi_write(acpi_handle handle, int reg) +{ + unsigned long long ret; /* Not used when writing */ + union acpi_object in_obj[1]; + struct acpi_object_list args = { 1, in_obj }; + + in_obj[0].type = ACPI_TYPE_INTEGER; + in_obj[0].integer.value = reg; + + return acpi_evaluate_integer(handle, "ALED", &args, &ret); +} + +static void hpled_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + hpled_acpi_write(adev.device->handle, !!value); +} + +static struct led_classdev hpled_led = { + .name = "hp:red:hddprotection", + .default_trigger = "heartbeat", + .brightness_set = hpled_set, +}; + +#ifdef CONFIG_PM +static int hpled_suspend(struct acpi_device *dev, pm_message_t state) +{ + led_classdev_suspend(&hpled_led); + return 0; +} + +static int hpled_resume(struct acpi_device *dev) +{ + led_classdev_resume(&hpled_led); + return 0; +} +#else +#define hpled_suspend NULL +#define hpled_resume NULL +#endif + +static int hpled_add(struct acpi_device *device) +{ + int ret; + + if (!device) + return -EINVAL; + + adev.device = device; + strcpy(acpi_device_name(device), DRIVER_NAME); + strcpy(acpi_device_class(device), ACPI_MDPS_CLASS); + device->driver_data = &adev; + + ret = led_classdev_register(NULL, &hpled_led); + return ret; +} + +static int hpled_remove(struct acpi_device *device, int type) +{ + if (!device) + return -EINVAL; + + led_classdev_unregister(&hpled_led); + return 0; +} + + + +static struct acpi_driver leds_hp_driver = { + .name = DRIVER_NAME, + .class = ACPI_MDPS_CLASS, + .ids = hpled_device_ids, + .ops = { + .add = hpled_add, + .remove = hpled_remove, + .suspend = hpled_suspend, + .resume = hpled_resume, + } +}; + +static int __init hpled_init_module(void) +{ + int ret; + + if (acpi_disabled) + return -ENODEV; + + ret = acpi_bus_register_driver(&leds_hp_driver); + if (ret < 0) + return ret; + + printk(KERN_INFO DRIVER_NAME " driver loaded.\n"); + + return 0; +} + +static void __exit hpled_exit_module(void) +{ + acpi_bus_unregister_driver(&leds_hp_driver); +} + +MODULE_DESCRIPTION("Driver for HP disk protection LED"); +MODULE_AUTHOR("Pavel Machek <pavel@suse.cz>"); +MODULE_LICENSE("GPL"); + +module_init(hpled_init_module); +module_exit(hpled_exit_module); diff --git a/drivers/leds/leds-hp6xx.c b/drivers/leds/leds-hp6xx.c new file mode 100644 index 0000000..e8fb1ba --- /dev/null +++ b/drivers/leds/leds-hp6xx.c @@ -0,0 +1,126 @@ +/* + * LED Triggers Core + * For the HP Jornada 620/660/680/690 handhelds + * + * Copyright 2008 Kristoffer Ericson <kristoffer.ericson@gmail.com> + * this driver is based on leds-spitz.c by Richard Purdie. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <asm/hd64461.h> +#include <mach/hp6xx.h> + +static void hp6xxled_green_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + u8 v8; + + v8 = inb(PKDR); + if (value) + outb(v8 & (~PKDR_LED_GREEN), PKDR); + else + outb(v8 | PKDR_LED_GREEN, PKDR); +} + +static void hp6xxled_red_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + u16 v16; + + v16 = inw(HD64461_GPBDR); + if (value) + outw(v16 & (~HD64461_GPBDR_LED_RED), HD64461_GPBDR); + else + outw(v16 | HD64461_GPBDR_LED_RED, HD64461_GPBDR); +} + +static struct led_classdev hp6xx_red_led = { + .name = "hp6xx:red", + .default_trigger = "hp6xx-charge", + .brightness_set = hp6xxled_red_set, +}; + +static struct led_classdev hp6xx_green_led = { + .name = "hp6xx:green", + .default_trigger = "ide-disk", + .brightness_set = hp6xxled_green_set, +}; + +#ifdef CONFIG_PM +static int hp6xxled_suspend(struct platform_device *dev, pm_message_t state) +{ + led_classdev_suspend(&hp6xx_red_led); + led_classdev_suspend(&hp6xx_green_led); + return 0; +} + +static int hp6xxled_resume(struct platform_device *dev) +{ + led_classdev_resume(&hp6xx_red_led); + led_classdev_resume(&hp6xx_green_led); + return 0; +} +#endif + +static int hp6xxled_probe(struct platform_device *pdev) +{ + int ret; + + ret = led_classdev_register(&pdev->dev, &hp6xx_red_led); + if (ret < 0) + return ret; + + ret = led_classdev_register(&pdev->dev, &hp6xx_green_led); + if (ret < 0) + led_classdev_unregister(&hp6xx_red_led); + + return ret; +} + +static int hp6xxled_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&hp6xx_red_led); + led_classdev_unregister(&hp6xx_green_led); + + return 0; +} + +/* work with hotplug and coldplug */ +MODULE_ALIAS("platform:hp6xx-led"); + +static struct platform_driver hp6xxled_driver = { + .probe = hp6xxled_probe, + .remove = hp6xxled_remove, +#ifdef CONFIG_PM + .suspend = hp6xxled_suspend, + .resume = hp6xxled_resume, +#endif + .driver = { + .name = "hp6xx-led", + .owner = THIS_MODULE, + }, +}; + +static int __init hp6xxled_init(void) +{ + return platform_driver_register(&hp6xxled_driver); +} + +static void __exit hp6xxled_exit(void) +{ + platform_driver_unregister(&hp6xxled_driver); +} + +module_init(hp6xxled_init); +module_exit(hp6xxled_exit); + +MODULE_AUTHOR("Kristoffer Ericson <kristoffer.ericson@gmail.com>"); +MODULE_DESCRIPTION("HP Jornada 6xx LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-locomo.c b/drivers/leds/leds-locomo.c new file mode 100644 index 0000000..5d91362 --- /dev/null +++ b/drivers/leds/leds-locomo.c @@ -0,0 +1,96 @@ +/* + * linux/drivers/leds/leds-locomo.c + * + * Copyright (C) 2005 John Lenz <lenz@cs.wisc.edu> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/leds.h> + +#include <mach/hardware.h> +#include <asm/hardware/locomo.h> + +static void locomoled_brightness_set(struct led_classdev *led_cdev, + enum led_brightness value, int offset) +{ + struct locomo_dev *locomo_dev = LOCOMO_DEV(led_cdev->dev->parent); + unsigned long flags; + + local_irq_save(flags); + if (value) + locomo_writel(LOCOMO_LPT_TOFH, locomo_dev->mapbase + offset); + else + locomo_writel(LOCOMO_LPT_TOFL, locomo_dev->mapbase + offset); + local_irq_restore(flags); +} + +static void locomoled_brightness_set0(struct led_classdev *led_cdev, + enum led_brightness value) +{ + locomoled_brightness_set(led_cdev, value, LOCOMO_LPT0); +} + +static void locomoled_brightness_set1(struct led_classdev *led_cdev, + enum led_brightness value) +{ + locomoled_brightness_set(led_cdev, value, LOCOMO_LPT1); +} + +static struct led_classdev locomo_led0 = { + .name = "locomo:amber:charge", + .default_trigger = "sharpsl-charge", + .brightness_set = locomoled_brightness_set0, +}; + +static struct led_classdev locomo_led1 = { + .name = "locomo:green:mail", + .default_trigger = "nand-disk", + .brightness_set = locomoled_brightness_set1, +}; + +static int locomoled_probe(struct locomo_dev *ldev) +{ + int ret; + + ret = led_classdev_register(&ldev->dev, &locomo_led0); + if (ret < 0) + return ret; + + ret = led_classdev_register(&ldev->dev, &locomo_led1); + if (ret < 0) + led_classdev_unregister(&locomo_led0); + + return ret; +} + +static int locomoled_remove(struct locomo_dev *dev) +{ + led_classdev_unregister(&locomo_led0); + led_classdev_unregister(&locomo_led1); + return 0; +} + +static struct locomo_driver locomoled_driver = { + .drv = { + .name = "locomoled" + }, + .devid = LOCOMO_DEVID_LED, + .probe = locomoled_probe, + .remove = locomoled_remove, +}; + +static int __init locomoled_init(void) +{ + return locomo_driver_register(&locomoled_driver); +} +module_init(locomoled_init); + +MODULE_AUTHOR("John Lenz <lenz@cs.wisc.edu>"); +MODULE_DESCRIPTION("Locomo LED driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-net48xx.c b/drivers/leds/leds-net48xx.c new file mode 100644 index 0000000..0543604 --- /dev/null +++ b/drivers/leds/leds-net48xx.c @@ -0,0 +1,115 @@ +/* + * LEDs driver for Soekris net48xx + * + * Copyright (C) 2006 Chris Boot <bootc@bootc.net> + * + * Based on leds-ams-delta.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <asm/io.h> +#include <linux/nsc_gpio.h> +#include <linux/scx200_gpio.h> + +#define DRVNAME "net48xx-led" +#define NET48XX_ERROR_LED_GPIO 20 + +static struct platform_device *pdev; + +static void net48xx_error_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + scx200_gpio_ops.gpio_set(NET48XX_ERROR_LED_GPIO, value ? 1 : 0); +} + +static struct led_classdev net48xx_error_led = { + .name = "net48xx::error", + .brightness_set = net48xx_error_led_set, +}; + +#ifdef CONFIG_PM +static int net48xx_led_suspend(struct platform_device *dev, + pm_message_t state) +{ + led_classdev_suspend(&net48xx_error_led); + return 0; +} + +static int net48xx_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&net48xx_error_led); + return 0; +} +#else +#define net48xx_led_suspend NULL +#define net48xx_led_resume NULL +#endif + +static int net48xx_led_probe(struct platform_device *pdev) +{ + return led_classdev_register(&pdev->dev, &net48xx_error_led); +} + +static int net48xx_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&net48xx_error_led); + return 0; +} + +static struct platform_driver net48xx_led_driver = { + .probe = net48xx_led_probe, + .remove = net48xx_led_remove, + .suspend = net48xx_led_suspend, + .resume = net48xx_led_resume, + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init net48xx_led_init(void) +{ + int ret; + + /* small hack, but scx200_gpio doesn't set .dev if the probe fails */ + if (!scx200_gpio_ops.dev) { + ret = -ENODEV; + goto out; + } + + ret = platform_driver_register(&net48xx_led_driver); + if (ret < 0) + goto out; + + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + platform_driver_unregister(&net48xx_led_driver); + goto out; + } + +out: + return ret; +} + +static void __exit net48xx_led_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&net48xx_led_driver); +} + +module_init(net48xx_led_init); +module_exit(net48xx_led_exit); + +MODULE_AUTHOR("Chris Boot <bootc@bootc.net>"); +MODULE_DESCRIPTION("Soekris net48xx LED driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c new file mode 100644 index 0000000..4064d4f --- /dev/null +++ b/drivers/leds/leds-pca9532.c @@ -0,0 +1,337 @@ +/* + * pca9532.c - 16-bit Led dimmer + * + * Copyright (C) 2008 Riku Voipio <riku.voipio@movial.fi> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * Datasheet: http://www.nxp.com/acrobat/datasheets/PCA9532_3.pdf + * + */ + +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/input.h> +#include <linux/mutex.h> +#include <linux/leds-pca9532.h> + +static const unsigned short normal_i2c[] = { /*0x60,*/ I2C_CLIENT_END}; +I2C_CLIENT_INSMOD_1(pca9532); + +#define PCA9532_REG_PSC(i) (0x2+(i)*2) +#define PCA9532_REG_PWM(i) (0x3+(i)*2) +#define PCA9532_REG_LS0 0x6 +#define LED_REG(led) ((led>>2)+PCA9532_REG_LS0) +#define LED_NUM(led) (led & 0x3) + +#define ldev_to_led(c) container_of(c, struct pca9532_led, ldev) + +struct pca9532_data { + struct i2c_client *client; + struct pca9532_led leds[16]; + struct mutex update_lock; + struct input_dev *idev; + u8 pwm[2]; + u8 psc[2]; +}; + +static int pca9532_probe(struct i2c_client *client, + const struct i2c_device_id *id); +static int pca9532_remove(struct i2c_client *client); + +static const struct i2c_device_id pca9532_id[] = { + { "pca9532", 0 }, + { } +}; + +MODULE_DEVICE_TABLE(i2c, pca9532_id); + +static struct i2c_driver pca9532_driver = { + .driver = { + .name = "pca9532", + }, + .probe = pca9532_probe, + .remove = pca9532_remove, + .id_table = pca9532_id, +}; + +/* We have two pwm/blinkers, but 16 possible leds to drive. Additionaly, + * the clever Thecus people are using one pwm to drive the beeper. So, + * as a compromise we average one pwm to the values requested by all + * leds that are not ON/OFF. + * */ +static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink, + enum led_brightness value) +{ + int a = 0, b = 0, i = 0; + struct pca9532_data *data = i2c_get_clientdata(client); + for (i = 0; i < 16; i++) { + if (data->leds[i].type == PCA9532_TYPE_LED && + data->leds[i].state == PCA9532_PWM0+pwm) { + a++; + b += data->leds[i].ldev.brightness; + } + } + if (a == 0) { + dev_err(&client->dev, + "fear of division by zero %d/%d, wanted %d\n", + b, a, value); + return -EINVAL; + } + b = b/a; + if (b > 0xFF) + return -EINVAL; + mutex_lock(&data->update_lock); + data->pwm[pwm] = b; + i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(pwm), + data->pwm[pwm]); + data->psc[pwm] = blink; + i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(pwm), + data->psc[pwm]); + mutex_unlock(&data->update_lock); + return 0; +} + +/* Set LED routing */ +static void pca9532_setled(struct pca9532_led *led) +{ + struct i2c_client *client = led->client; + struct pca9532_data *data = i2c_get_clientdata(client); + char reg; + + mutex_lock(&data->update_lock); + reg = i2c_smbus_read_byte_data(client, LED_REG(led->id)); + /* zero led bits */ + reg = reg & ~(0x3<<LED_NUM(led->id)*2); + /* set the new value */ + reg = reg | (led->state << LED_NUM(led->id)*2); + i2c_smbus_write_byte_data(client, LED_REG(led->id), reg); + mutex_unlock(&data->update_lock); +} + +static void pca9532_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + int err = 0; + struct pca9532_led *led = ldev_to_led(led_cdev); + + if (value == LED_OFF) + led->state = PCA9532_OFF; + else if (value == LED_FULL) + led->state = PCA9532_ON; + else { + led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */ + err = pca9532_setpwm(led->client, 0, 0, value); + if (err) + return; /* XXX: led api doesn't allow error code? */ + } + pca9532_setled(led); +} + +static int pca9532_set_blink(struct led_classdev *led_cdev, + unsigned long *delay_on, unsigned long *delay_off) +{ + struct pca9532_led *led = ldev_to_led(led_cdev); + struct i2c_client *client = led->client; + int psc; + + if (*delay_on == 0 && *delay_off == 0) { + /* led subsystem ask us for a blink rate */ + *delay_on = 1000; + *delay_off = 1000; + } + if (*delay_on != *delay_off || *delay_on > 1690 || *delay_on < 6) + return -EINVAL; + + /* Thecus specific: only use PSC/PWM 0 */ + psc = (*delay_on * 152-1)/1000; + return pca9532_setpwm(client, 0, psc, led_cdev->brightness); +} + +int pca9532_event(struct input_dev *dev, unsigned int type, unsigned int code, + int value) +{ + struct pca9532_data *data = input_get_drvdata(dev); + + if (type != EV_SND && (code != SND_BELL || code != SND_TONE)) + return -1; + + /* XXX: allow different kind of beeps with psc/pwm modifications */ + if (value > 1 && value < 32767) + data->pwm[1] = 127; + else + data->pwm[1] = 0; + + dev_info(&dev->dev, "setting beep to %d \n", data->pwm[1]); + mutex_lock(&data->update_lock); + i2c_smbus_write_byte_data(data->client, PCA9532_REG_PWM(1), + data->pwm[1]); + mutex_unlock(&data->update_lock); + + return 0; +} + +static int pca9532_configure(struct i2c_client *client, + struct pca9532_data *data, struct pca9532_platform_data *pdata) +{ + int i, err = 0; + + for (i = 0; i < 2; i++) { + data->pwm[i] = pdata->pwm[i]; + data->psc[i] = pdata->psc[i]; + i2c_smbus_write_byte_data(client, PCA9532_REG_PWM(i), + data->pwm[i]); + i2c_smbus_write_byte_data(client, PCA9532_REG_PSC(i), + data->psc[i]); + } + + for (i = 0; i < 16; i++) { + struct pca9532_led *led = &data->leds[i]; + struct pca9532_led *pled = &pdata->leds[i]; + led->client = client; + led->id = i; + led->type = pled->type; + switch (led->type) { + case PCA9532_TYPE_NONE: + break; + case PCA9532_TYPE_LED: + led->state = pled->state; + led->name = pled->name; + led->ldev.name = led->name; + led->ldev.brightness = LED_OFF; + led->ldev.brightness_set = pca9532_set_brightness; + led->ldev.blink_set = pca9532_set_blink; + if (led_classdev_register(&client->dev, + &led->ldev) < 0) { + dev_err(&client->dev, + "couldn't register LED %s\n", + led->name); + goto exit; + } + pca9532_setled(led); + break; + case PCA9532_TYPE_N2100_BEEP: + BUG_ON(data->idev); + led->state = PCA9532_PWM1; + pca9532_setled(led); + data->idev = input_allocate_device(); + if (data->idev == NULL) { + err = -ENOMEM; + goto exit; + } + data->idev->name = pled->name; + data->idev->phys = "i2c/pca9532"; + data->idev->id.bustype = BUS_HOST; + data->idev->id.vendor = 0x001f; + data->idev->id.product = 0x0001; + data->idev->id.version = 0x0100; + data->idev->evbit[0] = BIT_MASK(EV_SND); + data->idev->sndbit[0] = BIT_MASK(SND_BELL) | + BIT_MASK(SND_TONE); + data->idev->event = pca9532_event; + input_set_drvdata(data->idev, data); + err = input_register_device(data->idev); + if (err) { + input_free_device(data->idev); + data->idev = NULL; + goto exit; + } + break; + } + } + return 0; + +exit: + if (i > 0) + for (i = i - 1; i >= 0; i--) + switch (data->leds[i].type) { + case PCA9532_TYPE_NONE: + break; + case PCA9532_TYPE_LED: + led_classdev_unregister(&data->leds[i].ldev); + break; + case PCA9532_TYPE_N2100_BEEP: + if (data->idev != NULL) { + input_unregister_device(data->idev); + input_free_device(data->idev); + data->idev = NULL; + } + break; + } + + return err; + +} + +static int pca9532_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca9532_data *data = i2c_get_clientdata(client); + struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data; + + if (!i2c_check_functionality(client->adapter, + I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct pca9532_data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + dev_info(&client->dev, "setting platform data\n"); + i2c_set_clientdata(client, data); + data->client = client; + mutex_init(&data->update_lock); + + if (pca9532_pdata == NULL) + return -EIO; + + pca9532_configure(client, data, pca9532_pdata); + return 0; + +} + +static int pca9532_remove(struct i2c_client *client) +{ + struct pca9532_data *data = i2c_get_clientdata(client); + int i; + for (i = 0; i < 16; i++) + switch (data->leds[i].type) { + case PCA9532_TYPE_NONE: + break; + case PCA9532_TYPE_LED: + led_classdev_unregister(&data->leds[i].ldev); + break; + case PCA9532_TYPE_N2100_BEEP: + if (data->idev != NULL) { + input_unregister_device(data->idev); + input_free_device(data->idev); + data->idev = NULL; + } + break; + } + + kfree(data); + i2c_set_clientdata(client, NULL); + return 0; +} + +static int __init pca9532_init(void) +{ + return i2c_add_driver(&pca9532_driver); +} + +static void __exit pca9532_exit(void) +{ + i2c_del_driver(&pca9532_driver); +} + +MODULE_AUTHOR("Riku Voipio <riku.voipio@movial.fi>"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("PCA 9532 LED dimmer"); + +module_init(pca9532_init); +module_exit(pca9532_exit); + diff --git a/drivers/leds/leds-pca955x.c b/drivers/leds/leds-pca955x.c new file mode 100644 index 0000000..4e2d1a4 --- /dev/null +++ b/drivers/leds/leds-pca955x.c @@ -0,0 +1,390 @@ +/* + * Copyright 2007-2008 Extreme Engineering Solutions, Inc. + * + * Author: Nate Case <ncase@xes-inc.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for various PCA955x I2C LED drivers + * + * Supported devices: + * + * Device Description 7-bit slave address + * ------ ----------- ------------------- + * PCA9550 2-bit driver 0x60 .. 0x61 + * PCA9551 8-bit driver 0x60 .. 0x67 + * PCA9552 16-bit driver 0x60 .. 0x67 + * PCA9553/01 4-bit driver 0x62 + * PCA9553/02 4-bit driver 0x63 + * + * Philips PCA955x LED driver chips follow a register map as shown below: + * + * Control Register Description + * ---------------- ----------- + * 0x0 Input register 0 + * .. + * NUM_INPUT_REGS - 1 Last Input register X + * + * NUM_INPUT_REGS Frequency prescaler 0 + * NUM_INPUT_REGS + 1 PWM register 0 + * NUM_INPUT_REGS + 2 Frequency prescaler 1 + * NUM_INPUT_REGS + 3 PWM register 1 + * + * NUM_INPUT_REGS + 4 LED selector 0 + * NUM_INPUT_REGS + 4 + * + NUM_LED_REGS - 1 Last LED selector + * + * where NUM_INPUT_REGS and NUM_LED_REGS vary depending on how many + * bits the chip supports. + */ + +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/string.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> + +/* LED select registers determine the source that drives LED outputs */ +#define PCA955X_LS_LED_ON 0x0 /* Output LOW */ +#define PCA955X_LS_LED_OFF 0x1 /* Output HI-Z */ +#define PCA955X_LS_BLINK0 0x2 /* Blink at PWM0 rate */ +#define PCA955X_LS_BLINK1 0x3 /* Blink at PWM1 rate */ + +enum pca955x_type { + pca9550, + pca9551, + pca9552, + pca9553, +}; + +struct pca955x_chipdef { + int bits; + u8 slv_addr; /* 7-bit slave address mask */ + int slv_addr_shift; /* Number of bits to ignore */ +}; + +static struct pca955x_chipdef pca955x_chipdefs[] = { + [pca9550] = { + .bits = 2, + .slv_addr = /* 110000x */ 0x60, + .slv_addr_shift = 1, + }, + [pca9551] = { + .bits = 8, + .slv_addr = /* 1100xxx */ 0x60, + .slv_addr_shift = 3, + }, + [pca9552] = { + .bits = 16, + .slv_addr = /* 1100xxx */ 0x60, + .slv_addr_shift = 3, + }, + [pca9553] = { + .bits = 4, + .slv_addr = /* 110001x */ 0x62, + .slv_addr_shift = 1, + }, +}; + +static const struct i2c_device_id pca955x_id[] = { + { "pca9550", pca9550 }, + { "pca9551", pca9551 }, + { "pca9552", pca9552 }, + { "pca9553", pca9553 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, pca955x_id); + +struct pca955x_led { + struct pca955x_chipdef *chipdef; + struct i2c_client *client; + struct work_struct work; + spinlock_t lock; + enum led_brightness brightness; + struct led_classdev led_cdev; + int led_num; /* 0 .. 15 potentially */ + char name[32]; +}; + +/* 8 bits per input register */ +static inline int pca95xx_num_input_regs(int bits) +{ + return (bits + 7) / 8; +} + +/* 4 bits per LED selector register */ +static inline int pca95xx_num_led_regs(int bits) +{ + return (bits + 3) / 4; +} + +/* + * Return an LED selector register value based on an existing one, with + * the appropriate 2-bit state value set for the given LED number (0-3). + */ +static inline u8 pca955x_ledsel(u8 oldval, int led_num, int state) +{ + return (oldval & (~(0x3 << (led_num << 1)))) | + ((state & 0x3) << (led_num << 1)); +} + +/* + * Write to frequency prescaler register, used to program the + * period of the PWM output. period = (PSCx + 1) / 38 + */ +static void pca955x_write_psc(struct i2c_client *client, int n, u8 val) +{ + struct pca955x_led *pca955x = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, + pca95xx_num_input_regs(pca955x->chipdef->bits) + 2*n, + val); +} + +/* + * Write to PWM register, which determines the duty cycle of the + * output. LED is OFF when the count is less than the value of this + * register, and ON when it is greater. If PWMx == 0, LED is always OFF. + * + * Duty cycle is (256 - PWMx) / 256 + */ +static void pca955x_write_pwm(struct i2c_client *client, int n, u8 val) +{ + struct pca955x_led *pca955x = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, + pca95xx_num_input_regs(pca955x->chipdef->bits) + 1 + 2*n, + val); +} + +/* + * Write to LED selector register, which determines the source that + * drives the LED output. + */ +static void pca955x_write_ls(struct i2c_client *client, int n, u8 val) +{ + struct pca955x_led *pca955x = i2c_get_clientdata(client); + + i2c_smbus_write_byte_data(client, + pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n, + val); +} + +/* + * Read the LED selector register, which determines the source that + * drives the LED output. + */ +static u8 pca955x_read_ls(struct i2c_client *client, int n) +{ + struct pca955x_led *pca955x = i2c_get_clientdata(client); + + return (u8) i2c_smbus_read_byte_data(client, + pca95xx_num_input_regs(pca955x->chipdef->bits) + 4 + n); +} + +static void pca955x_led_work(struct work_struct *work) +{ + struct pca955x_led *pca955x; + u8 ls; + int chip_ls; /* which LSx to use (0-3 potentially) */ + int ls_led; /* which set of bits within LSx to use (0-3) */ + + pca955x = container_of(work, struct pca955x_led, work); + chip_ls = pca955x->led_num / 4; + ls_led = pca955x->led_num % 4; + + ls = pca955x_read_ls(pca955x->client, chip_ls); + + switch (pca955x->brightness) { + case LED_FULL: + ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_ON); + break; + case LED_OFF: + ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_LED_OFF); + break; + case LED_HALF: + ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK0); + break; + default: + /* + * Use PWM1 for all other values. This has the unwanted + * side effect of making all LEDs on the chip share the + * same brightness level if set to a value other than + * OFF, HALF, or FULL. But, this is probably better than + * just turning off for all other values. + */ + pca955x_write_pwm(pca955x->client, 1, 255-pca955x->brightness); + ls = pca955x_ledsel(ls, ls_led, PCA955X_LS_BLINK1); + break; + } + + pca955x_write_ls(pca955x->client, chip_ls, ls); +} + +static void pca955x_led_set(struct led_classdev *led_cdev, enum led_brightness value) +{ + struct pca955x_led *pca955x; + + pca955x = container_of(led_cdev, struct pca955x_led, led_cdev); + + spin_lock(&pca955x->lock); + pca955x->brightness = value; + + /* + * Must use workqueue for the actual I/O since I2C operations + * can sleep. + */ + schedule_work(&pca955x->work); + + spin_unlock(&pca955x->lock); +} + +static int __devinit pca955x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pca955x_led *pca955x; + struct pca955x_chipdef *chip; + struct i2c_adapter *adapter; + struct led_platform_data *pdata; + int i, err; + + chip = &pca955x_chipdefs[id->driver_data]; + adapter = to_i2c_adapter(client->dev.parent); + pdata = client->dev.platform_data; + + /* Make sure the slave address / chip type combo given is possible */ + if ((client->addr & ~((1 << chip->slv_addr_shift) - 1)) != + chip->slv_addr) { + dev_err(&client->dev, "invalid slave address %02x\n", + client->addr); + return -ENODEV; + } + + printk(KERN_INFO "leds-pca955x: Using %s %d-bit LED driver at " + "slave address 0x%02x\n", + id->name, chip->bits, client->addr); + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -EIO; + + if (pdata) { + if (pdata->num_leds != chip->bits) { + dev_err(&client->dev, "board info claims %d LEDs" + " on a %d-bit chip\n", + pdata->num_leds, chip->bits); + return -ENODEV; + } + } + + pca955x = kzalloc(sizeof(*pca955x) * chip->bits, GFP_KERNEL); + if (!pca955x) + return -ENOMEM; + + i2c_set_clientdata(client, pca955x); + + for (i = 0; i < chip->bits; i++) { + pca955x[i].chipdef = chip; + pca955x[i].client = client; + pca955x[i].led_num = i; + + /* Platform data can specify LED names and default triggers */ + if (pdata) { + if (pdata->leds[i].name) + snprintf(pca955x[i].name, + sizeof(pca955x[i].name), "pca955x:%s", + pdata->leds[i].name); + if (pdata->leds[i].default_trigger) + pca955x[i].led_cdev.default_trigger = + pdata->leds[i].default_trigger; + } else { + snprintf(pca955x[i].name, sizeof(pca955x[i].name), + "pca955x:%d", i); + } + + spin_lock_init(&pca955x[i].lock); + + pca955x[i].led_cdev.name = pca955x[i].name; + pca955x[i].led_cdev.brightness_set = pca955x_led_set; + + INIT_WORK(&pca955x[i].work, pca955x_led_work); + + err = led_classdev_register(&client->dev, &pca955x[i].led_cdev); + if (err < 0) + goto exit; + } + + /* Turn off LEDs */ + for (i = 0; i < pca95xx_num_led_regs(chip->bits); i++) + pca955x_write_ls(client, i, 0x55); + + /* PWM0 is used for half brightness or 50% duty cycle */ + pca955x_write_pwm(client, 0, 255-LED_HALF); + + /* PWM1 is used for variable brightness, default to OFF */ + pca955x_write_pwm(client, 1, 0); + + /* Set to fast frequency so we do not see flashing */ + pca955x_write_psc(client, 0, 0); + pca955x_write_psc(client, 1, 0); + + return 0; + +exit: + while (i--) { + led_classdev_unregister(&pca955x[i].led_cdev); + cancel_work_sync(&pca955x[i].work); + } + + kfree(pca955x); + i2c_set_clientdata(client, NULL); + + return err; +} + +static int __devexit pca955x_remove(struct i2c_client *client) +{ + struct pca955x_led *pca955x = i2c_get_clientdata(client); + int i; + + for (i = 0; i < pca955x->chipdef->bits; i++) { + led_classdev_unregister(&pca955x[i].led_cdev); + cancel_work_sync(&pca955x[i].work); + } + + kfree(pca955x); + i2c_set_clientdata(client, NULL); + + return 0; +} + +static struct i2c_driver pca955x_driver = { + .driver = { + .name = "leds-pca955x", + .owner = THIS_MODULE, + }, + .probe = pca955x_probe, + .remove = __devexit_p(pca955x_remove), + .id_table = pca955x_id, +}; + +static int __init pca955x_leds_init(void) +{ + return i2c_add_driver(&pca955x_driver); +} + +static void __exit pca955x_leds_exit(void) +{ + i2c_del_driver(&pca955x_driver); +} + +module_init(pca955x_leds_init); +module_exit(pca955x_leds_exit); + +MODULE_AUTHOR("Nate Case <ncase@xes-inc.com>"); +MODULE_DESCRIPTION("PCA955x LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-s3c24xx.c b/drivers/leds/leds-s3c24xx.c new file mode 100644 index 0000000..25a07f2 --- /dev/null +++ b/drivers/leds/leds-s3c24xx.c @@ -0,0 +1,163 @@ +/* drivers/leds/leds-s3c24xx.c + * + * (c) 2006 Simtec Electronics + * http://armlinux.simtec.co.uk/ + * Ben Dooks <ben@simtec.co.uk> + * + * S3C24XX - LEDs GPIO driver + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> + +#include <mach/hardware.h> +#include <mach/regs-gpio.h> +#include <mach/leds-gpio.h> + +/* our context */ + +struct s3c24xx_gpio_led { + struct led_classdev cdev; + struct s3c24xx_led_platdata *pdata; +}; + +static inline struct s3c24xx_gpio_led *pdev_to_gpio(struct platform_device *dev) +{ + return platform_get_drvdata(dev); +} + +static inline struct s3c24xx_gpio_led *to_gpio(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct s3c24xx_gpio_led, cdev); +} + +static void s3c24xx_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + struct s3c24xx_gpio_led *led = to_gpio(led_cdev); + struct s3c24xx_led_platdata *pd = led->pdata; + + /* there will be a short delay between setting the output and + * going from output to input when using tristate. */ + + s3c2410_gpio_setpin(pd->gpio, (value ? 1 : 0) ^ + (pd->flags & S3C24XX_LEDF_ACTLOW)); + + if (pd->flags & S3C24XX_LEDF_TRISTATE) + s3c2410_gpio_cfgpin(pd->gpio, + value ? S3C2410_GPIO_OUTPUT : S3C2410_GPIO_INPUT); + +} + +static int s3c24xx_led_remove(struct platform_device *dev) +{ + struct s3c24xx_gpio_led *led = pdev_to_gpio(dev); + + led_classdev_unregister(&led->cdev); + kfree(led); + + return 0; +} + +static int s3c24xx_led_probe(struct platform_device *dev) +{ + struct s3c24xx_led_platdata *pdata = dev->dev.platform_data; + struct s3c24xx_gpio_led *led; + int ret; + + led = kzalloc(sizeof(struct s3c24xx_gpio_led), GFP_KERNEL); + if (led == NULL) { + dev_err(&dev->dev, "No memory for device\n"); + return -ENOMEM; + } + + platform_set_drvdata(dev, led); + + led->cdev.brightness_set = s3c24xx_led_set; + led->cdev.default_trigger = pdata->def_trigger; + led->cdev.name = pdata->name; + + led->pdata = pdata; + + /* no point in having a pull-up if we are always driving */ + + if (pdata->flags & S3C24XX_LEDF_TRISTATE) { + s3c2410_gpio_setpin(pdata->gpio, 0); + s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_INPUT); + } else { + s3c2410_gpio_pullup(pdata->gpio, 0); + s3c2410_gpio_setpin(pdata->gpio, 0); + s3c2410_gpio_cfgpin(pdata->gpio, S3C2410_GPIO_OUTPUT); + } + + /* register our new led device */ + + ret = led_classdev_register(&dev->dev, &led->cdev); + if (ret < 0) { + dev_err(&dev->dev, "led_classdev_register failed\n"); + goto exit_err1; + } + + return 0; + + exit_err1: + kfree(led); + return ret; +} + + +#ifdef CONFIG_PM +static int s3c24xx_led_suspend(struct platform_device *dev, pm_message_t state) +{ + struct s3c24xx_gpio_led *led = pdev_to_gpio(dev); + + led_classdev_suspend(&led->cdev); + return 0; +} + +static int s3c24xx_led_resume(struct platform_device *dev) +{ + struct s3c24xx_gpio_led *led = pdev_to_gpio(dev); + + led_classdev_resume(&led->cdev); + return 0; +} +#else +#define s3c24xx_led_suspend NULL +#define s3c24xx_led_resume NULL +#endif + +static struct platform_driver s3c24xx_led_driver = { + .probe = s3c24xx_led_probe, + .remove = s3c24xx_led_remove, + .suspend = s3c24xx_led_suspend, + .resume = s3c24xx_led_resume, + .driver = { + .name = "s3c24xx_led", + .owner = THIS_MODULE, + }, +}; + +static int __init s3c24xx_led_init(void) +{ + return platform_driver_register(&s3c24xx_led_driver); +} + +static void __exit s3c24xx_led_exit(void) +{ + platform_driver_unregister(&s3c24xx_led_driver); +} + +module_init(s3c24xx_led_init); +module_exit(s3c24xx_led_exit); + +MODULE_AUTHOR("Ben Dooks <ben@simtec.co.uk>"); +MODULE_DESCRIPTION("S3C24XX LED driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:s3c24xx_led"); diff --git a/drivers/leds/leds-sunfire.c b/drivers/leds/leds-sunfire.c new file mode 100644 index 0000000..6b008f0 --- /dev/null +++ b/drivers/leds/leds-sunfire.c @@ -0,0 +1,273 @@ +/* leds-sunfire.c: SUNW,Ultra-Enterprise LED driver. + * + * Copyright (C) 2008 David S. Miller <davem@davemloft.net> + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/leds.h> +#include <linux/io.h> +#include <linux/platform_device.h> + +#include <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"); + +struct sunfire_led { + struct led_classdev led_cdev; + void __iomem *reg; +}; +#define to_sunfire_led(d) container_of(d, struct sunfire_led, led_cdev) + +static void __clockboard_set(struct led_classdev *led_cdev, + enum led_brightness led_val, u8 bit) +{ + struct sunfire_led *p = to_sunfire_led(led_cdev); + u8 reg = upa_readb(p->reg); + + switch (bit) { + case CLOCK_CTRL_LLED: + if (led_val) + reg &= ~bit; + else + reg |= bit; + break; + + default: + if (led_val) + reg |= bit; + else + reg &= ~bit; + break; + } + upa_writeb(reg, p->reg); +} + +static void clockboard_left_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __clockboard_set(led_cdev, led_val, CLOCK_CTRL_LLED); +} + +static void clockboard_middle_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __clockboard_set(led_cdev, led_val, CLOCK_CTRL_MLED); +} + +static void clockboard_right_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __clockboard_set(led_cdev, led_val, CLOCK_CTRL_RLED); +} + +static void __fhc_set(struct led_classdev *led_cdev, + enum led_brightness led_val, u32 bit) +{ + struct sunfire_led *p = to_sunfire_led(led_cdev); + u32 reg = upa_readl(p->reg); + + switch (bit) { + case FHC_CONTROL_LLED: + if (led_val) + reg &= ~bit; + else + reg |= bit; + break; + + default: + if (led_val) + reg |= bit; + else + reg &= ~bit; + break; + } + upa_writel(reg, p->reg); +} + +static void fhc_left_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __fhc_set(led_cdev, led_val, FHC_CONTROL_LLED); +} + +static void fhc_middle_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __fhc_set(led_cdev, led_val, FHC_CONTROL_MLED); +} + +static void fhc_right_set(struct led_classdev *led_cdev, + enum led_brightness led_val) +{ + __fhc_set(led_cdev, led_val, FHC_CONTROL_RLED); +} + +typedef void (*set_handler)(struct led_classdev *, enum led_brightness); +struct led_type { + const char *name; + set_handler handler; + const char *default_trigger; +}; + +#define NUM_LEDS_PER_BOARD 3 +struct sunfire_drvdata { + struct sunfire_led leds[NUM_LEDS_PER_BOARD]; +}; + +static int __devinit sunfire_led_generic_probe(struct platform_device *pdev, + struct led_type *types) +{ + struct sunfire_drvdata *p; + int i, err = -EINVAL; + + if (pdev->num_resources != 1) { + printk(KERN_ERR PFX "Wrong number of resources %d, should be 1\n", + pdev->num_resources); + goto out; + } + + p = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p) { + printk(KERN_ERR PFX "Could not allocate struct sunfire_drvdata\n"); + goto out; + } + + for (i = 0; i < NUM_LEDS_PER_BOARD; i++) { + struct led_classdev *lp = &p->leds[i].led_cdev; + + p->leds[i].reg = (void __iomem *) pdev->resource[0].start; + lp->name = types[i].name; + lp->brightness = LED_FULL; + lp->brightness_set = types[i].handler; + lp->default_trigger = types[i].default_trigger; + + err = led_classdev_register(&pdev->dev, lp); + if (err) { + printk(KERN_ERR PFX "Could not register %s LED\n", + lp->name); + goto out_unregister_led_cdevs; + } + } + + dev_set_drvdata(&pdev->dev, p); + + err = 0; +out: + return err; + +out_unregister_led_cdevs: + for (i--; i >= 0; i--) + led_classdev_unregister(&p->leds[i].led_cdev); + goto out; +} + +static int __devexit sunfire_led_generic_remove(struct platform_device *pdev) +{ + struct sunfire_drvdata *p = dev_get_drvdata(&pdev->dev); + int i; + + for (i = 0; i < NUM_LEDS_PER_BOARD; i++) + led_classdev_unregister(&p->leds[i].led_cdev); + + kfree(p); + + return 0; +} + +static struct led_type clockboard_led_types[NUM_LEDS_PER_BOARD] = { + { + .name = "clockboard-left", + .handler = clockboard_left_set, + }, + { + .name = "clockboard-middle", + .handler = clockboard_middle_set, + }, + { + .name = "clockboard-right", + .handler = clockboard_right_set, + .default_trigger= "heartbeat", + }, +}; + +static int __devinit sunfire_clockboard_led_probe(struct platform_device *pdev) +{ + return sunfire_led_generic_probe(pdev, clockboard_led_types); +} + +static struct led_type fhc_led_types[NUM_LEDS_PER_BOARD] = { + { + .name = "fhc-left", + .handler = fhc_left_set, + }, + { + .name = "fhc-middle", + .handler = fhc_middle_set, + }, + { + .name = "fhc-right", + .handler = fhc_right_set, + .default_trigger= "heartbeat", + }, +}; + +static int __devinit sunfire_fhc_led_probe(struct platform_device *pdev) +{ + return sunfire_led_generic_probe(pdev, fhc_led_types); +} + +MODULE_ALIAS("platform:sunfire-clockboard-leds"); +MODULE_ALIAS("platform:sunfire-fhc-leds"); + +static struct platform_driver sunfire_clockboard_led_driver = { + .probe = sunfire_clockboard_led_probe, + .remove = __devexit_p(sunfire_led_generic_remove), + .driver = { + .name = "sunfire-clockboard-leds", + .owner = THIS_MODULE, + }, +}; + +static struct platform_driver sunfire_fhc_led_driver = { + .probe = sunfire_fhc_led_probe, + .remove = __devexit_p(sunfire_led_generic_remove), + .driver = { + .name = "sunfire-fhc-leds", + .owner = THIS_MODULE, + }, +}; + +static int __init sunfire_leds_init(void) +{ + int err = platform_driver_register(&sunfire_clockboard_led_driver); + + if (err) { + printk(KERN_ERR PFX "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"); + platform_driver_unregister(&sunfire_clockboard_led_driver); + } + + return err; +} + +static void __exit sunfire_leds_exit(void) +{ + platform_driver_unregister(&sunfire_clockboard_led_driver); + platform_driver_unregister(&sunfire_fhc_led_driver); +} + +module_init(sunfire_leds_init); +module_exit(sunfire_leds_exit); diff --git a/drivers/leds/leds-wrap.c b/drivers/leds/leds-wrap.c new file mode 100644 index 0000000..2f3aa87 --- /dev/null +++ b/drivers/leds/leds-wrap.c @@ -0,0 +1,174 @@ +/* + * LEDs driver for PCEngines WRAP + * + * Copyright (C) 2006 Kristian Kielhofner <kris@krisk.org> + * + * Based on leds-net48xx.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/leds.h> +#include <linux/err.h> +#include <asm/io.h> +#include <linux/scx200_gpio.h> + +#define DRVNAME "wrap-led" +#define WRAP_POWER_LED_GPIO 2 +#define WRAP_ERROR_LED_GPIO 3 +#define WRAP_EXTRA_LED_GPIO 18 + +static struct platform_device *pdev; + +static void wrap_power_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) + scx200_gpio_set_low(WRAP_POWER_LED_GPIO); + else + scx200_gpio_set_high(WRAP_POWER_LED_GPIO); +} + +static void wrap_error_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) + scx200_gpio_set_low(WRAP_ERROR_LED_GPIO); + else + scx200_gpio_set_high(WRAP_ERROR_LED_GPIO); +} + +static void wrap_extra_led_set(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value) + scx200_gpio_set_low(WRAP_EXTRA_LED_GPIO); + else + scx200_gpio_set_high(WRAP_EXTRA_LED_GPIO); +} + +static struct led_classdev wrap_power_led = { + .name = "wrap::power", + .brightness_set = wrap_power_led_set, + .default_trigger = "default-on", +}; + +static struct led_classdev wrap_error_led = { + .name = "wrap::error", + .brightness_set = wrap_error_led_set, +}; + +static struct led_classdev wrap_extra_led = { + .name = "wrap::extra", + .brightness_set = wrap_extra_led_set, +}; + +#ifdef CONFIG_PM +static int wrap_led_suspend(struct platform_device *dev, + pm_message_t state) +{ + led_classdev_suspend(&wrap_power_led); + led_classdev_suspend(&wrap_error_led); + led_classdev_suspend(&wrap_extra_led); + return 0; +} + +static int wrap_led_resume(struct platform_device *dev) +{ + led_classdev_resume(&wrap_power_led); + led_classdev_resume(&wrap_error_led); + led_classdev_resume(&wrap_extra_led); + return 0; +} +#else +#define wrap_led_suspend NULL +#define wrap_led_resume NULL +#endif + +static int wrap_led_probe(struct platform_device *pdev) +{ + int ret; + + ret = led_classdev_register(&pdev->dev, &wrap_power_led); + if (ret < 0) + return ret; + + ret = led_classdev_register(&pdev->dev, &wrap_error_led); + if (ret < 0) + goto err1; + + ret = led_classdev_register(&pdev->dev, &wrap_extra_led); + if (ret < 0) + goto err2; + + return ret; + +err2: + led_classdev_unregister(&wrap_error_led); +err1: + led_classdev_unregister(&wrap_power_led); + + return ret; +} + +static int wrap_led_remove(struct platform_device *pdev) +{ + led_classdev_unregister(&wrap_power_led); + led_classdev_unregister(&wrap_error_led); + led_classdev_unregister(&wrap_extra_led); + return 0; +} + +static struct platform_driver wrap_led_driver = { + .probe = wrap_led_probe, + .remove = wrap_led_remove, + .suspend = wrap_led_suspend, + .resume = wrap_led_resume, + .driver = { + .name = DRVNAME, + .owner = THIS_MODULE, + }, +}; + +static int __init wrap_led_init(void) +{ + int ret; + + if (!scx200_gpio_present()) { + ret = -ENODEV; + goto out; + } + + ret = platform_driver_register(&wrap_led_driver); + if (ret < 0) + goto out; + + pdev = platform_device_register_simple(DRVNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + ret = PTR_ERR(pdev); + platform_driver_unregister(&wrap_led_driver); + goto out; + } + +out: + return ret; +} + +static void __exit wrap_led_exit(void) +{ + platform_device_unregister(pdev); + platform_driver_unregister(&wrap_led_driver); +} + +module_init(wrap_led_init); +module_exit(wrap_led_exit); + +MODULE_AUTHOR("Kristian Kielhofner <kris@krisk.org>"); +MODULE_DESCRIPTION("PCEngines WRAP LED driver"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/leds/leds.h b/drivers/leds/leds.h new file mode 100644 index 0000000..5edbf52 --- /dev/null +++ b/drivers/leds/leds.h @@ -0,0 +1,54 @@ +/* + * LED Core + * + * Copyright 2005 Openedhand Ltd. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#ifndef __LEDS_H_INCLUDED +#define __LEDS_H_INCLUDED + +#include <linux/device.h> +#include <linux/rwsem.h> +#include <linux/leds.h> + +static inline void led_set_brightness(struct led_classdev *led_cdev, + enum led_brightness value) +{ + if (value > LED_FULL) + value = LED_FULL; + led_cdev->brightness = value; + if (!(led_cdev->flags & LED_SUSPENDED)) + led_cdev->brightness_set(led_cdev, value); +} + +static inline int led_get_brightness(struct led_classdev *led_cdev) +{ + return led_cdev->brightness; +} + +extern struct rw_semaphore leds_list_lock; +extern struct list_head leds_list; + +#ifdef CONFIG_LEDS_TRIGGERS +void led_trigger_set_default(struct led_classdev *led_cdev); +void led_trigger_set(struct led_classdev *led_cdev, + struct led_trigger *trigger); +void led_trigger_remove(struct led_classdev *led_cdev); +#else +#define led_trigger_set_default(x) do {} while (0) +#define led_trigger_set(x, y) do {} while (0) +#define led_trigger_remove(x) do {} while (0) +#endif + +ssize_t led_trigger_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count); +ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, + char *buf); + +#endif /* __LEDS_H_INCLUDED */ diff --git a/drivers/leds/ledtrig-backlight.c b/drivers/leds/ledtrig-backlight.c new file mode 100644 index 0000000..d3dfcfb --- /dev/null +++ b/drivers/leds/ledtrig-backlight.c @@ -0,0 +1,110 @@ +/* + * Backlight emulation LED trigger + * + * Copyright 2008 (C) Rodolfo Giometti <giometti@linux.it> + * Copyright 2008 (C) Eurotech S.p.A. <info@eurotech.it> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/leds.h> +#include "leds.h" + +#define BLANK 1 +#define UNBLANK 0 + +struct bl_trig_notifier { + struct led_classdev *led; + int brightness; + int old_status; + struct notifier_block notifier; +}; + +static int fb_notifier_callback(struct notifier_block *p, + unsigned long event, void *data) +{ + struct bl_trig_notifier *n = container_of(p, + struct bl_trig_notifier, notifier); + struct led_classdev *led = n->led; + struct fb_event *fb_event = data; + int *blank = fb_event->data; + + switch (event) { + case FB_EVENT_BLANK : + if (*blank && n->old_status == UNBLANK) { + n->brightness = led->brightness; + led_set_brightness(led, LED_OFF); + n->old_status = BLANK; + } else if (!*blank && n->old_status == BLANK) { + led_set_brightness(led, n->brightness); + n->old_status = UNBLANK; + } + break; + } + + return 0; +} + +static void bl_trig_activate(struct led_classdev *led) +{ + int ret; + + struct bl_trig_notifier *n; + + n = kzalloc(sizeof(struct bl_trig_notifier), GFP_KERNEL); + led->trigger_data = n; + if (!n) { + dev_err(led->dev, "unable to allocate backlight trigger\n"); + return; + } + + n->led = led; + n->brightness = led->brightness; + n->old_status = UNBLANK; + n->notifier.notifier_call = fb_notifier_callback; + + ret = fb_register_client(&n->notifier); + if (ret) + dev_err(led->dev, "unable to register backlight trigger\n"); +} + +static void bl_trig_deactivate(struct led_classdev *led) +{ + struct bl_trig_notifier *n = + (struct bl_trig_notifier *) led->trigger_data; + + if (n) { + fb_unregister_client(&n->notifier); + kfree(n); + } +} + +static struct led_trigger bl_led_trigger = { + .name = "backlight", + .activate = bl_trig_activate, + .deactivate = bl_trig_deactivate +}; + +static int __init bl_trig_init(void) +{ + return led_trigger_register(&bl_led_trigger); +} + +static void __exit bl_trig_exit(void) +{ + led_trigger_unregister(&bl_led_trigger); +} + +module_init(bl_trig_init); +module_exit(bl_trig_exit); + +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_DESCRIPTION("Backlight emulation LED trigger"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/ledtrig-default-on.c b/drivers/leds/ledtrig-default-on.c new file mode 100644 index 0000000..92995e4 --- /dev/null +++ b/drivers/leds/ledtrig-default-on.c @@ -0,0 +1,45 @@ +/* + * LED Kernel Default ON Trigger + * + * Copyright 2008 Nick Forbes <nick.forbes@incepta.com> + * + * Based on Richard Purdie's ledtrig-timer.c. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/leds.h> +#include "leds.h" + +static void defon_trig_activate(struct led_classdev *led_cdev) +{ + led_set_brightness(led_cdev, LED_FULL); +} + +static struct led_trigger defon_led_trigger = { + .name = "default-on", + .activate = defon_trig_activate, +}; + +static int __init defon_trig_init(void) +{ + return led_trigger_register(&defon_led_trigger); +} + +static void __exit defon_trig_exit(void) +{ + led_trigger_unregister(&defon_led_trigger); +} + +module_init(defon_trig_init); +module_exit(defon_trig_exit); + +MODULE_AUTHOR("Nick Forbes <nick.forbes@incepta.com>"); +MODULE_DESCRIPTION("Default-ON LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-heartbeat.c b/drivers/leds/ledtrig-heartbeat.c new file mode 100644 index 0000000..4bf8cec --- /dev/null +++ b/drivers/leds/ledtrig-heartbeat.c @@ -0,0 +1,118 @@ +/* + * LED Heartbeat Trigger + * + * Copyright (C) 2006 Atsushi Nemoto <anemo@mba.ocn.ne.jp> + * + * Based on Richard Purdie's ledtrig-timer.c and some arch's + * CONFIG_HEARTBEAT code. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/sched.h> +#include <linux/leds.h> +#include "leds.h" + +struct heartbeat_trig_data { + unsigned int phase; + unsigned int period; + struct timer_list timer; +}; + +static void led_heartbeat_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *) data; + struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; + unsigned long brightness = LED_OFF; + unsigned long delay = 0; + + /* acts like an actual heart beat -- ie thump-thump-pause... */ + switch (heartbeat_data->phase) { + case 0: + /* + * The hyperbolic function below modifies the + * heartbeat period length in dependency of the + * current (1min) load. It goes through the points + * f(0)=1260, f(1)=860, f(5)=510, f(inf)->300. + */ + heartbeat_data->period = 300 + + (6720 << FSHIFT) / (5 * avenrun[0] + (7 << FSHIFT)); + heartbeat_data->period = + msecs_to_jiffies(heartbeat_data->period); + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + brightness = LED_FULL; + break; + case 1: + delay = heartbeat_data->period / 4 - msecs_to_jiffies(70); + heartbeat_data->phase++; + break; + case 2: + delay = msecs_to_jiffies(70); + heartbeat_data->phase++; + brightness = LED_FULL; + break; + default: + delay = heartbeat_data->period - heartbeat_data->period / 4 - + msecs_to_jiffies(70); + heartbeat_data->phase = 0; + break; + } + + led_set_brightness(led_cdev, brightness); + mod_timer(&heartbeat_data->timer, jiffies + delay); +} + +static void heartbeat_trig_activate(struct led_classdev *led_cdev) +{ + struct heartbeat_trig_data *heartbeat_data; + + heartbeat_data = kzalloc(sizeof(*heartbeat_data), GFP_KERNEL); + if (!heartbeat_data) + return; + + led_cdev->trigger_data = heartbeat_data; + setup_timer(&heartbeat_data->timer, + led_heartbeat_function, (unsigned long) led_cdev); + heartbeat_data->phase = 0; + led_heartbeat_function(heartbeat_data->timer.data); +} + +static void heartbeat_trig_deactivate(struct led_classdev *led_cdev) +{ + struct heartbeat_trig_data *heartbeat_data = led_cdev->trigger_data; + + if (heartbeat_data) { + del_timer_sync(&heartbeat_data->timer); + kfree(heartbeat_data); + } +} + +static struct led_trigger heartbeat_led_trigger = { + .name = "heartbeat", + .activate = heartbeat_trig_activate, + .deactivate = heartbeat_trig_deactivate, +}; + +static int __init heartbeat_trig_init(void) +{ + return led_trigger_register(&heartbeat_led_trigger); +} + +static void __exit heartbeat_trig_exit(void) +{ + led_trigger_unregister(&heartbeat_led_trigger); +} + +module_init(heartbeat_trig_init); +module_exit(heartbeat_trig_exit); + +MODULE_AUTHOR("Atsushi Nemoto <anemo@mba.ocn.ne.jp>"); +MODULE_DESCRIPTION("Heartbeat LED trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-ide-disk.c b/drivers/leds/ledtrig-ide-disk.c new file mode 100644 index 0000000..883a577 --- /dev/null +++ b/drivers/leds/ledtrig-ide-disk.c @@ -0,0 +1,63 @@ +/* + * LED IDE-Disk Activity Trigger + * + * Copyright 2006 Openedhand Ltd. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/leds.h> + +static void ledtrig_ide_timerfunc(unsigned long data); + +DEFINE_LED_TRIGGER(ledtrig_ide); +static DEFINE_TIMER(ledtrig_ide_timer, ledtrig_ide_timerfunc, 0, 0); +static int ide_activity; +static int ide_lastactivity; + +void ledtrig_ide_activity(void) +{ + ide_activity++; + if (!timer_pending(&ledtrig_ide_timer)) + mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10)); +} +EXPORT_SYMBOL(ledtrig_ide_activity); + +static void ledtrig_ide_timerfunc(unsigned long data) +{ + if (ide_lastactivity != ide_activity) { + ide_lastactivity = ide_activity; + led_trigger_event(ledtrig_ide, LED_FULL); + mod_timer(&ledtrig_ide_timer, jiffies + msecs_to_jiffies(10)); + } else { + led_trigger_event(ledtrig_ide, LED_OFF); + } +} + +static int __init ledtrig_ide_init(void) +{ + led_trigger_register_simple("ide-disk", &ledtrig_ide); + return 0; +} + +static void __exit ledtrig_ide_exit(void) +{ + led_trigger_unregister_simple(ledtrig_ide); +} + +module_init(ledtrig_ide_init); +module_exit(ledtrig_ide_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); +MODULE_DESCRIPTION("LED IDE Disk Activity Trigger"); +MODULE_LICENSE("GPL"); diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/ledtrig-timer.c new file mode 100644 index 0000000..db68196 --- /dev/null +++ b/drivers/leds/ledtrig-timer.c @@ -0,0 +1,232 @@ +/* + * LED Kernel Timer Trigger + * + * Copyright 2005-2006 Openedhand Ltd. + * + * Author: Richard Purdie <rpurdie@openedhand.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/jiffies.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include <linux/sysdev.h> +#include <linux/timer.h> +#include <linux/ctype.h> +#include <linux/leds.h> +#include "leds.h" + +struct timer_trig_data { + int brightness_on; /* LED brightness during "on" period. + * (LED_OFF < brightness_on <= LED_FULL) + */ + unsigned long delay_on; /* milliseconds on */ + unsigned long delay_off; /* milliseconds off */ + struct timer_list timer; +}; + +static void led_timer_function(unsigned long data) +{ + struct led_classdev *led_cdev = (struct led_classdev *) data; + struct timer_trig_data *timer_data = led_cdev->trigger_data; + unsigned long brightness; + unsigned long delay; + + if (!timer_data->delay_on || !timer_data->delay_off) { + led_set_brightness(led_cdev, LED_OFF); + return; + } + + brightness = led_get_brightness(led_cdev); + if (!brightness) { + /* Time to switch the LED on. */ + brightness = timer_data->brightness_on; + delay = timer_data->delay_on; + } else { + /* Store the current brightness value to be able + * to restore it when the delay_off period is over. + */ + timer_data->brightness_on = brightness; + brightness = LED_OFF; + delay = timer_data->delay_off; + } + + led_set_brightness(led_cdev, brightness); + + mod_timer(&timer_data->timer, jiffies + msecs_to_jiffies(delay)); +} + +static ssize_t led_delay_on_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct timer_trig_data *timer_data = led_cdev->trigger_data; + + return sprintf(buf, "%lu\n", timer_data->delay_on); +} + +static ssize_t led_delay_on_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct timer_trig_data *timer_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long state = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + if (timer_data->delay_on != state) { + /* the new value differs from the previous */ + timer_data->delay_on = state; + + /* deactivate previous settings */ + del_timer_sync(&timer_data->timer); + + /* try to activate hardware acceleration, if any */ + if (!led_cdev->blink_set || + led_cdev->blink_set(led_cdev, + &timer_data->delay_on, &timer_data->delay_off)) { + /* no hardware acceleration, blink via timer */ + mod_timer(&timer_data->timer, jiffies + 1); + } + } + ret = count; + } + + return ret; +} + +static ssize_t led_delay_off_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct timer_trig_data *timer_data = led_cdev->trigger_data; + + return sprintf(buf, "%lu\n", timer_data->delay_off); +} + +static ssize_t led_delay_off_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t size) +{ + struct led_classdev *led_cdev = dev_get_drvdata(dev); + struct timer_trig_data *timer_data = led_cdev->trigger_data; + int ret = -EINVAL; + char *after; + unsigned long state = simple_strtoul(buf, &after, 10); + size_t count = after - buf; + + if (*after && isspace(*after)) + count++; + + if (count == size) { + if (timer_data->delay_off != state) { + /* the new value differs from the previous */ + timer_data->delay_off = state; + + /* deactivate previous settings */ + del_timer_sync(&timer_data->timer); + + /* try to activate hardware acceleration, if any */ + if (!led_cdev->blink_set || + led_cdev->blink_set(led_cdev, + &timer_data->delay_on, &timer_data->delay_off)) { + /* no hardware acceleration, blink via timer */ + mod_timer(&timer_data->timer, jiffies + 1); + } + } + ret = count; + } + + return ret; +} + +static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store); +static DEVICE_ATTR(delay_off, 0644, led_delay_off_show, led_delay_off_store); + +static void timer_trig_activate(struct led_classdev *led_cdev) +{ + struct timer_trig_data *timer_data; + int rc; + + timer_data = kzalloc(sizeof(struct timer_trig_data), GFP_KERNEL); + if (!timer_data) + return; + + timer_data->brightness_on = led_get_brightness(led_cdev); + if (timer_data->brightness_on == LED_OFF) + timer_data->brightness_on = LED_FULL; + led_cdev->trigger_data = timer_data; + + init_timer(&timer_data->timer); + timer_data->timer.function = led_timer_function; + timer_data->timer.data = (unsigned long) led_cdev; + + rc = device_create_file(led_cdev->dev, &dev_attr_delay_on); + if (rc) + goto err_out; + rc = device_create_file(led_cdev->dev, &dev_attr_delay_off); + if (rc) + goto err_out_delayon; + + /* If there is hardware support for blinking, start one + * user friendly blink rate chosen by the driver. + */ + if (led_cdev->blink_set) + led_cdev->blink_set(led_cdev, + &timer_data->delay_on, &timer_data->delay_off); + + return; + +err_out_delayon: + device_remove_file(led_cdev->dev, &dev_attr_delay_on); +err_out: + led_cdev->trigger_data = NULL; + kfree(timer_data); +} + +static void timer_trig_deactivate(struct led_classdev *led_cdev) +{ + struct timer_trig_data *timer_data = led_cdev->trigger_data; + + if (timer_data) { + device_remove_file(led_cdev->dev, &dev_attr_delay_on); + device_remove_file(led_cdev->dev, &dev_attr_delay_off); + del_timer_sync(&timer_data->timer); + kfree(timer_data); + } +} + +static struct led_trigger timer_led_trigger = { + .name = "timer", + .activate = timer_trig_activate, + .deactivate = timer_trig_deactivate, +}; + +static int __init timer_trig_init(void) +{ + return led_trigger_register(&timer_led_trigger); +} + +static void __exit timer_trig_exit(void) +{ + led_trigger_unregister(&timer_led_trigger); +} + +module_init(timer_trig_init); +module_exit(timer_trig_exit); + +MODULE_AUTHOR("Richard Purdie <rpurdie@openedhand.com>"); +MODULE_DESCRIPTION("Timer LED trigger"); +MODULE_LICENSE("GPL"); |