blob: 6e46781bc9acff9e8fd4f4bfcff27d5d7d7b0eb3 [file] [log] [blame]
Thomas Gleixnerd2912cb2019-06-04 10:11:33 +02001// SPDX-License-Identifier: GPL-2.0-only
Tomasz Figaf1189982013-04-20 23:22:13 +02002/*
3 * Copyright (c) 2011 Samsung Electronics Co., Ltd.
4 * http://www.samsung.com/
5 *
6 * samsung - Common hr-timer support (s3c and s5p)
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -04007 */
Tomasz Figaf1189982013-04-20 23:22:13 +02008
9#include <linux/interrupt.h>
10#include <linux/irq.h>
11#include <linux/err.h>
12#include <linux/clk.h>
13#include <linux/clockchips.h>
14#include <linux/list.h>
15#include <linux/module.h>
16#include <linux/of.h>
17#include <linux/of_address.h>
18#include <linux/of_irq.h>
19#include <linux/platform_device.h>
20#include <linux/slab.h>
Stephen Boyd38ff87f2013-06-01 23:39:40 -070021#include <linux/sched_clock.h>
Tomasz Figaf1189982013-04-20 23:22:13 +020022
23#include <clocksource/samsung_pwm.h>
24
Tomasz Figaf1189982013-04-20 23:22:13 +020025/*
26 * Clocksource driver
27 */
28
29#define REG_TCFG0 0x00
30#define REG_TCFG1 0x04
31#define REG_TCON 0x08
32#define REG_TINT_CSTAT 0x44
33
34#define REG_TCNTB(chan) (0x0c + 12 * (chan))
35#define REG_TCMPB(chan) (0x10 + 12 * (chan))
36
37#define TCFG0_PRESCALER_MASK 0xff
38#define TCFG0_PRESCALER1_SHIFT 8
39
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -040040#define TCFG1_SHIFT(x) ((x) * 4)
41#define TCFG1_MUX_MASK 0xf
Tomasz Figaf1189982013-04-20 23:22:13 +020042
Tomasz Figaceea1242013-06-17 02:10:24 +020043/*
44 * Each channel occupies 4 bits in TCON register, but there is a gap of 4
45 * bits (one channel) after channel 0, so channels have different numbering
46 * when accessing TCON register.
47 *
48 * In addition, the location of autoreload bit for channel 4 (TCON channel 5)
49 * in its set of bits is 2 as opposed to 3 for other channels.
50 */
Tomasz Figaf1189982013-04-20 23:22:13 +020051#define TCON_START(chan) (1 << (4 * (chan) + 0))
52#define TCON_MANUALUPDATE(chan) (1 << (4 * (chan) + 1))
53#define TCON_INVERT(chan) (1 << (4 * (chan) + 2))
Tomasz Figaceea1242013-06-17 02:10:24 +020054#define _TCON_AUTORELOAD(chan) (1 << (4 * (chan) + 3))
55#define _TCON_AUTORELOAD4(chan) (1 << (4 * (chan) + 2))
56#define TCON_AUTORELOAD(chan) \
57 ((chan < 5) ? _TCON_AUTORELOAD(chan) : _TCON_AUTORELOAD4(chan))
Tomasz Figaf1189982013-04-20 23:22:13 +020058
Tomasz Figa7aac4822013-04-23 17:46:24 +020059DEFINE_SPINLOCK(samsung_pwm_lock);
60EXPORT_SYMBOL(samsung_pwm_lock);
61
Tomasz Figa030c2a12013-04-23 17:46:25 +020062struct samsung_pwm_clocksource {
63 void __iomem *base;
Krzysztof Kozlowskib4318ce2021-05-06 16:27:28 -040064 const void __iomem *source_reg;
Tomasz Figa030c2a12013-04-23 17:46:25 +020065 unsigned int irq[SAMSUNG_PWM_NUM];
66 struct samsung_pwm_variant variant;
67
68 struct clk *timerclk;
69
Tomasz Figaf1189982013-04-20 23:22:13 +020070 unsigned int event_id;
71 unsigned int source_id;
72 unsigned int tcnt_max;
73 unsigned int tscaler_div;
74 unsigned int tdiv;
Tomasz Figa030c2a12013-04-23 17:46:25 +020075
76 unsigned long clock_count_per_tick;
Tomasz Figaf1189982013-04-20 23:22:13 +020077};
78
Tomasz Figa030c2a12013-04-23 17:46:25 +020079static struct samsung_pwm_clocksource pwm;
Tomasz Figaf1189982013-04-20 23:22:13 +020080
Tomasz Figa030c2a12013-04-23 17:46:25 +020081static void samsung_timer_set_prescale(unsigned int channel, u16 prescale)
Tomasz Figaf1189982013-04-20 23:22:13 +020082{
83 unsigned long flags;
84 u8 shift = 0;
85 u32 reg;
86
87 if (channel >= 2)
88 shift = TCFG0_PRESCALER1_SHIFT;
89
Tomasz Figa7aac4822013-04-23 17:46:24 +020090 spin_lock_irqsave(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +020091
Tomasz Figa030c2a12013-04-23 17:46:25 +020092 reg = readl(pwm.base + REG_TCFG0);
Tomasz Figaf1189982013-04-20 23:22:13 +020093 reg &= ~(TCFG0_PRESCALER_MASK << shift);
94 reg |= (prescale - 1) << shift;
Tomasz Figa030c2a12013-04-23 17:46:25 +020095 writel(reg, pwm.base + REG_TCFG0);
Tomasz Figaf1189982013-04-20 23:22:13 +020096
Tomasz Figa7aac4822013-04-23 17:46:24 +020097 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +020098}
99
Tomasz Figa030c2a12013-04-23 17:46:25 +0200100static void samsung_timer_set_divisor(unsigned int channel, u8 divisor)
Tomasz Figaf1189982013-04-20 23:22:13 +0200101{
102 u8 shift = TCFG1_SHIFT(channel);
103 unsigned long flags;
104 u32 reg;
105 u8 bits;
106
Tomasz Figa030c2a12013-04-23 17:46:25 +0200107 bits = (fls(divisor) - 1) - pwm.variant.div_base;
Tomasz Figaf1189982013-04-20 23:22:13 +0200108
Tomasz Figa7aac4822013-04-23 17:46:24 +0200109 spin_lock_irqsave(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200110
Tomasz Figa030c2a12013-04-23 17:46:25 +0200111 reg = readl(pwm.base + REG_TCFG1);
Tomasz Figaf1189982013-04-20 23:22:13 +0200112 reg &= ~(TCFG1_MUX_MASK << shift);
113 reg |= bits << shift;
Tomasz Figa030c2a12013-04-23 17:46:25 +0200114 writel(reg, pwm.base + REG_TCFG1);
Tomasz Figaf1189982013-04-20 23:22:13 +0200115
Tomasz Figa7aac4822013-04-23 17:46:24 +0200116 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200117}
118
119static void samsung_time_stop(unsigned int channel)
120{
121 unsigned long tcon;
122 unsigned long flags;
123
124 if (channel > 0)
125 ++channel;
126
Tomasz Figa7aac4822013-04-23 17:46:24 +0200127 spin_lock_irqsave(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200128
Matthew Leach7cc06172016-06-16 15:51:29 +0200129 tcon = readl_relaxed(pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200130 tcon &= ~TCON_START(channel);
Matthew Leach7cc06172016-06-16 15:51:29 +0200131 writel_relaxed(tcon, pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200132
Tomasz Figa7aac4822013-04-23 17:46:24 +0200133 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200134}
135
136static void samsung_time_setup(unsigned int channel, unsigned long tcnt)
137{
138 unsigned long tcon;
139 unsigned long flags;
140 unsigned int tcon_chan = channel;
141
142 if (tcon_chan > 0)
143 ++tcon_chan;
144
Tomasz Figa7aac4822013-04-23 17:46:24 +0200145 spin_lock_irqsave(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200146
Matthew Leach7cc06172016-06-16 15:51:29 +0200147 tcon = readl_relaxed(pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200148
Tomasz Figaf1189982013-04-20 23:22:13 +0200149 tcon &= ~(TCON_START(tcon_chan) | TCON_AUTORELOAD(tcon_chan));
150 tcon |= TCON_MANUALUPDATE(tcon_chan);
151
Matthew Leach7cc06172016-06-16 15:51:29 +0200152 writel_relaxed(tcnt, pwm.base + REG_TCNTB(channel));
153 writel_relaxed(tcnt, pwm.base + REG_TCMPB(channel));
154 writel_relaxed(tcon, pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200155
Tomasz Figa7aac4822013-04-23 17:46:24 +0200156 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200157}
158
159static void samsung_time_start(unsigned int channel, bool periodic)
160{
161 unsigned long tcon;
162 unsigned long flags;
163
164 if (channel > 0)
165 ++channel;
166
Tomasz Figa7aac4822013-04-23 17:46:24 +0200167 spin_lock_irqsave(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200168
Matthew Leach7cc06172016-06-16 15:51:29 +0200169 tcon = readl_relaxed(pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200170
171 tcon &= ~TCON_MANUALUPDATE(channel);
172 tcon |= TCON_START(channel);
173
174 if (periodic)
175 tcon |= TCON_AUTORELOAD(channel);
176 else
177 tcon &= ~TCON_AUTORELOAD(channel);
178
Matthew Leach7cc06172016-06-16 15:51:29 +0200179 writel_relaxed(tcon, pwm.base + REG_TCON);
Tomasz Figaf1189982013-04-20 23:22:13 +0200180
Tomasz Figa7aac4822013-04-23 17:46:24 +0200181 spin_unlock_irqrestore(&samsung_pwm_lock, flags);
Tomasz Figaf1189982013-04-20 23:22:13 +0200182}
183
184static int samsung_set_next_event(unsigned long cycles,
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400185 struct clock_event_device *evt)
Tomasz Figaf1189982013-04-20 23:22:13 +0200186{
Tomasz Figa81d4f7b2013-04-23 17:46:30 +0200187 /*
188 * This check is needed to account for internal rounding
189 * errors inside clockevents core, which might result in
190 * passing cycles = 0, which in turn would not generate any
191 * timer interrupt and hang the system.
192 *
193 * Another solution would be to set up the clockevent device
194 * with min_delta = 2, but this would unnecessarily increase
195 * the minimum sleep period.
196 */
197 if (!cycles)
198 cycles = 1;
199
Tomasz Figa030c2a12013-04-23 17:46:25 +0200200 samsung_time_setup(pwm.event_id, cycles);
201 samsung_time_start(pwm.event_id, false);
Tomasz Figaf1189982013-04-20 23:22:13 +0200202
203 return 0;
204}
205
Viresh Kumarb49b5702015-06-18 16:24:33 +0530206static int samsung_shutdown(struct clock_event_device *evt)
Tomasz Figaf1189982013-04-20 23:22:13 +0200207{
Tomasz Figa030c2a12013-04-23 17:46:25 +0200208 samsung_time_stop(pwm.event_id);
Viresh Kumarb49b5702015-06-18 16:24:33 +0530209 return 0;
210}
Tomasz Figaf1189982013-04-20 23:22:13 +0200211
Viresh Kumarb49b5702015-06-18 16:24:33 +0530212static int samsung_set_periodic(struct clock_event_device *evt)
213{
214 samsung_time_stop(pwm.event_id);
215 samsung_time_setup(pwm.event_id, pwm.clock_count_per_tick - 1);
216 samsung_time_start(pwm.event_id, true);
217 return 0;
Tomasz Figaf1189982013-04-20 23:22:13 +0200218}
219
Tomasz Figa0b962582013-06-17 01:11:31 +0200220static void samsung_clockevent_resume(struct clock_event_device *cev)
221{
222 samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);
223 samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);
224
225 if (pwm.variant.has_tint_cstat) {
226 u32 mask = (1 << pwm.event_id);
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400227
Tomasz Figa0b962582013-06-17 01:11:31 +0200228 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
229 }
230}
231
Tomasz Figaf1189982013-04-20 23:22:13 +0200232static struct clock_event_device time_event_device = {
Viresh Kumarb49b5702015-06-18 16:24:33 +0530233 .name = "samsung_event_timer",
234 .features = CLOCK_EVT_FEAT_PERIODIC |
235 CLOCK_EVT_FEAT_ONESHOT,
236 .rating = 200,
237 .set_next_event = samsung_set_next_event,
238 .set_state_shutdown = samsung_shutdown,
239 .set_state_periodic = samsung_set_periodic,
240 .set_state_oneshot = samsung_shutdown,
241 .tick_resume = samsung_shutdown,
242 .resume = samsung_clockevent_resume,
Tomasz Figaf1189982013-04-20 23:22:13 +0200243};
244
245static irqreturn_t samsung_clock_event_isr(int irq, void *dev_id)
246{
247 struct clock_event_device *evt = dev_id;
248
Tomasz Figa030c2a12013-04-23 17:46:25 +0200249 if (pwm.variant.has_tint_cstat) {
250 u32 mask = (1 << pwm.event_id);
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400251
Tomasz Figa030c2a12013-04-23 17:46:25 +0200252 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
Tomasz Figaf1189982013-04-20 23:22:13 +0200253 }
254
255 evt->event_handler(evt);
256
257 return IRQ_HANDLED;
258}
259
Tomasz Figaf1189982013-04-20 23:22:13 +0200260static void __init samsung_clockevent_init(void)
261{
262 unsigned long pclk;
263 unsigned long clock_rate;
264 unsigned int irq_number;
265
Tomasz Figa030c2a12013-04-23 17:46:25 +0200266 pclk = clk_get_rate(pwm.timerclk);
Tomasz Figaf1189982013-04-20 23:22:13 +0200267
Tomasz Figa030c2a12013-04-23 17:46:25 +0200268 samsung_timer_set_prescale(pwm.event_id, pwm.tscaler_div);
269 samsung_timer_set_divisor(pwm.event_id, pwm.tdiv);
Tomasz Figaf1189982013-04-20 23:22:13 +0200270
Tomasz Figa030c2a12013-04-23 17:46:25 +0200271 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
272 pwm.clock_count_per_tick = clock_rate / HZ;
Tomasz Figaf1189982013-04-20 23:22:13 +0200273
274 time_event_device.cpumask = cpumask_of(0);
Tomasz Figae9b852b2013-04-23 17:46:28 +0200275 clockevents_config_and_register(&time_event_device,
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400276 clock_rate, 1, pwm.tcnt_max);
Tomasz Figaf1189982013-04-20 23:22:13 +0200277
Tomasz Figa030c2a12013-04-23 17:46:25 +0200278 irq_number = pwm.irq[pwm.event_id];
afzal mohammedcc2550b2020-02-27 16:29:02 +0530279 if (request_irq(irq_number, samsung_clock_event_isr,
280 IRQF_TIMER | IRQF_IRQPOLL, "samsung_time_irq",
281 &time_event_device))
282 pr_err("%s: request_irq() failed\n", "samsung_time_irq");
Tomasz Figaf1189982013-04-20 23:22:13 +0200283
Tomasz Figa030c2a12013-04-23 17:46:25 +0200284 if (pwm.variant.has_tint_cstat) {
285 u32 mask = (1 << pwm.event_id);
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400286
Tomasz Figa030c2a12013-04-23 17:46:25 +0200287 writel(mask | (mask << 5), pwm.base + REG_TINT_CSTAT);
Tomasz Figaf1189982013-04-20 23:22:13 +0200288 }
289}
290
Tomasz Figa0b962582013-06-17 01:11:31 +0200291static void samsung_clocksource_suspend(struct clocksource *cs)
Tomasz Figaf1189982013-04-20 23:22:13 +0200292{
Tomasz Figa0b962582013-06-17 01:11:31 +0200293 samsung_time_stop(pwm.source_id);
Tomasz Figaf1189982013-04-20 23:22:13 +0200294}
295
Tomasz Figa0b962582013-06-17 01:11:31 +0200296static void samsung_clocksource_resume(struct clocksource *cs)
297{
298 samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);
299 samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);
300
301 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
302 samsung_time_start(pwm.source_id, true);
303}
304
Thomas Gleixnera5a1d1c2016-12-21 20:32:01 +0100305static u64 notrace samsung_clocksource_read(struct clocksource *c)
Tomasz Figa6792e632013-06-17 00:13:06 +0200306{
307 return ~readl_relaxed(pwm.source_reg);
308}
309
310static struct clocksource samsung_clocksource = {
311 .name = "samsung_clocksource_timer",
312 .rating = 250,
313 .read = samsung_clocksource_read,
Tomasz Figa0b962582013-06-17 01:11:31 +0200314 .suspend = samsung_clocksource_suspend,
315 .resume = samsung_clocksource_resume,
Tomasz Figa6792e632013-06-17 00:13:06 +0200316 .flags = CLOCK_SOURCE_IS_CONTINUOUS,
317};
318
Tomasz Figaf1189982013-04-20 23:22:13 +0200319/*
320 * Override the global weak sched_clock symbol with this
321 * local implementation which uses the clocksource to get some
322 * better resolution when scheduling the kernel. We accept that
323 * this wraps around for now, since it is just a relative time
324 * stamp. (Inspired by U300 implementation.)
325 */
Stephen Boyd2902b302013-07-18 16:21:25 -0700326static u64 notrace samsung_read_sched_clock(void)
Tomasz Figaf1189982013-04-20 23:22:13 +0200327{
Tomasz Figa6792e632013-06-17 00:13:06 +0200328 return samsung_clocksource_read(NULL);
Tomasz Figaf1189982013-04-20 23:22:13 +0200329}
330
Daniel Lezcano0993f572016-06-06 17:58:56 +0200331static int __init samsung_clocksource_init(void)
Tomasz Figaf1189982013-04-20 23:22:13 +0200332{
Tomasz Figaf1189982013-04-20 23:22:13 +0200333 unsigned long pclk;
334 unsigned long clock_rate;
Tomasz Figaf1189982013-04-20 23:22:13 +0200335
Tomasz Figa030c2a12013-04-23 17:46:25 +0200336 pclk = clk_get_rate(pwm.timerclk);
Tomasz Figaf1189982013-04-20 23:22:13 +0200337
Tomasz Figa030c2a12013-04-23 17:46:25 +0200338 samsung_timer_set_prescale(pwm.source_id, pwm.tscaler_div);
339 samsung_timer_set_divisor(pwm.source_id, pwm.tdiv);
Tomasz Figaf1189982013-04-20 23:22:13 +0200340
Tomasz Figa030c2a12013-04-23 17:46:25 +0200341 clock_rate = pclk / (pwm.tscaler_div * pwm.tdiv);
Tomasz Figaf1189982013-04-20 23:22:13 +0200342
Tomasz Figa030c2a12013-04-23 17:46:25 +0200343 samsung_time_setup(pwm.source_id, pwm.tcnt_max);
344 samsung_time_start(pwm.source_id, true);
Tomasz Figaf1189982013-04-20 23:22:13 +0200345
Tomasz Figa61d7e202013-06-17 00:07:03 +0200346 if (pwm.source_id == 4)
347 pwm.source_reg = pwm.base + 0x40;
348 else
349 pwm.source_reg = pwm.base + pwm.source_id * 0x0c + 0x14;
350
Stephen Boyd2902b302013-07-18 16:21:25 -0700351 sched_clock_register(samsung_read_sched_clock,
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400352 pwm.variant.bits, clock_rate);
Tomasz Figaf1189982013-04-20 23:22:13 +0200353
Tomasz Figa6792e632013-06-17 00:13:06 +0200354 samsung_clocksource.mask = CLOCKSOURCE_MASK(pwm.variant.bits);
Daniel Lezcano0993f572016-06-06 17:58:56 +0200355 return clocksource_register_hz(&samsung_clocksource, clock_rate);
Tomasz Figaf1189982013-04-20 23:22:13 +0200356}
357
358static void __init samsung_timer_resources(void)
359{
Tomasz Figa030c2a12013-04-23 17:46:25 +0200360 clk_prepare_enable(pwm.timerclk);
Tomasz Figaf1189982013-04-20 23:22:13 +0200361
Tomasz Figa030c2a12013-04-23 17:46:25 +0200362 pwm.tcnt_max = (1UL << pwm.variant.bits) - 1;
363 if (pwm.variant.bits == 16) {
364 pwm.tscaler_div = 25;
365 pwm.tdiv = 2;
Tomasz Figaf1189982013-04-20 23:22:13 +0200366 } else {
Tomasz Figa030c2a12013-04-23 17:46:25 +0200367 pwm.tscaler_div = 2;
368 pwm.tdiv = 1;
Tomasz Figaf1189982013-04-20 23:22:13 +0200369 }
370}
371
372/*
373 * PWM master driver
374 */
Daniel Lezcano0993f572016-06-06 17:58:56 +0200375static int __init _samsung_pwm_clocksource_init(void)
Tomasz Figaf1189982013-04-20 23:22:13 +0200376{
377 u8 mask;
378 int channel;
379
Tomasz Figa030c2a12013-04-23 17:46:25 +0200380 mask = ~pwm.variant.output_mask & ((1 << SAMSUNG_PWM_NUM) - 1);
Tomasz Figaf1189982013-04-20 23:22:13 +0200381 channel = fls(mask) - 1;
Daniel Lezcano0993f572016-06-06 17:58:56 +0200382 if (channel < 0) {
Rafał Miłeckiac9ce6d2017-03-09 10:47:10 +0100383 pr_crit("failed to find PWM channel for clocksource\n");
Daniel Lezcano0993f572016-06-06 17:58:56 +0200384 return -EINVAL;
385 }
Tomasz Figa030c2a12013-04-23 17:46:25 +0200386 pwm.source_id = channel;
Tomasz Figaf1189982013-04-20 23:22:13 +0200387
388 mask &= ~(1 << channel);
389 channel = fls(mask) - 1;
Daniel Lezcano0993f572016-06-06 17:58:56 +0200390 if (channel < 0) {
Rafał Miłeckiac9ce6d2017-03-09 10:47:10 +0100391 pr_crit("failed to find PWM channel for clock event\n");
Daniel Lezcano0993f572016-06-06 17:58:56 +0200392 return -EINVAL;
393 }
Tomasz Figa030c2a12013-04-23 17:46:25 +0200394 pwm.event_id = channel;
Tomasz Figaf1189982013-04-20 23:22:13 +0200395
396 samsung_timer_resources();
397 samsung_clockevent_init();
Daniel Lezcano0993f572016-06-06 17:58:56 +0200398
399 return samsung_clocksource_init();
Tomasz Figaf1189982013-04-20 23:22:13 +0200400}
401
Tomasz Figaf9bb48a22013-04-23 17:46:27 +0200402void __init samsung_pwm_clocksource_init(void __iomem *base,
Krzysztof Kozlowskia0143f5a2021-05-06 16:27:25 -0400403 unsigned int *irqs,
Krzysztof Kozlowskibb08e962021-05-06 16:27:26 -0400404 const struct samsung_pwm_variant *variant)
Tomasz Figaf9bb48a22013-04-23 17:46:27 +0200405{
406 pwm.base = base;
407 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
408 memcpy(pwm.irq, irqs, SAMSUNG_PWM_NUM * sizeof(*irqs));
409
Tomasz Figaa1fa6f52013-08-26 19:08:58 +0200410 pwm.timerclk = clk_get(NULL, "timers");
411 if (IS_ERR(pwm.timerclk))
412 panic("failed to get timers clock for timer");
413
Tomasz Figaf9bb48a22013-04-23 17:46:27 +0200414 _samsung_pwm_clocksource_init();
415}
416
Daniel Lezcanobb0eb052017-05-26 19:34:11 +0200417#ifdef CONFIG_TIMER_OF
Daniel Lezcano0993f572016-06-06 17:58:56 +0200418static int __init samsung_pwm_alloc(struct device_node *np,
419 const struct samsung_pwm_variant *variant)
Tomasz Figaf1189982013-04-20 23:22:13 +0200420{
Tomasz Figaf1189982013-04-20 23:22:13 +0200421 struct property *prop;
422 const __be32 *cur;
423 u32 val;
Krzysztof Kozlowski63e83bd2021-05-06 16:27:27 -0400424 int i, ret;
Tomasz Figaf1189982013-04-20 23:22:13 +0200425
Tomasz Figa030c2a12013-04-23 17:46:25 +0200426 memcpy(&pwm.variant, variant, sizeof(pwm.variant));
Tomasz Figaf1189982013-04-20 23:22:13 +0200427 for (i = 0; i < SAMSUNG_PWM_NUM; ++i)
Tomasz Figa030c2a12013-04-23 17:46:25 +0200428 pwm.irq[i] = irq_of_parse_and_map(np, i);
Tomasz Figaf1189982013-04-20 23:22:13 +0200429
430 of_property_for_each_u32(np, "samsung,pwm-outputs", prop, cur, val) {
431 if (val >= SAMSUNG_PWM_NUM) {
Kefeng Wang43fc6b22019-10-18 11:18:28 +0800432 pr_warn("%s: invalid channel index in samsung,pwm-outputs property\n", __func__);
Tomasz Figaf1189982013-04-20 23:22:13 +0200433 continue;
434 }
Tomasz Figa030c2a12013-04-23 17:46:25 +0200435 pwm.variant.output_mask |= 1 << val;
Tomasz Figaf1189982013-04-20 23:22:13 +0200436 }
437
Tomasz Figae2415482013-06-13 21:22:44 +0200438 pwm.base = of_iomap(np, 0);
Tomasz Figa030c2a12013-04-23 17:46:25 +0200439 if (!pwm.base) {
Tomasz Figaf1189982013-04-20 23:22:13 +0200440 pr_err("%s: failed to map PWM registers\n", __func__);
Daniel Lezcano0993f572016-06-06 17:58:56 +0200441 return -ENXIO;
Tomasz Figaf1189982013-04-20 23:22:13 +0200442 }
443
Tomasz Figaa1fa6f52013-08-26 19:08:58 +0200444 pwm.timerclk = of_clk_get_by_name(np, "timers");
Daniel Lezcano0993f572016-06-06 17:58:56 +0200445 if (IS_ERR(pwm.timerclk)) {
Rafał Miłeckiac9ce6d2017-03-09 10:47:10 +0100446 pr_crit("failed to get timers clock for timer\n");
Krzysztof Kozlowski63e83bd2021-05-06 16:27:27 -0400447 ret = PTR_ERR(pwm.timerclk);
448 goto err_clk;
Daniel Lezcano0993f572016-06-06 17:58:56 +0200449 }
Tomasz Figaf1189982013-04-20 23:22:13 +0200450
Krzysztof Kozlowski63e83bd2021-05-06 16:27:27 -0400451 ret = _samsung_pwm_clocksource_init();
452 if (ret)
453 goto err_clocksource;
454
455 return 0;
456
457err_clocksource:
458 clk_put(pwm.timerclk);
459 pwm.timerclk = NULL;
460err_clk:
461 iounmap(pwm.base);
462 pwm.base = NULL;
463
464 return ret;
Tomasz Figaf1189982013-04-20 23:22:13 +0200465}
466
467static const struct samsung_pwm_variant s3c24xx_variant = {
468 .bits = 16,
469 .div_base = 1,
470 .has_tint_cstat = false,
471 .tclk_mask = (1 << 4),
472};
473
Daniel Lezcano0993f572016-06-06 17:58:56 +0200474static int __init s3c2410_pwm_clocksource_init(struct device_node *np)
Tomasz Figaf1189982013-04-20 23:22:13 +0200475{
Daniel Lezcano0993f572016-06-06 17:58:56 +0200476 return samsung_pwm_alloc(np, &s3c24xx_variant);
Tomasz Figaf1189982013-04-20 23:22:13 +0200477}
Daniel Lezcano17273392017-05-26 16:56:11 +0200478TIMER_OF_DECLARE(s3c2410_pwm, "samsung,s3c2410-pwm", s3c2410_pwm_clocksource_init);
Tomasz Figaf1189982013-04-20 23:22:13 +0200479
480static const struct samsung_pwm_variant s3c64xx_variant = {
481 .bits = 32,
482 .div_base = 0,
483 .has_tint_cstat = true,
484 .tclk_mask = (1 << 7) | (1 << 6) | (1 << 5),
485};
486
Daniel Lezcano0993f572016-06-06 17:58:56 +0200487static int __init s3c64xx_pwm_clocksource_init(struct device_node *np)
Tomasz Figaf1189982013-04-20 23:22:13 +0200488{
Daniel Lezcano0993f572016-06-06 17:58:56 +0200489 return samsung_pwm_alloc(np, &s3c64xx_variant);
Tomasz Figaf1189982013-04-20 23:22:13 +0200490}
Daniel Lezcano17273392017-05-26 16:56:11 +0200491TIMER_OF_DECLARE(s3c6400_pwm, "samsung,s3c6400-pwm", s3c64xx_pwm_clocksource_init);
Tomasz Figaf1189982013-04-20 23:22:13 +0200492
493static const struct samsung_pwm_variant s5p64x0_variant = {
494 .bits = 32,
495 .div_base = 0,
496 .has_tint_cstat = true,
497 .tclk_mask = 0,
498};
499
Daniel Lezcano0993f572016-06-06 17:58:56 +0200500static int __init s5p64x0_pwm_clocksource_init(struct device_node *np)
Tomasz Figaf1189982013-04-20 23:22:13 +0200501{
Daniel Lezcano0993f572016-06-06 17:58:56 +0200502 return samsung_pwm_alloc(np, &s5p64x0_variant);
Tomasz Figaf1189982013-04-20 23:22:13 +0200503}
Daniel Lezcano17273392017-05-26 16:56:11 +0200504TIMER_OF_DECLARE(s5p6440_pwm, "samsung,s5p6440-pwm", s5p64x0_pwm_clocksource_init);
Tomasz Figaf1189982013-04-20 23:22:13 +0200505
506static const struct samsung_pwm_variant s5p_variant = {
507 .bits = 32,
508 .div_base = 0,
509 .has_tint_cstat = true,
510 .tclk_mask = (1 << 5),
511};
512
Daniel Lezcano0993f572016-06-06 17:58:56 +0200513static int __init s5p_pwm_clocksource_init(struct device_node *np)
Tomasz Figaf1189982013-04-20 23:22:13 +0200514{
Daniel Lezcano0993f572016-06-06 17:58:56 +0200515 return samsung_pwm_alloc(np, &s5p_variant);
Tomasz Figaf1189982013-04-20 23:22:13 +0200516}
Daniel Lezcano17273392017-05-26 16:56:11 +0200517TIMER_OF_DECLARE(s5pc100_pwm, "samsung,s5pc100-pwm", s5p_pwm_clocksource_init);
Tomasz Figaf9bb48a22013-04-23 17:46:27 +0200518#endif