From 86b1aba4333787600b536c40ea615168bd312383 Mon Sep 17 00:00:00 2001 From: Evgeny Voevodin Date: Thu, 14 Jun 2012 10:29:42 +0400 Subject: [PATCH] Tizen ARM: Add and improve ARM SOC specific files. This commit adds and improves files related to Exynos4210 SOC emulation. This SOC will be used in maru ARM board. Signed-off-by: Evgeny Voevodin --- Makefile.objs | 1 + Makefile.target | 2 + default-configs/arm-softmmu.mak | 1 + hw/exynos4210.h | 57 ++ hw/exynos4210_audio.c | 149 ++++ hw/exynos4210_cmu.c | 1482 +++++++++++++++++++++++++++++++++++++++ hw/exynos4210_g3d.c | 833 ++++++++++++++++++++++ hw/exynos4210_gic.c | 79 ++- hw/exynos4210_i2c.c | 334 +++++++++ hw/exynos4210_i2s.c | 577 +++++++++++++++ hw/exynos4210_i2s.h | 57 ++ hw/exynos4210_rtc.c | 612 ++++++++++++++++ hw/exynos4210_uart.c | 51 +- hw/wm8994.c | 644 +++++++++++++++++ hw/wm8994.h | 14 + 15 files changed, 4857 insertions(+), 36 deletions(-) create mode 100644 hw/exynos4210_audio.c create mode 100644 hw/exynos4210_cmu.c create mode 100644 hw/exynos4210_g3d.c create mode 100644 hw/exynos4210_i2c.c create mode 100644 hw/exynos4210_i2s.c create mode 100644 hw/exynos4210_i2s.h create mode 100644 hw/exynos4210_rtc.c create mode 100644 hw/wm8994.c create mode 100644 hw/wm8994.h diff --git a/Makefile.objs b/Makefile.objs index 6c9efd3..b001d21 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -106,6 +106,7 @@ common-obj-y += irq.o input.o common-obj-$(CONFIG_PTIMER) += ptimer.o common-obj-$(CONFIG_MAX7310) += max7310.o common-obj-$(CONFIG_WM8750) += wm8750.o +common-obj-$(CONFIG_WM8994) += wm8994.o common-obj-$(CONFIG_TWL92230) += twl92230.o common-obj-$(CONFIG_TSC2005) += tsc2005.o common-obj-$(CONFIG_LM832X) += lm832x.o diff --git a/Makefile.target b/Makefile.target index 03598f5..547c5ce 100755 --- a/Makefile.target +++ b/Makefile.target @@ -349,6 +349,8 @@ obj-arm-y += realview_gic.o realview.o arm_sysctl.o arm11mpcore.o a9mpcore.o obj-arm-y += exynos4210_gic.o exynos4210_combiner.o exynos4210.o obj-arm-y += exynos4_boards.o exynos4210_uart.o exynos4210_pwm.o obj-arm-y += exynos4210_pmu.o exynos4210_mct.o exynos4210_fimd.o +obj-arm-y += exynos4210_rtc.o exynos4210_cmu.o exynos4210_g3d.o +obj-arm-y += exynos4210_i2c.o exynos4210_i2s.o exynos4210_audio.o obj-arm-y += arm_l2x0.o obj-arm-y += arm_mptimer.o a15mpcore.o obj-arm-y += armv7m.o armv7m_nvic.o stellaris.o pl022.o stellaris_enet.o diff --git a/default-configs/arm-softmmu.mak b/default-configs/arm-softmmu.mak index e542b4f..dd64484 100644 --- a/default-configs/arm-softmmu.mak +++ b/default-configs/arm-softmmu.mak @@ -11,6 +11,7 @@ CONFIG_PTIMER=y CONFIG_SD=y CONFIG_MAX7310=y CONFIG_WM8750=y +CONFIG_WM8994=y CONFIG_TWL92230=y CONFIG_TSC2005=y CONFIG_LM832X=y diff --git a/hw/exynos4210.h b/hw/exynos4210.h index f7c7027..1a37565 100644 --- a/hw/exynos4210.h +++ b/hw/exynos4210.h @@ -43,6 +43,11 @@ #define EXYNOS4210_IRAM_BASE_ADDR 0x02020000 #define EXYNOS4210_IRAM_SIZE 0x00020000 /* 128 KB */ +#define EXYNOS4210_AUDSS_INTMEM_BASE_ADDR 0x03000000 +#define EXYNOS4210_AUDSS_INTMEM_SIZE 0x00039000 /* 228 KB */ + +#define EXYNOS4210_VPCI_CFG_BASE_ADDR 0xC2000000 + /* Secondary CPU startup code is in IROM memory */ #define EXYNOS4210_SMP_BOOT_ADDR EXYNOS4210_IROM_BASE_ADDR #define EXYNOS4210_SMP_BOOT_SIZE 0x1000 @@ -53,6 +58,8 @@ #define EXYNOS4210_SMP_PRIVATE_BASE_ADDR 0x10500000 #define EXYNOS4210_L2X0_BASE_ADDR 0x10502000 +#define EXYNOS4210_I2C_NUMBER 9 + /* * exynos4210 IRQ subsystem stub definitions. */ @@ -95,6 +102,10 @@ typedef struct Exynos4210State { MemoryRegion dram1_mem; MemoryRegion boot_secondary; MemoryRegion bootreg_mem; + MemoryRegion audss_intmem; + i2c_bus *i2c_if[EXYNOS4210_I2C_NUMBER]; + BusState *i2s_bus[3]; + SysBusDevice *vpci_bus; } Exynos4210State; void exynos4210_write_secondary(CPUARMState *env, @@ -123,8 +134,54 @@ void exynos4210_combiner_get_gpioin(Exynos4210Irq *irqs, DeviceState *dev, int ext); /* + * Interface for exynos4210 Clock Management Units (CMUs) + */ +typedef enum { + UNSPECIFIED_CMU = -1, + EXYNOS4210_CMU_LEFTBUS, + EXYNOS4210_CMU_RIGHTBUS, + EXYNOS4210_CMU_TOP, + EXYNOS4210_CMU_DMC, + EXYNOS4210_CMU_CPU, + EXYNOS4210_CMU_NUMBER +} Exynos4210Cmu; + +typedef enum { + UNSPECIFIED_CLOCK, + EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, +// EXYNOS4210_USB_PHY, +// EXYNOS4210_USB_HOST_PHY, +// EXYNOS4210_HDMI_PHY, + EXYNOS4210_APLL, + EXYNOS4210_MPLL, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_APLL, + EXYNOS4210_SCLK_MPLL, + EXYNOS4210_ACLK_100, + EXYNOS4210_SCLK_UART0, + EXYNOS4210_SCLK_UART1, + EXYNOS4210_SCLK_UART2, + EXYNOS4210_SCLK_UART3, + EXYNOS4210_SCLK_UART4, + EXYNOS4210_CLOCKS_NUMBER +} Exynos4210Clock; + +typedef void ClockChangeHandler(void *opaque); + +DeviceState *exynos4210_cmu_create(target_phys_addr_t addr, Exynos4210Cmu cmu); +uint64_t exynos4210_cmu_get_rate(Exynos4210Clock clock_id); +void exynos4210_register_clock_handler(ClockChangeHandler *func, + Exynos4210Clock clock_id, + void *opaque); + +/* * exynos4210 UART */ + DeviceState *exynos4210_uart_create(target_phys_addr_t addr, int fifo_size, int channel, diff --git a/hw/exynos4210_audio.c b/hw/exynos4210_audio.c new file mode 100644 index 0000000..c5004ab --- /dev/null +++ b/hw/exynos4210_audio.c @@ -0,0 +1,149 @@ +/* + * Samsung exynos4210 Audio driver + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Vorobiov Stanislav + * + * 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, see . + * + */ + +#include "exynos4210_i2s.h" +#include "wm8994.h" + +/* #define DEBUG_EXYNOS4210_AUDIO */ + +#ifdef DEBUG_EXYNOS4210_AUDIO +#define DPRINTF(fmt, ...) \ + do { \ + fprintf(stdout, "AUDIO: [%s:%d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define AUDIO_MAX_WORDS (4096 / EXYNOS4210_I2S_WORD_LEN) + +typedef struct { + Exynos4210I2SSlave i2s; + + DeviceState *wm8994; +} Exynos4210AudioState; + +static void exynos4210_audio_callback(void *opaque, int free_out_bytes) +{ + Exynos4210AudioState *s = (Exynos4210AudioState *)opaque; + uint8_t buff[AUDIO_MAX_WORDS * EXYNOS4210_I2S_WORD_LEN]; + int free_out_words = free_out_bytes / EXYNOS4210_I2S_WORD_LEN; + uint32_t num_words; + + if (free_out_words <= 0) { + return; + } + + if (free_out_words > AUDIO_MAX_WORDS) { + free_out_words = AUDIO_MAX_WORDS; + } + + if (!exynos4210_i2s_dma_enabled(qdev_get_parent_bus(&s->i2s.qdev))) { + return; + } + + num_words = exynos4210_i2s_dma_get_words_available( + qdev_get_parent_bus(&s->i2s.qdev)); + + num_words = MIN(num_words, free_out_words); + + exynos4210_i2s_dma_read(qdev_get_parent_bus(&s->i2s.qdev), + &buff[0], + num_words); + + num_words = wm8994_dac_write(s->wm8994, + &buff[0], + num_words * EXYNOS4210_I2S_WORD_LEN) / EXYNOS4210_I2S_WORD_LEN; + + exynos4210_i2s_dma_advance(qdev_get_parent_bus(&s->i2s.qdev), num_words); +} + +static void exynos4210_audio_dma_enable(Exynos4210I2SSlave *i2s, bool enable) +{ + Exynos4210AudioState *s = + FROM_EXYNOS4210_I2S_SLAVE(Exynos4210AudioState, i2s); + + DPRINTF("enter %d\n", enable); + + wm8994_set_active(s->wm8994, enable); +} + +static void exynos4210_audio_reset(DeviceState *dev) +{ + DPRINTF("enter\n"); +} + +static int exynos4210_audio_init(Exynos4210I2SSlave *i2s) +{ + Exynos4210AudioState *s = + FROM_EXYNOS4210_I2S_SLAVE(Exynos4210AudioState, i2s); + + wm8994_data_req_set(s->wm8994, exynos4210_audio_callback, s); + + return 0; +} + +static const VMStateDescription vmstate_exynos4210_audio = { + .name = "exynos4210.audio", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_EXYNOS4210_I2S_SLAVE(i2s, Exynos4210AudioState), + VMSTATE_END_OF_LIST() + } +}; + +static Property exynos4210_audio_properties[] = { + { + .name = "wm8994", + .info = &qdev_prop_ptr, + .offset = offsetof(Exynos4210AudioState, wm8994), + }, + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_audio_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + Exynos4210I2SSlaveClass *sc = EXYNOS4210_I2S_SLAVE_CLASS(klass); + + sc->init = exynos4210_audio_init; + sc->dma_enable = exynos4210_audio_dma_enable; + dc->reset = exynos4210_audio_reset; + dc->props = exynos4210_audio_properties; + dc->vmsd = &vmstate_exynos4210_audio; +} + +static TypeInfo exynos4210_audio_info = { + .name = "exynos4210.audio", + .parent = TYPE_EXYNOS4210_I2S_SLAVE, + .instance_size = sizeof(Exynos4210AudioState), + .class_init = exynos4210_audio_class_init, +}; + +static void exynos4210_audio_register_types(void) +{ + type_register_static(&exynos4210_audio_info); +} + +type_init(exynos4210_audio_register_types) diff --git a/hw/exynos4210_cmu.c b/hw/exynos4210_cmu.c new file mode 100644 index 0000000..1ea9d7a --- /dev/null +++ b/hw/exynos4210_cmu.c @@ -0,0 +1,1482 @@ +/* + * exynos4210 Clock Management Units (CMUs) Emulation + * + * Copyright (C) 2011 Samsung Electronics Co Ltd. + * Maksim Kozlov, + * + * 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, see . + * + */ + +#include "sysbus.h" + +#include "exynos4210.h" + + + +#define DEBUG_CMU 0 +#define DEBUG_CMU_EXTEND 0 + +#if DEBUG_CMU || DEBUG_CMU_EXTEND + + #define PRINT_DEBUG(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + + #define PRINT_DEBUG_SIMPLE(fmt, args...) \ + do { \ + fprintf(stderr, fmt, ## args); \ + } while (0) + +#if DEBUG_CMU_EXTEND + + #define PRINT_DEBUG_EXTEND(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) +#else + #define PRINT_DEBUG_EXTEND(fmt, args...) \ + do {} while (0) +#endif /* EXTEND */ + +#else + #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) +#endif + +#define PRINT_ERROR(fmt, args...) \ + do { \ + fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ + } while (0) + + + +/* function blocks */ +#define LEFTBUS_BLK 0x0 +#define RIGHTBUS_BLK 0x0 +#define TOP_BLK 0x10 +#define TOP0_BLK 0x10 +#define TOP1_BLK 0x14 +#define DMC_BLK 0x0 +#define DMC0_BLK 0x0 +#define DMC1_BLK 0x4 +#define CPU_BLK 0x0 +#define CPU0_BLK 0x0 +#define CPU1_BLK 0x4 + +#define CAM_BLK 0x20 +#define TV_BLK 0x24 +#define MFC_BLK 0x28 +#define G3D_BLK 0x2C +#define IMAGE_BLK 0x30 +#define LCD0_BLK 0x34 +#define LCD1_BLK 0x38 +#define MAUDIO_BLK 0x3C +#define FSYS_BLK 0x40 +#define FSYS0_BLK 0x40 +#define FSYS1_BLK 0x44 +#define FSYS2_BLK 0x48 +#define FSYS3_BLK 0x4C +#define GPS_BLK 0x4C /* CLK_GATE_IP_GPS in CMU_TOP */ +#define PERIL_BLK 0x50 +#define PERIL0_BLK 0x50 +#define PERIL1_BLK 0x54 +#define PERIL2_BLK 0x58 +#define PERIL3_BLK 0x5C +#define PERIL4_BLK 0x60 +#define PERIR_BLK 0x60 /* CLK_GATE_IP_PERIR in CMU_TOP */ +#define PERIL5_BLK 0x64 + +#define BLOCK_MASK 0xFF + +/* PLLs */ +/* located in CMU_CPU block */ +#define APLL 0x00 +#define MPLL 0x08 +/* located in CMU_TOP block */ +#define EPLL 0x10 +#define VPLL 0x20 + +/* groups of registers */ +#define PLL_LOCK 0x000 +#define PLL_CON 0x100 +#define CLK_SRC 0x200 +#define CLK_SRC_MASK 0x300 +#define CLK_MUX_STAT 0x400 +#define CLK_DIV 0x500 +#define CLK_DIV_STAT 0x600 +#define CLK_GATE_SCLK 0x800 +#define CLK_GATE_IP 0x900 + +#define GROUP_MASK 0xF00 + +#define PLL_LOCK_(pll) (PLL_LOCK + pll) +#define PLL_CON0_(pll) (PLL_CON + pll) +#define PLL_CON1_(pll) (PLL_CON + pll + 4) + +#define CLK_SRC_(block) (CLK_SRC + block) +#define CLK_SRC_MASK_(block) (CLK_SRC_MASK + block) +#define CLK_MUX_STAT_(block) (CLK_MUX_STAT + block) +#define CLK_DIV_(block) (CLK_DIV + block) +#define CLKDIV2_RATIO 0x580 /* described for CMU_TOP only */ +#define CLK_DIV_STAT_(block) (CLK_DIV_STAT + block) +#define CLKDIV2_STAT 0x680 /* described for CMU_TOP only */ +#define CLK_GATE_SCLK_(block) (CLK_GATE_SCLK + block) +/* For CMU_LEFTBUS and CMU_RIGHTBUS, CLK_GATE_IP_XXX + registers are located at 0x800. */ +#define CLK_GATE_IP_LR_BUS 0x800 +#define CLK_GATE_IP_(block) (CLK_GATE_IP + block) +#define CLK_GATE_BLOCK 0x970 /* described for CMU_TOP only */ +#define CLKOUT_CMU 0xA00 +#define CLKOUT_CMU_DIV_STAT 0xA04 + +/* + * registers which are located outside of 0xAFF region + */ +/* CMU_DMC */ +#define DCGIDX_MAP0 0x01000 +#define DCGIDX_MAP1 0x01004 +#define DCGIDX_MAP2 0x01008 +#define DCGPERF_MAP0 0x01020 +#define DCGPERF_MAP1 0x01024 +#define DVCIDX_MAP 0x01040 +#define FREQ_CPU 0x01060 +#define FREQ_DPM 0x01064 +#define DVSEMCLK_EN 0x01080 +#define MAXPERF 0x01084 +/* CMU_CPU */ +#define ARMCLK_STOPCTRL 0x01000 +#define ATCLK_STOPCTRL 0x01004 +#define PARITYFAIL_STATUS 0x01010 +#define PARITYFAIL_CLEAR 0x01014 +#define PWR_CTRL 0x01020 +#define APLL_CON0_L8 0x01100 +#define APLL_CON0_L7 0x01104 +#define APLL_CON0_L6 0x01108 +#define APLL_CON0_L5 0x0110C +#define APLL_CON0_L4 0x01110 +#define APLL_CON0_L3 0x01114 +#define APLL_CON0_L2 0x01118 +#define APLL_CON0_L1 0x0111C +#define IEM_CONTROL 0x01120 +#define APLL_CON1_L8 0x01200 +#define APLL_CON1_L7 0x01204 +#define APLL_CON1_L6 0x01208 +#define APLL_CON1_L5 0x0120C +#define APLL_CON1_L4 0x01210 +#define APLL_CON1_L3 0x01214 +#define APLL_CON1_L2 0x01218 +#define APLL_CON1_L1 0x0121C +#define CLKDIV_IEM_L8 0x01300 +#define CLKDIV_IEM_L7 0x01304 +#define CLKDIV_IEM_L6 0x01308 +#define CLKDIV_IEM_L5 0x0130C +#define CLKDIV_IEM_L4 0x01310 +#define CLKDIV_IEM_L3 0x01314 +#define CLKDIV_IEM_L2 0x01318 +#define CLKDIV_IEM_L1 0x0131C + +#define EXTENDED_REGION_MASK 0x1000 +#define EXTENDED_REGISTER_MASK 0xFFF + +typedef struct { + const char *name; /* for debugging */ + uint32_t offset; + uint32_t reset_value; +} Exynos4210CmuReg; + + +static Exynos4210CmuReg exynos4210_cmu_leftbus_regs[] = { + /* CMU_LEFTBUS registers */ + {"CLK_SRC_LEFTBUS", CLK_SRC_(LEFTBUS_BLK), 0x00000000}, + {"CLK_MUX_STAT_LEFTBUS", CLK_MUX_STAT_(LEFTBUS_BLK), 0x00000001}, + {"CLK_DIV_LEFTBUS", CLK_DIV_(LEFTBUS_BLK), 0x00000000}, + {"CLK_DIV_STAT_LEFTBUS", CLK_DIV_STAT_(LEFTBUS_BLK), 0x00000000}, + {"CLK_GATE_IP_LEFTBUS", CLK_GATE_IP_LR_BUS, 0xFFFFFFFF}, + {"CLKOUT_CMU_LEFTBUS", CLKOUT_CMU, 0x00010000}, + {"CLKOUT_CMU_LEFTBUS_DIV_STAT", CLKOUT_CMU_DIV_STAT, 0x00000000}, +}; + +static Exynos4210CmuReg exynos4210_cmu_rightbus_regs[] = { + /* CMU_RIGHTBUS registers */ + {"CLK_SRC_RIGHTBUS", CLK_SRC_(RIGHTBUS_BLK), 0x00000000}, + {"CLK_MUX_STAT_RIGHTBUS", CLK_MUX_STAT_(RIGHTBUS_BLK), 0x00000001}, + {"CLK_DIV_RIGHTBUS", CLK_DIV_(RIGHTBUS_BLK), 0x00000000}, + {"CLK_DIV_STAT_RIGHTBUS", CLK_DIV_STAT_(RIGHTBUS_BLK), 0x00000000}, + {"CLK_GATE_IP_RIGHTBUS", CLK_GATE_IP_LR_BUS, 0xFFFFFFFF}, + {"CLKOUT_CMU_RIGHTBUS", CLKOUT_CMU, 0x00010000}, + {"CLKOUT_CMU_RIGHTBUS_DIV_STAT", CLKOUT_CMU_DIV_STAT, 0x00000000}, +}; + +static Exynos4210CmuReg exynos4210_cmu_top_regs[] = { + /* CMU_TOP registers */ + {"EPLL_LOCK", PLL_LOCK_(EPLL), 0x00000FFF}, + {"VPLL_LOCK", PLL_LOCK_(VPLL), 0x00000FFF}, + {"EPLL_CON0", PLL_CON0_(EPLL), 0x00300301}, + {"EPLL_CON1", PLL_CON1_(EPLL), 0x00000000}, + {"VPLL_CON0", PLL_CON0_(VPLL), 0x00240201}, + {"VPLL_CON1", PLL_CON1_(VPLL), 0x66010464}, + {"CLK_SRC_TOP0", CLK_SRC_(TOP0_BLK), 0x00000000}, + {"CLK_SRC_TOP1", CLK_SRC_(TOP1_BLK), 0x00000000}, + {"CLK_SRC_CAM", CLK_SRC_(CAM_BLK), 0x11111111}, + {"CLK_SRC_TV", CLK_SRC_(TV_BLK), 0x00000000}, + {"CLK_SRC_MFC", CLK_SRC_(MFC_BLK), 0x00000000}, + {"CLK_SRC_G3D", CLK_SRC_(G3D_BLK), 0x00000000}, + {"CLK_SRC_IMAGE", CLK_SRC_(IMAGE_BLK), 0x00000000}, + {"CLK_SRC_LCD0", CLK_SRC_(LCD0_BLK), 0x00001111}, + {"CLK_SRC_LCD1", CLK_SRC_(LCD1_BLK), 0x00001111}, + {"CLK_SRC_MAUDIO", CLK_SRC_(MAUDIO_BLK), 0x00000005}, + {"CLK_SRC_FSYS", CLK_SRC_(FSYS_BLK), 0x00011111}, + {"CLK_SRC_PERIL0", CLK_SRC_(PERIL0_BLK), 0x00011111}, + {"CLK_SRC_PERIL1", CLK_SRC_(PERIL1_BLK), 0x01110055}, + {"CLK_SRC_MASK_TOP", CLK_SRC_MASK_(TOP_BLK), 0x00000001}, + {"CLK_SRC_MASK_CAM", CLK_SRC_MASK_(CAM_BLK), 0x11111111}, + {"CLK_SRC_MASK_TV", CLK_SRC_MASK_(TV_BLK), 0x00000111}, + {"CLK_SRC_MASK_LCD0", CLK_SRC_MASK_(LCD0_BLK), 0x00001111}, + {"CLK_SRC_MASK_LCD1", CLK_SRC_MASK_(LCD1_BLK), 0x00001111}, + {"CLK_SRC_MASK_MAUDIO", CLK_SRC_MASK_(MAUDIO_BLK), 0x00000001}, + {"CLK_SRC_MASK_FSYS", CLK_SRC_MASK_(FSYS_BLK), 0x01011111}, + {"CLK_SRC_MASK_PERIL0", CLK_SRC_MASK_(PERIL0_BLK), 0x00011111}, + {"CLK_SRC_MASK_PERIL1", CLK_SRC_MASK_(PERIL1_BLK), 0x01110111}, + {"CLK_MUX_STAT_TOP", CLK_MUX_STAT_(TOP_BLK), 0x11111111}, + {"CLK_MUX_STAT_MFC", CLK_MUX_STAT_(MFC_BLK), 0x00000111}, + {"CLK_MUX_STAT_G3D", CLK_MUX_STAT_(G3D_BLK), 0x00000111}, + {"CLK_MUX_STAT_IMAGE", CLK_MUX_STAT_(IMAGE_BLK), 0x00000111}, + {"CLK_DIV_TOP", CLK_DIV_(TOP_BLK), 0x00000000}, + {"CLK_DIV_CAM", CLK_DIV_(CAM_BLK), 0x00000000}, + {"CLK_DIV_TV", CLK_DIV_(TV_BLK), 0x00000000}, + {"CLK_DIV_MFC", CLK_DIV_(MFC_BLK), 0x00000000}, + {"CLK_DIV_G3D", CLK_DIV_(G3D_BLK), 0x00000000}, + {"CLK_DIV_IMAGE", CLK_DIV_(IMAGE_BLK), 0x00000000}, + {"CLK_DIV_LCD0", CLK_DIV_(LCD0_BLK), 0x00700000}, + {"CLK_DIV_LCD1", CLK_DIV_(LCD1_BLK), 0x00700000}, + {"CLK_DIV_MAUDIO", CLK_DIV_(MAUDIO_BLK), 0x00000000}, + {"CLK_DIV_FSYS0", CLK_DIV_(FSYS0_BLK), 0x00B00000}, + {"CLK_DIV_FSYS1", CLK_DIV_(FSYS1_BLK), 0x00000000}, + {"CLK_DIV_FSYS2", CLK_DIV_(FSYS2_BLK), 0x00000000}, + {"CLK_DIV_FSYS3", CLK_DIV_(FSYS3_BLK), 0x00000000}, + {"CLK_DIV_PERIL0", CLK_DIV_(PERIL0_BLK), 0x00000000}, + {"CLK_DIV_PERIL1", CLK_DIV_(PERIL1_BLK), 0x00000000}, + {"CLK_DIV_PERIL2", CLK_DIV_(PERIL2_BLK), 0x00000000}, + {"CLK_DIV_PERIL3", CLK_DIV_(PERIL3_BLK), 0x00000000}, + {"CLK_DIV_PERIL4", CLK_DIV_(PERIL4_BLK), 0x00000000}, + {"CLK_DIV_PERIL5", CLK_DIV_(PERIL5_BLK), 0x00000000}, + {"CLKDIV2_RATIO", CLKDIV2_RATIO, 0x11111111}, + {"CLK_DIV_STAT_TOP", CLK_DIV_STAT_(TOP_BLK), 0x00000000}, + {"CLK_DIV_STAT_CAM", CLK_DIV_STAT_(CAM_BLK), 0x00000000}, + {"CLK_DIV_STAT_TV", CLK_DIV_STAT_(TV_BLK), 0x00000000}, + {"CLK_DIV_STAT_MFC", CLK_DIV_STAT_(MFC_BLK), 0x00000000}, + {"CLK_DIV_STAT_G3D", CLK_DIV_STAT_(G3D_BLK), 0x00000000}, + {"CLK_DIV_STAT_IMAGE", CLK_DIV_STAT_(IMAGE_BLK), 0x00000000}, + {"CLK_DIV_STAT_LCD0", CLK_DIV_STAT_(LCD0_BLK), 0x00000000}, + {"CLK_DIV_STAT_LCD1", CLK_DIV_STAT_(LCD1_BLK), 0x00000000}, + {"CLK_DIV_STAT_MAUDIO", CLK_DIV_STAT_(MAUDIO_BLK), 0x00000000}, + {"CLK_DIV_STAT_FSYS0", CLK_DIV_STAT_(FSYS0_BLK), 0x00000000}, + {"CLK_DIV_STAT_FSYS1", CLK_DIV_STAT_(FSYS1_BLK), 0x00000000}, + {"CLK_DIV_STAT_FSYS2", CLK_DIV_STAT_(FSYS2_BLK), 0x00000000}, + {"CLK_DIV_STAT_FSYS3", CLK_DIV_STAT_(FSYS3_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL0", CLK_DIV_STAT_(PERIL0_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL1", CLK_DIV_STAT_(PERIL1_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL2", CLK_DIV_STAT_(PERIL2_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL3", CLK_DIV_STAT_(PERIL3_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL4", CLK_DIV_STAT_(PERIL4_BLK), 0x00000000}, + {"CLK_DIV_STAT_PERIL5", CLK_DIV_STAT_(PERIL5_BLK), 0x00000000}, + {"CLKDIV2_STAT", CLKDIV2_STAT, 0x00000000}, + {"CLK_GATE_SCLK_CAM", CLK_GATE_SCLK_(CAM_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_CAM", CLK_GATE_IP_(CAM_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_TV", CLK_GATE_IP_(TV_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_MFC", CLK_GATE_IP_(MFC_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_G3D", CLK_GATE_IP_(G3D_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_IMAGE", CLK_GATE_IP_(IMAGE_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_LCD0", CLK_GATE_IP_(LCD0_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_LCD1", CLK_GATE_IP_(LCD1_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_FSYS", CLK_GATE_IP_(FSYS_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_GPS", CLK_GATE_IP_(GPS_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_PERIL", CLK_GATE_IP_(PERIL_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_PERIR", CLK_GATE_IP_(PERIR_BLK), 0xFFFFFFFF}, + {"CLK_GATE_BLOCK", CLK_GATE_BLOCK, 0xFFFFFFFF}, + {"CLKOUT_CMU_TOP", CLKOUT_CMU, 0x00010000}, + {"CLKOUT_CMU_TOP_DIV_STAT", CLKOUT_CMU_DIV_STAT, 0x00000000}, +}; + +static Exynos4210CmuReg exynos4210_cmu_dmc_regs[] = { + /* CMU_DMC registers */ + {"CLK_SRC_DMC", CLK_SRC_(DMC_BLK), 0x00010000}, + {"CLK_SRC_MASK_DMC", CLK_SRC_MASK_(DMC_BLK), 0x00010000}, + {"CLK_MUX_STAT_DMC", CLK_MUX_STAT_(DMC_BLK), 0x11100110}, + {"CLK_DIV_DMC0", CLK_DIV_(DMC0_BLK), 0x00000000}, + {"CLK_DIV_DMC1", CLK_DIV_(DMC1_BLK), 0x00000000}, + {"CLK_DIV_STAT_DMC0", CLK_DIV_STAT_(DMC0_BLK), 0x00000000}, + {"CLK_DIV_STAT_DMC1", CLK_DIV_STAT_(DMC1_BLK), 0x00000000}, + {"CLK_GATE_IP_DMC", CLK_GATE_IP_(DMC_BLK), 0xFFFFFFFF}, + {"CLKOUT_CMU_DMC", CLKOUT_CMU, 0x00010000}, + {"CLKOUT_CMU_DMC_DIV_STAT", CLKOUT_CMU_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}, +}; + +static Exynos4210CmuReg exynos4210_cmu_cpu_regs[] = { + /* CMU_CPU registers */ + {"APLL_LOCK", PLL_LOCK_(APLL), 0x00000FFF}, + {"MPLL_LOCK", PLL_LOCK_(MPLL), 0x00000FFF}, + {"APLL_CON0", PLL_CON0_(APLL), 0x00C80601}, + {"APLL_CON1", PLL_CON1_(APLL), 0x0000001C}, + {"MPLL_CON0", PLL_CON0_(MPLL), 0x00C80601}, + {"MPLL_CON1", PLL_CON1_(MPLL), 0x0000001C}, + {"CLK_SRC_CPU", CLK_SRC_(CPU_BLK), 0x00000000}, + {"CLK_MUX_STAT_CPU", CLK_MUX_STAT_(CPU_BLK), 0x00110101}, + {"CLK_DIV_CPU0", CLK_DIV_(CPU0_BLK), 0x00000000}, + {"CLK_DIV_CPU1", CLK_DIV_(CPU1_BLK), 0x00000000}, + {"CLK_DIV_STAT_CPU0", CLK_DIV_STAT_(CPU0_BLK), 0x00000000}, + {"CLK_DIV_STAT_CPU1", CLK_DIV_STAT_(CPU1_BLK), 0x00000000}, + {"CLK_GATE_SCLK_CPU", CLK_GATE_SCLK_(CPU_BLK), 0xFFFFFFFF}, + {"CLK_GATE_IP_CPU", CLK_GATE_IP_(CPU_BLK), 0xFFFFFFFF}, + {"CLKOUT_CMU_CPU", CLKOUT_CMU, 0x00010000}, + {"CLKOUT_CMU_CPU_DIV_STAT", CLKOUT_CMU_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}, +}; + +#define EXYNOS4210_CMU_REGS_MEM_SIZE 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 + +#define SOURCES_NUMBER 9 + +typedef struct ClockChangeEntry { + QTAILQ_ENTRY(ClockChangeEntry) entry; + ClockChangeHandler *func; + void *opaque; +} ClockChangeEntry; + +#define TYPE_EXYNOS4210_CMU "exynos4210.cmu" + +typedef struct { + + const char *name; + Exynos4210Clock id; + uint64_t rate; + + /* Current source clock */ + Exynos4210Clock src_id; + /* + * Available sources. Their order must correspond to CLK_SRC_ register + */ + Exynos4210Clock src_ids[SOURCES_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; + + /* Which CMU controls this clock */ + Exynos4210Cmu cmu_id; + + QTAILQ_HEAD(, ClockChangeEntry) clock_change_handler; + +} Exynos4210ClockState; + + +typedef struct { + + SysBusDevice busdev; + MemoryRegion iomem; + + /* registers values */ + uint32_t reg[EXYNOS4210_CMU_REGS_MEM_SIZE]; + + /* which CMU it is */ + Exynos4210Cmu cmu_id; + + /* registers information for debugging and resetting */ + Exynos4210CmuReg *regs; + int regs_number; + + Exynos4210ClockState *clock; + int clock_number; /* how many clocks are controlled by given CMU */ + +} Exynos4210CmuState; + + +/* Clocks from Clock Pads */ +/* + * Two following clocks aren't controlled by any CMUs. These structures are + * used directly from global space and their fields shouldn't be modified. + */ + +/* It should be used only for testing purposes. XOM_0 is 0 */ +static Exynos4210ClockState xxti = { + .name = "XXTI", + .id = EXYNOS4210_XXTI, + .rate = 24000000, + .cmu_id = UNSPECIFIED_CMU, +}; + +/* Main source. XOM_0 is 1 */ +static Exynos4210ClockState xusbxti = { + .name = "XUSBXTI", + .id = EXYNOS4210_XUSBXTI, + .rate = 24000000, + .cmu_id = UNSPECIFIED_CMU, +}; + +//static Exynos4210ClockState usb_phy = { +// .name = "USB_PHY", +// .id = EXYNOS4210_USB_PHY, +// .src_id = EXYNOS4210_XUSBXTI, +// .cmu_id = UNSPECIFIED_CMU, +//}; +// +//static Exynos4210ClockState usb_host_phy = { +// .name = "USB_HOST_PHY", +// .id = EXYNOS4210_USB_HOST_PHY, +// .src_id = EXYNOS4210_XUSBXTI, +// .cmu_id = UNSPECIFIED_CMU, +//}; +// +//static Exynos4210ClockState hdmi_phy = { +// .name = "HDMI_PHY", +// .id = EXYNOS4210_HDMI_PHY, +// .src_id = EXYNOS4210_XUSBXTI, +// .cmu_id = UNSPECIFIED_CMU, +//}; + + +/* PLLs */ + +static Exynos4210ClockState mpll = { + .name = "MPLL", + .id = EXYNOS4210_MPLL, + .src_id = (XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI), + .div_reg = PLL_CON0_(MPLL), + .cmu_id = EXYNOS4210_CMU_CPU, +}; + +static Exynos4210ClockState apll = { + .name = "APLL", + .id = EXYNOS4210_APLL, + .src_id = (XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI), + .div_reg = PLL_CON0_(APLL), + .cmu_id = EXYNOS4210_CMU_CPU, +}; + + +/**/ + +static Exynos4210ClockState sclk_hdmi24m = { + .name = "SCLK_HDMI24M", + .id = EXYNOS4210_SCLK_HDMI24M, + .rate = 24000000, +// .src_id = EXYNOS4210_HDMI_PHY, + .cmu_id = UNSPECIFIED_CMU, +}; + +static Exynos4210ClockState sclk_usbphy0 = { + .name = "SCLK_USBPHY0", + .id = EXYNOS4210_SCLK_USBPHY0, + .rate = 24000000, +// .src_id = EXYNOS4210_USB_PHY, + .cmu_id = UNSPECIFIED_CMU, +}; + +static Exynos4210ClockState sclk_usbphy1 = { + .name = "SCLK_USBPHY1", + .id = EXYNOS4210_SCLK_USBPHY1, + .rate = 24000000, +// .src_id = EXYNOS4210_USB_HOST_PHY, + .cmu_id = UNSPECIFIED_CMU, +}; + +static Exynos4210ClockState sclk_hdmiphy = { + .name = "SCLK_HDMIPHY", + .id = EXYNOS4210_SCLK_HDMIPHY, + .rate = 24000000, +// .src_id = EXYNOS4210_HDMI_PHY, + .cmu_id = UNSPECIFIED_CMU, +}; + +static Exynos4210ClockState sclk_mpll = { + .name = "SCLK_MPLL", + .id = EXYNOS4210_SCLK_MPLL, + .src_ids = {XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI, + EXYNOS4210_MPLL}, + .src_reg = CLK_SRC_(CPU_BLK), + .mux_shift = MUX_MPLL_SEL_SHIFT, + .cmu_id = EXYNOS4210_CMU_CPU, +}; + +static Exynos4210ClockState sclk_apll = { + .name = "SCLK_APLL", + .id = EXYNOS4210_SCLK_APLL, + .src_ids = {XOM_0 ? EXYNOS4210_XUSBXTI : EXYNOS4210_XXTI, + EXYNOS4210_APLL}, + .src_reg = CLK_SRC_(CPU_BLK), + .div_reg = CLK_DIV_(CPU0_BLK), + .mux_shift = MUX_APLL_SEL_SHIFT, + .div_shift = APLL_RATIO_SHIFT, + .cmu_id = EXYNOS4210_CMU_CPU, +}; + +static Exynos4210ClockState aclk_100 = { + .name = "ACLK_100", + .id = EXYNOS4210_ACLK_100, + .src_ids = {EXYNOS4210_SCLK_MPLL, EXYNOS4210_SCLK_APLL}, + .src_reg = CLK_SRC_(TOP0_BLK), + .div_reg = CLK_DIV_(TOP_BLK), + .mux_shift = MUX_ACLK_100_SEL_SHIFT, + .div_shift = ACLK_100_RATIO_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +static Exynos4210ClockState sclk_uart0 = { + .name = "SCLK_UART0", + .id = EXYNOS4210_SCLK_UART0, + .src_ids = {EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_MPLL}, + .src_reg = CLK_SRC_(PERIL0_BLK), + .div_reg = CLK_DIV_(PERIL0_BLK), + .mux_shift = UART0_SEL_SHIFT, + .div_shift = UART0_DIV_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +static Exynos4210ClockState sclk_uart1 = { + .name = "SCLK_UART1", + .id = EXYNOS4210_SCLK_UART1, + .src_ids = {EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_MPLL}, + .src_reg = CLK_SRC_(PERIL0_BLK), + .div_reg = CLK_DIV_(PERIL0_BLK), + .mux_shift = UART1_SEL_SHIFT, + .div_shift = UART1_DIV_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +static Exynos4210ClockState sclk_uart2 = { + .name = "SCLK_UART2", + .id = EXYNOS4210_SCLK_UART2, + .src_ids = {EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_MPLL}, + .src_reg = CLK_SRC_(PERIL0_BLK), + .div_reg = CLK_DIV_(PERIL0_BLK), + .mux_shift = UART2_SEL_SHIFT, + .div_shift = UART2_DIV_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +static Exynos4210ClockState sclk_uart3 = { + .name = "SCLK_UART3", + .id = EXYNOS4210_SCLK_UART3, + .src_ids = {EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_MPLL}, + .src_reg = CLK_SRC_(PERIL0_BLK), + .div_reg = CLK_DIV_(PERIL0_BLK), + .mux_shift = UART3_SEL_SHIFT, + .div_shift = UART3_DIV_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +static Exynos4210ClockState sclk_uart4 = { + .name = "SCLK_UART4", + .id = EXYNOS4210_SCLK_UART4, + .src_ids = {EXYNOS4210_XXTI, + EXYNOS4210_XUSBXTI, + EXYNOS4210_SCLK_HDMI24M, + EXYNOS4210_SCLK_USBPHY0, + EXYNOS4210_SCLK_USBPHY1, + EXYNOS4210_SCLK_HDMIPHY, + EXYNOS4210_SCLK_MPLL}, + .src_reg = CLK_SRC_(PERIL0_BLK), + .div_reg = CLK_DIV_(PERIL0_BLK), + .mux_shift = UART4_SEL_SHIFT, + .div_shift = UART4_DIV_SHIFT, + .cmu_id = EXYNOS4210_CMU_TOP, +}; + +/* + * This array must correspond to Exynos4210Clock enumerator + * which is defined in exynos4210.h file + * + */ +static Exynos4210ClockState *exynos4210_clock[] = { + NULL, + &xxti, + &xusbxti, +// &usb_phy, +// &usb_host_phy, +// &hdmi_phy, + &apll, + &mpll, + &sclk_hdmi24m, + &sclk_usbphy0, + &sclk_usbphy1, + &sclk_hdmiphy, + &sclk_apll, + &sclk_mpll, + &aclk_100, + &sclk_uart0, + &sclk_uart1, + &sclk_uart2, + &sclk_uart3, + &sclk_uart4, + NULL, +}; + +/* + * This array must correspond to Exynos4210Cmu enumerator + * which is defined in exynos4210.h file + * + */ +static char exynos4210_cmu_path[][13] = { + "cmu_leftbus", + "cmu_rightbus", + "cmu_top", + "cmu_dmc", + "cmu_cpu", +}; + +#if DEBUG_CMU_EXTEND +/* The only meaning of life - debugging. This function should be only used + * inside PRINT_DEBUG_EXTEND macros + */ +static const char *exynos4210_cmu_regname(Exynos4210CmuState *s, + target_phys_addr_t offset) +{ + int i; + + for (i = 0; i < s->regs_number; i++) { + if (offset == s->regs[i].offset) { + return s->regs[i].name; + } + } + + return NULL; +} +#endif + + +static Exynos4210ClockState *exynos4210_clock_find(Exynos4210Clock clock_id) +{ + int i; + int cmu_id; + Object *cmu; + Exynos4210CmuState *s; + + cmu_id = exynos4210_clock[clock_id]->cmu_id; + + if (cmu_id == UNSPECIFIED_CMU) { + for (i = 1; i < EXYNOS4210_CLOCKS_NUMBER; i++) { + if (exynos4210_clock[i]->id == clock_id) { + + PRINT_DEBUG("Clock %s [%p] in CMU %d have been found\n", + exynos4210_clock[i]->name, + exynos4210_clock[i], + cmu_id); + + return exynos4210_clock[i]; + } + } + } + + cmu = object_resolve_path(exynos4210_cmu_path[cmu_id], NULL); + s = OBJECT_CHECK(Exynos4210CmuState, cmu, TYPE_EXYNOS4210_CMU); + + for (i = 0; i < s->clock_number; i++) { + if (s->clock[i].id == clock_id) { + + PRINT_DEBUG("Clock %s [%p] in CMU %d have been found\n", + s->clock[i].name, + &s->clock[i], + s->clock[i].cmu_id); + return &s->clock[i]; + } + } + + PRINT_ERROR("Clock %d not found\n", clock_id); + + return NULL; +} + + +void exynos4210_register_clock_handler(ClockChangeHandler *func, + Exynos4210Clock clock_id, void *opaque) +{ + ClockChangeEntry *cce = g_malloc0(sizeof(ClockChangeEntry)); + Exynos4210ClockState *clock = exynos4210_clock_find(clock_id); + + if (clock == NULL) { + hw_error("We aren't be able to find clock %d\n", clock_id); + } else if (clock->cmu_id == UNSPECIFIED_CMU) { + + PRINT_DEBUG("Clock %s never are changed. Handler won't be set.", + exynos4210_clock[clock_id]->name); + + return; + } + + cce->func = func; + cce->opaque = opaque; + + QTAILQ_INSERT_TAIL(&clock->clock_change_handler, cce, entry); + + PRINT_DEBUG("For %s have been set handler [%p]\n", clock->name, cce->func); + + return; +} + +uint64_t exynos4210_cmu_get_rate(Exynos4210Clock clock_id) +{ + Exynos4210ClockState *clock = exynos4210_clock_find(clock_id); + + if (clock == NULL) { + hw_error("We aren't be able to find clock %d\n", clock_id); + } + + return clock->rate; +} + +static void exynos4210_cmu_set_pll(void *opaque, Exynos4210ClockState *pll) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + Exynos4210ClockState *source; + target_phys_addr_t offset = pll->div_reg; + ClockChangeEntry *cce; + uint32_t pdiv, mdiv, sdiv, enable; + + source = exynos4210_clock_find(pll->src_id); + + if (source == NULL) { + hw_error("We haven't find source clock %d (requested for %s)\n", + pll->src_id, pll->name); + } + + /* + * 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; + + if (source) { + if (enable) { + pll->rate = mdiv * source->rate / (pdiv * (1 << (sdiv-1))); + } else { + pll->rate = 0; + } + } else { + hw_error("%s: Source undefined for %s\n", __FUNCTION__, pll->name); + } + + QTAILQ_FOREACH(cce, &pll->clock_change_handler, entry) { + cce->func(cce->opaque); + } + + PRINT_DEBUG("%s rate: %llu\n", pll->name, pll->rate); + + s->reg[I_(offset)] |= PLL_LOCKED_MASK; +} + + +static void exynos4210_cmu_set_rate(void *opaque, Exynos4210Clock clock_id) +{ + Exynos4210CmuState *s = (Exynos4210CmuState *)opaque; + Exynos4210ClockState *clock = exynos4210_clock_find(clock_id); + ClockChangeEntry *cce; + + if (clock == NULL) { + hw_error("We haven't find source clock %d ", clock_id); + } + + if ((clock->id == EXYNOS4210_MPLL) || (clock->id == EXYNOS4210_APLL)) { + + exynos4210_cmu_set_pll(s, clock); + + } else if ((clock->cmu_id != UNSPECIFIED_CMU)) { + + Exynos4210ClockState *source; + + uint32_t src_index = I_(clock->src_reg); + uint32_t div_index = I_(clock->div_reg); + + clock->src_id = clock->src_ids[(s->reg[src_index] >> + clock->mux_shift) & 0xf]; + + source = exynos4210_clock_find(clock->src_id); + if (source == NULL) { + hw_error("We haven't find source clock %d (requested for %s)\n", + clock->src_id, clock->name); + } + + clock->rate = muldiv64(source->rate, 1, + ((((clock->div_reg ? s->reg[div_index] : 0) >> + clock->div_shift) & 0xf) + 1)); + + QTAILQ_FOREACH(cce, &clock->clock_change_handler, entry) { + cce->func(cce->opaque); + } + + PRINT_DEBUG_EXTEND("SRC: <0x%05x> %s, SHIFT: %d\n", + clock->src_reg, + exynos4210_cmu_regname(s, clock->src_reg), + clock->mux_shift); + + PRINT_DEBUG("%s [%s:%llu]: %llu\n", + clock->name, + source->name, + (long long unsigned int)source->rate, + (long long unsigned int)clock->rate); + } +} + + +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))) { + PRINT_ERROR("Bad offset: 0x%x\n", (int)offset); + return 0; + } + + if (offset & EXTENDED_REGION_MASK) { + if (s->cmu_id == EXYNOS4210_CMU_DMC) { + switch (offset & 0xFFF) { + case DCGIDX_MAP0: + case DCGIDX_MAP1: + case DCGIDX_MAP2: + case DCGPERF_MAP0: + case DCGPERF_MAP1: + case DVCIDX_MAP: + case FREQ_CPU: + case FREQ_DPM: + case DVSEMCLK_EN: + case MAXPERF: + return s->reg[I_(offset)]; + default: + PRINT_ERROR("Bad offset: 0x%x\n", (int)offset); + return 0; + } + } + + if (s->cmu_id == EXYNOS4210_CMU_CPU) { + switch (offset & 0xFFF) { + case ARMCLK_STOPCTRL: + case ATCLK_STOPCTRL: + case PARITYFAIL_STATUS: + case PARITYFAIL_CLEAR: + case PWR_CTRL: + case APLL_CON0_L8: + case APLL_CON0_L7: + case APLL_CON0_L6: + case APLL_CON0_L5: + case APLL_CON0_L4: + case APLL_CON0_L3: + case APLL_CON0_L2: + case APLL_CON0_L1: + case IEM_CONTROL: + case APLL_CON1_L8: + case APLL_CON1_L7: + case APLL_CON1_L6: + case APLL_CON1_L5: + case APLL_CON1_L4: + case APLL_CON1_L3: + case APLL_CON1_L2: + case APLL_CON1_L1: + case CLKDIV_IEM_L8: + case CLKDIV_IEM_L7: + case CLKDIV_IEM_L6: + case CLKDIV_IEM_L5: + case CLKDIV_IEM_L4: + case CLKDIV_IEM_L3: + case CLKDIV_IEM_L2: + case CLKDIV_IEM_L1: + return s->reg[I_(offset)]; + default: + PRINT_ERROR("Bad offset: 0x%x\n", (int)offset); + return 0; + } + } + } + + switch (offset & GROUP_MASK) { + case PLL_LOCK: + case PLL_CON: + case CLK_SRC: + case CLK_SRC_MASK: + case CLK_MUX_STAT: + case CLK_DIV: + case CLK_DIV_STAT: + case 0x700: /* Reserved */ + case CLK_GATE_SCLK: /* reserved? */ + case CLK_GATE_IP: + case CLKOUT_CMU: + return s->reg[I_(offset)]; + default: + PRINT_ERROR("Bad offset: 0x%x\n", (int)offset); + return 0; + } + + PRINT_DEBUG_EXTEND("<0x%05x> %s -> %08x\n", offset, + exynos4210_cmu_regname(s, offset), 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 group, block; + + group = offset & GROUP_MASK; + block = offset & BLOCK_MASK; + + switch (group) { + case PLL_LOCK: + /* it's not necessary at this moment + * TODO: do it + */ + break; + case PLL_CON: + switch (block) { + case APLL: + { + uint32_t pre_val = s->reg[I_(offset)]; + s->reg[I_(offset)] = val; + val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK); + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, EXYNOS4210_APLL); + } + break; + case MPLL: + { + uint32_t pre_val = s->reg[I_(offset)]; + s->reg[I_(offset)] = val; + val = (val & ~PLL_LOCKED_MASK) | (pre_val & PLL_LOCKED_MASK); + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, EXYNOS4210_MPLL); + } + break; + } + break; + case CLK_SRC: + switch (block) { + case CPU_BLK: + { + uint32_t pre_val = s->reg[I_(offset)]; + s->reg[I_(offset)] = val; + + if (val & MUX_APLL_SEL) { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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, EXYNOS4210_APLL); + } + + } else { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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 ? EXYNOS4210_XUSBXTI : + EXYNOS4210_XXTI); + } + } + + + if (val & MUX_MPLL_SEL) { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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, EXYNOS4210_MPLL); + } + + } else { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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 ? EXYNOS4210_XUSBXTI : + EXYNOS4210_XXTI); + } + } + + if (val & MUX_CORE_SEL) { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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, EXYNOS4210_SCLK_MPLL); + } + + } else { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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 ? EXYNOS4210_XUSBXTI : + EXYNOS4210_XXTI); + } + } + + if (val & MUX_HPM_SEL) { + exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL); + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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, EXYNOS4210_SCLK_MPLL); + } + + } else { + s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] = + (s->reg[I_(CLK_MUX_STAT_(CPU_BLK))] & ~(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 ? EXYNOS4210_XUSBXTI : + EXYNOS4210_XXTI); + } + } + } + break; + case TOP0_BLK: + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, EXYNOS4210_ACLK_100); + break; + default: + PRINT_ERROR("Unknown functional block: 0x%x\n", (int)block); + } + break; + case CLK_SRC_MASK: + break; + case CLK_MUX_STAT: + break; + case CLK_DIV: + switch (block) { + case TOP_BLK: + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, EXYNOS4210_ACLK_100); + break; + case CPU0_BLK: + s->reg[I_(offset)] = val; + exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_APLL); + exynos4210_cmu_set_rate(s, EXYNOS4210_SCLK_MPLL); + break; + } + case CLK_DIV_STAT: /* CLK_DIV_STAT */ + case 0x700: /* Reserved */ + case CLK_GATE_SCLK: /* reserved? */ + case CLK_GATE_IP: + case CLKOUT_CMU: + break; + default: + PRINT_ERROR("Bad offset: 0x%x\n", (int)offset); + } +} + +static const MemoryRegionOps exynos4210_cmu_ops = { + .read = exynos4210_cmu_read, + .write = exynos4210_cmu_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void clock_rate_changed(void *opaque) +{ + Exynos4210ClockState *cs = (Exynos4210ClockState *)opaque; + Object *cmu = object_resolve_path(exynos4210_cmu_path[cs->cmu_id], NULL); + Exynos4210CmuState *s = OBJECT_CHECK(Exynos4210CmuState, cmu, + TYPE_EXYNOS4210_CMU); + + PRINT_DEBUG("Clock %s was changed\n", cs->name); + + exynos4210_cmu_set_rate(s, cs->id); + +} + +static void exynos4210_cmu_reset(DeviceState *dev) +{ + Exynos4210CmuState *s = OBJECT_CHECK(Exynos4210CmuState, OBJECT(dev), + TYPE_EXYNOS4210_CMU); + int i, j; + uint32_t index = 0; + + for (i = 0; i < s->regs_number; i++) { + index = (s->regs[i].offset) / sizeof(uint32_t); + s->reg[index] = s->regs[i].reset_value; + } + + for (i = 0; i < s->clock_number; i++) { + + for (j = 0; j < SOURCES_NUMBER; j++) { + + if (s->clock[i].src_ids[j] == UNSPECIFIED_CLOCK) { + + if (j == 0) { + /* + * we have empty '.sources[]' array + */ + if (s->clock[i].src_id != UNSPECIFIED_CLOCK) { + + s->clock[i].src_ids[j] = s->clock[i].src_id; + + } else { + + if (s->clock[i].cmu_id != UNSPECIFIED_CMU) { + /* + * We haven't any defined sources for this clock. + * Error during definition of appropriate clock + * structure + */ + hw_error("exynos4210_cmu_reset:" + "There aren't any sources for %s clock!\n", + s->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 */ + } + } /* src_ids[j] == UNSPECIFIED_CLOCK */ + + Exynos4210ClockState *source = + exynos4210_clock_find(s->clock[i].src_ids[j]); + + if (source == NULL) { + hw_error("We aren't be able to find source clock %d " + "(requested for %s)\n", + s->clock[i].src_ids[j], s->clock[i].name); + } + + if (source->cmu_id != UNSPECIFIED_CMU) { + + exynos4210_register_clock_handler(clock_rate_changed, + s->clock[i].src_ids[j], &s->clock[i]); + } + } /* SOURCES_NUMBER */ + + exynos4210_cmu_set_rate(s, s->clock[i].id); + } + + PRINT_DEBUG("CMU %d reset completed\n", s->cmu_id); +} + + +static const VMStateDescription vmstate_exynos4210_cmu = { + .name = TYPE_EXYNOS4210_CMU, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + /* + * TODO: Maybe we should save Exynos4210ClockState structs as well + */ + VMSTATE_UINT32_ARRAY(reg, Exynos4210CmuState, + EXYNOS4210_CMU_REGS_MEM_SIZE), + VMSTATE_END_OF_LIST() + } +}; + +DeviceState *exynos4210_cmu_create(target_phys_addr_t addr, + Exynos4210Cmu cmu_id) +{ + DeviceState *dev; + SysBusDevice *bus; + + dev = qdev_create(NULL, TYPE_EXYNOS4210_CMU); + + qdev_prop_set_int32(dev, "cmu_id", cmu_id); + + bus = sysbus_from_qdev(dev); + qdev_init_nofail(dev); + if (addr != (target_phys_addr_t)-1) { + sysbus_mmio_map(bus, 0, addr); + } + + return dev; +} + +static int exynos4210_cmu_init(SysBusDevice *dev) +{ + Exynos4210CmuState *s = FROM_SYSBUS(Exynos4210CmuState, dev); + int i, n; + + memory_region_init_io(&s->iomem, &exynos4210_cmu_ops, s, + TYPE_EXYNOS4210_CMU, EXYNOS4210_CMU_REGS_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + switch (s->cmu_id) { + case EXYNOS4210_CMU_LEFTBUS: + s->regs = exynos4210_cmu_leftbus_regs; + s->regs_number = ARRAY_SIZE(exynos4210_cmu_leftbus_regs); + break; + case EXYNOS4210_CMU_RIGHTBUS: + s->regs = exynos4210_cmu_rightbus_regs; + s->regs_number = ARRAY_SIZE(exynos4210_cmu_rightbus_regs); + break; + case EXYNOS4210_CMU_TOP: + s->regs = exynos4210_cmu_top_regs; + s->regs_number = ARRAY_SIZE(exynos4210_cmu_top_regs); + break; + case EXYNOS4210_CMU_DMC: + s->regs = exynos4210_cmu_dmc_regs; + s->regs_number = ARRAY_SIZE(exynos4210_cmu_dmc_regs); + break; + case EXYNOS4210_CMU_CPU: + s->regs = exynos4210_cmu_cpu_regs; + s->regs_number = ARRAY_SIZE(exynos4210_cmu_cpu_regs); + break; + default: + hw_error("Wrong CMU: %d\n", s->cmu_id); + } + + for (i = 1, n = 0; i < EXYNOS4210_CLOCKS_NUMBER; i++) { + if (s->cmu_id == exynos4210_clock[i]->cmu_id) { + n++; + } + } + + s->clock = + (Exynos4210ClockState *)g_malloc0(n * sizeof(Exynos4210ClockState)); + + for (i = 1, s->clock_number = 0; i < EXYNOS4210_CLOCKS_NUMBER; i++) { + + if (s->cmu_id == exynos4210_clock[i]->cmu_id) { + + memcpy(&s->clock[s->clock_number], exynos4210_clock[i], + sizeof(Exynos4210ClockState)); + + QTAILQ_INIT(&s->clock[s->clock_number].clock_change_handler); + + PRINT_DEBUG("Clock %s was added to \"%s\"\n", + s->clock[s->clock_number].name, + exynos4210_cmu_path[s->cmu_id]); + + s->clock_number++; + + } + } + + object_property_add_child(object_get_root(), exynos4210_cmu_path[s->cmu_id], + OBJECT(dev), NULL); + + return 0; +} + +static Property exynos4210_cmu_properties[] = { + DEFINE_PROP_INT32("cmu_id", Exynos4210CmuState, cmu_id, UNSPECIFIED_CMU), + DEFINE_PROP_END_OF_LIST(), +}; + +static void exynos4210_cmu_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_cmu_init; + dc->reset = exynos4210_cmu_reset; + dc->props = exynos4210_cmu_properties; + dc->vmsd = &vmstate_exynos4210_cmu; +} + +static TypeInfo exynos4210_cmu_info = { + .name = TYPE_EXYNOS4210_CMU, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210CmuState), + .class_init = exynos4210_cmu_class_init, +}; + +static void exynos4210_cmu_register_types(void) +{ + type_register_static(&exynos4210_cmu_info); +} + +type_init(exynos4210_cmu_register_types) diff --git a/hw/exynos4210_g3d.c b/hw/exynos4210_g3d.c new file mode 100644 index 0000000..b66860e --- /dev/null +++ b/hw/exynos4210_g3d.c @@ -0,0 +1,833 @@ +/* + * Samsung exynos4210 MALI400 gpu (G3D) emulation + * + * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd. + * All rights reserved. + * + * Mitsyanko Igor + * + * 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, see . + */ + +#include "qemu-common.h" +#include "sysbus.h" + +/* Debug messages configuration */ +#define EXY_G3D_DEBUG 0 + +#if EXY_G3D_DEBUG == 0 + #define DPRINT_L1(fmt, args...) do { } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) do { } while (0) +#elif EXY_G3D_DEBUG == 1 + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) do { } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU G3D ERROR: "fmt, ## args); } while (0) +#else + #define DPRINT_L1(fmt, args...) \ + do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0) + #define DPRINT_L2(fmt, args...) \ + do {fprintf(stderr, "QEMU G3D: "fmt, ## args); } while (0) + #define DPRINT_ERROR(fmt, args...) \ + do {fprintf(stderr, "QEMU G3D ERROR: "fmt, ## args); } while (0) +#endif + +#define NUM_OF_PIXPROC 4 +#define EXYNOS4210_G3D_REG_MEM_SIZE 0x10000 +#define MALIGP_REGS_SIZE 0x98 +#define MALIPP_REGS_SIZE 0x10f0 +#define MALIMMU_REGS_SIZE 0x24 +#define MALI_L2CACHE_REGS_SIZE 0x30 + +#define GP_OFF_START 0x0000 +#define GP_OFF_END (GP_OFF_START + MALIGP_REGS_SIZE) +#define L2_OFF_START 0x1000 +#define L2_OFF_END (L2_OFF_START + MALI_L2CACHE_REGS_SIZE) +#define PMU_OFF_START 0x2000 +#define PMU_OFF_END 0x2FFC +#define GP_MMU_OFF_START 0x3000 +#define GP_MMU_OFF_END (GP_MMU_OFF_START + MALIMMU_REGS_SIZE) +#define PP0_MMU_OFF_START 0x4000 +#define PP0_MMU_OFF_END (PP0_MMU_OFF_START + MALIMMU_REGS_SIZE) +#define PP1_MMU_OFF_START 0x5000 +#define PP1_MMU_OFF_END (PP1_MMU_OFF_START + MALIMMU_REGS_SIZE) +#define PP2_MMU_OFF_START 0x6000 +#define PP2_MMU_OFF_END (PP2_MMU_OFF_START + MALIMMU_REGS_SIZE) +#define PP3_MMU_OFF_START 0x7000 +#define PP3_MMU_OFF_END (PP3_MMU_OFF_START + MALIMMU_REGS_SIZE) +#define PP0_OFF_START 0x8000 +#define PP0_OFF_END (PP0_OFF_START + MALIPP_REGS_SIZE) +#define PP1_OFF_START 0xA000 +#define PP1_OFF_END (PP1_OFF_START + MALIPP_REGS_SIZE) +#define PP2_OFF_START 0xC000 +#define PP2_OFF_END (PP2_OFF_START + MALIPP_REGS_SIZE) +#define PP3_OFF_START 0xE000 +#define PP3_OFF_END (PP3_OFF_START + MALIPP_REGS_SIZE) + +/************************************************** + * MALI geometry processor register defines + **************************************************/ + +#define MALIGP_REG_OFF_VSCL_START_ADDR 0x00 +#define MALIGP_REG_OFF_VSCL_END_ADDR 0x04 +#define MALIGP_REG_OFF_PLBUCL_START_ADDR 0x08 +#define MALIGP_REG_OFF_PLBUCL_END_ADDR 0x0c +#define MALIGP_REG_OFF_PLBU_ALLOC_START_ADDR 0x10 +#define MALIGP_REG_OFF_PLBU_ALLOC_END_ADDR 0x14 + +/* Command to geometry processor */ +#define MALIGP_REG_OFF_CMD 0x20 +#define MALIGP_REG_VAL_CMD_START_VS (1 << 0) +#define MALIGP_REG_VAL_CMD_START_PLBU (1 << 1) +#define MALIGP_REG_VAL_CMD_UPDATE_PLBU_ALLOC (1 << 4) +#define MALIGP_REG_VAL_CMD_RESET (1 << 5) +#define MALIGP_REG_VAL_CMD_FORCE_HANG (1 << 6) +#define MALIGP_REG_VAL_CMD_STOP_BUS (1 << 9) +#define MALIGP_REG_VAL_CMD_SOFT_RESET (1 << 10) + +/* Raw interrupt ststus - can not be masked by INT_MASK */ +#define MALIGP_REG_OFF_INT_RAWSTAT 0x24 +/* WO register - write 1 to clear irq flag in INT_RAWSTAT */ +#define MALIGP_REG_OFF_INT_CLEAR 0x28 +/* MASK interrupt requests in IRQ_STATUS */ +#define MALIGP_REG_OFF_INT_MASK 0x2C +/* Bits are set only if not masked and if bit in RAWSTAT is set */ +#define MALIGP_REG_OFF_INT_STAT 0x30 +/* Interrupt statuses of geometry processor */ +#define MALIGP_REG_VAL_IRQ_VS_END_CMD_LST (1 << 0) +#define MALIGP_REG_VAL_IRQ_PLBU_END_CMD_LST (1 << 1) +#define MALIGP_REG_VAL_IRQ_PLBU_OUT_OF_MEM (1 << 2) +#define MALIGP_REG_VAL_IRQ_VS_SEM_IRQ (1 << 3) +#define MALIGP_REG_VAL_IRQ_PLBU_SEM_IRQ (1 << 4) +#define MALIGP_REG_VAL_IRQ_HANG (1 << 5) +#define MALIGP_REG_VAL_IRQ_FORCE_HANG (1 << 6) +#define MALIGP_REG_VAL_IRQ_PERF_CNT_0_LIMIT (1 << 7) +#define MALIGP_REG_VAL_IRQ_PERF_CNT_1_LIMIT (1 << 8) +#define MALIGP_REG_VAL_IRQ_WRITE_BOUND_ERR (1 << 9) +#define MALIGP_REG_VAL_IRQ_SYNC_ERROR (1 << 10) +#define MALIGP_REG_VAL_IRQ_AXI_BUS_ERROR (1 << 11) +#define MALIGP_REG_VAL_IRQ_AXI_BUS_STOPPED (1 << 12) +#define MALIGP_REG_VAL_IRQ_VS_INVALID_CMD (1 << 13) +#define MALIGP_REG_VAL_IRQ_PLB_INVALID_CMD (1 << 14) +#define MALIGP_REG_VAL_IRQ_RESET_COMPLETED (1 << 19) +#define MALIGP_REG_VAL_IRQ_SEMAPHORE_UNDERFLOW (1 << 20) +#define MALIGP_REG_VAL_IRQ_SEMAPHORE_OVERFLOW (1 << 21) +#define MALIGP_REG_VAL_IRQ_PTR_ARRAY_OUT_OF_BOUNDS (1 << 22) +#define MALIGP_REG_VAL_IRQ_MASK_ALL \ + (\ + MALIGP_REG_VAL_IRQ_VS_END_CMD_LST | \ + MALIGP_REG_VAL_IRQ_PLBU_END_CMD_LST | \ + MALIGP_REG_VAL_IRQ_PLBU_OUT_OF_MEM | \ + MALIGP_REG_VAL_IRQ_VS_SEM_IRQ | \ + MALIGP_REG_VAL_IRQ_PLBU_SEM_IRQ | \ + MALIGP_REG_VAL_IRQ_HANG | \ + MALIGP_REG_VAL_IRQ_FORCE_HANG | \ + MALIGP_REG_VAL_IRQ_PERF_CNT_0_LIMIT | \ + MALIGP_REG_VAL_IRQ_PERF_CNT_1_LIMIT | \ + MALIGP_REG_VAL_IRQ_WRITE_BOUND_ERR | \ + MALIGP_REG_VAL_IRQ_SYNC_ERROR | \ + MALIGP_REG_VAL_IRQ_AXI_BUS_ERROR | \ + MALIGP_REG_VAL_IRQ_AXI_BUS_STOPPED | \ + MALIGP_REG_VAL_IRQ_VS_INVALID_CMD | \ + MALIGP_REG_VAL_IRQ_PLB_INVALID_CMD | \ + MALIGP_REG_VAL_IRQ_RESET_COMPLETED | \ + MALIGP_REG_VAL_IRQ_SEMAPHORE_UNDERFLOW | \ + MALIGP_REG_VAL_IRQ_SEMAPHORE_OVERFLOW | \ + MALIGP_REG_VAL_IRQ_PTR_ARRAY_OUT_OF_BOUNDS) + + +#define MALIGP_REG_OFF_WRITE_BOUND_LOW 0x34 +#define MALIGP_REG_OFF_PERF_CNT_0_ENABLE 0x3C +#define MALIGP_REG_OFF_PERF_CNT_1_ENABLE 0x40 +#define MALIGP_REG_OFF_PERF_CNT_0_SRC 0x44 +#define MALIGP_REG_OFF_PERF_CNT_1_SRC 0x48 +#define MALIGP_REG_OFF_PERF_CNT_0_VALUE 0x4C +#define MALIGP_REG_OFF_PERF_CNT_1_VALUE 0x50 +#define MALIGP_REG_OFF_STATUS 0x68 + +/* Geometry processor version register */ +#define MALIGP_REG_OFF_VERSION 0x6C +#define MALI400_GP_PRODUCT_ID 0xB07 +#define MALIGP_VERSION_DEFAULT (MALI400_GP_PRODUCT_ID << 16) + +#define MALIGP_REG_OFF_VSCL_START_ADDR_READ 0x80 +#define MALIGP_REG_OFF_PLBCL_START_ADDR_READ 0x84 +#define MALIGP_CONTR_AXI_BUS_ERROR_STAT 0x94 +#define MALIGP_REGISTER_ADDRESS_SPACE_SIZE 0x98 + + +/************************************************** + * MALI pixel processor registers defines + **************************************************/ + +/* MALI Pixel Processor version register */ +#define MALIPP_REG_OFF_VERSION 0x1000 +#define MALI400_PP_PRODUCT_ID 0xCD07 +#define MALIPP_MGMT_VERSION_DEFAULT (MALI400_PP_PRODUCT_ID << 16) + +#define MALIPP_REG_OFF_CURRENT_REND_LIST_ADDR 0x1004 +#define MALIPP_REG_OFF_STATUS 0x1008 + +/* Control and commands register */ +#define MALIPP_REG_OFF_CTRL_MGMT 0x100c +#define MALIPP_REG_VAL_CTRL_MGMT_STOP_BUS (1 << 0) +#define MALIPP_REG_VAL_CTRL_MGMT_FORCE_RESET (1 << 5) +#define MALIPP_REG_VAL_CTRL_MGMT_START_RENDERING (1 << 6) +#define MALIPP_REG_VAL_CTRL_MGMT_SOFT_RESET (1 << 7) + +/* Interrupt control registers */ +#define MALIPP_REG_OFF_INT_RAWSTAT 0x1020 +#define MALIPP_REG_OFF_INT_CLEAR 0x1024 +#define MALIPP_REG_OFF_INT_MASK 0x1028 +#define MALIPP_REG_OFF_INT_STATUS 0x102c +/* Interrupt statuses */ +#define MALIPP_REG_VAL_IRQ_END_OF_FRAME (1 << 0) +#define MALIPP_REG_VAL_IRQ_END_OF_TILE (1 << 1) +#define MALIPP_REG_VAL_IRQ_HANG (1 << 2) +#define MALIPP_REG_VAL_IRQ_FORCE_HANG (1 << 3) +#define MALIPP_REG_VAL_IRQ_BUS_ERROR (1 << 4) +#define MALIPP_REG_VAL_IRQ_BUS_STOP (1 << 5) +#define MALIPP_REG_VAL_IRQ_CNT_0_LIMIT (1 << 6) +#define MALIPP_REG_VAL_IRQ_CNT_1_LIMIT (1 << 7) +#define MALIPP_REG_VAL_IRQ_WRITE_BOUNDARY_ERROR (1 << 8) +#define MALIPP_REG_VAL_IRQ_INVALID_PLIST_COMMAND (1 << 9) +#define MALIPP_REG_VAL_IRQ_CALL_STACK_UNDERFLOW (1 << 10) +#define MALIPP_REG_VAL_IRQ_CALL_STACK_OVERFLOW (1 << 11) +#define MALIPP_REG_VAL_IRQ_RESET_COMPLETED (1 << 12) + + +#define MALIPP_REG_OFF_WRITE_BOUNDARY_LOW 0x1044 +#define MALIPP_REG_OFF_BUS_ERROR_STATUS 0x1050 +#define MALIPP_REG_OFF_PERF_CNT_0_ENABLE 0x1080 +#define MALIPP_REG_OFF_PERF_CNT_0_SRC 0x1084 +#define MALIPP_REG_OFF_PERF_CNT_0_VALUE 0x108c +#define MALIPP_REG_OFF_PERF_CNT_1_ENABLE 0x10a0 +#define MALIPP_REG_OFF_PERF_CNT_1_SRC 0x10a4 +#define MALIPP_REG_OFF_PERF_CNT_1_VALUE 0x10ac +#define MALIPP_REG_SIZEOF_REGISTER_BANK 0x10f0 + +/************************************************** + * MALI MMU register defines + **************************************************/ + +/* Current Page Directory Pointer */ +#define MALI_MMU_REG_DTE_ADDR 0x0000 + +/* Status of the MMU */ +#define MALI_MMU_REG_STATUS 0x0004 +/* MALI MMU ststus bits */ +#define MALI_MMU_STATUS_PAGING_ENABLED (1 << 0) +#define MALI_MMU_STATUS_PAGE_FAULT_ACTIVE (1 << 1) +#define MALI_MMU_STATUS_STALL_ACTIVE (1 << 2) +#define MALI_MMU_STATUS_IDLE (1 << 3) +#define MALI_MMU_STATUS_REPLAY_BUFFER_EMPTY (1 << 4) +#define MALI_MMU_STATUS_PAGE_FAULT_IS_WRITE (1 << 5) + +/* Command register, used to control the MMU */ +#define MALI_MMU_REG_COMMAND 0x0008 +/* MALI MMU commands */ +/* Enable paging (memory translation) */ +#define MALI_MMU_COMMAND_ENABLE_PAGING 0x00 +/* Disable paging (memory translation) */ +#define MALI_MMU_COMMAND_DISABLE_PAGING 0x01 +/* Enable stall on page fault */ +#define MALI_MMU_COMMAND_ENABLE_STALL 0x02 +/* Disable stall on page fault */ +#define MALI_MMU_COMMAND_DISABLE_STALL 0x03 +/* Zap the entire page table cache */ +#define MALI_MMU_COMMAND_ZAP_CACHE 0x04 +/* Page fault processed */ +#define MALI_MMU_COMMAND_PAGE_FAULT_DONE 0x05 +/* Reset the MMU back to power-on settings */ +#define MALI_MMU_COMMAND_SOFT_RESET 0x06 + +/* Logical address of the last page fault */ +#define MALI_MMU_REG_PAGE_FAULT_ADDR 0x000C + +/* Used to invalidate the mapping of a single page from the MMU */ +#define MALI_MMU_REG_ZAP_ONE_LINE 0x0010 + +/* Raw interrupt status, all interrupts visible */ +#define MALI_MMU_REG_INT_RAWSTAT 0x0014 +/* Indicate to the MMU that the interrupt has been received */ +#define MALI_MMU_REG_INT_CLEAR 0x0018 +/* Enable/disable types of interrupts */ +#define MALI_MMU_REG_INT_MASK 0x001C +/* Interrupt status based on the mask */ +#define MALI_MMU_REG_INT_STATUS 0x0020 +/* MALI MMU interrupt registers bits */ +/* A page fault occured */ +#define MALI_MMU_INT_PAGE_FAULT 0x01 +/* A bus read error occured */ +#define MALI_MMU_INT_READ_BUS_ERROR 0x02 + + +/************************************************** + * MALI L2 cache register defines + **************************************************/ + +/* MALI L2 cache status bits */ +#define MALI_L2CACHE_REG_STATUS 0x0008 +/* Command handler of L2 cache is busy */ +#define MALI_L2CACHE_STATUS_COMMAND_BUSY 0x01 +/* L2 cache is busy handling data requests */ +#define MALI_L2CACHE_STATUS_DATA_BUSY 0x02 + +/* Misc cache commands, e.g. clear */ +#define MALI_L2CACHE_REG_COMMAND 0x0010 +/* Clear the entire cache */ +#define MALI_L2CACHE_CMD_CLEAR_ALL 0x01 + +#define MALI_L2CACHE_REG_CLEAR_PAGE 0x0014 + +/* Enable misc cache features */ +#define MALI_L2CACHE_REG_ENABLE 0x001C +/* Default state of enable register */ +#define MALI_L2CACHE_ENABLE_DEFAULT 0x0 +/* Permit cacheable accesses */ +#define MALI_L2CACHE_ENABLE_ACCESS 0x01 +/* Permit cache read allocate */ +#define MALI_L2CACHE_ENABLE_READ_ALLOCATE 0x02 + +#define MALI_L2CACHE_REG_PERFCNT_SRC0 0x0020 +#define MALI_L2CACHE_REG_PERFCNT_VAL0 0x0024 +#define MALI_L2CACHE_REG_PERFCNT_SRC1 0x0028 +#define MALI_L2CACHE_REG_PERFCNT_VAL1 0x002C + +typedef struct GeometryProc { + uint32_t vscl_start; + uint32_t vscl_end; + uint32_t plbucl_start; + uint32_t plbucl_end; + uint32_t plbu_alloc_start; + uint32_t plbu_alloc_end; + uint32_t cmd; + uint32_t int_rawstat; +/* uint32_t int_clear; Write only register */ + uint32_t int_mask; + uint32_t int_stat; + uint32_t write_bound; + uint32_t perfcnt0_en; + uint32_t perfcnt1_en; + uint32_t perfcnt0_src; + uint32_t perfcnt1_src; + uint32_t perfcnt0_value; + uint32_t perfcnt1_value; + uint32_t status; +/* uint32_t version; RO register */ + uint32_t vscl_start_read; + uint32_t plbcl_start_read; + uint32_t axi_error; + uint32_t addr_space_size; + + qemu_irq irq_gp; /* geometry processor interrupt */ +} GeometryProc; + +typedef struct PixelProc { +/* uint32_t version; RO register */ + uint32_t cur_rend_list; + uint32_t status; + uint32_t ctrl_mgmt; + uint32_t int_rawstat; +/* uint32_t int_clear; Write only register */ + uint32_t int_mask; + uint32_t int_stat; + uint32_t write_bound; + uint32_t bus_error; + uint32_t perfcnt0_en; + uint32_t perfcnt0_src; + uint32_t perfcnt0_value; + uint32_t perfcnt1_en; + uint32_t perfcnt1_src; + uint32_t perfcnt1_value; + uint32_t sizeof_regs; + + qemu_irq irq_pp; /* pixel processor interrupt */ +} PixelProc; + +typedef struct MaliMMU { + uint32_t dte_addr; + uint32_t status; + uint32_t command; + uint32_t page_fault_addr; + uint32_t zap_one_line; + uint32_t int_rawstat; + uint32_t int_mask; + uint32_t int_status; + + qemu_irq irq_mmu; /* MALI MMU interrupt */ +} MaliMMU; + +typedef struct MaliL2Cache { + uint32_t status; + uint32_t command; + uint32_t clear_page; + uint32_t enable; + uint32_t perfcnt_src0; + uint32_t perfcnt_val0; + uint32_t perfcnt_src1; + uint32_t perfcnt_val1; +} MaliL2Cache; + +typedef struct Exynos4210G3DState { + SysBusDevice busdev; + MemoryRegion iomem; + + GeometryProc gp; /* Geometry processor */ + PixelProc pp[NUM_OF_PIXPROC]; + MaliMMU gp_mmu; + MaliMMU pp_mmu[NUM_OF_PIXPROC]; + MaliL2Cache l2_cache; + qemu_irq irq_pmu; /* power unit interrupt */ +} Exynos4210G3DState; + +/* TODO maybe reset functions must do something else besides clearing reg-s */ +static inline void exynos4210_maligp_reset(GeometryProc *gp) +{ + memset(gp, 0, offsetof(GeometryProc, irq_gp)); +} + +static inline void exynos4210_malipp_reset(PixelProc *pp) +{ + memset(pp, 0, offsetof(PixelProc, irq_pp)); +} + +static inline void exynos4210_malimmu_reset(MaliMMU *mmu) +{ + memset(mmu, 0, offsetof(MaliMMU, irq_mmu)); +} + +static inline void exynos4210_mali_l2cache_reset(MaliL2Cache *l2cach) +{ + memset(l2cach, 0, sizeof(MaliL2Cache)); +} + +static void exynos4210_g3d_reset(DeviceState *d) +{ + Exynos4210G3DState *s = DO_UPCAST(Exynos4210G3DState, busdev.qdev, d); + int i; + + exynos4210_maligp_reset(&s->gp); + exynos4210_malimmu_reset(&s->gp_mmu); + exynos4210_mali_l2cache_reset(&s->l2_cache); + for (i = 0; i < NUM_OF_PIXPROC; i++) { + exynos4210_malipp_reset(&s->pp[i]); + exynos4210_malimmu_reset(&s->pp_mmu[i]); + } +} + +/* Geometry processor register map read */ +static inline uint32_t exynos4210_maligp_read(GeometryProc *gp, + target_phys_addr_t offset) +{ + DPRINT_L2("MALI GEOMETRY PROCESSOR: read offset 0x" + TARGET_FMT_plx "\n", offset); + + switch (offset) { + /* Geometry processor */ + case MALIGP_REG_OFF_CMD: + return gp->cmd; + case MALIGP_REG_OFF_INT_RAWSTAT: + return gp->int_rawstat; + case MALIGP_REG_OFF_INT_MASK: + return gp->int_mask; + case MALIGP_REG_OFF_INT_STAT: + return gp->int_stat; + case MALIGP_REG_OFF_VERSION: + return MALIGP_VERSION_DEFAULT; + case MALIGP_REG_OFF_INT_CLEAR: + DPRINT_L1("MALI GEOM PROC: read from WO register! Offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + default: + DPRINT_L1("MALI GEOM PROC: read from non-existing register! " + "Offset 0x" TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* Geometry processor register map write */ +static uint32_t exynos4210_malipp_read(PixelProc *pp, target_phys_addr_t offset) +{ + DPRINT_L2("read offset 0x" TARGET_FMT_plx "\n", offset); + + switch (offset) { + case MALIPP_REG_OFF_VERSION: + return MALIPP_MGMT_VERSION_DEFAULT; + case MALIPP_REG_OFF_CTRL_MGMT: + return pp->ctrl_mgmt; + case MALIPP_REG_OFF_INT_RAWSTAT: + return pp->int_rawstat; + case MALIPP_REG_OFF_INT_MASK: + return pp->int_mask; + case MALIPP_REG_OFF_INT_STATUS: + return pp->int_stat; + case MALIPP_REG_OFF_INT_CLEAR: + DPRINT_L1("MALI PIX PROC: read from WO register! Offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + default: + DPRINT_L1("MALI PIX PROC: read from non-existing register! Offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* MALI MMU register map read */ +static uint32_t exynos4210_malimmu_read(MaliMMU *mmu, target_phys_addr_t offset) +{ + DPRINT_L2("read offset 0x" TARGET_FMT_plx "\n", offset); + + switch (offset) { + case MALI_MMU_REG_DTE_ADDR: + return mmu->dte_addr; + case MALI_MMU_REG_COMMAND: + return mmu->command; + case MALI_MMU_REG_INT_RAWSTAT: + return mmu->int_rawstat; + case MALI_MMU_REG_INT_MASK: + return mmu->int_mask; + case MALI_MMU_REG_INT_STATUS: + return mmu->int_status; + case MALI_MMU_REG_INT_CLEAR: + DPRINT_L1("MALI MMU: read from WO register! Offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + default: + DPRINT_L1("MALI MMU: read from non-existing register! Offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* MALI L2 cache register map read */ +static uint32_t exynos4210_mali_l2cache_read(MaliL2Cache *cache, + target_phys_addr_t offset) +{ + DPRINT_L2("MALI L2 CACHE: read offset 0x" + TARGET_FMT_plx "\n", offset); + + switch (offset) { + case MALI_L2CACHE_REG_STATUS: + return cache->status; + case MALI_L2CACHE_REG_COMMAND: + return cache->command; + case MALI_L2CACHE_REG_ENABLE: + return cache->enable; + default: + return 0; + } +} + +static uint64_t exynos4210_g3d_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210G3DState *s = (Exynos4210G3DState *)opaque; + + switch (offset) { + case GP_OFF_START ... GP_OFF_END: + return exynos4210_maligp_read(&s->gp, offset - GP_OFF_START); + case PP0_OFF_START ... PP0_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#0: "); + return exynos4210_malipp_read(&s->pp[0], offset - PP0_OFF_START); + case PP1_OFF_START ... PP1_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#1: "); + return exynos4210_malipp_read(&s->pp[1], offset - PP1_OFF_START); + case PP2_OFF_START ... PP2_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#2: "); + return exynos4210_malipp_read(&s->pp[2], offset - PP2_OFF_START); + case PP3_OFF_START ... PP3_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#3: "); + return exynos4210_malipp_read(&s->pp[3], offset - PP3_OFF_START); + case GP_MMU_OFF_START ... GP_MMU_OFF_END: + DPRINT_L2("MALI GEOM PROC MMU: "); + return exynos4210_malimmu_read(&s->gp_mmu, offset - GP_MMU_OFF_START); + case PP0_MMU_OFF_START ... PP0_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#0 MMU: "); + return exynos4210_malimmu_read(&s->pp_mmu[0], + offset - PP0_MMU_OFF_START); + case PP1_MMU_OFF_START ... PP1_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#1 MMU: "); + return exynos4210_malimmu_read(&s->pp_mmu[1], + offset - PP1_MMU_OFF_START); + case PP2_MMU_OFF_START ... PP2_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#2 MMU: "); + return exynos4210_malimmu_read(&s->pp_mmu[2], + offset - PP2_MMU_OFF_START); + case PP3_MMU_OFF_START ... PP3_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#3 MMU: "); + return exynos4210_malimmu_read(&s->pp_mmu[3], + offset - PP3_MMU_OFF_START); + case L2_OFF_START ... L2_OFF_END: + return exynos4210_mali_l2cache_read(&s->l2_cache, + offset - L2_OFF_START); + default: + DPRINT_L1("MALI UNKNOWN REGION: read offset 0x" + TARGET_FMT_plx "\n", offset); + return 0; + } +} + +/* Geometry processor register map write */ +static void exynos4210_maligp_write(GeometryProc *gp, + target_phys_addr_t offset, uint32_t val) +{ + DPRINT_L2("MALI GEOMETRY PROCESSOR: write offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + + switch (offset) { + case MALIGP_REG_OFF_CMD: + gp->cmd = val; + if (val & MALIGP_REG_VAL_CMD_SOFT_RESET) { + exynos4210_maligp_reset(gp); + gp->int_rawstat |= MALIGP_REG_VAL_IRQ_RESET_COMPLETED; + gp->int_stat = gp->int_rawstat & gp->int_mask; + qemu_set_irq(gp->irq_gp, gp->int_stat); + } + break; + case MALIGP_REG_OFF_INT_CLEAR: + gp->int_rawstat &= ~val; + gp->int_stat = gp->int_rawstat & gp->int_mask; + qemu_irq_lower(gp->irq_gp); + break; + case MALIGP_REG_OFF_INT_MASK: + gp->int_mask = val; + break; + case MALIGP_REG_OFF_VERSION: case MALIGP_REG_OFF_INT_RAWSTAT: + case MALIGP_REG_OFF_INT_STAT: + DPRINT_L1("MALI GEOM PROC: writing to RO register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + default: + DPRINT_L1("MALI GEOM PROC: writing to non-existing register! " + "Offset 0x" TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + } +} + +/* Pixel processor register map write */ +static void exynos4210_malipp_write(PixelProc *pp, + target_phys_addr_t offset, uint32_t val) +{ + DPRINT_L2("write offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + + switch (offset) { + case MALIPP_REG_OFF_CTRL_MGMT: + pp->ctrl_mgmt = val; + if (val & MALIPP_REG_VAL_CTRL_MGMT_SOFT_RESET) { + exynos4210_malipp_reset(pp); + pp->int_rawstat |= MALIPP_REG_VAL_IRQ_RESET_COMPLETED; + pp->int_stat = pp->int_rawstat & pp->int_mask; + qemu_set_irq(pp->irq_pp, pp->int_stat); + } + break; + case MALIPP_REG_OFF_INT_CLEAR: + pp->int_rawstat &= ~val; + pp->int_stat = pp->int_rawstat & pp->int_mask; + qemu_irq_lower(pp->irq_pp); + break; + case MALIPP_REG_OFF_INT_MASK: + pp->int_mask = val; + break; + case MALIPP_REG_OFF_INT_STATUS: case MALIPP_REG_OFF_VERSION: + case MALIPP_REG_OFF_INT_RAWSTAT: + DPRINT_L1("MALI PIX PROC: writing to RO register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + default: + DPRINT_L1("MALI PIX PROC: writing to non-existing register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + } +} + +/* MALI MMU register map write */ +static void exynos4210_malimmu_write(MaliMMU *mmu, + target_phys_addr_t offset, uint32_t val) +{ + DPRINT_L2("write offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + + switch (offset) { + case MALI_MMU_REG_DTE_ADDR: + mmu->dte_addr = val; + break; + case MALI_MMU_REG_COMMAND: + mmu->command = val; + if (val & MALI_MMU_COMMAND_SOFT_RESET) { + exynos4210_malimmu_reset(mmu); + } + break; + case MALI_MMU_REG_INT_CLEAR: + mmu->int_rawstat &= ~val; + mmu->int_status = mmu->int_rawstat & mmu->int_mask; + qemu_irq_lower(mmu->irq_mmu); + break; + case MALI_MMU_REG_INT_MASK: + mmu->int_mask = val; + break; + case MALI_MMU_REG_INT_STATUS: case MALI_MMU_REG_INT_RAWSTAT: + DPRINT_L1("MALI MMU: writing to RO register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + default: + DPRINT_L1("MALI MMU: writing to non-existing register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + } +} + +/* MALI L2 cache register map write */ +static void exynos4210_mali_l2cache_write(MaliL2Cache *cache, + target_phys_addr_t offset, uint32_t val) +{ + DPRINT_L2("MALI L2 CACHE: write offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + + switch (offset) { + case MALI_L2CACHE_REG_COMMAND: + cache->command = val; + break; + case MALI_L2CACHE_REG_ENABLE: + cache->enable = val; + break; + case MALI_L2CACHE_REG_STATUS: + DPRINT_L1("MALI L2 CACHE: writing to RO register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + default: + DPRINT_L1("MALI L2 CACHE: writing non-existing register! Offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + } +} + +static void exynos4210_g3d_write(void *opaque, target_phys_addr_t offset, + uint64_t val, unsigned size) +{ + Exynos4210G3DState *s = (Exynos4210G3DState *)opaque; + + switch (offset) { + case GP_OFF_START ... GP_OFF_END: + exynos4210_maligp_write(&s->gp, offset - GP_OFF_START, val); + break; + case PP0_OFF_START ... PP0_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#0: "); + exynos4210_malipp_write(&s->pp[0], offset - PP0_OFF_START, val); + break; + case PP1_OFF_START ... PP1_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#1: "); + exynos4210_malipp_write(&s->pp[1], offset - PP1_OFF_START, val); + break; + case PP2_OFF_START ... PP2_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#2: "); + exynos4210_malipp_write(&s->pp[2], offset - PP2_OFF_START, val); + break; + case PP3_OFF_START ... PP3_OFF_END: + DPRINT_L2("MALI PIXEL PROCESSOR#3: "); + exynos4210_malipp_write(&s->pp[3], offset - PP3_OFF_START, val); + break; + case GP_MMU_OFF_START ... GP_MMU_OFF_END: + DPRINT_L2("MALI GEOM PROC MMU: "); + exynos4210_malimmu_write(&s->gp_mmu, offset - GP_MMU_OFF_START, val); + break; + case PP0_MMU_OFF_START ... PP0_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#0 MMU: "); + exynos4210_malimmu_write(&s->pp_mmu[0], + offset - PP0_MMU_OFF_START, val); + break; + case PP1_MMU_OFF_START ... PP1_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#1 MMU: "); + exynos4210_malimmu_write(&s->pp_mmu[1], + offset - PP1_MMU_OFF_START, val); + break; + case PP2_MMU_OFF_START ... PP2_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#2 MMU: "); + exynos4210_malimmu_write(&s->pp_mmu[2], + offset - PP2_MMU_OFF_START, val); + break; + case PP3_MMU_OFF_START ... PP3_MMU_OFF_END: + DPRINT_L2("MALI PIX PROC#3 MMU: "); + exynos4210_malimmu_write(&s->pp_mmu[3], + offset - PP3_MMU_OFF_START, val); + break; + case L2_OFF_START ... L2_OFF_END: + exynos4210_mali_l2cache_write(&s->l2_cache, offset - L2_OFF_START, val); + break; + default: + DPRINT_L1("MALI UNKNOWN REGION: write offset 0x" + TARGET_FMT_plx ", value=%d(0x%x) \n", offset, val, val); + break; + } +} + +static const MemoryRegionOps exynos4210_g3d_mmio_ops = { + .read = exynos4210_g3d_read, + .write = exynos4210_g3d_write, + .valid = { + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static int exynos4210_g3d_init(SysBusDevice *dev) +{ + Exynos4210G3DState *s = FROM_SYSBUS(Exynos4210G3DState, dev); + + sysbus_init_irq(dev, &s->pp[0].irq_pp); + sysbus_init_irq(dev, &s->pp[1].irq_pp); + sysbus_init_irq(dev, &s->pp[2].irq_pp); + sysbus_init_irq(dev, &s->pp[3].irq_pp); + sysbus_init_irq(dev, &s->gp.irq_gp); + sysbus_init_irq(dev, &s->irq_pmu); + sysbus_init_irq(dev, &s->pp_mmu[0].irq_mmu); + sysbus_init_irq(dev, &s->pp_mmu[1].irq_mmu); + sysbus_init_irq(dev, &s->pp_mmu[2].irq_mmu); + sysbus_init_irq(dev, &s->pp_mmu[3].irq_mmu); + sysbus_init_irq(dev, &s->gp_mmu.irq_mmu); + + memory_region_init_io(&s->iomem, &exynos4210_g3d_mmio_ops, s, + "exynos4210.g3d", EXYNOS4210_G3D_REG_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void exynos4210_g3d_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + dc->reset = exynos4210_g3d_reset; + k->init = exynos4210_g3d_init; +} + +static TypeInfo exynos4210_g3d_info = { + .name = "exynos4210.g3d", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210G3DState), + .class_init = exynos4210_g3d_class_init, +}; + +static void exynos4210_g3d_register_devices(void) +{ + type_register_static(&exynos4210_g3d_info); +} + +type_init(exynos4210_g3d_register_devices) diff --git a/hw/exynos4210_gic.c b/hw/exynos4210_gic.c index e1b215e..ec2a9e0 100644 --- a/hw/exynos4210_gic.c +++ b/hw/exynos4210_gic.c @@ -106,7 +106,8 @@ enum ExtInt { EXT_GIC_ID_EXTINT12, EXT_GIC_ID_EXTINT13, EXT_GIC_ID_EXTINT14, - EXT_GIC_ID_EXTINT15 + EXT_GIC_ID_EXTINT15, + EXT_GIC_ID_EXTINT16_31, }; /* @@ -160,9 +161,19 @@ combiner_grp_to_gic_id[64-EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = { { EXT_GIC_ID_MIXER }, /* int combiner group 37 */ { EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6, - EXT_GIC_ID_EXTINT7 }, - /* groups 38-50 */ - { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, + EXT_GIC_ID_EXTINT7 }, + /* int combiner group 38 */ + { EXT_GIC_ID_EXTINT8, EXT_GIC_ID_EXTINT9, EXT_GIC_ID_EXTINT10, + EXT_GIC_ID_EXTINT11, EXT_GIC_ID_EXTINT12, EXT_GIC_ID_EXTINT13, + EXT_GIC_ID_EXTINT14, EXT_GIC_ID_EXTINT15 }, + /* int combiner groups 39-43 */ + { EXT_GIC_ID_EXTINT16_31 }, + { EXT_GIC_ID_EXTINT0 }, + { EXT_GIC_ID_EXTINT1 }, + { EXT_GIC_ID_EXTINT2 }, + { EXT_GIC_ID_EXTINT3 }, + /* groups 44-50 */ + { }, { }, { }, { }, { }, { }, { }, /* int combiner group 51 */ { EXT_GIC_ID_MCT_L0, 0, 0, 0, EXT_GIC_ID_MCT_G0, EXT_GIC_ID_MCT_G1 }, /* group 52 */ @@ -369,18 +380,29 @@ type_init(exynos4210_gic_register_types) typedef struct { SysBusDevice busdev; - qemu_irq pic_irq[EXYNOS4210_NCPUS]; /* output IRQs to PICs */ - uint32_t gpio_level[EXYNOS4210_IRQ_GATE_NINPUTS]; /* Input levels */ + qemu_irq out[QDEV_MAX_IRQ]; /* output IRQs */ + uint32_t n_out; /* outputs amount */ + uint32_t gpio_level[QDEV_MAX_IRQ]; /* Input levels */ + uint32_t n_in; /* inputs amount */ + uint32_t group_size; /* input group size */ } Exynos4210IRQGateState; +static Property exynos4210_irq_gate_properties[] = { + DEFINE_PROP_UINT32("n_out", Exynos4210IRQGateState, n_out, 1), + DEFINE_PROP_UINT32("n_in", Exynos4210IRQGateState, n_in, 1), + DEFINE_PROP_END_OF_LIST(), +}; + static const VMStateDescription vmstate_exynos4210_irq_gate = { .name = "exynos4210.irq_gate", .version_id = 1, .minimum_version_id = 1, .minimum_version_id_old = 1, .fields = (VMStateField[]) { - VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState, - EXYNOS4210_IRQ_GATE_NINPUTS), + VMSTATE_UINT32(n_out, Exynos4210IRQGateState), + VMSTATE_UINT32_ARRAY(gpio_level, Exynos4210IRQGateState, QDEV_MAX_IRQ), + VMSTATE_UINT32(n_in, Exynos4210IRQGateState), + VMSTATE_UINT32(group_size, Exynos4210IRQGateState), VMSTATE_END_OF_LIST() } }; @@ -390,25 +412,24 @@ static void exynos4210_irq_gate_handler(void *opaque, int irq, int level) { Exynos4210IRQGateState *s = (Exynos4210IRQGateState *)opaque; - uint32_t odd, even; - - if (irq & 1) { - odd = irq; - even = irq & ~1; - } else { - even = irq; - odd = irq | 1; - } + uint32_t n_out, n_group, i; + + assert(irq < s->n_in); + + n_out = irq / s->group_size; + n_group = n_out * s->group_size; - assert(irq < EXYNOS4210_IRQ_GATE_NINPUTS); s->gpio_level[irq] = level; - if (s->gpio_level[odd] >= 1 || s->gpio_level[even] >= 1) { - qemu_irq_raise(s->pic_irq[even >> 1]); - } else { - qemu_irq_lower(s->pic_irq[even >> 1]); + for (i = 0; i < s->group_size; i++) { + if (s->gpio_level[n_group + i] >= 1) { + qemu_irq_raise(s->out[n_out]); + return; + } } + qemu_irq_lower(s->out[n_out]); + return; } @@ -428,14 +449,19 @@ static int exynos4210_irq_gate_init(SysBusDevice *dev) Exynos4210IRQGateState *s = FROM_SYSBUS(Exynos4210IRQGateState, dev); + /* Gate will make each input group of size n_in / n_out */ + assert((s->n_in % s->n_out) == 0); + + s->group_size = s->n_in / s->n_out; + /* Allocate general purpose input signals and connect a handler to each of * them */ qdev_init_gpio_in(&s->busdev.qdev, exynos4210_irq_gate_handler, - EXYNOS4210_IRQ_GATE_NINPUTS); + s->n_in); - /* Connect SysBusDev irqs to device specific irqs */ - for (i = 0; i < EXYNOS4210_NCPUS; i++) { - sysbus_init_irq(dev, &s->pic_irq[i]); + /* Pass gate outs to SysBusDev */ + for (i = 0; i < s->n_out; i++) { + sysbus_init_irq(dev, &s->out[i]); } return 0; @@ -449,6 +475,7 @@ static void exynos4210_irq_gate_class_init(ObjectClass *klass, void *data) k->init = exynos4210_irq_gate_init; dc->reset = exynos4210_irq_gate_reset; dc->vmsd = &vmstate_exynos4210_irq_gate; + dc->props = exynos4210_irq_gate_properties; } static TypeInfo exynos4210_irq_gate_info = { diff --git a/hw/exynos4210_i2c.c b/hw/exynos4210_i2c.c new file mode 100644 index 0000000..fa10bfb --- /dev/null +++ b/hw/exynos4210_i2c.c @@ -0,0 +1,334 @@ +/* + * Exynos4210 I2C Bus Serial Interface Emulation + * + * Copyright (C) 2012 Samsung Electronics Co Ltd. + * Maksim Kozlov, + * 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, see . + * + */ + +#include "qemu-timer.h" +#include "sysbus.h" +#include "i2c.h" + +#ifndef EXYNOS4_I2C_DEBUG +#define EXYNOS4_I2C_DEBUG 0 +#endif + +#define TYPE_EXYNOS4_I2C "exynos4210.i2c" +#define EXYNOS4_I2C(obj) \ + OBJECT_CHECK(Exynos4210I2CState, (obj), TYPE_EXYNOS4_I2C) + +/* Exynos4210 I2C memory map */ +#define EXYNOS4_I2C_MEM_SIZE 0x14 +#define I2CCON_ADDR 0x00 /* control register */ +#define I2CSTAT_ADDR 0x04 /* control/status register */ +#define I2CADD_ADDR 0x08 /* address register */ +#define I2CDS_ADDR 0x0c /* data shift register */ +#define I2CLC_ADDR 0x10 /* line control register */ + +#define I2CCON_ACK_GEN (1 << 7) +#define I2CCON_INTRS_EN (1 << 5) +#define I2CCON_INT_PEND (1 << 4) + +#define EXYNOS4_I2C_MODE(reg) (((reg) >> 6) & 3) +#define I2C_IN_MASTER_MODE(reg) (((reg) >> 6) & 2) +#define I2CMODE_MASTER_Rx 0x2 +#define I2CMODE_MASTER_Tx 0x3 +#define I2CSTAT_LAST_BIT (1 << 0) +#define I2CSTAT_OUTPUT_EN (1 << 4) +#define I2CSTAT_START_BUSY (1 << 5) + + +#if EXYNOS4_I2C_DEBUG +#define DPRINT(fmt, args...) \ + do { fprintf(stderr, "QEMU I2C: "fmt, ## args); } while (0) + +static const char *exynos4_i2c_get_regname(unsigned offset) +{ + switch (offset) { + case I2CCON_ADDR: + return "I2CCON"; + case I2CSTAT_ADDR: + return "I2CSTAT"; + case I2CADD_ADDR: + return "I2CADD"; + case I2CDS_ADDR: + return "I2CDS"; + case I2CLC_ADDR: + return "I2CLC"; + default: + return "[?]"; + } +} + +#else +#define DPRINT(fmt, args...) do { } while (0) +#endif + +typedef struct Exynos4210I2CState { + SysBusDevice busdev; + MemoryRegion iomem; + i2c_bus *bus; + qemu_irq irq; + + uint8_t i2ccon; + uint8_t i2cstat; + uint8_t i2cadd; + uint8_t i2cds; + uint8_t i2clc; + bool scl_free; +} Exynos4210I2CState; + +static inline void exynos4210_i2c_raise_interrupt(Exynos4210I2CState *s) +{ + if (s->i2ccon & I2CCON_INTRS_EN) { + s->i2ccon |= I2CCON_INT_PEND; + qemu_irq_raise(s->irq); + } +} + +static void exynos4210_i2c_data_receive(void *opaque) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + int ret; + + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->scl_free = false; + ret = i2c_recv(s->bus); + if (ret < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; /* Data is not acknowledged */ + } else { + s->i2cds = ret; + } + exynos4210_i2c_raise_interrupt(s); +} + +static void exynos4210_i2c_data_send(void *opaque) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->scl_free = false; + if (i2c_send(s->bus, s->i2cds) < 0 && (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; + } + exynos4210_i2c_raise_interrupt(s); +} + +static uint64_t exynos4210_i2c_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + uint8_t value; + + switch (offset) { + case I2CCON_ADDR: + value = s->i2ccon; + break; + case I2CSTAT_ADDR: + value = s->i2cstat; + break; + case I2CADD_ADDR: + value = s->i2cadd; + break; + case I2CDS_ADDR: + value = s->i2cds; + s->scl_free = true; + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx && + (s->i2cstat & I2CSTAT_START_BUSY) && + !(s->i2ccon & I2CCON_INT_PEND)) { + exynos4210_i2c_data_receive(s); + } + break; + case I2CLC_ADDR: + value = s->i2clc; + break; + default: + value = 0; + DPRINT("ERROR: Bad read offset 0x%x\n", (unsigned int)offset); + break; + } + + DPRINT("read %s [0x%02x] -> 0x%02x\n", exynos4_i2c_get_regname(offset), + (unsigned int)offset, value); + return value; +} + +static void exynos4210_i2c_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + Exynos4210I2CState *s = (Exynos4210I2CState *)opaque; + uint8_t v = value & 0xff; + + DPRINT("write %s [0x%02x] <- 0x%02x\n", exynos4_i2c_get_regname(offset), + (unsigned int)offset, v); + + switch (offset) { + case I2CCON_ADDR: + s->i2ccon = (v & ~I2CCON_INT_PEND) | (s->i2ccon & I2CCON_INT_PEND); + if ((s->i2ccon & I2CCON_INT_PEND) && !(v & I2CCON_INT_PEND)) { + s->i2ccon &= ~I2CCON_INT_PEND; + qemu_irq_lower(s->irq); + if (!(s->i2ccon & I2CCON_INTRS_EN)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + } + + if (s->i2cstat & I2CSTAT_START_BUSY) { + if (s->scl_free) { + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx) { + exynos4210_i2c_data_send(s); + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == + I2CMODE_MASTER_Rx) { + exynos4210_i2c_data_receive(s); + } + } else { + s->i2ccon |= I2CCON_INT_PEND; + qemu_irq_raise(s->irq); + } + } + } + break; + case I2CSTAT_ADDR: + s->i2cstat = + (s->i2cstat & I2CSTAT_START_BUSY) | (v & ~I2CSTAT_START_BUSY); + + if (!(s->i2cstat & I2CSTAT_OUTPUT_EN)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + s->scl_free = true; + qemu_irq_lower(s->irq); + break; + } + + /* Nothing to do if in i2c slave mode */ + if (!I2C_IN_MASTER_MODE(s->i2cstat)) { + break; + } + + if (v & I2CSTAT_START_BUSY) { + s->i2cstat &= ~I2CSTAT_LAST_BIT; + s->i2cstat |= I2CSTAT_START_BUSY; /* Line is busy */ + s->scl_free = false; + + /* Generate start bit and send slave address */ + if (i2c_start_transfer(s->bus, s->i2cds >> 1, s->i2cds & 0x1) && + (s->i2ccon & I2CCON_ACK_GEN)) { + s->i2cstat |= I2CSTAT_LAST_BIT; + } else if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Rx) { + exynos4210_i2c_data_receive(s); + } + exynos4210_i2c_raise_interrupt(s); + } else { + i2c_end_transfer(s->bus); + if (!(s->i2ccon & I2CCON_INT_PEND)) { + s->i2cstat &= ~I2CSTAT_START_BUSY; + } + s->scl_free = true; + } + break; + case I2CADD_ADDR: + if ((s->i2cstat & I2CSTAT_OUTPUT_EN) == 0) { + s->i2cadd = v; + } + break; + case I2CDS_ADDR: + if (s->i2cstat & I2CSTAT_OUTPUT_EN) { + s->i2cds = v; + s->scl_free = true; + if (EXYNOS4_I2C_MODE(s->i2cstat) == I2CMODE_MASTER_Tx && + (s->i2cstat & I2CSTAT_START_BUSY) && + !(s->i2ccon & I2CCON_INT_PEND)) { + exynos4210_i2c_data_send(s); + } + } + break; + case I2CLC_ADDR: + s->i2clc = v; + break; + default: + DPRINT("ERROR: Bad write offset 0x%x\n", (unsigned int)offset); + break; + } +} + +static const MemoryRegionOps exynos4210_i2c_ops = { + .read = exynos4210_i2c_read, + .write = exynos4210_i2c_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription exynos4210_i2c_vmstate = { + .name = TYPE_EXYNOS4_I2C, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT8(i2ccon, Exynos4210I2CState), + VMSTATE_UINT8(i2cstat, Exynos4210I2CState), + VMSTATE_UINT8(i2cds, Exynos4210I2CState), + VMSTATE_UINT8(i2cadd, Exynos4210I2CState), + VMSTATE_UINT8(i2clc, Exynos4210I2CState), + VMSTATE_BOOL(scl_free, Exynos4210I2CState), + VMSTATE_END_OF_LIST() + } +}; + +static void exynos4210_i2c_reset(DeviceState *d) +{ + Exynos4210I2CState *s = EXYNOS4_I2C(d); + + s->i2ccon = 0x00; + s->i2cstat = 0x00; + s->i2cds = 0xFF; + s->i2clc = 0x00; + s->i2cadd = 0xFF; + s->scl_free = true; +} + +static int exynos4210_i2c_realize(SysBusDevice *dev) +{ + Exynos4210I2CState *s = EXYNOS4_I2C(dev); + + memory_region_init_io(&s->iomem, &exynos4210_i2c_ops, s, TYPE_EXYNOS4_I2C, + EXYNOS4_I2C_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + sysbus_init_irq(dev, &s->irq); + s->bus = i2c_init_bus(&dev->qdev, "i2c"); + return 0; +} + +static void exynos4210_i2c_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *sbdc = SYS_BUS_DEVICE_CLASS(klass); + + dc->vmsd = &exynos4210_i2c_vmstate; + dc->reset = exynos4210_i2c_reset; + sbdc->init = exynos4210_i2c_realize; +} + +static const TypeInfo exynos4210_i2c_info = { + .name = TYPE_EXYNOS4_I2C, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210I2CState), + .class_init = exynos4210_i2c_class_init, +}; + +static void exynos4210_i2c_register_types(void) +{ + type_register_static(&exynos4210_i2c_info); +} + +type_init(exynos4210_i2c_register_types) diff --git a/hw/exynos4210_i2s.c b/hw/exynos4210_i2s.c new file mode 100644 index 0000000..236cc57 --- /dev/null +++ b/hw/exynos4210_i2s.c @@ -0,0 +1,577 @@ +/* + * Samsung exynos4210 I2S driver + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Vorobiov Stanislav + * + * 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, see . + * + */ + +#include "exynos4210_i2s.h" +#include "exec-memory.h" + +/* #define DEBUG_I2S */ + +#ifdef DEBUG_I2S +#define DPRINTF(fmt, ...) \ + do { \ + fprintf(stdout, "I2S: [%s:%d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +/* Registers */ +#define IISCON 0x00 +#define IISMOD 0x04 +#define IISFIC 0x08 +#define IISPSR 0x0C +#define IISTXD 0x10 +#define IISRXD 0x14 +#define IISFICS 0x18 +#define IISTXDS 0x1C +#define IISAHB 0x20 +#define IISSTR0 0x24 +#define IISSIZE 0x28 +#define IISTRNCNT 0x2C +#define IISLVL0ADDR 0x30 +#define IISLVL1ADDR 0x34 +#define IISLVL2ADDR 0x38 +#define IISLVL3ADDR 0x3C +#define IISSTR1 0x40 + +#define I2S_REGS_MEM_SIZE 0x44 +#define I2S_REGS_NUM (I2S_REGS_MEM_SIZE >> 2) + +#define I_(reg) (reg / sizeof(uint32_t)) + +/* Interface Control Register */ +#define IISCON_I2SACTIVE (1 << 0) +#define IISCON_RXDMACTIVE (1 << 1) +#define IISCON_TXDMACTIVE (1 << 2) +#define IISCON_RXCHPAUSE (1 << 3) +#define IISCON_TXCHPAUSE (1 << 4) +#define IISCON_RXDMAPAUSE (1 << 5) +#define IISCON_TXDMAPAUSE (1 << 6) +#define IISCON_FRXFULL (1 << 7) +#define IISCON_FTX0FULL (1 << 8) +#define IISCON_FRXEMPT (1 << 9) +#define IISCON_FTX0EMPT (1 << 10) +#define IISCON_LRI (1 << 11) +#define IISCON_FTX1FULL (1 << 12) +#define IISCON_FTX2FULL (1 << 13) +#define IISCON_FTX1EMPT (1 << 14) +#define IISCON_FTX2EMPT (1 << 15) +#define IISCON_FTXURINTEN (1 << 16) +#define IISCON_FTXURSTATUS (1 << 17) +#define IISCON_TXSDMACTIVE (1 << 18) +#define IISCON_TXSDMAPAUSE (1 << 20) +#define IISCON_FTXSFULL (1 << 21) +#define IISCON_FTXSEMPT (1 << 22) +#define IISCON_FTXSURINTEN (1 << 23) +#define IISCON_FTXSURSTATUS (1 << 24) +#define IISCON_FRXOFINTEN (1 << 25) +#define IISCON_FRXOFSTATUS (1 << 26) +#define IISCON_SW_RST (1 << 31) + +#define IISCON_WRITE_MASK (0x8295007F) + +/* AHB DMA Control Register */ +#define IISAHB_WRITE_MASK 0xFF0000FB +#define IISAHB_LVL3EN (1 << 27) +#define IISAHB_LVL2EN (1 << 26) +#define IISAHB_LVL1EN (1 << 25) +#define IISAHB_LVL0EN (1 << 24) +#define IISAHB_LVL3INT (1 << 23) +#define IISAHB_LVL2INT (1 << 22) +#define IISAHB_LVL1INT (1 << 21) +#define IISAHB_LVL0INT (1 << 20) +#define IISAHB_LVL3CLR (1 << 19) +#define IISAHB_LVL2CLR (1 << 18) +#define IISAHB_LVL1CLR (1 << 17) +#define IISAHB_LVL0CLR (1 << 16) +#define IISAHB_DMA_STRADDRRST (1 << 7) +#define IISAHB_DMA_STRADDRTOG (1 << 6) +#define IISAHB_DMARLD (1 << 5) +#define IISAHB_INTMASK (1 << 3) +#define IISAHB_DMAINT (1 << 2) +#define IISAHB_DMACLR (1 << 1) +#define IISAHB_DMAEN (1 << 0) + +/* AHB DMA Size Register */ +#define IISSIZE_TRNS_SIZE_OFFSET 16 +#define IISSIZE_TRNS_SIZE_MASK 0xFFFF + +/* AHB DMA Transfer Count Register */ +#define IISTRNCNT_OFFSET 0 +#define IISTRNCNT_MASK 0xFFFFFF + +typedef struct { + const char *name; + target_phys_addr_t offset; + uint32_t reset_value; +} Exynos4210I2SReg; + +static Exynos4210I2SReg exynos4210_i2s_regs[I2S_REGS_NUM] = { + {"IISCON", IISCON, 0x00000000}, + {"IISMOD", IISMOD, 0x00000000}, + {"IISFIC", IISFIC, 0x00000000}, + {"IISPSR", IISPSR, 0x00000000}, + {"IISTXD", IISTXD, 0x00000000}, + {"IISRXD", IISRXD, 0x00000000}, + {"IISFICS", IISFICS, 0x00000000}, + {"IISTXDS", IISTXDS, 0x00000000}, + {"IISAHB", IISAHB, 0x00000000}, + {"IISSTR0", IISSTR0, 0x00000000}, + {"IISSIZE", IISSIZE, 0x7FFF0000}, + {"IISTRNCNT", IISTRNCNT, 0x00000000}, + {"IISLVL0ADDR", IISLVL0ADDR, 0x00000000}, + {"IISLVL1ADDR", IISLVL1ADDR, 0x00000000}, + {"IISLVL2ADDR", IISLVL2ADDR, 0x00000000}, + {"IISLVL3ADDR", IISLVL3ADDR, 0x00000000}, + {"IISSTR1", IISSTR1, 0x00000000}, +}; + +static struct { + uint32_t ahb_en_bit; + uint32_t ahb_int_bit; + uint32_t ahb_clr_bit; + int reg_offset; +} lvl_regs[] = { + { IISAHB_LVL3EN, IISAHB_LVL3INT, IISAHB_LVL3CLR, IISLVL3ADDR }, + { IISAHB_LVL2EN, IISAHB_LVL2INT, IISAHB_LVL2CLR, IISLVL2ADDR }, + { IISAHB_LVL1EN, IISAHB_LVL1INT, IISAHB_LVL1CLR, IISLVL1ADDR }, + { IISAHB_LVL0EN, IISAHB_LVL0INT, IISAHB_LVL0CLR, IISLVL0ADDR }, +}; + +typedef struct { + BusState qbus; + + MemoryRegion iomem; + qemu_irq irq; + uint32_t reg[I2S_REGS_NUM]; +} Exynos4210I2SBus; + +static Exynos4210I2SSlave *get_slave(Exynos4210I2SBus *bus) +{ + DeviceState *dev = QTAILQ_FIRST(&bus->qbus.children); + + if (dev) { + return EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev); + } + + return NULL; +} + +static void reset_internal(BusState *qbus, bool reset_slave) +{ + Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus); + Exynos4210I2SSlave *s; + int i; + + DPRINTF("enter\n"); + + qemu_irq_lower(bus->irq); + + for (i = 0; i < ARRAY_SIZE(exynos4210_i2s_regs); i++) { + bus->reg[i] = exynos4210_i2s_regs[i].reset_value; + } + + if (reset_slave) { + s = get_slave(bus); + + if (s != NULL) { + device_reset(&s->qdev); + } + } +} + +static uint32_t get_dma_size_words(Exynos4210I2SBus *bus) +{ + return (bus->reg[I_(IISSIZE)] >> IISSIZE_TRNS_SIZE_OFFSET) & + IISSIZE_TRNS_SIZE_MASK; +} + +static uint32_t get_dma_trncnt_words(Exynos4210I2SBus *bus) +{ + return (bus->reg[I_(IISTRNCNT)] >> IISTRNCNT_OFFSET) & + IISTRNCNT_MASK; +} + +static void set_dma_trncnt_words(Exynos4210I2SBus *bus, uint32_t trncnt_words) +{ + bus->reg[I_(IISTRNCNT)] &= ~(IISTRNCNT_MASK << IISTRNCNT_OFFSET); + bus->reg[I_(IISTRNCNT)] |= + ((trncnt_words & IISTRNCNT_MASK) << IISTRNCNT_OFFSET); +} + +static int exynos4210_i2s_bus_reset(BusState *qbus); + +static struct BusInfo exynos4210_i2s_bus_info = { + .name = "Exynos4210.I2S", + .size = sizeof(Exynos4210I2SBus), + .reset = exynos4210_i2s_bus_reset +}; + +static const VMStateDescription vmstate_exynos4210_i2s_bus = { + .name = "exynos4210.i2s_bus", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(reg, Exynos4210I2SBus, + I2S_REGS_NUM), + VMSTATE_END_OF_LIST() + } +}; + +#ifdef DEBUG_I2S +static const char *exynos4210_i2s_regname(Exynos4210I2SBus *bus, + target_phys_addr_t offset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(exynos4210_i2s_regs); i++) { + if (offset == exynos4210_i2s_regs[i].offset) { + return exynos4210_i2s_regs[i].name; + } + } + + return NULL; +} +#endif + +static void exynos4210_i2s_bus_write(void *opaque, + target_phys_addr_t offset, + uint64_t value, + unsigned size) +{ + Exynos4210I2SBus *bus = opaque; + Exynos4210I2SSlave *s; + Exynos4210I2SSlaveClass *sc; + int i; + + s = get_slave(bus); + + assert(s != NULL); + + if (s == NULL) { + hw_error("Exynos I2S cannot operate without a slave\n"); + } + + sc = EXYNOS4210_I2S_SLAVE_GET_CLASS(s); + + switch (offset) { + case IISCON: + if (!(value & IISCON_SW_RST) && + (bus->reg[I_(IISCON)] & IISCON_SW_RST)) { + reset_internal(&bus->qbus, 1); + } + + bus->reg[I_(offset)] = (bus->reg[I_(offset)] & ~IISCON_WRITE_MASK) | + (value & IISCON_WRITE_MASK); + + DPRINTF("0x%.8X -> %s\n", + bus->reg[I_(offset)], + exynos4210_i2s_regname(bus, offset)); + + break; + case IISAHB: + if (((value & IISAHB_DMAEN) != 0) && ((value & IISAHB_DMARLD) == 0)) { + hw_error("Non auto-reload DMA is not supported\n"); + } + + if (((value & IISAHB_DMAEN) != 0) && ((value & IISAHB_INTMASK) == 0)) { + hw_error("Non buffer level DMA interrupt is not supported\n"); + } + + if (((value & IISAHB_DMAEN) != 0) && + ((value & IISAHB_DMA_STRADDRTOG) != 0)) { + hw_error("DMA start address toggle is not supported\n"); + } + + for (i = 0; i < ARRAY_SIZE(lvl_regs); ++i) { + if ((value & lvl_regs[i].ahb_clr_bit) && + (bus->reg[I_(IISAHB)] & lvl_regs[i].ahb_int_bit)) { + qemu_irq_lower(bus->irq); + bus->reg[I_(IISAHB)] &= ~lvl_regs[i].ahb_int_bit; + } + } + + if ((value & IISAHB_DMACLR) && + (bus->reg[I_(IISAHB)] & IISAHB_DMAINT)) { + qemu_irq_lower(bus->irq); + bus->reg[I_(IISAHB)] &= ~IISAHB_DMAINT; + } + + if ((value & IISAHB_DMAEN) != + (bus->reg[I_(IISAHB)] & IISAHB_DMAEN)) { + if ((value & IISAHB_DMAEN) == 0) { + qemu_irq_lower(bus->irq); + } + if (sc->dma_enable) { + sc->dma_enable(s, ((value & IISAHB_DMAEN) != 0)); + } + } + + bus->reg[I_(offset)] = (bus->reg[I_(offset)] & ~IISAHB_WRITE_MASK) | + (value & IISAHB_WRITE_MASK); + + DPRINTF("0x%.8X -> %s\n", + bus->reg[I_(offset)], + exynos4210_i2s_regname(bus, offset)); + + break; + case IISTRNCNT: + hw_error("Cannot write IISTRNCNT\n"); + break; + case IISSIZE: + if (value == 0) { + hw_error("IISSIZE cannot be 0\n"); + } + + bus->reg[I_(offset)] = value; + + if (get_dma_size_words(bus) <= get_dma_trncnt_words(bus)) { + set_dma_trncnt_words(bus, 0); + } + + DPRINTF("0x%.8X -> %s\n", + bus->reg[I_(offset)], + exynos4210_i2s_regname(bus, offset)); + + break; + case IISLVL0ADDR: + case IISLVL1ADDR: + case IISLVL2ADDR: + case IISLVL3ADDR: + case IISTXD: + case IISMOD: + case IISFIC: + case IISPSR: + case IISTXDS: + case IISFICS: + case IISSTR0: + case IISSTR1: + bus->reg[I_(offset)] = value; + + DPRINTF("0x%.8X -> %s\n", + bus->reg[I_(offset)], + exynos4210_i2s_regname(bus, offset)); + + break; + default: + hw_error("Bad offset: 0x%X\n", (int)offset); + } +} + +static uint64_t exynos4210_i2s_bus_read(void *opaque, + target_phys_addr_t offset, + unsigned size) +{ + Exynos4210I2SBus *bus = opaque; + + if (offset > (I2S_REGS_MEM_SIZE - sizeof(uint32_t))) { + hw_error("Bad offset: 0x%X\n", (int)offset); + return 0; + } + + DPRINTF("%s -> 0x%.8X\n", + exynos4210_i2s_regname(bus, offset), + bus->reg[I_(offset)]); + + return bus->reg[I_(offset)]; +} + +static int exynos4210_i2s_bus_reset(BusState *qbus) +{ + reset_internal(qbus, 0); + + return 0; +} + +static const MemoryRegionOps exynos4210_i2s_bus_ops = { + .read = exynos4210_i2s_bus_read, + .write = exynos4210_i2s_bus_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + .max_access_size = 4, + .unaligned = false + }, +}; + +BusState *exynos4210_i2s_bus_new(const char *name, + target_phys_addr_t addr, + qemu_irq irq) +{ + Exynos4210I2SBus *bus; + + bus = FROM_QBUS(Exynos4210I2SBus, + qbus_create(&exynos4210_i2s_bus_info, NULL, name)); + vmstate_register(NULL, -1, &vmstate_exynos4210_i2s_bus, bus); + + memory_region_init_io(&bus->iomem, + &exynos4210_i2s_bus_ops, + bus, + "exynos4210.i2s", + I2S_REGS_MEM_SIZE); + + memory_region_add_subregion(get_system_memory(), + addr, + &bus->iomem); + + bus->irq = irq; + + return &bus->qbus; +} + +bool exynos4210_i2s_dma_enabled(BusState *qbus) +{ + Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus); + + return ((bus->reg[I_(IISAHB)] & IISAHB_DMAEN) != 0); +} + +uint32_t exynos4210_i2s_dma_get_words_available(BusState *qbus) +{ + Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus); + + return get_dma_size_words(bus); +} + +void exynos4210_i2s_dma_read(BusState *qbus, void *buf, uint32_t num_words) +{ + Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus); + target_phys_addr_t addr; + uint32_t size_words, trncnt_words; + + assert(num_words <= exynos4210_i2s_dma_get_words_available(qbus)); + + if (num_words > exynos4210_i2s_dma_get_words_available(qbus)) { + hw_error("Bad DMA read length: %d\n", num_words); + } + + size_words = get_dma_size_words(bus); + addr = bus->reg[I_(IISSTR0)]; + trncnt_words = get_dma_trncnt_words(bus); + + assert(trncnt_words < size_words); + + if (num_words > (size_words - trncnt_words)) { + cpu_physical_memory_read(addr + + (trncnt_words * EXYNOS4210_I2S_WORD_LEN), + buf, + (size_words - trncnt_words) * EXYNOS4210_I2S_WORD_LEN); + num_words -= (size_words - trncnt_words); + buf += (size_words - trncnt_words) * EXYNOS4210_I2S_WORD_LEN; + trncnt_words = 0; + } + + cpu_physical_memory_read(addr + (trncnt_words * EXYNOS4210_I2S_WORD_LEN), + buf, + num_words * EXYNOS4210_I2S_WORD_LEN); +} + +void exynos4210_i2s_dma_advance(BusState *qbus, uint32_t num_words) +{ + Exynos4210I2SBus *bus = DO_UPCAST(Exynos4210I2SBus, qbus, qbus); + uint32_t size_words, trncnt_words; + int i; + + assert(num_words <= exynos4210_i2s_dma_get_words_available(qbus)); + + if (num_words > exynos4210_i2s_dma_get_words_available(qbus)) { + hw_error("Bad DMA read length: %d\n", num_words); + } + + size_words = get_dma_size_words(bus); + trncnt_words = get_dma_trncnt_words(bus); + + for (i = 0; i < ARRAY_SIZE(lvl_regs); ++i) { + target_phys_addr_t dma_offset; + uint32_t tmp_num_words = num_words, tmp_trncnt_words = trncnt_words; + + if ((bus->reg[I_(IISAHB)] & lvl_regs[i].ahb_en_bit) == 0) { + continue; + } + + if (bus->reg[I_(lvl_regs[i].reg_offset)] < bus->reg[I_(IISSTR0)]) { + continue; + } + + dma_offset = bus->reg[I_(lvl_regs[i].reg_offset)] - + bus->reg[I_(IISSTR0)]; + + if (tmp_num_words > (size_words - tmp_trncnt_words)) { + if ((dma_offset >= (tmp_trncnt_words * EXYNOS4210_I2S_WORD_LEN)) && + (dma_offset < size_words * EXYNOS4210_I2S_WORD_LEN)) { + bus->reg[I_(IISAHB)] |= lvl_regs[i].ahb_int_bit; + qemu_irq_raise(bus->irq); + break; + } + + tmp_num_words -= (size_words - tmp_trncnt_words); + tmp_trncnt_words = 0; + } + + if ((dma_offset >= (tmp_trncnt_words * EXYNOS4210_I2S_WORD_LEN)) && + (dma_offset < + (tmp_trncnt_words + tmp_num_words) * EXYNOS4210_I2S_WORD_LEN)) { + bus->reg[I_(IISAHB)] |= lvl_regs[i].ahb_int_bit; + qemu_irq_raise(bus->irq); + } + } + + set_dma_trncnt_words(bus, (trncnt_words + num_words) % size_words); +} + +const VMStateDescription vmstate_exynos4210_i2s_slave = { + .name = "Exynos4210I2SSlave", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1 +}; + +static int exynos4210_i2s_slave_qdev_init(DeviceState *dev) +{ + Exynos4210I2SSlave *s = EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev); + Exynos4210I2SSlaveClass *sc = EXYNOS4210_I2S_SLAVE_GET_CLASS(s); + + return sc->init ? sc->init(s) : 0; +} + +static void exynos4210_i2s_slave_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *k = DEVICE_CLASS(klass); + k->init = exynos4210_i2s_slave_qdev_init; + k->bus_info = &exynos4210_i2s_bus_info; +} + +static TypeInfo exynos4210_i2s_slave_type_info = { + .name = TYPE_EXYNOS4210_I2S_SLAVE, + .parent = TYPE_DEVICE, + .instance_size = sizeof(Exynos4210I2SSlave), + .abstract = true, + .class_size = sizeof(Exynos4210I2SSlaveClass), + .class_init = exynos4210_i2s_slave_class_init, +}; + +static void exynos4210_i2s_slave_register_types(void) +{ + type_register_static(&exynos4210_i2s_slave_type_info); +} + +type_init(exynos4210_i2s_slave_register_types) diff --git a/hw/exynos4210_i2s.h b/hw/exynos4210_i2s.h new file mode 100644 index 0000000..86b9c30 --- /dev/null +++ b/hw/exynos4210_i2s.h @@ -0,0 +1,57 @@ +#ifndef QEMU_HW_EXYNOS4210_I2S_H +#define QEMU_HW_EXYNOS4210_I2S_H + +#include "qdev.h" + +#define EXYNOS4210_I2S_WORD_LEN 4 + +typedef struct Exynos4210I2SSlave Exynos4210I2SSlave; + +#define TYPE_EXYNOS4210_I2S_SLAVE "exynos4210.i2s-slave" +#define EXYNOS4210_I2S_SLAVE(obj) \ + OBJECT_CHECK(Exynos4210I2SSlave, (obj), TYPE_EXYNOS4210_I2S_SLAVE) +#define EXYNOS4210_I2S_SLAVE_CLASS(klass) \ + OBJECT_CLASS_CHECK(Exynos4210I2SSlaveClass, (klass), \ + TYPE_EXYNOS4210_I2S_SLAVE) +#define EXYNOS4210_I2S_SLAVE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(Exynos4210I2SSlaveClass, (obj), TYPE_EXYNOS4210_I2S_SLAVE) + +typedef struct { + DeviceClass parent_class; + + int (*init)(Exynos4210I2SSlave *s); + + void (*dma_enable)(Exynos4210I2SSlave *s, bool enable); +} Exynos4210I2SSlaveClass; + +struct Exynos4210I2SSlave { + DeviceState qdev; +}; + +BusState *exynos4210_i2s_bus_new(const char *name, + target_phys_addr_t addr, + qemu_irq irq); + +#define EXYNOS4210_I2S_SLAVE_FROM_QDEV(dev) \ + DO_UPCAST(Exynos4210I2SSlave, qdev, dev) +#define FROM_EXYNOS4210_I2S_SLAVE(type, dev) DO_UPCAST(type, i2s, dev) + +bool exynos4210_i2s_dma_enabled(BusState *qbus); + +uint32_t exynos4210_i2s_dma_get_words_available(BusState *qbus); + +void exynos4210_i2s_dma_read(BusState *qbus, void *buf, uint32_t num_words); + +void exynos4210_i2s_dma_advance(BusState *qbus, uint32_t num_words); + +extern const VMStateDescription vmstate_exynos4210_i2s_slave; + +#define VMSTATE_EXYNOS4210_I2S_SLAVE(_field, _state) { \ + .name = (stringify(_field)), \ + .size = sizeof(Exynos4210I2SSlave), \ + .vmsd = &vmstate_exynos4210_i2s_slave, \ + .flags = VMS_STRUCT, \ + .offset = vmstate_offset_value(_state, _field, Exynos4210I2SSlave), \ +} + +#endif diff --git a/hw/exynos4210_rtc.c b/hw/exynos4210_rtc.c new file mode 100644 index 0000000..1114c64 --- /dev/null +++ b/hw/exynos4210_rtc.c @@ -0,0 +1,612 @@ +/* + * Samsung exynos4210 Real Time Clock + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Ogurtsov Oleg + * + * 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, see . + * + */ + +/* Description: + * Register RTCCON: + * CLKSEL Bit[1] not used + * CLKOUTEN Bit[9] not used + */ + +#include "sysbus.h" +#include "qemu-timer.h" +#include "qemu-common.h" +#include "ptimer.h" + +#include "hw.h" +#include "qemu-timer.h" +#include "sysemu.h" + +#include "exynos4210.h" + +#define DEBUG_RTC 0 +/* Get time from host */ +#define EXYNOS4210_RTC_GETSYSTIME 1 + +#if DEBUG_RTC +#define DPRINTF(fmt, ...) \ + do { fprintf(stdout, "RTC: [%24s:%5d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +#define EXYNOS4210_RTC_REG_MEM_SIZE 0x0100 + +#define INTP 0x0030 +#define RTCCON 0x0040 +#define TICCNT 0x0044 +#define RTCALM 0x0050 +#define ALMSEC 0x0054 +#define ALMMIN 0x0058 +#define ALMHOUR 0x005C +#define ALMDAY 0x0060 +#define ALMMON 0x0064 +#define ALMYEAR 0x0068 +#define BCDSEC 0x0070 +#define BDCMIN 0x0074 +#define BCDHOUR 0x0078 +#define BCDDAY 0x007C +#define BCDDAYWEEK 0x0080 +#define BCDMON 0x0084 +#define BCDYEAR 0x0088 +#define CURTICNT 0x0090 + +#define TICK_TIMER_ENABLE 0x0100 +#define TICNT_THRESHHOLD 2 + + +#define RTC_ENABLE 0x0001 + +#define INTP_TICK_ENABLE 0x0001 +#define INTP_ALM_ENABLE 0x0002 + +#define ALARM_INT_ENABLE 0x0040 + +#define RTC_BASE_FREQ 32768 + +typedef struct Exynos4210RTCState { + SysBusDevice busdev; + MemoryRegion iomem; + + /* registers */ + uint32_t reg_intp; + uint32_t reg_rtccon; + uint32_t reg_ticcnt; + uint32_t reg_rtcalm; + uint32_t reg_almsec; + uint32_t reg_almmin; + uint32_t reg_almhour; + uint32_t reg_almday; + uint32_t reg_almmon; + uint32_t reg_almyear; + uint32_t reg_curticcnt; + + ptimer_state *ptimer; /* tick timer */ + ptimer_state *ptimer_1Hz; /* clock timer */ + uint32_t freq; + + qemu_irq tick_irq; /* Time Tick Generator irq */ + qemu_irq alm_irq; /* alarm irq */ + + struct tm current_tm; /* current time */ +} Exynos4210RTCState; + +#define TICCKSEL(value) ((value & (0x0F << 4)) >> 4) + +/*** VMState ***/ +static const VMStateDescription vmstate_exynos4210_rtc_state = { + .name = "exynos4210.rtc", + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32(reg_intp, Exynos4210RTCState), + VMSTATE_UINT32(reg_rtccon, Exynos4210RTCState), + VMSTATE_UINT32(reg_ticcnt, Exynos4210RTCState), + VMSTATE_UINT32(reg_rtcalm, Exynos4210RTCState), + VMSTATE_UINT32(reg_almsec, Exynos4210RTCState), + VMSTATE_UINT32(reg_almmin, Exynos4210RTCState), + VMSTATE_UINT32(reg_almhour, Exynos4210RTCState), + VMSTATE_UINT32(reg_almday, Exynos4210RTCState), + VMSTATE_UINT32(reg_almmon, Exynos4210RTCState), + VMSTATE_UINT32(reg_almyear, Exynos4210RTCState), + VMSTATE_UINT32(reg_curticcnt, Exynos4210RTCState), + VMSTATE_PTIMER(ptimer, Exynos4210RTCState), + VMSTATE_PTIMER(ptimer_1Hz, Exynos4210RTCState), + VMSTATE_UINT32(freq, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_sec, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_min, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_hour, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_wday, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_mday, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_mon, Exynos4210RTCState), + VMSTATE_INT32(current_tm.tm_year, Exynos4210RTCState), + VMSTATE_END_OF_LIST() + } +}; + +static inline int rtc_to_bcd(int a, int count) +{ + int ret = (((a % 100) / 10) << 4) | (a % 10); + if (count == 3) { + ret |= (((a % 1000) / 100) << 8); + } + return ret; +} + +static inline int rtc_from_bcd(uint32_t a, int count) +{ + int ret = (((a >> 4) & 0x0f) * 10) + (a & 0x0f); + if (count == 3) { + ret += (((a >> 8) & 0x0f) * 100); + } + return ret; +} + +static void check_alarm_raise(Exynos4210RTCState *s) +{ + unsigned int alarm_raise = 0; + struct tm stm = s->current_tm; + + if ((s->reg_rtcalm & 0x01) && + (rtc_to_bcd(stm.tm_sec, 2) == s->reg_almsec)) { + alarm_raise = 1; + } + if ((s->reg_rtcalm & 0x02) && + (rtc_to_bcd(stm.tm_min, 2) == s->reg_almmin)) { + alarm_raise = 1; + } + if ((s->reg_rtcalm & 0x04) && + (rtc_to_bcd(stm.tm_hour, 2) == s->reg_almhour)) { + alarm_raise = 1; + } + if ((s->reg_rtcalm & 0x08) && + (rtc_to_bcd(stm.tm_mday, 2) == s->reg_almday)) { + alarm_raise = 1; + } + if ((s->reg_rtcalm & 0x10) && + (rtc_to_bcd(stm.tm_mon, 2) == s->reg_almmon)) { + alarm_raise = 1; + } + if ((s->reg_rtcalm & 0x20) && + (rtc_to_bcd(stm.tm_year, 3) == s->reg_almyear)) { + alarm_raise = 1; + } + + if (alarm_raise) { + DPRINTF("ALARM IRQ\n"); + /* set irq status */ + s->reg_intp |= INTP_ALM_ENABLE; + qemu_irq_raise(s->alm_irq); + } +} + +/* + * RTC update frequency + * Parameters: + * reg_value - current RTCCON register or his new value + */ +static void exynos4210_rtc_update_freq(Exynos4210RTCState *s, + uint32_t reg_value) +{ + uint32_t freq; + + freq = s->freq; + /* set frequncy for time generator */ + s->freq = RTC_BASE_FREQ / (1 << TICCKSEL(reg_value)); + + if (freq != s->freq) { + ptimer_set_freq(s->ptimer, s->freq); + DPRINTF("freq=%dHz\n", s->freq); + } +} + +/* month is between 0 and 11. */ +static int get_days_in_month(int month, int year) +{ + static const int days_tab[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + int d; + if ((unsigned)month >= 12) { + return 31; + } + d = days_tab[month]; + if (month == 1) { + if ((year % 4) == 0 && ((year % 100) != 0 || (year % 400) == 0)) { + d++; + } + } + return d; +} + +/* update 'tm' to the next second */ +static void rtc_next_second(struct tm *tm) +{ + int days_in_month; + + tm->tm_sec++; + if ((unsigned)tm->tm_sec >= 60) { + tm->tm_sec = 0; + tm->tm_min++; + if ((unsigned)tm->tm_min >= 60) { + tm->tm_min = 0; + tm->tm_hour++; + if ((unsigned)tm->tm_hour >= 24) { + tm->tm_hour = 0; + /* next day */ + tm->tm_wday++; + if ((unsigned)tm->tm_wday >= 7) { + tm->tm_wday = 0; + } + days_in_month = get_days_in_month(tm->tm_mon, + tm->tm_year + 1900); + tm->tm_mday++; + if (tm->tm_mday < 1) { + tm->tm_mday = 1; + } else if (tm->tm_mday > days_in_month) { + tm->tm_mday = 1; + tm->tm_mon++; + if (tm->tm_mon >= 12) { + tm->tm_mon = 0; + tm->tm_year++; + } + } + } + } + } +} + +/* + * tick handler + */ +static void exynos4210_rtc_tick(void *opaque) +{ + Exynos4210RTCState *s = (Exynos4210RTCState *)opaque; + + DPRINTF("TICK IRQ\n"); + /* set irq status */ + s->reg_intp |= INTP_TICK_ENABLE; + /* raise IRQ */ + qemu_irq_raise(s->tick_irq); + + /* restart timer */ + ptimer_set_count(s->ptimer, s->reg_ticcnt); + ptimer_run(s->ptimer, 1); +} + +/* + * 1Hz clock handler + */ +static void exynos4210_rtc_1Hz_tick(void *opaque) +{ + Exynos4210RTCState *s = (Exynos4210RTCState *)opaque; + + rtc_next_second(&s->current_tm); + /* DPRINTF("1Hz tick\n"); */ + + /* raise IRQ */ + if (s->reg_rtcalm & ALARM_INT_ENABLE) { + check_alarm_raise(s); + } + + ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ); + ptimer_run(s->ptimer_1Hz, 1); +} + +/* + * RTC Read + */ +static uint64_t exynos4210_rtc_read(void *opaque, target_phys_addr_t offset, + unsigned size) +{ + uint32_t value = 0; + Exynos4210RTCState *s = (Exynos4210RTCState *)opaque; + + switch (offset) { + case INTP: + value = s->reg_intp; + break; + case RTCCON: + value = s->reg_rtccon; + break; + case TICCNT: + value = s->reg_ticcnt; + break; + case RTCALM: + value = s->reg_rtcalm; + break; + case ALMSEC: + value = s->reg_almsec; + break; + case ALMMIN: + value = s->reg_almmin; + break; + case ALMHOUR: + value = s->reg_almhour; + break; + case ALMDAY: + value = s->reg_almday; + break; + case ALMMON: + value = s->reg_almmon; + break; + case ALMYEAR: + value = s->reg_almyear; + break; + + case BCDSEC: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_sec, 2); + break; + case BDCMIN: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_min, 2); + break; + case BCDHOUR: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_hour, 2); + break; + case BCDDAYWEEK: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_wday, 2); + break; + case BCDDAY: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_mday, 2); + break; + case BCDMON: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_mon + 1, 2); + break; + case BCDYEAR: + value = (uint32_t)rtc_to_bcd(s->current_tm.tm_year, 3); + break; + + case CURTICNT: + s->reg_curticcnt = ptimer_get_count(s->ptimer); + value = s->reg_curticcnt; + break; + + default: + fprintf(stderr, + "[exynos4210.rtc: bad read offset " TARGET_FMT_plx "]\n", + offset); + break; + } + return value; +} + +/* + * RTC Write + */ +static void exynos4210_rtc_write(void *opaque, target_phys_addr_t offset, + uint64_t value, unsigned size) +{ + Exynos4210RTCState *s = (Exynos4210RTCState *)opaque; + + switch (offset) { + case INTP: + if (value & INTP_ALM_ENABLE) { + qemu_irq_lower(s->alm_irq); + s->reg_intp &= (~INTP_ALM_ENABLE); + } + if (value & INTP_TICK_ENABLE) { + qemu_irq_lower(s->tick_irq); + s->reg_intp &= (~INTP_TICK_ENABLE); + } + break; + case RTCCON: + if (value & RTC_ENABLE) { + exynos4210_rtc_update_freq(s, value); + } + if ((value & RTC_ENABLE) > (s->reg_rtccon & RTC_ENABLE)) { + /* clock timer */ + ptimer_set_count(s->ptimer_1Hz, RTC_BASE_FREQ); + ptimer_run(s->ptimer_1Hz, 1); + DPRINTF("run clock timer\n"); + } + if ((value & RTC_ENABLE) < (s->reg_rtccon & RTC_ENABLE)) { + /* tick timer */ + ptimer_stop(s->ptimer); + /* clock timer */ + ptimer_stop(s->ptimer_1Hz); + DPRINTF("stop all timers\n"); + } + if (value & RTC_ENABLE) { + if ((value & TICK_TIMER_ENABLE) > + (s->reg_rtccon & TICK_TIMER_ENABLE) && + (s->reg_ticcnt)) { + ptimer_set_count(s->ptimer, s->reg_ticcnt); + ptimer_run(s->ptimer, 1); + DPRINTF("run tick timer\n"); + } + if ((value & TICK_TIMER_ENABLE) < + (s->reg_rtccon & TICK_TIMER_ENABLE)) { + ptimer_stop(s->ptimer); + } + } + s->reg_rtccon = value; + break; + case TICCNT: + if (value > TICNT_THRESHHOLD) { + s->reg_ticcnt = value; + } else { + fprintf(stderr, + "[exynos4210.rtc: bad TICNT value %u ]\n", + (uint32_t)value); + } + break; + + case RTCALM: + s->reg_rtcalm = value; + break; + case ALMSEC: + s->reg_almsec = (value & 0x7f); + break; + case ALMMIN: + s->reg_almmin = (value & 0x7f); + break; + case ALMHOUR: + s->reg_almhour = (value & 0x3f); + break; + case ALMDAY: + s->reg_almday = (value & 0x3f); + break; + case ALMMON: + s->reg_almmon = (value & 0x1f); + break; + case ALMYEAR: + s->reg_almyear = (value & 0x0fff); + break; + + case BCDSEC: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_sec = rtc_from_bcd(value, 2); + } + break; + case BDCMIN: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_min = rtc_from_bcd(value, 2); + } + break; + case BCDHOUR: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_hour = rtc_from_bcd(value, 2); + } + break; + case BCDDAYWEEK: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_wday = rtc_from_bcd(value, 2); + } + break; + case BCDDAY: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_mday = rtc_from_bcd(value, 2); + } + break; + case BCDMON: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_mon = rtc_from_bcd(value, 2) - 1; + } + break; + case BCDYEAR: + if (s->reg_rtccon & RTC_ENABLE) { + s->current_tm.tm_year = rtc_from_bcd(value, 3); + } + break; + + default: + fprintf(stderr, + "[exynos4210.rtc: bad write offset " TARGET_FMT_plx "]\n", + offset); + break; + + } +} + +/* + * Set default values to timer fields and registers + */ +static void exynos4210_rtc_reset(DeviceState *d) +{ + Exynos4210RTCState *s = (Exynos4210RTCState *)d; + +#if EXYNOS4210_RTC_GETSYSTIME + time_t ct; + + time(&ct); +#ifndef _WIN32 + gmtime_r(&ct, &s->current_tm); + DPRINTF("Get time from host: %d-%d-%d %2d:%02d:%02d\n", + s->current_tm.tm_year, s->current_tm.tm_mon, s->current_tm.tm_mday, + s->current_tm.tm_hour, s->current_tm.tm_min, s->current_tm.tm_sec); +#endif +#endif + + s->reg_intp = 0; + s->reg_rtccon = 0; + s->reg_ticcnt = 0; + s->reg_rtcalm = 0; + s->reg_almsec = 0; + s->reg_almmin = 0; + s->reg_almhour = 0; + s->reg_almday = 0; + s->reg_almmon = 0; + s->reg_almyear = 0; + + s->reg_curticcnt = 0; + + exynos4210_rtc_update_freq(s, s->reg_rtccon); + ptimer_stop(s->ptimer); + ptimer_stop(s->ptimer_1Hz); +} + +static const MemoryRegionOps exynos4210_rtc_ops = { + .read = exynos4210_rtc_read, + .write = exynos4210_rtc_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +/* + * RTC timer initialization + */ +static int exynos4210_rtc_init(SysBusDevice *dev) +{ + Exynos4210RTCState *s = FROM_SYSBUS(Exynos4210RTCState, dev); + QEMUBH *bh; + + bh = qemu_bh_new(exynos4210_rtc_tick, s); + s->ptimer = ptimer_init(bh); + ptimer_set_freq(s->ptimer, RTC_BASE_FREQ); + exynos4210_rtc_update_freq(s, 0); + + bh = qemu_bh_new(exynos4210_rtc_1Hz_tick, s); + s->ptimer_1Hz = ptimer_init(bh); + ptimer_set_freq(s->ptimer_1Hz, RTC_BASE_FREQ); + + sysbus_init_irq(dev, &s->alm_irq); + sysbus_init_irq(dev, &s->tick_irq); + + memory_region_init_io(&s->iomem, &exynos4210_rtc_ops, s, "exynos4210-rtc", + EXYNOS4210_RTC_REG_MEM_SIZE); + sysbus_init_mmio(dev, &s->iomem); + + return 0; +} + +static void exynos4210_rtc_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass); + + k->init = exynos4210_rtc_init; + dc->reset = exynos4210_rtc_reset; + dc->vmsd = &vmstate_exynos4210_rtc_state; +} + +static TypeInfo exynos4210_rtc_info = { + .name = "exynos4210.rtc", + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(Exynos4210RTCState), + .class_init = exynos4210_rtc_class_init, +}; + +static void exynos4210_rtc_register_types(void) +{ + type_register_static(&exynos4210_rtc_info); +} + +type_init(exynos4210_rtc_register_types) diff --git a/hw/exynos4210_uart.c b/hw/exynos4210_uart.c index ccc47804..155abdb 100644 --- a/hw/exynos4210_uart.c +++ b/hw/exynos4210_uart.c @@ -25,19 +25,13 @@ #include "exynos4210.h" -#undef DEBUG_UART -#undef DEBUG_UART_EXTEND -#undef DEBUG_IRQ -#undef DEBUG_Rx_DATA -#undef DEBUG_Tx_DATA - #define DEBUG_UART 0 #define DEBUG_UART_EXTEND 0 #define DEBUG_IRQ 0 #define DEBUG_Rx_DATA 0 #define DEBUG_Tx_DATA 0 -#if DEBUG_UART +#if DEBUG_UART || DEBUG_UART_EXTEND #define PRINT_DEBUG(fmt, args...) \ do { \ fprintf(stderr, " [%s:%d] "fmt, __func__, __LINE__, ##args); \ @@ -160,6 +154,8 @@ static Exynos4210UartReg exynos4210_uart_regs[] = { #define UERSTAT_FRAME 0x4 #define UERSTAT_BREAK 0x8 +#define TYPE_EXYNOS4210_UART "exynos4210.uart" + typedef struct { uint8_t *data; uint32_t sp, rp; /* store and retrieve pointers */ @@ -182,7 +178,7 @@ typedef struct { } Exynos4210UartState; -#if DEBUG_UART +#if DEBUG_UART || DEBUG_UART_EXTEND /* Used only for debugging inside PRINT_DEBUG_... macros */ static const char *exynos4210_uart_regname(target_phys_addr_t offset) { @@ -307,6 +303,7 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s) uint64_t uclk_rate; if (s->reg[I_(UBRDIV)] == 0) { + PRINT_DEBUG("Baud rate division value is 0\n"); return; } @@ -332,7 +329,19 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s) frame_size += data_bits + stop_bits; - uclk_rate = 24000000; + switch (s->channel) { + case 0: + uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART0); break; + case 1: + uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART1); break; + case 2: + uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART2); break; + case 3: + uclk_rate = exynos4210_cmu_get_rate(EXYNOS4210_SCLK_UART3); 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); @@ -348,6 +357,15 @@ static void exynos4210_uart_update_parameters(Exynos4210UartState *s) s->channel, speed, parity, data_bits, stop_bits); } +static void uclk_rate_changed(void *opaque) +{ + Exynos4210UartState *s = (Exynos4210UartState *)opaque; + + PRINT_DEBUG("Clock sclk_uart%d was changed\n", s->channel); + + exynos4210_uart_update_parameters(s); +} + static void exynos4210_uart_write(void *opaque, target_phys_addr_t offset, uint64_t val, unsigned size) { @@ -542,6 +560,7 @@ static void exynos4210_uart_reset(DeviceState *dev) container_of(dev, Exynos4210UartState, busdev.qdev); int regs_number = sizeof(exynos4210_uart_regs)/sizeof(Exynos4210UartReg); int i; + Exynos4210Clock clock_id; for (i = 0; i < regs_number; i++) { s->reg[I_(exynos4210_uart_regs[i].offset)] = @@ -551,6 +570,17 @@ static void exynos4210_uart_reset(DeviceState *dev) fifo_reset(&s->rx); fifo_reset(&s->tx); + switch (s->channel) { + case 0: clock_id = EXYNOS4210_SCLK_UART0; break; + case 1: clock_id = EXYNOS4210_SCLK_UART1; break; + case 2: clock_id = EXYNOS4210_SCLK_UART2; break; + case 3: clock_id = EXYNOS4210_SCLK_UART3; break; + default: + hw_error("Wrong channel number: %d.\n", s->channel); + } + + exynos4210_register_clock_handler(uclk_rate_changed, clock_id, s); + PRINT_DEBUG("UART%d: Rx FIFO size: %d\n", s->channel, s->rx.size); } @@ -642,6 +672,7 @@ static int exynos4210_uart_init(SysBusDevice *dev) return 0; } + static Property exynos4210_uart_properties[] = { DEFINE_PROP_CHR("chardev", Exynos4210UartState, chr), DEFINE_PROP_UINT32("channel", Exynos4210UartState, channel, 0), @@ -662,7 +693,7 @@ static void exynos4210_uart_class_init(ObjectClass *klass, void *data) } static TypeInfo exynos4210_uart_info = { - .name = "exynos4210.uart", + .name = TYPE_EXYNOS4210_UART, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(Exynos4210UartState), .class_init = exynos4210_uart_class_init, diff --git a/hw/wm8994.c b/hw/wm8994.c new file mode 100644 index 0000000..9d3b9a0 --- /dev/null +++ b/hw/wm8994.c @@ -0,0 +1,644 @@ +/* + * Samsung exynos4210 wm8994 driver + * + * Copyright (c) 2012 Samsung Electronics Co., Ltd. + * Vorobiov Stanislav + * + * 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, see . + * + */ + +#include "wm8994.h" +#include "hw.h" +#include "i2c.h" +#include "audio/audio.h" + +/* #define DEBUG_WM8994 */ + +#ifdef DEBUG_WM8994 +#define DPRINTF(fmt, ...) \ + do { \ + fprintf(stdout, "WM8994: [%s:%d] " fmt, __func__, __LINE__, \ + ## __VA_ARGS__); \ + } while (0) +#else +#define DPRINTF(fmt, ...) do {} while (0) +#endif + +/* Registers */ +#define WM8994_SOFTWARE_RESET 0x00 +#define WM8994_POWER_MANAGEMENT_2 0x02 +#define WM8994_SPEAKER_VOLUME_LEFT 0x26 /* Speaker */ +#define WM8994_SPEAKER_VOLUME_RIGHT 0x27 +#define WM8994_CHIP_REVISION 0x100 +#define WM8994_AIF1_RATE 0x210 +#define WM8994_AIF1_CONTROL_1 0x300 +#define WM8994_GPIO_1 0x700 +#define WM8994_GPIO_3 0x702 +#define WM8994_GPIO_4 0x703 +#define WM8994_GPIO_5 0x704 +#define WM8994_GPIO_6 0x705 +#define WM8994_GPIO_7 0x706 +#define WM8994_GPIO_8 0x707 +#define WM8994_GPIO_9 0x708 +#define WM8994_GPIO_10 0x709 +#define WM8994_GPIO_11 0x70A +#define WM8994_PULL_CONTROL_2 0x721 +#define WM8994_INPUT_MIXER_1 0x15 +#define WM8994_LEFT_LINE_INPUT_1_2_VOLUME 0x18 +#define WM8994_LEFT_LINE_INPUT_3_4_VOLUME 0x19 +#define WM8994_RIGHT_LINE_INPUT_1_2_VOLUME 0x1A +#define WM8994_RIGHT_LINE_INPUT_3_4_VOLUME 0x1B +#define WM8994_LEFT_OUTPUT_VOLUME 0x1C +#define WM8994_RIGHT_OUTPUT_VOLUME 0x1D +#define WM8994_LEFT_OPGA_VOLUME 0x20 +#define WM8994_RIGHT_OPGA_VOLUME 0x21 +#define WM8994_LINE_MIXER_1 0x34 +#define WM8994_LINE_MIXER_2 0x35 +#define WM8994_ANTIPOP_1 0x38 +#define WM8994_LDO_1 0x3B +#define WM8994_LDO_2 0x3C +#define WM8994_AIF1_ADC1_LEFT_VOLUME 0x400 +#define WM8994_AIF1_ADC1_RIGHT_VOLUME 0x401 +#define WM8994_AIF1_DAC1_LEFT_VOLUME 0x402 +#define WM8994_AIF1_DAC1_RIGHT_VOLUME 0x403 +#define WM8994_AIF1_ADC2_LEFT_VOLUME 0x404 +#define WM8994_AIF1_ADC2_RIGHT_VOLUME 0x405 +#define WM8994_AIF1_DAC2_LEFT_VOLUME 0x406 +#define WM8994_AIF1_DAC2_RIGHT_VOLUME 0x407 +#define WM8994_AIF1_DAC1_FILTERS_2 0x421 +#define WM8994_AIF1_DAC2_FILTERS_2 0x423 +#define WM8994_AIF2_ADC_LEFT_VOLUME 0x500 +#define WM8994_AIF2_ADC_RIGHT_VOLUME 0x501 +#define WM8994_AIF2_DAC_LEFT_VOLUME 0x502 +#define WM8994_AIF2_DAC_RIGHT_VOLUME 0x503 +#define WM8994_AIF2_DAC_FILTERS_2 0x521 +#define WM8994_DAC1_LEFT_VOLUME 0x610 +#define WM8994_DAC1_RIGHT_VOLUME 0x611 +#define WM8994_DAC2_LEFT_VOLUME 0x612 +#define WM8994_DAC2_RIGHT_VOLUME 0x613 +#define WM8994_POWER_MANAGEMENT_1 0x01 +#define WM8994_POWER_MANAGEMENT_3 0x03 +#define WM8994_ANTIPOP_2 0x39 +#define WM8994_AIF1_CLOCKING_1 0x200 +#define WM8994_FLL1_CONTROL_1 0x220 +#define WM8994_FLL1_CONTROL_2 0x221 +#define WM8994_FLL1_CONTROL_3 0x222 +#define WM8994_FLL1_CONTROL_4 0x223 +#define WM8994_FLL1_CONTROL_5 0x224 +#define WM8994_AIF1_MASTER_SLAVE 0x302 +#define WM8994_AIF1_BCLK 0x303 +#define WM8994_AIF1DAC_LRCLK 0x305 +#define WM8994_AIF1_DAC1_FILTERS_1 0x420 + +#define WM8994_REGS_NUM 0x74A + +/* + * R528 (0x210) - AIF1 Rate + */ +#define WM8994_AIF1_SR_MASK 0x00F0 /* AIF1_SR - [7:4] */ +#define WM8994_AIF1_SR_SHIFT 4 /* AIF1_SR - [7:4] */ +#define WM8994_AIF1_SR_WIDTH 4 /* AIF1_SR - [7:4] */ +#define WM8994_AIF1CLK_RATE_MASK 0x000F /* AIF1CLK_RATE - [3:0] */ +#define WM8994_AIF1CLK_RATE_SHIFT 0 /* AIF1CLK_RATE - [3:0] */ +#define WM8994_AIF1CLK_RATE_WIDTH 4 /* AIF1CLK_RATE - [3:0] */ + +/* + * R768 (0x300) - AIF1 Control (1) + */ +#define WM8994_AIF1ADCL_SRC 0x8000 /* AIF1ADCL_SRC */ +#define WM8994_AIF1ADCL_SRC_MASK 0x8000 /* AIF1ADCL_SRC */ +#define WM8994_AIF1ADCL_SRC_SHIFT 15 /* AIF1ADCL_SRC */ +#define WM8994_AIF1ADCL_SRC_WIDTH 1 /* AIF1ADCL_SRC */ +#define WM8994_AIF1ADCR_SRC 0x4000 /* AIF1ADCR_SRC */ +#define WM8994_AIF1ADCR_SRC_MASK 0x4000 /* AIF1ADCR_SRC */ +#define WM8994_AIF1ADCR_SRC_SHIFT 14 /* AIF1ADCR_SRC */ +#define WM8994_AIF1ADCR_SRC_WIDTH 1 /* AIF1ADCR_SRC */ +#define WM8994_AIF1ADC_TDM 0x2000 /* AIF1ADC_TDM */ +#define WM8994_AIF1ADC_TDM_MASK 0x2000 /* AIF1ADC_TDM */ +#define WM8994_AIF1ADC_TDM_SHIFT 13 /* AIF1ADC_TDM */ +#define WM8994_AIF1ADC_TDM_WIDTH 1 /* AIF1ADC_TDM */ +#define WM8994_AIF1_BCLK_INV 0x0100 /* AIF1_BCLK_INV */ +#define WM8994_AIF1_BCLK_INV_MASK 0x0100 /* AIF1_BCLK_INV */ +#define WM8994_AIF1_BCLK_INV_SHIFT 8 /* AIF1_BCLK_INV */ +#define WM8994_AIF1_BCLK_INV_WIDTH 1 /* AIF1_BCLK_INV */ +#define WM8994_AIF1_LRCLK_INV 0x0080 /* AIF1_LRCLK_INV */ +#define WM8994_AIF1_LRCLK_INV_MASK 0x0080 /* AIF1_LRCLK_INV */ +#define WM8994_AIF1_LRCLK_INV_SHIFT 7 /* AIF1_LRCLK_INV */ +#define WM8994_AIF1_LRCLK_INV_WIDTH 1 /* AIF1_LRCLK_INV */ +#define WM8994_AIF1_WL_MASK 0x0060 /* AIF1_WL - [6:5] */ +#define WM8994_AIF1_WL_SHIFT 5 /* AIF1_WL - [6:5] */ +#define WM8994_AIF1_WL_WIDTH 2 /* AIF1_WL - [6:5] */ +#define WM8994_AIF1_FMT_MASK 0x0018 /* AIF1_FMT - [4:3] */ +#define WM8994_AIF1_FMT_SHIFT 3 /* AIF1_FMT - [4:3] */ +#define WM8994_AIF1_FMT_WIDTH 2 /* AIF1_FMT - [4:3] */ + +/* + * R1056 (0x420) - AIF1 DAC1 Filters (1) + */ +#define WM8994_AIF1DAC1_MUTE 0x0200 /* AIF1DAC1_MUTE */ +#define WM8994_AIF1DAC1_MUTE_MASK 0x0200 /* AIF1DAC1_MUTE */ +#define WM8994_AIF1DAC1_MUTE_SHIFT 9 /* AIF1DAC1_MUTE */ +#define WM8994_AIF1DAC1_MUTE_WIDTH 1 /* AIF1DAC1_MUTE */ +#define WM8994_AIF1DAC1_MONO 0x0080 /* AIF1DAC1_MONO */ +#define WM8994_AIF1DAC1_MONO_MASK 0x0080 /* AIF1DAC1_MONO */ +#define WM8994_AIF1DAC1_MONO_SHIFT 7 /* AIF1DAC1_MONO */ +#define WM8994_AIF1DAC1_MONO_WIDTH 1 /* AIF1DAC1_MONO */ +#define WM8994_AIF1DAC1_MUTERATE 0x0020 /* AIF1DAC1_MUTERATE */ +#define WM8994_AIF1DAC1_MUTERATE_MASK 0x0020 /* AIF1DAC1_MUTERATE */ +#define WM8994_AIF1DAC1_MUTERATE_SHIFT 5 /* AIF1DAC1_MUTERATE */ +#define WM8994_AIF1DAC1_MUTERATE_WIDTH 1 /* AIF1DAC1_MUTERATE */ +#define WM8994_AIF1DAC1_UNMUTE_RAMP 0x0010 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8994_AIF1DAC1_UNMUTE_RAMP_MASK 0x0010 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8994_AIF1DAC1_UNMUTE_RAMP_SHIFT 4 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8994_AIF1DAC1_UNMUTE_RAMP_WIDTH 1 /* AIF1DAC1_UNMUTE_RAMP */ +#define WM8994_AIF1DAC1_DEEMP_MASK 0x0006 /* AIF1DAC1_DEEMP - [2:1] */ +#define WM8994_AIF1DAC1_DEEMP_SHIFT 1 /* AIF1DAC1_DEEMP - [2:1] */ +#define WM8994_AIF1DAC1_DEEMP_WIDTH 2 /* AIF1DAC1_DEEMP - [2:1] */ + +/* + * R38 (0x26), R39 (0x27) - Speaker Volume + */ +#define WM8994_SPKOUT_VU 0x0100 /* SPKOUT_VU */ +#define WM8994_SPKOUT_VU_MASK 0x0100 /* SPKOUT_VU */ +#define WM8994_SPKOUT_VU_SHIFT 8 /* SPKOUT_VU */ +#define WM8994_SPKOUT_VU_WIDTH 1 /* SPKOUT_VU */ +#define WM8994_SPKOUTL_ZC 0x0080 /* SPKOUTL_ZC */ +#define WM8994_SPKOUTL_ZC_MASK 0x0080 /* SPKOUTL_ZC */ +#define WM8994_SPKOUTL_ZC_SHIFT 7 /* SPKOUTL_ZC */ +#define WM8994_SPKOUTL_ZC_WIDTH 1 /* SPKOUTL_ZC */ +#define WM8994_SPKOUTL_MUTE_N 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8994_SPKOUTL_MUTE_N_MASK 0x0040 /* SPKOUTL_MUTE_N */ +#define WM8994_SPKOUTL_MUTE_N_SHIFT 6 /* SPKOUTL_MUTE_N */ +#define WM8994_SPKOUTL_MUTE_N_WIDTH 1 /* SPKOUTL_MUTE_N */ +#define WM8994_SPKOUTL_VOL_MASK 0x003F /* SPKOUTL_VOL - [5:0] */ +#define WM8994_SPKOUTL_VOL_SHIFT 0 /* SPKOUTL_VOL - [5:0] */ +#define WM8994_SPKOUTL_VOL_WIDTH 6 /* SPKOUTL_VOL - [5:0] */ + +#define CODEC "wm8994" + +typedef struct { + const char *name; + uint16_t address; +} WM8994Reg; + +#ifdef DEBUG_WM8994 +static WM8994Reg wm8994_regs[] = { + {"SOFTWARE_RESET", WM8994_SOFTWARE_RESET }, + {"POWER_MANAGEMENT_2", WM8994_POWER_MANAGEMENT_2 }, + {"SPEAKER_VOLUME_LEFT", WM8994_SPEAKER_VOLUME_LEFT }, + {"SPEAKER_VOLUME_RIGHT", WM8994_SPEAKER_VOLUME_RIGHT }, + {"CHIP_REVISION", WM8994_CHIP_REVISION }, + {"AIF1_RATE", WM8994_AIF1_RATE }, + {"AIF1_CONTROL_1", WM8994_AIF1_CONTROL_1 }, + {"GPIO_1", WM8994_GPIO_1 }, + {"GPIO_3", WM8994_GPIO_3 }, + {"GPIO_4", WM8994_GPIO_4 }, + {"GPIO_5", WM8994_GPIO_5 }, + {"GPIO_6", WM8994_GPIO_6 }, + {"GPIO_7", WM8994_GPIO_7 }, + {"GPIO_8", WM8994_GPIO_8 }, + {"GPIO_9", WM8994_GPIO_9 }, + {"GPIO_10", WM8994_GPIO_10 }, + {"GPIO_11", WM8994_GPIO_11 }, + {"PULL_CONTROL_2", WM8994_PULL_CONTROL_2 }, + {"INPUT_MIXER_1", WM8994_INPUT_MIXER_1 }, + {"LEFT_LINE_INPUT_1_2_VOLUME", WM8994_LEFT_LINE_INPUT_1_2_VOLUME }, + {"LEFT_LINE_INPUT_3_4_VOLUME", WM8994_LEFT_LINE_INPUT_3_4_VOLUME }, + {"RIGHT_LINE_INPUT_1_2_VOLUME", WM8994_RIGHT_LINE_INPUT_1_2_VOLUME }, + {"RIGHT_LINE_INPUT_3_4_VOLUME", WM8994_RIGHT_LINE_INPUT_3_4_VOLUME }, + {"LEFT_OUTPUT_VOLUME", WM8994_LEFT_OUTPUT_VOLUME }, + {"RIGHT_OUTPUT_VOLUME", WM8994_RIGHT_OUTPUT_VOLUME }, + {"LEFT_OPGA_VOLUME", WM8994_LEFT_OPGA_VOLUME }, + {"RIGHT_OPGA_VOLUME", WM8994_RIGHT_OPGA_VOLUME }, + {"LINE_MIXER_1", WM8994_LINE_MIXER_1 }, + {"LINE_MIXER_2", WM8994_LINE_MIXER_2 }, + {"ANTIPOP_1", WM8994_ANTIPOP_1 }, + {"LDO_1", WM8994_LDO_1 }, + {"LDO_2", WM8994_LDO_2 }, + {"AIF1_ADC1_LEFT_VOLUME", WM8994_AIF1_ADC1_LEFT_VOLUME }, + {"AIF1_ADC1_RIGHT_VOLUME", WM8994_AIF1_ADC1_RIGHT_VOLUME }, + {"AIF1_DAC1_LEFT_VOLUME", WM8994_AIF1_DAC1_LEFT_VOLUME }, + {"AIF1_DAC1_RIGHT_VOLUME", WM8994_AIF1_DAC1_RIGHT_VOLUME }, + {"AIF1_ADC2_LEFT_VOLUME", WM8994_AIF1_ADC2_LEFT_VOLUME }, + {"AIF1_ADC2_RIGHT_VOLUME", WM8994_AIF1_ADC2_RIGHT_VOLUME }, + {"AIF1_DAC2_LEFT_VOLUME", WM8994_AIF1_DAC2_LEFT_VOLUME }, + {"AIF1_DAC2_RIGHT_VOLUME", WM8994_AIF1_DAC2_RIGHT_VOLUME }, + {"AIF1_DAC1_FILTERS_2", WM8994_AIF1_DAC1_FILTERS_2 }, + {"AIF1_DAC2_FILTERS_2", WM8994_AIF1_DAC2_FILTERS_2 }, + {"AIF2_ADC_LEFT_VOLUME", WM8994_AIF2_ADC_LEFT_VOLUME }, + {"AIF2_ADC_RIGHT_VOLUME", WM8994_AIF2_ADC_RIGHT_VOLUME }, + {"AIF2_DAC_LEFT_VOLUME", WM8994_AIF2_DAC_LEFT_VOLUME }, + {"AIF2_DAC_RIGHT_VOLUME", WM8994_AIF2_DAC_RIGHT_VOLUME }, + {"AIF2_DAC_FILTERS_2", WM8994_AIF2_DAC_FILTERS_2 }, + {"DAC1_LEFT_VOLUME", WM8994_DAC1_LEFT_VOLUME }, + {"DAC1_RIGHT_VOLUME", WM8994_DAC1_RIGHT_VOLUME }, + {"DAC2_LEFT_VOLUME", WM8994_DAC2_LEFT_VOLUME }, + {"DAC2_RIGHT_VOLUME", WM8994_DAC2_RIGHT_VOLUME }, + {"POWER_MANAGEMENT_1", WM8994_POWER_MANAGEMENT_1 }, + {"POWER_MANAGEMENT_3", WM8994_POWER_MANAGEMENT_3 }, + {"ANTIPOP_2", WM8994_ANTIPOP_2 }, + {"AIF1_CLOCKING_1", WM8994_AIF1_CLOCKING_1 }, + {"FLL1_CONTROL_1", WM8994_FLL1_CONTROL_1 }, + {"FLL1_CONTROL_2", WM8994_FLL1_CONTROL_2 }, + {"FLL1_CONTROL_3", WM8994_FLL1_CONTROL_3 }, + {"FLL1_CONTROL_4", WM8994_FLL1_CONTROL_4 }, + {"FLL1_CONTROL_5", WM8994_FLL1_CONTROL_5 }, + {"AIF1_MASTER_SLAVE", WM8994_AIF1_MASTER_SLAVE }, + {"AIF1_BCLK", WM8994_AIF1_BCLK }, + {"AIF1DAC_LRCLK", WM8994_AIF1DAC_LRCLK }, + {"AIF1_DAC1_FILTERS_1", WM8994_AIF1_DAC1_FILTERS_1 }, +}; +#endif + +typedef struct { + I2CSlave i2c; + uint16_t i2c_addr; + int i2c_idx; + + uint16_t reg[WM8994_REGS_NUM]; + + void (*data_req)(void *, int); + void *opaque; + + SWVoiceOut *dac_voice; + QEMUSoundCard card; + + bool active; +} WM8994State; + +static struct { + int val, rate; +} srs[] = { + { 0, 8000 }, + { 1, 11025 }, + { 2, 12000 }, + { 3, 16000 }, + { 4, 22050 }, + { 5, 24000 }, + { 6, 32000 }, + { 7, 44100 }, + { 8, 48000 }, + { 9, 88200 }, + { 10, 96000 }, +}; + +#ifdef DEBUG_WM8994 +static char wm8994_regname_buff[50]; +static const char *wm8994_regname(WM8994State *s, + uint16_t address) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(wm8994_regs); i++) { + if (address == wm8994_regs[i].address) { + return wm8994_regs[i].name; + } + } + + snprintf( + &wm8994_regname_buff[0], + sizeof(wm8994_regname_buff), + "reg(%.4X)", + (unsigned int)address); + + return &wm8994_regname_buff[0]; +} +#endif + +static void wm8994_update_format(WM8994State *s); + +static int vmstate_wm8994_post_load(void *opaque, int version) +{ + WM8994State *s = opaque; + + DPRINTF("enter\n"); + + wm8994_update_format(s); + + return 0; +} + +static const VMStateDescription vmstate_wm8994 = { + .name = CODEC, + .version_id = 1, + .minimum_version_id = 1, + .minimum_version_id_old = 1, + .post_load = vmstate_wm8994_post_load, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(i2c, WM8994State), + VMSTATE_UINT16(i2c_addr, WM8994State), + VMSTATE_INT32(i2c_idx, WM8994State), + VMSTATE_UINT16_ARRAY(reg, WM8994State, + WM8994_REGS_NUM), + VMSTATE_BOOL(active, WM8994State), + VMSTATE_END_OF_LIST() + } +}; + +static uint8_t wm8994_volume(WM8994State *s, uint16_t reg) +{ + return (s->reg[reg] >> WM8994_SPKOUTL_VOL_SHIFT) & + (WM8994_SPKOUTL_VOL_MASK >> WM8994_SPKOUTL_VOL_SHIFT); +} + +static int wm8994_rate(WM8994State *s, uint16_t reg) +{ + int i; + int rate = (s->reg[reg] >> WM8994_AIF1_SR_SHIFT) & + (WM8994_AIF1_SR_MASK >> WM8994_AIF1_SR_SHIFT); + + for (i = 0; i < ARRAY_SIZE(srs); ++i) { + if (rate == srs[i].val) { + return srs[i].rate; + } + } + + return 0; +} + +static audfmt_e wm8994_wl(WM8994State *s, uint16_t reg) +{ + int wl = (s->reg[reg] >> WM8994_AIF1_WL_SHIFT) & + (WM8994_AIF1_WL_MASK >> WM8994_AIF1_WL_SHIFT); + + switch (wl) { + case 0: return AUD_FMT_S16; + case 3: return AUD_FMT_S32; + case 1: + /* + * Unsupported format (20 bits per channel), but kernel + * sets this sometimes when not playing anything + */ + return AUD_FMT_S16; + case 2: + /* + * Unsupported format (24 bits per channel), but kernel + * sets this sometimes when not playing anything + */ + return AUD_FMT_S16; + default: + hw_error("Unknown format\n"); + } +} + +static void wm8994_audio_out_cb(void *opaque, int free_b) +{ + WM8994State *s = (WM8994State *) opaque; + + if (s->data_req) { + s->data_req(s->opaque, free_b); + } +} + +static void wm8994_update_volume(WM8994State *s) +{ + int volume_left = wm8994_volume(s, WM8994_SPEAKER_VOLUME_LEFT); + int volume_right = wm8994_volume(s, WM8994_SPEAKER_VOLUME_RIGHT); + + if (s->dac_voice) { + /* Speaker */ + AUD_set_volume_out(s->dac_voice, 1, volume_left, volume_right); + } +} + +static void wm8994_update_active(WM8994State *s) +{ + if (s->dac_voice) { + AUD_set_active_out(s->dac_voice, s->active); + } +} + +static void wm8994_update_format(WM8994State *s) +{ + struct audsettings out_fmt; + + if (s->dac_voice) { + AUD_set_active_out(s->dac_voice, 0); + } + + /* Setup output */ + out_fmt.endianness = 0; + out_fmt.nchannels = 2; + out_fmt.freq = wm8994_rate(s, WM8994_AIF1_RATE); + out_fmt.fmt = wm8994_wl(s, WM8994_AIF1_CONTROL_1); + + s->dac_voice = + AUD_open_out(&s->card, + s->dac_voice, + CODEC ".speaker", + s, + wm8994_audio_out_cb, + &out_fmt); + + wm8994_update_volume(s); + + wm8994_update_active(s); +} + +static void wm8994_reset(DeviceState *dev) +{ + WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev)); + + DPRINTF("enter\n"); + + s->i2c_addr = 0; + s->i2c_idx = 0; + s->active = 0; + + memset(s->reg, 0, sizeof(s->reg)); + s->reg[WM8994_SOFTWARE_RESET] = 0x8994; + s->reg[WM8994_POWER_MANAGEMENT_2] = 0x6000; + s->reg[WM8994_SPEAKER_VOLUME_LEFT] = (0x79 << WM8994_SPKOUTL_VOL_SHIFT); + s->reg[WM8994_SPEAKER_VOLUME_RIGHT] = (0x79 << WM8994_SPKOUTL_VOL_SHIFT); + s->reg[WM8994_AIF1_RATE] = (srs[0].val << WM8994_AIF1_SR_SHIFT); + s->reg[WM8994_AIF1_CONTROL_1] = (0x0 << WM8994_AIF1_WL_SHIFT); + + wm8994_update_format(s); +} + +static int wm8994_tx(I2CSlave *i2c, uint8_t data) +{ + WM8994State *s = (WM8994State *) i2c; + + if (s->i2c_idx < sizeof(s->i2c_addr)) { + s->i2c_addr |= + (uint16_t)data << ((sizeof(s->i2c_addr) - s->i2c_idx - 1) * 8); + } else { + uint16_t offset = s->i2c_idx - sizeof(s->i2c_addr); + uint16_t addr = s->i2c_addr + (offset / 2); + + offset %= 2; + + if (addr >= WM8994_REGS_NUM) { + hw_error("illegal write offset 0x%X\n", s->i2c_addr + offset); + } else { + s->reg[addr] &= ~(0xFF << ((1 - offset) * 8)); + s->reg[addr] |= (uint16_t)data << ((1 - offset) * 8); + + if (offset == 1) { + DPRINTF("0x%.4X -> %s\n", + (unsigned int)s->reg[addr], + wm8994_regname(s, addr)); + switch (addr) { + case WM8994_SOFTWARE_RESET: + wm8994_reset(&s->i2c.qdev); + break; + + case WM8994_SPEAKER_VOLUME_LEFT: + case WM8994_SPEAKER_VOLUME_RIGHT: + wm8994_update_volume(s); + break; + + case WM8994_AIF1_RATE: + case WM8994_AIF1_CONTROL_1: + wm8994_update_format(s); + break; + + default: + break; + } + } + } + } + + ++s->i2c_idx; + + return 1; +} + +static int wm8994_rx(I2CSlave *i2c) +{ + WM8994State *s = (WM8994State *) i2c; + + if (s->i2c_idx >= sizeof(uint16_t)) { + DPRINTF("too much data requested (%i bytes)\n", (s->i2c_idx + 1)); + + return 0; + } + + if (s->i2c_addr < WM8994_REGS_NUM) { + int ret = (s->reg[s->i2c_addr] >> ((1 - s->i2c_idx) * 8)) & 0xFF; + + if (s->i2c_idx == (sizeof(uint16_t) - 1)) { + DPRINTF("%s -> 0x%.4X\n", + wm8994_regname(s, s->i2c_addr), + (unsigned int)s->reg[s->i2c_addr]); + } + + ++s->i2c_idx; + + return ret; + } else { + hw_error( + "wm8994: illegal read offset 0x%x\n", s->i2c_addr + s->i2c_idx); + } +} + +static void wm8994_event(I2CSlave *i2c, enum i2c_event event) +{ + WM8994State *s = (WM8994State *) i2c; + + switch (event) { + case I2C_START_SEND: + s->i2c_addr = 0; + case I2C_START_RECV: + s->i2c_idx = 0; + break; + default: + break; + } +} + +void wm8994_set_active(DeviceState *dev, bool active) +{ + WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev)); + + DPRINTF("enter %d\n", active); + + s->active = active; + + wm8994_update_active(s); +} + +void wm8994_data_req_set(DeviceState *dev, + void (*data_req)(void*, int), + void *opaque) +{ + WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev)); + + s->data_req = data_req; + s->opaque = opaque; +} + +int wm8994_dac_write(DeviceState *dev, void *buf, int num_bytes) +{ + WM8994State *s = FROM_I2C_SLAVE(WM8994State, I2C_SLAVE_FROM_QDEV(dev)); + + int sent = 0; + + if (!s->dac_voice) { + return 0; + } + + while (sent < num_bytes) { + int ret = AUD_write(s->dac_voice, + (uint8_t *)buf + sent, + num_bytes - sent); + + sent += ret; + + if (ret == 0) { + break; + } + } + + return sent; +} + +static int wm8994_init(I2CSlave *i2c) +{ + WM8994State *s = FROM_I2C_SLAVE(WM8994State, i2c); + + AUD_register_card(CODEC, &s->card); + + return 0; +} + +static void wm8994_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *sc = I2C_SLAVE_CLASS(klass); + + sc->init = wm8994_init; + sc->event = wm8994_event; + sc->recv = wm8994_rx; + sc->send = wm8994_tx; + dc->reset = wm8994_reset; + dc->vmsd = &vmstate_wm8994; +} + +static TypeInfo wm8994_info = { + .name = "wm8994", + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(WM8994State), + .class_init = wm8994_class_init, +}; + +static void wm8994_register_types(void) +{ + type_register_static(&wm8994_info); +} + +type_init(wm8994_register_types) diff --git a/hw/wm8994.h b/hw/wm8994.h new file mode 100644 index 0000000..c4053f6 --- /dev/null +++ b/hw/wm8994.h @@ -0,0 +1,14 @@ +#ifndef QEMU_HW_WM8994_H +#define QEMU_HW_WM8994_H + +#include "qdev.h" + +void wm8994_set_active(DeviceState *dev, bool active); + +void wm8994_data_req_set(DeviceState *dev, + void (*data_req)(void*, int), + void *opaque); + +int wm8994_dac_write(DeviceState *dev, void *buf, int num_bytes); + +#endif -- 2.7.4