1// SPDX-License-Identifier: GPL-2.0+
2/*
3 * (C) Copyright 2016 Nexell
4 * Hyunseok, Jung <hsjung@nexell.co.kr>
5 */
6
7#include <common.h>
8#include <log.h>
9
10#include <asm/io.h>
11#include <asm/arch/nexell.h>
12#include <asm/arch/clk.h>
13#if defined(CONFIG_ARCH_S5P4418)
14#include <asm/arch/reset.h>
15#endif
16
17#if (CONFIG_TIMER_SYS_TICK_CH > 3)
18#error Not support timer channel. Please use "0~3" channels.
19#endif
20
21/* global variables to save timer count
22 *
23 * Section ".data" must be used because BSS is not available before relocation,
24 * in board_init_f(), respectively! I.e. global variables can not be used!
25 */
26static unsigned long timestamp __section(".data");
27static unsigned long lastdec __section(".data");
28static int	timerinit __section(".data");
29
30/* macro to hw timer tick config */
31static long	TIMER_FREQ  = 1000000;
32static long	TIMER_HZ    = 1000000 / CONFIG_SYS_HZ;
33static long	TIMER_COUNT = 0xFFFFFFFF;
34
35#define	REG_TCFG0			(0x00)
36#define	REG_TCFG1			(0x04)
37#define	REG_TCON			(0x08)
38#define	REG_TCNTB0			(0x0C)
39#define	REG_TCMPB0			(0x10)
40#define	REG_TCNT0			(0x14)
41#define	REG_CSTAT			(0x44)
42
43#define	TCON_BIT_AUTO			(1 << 3)
44#define	TCON_BIT_INVT			(1 << 2)
45#define	TCON_BIT_UP			(1 << 1)
46#define	TCON_BIT_RUN			(1 << 0)
47#define TCFG0_BIT_CH(ch)		((ch) == 0 || (ch) == 1 ? 0 : 8)
48#define TCFG1_BIT_CH(ch)		((ch) * 4)
49#define TCON_BIT_CH(ch)			((ch) ? (ch) * 4  + 4 : 0)
50#define TINT_CH(ch)			(ch)
51#define TINT_CSTAT_BIT_CH(ch)		((ch) + 5)
52#define	TINT_CSTAT_MASK			(0x1F)
53#define TIMER_TCNT_OFFS			(0xC)
54
55void reset_timer_masked(void);
56unsigned long get_timer_masked(void);
57
58/*
59 * Timer HW
60 */
61static inline void timer_clock(void __iomem *base, int ch, int mux, int scl)
62{
63	u32 val = readl(base + REG_TCFG0) & ~(0xFF << TCFG0_BIT_CH(ch));
64
65	writel(val | ((scl - 1) << TCFG0_BIT_CH(ch)), base + REG_TCFG0);
66	val = readl(base + REG_TCFG1) & ~(0xF << TCFG1_BIT_CH(ch));
67	writel(val | (mux << TCFG1_BIT_CH(ch)), base + REG_TCFG1);
68}
69
70static inline void timer_count(void __iomem *base, int ch, unsigned int cnt)
71{
72	writel((cnt - 1), base + REG_TCNTB0 + (TIMER_TCNT_OFFS * ch));
73	writel((cnt - 1), base + REG_TCMPB0 + (TIMER_TCNT_OFFS * ch));
74}
75
76static inline void timer_start(void __iomem *base, int ch)
77{
78	int on = 0;
79	u32 val = readl(base + REG_CSTAT) & ~(TINT_CSTAT_MASK << 5 | 0x1 << ch);
80
81	writel(val | (0x1 << TINT_CSTAT_BIT_CH(ch) | on << ch),
82	       base + REG_CSTAT);
83	val = readl(base + REG_TCON) & ~(0xE << TCON_BIT_CH(ch));
84	writel(val | (TCON_BIT_UP << TCON_BIT_CH(ch)), base + REG_TCON);
85
86	val &= ~(TCON_BIT_UP << TCON_BIT_CH(ch));
87	val |= ((TCON_BIT_AUTO | TCON_BIT_RUN) << TCON_BIT_CH(ch));
88	writel(val, base + REG_TCON);
89	dmb();
90}
91
92static inline void timer_stop(void __iomem *base, int ch)
93{
94	int on = 0;
95	u32 val = readl(base + REG_CSTAT) & ~(TINT_CSTAT_MASK << 5 | 0x1 << ch);
96
97	writel(val | (0x1 << TINT_CSTAT_BIT_CH(ch) | on << ch),
98	       base + REG_CSTAT);
99	val = readl(base + REG_TCON) & ~(TCON_BIT_RUN << TCON_BIT_CH(ch));
100	writel(val, base + REG_TCON);
101}
102
103static inline unsigned long timer_read(void __iomem *base, int ch)
104{
105	unsigned long ret;
106
107	ret = TIMER_COUNT - readl(base + REG_TCNT0 + (TIMER_TCNT_OFFS * ch));
108	return ret;
109}
110
111int timer_init(void)
112{
113	struct clk *clk = NULL;
114	char name[16] = "pclk";
115	int ch = CONFIG_TIMER_SYS_TICK_CH;
116	unsigned long rate, tclk = 0;
117	unsigned long mout, thz, cmp = -1UL;
118	int tcnt, tscl = 0, tmux = 0;
119	int mux = 0, scl = 0;
120	void __iomem *base = (void __iomem *)PHY_BASEADDR_TIMER;
121
122	if (timerinit)
123		return 0;
124
125	/* get with PCLK */
126	clk  = clk_get(name);
127	rate = clk_get_rate(clk);
128	for (mux = 0; mux < 5; mux++) {
129		mout = rate / (1 << mux), scl = mout / TIMER_FREQ,
130		thz = mout / scl;
131		if (!(mout % TIMER_FREQ) && 256 > scl) {
132			tclk = thz, tmux = mux, tscl = scl;
133			break;
134		}
135		if (scl > 256)
136			continue;
137		if (abs(thz - TIMER_FREQ) >= cmp)
138			continue;
139		tclk = thz, tmux = mux, tscl = scl;
140		cmp = abs(thz - TIMER_FREQ);
141	}
142	tcnt = tclk;	/* Timer Count := 1 Mhz counting */
143
144	TIMER_FREQ = tcnt;	/* Timer Count := 1 Mhz counting */
145	TIMER_HZ = TIMER_FREQ / CONFIG_SYS_HZ;
146	tcnt = TIMER_COUNT == 0xFFFFFFFF ? TIMER_COUNT + 1 : tcnt;
147
148	timer_stop(base, ch);
149	timer_clock(base, ch, tmux, tscl);
150	timer_count(base, ch, tcnt);
151	timer_start(base, ch);
152
153	reset_timer_masked();
154	timerinit = 1;
155
156	return 0;
157}
158
159void reset_timer(void)
160{
161	reset_timer_masked();
162}
163
164unsigned long get_timer(unsigned long base)
165{
166	long ret;
167	unsigned long time = get_timer_masked();
168	unsigned long hz = TIMER_HZ;
169
170	ret = time / hz - base;
171	return ret;
172}
173
174void set_timer(unsigned long t)
175{
176	timestamp = (unsigned long)t;
177}
178
179void reset_timer_masked(void)
180{
181	void __iomem *base = (void __iomem *)PHY_BASEADDR_TIMER;
182	int ch = CONFIG_TIMER_SYS_TICK_CH;
183
184	/* reset time */
185	/* capure current decrementer value time */
186	lastdec = timer_read(base, ch);
187	/* start "advancing" time stamp from 0 */
188	timestamp = 0;
189}
190
191unsigned long get_timer_masked(void)
192{
193	void __iomem *base = (void __iomem *)PHY_BASEADDR_TIMER;
194	int ch = CONFIG_TIMER_SYS_TICK_CH;
195
196	unsigned long now = timer_read(base, ch); /* current tick value */
197
198	if (now >= lastdec) {			  /* normal mode (non roll) */
199		/* move stamp fordward with absolute diff ticks */
200		timestamp += now - lastdec;
201	} else {
202		/* we have overflow of the count down timer */
203		/* nts = ts + ld + (TLV - now)
204		 * ts=old stamp, ld=time that passed before passing through -1
205		 * (TLV-now) amount of time after passing though -1
206		 * nts = new "advancing time stamp"...
207		 * it could also roll and cause problems.
208		 */
209		timestamp += now + TIMER_COUNT - lastdec;
210	}
211	/* save last */
212	lastdec = now;
213
214	debug("now=%lu, last=%lu, timestamp=%lu\n", now, lastdec, timestamp);
215	return (unsigned long)timestamp;
216}
217
218void __udelay(unsigned long usec)
219{
220	unsigned long tmo, tmp;
221
222	debug("+udelay=%ld\n", usec);
223
224	if (!timerinit)
225		timer_init();
226
227	/* if "big" number, spread normalization to seconds */
228	if (usec >= 1000) {
229		/* start to normalize for usec to ticks per sec */
230		tmo  = usec / 1000;
231		/* find number of "ticks" to wait to achieve target */
232		tmo *= TIMER_FREQ;
233		/* finish normalize. */
234		tmo /= 1000;
235	/* else small number, don't kill it prior to HZ multiply */
236	} else {
237		tmo = usec * TIMER_FREQ;
238		tmo /= (1000 * 1000);
239	}
240
241	tmp = get_timer_masked();	/* get current timestamp */
242	debug("A. tmo=%ld, tmp=%ld\n", tmo, tmp);
243
244	/* if setting this fordward will roll time stamp */
245	if (tmp > (tmo + tmp + 1))
246		/* reset "advancing" timestamp to 0, set lastdec value */
247		reset_timer_masked();
248	else
249		/* set advancing stamp wake up time */
250		tmo += tmp;
251
252	debug("B. tmo=%ld, tmp=%ld\n", tmo, tmp);
253
254	/* loop till event */
255	do {
256		tmp = get_timer_masked();
257	} while (tmo > tmp);
258	debug("-udelay=%ld\n", usec);
259}
260
261void udelay_masked(unsigned long usec)
262{
263	unsigned long tmo, endtime;
264	signed long diff;
265
266	/* if "big" number, spread normalization to seconds */
267	if (usec >= 1000) {
268		/* start to normalize for usec to ticks per sec */
269		tmo = usec / 1000;
270		/* find number of "ticks" to wait to achieve target */
271		tmo *= TIMER_FREQ;
272		/* finish normalize. */
273		tmo /= 1000;
274	} else { /* else small number, don't kill it prior to HZ multiply */
275		tmo = usec * TIMER_FREQ;
276		tmo /= (1000 * 1000);
277	}
278
279	endtime = get_timer_masked() + tmo;
280
281	do {
282		unsigned long now = get_timer_masked();
283
284		diff = endtime - now;
285	} while (diff >= 0);
286}
287
288unsigned long long get_ticks(void)
289{
290	return get_timer_masked();
291}
292
293#if defined(CONFIG_ARCH_S5P4418)
294ulong get_tbclk(void)
295{
296	ulong  tbclk = TIMER_FREQ;
297	return tbclk;
298}
299#endif
300