e844f0c5d0
OBJECT_DECLARE_SIMPLE_TYPE() macro provides the OrIRQState declaration for free. Besides, the QOM code style is to use the structure name as typedef, and QEMU style is to use Camel Case, so rename qemu_or_irq as OrIRQState. Mechanical change using: $ sed -i -e 's/qemu_or_irq/OrIRQState/g' $(git grep -l qemu_or_irq) Signed-off-by: Philippe Mathieu-Daudé <philmd@linaro.org> Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Message-id: 20230113200138.52869-5-philmd@linaro.org Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
854 lines
30 KiB
C
854 lines
30 KiB
C
/*
|
|
* Samsung exynos4210 SoC emulation
|
|
*
|
|
* Copyright (c) 2011 Samsung Electronics Co., Ltd. All rights reserved.
|
|
* Maksim Kozlov <m.kozlov@samsung.com>
|
|
* Evgeny Voevodin <e.voevodin@samsung.com>
|
|
* Igor Mitsyanko <i.mitsyanko@samsung.com>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License as published by the
|
|
* Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qapi/error.h"
|
|
#include "cpu.h"
|
|
#include "hw/cpu/a9mpcore.h"
|
|
#include "hw/irq.h"
|
|
#include "sysemu/blockdev.h"
|
|
#include "sysemu/sysemu.h"
|
|
#include "hw/sysbus.h"
|
|
#include "hw/arm/boot.h"
|
|
#include "hw/loader.h"
|
|
#include "hw/qdev-properties.h"
|
|
#include "hw/arm/exynos4210.h"
|
|
#include "hw/sd/sdhci.h"
|
|
#include "hw/usb/hcd-ehci.h"
|
|
|
|
#define EXYNOS4210_CHIPID_ADDR 0x10000000
|
|
|
|
/* PWM */
|
|
#define EXYNOS4210_PWM_BASE_ADDR 0x139D0000
|
|
|
|
/* RTC */
|
|
#define EXYNOS4210_RTC_BASE_ADDR 0x10070000
|
|
|
|
/* MCT */
|
|
#define EXYNOS4210_MCT_BASE_ADDR 0x10050000
|
|
|
|
/* I2C */
|
|
#define EXYNOS4210_I2C_SHIFT 0x00010000
|
|
#define EXYNOS4210_I2C_BASE_ADDR 0x13860000
|
|
/* Interrupt Group of External Interrupt Combiner for I2C */
|
|
#define EXYNOS4210_I2C_INTG 27
|
|
#define EXYNOS4210_HDMI_INTG 16
|
|
|
|
/* UART's definitions */
|
|
#define EXYNOS4210_UART0_BASE_ADDR 0x13800000
|
|
#define EXYNOS4210_UART1_BASE_ADDR 0x13810000
|
|
#define EXYNOS4210_UART2_BASE_ADDR 0x13820000
|
|
#define EXYNOS4210_UART3_BASE_ADDR 0x13830000
|
|
#define EXYNOS4210_UART0_FIFO_SIZE 256
|
|
#define EXYNOS4210_UART1_FIFO_SIZE 64
|
|
#define EXYNOS4210_UART2_FIFO_SIZE 16
|
|
#define EXYNOS4210_UART3_FIFO_SIZE 16
|
|
/* Interrupt Group of External Interrupt Combiner for UART */
|
|
#define EXYNOS4210_UART_INT_GRP 26
|
|
|
|
/* External GIC */
|
|
#define EXYNOS4210_EXT_GIC_CPU_BASE_ADDR 0x10480000
|
|
#define EXYNOS4210_EXT_GIC_DIST_BASE_ADDR 0x10490000
|
|
|
|
/* Combiner */
|
|
#define EXYNOS4210_EXT_COMBINER_BASE_ADDR 0x10440000
|
|
#define EXYNOS4210_INT_COMBINER_BASE_ADDR 0x10448000
|
|
|
|
/* SD/MMC host controllers */
|
|
#define EXYNOS4210_SDHCI_CAPABILITIES 0x05E80080
|
|
#define EXYNOS4210_SDHCI_BASE_ADDR 0x12510000
|
|
#define EXYNOS4210_SDHCI_ADDR(n) (EXYNOS4210_SDHCI_BASE_ADDR + \
|
|
0x00010000 * (n))
|
|
#define EXYNOS4210_SDHCI_NUMBER 4
|
|
|
|
/* PMU SFR base address */
|
|
#define EXYNOS4210_PMU_BASE_ADDR 0x10020000
|
|
|
|
/* Clock controller SFR base address */
|
|
#define EXYNOS4210_CLK_BASE_ADDR 0x10030000
|
|
|
|
/* PRNG/HASH SFR base address */
|
|
#define EXYNOS4210_RNG_BASE_ADDR 0x10830400
|
|
|
|
/* Display controllers (FIMD) */
|
|
#define EXYNOS4210_FIMD0_BASE_ADDR 0x11C00000
|
|
|
|
/* EHCI */
|
|
#define EXYNOS4210_EHCI_BASE_ADDR 0x12580000
|
|
|
|
/* DMA */
|
|
#define EXYNOS4210_PL330_BASE0_ADDR 0x12680000
|
|
#define EXYNOS4210_PL330_BASE1_ADDR 0x12690000
|
|
#define EXYNOS4210_PL330_BASE2_ADDR 0x12850000
|
|
|
|
enum ExtGicId {
|
|
EXT_GIC_ID_MDMA_LCD0 = 66,
|
|
EXT_GIC_ID_PDMA0,
|
|
EXT_GIC_ID_PDMA1,
|
|
EXT_GIC_ID_TIMER0,
|
|
EXT_GIC_ID_TIMER1,
|
|
EXT_GIC_ID_TIMER2,
|
|
EXT_GIC_ID_TIMER3,
|
|
EXT_GIC_ID_TIMER4,
|
|
EXT_GIC_ID_MCT_L0,
|
|
EXT_GIC_ID_WDT,
|
|
EXT_GIC_ID_RTC_ALARM,
|
|
EXT_GIC_ID_RTC_TIC,
|
|
EXT_GIC_ID_GPIO_XB,
|
|
EXT_GIC_ID_GPIO_XA,
|
|
EXT_GIC_ID_MCT_L1,
|
|
EXT_GIC_ID_IEM_APC,
|
|
EXT_GIC_ID_IEM_IEC,
|
|
EXT_GIC_ID_NFC,
|
|
EXT_GIC_ID_UART0,
|
|
EXT_GIC_ID_UART1,
|
|
EXT_GIC_ID_UART2,
|
|
EXT_GIC_ID_UART3,
|
|
EXT_GIC_ID_UART4,
|
|
EXT_GIC_ID_MCT_G0,
|
|
EXT_GIC_ID_I2C0,
|
|
EXT_GIC_ID_I2C1,
|
|
EXT_GIC_ID_I2C2,
|
|
EXT_GIC_ID_I2C3,
|
|
EXT_GIC_ID_I2C4,
|
|
EXT_GIC_ID_I2C5,
|
|
EXT_GIC_ID_I2C6,
|
|
EXT_GIC_ID_I2C7,
|
|
EXT_GIC_ID_SPI0,
|
|
EXT_GIC_ID_SPI1,
|
|
EXT_GIC_ID_SPI2,
|
|
EXT_GIC_ID_MCT_G1,
|
|
EXT_GIC_ID_USB_HOST,
|
|
EXT_GIC_ID_USB_DEVICE,
|
|
EXT_GIC_ID_MODEMIF,
|
|
EXT_GIC_ID_HSMMC0,
|
|
EXT_GIC_ID_HSMMC1,
|
|
EXT_GIC_ID_HSMMC2,
|
|
EXT_GIC_ID_HSMMC3,
|
|
EXT_GIC_ID_SDMMC,
|
|
EXT_GIC_ID_MIPI_CSI_4LANE,
|
|
EXT_GIC_ID_MIPI_DSI_4LANE,
|
|
EXT_GIC_ID_MIPI_CSI_2LANE,
|
|
EXT_GIC_ID_MIPI_DSI_2LANE,
|
|
EXT_GIC_ID_ONENAND_AUDI,
|
|
EXT_GIC_ID_ROTATOR,
|
|
EXT_GIC_ID_FIMC0,
|
|
EXT_GIC_ID_FIMC1,
|
|
EXT_GIC_ID_FIMC2,
|
|
EXT_GIC_ID_FIMC3,
|
|
EXT_GIC_ID_JPEG,
|
|
EXT_GIC_ID_2D,
|
|
EXT_GIC_ID_PCIe,
|
|
EXT_GIC_ID_MIXER,
|
|
EXT_GIC_ID_HDMI,
|
|
EXT_GIC_ID_HDMI_I2C,
|
|
EXT_GIC_ID_MFC,
|
|
EXT_GIC_ID_TVENC,
|
|
};
|
|
|
|
enum ExtInt {
|
|
EXT_GIC_ID_EXTINT0 = 48,
|
|
EXT_GIC_ID_EXTINT1,
|
|
EXT_GIC_ID_EXTINT2,
|
|
EXT_GIC_ID_EXTINT3,
|
|
EXT_GIC_ID_EXTINT4,
|
|
EXT_GIC_ID_EXTINT5,
|
|
EXT_GIC_ID_EXTINT6,
|
|
EXT_GIC_ID_EXTINT7,
|
|
EXT_GIC_ID_EXTINT8,
|
|
EXT_GIC_ID_EXTINT9,
|
|
EXT_GIC_ID_EXTINT10,
|
|
EXT_GIC_ID_EXTINT11,
|
|
EXT_GIC_ID_EXTINT12,
|
|
EXT_GIC_ID_EXTINT13,
|
|
EXT_GIC_ID_EXTINT14,
|
|
EXT_GIC_ID_EXTINT15
|
|
};
|
|
|
|
/*
|
|
* External GIC sources which are not from External Interrupt Combiner or
|
|
* External Interrupts are starting from EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ,
|
|
* which is INTG16 in Internal Interrupt Combiner.
|
|
*/
|
|
|
|
static const uint32_t
|
|
combiner_grp_to_gic_id[64 - EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][8] = {
|
|
/* int combiner groups 16-19 */
|
|
{ }, { }, { }, { },
|
|
/* int combiner group 20 */
|
|
{ 0, EXT_GIC_ID_MDMA_LCD0 },
|
|
/* int combiner group 21 */
|
|
{ EXT_GIC_ID_PDMA0, EXT_GIC_ID_PDMA1 },
|
|
/* int combiner group 22 */
|
|
{ EXT_GIC_ID_TIMER0, EXT_GIC_ID_TIMER1, EXT_GIC_ID_TIMER2,
|
|
EXT_GIC_ID_TIMER3, EXT_GIC_ID_TIMER4 },
|
|
/* int combiner group 23 */
|
|
{ EXT_GIC_ID_RTC_ALARM, EXT_GIC_ID_RTC_TIC },
|
|
/* int combiner group 24 */
|
|
{ EXT_GIC_ID_GPIO_XB, EXT_GIC_ID_GPIO_XA },
|
|
/* int combiner group 25 */
|
|
{ EXT_GIC_ID_IEM_APC, EXT_GIC_ID_IEM_IEC },
|
|
/* int combiner group 26 */
|
|
{ EXT_GIC_ID_UART0, EXT_GIC_ID_UART1, EXT_GIC_ID_UART2, EXT_GIC_ID_UART3,
|
|
EXT_GIC_ID_UART4 },
|
|
/* int combiner group 27 */
|
|
{ EXT_GIC_ID_I2C0, EXT_GIC_ID_I2C1, EXT_GIC_ID_I2C2, EXT_GIC_ID_I2C3,
|
|
EXT_GIC_ID_I2C4, EXT_GIC_ID_I2C5, EXT_GIC_ID_I2C6,
|
|
EXT_GIC_ID_I2C7 },
|
|
/* int combiner group 28 */
|
|
{ EXT_GIC_ID_SPI0, EXT_GIC_ID_SPI1, EXT_GIC_ID_SPI2 , EXT_GIC_ID_USB_HOST},
|
|
/* int combiner group 29 */
|
|
{ EXT_GIC_ID_HSMMC0, EXT_GIC_ID_HSMMC1, EXT_GIC_ID_HSMMC2,
|
|
EXT_GIC_ID_HSMMC3, EXT_GIC_ID_SDMMC },
|
|
/* int combiner group 30 */
|
|
{ EXT_GIC_ID_MIPI_CSI_4LANE, EXT_GIC_ID_MIPI_CSI_2LANE },
|
|
/* int combiner group 31 */
|
|
{ EXT_GIC_ID_MIPI_DSI_4LANE, EXT_GIC_ID_MIPI_DSI_2LANE },
|
|
/* int combiner group 32 */
|
|
{ EXT_GIC_ID_FIMC0, EXT_GIC_ID_FIMC1 },
|
|
/* int combiner group 33 */
|
|
{ EXT_GIC_ID_FIMC2, EXT_GIC_ID_FIMC3 },
|
|
/* int combiner group 34 */
|
|
{ EXT_GIC_ID_ONENAND_AUDI, EXT_GIC_ID_NFC },
|
|
/* int combiner group 35 */
|
|
{ 0, 0, 0, EXT_GIC_ID_MCT_L1 },
|
|
/* int combiner group 36 */
|
|
{ EXT_GIC_ID_MIXER },
|
|
/* int combiner group 37 */
|
|
{ EXT_GIC_ID_EXTINT4, EXT_GIC_ID_EXTINT5, EXT_GIC_ID_EXTINT6,
|
|
EXT_GIC_ID_EXTINT7 },
|
|
/* groups 38-50 */
|
|
{ }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { }, { },
|
|
/* int combiner group 51 */
|
|
{ EXT_GIC_ID_MCT_L0 },
|
|
/* group 52 */
|
|
{ },
|
|
/* int combiner group 53 */
|
|
{ EXT_GIC_ID_WDT },
|
|
/* groups 54-63 */
|
|
{ }, { }, { }, { }, { }, { }, { }, { }, { }, { }
|
|
};
|
|
|
|
#define EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit) ((grp) * 8 + (bit))
|
|
#define EXYNOS4210_COMBINER_GET_GRP_NUM(irq) ((irq) / 8)
|
|
#define EXYNOS4210_COMBINER_GET_BIT_NUM(irq) \
|
|
((irq) - 8 * EXYNOS4210_COMBINER_GET_GRP_NUM(irq))
|
|
|
|
/*
|
|
* Some interrupt lines go to multiple combiner inputs.
|
|
* This data structure defines those: each array element is
|
|
* a list of combiner inputs which are connected together;
|
|
* the one with the smallest interrupt ID value must be first.
|
|
* As with combiner_grp_to_gic_id[], we rely on (0, 0) not being
|
|
* wired to anything so we can use 0 as a terminator.
|
|
*/
|
|
#define IRQNO(G, B) EXYNOS4210_COMBINER_GET_IRQ_NUM(G, B)
|
|
#define IRQNONE 0
|
|
|
|
#define COMBINERMAP_SIZE 16
|
|
|
|
static const int combinermap[COMBINERMAP_SIZE][6] = {
|
|
/* MDNIE_LCD1 */
|
|
{ IRQNO(0, 4), IRQNO(1, 0), IRQNONE },
|
|
{ IRQNO(0, 5), IRQNO(1, 1), IRQNONE },
|
|
{ IRQNO(0, 6), IRQNO(1, 2), IRQNONE },
|
|
{ IRQNO(0, 7), IRQNO(1, 3), IRQNONE },
|
|
/* TMU */
|
|
{ IRQNO(2, 4), IRQNO(3, 4), IRQNONE },
|
|
{ IRQNO(2, 5), IRQNO(3, 5), IRQNONE },
|
|
{ IRQNO(2, 6), IRQNO(3, 6), IRQNONE },
|
|
{ IRQNO(2, 7), IRQNO(3, 7), IRQNONE },
|
|
/* LCD1 */
|
|
{ IRQNO(11, 4), IRQNO(12, 0), IRQNONE },
|
|
{ IRQNO(11, 5), IRQNO(12, 1), IRQNONE },
|
|
{ IRQNO(11, 6), IRQNO(12, 2), IRQNONE },
|
|
{ IRQNO(11, 7), IRQNO(12, 3), IRQNONE },
|
|
/* Multi-core timer */
|
|
{ IRQNO(1, 4), IRQNO(12, 4), IRQNO(35, 4), IRQNO(51, 4), IRQNO(53, 4), IRQNONE },
|
|
{ IRQNO(1, 5), IRQNO(12, 5), IRQNO(35, 5), IRQNO(51, 5), IRQNO(53, 5), IRQNONE },
|
|
{ IRQNO(1, 6), IRQNO(12, 6), IRQNO(35, 6), IRQNO(51, 6), IRQNO(53, 6), IRQNONE },
|
|
{ IRQNO(1, 7), IRQNO(12, 7), IRQNO(35, 7), IRQNO(51, 7), IRQNO(53, 7), IRQNONE },
|
|
};
|
|
|
|
#undef IRQNO
|
|
|
|
static const int *combinermap_entry(int irq)
|
|
{
|
|
/*
|
|
* If the interrupt number passed in is the first entry in some
|
|
* line of the combinermap, return a pointer to that line;
|
|
* otherwise return NULL.
|
|
*/
|
|
int i;
|
|
for (i = 0; i < COMBINERMAP_SIZE; i++) {
|
|
if (combinermap[i][0] == irq) {
|
|
return combinermap[i];
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int mapline_size(const int *mapline)
|
|
{
|
|
/* Return number of entries in this mapline in total */
|
|
int i = 0;
|
|
|
|
if (!mapline) {
|
|
/* Not in the map? IRQ goes to exactly one combiner input */
|
|
return 1;
|
|
}
|
|
while (*mapline != IRQNONE) {
|
|
mapline++;
|
|
i++;
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* Initialize board IRQs.
|
|
* These IRQs contain splitted Int/External Combiner and External Gic IRQs.
|
|
*/
|
|
static void exynos4210_init_board_irqs(Exynos4210State *s)
|
|
{
|
|
uint32_t grp, bit, irq_id, n;
|
|
DeviceState *extgicdev = DEVICE(&s->ext_gic);
|
|
DeviceState *intcdev = DEVICE(&s->int_combiner);
|
|
DeviceState *extcdev = DEVICE(&s->ext_combiner);
|
|
int splitcount = 0;
|
|
DeviceState *splitter;
|
|
const int *mapline;
|
|
int numlines, splitin, in;
|
|
|
|
for (n = 0; n < EXYNOS4210_MAX_EXT_COMBINER_IN_IRQ; n++) {
|
|
irq_id = 0;
|
|
if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 4)) {
|
|
/* MCT_G0 is passed to External GIC */
|
|
irq_id = EXT_GIC_ID_MCT_G0;
|
|
}
|
|
if (n == EXYNOS4210_COMBINER_GET_IRQ_NUM(1, 5)) {
|
|
/* MCT_G1 is passed to External and GIC */
|
|
irq_id = EXT_GIC_ID_MCT_G1;
|
|
}
|
|
|
|
if (s->irq_table[n]) {
|
|
/*
|
|
* This must be some non-first entry in a combinermap line,
|
|
* and we've already filled it in.
|
|
*/
|
|
continue;
|
|
}
|
|
mapline = combinermap_entry(n);
|
|
/*
|
|
* We need to connect the IRQ to multiple inputs on both combiners
|
|
* and possibly also to the external GIC.
|
|
*/
|
|
numlines = 2 * mapline_size(mapline);
|
|
if (irq_id) {
|
|
numlines++;
|
|
}
|
|
assert(splitcount < EXYNOS4210_NUM_SPLITTERS);
|
|
splitter = DEVICE(&s->splitter[splitcount]);
|
|
qdev_prop_set_uint16(splitter, "num-lines", numlines);
|
|
qdev_realize(splitter, NULL, &error_abort);
|
|
splitcount++;
|
|
|
|
in = n;
|
|
splitin = 0;
|
|
for (;;) {
|
|
s->irq_table[in] = qdev_get_gpio_in(splitter, 0);
|
|
qdev_connect_gpio_out(splitter, splitin,
|
|
qdev_get_gpio_in(intcdev, in));
|
|
qdev_connect_gpio_out(splitter, splitin + 1,
|
|
qdev_get_gpio_in(extcdev, in));
|
|
splitin += 2;
|
|
if (!mapline) {
|
|
break;
|
|
}
|
|
mapline++;
|
|
in = *mapline;
|
|
if (in == IRQNONE) {
|
|
break;
|
|
}
|
|
}
|
|
if (irq_id) {
|
|
qdev_connect_gpio_out(splitter, splitin,
|
|
qdev_get_gpio_in(extgicdev, irq_id - 32));
|
|
}
|
|
}
|
|
for (; n < EXYNOS4210_MAX_INT_COMBINER_IN_IRQ; n++) {
|
|
/* these IDs are passed to Internal Combiner and External GIC */
|
|
grp = EXYNOS4210_COMBINER_GET_GRP_NUM(n);
|
|
bit = EXYNOS4210_COMBINER_GET_BIT_NUM(n);
|
|
irq_id = combiner_grp_to_gic_id[grp -
|
|
EXYNOS4210_MAX_EXT_COMBINER_OUT_IRQ][bit];
|
|
|
|
if (s->irq_table[n]) {
|
|
/*
|
|
* This must be some non-first entry in a combinermap line,
|
|
* and we've already filled it in.
|
|
*/
|
|
continue;
|
|
}
|
|
|
|
if (irq_id) {
|
|
assert(splitcount < EXYNOS4210_NUM_SPLITTERS);
|
|
splitter = DEVICE(&s->splitter[splitcount]);
|
|
qdev_prop_set_uint16(splitter, "num-lines", 2);
|
|
qdev_realize(splitter, NULL, &error_abort);
|
|
splitcount++;
|
|
s->irq_table[n] = qdev_get_gpio_in(splitter, 0);
|
|
qdev_connect_gpio_out(splitter, 0, qdev_get_gpio_in(intcdev, n));
|
|
qdev_connect_gpio_out(splitter, 1,
|
|
qdev_get_gpio_in(extgicdev, irq_id - 32));
|
|
} else {
|
|
s->irq_table[n] = qdev_get_gpio_in(intcdev, n);
|
|
}
|
|
}
|
|
/*
|
|
* We check this here to avoid a more obscure assert later when
|
|
* qdev_assert_realized_properly() checks that we realized every
|
|
* child object we initialized.
|
|
*/
|
|
assert(splitcount == EXYNOS4210_NUM_SPLITTERS);
|
|
}
|
|
|
|
/*
|
|
* Get IRQ number from exynos4210 IRQ subsystem stub.
|
|
* To identify IRQ source use internal combiner group and bit number
|
|
* grp - group number
|
|
* bit - bit number inside group
|
|
*/
|
|
uint32_t exynos4210_get_irq(uint32_t grp, uint32_t bit)
|
|
{
|
|
return EXYNOS4210_COMBINER_GET_IRQ_NUM(grp, bit);
|
|
}
|
|
|
|
static uint8_t chipid_and_omr[] = { 0x11, 0x02, 0x21, 0x43,
|
|
0x09, 0x00, 0x00, 0x00 };
|
|
|
|
static uint64_t exynos4210_chipid_and_omr_read(void *opaque, hwaddr offset,
|
|
unsigned size)
|
|
{
|
|
assert(offset < sizeof(chipid_and_omr));
|
|
return chipid_and_omr[offset];
|
|
}
|
|
|
|
static void exynos4210_chipid_and_omr_write(void *opaque, hwaddr offset,
|
|
uint64_t value, unsigned size)
|
|
{
|
|
return;
|
|
}
|
|
|
|
static const MemoryRegionOps exynos4210_chipid_and_omr_ops = {
|
|
.read = exynos4210_chipid_and_omr_read,
|
|
.write = exynos4210_chipid_and_omr_write,
|
|
.endianness = DEVICE_NATIVE_ENDIAN,
|
|
.impl = {
|
|
.max_access_size = 1,
|
|
}
|
|
};
|
|
|
|
void exynos4210_write_secondary(ARMCPU *cpu,
|
|
const struct arm_boot_info *info)
|
|
{
|
|
int n;
|
|
uint32_t smpboot[] = {
|
|
0xe59f3034, /* ldr r3, External gic_cpu_if */
|
|
0xe59f2034, /* ldr r2, Internal gic_cpu_if */
|
|
0xe59f0034, /* ldr r0, startaddr */
|
|
0xe3a01001, /* mov r1, #1 */
|
|
0xe5821000, /* str r1, [r2] */
|
|
0xe5831000, /* str r1, [r3] */
|
|
0xe3a010ff, /* mov r1, #0xff */
|
|
0xe5821004, /* str r1, [r2, #4] */
|
|
0xe5831004, /* str r1, [r3, #4] */
|
|
0xf57ff04f, /* dsb */
|
|
0xe320f003, /* wfi */
|
|
0xe5901000, /* ldr r1, [r0] */
|
|
0xe1110001, /* tst r1, r1 */
|
|
0x0afffffb, /* beq <wfi> */
|
|
0xe12fff11, /* bx r1 */
|
|
EXYNOS4210_EXT_GIC_CPU_BASE_ADDR,
|
|
0, /* gic_cpu_if: base address of Internal GIC CPU interface */
|
|
0 /* bootreg: Boot register address is held here */
|
|
};
|
|
smpboot[ARRAY_SIZE(smpboot) - 1] = info->smp_bootreg_addr;
|
|
smpboot[ARRAY_SIZE(smpboot) - 2] = info->gic_cpu_if_addr;
|
|
for (n = 0; n < ARRAY_SIZE(smpboot); n++) {
|
|
smpboot[n] = tswap32(smpboot[n]);
|
|
}
|
|
rom_add_blob_fixed("smpboot", smpboot, sizeof(smpboot),
|
|
info->smp_loader_start);
|
|
}
|
|
|
|
static uint64_t exynos4210_calc_affinity(int cpu)
|
|
{
|
|
/* Exynos4210 has 0x9 as cluster ID */
|
|
return (0x9 << ARM_AFF1_SHIFT) | cpu;
|
|
}
|
|
|
|
static DeviceState *pl330_create(uint32_t base, OrIRQState *orgate,
|
|
qemu_irq irq, int nreq, int nevents, int width)
|
|
{
|
|
SysBusDevice *busdev;
|
|
DeviceState *dev;
|
|
int i;
|
|
|
|
dev = qdev_new("pl330");
|
|
object_property_set_link(OBJECT(dev), "memory",
|
|
OBJECT(get_system_memory()),
|
|
&error_fatal);
|
|
qdev_prop_set_uint8(dev, "num_events", nevents);
|
|
qdev_prop_set_uint8(dev, "num_chnls", 8);
|
|
qdev_prop_set_uint8(dev, "num_periph_req", nreq);
|
|
|
|
qdev_prop_set_uint8(dev, "wr_cap", 4);
|
|
qdev_prop_set_uint8(dev, "wr_q_dep", 8);
|
|
qdev_prop_set_uint8(dev, "rd_cap", 4);
|
|
qdev_prop_set_uint8(dev, "rd_q_dep", 8);
|
|
qdev_prop_set_uint8(dev, "data_width", width);
|
|
qdev_prop_set_uint16(dev, "data_buffer_dep", width);
|
|
busdev = SYS_BUS_DEVICE(dev);
|
|
sysbus_realize_and_unref(busdev, &error_fatal);
|
|
sysbus_mmio_map(busdev, 0, base);
|
|
|
|
object_property_set_int(OBJECT(orgate), "num-lines", nevents + 1,
|
|
&error_abort);
|
|
qdev_realize(DEVICE(orgate), NULL, &error_abort);
|
|
|
|
for (i = 0; i < nevents + 1; i++) {
|
|
sysbus_connect_irq(busdev, i, qdev_get_gpio_in(DEVICE(orgate), i));
|
|
}
|
|
qdev_connect_gpio_out(DEVICE(orgate), 0, irq);
|
|
return dev;
|
|
}
|
|
|
|
static void exynos4210_realize(DeviceState *socdev, Error **errp)
|
|
{
|
|
Exynos4210State *s = EXYNOS4210_SOC(socdev);
|
|
MemoryRegion *system_mem = get_system_memory();
|
|
SysBusDevice *busdev;
|
|
DeviceState *dev, *uart[4], *pl330[3];
|
|
int i, n;
|
|
|
|
for (n = 0; n < EXYNOS4210_NCPUS; n++) {
|
|
Object *cpuobj = object_new(ARM_CPU_TYPE_NAME("cortex-a9"));
|
|
|
|
/* By default A9 CPUs have EL3 enabled. This board does not currently
|
|
* support EL3 so the CPU EL3 property is disabled before realization.
|
|
*/
|
|
if (object_property_find(cpuobj, "has_el3")) {
|
|
object_property_set_bool(cpuobj, "has_el3", false, &error_fatal);
|
|
}
|
|
|
|
s->cpu[n] = ARM_CPU(cpuobj);
|
|
object_property_set_int(cpuobj, "mp-affinity",
|
|
exynos4210_calc_affinity(n), &error_abort);
|
|
object_property_set_int(cpuobj, "reset-cbar",
|
|
EXYNOS4210_SMP_PRIVATE_BASE_ADDR,
|
|
&error_abort);
|
|
qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
|
|
}
|
|
|
|
/* IRQ Gate */
|
|
for (i = 0; i < EXYNOS4210_NCPUS; i++) {
|
|
DeviceState *orgate = DEVICE(&s->cpu_irq_orgate[i]);
|
|
object_property_set_int(OBJECT(orgate), "num-lines",
|
|
EXYNOS4210_IRQ_GATE_NINPUTS,
|
|
&error_abort);
|
|
qdev_realize(orgate, NULL, &error_abort);
|
|
qdev_connect_gpio_out(orgate, 0,
|
|
qdev_get_gpio_in(DEVICE(s->cpu[i]), ARM_CPU_IRQ));
|
|
}
|
|
|
|
/* Private memory region and Internal GIC */
|
|
qdev_prop_set_uint32(DEVICE(&s->a9mpcore), "num-cpu", EXYNOS4210_NCPUS);
|
|
busdev = SYS_BUS_DEVICE(&s->a9mpcore);
|
|
sysbus_realize(busdev, &error_fatal);
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_SMP_PRIVATE_BASE_ADDR);
|
|
for (n = 0; n < EXYNOS4210_NCPUS; n++) {
|
|
sysbus_connect_irq(busdev, n,
|
|
qdev_get_gpio_in(DEVICE(&s->cpu_irq_orgate[n]), 0));
|
|
}
|
|
|
|
/* Cache controller */
|
|
sysbus_create_simple("l2x0", EXYNOS4210_L2X0_BASE_ADDR, NULL);
|
|
|
|
/* External GIC */
|
|
qdev_prop_set_uint32(DEVICE(&s->ext_gic), "num-cpu", EXYNOS4210_NCPUS);
|
|
busdev = SYS_BUS_DEVICE(&s->ext_gic);
|
|
sysbus_realize(busdev, &error_fatal);
|
|
/* Map CPU interface */
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_GIC_CPU_BASE_ADDR);
|
|
/* Map Distributer interface */
|
|
sysbus_mmio_map(busdev, 1, EXYNOS4210_EXT_GIC_DIST_BASE_ADDR);
|
|
for (n = 0; n < EXYNOS4210_NCPUS; n++) {
|
|
sysbus_connect_irq(busdev, n,
|
|
qdev_get_gpio_in(DEVICE(&s->cpu_irq_orgate[n]), 1));
|
|
}
|
|
|
|
/* Internal Interrupt Combiner */
|
|
busdev = SYS_BUS_DEVICE(&s->int_combiner);
|
|
sysbus_realize(busdev, &error_fatal);
|
|
for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
|
|
sysbus_connect_irq(busdev, n,
|
|
qdev_get_gpio_in(DEVICE(&s->a9mpcore), n));
|
|
}
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_INT_COMBINER_BASE_ADDR);
|
|
|
|
/* External Interrupt Combiner */
|
|
qdev_prop_set_uint32(DEVICE(&s->ext_combiner), "external", 1);
|
|
busdev = SYS_BUS_DEVICE(&s->ext_combiner);
|
|
sysbus_realize(busdev, &error_fatal);
|
|
for (n = 0; n < EXYNOS4210_MAX_INT_COMBINER_OUT_IRQ; n++) {
|
|
sysbus_connect_irq(busdev, n, qdev_get_gpio_in(DEVICE(&s->ext_gic), n));
|
|
}
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_EXT_COMBINER_BASE_ADDR);
|
|
|
|
/* Initialize board IRQs. */
|
|
exynos4210_init_board_irqs(s);
|
|
|
|
/*** Memory ***/
|
|
|
|
/* Chip-ID and OMR */
|
|
memory_region_init_io(&s->chipid_mem, OBJECT(socdev),
|
|
&exynos4210_chipid_and_omr_ops, NULL,
|
|
"exynos4210.chipid", sizeof(chipid_and_omr));
|
|
memory_region_add_subregion(system_mem, EXYNOS4210_CHIPID_ADDR,
|
|
&s->chipid_mem);
|
|
|
|
/* Internal ROM */
|
|
memory_region_init_rom(&s->irom_mem, OBJECT(socdev), "exynos4210.irom",
|
|
EXYNOS4210_IROM_SIZE, &error_fatal);
|
|
memory_region_add_subregion(system_mem, EXYNOS4210_IROM_BASE_ADDR,
|
|
&s->irom_mem);
|
|
/* mirror of iROM */
|
|
memory_region_init_alias(&s->irom_alias_mem, OBJECT(socdev),
|
|
"exynos4210.irom_alias", &s->irom_mem, 0,
|
|
EXYNOS4210_IROM_SIZE);
|
|
memory_region_add_subregion(system_mem, EXYNOS4210_IROM_MIRROR_BASE_ADDR,
|
|
&s->irom_alias_mem);
|
|
|
|
/* Internal RAM */
|
|
memory_region_init_ram(&s->iram_mem, NULL, "exynos4210.iram",
|
|
EXYNOS4210_IRAM_SIZE, &error_fatal);
|
|
memory_region_add_subregion(system_mem, EXYNOS4210_IRAM_BASE_ADDR,
|
|
&s->iram_mem);
|
|
|
|
/* PMU.
|
|
* The only reason of existence at the moment is that secondary CPU boot
|
|
* loader uses PMU INFORM5 register as a holding pen.
|
|
*/
|
|
sysbus_create_simple("exynos4210.pmu", EXYNOS4210_PMU_BASE_ADDR, NULL);
|
|
|
|
sysbus_create_simple("exynos4210.clk", EXYNOS4210_CLK_BASE_ADDR, NULL);
|
|
sysbus_create_simple("exynos4210.rng", EXYNOS4210_RNG_BASE_ADDR, NULL);
|
|
|
|
/* PWM */
|
|
sysbus_create_varargs("exynos4210.pwm", EXYNOS4210_PWM_BASE_ADDR,
|
|
s->irq_table[exynos4210_get_irq(22, 0)],
|
|
s->irq_table[exynos4210_get_irq(22, 1)],
|
|
s->irq_table[exynos4210_get_irq(22, 2)],
|
|
s->irq_table[exynos4210_get_irq(22, 3)],
|
|
s->irq_table[exynos4210_get_irq(22, 4)],
|
|
NULL);
|
|
/* RTC */
|
|
sysbus_create_varargs("exynos4210.rtc", EXYNOS4210_RTC_BASE_ADDR,
|
|
s->irq_table[exynos4210_get_irq(23, 0)],
|
|
s->irq_table[exynos4210_get_irq(23, 1)],
|
|
NULL);
|
|
|
|
/* Multi Core Timer */
|
|
dev = qdev_new("exynos4210.mct");
|
|
busdev = SYS_BUS_DEVICE(dev);
|
|
sysbus_realize_and_unref(busdev, &error_fatal);
|
|
for (n = 0; n < 4; n++) {
|
|
/* Connect global timer interrupts to Combiner gpio_in */
|
|
sysbus_connect_irq(busdev, n,
|
|
s->irq_table[exynos4210_get_irq(1, 4 + n)]);
|
|
}
|
|
/* Connect local timer interrupts to Combiner gpio_in */
|
|
sysbus_connect_irq(busdev, 4,
|
|
s->irq_table[exynos4210_get_irq(51, 0)]);
|
|
sysbus_connect_irq(busdev, 5,
|
|
s->irq_table[exynos4210_get_irq(35, 3)]);
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_MCT_BASE_ADDR);
|
|
|
|
/*** I2C ***/
|
|
for (n = 0; n < EXYNOS4210_I2C_NUMBER; n++) {
|
|
uint32_t addr = EXYNOS4210_I2C_BASE_ADDR + EXYNOS4210_I2C_SHIFT * n;
|
|
qemu_irq i2c_irq;
|
|
|
|
if (n < 8) {
|
|
i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_I2C_INTG, n)];
|
|
} else {
|
|
i2c_irq = s->irq_table[exynos4210_get_irq(EXYNOS4210_HDMI_INTG, 1)];
|
|
}
|
|
|
|
dev = qdev_new("exynos4210.i2c");
|
|
busdev = SYS_BUS_DEVICE(dev);
|
|
sysbus_realize_and_unref(busdev, &error_fatal);
|
|
sysbus_connect_irq(busdev, 0, i2c_irq);
|
|
sysbus_mmio_map(busdev, 0, addr);
|
|
s->i2c_if[n] = (I2CBus *)qdev_get_child_bus(dev, "i2c");
|
|
}
|
|
|
|
|
|
/*** UARTs ***/
|
|
uart[0] = exynos4210_uart_create(EXYNOS4210_UART0_BASE_ADDR,
|
|
EXYNOS4210_UART0_FIFO_SIZE, 0, serial_hd(0),
|
|
s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 0)]);
|
|
|
|
uart[1] = exynos4210_uart_create(EXYNOS4210_UART1_BASE_ADDR,
|
|
EXYNOS4210_UART1_FIFO_SIZE, 1, serial_hd(1),
|
|
s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 1)]);
|
|
|
|
uart[2] = exynos4210_uart_create(EXYNOS4210_UART2_BASE_ADDR,
|
|
EXYNOS4210_UART2_FIFO_SIZE, 2, serial_hd(2),
|
|
s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 2)]);
|
|
|
|
uart[3] = exynos4210_uart_create(EXYNOS4210_UART3_BASE_ADDR,
|
|
EXYNOS4210_UART3_FIFO_SIZE, 3, serial_hd(3),
|
|
s->irq_table[exynos4210_get_irq(EXYNOS4210_UART_INT_GRP, 3)]);
|
|
|
|
/*** SD/MMC host controllers ***/
|
|
for (n = 0; n < EXYNOS4210_SDHCI_NUMBER; n++) {
|
|
DeviceState *carddev;
|
|
BlockBackend *blk;
|
|
DriveInfo *di;
|
|
|
|
/* Compatible with:
|
|
* - SD Host Controller Specification Version 2.0
|
|
* - SDIO Specification Version 2.0
|
|
* - MMC Specification Version 4.3
|
|
* - SDMA
|
|
* - ADMA2
|
|
*
|
|
* As this part of the Exynos4210 is not publically available,
|
|
* we used the "HS-MMC Controller S3C2416X RISC Microprocessor"
|
|
* public datasheet which is very similar (implementing
|
|
* MMC Specification Version 4.0 being the only difference noted)
|
|
*/
|
|
dev = qdev_new(TYPE_S3C_SDHCI);
|
|
qdev_prop_set_uint64(dev, "capareg", EXYNOS4210_SDHCI_CAPABILITIES);
|
|
|
|
busdev = SYS_BUS_DEVICE(dev);
|
|
sysbus_realize_and_unref(busdev, &error_fatal);
|
|
sysbus_mmio_map(busdev, 0, EXYNOS4210_SDHCI_ADDR(n));
|
|
sysbus_connect_irq(busdev, 0, s->irq_table[exynos4210_get_irq(29, n)]);
|
|
|
|
di = drive_get(IF_SD, 0, n);
|
|
blk = di ? blk_by_legacy_dinfo(di) : NULL;
|
|
carddev = qdev_new(TYPE_SD_CARD);
|
|
qdev_prop_set_drive(carddev, "drive", blk);
|
|
qdev_realize_and_unref(carddev, qdev_get_child_bus(dev, "sd-bus"),
|
|
&error_fatal);
|
|
}
|
|
|
|
/*** Display controller (FIMD) ***/
|
|
sysbus_create_varargs("exynos4210.fimd", EXYNOS4210_FIMD0_BASE_ADDR,
|
|
s->irq_table[exynos4210_get_irq(11, 0)],
|
|
s->irq_table[exynos4210_get_irq(11, 1)],
|
|
s->irq_table[exynos4210_get_irq(11, 2)],
|
|
NULL);
|
|
|
|
sysbus_create_simple(TYPE_EXYNOS4210_EHCI, EXYNOS4210_EHCI_BASE_ADDR,
|
|
s->irq_table[exynos4210_get_irq(28, 3)]);
|
|
|
|
/*** DMA controllers ***/
|
|
pl330[0] = pl330_create(EXYNOS4210_PL330_BASE0_ADDR,
|
|
&s->pl330_irq_orgate[0],
|
|
s->irq_table[exynos4210_get_irq(21, 0)],
|
|
32, 32, 32);
|
|
pl330[1] = pl330_create(EXYNOS4210_PL330_BASE1_ADDR,
|
|
&s->pl330_irq_orgate[1],
|
|
s->irq_table[exynos4210_get_irq(21, 1)],
|
|
32, 32, 32);
|
|
pl330[2] = pl330_create(EXYNOS4210_PL330_BASE2_ADDR,
|
|
&s->pl330_irq_orgate[2],
|
|
s->irq_table[exynos4210_get_irq(20, 1)],
|
|
1, 31, 64);
|
|
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(uart[0]), 1,
|
|
qdev_get_gpio_in(pl330[0], 15));
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(uart[1]), 1,
|
|
qdev_get_gpio_in(pl330[1], 15));
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(uart[2]), 1,
|
|
qdev_get_gpio_in(pl330[0], 17));
|
|
sysbus_connect_irq(SYS_BUS_DEVICE(uart[3]), 1,
|
|
qdev_get_gpio_in(pl330[1], 17));
|
|
}
|
|
|
|
static void exynos4210_init(Object *obj)
|
|
{
|
|
Exynos4210State *s = EXYNOS4210_SOC(obj);
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->pl330_irq_orgate); i++) {
|
|
char *name = g_strdup_printf("pl330-irq-orgate%d", i);
|
|
OrIRQState *orgate = &s->pl330_irq_orgate[i];
|
|
|
|
object_initialize_child(obj, name, orgate, TYPE_OR_IRQ);
|
|
g_free(name);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->cpu_irq_orgate); i++) {
|
|
g_autofree char *name = g_strdup_printf("cpu-irq-orgate%d", i);
|
|
object_initialize_child(obj, name, &s->cpu_irq_orgate[i], TYPE_OR_IRQ);
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(s->splitter); i++) {
|
|
g_autofree char *name = g_strdup_printf("irq-splitter%d", i);
|
|
object_initialize_child(obj, name, &s->splitter[i], TYPE_SPLIT_IRQ);
|
|
}
|
|
|
|
object_initialize_child(obj, "a9mpcore", &s->a9mpcore, TYPE_A9MPCORE_PRIV);
|
|
object_initialize_child(obj, "ext-gic", &s->ext_gic, TYPE_EXYNOS4210_GIC);
|
|
object_initialize_child(obj, "int-combiner", &s->int_combiner,
|
|
TYPE_EXYNOS4210_COMBINER);
|
|
object_initialize_child(obj, "ext-combiner", &s->ext_combiner,
|
|
TYPE_EXYNOS4210_COMBINER);
|
|
}
|
|
|
|
static void exynos4210_class_init(ObjectClass *klass, void *data)
|
|
{
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
|
|
|
dc->realize = exynos4210_realize;
|
|
}
|
|
|
|
static const TypeInfo exynos4210_info = {
|
|
.name = TYPE_EXYNOS4210_SOC,
|
|
.parent = TYPE_SYS_BUS_DEVICE,
|
|
.instance_size = sizeof(Exynos4210State),
|
|
.instance_init = exynos4210_init,
|
|
.class_init = exynos4210_class_init,
|
|
};
|
|
|
|
static void exynos4210_register_types(void)
|
|
{
|
|
type_register_static(&exynos4210_info);
|
|
}
|
|
|
|
type_init(exynos4210_register_types)
|