2019-09-19 15:53:13 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2019 Crane Merchandising Systems. All rights reserved.
// Copyright (C) 2019 Oleh Kravchenko <oleg@kaa.org.ua>
# include <linux/delay.h>
# include <linux/leds.h>
2021-05-29 14:19:25 +03:00
# include <linux/mod_devicetable.h>
2019-09-19 15:53:13 +03:00
# include <linux/module.h>
2021-05-29 14:19:25 +03:00
# include <linux/property.h>
2019-09-19 15:53:13 +03:00
# include <linux/spi/spi.h>
/*
* EL15203000 SPI protocol description :
* + - - - - - + - - - - - - - - - +
* | LED | COMMAND |
* + - - - - - + - - - - - - - - - +
* | 1 | 1 |
* + - - - - - + - - - - - - - - - +
* ( * ) LEDs MCU board expects 20 msec delay per byte .
*
* LEDs :
* + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | ID | NAME | DESCRIPTION |
* + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | ' P ' 0x50 | Pipe | Consists from 5 LEDs , controlled by board |
* + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | ' S ' 0x53 | Screen frame | Light tube around the screen |
* + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | ' V ' 0x56 | Vending area | Highlights a cup of coffee |
* + - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
*
* COMMAND :
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - +
* | VALUES | PIPE | SCREEN FRAME | VENDING AREA |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - +
* | ' 0 ' 0x30 | Off |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
* | ' 1 ' 0x31 | On |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + - - - - - - - - - - - - - - +
* | ' 2 ' 0x32 | Cascade | Breathing |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - +
* | ' 3 ' 0x33 | Inverse cascade |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
* | ' 4 ' 0x34 | Bounce |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
* | ' 5 ' 0x35 | Inverse bounce |
* + - - - - - - - - - - + - - - - - - - - - - - - - - - - - +
*/
/* EL15203000 default settings */
# define EL_FW_DELAY_USEC 20000ul
# define EL_PATTERN_DELAY_MSEC 800u
# define EL_PATTERN_LEN 10u
# define EL_PATTERN_HALF_LEN (EL_PATTERN_LEN / 2)
enum el15203000_command {
/* for all LEDs */
EL_OFF = ' 0 ' ,
EL_ON = ' 1 ' ,
/* for Screen LED */
EL_SCREEN_BREATHING = ' 2 ' ,
/* for Pipe LED */
EL_PIPE_CASCADE = ' 2 ' ,
EL_PIPE_INV_CASCADE = ' 3 ' ,
EL_PIPE_BOUNCE = ' 4 ' ,
EL_PIPE_INV_BOUNCE = ' 5 ' ,
} ;
struct el15203000_led {
struct led_classdev ldev ;
2021-05-10 12:50:23 +03:00
struct el15203000 * priv ;
2019-09-19 15:53:13 +03:00
u32 reg ;
} ;
struct el15203000 {
struct device * dev ;
struct mutex lock ;
struct spi_device * spi ;
unsigned long delay ;
size_t count ;
struct el15203000_led leds [ ] ;
} ;
2021-05-10 12:50:23 +03:00
# define to_el15203000_led(d) container_of(d, struct el15203000_led, ldev)
2019-09-19 15:53:13 +03:00
static int el15203000_cmd ( struct el15203000_led * led , u8 brightness )
{
int ret ;
u8 cmd [ 2 ] ;
size_t i ;
mutex_lock ( & led - > priv - > lock ) ;
dev_dbg ( led - > priv - > dev , " Set brightness of 0x%02x(%c) to 0x%02x(%c) " ,
led - > reg , led - > reg , brightness , brightness ) ;
/* to avoid SPI mistiming with firmware we should wait some time */
if ( time_after ( led - > priv - > delay , jiffies ) ) {
dev_dbg ( led - > priv - > dev , " Wait %luus to sync " ,
EL_FW_DELAY_USEC ) ;
usleep_range ( EL_FW_DELAY_USEC ,
EL_FW_DELAY_USEC + 1 ) ;
}
cmd [ 0 ] = led - > reg ;
cmd [ 1 ] = brightness ;
for ( i = 0 ; i < ARRAY_SIZE ( cmd ) ; i + + ) {
if ( i )
usleep_range ( EL_FW_DELAY_USEC ,
EL_FW_DELAY_USEC + 1 ) ;
ret = spi_write ( led - > priv - > spi , & cmd [ i ] , sizeof ( cmd [ i ] ) ) ;
if ( ret ) {
dev_err ( led - > priv - > dev ,
" spi_write() error %d " , ret ) ;
break ;
}
}
led - > priv - > delay = jiffies + usecs_to_jiffies ( EL_FW_DELAY_USEC ) ;
mutex_unlock ( & led - > priv - > lock ) ;
return ret ;
}
static int el15203000_set_blocking ( struct led_classdev * ldev ,
enum led_brightness brightness )
{
2021-05-10 12:50:23 +03:00
struct el15203000_led * led = to_el15203000_led ( ldev ) ;
2019-09-19 15:53:13 +03:00
return el15203000_cmd ( led , brightness = = LED_OFF ? EL_OFF : EL_ON ) ;
}
static int el15203000_pattern_set_S ( struct led_classdev * ldev ,
struct led_pattern * pattern ,
u32 len , int repeat )
{
2021-05-10 12:50:23 +03:00
struct el15203000_led * led = to_el15203000_led ( ldev ) ;
2019-09-19 15:53:13 +03:00
if ( repeat > 0 | | len ! = 2 | |
pattern [ 0 ] . delta_t ! = 4000 | | pattern [ 0 ] . brightness ! = 0 | |
pattern [ 1 ] . delta_t ! = 4000 | | pattern [ 1 ] . brightness ! = 1 )
return - EINVAL ;
dev_dbg ( led - > priv - > dev , " Breathing mode for 0x%02x(%c) " ,
led - > reg , led - > reg ) ;
return el15203000_cmd ( led , EL_SCREEN_BREATHING ) ;
}
static bool is_cascade ( const struct led_pattern * pattern , u32 len ,
bool inv , bool right )
{
int val , t ;
u32 i ;
if ( len ! = EL_PATTERN_HALF_LEN )
return false ;
val = right ? BIT ( 4 ) : BIT ( 0 ) ;
for ( i = 0 ; i < len ; i + + ) {
t = inv ? ~ val & GENMASK ( 4 , 0 ) : val ;
if ( pattern [ i ] . delta_t ! = EL_PATTERN_DELAY_MSEC | |
pattern [ i ] . brightness ! = t )
return false ;
val = right ? val > > 1 : val < < 1 ;
}
return true ;
}
static bool is_bounce ( const struct led_pattern * pattern , u32 len , bool inv )
{
if ( len ! = EL_PATTERN_LEN )
return false ;
return is_cascade ( pattern , EL_PATTERN_HALF_LEN , inv , false ) & &
is_cascade ( pattern + EL_PATTERN_HALF_LEN ,
EL_PATTERN_HALF_LEN , inv , true ) ;
}
static int el15203000_pattern_set_P ( struct led_classdev * ldev ,
struct led_pattern * pattern ,
u32 len , int repeat )
{
2021-05-10 12:50:23 +03:00
struct el15203000_led * led = to_el15203000_led ( ldev ) ;
2019-09-19 15:53:13 +03:00
u8 cmd ;
if ( repeat > 0 )
return - EINVAL ;
if ( is_cascade ( pattern , len , false , false ) ) {
dev_dbg ( led - > priv - > dev , " Cascade mode for 0x%02x(%c) " ,
led - > reg , led - > reg ) ;
cmd = EL_PIPE_CASCADE ;
} else if ( is_cascade ( pattern , len , true , false ) ) {
dev_dbg ( led - > priv - > dev , " Inverse cascade mode for 0x%02x(%c) " ,
led - > reg , led - > reg ) ;
cmd = EL_PIPE_INV_CASCADE ;
} else if ( is_bounce ( pattern , len , false ) ) {
dev_dbg ( led - > priv - > dev , " Bounce mode for 0x%02x(%c) " ,
led - > reg , led - > reg ) ;
cmd = EL_PIPE_BOUNCE ;
} else if ( is_bounce ( pattern , len , true ) ) {
dev_dbg ( led - > priv - > dev , " Inverse bounce mode for 0x%02x(%c) " ,
led - > reg , led - > reg ) ;
cmd = EL_PIPE_INV_BOUNCE ;
} else {
dev_err ( led - > priv - > dev , " Invalid hw_pattern for 0x%02x(%c)! " ,
led - > reg , led - > reg ) ;
return - EINVAL ;
}
return el15203000_cmd ( led , cmd ) ;
}
static int el15203000_pattern_clear ( struct led_classdev * ldev )
{
2021-05-10 12:50:23 +03:00
struct el15203000_led * led = to_el15203000_led ( ldev ) ;
2019-09-19 15:53:13 +03:00
return el15203000_cmd ( led , EL_OFF ) ;
}
static int el15203000_probe_dt ( struct el15203000 * priv )
{
struct el15203000_led * led = priv - > leds ;
struct fwnode_handle * child ;
int ret ;
device_for_each_child_node ( priv - > dev , child ) {
struct led_init_data init_data = { } ;
ret = fwnode_property_read_u32 ( child , " reg " , & led - > reg ) ;
if ( ret ) {
dev_err ( priv - > dev , " LED without ID number " ) ;
2021-05-10 12:50:21 +03:00
goto err_child_out ;
2019-09-19 15:53:13 +03:00
}
if ( led - > reg > U8_MAX ) {
dev_err ( priv - > dev , " LED value %d is invalid " , led - > reg ) ;
2021-05-10 12:50:21 +03:00
ret = - EINVAL ;
goto err_child_out ;
2019-09-19 15:53:13 +03:00
}
led - > priv = priv ;
led - > ldev . max_brightness = LED_ON ;
led - > ldev . brightness_set_blocking = el15203000_set_blocking ;
if ( led - > reg = = ' S ' ) {
led - > ldev . pattern_set = el15203000_pattern_set_S ;
led - > ldev . pattern_clear = el15203000_pattern_clear ;
} else if ( led - > reg = = ' P ' ) {
led - > ldev . pattern_set = el15203000_pattern_set_P ;
led - > ldev . pattern_clear = el15203000_pattern_clear ;
}
init_data . fwnode = child ;
ret = devm_led_classdev_register_ext ( priv - > dev , & led - > ldev ,
& init_data ) ;
if ( ret ) {
dev_err ( priv - > dev ,
" failed to register LED device %s, err %d " ,
led - > ldev . name , ret ) ;
2021-05-10 12:50:21 +03:00
goto err_child_out ;
2019-09-19 15:53:13 +03:00
}
led + + ;
}
2021-05-10 12:50:21 +03:00
return 0 ;
err_child_out :
fwnode_handle_put ( child ) ;
2019-09-19 15:53:13 +03:00
return ret ;
}
static int el15203000_probe ( struct spi_device * spi )
{
struct el15203000 * priv ;
size_t count ;
count = device_get_child_node_count ( & spi - > dev ) ;
if ( ! count ) {
dev_err ( & spi - > dev , " LEDs are not defined in device tree! " ) ;
return - ENODEV ;
}
priv = devm_kzalloc ( & spi - > dev , struct_size ( priv , leds , count ) ,
GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
mutex_init ( & priv - > lock ) ;
priv - > count = count ;
priv - > dev = & spi - > dev ;
priv - > spi = spi ;
priv - > delay = jiffies -
usecs_to_jiffies ( EL_FW_DELAY_USEC ) ;
spi_set_drvdata ( spi , priv ) ;
return el15203000_probe_dt ( priv ) ;
}
2022-01-23 20:52:01 +03:00
static void el15203000_remove ( struct spi_device * spi )
2019-09-19 15:53:13 +03:00
{
struct el15203000 * priv = spi_get_drvdata ( spi ) ;
mutex_destroy ( & priv - > lock ) ;
}
static const struct of_device_id el15203000_dt_ids [ ] = {
{ . compatible = " crane,el15203000 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , el15203000_dt_ids ) ;
static struct spi_driver el15203000_driver = {
. probe = el15203000_probe ,
. remove = el15203000_remove ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = el15203000_dt_ids ,
} ,
} ;
module_spi_driver ( el15203000_driver ) ;
MODULE_AUTHOR ( " Oleh Kravchenko <oleg@kaa.org.ua> " ) ;
MODULE_DESCRIPTION ( " el15203000 LED driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " spi:el15203000 " ) ;