2011-11-01 04:11:55 +04:00
/*
* LED control using Renesas TPU
*
* Copyright ( C ) 2011 Magnus Damm
*
* 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
*
* 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/init.h>
# include <linux/platform_device.h>
# include <linux/spinlock.h>
# include <linux/printk.h>
# include <linux/ioport.h>
# include <linux/io.h>
# include <linux/clk.h>
# include <linux/leds.h>
2011-11-01 04:12:09 +04:00
# include <linux/platform_data/leds-renesas-tpu.h>
2011-11-01 04:11:55 +04:00
# include <linux/gpio.h>
# include <linux/err.h>
# include <linux/slab.h>
# include <linux/pm_runtime.h>
2011-11-01 04:12:06 +04:00
# include <linux/workqueue.h>
2011-11-01 04:11:55 +04:00
enum r_tpu_pin { R_TPU_PIN_UNUSED , R_TPU_PIN_GPIO , R_TPU_PIN_GPIO_FN } ;
enum r_tpu_timer { R_TPU_TIMER_UNUSED , R_TPU_TIMER_ON } ;
struct r_tpu_priv {
struct led_classdev ldev ;
void __iomem * mapbase ;
struct clk * clk ;
struct platform_device * pdev ;
enum r_tpu_pin pin_state ;
enum r_tpu_timer timer_state ;
unsigned long min_rate ;
unsigned int refresh_rate ;
2011-11-01 04:12:06 +04:00
struct work_struct work ;
enum led_brightness new_brightness ;
2011-11-01 04:11:55 +04:00
} ;
static DEFINE_SPINLOCK ( r_tpu_lock ) ;
# define TSTR -1 /* Timer start register (shared register) */
# define TCR 0 /* Timer control register (+0x00) */
# define TMDR 1 /* Timer mode register (+0x04) */
# define TIOR 2 /* Timer I/O control register (+0x08) */
# define TIER 3 /* Timer interrupt enable register (+0x0c) */
# define TSR 4 /* Timer status register (+0x10) */
# define TCNT 5 /* Timer counter (+0x14) */
# define TGRA 6 /* Timer general register A (+0x18) */
# define TGRB 7 /* Timer general register B (+0x1c) */
# define TGRC 8 /* Timer general register C (+0x20) */
# define TGRD 9 /* Timer general register D (+0x24) */
2013-05-31 16:55:04 +04:00
static inline u16 r_tpu_read ( struct r_tpu_priv * p , int reg_nr )
2011-11-01 04:11:55 +04:00
{
struct led_renesas_tpu_config * cfg = p - > pdev - > dev . platform_data ;
void __iomem * base = p - > mapbase ;
unsigned long offs = reg_nr < < 2 ;
if ( reg_nr = = TSTR )
return ioread16 ( base - cfg - > channel_offset ) ;
return ioread16 ( base + offs ) ;
}
2013-05-31 16:55:04 +04:00
static inline void r_tpu_write ( struct r_tpu_priv * p , int reg_nr , u16 value )
2011-11-01 04:11:55 +04:00
{
struct led_renesas_tpu_config * cfg = p - > pdev - > dev . platform_data ;
void __iomem * base = p - > mapbase ;
unsigned long offs = reg_nr < < 2 ;
if ( reg_nr = = TSTR ) {
iowrite16 ( value , base - cfg - > channel_offset ) ;
return ;
}
iowrite16 ( value , base + offs ) ;
}
static void r_tpu_start_stop_ch ( struct r_tpu_priv * p , int start )
{
struct led_renesas_tpu_config * cfg = p - > pdev - > dev . platform_data ;
2013-05-31 16:55:04 +04:00
unsigned long flags ;
u16 value ;
2011-11-01 04:11:55 +04:00
/* start stop register shared by multiple timer channels */
spin_lock_irqsave ( & r_tpu_lock , flags ) ;
value = r_tpu_read ( p , TSTR ) ;
if ( start )
value | = 1 < < cfg - > timer_bit ;
else
value & = ~ ( 1 < < cfg - > timer_bit ) ;
r_tpu_write ( p , TSTR , value ) ;
spin_unlock_irqrestore ( & r_tpu_lock , flags ) ;
}
static int r_tpu_enable ( struct r_tpu_priv * p , enum led_brightness brightness )
{
struct led_renesas_tpu_config * cfg = p - > pdev - > dev . platform_data ;
int prescaler [ ] = { 1 , 4 , 16 , 64 } ;
int k , ret ;
unsigned long rate , tmp ;
if ( p - > timer_state = = R_TPU_TIMER_ON )
return 0 ;
/* wake up device and enable clock */
pm_runtime_get_sync ( & p - > pdev - > dev ) ;
ret = clk_enable ( p - > clk ) ;
if ( ret ) {
dev_err ( & p - > pdev - > dev , " cannot enable clock \n " ) ;
return ret ;
}
/* make sure channel is disabled */
r_tpu_start_stop_ch ( p , 0 ) ;
/* get clock rate after enabling it */
rate = clk_get_rate ( p - > clk ) ;
/* pick the lowest acceptable rate */
2013-01-11 08:03:38 +04:00
for ( k = ARRAY_SIZE ( prescaler ) - 1 ; k > = 0 ; k - - )
if ( ( rate / prescaler [ k ] ) > = p - > min_rate )
2011-11-01 04:11:55 +04:00
break ;
2013-01-11 08:03:38 +04:00
if ( k < 0 ) {
2011-11-01 04:11:55 +04:00
dev_err ( & p - > pdev - > dev , " clock rate mismatch \n " ) ;
goto err0 ;
}
dev_dbg ( & p - > pdev - > dev , " rate = %lu, prescaler %u \n " ,
2013-01-11 08:03:38 +04:00
rate , prescaler [ k ] ) ;
2011-11-01 04:11:55 +04:00
/* clear TCNT on TGRB match, count on rising edge, set prescaler */
2013-01-11 08:03:38 +04:00
r_tpu_write ( p , TCR , 0x0040 | k ) ;
2011-11-01 04:11:55 +04:00
/* output 0 until TGRA, output 1 until TGRB */
r_tpu_write ( p , TIOR , 0x0002 ) ;
2013-01-11 08:03:38 +04:00
rate / = prescaler [ k ] * p - > refresh_rate ;
2011-11-01 04:11:55 +04:00
r_tpu_write ( p , TGRB , rate ) ;
dev_dbg ( & p - > pdev - > dev , " TRGB = 0x%04lx \n " , rate ) ;
tmp = ( cfg - > max_brightness - brightness ) * rate ;
r_tpu_write ( p , TGRA , tmp / cfg - > max_brightness ) ;
dev_dbg ( & p - > pdev - > dev , " TRGA = 0x%04lx \n " , tmp / cfg - > max_brightness ) ;
/* PWM mode */
r_tpu_write ( p , TMDR , 0x0002 ) ;
/* enable channel */
r_tpu_start_stop_ch ( p , 1 ) ;
p - > timer_state = R_TPU_TIMER_ON ;
return 0 ;
err0 :
clk_disable ( p - > clk ) ;
pm_runtime_put_sync ( & p - > pdev - > dev ) ;
return - ENOTSUPP ;
}
static void r_tpu_disable ( struct r_tpu_priv * p )
{
if ( p - > timer_state = = R_TPU_TIMER_UNUSED )
return ;
/* disable channel */
r_tpu_start_stop_ch ( p , 0 ) ;
/* stop clock and mark device as idle */
clk_disable ( p - > clk ) ;
pm_runtime_put_sync ( & p - > pdev - > dev ) ;
p - > timer_state = R_TPU_TIMER_UNUSED ;
}
static void r_tpu_set_pin ( struct r_tpu_priv * p , enum r_tpu_pin new_state ,
enum led_brightness brightness )
{
struct led_renesas_tpu_config * cfg = p - > pdev - > dev . platform_data ;
if ( p - > pin_state = = new_state ) {
if ( p - > pin_state = = R_TPU_PIN_GPIO )
gpio_set_value ( cfg - > pin_gpio , brightness ) ;
return ;
}
if ( p - > pin_state = = R_TPU_PIN_GPIO )
gpio_free ( cfg - > pin_gpio ) ;
if ( p - > pin_state = = R_TPU_PIN_GPIO_FN )
gpio_free ( cfg - > pin_gpio_fn ) ;
2012-10-23 16:18:47 +04:00
if ( new_state = = R_TPU_PIN_GPIO )
2013-03-08 06:39:04 +04:00
gpio_request_one ( cfg - > pin_gpio , ! ! brightness ?
GPIOF_OUT_INIT_HIGH : GPIOF_OUT_INIT_LOW ,
2012-10-23 16:18:47 +04:00
cfg - > name ) ;
2011-11-01 04:11:55 +04:00
if ( new_state = = R_TPU_PIN_GPIO_FN )
gpio_request ( cfg - > pin_gpio_fn , cfg - > name ) ;
p - > pin_state = new_state ;
}
2011-11-01 04:12:06 +04:00
static void r_tpu_work ( struct work_struct * work )
2011-11-01 04:11:55 +04:00
{
2011-11-01 04:12:06 +04:00
struct r_tpu_priv * p = container_of ( work , struct r_tpu_priv , work ) ;
enum led_brightness brightness = p - > new_brightness ;
2011-11-01 04:11:55 +04:00
r_tpu_disable ( p ) ;
/* off and maximum are handled as GPIO pins, in between PWM */
2011-11-01 04:12:06 +04:00
if ( ( brightness = = 0 ) | | ( brightness = = p - > ldev . max_brightness ) )
2011-11-01 04:11:55 +04:00
r_tpu_set_pin ( p , R_TPU_PIN_GPIO , brightness ) ;
else {
r_tpu_set_pin ( p , R_TPU_PIN_GPIO_FN , 0 ) ;
r_tpu_enable ( p , brightness ) ;
}
}
2011-11-01 04:12:06 +04:00
static void r_tpu_set_brightness ( struct led_classdev * ldev ,
enum led_brightness brightness )
{
struct r_tpu_priv * p = container_of ( ldev , struct r_tpu_priv , ldev ) ;
p - > new_brightness = brightness ;
schedule_work ( & p - > work ) ;
}
2012-11-19 22:23:02 +04:00
static int r_tpu_probe ( struct platform_device * pdev )
2011-11-01 04:11:55 +04:00
{
struct led_renesas_tpu_config * cfg = pdev - > dev . platform_data ;
struct r_tpu_priv * p ;
struct resource * res ;
2012-07-04 07:48:57 +04:00
int ret ;
2011-11-01 04:11:55 +04:00
if ( ! cfg ) {
dev_err ( & pdev - > dev , " missing platform data \n " ) ;
2012-08-09 01:27:58 +04:00
return - ENODEV ;
2011-11-01 04:11:55 +04:00
}
2012-07-04 07:48:57 +04:00
p = devm_kzalloc ( & pdev - > dev , sizeof ( * p ) , GFP_KERNEL ) ;
2011-11-01 04:11:55 +04:00
if ( p = = NULL ) {
dev_err ( & pdev - > dev , " failed to allocate driver data \n " ) ;
2012-07-04 07:48:57 +04:00
return - ENOMEM ;
2011-11-01 04:11:55 +04:00
}
res = platform_get_resource ( pdev , IORESOURCE_MEM , 0 ) ;
if ( ! res ) {
dev_err ( & pdev - > dev , " failed to get I/O memory \n " ) ;
2012-07-04 07:48:57 +04:00
return - ENXIO ;
2011-11-01 04:11:55 +04:00
}
/* map memory, let mapbase point to our channel */
2012-10-23 16:22:11 +04:00
p - > mapbase = devm_ioremap_nocache ( & pdev - > dev , res - > start ,
resource_size ( res ) ) ;
2011-11-01 04:11:55 +04:00
if ( p - > mapbase = = NULL ) {
dev_err ( & pdev - > dev , " failed to remap I/O memory \n " ) ;
2012-07-04 07:48:57 +04:00
return - ENXIO ;
2011-11-01 04:11:55 +04:00
}
/* get hold of clock */
2012-10-23 16:22:11 +04:00
p - > clk = devm_clk_get ( & pdev - > dev , NULL ) ;
2011-11-01 04:11:55 +04:00
if ( IS_ERR ( p - > clk ) ) {
dev_err ( & pdev - > dev , " cannot get clock \n " ) ;
2012-10-23 16:22:11 +04:00
return PTR_ERR ( p - > clk ) ;
2011-11-01 04:11:55 +04:00
}
p - > pdev = pdev ;
p - > pin_state = R_TPU_PIN_UNUSED ;
p - > timer_state = R_TPU_TIMER_UNUSED ;
p - > refresh_rate = cfg - > refresh_rate ? cfg - > refresh_rate : 100 ;
r_tpu_set_pin ( p , R_TPU_PIN_GPIO , LED_OFF ) ;
platform_set_drvdata ( pdev , p ) ;
2011-11-01 04:12:06 +04:00
INIT_WORK ( & p - > work , r_tpu_work ) ;
2011-11-01 04:11:55 +04:00
p - > ldev . name = cfg - > name ;
p - > ldev . brightness = LED_OFF ;
p - > ldev . max_brightness = cfg - > max_brightness ;
p - > ldev . brightness_set = r_tpu_set_brightness ;
p - > ldev . flags | = LED_CORE_SUSPENDRESUME ;
ret = led_classdev_register ( & pdev - > dev , & p - > ldev ) ;
if ( ret < 0 )
2012-10-23 16:22:11 +04:00
goto err0 ;
2011-11-01 04:11:55 +04:00
/* max_brightness may be updated by the LED core code */
p - > min_rate = p - > ldev . max_brightness * p - > refresh_rate ;
pm_runtime_enable ( & pdev - > dev ) ;
return 0 ;
err0 :
2012-10-23 16:22:11 +04:00
r_tpu_set_pin ( p , R_TPU_PIN_UNUSED , LED_OFF ) ;
2011-11-01 04:11:55 +04:00
return ret ;
}
2012-11-19 22:26:00 +04:00
static int r_tpu_remove ( struct platform_device * pdev )
2011-11-01 04:11:55 +04:00
{
struct r_tpu_priv * p = platform_get_drvdata ( pdev ) ;
r_tpu_set_brightness ( & p - > ldev , LED_OFF ) ;
led_classdev_unregister ( & p - > ldev ) ;
2011-11-01 04:12:06 +04:00
cancel_work_sync ( & p - > work ) ;
2011-11-01 04:11:55 +04:00
r_tpu_disable ( p ) ;
r_tpu_set_pin ( p , R_TPU_PIN_UNUSED , LED_OFF ) ;
pm_runtime_disable ( & pdev - > dev ) ;
return 0 ;
}
static struct platform_driver r_tpu_device_driver = {
. probe = r_tpu_probe ,
2012-11-19 22:20:20 +04:00
. remove = r_tpu_remove ,
2011-11-01 04:11:55 +04:00
. driver = {
. name = " leds-renesas-tpu " ,
}
} ;
2012-01-11 03:09:24 +04:00
module_platform_driver ( r_tpu_device_driver ) ;
2011-11-01 04:11:55 +04:00
MODULE_AUTHOR ( " Magnus Damm " ) ;
MODULE_DESCRIPTION ( " Renesas TPU LED Driver " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;