From patchwork Fri Dec 9 13:34:29 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Evgeny Voevodin X-Patchwork-Id: 130391 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 0F653B6F67 for ; Sat, 10 Dec 2011 01:28:00 +1100 (EST) Received: from localhost ([::1]:41370 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZ0cd-0006jR-TZ for incoming@patchwork.ozlabs.org; Fri, 09 Dec 2011 08:36:19 -0500 Received: from eggs.gnu.org ([140.186.70.92]:34502) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZ0bk-0004T5-6q for qemu-devel@nongnu.org; Fri, 09 Dec 2011 08:35:33 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1RZ0ba-0000WX-2y for qemu-devel@nongnu.org; Fri, 09 Dec 2011 08:35:24 -0500 Received: from mailout3.w1.samsung.com ([210.118.77.13]:8496) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1RZ0bZ-0000Vz-JT for qemu-devel@nongnu.org; Fri, 09 Dec 2011 08:35:14 -0500 MIME-version: 1.0 Content-transfer-encoding: 7BIT Content-type: TEXT/PLAIN Received: from euspt2 ([210.118.77.13]) by mailout3.w1.samsung.com (Sun Java(tm) System Messaging Server 6.3-8.04 (built Jul 29 2009; 32bit)) with ESMTP id <0LVX005UGV2N6N50@mailout3.w1.samsung.com> for qemu-devel@nongnu.org; Fri, 09 Dec 2011 13:35:11 +0000 (GMT) Received: from evvoevodinPC.rnd.samsung.ru ([106.109.8.48]) by spt2.w1.samsung.com (iPlanet Messaging Server 5.2 Patch 2 (built Jul 14 2004)) with ESMTPA id <0LVX00MANV28B5@spt2.w1.samsung.com> for qemu-devel@nongnu.org; Fri, 09 Dec 2011 13:35:11 +0000 (GMT) Date: Fri, 09 Dec 2011 17:34:29 +0400 From: Evgeny Voevodin In-reply-to: <1323437682-28792-1-git-send-email-e.voevodin@samsung.com> To: qemu-devel@nongnu.org Message-id: <1323437682-28792-3-git-send-email-e.voevodin@samsung.com> X-Mailer: git-send-email 1.7.4.1 References: <1323437682-28792-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.13 Cc: m.kozlov@samsung.com, d.solodkiy@samsung.com, Evgeny Voevodin Subject: [Qemu-devel] [PATCH 02/15] ARM: exynos4210: CMU support 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 Add exynos4210 Clock Management Units emulation Signed-off-by: Evgeny Voevodin --- Makefile.target | 2 +- hw/exynos4210.c | 7 + hw/exynos4210.h | 22 + hw/exynos4210_cmu.c | 1146 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 1176 insertions(+), 1 deletions(-) create mode 100644 hw/exynos4210_cmu.c diff --git a/Makefile.target b/Makefile.target index 624a142..ce4f1f8 100644 --- a/Makefile.target +++ b/Makefile.target @@ -344,7 +344,7 @@ obj-arm-y = integratorcp.o versatilepb.o arm_pic.o arm_timer.o obj-arm-y += arm_boot.o pl011.o pl031.o pl050.o pl080.o pl110.o pl181.o pl190.o obj-arm-y += versatile_pci.o obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o -obj-arm-y += exynos4210.o +obj-arm-y += exynos4210.o exynos4210_cmu.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o obj-arm-y += pl061.o obj-arm-y += arm-semi.o diff --git a/hw/exynos4210.c b/hw/exynos4210.c index 1550016..1a6e353 100644 --- a/hw/exynos4210.c +++ b/hw/exynos4210.c @@ -60,6 +60,10 @@ #define EXYNOS4210_BASE_BOOT_ADDR EXYNOS4210_DRAM0_BASE_ADDR +/* SFR Base Address for CMUs */ +#define EXYNOS4210_CMU_BASE_ADDR 0x10030000 + + static struct arm_boot_info exynos4210_binfo = { .loader_start = EXYNOS4210_BASE_BOOT_ADDR, }; @@ -172,6 +176,9 @@ static void exynos4210_init(ram_addr_t ram_size, memory_region_add_subregion(system_mem, EXYNOS4210_DRAM0_BASE_ADDR, dram0_mem); + /* CMU */ + sysbus_create_simple("exynos4210.cmu", EXYNOS4210_CMU_BASE_ADDR, NULL); + /*** Load kernel ***/ exynos4210_binfo.ram_size = ram_size; diff --git a/hw/exynos4210.h b/hw/exynos4210.h index 7137630..683a4a6 100644 --- a/hw/exynos4210.h +++ b/hw/exynos4210.h @@ -31,4 +31,26 @@ #define EXYNOS4210_MAX_CPUS 2 +/* + * Interface for exynos4210 Clock Management Units (CMUs) + */ + +typedef enum { + XXTI, + XUSBXTI, + APLL, + MPLL, + SCLK_APLL, + SCLK_MPLL, + ACLK_100, + SCLK_UART0, + SCLK_UART1, + SCLK_UART2, + SCLK_UART3, + SCLK_UART4, + CLOCKS_NUMBER +} Exynos4210CmuClock; + +uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock); + #endif /* EXYNOS4210_H_ */ diff --git a/hw/exynos4210_cmu.c b/hw/exynos4210_cmu.c new file mode 100644 index 0000000..fe4100c --- /dev/null +++ b/hw/exynos4210_cmu.c @@ -0,0 +1,1146 @@ +/* + * exynos4210 Clock Management Units (CMUs) Emulation + * + * Copyright (C) 2011 Samsung Electronics Co Ltd. + * Maksim Kozlov, + * + * Created on: 07.2011 + * + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "sysbus.h" + +#include "exynos4210.h" + + + +#undef DEBUG_CMU + +//#define DEBUG_CMU +//#define DEBUG_CMU_EXTEND + + +#define PRINT_DEBUG(fmt, args...) \ + do {} while (0) +#define PRINT_DEBUG_SIMPLE(fmt, args...) \ + do {} while (0) +#define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#define PRINT_ERROR(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + + +#ifdef DEBUG_CMU + + #undef PRINT_DEBUG + #define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + + #undef PRINT_DEBUG_SIMPLE + #define PRINT_DEBUG_SIMPLE(fmt, args...) \ + do { \ + fprintf(stderr, fmt, ## args); \ + } while (0) + +#ifdef DEBUG_CMU_EXTEND + + #undef PRINT_DEBUG_EXTEND + #define PRINT_DEBUG_EXTEND(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + +#endif /* EXTEND */ +#endif + + +/* + * Offsets for CMUs registers + */ + +/* CMU_LEFTBUS registers */ +#define CLK_SRC_LEFTBUS 0x04200 +#define CLK_MUX_STAT_LEFTBUS 0x04400 +#define CLK_DIV_LEFTBUS 0x04500 +#define CLK_DIV_STAT_LEFTBUS 0x04600 +#define CLK_GATE_IP_LEFTBUS 0x04800 +#define CLKOUT_CMU_LEFTBUS 0x04A00 +#define CLKOUT_CMU_LEFTBUS_DIV_STAT 0x04A04 +/* CMU_RIGHTBUS registers */ +#define CLK_SRC_RIGHTBUS 0x08200 +#define CLK_MUX_STAT_RIGHTBUS 0x08400 +#define CLK_DIV_RIGHTBUS 0x08500 +#define CLK_DIV_STAT_RIGHTBUS 0x08600 +#define CLK_GATE_IP_RIGHTBUS 0x08800 +#define CLKOUT_CMU_RIGHTBUS 0x08A00 +#define CLKOUT_CMU_RIGHTBUS_DIV_STAT 0x08A04 +/* CMU_TOP registers */ +#define EPLL_LOCK 0x0C010 +#define VPLL_LOCK 0x0C020 +#define EPLL_CON0 0x0C110 +#define EPLL_CON1 0x0C114 +#define VPLL_CON0 0x0C120 +#define VPLL_CON1 0x0C124 +#define CLK_SRC_TOP0 0x0C210 +#define CLK_SRC_TOP1 0x0C214 +#define CLK_SRC_CAM 0x0C220 +#define CLK_SRC_TV 0x0C224 +#define CLK_SRC_MFC 0x0C228 +#define CLK_SRC_G3D 0x0C22C +#define CLK_SRC_IMAGE 0x0C230 +#define CLK_SRC_LCD0 0x0C234 +#define CLK_SRC_LCD1 0x0C238 +#define CLK_SRC_MAUDIO 0x0C23C +#define CLK_SRC_FSYS 0x0C240 +#define CLK_SRC_PERIL0 0x0C250 +#define CLK_SRC_PERIL1 0x0C254 +#define CLK_SRC_MASK_TOP 0x0C310 +#define CLK_SRC_MASK_CAM 0x0C320 +#define CLK_SRC_MASK_TV 0x0C324 +#define CLK_SRC_MASK_LCD0 0x0C334 +#define CLK_SRC_MASK_LCD1 0x0C338 +#define CLK_SRC_MASK_MAUDIO 0x0C33C +#define CLK_SRC_MASK_FSYS 0x0C340 +#define CLK_SRC_MASK_PERIL0 0x0C350 +#define CLK_SRC_MASK_PERIL1 0x0C354 +#define CLK_MUX_STAT_TOP 0x0C410 +#define CLK_MUX_STAT_MFC 0x0C428 +#define CLK_MUX_STAT_G3D 0x0C42C +#define CLK_MUX_STAT_IMAGE 0x0C430 +#define CLK_DIV_TOP 0x0C510 +#define CLK_DIV_CAM 0x0C520 +#define CLK_DIV_TV 0x0C524 +#define CLK_DIV_MFC 0x0C528 +#define CLK_DIV_G3D 0x0C52C +#define CLK_DIV_IMAGE 0x0C530 +#define CLK_DIV_LCD0 0x0C534 +#define CLK_DIV_LCD1 0x0C538 +#define CLK_DIV_MAUDIO 0x0C53C +#define CLK_DIV_FSYS0 0x0C540 +#define CLK_DIV_FSYS1 0x0C544 +#define CLK_DIV_FSYS2 0x0C548 +#define CLK_DIV_FSYS3 0x0C54C +#define CLK_DIV_PERIL0 0x0C550 +#define CLK_DIV_PERIL1 0x0C554 +#define CLK_DIV_PERIL2 0x0C558 +#define CLK_DIV_PERIL3 0x0C55C +#define CLK_DIV_PERIL4 0x0C560 +#define CLK_DIV_PERIL5 0x0C564 +#define CLKDIV2_RATIO 0x0C580 +#define CLK_DIV_STAT_TOP 0x0C610 +#define CLK_DIV_STAT_CAM 0x0C620 +#define CLK_DIV_STAT_TV 0x0C624 +#define CLK_DIV_STAT_MFC 0x0C628 +#define CLK_DIV_STAT_G3D 0x0C62C +#define CLK_DIV_STAT_IMAGE 0x0C630 +#define CLK_DIV_STAT_LCD0 0x0C634 +#define CLK_DIV_STAT_LCD1 0x0C638 +#define CLK_DIV_STAT_MAUDIO 0x0C63C +#define CLK_DIV_STAT_FSYS0 0x0C640 +#define CLK_DIV_STAT_FSYS1 0x0C644 +#define CLK_DIV_STAT_FSYS2 0x0C648 +#define CLK_DIV_STAT_FSYS3 0x0C64C +#define CLK_DIV_STAT_PERIL0 0x0C650 +#define CLK_DIV_STAT_PERIL1 0x0C654 +#define CLK_DIV_STAT_PERIL2 0x0C658 +#define CLK_DIV_STAT_PERIL3 0x0C65C +#define CLK_DIV_STAT_PERIL4 0x0C660 +#define CLK_DIV_STAT_PERIL5 0x0C664 +#define CLKDIV2_STAT 0x0C680 +#define CLK_GATE_SCLK_CAM 0x0C820 +#define CLK_GATE_IP_CAM 0x0C920 +#define CLK_GATE_IP_TV 0x0C924 +#define CLK_GATE_IP_MFC 0x0C928 +#define CLK_GATE_IP_G3D 0x0C92C +#define CLK_GATE_IP_IMAGE 0x0C930 +#define CLK_GATE_IP_LCD0 0x0C934 +#define CLK_GATE_IP_LCD1 0x0C938 +#define CLK_GATE_IP_FSYS 0x0C940 +#define CLK_GATE_IP_GPS 0x0C94C +#define CLK_GATE_IP_PERIL 0x0C950 +#define CLK_GATE_IP_PERIR 0x0C960 +#define CLK_GATE_BLOCK 0x0C970 +#define CLKOUT_CMU_TOP 0x0CA00 +#define CLKOUT_CMU_TOP_DIV_STAT 0x0CA04 +/* CMU_DMC registers */ +#define CLK_SRC_DMC 0x10200 +#define CLK_SRC_MASK_DMC 0x10300 +#define CLK_MUX_STAT_DMC 0x10400 +#define CLK_DIV_DMC0 0x10500 +#define CLK_DIV_DMC1 0x10504 +#define CLK_DIV_STAT_DMC0 0x10600 +#define CLK_DIV_STAT_DMC1 0x10604 +#define CLK_GATE_IP_DMC 0x10900 +#define CLKOUT_CMU_DMC 0x10A00 +#define CLKOUT_CMU_DMC_DIV_STAT 0x10A04 +#define DCGIDX_MAP0 0x11000 +#define DCGIDX_MAP1 0x11004 +#define DCGIDX_MAP2 0x11008 +#define DCGPERF_MAP0 0x11020 +#define DCGPERF_MAP1 0x11024 +#define DVCIDX_MAP 0x11040 +#define FREQ_CPU 0x11060 +#define FREQ_DPM 0x11064 +#define DVSEMCLK_EN 0x11080 +#define MAXPERF 0x11084 +#define APLL_LOCK 0x14000 +#define MPLL_LOCK 0x14008 +#define APLL_CON0 0x14100 +#define APLL_CON1 0x14104 +#define MPLL_CON0 0x14108 +#define MPLL_CON1 0x1410C +/* CMU_CPU registers */ +#define CLK_SRC_CPU 0x14200 +#define CLK_MUX_STAT_CPU 0x14400 +#define CLK_DIV_CPU0 0x14500 +#define CLK_DIV_CPU1 0x14504 +#define CLK_DIV_STAT_CPU0 0x14600 +#define CLK_DIV_STAT_CPU1 0x14604 +#define CLK_GATE_SCLK_CPU 0x14800 +#define CLK_GATE_IP_CPU 0x14900 +#define CLKOUT_CMU_CPU 0x14A00 +#define CLKOUT_CMU_CPU_DIV_STAT 0x14A04 +#define ARMCLK_STOPCTRL 0x15000 +#define ATCLK_STOPCTRL 0x15004 +#define PARITYFAIL_STATUS 0x15010 +#define PARITYFAIL_CLEAR 0x15014 +#define PWR_CTRL 0x15020 +#define APLL_CON0_L8 0x15100 +#define APLL_CON0_L7 0x15104 +#define APLL_CON0_L6 0x15108 +#define APLL_CON0_L5 0x1510C +#define APLL_CON0_L4 0x15110 +#define APLL_CON0_L3 0x15114 +#define APLL_CON0_L2 0x15118 +#define APLL_CON0_L1 0x1511C +#define IEM_CONTROL 0x15120 +#define APLL_CON1_L8 0x15200 +#define APLL_CON1_L7 0x15204 +#define APLL_CON1_L6 0x15208 +#define APLL_CON1_L5 0x1520C +#define APLL_CON1_L4 0x15210 +#define APLL_CON1_L3 0x15214 +#define APLL_CON1_L2 0x15218 +#define APLL_CON1_L1 0x1521C +#define CLKDIV_IEM_L8 0x15300 +#define CLKDIV_IEM_L7 0x15304 +#define CLKDIV_IEM_L6 0x15308 +#define CLKDIV_IEM_L5 0x1530C +#define CLKDIV_IEM_L4 0x15310 +#define CLKDIV_IEM_L3 0x15314 +#define CLKDIV_IEM_L2 0x15318 +#define CLKDIV_IEM_L1 0x1531C + + +typedef struct Exynos4210CmuReg { + const char *name; /* for debugging */ + uint32_t offset; + uint32_t reset_value; +} Exynos4210CmuReg; + + +static Exynos4210CmuReg exynos4210_cmu_regs[] = { + /* CMU_LEFTBUS registers */ + {"CLK_SRC_LEFTBUS", CLK_SRC_LEFTBUS, 0x00000000}, + {"CLK_MUX_STAT_LEFTBUS", CLK_MUX_STAT_LEFTBUS, 0x00000001}, + {"CLK_DIV_LEFTBUS", CLK_DIV_LEFTBUS, 0x00000000}, + {"CLK_DIV_STAT_LEFTBUS", CLK_DIV_STAT_LEFTBUS, 0x00000000}, + {"CLK_GATE_IP_LEFTBUS", CLK_GATE_IP_LEFTBUS, 0xFFFFFFFF}, + {"CLKOUT_CMU_LEFTBUS", CLKOUT_CMU_LEFTBUS, 0x00010000}, + {"CLKOUT_CMU_LEFTBUS_DIV_STAT", CLKOUT_CMU_LEFTBUS_DIV_STAT, 0x00000000}, + /* CMU_RIGHTBUS registers */ + {"CLK_SRC_RIGHTBUS", CLK_SRC_RIGHTBUS, 0x00000000}, + {"CLK_MUX_STAT_RIGHTBUS", CLK_MUX_STAT_RIGHTBUS, 0x00000001}, + {"CLK_DIV_RIGHTBUS", CLK_DIV_RIGHTBUS, 0x00000000}, + {"CLK_DIV_STAT_RIGHTBUS", CLK_DIV_STAT_RIGHTBUS, 0x00000000}, + {"CLK_GATE_IP_RIGHTBUS", CLK_GATE_IP_RIGHTBUS, 0xFFFFFFFF}, + {"CLKOUT_CMU_RIGHTBUS", CLKOUT_CMU_RIGHTBUS, 0x00010000}, + {"CLKOUT_CMU_RIGHTBUS_DIV_STAT", CLKOUT_CMU_RIGHTBUS_DIV_STAT, 0x00000000}, + /* CMU_TOP registers */ + {"EPLL_LOCK", EPLL_LOCK, 0x00000FFF}, + {"VPLL_LOCK", VPLL_LOCK, 0x00000FFF}, + {"EPLL_CON0", EPLL_CON0, 0x00300301}, + {"EPLL_CON1", EPLL_CON1, 0x00000000}, + {"VPLL_CON0", VPLL_CON0, 0x00240201}, + {"VPLL_CON1", VPLL_CON1, 0x66010464}, + {"CLK_SRC_TOP0", CLK_SRC_TOP0, 0x00000000}, + {"CLK_SRC_TOP1", CLK_SRC_TOP1, 0x00000000}, + {"CLK_SRC_CAM", CLK_SRC_CAM, 0x11111111}, + {"CLK_SRC_TV", CLK_SRC_TV, 0x00000000}, + {"CLK_SRC_MFC", CLK_SRC_MFC, 0x00000000}, + {"CLK_SRC_G3D", CLK_SRC_G3D, 0x00000000}, + {"CLK_SRC_IMAGE", CLK_SRC_IMAGE, 0x00000000}, + {"CLK_SRC_LCD0", CLK_SRC_LCD0, 0x00001111}, + {"CLK_SRC_LCD1", CLK_SRC_LCD1, 0x00001111}, + {"CLK_SRC_MAUDIO", CLK_SRC_MAUDIO, 0x00000005}, + {"CLK_SRC_FSYS", CLK_SRC_FSYS, 0x00011111}, + {"CLK_SRC_PERIL0", CLK_SRC_PERIL0, 0x00011111}, + {"CLK_SRC_PERIL1", CLK_SRC_PERIL1, 0x01110055}, + {"CLK_SRC_MASK_TOP", CLK_SRC_MASK_TOP, 0x00000001}, + {"CLK_SRC_MASK_CAM", CLK_SRC_MASK_CAM, 0x11111111}, + {"CLK_SRC_MASK_TV", CLK_SRC_MASK_TV, 0x00000111}, + {"CLK_SRC_MASK_LCD0", CLK_SRC_MASK_LCD0, 0x00001111}, + {"CLK_SRC_MASK_LCD1", CLK_SRC_MASK_LCD1, 0x00001111}, + {"CLK_SRC_MASK_MAUDIO", CLK_SRC_MASK_MAUDIO, 0x00000001}, + {"CLK_SRC_MASK_FSYS", CLK_SRC_MASK_FSYS, 0x01011111}, + {"CLK_SRC_MASK_PERIL0", CLK_SRC_MASK_PERIL0, 0x00011111}, + {"CLK_SRC_MASK_PERIL1", CLK_SRC_MASK_PERIL1, 0x01110111}, + {"CLK_MUX_STAT_TOP", CLK_MUX_STAT_TOP, 0x11111111}, + {"CLK_MUX_STAT_MFC", CLK_MUX_STAT_MFC, 0x00000111}, + {"CLK_MUX_STAT_G3D", CLK_MUX_STAT_G3D, 0x00000111}, + {"CLK_MUX_STAT_IMAGE", CLK_MUX_STAT_IMAGE, 0x00000111}, + {"CLK_DIV_TOP", CLK_DIV_TOP, 0x00000000}, + {"CLK_DIV_CAM", CLK_DIV_CAM, 0x00000000}, + {"CLK_DIV_TV", CLK_DIV_TV, 0x00000000}, + {"CLK_DIV_MFC", CLK_DIV_MFC, 0x00000000}, + {"CLK_DIV_G3D", CLK_DIV_G3D, 0x00000000}, + {"CLK_DIV_IMAGE", CLK_DIV_IMAGE, 0x00000000}, + {"CLK_DIV_LCD0", CLK_DIV_LCD0, 0x00700000}, + {"CLK_DIV_LCD1", CLK_DIV_LCD1, 0x00700000}, + {"CLK_DIV_MAUDIO", CLK_DIV_MAUDIO, 0x00000000}, + {"CLK_DIV_FSYS0", CLK_DIV_FSYS0, 0x00B00000}, + {"CLK_DIV_FSYS1", CLK_DIV_FSYS1, 0x00000000}, + {"CLK_DIV_FSYS2", CLK_DIV_FSYS2, 0x00000000}, + {"CLK_DIV_FSYS3", CLK_DIV_FSYS3, 0x00000000}, + {"CLK_DIV_PERIL0", CLK_DIV_PERIL0, 0x00000000}, + {"CLK_DIV_PERIL1", CLK_DIV_PERIL1, 0x00000000}, + {"CLK_DIV_PERIL2", CLK_DIV_PERIL2, 0x00000000}, + {"CLK_DIV_PERIL3", CLK_DIV_PERIL3, 0x00000000}, + {"CLK_DIV_PERIL4", CLK_DIV_PERIL4, 0x00000000}, + {"CLK_DIV_PERIL5", CLK_DIV_PERIL5, 0x00000000}, + {"CLKDIV2_RATIO", CLKDIV2_RATIO, 0x11111111}, + {"CLK_DIV_STAT_TOP", CLK_DIV_STAT_TOP, 0x00000000}, + {"CLK_DIV_STAT_CAM", CLK_DIV_STAT_CAM, 0x00000000}, + {"CLK_DIV_STAT_TV", CLK_DIV_STAT_TV, 0x00000000}, + {"CLK_DIV_STAT_MFC", CLK_DIV_STAT_MFC, 0x00000000}, + {"CLK_DIV_STAT_G3D", CLK_DIV_STAT_G3D, 0x00000000}, + {"CLK_DIV_STAT_IMAGE", CLK_DIV_STAT_IMAGE, 0x00000000}, + {"CLK_DIV_STAT_LCD0", CLK_DIV_STAT_LCD0, 0x00000000}, + {"CLK_DIV_STAT_LCD1", CLK_DIV_STAT_LCD1, 0x00000000}, + {"CLK_DIV_STAT_MAUDIO", CLK_DIV_STAT_MAUDIO, 0x00000000}, + {"CLK_DIV_STAT_FSYS0", CLK_DIV_STAT_FSYS0, 0x00000000}, + {"CLK_DIV_STAT_FSYS1", CLK_DIV_STAT_FSYS1, 0x00000000}, + {"CLK_DIV_STAT_FSYS2", CLK_DIV_STAT_FSYS2, 0x00000000}, + {"CLK_DIV_STAT_FSYS3", CLK_DIV_STAT_FSYS3, 0x00000000}, + {"CLK_DIV_STAT_PERIL0", CLK_DIV_STAT_PERIL0, 0x00000000}, + {"CLK_DIV_STAT_PERIL1", CLK_DIV_STAT_PERIL1, 0x00000000}, + {"CLK_DIV_STAT_PERIL2", CLK_DIV_STAT_PERIL2, 0x00000000}, + {"CLK_DIV_STAT_PERIL3", CLK_DIV_STAT_PERIL3, 0x00000000}, + {"CLK_DIV_STAT_PERIL4", CLK_DIV_STAT_PERIL4, 0x00000000}, + {"CLK_DIV_STAT_PERIL5", CLK_DIV_STAT_PERIL5, 0x00000000}, + {"CLKDIV2_STAT", CLKDIV2_STAT, 0x00000000}, + {"CLK_GATE_SCLK_CAM", CLK_GATE_SCLK_CAM, 0xFFFFFFFF}, + {"CLK_GATE_IP_CAM", CLK_GATE_IP_CAM, 0xFFFFFFFF}, + {"CLK_GATE_IP_TV", CLK_GATE_IP_TV, 0xFFFFFFFF}, + {"CLK_GATE_IP_MFC", CLK_GATE_IP_MFC, 0xFFFFFFFF}, + {"CLK_GATE_IP_G3D", CLK_GATE_IP_G3D, 0xFFFFFFFF}, + {"CLK_GATE_IP_IMAGE", CLK_GATE_IP_IMAGE, 0xFFFFFFFF}, + {"CLK_GATE_IP_LCD0", CLK_GATE_IP_LCD0, 0xFFFFFFFF}, + {"CLK_GATE_IP_LCD1", CLK_GATE_IP_LCD1, 0xFFFFFFFF}, + {"CLK_GATE_IP_FSYS", CLK_GATE_IP_FSYS, 0xFFFFFFFF}, + {"CLK_GATE_IP_GPS", CLK_GATE_IP_GPS, 0xFFFFFFFF}, + {"CLK_GATE_IP_PERIL", CLK_GATE_IP_PERIL, 0xFFFFFFFF}, + {"CLK_GATE_IP_PERIR", CLK_GATE_IP_PERIR, 0xFFFFFFFF}, + {"CLK_GATE_BLOCK", CLK_GATE_BLOCK, 0xFFFFFFFF}, + {"CLKOUT_CMU_TOP", CLKOUT_CMU_TOP, 0x00010000}, + {"CLKOUT_CMU_TOP_DIV_STAT", CLKOUT_CMU_TOP_DIV_STAT, 0x00000000}, + /* CMU_DMC registers */ + {"CLK_SRC_DMC", CLK_SRC_DMC, 0x00010000}, + {"CLK_SRC_MASK_DMC", CLK_SRC_MASK_DMC, 0x00010000}, + {"CLK_MUX_STAT_DMC", CLK_MUX_STAT_DMC, 0x11100110}, + {"CLK_DIV_DMC0", CLK_DIV_DMC0, 0x00000000}, + {"CLK_DIV_DMC1", CLK_DIV_DMC1, 0x00000000}, + {"CLK_DIV_STAT_DMC0", CLK_DIV_STAT_DMC0, 0x00000000}, + {"CLK_DIV_STAT_DMC1", CLK_DIV_STAT_DMC1, 0x00000000}, + {"CLK_GATE_IP_DMC", CLK_GATE_IP_DMC, 0xFFFFFFFF}, + {"CLKOUT_CMU_DMC", CLKOUT_CMU_DMC, 0x00010000}, + {"CLKOUT_CMU_DMC_DIV_STAT", CLKOUT_CMU_DMC_DIV_STAT, 0x00000000}, + {"DCGIDX_MAP0", DCGIDX_MAP0, 0xFFFFFFFF}, + {"DCGIDX_MAP1", DCGIDX_MAP1, 0xFFFFFFFF}, + {"DCGIDX_MAP2", DCGIDX_MAP2, 0xFFFFFFFF}, + {"DCGPERF_MAP0", DCGPERF_MAP0, 0xFFFFFFFF}, + {"DCGPERF_MAP1", DCGPERF_MAP1, 0xFFFFFFFF}, + {"DVCIDX_MAP", DVCIDX_MAP, 0xFFFFFFFF}, + {"FREQ_CPU", FREQ_CPU, 0x00000000}, + {"FREQ_DPM", FREQ_DPM, 0x00000000}, + {"DVSEMCLK_EN", DVSEMCLK_EN, 0x00000000}, + {"MAXPERF", MAXPERF, 0x00000000}, + {"APLL_LOCK", APLL_LOCK, 0x00000FFF}, + {"MPLL_LOCK", MPLL_LOCK, 0x00000FFF}, + {"APLL_CON0", APLL_CON0, 0x00C80601}, + {"APLL_CON1", APLL_CON1, 0x0000001C}, + {"MPLL_CON0", MPLL_CON0, 0x00C80601}, + {"MPLL_CON1", MPLL_CON1, 0x0000001C}, + /* CMU_CPU registers */ + {"CLK_SRC_CPU", CLK_SRC_CPU, 0x00000000}, + {"CLK_MUX_STAT_CPU", CLK_MUX_STAT_CPU, 0x00110101}, + {"CLK_DIV_CPU0", CLK_DIV_CPU0, 0x00000000}, + {"CLK_DIV_CPU1", CLK_DIV_CPU1, 0x00000000}, + {"CLK_DIV_STAT_CPU0", CLK_DIV_STAT_CPU0, 0x00000000}, + {"CLK_DIV_STAT_CPU1", CLK_DIV_STAT_CPU1, 0x00000000}, + {"CLK_GATE_SCLK_CPU", CLK_GATE_SCLK_CPU, 0xFFFFFFFF}, + {"CLK_GATE_IP_CPU", CLK_GATE_IP_CPU, 0xFFFFFFFF}, + {"CLKOUT_CMU_CPU", CLKOUT_CMU_CPU, 0x00010000}, + {"CLKOUT_CMU_CPU_DIV_STAT", CLKOUT_CMU_CPU_DIV_STAT, 0x00000000}, + {"ARMCLK_STOPCTRL", ARMCLK_STOPCTRL, 0x00000044}, + {"ATCLK_STOPCTRL", ATCLK_STOPCTRL, 0x00000044}, + {"PARITYFAIL_STATUS", PARITYFAIL_STATUS, 0x00000000}, + {"PARITYFAIL_CLEAR", PARITYFAIL_CLEAR, 0x00000000}, + {"PWR_CTRL", PWR_CTRL, 0x00000033}, + {"APLL_CON0_L8", APLL_CON0_L8, 0x00C80601}, + {"APLL_CON0_L7", APLL_CON0_L7, 0x00C80601}, + {"APLL_CON0_L6", APLL_CON0_L6, 0x00C80601}, + {"APLL_CON0_L5", APLL_CON0_L5, 0x00C80601}, + {"APLL_CON0_L4", APLL_CON0_L4, 0x00C80601}, + {"APLL_CON0_L3", APLL_CON0_L3, 0x00C80601}, + {"APLL_CON0_L2", APLL_CON0_L2, 0x00C80601}, + {"APLL_CON0_L1", APLL_CON0_L1, 0x00C80601}, + {"IEM_CONTROL", IEM_CONTROL, 0x00000000}, + {"APLL_CON1_L8", APLL_CON1_L8, 0x00000000}, + {"APLL_CON1_L7", APLL_CON1_L7, 0x00000000}, + {"APLL_CON1_L6", APLL_CON1_L6, 0x00000000}, + {"APLL_CON1_L5", APLL_CON1_L5, 0x00000000}, + {"APLL_CON1_L4", APLL_CON1_L4, 0x00000000}, + {"APLL_CON1_L3", APLL_CON1_L3, 0x00000000}, + {"APLL_CON1_L2", APLL_CON1_L2, 0x00000000}, + {"APLL_CON1_L1", APLL_CON1_L1, 0x00000000}, + {"CLKDIV_IEM_L8", CLKDIV_IEM_L8, 0x00000000}, + {"CLKDIV_IEM_L7", CLKDIV_IEM_L7, 0x00000000}, + {"CLKDIV_IEM_L6", CLKDIV_IEM_L6, 0x00000000}, + {"CLKDIV_IEM_L5", CLKDIV_IEM_L5, 0x00000000}, + {"CLKDIV_IEM_L4", CLKDIV_IEM_L4, 0x00000000}, + {"CLKDIV_IEM_L3", CLKDIV_IEM_L3, 0x00000000}, + {"CLKDIV_IEM_L2", CLKDIV_IEM_L2, 0x00000000}, + {"CLKDIV_IEM_L1", CLKDIV_IEM_L1, 0x00000000}, +}; + + +/* + * There are five CMUs: + * + * CMU_LEFTBUS + * CMU_RIGHTBUS + * CMU_TOP + * CMU_DMC + * CMU_CPU + * + * each of them uses 16KB address space for SFRs + * + * + 0x4000 because SFR region for CMUs starts at 0x10030000, + * but the first CMU (CMU_LEFTBUS) starts with this offset + * + */ +#define EXYNOS4210_CMU_REGS_MEM_SIZE (0x4000 * 5 + 0x4000) + +/* + * for indexing register in the uint32_t array + * + * 'reg' - register offset (see offsets definitions above) + * + */ +#define I_(reg) (reg / sizeof(uint32_t)) + +#define XOM_0 1 /* Select XXTI (0) or XUSBXTI (1) base clock source */ + +/* + * Offsets in CLK_SRC_CPU register + * for control MUXMPLL and MUXAPLL + * + * 0 = FINPLL, 1 = MOUTM(A)PLLFOUT + */ +#define MUX_APLL_SEL_SHIFT 0 +#define MUX_MPLL_SEL_SHIFT 8 +#define MUX_CORE_SEL_SHIFT 16 +#define MUX_HPM_SEL_SHIFT 20 + +#define MUX_APLL_SEL (1 << MUX_APLL_SEL_SHIFT) +#define MUX_MPLL_SEL (1 << MUX_MPLL_SEL_SHIFT) +#define MUX_CORE_SEL (1 << MUX_CORE_SEL_SHIFT) +#define MUX_HPM_SEL (1 << MUX_HPM_SEL_SHIFT) + +/* Offsets for fields in CLK_MUX_STAT_CPU register */ +#define APLL_SEL_SHIFT 0 +#define APLL_SEL_MASK 0x00000007 +#define MPLL_SEL_SHIFT 8 +#define MPLL_SEL_MASK 0x00000700 +#define CORE_SEL_SHIFT 16 +#define CORE_SEL_MASK 0x00070000 +#define HPM_SEL_SHIFT 20 +#define HPM_SEL_MASK 0x00700000 + + +/* Offsets for fields in _CON0 register */ +#define PLL_ENABLE_SHIFT 31 +#define PLL_ENABLE_MASK 0x80000000 /* [31] bit */ +#define PLL_LOCKED_MASK 0x20000000 /* [29] bit */ +#define PLL_MDIV_SHIFT 16 +#define PLL_MDIV_MASK 0x03FF0000 /* [25:16] bits */ +#define PLL_PDIV_SHIFT 8 +#define PLL_PDIV_MASK 0x00003F00 /* [13:8] bits */ +#define PLL_SDIV_SHIFT 0 +#define PLL_SDIV_MASK 0x00000007 /* [2:0] bits */ + + + +/* + * Offset in CLK_DIV_CPU0 register + * for DIVAPLL clock divider ratio + */ +#define APLL_RATIO_SHIFT 24 +#define APLL_RATIO_MASK 0x07000000 /* [26:24] bits */ + +/* + * Offset in CLK_DIV_TOP register + * for DIVACLK_100 clock divider ratio + */ +#define ACLK_100_RATIO_SHIFT 4 +#define ACLK_100_RATIO_MASK 0x000000f0 /* [7:4] bits */ + +/* Offset in CLK_SRC_TOP0 register */ +#define MUX_ACLK_100_SEL_SHIFT 16 + +/* + * Offsets in CLK_SRC_PERIL0 register + * for clock sources of UARTs + */ +#define UART0_SEL_SHIFT 0 +#define UART1_SEL_SHIFT 4 +#define UART2_SEL_SHIFT 8 +#define UART3_SEL_SHIFT 12 +#define UART4_SEL_SHIFT 16 +/* + * Offsets in CLK_DIV_PERIL0 register + * for clock divider of UARTs + */ +#define UART0_DIV_SHIFT 0 +#define UART1_DIV_SHIFT 4 +#define UART2_DIV_SHIFT 8 +#define UART3_DIV_SHIFT 12 +#define UART4_DIV_SHIFT 16 + +typedef struct { + SysBusDevice busdev; + MemoryRegion iomem; + + uint32_t reg[EXYNOS4210_CMU_REGS_MEM_SIZE]; + +} Exynos4210CmuState; + +#define SOURCES_NUMBER 9 +#define RECIPIENTS_NUMBER 9 + +typedef struct Exynos4210CmuClockState { + + const char *name; + uint64_t rate; + + /* Current source clock */ + struct Exynos4210CmuClockState *source; + /* + * Available sources. Their order must correspond to CLK_SRC_ register + */ + struct Exynos4210CmuClockState *sources[SOURCES_NUMBER]; + /* Who uses this clock? */ + struct Exynos4210CmuClockState *recipients[RECIPIENTS_NUMBER]; + + uint32_t src_reg; /* Offset of CLK_SRC_<*> register */ + uint32_t div_reg; /* Offset of CLK_DIV_<*> register */ + + /* + * Shift for MUX__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; + +} Exynos4210CmuClockState; + + +/* Clocks from Clock Pads */ + +/* It should be used only for testing purposes. XOM_0 is 0 */ +static Exynos4210CmuClockState xxti = { + .name = "XXTI", + .rate = 24000000, +}; + +/* Main source. XOM_0 is 1 */ +static Exynos4210CmuClockState xusbxti = { + .name = "XUSBXTI", + .rate = 24000000, +}; + +/* PLLs */ + +static Exynos4210CmuClockState mpll = { + .name = "MPLL", + .source = (XOM_0 ? &xusbxti : &xxti), +}; + +static Exynos4210CmuClockState apll = { + .name = "APLL", + .source = (XOM_0 ? &xusbxti : &xxti), +}; + + +/**/ +static Exynos4210CmuClockState sclk_mpll = { + .name = "SCLK_MPLL", + .sources = {XOM_0 ? &xusbxti : &xxti, &mpll}, + .src_reg = CLK_SRC_CPU, + .mux_shift = MUX_MPLL_SEL_SHIFT, +}; + +static Exynos4210CmuClockState sclk_apll = { + .name = "SCLK_APLL", + .sources = {XOM_0 ? &xusbxti : &xxti, &apll}, + .src_reg = CLK_SRC_CPU, + .div_reg = CLK_DIV_CPU0, + .mux_shift = MUX_APLL_SEL_SHIFT, + .div_shift = APLL_RATIO_SHIFT, +}; + +static Exynos4210CmuClockState aclk_100 = { + .name = "ACLK_100", + .sources = {&sclk_mpll, &sclk_apll}, + .src_reg = CLK_SRC_TOP0, + .div_reg = CLK_DIV_TOP, + .mux_shift = MUX_ACLK_100_SEL_SHIFT, + .div_shift = ACLK_100_RATIO_SHIFT, +}; + + +/* TODO: add other needed structures for UARTs sources */ +static Exynos4210CmuClockState sclk_uart0 = { + .name = "SCLK_UART0", + .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll}, + .src_reg = CLK_SRC_PERIL0, + .div_reg = CLK_DIV_PERIL0, + .mux_shift = UART0_SEL_SHIFT, + .div_shift = UART0_DIV_SHIFT, +}; + +static Exynos4210CmuClockState sclk_uart1 = { + .name = "SCLK_UART1", + .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll}, + .src_reg = CLK_SRC_PERIL0, + .div_reg = CLK_DIV_PERIL0, + .mux_shift = UART1_SEL_SHIFT, + .div_shift = UART1_DIV_SHIFT, +}; + +static Exynos4210CmuClockState sclk_uart2 = { + .name = "SCLK_UART2", + .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll}, + .src_reg = CLK_SRC_PERIL0, + .div_reg = CLK_DIV_PERIL0, + .mux_shift = UART2_SEL_SHIFT, + .div_shift = UART2_DIV_SHIFT, +}; + +static Exynos4210CmuClockState sclk_uart3 = { + .name = "SCLK_UART3", + .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll}, + .src_reg = CLK_SRC_PERIL0, + .div_reg = CLK_DIV_PERIL0, + .mux_shift = UART3_SEL_SHIFT, + .div_shift = UART3_DIV_SHIFT, +}; + +static Exynos4210CmuClockState sclk_uart4 = { + .name = "SCLK_UART4", + .sources = {&xxti, &xusbxti, NULL, NULL, NULL, NULL, &sclk_mpll}, + .src_reg = CLK_SRC_PERIL0, + .div_reg = CLK_DIV_PERIL0, + .mux_shift = UART4_SEL_SHIFT, + .div_shift = UART4_DIV_SHIFT, +}; + +/* + * This array must correspond to Exynos4210CmuClock enumerator + * which is defined in exynos4210.h file + * + */ +static Exynos4210CmuClockState *exynos4210_clock[] = { + &xxti, + &xusbxti, + &apll, + &mpll, + &sclk_apll, + &sclk_mpll, + &aclk_100, + &sclk_uart0, + &sclk_uart1, + &sclk_uart2, + &sclk_uart3, + &sclk_uart4, + NULL +}; + + +uint64_t exynos4210_cmu_get_rate(Exynos4210CmuClock clock) +{ + return exynos4210_clock[clock]->rate; +} + +#ifdef DEBUG_CMU +/* The only meaning of life - debugging. This functions should be only used + * inside PRINT_DEBUG_... macroses + */ +static const char *exynos4210_cmu_regname(target_phys_addr_t offset) +{ + + int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg); + int i; + + for (i = 0; i < regs_number; i++) { + if (offset == exynos4210_cmu_regs[i].offset) { + return exynos4210_cmu_regs[i].name; + } + } + + return NULL; +} +#endif + +static void exynos4210_cmu_set_pll(void *opaque, target_phys_addr_t offset) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + uint32_t pdiv, mdiv, sdiv, enable; + + /* + * FOUT = MDIV * FIN / (PDIV * 2^(SDIV-1)) + */ + + enable = (s->reg[I_(offset)] & PLL_ENABLE_MASK) >> PLL_ENABLE_SHIFT; + mdiv = (s->reg[I_(offset)] & PLL_MDIV_MASK) >> PLL_MDIV_SHIFT; + pdiv = (s->reg[I_(offset)] & PLL_PDIV_MASK) >> PLL_PDIV_SHIFT; + sdiv = (s->reg[I_(offset)] & PLL_SDIV_MASK) >> PLL_SDIV_SHIFT; + + switch (offset) { + + case MPLL_CON0: + if (mpll.source) { + if (enable) { + mpll.rate = mdiv * mpll.source->rate / (pdiv * (1 << (sdiv-1))); + } else { + mpll.rate = 0; + } + } else { + hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n", + mpll.name); + } + PRINT_DEBUG("mpll.rate: %llu\n", (unsigned long long int)mpll.rate); + break; + + case APLL_CON0: + if (apll.source) { + if (enable) { + apll.rate = mdiv * apll.source->rate / (pdiv * (1 << (sdiv-1))); + } else { + apll.rate = 0; + } + } else { + hw_error("exynos4210_cmu_set_pll: Source undefined for %s\n", + apll.name); + } + PRINT_DEBUG("apll.rate: %llu\n", (unsigned long long int)apll.rate); + break; + + default: + hw_error("exynos4210_cmu_set_pll: Bad offset 0x%x\n", (int)offset); + } + + s->reg[I_(offset)] |= PLL_LOCKED_MASK; +} + + +static void +exynos4210_cmu_set_rate(void *opaque, Exynos4210CmuClockState *clock) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + int i; + + /* Rates of PLLs are calculated differently than ordinary clocks rates */ + if (clock == &mpll) { + exynos4210_cmu_set_pll(s, MPLL_CON0); + } else if (clock == &apll) { + exynos4210_cmu_set_pll(s, APLL_CON0); + } else if ((clock != &xxti) && (clock != &xusbxti)) { + /* + * Not root clock. We don't need calculating rate + * of root clock because it is hard coded. + */ + uint32_t src_index = I_(clock->src_reg); + uint32_t div_index = I_(clock->div_reg); + clock->source = clock->sources[(s->reg[src_index] >> + clock->mux_shift) & 0xf]; + clock->rate = muldiv64(clock->source->rate, 1, + (((s->reg[div_index] >> clock->div_shift) & 0xf) + + 1)); + + PRINT_DEBUG_EXTEND("SRC: <0x%05x> %s, SHIFT: %d\n", + clock->src_reg, + exynos4210_cmu_regname(clock->src_reg), + clock->mux_shift); + + PRINT_DEBUG("%s [%s:%llu]: %llu\n", + clock->name, + clock->source->name, + (uint64_t)clock->source->rate, + (uint64_t)clock->rate); + } + + /* Visit all recipients for given clock */ + i = 0; + do { + + Exynos4210CmuClockState *recipient_clock = clock->recipients[i]; + + if (recipient_clock == NULL) { + PRINT_DEBUG_EXTEND("%s have %d recipients\n", clock->name, i); + break; + } + + uint32_t src_index = recipient_clock->src_reg / sizeof(uint32_t); + int source_index = s->reg[src_index] >> + recipient_clock->mux_shift & 0xf; + recipient_clock->source = recipient_clock->sources[source_index]; + + if (recipient_clock->source != clock) { + break; + } + + exynos4210_cmu_set_rate(s, recipient_clock); + + i++; + } while (i < RECIPIENTS_NUMBER); +} + + +static uint64_t exynos4210_cmu_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + + if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) { + hw_error("exynos4210_cmu_read: Bad offset 0x%x\n", (int)offset); + } + + PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n", offset, + exynos4210_cmu_regname(offset), s->reg[I_(offset)]); + + return s->reg[I_(offset)]; +} + + +static void exynos4210_cmu_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + uint32_t pre_val; + + if (offset > (EXYNOS4210_CMU_REGS_MEM_SIZE - sizeof(uint32_t))) { + hw_error("exynos4210_cmu_write: Bad offset 0x%x\n", (int)offset); + } + + pre_val = s->reg[I_(offset)]; + s->reg[I_(offset)] = val; + + PRINT_DEBUG_EXTEND("<0x%05x> %s <- %08x\n", offset, + exynos4210_cmu_regname(offset), s->reg[I_(offset)]); + + switch (offset) { + + case APLL_CON0: + val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK); + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, &apll); + break; + case MPLL_CON0: + val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK); + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, &mpll); + break; + case CLK_SRC_CPU: + { + if (val & MUX_APLL_SEL) { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) | + (2 << APLL_SEL_SHIFT); + + if ((pre_val & MUX_APLL_SEL) != + (s->reg[I_(offset)] & MUX_APLL_SEL)) { + exynos4210_cmu_set_rate(s, &apll); + } + + } else { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(APLL_SEL_MASK)) | + (1 << APLL_SEL_SHIFT); + + if ((pre_val & MUX_APLL_SEL) != + (s->reg[I_(offset)] & MUX_APLL_SEL)) { + exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti); + } + } + + + if (val & MUX_MPLL_SEL) { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) | + (2 << MPLL_SEL_SHIFT); + + if ((pre_val & MUX_MPLL_SEL) != + (s->reg[I_(offset)] & MUX_MPLL_SEL)) { + exynos4210_cmu_set_rate(s, &mpll); + } + + } else { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(MPLL_SEL_MASK)) | + (1 << MPLL_SEL_SHIFT); + + if ((pre_val & MUX_MPLL_SEL) != + (s->reg[I_(offset)] & MUX_MPLL_SEL)) { + exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti); + } + } + + if (val & MUX_CORE_SEL) { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) | + (2 << CORE_SEL_SHIFT); + + if ((pre_val & MUX_CORE_SEL) != + (s->reg[I_(offset)] & MUX_CORE_SEL)) { + exynos4210_cmu_set_rate(s, &sclk_mpll); + } + + } else { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(CORE_SEL_MASK)) | + (1 << CORE_SEL_SHIFT); + + if ((pre_val & MUX_CORE_SEL) != + (s->reg[I_(offset)] & MUX_CORE_SEL)) { + exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti); + } + } + + if (val & MUX_HPM_SEL) { + exynos4210_cmu_set_rate(s, &sclk_mpll); + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) | + (2 << HPM_SEL_SHIFT); + + if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) { + exynos4210_cmu_set_rate(s, &sclk_mpll); + } + + } else { + s->reg[I_(CLK_MUX_STAT_CPU)] = + (s->reg[I_(CLK_MUX_STAT_CPU)] & ~(HPM_SEL_MASK)) | + (1 << HPM_SEL_SHIFT); + + if ((pre_val & MUX_HPM_SEL) != (s->reg[I_(offset)] & MUX_HPM_SEL)) { + exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti); + } + } + } + break; + case CLK_DIV_CPU0: + exynos4210_cmu_set_rate(s, &sclk_apll); + exynos4210_cmu_set_rate(s, &sclk_mpll); + break; + case CLK_SRC_TOP0: + case CLK_DIV_TOP: + exynos4210_cmu_set_rate(s, &aclk_100); + break; + default: + break; + } +} + + +static const MemoryRegionOps exynos4210_cmu_ops = { + .read = exynos4210_cmu_read, + .write = exynos4210_cmu_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + + +static void exynos4210_cmu_reset(void *opaque) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + int i = 0, j = 0, n = 0; + int regs_number = sizeof(exynos4210_cmu_regs)/sizeof(Exynos4210CmuReg); + uint32_t index = 0; + + /* Set default values for registers */ + for (i = 0; i < regs_number; i++) { + index = (exynos4210_cmu_regs[i].offset) / sizeof(uint32_t); + s->reg[index] = exynos4210_cmu_regs[i].reset_value; + } + + /* clear recipients array from previous reset */ + for (i = 0; i < CLOCKS_NUMBER; i++) { + bzero(exynos4210_clock[i]->recipients, + RECIPIENTS_NUMBER * sizeof(Exynos4210CmuClockState *)); + } + + /* + * Here we fill '.recipients' fields in all clocks. Also we fill empty + * 'sources[]' arrays by values of 'source' fields (it is necessary + * for set rate, for example). If 'sources[]' array and 'source' field + * is empty simultaneously we get hw_error. + * + */ + for (i = 0; i < CLOCKS_NUMBER; i++) { + + /* visit all clocks in the exynos4210_clock */ + + PRINT_DEBUG("[SOURCES] %s: ", exynos4210_clock[i]->name); + + j = 0; + do { /* visit all sources for current clock (exynos4210_clock[i]) */ + + if ((exynos4210_clock[i]->sources[j] == NULL)) { + + if (j == 0) { /* check if we have empty '.sources[]' array */ + if (exynos4210_clock[i]->source != NULL) { + exynos4210_clock[i]->sources[j] = + exynos4210_clock[i]->source; + } else { + /* + * We haven't any defined sources for this clock. Error + * during definition of appropriate clock structure + * + */ + if ((exynos4210_clock[i] != &xusbxti) && + (exynos4210_clock[i] != &xxti)) { + + hw_error("exynos4210_cmu_reset:" + "There aren't any sources for %s clock!\n", + exynos4210_clock[i]->name); + } else { + /* + * we don't need any sources for this clock + * because it's a root clock + */ + break; + } + } + } else { + break; /* leave because there are no more sources */ + } + + } + + PRINT_DEBUG_SIMPLE(" %s", exynos4210_clock[i]->sources[j]->name); + + /* + * find first empty place in 'recipients[]' array of + * current 'sources' element and put current clock there + */ + n = 0; + do { + if ((exynos4210_clock[i]->sources[j]->recipients[n]) == NULL) { + exynos4210_clock[i]->sources[j]->recipients[n] = + exynos4210_clock[i]; + break; + } + n++; + } while (n < RECIPIENTS_NUMBER); + + j++; + + } while (j < SOURCES_NUMBER); + + PRINT_DEBUG_SIMPLE("\n"); + + } /* CLOCKS_NUMBER */ + +#ifdef DEBUG_CMU + for (i = 0; i < CLOCKS_NUMBER; i++) { + PRINT_DEBUG("[RECIPIENTS] %s: ", exynos4210_clock[i]->name); + for (j = 0; + (j < RECIPIENTS_NUMBER) && + ((exynos4210_clock[i]->recipients[j]) != NULL); + j++) { + PRINT_DEBUG_SIMPLE("%s ", exynos4210_clock[i]->recipients[j]->name); + } + PRINT_DEBUG_SIMPLE("\n"); + } +#endif + + exynos4210_cmu_set_rate(s, XOM_0 ? &xusbxti : &xxti); +} + +static const VMStateDescription vmstate_exynos4210_cmu = { + .name = "exynos4210.cmu", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + /* + * TODO: Maybe we should save Exynos4210CmuClockState structs as well + */ + VMSTATE_UINT32_ARRAY(reg, Exynos4210CmuState, + EXYNOS4210_CMU_REGS_MEM_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +static int exynos4210_cmu_init(SysBusDevice *dev) +{ + Exynos4210CmuState *s = FROM_SYSBUS(Exynos4210CmuState, dev); + + /* memory mapping */ + memory_region_init_io(&s->iomem, &exynos4210_cmu_ops, s, "exynos4210.cmu", + EXYNOS4210_CMU_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + qemu_register_reset(exynos4210_cmu_reset, s); + + vmstate_register(&dev->qdev, -1, &vmstate_exynos4210_cmu, s); + + exynos4210_cmu_reset(s); + + return 0; +} + + +static void exynos4210_cmu_register(void) +{ + sysbus_register_dev("exynos4210.cmu", + sizeof(Exynos4210CmuState), + exynos4210_cmu_init); +} + + +device_init(exynos4210_cmu_register)