fsl_ftm_timer.c 8.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
/*
 * Freescale FlexTimer Module (FTM) timer driver.
 *
 * Copyright 2014 Freescale Semiconductor, Inc.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 */

#include <linux/clk.h>
#include <linux/clockchips.h>
#include <linux/clocksource.h>
#include <linux/err.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/sched_clock.h>
#include <linux/slab.h>

#define FTM_SC		0x00
#define FTM_SC_CLK_SHIFT	3
#define FTM_SC_CLK_MASK	(0x3 << FTM_SC_CLK_SHIFT)
#define FTM_SC_CLK(c)	((c) << FTM_SC_CLK_SHIFT)
#define FTM_SC_PS_MASK	0x7
#define FTM_SC_TOIE	BIT(6)
#define FTM_SC_TOF	BIT(7)

#define FTM_CNT		0x04
#define FTM_MOD		0x08
#define FTM_CNTIN	0x4C

#define FTM_PS_MAX	7

struct ftm_clock_device {
	void __iomem *clksrc_base;
	void __iomem *clkevt_base;
	unsigned long periodic_cyc;
	unsigned long ps;
	bool big_endian;
};

static struct ftm_clock_device *priv;

static inline u32 ftm_readl(void __iomem *addr)
{
	if (priv->big_endian)
		return ioread32be(addr);
	else
		return ioread32(addr);
}

static inline void ftm_writel(u32 val, void __iomem *addr)
{
	if (priv->big_endian)
		iowrite32be(val, addr);
	else
		iowrite32(val, addr);
}

static inline void ftm_counter_enable(void __iomem *base)
{
	u32 val;

	/* select and enable counter clock source */
	val = ftm_readl(base + FTM_SC);
	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
	val |= priv->ps | FTM_SC_CLK(1);
	ftm_writel(val, base + FTM_SC);
}

static inline void ftm_counter_disable(void __iomem *base)
{
	u32 val;

	/* disable counter clock source */
	val = ftm_readl(base + FTM_SC);
	val &= ~(FTM_SC_PS_MASK | FTM_SC_CLK_MASK);
	ftm_writel(val, base + FTM_SC);
}

static inline void ftm_irq_acknowledge(void __iomem *base)
{
	u32 val;

	val = ftm_readl(base + FTM_SC);
	val &= ~FTM_SC_TOF;
	ftm_writel(val, base + FTM_SC);
}

static inline void ftm_irq_enable(void __iomem *base)
{
	u32 val;

	val = ftm_readl(base + FTM_SC);
	val |= FTM_SC_TOIE;
	ftm_writel(val, base + FTM_SC);
}

static inline void ftm_irq_disable(void __iomem *base)
{
	u32 val;

	val = ftm_readl(base + FTM_SC);
	val &= ~FTM_SC_TOIE;
	ftm_writel(val, base + FTM_SC);
}

static inline void ftm_reset_counter(void __iomem *base)
{
	/*
	 * The CNT register contains the FTM counter value.
	 * Reset clears the CNT register. Writing any value to COUNT
	 * updates the counter with its initial value, CNTIN.
	 */
	ftm_writel(0x00, base + FTM_CNT);
}

121
static u64 notrace ftm_read_sched_clock(void)
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
{
	return ftm_readl(priv->clksrc_base + FTM_CNT);
}

static int ftm_set_next_event(unsigned long delta,
				struct clock_event_device *unused)
{
	/*
	 * The CNNIN and MOD are all double buffer registers, writing
	 * to the MOD register latches the value into a buffer. The MOD
	 * register is updated with the value of its write buffer with
	 * the following scenario:
	 * a, the counter source clock is diabled.
	 */
	ftm_counter_disable(priv->clkevt_base);

	/* Force the value of CNTIN to be loaded into the FTM counter */
	ftm_reset_counter(priv->clkevt_base);

	/*
	 * The counter increments until the value of MOD is reached,
	 * at which point the counter is reloaded with the value of CNTIN.
	 * The TOF (the overflow flag) bit is set when the FTM counter
	 * changes from MOD to CNTIN. So we should using the delta - 1.
	 */
	ftm_writel(delta - 1, priv->clkevt_base + FTM_MOD);

	ftm_counter_enable(priv->clkevt_base);

	ftm_irq_enable(priv->clkevt_base);

	return 0;
}

156
static int ftm_set_oneshot(struct clock_event_device *evt)
157
{
158 159 160 161 162 163 164 165
	ftm_counter_disable(priv->clkevt_base);
	return 0;
}

static int ftm_set_periodic(struct clock_event_device *evt)
{
	ftm_set_next_event(priv->periodic_cyc, evt);
	return 0;
166 167 168 169 170 171 172 173
}

static irqreturn_t ftm_evt_interrupt(int irq, void *dev_id)
{
	struct clock_event_device *evt = dev_id;

	ftm_irq_acknowledge(priv->clkevt_base);

174
	if (likely(clockevent_state_oneshot(evt))) {
175 176 177 178 179 180 181 182 183 184
		ftm_irq_disable(priv->clkevt_base);
		ftm_counter_disable(priv->clkevt_base);
	}

	evt->event_handler(evt);

	return IRQ_HANDLED;
}

static struct clock_event_device ftm_clockevent = {
185 186 187 188 189 190 191
	.name			= "Freescale ftm timer",
	.features		= CLOCK_EVT_FEAT_PERIODIC |
				  CLOCK_EVT_FEAT_ONESHOT,
	.set_state_periodic	= ftm_set_periodic,
	.set_state_oneshot	= ftm_set_oneshot,
	.set_next_event		= ftm_set_next_event,
	.rating			= 300,
192 193 194 195 196 197 198 199 200 201 202 203 204 205
};

static struct irqaction ftm_timer_irq = {
	.name		= "Freescale ftm timer",
	.flags		= IRQF_TIMER | IRQF_IRQPOLL,
	.handler	= ftm_evt_interrupt,
	.dev_id		= &ftm_clockevent,
};

static int __init ftm_clockevent_init(unsigned long freq, int irq)
{
	int err;

	ftm_writel(0x00, priv->clkevt_base + FTM_CNTIN);
206
	ftm_writel(~0u, priv->clkevt_base + FTM_MOD);
207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232

	ftm_reset_counter(priv->clkevt_base);

	err = setup_irq(irq, &ftm_timer_irq);
	if (err) {
		pr_err("ftm: setup irq failed: %d\n", err);
		return err;
	}

	ftm_clockevent.cpumask = cpumask_of(0);
	ftm_clockevent.irq = irq;

	clockevents_config_and_register(&ftm_clockevent,
					freq / (1 << priv->ps),
					1, 0xffff);

	ftm_counter_enable(priv->clkevt_base);

	return 0;
}

static int __init ftm_clocksource_init(unsigned long freq)
{
	int err;

	ftm_writel(0x00, priv->clksrc_base + FTM_CNTIN);
233
	ftm_writel(~0u, priv->clksrc_base + FTM_MOD);
234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318

	ftm_reset_counter(priv->clksrc_base);

	sched_clock_register(ftm_read_sched_clock, 16, freq / (1 << priv->ps));
	err = clocksource_mmio_init(priv->clksrc_base + FTM_CNT, "fsl-ftm",
				    freq / (1 << priv->ps), 300, 16,
				    clocksource_mmio_readl_up);
	if (err) {
		pr_err("ftm: init clock source mmio failed: %d\n", err);
		return err;
	}

	ftm_counter_enable(priv->clksrc_base);

	return 0;
}

static int __init __ftm_clk_init(struct device_node *np, char *cnt_name,
				 char *ftm_name)
{
	struct clk *clk;
	int err;

	clk = of_clk_get_by_name(np, cnt_name);
	if (IS_ERR(clk)) {
		pr_err("ftm: Cannot get \"%s\": %ld\n", cnt_name, PTR_ERR(clk));
		return PTR_ERR(clk);
	}
	err = clk_prepare_enable(clk);
	if (err) {
		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
			cnt_name, err);
		return err;
	}

	clk = of_clk_get_by_name(np, ftm_name);
	if (IS_ERR(clk)) {
		pr_err("ftm: Cannot get \"%s\": %ld\n", ftm_name, PTR_ERR(clk));
		return PTR_ERR(clk);
	}
	err = clk_prepare_enable(clk);
	if (err)
		pr_err("ftm: clock failed to prepare+enable \"%s\": %d\n",
			ftm_name, err);

	return clk_get_rate(clk);
}

static unsigned long __init ftm_clk_init(struct device_node *np)
{
	unsigned long freq;

	freq = __ftm_clk_init(np, "ftm-evt-counter-en", "ftm-evt");
	if (freq <= 0)
		return 0;

	freq = __ftm_clk_init(np, "ftm-src-counter-en", "ftm-src");
	if (freq <= 0)
		return 0;

	return freq;
}

static int __init ftm_calc_closest_round_cyc(unsigned long freq)
{
	priv->ps = 0;

	/* The counter register is only using the lower 16 bits, and
	 * if the 'freq' value is to big here, then the periodic_cyc
	 * may exceed 0xFFFF.
	 */
	do {
		priv->periodic_cyc = DIV_ROUND_CLOSEST(freq,
						HZ * (1 << priv->ps++));
	} while (priv->periodic_cyc > 0xFFFF);

	if (priv->ps > FTM_PS_MAX) {
		pr_err("ftm: the prescaler is %lu > %d\n",
				priv->ps, FTM_PS_MAX);
		return -EINVAL;
	}

	return 0;
}

319
static int __init ftm_timer_init(struct device_node *np)
320 321
{
	unsigned long freq;
322
	int ret, irq;
323 324 325

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
326
		return -ENOMEM;
327

328
	ret = -ENXIO;
329 330 331 332 333 334 335 336 337 338 339 340
	priv->clkevt_base = of_iomap(np, 0);
	if (!priv->clkevt_base) {
		pr_err("ftm: unable to map event timer registers\n");
		goto err;
	}

	priv->clksrc_base = of_iomap(np, 1);
	if (!priv->clksrc_base) {
		pr_err("ftm: unable to map source timer registers\n");
		goto err;
	}

341
	ret = -EINVAL;
342 343 344 345 346 347 348 349 350 351 352 353
	irq = irq_of_parse_and_map(np, 0);
	if (irq <= 0) {
		pr_err("ftm: unable to get IRQ from DT, %d\n", irq);
		goto err;
	}

	priv->big_endian = of_property_read_bool(np, "big-endian");

	freq = ftm_clk_init(np);
	if (!freq)
		goto err;

354 355
	ret = ftm_calc_closest_round_cyc(freq);
	if (ret)
356 357
		goto err;

358 359
	ret = ftm_clocksource_init(freq);
	if (ret)
360 361
		goto err;

362 363
	ret = ftm_clockevent_init(freq, irq);
	if (ret)
364 365
		goto err;

366
	return 0;
367 368 369

err:
	kfree(priv);
370
	return ret;
371
}
372
TIMER_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);