]>
Commit | Line | Data |
---|---|---|
c5d58e9e KC |
1 | /* |
2 | * Copyright (c) 2014 Zhang, Keguang <keguang.zhang@gmail.com> | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License as published by the | |
6 | * Free Software Foundation; either version 2 of the License, or (at your | |
7 | * option) any later version. | |
8 | */ | |
9 | ||
10 | #include <linux/clk.h> | |
11 | #include <linux/interrupt.h> | |
9ec88b60 | 12 | #include <linux/sizes.h> |
c5d58e9e KC |
13 | #include <asm/time.h> |
14 | ||
15 | #include <loongson1.h> | |
16 | #include <platform.h> | |
17 | ||
18 | #ifdef CONFIG_CEVT_CSRC_LS1X | |
19 | ||
20 | #if defined(CONFIG_TIMER_USE_PWM1) | |
21 | #define LS1X_TIMER_BASE LS1X_PWM1_BASE | |
22 | #define LS1X_TIMER_IRQ LS1X_PWM1_IRQ | |
23 | ||
24 | #elif defined(CONFIG_TIMER_USE_PWM2) | |
25 | #define LS1X_TIMER_BASE LS1X_PWM2_BASE | |
26 | #define LS1X_TIMER_IRQ LS1X_PWM2_IRQ | |
27 | ||
28 | #elif defined(CONFIG_TIMER_USE_PWM3) | |
29 | #define LS1X_TIMER_BASE LS1X_PWM3_BASE | |
30 | #define LS1X_TIMER_IRQ LS1X_PWM3_IRQ | |
31 | ||
32 | #else | |
33 | #define LS1X_TIMER_BASE LS1X_PWM0_BASE | |
34 | #define LS1X_TIMER_IRQ LS1X_PWM0_IRQ | |
35 | #endif | |
36 | ||
37 | DEFINE_RAW_SPINLOCK(ls1x_timer_lock); | |
38 | ||
9ec88b60 | 39 | static void __iomem *timer_reg_base; |
c5d58e9e KC |
40 | static uint32_t ls1x_jiffies_per_tick; |
41 | ||
42 | static inline void ls1x_pwmtimer_set_period(uint32_t period) | |
43 | { | |
9ec88b60 KC |
44 | __raw_writel(period, timer_reg_base + PWM_HRC); |
45 | __raw_writel(period, timer_reg_base + PWM_LRC); | |
c5d58e9e KC |
46 | } |
47 | ||
48 | static inline void ls1x_pwmtimer_restart(void) | |
49 | { | |
9ec88b60 KC |
50 | __raw_writel(0x0, timer_reg_base + PWM_CNT); |
51 | __raw_writel(INT_EN | CNT_EN, timer_reg_base + PWM_CTRL); | |
c5d58e9e KC |
52 | } |
53 | ||
54 | void __init ls1x_pwmtimer_init(void) | |
55 | { | |
9ec88b60 KC |
56 | timer_reg_base = ioremap_nocache(LS1X_TIMER_BASE, SZ_16); |
57 | if (!timer_reg_base) | |
c5d58e9e KC |
58 | panic("Failed to remap timer registers"); |
59 | ||
60 | ls1x_jiffies_per_tick = DIV_ROUND_CLOSEST(mips_hpt_frequency, HZ); | |
61 | ||
62 | ls1x_pwmtimer_set_period(ls1x_jiffies_per_tick); | |
63 | ls1x_pwmtimer_restart(); | |
64 | } | |
65 | ||
a5a1d1c2 | 66 | static u64 ls1x_clocksource_read(struct clocksource *cs) |
c5d58e9e KC |
67 | { |
68 | unsigned long flags; | |
69 | int count; | |
70 | u32 jifs; | |
71 | static int old_count; | |
72 | static u32 old_jifs; | |
73 | ||
74 | raw_spin_lock_irqsave(&ls1x_timer_lock, flags); | |
75 | /* | |
76 | * Although our caller may have the read side of xtime_lock, | |
77 | * this is now a seqlock, and we are cheating in this routine | |
78 | * by having side effects on state that we cannot undo if | |
79 | * there is a collision on the seqlock and our caller has to | |
80 | * retry. (Namely, old_jifs and old_count.) So we must treat | |
81 | * jiffies as volatile despite the lock. We read jiffies | |
82 | * before latching the timer count to guarantee that although | |
83 | * the jiffies value might be older than the count (that is, | |
84 | * the counter may underflow between the last point where | |
85 | * jiffies was incremented and the point where we latch the | |
86 | * count), it cannot be newer. | |
87 | */ | |
88 | jifs = jiffies; | |
89 | /* read the count */ | |
9ec88b60 | 90 | count = __raw_readl(timer_reg_base + PWM_CNT); |
c5d58e9e KC |
91 | |
92 | /* | |
93 | * It's possible for count to appear to go the wrong way for this | |
94 | * reason: | |
95 | * | |
96 | * The timer counter underflows, but we haven't handled the resulting | |
97 | * interrupt and incremented jiffies yet. | |
98 | * | |
99 | * Previous attempts to handle these cases intelligently were buggy, so | |
100 | * we just do the simple thing now. | |
101 | */ | |
102 | if (count < old_count && jifs == old_jifs) | |
103 | count = old_count; | |
104 | ||
105 | old_count = count; | |
106 | old_jifs = jifs; | |
107 | ||
108 | raw_spin_unlock_irqrestore(&ls1x_timer_lock, flags); | |
109 | ||
a5a1d1c2 | 110 | return (u64) (jifs * ls1x_jiffies_per_tick) + count; |
c5d58e9e KC |
111 | } |
112 | ||
113 | static struct clocksource ls1x_clocksource = { | |
114 | .name = "ls1x-pwmtimer", | |
115 | .read = ls1x_clocksource_read, | |
116 | .mask = CLOCKSOURCE_MASK(24), | |
117 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | |
118 | }; | |
119 | ||
120 | static irqreturn_t ls1x_clockevent_isr(int irq, void *devid) | |
121 | { | |
122 | struct clock_event_device *cd = devid; | |
123 | ||
124 | ls1x_pwmtimer_restart(); | |
125 | cd->event_handler(cd); | |
126 | ||
127 | return IRQ_HANDLED; | |
128 | } | |
129 | ||
1fed884d | 130 | static int ls1x_clockevent_set_state_periodic(struct clock_event_device *cd) |
c5d58e9e KC |
131 | { |
132 | raw_spin_lock(&ls1x_timer_lock); | |
1fed884d VK |
133 | ls1x_pwmtimer_set_period(ls1x_jiffies_per_tick); |
134 | ls1x_pwmtimer_restart(); | |
9ec88b60 | 135 | __raw_writel(INT_EN | CNT_EN, timer_reg_base + PWM_CTRL); |
c5d58e9e | 136 | raw_spin_unlock(&ls1x_timer_lock); |
1fed884d VK |
137 | |
138 | return 0; | |
139 | } | |
140 | ||
141 | static int ls1x_clockevent_tick_resume(struct clock_event_device *cd) | |
142 | { | |
143 | raw_spin_lock(&ls1x_timer_lock); | |
9ec88b60 | 144 | __raw_writel(INT_EN | CNT_EN, timer_reg_base + PWM_CTRL); |
1fed884d VK |
145 | raw_spin_unlock(&ls1x_timer_lock); |
146 | ||
147 | return 0; | |
148 | } | |
149 | ||
150 | static int ls1x_clockevent_set_state_shutdown(struct clock_event_device *cd) | |
151 | { | |
152 | raw_spin_lock(&ls1x_timer_lock); | |
9ec88b60 KC |
153 | __raw_writel(__raw_readl(timer_reg_base + PWM_CTRL) & ~CNT_EN, |
154 | timer_reg_base + PWM_CTRL); | |
1fed884d VK |
155 | raw_spin_unlock(&ls1x_timer_lock); |
156 | ||
157 | return 0; | |
c5d58e9e KC |
158 | } |
159 | ||
160 | static int ls1x_clockevent_set_next(unsigned long evt, | |
161 | struct clock_event_device *cd) | |
162 | { | |
163 | raw_spin_lock(&ls1x_timer_lock); | |
164 | ls1x_pwmtimer_set_period(evt); | |
165 | ls1x_pwmtimer_restart(); | |
166 | raw_spin_unlock(&ls1x_timer_lock); | |
167 | ||
168 | return 0; | |
169 | } | |
170 | ||
171 | static struct clock_event_device ls1x_clockevent = { | |
1fed884d VK |
172 | .name = "ls1x-pwmtimer", |
173 | .features = CLOCK_EVT_FEAT_PERIODIC, | |
174 | .rating = 300, | |
175 | .irq = LS1X_TIMER_IRQ, | |
176 | .set_next_event = ls1x_clockevent_set_next, | |
177 | .set_state_shutdown = ls1x_clockevent_set_state_shutdown, | |
178 | .set_state_periodic = ls1x_clockevent_set_state_periodic, | |
179 | .set_state_oneshot = ls1x_clockevent_set_state_shutdown, | |
180 | .tick_resume = ls1x_clockevent_tick_resume, | |
c5d58e9e KC |
181 | }; |
182 | ||
183 | static struct irqaction ls1x_pwmtimer_irqaction = { | |
184 | .name = "ls1x-pwmtimer", | |
185 | .handler = ls1x_clockevent_isr, | |
186 | .dev_id = &ls1x_clockevent, | |
187 | .flags = IRQF_PERCPU | IRQF_TIMER, | |
188 | }; | |
189 | ||
190 | static void __init ls1x_time_init(void) | |
191 | { | |
192 | struct clock_event_device *cd = &ls1x_clockevent; | |
193 | int ret; | |
194 | ||
195 | if (!mips_hpt_frequency) | |
196 | panic("Invalid timer clock rate"); | |
197 | ||
198 | ls1x_pwmtimer_init(); | |
199 | ||
200 | clockevent_set_clock(cd, mips_hpt_frequency); | |
201 | cd->max_delta_ns = clockevent_delta2ns(0xffffff, cd); | |
202 | cd->min_delta_ns = clockevent_delta2ns(0x000300, cd); | |
203 | cd->cpumask = cpumask_of(smp_processor_id()); | |
204 | clockevents_register_device(cd); | |
205 | ||
206 | ls1x_clocksource.rating = 200 + mips_hpt_frequency / 10000000; | |
207 | ret = clocksource_register_hz(&ls1x_clocksource, mips_hpt_frequency); | |
208 | if (ret) | |
209 | panic(KERN_ERR "Failed to register clocksource: %d\n", ret); | |
210 | ||
211 | setup_irq(LS1X_TIMER_IRQ, &ls1x_pwmtimer_irqaction); | |
212 | } | |
213 | #endif /* CONFIG_CEVT_CSRC_LS1X */ | |
214 | ||
215 | void __init plat_time_init(void) | |
216 | { | |
217 | struct clk *clk = NULL; | |
218 | ||
219 | /* initialize LS1X clocks */ | |
220 | ls1x_clk_init(); | |
221 | ||
222 | #ifdef CONFIG_CEVT_CSRC_LS1X | |
223 | /* setup LS1X PWM timer */ | |
9ec88b60 | 224 | clk = clk_get(NULL, "ls1x-pwmtimer"); |
c5d58e9e KC |
225 | if (IS_ERR(clk)) |
226 | panic("unable to get timer clock, err=%ld", PTR_ERR(clk)); | |
227 | ||
228 | mips_hpt_frequency = clk_get_rate(clk); | |
229 | ls1x_time_init(); | |
230 | #else | |
231 | /* setup mips r4k timer */ | |
232 | clk = clk_get(NULL, "cpu_clk"); | |
233 | if (IS_ERR(clk)) | |
234 | panic("unable to get cpu clock, err=%ld", PTR_ERR(clk)); | |
235 | ||
236 | mips_hpt_frequency = clk_get_rate(clk) / 2; | |
237 | #endif /* CONFIG_CEVT_CSRC_LS1X */ | |
238 | } |