diff options
Diffstat (limited to 'arch/mips/loongson64/hpet.c')
-rw-r--r-- | arch/mips/loongson64/hpet.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/arch/mips/loongson64/hpet.c b/arch/mips/loongson64/hpet.c new file mode 100644 index 000000000..e42825925 --- /dev/null +++ b/arch/mips/loongson64/hpet.c | |||
@@ -0,0 +1,285 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0 | ||
2 | #include <linux/init.h> | ||
3 | #include <linux/pci.h> | ||
4 | #include <linux/percpu.h> | ||
5 | #include <linux/delay.h> | ||
6 | #include <linux/spinlock.h> | ||
7 | #include <linux/interrupt.h> | ||
8 | |||
9 | #include <asm/hpet.h> | ||
10 | #include <asm/time.h> | ||
11 | |||
12 | #define SMBUS_CFG_BASE (loongson_sysconf.ht_control_base + 0x0300a000) | ||
13 | #define SMBUS_PCI_REG40 0x40 | ||
14 | #define SMBUS_PCI_REG64 0x64 | ||
15 | #define SMBUS_PCI_REGB4 0xb4 | ||
16 | |||
17 | #define HPET_MIN_CYCLES 16 | ||
18 | #define HPET_MIN_PROG_DELTA (HPET_MIN_CYCLES * 12) | ||
19 | |||
20 | static DEFINE_SPINLOCK(hpet_lock); | ||
21 | DEFINE_PER_CPU(struct clock_event_device, hpet_clockevent_device); | ||
22 | |||
23 | static unsigned int smbus_read(int offset) | ||
24 | { | ||
25 | return *(volatile unsigned int *)(SMBUS_CFG_BASE + offset); | ||
26 | } | ||
27 | |||
28 | static void smbus_write(int offset, int data) | ||
29 | { | ||
30 | *(volatile unsigned int *)(SMBUS_CFG_BASE + offset) = data; | ||
31 | } | ||
32 | |||
33 | static void smbus_enable(int offset, int bit) | ||
34 | { | ||
35 | unsigned int cfg = smbus_read(offset); | ||
36 | |||
37 | cfg |= bit; | ||
38 | smbus_write(offset, cfg); | ||
39 | } | ||
40 | |||
41 | static int hpet_read(int offset) | ||
42 | { | ||
43 | return *(volatile unsigned int *)(HPET_MMIO_ADDR + offset); | ||
44 | } | ||
45 | |||
46 | static void hpet_write(int offset, int data) | ||
47 | { | ||
48 | *(volatile unsigned int *)(HPET_MMIO_ADDR + offset) = data; | ||
49 | } | ||
50 | |||
51 | static void hpet_start_counter(void) | ||
52 | { | ||
53 | unsigned int cfg = hpet_read(HPET_CFG); | ||
54 | |||
55 | cfg |= HPET_CFG_ENABLE; | ||
56 | hpet_write(HPET_CFG, cfg); | ||
57 | } | ||
58 | |||
59 | static void hpet_stop_counter(void) | ||
60 | { | ||
61 | unsigned int cfg = hpet_read(HPET_CFG); | ||
62 | |||
63 | cfg &= ~HPET_CFG_ENABLE; | ||
64 | hpet_write(HPET_CFG, cfg); | ||
65 | } | ||
66 | |||
67 | static void hpet_reset_counter(void) | ||
68 | { | ||
69 | hpet_write(HPET_COUNTER, 0); | ||
70 | hpet_write(HPET_COUNTER + 4, 0); | ||
71 | } | ||
72 | |||
73 | static void hpet_restart_counter(void) | ||
74 | { | ||
75 | hpet_stop_counter(); | ||
76 | hpet_reset_counter(); | ||
77 | hpet_start_counter(); | ||
78 | } | ||
79 | |||
80 | static void hpet_enable_legacy_int(void) | ||
81 | { | ||
82 | /* Do nothing on Loongson-3 */ | ||
83 | } | ||
84 | |||
85 | static int hpet_set_state_periodic(struct clock_event_device *evt) | ||
86 | { | ||
87 | int cfg; | ||
88 | |||
89 | spin_lock(&hpet_lock); | ||
90 | |||
91 | pr_info("set clock event to periodic mode!\n"); | ||
92 | /* stop counter */ | ||
93 | hpet_stop_counter(); | ||
94 | |||
95 | /* enables the timer0 to generate a periodic interrupt */ | ||
96 | cfg = hpet_read(HPET_T0_CFG); | ||
97 | cfg &= ~HPET_TN_LEVEL; | ||
98 | cfg |= HPET_TN_ENABLE | HPET_TN_PERIODIC | HPET_TN_SETVAL | | ||
99 | HPET_TN_32BIT; | ||
100 | hpet_write(HPET_T0_CFG, cfg); | ||
101 | |||
102 | /* set the comparator */ | ||
103 | hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); | ||
104 | udelay(1); | ||
105 | hpet_write(HPET_T0_CMP, HPET_COMPARE_VAL); | ||
106 | |||
107 | /* start counter */ | ||
108 | hpet_start_counter(); | ||
109 | |||
110 | spin_unlock(&hpet_lock); | ||
111 | return 0; | ||
112 | } | ||
113 | |||
114 | static int hpet_set_state_shutdown(struct clock_event_device *evt) | ||
115 | { | ||
116 | int cfg; | ||
117 | |||
118 | spin_lock(&hpet_lock); | ||
119 | |||
120 | cfg = hpet_read(HPET_T0_CFG); | ||
121 | cfg &= ~HPET_TN_ENABLE; | ||
122 | hpet_write(HPET_T0_CFG, cfg); | ||
123 | |||
124 | spin_unlock(&hpet_lock); | ||
125 | return 0; | ||
126 | } | ||
127 | |||
128 | static int hpet_set_state_oneshot(struct clock_event_device *evt) | ||
129 | { | ||
130 | int cfg; | ||
131 | |||
132 | spin_lock(&hpet_lock); | ||
133 | |||
134 | pr_info("set clock event to one shot mode!\n"); | ||
135 | cfg = hpet_read(HPET_T0_CFG); | ||
136 | /* | ||
137 | * set timer0 type | ||
138 | * 1 : periodic interrupt | ||
139 | * 0 : non-periodic(oneshot) interrupt | ||
140 | */ | ||
141 | cfg &= ~HPET_TN_PERIODIC; | ||
142 | cfg |= HPET_TN_ENABLE | HPET_TN_32BIT; | ||
143 | hpet_write(HPET_T0_CFG, cfg); | ||
144 | |||
145 | spin_unlock(&hpet_lock); | ||
146 | return 0; | ||
147 | } | ||
148 | |||
149 | static int hpet_tick_resume(struct clock_event_device *evt) | ||
150 | { | ||
151 | spin_lock(&hpet_lock); | ||
152 | hpet_enable_legacy_int(); | ||
153 | spin_unlock(&hpet_lock); | ||
154 | |||
155 | return 0; | ||
156 | } | ||
157 | |||
158 | static int hpet_next_event(unsigned long delta, | ||
159 | struct clock_event_device *evt) | ||
160 | { | ||
161 | u32 cnt; | ||
162 | s32 res; | ||
163 | |||
164 | cnt = hpet_read(HPET_COUNTER); | ||
165 | cnt += (u32) delta; | ||
166 | hpet_write(HPET_T0_CMP, cnt); | ||
167 | |||
168 | res = (s32)(cnt - hpet_read(HPET_COUNTER)); | ||
169 | |||
170 | return res < HPET_MIN_CYCLES ? -ETIME : 0; | ||
171 | } | ||
172 | |||
173 | static irqreturn_t hpet_irq_handler(int irq, void *data) | ||
174 | { | ||
175 | int is_irq; | ||
176 | struct clock_event_device *cd; | ||
177 | unsigned int cpu = smp_processor_id(); | ||
178 | |||
179 | is_irq = hpet_read(HPET_STATUS); | ||
180 | if (is_irq & HPET_T0_IRS) { | ||
181 | /* clear the TIMER0 irq status register */ | ||
182 | hpet_write(HPET_STATUS, HPET_T0_IRS); | ||
183 | cd = &per_cpu(hpet_clockevent_device, cpu); | ||
184 | cd->event_handler(cd); | ||
185 | return IRQ_HANDLED; | ||
186 | } | ||
187 | return IRQ_NONE; | ||
188 | } | ||
189 | |||
190 | /* | ||
191 | * hpet address assignation and irq setting should be done in bios. | ||
192 | * but pmon don't do this, we just setup here directly. | ||
193 | * The operation under is normal. unfortunately, hpet_setup process | ||
194 | * is before pci initialize. | ||
195 | * | ||
196 | * { | ||
197 | * struct pci_dev *pdev; | ||
198 | * | ||
199 | * pdev = pci_get_device(PCI_VENDOR_ID_ATI, PCI_DEVICE_ID_ATI_SBX00_SMBUS, NULL); | ||
200 | * pci_write_config_word(pdev, SMBUS_PCI_REGB4, HPET_ADDR); | ||
201 | * | ||
202 | * ... | ||
203 | * } | ||
204 | */ | ||
205 | static void hpet_setup(void) | ||
206 | { | ||
207 | /* set hpet base address */ | ||
208 | smbus_write(SMBUS_PCI_REGB4, HPET_ADDR); | ||
209 | |||
210 | /* enable decoding of access to HPET MMIO*/ | ||
211 | smbus_enable(SMBUS_PCI_REG40, (1 << 28)); | ||
212 | |||
213 | /* HPET irq enable */ | ||
214 | smbus_enable(SMBUS_PCI_REG64, (1 << 10)); | ||
215 | |||
216 | hpet_enable_legacy_int(); | ||
217 | } | ||
218 | |||
219 | void __init setup_hpet_timer(void) | ||
220 | { | ||
221 | unsigned long flags = IRQF_NOBALANCING | IRQF_TIMER; | ||
222 | unsigned int cpu = smp_processor_id(); | ||
223 | struct clock_event_device *cd; | ||
224 | |||
225 | hpet_setup(); | ||
226 | |||
227 | cd = &per_cpu(hpet_clockevent_device, cpu); | ||
228 | cd->name = "hpet"; | ||
229 | cd->rating = 100; | ||
230 | cd->features = CLOCK_EVT_FEAT_PERIODIC | CLOCK_EVT_FEAT_ONESHOT; | ||
231 | cd->set_state_shutdown = hpet_set_state_shutdown; | ||
232 | cd->set_state_periodic = hpet_set_state_periodic; | ||
233 | cd->set_state_oneshot = hpet_set_state_oneshot; | ||
234 | cd->tick_resume = hpet_tick_resume; | ||
235 | cd->set_next_event = hpet_next_event; | ||
236 | cd->irq = HPET_T0_IRQ; | ||
237 | cd->cpumask = cpumask_of(cpu); | ||
238 | clockevent_set_clock(cd, HPET_FREQ); | ||
239 | cd->max_delta_ns = clockevent_delta2ns(0x7fffffff, cd); | ||
240 | cd->max_delta_ticks = 0x7fffffff; | ||
241 | cd->min_delta_ns = clockevent_delta2ns(HPET_MIN_PROG_DELTA, cd); | ||
242 | cd->min_delta_ticks = HPET_MIN_PROG_DELTA; | ||
243 | |||
244 | clockevents_register_device(cd); | ||
245 | if (request_irq(HPET_T0_IRQ, hpet_irq_handler, flags, "hpet", NULL)) | ||
246 | pr_err("Failed to request irq %d (hpet)\n", HPET_T0_IRQ); | ||
247 | pr_info("hpet clock event device register\n"); | ||
248 | } | ||
249 | |||
250 | static u64 hpet_read_counter(struct clocksource *cs) | ||
251 | { | ||
252 | return (u64)hpet_read(HPET_COUNTER); | ||
253 | } | ||
254 | |||
255 | static void hpet_suspend(struct clocksource *cs) | ||
256 | { | ||
257 | } | ||
258 | |||
259 | static void hpet_resume(struct clocksource *cs) | ||
260 | { | ||
261 | hpet_setup(); | ||
262 | hpet_restart_counter(); | ||
263 | } | ||
264 | |||
265 | static struct clocksource csrc_hpet = { | ||
266 | .name = "hpet", | ||
267 | /* mips clocksource rating is less than 300, so hpet is better. */ | ||
268 | .rating = 300, | ||
269 | .read = hpet_read_counter, | ||
270 | .mask = CLOCKSOURCE_MASK(32), | ||
271 | /* oneshot mode work normal with this flag */ | ||
272 | .flags = CLOCK_SOURCE_IS_CONTINUOUS, | ||
273 | .suspend = hpet_suspend, | ||
274 | .resume = hpet_resume, | ||
275 | .mult = 0, | ||
276 | .shift = 10, | ||
277 | }; | ||
278 | |||
279 | int __init init_hpet_clocksource(void) | ||
280 | { | ||
281 | csrc_hpet.mult = clocksource_hz2mult(HPET_FREQ, csrc_hpet.shift); | ||
282 | return clocksource_register_hz(&csrc_hpet, HPET_FREQ); | ||
283 | } | ||
284 | |||
285 | arch_initcall(init_hpet_clocksource); | ||