4 * Copyright IBM Corp. 2015
6 * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
7 * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
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.
15 #include <linux/leds.h>
16 #include <linux/module.h>
18 #include <linux/platform_device.h>
19 #include <linux/slab.h>
20 #include <linux/types.h>
24 /* Map LED type to description. */
29 static const struct led_type_map led_type_map
[] = {
30 {OPAL_SLOT_LED_TYPE_ID
, "identify"},
31 {OPAL_SLOT_LED_TYPE_FAULT
, "fault"},
32 {OPAL_SLOT_LED_TYPE_ATTN
, "attention"},
36 struct powernv_led_common
{
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
46 /* Max supported LED type */
53 /* PowerNV LED data */
54 struct powernv_led_data
{
55 struct led_classdev cdev
;
56 char *loc_code
; /* LED location code */
57 int led_type
; /* OPAL_SLOT_LED_TYPE_* */
59 struct powernv_led_common
*common
;
63 /* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
64 static int powernv_get_led_type(const char *led_type_desc
)
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
;
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.
80 static int powernv_led_set(struct powernv_led_data
*powernv_led
,
81 enum led_brightness value
)
84 u64 led_mask
, led_value
= 0;
87 struct device
*dev
= powernv_led
->cdev
.dev
;
88 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
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
;
97 token
= opal_async_get_token_interruptible();
99 if (token
!= -ERESTARTSYS
)
100 dev_err(dev
, "%s: Couldn't get OPAL async token\n",
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
);
113 rc
= opal_async_wait_response(token
, &msg
);
116 "%s: Failed to wait for the async response [rc=%d]\n",
121 rc
= opal_get_async_rc(msg
);
122 if (rc
!= OPAL_SUCCESS
)
123 dev_err(dev
, "%s : OAPL async call returned failed [rc=%d]\n",
127 opal_async_release_token(token
);
132 * This function fetches the LED state for a given LED type for
133 * mentioned LED classdev structure.
135 static enum led_brightness
powernv_led_get(struct powernv_led_data
*powernv_led
)
138 __be64 mask
, value
, max_type
;
139 u64 led_mask
, led_value
;
140 struct device
*dev
= powernv_led
->cdev
.dev
;
141 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
143 /* Fetch all LED status */
144 mask
= cpu_to_be64(0);
145 value
= cpu_to_be64(0);
146 max_type
= powernv_led_common
->max_led_type
;
148 rc
= opal_leds_get_ind(powernv_led
->loc_code
,
149 &mask
, &value
, &max_type
);
150 if (rc
!= OPAL_SUCCESS
&& rc
!= OPAL_PARTIAL
) {
151 dev_err(dev
, "%s: OPAL get led call failed [rc=%d]\n",
156 led_mask
= be64_to_cpu(mask
);
157 led_value
= be64_to_cpu(value
);
159 /* LED status available */
160 if (!((led_mask
>> powernv_led
->led_type
) & OPAL_SLOT_LED_STATE_ON
)) {
161 dev_err(dev
, "%s: LED status not available for %s\n",
162 __func__
, powernv_led
->cdev
.name
);
166 /* LED status value */
167 if ((led_value
>> powernv_led
->led_type
) & OPAL_SLOT_LED_STATE_ON
)
174 * LED classdev 'brightness_get' function. This schedules work
175 * to update LED state.
177 static int powernv_brightness_set(struct led_classdev
*led_cdev
,
178 enum led_brightness value
)
180 struct powernv_led_data
*powernv_led
=
181 container_of(led_cdev
, struct powernv_led_data
, cdev
);
182 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
185 /* Do not modify LED in unload path */
186 if (powernv_led_common
->led_disabled
)
189 mutex_lock(&powernv_led_common
->lock
);
190 rc
= powernv_led_set(powernv_led
, value
);
191 mutex_unlock(&powernv_led_common
->lock
);
196 /* LED classdev 'brightness_get' function */
197 static enum led_brightness
powernv_brightness_get(struct led_classdev
*led_cdev
)
199 struct powernv_led_data
*powernv_led
=
200 container_of(led_cdev
, struct powernv_led_data
, cdev
);
202 return powernv_led_get(powernv_led
);
206 * This function registers classdev structure for any given type of LED on
207 * a given child LED device node.
209 static int powernv_led_create(struct device
*dev
,
210 struct powernv_led_data
*powernv_led
,
211 const char *led_type_desc
)
215 /* Make sure LED type is supported */
216 powernv_led
->led_type
= powernv_get_led_type(led_type_desc
);
217 if (powernv_led
->led_type
== -1) {
218 dev_warn(dev
, "%s: No support for led type : %s\n",
219 __func__
, led_type_desc
);
223 /* Create the name for classdev */
224 powernv_led
->cdev
.name
= devm_kasprintf(dev
, GFP_KERNEL
, "%s:%s",
225 powernv_led
->loc_code
,
227 if (!powernv_led
->cdev
.name
)
230 powernv_led
->cdev
.brightness_set_blocking
= 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
;
235 /* Register the classdev */
236 rc
= devm_led_classdev_register(dev
, &powernv_led
->cdev
);
238 dev_err(dev
, "%s: Classdev registration failed for %s\n",
239 __func__
, powernv_led
->cdev
.name
);
245 /* Go through LED device tree node and register LED classdev structure */
246 static int powernv_led_classdev(struct platform_device
*pdev
,
247 struct device_node
*led_node
,
248 struct powernv_led_common
*powernv_led_common
)
250 const char *cur
= NULL
;
253 struct device_node
*np
;
254 struct powernv_led_data
*powernv_led
;
255 struct device
*dev
= &pdev
->dev
;
257 for_each_child_of_node(led_node
, np
) {
258 p
= of_find_property(np
, "led-types", NULL
);
260 while ((cur
= of_prop_next_string(p
, cur
)) != NULL
) {
261 powernv_led
= devm_kzalloc(dev
, sizeof(*powernv_led
),
268 powernv_led
->common
= powernv_led_common
;
269 powernv_led
->loc_code
= (char *)np
->name
;
271 rc
= powernv_led_create(dev
, powernv_led
, cur
);
282 /* Platform driver probe */
283 static int powernv_led_probe(struct platform_device
*pdev
)
285 struct device_node
*led_node
;
286 struct powernv_led_common
*powernv_led_common
;
287 struct device
*dev
= &pdev
->dev
;
289 led_node
= of_find_node_by_path("/ibm,opal/leds");
291 dev_err(dev
, "%s: LED parent device node not found\n",
296 powernv_led_common
= devm_kzalloc(dev
, sizeof(*powernv_led_common
),
298 if (!powernv_led_common
)
301 mutex_init(&powernv_led_common
->lock
);
302 powernv_led_common
->max_led_type
= cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX
);
304 platform_set_drvdata(pdev
, powernv_led_common
);
306 return powernv_led_classdev(pdev
, led_node
, powernv_led_common
);
309 /* Platform driver remove */
310 static int powernv_led_remove(struct platform_device
*pdev
)
312 struct powernv_led_common
*powernv_led_common
;
314 /* Disable LED operation */
315 powernv_led_common
= platform_get_drvdata(pdev
);
316 powernv_led_common
->led_disabled
= true;
319 mutex_destroy(&powernv_led_common
->lock
);
321 dev_info(&pdev
->dev
, "PowerNV led module unregistered\n");
325 /* Platform driver property match */
326 static const struct of_device_id powernv_led_match
[] = {
328 .compatible
= "ibm,opal-v3-led",
332 MODULE_DEVICE_TABLE(of
, powernv_led_match
);
334 static struct platform_driver powernv_led_driver
= {
335 .probe
= powernv_led_probe
,
336 .remove
= powernv_led_remove
,
338 .name
= "powernv-led-driver",
339 .of_match_table
= powernv_led_match
,
343 module_platform_driver(powernv_led_driver
);
345 MODULE_LICENSE("GPL v2");
346 MODULE_DESCRIPTION("PowerNV LED driver");
347 MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");