diff options
| -rw-r--r-- | Documentation/devicetree/bindings/leds/leds-powernv.txt | 26 | ||||
| -rw-r--r-- | drivers/leds/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/leds/Makefile | 1 | ||||
| -rw-r--r-- | drivers/leds/leds-powernv.c | 345 |
4 files changed, 383 insertions, 0 deletions
diff --git a/Documentation/devicetree/bindings/leds/leds-powernv.txt b/Documentation/devicetree/bindings/leds/leds-powernv.txt new file mode 100644 index 000000000000..66655690f749 --- /dev/null +++ b/Documentation/devicetree/bindings/leds/leds-powernv.txt | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | Device Tree binding for LEDs on IBM Power Systems | ||
| 2 | ------------------------------------------------- | ||
| 3 | |||
| 4 | Required properties: | ||
| 5 | - compatible : Should be "ibm,opal-v3-led". | ||
| 6 | - led-mode : Should be "lightpath" or "guidinglight". | ||
| 7 | |||
| 8 | Each location code of FRU/Enclosure must be expressed in the | ||
| 9 | form of a sub-node. | ||
| 10 | |||
| 11 | Required properties for the sub nodes: | ||
| 12 | - led-types : Supported LED types (attention/identify/fault) provided | ||
| 13 | in the form of string array. | ||
| 14 | |||
| 15 | Example: | ||
| 16 | |||
| 17 | leds { | ||
| 18 | compatible = "ibm,opal-v3-led"; | ||
| 19 | led-mode = "lightpath"; | ||
| 20 | |||
| 21 | U78C9.001.RST0027-P1-C1 { | ||
| 22 | led-types = "identify", "fault"; | ||
| 23 | }; | ||
| 24 | ... | ||
| 25 | ... | ||
| 26 | }; | ||
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig index 9ad35f72ab4c..f218cc3acc10 100644 --- a/drivers/leds/Kconfig +++ b/drivers/leds/Kconfig | |||
| @@ -560,6 +560,17 @@ config LEDS_BLINKM | |||
| 560 | This option enables support for the BlinkM RGB LED connected | 560 | This option enables support for the BlinkM RGB LED connected |
| 561 | through I2C. Say Y to enable support for the BlinkM LED. | 561 | through I2C. Say Y to enable support for the BlinkM LED. |
| 562 | 562 | ||
| 563 | config LEDS_POWERNV | ||
| 564 | tristate "LED support for PowerNV Platform" | ||
| 565 | depends on LEDS_CLASS | ||
| 566 | depends on PPC_POWERNV | ||
| 567 | depends on OF | ||
| 568 | help | ||
| 569 | This option enables support for the system LEDs present on | ||
| 570 | PowerNV platforms. Say 'y' to enable this support in kernel. | ||
| 571 | To compile this driver as a module, choose 'm' here: the module | ||
| 572 | will be called leds-powernv. | ||
| 573 | |||
| 563 | config LEDS_SYSCON | 574 | config LEDS_SYSCON |
| 564 | bool "LED support for LEDs on system controllers" | 575 | bool "LED support for LEDs on system controllers" |
| 565 | depends on LEDS_CLASS=y | 576 | depends on LEDS_CLASS=y |
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile index 8d6a24a2f513..6a943d16ecab 100644 --- a/drivers/leds/Makefile +++ b/drivers/leds/Makefile | |||
| @@ -65,6 +65,7 @@ obj-$(CONFIG_LEDS_VERSATILE) += leds-versatile.o | |||
| 65 | obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o | 65 | obj-$(CONFIG_LEDS_MENF21BMC) += leds-menf21bmc.o |
| 66 | obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o | 66 | obj-$(CONFIG_LEDS_PM8941_WLED) += leds-pm8941-wled.o |
| 67 | obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o | 67 | obj-$(CONFIG_LEDS_KTD2692) += leds-ktd2692.o |
| 68 | obj-$(CONFIG_LEDS_POWERNV) += leds-powernv.o | ||
| 68 | 69 | ||
| 69 | # LED SPI Drivers | 70 | # LED SPI Drivers |
| 70 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o | 71 | obj-$(CONFIG_LEDS_DAC124S085) += leds-dac124s085.o |
diff --git a/drivers/leds/leds-powernv.c b/drivers/leds/leds-powernv.c new file mode 100644 index 000000000000..a2fea192573b --- /dev/null +++ b/drivers/leds/leds-powernv.c | |||
| @@ -0,0 +1,345 @@ | |||
| 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. */ | ||
| 25 | struct led_type_map { | ||
| 26 | const int type; | ||
| 27 | const char *desc; | ||
| 28 | }; | ||
| 29 | static const struct led_type_map led_type_map[] = { | ||
| 30 | {OPAL_SLOT_LED_TYPE_ID, POWERNV_LED_TYPE_IDENTIFY}, | ||
| 31 | {OPAL_SLOT_LED_TYPE_FAULT, POWERNV_LED_TYPE_FAULT}, | ||
| 32 | {OPAL_SLOT_LED_TYPE_ATTN, POWERNV_LED_TYPE_ATTENTION}, | ||
| 33 | {-1, NULL}, | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct 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 */ | ||
| 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_* */ | ||
| 58 | |||
| 59 | struct powernv_led_common *common; | ||
| 60 | }; | ||
| 61 | |||
| 62 | |||
| 63 | /* Returns OPAL_SLOT_LED_TYPE_* for given led type string */ | ||
| 64 | static 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 | */ | ||
| 80 | static void 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; | ||
| 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 = be64_to_cpu(msg.params[1]); | ||
| 122 | if (rc != OPAL_SUCCESS) | ||
| 123 | dev_err(dev, "%s : OAPL async call returned failed [rc=%d]\n", | ||
| 124 | __func__, rc); | ||
| 125 | |||
| 126 | out_token: | ||
| 127 | opal_async_release_token(token); | ||
| 128 | } | ||
| 129 | |||
| 130 | /* | ||
| 131 | * This function fetches the LED state for a given LED type for | ||
| 132 | * mentioned LED classdev structure. | ||
| 133 | */ | ||
| 134 | static enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led) | ||
| 135 | { | ||
| 136 | int rc; | ||
| 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; | ||
| 141 | |||
| 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; | ||
| 146 | |||
| 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", | ||
| 151 | __func__, rc); | ||
| 152 | return LED_OFF; | ||
| 153 | } | ||
| 154 | |||
| 155 | led_mask = be64_to_cpu(mask); | ||
| 156 | led_value = be64_to_cpu(value); | ||
| 157 | |||
| 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); | ||
| 162 | return LED_OFF; | ||
| 163 | } | ||
| 164 | |||
| 165 | /* LED status value */ | ||
| 166 | if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON) | ||
| 167 | return LED_FULL; | ||
| 168 | |||
| 169 | return LED_OFF; | ||
| 170 | } | ||
| 171 | |||
| 172 | /* | ||
| 173 | * LED classdev 'brightness_get' function. This schedules work | ||
| 174 | * to update LED state. | ||
| 175 | */ | ||
| 176 | static void powernv_brightness_set(struct led_classdev *led_cdev, | ||
| 177 | enum led_brightness value) | ||
| 178 | { | ||
| 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; | ||
| 182 | |||
| 183 | /* Do not modify LED in unload path */ | ||
| 184 | if (powernv_led_common->led_disabled) | ||
| 185 | return; | ||
| 186 | |||
| 187 | mutex_lock(&powernv_led_common->lock); | ||
| 188 | powernv_led_set(powernv_led, value); | ||
| 189 | mutex_unlock(&powernv_led_common->lock); | ||
| 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 | dev_err(dev, | ||
| 225 | "%s: Memory allocation failed for classdev name\n", | ||
| 226 | __func__); | ||
| 227 | return -ENOMEM; | ||
| 228 | } | ||
| 229 | |||
| 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; | ||
| 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 */ | ||
| 246 | static 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 | if (!p) | ||
| 260 | continue; | ||
| 261 | |||
| 262 | while ((cur = of_prop_next_string(p, cur)) != NULL) { | ||
| 263 | powernv_led = devm_kzalloc(dev, sizeof(*powernv_led), | ||
| 264 | GFP_KERNEL); | ||
| 265 | if (!powernv_led) | ||
| 266 | return -ENOMEM; | ||
| 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 | return rc; | ||
| 274 | } /* while end */ | ||
| 275 | } | ||
| 276 | |||
| 277 | return rc; | ||
| 278 | } | ||
| 279 | |||
| 280 | /* Platform driver probe */ | ||
| 281 | static int powernv_led_probe(struct platform_device *pdev) | ||
| 282 | { | ||
| 283 | struct device_node *led_node; | ||
| 284 | struct powernv_led_common *powernv_led_common; | ||
| 285 | struct device *dev = &pdev->dev; | ||
| 286 | |||
| 287 | led_node = of_find_node_by_path("/ibm,opal/leds"); | ||
| 288 | if (!led_node) { | ||
| 289 | dev_err(dev, "%s: LED parent device node not found\n", | ||
| 290 | __func__); | ||
| 291 | return -EINVAL; | ||
| 292 | } | ||
| 293 | |||
| 294 | powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common), | ||
| 295 | GFP_KERNEL); | ||
| 296 | if (!powernv_led_common) | ||
| 297 | return -ENOMEM; | ||
| 298 | |||
| 299 | mutex_init(&powernv_led_common->lock); | ||
| 300 | powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX); | ||
| 301 | |||
| 302 | platform_set_drvdata(pdev, powernv_led_common); | ||
| 303 | |||
| 304 | return powernv_led_classdev(pdev, led_node, powernv_led_common); | ||
| 305 | } | ||
| 306 | |||
| 307 | /* Platform driver remove */ | ||
| 308 | static int powernv_led_remove(struct platform_device *pdev) | ||
| 309 | { | ||
| 310 | struct powernv_led_common *powernv_led_common; | ||
| 311 | |||
| 312 | /* Disable LED operation */ | ||
| 313 | powernv_led_common = platform_get_drvdata(pdev); | ||
| 314 | powernv_led_common->led_disabled = true; | ||
| 315 | |||
| 316 | /* Destroy lock */ | ||
| 317 | mutex_destroy(&powernv_led_common->lock); | ||
| 318 | |||
| 319 | dev_info(&pdev->dev, "PowerNV led module unregistered\n"); | ||
| 320 | return 0; | ||
| 321 | } | ||
| 322 | |||
| 323 | /* Platform driver property match */ | ||
| 324 | static const struct of_device_id powernv_led_match[] = { | ||
| 325 | { | ||
| 326 | .compatible = "ibm,opal-v3-led", | ||
| 327 | }, | ||
| 328 | {}, | ||
| 329 | }; | ||
| 330 | MODULE_DEVICE_TABLE(of, powernv_led_match); | ||
| 331 | |||
| 332 | static struct platform_driver powernv_led_driver = { | ||
| 333 | .probe = powernv_led_probe, | ||
| 334 | .remove = powernv_led_remove, | ||
| 335 | .driver = { | ||
| 336 | .name = "powernv-led-driver", | ||
| 337 | .of_match_table = powernv_led_match, | ||
| 338 | }, | ||
| 339 | }; | ||
| 340 | |||
| 341 | module_platform_driver(powernv_led_driver); | ||
| 342 | |||
| 343 | MODULE_LICENSE("GPL v2"); | ||
| 344 | MODULE_DESCRIPTION("PowerNV LED driver"); | ||
| 345 | MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>"); | ||
