2018-08-22 05:25:38 +03:00
// SPDX-License-Identifier: GPL-2.0
2017-01-27 23:02:15 +03:00
/*
* Renesas Timer Support - OSTM
*
* Copyright ( C ) 2017 Renesas Electronics America , Inc .
* Copyright ( C ) 2017 Chris Brandt
*/
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/interrupt.h>
# include <linux/sched_clock.h>
# include <linux/slab.h>
2019-10-16 17:47:46 +03:00
# include "timer-of.h"
2017-01-27 23:02:15 +03:00
/*
* The OSTM contains independent channels .
* The first OSTM channel probed will be set up as a free running
* clocksource . Additionally we will use this clocksource for the system
* schedule timer sched_clock ( ) .
*
* The second ( or more ) channel probed will be set up as an interrupt
* driven clock event .
*/
static void __iomem * system_clock ; /* For sched_clock() */
/* OSTM REGISTERS */
# define OSTM_CMP 0x000 /* RW,32 */
# define OSTM_CNT 0x004 /* R,32 */
# define OSTM_TE 0x010 /* R,8 */
# define OSTM_TS 0x014 /* W,8 */
# define OSTM_TT 0x018 /* W,8 */
# define OSTM_CTL 0x020 /* RW,8 */
# define TE 0x01
# define TS 0x01
# define TT 0x01
# define CTL_PERIODIC 0x00
# define CTL_ONESHOT 0x02
# define CTL_FREERUN 0x02
2019-10-16 17:47:46 +03:00
static void ostm_timer_stop ( struct timer_of * to )
2017-01-27 23:02:15 +03:00
{
2019-10-16 17:47:46 +03:00
if ( readb ( timer_of_base ( to ) + OSTM_TE ) & TE ) {
writeb ( TT , timer_of_base ( to ) + OSTM_TT ) ;
2017-01-27 23:02:15 +03:00
/*
* Read back the register simply to confirm the write operation
* has completed since I / O writes can sometimes get queued by
* the bus architecture .
*/
2019-10-16 17:47:46 +03:00
while ( readb ( timer_of_base ( to ) + OSTM_TE ) & TE )
2017-01-27 23:02:15 +03:00
;
}
}
2019-10-16 17:47:46 +03:00
static int __init ostm_init_clksrc ( struct timer_of * to )
2017-01-27 23:02:15 +03:00
{
2019-10-16 17:47:46 +03:00
ostm_timer_stop ( to ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
writel ( 0 , timer_of_base ( to ) + OSTM_CMP ) ;
writeb ( CTL_FREERUN , timer_of_base ( to ) + OSTM_CTL ) ;
writeb ( TS , timer_of_base ( to ) + OSTM_TS ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:47 +03:00
return clocksource_mmio_init ( timer_of_base ( to ) + OSTM_CNT ,
to - > np - > full_name , timer_of_rate ( to ) , 300 ,
32 , clocksource_mmio_readl_up ) ;
2017-01-27 23:02:15 +03:00
}
static u64 notrace ostm_read_sched_clock ( void )
{
return readl ( system_clock ) ;
}
2019-10-16 17:47:46 +03:00
static void __init ostm_init_sched_clock ( struct timer_of * to )
2017-01-27 23:02:15 +03:00
{
2019-10-16 17:47:46 +03:00
system_clock = timer_of_base ( to ) + OSTM_CNT ;
sched_clock_register ( ostm_read_sched_clock , 32 , timer_of_rate ( to ) ) ;
2017-01-27 23:02:15 +03:00
}
static int ostm_clock_event_next ( unsigned long delta ,
2019-10-16 17:47:46 +03:00
struct clock_event_device * ced )
2017-01-27 23:02:15 +03:00
{
2019-10-16 17:47:46 +03:00
struct timer_of * to = to_timer_of ( ced ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
ostm_timer_stop ( to ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
writel ( delta , timer_of_base ( to ) + OSTM_CMP ) ;
writeb ( CTL_ONESHOT , timer_of_base ( to ) + OSTM_CTL ) ;
writeb ( TS , timer_of_base ( to ) + OSTM_TS ) ;
2017-01-27 23:02:15 +03:00
return 0 ;
}
static int ostm_shutdown ( struct clock_event_device * ced )
{
2019-10-16 17:47:46 +03:00
struct timer_of * to = to_timer_of ( ced ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
ostm_timer_stop ( to ) ;
2017-01-27 23:02:15 +03:00
return 0 ;
}
static int ostm_set_periodic ( struct clock_event_device * ced )
{
2019-10-16 17:47:46 +03:00
struct timer_of * to = to_timer_of ( ced ) ;
2017-01-27 23:02:15 +03:00
if ( clockevent_state_oneshot ( ced ) | | clockevent_state_periodic ( ced ) )
2019-10-16 17:47:46 +03:00
ostm_timer_stop ( to ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
writel ( timer_of_period ( to ) - 1 , timer_of_base ( to ) + OSTM_CMP ) ;
writeb ( CTL_PERIODIC , timer_of_base ( to ) + OSTM_CTL ) ;
writeb ( TS , timer_of_base ( to ) + OSTM_TS ) ;
2017-01-27 23:02:15 +03:00
return 0 ;
}
static int ostm_set_oneshot ( struct clock_event_device * ced )
{
2019-10-16 17:47:46 +03:00
struct timer_of * to = to_timer_of ( ced ) ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
ostm_timer_stop ( to ) ;
2017-01-27 23:02:15 +03:00
return 0 ;
}
static irqreturn_t ostm_timer_interrupt ( int irq , void * dev_id )
{
2019-10-16 17:47:46 +03:00
struct clock_event_device * ced = dev_id ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
if ( clockevent_state_oneshot ( ced ) )
ostm_timer_stop ( to_timer_of ( ced ) ) ;
2017-01-27 23:02:15 +03:00
/* notify clockevent layer */
2019-10-16 17:47:46 +03:00
if ( ced - > event_handler )
ced - > event_handler ( ced ) ;
2017-01-27 23:02:15 +03:00
return IRQ_HANDLED ;
}
2019-10-16 17:47:46 +03:00
static int __init ostm_init_clkevt ( struct timer_of * to )
2017-01-27 23:02:15 +03:00
{
2019-10-16 17:47:46 +03:00
struct clock_event_device * ced = & to - > clkevt ;
2017-01-27 23:02:15 +03:00
ced - > features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_PERIODIC ;
ced - > set_state_shutdown = ostm_shutdown ;
ced - > set_state_periodic = ostm_set_periodic ;
ced - > set_state_oneshot = ostm_set_oneshot ;
ced - > set_next_event = ostm_clock_event_next ;
ced - > shift = 32 ;
ced - > rating = 300 ;
ced - > cpumask = cpumask_of ( 0 ) ;
2019-10-16 17:47:46 +03:00
clockevents_config_and_register ( ced , timer_of_rate ( to ) , 0xf ,
0xffffffff ) ;
2017-01-27 23:02:15 +03:00
return 0 ;
}
static int __init ostm_init ( struct device_node * np )
{
2019-10-16 17:47:46 +03:00
struct timer_of * to ;
int ret ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
to = kzalloc ( sizeof ( * to ) , GFP_KERNEL ) ;
if ( ! to )
return - ENOMEM ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
to - > flags = TIMER_OF_BASE | TIMER_OF_CLOCK ;
if ( system_clock ) {
/*
* clock sources don ' t use interrupts , clock events do
*/
to - > flags | = TIMER_OF_IRQ ;
to - > of_irq . flags = IRQF_TIMER | IRQF_IRQPOLL ;
to - > of_irq . handler = ostm_timer_interrupt ;
2017-01-27 23:02:15 +03:00
}
2019-10-16 17:47:46 +03:00
ret = timer_of_init ( np , to ) ;
if ( ret )
goto err_free ;
2017-01-27 23:02:15 +03:00
/*
* First probed device will be used as system clocksource . Any
* additional devices will be used as clock events .
*/
if ( ! system_clock ) {
2019-10-16 17:47:46 +03:00
ret = ostm_init_clksrc ( to ) ;
if ( ret )
goto err_cleanup ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:46 +03:00
ostm_init_sched_clock ( to ) ;
2019-10-16 17:47:47 +03:00
pr_info ( " %pOF: used for clocksource \n " , np ) ;
2017-01-27 23:02:15 +03:00
} else {
2019-10-16 17:47:46 +03:00
ret = ostm_init_clkevt ( to ) ;
if ( ret )
goto err_cleanup ;
2017-01-27 23:02:15 +03:00
2019-10-16 17:47:47 +03:00
pr_info ( " %pOF: used for clock events \n " , np ) ;
2017-01-27 23:02:15 +03:00
}
return 0 ;
2019-10-16 17:47:46 +03:00
err_cleanup :
timer_of_cleanup ( to ) ;
err_free :
kfree ( to ) ;
return ret ;
2017-01-27 23:02:15 +03:00
}
2017-05-26 17:56:11 +03:00
TIMER_OF_DECLARE ( ostm , " renesas,ostm " , ostm_init ) ;