2008-12-18 11:15:33

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH] leds: add a dac124s085 SPI LED driver

A simple LED driver using the DAC124S085 DAC from NatSemi

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---

Needs a previous patch making brightness resolution configurable:

http://marc.info/?l=linux-kernel&m=122771446123369&w=2

Richard, what's its status btw?

drivers/leds/Kconfig | 7 ++
drivers/leds/Makefile | 3 +
drivers/leds/leds-dac124s085.c | 141 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 151 insertions(+), 0 deletions(-)
create mode 100644 drivers/leds/leds-dac124s085.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a4a1ae2..a28635f 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -178,6 +178,13 @@ config LEDS_DA903X
This option enables support for on-chip LED drivers found
on Dialog Semiconductor DA9030/DA9034 PMICs.

+config LEDS_DAC124S085
+ tristate "LED Support for DAC124S085 SPI DAC"
+ depends on LEDS_CLASS && SPI
+ help
+ This option enables support for DAC124S085 SPI DAC from NatSemi,
+ which can be used to control up to four LEDs.
+
comment "LED Triggers"

config LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index bc247cb..8780152 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -26,6 +26,9 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o

+# LED SPI Drivers
+obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
+
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
new file mode 100644
index 0000000..415a437
--- /dev/null
+++ b/drivers/leds/leds-dac124s085.c
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * LED driver for the DAC124S085 SPI DAC
+ */
+
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/spi/spi.h>
+
+struct dac124s085_led {
+ struct led_classdev ldev;
+ struct spi_device *spi;
+ int id;
+ int brightness;
+ char name[sizeof("dac124s085-3")];
+
+ struct mutex mutex;
+ struct work_struct work;
+ spinlock_t lock;
+};
+
+struct dac124s085 {
+ struct dac124s085_led leds[4];
+};
+
+static void dac124s085_led_work(struct work_struct *work)
+{
+ struct dac124s085_led *led = container_of(work, struct dac124s085_led,
+ work);
+ u16 word;
+
+ mutex_lock(&led->mutex);
+ word = ((led->id) << 14) | (1 << 12) | (led->brightness & 0xfff);
+ spi_write(led->spi, (const u8 *)&word, sizeof(word));
+ mutex_unlock(&led->mutex);
+}
+
+static void dac124s085_set_brightness(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
+ ldev);
+
+ spin_lock(&led->lock);
+ led->brightness = brightness;
+ schedule_work(&led->work);
+ spin_unlock(&led->lock);
+}
+
+static int dac124s085_probe(struct spi_device *spi)
+{
+ struct dac124s085 *dac;
+ struct dac124s085_led *led;
+ int i, ret;
+
+ dac = kzalloc(sizeof(*dac), GFP_KERNEL);
+ if (!dac)
+ return -ENOMEM;
+
+ spi->bits_per_word = 16;
+
+ for (i = 0; i < 4; i++) {
+ led = dac->leds + i;
+ led->id = i;
+ led->brightness = LED_OFF;
+ led->spi = spi;
+ snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
+ spin_lock_init(&led->lock);
+ INIT_WORK(&led->work, dac124s085_led_work);
+ mutex_init(&led->mutex);
+ led->ldev.name = led->name;
+ led->ldev.brightness = LED_OFF;
+ led->ldev.max_brightness = 0xfff;
+ led->ldev.brightness_set = dac124s085_set_brightness;
+ ret = led_classdev_register(&spi->dev, &led->ldev);
+ if (ret < 0)
+ goto eledcr;
+ }
+
+ spi_set_drvdata(spi, dac);
+
+ return 0;
+
+eledcr:
+ while (i--)
+ led_classdev_unregister(&dac->leds[i].ldev);
+
+ spi_set_drvdata(spi, NULL);
+ kfree(dac);
+ return ret;
+}
+
+static int dac124s085_remove(struct spi_device *spi)
+{
+ struct dac124s085 *dac = spi_get_drvdata(spi);
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ led_classdev_unregister(&dac->leds[i].ldev);
+ cancel_work_sync(&dac->leds[i].work);
+ }
+
+ spi_set_drvdata(spi, NULL);
+ kfree(dac);
+
+ return 0;
+}
+
+static struct spi_driver dac124s085_driver = {
+ .probe = dac124s085_probe,
+ .remove = dac124s085_remove,
+ .driver = {
+ .name = "dac124s085",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init dac124s085_leds_init(void)
+{
+ return spi_register_driver(&dac124s085_driver);
+}
+
+static void __exit dac124s085_leds_exit(void)
+{
+ spi_unregister_driver(&dac124s085_driver);
+}
+
+module_init(dac124s085_leds_init);
+module_exit(dac124s085_leds_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
+MODULE_DESCRIPTION("DAC124S085 LED driver");
+MODULE_LICENSE("GPL v2");
--
1.5.4


2008-12-29 23:12:54

by Andrew Morton

[permalink] [raw]
Subject: Re: [PATCH v2] leds: add a dac124s085 SPI LED driver

On Thu, 18 Dec 2008 13:51:06 +0100 (CET)
Guennadi Liakhovetski <[email protected]> wrote:

> A simple LED driver using the DAC124S085 DAC from NatSemi
>
> Signed-off-by: Guennadi Liakhovetski <[email protected]>
> ---
>
> Changes since v1: implemented corrections by Ben Dooks: macro instead of a
> magic constant, endianness conversion, ARRAY_SIZE instead of a hard-coded
> constant.
>
> Still needs a previous patch making maximum brightness configurable:
>
> http://marc.info/?l=linux-kernel&m=122771446123369&w=2
>
> drivers/leds/Kconfig | 7 ++
> drivers/leds/Makefile | 3 +
> drivers/leds/leds-dac124s085.c | 147 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 157 insertions(+), 0 deletions(-)
> create mode 100644 drivers/leds/leds-dac124s085.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index a4a1ae2..a28635f 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -178,6 +178,13 @@ config LEDS_DA903X
> This option enables support for on-chip LED drivers found
> on Dialog Semiconductor DA9030/DA9034 PMICs.
>
> +config LEDS_DAC124S085
> + tristate "LED Support for DAC124S085 SPI DAC"
> + depends on LEDS_CLASS && SPI
> + help
> + This option enables support for DAC124S085 SPI DAC from NatSemi,
> + which can be used to control up to four LEDs.
> +
> comment "LED Triggers"
>
> config LEDS_TRIGGERS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index bc247cb..8780152 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -26,6 +26,9 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
> obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o
> obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
>
> +# LED SPI Drivers
> +obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
> +
> # LED Triggers
> obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
> obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
> diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
> new file mode 100644
> index 0000000..9bd25c1
> --- /dev/null
> +++ b/drivers/leds/leds-dac124s085.c
> @@ -0,0 +1,147 @@
> +/*
> + * Copyright 2008
> + * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
> + *
> + * This file is subject to the terms and conditions of version 2 of
> + * the GNU General Public License. See the file COPYING in the main
> + * directory of this archive for more details.
> + *
> + * LED driver for the DAC124S085 SPI DAC
> + */
> +
> +#include <linux/leds.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/spi/spi.h>
> +
> +struct dac124s085_led {
> + struct led_classdev ldev;
> + struct spi_device *spi;
> + int id;
> + int brightness;
> + char name[sizeof("dac124s085-3")];
> +
> + struct mutex mutex;
> + struct work_struct work;
> + spinlock_t lock;
> +};
> +
> +struct dac124s085 {
> + struct dac124s085_led leds[4];
> +};
> +
> +#define REG_WRITE (0 << 12)
> +#define REG_WRITE_UPDATE (1 << 12)
> +#define ALL_WRITE_UPDATE (2 << 12)
> +#define POWER_DOWN_OUTPUT (3 << 12)
> +
> +static void dac124s085_led_work(struct work_struct *work)
> +{
> + struct dac124s085_led *led = container_of(work, struct dac124s085_led,
> + work);
> + u16 word;
> +
> + mutex_lock(&led->mutex);
> + word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE |
> + (led->brightness & 0xfff));
> + spi_write(led->spi, (const u8 *)&word, sizeof(word));

This looks like it might have endianness issues?

> + mutex_unlock(&led->mutex);
> +}
> +
> +static void dac124s085_set_brightness(struct led_classdev *ldev,
> + enum led_brightness brightness)
> +{
> + struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
> + ldev);
> +
> + spin_lock(&led->lock);
> + led->brightness = brightness;
> + schedule_work(&led->work);
> + spin_unlock(&led->lock);
> +}

So.. why do we need to defer this to a workqueue rather than just
performing the operation synchronously?

What happens if there are two calls to set_brightness() in quick
succession, and the second occurs before the first has completed? I
think the schedule_work() fails and the second write gets lost?

> +static int dac124s085_probe(struct spi_device *spi)
> +{
> + struct dac124s085 *dac;
> + struct dac124s085_led *led;
> + int i, ret;
> +
> + dac = kzalloc(sizeof(*dac), GFP_KERNEL);
> + if (!dac)
> + return -ENOMEM;
> +
> + spi->bits_per_word = 16;
> +
> + for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
> + led = dac->leds + i;
> + led->id = i;
> + led->brightness = LED_OFF;
> + led->spi = spi;
> + snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
> + spin_lock_init(&led->lock);
> + INIT_WORK(&led->work, dac124s085_led_work);
> + mutex_init(&led->mutex);
> + led->ldev.name = led->name;
> + led->ldev.brightness = LED_OFF;
> + led->ldev.max_brightness = 0xfff;
> + led->ldev.brightness_set = dac124s085_set_brightness;
> + ret = led_classdev_register(&spi->dev, &led->ldev);
> + if (ret < 0)
> + goto eledcr;
> + }
> +
> + spi_set_drvdata(spi, dac);
> +
> + return 0;
> +
> +eledcr:
> + while (i--)
> + led_classdev_unregister(&dac->leds[i].ldev);
> +
> + spi_set_drvdata(spi, NULL);
> + kfree(dac);
> + return ret;
> +}
> +
> +static int dac124s085_remove(struct spi_device *spi)
> +{
> + struct dac124s085 *dac = spi_get_drvdata(spi);
> + int i;
> +
> + for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
> + led_classdev_unregister(&dac->leds[i].ldev);
> + cancel_work_sync(&dac->leds[i].work);
> + }
> +
> + spi_set_drvdata(spi, NULL);
> + kfree(dac);
> +
> + return 0;
> +}
> +
> +static struct spi_driver dac124s085_driver = {
> + .probe = dac124s085_probe,
> + .remove = dac124s085_remove,
> + .driver = {
> + .name = "dac124s085",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init dac124s085_leds_init(void)
> +{
> + return spi_register_driver(&dac124s085_driver);
> +}
> +
> +static void __exit dac124s085_leds_exit(void)
> +{
> + spi_unregister_driver(&dac124s085_driver);
> +}
> +
> +module_init(dac124s085_leds_init);
> +module_exit(dac124s085_leds_exit);
> +
> +MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
> +MODULE_DESCRIPTION("DAC124S085 LED driver");
> +MODULE_LICENSE("GPL v2");

2008-12-29 23:55:00

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH v2] leds: add a dac124s085 SPI LED driver

On Mon, 29 Dec 2008, Andrew Morton wrote:

> On Thu, 18 Dec 2008 13:51:06 +0100 (CET)
> Guennadi Liakhovetski <[email protected]> wrote:
>
> > +static void dac124s085_led_work(struct work_struct *work)
> > +{
> > + struct dac124s085_led *led = container_of(work, struct dac124s085_led,
> > + work);
> > + u16 word;
> > +
> > + mutex_lock(&led->mutex);
> > + word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE |
> > + (led->brightness & 0xfff));
> > + spi_write(led->spi, (const u8 *)&word, sizeof(word));
>
> This looks like it might have endianness issues?

Actually this was an attempt to fix the endianness issue pointed out in an
earlier review by Ben Dooks, have I done it wrong?

> > + mutex_unlock(&led->mutex);
> > +}
> > +
> > +static void dac124s085_set_brightness(struct led_classdev *ldev,
> > + enum led_brightness brightness)
> > +{
> > + struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
> > + ldev);
> > +
> > + spin_lock(&led->lock);
> > + led->brightness = brightness;
> > + schedule_work(&led->work);
> > + spin_unlock(&led->lock);
> > +}
>
> So.. why do we need to defer this to a workqueue rather than just
> performing the operation synchronously?

Here's why (from include/linux/leds.h):

/* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */
void (*brightness_set)(struct led_classdev *led_cdev,
enum led_brightness brightness);

> What happens if there are two calls to set_brightness() in quick
> succession, and the second occurs before the first has completed? I
> think the schedule_work() fails and the second write gets lost?

This seems to be a common "feature" of multiple leds drivers. In fact, if
the two calls are successive, then the queue will take care of the
execution order. If they are "simultaneous," i.e., the callers didn't
synchronise, how do we know who is the first and who is the second? Isn't
the one that _completes_ first actually the first? Or am I missing
something?

Thanks for the review!
Guennadi
---
Guennadi Liakhovetski, Ph.D.

DENX Software Engineering GmbH, MD: Wolfgang Denk & Detlev Zundel
HRB 165235 Munich, Office: Kirchenstr.5, D-82194 Groebenzell, Germany
Phone: +49-8142-66989-0 Fax: +49-8142-66989-80 Email: [email protected]

2008-12-18 12:06:35

by Ben Dooks

[permalink] [raw]
Subject: Re: [PATCH] leds: add a dac124s085 SPI LED driver

On Thu, Dec 18, 2008 at 12:15:28PM +0100, Guennadi Liakhovetski wrote:
> A simple LED driver using the DAC124S085 DAC from NatSemi
>
> Signed-off-by: Guennadi Liakhovetski <[email protected]>
> ---
>
> Needs a previous patch making brightness resolution configurable:
>
> http://marc.info/?l=linux-kernel&m=122771446123369&w=2
>
> Richard, what's its status btw?
>
> drivers/leds/Kconfig | 7 ++
> drivers/leds/Makefile | 3 +
> drivers/leds/leds-dac124s085.c | 141 ++++++++++++++++++++++++++++++++++++++++
> 3 files changed, 151 insertions(+), 0 deletions(-)
> create mode 100644 drivers/leds/leds-dac124s085.c
>
> diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
> index a4a1ae2..a28635f 100644
> --- a/drivers/leds/Kconfig
> +++ b/drivers/leds/Kconfig
> @@ -178,6 +178,13 @@ config LEDS_DA903X
> This option enables support for on-chip LED drivers found
> on Dialog Semiconductor DA9030/DA9034 PMICs.
>
> +config LEDS_DAC124S085
> + tristate "LED Support for DAC124S085 SPI DAC"
> + depends on LEDS_CLASS && SPI
> + help
> + This option enables support for DAC124S085 SPI DAC from NatSemi,
> + which can be used to control up to four LEDs.
> +
> comment "LED Triggers"
>
> config LEDS_TRIGGERS
> diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
> index bc247cb..8780152 100644
> --- a/drivers/leds/Makefile
> +++ b/drivers/leds/Makefile
> @@ -26,6 +26,9 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
> obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o
> obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o
>
> +# LED SPI Drivers
> +obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
> +
> # LED Triggers
> obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
> obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
> diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
> new file mode 100644
> index 0000000..415a437
> --- /dev/null
> +++ b/drivers/leds/leds-dac124s085.c
> @@ -0,0 +1,141 @@
> +/*
> + * Copyright 2008
> + * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
> + *
> + * This file is subject to the terms and conditions of version 2 of
> + * the GNU General Public License. See the file COPYING in the main
> + * directory of this archive for more details.
> + *
> + * LED driver for the DAC124S085 SPI DAC
> + */
> +
> +#include <linux/leds.h>
> +#include <linux/mutex.h>
> +#include <linux/spinlock.h>
> +#include <linux/workqueue.h>
> +#include <linux/spi/spi.h>
> +
> +struct dac124s085_led {
> + struct led_classdev ldev;
> + struct spi_device *spi;
> + int id;
> + int brightness;
> + char name[sizeof("dac124s085-3")];
> +
> + struct mutex mutex;
> + struct work_struct work;
> + spinlock_t lock;
> +};
> +
> +struct dac124s085 {
> + struct dac124s085_led leds[4];
> +};
> +
> +static void dac124s085_led_work(struct work_struct *work)
> +{
> + struct dac124s085_led *led = container_of(work, struct dac124s085_led,
> + work);
> + u16 word;
> +
> + mutex_lock(&led->mutex);
> + word = ((led->id) << 14) | (1 << 12) | (led->brightness & 0xfff);

magic constant, please define and use a define.

> + spi_write(led->spi, (const u8 *)&word, sizeof(word));

casting between u16 and u8 is just waiting for endian-ness problems to
happen.

> + mutex_unlock(&led->mutex);
> +}
> +
> +static void dac124s085_set_brightness(struct led_classdev *ldev,
> + enum led_brightness brightness)
> +{
> + struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
> + ldev);
> +
> + spin_lock(&led->lock);
> + led->brightness = brightness;
> + schedule_work(&led->work);
> + spin_unlock(&led->lock);
> +}
> +
> +static int dac124s085_probe(struct spi_device *spi)
> +{
> + struct dac124s085 *dac;
> + struct dac124s085_led *led;
> + int i, ret;
> +
> + dac = kzalloc(sizeof(*dac), GFP_KERNEL);
> + if (!dac)
> + return -ENOMEM;
> +
> + spi->bits_per_word = 16;
> +
> + for (i = 0; i < 4; i++) {

how about ARRAY_SIZE(dac->leds) instead of 4.

> + led = dac->leds + i;
> + led->id = i;
> + led->brightness = LED_OFF;
> + led->spi = spi;
> + snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
> + spin_lock_init(&led->lock);
> + INIT_WORK(&led->work, dac124s085_led_work);
> + mutex_init(&led->mutex);
> + led->ldev.name = led->name;
> + led->ldev.brightness = LED_OFF;
> + led->ldev.max_brightness = 0xfff;
> + led->ldev.brightness_set = dac124s085_set_brightness;
> + ret = led_classdev_register(&spi->dev, &led->ldev);
> + if (ret < 0)
> + goto eledcr;
> + }
> +
> + spi_set_drvdata(spi, dac);
> +
> + return 0;
> +
> +eledcr:
> + while (i--)
> + led_classdev_unregister(&dac->leds[i].ldev);
> +
> + spi_set_drvdata(spi, NULL);
> + kfree(dac);
> + return ret;
> +}
> +
> +static int dac124s085_remove(struct spi_device *spi)
> +{
> + struct dac124s085 *dac = spi_get_drvdata(spi);
> + int i;
> +
> + for (i = 0; i < 4; i++) {

see previous comment.

> + led_classdev_unregister(&dac->leds[i].ldev);
> + cancel_work_sync(&dac->leds[i].work);
> + }
> +
> + spi_set_drvdata(spi, NULL);
> + kfree(dac);
> +
> + return 0;
> +}
> +
> +static struct spi_driver dac124s085_driver = {
> + .probe = dac124s085_probe,
> + .remove = dac124s085_remove,
> + .driver = {
> + .name = "dac124s085",
> + .owner = THIS_MODULE,
> + },
> +};
> +
> +static int __init dac124s085_leds_init(void)
> +{
> + return spi_register_driver(&dac124s085_driver);
> +}
> +
> +static void __exit dac124s085_leds_exit(void)
> +{
> + spi_unregister_driver(&dac124s085_driver);
> +}
> +
> +module_init(dac124s085_leds_init);
> +module_exit(dac124s085_leds_exit);
> +
> +MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
> +MODULE_DESCRIPTION("DAC124S085 LED driver");
> +MODULE_LICENSE("GPL v2");

--
Ben ([email protected], http://www.fluff.org/)

'a smiley only costs 4 bytes'

2008-12-18 12:51:24

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH v2] leds: add a dac124s085 SPI LED driver

A simple LED driver using the DAC124S085 DAC from NatSemi

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---

Changes since v1: implemented corrections by Ben Dooks: macro instead of a
magic constant, endianness conversion, ARRAY_SIZE instead of a hard-coded
constant.

Still needs a previous patch making maximum brightness configurable:

http://marc.info/?l=linux-kernel&m=122771446123369&w=2

drivers/leds/Kconfig | 7 ++
drivers/leds/Makefile | 3 +
drivers/leds/leds-dac124s085.c | 147 ++++++++++++++++++++++++++++++++++++++++
3 files changed, 157 insertions(+), 0 deletions(-)
create mode 100644 drivers/leds/leds-dac124s085.c

diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index a4a1ae2..a28635f 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -178,6 +178,13 @@ config LEDS_DA903X
This option enables support for on-chip LED drivers found
on Dialog Semiconductor DA9030/DA9034 PMICs.

+config LEDS_DAC124S085
+ tristate "LED Support for DAC124S085 SPI DAC"
+ depends on LEDS_CLASS && SPI
+ help
+ This option enables support for DAC124S085 SPI DAC from NatSemi,
+ which can be used to control up to four LEDs.
+
comment "LED Triggers"

config LEDS_TRIGGERS
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index bc247cb..8780152 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -26,6 +26,9 @@ obj-$(CONFIG_LEDS_DA903X) += leds-da903x.o
obj-$(CONFIG_LEDS_HP_DISK) += leds-hp-disk.o
obj-$(CONFIG_LEDS_WM8350) += leds-wm8350.o

+# LED SPI Drivers
+obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
+
# LED Triggers
obj-$(CONFIG_LEDS_TRIGGER_TIMER) += ledtrig-timer.o
obj-$(CONFIG_LEDS_TRIGGER_IDE_DISK) += ledtrig-ide-disk.o
diff --git a/drivers/leds/leds-dac124s085.c b/drivers/leds/leds-dac124s085.c
new file mode 100644
index 0000000..9bd25c1
--- /dev/null
+++ b/drivers/leds/leds-dac124s085.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * This file is subject to the terms and conditions of version 2 of
+ * the GNU General Public License. See the file COPYING in the main
+ * directory of this archive for more details.
+ *
+ * LED driver for the DAC124S085 SPI DAC
+ */
+
+#include <linux/leds.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/spi/spi.h>
+
+struct dac124s085_led {
+ struct led_classdev ldev;
+ struct spi_device *spi;
+ int id;
+ int brightness;
+ char name[sizeof("dac124s085-3")];
+
+ struct mutex mutex;
+ struct work_struct work;
+ spinlock_t lock;
+};
+
+struct dac124s085 {
+ struct dac124s085_led leds[4];
+};
+
+#define REG_WRITE (0 << 12)
+#define REG_WRITE_UPDATE (1 << 12)
+#define ALL_WRITE_UPDATE (2 << 12)
+#define POWER_DOWN_OUTPUT (3 << 12)
+
+static void dac124s085_led_work(struct work_struct *work)
+{
+ struct dac124s085_led *led = container_of(work, struct dac124s085_led,
+ work);
+ u16 word;
+
+ mutex_lock(&led->mutex);
+ word = cpu_to_le16(((led->id) << 14) | REG_WRITE_UPDATE |
+ (led->brightness & 0xfff));
+ spi_write(led->spi, (const u8 *)&word, sizeof(word));
+ mutex_unlock(&led->mutex);
+}
+
+static void dac124s085_set_brightness(struct led_classdev *ldev,
+ enum led_brightness brightness)
+{
+ struct dac124s085_led *led = container_of(ldev, struct dac124s085_led,
+ ldev);
+
+ spin_lock(&led->lock);
+ led->brightness = brightness;
+ schedule_work(&led->work);
+ spin_unlock(&led->lock);
+}
+
+static int dac124s085_probe(struct spi_device *spi)
+{
+ struct dac124s085 *dac;
+ struct dac124s085_led *led;
+ int i, ret;
+
+ dac = kzalloc(sizeof(*dac), GFP_KERNEL);
+ if (!dac)
+ return -ENOMEM;
+
+ spi->bits_per_word = 16;
+
+ for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
+ led = dac->leds + i;
+ led->id = i;
+ led->brightness = LED_OFF;
+ led->spi = spi;
+ snprintf(led->name, sizeof(led->name), "dac124s085-%d", i);
+ spin_lock_init(&led->lock);
+ INIT_WORK(&led->work, dac124s085_led_work);
+ mutex_init(&led->mutex);
+ led->ldev.name = led->name;
+ led->ldev.brightness = LED_OFF;
+ led->ldev.max_brightness = 0xfff;
+ led->ldev.brightness_set = dac124s085_set_brightness;
+ ret = led_classdev_register(&spi->dev, &led->ldev);
+ if (ret < 0)
+ goto eledcr;
+ }
+
+ spi_set_drvdata(spi, dac);
+
+ return 0;
+
+eledcr:
+ while (i--)
+ led_classdev_unregister(&dac->leds[i].ldev);
+
+ spi_set_drvdata(spi, NULL);
+ kfree(dac);
+ return ret;
+}
+
+static int dac124s085_remove(struct spi_device *spi)
+{
+ struct dac124s085 *dac = spi_get_drvdata(spi);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(dac->leds); i++) {
+ led_classdev_unregister(&dac->leds[i].ldev);
+ cancel_work_sync(&dac->leds[i].work);
+ }
+
+ spi_set_drvdata(spi, NULL);
+ kfree(dac);
+
+ return 0;
+}
+
+static struct spi_driver dac124s085_driver = {
+ .probe = dac124s085_probe,
+ .remove = dac124s085_remove,
+ .driver = {
+ .name = "dac124s085",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init dac124s085_leds_init(void)
+{
+ return spi_register_driver(&dac124s085_driver);
+}
+
+static void __exit dac124s085_leds_exit(void)
+{
+ spi_unregister_driver(&dac124s085_driver);
+}
+
+module_init(dac124s085_leds_init);
+module_exit(dac124s085_leds_exit);
+
+MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
+MODULE_DESCRIPTION("DAC124S085 LED driver");
+MODULE_LICENSE("GPL v2");
--
1.5.4