2014-09-29 03:50:05 +04:00
/*
* Amlogic Meson6 SoCs timer handling .
*
* Copyright ( C ) 2014 Carlo Caione < carlo @ caione . org >
*
* Based on code from Amlogic , Inc
*
* This file is licensed under the terms of the GNU General Public
* License version 2. This program is licensed " as is " without any
* warranty of any kind , whether express or implied .
*/
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/interrupt.h>
# include <linux/irq.h>
# include <linux/irqreturn.h>
# include <linux/sched_clock.h>
# include <linux/of.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# define CED_ID 0
# define CSD_ID 4
# define TIMER_ISA_MUX 0
# define TIMER_ISA_VAL(t) (((t) + 1) << 2)
# define TIMER_INPUT_BIT(t) (2 * (t))
# define TIMER_ENABLE_BIT(t) (16 + (t))
# define TIMER_PERIODIC_BIT(t) (12 + (t))
# define TIMER_CED_INPUT_MASK (3UL << TIMER_INPUT_BIT(CED_ID))
# define TIMER_CSD_INPUT_MASK (7UL << TIMER_INPUT_BIT(CSD_ID))
# define TIMER_CED_UNIT_1US 0
# define TIMER_CSD_UNIT_1US 1
static void __iomem * timer_base ;
static u64 notrace meson6_timer_sched_read ( void )
{
return ( u64 ) readl ( timer_base + TIMER_ISA_VAL ( CSD_ID ) ) ;
}
static void meson6_clkevt_time_stop ( unsigned char timer )
{
u32 val = readl ( timer_base + TIMER_ISA_MUX ) ;
writel ( val & ~ TIMER_ENABLE_BIT ( timer ) , timer_base + TIMER_ISA_MUX ) ;
}
static void meson6_clkevt_time_setup ( unsigned char timer , unsigned long delay )
{
writel ( delay , timer_base + TIMER_ISA_VAL ( timer ) ) ;
}
static void meson6_clkevt_time_start ( unsigned char timer , bool periodic )
{
u32 val = readl ( timer_base + TIMER_ISA_MUX ) ;
if ( periodic )
val | = TIMER_PERIODIC_BIT ( timer ) ;
else
val & = ~ TIMER_PERIODIC_BIT ( timer ) ;
writel ( val | TIMER_ENABLE_BIT ( timer ) , timer_base + TIMER_ISA_MUX ) ;
}
2015-06-18 13:54:23 +03:00
static int meson6_shutdown ( struct clock_event_device * evt )
2014-09-29 03:50:05 +04:00
{
2015-06-18 13:54:23 +03:00
meson6_clkevt_time_stop ( CED_ID ) ;
return 0 ;
}
static int meson6_set_oneshot ( struct clock_event_device * evt )
{
meson6_clkevt_time_stop ( CED_ID ) ;
meson6_clkevt_time_start ( CED_ID , false ) ;
return 0 ;
}
static int meson6_set_periodic ( struct clock_event_device * evt )
{
meson6_clkevt_time_stop ( CED_ID ) ;
meson6_clkevt_time_setup ( CED_ID , USEC_PER_SEC / HZ - 1 ) ;
meson6_clkevt_time_start ( CED_ID , true ) ;
return 0 ;
2014-09-29 03:50:05 +04:00
}
static int meson6_clkevt_next_event ( unsigned long evt ,
struct clock_event_device * unused )
{
meson6_clkevt_time_stop ( CED_ID ) ;
meson6_clkevt_time_setup ( CED_ID , evt ) ;
meson6_clkevt_time_start ( CED_ID , false ) ;
return 0 ;
}
static struct clock_event_device meson6_clockevent = {
2015-06-18 13:54:23 +03:00
. name = " meson6_tick " ,
. rating = 400 ,
. features = CLOCK_EVT_FEAT_PERIODIC |
CLOCK_EVT_FEAT_ONESHOT ,
. set_state_shutdown = meson6_shutdown ,
. set_state_periodic = meson6_set_periodic ,
. set_state_oneshot = meson6_set_oneshot ,
. tick_resume = meson6_shutdown ,
. set_next_event = meson6_clkevt_next_event ,
2014-09-29 03:50:05 +04:00
} ;
static irqreturn_t meson6_timer_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * evt = ( struct clock_event_device * ) dev_id ;
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
static struct irqaction meson6_timer_irq = {
. name = " meson6_timer " ,
. flags = IRQF_TIMER | IRQF_IRQPOLL ,
. handler = meson6_timer_interrupt ,
. dev_id = & meson6_clockevent ,
} ;
static void __init meson6_timer_init ( struct device_node * node )
{
u32 val ;
int ret , irq ;
timer_base = of_io_request_and_map ( node , 0 , " meson6-timer " ) ;
if ( IS_ERR ( timer_base ) )
panic ( " Can't map registers " ) ;
irq = irq_of_parse_and_map ( node , 0 ) ;
if ( irq < = 0 )
panic ( " Can't parse IRQ " ) ;
/* Set 1us for timer E */
val = readl ( timer_base + TIMER_ISA_MUX ) ;
val & = ~ TIMER_CSD_INPUT_MASK ;
val | = TIMER_CSD_UNIT_1US < < TIMER_INPUT_BIT ( CSD_ID ) ;
writel ( val , timer_base + TIMER_ISA_MUX ) ;
sched_clock_register ( meson6_timer_sched_read , 32 , USEC_PER_SEC ) ;
clocksource_mmio_init ( timer_base + TIMER_ISA_VAL ( CSD_ID ) , node - > name ,
1000 * 1000 , 300 , 32 , clocksource_mmio_readl_up ) ;
/* Timer A base 1us */
val & = ~ TIMER_CED_INPUT_MASK ;
val | = TIMER_CED_UNIT_1US < < TIMER_INPUT_BIT ( CED_ID ) ;
writel ( val , timer_base + TIMER_ISA_MUX ) ;
/* Stop the timer A */
meson6_clkevt_time_stop ( CED_ID ) ;
ret = setup_irq ( irq , & meson6_timer_irq ) ;
if ( ret )
pr_warn ( " failed to setup irq %d \n " , irq ) ;
meson6_clockevent . cpumask = cpu_possible_mask ;
meson6_clockevent . irq = irq ;
clockevents_config_and_register ( & meson6_clockevent , USEC_PER_SEC ,
1 , 0xfffe ) ;
}
CLOCKSOURCE_OF_DECLARE ( meson6 , " amlogic,meson6-timer " ,
meson6_timer_init ) ;