| // 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, ¤t_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"); |