* [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support.
@ 2011-12-09 13:34 Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
` (14 more replies)
0 siblings, 15 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Second version of patches: add support for Samsung Exynos4210-based boards NURI
and SMDKC210.
Tested on Linux kernel v3.x series. Usage of "-smp 2" option is reuired for now.
Evgeny Voevodin (9):
hw/sysbus.h: Increase maximum number of device IRQs.
ARM: exynos4210: IRQ subsystem support.
ARM: exynos4210: PWM support.
hw/arm_boot.c: Add new secondary CPU bootloader.
hw/arm_gic.c: lower IRQ only on changing of enable bit.
ARM: exynos4210: MCT support.
hw/exynos4210.c: Boot secondary CPU.
hw/lan9118: Add basic 16-bit mode support.
hw/exynos4210.c: Add LAN support for SMDKC210.
Maksim Kozlov (3):
ARM: Samsung exynos4210-based boards emulation
ARM: exynos4210: CMU support
ARM: exynos4210: UART support
Mitsyanko Igor (3):
hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API
ARM: exynos4210: added SD/MMC host controller
ARM: exynos4210: added display controller implementation
Makefile.target | 3 +
hw/arm-misc.h | 1 +
hw/arm_boot.c | 22 +-
hw/arm_gic.c | 20 +-
hw/devices.h | 2 +-
hw/exynos4210.c | 547 +++++++++++++++
hw/exynos4210.h | 106 +++
hw/exynos4210_cmu.c | 1146 ++++++++++++++++++++++++++++++
hw/exynos4210_combiner.c | 385 ++++++++++
hw/exynos4210_fimd.c | 1737 ++++++++++++++++++++++++++++++++++++++++++++++
hw/exynos4210_gic.c | 415 +++++++++++
hw/exynos4210_mct.c | 1486 +++++++++++++++++++++++++++++++++++++++
hw/exynos4210_pwm.c | 433 ++++++++++++
hw/exynos4210_sdhc.c | 1666 ++++++++++++++++++++++++++++++++++++++++++++
hw/exynos4210_uart.c | 674 ++++++++++++++++++
hw/lan9118.c | 115 +++-
hw/sd.c | 5 +
hw/sd.h | 1 +
hw/sysbus.h | 2 +-
19 files changed, 8749 insertions(+), 17 deletions(-)
create mode 100644 hw/exynos4210.c
create mode 100644 hw/exynos4210.h
create mode 100644 hw/exynos4210_cmu.c
create mode 100644 hw/exynos4210_combiner.c
create mode 100644 hw/exynos4210_fimd.c
create mode 100644 hw/exynos4210_gic.c
create mode 100644 hw/exynos4210_mct.c
create mode 100644 hw/exynos4210_pwm.c
create mode 100644 hw/exynos4210_sdhc.c
create mode 100644 hw/exynos4210_uart.c
--
1.7.4.1
^ permalink raw reply [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
` (13 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
From: Maksim Kozlov <m.kozlov@samsung.com>
Add initial code for support of NURI and SMDKC210 boards
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 1 +
hw/exynos4210.c | 224 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/exynos4210.h | 34 ++++++++
3 files changed, 259 insertions(+), 0 deletions(-)
create mode 100644 hw/exynos4210.c
create mode 100644 hw/exynos4210.h
diff --git a/Makefile.target b/Makefile.target
index a111521..624a142 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,6 +344,7 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
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
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
new file mode 100644
index 0000000..1550016
--- /dev/null
+++ b/hw/exynos4210.c
@@ -0,0 +1,224 @@
+/*
+ * Samsung exynos4210-based boards emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ * Maksim Kozlov <m.kozlov@samsung.com>
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ * Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ * 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 "boards.h"
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exec-memory.h"
+#include "exynos4210.h"
+
+#undef DEBUG
+
+//#define DEBUG
+
+#ifdef DEBUG
+ #undef PRINT_DEBUG
+ #define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+#else
+ #define PRINT_DEBUG(fmt, args...) \
+ do {} while (0)
+#endif
+
+#define EXYNOS4210_DRAM0_BASE_ADDR 0x40000000
+#define EXYNOS4210_DRAM1_BASE_ADDR 0xa0000000
+#define EXYNOS4210_DRAM_MAX_SIZE 0x60000000 /* 1.5 GB */
+
+#define EXYNOS4210_IROM_BASE_ADDR 0x00000000
+#define EXYNOS4210_IROM_SIZE 0x00010000 /* 64 KB */
+#define EXYNOS4210_IROM_MIRROR_BASE_ADDR 0x02000000
+#define EXYNOS4210_IROM_MIRROR_SIZE 0x00010000 /* 64 KB */
+
+#define EXYNOS4210_IRAM_BASE_ADDR 0x02020000
+#define EXYNOS4210_IRAM_SIZE 0x00020000 /* 128 KB */
+
+#define EXYNOS4210_SFR_BASE_ADDR 0x10000000
+
+#define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR
+
+static struct arm_boot_info exynos4210_binfo = {
+ .loader_start = EXYNOS4210_BASE_BOOT_ADDR,
+};
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+ 0x09, 0x00, 0x00, 0x00 };
+
+enum exynos4210_board_type {
+ BOARD_EXYNOS4210_NURI,
+ BOARD_EXYNOS4210_SMDKC210,
+};
+
+enum exynos4210_mach_id {
+ MACH_NURI_ID = 0xD33,
+ MACH_SMDKC210_ID = 0xB16,
+};
+
+
+static void exynos4210_init(ram_addr_t ram_size,
+ const char *boot_device,
+ const char *kernel_filename,
+ const char *kernel_cmdline,
+ const char *initrd_filename,
+ const char *cpu_model,
+ enum exynos4210_board_type board_type)
+{
+ CPUState *env;
+ MemoryRegion *system_mem = get_system_memory();
+ MemoryRegion *chipid_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *iram_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *irom_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *dram1_mem = NULL;
+ qemu_irq *irqp;
+ qemu_irq cpu_irq[4];
+ ram_addr_t mem_size;
+ int n;
+
+ switch (board_type) {
+ case BOARD_EXYNOS4210_NURI:
+ exynos4210_binfo.board_id = MACH_NURI_ID;
+ break;
+ case BOARD_EXYNOS4210_SMDKC210:
+ exynos4210_binfo.board_id = MACH_SMDKC210_ID;
+ break;
+ default:
+ break;
+ }
+ if (!cpu_model) {
+ cpu_model = "cortex-a9";
+ }
+
+ for (n = 0; n < smp_cpus; n++) {
+ env = cpu_init(cpu_model);
+ if (!env) {
+ fprintf(stderr, "Unable to find CPU %d definition\n", n);
+ exit(1);
+ }
+ /* Create PIC controller for each processor instance */
+ irqp = arm_pic_init_cpu(env);
+
+ /*
+ * Get GICs gpio_in cpu_irq to connect a combiner to them later.
+ * Use only IRQ for a while.
+ */
+ cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
+ }
+
+ /*** Memory ***/
+
+ /* Chip-ID and OMR */
+ memory_region_init_ram_ptr(chipid_mem, NULL, "exynos4210.chipid",
+ sizeof(chipid_and_omr), chipid_and_omr);
+ memory_region_set_readonly(chipid_mem, true);
+ memory_region_add_subregion(system_mem, EXYNOS4210_SFR_BASE_ADDR,
+ chipid_mem);
+
+ /* Internal ROM */
+ memory_region_init_ram(irom_mem, NULL, "exynos4210.irom",
+ EXYNOS4210_IROM_SIZE);
+ memory_region_set_readonly(irom_mem, true);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR,
+ irom_mem);
+ /* mirror of 0x0 – 0x10000 */
+ memory_region_init_alias(irom_alias_mem, "exynos4210.irom_alias",
+ irom_mem, 0, EXYNOS4210_IROM_SIZE);
+ memory_region_set_readonly(irom_alias_mem, true);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR,
+ irom_alias_mem);
+
+ /* Internal RAM */
+ memory_region_init_ram(iram_mem, NULL, "exynos4210.iram",
+ EXYNOS4210_IRAM_SIZE);
+ memory_region_set_readonly(iram_mem, false);
+ memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR,
+ iram_mem);
+
+ /* DRAM */
+ mem_size = ram_size;
+ if (mem_size > EXYNOS4210_DRAM_MAX_SIZE) {
+ dram1_mem = g_new(MemoryRegion, 1);
+ memory_region_init_ram(dram1_mem, NULL, "exynos4210.dram1",
+ mem_size - EXYNOS4210_DRAM_MAX_SIZE);
+ memory_region_add_subregion(system_mem, EXYNOS4210_DRAM1_BASE_ADDR,
+ dram1_mem);
+ mem_size = EXYNOS4210_DRAM_MAX_SIZE;
+ }
+ memory_region_init_ram(dram0_mem, NULL, "exynos4210.dram0", mem_size);
+ memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
+ dram0_mem);
+
+ /*** Load kernel ***/
+
+ exynos4210_binfo.ram_size = ram_size;
+ exynos4210_binfo.nb_cpus = smp_cpus;
+ exynos4210_binfo.kernel_filename = kernel_filename;
+ exynos4210_binfo.initrd_filename = initrd_filename;
+ exynos4210_binfo.kernel_cmdline = kernel_cmdline;
+
+ arm_load_kernel(first_cpu, &exynos4210_binfo);
+}
+
+static void exynos4210_nuri_init(ram_addr_t ram_size,
+ const char *boot_device,
+ const char *kernel_filename, const char *kernel_cmdline,
+ const char *initrd_filename, const char *cpu_model)
+{
+ exynos4210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+ initrd_filename, cpu_model, BOARD_EXYNOS4210_NURI);
+}
+
+static void exynos4210_smdkc210_init(ram_addr_t ram_size,
+ const char *boot_device,
+ const char *kernel_filename, const char *kernel_cmdline,
+ const char *initrd_filename, const char *cpu_model)
+{
+ exynos4210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+ initrd_filename, cpu_model, BOARD_EXYNOS4210_SMDKC210);
+}
+
+static QEMUMachine exynos4210_nuri_machine = {
+ .name = "exynos4210-nuri",
+ .desc = "Samsung Exynos4210 NURI board",
+ .init = exynos4210_nuri_init,
+ .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static QEMUMachine exynos4210_smdkc210_machine = {
+ .name = "exynos4210-smdkc210",
+ .desc = "Samsung Exynos4210 SMDKC210 board",
+ .init = exynos4210_smdkc210_init,
+ .max_cpus = EXYNOS4210_MAX_CPUS,
+};
+
+static void exynos4210_machine_init(void)
+{
+ qemu_register_machine(&exynos4210_nuri_machine);
+ qemu_register_machine(&exynos4210_smdkc210_machine);
+}
+
+machine_init(exynos4210_machine_init);
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
new file mode 100644
index 0000000..7137630
--- /dev/null
+++ b/hw/exynos4210.h
@@ -0,0 +1,34 @@
+/*
+ * Samsung exynos4210-based boards emulation
+ *
+ * Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
+ * Maksim Kozlov <m.kozlov@samsung.com>
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ * Igor Mitsyanko <i.mitsyanko@samsung.com>
+ *
+ *
+ * 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
+ *
+ */
+
+
+#ifndef EXYNOS4210_H_
+#define EXYNOS4210_H_
+
+#include "qemu-common.h"
+
+#define EXYNOS4210_MAX_CPUS 2
+
+#endif /* EXYNOS4210_H_ */
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
` (12 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
From: Maksim Kozlov <m.kozlov@samsung.com>
Add exynos4210 Clock Management Units emulation
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/exynos4210.c | 7 +
hw/exynos4210.h | 22 +
hw/exynos4210_cmu.c | 1146 +++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 1176 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_cmu.c
diff --git a/Makefile.target b/Makefile.target
index 624a142..ce4f1f8 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,7 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
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
+obj-arm-y += exynos4210.o exynos4210_cmu.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 1550016..1a6e353 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -60,6 +60,10 @@
#define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR
+/* SFR Base Address for CMUs */
+#define EXYNOS4210_CMU_BASE_ADDR 0x10030000
+
+
static struct arm_boot_info exynos4210_binfo = {
.loader_start = EXYNOS4210_BASE_BOOT_ADDR,
};
@@ -172,6 +176,9 @@ static void exynos4210_init(ram_addr_t ram_size,
memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
dram0_mem);
+ /* CMU */
+ sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
+
/*** Load kernel ***/
exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 7137630..683a4a6 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -31,4 +31,26 @@
#define EXYNOS4210_MAX_CPUS 2
+/*
+ * Interface for exynos4210 Clock Management Units (CMUs)
+ */
+
+typedef enum {
+ XXTI,
+ XUSBXTI,
+ APLL,
+ MPLL,
+ SCLK_APLL,
+ SCLK_MPLL,
+ ACLK_100,
+ SCLK_UART0,
+ SCLK_UART1,
+ SCLK_UART2,
+ SCLK_UART3,
+ SCLK_UART4,
+ CLOCKS_NUMBER
+} Exynos4210CmuClock;
+
+uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock);
+
#endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_cmu.c b/hw/exynos4210_cmu.c
new file mode 100644
index 0000000..fe4100c
--- /dev/null
+++ b/hw/exynos4210_cmu.c
@@ -0,0 +1,1146 @@
+/*
+ * exynos4210 Clock Management Units (CMUs) Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ * Created on: 07.2011
+ *
+ *
+ * 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 "exynos4210.h"
+
+
+
+#undef DEBUG_CMU
+
+//#define DEBUG_CMU
+//#define DEBUG_CMU_EXTEND
+
+
+#define PRINT_DEBUG(fmt, args...) \
+ do {} while (0)
+#define PRINT_DEBUG_SIMPLE(fmt, args...) \
+ do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#define PRINT_ERROR(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+
+#ifdef DEBUG_CMU
+
+ #undef PRINT_DEBUG
+ #define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+ #undef PRINT_DEBUG_SIMPLE
+ #define PRINT_DEBUG_SIMPLE(fmt, args...) \
+ do { \
+ fprintf(stderr, fmt, ## args); \
+ } while (0)
+
+#ifdef DEBUG_CMU_EXTEND
+
+ #undef PRINT_DEBUG_EXTEND
+ #define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#endif /* EXTEND */
+#endif
+
+
+/*
+ * Offsets for CMUs registers
+ */
+
+/* CMU_LEFTBUS registers */
+#define CLK_SRC_LEFTBUS 0x04200
+#define CLK_MUX_STAT_LEFTBUS 0x04400
+#define CLK_DIV_LEFTBUS 0x04500
+#define CLK_DIV_STAT_LEFTBUS 0x04600
+#define CLK_GATE_IP_LEFTBUS 0x04800
+#define CLKOUT_CMU_LEFTBUS 0x04A00
+#define CLKOUT_CMU_LEFTBUS_DIV_STAT 0x04A04
+/* CMU_RIGHTBUS registers */
+#define CLK_SRC_RIGHTBUS 0x08200
+#define CLK_MUX_STAT_RIGHTBUS 0x08400
+#define CLK_DIV_RIGHTBUS 0x08500
+#define CLK_DIV_STAT_RIGHTBUS 0x08600
+#define CLK_GATE_IP_RIGHTBUS 0x08800
+#define CLKOUT_CMU_RIGHTBUS 0x08A00
+#define CLKOUT_CMU_RIGHTBUS_DIV_STAT 0x08A04
+/* CMU_TOP registers */
+#define EPLL_LOCK 0x0C010
+#define VPLL_LOCK 0x0C020
+#define EPLL_CON0 0x0C110
+#define EPLL_CON1 0x0C114
+#define VPLL_CON0 0x0C120
+#define VPLL_CON1 0x0C124
+#define CLK_SRC_TOP0 0x0C210
+#define CLK_SRC_TOP1 0x0C214
+#define CLK_SRC_CAM 0x0C220
+#define CLK_SRC_TV 0x0C224
+#define CLK_SRC_MFC 0x0C228
+#define CLK_SRC_G3D 0x0C22C
+#define CLK_SRC_IMAGE 0x0C230
+#define CLK_SRC_LCD0 0x0C234
+#define CLK_SRC_LCD1 0x0C238
+#define CLK_SRC_MAUDIO 0x0C23C
+#define CLK_SRC_FSYS 0x0C240
+#define CLK_SRC_PERIL0 0x0C250
+#define CLK_SRC_PERIL1 0x0C254
+#define CLK_SRC_MASK_TOP 0x0C310
+#define CLK_SRC_MASK_CAM 0x0C320
+#define CLK_SRC_MASK_TV 0x0C324
+#define CLK_SRC_MASK_LCD0 0x0C334
+#define CLK_SRC_MASK_LCD1 0x0C338
+#define CLK_SRC_MASK_MAUDIO 0x0C33C
+#define CLK_SRC_MASK_FSYS 0x0C340
+#define CLK_SRC_MASK_PERIL0 0x0C350
+#define CLK_SRC_MASK_PERIL1 0x0C354
+#define CLK_MUX_STAT_TOP 0x0C410
+#define CLK_MUX_STAT_MFC 0x0C428
+#define CLK_MUX_STAT_G3D 0x0C42C
+#define CLK_MUX_STAT_IMAGE 0x0C430
+#define CLK_DIV_TOP 0x0C510
+#define CLK_DIV_CAM 0x0C520
+#define CLK_DIV_TV 0x0C524
+#define CLK_DIV_MFC 0x0C528
+#define CLK_DIV_G3D 0x0C52C
+#define CLK_DIV_IMAGE 0x0C530
+#define CLK_DIV_LCD0 0x0C534
+#define CLK_DIV_LCD1 0x0C538
+#define CLK_DIV_MAUDIO 0x0C53C
+#define CLK_DIV_FSYS0 0x0C540
+#define CLK_DIV_FSYS1 0x0C544
+#define CLK_DIV_FSYS2 0x0C548
+#define CLK_DIV_FSYS3 0x0C54C
+#define CLK_DIV_PERIL0 0x0C550
+#define CLK_DIV_PERIL1 0x0C554
+#define CLK_DIV_PERIL2 0x0C558
+#define CLK_DIV_PERIL3 0x0C55C
+#define CLK_DIV_PERIL4 0x0C560
+#define CLK_DIV_PERIL5 0x0C564
+#define CLKDIV2_RATIO 0x0C580
+#define CLK_DIV_STAT_TOP 0x0C610
+#define CLK_DIV_STAT_CAM 0x0C620
+#define CLK_DIV_STAT_TV 0x0C624
+#define CLK_DIV_STAT_MFC 0x0C628
+#define CLK_DIV_STAT_G3D 0x0C62C
+#define CLK_DIV_STAT_IMAGE 0x0C630
+#define CLK_DIV_STAT_LCD0 0x0C634
+#define CLK_DIV_STAT_LCD1 0x0C638
+#define CLK_DIV_STAT_MAUDIO 0x0C63C
+#define CLK_DIV_STAT_FSYS0 0x0C640
+#define CLK_DIV_STAT_FSYS1 0x0C644
+#define CLK_DIV_STAT_FSYS2 0x0C648
+#define CLK_DIV_STAT_FSYS3 0x0C64C
+#define CLK_DIV_STAT_PERIL0 0x0C650
+#define CLK_DIV_STAT_PERIL1 0x0C654
+#define CLK_DIV_STAT_PERIL2 0x0C658
+#define CLK_DIV_STAT_PERIL3 0x0C65C
+#define CLK_DIV_STAT_PERIL4 0x0C660
+#define CLK_DIV_STAT_PERIL5 0x0C664
+#define CLKDIV2_STAT 0x0C680
+#define CLK_GATE_SCLK_CAM 0x0C820
+#define CLK_GATE_IP_CAM 0x0C920
+#define CLK_GATE_IP_TV 0x0C924
+#define CLK_GATE_IP_MFC 0x0C928
+#define CLK_GATE_IP_G3D 0x0C92C
+#define CLK_GATE_IP_IMAGE 0x0C930
+#define CLK_GATE_IP_LCD0 0x0C934
+#define CLK_GATE_IP_LCD1 0x0C938
+#define CLK_GATE_IP_FSYS 0x0C940
+#define CLK_GATE_IP_GPS 0x0C94C
+#define CLK_GATE_IP_PERIL 0x0C950
+#define CLK_GATE_IP_PERIR 0x0C960
+#define CLK_GATE_BLOCK 0x0C970
+#define CLKOUT_CMU_TOP 0x0CA00
+#define CLKOUT_CMU_TOP_DIV_STAT 0x0CA04
+/* CMU_DMC registers */
+#define CLK_SRC_DMC 0x10200
+#define CLK_SRC_MASK_DMC 0x10300
+#define CLK_MUX_STAT_DMC 0x10400
+#define CLK_DIV_DMC0 0x10500
+#define CLK_DIV_DMC1 0x10504
+#define CLK_DIV_STAT_DMC0 0x10600
+#define CLK_DIV_STAT_DMC1 0x10604
+#define CLK_GATE_IP_DMC 0x10900
+#define CLKOUT_CMU_DMC 0x10A00
+#define CLKOUT_CMU_DMC_DIV_STAT 0x10A04
+#define DCGIDX_MAP0 0x11000
+#define DCGIDX_MAP1 0x11004
+#define DCGIDX_MAP2 0x11008
+#define DCGPERF_MAP0 0x11020
+#define DCGPERF_MAP1 0x11024
+#define DVCIDX_MAP 0x11040
+#define FREQ_CPU 0x11060
+#define FREQ_DPM 0x11064
+#define DVSEMCLK_EN 0x11080
+#define MAXPERF 0x11084
+#define APLL_LOCK 0x14000
+#define MPLL_LOCK 0x14008
+#define APLL_CON0 0x14100
+#define APLL_CON1 0x14104
+#define MPLL_CON0 0x14108
+#define MPLL_CON1 0x1410C
+/* CMU_CPU registers */
+#define CLK_SRC_CPU 0x14200
+#define CLK_MUX_STAT_CPU 0x14400
+#define CLK_DIV_CPU0 0x14500
+#define CLK_DIV_CPU1 0x14504
+#define CLK_DIV_STAT_CPU0 0x14600
+#define CLK_DIV_STAT_CPU1 0x14604
+#define CLK_GATE_SCLK_CPU 0x14800
+#define CLK_GATE_IP_CPU 0x14900
+#define CLKOUT_CMU_CPU 0x14A00
+#define CLKOUT_CMU_CPU_DIV_STAT 0x14A04
+#define ARMCLK_STOPCTRL 0x15000
+#define ATCLK_STOPCTRL 0x15004
+#define PARITYFAIL_STATUS 0x15010
+#define PARITYFAIL_CLEAR 0x15014
+#define PWR_CTRL 0x15020
+#define APLL_CON0_L8 0x15100
+#define APLL_CON0_L7 0x15104
+#define APLL_CON0_L6 0x15108
+#define APLL_CON0_L5 0x1510C
+#define APLL_CON0_L4 0x15110
+#define APLL_CON0_L3 0x15114
+#define APLL_CON0_L2 0x15118
+#define APLL_CON0_L1 0x1511C
+#define IEM_CONTROL 0x15120
+#define APLL_CON1_L8 0x15200
+#define APLL_CON1_L7 0x15204
+#define APLL_CON1_L6 0x15208
+#define APLL_CON1_L5 0x1520C
+#define APLL_CON1_L4 0x15210
+#define APLL_CON1_L3 0x15214
+#define APLL_CON1_L2 0x15218
+#define APLL_CON1_L1 0x1521C
+#define CLKDIV_IEM_L8 0x15300
+#define CLKDIV_IEM_L7 0x15304
+#define CLKDIV_IEM_L6 0x15308
+#define CLKDIV_IEM_L5 0x1530C
+#define CLKDIV_IEM_L4 0x15310
+#define CLKDIV_IEM_L3 0x15314
+#define CLKDIV_IEM_L2 0x15318
+#define CLKDIV_IEM_L1 0x1531C
+
+
+typedef struct Exynos4210CmuReg {
+ const char *name; /* for debugging */
+ uint32_t offset;
+ uint32_t reset_value;
+} Exynos4210CmuReg;
+
+
+static Exynos4210CmuReg exynos4210_cmu_regs[] = {
+ /* CMU_LEFTBUS registers */
+ {"CLK_SRC_LEFTBUS", CLK_SRC_LEFTBUS, 0x00000000},
+ {"CLK_MUX_STAT_LEFTBUS", CLK_MUX_STAT_LEFTBUS, 0x00000001},
+ {"CLK_DIV_LEFTBUS", CLK_DIV_LEFTBUS, 0x00000000},
+ {"CLK_DIV_STAT_LEFTBUS", CLK_DIV_STAT_LEFTBUS, 0x00000000},
+ {"CLK_GATE_IP_LEFTBUS", CLK_GATE_IP_LEFTBUS, 0xFFFFFFFF},
+ {"CLKOUT_CMU_LEFTBUS", CLKOUT_CMU_LEFTBUS, 0x00010000},
+ {"CLKOUT_CMU_LEFTBUS_DIV_STAT", CLKOUT_CMU_LEFTBUS_DIV_STAT, 0x00000000},
+ /* CMU_RIGHTBUS registers */
+ {"CLK_SRC_RIGHTBUS", CLK_SRC_RIGHTBUS, 0x00000000},
+ {"CLK_MUX_STAT_RIGHTBUS", CLK_MUX_STAT_RIGHTBUS, 0x00000001},
+ {"CLK_DIV_RIGHTBUS", CLK_DIV_RIGHTBUS, 0x00000000},
+ {"CLK_DIV_STAT_RIGHTBUS", CLK_DIV_STAT_RIGHTBUS, 0x00000000},
+ {"CLK_GATE_IP_RIGHTBUS", CLK_GATE_IP_RIGHTBUS, 0xFFFFFFFF},
+ {"CLKOUT_CMU_RIGHTBUS", CLKOUT_CMU_RIGHTBUS, 0x00010000},
+ {"CLKOUT_CMU_RIGHTBUS_DIV_STAT", CLKOUT_CMU_RIGHTBUS_DIV_STAT, 0x00000000},
+ /* CMU_TOP registers */
+ {"EPLL_LOCK", EPLL_LOCK, 0x00000FFF},
+ {"VPLL_LOCK", VPLL_LOCK, 0x00000FFF},
+ {"EPLL_CON0", EPLL_CON0, 0x00300301},
+ {"EPLL_CON1", EPLL_CON1, 0x00000000},
+ {"VPLL_CON0", VPLL_CON0, 0x00240201},
+ {"VPLL_CON1", VPLL_CON1, 0x66010464},
+ {"CLK_SRC_TOP0", CLK_SRC_TOP0, 0x00000000},
+ {"CLK_SRC_TOP1", CLK_SRC_TOP1, 0x00000000},
+ {"CLK_SRC_CAM", CLK_SRC_CAM, 0x11111111},
+ {"CLK_SRC_TV", CLK_SRC_TV, 0x00000000},
+ {"CLK_SRC_MFC", CLK_SRC_MFC, 0x00000000},
+ {"CLK_SRC_G3D", CLK_SRC_G3D, 0x00000000},
+ {"CLK_SRC_IMAGE", CLK_SRC_IMAGE, 0x00000000},
+ {"CLK_SRC_LCD0", CLK_SRC_LCD0, 0x00001111},
+ {"CLK_SRC_LCD1", CLK_SRC_LCD1, 0x00001111},
+ {"CLK_SRC_MAUDIO", CLK_SRC_MAUDIO, 0x00000005},
+ {"CLK_SRC_FSYS", CLK_SRC_FSYS, 0x00011111},
+ {"CLK_SRC_PERIL0", CLK_SRC_PERIL0, 0x00011111},
+ {"CLK_SRC_PERIL1", CLK_SRC_PERIL1, 0x01110055},
+ {"CLK_SRC_MASK_TOP", CLK_SRC_MASK_TOP, 0x00000001},
+ {"CLK_SRC_MASK_CAM", CLK_SRC_MASK_CAM, 0x11111111},
+ {"CLK_SRC_MASK_TV", CLK_SRC_MASK_TV, 0x00000111},
+ {"CLK_SRC_MASK_LCD0", CLK_SRC_MASK_LCD0, 0x00001111},
+ {"CLK_SRC_MASK_LCD1", CLK_SRC_MASK_LCD1, 0x00001111},
+ {"CLK_SRC_MASK_MAUDIO", CLK_SRC_MASK_MAUDIO, 0x00000001},
+ {"CLK_SRC_MASK_FSYS", CLK_SRC_MASK_FSYS, 0x01011111},
+ {"CLK_SRC_MASK_PERIL0", CLK_SRC_MASK_PERIL0, 0x00011111},
+ {"CLK_SRC_MASK_PERIL1", CLK_SRC_MASK_PERIL1, 0x01110111},
+ {"CLK_MUX_STAT_TOP", CLK_MUX_STAT_TOP, 0x11111111},
+ {"CLK_MUX_STAT_MFC", CLK_MUX_STAT_MFC, 0x00000111},
+ {"CLK_MUX_STAT_G3D", CLK_MUX_STAT_G3D, 0x00000111},
+ {"CLK_MUX_STAT_IMAGE", CLK_MUX_STAT_IMAGE, 0x00000111},
+ {"CLK_DIV_TOP", CLK_DIV_TOP, 0x00000000},
+ {"CLK_DIV_CAM", CLK_DIV_CAM, 0x00000000},
+ {"CLK_DIV_TV", CLK_DIV_TV, 0x00000000},
+ {"CLK_DIV_MFC", CLK_DIV_MFC, 0x00000000},
+ {"CLK_DIV_G3D", CLK_DIV_G3D, 0x00000000},
+ {"CLK_DIV_IMAGE", CLK_DIV_IMAGE, 0x00000000},
+ {"CLK_DIV_LCD0", CLK_DIV_LCD0, 0x00700000},
+ {"CLK_DIV_LCD1", CLK_DIV_LCD1, 0x00700000},
+ {"CLK_DIV_MAUDIO", CLK_DIV_MAUDIO, 0x00000000},
+ {"CLK_DIV_FSYS0", CLK_DIV_FSYS0, 0x00B00000},
+ {"CLK_DIV_FSYS1", CLK_DIV_FSYS1, 0x00000000},
+ {"CLK_DIV_FSYS2", CLK_DIV_FSYS2, 0x00000000},
+ {"CLK_DIV_FSYS3", CLK_DIV_FSYS3, 0x00000000},
+ {"CLK_DIV_PERIL0", CLK_DIV_PERIL0, 0x00000000},
+ {"CLK_DIV_PERIL1", CLK_DIV_PERIL1, 0x00000000},
+ {"CLK_DIV_PERIL2", CLK_DIV_PERIL2, 0x00000000},
+ {"CLK_DIV_PERIL3", CLK_DIV_PERIL3, 0x00000000},
+ {"CLK_DIV_PERIL4", CLK_DIV_PERIL4, 0x00000000},
+ {"CLK_DIV_PERIL5", CLK_DIV_PERIL5, 0x00000000},
+ {"CLKDIV2_RATIO", CLKDIV2_RATIO, 0x11111111},
+ {"CLK_DIV_STAT_TOP", CLK_DIV_STAT_TOP, 0x00000000},
+ {"CLK_DIV_STAT_CAM", CLK_DIV_STAT_CAM, 0x00000000},
+ {"CLK_DIV_STAT_TV", CLK_DIV_STAT_TV, 0x00000000},
+ {"CLK_DIV_STAT_MFC", CLK_DIV_STAT_MFC, 0x00000000},
+ {"CLK_DIV_STAT_G3D", CLK_DIV_STAT_G3D, 0x00000000},
+ {"CLK_DIV_STAT_IMAGE", CLK_DIV_STAT_IMAGE, 0x00000000},
+ {"CLK_DIV_STAT_LCD0", CLK_DIV_STAT_LCD0, 0x00000000},
+ {"CLK_DIV_STAT_LCD1", CLK_DIV_STAT_LCD1, 0x00000000},
+ {"CLK_DIV_STAT_MAUDIO", CLK_DIV_STAT_MAUDIO, 0x00000000},
+ {"CLK_DIV_STAT_FSYS0", CLK_DIV_STAT_FSYS0, 0x00000000},
+ {"CLK_DIV_STAT_FSYS1", CLK_DIV_STAT_FSYS1, 0x00000000},
+ {"CLK_DIV_STAT_FSYS2", CLK_DIV_STAT_FSYS2, 0x00000000},
+ {"CLK_DIV_STAT_FSYS3", CLK_DIV_STAT_FSYS3, 0x00000000},
+ {"CLK_DIV_STAT_PERIL0", CLK_DIV_STAT_PERIL0, 0x00000000},
+ {"CLK_DIV_STAT_PERIL1", CLK_DIV_STAT_PERIL1, 0x00000000},
+ {"CLK_DIV_STAT_PERIL2", CLK_DIV_STAT_PERIL2, 0x00000000},
+ {"CLK_DIV_STAT_PERIL3", CLK_DIV_STAT_PERIL3, 0x00000000},
+ {"CLK_DIV_STAT_PERIL4", CLK_DIV_STAT_PERIL4, 0x00000000},
+ {"CLK_DIV_STAT_PERIL5", CLK_DIV_STAT_PERIL5, 0x00000000},
+ {"CLKDIV2_STAT", CLKDIV2_STAT, 0x00000000},
+ {"CLK_GATE_SCLK_CAM", CLK_GATE_SCLK_CAM, 0xFFFFFFFF},
+ {"CLK_GATE_IP_CAM", CLK_GATE_IP_CAM, 0xFFFFFFFF},
+ {"CLK_GATE_IP_TV", CLK_GATE_IP_TV, 0xFFFFFFFF},
+ {"CLK_GATE_IP_MFC", CLK_GATE_IP_MFC, 0xFFFFFFFF},
+ {"CLK_GATE_IP_G3D", CLK_GATE_IP_G3D, 0xFFFFFFFF},
+ {"CLK_GATE_IP_IMAGE", CLK_GATE_IP_IMAGE, 0xFFFFFFFF},
+ {"CLK_GATE_IP_LCD0", CLK_GATE_IP_LCD0, 0xFFFFFFFF},
+ {"CLK_GATE_IP_LCD1", CLK_GATE_IP_LCD1, 0xFFFFFFFF},
+ {"CLK_GATE_IP_FSYS", CLK_GATE_IP_FSYS, 0xFFFFFFFF},
+ {"CLK_GATE_IP_GPS", CLK_GATE_IP_GPS, 0xFFFFFFFF},
+ {"CLK_GATE_IP_PERIL", CLK_GATE_IP_PERIL, 0xFFFFFFFF},
+ {"CLK_GATE_IP_PERIR", CLK_GATE_IP_PERIR, 0xFFFFFFFF},
+ {"CLK_GATE_BLOCK", CLK_GATE_BLOCK, 0xFFFFFFFF},
+ {"CLKOUT_CMU_TOP", CLKOUT_CMU_TOP, 0x00010000},
+ {"CLKOUT_CMU_TOP_DIV_STAT", CLKOUT_CMU_TOP_DIV_STAT, 0x00000000},
+ /* CMU_DMC registers */
+ {"CLK_SRC_DMC", CLK_SRC_DMC, 0x00010000},
+ {"CLK_SRC_MASK_DMC", CLK_SRC_MASK_DMC, 0x00010000},
+ {"CLK_MUX_STAT_DMC", CLK_MUX_STAT_DMC, 0x11100110},
+ {"CLK_DIV_DMC0", CLK_DIV_DMC0, 0x00000000},
+ {"CLK_DIV_DMC1", CLK_DIV_DMC1, 0x00000000},
+ {"CLK_DIV_STAT_DMC0", CLK_DIV_STAT_DMC0, 0x00000000},
+ {"CLK_DIV_STAT_DMC1", CLK_DIV_STAT_DMC1, 0x00000000},
+ {"CLK_GATE_IP_DMC", CLK_GATE_IP_DMC, 0xFFFFFFFF},
+ {"CLKOUT_CMU_DMC", CLKOUT_CMU_DMC, 0x00010000},
+ {"CLKOUT_CMU_DMC_DIV_STAT", CLKOUT_CMU_DMC_DIV_STAT, 0x00000000},
+ {"DCGIDX_MAP0", DCGIDX_MAP0, 0xFFFFFFFF},
+ {"DCGIDX_MAP1", DCGIDX_MAP1, 0xFFFFFFFF},
+ {"DCGIDX_MAP2", DCGIDX_MAP2, 0xFFFFFFFF},
+ {"DCGPERF_MAP0", DCGPERF_MAP0, 0xFFFFFFFF},
+ {"DCGPERF_MAP1", DCGPERF_MAP1, 0xFFFFFFFF},
+ {"DVCIDX_MAP", DVCIDX_MAP, 0xFFFFFFFF},
+ {"FREQ_CPU", FREQ_CPU, 0x00000000},
+ {"FREQ_DPM", FREQ_DPM, 0x00000000},
+ {"DVSEMCLK_EN", DVSEMCLK_EN, 0x00000000},
+ {"MAXPERF", MAXPERF, 0x00000000},
+ {"APLL_LOCK", APLL_LOCK, 0x00000FFF},
+ {"MPLL_LOCK", MPLL_LOCK, 0x00000FFF},
+ {"APLL_CON0", APLL_CON0, 0x00C80601},
+ {"APLL_CON1", APLL_CON1, 0x0000001C},
+ {"MPLL_CON0", MPLL_CON0, 0x00C80601},
+ {"MPLL_CON1", MPLL_CON1, 0x0000001C},
+ /* CMU_CPU registers */
+ {"CLK_SRC_CPU", CLK_SRC_CPU, 0x00000000},
+ {"CLK_MUX_STAT_CPU", CLK_MUX_STAT_CPU, 0x00110101},
+ {"CLK_DIV_CPU0", CLK_DIV_CPU0, 0x00000000},
+ {"CLK_DIV_CPU1", CLK_DIV_CPU1, 0x00000000},
+ {"CLK_DIV_STAT_CPU0", CLK_DIV_STAT_CPU0, 0x00000000},
+ {"CLK_DIV_STAT_CPU1", CLK_DIV_STAT_CPU1, 0x00000000},
+ {"CLK_GATE_SCLK_CPU", CLK_GATE_SCLK_CPU, 0xFFFFFFFF},
+ {"CLK_GATE_IP_CPU", CLK_GATE_IP_CPU, 0xFFFFFFFF},
+ {"CLKOUT_CMU_CPU", CLKOUT_CMU_CPU, 0x00010000},
+ {"CLKOUT_CMU_CPU_DIV_STAT", CLKOUT_CMU_CPU_DIV_STAT, 0x00000000},
+ {"ARMCLK_STOPCTRL", ARMCLK_STOPCTRL, 0x00000044},
+ {"ATCLK_STOPCTRL", ATCLK_STOPCTRL, 0x00000044},
+ {"PARITYFAIL_STATUS", PARITYFAIL_STATUS, 0x00000000},
+ {"PARITYFAIL_CLEAR", PARITYFAIL_CLEAR, 0x00000000},
+ {"PWR_CTRL", PWR_CTRL, 0x00000033},
+ {"APLL_CON0_L8", APLL_CON0_L8, 0x00C80601},
+ {"APLL_CON0_L7", APLL_CON0_L7, 0x00C80601},
+ {"APLL_CON0_L6", APLL_CON0_L6, 0x00C80601},
+ {"APLL_CON0_L5", APLL_CON0_L5, 0x00C80601},
+ {"APLL_CON0_L4", APLL_CON0_L4, 0x00C80601},
+ {"APLL_CON0_L3", APLL_CON0_L3, 0x00C80601},
+ {"APLL_CON0_L2", APLL_CON0_L2, 0x00C80601},
+ {"APLL_CON0_L1", APLL_CON0_L1, 0x00C80601},
+ {"IEM_CONTROL", IEM_CONTROL, 0x00000000},
+ {"APLL_CON1_L8", APLL_CON1_L8, 0x00000000},
+ {"APLL_CON1_L7", APLL_CON1_L7, 0x00000000},
+ {"APLL_CON1_L6", APLL_CON1_L6, 0x00000000},
+ {"APLL_CON1_L5", APLL_CON1_L5, 0x00000000},
+ {"APLL_CON1_L4", APLL_CON1_L4, 0x00000000},
+ {"APLL_CON1_L3", APLL_CON1_L3, 0x00000000},
+ {"APLL_CON1_L2", APLL_CON1_L2, 0x00000000},
+ {"APLL_CON1_L1", APLL_CON1_L1, 0x00000000},
+ {"CLKDIV_IEM_L8", CLKDIV_IEM_L8, 0x00000000},
+ {"CLKDIV_IEM_L7", CLKDIV_IEM_L7, 0x00000000},
+ {"CLKDIV_IEM_L6", CLKDIV_IEM_L6, 0x00000000},
+ {"CLKDIV_IEM_L5", CLKDIV_IEM_L5, 0x00000000},
+ {"CLKDIV_IEM_L4", CLKDIV_IEM_L4, 0x00000000},
+ {"CLKDIV_IEM_L3", CLKDIV_IEM_L3, 0x00000000},
+ {"CLKDIV_IEM_L2", CLKDIV_IEM_L2, 0x00000000},
+ {"CLKDIV_IEM_L1", CLKDIV_IEM_L1, 0x00000000},
+};
+
+
+/*
+ * There are five CMUs:
+ *
+ * CMU_LEFTBUS
+ * CMU_RIGHTBUS
+ * CMU_TOP
+ * CMU_DMC
+ * CMU_CPU
+ *
+ * each of them uses 16KB address space for SFRs
+ *
+ * + 0x4000 because SFR region for CMUs starts at 0x10030000,
+ * but the first CMU (CMU_LEFTBUS) starts with this offset
+ *
+ */
+#define EXYNOS4210_CMU_REGS_MEM_SIZE (0x4000 * 5 + 0x4000)
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+#define XOM_0 1 /* Select XXTI (0) or XUSBXTI (1) base clock source */
+
+/*
+ * Offsets in CLK_SRC_CPU register
+ * for control MUXMPLL and MUXAPLL
+ *
+ * 0 = FINPLL, 1 = MOUTM(A)PLLFOUT
+ */
+#define MUX_APLL_SEL_SHIFT 0
+#define MUX_MPLL_SEL_SHIFT 8
+#define MUX_CORE_SEL_SHIFT 16
+#define MUX_HPM_SEL_SHIFT 20
+
+#define MUX_APLL_SEL (1 << MUX_APLL_SEL_SHIFT)
+#define MUX_MPLL_SEL (1 << MUX_MPLL_SEL_SHIFT)
+#define MUX_CORE_SEL (1 << MUX_CORE_SEL_SHIFT)
+#define MUX_HPM_SEL (1 << MUX_HPM_SEL_SHIFT)
+
+/* Offsets for fields in CLK_MUX_STAT_CPU register */
+#define APLL_SEL_SHIFT 0
+#define APLL_SEL_MASK 0x00000007
+#define MPLL_SEL_SHIFT 8
+#define MPLL_SEL_MASK 0x00000700
+#define CORE_SEL_SHIFT 16
+#define CORE_SEL_MASK 0x00070000
+#define HPM_SEL_SHIFT 20
+#define HPM_SEL_MASK 0x00700000
+
+
+/* Offsets for fields in <pll>_CON0 register */
+#define PLL_ENABLE_SHIFT 31
+#define PLL_ENABLE_MASK 0x80000000 /* [31] bit */
+#define PLL_LOCKED_MASK 0x20000000 /* [29] bit */
+#define PLL_MDIV_SHIFT 16
+#define PLL_MDIV_MASK 0x03FF0000 /* [25:16] bits */
+#define PLL_PDIV_SHIFT 8
+#define PLL_PDIV_MASK 0x00003F00 /* [13:8] bits */
+#define PLL_SDIV_SHIFT 0
+#define PLL_SDIV_MASK 0x00000007 /* [2:0] bits */
+
+
+
+/*
+ * Offset in CLK_DIV_CPU0 register
+ * for DIVAPLL clock divider ratio
+ */
+#define APLL_RATIO_SHIFT 24
+#define APLL_RATIO_MASK 0x07000000 /* [26:24] bits */
+
+/*
+ * Offset in CLK_DIV_TOP register
+ * for DIVACLK_100 clock divider ratio
+ */
+#define ACLK_100_RATIO_SHIFT 4
+#define ACLK_100_RATIO_MASK 0x000000f0 /* [7:4] bits */
+
+/* Offset in CLK_SRC_TOP0 register */
+#define MUX_ACLK_100_SEL_SHIFT 16
+
+/*
+ * Offsets in CLK_SRC_PERIL0 register
+ * for clock sources of UARTs
+ */
+#define UART0_SEL_SHIFT 0
+#define UART1_SEL_SHIFT 4
+#define UART2_SEL_SHIFT 8
+#define UART3_SEL_SHIFT 12
+#define UART4_SEL_SHIFT 16
+/*
+ * Offsets in CLK_DIV_PERIL0 register
+ * for clock divider of UARTs
+ */
+#define UART0_DIV_SHIFT 0
+#define UART1_DIV_SHIFT 4
+#define UART2_DIV_SHIFT 8
+#define UART3_DIV_SHIFT 12
+#define UART4_DIV_SHIFT 16
+
+typedef struct {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg[EXYNOS4210_CMU_REGS_MEM_SIZE];
+
+} Exynos4210CmuState;
+
+#define SOURCES_NUMBER 9
+#define RECIPIENTS_NUMBER 9
+
+typedef struct Exynos4210CmuClockState {
+
+ const char *name;
+ uint64_t rate;
+
+ /* Current source clock */
+ struct Exynos4210CmuClockState *source;
+ /*
+ * Available sources. Their order must correspond to CLK_SRC_ register
+ */
+ struct Exynos4210CmuClockState *sources[SOURCES_NUMBER];
+ /* Who uses this clock? */
+ struct Exynos4210CmuClockState *recipients[RECIPIENTS_NUMBER];
+
+ uint32_t src_reg; /* Offset of CLK_SRC_<*> register */
+ uint32_t div_reg; /* Offset of CLK_DIV_<*> register */
+
+ /*
+ * Shift for MUX_<clk>_SEL value which is stored
+ * in appropriate CLK_MUX_STAT_<cmu> register
+ */
+ uint8_t mux_shift;
+
+ /*
+ * Shift for <clk>_RATIO value which is stored
+ * in appropriate CLK_DIV_<cmu> register
+ */
+ uint8_t div_shift;
+
+} Exynos4210CmuClockState;
+
+
+/* Clocks from Clock Pads */
+
+/* It should be used only for testing purposes. XOM_0 is 0 */
+static Exynos4210CmuClockState xxti = {
+ .name = "XXTI",
+ .rate = 24000000,
+};
+
+/* Main source. XOM_0 is 1 */
+static Exynos4210CmuClockState xusbxti = {
+ .name = "XUSBXTI",
+ .rate = 24000000,
+};
+
+/* PLLs */
+
+static Exynos4210CmuClockState mpll = {
+ .name = "MPLL",
+ .source = (XOM_0 ? &xusbxti : &xxti),
+};
+
+static Exynos4210CmuClockState apll = {
+ .name = "APLL",
+ .source = (XOM_0 ? &xusbxti : &xxti),
+};
+
+
+/**/
+static Exynos4210CmuClockState sclk_mpll = {
+ .name = "SCLK_MPLL",
+ .sources = {XOM_0 ? &xusbxti : &xxti, &mpll},
+ .src_reg = CLK_SRC_CPU,
+ .mux_shift = MUX_MPLL_SEL_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_apll = {
+ .name = "SCLK_APLL",
+ .sources = {XOM_0 ? &xusbxti : &xxti, &apll},
+ .src_reg = CLK_SRC_CPU,
+ .div_reg = CLK_DIV_CPU0,
+ .mux_shift = MUX_APLL_SEL_SHIFT,
+ .div_shift = APLL_RATIO_SHIFT,
+};
+
+static Exynos4210CmuClockState aclk_100 = {
+ .name = "ACLK_100",
+ .sources = {&sclk_mpll, &sclk_apll},
+ .src_reg = CLK_SRC_TOP0,
+ .div_reg = CLK_DIV_TOP,
+ .mux_shift = MUX_ACLK_100_SEL_SHIFT,
+ .div_shift = ACLK_100_RATIO_SHIFT,
+};
+
+
+/* TODO: add other needed structures for UARTs sources */
+static Exynos4210CmuClockState sclk_uart0 = {
+ .name = "SCLK_UART0",
+ .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+ .src_reg = CLK_SRC_PERIL0,
+ .div_reg = CLK_DIV_PERIL0,
+ .mux_shift = UART0_SEL_SHIFT,
+ .div_shift = UART0_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart1 = {
+ .name = "SCLK_UART1",
+ .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+ .src_reg = CLK_SRC_PERIL0,
+ .div_reg = CLK_DIV_PERIL0,
+ .mux_shift = UART1_SEL_SHIFT,
+ .div_shift = UART1_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart2 = {
+ .name = "SCLK_UART2",
+ .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+ .src_reg = CLK_SRC_PERIL0,
+ .div_reg = CLK_DIV_PERIL0,
+ .mux_shift = UART2_SEL_SHIFT,
+ .div_shift = UART2_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart3 = {
+ .name = "SCLK_UART3",
+ .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+ .src_reg = CLK_SRC_PERIL0,
+ .div_reg = CLK_DIV_PERIL0,
+ .mux_shift = UART3_SEL_SHIFT,
+ .div_shift = UART3_DIV_SHIFT,
+};
+
+static Exynos4210CmuClockState sclk_uart4 = {
+ .name = "SCLK_UART4",
+ .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll},
+ .src_reg = CLK_SRC_PERIL0,
+ .div_reg = CLK_DIV_PERIL0,
+ .mux_shift = UART4_SEL_SHIFT,
+ .div_shift = UART4_DIV_SHIFT,
+};
+
+/*
+ * This array must correspond to Exynos4210CmuClock enumerator
+ * which is defined in exynos4210.h file
+ *
+ */
+static Exynos4210CmuClockState *exynos4210_clock[] = {
+ &xxti,
+ &xusbxti,
+ &apll,
+ &mpll,
+ &sclk_apll,
+ &sclk_mpll,
+ &aclk_100,
+ &sclk_uart0,
+ &sclk_uart1,
+ &sclk_uart2,
+ &sclk_uart3,
+ &sclk_uart4,
+ NULL
+};
+
+
+uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock)
+{
+ return exynos4210_clock[clock]->rate;
+}
+
+#ifdef DEBUG_CMU
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *exynos4210_cmu_regname(target_phys_addr_t offset)
+{
+
+ int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ if (offset == exynos4210_cmu_regs[i].offset) {
+ return exynos4210_cmu_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+static void exynos4210_cmu_set_pll(void *opaque, target_phys_addr_t offset)
+{
+ Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+ uint32_t pdiv, mdiv, sdiv, enable;
+
+ /*
+ * FOUT = MDIV * FIN / (PDIV * 2^(SDIV-1))
+ */
+
+ enable = (s->reg[I_(offset)] & PLL_ENABLE_MASK) >> PLL_ENABLE_SHIFT;
+ mdiv = (s->reg[I_(offset)] & PLL_MDIV_MASK) >> PLL_MDIV_SHIFT;
+ pdiv = (s->reg[I_(offset)] & PLL_PDIV_MASK) >> PLL_PDIV_SHIFT;
+ sdiv = (s->reg[I_(offset)] & PLL_SDIV_MASK) >> PLL_SDIV_SHIFT;
+
+ switch (offset) {
+
+ case MPLL_CON0:
+ if (mpll.source) {
+ if (enable) {
+ mpll.rate = mdiv * mpll.source->rate / (pdiv * (1 << (sdiv-1)));
+ } else {
+ mpll.rate = 0;
+ }
+ } else {
+ hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n",
+ mpll.name);
+ }
+ PRINT_DEBUG("mpll.rate: %llu\n", (unsigned long long int)mpll.rate);
+ break;
+
+ case APLL_CON0:
+ if (apll.source) {
+ if (enable) {
+ apll.rate = mdiv * apll.source->rate / (pdiv * (1 << (sdiv-1)));
+ } else {
+ apll.rate = 0;
+ }
+ } else {
+ hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n",
+ apll.name);
+ }
+ PRINT_DEBUG("apll.rate: %llu\n", (unsigned long long int)apll.rate);
+ break;
+
+ default:
+ hw_error("exynos4210_cmu_set_pll: Bad offset 0x%x\n", (int)offset);
+ }
+
+ s->reg[I_(offset)] |= PLL_LOCKED_MASK;
+}
+
+
+static void
+exynos4210_cmu_set_rate(void *opaque, Exynos4210CmuClockState *clock)
+{
+ Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+ int i;
+
+ /* Rates of PLLs are calculated differently than ordinary clocks rates */
+ if (clock == &mpll) {
+ exynos4210_cmu_set_pll(s, MPLL_CON0);
+ } else if (clock == &apll) {
+ exynos4210_cmu_set_pll(s, APLL_CON0);
+ } else if ((clock != &xxti) && (clock != &xusbxti)) {
+ /*
+ * Not root clock. We don't need calculating rate
+ * of root clock because it is hard coded.
+ */
+ uint32_t src_index = I_(clock->src_reg);
+ uint32_t div_index = I_(clock->div_reg);
+ clock->source = clock->sources[(s->reg[src_index] >>
+ clock->mux_shift) & 0xf];
+ clock->rate = muldiv64(clock->source->rate, 1,
+ (((s->reg[div_index] >> clock->div_shift) & 0xf)
+ + 1));
+
+ PRINT_DEBUG_EXTEND("SRC: <0x%05x> %s, SHIFT: %d\n",
+ clock->src_reg,
+ exynos4210_cmu_regname(clock->src_reg),
+ clock->mux_shift);
+
+ PRINT_DEBUG("%s [%s:%llu]: %llu\n",
+ clock->name,
+ clock->source->name,
+ (uint64_t)clock->source->rate,
+ (uint64_t)clock->rate);
+ }
+
+ /* Visit all recipients for given clock */
+ i = 0;
+ do {
+
+ Exynos4210CmuClockState *recipient_clock = clock->recipients[i];
+
+ if (recipient_clock == NULL) {
+ PRINT_DEBUG_EXTEND("%s have %d recipients\n", clock->name, i);
+ break;
+ }
+
+ uint32_t src_index = recipient_clock->src_reg / sizeof(uint32_t);
+ int source_index = s->reg[src_index] >>
+ recipient_clock->mux_shift & 0xf;
+ recipient_clock->source = recipient_clock->sources[source_index];
+
+ if (recipient_clock->source != clock) {
+ break;
+ }
+
+ exynos4210_cmu_set_rate(s, recipient_clock);
+
+ i++;
+ } while (i < RECIPIENTS_NUMBER);
+}
+
+
+static uint64_t exynos4210_cmu_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+
+ if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("exynos4210_cmu_read: Bad offset 0x%x\n", (int)offset);
+ }
+
+ PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n", offset,
+ exynos4210_cmu_regname(offset), s->reg[I_(offset)]);
+
+ return s->reg[I_(offset)];
+}
+
+
+static void exynos4210_cmu_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+ uint32_t pre_val;
+
+ if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("exynos4210_cmu_write: Bad offset 0x%x\n", (int)offset);
+ }
+
+ pre_val = s->reg[I_(offset)];
+ s->reg[I_(offset)] = val;
+
+ PRINT_DEBUG_EXTEND("<0x%05x> %s <- %08x\n", offset,
+ exynos4210_cmu_regname(offset), s->reg[I_(offset)]);
+
+ switch (offset) {
+
+ case APLL_CON0:
+ val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+ s->reg[I_(offset)] = val;
+ exynos4210_cmu_set_rate(s, &apll);
+ break;
+ case MPLL_CON0:
+ val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+ s->reg[I_(offset)] = val;
+ exynos4210_cmu_set_rate(s, &mpll);
+ break;
+ case CLK_SRC_CPU:
+ {
+ if (val & MUX_APLL_SEL) {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) |
+ (2 << APLL_SEL_SHIFT);
+
+ if ((pre_val & MUX_APLL_SEL) !=
+ (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+ exynos4210_cmu_set_rate(s, &apll);
+ }
+
+ } else {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) |
+ (1 << APLL_SEL_SHIFT);
+
+ if ((pre_val & MUX_APLL_SEL) !=
+ (s->reg[I_(offset)] & MUX_APLL_SEL)) {
+ exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+
+
+ if (val & MUX_MPLL_SEL) {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) |
+ (2 << MPLL_SEL_SHIFT);
+
+ if ((pre_val & MUX_MPLL_SEL) !=
+ (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+ exynos4210_cmu_set_rate(s, &mpll);
+ }
+
+ } else {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) |
+ (1 << MPLL_SEL_SHIFT);
+
+ if ((pre_val & MUX_MPLL_SEL) !=
+ (s->reg[I_(offset)] & MUX_MPLL_SEL)) {
+ exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+
+ if (val & MUX_CORE_SEL) {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) |
+ (2 << CORE_SEL_SHIFT);
+
+ if ((pre_val & MUX_CORE_SEL) !=
+ (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+ exynos4210_cmu_set_rate(s, &sclk_mpll);
+ }
+
+ } else {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) |
+ (1 << CORE_SEL_SHIFT);
+
+ if ((pre_val & MUX_CORE_SEL) !=
+ (s->reg[I_(offset)] & MUX_CORE_SEL)) {
+ exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+
+ if (val & MUX_HPM_SEL) {
+ exynos4210_cmu_set_rate(s, &sclk_mpll);
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) |
+ (2 << HPM_SEL_SHIFT);
+
+ if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+ exynos4210_cmu_set_rate(s, &sclk_mpll);
+ }
+
+ } else {
+ s->reg[I_(CLK_MUX_STAT_CPU)] =
+ (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) |
+ (1 << HPM_SEL_SHIFT);
+
+ if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) {
+ exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+ }
+ break;
+ case CLK_DIV_CPU0:
+ exynos4210_cmu_set_rate(s, &sclk_apll);
+ exynos4210_cmu_set_rate(s, &sclk_mpll);
+ break;
+ case CLK_SRC_TOP0:
+ case CLK_DIV_TOP:
+ exynos4210_cmu_set_rate(s, &aclk_100);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static const MemoryRegionOps exynos4210_cmu_ops = {
+ .read = exynos4210_cmu_read,
+ .write = exynos4210_cmu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void exynos4210_cmu_reset(void *opaque)
+{
+ Exynos4210CmuState *s = (Exynos4210CmuState *)opaque;
+ int i = 0, j = 0, n = 0;
+ int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg);
+ uint32_t index = 0;
+
+ /* Set default values for registers */
+ for (i = 0; i < regs_number; i++) {
+ index = (exynos4210_cmu_regs[i].offset) / sizeof(uint32_t);
+ s->reg[index] = exynos4210_cmu_regs[i].reset_value;
+ }
+
+ /* clear recipients array from previous reset */
+ for (i = 0; i < CLOCKS_NUMBER; i++) {
+ bzero(exynos4210_clock[i]->recipients,
+ RECIPIENTS_NUMBER * sizeof(Exynos4210CmuClockState *));
+ }
+
+ /*
+ * Here we fill '.recipients' fields in all clocks. Also we fill empty
+ * 'sources[]' arrays by values of 'source' fields (it is necessary
+ * for set rate, for example). If 'sources[]' array and 'source' field
+ * is empty simultaneously we get hw_error.
+ *
+ */
+ for (i = 0; i < CLOCKS_NUMBER; i++) {
+
+ /* visit all clocks in the exynos4210_clock */
+
+ PRINT_DEBUG("[SOURCES] %s: ", exynos4210_clock[i]->name);
+
+ j = 0;
+ do { /* visit all sources for current clock (exynos4210_clock[i]) */
+
+ if ((exynos4210_clock[i]->sources[j] == NULL)) {
+
+ if (j == 0) { /* check if we have empty '.sources[]' array */
+ if (exynos4210_clock[i]->source != NULL) {
+ exynos4210_clock[i]->sources[j] =
+ exynos4210_clock[i]->source;
+ } else {
+ /*
+ * We haven't any defined sources for this clock. Error
+ * during definition of appropriate clock structure
+ *
+ */
+ if ((exynos4210_clock[i] != &xusbxti) &&
+ (exynos4210_clock[i] != &xxti)) {
+
+ hw_error("exynos4210_cmu_reset:"
+ "There aren't any sources for %s clock!\n",
+ exynos4210_clock[i]->name);
+ } else {
+ /*
+ * we don't need any sources for this clock
+ * because it's a root clock
+ */
+ break;
+ }
+ }
+ } else {
+ break; /* leave because there are no more sources */
+ }
+
+ }
+
+ PRINT_DEBUG_SIMPLE(" %s", exynos4210_clock[i]->sources[j]->name);
+
+ /*
+ * find first empty place in 'recipients[]' array of
+ * current 'sources' element and put current clock there
+ */
+ n = 0;
+ do {
+ if ((exynos4210_clock[i]->sources[j]->recipients[n]) == NULL) {
+ exynos4210_clock[i]->sources[j]->recipients[n] =
+ exynos4210_clock[i];
+ break;
+ }
+ n++;
+ } while (n < RECIPIENTS_NUMBER);
+
+ j++;
+
+ } while (j < SOURCES_NUMBER);
+
+ PRINT_DEBUG_SIMPLE("\n");
+
+ } /* CLOCKS_NUMBER */
+
+#ifdef DEBUG_CMU
+ for (i = 0; i < CLOCKS_NUMBER; i++) {
+ PRINT_DEBUG("[RECIPIENTS] %s: ", exynos4210_clock[i]->name);
+ for (j = 0;
+ (j < RECIPIENTS_NUMBER) &&
+ ((exynos4210_clock[i]->recipients[j]) != NULL);
+ j++) {
+ PRINT_DEBUG_SIMPLE("%s ", exynos4210_clock[i]->recipients[j]->name);
+ }
+ PRINT_DEBUG_SIMPLE("\n");
+ }
+#endif
+
+ exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+}
+
+static const VMStateDescription vmstate_exynos4210_cmu = {
+ .name = "exynos4210.cmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ /*
+ * TODO: Maybe we should save Exynos4210CmuClockState structs as well
+ */
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210CmuState,
+ EXYNOS4210_CMU_REGS_MEM_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int exynos4210_cmu_init(SysBusDevice *dev)
+{
+ Exynos4210CmuState *s = FROM_SYSBUS(Exynos4210CmuState, dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, &exynos4210_cmu_ops, s, "exynos4210.cmu",
+ EXYNOS4210_CMU_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ qemu_register_reset(exynos4210_cmu_reset, s);
+
+ vmstate_register(&dev->qdev, -1, &vmstate_exynos4210_cmu, s);
+
+ exynos4210_cmu_reset(s);
+
+ return 0;
+}
+
+
+static void exynos4210_cmu_register(void)
+{
+ sysbus_register_dev("exynos4210.cmu",
+ sizeof(Exynos4210CmuState),
+ exynos4210_cmu_init);
+}
+
+
+device_init(exynos4210_cmu_register)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
` (11 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
From: Maksim Kozlov <m.kozlov@samsung.com>
Add basic support of exynos4210 UART
Conflicts:
Makefile.target
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/exynos4210.c | 51 ++++
hw/exynos4210.h | 9 +
hw/exynos4210_uart.c | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 735 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_uart.c
diff --git a/Makefile.target b/Makefile.target
index ce4f1f8..4c706b1 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,7 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
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
+obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.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 1a6e353..d5a1fe0 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -63,6 +63,18 @@
/* SFR Base Address for CMUs */
#define EXYNOS4210_CMU_BASE_ADDR 0x10030000
+/* UART's definitions */
+#define EXYNOS4210_UART_BASE_ADDR 0x13800000
+#define EXYNOS4210_UART_SHIFT 0x00010000
+
+#define EXYNOS4210_UARTS_NUMBER 4
+
+#define EXYNOS4210_UART_CHANNEL(addr) ((addr >> 16) & 0x7)
+#define EXYNOS4210_UART0_FIFO_SIZE 256
+#define EXYNOS4210_UART1_FIFO_SIZE 64
+#define EXYNOS4210_UART2_FIFO_SIZE 16
+#define EXYNOS4210_UART3_FIFO_SIZE 16
+
static struct arm_boot_info exynos4210_binfo = {
.loader_start = EXYNOS4210_BASE_BOOT_ADDR,
@@ -179,6 +191,45 @@ static void exynos4210_init(ram_addr_t ram_size,
/* CMU */
sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
+ /*** UARTs ***/
+ for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
+
+ uint32_t addr = EXYNOS4210_UART_BASE_ADDR + EXYNOS4210_UART_SHIFT * n;
+ int channel = EXYNOS4210_UART_CHANNEL(addr);
+ qemu_irq uart_irq;
+ int fifo_size = 0;
+
+ switch (channel) {
+ case 0:
+ fifo_size = EXYNOS4210_UART0_FIFO_SIZE;
+ break;
+ case 1:
+ fifo_size = EXYNOS4210_UART1_FIFO_SIZE;
+ break;
+ case 2:
+ fifo_size = EXYNOS4210_UART2_FIFO_SIZE;
+ break;
+ case 3:
+ fifo_size = EXYNOS4210_UART3_FIFO_SIZE;
+ break;
+ default:
+ fifo_size = 0;
+ PRINT_DEBUG("Wrong channel number: %d\n", channel);
+ break;
+ }
+
+ if (fifo_size == 0) {
+ PRINT_DEBUG("Can't create UART%d with fifo size %d\n",
+ channel, fifo_size);
+ continue;
+ }
+
+ uart_irq = NULL;
+
+ exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
+ }
+
+
/*** Load kernel ***/
exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 683a4a6..3df7322 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -53,4 +53,13 @@ typedef enum {
uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock);
+/*
+ * exynos4210 UART
+ */
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq);
+
#endif /* EXYNOS4210_H_ */
diff --git a/hw/exynos4210_uart.c b/hw/exynos4210_uart.c
new file mode 100644
index 0000000..22c24b7
--- /dev/null
+++ b/hw/exynos4210_uart.c
@@ -0,0 +1,674 @@
+/*
+ * exynos4210 UART Emulation
+ *
+ * Copyright (C) 2011 Samsung Electronics Co Ltd.
+ * Maksim Kozlov, <m.kozlov@samsung.com>
+ *
+ * Created on: 07.2011
+ *
+ *
+ * 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 "sysemu.h"
+#include "qemu-char.h"
+
+#include "exynos4210.h"
+
+#undef DEBUG_UART
+#undef DEBUG_UART_EXTEND
+#undef DEBUG_IRQ
+#undef DEBUG_Rx_DATA
+#undef DEBUG_Tx_DATA
+
+
+//#define DEBUG_UART
+//#define DEBUG_UART_EXTEND
+//#define DEBUG_IRQ
+//#define DEBUG_Rx_DATA
+//#define DEBUG_Tx_DATA
+
+
+#define PRINT_DEBUG(fmt, args...) \
+ do {} while (0)
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do {} while (0)
+#define PRINT_ERROR(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#ifdef DEBUG_UART
+
+#undef PRINT_DEBUG
+#define PRINT_DEBUG(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#ifdef DEBUG_UART_EXTEND
+
+#undef PRINT_DEBUG_EXTEND
+#define PRINT_DEBUG_EXTEND(fmt, args...) \
+ do { \
+ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \
+ } while (0)
+
+#endif /* EXTEND */
+#endif
+
+
+/*
+ * Offsets for UART registers relative to SFR base address
+ * for UARTn
+ *
+ */
+#define ULCON 0x0000 /* Line Control */
+#define UCON 0x0004 /* Control */
+#define UFCON 0x0008 /* FIFO Control */
+#define UMCON 0x000C /* Modem Control */
+#define UTRSTAT 0x0010 /* Tx/Rx Status */
+#define UERSTAT 0x0014 /* Rx Error Status */
+#define UFSTAT 0x0018 /* FIFO Status */
+#define UMSTAT 0x001C /* Modem Status */
+#define UTXH 0x0020 /* Transmit Buffer */
+#define URXH 0x0024 /* Receive Buffer */
+#define UBRDIV 0x0028 /* Baud Rate Divisor */
+#define UFRACVAL 0x002C /* Divisor Fractional Value */
+#define UINTP 0x0030 /* Interrupt Pending */
+#define UINTSP 0x0034 /* Interrupt Source Pending */
+#define UINTM 0x0038 /* Interrupt Mask */
+
+/*
+ * for indexing register in the uint32_t array
+ *
+ * 'reg' - register offset (see offsets definitions above)
+ *
+ */
+#define I_(reg) (reg / sizeof(uint32_t))
+
+typedef struct Exynos4210UartReg {
+ const char *name; /* the only reason is the debug output */
+ target_phys_addr_t offset;
+ uint32_t reset_value;
+} Exynos4210UartReg;
+
+static Exynos4210UartReg exynos4210_uart_regs[] = {
+ {"ULCON" , ULCON , 0x00000000},
+ {"UCON" , UCON , 0x00003000},
+ {"UFCON" , UFCON , 0x00000000},
+ {"UMCON" , UMCON , 0x00000000},
+ {"UTRSTAT" , UTRSTAT , 0x00000006}, /* RO */
+ {"UERSTAT" , UERSTAT , 0x00000000}, /* RO */
+ {"UFSTAT" , UFSTAT , 0x00000000}, /* RO */
+ {"UMSTAT" , UMSTAT , 0x00000000}, /* RO */
+ {"UTXH" , UTXH , 0x5c5c5c5c}, /* WO, undefined reset value*/
+ {"URXH" , URXH , 0x00000000}, /* RO */
+ {"UBRDIV" , UBRDIV , 0x00000000},
+ {"UFRACVAL", UFRACVAL, 0x00000000},
+ {"UINTP" , UINTP , 0x00000000},
+ {"UINTSP" , UINTSP , 0x00000000},
+ {"UINTM" , UINTM , 0x00000000},
+};
+
+#define EXYNOS4210_UART_REGS_MEM_SIZE 0x3c
+
+/* UART FIFO Control */
+#define UFCON_FIFO_ENABLE 0x1
+#define UFCON_Rx_FIFO_RESET 0x2
+#define UFCON_Tx_FIFO_RESET 0x4
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT 8
+#define UFCON_Tx_FIFO_TRIGGER_LEVEL \
+ (7 << UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT)
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT 4
+#define UFCON_Rx_FIFO_TRIGGER_LEVEL \
+ (7 << UFCON_Rx_FIFO_TRIGGER_LEVEL_SHIFT)
+
+/* Uart FIFO Status */
+#define UFSTAT_Rx_FIFO_COUNT 0xff
+#define UFSTAT_Rx_FIFO_FULL 0x100
+#define UFSTAT_Rx_FIFO_ERROR 0x200
+#define UFSTAT_Tx_FIFO_COUNT_SHIFT 16
+#define UFSTAT_Tx_FIFO_COUNT (0xff << UFSTAT_Tx_FIFO_COUNT_SHIFT)
+#define UFSTAT_Tx_FIFO_FULL_SHIFT 24
+#define UFSTAT_Tx_FIFO_FULL (1 << UFSTAT_Tx_FIFO_FULL_SHIFT)
+
+/* UART Interrupt Source Pending */
+#define UINTSP_RXD 0x1 /* Receive interrupt */
+#define UINTSP_ERROR 0x2 /* Error interrupt */
+#define UINTSP_TXD 0x4 /* Transmit interrupt */
+#define UINTSP_MODEM 0x8 /* Modem interrupt */
+
+/* UART Line Control */
+#define ULCON_IR_MODE_SHIFT 6
+#define ULCON_PARITY_SHIFT 3
+#define ULCON_STOP_BIT_SHIFT 1
+
+
+
+/* Specifies Tx/Rx Status */
+#define UTRSTAT_TRANSMITTER_EMPTY 0x4
+#define UTRSTAT_Tx_BUFFER_EMPTY 0x2
+#define UTRSTAT_Rx_BUFFER_DATA_READY 0x1
+
+typedef struct {
+ uint8_t *data;
+ uint32_t sp, rp; /* store and retrieve pointers */
+ uint32_t size;
+} Exynos4210UartFIFO;
+
+typedef struct {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg[EXYNOS4210_UART_REGS_MEM_SIZE];
+ Exynos4210UartFIFO rx;
+ Exynos4210UartFIFO tx;
+
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t channel;
+
+} Exynos4210UartState;
+
+
+#ifdef DEBUG_UART
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *exynos4210_uart_regname(target_phys_addr_t offset)
+{
+
+ int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ if (offset == exynos4210_uart_regs[i].offset) {
+ return exynos4210_uart_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void fifo_store(Exynos4210UartFIFO *q, uint8_t ch)
+{
+ q->data[q->sp] = ch;
+ q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(Exynos4210UartFIFO *q)
+{
+ uint8_t ret = q->data[q->rp];
+ q->rp = (q->rp + 1) % q->size;
+ return ret;
+}
+
+static int fifo_elements_number(Exynos4210UartFIFO *q)
+{
+ if (q->sp < q->rp) {
+ return q->size - q->rp + q->sp;
+ }
+
+ return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(Exynos4210UartFIFO *q)
+{
+ return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(Exynos4210UartFIFO *q)
+{
+ if (q->data != NULL) {
+ g_free(q->data);
+ q->data = NULL;
+ }
+
+ q->data = (uint8_t *)g_malloc0(q->size);
+
+ q->sp = 0;
+ q->rp = 0;
+}
+
+static uint32_t exynos4210_uart_Tx_FIFO_trigger_level(Exynos4210UartState *s)
+{
+ uint32_t level = 0;
+ uint32_t reg;
+
+ reg = (s->reg[I_(UFCON)] && UFCON_Tx_FIFO_TRIGGER_LEVEL) >>
+ UFCON_Tx_FIFO_TRIGGER_LEVEL_SHIFT;
+
+ switch (s->channel) {
+ case 0:
+ level = reg * 32;
+ break;
+ case 1:
+ case 4:
+ level = reg * 8;
+ break;
+ case 2:
+ case 3:
+ level = reg * 2;
+ break;
+ default:
+ level = 0;
+ PRINT_ERROR("Wrong UART chennel number: %d\n", s->channel);
+ }
+
+ return level;
+}
+
+static void exynos4210_uart_update_irq(Exynos4210UartState *s)
+{
+ /*
+ * The Tx interrupt is always requested if the number of data in the
+ * transmit FIFO is smaller than the trigger level.
+ */
+ if (s->reg[I_(UFCON)] && UFCON_FIFO_ENABLE) {
+
+ uint32_t count = (s->reg[I_(UFSTAT)] && UFSTAT_Tx_FIFO_COUNT) >>
+ UFSTAT_Tx_FIFO_COUNT_SHIFT;
+
+ if (count <= exynos4210_uart_Tx_FIFO_trigger_level(s)) {
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ }
+ }
+
+ s->reg[I_(UINTP)] = s->reg[I_(UINTSP)] & ~s->reg[I_(UINTM)];
+
+ if (s->reg[I_(UINTP)]) {
+ qemu_irq_raise(s->irq);
+
+#ifdef DEBUG_IRQ
+ fprintf(stderr, "UART%d: IRQ have been raised: %08x\n",
+ s->channel, s->reg[I_(UINTP)]);
+#endif
+
+ } else {
+ qemu_irq_lower(s->irq);
+ }
+}
+
+static void exynos4210_uart_update_parameters(Exynos4210UartState *s)
+{
+ int speed, parity, data_bits, stop_bits, frame_size;
+ QEMUSerialSetParams ssp;
+ uint64_t uclk_rate;
+
+ if (s->reg[I_(UBRDIV)] == 0) {
+ return;
+ }
+
+ frame_size = 1; /* start bit */
+ if (s->reg[I_(ULCON)] & 0x20) {
+ frame_size++; /* parity bit */
+ if (s->reg[I_(ULCON)] & 0x28) {
+ parity = 'E';
+ } else {
+ parity = 'O';
+ }
+ } else {
+ parity = 'N';
+ }
+
+ if (s->reg[I_(ULCON)] & 0x4) {
+ stop_bits = 2;
+ } else {
+ stop_bits = 1;
+ }
+
+ data_bits = (s->reg[I_(ULCON)] & 0x3) + 5;
+
+ frame_size += data_bits + stop_bits;
+
+ switch (s->channel) {
+ case 0:
+ uclk_rate = exynos4210_cmu_get_rate(SCLK_UART0); break;
+ case 1:
+ uclk_rate = exynos4210_cmu_get_rate(SCLK_UART1); break;
+ case 2:
+ uclk_rate = exynos4210_cmu_get_rate(SCLK_UART2); break;
+ case 3:
+ uclk_rate = exynos4210_cmu_get_rate(SCLK_UART3); break;
+ case 4:
+ uclk_rate = exynos4210_cmu_get_rate(SCLK_UART4); break;
+ default:
+ hw_error("%s: Incorrect UART channel: %d\n",
+ __func__, s->channel);
+ }
+
+ speed = uclk_rate / ((16 * (s->reg[I_(UBRDIV)]) & 0xffff) +
+ (s->reg[I_(UFRACVAL)] & 0x7) + 16);
+
+ ssp.speed = speed;
+ ssp.parity = parity;
+ ssp.data_bits = data_bits;
+ ssp.stop_bits = stop_bits;
+
+ qemu_chr_fe_ioctl(s->chr, CHR_IOCTL_SERIAL_SET_PARAMS, &ssp);
+
+ PRINT_DEBUG("UART%d: speed: %d, parity: %c, data: %d, stop: %d\n",
+ s->channel, speed, parity, data_bits, stop_bits);
+}
+
+static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint8_t ch;
+
+ if (offset > (EXYNOS4210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("exynos4210_cmu_write: Bad offset 0x%x\n", (int)offset);
+ }
+
+ PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n",
+ s->channel, offset, exynos4210_uart_regname(offset), val);
+
+ switch (offset) {
+ case ULCON:
+ case UBRDIV:
+ case UFRACVAL:
+ s->reg[I_(offset)] = val;
+ exynos4210_uart_update_parameters(s);
+ break;
+ case UFCON:
+ s->reg[I_(UFCON)] = val;
+ if (val & UFCON_Rx_FIFO_RESET) {
+ fifo_reset(&s->rx);
+ s->reg[I_(UFCON)] &= ~UFCON_Rx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Rx FIFO Reset\n", s->channel);
+ }
+ if (val & UFCON_Tx_FIFO_RESET) {
+ fifo_reset(&s->tx);
+ s->reg[I_(UFCON)] &= ~UFCON_Tx_FIFO_RESET;
+ PRINT_DEBUG("UART%d: Tx FIFO Reset\n", s->channel);
+ }
+ break;
+
+ case UTXH:
+ if (s->chr) {
+ s->reg[I_(UTRSTAT)] &= ~(UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY);
+ ch = (uint8_t)val;
+ qemu_chr_fe_write(s->chr, &ch, 1);
+#ifdef DEBUG_Tx_DATA
+ fprintf(stderr, "%c", ch);
+#endif
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_TRANSMITTER_EMPTY |
+ UTRSTAT_Tx_BUFFER_EMPTY;
+ s->reg[I_(UINTSP)] |= UINTSP_TXD;
+ exynos4210_uart_update_irq(s);
+ }
+ break;
+
+ case UINTP:
+ s->reg[I_(UINTP)] &= ~val;
+ /* XXX: It's the assumption that it works in this way */
+ s->reg[I_(UINTSP)] &= ~val;
+ PRINT_DEBUG("UART%d: UINTP [%04x] have been cleared: %08x\n",
+ s->channel, offset, s->reg[I_(UINTP)]);
+ exynos4210_uart_update_irq(s);
+ break;
+ case UTRSTAT:
+ case UERSTAT:
+ case UFSTAT:
+ case UMSTAT:
+ case URXH:
+ PRINT_DEBUG("UART%d: Trying to write into RO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ case UINTSP:
+ /* XXX: It's the assumption that it works in this way */
+ s->reg[I_(UINTSP)] &= ~val;
+ break;
+ case UINTM:
+ s->reg[I_(UINTM)] = val;
+ exynos4210_uart_update_irq(s);
+ break;
+ case UCON:
+ case UMCON:
+ default:
+ s->reg[I_(offset)] = val;
+ break;
+ }
+}
+static uint64_t exynos4210_uart_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ uint32_t res;
+
+ if (offset > (EXYNOS4210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("exynos4210_cmu_read: Bad offset 0x%x\n", (int)offset);
+ }
+
+ switch (offset) {
+ case UERSTAT: /* Read Only */
+ res = s->reg[I_(UERSTAT)];
+ s->reg[I_(UERSTAT)] = 0;
+ return res;
+ case UFSTAT: /* Read Only */
+ s->reg[I_(UFSTAT)] = fifo_elements_number(&s->rx) & 0xff;
+ if (fifo_empty_elements_number(&s->rx) == 0) {
+ s->reg[I_(UFSTAT)] |= UFSTAT_Rx_FIFO_FULL;
+ s->reg[I_(UFSTAT)] &= ~0xff;
+ }
+ return s->reg[I_(UFSTAT)];
+ case URXH:
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_elements_number(&s->rx)) {
+ res = fifo_retrieve(&s->rx);
+#ifdef DEBUG_Rx_DATA
+ fprintf(stderr, "%c", res);
+#endif
+ if (!fifo_elements_number(&s->rx)) {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ } else {
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ exynos4210_uart_update_irq(s);
+ res = 0;
+ }
+ } else {
+ s->reg[I_(UTRSTAT)] &= ~UTRSTAT_Rx_BUFFER_DATA_READY;
+ res = s->reg[I_(URXH)];
+ }
+ return res;
+ case UTXH:
+ PRINT_DEBUG("UART%d: Trying to read from WO register: %s [%04x]\n",
+ s->channel, exynos4210_uart_regname(offset), offset);
+ break;
+ default:
+ return s->reg[I_(offset)];
+ break;
+ }
+
+ return 0;
+}
+
+
+static const MemoryRegionOps exynos4210_uart_ops = {
+ .read = exynos4210_uart_read,
+ .write = exynos4210_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_uart_can_receive(void *opaque)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+
+ return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void exynos4210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ Exynos4210UartState *s = (Exynos4210UartState *)opaque;
+ int i;
+
+ if (s->reg[I_(UFCON)] & UFCON_FIFO_ENABLE) {
+ if (fifo_empty_elements_number(&s->rx) < size) {
+ for (i = 0; i < fifo_empty_elements_number(&s->rx); i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UINTSP)] |= UINTSP_ERROR;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ } else {
+ for (i = 0; i < size; i++) {
+ fifo_store(&s->rx, buf[i]);
+ }
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+ /* XXX: after achieve trigger level*/
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ } else {
+ s->reg[I_(URXH)] = buf[0];
+ s->reg[I_(UINTSP)] |= UINTSP_RXD;
+ s->reg[I_(UTRSTAT)] |= UTRSTAT_Rx_BUFFER_DATA_READY;
+ }
+
+ exynos4210_uart_update_irq(s);
+}
+
+
+static void exynos4210_uart_event(void *opaque, int event)
+{
+ /*
+ * TODO: Implement this.
+ *
+ */
+}
+
+
+static void exynos4210_uart_reset(DeviceState *dev)
+{
+ Exynos4210UartState *s =
+ container_of(dev, Exynos4210UartState, busdev.qdev);
+ int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ s->reg[I_(exynos4210_uart_regs[i].offset)] =
+ exynos4210_uart_regs[i].reset_value;
+ }
+
+ fifo_reset(&s->rx);
+ fifo_reset(&s->tx);
+
+ PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+
+static const VMStateDescription vmstate_exynos4210_uart = {
+ .name = "exynos4210.uart",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ /*
+ * TODO: We should save fifo too
+ */
+ VMSTATE_UINT32_ARRAY(reg, Exynos4210UartState,
+ EXYNOS4210_UART_REGS_MEM_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+DeviceState *exynos4210_uart_create(target_phys_addr_t addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+
+ dev = qdev_create(NULL, "exynos4210.uart");
+
+ if (!chr) {
+ if (channel >= MAX_SERIAL_PORTS) {
+ hw_error("Only %d serial ports are supported by QEMU.\n",
+ MAX_SERIAL_PORTS);
+ }
+ chr = serial_hds[channel];
+ if (!chr) {
+ chr = qemu_chr_new("exynos4210.uart", "null", NULL);
+ if (!(chr)) {
+ hw_error("Can't assign serial port to UART%d.\n", channel);
+ }
+ }
+ }
+
+ qdev_prop_set_chr(dev, "chardev", chr);
+ qdev_prop_set_uint32(dev, "channel", channel);
+ qdev_prop_set_uint32(dev, "rx-size", fifo_size);
+ qdev_prop_set_uint32(dev, "tx-size", fifo_size);
+
+ bus = sysbus_from_qdev(dev);
+ qdev_init_nofail(dev);
+ if (addr != (target_phys_addr_t)-1) {
+ sysbus_mmio_map(bus, 0, addr);
+ }
+ sysbus_connect_irq(bus, 0, irq);
+
+ return dev;
+}
+
+static int exynos4210_uart_init(SysBusDevice *dev)
+{
+ Exynos4210UartState *s = FROM_SYSBUS(Exynos4210UartState, dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, &exynos4210_uart_ops, s, "exynos4210.uart",
+ EXYNOS4210_UART_REGS_MEM_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ qemu_chr_add_handlers(s->chr, exynos4210_uart_can_receive,
+ exynos4210_uart_receive, exynos4210_uart_event, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo exynos4210_uart_info = {
+ .init = exynos4210_uart_init,
+ .qdev.name = "exynos4210.uart",
+ .qdev.size = sizeof(Exynos4210UartState),
+ .qdev.vmsd = &vmstate_exynos4210_uart,
+ .qdev.reset = exynos4210_uart_reset,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr),
+ DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0),
+ DEFINE_PROP_UINT32("rx-size", Exynos4210UartState, rx.size, 16),
+ DEFINE_PROP_UINT32("tx-size", Exynos4210UartState, tx.size, 16),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void exynos4210_uart_register(void)
+{
+ sysbus_register_withprop(&exynos4210_uart_info);
+}
+
+device_init(exynos4210_uart_register)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (2 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
` (10 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Samsung exynos4210 Interrupt Combiner needs 512 IRQ sources.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/sysbus.h | 2 +-
1 files changed, 1 insertions(+), 1 deletions(-)
diff --git a/hw/sysbus.h b/hw/sysbus.h
index 9bac582..4ef0c3c 100644
--- a/hw/sysbus.h
+++ b/hw/sysbus.h
@@ -8,7 +8,7 @@
#define QDEV_MAX_MMIO 32
#define QDEV_MAX_PIO 32
-#define QDEV_MAX_IRQ 256
+#define QDEV_MAX_IRQ 512
typedef struct SysBusDevice SysBusDevice;
typedef void (*mmio_mapfunc)(SysBusDevice *dev, target_phys_addr_t addr);
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (3 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
` (9 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 3 +-
hw/exynos4210.c | 161 ++++++++++++++++++-
hw/exynos4210.h | 41 +++++
hw/exynos4210_combiner.c | 385 ++++++++++++++++++++++++++++++++++++++++++
hw/exynos4210_gic.c | 415 ++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 1003 insertions(+), 2 deletions(-)
create mode 100644 hw/exynos4210_combiner.c
create mode 100644 hw/exynos4210_gic.c
diff --git a/Makefile.target b/Makefile.target
index 4c706b1..779c9d4 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -344,7 +344,8 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o
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
+obj-arm-y += exynos4210.o exynos4210_cmu.o exynos4210_uart.o exynos4210_gic.o \
+ exynos4210_combiner.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 d5a1fe0..a936dcf 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -58,6 +58,8 @@
#define EXYNOS4210_SFR_BASE_ADDR 0x10000000
+#define EXYNOS4210_SMP_PRIVATE_BASE_ADDR 0x10500000
+
#define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR
/* SFR Base Address for CMUs */
@@ -74,7 +76,16 @@
#define EXYNOS4210_UART1_FIFO_SIZE 64
#define EXYNOS4210_UART2_FIFO_SIZE 16
#define EXYNOS4210_UART3_FIFO_SIZE 16
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define EXYNOS4210_UART_INTG 26
+
+/* External GIC */
+#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000
+#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000
+/* Combiner */
+#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
+#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
static struct arm_boot_info exynos4210_binfo = {
.loader_start = EXYNOS4210_BASE_BOOT_ADDR,
@@ -93,6 +104,87 @@ enum exynos4210_mach_id {
MACH_SMDKC210_ID = 0xB16,
};
+static void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs,
+ DeviceState *dev,
+ int ext)
+{
+ int n;
+ int bit;
+ int max;
+ qemu_irq *irq;
+
+ max = ext ? EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ :
+ EXYNOS4210_MAX_INT_COMBINER_IN_IRQ;
+ irq = ext ? irqs->ext_combiner_irq : irqs->int_combiner_irq;
+
+ /*
+ * Some IRQs of Int/External Combiner are going to two Combiners groups,
+ * so let split them.
+ */
+ for (n = 0; n < max; n++) {
+
+ bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+
+ switch (n) {
+ /* MDNIE_LCD1 INTG1*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 0) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 3):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+ continue;
+ break;
+
+ /* TMU INTG3*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(3, 4):
+ irq[n] =
+ qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(2, bit)]);
+ continue;
+ break;
+
+ /* LCD1 INTG12*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 0) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 3):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG12*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG35*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(35, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG51*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(51, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG53*/
+ case EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 4) ...
+ EXYNOS4210_COMBINER_GET_IRQ_NUM(53, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[EXYNOS4210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+ }
+
+ irq[n] = qdev_get_gpio_in(dev, n);
+ }
+}
static void exynos4210_init(ram_addr_t ram_size,
const char *boot_device,
@@ -110,8 +202,12 @@ static void exynos4210_init(ram_addr_t ram_size,
MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
MemoryRegion *dram1_mem = NULL;
+ Exynos4210Irq *irqs;
+ qemu_irq *irq_table;
qemu_irq *irqp;
qemu_irq cpu_irq[4];
+ DeviceState *dev;
+ SysBusDevice *busdev;
ram_addr_t mem_size;
int n;
@@ -129,6 +225,12 @@ static void exynos4210_init(ram_addr_t ram_size,
cpu_model = "cortex-a9";
}
+ irqs = g_malloc0(sizeof(Exynos4210Irq));
+ if (!irqs) {
+ fprintf(stderr, "Can't allocate IRQs\n");
+ exit(1);
+ }
+
for (n = 0; n < smp_cpus; n++) {
env = cpu_init(cpu_model);
if (!env) {
@@ -145,6 +247,63 @@ static void exynos4210_init(ram_addr_t ram_size,
cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
}
+ irq_table = exynos4210_init_irq(irqs);
+
+ /*** SMP ***/
+
+ /* Private memory region and Internal GIC */
+ dev = qdev_create(NULL, "a9mpcore_priv");
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ }
+ for (n = 0; n < EXYNOS4210_INT_GIC_NIRQ; n++) {
+ irqs->int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* External GIC */
+ dev = qdev_create(NULL, "exynos4210.gic");
+ qdev_prop_set_uint32(dev, "num-cpu", smp_cpus);
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ /* Map CPU interface */
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR);
+ /* Map Distributer interface */
+ sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ }
+ for (n = 0; n < EXYNOS4210_EXT_GIC_NIRQ; n++) {
+ irqs->ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* Internal Interrupt Combiner */
+ dev = qdev_create(NULL, "exynos4210.combiner");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, irqs->int_gic_irq[n]);
+ }
+ exynos4210_combiner_get_gpioin(irqs, dev, 0);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR);
+
+ /* External Interrupt Combiner */
+ dev = qdev_create(NULL, "exynos4210.combiner");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, irqs->ext_gic_irq[n]);
+ }
+ exynos4210_combiner_get_gpioin(irqs, dev, 1);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR);
+ qdev_prop_set_uint32(dev, "external", 1);
+
+ /* Initialize board IRQs. */
+ exynos4210_init_board_irqs(irqs);
+
/*** Memory ***/
/* Chip-ID and OMR */
@@ -224,7 +383,7 @@ static void exynos4210_init(ram_addr_t ram_size,
continue;
}
- uart_irq = NULL;
+ uart_irq = irq_table[exynos4210_get_irq(EXYNOS4210_UART_INTG, channel)];
exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
diff --git a/hw/exynos4210.h b/hw/exynos4210.h
index 3df7322..83c4e3c 100644
--- a/hw/exynos4210.h
+++ b/hw/exynos4210.h
@@ -32,6 +32,47 @@
#define EXYNOS4210_MAX_CPUS 2
/*
+ * exynos4210 IRQ subsystem stub definitions.
+ */
+
+#define EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ 64
+#define EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ 16
+#define EXYNOS4210_MAX_INT_COMBINER_IN_IRQ \
+ (EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ * 8)
+#define EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ \
+ (EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ * 8)
+
+#define EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit) ((grp)*8 + (bit))
+#define EXYNOS4210_COMBINER_GET_GRP_NUM(irq) ((irq) / 8)
+#define EXYNOS4210_COMBINER_GET_BIT_NUM(irq) \
+ ((irq) - 8 * EXYNOS4210_COMBINER_GET_GRP_NUM(irq))
+
+/* IRQs number for external and internal GIC */
+#define EXYNOS4210_EXT_GIC_NIRQ (160-32)
+#define EXYNOS4210_INT_GIC_NIRQ 64
+
+typedef struct Exynos4210Irq {
+ qemu_irq int_combiner_irq[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+ qemu_irq ext_combiner_irq[EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ];
+ qemu_irq int_gic_irq[EXYNOS4210_INT_GIC_NIRQ];
+ qemu_irq ext_gic_irq[EXYNOS4210_EXT_GIC_NIRQ];
+ qemu_irq board_irqs[EXYNOS4210_MAX_INT_COMBINER_IN_IRQ];
+} Exynos4210Irq;
+
+/* Initialize exynos4210 IRQ subsystem stub */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *env);
+
+/* Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs */
+void exynos4210_init_board_irqs(Exynos4210Irq *s);
+
+/* Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ * grp - group number
+ * bit - bit number inside group */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit);
+
+/*
* Interface for exynos4210 Clock Management Units (CMUs)
*/
diff --git a/hw/exynos4210_combiner.c b/hw/exynos4210_combiner.c
new file mode 100644
index 0000000..6a080d0
--- /dev/null
+++ b/hw/exynos4210_combiner.c
@@ -0,0 +1,385 @@
+/*
+ * Samsung exynos4210 Interrupt Combiner
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * 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 "exynos4210.h"
+
+//#define DEBUG_COMBINER
+
+#ifdef DEBUG_COMBINER
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "COMBINER: [%s:%d] " fmt, __func__ , __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define IIC_NGRP 64 /* Internal Interrupt Combiner
+ Groups number */
+#define IIC_NIRQ (IIC_NGRP*8) /* Internal Interrupt Combiner
+ Interrupts number */
+#define IIC_REGION_SIZE 0x108 /* Size of memory mapped region */
+#define IIC_REGSET_SIZE 0x41
+
+/*
+ * Combiner registers
+ */
+struct CombinerReg {
+ uint32_t iiesr; /* Internal Interrupt Enable Set register */
+ uint32_t iiecr; /* Internal Interrupt Enable Clear register */
+ uint32_t iistr; /* Internal Interrupt Status register.
+ * Shows status of interrupt pending BEFORE masking
+ */
+ uint32_t iimsr; /* Internal Interrupt Mask Status register.
+ * Shows status of interrupt pending AFTER masking
+ */
+};
+
+/*
+ * State for each output signal of internal combiner
+ */
+typedef struct CombinerGroupState {
+ uint8_t src_mask; /* 1 - source enabled, 0 - disabled */
+ uint8_t src_pending; /* Pending source interrupts before masking */
+} CombinerGroupState;
+
+typedef struct Exynos4210CombinerState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ struct CombinerGroupState group[IIC_NGRP];
+ uint32_t reg_set[IIC_REGSET_SIZE];
+ uint32_t icipsr[2];
+ uint32_t external; /* 1 means that this combiner is external */
+
+ qemu_irq output_irq[IIC_NGRP];
+} Exynos4210CombinerState;
+
+static const VMStateDescription VMState_Exynos4210CombinerGroupState = {
+ .name = "exynos4210.combiner.groupstate",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(src_mask, CombinerGroupState),
+ VMSTATE_UINT8(src_pending, CombinerGroupState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_Exynos4210Combiner = {
+ .name = "exynos4210.combiner",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(group, Exynos4210CombinerState, IIC_NGRP, 0,
+ VMState_Exynos4210CombinerGroupState, CombinerGroupState),
+ VMSTATE_UINT32_ARRAY(reg_set, Exynos4210CombinerState,
+ IIC_REGSET_SIZE),
+ VMSTATE_UINT32_ARRAY(icipsr, Exynos4210CombinerState, 2),
+ VMSTATE_UINT32(external, Exynos4210CombinerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t
+exynos4210_combiner_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
+ get a start of corresponding group quad */
+ uint32_t grp_quad_base_n; /* Base of group quad */
+ uint32_t reg_n; /* Register number inside the quad */
+ uint32_t val;
+
+ if (s->external && (offset > 0x3c && offset != 0x100)) {
+ hw_error("exynos4210.combiner: unallowed read access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ }
+
+ req_quad_base_n = offset >> 4;
+ grp_quad_base_n = req_quad_base_n << 2;
+ reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+ if (req_quad_base_n >= IIC_NGRP) {
+ /* Read of ICIPSR register */
+ return s->icipsr[reg_n];
+ }
+
+ val = 0;
+
+ switch (reg_n) {
+ /* IISTR */
+ case 2:
+ val |= s->group[grp_quad_base_n].src_pending;
+ val |= s->group[grp_quad_base_n+1].src_pending << 8;
+ val |= s->group[grp_quad_base_n+2].src_pending << 16;
+ val |= s->group[grp_quad_base_n+3].src_pending << 24;
+ break;
+ /* IIMSR */
+ case 3:
+ val |= s->group[grp_quad_base_n].src_mask &
+ s->group[grp_quad_base_n].src_pending;
+ val |= (s->group[grp_quad_base_n+1].src_mask &
+ s->group[grp_quad_base_n+1].src_pending) << 8;
+ val |= (s->group[grp_quad_base_n+2].src_mask &
+ s->group[grp_quad_base_n+2].src_pending) << 16;
+ val |= (s->group[grp_quad_base_n+3].src_mask &
+ s->group[grp_quad_base_n+3].src_pending) << 24;
+ break;
+ default:
+ if (offset >> 2 >= IIC_REGSET_SIZE) {
+ hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+ TARGET_FMT_plx "offset\n", offset);
+ }
+ val = s->reg_set[offset >> 2];
+ return 0;
+ }
+ return val;
+}
+
+static void exynos4210_combiner_update(void *opaque, uint8_t group_n)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+
+ /* Send interrupt if needed */
+ if (s->group[group_n].src_mask & s->group[group_n].src_pending) {
+#ifdef DEBUG_COMBINER
+ if (group_n != 26) {
+ /* skip uart */
+ DPRINTF("%s raise IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+ }
+#endif
+
+ /* Set Combiner interrupt pending status after masking */
+ if (group_n >= 32) {
+ s->icipsr[1] |= 1 << (group_n-32);
+ } else {
+ s->icipsr[0] |= 1 << group_n;
+ }
+
+ qemu_irq_raise(s->output_irq[group_n]);
+ } else {
+#ifdef DEBUG_COMBINER
+ if (group_n != 26) {
+ /* skip uart */
+ DPRINTF("%s lower IRQ[%d]\n", s->external ? "EXT" : "INT", group_n);
+ }
+#endif
+
+ /* Set Combiner interrupt pending status after masking */
+ if (group_n >= 32) {
+ s->icipsr[1] &= ~(1 << (group_n-32));
+ } else {
+ s->icipsr[0] &= ~(1 << group_n);
+ }
+
+ qemu_irq_lower(s->output_irq[group_n]);
+ }
+}
+
+static void exynos4210_combiner_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint32_t req_quad_base_n; /* Base of registers quad. Multiply it by 4 and
+ get a start of corresponding group quad */
+ uint32_t grp_quad_base_n; /* Base of group quad */
+ uint32_t reg_n; /* Register number inside the quad */
+
+ if (s->external && (offset > 0x3c && offset != 0x100)) {
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ }
+
+ req_quad_base_n = offset >> 4;
+ grp_quad_base_n = req_quad_base_n << 2;
+ reg_n = (offset - (req_quad_base_n << 4)) >> 2;
+
+ if (req_quad_base_n >= IIC_NGRP) {
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (reg_n > 1) {
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (offset >> 2 >= IIC_REGSET_SIZE) {
+ hw_error("exynos4210.combiner: overflow of reg_set by 0x"
+ TARGET_FMT_plx "offset\n", offset);
+ }
+ s->reg_set[offset >> 2] = val;
+
+ switch (reg_n) {
+ /* IIESR */
+ case 0:
+ /* FIXME: what if irq is pending, allowed by mask, and we allow it
+ * again. Interrupt will rise again! */
+
+ DPRINTF("%s enable IRQ for groups %d, %d, %d, %d\n",
+ s->external ? "EXT" : "INT", grp_quad_base_n, grp_quad_base_n+1,
+ grp_quad_base_n+2, grp_quad_base_n+3);
+ /* Enable interrupt sources */
+ s->group[grp_quad_base_n].src_mask |= val&0xFF;
+ s->group[grp_quad_base_n+1].src_mask |= (val&0xFF00)>>8;
+ s->group[grp_quad_base_n+2].src_mask |= (val&0xFF0000)>>16;
+ s->group[grp_quad_base_n+3].src_mask |= (val&0xFF000000)>>24;
+
+ exynos4210_combiner_update(s, grp_quad_base_n);
+ exynos4210_combiner_update(s, grp_quad_base_n+1);
+ exynos4210_combiner_update(s, grp_quad_base_n+2);
+ exynos4210_combiner_update(s, grp_quad_base_n+3);
+ break;
+ /* IIECR */
+ case 1:
+ DPRINTF("%s disable IRQ for groups %d, %d, %d, %d\n",
+ s->external ? "EXT" : "INT", grp_quad_base_n, grp_quad_base_n+1,
+ grp_quad_base_n+2, grp_quad_base_n+3);
+ /* Disable interrupt sources */
+ s->group[grp_quad_base_n].src_mask &= ~(val&0xFF);
+ s->group[grp_quad_base_n+1].src_mask &= ~((val&0xFF00)>>8);
+ s->group[grp_quad_base_n+2].src_mask &= ~((val&0xFF0000)>>16);
+ s->group[grp_quad_base_n+3].src_mask &= ~((val&0xFF000000)>>24);
+
+ exynos4210_combiner_update(s, grp_quad_base_n);
+ exynos4210_combiner_update(s, grp_quad_base_n+1);
+ exynos4210_combiner_update(s, grp_quad_base_n+2);
+ exynos4210_combiner_update(s, grp_quad_base_n+3);
+ break;
+ default:
+ hw_error("exynos4210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+
+ return;
+}
+
+/* Get combiner group and bit from irq number */
+static uint8_t get_combiner_group_and_bit(int irq, uint8_t *bit)
+{
+ *bit = irq - ((irq >> 3)<<3);
+ return irq >> 3;
+}
+
+/* Process a change in an external IRQ input. */
+static void exynos4210_combiner_handler(void *opaque, int irq, int level)
+{
+ struct Exynos4210CombinerState *s =
+ (struct Exynos4210CombinerState *)opaque;
+ uint8_t bit_n, group_n;
+
+ group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+ if (s->external && group_n >= EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ) {
+ DPRINTF("%s unallowed IRQ group 0x%x\n", s->external ? "EXT" : "INT"
+ , group_n);
+ return;
+ }
+
+ if (level) {
+ s->group[group_n].src_pending |= 1 << bit_n;
+ } else {
+ s->group[group_n].src_pending &= ~(1 << bit_n);
+ }
+
+ exynos4210_combiner_update(s, group_n);
+
+ return;
+}
+
+static void exynos4210_combiner_reset(DeviceState *d)
+{
+ struct Exynos4210CombinerState *s = (struct Exynos4210CombinerState *)d;
+
+ memset(&s->group, 0, sizeof(s->group));
+ memset(&s->reg_set, 0, sizeof(s->reg_set));
+
+ s->reg_set[0xC0 >> 2] = 0x01010101;
+ s->reg_set[0xC4 >> 2] = 0x01010101;
+ s->reg_set[0xD0 >> 2] = 0x01010101;
+ s->reg_set[0xD4 >> 2] = 0x01010101;
+}
+
+static const MemoryRegionOps exynos4210_combiner_ops = {
+ .read = exynos4210_combiner_read,
+ .write = exynos4210_combiner_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int exynos4210_combiner_init(SysBusDevice *dev)
+{
+ unsigned int i;
+ struct Exynos4210CombinerState *s =
+ FROM_SYSBUS(struct Exynos4210CombinerState, dev);
+
+ /* Allocate general purpose input signals and connect a handler to each of
+ * them */
+ qdev_init_gpio_in(&s->busdev.qdev, exynos4210_combiner_handler, IIC_NIRQ);
+
+ /* Connect SysBusDev irqs to device specific irqs */
+ for (i = 0; i < IIC_NIRQ; i++) {
+ sysbus_init_irq(dev, &s->output_irq[i]);
+ }
+
+ memory_region_init_io(&s->iomem, &exynos4210_combiner_ops, s,
+ "exynos4210-combiner", IIC_REGION_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ exynos4210_combiner_reset((DeviceState *)s);
+ return 0;
+}
+
+static SysBusDeviceInfo exynos4210_combiner_info = {
+ .qdev.name = "exynos4210.combiner",
+ .qdev.size = sizeof(struct Exynos4210CombinerState),
+ .qdev.reset = exynos4210_combiner_reset,
+ .qdev.vmsd = &VMState_Exynos4210Combiner,
+ .init = exynos4210_combiner_init,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT32("external",
+ struct Exynos4210CombinerState,
+ external,
+ 0),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void exynos4210_combiner_register_devices(void)
+{
+ sysbus_register_withprop(&exynos4210_combiner_info);
+}
+
+device_init(exynos4210_combiner_register_devices)
diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c
new file mode 100644
index 0000000..56af00d
--- /dev/null
+++ b/hw/exynos4210_gic.c
@@ -0,0 +1,415 @@
+/*
+ * Samsung exynos4210 GIC implementation. Based on hw/arm_gic.c
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * 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-common.h"
+#include "irq.h"
+#include "exynos4210.h"
+
+//#define DEBUG_EXYNOS4210_IRQ
+//#define DEBUG_EXYNOS4210_GIC
+
+#ifdef DEBUG_EXYNOS4210_IRQ
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \
+ do { fprintf(stdout, "EXYNOS4210_IRQ: [%24s:%5d] " fmt, __func__, \
+ __LINE__, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_EXYNOS4210_GIC
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) \
+ do { fprintf(stdout, "EXT_GIC: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_EXYNOS4210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#define EXT_GIC_ID_TVENC 127
+#define EXT_GIC_ID_MFC 126
+#define EXT_GIC_ID_HDMI_I2C 125
+#define EXT_GIC_ID_HDMI 124
+#define EXT_GIC_ID_MIXER 123
+#define EXT_GIC_ID_PCIe 122
+#define EXT_GIC_ID_2D 121
+#define EXT_GIC_ID_JPEG 120
+#define EXT_GIC_ID_FIMC3 119
+#define EXT_GIC_ID_FIMC2 118
+#define EXT_GIC_ID_FIMC1 117
+#define EXT_GIC_ID_FIMC0 116
+#define EXT_GIC_ID_ROTATOR 115
+#define EXT_GIC_ID_ONENAND_AUDI 114
+#define EXT_GIC_ID_MIPI_DSI_2LANE 113
+#define EXT_GIC_ID_MIPI_CSI_2LANE 112
+#define EXT_GIC_ID_MIPI_DSI_4LANE 111
+#define EXT_GIC_ID_MIPI_CSI_4LANE 110
+#define EXT_GIC_ID_SDMMC 109
+#define EXT_GIC_ID_HSMMC3 108
+#define EXT_GIC_ID_HSMMC2 107
+#define EXT_GIC_ID_HSMMC1 106
+#define EXT_GIC_ID_HSMMC0 105
+#define EXT_GIC_ID_MODEMIF 104
+#define EXT_GIC_ID_USB_DEVICE 103
+#define EXT_GIC_ID_USB_HOST 102
+#define EXT_GIC_ID_MCT_G1 101
+#define EXT_GIC_ID_SPI2 100
+#define EXT_GIC_ID_SPI1 99
+#define EXT_GIC_ID_SPI0 98
+#define EXT_GIC_ID_I2C7 97
+#define EXT_GIC_ID_I2C6 96
+#define EXT_GIC_ID_I2C5 95
+#define EXT_GIC_ID_I2C4 94
+#define EXT_GIC_ID_I2C3 93
+#define EXT_GIC_ID_I2C2 92
+#define EXT_GIC_ID_I2C1 91
+#define EXT_GIC_ID_I2C0 90
+#define EXT_GIC_ID_MCT_G0 89
+#define EXT_GIC_ID_UART4 88
+#define EXT_GIC_ID_UART3 87
+#define EXT_GIC_ID_UART2 86
+#define EXT_GIC_ID_UART1 85
+#define EXT_GIC_ID_UART0 84
+#define EXT_GIC_ID_NFC 83
+#define EXT_GIC_ID_IEM_IEC 82
+#define EXT_GIC_ID_IEM_APC 81
+#define EXT_GIC_ID_MCT_L1 80
+#define EXT_GIC_ID_GPIO_XA 79
+#define EXT_GIC_ID_GPIO_XB 78
+#define EXT_GIC_ID_RTC_TIC 77
+#define EXT_GIC_ID_RTC_ALARM 76
+#define EXT_GIC_ID_WDT 75
+#define EXT_GIC_ID_MCT_L0 74
+#define EXT_GIC_ID_TIMER4 73
+#define EXT_GIC_ID_TIMER3 72
+#define EXT_GIC_ID_TIMER2 71
+#define EXT_GIC_ID_TIMER1 70
+#define EXT_GIC_ID_TIMER0 69
+#define EXT_GIC_ID_PDMA1 68
+#define EXT_GIC_ID_PDMA0 67
+#define EXT_GIC_ID_MDMA_LCD0 66
+
+enum ext_int {
+ EXT_GIC_ID_EXTINT0 = 48,
+ EXT_GIC_ID_EXTINT1,
+ EXT_GIC_ID_EXTINT2,
+ EXT_GIC_ID_EXTINT3,
+ EXT_GIC_ID_EXTINT4,
+ EXT_GIC_ID_EXTINT5,
+ EXT_GIC_ID_EXTINT6,
+ EXT_GIC_ID_EXTINT7,
+ EXT_GIC_ID_EXTINT8,
+ EXT_GIC_ID_EXTINT9,
+ EXT_GIC_ID_EXTINT10,
+ EXT_GIC_ID_EXTINT11,
+ EXT_GIC_ID_EXTINT12,
+ EXT_GIC_ID_EXTINT13,
+ EXT_GIC_ID_EXTINT14,
+ EXT_GIC_ID_EXTINT15
+};
+
+/*
+ * External GIC sources which are not from External Interrupt Combiner or
+ * External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
+ /* int combiner groups 16-19 */
+ {}, {}, {}, {},
+ /* int combiner group 20 */
+ {0, EXT_GIC_ID_MDMA_LCD0},
+ /* int combiner group 21 */
+ {EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1},
+ /* int combiner group 22 */
+ {EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
+ EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4},
+ /* int combiner group 23 */
+ {EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC},
+ /* int combiner group 24 */
+ {EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA},
+ /* int combiner group 25 */
+ {EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC},
+ /* int combiner group 26 */
+ {EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
+ EXT_GIC_ID_UART4},
+ /* int combiner group 27 */
+ {EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
+ EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
+ EXT_GIC_ID_I2C7},
+ /* int combiner group 28 */
+ {EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2},
+ /* int combiner group 29 */
+ {EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
+ EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC},
+ /* int combiner group 30 */
+ {EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE},
+ /* int combiner group 31 */
+ {EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE},
+ /* int combiner group 32 */
+ {EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1},
+ /* int combiner group 33 */
+ {EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3},
+ /* int combiner group 34 */
+ {EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC},
+ /* int combiner group 35 */
+ {0, 0, 0, EXT_GIC_ID_MCT_L1, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+ /* int combiner group 36 */
+ {EXT_GIC_ID_MIXER},
+ /* int combiner group 37 */
+ {EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
+ EXT_GIC_ID_EXTINT7},
+ /* groups 38-50 */
+ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {},
+ /* int combiner group 51 */
+ {EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+ /* group 52 */
+ {},
+ /* int combiner group 53 */
+ {EXT_GIC_ID_WDT, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1},
+ /* groups 54-63 */
+ {}, {}, {}, {}, {}, {}, {}, {}, {}, {}
+};
+
+#define GIC_NIRQ 160
+#define NCPU EXYNOS4210_MAX_CPUS
+
+#define EXYNOS4210_GIC_CPU_REGION_SIZE 0x8050
+#define EXYNOS4210_GIC_DIST_REGION_SIZE 0x8F04
+
+static void exynos4210_irq_handler(void *opaque, int irq, int level)
+{
+ Exynos4210Irq *s = (Exynos4210Irq *)opaque;
+
+ /* Bypass */
+ qemu_set_irq(s->board_irqs[irq], level);
+
+ return;
+}
+
+/*
+ * Initialize exynos4210 IRQ subsystem stub.
+ */
+qemu_irq *exynos4210_init_irq(Exynos4210Irq *s)
+{
+ return qemu_allocate_irqs(exynos4210_irq_handler, s,
+ EXYNOS4210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void exynos4210_init_board_irqs(Exynos4210Irq *s)
+{
+ uint32_t grp, bit, irq_id, n;
+
+ for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_combiner_irq[n]);
+
+ irq_id = 0;
+ if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4) ||
+ n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 4)) {
+ /* MCT_G0 is passed to External GIC */
+ irq_id = EXT_GIC_ID_MCT_G0;
+ }
+ if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5) ||
+ n == EXYNOS4210_COMBINER_GET_IRQ_NUM(12, 5)) {
+ /* MCT_G1 is passed to External and GIC */
+ irq_id = EXT_GIC_ID_MCT_G1;
+ }
+ if (irq_id) {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_gic_irq[irq_id-32]);
+ }
+
+ }
+ for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
+ /* these IDs are passed to Internal Combiner and External GIC */
+ grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
+ bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
+ irq_id =
+ combiner_grp_to_gic_id[grp -
+ EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
+
+ if (irq_id) {
+ s->board_irqs[n] = qemu_irq_split(s->int_combiner_irq[n],
+ s->ext_gic_irq[irq_id-32]);
+ }
+ }
+}
+
+/*
+ * Get IRQ number from exynos4210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ * grp - group number
+ * bit - bit number inside group
+ */
+uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
+{
+ return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
+}
+
+/********* GIC part *********/
+
+static inline int
+gic_get_current_cpu(void)
+{
+ return cpu_single_env->cpu_index;
+}
+
+#include "arm_gic.c"
+
+typedef struct {
+ gic_state gic;
+ MemoryRegion cpu_container;
+ MemoryRegion dist_container;
+ uint32_t num_cpu;
+} Exynos4210GicState;
+
+static uint64_t exynos4210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("CPU%d: read offset 0x%x\n",
+ gic_get_current_cpu(), offset);
+ return gic_cpu_read(&s->gic, gic_get_current_cpu(), offset & ~0x8000);
+}
+
+static void exynos4210_gic_cpu_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("CPU%d: write offset 0x%x, value 0x%x\n",
+ gic_get_current_cpu(), offset, value);
+ gic_cpu_write(&s->gic, gic_get_current_cpu(), offset & ~0x8000, value);
+}
+
+static uint32_t
+exynos4210_gic_dist_readb(void *opaque, target_phys_addr_t offset)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: readb offset 0x%x\n", offset);
+ return gic_dist_readb(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readw(void *opaque, target_phys_addr_t offset)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: readw offset 0x%x\n", offset);
+ return gic_dist_readw(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t
+exynos4210_gic_dist_readl(void *opaque, target_phys_addr_t offset)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: readl offset 0x%x\n", offset);
+ return gic_dist_readl(&s->gic, offset & ~0x8000);
+}
+
+static void
+exynos4210_gic_dist_writeb(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writeb(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writew(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writew(&s->gic, offset & ~0x8000, value);
+}
+
+static void exynos4210_gic_dist_writel(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ Exynos4210GicState *s = (Exynos4210GicState *) opaque;
+ DPRINTF_EXYNOS4210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writel(&s->gic, offset & ~0x8000, value);
+}
+
+static const MemoryRegionOps exynos4210_gic_cpu_ops = {
+ .read = exynos4210_gic_cpu_read,
+ .write = exynos4210_gic_cpu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps exynos4210_gic_dist_ops = {
+ .old_mmio = {
+ .read = { exynos4210_gic_dist_readb,
+ exynos4210_gic_dist_readw,
+ exynos4210_gic_dist_readl, },
+ .write = { exynos4210_gic_dist_writeb,
+ exynos4210_gic_dist_writew,
+ exynos4210_gic_dist_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_gic_init(SysBusDevice *dev)
+{
+ Exynos4210GicState *s = FROM_SYSBUSGIC(Exynos4210GicState, dev);
+ gic_init(&s->gic, s->num_cpu);
+
+ memory_region_init(&s->cpu_container, "exynos4210-gic-cpu_container",
+ EXYNOS4210_GIC_CPU_REGION_SIZE);
+ memory_region_init(&s->cpu_container, "exynos4210-gic-dist_container",
+ EXYNOS4210_GIC_DIST_REGION_SIZE);
+ memory_region_init_io(&s->cpu_container, &exynos4210_gic_cpu_ops, &s->gic,
+ "exynos4210-gic-cpu", EXYNOS4210_GIC_CPU_REGION_SIZE);
+ memory_region_init_io(&s->dist_container, &exynos4210_gic_dist_ops, &s->gic,
+ "exynos4210-gic-dist", EXYNOS4210_GIC_DIST_REGION_SIZE);
+
+ sysbus_init_mmio(dev, &s->cpu_container);
+ sysbus_init_mmio(dev, &s->dist_container);
+
+ gic_cpu_write(&s->gic, 1, 0, 1);
+ return 0;
+}
+
+static SysBusDeviceInfo exynos4210_gic_info = {
+ .init = exynos4210_gic_init,
+ .qdev.name = "exynos4210.gic",
+ .qdev.size = sizeof(Exynos4210GicState),
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT32("num-cpu", Exynos4210GicState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void exynos4210_gic_register_devices(void)
+{
+ sysbus_register_withprop(&exynos4210_gic_info);
+}
+
+device_init(exynos4210_gic_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (4 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
` (8 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
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 a936dcf..361cdfb 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
@@ -350,6 +353,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 <e.voevodin@samsung.com>
+ *
+ * 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
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (5 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
` (7 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Secondary CPU bootloader enables interrupt and issues wfi until start address
is written to system controller. The position where to find this start
address is hardcoded to 0x10000030. This commit adds new bootloader for
secondary CPU which allows a target board to cpecify a position where
to find start address. If target board doesn't specify start address then
default 0x10000030 is used
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/arm-misc.h | 1 +
hw/arm_boot.c | 22 +++++++++++++++-------
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index af403a1..6e8ae6b 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -31,6 +31,7 @@ struct arm_boot_info {
const char *initrd_filename;
target_phys_addr_t loader_start;
target_phys_addr_t smp_loader_start;
+ target_phys_addr_t smp_bootreg_addr;
target_phys_addr_t smp_priv_base;
int nb_cpus;
int board_id;
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index 215d5de..ecaac22 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -31,17 +31,17 @@ static uint32_t bootloader[] = {
/* Entry point for secondary CPUs. Enable interrupt controller and
Issue WFI until start address is written to system controller. */
static uint32_t smpboot[] = {
- 0xe59f0020, /* ldr r0, privbase */
- 0xe3a01001, /* mov r1, #1 */
- 0xe5801100, /* str r1, [r0, #0x100] */
- 0xe3a00201, /* mov r0, #0x10000000 */
- 0xe3800030, /* orr r0, #0x30 */
+ 0xe59f201c, /* ldr r2, privbase */
+ 0xe59f001c, /* ldr r0, startaddr */
+ 0xe3a01001, /* mov r1, #1 */
+ 0xe5821100, /* str r1, [r2, #256] */
0xe320f003, /* wfi */
0xe5901000, /* ldr r1, [r0] */
0xe1110001, /* tst r1, r1 */
0x0afffffb, /* beq <wfi> */
0xe12fff11, /* bx r1 */
- 0 /* privbase: Private memory region base address. */
+ 0, /* privbase: Private memory region base address. */
+ 0 /* bootreg: Boot register address is hold here */
};
#define WRITE_WORD(p, value) do { \
@@ -179,6 +179,7 @@ static void do_cpu_reset(void *opaque)
{
CPUState *env = opaque;
const struct arm_boot_info *info = env->boot_info;
+ uint8_t smp_bootreg_addr[4] = {0};
cpu_reset(env);
if (info) {
@@ -197,6 +198,8 @@ static void do_cpu_reset(void *opaque)
info->loader_start);
}
} else {
+ cpu_physical_memory_rw(info->smp_bootreg_addr, smp_bootreg_addr,
+ sizeof(smp_bootreg_addr), 1);
env->regs[15] = info->smp_loader_start;
}
}
@@ -262,6 +265,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
} else {
initrd_size = 0;
}
+
bootloader[1] |= info->board_id & 0xff;
bootloader[2] |= (info->board_id >> 8) & 0xff;
bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
@@ -272,7 +276,11 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
rom_add_blob_fixed("bootloader", bootloader, sizeof(bootloader),
info->loader_start);
if (info->nb_cpus > 1) {
- smpboot[10] = info->smp_priv_base;
+ if (!info->smp_bootreg_addr) {
+ info->smp_bootreg_addr = 0x10000030;
+ }
+ smpboot[(sizeof(smpboot) - 8)/4] = info->smp_priv_base;
+ smpboot[(sizeof(smpboot) - 4)/4] = info->smp_bootreg_addr;
for (n = 0; n < sizeof(smpboot) / 4; n++) {
smpboot[n] = tswap32(smpboot[n]);
}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (6 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 15:55 ` Paul Brook
2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
` (6 subsequent siblings)
14 siblings, 1 reply; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
In previous version IRQ was lowered every time if enable bits were
not set. If platform has splitted IRQ source to pass IRQ to two
identical GICs simultaneously in first of which IRQ passing is
enabled but in second is disabled, handling IRQ by second GIC would
lower IRQ previously raised by first GIC.
Linux kernel v3.0 faces this problem.
The problem is avoided if IRQ is only lowered as result of
transitioning enable bits to zeroes.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/arm_gic.c | 20 +++++++++++++++++++-
1 files changed, 19 insertions(+), 1 deletions(-)
diff --git a/hw/arm_gic.c b/hw/arm_gic.c
index 527c9ce..7e3db4f 100644
--- a/hw/arm_gic.c
+++ b/hw/arm_gic.c
@@ -84,7 +84,9 @@ typedef struct gic_state
SysBusDevice busdev;
qemu_irq parent_irq[NCPU];
int enabled;
+ int enabled_prev;
int cpu_enabled[NCPU];
+ int cpu_enabled_prev[NCPU];
gic_irq_state irq_state[GIC_NIRQ];
#ifndef NVIC
@@ -116,12 +118,22 @@ static void gic_update(gic_state *s)
int level;
int cpu;
int cm;
+ int enabled_prev;
+ int cpu_enabled_prev;
+ enabled_prev = s->enabled_prev;
+ s->enabled_prev = s->enabled;
for (cpu = 0; cpu < NUM_CPU(s); cpu++) {
+ cpu_enabled_prev = s->cpu_enabled_prev[cpu];
+ s->cpu_enabled_prev[cpu] = s->cpu_enabled[cpu];
cm = 1 << cpu;
s->current_pending[cpu] = 1023;
if (!s->enabled || !s->cpu_enabled[cpu]) {
- qemu_irq_lower(s->parent_irq[cpu]);
+ /* lower IRQ only if enable bit was changed */
+ if (enabled_prev != s->enabled
+ || cpu_enabled_prev != s->cpu_enabled[cpu]) {
+ qemu_irq_lower(s->parent_irq[cpu]);
+ }
return;
}
best_prio = 0x100;
@@ -650,6 +662,7 @@ static void gic_reset(gic_state *s)
#else
s->cpu_enabled[i] = 0;
#endif
+ s->cpu_enabled_prev[i] = s->cpu_enabled[i];
}
for (i = 0; i < 16; i++) {
GIC_SET_ENABLED(i, ALL_CPU_MASK);
@@ -661,6 +674,7 @@ static void gic_reset(gic_state *s)
#else
s->enabled = 0;
#endif
+ s->enabled_prev = s->enabled;
}
static void gic_save(QEMUFile *f, void *opaque)
@@ -669,8 +683,10 @@ static void gic_save(QEMUFile *f, void *opaque)
int i;
int j;
+ qemu_put_be32(f, s->enabled_prev);
qemu_put_be32(f, s->enabled);
for (i = 0; i < NUM_CPU(s); i++) {
+ qemu_put_be32(f, s->cpu_enabled_prev[i]);
qemu_put_be32(f, s->cpu_enabled[i]);
for (j = 0; j < 32; j++)
qemu_put_be32(f, s->priority1[j][i]);
@@ -706,8 +722,10 @@ static int gic_load(QEMUFile *f, void *opaque, int version_id)
if (version_id != 2)
return -EINVAL;
+ s->enabled_prev = qemu_get_be32(f);
s->enabled = qemu_get_be32(f);
for (i = 0; i < NUM_CPU(s); i++) {
+ s->cpu_enabled_prev[i] = qemu_get_be32(f);
s->cpu_enabled[i] = qemu_get_be32(f);
for (j = 0; j < 32; j++)
s->priority1[j][i] = qemu_get_be32(f);
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (7 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
` (5 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/exynos4210.c | 19 +
hw/exynos4210_mct.c | 1486 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1506 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_mct.c
diff --git a/Makefile.target b/Makefile.target
index 709e9e2..691582b 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_pwm.o
+ exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.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 361cdfb..f172346 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -68,6 +68,9 @@
/* PWM */
#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000
+/* MCT */
+#define EXYNOS4210_MCT_BASE_ADDR 0x10050000
+
/* UART's definitions */
#define EXYNOS4210_UART_BASE_ADDR 0x13800000
#define EXYNOS4210_UART_SHIFT 0x00010000
@@ -362,6 +365,22 @@ static void exynos4210_init(ram_addr_t ram_size,
irq_table[exynos4210_get_irq(22, 4)],
NULL);
+ /* Multi Core Timer */
+ dev = qdev_create(NULL, "exynos4210.mct");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ for (n = 0; n < 4; n++) {
+ /* Connect global timer interrupts to Combiner gpio_in */
+ sysbus_connect_irq(busdev, n,
+ irq_table[exynos4210_get_irq(1, 4 + n)]);
+ }
+ /* Connect local timer interrupts to Combiner gpio_in */
+ sysbus_connect_irq(busdev, 4,
+ irq_table[exynos4210_get_irq(51, 0)]);
+ sysbus_connect_irq(busdev, 5,
+ irq_table[exynos4210_get_irq(35, 3)]);
+ sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
+
/*** UARTs ***/
for (n = 0; n < EXYNOS4210_UARTS_NUMBER; n++) {
diff --git a/hw/exynos4210_mct.c b/hw/exynos4210_mct.c
new file mode 100644
index 0000000..d754a94
--- /dev/null
+++ b/hw/exynos4210_mct.c
@@ -0,0 +1,1486 @@
+/*
+ * Samsung exynos4210 Multi Core timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <e.voevodin@samsung.com>
+ *
+ * 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
+ */
+
+/*
+ * Global Timer:
+ *
+ * Consists of two timers. First represents Free Running Counter and second
+ * is used to measure interval from FRC to nearest comparator.
+ *
+ * 0 0xFFFF...
+ * | timer0 |
+ * | <-------------------------------------------------------------- |
+ * | --------------------------------------------frc---------------> |
+ * |______________________________________________|__________________|
+ * CMP0 CMP1 CMP2 | CMP3
+ * __| |_
+ * | timer1 |
+ * | -------------> |
+ * frc CMPx
+ *
+ * Problem: when implementing global timer as is, overflow arises.
+ * next_time = cur_time + period*count;
+ * period and count are 64 bits width and count == 0xFF..FF 64its.
+ * Lets arm timer for 0xFFFFFFFF count and update internal G_CNT register
+ * during each event.
+ *
+ * Problem: both timers need to be implemented using MCT_XT_COUNTER_STEP because
+ * local timer contains two counters: TCNT and ICNT. TCNT == 0 -> ICNT--.
+ * IRQ is generated when ICNT riches zero. If make timer for every TCNT == 0
+ * possible too frequently events (yes, if ICNT == 0 both, we got no luck).
+ * So, better to have one uint64_t counter equal to TCNT*ICNT and arm ptimer.c
+ * for a minimum(TCNT*ICNT, MCT_GT_COUNTER_STEP);
+ */
+
+#include "sysbus.h"
+#include "qemu-timer.h"
+#include "qemu-common.h"
+#include "osdep.h"
+
+#include "exynos4210.h"
+
+//#define DEBUG_MCT
+
+#ifdef DEBUG_MCT
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "MCT: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define MCT_CFG 0x000
+#define G_CNT_L 0x100
+#define G_CNT_U 0x104
+#define G_CNT_WSTAT 0x110
+#define G_COMP0_L 0x200
+#define G_COMP0_U 0x204
+#define G_COMP0_ADD_INCR 0x208
+#define G_COMP1_L 0x210
+#define G_COMP1_U 0x214
+#define G_COMP1_ADD_INCR 0x218
+#define G_COMP2_L 0x220
+#define G_COMP2_U 0x224
+#define G_COMP2_ADD_INCR 0x228
+#define G_COMP3_L 0x230
+#define G_COMP3_U 0x234
+#define G_COMP3_ADD_INCR 0x238
+#define G_TCON 0x240
+#define G_INT_CSTAT 0x244
+#define G_INT_ENB 0x248
+#define G_WSTAT 0x24C
+#define L0_TCNTB 0x300
+#define L0_TCNTO 0x304
+#define L0_ICNTB 0x308
+#define L0_ICNTO 0x30C
+#define L0_FRCNTB 0x310
+#define L0_FRCNTO 0x314
+#define L0_TCON 0x320
+#define L0_INT_CSTAT 0x330
+#define L0_INT_ENB 0x334
+#define L0_WSTAT 0x340
+#define L1_TCNTB 0x400
+#define L1_TCNTO 0x404
+#define L1_ICNTB 0x408
+#define L1_ICNTO 0x40C
+#define L1_FRCNTB 0x410
+#define L1_FRCNTO 0x414
+#define L1_TCON 0x420
+#define L1_INT_CSTAT 0x430
+#define L1_INT_ENB 0x434
+#define L1_WSTAT 0x440
+
+#define MCT_CFG_GET_PRESCALER(x) (x&0xFF)
+#define MCT_CFG_GET_DIVIDER(x) (1<<(x>>8 & 7))
+
+#define GET_G_COMP_IDX(offset) ((offset - G_COMP0_L)/0x10)
+#define GET_G_COMP_ADD_INCR_IDX(offset) ((offset - G_COMP0_ADD_INCR)/0x10)
+
+#define G_COMP_L(x) (G_COMP0_L+x*0x10)
+#define G_COMP_U(x) (G_COMP0_U+x*0x10)
+
+#define G_COMP_ADD_INCR(x) (G_COMP0_ADD_INCR+x*0x10)
+
+/* MCT bits */
+#define G_TCON_COMP_ENABLE(x) (1<<2*x)
+#define G_TCON_AUTO_ICREMENT(x) (1<<(2*x + 1))
+#define G_TCON_TIMER_ENABLE (1<<8)
+
+#define G_INT_ENABLE(x) (1<<x)
+#define G_INT_CSTAT_COMP(x) (1<<x)
+
+#define G_CNT_WSTAT_L 1
+#define G_CNT_WSTAT_U 2
+
+#define G_WSTAT_COMP_L(x) (1<<4*x)
+#define G_WSTAT_COMP_U(x) (1<<((4*x)+1))
+#define G_WSTAT_COMP_ADDINCR(x) (1<<((4*x)+2))
+#define G_WSTAT_TCON_WRITE (1<<16)
+
+#define GET_L_TIMER_IDX(offset) (((offset&0xF00) - L0_TCNTB)/0x100)
+#define GET_L_TIMER_CNT_REG_IDX(offset, lt_i) \
+ ((offset - (L0_TCNTB+0x100*lt_i))>>2)
+
+#define L_ICNTB_MANUAL_UPDATE (1<<31)
+
+#define L_TCON_TICK_START (1)
+#define L_TCON_INT_START (1<<1)
+#define L_TCON_INTERVAL_MODE (1<<2)
+#define L_TCON_FRC_START (1<<3)
+
+#define L_INT_CSTAT_INTCNT (1<<0)
+#define L_INT_CSTAT_FRCCNT (1<<1)
+
+#define L_INT_INTENB_ICNTEIE (1<<0)
+#define L_INT_INTENB_FRCEIE (1<<1)
+
+#define L_WSTAT_TCNTB_WRITE (1<<0)
+#define L_WSTAT_ICNTB_WRITE (1<<1)
+#define L_WSTAT_FRCCNTB_WRITE (1<<2)
+#define L_WSTAT_TCON_WRITE (1<<3)
+
+enum local_timer_reg_cnt_indexes {
+ L_REG_CNT_TCNTB,
+ L_REG_CNT_TCNTO,
+ L_REG_CNT_ICNTB,
+ L_REG_CNT_ICNTO,
+ L_REG_CNT_FRCCNTB,
+ L_REG_CNT_FRCCNTO,
+
+ L_REG_CNT_AMOUNT
+};
+
+#define MCT_NIRQ 6
+#define MCT_SFR_SIZE 0x444
+
+#define MCT_GT_CMP_NUM 4
+
+#define MCT_GT_MAX_VAL 0xFFFFFFFFFFFFFFFF
+
+#define MCT_GT_COUNTER_STEP 0x100000000
+#define MCT_LT_COUNTER_STEP 0x100000000
+#define MCT_LT_CNT_LOW_LIMIT 0x100
+
+/* global timer */
+typedef struct {
+ qemu_irq irq[MCT_GT_CMP_NUM];
+
+ struct gregs {
+ uint64_t cnt;
+ uint32_t cnt_wstat;
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ uint64_t comp[MCT_GT_CMP_NUM];
+ uint32_t comp_add_incr[MCT_GT_CMP_NUM];
+ } reg;
+
+ uint64_t count; /* Value FRC was armed with */
+ int32_t curr_comp; /* Current comparator FRC is running to */
+
+ ptimer_state *ptimer_frc; /* FRC timer */
+
+} exynos4210_mct_gt;
+
+/* local timer */
+typedef struct {
+ int id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+
+ struct tick_timer {
+ uint32_t cnt_run; /* cnt timer is running */
+ uint32_t int_run; /* int timer is running */
+
+ uint32_t last_icnto;
+ uint32_t last_tcnto;
+ uint32_t tcntb; /* initial value for TCNTB */
+ uint32_t icntb; /* initial value for ICNTB */
+
+ /* for step mode */
+ uint64_t distance; /* distance to count to the next event */
+ uint64_t progress; /* progress when counting by steps */
+ uint64_t count; /* count to arm timer with */
+
+ ptimer_state *ptimer_tick; /* timer for tick counter */
+ } tick_timer;
+
+ /* use ptimer.c to represent count down timer */
+
+ ptimer_state *ptimer_frc; /* timer for free running counter */
+
+ /* registers */
+ struct lregs {
+ uint32_t cnt[L_REG_CNT_AMOUNT];
+ uint32_t tcon;
+ uint32_t int_cstat;
+ uint32_t int_enb;
+ uint32_t wstat;
+ } reg;
+
+} exynos4210_mct_lt;
+
+
+
+typedef struct Exynos4210MCTState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ /* Registers */
+ uint32_t reg_mct_cfg;
+
+ exynos4210_mct_lt l_timer[2];
+ exynos4210_mct_gt g_timer;
+
+ uint32_t freq; /* all timers tick frequency, TCLK */
+} Exynos4210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_tick_timer = {
+ .name = "exynos4210.mct.tick_timer",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(cnt_run, struct tick_timer),
+ VMSTATE_UINT32(int_run, struct tick_timer),
+ VMSTATE_UINT32(last_icnto, struct tick_timer),
+ VMSTATE_UINT32(last_tcnto, struct tick_timer),
+ VMSTATE_UINT32(tcntb, struct tick_timer),
+ VMSTATE_UINT32(icntb, struct tick_timer),
+ VMSTATE_UINT64(distance, struct tick_timer),
+ VMSTATE_UINT64(progress, struct tick_timer),
+ VMSTATE_UINT64(count, struct tick_timer),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_lregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(cnt, struct lregs, L_REG_CNT_AMOUNT),
+ VMSTATE_UINT32(tcon, struct lregs),
+ VMSTATE_UINT32(int_cstat, struct lregs),
+ VMSTATE_UINT32(int_enb, struct lregs),
+ VMSTATE_UINT32(wstat, struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_Exynos4210_mct_lt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(id, exynos4210_mct_lt),
+ VMSTATE_STRUCT(tick_timer, exynos4210_mct_lt, 0,
+ VMState_tick_timer,
+ struct tick_timer),
+ VMSTATE_STRUCT(reg, exynos4210_mct_lt, 0,
+ VMState_lregs,
+ struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_gregs = {
+ .name = "exynos4210.mct.lregs",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT64(cnt, struct gregs),
+ VMSTATE_UINT32(cnt_wstat, struct gregs),
+ VMSTATE_UINT32(tcon, struct gregs),
+ VMSTATE_UINT32(int_cstat, struct gregs),
+ VMSTATE_UINT32(int_enb, struct gregs),
+ VMSTATE_UINT32(wstat, struct gregs),
+ VMSTATE_UINT64_ARRAY(comp, struct gregs, MCT_GT_CMP_NUM),
+ VMSTATE_UINT32_ARRAY(comp_add_incr, struct gregs,
+ MCT_GT_CMP_NUM),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_Exynos4210_mct_gt = {
+ .name = "exynos4210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(reg, exynos4210_mct_gt, 0, VMState_gregs,
+ struct gregs),
+ VMSTATE_UINT64(count, exynos4210_mct_gt),
+ VMSTATE_INT32(curr_comp, exynos4210_mct_gt),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_Exynos4210MCTState = {
+ .name = "exynos4210.mct",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_mct_cfg, Exynos4210MCTState),
+ VMSTATE_STRUCT_ARRAY(l_timer, Exynos4210MCTState, 2, 0,
+ VMState_Exynos4210_mct_lt, exynos4210_mct_lt),
+ VMSTATE_STRUCT(g_timer, Exynos4210MCTState, 0,
+ VMState_Exynos4210_mct_gt, exynos4210_mct_gt),
+ VMSTATE_UINT32(freq, Exynos4210MCTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ */
+static void exynos4210_gfrc_set_count(exynos4210_mct_gt *s, uint64_t count)
+{
+ s->count = count;
+ DPRINTF("global timer frc set count 0x%llx\n", count);
+ ptimer_set_count(s->ptimer_frc, count);
+}
+
+/*
+ * Get counter of FRC global timer.
+ */
+static uint64_t exynos4210_gfrc_get_count(exynos4210_mct_gt *s)
+{
+ uint64_t count = 0;
+ count = ptimer_get_count(s->ptimer_frc);
+ if (!count) {
+ /* Timer event was generated and s->reg.cnt holds adequate value */
+ return s->reg.cnt;
+ }
+ count = s->count - count;
+ return s->reg.cnt + count;
+}
+
+/*
+ * Stop global FRC timer
+ */
+static void exynos4210_gfrc_stop(exynos4210_mct_gt *s)
+{
+ DPRINTF("global timer frc stop\n");
+
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void exynos4210_gfrc_start(exynos4210_mct_gt *s)
+{
+ DPRINTF("global timer frc start\n");
+
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Find next nearest Comparator. If current Comparator value equals to other
+ * Comparator value, skip them both
+ */
+static int32_t exynos4210_gcomp_find(Exynos4210MCTState *s)
+{
+ int res;
+ int i;
+ int enabled;
+ uint64_t min;
+ int min_comp_i;
+ uint64_t gfrc;
+ uint64_t distance;
+ uint64_t distance_min;
+ int comp_i;
+
+ /* get gfrc count */
+ gfrc = exynos4210_gfrc_get_count(&s->g_timer);
+
+ min = 0xFFFFFFFFFFFFFFFF;
+ distance_min = 0xFFFFFFFFFFFFFFFF;
+ comp_i = MCT_GT_CMP_NUM;
+ min_comp_i = MCT_GT_CMP_NUM;
+ enabled = 0;
+
+ /* lookup for nearest comparator */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.tcon & G_TCON_COMP_ENABLE(i)) {
+
+ enabled = 1;
+
+ if (s->g_timer.reg.comp[i] > gfrc) {
+ /* Comparator is upper then FRC */
+ distance = s->g_timer.reg.comp[i] - gfrc;
+
+ if (distance <= distance_min) {
+ distance_min = distance;
+ comp_i = i;
+ }
+ } else {
+ /* Comparator is below FRC, find the smallest */
+
+ if (s->g_timer.reg.comp[i] <= min) {
+ min = s->g_timer.reg.comp[i];
+ min_comp_i = i;
+ }
+ }
+ }
+ }
+
+ if (!enabled) {
+ /* All Comparators disabled */
+ res = -1;
+ } else if (comp_i < MCT_GT_CMP_NUM) {
+ /* Found upper Comparator */
+ res = comp_i;
+ } else {
+ /* All Comparators are below or equal to FRC */
+ res = min_comp_i;
+ }
+
+ DPRINTF("found comparator %d: comp 0x%llx distance 0x%llx, gfrc 0x%llx\n",
+ res,
+ s->g_timer.reg.comp[res],
+ distance_min,
+ gfrc);
+
+ return res;
+}
+
+/*
+ * Get distance to nearest Comparator
+ */
+static uint64_t exynos4210_gcomp_get_distance(Exynos4210MCTState *s, int32_t id)
+{
+ if (id == -1) {
+ /* no enabled Comparators, choose max distance */
+ return MCT_GT_COUNTER_STEP;
+ }
+ if (s->g_timer.reg.comp[id] - s->g_timer.reg.cnt < MCT_GT_COUNTER_STEP) {
+ return s->g_timer.reg.comp[id] - s->g_timer.reg.cnt;
+ } else {
+ return MCT_GT_COUNTER_STEP;
+ }
+}
+
+/*
+ * Restart global FRC timer
+ */
+static void exynos4210_gfrc_restart(Exynos4210MCTState *s)
+{
+ uint64_t distance;
+
+ exynos4210_gfrc_stop(&s->g_timer);
+
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+ exynos4210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void exynos4210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+ exynos4210_mct_gt *s = opaque;
+
+ /* If CSTAT is pending and IRQ is enabled */
+ if ((s->reg.int_cstat & G_INT_CSTAT_COMP(id)) &&
+ (s->reg.int_enb & G_INT_ENABLE(id))) {
+ DPRINTF("gcmp timer[%d] IRQ\n", id);
+ qemu_irq_raise(s->irq[id]);
+ }
+}
+
+/*
+ * Lower global timer CMP IRQ
+ */
+static void exynos4210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+ exynos4210_mct_gt *s = opaque;
+ qemu_irq_lower(s->irq[id]);
+}
+
+/*
+ * Global timer FRC event handler.
+ * Each event occurs when internal counter reaches counter + MCT_GT_COUNTER_STEP
+ * Every time we arm global FRC timer to count for MCT_GT_COUNTER_STEP value
+ */
+static void exynos4210_gfrc_event(void *opaque)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int i;
+ uint64_t distance;
+
+ DPRINTF("\n");
+
+ s->g_timer.reg.cnt += s->g_timer.count;
+
+ /* Process all comparators */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+
+ if (s->g_timer.reg.cnt == s->g_timer.reg.comp[i]) {
+ /* reached nearest comparator */
+
+ s->g_timer.reg.int_cstat |= G_INT_CSTAT_COMP(i);
+
+ /* Auto increment */
+ if (s->g_timer.reg.tcon & G_TCON_AUTO_ICREMENT(i)) {
+ s->g_timer.reg.comp[i] += s->g_timer.reg.comp_add_incr[i];
+ }
+
+ /* IRQ */
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ /* Reload FRC to reach nearest comparator */
+ s->g_timer.curr_comp = exynos4210_gcomp_find(s);
+ distance = exynos4210_gcomp_get_distance(s, s->g_timer.curr_comp);
+ if (distance > MCT_GT_COUNTER_STEP) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+ exynos4210_gfrc_set_count(&s->g_timer, distance);
+
+ exynos4210_gfrc_start(&s->g_timer);
+
+ return;
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t exynos4210_lfrc_get_count(exynos4210_mct_lt *s)
+{
+ return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ */
+static void exynos4210_lfrc_update_count(exynos4210_mct_lt *s)
+{
+ if (!s->reg.cnt[L_REG_CNT_FRCCNTB]) {
+ ptimer_set_count(s->ptimer_frc, MCT_LT_COUNTER_STEP);
+ } else {
+ ptimer_set_count(s->ptimer_frc, s->reg.cnt[L_REG_CNT_FRCCNTB]);
+ }
+}
+
+/*
+ * Start local FRC timer
+ */
+static void exynos4210_lfrc_start(exynos4210_mct_lt *s)
+{
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void exynos4210_lfrc_stop(exynos4210_mct_lt *s)
+{
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void exynos4210_lfrc_event(void *opaque)
+{
+ exynos4210_mct_lt * s = (exynos4210_mct_lt *)opaque;
+
+ /* local frc expired */
+
+ DPRINTF("\n");
+
+ s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+ /* update frc counter */
+ exynos4210_lfrc_update_count(s);
+
+ /* raise irq */
+ if (s->reg.int_enb & L_INT_INTENB_FRCEIE) {
+ qemu_irq_raise(s->irq);
+ }
+
+ /* we reached here, this means that timer is enabled */
+ exynos4210_lfrc_start(s);
+}
+
+
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_cnt_start(struct tick_timer *s);
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s);
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void exynos4210_ltick_recalc_count(struct tick_timer *s);
+
+
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void exynos4210_ltick_int_start(struct tick_timer *s)
+{
+ if (!s->int_run) {
+ s->int_run = 1;
+ }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void exynos4210_ltick_int_stop(struct tick_timer *s)
+{
+ if (s->int_run) {
+ s->last_icnto = exynos4210_ltick_int_get_cnto(s);
+ s->int_run = 0;
+ }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t exynos4210_ltick_int_get_cnto(struct tick_timer *s)
+{
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t count;
+ uint64_t counted;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->int_run) {
+ /* INT is stopped. */
+ icnto = s->last_icnto;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ }
+
+ return icnto;
+}
+
+/*
+ * Start local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_start(struct tick_timer *s)
+{
+ if (!s->cnt_run) {
+
+ exynos4210_ltick_recalc_count(s);
+ ptimer_set_count(s->ptimer_tick, s->count);
+ ptimer_run(s->ptimer_tick, 1);
+
+ s->cnt_run = 1;
+ }
+}
+
+/*
+ * Stop local tick cnt timer.
+ */
+static void exynos4210_ltick_cnt_stop(struct tick_timer *s)
+{
+ if (s->cnt_run) {
+
+ s->last_tcnto = exynos4210_ltick_cnt_get_cnto(s);
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ }
+
+ ptimer_stop(s->ptimer_tick);
+
+ s->cnt_run = 0;
+ }
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t exynos4210_ltick_cnt_get_cnto(struct tick_timer *s)
+{
+ uint32_t tcnto;
+ uint32_t icnto;
+ uint64_t remain;
+ uint64_t counted;
+ uint64_t count;
+ uint64_t cur_progress;
+
+ count = ptimer_get_count(s->ptimer_tick);
+ if (count) {
+ /* timer is still counting, called not from event */
+ counted = s->count - ptimer_get_count(s->ptimer_tick);
+ cur_progress = s->progress + counted;
+ } else {
+ /* timer expired earlier */
+ cur_progress = s->progress;
+ }
+
+ remain = s->distance - cur_progress;
+
+ if (!s->cnt_run) {
+ /* Both are stopped. */
+ tcnto = s->last_tcnto;
+ } else if (!s->int_run) {
+ /* INT counter is stopped, progress is by CNT timer */
+ tcnto = remain % s->tcntb;
+ } else {
+ /* Both are counting */
+ icnto = remain / s->tcntb;
+ if (icnto) {
+ tcnto = remain % (icnto * s->tcntb);
+ } else {
+ tcnto = remain % s->tcntb;
+ }
+ }
+
+ return tcnto;
+}
+
+/*
+ * Set new values of counters for CNT and INT timers
+ */
+static void exynos4210_ltick_set_cntb(struct tick_timer *s, uint32_t new_cnt,
+ uint32_t new_int)
+{
+ uint32_t cnt_stopped = 0;
+ uint32_t int_stopped = 0;
+
+ if (s->cnt_run) {
+ exynos4210_ltick_cnt_stop(s);
+ cnt_stopped = 1;
+ }
+
+ if (s->int_run) {
+ exynos4210_ltick_int_stop(s);
+ int_stopped = 1;
+ }
+
+ s->tcntb = new_cnt + 1;
+ s->icntb = new_int + 1;
+
+ if (cnt_stopped) {
+ exynos4210_ltick_cnt_start(s);
+ }
+ if (int_stopped) {
+ exynos4210_ltick_int_start(s);
+ }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void exynos4210_ltick_recalc_count(struct tick_timer *s)
+{
+ uint64_t to_count;
+
+ if ((s->cnt_run && s->last_tcnto) || (s->int_run && s->last_icnto)) {
+ /*
+ * one or both timers run and not counted to the end;
+ * distance is not passed, recalculate with last_tcnto * last_icnto
+ */
+
+ if (s->last_tcnto) {
+ to_count = s->last_tcnto * s->last_icnto;
+ } else {
+ to_count = s->last_icnto;
+ }
+ } else {
+ /* distance is passed, recalculate with tcnto * icnto */
+ if (s->icntb) {
+ s->distance = s->tcntb * s->icntb;
+ } else {
+ s->distance = s->tcntb;
+ }
+
+ to_count = s->distance;
+ s->progress = 0;
+ }
+
+ if (to_count > MCT_LT_COUNTER_STEP) {
+ /* count by step */
+ s->count = MCT_LT_COUNTER_STEP;
+ } else {
+ s->count = to_count;
+ }
+}
+
+/*
+ * Initialize tick_timer
+ */
+static void exynos4210_ltick_timer_init(struct tick_timer *s)
+{
+ exynos4210_ltick_int_stop(s);
+ exynos4210_ltick_cnt_stop(s);
+
+ s->count = 0;
+ s->distance = 0;
+ s->progress = 0;
+ s->icntb = 0;
+ s->tcntb = 0;
+}
+
+/*
+ * tick_timer event.
+ * Raises when abstract tick_timer expires.
+ */
+static void exynos4210_ltick_timer_event(struct tick_timer *s)
+{
+ s->progress += s->count;
+}
+
+/*
+ * Local timer tick counter handler.
+ * Don't use reloaded timers. If timer counter = zero
+ * then handler called but after handler finished no
+ * timer reload occurs.
+ */
+static void exynos4210_ltick_event(void *opaque)
+{
+ exynos4210_mct_lt * s = (exynos4210_mct_lt *)opaque;
+ uint32_t tcnto;
+ uint32_t icnto;
+#ifdef DEBUG_MCT
+ static uint64_t time1[2] = {0};
+ static uint64_t time2[2] = {0};
+#endif
+
+ /* Call tick_timer event handler, it will update it's tcntb and icntb */
+ exynos4210_ltick_timer_event(&s->tick_timer);
+
+ /* get tick_timer cnt */
+ tcnto = exynos4210_ltick_cnt_get_cnto(&s->tick_timer);
+
+ /* get tick_timer int */
+ icnto = exynos4210_ltick_int_get_cnto(&s->tick_timer);
+
+ /* raise IRQ if needed */
+ if (!icnto && s->reg.tcon & L_TCON_INT_START) {
+ /* INT counter enabled and expired */
+
+ s->reg.int_cstat |= L_INT_CSTAT_INTCNT;
+
+ /* raise interrupt if enabled */
+ if (s->reg.int_enb & L_INT_INTENB_ICNTEIE) {
+#ifdef DEBUG_MCT
+ time2[s->id] = qemu_get_clock_ns(vm_clock);
+ DPRINTF("local timer[%d] IRQ: %llx\n", s->id,
+ time2[s->id] - time1[s->id]);
+ time1[s->id] = time2[s->id];
+#endif
+ qemu_irq_raise(s->irq);
+ }
+
+ /* reload ICNTB */
+ if (s->reg.tcon & L_TCON_INTERVAL_MODE) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ s->reg.cnt[L_REG_CNT_ICNTB]);
+ }
+ } else {
+ /* reload TCNTB */
+ if (!tcnto) {
+ exynos4210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ icnto);
+ }
+ }
+
+ /* start tick_timer cnt */
+ exynos4210_ltick_cnt_start(&s->tick_timer);
+
+ /* start tick_timer int */
+ exynos4210_ltick_int_start(&s->tick_timer);
+}
+
+/* update timer frequency */
+static void exynos4210_mct_update_freq(Exynos4210MCTState *s)
+{
+ uint32_t freq = s->freq;
+ s->freq = exynos4210_cmu_get_rate(XXTI) /
+ ((MCT_CFG_GET_PRESCALER(s->reg_mct_cfg)+1) *
+ MCT_CFG_GET_DIVIDER(s->reg_mct_cfg));
+
+ if (freq != s->freq) {
+ DPRINTF("freq=%dHz\n", s->freq);
+
+ /* global timer */
+ ptimer_set_freq(s->g_timer.ptimer_frc, s->freq);
+
+ /* local timer */
+ ptimer_set_freq(s->l_timer[0].tick_timer.ptimer_tick, s->freq);
+ ptimer_set_freq(s->l_timer[0].ptimer_frc, s->freq);
+ ptimer_set_freq(s->l_timer[1].tick_timer.ptimer_tick, s->freq);
+ ptimer_set_freq(s->l_timer[1].ptimer_frc, s->freq);
+ }
+}
+
+/* set defaul_timer values for all fields */
+static void exynos4210_mct_reset(void *opaque)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *) opaque;
+ uint32_t i;
+
+ s->reg_mct_cfg = 0;
+
+ /* global timer */
+ memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+ exynos4210_gfrc_stop(&s->g_timer);
+
+ /* local timer */
+ memset(s->l_timer[0].reg.cnt, 0, sizeof(s->l_timer[0].reg.cnt));
+ memset(s->l_timer[1].reg.cnt, 0, sizeof(s->l_timer[1].reg.cnt));
+ for (i = 0; i < 2; i++) {
+ s->l_timer[i].reg.int_cstat = 0;
+ s->l_timer[i].reg.int_enb = 0;
+ s->l_timer[i].reg.tcon = 0;
+ s->l_timer[i].reg.wstat = 0;
+ s->l_timer[i].tick_timer.count = 0;
+ s->l_timer[i].tick_timer.distance = 0;
+ s->l_timer[i].tick_timer.progress = 0;
+ ptimer_stop(s->l_timer[i].ptimer_frc);
+
+ exynos4210_ltick_timer_init(&s->l_timer[i].tick_timer);
+ }
+
+ exynos4210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t exynos4210_mct_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index;
+ int shift;
+ uint64_t count;
+ uint32_t value;
+ int lt_i;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ value = s->reg_mct_cfg;
+ break;
+
+ case G_CNT_L: case G_CNT_U:
+ shift = 8 * (offset & 0x4);
+ count = exynos4210_gfrc_get_count(&s->g_timer);
+ value = 0xFFFFFFFF & (count >> shift);
+ DPRINTF("read FRC=0x%llx\n", count);
+ break;
+
+ case G_CNT_WSTAT:
+ value = s->g_timer.reg.cnt_wstat;
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8 * (offset & 0x4);
+ value = 0xFFFFFFFF & (s->g_timer.reg.comp[index] >> shift);
+ break;
+
+ case G_TCON:
+ value = s->g_timer.reg.tcon;
+ break;
+
+ case G_INT_CSTAT:
+ value = s->g_timer.reg.int_cstat;
+ break;
+
+ case G_INT_ENB:
+ value = s->g_timer.reg.int_enb;
+ break;
+ break;
+ case G_WSTAT:
+ value = s->g_timer.reg.wstat;
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ value = s->g_timer.reg.comp_add_incr[GET_G_COMP_ADD_INCR_IDX(offset)];
+ break;
+
+ /* Local timers */
+ case L0_TCNTB: case L0_ICNTB: case L0_FRCNTB:
+ case L1_TCNTB: case L1_ICNTB: case L1_FRCNTB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+ value = s->l_timer[lt_i].reg.cnt[index];
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_cnt_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read TCNTO %x\n", lt_i, value);
+ break;
+
+ case L0_ICNTO: case L1_ICNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_ltick_int_get_cnto(&s->l_timer[lt_i].tick_timer);
+ DPRINTF("local timer[%d] read ICNTO %x\n", lt_i, value);
+ break;
+
+ case L0_FRCNTO: case L1_FRCNTO:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ value = exynos4210_lfrc_get_count(&s->l_timer[lt_i]);
+
+ break;
+
+ case L0_TCON: case L1_TCON:
+ lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+ value = s->l_timer[lt_i].reg.tcon;
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+ value = s->l_timer[lt_i].reg.int_cstat;
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+ value = s->l_timer[lt_i].reg.int_enb;
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = ((offset&0xF00) - L0_TCNTB)/0x100;
+ value = s->l_timer[lt_i].reg.wstat;
+ break;
+
+ default:
+ hw_error("exynos4210.mct: bad read offset "
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+ return value;
+}
+
+/* MCT write */
+static void exynos4210_mct_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ Exynos4210MCTState *s = (Exynos4210MCTState *)opaque;
+ int index; /* index in buffer which represents register set */
+ int shift;
+ int lt_i;
+ uint64_t new_frc;
+ uint32_t i;
+ uint32_t old_val;
+#ifdef DEBUG_MCT
+ static uint32_t icntb_max[2] = {0};
+ static uint32_t icntb_min[2] = {0xFFFFFFFF, 0xFFFFFFFF};
+ static uint32_t tcntb_max[2] = {0};
+ static uint32_t tcntb_min[2] = {0xFFFFFFFF, 0xFFFFFFFF};
+#endif
+
+ new_frc = s->g_timer.reg.cnt;
+
+ switch (offset) {
+
+ case MCT_CFG:
+ s->reg_mct_cfg = value;
+ exynos4210_mct_update_freq(s);
+ break;
+
+ case G_CNT_L:
+ case G_CNT_U:
+ if (offset == G_CNT_L) {
+
+ DPRINTF("global timer write to reg.cntl %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & 0xFFFFFFFF00000000) +
+ value;
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_L;
+ }
+ if (offset == G_CNT_U) {
+
+ DPRINTF("global timer write to reg.cntu %llx\n", value);
+
+ new_frc = (s->g_timer.reg.cnt & 0xFFFFFFFF) +
+ ((uint64_t)value<<32);
+ s->g_timer.reg.cnt_wstat |= G_CNT_WSTAT_U;
+ }
+
+ s->g_timer.reg.cnt = new_frc;
+ exynos4210_gfrc_restart(s);
+ break;
+
+ case G_CNT_WSTAT:
+ s->g_timer.reg.cnt_wstat &= ~(value);
+ break;
+
+ case G_COMP_L(0): case G_COMP_L(1): case G_COMP_L(2): case G_COMP_L(3):
+ case G_COMP_U(0): case G_COMP_U(1): case G_COMP_U(2): case G_COMP_U(3):
+ index = GET_G_COMP_IDX(offset);
+ shift = 8*(offset&0x4);
+ s->g_timer.reg.comp[index] =
+ (s->g_timer.reg.comp[index] & (0xFFFFFFFF00000000>>shift)) +
+ (value<<shift);
+
+ DPRINTF("comparator %d write 0x%llx val << %d\n", index, value, shift);
+
+ if (offset&0x4) {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_U(index);
+ } else {
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_L(index);
+ }
+
+ exynos4210_gfrc_restart(s);
+ break;
+
+ case G_TCON:
+ old_val = s->g_timer.reg.tcon;
+ s->g_timer.reg.tcon = value;
+ s->g_timer.reg.wstat |= G_WSTAT_TCON_WRITE;
+
+ DPRINTF("global timer write to reg.g_tcon %llx\n", value);
+
+ /* Start FRC if transition from disabled to enabled */
+ if ((value & G_TCON_TIMER_ENABLE) > (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_start(&s->g_timer);
+ }
+ if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ exynos4210_gfrc_stop(&s->g_timer);
+ }
+
+ /* Start CMP if transition from disabled to enabled */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_TCON_COMP_ENABLE(i)) != (old_val &
+ G_TCON_COMP_ENABLE(i))) {
+ exynos4210_gfrc_restart(s);
+ }
+ }
+ break;
+
+ case G_INT_CSTAT:
+ s->g_timer.reg.int_cstat &= ~(value);
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if (value & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+ break;
+
+ case G_INT_ENB:
+
+ /* Raise IRQ if transition from disabled to enabled and CSTAT pending */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ if ((value & G_INT_ENABLE(i)) > (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ if (s->g_timer.reg.int_cstat & G_INT_CSTAT_COMP(i)) {
+ exynos4210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ exynos4210_gcomp_lower_irq(&s->g_timer, i);
+ }
+ }
+
+ DPRINTF("global timer INT enable %llx\n", value);
+ s->g_timer.reg.int_enb = value;
+ break;
+
+ case G_WSTAT:
+ s->g_timer.reg.wstat &= ~(value);
+ break;
+
+ case G_COMP0_ADD_INCR: case G_COMP1_ADD_INCR:
+ case G_COMP2_ADD_INCR: case G_COMP3_ADD_INCR:
+ index = GET_G_COMP_ADD_INCR_IDX(offset);
+ s->g_timer.reg.comp_add_incr[index] = value;
+ s->g_timer.reg.wstat |= G_WSTAT_COMP_ADDINCR(index);
+ break;
+
+ /* Local timers */
+ case L0_TCON: case L1_TCON:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.tcon;
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCON_WRITE;
+ s->l_timer[lt_i].reg.tcon = value;
+
+ /* Stop local CNT */
+ if ((value & L_TCON_TICK_START) <
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] stop cnt\n", lt_i);
+ exynos4210_ltick_cnt_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Stop local INT */
+ if ((value & L_TCON_INT_START) <
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] stop int\n", lt_i);
+ exynos4210_ltick_int_stop(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local CNT */
+ if ((value & L_TCON_TICK_START) >
+ (old_val & L_TCON_TICK_START)) {
+ DPRINTF("local timer[%d] start cnt\n", lt_i);
+ exynos4210_ltick_cnt_start(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start local INT */
+ if ((value & L_TCON_INT_START) >
+ (old_val & L_TCON_INT_START)) {
+ DPRINTF("local timer[%d] start int\n", lt_i);
+ exynos4210_ltick_int_start(&s->l_timer[lt_i].tick_timer);
+ }
+
+ /* Start or Stop local FRC if TCON changed */
+ if ((value & L_TCON_FRC_START) >
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] start frc\n", lt_i);
+ exynos4210_lfrc_start(&s->l_timer[lt_i]);
+ }
+ if ((value & L_TCON_FRC_START) <
+ (s->l_timer[lt_i].reg.tcon & L_TCON_FRC_START)) {
+ DPRINTF("local timer[%d] stop frc\n", lt_i);
+ exynos4210_lfrc_stop(&s->l_timer[lt_i]);
+ }
+ break;
+
+ case L0_TCNTB: case L1_TCNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ /*
+ * TCNTB is updated to internal register only after CNT expired.
+ * Due to this we should reload timer to nearest moment when CNT is
+ * expired and then in event handler update tcntb to new TCNTB value.
+ */
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer, value,
+ s->l_timer[lt_i].tick_timer.icntb);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_TCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] = value;
+
+#ifdef DEBUG_MCT
+ if (tcntb_min[lt_i] > value) {
+ tcntb_min[lt_i] = value;
+ }
+ if (tcntb_max[lt_i] < value) {
+ tcntb_max[lt_i] = value;
+ }
+ DPRINTF("local timer[%d] TCNTB write %llx; max=%x, min=%x\n",
+ lt_i, value, tcntb_max[lt_i], tcntb_min[lt_i]);
+#endif
+ break;
+
+ case L0_ICNTB: case L1_ICNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_ICNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] = value &
+ ~L_ICNTB_MANUAL_UPDATE;
+
+ /*
+ * FIXME: this stub should be removed
+ * We need to avoid too small values for TCNTB*ICNTB. If not, IRQ event
+ * could raise too fast disallowing QEMU to execute target code.
+ * One way is to use -icount option, but in that case target 1 second
+ * could turn in host 100 seconds which leads to enormous sleep(n)
+ * awaiting. So let just enlarge small values of TCNTB*ICNTB to avoid
+ * too fast IRQs.
+ */
+ if (s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] *
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB] < MCT_LT_CNT_LOW_LIMIT) {
+ if (!s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB]) {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT;
+ } else {
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB] =
+ MCT_LT_CNT_LOW_LIMIT /
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_TCNTB];
+ }
+ }
+
+
+ if (value & L_ICNTB_MANUAL_UPDATE) {
+ exynos4210_ltick_set_cntb(&s->l_timer[lt_i].tick_timer,
+ s->l_timer[lt_i].tick_timer.tcntb,
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_ICNTB]);
+ }
+
+#ifdef DEBUG_MCT
+ if (icntb_min[lt_i] > value) {
+ icntb_min[lt_i] = value;
+ }
+ if (icntb_max[lt_i] < value) {
+ icntb_max[lt_i] = value;
+ }
+DPRINTF("local timer[%d] ICNTB write %llx; max=%x, min=%x\n\n",
+ lt_i, value, icntb_max[lt_i], icntb_min[lt_i]);
+#endif
+break;
+
+ case L0_FRCNTB: case L1_FRCNTB:
+
+ lt_i = GET_L_TIMER_IDX(offset);
+ index = GET_L_TIMER_CNT_REG_IDX(offset, lt_i);
+
+ DPRINTF("local timer[%d] FRCNTB write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.wstat |= L_WSTAT_FRCCNTB_WRITE;
+ s->l_timer[lt_i].reg.cnt[L_REG_CNT_FRCCNTB] = value;
+
+ break;
+
+ case L0_TCNTO: case L1_TCNTO:
+ case L0_ICNTO: case L1_ICNTO:
+ case L0_FRCNTO: case L1_FRCNTO:
+ fprintf(stderr, "\n[exynos4210.mct: write to RO register "
+ TARGET_FMT_plx "]\n\n", offset);
+ break;
+
+ case L0_INT_CSTAT: case L1_INT_CSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ DPRINTF("local timer[%d] CSTAT write %llx\n", lt_i, value);
+
+ s->l_timer[lt_i].reg.int_cstat &= ~value;
+ if (!s->l_timer[lt_i].reg.int_cstat) {
+ qemu_irq_lower(s->l_timer[lt_i].irq);
+ }
+ break;
+
+ case L0_INT_ENB: case L1_INT_ENB:
+ lt_i = GET_L_TIMER_IDX(offset);
+ old_val = s->l_timer[lt_i].reg.int_enb;
+
+ /* Raise Local timer IRQ if cstat is pending */
+ if ((value & L_INT_INTENB_ICNTEIE) > (old_val & L_INT_INTENB_ICNTEIE)) {
+ if (s->l_timer[lt_i].reg.int_cstat & L_INT_CSTAT_INTCNT) {
+ qemu_irq_raise(s->l_timer[lt_i].irq);
+ }
+ }
+
+ s->l_timer[lt_i].reg.int_enb = value;
+
+ break;
+
+ case L0_WSTAT: case L1_WSTAT:
+ lt_i = GET_L_TIMER_IDX(offset);
+
+ s->l_timer[lt_i].reg.wstat &= ~value;
+ break;
+
+ default:
+ hw_error("exynos4210.mct: bad write offset "
+ TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_mct_ops = {
+ .read = exynos4210_mct_read,
+ .write = exynos4210_mct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int exynos4210_mct_init(SysBusDevice *dev)
+{
+ int i;
+ Exynos4210MCTState *s = FROM_SYSBUS(Exynos4210MCTState, dev);
+ QEMUBH * bh[2];
+
+ /* Global timer */
+ bh[0] = qemu_bh_new(exynos4210_gfrc_event, s);
+ s->g_timer.ptimer_frc = ptimer_init(bh[0]);
+ memset(&s->g_timer.reg, 0, sizeof(struct gregs));
+
+ /* Local timers */
+ for (i = 0; i < 2; i++) {
+ bh[0] = qemu_bh_new(exynos4210_ltick_event, &s->l_timer[i]);
+ bh[1] = qemu_bh_new(exynos4210_lfrc_event, &s->l_timer[i]);
+ s->l_timer[i].tick_timer.ptimer_tick = ptimer_init(bh[0]);
+ s->l_timer[i].ptimer_frc = ptimer_init(bh[1]);
+ s->l_timer[i].id = i;
+ }
+
+ /* IRQs */
+ for (i = 0; i < MCT_GT_CMP_NUM; i++) {
+ sysbus_init_irq(dev, &s->g_timer.irq[i]);
+ }
+ for (i = 0; i < 2; i++) {
+ sysbus_init_irq(dev, &s->l_timer[i].irq);
+ }
+
+ memory_region_init_io(&s->iomem, &exynos4210_mct_ops, s, "exynos4210-mct",
+ MCT_SFR_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+
+ exynos4210_mct_reset(s);
+
+ qemu_register_reset(exynos4210_mct_reset, s);
+ vmstate_register(NULL, -1, &VMState_Exynos4210MCTState, s);
+ return 0;
+}
+
+static void exynos4210_mct_register_devices(void)
+{
+ sysbus_register_dev("exynos4210.mct", sizeof(Exynos4210MCTState),
+ exynos4210_mct_init);
+}
+
+device_init(exynos4210_mct_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (8 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
` (4 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/exynos4210.c | 28 +++++++++++++++++++++++++++-
1 files changed, 27 insertions(+), 1 deletions(-)
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index f172346..8d8a12c 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -93,6 +93,12 @@
#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
+/* Secondary CPU polling address to get loader start from */
+#define EXYNOS4210_SECOND_CPU_BOOTREG 0x10020814
+
+/* Secondary CPU startup code is in IROM memory */
+#define EXYNOS4210_SMP_BOOT_ADDR EXYNOS4210_IROM_BASE_ADDR
+
static struct arm_boot_info exynos4210_binfo = {
.loader_start = EXYNOS4210_BASE_BOOT_ADDR,
};
@@ -208,6 +214,8 @@ static void exynos4210_init(ram_addr_t ram_size,
MemoryRegion *irom_alias_mem = g_new(MemoryRegion, 1);
MemoryRegion *dram0_mem = g_new(MemoryRegion, 1);
MemoryRegion *dram1_mem = NULL;
+ MemoryRegion *hack_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *bootreg_mem = g_new(MemoryRegion, 1);
Exynos4210Irq *irqs;
qemu_irq *irq_table;
qemu_irq *irqp;
@@ -219,10 +227,12 @@ static void exynos4210_init(ram_addr_t ram_size,
switch (board_type) {
case BOARD_EXYNOS4210_NURI:
- exynos4210_binfo.board_id = MACH_NURI_ID;
+ exynos4210_binfo.board_id = MACH_NURI_ID;
+ exynos4210_binfo.smp_bootreg_addr = EXYNOS4210_SECOND_CPU_BOOTREG;
break;
case BOARD_EXYNOS4210_SMDKC210:
exynos4210_binfo.board_id = MACH_SMDKC210_ID;
+ exynos4210_binfo.smp_bootreg_addr = EXYNOS4210_SECOND_CPU_BOOTREG;
break;
default:
break;
@@ -353,6 +363,21 @@ static void exynos4210_init(ram_addr_t ram_size,
memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR,
dram0_mem);
+ /*
+ * Secondary CPU startup code will be placed here.
+ */
+ memory_region_init_ram(hack_mem, NULL, "exynos4210.hack", 0x1000);
+ memory_region_add_subregion(system_mem, EXYNOS4210_SMP_BOOT_ADDR,
+ hack_mem);
+
+ /*
+ * Hack: Map SECOND_CPU_BOOTREG, because it is in PMU USER5 register.
+ */
+ memory_region_init_ram(bootreg_mem, NULL, "exynos4210.bootreg", 0x4);
+ memory_region_add_subregion(system_mem, EXYNOS4210_SECOND_CPU_BOOTREG,
+ bootreg_mem);
+
+
/* CMU */
sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL);
@@ -427,6 +452,7 @@ static void exynos4210_init(ram_addr_t ram_size,
exynos4210_binfo.kernel_filename = kernel_filename;
exynos4210_binfo.initrd_filename = initrd_filename;
exynos4210_binfo.kernel_cmdline = kernel_cmdline;
+ exynos4210_binfo.smp_priv_base = EXYNOS4210_SMP_PRIVATE_BASE_ADDR;
arm_load_kernel(first_cpu, &exynos4210_binfo);
}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (9 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
` (3 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/devices.h | 2 +-
hw/lan9118.c | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
2 files changed, 109 insertions(+), 8 deletions(-)
diff --git a/hw/devices.h b/hw/devices.h
index 1a55c1e..24cae4c 100644
--- a/hw/devices.h
+++ b/hw/devices.h
@@ -10,7 +10,7 @@ struct MemoryRegion;
void smc91c111_init(NICInfo *, uint32_t, qemu_irq);
/* lan9118.c */
-void lan9118_init(NICInfo *, uint32_t, qemu_irq);
+DeviceState *lan9118_init(NICInfo *, uint32_t, qemu_irq);
/* tsc210x.c */
uWireSlave *tsc2102_init(qemu_irq pint);
diff --git a/hw/lan9118.c b/hw/lan9118.c
index ee8b2ea..e44e5f8 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -212,6 +212,17 @@ typedef struct {
int rxp_offset;
int rxp_size;
int rxp_pad;
+
+ uint32_t write_word_prev_offset;
+ uint32_t write_word_n;
+ uint16_t write_word_l;
+ uint16_t write_word_h;
+ uint32_t read_word_prev_offset;
+ uint32_t read_word_n;
+ uint32_t read_long;
+
+ uint32_t mode_16bit;
+
} lan9118_state;
static void lan9118_update(lan9118_state *s)
@@ -345,6 +356,9 @@ static void lan9118_reset(DeviceState *d)
s->mac_mii_data = 0;
s->mac_flow = 0;
+ s->read_word_n = 0;
+ s->write_word_n = 0;
+
phy_reset(s);
s->eeprom_writable = 0;
@@ -896,11 +910,11 @@ static void lan9118_tick(void *opaque)
}
static void lan9118_writel(void *opaque, target_phys_addr_t offset,
- uint64_t val, unsigned size)
+ uint32_t val)
{
lan9118_state *s = (lan9118_state *)opaque;
offset &= 0xff;
-
+
//DPRINTF("Write reg 0x%02x = 0x%08x\n", (int)offset, val);
if (offset >= 0x20 && offset < 0x40) {
/* TX FIFO */
@@ -1029,8 +1043,47 @@ static void lan9118_writel(void *opaque, target_phys_addr_t offset,
lan9118_update(s);
}
-static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
- unsigned size)
+static void lan9118_writew(void *opaque, target_phys_addr_t offset,
+ uint32_t val)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ offset &= 0xff;
+
+ if (s->write_word_prev_offset != (offset & ~0x3)) {
+ /* New offset, reset word counter */
+ s->write_word_n = 0;
+ s->write_word_prev_offset = offset & ~0x3;
+ }
+
+ if (offset & 0x2) {
+ s->write_word_h = val;
+ } else {
+ s->write_word_l = val;
+ }
+
+ //DPRINTF("Writew reg 0x%02x = 0x%08x\n", (int)offset, val);
+ s->write_word_n++;
+ if (s->write_word_n == 2) {
+ s->write_word_n = 0;
+ lan9118_writel(s, offset & ~3, s->write_word_l +
+ (s->write_word_h << 16));
+ }
+}
+
+static void lan9118_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ switch (size) {
+ case 2:
+ return lan9118_writew(opaque, offset, (uint32_t)val);
+ case 4:
+ return lan9118_writel(opaque, offset, (uint32_t)val);
+ }
+
+ hw_error("lan9118_write: Bad size 0x%x\n", size);
+}
+
+static uint32_t lan9118_readl(void *opaque, target_phys_addr_t offset)
{
lan9118_state *s = (lan9118_state *)opaque;
@@ -1065,6 +1118,9 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
case CSR_TX_CFG:
return s->tx_cfg;
case CSR_HW_CFG:
+ if (s->mode_16bit) {
+ return s->hw_cfg & ~0x4;
+ }
return s->hw_cfg | 0x4;
case CSR_RX_DP_CTRL:
return 0;
@@ -1103,9 +1159,51 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
return 0;
}
+static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ uint32_t val;
+
+ if (s->read_word_prev_offset != (offset & ~0x3)) {
+ /* New offset, reset word counter */
+ s->read_word_n = 0;
+ s->read_word_prev_offset = offset & ~0x3;
+ }
+
+ s->read_word_n++;
+ if (s->read_word_n == 1) {
+ s->read_long = lan9118_readl(s, offset & ~3);
+ } else {
+ s->read_word_n = 0;
+ }
+
+ if (offset & 2) {
+ val = s->read_long >> 16;
+ } else {
+ val = s->read_long & 0xFFFF;
+ }
+
+ //DPRINTF("Readw reg 0x%02x, val 0x%x\n", (int)offset, val);
+ return val;
+}
+
+static uint64_t lan9118_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ switch (size) {
+ case 2:
+ return lan9118_readw(opaque, offset);
+ case 4:
+ return lan9118_readl(opaque, offset);
+ }
+
+ hw_error("lan9118_read: Bad size 0x%x\n", size);
+ return 0;
+}
+
static const MemoryRegionOps lan9118_mem_ops = {
- .read = lan9118_readl,
- .write = lan9118_writel,
+ .read = lan9118_read,
+ .write = lan9118_write,
.endianness = DEVICE_NATIVE_ENDIAN,
};
@@ -1162,6 +1260,7 @@ static SysBusDeviceInfo lan9118_info = {
.qdev.reset = lan9118_reset,
.qdev.props = (Property[]) {
DEFINE_NIC_PROPERTIES(lan9118_state, conf),
+ DEFINE_PROP_UINT32("mode_16bit", lan9118_state, mode_16bit, 0),
DEFINE_PROP_END_OF_LIST(),
}
};
@@ -1173,7 +1272,7 @@ static void lan9118_register_devices(void)
/* Legacy helper function. Should go away when machine config files are
implemented. */
-void lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
+DeviceState *lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
{
DeviceState *dev;
SysBusDevice *s;
@@ -1185,6 +1284,8 @@ void lan9118_init(NICInfo *nd, uint32_t base, qemu_irq irq)
s = sysbus_from_qdev(dev);
sysbus_mmio_map(s, 0, base);
sysbus_connect_irq(s, 0, irq);
+
+ return dev;
}
device_init(lan9118_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210.
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (10 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
` (2 subsequent siblings)
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
SMDKC210 uses lan9215 chip, but lan9118 in 16-bit mode seems to
be enough.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/exynos4210.c | 18 ++++++++++++++++++
1 files changed, 18 insertions(+), 0 deletions(-)
diff --git a/hw/exynos4210.c b/hw/exynos4210.c
index 8d8a12c..5b18b68 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -26,6 +26,8 @@
#include "sysemu.h"
#include "sysbus.h"
#include "arm-misc.h"
+#include "net.h"
+#include "devices.h"
#include "exec-memory.h"
#include "exynos4210.h"
@@ -224,6 +226,8 @@ static void exynos4210_init(ram_addr_t ram_size,
SysBusDevice *busdev;
ram_addr_t mem_size;
int n;
+ NICInfo *nd;
+ int done_nic = 0;
switch (board_type) {
case BOARD_EXYNOS4210_NURI:
@@ -444,6 +448,20 @@ static void exynos4210_init(ram_addr_t ram_size,
exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
+ /*** LAN adapter: this should be a 9215 but the 9118 is close enough ***/
+ if (board_type == BOARD_EXYNOS4210_SMDKC210) {
+ for (n = 0; n < nb_nics; n++) {
+ nd = &nd_table[n];
+
+ if (!done_nic && (!nd->model ||
+ strcmp(nd->model, "lan9118") == 0)) {
+ dev = lan9118_init(nd, 0x05000000,
+ qemu_irq_invert(irq_table[exynos4210_get_irq(37, 1)]));
+ qdev_prop_set_uint32(dev, "mode_16bit", 1);
+ done_nic = 1;
+ }
+ }
+ }
/*** Load kernel ***/
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (11 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin
From: Mitsyanko Igor <i.mitsyanko@samsung.com>
Data transfer direction between host controller and SD/MMC card is selected by
host controller configuration registers, but whether we actually need or need
not perform data transfer depends on type of last issued command. To avoid
memorization of which type of command host controller issued the last time, we
can use simple query procedures, to make sure that SD/MMC card is in the
right state. The only query routine currently presented in SD/MMC card
emulation is sd_data_ready(), this patch adds sd_receive_ready() routine.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/sd.c | 5 +++++
hw/sd.h | 1 +
2 files changed, 6 insertions(+), 0 deletions(-)
diff --git a/hw/sd.c b/hw/sd.c
index 10e26ad..b0a8557 100644
--- a/hw/sd.c
+++ b/hw/sd.c
@@ -1670,3 +1670,8 @@ void sd_enable(SDState *sd, int enable)
{
sd->enable = enable;
}
+
+int sd_receive_ready(SDState *sd)
+{
+ return sd->state == sd_receivingdata_state;
+}
diff --git a/hw/sd.h b/hw/sd.h
index ac4b7c4..71ab781 100644
--- a/hw/sd.h
+++ b/hw/sd.h
@@ -75,5 +75,6 @@ uint8_t sd_read_data(SDState *sd);
void sd_set_cb(SDState *sd, qemu_irq readonly, qemu_irq insert);
int sd_data_ready(SDState *sd);
void sd_enable(SDState *sd, int enable);
+int sd_receive_ready(SDState *sd);
#endif /* __hw_sd_h */
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (12 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin
From: Mitsyanko Igor <i.mitsyanko@samsung.com>
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 3 +-
hw/exynos4210.c | 20 +
hw/exynos4210_sdhc.c | 1666 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1688 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_sdhc.c
diff --git a/Makefile.target b/Makefile.target
index 691582b..a5d67c9 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,8 @@ 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_pwm.o exynos4210_mct.o
+ exynos4210_combiner.o exynos4210_pwm.o exynos4210_mct.o \
+ exynos4210_sdhc.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 5b18b68..62c90e9 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -95,6 +95,12 @@
#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
+/* SD/MMC host controllers SFR base addresses */
+#define EXYNOS4210_SDHC0_BASE_ADDR 0x12510000
+#define EXYNOS4210_SDHC1_BASE_ADDR 0x12520000
+#define EXYNOS4210_SDHC2_BASE_ADDR 0x12530000
+#define EXYNOS4210_SDHC3_BASE_ADDR 0x12540000
+
/* Secondary CPU polling address to get loader start from */
#define EXYNOS4210_SECOND_CPU_BOOTREG 0x10020814
@@ -448,6 +454,20 @@ static void exynos4210_init(ram_addr_t ram_size,
exynos4210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
+ /*** SD/MMC host controllers ***/
+
+ sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC0_BASE_ADDR,
+ irq_table[exynos4210_get_irq(29, 0)]);
+
+ sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC1_BASE_ADDR,
+ irq_table[exynos4210_get_irq(29, 1)]);
+
+ sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC2_BASE_ADDR,
+ irq_table[exynos4210_get_irq(29, 2)]);
+
+ sysbus_create_simple("exynos4210.sdhc", EXYNOS4210_SDHC3_BASE_ADDR,
+ irq_table[exynos4210_get_irq(29, 3)]);
+
/*** LAN adapter: this should be a 9215 but the 9118 is close enough ***/
if (board_type == BOARD_EXYNOS4210_SMDKC210) {
for (n = 0; n < nb_nics; n++) {
diff --git a/hw/exynos4210_sdhc.c b/hw/exynos4210_sdhc.c
new file mode 100644
index 0000000..05dd05c
--- /dev/null
+++ b/hw/exynos4210_sdhc.c
@@ -0,0 +1,1666 @@
+/*
+ * Samsung exynos4210 SD/MMC host controller
+ * (SD host controller specification ver. 2.0 compliant)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * Based on MMC controller for Samsung S5PC1xx-based board emulation
+ * by Alexey Merkulov and Vladimir Monakhov.
+ *
+ * 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 "hw.h"
+#include "sd.h"
+#include "qemu-common.h"
+#include "block_int.h"
+#include "blockdev.h"
+#include "sysbus.h"
+#include "qemu-timer.h"
+
+/* host controller debug messages */
+#define DEBUG_SDHC 0
+
+#if DEBUG_SDHC
+#define DPRINTF(fmt, args...) \
+do { fprintf(stderr, "QEMU SDHC: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+#define TARGET_WORD_SIZE (TARGET_PHYS_ADDR_SPACE_BITS / 8)
+#define READ_BUFFER_DELAY 5
+#define WRITE_BUFFER_DELAY 6
+#define INSERTION_DELAY (get_ticks_per_sec())
+#define CMD_RESPONSE (3 << 0)
+
+/* Default SD/MMC host controller features information, which will be
+ * presented in host's CAPABILITIES REGISTER at reset.
+ * If not stated otherwise:
+ * 0 - not supported, 1 - supported, other - prohibited.
+ */
+#define SDHC_CAPAB_64BITBUS 0ul /* 64-bit System Bus Support */
+#define SDHC_CAPAB_18V 1ul /* Voltage support 1.8v */
+#define SDHC_CAPAB_30V 0ul /* Voltage support 3.0v */
+#define SDHC_CAPAB_33V 1ul /* Voltage support 3.3v */
+#define SDHC_CAPAB_SUSPRESUME 0ul /* Suspend/resume support */
+#define SDHC_CAPAB_SDMA 1ul /* SDMA support */
+#define SDHC_CAPAB_HIGHSPEED 1ul /* High speed support */
+#define SDHC_CAPAB_ADMA 1ul /* ADMA2 support */
+/* Maximum host controller R/W buffers size
+ * Possible values: 512, 1024, 2048 bytes */
+#define SDHC_CAPAB_MAXBLOCKLENGTH 512ul
+/* Maximum clock frequency for SDclock in MHz
+ * value in range 10-63 MHz, 0 - not defined */
+#define SDHC_CAPAB_BASECLKFREQ 0ul
+#define SDHC_CAPAB_TOUNIT 1ul /* Timeout clock unit 0 - kHz, 1 - MHz */
+/* Timeout clock frequency 1-63, 0 - not defined */
+#define SDHC_CAPAB_TOCLKFREQ 0ul
+
+/* Now check all parameters and calculate CAPABILITIES REGISTER value */
+#if SDHC_CAPAB_64BITBUS > 1 || SDHC_CAPAB_18V > 1 || SDHC_CAPAB_30V > 1 ||\
+ SDHC_CAPAB_33V > 1 || SDHC_CAPAB_SUSPRESUME > 1 || SDHC_CAPAB_SDMA > 1 ||\
+ SDHC_CAPAB_HIGHSPEED > 1 || SDHC_CAPAB_ADMA > 1 || SDHC_CAPAB_TOUNIT > 1
+#error Capabilities features SDHC_CAPAB_x must have value 0 or 1!
+#endif
+
+#if SDHC_CAPAB_MAXBLOCKLENGTH == 512
+#define MAX_BLOCK_LENGTH 0ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 1024
+#define MAX_BLOCK_LENGTH 1ul
+#elif SDHC_CAPAB_MAXBLOCKLENGTH == 2048
+#define MAX_BLOCK_LENGTH 2ul
+#else
+#error Max host controller block size can have value 512, 1024 or 2048 only!
+#endif
+
+#if (SDHC_CAPAB_BASECLKFREQ > 0 && SDHC_CAPAB_BASECLKFREQ < 10) || \
+ SDHC_CAPAB_BASECLKFREQ > 63
+#error SDclock frequency can have value in range 0, 10-63 only!
+#endif
+
+#if SDHC_CAPAB_TOCLKFREQ > 63
+#error Timeout clock frequency can have value in range 0-63 only!
+#endif
+
+#define SDHC_CAPAB_REG_DEFAULT ((SDHC_CAPAB_64BITBUS<<28)|(SDHC_CAPAB_18V<<26)|\
+ (SDHC_CAPAB_30V<<25)|(SDHC_CAPAB_33V<<24)|(SDHC_CAPAB_SUSPRESUME<<23)|\
+ (SDHC_CAPAB_SDMA<<22)|(SDHC_CAPAB_HIGHSPEED<<21)|(SDHC_CAPAB_ADMA<<19)|\
+ (MAX_BLOCK_LENGTH<<16)|(SDHC_CAPAB_BASECLKFREQ<<8)|(SDHC_CAPAB_TOUNIT<<7)|\
+ (SDHC_CAPAB_TOCLKFREQ))
+
+/****************************************************
+ * SD host controller registers offsets
+ ***************************************************/
+
+/* R/W SDMA System Address register 0x0 */
+#define SDHC_SYSAD 0x00
+
+/* R/W Host DMA Buffer Boundary and Transfer Block Size Register 0x0 */
+#define SDHC_BLKSIZE 0x04
+
+/* R/W Blocks count for current transfer 0x0 */
+#define SDHC_BLKCNT 0x06
+
+/* R/W Command Argument Register 0x0 */
+#define SDHC_ARGUMENT 0x08
+
+/* R/W Transfer Mode Setting Register 0x0 */
+#define SDHC_TRNMOD 0x0C
+#define SDHC_TRNS_DMA 0x0001
+#define SDHC_TRNS_BLK_CNT_EN 0x0002
+#define SDHC_TRNS_ACMD12 0x0004
+#define SDHC_TRNS_READ 0x0010
+#define SDHC_TRNS_MULTI 0x0020
+#define SDHC_TRNS_CEATA 0x0300
+#define SDHC_TRNS_BOOTCMD 0x1000
+#define SDHC_TRNS_BOOTACK 0x2000
+
+/* R/W Command Register 0x0 */
+#define SDHC_CMDREG 0x0E
+#define SDHC_CMD_RSP_WITH_BUSY (3 << 0)
+#define SDHC_CMD_DATA_PRESENT (1 << 5)
+#define SDHC_CMD_SUSPEND_MASK (1 << 6)
+#define SDHC_CMD_RESUME_MASK (1 << 7)
+#define SDHC_CMD_ABORT_MASK ((1 << 6)|(1 << 7))
+#define SDHC_CMD_TYPE_MASK ((1 << 6)|(1 << 7))
+
+/* ROC Response Register 0 0x0 */
+#define SDHC_RSPREG0 0x10
+/* ROC Response Register 1 0x0 */
+#define SDHC_RSPREG1 0x14
+/* ROC Response Register 2 0x0 */
+#define SDHC_RSPREG2 0x18
+/* ROC Response Register 3 0x0 */
+#define SDHC_RSPREG3 0x1C
+#define SDHC_CMD_RESP_MASK 0x00000003
+#define SDHC_CMD_CRC 0x00000008
+#define SDHC_CMD_INDEX 0x00000010
+#define SDHC_CMD_DATA 0x00000020
+#define SDHC_CMD_RESP_NONE 0x00000000
+#define SDHC_CMD_RESP_LONG 0x00000001
+#define SDHC_CMD_RESP_SHORT 0x00000002
+#define SDHC_CMD_RESP_SHORT_BUSY 0x00000003
+#define SDHC_MAKE_CMD(c, f) (((c & 0xFF) << 8) | (f & 0xFF))
+
+/* R/W Buffer Data Register 0x0 */
+#define SDHC_BDATA 0x20
+
+/* R/ROC Present State Register 0x000A0000 */
+#define SDHC_PRNSTS 0x24
+#define SDHC_CMD_INHIBIT 0x00000001
+#define SDHC_DATA_INHIBIT 0x00000002
+#define SDHC_DAT_LINE_ACTIVE 0x00000004
+#define SDHC_DOING_WRITE 0x00000100
+#define SDHC_DOING_READ 0x00000200
+#define SDHC_SPACE_AVAILABLE 0x00000400
+#define SDHC_DATA_AVAILABLE 0x00000800
+#define SDHC_CARD_PRESENT 0x00010000
+#define SDHC_WRITE_PROTECT 0x00080000
+
+/* R/W Host control Register 0x0 */
+#define SDHC_HOSTCTL 0x28
+#define SDHC_CTRL_LED 0x01
+#define SDHC_CTRL_4BITBUS 0x02
+#define SDHC_CTRL_HIGHSPEED 0x04
+#define SDHC_CTRL_1BIT 0x00
+#define SDHC_CTRL_4BIT 0x02
+#define SDHC_CTRL_8BIT 0x20
+#define SDHC_CTRL_DMA_CHECK_MASK 0x18
+#define SDHC_CTRL_SDMA 0x00
+#define SDHC_CTRL_ADMA2_32 0x10
+
+/* R/W Power Control Register 0x0 */
+#define SDHC_PWRCON 0x29
+#define SDHC_POWER_OFF 0x00
+#define SDHC_POWER_ON 0x01
+#define SDHC_POWER_180 0x0A
+#define SDHC_POWER_300 0x0C
+#define SDHC_POWER_330 0x0E
+#define SDHC_POWER_ON_ALL 0xFF
+
+/* R/W Block Gap Control Register 0x0 */
+#define SDHC_BLKGAP 0x2A
+#define SDHC_STOP_AT_GAP_REQ 0x01
+#define SDHC_CONTINUE_REQ 0x02
+
+/* R/W WakeUp Control Register 0x0 */
+#define SDHC_WAKCON 0x2B
+#define SDHC_STAWAKEUP 0x08
+
+/*
+ * CLKCON
+ * SELFREQ[15:8] : base clock divided by value
+ * ENSDCLK[2] : SD Clock Enable
+ * STBLINTCLK[1] : Internal Clock Stable
+ * ENINTCLK[0] : Internal Clock Enable
+ */
+#define SDHC_CLKCON 0x2C
+#define SDHC_DIVIDER_SHIFT 0x0008
+#define SDHC_CLOCK_EXT_STABLE 0x0008
+#define SDHC_CLOCK_CARD_EN 0x0004
+#define SDHC_CLOCK_INT_STABLE 0x0002
+#define SDHC_CLOCK_INT_EN 0x0001
+#define SDHC_CLOCK_CHK_MASK 0x000F
+
+/* R/W Timeout Control Register 0x0 */
+#define SDHC_TIMEOUTCON 0x2E
+#define SDHC_TIMEOUT_MAX 0x0E
+
+/* R/W Software Reset Register 0x0 */
+#define SDHC_SWRST 0x2F
+#define SDHC_RESET_ALL 0x01
+#define SDHC_RESET_CMD 0x02
+#define SDHC_RESET_DATA 0x04
+
+/* ROC/RW1C Normal Interrupt Status Register 0x0 */
+#define SDHC_NORINTSTS 0x30
+#define SDHC_NIS_ERR 0x8000
+#define SDHC_NIS_CMDCMP 0x0001
+#define SDHC_NIS_TRSCMP 0x0002
+#define SDHC_NIS_BLKGAP 0x0004
+#define SDHC_NIS_DMA 0x0008
+#define SDHC_NIS_WBUFRDY 0x0010
+#define SDHC_NIS_RBUFRDY 0x0020
+#define SDHC_NIS_INSERT 0x0040
+#define SDHC_NIS_REMOVE 0x0080
+#define SDHC_NIS_CARDINT 0x0100
+
+/* ROC/RW1C Error Interrupt Status Register 0x0 */
+#define SDHC_ERRINTSTS 0x32
+#define SDHC_EIS_CMDTIMEOUT 0x0001
+#define SDHC_EIS_BLKGAP 0x0004
+#define SDHC_EIS_CMDERR 0x000E
+#define SDHC_EIS_DATATIMEOUT 0x0010
+#define SDHC_EIS_DATAERR 0x0060
+#define SDHC_EIS_CMD12ERR 0x0100
+#define SDHC_EIS_ADMAERR 0x0200
+#define SDHC_EIS_STABOOTACKERR 0x0400
+
+/* R/W Normal Interrupt Status Enable Register 0x0 */
+#define SDHC_NORINTSTSEN 0x34
+#define SDHC_NISEN_CMDCMP 0x0001
+#define SDHC_NISEN_TRSCMP 0x0002
+#define SDHC_NISEN_DMA 0x0008
+#define SDHC_NISEN_WBUFRDY 0x0010
+#define SDHC_NISEN_RBUFRDY 0x0020
+#define SDHC_NISEN_INSERT 0x0040
+#define SDHC_NISEN_REMOVE 0x0080
+#define SDHC_NISEN_CARDINT 0x0100
+
+/* R/W Error Interrupt Status Enable Register 0x0 */
+#define SDHC_ERRINTSTSEN 0x36
+#define SDHC_EISEN_CMDTIMEOUT 0x0001
+#define SDHC_EISEN_BLKGAP 0x0004
+#define SDHC_EISEN_ADMAERR 0x0200
+#define SDHC_EISEN_STABOOTACKERR 0x0400
+
+/* R/W Normal Interrupt Signal Enable Register 0x0 */
+#define SDHC_NORINTSIGEN 0x38
+#define SDHC_NISSIG_MASK_ALL 0x00
+#define SDHC_NISSIG_RESPONSE 0x0001
+#define SDHC_NISSIG_DATA_END 0x0002
+#define SDHC_NISSIG_DMA_END 0x0008
+#define SDHC_NISSIG_SPACE_AVAIL 0x0010
+#define SDHC_NISSIG_DATA_AVAIL 0x0020
+#define SDHC_NISSIG_CARD_INSERT 0x0040
+#define SDHC_NISSIG_CARD_REMOVE 0x0080
+#define SDHC_NISSIG_CARD_CHANGE 0x00C0
+#define SDHC_NISSIG_CARD_INT 0x0100
+
+/* R/W Error Interrupt Signal Enable Register 0x0 */
+#define SDHC_ERRINTSIGEN 0x3A
+#define SDHC_EISSIG_STABOOTACKERR 0x0400
+#define SDHC_EISSIG_ADMAERR 0x0200
+
+/* ROC Auto CMD12 error status register 0x0 */
+#define SDHC_ACMD12ERRSTS 0x3C
+
+/* HWInit Capabilities Register 0x05E80080 */
+#define SDHC_CAPAREG 0x40
+#define SDHC_TIMEOUT_CLK_MASK 0x0000003F
+#define SDHC_TIMEOUT_CLK_SHIFT 0x0
+#define SDHC_TIMEOUT_CLK_UNIT 0x00000080
+#define SDHC_CLOCK_BASE_MASK 0x00003F00
+#define SDHC_CLOCK_BASE_SHIFT 0x8
+#define SDHC_MAX_BLOCK_MASK 0x00030000
+#define SDHC_MAX_BLOCK_SHIFT 0x10
+#define SDHC_CAN_DO_DMA 0x00400000
+#define SDHC_CAN_DO_ADMA2 0x00080000
+#define SDHC_CAN_VDD_330 0x01000000
+#define SDHC_CAN_VDD_300 0x02000000
+#define SDHC_CAN_VDD_180 0x04000000
+
+/* HWInit Maximum Current Capabilities Register 0x0 */
+#define SDHC_MAXCURR 0x48
+
+/* For ADMA2 */
+
+/* W Force Event Auto CMD12 Error Interrupt Register 0x0000 */
+#define SDHC_FEAER 0x50
+/* W Force Event Error Interrupt Register Error Interrupt 0x0000 */
+#define SDHC_FEERR 0x52
+
+/* R/W ADMA Error Status Register 0x00 */
+#define SDHC_ADMAERR 0x54
+#define SDHC_ADMAERR_FINAL_BLOCK (1 << 10)
+#define SDHC_ADMAERR_CONTINUE_REQUEST (1 << 9)
+#define SDHC_ADMAERR_INTRRUPT_STATUS (1 << 8)
+#define SDHC_ADMAERR_LENGTH_MISMATCH (1 << 2)
+#define SDHC_ADMAERR_STATE_ST_STOP (0 << 0)
+#define SDHC_ADMAERR_STATE_ST_FDS (1 << 0)
+#define SDHC_ADMAERR_STATE_ST_TFR (3 << 0)
+#define SDHC_ADMAERR_STATE_MASK (3 << 0)
+
+/* R/W ADMA System Address Register 0x00 */
+#define SDHC_ADMASYSADDR 0x58
+#define SDHC_ADMA_ATTR_MSK 0x3F
+#define SDHC_ADMA_ATTR_ACT_NOP (0 << 4)
+#define SDHC_ADMA_ATTR_ACT_RSV (1 << 4)
+#define SDHC_ADMA_ATTR_ACT_TRAN (1 << 5)
+#define SDHC_ADMA_ATTR_ACT_LINK (3 << 4)
+#define SDHC_ADMA_ATTR_INT (1 << 2)
+#define SDHC_ADMA_ATTR_END (1 << 1)
+#define SDHC_ADMA_ATTR_VALID (1 << 0)
+#define SDHC_ADMA_ATTR_ACT_MASK ((1 << 4)|(1 << 5))
+
+/* R/W Control register 2 0x0 */
+#define SDHC_CONTROL2 0x80
+#define SDHC_HWINITFIN 0x00000001
+#define SDHC_SDOPSIGPC 0x00001000
+#define SDHC_ENCLKOUTHOLD 0x00000100
+#define SDHC_SDINPSIGPC 0x00000008
+#define SDHC_DISBUFRD 0x00000040
+#define SDHC_ENCMDCNFMSK 0x40000000
+
+/* R/W FIFO Interrupt Control (Control Register 3) 0x7F5F3F1F */
+#define SDHC_CONTROL3 0x84
+
+/* R/W Control register 4 0x0 */
+#define SDHC_CONTROL4 0x8C
+
+/* Magic register which is used from kernel! */
+#define SDHC_SLOT_INT_STATUS 0xFC
+
+/* HWInit Host Controller Version Register 0x0401 */
+#define SDHC_HCVER 0xFE
+#define SDHC_VENDOR_VER_MASK 0xFF00
+#define SDHC_VENDOR_VER_SHIFT 0x8
+#define SDHC_SPEC_VER_MASK 0x00FF
+#define SDHC_SPEC_VER_SHIFT 0x0
+
+#define SDHC_REG_SIZE 0x100
+
+enum {
+ not_stoped = 0, /* normal SDHC state */
+ gap_read = 1, /* SDHC stopped at block gap during read operation */
+ gap_write = 2, /* SDHC stopped at block gap during write operation */
+ adma_intr = 3 /* SDHC stopped after ADMA interrupt occurred */
+};
+
+/* SD/MMC host controller state */
+typedef struct Exynos4210SDHCState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ SDState *card;
+
+ QEMUTimer *insert_timer; /* timer for 'changing' sd card. */
+ QEMUTimer *read_buffer_timer; /* read block of data to controller FIFO */
+ QEMUTimer *write_buffer_timer; /* write block of data to card from FIFO */
+ QEMUTimer *transfer_complete_timer; /* raise transfer complete irq */
+ qemu_irq eject;
+ qemu_irq irq;
+
+ uint32_t sdmasysad; /* SDMA System Address register */
+ uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlkSize Reg */
+ uint16_t blkcnt; /* Blocks count for current transfer */
+ uint32_t argument; /* Command Argument Register */
+ uint16_t trnmod; /* Transfer Mode Setting Register */
+ uint16_t cmdreg; /* Command Register */
+ uint32_t rspreg[4]; /* Response Registers 0-3 */
+/* 32 bit bdata Buffer Data Register - access point to R and W buffers */
+ uint32_t prnsts; /* Present State Register */
+ uint8_t hostctl; /* Present State Register */
+ uint8_t pwrcon; /* Present State Register */
+ uint8_t blkgap; /* Block Gap Control Register */
+ uint8_t wakcon; /* WakeUp Control Register */
+ uint16_t clkcon; /* Command Register */
+ uint8_t timeoutcon; /* Timeout Control Register */
+ uint8_t stoped_state; /* Current SDHC state */
+ /* SD host i/o FIFO buffer */
+ uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+ uint16_t data_count; /* current element in FIFO buffer */
+/* 8 bit swrst Software Reset Register - read as 0 */
+ uint16_t norintsts; /* Normal Interrupt Status Register */
+ uint16_t errintsts; /* Error Interrupt Status Register */
+ uint16_t norintstsen; /* Normal Interrupt Status Enable Register */
+ uint16_t errintstsen; /* Error Interrupt Status Enable Register */
+ uint16_t norintsigen; /* Normal Interrupt Signal Enable Register */
+ uint16_t errintsigen; /* Error Interrupt Signal Enable Register */
+ uint16_t acmd12errsts; /* Auto CMD12 error status register */
+ uint32_t capareg; /* Capabilities Register */
+ uint32_t maxcurr; /* Maximum Current Capabilities Register */
+/* 16 bit feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/* 16 bit feerr Force Event Error Interrupt Register- write only */
+ uint32_t admaerr; /* ADMA Error Status Register */
+ uint32_t admasysaddr; /* ADMA System Address Register */
+ uint32_t control2; /* Control Register 2 */
+ uint32_t control3; /* FIFO Interrupt Control (Control Register 3) */
+/* 32 bit control4 makes no sense in emulation so always return 0 */
+/* 32 bit hcver Host Controller Ver Reg 0x2401 -SD specification v2 */
+} Exynos4210SDHCState;
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+ /* free data transfer line */
+ s->prnsts &= ~(SDHC_DOING_READ | SDHC_DOING_WRITE |
+ SDHC_DAT_LINE_ACTIVE | SDHC_DATA_INHIBIT |
+ SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE);
+
+ if (s->norintstsen & SDHC_NISEN_TRSCMP) {
+ s->norintsts |= SDHC_NIS_TRSCMP;
+ }
+
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* raise command response received interrupt */
+static inline void sdhc_raise_response_recieved_irq(Exynos4210SDHCState *s)
+{
+ DPRINTF("raise IRQ response\n");
+
+ if (s->norintstsen & SDHC_NISEN_CMDCMP) {
+ s->norintsts |= SDHC_NIS_CMDCMP;
+ }
+
+ qemu_set_irq(s->irq, ((s->norintsts & s->norintsigen) ||
+ (s->errintsts & s->errintsigen)));
+}
+
+static void sdhc_raise_insertion_irq(void *opaque)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+ DPRINTF("raise IRQ response\n");
+
+ if (s->norintsts & SDHC_NIS_REMOVE) {
+ DPRINTF("sdhc_raise_insertion_irq: set timer!\n");
+ qemu_mod_timer(s->insert_timer,
+ qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+ } else {
+ DPRINTF("sdhc_raise_insertion_irq: raise irq!\n");
+ if (s->norintstsen & SDHC_NIS_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+ }
+}
+
+static void sdhc_insert_eject(void *opaque, int irq, int level)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+ DPRINTF("change card state: %s!\n", level ? "insert" : "eject");
+
+ if (s->norintsts & SDHC_NIS_REMOVE) {
+ if (level) {
+ DPRINTF("change card state: timer set!\n");
+ qemu_mod_timer(s->insert_timer,
+ qemu_get_clock_ns(vm_clock) + INSERTION_DELAY);
+ }
+ } else {
+ if (level) {
+ s->prnsts = 0x1ff0000;
+ if (s->norintstsen & SDHC_NIS_INSERT) {
+ s->norintsts |= SDHC_NIS_INSERT;
+ }
+ } else {
+ s->prnsts = 0x1fa0000;
+ if (s->norintstsen & SDHC_NIS_REMOVE) {
+ s->norintsts |= SDHC_NIS_REMOVE;
+ }
+ }
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+ }
+}
+
+static void exynos4210_sdhc_reset(DeviceState *d)
+{
+ Exynos4210SDHCState *s = container_of(d, Exynos4210SDHCState, busdev.qdev);
+ unsigned long begin = (unsigned long)s + offsetof(Exynos4210SDHCState,
+ sdmasysad);
+ unsigned long len = ((unsigned long)s + offsetof(Exynos4210SDHCState,
+ control3)) - begin;
+
+ /* Set all registers to 0 and then set appropriate values
+ * for hardware-initialize registers */
+ memset((void *)begin, 0, len);
+ (s->card) ? (s->prnsts = 0x1ff0000) : (s->prnsts = 0x1fa0000);
+ s->stoped_state = not_stoped;
+ s->data_count = 0;
+ s->capareg = SDHC_CAPAB_REG_DEFAULT;
+ s->control3 = 0x7F5F3F1F;
+}
+
+static void sdhc_send_command(Exynos4210SDHCState *s)
+{
+ SDRequest request;
+ uint8_t response[16];
+ int rlen;
+ s->errintsts = 0;
+ if (!s->card) {
+ goto error;
+ }
+
+ request.cmd = s->cmdreg >> 8;
+ request.arg = s->argument;
+ DPRINTF("Command %d %08x\n", request.cmd, request.arg);
+ rlen = sd_do_command(s->card, &request, response);
+ if (rlen < 0) {
+ goto error;
+ }
+ if ((s->cmdreg & CMD_RESPONSE) != 0) {
+#define RWORD(n) ((n >= 0 ? (response[n] << 24) : 0) \
+ | (response[n + 1] << 16) \
+ | (response[n + 2] << 8) \
+ | response[n + 3])
+
+ if ((rlen == 0) || (rlen != 4 && rlen != 16)) {
+ goto error;
+ }
+
+ s->rspreg[0] = RWORD(0);
+ if (rlen == 4) {
+ s->rspreg[1] = s->rspreg[2] = s->rspreg[3] = 0;
+ } else {
+ s->rspreg[0] = RWORD(11);
+ s->rspreg[1] = RWORD(7);
+ s->rspreg[2] = RWORD(3);
+ s->rspreg[3] = RWORD(-1);
+ }
+ DPRINTF("Response received\n");
+#undef RWORD
+ } else {
+ DPRINTF("Command sent\n");
+ }
+ return;
+
+error:
+ DPRINTF("Timeout\n");
+ if (s->errintstsen & SDHC_EISEN_CMDTIMEOUT) {
+ s->errintsts |= SDHC_EIS_CMDTIMEOUT;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+}
+
+static void sdhc_do_transfer_complete(Exynos4210SDHCState *s)
+{
+ /* Automatically send CMD12 to stop transfer if AutoCMD12 enabled */
+ if ((s->trnmod & SDHC_TRNS_ACMD12) != 0) {
+ SDRequest request;
+ uint8_t response[16];
+
+ request.cmd = 0x0C;
+ request.arg = 0;
+ DPRINTF("Command Auto%d %08x\n", request.cmd, request.arg);
+ sd_do_command(s->card, &request, response);
+ }
+ /* pend a timer which will raise a transfer complete irq */
+ qemu_mod_timer(s->transfer_complete_timer,
+ qemu_get_clock_ns(vm_clock) + 1);
+}
+
+/*
+ * Programmed i/o data transfer
+ */
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+ uint32_t value = 0;
+ int n = 0, index = 0;
+ uint16_t datacount = s->blksize & 0x0fff;
+
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0)) {
+ return;
+ }
+
+ while (datacount) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ s->fifo_buffer[index] = value;
+ value = 0;
+ n = 0;
+ index++;
+ }
+ datacount--;
+ }
+
+ /* New data now available for READ through Buffer Port Register */
+ s->prnsts |= SDHC_DATA_AVAILABLE;
+ if (s->norintstsen & SDHC_NISEN_RBUFRDY) {
+ s->norintsts |= SDHC_NIS_RBUFRDY;
+ }
+
+ /* Clear DAT line active status if that was the last block */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_MULTI) && s->blkcnt == 1)) {
+ s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+ }
+
+ /* If stop at block gap request was set and it's not the last block of
+ * data - generate Block Event interrupt */
+ if (s->stoped_state == gap_read && (s->trnmod & SDHC_TRNS_MULTI) &&
+ s->blkcnt != 1) {
+ s->prnsts &= ~SDHC_DAT_LINE_ACTIVE;
+ if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+ s->norintsts |= SDHC_EIS_BLKGAP;
+ }
+ }
+
+ qemu_set_irq(s->irq, (s->norintsts & s->norintsigen));
+}
+
+/* Read data from host controller BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(Exynos4210SDHCState *s)
+{
+ /* first check if a valid data exists in host controller input buffer
+ * and that buffer read is not disabled */
+ if ((s->prnsts & SDHC_DATA_AVAILABLE) == 0 ||
+ (s->control2 & SDHC_DISBUFRD)) {
+ DPRINTF("Trying to read from empty buffer or read disabled\n");
+ return 0;
+ }
+
+ uint32_t value = s->fifo_buffer[s->data_count];
+ s->data_count++;
+ /* check if we've read all valid data (blksize bytes) from buffer */
+ if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+ s->prnsts &= ~SDHC_DATA_AVAILABLE; /* no more data in a buffer */
+ s->data_count = 0; /* next buff read must start at position [0] */
+
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ s->blkcnt--;
+ }
+
+ /* if that was the last block of data */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+ sdhc_do_transfer_complete(s);
+ } else if (s->stoped_state == gap_read && /* stop at gap request */
+ !(s->prnsts & SDHC_DAT_LINE_ACTIVE)) {
+ sdhc_raise_transfer_complete_irq(s);
+ } else { /* if there are more data, read next block from card */
+ qemu_mod_timer(s->read_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+ }
+ }
+ return value;
+}
+
+/* Write data from host controller FIFO to card */
+static void sdhc_write_block_to_card(void *opaque)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+ uint32_t value = 0;
+ int n = 0, index = 0;
+ uint16_t datacount = s->blksize & 0x0fff;
+
+ if (s->prnsts & SDHC_SPACE_AVAILABLE) {
+ if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+ s->norintsts |= SDHC_NIS_WBUFRDY;
+ }
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ return;
+ }
+
+ if (s->trnmod & SDHC_TRNS_BLK_CNT_EN) {
+ if (s->blkcnt == 0) {
+ return;
+ } else {
+ s->blkcnt--;
+ }
+ }
+
+ while (datacount) {
+ if (n == 0) {
+ value = s->fifo_buffer[index];
+ n = 4;
+ index++;
+ }
+ sd_write_data(s->card, (uint8_t)(value & 0xff));
+ value >>= 8;
+ n--;
+ datacount--;
+ }
+
+ /* Next data can be written through BUFFER DATORT register */
+ s->prnsts |= SDHC_SPACE_AVAILABLE;
+ if (s->norintstsen & SDHC_NISEN_WBUFRDY) {
+ s->norintsts |= SDHC_NIS_WBUFRDY;
+ }
+
+ /* Finish transfer if that was the last block of data */
+ if ((s->trnmod & SDHC_TRNS_MULTI) == 0 ||
+ ((s->trnmod & SDHC_TRNS_MULTI) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) && (s->blkcnt == 0))) {
+ sdhc_do_transfer_complete(s);
+ }
+
+ /* Generate Block Gap Event if requested and if not the last block */
+ if (s->stoped_state == gap_write && (s->trnmod & SDHC_TRNS_MULTI) &&
+ s->blkcnt > 0) {
+ s->prnsts &= ~SDHC_DOING_WRITE;
+ if (s->norintstsen & SDHC_EISEN_BLKGAP) {
+ s->norintsts |= SDHC_EIS_BLKGAP;
+ }
+ qemu_mod_timer(s->transfer_complete_timer,
+ qemu_get_clock_ns(vm_clock)+1);
+ }
+
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+}
+
+/* Write data to host controller BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(Exynos4210SDHCState *s, uint32_t value)
+{
+ if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+ DPRINTF("exynos4210.sdhc: Trying to write to full buffer\n");
+ return;
+ }
+
+ s->fifo_buffer[s->data_count] = value;
+ s->data_count++;
+
+ if (((s->data_count) * 4) >= (s->blksize & 0x0fff)) {
+ s->data_count = 0;
+ s->prnsts &= ~SDHC_SPACE_AVAILABLE;
+ if (s->prnsts & SDHC_DOING_WRITE) {
+ qemu_mod_timer(s->write_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+ }
+ }
+}
+
+/*
+ * Single DMA data transfer
+ */
+
+/* Multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(Exynos4210SDHCState *s)
+{
+ uint32_t value = 0, datacnt, saved_datacnt, pos = 0;
+ int n = 0, page_aligned = 0;
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ uint32_t dma_buf_boundary = (s->blksize & 0xf000) >> 12;
+ uint32_t boundary_chk = 1 << (dma_buf_boundary+12);
+ uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk);
+
+ /* XXX: Some sd/mmc drivers (for example, u-boot-slp) do not account for
+ * possible stop at page boundary if initial address is not page aligned.
+ * Documentation is vague on how this really behaves in hardware, so allow
+ * them to work properly */
+ if ((s->sdmasysad % boundary_chk) == 0) {
+ page_aligned = 1;
+ }
+
+ if (is_read) {
+ s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ } else {
+ s->prnsts |= SDHC_DOING_WRITE | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ }
+
+ /* Transfer data left from last SDMA transaction loop, after controller
+ * stopped at DMA buffer boundary. This is required only if transfers
+ * are not aligned to DMA buffer boundary */
+ saved_datacnt = s->data_count;
+ while (saved_datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ saved_datacnt--;
+ }
+ if (s->data_count) {
+ s->blkcnt--;
+ boundary_count -= s->data_count--;
+ s->sdmasysad += s->data_count--;
+ s->data_count = 0;
+ }
+
+ n = value = 0;
+ while (s->blkcnt) {
+ if ((boundary_count < (s->blksize & 0x0fff)) && page_aligned) {
+ datacnt = boundary_count;
+ s->data_count = (s->blksize & 0x0fff) - boundary_count;
+ } else {
+ datacnt = s->blksize & 0x0fff;
+ }
+ pos = 0;
+ saved_datacnt = datacnt;
+
+ if (is_read) {
+ while (datacnt) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ datacnt--;
+ }
+ } else {
+ while (datacnt) {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ datacnt--;
+ }
+ }
+
+ s->sdmasysad += saved_datacnt;
+ boundary_count -= saved_datacnt;
+ if (s->data_count == 0) {
+ s->blkcnt--;
+ }
+
+ if (boundary_count == 0) {
+ break;
+ }
+ }
+
+ if (s->blkcnt == 0) {
+ sdhc_do_transfer_complete(s);
+ } else {
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ }
+}
+
+/* single block SDMA transfer */
+static void sdhc_sdma_transfer_single_block(Exynos4210SDHCState *s)
+{
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ int n = 0;
+ uint32_t value = 0, pos = 0;
+ uint32_t datacnt = s->blksize & 0x0fff;
+
+ while (datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(s->sdmasysad + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ datacnt--;
+ }
+
+ if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) != 0) {
+ s->blkcnt--;
+ }
+
+ sdhc_do_transfer_complete(s);
+}
+
+/* Advanced DMA data transfer */
+static void sdhc_run_adma(Exynos4210SDHCState *s)
+{
+ uint32_t value;
+ int n, pos, length;
+ uint32_t address, datacnt;
+ uint16_t length_table;
+ uint8_t attributes;
+
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ s->admaerr &= ~(SDHC_ADMAERR_FINAL_BLOCK | SDHC_ADMAERR_LENGTH_MISMATCH);
+
+ while (1) {
+ address = length_table = length = attributes = 0;
+ value = n = pos = 0;
+
+ /* fetch next entry from descriptor table */
+ cpu_physical_memory_read(s->admasysaddr+4, (uint8_t *)(&address), 4);
+ cpu_physical_memory_read(s->admasysaddr+2,
+ (uint8_t *)(&length_table), 2);
+ cpu_physical_memory_read(s->admasysaddr, (uint8_t *)(&attributes), 1);
+ DPRINTF("ADMA loop: addr=0x%x, len=%d, attr=%x\n", address,
+ length_table, attributes);
+
+ if ((attributes & SDHC_ADMA_ATTR_VALID) == 0) {
+ /* Indicate that error occurred in ST_FDS state */
+ s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+ s->admaerr |= SDHC_ADMAERR_STATE_ST_FDS;
+
+ /* Generate ADMA error interrupt */
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+ break;
+ }
+
+ if (length_table == 0) {
+ length = 65536;
+ } else {
+ length = length_table;
+ }
+
+ address &= 0xfffffffc; /* minimum unit of address is 4 byte */
+
+ switch (attributes & SDHC_ADMA_ATTR_ACT_MASK) {
+ case (SDHC_ADMA_ATTR_ACT_TRAN): /* data transfer */
+ for (;;) {
+ if (s->data_count) {
+ datacnt = s->data_count;
+ s->data_count = 0;
+ } else {
+ datacnt = s->blksize & 0x0fff;
+ }
+
+ length -= datacnt;
+
+ if (length < 0) {
+ s->data_count = (uint16_t)(-length);
+ datacnt += length;
+ }
+
+ while (datacnt) {
+ if (is_read) {
+ value |= (uint32_t)sd_read_data(s->card) << (n * 8);
+ n++;
+ if (n == 4) {
+ cpu_physical_memory_write(address + pos,
+ (uint8_t *)(&value), 4);
+ value = 0;
+ n = 0;
+ pos += 4;
+ }
+ } else {
+ if (n == 0) {
+ cpu_physical_memory_read(address + pos,
+ (uint8_t *)(&value), 4);
+ n = 4;
+ pos += 4;
+ }
+ sd_write_data(s->card, value & 0xff);
+ value >>= 8;
+ n--;
+ }
+ datacnt--;
+ }
+
+ if ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ (!s->data_count)) {
+ s->blkcnt--;
+ if (s->blkcnt == 0) {
+ break;
+ }
+ }
+
+ if (length == 0 || s->data_count) {
+ break;
+ }
+ }
+ s->admasysaddr += 8;
+ break;
+ case (SDHC_ADMA_ATTR_ACT_LINK): /* link to next descriptor table */
+ s->admasysaddr = address;
+ DPRINTF("ADMA link: admasysaddr=0x%x\n", s->admasysaddr);
+ break;
+ default:
+ s->admasysaddr += 8;
+ break;
+ }
+
+ /* ADMA transfer terminates if blkcnt == 0 or by END attribute */
+ if (((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ (s->blkcnt == 0)) || (attributes & SDHC_ADMA_ATTR_END)) {
+ DPRINTF("ADMA transfer completed\n");
+ if (length || ((attributes & SDHC_ADMA_ATTR_END) &&
+ (s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ s->blkcnt != 0) || ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) &&
+ s->blkcnt == 0 && (attributes & SDHC_ADMA_ATTR_END) == 0)) {
+ DPRINTF("SD/MMC host ADMA length mismatch\n");
+ s->admaerr |= SDHC_ADMAERR_LENGTH_MISMATCH |
+ SDHC_ADMAERR_STATE_ST_TFR;
+ if (s->errintstsen & SDHC_EISEN_ADMAERR) {
+ DPRINTF("Set ADMA error flag\n");
+ s->errintsts |= SDHC_EIS_ADMAERR;
+ s->norintsts |= SDHC_NIS_ERR;
+ }
+ qemu_set_irq(s->irq, (s->errintsigen & s->errintsts));
+ }
+
+ s->admaerr |= SDHC_ADMAERR_FINAL_BLOCK;
+ sdhc_do_transfer_complete(s);
+ break;
+ }
+
+ if (attributes & SDHC_ADMA_ATTR_INT) {
+ DPRINTF("ADMA interrupt: admasysaddr=0x%x\n", s->admasysaddr);
+ s->admaerr |= SDHC_ADMAERR_INTRRUPT_STATUS;
+ s->stoped_state = adma_intr;
+ if (s->norintstsen & SDHC_NISEN_DMA) {
+ s->norintsts |= SDHC_NIS_DMA;
+ }
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ break;
+ }
+ }
+}
+
+/* Perform data transfer according to controller configuration */
+static void sdhc_process_command(Exynos4210SDHCState *s)
+{
+ int is_read = (s->trnmod & SDHC_TRNS_READ) != 0;
+ if (s->trnmod & SDHC_TRNS_DMA) {
+ if (!((is_read && sd_data_ready(s->card)) ||
+ (!is_read && sd_receive_ready(s->card)))) {
+ return;
+ }
+
+ switch (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) {
+ case SDHC_CTRL_SDMA:
+ if ((s->trnmod & SDHC_TRNS_MULTI) &&
+ ((s->trnmod & SDHC_TRNS_BLK_CNT_EN) == 0
+ || s->blkcnt == 0)) {
+ break;
+ }
+
+ if ((s->blkcnt == 1) || !(s->trnmod & SDHC_TRNS_MULTI)) {
+ sdhc_sdma_transfer_single_block(s);
+ } else {
+ s->data_count = 0; /* # of bytes from last transfer */
+ sdhc_sdma_transfer_multi_blocks(s);
+ }
+ break;
+ case SDHC_CTRL_ADMA2_32:
+ s->data_count = 0; /* # of bytes from last transfer */
+ sdhc_run_adma(s);
+ break;
+ default:
+ DPRINTF("Unsupported DMA type\n");
+ break;
+ }
+ } else {
+ if (is_read && sd_data_ready(s->card)) {
+ s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
+ SDHC_DAT_LINE_ACTIVE;
+ s->data_count = 0;
+ qemu_mod_timer(s->read_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+ } else if (!is_read && sd_receive_ready(s->card)) {
+ s->prnsts |= SDHC_DOING_WRITE | SDHC_DAT_LINE_ACTIVE |
+ SDHC_SPACE_AVAILABLE | SDHC_DATA_INHIBIT;
+ s->data_count = 0;
+ qemu_mod_timer(s->write_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + WRITE_BUFFER_DELAY);
+ }
+ }
+}
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_1(Exynos4210SDHCState *s,
+ target_phys_addr_t offset)
+{
+ switch (offset) {
+ case SDHC_HOSTCTL:
+ return s->hostctl;
+ case SDHC_PWRCON:
+ return s->pwrcon;
+ case SDHC_BLKGAP:
+ return s->blkgap;
+ case SDHC_WAKCON:
+ return s->wakcon;
+ case SDHC_TIMEOUTCON:
+ return s->timeoutcon;
+ case SDHC_SWRST:
+ return 0;
+ case SDHC_RSPREG0 + 0x3:
+ return (uint8_t)(s->rspreg[0] >> 24);
+ case SDHC_RSPREG1 + 0x3:
+ return (uint8_t)(s->rspreg[1] >> 24);
+ case SDHC_RSPREG2 + 0x3:
+ return (uint8_t)(s->rspreg[2] >> 24);
+ default:
+ DPRINTF("bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t exynos4210_sdhc_read_2(Exynos4210SDHCState *s,
+ target_phys_addr_t offset)
+{
+ switch (offset) {
+ case SDHC_BLKSIZE:
+ return s->blksize;
+ case SDHC_BLKCNT:
+ return s->blkcnt;
+ case SDHC_TRNMOD:
+ return s->trnmod;
+ case SDHC_CMDREG:
+ return s->cmdreg;
+ case SDHC_CLKCON:
+ return s->clkcon;
+ case SDHC_NORINTSTS:
+ qemu_set_irq(s->irq, 0);
+ return s->norintsts;
+ case SDHC_ERRINTSTS:
+ qemu_set_irq(s->irq, 0);
+ return s->errintsts;
+ case SDHC_NORINTSTSEN:
+ return s->norintstsen;
+ case SDHC_ERRINTSTSEN:
+ return s->errintstsen;
+ case SDHC_NORINTSIGEN:
+ return s->norintsigen;
+ case SDHC_ERRINTSIGEN:
+ return s->errintsigen;
+ case SDHC_ACMD12ERRSTS:
+ return s->acmd12errsts;
+ case SDHC_SLOT_INT_STATUS:
+ return 0;
+ case SDHC_FEAER: case SDHC_FEERR:
+ DPRINTF("Reading from WO register\n");
+ return 0;
+ case SDHC_HCVER:
+ return 0x2401; /* SD Host Specification Version 2.0 */
+ default:
+ DPRINTF("bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t exynos4210_sdhc_read_4(Exynos4210SDHCState *s,
+ target_phys_addr_t offset)
+{
+ switch (offset) {
+ case SDHC_SYSAD:
+ return s->sdmasysad;
+ case SDHC_ARGUMENT:
+ return s->argument;
+ case SDHC_RSPREG0:
+ return s->rspreg[0];
+ case SDHC_RSPREG1:
+ return s->rspreg[1];
+ case SDHC_RSPREG2:
+ return s->rspreg[2];
+ case SDHC_RSPREG3:
+ return s->rspreg[3];
+ case SDHC_BDATA:
+ return sdhc_read_dataport(s);
+ case SDHC_PRNSTS:
+ return s->prnsts;
+ case SDHC_NORINTSTS:
+ qemu_set_irq(s->irq, 0);
+ return (s->errintsts << 16) | (s->norintsts);
+ case SDHC_NORINTSTSEN:
+ return (s->errintstsen << 16) | s->norintstsen;
+ case SDHC_NORINTSIGEN:
+ return (s->errintsigen << 16) | s->norintsigen;
+ case SDHC_CAPAREG:
+ return s->capareg;
+ case SDHC_MAXCURR:
+ return s->maxcurr;
+ case SDHC_ADMAERR:
+ return s->admaerr;
+ case SDHC_ADMASYSADDR:
+ return s->admasysaddr;
+ case SDHC_CONTROL2:
+ return s->control2;
+ case SDHC_CONTROL3:
+ return s->control3;
+ case SDHC_CONTROL4:
+ return 0; /* makes no sense in emulation so return 0 */
+ default:
+ DPRINTF("bad 4 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* MMC write (byte) function */
+static void
+exynos4210_sdhc_write_1(Exynos4210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_HOSTCTL:
+ s->hostctl = value & 0x3F;
+ break;
+ case SDHC_PWRCON:
+ s->pwrcon = value & 0x0F;
+ break;
+ case SDHC_BLKGAP:
+ if (value & 0x0C) {
+ error_report("SDHC: ReadWait & IntAtBlockGap not implemented\n");
+ }
+
+ if ((value & SDHC_STOP_AT_GAP_REQ) &&
+ (s->blkgap & SDHC_STOP_AT_GAP_REQ)) {
+ break;
+ }
+ s->blkgap = value & (SDHC_STOP_AT_GAP_REQ);
+
+ if ((value & SDHC_CONTINUE_REQ) && s->stoped_state &&
+ (s->blkgap & SDHC_STOP_AT_GAP_REQ) == 0) {
+ if (s->stoped_state == gap_read) {
+ s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_READ;
+ qemu_mod_timer(s->read_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+ } else {
+ s->prnsts |= SDHC_DAT_LINE_ACTIVE | SDHC_DOING_WRITE;
+ qemu_mod_timer(s->write_buffer_timer,
+ qemu_get_clock_ns(vm_clock) + READ_BUFFER_DELAY);
+ }
+ s->stoped_state = not_stoped;
+ } else if (!s->stoped_state && (value & SDHC_STOP_AT_GAP_REQ)) {
+ if (s->prnsts & SDHC_DOING_READ) {
+ s->stoped_state = gap_read;
+ } else if (s->prnsts & SDHC_DOING_WRITE) {
+ s->stoped_state = gap_write;
+ }
+ }
+ break;
+ case SDHC_WAKCON:
+ s->wakcon = value & 0x07;
+ s->wakcon &= ~(value & SDHC_STAWAKEUP);
+ break;
+ case SDHC_TIMEOUTCON:
+ s->timeoutcon = value & 0x0F;
+ break;
+ case SDHC_SWRST:
+ switch (value) {
+ case SDHC_RESET_ALL:
+ exynos4210_sdhc_reset(&s->busdev.qdev);
+ break;
+ case SDHC_RESET_CMD:
+ s->prnsts &= ~SDHC_CMD_INHIBIT;
+ s->norintsts &= ~SDHC_NIS_CMDCMP;
+ break;
+ case SDHC_RESET_DATA:
+ s->data_count = 0;
+ qemu_del_timer(s->read_buffer_timer);
+ qemu_del_timer(s->write_buffer_timer);
+ s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
+ SDHC_DOING_READ | SDHC_DOING_WRITE |
+ SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
+ s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ);
+ s->stoped_state = not_stoped;
+ s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
+ SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+ break;
+ }
+ break;
+ case (SDHC_NORINTSTS+1):
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~1;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (~((value << 8) & 0xFF00));
+ break;
+ case (SDHC_NORINTSTS):
+ s->norintsts &= ~(((uint16_t)value) & 0x00FF);
+ break;
+ default:
+ DPRINTF("bad 1 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+/* MMC write 2 bytes function */
+static void
+exynos4210_sdhc_write_2(Exynos4210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_BLKSIZE:
+ if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+ s->blksize = value & 0x7FFF;
+ }
+ break;
+ case SDHC_BLKCNT:
+ if (!(s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))) {
+ s->blkcnt = value;
+ }
+ break;
+ case SDHC_TRNMOD:
+ if (value & (SDHC_TRNS_BOOTCMD | SDHC_TRNS_BOOTACK |
+ SDHC_TRNS_CEATA)) {
+ error_report("QEMU SDHC: CEATA mode not implemented\n");
+ }
+ if ((s->capareg & SDHC_CAN_DO_DMA) == 0) {
+ value &= ~SDHC_TRNS_DMA;
+ }
+ s->trnmod = value & 0x0037;
+ break;
+ case SDHC_CMDREG: /* Command */
+ s->cmdreg = value & 0x3FFB;
+
+ if (((s->control2 & SDHC_SDOPSIGPC) || (s->control2 & SDHC_SDINPSIGPC))
+ && !(s->pwrcon & SDHC_POWER_ON)) {
+ DPRINTF("Can't issue command with power off\n");
+ break;
+ }
+
+ if ((s->clkcon & SDHC_CLOCK_CHK_MASK) != SDHC_CLOCK_CHK_MASK) {
+ DPRINTF("Can't issue command with clock off\n");
+ break;
+ }
+
+ if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_RESUME_MASK ||
+ (value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_SUSPEND_MASK) {
+ DPRINTF("sdhc: suspend/resume commands not implemented\n");
+ break;
+ }
+
+ if ((s->stoped_state || (s->prnsts & SDHC_DATA_INHIBIT)) &&
+ ((value & SDHC_CMD_DATA_PRESENT) || ((value & SDHC_CMD_RSP_WITH_BUSY) &&
+ !(value & SDHC_CMD_ABORT_MASK)))) {
+ DPRINTF("Can't issue command which uses DAT line\n");
+ break;
+ }
+
+ if ((value & SDHC_CMD_TYPE_MASK) == SDHC_CMD_ABORT_MASK &&
+ ((s->prnsts & SDHC_DOING_READ) || (s->prnsts & SDHC_DOING_WRITE))) {
+ DPRINTF("ABORT command\n");
+ qemu_del_timer(s->read_buffer_timer); /* stop reading data */
+ qemu_del_timer(s->write_buffer_timer); /* stop writing data */
+ qemu_mod_timer(s->transfer_complete_timer,
+ qemu_get_clock_ns(vm_clock) + 1);
+ }
+
+ sdhc_send_command(s);
+ sdhc_raise_response_recieved_irq(s);
+
+ if (s->blksize == 0) {
+ break;
+ }
+ sdhc_process_command(s);
+ break;
+ case SDHC_CLKCON:
+ s->clkcon = value;
+ if (SDHC_CLOCK_INT_EN & s->clkcon) {
+ s->clkcon |= SDHC_CLOCK_INT_STABLE;
+ } else {
+ s->clkcon &= ~SDHC_CLOCK_INT_STABLE;
+ }
+ if (SDHC_CLOCK_CARD_EN & s->clkcon) {
+ s->clkcon |= SDHC_CLOCK_EXT_STABLE;
+ } else {
+ s->clkcon &= ~SDHC_CLOCK_EXT_STABLE;
+ }
+ break;
+ case SDHC_NORINTSTS:
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~SDHC_NIS_CARDINT;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+ break;
+ case SDHC_ERRINTSTS:
+ s->errintsts &= ~((value & 0x077F) | 0xF880);
+ s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+ (s->norintsts &= ~SDHC_NIS_ERR);
+ break;
+ case SDHC_NORINTSTSEN:
+ s->norintstsen = value & 0x7FFF;
+ break;
+ case SDHC_ERRINTSTSEN:
+ s->errintstsen = value & 0x07FF;
+ break;
+ case SDHC_NORINTSIGEN:
+ s->norintsigen = value & 0x7FFF;
+ break;
+ case SDHC_ERRINTSIGEN:
+ s->errintsigen = value & 0x07FF;
+ break;
+ case SDHC_ACMD12ERRSTS: case SDHC_FEAER: case SDHC_HCVER:
+ break;
+ case SDHC_FEERR:
+ s->norintsts |= (value & 0x073F) & s->norintstsen;
+ qemu_set_irq(s->irq, (s->norintsigen & s->norintsts));
+ break;
+ default:
+ DPRINTF("bad 2 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+/* MMC write 4 bytes function */
+static void
+exynos4210_sdhc_write_4(Exynos4210SDHCState *s, target_phys_addr_t offset,
+ uint32_t value)
+{
+ switch (offset) {
+ case SDHC_SYSAD:
+ s->sdmasysad = value;
+ if ((s->prnsts & (SDHC_DOING_READ | SDHC_DOING_WRITE))
+ && (s->blkcnt != 0) && (s->blksize != 0) &&
+ (s->hostctl & SDHC_CTRL_DMA_CHECK_MASK) == SDHC_CTRL_SDMA) {
+ sdhc_sdma_transfer_multi_blocks(s);
+ }
+ break;
+ case SDHC_ARGUMENT:
+ s->argument = value;
+ break;
+ case SDHC_BDATA:
+ sdhc_write_dataport(s, value);
+ break;
+ case SDHC_NORINTSTS:
+ if (s->norintstsen & SDHC_NISEN_CARDINT) {
+ value &= ~SDHC_NIS_CARDINT;
+ }
+ s->norintsts &= (s->norintsts & 0x8000) | (uint16_t)(~(value & 0xFFFF));
+
+ s->errintsts &= ~(((value >> 16) & 0x077F) | 0xF880);
+ s->errintsts ? (s->norintsts |= SDHC_NIS_ERR) :
+ (s->norintsts &= ~SDHC_NIS_ERR);
+ break;
+ case SDHC_NORINTSTSEN:
+ s->norintstsen = (uint16_t)(value & 0x7FFF);
+ s->errintstsen = (uint16_t)((value >> 16) & 0x07FF);
+ break;
+ case SDHC_NORINTSIGEN:
+ s->norintsigen = (uint16_t)(value & 0x7FFF);
+ s->errintsigen = (uint16_t)((value >> 16) & 0x07FF);
+ break;
+ case SDHC_CAPAREG:
+ if (!(s->control2 & SDHC_HWINITFIN)) {
+ s->capareg = value & 0x07EB3FBF;
+ }
+ break;
+ case SDHC_MAXCURR:
+ if (!(s->control2 & SDHC_HWINITFIN)) {
+ s->maxcurr = value & 0x00FFFFFF;
+ }
+ break;
+ case SDHC_ADMAERR:
+ s->admaerr = ((s->admaerr & SDHC_ADMAERR_FINAL_BLOCK) |
+ (value & 0x00000007)) & 0x00000507;
+ s->admaerr &= ~(value & SDHC_ADMAERR_INTRRUPT_STATUS);
+ if (value & SDHC_ADMAERR_CONTINUE_REQUEST) {
+ s->stoped_state = not_stoped;
+ sdhc_run_adma(s);
+ }
+ break;
+ case SDHC_ADMASYSADDR:
+ s->admasysaddr = value;
+ break;
+ case SDHC_CONTROL2:
+ s->control2 = value & 0xDF00DFFB;
+ break;
+ case SDHC_CONTROL3:
+ s->control3 = value;
+ break;
+ case SDHC_RSPREG0: case SDHC_RSPREG1: case SDHC_RSPREG2:
+ case SDHC_RSPREG3: case SDHC_PRNSTS: case SDHC_CONTROL4:
+ /* Nothing for emulation */
+ break;
+ default:
+ DPRINTF("bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static uint64_t
+exynos4210_sdhc_read(void *opaque, target_phys_addr_t offset, unsigned size)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ return exynos4210_sdhc_read_1(s, offset);
+ case (2):
+ return exynos4210_sdhc_read_2(s, offset);
+ case (4):
+ return exynos4210_sdhc_read_4(s, offset);
+ }
+ return 0xBAADBAADull;
+}
+
+static void
+exynos4210_sdhc_write(void *opaque, target_phys_addr_t offset, uint64_t val,
+ unsigned size)
+{
+ Exynos4210SDHCState *s = (Exynos4210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ exynos4210_sdhc_write_1(s, offset, (uint32_t)val);
+ break;
+ case (2):
+ exynos4210_sdhc_write_2(s, offset, (uint32_t)val);
+ break;
+ case (4):
+ exynos4210_sdhc_write_4(s, offset, (uint32_t)val);
+ break;
+ }
+}
+
+static const MemoryRegionOps exynos4210_sdhc_mmio_ops = {
+ .read = exynos4210_sdhc_read,
+ .write = exynos4210_sdhc_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription exynos4210_sdhc_vmstate = {
+ .name = "exynos4210.sdhc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(stoped_state, Exynos4210SDHCState),
+ VMSTATE_UINT32_ARRAY(fifo_buffer, Exynos4210SDHCState,
+ (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+ VMSTATE_UINT16(data_count, Exynos4210SDHCState),
+ VMSTATE_UINT32(sdmasysad, Exynos4210SDHCState),
+ VMSTATE_UINT16(blksize, Exynos4210SDHCState),
+ VMSTATE_UINT16(blkcnt, Exynos4210SDHCState),
+ VMSTATE_UINT32(argument, Exynos4210SDHCState),
+ VMSTATE_UINT16(trnmod, Exynos4210SDHCState),
+ VMSTATE_UINT16(cmdreg, Exynos4210SDHCState),
+ VMSTATE_UINT32_ARRAY(rspreg, Exynos4210SDHCState, 4),
+ VMSTATE_UINT32(prnsts, Exynos4210SDHCState),
+ VMSTATE_UINT8(hostctl, Exynos4210SDHCState),
+ VMSTATE_UINT8(pwrcon, Exynos4210SDHCState),
+ VMSTATE_UINT8(blkgap, Exynos4210SDHCState),
+ VMSTATE_UINT8(wakcon, Exynos4210SDHCState),
+ VMSTATE_UINT16(clkcon, Exynos4210SDHCState),
+ VMSTATE_UINT8(timeoutcon, Exynos4210SDHCState),
+ VMSTATE_UINT16(norintsts, Exynos4210SDHCState),
+ VMSTATE_UINT16(errintsts, Exynos4210SDHCState),
+ VMSTATE_UINT16(norintstsen, Exynos4210SDHCState),
+ VMSTATE_UINT16(errintstsen, Exynos4210SDHCState),
+ VMSTATE_UINT16(norintsigen, Exynos4210SDHCState),
+ VMSTATE_UINT16(errintsigen, Exynos4210SDHCState),
+ VMSTATE_UINT16(acmd12errsts, Exynos4210SDHCState),
+ VMSTATE_UINT32(capareg, Exynos4210SDHCState),
+ VMSTATE_UINT32(maxcurr, Exynos4210SDHCState),
+ VMSTATE_UINT32(admaerr, Exynos4210SDHCState),
+ VMSTATE_UINT32(admasysaddr, Exynos4210SDHCState),
+ VMSTATE_UINT32(control2, Exynos4210SDHCState),
+ VMSTATE_UINT32(control3, Exynos4210SDHCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int exynos4210_sdhc_init(SysBusDevice *dev)
+{
+ Exynos4210SDHCState *s = FROM_SYSBUS(Exynos4210SDHCState, dev);
+ DriveInfo *bd;
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->iomem, &exynos4210_sdhc_mmio_ops, s,
+ "exynos4210.sdhc", SDHC_REG_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ bd = drive_get_next(IF_SD);
+
+ if ((bd == NULL)) {
+ s->card = NULL;
+ DPRINTF("s->card = NULL\n");
+ } else {
+ s->eject = qemu_allocate_irqs(sdhc_insert_eject, s, 1)[0];
+ DPRINTF("name = %s, sectors = %ld\n",
+ bd->bdrv->device_name, bd->bdrv->total_sectors);
+ s->card = sd_init(bd->bdrv, 0);
+ sd_set_cb(s->card, NULL, s->eject);
+ }
+
+ s->insert_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_insertion_irq, s);
+
+ s->read_buffer_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_read_block_from_card, s);
+
+ s->write_buffer_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_write_block_to_card, s);
+
+ s->transfer_complete_timer =
+ qemu_new_timer(vm_clock, SCALE_NS, sdhc_raise_transfer_complete_irq, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo exynos4210_sdhc_info = {
+ .init = exynos4210_sdhc_init,
+ .qdev.name = "exynos4210.sdhc",
+ .qdev.size = sizeof(Exynos4210SDHCState),
+ .qdev.vmsd = &exynos4210_sdhc_vmstate,
+ .qdev.reset = exynos4210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+ sysbus_register_withprop(&exynos4210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
` (13 preceding siblings ...)
2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
@ 2011-12-09 13:34 ` Evgeny Voevodin
14 siblings, 0 replies; 17+ messages in thread
From: Evgeny Voevodin @ 2011-12-09 13:34 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, Mitsyanko Igor, d.solodkiy, Evgeny Voevodin
From: Mitsyanko Igor <i.mitsyanko@samsung.com>
Exynos4210 display controller (FIMD) supports up to 5 hardware windows,
along with alpha and chromakey blending capabilities.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/exynos4210.c | 11 +
hw/exynos4210_fimd.c | 1737 ++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1749 insertions(+), 1 deletions(-)
create mode 100644 hw/exynos4210_fimd.c
diff --git a/Makefile.target b/Makefile.target
index a5d67c9..b4cd185 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -346,7 +346,7 @@ 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_pwm.o exynos4210_mct.o \
- exynos4210_sdhc.o
+ exynos4210_sdhc.o exynos4210_fimd.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 62c90e9..2f3a5be 100644
--- a/hw/exynos4210.c
+++ b/hw/exynos4210.c
@@ -101,6 +101,10 @@
#define EXYNOS4210_SDHC2_BASE_ADDR 0x12530000
#define EXYNOS4210_SDHC3_BASE_ADDR 0x12540000
+/* Display controllers (FIMD) */
+#define EXYNOS4210_FIMD0_BASE_ADDR 0x11C00000
+#define EXYNOS4210_FIMD1_BASE_ADDR 0x12000000
+
/* Secondary CPU polling address to get loader start from */
#define EXYNOS4210_SECOND_CPU_BOOTREG 0x10020814
@@ -483,6 +487,13 @@ static void exynos4210_init(ram_addr_t ram_size,
}
}
+ /*** Display controller (FIMD) ***/
+ sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
+ irq_table[exynos4210_get_irq(11, 0)],
+ irq_table[exynos4210_get_irq(11, 1)],
+ irq_table[exynos4210_get_irq(11, 2)],
+ NULL);
+
/*** Load kernel ***/
exynos4210_binfo.ram_size = ram_size;
diff --git a/hw/exynos4210_fimd.c b/hw/exynos4210_fimd.c
new file mode 100644
index 0000000..dc26b8e
--- /dev/null
+++ b/hw/exynos4210_fimd.c
@@ -0,0 +1,1737 @@
+/*
+ * Samsung exynos4210 Display Controller (FIMD)
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ * Based on LCD controller for Samsung S5PC1xx-based board emulation
+ * by Kirill Batuzov <batuzovk@ispras.ru>
+ *
+ * Contributed by Mitsyanko Igor <i.mitsyanko@samsung.com>
+ *
+ * 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 "qemu-common.h"
+#include "cpu-all.h"
+#include "sysbus.h"
+#include "console.h"
+#include "pixel_ops.h"
+
+/* Debug messages configuration */
+#define EXY_FIMD_DEBUG 0
+#define EXY_FIMD_MODE_TRACE 0
+
+#if EXY_FIMD_DEBUG == 0
+ #define DPRINT_L1(fmt, args...) do { } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define DPRINT_ERROR(fmt, args...) do { } while (0)
+#elif EXY_FIMD_DEBUG == 1
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) do { } while (0)
+ #define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+ #define DPRINT_L1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_L2(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define DPRINT_ERROR(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#endif
+
+#if EXY_FIMD_MODE_TRACE == 0
+ #define DPRINT_TRACE(fmt, args...) do { } while (0)
+#else
+ #define DPRINT_TRACE(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+#endif
+
+#define NUM_OF_WINDOWS 5
+#define FIMD_REGS_SIZE 0x4114
+
+/* Video main control registers */
+#define FIMD_VIDCON0 0x0000
+#define FIMD_VIDCON1 0x0004
+#define FIMD_VIDCON2 0x0008
+#define FIMD_VIDCON3 0x000C
+#define FIMD_VIDCON0_ENVID_F (1 << 0)
+#define FIMD_VIDCON0_ENVID (1 << 1)
+#define FIMD_VIDCON0_ENVID_MASK ((1 << 0) | (1 << 1))
+#define FIMD_VIDCON1_ROMASK 0x07FFE000
+
+/* Video time control registers */
+#define FIMD_VIDTCON_START 0x10
+#define FIMD_VIDTCON_END 0x1C
+#define FIMD_VIDTCON2_SIZE_MASK 0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT 0
+#define FIMD_VIDTCON2_VER_SHIFT 11
+
+/* Window control registers */
+#define FIMD_WINCON_START 0x0020
+#define FIMD_WINCON_END 0x0030
+#define FIMD_WINCON_ROMASK 0x82200000
+#define FIMD_WINCON_ENWIN (1 << 0)
+#define FIMD_WINCON_BLD_PIX (1 << 6)
+#define FIMD_WINCON_ALPHA_MUL (1 << 7)
+#define FIMD_WINCON_ALPHA_SEL (1 << 1)
+#define FIMD_WINCON_SWAP 0x078000
+#define FIMD_WINCON_SWAP_SHIFT 15
+#define FIMD_WINCON_SWAP_WORD 0x1
+#define FIMD_WINCON_SWAP_HWORD 0x2
+#define FIMD_WINCON_SWAP_BYTE 0x4
+#define FIMD_WINCON_SWAP_BITS 0x8
+#define FIMD_WINCON_BUFSTATUS_L (1 << 21)
+#define FIMD_WINCON_BUFSTATUS_H (1 << 31)
+#define FIMD_WINCON_BUFSTATUS ((1 << 21) | (1 << 31))
+#define FIMD_WINCON_BUF0_STAT ((0 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF1_STAT ((1 << 21) | (0 << 31))
+#define FIMD_WINCON_BUF2_STAT ((0 << 21) | (1 << 31))
+#define FIMD_WINCON_BUFSELECT ((1 << 20) | (1 << 30))
+#define FIMD_WINCON_BUF0_SEL ((0 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF1_SEL ((1 << 20) | (0 << 30))
+#define FIMD_WINCON_BUF2_SEL ((0 << 20) | (1 << 30))
+#define FIMD_WINCON_BUFMODE (1 << 14)
+#define IS_PALETTIZED_MODE(w) (w->wincon & 0xC)
+#define WIN_BPP_MODE(w) ((w->wincon >> 2) & 0xF)
+
+/* Shadow control register */
+#define FIMD_SHADOWCON 0x0034
+#define FIMD_WIN_NOT_PROTECTED(s, w) (!(s->shadowcon & (1 << (10 + (w)))))
+/* Channel mapping control register */
+#define FIMD_WINCHMAP 0x003C
+
+/* Window position control registers */
+#define FIMD_VIDOSD_START 0x0040
+#define FIMD_VIDOSD_END 0x0088
+#define FIMD_VIDOSD_COORD_MASK 0x07FF
+#define FIMD_VIDOSD_HOR_SHIFT 11
+#define FIMD_VIDOSD_VER_SHIFT 0
+#define FIMD_VIDOSD_ALPHA_AEN0 0xFFF000
+#define FIMD_VIDOSD_AEN0_SHIFT 12
+#define FIMD_VIDOSD_ALPHA_AEN1 0x000FFF
+
+/* Frame buffer address registers */
+#define FIMD_VIDWADD0_START 0x0A0
+#define FIMD_VIDWADD0_END 0x0C4
+#define FIMD_VIDWADD1_START 0x0D0
+#define FIMD_VIDWADD1_END 0x0F4
+#define FIMD_VIDWADD2_START 0x100
+#define FIMD_VIDWADD2_END 0x110
+#define FIMD_VIDWADD2_PAGEWIDTH 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+
+/* Video interrupt control registers */
+#define FIMD_VIDINTCON0 0x130
+#define FIMD_VIDINTCON1 0x134
+
+/* Window color key registers */
+#define FIMD_WKEYCON_START 0x140
+#define FIMD_WKEYCON_END 0x15C
+#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT 24
+#define FIMD_WKEYCON0_DIRCON (1 << 24)
+#define FIMD_WKEYCON0_KEYEN (1 << 25)
+#define FIMD_WKEYCON0_KEYBLEN (1 << 26)
+/* Window color key alpha control register */
+#define FIMD_WKEYALPHA_START 0x160
+#define FIMD_WKEYALPHA_END 0x16C
+
+/* Dithering control register */
+#define FIMD_DITHMODE 0x170
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_LOWER 0x000F0F0F
+#define FIMD_VIDALPHA_ALPHA_UPPER 0x00F0F0F0
+#define FIMD_VIDWALPHA_START 0x21C
+#define FIMD_VIDWALPHA_END 0x240
+
+/* Window color map registers */
+#define FIMD_WINMAP_START 0x180
+#define FIMD_WINMAP_END 0x190
+#define FIMD_WINMAP_EN (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF
+
+/* Window palette control registers */
+#define FIMD_WPALCON_HIGH 0x019C
+#define FIMD_WPALCON_LOW 0x01A0
+#define FIMD_WPALCON_UPDATEEN (1 << 9)
+#define FIMD_WPAL_W0PAL_L 0x07
+#define FIMD_WPAL_W0PAL_L_SHT 0
+#define FIMD_WPAL_W1PAL_L 0x07
+#define FIMD_WPAL_W1PAL_L_SHT 3
+#define FIMD_WPAL_W2PAL_L 0x01
+#define FIMD_WPAL_W2PAL_L_SHT 6
+#define FIMD_WPAL_W2PAL_H 0x06
+#define FIMD_WPAL_W2PAL_H_SHT 8
+#define FIMD_WPAL_W3PAL_L 0x01
+#define FIMD_WPAL_W3PAL_L_SHT 7
+#define FIMD_WPAL_W3PAL_H 0x06
+#define FIMD_WPAL_W3PAL_H_SHT 12
+#define FIMD_WPAL_W4PAL_L 0x01
+#define FIMD_WPAL_W4PAL_L_SHT 8
+#define FIMD_WPAL_W4PAL_H 0x06
+#define FIMD_WPAL_W4PAL_H_SHT 16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON 0x01A4
+#define FIMD_TRIGCON_ROMASK 0x00000004
+
+/* LCD I80 Interface Control */
+#define FIMD_I80IFCON_START 0x01B0
+#define FIMD_I80IFCON_END 0x01BC
+/* Color gain control register */
+#define FIMD_COLORGAINCON 0x01C0
+/* LCD i80 Interface Command Control */
+#define FIMD_LDI_CMDCON0 0x01D0
+#define FIMD_LDI_CMDCON1 0x01D4
+/* I80 System Interface Manual Command Control */
+#define FIMD_SIFCCON0 0x01E0
+#define FIMD_SIFCCON2 0x01E8
+
+/* Hue Control Registers */
+#define FIMD_HUECOEFCR_START 0x01EC
+#define FIMD_HUECOEFCR_END 0x01F4
+#define FIMD_HUECOEFCB_START 0x01FC
+#define FIMD_HUECOEFCB_END 0x0208
+#define FIMD_HUEOFFSET 0x020C
+
+/* Video interrupt control registers */
+#define FIMD_VIDINT_INTFIFOPEND (1 << 0)
+#define FIMD_VIDINT_INTFRMPEND (1 << 1)
+#define FIMD_VIDINT_INTI80PEND (1 << 2)
+#define FIMD_VIDINT_INTEN (1 << 0)
+#define FIMD_VIDINT_INTFIFOEN (1 << 1)
+#define FIMD_VIDINT_INTFRMEN (1 << 12)
+#define FIMD_VIDINT_I80IFDONE (1 << 17)
+
+/* Window blend equation control registers */
+#define FIMD_BLENDEQ_START 0x0244
+#define FIMD_BLENDEQ_END 0x0250
+#define FIMD_BLENDCON 0x0260
+#define FIMD_ALPHA_8BIT (1 << 0)
+#define FIMD_BLENDEQ_COEF_MASK 0xF
+
+/* Window RTQOS Control Registers */
+#define FIMD_WRTQOSCON_START 0x0264
+#define FIMD_WRTQOSCON_END 0x0274
+
+/* LCD I80 Interface Command */
+#define FIMD_I80IFCMD_START 0x0280
+#define FIMD_I80IFCMD_END 0x02AC
+
+/* Shadow windows control registers */
+#define FIMD_SHD_ADD0_START 0x40A0
+#define FIMD_SHD_ADD0_END 0x40C0
+#define FIMD_SHD_ADD1_START 0x40D0
+#define FIMD_SHD_ADD1_END 0x40F0
+#define FIMD_SHD_ADD2_START 0x4100
+#define FIMD_SHD_ADD2_END 0x4110
+
+/* Palette memory */
+#define FIMD_PAL_MEM_START 0x2400
+#define FIMD_PAL_MEM_END 0x37FC
+/* Palette memory aliases for windows 0 and 1 */
+#define FIMD_PALMEM_AL_START 0x0400
+#define FIMD_PALMEM_AL_END 0x0BFC
+
+typedef struct {
+ uint8_t r, g, b;
+ /* D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+ uint32_t a;
+} rgba;
+#define RGBA_SIZE 7
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef struct Exynos4210fimdWindow Exynos4210fimdWindow;
+
+struct Exynos4210fimdWindow {
+ uint32_t wincon; /* Window control register */
+ uint32_t buf_start[3]; /* Start address for video frame buffer */
+ uint32_t buf_end[3]; /* End address for video frame buffer */
+ uint32_t keycon[2]; /* Window color key registers */
+ uint32_t keyalpha; /* Color key alpha control register */
+ uint32_t winmap; /* Window color map register */
+ uint32_t blendeq; /* Window blending equation control register */
+ uint32_t rtqoscon; /* Window RTQOS Control Registers */
+ uint32_t palette[256]; /* Palette RAM */
+ uint32_t shadow_buf_start; /* Start address of shadow frame buffer */
+ uint32_t shadow_buf_end; /* End address of shadow frame buffer */
+ uint32_t shadow_buf_size; /* Virtual shadow screen width */
+
+ pixel_to_rgb_func *pixel_to_rgb;
+ void (*draw_line)(Exynos4210fimdWindow *w, uint8_t *src, uint8_t *dst,
+ Exynos4210fimdWindow *w_blend);
+ uint16_t lefttop_x, lefttop_y; /* VIDOSD0 register */
+ uint16_t rightbot_x, rightbot_y; /* VIDOSD1 register */
+ uint32_t osdsize; /* VIDOSD2&3 register */
+ uint32_t alpha_val[2]; /* VIDOSD2&3, VIDWALPHA registers */
+ uint16_t virtpage_width; /* VIDWADD2 register */
+ uint16_t virtpage_offsize; /* VIDWADD2 register */
+ bool alpha_8bit; /* BLENDCON register: alpha 4 or 8 bit */
+ uint8_t pal_format; /* Window palette format index */
+};
+
+typedef struct {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ DisplayState *console;
+ qemu_irq irq[3];
+
+ uint32_t vidcon[4]; /* Video main control registers 0-3 */
+ uint32_t vidtcon[4]; /* Video time control registers 0-3 */
+ uint32_t shadowcon; /* Window shadow control register */
+ uint32_t winchmap; /* Channel mapping control register */
+ uint32_t vidintcon[2]; /* Video interrupt control registers */
+ uint32_t dithmode; /* Dithering control register */
+ uint32_t wpalcon[2]; /* Window palette control registers */
+ uint32_t trigcon; /* Trigger control register */
+ uint32_t i80ifcon[4]; /* I80 interface control registers */
+ uint32_t colorgaincon; /* Color gain control register */
+ uint32_t ldi_cmdcon[2]; /* LCD I80 interface command control */
+ uint32_t sifccon[3]; /* I80 System Interface Manual Command Control */
+ uint32_t huecoef_cr[4]; /* Hue control registers */
+ uint32_t huecoef_cb[4]; /* Hue control registers */
+ uint32_t hueoffset; /* Hue offset control register */
+/* blendcon Blend equation control register substituted by window.alpha_8bit */
+ uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */
+
+ Exynos4210fimdWindow window[5]; /* Window-specific registers */
+ uint8_t *ifb; /* Internal frame buffer */
+ bool invalidate; /* Image needs to be redrawn */
+ bool enabled; /* Display controller is enabled */
+} Exynos4210fimdState;
+
+/* Perform byte/halfword/word swap of data according to WINCON */
+static inline void fimd_swap_data(unsigned int swap_ctl, uint64_t *data)
+{
+ int i;
+ uint64_t res;
+ uint64_t x = *data;
+
+ if (swap_ctl & FIMD_WINCON_SWAP_BITS) {
+ res = 0;
+ for (i = 0; i < 64; i++) {
+ if (x & (1ULL << (64 - i))) {
+ res |= (1ULL << i);
+ }
+ }
+ x = res;
+ }
+ if (swap_ctl & FIMD_WINCON_SWAP_BYTE) {
+ x = ((x & 0x00000000000000FFULL) << 56) |
+ ((x & 0x000000000000FF00ULL) << 40) |
+ ((x & 0x0000000000FF0000ULL) << 24) |
+ ((x & 0x00000000FF000000ULL) << 8) |
+ ((x & 0x000000FF00000000ULL) >> 8) |
+ ((x & 0x0000FF0000000000ULL) >> 24) |
+ ((x & 0x00FF000000000000ULL) >> 40) |
+ ((x & 0xFF00000000000000ULL) >> 56);
+ }
+ if (swap_ctl & FIMD_WINCON_SWAP_HWORD) {
+ x = ((x & 0x000000000000FFFFULL) << 48) |
+ ((x & 0x00000000FFFF0000ULL) << 16) |
+ ((x & 0x0000FFFF00000000ULL) >> 16) |
+ ((x & 0xFFFF000000000000ULL) >> 48);
+ }
+ if (swap_ctl & FIMD_WINCON_SWAP_WORD) {
+ x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+ ((x & 0xFFFFFFFF00000000ULL) >> 32);
+ }
+
+ *data = x;
+}
+
+/* Conversion routines of Pixel data from frame buffer area to internal RGBA
+ * pixel representation.
+ * Every color component internally represented as 8-bit value. If original
+ * data has less than 8 bit for component, data is extended to 8 bit. For
+ * example, if blue component has only two possible values 0 and 1 it will be
+ * extended to 0 and 0xFF */
+
+/* One bit for alpha representation */
+#define DEF_PIXEL_TO_RGB_A1(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & 0x1); \
+}
+
+DEF_PIXEL_TO_RGB_A1(pixel_a444_to_rgb, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A1(pixel_a555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A1(pixel_a665_to_rgb, 6, 6, 5)
+DEF_PIXEL_TO_RGB_A1(pixel_a888_to_rgb, 8, 8, 8)
+DEF_PIXEL_TO_RGB_A1(pixel_a887_to_rgb, 8, 8, 7)
+
+/* Alpha component is always zero */
+#define DEF_PIXEL_TO_RGB_A0(N, R, G, B) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ p->a = 0x0; \
+}
+
+DEF_PIXEL_TO_RGB_A0(pixel_565_to_rgb, 5, 6, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_555_to_rgb, 5, 5, 5)
+DEF_PIXEL_TO_RGB_A0(pixel_666_to_rgb, 6, 6, 6)
+DEF_PIXEL_TO_RGB_A0(pixel_888_to_rgb, 8, 8, 8)
+
+/* Alpha component has some meaningful value */
+#define DEF_PIXEL_TO_RGB_A(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = ((pixel & ((1 << (B)) - 1)) << (8 - (B))) | \
+ ((pixel >> (2 * (B) - 8)) & ((1 << (8 - (B))) - 1)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)) | \
+ ((pixel >> (2 * (G) - 8)) & ((1 << (8 - (G))) - 1)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)) | \
+ ((pixel >> (2 * (R) - 8)) & ((1 << (8 - (R))) - 1)); \
+ pixel >>= (R); \
+ p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)) | \
+ ((pixel >> (2 * (A) - 8)) & ((1 << (8 - (A))) - 1)); \
+ p->a = p->a | (p->a << 8) | (p->a << 16); \
+}
+
+DEF_PIXEL_TO_RGB_A(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB_A(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Lookup table to extent 2-bit color component to 8 bit */
+static const uint8_t pixel_lutable_2b[4] = {
+ 0x0, 0x55, 0xAA, 0xFF
+};
+/* Lookup table to extent 3-bit color component to 8 bit */
+static const uint8_t pixel_lutable_3b[8] = {
+ 0x0, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, 0xFF
+};
+/* Special case for a232 bpp mode */
+static void pixel_a232_to_rgb(uint32_t pixel, rgba *p)
+{
+ p->b = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->g = pixel_lutable_3b[(pixel & 0x7)];
+ pixel >>= 3;
+ p->r = pixel_lutable_2b[(pixel & 0x3)];
+ pixel >>= 2;
+ p->a = (pixel & 0x1);
+}
+
+/* Special case for (5+1, 5+1, 5+1) mode. Data bit 15 is common LSB
+ * for all three color components */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+ uint8_t comm = (pixel >> 15) & 1;
+ p->b = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->g = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ pixel >>= 5;
+ p->r = ((((pixel & 0x1F) << 1) | comm) << 2) | ((pixel >> 3) & 0x3);
+ p->a = 0x0;
+}
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_pixel_ifb(const rgba p, uint8_t *d)
+{
+ *(uint8_t *)d++ = p.r;
+ *(uint8_t *)d++ = p.g;
+ *(uint8_t *)d++ = p.b;
+ *(uint32_t *)d = p.a;
+ return RGBA_SIZE;
+}
+
+static int get_pixel_ifb(const uint8_t *s, rgba *p)
+{
+ p->r = *(uint8_t *)s++;
+ p->g = *(uint8_t *)s++;
+ p->b = *(uint8_t *)s++;
+ p->a = (*(uint32_t *)s) & 0x00FFFFFF;
+ return RGBA_SIZE;
+}
+#define FIMD_1_MINUS_COLOR(x) \
+ ((0xFF - ((x) & 0xFF)) | (0xFF00 - ((x) & 0xFF00)) | \
+ (0xFF0000 - ((x) & 0xFF0000)))
+#define EXTEND_LOWER_HALFBYTE(x) (((x) & 0xF0F0F) | (((x) << 4) & 0xF0F0F0))
+#define EXTEND_UPPER_HALFBYTE(x) (((x) & 0xF0F0F0) | (((x) >> 4) & 0xF0F0F))
+
+/* Multiply three lower bytes of two 32-bit words with each other.
+ * Each byte with values 0-255 is considered as a number with possible values
+ * in a range [0 - 1] */
+static inline uint32_t fimd_mult_each_byte(uint32_t a, uint32_t b)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* For each corresponding bytes of two 32-bit words: (a*b + c*d)
+ * Byte values 0-255 are mapped to a range [0 .. 1] */
+static inline uint32_t
+fimd_mult_and_sum_each_byte(uint32_t a, uint32_t b, uint32_t c, uint32_t d)
+{
+ uint32_t tmp;
+ uint32_t ret;
+
+ ret = ((tmp = (((a & 0xFF) * (b & 0xFF) + (c & 0xFF) * (d & 0xFF)) / 0xFF))
+ > 0xFF) ? 0xFF : tmp;
+ ret |= ((tmp = ((((a >> 8) & 0xFF) * ((b >> 8) & 0xFF) + ((c >> 8) & 0xFF) *
+ ((d >> 8) & 0xFF)) / 0xFF)) > 0xFF) ? 0xFF00 : tmp << 8;
+ ret |= ((tmp = ((((a >> 16) & 0xFF) * ((b >> 16) & 0xFF) +
+ ((c >> 16) & 0xFF) * ((d >> 16) & 0xFF)) / 0xFF)) > 0xFF) ?
+ 0xFF0000 : tmp << 16;
+ return ret;
+}
+
+/* Updates currently active alpha value for 3 color components combined
+ * in 32-bit word:
+ * D[31..24]dummy, D[23..16]rAlpha, D[15..8]gAlpha, D[7..0]bAlpha */
+static inline uint32_t fimd_get_alpha(Exynos4210fimdWindow *w, rgba pix)
+{
+ uint32_t ret;
+
+ if (w->wincon & FIMD_WINCON_BLD_PIX) {
+ if ((w->wincon & FIMD_WINCON_ALPHA_SEL) &&
+ (WIN_BPP_MODE(w) == 0xD || WIN_BPP_MODE(w) == 0xE)) {
+ /* In this case, alpha component contains meaningful value */
+ ret = w->alpha_8bit ? pix.a : EXTEND_LOWER_HALFBYTE(pix.a);
+ if (w->wincon & FIMD_WINCON_ALPHA_MUL) {
+ uint32_t mult = w->alpha_val[0];
+ if (w->alpha_8bit == false) {
+ mult = EXTEND_UPPER_HALFBYTE(mult);
+ }
+ ret = fimd_mult_each_byte(ret, mult);
+ }
+ return ret;
+ } else {
+ if (IS_PALETTIZED_MODE(w) && (w->pal_format == 7)) {
+ /* Alpha component has 8-bit numeric value */
+ ret = pix.a;
+ } else {
+ /* Alpha has only two possible values (AEN) */
+ ret = w->alpha_val[pix.a];
+ }
+ }
+ } else {
+ ret = w->alpha_val[(w->wincon & FIMD_WINCON_ALPHA_SEL) ? 1 : 0];
+ }
+
+ if (w->alpha_8bit == false) {
+ ret = EXTEND_UPPER_HALFBYTE(ret);
+ }
+ return ret;
+}
+
+/* Blends current window's (w) pixel (foreground pixel *ret) with background
+ * window (w_blend) pixel p_bg according to formula:
+ * NEW_COLOR = a_coef x FG_PIXEL_COLOR + b_coef x BG_PIXEL_COLOR
+ * NEW_ALPHA = p_coef x FG_ALPHA + q_coef x BG_ALPHA
+ */
+static void exynos4210_fimd_blend_pixel(Exynos4210fimdWindow *w,
+ Exynos4210fimdWindow *w_blend, rgba p_bg, rgba *ret)
+{
+ rgba p_fg = *ret;
+ uint32_t bg_color = ((p_bg.r & 0xFF) << 16) | ((p_bg.g & 0xFF) << 8) |
+ (p_bg.b & 0xFF);
+ uint32_t fg_color = ((p_fg.r & 0xFF) << 16) | ((p_fg.g & 0xFF) << 8) |
+ (p_fg.b & 0xFF);
+ uint32_t alpha_fg, alpha_bg;
+ int i;
+ /* It is possible that blending equation parameters a and b do not
+ * depend on window BLENEQ register. Account for this with first_coef */
+ enum { A_COEF = 0, B_COEF = 1, P_COEF = 2, Q_COEF = 3, COEF_NUM = 4};
+ uint32_t first_coef = A_COEF;
+ uint32_t blend_param[COEF_NUM];
+
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYEN) {
+ uint32_t colorkey = (w->keycon[1] &
+ ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) & FIMD_WKEYCON0_COMPKEY;
+
+ if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) &&
+ (bg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Foreground pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ alpha_bg = w_blend->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ alpha_bg = 0;
+ blend_param[A_COEF] = 0xFFFFFF;
+ blend_param[B_COEF] = 0x0;
+ }
+ first_coef = P_COEF;
+ } else if ((w->keycon[0] & FIMD_WKEYCON0_DIRCON) == 0 &&
+ (fg_color & ~(w->keycon[0] & FIMD_WKEYCON0_COMPKEY)) == colorkey) {
+ /* Background pixel is displayed */
+ if (w->keycon[0] & FIMD_WKEYCON0_KEYBLEN) {
+ alpha_fg = w->keyalpha;
+ alpha_bg = w_blend->keyalpha;
+ blend_param[A_COEF] = alpha_fg;
+ blend_param[B_COEF] = FIMD_1_MINUS_COLOR(alpha_fg);
+ } else {
+ alpha_fg = 0;
+ alpha_bg = 0;
+ blend_param[A_COEF] = 0x0;
+ blend_param[B_COEF] = 0xFFFFFF;
+ }
+ first_coef = P_COEF;
+ } else {
+ alpha_fg = fimd_get_alpha(w, p_fg);
+ alpha_bg = fimd_get_alpha(w_blend, p_bg);
+ /* Depend on window BLENDEQ register */
+ }
+ } else {
+ alpha_fg = fimd_get_alpha(w, p_fg);
+ alpha_bg = fimd_get_alpha(w_blend, p_bg);
+ /* Depend on window BLENDEQ register */
+ }
+
+ for (i = first_coef; i < COEF_NUM; i++) {
+ switch ((w->blendeq >> i * 6) & FIMD_BLENDEQ_COEF_MASK) {
+ case 0:
+ blend_param[i] = 0;
+ break;
+ case 1:
+ blend_param[i] = 0xFFFFFF;
+ break;
+ case 2:
+ blend_param[i] = alpha_fg;
+ break;
+ case 3:
+ blend_param[i] = FIMD_1_MINUS_COLOR(alpha_fg);
+ break;
+ case 4:
+ blend_param[i] = alpha_bg;
+ break;
+ case 5:
+ blend_param[i] = FIMD_1_MINUS_COLOR(alpha_bg);
+ break;
+ case 6:
+ blend_param[i] = w->alpha_val[0];
+ break;
+ case 10:
+ blend_param[i] = fg_color;
+ break;
+ case 11:
+ blend_param[i] = FIMD_1_MINUS_COLOR(fg_color);
+ break;
+ case 12:
+ blend_param[i] = bg_color;
+ break;
+ case 13:
+ blend_param[i] = FIMD_1_MINUS_COLOR(bg_color);
+ break;
+ default:
+ hw_error("exynos4210.fimd: blend equation coef illegal value\n");
+ break;
+ }
+ }
+
+ fg_color = fimd_mult_and_sum_each_byte(bg_color, blend_param[B_COEF],
+ fg_color, blend_param[A_COEF]);
+ ret->b = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->g = fg_color & 0xFF;
+ fg_color >>= 8;
+ ret->r = fg_color & 0xFF;
+ ret->a = fimd_mult_and_sum_each_byte(alpha_fg, blend_param[P_COEF],
+ alpha_bg, blend_param[Q_COEF]);
+}
+
+/* These routines read data from video frame buffer in system RAM, convert
+ * this data to display controller internal representation, if necessary,
+ * perform pixel blending with data, currently presented in internal buffer.
+ * Result is stored in display controller internal frame buffer. */
+
+/* Draw line with index in palette table in RAM frame buffer data */
+#define DEF_DRAW_LINE_PALLETE(N) \
+static void glue(draw_line_palette_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, Exynos4210fimdWindow *w_blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ data = ldq_raw((void *)src); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb(w->palette[(data >> ((N) * i)) & \
+ ((1ULL << (N)) - 1)], &p); \
+ if (w_blend != NULL) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+/* Draw line with direct color value in RAM frame buffer data */
+#define DEF_DRAW_LINE_NOPALLETE(N) \
+static void glue(draw_line_, N)(Exynos4210fimdWindow *w, uint8_t *src, \
+ uint8_t *dst, Exynos4210fimdWindow *w_blend) \
+{ \
+ int width = w->rightbot_x - w->lefttop_x + 1; \
+ uint8_t *ifb = dst; \
+ uint8_t swap = (w->wincon & FIMD_WINCON_SWAP) >> FIMD_WINCON_SWAP_SHIFT; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ data = ldq_raw((void *)src); \
+ src += 8; \
+ fimd_swap_data(swap, &data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ w->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+ if (w_blend != NULL) { \
+ ifb += get_pixel_ifb(ifb, &p_old); \
+ exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p); \
+ } \
+ dst += put_pixel_ifb(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+DEF_DRAW_LINE_PALLETE(1)
+DEF_DRAW_LINE_PALLETE(2)
+DEF_DRAW_LINE_PALLETE(4)
+DEF_DRAW_LINE_PALLETE(8)
+DEF_DRAW_LINE_NOPALLETE(8) /* 8bpp mode has palette and non-palette versions */
+DEF_DRAW_LINE_NOPALLETE(16)
+DEF_DRAW_LINE_NOPALLETE(32)
+
+/* Special draw line routine for window color map case */
+static void draw_line_mapcolor(Exynos4210fimdWindow *w, uint8_t *src,
+ uint8_t *dst, Exynos4210fimdWindow *w_blend)
+{
+ rgba p, p_old;
+ uint8_t *ifb = dst;
+ int width = w->rightbot_x - w->lefttop_x + 1;
+ uint32_t map_color = w->winmap & FIMD_WINMAP_COLOR_MASK;
+
+ do {
+ pixel_888_to_rgb(map_color, &p);
+ if (w_blend != NULL) {
+ ifb += get_pixel_ifb(ifb, &p_old);
+ exynos4210_fimd_blend_pixel(w, w_blend, p_old, &p);
+ }
+ dst += put_pixel_ifb(p, dst);
+ } while (--width);
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_to_qemufb_pixel8(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel8(p.r, p.g, p.b);
+ *(uint8_t *)d = pixel;
+ return 1;
+}
+
+static int put_to_qemufb_pixel15(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel15(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel16(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel16(p.r, p.g, p.b);
+ *(uint16_t *)d = pixel;
+ return 2;
+}
+
+static int put_to_qemufb_pixel24(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint8_t *)d++ = (pixel >> 0) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 8) & 0xFF;
+ *(uint8_t *)d++ = (pixel >> 16) & 0xFF;
+ return 3;
+}
+
+static int put_to_qemufb_pixel32(const rgba p, uint8_t *d)
+{
+ uint32_t pixel = rgb_to_pixel24(p.r, p.g, p.b);
+ *(uint32_t *)d = pixel;
+ return 4;
+}
+
+/* Routine to copy pixel from internal buffer to QEMU buffer */
+static int (*put_pixel_toqemu)(const rgba p, uint8_t *pixel);
+static inline void fimd_update_putpix_qemu(int bpp)
+{
+ switch (bpp) {
+ case 8:
+ put_pixel_toqemu = put_to_qemufb_pixel8;
+ break;
+ case 15:
+ put_pixel_toqemu = put_to_qemufb_pixel15;
+ break;
+ case 16:
+ put_pixel_toqemu = put_to_qemufb_pixel16;
+ break;
+ case 24:
+ put_pixel_toqemu = put_to_qemufb_pixel24;
+ break;
+ case 32:
+ put_pixel_toqemu = put_to_qemufb_pixel32;
+ break;
+ default:
+ hw_error("exynos4210.fimd: unsupported BPP (%d)", bpp);
+ break;
+ }
+}
+
+/* Routine to copy a line from internal frame buffer to QEMU display */
+static void fimd_copy_line_toqemu(int width, uint8_t *src, uint8_t *dst)
+{
+ rgba p;
+
+ do {
+ src += get_pixel_ifb(src, &p);
+ dst += put_pixel_toqemu(p, dst);
+ } while (--width);
+}
+
+static pixel_to_rgb_func *palette_data_format[8] = {
+ [0] = pixel_565_to_rgb,
+ [1] = pixel_a555_to_rgb,
+ [2] = pixel_666_to_rgb,
+ [3] = pixel_a665_to_rgb,
+ [4] = pixel_a666_to_rgb,
+ [5] = pixel_888_to_rgb,
+ [6] = pixel_a888_to_rgb,
+ [7] = pixel_8888_to_rgb
+};
+
+/* Returns Index in palette data formats table for given window number WINDOW */
+static uint32_t
+exynos4210_fimd_palette_format(Exynos4210fimdState *s, int window)
+{
+ uint32_t ret;
+
+ switch (window) {
+ case 0:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SHT) & FIMD_WPAL_W0PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 1:
+ ret = (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SHT) & FIMD_WPAL_W1PAL_L;
+ if (ret != 7) {
+ ret = 6 - ret;
+ }
+ break;
+ case 2:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SHT) & FIMD_WPAL_W2PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SHT) & FIMD_WPAL_W2PAL_L);
+ break;
+ case 3:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SHT) & FIMD_WPAL_W3PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SHT) & FIMD_WPAL_W3PAL_L);
+ break;
+ case 4:
+ ret = ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SHT) & FIMD_WPAL_W4PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SHT) & FIMD_WPAL_W4PAL_L);
+ break;
+ default:
+ hw_error("exynos4210.fimd: incorrect window number %d\n", window);
+ ret = 0;
+ break;
+ }
+ return ret;
+}
+
+/* Parse BPPMODE_F = WINCON1[5:2] bits */
+static void exynos4210_fimd_update_win_bppmode(Exynos4210fimdState *s, int win)
+{
+ Exynos4210fimdWindow *w = &s->window[win];
+ uint32_t palette_format_idx = exynos4210_fimd_palette_format(s, win);
+ w->pal_format = palette_format_idx;
+
+ if (w->winmap & FIMD_WINMAP_EN) {
+ w->draw_line = draw_line_mapcolor;
+ return;
+ }
+
+ switch (WIN_BPP_MODE(w)) {
+ case 0:
+ w->draw_line = draw_line_palette_1;
+ w->pixel_to_rgb = palette_data_format[palette_format_idx];
+ break;
+ case 1:
+ w->draw_line = draw_line_palette_2;
+ w->pixel_to_rgb = palette_data_format[palette_format_idx];
+ break;
+ case 2:
+ w->draw_line = draw_line_palette_4;
+ w->pixel_to_rgb = palette_data_format[palette_format_idx];
+ break;
+ case 3:
+ w->draw_line = draw_line_palette_8;
+ w->pixel_to_rgb = palette_data_format[palette_format_idx];
+ break;
+ case 4:
+ w->draw_line = draw_line_8;
+ w->pixel_to_rgb = pixel_a232_to_rgb;
+ break;
+ case 5:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_565_to_rgb;
+ break;
+ case 6:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_a555_to_rgb;
+ break;
+ case 7:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_1555_to_rgb;
+ break;
+ case 8:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_666_to_rgb;
+ break;
+ case 9:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a665_to_rgb;
+ break;
+ case 10:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a666_to_rgb;
+ break;
+ case 11:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_888_to_rgb;
+ break;
+ case 12:
+ w->draw_line = draw_line_32;
+ w->pixel_to_rgb = pixel_a887_to_rgb;
+ break;
+ case 13:
+ w->draw_line = draw_line_32;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_8888_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a888_to_rgb;
+ }
+ break;
+ case 14:
+ w->draw_line = draw_line_16;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ w->pixel_to_rgb = pixel_4444_to_rgb;
+ } else {
+ w->pixel_to_rgb = pixel_a444_to_rgb;
+ }
+ break;
+ case 15:
+ w->draw_line = draw_line_16;
+ w->pixel_to_rgb = pixel_555_to_rgb;
+ break;
+ }
+}
+
+#if EXY_FIMD_MODE_TRACE > 0
+static const char *exynos4210_fimd_get_bppmode(int mode_code)
+{
+ switch (mode_code) {
+ case 0:
+ return "1 bpp";
+ case 1:
+ return "2 bpp";
+ case 2:
+ return "4 bpp";
+ case 3:
+ return "8 bpp (palletized)";
+ case 4:
+ return "8 bpp (non-palletized, A: 1-R:2-G:3-B:2)";
+ case 5:
+ return "16 bpp (non-palletized, R:5-G:6-B:5)";
+ case 6:
+ return "16 bpp (non-palletized, A:1-R:5-G:5-B:5)";
+ case 7:
+ return "16 bpp (non-palletized, I :1-R:5-G:5-B:5)";
+ case 8:
+ return "Unpacked 18 bpp (non-palletized, R:6-G:6-B:6)";
+ case 9:
+ return "Unpacked 18bpp (non-palletized,A:1-R:6-G:6-B:5)";
+ case 10:
+ return "Unpacked 19bpp (non-palletized,A:1-R:6-G:6-B:6)";
+ case 11:
+ return "Unpacked 24 bpp (non-palletized R:8-G:8-B:8)";
+ case 12:
+ return "Unpacked 24 bpp (non-palletized A:1-R:8-G:8-B:7)";
+ case 13:
+ return "Unpacked 25 bpp (non-palletized A:1-R:8-G:8-B:8)";
+ case 14:
+ return "Unpacked 13 bpp (non-palletized A:1-R:4-G:4-B:4)";
+ case 15:
+ return "Unpacked 15 bpp (non-palletized R:5-G:5-B:5)";
+ default:
+ return "Non-existing bpp mode";
+ }
+}
+
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdWindow *w,
+ int win_num, uint32_t val)
+{
+ if (((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) ==
+ ((val >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE)) {
+ return;
+ }
+ printf("QEMU FIMD: Window %d BPP mode changed from %s to %s\n", win_num,
+ exynos4210_fimd_get_bppmode((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) &
+ FIMD_WINCON_BPPMODE),
+ exynos4210_fimd_get_bppmode((val >> FIMD_WINCON_BPPMODE_SHIFT) &
+ FIMD_WINCON_BPPMODE));
+}
+#else
+static inline void exynos4210_fimd_trace_bppmode(Exynos4210fimdWindow *w,
+ int win_num, uint32_t val)
+{
+
+}
+#endif
+
+static inline void exynos4210_fimd_enable(Exynos4210fimdState *s, bool enabled)
+{
+ s->enabled = enabled ? true : false;
+ DPRINT_TRACE("QEMU FIMD: display controller %s\n",
+ enabled ? "enabled" : "disabled");
+}
+
+static inline uint32_t unpack_upper_4(uint32_t x)
+{
+ return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+static inline uint32_t pack_upper_4(uint32_t x)
+{
+ return (((x & 0xF00000) >> 12) | ((x & 0xF000) >> 8) |
+ ((x & 0xF0) >> 4)) & 0xFFF;
+}
+
+static void exynos4210_fimd_update_irq(Exynos4210fimdState *s)
+{
+ if (!(s->vidintcon[0] & FIMD_VIDINT_INTEN)) {
+ qemu_irq_lower(s->irq[0]);
+ qemu_irq_lower(s->irq[1]);
+ qemu_irq_lower(s->irq[2]);
+ return;
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFIFOEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFIFOPEND)) {
+ qemu_irq_raise(s->irq[0]);
+ } else {
+ qemu_irq_lower(s->irq[0]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_INTFRMEN) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTFRMPEND)) {
+ qemu_irq_raise(s->irq[1]);
+ } else {
+ qemu_irq_lower(s->irq[1]);
+ }
+ if ((s->vidintcon[0] & FIMD_VIDINT_I80IFDONE) &&
+ (s->vidintcon[1] & FIMD_VIDINT_INTI80PEND)) {
+ qemu_irq_raise(s->irq[2]);
+ } else {
+ qemu_irq_lower(s->irq[2]);
+ }
+}
+
+static void exynos4210_update_resolution(Exynos4210fimdState *s)
+{
+ /* LCD resolution is stored in VIDEO TIME CONTROL REGISTER 2 */
+ uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+ uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+ if (s->ifb == NULL || ds_get_width(s->console) != width ||
+ ds_get_height(s->console) != height) {
+ DPRINT_L1("Resolution changed from %ux%u to %ux%u\n",
+ ds_get_width(s->console), ds_get_height(s->console), width, height);
+ qemu_console_resize(s->console, width, height);
+ s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1);
+ memset(s->ifb, 0, width * height * RGBA_SIZE + 1);
+ s->invalidate = true;
+ }
+}
+
+static inline int fimd_get_buffer_id(Exynos4210fimdWindow *w)
+{
+ switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+ case FIMD_WINCON_BUF0_STAT:
+ return 0;
+ case FIMD_WINCON_BUF1_STAT:
+ return 1;
+ case FIMD_WINCON_BUF2_STAT:
+ return 2;
+ default:
+ DPRINT_ERROR("Non-existent buffer index\n");
+ return 0;
+ }
+}
+
+static void exynos4210_fimd_update(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int i, line;
+ target_phys_addr_t fb_start_addr, fb_line_end_addr, inc_size, fb_len, x;
+ int scrn_height;
+ int first_line = -1, last_line = -1, scrn_width;
+ Exynos4210fimdWindow *window_to_blend = NULL;
+ uint8_t *host_fb_addr, *host_fb_start_addr;
+ bool is_dirty = false;
+ ram_addr_t pd;
+ const int global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1;
+ const int global_height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) &
+ FIMD_VIDTCON2_SIZE_MASK) + 1;
+
+ if (!s || !s->console || !ds_get_bits_per_pixel(s->console)) {
+ return;
+ }
+
+ if (s->enabled == false) {
+ return;
+ }
+
+ exynos4210_update_resolution(s);
+
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ if ((s->window[i].wincon & FIMD_WINCON_ENWIN) &&
+ FIMD_WIN_NOT_PROTECTED(s, i)) {
+ scrn_height = s->window[i].rightbot_y - s->window[i].lefttop_y + 1;
+ scrn_width = s->window[i].virtpage_width;
+ fb_start_addr =
+ s->window[i].buf_start[fimd_get_buffer_id(&s->window[i])];
+ /* Total width of virtual screen page in bytes */
+ inc_size = scrn_width + s->window[i].virtpage_offsize;
+ /* Total number of bytes of virtual screen used by current window */
+ fb_len = inc_size * scrn_height;
+ cpu_physical_sync_dirty_bitmap(fb_start_addr,
+ fb_start_addr + fb_len);
+ host_fb_addr = cpu_physical_memory_map(fb_start_addr, &fb_len, 0);
+ if (!host_fb_addr) {
+ return;
+ }
+ if (fb_len != inc_size * scrn_height) {
+ cpu_physical_memory_unmap(host_fb_addr, fb_len, 0, 0);
+ return;
+ }
+
+ host_fb_start_addr = host_fb_addr;
+ for (line = 0; line < scrn_height; line++) {
+ fb_line_end_addr = fb_start_addr + scrn_width;
+ for (x = fb_start_addr; x < fb_line_end_addr;
+ x += TARGET_PAGE_SIZE) {
+ pd = (cpu_get_physical_page_desc(x) & TARGET_PAGE_MASK) +
+ (x & ~TARGET_PAGE_MASK);
+ is_dirty = is_dirty ||
+ cpu_physical_memory_get_dirty(pd, VGA_DIRTY_FLAG);
+ }
+
+ if (s->invalidate || is_dirty) {
+ if (first_line == -1) {
+ first_line = line;
+ }
+ last_line = line;
+ s->window[i].draw_line(&s->window[i], host_fb_addr,
+ s->ifb + s->window[i].lefttop_x * RGBA_SIZE +
+ (s->window[i].lefttop_y + line) * global_width
+ * RGBA_SIZE, window_to_blend);
+ }
+ host_fb_addr += inc_size;
+ is_dirty = false;
+ fb_start_addr += inc_size;
+ }
+ cpu_physical_memory_unmap(host_fb_start_addr, fb_len, 0, 0);
+ fb_start_addr =
+ s->window[i].buf_start[fimd_get_buffer_id(&s->window[i])];
+ pd = (cpu_get_physical_page_desc(fb_start_addr) & TARGET_PAGE_MASK)+
+ (fb_start_addr & ~TARGET_PAGE_MASK);
+ cpu_physical_memory_reset_dirty(pd, pd + inc_size * scrn_height,
+ VGA_DIRTY_FLAG);
+ window_to_blend = &s->window[i];
+ }
+ }
+
+ /* Copy resulting image to QEMU_CONSOLE. */
+ if (first_line >= 0) {
+ uint8_t *d;
+ int bpp;
+
+ bpp = ds_get_bits_per_pixel(s->console);
+ fimd_update_putpix_qemu(bpp);
+ bpp = (bpp + 1) >> 3;
+ d = ds_get_data(s->console);
+ for (line = first_line; line < last_line; line++) {
+ fimd_copy_line_toqemu(global_width, s->ifb + global_width * line *
+ RGBA_SIZE, d + global_width * line * bpp);
+ }
+ dpy_update(s->console, 0, 0, global_width, global_height);
+ }
+ s->invalidate = false;
+ s->vidintcon[1] |= FIMD_VIDINT_INTFRMPEND;
+ if ((s->vidcon[0] & FIMD_VIDCON0_ENVID_F) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ exynos4210_fimd_update_irq(s);
+}
+
+static void exynos4210_fimd_invalidate(void *opaque)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ s->invalidate = true;
+}
+
+static void exynos4210_fimd_reset(DeviceState *d)
+{
+ Exynos4210fimdState *s = container_of(d, Exynos4210fimdState, busdev.qdev);
+ int i;
+ unsigned long begin =
+ (unsigned long)s + offsetof(Exynos4210fimdState, vidcon);
+ unsigned long len =
+ ((unsigned long)s + offsetof(Exynos4210fimdState, window)) - begin;
+ DPRINT_TRACE("Display controller reset\n");
+
+ /* Set all display controller registers to 0 */
+ memset((void *)begin, 0, len);
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ memset(&s->window[i], 0, sizeof(Exynos4210fimdWindow));
+ s->window[i].blendeq = 0xC2;
+ s->window[i].pixel_to_rgb = NULL;
+ s->window[i].draw_line = NULL;
+ }
+
+ if (s->ifb != NULL) {
+ g_free(s->ifb);
+ }
+ s->ifb = NULL;
+
+ s->invalidate = true;
+ exynos4210_fimd_enable(s, false);
+ /* Some registers have non-zero initial values */
+ s->winchmap = 0x7D517D51;
+ s->colorgaincon = 0x10040100;
+ s->huecoef_cr[0] = s->huecoef_cr[3] = 0x01000100;
+ s->huecoef_cb[0] = s->huecoef_cb[3] = 0x01000100;
+ s->hueoffset = 0x01800080;
+}
+
+static void exynos4210_fimd_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w, i;
+
+ DPRINT_L2("write offset 0x%08x, value=%ld(0x%08lx)\n", offset, val, val);
+
+ if (offset == FIMD_VIDCON0) {
+ if ((val & FIMD_VIDCON0_ENVID_MASK) == FIMD_VIDCON0_ENVID_MASK) {
+ exynos4210_fimd_enable(s, true);
+ } else {
+ if ((val & FIMD_VIDCON0_ENVID) == 0) {
+ exynos4210_fimd_enable(s, false);
+ }
+ }
+ s->vidcon[0] = val;
+ } else if (offset == FIMD_VIDCON1) {
+ /* Leave read-only bits as is */
+ val = (val & (~FIMD_VIDCON1_ROMASK)) |
+ (s->vidcon[1] & FIMD_VIDCON1_ROMASK);
+ s->vidcon[1] = val;
+ } else if (offset >= FIMD_VIDCON2 && offset <= FIMD_VIDCON3) {
+ s->vidcon[(offset) >> 2] = val;
+ } else if (offset >= FIMD_VIDTCON_START && offset <= FIMD_VIDTCON_END) {
+ s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2] = val;
+ } else if (offset >= FIMD_WINCON_START && offset <= FIMD_WINCON_END) {
+ w = (offset - FIMD_WINCON_START) >> 2;
+ val = (val & ~FIMD_WINCON_ROMASK) |
+ (s->window[w].wincon & FIMD_WINCON_ROMASK);
+ if (w == 0) {
+ /* Window 0 wincon ALPHA_MUL bit must always be 0 */
+ val &= ~FIMD_WINCON_ALPHA_MUL;
+ }
+ exynos4210_fimd_trace_bppmode(&s->window[w], w, val);
+ switch (val & FIMD_WINCON_BUFSELECT) {
+ case FIMD_WINCON_BUF0_SEL:
+ val &= ~FIMD_WINCON_BUFSTATUS;
+ break;
+ case FIMD_WINCON_BUF1_SEL:
+ val = (val & ~FIMD_WINCON_BUFSTATUS_H) |
+ FIMD_WINCON_BUFSTATUS_L;
+ break;
+ case FIMD_WINCON_BUF2_SEL:
+ if (val & FIMD_WINCON_BUFMODE) {
+ val = (val & ~FIMD_WINCON_BUFSTATUS_L) |
+ FIMD_WINCON_BUFSTATUS_H;
+ }
+ break;
+ default:
+ break;
+ }
+ s->window[w].wincon = val;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ } else if (offset == FIMD_SHADOWCON) {
+ s->shadowcon = val;
+ } else if (offset == FIMD_WINCHMAP) {
+ s->winchmap = val;
+ } else if (offset >= FIMD_VIDOSD_START && offset <= FIMD_VIDOSD_END) {
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ break;
+ case 1:
+ s->window[w].rightbot_x = (val >> FIMD_VIDOSD_HOR_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ s->window[w].rightbot_y = (val >> FIMD_VIDOSD_VER_SHIFT) &
+ FIMD_VIDOSD_COORD_MASK;
+ break;
+ case 2:
+ if (w == 0) {
+ s->window[w].osdsize = val;
+ } else {
+ s->window[w].alpha_val[0] =
+ unpack_upper_4((val & FIMD_VIDOSD_ALPHA_AEN0) >>
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ (s->window[w].alpha_val[0] & FIMD_VIDALPHA_ALPHA_LOWER);
+ s->window[w].alpha_val[1] =
+ unpack_upper_4(val & FIMD_VIDOSD_ALPHA_AEN1) |
+ (s->window[w].alpha_val[1] & FIMD_VIDALPHA_ALPHA_LOWER);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ DPRINT_ERROR("Bad write offset 0x%08x\n", offset);
+ return;
+ }
+ s->window[w].osdsize = val;
+ break;
+ }
+ } else if (offset >= FIMD_VIDWADD0_START && offset <= FIMD_VIDWADD0_END) {
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ s->window[w].buf_start[i] = val;
+ } else if (offset >= FIMD_VIDWADD1_START && offset <= FIMD_VIDWADD1_END) {
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ s->window[w].buf_end[i] = val;
+ } else if (offset >= FIMD_VIDWADD2_START && offset <= FIMD_VIDWADD2_END) {
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ s->window[w].virtpage_width = val & FIMD_VIDWADD2_PAGEWIDTH;
+ s->window[w].virtpage_offsize = (val >> FIMD_VIDWADD2_OFFSIZE_SHIFT) &
+ FIMD_VIDWADD2_OFFSIZE;
+ } else if (offset == FIMD_VIDINTCON0) {
+ s->vidintcon[0] = val;
+ } else if (offset == FIMD_VIDINTCON1) {
+ s->vidintcon[1] &= ~(val & 7);
+ exynos4210_fimd_update_irq(s);
+ } else if (offset >= FIMD_WKEYCON_START && offset <= FIMD_WKEYCON_END) {
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ s->window[w].keycon[i] = val;
+ } else if (offset >= FIMD_WKEYALPHA_START && offset <= FIMD_WKEYALPHA_END) {
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ s->window[w].keyalpha = val;
+ } else if (offset == FIMD_DITHMODE) {
+ s->dithmode = val;
+ } else if (offset >= FIMD_WINMAP_START && offset <= FIMD_WINMAP_END) {
+ w = (offset - FIMD_WINMAP_START) >> 2;
+ s->window[w].winmap = val;
+ if (val & FIMD_WINMAP_EN) {
+ s->invalidate = true;
+ exynos4210_fimd_update_win_bppmode(s, w);
+ exynos4210_fimd_update(s);
+ }
+ } else if (offset >= FIMD_WPALCON_HIGH && offset <= FIMD_WPALCON_LOW) {
+ i = (offset - FIMD_WPALCON_HIGH) >> 2;
+ s->wpalcon[i] = val;
+ if (s->wpalcon[1] & FIMD_WPALCON_UPDATEEN) {
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ }
+ }
+ } else if (offset == FIMD_TRIGCON) {
+ val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+ s->trigcon = val;
+ } else if (offset >= FIMD_I80IFCON_START && offset <= FIMD_I80IFCON_END) {
+ s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2] = val;
+ } else if (offset == FIMD_COLORGAINCON) {
+ s->colorgaincon = val;
+ } else if (offset >= FIMD_LDI_CMDCON0 && offset <= FIMD_LDI_CMDCON1) {
+ s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2] = val;
+ } else if (offset >= FIMD_SIFCCON0 && offset <= FIMD_SIFCCON2) {
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ if (i != 2) {
+ s->sifccon[i] = val;
+ }
+ } else if (offset >= FIMD_HUECOEFCR_START && offset <= FIMD_HUECOEFCR_END) {
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ s->huecoef_cr[i] = val;
+ } else if (offset >= FIMD_HUECOEFCB_START && offset <= FIMD_HUECOEFCB_END) {
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ s->huecoef_cb[i] = val;
+ } else if (offset == FIMD_HUEOFFSET) {
+ s->hueoffset = val;
+ } else if (offset >= FIMD_VIDWALPHA_START && offset <= FIMD_VIDWALPHA_END) {
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ if (w == 0) {
+ s->window[w].alpha_val[i] = val;
+ } else {
+ s->window[w].alpha_val[i] = (val & FIMD_VIDALPHA_ALPHA_LOWER) |
+ (s->window[w].alpha_val[i] & FIMD_VIDALPHA_ALPHA_UPPER);
+ }
+ } else if (offset >= FIMD_BLENDEQ_START && offset <= FIMD_BLENDEQ_END) {
+ s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq = val;
+ } else if (offset == FIMD_BLENDCON) {
+ /* We need information in BLENDCON register to be accessible from
+ * within every Window substructure */
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ s->window[i].alpha_8bit = (val & FIMD_ALPHA_8BIT) ? true : false;
+ }
+ } else if (offset >= FIMD_WRTQOSCON_START && offset <= FIMD_WRTQOSCON_END) {
+ s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon = val;
+ } else if (offset >= FIMD_I80IFCMD_START && offset <= FIMD_I80IFCMD_END) {
+ s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2] = val;
+ } else if (offset >= FIMD_SHD_ADD0_START && offset <= FIMD_SHD_ADD0_END) {
+ if (offset & 0x0004) {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ }
+ s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start = val;
+ } else if (offset >= FIMD_SHD_ADD1_START && offset <= FIMD_SHD_ADD1_END) {
+ if (offset & 0x0004) {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ }
+ s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end = val;
+ } else if (offset >= FIMD_SHD_ADD2_START && offset <= FIMD_SHD_ADD2_END) {
+ s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size = val;
+ } else if (offset >= FIMD_PAL_MEM_START && offset <= FIMD_PAL_MEM_END) {
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ } else if (offset >= FIMD_PALMEM_AL_START && offset <= FIMD_PALMEM_AL_END) {
+ /* Palette memory aliases for windows 0 and 1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ } else {
+ DPRINT_ERROR("bad write offset 0x%08x\n", offset);
+ }
+}
+
+static uint64_t exynos4210_fimd_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w, i;
+ uint32_t ret = 0;
+
+ DPRINT_L2("read offset 0x%08x\n", offset);
+
+ if (offset <= FIMD_VIDCON3) {
+ return s->vidcon[(offset) >> 2];
+ } else if (offset >= FIMD_VIDTCON_START && offset <= FIMD_VIDTCON_END) {
+ return s->vidtcon[(offset - FIMD_VIDTCON_START) >> 2];
+ } else if (offset >= FIMD_WINCON_START && offset <= FIMD_WINCON_END) {
+ return s->window[(offset - FIMD_WINCON_START) >> 2].wincon;
+ } else if (offset == FIMD_SHADOWCON) {
+ return s->shadowcon;
+ } else if (offset == FIMD_WINCHMAP) {
+ return s->winchmap;
+ } else if (offset >= FIMD_VIDOSD_START && offset <= FIMD_VIDOSD_END) {
+ w = (offset - FIMD_VIDOSD_START) >> 4;
+ i = ((offset - FIMD_VIDOSD_START) & 0xF) >> 2;
+ switch (i) {
+ case 0:
+ ret = ((s->window[w].lefttop_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].lefttop_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 1:
+ ret = ((s->window[w].rightbot_x & FIMD_VIDOSD_COORD_MASK) <<
+ FIMD_VIDOSD_HOR_SHIFT) |
+ (s->window[w].rightbot_y & FIMD_VIDOSD_COORD_MASK);
+ break;
+ case 2:
+ if (w == 0) {
+ ret = s->window[w].osdsize;
+ } else {
+ ret = (pack_upper_4(s->window[w].alpha_val[0]) <<
+ FIMD_VIDOSD_AEN0_SHIFT) |
+ pack_upper_4(s->window[w].alpha_val[1]);
+ }
+ break;
+ case 3:
+ if (w != 1 && w != 2) {
+ goto return_error;
+ }
+ ret = s->window[w].osdsize;
+ break;
+ }
+ return ret;
+ } else if (offset >= FIMD_VIDWADD0_START && offset <= FIMD_VIDWADD0_END) {
+ w = (offset - FIMD_VIDWADD0_START) >> 3;
+ i = ((offset - FIMD_VIDWADD0_START) >> 2) & 1;
+ return s->window[w].buf_start[i];
+ } else if (offset >= FIMD_VIDWADD1_START && offset <= FIMD_VIDWADD1_END) {
+ w = (offset - FIMD_VIDWADD1_START) >> 3;
+ i = ((offset - FIMD_VIDWADD1_START) >> 2) & 1;
+ return s->window[w].buf_end[i];
+ } else if (offset >= FIMD_VIDWADD2_START && offset <= FIMD_VIDWADD2_END) {
+ w = (offset - FIMD_VIDWADD2_START) >> 2;
+ return s->window[w].virtpage_width | (s->window[w].virtpage_offsize <<
+ FIMD_VIDWADD2_OFFSIZE_SHIFT);
+ } else if (offset >= FIMD_VIDINTCON0 && offset <= FIMD_VIDINTCON1) {
+ return s->vidintcon[(offset - FIMD_VIDINTCON0) >> 2];
+ } else if (offset >= FIMD_WKEYCON_START && offset <= FIMD_WKEYCON_END) {
+ w = ((offset - FIMD_WKEYCON_START) >> 3) + 1;
+ i = ((offset - FIMD_WKEYCON_START) >> 2) & 1;
+ return s->window[w].keycon[i];
+ } else if (offset >= FIMD_WKEYALPHA_START && offset <= FIMD_WKEYALPHA_END) {
+ w = ((offset - FIMD_WKEYALPHA_START) >> 2) + 1;
+ return s->window[w].keyalpha;
+ } else if (offset == FIMD_DITHMODE) {
+ return s->dithmode;
+ } else if (offset >= FIMD_WINMAP_START && offset <= FIMD_WINMAP_END) {
+ return s->window[(offset - FIMD_WINMAP_START) >> 2].winmap;
+ } else if (offset >= FIMD_WPALCON_HIGH && offset <= FIMD_WPALCON_LOW) {
+ return s->wpalcon[(offset - FIMD_WPALCON_HIGH) >> 2];
+ } else if (offset == FIMD_TRIGCON) {
+ return s->trigcon;
+ } else if (offset >= FIMD_I80IFCON_START && offset <= FIMD_I80IFCON_END) {
+ return s->i80ifcon[(offset - FIMD_I80IFCON_START) >> 2];
+ } else if (offset == FIMD_COLORGAINCON) {
+ return s->colorgaincon;
+ } else if (offset >= FIMD_LDI_CMDCON0 && offset <= FIMD_LDI_CMDCON1) {
+ return s->ldi_cmdcon[(offset - FIMD_LDI_CMDCON0) >> 2];
+ } else if (offset >= FIMD_SIFCCON0 && offset <= FIMD_SIFCCON2) {
+ i = (offset - FIMD_SIFCCON0) >> 2;
+ return s->sifccon[i];
+ } else if (offset >= FIMD_HUECOEFCR_START && offset <= FIMD_HUECOEFCR_END) {
+ i = (offset - FIMD_HUECOEFCR_START) >> 2;
+ return s->huecoef_cr[i];
+ } else if (offset >= FIMD_HUECOEFCB_START && offset <= FIMD_HUECOEFCB_END) {
+ i = (offset - FIMD_HUECOEFCB_START) >> 2;
+ return s->huecoef_cb[i];
+ } else if (offset == FIMD_HUEOFFSET) {
+ return s->hueoffset;
+ } else if (offset >= FIMD_VIDWALPHA_START && offset <= FIMD_VIDWALPHA_END) {
+ w = ((offset - FIMD_VIDWALPHA_START) >> 3);
+ i = ((offset - FIMD_VIDWALPHA_START) >> 2) & 1;
+ return s->window[w].alpha_val[i] &
+ (w == 0 ? 0xFFFFFF : FIMD_VIDALPHA_ALPHA_LOWER);
+ } else if (offset >= FIMD_BLENDEQ_START && offset <= FIMD_BLENDEQ_END) {
+ return s->window[(offset - FIMD_BLENDEQ_START) >> 2].blendeq;
+ } else if (offset == FIMD_BLENDCON) {
+ return (s->window[0].alpha_8bit == true) ? 0x1 : 0x0;
+ } else if (offset >= FIMD_WRTQOSCON_START && offset <= FIMD_WRTQOSCON_END) {
+ return s->window[(offset - FIMD_WRTQOSCON_START) >> 2].rtqoscon;
+ } else if (offset >= FIMD_I80IFCMD_START && offset <= FIMD_I80IFCMD_END) {
+ return s->i80ifcmd[(offset - FIMD_I80IFCMD_START) >> 2];
+ } else if (offset >= FIMD_SHD_ADD0_START && offset <= FIMD_SHD_ADD0_END) {
+ if (offset & 0x0004) {
+ goto return_error;
+ }
+ return s->window[(offset - FIMD_SHD_ADD0_START) >> 3].shadow_buf_start;
+ } else if (offset >= FIMD_SHD_ADD1_START && offset <= FIMD_SHD_ADD1_END) {
+ if (offset & 0x0004) {
+ goto return_error;
+ }
+ return s->window[(offset - FIMD_SHD_ADD1_START) >> 3].shadow_buf_end;
+ } else if (offset >= FIMD_SHD_ADD2_START && offset <= FIMD_SHD_ADD2_END) {
+ return s->window[(offset - FIMD_SHD_ADD2_START) >> 2].shadow_buf_size;
+ } else if (offset >= FIMD_PAL_MEM_START && offset <= FIMD_PAL_MEM_END) {
+ w = (offset - FIMD_PAL_MEM_START) >> 10;
+ i = ((offset - FIMD_PAL_MEM_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ } else if (offset >= FIMD_PALMEM_AL_START && offset <= FIMD_PALMEM_AL_END) {
+ /* Palette aliases for win 0,1 */
+ w = (offset - FIMD_PALMEM_AL_START) >> 10;
+ i = ((offset - FIMD_PALMEM_AL_START) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ }
+return_error:
+ DPRINT_ERROR("bad read offset 0x%08x\n", offset);
+ return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps exynos4210_fimd_mmio_ops = {
+ .read = exynos4210_fimd_read,
+ .write = exynos4210_fimd_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int exynos4210_fimd_load(void *opaque, int version_id)
+{
+ Exynos4210fimdState *s = (Exynos4210fimdState *)opaque;
+ int w;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+
+ for (w = 0; w < NUM_OF_WINDOWS; w++) {
+ exynos4210_fimd_update_win_bppmode(s, w);
+ }
+
+ /* Redraw the whole screen */
+ exynos4210_update_resolution(s);
+ exynos4210_fimd_invalidate(s);
+ exynos4210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+ FIMD_VIDCON0_ENVID_MASK);
+ return 0;
+}
+
+static const VMStateDescription exynos4210_fimd_window_vmstate = {
+ .name = "exynos4210.fimd_window",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(wincon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(buf_start, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(buf_end, Exynos4210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(keycon, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT32(keyalpha, Exynos4210fimdWindow),
+ VMSTATE_UINT32(winmap, Exynos4210fimdWindow),
+ VMSTATE_UINT32(blendeq, Exynos4210fimdWindow),
+ VMSTATE_UINT32(rtqoscon, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(palette, Exynos4210fimdWindow, 256),
+ VMSTATE_UINT32(shadow_buf_start, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_end, Exynos4210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_size, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(lefttop_y, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_x, Exynos4210fimdWindow),
+ VMSTATE_UINT16(rightbot_y, Exynos4210fimdWindow),
+ VMSTATE_UINT32(osdsize, Exynos4210fimdWindow),
+ VMSTATE_UINT32_ARRAY(alpha_val, Exynos4210fimdWindow, 2),
+ VMSTATE_UINT16(virtpage_width, Exynos4210fimdWindow),
+ VMSTATE_UINT16(virtpage_offsize, Exynos4210fimdWindow),
+ VMSTATE_BOOL(alpha_8bit, Exynos4210fimdWindow),
+ VMSTATE_UINT8(pal_format, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription exynos4210_fimd_vmstate = {
+ .name = "exynos4210.fimd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = exynos4210_fimd_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(vidcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(vidtcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(shadowcon, Exynos4210fimdState),
+ VMSTATE_UINT32(winchmap, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(vidintcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(dithmode, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(wpalcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32(trigcon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcon, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(colorgaincon, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(ldi_cmdcon, Exynos4210fimdState, 2),
+ VMSTATE_UINT32_ARRAY(sifccon, Exynos4210fimdState, 3),
+ VMSTATE_UINT32_ARRAY(huecoef_cr, Exynos4210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(huecoef_cb, Exynos4210fimdState, 4),
+ VMSTATE_UINT32(hueoffset, Exynos4210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcmd, Exynos4210fimdState, 12),
+ VMSTATE_STRUCT_ARRAY(window, Exynos4210fimdState, 5, 1,
+ exynos4210_fimd_window_vmstate, Exynos4210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int exynos4210_fimd_init(SysBusDevice *dev)
+{
+ Exynos4210fimdState *s = FROM_SYSBUS(Exynos4210fimdState, dev);
+
+ s->ifb = NULL;
+
+ sysbus_init_irq(dev, &s->irq[0]);
+ sysbus_init_irq(dev, &s->irq[1]);
+ sysbus_init_irq(dev, &s->irq[2]);
+
+ memory_region_init_io(&s->iomem, &exynos4210_fimd_mmio_ops, s,
+ "exynos4210.fimd", FIMD_REGS_SIZE);
+ sysbus_init_mmio(dev, &s->iomem);
+ s->console = graphic_console_init(exynos4210_fimd_update,
+ exynos4210_fimd_invalidate, NULL, NULL, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo exynos4210_fimd_info = {
+ .init = exynos4210_fimd_init,
+ .qdev.name = "exynos4210.fimd",
+ .qdev.size = sizeof(Exynos4210fimdState),
+ .qdev.vmsd = &exynos4210_fimd_vmstate,
+ .qdev.reset = exynos4210_fimd_reset,
+};
+
+static void exynos4210_fimd_register_devices(void)
+{
+ sysbus_register_withprop(&exynos4210_fimd_info);
+}
+
+device_init(exynos4210_fimd_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit.
2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-09 15:55 ` Paul Brook
0 siblings, 0 replies; 17+ messages in thread
From: Paul Brook @ 2011-12-09 15:55 UTC (permalink / raw)
To: qemu-devel; +Cc: m.kozlov, d.solodkiy, Evgeny Voevodin
> In previous version IRQ was lowered every time if enable bits were
> not set. If platform has splitted IRQ source to pass IRQ to two
> identical GICs simultaneously in first of which IRQ passing is
> enabled but in second is disabled, handling IRQ by second GIC would
> lower IRQ previously raised by first GIC.
> Linux kernel v3.0 faces this problem.
> The problem is avoided if IRQ is only lowered as result of
> transitioning enable bits to zeroes.
This is almost certainly wrong. IRQ lines are state based, not event based.
Re-asserting an existing level should always be a no-op.
My guess is your real bug it that your IRQ wiring is broken, and you have
multiple outputs connected to a single input. Don't do that. Ever. If you
need to connent multiple output pins to a single input pin then you need an
explicit device (e.g. an or gate) in between.
Paul
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2011-12-09 15:55 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2011-12-09 13:34 [Qemu-devel] [PATCH 00/15 V2] ARM: Samsung Exynos4210-based boards support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 01/15] ARM: Samsung exynos4210-based boards emulation Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 03/15] ARM: exynos4210: UART support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 04/15] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 05/15] ARM: exynos4210: IRQ subsystem support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 06/15] ARM: exynos4210: PWM support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 07/15] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 08/15] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
2011-12-09 15:55 ` Paul Brook
2011-12-09 13:34 ` [Qemu-devel] [PATCH 09/15] ARM: exynos4210: MCT support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 10/15] hw/exynos4210.c: Boot secondary CPU Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 11/15] hw/lan9118: Add basic 16-bit mode support Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 12/15] hw/exynos4210.c: Add LAN support for SMDKC210 Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 13/15] hw/sd.c, hw/sd.h: add receive ready query routine to SD/MMC API Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 14/15] ARM: exynos4210: added SD/MMC host controller Evgeny Voevodin
2011-12-09 13:34 ` [Qemu-devel] [PATCH 15/15] ARM: exynos4210: added display controller implementation Evgeny Voevodin
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.