brintos

brintos / linux-shallow public Read only

0
0
Text · 8.6 KiB · 49ab8c9 Raw
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