Commit | Line | Data |
---|---|---|
029aede7 | 1 | // SPDX-License-Identifier: GPL-2.0 |
0c1dcfd5 SH |
2 | /* |
3 | * Marvell Orion SoC timer handling. | |
4 | * | |
5 | * Sebastian Hesselbarth <sebastian.hesselbarth@gmail.com> | |
6 | * | |
0c1dcfd5 SH |
7 | * Timer 0 is used as free-running clocksource, while timer 1 is |
8 | * used as clock_event_device. | |
9 | */ | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/bitops.h> | |
13 | #include <linux/clk.h> | |
14 | #include <linux/clockchips.h> | |
9a4914ce | 15 | #include <linux/delay.h> |
0c1dcfd5 SH |
16 | #include <linux/interrupt.h> |
17 | #include <linux/of_address.h> | |
18 | #include <linux/of_irq.h> | |
19 | #include <linux/spinlock.h> | |
19b0a1e5 | 20 | #include <linux/sched_clock.h> |
0c1dcfd5 SH |
21 | |
22 | #define TIMER_CTRL 0x00 | |
23 | #define TIMER0_EN BIT(0) | |
24 | #define TIMER0_RELOAD_EN BIT(1) | |
25 | #define TIMER1_EN BIT(2) | |
26 | #define TIMER1_RELOAD_EN BIT(3) | |
27 | #define TIMER0_RELOAD 0x10 | |
28 | #define TIMER0_VAL 0x14 | |
29 | #define TIMER1_RELOAD 0x18 | |
30 | #define TIMER1_VAL 0x1c | |
31 | ||
32 | #define ORION_ONESHOT_MIN 1 | |
33 | #define ORION_ONESHOT_MAX 0xfffffffe | |
34 | ||
35 | static void __iomem *timer_base; | |
0c1dcfd5 | 36 | |
9a4914ce RK |
37 | static unsigned long notrace orion_read_timer(void) |
38 | { | |
39 | return ~readl(timer_base + TIMER0_VAL); | |
40 | } | |
41 | ||
42 | static struct delay_timer orion_delay_timer = { | |
43 | .read_current_timer = orion_read_timer, | |
44 | }; | |
45 | ||
46 | static void orion_delay_timer_init(unsigned long rate) | |
47 | { | |
48 | orion_delay_timer.freq = rate; | |
49 | register_current_timer_delay(&orion_delay_timer); | |
50 | } | |
51 | ||
0c1dcfd5 SH |
52 | /* |
53 | * Free-running clocksource handling. | |
54 | */ | |
2e8bac53 | 55 | static u64 notrace orion_read_sched_clock(void) |
0c1dcfd5 SH |
56 | { |
57 | return ~readl(timer_base + TIMER0_VAL); | |
58 | } | |
59 | ||
60 | /* | |
61 | * Clockevent handling. | |
62 | */ | |
63 | static u32 ticks_per_jiffy; | |
64 | ||
65 | static int orion_clkevt_next_event(unsigned long delta, | |
66 | struct clock_event_device *dev) | |
67 | { | |
68 | /* setup and enable one-shot timer */ | |
69 | writel(delta, timer_base + TIMER1_VAL); | |
0a54a069 EG |
70 | atomic_io_modify(timer_base + TIMER_CTRL, |
71 | TIMER1_RELOAD_EN | TIMER1_EN, TIMER1_EN); | |
0c1dcfd5 SH |
72 | |
73 | return 0; | |
74 | } | |
75 | ||
5dd2482c | 76 | static int orion_clkevt_shutdown(struct clock_event_device *dev) |
0c1dcfd5 | 77 | { |
5dd2482c VK |
78 | /* disable timer */ |
79 | atomic_io_modify(timer_base + TIMER_CTRL, | |
80 | TIMER1_RELOAD_EN | TIMER1_EN, 0); | |
81 | return 0; | |
82 | } | |
83 | ||
84 | static int orion_clkevt_set_periodic(struct clock_event_device *dev) | |
85 | { | |
86 | /* setup and enable periodic timer at 1/HZ intervals */ | |
87 | writel(ticks_per_jiffy - 1, timer_base + TIMER1_RELOAD); | |
88 | writel(ticks_per_jiffy - 1, timer_base + TIMER1_VAL); | |
89 | atomic_io_modify(timer_base + TIMER_CTRL, | |
90 | TIMER1_RELOAD_EN | TIMER1_EN, | |
91 | TIMER1_RELOAD_EN | TIMER1_EN); | |
92 | return 0; | |
0c1dcfd5 SH |
93 | } |
94 | ||
95 | static struct clock_event_device orion_clkevt = { | |
5dd2482c VK |
96 | .name = "orion_event", |
97 | .features = CLOCK_EVT_FEAT_ONESHOT | | |
98 | CLOCK_EVT_FEAT_PERIODIC, | |
99 | .shift = 32, | |
100 | .rating = 300, | |
101 | .set_next_event = orion_clkevt_next_event, | |
102 | .set_state_shutdown = orion_clkevt_shutdown, | |
103 | .set_state_periodic = orion_clkevt_set_periodic, | |
104 | .set_state_oneshot = orion_clkevt_shutdown, | |
105 | .tick_resume = orion_clkevt_shutdown, | |
0c1dcfd5 SH |
106 | }; |
107 | ||
108 | static irqreturn_t orion_clkevt_irq_handler(int irq, void *dev_id) | |
109 | { | |
110 | orion_clkevt.event_handler(&orion_clkevt); | |
111 | return IRQ_HANDLED; | |
112 | } | |
113 | ||
fbe4b356 | 114 | static int __init orion_timer_init(struct device_node *np) |
0c1dcfd5 | 115 | { |
0d9298ea | 116 | unsigned long rate; |
0c1dcfd5 | 117 | struct clk *clk; |
fbe4b356 | 118 | int irq, ret; |
0c1dcfd5 SH |
119 | |
120 | /* timer registers are shared with watchdog timer */ | |
121 | timer_base = of_iomap(np, 0); | |
fbe4b356 | 122 | if (!timer_base) { |
2a4849d2 | 123 | pr_err("%pOFn: unable to map resource\n", np); |
fbe4b356 DL |
124 | return -ENXIO; |
125 | } | |
0c1dcfd5 SH |
126 | |
127 | clk = of_clk_get(np, 0); | |
fbe4b356 | 128 | if (IS_ERR(clk)) { |
2a4849d2 | 129 | pr_err("%pOFn: unable to get clk\n", np); |
fbe4b356 DL |
130 | return PTR_ERR(clk); |
131 | } | |
132 | ||
133 | ret = clk_prepare_enable(clk); | |
134 | if (ret) { | |
ac9ce6d1 | 135 | pr_err("Failed to prepare clock\n"); |
fbe4b356 DL |
136 | return ret; |
137 | } | |
0c1dcfd5 SH |
138 | |
139 | /* we are only interested in timer1 irq */ | |
140 | irq = irq_of_parse_and_map(np, 1); | |
fbe4b356 | 141 | if (irq <= 0) { |
2a4849d2 | 142 | pr_err("%pOFn: unable to parse timer1 irq\n", np); |
c1e6cad0 YY |
143 | ret = -EINVAL; |
144 | goto out_unprep_clk; | |
fbe4b356 | 145 | } |
0c1dcfd5 | 146 | |
0d9298ea RK |
147 | rate = clk_get_rate(clk); |
148 | ||
0c1dcfd5 SH |
149 | /* setup timer0 as free-running clocksource */ |
150 | writel(~0, timer_base + TIMER0_VAL); | |
151 | writel(~0, timer_base + TIMER0_RELOAD); | |
0a54a069 EG |
152 | atomic_io_modify(timer_base + TIMER_CTRL, |
153 | TIMER0_RELOAD_EN | TIMER0_EN, | |
154 | TIMER0_RELOAD_EN | TIMER0_EN); | |
fbe4b356 | 155 | |
0d9298ea RK |
156 | ret = clocksource_mmio_init(timer_base + TIMER0_VAL, |
157 | "orion_clocksource", rate, 300, 32, | |
fbe4b356 DL |
158 | clocksource_mmio_readl_down); |
159 | if (ret) { | |
ac9ce6d1 | 160 | pr_err("Failed to initialize mmio timer\n"); |
c1e6cad0 | 161 | goto out_unprep_clk; |
fbe4b356 DL |
162 | } |
163 | ||
0d9298ea | 164 | sched_clock_register(orion_read_sched_clock, 32, rate); |
0c1dcfd5 SH |
165 | |
166 | /* setup timer1 as clockevent timer */ | |
cc2550b4 | 167 | ret = request_irq(irq, orion_clkevt_irq_handler, IRQF_TIMER, |
168 | "orion_event", NULL); | |
fbe4b356 | 169 | if (ret) { |
2a4849d2 | 170 | pr_err("%pOFn: unable to setup irq\n", np); |
c1e6cad0 | 171 | goto out_unprep_clk; |
fbe4b356 | 172 | } |
0c1dcfd5 SH |
173 | |
174 | ticks_per_jiffy = (clk_get_rate(clk) + HZ/2) / HZ; | |
175 | orion_clkevt.cpumask = cpumask_of(0); | |
176 | orion_clkevt.irq = irq; | |
0d9298ea | 177 | clockevents_config_and_register(&orion_clkevt, rate, |
0c1dcfd5 | 178 | ORION_ONESHOT_MIN, ORION_ONESHOT_MAX); |
fbe4b356 | 179 | |
9a4914ce RK |
180 | |
181 | orion_delay_timer_init(rate); | |
182 | ||
fbe4b356 | 183 | return 0; |
c1e6cad0 YY |
184 | |
185 | out_unprep_clk: | |
186 | clk_disable_unprepare(clk); | |
187 | return ret; | |
0c1dcfd5 | 188 | } |
17273395 | 189 | TIMER_OF_DECLARE(orion_timer, "marvell,orion-timer", orion_timer_init); |