2018-11-02 19:51:30 +03:00
// SPDX-License-Identifier: GPL-2.0
// Copyright (C) 2018 Hangzhou C-SKY Microsystems co.,ltd.
# include <linux/init.h>
# include <linux/interrupt.h>
# include <linux/sched_clock.h>
# include "timer-of.h"
# define CLKSRC_OFFSET 0x40
# define TIMER_STATUS 0x00
# define TIMER_VALUE 0x04
# define TIMER_CONTRL 0x10
# define TIMER_CONFIG 0x20
# define TIMER_DIV 0x24
# define TIMER_INI 0x28
# define GX6605S_STATUS_CLR BIT(0)
# define GX6605S_CONTRL_RST BIT(0)
# define GX6605S_CONTRL_START BIT(1)
# define GX6605S_CONFIG_EN BIT(0)
# define GX6605S_CONFIG_IRQ_EN BIT(1)
static irqreturn_t gx6605s_timer_interrupt ( int irq , void * dev )
{
struct clock_event_device * ce = dev ;
void __iomem * base = timer_of_base ( to_timer_of ( ce ) ) ;
writel_relaxed ( GX6605S_STATUS_CLR , base + TIMER_STATUS ) ;
2020-08-18 10:31:17 +03:00
writel_relaxed ( 0 , base + TIMER_INI ) ;
2018-11-02 19:51:30 +03:00
ce - > event_handler ( ce ) ;
return IRQ_HANDLED ;
}
static int gx6605s_timer_set_oneshot ( struct clock_event_device * ce )
{
void __iomem * base = timer_of_base ( to_timer_of ( ce ) ) ;
/* reset and stop counter */
writel_relaxed ( GX6605S_CONTRL_RST , base + TIMER_CONTRL ) ;
/* enable with irq and start */
writel_relaxed ( GX6605S_CONFIG_EN | GX6605S_CONFIG_IRQ_EN ,
base + TIMER_CONFIG ) ;
return 0 ;
}
static int gx6605s_timer_set_next_event ( unsigned long delta ,
struct clock_event_device * ce )
{
void __iomem * base = timer_of_base ( to_timer_of ( ce ) ) ;
/* use reset to pause timer */
writel_relaxed ( GX6605S_CONTRL_RST , base + TIMER_CONTRL ) ;
/* config next timeout value */
writel_relaxed ( ULONG_MAX - delta , base + TIMER_INI ) ;
writel_relaxed ( GX6605S_CONTRL_START , base + TIMER_CONTRL ) ;
return 0 ;
}
static int gx6605s_timer_shutdown ( struct clock_event_device * ce )
{
void __iomem * base = timer_of_base ( to_timer_of ( ce ) ) ;
writel_relaxed ( 0 , base + TIMER_CONTRL ) ;
writel_relaxed ( 0 , base + TIMER_CONFIG ) ;
return 0 ;
}
static struct timer_of to = {
. flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK ,
. clkevt = {
. rating = 300 ,
. features = CLOCK_EVT_FEAT_DYNIRQ |
CLOCK_EVT_FEAT_ONESHOT ,
. set_state_shutdown = gx6605s_timer_shutdown ,
. set_state_oneshot = gx6605s_timer_set_oneshot ,
. set_next_event = gx6605s_timer_set_next_event ,
. cpumask = cpu_possible_mask ,
} ,
. of_irq = {
. handler = gx6605s_timer_interrupt ,
. flags = IRQF_TIMER | IRQF_IRQPOLL ,
} ,
} ;
static u64 notrace gx6605s_sched_clock_read ( void )
{
void __iomem * base ;
base = timer_of_base ( & to ) + CLKSRC_OFFSET ;
return ( u64 ) readl_relaxed ( base + TIMER_VALUE ) ;
}
static void gx6605s_clkevt_init ( void __iomem * base )
{
writel_relaxed ( 0 , base + TIMER_DIV ) ;
writel_relaxed ( 0 , base + TIMER_CONFIG ) ;
clockevents_config_and_register ( & to . clkevt , timer_of_rate ( & to ) , 2 ,
ULONG_MAX ) ;
}
static int gx6605s_clksrc_init ( void __iomem * base )
{
writel_relaxed ( 0 , base + TIMER_DIV ) ;
writel_relaxed ( 0 , base + TIMER_INI ) ;
writel_relaxed ( GX6605S_CONTRL_RST , base + TIMER_CONTRL ) ;
writel_relaxed ( GX6605S_CONFIG_EN , base + TIMER_CONFIG ) ;
writel_relaxed ( GX6605S_CONTRL_START , base + TIMER_CONTRL ) ;
sched_clock_register ( gx6605s_sched_clock_read , 32 , timer_of_rate ( & to ) ) ;
return clocksource_mmio_init ( base + TIMER_VALUE , " gx6605s " ,
timer_of_rate ( & to ) , 200 , 32 , clocksource_mmio_readl_up ) ;
}
static int __init gx6605s_timer_init ( struct device_node * np )
{
int ret ;
/*
* The timer driver is for nationalchip gx6605s SOC and there are two
* same timer in gx6605s . We use one for clkevt and another for clksrc .
*
* The timer is mmio map to access , so we need give mmio address in dts .
*
* It provides a 32 bit countup timer and interrupt will be caused by
* count - overflow .
* So we need set - next - event by ULONG_MAX - delta in TIMER_INI reg .
*
* The counter at 0x0 offset is clock event .
* The counter at 0x40 offset is clock source .
* They are the same in hardware , just different used by driver .
*/
ret = timer_of_init ( np , & to ) ;
if ( ret )
return ret ;
gx6605s_clkevt_init ( timer_of_base ( & to ) ) ;
return gx6605s_clksrc_init ( timer_of_base ( & to ) + CLKSRC_OFFSET ) ;
}
TIMER_OF_DECLARE ( csky_gx6605s_timer , " csky,gx6605s-timer " , gx6605s_timer_init ) ;