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