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 <linux/module.h> 32 #include <mach/leds-ns2.h> 33 34 /* 35 * The Network Space v2 dual-GPIO LED is wired to a CPLD and can blink in 36 * relation with the SATA activity. This capability is exposed through the 37 * "sata" sysfs attribute. 38 * 39 * The following array detail the different LED registers and the combination 40 * of their possible values: 41 * 42 * cmd_led | slow_led | /SATA active | LED state 43 * | | | 44 * 1 | 0 | x | off 45 * - | 1 | x | on 46 * 0 | 0 | 1 | on 47 * 0 | 0 | 0 | blink (rate 300ms) 48 */ 49 50 enum ns2_led_modes { 51 NS_V2_LED_OFF, 52 NS_V2_LED_ON, 53 NS_V2_LED_SATA, 54 }; 55 56 struct ns2_led_mode_value { 57 enum ns2_led_modes mode; 58 int cmd_level; 59 int slow_level; 60 }; 61 62 static struct ns2_led_mode_value ns2_led_modval[] = { 63 { NS_V2_LED_OFF , 1, 0 }, 64 { NS_V2_LED_ON , 0, 1 }, 65 { NS_V2_LED_ON , 1, 1 }, 66 { NS_V2_LED_SATA, 0, 0 }, 67 }; 68 69 struct ns2_led_data { 70 struct led_classdev cdev; 71 unsigned cmd; 72 unsigned slow; 73 unsigned char sata; /* True when SATA mode active. */ 74 rwlock_t rw_lock; /* Lock GPIOs. */ 75 }; 76 77 static int ns2_led_get_mode(struct ns2_led_data *led_dat, 78 enum ns2_led_modes *mode) 79 { 80 int i; 81 int ret = -EINVAL; 82 int cmd_level; 83 int slow_level; 84 85 read_lock_irq(&led_dat->rw_lock); 86 87 cmd_level = gpio_get_value(led_dat->cmd); 88 slow_level = gpio_get_value(led_dat->slow); 89 90 for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 91 if (cmd_level == ns2_led_modval[i].cmd_level && 92 slow_level == ns2_led_modval[i].slow_level) { 93 *mode = ns2_led_modval[i].mode; 94 ret = 0; 95 break; 96 } 97 } 98 99 read_unlock_irq(&led_dat->rw_lock); 100 101 return ret; 102 } 103 104 static void ns2_led_set_mode(struct ns2_led_data *led_dat, 105 enum ns2_led_modes mode) 106 { 107 int i; 108 unsigned long flags; 109 110 write_lock_irqsave(&led_dat->rw_lock, flags); 111 112 for (i = 0; i < ARRAY_SIZE(ns2_led_modval); i++) { 113 if (mode == ns2_led_modval[i].mode) { 114 gpio_set_value(led_dat->cmd, 115 ns2_led_modval[i].cmd_level); 116 gpio_set_value(led_dat->slow, 117 ns2_led_modval[i].slow_level); 118 } 119 } 120 121 write_unlock_irqrestore(&led_dat->rw_lock, flags); 122 } 123 124 static void ns2_led_set(struct led_classdev *led_cdev, 125 enum led_brightness value) 126 { 127 struct ns2_led_data *led_dat = 128 container_of(led_cdev, struct ns2_led_data, cdev); 129 enum ns2_led_modes mode; 130 131 if (value == LED_OFF) 132 mode = NS_V2_LED_OFF; 133 else if (led_dat->sata) 134 mode = NS_V2_LED_SATA; 135 else 136 mode = NS_V2_LED_ON; 137 138 ns2_led_set_mode(led_dat, mode); 139 } 140 141 static ssize_t ns2_led_sata_store(struct device *dev, 142 struct device_attribute *attr, 143 const char *buff, size_t count) 144 { 145 struct led_classdev *led_cdev = dev_get_drvdata(dev); 146 struct ns2_led_data *led_dat = 147 container_of(led_cdev, struct ns2_led_data, cdev); 148 int ret; 149 unsigned long enable; 150 enum ns2_led_modes mode; 151 152 ret = strict_strtoul(buff, 10, &enable); 153 if (ret < 0) 154 return ret; 155 156 enable = !!enable; 157 158 if (led_dat->sata == enable) 159 return count; 160 161 ret = ns2_led_get_mode(led_dat, &mode); 162 if (ret < 0) 163 return ret; 164 165 if (enable && mode == NS_V2_LED_ON) 166 ns2_led_set_mode(led_dat, NS_V2_LED_SATA); 167 if (!enable && mode == NS_V2_LED_SATA) 168 ns2_led_set_mode(led_dat, NS_V2_LED_ON); 169 170 led_dat->sata = enable; 171 172 return count; 173 } 174 175 static ssize_t ns2_led_sata_show(struct device *dev, 176 struct device_attribute *attr, char *buf) 177 { 178 struct led_classdev *led_cdev = dev_get_drvdata(dev); 179 struct ns2_led_data *led_dat = 180 container_of(led_cdev, struct ns2_led_data, cdev); 181 182 return sprintf(buf, "%d\n", led_dat->sata); 183 } 184 185 static DEVICE_ATTR(sata, 0644, ns2_led_sata_show, ns2_led_sata_store); 186 187 static int __devinit 188 create_ns2_led(struct platform_device *pdev, struct ns2_led_data *led_dat, 189 const struct ns2_led *template) 190 { 191 int ret; 192 enum ns2_led_modes mode; 193 194 ret = gpio_request(template->cmd, template->name); 195 if (ret == 0) { 196 ret = gpio_direction_output(template->cmd, 197 gpio_get_value(template->cmd)); 198 if (ret) 199 gpio_free(template->cmd); 200 } 201 if (ret) { 202 dev_err(&pdev->dev, "%s: failed to setup command GPIO\n", 203 template->name); 204 } 205 206 ret = gpio_request(template->slow, template->name); 207 if (ret == 0) { 208 ret = gpio_direction_output(template->slow, 209 gpio_get_value(template->slow)); 210 if (ret) 211 gpio_free(template->slow); 212 } 213 if (ret) { 214 dev_err(&pdev->dev, "%s: failed to setup slow GPIO\n", 215 template->name); 216 goto err_free_cmd; 217 } 218 219 rwlock_init(&led_dat->rw_lock); 220 221 led_dat->cdev.name = template->name; 222 led_dat->cdev.default_trigger = template->default_trigger; 223 led_dat->cdev.blink_set = NULL; 224 led_dat->cdev.brightness_set = ns2_led_set; 225 led_dat->cdev.flags |= LED_CORE_SUSPENDRESUME; 226 led_dat->cmd = template->cmd; 227 led_dat->slow = template->slow; 228 229 ret = ns2_led_get_mode(led_dat, &mode); 230 if (ret < 0) 231 goto err_free_slow; 232 233 /* Set LED initial state. */ 234 led_dat->sata = (mode == NS_V2_LED_SATA) ? 1 : 0; 235 led_dat->cdev.brightness = 236 (mode == NS_V2_LED_OFF) ? LED_OFF : LED_FULL; 237 238 ret = led_classdev_register(&pdev->dev, &led_dat->cdev); 239 if (ret < 0) 240 goto err_free_slow; 241 242 ret = device_create_file(led_dat->cdev.dev, &dev_attr_sata); 243 if (ret < 0) 244 goto err_free_cdev; 245 246 return 0; 247 248 err_free_cdev: 249 led_classdev_unregister(&led_dat->cdev); 250 err_free_slow: 251 gpio_free(led_dat->slow); 252 err_free_cmd: 253 gpio_free(led_dat->cmd); 254 255 return ret; 256 } 257 258 static void delete_ns2_led(struct ns2_led_data *led_dat) 259 { 260 device_remove_file(led_dat->cdev.dev, &dev_attr_sata); 261 led_classdev_unregister(&led_dat->cdev); 262 gpio_free(led_dat->cmd); 263 gpio_free(led_dat->slow); 264 } 265 266 static int __devinit ns2_led_probe(struct platform_device *pdev) 267 { 268 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 269 struct ns2_led_data *leds_data; 270 int i; 271 int ret; 272 273 if (!pdata) 274 return -EINVAL; 275 276 leds_data = devm_kzalloc(&pdev->dev, sizeof(struct ns2_led_data) * 277 pdata->num_leds, GFP_KERNEL); 278 if (!leds_data) 279 return -ENOMEM; 280 281 for (i = 0; i < pdata->num_leds; i++) { 282 ret = create_ns2_led(pdev, &leds_data[i], &pdata->leds[i]); 283 if (ret < 0) { 284 for (i = i - 1; i >= 0; i--) 285 delete_ns2_led(&leds_data[i]); 286 return ret; 287 } 288 } 289 290 platform_set_drvdata(pdev, leds_data); 291 292 return 0; 293 } 294 295 static int __devexit ns2_led_remove(struct platform_device *pdev) 296 { 297 int i; 298 struct ns2_led_platform_data *pdata = pdev->dev.platform_data; 299 struct ns2_led_data *leds_data; 300 301 leds_data = platform_get_drvdata(pdev); 302 303 for (i = 0; i < pdata->num_leds; i++) 304 delete_ns2_led(&leds_data[i]); 305 306 platform_set_drvdata(pdev, NULL); 307 308 return 0; 309 } 310 311 static struct platform_driver ns2_led_driver = { 312 .probe = ns2_led_probe, 313 .remove = __devexit_p(ns2_led_remove), 314 .driver = { 315 .name = "leds-ns2", 316 .owner = THIS_MODULE, 317 }, 318 }; 319 320 module_platform_driver(ns2_led_driver); 321 322 MODULE_AUTHOR("Simon Guinot <sguinot@lacie.com>"); 323 MODULE_DESCRIPTION("Network Space v2 LED driver"); 324 MODULE_LICENSE("GPL"); 325 MODULE_ALIAS("platform:leds-ns2"); 326