diff options
Diffstat (limited to 'drivers/leds/leds-lp5521.c')
| -rw-r--r-- | drivers/leds/leds-lp5521.c | 946 |
1 files changed, 371 insertions, 575 deletions
diff --git a/drivers/leds/leds-lp5521.c b/drivers/leds/leds-lp5521.c index 3782f31f06d..8ca197af286 100644 --- a/drivers/leds/leds-lp5521.c +++ b/drivers/leds/leds-lp5521.c @@ -2,8 +2,10 @@ * LP5521 LED chip driver. * * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 Texas Instruments * * Contact: Samu Onkalo <samu.p.onkalo@nokia.com> + * Milo(Woogyom) Kim <milo.kim@ti.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -20,33 +22,21 @@ * 02110-1301 USA */ -#include <linux/module.h> -#include <linux/init.h> -#include <linux/i2c.h> -#include <linux/mutex.h> -#include <linux/gpio.h> -#include <linux/interrupt.h> #include <linux/delay.h> -#include <linux/ctype.h> -#include <linux/spinlock.h> -#include <linux/wait.h> +#include <linux/firmware.h> +#include <linux/i2c.h> #include <linux/leds.h> -#include <linux/leds-lp5521.h> -#include <linux/workqueue.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/platform_data/leds-lp55xx.h> #include <linux/slab.h> +#include <linux/of.h> -#define LP5521_PROGRAM_LENGTH 32 /* in bytes */ - -#define LP5521_MAX_LEDS 3 /* Maximum number of LEDs */ -#define LP5521_MAX_ENGINES 3 /* Maximum number of engines */ +#include "leds-lp55xx-common.h" -#define LP5521_ENG_MASK_BASE 0x30 /* 00110000 */ -#define LP5521_ENG_STATUS_MASK 0x07 /* 00000111 */ - -#define LP5521_CMD_LOAD 0x15 /* 00010101 */ -#define LP5521_CMD_RUN 0x2a /* 00101010 */ -#define LP5521_CMD_DIRECT 0x3f /* 00111111 */ -#define LP5521_CMD_DISABLED 0x00 /* 00000000 */ +#define LP5521_PROGRAM_LENGTH 32 +#define LP5521_MAX_LEDS 3 +#define LP5521_CMD_DIRECT 0x3F /* Registers */ #define LP5521_REG_ENABLE 0x00 @@ -58,22 +48,14 @@ #define LP5521_REG_G_CURRENT 0x06 #define LP5521_REG_B_CURRENT 0x07 #define LP5521_REG_CONFIG 0x08 -#define LP5521_REG_R_CHANNEL_PC 0x09 -#define LP5521_REG_G_CHANNEL_PC 0x0A -#define LP5521_REG_B_CHANNEL_PC 0x0B #define LP5521_REG_STATUS 0x0C #define LP5521_REG_RESET 0x0D -#define LP5521_REG_GPO 0x0E #define LP5521_REG_R_PROG_MEM 0x10 #define LP5521_REG_G_PROG_MEM 0x30 #define LP5521_REG_B_PROG_MEM 0x50 -#define LP5521_PROG_MEM_BASE LP5521_REG_R_PROG_MEM -#define LP5521_PROG_MEM_SIZE 0x20 - /* Base register to set LED current */ #define LP5521_REG_LED_CURRENT_BASE LP5521_REG_R_CURRENT - /* Base register to set the brightness */ #define LP5521_REG_LED_PWM_BASE LP5521_REG_R_PWM @@ -81,360 +63,335 @@ #define LP5521_MASTER_ENABLE 0x40 /* Chip master enable */ #define LP5521_LOGARITHMIC_PWM 0x80 /* Logarithmic PWM adjustment */ #define LP5521_EXEC_RUN 0x2A +#define LP5521_ENABLE_DEFAULT \ + (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM) +#define LP5521_ENABLE_RUN_PROGRAM \ + (LP5521_ENABLE_DEFAULT | LP5521_EXEC_RUN) -/* Bits in CONFIG register */ +/* CONFIG register */ #define LP5521_PWM_HF 0x40 /* PWM: 0 = 256Hz, 1 = 558Hz */ #define LP5521_PWRSAVE_EN 0x20 /* 1 = Power save mode */ #define LP5521_CP_MODE_OFF 0 /* Charge pump (CP) off */ #define LP5521_CP_MODE_BYPASS 8 /* CP forced to bypass mode */ #define LP5521_CP_MODE_1X5 0x10 /* CP forced to 1.5x mode */ #define LP5521_CP_MODE_AUTO 0x18 /* Automatic mode selection */ -#define LP5521_R_TO_BATT 4 /* R out: 0 = CP, 1 = Vbat */ -#define LP5521_CLK_SRC_EXT 0 /* Ext-clk source (CLK_32K) */ -#define LP5521_CLK_INT 1 /* Internal clock */ -#define LP5521_CLK_AUTO 2 /* Automatic clock selection */ +#define LP5521_R_TO_BATT 0x04 /* R out: 0 = CP, 1 = Vbat */ +#define LP5521_CLK_INT 0x01 /* Internal clock */ +#define LP5521_DEFAULT_CFG \ + (LP5521_PWM_HF | LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO) /* Status */ #define LP5521_EXT_CLK_USED 0x08 -struct lp5521_engine { - const struct attribute_group *attributes; - int id; - u8 mode; - u8 prog_page; - u8 engine_mask; -}; - -struct lp5521_led { - int id; - u8 chan_nr; - u8 led_current; - u8 max_current; - struct led_classdev cdev; - struct work_struct brightness_work; - u8 brightness; -}; - -struct lp5521_chip { - struct lp5521_platform_data *pdata; - struct mutex lock; /* Serialize control */ - struct i2c_client *client; - struct lp5521_engine engines[LP5521_MAX_ENGINES]; - struct lp5521_led leds[LP5521_MAX_LEDS]; - u8 num_channels; - u8 num_leds; -}; - -#define cdev_to_led(c) container_of(c, struct lp5521_led, cdev) -#define engine_to_lp5521(eng) container_of((eng), struct lp5521_chip, \ - engines[(eng)->id - 1]) -#define led_to_lp5521(led) container_of((led), struct lp5521_chip, \ - leds[(led)->id]) - -static void lp5521_led_brightness_work(struct work_struct *work); - -static inline int lp5521_write(struct i2c_client *client, u8 reg, u8 value) +/* default R channel current register value */ +#define LP5521_REG_R_CURR_DEFAULT 0xAF + +/* Reset register value */ +#define LP5521_RESET 0xFF + +/* Program Memory Operations */ +#define LP5521_MODE_R_M 0x30 /* Operation Mode Register */ +#define LP5521_MODE_G_M 0x0C +#define LP5521_MODE_B_M 0x03 +#define LP5521_LOAD_R 0x10 +#define LP5521_LOAD_G 0x04 +#define LP5521_LOAD_B 0x01 + +#define LP5521_R_IS_LOADING(mode) \ + ((mode & LP5521_MODE_R_M) == LP5521_LOAD_R) +#define LP5521_G_IS_LOADING(mode) \ + ((mode & LP5521_MODE_G_M) == LP5521_LOAD_G) +#define LP5521_B_IS_LOADING(mode) \ + ((mode & LP5521_MODE_B_M) == LP5521_LOAD_B) + +#define LP5521_EXEC_R_M 0x30 /* Enable Register */ +#define LP5521_EXEC_G_M 0x0C +#define LP5521_EXEC_B_M 0x03 +#define LP5521_EXEC_M 0x3F +#define LP5521_RUN_R 0x20 +#define LP5521_RUN_G 0x08 +#define LP5521_RUN_B 0x02 + +static inline void lp5521_wait_opmode_done(void) { - return i2c_smbus_write_byte_data(client, reg, value); + /* operation mode change needs to be longer than 153 us */ + usleep_range(200, 300); } -static int lp5521_read(struct i2c_client *client, u8 reg, u8 *buf) +static inline void lp5521_wait_enable_done(void) { - s32 ret; - - ret = i2c_smbus_read_byte_data(client, reg); - if (ret < 0) - return -EIO; - - *buf = ret; - return 0; + /* it takes more 488 us to update ENABLE register */ + usleep_range(500, 600); } -static int lp5521_set_engine_mode(struct lp5521_engine *engine, u8 mode) +static void lp5521_set_led_current(struct lp55xx_led *led, u8 led_current) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; - int ret; - u8 engine_state; + led->led_current = led_current; + lp55xx_write(led->chip, LP5521_REG_LED_CURRENT_BASE + led->chan_nr, + led_current); +} - /* Only transition between RUN and DIRECT mode are handled here */ - if (mode == LP5521_CMD_LOAD) - return 0; +static void lp5521_load_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5521_MODE_R_M, + [LP55XX_ENGINE_2] = LP5521_MODE_G_M, + [LP55XX_ENGINE_3] = LP5521_MODE_B_M, + }; + + u8 val[] = { + [LP55XX_ENGINE_1] = LP5521_LOAD_R, + [LP55XX_ENGINE_2] = LP5521_LOAD_G, + [LP55XX_ENGINE_3] = LP5521_LOAD_B, + }; + + lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], val[idx]); + + lp5521_wait_opmode_done(); +} - if (mode == LP5521_CMD_DISABLED) - mode = LP5521_CMD_DIRECT; +static void lp5521_stop_all_engines(struct lp55xx_chip *chip) +{ + lp55xx_write(chip, LP5521_REG_OP_MODE, 0); + lp5521_wait_opmode_done(); +} - ret = lp5521_read(client, LP5521_REG_OP_MODE, &engine_state); +static void lp5521_stop_engine(struct lp55xx_chip *chip) +{ + enum lp55xx_engine_index idx = chip->engine_idx; + u8 mask[] = { + [LP55XX_ENGINE_1] = LP5521_MODE_R_M, + [LP55XX_ENGINE_2] = LP5521_MODE_G_M, + [LP55XX_ENGINE_3] = LP5521_MODE_B_M, + }; - /* set mode only for this engine */ - engine_state &= ~(engine->engine_mask); - mode &= engine->engine_mask; - engine_state |= mode; - ret |= lp5521_write(client, LP5521_REG_OP_MODE, engine_state); + lp55xx_update_bits(chip, LP5521_REG_OP_MODE, mask[idx], 0); - return ret; + lp5521_wait_opmode_done(); } -static int lp5521_load_program(struct lp5521_engine *eng, const u8 *pattern) +static void lp5521_run_engine(struct lp55xx_chip *chip, bool start) { - struct lp5521_chip *chip = engine_to_lp5521(eng); - struct i2c_client *client = chip->client; int ret; - int addr; u8 mode; + u8 exec; + + /* stop engine */ + if (!start) { + lp5521_stop_engine(chip); + lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); + lp5521_wait_opmode_done(); + return; + } - /* move current engine to direct mode and remember the state */ - ret = lp5521_set_engine_mode(eng, LP5521_CMD_DIRECT); - usleep_range(1000, 10000); - ret |= lp5521_read(client, LP5521_REG_OP_MODE, &mode); + /* + * To run the engine, + * operation mode and enable register should updated at the same time + */ - /* For loading, all the engines to load mode */ - lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); - usleep_range(1000, 10000); - lp5521_write(client, LP5521_REG_OP_MODE, LP5521_CMD_LOAD); - usleep_range(1000, 10000); + ret = lp55xx_read(chip, LP5521_REG_OP_MODE, &mode); + if (ret) + return; - addr = LP5521_PROG_MEM_BASE + eng->prog_page * LP5521_PROG_MEM_SIZE; - i2c_smbus_write_i2c_block_data(client, - addr, - LP5521_PROG_MEM_SIZE, - pattern); + ret = lp55xx_read(chip, LP5521_REG_ENABLE, &exec); + if (ret) + return; - ret |= lp5521_write(client, LP5521_REG_OP_MODE, mode); - return ret; -} + /* change operation mode to RUN only when each engine is loading */ + if (LP5521_R_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_R_M) | LP5521_RUN_R; + exec = (exec & ~LP5521_EXEC_R_M) | LP5521_RUN_R; + } -static int lp5521_set_led_current(struct lp5521_chip *chip, int led, u8 curr) -{ - return lp5521_write(chip->client, - LP5521_REG_LED_CURRENT_BASE + chip->leds[led].chan_nr, - curr); -} + if (LP5521_G_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_G_M) | LP5521_RUN_G; + exec = (exec & ~LP5521_EXEC_G_M) | LP5521_RUN_G; + } -static void lp5521_init_engine(struct lp5521_chip *chip, - const struct attribute_group *attr_group) -{ - int i; - for (i = 0; i < ARRAY_SIZE(chip->engines); i++) { - chip->engines[i].id = i + 1; - chip->engines[i].engine_mask = LP5521_ENG_MASK_BASE >> (i * 2); - chip->engines[i].prog_page = i; - chip->engines[i].attributes = &attr_group[i]; + if (LP5521_B_IS_LOADING(mode)) { + mode = (mode & ~LP5521_MODE_B_M) | LP5521_RUN_B; + exec = (exec & ~LP5521_EXEC_B_M) | LP5521_RUN_B; } + + lp55xx_write(chip, LP5521_REG_OP_MODE, mode); + lp5521_wait_opmode_done(); + + lp55xx_update_bits(chip, LP5521_REG_ENABLE, LP5521_EXEC_M, exec); + lp5521_wait_enable_done(); } -static int lp5521_configure(struct i2c_client *client, - const struct attribute_group *attr_group) +static int lp5521_update_program_memory(struct lp55xx_chip *chip, + const u8 *data, size_t size) { - struct lp5521_chip *chip = i2c_get_clientdata(client); + enum lp55xx_engine_index idx = chip->engine_idx; + u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; + u8 addr[] = { + [LP55XX_ENGINE_1] = LP5521_REG_R_PROG_MEM, + [LP55XX_ENGINE_2] = LP5521_REG_G_PROG_MEM, + [LP55XX_ENGINE_3] = LP5521_REG_B_PROG_MEM, + }; + unsigned cmd; + char c[3]; + int nrchars; int ret; + int offset = 0; + int i = 0; - lp5521_init_engine(chip, attr_group); - - lp5521_write(client, LP5521_REG_RESET, 0xff); + while ((offset < size - 1) && (i < LP5521_PROGRAM_LENGTH)) { + /* separate sscanfs because length is working only for %s */ + ret = sscanf(data + offset, "%2s%n ", c, &nrchars); + if (ret != 1) + goto err; - usleep_range(10000, 20000); + ret = sscanf(c, "%2x", &cmd); + if (ret != 1) + goto err; - /* Set all PWMs to direct control mode */ - ret = lp5521_write(client, LP5521_REG_OP_MODE, 0x3F); + pattern[i] = (u8)cmd; + offset += nrchars; + i++; + } - /* Enable auto-powersave, set charge pump to auto, red to battery */ - ret |= lp5521_write(client, LP5521_REG_CONFIG, - LP5521_PWRSAVE_EN | LP5521_CP_MODE_AUTO | LP5521_R_TO_BATT); + /* Each instruction is 16bit long. Check that length is even */ + if (i % 2) + goto err; - /* Initialize all channels PWM to zero -> leds off */ - ret |= lp5521_write(client, LP5521_REG_R_PWM, 0); - ret |= lp5521_write(client, LP5521_REG_G_PWM, 0); - ret |= lp5521_write(client, LP5521_REG_B_PWM, 0); + for (i = 0; i < LP5521_PROGRAM_LENGTH; i++) { + ret = lp55xx_write(chip, addr[idx] + i, pattern[i]); + if (ret) + return -EINVAL; + } - /* Set engines are set to run state when OP_MODE enables engines */ - ret |= lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM | - LP5521_EXEC_RUN); - /* enable takes 500us */ - usleep_range(500, 20000); + return size; - return ret; +err: + dev_err(&chip->cl->dev, "wrong pattern format\n"); + return -EINVAL; } -static int lp5521_run_selftest(struct lp5521_chip *chip, char *buf) +static void lp5521_firmware_loaded(struct lp55xx_chip *chip) { - int ret; - u8 status; + const struct firmware *fw = chip->fw; - ret = lp5521_read(chip->client, LP5521_REG_STATUS, &status); - if (ret < 0) - return ret; + if (fw->size > LP5521_PROGRAM_LENGTH) { + dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n", + fw->size); + return; + } - /* Check that ext clock is really in use if requested */ - if (chip->pdata && chip->pdata->clock_mode == LP5521_CLOCK_EXT) - if ((status & LP5521_EXT_CLK_USED) == 0) - return -EIO; - return 0; -} + /* + * Program momery sequence + * 1) set engine mode to "LOAD" + * 2) write firmware data into program memory + */ -static void lp5521_set_brightness(struct led_classdev *cdev, - enum led_brightness brightness) -{ - struct lp5521_led *led = cdev_to_led(cdev); - led->brightness = (u8)brightness; - schedule_work(&led->brightness_work); + lp5521_load_engine(chip); + lp5521_update_program_memory(chip, fw->data, fw->size); } -static void lp5521_led_brightness_work(struct work_struct *work) +static int lp5521_post_init_device(struct lp55xx_chip *chip) { - struct lp5521_led *led = container_of(work, - struct lp5521_led, - brightness_work); - struct lp5521_chip *chip = led_to_lp5521(led); - struct i2c_client *client = chip->client; + int ret; + u8 val; + + /* + * Make sure that the chip is reset by reading back the r channel + * current reg. This is dummy read is required on some platforms - + * otherwise further access to the R G B channels in the + * LP5521_REG_ENABLE register will not have any effect - strange! + */ + ret = lp55xx_read(chip, LP5521_REG_R_CURRENT, &val); + if (ret) { + dev_err(&chip->cl->dev, "error in resetting chip\n"); + return ret; + } + if (val != LP5521_REG_R_CURR_DEFAULT) { + dev_err(&chip->cl->dev, + "unexpected data in register (expected 0x%x got 0x%x)\n", + LP5521_REG_R_CURR_DEFAULT, val); + ret = -EINVAL; + return ret; + } + usleep_range(10000, 20000); - mutex_lock(&chip->lock); - lp5521_write(client, LP5521_REG_LED_PWM_BASE + led->chan_nr, - led->brightness); - mutex_unlock(&chip->lock); -} + /* Set all PWMs to direct control mode */ + ret = lp55xx_write(chip, LP5521_REG_OP_MODE, LP5521_CMD_DIRECT); -/* Detect the chip by setting its ENABLE register and reading it back. */ -static int lp5521_detect(struct i2c_client *client) -{ - int ret; - u8 buf; + /* Update configuration for the clock setting */ + val = LP5521_DEFAULT_CFG; + if (!lp55xx_is_extclk_used(chip)) + val |= LP5521_CLK_INT; - ret = lp5521_write(client, LP5521_REG_ENABLE, - LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM); + ret = lp55xx_write(chip, LP5521_REG_CONFIG, val); if (ret) return ret; - usleep_range(1000, 10000); - ret = lp5521_read(client, LP5521_REG_ENABLE, &buf); + + /* Initialize all channels PWM to zero -> leds off */ + lp55xx_write(chip, LP5521_REG_R_PWM, 0); + lp55xx_write(chip, LP5521_REG_G_PWM, 0); + lp55xx_write(chip, LP5521_REG_B_PWM, 0); + + /* Set engines are set to run state when OP_MODE enables engines */ + ret = lp55xx_write(chip, LP5521_REG_ENABLE, LP5521_ENABLE_RUN_PROGRAM); if (ret) return ret; - if (buf != (LP5521_MASTER_ENABLE | LP5521_LOGARITHMIC_PWM)) - return -ENODEV; + + lp5521_wait_enable_done(); return 0; } -/* Set engine mode and create appropriate sysfs attributes, if required. */ -static int lp5521_set_mode(struct lp5521_engine *engine, u8 mode) +static int lp5521_run_selftest(struct lp55xx_chip *chip, char *buf) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; - struct device *dev = &client->dev; - int ret = 0; - - /* if in that mode already do nothing, except for run */ - if (mode == engine->mode && mode != LP5521_CMD_RUN) - return 0; - - if (mode == LP5521_CMD_RUN) { - ret = lp5521_set_engine_mode(engine, LP5521_CMD_RUN); - } else if (mode == LP5521_CMD_LOAD) { - lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); - lp5521_set_engine_mode(engine, LP5521_CMD_LOAD); + struct lp55xx_platform_data *pdata = chip->pdata; + int ret; + u8 status; - ret = sysfs_create_group(&dev->kobj, engine->attributes); - if (ret) - return ret; - } else if (mode == LP5521_CMD_DISABLED) { - lp5521_set_engine_mode(engine, LP5521_CMD_DISABLED); - } + ret = lp55xx_read(chip, LP5521_REG_STATUS, &status); + if (ret < 0) + return ret; - /* remove load attribute from sysfs if not in load mode */ - if (engine->mode == LP5521_CMD_LOAD && mode != LP5521_CMD_LOAD) - sysfs_remove_group(&dev->kobj, engine->attributes); + if (pdata->clock_mode != LP55XX_CLOCK_EXT) + return 0; - engine->mode = mode; + /* Check that ext clock is really in use if requested */ + if ((status & LP5521_EXT_CLK_USED) == 0) + return -EIO; - return ret; + return 0; } -static int lp5521_do_store_load(struct lp5521_engine *engine, - const char *buf, size_t len) +static void lp5521_led_brightness_work(struct work_struct *work) { - struct lp5521_chip *chip = engine_to_lp5521(engine); - struct i2c_client *client = chip->client; - int ret, nrchars, offset = 0, i = 0; - char c[3]; - unsigned cmd; - u8 pattern[LP5521_PROGRAM_LENGTH] = {0}; - - while ((offset < len - 1) && (i < LP5521_PROGRAM_LENGTH)) { - /* separate sscanfs because length is working only for %s */ - ret = sscanf(buf + offset, "%2s%n ", c, &nrchars); - ret = sscanf(c, "%2x", &cmd); - if (ret != 1) - goto fail; - pattern[i] = (u8)cmd; - - offset += nrchars; - i++; - } - - /* Each instruction is 16bit long. Check that length is even */ - if (i % 2) - goto fail; + struct lp55xx_led *led = container_of(work, struct lp55xx_led, + brightness_work); + struct lp55xx_chip *chip = led->chip; mutex_lock(&chip->lock); - ret = lp5521_load_program(engine, pattern); + lp55xx_write(chip, LP5521_REG_LED_PWM_BASE + led->chan_nr, + led->brightness); mutex_unlock(&chip->lock); - - if (ret) { - dev_err(&client->dev, "failed loading pattern\n"); - return ret; - } - - return len; -fail: - dev_err(&client->dev, "wrong pattern format\n"); - return -EINVAL; -} - -static ssize_t store_engine_load(struct device *dev, - struct device_attribute *attr, - const char *buf, size_t len, int nr) -{ - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - return lp5521_do_store_load(&chip->engines[nr - 1], buf, len); } -#define store_load(nr) \ -static ssize_t store_engine##nr##_load(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_load(dev, attr, buf, len, nr); \ -} -store_load(1) -store_load(2) -store_load(3) - static ssize_t show_engine_mode(struct device *dev, struct device_attribute *attr, char *buf, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - switch (chip->engines[nr - 1].mode) { - case LP5521_CMD_RUN: + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + enum lp55xx_engine_mode mode = chip->engines[nr - 1].mode; + + switch (mode) { + case LP55XX_ENGINE_RUN: return sprintf(buf, "run\n"); - case LP5521_CMD_LOAD: + case LP55XX_ENGINE_LOAD: return sprintf(buf, "load\n"); - case LP5521_CMD_DISABLED: - return sprintf(buf, "disabled\n"); + case LP55XX_ENGINE_DISABLED: default: return sprintf(buf, "disabled\n"); } } - -#define show_mode(nr) \ -static ssize_t show_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - char *buf) \ -{ \ - return show_engine_mode(dev, attr, buf, nr); \ -} show_mode(1) show_mode(2) show_mode(3) @@ -443,141 +400,88 @@ static ssize_t store_engine_mode(struct device *dev, struct device_attribute *attr, const char *buf, size_t len, int nr) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); - struct lp5521_engine *engine = &chip->engines[nr - 1]; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + struct lp55xx_engine *engine = &chip->engines[nr - 1]; + mutex_lock(&chip->lock); - if (!strncmp(buf, "run", 3)) - lp5521_set_mode(engine, LP5521_CMD_RUN); - else if (!strncmp(buf, "load", 4)) - lp5521_set_mode(engine, LP5521_CMD_LOAD); - else if (!strncmp(buf, "disabled", 8)) - lp5521_set_mode(engine, LP5521_CMD_DISABLED); + chip->engine_idx = nr; + + if (!strncmp(buf, "run", 3)) { + lp5521_run_engine(chip, true); + engine->mode = LP55XX_ENGINE_RUN; + } else if (!strncmp(buf, "load", 4)) { + lp5521_stop_engine(chip); + lp5521_load_engine(chip); + engine->mode = LP55XX_ENGINE_LOAD; + } else if (!strncmp(buf, "disabled", 8)) { + lp5521_stop_engine(chip); + engine->mode = LP55XX_ENGINE_DISABLED; + } mutex_unlock(&chip->lock); - return len; -} -#define store_mode(nr) \ -static ssize_t store_engine##nr##_mode(struct device *dev, \ - struct device_attribute *attr, \ - const char *buf, size_t len) \ -{ \ - return store_engine_mode(dev, attr, buf, len, nr); \ + return len; } store_mode(1) store_mode(2) store_mode(3) -static ssize_t show_max_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->max_current); -} - -static ssize_t show_current(struct device *dev, - struct device_attribute *attr, - char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - - return sprintf(buf, "%d\n", led->led_current); -} - -static ssize_t store_current(struct device *dev, +static ssize_t store_engine_load(struct device *dev, struct device_attribute *attr, - const char *buf, size_t len) + const char *buf, size_t len, int nr) { - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct lp5521_led *led = cdev_to_led(led_cdev); - struct lp5521_chip *chip = led_to_lp5521(led); - ssize_t ret; - unsigned long curr; - - if (strict_strtoul(buf, 0, &curr)) - return -EINVAL; - - if (curr > led->max_current) - return -EINVAL; + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; + int ret; mutex_lock(&chip->lock); - ret = lp5521_set_led_current(chip, led->id, curr); - mutex_unlock(&chip->lock); - if (ret < 0) - return ret; + chip->engine_idx = nr; + lp5521_load_engine(chip); + ret = lp5521_update_program_memory(chip, buf, len); - led->led_current = (u8)curr; + mutex_unlock(&chip->lock); - return len; + return ret; } +store_load(1) +store_load(2) +store_load(3) static ssize_t lp5521_selftest(struct device *dev, struct device_attribute *attr, char *buf) { - struct i2c_client *client = to_i2c_client(dev); - struct lp5521_chip *chip = i2c_get_clientdata(client); + struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev)); + struct lp55xx_chip *chip = led->chip; int ret; mutex_lock(&chip->lock); ret = lp5521_run_selftest(chip, buf); mutex_unlock(&chip->lock); - return sprintf(buf, "%s\n", ret ? "FAIL" : "OK"); -} - -/* led class device attributes */ -static DEVICE_ATTR(led_current, S_IRUGO | S_IWUGO, show_current, store_current); -static DEVICE_ATTR(max_current, S_IRUGO , show_max_current, NULL); -static struct attribute *lp5521_led_attributes[] = { - &dev_attr_led_current.attr, - &dev_attr_max_current.attr, - NULL, -}; - -static struct attribute_group lp5521_led_attribute_group = { - .attrs = lp5521_led_attributes -}; + return scnprintf(buf, PAGE_SIZE, "%s\n", ret ? "FAIL" : "OK"); +} /* device attributes */ -static DEVICE_ATTR(engine1_mode, S_IRUGO | S_IWUGO, - show_engine1_mode, store_engine1_mode); -static DEVICE_ATTR(engine2_mode, S_IRUGO | S_IWUGO, - show_engine2_mode, store_engine2_mode); -static DEVICE_ATTR(engine3_mode, S_IRUGO | S_IWUGO, - show_engine3_mode, store_engine3_mode); -static DEVICE_ATTR(engine1_load, S_IWUGO, NULL, store_engine1_load); -static DEVICE_ATTR(engine2_load, S_IWUGO, NULL, store_engine2_load); -static DEVICE_ATTR(engine3_load, S_IWUGO, NULL, store_engine3_load); -static DEVICE_ATTR(selftest, S_IRUGO, lp5521_selftest, NULL); +static LP55XX_DEV_ATTR_RW(engine1_mode, show_engine1_mode, store_engine1_mode); +static LP55XX_DEV_ATTR_RW(engine2_mode, show_engine2_mode, store_engine2_mode); +static LP55XX_DEV_ATTR_RW(engine3_mode, show_engine3_mode, store_engine3_mode); +static LP55XX_DEV_ATTR_WO(engine1_load, store_engine1_load); +static LP55XX_DEV_ATTR_WO(engine2_load, store_engine2_load); +static LP55XX_DEV_ATTR_WO(engine3_load, store_engine3_load); +static LP55XX_DEV_ATTR_RO(selftest, lp5521_selftest); static struct attribute *lp5521_attributes[] = { &dev_attr_engine1_mode.attr, &dev_attr_engine2_mode.attr, &dev_attr_engine3_mode.attr, - &dev_attr_selftest.attr, - NULL -}; - -static struct attribute *lp5521_engine1_attributes[] = { &dev_attr_engine1_load.attr, - NULL -}; - -static struct attribute *lp5521_engine2_attributes[] = { &dev_attr_engine2_load.attr, - NULL -}; - -static struct attribute *lp5521_engine3_attributes[] = { &dev_attr_engine3_load.attr, + &dev_attr_selftest.attr, NULL }; @@ -585,199 +489,99 @@ static const struct attribute_group lp5521_group = { .attrs = lp5521_attributes, }; -static const struct attribute_group lp5521_engine_group[] = { - {.attrs = lp5521_engine1_attributes }, - {.attrs = lp5521_engine2_attributes }, - {.attrs = lp5521_engine3_attributes }, +/* Chip specific configurations */ +static struct lp55xx_device_config lp5521_cfg = { + .reset = { + .addr = LP5521_REG_RESET, + .val = LP5521_RESET, + }, + .enable = { + .addr = LP5521_REG_ENABLE, + .val = LP5521_ENABLE_DEFAULT, + }, + .max_channel = LP5521_MAX_LEDS, + .post_init_device = lp5521_post_init_device, + .brightness_work_fn = lp5521_led_brightness_work, + .set_led_current = lp5521_set_led_current, + .firmware_cb = lp5521_firmware_loaded, + .run_engine = lp5521_run_engine, + .dev_attr_group = &lp5521_group, }; -static int lp5521_register_sysfs(struct i2c_client *client) -{ - struct device *dev = &client->dev; - return sysfs_create_group(&dev->kobj, &lp5521_group); -} - -static void lp5521_unregister_sysfs(struct i2c_client *client) -{ - struct lp5521_chip *chip = i2c_get_clientdata(client); - struct device *dev = &client->dev; - int i; - - sysfs_remove_group(&dev->kobj, &lp5521_group); - - for (i = 0; i < ARRAY_SIZE(chip->engines); i++) { - if (chip->engines[i].mode == LP5521_CMD_LOAD) - sysfs_remove_group(&dev->kobj, - chip->engines[i].attributes); - } - - for (i = 0; i < chip->num_leds; i++) - sysfs_remove_group(&chip->leds[i].cdev.dev->kobj, - &lp5521_led_attribute_group); -} - -static int __init lp5521_init_led(struct lp5521_led *led, - struct i2c_client *client, - int chan, struct lp5521_platform_data *pdata) -{ - struct device *dev = &client->dev; - char name[32]; - int res; - - if (chan >= LP5521_MAX_LEDS) - return -EINVAL; - - if (pdata->led_config[chan].led_current == 0) - return 0; - - led->led_current = pdata->led_config[chan].led_current; - led->max_current = pdata->led_config[chan].max_current; - led->chan_nr = pdata->led_config[chan].chan_nr; - - if (led->chan_nr >= LP5521_MAX_LEDS) { - dev_err(dev, "Use channel numbers between 0 and %d\n", - LP5521_MAX_LEDS - 1); - return -EINVAL; - } - - snprintf(name, sizeof(name), "%s:channel%d", client->name, chan); - led->cdev.brightness_set = lp5521_set_brightness; - led->cdev.name = name; - res = led_classdev_register(dev, &led->cdev); - if (res < 0) { - dev_err(dev, "couldn't register led on channel %d\n", chan); - return res; - } - - res = sysfs_create_group(&led->cdev.dev->kobj, - &lp5521_led_attribute_group); - if (res < 0) { - dev_err(dev, "couldn't register current attribute\n"); - led_classdev_unregister(&led->cdev); - return res; - } - return 0; -} - static int lp5521_probe(struct i2c_client *client, const struct i2c_device_id *id) { - struct lp5521_chip *chip; - struct lp5521_platform_data *pdata; - int ret, i, led; + int ret; + struct lp55xx_chip *chip; + struct lp55xx_led *led; + struct lp55xx_platform_data *pdata; + struct device_node *np = client->dev.of_node; + + if (!dev_get_platdata(&client->dev)) { + if (np) { + ret = lp55xx_of_populate_pdata(&client->dev, np); + if (ret < 0) + return ret; + } else { + dev_err(&client->dev, "no platform data\n"); + return -EINVAL; + } + } + pdata = dev_get_platdata(&client->dev); - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; - i2c_set_clientdata(client, chip); - chip->client = client; - - pdata = client->dev.platform_data; + led = devm_kzalloc(&client->dev, + sizeof(*led) * pdata->num_channels, GFP_KERNEL); + if (!led) + return -ENOMEM; - if (!pdata) { - dev_err(&client->dev, "no platform data\n"); - ret = -EINVAL; - goto fail1; - } + chip->cl = client; + chip->pdata = pdata; + chip->cfg = &lp5521_cfg; mutex_init(&chip->lock); - chip->pdata = pdata; - - if (pdata->setup_resources) { - ret = pdata->setup_resources(); - if (ret < 0) - goto fail1; - } - - if (pdata->enable) { - pdata->enable(0); - usleep_range(1000, 10000); - pdata->enable(1); - usleep_range(1000, 10000); /* Spec says min 500us */ - } - - ret = lp5521_detect(client); + i2c_set_clientdata(client, led); - if (ret) { - dev_err(&client->dev, "Chip not found\n"); - goto fail2; - } + ret = lp55xx_init_device(chip); + if (ret) + goto err_init; dev_info(&client->dev, "%s programmable led chip found\n", id->name); - ret = lp5521_configure(client, lp5521_engine_group); - if (ret < 0) { - dev_err(&client->dev, "error configuring chip\n"); - goto fail2; - } - - /* Initialize leds */ - chip->num_channels = pdata->num_channels; - chip->num_leds = 0; - led = 0; - for (i = 0; i < pdata->num_channels; i++) { - /* Do not initialize channels that are not connected */ - if (pdata->led_config[i].led_current == 0) - continue; - - ret = lp5521_init_led(&chip->leds[led], client, i, pdata); - if (ret) { - dev_err(&client->dev, "error initializing leds\n"); - goto fail3; - } - chip->num_leds++; - - chip->leds[led].id = led; - /* Set initial LED current */ - lp5521_set_led_current(chip, led, - chip->leds[led].led_current); - - INIT_WORK(&(chip->leds[led].brightness_work), - lp5521_led_brightness_work); - - led++; - } + ret = lp55xx_register_leds(led, chip); + if (ret) + goto err_register_leds; - ret = lp5521_register_sysfs(client); + ret = lp55xx_register_sysfs(chip); if (ret) { dev_err(&client->dev, "registering sysfs failed\n"); - goto fail3; - } - return ret; -fail3: - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); + goto err_register_sysfs; } -fail2: - if (pdata->enable) - pdata->enable(0); - if (pdata->release_resources) - pdata->release_resources(); -fail1: - kfree(chip); + + return 0; + +err_register_sysfs: + lp55xx_unregister_leds(led, chip); +err_register_leds: + lp55xx_deinit_device(chip); +err_init: return ret; } static int lp5521_remove(struct i2c_client *client) { - struct lp5521_chip *chip = i2c_get_clientdata(client); - int i; + struct lp55xx_led *led = i2c_get_clientdata(client); + struct lp55xx_chip *chip = led->chip; - lp5521_unregister_sysfs(client); + lp5521_stop_all_engines(chip); + lp55xx_unregister_sysfs(chip); + lp55xx_unregister_leds(led, chip); + lp55xx_deinit_device(chip); - for (i = 0; i < chip->num_leds; i++) { - led_classdev_unregister(&chip->leds[i].cdev); - cancel_work_sync(&chip->leds[i].brightness_work); - } - - if (chip->pdata->enable) - chip->pdata->enable(0); - if (chip->pdata->release_resources) - chip->pdata->release_resources(); - kfree(chip); return 0; } @@ -787,35 +591,27 @@ static const struct i2c_device_id lp5521_id[] = { }; MODULE_DEVICE_TABLE(i2c, lp5521_id); +#ifdef CONFIG_OF +static const struct of_device_id of_lp5521_leds_match[] = { + { .compatible = "national,lp5521", }, + {}, +}; + +MODULE_DEVICE_TABLE(of, of_lp5521_leds_match); +#endif static struct i2c_driver lp5521_driver = { .driver = { .name = "lp5521", + .of_match_table = of_match_ptr(of_lp5521_leds_match), }, .probe = lp5521_probe, .remove = lp5521_remove, .id_table = lp5521_id, }; -static int __init lp5521_init(void) -{ - int ret; - - ret = i2c_add_driver(&lp5521_driver); - - if (ret < 0) - printk(KERN_ALERT "Adding lp5521 driver failed\n"); - - return ret; -} - -static void __exit lp5521_exit(void) -{ - i2c_del_driver(&lp5521_driver); -} - -module_init(lp5521_init); -module_exit(lp5521_exit); +module_i2c_driver(lp5521_driver); MODULE_AUTHOR("Mathias Nyman, Yuri Zaporozhets, Samu Onkalo"); +MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>"); MODULE_DESCRIPTION("LP5521 LED engine"); MODULE_LICENSE("GPL v2"); |
