1/*
2 * Copyright (C) Maxime Coquelin 2015
3 * Author:  Maxime Coquelin <mcoquelin.stm32@gmail.com>
4 * License terms:  GNU General Public License (GPL), version 2
5 */
6
7#include <linux/kernel.h>
8#include <linux/clocksource.h>
9#include <linux/clockchips.h>
10#include <linux/of.h>
11#include <linux/of_address.h>
12#include <linux/clk.h>
13#include <linux/bitops.h>
14
15#define SYST_CSR	0x00
16#define SYST_RVR	0x04
17#define SYST_CVR	0x08
18#define SYST_CALIB	0x0c
19
20#define SYST_CSR_ENABLE BIT(0)
21
22#define SYSTICK_LOAD_RELOAD_MASK 0x00FFFFFF
23
24static void __init system_timer_of_register(struct device_node *np)
25{
26	struct clk *clk = NULL;
27	void __iomem *base;
28	u32 rate;
29	int ret;
30
31	base = of_iomap(np, 0);
32	if (!base) {
33		pr_warn("system-timer: invalid base address\n");
34		return;
35	}
36
37	ret = of_property_read_u32(np, "clock-frequency", &rate);
38	if (ret) {
39		clk = of_clk_get(np, 0);
40		if (IS_ERR(clk))
41			goto out_unmap;
42
43		ret = clk_prepare_enable(clk);
44		if (ret)
45			goto out_clk_put;
46
47		rate = clk_get_rate(clk);
48		if (!rate)
49			goto out_clk_disable;
50	}
51
52	writel_relaxed(SYSTICK_LOAD_RELOAD_MASK, base + SYST_RVR);
53	writel_relaxed(SYST_CSR_ENABLE, base + SYST_CSR);
54
55	ret = clocksource_mmio_init(base + SYST_CVR, "arm_system_timer", rate,
56			200, 24, clocksource_mmio_readl_down);
57	if (ret) {
58		pr_err("failed to init clocksource (%d)\n", ret);
59		if (clk)
60			goto out_clk_disable;
61		else
62			goto out_unmap;
63	}
64
65	pr_info("ARM System timer initialized as clocksource\n");
66
67	return;
68
69out_clk_disable:
70	clk_disable_unprepare(clk);
71out_clk_put:
72	clk_put(clk);
73out_unmap:
74	iounmap(base);
75	pr_warn("ARM System timer register failed (%d)\n", ret);
76}
77
78CLOCKSOURCE_OF_DECLARE(arm_systick, "arm,armv7m-systick",
79			system_timer_of_register);
80