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 struct led_classdev *led_cdev = dev_get_drvdata(dev); 145 struct ns2_led_data *led_dat = 146 container_of(led_cdev, struct ns2_led_data, cdev); 147 int ret; 148 unsigned long enable; 149 enum ns2_led_modes mode; 150 151 ret = strict_strtoul(buff, 10, &enable); 152 if (ret < 0) 153 return ret; 154 155 enable = !!enable; 156 157 if (led_dat->sata == enable) 158 return count; 159 160 ret = ns2_led_get_mode(led_dat, &mode); 161 if (ret < 0) 162 return ret; 163 164 if (enable && mode == NS_V2_LED_ON) 165 ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 166 if (!enable && mode == NS_V2_LED_SATA) 167 ns2_led_set_mode(led_dat, NS_V2_LED_ON); 168 169 led_dat->sata = enable; 170 171 return count; 172 } 173 174 static ssize_t ns2_led_sata_show(struct device *dev, 175 struct device_attribute *attr, char *buf) 176 { 177 struct led_classdev *led_cdev = dev_get_drvdata(dev); 178 struct ns2_led_data *led_dat = 179 container_of(led_cdev, struct ns2_led_data, cdev); 180 181 return sprintf(buf, "%d\n", led_dat->sata); 182 } 183 184 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 185 186 static int __devinit 187 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 188 const struct ns2_led *template) 189 { 190 int ret; 191 enum ns2_led_modes mode; 192 193 ret = gpio_request(template->cmd, template->name); 194 if (ret == 0) { 195 ret = gpio_direction_output(template->cmd, 196 gpio_get_value(template->cmd)); 197 if (ret) 198 gpio_free(template->cmd); 199 } 200 if (ret) { 201 dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 202 template->name); 203 } 204 205 ret = gpio_request(template->slow, template->name); 206 if (ret == 0) { 207 ret = gpio_direction_output(template->slow, 208 gpio_get_value(template->slow)); 209 if (ret) 210 gpio_free(template->slow); 211 } 212 if (ret) { 213 dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 214 template->name); 215 goto err_free_cmd; 216 } 217 218 rwlock_init(&led_dat->rw_lock); 219 220 led_dat->cdev.name = template->name; 221 led_dat->cdev.default_trigger = template->default_trigger; 222 led_dat->cdev.blink_set = NULL; 223 led_dat->cdev.brightness_set = ns2_led_set; 224 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 225 led_dat->cmd = template->cmd; 226 led_dat->slow = template->slow; 227 228 ret = ns2_led_get_mode(led_dat, &mode); 229 if (ret < 0) 230 goto err_free_slow; 231 232 /* Set LED initial state. */ 233 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 234 led_dat->cdev.brightness = 235 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 236 237 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 238 if (ret < 0) 239 goto err_free_slow; 240 241 ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 242 if (ret < 0) 243 goto err_free_cdev; 244 245 return 0; 246 247 err_free_cdev: 248 led_classdev_unregister(&led_dat->cdev); 249 err_free_slow: 250 gpio_free(led_dat->slow); 251 err_free_cmd: 252 gpio_free(led_dat->cmd); 253 254 return ret; 255 } 256 257 static void __devexit delete_ns2_led(struct ns2_led_data *led_dat) 258 { 259 device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 260 led_classdev_unregister(&led_dat->cdev); 261 gpio_free(led_dat->cmd); 262 gpio_free(led_dat->slow); 263 } 264 265 static int __devinit ns2_led_probe(struct platform_device *pdev) 266 { 267 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 268 struct ns2_led_data *leds_data; 269 int i; 270 int ret; 271 272 if (!pdata) 273 return -EINVAL; 274 275 leds_data = kzalloc(sizeof(struct ns2_led_data) * 276 pdata->num_leds, GFP_KERNEL); 277 if (!leds_data) 278 return -ENOMEM; 279 280 for (i = 0; i < pdata->num_leds; i++) { 281 ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); 282 if (ret < 0) 283 goto err; 284 285 } 286 287 platform_set_drvdata(pdev, leds_data); 288 289 return 0; 290 291 err: 292 for (i = i - 1; i >= 0; i--) 293 delete_ns2_led(&leds_data[i]); 294 295 kfree(leds_data); 296 297 return ret; 298 } 299 300 static int __devexit ns2_led_remove(struct platform_device *pdev) 301 { 302 int i; 303 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 304 struct ns2_led_data *leds_data; 305 306 leds_data = platform_get_drvdata(pdev); 307 308 for (i = 0; i < pdata->num_leds; i++) 309 delete_ns2_led(&leds_data[i]); 310 311 kfree(leds_data); 312 platform_set_drvdata(pdev, NULL); 313 314 return 0; 315 } 316 317 static struct platform_driver ns2_led_driver = { 318 .probe = ns2_led_probe, 319 .remove = __devexit_p(ns2_led_remove), 320 .driver = { 321 .name = "leds-ns2", 322 .owner = THIS_MODULE, 323 }, 324 }; 325 MODULE_ALIAS("platform:leds-ns2"); 326 327 static int __init ns2_led_init(void) 328 { 329 return platform_driver_register(&ns2_led_driver); 330 } 331 332 static void __exit ns2_led_exit(void) 333 { 334 platform_driver_unregister(&ns2_led_driver); 335 } 336 337 module_init(ns2_led_init); 338 module_exit(ns2_led_exit); 339 340 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 341 MODULE_DESCRIPTION("Network Space v2 LED driver"); 342 MODULE_LICENSE("GPL"); 343