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