From patchwork Wed Dec 7 09:46:52 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Evgeny Voevodin X-Patchwork-Id: 129937 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [140.186.70.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 7741E1007D2 for ; Wed, 7 Dec 2011 21:54:16 +1100 (EST) Received: from localhost ([::1]:50740 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RYE7D-0001Xz-9a for incoming@patchwork.ozlabs.org; Wed, 07 Dec 2011 04:48:39 -0500 Received: from eggs.gnu.org ([140.186.70.92]:55936) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RYE6V-0000Pu-QX for qemu-devel@nongnu.org; Wed, 07 Dec 2011 04:48:04 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1RYE6P-0001wg-6K for qemu-devel@nongnu.org; Wed, 07 Dec 2011 04:47:55 -0500 Received: from mailout2.w1.samsung.com ([210.118.77.12]:26478) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RYE6O-0001wH-K2 for qemu-devel@nongnu.org; Wed, 07 Dec 2011 04:47:49 -0500 Received: from euspt1 (mailout2.w1.samsung.com [210.118.77.12]) by mailout2.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTP id <0LVT00HQ1V7LVY@mailout2.w1.samsung.com> for qemu-devel@nongnu.org; Wed, 07 Dec 2011 09:47:46 +0000 (GMT) Received: from evvoevodinPC.rnd.samsung.ru ([106.109.8.48]) by spt1.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0LVT00GNZV7ATA@spt1.w1.samsung.com> for qemu-devel@nongnu.org; Wed, 07 Dec 2011 09:47:45 +0000 (GMT) Date: Wed, 07 Dec 2011 13:46:52 +0400 From: Evgeny Voevodin In-reply-to: <1323251225-1072-1-git-send-email-e.voevodin@samsung.com> To: qemu-devel@nongnu.org Message-id: <1323251225-1072-2-git-send-email-e.voevodin@samsung.com> MIME-version: 1.0 X-Mailer: git-send-email 1.7.4.1 Content-type: text/plain; charset=UTF-8 Content-transfer-encoding: QUOTED-PRINTABLE References: <1323251225-1072-1-git-send-email-e.voevodin@samsung.com> X-detected-operating-system: by eggs.gnu.org: Solaris 9.1 X-Received-From: 210.118.77.12 Cc: Maksim Kozlov , d.solodkiy@samsung.com, Evgeny Voevodin Subject: [Qemu-devel] =?utf-8?q?=5BPATCH_01/14=5D_ARM=3A_s5pc210=3A_Basic_?= =?utf-8?q?support_of_s5pc210_boards?= X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org From: Maksim Kozlov Added support of S5PC210 based boards - SMDKC210 and NURI. CMU and UART basic implementation. Signed-off-by: Evgeny Voevodin --- 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 + * Evgeny Voevodin + * Igor Mitsyanko + * + * 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 + * Evgeny Voevodin + * Igor Mitsyanko + * + * + * 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, + * + * 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 _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__SEL value which is stored + * in appropriate CLK_MUX_STAT_ register + */ + uint8_t mux_shift; + + /* + * Shift for _RATIO value which is stored + * in appropriate CLK_DIV_ 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, + * + * 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)