1 | /* |
2 | * Samsung exynos4210 Pulse Width Modulation Timer |
3 | * |
4 | * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. |
5 | * All rights reserved. |
6 | * |
7 | * Evgeny Voevodin <e.voevodin@samsung.com> |
8 | * |
9 | * This program is free software; you can redistribute it and/or modify it |
10 | * under the terms of the GNU General Public License as published by the |
11 | * Free Software Foundation; either version 2 of the License, or (at your |
12 | * option) any later version. |
13 | * |
14 | * This program is distributed in the hope that it will be useful, |
15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | * See the GNU General Public License for more details. |
18 | * |
19 | * You should have received a copy of the GNU General Public License along |
20 | * with this program; if not, see <http://www.gnu.org/licenses/>. |
21 | */ |
22 | |
23 | #include "qemu/osdep.h" |
24 | #include "qemu/log.h" |
25 | #include "hw/sysbus.h" |
26 | #include "migration/vmstate.h" |
27 | #include "qemu/timer.h" |
28 | #include "qemu/main-loop.h" |
29 | #include "qemu/module.h" |
30 | #include "hw/ptimer.h" |
31 | |
32 | #include "hw/arm/exynos4210.h" |
33 | #include "hw/irq.h" |
34 | |
35 | //#define DEBUG_PWM |
36 | |
37 | #ifdef DEBUG_PWM |
38 | #define DPRINTF(fmt, ...) \ |
39 | do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \ |
40 | ## __VA_ARGS__); } while (0) |
41 | #else |
42 | #define DPRINTF(fmt, ...) do {} while (0) |
43 | #endif |
44 | |
45 | #define EXYNOS4210_PWM_TIMERS_NUM 5 |
46 | #define EXYNOS4210_PWM_REG_MEM_SIZE 0x50 |
47 | |
48 | #define TCFG0 0x0000 |
49 | #define TCFG1 0x0004 |
50 | #define TCON 0x0008 |
51 | #define TCNTB0 0x000C |
52 | #define TCMPB0 0x0010 |
53 | #define TCNTO0 0x0014 |
54 | #define TCNTB1 0x0018 |
55 | #define TCMPB1 0x001C |
56 | #define TCNTO1 0x0020 |
57 | #define TCNTB2 0x0024 |
58 | #define TCMPB2 0x0028 |
59 | #define TCNTO2 0x002C |
60 | #define TCNTB3 0x0030 |
61 | #define TCMPB3 0x0034 |
62 | #define TCNTO3 0x0038 |
63 | #define TCNTB4 0x003C |
64 | #define TCNTO4 0x0040 |
65 | #define TINT_CSTAT 0x0044 |
66 | |
67 | #define TCNTB(x) (0xC * (x)) |
68 | #define TCMPB(x) (0xC * (x) + 1) |
69 | #define TCNTO(x) (0xC * (x) + 2) |
70 | |
71 | #define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x)) |
72 | #define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x)))) |
73 | |
74 | /* |
75 | * Attention! Timer4 doesn't have OUTPUT_INVERTER, |
76 | * so Auto Reload bit is not accessible by macros! |
77 | */ |
78 | #define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x)) |
79 | #define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0)) |
80 | #define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1)) |
81 | #define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2)) |
82 | #define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3)) |
83 | #define TCON_TIMER4_AUTO_RELOAD (1 << 22) |
84 | |
85 | #define TINT_CSTAT_STATUS(x) (1 << (5 + (x))) |
86 | #define TINT_CSTAT_ENABLE(x) (1 << (x)) |
87 | |
88 | /* timer struct */ |
89 | typedef struct { |
90 | uint32_t id; /* timer id */ |
91 | qemu_irq irq; /* local timer irq */ |
92 | uint32_t freq; /* timer frequency */ |
93 | |
94 | /* use ptimer.c to represent count down timer */ |
95 | ptimer_state *ptimer; /* timer */ |
96 | |
97 | /* registers */ |
98 | uint32_t reg_tcntb; /* counter register buffer */ |
99 | uint32_t reg_tcmpb; /* compare register buffer */ |
100 | |
101 | struct Exynos4210PWMState *parent; |
102 | |
103 | } Exynos4210PWM; |
104 | |
105 | #define TYPE_EXYNOS4210_PWM "exynos4210.pwm" |
106 | #define EXYNOS4210_PWM(obj) \ |
107 | OBJECT_CHECK(Exynos4210PWMState, (obj), TYPE_EXYNOS4210_PWM) |
108 | |
109 | typedef struct Exynos4210PWMState { |
110 | SysBusDevice parent_obj; |
111 | |
112 | MemoryRegion iomem; |
113 | |
114 | uint32_t reg_tcfg[2]; |
115 | uint32_t reg_tcon; |
116 | uint32_t reg_tint_cstat; |
117 | |
118 | Exynos4210PWM timer[EXYNOS4210_PWM_TIMERS_NUM]; |
119 | |
120 | } Exynos4210PWMState; |
121 | |
122 | /*** VMState ***/ |
123 | static const VMStateDescription vmstate_exynos4210_pwm = { |
124 | .name = "exynos4210.pwm.pwm" , |
125 | .version_id = 1, |
126 | .minimum_version_id = 1, |
127 | .fields = (VMStateField[]) { |
128 | VMSTATE_UINT32(id, Exynos4210PWM), |
129 | VMSTATE_UINT32(freq, Exynos4210PWM), |
130 | VMSTATE_PTIMER(ptimer, Exynos4210PWM), |
131 | VMSTATE_UINT32(reg_tcntb, Exynos4210PWM), |
132 | VMSTATE_UINT32(reg_tcmpb, Exynos4210PWM), |
133 | VMSTATE_END_OF_LIST() |
134 | } |
135 | }; |
136 | |
137 | static const VMStateDescription vmstate_exynos4210_pwm_state = { |
138 | .name = "exynos4210.pwm" , |
139 | .version_id = 1, |
140 | .minimum_version_id = 1, |
141 | .fields = (VMStateField[]) { |
142 | VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2), |
143 | VMSTATE_UINT32(reg_tcon, Exynos4210PWMState), |
144 | VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState), |
145 | VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState, |
146 | EXYNOS4210_PWM_TIMERS_NUM, 0, |
147 | vmstate_exynos4210_pwm, Exynos4210PWM), |
148 | VMSTATE_END_OF_LIST() |
149 | } |
150 | }; |
151 | |
152 | /* |
153 | * PWM update frequency |
154 | */ |
155 | static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id) |
156 | { |
157 | uint32_t freq; |
158 | freq = s->timer[id].freq; |
159 | if (id > 1) { |
160 | s->timer[id].freq = 24000000 / |
161 | ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) * |
162 | (GET_DIVIDER(s->reg_tcfg[1], id))); |
163 | } else { |
164 | s->timer[id].freq = 24000000 / |
165 | ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) * |
166 | (GET_DIVIDER(s->reg_tcfg[1], id))); |
167 | } |
168 | |
169 | if (freq != s->timer[id].freq) { |
170 | ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq); |
171 | DPRINTF("freq=%dHz\n" , s->timer[id].freq); |
172 | } |
173 | } |
174 | |
175 | /* |
176 | * Counter tick handler |
177 | */ |
178 | static void exynos4210_pwm_tick(void *opaque) |
179 | { |
180 | Exynos4210PWM *s = (Exynos4210PWM *)opaque; |
181 | Exynos4210PWMState *p = (Exynos4210PWMState *)s->parent; |
182 | uint32_t id = s->id; |
183 | bool cmp; |
184 | |
185 | DPRINTF("timer %d tick\n" , id); |
186 | |
187 | /* set irq status */ |
188 | p->reg_tint_cstat |= TINT_CSTAT_STATUS(id); |
189 | |
190 | /* raise IRQ */ |
191 | if (p->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) { |
192 | DPRINTF("timer %d IRQ\n" , id); |
193 | qemu_irq_raise(p->timer[id].irq); |
194 | } |
195 | |
196 | /* reload timer */ |
197 | if (id != 4) { |
198 | cmp = p->reg_tcon & TCON_TIMER_AUTO_RELOAD(id); |
199 | } else { |
200 | cmp = p->reg_tcon & TCON_TIMER4_AUTO_RELOAD; |
201 | } |
202 | |
203 | if (cmp) { |
204 | DPRINTF("auto reload timer %d count to %x\n" , id, |
205 | p->timer[id].reg_tcntb); |
206 | ptimer_set_count(p->timer[id].ptimer, p->timer[id].reg_tcntb); |
207 | ptimer_run(p->timer[id].ptimer, 1); |
208 | } else { |
209 | /* stop timer, set status to STOP, see Basic Timer Operation */ |
210 | p->reg_tcon &= ~TCON_TIMER_START(id); |
211 | ptimer_stop(p->timer[id].ptimer); |
212 | } |
213 | } |
214 | |
215 | /* |
216 | * PWM Read |
217 | */ |
218 | static uint64_t exynos4210_pwm_read(void *opaque, hwaddr offset, |
219 | unsigned size) |
220 | { |
221 | Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; |
222 | uint32_t value = 0; |
223 | int index; |
224 | |
225 | switch (offset) { |
226 | case TCFG0: case TCFG1: |
227 | index = (offset - TCFG0) >> 2; |
228 | value = s->reg_tcfg[index]; |
229 | break; |
230 | |
231 | case TCON: |
232 | value = s->reg_tcon; |
233 | break; |
234 | |
235 | case TCNTB0: case TCNTB1: |
236 | case TCNTB2: case TCNTB3: case TCNTB4: |
237 | index = (offset - TCNTB0) / 0xC; |
238 | value = s->timer[index].reg_tcntb; |
239 | break; |
240 | |
241 | case TCMPB0: case TCMPB1: |
242 | case TCMPB2: case TCMPB3: |
243 | index = (offset - TCMPB0) / 0xC; |
244 | value = s->timer[index].reg_tcmpb; |
245 | break; |
246 | |
247 | case TCNTO0: case TCNTO1: |
248 | case TCNTO2: case TCNTO3: case TCNTO4: |
249 | index = (offset == TCNTO4) ? 4 : (offset - TCNTO0) / 0xC; |
250 | value = ptimer_get_count(s->timer[index].ptimer); |
251 | break; |
252 | |
253 | case TINT_CSTAT: |
254 | value = s->reg_tint_cstat; |
255 | break; |
256 | |
257 | default: |
258 | qemu_log_mask(LOG_GUEST_ERROR, |
259 | "exynos4210.pwm: bad read offset " TARGET_FMT_plx, |
260 | offset); |
261 | break; |
262 | } |
263 | return value; |
264 | } |
265 | |
266 | /* |
267 | * PWM Write |
268 | */ |
269 | static void exynos4210_pwm_write(void *opaque, hwaddr offset, |
270 | uint64_t value, unsigned size) |
271 | { |
272 | Exynos4210PWMState *s = (Exynos4210PWMState *)opaque; |
273 | int index; |
274 | uint32_t new_val; |
275 | int i; |
276 | |
277 | switch (offset) { |
278 | case TCFG0: case TCFG1: |
279 | index = (offset - TCFG0) >> 2; |
280 | s->reg_tcfg[index] = value; |
281 | |
282 | /* update timers frequencies */ |
283 | for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { |
284 | exynos4210_pwm_update_freq(s, s->timer[i].id); |
285 | } |
286 | break; |
287 | |
288 | case TCON: |
289 | for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { |
290 | if ((value & TCON_TIMER_MANUAL_UPD(i)) > |
291 | (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) { |
292 | /* |
293 | * TCNTB and TCMPB are loaded into TCNT and TCMP. |
294 | * Update timers. |
295 | */ |
296 | |
297 | /* this will start timer to run, this ok, because |
298 | * during processing start bit timer will be stopped |
299 | * if needed */ |
300 | ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb); |
301 | DPRINTF("set timer %d count to %x\n" , i, |
302 | s->timer[i].reg_tcntb); |
303 | } |
304 | |
305 | if ((value & TCON_TIMER_START(i)) > |
306 | (s->reg_tcon & TCON_TIMER_START(i))) { |
307 | /* changed to start */ |
308 | ptimer_run(s->timer[i].ptimer, 1); |
309 | DPRINTF("run timer %d\n" , i); |
310 | } |
311 | |
312 | if ((value & TCON_TIMER_START(i)) < |
313 | (s->reg_tcon & TCON_TIMER_START(i))) { |
314 | /* changed to stop */ |
315 | ptimer_stop(s->timer[i].ptimer); |
316 | DPRINTF("stop timer %d\n" , i); |
317 | } |
318 | } |
319 | s->reg_tcon = value; |
320 | break; |
321 | |
322 | case TCNTB0: case TCNTB1: |
323 | case TCNTB2: case TCNTB3: case TCNTB4: |
324 | index = (offset - TCNTB0) / 0xC; |
325 | s->timer[index].reg_tcntb = value; |
326 | break; |
327 | |
328 | case TCMPB0: case TCMPB1: |
329 | case TCMPB2: case TCMPB3: |
330 | index = (offset - TCMPB0) / 0xC; |
331 | s->timer[index].reg_tcmpb = value; |
332 | break; |
333 | |
334 | case TINT_CSTAT: |
335 | new_val = (s->reg_tint_cstat & 0x3E0) + (0x1F & value); |
336 | new_val &= ~(0x3E0 & value); |
337 | |
338 | for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { |
339 | if ((new_val & TINT_CSTAT_STATUS(i)) < |
340 | (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) { |
341 | qemu_irq_lower(s->timer[i].irq); |
342 | } |
343 | } |
344 | |
345 | s->reg_tint_cstat = new_val; |
346 | break; |
347 | |
348 | default: |
349 | qemu_log_mask(LOG_GUEST_ERROR, |
350 | "exynos4210.pwm: bad write offset " TARGET_FMT_plx, |
351 | offset); |
352 | break; |
353 | |
354 | } |
355 | } |
356 | |
357 | /* |
358 | * Set default values to timer fields and registers |
359 | */ |
360 | static void exynos4210_pwm_reset(DeviceState *d) |
361 | { |
362 | Exynos4210PWMState *s = EXYNOS4210_PWM(d); |
363 | int i; |
364 | s->reg_tcfg[0] = 0x0101; |
365 | s->reg_tcfg[1] = 0x0; |
366 | s->reg_tcon = 0; |
367 | s->reg_tint_cstat = 0; |
368 | for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { |
369 | s->timer[i].reg_tcmpb = 0; |
370 | s->timer[i].reg_tcntb = 0; |
371 | |
372 | exynos4210_pwm_update_freq(s, s->timer[i].id); |
373 | ptimer_stop(s->timer[i].ptimer); |
374 | } |
375 | } |
376 | |
377 | static const MemoryRegionOps exynos4210_pwm_ops = { |
378 | .read = exynos4210_pwm_read, |
379 | .write = exynos4210_pwm_write, |
380 | .endianness = DEVICE_NATIVE_ENDIAN, |
381 | }; |
382 | |
383 | /* |
384 | * PWM timer initialization |
385 | */ |
386 | static void exynos4210_pwm_init(Object *obj) |
387 | { |
388 | Exynos4210PWMState *s = EXYNOS4210_PWM(obj); |
389 | SysBusDevice *dev = SYS_BUS_DEVICE(obj); |
390 | int i; |
391 | QEMUBH *bh; |
392 | |
393 | for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) { |
394 | bh = qemu_bh_new(exynos4210_pwm_tick, &s->timer[i]); |
395 | sysbus_init_irq(dev, &s->timer[i].irq); |
396 | s->timer[i].ptimer = ptimer_init(bh, PTIMER_POLICY_DEFAULT); |
397 | s->timer[i].id = i; |
398 | s->timer[i].parent = s; |
399 | } |
400 | |
401 | memory_region_init_io(&s->iomem, obj, &exynos4210_pwm_ops, s, |
402 | "exynos4210-pwm" , EXYNOS4210_PWM_REG_MEM_SIZE); |
403 | sysbus_init_mmio(dev, &s->iomem); |
404 | } |
405 | |
406 | static void exynos4210_pwm_class_init(ObjectClass *klass, void *data) |
407 | { |
408 | DeviceClass *dc = DEVICE_CLASS(klass); |
409 | |
410 | dc->reset = exynos4210_pwm_reset; |
411 | dc->vmsd = &vmstate_exynos4210_pwm_state; |
412 | } |
413 | |
414 | static const TypeInfo exynos4210_pwm_info = { |
415 | .name = TYPE_EXYNOS4210_PWM, |
416 | .parent = TYPE_SYS_BUS_DEVICE, |
417 | .instance_size = sizeof(Exynos4210PWMState), |
418 | .instance_init = exynos4210_pwm_init, |
419 | .class_init = exynos4210_pwm_class_init, |
420 | }; |
421 | |
422 | static void exynos4210_pwm_register_types(void) |
423 | { |
424 | type_register_static(&exynos4210_pwm_info); |
425 | } |
426 | |
427 | type_init(exynos4210_pwm_register_types) |
428 | |