linux/drivers/leds/leds-powernv.c
<<
>>
Prefs
   1/*
   2 * PowerNV LED Driver
   3 *
   4 * Copyright IBM Corp. 2015
   5 *
   6 * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com>
   7 * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com>
   8 *
   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.
  13 */
  14
  15#include <linux/leds.h>
  16#include <linux/module.h>
  17#include <linux/of.h>
  18#include <linux/platform_device.h>
  19#include <linux/slab.h>
  20#include <linux/types.h>
  21
  22#include <asm/opal.h>
  23
  24/* Map LED type to description. */
  25struct led_type_map {
  26        const int       type;
  27        const char      *desc;
  28};
  29static 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"},
  33        {-1,                            NULL},
  34};
  35
  36struct powernv_led_common {
  37        /*
  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
  42         * unload path.
  43         */
  44        bool            led_disabled;
  45
  46        /* Max supported LED type */
  47        __be64          max_led_type;
  48
  49        /* glabal lock */
  50        struct mutex    lock;
  51};
  52
  53/* PowerNV LED data */
  54struct powernv_led_data {
  55        struct led_classdev     cdev;
  56        char                    *loc_code;      /* LED location code */
  57        int                     led_type;       /* OPAL_SLOT_LED_TYPE_* */
  58
  59        struct powernv_led_common *common;
  60};
  61
  62
  63/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */
  64static int powernv_get_led_type(const char *led_type_desc)
  65{
  66        int i;
  67
  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;
  71
  72        return -1;
  73}
  74
  75/*
  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.
  79 */
  80static int powernv_led_set(struct powernv_led_data *powernv_led,
  81                            enum led_brightness value)
  82{
  83        int rc, token;
  84        u64 led_mask, led_value = 0;
  85        __be64 max_type;
  86        struct opal_msg msg;
  87        struct device *dev = powernv_led->cdev.dev;
  88        struct powernv_led_common *powernv_led_common = powernv_led->common;
  89
  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;
  93        if (value)
  94                led_value = led_mask;
  95
  96        /* OPAL async call */
  97        token = opal_async_get_token_interruptible();
  98        if (token < 0) {
  99                if (token != -ERESTARTSYS)
 100                        dev_err(dev, "%s: Couldn't get OPAL async token\n",
 101                                __func__);
 102                return token;
 103        }
 104
 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);
 110                goto out_token;
 111        }
 112
 113        rc = opal_async_wait_response(token, &msg);
 114        if (rc) {
 115                dev_err(dev,
 116                        "%s: Failed to wait for the async response [rc=%d]\n",
 117                        __func__, rc);
 118                goto out_token;
 119        }
 120
 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",
 124                        __func__, rc);
 125
 126out_token:
 127        opal_async_release_token(token);
 128        return rc;
 129}
 130
 131/*
 132 * This function fetches the LED state for a given LED type for
 133 * mentioned LED classdev structure.
 134 */
 135static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led)
 136{
 137        int rc;
 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;
 142
 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;
 147
 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",
 152                        __func__, rc);
 153                return LED_OFF;
 154        }
 155
 156        led_mask = be64_to_cpu(mask);
 157        led_value = be64_to_cpu(value);
 158
 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);
 163                return LED_OFF;
 164        }
 165
 166        /* LED status value */
 167        if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)
 168                return LED_FULL;
 169
 170        return LED_OFF;
 171}
 172
 173/*
 174 * LED classdev 'brightness_get' function. This schedules work
 175 * to update LED state.
 176 */
 177static int powernv_brightness_set(struct led_classdev *led_cdev,
 178                                   enum led_brightness value)
 179{
 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;
 183        int rc;
 184
 185        /* Do not modify LED in unload path */
 186        if (powernv_led_common->led_disabled)
 187                return 0;
 188
 189        mutex_lock(&powernv_led_common->lock);
 190        rc = powernv_led_set(powernv_led, value);
 191        mutex_unlock(&powernv_led_common->lock);
 192
 193        return rc;
 194}
 195
 196/* LED classdev 'brightness_get' function */
 197static enum led_brightness powernv_brightness_get(struct led_classdev *led_cdev)
 198{
 199        struct powernv_led_data *powernv_led =
 200                container_of(led_cdev, struct powernv_led_data, cdev);
 201
 202        return powernv_led_get(powernv_led);
 203}
 204
 205/*
 206 * This function registers classdev structure for any given type of LED on
 207 * a given child LED device node.
 208 */
 209static int powernv_led_create(struct device *dev,
 210                              struct powernv_led_data *powernv_led,
 211                              const char *led_type_desc)
 212{
 213        int rc;
 214
 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);
 220                return -EINVAL;
 221        }
 222
 223        /* Create the name for classdev */
 224        powernv_led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s",
 225                                                powernv_led->loc_code,
 226                                                led_type_desc);
 227        if (!powernv_led->cdev.name)
 228                return -ENOMEM;
 229
 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;
 234
 235        /* Register the classdev */
 236        rc = devm_led_classdev_register(dev, &powernv_led->cdev);
 237        if (rc) {
 238                dev_err(dev, "%s: Classdev registration failed for %s\n",
 239                        __func__, powernv_led->cdev.name);
 240        }
 241
 242        return rc;
 243}
 244
 245/* Go through LED device tree node and register LED classdev structure */
 246static int powernv_led_classdev(struct platform_device *pdev,
 247                                struct device_node *led_node,
 248                                struct powernv_led_common *powernv_led_common)
 249{
 250        const char *cur = NULL;
 251        int rc = -1;
 252        struct property *p;
 253        struct device_node *np;
 254        struct powernv_led_data *powernv_led;
 255        struct device *dev = &pdev->dev;
 256
 257        for_each_child_of_node(led_node, np) {
 258                p = of_find_property(np, "led-types", NULL);
 259
 260                while ((cur = of_prop_next_string(p, cur)) != NULL) {
 261                        powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),
 262                                                   GFP_KERNEL);
 263                        if (!powernv_led) {
 264                                of_node_put(np);
 265                                return -ENOMEM;
 266                        }
 267
 268                        powernv_led->common = powernv_led_common;
 269                        powernv_led->loc_code = (char *)np->name;
 270
 271                        rc = powernv_led_create(dev, powernv_led, cur);
 272                        if (rc) {
 273                                of_node_put(np);
 274                                return rc;
 275                        }
 276                } /* while end */
 277        }
 278
 279        return rc;
 280}
 281
 282/* Platform driver probe */
 283static int powernv_led_probe(struct platform_device *pdev)
 284{
 285        struct device_node *led_node;
 286        struct powernv_led_common *powernv_led_common;
 287        struct device *dev = &pdev->dev;
 288
 289        led_node = of_find_node_by_path("/ibm,opal/leds");
 290        if (!led_node) {
 291                dev_err(dev, "%s: LED parent device node not found\n",
 292                        __func__);
 293                return -EINVAL;
 294        }
 295
 296        powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),
 297                                          GFP_KERNEL);
 298        if (!powernv_led_common)
 299                return -ENOMEM;
 300
 301        mutex_init(&powernv_led_common->lock);
 302        powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);
 303
 304        platform_set_drvdata(pdev, powernv_led_common);
 305
 306        return powernv_led_classdev(pdev, led_node, powernv_led_common);
 307}
 308
 309/* Platform driver remove */
 310static int powernv_led_remove(struct platform_device *pdev)
 311{
 312        struct powernv_led_common *powernv_led_common;
 313
 314        /* Disable LED operation */
 315        powernv_led_common = platform_get_drvdata(pdev);
 316        powernv_led_common->led_disabled = true;
 317
 318        /* Destroy lock */
 319        mutex_destroy(&powernv_led_common->lock);
 320
 321        dev_info(&pdev->dev, "PowerNV led module unregistered\n");
 322        return 0;
 323}
 324
 325/* Platform driver property match */
 326static const struct of_device_id powernv_led_match[] = {
 327        {
 328                .compatible     = "ibm,opal-v3-led",
 329        },
 330        {},
 331};
 332MODULE_DEVICE_TABLE(of, powernv_led_match);
 333
 334static struct platform_driver powernv_led_driver = {
 335        .probe  = powernv_led_probe,
 336        .remove = powernv_led_remove,
 337        .driver = {
 338                .name = "powernv-led-driver",
 339                .of_match_table = powernv_led_match,
 340        },
 341};
 342
 343module_platform_driver(powernv_led_driver);
 344
 345MODULE_LICENSE("GPL v2");
 346MODULE_DESCRIPTION("PowerNV LED driver");
 347MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");
 348