2018-05-23 02:05:05 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// Copyright 2016 Freescale Semiconductor, Inc.
// Copyright 2017 NXP
2017-08-01 11:40:17 +03:00
# include <linux/clk.h>
# include <linux/clockchips.h>
# include <linux/clocksource.h>
# include <linux/delay.h>
# include <linux/interrupt.h>
# include <linux/of_address.h>
# include <linux/of_irq.h>
# include <linux/sched_clock.h>
2018-11-06 08:15:58 +03:00
# include "timer-of.h"
2018-03-28 06:22:38 +03:00
# define TPM_PARAM 0x4
# define TPM_PARAM_WIDTH_SHIFT 16
# define TPM_PARAM_WIDTH_MASK (0xff << 16)
2017-08-01 11:40:17 +03:00
# define TPM_SC 0x10
# define TPM_SC_CMOD_INC_PER_CNT (0x1 << 3)
# define TPM_SC_CMOD_DIV_DEFAULT 0x3
2018-03-28 06:22:38 +03:00
# define TPM_SC_CMOD_DIV_MAX 0x7
2018-03-28 06:22:37 +03:00
# define TPM_SC_TOF_MASK (0x1 << 7)
2017-08-01 11:40:17 +03:00
# define TPM_CNT 0x14
# define TPM_MOD 0x18
# define TPM_STATUS 0x1c
# define TPM_STATUS_CH0F BIT(0)
# define TPM_C0SC 0x20
# define TPM_C0SC_CHIE BIT(6)
# define TPM_C0SC_MODE_SHIFT 2
# define TPM_C0SC_MODE_MASK 0x3c
# define TPM_C0SC_MODE_SW_COMPARE 0x4
2018-03-28 06:22:37 +03:00
# define TPM_C0SC_CHF_MASK (0x1 << 7)
2017-08-01 11:40:17 +03:00
# define TPM_C0V 0x24
2018-03-28 06:22:38 +03:00
static int counter_width ;
2017-08-01 11:40:17 +03:00
static void __iomem * timer_base ;
static inline void tpm_timer_disable ( void )
{
unsigned int val ;
/* channel disable */
val = readl ( timer_base + TPM_C0SC ) ;
val & = ~ ( TPM_C0SC_MODE_MASK | TPM_C0SC_CHIE ) ;
writel ( val , timer_base + TPM_C0SC ) ;
}
static inline void tpm_timer_enable ( void )
{
unsigned int val ;
/* channel enabled in sw compare mode */
val = readl ( timer_base + TPM_C0SC ) ;
val | = ( TPM_C0SC_MODE_SW_COMPARE < < TPM_C0SC_MODE_SHIFT ) |
TPM_C0SC_CHIE ;
writel ( val , timer_base + TPM_C0SC ) ;
}
static inline void tpm_irq_acknowledge ( void )
{
writel ( TPM_STATUS_CH0F , timer_base + TPM_STATUS ) ;
}
static struct delay_timer tpm_delay_timer ;
static inline unsigned long tpm_read_counter ( void )
{
return readl ( timer_base + TPM_CNT ) ;
}
static unsigned long tpm_read_current_timer ( void )
{
return tpm_read_counter ( ) ;
}
static u64 notrace tpm_read_sched_clock ( void )
{
return tpm_read_counter ( ) ;
}
static int tpm_set_next_event ( unsigned long delta ,
struct clock_event_device * evt )
{
unsigned long next , now ;
next = tpm_read_counter ( ) ;
next + = delta ;
writel ( next , timer_base + TPM_C0V ) ;
now = tpm_read_counter ( ) ;
/*
* NOTE : We observed in a very small probability , the bus fabric
* contention between GPU and A7 may results a few cycles delay
* of writing CNT registers which may cause the min_delta event got
* missed , so we need add a ETIME check here in case it happened .
*/
2018-04-19 09:04:43 +03:00
return ( int ) ( next - now ) < = 0 ? - ETIME : 0 ;
2017-08-01 11:40:17 +03:00
}
static int tpm_set_state_oneshot ( struct clock_event_device * evt )
{
tpm_timer_enable ( ) ;
return 0 ;
}
static int tpm_set_state_shutdown ( struct clock_event_device * evt )
{
tpm_timer_disable ( ) ;
return 0 ;
}
static irqreturn_t tpm_timer_interrupt ( int irq , void * dev_id )
{
struct clock_event_device * evt = dev_id ;
tpm_irq_acknowledge ( ) ;
evt - > event_handler ( evt ) ;
return IRQ_HANDLED ;
}
2018-11-06 08:15:58 +03:00
static struct timer_of to_tpm = {
. flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK ,
. clkevt = {
. name = " i.MX7ULP TPM Timer " ,
. rating = 200 ,
. features = CLOCK_EVT_FEAT_ONESHOT ,
. set_state_shutdown = tpm_set_state_shutdown ,
. set_state_oneshot = tpm_set_state_oneshot ,
. set_next_event = tpm_set_next_event ,
. cpumask = cpu_possible_mask ,
} ,
. of_irq = {
. handler = tpm_timer_interrupt ,
. flags = IRQF_TIMER | IRQF_IRQPOLL ,
} ,
2018-12-07 09:05:36 +03:00
. of_clk = {
. name = " per " ,
} ,
2017-08-01 11:40:17 +03:00
} ;
2018-11-06 08:15:58 +03:00
static int __init tpm_clocksource_init ( void )
2017-08-01 11:40:17 +03:00
{
2018-11-06 08:15:58 +03:00
tpm_delay_timer . read_current_timer = & tpm_read_current_timer ;
tpm_delay_timer . freq = timer_of_rate ( & to_tpm ) > > 3 ;
register_current_timer_delay ( & tpm_delay_timer ) ;
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
sched_clock_register ( tpm_read_sched_clock , counter_width ,
timer_of_rate ( & to_tpm ) > > 3 ) ;
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
return clocksource_mmio_init ( timer_base + TPM_CNT ,
" imx-tpm " ,
timer_of_rate ( & to_tpm ) > > 3 ,
to_tpm . clkevt . rating ,
counter_width ,
clocksource_mmio_readl_up ) ;
}
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
static void __init tpm_clockevent_init ( void )
{
clockevents_config_and_register ( & to_tpm . clkevt ,
timer_of_rate ( & to_tpm ) > > 3 ,
300 ,
GENMASK ( counter_width - 1 ,
1 ) ) ;
2017-08-01 11:40:17 +03:00
}
static int __init tpm_timer_init ( struct device_node * np )
{
2018-11-06 08:15:58 +03:00
struct clk * ipg ;
int ret ;
2017-08-01 11:40:17 +03:00
ipg = of_clk_get_by_name ( np , " ipg " ) ;
2018-11-06 08:15:58 +03:00
if ( IS_ERR ( ipg ) ) {
pr_err ( " tpm: failed to get ipg clk \n " ) ;
return - ENODEV ;
2017-08-01 11:40:17 +03:00
}
/* enable clk before accessing registers */
ret = clk_prepare_enable ( ipg ) ;
if ( ret ) {
pr_err ( " tpm: ipg clock enable failed (%d) \n " , ret ) ;
2018-11-06 08:15:58 +03:00
clk_put ( ipg ) ;
return ret ;
2017-08-01 11:40:17 +03:00
}
2018-11-06 08:15:58 +03:00
ret = timer_of_init ( np , & to_tpm ) ;
if ( ret )
return ret ;
timer_base = timer_of_base ( & to_tpm ) ;
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
counter_width = ( readl ( timer_base + TPM_PARAM )
& TPM_PARAM_WIDTH_MASK ) > > TPM_PARAM_WIDTH_SHIFT ;
2018-03-28 06:22:38 +03:00
/* use rating 200 for 32-bit counter and 150 for 16-bit counter */
2018-11-06 08:15:58 +03:00
to_tpm . clkevt . rating = counter_width = = 0x20 ? 200 : 150 ;
2018-03-28 06:22:38 +03:00
2017-08-01 11:40:17 +03:00
/*
* Initialize tpm module to a known state
* 1 ) Counter disabled
* 2 ) TPM counter operates in up counting mode
* 3 ) Timer Overflow Interrupt disabled
* 4 ) Channel0 disabled
* 5 ) DMA transfers disabled
*/
2018-03-28 06:22:37 +03:00
/* make sure counter is disabled */
2017-08-01 11:40:17 +03:00
writel ( 0 , timer_base + TPM_SC ) ;
2018-03-28 06:22:37 +03:00
/* TOF is W1C */
writel ( TPM_SC_TOF_MASK , timer_base + TPM_SC ) ;
2017-08-01 11:40:17 +03:00
writel ( 0 , timer_base + TPM_CNT ) ;
2018-03-28 06:22:37 +03:00
/* CHF is W1C */
writel ( TPM_C0SC_CHF_MASK , timer_base + TPM_C0SC ) ;
2017-08-01 11:40:17 +03:00
2018-03-28 06:22:38 +03:00
/*
* increase per cnt ,
* div 8 for 32 - bit counter and div 128 for 16 - bit counter
*/
writel ( TPM_SC_CMOD_INC_PER_CNT |
( counter_width = = 0x20 ?
TPM_SC_CMOD_DIV_DEFAULT : TPM_SC_CMOD_DIV_MAX ) ,
2018-11-06 08:15:58 +03:00
timer_base + TPM_SC ) ;
2017-08-01 11:40:17 +03:00
/* set MOD register to maximum for free running mode */
2018-03-28 06:22:38 +03:00
writel ( GENMASK ( counter_width - 1 , 0 ) , timer_base + TPM_MOD ) ;
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
tpm_clockevent_init ( ) ;
2017-08-01 11:40:17 +03:00
2018-11-06 08:15:58 +03:00
return tpm_clocksource_init ( ) ;
2017-08-01 11:40:17 +03:00
}
TIMER_OF_DECLARE ( imx7ulp , " fsl,imx7ulp-tpm " , tpm_timer_init ) ;