root/drivers/leds/leds-powernv.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. powernv_get_led_type
  2. powernv_led_set
  3. powernv_led_get
  4. powernv_brightness_set
  5. powernv_brightness_get
  6. powernv_led_create
  7. powernv_led_classdev
  8. powernv_led_probe
  9. powernv_led_remove

   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. */
  21 struct led_type_map {
  22         const int       type;
  23         const char      *desc;
  24 };
  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"},
  29         {-1,                            NULL},
  30 };
  31 
  32 struct 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 */
  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_* */
  54 
  55         struct powernv_led_common *common;
  56 };
  57 
  58 
  59 /* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
  60 static 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  */
  76 static 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 
 122 out_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  */
 131 static 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  */
 173 static 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 */
 193 static 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  */
 205 static 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 */
 242 static 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_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 */
 279 static 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);
 306 out:
 307         of_node_put(led_node);
 308         return rc;
 309 }
 310 
 311 /* Platform driver remove */
 312 static 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 */
 328 static const struct of_device_id powernv_led_match[] = {
 329         {
 330                 .compatible     = "ibm,opal-v3-led",
 331         },
 332         {},
 333 };
 334 MODULE_DEVICE_TABLE(of, powernv_led_match);
 335 
 336 static 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 
 345 module_platform_driver(powernv_led_driver);
 346 
 347 MODULE_LICENSE("GPL v2");
 348 MODULE_DESCRIPTION("PowerNV LED driver");
 349 MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");

/* [<][>][^][v][top][bottom][index][help] */