Linux Audio

Check our new training course

Loading...
Note: File does not exist in v3.1.
  1// SPDX-License-Identifier: GPL-2.0-or-later
  2/*
  3 * Freescale FlexTimer Module (FTM) timer driver.
  4 *
  5 * Copyright 2014 Freescale Semiconductor, Inc.
  6 */
  7
  8#include <linux/clk.h>
  9#include <linux/clockchips.h>
 10#include <linux/clocksource.h>
 11#include <linux/err.h>
 12#include <linux/interrupt.h>
 13#include <linux/io.h>
 14#include <linux/of_address.h>
 15#include <linux/of_irq.h>
 16#include <linux/sched_clock.h>
 17#include <linux/slab.h>
 18#include <linux/fsl/ftm.h>
 19
 20#define FTM_SC_CLK(c)	((c) << FTM_SC_CLK_MASK_SHIFT)
 21
 22struct ftm_clock_device {
 23	void __iomem *clksrc_base;
 24	void __iomem *clkevt_base;
 25	unsigned long periodic_cyc;
 26	unsigned long ps;
 27	bool big_endian;
 28};
 29
 30static struct ftm_clock_device *priv;
 31
 32static inline u32 ftm_readl(void __iomem *addr)
 33{
 34	if (priv->big_endian)
 35		return ioread32be(addr);
 36	else
 37		return ioread32(addr);
 38}
 39
 40static inline void ftm_writel(u32 val, void __iomem *addr)
 41{
 42	if (priv->big_endian)
 43		iowrite32be(val, addr);
 44	else
 45		iowrite32(val, addr);
 46}
 47
 48static inline void ftm_counter_enable(void __iomem *base)
 49{
 50	u32 val;
 51
 52	/* select and enable counter clock source */
 53	val = ftm_readl(base + FTM_SC);
 54	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
 55	val |= priv->ps | FTM_SC_CLK(1);
 56	ftm_writel(val, base + FTM_SC);
 57}
 58
 59static inline void ftm_counter_disable(void __iomem *base)
 60{
 61	u32 val;
 62
 63	/* disable counter clock source */
 64	val = ftm_readl(base + FTM_SC);
 65	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
 66	ftm_writel(val, base + FTM_SC);
 67}
 68
 69static inline void ftm_irq_acknowledge(void __iomem *base)
 70{
 71	u32 val;
 72
 73	val = ftm_readl(base + FTM_SC);
 74	val &= ~FTM_SC_TOF;
 75	ftm_writel(val, base + FTM_SC);
 76}
 77
 78static inline void ftm_irq_enable(void __iomem *base)
 79{
 80	u32 val;
 81
 82	val = ftm_readl(base + FTM_SC);
 83	val |= FTM_SC_TOIE;
 84	ftm_writel(val, base + FTM_SC);
 85}
 86
 87static inline void ftm_irq_disable(void __iomem *base)
 88{
 89	u32 val;
 90
 91	val = ftm_readl(base + FTM_SC);
 92	val &= ~FTM_SC_TOIE;
 93	ftm_writel(val, base + FTM_SC);
 94}
 95
 96static inline void ftm_reset_counter(void __iomem *base)
 97{
 98	/*
 99	 * The CNT register contains the FTM counter value.
100	 * Reset clears the CNT register. Writing any value to COUNT
101	 * updates the counter with its initial value, CNTIN.
102	 */
103	ftm_writel(0x00, base + FTM_CNT);
104}
105
106static u64 notrace ftm_read_sched_clock(void)
107{
108	return ftm_readl(priv->clksrc_base + FTM_CNT);
109}
110
111static int ftm_set_next_event(unsigned long delta,
112				struct clock_event_device *unused)
113{
114	/*
115	 * The CNNIN and MOD are all double buffer registers, writing
116	 * to the MOD register latches the value into a buffer. The MOD
117	 * register is updated with the value of its write buffer with
118	 * the following scenario:
119	 * a, the counter source clock is diabled.
120	 */
121	ftm_counter_disable(priv->clkevt_base);
122
123	/* Force the value of CNTIN to be loaded into the FTM counter */
124	ftm_reset_counter(priv->clkevt_base);
125
126	/*
127	 * The counter increments until the value of MOD is reached,
128	 * at which point the counter is reloaded with the value of CNTIN.
129	 * The TOF (the overflow flag) bit is set when the FTM counter
130	 * changes from MOD to CNTIN. So we should using the delta - 1.
131	 */
132	ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);
133
134	ftm_counter_enable(priv->clkevt_base);
135
136	ftm_irq_enable(priv->clkevt_base);
137
138	return 0;
139}
140
141static int ftm_set_oneshot(struct clock_event_device *evt)
142{
143	ftm_counter_disable(priv->clkevt_base);
144	return 0;
145}
146
147static int ftm_set_periodic(struct clock_event_device *evt)
148{
149	ftm_set_next_event(priv->periodic_cyc, evt);
150	return 0;
151}
152
153static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)
154{
155	struct clock_event_device *evt = dev_id;
156
157	ftm_irq_acknowledge(priv->clkevt_base);
158
159	if (likely(clockevent_state_oneshot(evt))) {
160		ftm_irq_disable(priv->clkevt_base);
161		ftm_counter_disable(priv->clkevt_base);
162	}
163
164	evt->event_handler(evt);
165
166	return IRQ_HANDLED;
167}
168
169static struct clock_event_device ftm_clockevent = {
170	.name			= "Freescale ftm timer",
171	.features		= CLOCK_EVT_FEAT_PERIODIC |
172				  CLOCK_EVT_FEAT_ONESHOT,
173	.set_state_periodic	= ftm_set_periodic,
174	.set_state_oneshot	= ftm_set_oneshot,
175	.set_next_event		= ftm_set_next_event,
176	.rating			= 300,
177};
178
179static struct irqaction ftm_timer_irq = {
180	.name		= "Freescale ftm timer",
181	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
182	.handler	= ftm_evt_interrupt,
183	.dev_id		= &ftm_clockevent,
184};
185
186static int __init ftm_clockevent_init(unsigned long freq, int irq)
187{
188	int err;
189
190	ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);
191	ftm_writel(~0u, priv->clkevt_base + FTM_MOD);
192
193	ftm_reset_counter(priv->clkevt_base);
194
195	err = setup_irq(irq, &ftm_timer_irq);
196	if (err) {
197		pr_err("ftm: setup irq failed: %d\n", err);
198		return err;
199	}
200
201	ftm_clockevent.cpumask = cpumask_of(0);
202	ftm_clockevent.irq = irq;
203
204	clockevents_config_and_register(&ftm_clockevent,
205					freq / (1 << priv->ps),
206					1, 0xffff);
207
208	ftm_counter_enable(priv->clkevt_base);
209
210	return 0;
211}
212
213static int __init ftm_clocksource_init(unsigned long freq)
214{
215	int err;
216
217	ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);
218	ftm_writel(~0u, priv->clksrc_base + FTM_MOD);
219
220	ftm_reset_counter(priv->clksrc_base);
221
222	sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));
223	err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",
224				    freq / (1 << priv->ps), 300, 16,
225				    clocksource_mmio_readl_up);
226	if (err) {
227		pr_err("ftm: init clock source mmio failed: %d\n", err);
228		return err;
229	}
230
231	ftm_counter_enable(priv->clksrc_base);
232
233	return 0;
234}
235
236static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,
237				 char *ftm_name)
238{
239	struct clk *clk;
240	int err;
241
242	clk = of_clk_get_by_name(np, cnt_name);
243	if (IS_ERR(clk)) {
244		pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk));
245		return PTR_ERR(clk);
246	}
247	err = clk_prepare_enable(clk);
248	if (err) {
249		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
250			cnt_name, err);
251		return err;
252	}
253
254	clk = of_clk_get_by_name(np, ftm_name);
255	if (IS_ERR(clk)) {
256		pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));
257		return PTR_ERR(clk);
258	}
259	err = clk_prepare_enable(clk);
260	if (err)
261		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
262			ftm_name, err);
263
264	return clk_get_rate(clk);
265}
266
267static unsigned long __init ftm_clk_init(struct device_node *np)
268{
269	long freq;
270
271	freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");
272	if (freq <= 0)
273		return 0;
274
275	freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");
276	if (freq <= 0)
277		return 0;
278
279	return freq;
280}
281
282static int __init ftm_calc_closest_round_cyc(unsigned long freq)
283{
284	priv->ps = 0;
285
286	/* The counter register is only using the lower 16 bits, and
287	 * if the 'freq' value is to big here, then the periodic_cyc
288	 * may exceed 0xFFFF.
289	 */
290	do {
291		priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,
292						HZ * (1 << priv->ps++));
293	} while (priv->periodic_cyc > 0xFFFF);
294
295	if (priv->ps > FTM_PS_MAX) {
296		pr_err("ftm: the prescaler is %lu > %d\n",
297				priv->ps, FTM_PS_MAX);
298		return -EINVAL;
299	}
300
301	return 0;
302}
303
304static int __init ftm_timer_init(struct device_node *np)
305{
306	unsigned long freq;
307	int ret, irq;
308
309	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
310	if (!priv)
311		return -ENOMEM;
312
313	ret = -ENXIO;
314	priv->clkevt_base = of_iomap(np, 0);
315	if (!priv->clkevt_base) {
316		pr_err("ftm: unable to map event timer registers\n");
317		goto err_clkevt;
318	}
319
320	priv->clksrc_base = of_iomap(np, 1);
321	if (!priv->clksrc_base) {
322		pr_err("ftm: unable to map source timer registers\n");
323		goto err_clksrc;
324	}
325
326	ret = -EINVAL;
327	irq = irq_of_parse_and_map(np, 0);
328	if (irq <= 0) {
329		pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
330		goto err;
331	}
332
333	priv->big_endian = of_property_read_bool(np, "big-endian");
334
335	freq = ftm_clk_init(np);
336	if (!freq)
337		goto err;
338
339	ret = ftm_calc_closest_round_cyc(freq);
340	if (ret)
341		goto err;
342
343	ret = ftm_clocksource_init(freq);
344	if (ret)
345		goto err;
346
347	ret = ftm_clockevent_init(freq, irq);
348	if (ret)
349		goto err;
350
351	return 0;
352
353err:
354	iounmap(priv->clksrc_base);
355err_clksrc:
356	iounmap(priv->clkevt_base);
357err_clkevt:
358	kfree(priv);
359	return ret;
360}
361TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);