From 8c40b7d09ec17c7c01a6db2e16a52dca57b68e36 Mon Sep 17 00:00:00 2001 From: "H. Nikolaus Schaller" Date: Tue, 19 Jul 2016 13:47:30 +0200 Subject: leds: is31fl319x: 1/3/6/9-channel light effect led driver This is a driver for the Integrated Silicon Solution Inc. LED driver chips series IS31FL319x. They can drive 1, 3, 6 or up to 9 LEDs. Each LED is individually controllable in brightness (through pwm) in 256 steps so that RGB LEDs can show any of ca. 16 Mio colors. The maximum current of the LEDs can be programmed and limited to 5 .. 40mA through a device tree property. The chip is connected through I2C and can have one of 4 addresses in the range 0x64 .. 0x67 depending on how the AD pin is connected. The address is defined by the reg property as usual. The chip also has a shutdown input which could be connected to a GPIO, but this driver uses software shutdown if all LEDs are inactivated. The chip also has breathing and audio features which are not fully supported by this driver. Tested-on: OMAP5 based Pyra handheld prototype. Signed-off-by: H. Nikolaus Schaller Signed-off-by: Andrey Utkin Signed-off-by: Jacek Anaszewski --- drivers/leds/Kconfig | 12 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-is31fl319x.c | 450 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 463 insertions(+) create mode 100644 drivers/leds/leds-is31fl319x.c (limited to 'drivers') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9dcc9b13d495..6df9a2f098f5 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 diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 0684c865a1c0..e7a0e4edadd4 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o obj-$(CONFIG_LEDS_SEAD3) += leds-sead3.o +obj-$(CONFIG_LEDS_IS31FL319X) += leds-is31fl319x.o obj-$(CONFIG_LEDS_IS31FL32XX) += leds-is31fl32xx.o # LED SPI Drivers 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +MODULE_AUTHOR("Andrey Utkin "); +MODULE_DESCRIPTION("IS31FL319X LED driver"); +MODULE_LICENSE("GPL v2"); -- cgit v1.2.3-58-ga151 From 50237863c4edd9c342b522d8edfedf23b20d4a21 Mon Sep 17 00:00:00 2001 From: Paul Gortmaker Date: Mon, 15 Aug 2016 16:54:48 -0400 Subject: leds: make triggers explicitly non-modular The Kconfig currently controlling compilation of this code is: drivers/leds/trigger/Kconfig:menuconfig LEDS_TRIGGERS drivers/leds/trigger/Kconfig: bool "LED Trigger support" ...meaning that it currently is not being built as a module by anyone. Lets remove the couple traces of modular infrastructure use, so that when reading the driver there is no doubt it is builtin-only. We also delete the MODULE_LICENSE tag etc. since all that information is already contained at the top of the file in the comments. We don't replace module.h with init.h since the file doesn't need that. However it does use EXPORT_SYMBOL, so we add the export.h header. Cc: Richard Purdie Cc: linux-leds@vger.kernel.org Signed-off-by: Paul Gortmaker Signed-off-by: Jacek Anaszewski --- drivers/leds/led-triggers.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index c92702a684ce..f0b3d101994b 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -11,7 +11,7 @@ * */ -#include +#include #include #include #include @@ -357,7 +357,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"); -- cgit v1.2.3-58-ga151 From 3b9b95363c45365d606ad4bbba16acca75fdf6d3 Mon Sep 17 00:00:00 2001 From: Nathan Sullivan Date: Mon, 15 Aug 2016 17:20:14 -0500 Subject: leds: do not overflow sysfs buffer in led_trigger_show Per the documentation, use scnprintf instead of sprintf to ensure there is never more than PAGE_SIZE bytes of trigger names put into the buffer. Signed-off-by: Nathan Sullivan Signed-off-by: Zach Brown Signed-off-by: Jacek Anaszewski --- drivers/leds/led-triggers.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index f0b3d101994b..59d6a3e58a3d 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -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); -- cgit v1.2.3-58-ga151 From 7f866986e7052197396d8a663fa009209af18aee Mon Sep 17 00:00:00 2001 From: Linus Walleij Date: Tue, 16 Aug 2016 11:25:10 +0200 Subject: leds: add PM8058 LEDs driver This adds a driver for the six PM8058 LEDs, three ordinary LEDs, two "flash" LEDs and one "keypad" LED. The "keypad" and "flash" LEDs are not really hard-wired to these usecases: for example on the APQ8060 Dragonboard, the "keypad" LED is instead used to drive an IR LED used for the proximity sensor. The "flash" LEDs are just ordinary high-current LED drivers. Cc: linux-arm-msm@vger.kernel.org Cc: Andy Gross Cc: Stephen Boyd Signed-off-by: Linus Walleij Reviewed-by: Bjorn Andersson Signed-off-by: Jacek Anaszewski --- drivers/leds/Kconfig | 8 ++ drivers/leds/Makefile | 1 + drivers/leds/leds-pm8058.c | 191 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 drivers/leds/leds-pm8058.c (limited to 'drivers') diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 6df9a2f098f5..969c720dfd10 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -643,6 +643,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_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. + comment "LED Triggers" source "drivers/leds/trigger/Kconfig" diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index e7a0e4edadd4..06a27ef17554 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -69,6 +69,7 @@ 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 # LED SPI Drivers obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o 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 +#include +#include +#include +#include +#include +#include + +#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"); -- cgit v1.2.3-58-ga151 From be4fdf99fa4dc95aa01144b207caf2cc9fa074d8 Mon Sep 17 00:00:00 2001 From: Vadim Pasternak Date: Thu, 8 Sep 2016 07:25:53 +0000 Subject: leds: add driver for Mellanox systems LEDs This makes it possible to create a set of LEDs for Mellanox systems: "msx6710", "msx6720", "msb7700", "msn2700", "msx1410", "msn2410", "msb7800", "msn2740", "msn2100". Driver obtains LED devices according to system configuration, provided through system DMI data, like mlxcpld:fan1:green, mlxcpld:fan1:red and creates devices in form: "devicename:colour:function". LED setting is controlled through on board CPLD Lattice device. For setting particular LED off, solid, blink: echo 0 > /sys/class/leds/mlxcpld\:status\:green/brightness echo 1 > /sys/class/leds/mlxcpld\:status\:green/brightness echo timer > /sys/class/leds/mlxcpld\:status\:green/trigger On module probing all LEDs are set green, on removing - off. Last setting overwrites previous, f.e. sequence for changing LED from green - red - green: echo 1 > /sys/class/leds/mlxcpld\:psu\:green/brightness echo 1 > /sys/class/leds/mlxcpld\:psu\:red/brightness echo 1 > /sys/class/leds/mlxcpld\:psu\:green/brightness Note: LEDs cannot be turned on/off simultaneously. The Kconfig currently controlling compilation of this code is: drivers/leds/Kconfig:config LEDS_MLXCPLD Signed-off-by: Vadim Pasternak Reviewed-by: Jiri Pirko Reviewed-by: Wei Yongjun Signed-off-by: Jacek Anaszewski --- Documentation/leds/leds-mlxcpld.txt | 110 +++++++++ MAINTAINERS | 7 + drivers/leds/Kconfig | 8 + drivers/leds/Makefile | 1 + drivers/leds/leds-mlxcpld.c | 430 ++++++++++++++++++++++++++++++++++++ 5 files changed, 556 insertions(+) create mode 100644 Documentation/leds/leds-mlxcpld.txt create mode 100644 drivers/leds/leds-mlxcpld.c (limited to 'drivers') diff --git a/Documentation/leds/leds-mlxcpld.txt b/Documentation/leds/leds-mlxcpld.txt new file mode 100644 index 000000000000..a0e8fd457117 --- /dev/null +++ b/Documentation/leds/leds-mlxcpld.txt @@ -0,0 +1,110 @@ +Kernel driver for Mellanox systems LEDs +======================================= + +Provide system LED support for the nex Mellanox systems: +"msx6710", "msx6720", "msb7700", "msn2700", "msx1410", +"msn2410", "msb7800", "msn2740", "msn2100". + +Description +----------- +Driver provides the following LEDs for the systems "msx6710", "msx6720", +"msb7700", "msn2700", "msx1410", "msn2410", "msb7800", "msn2740": + mlxcpld:fan1:green + mlxcpld:fan1:red + mlxcpld:fan2:green + mlxcpld:fan2:red + mlxcpld:fan3:green + mlxcpld:fan3:red + mlxcpld:fan4:green + mlxcpld:fan4:red + mlxcpld:psu:green + mlxcpld:psu:red + mlxcpld:status:green + mlxcpld:status:red + + "status" + CPLD reg offset: 0x20 + Bits [3:0] + + "psu" + CPLD reg offset: 0x20 + Bits [7:4] + + "fan1" + CPLD reg offset: 0x21 + Bits [3:0] + + "fan2" + CPLD reg offset: 0x21 + Bits [7:4] + + "fan3" + CPLD reg offset: 0x22 + Bits [3:0] + + "fan4" + CPLD reg offset: 0x22 + Bits [7:4] + + Color mask for all the above LEDs: + [bit3,bit2,bit1,bit0] or + [bit7,bit6,bit5,bit4]: + [0,0,0,0] = LED OFF + [0,1,0,1] = Red static ON + [1,1,0,1] = Green static ON + [0,1,1,0] = Red blink 3Hz + [1,1,1,0] = Green blink 3Hz + [0,1,1,1] = Red blink 6Hz + [1,1,1,1] = Green blink 6Hz + +Driver provides the following LEDs for the system "msn2100": + mlxcpld:fan:green + mlxcpld:fan:red + mlxcpld:psu1:green + mlxcpld:psu1:red + mlxcpld:psu2:green + mlxcpld:psu2:red + mlxcpld:status:green + mlxcpld:status:red + mlxcpld:uid:blue + + "status" + CPLD reg offset: 0x20 + Bits [3:0] + + "fan" + CPLD reg offset: 0x21 + Bits [3:0] + + "psu1" + CPLD reg offset: 0x23 + Bits [3:0] + + "psu2" + CPLD reg offset: 0x23 + Bits [7:4] + + "uid" + CPLD reg offset: 0x24 + Bits [3:0] + + Color mask for all the above LEDs, excepted uid: + [bit3,bit2,bit1,bit0] or + [bit7,bit6,bit5,bit4]: + [0,0,0,0] = LED OFF + [0,1,0,1] = Red static ON + [1,1,0,1] = Green static ON + [0,1,1,0] = Red blink 3Hz + [1,1,1,0] = Green blink 3Hz + [0,1,1,1] = Red blink 6Hz + [1,1,1,1] = Green blink 6Hz + + Color mask for uid LED: + [bit3,bit2,bit1,bit0]: + [0,0,0,0] = LED OFF + [1,1,0,1] = Blue static ON + [1,1,1,0] = Blue blink 3Hz + [1,1,1,1] = Blue blink 6Hz + +Driver supports HW blinking at 3Hz and 6Hz frequency (50% duty cycle). +For 3Hz duty cylce is about 167 msec, for 6Hz is about 83 msec. diff --git a/MAINTAINERS b/MAINTAINERS index a306795a7450..f742e88ccf21 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -7649,6 +7649,13 @@ W: http://www.mellanox.com Q: http://patchwork.ozlabs.org/project/netdev/list/ F: drivers/net/ethernet/mellanox/mlxsw/ +MELLANOX MLXCPLD LED DRIVER +M: Vadim Pasternak +L: linux-leds@vger.kernel.org +S: Supported +F: drivers/leds/leds-mlxcpld.c +F: Documentation/leds/leds-mlxcpld.txt + SOFT-ROCE DRIVER (rxe) M: Moni Shoua L: linux-rdma@vger.kernel.org diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 969c720dfd10..7a628c6516f6 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig @@ -651,6 +651,14 @@ config LEDS_PM8058 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 06a27ef17554..3965070190f5 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile @@ -70,6 +70,7 @@ 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/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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_DESCRIPTION("Mellanox board LED driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:leds_mlxcpld"); -- cgit v1.2.3-58-ga151 From 68620e594c250ba8c43a78e77f5296cb9952582e Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:54:12 +0200 Subject: leds: gpio: introduce gpio_blink_set_t Introduce a typedef gpio_blink_set_t to improve readability of the code. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 6 ++---- include/linux/leds.h | 9 ++++++--- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 9b991d46ed84..3599b2e4977c 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -26,8 +26,7 @@ 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 void gpio_led_set(struct led_classdev *led_cdev, @@ -74,8 +73,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; diff --git a/include/linux/leds.h b/include/linux/leds.h index 8a3b5d29602f..ddfcb2df3656 100644 --- a/include/linux/leds.h +++ b/include/linux/leds.h @@ -359,6 +359,11 @@ struct led_platform_data { struct led_info *leds; }; +struct gpio_desc; +typedef int (*gpio_blink_set_t)(struct gpio_desc *desc, int state, + unsigned long *delay_on, + unsigned long *delay_off); + /* For the leds-gpio driver */ struct gpio_led { const char *name; @@ -382,9 +387,7 @@ struct gpio_led_platform_data { #define GPIO_LED_NO_BLINK_LOW 0 /* No blink GPIO state low */ #define GPIO_LED_NO_BLINK_HIGH 1 /* No blink GPIO state high */ #define GPIO_LED_BLINK 2 /* Please, blink */ - int (*gpio_blink_set)(struct gpio_desc *desc, int state, - unsigned long *delay_on, - unsigned long *delay_off); + gpio_blink_set_t gpio_blink_set; }; #ifdef CONFIG_NEW_LEDS -- cgit v1.2.3-58-ga151 From 061b5c1d013a94af385ba8980ea3b2617f1ff0f6 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:54:31 +0200 Subject: leds: gpio: fix an unhandled error case in create_gpio_led gpiod_get_value_cansleep returns 0, 1, or an error code. So far errors are not handled and treated the same as 1. Change this to bail out if an error code is returned and remove the double negation. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 3599b2e4977c..10c851ecc6ce 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -118,10 +118,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; -- cgit v1.2.3-58-ga151 From 4580809436fc391aff9d876a14d07f66a5de6b28 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:55:00 +0200 Subject: leds: gpio: add helper cdev_to_gpio_led_data Add a helper for the container_of as it's used more than once. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 10c851ecc6ce..da4aa8e95a58 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -29,11 +29,16 @@ struct gpio_led_data { 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) @@ -63,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, -- cgit v1.2.3-58-ga151 From 8ca03da5e0f70a413986221da4c88459bb6f6878 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:55:06 +0200 Subject: leds: gpio: simplify gpio_leds_create Definition of np can be moved into the loop as well to simplify the code a little. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index da4aa8e95a58..171ba2fbb59a 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -159,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) @@ -173,6 +172,7 @@ 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)) { @@ -181,8 +181,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) goto err; } - np = to_of_node(child); - if (fwnode_property_present(child, "label")) { fwnode_property_read_string(child, "label", &led.name); } else { -- cgit v1.2.3-58-ga151 From bec69de89bab29c739ab84ceb04ae63d6811babf Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:55:18 +0200 Subject: leds: gpio: fix and simplify reading property "label" Checking for the presence of the property first isn't strictly needed as we can react on the return code of fwnode_property_read_string. Also, even if the presence of a property "label" was checked, reading a string value for it theoretically still can fail and this case isn't handled. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 171ba2fbb59a..00a24e32c6c6 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -181,16 +181,14 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) goto err; } - 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) { + ret = -EINVAL; + goto err; } + fwnode_property_read_string(child, "linux,default-trigger", &led.default_trigger); -- cgit v1.2.3-58-ga151 From bc2c0dd85a0a31505ca2f92bef891ddac9126725 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:55:27 +0200 Subject: leds: gpio: switch to managed version of led_classdev_register Using the managed version of led_classdev_register allows to significantly simplify the code. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index 00a24e32c6c6..ab273f85ac3c 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -139,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 { @@ -219,8 +219,6 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) return priv; err: - for (count = priv->num_leds - 1; count >= 0; count--) - led_classdev_unregister(&priv->leds[count].cdev); return ERR_PTR(ret); } @@ -249,13 +247,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); @@ -268,17 +261,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); @@ -293,7 +275,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", -- cgit v1.2.3-58-ga151 From 74b69e5246d13840963b7b23e21dbab541cb26d2 Mon Sep 17 00:00:00 2001 From: Heiner Kallweit Date: Wed, 14 Sep 2016 20:55:43 +0200 Subject: leds: gpio: fix and simplify error handling in gpio_leds_create Simplify the error handling and add a missing call to fwnode_handle_put when checking led.name. Signed-off-by: Heiner Kallweit Signed-off-by: Jacek Anaszewski --- drivers/leds/leds-gpio.c | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/leds/leds-gpio.c b/drivers/leds/leds-gpio.c index ab273f85ac3c..d400dcaf4d29 100644 --- a/drivers/leds/leds-gpio.c +++ b/drivers/leds/leds-gpio.c @@ -177,16 +177,15 @@ static struct gpio_leds_priv *gpio_leds_create(struct platform_device *pdev) 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); } ret = fwnode_property_read_string(child, "label", &led.name); if (ret && IS_ENABLED(CONFIG_OF) && np) led.name = np->name; if (!led.name) { - ret = -EINVAL; - goto err; + fwnode_handle_put(child); + return ERR_PTR(-EINVAL); } fwnode_property_read_string(child, "linux,default-trigger", @@ -210,16 +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: - return ERR_PTR(ret); } static const struct of_device_id of_gpio_leds_match[] = { -- cgit v1.2.3-58-ga151 From fbfa197afddd13f9bdca1c822f5d5730b50639eb Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Sun, 18 Sep 2016 20:24:29 +0200 Subject: leds: triggers: Return from led_trigger_set() if there is nothing to do If led_trigger_set() is called with "trig" argument set to NULL, and there is no trigger to remove then the function should return immediately so as to avoid doing unnecessary allocation and sending uevent. Signed-off-by: Jacek Anaszewski Reported-by: Daniel Romell Acked-by Daniel Romell --- drivers/leds/led-triggers.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'drivers') diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index 59d6a3e58a3d..c7a38d43ff62 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -110,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); -- cgit v1.2.3-58-ga151 From 6f3bad9670729ea3a7c78b3752a89c94ffa2397a Mon Sep 17 00:00:00 2001 From: Jacek Anaszewski Date: Mon, 19 Sep 2016 12:44:50 +0200 Subject: leds: triggers: Check return value of kobject_uevent_env() Log error message if kobject_uevent_env() fails in led_trigger_set(). Signed-off-by: Jacek Anaszewski Acked-by: Greg Kroah-Hartman --- drivers/leds/led-triggers.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/leds/led-triggers.c b/drivers/leds/led-triggers.c index c7a38d43ff62..431123b048a2 100644 --- a/drivers/leds/led-triggers.c +++ b/drivers/leds/led-triggers.c @@ -141,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); } } -- cgit v1.2.3-58-ga151