339 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * PowerNV LED Driver4 *5 * Copyright IBM Corp. 20156 *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 PowerNV35 * platform we want to retain LED state across reboot as these36 * are controlled by firmware. Also service processor can modify37 * the LEDs independent of OS. Hence avoid resetting LEDs in38 * 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 gets74 * 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 for129 * 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 work171 * 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 on203 * 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 powernv_led_data *powernv_led;250 struct device *dev = &pdev->dev;251 252 for_each_available_child_of_node_scoped(led_node, np) {253 p = of_find_property(np, "led-types", NULL);254 255 while ((cur = of_prop_next_string(p, cur)) != NULL) {256 powernv_led = devm_kzalloc(dev, sizeof(*powernv_led),257 GFP_KERNEL);258 if (!powernv_led)259 return -ENOMEM;260 261 powernv_led->common = powernv_led_common;262 powernv_led->loc_code = (char *)np->name;263 264 rc = powernv_led_create(dev, powernv_led, cur);265 if (rc)266 return rc;267 268 } /* while end */269 }270 271 return rc;272}273 274/* Platform driver probe */275static int powernv_led_probe(struct platform_device *pdev)276{277 struct powernv_led_common *powernv_led_common;278 struct device *dev = &pdev->dev;279 struct device_node *led_node280 __free(device_node) = of_find_node_by_path("/ibm,opal/leds");281 282 if (!led_node) {283 dev_err(dev, "%s: LED parent device node not found\n",284 __func__);285 return -EINVAL;286 }287 288 powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common),289 GFP_KERNEL);290 if (!powernv_led_common)291 return -ENOMEM;292 293 mutex_init(&powernv_led_common->lock);294 powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX);295 296 platform_set_drvdata(pdev, powernv_led_common);297 298 return powernv_led_classdev(pdev, led_node, powernv_led_common);299}300 301/* Platform driver remove */302static void powernv_led_remove(struct platform_device *pdev)303{304 struct powernv_led_common *powernv_led_common;305 306 /* Disable LED operation */307 powernv_led_common = platform_get_drvdata(pdev);308 powernv_led_common->led_disabled = true;309 310 /* Destroy lock */311 mutex_destroy(&powernv_led_common->lock);312 313 dev_info(&pdev->dev, "PowerNV led module unregistered\n");314}315 316/* Platform driver property match */317static const struct of_device_id powernv_led_match[] = {318 {319 .compatible = "ibm,opal-v3-led",320 },321 {},322};323MODULE_DEVICE_TABLE(of, powernv_led_match);324 325static struct platform_driver powernv_led_driver = {326 .probe = powernv_led_probe,327 .remove_new = powernv_led_remove,328 .driver = {329 .name = "powernv-led-driver",330 .of_match_table = powernv_led_match,331 },332};333 334module_platform_driver(powernv_led_driver);335 336MODULE_LICENSE("GPL v2");337MODULE_DESCRIPTION("PowerNV LED driver");338MODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>");339