2019-05-27 09:55:05 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
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 >
*/
# include <linux/kernel.h>
# include <linux/platform_device.h>
# include <linux/slab.h>
2020-01-07 17:10:28 +03:00
# include <linux/gpio/consumer.h>
2010-07-06 18:08:46 +04:00
# include <linux/leds.h>
2011-07-03 21:56:03 +04:00
# include <linux/module.h>
2013-09-28 15:38:30 +04:00
# include <linux/of.h>
2015-07-02 20:56:42 +03:00
# include "leds.h"
2010-07-06 18:08:46 +04:00
2020-01-07 17:10:28 +03:00
enum ns2_led_modes {
NS_V2_LED_OFF ,
NS_V2_LED_ON ,
NS_V2_LED_SATA ,
} ;
2020-09-26 23:11:30 +03:00
/*
* If the size of this structure or types of its members is changed ,
* the filling of array modval in function ns2_led_register must be changed
* accordingly .
*/
2020-01-07 17:10:28 +03:00
struct ns2_led_modval {
2020-09-26 23:11:30 +03:00
u32 mode ;
u32 cmd_level ;
u32 slow_level ;
} __packed ;
2020-01-07 17:10:28 +03:00
2010-07-06 18:08:46 +04:00
/*
2015-07-02 20:56:40 +03:00
* The Network Space v2 dual - GPIO LED is wired to a CPLD . Three different LED
* modes are available : off , on and SATA activity blinking . The LED modes are
* controlled through two GPIOs ( command and slow ) : each combination of values
* for the command / slow GPIOs corresponds to a LED mode .
2010-07-06 18:08:46 +04:00
*/
2020-09-18 01:33:29 +03:00
struct ns2_led {
2010-07-06 18:08:46 +04:00
struct led_classdev cdev ;
2020-01-07 17:10:29 +03:00
struct gpio_desc * cmd ;
struct gpio_desc * slow ;
2015-07-02 20:56:42 +03:00
bool can_sleep ;
2010-07-06 18:08:46 +04:00
unsigned char sata ; /* True when SATA mode active. */
rwlock_t rw_lock ; /* Lock GPIOs. */
2015-07-02 20:56:40 +03:00
int num_modes ;
struct ns2_led_modval * modval ;
2010-07-06 18:08:46 +04:00
} ;
2020-09-18 01:33:30 +03:00
static int ns2_led_get_mode ( struct ns2_led * led , enum ns2_led_modes * mode )
2010-07-06 18:08:46 +04:00
{
int i ;
int cmd_level ;
int slow_level ;
2020-09-18 01:33:30 +03:00
cmd_level = gpiod_get_value_cansleep ( led - > cmd ) ;
slow_level = gpiod_get_value_cansleep ( led - > slow ) ;
2010-07-06 18:08:46 +04:00
2020-09-18 01:33:30 +03:00
for ( i = 0 ; i < led - > num_modes ; i + + ) {
if ( cmd_level = = led - > modval [ i ] . cmd_level & &
slow_level = = led - > modval [ i ] . slow_level ) {
* mode = led - > modval [ i ] . mode ;
2020-09-18 01:33:31 +03:00
return 0 ;
2010-07-06 18:08:46 +04:00
}
}
2020-09-18 01:33:31 +03:00
return - EINVAL ;
2010-07-06 18:08:46 +04:00
}
2020-09-18 01:33:30 +03:00
static void ns2_led_set_mode ( struct ns2_led * led , enum ns2_led_modes mode )
2010-07-06 18:08:46 +04:00
{
int i ;
2010-09-19 17:30:59 +04:00
unsigned long flags ;
2010-07-06 18:08:46 +04:00
2020-09-18 01:33:30 +03:00
for ( i = 0 ; i < led - > num_modes ; i + + )
2020-09-18 01:33:34 +03:00
if ( mode = = led - > modval [ i ] . mode )
2015-07-02 20:56:42 +03:00
break ;
2020-09-18 01:33:34 +03:00
if ( i = = led - > num_modes )
2015-07-02 20:56:42 +03:00
return ;
2020-09-18 01:33:30 +03:00
write_lock_irqsave ( & led - > rw_lock , flags ) ;
2015-07-02 20:56:42 +03:00
2020-09-18 01:33:30 +03:00
if ( ! led - > can_sleep ) {
gpiod_set_value ( led - > cmd , led - > modval [ i ] . cmd_level ) ;
gpiod_set_value ( led - > slow , led - > modval [ i ] . slow_level ) ;
2015-07-02 20:56:42 +03:00
goto exit_unlock ;
2010-07-06 18:08:46 +04:00
}
2020-09-18 01:33:30 +03:00
gpiod_set_value_cansleep ( led - > cmd , led - > modval [ i ] . cmd_level ) ;
gpiod_set_value_cansleep ( led - > slow , led - > modval [ i ] . slow_level ) ;
2015-07-02 20:56:42 +03:00
exit_unlock :
2020-09-18 01:33:30 +03:00
write_unlock_irqrestore ( & led - > 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 )
{
2020-09-18 01:33:30 +03:00
struct ns2_led * led = container_of ( led_cdev , struct ns2_led , cdev ) ;
2010-07-06 18:08:46 +04:00
enum ns2_led_modes mode ;
if ( value = = LED_OFF )
mode = NS_V2_LED_OFF ;
2020-09-18 01:33:30 +03:00
else if ( led - > sata )
2010-07-06 18:08:46 +04:00
mode = NS_V2_LED_SATA ;
else
mode = NS_V2_LED_ON ;
2020-09-18 01:33:30 +03:00
ns2_led_set_mode ( led , mode ) ;
2010-07-06 18:08:46 +04:00
}
2015-11-20 13:39:41 +03:00
static int ns2_led_set_blocking ( struct led_classdev * led_cdev ,
enum led_brightness value )
{
ns2_led_set ( led_cdev , value ) ;
return 0 ;
}
2010-07-06 18:08:46 +04:00
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 ) ;
2020-09-18 01:33:30 +03:00
struct ns2_led * led = container_of ( led_cdev , struct ns2_led , cdev ) ;
2010-07-06 18:08:46 +04:00
int ret ;
unsigned long enable ;
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 ;
2020-09-18 01:33:30 +03:00
if ( led - > sata = = enable )
2015-07-02 20:56:42 +03:00
goto exit ;
2010-07-06 18:08:46 +04:00
2020-09-18 01:33:30 +03:00
led - > sata = enable ;
2015-07-02 20:56:42 +03:00
if ( ! led_get_brightness ( led_cdev ) )
goto exit ;
2010-07-06 18:08:46 +04:00
2015-07-02 20:56:42 +03:00
if ( enable )
2020-09-18 01:33:30 +03:00
ns2_led_set_mode ( led , NS_V2_LED_SATA ) ;
2015-07-02 20:56:42 +03:00
else
2020-09-18 01:33:30 +03:00
ns2_led_set_mode ( led , NS_V2_LED_ON ) ;
2010-07-06 18:08:46 +04:00
2015-07-02 20:56:42 +03:00
exit :
2010-07-06 18:08:46 +04:00
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 ) ;
2020-09-18 01:33:30 +03:00
struct ns2_led * led = container_of ( led_cdev , struct ns2_led , cdev ) ;
2010-07-06 18:08:46 +04:00
2020-09-18 01:33:30 +03:00
return sprintf ( buf , " %d \n " , led - > sata ) ;
2010-07-06 18:08:46 +04:00
}
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 ) ;
2020-09-26 23:11:30 +03:00
static int ns2_led_register ( struct device * dev , struct fwnode_handle * node ,
2020-09-18 01:33:33 +03:00
struct ns2_led * led )
2020-09-18 01:33:27 +03:00
{
2020-09-18 01:33:37 +03:00
struct led_init_data init_data = { } ;
2020-09-18 01:33:27 +03:00
struct ns2_led_modval * modval ;
2020-09-18 01:33:33 +03:00
enum ns2_led_modes mode ;
2020-09-26 23:11:30 +03:00
int nmodes , ret ;
2020-09-18 01:33:27 +03:00
2020-09-26 23:11:30 +03:00
led - > cmd = devm_fwnode_gpiod_get_index ( dev , node , " cmd " , 0 , GPIOD_ASIS ,
fwnode_get_name ( node ) ) ;
2020-09-18 01:33:27 +03:00
if ( IS_ERR ( led - > cmd ) )
return PTR_ERR ( led - > cmd ) ;
2020-09-26 23:11:30 +03:00
led - > slow = devm_fwnode_gpiod_get_index ( dev , node , " slow " , 0 ,
GPIOD_ASIS ,
fwnode_get_name ( node ) ) ;
2020-09-18 01:33:27 +03:00
if ( IS_ERR ( led - > slow ) )
return PTR_ERR ( led - > slow ) ;
2020-09-26 23:11:30 +03:00
ret = fwnode_property_count_u32 ( node , " modes-map " ) ;
2020-09-18 01:33:27 +03:00
if ( ret < 0 | | ret % 3 ) {
2020-09-26 23:11:30 +03:00
dev_err ( dev , " Missing or malformed modes-map for %pfw \n " , node ) ;
2020-09-18 01:33:27 +03:00
return - EINVAL ;
}
nmodes = ret / 3 ;
modval = devm_kcalloc ( dev , nmodes , sizeof ( * modval ) , GFP_KERNEL ) ;
if ( ! modval )
return - ENOMEM ;
2020-09-26 23:11:30 +03:00
fwnode_property_read_u32_array ( node , " modes-map " , ( void * ) modval ,
nmodes * 3 ) ;
2020-09-18 01:33:27 +03:00
2020-09-18 01:33:33 +03:00
rwlock_init ( & led - > rw_lock ) ;
led - > cdev . blink_set = NULL ;
led - > cdev . flags | = LED_CORE_SUSPENDRESUME ;
led - > cdev . groups = ns2_led_groups ;
led - > can_sleep = gpiod_cansleep ( led - > cmd ) | | gpiod_cansleep ( led - > slow ) ;
if ( led - > can_sleep )
led - > cdev . brightness_set_blocking = ns2_led_set_blocking ;
else
led - > cdev . brightness_set = ns2_led_set ;
2020-09-18 01:33:27 +03:00
led - > num_modes = nmodes ;
led - > modval = modval ;
2020-09-18 01:33:33 +03:00
ret = ns2_led_get_mode ( led , & mode ) ;
if ( ret < 0 )
return ret ;
2012-10-17 14:09:03 +04:00
2020-09-18 01:33:33 +03:00
/* Set LED initial state. */
led - > sata = ( mode = = NS_V2_LED_SATA ) ? 1 : 0 ;
led - > cdev . brightness = ( mode = = NS_V2_LED_OFF ) ? LED_OFF : LED_FULL ;
2012-10-17 14:09:03 +04:00
2020-09-26 23:11:30 +03:00
init_data . fwnode = node ;
2020-09-18 01:33:37 +03:00
ret = devm_led_classdev_register_ext ( dev , & led - > cdev , & init_data ) ;
2020-09-18 01:33:33 +03:00
if ( ret )
2020-09-26 23:11:30 +03:00
dev_err ( dev , " Failed to register LED for node %pfw \n " , node ) ;
2012-10-17 14:09:03 +04:00
2020-09-18 01:33:33 +03:00
return ret ;
2012-10-17 14:09:03 +04:00
}
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
{
2020-09-18 01:33:32 +03:00
struct device * dev = & pdev - > dev ;
2020-09-26 23:11:30 +03:00
struct fwnode_handle * child ;
2020-09-18 01:33:29 +03:00
struct ns2_led * leds ;
2020-09-18 01:33:33 +03:00
int count ;
2010-07-06 18:08:46 +04:00
int ret ;
2020-09-26 23:11:30 +03:00
count = device_get_child_node_count ( dev ) ;
2020-09-18 01:33:33 +03:00
if ( ! count )
return - ENODEV ;
2010-07-06 18:08:46 +04:00
2020-09-18 01:33:33 +03:00
leds = devm_kzalloc ( dev , array_size ( sizeof ( * leds ) , count ) , GFP_KERNEL ) ;
2020-09-18 01:33:25 +03:00
if ( ! leds )
2010-07-06 18:08:46 +04:00
return - ENOMEM ;
2020-09-26 23:11:30 +03:00
device_for_each_child_node ( dev , child ) {
2020-09-18 01:33:33 +03:00
ret = ns2_led_register ( dev , child , leds + + ) ;
if ( ret ) {
2020-09-26 23:11:30 +03:00
fwnode_handle_put ( child ) ;
2012-07-04 08:30:50 +04:00
return ret ;
2020-09-18 01:33:33 +03:00
}
2010-07-06 18:08:46 +04:00
}
return 0 ;
}
2020-09-26 23:11:31 +03:00
static const struct of_device_id of_ns2_leds_match [ ] = {
{ . compatible = " lacie,ns2-leds " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , of_ns2_leds_match ) ;
2010-07-06 18:08:46 +04:00
static struct platform_driver ns2_led_driver = {
. probe = ns2_led_probe ,
. driver = {
2012-10-17 14:09:03 +04:00
. name = " leds-ns2 " ,
2020-09-26 23:11:31 +03:00
. of_match_table = 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 " ) ;