162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * PowerNV LED Driver 462306a36Sopenharmony_ci * 562306a36Sopenharmony_ci * Copyright IBM Corp. 2015 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * Author: Vasant Hegde <hegdevasant@linux.vnet.ibm.com> 862306a36Sopenharmony_ci * Author: Anshuman Khandual <khandual@linux.vnet.ibm.com> 962306a36Sopenharmony_ci */ 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include <linux/leds.h> 1262306a36Sopenharmony_ci#include <linux/module.h> 1362306a36Sopenharmony_ci#include <linux/of.h> 1462306a36Sopenharmony_ci#include <linux/platform_device.h> 1562306a36Sopenharmony_ci#include <linux/slab.h> 1662306a36Sopenharmony_ci#include <linux/types.h> 1762306a36Sopenharmony_ci 1862306a36Sopenharmony_ci#include <asm/opal.h> 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci/* Map LED type to description. */ 2162306a36Sopenharmony_cistruct led_type_map { 2262306a36Sopenharmony_ci const int type; 2362306a36Sopenharmony_ci const char *desc; 2462306a36Sopenharmony_ci}; 2562306a36Sopenharmony_cistatic const struct led_type_map led_type_map[] = { 2662306a36Sopenharmony_ci {OPAL_SLOT_LED_TYPE_ID, "identify"}, 2762306a36Sopenharmony_ci {OPAL_SLOT_LED_TYPE_FAULT, "fault"}, 2862306a36Sopenharmony_ci {OPAL_SLOT_LED_TYPE_ATTN, "attention"}, 2962306a36Sopenharmony_ci {-1, NULL}, 3062306a36Sopenharmony_ci}; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistruct powernv_led_common { 3362306a36Sopenharmony_ci /* 3462306a36Sopenharmony_ci * By default unload path resets all the LEDs. But on PowerNV 3562306a36Sopenharmony_ci * platform we want to retain LED state across reboot as these 3662306a36Sopenharmony_ci * are controlled by firmware. Also service processor can modify 3762306a36Sopenharmony_ci * the LEDs independent of OS. Hence avoid resetting LEDs in 3862306a36Sopenharmony_ci * unload path. 3962306a36Sopenharmony_ci */ 4062306a36Sopenharmony_ci bool led_disabled; 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci /* Max supported LED type */ 4362306a36Sopenharmony_ci __be64 max_led_type; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci /* glabal lock */ 4662306a36Sopenharmony_ci struct mutex lock; 4762306a36Sopenharmony_ci}; 4862306a36Sopenharmony_ci 4962306a36Sopenharmony_ci/* PowerNV LED data */ 5062306a36Sopenharmony_cistruct powernv_led_data { 5162306a36Sopenharmony_ci struct led_classdev cdev; 5262306a36Sopenharmony_ci char *loc_code; /* LED location code */ 5362306a36Sopenharmony_ci int led_type; /* OPAL_SLOT_LED_TYPE_* */ 5462306a36Sopenharmony_ci 5562306a36Sopenharmony_ci struct powernv_led_common *common; 5662306a36Sopenharmony_ci}; 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/* Returns OPAL_SLOT_LED_TYPE_* for given led type string */ 6062306a36Sopenharmony_cistatic int powernv_get_led_type(const char *led_type_desc) 6162306a36Sopenharmony_ci{ 6262306a36Sopenharmony_ci int i; 6362306a36Sopenharmony_ci 6462306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(led_type_map); i++) 6562306a36Sopenharmony_ci if (!strcmp(led_type_map[i].desc, led_type_desc)) 6662306a36Sopenharmony_ci return led_type_map[i].type; 6762306a36Sopenharmony_ci 6862306a36Sopenharmony_ci return -1; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci/* 7262306a36Sopenharmony_ci * This commits the state change of the requested LED through an OPAL call. 7362306a36Sopenharmony_ci * This function is called from work queue task context when ever it gets 7462306a36Sopenharmony_ci * scheduled. This function can sleep at opal_async_wait_response call. 7562306a36Sopenharmony_ci */ 7662306a36Sopenharmony_cistatic int powernv_led_set(struct powernv_led_data *powernv_led, 7762306a36Sopenharmony_ci enum led_brightness value) 7862306a36Sopenharmony_ci{ 7962306a36Sopenharmony_ci int rc, token; 8062306a36Sopenharmony_ci u64 led_mask, led_value = 0; 8162306a36Sopenharmony_ci __be64 max_type; 8262306a36Sopenharmony_ci struct opal_msg msg; 8362306a36Sopenharmony_ci struct device *dev = powernv_led->cdev.dev; 8462306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common = powernv_led->common; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci /* Prepare for the OPAL call */ 8762306a36Sopenharmony_ci max_type = powernv_led_common->max_led_type; 8862306a36Sopenharmony_ci led_mask = OPAL_SLOT_LED_STATE_ON << powernv_led->led_type; 8962306a36Sopenharmony_ci if (value) 9062306a36Sopenharmony_ci led_value = led_mask; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_ci /* OPAL async call */ 9362306a36Sopenharmony_ci token = opal_async_get_token_interruptible(); 9462306a36Sopenharmony_ci if (token < 0) { 9562306a36Sopenharmony_ci if (token != -ERESTARTSYS) 9662306a36Sopenharmony_ci dev_err(dev, "%s: Couldn't get OPAL async token\n", 9762306a36Sopenharmony_ci __func__); 9862306a36Sopenharmony_ci return token; 9962306a36Sopenharmony_ci } 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci rc = opal_leds_set_ind(token, powernv_led->loc_code, 10262306a36Sopenharmony_ci led_mask, led_value, &max_type); 10362306a36Sopenharmony_ci if (rc != OPAL_ASYNC_COMPLETION) { 10462306a36Sopenharmony_ci dev_err(dev, "%s: OPAL set LED call failed for %s [rc=%d]\n", 10562306a36Sopenharmony_ci __func__, powernv_led->loc_code, rc); 10662306a36Sopenharmony_ci goto out_token; 10762306a36Sopenharmony_ci } 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci rc = opal_async_wait_response(token, &msg); 11062306a36Sopenharmony_ci if (rc) { 11162306a36Sopenharmony_ci dev_err(dev, 11262306a36Sopenharmony_ci "%s: Failed to wait for the async response [rc=%d]\n", 11362306a36Sopenharmony_ci __func__, rc); 11462306a36Sopenharmony_ci goto out_token; 11562306a36Sopenharmony_ci } 11662306a36Sopenharmony_ci 11762306a36Sopenharmony_ci rc = opal_get_async_rc(msg); 11862306a36Sopenharmony_ci if (rc != OPAL_SUCCESS) 11962306a36Sopenharmony_ci dev_err(dev, "%s : OAPL async call returned failed [rc=%d]\n", 12062306a36Sopenharmony_ci __func__, rc); 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ciout_token: 12362306a36Sopenharmony_ci opal_async_release_token(token); 12462306a36Sopenharmony_ci return rc; 12562306a36Sopenharmony_ci} 12662306a36Sopenharmony_ci 12762306a36Sopenharmony_ci/* 12862306a36Sopenharmony_ci * This function fetches the LED state for a given LED type for 12962306a36Sopenharmony_ci * mentioned LED classdev structure. 13062306a36Sopenharmony_ci */ 13162306a36Sopenharmony_cistatic enum led_brightness powernv_led_get(struct powernv_led_data *powernv_led) 13262306a36Sopenharmony_ci{ 13362306a36Sopenharmony_ci int rc; 13462306a36Sopenharmony_ci __be64 mask, value, max_type; 13562306a36Sopenharmony_ci u64 led_mask, led_value; 13662306a36Sopenharmony_ci struct device *dev = powernv_led->cdev.dev; 13762306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common = powernv_led->common; 13862306a36Sopenharmony_ci 13962306a36Sopenharmony_ci /* Fetch all LED status */ 14062306a36Sopenharmony_ci mask = cpu_to_be64(0); 14162306a36Sopenharmony_ci value = cpu_to_be64(0); 14262306a36Sopenharmony_ci max_type = powernv_led_common->max_led_type; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ci rc = opal_leds_get_ind(powernv_led->loc_code, 14562306a36Sopenharmony_ci &mask, &value, &max_type); 14662306a36Sopenharmony_ci if (rc != OPAL_SUCCESS && rc != OPAL_PARTIAL) { 14762306a36Sopenharmony_ci dev_err(dev, "%s: OPAL get led call failed [rc=%d]\n", 14862306a36Sopenharmony_ci __func__, rc); 14962306a36Sopenharmony_ci return LED_OFF; 15062306a36Sopenharmony_ci } 15162306a36Sopenharmony_ci 15262306a36Sopenharmony_ci led_mask = be64_to_cpu(mask); 15362306a36Sopenharmony_ci led_value = be64_to_cpu(value); 15462306a36Sopenharmony_ci 15562306a36Sopenharmony_ci /* LED status available */ 15662306a36Sopenharmony_ci if (!((led_mask >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON)) { 15762306a36Sopenharmony_ci dev_err(dev, "%s: LED status not available for %s\n", 15862306a36Sopenharmony_ci __func__, powernv_led->cdev.name); 15962306a36Sopenharmony_ci return LED_OFF; 16062306a36Sopenharmony_ci } 16162306a36Sopenharmony_ci 16262306a36Sopenharmony_ci /* LED status value */ 16362306a36Sopenharmony_ci if ((led_value >> powernv_led->led_type) & OPAL_SLOT_LED_STATE_ON) 16462306a36Sopenharmony_ci return LED_FULL; 16562306a36Sopenharmony_ci 16662306a36Sopenharmony_ci return LED_OFF; 16762306a36Sopenharmony_ci} 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci/* 17062306a36Sopenharmony_ci * LED classdev 'brightness_get' function. This schedules work 17162306a36Sopenharmony_ci * to update LED state. 17262306a36Sopenharmony_ci */ 17362306a36Sopenharmony_cistatic int powernv_brightness_set(struct led_classdev *led_cdev, 17462306a36Sopenharmony_ci enum led_brightness value) 17562306a36Sopenharmony_ci{ 17662306a36Sopenharmony_ci struct powernv_led_data *powernv_led = 17762306a36Sopenharmony_ci container_of(led_cdev, struct powernv_led_data, cdev); 17862306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common = powernv_led->common; 17962306a36Sopenharmony_ci int rc; 18062306a36Sopenharmony_ci 18162306a36Sopenharmony_ci /* Do not modify LED in unload path */ 18262306a36Sopenharmony_ci if (powernv_led_common->led_disabled) 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci 18562306a36Sopenharmony_ci mutex_lock(&powernv_led_common->lock); 18662306a36Sopenharmony_ci rc = powernv_led_set(powernv_led, value); 18762306a36Sopenharmony_ci mutex_unlock(&powernv_led_common->lock); 18862306a36Sopenharmony_ci 18962306a36Sopenharmony_ci return rc; 19062306a36Sopenharmony_ci} 19162306a36Sopenharmony_ci 19262306a36Sopenharmony_ci/* LED classdev 'brightness_get' function */ 19362306a36Sopenharmony_cistatic enum led_brightness powernv_brightness_get(struct led_classdev *led_cdev) 19462306a36Sopenharmony_ci{ 19562306a36Sopenharmony_ci struct powernv_led_data *powernv_led = 19662306a36Sopenharmony_ci container_of(led_cdev, struct powernv_led_data, cdev); 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci return powernv_led_get(powernv_led); 19962306a36Sopenharmony_ci} 20062306a36Sopenharmony_ci 20162306a36Sopenharmony_ci/* 20262306a36Sopenharmony_ci * This function registers classdev structure for any given type of LED on 20362306a36Sopenharmony_ci * a given child LED device node. 20462306a36Sopenharmony_ci */ 20562306a36Sopenharmony_cistatic int powernv_led_create(struct device *dev, 20662306a36Sopenharmony_ci struct powernv_led_data *powernv_led, 20762306a36Sopenharmony_ci const char *led_type_desc) 20862306a36Sopenharmony_ci{ 20962306a36Sopenharmony_ci int rc; 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* Make sure LED type is supported */ 21262306a36Sopenharmony_ci powernv_led->led_type = powernv_get_led_type(led_type_desc); 21362306a36Sopenharmony_ci if (powernv_led->led_type == -1) { 21462306a36Sopenharmony_ci dev_warn(dev, "%s: No support for led type : %s\n", 21562306a36Sopenharmony_ci __func__, led_type_desc); 21662306a36Sopenharmony_ci return -EINVAL; 21762306a36Sopenharmony_ci } 21862306a36Sopenharmony_ci 21962306a36Sopenharmony_ci /* Create the name for classdev */ 22062306a36Sopenharmony_ci powernv_led->cdev.name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", 22162306a36Sopenharmony_ci powernv_led->loc_code, 22262306a36Sopenharmony_ci led_type_desc); 22362306a36Sopenharmony_ci if (!powernv_led->cdev.name) 22462306a36Sopenharmony_ci return -ENOMEM; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci powernv_led->cdev.brightness_set_blocking = powernv_brightness_set; 22762306a36Sopenharmony_ci powernv_led->cdev.brightness_get = powernv_brightness_get; 22862306a36Sopenharmony_ci powernv_led->cdev.brightness = LED_OFF; 22962306a36Sopenharmony_ci powernv_led->cdev.max_brightness = LED_FULL; 23062306a36Sopenharmony_ci 23162306a36Sopenharmony_ci /* Register the classdev */ 23262306a36Sopenharmony_ci rc = devm_led_classdev_register(dev, &powernv_led->cdev); 23362306a36Sopenharmony_ci if (rc) { 23462306a36Sopenharmony_ci dev_err(dev, "%s: Classdev registration failed for %s\n", 23562306a36Sopenharmony_ci __func__, powernv_led->cdev.name); 23662306a36Sopenharmony_ci } 23762306a36Sopenharmony_ci 23862306a36Sopenharmony_ci return rc; 23962306a36Sopenharmony_ci} 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci/* Go through LED device tree node and register LED classdev structure */ 24262306a36Sopenharmony_cistatic int powernv_led_classdev(struct platform_device *pdev, 24362306a36Sopenharmony_ci struct device_node *led_node, 24462306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common) 24562306a36Sopenharmony_ci{ 24662306a36Sopenharmony_ci const char *cur = NULL; 24762306a36Sopenharmony_ci int rc = -1; 24862306a36Sopenharmony_ci struct property *p; 24962306a36Sopenharmony_ci struct device_node *np; 25062306a36Sopenharmony_ci struct powernv_led_data *powernv_led; 25162306a36Sopenharmony_ci struct device *dev = &pdev->dev; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci for_each_available_child_of_node(led_node, np) { 25462306a36Sopenharmony_ci p = of_find_property(np, "led-types", NULL); 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci while ((cur = of_prop_next_string(p, cur)) != NULL) { 25762306a36Sopenharmony_ci powernv_led = devm_kzalloc(dev, sizeof(*powernv_led), 25862306a36Sopenharmony_ci GFP_KERNEL); 25962306a36Sopenharmony_ci if (!powernv_led) { 26062306a36Sopenharmony_ci of_node_put(np); 26162306a36Sopenharmony_ci return -ENOMEM; 26262306a36Sopenharmony_ci } 26362306a36Sopenharmony_ci 26462306a36Sopenharmony_ci powernv_led->common = powernv_led_common; 26562306a36Sopenharmony_ci powernv_led->loc_code = (char *)np->name; 26662306a36Sopenharmony_ci 26762306a36Sopenharmony_ci rc = powernv_led_create(dev, powernv_led, cur); 26862306a36Sopenharmony_ci if (rc) { 26962306a36Sopenharmony_ci of_node_put(np); 27062306a36Sopenharmony_ci return rc; 27162306a36Sopenharmony_ci } 27262306a36Sopenharmony_ci } /* while end */ 27362306a36Sopenharmony_ci } 27462306a36Sopenharmony_ci 27562306a36Sopenharmony_ci return rc; 27662306a36Sopenharmony_ci} 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci/* Platform driver probe */ 27962306a36Sopenharmony_cistatic int powernv_led_probe(struct platform_device *pdev) 28062306a36Sopenharmony_ci{ 28162306a36Sopenharmony_ci struct device_node *led_node; 28262306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common; 28362306a36Sopenharmony_ci struct device *dev = &pdev->dev; 28462306a36Sopenharmony_ci int rc; 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci led_node = of_find_node_by_path("/ibm,opal/leds"); 28762306a36Sopenharmony_ci if (!led_node) { 28862306a36Sopenharmony_ci dev_err(dev, "%s: LED parent device node not found\n", 28962306a36Sopenharmony_ci __func__); 29062306a36Sopenharmony_ci return -EINVAL; 29162306a36Sopenharmony_ci } 29262306a36Sopenharmony_ci 29362306a36Sopenharmony_ci powernv_led_common = devm_kzalloc(dev, sizeof(*powernv_led_common), 29462306a36Sopenharmony_ci GFP_KERNEL); 29562306a36Sopenharmony_ci if (!powernv_led_common) { 29662306a36Sopenharmony_ci rc = -ENOMEM; 29762306a36Sopenharmony_ci goto out; 29862306a36Sopenharmony_ci } 29962306a36Sopenharmony_ci 30062306a36Sopenharmony_ci mutex_init(&powernv_led_common->lock); 30162306a36Sopenharmony_ci powernv_led_common->max_led_type = cpu_to_be64(OPAL_SLOT_LED_TYPE_MAX); 30262306a36Sopenharmony_ci 30362306a36Sopenharmony_ci platform_set_drvdata(pdev, powernv_led_common); 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci rc = powernv_led_classdev(pdev, led_node, powernv_led_common); 30662306a36Sopenharmony_ciout: 30762306a36Sopenharmony_ci of_node_put(led_node); 30862306a36Sopenharmony_ci return rc; 30962306a36Sopenharmony_ci} 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci/* Platform driver remove */ 31262306a36Sopenharmony_cistatic int powernv_led_remove(struct platform_device *pdev) 31362306a36Sopenharmony_ci{ 31462306a36Sopenharmony_ci struct powernv_led_common *powernv_led_common; 31562306a36Sopenharmony_ci 31662306a36Sopenharmony_ci /* Disable LED operation */ 31762306a36Sopenharmony_ci powernv_led_common = platform_get_drvdata(pdev); 31862306a36Sopenharmony_ci powernv_led_common->led_disabled = true; 31962306a36Sopenharmony_ci 32062306a36Sopenharmony_ci /* Destroy lock */ 32162306a36Sopenharmony_ci mutex_destroy(&powernv_led_common->lock); 32262306a36Sopenharmony_ci 32362306a36Sopenharmony_ci dev_info(&pdev->dev, "PowerNV led module unregistered\n"); 32462306a36Sopenharmony_ci return 0; 32562306a36Sopenharmony_ci} 32662306a36Sopenharmony_ci 32762306a36Sopenharmony_ci/* Platform driver property match */ 32862306a36Sopenharmony_cistatic const struct of_device_id powernv_led_match[] = { 32962306a36Sopenharmony_ci { 33062306a36Sopenharmony_ci .compatible = "ibm,opal-v3-led", 33162306a36Sopenharmony_ci }, 33262306a36Sopenharmony_ci {}, 33362306a36Sopenharmony_ci}; 33462306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, powernv_led_match); 33562306a36Sopenharmony_ci 33662306a36Sopenharmony_cistatic struct platform_driver powernv_led_driver = { 33762306a36Sopenharmony_ci .probe = powernv_led_probe, 33862306a36Sopenharmony_ci .remove = powernv_led_remove, 33962306a36Sopenharmony_ci .driver = { 34062306a36Sopenharmony_ci .name = "powernv-led-driver", 34162306a36Sopenharmony_ci .of_match_table = powernv_led_match, 34262306a36Sopenharmony_ci }, 34362306a36Sopenharmony_ci}; 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_cimodule_platform_driver(powernv_led_driver); 34662306a36Sopenharmony_ci 34762306a36Sopenharmony_ciMODULE_LICENSE("GPL v2"); 34862306a36Sopenharmony_ciMODULE_DESCRIPTION("PowerNV LED driver"); 34962306a36Sopenharmony_ciMODULE_AUTHOR("Vasant Hegde <hegdevasant@linux.vnet.ibm.com>"); 350