[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support.
From: |
Evgeny Voevodin |
Subject: |
[Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support. |
Date: |
Wed, 07 Dec 2011 13:46:55 +0400 |
Signed-off-by: Evgeny Voevodin <address@hidden>
---
Makefile.target | 2 +-
hw/s5pc210.c | 12 ++
hw/s5pc210_pwm.c | 433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 446 insertions(+), 1 deletions(-)
create mode 100644 hw/s5pc210_pwm.c
diff --git a/Makefile.target b/Makefile.target
index ed338a8..b9830d3 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o
+ s5pc210_combiner.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 99e9b1b..9110228 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -89,6 +89,9 @@
#define S5PC210_EXT_COMBINER_BASE_ADDR 0x10440000
#define S5PC210_INT_COMBINER_BASE_ADDR 0x10448000
+/* PWM */
+#define S5PC210_PWM_BASE_ADDR 0x139D0000
+
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
static struct arm_boot_info s5pc210_binfo = {
@@ -350,6 +353,15 @@ static void s5pc210_init(ram_addr_t ram_size,
/* CMU */
sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
+ /* PWM */
+ sysbus_create_varargs("s5pc210.pwm", S5PC210_PWM_BASE_ADDR,
+ irq_table[s5pc210_get_irq(22, 0)],
+ irq_table[s5pc210_get_irq(22, 1)],
+ irq_table[s5pc210_get_irq(22, 2)],
+ irq_table[s5pc210_get_irq(22, 3)],
+ irq_table[s5pc210_get_irq(22, 4)],
+ NULL);
+
/*** UARTs ***/
for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
diff --git a/hw/s5pc210_pwm.c b/hw/s5pc210_pwm.c
new file mode 100644
index 0000000..5dfaa41
--- /dev/null
+++ b/hw/s5pc210_pwm.c
@@ -0,0 +1,433 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.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 S5PC210_PWM_TIMERS_NUM 5
+#define S5PC210_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 */
+
+} S5pc210_pwm;
+
+
+typedef struct S5pc210PWMState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg_tcfg[2];
+ uint32_t reg_tcon;
+ uint32_t reg_tint_cstat;
+
+ S5pc210CmuClock clk; /* clock source for timer */
+ S5pc210_pwm timer[S5PC210_PWM_TIMERS_NUM];
+
+} S5pc210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_S5pc210_pwm = {
+ .name = "s5pc210.pwm.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, S5pc210_pwm),
+ VMSTATE_UINT32(freq, S5pc210_pwm),
+ VMSTATE_UINT32(reg_tcntb, S5pc210_pwm),
+ VMSTATE_UINT32(reg_tcmpb, S5pc210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_S5pc210PWMState = {
+ .name = "s5pc210.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg_tcfg, S5pc210PWMState, 2),
+ VMSTATE_UINT32(reg_tcon, S5pc210PWMState),
+ VMSTATE_UINT32(reg_tint_cstat, S5pc210PWMState),
+ VMSTATE_STRUCT_ARRAY(timer, S5pc210PWMState,
+ S5PC210_PWM_TIMERS_NUM, 0,
+ VMState_S5pc210_pwm, S5pc210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * PWM update frequency
+ */
+static void s5pc210_pwm_update_freq(S5pc210PWMState *s, uint32_t id)
+{
+ uint32_t freq;
+ freq = s->timer[id].freq;
+ if (id > 1) {
+ s->timer[id].freq = s5pc210_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 = s5pc210_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 s5pc210_pwm_tick(void *opaque, uint32_t id)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)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 s5pc210_pwm_tick0(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 0);
+}
+static void s5pc210_pwm_tick1(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 1);
+}
+static void s5pc210_pwm_tick2(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 2);
+}
+static void s5pc210_pwm_tick3(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 3);
+}
+static void s5pc210_pwm_tick4(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 4);
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t s5pc210_pwm_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)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,
+ "[s5pc210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * PWM Write
+ */
+static void s5pc210_pwm_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)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 < S5PC210_PWM_TIMERS_NUM; i++) {
+ s5pc210_pwm_update_freq(s, s->timer[i].id);
+ }
+ break;
+
+ case TCON:
+ for (i = 0; i < S5PC210_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 < S5PC210_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,
+ "[s5pc210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void s5pc210_pwm_reset(S5pc210PWMState *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 < S5PC210_PWM_TIMERS_NUM; i++) {
+ s->timer[i].reg_tcmpb = 0;
+ s->timer[i].reg_tcntb = 0;
+
+ s5pc210_pwm_update_freq(s, s->timer[i].id);
+ ptimer_stop(s->timer[i].ptimer);
+ }
+}
+
+static const MemoryRegionOps s5pc210_pwm_ops = {
+ .read = s5pc210_pwm_read,
+ .write = s5pc210_pwm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int s5pc210_pwm_init(SysBusDevice *dev)
+{
+ S5pc210PWMState *s = FROM_SYSBUS(S5pc210PWMState, dev);
+ int i;
+ QEMUBH * bh[S5PC210_PWM_TIMERS_NUM];
+
+ s->clk = ACLK_100;
+
+ bh[0] = qemu_bh_new(s5pc210_pwm_tick0, s);
+ bh[1] = qemu_bh_new(s5pc210_pwm_tick1, s);
+ bh[2] = qemu_bh_new(s5pc210_pwm_tick2, s);
+ bh[3] = qemu_bh_new(s5pc210_pwm_tick3, s);
+ bh[4] = qemu_bh_new(s5pc210_pwm_tick4, s);
+
+ for (i = 0; i < S5PC210_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, &s5pc210_pwm_ops, s, "s5pc210-pwm",
+ S5PC210_PWM_REG_MEM_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ s5pc210_pwm_reset(s);
+
+ qemu_register_reset((QEMUResetHandler *)s5pc210_pwm_reset, s);
+ vmstate_register(NULL, -1, &VMState_S5pc210PWMState, s);
+ return 0;
+}
+
+static void s5pc210_pwm_register_devices(void)
+{
+ sysbus_register_dev("s5pc210.pwm", sizeof(S5pc210PWMState),
+ s5pc210_pwm_init);
+}
+
+device_init(s5pc210_pwm_register_devices)
--
1.7.4.1
- [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support.,
Evgeny Voevodin <=
- [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards, Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state)., Evgeny Voevodin, 2011/12/07
- [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit., Evgeny Voevodin, 2011/12/07