Skip to contentSkip to conversation

leds: Added driver for the NXP PCA9685 I2C chip

PATCH Superseded Maximilian Güntner 4 participants 11 messages Oct 14, 2013 – Oct 17, 2013 1 message not in the archive LED

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 leds: Added driver for the NXP PCA9685 I2C chip

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---
 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 219 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  34 +++++
 4 files changed, 264 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..98268b3 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
 	  LED driver chip accessed via the I2C bus. Supported
 	  devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+	tristate "LED support for PCA9685 I2C chip"
+	depends on LEDS_CLASS
+	depends on I2C
+	help
+	  This option enables support for LEDs connected to the PCA9685
+	  LED driver chips accessed via the I2C bus.
+	  The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+	  16 individual channels.
+
 config LEDS_WM831X_STATUS
 	tristate "LED support for status LEDs on WM831x PMICs"
 	depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)		+= leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)		+= leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)		+= leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +219 -0

219 lines not on this page · raw message

include/linux/platform_data/leds-pca9685.h new file +34 -0
new file mode 100644
index 0000000..cce28b8
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+	PCA9685_OPEN_DRAIN,
+	PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+	PCA9685_NOT_INVERTED,
+	PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+	struct led_platform_data leds;
+	enum pca9685_outdrv outdrv;
+	enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
Signature · 1 line
1.8.4
#1 lore raw
Quoted 4 lines · Maximilian Güntner

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

I'm OK for this patch basically, just a little picky coding style issue as below.

Thanks,
-Bryan

Quoted 133 lines
---
 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 219 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  34 +++++
 4 files changed, 264 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..98268b3 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
          LED driver chip accessed via the I2C bus. Supported
          devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+       tristate "LED support for PCA9685 I2C chip"
+       depends on LEDS_CLASS
+       depends on I2C
+       help
+         This option enables support for LEDs connected to the PCA9685
+         LED driver chips accessed via the I2C bus.
+         The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+         16 individual channels.
+
 config LEDS_WM831X_STATUS
        tristate "LED support for status LEDs on WM831x PMICs"
        depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)              += leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)                 += leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)             += leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)             += leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)             += leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)              += leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)              += leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)       += leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +84 -0
new file mode 100644
index 0000000..a2078bf
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.c driver by
+ * Peter Meerwald <[email protected]>
+ *
+ * Driver for the NXP PCA9685 12-Bit I2C chip.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#include <linux/platform_data/leds-pca9685.h>
+
+/* Register Addresses */
+#define PCA9685_MODE1 0x00
+#define PCA9685_MODE2 0x01
+#define PCA9685_LED0_ON_L 0x06
+#define PCA9685_ALL_LED_ON_L 0xFA
+
+/* MODE1 Register */
+#define PCA9685_ALLCALL 0x00
+#define PCA9685_SLEEP   0x04
+#define PCA9685_AI      0x05
+
+/* MODE2 Register */
+#define PCA9685_INVRT   0x04
+#define PCA9685_OUTDRV  0x02
+
+static const struct i2c_device_id pca9685_id[] = {
+       { "pca9685", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, pca9685_id);
+
+struct pca9685_led {
+       struct i2c_client *client;
+       struct work_struct work;
+       u16 brightness;
+       struct led_classdev led_cdev;
+       int led_num; /* 0-15 */
+       char name[32];
+};
+
+static void pca9685_write_msg(struct i2c_client *client, u8 *buf, u8 len)
+{
+       struct i2c_msg msg = {
+               .addr = client->addr,
+               .flags = 0x00,
+               .len = len,
+               .buf = buf
+       };
+       i2c_transfer(client->adapter, &msg, 1);
+}
+
+static void pca9685_all_off(struct i2c_client *client)
+{
+       u8 i2c_buffer[5] = {PCA9685_ALL_LED_ON_L, 0x00, 0x00, 0x00, 0x10};
+       pca9685_write_msg(client, i2c_buffer, 5);
+}
+
+static void pca9685_led_work(struct work_struct *work)
+{
+       struct pca9685_led *pca9685;
+       u16 brightness;
+       u8 i2c_buffer[5];
+       pca9685 = container_of(work, struct pca9685_led, work);
+       /*
+        * 4095 is the maximum brightness, so we set the ON time to 0x1000
+        * which disables the PWM generator for that LED
+        */
+       if (pca9685->brightness == 4095)

drivers/leds/leds-pca9685.c line 94

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+               brightness = 4096;
+       else
+               brightness = pca9685->brightness;
+
+       i2c_buffer[0] = PCA9685_LED0_ON_L + 4 * pca9685->led_num;
+
+       if (brightness == 4096)
+               *((u16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000);
+       else
+               *((u16 *)(i2c_buffer+1)) = 0x0000;

One empty line here probably is better.

Quoted 20 lines
drivers/leds/leds-pca9685.c new file +20 -0
+       if (brightness == 0)
+               *((u16 *)(i2c_buffer+3)) = cpu_to_le16(0x1000);
+       else if (brightness == 4096)
+               *((u16 *)(i2c_buffer+3)) = 0x0000;
+       else
+               *((u16 *)(i2c_buffer+3)) = cpu_to_le16(brightness);
+
+       pca9685_write_msg(pca9685->client, i2c_buffer, 5);
+}
+
+static void pca9685_led_set(struct led_classdev *led_cdev,
+               enum led_brightness value)
+{
+       struct pca9685_led *pca9685;
+       pca9685 = container_of(led_cdev, struct pca9685_led, led_cdev);
+       pca9685->brightness = value;
+
+       schedule_work(&pca9685->work);
+}
+

drivers/leds/leds-pca9685.c line 124

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+static int pca9685_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       struct pca9685_led *pca9685;
+       struct pca9685_platform_data *pdata;
+       int err;
+       u8 i;
+
+       pdata = dev_get_platdata(&client->dev);
+

No need empty line here.

drivers/leds/leds-pca9685.c line 133

Quoted 9 lines
drivers/leds/leds-pca9685.c new file +9 -0
+       if (pdata) {
+               if (pdata->leds.num_leds < 1 || pdata->leds.num_leds > 15) {
+                       dev_err(&client->dev, "board info must claim 1-16 LEDs");
+                       return -EINVAL;
+               }
+       }
+
+       pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685), GFP_KERNEL);
+

No need empty line here.

Quoted 28 lines
drivers/leds/leds-pca9685.c new file +28 -0
+       if (!pca9685)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, pca9685);
+       pca9685_all_off(client);
+
+       for (i = 0; i < 16; i++) {
+               pca9685[i].client = client;
+               pca9685[i].led_num = i;
+               pca9685[i].name[0] = '\0';
+               if (pdata && i < pdata->leds.num_leds) {
+                       if (pdata->leds.leds[i].name)
+                               strncpy(pca9685[i].name,
+                                       pdata->leds.leds[i].name,
+                                       sizeof(pca9685[i].name)-1);
+                       if (pdata->leds.leds[i].default_trigger)
+                               pca9685[i].led_cdev.default_trigger =
+                                       pdata->leds.leds[i].default_trigger;
+               }
+               if (strlen(pca9685[i].name) == 0) {
+                       /*
+                        * Write adapter and address to the name as well.
+                        * Otherwise multiple chips attached to one host would
+                        * not work.
+                        */
+                       snprintf(pca9685[i].name, sizeof(pca9685[i].name),
+                                       "pca9685:%d:x%.2x:%d",
+                                       client->adapter->nr, client->addr, i);

drivers/leds/leds-pca9685.c line 171

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+               }
+               pca9685[i].led_cdev.name = pca9685[i].name;
+               pca9685[i].led_cdev.max_brightness = 0xfff;
+               pca9685[i].led_cdev.brightness_set = pca9685_led_set;
+
+               INIT_WORK(&pca9685[i].work, pca9685_led_work);
+               err = led_classdev_register(&client->dev, &pca9685[i].led_cdev);
+               if (err < 0)
+                       goto exit;
+       }

You can add one empty line here.

Quoted 89 lines
drivers/leds/leds-pca9685.c new file +48 -0
+       if (pdata)
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       pdata->outdrv << PCA9685_OUTDRV |
+                       pdata->inverted << PCA9685_INVRT);
+       else
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       PCA9685_TOTEM_POLE << PCA9685_OUTDRV);
+       /* Enable Auto-Increment, enable oscillator, ALLCALL/SUBADDR disabled */
+       i2c_smbus_write_byte_data(client, PCA9685_MODE1, 1 << PCA9685_AI);
+
+       return 0;
+
+exit:
+       while (i--) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       return err;
+}
+
+static int pca9685_remove(struct i2c_client *client)
+{
+       struct pca9685_led *pca9685 = i2c_get_clientdata(client);
+       u8 i;
+
+       for (i = 0; i < 16; i++) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       pca9685_all_off(client);
+       return 0;
+}
+
+static struct i2c_driver pca9685_driver = {
+       .driver = {
+               .name = "leds-pca9685",
+               .owner = THIS_MODULE,
+       },
+       .probe = pca9685_probe,
+       .remove = pca9685_remove,
+       .id_table = pca9685_id,
+};
+
+module_i2c_driver(pca9685_driver);
+
+MODULE_AUTHOR("Maximilian Güntner <[email protected]>");
+MODULE_DESCRIPTION("PCA9685 LED Driver");
+MODULE_LICENSE("GPL v2");
include/linux/platform_data/leds-pca9685.h new file +34 -1
new file mode 100644
index 0000000..cce28b8
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+       PCA9685_OPEN_DRAIN,
+       PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+       PCA9685_NOT_INVERTED,
+       PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+       struct led_platform_data leds;
+       enum pca9685_outdrv outdrv;
+       enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
--
Quoted 2 lines

1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#8 lore raw
In reply to Maximilian Güntner
Quoted 4 lines

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

some nitpicking inline

Quoted 15 lines
---
 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 219 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  34 +++++
 4 files changed, 264 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +0 -0
index 875bbe4..98268b3 100644

drivers/leds/Kconfig config LEDS_PCA963X · line 309

Quoted 10 lines
drivers/leds/Kconfig +7 -0
@@ -300,6 +300,16 @@ config LEDS_PCA963X
 	  LED driver chip accessed via the I2C bus. Supported
 	  devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+	tristate "LED support for PCA9685 I2C chip"
+	depends on LEDS_CLASS
+	depends on I2C
+	help
+	  This option enables support for LEDs connected to the PCA9685
+	  LED driver chips accessed via the I2C bus.

chip

Quoted 25 lines
drivers/leds/Kconfig +3 -0
+	  The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+	  16 individual channels.
+
 config LEDS_WM831X_STATUS
 	tristate "LED support for status LEDs on WM831x PMICs"
 	depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)		+= leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)		+= leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)		+= leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +1 -0
new file mode 100644
index 0000000..a2078bf
@@ -0,0 +1,219 @@
+/*

drivers/leds/leds-pca9685.c line 11

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.c driver by
+ * Peter Meerwald <[email protected]>
+ *
+ * Driver for the NXP PCA9685 12-Bit I2C chip.

this could be more descriptive: 12-bit PWM LED driver chip

Quoted 64 lines
drivers/leds/leds-pca9685.c new file +64 -0
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#include <linux/platform_data/leds-pca9685.h>
+
+/* Register Addresses */
+#define PCA9685_MODE1 0x00
+#define PCA9685_MODE2 0x01
+#define PCA9685_LED0_ON_L 0x06
+#define PCA9685_ALL_LED_ON_L 0xFA
+
+/* MODE1 Register */
+#define PCA9685_ALLCALL 0x00
+#define PCA9685_SLEEP   0x04
+#define PCA9685_AI      0x05
+
+/* MODE2 Register */
+#define PCA9685_INVRT   0x04
+#define PCA9685_OUTDRV  0x02
+
+static const struct i2c_device_id pca9685_id[] = {
+	{ "pca9685", 0 },
+	{ }
+};
+MODULE_DEVICE_TABLE(i2c, pca9685_id);
+
+struct pca9685_led {
+	struct i2c_client *client;
+	struct work_struct work;
+	u16 brightness;
+	struct led_classdev led_cdev;
+	int led_num; /* 0-15 */
+	char name[32];
+};
+
+static void pca9685_write_msg(struct i2c_client *client, u8 *buf, u8 len)
+{
+	struct i2c_msg msg = {
+		.addr = client->addr,
+		.flags = 0x00,
+		.len = len,
+		.buf = buf
+	};
+	i2c_transfer(client->adapter, &msg, 1);
+}
+
+static void pca9685_all_off(struct i2c_client *client)
+{
+	u8 i2c_buffer[5] = {PCA9685_ALL_LED_ON_L, 0x00, 0x00, 0x00, 0x10};
+	pca9685_write_msg(client, i2c_buffer, 5);
+}
+
+static void pca9685_led_work(struct work_struct *work)
+{

drivers/leds/leds-pca9685.c line 85

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+	struct pca9685_led *pca9685;
+	u16 brightness;
+	u8 i2c_buffer[5];
+	pca9685 = container_of(work, struct pca9685_led, work);
+	/*
+	 * 4095 is the maximum brightness, so we set the ON time to 0x1000
+	 * which disables the PWM generator for that LED
+	 */
+	if (pca9685->brightness == 4095)
+		brightness = 4096;

why not check for 4095 instead of 4096 below and save this if statement?

Quoted 85 lines
drivers/leds/leds-pca9685.c new file +85 -0
+	else
+		brightness = pca9685->brightness;
+
+	i2c_buffer[0] = PCA9685_LED0_ON_L + 4 * pca9685->led_num;
+
+	if (brightness == 4096)
+		*((u16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000);
+	else
+		*((u16 *)(i2c_buffer+1)) = 0x0000;
+	if (brightness == 0)
+		*((u16 *)(i2c_buffer+3)) = cpu_to_le16(0x1000);
+	else if (brightness == 4096)
+		*((u16 *)(i2c_buffer+3)) = 0x0000;
+	else
+		*((u16 *)(i2c_buffer+3)) = cpu_to_le16(brightness);
+
+	pca9685_write_msg(pca9685->client, i2c_buffer, 5);
+}
+
+static void pca9685_led_set(struct led_classdev *led_cdev,
+		enum led_brightness value)
+{
+	struct pca9685_led *pca9685;
+	pca9685 = container_of(led_cdev, struct pca9685_led, led_cdev);
+	pca9685->brightness = value;
+
+	schedule_work(&pca9685->work);
+}
+
+static int pca9685_probe(struct i2c_client *client,
+		const struct i2c_device_id *id)
+{
+	struct pca9685_led *pca9685;
+	struct pca9685_platform_data *pdata;
+	int err;
+	u8 i;
+
+	pdata = dev_get_platdata(&client->dev);
+
+	if (pdata) {
+		if (pdata->leds.num_leds < 1 || pdata->leds.num_leds > 15) {
+			dev_err(&client->dev, "board info must claim 1-16 LEDs");
+			return -EINVAL;
+		}
+	}
+
+	pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685), GFP_KERNEL);
+
+	if (!pca9685)
+		return -ENOMEM;
+
+	i2c_set_clientdata(client, pca9685);
+	pca9685_all_off(client);
+
+	for (i = 0; i < 16; i++) {
+		pca9685[i].client = client;
+		pca9685[i].led_num = i;
+		pca9685[i].name[0] = '\0';
+		if (pdata && i < pdata->leds.num_leds) {
+			if (pdata->leds.leds[i].name)
+				strncpy(pca9685[i].name,
+					pdata->leds.leds[i].name,
+					sizeof(pca9685[i].name)-1);
+			if (pdata->leds.leds[i].default_trigger)
+				pca9685[i].led_cdev.default_trigger =
+					pdata->leds.leds[i].default_trigger;
+		}
+		if (strlen(pca9685[i].name) == 0) {
+			/*
+			 * Write adapter and address to the name as well.
+			 * Otherwise multiple chips attached to one host would
+			 * not work.
+			 */
+			snprintf(pca9685[i].name, sizeof(pca9685[i].name),
+					"pca9685:%d:x%.2x:%d",
+					client->adapter->nr, client->addr, i);
+		}
+		pca9685[i].led_cdev.name = pca9685[i].name;
+		pca9685[i].led_cdev.max_brightness = 0xfff;
+		pca9685[i].led_cdev.brightness_set = pca9685_led_set;
+
+		INIT_WORK(&pca9685[i].work, pca9685_led_work);
+		err = led_classdev_register(&client->dev, &pca9685[i].led_cdev);
+		if (err < 0)
+			goto exit;

drivers/leds/leds-pca9685.c line 180

Quoted 10 lines
drivers/leds/leds-pca9685.c new file +10 -0
+	}
+	if (pdata)
+		i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+			pdata->outdrv << PCA9685_OUTDRV |
+			pdata->inverted << PCA9685_INVRT);
+	else
+		i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+			PCA9685_TOTEM_POLE << PCA9685_OUTDRV);
+	/* Enable Auto-Increment, enable oscillator, ALLCALL/SUBADDR disabled */
+	i2c_smbus_write_byte_data(client, PCA9685_MODE1, 1 << PCA9685_AI);

maybe BIT(PCA9685_AI)

Quoted 80 lines
drivers/leds/leds-pca9685.c new file +39 -0
+
+	return 0;
+
+exit:
+	while (i--) {
+		led_classdev_unregister(&pca9685[i].led_cdev);
+		cancel_work_sync(&pca9685[i].work);
+	}
+	return err;
+}
+
+static int pca9685_remove(struct i2c_client *client)
+{
+	struct pca9685_led *pca9685 = i2c_get_clientdata(client);
+	u8 i;
+
+	for (i = 0; i < 16; i++) {
+		led_classdev_unregister(&pca9685[i].led_cdev);
+		cancel_work_sync(&pca9685[i].work);
+	}
+	pca9685_all_off(client);
+	return 0;
+}
+
+static struct i2c_driver pca9685_driver = {
+	.driver = {
+		.name = "leds-pca9685",
+		.owner = THIS_MODULE,
+	},
+	.probe = pca9685_probe,
+	.remove = pca9685_remove,
+	.id_table = pca9685_id,
+};
+
+module_i2c_driver(pca9685_driver);
+
+MODULE_AUTHOR("Maximilian Güntner <[email protected]>");
+MODULE_DESCRIPTION("PCA9685 LED Driver");
+MODULE_LICENSE("GPL v2");
include/linux/platform_data/leds-pca9685.h new file +34 -0
new file mode 100644
index 0000000..cce28b8
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+	PCA9685_OPEN_DRAIN,
+	PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+	PCA9685_NOT_INVERTED,
+	PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+	struct led_platform_data leds;
+	enum pca9685_outdrv outdrv;
+	enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
Signature · 2 lines
Peter Meerwald
+43-664-2444418 (mobile)
#9 lore raw
Quoted 5 lines · On Tuesday 15 October 2013 00:38:13 Peter Meerwald wrote:
Quoted 3 lines

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

some nitpicking inline

Thank you for your comments.

Quoted 1 line

chip

fixed.

Quoted 3 lines
Quoted 2 lines

+ * Driver for the NXP PCA9685 12-Bit I2C chip.

this could be more descriptive: 12-bit PWM LED driver chip

fixed.

Quoted 8 lines

drivers/leds/leds-pca9685.c line 85

Quoted 6 lines
drivers/leds/leds-pca9685.c new file +6 -0
+	/*
+	 * 4095 is the maximum brightness, so we set the ON time to 0x1000
+	 * which disables the PWM generator for that LED
+	 */
+	if (pca9685->brightness == 4095)
+		brightness = 4096;

why not check for 4095 instead of 4096 below and save this if statement?

removed.

I left it there in order to improve the code readability, which should be equally good when we just remove the statement and check for 4095 below.

Quoted 3 lines

drivers/leds/leds-pca9685.c line 180

Quoted 1 line
drivers/leds/leds-pca9685.c new file +1 -0
+	i2c_smbus_write_byte_data(client, PCA9685_MODE1, 1 << PCA9685_AI);

maybe BIT(PCA9685_AI)

replaced.

#2 lore raw
In reply to Bryan Wu
Quoted 7 lines · On Monday 14 October 2013 15:08:15 Bryan Wu wrote:

On Mon, Oct 14, 2013 at 10:31 AM, Maximilian Güntner

Quoted 3 lines · <[email protected]> wrote:

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

Quoted 8 lines

I'm OK for this patch basically, just a little picky coding style issue as below.

drivers/leds/leds-pca9685.c line 94

Quoted 4 lines
drivers/leds/leds-pca9685.c new file +4 -0
+       if (brightness == 4096)
+               *((u16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000);
+       else
+               *((u16 *)(i2c_buffer+1)) = 0x0000;

One empty line here probably is better.

fixed.

Quoted 5 lines

drivers/leds/leds-pca9685.c line 124

Quoted 3 lines
drivers/leds/leds-pca9685.c new file +3 -0
+
+       pdata = dev_get_platdata(&client->dev);
+

No need empty line here.

fixed.

Quoted 4 lines
Quoted 2 lines

+ pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685),
GFP_KERNEL); +

No need empty line here.

fixed.

Quoted 5 lines

Review

Quoted 3 lines

&pca9685[i].led_cdev); + if (err < 0)

(quoted diff) +2 -0
+                       goto exit;
+       }

You can add one empty line here.

fixed

Thanks for the quick review. A v2 of this patch is on its way which addresses your and Peter Meerwald's comments.

Maximilian
--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#10 lore raw
In reply to Maximilian Güntner
Quoted 6 lines · Maximilian Guntner

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

I added some nit-picking comments. :-)

Quoted 9 lines
---
 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 219 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  34 +++++
 4 files changed, 264 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h

[.....]

drivers/leds/leds-pca9685.c line 25

Quoted 12 lines
drivers/leds/leds-pca9685.c new file +12 -0
+
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/delay.h>
+#include <linux/ctype.h>
+#include <linux/leds.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/slab.h>
+
+#include <linux/platform_data/leds-pca9685.h>

Please, sort these header inclusions alphabetically for better readability.

[.....]

include/linux/platform_data/leds-pca9685.h line 15

Quoted 2 lines
include/linux/platform_data/leds-pca9685.h new file +2 -0
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H

You can add one empty line here.

Review

Quoted 2 lines
(quoted diff) +2 -0
+#include <linux/leds.h>
+

Best regards,
Jingoo Han

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#3 lore raw

PATCH v2 1/1 leds: Added driver for the NXP PCA9685 I2C chip

In reply to Bryan Wu

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---

v2:
addresses Bryan Wu's comments:
* removed/added some empty lines
addresses Peter Meerwald's comments:
* fixed typos
* replaced/moved one if statement
* replaced (1 << PCA9685_AI) with BIT(PCA9685_AI)
addresses Jingoo Han's comments:
* added an empty line
* sorted the header inclusions alphabetically

 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 213 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  35 +++++
 4 files changed, 259 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..72156c1 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
 	  LED driver chip accessed via the I2C bus. Supported
 	  devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+	tristate "LED support for PCA9685 I2C chip"
+	depends on LEDS_CLASS
+	depends on I2C
+	help
+	  This option enables support for LEDs connected to the PCA9685
+	  LED driver chip accessed via the I2C bus.
+	  The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+	  16 individual channels.
+
 config LEDS_WM831X_STATUS
 	tristate "LED support for status LEDs on WM831x PMICs"
 	depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)		+= leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)		+= leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)		+= leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +213 -0

213 lines not on this page · raw message

include/linux/platform_data/leds-pca9685.h new file +35 -0
new file mode 100644
index 0000000..778e9e4
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+	PCA9685_OPEN_DRAIN,
+	PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+	PCA9685_NOT_INVERTED,
+	PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+	struct led_platform_data leds;
+	enum pca9685_outdrv outdrv;
+	enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
Signature · 6 lines
1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#4 lore raw
Quoted 7 lines · Maximilian Güntner

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---

Quoted 11 lines

v2:
addresses Bryan Wu's comments:
* removed/added some empty lines
addresses Peter Meerwald's comments:
* fixed typos
* replaced/moved one if statement
* replaced (1 << PCA9685_AI) with BIT(PCA9685_AI)
addresses Jingoo Han's comments:
* added an empty line
* sorted the header inclusions alphabetically

Thanks, I merged it into my tree.
-Bryan

Quoted 304 lines
 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 213 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  35 +++++
 4 files changed, 259 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..72156c1 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
          LED driver chip accessed via the I2C bus. Supported
          devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+       tristate "LED support for PCA9685 I2C chip"
+       depends on LEDS_CLASS
+       depends on I2C
+       help
+         This option enables support for LEDs connected to the PCA9685
+         LED driver chip accessed via the I2C bus.
+         The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+         16 individual channels.
+
 config LEDS_WM831X_STATUS
        tristate "LED support for status LEDs on WM831x PMICs"
        depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)              += leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)                 += leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)             += leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)             += leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)             += leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)              += leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)              += leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)       += leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +213 -0
new file mode 100644
index 0000000..f87719e
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.c driver by
+ * Peter Meerwald <[email protected]>
+ *
+ * Driver for the NXP PCA9685 12-Bit PWM LED driver chip.
+ *
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+
+#include <linux/platform_data/leds-pca9685.h>
+
+/* Register Addresses */
+#define PCA9685_MODE1 0x00
+#define PCA9685_MODE2 0x01
+#define PCA9685_LED0_ON_L 0x06
+#define PCA9685_ALL_LED_ON_L 0xFA
+
+/* MODE1 Register */
+#define PCA9685_ALLCALL 0x00
+#define PCA9685_SLEEP   0x04
+#define PCA9685_AI      0x05
+
+/* MODE2 Register */
+#define PCA9685_INVRT   0x04
+#define PCA9685_OUTDRV  0x02
+
+static const struct i2c_device_id pca9685_id[] = {
+       { "pca9685", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, pca9685_id);
+
+struct pca9685_led {
+       struct i2c_client *client;
+       struct work_struct work;
+       u16 brightness;
+       struct led_classdev led_cdev;
+       int led_num; /* 0-15 */
+       char name[32];
+};
+
+static void pca9685_write_msg(struct i2c_client *client, u8 *buf, u8 len)
+{
+       struct i2c_msg msg = {
+               .addr = client->addr,
+               .flags = 0x00,
+               .len = len,
+               .buf = buf
+       };
+       i2c_transfer(client->adapter, &msg, 1);
+}
+
+static void pca9685_all_off(struct i2c_client *client)
+{
+       u8 i2c_buffer[5] = {PCA9685_ALL_LED_ON_L, 0x00, 0x00, 0x00, 0x10};
+       pca9685_write_msg(client, i2c_buffer, 5);
+}
+
+static void pca9685_led_work(struct work_struct *work)
+{
+       struct pca9685_led *pca9685;
+       u8 i2c_buffer[5];
+
+       pca9685 = container_of(work, struct pca9685_led, work);
+       i2c_buffer[0] = PCA9685_LED0_ON_L + 4 * pca9685->led_num;
+       /*
+        * 4095 is the maximum brightness, so we set the ON time to 0x1000
+        * which disables the PWM generator for that LED
+        */
+       if (pca9685->brightness == 4095)
+               *((u16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000);
+       else
+               *((u16 *)(i2c_buffer+1)) = 0x0000;
+
+       if (pca9685->brightness == 0)
+               *((u16 *)(i2c_buffer+3)) = cpu_to_le16(0x1000);
+       else if (pca9685->brightness == 4095)
+               *((u16 *)(i2c_buffer+3)) = 0x0000;
+       else
+               *((u16 *)(i2c_buffer+3)) = cpu_to_le16(pca9685->brightness);
+
+       pca9685_write_msg(pca9685->client, i2c_buffer, 5);
+}
+
+static void pca9685_led_set(struct led_classdev *led_cdev,
+               enum led_brightness value)
+{
+       struct pca9685_led *pca9685;
+       pca9685 = container_of(led_cdev, struct pca9685_led, led_cdev);
+       pca9685->brightness = value;
+
+       schedule_work(&pca9685->work);
+}
+
+static int pca9685_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       struct pca9685_led *pca9685;
+       struct pca9685_platform_data *pdata;
+       int err;
+       u8 i;
+
+       pdata = dev_get_platdata(&client->dev);
+       if (pdata) {
+               if (pdata->leds.num_leds < 1 || pdata->leds.num_leds > 15) {
+                       dev_err(&client->dev, "board info must claim 1-16 LEDs");
+                       return -EINVAL;
+               }
+       }
+
+       pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685), GFP_KERNEL);
+       if (!pca9685)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, pca9685);
+       pca9685_all_off(client);
+
+       for (i = 0; i < 16; i++) {
+               pca9685[i].client = client;
+               pca9685[i].led_num = i;
+               pca9685[i].name[0] = '\0';
+               if (pdata && i < pdata->leds.num_leds) {
+                       if (pdata->leds.leds[i].name)
+                               strncpy(pca9685[i].name,
+                                       pdata->leds.leds[i].name,
+                                       sizeof(pca9685[i].name)-1);
+                       if (pdata->leds.leds[i].default_trigger)
+                               pca9685[i].led_cdev.default_trigger =
+                                       pdata->leds.leds[i].default_trigger;
+               }
+               if (strlen(pca9685[i].name) == 0) {
+                       /*
+                        * Write adapter and address to the name as well.
+                        * Otherwise multiple chips attached to one host would
+                        * not work.
+                        */
+                       snprintf(pca9685[i].name, sizeof(pca9685[i].name),
+                                       "pca9685:%d:x%.2x:%d",
+                                       client->adapter->nr, client->addr, i);
+               }
+               pca9685[i].led_cdev.name = pca9685[i].name;
+               pca9685[i].led_cdev.max_brightness = 0xfff;
+               pca9685[i].led_cdev.brightness_set = pca9685_led_set;
+
+               INIT_WORK(&pca9685[i].work, pca9685_led_work);
+               err = led_classdev_register(&client->dev, &pca9685[i].led_cdev);
+               if (err < 0)
+                       goto exit;
+       }
+
+       if (pdata)
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       pdata->outdrv << PCA9685_OUTDRV |
+                       pdata->inverted << PCA9685_INVRT);
+       else
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       PCA9685_TOTEM_POLE << PCA9685_OUTDRV);
+       /* Enable Auto-Increment, enable oscillator, ALLCALL/SUBADDR disabled */
+       i2c_smbus_write_byte_data(client, PCA9685_MODE1, BIT(PCA9685_AI));
+
+       return 0;
+
+exit:
+       while (i--) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       return err;
+}
+
+static int pca9685_remove(struct i2c_client *client)
+{
+       struct pca9685_led *pca9685 = i2c_get_clientdata(client);
+       u8 i;
+
+       for (i = 0; i < 16; i++) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       pca9685_all_off(client);
+       return 0;
+}
+
+static struct i2c_driver pca9685_driver = {
+       .driver = {
+               .name = "leds-pca9685",
+               .owner = THIS_MODULE,
+       },
+       .probe = pca9685_probe,
+       .remove = pca9685_remove,
+       .id_table = pca9685_id,
+};
+
+module_i2c_driver(pca9685_driver);
+
+MODULE_AUTHOR("Maximilian Güntner <[email protected]>");
+MODULE_DESCRIPTION("PCA9685 LED Driver");
+MODULE_LICENSE("GPL v2");
include/linux/platform_data/leds-pca9685.h new file +35 -1
new file mode 100644
index 0000000..778e9e4
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+       PCA9685_OPEN_DRAIN,
+       PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+       PCA9685_NOT_INVERTED,
+       PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+       struct led_platform_data leds;
+       enum pca9685_outdrv outdrv;
+       enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
--

1.8.4

#5 lore raw

PATCH v3 1/1 leds: Added driver for the NXP PCA9685 I2C chip

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---
v3:
fixed warnings when running make C=1 CF=-D__CHECK_ENDIAN__:
* replaced u16* by __le16*

v2:
addresses bryan wu's comments:
* removed/added some empty lines
addresses peter meerwald's comments:
* fixed typos
* replaced/moved one if statement
* replaced (1 << pca9685_ai) with bit(pca9685_ai)
addresses jingoo han's comments:
* added an empty line
* sorted the header inclusions alphabetically

 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 213 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  35 +++++
 4 files changed, 259 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..72156c1 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
 	  LED driver chip accessed via the I2C bus. Supported
 	  devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+	tristate "LED support for PCA9685 I2C chip"
+	depends on LEDS_CLASS
+	depends on I2C
+	help
+	  This option enables support for LEDs connected to the PCA9685
+	  LED driver chip accessed via the I2C bus.
+	  The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+	  16 individual channels.
+
 config LEDS_WM831X_STATUS
 	tristate "LED support for status LEDs on WM831x PMICs"
 	depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)		+= leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)			+= leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)		+= leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)		+= leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)		+= leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)		+= leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)		+= leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)	+= leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +213 -0

213 lines not on this page · raw message

include/linux/platform_data/leds-pca9685.h new file +35 -0
new file mode 100644
index 0000000..778e9e4
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+	PCA9685_OPEN_DRAIN,
+	PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+	PCA9685_NOT_INVERTED,
+	PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+	struct led_platform_data leds;
+	enum pca9685_outdrv outdrv;
+	enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
Signature · 6 lines
1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
#6 lore raw
Quoted 10 lines · Maximilian Güntner

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---
v3:
fixed warnings when running make C=1 CF=-D__CHECK_ENDIAN__:
* replaced u16* by __le16*

OK, replaced the old one in my tree.

Thanks,
-Bryan

Quoted 313 lines

v2:
addresses bryan wu's comments:
* removed/added some empty lines
addresses peter meerwald's comments:
* fixed typos
* replaced/moved one if statement
* replaced (1 << pca9685_ai) with bit(pca9685_ai)
addresses jingoo han's comments:
* added an empty line
* sorted the header inclusions alphabetically

 drivers/leds/Kconfig                       |  10 ++
 drivers/leds/Makefile                      |   1 +
 drivers/leds/leds-pca9685.c                | 213 +++++++++++++++++++++++++++++
 include/linux/platform_data/leds-pca9685.h |  35 +++++
 4 files changed, 259 insertions(+)
 create mode 100644 drivers/leds/leds-pca9685.c
 create mode 100644 include/linux/platform_data/leds-pca9685.h
drivers/leds/Kconfig +10 -0
index 875bbe4..72156c1 100644
@@ -300,6 +300,16 @@ config LEDS_PCA963X
          LED driver chip accessed via the I2C bus. Supported
          devices include PCA9633 and PCA9634
 
+config LEDS_PCA9685
+       tristate "LED support for PCA9685 I2C chip"
+       depends on LEDS_CLASS
+       depends on I2C
+       help
+         This option enables support for LEDs connected to the PCA9685
+         LED driver chip accessed via the I2C bus.
+         The PCA9685 offers 12-bit PWM (4095 levels of brightness) on
+         16 individual channels.
+
 config LEDS_WM831X_STATUS
        tristate "LED support for status LEDs on WM831x PMICs"
        depends on LEDS_CLASS
drivers/leds/Makefile +1 -0
index 8979b0b..3cd76db 100644
@@ -36,6 +36,7 @@ obj-$(CONFIG_LEDS_OT200)              += leds-ot200.o
 obj-$(CONFIG_LEDS_FSG)                 += leds-fsg.o
 obj-$(CONFIG_LEDS_PCA955X)             += leds-pca955x.o
 obj-$(CONFIG_LEDS_PCA963X)             += leds-pca963x.o
+obj-$(CONFIG_LEDS_PCA9685)             += leds-pca9685.o
 obj-$(CONFIG_LEDS_DA903X)              += leds-da903x.o
 obj-$(CONFIG_LEDS_DA9052)              += leds-da9052.o
 obj-$(CONFIG_LEDS_WM831X_STATUS)       += leds-wm831x-status.o
drivers/leds/leds-pca9685.c new file +213 -0
new file mode 100644
index 0000000..6e1ef3a
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.c driver by
+ * Peter Meerwald <[email protected]>
+ *
+ * Driver for the NXP PCA9685 12-Bit PWM LED driver chip.
+ *
+ */
+
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+#include <linux/workqueue.h>
+
+#include <linux/platform_data/leds-pca9685.h>
+
+/* Register Addresses */
+#define PCA9685_MODE1 0x00
+#define PCA9685_MODE2 0x01
+#define PCA9685_LED0_ON_L 0x06
+#define PCA9685_ALL_LED_ON_L 0xFA
+
+/* MODE1 Register */
+#define PCA9685_ALLCALL 0x00
+#define PCA9685_SLEEP   0x04
+#define PCA9685_AI      0x05
+
+/* MODE2 Register */
+#define PCA9685_INVRT   0x04
+#define PCA9685_OUTDRV  0x02
+
+static const struct i2c_device_id pca9685_id[] = {
+       { "pca9685", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, pca9685_id);
+
+struct pca9685_led {
+       struct i2c_client *client;
+       struct work_struct work;
+       u16 brightness;
+       struct led_classdev led_cdev;
+       int led_num; /* 0-15 */
+       char name[32];
+};
+
+static void pca9685_write_msg(struct i2c_client *client, u8 *buf, u8 len)
+{
+       struct i2c_msg msg = {
+               .addr = client->addr,
+               .flags = 0x00,
+               .len = len,
+               .buf = buf
+       };
+       i2c_transfer(client->adapter, &msg, 1);
+}
+
+static void pca9685_all_off(struct i2c_client *client)
+{
+       u8 i2c_buffer[5] = {PCA9685_ALL_LED_ON_L, 0x00, 0x00, 0x00, 0x10};
+       pca9685_write_msg(client, i2c_buffer, 5);
+}
+
+static void pca9685_led_work(struct work_struct *work)
+{
+       struct pca9685_led *pca9685;
+       u8 i2c_buffer[5];
+
+       pca9685 = container_of(work, struct pca9685_led, work);
+       i2c_buffer[0] = PCA9685_LED0_ON_L + 4 * pca9685->led_num;
+       /*
+        * 4095 is the maximum brightness, so we set the ON time to 0x1000
+        * which disables the PWM generator for that LED
+        */
+       if (pca9685->brightness == 4095)
+               *((__le16 *)(i2c_buffer+1)) = cpu_to_le16(0x1000);
+       else
+               *((__le16 *)(i2c_buffer+1)) = 0x0000;
+
+       if (pca9685->brightness == 0)
+               *((__le16 *)(i2c_buffer+3)) = cpu_to_le16(0x1000);
+       else if (pca9685->brightness == 4095)
+               *((__le16 *)(i2c_buffer+3)) = 0x0000;
+       else
+               *((__le16 *)(i2c_buffer+3)) = cpu_to_le16(pca9685->brightness);
+
+       pca9685_write_msg(pca9685->client, i2c_buffer, 5);
+}
+
+static void pca9685_led_set(struct led_classdev *led_cdev,
+               enum led_brightness value)
+{
+       struct pca9685_led *pca9685;
+       pca9685 = container_of(led_cdev, struct pca9685_led, led_cdev);
+       pca9685->brightness = value;
+
+       schedule_work(&pca9685->work);
+}
+
+static int pca9685_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       struct pca9685_led *pca9685;
+       struct pca9685_platform_data *pdata;
+       int err;
+       u8 i;
+
+       pdata = dev_get_platdata(&client->dev);
+       if (pdata) {
+               if (pdata->leds.num_leds < 1 || pdata->leds.num_leds > 15) {
+                       dev_err(&client->dev, "board info must claim 1-16 LEDs");
+                       return -EINVAL;
+               }
+       }
+
+       pca9685 = devm_kzalloc(&client->dev, 16 * sizeof(*pca9685), GFP_KERNEL);
+       if (!pca9685)
+               return -ENOMEM;
+
+       i2c_set_clientdata(client, pca9685);
+       pca9685_all_off(client);
+
+       for (i = 0; i < 16; i++) {
+               pca9685[i].client = client;
+               pca9685[i].led_num = i;
+               pca9685[i].name[0] = '\0';
+               if (pdata && i < pdata->leds.num_leds) {
+                       if (pdata->leds.leds[i].name)
+                               strncpy(pca9685[i].name,
+                                       pdata->leds.leds[i].name,
+                                       sizeof(pca9685[i].name)-1);
+                       if (pdata->leds.leds[i].default_trigger)
+                               pca9685[i].led_cdev.default_trigger =
+                                       pdata->leds.leds[i].default_trigger;
+               }
+               if (strlen(pca9685[i].name) == 0) {
+                       /*
+                        * Write adapter and address to the name as well.
+                        * Otherwise multiple chips attached to one host would
+                        * not work.
+                        */
+                       snprintf(pca9685[i].name, sizeof(pca9685[i].name),
+                                       "pca9685:%d:x%.2x:%d",
+                                       client->adapter->nr, client->addr, i);
+               }
+               pca9685[i].led_cdev.name = pca9685[i].name;
+               pca9685[i].led_cdev.max_brightness = 0xfff;
+               pca9685[i].led_cdev.brightness_set = pca9685_led_set;
+
+               INIT_WORK(&pca9685[i].work, pca9685_led_work);
+               err = led_classdev_register(&client->dev, &pca9685[i].led_cdev);
+               if (err < 0)
+                       goto exit;
+       }
+
+       if (pdata)
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       pdata->outdrv << PCA9685_OUTDRV |
+                       pdata->inverted << PCA9685_INVRT);
+       else
+               i2c_smbus_write_byte_data(client, PCA9685_MODE2,
+                       PCA9685_TOTEM_POLE << PCA9685_OUTDRV);
+       /* Enable Auto-Increment, enable oscillator, ALLCALL/SUBADDR disabled */
+       i2c_smbus_write_byte_data(client, PCA9685_MODE1, BIT(PCA9685_AI));
+
+       return 0;
+
+exit:
+       while (i--) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       return err;
+}
+
+static int pca9685_remove(struct i2c_client *client)
+{
+       struct pca9685_led *pca9685 = i2c_get_clientdata(client);
+       u8 i;
+
+       for (i = 0; i < 16; i++) {
+               led_classdev_unregister(&pca9685[i].led_cdev);
+               cancel_work_sync(&pca9685[i].work);
+       }
+       pca9685_all_off(client);
+       return 0;
+}
+
+static struct i2c_driver pca9685_driver = {
+       .driver = {
+               .name = "leds-pca9685",
+               .owner = THIS_MODULE,
+       },
+       .probe = pca9685_probe,
+       .remove = pca9685_remove,
+       .id_table = pca9685_id,
+};
+
+module_i2c_driver(pca9685_driver);
+
+MODULE_AUTHOR("Maximilian Güntner <[email protected]>");
+MODULE_DESCRIPTION("PCA9685 LED Driver");
+MODULE_LICENSE("GPL v2");
include/linux/platform_data/leds-pca9685.h new file +35 -1
new file mode 100644
index 0000000..778e9e4
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2013 Maximilian Güntner <[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.
+ *
+ * Based on leds-pca963x.h by Peter Meerwald <[email protected]>
+ *
+ * LED driver for the NXP PCA9685 PWM chip
+ *
+ */
+
+#ifndef __LINUX_PCA9685_H
+#define __LINUX_PCA9685_H
+
+#include <linux/leds.h>
+
+enum pca9685_outdrv {
+       PCA9685_OPEN_DRAIN,
+       PCA9685_TOTEM_POLE,
+};
+
+enum pca9685_inverted {
+       PCA9685_NOT_INVERTED,
+       PCA9685_INVERTED,
+};
+
+struct pca9685_platform_data {
+       struct led_platform_data leds;
+       enum pca9685_outdrv outdrv;
+       enum pca9685_inverted inverted;
+};
+
+#endif /* __LINUX_PCA9685_H */
--
Quoted 2 lines

1.8.4

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html

#7 lore raw

Hello Bryan,

thank you for applying the patch so quickly.

Maximilian

2013/10/18 Bryan Wu <[email protected]>:

Quoted 13 lines
Quoted 10 lines · Maximilian Güntner

The NXP PCA9685 supports 16 channels/leds using a 12-bit PWM (4095 levels of brightness)
This driver supports configuration using platform_data.

---
v3:
fixed warnings when running make C=1 CF=-D__CHECK_ENDIAN__:
* replaced u16* by __le16*

Quoted 5 lines

OK, replaced the old one in my tree.

Thanks,
-Bryan

--
To unsubscribe from this list: send the line "unsubscribe linux-leds" in the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html