2010-07-06 18:08:46 +04:00
/*
* leds - ns2 . c - Driver for the Network Space v2 ( and parents ) dual - GPIO LED
*
* Copyright ( C ) 2010 LaCie
*
* Author : Simon Guinot < sguinot @ lacie . com >
*
* Based on leds - gpio . c by Raphael Assenat < raph @ 8 d . com >
*
* This program is free software ; you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation ; either version 2 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
# include <linux/gpio.h>
# include <linux/leds.h>
2011-07-03 21:56:03 +04:00
# include <linux/module.h>
2012-08-24 17:21:54 +04:00
# include <linux/platform_data/leds-kirkwood-ns2.h>
2013-09-28 15:38:30 +04:00
# include <linux/of.h>
2012-10-17 14:09:03 +04:00
# include <linux/of_gpio.h>
2010-07-06 18:08:46 +04:00
/*
* The Network Space v2 dual - GPIO LED is wired to a CPLD and can blink in
* relation with the SATA activity . This capability is exposed through the
* " sata " sysfs attribute .
*
* The following array detail the different LED registers and the combination
* of their possible values :
*
* cmd_led | slow_led | / SATA active | LED state
* | | |
* 1 | 0 | x | off
* - | 1 | x | on
* 0 | 0 | 1 | on
* 0 | 0 | 0 | blink ( rate 300 ms )
*/
enum ns2_led_modes {
NS_V2_LED_OFF ,
NS_V2_LED_ON ,
NS_V2_LED_SATA ,
} ;
struct ns2_led_mode_value {
enum ns2_led_modes mode ;
int cmd_level ;
int slow_level ;
} ;
static struct ns2_led_mode_value ns2_led_modval [ ] = {
{ NS_V2_LED_OFF , 1 , 0 } ,
{ NS_V2_LED_ON , 0 , 1 } ,
{ NS_V2_LED_ON , 1 , 1 } ,
{ NS_V2_LED_SATA , 0 , 0 } ,
} ;
struct ns2_led_data {
struct led_classdev cdev ;
unsigned cmd ;
unsigned slow ;
unsigned char sata ; /* True when SATA mode active. */
rwlock_t rw_lock ; /* Lock GPIOs. */
} ;
static int ns2_led_get_mode ( struct ns2_led_data * led_dat ,
enum ns2_led_modes * mode )
{
int i ;
int ret = - EINVAL ;
int cmd_level ;
int slow_level ;
2010-09-19 17:30:59 +04:00
read_lock_irq ( & led_dat - > rw_lock ) ;
2010-07-06 18:08:46 +04:00
cmd_level = gpio_get_value ( led_dat - > cmd ) ;
slow_level = gpio_get_value ( led_dat - > slow ) ;
for ( i = 0 ; i < ARRAY_SIZE ( ns2_led_modval ) ; i + + ) {
if ( cmd_level = = ns2_led_modval [ i ] . cmd_level & &
slow_level = = ns2_led_modval [ i ] . slow_level ) {
* mode = ns2_led_modval [ i ] . mode ;
ret = 0 ;
break ;
}
}
2010-09-19 17:30:59 +04:00
read_unlock_irq ( & led_dat - > rw_lock ) ;
2010-07-06 18:08:46 +04:00
return ret ;
}
static void ns2_led_set_mode ( struct ns2_led_data * led_dat ,
enum ns2_led_modes mode )
{
int i ;
2010-09-19 17:30:59 +04:00
unsigned long flags ;
2010-07-06 18:08:46 +04:00
2010-09-19 17:30:59 +04:00
write_lock_irqsave ( & led_dat - > rw_lock , flags ) ;
2010-07-06 18:08:46 +04:00
for ( i = 0 ; i < ARRAY_SIZE ( ns2_led_modval ) ; i + + ) {
if ( mode = = ns2_led_modval [ i ] . mode ) {
gpio_set_value ( led_dat - > cmd ,
ns2_led_modval [ i ] . cmd_level ) ;
gpio_set_value ( led_dat - > slow ,
ns2_led_modval [ i ] . slow_level ) ;
}
}
2010-09-19 17:30:59 +04:00
write_unlock_irqrestore ( & led_dat - > rw_lock , flags ) ;
2010-07-06 18:08:46 +04:00
}
static void ns2_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct ns2_led_data * led_dat =
container_of ( led_cdev , struct ns2_led_data , cdev ) ;
enum ns2_led_modes mode ;
if ( value = = LED_OFF )
mode = NS_V2_LED_OFF ;
else if ( led_dat - > sata )
mode = NS_V2_LED_SATA ;
else
mode = NS_V2_LED_ON ;
ns2_led_set_mode ( led_dat , mode ) ;
}
static ssize_t ns2_led_sata_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buff , size_t count )
{
2010-10-07 18:35:40 +04:00
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct ns2_led_data * led_dat =
container_of ( led_cdev , struct ns2_led_data , cdev ) ;
2010-07-06 18:08:46 +04:00
int ret ;
unsigned long enable ;
enum ns2_led_modes mode ;
2012-10-23 16:25:35 +04:00
ret = kstrtoul ( buff , 10 , & enable ) ;
2010-07-06 18:08:46 +04:00
if ( ret < 0 )
return ret ;
enable = ! ! enable ;
if ( led_dat - > sata = = enable )
return count ;
ret = ns2_led_get_mode ( led_dat , & mode ) ;
if ( ret < 0 )
return ret ;
if ( enable & & mode = = NS_V2_LED_ON )
ns2_led_set_mode ( led_dat , NS_V2_LED_SATA ) ;
if ( ! enable & & mode = = NS_V2_LED_SATA )
ns2_led_set_mode ( led_dat , NS_V2_LED_ON ) ;
led_dat - > sata = enable ;
return count ;
}
static ssize_t ns2_led_sata_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
2010-10-07 18:35:40 +04:00
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct ns2_led_data * led_dat =
container_of ( led_cdev , struct ns2_led_data , cdev ) ;
2010-07-06 18:08:46 +04:00
return sprintf ( buf , " %d \n " , led_dat - > sata ) ;
}
static DEVICE_ATTR ( sata , 0644 , ns2_led_sata_show , ns2_led_sata_store ) ;
2014-06-25 21:08:51 +04:00
static struct attribute * ns2_led_attrs [ ] = {
& dev_attr_sata . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( ns2_led ) ;
2012-11-19 22:23:02 +04:00
static int
2010-07-06 18:08:46 +04:00
create_ns2_led ( struct platform_device * pdev , struct ns2_led_data * led_dat ,
const struct ns2_led * template )
{
int ret ;
enum ns2_led_modes mode ;
2012-11-25 08:58:10 +04:00
ret = devm_gpio_request_one ( & pdev - > dev , template - > cmd ,
2013-03-08 06:38:26 +04:00
gpio_get_value ( template - > cmd ) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW ,
2012-10-23 16:18:21 +04:00
template - > name ) ;
2010-07-06 18:08:46 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " %s: failed to setup command GPIO \n " ,
template - > name ) ;
2012-10-23 16:18:21 +04:00
return ret ;
2010-07-06 18:08:46 +04:00
}
2012-11-25 08:58:10 +04:00
ret = devm_gpio_request_one ( & pdev - > dev , template - > slow ,
2013-03-08 06:38:26 +04:00
gpio_get_value ( template - > slow ) ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW ,
2012-10-23 16:18:21 +04:00
template - > name ) ;
2010-07-06 18:08:46 +04:00
if ( ret ) {
dev_err ( & pdev - > dev , " %s: failed to setup slow GPIO \n " ,
template - > name ) ;
2012-11-25 08:58:10 +04:00
return ret ;
2010-07-06 18:08:46 +04:00
}
rwlock_init ( & led_dat - > rw_lock ) ;
led_dat - > cdev . name = template - > name ;
led_dat - > cdev . default_trigger = template - > default_trigger ;
led_dat - > cdev . blink_set = NULL ;
led_dat - > cdev . brightness_set = ns2_led_set ;
led_dat - > cdev . flags | = LED_CORE_SUSPENDRESUME ;
2014-06-25 21:08:51 +04:00
led_dat - > cdev . groups = ns2_led_groups ;
2010-07-06 18:08:46 +04:00
led_dat - > cmd = template - > cmd ;
led_dat - > slow = template - > slow ;
ret = ns2_led_get_mode ( led_dat , & mode ) ;
if ( ret < 0 )
2012-11-25 08:58:10 +04:00
return ret ;
2010-07-06 18:08:46 +04:00
/* Set LED initial state. */
led_dat - > sata = ( mode = = NS_V2_LED_SATA ) ? 1 : 0 ;
led_dat - > cdev . brightness =
( mode = = NS_V2_LED_OFF ) ? LED_OFF : LED_FULL ;
ret = led_classdev_register ( & pdev - > dev , & led_dat - > cdev ) ;
if ( ret < 0 )
2012-11-25 08:58:10 +04:00
return ret ;
2010-07-06 18:08:46 +04:00
return 0 ;
}
2012-05-11 00:01:46 +04:00
static void delete_ns2_led ( struct ns2_led_data * led_dat )
2010-07-06 18:08:46 +04:00
{
led_classdev_unregister ( & led_dat - > cdev ) ;
}
2012-10-17 14:09:03 +04:00
# ifdef CONFIG_OF_GPIO
/*
* Translate OpenFirmware node properties into platform_data .
*/
2012-12-13 00:14:06 +04:00
static int
2012-10-17 14:09:03 +04:00
ns2_leds_get_of_pdata ( struct device * dev , struct ns2_led_platform_data * pdata )
{
struct device_node * np = dev - > of_node ;
struct device_node * child ;
struct ns2_led * leds ;
int num_leds = 0 ;
int i = 0 ;
num_leds = of_get_child_count ( np ) ;
if ( ! num_leds )
return - ENODEV ;
leds = devm_kzalloc ( dev , num_leds * sizeof ( struct ns2_led ) ,
GFP_KERNEL ) ;
if ( ! leds )
return - ENOMEM ;
for_each_child_of_node ( np , child ) {
const char * string ;
int ret ;
ret = of_get_named_gpio ( child , " cmd-gpio " , 0 ) ;
if ( ret < 0 )
return ret ;
leds [ i ] . cmd = ret ;
ret = of_get_named_gpio ( child , " slow-gpio " , 0 ) ;
if ( ret < 0 )
return ret ;
leds [ i ] . slow = ret ;
ret = of_property_read_string ( child , " label " , & string ) ;
leds [ i ] . name = ( ret = = 0 ) ? string : child - > name ;
ret = of_property_read_string ( child , " linux,default-trigger " ,
& string ) ;
if ( ret = = 0 )
leds [ i ] . default_trigger = string ;
i + + ;
}
pdata - > leds = leds ;
pdata - > num_leds = num_leds ;
return 0 ;
}
static const struct of_device_id of_ns2_leds_match [ ] = {
{ . compatible = " lacie,ns2-leds " , } ,
{ } ,
} ;
# endif /* CONFIG_OF_GPIO */
2013-03-19 22:07:29 +04:00
struct ns2_led_priv {
int num_leds ;
struct ns2_led_data leds_data [ ] ;
} ;
static inline int sizeof_ns2_led_priv ( int num_leds )
{
return sizeof ( struct ns2_led_priv ) +
( sizeof ( struct ns2_led_data ) * num_leds ) ;
}
2012-11-19 22:23:02 +04:00
static int ns2_led_probe ( struct platform_device * pdev )
2010-07-06 18:08:46 +04:00
{
2013-07-30 12:07:35 +04:00
struct ns2_led_platform_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2013-03-19 22:07:29 +04:00
struct ns2_led_priv * priv ;
2010-07-06 18:08:46 +04:00
int i ;
int ret ;
2012-10-17 14:09:03 +04:00
# ifdef CONFIG_OF_GPIO
if ( ! pdata ) {
pdata = devm_kzalloc ( & pdev - > dev ,
sizeof ( struct ns2_led_platform_data ) ,
GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
ret = ns2_leds_get_of_pdata ( & pdev - > dev , pdata ) ;
if ( ret )
return ret ;
}
# else
2010-07-06 18:08:46 +04:00
if ( ! pdata )
return - EINVAL ;
2012-10-17 14:09:03 +04:00
# endif /* CONFIG_OF_GPIO */
2010-07-06 18:08:46 +04:00
2013-03-19 22:07:29 +04:00
priv = devm_kzalloc ( & pdev - > dev ,
sizeof_ns2_led_priv ( pdata - > num_leds ) , GFP_KERNEL ) ;
if ( ! priv )
2010-07-06 18:08:46 +04:00
return - ENOMEM ;
2013-03-19 22:07:29 +04:00
priv - > num_leds = pdata - > num_leds ;
2010-07-06 18:08:46 +04:00
2013-03-19 22:07:29 +04:00
for ( i = 0 ; i < priv - > num_leds ; i + + ) {
ret = create_ns2_led ( pdev , & priv - > leds_data [ i ] ,
& pdata - > leds [ i ] ) ;
2012-07-04 08:30:50 +04:00
if ( ret < 0 ) {
for ( i = i - 1 ; i > = 0 ; i - - )
2013-03-19 22:07:29 +04:00
delete_ns2_led ( & priv - > leds_data [ i ] ) ;
2012-07-04 08:30:50 +04:00
return ret ;
}
2010-07-06 18:08:46 +04:00
}
2013-03-19 22:07:29 +04:00
platform_set_drvdata ( pdev , priv ) ;
2010-07-06 18:08:46 +04:00
return 0 ;
}
2012-11-19 22:26:00 +04:00
static int ns2_led_remove ( struct platform_device * pdev )
2010-07-06 18:08:46 +04:00
{
int i ;
2013-03-19 22:07:29 +04:00
struct ns2_led_priv * priv ;
2010-07-06 18:08:46 +04:00
2013-03-19 22:07:29 +04:00
priv = platform_get_drvdata ( pdev ) ;
2010-07-06 18:08:46 +04:00
2013-03-19 22:07:29 +04:00
for ( i = 0 ; i < priv - > num_leds ; i + + )
delete_ns2_led ( & priv - > leds_data [ i ] ) ;
2010-07-06 18:08:46 +04:00
return 0 ;
}
static struct platform_driver ns2_led_driver = {
. probe = ns2_led_probe ,
2012-11-19 22:20:20 +04:00
. remove = ns2_led_remove ,
2010-07-06 18:08:46 +04:00
. driver = {
2012-10-17 14:09:03 +04:00
. name = " leds-ns2 " ,
. owner = THIS_MODULE ,
. of_match_table = of_match_ptr ( of_ns2_leds_match ) ,
2010-07-06 18:08:46 +04:00
} ,
} ;
2012-01-11 03:09:24 +04:00
module_platform_driver ( ns2_led_driver ) ;
2010-07-06 18:08:46 +04:00
MODULE_AUTHOR ( " Simon Guinot <sguinot@lacie.com> " ) ;
MODULE_DESCRIPTION ( " Network Space v2 LED driver " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-01-11 03:09:24 +04:00
MODULE_ALIAS ( " platform:leds-ns2 " ) ;