Merge tag 'omap-for-v3.13/fixes-for-merge-window-take2' of git://git.kernel.org/pub...
[cascardo/linux.git] / drivers / leds / leds-lp5523.c
index 29c8b19..6b553d9 100644 (file)
@@ -29,6 +29,7 @@
 #include <linux/leds.h>
 #include <linux/module.h>
 #include <linux/mutex.h>
+#include <linux/of.h>
 #include <linux/platform_data/leds-lp55xx.h>
 #include <linux/slab.h>
 
@@ -74,6 +75,9 @@
 #define LP5523_PAGE_ENG1               0
 #define LP5523_PAGE_ENG2               1
 #define LP5523_PAGE_ENG3               2
+#define LP5523_PAGE_MUX1               3
+#define LP5523_PAGE_MUX2               4
+#define LP5523_PAGE_MUX3               5
 
 /* Program Memory Operations */
 #define LP5523_MODE_ENG1_M             0x30    /* Operation Mode Register */
 #define LP5523_RUN_ENG2                        0x08
 #define LP5523_RUN_ENG3                        0x02
 
+#define LED_ACTIVE(mux, led)           (!!(mux & (0x0001 << led)))
+
 enum lp5523_chip_id {
        LP5523,
        LP55231,
@@ -307,17 +313,11 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip,
        u8 pattern[LP5523_PROGRAM_LENGTH] = {0};
        unsigned cmd;
        char c[3];
-       int update_size;
        int nrchars;
-       int offset = 0;
        int ret;
-       int i;
-
-       /* clear program memory before updating */
-       for (i = 0; i < LP5523_PROGRAM_LENGTH; i++)
-               lp55xx_write(chip, LP5523_REG_PROG_MEM + i, 0);
+       int offset = 0;
+       int i = 0;
 
-       i = 0;
        while ((offset < size - 1) && (i < LP5523_PROGRAM_LENGTH)) {
                /* separate sscanfs because length is working only for %s */
                ret = sscanf(data + offset, "%2s%n ", c, &nrchars);
@@ -337,11 +337,19 @@ static int lp5523_update_program_memory(struct lp55xx_chip *chip,
        if (i % 2)
                goto err;
 
-       update_size = i;
-       for (i = 0; i < update_size; i++)
-               lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
+       mutex_lock(&chip->lock);
 
-       return 0;
+       for (i = 0; i < LP5523_PROGRAM_LENGTH; i++) {
+               ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + i, pattern[i]);
+               if (ret) {
+                       mutex_unlock(&chip->lock);
+                       return -EINVAL;
+               }
+       }
+
+       mutex_unlock(&chip->lock);
+
+       return size;
 
 err:
        dev_err(&chip->cl->dev, "wrong pattern format\n");
@@ -368,6 +376,192 @@ static void lp5523_firmware_loaded(struct lp55xx_chip *chip)
        lp5523_update_program_memory(chip, fw->data, fw->size);
 }
 
+static ssize_t 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 sprintf(buf, "run\n");
+       case LP55XX_ENGINE_LOAD:
+               return sprintf(buf, "load\n");
+       case LP55XX_ENGINE_DISABLED:
+       default:
+               return sprintf(buf, "disabled\n");
+       }
+}
+show_mode(1)
+show_mode(2)
+show_mode(3)
+
+static ssize_t 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;
+       struct lp55xx_engine *engine = &chip->engines[nr - 1];
+
+       mutex_lock(&chip->lock);
+
+       chip->engine_idx = nr;
+
+       if (!strncmp(buf, "run", 3)) {
+               lp5523_run_engine(chip, true);
+               engine->mode = LP55XX_ENGINE_RUN;
+       } else if (!strncmp(buf, "load", 4)) {
+               lp5523_stop_engine(chip);
+               lp5523_load_engine(chip);
+               engine->mode = LP55XX_ENGINE_LOAD;
+       } else if (!strncmp(buf, "disabled", 8)) {
+               lp5523_stop_engine(chip);
+               engine->mode = LP55XX_ENGINE_DISABLED;
+       }
+
+       mutex_unlock(&chip->lock);
+
+       return len;
+}
+store_mode(1)
+store_mode(2)
+store_mode(3)
+
+static int lp5523_mux_parse(const char *buf, u16 *mux, size_t len)
+{
+       u16 tmp_mux = 0;
+       int i;
+
+       len = min_t(int, len, LP5523_MAX_LEDS);
+
+       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;
+}
+
+static void lp5523_mux_to_array(u16 led_mux, char *array)
+{
+       int i, pos = 0;
+       for (i = 0; i < LP5523_MAX_LEDS; i++)
+               pos += sprintf(array + pos, "%x", LED_ACTIVE(led_mux, i));
+
+       array[pos] = '\0';
+}
+
+static ssize_t 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;
+       char mux[LP5523_MAX_LEDS + 1];
+
+       lp5523_mux_to_array(chip->engines[nr - 1].led_mux, mux);
+
+       return sprintf(buf, "%s\n", mux);
+}
+show_leds(1)
+show_leds(2)
+show_leds(3)
+
+static int lp5523_load_mux(struct lp55xx_chip *chip, u16 mux, int nr)
+{
+       struct lp55xx_engine *engine = &chip->engines[nr - 1];
+       int ret;
+       u8 mux_page[] = {
+               [LP55XX_ENGINE_1] = LP5523_PAGE_MUX1,
+               [LP55XX_ENGINE_2] = LP5523_PAGE_MUX2,
+               [LP55XX_ENGINE_3] = LP5523_PAGE_MUX3,
+       };
+
+       lp5523_load_engine(chip);
+
+       ret = lp55xx_write(chip, LP5523_REG_PROG_PAGE_SEL, mux_page[nr]);
+       if (ret)
+               return ret;
+
+       ret = lp55xx_write(chip, LP5523_REG_PROG_MEM , (u8)(mux >> 8));
+       if (ret)
+               return ret;
+
+       ret = lp55xx_write(chip, LP5523_REG_PROG_MEM + 1, (u8)(mux));
+       if (ret)
+               return ret;
+
+       engine->led_mux = mux;
+       return 0;
+}
+
+static ssize_t 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;
+       ssize_t ret;
+
+       if (lp5523_mux_parse(buf, &mux, len))
+               return -EINVAL;
+
+       mutex_lock(&chip->lock);
+
+       chip->engine_idx = nr;
+       ret = -EINVAL;
+
+       if (engine->mode != LP55XX_ENGINE_LOAD)
+               goto leave;
+
+       if (lp5523_load_mux(chip, mux, nr))
+               goto leave;
+
+       ret = len;
+leave:
+       mutex_unlock(&chip->lock);
+       return ret;
+}
+store_leds(1)
+store_leds(2)
+store_leds(3)
+
+static ssize_t 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;
+
+       mutex_lock(&chip->lock);
+
+       chip->engine_idx = nr;
+       lp5523_load_engine_and_select_page(chip);
+
+       mutex_unlock(&chip->lock);
+
+       return lp5523_update_program_memory(chip, buf, len);
+}
+store_load(1)
+store_load(2)
+store_load(3)
+
 static ssize_t lp5523_selftest(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
@@ -467,9 +661,27 @@ static void lp5523_led_brightness_work(struct work_struct *work)
        mutex_unlock(&chip->lock);
 }
 
-static DEVICE_ATTR(selftest, S_IRUGO, lp5523_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_RW(engine1_leds, show_engine1_leds, store_engine1_leds);
+static LP55XX_DEV_ATTR_RW(engine2_leds, show_engine2_leds, store_engine2_leds);
+static LP55XX_DEV_ATTR_RW(engine3_leds, show_engine3_leds, store_engine3_leds);
+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, lp5523_selftest);
 
 static struct attribute *lp5523_attributes[] = {
+       &dev_attr_engine1_mode.attr,
+       &dev_attr_engine2_mode.attr,
+       &dev_attr_engine3_mode.attr,
+       &dev_attr_engine1_load.attr,
+       &dev_attr_engine2_load.attr,
+       &dev_attr_engine3_load.attr,
+       &dev_attr_engine1_leds.attr,
+       &dev_attr_engine2_leds.attr,
+       &dev_attr_engine3_leds.attr,
        &dev_attr_selftest.attr,
        NULL,
 };