Commit | Line | Data |
---|---|---|
caab277b | 1 | // SPDX-License-Identifier: GPL-2.0-only |
af75655c | 2 | /* |
cfda5901 | 3 | * Copyright (C) 2012 Altera Corporation |
af75655c JI |
4 | * Copyright (c) 2011 Picochip Ltd., Jamie Iles |
5 | * | |
cfda5901 | 6 | * Modified from mach-picoxcell/time.c |
af75655c | 7 | */ |
9115df89 | 8 | #include <linux/delay.h> |
af75655c JI |
9 | #include <linux/dw_apb_timer.h> |
10 | #include <linux/of.h> | |
11 | #include <linux/of_address.h> | |
12 | #include <linux/of_irq.h> | |
a8b447f2 | 13 | #include <linux/clk.h> |
1f174a1a | 14 | #include <linux/reset.h> |
38ff87f7 | 15 | #include <linux/sched_clock.h> |
af75655c | 16 | |
5d9814df | 17 | static int __init timer_get_base_and_rate(struct device_node *np, |
af75655c JI |
18 | void __iomem **base, u32 *rate) |
19 | { | |
a8b447f2 HS |
20 | struct clk *timer_clk; |
21 | struct clk *pclk; | |
1f174a1a | 22 | struct reset_control *rstc; |
5d9814df | 23 | int ret; |
a8b447f2 | 24 | |
af75655c JI |
25 | *base = of_iomap(np, 0); |
26 | ||
27 | if (!*base) | |
2a4849d2 | 28 | panic("Unable to map regs for %pOFn", np); |
af75655c | 29 | |
1f174a1a DN |
30 | /* |
31 | * Reset the timer if the reset control is available, wiping | |
32 | * out the state the firmware may have left it | |
33 | */ | |
34 | rstc = of_reset_control_get(np, NULL); | |
35 | if (!IS_ERR(rstc)) { | |
36 | reset_control_assert(rstc); | |
37 | reset_control_deassert(rstc); | |
38 | } | |
39 | ||
a8b447f2 | 40 | /* |
4bf07f65 | 41 | * Not all implementations use a peripheral clock, so don't panic |
a8b447f2 HS |
42 | * if it's not present |
43 | */ | |
44 | pclk = of_clk_get_by_name(np, "pclk"); | |
45 | if (!IS_ERR(pclk)) | |
46 | if (clk_prepare_enable(pclk)) | |
2a4849d2 RH |
47 | pr_warn("pclk for %pOFn is present, but could not be activated\n", |
48 | np); | |
a8b447f2 | 49 | |
5d9814df DN |
50 | if (!of_property_read_u32(np, "clock-freq", rate) && |
51 | !of_property_read_u32(np, "clock-frequency", rate)) | |
52 | return 0; | |
53 | ||
a8b447f2 | 54 | timer_clk = of_clk_get_by_name(np, "timer"); |
397dc6f7 DN |
55 | if (IS_ERR(timer_clk)) { |
56 | ret = PTR_ERR(timer_clk); | |
57 | goto out_pclk_disable; | |
58 | } | |
a8b447f2 | 59 | |
5d9814df DN |
60 | ret = clk_prepare_enable(timer_clk); |
61 | if (ret) | |
397dc6f7 | 62 | goto out_timer_clk_put; |
5d9814df DN |
63 | |
64 | *rate = clk_get_rate(timer_clk); | |
397dc6f7 DN |
65 | if (!(*rate)) { |
66 | ret = -EINVAL; | |
67 | goto out_timer_clk_disable; | |
68 | } | |
a8b447f2 | 69 | |
5d9814df | 70 | return 0; |
397dc6f7 DN |
71 | |
72 | out_timer_clk_disable: | |
73 | clk_disable_unprepare(timer_clk); | |
74 | out_timer_clk_put: | |
75 | clk_put(timer_clk); | |
76 | out_pclk_disable: | |
77 | if (!IS_ERR(pclk)) { | |
78 | clk_disable_unprepare(pclk); | |
79 | clk_put(pclk); | |
80 | } | |
81 | iounmap(*base); | |
82 | return ret; | |
af75655c JI |
83 | } |
84 | ||
5d9814df | 85 | static int __init add_clockevent(struct device_node *event_timer) |
af75655c JI |
86 | { |
87 | void __iomem *iobase; | |
88 | struct dw_apb_clock_event_device *ced; | |
89 | u32 irq, rate; | |
5d9814df | 90 | int ret = 0; |
af75655c JI |
91 | |
92 | irq = irq_of_parse_and_map(event_timer, 0); | |
1a33bd2b | 93 | if (irq == 0) |
af75655c JI |
94 | panic("No IRQ for clock event timer"); |
95 | ||
5d9814df DN |
96 | ret = timer_get_base_and_rate(event_timer, &iobase, &rate); |
97 | if (ret) | |
98 | return ret; | |
af75655c | 99 | |
65e0f876 | 100 | ced = dw_apb_clockevent_init(-1, event_timer->name, 300, iobase, irq, |
af75655c JI |
101 | rate); |
102 | if (!ced) | |
5d9814df | 103 | return -EINVAL; |
af75655c JI |
104 | |
105 | dw_apb_clockevent_register(ced); | |
5d9814df DN |
106 | |
107 | return 0; | |
af75655c JI |
108 | } |
109 | ||
a1198f83 HS |
110 | static void __iomem *sched_io_base; |
111 | static u32 sched_rate; | |
112 | ||
5d9814df | 113 | static int __init add_clocksource(struct device_node *source_timer) |
af75655c JI |
114 | { |
115 | void __iomem *iobase; | |
116 | struct dw_apb_clocksource *cs; | |
117 | u32 rate; | |
5d9814df | 118 | int ret; |
af75655c | 119 | |
5d9814df DN |
120 | ret = timer_get_base_and_rate(source_timer, &iobase, &rate); |
121 | if (ret) | |
122 | return ret; | |
af75655c JI |
123 | |
124 | cs = dw_apb_clocksource_init(300, source_timer->name, iobase, rate); | |
125 | if (!cs) | |
5d9814df | 126 | return -EINVAL; |
af75655c JI |
127 | |
128 | dw_apb_clocksource_start(cs); | |
129 | dw_apb_clocksource_register(cs); | |
af75655c | 130 | |
a1198f83 HS |
131 | /* |
132 | * Fallback to use the clocksource as sched_clock if no separate | |
133 | * timer is found. sched_io_base then points to the current_value | |
134 | * register of the clocksource timer. | |
135 | */ | |
136 | sched_io_base = iobase + 0x04; | |
137 | sched_rate = rate; | |
5d9814df DN |
138 | |
139 | return 0; | |
a1198f83 | 140 | } |
af75655c | 141 | |
0d24d1f2 | 142 | static u64 notrace read_sched_clock(void) |
af75655c | 143 | { |
3a10013b | 144 | return ~readl_relaxed(sched_io_base); |
af75655c JI |
145 | } |
146 | ||
cfda5901 | 147 | static const struct of_device_id sptimer_ids[] __initconst = { |
af75655c JI |
148 | { .compatible = "picochip,pc3x2-rtc" }, |
149 | { /* Sentinel */ }, | |
150 | }; | |
151 | ||
1cf0203a | 152 | static void __init init_sched_clock(void) |
af75655c JI |
153 | { |
154 | struct device_node *sched_timer; | |
af75655c | 155 | |
cfda5901 | 156 | sched_timer = of_find_matching_node(NULL, sptimer_ids); |
a1198f83 HS |
157 | if (sched_timer) { |
158 | timer_get_base_and_rate(sched_timer, &sched_io_base, | |
159 | &sched_rate); | |
160 | of_node_put(sched_timer); | |
161 | } | |
af75655c | 162 | |
fa8296ae | 163 | sched_clock_register(read_sched_clock, 32, sched_rate); |
af75655c JI |
164 | } |
165 | ||
9115df89 JZ |
166 | #ifdef CONFIG_ARM |
167 | static unsigned long dw_apb_delay_timer_read(void) | |
168 | { | |
169 | return ~readl_relaxed(sched_io_base); | |
170 | } | |
171 | ||
172 | static struct delay_timer dw_apb_delay_timer = { | |
173 | .read_current_timer = dw_apb_delay_timer_read, | |
174 | }; | |
175 | #endif | |
176 | ||
10021488 | 177 | static int num_called; |
2e1773f8 | 178 | static int __init dw_apb_timer_init(struct device_node *timer) |
af75655c | 179 | { |
5d9814df DN |
180 | int ret = 0; |
181 | ||
10021488 | 182 | switch (num_called) { |
10021488 HS |
183 | case 1: |
184 | pr_debug("%s: found clocksource timer\n", __func__); | |
5d9814df DN |
185 | ret = add_clocksource(timer); |
186 | if (ret) | |
187 | return ret; | |
10021488 | 188 | init_sched_clock(); |
9115df89 JZ |
189 | #ifdef CONFIG_ARM |
190 | dw_apb_delay_timer.freq = sched_rate; | |
191 | register_current_timer_delay(&dw_apb_delay_timer); | |
192 | #endif | |
10021488 HS |
193 | break; |
194 | default: | |
6d2e16a3 | 195 | pr_debug("%s: found clockevent timer\n", __func__); |
5d9814df DN |
196 | ret = add_clockevent(timer); |
197 | if (ret) | |
198 | return ret; | |
10021488 HS |
199 | break; |
200 | } | |
af75655c | 201 | |
10021488 | 202 | num_called++; |
2e1773f8 DL |
203 | |
204 | return 0; | |
af75655c | 205 | } |
17273395 DL |
206 | TIMER_OF_DECLARE(pc3x2_timer, "picochip,pc3x2-timer", dw_apb_timer_init); |
207 | TIMER_OF_DECLARE(apb_timer_osc, "snps,dw-apb-timer-osc", dw_apb_timer_init); | |
208 | TIMER_OF_DECLARE(apb_timer_sp, "snps,dw-apb-timer-sp", dw_apb_timer_init); | |
209 | TIMER_OF_DECLARE(apb_timer, "snps,dw-apb-timer", dw_apb_timer_init); |