-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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. Signed-off-by: Maximilian Güntner <maximilian.guentner@gmail.com> Signed-off-by: Bryan Wu <cooloney@gmail.com>
- Loading branch information
Maximilian Güntner
authored and
Bryan Wu
committed
Oct 22, 2013
1 parent
1e08f72
commit bb6febd
Showing
4 changed files
with
259 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
/* | ||
* Copyright 2013 Maximilian Güntner <maximilian.guentner@gmail.com> | ||
* | ||
* This file is subject to the terms and conditions of version 2 of | ||
* the GNU General Public License. See the file COPYING in the main | ||
* directory of this archive for more details. | ||
* | ||
* Based on leds-pca963x.c driver by | ||
* Peter Meerwald <p.meerwald@bct-electronic.com> | ||
* | ||
* 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 <maximilian.guentner@gmail.com>"); | ||
MODULE_DESCRIPTION("PCA9685 LED Driver"); | ||
MODULE_LICENSE("GPL v2"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* | ||
* Copyright 2013 Maximilian Güntner <maximilian.guentner@gmail.com> | ||
* | ||
* This file is subject to the terms and conditions of version 2 of | ||
* the GNU General Public License. See the file COPYING in the main | ||
* directory of this archive for more details. | ||
* | ||
* Based on leds-pca963x.h by Peter Meerwald <p.meerwald@bct-electronic.com> | ||
* | ||
* 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 */ |