Commit d7d717fa authored by Linus Torvalds's avatar Linus Torvalds

Merge branch 'for-linus' of git://git.o-hand.com/linux-rpurdie-leds

* 'for-linus' of git://git.o-hand.com/linux-rpurdie-leds:
  leds: ledtrig-timer - on deactivation hardware blinking should be disabled
  leds: Add suspend/resume to the core class
  leds: Add WM8350 LED driver
  leds: leds-pcs9532 - Move i2c work to a workqueque
  leds: leds-pca9532 - fix memory leak and properly handle errors
  leds: Fix wrong loop direction on removal in leds-ams-delta
  leds: fix Cobalt Raq LED dependency
  leds: Fix sparse warning in leds-ams-delta
  leds: Fixup kdoc comment to match parameter names
  leds: Make header variable naming consistent
  leds: eds-pca9532: mark pca9532_event() static
  leds: ALIX.2 LEDs driver
parents b64dc5a4 ce70a245
......@@ -63,6 +63,12 @@ config LEDS_WRAP
help
This option enables support for the PCEngines WRAP programmable LEDs.
config LEDS_ALIX2
tristate "LED Support for ALIX.2 and ALIX.3 series"
depends on LEDS_CLASS && X86 && EXPERIMENTAL
help
This option enables support for the PCEngines ALIX.2 and ALIX.3 LEDs.
config LEDS_H1940
tristate "LED Support for iPAQ H1940 device"
depends on LEDS_CLASS && ARCH_H1940
......@@ -77,7 +83,7 @@ config LEDS_COBALT_QUBE
config LEDS_COBALT_RAQ
bool "LED Support for the Cobalt Raq series"
depends on LEDS_CLASS && MIPS_COBALT
depends on LEDS_CLASS=y && MIPS_COBALT
select LEDS_TRIGGERS
help
This option enables support for the Cobalt Raq series LEDs.
......@@ -158,6 +164,13 @@ config LEDS_PCA955X
LED driver chips accessed via the I2C bus. Supported
devices include PCA9550, PCA9551, PCA9552, and PCA9553.
config LEDS_WM8350
tristate "LED Support for WM8350 AudioPlus PMIC"
depends on LEDS_CLASS && MFD_WM8350
help
This option enables support for LEDs driven by the Wolfson
Microelectronics WM8350 AudioPlus PMIC.
config LEDS_DA903X
tristate "LED Support for DA9030/DA9034 PMIC"
depends on LEDS_CLASS && PMIC_DA903X
......
......@@ -11,6 +11,7 @@ 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_ALIX2) += leds-alix2.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
......@@ -23,6 +24,7 @@ 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
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
......
......@@ -91,9 +91,29 @@ void led_classdev_resume(struct led_classdev *led_cdev)
}
EXPORT_SYMBOL_GPL(led_classdev_resume);
static int led_suspend(struct device *dev, pm_message_t state)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
led_classdev_suspend(led_cdev);
return 0;
}
static int led_resume(struct device *dev)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
if (led_cdev->flags & LED_CORE_SUSPENDRESUME)
led_classdev_resume(led_cdev);
return 0;
}
/**
* led_classdev_register - register a new object of led_classdev class.
* @dev: The device to register.
* @parent: The device to register.
* @led_cdev: the led_classdev structure for this device.
*/
int led_classdev_register(struct device *parent, struct led_classdev *led_cdev)
......@@ -174,6 +194,8 @@ static int __init leds_init(void)
leds_class = class_create(THIS_MODULE, "leds");
if (IS_ERR(leds_class))
return PTR_ERR(leds_class);
leds_class->suspend = led_suspend;
leds_class->resume = led_resume;
return 0;
}
......
/*
* LEDs driver for PCEngines ALIX.2 and ALIX.3
*
* Copyright (C) 2008 Constantin Baranov <const@mimas.ru>
*/
#include <linux/err.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/string.h>
static int force = 0;
module_param(force, bool, 0444);
MODULE_PARM_DESC(force, "Assume system has ALIX.2 style LEDs");
struct alix_led {
struct led_classdev cdev;
unsigned short port;
unsigned int on_value;
unsigned int off_value;
};
static void alix_led_set(struct led_classdev *led_cdev,
enum led_brightness brightness)
{
struct alix_led *led_dev =
container_of(led_cdev, struct alix_led, cdev);
if (brightness)
outl(led_dev->on_value, led_dev->port);
else
outl(led_dev->off_value, led_dev->port);
}
static struct alix_led alix_leds[] = {
{
.cdev = {
.name = "alix:1",
.brightness_set = alix_led_set,
},
.port = 0x6100,
.on_value = 1 << 22,
.off_value = 1 << 6,
},
{
.cdev = {
.name = "alix:2",
.brightness_set = alix_led_set,
},
.port = 0x6180,
.on_value = 1 << 25,
.off_value = 1 << 9,
},
{
.cdev = {
.name = "alix:3",
.brightness_set = alix_led_set,
},
.port = 0x6180,
.on_value = 1 << 27,
.off_value = 1 << 11,
},
};
static int __init alix_led_probe(struct platform_device *pdev)
{
int i;
int ret;
for (i = 0; i < ARRAY_SIZE(alix_leds); i++) {
alix_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
ret = led_classdev_register(&pdev->dev, &alix_leds[i].cdev);
if (ret < 0)
goto fail;
}
return 0;
fail:
while (--i >= 0)
led_classdev_unregister(&alix_leds[i].cdev);
return ret;
}
static int alix_led_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
led_classdev_unregister(&alix_leds[i].cdev);
return 0;
}
static struct platform_driver alix_led_driver = {
.remove = alix_led_remove,
.driver = {
.name = KBUILD_MODNAME,
.owner = THIS_MODULE,
},
};
static int __init alix_present(void)
{
const unsigned long bios_phys = 0x000f0000;
const size_t bios_len = 0x00010000;
const char alix_sig[] = "PC Engines ALIX.";
const size_t alix_sig_len = sizeof(alix_sig) - 1;
const char *bios_virt;
const char *scan_end;
const char *p;
int ret = 0;
if (force) {
printk(KERN_NOTICE "%s: forced to skip BIOS test, "
"assume system has ALIX.2 style LEDs\n",
KBUILD_MODNAME);
ret = 1;
goto out;
}
bios_virt = phys_to_virt(bios_phys);
scan_end = bios_virt + bios_len - (alix_sig_len + 2);
for (p = bios_virt; p < scan_end; p++) {
const char *tail;
if (memcmp(p, alix_sig, alix_sig_len) != 0) {
continue;
}
tail = p + alix_sig_len;
if ((tail[0] == '2' || tail[0] == '3') && tail[1] == '\0') {
printk(KERN_INFO
"%s: system is recognized as \"%s\"\n",
KBUILD_MODNAME, p);
ret = 1;
break;
}
}
out:
return ret;
}
static struct platform_device *pdev;
static int __init alix_led_init(void)
{
int ret;
if (!alix_present()) {
ret = -ENODEV;
goto out;
}
pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0);
if (!IS_ERR(pdev)) {
ret = platform_driver_probe(&alix_led_driver, alix_led_probe);
if (ret)
platform_device_unregister(pdev);
} else
ret = PTR_ERR(pdev);
out:
return ret;
}
static void __exit alix_led_exit(void)
{
platform_device_unregister(pdev);
platform_driver_unregister(&alix_led_driver);
}
module_init(alix_led_init);
module_exit(alix_led_exit);
MODULE_AUTHOR("Constantin Baranov <const@mimas.ru>");
MODULE_DESCRIPTION("PCEngines ALIX.2 and ALIX.3 LED driver");
MODULE_LICENSE("GPL");
......@@ -79,37 +79,12 @@ static struct ams_delta_led ams_delta_leds[] = {
},
};
#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++) {
ams_delta_leds[i].cdev.flags |= LED_CORE_SUSPENDRESUME;
ret = led_classdev_register(&pdev->dev,
&ams_delta_leds[i].cdev);
if (ret < 0)
......@@ -127,7 +102,7 @@ static int ams_delta_led_remove(struct platform_device *pdev)
{
int i;
for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i--)
for (i = 0; i < ARRAY_SIZE(ams_delta_leds); i++)
led_classdev_unregister(&ams_delta_leds[i].cdev);
return 0;
......@@ -136,8 +111,6 @@ static int ams_delta_led_remove(struct platform_device *pdev)
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,
......@@ -151,7 +124,7 @@ static int __init ams_delta_led_init(void)
static void __exit ams_delta_led_exit(void)
{
return platform_driver_unregister(&ams_delta_led_driver);
platform_driver_unregister(&ams_delta_led_driver);
}
module_init(ams_delta_led_init);
......
......@@ -142,6 +142,7 @@ static struct led_classdev clevo_mail_led = {
.name = "clevo::mail",
.brightness_set = clevo_mail_led_set,
.blink_set = clevo_mail_led_blink,
.flags = LED_CORE_SUSPENDRESUME,
};
static int __init clevo_mail_led_probe(struct platform_device *pdev)
......@@ -155,29 +156,9 @@ static int clevo_mail_led_remove(struct platform_device *pdev)
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,
......
......@@ -99,64 +99,43 @@ static void fsg_led_ring_set(struct led_classdev *led_cdev,
}
static struct led_classdev fsg_wlan_led = {
.name = "fsg:blue:wlan",
.brightness_set = fsg_led_wlan_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev fsg_wan_led = {
.name = "fsg:blue:wan",
.brightness_set = fsg_led_wan_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev fsg_sata_led = {
.name = "fsg:blue:sata",
.brightness_set = fsg_led_sata_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev fsg_usb_led = {
.name = "fsg:blue:usb",
.brightness_set = fsg_led_usb_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev fsg_sync_led = {
.name = "fsg:blue:sync",
.brightness_set = fsg_led_sync_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev fsg_ring_led = {
.name = "fsg:blue:ring",
.brightness_set = fsg_led_ring_set,
.flags = LED_CORE_SUSPENDRESUME,
};
#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;
......@@ -232,10 +211,6 @@ static int fsg_led_remove(struct platform_device *pdev)
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",
},
......
......@@ -105,6 +105,7 @@ static int gpio_led_probe(struct platform_device *pdev)
}
led_dat->cdev.brightness_set = gpio_led_set;
led_dat->cdev.brightness = LED_OFF;
led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
gpio_direction_output(led_dat->gpio, led_dat->active_low);
......@@ -154,44 +155,9 @@ static int __devexit gpio_led_remove(struct platform_device *pdev)
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,
......
......@@ -68,25 +68,9 @@ static struct led_classdev hpled_led = {
.name = "hp:red:hddprotection",
.default_trigger = "heartbeat",
.brightness_set = hpled_set,
.flags = LED_CORE_SUSPENDRESUME,
};
#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;
......@@ -121,8 +105,6 @@ static struct acpi_driver leds_hp_driver = {
.ops = {
.add = hpled_add,
.remove = hpled_remove,
.suspend = hpled_suspend,
.resume = hpled_resume,
}
};
......
......@@ -45,30 +45,16 @@ static struct led_classdev hp6xx_red_led = {
.name = "hp6xx:red",
.default_trigger = "hp6xx-charge",
.brightness_set = hp6xxled_red_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev hp6xx_green_led = {
.name = "hp6xx:green",
.default_trigger = "ide-disk",
.brightness_set = hp6xxled_green_set,
.flags = LED_CORE_SUSPENDRESUME,
};
#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;
......@@ -98,10 +84,6 @@ 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,
......
......@@ -33,26 +33,9 @@ static void net48xx_error_led_set(struct led_classdev *led_cdev,
static struct led_classdev net48xx_error_led = {
.name = "net48xx::error",
.brightness_set = net48xx_error_led_set,
.flags = LED_CORE_SUSPENDRESUME,
};
#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);
......@@ -67,8 +50,6 @@ static int net48xx_led_remove(struct platform_device *pdev)
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,
......
......@@ -16,6 +16,7 @@
#include <linux/leds.h>
#include <linux/input.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include <linux/leds-pca9532.h>
static const unsigned short normal_i2c[] = { /*0x60,*/ I2C_CLIENT_END};
......@@ -34,6 +35,7 @@ struct pca9532_data {
struct pca9532_led leds[16];
struct mutex update_lock;
struct input_dev *idev;
struct work_struct work;
u8 pwm[2];
u8 psc[2];
};
......@@ -63,7 +65,7 @@ static struct i2c_driver pca9532_driver = {
* 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,
static int pca9532_calcpwm(struct i2c_client *client, int pwm, int blink,
enum led_brightness value)
{
int a = 0, b = 0, i = 0;
......@@ -84,11 +86,17 @@ static int pca9532_setpwm(struct i2c_client *client, int pwm, int blink,
b = b/a;
if (b > 0xFF)
return -EINVAL;
mutex_lock(&data->update_lock);
data->pwm[pwm] = b;
data->psc[pwm] = blink;
return 0;
}
static int pca9532_setpwm(struct i2c_client *client, int pwm)
{
struct pca9532_data *data = i2c_get_clientdata(client);
mutex_lock(&data->update_lock);
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);
......@@ -124,11 +132,11 @@ static void pca9532_set_brightness(struct led_classdev *led_cdev,
led->state = PCA9532_ON;
else {
led->state = PCA9532_PWM0; /* Thecus: hardcode one pwm */
err = pca9532_setpwm(led->client, 0, 0, value);
err = pca9532_calcpwm(led->client, 0, 0, value);
if (err)
return; /* XXX: led api doesn't allow error code? */
}
pca9532_setled(led);
schedule_work(&led->work);
}
static int pca9532_set_blink(struct led_classdev *led_cdev,
......@@ -137,6 +145,7 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
struct pca9532_led *led = ldev_to_led(led_cdev);
struct i2c_client *client = led->client;
int psc;
int err = 0;
if (*delay_on == 0 && *delay_off == 0) {
/* led subsystem ask us for a blink rate */
......@@ -148,11 +157,15 @@ static int pca9532_set_blink(struct led_classdev *led_cdev,
/* Thecus specific: only use PSC/PWM 0 */
psc = (*delay_on * 152-1)/1000;
return pca9532_setpwm(client, 0, psc, led_cdev->brightness);
err = pca9532_calcpwm(client, 0, psc, led_cdev->brightness);
if (err)
return err;
schedule_work(&led->work);
return 0;
}
int pca9532_event(struct input_dev *dev, unsigned int type, unsigned int code,
int value)
static int pca9532_event(struct input_dev *dev, unsigned int type,
unsigned int code, int value)
{
struct pca9532_data *data = input_get_drvdata(dev);
......@@ -165,13 +178,28 @@ int pca9532_event(struct input_dev *dev, unsigned int type, unsigned int code,
else
data->pwm[1] = 0;
dev_info(&dev->dev, "setting beep to %d \n", data->pwm[1]);
schedule_work(&data->work);
return 0;
}
static void pca9532_input_work(struct work_struct *work)
{
struct pca9532_data *data;
data = container_of(work, struct pca9532_data, work);
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 void pca9532_led_work(struct work_struct *work)
{
struct pca9532_led *led;
led = container_of(work, struct pca9532_led, work);
if (led->state == PCA9532_PWM0)
pca9532_setpwm(led->client, 0);
pca9532_setled(led);
}
static int pca9532_configure(struct i2c_client *client,
......@@ -204,8 +232,9 @@ static int pca9532_configure(struct i2c_client *client,
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) {
INIT_WORK(&led->work, pca9532_led_work);
err = led_classdev_register(&client->dev, &led->ldev);
if (err < 0) {
dev_err(&client->dev,
"couldn't register LED %s\n",
led->name);
......@@ -233,9 +262,11 @@ static int pca9532_configure(struct i2c_client *client,
BIT_MASK(SND_TONE);
data->idev->event = pca9532_event;
input_set_drvdata(data->idev, data);
INIT_WORK(&data->work, pca9532_input_work);
err = input_register_device(data->idev);
if (err) {
input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
goto exit;
}
......@@ -252,18 +283,19 @@ exit:
break;
case PCA9532_TYPE_LED:
led_classdev_unregister(&data->leds[i].ldev);
cancel_work_sync(&data->leds[i].work);
break;
case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) {
input_unregister_device(data->idev);
input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
}
break;
}
return err;
}
static int pca9532_probe(struct i2c_client *client,
......@@ -271,12 +303,16 @@ static int pca9532_probe(struct i2c_client *client,
{
struct pca9532_data *data = i2c_get_clientdata(client);
struct pca9532_platform_data *pca9532_pdata = client->dev.platform_data;
int err;
if (!pca9532_pdata)
return -EIO;
if (!i2c_check_functionality(client->adapter,
I2C_FUNC_SMBUS_BYTE_DATA))
return -EIO;
data = kzalloc(sizeof(struct pca9532_data), GFP_KERNEL);
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
......@@ -285,12 +321,13 @@ static int pca9532_probe(struct i2c_client *client,
data->client = client;
mutex_init(&data->update_lock);
if (pca9532_pdata == NULL)
return -EIO;
pca9532_configure(client, data, pca9532_pdata);
return 0;
err = pca9532_configure(client, data, pca9532_pdata);
if (err) {
kfree(data);
i2c_set_clientdata(client, NULL);
}
return err;
}
static int pca9532_remove(struct i2c_client *client)
......@@ -303,11 +340,13 @@ static int pca9532_remove(struct i2c_client *client)
break;
case PCA9532_TYPE_LED:
led_classdev_unregister(&data->leds[i].ldev);
cancel_work_sync(&data->leds[i].work);
break;
case PCA9532_TYPE_N2100_BEEP:
if (data->idev != NULL) {
input_unregister_device(data->idev);
input_free_device(data->idev);
cancel_work_sync(&data->work);
data->idev = NULL;
}
break;
......
......@@ -82,6 +82,7 @@ static int s3c24xx_led_probe(struct platform_device *dev)
led->cdev.brightness_set = s3c24xx_led_set;
led->cdev.default_trigger = pdata->def_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->pdata = pdata;
......@@ -111,33 +112,9 @@ static int s3c24xx_led_probe(struct platform_device *dev)
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,
......
/*
* LED driver for WM8350 driven LEDS.
*
* Copyright(C) 2007, 2008 Wolfson Microelectronics PLC.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/err.h>
#include <linux/mfd/wm8350/pmic.h>
#include <linux/regulator/consumer.h>
/* Microamps */
static const int isink_cur[] = {
4,
5,
6,
7,
8,
10,
11,
14,
16,
19,
23,
27,
32,
39,
46,
54,
65,
77,
92,
109,
130,
154,
183,
218,
259,
308,
367,
436,
518,
616,
733,
872,
1037,
1233,
1466,
1744,
2073,
2466,
2933,
3487,
4147,
4932,
5865,
6975,
8294,
9864,
11730,
13949,
16589,
19728,
23460,
27899,
33178,
39455,
46920,
55798,
66355,
78910,
93840,
111596,
132710,
157820,
187681,
223191
};
#define to_wm8350_led(led_cdev) \
container_of(led_cdev, struct wm8350_led, cdev)
static void wm8350_led_enable(struct wm8350_led *led)
{
int ret;
if (led->enabled)
return;
ret = regulator_enable(led->isink);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to enable ISINK: %d\n", ret);
return;
}
ret = regulator_enable(led->dcdc);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to enable DCDC: %d\n", ret);
regulator_disable(led->isink);
return;
}
led->enabled = 1;
}
static void wm8350_led_disable(struct wm8350_led *led)
{
int ret;
if (!led->enabled)
return;
ret = regulator_disable(led->dcdc);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to disable DCDC: %d\n", ret);
return;
}
ret = regulator_disable(led->isink);
if (ret != 0) {
dev_err(led->cdev.dev, "Failed to disable ISINK: %d\n", ret);
regulator_enable(led->dcdc);
return;
}
led->enabled = 0;
}
static void led_work(struct work_struct *work)
{
struct wm8350_led *led = container_of(work, struct wm8350_led, work);
int ret;
int uA;
unsigned long flags;
mutex_lock(&led->mutex);
spin_lock_irqsave(&led->value_lock, flags);
if (led->value == LED_OFF) {
spin_unlock_irqrestore(&led->value_lock, flags);
wm8350_led_disable(led);
goto out;
}
/* This scales linearly into the index of valid current
* settings which results in a linear scaling of perceived
* brightness due to the non-linear current settings provided
* by the hardware.
*/
uA = (led->max_uA_index * led->value) / LED_FULL;
spin_unlock_irqrestore(&led->value_lock, flags);
BUG_ON(uA >= ARRAY_SIZE(isink_cur));
ret = regulator_set_current_limit(led->isink, isink_cur[uA],
isink_cur[uA]);
if (ret != 0)
dev_err(led->cdev.dev, "Failed to set %duA: %d\n",
isink_cur[uA], ret);
wm8350_led_enable(led);
out:
mutex_unlock(&led->mutex);
}
static void wm8350_led_set(struct led_classdev *led_cdev,
enum led_brightness value)
{
struct wm8350_led *led = to_wm8350_led(led_cdev);
unsigned long flags;
spin_lock_irqsave(&led->value_lock, flags);
led->value = value;
schedule_work(&led->work);
spin_unlock_irqrestore(&led->value_lock, flags);
}
static void wm8350_led_shutdown(struct platform_device *pdev)
{
struct wm8350_led *led = platform_get_drvdata(pdev);
mutex_lock(&led->mutex);
led->value = LED_OFF;
wm8350_led_disable(led);
mutex_unlock(&led->mutex);
}
static int wm8350_led_probe(struct platform_device *pdev)
{
struct regulator *isink, *dcdc;
struct wm8350_led *led;
struct wm8350_led_platform_data *pdata = pdev->dev.platform_data;
int ret, i;
if (pdata == NULL) {
dev_err(&pdev->dev, "no platform data\n");
return -ENODEV;
}
if (pdata->max_uA < isink_cur[0]) {
dev_err(&pdev->dev, "Invalid maximum current %duA\n",
pdata->max_uA);
return -EINVAL;
}
isink = regulator_get(&pdev->dev, "led_isink");
if (IS_ERR(isink)) {
printk(KERN_ERR "%s: cant get ISINK\n", __func__);
return PTR_ERR(isink);
}
dcdc = regulator_get(&pdev->dev, "led_vcc");
if (IS_ERR(dcdc)) {
printk(KERN_ERR "%s: cant get DCDC\n", __func__);
ret = PTR_ERR(dcdc);
goto err_isink;
}
led = kzalloc(sizeof(*led), GFP_KERNEL);
if (led == NULL) {
ret = -ENOMEM;
goto err_dcdc;
}
led->cdev.brightness_set = wm8350_led_set;
led->cdev.default_trigger = pdata->default_trigger;
led->cdev.name = pdata->name;
led->cdev.flags |= LED_CORE_SUSPENDRESUME;
led->enabled = regulator_is_enabled(isink);
led->isink = isink;
led->dcdc = dcdc;
for (i = 0; i < ARRAY_SIZE(isink_cur) - 1; i++)
if (isink_cur[i] >= pdata->max_uA)
break;
led->max_uA_index = i;
if (pdata->max_uA != isink_cur[i])
dev_warn(&pdev->dev,
"Maximum current %duA is not directly supported,"
" check platform data\n",
pdata->max_uA);
spin_lock_init(&led->value_lock);
mutex_init(&led->mutex);
INIT_WORK(&led->work, led_work);
led->value = LED_OFF;
platform_set_drvdata(pdev, led);
ret = led_classdev_register(&pdev->dev, &led->cdev);
if (ret < 0)
goto err_led;
return 0;
err_led:
kfree(led);
err_dcdc:
regulator_put(dcdc);
err_isink:
regulator_put(isink);
return ret;
}
static int wm8350_led_remove(struct platform_device *pdev)
{
struct wm8350_led *led = platform_get_drvdata(pdev);
led_classdev_unregister(&led->cdev);
flush_scheduled_work();
wm8350_led_disable(led);
regulator_put(led->dcdc);
regulator_put(led->isink);
kfree(led);
return 0;
}
static struct platform_driver wm8350_led_driver = {
.driver = {
.name = "wm8350-led",
.owner = THIS_MODULE,
},
.probe = wm8350_led_probe,
.remove = wm8350_led_remove,
.shutdown = wm8350_led_shutdown,
};
static int __devinit wm8350_led_init(void)
{
return platform_driver_register(&wm8350_led_driver);
}
module_init(wm8350_led_init);
static void wm8350_led_exit(void)
{
platform_driver_unregister(&wm8350_led_driver);
}
module_exit(wm8350_led_exit);
MODULE_AUTHOR("Mark Brown");
MODULE_DESCRIPTION("WM8350 LED driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:wm8350-led");
......@@ -56,40 +56,21 @@ static struct led_classdev wrap_power_led = {
.name = "wrap::power",
.brightness_set = wrap_power_led_set,
.default_trigger = "default-on",
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev wrap_error_led = {
.name = "wrap::error",
.brightness_set = wrap_error_led_set,
.flags = LED_CORE_SUSPENDRESUME,
};
static struct led_classdev wrap_extra_led = {
.name = "wrap::extra",
.brightness_set = wrap_extra_led_set,
.flags = LED_CORE_SUSPENDRESUME,
};
#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;
......@@ -127,8 +108,6 @@ static int wrap_led_remove(struct platform_device *pdev)
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,
......
......@@ -199,6 +199,7 @@ err_out:
static void timer_trig_deactivate(struct led_classdev *led_cdev)
{
struct timer_trig_data *timer_data = led_cdev->trigger_data;
unsigned long on = 0, off = 0;
if (timer_data) {
device_remove_file(led_cdev->dev, &dev_attr_delay_on);
......@@ -206,6 +207,10 @@ static void timer_trig_deactivate(struct led_classdev *led_cdev)
del_timer_sync(&timer_data->timer);
kfree(timer_data);
}
/* If there is hardware support for blinking, stop it */
if (led_cdev->blink_set)
led_cdev->blink_set(led_cdev, &on, &off);
}
static struct led_trigger timer_led_trigger = {
......
......@@ -1453,6 +1453,9 @@ void wm8350_device_exit(struct wm8350 *wm8350)
{
int i;
for (i = 0; i < ARRAY_SIZE(wm8350->pmic.led); i++)
platform_device_unregister(wm8350->pmic.led[i].pdev);
for (i = 0; i < ARRAY_SIZE(wm8350->pmic.pdev); i++)
platform_device_unregister(wm8350->pmic.pdev[i]);
......
......@@ -1412,6 +1412,97 @@ int wm8350_register_regulator(struct wm8350 *wm8350, int reg,
}
EXPORT_SYMBOL_GPL(wm8350_register_regulator);
/**
* wm8350_register_led - Register a WM8350 LED output
*
* @param wm8350 The WM8350 device to configure.
* @param lednum LED device index to create.
* @param dcdc The DCDC to use for the LED.
* @param isink The ISINK to use for the LED.
* @param pdata Configuration for the LED.
*
* The WM8350 supports the use of an ISINK together with a DCDC to
* provide a power-efficient LED driver. This function registers the
* regulators and instantiates the platform device for a LED. The
* operating modes for the LED regulators must be configured using
* wm8350_isink_set_flash(), wm8350_dcdc25_set_mode() and
* wm8350_dcdc_set_slot() prior to calling this function.
*/
int wm8350_register_led(struct wm8350 *wm8350, int lednum, int dcdc, int isink,
struct wm8350_led_platform_data *pdata)
{
struct wm8350_led *led;
struct platform_device *pdev;
int ret;
if (lednum > ARRAY_SIZE(wm8350->pmic.led) || lednum < 0) {
dev_err(wm8350->dev, "Invalid LED index %d\n", lednum);
return -ENODEV;
}
led = &wm8350->pmic.led[lednum];
if (led->pdev) {
dev_err(wm8350->dev, "LED %d already allocated\n", lednum);
return -EINVAL;
}
pdev = platform_device_alloc("wm8350-led", lednum);
if (pdev == NULL) {
dev_err(wm8350->dev, "Failed to allocate LED %d\n", lednum);
return -ENOMEM;
}
led->isink_consumer.dev = &pdev->dev;
led->isink_consumer.supply = "led_isink";
led->isink_init.num_consumer_supplies = 1;
led->isink_init.consumer_supplies = &led->isink_consumer;
led->isink_init.constraints.min_uA = 0;
led->isink_init.constraints.max_uA = pdata->max_uA;
led->isink_init.constraints.valid_ops_mask = REGULATOR_CHANGE_CURRENT;
led->isink_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL;
ret = wm8350_register_regulator(wm8350, isink, &led->isink_init);
if (ret != 0) {
platform_device_put(pdev);
return ret;
}
led->dcdc_consumer.dev = &pdev->dev;
led->dcdc_consumer.supply = "led_vcc";
led->dcdc_init.num_consumer_supplies = 1;
led->dcdc_init.consumer_supplies = &led->dcdc_consumer;
led->dcdc_init.constraints.valid_modes_mask = REGULATOR_MODE_NORMAL;
ret = wm8350_register_regulator(wm8350, dcdc, &led->dcdc_init);
if (ret != 0) {
platform_device_put(pdev);
return ret;
}
switch (isink) {
case WM8350_ISINK_A:
wm8350->pmic.isink_A_dcdc = dcdc;
break;
case WM8350_ISINK_B:
wm8350->pmic.isink_B_dcdc = dcdc;
break;
}
pdev->dev.platform_data = pdata;
pdev->dev.parent = wm8350->dev;
ret = platform_device_add(pdev);
if (ret != 0) {
dev_err(wm8350->dev, "Failed to register LED %d: %d\n",
lednum, ret);
platform_device_put(pdev);
return ret;
}
led->pdev = pdev;
return 0;
}
EXPORT_SYMBOL_GPL(wm8350_register_led);
static struct platform_driver wm8350_regulator_driver = {
.probe = wm8350_regulator_probe,
.remove = wm8350_regulator_remove,
......
......@@ -15,6 +15,7 @@
#define __LINUX_PCA9532_H
#include <linux/leds.h>
#include <linux/workqueue.h>
enum pca9532_state {
PCA9532_OFF = 0x0,
......@@ -31,6 +32,7 @@ struct pca9532_led {
struct i2c_client *client;
char *name;
struct led_classdev ldev;
struct work_struct work;
enum pca9532_type type;
enum pca9532_state state;
};
......
......@@ -32,7 +32,10 @@ struct led_classdev {
int brightness;
int flags;
/* Lower 16 bits reflect status */
#define LED_SUSPENDED (1 << 0)
/* Upper 16 bits reflect control information */
#define LED_CORE_SUSPENDRESUME (1 << 16)
/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
......@@ -62,7 +65,7 @@ struct led_classdev {
extern int led_classdev_register(struct device *parent,
struct led_classdev *led_cdev);
extern void led_classdev_unregister(struct led_classdev *lcd);
extern void led_classdev_unregister(struct led_classdev *led_cdev);
extern void led_classdev_suspend(struct led_classdev *led_cdev);
extern void led_classdev_resume(struct led_classdev *led_cdev);
......
......@@ -13,6 +13,10 @@
#ifndef __LINUX_MFD_WM8350_PMIC_H
#define __LINUX_MFD_WM8350_PMIC_H
#include <linux/platform_device.h>
#include <linux/leds.h>
#include <linux/regulator/machine.h>
/*
* Register values.
*/
......@@ -700,6 +704,33 @@ struct wm8350;
struct platform_device;
struct regulator_init_data;
/*
* WM8350 LED platform data
*/
struct wm8350_led_platform_data {
const char *name;
const char *default_trigger;
int max_uA;
};
struct wm8350_led {
struct platform_device *pdev;
struct mutex mutex;
struct work_struct work;
spinlock_t value_lock;
enum led_brightness value;
struct led_classdev cdev;
int max_uA_index;
int enabled;
struct regulator *isink;
struct regulator_consumer_supply isink_consumer;
struct regulator_init_data isink_init;
struct regulator *dcdc;
struct regulator_consumer_supply dcdc_consumer;
struct regulator_init_data dcdc_init;
};
struct wm8350_pmic {
/* Number of regulators of each type on this device */
int max_dcdc;
......@@ -717,10 +748,15 @@ struct wm8350_pmic {
/* regulator devices */
struct platform_device *pdev[NUM_WM8350_REGULATORS];
/* LED devices */
struct wm8350_led led[2];
};
int wm8350_register_regulator(struct wm8350 *wm8350, int reg,
struct regulator_init_data *initdata);
int wm8350_register_led(struct wm8350 *wm8350, int lednum, int dcdc, int isink,
struct wm8350_led_platform_data *pdata);
/*
* Additional DCDC control not supported via regulator API
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment