summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-lp55xx-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/leds/leds-lp55xx-common.c')
-rw-r--r--drivers/leds/leds-lp55xx-common.c779
1 files changed, 705 insertions, 74 deletions
diff --git a/drivers/leds/leds-lp55xx-common.c b/drivers/leds/leds-lp55xx-common.c
index 8e7074f0fee0..e71456a56ab8 100644
--- a/drivers/leds/leds-lp55xx-common.c
+++ b/drivers/leds/leds-lp55xx-common.c
@@ -9,10 +9,13 @@
* Derived from leds-lp5521.c, leds-lp5523.c
*/
+#include <linux/bitfield.h>
+#include <linux/cleanup.h>
#include <linux/clk.h>
#include <linux/delay.h>
#include <linux/firmware.h>
#include <linux/i2c.h>
+#include <linux/iopoll.h>
#include <linux/leds.h>
#include <linux/module.h>
#include <linux/platform_data/leds-lp55xx.h>
@@ -22,6 +25,50 @@
#include "leds-lp55xx-common.h"
+/* OP MODE require at least 153 us to clear regs */
+#define LP55XX_CMD_SLEEP 200
+
+#define LP55xx_PROGRAM_PAGES 16
+#define LP55xx_MAX_PROGRAM_LENGTH (LP55xx_BYTES_PER_PAGE * 4) /* 128 bytes (4 pages) */
+
+/*
+ * Program Memory Operations
+ * Same Mask for each engine for both mode and exec
+ * ENG1 GENMASK(3, 2)
+ * ENG2 GENMASK(5, 4)
+ * ENG3 GENMASK(7, 6)
+ */
+#define LP55xx_MODE_DISABLE_ALL_ENG 0x0
+#define LP55xx_MODE_ENG_MASK GENMASK(1, 0)
+#define LP55xx_MODE_DISABLE_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x0)
+#define LP55xx_MODE_LOAD_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x1)
+#define LP55xx_MODE_RUN_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x2)
+#define LP55xx_MODE_HALT_ENG FIELD_PREP_CONST(LP55xx_MODE_ENG_MASK, 0x3)
+
+#define LP55xx_MODE_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
+#define LP55xx_MODE_ENGn_MASK(n, shift) (LP55xx_MODE_ENG_MASK << LP55xx_MODE_ENGn_SHIFT(n, shift))
+#define LP55xx_MODE_ENGn_GET(n, mode, shift) \
+ (((mode) >> LP55xx_MODE_ENGn_SHIFT(n, shift)) & LP55xx_MODE_ENG_MASK)
+
+#define LP55xx_EXEC_ENG_MASK GENMASK(1, 0)
+#define LP55xx_EXEC_HOLD_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x0)
+#define LP55xx_EXEC_STEP_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x1)
+#define LP55xx_EXEC_RUN_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x2)
+#define LP55xx_EXEC_ONCE_ENG FIELD_PREP_CONST(LP55xx_EXEC_ENG_MASK, 0x3)
+
+#define LP55xx_EXEC_ENGn_SHIFT(n, shift) ((shift) + (2 * (3 - (n))))
+#define LP55xx_EXEC_ENGn_MASK(n, shift) (LP55xx_EXEC_ENG_MASK << LP55xx_EXEC_ENGn_SHIFT(n, shift))
+
+/* Memory Page Selection */
+#define LP55xx_REG_PROG_PAGE_SEL 0x4f
+/* If supported, each ENGINE have an equal amount of pages offset from page 0 */
+#define LP55xx_PAGE_OFFSET(n, pages) (((n) - 1) * (pages))
+
+#define LED_ACTIVE(mux, led) (!!((mux) & (0x0001 << (led))))
+
+/* MASTER FADER common property */
+#define LP55xx_FADER_MAPPING_MASK GENMASK(7, 6)
+
/* External clock rate */
#define LP55XX_CLK_32K 32768
@@ -40,9 +87,259 @@ static struct lp55xx_led *mcled_cdev_to_led(struct led_classdev_mc *mc_cdev)
return container_of(mc_cdev, struct lp55xx_led, mc_cdev);
}
+static void lp55xx_wait_opmode_done(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int __always_unused ret;
+ u8 val;
+
+ /*
+ * Recent chip supports BUSY bit for engine.
+ * Check support by checking if val is not 0.
+ * For legacy device, sleep at least 153 us.
+ */
+ if (cfg->engine_busy.val) {
+ read_poll_timeout(lp55xx_read, ret, !(val & cfg->engine_busy.mask),
+ LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 10, false,
+ chip, cfg->engine_busy.addr, &val);
+ } else {
+ usleep_range(LP55XX_CMD_SLEEP, LP55XX_CMD_SLEEP * 2);
+ }
+}
+
+void lp55xx_stop_all_engine(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, LP55xx_MODE_DISABLE_ALL_ENG);
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_all_engine);
+
+void lp55xx_load_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask, val;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ val = LP55xx_MODE_LOAD_ENG << LP55xx_MODE_ENGn_SHIFT(idx, cfg->reg_op_mode.shift);
+
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, val);
+ lp55xx_wait_opmode_done(chip);
+
+ /* Setup PAGE if supported (pages_per_engine not 0)*/
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine));
+}
+EXPORT_SYMBOL_GPL(lp55xx_load_engine);
+
+int lp55xx_run_engine_common(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mode, exec;
+ int i, ret;
+
+ /* To run the engine, both OP MODE and EXEC needs to be put in RUN mode */
+ ret = lp55xx_read(chip, cfg->reg_op_mode.addr, &mode);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_read(chip, cfg->reg_exec.addr, &exec);
+ if (ret)
+ return ret;
+
+ /* Switch to RUN only for engine that were put in LOAD previously */
+ for (i = LP55XX_ENGINE_1; i <= LP55XX_ENGINE_3; i++) {
+ if (LP55xx_MODE_ENGn_GET(i, mode, cfg->reg_op_mode.shift) != LP55xx_MODE_LOAD_ENG)
+ continue;
+
+ mode &= ~LP55xx_MODE_ENGn_MASK(i, cfg->reg_op_mode.shift);
+ mode |= LP55xx_MODE_RUN_ENG << LP55xx_MODE_ENGn_SHIFT(i, cfg->reg_op_mode.shift);
+ exec &= ~LP55xx_EXEC_ENGn_MASK(i, cfg->reg_exec.shift);
+ exec |= LP55xx_EXEC_RUN_ENG << LP55xx_EXEC_ENGn_SHIFT(i, cfg->reg_exec.shift);
+ }
+
+ lp55xx_write(chip, cfg->reg_op_mode.addr, mode);
+ lp55xx_wait_opmode_done(chip);
+ lp55xx_write(chip, cfg->reg_exec.addr, exec);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(lp55xx_run_engine_common);
+
+int lp55xx_update_program_memory(struct lp55xx_chip *chip,
+ const u8 *data, size_t size)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 pattern[LP55xx_MAX_PROGRAM_LENGTH] = { };
+ u8 start_addr = cfg->prog_mem_base.addr;
+ int page, i = 0, offset = 0;
+ int program_length, ret;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ while ((offset < size - 1) && (i < program_length)) {
+ unsigned int cmd;
+ int nrchars;
+ char c[3];
+
+ /* separate sscanfs because length is working only for %s */
+ ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
+ if (ret != 1)
+ goto err;
+
+ ret = sscanf(c, "%2x", &cmd);
+ if (ret != 1)
+ goto err;
+
+ pattern[i] = (u8)cmd;
+ offset += nrchars;
+ i++;
+ }
+
+ /* Each instruction is 16bit long. Check that length is even */
+ if (i % 2)
+ goto err;
+
+ /*
+ * For legacy LED chip with no page support, engine base address are
+ * one after another at offset of 32.
+ * For LED chip that support page, PAGE is already set in load_engine.
+ */
+ if (!cfg->pages_per_engine)
+ start_addr += LP55xx_BYTES_PER_PAGE * idx;
+
+ for (page = 0; page < program_length / LP55xx_BYTES_PER_PAGE; page++) {
+ /* Write to the next page each 32 bytes (if supported) */
+ if (cfg->pages_per_engine)
+ lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL,
+ LP55xx_PAGE_OFFSET(idx, cfg->pages_per_engine) + page);
+
+ for (i = 0; i < LP55xx_BYTES_PER_PAGE; i++) {
+ ret = lp55xx_write(chip, start_addr + i,
+ pattern[i + (page * LP55xx_BYTES_PER_PAGE)]);
+ if (ret)
+ return -EINVAL;
+ }
+ }
+
+ return size;
+
+err:
+ dev_err(&chip->cl->dev, "wrong pattern format\n");
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(lp55xx_update_program_memory);
+
+void lp55xx_firmware_loaded_cb(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ const struct firmware *fw = chip->fw;
+ int program_length;
+
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (cfg->pages_per_engine)
+ program_length *= cfg->pages_per_engine;
+
+ /*
+ * the firmware is encoded in ascii hex character, with 2 chars
+ * per byte
+ */
+ if (fw->size > program_length * 2) {
+ dev_err(&chip->cl->dev, "firmware data size overflow: %zu\n",
+ fw->size);
+ return;
+ }
+
+ /*
+ * Program memory sequence
+ * 1) set engine mode to "LOAD"
+ * 2) write firmware data into program memory
+ */
+
+ lp55xx_load_engine(chip);
+ lp55xx_update_program_memory(chip, fw->data, fw->size);
+}
+EXPORT_SYMBOL_GPL(lp55xx_firmware_loaded_cb);
+
+int lp55xx_led_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_write(chip, cfg->reg_led_pwm_base.addr + led->chan_nr,
+ led->brightness);
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_led_brightness);
+
+int lp55xx_multicolor_brightness(struct lp55xx_led *led)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ int i;
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < led->mc_cdev.num_colors; i++) {
+ ret = lp55xx_write(chip,
+ cfg->reg_led_pwm_base.addr +
+ led->mc_cdev.subled_info[i].channel,
+ led->mc_cdev.subled_info[i].brightness);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_multicolor_brightness);
+
+void lp55xx_set_led_current(struct lp55xx_led *led, u8 led_current)
+{
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+
+ led->led_current = led_current;
+ lp55xx_write(led->chip, cfg->reg_led_current_base.addr + led->chan_nr,
+ led_current);
+}
+EXPORT_SYMBOL_GPL(lp55xx_set_led_current);
+
+void lp55xx_turn_off_channels(struct lp55xx_chip *chip)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i;
+
+ for (i = 0; i < cfg->max_channel; i++)
+ lp55xx_write(chip, cfg->reg_led_pwm_base.addr + i, 0);
+}
+EXPORT_SYMBOL_GPL(lp55xx_turn_off_channels);
+
+void lp55xx_stop_engine(struct lp55xx_chip *chip)
+{
+ enum lp55xx_engine_index idx = chip->engine_idx;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mask;
+
+ mask = LP55xx_MODE_ENGn_MASK(idx, cfg->reg_op_mode.shift);
+ lp55xx_update_bits(chip, cfg->reg_op_mode.addr, mask, 0);
+
+ lp55xx_wait_opmode_done(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_stop_engine);
+
static void lp55xx_reset_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->reset.addr;
u8 val = cfg->reset.val;
@@ -52,7 +349,7 @@ static void lp55xx_reset_device(struct lp55xx_chip *chip)
static int lp55xx_detect_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
u8 addr = cfg->enable.addr;
u8 val = cfg->enable.val;
int ret;
@@ -75,7 +372,7 @@ static int lp55xx_detect_device(struct lp55xx_chip *chip)
static int lp55xx_post_init_device(struct lp55xx_chip *chip)
{
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
if (!cfg->post_init_device)
return 0;
@@ -109,9 +406,9 @@ static ssize_t led_current_store(struct device *dev,
if (!chip->cfg->set_led_current)
return len;
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
chip->cfg->set_led_current(led, (u8)curr);
- mutex_unlock(&chip->lock);
return len;
}
@@ -140,7 +437,7 @@ static int lp55xx_set_mc_brightness(struct led_classdev *cdev,
{
struct led_classdev_mc *mc_dev = lcdev_to_mccdev(cdev);
struct lp55xx_led *led = mcled_cdev_to_led(mc_dev);
- struct lp55xx_device_config *cfg = led->chip->cfg;
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
led_mc_calc_color_components(&led->mc_cdev, brightness);
return cfg->multicolor_brightness_fn(led);
@@ -151,7 +448,7 @@ static int lp55xx_set_brightness(struct led_classdev *cdev,
enum led_brightness brightness)
{
struct lp55xx_led *led = cdev_to_lp55xx_led(cdev);
- struct lp55xx_device_config *cfg = led->chip->cfg;
+ const struct lp55xx_device_config *cfg = led->chip->cfg;
led->brightness = (u8)brightness;
return cfg->brightness_fn(led);
@@ -161,7 +458,7 @@ static int lp55xx_init_led(struct lp55xx_led *led,
struct lp55xx_chip *chip, int chan)
{
struct lp55xx_platform_data *pdata = chip->pdata;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
struct device *dev = &chip->cl->dev;
int max_channel = cfg->max_channel;
struct mc_subled *mc_led_info;
@@ -246,14 +543,12 @@ static void lp55xx_firmware_loaded(const struct firmware *fw, void *context)
}
/* handling firmware data is chip dependent */
- mutex_lock(&chip->lock);
-
- chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
- chip->fw = fw;
- if (chip->cfg->firmware_cb)
- chip->cfg->firmware_cb(chip);
-
- mutex_unlock(&chip->lock);
+ scoped_guard(mutex, &chip->lock) {
+ chip->engines[idx - 1].mode = LP55XX_ENGINE_LOAD;
+ chip->fw = fw;
+ if (chip->cfg->firmware_cb)
+ chip->cfg->firmware_cb(chip);
+ }
/* firmware should be released for other channel use */
release_firmware(chip->fw);
@@ -270,8 +565,8 @@ static int lp55xx_request_firmware(struct lp55xx_chip *chip)
}
static ssize_t select_engine_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+ struct device_attribute *attr,
+ char *buf)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -280,8 +575,8 @@ static ssize_t select_engine_show(struct device *dev,
}
static ssize_t select_engine_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+ struct device_attribute *attr,
+ const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -297,10 +592,10 @@ static ssize_t select_engine_store(struct device *dev,
case LP55XX_ENGINE_1:
case LP55XX_ENGINE_2:
case LP55XX_ENGINE_3:
- mutex_lock(&chip->lock);
- chip->engine_idx = val;
- ret = lp55xx_request_firmware(chip);
- mutex_unlock(&chip->lock);
+ scoped_guard(mutex, &chip->lock) {
+ chip->engine_idx = val;
+ ret = lp55xx_request_firmware(chip);
+ }
break;
default:
dev_err(dev, "%lu: invalid engine index. (1, 2, 3)\n", val);
@@ -322,8 +617,8 @@ static inline void lp55xx_run_engine(struct lp55xx_chip *chip, bool start)
}
static ssize_t run_engine_store(struct device *dev,
- struct device_attribute *attr,
- const char *buf, size_t len)
+ struct device_attribute *attr,
+ const char *buf, size_t len)
{
struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
struct lp55xx_chip *chip = led->chip;
@@ -339,9 +634,9 @@ static ssize_t run_engine_store(struct device *dev,
return len;
}
- mutex_lock(&chip->lock);
+ guard(mutex)(&chip->lock);
+
lp55xx_run_engine(chip, true);
- mutex_unlock(&chip->lock);
return len;
}
@@ -349,6 +644,279 @@ static ssize_t run_engine_store(struct device *dev,
static DEVICE_ATTR_RW(select_engine);
static DEVICE_ATTR_WO(run_engine);
+ssize_t lp55xx_show_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ 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 sysfs_emit(buf, "run\n");
+ case LP55XX_ENGINE_LOAD:
+ return sysfs_emit(buf, "load\n");
+ case LP55XX_ENGINE_DISABLED:
+ default:
+ return sysfs_emit(buf, "disabled\n");
+ }
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_engine_mode);
+
+ssize_t lp55xx_store_engine_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+
+ if (!strncmp(buf, "run", 3)) {
+ cfg->run_engine(chip, true);
+ engine->mode = LP55XX_ENGINE_RUN;
+ } else if (!strncmp(buf, "load", 4)) {
+ lp55xx_stop_engine(chip);
+ lp55xx_load_engine(chip);
+ engine->mode = LP55XX_ENGINE_LOAD;
+ } else if (!strncmp(buf, "disabled", 8)) {
+ lp55xx_stop_engine(chip);
+ engine->mode = LP55XX_ENGINE_DISABLED;
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_mode);
+
+ssize_t lp55xx_store_engine_load(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ int ret;
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+ lp55xx_load_engine(chip);
+ ret = lp55xx_update_program_memory(chip, buf, len);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_load);
+
+static int lp55xx_mux_parse(struct lp55xx_chip *chip, const char *buf,
+ u16 *mux, size_t len)
+{
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u16 tmp_mux = 0;
+ int i;
+
+ len = min_t(int, len, cfg->max_channel);
+
+ for (i = 0; i < len; i++) {
+ switch (buf[i]) {
+ case '1':
+ tmp_mux |= (1 << i);
+ break;
+ case '0':
+ break;
+ case '\n':
+ i = len;
+ break;
+ default:
+ return -1;
+ }
+ }
+ *mux = tmp_mux;
+
+ return 0;
+}
+
+ssize_t lp55xx_show_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ unsigned int led_active;
+ int i, pos = 0;
+
+ for (i = 0; i < cfg->max_channel; i++) {
+ led_active = LED_ACTIVE(chip->engines[nr - 1].led_mux, i);
+ pos += sysfs_emit_at(buf, pos, "%x", led_active);
+ }
+
+ pos += sysfs_emit_at(buf, pos, "\n");
+
+ return pos;
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_engine_leds);
+
+static int lp55xx_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
+{
+ struct lp55xx_engine *engine = &chip->engines[nr - 1];
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ u8 mux_page;
+ int ret;
+
+ lp55xx_load_engine(chip);
+
+ /* Derive the MUX page offset by starting at the end of the ENGINE pages */
+ mux_page = cfg->pages_per_engine * LP55XX_ENGINE_MAX + (nr - 1);
+ ret = lp55xx_write(chip, LP55xx_REG_PROG_PAGE_SEL, mux_page);
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, cfg->prog_mem_base.addr, (u8)(mux >> 8));
+ if (ret)
+ return ret;
+
+ ret = lp55xx_write(chip, cfg->prog_mem_base.addr + 1, (u8)(mux));
+ if (ret)
+ return ret;
+
+ engine->led_mux = mux;
+ return 0;
+}
+
+ssize_t lp55xx_store_engine_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ 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];
+ u16 mux = 0;
+
+ if (lp55xx_mux_parse(chip, buf, &mux, len))
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ chip->engine_idx = nr;
+
+ if (engine->mode != LP55XX_ENGINE_LOAD)
+ return -EINVAL;
+
+ if (lp55xx_load_mux(chip, mux, nr))
+ return -EINVAL;
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_engine_leds);
+
+ssize_t lp55xx_show_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ char *buf, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ u8 val;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_read(chip, cfg->reg_master_fader_base.addr + nr - 1, &val);
+
+ return ret ? ret : sysfs_emit(buf, "%u\n", val);
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_master_fader);
+
+ssize_t lp55xx_store_master_fader(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len, int nr)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int ret;
+ unsigned long val;
+
+ if (kstrtoul(buf, 0, &val))
+ return -EINVAL;
+
+ if (val > 0xff)
+ return -EINVAL;
+
+ guard(mutex)(&chip->lock);
+
+ ret = lp55xx_write(chip, cfg->reg_master_fader_base.addr + nr - 1,
+ (u8)val);
+
+ return ret ? ret : len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_master_fader);
+
+ssize_t lp55xx_show_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i, ret, pos = 0;
+ u8 val;
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < cfg->max_channel; i++) {
+ ret = lp55xx_read(chip, cfg->reg_led_ctrl_base.addr + i, &val);
+ if (ret)
+ return ret;
+
+ val = FIELD_GET(LP55xx_FADER_MAPPING_MASK, val);
+ if (val > FIELD_MAX(LP55xx_FADER_MAPPING_MASK)) {
+ return -EINVAL;
+ }
+ buf[pos++] = val + '0';
+ }
+ buf[pos++] = '\n';
+
+ return pos;
+}
+EXPORT_SYMBOL_GPL(lp55xx_show_master_fader_leds);
+
+ssize_t lp55xx_store_master_fader_leds(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t len)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(to_i2c_client(dev));
+ struct lp55xx_chip *chip = led->chip;
+ const struct lp55xx_device_config *cfg = chip->cfg;
+ int i, n, ret;
+ u8 val;
+
+ n = min_t(int, len, cfg->max_channel);
+
+ guard(mutex)(&chip->lock);
+
+ for (i = 0; i < n; i++) {
+ if (buf[i] >= '0' && buf[i] <= '3') {
+ val = (buf[i] - '0') << __bf_shf(LP55xx_FADER_MAPPING_MASK);
+ ret = lp55xx_update_bits(chip,
+ cfg->reg_led_ctrl_base.addr + i,
+ LP55xx_FADER_MAPPING_MASK,
+ val);
+ if (ret)
+ return ret;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ return len;
+}
+EXPORT_SYMBOL_GPL(lp55xx_store_master_fader_leds);
+
static struct attribute *lp55xx_engine_attributes[] = {
&dev_attr_select_engine.attr,
&dev_attr_run_engine.attr,
@@ -397,24 +965,16 @@ EXPORT_SYMBOL_GPL(lp55xx_update_bits);
bool lp55xx_is_extclk_used(struct lp55xx_chip *chip)
{
struct clk *clk;
- int err;
- clk = devm_clk_get(&chip->cl->dev, "32k_clk");
+ clk = devm_clk_get_enabled(&chip->cl->dev, "32k_clk");
if (IS_ERR(clk))
goto use_internal_clk;
- err = clk_prepare_enable(clk);
- if (err)
- goto use_internal_clk;
-
- if (clk_get_rate(clk) != LP55XX_CLK_32K) {
- clk_disable_unprepare(clk);
+ if (clk_get_rate(clk) != LP55XX_CLK_32K)
goto use_internal_clk;
- }
dev_info(&chip->cl->dev, "%dHz external clock used\n", LP55XX_CLK_32K);
- chip->clk = clk;
return true;
use_internal_clk:
@@ -423,10 +983,18 @@ use_internal_clk:
}
EXPORT_SYMBOL_GPL(lp55xx_is_extclk_used);
-int lp55xx_init_device(struct lp55xx_chip *chip)
+static void lp55xx_deinit_device(struct lp55xx_chip *chip)
+{
+ struct lp55xx_platform_data *pdata = chip->pdata;
+
+ if (pdata->enable_gpiod)
+ gpiod_set_value(pdata->enable_gpiod, 0);
+}
+
+static int lp55xx_init_device(struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata;
- struct lp55xx_device_config *cfg;
+ const struct lp55xx_device_config *cfg;
struct device *dev = &chip->cl->dev;
int ret = 0;
@@ -476,24 +1044,11 @@ err_post_init:
err:
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_init_device);
-void lp55xx_deinit_device(struct lp55xx_chip *chip)
+static int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
{
struct lp55xx_platform_data *pdata = chip->pdata;
-
- if (chip->clk)
- clk_disable_unprepare(chip->clk);
-
- if (pdata->enable_gpiod)
- gpiod_set_value(pdata->enable_gpiod, 0);
-}
-EXPORT_SYMBOL_GPL(lp55xx_deinit_device);
-
-int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
-{
- struct lp55xx_platform_data *pdata = chip->pdata;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
int num_channels = pdata->num_channels;
struct lp55xx_led *each;
u8 led_current;
@@ -530,12 +1085,11 @@ int lp55xx_register_leds(struct lp55xx_led *led, struct lp55xx_chip *chip)
err_init_led:
return ret;
}
-EXPORT_SYMBOL_GPL(lp55xx_register_leds);
-int lp55xx_register_sysfs(struct lp55xx_chip *chip)
+static int lp55xx_register_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
int ret;
if (!cfg->run_engine || !cfg->firmware_cb)
@@ -549,19 +1103,17 @@ dev_specific_attrs:
return cfg->dev_attr_group ?
sysfs_create_group(&dev->kobj, cfg->dev_attr_group) : 0;
}
-EXPORT_SYMBOL_GPL(lp55xx_register_sysfs);
-void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
+static void lp55xx_unregister_sysfs(struct lp55xx_chip *chip)
{
struct device *dev = &chip->cl->dev;
- struct lp55xx_device_config *cfg = chip->cfg;
+ const struct lp55xx_device_config *cfg = chip->cfg;
if (cfg->dev_attr_group)
sysfs_remove_group(&dev->kobj, cfg->dev_attr_group);
sysfs_remove_group(&dev->kobj, &lp55xx_engine_attr_group);
}
-EXPORT_SYMBOL_GPL(lp55xx_unregister_sysfs);
static int lp55xx_parse_common_child(struct device_node *np,
struct lp55xx_led_config *cfg,
@@ -580,9 +1132,6 @@ static int lp55xx_parse_common_child(struct device_node *np,
if (ret)
return ret;
- if (*chan_nr < 0 || *chan_nr > cfg->max_channel)
- return -EINVAL;
-
return 0;
}
@@ -610,16 +1159,13 @@ static int lp55xx_parse_multi_led(struct device_node *np,
struct lp55xx_led_config *cfg,
int child_number)
{
- struct device_node *child;
int num_colors = 0, ret;
- for_each_available_child_of_node(np, child) {
+ for_each_available_child_of_node_scoped(np, child) {
ret = lp55xx_parse_multi_led_child(child, cfg, child_number,
num_colors);
- if (ret) {
- of_node_put(child);
+ if (ret)
return ret;
- }
num_colors++;
}
@@ -654,9 +1200,9 @@ static int lp55xx_parse_logical_led(struct device_node *np,
return ret;
}
-struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
- struct device_node *np,
- struct lp55xx_chip *chip)
+static struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
+ struct device_node *np,
+ struct lp55xx_chip *chip)
{
struct device_node *child;
struct lp55xx_platform_data *pdata;
@@ -713,7 +1259,92 @@ struct lp55xx_platform_data *lp55xx_of_populate_pdata(struct device *dev,
return pdata;
}
-EXPORT_SYMBOL_GPL(lp55xx_of_populate_pdata);
+
+int lp55xx_probe(struct i2c_client *client)
+{
+ const struct i2c_device_id *id = i2c_client_get_device_id(client);
+ int program_length, ret;
+ struct lp55xx_chip *chip;
+ struct lp55xx_led *led;
+ struct lp55xx_platform_data *pdata = dev_get_platdata(&client->dev);
+ struct device_node *np = dev_of_node(&client->dev);
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->cfg = i2c_get_match_data(client);
+
+ if (!pdata) {
+ if (np) {
+ pdata = lp55xx_of_populate_pdata(&client->dev, np,
+ chip);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ } else {
+ dev_err(&client->dev, "no platform data\n");
+ return -EINVAL;
+ }
+ }
+
+ /* Validate max program page */
+ program_length = LP55xx_BYTES_PER_PAGE;
+ if (chip->cfg->pages_per_engine)
+ program_length *= chip->cfg->pages_per_engine;
+
+ /* support a max of 128bytes */
+ if (program_length > LP55xx_MAX_PROGRAM_LENGTH) {
+ dev_err(&client->dev, "invalid pages_per_engine configured\n");
+ return -EINVAL;
+ }
+
+ led = devm_kcalloc(&client->dev,
+ pdata->num_channels, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ chip->cl = client;
+ chip->pdata = pdata;
+
+ mutex_init(&chip->lock);
+
+ i2c_set_clientdata(client, led);
+
+ ret = lp55xx_init_device(chip);
+ if (ret)
+ goto err_init;
+
+ dev_info(&client->dev, "%s Programmable led chip found\n", id->name);
+
+ ret = lp55xx_register_leds(led, chip);
+ if (ret)
+ goto err_out;
+
+ ret = lp55xx_register_sysfs(chip);
+ if (ret) {
+ dev_err(&client->dev, "registering sysfs failed\n");
+ goto err_out;
+ }
+
+ return 0;
+
+err_out:
+ lp55xx_deinit_device(chip);
+err_init:
+ return ret;
+}
+EXPORT_SYMBOL_GPL(lp55xx_probe);
+
+void lp55xx_remove(struct i2c_client *client)
+{
+ struct lp55xx_led *led = i2c_get_clientdata(client);
+ struct lp55xx_chip *chip = led->chip;
+
+ lp55xx_stop_all_engine(chip);
+ lp55xx_unregister_sysfs(chip);
+ lp55xx_deinit_device(chip);
+}
+EXPORT_SYMBOL_GPL(lp55xx_remove);
MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>");
MODULE_DESCRIPTION("LP55xx Common Driver");