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