[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH v3 06/14] ARM: exynos4210: PWM support.
From: |
Evgeny Voevodin |
Subject: |
[Qemu-devel] [PATCH v3 06/14] ARM: exynos4210: PWM support. |
Date: |
Mon, 12 Dec 2011 10:43:18 +0400 |
Signed-off-by: Evgeny Voevodin <address@hidden>
---
Makefile.target | 2 +-
hw/exynos4210.c | 12 ++
hw/exynos4210_pwm.c | 433 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 446 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_pwm.c
diff --git a/Makefile.target b/Makefile.target
index 779c9d4..709e9e2 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o
pl110.o pl181.o pl190.o
obj-arm-y += versatile_pci.o
obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
- exynos4210_combiner.o
+ exynos4210_combiner.o exynos4210_pwm.o
obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
obj-arm-y += pl061.o
obj-arm-y += arm-semi.o
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 45d427e..7a7760d 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -65,6 +65,9 @@
/* SFR Base Address for CMUs */
#define EXYNOS4210_CMU_BASE_ADDR 0x10030000
+/* PWM */
+#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000
+
/* UART's definitions */
#define EXYNOS4210_UART_BASE_ADDR 0x13800000
#define EXYNOS4210_UART_SHIFT 0x00010000
@@ -364,6 +367,15 @@ static void exynos4210_init(ram_addr_t ram_size,
/* CMU */
sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
+ /* PWM */
+ sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
+ irq_table[exynos4210_get_irq(22, 0)],
+ irq_table[exynos4210_get_irq(22, 1)],
+ irq_table[exynos4210_get_irq(22, 2)],
+ irq_table[exynos4210_get_irq(22, 3)],
+ irq_table[exynos4210_get_irq(22, 4)],
+ NULL);
+
/*** UARTs ***/
for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
diff --git a/hw/exynos4210_pwm.c b/hw/exynos4210_pwm.c
new file mode 100644
index 0000000..1e80f10
--- /dev/null
+++ b/hw/exynos4210_pwm.c
@@ -0,0 +1,433 @@
+/*
+ * Samsung exynos4210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <address@hidden>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+ * See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc., 51
+ * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "hw.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_PWM
+
+#ifdef DEBUG_PWM
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define EXYNOS4210_PWM_TIMERS_NUM 5
+#define EXYNOS4210_PWM_REG_MEM_SIZE 0x50
+
+#define TCFG0 0x0000
+#define TCFG1 0x0004
+#define TCON 0x0008
+#define TCNTB0 0x000C
+#define TCMPB0 0x0010
+#define TCNTO0 0x0014
+#define TCNTB1 0x0018
+#define TCMPB1 0x001C
+#define TCNTO1 0x0020
+#define TCNTB2 0x0024
+#define TCMPB2 0x0028
+#define TCNTO2 0x002C
+#define TCNTB3 0x0030
+#define TCMPB3 0x0034
+#define TCNTO3 0x0038
+#define TCNTB4 0x003C
+#define TCNTO4 0x0040
+#define TINT_CSTAT 0x0044
+
+#define TCNTB(x) (0xC*x)
+#define TCMPB(x) (0xC*x+1)
+#define TCNTO(x) (0xC*x+2)
+
+#define GET_PRESCALER(reg, x) ((reg&(0xFF<<(8*x)))>>8*x)
+#define GET_DIVIDER(reg, x) (1<<((0xF<<(4*x))>>(4*x)))
+
+/*
+ * Attention! Timer4 doesn't have OUTPUT_INVERTER,
+ * so Auto Reload bit is not accessible by macros!
+ */
+#define TCON_TIMER_BASE(x) ((x ? 1 : 0)*4 + 4*x)
+#define TCON_TIMER_START(x) (1<<(TCON_TIMER_BASE(x) + 0))
+#define TCON_TIMER_MANUAL_UPD(x) (1<<(TCON_TIMER_BASE(x) + 1))
+#define TCON_TIMER_OUTPUT_INV(x) (1<<(TCON_TIMER_BASE(x) + 2))
+#define TCON_TIMER_AUTO_RELOAD(x) (1<<(TCON_TIMER_BASE(x) + 3))
+#define TCON_TIMER4_AUTO_RELOAD (1<<22)
+
+#define TINT_CSTAT_STATUS(x) (1<<(5+x))
+#define TINT_CSTAT_ENABLE(x) (1<<x)
+
+/* timer struct */
+typedef struct {
+ uint32_t id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+ uint32_t freq; /* timer frequency */
+
+ /* use ptimer.c to represent count down timer */
+ ptimer_state *ptimer; /* timer */
+
+ /* registers */
+ uint32_t reg_tcntb; /* counter register buffer */
+ uint32_t reg_tcmpb; /* compare register buffer */
+
+} Exynos4210_pwm;
+
+
+typedef struct Exynos4210PWMState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg_tcfg[2];
+ uint32_t reg_tcon;
+ uint32_t reg_tint_cstat;
+
+ Exynos4210CmuClock clk; /* clock source for timer */
+ Exynos4210_pwm timer[EXYNOS4210_PWM_TIMERS_NUM];
+
+} Exynos4210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_Exynos4210_pwm = {
+ .name = "exynos4210.pwm.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, Exynos4210_pwm),
+ VMSTATE_UINT32(freq, Exynos4210_pwm),
+ VMSTATE_UINT32(reg_tcntb, Exynos4210_pwm),
+ VMSTATE_UINT32(reg_tcmpb, Exynos4210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_Exynos4210PWMState = {
+ .name = "exynos4210.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg_tcfg, Exynos4210PWMState, 2),
+ VMSTATE_UINT32(reg_tcon, Exynos4210PWMState),
+ VMSTATE_UINT32(reg_tint_cstat, Exynos4210PWMState),
+ VMSTATE_STRUCT_ARRAY(timer, Exynos4210PWMState,
+ EXYNOS4210_PWM_TIMERS_NUM, 0,
+ VMState_Exynos4210_pwm, Exynos4210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * PWM update frequency
+ */
+static void exynos4210_pwm_update_freq(Exynos4210PWMState *s, uint32_t id)
+{
+ uint32_t freq;
+ freq = s->timer[id].freq;
+ if (id > 1) {
+ s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) /
+ ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
+ (1<<GET_DIVIDER(s->reg_tcfg[1], id)));
+ } else {
+ s->timer[id].freq = exynos4210_cmu_get_rate(s->clk) /
+ ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
+ (1<<GET_DIVIDER(s->preg_tcfg[1], id)));
+ }
+
+ if (freq != s->timer[id].freq) {
+ ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
+ DPRINTF("freq=%dHz\n", s->timer[id].freq);
+ }
+}
+
+/*
+ * Counter tick handler
+ */
+static void exynos4210_pwm_tick(void *opaque, uint32_t id)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+ bool cmp;
+
+ DPRINTF("timer %d tick\n", id);
+
+ /* set irq status */
+ s->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
+
+ /* raise IRQ */
+ if (s->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
+ DPRINTF("timer %d IRQ\n", id);
+ qemu_irq_raise(s->timer[id].irq);
+ }
+
+ /* reload timer */
+ if (id != 4) {
+ cmp = s->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
+ } else {
+ cmp = s->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
+ }
+
+ if (cmp) {
+ DPRINTF("auto reload timer %d count to %x\n", id,
+ s->timer[id].reg_tcntb);
+ ptimer_set_count(s->timer[id].ptimer, s->timer[id].reg_tcntb);
+ ptimer_run(s->timer[id].ptimer, 1);
+ } else {
+ /* stop timer, set status to STOP, see Basic Timer Operation */
+ s->reg_tcon = ~TCON_TIMER_START(id);
+ ptimer_stop(s->timer[id].ptimer);
+ }
+}
+
+static void exynos4210_pwm_tick0(void *opaque)
+{
+ exynos4210_pwm_tick(opaque, 0);
+}
+static void exynos4210_pwm_tick1(void *opaque)
+{
+ exynos4210_pwm_tick(opaque, 1);
+}
+static void exynos4210_pwm_tick2(void *opaque)
+{
+ exynos4210_pwm_tick(opaque, 2);
+}
+static void exynos4210_pwm_tick3(void *opaque)
+{
+ exynos4210_pwm_tick(opaque, 3);
+}
+static void exynos4210_pwm_tick4(void *opaque)
+{
+ exynos4210_pwm_tick(opaque, 4);
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t exynos4210_pwm_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+ uint32_t value = 0;
+ int index;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0)>>2;
+ value = s->reg_tcfg[index];
+ break;
+
+ case TCON:
+ value = s->reg_tcon;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0)/0xC;
+ value = s->timer[index].reg_tcntb;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0)/0xC;
+ value = s->timer[index].reg_tcmpb;
+ break;
+
+ case TCNTO0: case TCNTO1:
+ case TCNTO2: case TCNTO3: case TCNTO4:
+ index = (offset == TCNTO4) ? 4 : (offset - TCNTO0)/0xC;
+ value = ptimer_get_count(s->timer[index].ptimer);
+ break;
+
+ case TINT_CSTAT:
+ value = s->reg_tint_cstat;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * PWM Write
+ */
+static void exynos4210_pwm_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210PWMState *s = (Exynos4210PWMState *)opaque;
+ int index;
+ uint32_t new_val;
+ int i;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0)>>2;
+ s->reg_tcfg[index] = value;
+
+ /* update timers frequencies */
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ }
+ break;
+
+ case TCON:
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ if ((value & TCON_TIMER_MANUAL_UPD(i)) >
+ (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
+ /*
+ * TCNTB and TCMPB are loaded into TCNT and TCMP.
+ * Update timers.
+ */
+
+ /* this will start timer to run, this ok, because
+ * during processing start bit timer will be stopped
+ * if needed */
+ ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
+ DPRINTF("set timer %d count to %x\n", i,
+ s->timer[i].reg_tcntb);
+ }
+
+ if ((value & TCON_TIMER_START(i)) >
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to start */
+ ptimer_run(s->timer[i].ptimer, 1);
+ DPRINTF("run timer %d\n", i);
+ }
+
+ if ((value & TCON_TIMER_START(i)) <
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to stop */
+ ptimer_stop(s->timer[i].ptimer);
+ DPRINTF("stop timer %d\n", i);
+ }
+ }
+ s->reg_tcon = value;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0)/0xC;
+ s->timer[index].reg_tcntb = value;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0)/0xC;
+ s->timer[index].reg_tcmpb = value;
+ break;
+
+ case TINT_CSTAT:
+ new_val = (s->reg_tint_cstat&0x3E0) + (0x1F & value);
+ new_val &= ~(0x3E0 & value);
+
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ if ((new_val & TINT_CSTAT_STATUS(i)) <
+ (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
+ qemu_irq_lower(s->timer[i].irq);
+ }
+ }
+
+ s->reg_tint_cstat = new_val;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[exynos4210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void exynos4210_pwm_reset(Exynos4210PWMState *s)
+{
+ int i;
+ s->reg_tcfg[0] = 0x0101;
+ s->reg_tcfg[1] = 0x0;
+ s->reg_tcon = 0;
+ s->reg_tint_cstat = 0;
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ s->timer[i].reg_tcmpb = 0;
+ s->timer[i].reg_tcntb = 0;
+
+ exynos4210_pwm_update_freq(s, s->timer[i].id);
+ ptimer_stop(s->timer[i].ptimer);
+ }
+}
+
+static const MemoryRegionOps exynos4210_pwm_ops = {
+ .read = exynos4210_pwm_read,
+ .write = exynos4210_pwm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int exynos4210_pwm_init(SysBusDevice *dev)
+{
+ Exynos4210PWMState *s = FROM_SYSBUS(Exynos4210PWMState, dev);
+ int i;
+ QEMUBH * bh[EXYNOS4210_PWM_TIMERS_NUM];
+
+ s->clk = ACLK_100;
+
+ bh[0] = qemu_bh_new(exynos4210_pwm_tick0, s);
+ bh[1] = qemu_bh_new(exynos4210_pwm_tick1, s);
+ bh[2] = qemu_bh_new(exynos4210_pwm_tick2, s);
+ bh[3] = qemu_bh_new(exynos4210_pwm_tick3, s);
+ bh[4] = qemu_bh_new(exynos4210_pwm_tick4, s);
+
+ for (i = 0; i < EXYNOS4210_PWM_TIMERS_NUM; i++) {
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].ptimer = ptimer_init(bh[i]);
+ s->timer[i].id = i;
+ }
+
+ memory_region_init_io(&s->iomem, &exynos4210_pwm_ops, s, "exynos4210-pwm",
+ EXYNOS4210_PWM_REG_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ exynos4210_pwm_reset(s);
+
+ qemu_register_reset((QEMUResetHandler *)exynos4210_pwm_reset, s);
+ vmstate_register(NULL, -1, &VMState_Exynos4210PWMState, s);
+ return 0;
+}
+
+static void exynos4210_pwm_register_devices(void)
+{
+ sysbus_register_dev("exynos4210.pwm", sizeof(Exynos4210PWMState),
+ exynos4210_pwm_init);
+}
+
+device_init(exynos4210_pwm_register_devices)
--
1.7.4.1
- [Qemu-devel] [PATCH v3 00/14] ARM: Samsung Exynos4210-based boards support., Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 04/14] hw/sysbus.h: Increase maximum number of device IRQs., Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 09/14] hw/exynos4210.c: Boot secondary CPU., Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 06/14] ARM: exynos4210: PWM support.,
Evgeny Voevodin <=
- [Qemu-devel] [PATCH v3 10/14] hw/lan9118: Add basic 16-bit mode support., Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 07/14] hw/arm_boot.c: Add new secondary CPU bootloader., Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 03/14] ARM: exynos4210: UART support, Evgeny Voevodin, 2011/12/12
- [Qemu-devel] [PATCH v3 02/14] ARM: exynos4210: CMU support, Evgeny Voevodin, 2011/12/12