diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-04 10:25:53 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2016-10-04 10:25:53 -0700 |
commit | f80fa1822d6ccca369578108dc70576cff6c67a0 (patch) | |
tree | 52f048d60d00ea6f44c70ef8f4dc0d69247cc8ad /drivers | |
parent | 21f54ddae449f4bdd9f1498124901d67202243d9 (diff) | |
parent | 6f3bad9670729ea3a7c78b3752a89c94ffa2397a (diff) |
Merge branch 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds
Pull LED driver updates from Jacek Anaszewski:
"Three new LED class drivers and some minor fixes and improvementes to
the leds-gpio driver, LED Trigger core and documentation"
* 'for-next' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds:
leds: triggers: Check return value of kobject_uevent_env()
leds: triggers: Return from led_trigger_set() if there is nothing to do
leds: gpio: fix and simplify error handling in gpio_leds_create
leds: gpio: switch to managed version of led_classdev_register
leds: gpio: fix and simplify reading property "label"
leds: gpio: simplify gpio_leds_create
leds: gpio: add helper cdev_to_gpio_led_data
leds: gpio: fix an unhandled error case in create_gpio_led
leds: gpio: introduce gpio_blink_set_t
leds: add driver for Mellanox systems LEDs
Documentation: move oneshot trigger attributes documentation to ABI
leds: centralize definition of "default-state" property
leds: add PM8058 LEDs driver
leds: pm8058: add device tree bindings
leds: do not overflow sysfs buffer in led_trigger_show
leds: make triggers explicitly non-modular
DT: leds: Add bindings for ISSI is31fl319x
leds: is31fl319x: 1/3/6/9-channel light effect led driver
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/leds/Kconfig | 28 | ||||
-rw-r--r-- | drivers/leds/Makefile | 3 | ||||
-rw-r--r-- | drivers/leds/led-triggers.c | 25 | ||||
-rw-r--r-- | drivers/leds/leds-gpio.c | 78 | ||||
-rw-r--r-- | drivers/leds/leds-is31fl319x.c | 450 | ||||
-rw-r--r-- | drivers/leds/leds-mlxcpld.c | 430 | ||||
-rw-r--r-- | drivers/leds/leds-pm8058.c | 191 |
7 files changed, 1144 insertions, 61 deletions
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9dcc9b13d495..7a628c6516f6 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -584,6 +584,18 @@ config LEDS_SEAD3 This driver can also be built as a module. If so the module will be called leds-sead3. +config LEDS_IS31FL319X + tristate "LED Support for ISSI IS31FL319x I2C LED controller family" + depends on LEDS_CLASS && I2C && OF + select REGMAP_I2C + help + This option enables support for LEDs connected to ISSI IS31FL319x + fancy LED driver chips accessed via the I2C bus. + Driver supports individual PWM brightness control for each channel. + + This driver can also be built as a module. If so the module will be + called leds-is31fl319x. + config LEDS_IS31FL32XX tristate "LED support for ISSI IS31FL32XX I2C LED controller family" depends on LEDS_CLASS && I2C && OF @@ -631,6 +643,22 @@ config LEDS_VERSATILE This option enabled support for the LEDs on the ARM Versatile and RealView boards. Say Y to enabled these. +config LEDS_PM8058 + tristate "LED Support for the Qualcomm PM8058 PMIC" + depends on MFD_PM8921_CORE + depends on LEDS_CLASS + help + Choose this option if you want to use the LED drivers in + the Qualcomm PM8058 PMIC. + +config LEDS_MLXCPLD + tristate "LED support for the Mellanox boards" + depends on X86_64 && DMI + depends on LEDS_CLASS + help + This option enabled support for the LEDs on the Mellanox + boards. Say Y to enabled these. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 0684c865a1c0..3965070190f5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -67,7 +67,10 @@ 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_IS31FL319X) += leds-is31fl319x.o obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o +obj-$(CONFIG_LEDS_PM8058) += leds-pm8058.o +obj-$(CONFIG_LEDS_MLXCPLD) += leds-mlxcpld.o # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index c92702a684ce..431123b048a2 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -11,7 +11,7 @@ * */ -#include <linux/module.h> +#include <linux/export.h> #include <linux/kernel.h> #include <linux/list.h> #include <linux/spinlock.h> @@ -81,21 +81,23 @@ ssize_t led_trigger_show(struct device *dev, struct device_attribute *attr, down_read(&led_cdev->trigger_lock); if (!led_cdev->trigger) - len += sprintf(buf+len, "[none] "); + len += scnprintf(buf+len, PAGE_SIZE - len, "[none] "); else - len += sprintf(buf+len, "none "); + len += scnprintf(buf+len, PAGE_SIZE - len, "none "); list_for_each_entry(trig, &trigger_list, next_trig) { if (led_cdev->trigger && !strcmp(led_cdev->trigger->name, trig->name)) - len += sprintf(buf+len, "[%s] ", trig->name); + len += scnprintf(buf+len, PAGE_SIZE - len, "[%s] ", + trig->name); else - len += sprintf(buf+len, "%s ", trig->name); + len += scnprintf(buf+len, PAGE_SIZE - len, "%s ", + trig->name); } up_read(&led_cdev->trigger_lock); up_read(&triggers_list_lock); - len += sprintf(len+buf, "\n"); + len += scnprintf(len+buf, PAGE_SIZE - len, "\n"); return len; } EXPORT_SYMBOL_GPL(led_trigger_show); @@ -108,6 +110,9 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) char *envp[2]; const char *name; + if (!led_cdev->trigger && !trig) + return; + name = trig ? trig->name : "none"; event = kasprintf(GFP_KERNEL, "TRIGGER=%s", name); @@ -136,7 +141,9 @@ void led_trigger_set(struct led_classdev *led_cdev, struct led_trigger *trig) if (event) { envp[0] = event; envp[1] = NULL; - kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp); + if (kobject_uevent_env(&led_cdev->dev->kobj, KOBJ_CHANGE, envp)) + dev_err(led_cdev->dev, + "%s: Error sending uevent\n", __func__); kfree(event); } } @@ -357,7 +364,3 @@ void led_trigger_unregister_simple(struct led_trigger *trig) kfree(trig); } EXPORT_SYMBOL_GPL(led_trigger_unregister_simple); - -MODULE_AUTHOR("Richard Purdie"); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("LED Triggers Core"); diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 9b991d46ed84..d400dcaf4d29 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -26,15 +26,19 @@ struct gpio_led_data { struct gpio_desc *gpiod; u8 can_sleep; u8 blinking; - int (*platform_gpio_blink_set)(struct gpio_desc *desc, int state, - unsigned long *delay_on, unsigned long *delay_off); + gpio_blink_set_t platform_gpio_blink_set; }; +static inline struct gpio_led_data * + cdev_to_gpio_led_data(struct led_classdev *led_cdev) +{ + return container_of(led_cdev, struct gpio_led_data, cdev); +} + static void gpio_led_set(struct led_classdev *led_cdev, enum led_brightness value) { - struct gpio_led_data *led_dat = - container_of(led_cdev, struct gpio_led_data, cdev); + struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); int level; if (value == LED_OFF) @@ -64,8 +68,7 @@ static int gpio_led_set_blocking(struct led_classdev *led_cdev, static int gpio_blink_set(struct led_classdev *led_cdev, unsigned long *delay_on, unsigned long *delay_off) { - struct gpio_led_data *led_dat = - container_of(led_cdev, struct gpio_led_data, cdev); + struct gpio_led_data *led_dat = cdev_to_gpio_led_data(led_cdev); led_dat->blinking = 1; return led_dat->platform_gpio_blink_set(led_dat->gpiod, GPIO_LED_BLINK, @@ -74,8 +77,7 @@ static int gpio_blink_set(struct led_classdev *led_cdev, static int create_gpio_led(const struct gpio_led *template, struct gpio_led_data *led_dat, struct device *parent, - int (*blink_set)(struct gpio_desc *, int, unsigned long *, - unsigned long *)) + gpio_blink_set_t blink_set) { int ret, state; @@ -120,10 +122,13 @@ static int create_gpio_led(const struct gpio_led *template, led_dat->platform_gpio_blink_set = blink_set; led_dat->cdev.blink_set = gpio_blink_set; } - if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) - state = !!gpiod_get_value_cansleep(led_dat->gpiod); - else + if (template->default_state == LEDS_GPIO_DEFSTATE_KEEP) { + state = gpiod_get_value_cansleep(led_dat->gpiod); + if (state < 0) + return state; + } else { state = (template->default_state == LEDS_GPIO_DEFSTATE_ON); + } led_dat->cdev.brightness = state ? LED_FULL : LED_OFF; if (!template->retain_state_suspended) led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; @@ -134,7 +139,7 @@ static int create_gpio_led(const struct gpio_led *template, if (ret < 0) return ret; - return led_classdev_register(parent, &led_dat->cdev); + return devm_led_classdev_register(parent, &led_dat->cdev); } struct gpio_leds_priv { @@ -154,7 +159,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) struct fwnode_handle *child; struct gpio_leds_priv *priv; int count, ret; - struct device_node *np; count = device_get_child_node_count(dev); if (!count) @@ -168,26 +172,22 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) struct gpio_led_data *led_dat = &priv->leds[priv->num_leds]; struct gpio_led led = {}; const char *state = NULL; + struct device_node *np = to_of_node(child); led.gpiod = devm_get_gpiod_from_child(dev, NULL, child); if (IS_ERR(led.gpiod)) { fwnode_handle_put(child); - ret = PTR_ERR(led.gpiod); - goto err; + return ERR_CAST(led.gpiod); } - np = to_of_node(child); - - if (fwnode_property_present(child, "label")) { - fwnode_property_read_string(child, "label", &led.name); - } else { - if (IS_ENABLED(CONFIG_OF) && !led.name && np) - led.name = np->name; - if (!led.name) { - ret = -EINVAL; - goto err; - } + ret = fwnode_property_read_string(child, "label", &led.name); + if (ret && IS_ENABLED(CONFIG_OF) && np) + led.name = np->name; + if (!led.name) { + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); } + fwnode_property_read_string(child, "linux,default-trigger", &led.default_trigger); @@ -209,18 +209,13 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) ret = create_gpio_led(&led, led_dat, dev, NULL); if (ret < 0) { fwnode_handle_put(child); - goto err; + return ERR_PTR(ret); } led_dat->cdev.dev->of_node = np; priv->num_leds++; } return priv; - -err: - for (count = priv->num_leds - 1; count >= 0; count--) - led_classdev_unregister(&priv->leds[count].cdev); - return ERR_PTR(ret); } static const struct of_device_id of_gpio_leds_match[] = { @@ -248,13 +243,8 @@ static int gpio_led_probe(struct platform_device *pdev) ret = create_gpio_led(&pdata->leds[i], &priv->leds[i], &pdev->dev, pdata->gpio_blink_set); - if (ret < 0) { - /* On failure: unwind the led creations */ - for (i = i - 1; i >= 0; i--) - led_classdev_unregister( - &priv->leds[i].cdev); + if (ret < 0) return ret; - } } } else { priv = gpio_leds_create(pdev); @@ -267,17 +257,6 @@ static int gpio_led_probe(struct platform_device *pdev) return 0; } -static int gpio_led_remove(struct platform_device *pdev) -{ - struct gpio_leds_priv *priv = platform_get_drvdata(pdev); - int i; - - for (i = 0; i < priv->num_leds; i++) - led_classdev_unregister(&priv->leds[i].cdev); - - return 0; -} - static void gpio_led_shutdown(struct platform_device *pdev) { struct gpio_leds_priv *priv = platform_get_drvdata(pdev); @@ -292,7 +271,6 @@ static void gpio_led_shutdown(struct platform_device *pdev) static struct platform_driver gpio_led_driver = { .probe = gpio_led_probe, - .remove = gpio_led_remove, .shutdown = gpio_led_shutdown, .driver = { .name = "leds-gpio", diff --git a/drivers/leds/leds-is31fl319x.c b/drivers/leds/leds-is31fl319x.c new file mode 100644 index 000000000000..f123309597e4 --- /dev/null +++ b/drivers/leds/leds-is31fl319x.c @@ -0,0 +1,450 @@ +/* + * Copyright 2015-16 Golden Delicious Computers + * + * Author: Nikolaus Schaller <hns@goldelico.com> + * + * This file is subject to the terms and conditions of version 2 of + * the GNU General Public License. See the file COPYING in the main + * directory of this archive for more details. + * + * LED driver for the IS31FL319{0,1,3,6,9} to drive 1, 3, 6 or 9 light + * effect LEDs. + * + */ + +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/regmap.h> +#include <linux/slab.h> + +/* register numbers */ +#define IS31FL319X_SHUTDOWN 0x00 +#define IS31FL319X_CTRL1 0x01 +#define IS31FL319X_CTRL2 0x02 +#define IS31FL319X_CONFIG1 0x03 +#define IS31FL319X_CONFIG2 0x04 +#define IS31FL319X_RAMP_MODE 0x05 +#define IS31FL319X_BREATH_MASK 0x06 +#define IS31FL319X_PWM(channel) (0x07 + channel) +#define IS31FL319X_DATA_UPDATE 0x10 +#define IS31FL319X_T0(channel) (0x11 + channel) +#define IS31FL319X_T123_1 0x1a +#define IS31FL319X_T123_2 0x1b +#define IS31FL319X_T123_3 0x1c +#define IS31FL319X_T4(channel) (0x1d + channel) +#define IS31FL319X_TIME_UPDATE 0x26 +#define IS31FL319X_RESET 0xff + +#define IS31FL319X_REG_CNT (IS31FL319X_RESET + 1) + +#define IS31FL319X_MAX_LEDS 9 + +/* CS (Current Setting) in CONFIG2 register */ +#define IS31FL319X_CONFIG2_CS_SHIFT 4 +#define IS31FL319X_CONFIG2_CS_MASK 0x7 +#define IS31FL319X_CONFIG2_CS_STEP_REF 12 + +#define IS31FL319X_CURRENT_MIN ((u32)5000) +#define IS31FL319X_CURRENT_MAX ((u32)40000) +#define IS31FL319X_CURRENT_STEP ((u32)5000) +#define IS31FL319X_CURRENT_DEFAULT ((u32)20000) + +/* Audio gain in CONFIG2 register */ +#define IS31FL319X_AUDIO_GAIN_DB_MAX ((u32)21) +#define IS31FL319X_AUDIO_GAIN_DB_STEP ((u32)3) + +/* + * regmap is used as a cache of chip's register space, + * to avoid reading back brightness values from chip, + * which is known to hang. + */ +struct is31fl319x_chip { + const struct is31fl319x_chipdef *cdef; + struct i2c_client *client; + struct regmap *regmap; + struct mutex lock; + u32 audio_gain_db; + + struct is31fl319x_led { + struct is31fl319x_chip *chip; + struct led_classdev cdev; + u32 max_microamp; + bool configured; + } leds[IS31FL319X_MAX_LEDS]; +}; + +struct is31fl319x_chipdef { + int num_leds; +}; + +static const struct is31fl319x_chipdef is31fl3190_cdef = { + .num_leds = 1, +}; + +static const struct is31fl319x_chipdef is31fl3193_cdef = { + .num_leds = 3, +}; + +static const struct is31fl319x_chipdef is31fl3196_cdef = { + .num_leds = 6, +}; + +static const struct is31fl319x_chipdef is31fl3199_cdef = { + .num_leds = 9, +}; + +static const struct of_device_id of_is31fl319x_match[] = { + { .compatible = "issi,is31fl3190", .data = &is31fl3190_cdef, }, + { .compatible = "issi,is31fl3191", .data = &is31fl3190_cdef, }, + { .compatible = "issi,is31fl3193", .data = &is31fl3193_cdef, }, + { .compatible = "issi,is31fl3196", .data = &is31fl3196_cdef, }, + { .compatible = "issi,is31fl3199", .data = &is31fl3199_cdef, }, + { .compatible = "si-en,sn3199", .data = &is31fl3199_cdef, }, + { } +}; +MODULE_DEVICE_TABLE(of, of_is31fl319x_match); + +static int is31fl319x_brightness_set(struct led_classdev *cdev, + enum led_brightness brightness) +{ + struct is31fl319x_led *led = container_of(cdev, struct is31fl319x_led, + cdev); + struct is31fl319x_chip *is31 = led->chip; + int chan = led - is31->leds; + int ret; + int i; + u8 ctrl1 = 0, ctrl2 = 0; + + dev_dbg(&is31->client->dev, "%s %d: %d\n", __func__, chan, brightness); + + mutex_lock(&is31->lock); + + /* update PWM register */ + ret = regmap_write(is31->regmap, IS31FL319X_PWM(chan), brightness); + if (ret < 0) + goto out; + + /* read current brightness of all PWM channels */ + for (i = 0; i < is31->cdef->num_leds; i++) { + unsigned int pwm_value; + bool on; + + /* + * since neither cdev nor the chip can provide + * the current setting, we read from the regmap cache + */ + + ret = regmap_read(is31->regmap, IS31FL319X_PWM(i), &pwm_value); + dev_dbg(&is31->client->dev, "%s read %d: ret=%d: %d\n", + __func__, i, ret, pwm_value); + on = ret >= 0 && pwm_value > LED_OFF; + + if (i < 3) + ctrl1 |= on << i; /* 0..2 => bit 0..2 */ + else if (i < 6) + ctrl1 |= on << (i + 1); /* 3..5 => bit 4..6 */ + else + ctrl2 |= on << (i - 6); /* 6..8 => bit 0..2 */ + } + + if (ctrl1 > 0 || ctrl2 > 0) { + dev_dbg(&is31->client->dev, "power up %02x %02x\n", + ctrl1, ctrl2); + regmap_write(is31->regmap, IS31FL319X_CTRL1, ctrl1); + regmap_write(is31->regmap, IS31FL319X_CTRL2, ctrl2); + /* update PWMs */ + regmap_write(is31->regmap, IS31FL319X_DATA_UPDATE, 0x00); + /* enable chip from shut down */ + ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x01); + } else { + dev_dbg(&is31->client->dev, "power down\n"); + /* shut down (no need to clear CTRL1/2) */ + ret = regmap_write(is31->regmap, IS31FL319X_SHUTDOWN, 0x00); + } + +out: + mutex_unlock(&is31->lock); + + return ret; +} + +static int is31fl319x_parse_child_dt(const struct device *dev, + const struct device_node *child, + struct is31fl319x_led *led) +{ + struct led_classdev *cdev = &led->cdev; + int ret; + + if (of_property_read_string(child, "label", &cdev->name)) + cdev->name = child->name; + + ret = of_property_read_string(child, "linux,default-trigger", + &cdev->default_trigger); + if (ret < 0 && ret != -EINVAL) /* is optional */ + return ret; + + led->max_microamp = IS31FL319X_CURRENT_DEFAULT; + ret = of_property_read_u32(child, "led-max-microamp", + &led->max_microamp); + if (!ret) { + if (led->max_microamp < IS31FL319X_CURRENT_MIN) + return -EINVAL; /* not supported */ + led->max_microamp = min(led->max_microamp, + IS31FL319X_CURRENT_MAX); + } + + return 0; +} + +static int is31fl319x_parse_dt(struct device *dev, + struct is31fl319x_chip *is31) +{ + struct device_node *np = dev->of_node, *child; + const struct of_device_id *of_dev_id; + int count; + int ret; + + if (!np) + return -ENODEV; + + of_dev_id = of_match_device(of_is31fl319x_match, dev); + if (!of_dev_id) { + dev_err(dev, "Failed to match device with supported chips\n"); + return -EINVAL; + } + + is31->cdef = of_dev_id->data; + + count = of_get_child_count(np); + + dev_dbg(dev, "probe %s with %d leds defined in DT\n", + of_dev_id->compatible, count); + + if (!count || count > is31->cdef->num_leds) { + dev_err(dev, "Number of leds defined must be between 1 and %u\n", + is31->cdef->num_leds); + return -ENODEV; + } + + for_each_child_of_node(np, child) { + struct is31fl319x_led *led; + u32 reg; + + ret = of_property_read_u32(child, "reg", ®); + if (ret) { + dev_err(dev, "Failed to read led 'reg' property\n"); + goto put_child_node; + } + + if (reg < 1 || reg > is31->cdef->num_leds) { + dev_err(dev, "invalid led reg %u\n", reg); + ret = -EINVAL; + goto put_child_node; + } + + led = &is31->leds[reg - 1]; + + if (led->configured) { + dev_err(dev, "led %u is already configured\n", reg); + ret = -EINVAL; + goto put_child_node; + } + + ret = is31fl319x_parse_child_dt(dev, child, led); + if (ret) { + dev_err(dev, "led %u DT parsing failed\n", reg); + goto put_child_node; + } + + led->configured = true; + } + + is31->audio_gain_db = 0; + ret = of_property_read_u32(np, "audio-gain-db", &is31->audio_gain_db); + if (!ret) + is31->audio_gain_db = min(is31->audio_gain_db, + IS31FL319X_AUDIO_GAIN_DB_MAX); + + return 0; + +put_child_node: + of_node_put(child); + return ret; +} + +static bool is31fl319x_readable_reg(struct device *dev, unsigned int reg) +{ /* we have no readable registers */ + return false; +} + +static bool is31fl319x_volatile_reg(struct device *dev, unsigned int reg) +{ /* volatile registers are not cached */ + switch (reg) { + case IS31FL319X_DATA_UPDATE: + case IS31FL319X_TIME_UPDATE: + case IS31FL319X_RESET: + return true; /* always write-through */ + default: + return false; + } +} + +static const struct reg_default is31fl319x_reg_defaults[] = { + { IS31FL319X_CONFIG1, 0x00}, + { IS31FL319X_CONFIG2, 0x00}, + { IS31FL319X_PWM(0), 0x00}, + { IS31FL319X_PWM(1), 0x00}, + { IS31FL319X_PWM(2), 0x00}, + { IS31FL319X_PWM(3), 0x00}, + { IS31FL319X_PWM(4), 0x00}, + { IS31FL319X_PWM(5), 0x00}, + { IS31FL319X_PWM(6), 0x00}, + { IS31FL319X_PWM(7), 0x00}, + { IS31FL319X_PWM(8), 0x00}, +}; + +static struct regmap_config regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = IS31FL319X_REG_CNT, + .cache_type = REGCACHE_FLAT, + .readable_reg = is31fl319x_readable_reg, + .volatile_reg = is31fl319x_volatile_reg, + .reg_defaults = is31fl319x_reg_defaults, + .num_reg_defaults = ARRAY_SIZE(is31fl319x_reg_defaults), +}; + +static inline int is31fl319x_microamp_to_cs(struct device *dev, u32 microamp) +{ /* round down to nearest supported value (range check done by caller) */ + u32 step = microamp / IS31FL319X_CURRENT_STEP; + + return ((IS31FL319X_CONFIG2_CS_STEP_REF - step) & + IS31FL319X_CONFIG2_CS_MASK) << + IS31FL319X_CONFIG2_CS_SHIFT; /* CS encoding */ +} + +static inline int is31fl319x_db_to_gain(u32 dezibel) +{ /* round down to nearest supported value (range check done by caller) */ + return dezibel / IS31FL319X_AUDIO_GAIN_DB_STEP; +} + +static int is31fl319x_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct is31fl319x_chip *is31; + struct device *dev = &client->dev; + struct i2c_adapter *adapter = to_i2c_adapter(dev->parent); + int err; + int i = 0; + u32 aggregated_led_microamp = IS31FL319X_CURRENT_MAX; + + if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + return -EIO; + + is31 = devm_kzalloc(&client->dev, sizeof(*is31), GFP_KERNEL); + if (!is31) + return -ENOMEM; + + mutex_init(&is31->lock); + + err = is31fl319x_parse_dt(&client->dev, is31); + if (err) + goto free_mutex; + + is31->client = client; + is31->regmap = devm_regmap_init_i2c(client, ®map_config); + if (IS_ERR(is31->regmap)) { + dev_err(&client->dev, "failed to allocate register map\n"); + err = PTR_ERR(is31->regmap); + goto free_mutex; + } + + i2c_set_clientdata(client, is31); + + /* check for write-reply from chip (we can't read any registers) */ + err = regmap_write(is31->regmap, IS31FL319X_RESET, 0x00); + if (err < 0) { + dev_err(&client->dev, "no response from chip write: err = %d\n", + err); + err = -EIO; /* does not answer */ + goto free_mutex; + } + + /* + * Kernel conventions require per-LED led-max-microamp property. + * But the chip does not allow to limit individual LEDs. + * So we take minimum from all subnodes for safety of hardware. + */ + for (i = 0; i < is31->cdef->num_leds; i++) + if (is31->leds[i].configured && + is31->leds[i].max_microamp < aggregated_led_microamp) + aggregated_led_microamp = is31->leds[i].max_microamp; + + regmap_write(is31->regmap, IS31FL319X_CONFIG2, + is31fl319x_microamp_to_cs(dev, aggregated_led_microamp) | + is31fl319x_db_to_gain(is31->audio_gain_db)); + + for (i = 0; i < is31->cdef->num_leds; i++) { + struct is31fl319x_led *led = &is31->leds[i]; + + if (!led->configured) + continue; + + led->chip = is31; + led->cdev.brightness_set_blocking = is31fl319x_brightness_set; + + err = devm_led_classdev_register(&client->dev, &led->cdev); + if (err < 0) + goto free_mutex; + } + + return 0; + +free_mutex: + mutex_destroy(&is31->lock); + return err; +} + +static int is31fl319x_remove(struct i2c_client *client) +{ + struct is31fl319x_chip *is31 = i2c_get_clientdata(client); + + mutex_destroy(&is31->lock); + return 0; +} + +/* + * i2c-core (and modalias) requires that id_table be properly filled, + * even though it is not used for DeviceTree based instantiation. + */ +static const struct i2c_device_id is31fl319x_id[] = { + { "is31fl3190" }, + { "is31fl3191" }, + { "is31fl3193" }, + { "is31fl3196" }, + { "is31fl3199" }, + { "sn3199" }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, is31fl319x_id); + +static struct i2c_driver is31fl319x_driver = { + .driver = { + .name = "leds-is31fl319x", + .of_match_table = of_match_ptr(of_is31fl319x_match), + }, + .probe = is31fl319x_probe, + .remove = is31fl319x_remove, + .id_table = is31fl319x_id, +}; + +module_i2c_driver(is31fl319x_driver); + +MODULE_AUTHOR("H. Nikolaus Schaller <hns@goldelico.com>"); +MODULE_AUTHOR("Andrey Utkin <andrey_utkin@fastmail.com>"); +MODULE_DESCRIPTION("IS31FL319X LED driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/leds/leds-mlxcpld.c b/drivers/leds/leds-mlxcpld.c new file mode 100644 index 000000000000..197ab9b29a9c --- /dev/null +++ b/drivers/leds/leds-mlxcpld.c @@ -0,0 +1,430 @@ +/* + * drivers/leds/leds-mlxcpld.c + * Copyright (c) 2016 Mellanox Technologies. All rights reserved. + * Copyright (c) 2016 Vadim Pasternak <vadimp@mellanox.com> + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the names of the copyright holders nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/dmi.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/io.h> +#include <linux/leds.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#define MLXPLAT_CPLD_LPC_REG_BASE_ADRR 0x2500 /* LPC bus access */ + +/* Color codes for LEDs */ +#define MLXCPLD_LED_OFFSET_HALF 0x01 /* Offset from solid: 3Hz blink */ +#define MLXCPLD_LED_OFFSET_FULL 0x02 /* Offset from solid: 6Hz blink */ +#define MLXCPLD_LED_IS_OFF 0x00 /* Off */ +#define MLXCPLD_LED_RED_STATIC_ON 0x05 /* Solid red */ +#define MLXCPLD_LED_RED_BLINK_HALF (MLXCPLD_LED_RED_STATIC_ON + \ + MLXCPLD_LED_OFFSET_HALF) +#define MLXCPLD_LED_RED_BLINK_FULL (MLXCPLD_LED_RED_STATIC_ON + \ + MLXCPLD_LED_OFFSET_FULL) +#define MLXCPLD_LED_GREEN_STATIC_ON 0x0D /* Solid green */ +#define MLXCPLD_LED_GREEN_BLINK_HALF (MLXCPLD_LED_GREEN_STATIC_ON + \ + MLXCPLD_LED_OFFSET_HALF) +#define MLXCPLD_LED_GREEN_BLINK_FULL (MLXCPLD_LED_GREEN_STATIC_ON + \ + MLXCPLD_LED_OFFSET_FULL) +#define MLXCPLD_LED_BLINK_3HZ 167 /* ~167 msec off/on */ +#define MLXCPLD_LED_BLINK_6HZ 83 /* ~83 msec off/on */ + +/** + * mlxcpld_param - LED access parameters: + * @offset - offset for LED access in CPLD device + * @mask - mask for LED access in CPLD device + * @base_color - base color code for LED +**/ +struct mlxcpld_param { + u8 offset; + u8 mask; + u8 base_color; +}; + +/** + * mlxcpld_led_priv - LED private data: + * @cled - LED class device instance + * @param - LED CPLD access parameters +**/ +struct mlxcpld_led_priv { + struct led_classdev cdev; + struct mlxcpld_param param; +}; + +#define cdev_to_priv(c) container_of(c, struct mlxcpld_led_priv, cdev) + +/** + * mlxcpld_led_profile - system LED profile (defined per system class): + * @offset - offset for LED access in CPLD device + * @mask - mask for LED access in CPLD device + * @base_color - base color code + * @brightness - default brightness setting (on/off) + * @name - LED name +**/ +struct mlxcpld_led_profile { + u8 offset; + u8 mask; + u8 base_color; + enum led_brightness brightness; + const char *name; +}; + +/** + * mlxcpld_led_pdata - system LED private data + * @pdev - platform device pointer + * @pled - LED class device instance + * @profile - system configuration profile + * @num_led_instances - number of LED instances + * @lock - device access lock +**/ +struct mlxcpld_led_pdata { + struct platform_device *pdev; + struct mlxcpld_led_priv *pled; + struct mlxcpld_led_profile *profile; + int num_led_instances; + spinlock_t lock; +}; + +static struct mlxcpld_led_pdata *mlxcpld_led; + +/* Default profile fit the next Mellanox systems: + * "msx6710", "msx6720", "msb7700", "msn2700", "msx1410", + * "msn2410", "msb7800", "msn2740" + */ +static struct mlxcpld_led_profile mlxcpld_led_default_profile[] = { + { + 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:fan1:green", + }, + { + 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:fan1:red", + }, + { + 0x21, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:fan2:green", + }, + { + 0x21, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:fan2:red", + }, + { + 0x22, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:fan3:green", + }, + { + 0x22, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:fan3:red", + }, + { + 0x22, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:fan4:green", + }, + { + 0x22, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:fan4:red", + }, + { + 0x20, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:psu:green", + }, + { + 0x20, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:psu:red", + }, + { + 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:status:green", + }, + { + 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:status:red", + }, +}; + +/* Profile fit the Mellanox systems based on "msn2100" */ +static struct mlxcpld_led_profile mlxcpld_led_msn2100_profile[] = { + { + 0x21, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:fan:green", + }, + { + 0x21, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:fan:red", + }, + { + 0x23, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:psu1:green", + }, + { + 0x23, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:psu1:red", + }, + { + 0x23, 0x0f, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:psu2:green", + }, + { + 0x23, 0x0f, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:psu2:red", + }, + { + 0x20, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, 1, + "mlxcpld:status:green", + }, + { + 0x20, 0xf0, MLXCPLD_LED_RED_STATIC_ON, LED_OFF, + "mlxcpld:status:red", + }, + { + 0x24, 0xf0, MLXCPLD_LED_GREEN_STATIC_ON, LED_OFF, + "mlxcpld:uid:blue", + }, +}; + +enum mlxcpld_led_platform_types { + MLXCPLD_LED_PLATFORM_DEFAULT, + MLXCPLD_LED_PLATFORM_MSN2100, +}; + +static const char *mlx_product_names[] = { + "DEFAULT", + "MSN2100", +}; + +static enum +mlxcpld_led_platform_types mlxcpld_led_platform_check_sys_type(void) +{ + const char *mlx_product_name; + int i; + + mlx_product_name = dmi_get_system_info(DMI_PRODUCT_NAME); + if (!mlx_product_name) + return MLXCPLD_LED_PLATFORM_DEFAULT; + + for (i = 1; i < ARRAY_SIZE(mlx_product_names); i++) { + if (strstr(mlx_product_name, mlx_product_names[i])) + return i; + } + + return MLXCPLD_LED_PLATFORM_DEFAULT; +} + +static void mlxcpld_led_bus_access_func(u16 base, u8 offset, u8 rw_flag, + u8 *data) +{ + u32 addr = base + offset; + + if (rw_flag == 0) + outb(*data, addr); + else + *data = inb(addr); +} + +static void mlxcpld_led_store_hw(u8 mask, u8 off, u8 vset) +{ + u8 nib, val; + + /* + * Each LED is controlled through low or high nibble of the relevant + * CPLD register. Register offset is specified by off parameter. + * Parameter vset provides color code: 0x0 for off, 0x5 for solid red, + * 0x6 for 3Hz blink red, 0xd for solid green, 0xe for 3Hz blink + * green. + * Parameter mask specifies which nibble is used for specific LED: mask + * 0xf0 - lower nibble is to be used (bits from 0 to 3), mask 0x0f - + * higher nibble (bits from 4 to 7). + */ + spin_lock(&mlxcpld_led->lock); + mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 1, + &val); + nib = (mask == 0xf0) ? vset : (vset << 4); + val = (val & mask) | nib; + mlxcpld_led_bus_access_func(MLXPLAT_CPLD_LPC_REG_BASE_ADRR, off, 0, + &val); + spin_unlock(&mlxcpld_led->lock); +} + +static void mlxcpld_led_brightness_set(struct led_classdev *led, + enum led_brightness value) +{ + struct mlxcpld_led_priv *pled = cdev_to_priv(led); + + if (value) { + mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, + pled->param.base_color); + return; + } + + mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, + MLXCPLD_LED_IS_OFF); +} + +static int mlxcpld_led_blink_set(struct led_classdev *led, + unsigned long *delay_on, + unsigned long *delay_off) +{ + struct mlxcpld_led_priv *pled = cdev_to_priv(led); + + /* + * HW supports two types of blinking: full (6Hz) and half (3Hz). + * For delay on/off zero default setting 3Hz is used. + */ + if (!(*delay_on == 0 && *delay_off == 0) && + !(*delay_on == MLXCPLD_LED_BLINK_3HZ && + *delay_off == MLXCPLD_LED_BLINK_3HZ) && + !(*delay_on == MLXCPLD_LED_BLINK_6HZ && + *delay_off == MLXCPLD_LED_BLINK_6HZ)) + return -EINVAL; + + if (*delay_on == MLXCPLD_LED_BLINK_6HZ) + mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, + pled->param.base_color + + MLXCPLD_LED_OFFSET_FULL); + else + mlxcpld_led_store_hw(pled->param.mask, pled->param.offset, + pled->param.base_color + + MLXCPLD_LED_OFFSET_HALF); + + return 0; +} + +static int mlxcpld_led_config(struct device *dev, + struct mlxcpld_led_pdata *cpld) +{ + int i; + int err; + + cpld->pled = devm_kzalloc(dev, sizeof(struct mlxcpld_led_priv) * + cpld->num_led_instances, GFP_KERNEL); + if (!cpld->pled) + return -ENOMEM; + + for (i = 0; i < cpld->num_led_instances; i++) { + cpld->pled[i].cdev.name = cpld->profile[i].name; + cpld->pled[i].cdev.brightness = cpld->profile[i].brightness; + cpld->pled[i].cdev.max_brightness = 1; + cpld->pled[i].cdev.brightness_set = mlxcpld_led_brightness_set; + cpld->pled[i].cdev.blink_set = mlxcpld_led_blink_set; + cpld->pled[i].cdev.flags = LED_CORE_SUSPENDRESUME; + err = devm_led_classdev_register(dev, &cpld->pled[i].cdev); + if (err) + return err; + + cpld->pled[i].param.offset = mlxcpld_led->profile[i].offset; + cpld->pled[i].param.mask = mlxcpld_led->profile[i].mask; + cpld->pled[i].param.base_color = + mlxcpld_led->profile[i].base_color; + + if (mlxcpld_led->profile[i].brightness) + mlxcpld_led_brightness_set(&cpld->pled[i].cdev, + mlxcpld_led->profile[i].brightness); + } + + return 0; +} + +static int __init mlxcpld_led_probe(struct platform_device *pdev) +{ + enum mlxcpld_led_platform_types mlxcpld_led_plat = + mlxcpld_led_platform_check_sys_type(); + + mlxcpld_led = devm_kzalloc(&pdev->dev, sizeof(*mlxcpld_led), + GFP_KERNEL); + if (!mlxcpld_led) + return -ENOMEM; + + mlxcpld_led->pdev = pdev; + + switch (mlxcpld_led_plat) { + case MLXCPLD_LED_PLATFORM_MSN2100: + mlxcpld_led->profile = mlxcpld_led_msn2100_profile; + mlxcpld_led->num_led_instances = + ARRAY_SIZE(mlxcpld_led_msn2100_profile); + break; + + default: + mlxcpld_led->profile = mlxcpld_led_default_profile; + mlxcpld_led->num_led_instances = + ARRAY_SIZE(mlxcpld_led_default_profile); + break; + } + + spin_lock_init(&mlxcpld_led->lock); + + return mlxcpld_led_config(&pdev->dev, mlxcpld_led); +} + +static struct platform_driver mlxcpld_led_driver = { + .driver = { + .name = KBUILD_MODNAME, + }, +}; + +static int __init mlxcpld_led_init(void) +{ + struct platform_device *pdev; + int err; + + pdev = platform_device_register_simple(KBUILD_MODNAME, -1, NULL, 0); + if (IS_ERR(pdev)) { + pr_err("Device allocation failed\n"); + return PTR_ERR(pdev); + } + + err = platform_driver_probe(&mlxcpld_led_driver, mlxcpld_led_probe); + if (err) { + pr_err("Probe platform driver failed\n"); + platform_device_unregister(pdev); + } + + return err; +} + +static void __exit mlxcpld_led_exit(void) +{ + platform_device_unregister(mlxcpld_led->pdev); + platform_driver_unregister(&mlxcpld_led_driver); +} + +module_init(mlxcpld_led_init); +module_exit(mlxcpld_led_exit); + +MODULE_AUTHOR("Vadim Pasternak <vadimp@mellanox.com>"); +MODULE_DESCRIPTION("Mellanox board LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds_mlxcpld"); diff --git a/drivers/leds/leds-pm8058.c b/drivers/leds/leds-pm8058.c new file mode 100644 index 000000000000..a52674327857 --- /dev/null +++ b/drivers/leds/leds-pm8058.c @@ -0,0 +1,191 @@ +/* Copyright (c) 2010, 2011, 2016 The Linux Foundation. All rights reserved. + * + * 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/leds.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/regmap.h> + +#define PM8058_LED_TYPE_COMMON 0x00 +#define PM8058_LED_TYPE_KEYPAD 0x01 +#define PM8058_LED_TYPE_FLASH 0x02 + +#define PM8058_LED_TYPE_COMMON_MASK 0xf8 +#define PM8058_LED_TYPE_KEYPAD_MASK 0xf0 +#define PM8058_LED_TYPE_COMMON_SHIFT 3 +#define PM8058_LED_TYPE_KEYPAD_SHIFT 4 + +struct pm8058_led { + struct regmap *map; + u32 reg; + u32 ledtype; + struct led_classdev cdev; +}; + +static void pm8058_led_set(struct led_classdev *cled, + enum led_brightness value) +{ + struct pm8058_led *led; + int ret = 0; + unsigned int mask = 0; + unsigned int val = 0; + + led = container_of(cled, struct pm8058_led, cdev); + switch (led->ledtype) { + case PM8058_LED_TYPE_COMMON: + mask = PM8058_LED_TYPE_COMMON_MASK; + val = value << PM8058_LED_TYPE_COMMON_SHIFT; + break; + case PM8058_LED_TYPE_KEYPAD: + case PM8058_LED_TYPE_FLASH: + mask = PM8058_LED_TYPE_KEYPAD_MASK; + val = value << PM8058_LED_TYPE_KEYPAD_SHIFT; + break; + default: + break; + } + + ret = regmap_update_bits(led->map, led->reg, mask, val); + if (ret) + pr_err("Failed to set LED brightness\n"); +} + +static enum led_brightness pm8058_led_get(struct led_classdev *cled) +{ + struct pm8058_led *led; + int ret; + unsigned int val; + + led = container_of(cled, struct pm8058_led, cdev); + + ret = regmap_read(led->map, led->reg, &val); + if (ret) { + pr_err("Failed to get LED brightness\n"); + return LED_OFF; + } + + switch (led->ledtype) { + case PM8058_LED_TYPE_COMMON: + val &= PM8058_LED_TYPE_COMMON_MASK; + val >>= PM8058_LED_TYPE_COMMON_SHIFT; + break; + case PM8058_LED_TYPE_KEYPAD: + case PM8058_LED_TYPE_FLASH: + val &= PM8058_LED_TYPE_KEYPAD_MASK; + val >>= PM8058_LED_TYPE_KEYPAD_SHIFT; + break; + default: + val = LED_OFF; + break; + } + + return val; +} + +static int pm8058_led_probe(struct platform_device *pdev) +{ + struct pm8058_led *led; + struct device_node *np = pdev->dev.of_node; + int ret; + struct regmap *map; + const char *state; + enum led_brightness maxbright; + + led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL); + if (!led) + return -ENOMEM; + + led->ledtype = (u32)of_device_get_match_data(&pdev->dev); + + map = dev_get_regmap(pdev->dev.parent, NULL); + if (!map) { + dev_err(&pdev->dev, "Parent regmap unavailable.\n"); + return -ENXIO; + } + led->map = map; + + ret = of_property_read_u32(np, "reg", &led->reg); + if (ret) { + dev_err(&pdev->dev, "no register offset specified\n"); + return -EINVAL; + } + + /* Use label else node name */ + led->cdev.name = of_get_property(np, "label", NULL) ? : np->name; + led->cdev.default_trigger = + of_get_property(np, "linux,default-trigger", NULL); + led->cdev.brightness_set = pm8058_led_set; + led->cdev.brightness_get = pm8058_led_get; + if (led->ledtype == PM8058_LED_TYPE_COMMON) + maxbright = 31; /* 5 bits */ + else + maxbright = 15; /* 4 bits */ + led->cdev.max_brightness = maxbright; + + state = of_get_property(np, "default-state", NULL); + if (state) { + if (!strcmp(state, "keep")) { + led->cdev.brightness = pm8058_led_get(&led->cdev); + } else if (!strcmp(state, "on")) { + led->cdev.brightness = maxbright; + pm8058_led_set(&led->cdev, maxbright); + } else { + led->cdev.brightness = LED_OFF; + pm8058_led_set(&led->cdev, LED_OFF); + } + } + + if (led->ledtype == PM8058_LED_TYPE_KEYPAD || + led->ledtype == PM8058_LED_TYPE_FLASH) + led->cdev.flags = LED_CORE_SUSPENDRESUME; + + ret = devm_led_classdev_register(&pdev->dev, &led->cdev); + if (ret) { + dev_err(&pdev->dev, "unable to register led \"%s\"\n", + led->cdev.name); + return ret; + } + + return 0; +} + +static const struct of_device_id pm8058_leds_id_table[] = { + { + .compatible = "qcom,pm8058-led", + .data = (void *)PM8058_LED_TYPE_COMMON + }, + { + .compatible = "qcom,pm8058-keypad-led", + .data = (void *)PM8058_LED_TYPE_KEYPAD + }, + { + .compatible = "qcom,pm8058-flash-led", + .data = (void *)PM8058_LED_TYPE_FLASH + }, + { }, +}; +MODULE_DEVICE_TABLE(of, pm8058_leds_id_table); + +static struct platform_driver pm8058_led_driver = { + .probe = pm8058_led_probe, + .driver = { + .name = "pm8058-leds", + .of_match_table = pm8058_leds_id_table, + }, +}; +module_platform_driver(pm8058_led_driver); + +MODULE_DESCRIPTION("PM8058 LEDs driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:pm8058-leds"); |