2019-05-27 09:55:01 +03:00
// SPDX-License-Identifier: GPL-2.0-or-later
2015-05-21 20:11:10 +03:00
/*
* Driver for BCM6358 memory - mapped LEDs , based on leds - syscon . c
*
* Copyright 2015 Á lvaro Fernández Rojas < noltari @ gmail . com >
*/
# include <linux/delay.h>
# include <linux/io.h>
# include <linux/leds.h>
# include <linux/module.h>
# include <linux/of.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# define BCM6358_REG_MODE 0x0
# define BCM6358_REG_CTRL 0x4
# define BCM6358_SLED_CLKDIV_MASK 3
# define BCM6358_SLED_CLKDIV_1 0
# define BCM6358_SLED_CLKDIV_2 1
# define BCM6358_SLED_CLKDIV_4 2
# define BCM6358_SLED_CLKDIV_8 3
# define BCM6358_SLED_POLARITY BIT(2)
# define BCM6358_SLED_BUSY BIT(3)
# define BCM6358_SLED_MAX_COUNT 32
# define BCM6358_SLED_WAIT 100
/**
* struct bcm6358_led - state container for bcm6358 based LEDs
* @ cdev : LED class device for this LED
* @ mem : memory resource
* @ lock : memory lock
* @ pin : LED pin number
* @ active_low : LED is active low
*/
struct bcm6358_led {
struct led_classdev cdev ;
void __iomem * mem ;
spinlock_t * lock ;
unsigned long pin ;
bool active_low ;
} ;
static void bcm6358_led_write ( void __iomem * reg , unsigned long data )
{
2015-12-15 23:03:50 +03:00
# ifdef CONFIG_CPU_BIG_ENDIAN
2015-05-21 20:11:10 +03:00
iowrite32be ( data , reg ) ;
2015-12-15 23:03:50 +03:00
# else
writel ( data , reg ) ;
# endif
2015-05-21 20:11:10 +03:00
}
static unsigned long bcm6358_led_read ( void __iomem * reg )
{
2015-12-15 23:03:50 +03:00
# ifdef CONFIG_CPU_BIG_ENDIAN
2015-05-21 20:11:10 +03:00
return ioread32be ( reg ) ;
2015-12-15 23:03:50 +03:00
# else
return readl ( reg ) ;
# endif
2015-05-21 20:11:10 +03:00
}
static unsigned long bcm6358_led_busy ( void __iomem * mem )
{
unsigned long val ;
while ( ( val = bcm6358_led_read ( mem + BCM6358_REG_CTRL ) ) &
BCM6358_SLED_BUSY )
udelay ( BCM6358_SLED_WAIT ) ;
return val ;
}
2015-12-15 23:03:52 +03:00
static void bcm6358_led_set ( struct led_classdev * led_cdev ,
enum led_brightness value )
2015-05-21 20:11:10 +03:00
{
2015-12-15 23:03:52 +03:00
struct bcm6358_led * led =
container_of ( led_cdev , struct bcm6358_led , cdev ) ;
unsigned long flags , val ;
2015-05-21 20:11:10 +03:00
2015-12-15 23:03:52 +03:00
spin_lock_irqsave ( led - > lock , flags ) ;
2015-05-21 20:11:10 +03:00
bcm6358_led_busy ( led - > mem ) ;
val = bcm6358_led_read ( led - > mem + BCM6358_REG_MODE ) ;
if ( ( led - > active_low & & value = = LED_OFF ) | |
( ! led - > active_low & & value ! = LED_OFF ) )
val | = BIT ( led - > pin ) ;
else
val & = ~ ( BIT ( led - > pin ) ) ;
bcm6358_led_write ( led - > mem + BCM6358_REG_MODE , val ) ;
spin_unlock_irqrestore ( led - > lock , flags ) ;
}
static int bcm6358_led ( struct device * dev , struct device_node * nc , u32 reg ,
void __iomem * mem , spinlock_t * lock )
{
2020-09-18 01:32:59 +03:00
struct led_init_data init_data = { } ;
2015-05-21 20:11:10 +03:00
struct bcm6358_led * led ;
const char * state ;
int rc ;
led = devm_kzalloc ( dev , sizeof ( * led ) , GFP_KERNEL ) ;
if ( ! led )
return - ENOMEM ;
led - > pin = reg ;
led - > mem = mem ;
led - > lock = lock ;
if ( of_property_read_bool ( nc , " active-low " ) )
led - > active_low = true ;
if ( ! of_property_read_string ( nc , " default-state " , & state ) ) {
if ( ! strcmp ( state , " on " ) ) {
led - > cdev . brightness = LED_FULL ;
} else if ( ! strcmp ( state , " keep " ) ) {
unsigned long val ;
val = bcm6358_led_read ( led - > mem + BCM6358_REG_MODE ) ;
val & = BIT ( led - > pin ) ;
if ( ( led - > active_low & & ! val ) | |
( ! led - > active_low & & val ) )
led - > cdev . brightness = LED_FULL ;
else
led - > cdev . brightness = LED_OFF ;
} else {
led - > cdev . brightness = LED_OFF ;
}
} else {
led - > cdev . brightness = LED_OFF ;
}
2015-12-15 23:03:47 +03:00
bcm6358_led_set ( & led - > cdev , led - > cdev . brightness ) ;
2015-05-21 20:11:10 +03:00
led - > cdev . brightness_set = bcm6358_led_set ;
2020-09-18 01:32:59 +03:00
init_data . fwnode = of_fwnode_handle ( nc ) ;
2015-05-21 20:11:10 +03:00
2020-09-18 01:32:59 +03:00
rc = devm_led_classdev_register_ext ( dev , & led - > cdev , & init_data ) ;
2015-05-21 20:11:10 +03:00
if ( rc < 0 )
return rc ;
dev_dbg ( dev , " registered LED %s \n " , led - > cdev . name ) ;
return 0 ;
}
static int bcm6358_leds_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2020-09-18 01:32:54 +03:00
struct device_node * np = dev_of_node ( & pdev - > dev ) ;
2015-05-21 20:11:10 +03:00
struct device_node * child ;
void __iomem * mem ;
spinlock_t * lock ; /* memory lock */
unsigned long val ;
u32 clk_div ;
2019-09-20 15:44:06 +03:00
mem = devm_platform_ioremap_resource ( pdev , 0 ) ;
2015-05-21 20:11:10 +03:00
if ( IS_ERR ( mem ) )
return PTR_ERR ( mem ) ;
lock = devm_kzalloc ( dev , sizeof ( * lock ) , GFP_KERNEL ) ;
if ( ! lock )
return - ENOMEM ;
spin_lock_init ( lock ) ;
val = bcm6358_led_busy ( mem ) ;
val & = ~ ( BCM6358_SLED_POLARITY | BCM6358_SLED_CLKDIV_MASK ) ;
if ( of_property_read_bool ( np , " brcm,clk-dat-low " ) )
val | = BCM6358_SLED_POLARITY ;
of_property_read_u32 ( np , " brcm,clk-div " , & clk_div ) ;
switch ( clk_div ) {
case 8 :
val | = BCM6358_SLED_CLKDIV_8 ;
break ;
case 4 :
val | = BCM6358_SLED_CLKDIV_4 ;
break ;
case 2 :
val | = BCM6358_SLED_CLKDIV_2 ;
break ;
default :
val | = BCM6358_SLED_CLKDIV_1 ;
break ;
}
bcm6358_led_write ( mem + BCM6358_REG_CTRL , val ) ;
for_each_available_child_of_node ( np , child ) {
int rc ;
u32 reg ;
if ( of_property_read_u32 ( child , " reg " , & reg ) )
continue ;
if ( reg > = BCM6358_SLED_MAX_COUNT ) {
dev_err ( dev , " invalid LED (%u >= %d) \n " , reg ,
BCM6358_SLED_MAX_COUNT ) ;
continue ;
}
rc = bcm6358_led ( dev , child , reg , mem , lock ) ;
2015-10-24 17:42:28 +03:00
if ( rc < 0 ) {
of_node_put ( child ) ;
2015-05-21 20:11:10 +03:00
return rc ;
2015-10-24 17:42:28 +03:00
}
2015-05-21 20:11:10 +03:00
}
return 0 ;
}
static const struct of_device_id bcm6358_leds_of_match [ ] = {
{ . compatible = " brcm,bcm6358-leds " , } ,
{ } ,
} ;
2015-09-02 00:35:55 +03:00
MODULE_DEVICE_TABLE ( of , bcm6358_leds_of_match ) ;
2015-05-21 20:11:10 +03:00
static struct platform_driver bcm6358_leds_driver = {
. probe = bcm6358_leds_probe ,
. driver = {
. name = " leds-bcm6358 " ,
. of_match_table = bcm6358_leds_of_match ,
} ,
} ;
module_platform_driver ( bcm6358_leds_driver ) ;
MODULE_AUTHOR ( " Álvaro Fernández Rojas <noltari@gmail.com> " ) ;
MODULE_DESCRIPTION ( " LED driver for BCM6358 controllers " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;
MODULE_ALIAS ( " platform:leds-bcm6358 " ) ;