blob: aeb4c175f7bd64f9a186710696708d57977f3c93 [file]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (C) 2018 Google, Inc.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/leds-lp5018.h>
/* Register Table */
/* LED mode registers (read/write) */
#define LP5018_DEVICE_CONFIG0 0x00
#define LP5018_DEVICE_CONFIG1 0x01
#define LP5018_GLOBAL_DIMMING 0x03
#define LP5018_BRIGHTNESS_BASE 0x07
#define LP5018_COLOR_BASE 0x0F
#define LP5018_MAX_LED_CHANNELS 24
#define NUM_OF_RETRIES 5
#define MAX_CURRENT_LIMIT_PERCENT 100
#define MAX_PWM_SCALE 255
#define MAX_GLOBAL_DIMMING 255
#define ATTR_NAME_LEN_MAX (8)
static int skip;
//module_param(skip, int, 0444);
struct lp5018_led {
struct i2c_client *client;
const struct lp5018_platform_data *pdata;
struct attribute **all_attrs;
struct device_attribute *iref_pwm_attrs;
struct attribute_group iref_pwm_attr_group;
const struct lp5018_registers *reg;
u32 current_limit;
u32 pwm_scale;
u32 global_dimming;
/* following is to reduce i2c traffic */
int force_sync;
unsigned char synced[MAX_NUM_LED];
unsigned char saved_value[LP5018_MAX_LED_CHANNELS];
};
enum chip_model_t {
LP5018,
};
static const struct lp5018_registers {
const char *model;
const u8 reg_pwm_base;
const u8 reg_iref_base;
const int led_channels;
} model_register_map[] = {
{ "lp5018",
LP5018_COLOR_BASE,
LP5018_BRIGHTNESS_BASE,
LP5018_MAX_LED_CHANNELS},
};
static const struct lp5018_registers *lp5018_dev_to_register(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
return data->reg;
}
// need to NULL terminate the list of attributes -> add 1
static struct attribute *lp5018_all_attrs[2 * MAX_NUM_LED + 1];
static struct device_attribute lp5018_iref_pwm_attrs[2 * MAX_NUM_LED];
static char lp5018_attr_names[2 * MAX_NUM_LED][ATTR_NAME_LEN_MAX + 1];
static int lp5018_led_read_byte(struct i2c_client *client, u8 reg, u8 *buf)
{
s32 ret = i2c_smbus_read_byte_data(client, reg);
if (ret < 0)
return ret;
*buf = ret;
return 0;
}
static int lp5018_led_write_byte(struct i2c_client *client, u8 reg, u8 val)
{
int ret = i2c_smbus_write_byte_data(client, reg, val);
if (ret < 0) {
dev_err(&client->dev, "%s: failed to write byte data, ret=%d\n",
__func__, ret);
}
return ret;
}
static int lp5018_led_read_block_data(struct i2c_client *client, u8 reg,
u8 len, void *val)
{
int ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
if (ret == len)
return 0;
dev_err(&client->dev, "%s: failed on block read.\n", __func__);
return -EIO;
}
static int lp5018_led_write_block_data(struct i2c_client *client, u8 reg,
u8 len, const void *val)
{
int ret;
int retry = 0;
// TODO: We should make sure Auto_Incr_EN is enabled
do {
ret = i2c_smbus_write_i2c_block_data(client,
reg, len, val);
if (ret < 0) {
dev_err(&client->dev,
"%s: failed on block write. ret=%d, retry=%d\n",
__func__, ret, retry);
}
++retry;
} while (ret == -ERESTARTSYS && retry < NUM_OF_RETRIES);
return ret;
}
static int lp5018_led_init(struct lp5018_led *data)
{
int ret;
int i = 0;
int num_leds = data->pdata->num_leds;
struct i2c_client *client = data->client;
pr_info("%s\n", __func__);
// Enable driver
// Set Chip_EN
ret = lp5018_led_write_byte(client, LP5018_DEVICE_CONFIG0, 0x40);
if (ret < 0) {
dev_err(&client->dev, "%s: failed to set Chip_EN, ret=%d.\n",
__func__, ret);
return ret;
}
// Set Log_Scale_EN = 0, Power_save_EN = 1, Auto_incr_EN = 1 and
// PWM_Dithering_EN = 1
ret = lp5018_led_write_byte(client, LP5018_DEVICE_CONFIG1, 0x1C);
if (ret < 0) {
dev_err(&client->dev, "%s: failed to set Log_Scale_EN, Power_save_EN,"
"Auto_incr_EN and PWM_Dithering_EN, ret=%d.\n",
__func__, ret);
return ret;
}
// Set default brightness to 0xFF
for (i = 0; i < num_leds; ++i) {
ret = lp5018_led_write_byte(client, LP5018_BRIGHTNESS_BASE + i, 0xFF);
if (ret < 0) {
dev_err(&client->dev, "%s: failed to set default brightness"
"for led %d, ret=%d.\n",
__func__, i, ret);
return ret;
}
}
data->reg = &model_register_map[LP5018];
return 0;
}
/* Helper function to show pwm control values. */
static ssize_t lp5018_led_pwm_show_helper(struct device *dev,
unsigned int led_index, char *buf,
u8 addr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int ret;
u8 rgb[3];
if (led_index >= data->pdata->num_leds) {
dev_err(dev, "%s: invalid led number: %d\n", __func__,
led_index);
return -EINVAL;
}
addr += data->pdata->start_num[led_index];
ret = lp5018_led_read_block_data(client, addr, sizeof(rgb), rgb);
if (ret) {
dev_err(dev, "%s: failed to read RGB values.\n", __func__);
return -EIO;
}
return scnprintf(buf, PAGE_SIZE, "%hhu %hhu %hhu\n",
rgb[0], rgb[1], rgb[2]);
}
/* Helper function to show iref control values. */
static ssize_t lp5018_led_iref_show_helper(struct device *dev,
unsigned int led_index, char *buf,
u8 addr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int ret;
u8 brightness;
if (led_index >= data->pdata->num_leds) {
dev_err(dev, "%s: invalid led number: %d\n", __func__,
led_index);
return -EINVAL;
}
addr += led_index;
ret = lp5018_led_read_byte(client, addr, &brightness);
if (ret) {
dev_err(dev, "%s: failed to read brightness.\n", __func__);
return -EIO;
}
return scnprintf(buf, PAGE_SIZE, "%hhu\n", brightness);
}
/* Helper function to store pwm control value to certain LED.
* Input is a char array.
* Format: "255 255 255"
* The input represent R, G, B values from left to right, respectively.
*/
static ssize_t lp5018_led_pwm_store_helper(struct device *dev,
unsigned int led_index,
const char *buf, size_t count, u8 addr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int ret;
u8 rgb[3];
u8 *saved;
if (led_index >= data->pdata->num_leds) {
dev_err(dev, "%s: invalid led number: %d\n", __func__,
led_index);
return -EINVAL;
}
ret = sscanf(buf, "%hhu %hhu %hhu", &rgb[0], &rgb[1], &rgb[2]);
if (ret != 3) {
dev_err(dev, "%s: failed to parse rgb values, ret=%d.\n",
__func__, ret);
return -EINVAL;
}
// Apply Current Limit
rgb[0] = (u8)((u32)(rgb[0]) * data->pwm_scale / (u32)MAX_PWM_SCALE);
rgb[1] = (u8)((u32)(rgb[1]) * data->pwm_scale / (u32)MAX_PWM_SCALE);
rgb[2] = (u8)((u32)(rgb[2]) * data->pwm_scale / (u32)MAX_PWM_SCALE);
saved = &data->saved_value[led_index * 3];
if (!data->force_sync && data->synced[led_index] &&
saved[0] == rgb[0] && saved[1] == rgb[1] && saved[2] == rgb[2])
return count;
addr += data->pdata->start_num[led_index];
ret = lp5018_led_write_block_data(client, addr, sizeof(rgb), rgb);
if (ret < 0) {
dev_err(dev, "%s: failed to write rgb values to led%d.\n",
__func__, led_index);
return ret;
}
dev_dbg(dev, "%s %s: set led%u(0x%02x): R=%hhu, G=%hhu, B=%hhu\n",
__func__, client->name, led_index, addr,
rgb[0], rgb[1], rgb[2]);
memcpy(&data->saved_value[data->pdata->start_num[led_index]], rgb, 3);
data->synced[led_index] = 1;
return count;
}
static ssize_t lp5018_led_iref_store_helper(struct device *dev,
unsigned int led_index,
const char *buf, size_t count, u8 addr)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int ret;
u8 brightness;
if (led_index >= data->pdata->num_leds) {
dev_err(dev, "%s: invalid led number: %d\n", __func__,
led_index);
return -EINVAL;
}
ret = sscanf(buf, "%hhu", &brightness);
if (ret != 1) {
dev_err(dev, "%s: failed to parse brightness, ret=%d.\n",
__func__, ret);
return -EINVAL;
}
addr += led_index;
ret = lp5018_led_write_byte(client, addr, brightness);
if (ret < 0) {
dev_err(dev, "%s: failed to write brightness to led%d.\n",
__func__, led_index);
return ret;
}
dev_dbg(dev, "%s %s: set led%u(0x%02x): Val=%hhu\n",
__func__, client->name, led_index, addr,
brightness);
return count;
}
static ssize_t lp5018_led_pwm_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int led_index;
int ret = sscanf(attr->attr.name, "pwm%u", &led_index);
if (ret != 1) {
dev_err(dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
return lp5018_led_pwm_show_helper(dev, led_index, buf,
lp5018_dev_to_register(dev)->reg_pwm_base);
}
static ssize_t lp5018_led_pwm_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int led_index;
int ret = sscanf(attr->attr.name, "pwm%u", &led_index);
if (ret != 1) {
dev_err(dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
return lp5018_led_pwm_store_helper(dev, led_index, buf, count,
lp5018_dev_to_register(dev)->reg_pwm_base);
}
/* Function to store pwm control values to specified LEDs.
* Input Format" "LED_Index R G B LED_Index R G B ..."
* Note: Let i be the lowest LED_Index found in the input, and
* Let j be the highest LED_Index found in the input.
* All LED indices not specified between i and j will have their
* (R,G,B) set to (0, 0 ,0)
* All LED indices < i or > j will be unchanged.
* LED indices can be specified in any order
* For example: "0 255 255 255 1 100 150 200" is equivalent
* to "1 100 150 200 0 255 255 255"
*/
static ssize_t lp5018_led_pwm_all_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int num_leds = 0;
int chars_read = 0;
int led_idx;
int ret;
int rgb_start_idx;
int min_rgb_start_idx = lp5018_dev_to_register(dev)->led_channels;
int max_rgb_start_idx = 0;
u8 rgb[LP5018_MAX_LED_CHANNELS]; /* lp5018_dev_to_register(dev)->led_channels */
int need_to_update = data->force_sync;
memcpy(&rgb[0], &data->saved_value[0], lp5018_dev_to_register(dev)->led_channels);
while (sscanf(buf, "%d%n", &led_idx, &chars_read) == 1) {
if (led_idx > data->pdata->num_leds - 1 || led_idx < 0) {
dev_err(dev, "%s: incorrect led index %d supplied\n", __func__, led_idx);
return -EINVAL;
}
++num_leds;
if (num_leds > data->pdata->num_leds) {
dev_err(dev, "%s: incorrect number of leds supplied\n", __func__);
return -EINVAL;
}
buf += chars_read;
rgb_start_idx = data->pdata->start_num[led_idx];
// Keep track of the minimum-sized contiguous block of data we need to write
if (rgb_start_idx < min_rgb_start_idx)
min_rgb_start_idx = rgb_start_idx;
if (rgb_start_idx > max_rgb_start_idx)
max_rgb_start_idx = rgb_start_idx;
if (sscanf(buf, "%hhu %hhu %hhu%n", &rgb[rgb_start_idx],
&rgb[rgb_start_idx + 1],
&rgb[rgb_start_idx + 2],
&chars_read) != 3) {
dev_err(dev, "%s: unable to parse led values to write\n", __func__);
return -EINVAL;
}
// Apply Current Limit
rgb[rgb_start_idx] =
(u8)((u32)(rgb[rgb_start_idx]) * data->pwm_scale /
(u32)MAX_PWM_SCALE);
rgb[rgb_start_idx + 1] =
(u8)((u32)(rgb[rgb_start_idx + 1]) * data->pwm_scale /
(u32)MAX_PWM_SCALE);
rgb[rgb_start_idx + 2] =
(u8)((u32)(rgb[rgb_start_idx + 2]) * data->pwm_scale /
(u32)MAX_PWM_SCALE);
buf += chars_read;
if (!need_to_update && (!data->synced[led_idx] ||
rgb[rgb_start_idx] != data->saved_value[rgb_start_idx] ||
rgb[rgb_start_idx + 1] != data->saved_value[rgb_start_idx + 1] ||
rgb[rgb_start_idx + 2] != data->saved_value[rgb_start_idx + 2])) {
need_to_update = 1;
}
data->synced[led_idx] = 1;
}
if (!need_to_update)
return count;
// Set contiguous block bounds to something sensible in case of a no-LED
// update
if (min_rgb_start_idx == lp5018_dev_to_register(dev)->led_channels ||
max_rgb_start_idx == 0) {
min_rgb_start_idx = data->pdata->start_num[0];
max_rgb_start_idx = data->pdata->start_num[data->pdata->num_leds - 1];
}
// Perform a minimum-size write to preserve as much existing LED state as
// possible (i.e. if any other LEDs are on, keep them on).
ret = lp5018_led_write_block_data(client,
lp5018_dev_to_register(dev)->reg_pwm_base + min_rgb_start_idx,
max_rgb_start_idx + 3 - min_rgb_start_idx, rgb + min_rgb_start_idx);
if (ret < 0) {
dev_err(dev, "%s: failed to write rgb values to all leds, err: %d\n",
__func__, ret);
return ret;
}
memcpy(&data->saved_value[min_rgb_start_idx],
&rgb[min_rgb_start_idx],
max_rgb_start_idx + 3 - min_rgb_start_idx);
return count;
}
static ssize_t lp5018_led_force_sync_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
unsigned char force_sync = (unsigned char)data->force_sync;
int ret;
ret = kstrtou8(buf, 0, &force_sync);
if (ret == 1 && (force_sync == 0 || force_sync == 1)) {
dev_info(dev, "%s: force LED sync: %u\n", __func__, force_sync);
data->force_sync = force_sync;
} else {
dev_err(dev, "%s: invalid value: %u\n", __func__, force_sync);
}
return count;
}
static ssize_t lp5018_led_iref_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
unsigned int led_index;
int ret = sscanf(attr->attr.name, "iref%u", &led_index);
if (ret != 1) {
dev_err(dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
return lp5018_led_iref_show_helper(dev, led_index, buf,
lp5018_dev_to_register(dev)->reg_iref_base);
}
static ssize_t lp5018_led_iref_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
unsigned int led_index;
int ret = sscanf(attr->attr.name, "iref%u", &led_index);
if (ret != 1) {
dev_err(dev, "%s: failed to get led index.\n", __func__);
return -EINVAL;
}
return lp5018_led_iref_store_helper(dev, led_index, buf, count,
lp5018_dev_to_register(dev)->reg_iref_base);
}
static ssize_t lp5018_led_global_dimming_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
return scnprintf(buf, PAGE_SIZE, "%u\n", data->global_dimming);
}
static __always_inline void lp5018_set_pwm_scale(struct lp5018_led *data)
{
data->pwm_scale = MAX_PWM_SCALE * data->current_limit *
data->global_dimming / (MAX_CURRENT_LIMIT_PERCENT *
MAX_GLOBAL_DIMMING);
}
static ssize_t lp5018_led_global_dimming_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int global_dimming;
if (kstrtouint(buf, 10, &global_dimming))
return -EINVAL;
if (global_dimming > 255 || global_dimming < 0) {
dev_err(dev, "%s: Invalid Global Dimming value %d, valid range [0,255]\n",
__func__, global_dimming);
return -EINVAL;
}
data->global_dimming = global_dimming;
lp5018_set_pwm_scale(data);
dev_dbg(dev, "%s: Updated Global Dimming to %u\n", __func__,
data->global_dimming);
return count;
}
static ssize_t lp5018_led_init_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int ret;
dev_info(dev, "%s: initializing LED...\n", __func__);
ret = lp5018_led_init(data);
if (ret) {
dev_err(dev, "%s: failed to initialize LED. ret=%d\n",
__func__, ret);
return ret;
}
return count;
}
static ssize_t lp5018_current_limit_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
return scnprintf(buf, PAGE_SIZE, "%u\n", data->current_limit);
}
static ssize_t lp5018_current_limit_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct i2c_client *client = to_i2c_client(dev);
struct lp5018_led *data = i2c_get_clientdata(client);
int current_limit;
if (kstrtouint(buf, 10, &current_limit))
return -EINVAL;
if (current_limit > MAX_CURRENT_LIMIT_PERCENT) {
dev_err(dev, "%s: Invalid Current Limit %d\n", __func__,
current_limit);
return -EINVAL;
}
dev_info(dev, "%s: Updating Current Limit to %02hhd\n", __func__,
current_limit);
data->current_limit = current_limit;
lp5018_set_pwm_scale(data);
return count;
}
static DEVICE_ATTR(global_dimming, 0644/* S_IWUSR | S_IWGRP | S_IRUGO */,
lp5018_led_global_dimming_show,
lp5018_led_global_dimming_store);
static DEVICE_ATTR(init_led, 0220/* S_IWUSR | S_IWGRP */,
NULL, lp5018_led_init_store);
static DEVICE_ATTR(pwm_all, 0644/* S_IWUSR | S_IWGRP | S_IRUGO */,
NULL, lp5018_led_pwm_all_store);
static DEVICE_ATTR(current_limit, 0644/* S_IWUSR | S_IWGRP | S_IRUGO */,
lp5018_current_limit_show, lp5018_current_limit_store);
static DEVICE_ATTR(force_sync, 0644/* S_IWUSR | S_IWGRP | S_IRUGO */,
NULL, lp5018_led_force_sync_store);
static struct attribute *other_led_attrs[] = {
&dev_attr_global_dimming.attr,
&dev_attr_init_led.attr,
&dev_attr_pwm_all.attr,
&dev_attr_current_limit.attr,
&dev_attr_force_sync.attr,
NULL
};
static const struct attribute_group lp5018_led_other_attr_group = {
.attrs = other_led_attrs,
};
#ifdef CONFIG_OF
static struct lp5018_platform_data *lp5018_led_parse_devtree(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct device_node *node;
struct lp5018_platform_data *pdata;
int reset_gpio = -1;
int i;
node = dev->of_node;
if (!node) {
dev_err(dev, "%s: of_node is NULL.\n", __func__);
return ERR_PTR(-ENODEV);
}
pdata = devm_kzalloc(dev, sizeof(struct device_node), GFP_KERNEL);
if (!pdata)
return ERR_PTR(-ENOMEM);
if (of_property_read_u32(node, "num_leds", &pdata->num_leds)) {
dev_err(dev, "%s: failed to get number of leds.\n", __func__);
devm_kfree(dev, pdata);
return ERR_PTR(-EINVAL);
}
dev_info(dev, "lp5018 num_leds=%d\n", pdata->num_leds);
if (of_property_read_u32_array(node, "start_num", &pdata->start_num[0],
pdata->num_leds)) {
dev_err(dev, "%s: failed to get pwm register addresses.\n",
__func__);
devm_kfree(dev, pdata);
return ERR_PTR(-EINVAL);
}
for (i = 0; i < pdata->num_leds; i++) {
if (pdata->start_num[i] > pdata->num_leds * 3 - 3) {
dev_err(dev, "%s: invalid pwm register addresses.\n", __func__);
devm_kfree(dev, pdata);
return ERR_PTR(-EINVAL);
}
}
reset_gpio = of_get_named_gpio(node, "reset_gpio", 0);
if (!gpio_is_valid(reset_gpio)) {
dev_err(dev, "%s: failed to get reset_gpio.\n", __func__);
return ERR_PTR(-ENODEV);
}
pdata->reset_gpio = reset_gpio;
if (of_property_read_u32(node, "default_current_limit_percent",
&pdata->default_current_limit_percent)) {
dev_err(dev, "%s: no default current limit specified in dts.\n", __func__);
pdata->default_current_limit_percent = MAX_CURRENT_LIMIT_PERCENT;
}
if (pdata->default_current_limit_percent > MAX_CURRENT_LIMIT_PERCENT) {
dev_err(dev, "%s: Invalid default current in dts.\n", __func__);
pdata->default_current_limit_percent = MAX_CURRENT_LIMIT_PERCENT;
}
dev_info(dev, "lp5018 current_limit=%d\n", pdata->default_current_limit_percent);
return pdata;
}
#endif
static int lp5018_led_reset(struct lp5018_led *data)
{
struct i2c_client *client = data->client;
int reset = data->pdata->reset_gpio;
int ret = gpio_request(reset, "LedRST");
if (ret) {
dev_err(&client->dev, "%s: failed to request GPIO%d.\n",
__func__, reset);
return ret;
}
ret = gpio_direction_output(reset, 1);
if (ret) {
dev_err(&client->dev,
"%s: failed to set gpio to 1.\n", __func__);
gpio_free(reset);
return ret;
}
gpio_set_value(reset, 0);
msleep(20);
gpio_set_value(reset, 1);
gpio_free(reset);
return ret;
}
static void delete_iref_pwm_attr_array(struct lp5018_led *data, int num_leds)
{
memset(lp5018_attr_names, 0, sizeof(lp5018_attr_names));
memset(lp5018_iref_pwm_attrs, 0, sizeof(lp5018_iref_pwm_attrs));
memset(lp5018_all_attrs, 0, sizeof(lp5018_all_attrs));
}
static void construct_iref_pwm_device_attributes(struct lp5018_led *data,
int num_leds,
struct kobject *kobj)
{
int i, error;
if (num_leds > MAX_NUM_LED)
return;
data->all_attrs = lp5018_all_attrs;
data->iref_pwm_attrs = lp5018_iref_pwm_attrs;
for (i = 0; i < 2 * num_leds; ++i) {
char buf[ATTR_NAME_LEN_MAX];
char *name;
if (i < num_leds)
snprintf(buf, sizeof(buf), "iref%u", i);
else
snprintf(buf, sizeof(buf), "pwm%u", i - num_leds);
name = lp5018_attr_names[i];
strncpy(name, buf, ATTR_NAME_LEN_MAX);
data->iref_pwm_attrs[i].attr.name = name;
data->iref_pwm_attrs[i].attr.mode = 0664; /* S_IWUSR | S_IWGRP | S_IRUGO */
if (i < num_leds) {
data->iref_pwm_attrs[i].show = lp5018_led_iref_show;
data->iref_pwm_attrs[i].store = lp5018_led_iref_store;
} else {
data->iref_pwm_attrs[i].show = lp5018_led_pwm_show;
data->iref_pwm_attrs[i].store = lp5018_led_pwm_store;
}
data->all_attrs[i] = &data->iref_pwm_attrs[i].attr;
}
data->iref_pwm_attr_group.attrs = data->all_attrs;
error = sysfs_create_group(kobj, &data->iref_pwm_attr_group);
if (error)
pr_err("%s: failed to create sysfs, err:%d\n", __func__, error);
}
static int lp5018_led_probe(struct i2c_client *client)
{
struct lp5018_led *data;
struct device *dev = &client->dev;
struct lp5018_platform_data *pdata = dev_get_platdata(dev);
int error = 0;
if (skip == 1)
return -ENODEV;
if (!pdata) {
pdata = lp5018_led_parse_devtree(client);
if (IS_ERR(pdata)) {
dev_err(dev,
"%s: failed to get device data from device tree.\n",
__func__);
error = -EINVAL;
goto err_get_pdata;
}
}
dev_info(dev, "%s: %s number of leds supported: %d\n", __func__,
client->name, pdata->num_leds);
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data) {
error = -ENOMEM;
goto err_free_mem;
}
data->client = client;
data->pdata = pdata;
data->current_limit = pdata->default_current_limit_percent;
data->global_dimming = MAX_GLOBAL_DIMMING;
lp5018_set_pwm_scale(data);
i2c_set_clientdata(client, data);
error = lp5018_led_init(data);
if (error) {
dev_err(&client->dev, "%s: failed to initialize leds, err:%d\n",
__func__, error);
goto err_free_mem;
}
construct_iref_pwm_device_attributes(data, pdata->num_leds,
&client->dev.kobj);
error = sysfs_create_group(&client->dev.kobj,
&lp5018_led_other_attr_group);
if (error) {
dev_err(&client->dev, "%s: failed to create sysfs, err:%d\n",
__func__, error);
goto err_free_sysfs;
}
dev_info(dev, "%s: %s probe successfully!\n", __func__, client->name);
return 0;
err_free_sysfs:
delete_iref_pwm_attr_array(data, pdata->num_leds);
err_free_mem:
kfree(data);
err_get_pdata:
return error;
}
static void lp5018_led_remove(struct i2c_client *client)
{
/* There is no power-off for the product so probably i2c driver/device
* unbinding is not called. Leave this function here for driver
* integrity but return 0 directly.
*/
}
static void lp5018_led_shutdown(struct i2c_client *client)
{
struct lp5018_led *data = i2c_get_clientdata(client);
int num_leds = data->pdata->num_leds;
int ret;
dev_info(&client->dev, "%s: prepare to shutdown device.\n", __func__);
ret = lp5018_led_reset(data);
if (ret)
dev_err(&client->dev, "%s: failed to reset LED.\n", __func__);
sysfs_remove_group(&client->dev.kobj, &data->iref_pwm_attr_group);
sysfs_remove_group(&client->dev.kobj, &lp5018_led_other_attr_group);
delete_iref_pwm_attr_array(data, num_leds);
kfree(data);
}
static const struct i2c_device_id lp5018_led_id[] = {
{ "led-lp5018", 0 },
{ }
};
MODULE_DEVICE_TABLE(i2c, lp5018_led_id);
#ifdef CONFIG_OF
static const struct of_device_id lp5018_led_of_match[] = {
{ .compatible = "ti,led-lp5018" },
{ }
};
#endif
static struct i2c_driver lp5018_led_driver = {
.driver = {
.name = "lp5018-leds",
.owner = THIS_MODULE,
.pm = NULL,
.of_match_table = of_match_ptr(lp5018_led_of_match),
},
.probe = lp5018_led_probe,
.remove = lp5018_led_remove,
.shutdown = lp5018_led_shutdown,
.id_table = lp5018_led_id,
};
int lp5018_i2c_init(void)
{
int ret = 0;
AW_LOG("enter, ti lp5018 driver\n");
ret = i2c_add_driver(&lp5018_led_driver);
if (ret)
AW_ERR("failed to register lp5018 driver!\n");
return ret;
}
void __exit lp5018_i2c_exit(void)
{
i2c_del_driver(&lp5018_led_driver);
}
MODULE_AUTHOR("Blake Jacquot <blakejacquot@google.com>");
MODULE_DESCRIPTION("TI LP5018 LED driver");
MODULE_LICENSE("GPL");