* [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 11:01 ` Peter Maydell
2011-12-07 9:46 ` [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
` (13 subsequent siblings)
14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: Maksim Kozlov, d.solodkiy, Evgeny Voevodin
From: Maksim Kozlov <m.kozlov@samsung.com>
Added support of S5PC210 based boards - SMDKC210 and NURI.
CMU and UART basic implementation.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 1 +
hw/s5pc210.c | 284 +++++++++++++
hw/s5pc210.h | 66 +++
hw/s5pc210_cmu.c | 1144 +++++++++++++++++++++++++++++++++++++++++++++++++++++
hw/s5pc210_uart.c | 677 +++++++++++++++++++++++++++++++
5 files changed, 2172 insertions(+), 0 deletions(-)
create mode 100644 hw/s5pc210.c
create mode 100644 hw/s5pc210.h
create mode 100644 hw/s5pc210_cmu.c
create mode 100644 hw/s5pc210_uart.c
diff --git a/Makefile.target b/Makefile.target
index a111521..38fc364 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 += s5pc210.o s5pc210_cmu.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
new file mode 100644
index 0000000..d20ac95
--- /dev/null
+++ b/hw/s5pc210.c
@@ -0,0 +1,284 @@
+/*
+ * Samsung s5pc210 (aka Exynos4210) board 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>
+ *
+ * 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 "boards.h"
+#include "sysemu.h"
+#include "sysbus.h"
+#include "arm-misc.h"
+#include "exec-memory.h"
+#include "s5pc210.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 S5PC210_DRAM0_BASE_ADDR 0x40000000
+#define S5PC210_DRAM1_BASE_ADDR 0xa0000000
+#define S5PC210_DRAM_MAX_SIZE 0x60000000 /* 1.5 GB */
+
+#define S5PC210_IROM_BASE_ADDR 0x00000000
+#define S5PC210_IROM_SIZE 0x00010000 /* 64 KB */
+#define S5PC210_IROM_MIRROR_BASE_ADDR 0x02000000
+#define S5PC210_IROM_MIRROR_SIZE 0x00010000 /* 64 KB */
+
+#define S5PC210_IRAM_BASE_ADDR 0x02020000
+#define S5PC210_IRAM_SIZE 0x00020000 /* 128 KB */
+
+#define S5PC210_SFR_BASE_ADDR 0x10000000
+
+/* SFR Base Address for CMUs */
+#define S5PC210_CMU_BASE_ADDR 0x10030000
+
+/* UART's definitions */
+#define S5PC210_UART_BASE_ADDR 0x13800000
+#define S5PC210_UART_SHIFT 0x00010000
+
+#define S5PC210_UARTS_NUMBER 4
+
+#define S5PC210_UART_CHANNEL(addr) ((addr >> 16) & 0x7)
+#define S5PC210_UART0_FIFO_SIZE 256
+#define S5PC210_UART1_FIFO_SIZE 64
+#define S5PC210_UART2_FIFO_SIZE 16
+#define S5PC210_UART3_FIFO_SIZE 16
+#define S5PC210_UART4_FIFO_SIZE 64
+
+#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
+
+static struct arm_boot_info s5pc210_binfo = {
+ .loader_start = S5PC210_BASE_BOOT_ADDR,
+};
+
+static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
+ 0x09, 0x00, 0x00, 0x00 };
+
+enum s5pc210_board_type {
+ BOARD_S5PC210_NURI,
+ BOARD_S5PC210_SMDKC210,
+};
+
+enum s5pc210_mach_id {
+ MACH_NURI_ID = 0xD33,
+ MACH_SMDKC210_ID = 0xB16,
+};
+
+
+static void s5pc210_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 s5pc210_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_S5PC210_NURI:
+ s5pc210_binfo.board_id = MACH_NURI_ID;
+ break;
+ case BOARD_S5PC210_SMDKC210:
+ s5pc210_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, "s5pc210.chipid",
+ sizeof(chipid_and_omr), chipid_and_omr);
+ memory_region_set_readonly(chipid_mem, true);
+ memory_region_add_subregion(system_mem, S5PC210_SFR_BASE_ADDR, chipid_mem);
+
+ /* Internal ROM */
+ memory_region_init_ram(irom_mem, NULL, "s5pc210.irom", S5PC210_IROM_SIZE);
+ memory_region_set_readonly(irom_mem, true);
+ memory_region_add_subregion(system_mem, S5PC210_IROM_BASE_ADDR, irom_mem);
+ /* mirror of 0x0 – 0x10000 */
+ memory_region_init_alias(irom_alias_mem, "s5pc210.irom_alias",
+ irom_mem, 0, S5PC210_IROM_SIZE);
+ memory_region_set_readonly(irom_alias_mem, true);
+ memory_region_add_subregion(system_mem, S5PC210_IROM_MIRROR_BASE_ADDR,
+ irom_alias_mem);
+
+ /* Internal RAM */
+ memory_region_init_ram(iram_mem, NULL, "s5pc210.iram", S5PC210_IRAM_SIZE);
+ memory_region_set_readonly(iram_mem, false);
+ memory_region_add_subregion(system_mem, S5PC210_IRAM_BASE_ADDR, iram_mem);
+
+ /* DRAM */
+ mem_size = ram_size;
+ if (mem_size > S5PC210_DRAM_MAX_SIZE) {
+ dram1_mem = g_new(MemoryRegion, 1);
+ memory_region_init_ram(dram1_mem, NULL, "s5pc210.dram1",
+ mem_size - S5PC210_DRAM_MAX_SIZE);
+ memory_region_add_subregion(system_mem, S5PC210_DRAM1_BASE_ADDR,
+ dram1_mem);
+ mem_size = S5PC210_DRAM_MAX_SIZE;
+ }
+ memory_region_init_ram(dram0_mem, NULL, "s5pc210.dram0", mem_size);
+ memory_region_add_subregion(system_mem, S5PC210_DRAM0_BASE_ADDR,
+ dram0_mem);
+
+ /* CMU */
+ sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
+
+ /*** UARTs ***/
+ for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
+
+ uint32_t addr = S5PC210_UART_BASE_ADDR + S5PC210_UART_SHIFT * n;
+ int channel = S5PC210_UART_CHANNEL(addr);
+ qemu_irq uart_irq;
+ int fifo_size = 0;
+
+ switch (channel) {
+ case 0:
+ fifo_size = S5PC210_UART0_FIFO_SIZE;
+ break;
+ case 1:
+ fifo_size = S5PC210_UART1_FIFO_SIZE;
+ break;
+ case 2:
+ fifo_size = S5PC210_UART2_FIFO_SIZE;
+ break;
+ case 3:
+ fifo_size = S5PC210_UART3_FIFO_SIZE;
+ break;
+ case 4:
+ fifo_size = S5PC210_UART4_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;
+
+ s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
+ }
+
+ /*** Load kernel ***/
+
+ s5pc210_binfo.ram_size = ram_size;
+ s5pc210_binfo.nb_cpus = smp_cpus;
+ s5pc210_binfo.kernel_filename = kernel_filename;
+ s5pc210_binfo.initrd_filename = initrd_filename;
+ s5pc210_binfo.kernel_cmdline = kernel_cmdline;
+
+
+ arm_load_kernel(first_cpu, &s5pc210_binfo);
+}
+
+static void s5pc210_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)
+{
+ s5pc210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+ initrd_filename, cpu_model, BOARD_S5PC210_NURI);
+}
+
+static void s5pc210_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)
+{
+ s5pc210_init(ram_size, boot_device, kernel_filename, kernel_cmdline,
+ initrd_filename, cpu_model, BOARD_S5PC210_SMDKC210);
+}
+
+
+static QEMUMachine s5pc210_nuri_machine = {
+ .name = "s5pc210-nuri",
+ .desc = "Samsung Exynos4210 NURI board",
+ .init = s5pc210_nuri_init,
+ .max_cpus = S5PC210_MAX_CPUS,
+};
+
+static QEMUMachine s5pc210_smdkc210_machine = {
+ .name = "s5pc210-smdkc210",
+ .desc = "Samsung Exynos4210 SMDKC210 board",
+ .init = s5pc210_smdkc210_init,
+ .max_cpus = S5PC210_MAX_CPUS,
+};
+
+static void s5pc210_machine_init(void)
+{
+ qemu_register_machine(&s5pc210_nuri_machine);
+ qemu_register_machine(&s5pc210_smdkc210_machine);
+}
+
+machine_init(s5pc210_machine_init);
diff --git a/hw/s5pc210.h b/hw/s5pc210.h
new file mode 100644
index 0000000..bbf927c
--- /dev/null
+++ b/hw/s5pc210.h
@@ -0,0 +1,66 @@
+/*
+ * Samsung s5pc210 (aka Exynos4210) board 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 S5PC210_H_
+#define S5PC210_H_
+
+#include "qemu-common.h"
+
+#define S5PC210_MAX_CPUS 2
+
+/*
+ * Interface for s5pc210 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
+} S5pc210CmuClock;
+
+uint64_t s5pc210_cmu_get_rate(S5pc210CmuClock clock);
+
+/*
+ * s5pc210 UART
+ */
+
+DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq);
+
+#endif /* S5PC210_H_ */
diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
new file mode 100644
index 0000000..12fba47
--- /dev/null
+++ b/hw/s5pc210_cmu.c
@@ -0,0 +1,1144 @@
+/*
+ * s5pc210 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 "s5pc210.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 S5pc210CmuReg {
+ const char *name; /* for debugging */
+ uint32_t offset;
+ uint32_t reset_value;
+} S5pc210CmuReg;
+
+
+static S5pc210CmuReg s5pc210_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 S5PC210_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[S5PC210_CMU_REGS_MEM_SIZE];
+
+} S5pc210CmuState;
+
+#define SOURCES_NUMBER 9
+#define RECIPIENTS_NUMBER 9
+
+typedef struct S5pc210CmuClockState {
+
+ const char *name;
+ uint64_t rate;
+
+ /* Current source clock */
+ struct S5pc210CmuClockState *source;
+ /*
+ * Available sources. Their order must correspond to CLK_SRC_ register
+ */
+ struct S5pc210CmuClockState *sources[SOURCES_NUMBER];
+ /* Who uses this clock? */
+ struct S5pc210CmuClockState *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;
+
+} S5pc210CmuClockState;
+
+
+/* Clocks from Clock Pads */
+
+/* It should be used only for testing purposes. XOM_0 is 0 */
+static S5pc210CmuClockState xxti = {
+ .name = "XXTI",
+ .rate = 24000000,
+};
+
+/* Main source. XOM_0 is 1 */
+static S5pc210CmuClockState xusbxti = {
+ .name = "XUSBXTI",
+ .rate = 24000000,
+};
+
+/* PLLs */
+
+static S5pc210CmuClockState mpll = {
+ .name = "MPLL",
+ .source = (XOM_0 ? &xusbxti : &xxti),
+};
+
+static S5pc210CmuClockState apll = {
+ .name = "APLL",
+ .source = (XOM_0 ? &xusbxti : &xxti),
+};
+
+
+/**/
+static S5pc210CmuClockState sclk_mpll = {
+ .name = "SCLK_MPLL",
+ .sources = {XOM_0 ? &xusbxti : &xxti, &mpll},
+ .src_reg = CLK_SRC_CPU,
+ .mux_shift = MUX_MPLL_SEL_SHIFT,
+};
+
+static S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClockState 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 S5pc210CmuClock enumerator
+ * which is defined in s5pc210.h file
+ *
+ */
+static S5pc210CmuClockState *s5pc210_clock[] = {
+ &xxti,
+ &xusbxti,
+ &apll,
+ &mpll,
+ &sclk_apll,
+ &sclk_mpll,
+ &aclk_100,
+ &sclk_uart0,
+ &sclk_uart1,
+ &sclk_uart2,
+ &sclk_uart3,
+ &sclk_uart4,
+ NULL
+};
+
+
+uint64_t s5pc210_cmu_get_rate(S5pc210CmuClock clock)
+{
+ return s5pc210_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 *s5pc210_cmu_regname(target_phys_addr_t offset)
+{
+
+ int regs_number = sizeof(s5pc210_cmu_regs)/sizeof(S5pc210CmuReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ if (offset == s5pc210_cmu_regs[i].offset) {
+ return s5pc210_cmu_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+static void s5pc210_cmu_set_pll(void *opaque, target_phys_addr_t offset)
+{
+ S5pc210CmuState *s = (S5pc210CmuState *)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("s5pc210_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("s5pc210_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("s5pc210_cmu_set_pll: Bad offset 0x%x\n", (int)offset);
+ }
+
+ s->reg[I_(offset)] |= PLL_LOCKED_MASK;
+}
+
+
+static void s5pc210_cmu_set_rate(void *opaque, S5pc210CmuClockState *clock)
+{
+ S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+ int i;
+
+ /* Rates of PLLs are calculated differently than ordinary clocks rates */
+ if (clock == &mpll) {
+ s5pc210_cmu_set_pll(s, MPLL_CON0);
+ } else if (clock == &apll) {
+ s5pc210_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,
+ s5pc210_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 {
+
+ S5pc210CmuClockState *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;
+ }
+
+ s5pc210_cmu_set_rate(s, recipient_clock);
+
+ i++;
+ } while (i < RECIPIENTS_NUMBER);
+}
+
+
+static uint64_t s5pc210_cmu_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+
+ if (offset > (S5PC210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("s5pc210_cmu_read: Bad offset 0x%x\n", (int)offset);
+ }
+
+ PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n",
+ offset, s5pc210_cmu_regname(offset), s->reg[I_(offset)]);
+
+ return s->reg[I_(offset)];
+}
+
+
+static void s5pc210_cmu_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+ uint32_t pre_val;
+
+ if (offset > (S5PC210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("s5pc210_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, s5pc210_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;
+ s5pc210_cmu_set_rate(s, &apll);
+ break;
+ case MPLL_CON0:
+ val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK);
+ s->reg[I_(offset)] = val;
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+
+ if (val & MUX_HPM_SEL) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+ }
+ }
+ }
+ break;
+ case CLK_DIV_CPU0:
+ s5pc210_cmu_set_rate(s, &sclk_apll);
+ s5pc210_cmu_set_rate(s, &sclk_mpll);
+ break;
+ case CLK_SRC_TOP0:
+ case CLK_DIV_TOP:
+ s5pc210_cmu_set_rate(s, &aclk_100);
+ break;
+ default:
+ break;
+ }
+}
+
+
+static const MemoryRegionOps s5pc210_cmu_ops = {
+ .read = s5pc210_cmu_read,
+ .write = s5pc210_cmu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+
+static void s5pc210_cmu_reset(void *opaque)
+{
+ S5pc210CmuState *s = (S5pc210CmuState *)opaque;
+ int i = 0, j = 0, n = 0;
+ int regs_number = sizeof(s5pc210_cmu_regs)/sizeof(S5pc210CmuReg);
+ uint32_t index = 0;
+
+ /* Set default values for registers */
+ for (i = 0; i < regs_number; i++) {
+ index = (s5pc210_cmu_regs[i].offset) / sizeof(uint32_t);
+ s->reg[index] = s5pc210_cmu_regs[i].reset_value;
+ }
+
+ /* clear recipients array from previous reset */
+ for (i = 0; i < CLOCKS_NUMBER; i++) {
+ bzero(s5pc210_clock[i]->recipients,
+ RECIPIENTS_NUMBER * sizeof(S5pc210CmuClockState *));
+ }
+
+ /*
+ * 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 s5pc210_clock */
+
+ PRINT_DEBUG("[SOURCES] %s: ", s5pc210_clock[i]->name);
+
+ j = 0;
+ do { /* visit all sources for current clock (s5pc210_clock[i]) */
+
+ if ((s5pc210_clock[i]->sources[j] == NULL)) {
+
+ if (j == 0) { /* check if we have empty '.sources[]' array */
+ if (s5pc210_clock[i]->source != NULL) {
+ s5pc210_clock[i]->sources[j] = s5pc210_clock[i]->source;
+ } else {
+ /*
+ * We haven't any defined sources for this clock. Error
+ * during definition of appropriate clock structure
+ *
+ */
+ if ((s5pc210_clock[i] != &xusbxti) &&
+ (s5pc210_clock[i] != &xxti)) {
+
+ hw_error("s5pc210_cmu_reset:"
+ "There aren't any sources for %s clock!\n",
+ s5pc210_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", s5pc210_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 ((s5pc210_clock[i]->sources[j]->recipients[n]) == NULL) {
+ s5pc210_clock[i]->sources[j]->recipients[n] =
+ s5pc210_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: ", s5pc210_clock[i]->name);
+ for (j = 0;
+ (j < RECIPIENTS_NUMBER) &&
+ ((s5pc210_clock[i]->recipients[j]) != NULL);
+ j++) {
+ PRINT_DEBUG_SIMPLE("%s ", s5pc210_clock[i]->recipients[j]->name);
+ }
+ PRINT_DEBUG_SIMPLE("\n");
+ }
+#endif
+
+ s5pc210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti);
+}
+
+static const VMStateDescription vmstate_s5pc210_cmu = {
+ .name = "s5pc210.cmu",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ /*
+ * TODO: Maybe we should save S5pc210CmuClockState structs as well
+ */
+ VMSTATE_UINT32_ARRAY(reg, S5pc210CmuState,
+ S5PC210_CMU_REGS_MEM_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int s5pc210_cmu_init(SysBusDevice *dev)
+{
+ S5pc210CmuState *s = FROM_SYSBUS(S5pc210CmuState, dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
+ S5PC210_CMU_REGS_MEM_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ qemu_register_reset(s5pc210_cmu_reset, s);
+
+ vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_cmu, s);
+
+ s5pc210_cmu_reset(s);
+
+ return 0;
+}
+
+
+static void s5pc210_cmu_register(void)
+{
+ sysbus_register_dev("s5pc210.cmu",
+ sizeof(S5pc210CmuState),
+ s5pc210_cmu_init);
+}
+
+
+device_init(s5pc210_cmu_register)
diff --git a/hw/s5pc210_uart.c b/hw/s5pc210_uart.c
new file mode 100644
index 0000000..d1fbddc
--- /dev/null
+++ b/hw/s5pc210_uart.c
@@ -0,0 +1,677 @@
+/*
+ * s5pc210 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 "s5pc210.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 S5pc210UartReg {
+ const char *name; /* the only reason is the debug output */
+ target_phys_addr_t offset;
+ uint32_t reset_value;
+} S5pc210UartReg;
+
+static S5pc210UartReg s5pc210_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 S5PC210_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;
+} S5pc210UartFIFO;
+
+typedef struct {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg[S5PC210_UART_REGS_MEM_SIZE];
+ S5pc210UartFIFO rx;
+ S5pc210UartFIFO tx;
+
+ CharDriverState *chr;
+ qemu_irq irq;
+
+ uint32_t channel;
+
+} S5pc210UartState;
+
+
+#ifdef DEBUG_UART
+/* The only meaning of life - debugging. This functions should be only used
+ * inside PRINT_DEBUG_... macroses
+ */
+static const char *s5pc210_uart_regname(target_phys_addr_t offset)
+{
+
+ int regs_number = sizeof(s5pc210_uart_regs)/sizeof(S5pc210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ if (offset == s5pc210_uart_regs[i].offset) {
+ return s5pc210_uart_regs[i].name;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+
+static void fifo_store(S5pc210UartFIFO *q, uint8_t ch)
+{
+ q->data[q->sp] = ch;
+ q->sp = (q->sp + 1) % q->size;
+}
+
+static uint8_t fifo_retrieve(S5pc210UartFIFO *q)
+{
+ uint8_t ret = q->data[q->rp];
+ q->rp = (q->rp + 1) % q->size;
+ return ret;
+}
+
+static int fifo_elements_number(S5pc210UartFIFO *q)
+{
+ if (q->sp < q->rp) {
+ return q->size - q->rp + q->sp;
+ }
+
+ return q->sp - q->rp;
+}
+
+static int fifo_empty_elements_number(S5pc210UartFIFO *q)
+{
+ return q->size - fifo_elements_number(q);
+}
+
+static void fifo_reset(S5pc210UartFIFO *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 s5pc210_uart_Tx_FIFO_trigger_level(S5pc210UartState *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 s5pc210_uart_update_irq(S5pc210UartState *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 <= s5pc210_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 s5pc210_uart_update_parameters(S5pc210UartState *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 = s5pc210_cmu_get_rate(SCLK_UART0); break;
+ case 1:
+ uclk_rate = s5pc210_cmu_get_rate(SCLK_UART1); break;
+ case 2:
+ uclk_rate = s5pc210_cmu_get_rate(SCLK_UART2); break;
+ case 3:
+ uclk_rate = s5pc210_cmu_get_rate(SCLK_UART3); break;
+ case 4:
+ uclk_rate = s5pc210_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 s5pc210_uart_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ S5pc210UartState *s = (S5pc210UartState *)opaque;
+ uint8_t ch;
+
+ if (offset > (S5PC210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("s5pc210_cmu_write: Bad offset 0x%x\n", (int)offset);
+ }
+
+ PRINT_DEBUG_EXTEND("UART%d: <0x%04x> %s <- 0x%08llx\n",
+ s->channel, offset, s5pc210_uart_regname(offset), val);
+
+ switch (offset) {
+ case ULCON:
+ case UBRDIV:
+ case UFRACVAL:
+ s->reg[I_(offset)] = val;
+ s5pc210_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;
+ s5pc210_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)]);
+ s5pc210_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, s5pc210_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;
+ s5pc210_uart_update_irq(s);
+ break;
+ case UCON:
+ case UMCON:
+ default:
+ s->reg[I_(offset)] = val;
+ break;
+ }
+}
+static uint64_t s5pc210_uart_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210UartState *s = (S5pc210UartState *)opaque;
+ uint32_t res;
+
+ if (offset > (S5PC210_UART_REGS_MEM_SIZE - sizeof(uint32_t))) {
+ hw_error("s5pc210_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;
+ s5pc210_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, s5pc210_uart_regname(offset), offset);
+ break;
+ default:
+ return s->reg[I_(offset)];
+ break;
+ }
+
+ return 0;
+}
+
+
+static const MemoryRegionOps s5pc210_uart_ops = {
+ .read = s5pc210_uart_read,
+ .write = s5pc210_uart_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_uart_can_receive(void *opaque)
+{
+ S5pc210UartState *s = (S5pc210UartState *)opaque;
+
+ return fifo_empty_elements_number(&s->rx);
+}
+
+
+static void s5pc210_uart_receive(void *opaque, const uint8_t *buf, int size)
+{
+ S5pc210UartState *s = (S5pc210UartState *)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;
+ }
+
+ s5pc210_uart_update_irq(s);
+}
+
+
+static void s5pc210_uart_event(void *opaque, int event)
+{
+ /*
+ * TODO: Implement this.
+ *
+ */
+}
+
+
+static void s5pc210_uart_reset(void *opaque)
+{
+ S5pc210UartState *s = (S5pc210UartState *)opaque;
+ int regs_number = sizeof(s5pc210_uart_regs)/sizeof(S5pc210UartReg);
+ int i;
+
+ for (i = 0; i < regs_number; i++) {
+ s->reg[I_(s5pc210_uart_regs[i].offset)] =
+ s5pc210_uart_regs[i].reset_value;
+ }
+
+ fifo_reset(&s->rx);
+ fifo_reset(&s->tx);
+
+ PRINT_DEBUG_EXTEND("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size);
+}
+
+
+static const VMStateDescription vmstate_s5pc210_uart = {
+ .name = "s5pc210.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, S5pc210UartState,
+ S5PC210_UART_REGS_MEM_SIZE),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
+ int fifo_size,
+ int channel,
+ CharDriverState *chr,
+ qemu_irq irq)
+{
+ DeviceState *dev;
+ SysBusDevice *bus;
+ S5pc210UartState *state;
+
+ dev = qdev_create(NULL, "s5pc210.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("s5pc210.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);
+
+ 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);
+
+ state = FROM_SYSBUS(S5pc210UartState, bus);
+
+ state->rx.size = fifo_size;
+ state->tx.size = fifo_size;
+
+ return dev;
+}
+
+static int s5pc210_uart_init(SysBusDevice *dev)
+{
+ S5pc210UartState *s = FROM_SYSBUS(S5pc210UartState, dev);
+
+ /* memory mapping */
+ memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
+ S5PC210_UART_REGS_MEM_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ sysbus_init_irq(dev, &s->irq);
+
+ qemu_chr_add_handlers(s->chr, s5pc210_uart_can_receive,
+ s5pc210_uart_receive, s5pc210_uart_event, s);
+
+ vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_uart, s);
+
+ qemu_register_reset(s5pc210_uart_reset, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo s5pc210_uart_info = {
+ .init = s5pc210_uart_init,
+ .qdev.name = "s5pc210.uart",
+ .qdev.size = sizeof(S5pc210UartState),
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_CHR("chardev", S5pc210UartState, chr),
+ DEFINE_PROP_UINT32("channel", S5pc210UartState, channel, 0),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void s5pc210_uart_register(void)
+{
+ sysbus_register_withprop(&s5pc210_uart_info);
+}
+
+device_init(s5pc210_uart_register)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards
2011-12-07 9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
@ 2011-12-07 11:01 ` Peter Maydell
0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 11:01 UTC (permalink / raw)
To: Evgeny Voevodin; +Cc: Maksim Kozlov, qemu-devel, d.solodkiy
If you split this patch up so it is:
1 cmu
2 uart
3 initial basic board
this will be easier to review than a 2000 line patch.
> +DeviceState *s5pc210_uart_create(target_phys_addr_t addr,
> + int fifo_size,
> + int channel,
> + CharDriverState *chr,
> + qemu_irq irq)
> +{
> + DeviceState *dev;
> + SysBusDevice *bus;
> + S5pc210UartState *state;
> +
> + dev = qdev_create(NULL, "s5pc210.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("s5pc210.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);
> +
> + 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);
> +
> + state = FROM_SYSBUS(S5pc210UartState, bus);
> +
> + state->rx.size = fifo_size;
> + state->tx.size = fifo_size;
This is wrong. Code at the "qdev_create(something)" level should
not then reach in and mess with the underlying state structure
of the device it's just created. fifo_size should probably be passed
to the device as a qdev property.
> +static int s5pc210_uart_init(SysBusDevice *dev)
> +{
> + S5pc210UartState *s = FROM_SYSBUS(S5pc210UartState, dev);
> +
> + /* memory mapping */
> + memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
> + S5PC210_UART_REGS_MEM_SIZE);
> + sysbus_init_mmio_region(dev, &s->iomem);
> +
> + sysbus_init_irq(dev, &s->irq);
> +
> + qemu_chr_add_handlers(s->chr, s5pc210_uart_can_receive,
> + s5pc210_uart_receive, s5pc210_uart_event, s);
> +
> + vmstate_register(&dev->qdev, -1, &vmstate_s5pc210_uart, s);
> +
> + qemu_register_reset(s5pc210_uart_reset, s);
You can set these up using .qdev.reset and .qdev.vmsd fields
in your SysBusDeviceInfo struct, which is cleaner than calling
functions to register them.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support Evgeny Voevodin
` (12 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Samsung s5pc210 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] 25+ messages in thread
* [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 01/14] ARM: s5pc210: Basic support of s5pc210 boards Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 02/14] hw/sysbus.h: Increase maximum number of device IRQs Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support Evgeny Voevodin
` (11 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 3 +-
hw/s5pc210.c | 163 +++++++++++++++++++-
hw/s5pc210.h | 39 +++++
hw/s5pc210_combiner.c | 381 +++++++++++++++++++++++++++++++++++++++++++++
hw/s5pc210_gic.c | 411 +++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 995 insertions(+), 2 deletions(-)
create mode 100644 hw/s5pc210_combiner.c
create mode 100644 hw/s5pc210_gic.c
diff --git a/Makefile.target b/Makefile.target
index 38fc364..ed338a8 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o
+obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
+ s5pc210_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/s5pc210.c b/hw/s5pc210.c
index d20ac95..99e9b1b 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -61,6 +61,8 @@
#define S5PC210_SFR_BASE_ADDR 0x10000000
+#define S5PC210_SMP_PRIVATE_BASE_ADDR 0x10500000
+
/* SFR Base Address for CMUs */
#define S5PC210_CMU_BASE_ADDR 0x10030000
@@ -76,6 +78,16 @@
#define S5PC210_UART2_FIFO_SIZE 16
#define S5PC210_UART3_FIFO_SIZE 16
#define S5PC210_UART4_FIFO_SIZE 64
+/* Interrupt Group of External Interrupt Combiner for UART */
+#define S5PC210_UART_INTG 26
+
+/* External GIC */
+#define S5PC210_EXT_GIC_CPU_BASE_ADDR 0x10480000
+#define S5PC210_EXT_GIC_DIST_BASE_ADDR 0x10490000
+
+/* Combiner */
+#define S5PC210_EXT_COMBINER_BASE_ADDR 0x10440000
+#define S5PC210_INT_COMBINER_BASE_ADDR 0x10448000
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
@@ -96,6 +108,88 @@ enum s5pc210_mach_id {
MACH_SMDKC210_ID = 0xB16,
};
+static void s5pc210_combiner_get_gpioin(S5pc210Irq *irqs,
+ DeviceState *dev,
+ int ext)
+{
+ int n;
+ int bit;
+ int max;
+ qemu_irq *irq;
+
+ max = ext ? S5PC210_MAX_EXT_COMBINER_IN_IRQ :
+ S5PC210_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 = S5PC210_COMBINER_GET_BIT_NUM(n);
+
+ switch (n) {
+ /* MDNIE_LCD1 INTG1*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(1, 0) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(1, 3):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(0, bit + 4)]);
+ continue;
+ break;
+
+ /* TMU INTG3*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(3, 4):
+ irq[n] =
+ qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(2, bit)]);
+ continue;
+ break;
+
+ /* LCD1 INTG12*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(12, 0) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(12, 3):
+ irq[n] =
+ qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(11, bit + 4)]);
+ continue;
+
+ /* Multi-Core Timer INTG12*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(12, 4) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(12, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG35*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(35, 4) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(35, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG51*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(51, 4) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(51, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+
+ /* Multi-Core Timer INTG53*/
+ case S5PC210_COMBINER_GET_IRQ_NUM(53, 4) ...
+ S5PC210_COMBINER_GET_IRQ_NUM(53, 8):
+ irq[n] = qemu_irq_split(qdev_get_gpio_in(dev, n),
+ irq[S5PC210_COMBINER_GET_IRQ_NUM(1, bit + 4)]);
+ continue;
+ break;
+ }
+
+ irq[n] = qdev_get_gpio_in(dev, n);
+ }
+}
static void s5pc210_init(ram_addr_t ram_size,
const char *boot_device,
@@ -113,8 +207,12 @@ static void s5pc210_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;
+ S5pc210Irq *irqs;
+ qemu_irq *irq_table;
qemu_irq *irqp;
qemu_irq cpu_irq[4];
+ DeviceState *dev;
+ SysBusDevice *busdev;
ram_addr_t mem_size;
int n;
@@ -132,6 +230,12 @@ static void s5pc210_init(ram_addr_t ram_size,
cpu_model = "cortex-a9";
}
+ irqs = g_malloc0(sizeof(S5pc210Irq));
+ 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) {
@@ -148,6 +252,63 @@ static void s5pc210_init(ram_addr_t ram_size,
cpu_irq[n] = irqp[ARM_PIC_CPU_IRQ];
}
+ irq_table = s5pc210_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, S5PC210_SMP_PRIVATE_BASE_ADDR);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ }
+ for (n = 0; n < S5PC210_INT_GIC_NIRQ; n++) {
+ irqs->int_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* External GIC */
+ dev = qdev_create(NULL, "s5pc210_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, S5PC210_EXT_GIC_CPU_BASE_ADDR);
+ /* Map Distributer interface */
+ sysbus_mmio_map(busdev, 1, S5PC210_EXT_GIC_DIST_BASE_ADDR);
+ for (n = 0; n < smp_cpus; n++) {
+ sysbus_connect_irq(busdev, n, cpu_irq[n]);
+ }
+ for (n = 0; n < S5PC210_EXT_GIC_NIRQ; n++) {
+ irqs->ext_gic_irq[n] = qdev_get_gpio_in(dev, n);
+ }
+
+ /* Internal Interrupt Combiner */
+ dev = qdev_create(NULL, "s5pc210.combiner");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ for (n = 0; n < S5PC210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, irqs->int_gic_irq[n]);
+ }
+ s5pc210_combiner_get_gpioin(irqs, dev, 0);
+ sysbus_mmio_map(busdev, 0, S5PC210_INT_COMBINER_BASE_ADDR);
+
+ /* External Interrupt Combiner */
+ dev = qdev_create(NULL, "s5pc210.combiner");
+ qdev_init_nofail(dev);
+ busdev = sysbus_from_qdev(dev);
+ for (n = 0; n < S5PC210_MAX_INT_COMBINER_OUT_IRQ; n++) {
+ sysbus_connect_irq(busdev, n, irqs->ext_gic_irq[n]);
+ }
+ s5pc210_combiner_get_gpioin(irqs, dev, 1);
+ sysbus_mmio_map(busdev, 0, S5PC210_EXT_COMBINER_BASE_ADDR);
+ qdev_prop_set_uint32(dev, "external", 1);
+
+ /* Initialize board IRQs. */
+ s5pc210_init_board_irqs(irqs);
+
/*** Memory ***/
/* Chip-ID and OMR */
@@ -225,7 +386,7 @@ static void s5pc210_init(ram_addr_t ram_size,
continue;
}
- uart_irq = NULL;
+ uart_irq = irq_table[s5pc210_get_irq(S5PC210_UART_INTG, channel)];
s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
diff --git a/hw/s5pc210.h b/hw/s5pc210.h
index bbf927c..412d004 100644
--- a/hw/s5pc210.h
+++ b/hw/s5pc210.h
@@ -32,6 +32,45 @@
#define S5PC210_MAX_CPUS 2
/*
+ * s5pc210 IRQ subsystem stub definitions.
+ */
+
+#define S5PC210_MAX_INT_COMBINER_OUT_IRQ 64
+#define S5PC210_MAX_EXT_COMBINER_OUT_IRQ 16
+#define S5PC210_MAX_INT_COMBINER_IN_IRQ (S5PC210_MAX_INT_COMBINER_OUT_IRQ * 8)
+#define S5PC210_MAX_EXT_COMBINER_IN_IRQ (S5PC210_MAX_EXT_COMBINER_OUT_IRQ * 8)
+
+#define S5PC210_COMBINER_GET_IRQ_NUM(grp, bit) ((grp)*8 + (bit))
+#define S5PC210_COMBINER_GET_GRP_NUM(irq) ((irq) / 8)
+#define S5PC210_COMBINER_GET_BIT_NUM(irq) \
+ ((irq) - 8 * S5PC210_COMBINER_GET_GRP_NUM(irq))
+
+/* IRQs number for external and internal GIC */
+#define S5PC210_EXT_GIC_NIRQ (160-32)
+#define S5PC210_INT_GIC_NIRQ 64
+
+typedef struct S5pc210Irq {
+ qemu_irq int_combiner_irq[S5PC210_MAX_INT_COMBINER_IN_IRQ];
+ qemu_irq ext_combiner_irq[S5PC210_MAX_EXT_COMBINER_IN_IRQ];
+ qemu_irq int_gic_irq[S5PC210_INT_GIC_NIRQ];
+ qemu_irq ext_gic_irq[S5PC210_EXT_GIC_NIRQ];
+ qemu_irq board_irqs[S5PC210_MAX_INT_COMBINER_IN_IRQ];
+} S5pc210Irq;
+
+/* Initialize s5pc210 IRQ subsystem stub */
+qemu_irq *s5pc210_init_irq(S5pc210Irq *env);
+
+/* Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs */
+void s5pc210_init_board_irqs(S5pc210Irq *s);
+
+/* Get IRQ number from s5pc210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ * grp - group number
+ * bit - bit number inside group */
+uint32_t s5pc210_get_irq(uint32_t grp, uint32_t bit);
+
+/*
* Interface for s5pc210 Clock Management Units (CMUs)
*/
diff --git a/hw/s5pc210_combiner.c b/hw/s5pc210_combiner.c
new file mode 100644
index 0000000..04ae024
--- /dev/null
+++ b/hw/s5pc210_combiner.c
@@ -0,0 +1,381 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.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 S5pc210CombinerState {
+ 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];
+} S5pc210CombinerState;
+
+static const VMStateDescription VMState_S5pc210CombinerGroupState = {
+ .name = "s5pc210.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_S5pc210Combiner = {
+ .name = "s5pc210.combiner",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT_ARRAY(group, S5pc210CombinerState, IIC_NGRP, 0,
+ VMState_S5pc210CombinerGroupState, CombinerGroupState),
+ VMSTATE_UINT32_ARRAY(reg_set, S5pc210CombinerState,
+ IIC_REGSET_SIZE),
+ VMSTATE_UINT32_ARRAY(icipsr, S5pc210CombinerState, 2),
+ VMSTATE_UINT32(external, S5pc210CombinerState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint64_t s5pc210_combiner_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)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("s5pc210.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("s5pc210.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 s5pc210_combiner_update(void *opaque, uint8_t group_n)
+{
+ struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)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 s5pc210_combiner_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)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("s5pc210.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("s5pc210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (reg_n > 1) {
+ hw_error("s5pc210.combiner: unallowed write access at offset 0x"
+ TARGET_FMT_plx "\n", offset);
+ return;
+ }
+
+ if (offset >> 2 >= IIC_REGSET_SIZE) {
+ hw_error("s5pc210.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;
+
+ s5pc210_combiner_update(s, grp_quad_base_n);
+ s5pc210_combiner_update(s, grp_quad_base_n+1);
+ s5pc210_combiner_update(s, grp_quad_base_n+2);
+ s5pc210_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);
+
+ s5pc210_combiner_update(s, grp_quad_base_n);
+ s5pc210_combiner_update(s, grp_quad_base_n+1);
+ s5pc210_combiner_update(s, grp_quad_base_n+2);
+ s5pc210_combiner_update(s, grp_quad_base_n+3);
+ break;
+ default:
+ hw_error("s5pc210.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 s5pc210_combiner_handler(void *opaque, int irq, int level)
+{
+ struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+ uint8_t bit_n, group_n;
+
+ group_n = get_combiner_group_and_bit(irq, &bit_n);
+
+ if (s->external && group_n >= S5PC210_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);
+ }
+
+ s5pc210_combiner_update(s, group_n);
+
+ return;
+}
+
+static void s5pc210_combiner_reset(void *opaque)
+{
+ struct S5pc210CombinerState *s = (struct S5pc210CombinerState *)opaque;
+
+ 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 s5pc210_combiner_ops = {
+ .read = s5pc210_combiner_read,
+ .write = s5pc210_combiner_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * Internal Combiner initialization.
+ */
+static int s5pc210_combiner_init(SysBusDevice *dev)
+{
+ unsigned int i;
+ struct S5pc210CombinerState *s =
+ FROM_SYSBUS(struct S5pc210CombinerState, dev);
+
+ /* Allocate general purpose input signals and connect a handler to each of
+ * them */
+ qdev_init_gpio_in(&s->busdev.qdev, s5pc210_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, &s5pc210_combiner_ops, s,
+ "s5pc210-combiner", IIC_REGION_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ s5pc210_combiner_reset(s);
+ vmstate_register(NULL, -1, &VMState_S5pc210Combiner, s);
+ return 0;
+}
+
+static SysBusDeviceInfo s5pc210_combiner_info = {
+ .qdev.name = "s5pc210.combiner",
+ .qdev.size = sizeof(struct S5pc210CombinerState),
+ .qdev.vmsd = &VMState_S5pc210Combiner,
+ .init = s5pc210_combiner_init,
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT32("external",
+ struct S5pc210CombinerState,
+ external,
+ 0),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void s5pc210_combiner_register_devices(void)
+{
+ sysbus_register_withprop(&s5pc210_combiner_info);
+}
+
+device_init(s5pc210_combiner_register_devices)
diff --git a/hw/s5pc210_gic.c b/hw/s5pc210_gic.c
new file mode 100644
index 0000000..c6f18d4
--- /dev/null
+++ b/hw/s5pc210_gic.c
@@ -0,0 +1,411 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.h"
+
+//#define DEBUG_S5PC210_IRQ
+//#define DEBUG_S5PC210_GIC
+
+#ifdef DEBUG_S5PC210_IRQ
+#define DPRINTF_S5PC210_GIC(fmt, ...) \
+ do { fprintf(stdout, "S5PC210_IRQ: [%24s:%5d] " fmt, __func__, \
+ __LINE__, ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_S5PC210_GIC(fmt, ...) do {} while (0)
+#endif
+
+#ifdef DEBUG_S5PC210_GIC
+#define DPRINTF_S5PC210_GIC(fmt, ...) \
+ do { fprintf(stdout, "EXT_GIC: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF_S5PC210_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 S5PC210_MAX_EXT_COMBINER_OUT_IRQ,
+ * which is INTG16 in Internal Interrupt Combiner.
+ */
+
+static uint32_t
+combiner_grp_to_gic_id[64-S5PC210_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 S5PC210_MAX_CPUS
+
+#define S5PC210_GIC_CPU_REGION_SIZE 0x8050
+#define S5PC210_GIC_DIST_REGION_SIZE 0x8F04
+
+static void s5pc210_irq_handler(void *opaque, int irq, int level)
+{
+ S5pc210Irq *s = (S5pc210Irq *)opaque;
+
+ /* Bypass */
+ qemu_set_irq(s->board_irqs[irq], level);
+
+ return;
+}
+
+/*
+ * Initialize s5pc210 IRQ subsystem stub.
+ */
+qemu_irq *s5pc210_init_irq(S5pc210Irq *s)
+{
+ return qemu_allocate_irqs(s5pc210_irq_handler, s,
+ S5PC210_MAX_INT_COMBINER_IN_IRQ);
+}
+
+/*
+ * Initialize board IRQs.
+ * These IRQs contain splitted Int/External Combiner and External Gic IRQs.
+ */
+void s5pc210_init_board_irqs(S5pc210Irq *s)
+{
+ uint32_t grp, bit, irq_id, n;
+
+ for (n = 0; n < S5PC210_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 == S5PC210_COMBINER_GET_IRQ_NUM(1, 4) ||
+ n == S5PC210_COMBINER_GET_IRQ_NUM(12, 4)) {
+ /* MCT_G0 is passed to External GIC */
+ irq_id = EXT_GIC_ID_MCT_G0;
+ }
+ if (n == S5PC210_COMBINER_GET_IRQ_NUM(1, 5) ||
+ n == S5PC210_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 < S5PC210_MAX_INT_COMBINER_IN_IRQ; n++) {
+ /* these IDs are passed to Internal Combiner and External GIC */
+ grp = S5PC210_COMBINER_GET_GRP_NUM(n);
+ bit = S5PC210_COMBINER_GET_BIT_NUM(n);
+ irq_id =
+ combiner_grp_to_gic_id[grp -
+ S5PC210_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 s5pc210 IRQ subsystem stub.
+ * To identify IRQ source use internal combiner group and bit number
+ * grp - group number
+ * bit - bit number inside group
+ */
+uint32_t s5pc210_get_irq(uint32_t grp, uint32_t bit)
+{
+ return S5PC210_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;
+} S5pc210GicState;
+
+static uint64_t s5pc210_gic_cpu_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_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 s5pc210_gic_cpu_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_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 s5pc210_gic_dist_readb(void *opaque, target_phys_addr_t offset)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: readb offset 0x%x\n", offset);
+ return gic_dist_readb(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t s5pc210_gic_dist_readw(void *opaque, target_phys_addr_t offset)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: readw offset 0x%x\n", offset);
+ return gic_dist_readw(&s->gic, offset & ~0x8000);
+}
+
+static uint32_t s5pc210_gic_dist_readl(void *opaque, target_phys_addr_t offset)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: readl offset 0x%x\n", offset);
+ return gic_dist_readl(&s->gic, offset & ~0x8000);
+}
+
+static void s5pc210_gic_dist_writeb(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: writeb offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writeb(&s->gic, offset & ~0x8000, value);
+}
+
+static void s5pc210_gic_dist_writew(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: writew offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writew(&s->gic, offset & ~0x8000, value);
+}
+
+static void s5pc210_gic_dist_writel(void *opaque, target_phys_addr_t offset,
+ uint32_t value)
+{
+ S5pc210GicState *s = (S5pc210GicState *) opaque;
+ DPRINTF_S5PC210_GIC("DIST: writel offset 0x%x, value 0x%x\n", offset,
+ value);
+ gic_dist_writel(&s->gic, offset & ~0x8000, value);
+}
+
+static const MemoryRegionOps s5pc210_gic_cpu_ops = {
+ .read = s5pc210_gic_cpu_read,
+ .write = s5pc210_gic_cpu_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const MemoryRegionOps s5pc210_gic_dist_ops = {
+ .old_mmio = {
+ .read = { s5pc210_gic_dist_readb,
+ s5pc210_gic_dist_readw,
+ s5pc210_gic_dist_readl, },
+ .write = { s5pc210_gic_dist_writeb,
+ s5pc210_gic_dist_writew,
+ s5pc210_gic_dist_writel, },
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_gic_init(SysBusDevice *dev)
+{
+ S5pc210GicState *s = FROM_SYSBUSGIC(S5pc210GicState, dev);
+ gic_init(&s->gic, s->num_cpu);
+
+ memory_region_init(&s->cpu_container, "s5pc210-gic-cpu_container",
+ S5PC210_GIC_CPU_REGION_SIZE);
+ memory_region_init(&s->cpu_container, "s5pc210-gic-dist_container",
+ S5PC210_GIC_DIST_REGION_SIZE);
+ memory_region_init_io(&s->cpu_container, &s5pc210_gic_cpu_ops, &s->gic,
+ "s5pc210-gic-cpu", S5PC210_GIC_CPU_REGION_SIZE);
+ memory_region_init_io(&s->dist_container, &s5pc210_gic_dist_ops, &s->gic,
+ "s5pc210-gic-dist", S5PC210_GIC_DIST_REGION_SIZE);
+
+ sysbus_init_mmio_region(dev, &s->cpu_container);
+ sysbus_init_mmio_region(dev, &s->dist_container);
+
+ gic_cpu_write(&s->gic, 1, 0, 1);
+ return 0;
+}
+
+static SysBusDeviceInfo s5pc210_gic_info = {
+ .init = s5pc210_gic_init,
+ .qdev.name = "s5pc210_gic",
+ .qdev.size = sizeof(S5pc210GicState),
+ .qdev.props = (Property[]) {
+ DEFINE_PROP_UINT32("num-cpu", S5pc210GicState, num_cpu, 1),
+ DEFINE_PROP_END_OF_LIST(),
+ }
+};
+
+static void s5pc210_gic_register_devices(void)
+{
+ sysbus_register_withprop(&s5pc210_gic_info);
+}
+
+device_init(s5pc210_gic_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (2 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 03/14] ARM: s5pc210: IRQ subsystem support Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
` (10 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/s5pc210.c | 12 ++
hw/s5pc210_pwm.c | 433 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 446 insertions(+), 1 deletions(-)
create mode 100644 hw/s5pc210_pwm.c
diff --git a/Makefile.target b/Makefile.target
index ed338a8..b9830d3 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
obj-arm-y += versatile_pci.o
obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o
+ s5pc210_combiner.o s5pc210_pwm.o
obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o
obj-arm-y += pl061.o
obj-arm-y += arm-semi.o
diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index 99e9b1b..9110228 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -89,6 +89,9 @@
#define S5PC210_EXT_COMBINER_BASE_ADDR 0x10440000
#define S5PC210_INT_COMBINER_BASE_ADDR 0x10448000
+/* PWM */
+#define S5PC210_PWM_BASE_ADDR 0x139D0000
+
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
static struct arm_boot_info s5pc210_binfo = {
@@ -350,6 +353,15 @@ static void s5pc210_init(ram_addr_t ram_size,
/* CMU */
sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
+ /* PWM */
+ sysbus_create_varargs("s5pc210.pwm", S5PC210_PWM_BASE_ADDR,
+ irq_table[s5pc210_get_irq(22, 0)],
+ irq_table[s5pc210_get_irq(22, 1)],
+ irq_table[s5pc210_get_irq(22, 2)],
+ irq_table[s5pc210_get_irq(22, 3)],
+ irq_table[s5pc210_get_irq(22, 4)],
+ NULL);
+
/*** UARTs ***/
for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
diff --git a/hw/s5pc210_pwm.c b/hw/s5pc210_pwm.c
new file mode 100644
index 0000000..5dfaa41
--- /dev/null
+++ b/hw/s5pc210_pwm.c
@@ -0,0 +1,433 @@
+/*
+ * Samsung s5pc210 Pulse Width Modulation Timer
+ *
+ * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
+ * All rights reserved.
+ *
+ * Evgeny Voevodin <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 "s5pc210.h"
+
+//#define DEBUG_PWM
+
+#ifdef DEBUG_PWM
+#define DPRINTF(fmt, ...) \
+ do { fprintf(stdout, "PWM: [%24s:%5d] " fmt, __func__, __LINE__, \
+ ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) do {} while (0)
+#endif
+
+#define S5PC210_PWM_TIMERS_NUM 5
+#define S5PC210_PWM_REG_MEM_SIZE 0x50
+
+#define TCFG0 0x0000
+#define TCFG1 0x0004
+#define TCON 0x0008
+#define TCNTB0 0x000C
+#define TCMPB0 0x0010
+#define TCNTO0 0x0014
+#define TCNTB1 0x0018
+#define TCMPB1 0x001C
+#define TCNTO1 0x0020
+#define TCNTB2 0x0024
+#define TCMPB2 0x0028
+#define TCNTO2 0x002C
+#define TCNTB3 0x0030
+#define TCMPB3 0x0034
+#define TCNTO3 0x0038
+#define TCNTB4 0x003C
+#define TCNTO4 0x0040
+#define TINT_CSTAT 0x0044
+
+#define TCNTB(x) (0xC*x)
+#define TCMPB(x) (0xC*x+1)
+#define TCNTO(x) (0xC*x+2)
+
+#define GET_PRESCALER(reg, x) ((reg&(0xFF<<(8*x)))>>8*x)
+#define GET_DIVIDER(reg, x) (1<<((0xF<<(4*x))>>(4*x)))
+
+/*
+ * Attention! Timer4 doesn't have OUTPUT_INVERTER,
+ * so Auto Reload bit is not accessible by macros!
+ */
+#define TCON_TIMER_BASE(x) ((x ? 1 : 0)*4 + 4*x)
+#define TCON_TIMER_START(x) (1<<(TCON_TIMER_BASE(x) + 0))
+#define TCON_TIMER_MANUAL_UPD(x) (1<<(TCON_TIMER_BASE(x) + 1))
+#define TCON_TIMER_OUTPUT_INV(x) (1<<(TCON_TIMER_BASE(x) + 2))
+#define TCON_TIMER_AUTO_RELOAD(x) (1<<(TCON_TIMER_BASE(x) + 3))
+#define TCON_TIMER4_AUTO_RELOAD (1<<22)
+
+#define TINT_CSTAT_STATUS(x) (1<<(5+x))
+#define TINT_CSTAT_ENABLE(x) (1<<x)
+
+/* timer struct */
+typedef struct {
+ uint32_t id; /* timer id */
+ qemu_irq irq; /* local timer irq */
+ uint32_t freq; /* timer frequency */
+
+ /* use ptimer.c to represent count down timer */
+ ptimer_state *ptimer; /* timer */
+
+ /* registers */
+ uint32_t reg_tcntb; /* counter register buffer */
+ uint32_t reg_tcmpb; /* compare register buffer */
+
+} S5pc210_pwm;
+
+
+typedef struct S5pc210PWMState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ uint32_t reg_tcfg[2];
+ uint32_t reg_tcon;
+ uint32_t reg_tint_cstat;
+
+ S5pc210CmuClock clk; /* clock source for timer */
+ S5pc210_pwm timer[S5PC210_PWM_TIMERS_NUM];
+
+} S5pc210PWMState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_S5pc210_pwm = {
+ .name = "s5pc210.pwm.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, S5pc210_pwm),
+ VMSTATE_UINT32(freq, S5pc210_pwm),
+ VMSTATE_UINT32(reg_tcntb, S5pc210_pwm),
+ VMSTATE_UINT32(reg_tcmpb, S5pc210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_S5pc210PWMState = {
+ .name = "s5pc210.pwm",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(reg_tcfg, S5pc210PWMState, 2),
+ VMSTATE_UINT32(reg_tcon, S5pc210PWMState),
+ VMSTATE_UINT32(reg_tint_cstat, S5pc210PWMState),
+ VMSTATE_STRUCT_ARRAY(timer, S5pc210PWMState,
+ S5PC210_PWM_TIMERS_NUM, 0,
+ VMState_S5pc210_pwm, S5pc210_pwm),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/*
+ * PWM update frequency
+ */
+static void s5pc210_pwm_update_freq(S5pc210PWMState *s, uint32_t id)
+{
+ uint32_t freq;
+ freq = s->timer[id].freq;
+ if (id > 1) {
+ s->timer[id].freq = s5pc210_cmu_get_rate(s->clk) /
+ ((GET_PRESCALER(s->reg_tcfg[0], 1) + 1) *
+ (1<<GET_DIVIDER(s->reg_tcfg[1], id)));
+ } else {
+ s->timer[id].freq = s5pc210_cmu_get_rate(s->clk) /
+ ((GET_PRESCALER(s->reg_tcfg[0], 0) + 1) *
+ (1<<GET_DIVIDER(s->preg_tcfg[1], id)));
+ }
+
+ if (freq != s->timer[id].freq) {
+ ptimer_set_freq(s->timer[id].ptimer, s->timer[id].freq);
+ DPRINTF("freq=%dHz\n", s->timer[id].freq);
+ }
+}
+
+/*
+ * Counter tick handler
+ */
+static void s5pc210_pwm_tick(void *opaque, uint32_t id)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)opaque;
+ bool cmp;
+
+ DPRINTF("timer %d tick\n", id);
+
+ /* set irq status */
+ s->reg_tint_cstat |= TINT_CSTAT_STATUS(id);
+
+ /* raise IRQ */
+ if (s->reg_tint_cstat & TINT_CSTAT_ENABLE(id)) {
+ DPRINTF("timer %d IRQ\n", id);
+ qemu_irq_raise(s->timer[id].irq);
+ }
+
+ /* reload timer */
+ if (id != 4) {
+ cmp = s->reg_tcon & TCON_TIMER_AUTO_RELOAD(id);
+ } else {
+ cmp = s->reg_tcon & TCON_TIMER4_AUTO_RELOAD;
+ }
+
+ if (cmp) {
+ DPRINTF("auto reload timer %d count to %x\n", id,
+ s->timer[id].reg_tcntb);
+ ptimer_set_count(s->timer[id].ptimer, s->timer[id].reg_tcntb);
+ ptimer_run(s->timer[id].ptimer, 1);
+ } else {
+ /* stop timer, set status to STOP, see Basic Timer Operation */
+ s->reg_tcon = ~TCON_TIMER_START(id);
+ ptimer_stop(s->timer[id].ptimer);
+ }
+}
+
+static void s5pc210_pwm_tick0(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 0);
+}
+static void s5pc210_pwm_tick1(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 1);
+}
+static void s5pc210_pwm_tick2(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 2);
+}
+static void s5pc210_pwm_tick3(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 3);
+}
+static void s5pc210_pwm_tick4(void *opaque)
+{
+ s5pc210_pwm_tick(opaque, 4);
+}
+
+/*
+ * PWM Read
+ */
+static uint64_t s5pc210_pwm_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)opaque;
+ uint32_t value = 0;
+ int index;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0)>>2;
+ value = s->reg_tcfg[index];
+ break;
+
+ case TCON:
+ value = s->reg_tcon;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0)/0xC;
+ value = s->timer[index].reg_tcntb;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0)/0xC;
+ value = s->timer[index].reg_tcmpb;
+ break;
+
+ case TCNTO0: case TCNTO1:
+ case TCNTO2: case TCNTO3: case TCNTO4:
+ index = (offset == TCNTO4) ? 4 : (offset - TCNTO0)/0xC;
+ value = ptimer_get_count(s->timer[index].ptimer);
+ break;
+
+ case TINT_CSTAT:
+ value = s->reg_tint_cstat;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[s5pc210.pwm: bad read offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+ }
+ return value;
+}
+
+/*
+ * PWM Write
+ */
+static void s5pc210_pwm_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ S5pc210PWMState *s = (S5pc210PWMState *)opaque;
+ int index;
+ uint32_t new_val;
+ int i;
+
+ switch (offset) {
+ case TCFG0: case TCFG1:
+ index = (offset - TCFG0)>>2;
+ s->reg_tcfg[index] = value;
+
+ /* update timers frequencies */
+ for (i = 0; i < S5PC210_PWM_TIMERS_NUM; i++) {
+ s5pc210_pwm_update_freq(s, s->timer[i].id);
+ }
+ break;
+
+ case TCON:
+ for (i = 0; i < S5PC210_PWM_TIMERS_NUM; i++) {
+ if ((value & TCON_TIMER_MANUAL_UPD(i)) >
+ (s->reg_tcon & TCON_TIMER_MANUAL_UPD(i))) {
+ /*
+ * TCNTB and TCMPB are loaded into TCNT and TCMP.
+ * Update timers.
+ */
+
+ /* this will start timer to run, this ok, because
+ * during processing start bit timer will be stopped
+ * if needed */
+ ptimer_set_count(s->timer[i].ptimer, s->timer[i].reg_tcntb);
+ DPRINTF("set timer %d count to %x\n", i,
+ s->timer[i].reg_tcntb);
+ }
+
+ if ((value & TCON_TIMER_START(i)) >
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to start */
+ ptimer_run(s->timer[i].ptimer, 1);
+ DPRINTF("run timer %d\n", i);
+ }
+
+ if ((value & TCON_TIMER_START(i)) <
+ (s->reg_tcon & TCON_TIMER_START(i))) {
+ /* changed to stop */
+ ptimer_stop(s->timer[i].ptimer);
+ DPRINTF("stop timer %d\n", i);
+ }
+ }
+ s->reg_tcon = value;
+ break;
+
+ case TCNTB0: case TCNTB1:
+ case TCNTB2: case TCNTB3: case TCNTB4:
+ index = (offset - TCNTB0)/0xC;
+ s->timer[index].reg_tcntb = value;
+ break;
+
+ case TCMPB0: case TCMPB1:
+ case TCMPB2: case TCMPB3:
+ index = (offset - TCMPB0)/0xC;
+ s->timer[index].reg_tcmpb = value;
+ break;
+
+ case TINT_CSTAT:
+ new_val = (s->reg_tint_cstat&0x3E0) + (0x1F & value);
+ new_val &= ~(0x3E0 & value);
+
+ for (i = 0; i < S5PC210_PWM_TIMERS_NUM; i++) {
+ if ((new_val & TINT_CSTAT_STATUS(i)) <
+ (s->reg_tint_cstat & TINT_CSTAT_STATUS(i))) {
+ qemu_irq_lower(s->timer[i].irq);
+ }
+ }
+
+ s->reg_tint_cstat = new_val;
+ break;
+
+ default:
+ fprintf(stderr,
+ "[s5pc210.pwm: bad write offset " TARGET_FMT_plx "]\n",
+ offset);
+ break;
+
+ }
+}
+
+/*
+ * Set default values to timer fields and registers
+ */
+static void s5pc210_pwm_reset(S5pc210PWMState *s)
+{
+ int i;
+ s->reg_tcfg[0] = 0x0101;
+ s->reg_tcfg[1] = 0x0;
+ s->reg_tcon = 0;
+ s->reg_tint_cstat = 0;
+ for (i = 0; i < S5PC210_PWM_TIMERS_NUM; i++) {
+ s->timer[i].reg_tcmpb = 0;
+ s->timer[i].reg_tcntb = 0;
+
+ s5pc210_pwm_update_freq(s, s->timer[i].id);
+ ptimer_stop(s->timer[i].ptimer);
+ }
+}
+
+static const MemoryRegionOps s5pc210_pwm_ops = {
+ .read = s5pc210_pwm_read,
+ .write = s5pc210_pwm_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/*
+ * PWM timer initialization
+ */
+static int s5pc210_pwm_init(SysBusDevice *dev)
+{
+ S5pc210PWMState *s = FROM_SYSBUS(S5pc210PWMState, dev);
+ int i;
+ QEMUBH * bh[S5PC210_PWM_TIMERS_NUM];
+
+ s->clk = ACLK_100;
+
+ bh[0] = qemu_bh_new(s5pc210_pwm_tick0, s);
+ bh[1] = qemu_bh_new(s5pc210_pwm_tick1, s);
+ bh[2] = qemu_bh_new(s5pc210_pwm_tick2, s);
+ bh[3] = qemu_bh_new(s5pc210_pwm_tick3, s);
+ bh[4] = qemu_bh_new(s5pc210_pwm_tick4, s);
+
+ for (i = 0; i < S5PC210_PWM_TIMERS_NUM; i++) {
+ sysbus_init_irq(dev, &s->timer[i].irq);
+ s->timer[i].ptimer = ptimer_init(bh[i]);
+ s->timer[i].id = i;
+ }
+
+ memory_region_init_io(&s->iomem, &s5pc210_pwm_ops, s, "s5pc210-pwm",
+ S5PC210_PWM_REG_MEM_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ s5pc210_pwm_reset(s);
+
+ qemu_register_reset((QEMUResetHandler *)s5pc210_pwm_reset, s);
+ vmstate_register(NULL, -1, &VMState_S5pc210PWMState, s);
+ return 0;
+}
+
+static void s5pc210_pwm_register_devices(void)
+{
+ sysbus_register_dev("s5pc210.pwm", sizeof(S5pc210PWMState),
+ s5pc210_pwm_init);
+}
+
+device_init(s5pc210_pwm_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (3 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 04/14] ARM: s5pc210: PWM support Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
` (9 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: 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] 25+ messages in thread
* [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (4 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 05/14] hw/arm_boot.c: Add new secondary CPU bootloader Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support Evgeny Voevodin
` (8 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: 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] 25+ messages in thread
* [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (5 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 06/14] hw/arm_gic.c: lower IRQ only on changing of enable bit Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:46 ` [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU Evgeny Voevodin
` (7 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/s5pc210.c | 19 +
hw/s5pc210_mct.c | 1483 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1503 insertions(+), 1 deletions(-)
create mode 100644 hw/s5pc210_mct.c
diff --git a/Makefile.target b/Makefile.target
index b9830d3..805993b 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
obj-arm-y += versatile_pci.o
obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o s5pc210_pwm.o
+ s5pc210_combiner.o s5pc210_pwm.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 9110228..eabe3b1 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -92,6 +92,9 @@
/* PWM */
#define S5PC210_PWM_BASE_ADDR 0x139D0000
+/* MCT */
+#define S5PC210_MCT_BASE_ADDR 0x10050000
+
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
static struct arm_boot_info s5pc210_binfo = {
@@ -362,6 +365,22 @@ static void s5pc210_init(ram_addr_t ram_size,
irq_table[s5pc210_get_irq(22, 4)],
NULL);
+ /* Multi Core Timer */
+ dev = qdev_create(NULL, "s5pc210.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[s5pc210_get_irq(1, 4 + n)]);
+ }
+ /* Connect local timer interrupts to Combiner gpio_in */
+ sysbus_connect_irq(busdev, 4,
+ irq_table[s5pc210_get_irq(51, 0)]);
+ sysbus_connect_irq(busdev, 5,
+ irq_table[s5pc210_get_irq(35, 3)]);
+ sysbus_mmio_map(busdev, 0, S5PC210_MCT_BASE_ADDR);
+
/*** UARTs ***/
for (n = 0; n < S5PC210_UARTS_NUMBER; n++) {
diff --git a/hw/s5pc210_mct.c b/hw/s5pc210_mct.c
new file mode 100644
index 0000000..7a7dd84
--- /dev/null
+++ b/hw/s5pc210_mct.c
@@ -0,0 +1,1483 @@
+/*
+ * Samsung s5pc210 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 "s5pc210.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 */
+
+} s5pc210_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;
+
+} s5pc210_mct_lt;
+
+
+
+typedef struct S5pc210MCTState {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+
+ /* Registers */
+ uint32_t reg_mct_cfg;
+
+ s5pc210_mct_lt l_timer[2];
+ s5pc210_mct_gt g_timer;
+
+ uint32_t freq; /* all timers tick frequency, TCLK */
+} S5pc210MCTState;
+
+/*** VMState ***/
+static const VMStateDescription VMState_tick_timer = {
+ .name = "s5pc210.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 = "s5pc210.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_S5pc210_mct_lt = {
+ .name = "s5pc210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_INT32(id, s5pc210_mct_lt),
+ VMSTATE_STRUCT(tick_timer, s5pc210_mct_lt, 0,
+ VMState_tick_timer,
+ struct tick_timer),
+ VMSTATE_STRUCT(reg, s5pc210_mct_lt, 0,
+ VMState_lregs,
+ struct lregs),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_gregs = {
+ .name = "s5pc210.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_S5pc210_mct_gt = {
+ .name = "s5pc210.mct.lt",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_STRUCT(reg, s5pc210_mct_gt, 0, VMState_gregs, struct gregs),
+ VMSTATE_UINT64(count, s5pc210_mct_gt),
+ VMSTATE_INT32(curr_comp, s5pc210_mct_gt),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription VMState_S5pc210MCTState = {
+ .name = "s5pc210.mct",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(reg_mct_cfg, S5pc210MCTState),
+ VMSTATE_STRUCT_ARRAY(l_timer, S5pc210MCTState, 2, 0,
+ VMState_S5pc210_mct_lt, s5pc210_mct_lt),
+ VMSTATE_STRUCT(g_timer, S5pc210MCTState, 0,
+ VMState_S5pc210_mct_gt, s5pc210_mct_gt),
+ VMSTATE_UINT32(freq, S5pc210MCTState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void s5pc210_mct_update_freq(S5pc210MCTState *s);
+
+/*
+ * Set counter of FRC global timer.
+ */
+static void s5pc210_gfrc_set_count(s5pc210_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 s5pc210_gfrc_get_count(s5pc210_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 s5pc210_gfrc_stop(s5pc210_mct_gt *s)
+{
+ DPRINTF("global timer frc stop\n");
+
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Start global FRC timer
+ */
+static void s5pc210_gfrc_start(s5pc210_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 s5pc210_gcomp_find(S5pc210MCTState *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 = s5pc210_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 s5pc210_gcomp_get_distance(S5pc210MCTState *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 s5pc210_gfrc_restart(S5pc210MCTState *s)
+{
+ uint64_t distance;
+
+ s5pc210_gfrc_stop(&s->g_timer);
+
+ s->g_timer.curr_comp = s5pc210_gcomp_find(s);
+
+ distance = s5pc210_gcomp_get_distance(s, s->g_timer.curr_comp);
+
+ if (distance > MCT_GT_COUNTER_STEP || !distance) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+
+ s5pc210_gfrc_set_count(&s->g_timer, distance);
+ s5pc210_gfrc_start(&s->g_timer);
+}
+
+/*
+ * Raise global timer CMP IRQ
+ */
+static void s5pc210_gcomp_raise_irq(void *opaque, uint32_t id)
+{
+ s5pc210_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 s5pc210_gcomp_lower_irq(void *opaque, uint32_t id)
+{
+ s5pc210_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 s5pc210_gfrc_event(void *opaque)
+{
+ S5pc210MCTState *s = (S5pc210MCTState *)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 */
+ s5pc210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ /* Reload FRC to reach nearest comparator */
+ s->g_timer.curr_comp = s5pc210_gcomp_find(s);
+ distance = s5pc210_gcomp_get_distance(s, s->g_timer.curr_comp);
+ if (distance > MCT_GT_COUNTER_STEP) {
+ distance = MCT_GT_COUNTER_STEP;
+ }
+ s5pc210_gfrc_set_count(&s->g_timer, distance);
+
+ s5pc210_gfrc_start(&s->g_timer);
+
+ return;
+}
+
+/*
+ * Get counter of FRC local timer.
+ */
+static uint64_t s5pc210_lfrc_get_count(s5pc210_mct_lt *s)
+{
+ return ptimer_get_count(s->ptimer_frc);
+}
+
+/*
+ * Set counter of FRC local timer.
+ */
+static void s5pc210_lfrc_update_count(s5pc210_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 s5pc210_lfrc_start(s5pc210_mct_lt *s)
+{
+ ptimer_run(s->ptimer_frc, 1);
+}
+
+/*
+ * Stop local FRC timer
+ */
+static void s5pc210_lfrc_stop(s5pc210_mct_lt *s)
+{
+ ptimer_stop(s->ptimer_frc);
+}
+
+/*
+ * Local timer free running counter tick handler
+ */
+static void s5pc210_lfrc_event(void *opaque)
+{
+ s5pc210_mct_lt * s = (s5pc210_mct_lt *)opaque;
+
+ /* local frc expired */
+
+ DPRINTF("\n");
+
+ s->reg.int_cstat |= L_INT_CSTAT_FRCCNT;
+
+ /* update frc counter */
+ s5pc210_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 */
+ s5pc210_lfrc_start(s);
+}
+
+
+static uint32_t s5pc210_ltick_int_get_cnto(struct tick_timer *s);
+static void s5pc210_ltick_cnt_start(struct tick_timer *s);
+static void s5pc210_ltick_cnt_stop(struct tick_timer *s);
+static uint32_t s5pc210_ltick_cnt_get_cnto(struct tick_timer *s);
+static void s5pc210_ltick_recalc_count(struct tick_timer *s);
+
+
+
+/*
+ * Action on enabling local tick int timer
+ */
+static void s5pc210_ltick_int_start(struct tick_timer *s)
+{
+ if (!s->int_run) {
+ s->int_run = 1;
+ }
+}
+
+/*
+ * Action on disabling local tick int timer
+ */
+static void s5pc210_ltick_int_stop(struct tick_timer *s)
+{
+ if (s->int_run) {
+ s->last_icnto = s5pc210_ltick_int_get_cnto(s);
+ s->int_run = 0;
+ }
+}
+
+/*
+ * Get count for INT timer
+ */
+static uint32_t s5pc210_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 s5pc210_ltick_cnt_start(struct tick_timer *s)
+{
+ if (!s->cnt_run) {
+
+ s5pc210_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 s5pc210_ltick_cnt_stop(struct tick_timer *s)
+{
+ if (s->cnt_run) {
+
+ s->last_tcnto = s5pc210_ltick_cnt_get_cnto(s);
+
+ if (s->int_run) {
+ s5pc210_ltick_int_stop(s);
+ }
+
+ ptimer_stop(s->ptimer_tick);
+
+ s->cnt_run = 0;
+ }
+}
+
+/*
+ * Get counter for CNT timer
+ */
+static uint32_t s5pc210_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 s5pc210_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) {
+ s5pc210_ltick_cnt_stop(s);
+ cnt_stopped = 1;
+ }
+
+ if (s->int_run) {
+ s5pc210_ltick_int_stop(s);
+ int_stopped = 1;
+ }
+
+ s->tcntb = new_cnt + 1;
+ s->icntb = new_int + 1;
+
+ if (cnt_stopped) {
+ s5pc210_ltick_cnt_start(s);
+ }
+ if (int_stopped) {
+ s5pc210_ltick_int_start(s);
+ }
+
+}
+
+/*
+ * Calculate new counter value for tick timer
+ */
+static void s5pc210_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 s5pc210_ltick_timer_init(struct tick_timer *s)
+{
+ s5pc210_ltick_int_stop(s);
+ s5pc210_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 s5pc210_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 s5pc210_ltick_event(void *opaque)
+{
+ s5pc210_mct_lt * s = (s5pc210_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 */
+ s5pc210_ltick_timer_event(&s->tick_timer);
+
+ /* get tick_timer cnt */
+ tcnto = s5pc210_ltick_cnt_get_cnto(&s->tick_timer);
+
+ /* get tick_timer int */
+ icnto = s5pc210_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) {
+ s5pc210_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) {
+ s5pc210_ltick_set_cntb(&s->tick_timer,
+ s->reg.cnt[L_REG_CNT_TCNTB],
+ icnto);
+ }
+ }
+
+ /* start tick_timer cnt */
+ s5pc210_ltick_cnt_start(&s->tick_timer);
+
+ /* start tick_timer int */
+ s5pc210_ltick_int_start(&s->tick_timer);
+}
+
+/* update timer frequency */
+static void s5pc210_mct_update_freq(S5pc210MCTState *s)
+{
+ uint32_t freq = s->freq;
+ s->freq = s5pc210_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 s5pc210_mct_reset(void *opaque)
+{
+ S5pc210MCTState *s = (S5pc210MCTState *) opaque;
+ uint32_t i;
+
+ s->reg_mct_cfg = 0;
+
+ /* global timer */
+ memset(&s->g_timer.reg, 0, sizeof(s->g_timer.reg));
+ s5pc210_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);
+
+ s5pc210_ltick_timer_init(&s->l_timer[i].tick_timer);
+ }
+
+ s5pc210_mct_update_freq(s);
+
+}
+
+/* Multi Core Timer read */
+static uint64_t s5pc210_mct_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210MCTState *s = (S5pc210MCTState *)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 = s5pc210_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 = s5pc210_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 = s5pc210_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 = s5pc210_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("s5pc210.mct: bad read offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+ return value;
+}
+
+/* MCT write */
+static void s5pc210_mct_write(void *opaque, target_phys_addr_t offset,
+ uint64_t value, unsigned size)
+{
+ S5pc210MCTState *s = (S5pc210MCTState *)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;
+ s5pc210_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;
+ s5pc210_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);
+ }
+
+ s5pc210_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)) {
+ s5pc210_gfrc_start(&s->g_timer);
+ }
+ if ((value & G_TCON_TIMER_ENABLE) < (old_val &
+ G_TCON_TIMER_ENABLE)) {
+ s5pc210_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))) {
+ s5pc210_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)) {
+ s5pc210_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)) {
+ s5pc210_gcomp_raise_irq(&s->g_timer, i);
+ }
+ }
+
+ if ((value & G_INT_ENABLE(i)) < (s->g_timer.reg.tcon &
+ G_INT_ENABLE(i))) {
+ s5pc210_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);
+ s5pc210_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);
+ s5pc210_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);
+ s5pc210_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);
+ s5pc210_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);
+ s5pc210_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);
+ s5pc210_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.
+ */
+ s5pc210_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) {
+ s5pc210_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[s5pc210.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("s5pc210.mct: bad write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static const MemoryRegionOps s5pc210_mct_ops = {
+ .read = s5pc210_mct_read,
+ .write = s5pc210_mct_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+/* MCT init */
+static int s5pc210_mct_init(SysBusDevice *dev)
+{
+ int i;
+ S5pc210MCTState *s = FROM_SYSBUS(S5pc210MCTState, dev);
+ QEMUBH * bh[2];
+
+ /* Global timer */
+ bh[0] = qemu_bh_new(s5pc210_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(s5pc210_ltick_event, &s->l_timer[i]);
+ bh[1] = qemu_bh_new(s5pc210_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, &s5pc210_mct_ops, s, "s5pc210-mct",
+ MCT_SFR_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+
+ s5pc210_mct_reset(s);
+
+ qemu_register_reset(s5pc210_mct_reset, s);
+ vmstate_register(NULL, -1, &VMState_S5pc210MCTState, s);
+ return 0;
+}
+
+static void s5pc210_mct_register_devices(void)
+{
+ sysbus_register_dev("s5pc210.mct", sizeof(S5pc210MCTState),
+ s5pc210_mct_init);
+}
+
+device_init(s5pc210_mct_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (6 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 07/14] ARM: s5pc210: MCT support Evgeny Voevodin
@ 2011-12-07 9:46 ` Evgeny Voevodin
2011-12-07 9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
` (6 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:46 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/s5pc210.c | 26 +++++++++++++++++++++++++-
1 files changed, 25 insertions(+), 1 deletions(-)
diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index eabe3b1..90858e9 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,12 @@
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
+/* Secondary CPU startup code is in IROM memory */
+#define S5PC210_SMP_BOOT_ADDR S5PC210_IROM_BASE_ADDR
+
+/* Secondary CPU polling address to get loader start from */
+#define S5PC210_SECOND_CPU_BOOTREG 0x10020814
+
static struct arm_boot_info s5pc210_binfo = {
.loader_start = S5PC210_BASE_BOOT_ADDR,
};
@@ -213,6 +219,8 @@ static void s5pc210_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);
S5pc210Irq *irqs;
qemu_irq *irq_table;
qemu_irq *irqp;
@@ -225,9 +233,11 @@ static void s5pc210_init(ram_addr_t ram_size,
switch (board_type) {
case BOARD_S5PC210_NURI:
s5pc210_binfo.board_id = MACH_NURI_ID;
+ s5pc210_binfo.smp_bootreg_addr = S5PC210_SECOND_CPU_BOOTREG;
break;
case BOARD_S5PC210_SMDKC210:
s5pc210_binfo.board_id = MACH_SMDKC210_ID;
+ s5pc210_binfo.smp_bootreg_addr = S5PC210_SECOND_CPU_BOOTREG;
break;
default:
break;
@@ -353,6 +363,20 @@ static void s5pc210_init(ram_addr_t ram_size,
memory_region_add_subregion(system_mem, S5PC210_DRAM0_BASE_ADDR,
dram0_mem);
+ /*
+ * Secondary CPU startup code will be placed here.
+ */
+ memory_region_init_ram(hack_mem, NULL, "s5pc210.hack", 0x1000);
+ memory_region_add_subregion(system_mem, S5PC210_SMP_BOOT_ADDR,
+ hack_mem);
+
+ /*
+ * Hack: Map SECOND_CPU_BOOTREG, because it is in PMU USER5 register.
+ */
+ memory_region_init_ram(bootreg_mem, NULL, "s5pc210.bootreg", 0x4);
+ memory_region_add_subregion(system_mem, S5PC210_SECOND_CPU_BOOTREG,
+ bootreg_mem);
+
/* CMU */
sysbus_create_simple("s5pc210.cmu", S5PC210_CMU_BASE_ADDR, NULL);
@@ -429,7 +453,7 @@ static void s5pc210_init(ram_addr_t ram_size,
s5pc210_binfo.kernel_filename = kernel_filename;
s5pc210_binfo.initrd_filename = initrd_filename;
s5pc210_binfo.kernel_cmdline = kernel_cmdline;
-
+ s5pc210_binfo.smp_priv_base = S5PC210_SMP_PRIVATE_BASE_ADDR;
arm_load_kernel(first_cpu, &s5pc210_binfo);
}
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (7 preceding siblings ...)
2011-12-07 9:46 ` [Qemu-devel] [PATCH 08/14] ARM: s5pc210: Boot secondary CPU Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 10:09 ` Peter Maydell
2011-12-07 9:47 ` [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board Evgeny Voevodin
` (5 subsequent siblings)
14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
We included this chip into s5pc210 platform because SMDK board holds
lan9215 chip. Difference is that 9215 access is 16-bit wide and some
registers differ. By addition basic 16-bit access to 9118 emulation we
achieved ethernet controller support by Linux lernel on SMDK boards.
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/lan9118.c | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++----
1 files changed, 90 insertions(+), 6 deletions(-)
diff --git a/hw/lan9118.c b/hw/lan9118.c
index ee8b2ea..7aebabd 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -212,6 +212,14 @@ 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;
} lan9118_state;
static void lan9118_update(lan9118_state *s)
@@ -345,6 +353,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 +907,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 +1040,43 @@ 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_writeb(void *opaque, target_phys_addr_t offset,
+ uint32_t val)
+{
+ hw_error("lan9118_writeb: Bad reg 0x%x = %x\n", (int)offset, val);
+}
+
+static void lan9118_writew(void *opaque, target_phys_addr_t offset,
+ uint32_t val)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ offset &= 0xff;
+
+ /* TODO: Implement fair word operation, because this implementation
+ * assumes that any register is accessed as two 16-bit operations. */
+
+ 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 uint32_t lan9118_readl(void *opaque, target_phys_addr_t offset)
{
lan9118_state *s = (lan9118_state *)opaque;
@@ -1103,9 +1149,47 @@ static uint64_t lan9118_readl(void *opaque, target_phys_addr_t offset,
return 0;
}
+static uint32_t lan9118_readb(void *opaque, target_phys_addr_t offset)
+{
+ hw_error("lan9118_readb: Bad reg 0x%x\n", (int)offset);
+}
+
+static uint32_t lan9118_readw(void *opaque, target_phys_addr_t offset)
+{
+ lan9118_state *s = (lan9118_state *)opaque;
+ uint32_t val;
+
+ /* TODO: Implement fair word operation, because this implementation
+ * assumes that any register is accessed as two 16-bit operations. */
+
+ 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 const MemoryRegionOps lan9118_mem_ops = {
- .read = lan9118_readl,
- .write = lan9118_writel,
+ .old_mmio = {
+ .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
+ .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
+ },
.endianness = DEVICE_NATIVE_ENDIAN,
};
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
2011-12-07 9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
@ 2011-12-07 10:09 ` Peter Maydell
2011-12-07 10:58 ` Evgeny Voevodin
0 siblings, 1 reply; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:09 UTC (permalink / raw)
To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy
On 7 December 2011 09:47, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> We included this chip into s5pc210 platform because SMDK board holds
> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
> registers differ. By addition basic 16-bit access to 9118 emulation we
> achieved ethernet controller support by Linux lernel on SMDK boards.
If it differs then shouldn't we add a new qdev device for 9215 ?
(sharing most of the implementation code, obviously)
> static const MemoryRegionOps lan9118_mem_ops = {
> - .read = lan9118_readl,
> - .write = lan9118_writel,
> + .old_mmio = {
> + .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
> + .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
> + },
> .endianness = DEVICE_NATIVE_ENDIAN,
> };
This is going backwards -- the .old_mmio hooks are for backwards
compatibility when converting old devices to MemoryRegions -- they
shouldn't be added in new code.
You need to make the lan9118_read/write functions look at their
'size' argument instead.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
2011-12-07 10:09 ` Peter Maydell
@ 2011-12-07 10:58 ` Evgeny Voevodin
2011-12-07 11:17 ` Peter Maydell
0 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 10:58 UTC (permalink / raw)
To: Peter Maydell; +Cc: qemu-devel, d.solodkiy
On 12/07/2011 02:09 PM, Peter Maydell wrote:
> On 7 December 2011 09:47, Evgeny Voevodin<e.voevodin@samsung.com> wrote:
>> We included this chip into s5pc210 platform because SMDK board holds
>> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
>> registers differ. By addition basic 16-bit access to 9118 emulation we
>> achieved ethernet controller support by Linux lernel on SMDK boards.
>
> If it differs then shouldn't we add a new qdev device for 9215 ?
> (sharing most of the implementation code, obviously)
>
This patch could be interpreted as lan9118 emulation expansion since
this chip supports 16-bit access too. These changes don't cover all the
difference between 9118 and 9215, but it's enough to provide network
support to Samsung boards. When 9215 support will be added we can easily
switch to this chip.
>> static const MemoryRegionOps lan9118_mem_ops = {
>> - .read = lan9118_readl,
>> - .write = lan9118_writel,
>> + .old_mmio = {
>> + .read = { lan9118_readb, lan9118_readw, lan9118_readl, },
>> + .write = { lan9118_writeb, lan9118_writew, lan9118_writel, },
>> + },
>> .endianness = DEVICE_NATIVE_ENDIAN,
>> };
>
> This is going backwards -- the .old_mmio hooks are for backwards
> compatibility when converting old devices to MemoryRegions -- they
> shouldn't be added in new code.
>
> You need to make the lan9118_read/write functions look at their
> 'size' argument instead.
>
> -- PMM
>
>
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support.
2011-12-07 10:58 ` Evgeny Voevodin
@ 2011-12-07 11:17 ` Peter Maydell
0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 11:17 UTC (permalink / raw)
To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy
On 7 December 2011 10:58, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> On 12/07/2011 02:09 PM, Peter Maydell wrote:
>>
>> On 7 December 2011 09:47, Evgeny Voevodin<e.voevodin@samsung.com> wrote:
>>>
>>> We included this chip into s5pc210 platform because SMDK board holds
>>> lan9215 chip. Difference is that 9215 access is 16-bit wide and some
>>> registers differ. By addition basic 16-bit access to 9118 emulation we
>>> achieved ethernet controller support by Linux lernel on SMDK boards.
>>
>>
>> If it differs then shouldn't we add a new qdev device for 9215 ?
>> (sharing most of the implementation code, obviously)
>>
>
> This patch could be interpreted as lan9118 emulation expansion since this
> chip supports 16-bit access too. These changes don't cover all the
> difference between 9118 and 9215, but it's enough to provide network support
> to Samsung boards. When 9215 support will be added we can easily switch to
> this chip.
If you're adding a legitimate missing feature (16 bit access support) to
lan9118 then your commit message should say so, not talk about 9215.
The right place to say "this should be a 9215 but the 9118 is close enough"
is in the patch adding the network chip to the board model, not in the
commit adding a feature to the network chip model.
We should probably make 16 vs 32 bit be a qdev property of lan9118
(corresponding to the way the hardware is configured by an input pin
on startup) and reflect this properly in the HW_CFG register.
Also, this todo comment is too obscure:
+ /* TODO: Implement fair word operation, because this implementation
+ * assumes that any register is accessed as two 16-bit operations. */
I have no idea what it means.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (8 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 09/14] hw/lan9118.c: Basic byte/word/long access support Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 9:47 ` [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD) Evgeny Voevodin
` (4 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/s5pc210.c | 19 +++++++++++++++++++
1 files changed, 19 insertions(+), 0 deletions(-)
diff --git a/hw/s5pc210.c b/hw/s5pc210.c
index 90858e9..8678b97 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -29,6 +29,8 @@
#include "sysemu.h"
#include "sysbus.h"
#include "arm-misc.h"
+#include "net.h"
+#include "devices.h"
#include "exec-memory.h"
#include "s5pc210.h"
@@ -229,6 +231,8 @@ static void s5pc210_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_S5PC210_NURI:
@@ -446,6 +450,21 @@ static void s5pc210_init(ram_addr_t ram_size,
s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
+ /*** LAN adapter ***/
+ if (board_type == BOARD_S5PC210_SMDKC210) {
+
+ for (n = 0; n < nb_nics; n++) {
+ nd = &nd_table[n];
+
+ if (!done_nic && (!nd->model ||
+ strcmp(nd->model, "lan9118") == 0)) {
+ lan9118_init(nd, 0x05000000,
+ qemu_irq_invert(irq_table[s5pc210_get_irq(37, 1)]));
+ done_nic = 1;
+ }
+ }
+ }
+
/*** Load kernel ***/
s5pc210_binfo.ram_size = ram_size;
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD)
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (9 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 10/14] hw/s5pc210.c: Add lan9118 support to SMDK board Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 9:47 ` [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state) Evgeny Voevodin
` (3 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: Mitsyanko Igor, d.solodkiy, Evgeny Voevodin
From: Mitsyanko Igor <i.mitsyanko@samsung.com>
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
Makefile.target | 2 +-
hw/s5pc210.c | 11 +
hw/s5pc210_fimd.c | 1698 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1710 insertions(+), 1 deletions(-)
create mode 100644 hw/s5pc210_fimd.c
diff --git a/Makefile.target b/Makefile.target
index 805993b..84c4a05 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -345,7 +345,7 @@ obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o
obj-arm-y += versatile_pci.o
obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o
obj-arm-y += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o
+ s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_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/s5pc210.c b/hw/s5pc210.c
index 8678b97..c7cba3e 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,10 @@
/* MCT */
#define S5PC210_MCT_BASE_ADDR 0x10050000
+/* Display controllers (FIMD) */
+#define S5PC210_FIMD0_BASE_ADDR 0x11C00000
+#define S5PC210_FIMD1_BASE_ADDR 0x12000000
+
#define S5PC210_BASE_BOOT_ADDR S5PC210_DRAM0_BASE_ADDR
/* Secondary CPU startup code is in IROM memory */
@@ -465,6 +469,13 @@ static void s5pc210_init(ram_addr_t ram_size,
}
}
+ /*** Display controller (FIMD) ***/
+ sysbus_create_varargs("s5pc210.fimd", S5PC210_FIMD0_BASE_ADDR,
+ irq_table[s5pc210_get_irq(11, 0)],
+ irq_table[s5pc210_get_irq(11, 1)],
+ irq_table[s5pc210_get_irq(11, 2)],
+ NULL);
+
/*** Load kernel ***/
s5pc210_binfo.ram_size = ram_size;
diff --git a/hw/s5pc210_fimd.c b/hw/s5pc210_fimd.c
new file mode 100644
index 0000000..b3b01f2
--- /dev/null
+++ b/hw/s5pc210_fimd.c
@@ -0,0 +1,1698 @@
+/*
+ * Samsung s5pc210 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 S5P_FIMD_DEBUG 0
+#define S5P_FIMD_MODE_TRACE 0
+
+#if S5P_FIMD_DEBUG == 0
+ #define print_debug1(fmt, args...) do { } while (0)
+ #define print_debug2(fmt, args...) do { } while (0)
+ #define print_error(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#elif S5P_FIMD_DEBUG == 1
+ #define print_debug1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define print_debug2(fmt, args...) do { } while (0)
+ #define print_error(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "fmt, ## args); } while (0)
+#else
+ #define print_debug1(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define print_debug2(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD: "fmt, ## args); } while (0)
+ #define print_error(fmt, args...) \
+ do {fprintf(stderr, "QEMU FIMD ERROR: "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_VIDTCON2_SIZE_MASK 0x07FF
+#define FIMD_VIDTCON2_HOR_SHIFT 0
+#define FIMD_VIDTCON2_VER_SHIFT 11
+
+/* Window control registers */
+#define FIMD_WINCON0 0x0020
+#define FIMD_WINCON_ROMASK 0x82200000
+#define FIMD_WINCON_ENWIN (1 << 0)
+#define FIMD_WINCON_BLD_PIX (1 << 6)
+#define FIMD_WINCON_ALPHA_SEL (1 << 1)
+#define FIMD_WINCON_ALSEL_SHIFT 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_BPPMODE_SHIFT 2
+#define FIMD_WINCON_BPPMODE 0x0F
+#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)
+
+/* Window position control registers */
+#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_VIDWADD2_PAGEWIDTH 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE 0x1FFF
+#define FIMD_VIDWADD2_OFFSIZE_SHIFT 13
+
+/* Window color key registers */
+#define FIMD_WKEYCON0_COMPKEY 0x00FFFFFF
+#define FIMD_WKEYCON0_CTL_SHIFT 24
+#define FIMD_WKEYCON0_CTL_DIRCON 0x1
+#define FIMD_WKEYCON0_CTL_KEYEN 0x2
+#define FIMD_WKEYCON0_CTL_KEYBLEN 0x4
+
+/* Window alpha control registers */
+#define FIMD_VIDALPHA_ALPHA_MASK 0x000F0F0F
+
+/* Window color map registers */
+#define FIMD_WINMAP_EN (1 << 24)
+#define FIMD_WINMAP_COLOR_MASK 0x00FFFFFF
+#define FIMD_WINMAP_NOCOLOR (~FIMD_WINMAP_COLOR_MASK)
+
+/* Window palette control registers */
+#define FIMD_WPAL_W0PAL_L 0x07
+#define FIMD_WPAL_W0PAL_L_SH 0
+#define FIMD_WPAL_W1PAL_L 0x07
+#define FIMD_WPAL_W1PAL_L_SH 3
+#define FIMD_WPAL_W2PAL_L 0x01
+#define FIMD_WPAL_W2PAL_L_SH 6
+#define FIMD_WPAL_W2PAL_H 0x06
+#define FIMD_WPAL_W2PAL_H_SH 8
+#define FIMD_WPAL_W3PAL_L 0x01
+#define FIMD_WPAL_W3PAL_L_SH 7
+#define FIMD_WPAL_W3PAL_H 0x06
+#define FIMD_WPAL_W3PAL_H_SH 12
+#define FIMD_WPAL_W4PAL_L 0x01
+#define FIMD_WPAL_W4PAL_L_SH 8
+#define FIMD_WPAL_W4PAL_H 0x06
+#define FIMD_WPAL_W4PAL_H_SH 16
+
+/* Trigger control registers */
+#define FIMD_TRIGCON 0x01A4
+#define FIMD_TRIGCON_ROMASK 0x00000004
+
+/* 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_COEF_MASK 0xF
+#define FIMD_BLENDEQ_COEF_Q 18
+#define FIMD_BLENDEQ_COEF_P 12
+#define FIMD_BLENDEQ_COEF_B 6
+#define FIMD_BLENDEQ_COEF_A 0
+
+typedef struct {
+ uint8_t r, g, b;
+ uint32_t a;
+} rgba;
+#define RGBA_SIZE 7
+
+typedef struct DrawConfig DrawConfig;
+
+typedef void pixel_to_rgb_func(uint32_t pixel, rgba *p);
+typedef void draw_line_func(struct DrawConfig *cfg, uint8_t *src,
+ uint8_t *dst, uint8_t *ifb);
+typedef uint32_t coef_func(const struct DrawConfig *cfg, rgba pa, rgba pb);
+
+typedef struct {
+ uint32_t wincon; /* Window control register */
+ uint32_t vidosd[4]; /* Window position control registers A-D */
+ uint32_t buf_start[3]; /* Start address for video frame buffer */
+ uint32_t buf_end[3]; /* End address for video frame buffer */
+ uint32_t buf_size; /* Virtual screen width */
+ 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 vidw_alpha[2]; /* Window alpha control registers */
+ uint32_t blendeq; /* Window blending equation control register */
+ uint32_t rtqoscon; /* Window RTQOS Control Registers */
+ uint32_t palette[256]; /* Pallete 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 */
+} S5pc210fimdWindow;
+
+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 maping control register */
+ uint32_t vidintcon[2]; /* Video interrupt control registers */
+ uint32_t dithmode; /* Dithering control register */
+ uint32_t wpalcon[2]; /* Window pallete 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 */
+ uint32_t blendcon; /* Blending equation control register */
+ uint32_t dualrgb; /* Undocumented register */
+ uint32_t i80ifcmd[12]; /* LCD I80 Interface Command */
+
+ S5pc210fimdWindow window[5]; /* Window-specific registers */
+ uint8_t *ifb; /* Internal frame buffer */
+ bool invalidate; /* Image needs to be redrawn */
+ bool enabled; /* Display controller is enabled */
+} S5pc210fimdState;
+
+struct DrawConfig {
+ pixel_to_rgb_func *pixel_to_rgb;
+ draw_line_func *draw_line;
+ int (*put_pixel)(const rgba p, uint8_t *pixel);
+ int (*get_pixel)(const uint8_t *src, rgba *p);
+ void (*blend)(struct DrawConfig *cfg, rgba p_old, rgba p_new, rgba *p);
+ coef_func *coef_p, *coef_q, *coef_a, *coef_b;
+ uint8_t is_palletized;
+ uint32_t bg_alpha[2], fg_alpha[2];
+ uint32_t color_key, color_mask, color_ctl;
+ uint8_t fg_alpha_pix, bg_alpha_pix;
+ int width;
+ int bpp;
+ uint32_t *palette;
+ uint8_t swap;
+ uint8_t fg_pixel_blending, bg_pixel_blending;
+ uint8_t fg_alpha_sel, bg_alpha_sel;
+ uint32_t map_color;
+};
+
+static inline int s5pc210_buffer_status(S5pc210fimdWindow *w)
+{
+ switch (w->wincon & FIMD_WINCON_BUFSTATUS) {
+ case FIMD_WINCON_BUF0_STAT: default:
+ return 0;
+ case FIMD_WINCON_BUF1_STAT:
+ return 1;
+ case FIMD_WINCON_BUF2_STAT:
+ return 2;
+ }
+}
+
+/* Perform byte/halfword/word swap of data according to config */
+static inline uint64_t swap_data(const DrawConfig *cfg, uint64_t x)
+{
+ int i;
+ uint64_t res;
+
+ if (cfg->swap & FIMD_WINCON_SWAP_BITS) {
+ res = 0;
+ for (i = 0; i < 64; i++) {
+ if (x & (1ULL << (64 - i))) {
+ res |= (1ULL << i);
+ }
+ }
+ x = res;
+ }
+ if (cfg->swap & 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 (cfg->swap & FIMD_WINCON_SWAP_HWORD) {
+ x = ((x & 0x000000000000FFFFULL) << 48) |
+ ((x & 0x00000000FFFF0000ULL) << 16) |
+ ((x & 0x0000FFFF00000000ULL) >> 16) |
+ ((x & 0xFFFF000000000000ULL) >> 48);
+ }
+ if (cfg->swap & FIMD_WINCON_SWAP_WORD) {
+ x = ((x & 0x00000000FFFFFFFFULL) << 32) |
+ ((x & 0xFFFFFFFF00000000ULL) >> 32);
+ }
+ return x;
+}
+
+/* Palette/pixel to RGB conversion */
+
+#define DEF_PIXEL_TO_RGB(N, R, G, B, A) \
+static void N(uint32_t pixel, rgba *p) \
+{ \
+ p->b = (pixel & ((1 << (B)) - 1)) << (8 - (B)); \
+ pixel >>= (B); \
+ p->g = (pixel & ((1 << (G)) - 1)) << (8 - (G)); \
+ pixel >>= (G); \
+ p->r = (pixel & ((1 << (R)) - 1)) << (8 - (R)); \
+ pixel >>= (R); \
+ if (1 == (A)) { \
+ p->a = pixel & 1; \
+ } else if (8 == (A)) { \
+ p->a = pixel & 0xFF; \
+ p->a = (p->a << 16) | (p->a << 8) | p->a; \
+ } else { \
+ p->a = (pixel & ((1 << (A)) - 1)) << (8 - (A)); \
+ } \
+}
+
+DEF_PIXEL_TO_RGB(pixel_a232_to_rgb, 2, 3, 2, 1)
+DEF_PIXEL_TO_RGB(pixel_a444_to_rgb, 4, 4, 4, 1)
+DEF_PIXEL_TO_RGB(pixel_4444_to_rgb, 4, 4, 4, 4)
+DEF_PIXEL_TO_RGB(pixel_565_to_rgb, 5, 6, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_a555_to_rgb, 5, 5, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_555_to_rgb, 5, 5, 5, 0)
+DEF_PIXEL_TO_RGB(pixel_666_to_rgb, 6, 6, 6, 0)
+DEF_PIXEL_TO_RGB(pixel_a666_to_rgb, 6, 6, 6, 1)
+DEF_PIXEL_TO_RGB(pixel_a665_to_rgb, 6, 6, 5, 1)
+DEF_PIXEL_TO_RGB(pixel_888_to_rgb, 8, 8, 8, 0)
+DEF_PIXEL_TO_RGB(pixel_a888_to_rgb, 8, 8, 8, 1)
+DEF_PIXEL_TO_RGB(pixel_a887_to_rgb, 8, 8, 7, 1)
+DEF_PIXEL_TO_RGB(pixel_8888_to_rgb, 8, 8, 8, 8)
+
+/* Special case for (5+1,5+1,5+1) mode */
+static void pixel_1555_to_rgb(uint32_t pixel, rgba *p)
+{
+ uint8_t u = (pixel >> 15) & 1;
+ p->b = (((pixel & 0x1F) << 1) | u) << 2;
+ pixel >>= 5;
+ p->g = (((pixel & 0x3F) << 1) | u) << 2;
+ pixel >>= 6;
+ p->r = (((pixel & 0x1F) << 1) | u) << 2;
+}
+
+/* Draw line with pallete index in frame buffer data */
+#define DEF_DRAW_LINE_PALLETE(N) \
+static void glue(draw_line_pallete_, N)(DrawConfig *cfg, uint8_t *src, \
+ uint8_t *dst, uint8_t *ifb) \
+{ \
+ int width = cfg->width; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ data = ldq_raw((void *)src); \
+ src += 8; \
+ data = swap_data(cfg, data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ cfg->pixel_to_rgb(cfg->palette[(data >> ((N) * i)) & \
+ ((1ULL << (N)) - 1)], &p); \
+ if (cfg->blend) { \
+ ifb += cfg->get_pixel(ifb, &p_old); \
+ cfg->blend(cfg, p_old, p, &p); \
+ } \
+ dst += cfg->put_pixel(p, dst); \
+ } \
+ width -= (64 / (N)); \
+ } while (width > 0); \
+}
+
+/* Draw line with direct color value in frame buffer data */
+#define DEF_DRAW_LINE_NOPALLETE(N) \
+static void glue(draw_line_, N)(DrawConfig *cfg, uint8_t *src, \
+ uint8_t *dst, uint8_t *ifb) \
+{ \
+ int width = cfg->width; \
+ uint64_t data; \
+ rgba p, p_old; \
+ int i; \
+ do { \
+ data = ldq_raw((void *)src); \
+ src += 8; \
+ data = swap_data(cfg, data); \
+ for (i = (64 / (N) - 1); i >= 0; i--) { \
+ cfg->pixel_to_rgb((data >> ((N) * i)) & ((1ULL << (N)) - 1), &p); \
+ if (cfg->blend) { \
+ ifb += cfg->get_pixel(ifb, &p_old); \
+ cfg->blend(cfg, p_old, p, &p); \
+ } \
+ dst += cfg->put_pixel(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 pallete and non-pallete 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(DrawConfig *cfg, uint8_t *src,
+ uint8_t *dst, uint8_t *ifb)
+{
+ rgba p, p_old;
+ int width = cfg->width;
+ uint32_t map_color = cfg->map_color;
+
+ do {
+ pixel_888_to_rgb(map_color, &p);
+ if (cfg->blend) {
+ ifb += cfg->get_pixel(ifb, &p_old);
+ cfg->blend(cfg, p_old, p, &p);
+ }
+ dst += cfg->put_pixel(p, dst);
+ } while (--width);
+}
+
+/* Routine to copy line from internal frame buffer to QEMU display */
+static void draw_line_copy(DrawConfig *cfg, uint8_t *src, uint8_t *dst)
+{
+ rgba p;
+ int width = cfg->width;
+
+ do {
+ src += cfg->get_pixel(src, &p);
+ dst += cfg->put_pixel(p, dst);
+ } while (--width);
+}
+
+/* Parse BPPMODE_F bits and setup known DRAW_CONFIG fields accordingly.
+ BPPMODE_F = WINCON1[5:2] */
+static void s5pc210_parse_win_bppmode(S5pc210fimdWindow *w, DrawConfig *cfg)
+{
+ switch ((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) & FIMD_WINCON_BPPMODE) {
+ case 0:
+ cfg->draw_line = draw_line_pallete_1;
+ cfg->is_palletized = 1;
+ cfg->bpp = 1;
+ break;
+ case 1:
+ cfg->draw_line = draw_line_pallete_2;
+ cfg->is_palletized = 1;
+ cfg->bpp = 2;
+ break;
+ case 2:
+ cfg->draw_line = draw_line_pallete_4;
+ cfg->is_palletized = 1;
+ cfg->bpp = 4;
+ break;
+ case 3:
+ cfg->draw_line = draw_line_pallete_8;
+ cfg->is_palletized = 1;
+ cfg->bpp = 8;
+ break;
+ case 4:
+ cfg->draw_line = draw_line_8;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_a232_to_rgb;
+ cfg->bpp = 8;
+ break;
+ case 5:
+ cfg->draw_line = draw_line_16;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_565_to_rgb;
+ cfg->bpp = 16;
+ break;
+ case 6:
+ cfg->draw_line = draw_line_16;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_a555_to_rgb;
+ cfg->bpp = 16;
+ break;
+ case 7:
+ cfg->draw_line = draw_line_16;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_1555_to_rgb;
+ cfg->bpp = 16;
+ break;
+ case 8:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_666_to_rgb;
+ cfg->bpp = 32;
+ break;
+ case 9:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_a665_to_rgb;
+ cfg->bpp = 32;
+ break;
+ case 10:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_a666_to_rgb;
+ cfg->bpp = 32;
+ break;
+ case 11:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_888_to_rgb;
+ cfg->bpp = 32;
+ break;
+ case 12:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_a887_to_rgb;
+ cfg->bpp = 32;
+ break;
+ case 13:
+ cfg->draw_line = draw_line_32;
+ cfg->is_palletized = 0;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ cfg->pixel_to_rgb = pixel_8888_to_rgb;
+ cfg->fg_alpha_pix = 1;
+ } else {
+ cfg->pixel_to_rgb = pixel_a888_to_rgb;
+ }
+ cfg->bpp = 32;
+ break;
+ case 14:
+ cfg->draw_line = draw_line_16;
+ cfg->is_palletized = 0;
+ if ((w->wincon & FIMD_WINCON_BLD_PIX) && (w->wincon &
+ FIMD_WINCON_ALPHA_SEL)) {
+ cfg->pixel_to_rgb = pixel_4444_to_rgb;
+ cfg->fg_alpha_pix = 1;
+ } else {
+ cfg->pixel_to_rgb = pixel_a444_to_rgb;
+ }
+ cfg->bpp = 16;
+ break;
+ case 15:
+ cfg->draw_line = draw_line_16;
+ cfg->is_palletized = 0;
+ cfg->pixel_to_rgb = pixel_555_to_rgb;
+ cfg->bpp = 16;
+ break;
+ }
+}
+
+#if S5P_FIMD_MODE_TRACE > 0
+static const char *s5pc210_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";
+ }
+}
+#endif
+
+static inline void s5pc210_fimd_trace_bppmode(S5pc210fimdWindow *w,
+ int win_num, uint32_t val)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+ 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,
+ s5pc210_fimd_get_bppmode((w->wincon >> FIMD_WINCON_BPPMODE_SHIFT) &
+ FIMD_WINCON_BPPMODE),
+ s5pc210_fimd_get_bppmode((val >> FIMD_WINCON_BPPMODE_SHIFT) &
+ FIMD_WINCON_BPPMODE));
+#endif
+}
+
+static inline void s5pc210_fimd_trace_reset(void)
+{
+#if S5P_FIMD_MODE_TRACE > 0
+ fprintf(stderr, "QEMU FIMD: Display controller reset\n");
+#endif
+}
+
+static inline void s5pc210_fimd_enable(S5pc210fimdState *s, bool enabled)
+{
+ s->enabled = enabled ? true : false;
+#if S5P_FIMD_MODE_TRACE > 0
+ fprintf(stderr, "QEMU FIMD: display controller %s\n",
+ (enabled ? "enabled" : "disabled"));
+#endif
+}
+
+/* Returns WxPAL for given window number WINDOW */
+static uint32_t s5pc210_wxpal(S5pc210fimdState *s, int window)
+{
+ switch (window) {
+ case 0:
+ return (s->wpalcon[1] >> FIMD_WPAL_W0PAL_L_SH) & FIMD_WPAL_W0PAL_L;
+ case 1:
+ return (s->wpalcon[1] >> FIMD_WPAL_W1PAL_L_SH) & FIMD_WPAL_W1PAL_L;
+ case 2:
+ return ((s->wpalcon[0] >> FIMD_WPAL_W2PAL_H_SH) & FIMD_WPAL_W2PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W2PAL_L_SH) & FIMD_WPAL_W2PAL_L);
+ case 3:
+ return ((s->wpalcon[0] >> FIMD_WPAL_W3PAL_H_SH) & FIMD_WPAL_W3PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W3PAL_L_SH) & FIMD_WPAL_W3PAL_L);
+ case 4:
+ return ((s->wpalcon[0] >> FIMD_WPAL_W4PAL_H_SH) & FIMD_WPAL_W4PAL_H) |
+ ((s->wpalcon[1] >> FIMD_WPAL_W4PAL_L_SH) & FIMD_WPAL_W4PAL_L);
+ }
+ hw_error("s5pc210.fimd: incorrect window number %d\n", window);
+ return 0;
+}
+
+pixel_to_rgb_func *wxpal_to_rgb[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
+};
+
+/* Put/get pixel to/from internal LCD Controller framebuffer */
+
+static int put_rgba(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_rgba(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;
+ return RGBA_SIZE;
+}
+
+/* Write RGB to QEMU's GraphicConsole framebuffer */
+
+static int put_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_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_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_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_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;
+}
+
+static inline uint32_t unpack_by_4(uint32_t x)
+{
+ return ((x & 0xF00) << 12) | ((x & 0xF0) << 8) | ((x & 0xF) << 4);
+}
+
+
+/* Coefficient extraction functions */
+
+static uint32_t coef_zero(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return 0;
+}
+
+static uint32_t coef_one(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return 0xFFFFFF;
+}
+
+static uint32_t coef_alphaa(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ if (!cfg->fg_pixel_blending) {
+ pa.a = cfg->fg_alpha_sel;
+ }
+ if (cfg->fg_alpha_pix) {
+ return pa.a;
+ } else {
+ return cfg->fg_alpha[pa.a];
+ }
+}
+
+static uint32_t coef_one_minus_alphaa(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ if (!cfg->fg_pixel_blending) {
+ pa.a = cfg->fg_alpha_sel;
+ }
+ if (cfg->fg_alpha_pix) {
+ return 0xFFFFFF - pa.a;
+ } else {
+ return 0xFFFFFF - cfg->fg_alpha[pa.a];
+ }
+}
+
+static uint32_t coef_alphab(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ if (!cfg->bg_pixel_blending) {
+ pb.a = cfg->bg_alpha_sel;
+ }
+ if (cfg->bg_alpha_pix) {
+ return pb.a;
+ } else {
+ return cfg->bg_alpha[pb.a];
+ }
+}
+
+static uint32_t coef_one_minus_alphab(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ if (!cfg->bg_pixel_blending) {
+ pb.a = cfg->bg_alpha_sel;
+ }
+ if (cfg->bg_alpha_pix) {
+ return 0xFFFFFF - pb.a;
+ } else {
+ return 0xFFFFFF - cfg->bg_alpha[pb.a];
+ }
+}
+
+static uint32_t coef_a(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return (pa.r << 16) | (pa.g << 8) | pa.b;
+}
+
+static uint32_t coef_one_minus_a(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return 0xFFFFFF - ((pa.r << 16) | (pa.g << 8) | pa.b);
+}
+
+static uint32_t coef_b(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return (pb.r << 16) | (pb.g << 8) | pb.b;
+}
+
+static uint32_t coef_one_minus_b(const DrawConfig *cfg,
+ rgba pa, rgba pb)
+{
+ return 0xFFFFFF - ((pb.r << 16) | (pb.g << 8) | pb.b);
+}
+
+
+static coef_func *coef_decode(uint32_t x)
+{
+ switch (x) {
+ case 0:
+ return coef_zero;
+ case 1:
+ return coef_one;
+ case 2:
+ return coef_alphaa;
+ case 3:
+ return coef_one_minus_alphaa;
+ case 4:
+ return coef_alphab;
+ case 5:
+ return coef_one_minus_alphab;
+ case 10:
+ return coef_a;
+ case 11:
+ return coef_one_minus_a;
+ case 12:
+ return coef_b;
+ case 13:
+ return coef_one_minus_b;
+ default:
+ hw_error("s5pc210.fimd: blend equation coef illegal value\n");
+ return 0;
+ }
+}
+
+static void blend_alpha(const DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *res)
+{
+ uint32_t pl, ql, al, bl;
+ uint32_t p, q, a, b, fg, bg, fga, bga;
+
+ pl = cfg->coef_p(cfg, p_fg, p_bg);
+ ql = cfg->coef_q(cfg, p_fg, p_bg);
+ al = cfg->coef_a(cfg, p_fg, p_bg);
+ bl = cfg->coef_b(cfg, p_fg, p_bg);
+ res->a = 0;
+ /* B */
+ p = pl & 0xFF;
+ pl >>= 8;
+ q = ql & 0xFF;
+ ql >>= 8;
+ a = al & 0xFF;
+ al >>= 8;
+ b = bl & 0xFF;
+ bl >>= 8;
+ fg = p_fg.b;
+ bg = p_bg.b;
+ if (cfg->fg_pixel_blending) {
+ if (cfg->fg_alpha_pix) {
+ fga = p_fg.a & 0xFF;
+ } else {
+ fga = cfg->fg_alpha[p_fg.a] & 0xFF;
+ }
+ } else {
+ fga = cfg->fg_alpha[cfg->fg_alpha_sel] & 0xFF;
+ }
+ if (cfg->bg_pixel_blending) {
+ if (cfg->bg_alpha_pix) {
+ bga = p_bg.a & 0xFF;
+ } else {
+ bga = cfg->bg_alpha[p_bg.a] & 0xFF;
+ }
+ } else {
+ bga = cfg->bg_alpha[cfg->bg_alpha_sel] & 0xFF;
+ }
+ bg = (bg * b + fg * a) / 0xFF;
+ if (bg > 0xFF) {
+ res->b = 0xFF;
+ } else {
+ res->b = bg;
+ }
+ bga = (bga * p + fga * q) / 0xFF;
+ if (bga > 0xFF) {
+ res->a |= 0xFF;
+ } else {
+ res->a |= bga;
+ }
+ /* G */
+ p = pl & 0xFF;
+ pl >>= 8;
+ q = ql & 0xFF;
+ ql >>= 8;
+ a = al & 0xFF;
+ al >>= 8;
+ b = bl & 0xFF;
+ bl >>= 8;
+ fg = p_fg.g;
+ bg = p_bg.g;
+ if (cfg->fg_pixel_blending) {
+ if (cfg->fg_alpha_pix) {
+ fga = (p_fg.a >> 8) & 0xFF;
+ } else {
+ fga = (cfg->fg_alpha[p_fg.a] >> 8) & 0xFF;
+ }
+ } else {
+ fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 8) & 0xFF;
+ }
+ if (cfg->bg_pixel_blending) {
+ if (cfg->bg_alpha_pix) {
+ bga = (p_bg.a >> 8) & 0xFF;
+ } else {
+ bga = (cfg->bg_alpha[p_bg.a] >> 8) & 0xFF;
+ }
+ } else {
+ bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 8) & 0xFF;
+ }
+ bg = (bg * b + fg * a) / 0xFF;
+ if (bg > 0xFF) {
+ res->g = 0xFF;
+ } else {
+ res->g = bg;
+ }
+ bga = (bga * p + fga * q) / 0xFF;
+ if (bga > 0xFF) {
+ res->a |= 0xFF << 8;
+ } else {
+ res->a |= bga << 8;
+ }
+ /* R */
+ p = pl & 0xFF;
+ pl >>= 8;
+ q = ql & 0xFF;
+ ql >>= 8;
+ a = al & 0xFF;
+ al >>= 8;
+ b = bl & 0xFF;
+ bl >>= 8;
+ fg = p_fg.r;
+ bg = p_bg.r;
+ if (cfg->fg_pixel_blending) {
+ if (cfg->fg_alpha_pix) {
+ fga = (p_fg.a >> 16) & 0xFF;
+ } else {
+ fga = (cfg->fg_alpha[p_fg.a] >> 16) & 0xFF;
+ }
+ } else {
+ fga = (cfg->fg_alpha[cfg->fg_alpha_sel] >> 16) & 0xFF;
+ }
+ if (cfg->bg_pixel_blending) {
+ if (cfg->bg_alpha_pix) {
+ bga = (p_bg.a >> 16) & 0xFF;
+ } else {
+ bga = (cfg->bg_alpha[p_bg.a] >> 16) & 0xFF;
+ }
+ } else {
+ bga = (cfg->bg_alpha[cfg->bg_alpha_sel] >> 16) & 0xFF;
+ }
+ bg = (bg * b + fg * a) / 0xFF;
+ if (bg > 0xFF) {
+ res->r = 0xFF;
+ } else {
+ res->r = bg;
+ }
+ bga = (bga * p + fga * q) / 0xFF;
+ if (bga > 0xFF) {
+ res->a |= 0xFF << 16;
+ } else {
+ res->a |= bga << 16;
+ }
+}
+
+static void blend_colorkey(DrawConfig *cfg, rgba p_bg, rgba p_fg, rgba *p)
+{
+ uint8_t r, g, b;
+
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYEN) {
+ blend_alpha(cfg, p_bg, p_fg, p);
+ return ;
+ }
+ r = ((cfg->color_key & ~cfg->color_mask) >> 16) & 0xFF;
+ g = ((cfg->color_key & ~cfg->color_mask) >> 8) & 0xFF;
+ b = ((cfg->color_key & ~cfg->color_mask) >> 0) & 0xFF;
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_DIRCON) {
+ if ((p_fg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+ (p_fg.g & ~((cfg->color_mask >> 8) & 0xFF)) == g &&
+ (p_fg.b & ~((cfg->color_mask >> 0) & 0xFF)) == b) {
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+ p_fg.a = 1;
+ cfg->fg_pixel_blending = 0;
+ blend_alpha(cfg, p_bg, p_fg, p);
+ } else {
+ *p = p_bg;
+ }
+ } else {
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+ p_fg.a = 0;
+ cfg->fg_pixel_blending = 0;
+ blend_alpha(cfg, p_bg, p_fg, p);
+ } else {
+ *p = p_fg;
+ }
+ }
+ } else {
+ if ((p_bg.r & ~((cfg->color_mask >> 16) & 0xFF)) == r &&
+ (p_bg.g & ~((cfg->color_mask >> 8) & 0xFF)) == g &&
+ (p_bg.b & ~((cfg->color_mask >> 0) & 0xFF)) == b) {
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+ p_fg.a = 1;
+ cfg->fg_pixel_blending = 0;
+ blend_alpha(cfg, p_bg, p_fg, p);
+ } else {
+ *p = p_fg;
+ }
+ } else {
+ if (cfg->color_ctl & FIMD_WKEYCON0_CTL_KEYBLEN) {
+ p_fg.a = 0;
+ cfg->fg_pixel_blending = 0;
+ blend_alpha(cfg, p_bg, p_fg, p);
+ } else {
+ *p = p_bg;
+ }
+ }
+ }
+}
+
+static inline void putpixel_by_bpp(DrawConfig *cfg, int bpp)
+{
+ switch (bpp) {
+ case 8:
+ cfg->put_pixel = put_pixel8;
+ break;
+ case 15:
+ cfg->put_pixel = put_pixel15;
+ break;
+ case 16:
+ cfg->put_pixel = put_pixel16;
+ break;
+ case 24:
+ cfg->put_pixel = put_pixel24;
+ break;
+ case 32:
+ cfg->put_pixel = put_pixel32;
+ break;
+ default:
+ hw_error("s5pc210.fimd: unsupported BPP (%d)", bpp);
+ break;
+ }
+}
+
+static void s5pc210_fimd_update_irq(S5pc210fimdState *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 s5pc210_update_resolution(S5pc210fimdState *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) {
+ print_debug1("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);
+ memset(s->ifb, 0, width * height * RGBA_SIZE);
+ s->invalidate = true;
+ }
+}
+
+static void s5pc210_fimd_update(void *opaque)
+{
+ S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+ DrawConfig cfg;
+ int i, line, buf_id;
+ target_phys_addr_t fb_start_addr, fb_next_line_addr, inc_size, fb_len, x;
+ int lefttop_x, lefttop_y, rightbottom_x, rightbottom_y;
+ int width, height;
+ int first_line = -1, last_line = -1, fb_line_size;
+ uint8_t is_first_window = 1;
+ 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;
+ }
+
+ memset(&cfg, 0, sizeof(cfg));
+ s5pc210_update_resolution(s);
+
+ for (i = 0; i < NUM_OF_WINDOWS; i++) {
+ if (s->window[i].wincon & FIMD_WINCON_ENWIN) {
+ cfg.fg_alpha_pix = 0;
+ s5pc210_parse_win_bppmode(&s->window[i], &cfg);
+ /* If we have mode with palletized color then we need to parse
+ palette color mode and set pixel-to-rgb conversion function
+ accordingly. */
+ if (cfg.is_palletized) {
+ uint32_t tmp = s5pc210_wxpal(s, i);
+ /* Different windows have different mapping WxPAL to palette
+ pixel format. This transform happens to unify them all. */
+ if (i < 2 && tmp < 7) {
+ tmp = 6 - tmp;
+ }
+ cfg.pixel_to_rgb = wxpal_to_rgb[tmp];
+ if (tmp == 7) {
+ cfg.fg_alpha_pix = 1;
+ }
+ }
+ cfg.put_pixel = put_rgba;
+ cfg.get_pixel = get_rgba;
+ cfg.bg_alpha_pix = 1;
+ cfg.color_mask = s->window[i].keycon[0] & FIMD_WKEYCON0_COMPKEY;
+ cfg.color_key = s->window[i].keycon[1];
+ cfg.color_ctl =
+ (s->window[i].keycon[0] >> FIMD_WKEYCON0_CTL_SHIFT) & 7;
+ if (i == 0) {
+ cfg.fg_alpha[0] = s->window[i].vidw_alpha[0];
+ cfg.fg_alpha[1] = s->window[i].vidw_alpha[1];
+ } else {
+ cfg.fg_alpha[0] =
+ unpack_by_4((s->window[i].vidosd[2] &
+ FIMD_VIDOSD_ALPHA_AEN0) >> FIMD_VIDOSD_AEN0_SHIFT) |
+ (s->window[i].vidw_alpha[0] & FIMD_VIDALPHA_ALPHA_MASK);
+ cfg.fg_alpha[1] =
+ unpack_by_4(s->window[i].vidosd[2] &
+ FIMD_VIDOSD_ALPHA_AEN1) | (s->window[i].vidw_alpha[0]
+ & FIMD_VIDALPHA_ALPHA_MASK);
+ }
+ cfg.bg_pixel_blending = 1;
+ cfg.fg_pixel_blending = s->window[i].wincon & FIMD_WINCON_BLD_PIX;
+ cfg.fg_alpha_sel = (s->window[i].wincon & FIMD_WINCON_ALPHA_SEL) >>
+ FIMD_WINCON_ALSEL_SHIFT;
+ cfg.palette = s->window[i].palette;
+ cfg.swap = (s->window[i].wincon & FIMD_WINCON_SWAP) >>
+ FIMD_WINCON_SWAP_SHIFT;
+ cfg.coef_q = coef_decode((s->window[i].blendeq >>
+ FIMD_BLENDEQ_COEF_Q) & FIMD_BLENDEQ_COEF_MASK);
+ cfg.coef_p = coef_decode((s->window[i].blendeq >>
+ FIMD_BLENDEQ_COEF_P) & FIMD_BLENDEQ_COEF_MASK);
+ cfg.coef_b = coef_decode((s->window[i].blendeq >>
+ FIMD_BLENDEQ_COEF_B) & FIMD_BLENDEQ_COEF_MASK);
+ cfg.coef_a = coef_decode((s->window[i].blendeq >>
+ FIMD_BLENDEQ_COEF_A) & FIMD_BLENDEQ_COEF_MASK);
+ if (is_first_window) {
+ cfg.blend = NULL;
+ } else {
+ cfg.blend = blend_colorkey;
+ }
+ is_first_window = 0;
+ if (s->window[i].winmap & FIMD_WINMAP_EN) {
+ cfg.map_color = s->window[i].winmap & FIMD_WINMAP_COLOR_MASK;
+ cfg.draw_line = draw_line_mapcolor;
+ }
+
+ /* At this point CFG is fully set up except WIDTH. We can proceed
+ with drawing. */
+ lefttop_x = (s->window[i].vidosd[0] >> FIMD_VIDOSD_HOR_SHIFT)
+ & FIMD_VIDOSD_COORD_MASK;
+ lefttop_y = (s->window[i].vidosd[0] >> FIMD_VIDOSD_VER_SHIFT)
+ & FIMD_VIDOSD_COORD_MASK;
+ rightbottom_x = (s->window[i].vidosd[1] >> FIMD_VIDOSD_HOR_SHIFT)
+ & FIMD_VIDOSD_COORD_MASK;
+ rightbottom_y = (s->window[i].vidosd[1] >> FIMD_VIDOSD_VER_SHIFT)
+ & FIMD_VIDOSD_COORD_MASK;
+ height = rightbottom_y - lefttop_y + 1;
+ width = rightbottom_x - lefttop_x + 1;
+ cfg.width = width;
+ buf_id = s5pc210_buffer_status(&s->window[i]);
+
+ fb_line_size = s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH;
+ fb_start_addr = s->window[i].buf_start[buf_id];
+ inc_size = (s->window[i].buf_size & FIMD_VIDWADD2_PAGEWIDTH) +
+ ((s->window[i].buf_size >> FIMD_VIDWADD2_OFFSIZE_SHIFT) &
+ FIMD_VIDWADD2_OFFSIZE);
+ fb_len = inc_size * 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 * height) {
+ cpu_physical_memory_unmap(host_fb_addr, fb_len, 0, 0);
+ return;
+ }
+
+ host_fb_start_addr = host_fb_addr;
+ for (line = 0; line < height; line++) {
+ fb_next_line_addr = fb_start_addr + fb_line_size;
+ for (x = fb_start_addr; x < fb_next_line_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;
+ cfg.draw_line(&cfg, host_fb_addr,
+ s->ifb + lefttop_x * RGBA_SIZE +
+ (lefttop_y + line) * global_width * RGBA_SIZE,
+ s->ifb + lefttop_x * RGBA_SIZE +
+ (lefttop_y + line) * global_width * RGBA_SIZE);
+ }
+ 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[buf_id];
+ 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 * height,
+ VGA_DIRTY_FLAG);
+ }
+ }
+ /* Last pass: copy resulting image to QEMU_CONSOLE. */
+ if (first_line >= 0) {
+ uint8_t *d;
+ int bpp;
+
+ cfg.get_pixel = get_rgba;
+ bpp = ds_get_bits_per_pixel(s->console);
+ putpixel_by_bpp(&cfg, bpp);
+ bpp = (bpp + 1) >> 3;
+ d = ds_get_data(s->console);
+ for (line = first_line; line < last_line; line++) {
+ draw_line_copy(&cfg, 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) {
+ s5pc210_fimd_enable(s, false);
+ }
+ s5pc210_fimd_update_irq(s);
+}
+
+static void s5pc210_fimd_invalidate(void *opaque)
+{
+ S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+ s->invalidate = true;
+}
+
+static void s5pc210_fimd_reset(DeviceState *d)
+{
+ S5pc210fimdState *s = container_of(d, S5pc210fimdState, busdev.qdev);
+ int i;
+ unsigned long begin = (unsigned long)s + offsetof(S5pc210fimdState, vidcon);
+ unsigned long len = ((unsigned long)s + offsetof(S5pc210fimdState, window))
+ - begin;
+ s5pc210_fimd_trace_reset();
+
+ /* 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(S5pc210fimdWindow));
+ s->window[i].blendeq = 0xC2;
+ }
+
+ if (s->ifb != NULL) {
+ g_free(s->ifb);
+ }
+ s->ifb = NULL;
+
+ s->invalidate = true;
+ s5pc210_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 s5pc210_fimd_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+ int w, i;
+
+ print_debug2("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) {
+ s5pc210_fimd_enable(s, true);
+ } else {
+ if ((val & FIMD_VIDCON0_ENVID) == 0) {
+ s5pc210_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 >= 0x010 && offset <= 0x01C) {
+ s->vidtcon[(offset - 0x010) >> 2] = val;
+ } else if (offset >= 0x020 && offset <= 0x030) {
+ w = (offset - 0x020) >> 2;
+ val = (val & ~FIMD_WINCON_ROMASK) |
+ (s->window[w].wincon & FIMD_WINCON_ROMASK);
+ s5pc210_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;
+ } else if (offset == 0x034) {
+ s->shadowcon = val;
+ } else if (offset == 0x03C) {
+ s->winchmap = val;
+ } else if (offset >= 0x040 && offset <= 0x088) {
+ w = (offset - 0x040) >> 4;
+ i = ((offset - 0x040) & 0xF) >> 2;
+ if (i == 3 && w != 1 && w != 2) {
+ print_error("Bad write offset 0x%08x\n", offset);
+ }
+ s->window[w].vidosd[i] = val;
+ } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+ w = (offset - 0x0A0) >> 3;
+ i = ((offset - 0x0A0) >> 2) & 1;
+ s->window[w].buf_start[i] = val;
+ } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+ w = (offset - 0x0D0) >> 3;
+ i = ((offset - 0x0D0) >> 2) & 1;
+ s->window[w].buf_end[i] = val;
+ } else if (offset >= 0x100 && offset <= 0x110) {
+ s->window[(offset - 0x100) >> 2].buf_size = val;
+ } else if (offset == 0x130) {
+ s->vidintcon[0] = val;
+ } else if (offset == 0x134) {
+ s->vidintcon[1] &= ~(val & 7);
+ s5pc210_fimd_update_irq(s);
+ } else if (offset >= 0x140 && offset <= 0x15C) {
+ w = ((offset - 0x140) >> 3) + 1;
+ i = ((offset - 0x140) >> 2) & 1;
+ s->window[w].keycon[i] = val;
+ } else if (offset >= 0x160 && offset <= 0x16C) {
+ w = ((offset - 0x160) >> 2) + 1;
+ s->window[w].keyalpha = val;
+ } else if (offset == 0x170) {
+ s->dithmode = val;
+ } else if (offset >= 0x180 && offset <= 0x190) {
+ if (val & FIMD_WINMAP_EN) {
+ s->invalidate = true;
+ s5pc210_fimd_update(s);
+ }
+ s->window[(offset - 0x180) >> 2].winmap = val;
+ } else if (offset >= 0x19C && offset <= 0x1A0) {
+ s->wpalcon[(offset - 0x19C) >> 2] = val;
+ } else if (offset == 0x1A4) {
+ val = (val & ~FIMD_TRIGCON_ROMASK) | (s->trigcon & FIMD_TRIGCON_ROMASK);
+ s->trigcon = val;
+ } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+ s->i80ifcon[(offset - 0x1B0) >> 2] = val;
+ } else if (offset == 0x1C0) {
+ s->colorgaincon = val;
+ } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+ s->ldi_cmdcon[(offset - 0x1D0) >> 2] = val;
+ } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+ i = (offset - 0x1E0) >> 2;
+ if (i != 2) {
+ s->sifccon[i] = val;
+ }
+ } else if (offset >= 0x1EC && offset <= 0x1F8) {
+ i = (offset - 0x1EC) >> 2;
+ s->huecoef_cr[i] = val;
+ } else if (offset >= 0x1FC && offset <= 0x208) {
+ i = (offset - 0x1FC) >> 2;
+ s->huecoef_cb[i] = val;
+ } else if (offset == 0x20C) {
+ s->hueoffset = val;
+ } else if (offset >= 0x21C && offset <= 0x240) {
+ w = ((offset - 0x21C) >> 3);
+ i = ((offset - 0x21C) >> 2) & 1;
+ s->window[w].vidw_alpha[i] = val;
+ } else if (offset >= 0x244 && offset <= 0x250) {
+ s->window[(offset - 0x244) >> 2].blendeq = val;
+ } else if (offset == 0x260) {
+ s->blendcon = val;
+ } else if (offset >= 0x264 && offset <= 0x274) {
+ s->window[(offset - 0x264) >> 2].rtqoscon = val;
+ } else if (offset == 0x27C) {
+ s->dualrgb = val;
+ } else if (offset >= 0x280 && offset <= 0x2AC) {
+ s->i80ifcmd[(offset - 0x280) >> 2] = val;
+ } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+ if (offset & 0x0004) {
+ print_error("bad write offset 0x%08x\n", offset);
+ }
+ s->window[(offset - 0x40A0) >> 3].shadow_buf_start = val;
+ } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+ if (offset & 0x0004) {
+ print_error("bad write offset 0x%08x\n", offset);
+ }
+ s->window[(offset - 0x40D0) >> 3].shadow_buf_end = val;
+ } else if (offset >= 0x4100 && offset <= 0x4110) {
+ s->window[(offset - 0x4100) >> 2].shadow_buf_size = val;
+ } else if (offset >= 0x2400 && offset <= 0x37FC) {
+ w = (offset - 0x2400) >> 10;
+ i = ((offset - 0x2400) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+ w = (offset - 0x0400) >> 10;
+ i = ((offset - 0x0400) >> 2) & 0xFF;
+ s->window[w].palette[i] = val;
+ } else {
+ print_error("bad write offset 0x%08x\n", offset);
+ }
+}
+
+static uint64_t s5pc210_fimd_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+ int w, i;
+
+ print_debug2("read offset 0x%08x\n", offset);
+
+ if (offset <= 0x00C) {
+ return s->vidcon[(offset) >> 2];
+ } else if (offset >= 0x010 && offset <= 0x01C) {
+ return s->vidtcon[(offset - 0x010) >> 2];
+ } else if (offset >= 0x020 && offset <= 0x030) {
+ return s->window[(offset - 0x020) >> 2].wincon;
+ } else if (offset == 0x034) {
+ return s->shadowcon;
+ } else if (offset == 0x03C) {
+ return s->winchmap;
+ } else if (offset >= 0x040 && offset <= 0x088) {
+ w = (offset - 0x040) >> 4;
+ i = ((offset - 0x040) & 0xF) >> 2;
+ if (i == 3 && w != 1 && w != 2) {
+ goto return_error;
+ }
+ return s->window[w].vidosd[i];
+ } else if (offset >= 0x0A0 && offset <= 0x0C4) {
+ w = (offset - 0x0A0) >> 3;
+ i = ((offset - 0x0A0) >> 2) & 1;
+ return s->window[w].buf_start[i];
+ } else if (offset >= 0x0D0 && offset <= 0x0F4) {
+ w = (offset - 0x0D0) >> 3;
+ i = ((offset - 0x0D0) >> 2) & 1;
+ return s->window[w].buf_end[i];
+ } else if (offset >= 0x100 && offset <= 0x110) {
+ return s->window[(offset - 0x100) >> 2].buf_size;
+ } else if (offset >= 0x130 && offset <= 0x134) {
+ return s->vidintcon[(offset - 0x130) >> 2];
+ } else if (offset >= 0x140 && offset <= 0x15C) {
+ w = ((offset - 0x140) >> 3) + 1;
+ i = ((offset - 0x140) >> 2) & 1;
+ return s->window[w].keycon[i];
+ } else if (offset >= 0x160 && offset <= 0x16C) {
+ w = ((offset - 0x160) >> 2) + 1;
+ return s->window[w].keyalpha;
+ } else if (offset == 0x170) {
+ return s->dithmode;
+ } else if (offset >= 0x180 && offset <= 0x190) {
+ return s->window[(offset - 0x180) >> 2].winmap;
+ } else if (offset >= 0x19C && offset <= 0x1A0) {
+ return s->wpalcon[(offset - 0x19C) >> 2];
+ } else if (offset == 0x1A4) {
+ return s->trigcon;
+ } else if (offset >= 0x1B0 && offset <= 0x1BC) {
+ return s->i80ifcon[(offset - 0x1B0) >> 2];
+ } else if (offset == 0x1C0) {
+ return s->colorgaincon;
+ } else if (offset >= 0x1D0 && offset <= 0x1D4) {
+ return s->ldi_cmdcon[(offset - 0x1D0) >> 2];
+ } else if (offset >= 0x1E0 && offset <= 0x1E8) {
+ i = (offset - 0x1E0) >> 2;
+ return s->sifccon[i];
+ } else if (offset >= 0x1EC && offset <= 0x1F8) {
+ i = (offset - 0x1EC) >> 2;
+ return s->huecoef_cr[i];
+ } else if (offset >= 0x1FC && offset <= 0x208) {
+ i = (offset - 0x1FC) >> 2;
+ return s->huecoef_cb[i];
+ } else if (offset == 0x20C) {
+ return s->hueoffset;
+ } else if (offset >= 0x21C && offset <= 0x240) {
+ w = ((offset - 0x21C) >> 3);
+ i = ((offset - 0x21C) >> 2) & 1;
+ return s->window[w].vidw_alpha[i];
+ } else if (offset >= 0x244 && offset <= 0x250) {
+ return s->window[(offset - 0x244) >> 2].blendeq;
+ } else if (offset == 0x260) {
+ return s->blendcon;
+ } else if (offset >= 0x264 && offset <= 0x274) {
+ return s->window[(offset - 0x264) >> 2].rtqoscon;
+ } else if (offset == 0x27C) {
+ return s->dualrgb;
+ } else if (offset >= 0x280 && offset <= 0x2AC) {
+ return s->i80ifcmd[(offset - 0x280) >> 2];
+ } else if (offset >= 0x40A0 && offset <= 0x40C0) {
+ if (offset & 0x0004) {
+ goto return_error;
+ }
+ return s->window[(offset - 0x40A0) >> 3].shadow_buf_start;
+ } else if (offset >= 0x40D0 && offset <= 0x40F0) {
+ if (offset & 0x0004) {
+ goto return_error;
+ }
+ return s->window[(offset - 0x40D0) >> 3].shadow_buf_end;
+ } else if (offset >= 0x4100 && offset <= 0x4110) {
+ return s->window[(offset - 0x4100) >> 2].shadow_buf_size;
+ } else if (offset >= 0x2400 && offset <= 0x37FC) {
+ w = (offset - 0x2400) >> 10;
+ i = ((offset - 0x2400) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ } else if (offset >= 0x0400 && offset <= 0x0BFC) {
+ /* Palete aliases for win 0,1 */
+ w = (offset - 0x0400) >> 10;
+ i = ((offset - 0x0400) >> 2) & 0xFF;
+ return s->window[w].palette[i];
+ }
+return_error:
+ print_error("bad read offset 0x%08x\n", offset);
+ return 0xBAADBAAD;
+}
+
+static const MemoryRegionOps s5pc210_fimd_mmio_ops = {
+ .read = s5pc210_fimd_read,
+ .write = s5pc210_fimd_write,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int s5pc210_fimd_load(void *opaque, int version_id)
+{
+ S5pc210fimdState *s = (S5pc210fimdState *)opaque;
+
+ if (version_id != 1) {
+ return -EINVAL;
+ }
+
+ /* Redraw the whole screen */
+ s5pc210_update_resolution(s);
+ s5pc210_fimd_invalidate(s);
+ s5pc210_fimd_enable(s, (s->vidcon[0] & FIMD_VIDCON0_ENVID_MASK) ==
+ FIMD_VIDCON0_ENVID_MASK);
+ return 0;
+}
+
+static const VMStateDescription s5pc210_fimd_window_vmstate = {
+ .name = "s5pc210.fimd_window",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(wincon, S5pc210fimdWindow),
+ VMSTATE_UINT32_ARRAY(vidosd, S5pc210fimdWindow, 4),
+ VMSTATE_UINT32_ARRAY(buf_start, S5pc210fimdWindow, 3),
+ VMSTATE_UINT32_ARRAY(buf_end, S5pc210fimdWindow, 3),
+ VMSTATE_UINT32(buf_size, S5pc210fimdWindow),
+ VMSTATE_UINT32_ARRAY(keycon, S5pc210fimdWindow, 2),
+ VMSTATE_UINT32(keyalpha, S5pc210fimdWindow),
+ VMSTATE_UINT32(winmap, S5pc210fimdWindow),
+ VMSTATE_UINT32_ARRAY(vidw_alpha, S5pc210fimdWindow, 2),
+ VMSTATE_UINT32(blendeq, S5pc210fimdWindow),
+ VMSTATE_UINT32(rtqoscon, S5pc210fimdWindow),
+ VMSTATE_UINT32_ARRAY(palette, S5pc210fimdWindow, 256),
+ VMSTATE_UINT32(shadow_buf_start, S5pc210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_end, S5pc210fimdWindow),
+ VMSTATE_UINT32(shadow_buf_size, S5pc210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription s5pc210_fimd_vmstate = {
+ .name = "s5pc210.fimd",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .post_load = s5pc210_fimd_load,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(vidcon, S5pc210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(vidtcon, S5pc210fimdState, 4),
+ VMSTATE_UINT32(shadowcon, S5pc210fimdState),
+ VMSTATE_UINT32(winchmap, S5pc210fimdState),
+ VMSTATE_UINT32_ARRAY(vidintcon, S5pc210fimdState, 2),
+ VMSTATE_UINT32(dithmode, S5pc210fimdState),
+ VMSTATE_UINT32_ARRAY(wpalcon, S5pc210fimdState, 2),
+ VMSTATE_UINT32(trigcon, S5pc210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcon, S5pc210fimdState, 4),
+ VMSTATE_UINT32(colorgaincon, S5pc210fimdState),
+ VMSTATE_UINT32_ARRAY(ldi_cmdcon, S5pc210fimdState, 2),
+ VMSTATE_UINT32_ARRAY(sifccon, S5pc210fimdState, 3),
+ VMSTATE_UINT32_ARRAY(huecoef_cr, S5pc210fimdState, 4),
+ VMSTATE_UINT32_ARRAY(huecoef_cb, S5pc210fimdState, 4),
+ VMSTATE_UINT32(hueoffset, S5pc210fimdState),
+ VMSTATE_UINT32(blendcon, S5pc210fimdState),
+ VMSTATE_UINT32(dualrgb, S5pc210fimdState),
+ VMSTATE_UINT32_ARRAY(i80ifcmd, S5pc210fimdState, 12),
+ VMSTATE_STRUCT_ARRAY(window, S5pc210fimdState, 5, 1,
+ s5pc210_fimd_window_vmstate, S5pc210fimdWindow),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static int s5pc210_fimd_init(SysBusDevice *dev)
+{
+ S5pc210fimdState *s = FROM_SYSBUS(S5pc210fimdState, 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, &s5pc210_fimd_mmio_ops, s, "s5pc210.fimd",
+ FIMD_REGS_SIZE);
+ sysbus_init_mmio_region(dev, &s->iomem);
+ s->console = graphic_console_init(s5pc210_fimd_update,
+ s5pc210_fimd_invalidate, NULL, NULL, s);
+
+ return 0;
+}
+
+static SysBusDeviceInfo s5pc210_fimd_info = {
+ .init = s5pc210_fimd_init,
+ .qdev.name = "s5pc210.fimd",
+ .qdev.size = sizeof(S5pc210fimdState),
+ .qdev.vmsd = &s5pc210_fimd_vmstate,
+ .qdev.reset = s5pc210_fimd_reset,
+};
+
+static void s5pc210_fimd_register_devices(void)
+{
+ sysbus_register_withprop(&s5pc210_fimd_info);
+}
+
+device_init(s5pc210_fimd_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state).
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (10 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 11/14] ARM: s5pc210: added s5pc210 display controller device (FIMD) Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 9:47 ` [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation Evgeny Voevodin
` (2 subsequent siblings)
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: Mitsyanko Igor, d.solodkiy, Evgeny Voevodin
From: Mitsyanko Igor <i.mitsyanko@samsung.com>
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..ddb9d39 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_recieve_ready(SDState *sd)
+{
+ return sd->state == sd_receivingdata_state;
+}
diff --git a/hw/sd.h b/hw/sd.h
index ac4b7c4..782c8d6 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_recieve_ready(SDState *sd);
#endif /* __hw_sd_h */
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (11 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 12/14] SD card: add query function to check wether SD card currently ready to recieve data Before executing data transfer to card, we must check that previously issued command wasn't a simple query command (for ex. CMD13), which doesn't require data transfer. Currently, we only can aquire information about whether SD card is in sending data state or not. This patch allows us to query wether previous command was data write command and it was successfully accepted by card (meaning that SD card in recieving data state) Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
14 siblings, 0 replies; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: 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/s5pc210.c | 20 +
hw/s5pc210_sdhc.c | 1693 +++++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 1715 insertions(+), 1 deletions(-)
create mode 100644 hw/s5pc210_sdhc.c
diff --git a/Makefile.target b/Makefile.target
index 84c4a05..be51dbf 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 += s5pc210.o s5pc210_cmu.o s5pc210_uart.o s5pc210_gic.o \
- s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o
+ s5pc210_combiner.o s5pc210_pwm.o s5pc210_mct.o s5pc210_fimd.o \
+ s5pc210_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/s5pc210.c b/hw/s5pc210.c
index c7cba3e..8892f0a 100644
--- a/hw/s5pc210.c
+++ b/hw/s5pc210.c
@@ -97,6 +97,12 @@
/* MCT */
#define S5PC210_MCT_BASE_ADDR 0x10050000
+/* SD/MMC host controllers SFR base addresses */
+#define S5PC210_SDHC0_BASE_ADDR 0x12510000
+#define S5PC210_SDHC1_BASE_ADDR 0x12520000
+#define S5PC210_SDHC2_BASE_ADDR 0x12530000
+#define S5PC210_SDHC3_BASE_ADDR 0x12540000
+
/* Display controllers (FIMD) */
#define S5PC210_FIMD0_BASE_ADDR 0x11C00000
#define S5PC210_FIMD1_BASE_ADDR 0x12000000
@@ -454,6 +460,20 @@ static void s5pc210_init(ram_addr_t ram_size,
s5pc210_uart_create(addr, fifo_size, channel, NULL, uart_irq);
}
+ /*** SD/MMC host controllers ***/
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC0_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 0)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC1_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 1)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC2_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 2)]);
+
+ sysbus_create_simple("s5pc210.sdhc", S5PC210_SDHC3_BASE_ADDR,
+ irq_table[s5pc210_get_irq(29, 3)]);
+
/*** LAN adapter ***/
if (board_type == BOARD_S5PC210_SMDKC210) {
diff --git a/hw/s5pc210_sdhc.c b/hw/s5pc210_sdhc.c
new file mode 100644
index 0000000..dbbb885
--- /dev/null
+++ b/hw/s5pc210_sdhc.c
@@ -0,0 +1,1693 @@
+/*
+ * Samsung s5pc210 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 Suport */
+#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 offset
+ ***************************************************/
+
+/* 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 divied 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 stoped at bloch gap during read operation */
+ gap_write = 2, /* sdhc stoped at bloch gap during write operation */
+ adma_intr = 3 /* sdhc stoped after ADMA interrupt occured */
+};
+
+/* SD/MMC host controller state */
+typedef struct S5pc210SDHCState {
+ 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;
+ uint8_t stoped_state;
+
+ /* sd host i/o fifo buffer */
+ uint32_t fifo_buffer[SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE];
+ uint16_t data_count; /* curent element in fifo buffer */
+
+ uint32_t sdmasysad; /* SDMA System Address register */
+ uint16_t blksize; /* Host DMA Buff Boundary and Transfer BlSize 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 */
+/* uint32_t 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 swrst Software Reset Register - must 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 */
+/* uint16_t feaer Force Event Auto CMD12 Error Interrupt Reg - write only */
+/* uint16_t 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) */
+/* uint32_t control4 makes no sense in emulation so always return 0 */
+/* uint16_t hcver Host Controller Ver Reg 0x2401 -sd specification v2 */
+} S5pc210SDHCState;
+
+/****************************************************
+ * Interrupt raising
+ * functions
+ ***************************************************/
+
+static void sdhc_raise_transfer_complete_irq(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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 comand response recieved interrupt */
+static inline void sdhc_raise_response_recieved_irq(S5pc210SDHCState *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)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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));
+ }
+}
+
+/****************************************************
+ * Misc control
+ * functions
+ ***************************************************/
+
+/* Reset ALL */
+static void s5pc210_sdhc_reset(DeviceState *d)
+{
+ S5pc210SDHCState *s = container_of(d, S5pc210SDHCState, busdev.qdev);
+ unsigned long begin = (unsigned long)s + offsetof(S5pc210SDHCState,
+ sdmasysad);
+ unsigned long len = ((unsigned long)s + offsetof(S5pc210SDHCState,
+ 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(S5pc210SDHCState *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;
+ }
+}
+
+/* Do transfer complete routine */
+static void sdhc_do_transfer_complete(S5pc210SDHCState *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
+ * functions
+ ***************************************************/
+
+/* Fill host controlerr's read buffer with BLKSIZE bytes of data from card */
+static void sdhc_read_block_from_card(void *opaque)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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 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 controler BUFFER DATA PORT register */
+static inline uint32_t sdhc_read_dataport(S5pc210SDHCState *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) 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)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)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 writen 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 requsted and if not the last block of data */
+ 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 controler BUFFER DATA PORT register */
+static inline void sdhc_write_dataport(S5pc210SDHCState *s, uint32_t value)
+{
+ if ((s->prnsts & SDHC_SPACE_AVAILABLE) == 0) {
+ DPRINTF("s5pc210.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
+ * functions
+ ***************************************************/
+
+/* multi block SDMA transfer */
+static void sdhc_sdma_transfer_multi_blocks(S5pc210SDHCState *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 loop when it stoped at DMA buffer boundary.
+ * Will not execute if all SDMA transfer are aligned to DMA buff 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(S5pc210SDHCState *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);
+}
+
+/****************************************************
+ * Advansed DMA data transfer
+ * function
+ ***************************************************/
+
+static void sdhc_run_adma(S5pc210SDHCState *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 occured 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;
+ }
+ }
+}
+
+/****************************************************
+ * Do nessesary transfers, specified by previosly
+ * issued command
+ ***************************************************/
+static void sdhc_process_command(S5pc210SDHCState *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_recieve_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_recieve_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 from and write to SD/MMC host controller
+ * registers functions
+ ***************************************************/
+
+/* Read byte from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_1(S5pc210SDHCState *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("sdhc: bad 1 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* Read two bytes from SD/MMC host controller registers */
+static uint32_t s5pc210_sdhc_read_2(S5pc210SDHCState *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("sdhc: bad 2 byte read offset " TARGET_FMT_plx "\n", offset);
+ return 0xBAADBAAD;
+ }
+}
+
+/* MMC read 4 bytes function */
+static uint32_t s5pc210_sdhc_read_4(S5pc210SDHCState *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 s5pc210_sdhc_write_1(S5pc210SDHCState *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:
+ s5pc210_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 s5pc210_sdhc_write_2(S5pc210SDHCState *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 s5pc210_sdhc_write_4(S5pc210SDHCState *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("sdhc: bad 4 byte write offset " TARGET_FMT_plx "\n", offset);
+ break;
+ }
+}
+
+static uint64_t s5pc210_sdhc_read(void *opaque, target_phys_addr_t offset,
+ unsigned size)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ return s5pc210_sdhc_read_1(s, offset);
+ case (2):
+ return s5pc210_sdhc_read_2(s, offset);
+ case (4):
+ return s5pc210_sdhc_read_4(s, offset);
+ }
+ return 0xBAADBAADull;
+}
+
+static void s5pc210_sdhc_write(void *opaque, target_phys_addr_t offset,
+ uint64_t val, unsigned size)
+{
+ S5pc210SDHCState *s = (S5pc210SDHCState *)opaque;
+
+ switch (size) {
+ case (1):
+ s5pc210_sdhc_write_1(s, offset, (uint32_t)val);
+ break;
+ case (2):
+ s5pc210_sdhc_write_2(s, offset, (uint32_t)val);
+ break;
+ case (4):
+ s5pc210_sdhc_write_4(s, offset, (uint32_t)val);
+ break;
+ }
+}
+
+static const MemoryRegionOps s5pc210_sdhc_mmio_ops = {
+ .read = s5pc210_sdhc_read,
+ .write = s5pc210_sdhc_write,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ .unaligned = false
+ },
+ .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static const VMStateDescription s5pc210_sdhc_vmstate = {
+ .name = "s5pc210.sdhc",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .minimum_version_id_old = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT8(stoped_state, S5pc210SDHCState),
+ VMSTATE_UINT32_ARRAY(fifo_buffer, S5pc210SDHCState,
+ (SDHC_CAPAB_MAXBLOCKLENGTH / TARGET_WORD_SIZE)),
+ VMSTATE_UINT16(data_count, S5pc210SDHCState),
+ VMSTATE_UINT32(sdmasysad, S5pc210SDHCState),
+ VMSTATE_UINT16(blksize, S5pc210SDHCState),
+ VMSTATE_UINT16(blkcnt, S5pc210SDHCState),
+ VMSTATE_UINT32(argument, S5pc210SDHCState),
+ VMSTATE_UINT16(trnmod, S5pc210SDHCState),
+ VMSTATE_UINT16(cmdreg, S5pc210SDHCState),
+ VMSTATE_UINT32_ARRAY(rspreg, S5pc210SDHCState, 4),
+ VMSTATE_UINT32(prnsts, S5pc210SDHCState),
+ VMSTATE_UINT8(hostctl, S5pc210SDHCState),
+ VMSTATE_UINT8(pwrcon, S5pc210SDHCState),
+ VMSTATE_UINT8(blkgap, S5pc210SDHCState),
+ VMSTATE_UINT8(wakcon, S5pc210SDHCState),
+ VMSTATE_UINT16(clkcon, S5pc210SDHCState),
+ VMSTATE_UINT8(timeoutcon, S5pc210SDHCState),
+ VMSTATE_UINT16(norintsts, S5pc210SDHCState),
+ VMSTATE_UINT16(errintsts, S5pc210SDHCState),
+ VMSTATE_UINT16(norintstsen, S5pc210SDHCState),
+ VMSTATE_UINT16(errintstsen, S5pc210SDHCState),
+ VMSTATE_UINT16(norintsigen, S5pc210SDHCState),
+ VMSTATE_UINT16(errintsigen, S5pc210SDHCState),
+ VMSTATE_UINT16(acmd12errsts, S5pc210SDHCState),
+ VMSTATE_UINT32(capareg, S5pc210SDHCState),
+ VMSTATE_UINT32(maxcurr, S5pc210SDHCState),
+ VMSTATE_UINT32(admaerr, S5pc210SDHCState),
+ VMSTATE_UINT32(admasysaddr, S5pc210SDHCState),
+ VMSTATE_UINT32(control2, S5pc210SDHCState),
+ VMSTATE_UINT32(control3, S5pc210SDHCState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+/****************************************************
+ * Initialization and registration
+ * functions
+ ***************************************************/
+
+static int s5pc210_sdhc_init(SysBusDevice *dev)
+{
+ S5pc210SDHCState *s = FROM_SYSBUS(S5pc210SDHCState, dev);
+ DriveInfo *bd;
+
+ sysbus_init_irq(dev, &s->irq);
+ memory_region_init_io(&s->iomem, &s5pc210_sdhc_mmio_ops, s, "s5pc210.sdhc",
+ SDHC_REG_SIZE);
+ sysbus_init_mmio_region(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 s5pc210_sdhc_info = {
+ .init = s5pc210_sdhc_init,
+ .qdev.name = "s5pc210.sdhc",
+ .qdev.size = sizeof(S5pc210SDHCState),
+ .qdev.vmsd = &s5pc210_sdhc_vmstate,
+ .qdev.reset = s5pc210_sdhc_reset,
+};
+
+static void sdhc_register_devices(void)
+{
+ sysbus_register_withprop(&s5pc210_sdhc_info);
+}
+
+device_init(sdhc_register_devices)
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (12 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 13/14] ARM: s5pc210: added SD/MMC host controller (ver. 2.0 compliant) implementation Evgeny Voevodin
@ 2011-12-07 9:47 ` Evgeny Voevodin
2011-12-07 10:41 ` Peter Maydell
2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
14 siblings, 1 reply; 25+ messages in thread
From: Evgeny Voevodin @ 2011-12-07 9:47 UTC (permalink / raw)
To: qemu-devel; +Cc: d.solodkiy, Evgeny Voevodin
Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
---
hw/s5pc210_cmu.c | 2 +-
hw/s5pc210_combiner.c | 2 +-
hw/s5pc210_fimd.c | 2 +-
hw/s5pc210_gic.c | 4 ++--
hw/s5pc210_mct.c | 2 +-
hw/s5pc210_pwm.c | 2 +-
hw/s5pc210_sdhc.c | 2 +-
hw/s5pc210_uart.c | 2 +-
8 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
index 12fba47..b8f0e5d 100644
--- a/hw/s5pc210_cmu.c
+++ b/hw/s5pc210_cmu.c
@@ -1121,7 +1121,7 @@ static int s5pc210_cmu_init(SysBusDevice *dev)
/* memory mapping */
memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
S5PC210_CMU_REGS_MEM_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
qemu_register_reset(s5pc210_cmu_reset, s);
diff --git a/hw/s5pc210_combiner.c b/hw/s5pc210_combiner.c
index 04ae024..680e761 100644
--- a/hw/s5pc210_combiner.c
+++ b/hw/s5pc210_combiner.c
@@ -352,7 +352,7 @@ static int s5pc210_combiner_init(SysBusDevice *dev)
memory_region_init_io(&s->iomem, &s5pc210_combiner_ops, s,
"s5pc210-combiner", IIC_REGION_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
s5pc210_combiner_reset(s);
vmstate_register(NULL, -1, &VMState_S5pc210Combiner, s);
diff --git a/hw/s5pc210_fimd.c b/hw/s5pc210_fimd.c
index b3b01f2..77174ee 100644
--- a/hw/s5pc210_fimd.c
+++ b/hw/s5pc210_fimd.c
@@ -1675,7 +1675,7 @@ static int s5pc210_fimd_init(SysBusDevice *dev)
memory_region_init_io(&s->iomem, &s5pc210_fimd_mmio_ops, s, "s5pc210.fimd",
FIMD_REGS_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
s->console = graphic_console_init(s5pc210_fimd_update,
s5pc210_fimd_invalidate, NULL, NULL, s);
diff --git a/hw/s5pc210_gic.c b/hw/s5pc210_gic.c
index c6f18d4..4eeeae1 100644
--- a/hw/s5pc210_gic.c
+++ b/hw/s5pc210_gic.c
@@ -386,8 +386,8 @@ static int s5pc210_gic_init(SysBusDevice *dev)
memory_region_init_io(&s->dist_container, &s5pc210_gic_dist_ops, &s->gic,
"s5pc210-gic-dist", S5PC210_GIC_DIST_REGION_SIZE);
- sysbus_init_mmio_region(dev, &s->cpu_container);
- sysbus_init_mmio_region(dev, &s->dist_container);
+ sysbus_init_mmio(dev, &s->cpu_container);
+ sysbus_init_mmio(dev, &s->dist_container);
gic_cpu_write(&s->gic, 1, 0, 1);
return 0;
diff --git a/hw/s5pc210_mct.c b/hw/s5pc210_mct.c
index 7a7dd84..adb0a2c 100644
--- a/hw/s5pc210_mct.c
+++ b/hw/s5pc210_mct.c
@@ -1465,7 +1465,7 @@ static int s5pc210_mct_init(SysBusDevice *dev)
memory_region_init_io(&s->iomem, &s5pc210_mct_ops, s, "s5pc210-mct",
MCT_SFR_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
s5pc210_mct_reset(s);
diff --git a/hw/s5pc210_pwm.c b/hw/s5pc210_pwm.c
index 5dfaa41..8b280d1 100644
--- a/hw/s5pc210_pwm.c
+++ b/hw/s5pc210_pwm.c
@@ -415,7 +415,7 @@ static int s5pc210_pwm_init(SysBusDevice *dev)
memory_region_init_io(&s->iomem, &s5pc210_pwm_ops, s, "s5pc210-pwm",
S5PC210_PWM_REG_MEM_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
s5pc210_pwm_reset(s);
diff --git a/hw/s5pc210_sdhc.c b/hw/s5pc210_sdhc.c
index dbbb885..8aecf51 100644
--- a/hw/s5pc210_sdhc.c
+++ b/hw/s5pc210_sdhc.c
@@ -1648,7 +1648,7 @@ static int s5pc210_sdhc_init(SysBusDevice *dev)
sysbus_init_irq(dev, &s->irq);
memory_region_init_io(&s->iomem, &s5pc210_sdhc_mmio_ops, s, "s5pc210.sdhc",
SDHC_REG_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
bd = drive_get_next(IF_SD);
if ((bd == NULL)) {
diff --git a/hw/s5pc210_uart.c b/hw/s5pc210_uart.c
index d1fbddc..3870740 100644
--- a/hw/s5pc210_uart.c
+++ b/hw/s5pc210_uart.c
@@ -644,7 +644,7 @@ static int s5pc210_uart_init(SysBusDevice *dev)
/* memory mapping */
memory_region_init_io(&s->iomem, &s5pc210_uart_ops, s, "s5pc210.uart",
S5PC210_UART_REGS_MEM_SIZE);
- sysbus_init_mmio_region(dev, &s->iomem);
+ sysbus_init_mmio(dev, &s->iomem);
sysbus_init_irq(dev, &s->irq);
--
1.7.4.1
^ permalink raw reply related [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio.
2011-12-07 9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
@ 2011-12-07 10:41 ` Peter Maydell
0 siblings, 0 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:41 UTC (permalink / raw)
To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy
On 7 December 2011 09:47, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
>
> Signed-off-by: Evgeny Voevodin <e.voevodin@samsung.com>
> ---
> hw/s5pc210_cmu.c | 2 +-
> hw/s5pc210_combiner.c | 2 +-
> hw/s5pc210_fimd.c | 2 +-
> hw/s5pc210_gic.c | 4 ++--
> hw/s5pc210_mct.c | 2 +-
> hw/s5pc210_pwm.c | 2 +-
> hw/s5pc210_sdhc.c | 2 +-
> hw/s5pc210_uart.c | 2 +-
> 8 files changed, 9 insertions(+), 9 deletions(-)
>
> diff --git a/hw/s5pc210_cmu.c b/hw/s5pc210_cmu.c
> index 12fba47..b8f0e5d 100644
> --- a/hw/s5pc210_cmu.c
> +++ b/hw/s5pc210_cmu.c
> @@ -1121,7 +1121,7 @@ static int s5pc210_cmu_init(SysBusDevice *dev)
> /* memory mapping */
> memory_region_init_io(&s->iomem, &s5pc210_cmu_ops, s, "s5pc210.cmu",
> S5PC210_CMU_REGS_MEM_SIZE);
> - sysbus_init_mmio_region(dev, &s->iomem);
> + sysbus_init_mmio(dev, &s->iomem);
>
> qemu_register_reset(s5pc210_cmu_reset, s);
These changes need to be folded in with the earlier patches, otherwise
they will not compile. Each patch in the series should be correct as it
stands, not broken and fixed up by a later patch.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
2011-12-07 9:46 [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Evgeny Voevodin
` (13 preceding siblings ...)
2011-12-07 9:47 ` [Qemu-devel] [PATCH 14/14] s5pc210: Switch to sysbus_init_mmio Evgeny Voevodin
@ 2011-12-07 10:33 ` Peter Maydell
2011-12-07 10:45 ` Dmitry Solodkiy
14 siblings, 1 reply; 25+ messages in thread
From: Peter Maydell @ 2011-12-07 10:33 UTC (permalink / raw)
To: Evgeny Voevodin; +Cc: qemu-devel, d.solodkiy
On 7 December 2011 09:46, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
> Tested on Linux kernel v3.x series.
I'm a bit confused by the naming here -- which Linux kernel mach-*
directory does this correspond to? I can see a mach-s5pc100 and
a mach-s5pv210 but no mach-s5pc210. The NURE and SMDKC210 board
source files seem to be in the mach-exynos4 directory.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
2011-12-07 10:33 ` [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support Peter Maydell
@ 2011-12-07 10:45 ` Dmitry Solodkiy
2011-12-08 10:58 ` Peter Maydell
0 siblings, 1 reply; 25+ messages in thread
From: Dmitry Solodkiy @ 2011-12-07 10:45 UTC (permalink / raw)
To: 'Peter Maydell'; +Cc: qemu-devel, 'Evgeny Voevodin'
Dear Peter,
Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
We decided that s5pc210 is more informative than exynos4 (there are many hardware models started with Exynos4...) or Orion(obsolete one).
Thanks,
Dmitry Solodkiy,
Mobile SW PL, Advanced Software Group,
Moscow R&D center, Samsung Electronics
-----Original Message-----
From: Peter Maydell [mailto:peter.maydell@linaro.org]
Sent: Wednesday, December 07, 2011 2:34 PM
To: Evgeny Voevodin
Cc: qemu-devel@nongnu.org; d.solodkiy@samsung.com
Subject: Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
On 7 December 2011 09:46, Evgeny Voevodin <e.voevodin@samsung.com> wrote:
> This set of patches adds support for Samsung S5PC210-based boards NURI and SMDKC210.
> Tested on Linux kernel v3.x series.
I'm a bit confused by the naming here -- which Linux kernel mach-* directory does this correspond to? I can see a mach-s5pc100 and a mach-s5pv210 but no mach-s5pc210. The NURE and SMDKC210 board source files seem to be in the mach-exynos4 directory.
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
2011-12-07 10:45 ` Dmitry Solodkiy
@ 2011-12-08 10:58 ` Peter Maydell
2011-12-09 1:54 ` Chih-Min Chao
2011-12-09 9:21 ` Dmitry Solodkiy
0 siblings, 2 replies; 25+ messages in thread
From: Peter Maydell @ 2011-12-08 10:58 UTC (permalink / raw)
To: Dmitry Solodkiy; +Cc: qemu-devel, Evgeny Voevodin
On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
> Dear Peter,
>
> Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
> We decided that s5pc210 is more informative than exynos4 (there are
> many hardware models started with Exynos4...) or Orion(obsolete one).
Hmm. You know the SoC better than I do, but I'm a bit dubious
about using a different naming scheme than the kernel does.
u-boot is currently working on a renaming too (eg patches being
submitted: http://patchwork.ozlabs.org/patch/129922/)
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
2011-12-08 10:58 ` Peter Maydell
@ 2011-12-09 1:54 ` Chih-Min Chao
2011-12-09 9:21 ` Dmitry Solodkiy
1 sibling, 0 replies; 25+ messages in thread
From: Chih-Min Chao @ 2011-12-09 1:54 UTC (permalink / raw)
To: Peter Maydell; +Cc: Dmitry Solodkiy, Evgeny Voevodin, qemu-devel
On Thu, Dec 8, 2011 at 6:58 PM, Peter Maydell <peter.maydell@linaro.org> wrote:
> On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
>> Dear Peter,
>>
>> Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
>> We decided that s5pc210 is more informative than exynos4 (there are
>> many hardware models started with Exynos4...) or Orion(obsolete one).
>
> Hmm. You know the SoC better than I do, but I'm a bit dubious
> about using a different naming scheme than the kernel does.
> u-boot is currently working on a renaming too (eg patches being
> submitted: http://patchwork.ozlabs.org/patch/129922/)
>
> -- PMM
>
>
In u-boot
To replace arch-s5pc2xx by arch-exynos is also proposed
http://www.mail-archive.com/u-boot@lists.denx.de/msg72104.html
^ permalink raw reply [flat|nested] 25+ messages in thread
* Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
2011-12-08 10:58 ` Peter Maydell
2011-12-09 1:54 ` Chih-Min Chao
@ 2011-12-09 9:21 ` Dmitry Solodkiy
1 sibling, 0 replies; 25+ messages in thread
From: Dmitry Solodkiy @ 2011-12-09 9:21 UTC (permalink / raw)
To: 'Peter Maydell'; +Cc: qemu-devel, 'Evgeny Voevodin'
Dear Peter,
Well, for a long-term Exynos keyword would be better so we're going to use it in the next patch proposal (estimated today (09.12) or next Monday).
Thanks,
Dmitry Solodkiy,
Mobile SW PL, Advanced Software Group,
Moscow R&D center, Samsung Electronics
-----Original Message-----
From: Peter Maydell [mailto:peter.maydell@linaro.org]
Sent: Thursday, December 08, 2011 2:58 PM
To: Dmitry Solodkiy
Cc: qemu-devel@nongnu.org; Evgeny Voevodin
Subject: Re: [Qemu-devel] [PATCH 00/14] ARM: Samsung S5PC210-based boards support.
On 7 December 2011 10:45, Dmitry Solodkiy <d.solodkiy@samsung.com> wrote:
> Dear Peter,
>
> Orion, s5pc210 and Exynos4210 are aliases for the same hardware chip.
> We decided that s5pc210 is more informative than exynos4 (there are
> many hardware models started with Exynos4...) or Orion(obsolete one).
Hmm. You know the SoC better than I do, but I'm a bit dubious about using a different naming scheme than the kernel does.
u-boot is currently working on a renaming too (eg patches being
submitted: http://patchwork.ozlabs.org/patch/129922/)
-- PMM
^ permalink raw reply [flat|nested] 25+ messages in thread