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