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