2018-04-02 15:53:50 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Crane Merchandising Systems. All rights reserved.
// Copyright (C) 2018 Oleh Kravchenko <oleg@kaa.org.ua>
# include <linux/delay.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/of_device.h>
# include <linux/spi/spi.h>
# include <linux/workqueue.h>
/*
* CR0014114 SPI protocol descrtiption :
* + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - +
* | CMD | BRIGHTNESS | CRC |
* + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - +
* | | LED0 | LED1 | LED2 | LED3 | LED4 | LED5 | |
* | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + |
* | | R | G | B | R | G | B | R | G | B | R | G | B | R | G | B | R | G | B | |
* | 1 + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + 1 |
* | | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | |
* | + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + |
* | | 18 | |
* + - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - +
* | 20 |
* + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
*
* PS : Boards can be connected to the chain :
* SPI - > board0 - > board1 - > board2 . .
*/
/* CR0014114 SPI commands */
# define CR_SET_BRIGHTNESS 0x80
# define CR_INIT_REENUMERATE 0x81
# define CR_NEXT_REENUMERATE 0x82
/* CR0014114 default settings */
# define CR_MAX_BRIGHTNESS GENMASK(6, 0)
# define CR_FW_DELAY_MSEC 10
# define CR_RECOUNT_DELAY (HZ * 3600)
2019-06-09 21:19:04 +03:00
# define CR_DEV_NAME "cr0014114"
2018-04-02 15:53:50 +03:00
struct cr0014114_led {
struct cr0014114 * priv ;
struct led_classdev ldev ;
u8 brightness ;
} ;
struct cr0014114 {
bool do_recount ;
size_t count ;
struct delayed_work work ;
struct device * dev ;
struct mutex lock ;
struct spi_device * spi ;
u8 * buf ;
unsigned long delay ;
struct cr0014114_led leds [ ] ;
} ;
static void cr0014114_calc_crc ( u8 * buf , const size_t len )
{
size_t i ;
u8 crc ;
for ( i = 1 , crc = 1 ; i < len - 1 ; i + + )
crc + = buf [ i ] ;
crc | = BIT ( 7 ) ;
/* special case when CRC matches the SPI commands */
if ( crc = = CR_SET_BRIGHTNESS | |
crc = = CR_INIT_REENUMERATE | |
crc = = CR_NEXT_REENUMERATE )
crc = 0xfe ;
buf [ len - 1 ] = crc ;
}
static int cr0014114_recount ( struct cr0014114 * priv )
{
int ret ;
size_t i ;
u8 cmd ;
dev_dbg ( priv - > dev , " LEDs recount is started \n " ) ;
cmd = CR_INIT_REENUMERATE ;
ret = spi_write ( priv - > spi , & cmd , sizeof ( cmd ) ) ;
if ( ret )
goto err ;
cmd = CR_NEXT_REENUMERATE ;
for ( i = 0 ; i < priv - > count ; i + + ) {
msleep ( CR_FW_DELAY_MSEC ) ;
ret = spi_write ( priv - > spi , & cmd , sizeof ( cmd ) ) ;
if ( ret )
goto err ;
}
err :
dev_dbg ( priv - > dev , " LEDs recount is finished \n " ) ;
if ( ret )
dev_err ( priv - > dev , " with error %d " , ret ) ;
return ret ;
}
static int cr0014114_sync ( struct cr0014114 * priv )
{
int ret ;
size_t i ;
unsigned long udelay , now = jiffies ;
/* to avoid SPI mistiming with firmware we should wait some time */
if ( time_after ( priv - > delay , now ) ) {
udelay = jiffies_to_usecs ( priv - > delay - now ) ;
usleep_range ( udelay , udelay + 1 ) ;
}
if ( unlikely ( priv - > do_recount ) ) {
ret = cr0014114_recount ( priv ) ;
if ( ret )
goto err ;
priv - > do_recount = false ;
msleep ( CR_FW_DELAY_MSEC ) ;
}
priv - > buf [ 0 ] = CR_SET_BRIGHTNESS ;
for ( i = 0 ; i < priv - > count ; i + + )
priv - > buf [ i + 1 ] = priv - > leds [ i ] . brightness ;
cr0014114_calc_crc ( priv - > buf , priv - > count + 2 ) ;
ret = spi_write ( priv - > spi , priv - > buf , priv - > count + 2 ) ;
err :
priv - > delay = jiffies + msecs_to_jiffies ( CR_FW_DELAY_MSEC ) ;
return ret ;
}
static void cr0014114_recount_work ( struct work_struct * work )
{
int ret ;
struct cr0014114 * priv = container_of ( work ,
struct cr0014114 ,
work . work ) ;
mutex_lock ( & priv - > lock ) ;
priv - > do_recount = true ;
ret = cr0014114_sync ( priv ) ;
mutex_unlock ( & priv - > lock ) ;
if ( ret )
dev_warn ( priv - > dev , " sync of LEDs failed %d \n " , ret ) ;
schedule_delayed_work ( & priv - > work , CR_RECOUNT_DELAY ) ;
}
static int cr0014114_set_sync ( struct led_classdev * ldev ,
enum led_brightness brightness )
{
int ret ;
struct cr0014114_led * led = container_of ( ldev ,
struct cr0014114_led ,
ldev ) ;
2019-06-09 21:19:04 +03:00
dev_dbg ( led - > priv - > dev , " Set brightness to %d \n " , brightness ) ;
2018-04-02 15:53:50 +03:00
mutex_lock ( & led - > priv - > lock ) ;
led - > brightness = ( u8 ) brightness ;
ret = cr0014114_sync ( led - > priv ) ;
mutex_unlock ( & led - > priv - > lock ) ;
return ret ;
}
static int cr0014114_probe_dt ( struct cr0014114 * priv )
{
size_t i = 0 ;
struct cr0014114_led * led ;
struct fwnode_handle * child ;
2019-06-09 21:19:04 +03:00
struct led_init_data init_data = { } ;
2018-04-02 15:53:50 +03:00
int ret ;
device_for_each_child_node ( priv - > dev , child ) {
led = & priv - > leds [ i ] ;
fwnode_property_read_string ( child , " linux,default-trigger " ,
& led - > ldev . default_trigger ) ;
led - > priv = priv ;
led - > ldev . max_brightness = CR_MAX_BRIGHTNESS ;
led - > ldev . brightness_set_blocking = cr0014114_set_sync ;
2019-06-09 21:19:04 +03:00
init_data . fwnode = child ;
init_data . devicename = CR_DEV_NAME ;
init_data . default_label = " : " ;
ret = devm_led_classdev_register_ext ( priv - > dev , & led - > ldev ,
& init_data ) ;
2018-04-02 15:53:50 +03:00
if ( ret ) {
dev_err ( priv - > dev ,
2019-06-09 21:19:04 +03:00
" failed to register LED device, err %d " , ret ) ;
2018-04-02 15:53:50 +03:00
fwnode_handle_put ( child ) ;
return ret ;
}
i + + ;
}
return 0 ;
}
static int cr0014114_probe ( struct spi_device * spi )
{
struct cr0014114 * priv ;
size_t count ;
int ret ;
count = device_get_child_node_count ( & spi - > dev ) ;
if ( ! count ) {
dev_err ( & spi - > dev , " LEDs are not defined in device tree! " ) ;
return - ENODEV ;
}
2018-06-12 02:31:49 +03:00
priv = devm_kzalloc ( & spi - > dev , struct_size ( priv , leds , count ) ,
2018-04-02 15:53:50 +03:00
GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
priv - > buf = devm_kzalloc ( & spi - > dev , count + 2 , GFP_KERNEL ) ;
if ( ! priv - > buf )
return - ENOMEM ;
mutex_init ( & priv - > lock ) ;
INIT_DELAYED_WORK ( & priv - > work , cr0014114_recount_work ) ;
priv - > count = count ;
priv - > dev = & spi - > dev ;
priv - > spi = spi ;
priv - > delay = jiffies -
msecs_to_jiffies ( CR_FW_DELAY_MSEC ) ;
priv - > do_recount = true ;
ret = cr0014114_sync ( priv ) ;
if ( ret ) {
dev_err ( priv - > dev , " first recount failed %d \n " , ret ) ;
return ret ;
}
priv - > do_recount = true ;
ret = cr0014114_sync ( priv ) ;
if ( ret ) {
dev_err ( priv - > dev , " second recount failed %d \n " , ret ) ;
return ret ;
}
ret = cr0014114_probe_dt ( priv ) ;
if ( ret )
return ret ;
/* setup recount work to workaround buggy firmware */
schedule_delayed_work ( & priv - > work , CR_RECOUNT_DELAY ) ;
spi_set_drvdata ( spi , priv ) ;
return 0 ;
}
static int cr0014114_remove ( struct spi_device * spi )
{
struct cr0014114 * priv = spi_get_drvdata ( spi ) ;
cancel_delayed_work_sync ( & priv - > work ) ;
mutex_destroy ( & priv - > lock ) ;
return 0 ;
}
static const struct of_device_id cr0014114_dt_ids [ ] = {
{ . compatible = " crane,cr0014114 " , } ,
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , cr0014114_dt_ids ) ;
static struct spi_driver cr0014114_driver = {
. probe = cr0014114_probe ,
. remove = cr0014114_remove ,
. driver = {
. name = KBUILD_MODNAME ,
. of_match_table = cr0014114_dt_ids ,
} ,
} ;
module_spi_driver ( cr0014114_driver ) ;
MODULE_AUTHOR ( " Oleh Kravchenko <oleg@kaa.org.ua> " ) ;
MODULE_DESCRIPTION ( " cr0014114 LED driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " spi:cr0014114 " ) ;