diff options
author | Kelvin Cheung <keguang.zhang@gmail.com> | 2014-10-09 23:40:02 -0400 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2014-11-24 01:45:09 -0500 |
commit | c5d58e9e79dd8d687454915fc4e845434f973175 (patch) | |
tree | f0031c5b606865d4a02131cd42d7a01c9d609576 /arch/mips/loongson1 | |
parent | f29ad10de6c345c8ae4cb33a99ba8ff29bdcd751 (diff) |
MIPS: Loongson1B: Add a clockevent/clocksource using PWM Timer
This patch add a clockevent/clocksource using PWM Timer for Loongson1B,
which is based on earlier work by Tang, Haifeng.
Signed-off-by: Kelvin Cheung <keguang.zhang@gmail.com>
Cc: linux-mips@linux-mips.org
Patchwork: https://patchwork.linux-mips.org/patch/8025/
Signed-off-by: Ralf Baechle <ralf@linux-mips.org>
Diffstat (limited to 'arch/mips/loongson1')
-rw-r--r-- | arch/mips/loongson1/Kconfig | 41 | ||||
-rw-r--r-- | arch/mips/loongson1/common/Makefile | 2 | ||||
-rw-r--r-- | arch/mips/loongson1/common/clock.c | 28 | ||||
-rw-r--r-- | arch/mips/loongson1/common/time.c | 226 |
4 files changed, 266 insertions, 31 deletions
diff --git a/arch/mips/loongson1/Kconfig b/arch/mips/loongson1/Kconfig index 4ed9744fe051..a2b796eaf3c3 100644 --- a/arch/mips/loongson1/Kconfig +++ b/arch/mips/loongson1/Kconfig | |||
@@ -5,8 +5,8 @@ choice | |||
5 | 5 | ||
6 | config LOONGSON1_LS1B | 6 | config LOONGSON1_LS1B |
7 | bool "Loongson LS1B board" | 7 | bool "Loongson LS1B board" |
8 | select CEVT_R4K | 8 | select CEVT_R4K if !MIPS_EXTERNAL_TIMER |
9 | select CSRC_R4K | 9 | select CSRC_R4K if !MIPS_EXTERNAL_TIMER |
10 | select SYS_HAS_CPU_LOONGSON1B | 10 | select SYS_HAS_CPU_LOONGSON1B |
11 | select DMA_NONCOHERENT | 11 | select DMA_NONCOHERENT |
12 | select BOOT_ELF32 | 12 | select BOOT_ELF32 |
@@ -21,4 +21,41 @@ config LOONGSON1_LS1B | |||
21 | 21 | ||
22 | endchoice | 22 | endchoice |
23 | 23 | ||
24 | menuconfig CEVT_CSRC_LS1X | ||
25 | bool "Use PWM Timer for clockevent/clocksource" | ||
26 | select MIPS_EXTERNAL_TIMER | ||
27 | depends on CPU_LOONGSON1 | ||
28 | help | ||
29 | This option changes the default clockevent/clocksource to PWM Timer, | ||
30 | and is required by Loongson1 CPUFreq support. | ||
31 | |||
32 | If unsure, say N. | ||
33 | |||
34 | choice | ||
35 | prompt "Select clockevent/clocksource" | ||
36 | depends on CEVT_CSRC_LS1X | ||
37 | default TIMER_USE_PWM0 | ||
38 | |||
39 | config TIMER_USE_PWM0 | ||
40 | bool "Use PWM Timer 0" | ||
41 | help | ||
42 | Use PWM Timer 0 as the default clockevent/clocksourcer. | ||
43 | |||
44 | config TIMER_USE_PWM1 | ||
45 | bool "Use PWM Timer 1" | ||
46 | help | ||
47 | Use PWM Timer 1 as the default clockevent/clocksourcer. | ||
48 | |||
49 | config TIMER_USE_PWM2 | ||
50 | bool "Use PWM Timer 2" | ||
51 | help | ||
52 | Use PWM Timer 2 as the default clockevent/clocksourcer. | ||
53 | |||
54 | config TIMER_USE_PWM3 | ||
55 | bool "Use PWM Timer 3" | ||
56 | help | ||
57 | Use PWM Timer 3 as the default clockevent/clocksourcer. | ||
58 | |||
59 | endchoice | ||
60 | |||
24 | endif # MACH_LOONGSON1 | 61 | endif # MACH_LOONGSON1 |
diff --git a/arch/mips/loongson1/common/Makefile b/arch/mips/loongson1/common/Makefile index b2797709ef5b..723b4ce3b8f0 100644 --- a/arch/mips/loongson1/common/Makefile +++ b/arch/mips/loongson1/common/Makefile | |||
@@ -2,4 +2,4 @@ | |||
2 | # Makefile for common code of loongson1 based machines. | 2 | # Makefile for common code of loongson1 based machines. |
3 | # | 3 | # |
4 | 4 | ||
5 | obj-y += clock.o irq.o platform.o prom.o reset.o setup.o | 5 | obj-y += time.o irq.o platform.o prom.o reset.o setup.o |
diff --git a/arch/mips/loongson1/common/clock.c b/arch/mips/loongson1/common/clock.c deleted file mode 100644 index b4437f19c3d9..000000000000 --- a/arch/mips/loongson1/common/clock.c +++ /dev/null | |||
@@ -1,28 +0,0 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2011 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/err.h> | ||
12 | #include <asm/time.h> | ||
13 | #include <platform.h> | ||
14 | |||
15 | void __init plat_time_init(void) | ||
16 | { | ||
17 | struct clk *clk; | ||
18 | |||
19 | /* Initialize LS1X clocks */ | ||
20 | ls1x_clk_init(); | ||
21 | |||
22 | /* setup mips r4k timer */ | ||
23 | clk = clk_get(NULL, "cpu"); | ||
24 | if (IS_ERR(clk)) | ||
25 | panic("unable to get cpu clock, err=%ld", PTR_ERR(clk)); | ||
26 | |||
27 | mips_hpt_frequency = clk_get_rate(clk) / 2; | ||
28 | } | ||
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 | } | ||