leds-powernv.c (8986B)
1// SPDX-License-Identifier: GPL-2.0-or-later 2/* 3 * PowerNV LED Driver 4 * 5 * Copyright IBM Corp. 2015 6 * 7 * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com> 8 * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com> 9 */ 10 11#include <linux/leds.h> 12#include <linux/module.h> 13#include <linux/of.h> 14#include <linux/platform_device.h> 15#include <linux/slab.h> 16#include <linux/types.h> 17 18#include <asm/opal.h> 19 20/* Map LED type to description. */ 21struct led_type_map { 22 const int type; 23 const char *desc; 24}; 25static 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"}, 29 {-1, NULL}, 30}; 31 32struct powernv_led_common { 33 /* 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 38 * unload path. 39 */ 40 bool led_disabled; 41 42 /* Max supported LED type */ 43 __be64 max_led_type; 44 45 /* glabal lock */ 46 struct mutex lock; 47}; 48 49/* PowerNV LED data */ 50struct powernv_led_data { 51 struct led_classdev cdev; 52 char *loc_code; /* LED location code */ 53 int led_type; /* OPAL_SLOT_LED_TYPE_* */ 54 55 struct powernv_led_common *common; 56}; 57 58 59/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */ 60static int powernv_get_led_type(const char *led_type_desc) 61{ 62 int i; 63 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; 67 68 return -1; 69} 70 71/* 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. 75 */ 76static int powernv_led_set(struct powernv_led_data *powernv_led, 77 enum led_brightness value) 78{ 79 int rc, token; 80 u64 led_mask, led_value = 0; 81 __be64 max_type; 82 struct opal_msg msg; 83 struct device *dev = powernv_led->cdev.dev; 84 struct powernv_led_common *powernv_led_common = powernv_led->common; 85 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; 89 if (value) 90 led_value = led_mask; 91 92 /* OPAL async call */ 93 token = opal_async_get_token_interruptible(); 94 if (token < 0) { 95 if (token != -ERESTARTSYS) 96 dev_err(dev, "%s: Couldn't get OPAL async token\n", 97 __func__); 98 return token; 99 } 100 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); 106 goto out_token; 107 } 108 109 rc = opal_async_wait_response(token, &msg); 110 if (rc) { 111 dev_err(dev, 112 "%s: Failed to wait for the async response [rc=%d]\n", 113 __func__, rc); 114 goto out_token; 115 } 116 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", 120 __func__, rc); 121 122out_token: 123 opal_async_release_token(token); 124 return rc; 125} 126 127/* 128 * This function fetches the LED state for a given LED type for 129 * mentioned LED classdev structure. 130 */ 131static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led) 132{ 133 int rc; 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; 138 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; 143 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", 148 __func__, rc); 149 return LED_OFF; 150 } 151 152 led_mask = be64_to_cpu(mask); 153 led_value = be64_to_cpu(value); 154 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); 159 return LED_OFF; 160 } 161 162 /* LED status value */ 163 if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON) 164 return LED_FULL; 165 166 return LED_OFF; 167} 168 169/* 170 * LED classdev 'brightness_get' function. This schedules work 171 * to update LED state. 172 */ 173static int powernv_brightness_set(struct led_classdev *led_cdev, 174 enum led_brightness value) 175{ 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; 179 int rc; 180 181 /* Do not modify LED in unload path */ 182 if (powernv_led_common->led_disabled) 183 return 0; 184 185 mutex_lock(&powernv_led_common->lock); 186 rc = powernv_led_set(powernv_led, value); 187 mutex_unlock(&powernv_led_common->lock); 188 189 return rc; 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 return -ENOMEM; 225 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; 230 231 /* Register the classdev */ 232 rc = devm_led_classdev_register(dev, &powernv_led->cdev); 233 if (rc) { 234 dev_err(dev, "%s: Classdev registration failed for %s\n", 235 __func__, powernv_led->cdev.name); 236 } 237 238 return rc; 239} 240 241/* Go through LED device tree node and register LED classdev structure */ 242static int powernv_led_classdev(struct platform_device *pdev, 243 struct device_node *led_node, 244 struct powernv_led_common *powernv_led_common) 245{ 246 const char *cur = NULL; 247 int rc = -1; 248 struct property *p; 249 struct device_node *np; 250 struct powernv_led_data *powernv_led; 251 struct device *dev = &pdev->dev; 252 253 for_each_available_child_of_node(led_node, np) { 254 p = of_find_property(np, "led-types", NULL); 255 256 while ((cur = of_prop_next_string(p, cur)) != NULL) { 257 powernv_led = devm_kzalloc(dev, sizeof(*powernv_led), 258 GFP_KERNEL); 259 if (!powernv_led) { 260 of_node_put(np); 261 return -ENOMEM; 262 } 263 264 powernv_led->common = powernv_led_common; 265 powernv_led->loc_code = (char *)np->name; 266 267 rc = powernv_led_create(dev, powernv_led, cur); 268 if (rc) { 269 of_node_put(np); 270 return rc; 271 } 272 } /* while end */ 273 } 274 275 return rc; 276} 277 278/* Platform driver probe */ 279static int powernv_led_probe(struct platform_device *pdev) 280{ 281 struct device_node *led_node; 282 struct powernv_led_common *powernv_led_common; 283 struct device *dev = &pdev->dev; 284 int rc; 285 286 led_node = of_find_node_by_path("/ibm,opal/leds"); 287 if (!led_node) { 288 dev_err(dev, "%s: LED parent device node not found\n", 289 __func__); 290 return -EINVAL; 291 } 292 293 powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common), 294 GFP_KERNEL); 295 if (!powernv_led_common) { 296 rc = -ENOMEM; 297 goto out; 298 } 299 300 mutex_init(&powernv_led_common->lock); 301 powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX); 302 303 platform_set_drvdata(pdev, powernv_led_common); 304 305 rc = powernv_led_classdev(pdev, led_node, powernv_led_common); 306out: 307 of_node_put(led_node); 308 return rc; 309} 310 311/* Platform driver remove */ 312static int powernv_led_remove(struct platform_device *pdev) 313{ 314 struct powernv_led_common *powernv_led_common; 315 316 /* Disable LED operation */ 317 powernv_led_common = platform_get_drvdata(pdev); 318 powernv_led_common->led_disabled = true; 319 320 /* Destroy lock */ 321 mutex_destroy(&powernv_led_common->lock); 322 323 dev_info(&pdev->dev, "PowerNV led module unregistered\n"); 324 return 0; 325} 326 327/* Platform driver property match */ 328static const struct of_device_id powernv_led_match[] = { 329 { 330 .compatible = "ibm,opal-v3-led", 331 }, 332 {}, 333}; 334MODULE_DEVICE_TABLE(of, powernv_led_match); 335 336static struct platform_driver powernv_led_driver = { 337 .probe = powernv_led_probe, 338 .remove = powernv_led_remove, 339 .driver = { 340 .name = "powernv-led-driver", 341 .of_match_table = powernv_led_match, 342 }, 343}; 344 345module_platform_driver(powernv_led_driver); 346 347MODULE_LICENSE("GPL v2"); 348MODULE_DESCRIPTION("PowerNV LED driver"); 349MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");