2010-10-07 18:38:49 +04:00
/*
* leds - netxbig . c - Driver for the 2 Big and 5 Big Network series LEDs
*
* Copyright ( C ) 2010 LaCie
*
* Author : Simon Guinot < sguinot @ lacie . 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/module.h>
# include <linux/irq.h>
# include <linux/slab.h>
# include <linux/spinlock.h>
# include <linux/platform_device.h>
# include <linux/gpio.h>
2015-09-27 00:02:34 +03:00
# include <linux/of_gpio.h>
2010-10-07 18:38:49 +04:00
# include <linux/leds.h>
2012-08-24 17:21:54 +04:00
# include <linux/platform_data/leds-kirkwood-netxbig.h>
2010-10-07 18:38:49 +04:00
/*
* GPIO extension bus .
*/
static DEFINE_SPINLOCK ( gpio_ext_lock ) ;
static void gpio_ext_set_addr ( struct netxbig_gpio_ext * gpio_ext , int addr )
{
int pin ;
for ( pin = 0 ; pin < gpio_ext - > num_addr ; pin + + )
gpio_set_value ( gpio_ext - > addr [ pin ] , ( addr > > pin ) & 1 ) ;
}
static void gpio_ext_set_data ( struct netxbig_gpio_ext * gpio_ext , int data )
{
int pin ;
for ( pin = 0 ; pin < gpio_ext - > num_data ; pin + + )
gpio_set_value ( gpio_ext - > data [ pin ] , ( data > > pin ) & 1 ) ;
}
static void gpio_ext_enable_select ( struct netxbig_gpio_ext * gpio_ext )
{
/* Enable select is done on the raising edge. */
gpio_set_value ( gpio_ext - > enable , 0 ) ;
gpio_set_value ( gpio_ext - > enable , 1 ) ;
}
static void gpio_ext_set_value ( struct netxbig_gpio_ext * gpio_ext ,
int addr , int value )
{
unsigned long flags ;
spin_lock_irqsave ( & gpio_ext_lock , flags ) ;
gpio_ext_set_addr ( gpio_ext , addr ) ;
gpio_ext_set_data ( gpio_ext , value ) ;
gpio_ext_enable_select ( gpio_ext ) ;
spin_unlock_irqrestore ( & gpio_ext_lock , flags ) ;
}
2015-09-27 00:02:37 +03:00
static int gpio_ext_init ( struct platform_device * pdev ,
struct netxbig_gpio_ext * gpio_ext )
2010-10-07 18:38:49 +04:00
{
int err ;
int i ;
if ( unlikely ( ! gpio_ext ) )
return - EINVAL ;
/* Configure address GPIOs. */
for ( i = 0 ; i < gpio_ext - > num_addr ; i + + ) {
2015-09-27 00:02:37 +03:00
err = devm_gpio_request_one ( & pdev - > dev , gpio_ext - > addr [ i ] ,
GPIOF_OUT_INIT_LOW ,
" GPIO extension addr " ) ;
2010-10-07 18:38:49 +04:00
if ( err )
2015-09-27 00:02:37 +03:00
return err ;
2010-10-07 18:38:49 +04:00
}
/* Configure data GPIOs. */
for ( i = 0 ; i < gpio_ext - > num_data ; i + + ) {
2015-09-27 00:02:37 +03:00
err = devm_gpio_request_one ( & pdev - > dev , gpio_ext - > data [ i ] ,
GPIOF_OUT_INIT_LOW ,
" GPIO extension data " ) ;
2010-10-07 18:38:49 +04:00
if ( err )
2015-09-27 00:02:37 +03:00
return err ;
2010-10-07 18:38:49 +04:00
}
/* Configure "enable select" GPIO. */
2015-09-27 00:02:37 +03:00
err = devm_gpio_request_one ( & pdev - > dev , gpio_ext - > enable ,
GPIOF_OUT_INIT_LOW ,
" GPIO extension enable " ) ;
2010-10-07 18:38:49 +04:00
if ( err )
2015-09-27 00:02:37 +03:00
return err ;
2010-10-07 18:38:49 +04:00
return 0 ;
}
/*
* Class LED driver .
*/
struct netxbig_led_data {
struct netxbig_gpio_ext * gpio_ext ;
struct led_classdev cdev ;
int mode_addr ;
int * mode_val ;
int bright_addr ;
struct netxbig_led_timer * timer ;
int num_timer ;
enum netxbig_led_mode mode ;
int sata ;
spinlock_t lock ;
} ;
static int netxbig_led_get_timer_mode ( enum netxbig_led_mode * mode ,
unsigned long delay_on ,
unsigned long delay_off ,
struct netxbig_led_timer * timer ,
int num_timer )
{
int i ;
for ( i = 0 ; i < num_timer ; i + + ) {
if ( timer [ i ] . delay_on = = delay_on & &
timer [ i ] . delay_off = = delay_off ) {
* mode = timer [ i ] . mode ;
return 0 ;
}
}
return - EINVAL ;
}
static int netxbig_led_blink_set ( struct led_classdev * led_cdev ,
unsigned long * delay_on ,
unsigned long * delay_off )
{
struct netxbig_led_data * led_dat =
container_of ( led_cdev , struct netxbig_led_data , cdev ) ;
enum netxbig_led_mode mode ;
int mode_val ;
int ret ;
/* Look for a LED mode with the requested timer frequency. */
ret = netxbig_led_get_timer_mode ( & mode , * delay_on , * delay_off ,
led_dat - > timer , led_dat - > num_timer ) ;
if ( ret < 0 )
return ret ;
mode_val = led_dat - > mode_val [ mode ] ;
if ( mode_val = = NETXBIG_LED_INVALID_MODE )
return - EINVAL ;
spin_lock_irq ( & led_dat - > lock ) ;
gpio_ext_set_value ( led_dat - > gpio_ext , led_dat - > mode_addr , mode_val ) ;
led_dat - > mode = mode ;
spin_unlock_irq ( & led_dat - > lock ) ;
return 0 ;
}
static void netxbig_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
struct netxbig_led_data * led_dat =
container_of ( led_cdev , struct netxbig_led_data , cdev ) ;
enum netxbig_led_mode mode ;
2015-09-27 00:02:38 +03:00
int mode_val ;
2010-10-07 18:38:49 +04:00
int set_brightness = 1 ;
unsigned long flags ;
spin_lock_irqsave ( & led_dat - > lock , flags ) ;
if ( value = = LED_OFF ) {
mode = NETXBIG_LED_OFF ;
set_brightness = 0 ;
} else {
if ( led_dat - > sata )
mode = NETXBIG_LED_SATA ;
else if ( led_dat - > mode = = NETXBIG_LED_OFF )
mode = NETXBIG_LED_ON ;
else /* Keep 'timer' mode. */
mode = led_dat - > mode ;
}
mode_val = led_dat - > mode_val [ mode ] ;
gpio_ext_set_value ( led_dat - > gpio_ext , led_dat - > mode_addr , mode_val ) ;
led_dat - > mode = mode ;
/*
* Note that the brightness register is shared between all the
* SATA LEDs . So , change the brightness setting for a single
* SATA LED will affect all the others .
*/
2015-09-27 00:02:38 +03:00
if ( set_brightness )
2010-10-07 18:38:49 +04:00
gpio_ext_set_value ( led_dat - > gpio_ext ,
2015-09-27 00:02:38 +03:00
led_dat - > bright_addr , value ) ;
2010-10-07 18:38:49 +04:00
spin_unlock_irqrestore ( & led_dat - > lock , flags ) ;
}
static ssize_t netxbig_led_sata_store ( struct device * dev ,
struct device_attribute * attr ,
const char * buff , size_t count )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct netxbig_led_data * led_dat =
container_of ( led_cdev , struct netxbig_led_data , cdev ) ;
unsigned long enable ;
enum netxbig_led_mode mode ;
int mode_val ;
int ret ;
2012-10-23 16:24:33 +04:00
ret = kstrtoul ( buff , 10 , & enable ) ;
2010-10-07 18:38:49 +04:00
if ( ret < 0 )
return ret ;
enable = ! ! enable ;
spin_lock_irq ( & led_dat - > lock ) ;
if ( led_dat - > sata = = enable ) {
ret = count ;
goto exit_unlock ;
}
if ( led_dat - > mode ! = NETXBIG_LED_ON & &
led_dat - > mode ! = NETXBIG_LED_SATA )
mode = led_dat - > mode ; /* Keep modes 'off' and 'timer'. */
else if ( enable )
mode = NETXBIG_LED_SATA ;
else
mode = NETXBIG_LED_ON ;
mode_val = led_dat - > mode_val [ mode ] ;
if ( mode_val = = NETXBIG_LED_INVALID_MODE ) {
ret = - EINVAL ;
goto exit_unlock ;
}
gpio_ext_set_value ( led_dat - > gpio_ext , led_dat - > mode_addr , mode_val ) ;
led_dat - > mode = mode ;
led_dat - > sata = enable ;
ret = count ;
exit_unlock :
spin_unlock_irq ( & led_dat - > lock ) ;
return ret ;
}
static ssize_t netxbig_led_sata_show ( struct device * dev ,
struct device_attribute * attr , char * buf )
{
struct led_classdev * led_cdev = dev_get_drvdata ( dev ) ;
struct netxbig_led_data * led_dat =
container_of ( led_cdev , struct netxbig_led_data , cdev ) ;
return sprintf ( buf , " %d \n " , led_dat - > sata ) ;
}
static DEVICE_ATTR ( sata , 0644 , netxbig_led_sata_show , netxbig_led_sata_store ) ;
2014-06-25 21:08:50 +04:00
static struct attribute * netxbig_led_attrs [ ] = {
& dev_attr_sata . attr ,
NULL
} ;
ATTRIBUTE_GROUPS ( netxbig_led ) ;
2015-09-27 00:02:37 +03:00
static int create_netxbig_led ( struct platform_device * pdev ,
struct netxbig_led_platform_data * pdata ,
struct netxbig_led_data * led_dat ,
const struct netxbig_led * template )
2010-10-07 18:38:49 +04:00
{
spin_lock_init ( & led_dat - > lock ) ;
led_dat - > gpio_ext = pdata - > gpio_ext ;
led_dat - > cdev . name = template - > name ;
led_dat - > cdev . default_trigger = template - > default_trigger ;
led_dat - > cdev . blink_set = netxbig_led_blink_set ;
led_dat - > cdev . brightness_set = netxbig_led_set ;
/*
* Because the GPIO extension bus don ' t allow to read registers
* value , there is no way to probe the LED initial state .
* So , the initial sysfs LED value for the " brightness " and " sata "
* attributes are inconsistent .
*
* Note that the initial LED state can ' t be reconfigured .
* The reason is that the LED behaviour must stay uniform during
* the whole boot process ( bootloader + linux ) .
*/
led_dat - > sata = 0 ;
led_dat - > cdev . brightness = LED_OFF ;
2015-09-27 00:02:38 +03:00
led_dat - > cdev . max_brightness = template - > bright_max ;
2010-10-07 18:38:49 +04:00
led_dat - > cdev . flags | = LED_CORE_SUSPENDRESUME ;
led_dat - > mode_addr = template - > mode_addr ;
led_dat - > mode_val = template - > mode_val ;
led_dat - > bright_addr = template - > bright_addr ;
led_dat - > timer = pdata - > timer ;
led_dat - > num_timer = pdata - > num_timer ;
2014-12-02 18:32:10 +03:00
/*
* If available , expose the SATA activity blink capability through
* a " sata " sysfs attribute .
*/
if ( led_dat - > mode_val [ NETXBIG_LED_SATA ] ! = NETXBIG_LED_INVALID_MODE )
led_dat - > cdev . groups = netxbig_led_groups ;
2010-10-07 18:38:49 +04:00
2015-09-27 00:02:37 +03:00
return devm_led_classdev_register ( & pdev - > dev , & led_dat - > cdev ) ;
2010-10-07 18:38:49 +04:00
}
2015-09-27 00:02:34 +03:00
# ifdef CONFIG_OF_GPIO
static int gpio_ext_get_of_pdata ( struct device * dev , struct device_node * np ,
struct netxbig_gpio_ext * gpio_ext )
{
int * addr , * data ;
int num_addr , num_data ;
int ret ;
int i ;
ret = of_gpio_named_count ( np , " addr-gpios " ) ;
if ( ret < 0 ) {
dev_err ( dev ,
" Failed to count GPIOs in DT property addr-gpios \n " ) ;
return ret ;
}
num_addr = ret ;
addr = devm_kzalloc ( dev , num_addr * sizeof ( * addr ) , GFP_KERNEL ) ;
if ( ! addr )
return - ENOMEM ;
for ( i = 0 ; i < num_addr ; i + + ) {
ret = of_get_named_gpio ( np , " addr-gpios " , i ) ;
if ( ret < 0 )
return ret ;
addr [ i ] = ret ;
}
gpio_ext - > addr = addr ;
gpio_ext - > num_addr = num_addr ;
ret = of_gpio_named_count ( np , " data-gpios " ) ;
if ( ret < 0 ) {
dev_err ( dev ,
" Failed to count GPIOs in DT property data-gpios \n " ) ;
return ret ;
}
num_data = ret ;
data = devm_kzalloc ( dev , num_data * sizeof ( * data ) , GFP_KERNEL ) ;
if ( ! data )
return - ENOMEM ;
for ( i = 0 ; i < num_data ; i + + ) {
ret = of_get_named_gpio ( np , " data-gpios " , i ) ;
if ( ret < 0 )
return ret ;
data [ i ] = ret ;
}
gpio_ext - > data = data ;
gpio_ext - > num_data = num_data ;
ret = of_get_named_gpio ( np , " enable-gpio " , 0 ) ;
if ( ret < 0 ) {
dev_err ( dev ,
" Failed to get GPIO from DT property enable-gpio \n " ) ;
return ret ;
}
gpio_ext - > enable = ret ;
return 0 ;
}
static int netxbig_leds_get_of_pdata ( struct device * dev ,
struct netxbig_led_platform_data * pdata )
{
struct device_node * np = dev - > of_node ;
struct device_node * gpio_ext_np ;
struct device_node * child ;
struct netxbig_gpio_ext * gpio_ext ;
struct netxbig_led_timer * timers ;
struct netxbig_led * leds , * led ;
int num_timers ;
int num_leds = 0 ;
int ret ;
int i ;
/* GPIO extension */
gpio_ext_np = of_parse_phandle ( np , " gpio-ext " , 0 ) ;
if ( ! gpio_ext_np ) {
dev_err ( dev , " Failed to get DT handle gpio-ext \n " ) ;
return - EINVAL ;
}
gpio_ext = devm_kzalloc ( dev , sizeof ( * gpio_ext ) , GFP_KERNEL ) ;
if ( ! gpio_ext )
return - ENOMEM ;
ret = gpio_ext_get_of_pdata ( dev , gpio_ext_np , gpio_ext ) ;
if ( ret )
return ret ;
of_node_put ( gpio_ext_np ) ;
pdata - > gpio_ext = gpio_ext ;
/* Timers (optional) */
ret = of_property_count_u32_elems ( np , " timers " ) ;
if ( ret > 0 ) {
if ( ret % 3 )
return - EINVAL ;
num_timers = ret / 3 ;
timers = devm_kzalloc ( dev , num_timers * sizeof ( * timers ) ,
GFP_KERNEL ) ;
if ( ! timers )
return - ENOMEM ;
for ( i = 0 ; i < num_timers ; i + + ) {
u32 tmp ;
of_property_read_u32_index ( np , " timers " , 3 * i ,
& timers [ i ] . mode ) ;
if ( timers [ i ] . mode > = NETXBIG_LED_MODE_NUM )
return - EINVAL ;
of_property_read_u32_index ( np , " timers " ,
3 * i + 1 , & tmp ) ;
timers [ i ] . delay_on = tmp ;
of_property_read_u32_index ( np , " timers " ,
3 * i + 2 , & tmp ) ;
timers [ i ] . delay_off = tmp ;
}
pdata - > timer = timers ;
pdata - > num_timer = num_timers ;
}
/* LEDs */
num_leds = of_get_child_count ( np ) ;
if ( ! num_leds ) {
dev_err ( dev , " No LED subnodes found in DT \n " ) ;
return - ENODEV ;
}
leds = devm_kzalloc ( dev , num_leds * sizeof ( * leds ) , GFP_KERNEL ) ;
if ( ! leds )
return - ENOMEM ;
led = leds ;
for_each_child_of_node ( np , child ) {
const char * string ;
int * mode_val ;
int num_modes ;
ret = of_property_read_u32 ( child , " mode-addr " ,
& led - > mode_addr ) ;
if ( ret )
goto err_node_put ;
ret = of_property_read_u32 ( child , " bright-addr " ,
& led - > bright_addr ) ;
if ( ret )
goto err_node_put ;
ret = of_property_read_u32 ( child , " max-brightness " ,
& led - > bright_max ) ;
if ( ret )
goto err_node_put ;
mode_val =
devm_kzalloc ( dev ,
NETXBIG_LED_MODE_NUM * sizeof ( * mode_val ) ,
GFP_KERNEL ) ;
if ( ! mode_val ) {
ret = - ENOMEM ;
goto err_node_put ;
}
for ( i = 0 ; i < NETXBIG_LED_MODE_NUM ; i + + )
mode_val [ i ] = NETXBIG_LED_INVALID_MODE ;
ret = of_property_count_u32_elems ( child , " mode-val " ) ;
if ( ret < 0 | | ret % 2 ) {
ret = - EINVAL ;
goto err_node_put ;
}
num_modes = ret / 2 ;
if ( num_modes > NETXBIG_LED_MODE_NUM ) {
ret = - EINVAL ;
goto err_node_put ;
}
for ( i = 0 ; i < num_modes ; i + + ) {
int mode ;
int val ;
of_property_read_u32_index ( child ,
" mode-val " , 2 * i , & mode ) ;
of_property_read_u32_index ( child ,
" mode-val " , 2 * i + 1 , & val ) ;
if ( mode > = NETXBIG_LED_MODE_NUM ) {
ret = - EINVAL ;
goto err_node_put ;
}
mode_val [ mode ] = val ;
}
led - > mode_val = mode_val ;
if ( ! of_property_read_string ( child , " label " , & string ) )
led - > name = string ;
else
led - > name = child - > name ;
if ( ! of_property_read_string ( child ,
" linux,default-trigger " , & string ) )
led - > default_trigger = string ;
led + + ;
}
pdata - > leds = leds ;
pdata - > num_leds = num_leds ;
return 0 ;
err_node_put :
of_node_put ( child ) ;
return ret ;
}
static const struct of_device_id of_netxbig_leds_match [ ] = {
{ . compatible = " lacie,netxbig-leds " , } ,
{ } ,
} ;
# else
static inline int
netxbig_leds_get_of_pdata ( struct device * dev ,
struct netxbig_led_platform_data * pdata )
{
return - ENODEV ;
}
# endif /* CONFIG_OF_GPIO */
2012-11-19 22:23:02 +04:00
static int netxbig_led_probe ( struct platform_device * pdev )
2010-10-07 18:38:49 +04:00
{
2013-07-30 12:07:35 +04:00
struct netxbig_led_platform_data * pdata = dev_get_platdata ( & pdev - > dev ) ;
2015-09-27 00:02:37 +03:00
struct netxbig_led_data * leds_data ;
2010-10-07 18:38:49 +04:00
int i ;
int ret ;
2015-09-27 00:02:34 +03:00
if ( ! pdata ) {
pdata = devm_kzalloc ( & pdev - > dev , sizeof ( * pdata ) , GFP_KERNEL ) ;
if ( ! pdata )
return - ENOMEM ;
ret = netxbig_leds_get_of_pdata ( & pdev - > dev , pdata ) ;
if ( ret )
return ret ;
}
2010-10-07 18:38:49 +04:00
2015-09-27 00:02:37 +03:00
leds_data = devm_kzalloc ( & pdev - > dev ,
pdata - > num_leds * sizeof ( * leds_data ) ,
GFP_KERNEL ) ;
if ( ! leds_data )
2010-10-07 18:38:49 +04:00
return - ENOMEM ;
2015-09-27 00:02:37 +03:00
ret = gpio_ext_init ( pdev , pdata - > gpio_ext ) ;
2010-10-07 18:38:49 +04:00
if ( ret < 0 )
2012-07-03 08:42:55 +04:00
return ret ;
2010-10-07 18:38:49 +04:00
for ( i = 0 ; i < pdata - > num_leds ; i + + ) {
2015-09-27 00:02:37 +03:00
ret = create_netxbig_led ( pdev , pdata ,
& leds_data [ i ] , & pdata - > leds [ i ] ) ;
2010-10-07 18:38:49 +04:00
if ( ret < 0 )
2015-09-27 00:02:37 +03:00
return ret ;
2010-10-07 18:38:49 +04:00
}
return 0 ;
}
static struct platform_driver netxbig_led_driver = {
. probe = netxbig_led_probe ,
. driver = {
2015-09-27 00:02:34 +03:00
. name = " leds-netxbig " ,
. of_match_table = of_match_ptr ( of_netxbig_leds_match ) ,
2010-10-07 18:38:49 +04:00
} ,
} ;
2012-01-11 03:09:24 +04:00
module_platform_driver ( netxbig_led_driver ) ;
2010-10-07 18:38:49 +04:00
MODULE_AUTHOR ( " Simon Guinot <sguinot@lacie.com> " ) ;
MODULE_DESCRIPTION ( " LED driver for LaCie xBig Network boards " ) ;
MODULE_LICENSE ( " GPL " ) ;
2012-01-11 03:09:24 +04:00
MODULE_ALIAS ( " platform:leds-netxbig " ) ;