summaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorStephen Rothwell <sfr@canb.auug.org.au>2008-12-29 20:53:26 +1100
committerStephen Rothwell <sfr@canb.auug.org.au>2008-12-29 20:53:26 +1100
commite645972955fbae79b3ec58d4113d6440107d83dc (patch)
tree62a01f39fd725ef6c5c8974a50a2c2742d68d54e /drivers
parenta9cab0dec6a2842cfcc88e1594e57db913505051 (diff)
parentd528785f4a5583ddd7a921e4b2501e71243c61b8 (diff)
Merge commit 'leds/for-mm'
Diffstat (limited to 'drivers')
-rw-r--r--drivers/leds/Kconfig15
-rw-r--r--drivers/leds/Makefile2
-rw-r--r--drivers/leds/led-class.c25
-rw-r--r--drivers/leds/leds-alix2.c209
-rw-r--r--drivers/leds/leds-ams-delta.c4
-rw-r--r--drivers/leds/leds-pca9532.c77
-rw-r--r--drivers/leds/leds-wm8350.c333
-rw-r--r--drivers/leds/ledtrig-timer.c92
-rw-r--r--drivers/mfd/wm8350-core.c3
-rw-r--r--drivers/regulator/wm8350-regulator.c91
10 files changed, 764 insertions, 87 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index e7fb7d2fcbfc..a4a1ae214630 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -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
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index e1967a29850e..bc247cb02e82 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -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
diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c
index 6c4a326176d7..f0d5acf320d0 100644
--- a/drivers/leds/led-class.c
+++ b/drivers/leds/led-class.c
@@ -45,23 +45,18 @@ 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;
+ unsigned long state;
+ ssize_t ret;
- if (*after && isspace(*after))
- count++;
+ ret = strict_strtoul(buf, 10, &state);
+ if (ret)
+ return ret;
- if (count == size) {
- ret = count;
+ if (state == LED_OFF)
+ led_trigger_remove(led_cdev);
+ led_set_brightness(led_cdev, state);
- if (state == LED_OFF)
- led_trigger_remove(led_cdev);
- led_set_brightness(led_cdev, state);
- }
-
- return ret;
+ return strnlen(buf, size);
}
static DEVICE_ATTR(brightness, 0644, led_brightness_show, led_brightness_store);
@@ -93,7 +88,7 @@ EXPORT_SYMBOL_GPL(led_classdev_resume);
/**
* 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)
diff --git a/drivers/leds/leds-alix2.c b/drivers/leds/leds-alix2.c
new file mode 100644
index 000000000000..d7666e6cb425
--- /dev/null
+++ b/drivers/leds/leds-alix2.c
@@ -0,0 +1,209 @@
+/*
+ * 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,
+ },
+};
+
+#ifdef CONFIG_PM
+
+static int alix_led_suspend(struct platform_device *dev, pm_message_t state)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
+ led_classdev_suspend(&alix_leds[i].cdev);
+ return 0;
+}
+
+static int alix_led_resume(struct platform_device *dev)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(alix_leds); i++)
+ led_classdev_resume(&alix_leds[i].cdev);
+ return 0;
+}
+
+#else
+
+#define alix_led_suspend NULL
+#define alix_led_resume NULL
+
+#endif
+
+static int __init alix_led_probe(struct platform_device *pdev)
+{
+ int i;
+ int ret;
+
+ for (i = 0; i < ARRAY_SIZE(alix_leds); i++) {
+ 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,
+ .suspend = alix_led_suspend,
+ .resume = alix_led_resume,
+ .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");
diff --git a/drivers/leds/leds-ams-delta.c b/drivers/leds/leds-ams-delta.c
index 1bd590bb3a6e..072157c3ba72 100644
--- a/drivers/leds/leds-ams-delta.c
+++ b/drivers/leds/leds-ams-delta.c
@@ -127,7 +127,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;
@@ -151,7 +151,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);
diff --git a/drivers/leds/leds-pca9532.c b/drivers/leds/leds-pca9532.c
index 4064d4f6b33b..76ec7498e2d5 100644
--- a/drivers/leds/leds-pca9532.c
+++ b/drivers/leds/leds-pca9532.c
@@ -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;
diff --git a/drivers/leds/leds-wm8350.c b/drivers/leds/leds-wm8350.c
new file mode 100644
index 000000000000..846ed978103f
--- /dev/null
+++ b/drivers/leds/leds-wm8350.c
@@ -0,0 +1,333 @@
+/*
+ * 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);
+}
+
+#ifdef CONFIG_PM
+static int wm8350_led_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct wm8350_led *led = platform_get_drvdata(pdev);
+
+ led_classdev_suspend(&led->cdev);
+ return 0;
+}
+
+static int wm8350_led_resume(struct platform_device *pdev)
+{
+ struct wm8350_led *led = platform_get_drvdata(pdev);
+
+ led_classdev_resume(&led->cdev);
+ return 0;
+}
+#else
+#define wm8350_led_suspend NULL
+#define wm8350_led_resume NULL
+#endif
+
+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->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,
+ .suspend = wm8350_led_suspend,
+ .resume = wm8350_led_resume,
+};
+
+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");
diff --git a/drivers/leds/ledtrig-timer.c b/drivers/leds/ledtrig-timer.c
index db681962d7bb..b0006f048dad 100644
--- a/drivers/leds/ledtrig-timer.c
+++ b/drivers/leds/ledtrig-timer.c
@@ -78,34 +78,30 @@ static ssize_t led_delay_on_store(struct device *dev,
{
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);
- }
+ unsigned long state;
+ ssize_t ret;
+
+ ret = strict_strtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ 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;
+ return strnlen(buf, size);
}
static ssize_t led_delay_off_show(struct device *dev,
@@ -122,34 +118,30 @@ static ssize_t led_delay_off_store(struct device *dev,
{
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);
- }
+ unsigned long state;
+ ssize_t ret;
+
+ ret = strict_strtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ 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;
+ return strnlen(buf, size);
}
static DEVICE_ATTR(delay_on, 0644, led_delay_on_show, led_delay_on_store);
diff --git a/drivers/mfd/wm8350-core.c b/drivers/mfd/wm8350-core.c
index 0d47fb9e4b3b..6004ff825cfa 100644
--- a/drivers/mfd/wm8350-core.c
+++ b/drivers/mfd/wm8350-core.c
@@ -1257,6 +1257,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]);
diff --git a/drivers/regulator/wm8350-regulator.c b/drivers/regulator/wm8350-regulator.c
index 1f44b17e23b1..de9a8ee13cca 100644
--- a/drivers/regulator/wm8350-regulator.c
+++ b/drivers/regulator/wm8350-regulator.c
@@ -1405,6 +1405,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,