leds-ns2.c 10.1 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED
 *
 * Copyright (C) 2010 LaCie
 *
 * Author: Simon Guinot <sguinot@lacie.com>
 *
 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/kernel.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/leds.h>
30
#include <linux/module.h>
31
#include <linux/platform_data/leds-kirkwood-ns2.h>
S
Sachin Kamat 已提交
32
#include <linux/of.h>
33
#include <linux/of_gpio.h>
34
#include "leds.h"
35 36

/*
37 38 39 40
 * The Network Space v2 dual-GPIO LED is wired to a CPLD. Three different LED
 * modes are available: off, on and SATA activity blinking. The LED modes are
 * controlled through two GPIOs (command and slow): each combination of values
 * for the command/slow GPIOs corresponds to a LED mode.
41 42 43 44 45 46
 */

struct ns2_led_data {
	struct led_classdev	cdev;
	unsigned		cmd;
	unsigned		slow;
47 48
	bool			can_sleep;
	int			mode_index;
49 50
	unsigned char		sata; /* True when SATA mode active. */
	rwlock_t		rw_lock; /* Lock GPIOs. */
51
	struct work_struct	work;
52 53
	int			num_modes;
	struct ns2_led_modval	*modval;
54 55
};

56 57 58 59 60 61 62 63 64 65
static void ns2_led_work(struct work_struct *work)
{
	struct ns2_led_data *led_dat =
		container_of(work, struct ns2_led_data, work);
	int i = led_dat->mode_index;

	gpio_set_value_cansleep(led_dat->cmd, led_dat->modval[i].cmd_level);
	gpio_set_value_cansleep(led_dat->slow, led_dat->modval[i].slow_level);
}

66 67 68 69 70 71 72 73
static int ns2_led_get_mode(struct ns2_led_data *led_dat,
			    enum ns2_led_modes *mode)
{
	int i;
	int ret = -EINVAL;
	int cmd_level;
	int slow_level;

74 75
	cmd_level = gpio_get_value_cansleep(led_dat->cmd);
	slow_level = gpio_get_value_cansleep(led_dat->slow);
76

77 78 79 80
	for (i = 0; i < led_dat->num_modes; i++) {
		if (cmd_level == led_dat->modval[i].cmd_level &&
		    slow_level == led_dat->modval[i].slow_level) {
			*mode = led_dat->modval[i].mode;
81 82 83 84 85 86 87 88 89 90 91 92
			ret = 0;
			break;
		}
	}

	return ret;
}

static void ns2_led_set_mode(struct ns2_led_data *led_dat,
			     enum ns2_led_modes mode)
{
	int i;
93
	bool found = false;
S
Simon Guinot 已提交
94
	unsigned long flags;
95

96
	for (i = 0; i < led_dat->num_modes; i++)
97
		if (mode == led_dat->modval[i].mode) {
98 99
			found = true;
			break;
100
		}
101 102 103 104 105 106 107 108 109 110 111 112

	if (!found)
		return;

	write_lock_irqsave(&led_dat->rw_lock, flags);

	if (!led_dat->can_sleep) {
		gpio_set_value(led_dat->cmd,
			       led_dat->modval[i].cmd_level);
		gpio_set_value(led_dat->slow,
			       led_dat->modval[i].slow_level);
		goto exit_unlock;
113 114
	}

115 116 117 118
	led_dat->mode_index = i;
	schedule_work(&led_dat->work);

exit_unlock:
S
Simon Guinot 已提交
119
	write_unlock_irqrestore(&led_dat->rw_lock, flags);
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
}

static void ns2_led_set(struct led_classdev *led_cdev,
			enum led_brightness value)
{
	struct ns2_led_data *led_dat =
		container_of(led_cdev, struct ns2_led_data, cdev);
	enum ns2_led_modes mode;

	if (value == LED_OFF)
		mode = NS_V2_LED_OFF;
	else if (led_dat->sata)
		mode = NS_V2_LED_SATA;
	else
		mode = NS_V2_LED_ON;

	ns2_led_set_mode(led_dat, mode);
}

static ssize_t ns2_led_sata_store(struct device *dev,
				  struct device_attribute *attr,
				  const char *buff, size_t count)
{
143 144 145
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct ns2_led_data *led_dat =
		container_of(led_cdev, struct ns2_led_data, cdev);
146 147 148
	int ret;
	unsigned long enable;

149
	ret = kstrtoul(buff, 10, &enable);
150 151 152 153 154 155
	if (ret < 0)
		return ret;

	enable = !!enable;

	if (led_dat->sata == enable)
156
		goto exit;
157

158 159 160 161
	led_dat->sata = enable;

	if (!led_get_brightness(led_cdev))
		goto exit;
162

163
	if (enable)
164
		ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
165
	else
166 167
		ns2_led_set_mode(led_dat, NS_V2_LED_ON);

168
exit:
169 170 171 172 173 174
	return count;
}

static ssize_t ns2_led_sata_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
175 176 177
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct ns2_led_data *led_dat =
		container_of(led_cdev, struct ns2_led_data, cdev);
178 179 180 181 182 183

	return sprintf(buf, "%d\n", led_dat->sata);
}

static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store);

184 185 186 187 188 189
static struct attribute *ns2_led_attrs[] = {
	&dev_attr_sata.attr,
	NULL
};
ATTRIBUTE_GROUPS(ns2_led);

B
Bill Pemberton 已提交
190
static int
191 192 193 194 195 196
create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat,
	       const struct ns2_led *template)
{
	int ret;
	enum ns2_led_modes mode;

197
	ret = devm_gpio_request_one(&pdev->dev, template->cmd,
198
			gpio_get_value_cansleep(template->cmd) ?
199
			GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
J
Jingoo Han 已提交
200
			template->name);
201 202 203
	if (ret) {
		dev_err(&pdev->dev, "%s: failed to setup command GPIO\n",
			template->name);
J
Jingoo Han 已提交
204
		return ret;
205 206
	}

207
	ret = devm_gpio_request_one(&pdev->dev, template->slow,
208
			gpio_get_value_cansleep(template->slow) ?
209
			GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW,
J
Jingoo Han 已提交
210
			template->name);
211 212 213
	if (ret) {
		dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n",
			template->name);
214
		return ret;
215 216 217 218 219 220 221 222 223
	}

	rwlock_init(&led_dat->rw_lock);

	led_dat->cdev.name = template->name;
	led_dat->cdev.default_trigger = template->default_trigger;
	led_dat->cdev.blink_set = NULL;
	led_dat->cdev.brightness_set = ns2_led_set;
	led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME;
224
	led_dat->cdev.groups = ns2_led_groups;
225 226
	led_dat->cmd = template->cmd;
	led_dat->slow = template->slow;
227 228
	led_dat->can_sleep = gpio_cansleep(led_dat->cmd) |
				gpio_cansleep(led_dat->slow);
229 230
	led_dat->modval = template->modval;
	led_dat->num_modes = template->num_modes;
231 232 233

	ret = ns2_led_get_mode(led_dat, &mode);
	if (ret < 0)
234
		return ret;
235 236 237 238 239 240

	/* Set LED initial state. */
	led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0;
	led_dat->cdev.brightness =
		(mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL;

241 242
	INIT_WORK(&led_dat->work, ns2_led_work);

243 244
	ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
	if (ret < 0)
245
		return ret;
246 247 248 249

	return 0;
}

250
static void delete_ns2_led(struct ns2_led_data *led_dat)
251 252
{
	led_classdev_unregister(&led_dat->cdev);
253
	cancel_work_sync(&led_dat->work);
254 255
}

256 257 258 259
#ifdef CONFIG_OF_GPIO
/*
 * Translate OpenFirmware node properties into platform_data.
 */
260
static int
261 262 263 264
ns2_leds_get_of_pdata(struct device *dev, struct ns2_led_platform_data *pdata)
{
	struct device_node *np = dev->of_node;
	struct device_node *child;
265
	struct ns2_led *led, *leds;
266 267 268 269 270 271 272 273 274 275 276
	int num_leds = 0;

	num_leds = of_get_child_count(np);
	if (!num_leds)
		return -ENODEV;

	leds = devm_kzalloc(dev, num_leds * sizeof(struct ns2_led),
			    GFP_KERNEL);
	if (!leds)
		return -ENOMEM;

277
	led = leds;
278 279
	for_each_child_of_node(np, child) {
		const char *string;
280 281
		int ret, i, num_modes;
		struct ns2_led_modval *modval;
282 283 284 285

		ret = of_get_named_gpio(child, "cmd-gpio", 0);
		if (ret < 0)
			return ret;
286
		led->cmd = ret;
287 288 289
		ret = of_get_named_gpio(child, "slow-gpio", 0);
		if (ret < 0)
			return ret;
290
		led->slow = ret;
291
		ret = of_property_read_string(child, "label", &string);
292
		led->name = (ret == 0) ? string : child->name;
293 294 295
		ret = of_property_read_string(child, "linux,default-trigger",
					      &string);
		if (ret == 0)
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
			led->default_trigger = string;

		ret = of_property_count_u32_elems(child, "modes-map");
		if (ret < 0 || ret % 3) {
			dev_err(dev,
				"Missing or malformed modes-map property\n");
			return -EINVAL;
		}

		num_modes = ret / 3;
		modval = devm_kzalloc(dev,
				      num_modes * sizeof(struct ns2_led_modval),
				      GFP_KERNEL);
		if (!modval)
			return -ENOMEM;

		for (i = 0; i < num_modes; i++) {
			of_property_read_u32_index(child,
						"modes-map", 3 * i,
						(u32 *) &modval[i].mode);
			of_property_read_u32_index(child,
						"modes-map", 3 * i + 1,
						(u32 *) &modval[i].cmd_level);
			of_property_read_u32_index(child,
						"modes-map", 3 * i + 2,
						(u32 *) &modval[i].slow_level);
		}

		led->num_modes = num_modes;
		led->modval = modval;
326

327
		led++;
328 329 330 331 332 333 334 335 336 337 338 339
	}

	pdata->leds = leds;
	pdata->num_leds = num_leds;

	return 0;
}

static const struct of_device_id of_ns2_leds_match[] = {
	{ .compatible = "lacie,ns2-leds", },
	{},
};
340
MODULE_DEVICE_TABLE(of, of_ns2_leds_match);
341 342
#endif /* CONFIG_OF_GPIO */

343 344 345 346 347 348 349 350 351 352 353
struct ns2_led_priv {
	int num_leds;
	struct ns2_led_data leds_data[];
};

static inline int sizeof_ns2_led_priv(int num_leds)
{
	return sizeof(struct ns2_led_priv) +
		      (sizeof(struct ns2_led_data) * num_leds);
}

B
Bill Pemberton 已提交
354
static int ns2_led_probe(struct platform_device *pdev)
355
{
J
Jingoo Han 已提交
356
	struct ns2_led_platform_data *pdata = dev_get_platdata(&pdev->dev);
357
	struct ns2_led_priv *priv;
358 359 360
	int i;
	int ret;

361 362 363 364 365 366 367 368 369 370 371 372 373
#ifdef CONFIG_OF_GPIO
	if (!pdata) {
		pdata = devm_kzalloc(&pdev->dev,
				     sizeof(struct ns2_led_platform_data),
				     GFP_KERNEL);
		if (!pdata)
			return -ENOMEM;

		ret = ns2_leds_get_of_pdata(&pdev->dev, pdata);
		if (ret)
			return ret;
	}
#else
374 375
	if (!pdata)
		return -EINVAL;
376
#endif /* CONFIG_OF_GPIO */
377

378 379 380
	priv = devm_kzalloc(&pdev->dev,
			    sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
	if (!priv)
381
		return -ENOMEM;
382
	priv->num_leds = pdata->num_leds;
383

384 385 386
	for (i = 0; i < priv->num_leds; i++) {
		ret = create_ns2_led(pdev, &priv->leds_data[i],
				     &pdata->leds[i]);
387 388
		if (ret < 0) {
			for (i = i - 1; i >= 0; i--)
389
				delete_ns2_led(&priv->leds_data[i]);
390 391
			return ret;
		}
392 393
	}

394
	platform_set_drvdata(pdev, priv);
395 396 397 398

	return 0;
}

B
Bill Pemberton 已提交
399
static int ns2_led_remove(struct platform_device *pdev)
400 401
{
	int i;
402
	struct ns2_led_priv *priv;
403

404
	priv = platform_get_drvdata(pdev);
405

406 407
	for (i = 0; i < priv->num_leds; i++)
		delete_ns2_led(&priv->leds_data[i]);
408 409 410 411 412 413

	return 0;
}

static struct platform_driver ns2_led_driver = {
	.probe		= ns2_led_probe,
B
Bill Pemberton 已提交
414
	.remove		= ns2_led_remove,
415
	.driver		= {
416 417
		.name		= "leds-ns2",
		.of_match_table	= of_match_ptr(of_ns2_leds_match),
418 419 420
	},
};

421
module_platform_driver(ns2_led_driver);
422 423 424 425

MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>");
MODULE_DESCRIPTION("Network Space v2 LED driver");
MODULE_LICENSE("GPL");
426
MODULE_ALIAS("platform:leds-ns2");