leds: aw2013_led: add aw2013 led driver

Add AWINIC RGB LED driver support. This LED controller is
connected to the host processor via I2C.

Change-Id: Ib0ba6abced6eac605bc55129208a8583d874c325
Signed-off-by: Mao Li <maol@codeaurora.org>
This commit is contained in:
Mao Li 2014-12-09 05:21:05 -05:00
parent de3f351e31
commit 613d0a748a
6 changed files with 873 additions and 0 deletions

View file

@ -0,0 +1,75 @@
Binding for RGB LEDs connected to AW2013.
AWINIC AW2013 RGB LED driver is used to provide red/green/blue
led blink or glowing to notify user for different system events,
such as missed call, new sms, low battery. AW2013 RGB LED is
connected through I2C.
Required properties:
- compatible : should be compatible = "awinic,aw2013"
- reg : i2c slave address of the device
- vdd-supply : Power supply needed to power up the device
- vcc-supply : Power source required to power up i2c bus
LED required sub-node properties:
- aw2013,name : name of the LED
- aw2013,id : id of the LED
- aw2013,max-brightness: max brightness set of the LED
- aw2013,max-current : max current set of the LED
- aw2013,rise-time-ms : the rise time when led in breathe mode
- aw2013,hold-time-ms : the hold time when led in breathe mode
- aw2013,fall-time-ms : the fall time when led in breathe mode
- aw2013,off-time-ms : the off time when led in breathe mode
The definition of each time described as
shown in figure:
/-----------\
/ | \
/| | |\
/ | | | \-----------
|hold_time_ms | |
| | |
rise_time_ms fall_time_ms |
off_time_ms
Example:
aw2013@45 {
compatible = "awinic,aw2013";
reg = <0x45>;
vdd-supply = <&pm8909_l17>;
vcc-supply = <&pm8909_l6>;
aw2013,red {
aw2013,name = "red";
aw2013,id = <0>;
aw2013,max-brightness = <255>;
aw2013,max-current = <1>;
aw2013,rise-time-ms = <2>;
aw2013,hold-time-ms = <1>;
aw2013,fall-time-ms = <2>;
aw2013,off-time-ms = <1>;
};
aw2013,green {
aw2013,name = "green";
aw2013,id = <1>;
aw2013,max-brightness = <255>;
aw2013,max-current = <1>;
aw2013,rise-time-ms = <2>;
aw2013,hold-time-ms = <1>;
aw2013,fall-time-ms = <2>;
aw2013,off-time-ms = <1>;
};
aw2013,blue {
aw2013,name = "blue";
aw2013,id = <2>;
aw2013,max-brightness = <255>;
aw2013,max-current = <1>;
aw2013,rise-time-ms = <2>;
aw2013,hold-time-ms = <1>;
aw2013,fall-time-ms = <2>;
aw2013,off-time-ms = <1>;
};
};

View file

@ -12,6 +12,7 @@ apm Applied Micro Circuits Corporation (APM)
arm ARM Ltd.
atmel Atmel Corporation
avago Avago Technologies
awinic AWINIC Technology Co.Ltd
bosch Bosch Sensortec GmbH
brcm Broadcom Corporation
capella Capella Microsystems, Inc.

View file

@ -518,6 +518,13 @@ config LEDS_BLINKM
This option enables support for the BlinkM RGB LED connected
through I2C. Say Y to enable support for the BlinkM LED.
config LEDS_AW2013
tristate "LED support for AW2013"
depends on LEDS_CLASS && I2C
help
This option enables support for the AW2013 RGB LED connected
through I2C. Say Y to enable support for the AW2013 LED.
comment "LED Triggers"
source "drivers/leds/trigger/Kconfig"

View file

@ -57,6 +57,7 @@ obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_MSM_GPIO_FLASH) += leds-msm-gpio-flash.o
obj-$(CONFIG_LEDS_AW2013) += leds-aw2013.o
# LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o

751
drivers/leds/leds-aw2013.c Normal file
View file

@ -0,0 +1,751 @@
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/regulator/consumer.h>
#include <linux/leds-aw2013.h>
#if defined(CONFIG_FB)
#include <linux/notifier.h>
#include <linux/fb.h>
#endif
/* register address */
#define AW_REG_RESET 0x00
#define AW_REG_GLOBAL_CONTROL 0x01
#define AW_REG_LED_STATUS 0x02
#define AW_REG_LED_ENABLE 0x30
#define AW_REG_LED_CONFIG_BASE 0x31
#define AW_REG_LED_BRIGHTNESS_BASE 0x34
#define AW_REG_TIMESET0_BASE 0x37
#define AW_REG_TIMESET1_BASE 0x38
/* register bits */
#define AW2013_CHIPID 0x33
#define AW_LED_MOUDLE_ENABLE_MASK 0x01
#define AW_LED_FADE_OFF_MASK 0x40
#define AW_LED_FADE_ON_MASK 0x20
#define AW_LED_BREATHE_MODE_MASK 0x10
#define AW_LED_RESET_MASK 0x55
#define AW_LED_RESET_DELAY 8
#define AW2013_VDD_MIN_UV 2600000
#define AW2013_VDD_MAX_UV 3300000
#define AW2013_VI2C_MIN_UV 1800000
#define AW2013_VI2C_MAX_UV 1800000
#define MAX_RISE_TIME_MS 7
#define MAX_HOLD_TIME_MS 5
#define MAX_FALL_TIME_MS 7
#define MAX_OFF_TIME_MS 5
struct aw2013_led {
struct i2c_client *client;
struct led_classdev cdev;
struct aw2013_platform_data *pdata;
struct work_struct brightness_work;
struct mutex lock;
struct regulator *vdd;
struct regulator *vcc;
#if defined(CONFIG_FB)
struct notifier_block fb_notif;
#endif
int num_leds;
int id;
};
static int aw2013_write(struct aw2013_led *led, u8 reg, u8 val)
{
return i2c_smbus_write_byte_data(led->client, reg, val);
}
static int aw2013_read(struct aw2013_led *led, u8 reg, u8 *val)
{
s32 ret;
ret = i2c_smbus_read_byte_data(led->client, reg);
if (ret < 0)
return ret;
*val = ret;
return 0;
}
static int aw2013_power_on(struct aw2013_led *led, bool on)
{
int rc;
if (on) {
rc = regulator_enable(led->vdd);
if (rc) {
dev_err(&led->client->dev,
"Regulator vdd enable failed rc=%d\n", rc);
return rc;
}
rc = regulator_enable(led->vcc);
if (rc) {
dev_err(&led->client->dev,
"Regulator vcc enable failed rc=%d\n", rc);
goto fail_enable_reg;
}
} else {
rc = regulator_disable(led->vdd);
if (rc) {
dev_err(&led->client->dev,
"Regulator vdd disable failed rc=%d\n", rc);
return rc;
}
rc = regulator_disable(led->vcc);
if (rc) {
dev_err(&led->client->dev,
"Regulator vcc disable failed rc=%d\n", rc);
goto fail_disable_reg;
}
}
return rc;
fail_enable_reg:
rc = regulator_disable(led->vdd);
if (rc)
dev_err(&led->client->dev,
"Regulator vdd disable failed rc=%d\n", rc);
return rc;
fail_disable_reg:
rc = regulator_enable(led->vdd);
if (rc)
dev_err(&led->client->dev,
"Regulator vdd enable failed rc=%d\n", rc);
return rc;
}
static int aw2013_power_init(struct aw2013_led *led, bool on)
{
int rc;
if (on) {
led->vdd = regulator_get(&led->client->dev, "vdd");
if (IS_ERR(led->vdd)) {
rc = PTR_ERR(led->vdd);
dev_err(&led->client->dev,
"Regulator get failed vdd rc=%d\n", rc);
return rc;
}
if (regulator_count_voltages(led->vdd) > 0) {
rc = regulator_set_voltage(led->vdd, AW2013_VDD_MIN_UV,
AW2013_VDD_MAX_UV);
if (rc) {
dev_err(&led->client->dev,
"Regulator set_vtg failed vdd rc=%d\n",
rc);
goto reg_vdd_put;
}
}
led->vcc = regulator_get(&led->client->dev, "vcc");
if (IS_ERR(led->vcc)) {
rc = PTR_ERR(led->vcc);
dev_err(&led->client->dev,
"Regulator get failed vcc rc=%d\n", rc);
goto reg_vdd_set_vtg;
}
if (regulator_count_voltages(led->vcc) > 0) {
rc = regulator_set_voltage(led->vcc, AW2013_VI2C_MIN_UV,
AW2013_VI2C_MAX_UV);
if (rc) {
dev_err(&led->client->dev,
"Regulator set_vtg failed vcc rc=%d\n", rc);
goto reg_vcc_put;
}
}
} else {
if (regulator_count_voltages(led->vdd) > 0)
regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV);
regulator_put(led->vdd);
if (regulator_count_voltages(led->vcc) > 0)
regulator_set_voltage(led->vcc, 0, AW2013_VI2C_MAX_UV);
regulator_put(led->vcc);
}
return 0;
reg_vcc_put:
regulator_put(led->vcc);
reg_vdd_set_vtg:
if (regulator_count_voltages(led->vdd) > 0)
regulator_set_voltage(led->vdd, 0, AW2013_VDD_MAX_UV);
reg_vdd_put:
regulator_put(led->vdd);
return rc;
}
static void aw2013_brightness_work(struct work_struct *work)
{
struct aw2013_led *led = container_of(work, struct aw2013_led,
brightness_work);
u8 val;
mutex_lock(&led->pdata->led->lock);
if (led->cdev.brightness > 0) {
if (led->cdev.brightness > led->cdev.max_brightness)
led->cdev.brightness = led->cdev.max_brightness;
aw2013_write(led, AW_REG_GLOBAL_CONTROL,
AW_LED_MOUDLE_ENABLE_MASK);
aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id,
led->pdata->max_current);
aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id,
led->cdev.brightness);
aw2013_read(led, AW_REG_LED_ENABLE, &val);
aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id));
} else {
aw2013_read(led, AW_REG_LED_ENABLE, &val);
aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id)));
}
mutex_unlock(&led->pdata->led->lock);
}
static void aw2013_led_blink_set(struct aw2013_led *led, unsigned long blinking)
{
u8 val;
led->cdev.brightness = blinking ? led->cdev.max_brightness : 0;
if (blinking > 0) {
aw2013_write(led, AW_REG_GLOBAL_CONTROL,
AW_LED_MOUDLE_ENABLE_MASK);
aw2013_write(led, AW_REG_LED_CONFIG_BASE + led->id,
AW_LED_FADE_OFF_MASK | AW_LED_FADE_ON_MASK |
AW_LED_BREATHE_MODE_MASK | led->pdata->max_current);
aw2013_write(led, AW_REG_LED_BRIGHTNESS_BASE + led->id,
led->cdev.brightness);
aw2013_write(led, AW_REG_TIMESET0_BASE + led->id * 3,
led->pdata->rise_time_ms << 4 |
led->pdata->hold_time_ms);
aw2013_write(led, AW_REG_TIMESET1_BASE + led->id * 3,
led->pdata->fall_time_ms << 4 |
led->pdata->off_time_ms);
aw2013_read(led, AW_REG_LED_ENABLE, &val);
aw2013_write(led, AW_REG_LED_ENABLE, val | (1 << led->id));
} else {
aw2013_read(led, AW_REG_LED_ENABLE, &val);
aw2013_write(led, AW_REG_LED_ENABLE, val & (~(1 << led->id)));
}
}
static void aw2013_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct aw2013_led *led = container_of(cdev, struct aw2013_led, cdev);
led->cdev.brightness = brightness;
schedule_work(&led->brightness_work);
}
static ssize_t aw2013_store_blink(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
unsigned long blinking;
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw2013_led *led =
container_of(led_cdev, struct aw2013_led, cdev);
ssize_t ret = -EINVAL;
ret = kstrtoul(buf, 10, &blinking);
if (ret)
return ret;
mutex_lock(&led->pdata->led->lock);
aw2013_led_blink_set(led, blinking);
mutex_unlock(&led->pdata->led->lock);
return len;
}
static ssize_t aw2013_led_time_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw2013_led *led =
container_of(led_cdev, struct aw2013_led, cdev);
return snprintf(buf, PAGE_SIZE, "%d %d %d %d\n",
led->pdata->rise_time_ms, led->pdata->hold_time_ms,
led->pdata->fall_time_ms, led->pdata->off_time_ms);
}
static ssize_t aw2013_led_time_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t len)
{
struct led_classdev *led_cdev = dev_get_drvdata(dev);
struct aw2013_led *led =
container_of(led_cdev, struct aw2013_led, cdev);
int rc, rise_time_ms, hold_time_ms, fall_time_ms, off_time_ms;
rc = sscanf(buf, "%d %d %d %d",
&rise_time_ms, &hold_time_ms,
&fall_time_ms, &off_time_ms);
mutex_lock(&led->pdata->led->lock);
led->pdata->rise_time_ms = (rise_time_ms > MAX_RISE_TIME_MS) ?
MAX_RISE_TIME_MS : rise_time_ms;
led->pdata->hold_time_ms = (hold_time_ms > MAX_HOLD_TIME_MS) ?
MAX_HOLD_TIME_MS : hold_time_ms;
led->pdata->fall_time_ms = (fall_time_ms > MAX_FALL_TIME_MS) ?
MAX_FALL_TIME_MS : fall_time_ms;
led->pdata->off_time_ms = (off_time_ms > MAX_OFF_TIME_MS) ?
MAX_OFF_TIME_MS : off_time_ms;
aw2013_led_blink_set(led, 1);
mutex_unlock(&led->pdata->led->lock);
return len;
}
static DEVICE_ATTR(blink, 0664, NULL, aw2013_store_blink);
static DEVICE_ATTR(led_time, 0664, aw2013_led_time_show, aw2013_led_time_store);
static struct attribute *aw2013_led_attributes[] = {
&dev_attr_blink.attr,
&dev_attr_led_time.attr,
NULL,
};
static struct attribute_group aw2013_led_attr_group = {
.attrs = aw2013_led_attributes
};
static int aw_2013_check_chipid(struct aw2013_led *led)
{
u8 val;
aw2013_write(led, AW_REG_RESET, AW_LED_RESET_MASK);
usleep(AW_LED_RESET_DELAY);
aw2013_read(led, AW_REG_RESET, &val);
if (val == AW2013_CHIPID)
return 0;
else
return -EINVAL;
}
#ifdef CONFIG_PM
static int aw2013_led_suspend(struct device *dev)
{
struct aw2013_led *led = dev_get_drvdata(dev);
int ret;
u8 val;
aw2013_read(led, AW_REG_LED_ENABLE, &val);
/*
* If value in AW_REG_LED_ENABLE is 0, it means the RGB leds are
* all off. So we need to power it off.
* If value in AW_REG_LED_ENABLE is not 0, that means LEDs are
* already turned on by upper layer, we keep them alive during
* suspend so as to support screen-off notification LED.
*/
if (val == 0) {
ret = aw2013_power_on(led, false);
if (ret) {
dev_err(dev, "power off failed");
return ret;
}
}
return ret;
}
static int aw2013_led_resume(struct device *dev)
{
struct aw2013_led *led = dev_get_drvdata(dev);
int ret;
u8 val;
aw2013_read(led, AW_REG_LED_ENABLE, &val);
/*
* If value in AW_REG_LED_ENABLE is not 0, it means at least
* one of the RGB leds is on. So we do not need to power on again.
*/
if (val > 0)
return ret;
ret = aw2013_power_on(led, true);
if (ret) {
dev_err(dev, "power on failed");
return ret;
}
return ret;
}
#else
static int aw2013_led_suspend(struct device *dev)
{
return 0;
}
static int aw2013_led_resume(struct device *dev)
{
return 0;
}
#endif
#if (defined(CONFIG_PM) && !defined(CONFIG_FB))
static const struct dev_pm_ops aw2013_led_pm_ops = {
.suspend = aw2013_led_suspend,
.resume = aw2013_led_resume,
};
#else
static const struct dev_pm_ops aw2013_led_pm_ops = {
};
#endif
/*
* If CONFIG_FB is defined, LEDs suspend/resume are triggered by framebuffer.
* If the screen is off, LEDs go to suspend; if screen is on, LEDs go to
* resume; based on user space definition, LEDs may blink when suspend, and
* may be off when resume.
*/
#if defined(CONFIG_FB)
static int fb_notifier_callback(struct notifier_block *self,
unsigned long event, void *data)
{
struct fb_event *evdata = data;
int *blank;
struct aw2013_led *led = container_of(self,
struct aw2013_led, fb_notif);
if (evdata && evdata->data && event == FB_EVENT_BLANK &&
led && led->client) {
blank = evdata->data;
if (*blank == FB_BLANK_UNBLANK)
aw2013_led_resume(&led->client->dev);
else if (*blank == FB_BLANK_POWERDOWN)
aw2013_led_suspend(&led->client->dev);
}
return 0;
}
static int aw2013_set_suspend_callback(struct aw2013_led *led_array)
{
int ret;
led_array->fb_notif.notifier_call = fb_notifier_callback;
ret = fb_register_client(&led_array->fb_notif);
if (ret)
dev_err(&led_array->client->dev,
"Unable to register fb_notifier: %d\n",
ret);
return ret;
}
#else
static int aw2013_set_suspend_callback(struct aw2013_led *led_array)
{
return 0;
}
#endif
static int aw2013_led_err_handle(struct aw2013_led *led_array,
int parsed_leds)
{
int i;
/*
* If probe fails, cannot free resource of all LEDs, only free
* resources of LEDs which have allocated these resource really.
*/
for (i = 0; i < parsed_leds; i++) {
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
&aw2013_led_attr_group);
led_classdev_unregister(&led_array[i].cdev);
cancel_work_sync(&led_array[i].brightness_work);
devm_kfree(&led_array->client->dev, led_array[i].pdata);
led_array[i].pdata = NULL;
}
return i;
}
static int aw2013_led_parse_child_node(struct aw2013_led *led_array,
struct device_node *node)
{
struct aw2013_led *led;
struct device_node *temp;
struct aw2013_platform_data *pdata;
int rc = 0, parsed_leds = 0;
for_each_child_of_node(node, temp) {
led = &led_array[parsed_leds];
led->client = led_array->client;
pdata = devm_kzalloc(&led->client->dev,
sizeof(struct aw2013_platform_data),
GFP_KERNEL);
if (!pdata) {
dev_err(&led->client->dev,
"Failed to allocate memory\n");
goto free_err;
}
pdata->led = led_array;
led->pdata = pdata;
rc = of_property_read_string(temp, "aw2013,name",
&led->cdev.name);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading led name, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,id",
&led->id);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading id, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,max-brightness",
&led->cdev.max_brightness);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading max-brightness, rc = %d\n",
rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,max-current",
&led->pdata->max_current);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading max-current, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,rise-time-ms",
&led->pdata->rise_time_ms);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading rise-time-ms, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,hold-time-ms",
&led->pdata->hold_time_ms);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading hold-time-ms, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,fall-time-ms",
&led->pdata->fall_time_ms);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading fall-time-ms, rc = %d\n", rc);
goto free_pdata;
}
rc = of_property_read_u32(temp, "aw2013,off-time-ms",
&led->pdata->off_time_ms);
if (rc < 0) {
dev_err(&led->client->dev,
"Failure reading off-time-ms, rc = %d\n", rc);
goto free_pdata;
}
INIT_WORK(&led->brightness_work, aw2013_brightness_work);
led->cdev.brightness_set = aw2013_set_brightness;
rc = led_classdev_register(&led->client->dev, &led->cdev);
if (rc) {
dev_err(&led->client->dev,
"unable to register led %d,rc=%d\n",
led->id, rc);
goto free_pdata;
}
rc = sysfs_create_group(&led->cdev.dev->kobj,
&aw2013_led_attr_group);
if (rc) {
dev_err(&led->client->dev, "led sysfs rc: %d\n", rc);
goto free_class;
}
parsed_leds++;
}
return 0;
free_class:
aw2013_led_err_handle(led_array, parsed_leds);
led_classdev_unregister(&led_array[parsed_leds].cdev);
cancel_work_sync(&led_array[parsed_leds].brightness_work);
devm_kfree(&led->client->dev, led_array[parsed_leds].pdata);
led_array[parsed_leds].pdata = NULL;
return rc;
free_pdata:
aw2013_led_err_handle(led_array, parsed_leds);
devm_kfree(&led->client->dev, led_array[parsed_leds].pdata);
return rc;
free_err:
aw2013_led_err_handle(led_array, parsed_leds);
return rc;
}
static int aw2013_led_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct aw2013_led *led_array;
struct device_node *node;
int ret, num_leds = 0;
node = client->dev.of_node;
if (node == NULL)
return -EINVAL;
num_leds = of_get_child_count(node);
if (!num_leds)
return -EINVAL;
led_array = devm_kzalloc(&client->dev,
(sizeof(struct aw2013_led) * num_leds), GFP_KERNEL);
if (!led_array) {
dev_err(&client->dev, "Unable to allocate memory\n");
return -ENOMEM;
}
led_array->client = client;
led_array->num_leds = num_leds;
mutex_init(&led_array->lock);
ret = aw_2013_check_chipid(led_array);
if (ret) {
dev_err(&client->dev, "Check chip id error\n");
goto free_led_arry;
}
ret = aw2013_led_parse_child_node(led_array, node);
if (ret) {
dev_err(&client->dev, "parsed node error\n");
goto free_led_arry;
}
i2c_set_clientdata(client, led_array);
ret = aw2013_power_init(led_array, true);
if (ret) {
dev_err(&client->dev, "power init failed");
goto fail_parsed_node;
}
ret = aw2013_power_on(led_array, true);
if (ret) {
dev_err(&client->dev, "power on failed");
goto pwr_deinit;
}
ret = aw2013_set_suspend_callback(led_array);
if (ret) {
dev_err(&client->dev, "set suspend callback failed");
goto pwr_deinit;
}
return 0;
pwr_deinit:
aw2013_power_init(led_array, false);
fail_parsed_node:
aw2013_led_err_handle(led_array, num_leds);
free_led_arry:
mutex_destroy(&led_array->lock);
devm_kfree(&client->dev, led_array);
led_array = NULL;
return ret;
}
static int aw2013_led_remove(struct i2c_client *client)
{
struct aw2013_led *led_array = i2c_get_clientdata(client);
int i, parsed_leds = led_array->num_leds;
for (i = 0; i < parsed_leds; i++) {
sysfs_remove_group(&led_array[i].cdev.dev->kobj,
&aw2013_led_attr_group);
led_classdev_unregister(&led_array[i].cdev);
cancel_work_sync(&led_array[i].brightness_work);
devm_kfree(&client->dev, led_array[i].pdata);
led_array[i].pdata = NULL;
}
mutex_destroy(&led_array->lock);
devm_kfree(&client->dev, led_array);
led_array = NULL;
return 0;
}
static const struct i2c_device_id aw2013_led_id[] = {
{"aw2013_led", 0},
{},
};
MODULE_DEVICE_TABLE(i2c, aw2013_led_id);
static struct of_device_id aw2013_match_table[] = {
{ .compatible = "awinic,aw2013",},
{ },
};
static struct i2c_driver aw2013_led_driver = {
.probe = aw2013_led_probe,
.remove = aw2013_led_remove,
.driver = {
.name = "aw2013_led",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(aw2013_match_table),
#ifdef CONFIG_PM
.pm = &aw2013_led_pm_ops,
#endif
},
.id_table = aw2013_led_id,
};
static int __init aw2013_led_init(void)
{
return i2c_add_driver(&aw2013_led_driver);
}
module_init(aw2013_led_init);
static void __exit aw2013_led_exit(void)
{
i2c_del_driver(&aw2013_led_driver);
}
module_exit(aw2013_led_exit);
MODULE_DESCRIPTION("AWINIC aw2013 LED driver");
MODULE_LICENSE("GPL v2");

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef __LINUX_AW2013_LED_H__
#define __LINUX_AW2013_LED_H__
/* The definition of each time described as shown in figure.
* /-----------\
* / | \
* /| | |\
* / | | | \-----------
* |hold_time_ms | |
* | | |
* rise_time_ms fall_time_ms |
* off_time_ms
*/
struct aw2013_platform_data {
int max_current;
int rise_time_ms;
int hold_time_ms;
int fall_time_ms;
int off_time_ms;
struct aw2013_led *led;
};
#endif