fsl_ftm_timer.c 8.1 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 121 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
/*
 * 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);
}

static u64 ftm_read_sched_clock(void)
{
	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 206 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 233 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 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366
};

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);
	ftm_writel(~0UL, priv->clkevt_base + FTM_MOD);

	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);
	ftm_writel(~0UL, priv->clksrc_base + FTM_MOD);

	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;
}

static void __init ftm_timer_init(struct device_node *np)
{
	unsigned long freq;
	int irq;

	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return;

	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;
	}

	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;

	if (ftm_calc_closest_round_cyc(freq))
		goto err;

	if (ftm_clocksource_init(freq))
		goto err;

	if (ftm_clockevent_init(freq, irq))
		goto err;

	return;

err:
	kfree(priv);
}
CLOCKSOURCE_OF_DECLARE(flextimer, "fsl,ftm-timer", ftm_timer_init);