2 * Samsung exynos4210 Pulse Width Modulation Timer
4 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
7 * Evgeny Voevodin <e.voevodin@samsung.com>
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.
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.
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/>.
23 #include "hw/sysbus.h"
24 #include "qemu/timer.h"
25 #include "qemu-common.h"
26 #include "qemu/main-loop.h"
27 #include "hw/ptimer.h"
29 #include "hw/arm/exynos4210.h"
34 #define DPRINTF(fmt, ...) \
35 do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
36 ## __VA_ARGS__); } while (0)
38 #define DPRINTF(fmt, ...) do {} while (0)
41 #define EXYNOS4210_PWM_TIMERS_NUM 5
42 #define EXYNOS4210_PWM_REG_MEM_SIZE 0x50
61 #define TINT_CSTAT 0x0044
63 #define TCNTB(x) (0xC * (x))
64 #define TCMPB(x) (0xC * (x) + 1)
65 #define TCNTO(x) (0xC * (x) + 2)
67 #define GET_PRESCALER(reg, x) (((reg) & (0xFF << (8 * (x)))) >> 8 * (x))
68 #define GET_DIVIDER(reg, x) (1 << (((reg) & (0xF << (4 * (x)))) >> (4 * (x))))
71 * Attention! Timer4 doesn't have OUTPUT_INVERTER,
72 * so Auto Reload bit is not accessible by macros!
74 #define TCON_TIMER_BASE(x) (((x) ? 1 : 0) * 4 + 4 * (x))
75 #define TCON_TIMER_START(x) (1 << (TCON_TIMER_BASE(x) + 0))
76 #define TCON_TIMER_MANUAL_UPD(x) (1 << (TCON_TIMER_BASE(x) + 1))
77 #define TCON_TIMER_OUTPUT_INV(x) (1 << (TCON_TIMER_BASE(x) + 2))
78 #define TCON_TIMER_AUTO_RELOAD(x) (1 << (TCON_TIMER_BASE(x) + 3))
79 #define TCON_TIMER4_AUTO_RELOAD (1 << 22)
81 #define TINT_CSTAT_STATUS(x) (1 << (5 + (x)))
82 #define TINT_CSTAT_ENABLE(x) (1 << (x))
86 uint32_t id
; /* timer id */
87 qemu_irq irq
; /* local timer irq */
88 uint32_t freq
; /* timer frequency */
90 /* use ptimer.c to represent count down timer */
91 ptimer_state
*ptimer
; /* timer */
94 uint32_t reg_tcntb
; /* counter register buffer */
95 uint32_t reg_tcmpb
; /* compare register buffer */
97 struct Exynos4210PWMState
*parent
;
101 #define TYPE_EXYNOS4210_PWM "exynos4210.pwm"
102 #define EXYNOS4210_PWM(obj) \
103 OBJECT_CHECK(Exynos4210PWMState, (obj), TYPE_EXYNOS4210_PWM)
105 typedef struct Exynos4210PWMState
{
106 SysBusDevice parent_obj
;
110 uint32_t reg_tcfg
[2];
112 uint32_t reg_tint_cstat
;
114 Exynos4210PWM timer
[EXYNOS4210_PWM_TIMERS_NUM
];
116 } Exynos4210PWMState
;
119 static const VMStateDescription vmstate_exynos4210_pwm
= {
120 .name
= "exynos4210.pwm.pwm",
122 .minimum_version_id
= 1,
123 .fields
= (VMStateField
[]) {
124 VMSTATE_UINT32(id
, Exynos4210PWM
),
125 VMSTATE_UINT32(freq
, Exynos4210PWM
),
126 VMSTATE_PTIMER(ptimer
, Exynos4210PWM
),
127 VMSTATE_UINT32(reg_tcntb
, Exynos4210PWM
),
128 VMSTATE_UINT32(reg_tcmpb
, Exynos4210PWM
),
129 VMSTATE_END_OF_LIST()
133 static const VMStateDescription vmstate_exynos4210_pwm_state
= {
134 .name
= "exynos4210.pwm",
136 .minimum_version_id
= 1,
137 .fields
= (VMStateField
[]) {
138 VMSTATE_UINT32_ARRAY(reg_tcfg
, Exynos4210PWMState
, 2),
139 VMSTATE_UINT32(reg_tcon
, Exynos4210PWMState
),
140 VMSTATE_UINT32(reg_tint_cstat
, Exynos4210PWMState
),
141 VMSTATE_STRUCT_ARRAY(timer
, Exynos4210PWMState
,
142 EXYNOS4210_PWM_TIMERS_NUM
, 0,
143 vmstate_exynos4210_pwm
, Exynos4210PWM
),
144 VMSTATE_END_OF_LIST()
149 * PWM update frequency
151 static void exynos4210_pwm_update_freq(Exynos4210PWMState
*s
, uint32_t id
)
154 freq
= s
->timer
[id
].freq
;
156 s
->timer
[id
].freq
= 24000000 /
157 ((GET_PRESCALER(s
->reg_tcfg
[0], 1) + 1) *
158 (GET_DIVIDER(s
->reg_tcfg
[1], id
)));
160 s
->timer
[id
].freq
= 24000000 /
161 ((GET_PRESCALER(s
->reg_tcfg
[0], 0) + 1) *
162 (GET_DIVIDER(s
->reg_tcfg
[1], id
)));
165 if (freq
!= s
->timer
[id
].freq
) {
166 ptimer_set_freq(s
->timer
[id
].ptimer
, s
->timer
[id
].freq
);
167 DPRINTF("freq=%dHz\n", s
->timer
[id
].freq
);
172 * Counter tick handler
174 static void exynos4210_pwm_tick(void *opaque
)
176 Exynos4210PWM
*s
= (Exynos4210PWM
*)opaque
;
177 Exynos4210PWMState
*p
= (Exynos4210PWMState
*)s
->parent
;
181 DPRINTF("timer %d tick\n", id
);
184 p
->reg_tint_cstat
|= TINT_CSTAT_STATUS(id
);
187 if (p
->reg_tint_cstat
& TINT_CSTAT_ENABLE(id
)) {
188 DPRINTF("timer %d IRQ\n", id
);
189 qemu_irq_raise(p
->timer
[id
].irq
);
194 cmp
= p
->reg_tcon
& TCON_TIMER_AUTO_RELOAD(id
);
196 cmp
= p
->reg_tcon
& TCON_TIMER4_AUTO_RELOAD
;
200 DPRINTF("auto reload timer %d count to %x\n", id
,
201 p
->timer
[id
].reg_tcntb
);
202 ptimer_set_count(p
->timer
[id
].ptimer
, p
->timer
[id
].reg_tcntb
);
203 ptimer_run(p
->timer
[id
].ptimer
, 1);
205 /* stop timer, set status to STOP, see Basic Timer Operation */
206 p
->reg_tcon
&= ~TCON_TIMER_START(id
);
207 ptimer_stop(p
->timer
[id
].ptimer
);
214 static uint64_t exynos4210_pwm_read(void *opaque
, hwaddr offset
,
217 Exynos4210PWMState
*s
= (Exynos4210PWMState
*)opaque
;
222 case TCFG0
: case TCFG1
:
223 index
= (offset
- TCFG0
) >> 2;
224 value
= s
->reg_tcfg
[index
];
231 case TCNTB0
: case TCNTB1
:
232 case TCNTB2
: case TCNTB3
: case TCNTB4
:
233 index
= (offset
- TCNTB0
) / 0xC;
234 value
= s
->timer
[index
].reg_tcntb
;
237 case TCMPB0
: case TCMPB1
:
238 case TCMPB2
: case TCMPB3
:
239 index
= (offset
- TCMPB0
) / 0xC;
240 value
= s
->timer
[index
].reg_tcmpb
;
243 case TCNTO0
: case TCNTO1
:
244 case TCNTO2
: case TCNTO3
: case TCNTO4
:
245 index
= (offset
== TCNTO4
) ? 4 : (offset
- TCNTO0
) / 0xC;
246 value
= ptimer_get_count(s
->timer
[index
].ptimer
);
250 value
= s
->reg_tint_cstat
;
255 "[exynos4210.pwm: bad read offset " TARGET_FMT_plx
"]\n",
265 static void exynos4210_pwm_write(void *opaque
, hwaddr offset
,
266 uint64_t value
, unsigned size
)
268 Exynos4210PWMState
*s
= (Exynos4210PWMState
*)opaque
;
274 case TCFG0
: case TCFG1
:
275 index
= (offset
- TCFG0
) >> 2;
276 s
->reg_tcfg
[index
] = value
;
278 /* update timers frequencies */
279 for (i
= 0; i
< EXYNOS4210_PWM_TIMERS_NUM
; i
++) {
280 exynos4210_pwm_update_freq(s
, s
->timer
[i
].id
);
285 for (i
= 0; i
< EXYNOS4210_PWM_TIMERS_NUM
; i
++) {
286 if ((value
& TCON_TIMER_MANUAL_UPD(i
)) >
287 (s
->reg_tcon
& TCON_TIMER_MANUAL_UPD(i
))) {
289 * TCNTB and TCMPB are loaded into TCNT and TCMP.
293 /* this will start timer to run, this ok, because
294 * during processing start bit timer will be stopped
296 ptimer_set_count(s
->timer
[i
].ptimer
, s
->timer
[i
].reg_tcntb
);
297 DPRINTF("set timer %d count to %x\n", i
,
298 s
->timer
[i
].reg_tcntb
);
301 if ((value
& TCON_TIMER_START(i
)) >
302 (s
->reg_tcon
& TCON_TIMER_START(i
))) {
303 /* changed to start */
304 ptimer_run(s
->timer
[i
].ptimer
, 1);
305 DPRINTF("run timer %d\n", i
);
308 if ((value
& TCON_TIMER_START(i
)) <
309 (s
->reg_tcon
& TCON_TIMER_START(i
))) {
310 /* changed to stop */
311 ptimer_stop(s
->timer
[i
].ptimer
);
312 DPRINTF("stop timer %d\n", i
);
318 case TCNTB0
: case TCNTB1
:
319 case TCNTB2
: case TCNTB3
: case TCNTB4
:
320 index
= (offset
- TCNTB0
) / 0xC;
321 s
->timer
[index
].reg_tcntb
= value
;
324 case TCMPB0
: case TCMPB1
:
325 case TCMPB2
: case TCMPB3
:
326 index
= (offset
- TCMPB0
) / 0xC;
327 s
->timer
[index
].reg_tcmpb
= value
;
331 new_val
= (s
->reg_tint_cstat
& 0x3E0) + (0x1F & value
);
332 new_val
&= ~(0x3E0 & value
);
334 for (i
= 0; i
< EXYNOS4210_PWM_TIMERS_NUM
; i
++) {
335 if ((new_val
& TINT_CSTAT_STATUS(i
)) <
336 (s
->reg_tint_cstat
& TINT_CSTAT_STATUS(i
))) {
337 qemu_irq_lower(s
->timer
[i
].irq
);
341 s
->reg_tint_cstat
= new_val
;
346 "[exynos4210.pwm: bad write offset " TARGET_FMT_plx
"]\n",
354 * Set default values to timer fields and registers
356 static void exynos4210_pwm_reset(DeviceState
*d
)
358 Exynos4210PWMState
*s
= EXYNOS4210_PWM(d
);
360 s
->reg_tcfg
[0] = 0x0101;
361 s
->reg_tcfg
[1] = 0x0;
363 s
->reg_tint_cstat
= 0;
364 for (i
= 0; i
< EXYNOS4210_PWM_TIMERS_NUM
; i
++) {
365 s
->timer
[i
].reg_tcmpb
= 0;
366 s
->timer
[i
].reg_tcntb
= 0;
368 exynos4210_pwm_update_freq(s
, s
->timer
[i
].id
);
369 ptimer_stop(s
->timer
[i
].ptimer
);
373 static const MemoryRegionOps exynos4210_pwm_ops
= {
374 .read
= exynos4210_pwm_read
,
375 .write
= exynos4210_pwm_write
,
376 .endianness
= DEVICE_NATIVE_ENDIAN
,
380 * PWM timer initialization
382 static int exynos4210_pwm_init(SysBusDevice
*dev
)
384 Exynos4210PWMState
*s
= EXYNOS4210_PWM(dev
);
388 for (i
= 0; i
< EXYNOS4210_PWM_TIMERS_NUM
; i
++) {
389 bh
= qemu_bh_new(exynos4210_pwm_tick
, &s
->timer
[i
]);
390 sysbus_init_irq(dev
, &s
->timer
[i
].irq
);
391 s
->timer
[i
].ptimer
= ptimer_init(bh
);
393 s
->timer
[i
].parent
= s
;
396 memory_region_init_io(&s
->iomem
, OBJECT(s
), &exynos4210_pwm_ops
, s
,
397 "exynos4210-pwm", EXYNOS4210_PWM_REG_MEM_SIZE
);
398 sysbus_init_mmio(dev
, &s
->iomem
);
403 static void exynos4210_pwm_class_init(ObjectClass
*klass
, void *data
)
405 DeviceClass
*dc
= DEVICE_CLASS(klass
);
406 SysBusDeviceClass
*k
= SYS_BUS_DEVICE_CLASS(klass
);
408 k
->init
= exynos4210_pwm_init
;
409 dc
->reset
= exynos4210_pwm_reset
;
410 dc
->vmsd
= &vmstate_exynos4210_pwm_state
;
413 static const TypeInfo exynos4210_pwm_info
= {
414 .name
= TYPE_EXYNOS4210_PWM
,
415 .parent
= TYPE_SYS_BUS_DEVICE
,
416 .instance_size
= sizeof(Exynos4210PWMState
),
417 .class_init
= exynos4210_pwm_class_init
,
420 static void exynos4210_pwm_register_types(void)
422 type_register_static(&exynos4210_pwm_info
);
425 type_init(exynos4210_pwm_register_types
)