aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/leds/leds-powernv.txt26
-rw-r--r--drivers/leds/Kconfig11
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-powernv.c345
4 files changed, 383 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/leds/leds-powernv.txt b/Documentation/devicetree/bindings/leds/leds-powernv.txt
new file mode 100644
index 000000000000..66655690f749
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-powernv.txt
@@ -0,0 +1,26 @@
1Device Tree binding for LEDs on IBM Power Systems
2-------------------------------------------------
3
4Required properties:
5- compatible : Should be "ibm,opal-v3-led".
6- led-mode : Should be "lightpath" or "guidinglight".
7
8Each location code of FRU/Enclosure must be expressed in the
9form of a sub-node.
10
11Required properties for the sub nodes:
12- led-types : Supported LED types (attention/identify/fault) provided
13 in the form of string array.
14
15Example:
16
17leds {
18 compatible = "ibm,opal-v3-led";
19 led-mode = "lightpath";
20
21 U78C9.001.RST0027-P1-C1 {
22 led-types = "identify", "fault";
23 };
24 ...
25 ...
26};
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 9ad35f72ab4c..f218cc3acc10 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -560,6 +560,17 @@ config LEDS_BLINKM
560 This option enables support for the BlinkM RGB LED connected 560 This option enables support for the BlinkM RGB LED connected
561 through I2C. Say Y to enable support for the BlinkM LED. 561 through I2C. Say Y to enable support for the BlinkM LED.
562 562
563config LEDS_POWERNV
564 tristate "LED support for PowerNV Platform"
565 depends on LEDS_CLASS
566 depends on PPC_POWERNV
567 depends on OF
568 help
569 This option enables support for the system LEDs present on
570 PowerNV platforms. Say 'y' to enable this support in kernel.
571 To compile this driver as a module, choose 'm' here: the module
572 will be called leds-powernv.
573
563config LEDS_SYSCON 574config LEDS_SYSCON
564 bool "LED support for LEDs on system controllers" 575 bool "LED support for LEDs on system controllers"
565 depends on LEDS_CLASS=y 576 depends on LEDS_CLASS=y
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index 8d6a24a2f513..6a943d16ecab 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -65,6 +65,7 @@ obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o
65obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o 65obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o
66obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o 66obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o
67obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o 67obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o
68obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o
68 69
69# LED SPI Drivers 70# LED SPI Drivers
70obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o 71obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o
diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c
new file mode 100644
index 000000000000..a2fea192573b
--- /dev/null
+++ b/drivers/leds/leds-powernv.c
@@ -0,0 +1,345 @@
1/*
2 * PowerNV LED Driver
3 *
4 * Copyright IBM Corp. 2015
5 *
6 * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
7 * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version
12 * 2 of the License, or (at your option) any later version.
13 */
14
15#include <linux/leds.h>
16#include <linux/module.h>
17#include <linux/of.h>
18#include <linux/platform_device.h>
19#include <linux/slab.h>
20#include <linux/types.h>
21
22#include <asm/opal.h>
23
24/* Map LED type to description. */
25struct led_type_map {
26 const int type;
27 const char *desc;
28};
29static const struct led_type_map led_type_map[] = {
30 {OPAL_SLOT_LED_TYPE_ID, POWERNV_LED_TYPE_IDENTIFY},
31 {OPAL_SLOT_LED_TYPE_FAULT, POWERNV_LED_TYPE_FAULT},
32 {OPAL_SLOT_LED_TYPE_ATTN, POWERNV_LED_TYPE_ATTENTION},
33 {-1, NULL},
34};
35
36struct powernv_led_common {
37 /*
38 * By default unload path resets all the LEDs. But on PowerNV
39 * platform we want to retain LED state across reboot as these
40 * are controlled by firmware. Also service processor can modify
41 * the LEDs independent of OS. Hence avoid resetting LEDs in
42 * unload path.
43 */
44 bool led_disabled;
45
46 /* Max supported LED type */
47 __be64 max_led_type;
48
49 /* glabal lock */
50 struct mutex lock;
51};
52
53/* PowerNV LED data */
54struct powernv_led_data {
55 struct led_classdev cdev;
56 char *loc_code; /* LED location code */
57 int led_type; /* OPAL_SLOT_LED_TYPE_* */
58
59 struct powernv_led_common *common;
60};
61
62
63/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
64static int powernv_get_led_type(const char *led_type_desc)
65{
66 int i;
67
68 for (i = 0; i < ARRAY_SIZE(led_type_map); i++)
69 if (!strcmp(led_type_map[i].desc, led_type_desc))
70 return led_type_map[i].type;
71
72 return -1;
73}
74
75/*
76 * This commits the state change of the requested LED through an OPAL call.
77 * This function is called from work queue task context when ever it gets
78 * scheduled. This function can sleep at opal_async_wait_response call.
79 */
80static void powernv_led_set(struct powernv_led_data *powernv_led,
81 enum led_brightness value)
82{
83 int rc, token;
84 u64 led_mask, led_value = 0;
85 __be64 max_type;
86 struct opal_msg msg;
87 struct device *dev = powernv_led->cdev.dev;
88 struct powernv_led_common *powernv_led_common = powernv_led->common;
89
90 /* Prepare for the OPAL call */
91 max_type = powernv_led_common->max_led_type;
92 led_mask = OPAL_SLOT_LED_STATE_ON << powernv_led->led_type;
93 if (value)
94 led_value = led_mask;
95
96 /* OPAL async call */
97 token = opal_async_get_token_interruptible();
98 if (token < 0) {
99 if (token != -ERESTARTSYS)
100 dev_err(dev, "%s: Couldn't get OPAL async token\n",
101 __func__);
102 return;
103 }
104
105 rc = opal_leds_set_ind(token, powernv_led->loc_code,
106 led_mask, led_value, &max_type);
107 if (rc != OPAL_ASYNC_COMPLETION) {
108 dev_err(dev, "%s: OPAL set LED call failed for %s [rc=%d]\n",
109 __func__, powernv_led->loc_code, rc);
110 goto out_token;
111 }
112
113 rc = opal_async_wait_response(token, &msg);
114 if (rc) {
115 dev_err(dev,
116 "%s: Failed to wait for the async response [rc=%d]\n",
117 __func__, rc);
118 goto out_token;
119 }
120
121 rc = be64_to_cpu(msg.params[1]);
122 if (rc != OPAL_SUCCESS)
123 dev_err(dev, "%s : OAPL async call returned failed [rc=%d]\n",
124 __func__, rc);
125
126out_token:
127 opal_async_release_token(token);
128}
129
130/*
131 * This function fetches the LED state for a given LED type for
132 * mentioned LED classdev structure.
133 */
134static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led)
135{
136 int rc;
137 __be64 mask, value, max_type;
138 u64 led_mask, led_value;
139 struct device *dev = powernv_led->cdev.dev;
140 struct powernv_led_common *powernv_led_common = powernv_led->common;
141
142 /* Fetch all LED status */
143 mask = cpu_to_be64(0);
144 value = cpu_to_be64(0);
145 max_type = powernv_led_common->max_led_type;
146
147 rc = opal_leds_get_ind(powernv_led->loc_code,
148 &mask, &value, &max_type);
149 if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) {
150 dev_err(dev, "%s: OPAL get led call failed [rc=%d]\n",
151 __func__, rc);
152 return LED_OFF;
153 }
154
155 led_mask = be64_to_cpu(mask);
156 led_value = be64_to_cpu(value);
157
158 /* LED status available */
159 if (!((led_mask >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)) {
160 dev_err(dev, "%s: LED status not available for %s\n",
161 __func__, powernv_led->cdev.name);
162 return LED_OFF;
163 }
164
165 /* LED status value */
166 if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)
167 return LED_FULL;
168
169 return LED_OFF;
170}
171
172/*
173 * LED classdev 'brightness_get' function. This schedules work
174 * to update LED state.
175 */
176static void powernv_brightness_set(struct led_classdev *led_cdev,
177 enum led_brightness value)
178{
179 struct powernv_led_data *powernv_led =
180 container_of(led_cdev, struct powernv_led_data, cdev);
181 struct powernv_led_common *powernv_led_common = powernv_led->common;
182
183 /* Do not modify LED in unload path */
184 if (powernv_led_common->led_disabled)
185 return;
186
187 mutex_lock(&powernv_led_common->lock);
188 powernv_led_set(powernv_led, value);
189 mutex_unlock(&powernv_led_common->lock);
190}
191
192/* LED classdev 'brightness_get' function */
193static enum led_brightness powernv_brightness_get(struct led_classdev *led_cdev)
194{
195 struct powernv_led_data *powernv_led =
196 container_of(led_cdev, struct powernv_led_data, cdev);
197
198 return powernv_led_get(powernv_led);
199}
200
201/*
202 * This function registers classdev structure for any given type of LED on
203 * a given child LED device node.
204 */
205static int powernv_led_create(struct device *dev,
206 struct powernv_led_data *powernv_led,
207 const char *led_type_desc)
208{
209 int rc;
210
211 /* Make sure LED type is supported */
212 powernv_led->led_type = powernv_get_led_type(led_type_desc);
213 if (powernv_led->led_type == -1) {
214 dev_warn(dev, "%s: No support for led type : %s\n",
215 __func__, led_type_desc);
216 return -EINVAL;
217 }
218
219 /* Create the name for classdev */
220 powernv_led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
221 powernv_led->loc_code,
222 led_type_desc);
223 if (!powernv_led->cdev.name) {
224 dev_err(dev,
225 "%s: Memory allocation failed for classdev name\n",
226 __func__);
227 return -ENOMEM;
228 }
229
230 powernv_led->cdev.brightness_set = powernv_brightness_set;
231 powernv_led->cdev.brightness_get = powernv_brightness_get;
232 powernv_led->cdev.brightness = LED_OFF;
233 powernv_led->cdev.max_brightness = LED_FULL;
234
235 /* Register the classdev */
236 rc = devm_led_classdev_register(dev, &powernv_led->cdev);
237 if (rc) {
238 dev_err(dev, "%s: Classdev registration failed for %s\n",
239 __func__, powernv_led->cdev.name);
240 }
241
242 return rc;
243}
244
245/* Go through LED device tree node and register LED classdev structure */
246static int powernv_led_classdev(struct platform_device *pdev,
247 struct device_node *led_node,
248 struct powernv_led_common *powernv_led_common)
249{
250 const char *cur = NULL;
251 int rc = -1;
252 struct property *p;
253 struct device_node *np;
254 struct powernv_led_data *powernv_led;
255 struct device *dev = &pdev->dev;
256
257 for_each_child_of_node(led_node, np) {
258 p = of_find_property(np, "led-types", NULL);
259 if (!p)
260 continue;
261
262 while ((cur = of_prop_next_string(p, cur)) != NULL) {
263 powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),
264 GFP_KERNEL);
265 if (!powernv_led)
266 return -ENOMEM;
267
268 powernv_led->common = powernv_led_common;
269 powernv_led->loc_code = (char *)np->name;
270
271 rc = powernv_led_create(dev, powernv_led, cur);
272 if (rc)
273 return rc;
274 } /* while end */
275 }
276
277 return rc;
278}
279
280/* Platform driver probe */
281static int powernv_led_probe(struct platform_device *pdev)
282{
283 struct device_node *led_node;
284 struct powernv_led_common *powernv_led_common;
285 struct device *dev = &pdev->dev;
286
287 led_node = of_find_node_by_path("/ibm,opal/leds");
288 if (!led_node) {
289 dev_err(dev, "%s: LED parent device node not found\n",
290 __func__);
291 return -EINVAL;
292 }
293
294 powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
295 GFP_KERNEL);
296 if (!powernv_led_common)
297 return -ENOMEM;
298
299 mutex_init(&powernv_led_common->lock);
300 powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);
301
302 platform_set_drvdata(pdev, powernv_led_common);
303
304 return powernv_led_classdev(pdev, led_node, powernv_led_common);
305}
306
307/* Platform driver remove */
308static int powernv_led_remove(struct platform_device *pdev)
309{
310 struct powernv_led_common *powernv_led_common;
311
312 /* Disable LED operation */
313 powernv_led_common = platform_get_drvdata(pdev);
314 powernv_led_common->led_disabled = true;
315
316 /* Destroy lock */
317 mutex_destroy(&powernv_led_common->lock);
318
319 dev_info(&pdev->dev, "PowerNV led module unregistered\n");
320 return 0;
321}
322
323/* Platform driver property match */
324static const struct of_device_id powernv_led_match[] = {
325 {
326 .compatible = "ibm,opal-v3-led",
327 },
328 {},
329};
330MODULE_DEVICE_TABLE(of, powernv_led_match);
331
332static struct platform_driver powernv_led_driver = {
333 .probe = powernv_led_probe,
334 .remove = powernv_led_remove,
335 .driver = {
336 .name = "powernv-led-driver",
337 .of_match_table = powernv_led_match,
338 },
339};
340
341module_platform_driver(powernv_led_driver);
342
343MODULE_LICENSE("GPL v2");
344MODULE_DESCRIPTION("PowerNV LED driver");
345MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");