summaryrefslogtreecommitdiff
path: root/drivers/leds/leds-ns2.c
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2015-08-31 17:08:05 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2015-08-31 17:08:05 -0700
commitedc837da4b54a01ba6fa3c29b411e35d1a8430ca (patch)
tree4b3e8d96f606b38619f66620d50557e1791fd4f0 /drivers/leds/leds-ns2.c
parente5aeced6bcec5a110e6dfcb78acc203dbe895b59 (diff)
parent4d59ed85451befda203d72462f1b7ead0fde3267 (diff)
Merge tag 'leds_for_4.3' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds
Pull LED updates from Jacek Anaszewski: "In this merge window we'd like to announce a change on the list of LED subsystem maintainers - Bryan Wu decided to step back and handed over the maintainership to me (Jacek Anaszewski), which entailed an update of LED subsystem git tree URL. Besides the changes in MAINTAINERS we have: - optimizations and improvements to existing LED class drivers - improvements to the common LEDs DT documentation - modifications of kirkwood-* dts files, to be in sync with patches for leds-ns2 driver, that move LEDs mode mapping outside of the driver" * tag 'leds_for_4.3' of git://git.kernel.org/pub/scm/linux/kernel/git/j.anaszewski/linux-leds: leds: Export OF module alias information in missing drivers leds:lp55xx: use the private data instead of updating I2C device platform data leds: syscon: Correct check for syscon_node_to_regmap() errors leds: leds-fsg: Use devm_led_classdev_register MAINTAINERS: Change LED subsystem git tree URL leds/led-class: Add missing put_device() ARM: Kirkwood: add modes-map property to ns2-leds nodes leds: tlc591xx: Remove redundant I2C_FUNC_SMBUS_BYTE_DATA functionality check leds: leds-ns2: depends on MACH_ARMADA_370 leds: leds-ns2: handle can_sleep GPIOs leds: leds-ns2: move LED modes mapping outside of the driver leds: lp8860: Constify reg_default tables leds: Drop owner assignment from i2c_driver leds: Allow compile test of LEDS_AAT1290 and LEDS_KTD2692 if !GPIOLIB leds: Allow compile test of GPIO consumers if !GPIOLIB DT: leds: Improve description of flash LEDs related properties
Diffstat (limited to 'drivers/leds/leds-ns2.c')
-rw-r--r--drivers/leds/leds-ns2.c169
1 files changed, 101 insertions, 68 deletions
diff --git a/drivers/leds/leds-ns2.c b/drivers/leds/leds-ns2.c
index 1fd6adbb43b7..b33514d9f427 100644
--- a/drivers/leds/leds-ns2.c
+++ b/drivers/leds/leds-ns2.c
@@ -31,50 +31,38 @@
#include <linux/platform_data/leds-kirkwood-ns2.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
+#include "leds.h"
/*
- * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
- * relation with the SATA activity. This capability is exposed through the
- * "sata" sysfs attribute.
- *
- * The following array detail the different LED registers and the combination
- * of their possible values:
- *
- * cmd_led | slow_led | /SATA active | LED state
- * | | |
- * 1 | 0 | x | off
- * - | 1 | x | on
- * 0 | 0 | 1 | on
- * 0 | 0 | 0 | blink (rate 300ms)
+ * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
+ * modes are available: off, on and SATA activity blinking. The LED modes are
+ * controlled through two GPIOs (command and slow): each combination of values
+ * for the command/slow GPIOs corresponds to a LED mode.
*/
-enum ns2_led_modes {
- NS_V2_LED_OFF,
- NS_V2_LED_ON,
- NS_V2_LED_SATA,
-};
-
-struct ns2_led_mode_value {
- enum ns2_led_modes mode;
- int cmd_level;
- int slow_level;
-};
-
-static struct ns2_led_mode_value ns2_led_modval[] = {
- { NS_V2_LED_OFF , 1, 0 },
- { NS_V2_LED_ON , 0, 1 },
- { NS_V2_LED_ON , 1, 1 },
- { NS_V2_LED_SATA, 0, 0 },
-};
-
struct ns2_led_data {
struct led_classdev cdev;
unsigned cmd;
unsigned slow;
+ bool can_sleep;
+ int mode_index;
unsigned char sata; /* True when SATA mode active. */
rwlock_t rw_lock; /* Lock GPIOs. */
+ struct work_struct work;
+ int num_modes;
+ struct ns2_led_modval *modval;
};
+static void ns2_led_work(struct work_struct *work)
+{
+ struct ns2_led_data *led_dat =
+ container_of(work, struct ns2_led_data, work);
+ int i = led_dat->mode_index;
+
+ gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
+ gpio_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level);
+}
+
static int ns2_led_get_mode(struct ns2_led_data *led_dat,
enum ns2_led_modes *mode)
{
@@ -83,22 +71,18 @@ static int ns2_led_get_mode(struct ns2_led_data *led_dat,
int cmd_level;
int slow_level;
- read_lock_irq(&led_dat->rw_lock);
+ cmd_level = gpio_get_value_cansleep(led_dat->cmd);
+ slow_level = gpio_get_value_cansleep(led_dat->slow);
- cmd_level = gpio_get_value(led_dat->cmd);
- slow_level = gpio_get_value(led_dat->slow);
-
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (cmd_level == ns2_led_modval[i].cmd_level &&
- slow_level == ns2_led_modval[i].slow_level) {
- *mode = ns2_led_modval[i].mode;
+ for (i = 0; i < led_dat->num_modes; i++) {
+ if (cmd_level == led_dat->modval[i].cmd_level &&
+ slow_level == led_dat->modval[i].slow_level) {
+ *mode = led_dat->modval[i].mode;
ret = 0;
break;
}
}
- read_unlock_irq(&led_dat->rw_lock);
-
return ret;
}
@@ -106,19 +90,32 @@ static void ns2_led_set_mode(struct ns2_led_data *led_dat,
enum ns2_led_modes mode)
{
int i;
+ bool found = false;
unsigned long flags;
+ for (i = 0; i < led_dat->num_modes; i++)
+ if (mode == led_dat->modval[i].mode) {
+ found = true;
+ break;
+ }
+
+ if (!found)
+ return;
+
write_lock_irqsave(&led_dat->rw_lock, flags);
- for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
- if (mode == ns2_led_modval[i].mode) {
- gpio_set_value(led_dat->cmd,
- ns2_led_modval[i].cmd_level);
- gpio_set_value(led_dat->slow,
- ns2_led_modval[i].slow_level);
- }
+ if (!led_dat->can_sleep) {
+ gpio_set_value(led_dat->cmd,
+ led_dat->modval[i].cmd_level);
+ gpio_set_value(led_dat->slow,
+ led_dat->modval[i].slow_level);
+ goto exit_unlock;
}
+ led_dat->mode_index = i;
+ schedule_work(&led_dat->work);
+
+exit_unlock:
write_unlock_irqrestore(&led_dat->rw_lock, flags);
}
@@ -148,7 +145,6 @@ static ssize_t ns2_led_sata_store(struct device *dev,
container_of(led_cdev, struct ns2_led_data, cdev);
int ret;
unsigned long enable;
- enum ns2_led_modes mode;
ret = kstrtoul(buff, 10, &enable);
if (ret < 0)
@@ -157,19 +153,19 @@ static ssize_t ns2_led_sata_store(struct device *dev,
enable = !!enable;
if (led_dat->sata == enable)
- return count;
+ goto exit;
- ret = ns2_led_get_mode(led_dat, &mode);
- if (ret < 0)
- return ret;
+ led_dat->sata = enable;
+
+ if (!led_get_brightness(led_cdev))
+ goto exit;
- if (enable && mode == NS_V2_LED_ON)
+ if (enable)
ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
- if (!enable && mode == NS_V2_LED_SATA)
+ else
ns2_led_set_mode(led_dat, NS_V2_LED_ON);
- led_dat->sata = enable;
-
+exit:
return count;
}
@@ -199,7 +195,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
enum ns2_led_modes mode;
ret = devm_gpio_request_one(&pdev->dev, template->cmd,
- gpio_get_value(template->cmd) ?
+ gpio_get_value_cansleep(template->cmd) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
template->name);
if (ret) {
@@ -209,7 +205,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
}
ret = devm_gpio_request_one(&pdev->dev, template->slow,
- gpio_get_value(template->slow) ?
+ gpio_get_value_cansleep(template->slow) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
template->name);
if (ret) {
@@ -228,6 +224,10 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
led_dat->cdev.groups = ns2_led_groups;
led_dat->cmd = template->cmd;
led_dat->slow = template->slow;
+ led_dat->can_sleep = gpio_cansleep(led_dat->cmd) |
+ gpio_cansleep(led_dat->slow);
+ led_dat->modval = template->modval;
+ led_dat->num_modes = template->num_modes;
ret = ns2_led_get_mode(led_dat, &mode);
if (ret < 0)
@@ -238,6 +238,8 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
led_dat->cdev.brightness =
(mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;
+ INIT_WORK(&led_dat->work, ns2_led_work);
+
ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
if (ret < 0)
return ret;
@@ -248,6 +250,7 @@ create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
static void delete_ns2_led(struct ns2_led_data *led_dat)
{
led_classdev_unregister(&led_dat->cdev);
+ cancel_work_sync(&led_dat->work);
}
#ifdef CONFIG_OF_GPIO
@@ -259,9 +262,8 @@ ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
{
struct device_node *np = dev->of_node;
struct device_node *child;
- struct ns2_led *leds;
+ struct ns2_led *led, *leds;
int num_leds = 0;
- int i = 0;
num_leds = of_get_child_count(np);
if (!num_leds)
@@ -272,26 +274,57 @@ ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
if (!leds)
return -ENOMEM;
+ led = leds;
for_each_child_of_node(np, child) {
const char *string;
- int ret;
+ int ret, i, num_modes;
+ struct ns2_led_modval *modval;
ret = of_get_named_gpio(child, "cmd-gpio", 0);
if (ret < 0)
return ret;
- leds[i].cmd = ret;
+ led->cmd = ret;
ret = of_get_named_gpio(child, "slow-gpio", 0);
if (ret < 0)
return ret;
- leds[i].slow = ret;
+ led->slow = ret;
ret = of_property_read_string(child, "label", &string);
- leds[i].name = (ret == 0) ? string : child->name;
+ led->name = (ret == 0) ? string : child->name;
ret = of_property_read_string(child, "linux,default-trigger",
&string);
if (ret == 0)
- leds[i].default_trigger = string;
+ led->default_trigger = string;
+
+ ret = of_property_count_u32_elems(child, "modes-map");
+ if (ret < 0 || ret % 3) {
+ dev_err(dev,
+ "Missing or malformed modes-map property\n");
+ return -EINVAL;
+ }
+
+ num_modes = ret / 3;
+ modval = devm_kzalloc(dev,
+ num_modes * sizeof(struct ns2_led_modval),
+ GFP_KERNEL);
+ if (!modval)
+ return -ENOMEM;
+
+ for (i = 0; i < num_modes; i++) {
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i,
+ (u32 *) &modval[i].mode);
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i + 1,
+ (u32 *) &modval[i].cmd_level);
+ of_property_read_u32_index(child,
+ "modes-map", 3 * i + 2,
+ (u32 *) &modval[i].slow_level);
+ }
+
+ led->num_modes = num_modes;
+ led->modval = modval;
- i++;
+ led++;
}
pdata->leds = leds;