Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 613d0a74 authored by Mao Li's avatar Mao Li
Browse files

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: default avatarMao Li <maol@codeaurora.org>
parent de3f351e
Loading
Loading
Loading
Loading
+75 −0
Original line number Diff line number Diff line
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>;
		};
	};
+1 −0
Original line number Diff line number Diff line
@@ -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.
+7 −0
Original line number Diff line number Diff line
@@ -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"

+1 −0
Original line number Diff line number Diff line
@@ -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 −0
Original line number Diff line number Diff line
/*
 * 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");
Loading