Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds

Pull LED subsystem updates from Bryan Wu:
 "In this cycle, we merged some fix and update for LED Flash class
  driver.  Then the core code of LED Flash class driver is in the kernel
  now.  Moreover, we also got some bug fixes, code cleanup and new
  drivers for LED controllers"

* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/cooloney/linux-leds:
  leds: Don't treat the LED name as a format string
  leds: Use log level warn instead of info when telling about a name clash
  leds/led-class: Handle LEDs with the same name
  leds: lp8860: Fix typo in MODULE_DESCRIPTION in leds-lp8860.c
  leds: lp8501: Fix typo in MODULE_DESCRIPTION in leds-lp8501.c
  DT: leds: Add uniqueness requirement for 'label' property.
  dt-binding: leds: Add common LED DT bindings macros
  leds: add Qualcomm PM8941 WLED driver
  leds: add DT binding for Qualcomm PM8941 WLED block
  leds: pca963x: Add missing initialiation of struct led_info.flags
  leds: flash: Fix the size of sysfs_groups array
  Documentation: leds: Add description of LED Flash class extension
  leds: flash: document sysfs interface
  leds: flash: Remove synchronized flash strobe feature
  leds: Introduce devres helper for led_classdev_register
  leds: lp8860: make use of devm_gpiod_get_optional
  leds: Let the binding document example for leds-gpio follow the gpio bindings
  leds: flash: remove stray include directive
  leds: leds-pwm: drop one pwm_get_period() call
This commit is contained in:
Linus Torvalds 2015-04-15 15:48:28 -07:00
commit e7c8241243
18 changed files with 735 additions and 120 deletions

View File

@ -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

View File

@ -14,8 +14,10 @@ Optional properties for child nodes:
- led-sources : List of device current outputs the LED is connected to. The - led-sources : List of device current outputs the LED is connected to. The
outputs are identified by the numbers that must be defined outputs are identified by the numbers that must be defined
in the LED device binding documentation. in the LED device binding documentation.
- label : The label for this LED. If omitted, the label is - label : The label for this LED. If omitted, the label is taken from the node
taken from the node name (excluding the unit address). 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 - linux,default-trigger : This parameter, if present, is a
string defining the trigger assigned to the LED. Current triggers are: string defining the trigger assigned to the LED. Current triggers are:

View File

@ -26,16 +26,18 @@ LED sub-node properties:
Examples: Examples:
#include <dt-bindings/gpio/gpio.h>
leds { leds {
compatible = "gpio-leds"; compatible = "gpio-leds";
hdd { hdd {
label = "IDE Activity"; label = "IDE Activity";
gpios = <&mcu_pio 0 1>; /* Active low */ gpios = <&mcu_pio 0 GPIO_ACTIVE_LOW>;
linux,default-trigger = "ide-disk"; linux,default-trigger = "ide-disk";
}; };
fault { fault {
gpios = <&mcu_pio 1 0>; gpios = <&mcu_pio 1 GPIO_ACTIVE_HIGH>;
/* Keep LED on if BIOS detected hardware fault */ /* Keep LED on if BIOS detected hardware fault */
default-state = "keep"; default-state = "keep";
}; };
@ -44,11 +46,11 @@ leds {
run-control { run-control {
compatible = "gpio-leds"; compatible = "gpio-leds";
red { red {
gpios = <&mpc8572 6 0>; gpios = <&mpc8572 6 GPIO_ACTIVE_HIGH>;
default-state = "off"; default-state = "off";
}; };
green { green {
gpios = <&mpc8572 7 0>; gpios = <&mpc8572 7 GPIO_ACTIVE_HIGH>;
default-state = "on"; default-state = "on";
}; };
}; };
@ -57,7 +59,7 @@ leds {
compatible = "gpio-leds"; compatible = "gpio-leds";
charger-led { charger-led {
gpios = <&gpio1 2 0>; gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "max8903-charger-charging"; linux,default-trigger = "max8903-charger-charging";
retain-state-suspended; retain-state-suspended;
}; };

View File

@ -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>;
};

View File

@ -289,6 +289,10 @@ IRQ
devm_request_irq() devm_request_irq()
devm_request_threaded_irq() devm_request_threaded_irq()
LED
devm_led_classdev_register()
devm_led_classdev_unregister()
MDIO MDIO
devm_mdiobus_alloc() devm_mdiobus_alloc()
devm_mdiobus_alloc_size() devm_mdiobus_alloc_size()

View File

@ -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

View File

@ -526,6 +526,14 @@ config LEDS_VERSATILE
This option enabled support for the LEDs on the ARM Versatile This option enabled support for the LEDs on the ARM Versatile
and RealView boards. Say Y to enabled these. 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" comment "LED Triggers"
source "drivers/leds/trigger/Kconfig" source "drivers/leds/trigger/Kconfig"

View File

@ -58,6 +58,7 @@ obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o
# LED SPI Drivers # LED SPI Drivers
obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o

View File

@ -216,75 +216,6 @@ static ssize_t flash_fault_show(struct device *dev,
} }
static DEVICE_ATTR_RO(flash_fault); 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[] = { static struct attribute *led_flash_strobe_attrs[] = {
&dev_attr_flash_strobe.attr, &dev_attr_flash_strobe.attr,
NULL, NULL,
@ -307,12 +238,6 @@ static struct attribute *led_flash_fault_attrs[] = {
NULL, 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 = { static const struct attribute_group led_flash_strobe_group = {
.attrs = led_flash_strobe_attrs, .attrs = led_flash_strobe_attrs,
}; };
@ -329,10 +254,6 @@ static const struct attribute_group led_flash_fault_group = {
.attrs = led_flash_fault_attrs, .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) static void led_flash_resume(struct led_classdev *led_cdev)
{ {
struct led_classdev_flash *fled_cdev = lcdev_to_flcdev(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) if (ops->fault_get)
flash_groups[num_sysfs_groups++] = &led_flash_fault_group; 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; led_cdev->groups = flash_groups;
} }

View File

@ -212,6 +212,31 @@ static const struct dev_pm_ops leds_class_dev_pm_ops = {
.resume = led_resume, .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. * led_classdev_register - register a new object of led_classdev class.
* @parent: The device to register. * @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) 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->dev = device_create_with_groups(leds_class, parent, 0,
led_cdev, led_cdev->groups, led_cdev, led_cdev->groups, "%s", name);
"%s", led_cdev->name);
if (IS_ERR(led_cdev->dev)) if (IS_ERR(led_cdev->dev))
return PTR_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 #ifdef CONFIG_LEDS_TRIGGERS
init_rwsem(&led_cdev->trigger_lock); init_rwsem(&led_cdev->trigger_lock);
#endif #endif
@ -288,6 +323,63 @@ void led_classdev_unregister(struct led_classdev *led_cdev)
} }
EXPORT_SYMBOL_GPL(led_classdev_unregister); 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) static int __init leds_init(void)
{ {
leds_class = class_create(THIS_MODULE, "leds"); leds_class = class_create(THIS_MODULE, "leds");

View File

@ -406,6 +406,6 @@ static struct i2c_driver lp8501_driver = {
module_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_AUTHOR("Milo Kim");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");

View File

@ -391,11 +391,13 @@ static int lp8860_probe(struct i2c_client *client,
} }
} }
led->enable_gpio = devm_gpiod_get(&client->dev, "enable"); led->enable_gpio = devm_gpiod_get_optional(&client->dev,
if (IS_ERR(led->enable_gpio)) "enable", GPIOD_OUT_LOW);
led->enable_gpio = NULL; if (IS_ERR(led->enable_gpio)) {
else ret = PTR_ERR(led->enable_gpio);
gpiod_direction_output(led->enable_gpio, 0); dev_err(&client->dev, "Failed to get enable gpio: %d\n", ret);
return ret;
}
led->regulator = devm_regulator_get(&client->dev, "vled"); led->regulator = devm_regulator_get(&client->dev, "vled");
if (IS_ERR(led->regulator)) if (IS_ERR(led->regulator))
@ -486,6 +488,6 @@ static struct i2c_driver lp8860_driver = {
}; };
module_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_AUTHOR("Dan Murphy <dmurphy@ti.com>");
MODULE_LICENSE("GPL"); MODULE_LICENSE("GPL");

View File

@ -289,7 +289,7 @@ pca963x_dt_init(struct i2c_client *client, struct pca963x_chipdef *chip)
return ERR_PTR(-ENOMEM); return ERR_PTR(-ENOMEM);
for_each_child_of_node(np, child) { for_each_child_of_node(np, child) {
struct led_info led; struct led_info led = {};
u32 reg; u32 reg;
int res; int res;

View File

@ -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");

View File

@ -121,9 +121,6 @@ static int led_pwm_add(struct device *dev, struct led_pwm_priv *priv,
return ret; return ret;
} }
if (child)
led_data->period = pwm_get_period(led_data->pwm);
led_data->can_sleep = pwm_can_sleep(led_data->pwm); led_data->can_sleep = pwm_can_sleep(led_data->pwm);
if (led_data->can_sleep) if (led_data->can_sleep)
INIT_WORK(&led_data->work, led_pwm_work); INIT_WORK(&led_data->work, led_pwm_work);

View File

@ -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 */

View File

@ -13,7 +13,6 @@
#define __LINUX_FLASH_LEDS_H_INCLUDED #define __LINUX_FLASH_LEDS_H_INCLUDED
#include <linux/leds.h> #include <linux/leds.h>
#include <uapi/linux/v4l2-controls.h>
struct device_node; struct device_node;
struct led_classdev_flash; struct led_classdev_flash;
@ -33,7 +32,7 @@ struct led_classdev_flash;
#define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8) #define LED_FAULT_LED_OVER_TEMPERATURE (1 << 8)
#define LED_NUM_FLASH_FAULTS 9 #define LED_NUM_FLASH_FAULTS 9
#define LED_FLASH_MAX_SYSFS_GROUPS 7 #define LED_FLASH_SYSFS_GROUPS_SIZE 5
struct led_flash_ops { struct led_flash_ops {
/* set flash brightness */ /* set flash brightness */
@ -81,21 +80,7 @@ struct led_classdev_flash {
struct led_flash_setting timeout; struct led_flash_setting timeout;
/* LED Flash class sysfs groups */ /* LED Flash class sysfs groups */
const struct attribute_group *sysfs_groups[LED_FLASH_MAX_SYSFS_GROUPS]; const struct attribute_group *sysfs_groups[LED_FLASH_SYSFS_GROUPS_SIZE];
/* 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;
}; };
static inline struct led_classdev_flash *lcdev_to_flcdev( static inline struct led_classdev_flash *lcdev_to_flcdev(

View File

@ -47,7 +47,6 @@ struct led_classdev {
#define SET_BRIGHTNESS_ASYNC (1 << 21) #define SET_BRIGHTNESS_ASYNC (1 << 21)
#define SET_BRIGHTNESS_SYNC (1 << 22) #define SET_BRIGHTNESS_SYNC (1 << 22)
#define LED_DEV_CAP_FLASH (1 << 23) #define LED_DEV_CAP_FLASH (1 << 23)
#define LED_DEV_CAP_SYNC_STROBE (1 << 24)
/* Set LED brightness level */ /* Set LED brightness level */
/* Must not sleep, use a workqueue if needed */ /* Must not sleep, use a workqueue if needed */
@ -105,7 +104,11 @@ struct led_classdev {
extern int led_classdev_register(struct device *parent, extern int led_classdev_register(struct device *parent,
struct led_classdev *led_cdev); 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 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_suspend(struct led_classdev *led_cdev);
extern void led_classdev_resume(struct led_classdev *led_cdev); extern void led_classdev_resume(struct led_classdev *led_cdev);