1 // SPDX-License-Identifier: GPL-2.0-or-later
5 * Copyright IBM Corp. 2015
7 * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
8 * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
11 #include <linux/leds.h>
12 #include <linux/module.h>
14 #include <linux/platform_device.h>
15 #include <linux/slab.h>
16 #include <linux/types.h>
20 /* Map LED type to description. */
25 static const struct led_type_map led_type_map
[] = {
26 {OPAL_SLOT_LED_TYPE_ID
, "identify"},
27 {OPAL_SLOT_LED_TYPE_FAULT
, "fault"},
28 {OPAL_SLOT_LED_TYPE_ATTN
, "attention"},
32 struct powernv_led_common
{
34 * By default unload path resets all the LEDs. But on PowerNV
35 * platform we want to retain LED state across reboot as these
36 * are controlled by firmware. Also service processor can modify
37 * the LEDs independent of OS. Hence avoid resetting LEDs in
42 /* Max supported LED type */
49 /* PowerNV LED data */
50 struct powernv_led_data
{
51 struct led_classdev cdev
;
52 char *loc_code
; /* LED location code */
53 int led_type
; /* OPAL_SLOT_LED_TYPE_* */
55 struct powernv_led_common
*common
;
59 /* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
60 static int powernv_get_led_type(const char *led_type_desc
)
64 for (i
= 0; i
< ARRAY_SIZE(led_type_map
); i
++)
65 if (!strcmp(led_type_map
[i
].desc
, led_type_desc
))
66 return led_type_map
[i
].type
;
72 * This commits the state change of the requested LED through an OPAL call.
73 * This function is called from work queue task context when ever it gets
74 * scheduled. This function can sleep at opal_async_wait_response call.
76 static int powernv_led_set(struct powernv_led_data
*powernv_led
,
77 enum led_brightness value
)
80 u64 led_mask
, led_value
= 0;
83 struct device
*dev
= powernv_led
->cdev
.dev
;
84 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
86 /* Prepare for the OPAL call */
87 max_type
= powernv_led_common
->max_led_type
;
88 led_mask
= OPAL_SLOT_LED_STATE_ON
<< powernv_led
->led_type
;
93 token
= opal_async_get_token_interruptible();
95 if (token
!= -ERESTARTSYS
)
96 dev_err(dev
, "%s: Couldn't get OPAL async token\n",
101 rc
= opal_leds_set_ind(token
, powernv_led
->loc_code
,
102 led_mask
, led_value
, &max_type
);
103 if (rc
!= OPAL_ASYNC_COMPLETION
) {
104 dev_err(dev
, "%s: OPAL set LED call failed for %s [rc=%d]\n",
105 __func__
, powernv_led
->loc_code
, rc
);
109 rc
= opal_async_wait_response(token
, &msg
);
112 "%s: Failed to wait for the async response [rc=%d]\n",
117 rc
= opal_get_async_rc(msg
);
118 if (rc
!= OPAL_SUCCESS
)
119 dev_err(dev
, "%s : OAPL async call returned failed [rc=%d]\n",
123 opal_async_release_token(token
);
128 * This function fetches the LED state for a given LED type for
129 * mentioned LED classdev structure.
131 static enum led_brightness
powernv_led_get(struct powernv_led_data
*powernv_led
)
134 __be64 mask
, value
, max_type
;
135 u64 led_mask
, led_value
;
136 struct device
*dev
= powernv_led
->cdev
.dev
;
137 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
139 /* Fetch all LED status */
140 mask
= cpu_to_be64(0);
141 value
= cpu_to_be64(0);
142 max_type
= powernv_led_common
->max_led_type
;
144 rc
= opal_leds_get_ind(powernv_led
->loc_code
,
145 &mask
, &value
, &max_type
);
146 if (rc
!= OPAL_SUCCESS
&& rc
!= OPAL_PARTIAL
) {
147 dev_err(dev
, "%s: OPAL get led call failed [rc=%d]\n",
152 led_mask
= be64_to_cpu(mask
);
153 led_value
= be64_to_cpu(value
);
155 /* LED status available */
156 if (!((led_mask
>> powernv_led
->led_type
) & OPAL_SLOT_LED_STATE_ON
)) {
157 dev_err(dev
, "%s: LED status not available for %s\n",
158 __func__
, powernv_led
->cdev
.name
);
162 /* LED status value */
163 if ((led_value
>> powernv_led
->led_type
) & OPAL_SLOT_LED_STATE_ON
)
170 * LED classdev 'brightness_get' function. This schedules work
171 * to update LED state.
173 static int powernv_brightness_set(struct led_classdev
*led_cdev
,
174 enum led_brightness value
)
176 struct powernv_led_data
*powernv_led
=
177 container_of(led_cdev
, struct powernv_led_data
, cdev
);
178 struct powernv_led_common
*powernv_led_common
= powernv_led
->common
;
181 /* Do not modify LED in unload path */
182 if (powernv_led_common
->led_disabled
)
185 mutex_lock(&powernv_led_common
->lock
);
186 rc
= powernv_led_set(powernv_led
, value
);
187 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
)
226 powernv_led
->cdev
.brightness_set_blocking
= powernv_brightness_set
;
227 powernv_led
->cdev
.brightness_get
= powernv_brightness_get
;
228 powernv_led
->cdev
.brightness
= LED_OFF
;
229 powernv_led
->cdev
.max_brightness
= LED_FULL
;
231 /* Register the classdev */
232 rc
= devm_led_classdev_register(dev
, &powernv_led
->cdev
);
234 dev_err(dev
, "%s: Classdev registration failed for %s\n",
235 __func__
, powernv_led
->cdev
.name
);
241 /* Go through LED device tree node and register LED classdev structure */
242 static int powernv_led_classdev(struct platform_device
*pdev
,
243 struct device_node
*led_node
,
244 struct powernv_led_common
*powernv_led_common
)
246 const char *cur
= NULL
;
249 struct device_node
*np
;
250 struct powernv_led_data
*powernv_led
;
251 struct device
*dev
= &pdev
->dev
;
253 for_each_available_child_of_node(led_node
, np
) {
254 p
= of_find_property(np
, "led-types", NULL
);
256 while ((cur
= of_prop_next_string(p
, cur
)) != NULL
) {
257 powernv_led
= devm_kzalloc(dev
, sizeof(*powernv_led
),
264 powernv_led
->common
= powernv_led_common
;
265 powernv_led
->loc_code
= (char *)np
->name
;
267 rc
= powernv_led_create(dev
, powernv_led
, cur
);
278 /* Platform driver probe */
279 static int powernv_led_probe(struct platform_device
*pdev
)
281 struct device_node
*led_node
;
282 struct powernv_led_common
*powernv_led_common
;
283 struct device
*dev
= &pdev
->dev
;
286 led_node
= of_find_node_by_path("/ibm,opal/leds");
288 dev_err(dev
, "%s: LED parent device node not found\n",
293 powernv_led_common
= devm_kzalloc(dev
, sizeof(*powernv_led_common
),
295 if (!powernv_led_common
) {
300 mutex_init(&powernv_led_common
->lock
);
301 powernv_led_common
->max_led_type
= cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX
);
303 platform_set_drvdata(pdev
, powernv_led_common
);
305 rc
= powernv_led_classdev(pdev
, led_node
, powernv_led_common
);
307 of_node_put(led_node
);
311 /* Platform driver remove */
312 static int powernv_led_remove(struct platform_device
*pdev
)
314 struct powernv_led_common
*powernv_led_common
;
316 /* Disable LED operation */
317 powernv_led_common
= platform_get_drvdata(pdev
);
318 powernv_led_common
->led_disabled
= true;
321 mutex_destroy(&powernv_led_common
->lock
);
323 dev_info(&pdev
->dev
, "PowerNV led module unregistered\n");
327 /* Platform driver property match */
328 static const struct of_device_id powernv_led_match
[] = {
330 .compatible
= "ibm,opal-v3-led",
334 MODULE_DEVICE_TABLE(of
, powernv_led_match
);
336 static struct platform_driver powernv_led_driver
= {
337 .probe
= powernv_led_probe
,
338 .remove
= powernv_led_remove
,
340 .name
= "powernv-led-driver",
341 .of_match_table
= powernv_led_match
,
345 module_platform_driver(powernv_led_driver
);
347 MODULE_LICENSE("GPL v2");
348 MODULE_DESCRIPTION("PowerNV LED driver");
349 MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");