643 lines
16 KiB
C
643 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
||
/*
|
||
* LP5812 LED driver
|
||
*
|
||
* Copyright (C) 2025 Texas Instruments
|
||
*
|
||
* Author: Jared Zhou <jared-zhou@ti.com>
|
||
*/
|
||
|
||
#include <linux/delay.h>
|
||
#include <linux/i2c.h>
|
||
#include <linux/init.h>
|
||
#include <linux/kernel.h>
|
||
#include <linux/led-class-multicolor.h>
|
||
#include <linux/leds.h>
|
||
#include <linux/module.h>
|
||
#include <linux/mutex.h>
|
||
#include <linux/sysfs.h>
|
||
#include <linux/types.h>
|
||
|
||
#include "leds-lp5812.h"
|
||
|
||
static const struct lp5812_mode_mapping chip_mode_map[] = {
|
||
{"direct_mode", 0, 0, 0, 0, 0, 0},
|
||
{"tcm:1:0", 1, 0, 0, 0, 0, 0},
|
||
{"tcm:1:1", 1, 1, 0, 0, 0, 0},
|
||
{"tcm:1:2", 1, 2, 0, 0, 0, 0},
|
||
{"tcm:1:3", 1, 3, 0, 0, 0, 0},
|
||
{"tcm:2:0:1", 2, 0, 1, 0, 0, 0},
|
||
{"tcm:2:0:2", 2, 0, 2, 0, 0, 0},
|
||
{"tcm:2:0:3", 2, 0, 3, 0, 0, 0},
|
||
{"tcm:2:1:2", 2, 1, 2, 0, 0, 0},
|
||
{"tcm:2:1:3", 2, 1, 3, 0, 0, 0},
|
||
{"tcm:2:2:3", 2, 2, 3, 0, 0, 0},
|
||
{"tcm:3:0:1:2", 3, 0, 1, 2, 0, 0},
|
||
{"tcm:3:0:1:3", 3, 0, 1, 3, 0, 0},
|
||
{"tcm:3:0:2:3", 3, 0, 2, 3, 0, 0},
|
||
{"tcm:4:0:1:2:3", 4, 0, 1, 2, 3, 0},
|
||
{"mix:1:0:1", 5, 1, 0, 0, 0, 0},
|
||
{"mix:1:0:2", 5, 2, 0, 0, 0, 0},
|
||
{"mix:1:0:3", 5, 3, 0, 0, 0, 0},
|
||
{"mix:1:1:0", 5, 0, 0, 0, 0, 1},
|
||
{"mix:1:1:2", 5, 2, 0, 0, 0, 1},
|
||
{"mix:1:1:3", 5, 3, 0, 0, 0, 1},
|
||
{"mix:1:2:0", 5, 0, 0, 0, 0, 2},
|
||
{"mix:1:2:1", 5, 1, 0, 0, 0, 2},
|
||
{"mix:1:2:3", 5, 3, 0, 0, 0, 2},
|
||
{"mix:1:3:0", 5, 0, 0, 0, 0, 3},
|
||
{"mix:1:3:1", 5, 1, 0, 0, 0, 3},
|
||
{"mix:1:3:2", 5, 2, 0, 0, 0, 3},
|
||
{"mix:2:0:1:2", 6, 1, 2, 0, 0, 0},
|
||
{"mix:2:0:1:3", 6, 1, 3, 0, 0, 0},
|
||
{"mix:2:0:2:3", 6, 2, 3, 0, 0, 0},
|
||
{"mix:2:1:0:2", 6, 0, 2, 0, 0, 1},
|
||
{"mix:2:1:0:3", 6, 0, 3, 0, 0, 1},
|
||
{"mix:2:1:2:3", 6, 2, 3, 0, 0, 1},
|
||
{"mix:2:2:0:1", 6, 0, 1, 0, 0, 2},
|
||
{"mix:2:2:0:3", 6, 0, 3, 0, 0, 2},
|
||
{"mix:2:2:1:3", 6, 1, 3, 0, 0, 2},
|
||
{"mix:2:3:0:1", 6, 0, 1, 0, 0, 3},
|
||
{"mix:2:3:0:2", 6, 0, 2, 0, 0, 3},
|
||
{"mix:2:3:1:2", 6, 1, 2, 0, 0, 3},
|
||
{"mix:3:0:1:2:3", 7, 1, 2, 3, 0, 0},
|
||
{"mix:3:1:0:2:3", 7, 0, 2, 3, 0, 1},
|
||
{"mix:3:2:0:1:3", 7, 0, 1, 3, 0, 2},
|
||
{"mix:3:3:0:1:2", 7, 0, 1, 2, 0, 3}
|
||
};
|
||
|
||
static int lp5812_write(struct lp5812_chip *chip, u16 reg, u8 val)
|
||
{
|
||
struct device *dev = &chip->client->dev;
|
||
struct i2c_msg msg;
|
||
u8 buf[LP5812_DATA_LENGTH];
|
||
u8 reg_addr_bit8_9;
|
||
int ret;
|
||
|
||
/* Extract register address bits 9 and 8 for Address Byte 1 */
|
||
reg_addr_bit8_9 = (reg >> LP5812_REG_ADDR_HIGH_SHIFT) & LP5812_REG_ADDR_BIT_8_9_MASK;
|
||
|
||
/* Prepare payload: Address Byte 2 (bits [7:0]) and value to write */
|
||
buf[LP5812_DATA_BYTE_0_IDX] = (u8)(reg & LP5812_REG_ADDR_LOW_MASK);
|
||
buf[LP5812_DATA_BYTE_1_IDX] = val;
|
||
|
||
/* Construct I2C message for a write operation */
|
||
msg.addr = (chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
|
||
msg.flags = 0;
|
||
msg.len = sizeof(buf);
|
||
msg.buf = buf;
|
||
|
||
ret = i2c_transfer(chip->client->adapter, &msg, 1);
|
||
if (ret == 1)
|
||
return 0;
|
||
|
||
dev_err(dev, "I2C write error, ret=%d\n", ret);
|
||
return ret < 0 ? ret : -EIO;
|
||
}
|
||
|
||
static int lp5812_read(struct lp5812_chip *chip, u16 reg, u8 *val)
|
||
{
|
||
struct device *dev = &chip->client->dev;
|
||
struct i2c_msg msgs[LP5812_READ_MSG_LENGTH];
|
||
u8 ret_val;
|
||
u8 reg_addr_bit8_9;
|
||
u8 converted_reg;
|
||
int ret;
|
||
|
||
/* Extract register address bits 9 and 8 for Address Byte 1 */
|
||
reg_addr_bit8_9 = (reg >> LP5812_REG_ADDR_HIGH_SHIFT) & LP5812_REG_ADDR_BIT_8_9_MASK;
|
||
|
||
/* Lower 8 bits go in Address Byte 2 */
|
||
converted_reg = (u8)(reg & LP5812_REG_ADDR_LOW_MASK);
|
||
|
||
/* Prepare I2C write message to set register address */
|
||
msgs[LP5812_MSG_0_IDX].addr =
|
||
(chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
|
||
msgs[LP5812_MSG_0_IDX].flags = 0;
|
||
msgs[LP5812_MSG_0_IDX].len = 1;
|
||
msgs[LP5812_MSG_0_IDX].buf = &converted_reg;
|
||
|
||
/* Prepare I2C read message to retrieve register value */
|
||
msgs[LP5812_MSG_1_IDX].addr =
|
||
(chip->client->addr << LP5812_CHIP_ADDR_SHIFT) | reg_addr_bit8_9;
|
||
msgs[LP5812_MSG_1_IDX].flags = I2C_M_RD;
|
||
msgs[LP5812_MSG_1_IDX].len = 1;
|
||
msgs[LP5812_MSG_1_IDX].buf = &ret_val;
|
||
|
||
ret = i2c_transfer(chip->client->adapter, msgs, LP5812_READ_MSG_LENGTH);
|
||
if (ret == LP5812_READ_MSG_LENGTH) {
|
||
*val = ret_val;
|
||
return 0;
|
||
}
|
||
|
||
dev_err(dev, "I2C read error, ret=%d\n", ret);
|
||
*val = 0;
|
||
return ret < 0 ? ret : -EIO;
|
||
}
|
||
|
||
static int lp5812_read_tsd_config_status(struct lp5812_chip *chip, u8 *reg_val)
|
||
{
|
||
return lp5812_read(chip, LP5812_TSD_CONFIG_STATUS, reg_val);
|
||
}
|
||
|
||
static int lp5812_update_regs_config(struct lp5812_chip *chip)
|
||
{
|
||
u8 reg_val;
|
||
int ret;
|
||
|
||
ret = lp5812_write(chip, LP5812_CMD_UPDATE, LP5812_UPDATE_CMD_VAL);
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = lp5812_read_tsd_config_status(chip, ®_val);
|
||
if (ret)
|
||
return ret;
|
||
|
||
return reg_val & LP5812_CFG_ERR_STATUS_MASK;
|
||
}
|
||
|
||
static ssize_t parse_drive_mode(struct lp5812_chip *chip, const char *str)
|
||
{
|
||
int i;
|
||
|
||
chip->drive_mode.bits.mix_sel_led_0 = false;
|
||
chip->drive_mode.bits.mix_sel_led_1 = false;
|
||
chip->drive_mode.bits.mix_sel_led_2 = false;
|
||
chip->drive_mode.bits.mix_sel_led_3 = false;
|
||
|
||
if (sysfs_streq(str, LP5812_MODE_DIRECT_NAME)) {
|
||
chip->drive_mode.bits.led_mode = LP5812_MODE_DIRECT_VALUE;
|
||
return 0;
|
||
}
|
||
|
||
for (i = 0; i < ARRAY_SIZE(chip_mode_map); i++) {
|
||
if (!sysfs_streq(str, chip_mode_map[i].mode_name))
|
||
continue;
|
||
|
||
chip->drive_mode.bits.led_mode = chip_mode_map[i].mode;
|
||
chip->scan_order.bits.order0 = chip_mode_map[i].scan_order_0;
|
||
chip->scan_order.bits.order1 = chip_mode_map[i].scan_order_1;
|
||
chip->scan_order.bits.order2 = chip_mode_map[i].scan_order_2;
|
||
chip->scan_order.bits.order3 = chip_mode_map[i].scan_order_3;
|
||
|
||
switch (chip_mode_map[i].selection_led) {
|
||
case LP5812_MODE_MIX_SELECT_LED_0:
|
||
chip->drive_mode.bits.mix_sel_led_0 = true;
|
||
break;
|
||
case LP5812_MODE_MIX_SELECT_LED_1:
|
||
chip->drive_mode.bits.mix_sel_led_1 = true;
|
||
break;
|
||
case LP5812_MODE_MIX_SELECT_LED_2:
|
||
chip->drive_mode.bits.mix_sel_led_2 = true;
|
||
break;
|
||
case LP5812_MODE_MIX_SELECT_LED_3:
|
||
chip->drive_mode.bits.mix_sel_led_3 = true;
|
||
break;
|
||
default:
|
||
return -EINVAL;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
return -EINVAL;
|
||
}
|
||
|
||
static int lp5812_set_drive_mode_scan_order(struct lp5812_chip *chip)
|
||
{
|
||
u8 val;
|
||
int ret;
|
||
|
||
val = chip->drive_mode.val;
|
||
ret = lp5812_write(chip, LP5812_DEV_CONFIG1, val);
|
||
if (ret)
|
||
return ret;
|
||
|
||
val = chip->scan_order.val;
|
||
ret = lp5812_write(chip, LP5812_DEV_CONFIG2, val);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_set_led_mode(struct lp5812_chip *chip, int led_number,
|
||
enum control_mode mode)
|
||
{
|
||
u8 reg_val;
|
||
u16 reg;
|
||
int ret;
|
||
|
||
/*
|
||
* Select device configuration register.
|
||
* Reg3 for LED_0–LED_3, LED_A0–A2, LED_B0
|
||
* Reg4 for LED_B1–B2, LED_C0–C2, LED_D0–D2
|
||
*/
|
||
if (led_number < LP5812_NUMBER_LED_IN_REG)
|
||
reg = LP5812_DEV_CONFIG3;
|
||
else
|
||
reg = LP5812_DEV_CONFIG4;
|
||
|
||
ret = lp5812_read(chip, reg, ®_val);
|
||
if (ret)
|
||
return ret;
|
||
|
||
if (mode == LP5812_MODE_MANUAL)
|
||
reg_val &= ~(LP5812_ENABLE << (led_number % LP5812_NUMBER_LED_IN_REG));
|
||
else
|
||
reg_val |= (LP5812_ENABLE << (led_number % LP5812_NUMBER_LED_IN_REG));
|
||
|
||
ret = lp5812_write(chip, reg, reg_val);
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = lp5812_update_regs_config(chip);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_manual_dc_pwm_control(struct lp5812_chip *chip, int led_number,
|
||
u8 val, enum dimming_type dimming_type)
|
||
{
|
||
u16 led_base_reg;
|
||
int ret;
|
||
|
||
if (dimming_type == LP5812_DIMMING_ANALOG)
|
||
led_base_reg = LP5812_MANUAL_DC_BASE;
|
||
else
|
||
led_base_reg = LP5812_MANUAL_PWM_BASE;
|
||
|
||
ret = lp5812_write(chip, led_base_reg + led_number, val);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_multicolor_brightness(struct lp5812_led *led)
|
||
{
|
||
struct lp5812_chip *chip = led->chip;
|
||
int ret, i;
|
||
|
||
guard(mutex)(&chip->lock);
|
||
for (i = 0; i < led->mc_cdev.num_colors; i++) {
|
||
ret = lp5812_manual_dc_pwm_control(chip, led->mc_cdev.subled_info[i].channel,
|
||
led->mc_cdev.subled_info[i].brightness,
|
||
LP5812_DIMMING_PWM);
|
||
if (ret)
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int lp5812_led_brightness(struct lp5812_led *led)
|
||
{
|
||
struct lp5812_chip *chip = led->chip;
|
||
struct lp5812_led_config *led_cfg;
|
||
int ret;
|
||
|
||
led_cfg = &chip->led_config[led->chan_nr];
|
||
|
||
guard(mutex)(&chip->lock);
|
||
ret = lp5812_manual_dc_pwm_control(chip, led_cfg->led_id[0],
|
||
led->brightness, LP5812_DIMMING_PWM);
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_set_brightness(struct led_classdev *cdev,
|
||
enum led_brightness brightness)
|
||
{
|
||
struct lp5812_led *led = container_of(cdev, struct lp5812_led, cdev);
|
||
|
||
led->brightness = (u8)brightness;
|
||
|
||
return lp5812_led_brightness(led);
|
||
}
|
||
|
||
static int lp5812_set_mc_brightness(struct led_classdev *cdev,
|
||
enum led_brightness brightness)
|
||
{
|
||
struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
|
||
struct lp5812_led *led = container_of(mc_dev, struct lp5812_led, mc_cdev);
|
||
|
||
led_mc_calc_color_components(&led->mc_cdev, brightness);
|
||
|
||
return lp5812_multicolor_brightness(led);
|
||
}
|
||
|
||
static int lp5812_init_led(struct lp5812_led *led, struct lp5812_chip *chip, int chan)
|
||
{
|
||
struct device *dev = &chip->client->dev;
|
||
struct mc_subled *mc_led_info;
|
||
struct led_classdev *led_cdev;
|
||
int i, ret;
|
||
|
||
if (chip->led_config[chan].name) {
|
||
led->cdev.name = chip->led_config[chan].name;
|
||
} else {
|
||
led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:channel%d",
|
||
chip->label ? : chip->client->name, chan);
|
||
if (!led->cdev.name)
|
||
return -ENOMEM;
|
||
}
|
||
|
||
if (!chip->led_config[chan].is_sc_led) {
|
||
mc_led_info = devm_kcalloc(dev, chip->led_config[chan].num_colors,
|
||
sizeof(*mc_led_info), GFP_KERNEL);
|
||
if (!mc_led_info)
|
||
return -ENOMEM;
|
||
|
||
led_cdev = &led->mc_cdev.led_cdev;
|
||
led_cdev->name = led->cdev.name;
|
||
led_cdev->brightness_set_blocking = lp5812_set_mc_brightness;
|
||
led->mc_cdev.num_colors = chip->led_config[chan].num_colors;
|
||
|
||
for (i = 0; i < led->mc_cdev.num_colors; i++) {
|
||
mc_led_info[i].color_index = chip->led_config[chan].color_id[i];
|
||
mc_led_info[i].channel = chip->led_config[chan].led_id[i];
|
||
}
|
||
|
||
led->mc_cdev.subled_info = mc_led_info;
|
||
} else {
|
||
led->cdev.brightness_set_blocking = lp5812_set_brightness;
|
||
}
|
||
|
||
led->chan_nr = chan;
|
||
|
||
if (chip->led_config[chan].is_sc_led) {
|
||
ret = devm_led_classdev_register(dev, &led->cdev);
|
||
if (ret == 0)
|
||
led->cdev.dev->platform_data = led;
|
||
} else {
|
||
ret = devm_led_classdev_multicolor_register(dev, &led->mc_cdev);
|
||
if (ret == 0)
|
||
led->mc_cdev.led_cdev.dev->platform_data = led;
|
||
}
|
||
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_register_leds(struct lp5812_led *leds, struct lp5812_chip *chip)
|
||
{
|
||
struct lp5812_led *led;
|
||
int num_channels = chip->num_channels;
|
||
u8 reg_val;
|
||
u16 reg;
|
||
int ret, i, j;
|
||
|
||
for (i = 0; i < num_channels; i++) {
|
||
led = &leds[i];
|
||
ret = lp5812_init_led(led, chip, i);
|
||
if (ret)
|
||
goto err_init_led;
|
||
|
||
led->chip = chip;
|
||
|
||
for (j = 0; j < chip->led_config[i].num_colors; j++) {
|
||
ret = lp5812_write(chip,
|
||
LP5812_AUTO_DC_BASE + chip->led_config[i].led_id[j],
|
||
chip->led_config[i].max_current[j]);
|
||
if (ret)
|
||
goto err_init_led;
|
||
|
||
ret = lp5812_manual_dc_pwm_control(chip, chip->led_config[i].led_id[j],
|
||
chip->led_config[i].max_current[j],
|
||
LP5812_DIMMING_ANALOG);
|
||
if (ret)
|
||
goto err_init_led;
|
||
|
||
ret = lp5812_set_led_mode(chip, chip->led_config[i].led_id[j],
|
||
LP5812_MODE_MANUAL);
|
||
if (ret)
|
||
goto err_init_led;
|
||
|
||
reg = (chip->led_config[i].led_id[j] < LP5812_NUMBER_LED_IN_REG) ?
|
||
LP5812_LED_EN_1 : LP5812_LED_EN_2;
|
||
|
||
ret = lp5812_read(chip, reg, ®_val);
|
||
if (ret)
|
||
goto err_init_led;
|
||
|
||
reg_val |= (LP5812_ENABLE << (chip->led_config[i].led_id[j] %
|
||
LP5812_NUMBER_LED_IN_REG));
|
||
|
||
ret = lp5812_write(chip, reg, reg_val);
|
||
if (ret)
|
||
goto err_init_led;
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
|
||
err_init_led:
|
||
return ret;
|
||
}
|
||
|
||
static int lp5812_init_device(struct lp5812_chip *chip)
|
||
{
|
||
int ret;
|
||
|
||
usleep_range(LP5812_WAIT_DEVICE_STABLE_MIN, LP5812_WAIT_DEVICE_STABLE_MAX);
|
||
|
||
ret = lp5812_write(chip, LP5812_REG_ENABLE, LP5812_ENABLE);
|
||
if (ret) {
|
||
dev_err(&chip->client->dev, "failed to enable LP5812 device\n");
|
||
return ret;
|
||
}
|
||
|
||
ret = lp5812_write(chip, LP5812_DEV_CONFIG12, LP5812_LSD_LOD_START_UP);
|
||
if (ret) {
|
||
dev_err(&chip->client->dev, "failed to configure device safety thresholds\n");
|
||
return ret;
|
||
}
|
||
|
||
ret = parse_drive_mode(chip, chip->scan_mode);
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = lp5812_set_drive_mode_scan_order(chip);
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = lp5812_update_regs_config(chip);
|
||
if (ret) {
|
||
dev_err(&chip->client->dev, "failed to apply configuration updates\n");
|
||
return ret;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void lp5812_deinit_device(struct lp5812_chip *chip)
|
||
{
|
||
lp5812_write(chip, LP5812_LED_EN_1, LP5812_DISABLE);
|
||
lp5812_write(chip, LP5812_LED_EN_2, LP5812_DISABLE);
|
||
lp5812_write(chip, LP5812_REG_ENABLE, LP5812_DISABLE);
|
||
}
|
||
|
||
static int lp5812_parse_led_channel(struct device_node *np,
|
||
struct lp5812_led_config *cfg,
|
||
int color_number)
|
||
{
|
||
int color_id, reg, ret;
|
||
u32 max_cur;
|
||
|
||
ret = of_property_read_u32(np, "reg", ®);
|
||
if (ret)
|
||
return ret;
|
||
|
||
cfg->led_id[color_number] = reg;
|
||
|
||
ret = of_property_read_u32(np, "led-max-microamp", &max_cur);
|
||
if (ret)
|
||
max_cur = 0;
|
||
/* Convert microamps to driver units */
|
||
cfg->max_current[color_number] = max_cur / 100;
|
||
|
||
ret = of_property_read_u32(np, "color", &color_id);
|
||
if (ret)
|
||
color_id = 0;
|
||
cfg->color_id[color_number] = color_id;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int lp5812_parse_led(struct device_node *np,
|
||
struct lp5812_led_config *cfg,
|
||
int led_index)
|
||
{
|
||
int num_colors, ret;
|
||
|
||
of_property_read_string(np, "label", &cfg[led_index].name);
|
||
|
||
ret = of_property_read_u32(np, "reg", &cfg[led_index].chan_nr);
|
||
if (ret)
|
||
return ret;
|
||
|
||
num_colors = 0;
|
||
for_each_available_child_of_node_scoped(np, child) {
|
||
ret = lp5812_parse_led_channel(child, &cfg[led_index], num_colors);
|
||
if (ret)
|
||
return ret;
|
||
|
||
num_colors++;
|
||
}
|
||
|
||
if (num_colors == 0) {
|
||
ret = lp5812_parse_led_channel(np, &cfg[led_index], 0);
|
||
if (ret)
|
||
return ret;
|
||
|
||
num_colors = 1;
|
||
cfg[led_index].is_sc_led = true;
|
||
} else {
|
||
cfg[led_index].is_sc_led = false;
|
||
}
|
||
|
||
cfg[led_index].num_colors = num_colors;
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int lp5812_of_probe(struct device *dev,
|
||
struct device_node *np,
|
||
struct lp5812_chip *chip)
|
||
{
|
||
struct lp5812_led_config *cfg;
|
||
int num_channels, i = 0, ret;
|
||
|
||
num_channels = of_get_available_child_count(np);
|
||
if (num_channels == 0) {
|
||
dev_err(dev, "no LED channels\n");
|
||
return -EINVAL;
|
||
}
|
||
|
||
cfg = devm_kcalloc(dev, num_channels, sizeof(*cfg), GFP_KERNEL);
|
||
if (!cfg)
|
||
return -ENOMEM;
|
||
|
||
chip->led_config = &cfg[0];
|
||
chip->num_channels = num_channels;
|
||
|
||
for_each_available_child_of_node_scoped(np, child) {
|
||
ret = lp5812_parse_led(child, cfg, i);
|
||
if (ret)
|
||
return -EINVAL;
|
||
i++;
|
||
}
|
||
|
||
ret = of_property_read_string(np, "ti,scan-mode", &chip->scan_mode);
|
||
if (ret)
|
||
chip->scan_mode = LP5812_MODE_DIRECT_NAME;
|
||
|
||
of_property_read_string(np, "label", &chip->label);
|
||
|
||
return 0;
|
||
}
|
||
|
||
static int lp5812_probe(struct i2c_client *client)
|
||
{
|
||
struct lp5812_chip *chip;
|
||
struct device_node *np = dev_of_node(&client->dev);
|
||
struct lp5812_led *leds;
|
||
int ret;
|
||
|
||
if (!np)
|
||
return -EINVAL;
|
||
|
||
chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
|
||
if (!chip)
|
||
return -ENOMEM;
|
||
|
||
ret = lp5812_of_probe(&client->dev, np, chip);
|
||
if (ret)
|
||
return ret;
|
||
|
||
leds = devm_kcalloc(&client->dev, chip->num_channels, sizeof(*leds), GFP_KERNEL);
|
||
if (!leds)
|
||
return -ENOMEM;
|
||
|
||
chip->client = client;
|
||
mutex_init(&chip->lock);
|
||
i2c_set_clientdata(client, chip);
|
||
|
||
ret = lp5812_init_device(chip);
|
||
if (ret)
|
||
return ret;
|
||
|
||
ret = lp5812_register_leds(leds, chip);
|
||
if (ret)
|
||
goto err_out;
|
||
|
||
return 0;
|
||
|
||
err_out:
|
||
lp5812_deinit_device(chip);
|
||
return ret;
|
||
}
|
||
|
||
static void lp5812_remove(struct i2c_client *client)
|
||
{
|
||
struct lp5812_chip *chip = i2c_get_clientdata(client);
|
||
|
||
lp5812_deinit_device(chip);
|
||
}
|
||
|
||
static const struct of_device_id of_lp5812_match[] = {
|
||
{ .compatible = "ti,lp5812" },
|
||
{ /* sentinel */ }
|
||
};
|
||
MODULE_DEVICE_TABLE(of, of_lp5812_match);
|
||
|
||
static struct i2c_driver lp5812_driver = {
|
||
.driver = {
|
||
.name = "lp5812",
|
||
.of_match_table = of_lp5812_match,
|
||
},
|
||
.probe = lp5812_probe,
|
||
.remove = lp5812_remove,
|
||
};
|
||
module_i2c_driver(lp5812_driver);
|
||
|
||
MODULE_DESCRIPTION("Texas Instruments LP5812 LED Driver");
|
||
MODULE_AUTHOR("Jared Zhou <jared-zhou@ti.com>");
|
||
MODULE_LICENSE("GPL");
|