diff options
-rw-r--r-- | Documentation/ABI/testing/sysfs-class-led-flash | 80 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/leds/common.txt | 6 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/leds/leds-gpio.txt | 12 | ||||
-rw-r--r-- | Documentation/devicetree/bindings/leds/leds-pm8941-wled.txt | 43 | ||||
-rw-r--r-- | Documentation/driver-model/devres.txt | 4 | ||||
-rw-r--r-- | Documentation/leds/leds-class-flash.txt | 22 | ||||
-rw-r--r-- | drivers/leds/Kconfig | 8 | ||||
-rw-r--r-- | drivers/leds/Makefile | 1 | ||||
-rw-r--r-- | drivers/leds/led-class-flash.c | 82 | ||||
-rw-r--r-- | drivers/leds/led-class.c | 96 | ||||
-rw-r--r-- | drivers/leds/leds-lp8501.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-lp8860.c | 14 | ||||
-rw-r--r-- | drivers/leds/leds-pca963x.c | 2 | ||||
-rw-r--r-- | drivers/leds/leds-pm8941-wled.c | 435 | ||||
-rw-r--r-- | drivers/leds/leds-pwm.c | 3 | ||||
-rw-r--r-- | include/dt-bindings/leds/common.h | 21 | ||||
-rw-r--r-- | include/linux/led-class-flash.h | 19 | ||||
-rw-r--r-- | include/linux/leds.h | 5 |
18 files changed, 735 insertions, 120 deletions
diff --git a/Documentation/ABI/testing/sysfs-class-led-flash b/Documentation/ABI/testing/sysfs-class-led-flash new file mode 100644 index 000000000000..220a0270b47b --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-led-flash @@ -0,0 +1,80 @@ +What: /sys/class/leds/<led>/flash_brightness +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read/write + Set the brightness of this LED in the flash strobe mode, in + microamperes. The file is created only for the flash LED devices + that support setting flash brightness. + + The value is between 0 and + /sys/class/leds/<led>/max_flash_brightness. + +What: /sys/class/leds/<led>/max_flash_brightness +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read only + Maximum brightness level for this LED in the flash strobe mode, + in microamperes. + +What: /sys/class/leds/<led>/flash_timeout +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read/write + Hardware timeout for flash, in microseconds. The flash strobe + is stopped after this period of time has passed from the start + of the strobe. The file is created only for the flash LED + devices that support setting flash timeout. + +What: /sys/class/leds/<led>/max_flash_timeout +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read only + Maximum flash timeout for this LED, in microseconds. + +What: /sys/class/leds/<led>/flash_strobe +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read/write + Flash strobe state. When written with 1 it triggers flash strobe + and when written with 0 it turns the flash off. + + On read 1 means that flash is currently strobing and 0 means + that flash is off. + +What: /sys/class/leds/<led>/flash_fault +Date: March 2015 +KernelVersion: 4.0 +Contact: Jacek Anaszewski <j.anaszewski@samsung.com> +Description: read only + Space separated list of flash faults that may have occurred. + Flash faults are re-read after strobing the flash. Possible + flash faults: + + * led-over-voltage - flash controller voltage to the flash LED + has exceeded the limit specific to the flash controller + * flash-timeout-exceeded - the flash strobe was still on when + the timeout set by the user has expired; not all flash + controllers may set this in all such conditions + * controller-over-temperature - the flash controller has + overheated + * controller-short-circuit - the short circuit protection + of the flash controller has been triggered + * led-power-supply-over-current - current in the LED power + supply has exceeded the limit specific to the flash + controller + * indicator-led-fault - the flash controller has detected + a short or open circuit condition on the indicator LED + * led-under-voltage - flash controller voltage to the flash + LED has been below the minimum limit specific to + the flash + * controller-under-voltage - the input voltage of the flash + controller is below the limit under which strobing the + flash at full current will not be possible; + the condition persists until this flag is no longer set + * led-over-temperature - the temperature of the LED has exceeded + its allowed upper limit diff --git a/Documentation/devicetree/bindings/leds/common.txt b/Documentation/devicetree/bindings/leds/common.txt index 34811c57db69..747c53805eec 100644 --- a/Documentation/devicetree/bindings/leds/common.txt +++ b/Documentation/devicetree/bindings/leds/common.txt @@ -14,8 +14,10 @@ Optional properties for child nodes: - led-sources : List of device current outputs the LED is connected to. The outputs are identified by the numbers that must be defined in the LED device binding documentation. -- label : The label for this LED. If omitted, the label is - taken from the node name (excluding the unit address). +- label : The label for this LED. If omitted, the label is taken from the node + name (excluding the unit address). It has to uniquely identify + a device, i.e. no other LED class device can be assigned the same + label. - linux,default-trigger : This parameter, if present, is a string defining the trigger assigned to the LED. Current triggers are: diff --git a/Documentation/devicetree/bindings/leds/leds-gpio.txt b/Documentation/devicetree/bindings/leds/leds-gpio.txt index f77148f372ea..fea1ebfe24a9 100644 --- a/Documentation/devicetree/bindings/leds/leds-gpio.txt +++ b/Documentation/devicetree/bindings/leds/leds-gpio.txt @@ -26,16 +26,18 @@ LED sub-node properties: Examples: +#include <dt-bindings/gpio/gpio.h> + leds { compatible = "gpio-leds"; hdd { label = "IDE Activity"; - gpios = <&mcu_pio 0 1>; /* Active low */ + gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>; linux,default-trigger = "ide-disk"; }; fault { - gpios = <&mcu_pio 1 0>; + gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>; /* Keep LED on if BIOS detected hardware fault */ default-state = "keep"; }; @@ -44,11 +46,11 @@ leds { run-control { compatible = "gpio-leds"; red { - gpios = <&mpc8572 6 0>; + gpios = <&mpc8572 6 GPIO_ACTIVE_HIGH>; default-state = "off"; }; green { - gpios = <&mpc8572 7 0>; + gpios = <&mpc8572 7 GPIO_ACTIVE_HIGH>; default-state = "on"; }; }; @@ -57,7 +59,7 @@ leds { compatible = "gpio-leds"; charger-led { - gpios = <&gpio1 2 0>; + gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; linux,default-trigger = "max8903-charger-charging"; retain-state-suspended; }; diff --git a/Documentation/devicetree/bindings/leds/leds-pm8941-wled.txt b/Documentation/devicetree/bindings/leds/leds-pm8941-wled.txt new file mode 100644 index 000000000000..a85a964d61f5 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-pm8941-wled.txt @@ -0,0 +1,43 @@ +Binding for Qualcomm PM8941 WLED driver + +Required properties: +- compatible: should be "qcom,pm8941-wled" +- reg: slave address + +Optional properties: +- label: The label for this led + See Documentation/devicetree/bindings/leds/common.txt +- linux,default-trigger: Default trigger assigned to the LED + See Documentation/devicetree/bindings/leds/common.txt +- qcom,cs-out: bool; enable current sink output +- qcom,cabc: bool; enable content adaptive backlight control +- qcom,ext-gen: bool; use externally generated modulator signal to dim +- qcom,current-limit: mA; per-string current limit; value from 0 to 25 + default: 20mA +- qcom,current-boost-limit: mA; boost current limit; one of: + 105, 385, 525, 805, 980, 1260, 1400, 1680 + default: 805mA +- qcom,switching-freq: kHz; switching frequency; one of: + 600, 640, 685, 738, 800, 872, 960, 1066, 1200, 1371, + 1600, 1920, 2400, 3200, 4800, 9600, + default: 1600kHz +- qcom,ovp: V; Over-voltage protection limit; one of: + 27, 29, 32, 35 + default: 29V +- qcom,num-strings: #; number of led strings attached; value from 1 to 3 + default: 2 + +Example: + +pm8941-wled@d800 { + compatible = "qcom,pm8941-wled"; + reg = <0xd800>; + label = "backlight"; + + qcom,cs-out; + qcom,current-limit = <20>; + qcom,current-boost-limit = <805>; + qcom,switching-freq = <1600>; + qcom,ovp = <29>; + qcom,num-strings = <2>; +}; diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt index 6d1e8eeb5990..e1e2bbd7a404 100644 --- a/Documentation/driver-model/devres.txt +++ b/Documentation/driver-model/devres.txt @@ -289,6 +289,10 @@ IRQ devm_request_irq() devm_request_threaded_irq() +LED + devm_led_classdev_register() + devm_led_classdev_unregister() + MDIO devm_mdiobus_alloc() devm_mdiobus_alloc_size() diff --git a/Documentation/leds/leds-class-flash.txt b/Documentation/leds/leds-class-flash.txt new file mode 100644 index 000000000000..19bb67355424 --- /dev/null +++ b/Documentation/leds/leds-class-flash.txt @@ -0,0 +1,22 @@ + +Flash LED handling under Linux +============================== + +Some LED devices provide two modes - torch and flash. In the LED subsystem +those modes are supported by LED class (see Documentation/leds/leds-class.txt) +and LED Flash class respectively. The torch mode related features are enabled +by default and the flash ones only if a driver declares it by setting +LED_DEV_CAP_FLASH flag. + +In order to enable the support for flash LEDs CONFIG_LEDS_CLASS_FLASH symbol +must be defined in the kernel config. A LED Flash class driver must be +registered in the LED subsystem with led_classdev_flash_register function. + +Following sysfs attributes are exposed for controlling flash LED devices: +(see Documentation/ABI/testing/sysfs-class-led-flash) + - flash_brightness + - max_flash_brightness + - flash_timeout + - max_flash_timeout + - flash_strobe + - flash_fault diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 25b320d64e26..966b9605f5f0 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -526,6 +526,14 @@ config LEDS_VERSATILE This option enabled support for the LEDs on the ARM Versatile and RealView boards. Say Y to enabled these. +config LEDS_PM8941_WLED + tristate "LED support for the Qualcomm PM8941 WLED block" + depends on LEDS_CLASS + select REGMAP + help + This option enables support for the 'White' LED block + on Qualcomm PM8941 PMICs. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index cbba921b6f1c..bf4609338e10 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o +obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/led-class-flash.c b/drivers/leds/led-class-flash.c index 4a19fd44f93f..3b2573411a37 100644 --- a/drivers/leds/led-class-flash.c +++ b/drivers/leds/led-class-flash.c @@ -216,75 +216,6 @@ static ssize_t flash_fault_show(struct device *dev, } static DEVICE_ATTR_RO(flash_fault); -static ssize_t available_sync_leds_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); - char *pbuf = buf; - int i, buf_len; - - buf_len = sprintf(pbuf, "[0: none] "); - pbuf += buf_len; - - for (i = 0; i < fled_cdev->num_sync_leds; ++i) { - buf_len = sprintf(pbuf, "[%d: %s] ", i + 1, - fled_cdev->sync_leds[i]->led_cdev.name); - pbuf += buf_len; - } - - return sprintf(buf, "%s\n", buf); -} -static DEVICE_ATTR_RO(available_sync_leds); - -static ssize_t flash_sync_strobe_store(struct device *dev, - struct device_attribute *attr, const char *buf, size_t size) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); - unsigned long led_id; - ssize_t ret; - - mutex_lock(&led_cdev->led_access); - - if (led_sysfs_is_disabled(led_cdev)) { - ret = -EBUSY; - goto unlock; - } - - ret = kstrtoul(buf, 10, &led_id); - if (ret) - goto unlock; - - if (led_id > fled_cdev->num_sync_leds) { - ret = -ERANGE; - goto unlock; - } - - fled_cdev->sync_led_id = led_id; - - ret = size; -unlock: - mutex_unlock(&led_cdev->led_access); - return ret; -} - -static ssize_t flash_sync_strobe_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - struct led_classdev *led_cdev = dev_get_drvdata(dev); - struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); - int sled_id = fled_cdev->sync_led_id; - char *sync_led_name = "none"; - - if (fled_cdev->sync_led_id > 0) - sync_led_name = (char *) - fled_cdev->sync_leds[sled_id - 1]->led_cdev.name; - - return sprintf(buf, "[%d: %s]\n", sled_id, sync_led_name); -} -static DEVICE_ATTR_RW(flash_sync_strobe); - static struct attribute *led_flash_strobe_attrs[] = { &dev_attr_flash_strobe.attr, NULL, @@ -307,12 +238,6 @@ static struct attribute *led_flash_fault_attrs[] = { NULL, }; -static struct attribute *led_flash_sync_strobe_attrs[] = { - &dev_attr_available_sync_leds.attr, - &dev_attr_flash_sync_strobe.attr, - NULL, -}; - static const struct attribute_group led_flash_strobe_group = { .attrs = led_flash_strobe_attrs, }; @@ -329,10 +254,6 @@ static const struct attribute_group led_flash_fault_group = { .attrs = led_flash_fault_attrs, }; -static const struct attribute_group led_flash_sync_strobe_group = { - .attrs = led_flash_sync_strobe_attrs, -}; - static void led_flash_resume(struct led_classdev *led_cdev) { struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(led_cdev); @@ -361,9 +282,6 @@ static void led_flash_init_sysfs_groups(struct led_classdev_flash *fled_cdev) if (ops->fault_get) flash_groups[num_sysfs_groups++] = &led_flash_fault_group; - if (led_cdev->flags & LED_DEV_CAP_SYNC_STROBE) - flash_groups[num_sysfs_groups++] = &led_flash_sync_strobe_group; - led_cdev->groups = flash_groups; } diff --git a/drivers/leds/led-class.c b/drivers/leds/led-class.c index 795ec994c663..728681debdbe 100644 --- a/drivers/leds/led-class.c +++ b/drivers/leds/led-class.c @@ -212,6 +212,31 @@ static const struct dev_pm_ops leds_class_dev_pm_ops = { .resume = led_resume, }; +static int match_name(struct device *dev, const void *data) +{ + if (!dev_name(dev)) + return 0; + return !strcmp(dev_name(dev), (char *)data); +} + +static int led_classdev_next_name(const char *init_name, char *name, + size_t len) +{ + unsigned int i = 0; + int ret = 0; + + strlcpy(name, init_name, len); + + while (class_find_device(leds_class, NULL, name, match_name) && + (ret < len)) + ret = snprintf(name, len, "%s_%u", init_name, ++i); + + if (ret >= len) + return -ENOMEM; + + return i; +} + /** * led_classdev_register - register a new object of led_classdev class. * @parent: The device to register. @@ -219,12 +244,22 @@ static const struct dev_pm_ops leds_class_dev_pm_ops = { */ int led_classdev_register(struct device *parent, struct led_classdev *led_cdev) { + char name[64]; + int ret; + + ret = led_classdev_next_name(led_cdev->name, name, sizeof(name)); + if (ret < 0) + return ret; + led_cdev->dev = device_create_with_groups(leds_class, parent, 0, - led_cdev, led_cdev->groups, - "%s", led_cdev->name); + led_cdev, led_cdev->groups, "%s", name); if (IS_ERR(led_cdev->dev)) return PTR_ERR(led_cdev->dev); + if (ret) + dev_warn(parent, "Led %s renamed to %s due to name collision", + led_cdev->name, dev_name(led_cdev->dev)); + #ifdef CONFIG_LEDS_TRIGGERS init_rwsem(&led_cdev->trigger_lock); #endif @@ -288,6 +323,63 @@ void led_classdev_unregister(struct led_classdev *led_cdev) } EXPORT_SYMBOL_GPL(led_classdev_unregister); +static void devm_led_classdev_release(struct device *dev, void *res) +{ + led_classdev_unregister(*(struct led_classdev **)res); +} + +/** + * devm_led_classdev_register - resource managed led_classdev_register() + * @parent: The device to register. + * @led_cdev: the led_classdev structure for this device. + */ +int devm_led_classdev_register(struct device *parent, + struct led_classdev *led_cdev) +{ + struct led_classdev **dr; + int rc; + + dr = devres_alloc(devm_led_classdev_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rc = led_classdev_register(parent, led_cdev); + if (rc) { + devres_free(dr); + return rc; + } + + *dr = led_cdev; + devres_add(parent, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_led_classdev_register); + +static int devm_led_classdev_match(struct device *dev, void *res, void *data) +{ + struct led_cdev **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +/** + * devm_led_classdev_unregister() - resource managed led_classdev_unregister() + * @parent: The device to unregister. + * @led_cdev: the led_classdev structure for this device. + */ +void devm_led_classdev_unregister(struct device *dev, + struct led_classdev *led_cdev) +{ + WARN_ON(devres_release(dev, + devm_led_classdev_release, + devm_led_classdev_match, led_cdev)); +} +EXPORT_SYMBOL_GPL(devm_led_classdev_unregister); + static int __init leds_init(void) { leds_class = class_create(THIS_MODULE, "leds"); diff --git a/drivers/leds/leds-lp8501.c b/drivers/leds/leds-lp8501.c index 00f068b0fa6f..d3098e395fff 100644 --- a/drivers/leds/leds-lp8501.c +++ b/drivers/leds/leds-lp8501.c @@ -406,6 +406,6 @@ static struct i2c_driver lp8501_driver = { module_i2c_driver(lp8501_driver); -MODULE_DESCRIPTION("Texas Instruments LP8501 LED drvier"); +MODULE_DESCRIPTION("Texas Instruments LP8501 LED driver"); MODULE_AUTHOR("Milo Kim"); MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-lp8860.c b/drivers/leds/leds-lp8860.c index 840e93f3ab3e..8c2b7fbe2392 100644 --- a/drivers/leds/leds-lp8860.c +++ b/drivers/leds/leds-lp8860.c @@ -391,11 +391,13 @@ static int lp8860_probe(struct i2c_client *client, } } - led->enable_gpio = devm_gpiod_get(&client->dev, "enable"); - if (IS_ERR(led->enable_gpio)) - led->enable_gpio = NULL; - else - gpiod_direction_output(led->enable_gpio, 0); + led->enable_gpio = devm_gpiod_get_optional(&client->dev, + "enable", GPIOD_OUT_LOW); + if (IS_ERR(led->enable_gpio)) { + ret = PTR_ERR(led->enable_gpio); + dev_err(&client->dev, "Failed to get enable gpio: %d\n", ret); + return ret; + } led->regulator = devm_regulator_get(&client->dev, "vled"); if (IS_ERR(led->regulator)) @@ -486,6 +488,6 @@ static struct i2c_driver lp8860_driver = { }; module_i2c_driver(lp8860_driver); -MODULE_DESCRIPTION("Texas Instruments LP8860 LED drvier"); +MODULE_DESCRIPTION("Texas Instruments LP8860 LED driver"); MODULE_AUTHOR("Dan Murphy <dmurphy@ti.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/leds/leds-pca963x.c b/drivers/leds/leds-pca963x.c index f110b4c456ba..bee3e1ab27fd 100644 --- a/drivers/leds/leds-pca963x.c +++ b/drivers/leds/leds-pca963x.c @@ -289,7 +289,7 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip) return ERR_PTR(-ENOMEM); for_each_child_of_node(np, child) { - struct led_info led; + struct led_info led = {}; u32 reg; int res; diff --git a/drivers/leds/leds-pm8941-wled.c b/drivers/leds/leds-pm8941-wled.c new file mode 100644 index 000000000000..bf64a593fbf1 --- /dev/null +++ b/drivers/leds/leds-pm8941-wled.c @@ -0,0 +1,435 @@ +/* Copyright (c) 2015, Sony Mobile Communications, AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> + +#define PM8941_WLED_REG_VAL_BASE 0x40 +#define PM8941_WLED_REG_VAL_MAX 0xFFF + +#define PM8941_WLED_REG_MOD_EN 0x46 +#define PM8941_WLED_REG_MOD_EN_BIT BIT(7) +#define PM8941_WLED_REG_MOD_EN_MASK BIT(7) + +#define PM8941_WLED_REG_SYNC 0x47 +#define PM8941_WLED_REG_SYNC_MASK 0x07 +#define PM8941_WLED_REG_SYNC_LED1 BIT(0) +#define PM8941_WLED_REG_SYNC_LED2 BIT(1) +#define PM8941_WLED_REG_SYNC_LED3 BIT(2) +#define PM8941_WLED_REG_SYNC_ALL 0x07 +#define PM8941_WLED_REG_SYNC_CLEAR 0x00 + +#define PM8941_WLED_REG_FREQ 0x4c +#define PM8941_WLED_REG_FREQ_MASK 0x0f + +#define PM8941_WLED_REG_OVP 0x4d +#define PM8941_WLED_REG_OVP_MASK 0x03 + +#define PM8941_WLED_REG_BOOST 0x4e +#define PM8941_WLED_REG_BOOST_MASK 0x07 + +#define PM8941_WLED_REG_SINK 0x4f +#define PM8941_WLED_REG_SINK_MASK 0xe0 +#define PM8941_WLED_REG_SINK_SHFT 0x05 + +/* Per-'string' registers below */ +#define PM8941_WLED_REG_STR_OFFSET 0x10 + +#define PM8941_WLED_REG_STR_MOD_EN_BASE 0x60 +#define PM8941_WLED_REG_STR_MOD_MASK BIT(7) +#define PM8941_WLED_REG_STR_MOD_EN BIT(7) + +#define PM8941_WLED_REG_STR_SCALE_BASE 0x62 +#define PM8941_WLED_REG_STR_SCALE_MASK 0x1f + +#define PM8941_WLED_REG_STR_MOD_SRC_BASE 0x63 +#define PM8941_WLED_REG_STR_MOD_SRC_MASK 0x01 +#define PM8941_WLED_REG_STR_MOD_SRC_INT 0x00 +#define PM8941_WLED_REG_STR_MOD_SRC_EXT 0x01 + +#define PM8941_WLED_REG_STR_CABC_BASE 0x66 +#define PM8941_WLED_REG_STR_CABC_MASK BIT(7) +#define PM8941_WLED_REG_STR_CABC_EN BIT(7) + +struct pm8941_wled_config { + u32 i_boost_limit; + u32 ovp; + u32 switch_freq; + u32 num_strings; + u32 i_limit; + bool cs_out_en; + bool ext_gen; + bool cabc_en; +}; + +struct pm8941_wled { + struct regmap *regmap; + u16 addr; + + struct led_classdev cdev; + + struct pm8941_wled_config cfg; +}; + +static int pm8941_wled_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct pm8941_wled *wled; + u8 ctrl = 0; + u16 val; + int rc; + int i; + + wled = container_of(cdev, struct pm8941_wled, cdev); + + if (value != 0) + ctrl = PM8941_WLED_REG_MOD_EN_BIT; + + val = value * PM8941_WLED_REG_VAL_MAX / LED_FULL; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_MOD_EN, + PM8941_WLED_REG_MOD_EN_MASK, ctrl); + if (rc) + return rc; + + for (i = 0; i < wled->cfg.num_strings; ++i) { + u8 v[2] = { val & 0xff, (val >> 8) & 0xf }; + + rc = regmap_bulk_write(wled->regmap, + wled->addr + PM8941_WLED_REG_VAL_BASE + 2 * i, + v, 2); + if (rc) + return rc; + } + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_SYNC, + PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_ALL); + if (rc) + return rc; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_SYNC, + PM8941_WLED_REG_SYNC_MASK, PM8941_WLED_REG_SYNC_CLEAR); + return rc; +} + +static void pm8941_wled_set_brightness(struct led_classdev *cdev, + enum led_brightness value) +{ + if (pm8941_wled_set(cdev, value)) { + dev_err(cdev->dev, "Unable to set brightness\n"); + return; + } + cdev->brightness = value; +} + +static int pm8941_wled_setup(struct pm8941_wled *wled) +{ + int rc; + int i; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_OVP, + PM8941_WLED_REG_OVP_MASK, wled->cfg.ovp); + if (rc) + return rc; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_BOOST, + PM8941_WLED_REG_BOOST_MASK, wled->cfg.i_boost_limit); + if (rc) + return rc; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_FREQ, + PM8941_WLED_REG_FREQ_MASK, wled->cfg.switch_freq); + if (rc) + return rc; + + if (wled->cfg.cs_out_en) { + u8 all = (BIT(wled->cfg.num_strings) - 1) + << PM8941_WLED_REG_SINK_SHFT; + + rc = regmap_update_bits(wled->regmap, + wled->addr + PM8941_WLED_REG_SINK, + PM8941_WLED_REG_SINK_MASK, all); + if (rc) + return rc; + } + + for (i = 0; i < wled->cfg.num_strings; ++i) { + u16 addr = wled->addr + PM8941_WLED_REG_STR_OFFSET * i; + + rc = regmap_update_bits(wled->regmap, + addr + PM8941_WLED_REG_STR_MOD_EN_BASE, + PM8941_WLED_REG_STR_MOD_MASK, + PM8941_WLED_REG_STR_MOD_EN); + if (rc) + return rc; + + if (wled->cfg.ext_gen) { + rc = regmap_update_bits(wled->regmap, + addr + PM8941_WLED_REG_STR_MOD_SRC_BASE, + PM8941_WLED_REG_STR_MOD_SRC_MASK, + PM8941_WLED_REG_STR_MOD_SRC_EXT); + if (rc) + return rc; + } + + rc = regmap_update_bits(wled->regmap, + addr + PM8941_WLED_REG_STR_SCALE_BASE, + PM8941_WLED_REG_STR_SCALE_MASK, + wled->cfg.i_limit); + if (rc) + return rc; + + rc = regmap_update_bits(wled->regmap, + addr + PM8941_WLED_REG_STR_CABC_BASE, + PM8941_WLED_REG_STR_CABC_MASK, + wled->cfg.cabc_en ? + PM8941_WLED_REG_STR_CABC_EN : 0); + if (rc) + return rc; + } + + return 0; +} + +static const struct pm8941_wled_config pm8941_wled_config_defaults = { + .i_boost_limit = 3, + .i_limit = 20, + .ovp = 2, + .switch_freq = 5, + .num_strings = 0, + .cs_out_en = false, + .ext_gen = false, + .cabc_en = false, +}; + +struct pm8941_wled_var_cfg { + const u32 *values; + u32 (*fn)(u32); + int size; +}; + +static const u32 pm8941_wled_i_boost_limit_values[] = { + 105, 385, 525, 805, 980, 1260, 1400, 1680, +}; + +static const struct pm8941_wled_var_cfg pm8941_wled_i_boost_limit_cfg = { + .values = pm8941_wled_i_boost_limit_values, + .size = ARRAY_SIZE(pm8941_wled_i_boost_limit_values), +}; + +static const u32 pm8941_wled_ovp_values[] = { + 35, 32, 29, 27, +}; + +static const struct pm8941_wled_var_cfg pm8941_wled_ovp_cfg = { + .values = pm8941_wled_ovp_values, + .size = ARRAY_SIZE(pm8941_wled_ovp_values), +}; + +static u32 pm8941_wled_num_strings_values_fn(u32 idx) +{ + return idx + 1; +} + +static const struct pm8941_wled_var_cfg pm8941_wled_num_strings_cfg = { + .fn = pm8941_wled_num_strings_values_fn, + .size = 3, +}; + +static u32 pm8941_wled_switch_freq_values_fn(u32 idx) +{ + return 19200 / (2 * (1 + idx)); +} + +static const struct pm8941_wled_var_cfg pm8941_wled_switch_freq_cfg = { + .fn = pm8941_wled_switch_freq_values_fn, + .size = 16, +}; + +static const struct pm8941_wled_var_cfg pm8941_wled_i_limit_cfg = { + .size = 26, +}; + +static u32 pm8941_wled_values(const struct pm8941_wled_var_cfg *cfg, u32 idx) +{ + if (idx >= cfg->size) + return UINT_MAX; + if (cfg->fn) + return cfg->fn(idx); + if (cfg->values) + return cfg->values[idx]; + return idx; +} + +static int pm8941_wled_configure(struct pm8941_wled *wled, struct device *dev) +{ + struct pm8941_wled_config *cfg = &wled->cfg; + u32 val; + int rc; + u32 c; + int i; + int j; + + const struct { + const char *name; + u32 *val_ptr; + const struct pm8941_wled_var_cfg *cfg; + } u32_opts[] = { + { + "qcom,current-boost-limit", + &cfg->i_boost_limit, + .cfg = &pm8941_wled_i_boost_limit_cfg, + }, + { + "qcom,current-limit", + &cfg->i_limit, + .cfg = &pm8941_wled_i_limit_cfg, + }, + { + "qcom,ovp", + &cfg->ovp, + .cfg = &pm8941_wled_ovp_cfg, + }, + { + "qcom,switching-freq", + &cfg->switch_freq, + .cfg = &pm8941_wled_switch_freq_cfg, + }, + { + "qcom,num-strings", + &cfg->num_strings, + .cfg = &pm8941_wled_num_strings_cfg, + }, + }; + const struct { + const char *name; + bool *val_ptr; + } bool_opts[] = { + { "qcom,cs-out", &cfg->cs_out_en, }, + { "qcom,ext-gen", &cfg->ext_gen, }, + { "qcom,cabc", &cfg->cabc_en, }, + }; + + rc = of_property_read_u32(dev->of_node, "reg", &val); + if (rc || val > 0xffff) { + dev_err(dev, "invalid IO resources\n"); + return rc ? rc : -EINVAL; + } + wled->addr = val; + + rc = of_property_read_string(dev->of_node, "label", &wled->cdev.name); + if (rc) + wled->cdev.name = dev->of_node->name; + + wled->cdev.default_trigger = of_get_property(dev->of_node, + "linux,default-trigger", NULL); + + *cfg = pm8941_wled_config_defaults; + for (i = 0; i < ARRAY_SIZE(u32_opts); ++i) { + rc = of_property_read_u32(dev->of_node, u32_opts[i].name, &val); + if (rc == -EINVAL) { + continue; + } else if (rc) { + dev_err(dev, "error reading '%s'\n", u32_opts[i].name); + return rc; + } + + c = UINT_MAX; + for (j = 0; c != val; j++) { + c = pm8941_wled_values(u32_opts[i].cfg, j); + if (c == UINT_MAX) { + dev_err(dev, "invalid value for '%s'\n", + u32_opts[i].name); + return -EINVAL; + } + } + + dev_dbg(dev, "'%s' = %u\n", u32_opts[i].name, c); + *u32_opts[i].val_ptr = j; + } + + for (i = 0; i < ARRAY_SIZE(bool_opts); ++i) { + if (of_property_read_bool(dev->of_node, bool_opts[i].name)) + *bool_opts[i].val_ptr = true; + } + + cfg->num_strings = cfg->num_strings + 1; + + return 0; +} + +static int pm8941_wled_probe(struct platform_device *pdev) +{ + struct pm8941_wled *wled; + struct regmap *regmap; + int rc; + + regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!regmap) { + dev_err(&pdev->dev, "Unable to get regmap\n"); + return -EINVAL; + } + + wled = devm_kzalloc(&pdev->dev, sizeof(*wled), GFP_KERNEL); + if (!wled) + return -ENOMEM; + + wled->regmap = regmap; + + rc = pm8941_wled_configure(wled, &pdev->dev); + if (rc) + return rc; + + rc = pm8941_wled_setup(wled); + if (rc) + return rc; + + wled->cdev.brightness_set = pm8941_wled_set_brightness; + + rc = devm_led_classdev_register(&pdev->dev, &wled->cdev); + if (rc) + return rc; + + platform_set_drvdata(pdev, wled); + + return 0; +}; + +static const struct of_device_id pm8941_wled_match_table[] = { + { .compatible = "qcom,pm8941-wled" }, + {} +}; +MODULE_DEVICE_TABLE(of, pm8941_wled_match_table); + +static struct platform_driver pm8941_wled_driver = { + .probe = pm8941_wled_probe, + .driver = { + .name = "pm8941-wled", + .of_match_table = pm8941_wled_match_table, + }, +}; + +module_platform_driver(pm8941_wled_driver); + +MODULE_DESCRIPTION("pm8941 wled driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pm8941-wled"); diff --git a/drivers/leds/leds-pwm.c b/drivers/leds/leds-pwm.c index f668500a2157..1d07e3e83d29 100644 --- a/drivers/leds/leds-pwm.c +++ b/drivers/leds/leds-pwm.c @@ -121,9 +121,6 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv, return ret; } - if (child) - led_data->period = pwm_get_period(led_data->pwm); - led_data->can_sleep = pwm_can_sleep(led_data->pwm); if (led_data->can_sleep) INIT_WORK(&led_data->work, led_pwm_work); diff --git a/include/dt-bindings/leds/common.h b/include/dt-bindings/leds/common.h new file mode 100644 index 000000000000..79fcef72ef57 --- /dev/null +++ b/include/dt-bindings/leds/common.h @@ -0,0 +1,21 @@ +/* + * This header provides macros for the common LEDs device tree bindings. + * + * Copyright (C) 2015, Samsung Electronics Co., Ltd. + * + * Author: Jacek Anaszewski <j.anaszewski@samsung.com> + */ + +#ifndef __DT_BINDINGS_LEDS_H__ +#define __DT_BINDINGS_LEDS_H + +/* External trigger type */ +#define LEDS_TRIG_TYPE_EDGE 0 +#define LEDS_TRIG_TYPE_LEVEL 1 + +/* Boost modes */ +#define LEDS_BOOST_OFF 0 +#define LEDS_BOOST_ADAPTIVE 1 +#define LEDS_BOOST_FIXED 2 + +#endif /* __DT_BINDINGS_LEDS_H */ diff --git a/include/linux/led-class-flash.h b/include/linux/led-class-flash.h index 5ba2facd7a51..e97966d1fb8d 100644 --- a/include/linux/led-class-flash.h +++ b/include/linux/led-class-flash.h @@ -13,7 +13,6 @@ #define __LINUX_FLASH_LEDS_H_INCLUDED #include <linux/leds.h> -#include <uapi/linux/v4l2-controls.h> struct device_node; struct led_classdev_flash; @@ -33,7 +32,7 @@ struct led_classdev_flash; #define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8) #define LED_NUM_FLASH_FAULTS 9 -#define LED_FLASH_MAX_SYSFS_GROUPS 7 +#define LED_FLASH_SYSFS_GROUPS_SIZE 5 struct led_flash_ops { /* set flash brightness */ @@ -81,21 +80,7 @@ struct led_classdev_flash { struct led_flash_setting timeout; /* LED Flash class sysfs groups */ - const struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS]; - - /* LEDs available for flash strobe synchronization */ - struct led_classdev_flash **sync_leds; - - /* Number of LEDs available for flash strobe synchronization */ - int num_sync_leds; - - /* - * The identifier of the sub-led to synchronize the flash strobe with. - * Identifiers start from 1, which reflects the first element from the - * sync_leds array. 0 means that the flash strobe should not be - * synchronized. - */ - u32 sync_led_id; + const struct attribute_group *sysfs_groups[LED_FLASH_SYSFS_GROUPS_SIZE]; }; static inline struct led_classdev_flash *lcdev_to_flcdev( diff --git a/include/linux/leds.h b/include/linux/leds.h index f70f84f35674..9a2b000094cf 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -47,7 +47,6 @@ struct led_classdev { #define SET_BRIGHTNESS_ASYNC (1 << 21) #define SET_BRIGHTNESS_SYNC (1 << 22) #define LED_DEV_CAP_FLASH (1 << 23) -#define LED_DEV_CAP_SYNC_STROBE (1 << 24) /* Set LED brightness level */ /* Must not sleep, use a workqueue if needed */ @@ -105,7 +104,11 @@ struct led_classdev { extern int led_classdev_register(struct device *parent, struct led_classdev *led_cdev); +extern int devm_led_classdev_register(struct device *parent, + struct led_classdev *led_cdev); extern void led_classdev_unregister(struct led_classdev *led_cdev); +extern void devm_led_classdev_unregister(struct device *parent, + struct led_classdev *led_cdev); extern void led_classdev_suspend(struct led_classdev *led_cdev); extern void led_classdev_resume(struct led_classdev *led_cdev); |