qemu/hw/arm/ast2400.c
Cédric Le Goater 7c1c69bca4 ast2400: add SMC controllers (FMC and SPI)
The Aspeed AST2400 soc includes a static memory controller for the BMC
which supports NOR, NAND and SPI flash memory modules. This controller
has two modes : the SMC for the legacy interface which supports only
one module and the FMC for the new interface which supports up to five
modules. The AST2400 also includes a SPI only controller used for the
host firmware, commonly called BIOS on Intel. It can be used in three
mode : a SPI master, SPI slave and SPI pass-through

Below is the initial framework for the SMC controller (FMC mode only)
and the SPI controller: the sysbus object, MMIO for registers
configuration and controls. Each controller has a SPI bus and a
configurable number of CS lines for SPI flash slaves.

The differences between the controllers are small, so they are
abstracted using indirections on the register numbers.

Only SPI flash modules are supported.

Signed-off-by: Cédric Le Goater <clg@kaod.org>
Message-id: 1467138270-32481-7-git-send-email-clg@kaod.org
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
[PMM: added one missing error_propagate]
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
2016-07-04 13:15:22 +01:00

211 lines
6.9 KiB
C

/*
* AST2400 SoC
*
* Andrew Jeffery <andrew@aj.id.au>
* Jeremy Kerr <jk@ozlabs.org>
*
* Copyright 2016 IBM Corp.
*
* This code is licensed under the GPL version 2 or later. See
* the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "qemu-common.h"
#include "cpu.h"
#include "exec/address-spaces.h"
#include "hw/arm/ast2400.h"
#include "hw/char/serial.h"
#include "qemu/log.h"
#include "hw/i2c/aspeed_i2c.h"
#define AST2400_UART_5_BASE 0x00184000
#define AST2400_IOMEM_SIZE 0x00200000
#define AST2400_IOMEM_BASE 0x1E600000
#define AST2400_SMC_BASE AST2400_IOMEM_BASE /* Legacy SMC */
#define AST2400_FMC_BASE 0X1E620000
#define AST2400_SPI_BASE 0X1E630000
#define AST2400_VIC_BASE 0x1E6C0000
#define AST2400_SCU_BASE 0x1E6E2000
#define AST2400_TIMER_BASE 0x1E782000
#define AST2400_I2C_BASE 0x1E78A000
#define AST2400_A0_SILICON_REV 0x02000303
static const int uart_irqs[] = { 9, 32, 33, 34, 10 };
static const int timer_irqs[] = { 16, 17, 18, 35, 36, 37, 38, 39, };
/*
* IO handlers: simply catch any reads/writes to IO addresses that aren't
* handled by a device mapping.
*/
static uint64_t ast2400_io_read(void *p, hwaddr offset, unsigned size)
{
qemu_log_mask(LOG_UNIMP, "%s: 0x%" HWADDR_PRIx " [%u]\n",
__func__, offset, size);
return 0;
}
static void ast2400_io_write(void *opaque, hwaddr offset, uint64_t value,
unsigned size)
{
qemu_log_mask(LOG_UNIMP, "%s: 0x%" HWADDR_PRIx " <- 0x%" PRIx64 " [%u]\n",
__func__, offset, value, size);
}
static const MemoryRegionOps ast2400_io_ops = {
.read = ast2400_io_read,
.write = ast2400_io_write,
.endianness = DEVICE_LITTLE_ENDIAN,
};
static void ast2400_init(Object *obj)
{
AST2400State *s = AST2400(obj);
s->cpu = cpu_arm_init("arm926");
object_initialize(&s->vic, sizeof(s->vic), TYPE_ASPEED_VIC);
object_property_add_child(obj, "vic", OBJECT(&s->vic), NULL);
qdev_set_parent_bus(DEVICE(&s->vic), sysbus_get_default());
object_initialize(&s->timerctrl, sizeof(s->timerctrl), TYPE_ASPEED_TIMER);
object_property_add_child(obj, "timerctrl", OBJECT(&s->timerctrl), NULL);
qdev_set_parent_bus(DEVICE(&s->timerctrl), sysbus_get_default());
object_initialize(&s->i2c, sizeof(s->i2c), TYPE_ASPEED_I2C);
object_property_add_child(obj, "i2c", OBJECT(&s->i2c), NULL);
qdev_set_parent_bus(DEVICE(&s->i2c), sysbus_get_default());
object_initialize(&s->scu, sizeof(s->scu), TYPE_ASPEED_SCU);
object_property_add_child(obj, "scu", OBJECT(&s->scu), NULL);
qdev_set_parent_bus(DEVICE(&s->scu), sysbus_get_default());
qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev",
AST2400_A0_SILICON_REV);
object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu),
"hw-strap1", &error_abort);
object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu),
"hw-strap2", &error_abort);
object_initialize(&s->smc, sizeof(s->smc), "aspeed.smc.fmc");
object_property_add_child(obj, "smc", OBJECT(&s->smc), NULL);
qdev_set_parent_bus(DEVICE(&s->smc), sysbus_get_default());
object_initialize(&s->spi, sizeof(s->spi), "aspeed.smc.spi");
object_property_add_child(obj, "spi", OBJECT(&s->spi), NULL);
qdev_set_parent_bus(DEVICE(&s->spi), sysbus_get_default());
}
static void ast2400_realize(DeviceState *dev, Error **errp)
{
int i;
AST2400State *s = AST2400(dev);
Error *err = NULL, *local_err = NULL;
/* IO space */
memory_region_init_io(&s->iomem, NULL, &ast2400_io_ops, NULL,
"ast2400.io", AST2400_IOMEM_SIZE);
memory_region_add_subregion_overlap(get_system_memory(), AST2400_IOMEM_BASE,
&s->iomem, -1);
/* VIC */
object_property_set_bool(OBJECT(&s->vic), true, "realized", &err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->vic), 0, AST2400_VIC_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->vic), 0,
qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_IRQ));
sysbus_connect_irq(SYS_BUS_DEVICE(&s->vic), 1,
qdev_get_gpio_in(DEVICE(s->cpu), ARM_CPU_FIQ));
/* Timer */
object_property_set_bool(OBJECT(&s->timerctrl), true, "realized", &err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->timerctrl), 0, AST2400_TIMER_BASE);
for (i = 0; i < ARRAY_SIZE(timer_irqs); i++) {
qemu_irq irq = qdev_get_gpio_in(DEVICE(&s->vic), timer_irqs[i]);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->timerctrl), i, irq);
}
/* SCU */
object_property_set_bool(OBJECT(&s->scu), true, "realized", &err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->scu), 0, AST2400_SCU_BASE);
/* UART - attach an 8250 to the IO space as our UART5 */
if (serial_hds[0]) {
qemu_irq uart5 = qdev_get_gpio_in(DEVICE(&s->vic), uart_irqs[4]);
serial_mm_init(&s->iomem, AST2400_UART_5_BASE, 2,
uart5, 38400, serial_hds[0], DEVICE_LITTLE_ENDIAN);
}
/* I2C */
object_property_set_bool(OBJECT(&s->i2c), true, "realized", &err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->i2c), 0, AST2400_I2C_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->i2c), 0,
qdev_get_gpio_in(DEVICE(&s->vic), 12));
/* SMC */
object_property_set_int(OBJECT(&s->smc), 1, "num-cs", &err);
object_property_set_bool(OBJECT(&s->smc), true, "realized", &local_err);
error_propagate(&err, local_err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->smc), 0, AST2400_FMC_BASE);
sysbus_connect_irq(SYS_BUS_DEVICE(&s->smc), 0,
qdev_get_gpio_in(DEVICE(&s->vic), 19));
/* SPI */
object_property_set_int(OBJECT(&s->spi), 1, "num-cs", &err);
object_property_set_bool(OBJECT(&s->spi), true, "realized", &local_err);
error_propagate(&err, local_err);
if (err) {
error_propagate(errp, err);
return;
}
sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 0, AST2400_SPI_BASE);
}
static void ast2400_class_init(ObjectClass *oc, void *data)
{
DeviceClass *dc = DEVICE_CLASS(oc);
dc->realize = ast2400_realize;
/*
* Reason: creates an ARM CPU, thus use after free(), see
* arm_cpu_class_init()
*/
dc->cannot_destroy_with_object_finalize_yet = true;
}
static const TypeInfo ast2400_type_info = {
.name = TYPE_AST2400,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(AST2400State),
.instance_init = ast2400_init,
.class_init = ast2400_class_init,
};
static void ast2400_register_types(void)
{
type_register_static(&ast2400_type_info);
}
type_init(ast2400_register_types)