1 /* 2 * leds-ns2.c - Driver for the Network Space v2 (and parents) dual-GPIO LED 3 * 4 * Copyright (C) 2010 LaCie 5 * 6 * Author: Simon Guinot <sguinot@lacie.com> 7 * 8 * Based on leds-gpio.c by Raphael Assenat <raph@8d.com> 9 * 10 * This program is free software; you can redistribute it and/or modify 11 * it under the terms of the GNU General Public License as published by 12 * the Free Software Foundation; either version 2 of the License, or 13 * (at your option) any later version. 14 * 15 * This program is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 * GNU General Public License for more details. 19 * 20 * You should have received a copy of the GNU General Public License 21 * along with this program; if not, write to the Free Software 22 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23 */ 24 25 #include <linux/kernel.h> 26 #include <linux/init.h> 27 #include <linux/platform_device.h> 28 #include <linux/slab.h> 29 #include <linux/gpio.h> 30 #include <linux/leds.h> 31 #include <mach/leds-ns2.h> 32 33 /* 34 * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in 35 * relation with the SATA activity. This capability is exposed through the 36 * "sata" sysfs attribute. 37 * 38 * The following array detail the different LED registers and the combination 39 * of their possible values: 40 * 41 * cmd_led | slow_led | /SATA active | LED state 42 * | | | 43 * 1 | 0 | x | off 44 * - | 1 | x | on 45 * 0 | 0 | 1 | on 46 * 0 | 0 | 0 | blink (rate 300ms) 47 */ 48 49 enum ns2_led_modes { 50 NS_V2_LED_OFF, 51 NS_V2_LED_ON, 52 NS_V2_LED_SATA, 53 }; 54 55 struct ns2_led_mode_value { 56 enum ns2_led_modes mode; 57 int cmd_level; 58 int slow_level; 59 }; 60 61 static struct ns2_led_mode_value ns2_led_modval[] = { 62 { NS_V2_LED_OFF , 1, 0 }, 63 { NS_V2_LED_ON , 0, 1 }, 64 { NS_V2_LED_ON , 1, 1 }, 65 { NS_V2_LED_SATA, 0, 0 }, 66 }; 67 68 struct ns2_led_data { 69 struct led_classdev cdev; 70 unsigned cmd; 71 unsigned slow; 72 unsigned char sata; /* True when SATA mode active. */ 73 rwlock_t rw_lock; /* Lock GPIOs. */ 74 }; 75 76 static int ns2_led_get_mode(struct ns2_led_data *led_dat, 77 enum ns2_led_modes *mode) 78 { 79 int i; 80 int ret = -EINVAL; 81 int cmd_level; 82 int slow_level; 83 84 read_lock_irq(&led_dat->rw_lock); 85 86 cmd_level = gpio_get_value(led_dat->cmd); 87 slow_level = gpio_get_value(led_dat->slow); 88 89 for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 90 if (cmd_level == ns2_led_modval[i].cmd_level && 91 slow_level == ns2_led_modval[i].slow_level) { 92 *mode = ns2_led_modval[i].mode; 93 ret = 0; 94 break; 95 } 96 } 97 98 read_unlock_irq(&led_dat->rw_lock); 99 100 return ret; 101 } 102 103 static void ns2_led_set_mode(struct ns2_led_data *led_dat, 104 enum ns2_led_modes mode) 105 { 106 int i; 107 unsigned long flags; 108 109 write_lock_irqsave(&led_dat->rw_lock, flags); 110 111 for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 112 if (mode == ns2_led_modval[i].mode) { 113 gpio_set_value(led_dat->cmd, 114 ns2_led_modval[i].cmd_level); 115 gpio_set_value(led_dat->slow, 116 ns2_led_modval[i].slow_level); 117 } 118 } 119 120 write_unlock_irqrestore(&led_dat->rw_lock, flags); 121 } 122 123 static void ns2_led_set(struct led_classdev *led_cdev, 124 enum led_brightness value) 125 { 126 struct ns2_led_data *led_dat = 127 container_of(led_cdev, struct ns2_led_data, cdev); 128 enum ns2_led_modes mode; 129 130 if (value == LED_OFF) 131 mode = NS_V2_LED_OFF; 132 else if (led_dat->sata) 133 mode = NS_V2_LED_SATA; 134 else 135 mode = NS_V2_LED_ON; 136 137 ns2_led_set_mode(led_dat, mode); 138 } 139 140 static ssize_t ns2_led_sata_store(struct device *dev, 141 struct device_attribute *attr, 142 const char *buff, size_t count) 143 { 144 int ret; 145 unsigned long enable; 146 enum ns2_led_modes mode; 147 struct ns2_led_data *led_dat = dev_get_drvdata(dev); 148 149 ret = strict_strtoul(buff, 10, &enable); 150 if (ret < 0) 151 return ret; 152 153 enable = !!enable; 154 155 if (led_dat->sata == enable) 156 return count; 157 158 ret = ns2_led_get_mode(led_dat, &mode); 159 if (ret < 0) 160 return ret; 161 162 if (enable && mode == NS_V2_LED_ON) 163 ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 164 if (!enable && mode == NS_V2_LED_SATA) 165 ns2_led_set_mode(led_dat, NS_V2_LED_ON); 166 167 led_dat->sata = enable; 168 169 return count; 170 } 171 172 static ssize_t ns2_led_sata_show(struct device *dev, 173 struct device_attribute *attr, char *buf) 174 { 175 struct ns2_led_data *led_dat = dev_get_drvdata(dev); 176 177 return sprintf(buf, "%d\n", led_dat->sata); 178 } 179 180 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 181 182 static int __devinit 183 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 184 const struct ns2_led *template) 185 { 186 int ret; 187 enum ns2_led_modes mode; 188 189 ret = gpio_request(template->cmd, template->name); 190 if (ret == 0) { 191 ret = gpio_direction_output(template->cmd, 192 gpio_get_value(template->cmd)); 193 if (ret) 194 gpio_free(template->cmd); 195 } 196 if (ret) { 197 dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 198 template->name); 199 } 200 201 ret = gpio_request(template->slow, template->name); 202 if (ret == 0) { 203 ret = gpio_direction_output(template->slow, 204 gpio_get_value(template->slow)); 205 if (ret) 206 gpio_free(template->slow); 207 } 208 if (ret) { 209 dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 210 template->name); 211 goto err_free_cmd; 212 } 213 214 rwlock_init(&led_dat->rw_lock); 215 216 led_dat->cdev.name = template->name; 217 led_dat->cdev.default_trigger = template->default_trigger; 218 led_dat->cdev.blink_set = NULL; 219 led_dat->cdev.brightness_set = ns2_led_set; 220 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 221 led_dat->cmd = template->cmd; 222 led_dat->slow = template->slow; 223 224 ret = ns2_led_get_mode(led_dat, &mode); 225 if (ret < 0) 226 goto err_free_slow; 227 228 /* Set LED initial state. */ 229 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 230 led_dat->cdev.brightness = 231 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 232 233 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 234 if (ret < 0) 235 goto err_free_slow; 236 237 dev_set_drvdata(led_dat->cdev.dev, led_dat); 238 ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 239 if (ret < 0) 240 goto err_free_cdev; 241 242 return 0; 243 244 err_free_cdev: 245 led_classdev_unregister(&led_dat->cdev); 246 err_free_slow: 247 gpio_free(led_dat->slow); 248 err_free_cmd: 249 gpio_free(led_dat->cmd); 250 251 return ret; 252 } 253 254 static void __devexit delete_ns2_led(struct ns2_led_data *led_dat) 255 { 256 device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 257 led_classdev_unregister(&led_dat->cdev); 258 gpio_free(led_dat->cmd); 259 gpio_free(led_dat->slow); 260 } 261 262 static int __devinit ns2_led_probe(struct platform_device *pdev) 263 { 264 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 265 struct ns2_led_data *leds_data; 266 int i; 267 int ret; 268 269 if (!pdata) 270 return -EINVAL; 271 272 leds_data = kzalloc(sizeof(struct ns2_led_data) * 273 pdata->num_leds, GFP_KERNEL); 274 if (!leds_data) 275 return -ENOMEM; 276 277 for (i = 0; i < pdata->num_leds; i++) { 278 ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); 279 if (ret < 0) 280 goto err; 281 282 } 283 284 platform_set_drvdata(pdev, leds_data); 285 286 return 0; 287 288 err: 289 for (i = i - 1; i >= 0; i--) 290 delete_ns2_led(&leds_data[i]); 291 292 kfree(leds_data); 293 294 return ret; 295 } 296 297 static int __devexit ns2_led_remove(struct platform_device *pdev) 298 { 299 int i; 300 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 301 struct ns2_led_data *leds_data; 302 303 leds_data = platform_get_drvdata(pdev); 304 305 for (i = 0; i < pdata->num_leds; i++) 306 delete_ns2_led(&leds_data[i]); 307 308 kfree(leds_data); 309 platform_set_drvdata(pdev, NULL); 310 311 return 0; 312 } 313 314 static struct platform_driver ns2_led_driver = { 315 .probe = ns2_led_probe, 316 .remove = __devexit_p(ns2_led_remove), 317 .driver = { 318 .name = "leds-ns2", 319 .owner = THIS_MODULE, 320 }, 321 }; 322 MODULE_ALIAS("platform:leds-ns2"); 323 324 static int __init ns2_led_init(void) 325 { 326 return platform_driver_register(&ns2_led_driver); 327 } 328 329 static void __exit ns2_led_exit(void) 330 { 331 platform_driver_unregister(&ns2_led_driver); 332 } 333 334 module_init(ns2_led_init); 335 module_exit(ns2_led_exit); 336 337 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 338 MODULE_DESCRIPTION("Network Space v2 LED driver"); 339 MODULE_LICENSE("GPL"); 340