leds-ns2.c 9.3 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 30
/*
 * 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/init.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/leds.h>
31
#include <linux/module.h>
32
#include <linux/platform_data/leds-kirkwood-ns2.h>
33
#include <linux/of_gpio.h>
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85

/*
 * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in
 * relation with the SATA activity. This capability is exposed through the
 * "sata" sysfs attribute.
 *
 * The following array detail the different LED registers and the combination
 * of their possible values:
 *
 *  cmd_led   |  slow_led  | /SATA active | LED state
 *            |            |              |
 *     1      |     0      |      x       |  off
 *     -      |     1      |      x       |  on
 *     0      |     0      |      1       |  on
 *     0      |     0      |      0       |  blink (rate 300ms)
 */

enum ns2_led_modes {
	NS_V2_LED_OFF,
	NS_V2_LED_ON,
	NS_V2_LED_SATA,
};

struct ns2_led_mode_value {
	enum ns2_led_modes	mode;
	int			cmd_level;
	int			slow_level;
};

static struct ns2_led_mode_value ns2_led_modval[] = {
	{ NS_V2_LED_OFF	, 1, 0 },
	{ NS_V2_LED_ON	, 0, 1 },
	{ NS_V2_LED_ON	, 1, 1 },
	{ NS_V2_LED_SATA, 0, 0 },
};

struct ns2_led_data {
	struct led_classdev	cdev;
	unsigned		cmd;
	unsigned		slow;
	unsigned char		sata; /* True when SATA mode active. */
	rwlock_t		rw_lock; /* Lock GPIOs. */
};

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;

S
Simon Guinot 已提交
86
	read_lock_irq(&led_dat->rw_lock);
87 88 89 90 91 92 93 94 95 96 97 98 99

	cmd_level = gpio_get_value(led_dat->cmd);
	slow_level = gpio_get_value(led_dat->slow);

	for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
		if (cmd_level == ns2_led_modval[i].cmd_level &&
		    slow_level == ns2_led_modval[i].slow_level) {
			*mode = ns2_led_modval[i].mode;
			ret = 0;
			break;
		}
	}

S
Simon Guinot 已提交
100
	read_unlock_irq(&led_dat->rw_lock);
101 102 103 104 105 106 107 108

	return ret;
}

static void ns2_led_set_mode(struct ns2_led_data *led_dat,
			     enum ns2_led_modes mode)
{
	int i;
S
Simon Guinot 已提交
109
	unsigned long flags;
110

S
Simon Guinot 已提交
111
	write_lock_irqsave(&led_dat->rw_lock, flags);
112 113 114 115 116 117 118 119 120 121

	for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) {
		if (mode == ns2_led_modval[i].mode) {
			gpio_set_value(led_dat->cmd,
				       ns2_led_modval[i].cmd_level);
			gpio_set_value(led_dat->slow,
				       ns2_led_modval[i].slow_level);
		}
	}

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

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)
{
146 147 148
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct ns2_led_data *led_dat =
		container_of(led_cdev, struct ns2_led_data, cdev);
149 150 151 152
	int ret;
	unsigned long enable;
	enum ns2_led_modes mode;

153
	ret = kstrtoul(buff, 10, &enable);
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
	if (ret < 0)
		return ret;

	enable = !!enable;

	if (led_dat->sata == enable)
		return count;

	ret = ns2_led_get_mode(led_dat, &mode);
	if (ret < 0)
		return ret;

	if (enable && mode == NS_V2_LED_ON)
		ns2_led_set_mode(led_dat, NS_V2_LED_SATA);
	if (!enable && mode == NS_V2_LED_SATA)
		ns2_led_set_mode(led_dat, NS_V2_LED_ON);

	led_dat->sata = enable;

	return count;
}

static ssize_t ns2_led_sata_show(struct device *dev,
				 struct device_attribute *attr, char *buf)
{
179 180 181
	struct led_classdev *led_cdev = dev_get_drvdata(dev);
	struct ns2_led_data *led_dat =
		container_of(led_cdev, struct ns2_led_data, cdev);
182 183 184 185 186 187

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

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

B
Bill Pemberton 已提交
188
static int
189 190 191 192 193 194
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;

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

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

	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;
	led_dat->cmd = template->cmd;
	led_dat->slow = template->slow;

	ret = ns2_led_get_mode(led_dat, &mode);
	if (ret < 0)
227
		return ret;
228 229 230 231 232 233 234 235

	/* 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;

	ret = led_classdev_register(&pdev->dev, &led_dat->cdev);
	if (ret < 0)
236
		return ret;
237 238 239 240 241 242 243 244 245 246 247 248

	ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata);
	if (ret < 0)
		goto err_free_cdev;

	return 0;

err_free_cdev:
	led_classdev_unregister(&led_dat->cdev);
	return ret;
}

249
static void delete_ns2_led(struct ns2_led_data *led_dat)
250 251 252 253 254
{
	device_remove_file(led_dat->cdev.dev, &dev_attr_sata);
	led_classdev_unregister(&led_dat->cdev);
}

255 256 257 258
#ifdef CONFIG_OF_GPIO
/*
 * Translate OpenFirmware node properties into platform_data.
 */
259
static int
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310
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;
	struct ns2_led *leds;
	int num_leds = 0;
	int i = 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;

	for_each_child_of_node(np, child) {
		const char *string;
		int ret;

		ret = of_get_named_gpio(child, "cmd-gpio", 0);
		if (ret < 0)
			return ret;
		leds[i].cmd = ret;
		ret = of_get_named_gpio(child, "slow-gpio", 0);
		if (ret < 0)
			return ret;
		leds[i].slow = ret;
		ret = of_property_read_string(child, "label", &string);
		leds[i].name = (ret == 0) ? string : child->name;
		ret = of_property_read_string(child, "linux,default-trigger",
					      &string);
		if (ret == 0)
			leds[i].default_trigger = string;

		i++;
	}

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

	return 0;
}

static const struct of_device_id of_ns2_leds_match[] = {
	{ .compatible = "lacie,ns2-leds", },
	{},
};
#endif /* CONFIG_OF_GPIO */

311 312 313 314 315 316 317 318 319 320 321
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 已提交
322
static int ns2_led_probe(struct platform_device *pdev)
323 324
{
	struct ns2_led_platform_data *pdata = pdev->dev.platform_data;
325
	struct ns2_led_priv *priv;
326 327 328
	int i;
	int ret;

329 330 331 332 333 334 335 336 337 338 339 340 341
#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
342 343
	if (!pdata)
		return -EINVAL;
344
#endif /* CONFIG_OF_GPIO */
345

346 347 348
	priv = devm_kzalloc(&pdev->dev,
			    sizeof_ns2_led_priv(pdata->num_leds), GFP_KERNEL);
	if (!priv)
349
		return -ENOMEM;
350
	priv->num_leds = pdata->num_leds;
351

352 353 354
	for (i = 0; i < priv->num_leds; i++) {
		ret = create_ns2_led(pdev, &priv->leds_data[i],
				     &pdata->leds[i]);
355 356
		if (ret < 0) {
			for (i = i - 1; i >= 0; i--)
357
				delete_ns2_led(&priv->leds_data[i]);
358 359
			return ret;
		}
360 361
	}

362
	platform_set_drvdata(pdev, priv);
363 364 365 366

	return 0;
}

B
Bill Pemberton 已提交
367
static int ns2_led_remove(struct platform_device *pdev)
368 369
{
	int i;
370
	struct ns2_led_priv *priv;
371

372
	priv = platform_get_drvdata(pdev);
373

374 375
	for (i = 0; i < priv->num_leds; i++)
		delete_ns2_led(&priv->leds_data[i]);
376 377 378 379 380 381

	return 0;
}

static struct platform_driver ns2_led_driver = {
	.probe		= ns2_led_probe,
B
Bill Pemberton 已提交
382
	.remove		= ns2_led_remove,
383
	.driver		= {
384 385 386
		.name		= "leds-ns2",
		.owner		= THIS_MODULE,
		.of_match_table	= of_match_ptr(of_ns2_leds_match),
387 388 389
	},
};

390
module_platform_driver(ns2_led_driver);
391 392 393 394

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