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 void 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
= be64_to_cpu(msg
.params
[1]);
122 if (rc
!= OPAL_SUCCESS
)
123 dev_err(dev
, "%s : OAPL async call returned failed [rc=%d]\n",
127 opal_async_release_token(token
);
131 * This function fetches the LED state for a given LED type for
132 * mentioned LED classdev structure.
134 static enum led_brightness
powernv_led_get(struct powernv_led_data
*powernv_led
)
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
;
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
;
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",
155 led_mask
= be64_to_cpu(mask
);
156 led_value
= be64_to_cpu(value
);
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
);
165 /* LED status value */
166 if ((led_value
>> powernv_led
->led_type
) & OPAL_SLOT_LED_STATE_ON
)
173 * LED classdev 'brightness_get' function. This schedules work
174 * to update LED state.
176 static void powernv_brightness_set(struct led_classdev
*led_cdev
,
177 enum led_brightness value
)
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
;
183 /* Do not modify LED in unload path */
184 if (powernv_led_common
->led_disabled
)
187 mutex_lock(&powernv_led_common
->lock
);
188 powernv_led_set(powernv_led
, value
);
189 mutex_unlock(&powernv_led_common
->lock
);
192 /* LED classdev 'brightness_get' function */
193 static enum led_brightness
powernv_brightness_get(struct led_classdev
*led_cdev
)
195 struct powernv_led_data
*powernv_led
=
196 container_of(led_cdev
, struct powernv_led_data
, cdev
);
198 return powernv_led_get(powernv_led
);
202 * This function registers classdev structure for any given type of LED on
203 * a given child LED device node.
205 static int powernv_led_create(struct device
*dev
,
206 struct powernv_led_data
*powernv_led
,
207 const char *led_type_desc
)
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
);
219 /* Create the name for classdev */
220 powernv_led
->cdev
.name
= devm_kasprintf(dev
, GFP_KERNEL
, "%s:%s",
221 powernv_led
->loc_code
,
223 if (!powernv_led
->cdev
.name
) {
225 "%s: Memory allocation failed for classdev name\n",
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
;
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
);
262 while ((cur
= of_prop_next_string(p
, cur
)) != NULL
) {
263 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
);
280 /* Platform driver probe */
281 static int powernv_led_probe(struct platform_device
*pdev
)
283 struct device_node
*led_node
;
284 struct powernv_led_common
*powernv_led_common
;
285 struct device
*dev
= &pdev
->dev
;
287 led_node
= of_find_node_by_path("/ibm,opal/leds");
289 dev_err(dev
, "%s: LED parent device node not found\n",
294 powernv_led_common
= devm_kzalloc(dev
, sizeof(*powernv_led_common
),
296 if (!powernv_led_common
)
299 mutex_init(&powernv_led_common
->lock
);
300 powernv_led_common
->max_led_type
= cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX
);
302 platform_set_drvdata(pdev
, powernv_led_common
);
304 return powernv_led_classdev(pdev
, led_node
, powernv_led_common
);
307 /* Platform driver remove */
308 static int powernv_led_remove(struct platform_device
*pdev
)
310 struct powernv_led_common
*powernv_led_common
;
312 /* Disable LED operation */
313 powernv_led_common
= platform_get_drvdata(pdev
);
314 powernv_led_common
->led_disabled
= true;
317 mutex_destroy(&powernv_led_common
->lock
);
319 dev_info(&pdev
->dev
, "PowerNV led module unregistered\n");
323 /* Platform driver property match */
324 static const struct of_device_id powernv_led_match
[] = {
326 .compatible
= "ibm,opal-v3-led",
330 MODULE_DEVICE_TABLE(of
, powernv_led_match
);
332 static struct platform_driver powernv_led_driver
= {
333 .probe
= powernv_led_probe
,
334 .remove
= powernv_led_remove
,
336 .name
= "powernv-led-driver",
337 .of_match_table
= powernv_led_match
,
341 module_platform_driver(powernv_led_driver
);
343 MODULE_LICENSE("GPL v2");
344 MODULE_DESCRIPTION("PowerNV LED driver");
345 MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");