2014-05-23 12:12:04 +04:00
/*
* Freescale FlexTimer Module ( FTM ) timer driver .
*
* Copyright 2014 Freescale Semiconductor , Inc .
*
* 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 , or ( at your option ) any later version .
*/
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/clocksource.h>
# include <linux/err.h>
# include <linux/interrupt.h>
# include <linux/io.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/sched_clock.h>
# include <linux/slab.h>
# define FTM_SC 0x00
# define FTM_SC_CLK_SHIFT 3
# define FTM_SC_CLK_MASK (0x3 << FTM_SC_CLK_SHIFT)
# define FTM_SC_CLK(c) ((c) << FTM_SC_CLK_SHIFT)
# define FTM_SC_PS_MASK 0x7
# define FTM_SC_TOIE BIT(6)
# define FTM_SC_TOF BIT(7)
# define FTM_CNT 0x04
# define FTM_MOD 0x08
# define FTM_CNTIN 0x4C
# define FTM_PS_MAX 7
struct ftm_clock_device {
void __iomem * clksrc_base ;
void __iomem * clkevt_base ;
unsigned long periodic_cyc ;
unsigned long ps ;
bool big_endian ;
} ;
static struct ftm_clock_device * priv ;
static inline u32 ftm_readl ( void __iomem * addr )
{
if ( priv - > big_endian )
return ioread32be ( addr ) ;
else
return ioread32 ( addr ) ;
}
static inline void ftm_writel ( u32 val , void __iomem * addr )
{
if ( priv - > big_endian )
iowrite32be ( val , addr ) ;
else
iowrite32 ( val , addr ) ;
}
static inline void ftm_counter_enable ( void __iomem * base )
{
u32 val ;
/* select and enable counter clock source */
val = ftm_readl ( base + FTM_SC ) ;
val & = ~ ( FTM_SC_PS_MASK | FTM_SC_CLK_MASK ) ;
val | = priv - > ps | FTM_SC_CLK ( 1 ) ;
ftm_writel ( val , base + FTM_SC ) ;
}
static inline void ftm_counter_disable ( void __iomem * base )
{
u32 val ;
/* disable counter clock source */
val = ftm_readl ( base + FTM_SC ) ;
val & = ~ ( FTM_SC_PS_MASK | FTM_SC_CLK_MASK ) ;
ftm_writel ( val , base + FTM_SC ) ;
}
static inline void ftm_irq_acknowledge ( void __iomem * base )
{
u32 val ;
val = ftm_readl ( base + FTM_SC ) ;
val & = ~ FTM_SC_TOF ;
ftm_writel ( val , base + FTM_SC ) ;
}
static inline void ftm_irq_enable ( void __iomem * base )
{
u32 val ;
val = ftm_readl ( base + FTM_SC ) ;
val | = FTM_SC_TOIE ;
ftm_writel ( val , base + FTM_SC ) ;
}
static inline void ftm_irq_disable ( void __iomem * base )
{
u32 val ;
val = ftm_readl ( base + FTM_SC ) ;
val & = ~ FTM_SC_TOIE ;
ftm_writel ( val , base + FTM_SC ) ;
}
static inline void ftm_reset_counter ( void __iomem * base )
{
/*
* The CNT register contains the FTM counter value .
* Reset clears the CNT register . Writing any value to COUNT
* updates the counter with its initial value , CNTIN .
*/
ftm_writel ( 0x00 , base + FTM_CNT ) ;
}
2015-10-20 11:02:40 +03:00
static u64 notrace ftm_read_sched_clock ( void )
2014-05-23 12:12:04 +04:00
{
return ftm_readl ( priv - > clksrc_base + FTM_CNT ) ;
}
static int ftm_set_next_event ( unsigned long delta ,
struct clock_event_device * unused )
{
/*
* The CNNIN and MOD are all double buffer registers , writing
* to the MOD register latches the value into a buffer . The MOD
* register is updated with the value of its write buffer with
* the following scenario :
* a , the counter source clock is diabled .
*/
ftm_counter_disable ( priv - > clkevt_base ) ;
/* Force the value of CNTIN to be loaded into the FTM counter */
ftm_reset_counter ( priv - > clkevt_base ) ;
/*
* The counter increments until the value of MOD is reached ,
* at which point the counter is reloaded with the value of CNTIN .
* The TOF ( the overflow flag ) bit is set when the FTM counter
* changes from MOD to CNTIN . So we should using the delta - 1.
*/
ftm_writel ( delta - 1 , priv - > clkevt_base + FTM_MOD ) ;
ftm_counter_enable ( priv - > clkevt_base ) ;
ftm_irq_enable ( priv - > clkevt_base ) ;
return 0 ;
}
2015-06-18 13:54:21 +03:00
static int ftm_set_oneshot ( struct clock_event_device * evt )
2014-05-23 12:12:04 +04:00
{
2015-06-18 13:54:21 +03:00
ftm_counter_disable ( priv - > clkevt_base ) ;
return 0 ;
}
static int ftm_set_periodic ( struct clock_event_device * evt )
{
ftm_set_next_event ( priv - > periodic_cyc , evt ) ;
return 0 ;
2014-05-23 12:12:04 +04:00
}
static irqreturn_t ftm_evt_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * evt = dev_id ;
ftm_irq_acknowledge ( priv - > clkevt_base ) ;
2015-06-18 13:54:21 +03:00
if ( likely ( clockevent_state_oneshot ( evt ) ) ) {
2014-05-23 12:12:04 +04:00
ftm_irq_disable ( priv - > clkevt_base ) ;
ftm_counter_disable ( priv - > clkevt_base ) ;
}
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
static struct clock_event_device ftm_clockevent = {
2015-06-18 13:54:21 +03:00
. name = " Freescale ftm timer " ,
. features = CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_ONESHOT ,
. set_state_periodic = ftm_set_periodic ,
. set_state_oneshot = ftm_set_oneshot ,
. set_next_event = ftm_set_next_event ,
. rating = 300 ,
2014-05-23 12:12:04 +04:00
} ;
static struct irqaction ftm_timer_irq = {
. name = " Freescale ftm timer " ,
. flags = IRQF_TIMER | IRQF_IRQPOLL ,
. handler = ftm_evt_interrupt ,
. dev_id = & ftm_clockevent ,
} ;
static int __init ftm_clockevent_init ( unsigned long freq , int irq )
{
int err ;
ftm_writel ( 0x00 , priv - > clkevt_base + FTM_CNTIN ) ;
2015-11-16 19:34:50 +03:00
ftm_writel ( ~ 0u , priv - > clkevt_base + FTM_MOD ) ;
2014-05-23 12:12:04 +04:00
ftm_reset_counter ( priv - > clkevt_base ) ;
err = setup_irq ( irq , & ftm_timer_irq ) ;
if ( err ) {
pr_err ( " ftm: setup irq failed: %d \n " , err ) ;
return err ;
}
ftm_clockevent . cpumask = cpumask_of ( 0 ) ;
ftm_clockevent . irq = irq ;
clockevents_config_and_register ( & ftm_clockevent ,
freq / ( 1 < < priv - > ps ) ,
1 , 0xffff ) ;
ftm_counter_enable ( priv - > clkevt_base ) ;
return 0 ;
}
static int __init ftm_clocksource_init ( unsigned long freq )
{
int err ;
ftm_writel ( 0x00 , priv - > clksrc_base + FTM_CNTIN ) ;
2015-11-16 19:34:50 +03:00
ftm_writel ( ~ 0u , priv - > clksrc_base + FTM_MOD ) ;
2014-05-23 12:12:04 +04:00
ftm_reset_counter ( priv - > clksrc_base ) ;
sched_clock_register ( ftm_read_sched_clock , 16 , freq / ( 1 < < priv - > ps ) ) ;
err = clocksource_mmio_init ( priv - > clksrc_base + FTM_CNT , " fsl-ftm " ,
freq / ( 1 < < priv - > ps ) , 300 , 16 ,
clocksource_mmio_readl_up ) ;
if ( err ) {
pr_err ( " ftm: init clock source mmio failed: %d \n " , err ) ;
return err ;
}
ftm_counter_enable ( priv - > clksrc_base ) ;
return 0 ;
}
static int __init __ftm_clk_init ( struct device_node * np , char * cnt_name ,
char * ftm_name )
{
struct clk * clk ;
int err ;
clk = of_clk_get_by_name ( np , cnt_name ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " ftm: Cannot get \" %s \" : %ld \n " , cnt_name , PTR_ERR ( clk ) ) ;
return PTR_ERR ( clk ) ;
}
err = clk_prepare_enable ( clk ) ;
if ( err ) {
pr_err ( " ftm: clock failed to prepare+enable \" %s \" : %d \n " ,
cnt_name , err ) ;
return err ;
}
clk = of_clk_get_by_name ( np , ftm_name ) ;
if ( IS_ERR ( clk ) ) {
pr_err ( " ftm: Cannot get \" %s \" : %ld \n " , ftm_name , PTR_ERR ( clk ) ) ;
return PTR_ERR ( clk ) ;
}
err = clk_prepare_enable ( clk ) ;
if ( err )
pr_err ( " ftm: clock failed to prepare+enable \" %s \" : %d \n " ,
ftm_name , err ) ;
return clk_get_rate ( clk ) ;
}
static unsigned long __init ftm_clk_init ( struct device_node * np )
{
2018-02-26 14:36:14 +03:00
long freq ;
2014-05-23 12:12:04 +04:00
freq = __ftm_clk_init ( np , " ftm-evt-counter-en " , " ftm-evt " ) ;
if ( freq < = 0 )
return 0 ;
freq = __ftm_clk_init ( np , " ftm-src-counter-en " , " ftm-src " ) ;
if ( freq < = 0 )
return 0 ;
return freq ;
}
static int __init ftm_calc_closest_round_cyc ( unsigned long freq )
{
priv - > ps = 0 ;
/* The counter register is only using the lower 16 bits, and
* if the ' freq ' value is to big here , then the periodic_cyc
* may exceed 0xFFFF .
*/
do {
priv - > periodic_cyc = DIV_ROUND_CLOSEST ( freq ,
HZ * ( 1 < < priv - > ps + + ) ) ;
} while ( priv - > periodic_cyc > 0xFFFF ) ;
if ( priv - > ps > FTM_PS_MAX ) {
pr_err ( " ftm: the prescaler is %lu > %d \n " ,
priv - > ps , FTM_PS_MAX ) ;
return - EINVAL ;
}
return 0 ;
}
2016-06-03 16:45:27 +03:00
static int __init ftm_timer_init ( struct device_node * np )
2014-05-23 12:12:04 +04:00
{
unsigned long freq ;
2016-06-03 16:45:27 +03:00
int ret , irq ;
2014-05-23 12:12:04 +04:00
priv = kzalloc ( sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
2016-06-03 16:45:27 +03:00
return - ENOMEM ;
2014-05-23 12:12:04 +04:00
2016-06-03 16:45:27 +03:00
ret = - ENXIO ;
2014-05-23 12:12:04 +04:00
priv - > clkevt_base = of_iomap ( np , 0 ) ;
if ( ! priv - > clkevt_base ) {
pr_err ( " ftm: unable to map event timer registers \n " ) ;
2017-06-20 14:03:30 +03:00
goto err_clkevt ;
2014-05-23 12:12:04 +04:00
}
priv - > clksrc_base = of_iomap ( np , 1 ) ;
if ( ! priv - > clksrc_base ) {
pr_err ( " ftm: unable to map source timer registers \n " ) ;
2017-06-20 14:03:30 +03:00
goto err_clksrc ;
2014-05-23 12:12:04 +04:00
}
2016-06-03 16:45:27 +03:00
ret = - EINVAL ;
2014-05-23 12:12:04 +04:00
irq = irq_of_parse_and_map ( np , 0 ) ;
if ( irq < = 0 ) {
pr_err ( " ftm: unable to get IRQ from DT, %d \n " , irq ) ;
goto err ;
}
priv - > big_endian = of_property_read_bool ( np , " big-endian " ) ;
freq = ftm_clk_init ( np ) ;
if ( ! freq )
goto err ;
2016-06-03 16:45:27 +03:00
ret = ftm_calc_closest_round_cyc ( freq ) ;
if ( ret )
2014-05-23 12:12:04 +04:00
goto err ;
2016-06-03 16:45:27 +03:00
ret = ftm_clocksource_init ( freq ) ;
if ( ret )
2014-05-23 12:12:04 +04:00
goto err ;
2016-06-03 16:45:27 +03:00
ret = ftm_clockevent_init ( freq , irq ) ;
if ( ret )
2014-05-23 12:12:04 +04:00
goto err ;
2016-06-03 16:45:27 +03:00
return 0 ;
2014-05-23 12:12:04 +04:00
err :
2017-06-20 14:03:30 +03:00
iounmap ( priv - > clksrc_base ) ;
err_clksrc :
iounmap ( priv - > clkevt_base ) ;
err_clkevt :
2014-05-23 12:12:04 +04:00
kfree ( priv ) ;
2016-06-03 16:45:27 +03:00
return ret ;
2014-05-23 12:12:04 +04:00
}
2017-05-26 17:56:11 +03:00
TIMER_OF_DECLARE ( flextimer , " fsl,ftm-timer " , ftm_timer_init ) ;