brintos

brintos / linux-shallow public Read only

0
0
Text · 6.5 KiB · f3010c4 Raw
285 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/*3 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED4 *5 * Copyright (C) 2010 LaCie6 *7 * Author: Simon Guinot <sguinot@lacie.com>8 *9 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>10 */11 12#include <linux/kernel.h>13#include <linux/platform_device.h>14#include <linux/slab.h>15#include <linux/gpio/consumer.h>16#include <linux/leds.h>17#include <linux/module.h>18#include <linux/of.h>19#include "leds.h"20 21enum ns2_led_modes {22	NS_V2_LED_OFF,23	NS_V2_LED_ON,24	NS_V2_LED_SATA,25};26 27/*28 * If the size of this structure or types of its members is changed,29 * the filling of array modval in function ns2_led_register must be changed30 * accordingly.31 */32struct ns2_led_modval {33	u32			mode;34	u32			cmd_level;35	u32			slow_level;36} __packed;37 38/*39 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED40 * modes are available: off, on and SATA activity blinking. The LED modes are41 * controlled through two GPIOs (command and slow): each combination of values42 * for the command/slow GPIOs corresponds to a LED mode.43 */44 45struct ns2_led {46	struct led_classdev	cdev;47	struct gpio_desc	*cmd;48	struct gpio_desc	*slow;49	bool			can_sleep;50	unsigned char		sata; /* True when SATA mode active. */51	rwlock_t		rw_lock; /* Lock GPIOs. */52	int			num_modes;53	struct ns2_led_modval	*modval;54};55 56static int ns2_led_get_mode(struct ns2_led *led, enum ns2_led_modes *mode)57{58	int i;59	int cmd_level;60	int slow_level;61 62	cmd_level = gpiod_get_value_cansleep(led->cmd);63	slow_level = gpiod_get_value_cansleep(led->slow);64 65	for (i = 0; i < led->num_modes; i++) {66		if (cmd_level == led->modval[i].cmd_level &&67		    slow_level == led->modval[i].slow_level) {68			*mode = led->modval[i].mode;69			return 0;70		}71	}72 73	return -EINVAL;74}75 76static void ns2_led_set_mode(struct ns2_led *led, enum ns2_led_modes mode)77{78	int i;79	unsigned long flags;80 81	for (i = 0; i < led->num_modes; i++)82		if (mode == led->modval[i].mode)83			break;84 85	if (i == led->num_modes)86		return;87 88	write_lock_irqsave(&led->rw_lock, flags);89 90	if (!led->can_sleep) {91		gpiod_set_value(led->cmd, led->modval[i].cmd_level);92		gpiod_set_value(led->slow, led->modval[i].slow_level);93		goto exit_unlock;94	}95 96	gpiod_set_value_cansleep(led->cmd, led->modval[i].cmd_level);97	gpiod_set_value_cansleep(led->slow, led->modval[i].slow_level);98 99exit_unlock:100	write_unlock_irqrestore(&led->rw_lock, flags);101}102 103static void ns2_led_set(struct led_classdev *led_cdev,104			enum led_brightness value)105{106	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);107	enum ns2_led_modes mode;108 109	if (value == LED_OFF)110		mode = NS_V2_LED_OFF;111	else if (led->sata)112		mode = NS_V2_LED_SATA;113	else114		mode = NS_V2_LED_ON;115 116	ns2_led_set_mode(led, mode);117}118 119static int ns2_led_set_blocking(struct led_classdev *led_cdev,120			enum led_brightness value)121{122	ns2_led_set(led_cdev, value);123	return 0;124}125 126static ssize_t ns2_led_sata_store(struct device *dev,127				  struct device_attribute *attr,128				  const char *buff, size_t count)129{130	struct led_classdev *led_cdev = dev_get_drvdata(dev);131	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);132	int ret;133	unsigned long enable;134 135	ret = kstrtoul(buff, 10, &enable);136	if (ret < 0)137		return ret;138 139	enable = !!enable;140 141	if (led->sata == enable)142		goto exit;143 144	led->sata = enable;145 146	if (!led_get_brightness(led_cdev))147		goto exit;148 149	if (enable)150		ns2_led_set_mode(led, NS_V2_LED_SATA);151	else152		ns2_led_set_mode(led, NS_V2_LED_ON);153 154exit:155	return count;156}157 158static ssize_t ns2_led_sata_show(struct device *dev,159				 struct device_attribute *attr, char *buf)160{161	struct led_classdev *led_cdev = dev_get_drvdata(dev);162	struct ns2_led *led = container_of(led_cdev, struct ns2_led, cdev);163 164	return sprintf(buf, "%d\n", led->sata);165}166 167static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);168 169static struct attribute *ns2_led_attrs[] = {170	&dev_attr_sata.attr,171	NULL172};173ATTRIBUTE_GROUPS(ns2_led);174 175static int ns2_led_register(struct device *dev, struct fwnode_handle *node,176			    struct ns2_led *led)177{178	struct led_init_data init_data = {};179	struct ns2_led_modval *modval;180	enum ns2_led_modes mode;181	int nmodes, ret;182 183	led->cmd = devm_fwnode_gpiod_get_index(dev, node, "cmd", 0, GPIOD_ASIS,184					       fwnode_get_name(node));185	if (IS_ERR(led->cmd))186		return PTR_ERR(led->cmd);187 188	led->slow = devm_fwnode_gpiod_get_index(dev, node, "slow", 0,189						GPIOD_ASIS,190						fwnode_get_name(node));191	if (IS_ERR(led->slow))192		return PTR_ERR(led->slow);193 194	ret = fwnode_property_count_u32(node, "modes-map");195	if (ret < 0 || ret % 3) {196		dev_err(dev, "Missing or malformed modes-map for %pfw\n", node);197		return -EINVAL;198	}199 200	nmodes = ret / 3;201	modval = devm_kcalloc(dev, nmodes, sizeof(*modval), GFP_KERNEL);202	if (!modval)203		return -ENOMEM;204 205	fwnode_property_read_u32_array(node, "modes-map", (void *)modval,206				       nmodes * 3);207 208	rwlock_init(&led->rw_lock);209 210	led->cdev.blink_set = NULL;211	led->cdev.flags |= LED_CORE_SUSPENDRESUME;212	led->cdev.groups = ns2_led_groups;213	led->can_sleep = gpiod_cansleep(led->cmd) || gpiod_cansleep(led->slow);214	if (led->can_sleep)215		led->cdev.brightness_set_blocking = ns2_led_set_blocking;216	else217		led->cdev.brightness_set = ns2_led_set;218	led->num_modes = nmodes;219	led->modval = modval;220 221	ret = ns2_led_get_mode(led, &mode);222	if (ret < 0)223		return ret;224 225	/* Set LED initial state. */226	led->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;227	led->cdev.brightness = (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;228 229	init_data.fwnode = node;230 231	ret = devm_led_classdev_register_ext(dev, &led->cdev, &init_data);232	if (ret)233		dev_err(dev, "Failed to register LED for node %pfw\n", node);234 235	return ret;236}237 238static int ns2_led_probe(struct platform_device *pdev)239{240	struct device *dev = &pdev->dev;241	struct fwnode_handle *child;242	struct ns2_led *leds;243	int count;244	int ret;245 246	count = device_get_child_node_count(dev);247	if (!count)248		return -ENODEV;249 250	leds = devm_kcalloc(dev, count, sizeof(*leds), GFP_KERNEL);251	if (!leds)252		return -ENOMEM;253 254	device_for_each_child_node(dev, child) {255		ret = ns2_led_register(dev, child, leds++);256		if (ret) {257			fwnode_handle_put(child);258			return ret;259		}260	}261 262	return 0;263}264 265static const struct of_device_id of_ns2_leds_match[] = {266	{ .compatible = "lacie,ns2-leds", },267	{},268};269MODULE_DEVICE_TABLE(of, of_ns2_leds_match);270 271static struct platform_driver ns2_led_driver = {272	.probe		= ns2_led_probe,273	.driver		= {274		.name		= "leds-ns2",275		.of_match_table	= of_ns2_leds_match,276	},277};278 279module_platform_driver(ns2_led_driver);280 281MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");282MODULE_DESCRIPTION("Network Space v2 LED driver");283MODULE_LICENSE("GPL");284MODULE_ALIAS("platform:leds-ns2");285