245 lines · c
1// SPDX-License-Identifier: GPL-2.0+2//3// Copyright 2016 Freescale Semiconductor, Inc.4// Copyright 2017 NXP5 6#include <linux/clk.h>7#include <linux/clockchips.h>8#include <linux/clocksource.h>9#include <linux/delay.h>10#include <linux/interrupt.h>11#include <linux/sched_clock.h>12 13#include "timer-of.h"14 15#define TPM_PARAM 0x416#define TPM_PARAM_WIDTH_SHIFT 1617#define TPM_PARAM_WIDTH_MASK (0xff << 16)18#define TPM_SC 0x1019#define TPM_SC_CMOD_INC_PER_CNT (0x1 << 3)20#define TPM_SC_CMOD_DIV_DEFAULT 0x321#define TPM_SC_CMOD_DIV_MAX 0x722#define TPM_SC_TOF_MASK (0x1 << 7)23#define TPM_CNT 0x1424#define TPM_MOD 0x1825#define TPM_STATUS 0x1c26#define TPM_STATUS_CH0F BIT(0)27#define TPM_C0SC 0x2028#define TPM_C0SC_CHIE BIT(6)29#define TPM_C0SC_MODE_SHIFT 230#define TPM_C0SC_MODE_MASK 0x3c31#define TPM_C0SC_MODE_SW_COMPARE 0x432#define TPM_C0SC_CHF_MASK (0x1 << 7)33#define TPM_C0V 0x2434 35static int counter_width __ro_after_init;36static void __iomem *timer_base __ro_after_init;37 38static inline void tpm_timer_disable(void)39{40 unsigned int val;41 42 /* channel disable */43 val = readl(timer_base + TPM_C0SC);44 val &= ~(TPM_C0SC_MODE_MASK | TPM_C0SC_CHIE);45 writel(val, timer_base + TPM_C0SC);46}47 48static inline void tpm_timer_enable(void)49{50 unsigned int val;51 52 /* channel enabled in sw compare mode */53 val = readl(timer_base + TPM_C0SC);54 val |= (TPM_C0SC_MODE_SW_COMPARE << TPM_C0SC_MODE_SHIFT) |55 TPM_C0SC_CHIE;56 writel(val, timer_base + TPM_C0SC);57}58 59static inline void tpm_irq_acknowledge(void)60{61 writel(TPM_STATUS_CH0F, timer_base + TPM_STATUS);62}63 64static inline unsigned long tpm_read_counter(void)65{66 return readl(timer_base + TPM_CNT);67}68 69#if defined(CONFIG_ARM)70static struct delay_timer tpm_delay_timer;71 72static unsigned long tpm_read_current_timer(void)73{74 return tpm_read_counter();75}76 77static u64 notrace tpm_read_sched_clock(void)78{79 return tpm_read_counter();80}81#endif82 83static int tpm_set_next_event(unsigned long delta,84 struct clock_event_device *evt)85{86 unsigned long next, prev, now;87 88 prev = tpm_read_counter();89 next = prev + delta;90 writel(next, timer_base + TPM_C0V);91 now = tpm_read_counter();92 93 /*94 * Need to wait CNT increase at least 1 cycle to make sure95 * the C0V has been updated into HW.96 */97 if ((next & 0xffffffff) != readl(timer_base + TPM_C0V))98 while (now == tpm_read_counter())99 ;100 101 /*102 * NOTE: We observed in a very small probability, the bus fabric103 * contention between GPU and A7 may results a few cycles delay104 * of writing CNT registers which may cause the min_delta event got105 * missed, so we need add a ETIME check here in case it happened.106 */107 return (now - prev) >= delta ? -ETIME : 0;108}109 110static int tpm_set_state_oneshot(struct clock_event_device *evt)111{112 tpm_timer_enable();113 114 return 0;115}116 117static int tpm_set_state_shutdown(struct clock_event_device *evt)118{119 tpm_timer_disable();120 121 return 0;122}123 124static irqreturn_t tpm_timer_interrupt(int irq, void *dev_id)125{126 struct clock_event_device *evt = dev_id;127 128 tpm_irq_acknowledge();129 130 evt->event_handler(evt);131 132 return IRQ_HANDLED;133}134 135static struct timer_of to_tpm = {136 .flags = TIMER_OF_IRQ | TIMER_OF_BASE | TIMER_OF_CLOCK,137 .clkevt = {138 .name = "i.MX TPM Timer",139 .rating = 200,140 .features = CLOCK_EVT_FEAT_ONESHOT | CLOCK_EVT_FEAT_DYNIRQ,141 .set_state_shutdown = tpm_set_state_shutdown,142 .set_state_oneshot = tpm_set_state_oneshot,143 .set_next_event = tpm_set_next_event,144 .cpumask = cpu_possible_mask,145 },146 .of_irq = {147 .handler = tpm_timer_interrupt,148 .flags = IRQF_TIMER,149 },150 .of_clk = {151 .name = "per",152 },153};154 155static int __init tpm_clocksource_init(void)156{157#if defined(CONFIG_ARM)158 tpm_delay_timer.read_current_timer = &tpm_read_current_timer;159 tpm_delay_timer.freq = timer_of_rate(&to_tpm) >> 3;160 register_current_timer_delay(&tpm_delay_timer);161 162 sched_clock_register(tpm_read_sched_clock, counter_width,163 timer_of_rate(&to_tpm) >> 3);164#endif165 166 return clocksource_mmio_init(timer_base + TPM_CNT,167 "imx-tpm",168 timer_of_rate(&to_tpm) >> 3,169 to_tpm.clkevt.rating,170 counter_width,171 clocksource_mmio_readl_up);172}173 174static void __init tpm_clockevent_init(void)175{176 clockevents_config_and_register(&to_tpm.clkevt,177 timer_of_rate(&to_tpm) >> 3,178 300,179 GENMASK(counter_width - 1,180 1));181}182 183static int __init tpm_timer_init(struct device_node *np)184{185 struct clk *ipg;186 int ret;187 188 ipg = of_clk_get_by_name(np, "ipg");189 if (IS_ERR(ipg)) {190 pr_err("tpm: failed to get ipg clk\n");191 return -ENODEV;192 }193 /* enable clk before accessing registers */194 ret = clk_prepare_enable(ipg);195 if (ret) {196 pr_err("tpm: ipg clock enable failed (%d)\n", ret);197 clk_put(ipg);198 return ret;199 }200 201 ret = timer_of_init(np, &to_tpm);202 if (ret)203 return ret;204 205 timer_base = timer_of_base(&to_tpm);206 207 counter_width = (readl(timer_base + TPM_PARAM)208 & TPM_PARAM_WIDTH_MASK) >> TPM_PARAM_WIDTH_SHIFT;209 /* use rating 200 for 32-bit counter and 150 for 16-bit counter */210 to_tpm.clkevt.rating = counter_width == 0x20 ? 200 : 150;211 212 /*213 * Initialize tpm module to a known state214 * 1) Counter disabled215 * 2) TPM counter operates in up counting mode216 * 3) Timer Overflow Interrupt disabled217 * 4) Channel0 disabled218 * 5) DMA transfers disabled219 */220 /* make sure counter is disabled */221 writel(0, timer_base + TPM_SC);222 /* TOF is W1C */223 writel(TPM_SC_TOF_MASK, timer_base + TPM_SC);224 writel(0, timer_base + TPM_CNT);225 /* CHF is W1C */226 writel(TPM_C0SC_CHF_MASK, timer_base + TPM_C0SC);227 228 /*229 * increase per cnt,230 * div 8 for 32-bit counter and div 128 for 16-bit counter231 */232 writel(TPM_SC_CMOD_INC_PER_CNT |233 (counter_width == 0x20 ?234 TPM_SC_CMOD_DIV_DEFAULT : TPM_SC_CMOD_DIV_MAX),235 timer_base + TPM_SC);236 237 /* set MOD register to maximum for free running mode */238 writel(GENMASK(counter_width - 1, 0), timer_base + TPM_MOD);239 240 tpm_clockevent_init();241 242 return tpm_clocksource_init();243}244TIMER_OF_DECLARE(imx7ulp, "fsl,imx7ulp-tpm", tpm_timer_init);245