]> git.karo-electronics.de Git - karo-tx-linux.git/commitdiff
Merge remote-tracking branch 'leds/for-next'
authorStephen Rothwell <sfr@canb.auug.org.au>
Thu, 11 Feb 2016 02:36:28 +0000 (13:36 +1100)
committerStephen Rothwell <sfr@canb.auug.org.au>
Thu, 11 Feb 2016 02:36:28 +0000 (13:36 +1100)
Documentation/devicetree/bindings/leds/leds-sn3218.txt [new file with mode: 0644]
Documentation/devicetree/bindings/vendor-prefixes.txt
drivers/leds/Kconfig
drivers/leds/Makefile
drivers/leds/led-class.c
drivers/leds/led-core.c
drivers/leds/leds-gpio.c
drivers/leds/leds-lp3944.c
drivers/leds/leds-sn3218.c [new file with mode: 0644]
include/linux/leds.h

diff --git a/Documentation/devicetree/bindings/leds/leds-sn3218.txt b/Documentation/devicetree/bindings/leds/leds-sn3218.txt
new file mode 100644 (file)
index 0000000..19cbf57
--- /dev/null
@@ -0,0 +1,41 @@
+* Si-En Technology - SN3218 18-Channel LED Driver
+
+Required properties:
+- compatible :
+       "si-en,sn3218"
+- address-cells : must be 1
+- size-cells : must be 0
+- reg : I2C slave address, typically 0x54
+
+There must be at least 1 LED which is represented as a sub-node
+of the sn3218 device.
+
+LED sub-node properties:
+- label : (optional) see Documentation/devicetree/bindings/leds/common.txt
+- reg : number of LED line (could be from 0 to 17)
+- linux,default-trigger : (optional)
+   see Documentation/devicetree/bindings/leds/common.txt
+
+Example:
+
+sn3218: led-controller@54 {
+       compatible = "si-en,sn3218";
+       #address-cells = <1>;
+       #size-cells = <0>;
+       reg = <0x54>;
+
+       led@0 {
+               label = "led1";
+               reg = <0x0>;
+       };
+
+       led@1 {
+               label = "led2";
+               reg = <0x1>;
+       };
+
+       led@2 {
+               label = "led3";
+               reg = <0x2>;
+       };
+};
index e00029d66d66d7ab8e0b87f2764ee081d41cecea..9c548515fc54047a5b3fb0e4af9ac46f2a8f5aa7 100644 (file)
@@ -205,6 +205,7 @@ seagate     Seagate Technology PLC
 semtech        Semtech Corporation
 sgx    SGX Sensortech
 sharp  Sharp Corporation
+si-en  Si-En Technology Ltd.
 sigma  Sigma Designs, Inc.
 sil    Silicon Image
 silabs Silicon Laboratories
index 7f940c24a16b0d00163051acfda665b692733b5d..1034696b77cfba2abc4fa0417c966cd1b865b78d 100644 (file)
@@ -568,6 +568,18 @@ config LEDS_SEAD3
          This driver can also be built as a module. If so the module
          will be called leds-sead3.
 
+config LEDS_SN3218
+       tristate "LED support for Si-En SN3218 I2C chip"
+       depends on LEDS_CLASS && I2C
+       depends on OF
+       select REGMAP_I2C
+       help
+         This option enables support for the Si-EN SN3218 LED driver
+         connected through I2C. Say Y to enable support for the SN3218 LED.
+
+         This driver can also be built as a module. If so the module
+         will be called leds-sn3218.
+
 comment "LED driver for blink(1) USB RGB LED is under Special HID drivers (HID_THINGM)"
 
 config LEDS_BLINKM
index e9d53092765d8ca8ae43e970dc63ec1c23e96b78..89c9b6ff65c3632d99db298ac30fb2927e3145c7 100644 (file)
@@ -66,6 +66,7 @@ obj-$(CONFIG_LEDS_MENF21BMC)          += leds-menf21bmc.o
 obj-$(CONFIG_LEDS_KTD2692)             += leds-ktd2692.o
 obj-$(CONFIG_LEDS_POWERNV)             += leds-powernv.o
 obj-$(CONFIG_LEDS_SEAD3)               += leds-sead3.o
+obj-$(CONFIG_LEDS_SN3218)              += leds-sn3218.o
 
 # LED SPI Drivers
 obj-$(CONFIG_LEDS_DAC124S085)          += leds-dac124s085.o
index 14139c337312c574c82a98bee4c163834cad6bc5..aa84e5b375931825e71b23f6e2d1eefc7c3bea17 100644 (file)
@@ -245,6 +245,8 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
        up_write(&led_cdev->trigger_lock);
 #endif
 
+       led_cdev->flags |= LED_UNREGISTERING;
+
        /* Stop blinking */
        led_stop_software_blink(led_cdev);
 
index 19e1e60dfaa354585ca01e2ffdc2a47f29c49c3b..ad684b6d0b724bc4b249102879c12202f6e6be50 100644 (file)
@@ -98,7 +98,10 @@ static void set_brightness_delayed(struct work_struct *ws)
                                                led_cdev->delayed_set_value);
        else
                ret = -ENOTSUPP;
-       if (ret < 0)
+       if (ret < 0 &&
+           /* LED HW might have been unplugged, therefore don't warn */
+           !(ret == -ENODEV && (led_cdev->flags & LED_UNREGISTERING) &&
+           (led_cdev->flags & LED_HW_PLUGGABLE)))
                dev_err(led_cdev->dev,
                        "Setting an LED's brightness failed (%d)\n", ret);
 }
index 7bc53280dbfdd91d0d2efa51bb63986a6bbf336b..5883dede34238862c8cbc8871240aa93f93ab780 100644 (file)
@@ -104,8 +104,8 @@ static int create_gpio_led(const struct gpio_led *template,
                        return ret;
 
                led_dat->gpiod = gpio_to_desc(template->gpio);
-               if (IS_ERR(led_dat->gpiod))
-                       return PTR_ERR(led_dat->gpiod);
+               if (!led_dat->gpiod)
+                       return -EINVAL;
        }
 
        led_dat->cdev.name = template->name;
index 6c758aea1bbdaa1fb77edf71e9c611e4e131ee0e..be60c181222a562f564eb839d470e85ae932f9d9 100644 (file)
@@ -199,8 +199,11 @@ static int lp3944_led_set(struct lp3944_led_data *led, u8 status)
        if (status > LP3944_LED_STATUS_DIM1)
                return -EINVAL;
 
-       /* invert only 0 and 1, leave unchanged the other values,
-        * remember we are abusing status to set blink patterns
+       /*
+        * Invert status only when it's < 2 (i.e. 0 or 1) which means it's
+        * controlling the on/off state directly.
+        * When, instead, status is >= 2 don't invert it because it would mean
+        * to mess with the hardware blinking mode.
         */
        if (led->type == LP3944_LED_TYPE_LED_INVERTED && status < 2)
                status = 1 - status;
diff --git a/drivers/leds/leds-sn3218.c b/drivers/leds/leds-sn3218.c
new file mode 100644 (file)
index 0000000..dcc2581
--- /dev/null
@@ -0,0 +1,306 @@
+/*
+ * Si-En SN3218 18 Channel LED Driver
+ *
+ * Copyright (C) 2016 Stefan Wahren <stefan.wahren@i2se.com>
+ *
+ * Based on leds-pca963x.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2 as published by the Free Software Foundation.
+ *
+ * Datasheet: http://www.si-en.com/uploadpdf/s2011517171720.pdf
+ *
+ */
+
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/leds.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define SN3218_MODE            0x00
+#define SN3218_PWM_1           0x01
+#define SN3218_PWM_2           0x02
+#define SN3218_PWM_3           0x03
+#define SN3218_PWM_4           0x04
+#define SN3218_PWM_5           0x05
+#define SN3218_PWM_6           0x06
+#define SN3218_PWM_7           0x07
+#define SN3218_PWM_8           0x08
+#define SN3218_PWM_9           0x09
+#define SN3218_PWM_10          0x0a
+#define SN3218_PWM_11          0x0b
+#define SN3218_PWM_12          0x0c
+#define SN3218_PWM_13          0x0d
+#define SN3218_PWM_14          0x0e
+#define SN3218_PWM_15          0x0f
+#define SN3218_PWM_16          0x10
+#define SN3218_PWM_17          0x11
+#define SN3218_PWM_18          0x12
+#define SN3218_LED_1_6         0x13
+#define SN3218_LED_7_12                0x14
+#define SN3218_LED_13_18       0x15
+#define SN3218_UPDATE          0x16    /* applies to reg 0x01 .. 0x15 */
+#define SN3218_RESET           0x17
+
+#define SN3218_LED_MASK                0x3f
+#define SN3218_LED_ON          0x01
+#define SN3218_LED_OFF         0x00
+
+#define SN3218_MODE_SHUTDOWN   0x00
+#define SN3218_MODE_NORMAL     0x01
+
+#define NUM_LEDS               18
+
+struct sn3218_led;
+
+/**
+ * struct sn3218 -
+ * @client - Pointer to the I2C client
+ * @leds - Pointer to the individual LEDs
+ * @num_leds - Actual number of LEDs
+**/
+struct sn3218 {
+       struct i2c_client *client;
+       struct regmap *regmap;
+       struct sn3218_led *leds;
+       int num_leds;
+};
+
+/**
+ * struct sn3218_led -
+ * @chip - Pointer to the container
+ * @led_cdev - led class device pointer
+ * @led_num - LED index ( 0 .. 17 )
+**/
+struct sn3218_led {
+       struct sn3218 *chip;
+       struct led_classdev led_cdev;
+       int led_num;
+};
+
+static int sn3218_led_set(struct led_classdev *led_cdev,
+                         enum led_brightness brightness)
+{
+       struct sn3218_led *led =
+                       container_of(led_cdev, struct sn3218_led, led_cdev);
+       struct regmap *regmap = led->chip->regmap;
+       u8 bank = led->led_num / 6;
+       u8 mask = 0x1 << (led->led_num % 6);
+       u8 val;
+       int ret;
+
+       if (brightness == LED_OFF)
+               val = 0;
+       else
+               val = mask;
+
+       ret = regmap_update_bits(regmap, SN3218_LED_1_6 + bank, mask, val);
+       if (ret < 0)
+               return ret;
+
+       if (brightness > LED_OFF) {
+               ret = regmap_write(regmap, SN3218_PWM_1 + led->led_num,
+                                  brightness);
+               if (ret < 0)
+                       return ret;
+       }
+
+       ret = regmap_write(regmap, SN3218_UPDATE, 0xff);
+
+       return ret;
+}
+
+static void sn3218_led_init(struct sn3218 *sn3218, struct device_node *node,
+                           u32 reg)
+{
+       struct sn3218_led *leds = sn3218->leds;
+       struct led_classdev *cdev = &leds[reg].led_cdev;
+
+       leds[reg].led_num = reg;
+       leds[reg].chip = sn3218;
+
+       if (of_property_read_string(node, "label", &cdev->name))
+               cdev->name = node->name;
+
+       of_property_read_string(node, "linux,default-trigger",
+                               &cdev->default_trigger);
+
+       cdev->brightness_set_blocking = sn3218_led_set;
+}
+
+static const struct reg_default sn3218_reg_defs[] = {
+       { SN3218_MODE, 0x00},
+       { SN3218_PWM_1, 0x00},
+       { SN3218_PWM_2, 0x00},
+       { SN3218_PWM_3, 0x00},
+       { SN3218_PWM_4, 0x00},
+       { SN3218_PWM_5, 0x00},
+       { SN3218_PWM_6, 0x00},
+       { SN3218_PWM_7, 0x00},
+       { SN3218_PWM_8, 0x00},
+       { SN3218_PWM_9, 0x00},
+       { SN3218_PWM_10, 0x00},
+       { SN3218_PWM_11, 0x00},
+       { SN3218_PWM_12, 0x00},
+       { SN3218_PWM_13, 0x00},
+       { SN3218_PWM_14, 0x00},
+       { SN3218_PWM_15, 0x00},
+       { SN3218_PWM_16, 0x00},
+       { SN3218_PWM_17, 0x00},
+       { SN3218_PWM_18, 0x00},
+       { SN3218_LED_1_6, 0x00},
+       { SN3218_LED_7_12, 0x00},
+       { SN3218_LED_13_18, 0x00},
+       { SN3218_UPDATE, 0x00},
+       { SN3218_RESET, 0x00},
+};
+
+static const struct regmap_config sn3218_regmap_config = {
+       .reg_bits = 8,
+       .val_bits = 8,
+
+       .max_register = SN3218_RESET,
+       .reg_defaults = sn3218_reg_defs,
+       .num_reg_defaults = ARRAY_SIZE(sn3218_reg_defs),
+       .cache_type = REGCACHE_RBTREE,
+};
+
+static int sn3218_init(struct i2c_client *client, struct sn3218 *sn3218)
+{
+       struct device_node *np = client->dev.of_node, *child;
+       struct sn3218_led *leds;
+       int ret, count;
+
+       count = of_get_child_count(np);
+       if (!count)
+               return -ENODEV;
+
+       if (count > NUM_LEDS) {
+               dev_err(&client->dev, "Invalid LED count %d\n", count);
+               return -EINVAL;
+       }
+
+       leds = devm_kzalloc(&client->dev, count * sizeof(*leds), GFP_KERNEL);
+       if (!leds)
+               return -ENOMEM;
+
+       sn3218->leds = leds;
+       sn3218->num_leds = count;
+       sn3218->client = client;
+
+       sn3218->regmap = devm_regmap_init_i2c(client, &sn3218_regmap_config);
+       if (IS_ERR(sn3218->regmap)) {
+               ret = PTR_ERR(sn3218->regmap);
+               dev_err(&client->dev, "Failed to allocate register map: %d\n",
+                       ret);
+               return ret;
+       }
+
+       for_each_child_of_node(np, child) {
+               u32 reg;
+
+               ret = of_property_read_u32(child, "reg", &reg);
+               if (ret)
+                       goto fail;
+
+               if (reg >= count) {
+                       dev_err(&client->dev, "Invalid LED (%u >= %d)\n", reg,
+                               count);
+                       ret = -EINVAL;
+                       goto fail;
+               }
+
+               sn3218_led_init(sn3218, child, reg);
+       }
+
+       return 0;
+
+fail:
+       of_node_put(child);
+       return ret;
+}
+
+static int sn3218_probe(struct i2c_client *client,
+                       const struct i2c_device_id *id)
+{
+       struct sn3218 *sn3218;
+       struct sn3218_led *leds;
+       struct device *dev = &client->dev;
+       int i, ret;
+
+       sn3218 = devm_kzalloc(dev, sizeof(*sn3218), GFP_KERNEL);
+       if (!sn3218)
+               return -ENOMEM;
+
+       ret = sn3218_init(client, sn3218);
+       if (ret)
+               return ret;
+
+       i2c_set_clientdata(client, sn3218);
+       leds = sn3218->leds;
+
+       /*
+        * Since the chip is write-only we need to reset him into
+        * a defined state (all LEDs off).
+        */
+       ret = regmap_write(sn3218->regmap, SN3218_RESET, 0xff);
+       if (ret)
+               return ret;
+
+       for (i = 0; i < sn3218->num_leds; i++) {
+               ret = devm_led_classdev_register(dev, &leds[i].led_cdev);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return regmap_write(sn3218->regmap, SN3218_MODE, SN3218_MODE_NORMAL);
+}
+
+static int sn3218_remove(struct i2c_client *client)
+{
+       struct sn3218 *sn3218 = i2c_get_clientdata(client);
+
+       regmap_write(sn3218->regmap, SN3218_MODE, SN3218_MODE_SHUTDOWN);
+
+       return 0;
+}
+
+static void sn3218_shutdown(struct i2c_client *client)
+{
+       struct sn3218 *sn3218 = i2c_get_clientdata(client);
+
+       regmap_write(sn3218->regmap, SN3218_MODE, SN3218_MODE_SHUTDOWN);
+}
+
+static const struct i2c_device_id sn3218_id[] = {
+       { "sn3218", 0 },
+       { }
+};
+MODULE_DEVICE_TABLE(i2c, sn3218_id);
+
+static const struct of_device_id of_sn3218_match[] = {
+       { .compatible = "si-en,sn3218", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, of_sn3218_match);
+
+static struct i2c_driver sn3218_driver = {
+       .driver = {
+               .name   = "leds-sn3218",
+               .of_match_table = of_match_ptr(of_sn3218_match),
+       },
+       .probe  = sn3218_probe,
+       .remove = sn3218_remove,
+       .shutdown = sn3218_shutdown,
+       .id_table = sn3218_id,
+};
+
+module_i2c_driver(sn3218_driver);
+
+MODULE_DESCRIPTION("Si-En SN3218 LED Driver");
+MODULE_AUTHOR("Stefan Wahren <stefan.wahren@i2se.com>");
+MODULE_LICENSE("GPL v2");
index bc1476fda96eb3206995b270e32cb54a49305eea..f203a8f89d30d0f3e214b43d2c72efea1bdad9f9 100644 (file)
@@ -39,6 +39,7 @@ struct led_classdev {
 
        /* Lower 16 bits reflect status */
 #define LED_SUSPENDED          (1 << 0)
+#define LED_UNREGISTERING      (1 << 1)
        /* Upper 16 bits reflect control information */
 #define LED_CORE_SUSPENDRESUME (1 << 16)
 #define LED_BLINK_ONESHOT      (1 << 17)
@@ -48,9 +49,12 @@ struct led_classdev {
 #define LED_BLINK_DISABLE      (1 << 21)
 #define LED_SYSFS_DISABLE      (1 << 22)
 #define LED_DEV_CAP_FLASH      (1 << 23)
+#define LED_HW_PLUGGABLE       (1 << 24)
 
-       /* Set LED brightness level */
-       /* Must not sleep, use a workqueue if needed */
+       /* Set LED brightness level
+        * Must not sleep. Use brightness_set_blocking for drivers
+        * that can sleep while setting brightness.
+        */
        void            (*brightness_set)(struct led_classdev *led_cdev,
                                          enum led_brightness brightness);
        /*