hw/arm: add experimental iPhone 5 A6 platform
This commit is contained in:
@@ -51,6 +51,14 @@ config DIGIC
|
||||
select PTIMER
|
||||
select PFLASH_CFI02
|
||||
|
||||
config S5L8950X
|
||||
bool
|
||||
default y
|
||||
depends on TCG && ARM
|
||||
select S5L8950X_GPIO
|
||||
select S5L8950X_USB_PHY
|
||||
select UNIMP
|
||||
|
||||
config EXYNOS4
|
||||
bool
|
||||
default y
|
||||
|
||||
@@ -21,6 +21,7 @@ arm_common_ss.add(when: 'CONFIG_SABRELITE', if_true: files('sabrelite.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_OMAP', if_true: files('omap1.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_ALLWINNER_A10', if_true: files('allwinner-a10.c', 'cubieboard.c'))
|
||||
arm_common_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-h3.c', 'orangepi.c'))
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/arm/s5l8950x.h"
|
||||
#include "hw/arm/machines-qom.h"
|
||||
#include "hw/core/boards.h"
|
||||
#include "hw/core/loader.h"
|
||||
#include "hw/misc/unimp.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "system/address-spaces.h"
|
||||
#include "target/arm/cpu.h"
|
||||
#include "qemu/units.h"
|
||||
#include "qom/object.h"
|
||||
#include "hw/arm/boot.h"
|
||||
#include "system/system.h"
|
||||
#include "target/arm/cpregs.h"
|
||||
|
||||
/*
|
||||
* Swift has implementation-defined CP15 controls which the SecureROM uses
|
||||
* while bringing caches and the memory system up. QEMU does not model
|
||||
* those microarchitectural details, so the matching control is write-ignore.
|
||||
*/
|
||||
static const ARMCPRegInfo s5l8950x_cp_reginfo[] = {
|
||||
{
|
||||
.name = "APPLE_SWIFT_CFG",
|
||||
.cp = 15,
|
||||
.opc1 = 1,
|
||||
.crn = 15,
|
||||
.crm = 2,
|
||||
.opc2 = 0,
|
||||
.access = PL1_W,
|
||||
.type = ARM_CP_NOP,
|
||||
},
|
||||
};
|
||||
|
||||
/* Addresses */
|
||||
#define S5L8950X_SECUREROM_BASE 0x00000000
|
||||
#define S5L8950X_SECUREROM_SIZE 0x10000
|
||||
#define S5L8950X_SRAM_BASE 0x10000000
|
||||
#define S5L8950X_SRAM_SIZE 0x80000
|
||||
#define S5L8950X_SDIO_BASE 0x20000000
|
||||
#define S5L8950X_USB_PHY_BASE 0x36000000
|
||||
#define S5L8950X_USB_BASE 0x36100000
|
||||
#define S5L8950X_SHA1_BASE 0x3C500000
|
||||
#define S5L8950X_SPI_BASE 0x3D200000
|
||||
#define S5L8950X_AES_BASE 0x3E000000
|
||||
#define S5L8950X_IIC_BASE 0x3E100000
|
||||
#define S5L8950X_PKE_BASE 0x3E200000
|
||||
#define S5L8950X_WDT_BASE 0x3E300000
|
||||
#define S5L8950X_CHIPID_BASE 0x3F500000
|
||||
#define S5L8950X_TIMER_BASE 0x3F200000
|
||||
#define S5L8950X_PMGR_BASE 0x3F100000
|
||||
#define S5L8950X_AMC_BASE 0x40000000
|
||||
#define S5L8950X_AIC_BASE 0x3F200000
|
||||
#define S5L8950X_UART_BASE 0x44300000
|
||||
#define S5L8950X_GPIO_BASE 0x3FA00000
|
||||
#define S5L8950X_DRAM_BASE 0x80000000
|
||||
|
||||
static void s5l8950x_init(Object *obj)
|
||||
{
|
||||
S5L8950XState *s = S5L8950X(obj);
|
||||
|
||||
object_initialize_child(obj, "cpu", &s->cpu, ARM_CPU_TYPE_NAME("cortex-a9"));
|
||||
define_arm_cp_regs(&s->cpu, s5l8950x_cp_reginfo);
|
||||
}
|
||||
|
||||
static void s5l8950x_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
S5L8950XState *s = S5L8950X(dev);
|
||||
Error *err = NULL;
|
||||
|
||||
/* Realize CPU */
|
||||
if (!qdev_realize(DEVICE(&s->cpu), NULL, &err)) {
|
||||
error_propagate(errp, err);
|
||||
return;
|
||||
}
|
||||
|
||||
/* SRAM */
|
||||
memory_region_init_ram(&s->sram, OBJECT(s), "s5l8950x.sram", S5L8950X_SRAM_SIZE, &error_fatal);
|
||||
memory_region_add_subregion(get_system_memory(), S5L8950X_SRAM_BASE, &s->sram);
|
||||
|
||||
/* SecureROM */
|
||||
memory_region_init_rom(&s->securerom, OBJECT(s), "s5l8950x.securerom", S5L8950X_SECUREROM_SIZE, &error_fatal);
|
||||
memory_region_add_subregion(get_system_memory(), S5L8950X_SECUREROM_BASE, &s->securerom);
|
||||
|
||||
/* Create unimplemented devices */
|
||||
create_unimplemented_device("s5l8950x.sdio", S5L8950X_SDIO_BASE, 0x10000);
|
||||
create_unimplemented_device("s5l8950x.sha1", S5L8950X_SHA1_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.spi", S5L8950X_SPI_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.aes", S5L8950X_AES_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.iic", S5L8950X_IIC_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.pke", S5L8950X_PKE_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.wdt", S5L8950X_WDT_BASE, 0x1000);
|
||||
create_unimplemented_device("s5l8950x.amc", S5L8950X_AMC_BASE, 0x1000);
|
||||
|
||||
/* Realize custom devices (assumes stubs will be created) */
|
||||
DeviceState *dev_chipid = qdev_new(TYPE_S5L8950X_CHIPID);
|
||||
s->chipid = SYS_BUS_DEVICE(dev_chipid);
|
||||
sysbus_realize_and_unref(s->chipid, &error_fatal);
|
||||
sysbus_mmio_map(s->chipid, 0, S5L8950X_CHIPID_BASE);
|
||||
|
||||
DeviceState *dev_aic = qdev_new(TYPE_S5L8950X_AIC);
|
||||
s->aic = SYS_BUS_DEVICE(dev_aic);
|
||||
sysbus_realize_and_unref(s->aic, &error_fatal);
|
||||
sysbus_mmio_map(s->aic, 0, S5L8950X_AIC_BASE);
|
||||
sysbus_connect_irq(s->aic, 0,
|
||||
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_IRQ));
|
||||
sysbus_connect_irq(s->aic, 1,
|
||||
qdev_get_gpio_in(DEVICE(&s->cpu), ARM_CPU_FIQ));
|
||||
|
||||
DeviceState *dev_uart = qdev_new(TYPE_S5L8950X_UART);
|
||||
qdev_prop_set_chr(dev_uart, "chardev", serial_hd(0));
|
||||
s->uart = SYS_BUS_DEVICE(dev_uart);
|
||||
sysbus_realize_and_unref(s->uart, &error_fatal);
|
||||
sysbus_mmio_map(s->uart, 0, S5L8950X_UART_BASE);
|
||||
|
||||
DeviceState *dev_pmgr = qdev_new(TYPE_S5L8950X_PMGR);
|
||||
s->pmgr = SYS_BUS_DEVICE(dev_pmgr);
|
||||
sysbus_realize_and_unref(s->pmgr, &error_fatal);
|
||||
sysbus_mmio_map(s->pmgr, 0, S5L8950X_PMGR_BASE);
|
||||
|
||||
DeviceState *dev_gpio = qdev_new(TYPE_S5L8950X_GPIO);
|
||||
qdev_prop_set_bit(dev_gpio, "force-dfu", true);
|
||||
s->gpio = SYS_BUS_DEVICE(dev_gpio);
|
||||
sysbus_realize_and_unref(s->gpio, &error_fatal);
|
||||
sysbus_mmio_map(s->gpio, 0, S5L8950X_GPIO_BASE);
|
||||
|
||||
DeviceState *dev_usb_phy = qdev_new(TYPE_S5L8950X_USB_PHY);
|
||||
qdev_prop_set_bit(dev_usb_phy, "cable-connected", true);
|
||||
s->usb_phy = SYS_BUS_DEVICE(dev_usb_phy);
|
||||
sysbus_realize_and_unref(s->usb_phy, &error_fatal);
|
||||
sysbus_mmio_map(s->usb_phy, 0, S5L8950X_USB_PHY_BASE);
|
||||
|
||||
DeviceState *dev_usb_otg = qdev_new(TYPE_S5L8950X_USB_OTG);
|
||||
s->usb_otg = SYS_BUS_DEVICE(dev_usb_otg);
|
||||
sysbus_realize_and_unref(s->usb_otg, &error_fatal);
|
||||
sysbus_mmio_map(s->usb_otg, 0, S5L8950X_USB_BASE);
|
||||
sysbus_connect_irq(s->usb_otg, 0,
|
||||
qdev_get_gpio_in(dev_aic, 11));
|
||||
}
|
||||
|
||||
static void s5l8950x_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(oc);
|
||||
dc->realize = s5l8950x_realize;
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_type_info = {
|
||||
.name = TYPE_S5L8950X,
|
||||
.parent = TYPE_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XState),
|
||||
.instance_init = s5l8950x_init,
|
||||
.class_init = s5l8950x_class_init,
|
||||
};
|
||||
|
||||
static void iphone5_machine_init(MachineState *machine)
|
||||
{
|
||||
S5L8950XState *s;
|
||||
|
||||
s = S5L8950X(object_new(TYPE_S5L8950X));
|
||||
object_property_add_child(OBJECT(machine), "soc", OBJECT(s));
|
||||
qdev_realize(DEVICE(s), NULL, &error_fatal);
|
||||
|
||||
/* Map DRAM */
|
||||
memory_region_add_subregion(get_system_memory(), S5L8950X_DRAM_BASE, machine->ram);
|
||||
|
||||
/* Load BIOS (SecureROM) */
|
||||
if (machine->firmware) {
|
||||
load_image_targphys(machine->firmware, S5L8950X_SECUREROM_BASE,
|
||||
S5L8950X_SECUREROM_SIZE, &error_fatal);
|
||||
}
|
||||
}
|
||||
|
||||
static void iphone5_machine_class_init(ObjectClass *oc, const void *data)
|
||||
{
|
||||
MachineClass *mc = MACHINE_CLASS(oc);
|
||||
|
||||
mc->desc = "Apple iPhone 5 (S5L8950X / A6)";
|
||||
mc->init = iphone5_machine_init;
|
||||
mc->default_ram_size = 1 * GiB;
|
||||
mc->default_ram_id = "dram";
|
||||
mc->ignore_memory_transaction_failures = true;
|
||||
}
|
||||
|
||||
static const TypeInfo iphone5_machine_type_info = {
|
||||
.name = MACHINE_TYPE_NAME("iphone5"),
|
||||
.parent = TYPE_MACHINE,
|
||||
.class_init = iphone5_machine_class_init,
|
||||
.interfaces = arm_machine_interfaces,
|
||||
};
|
||||
|
||||
static void s5l8950x_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_type_info);
|
||||
type_register_static(&iphone5_machine_type_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_register_types)
|
||||
@@ -26,6 +26,7 @@ system_ss.add(when: 'CONFIG_DIVA_GSP', if_true: files('diva-gsp.c'))
|
||||
system_ss.add(when: 'CONFIG_AVR_USART', if_true: files('avr_usart.c'))
|
||||
system_ss.add(when: 'CONFIG_COLDFIRE', if_true: files('mcf_uart.c'))
|
||||
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-uart.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-uart.c'))
|
||||
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_uart.c'))
|
||||
system_ss.add(when: 'CONFIG_MAX78000_UART', if_true: files('max78000_uart.c'))
|
||||
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_uart.c'))
|
||||
|
||||
@@ -0,0 +1,239 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "hw/core/qdev-properties-system.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/module.h"
|
||||
#include "chardev/char-fe.h"
|
||||
|
||||
#define TYPE_S5L8950X_UART "s5l8950x-uart"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUartState, S5L8950X_UART)
|
||||
|
||||
struct S5L8950XUartState {
|
||||
SysBusDevice parent_obj;
|
||||
|
||||
MemoryRegion iomem;
|
||||
CharFrontend chr;
|
||||
|
||||
uint32_t ulcon;
|
||||
uint32_t ucon;
|
||||
uint32_t ufcon;
|
||||
uint32_t umcon;
|
||||
uint32_t ubrdiv;
|
||||
uint32_t ufracval;
|
||||
|
||||
uint8_t rx_fifo[16];
|
||||
int rx_count;
|
||||
qemu_irq irq;
|
||||
};
|
||||
|
||||
#define UART_ULCON 0x00
|
||||
#define UART_UCON 0x04
|
||||
#define UART_UFCON 0x08
|
||||
#define UART_UMCON 0x0C
|
||||
#define UART_UTRSTAT 0x10
|
||||
#define UART_UERSTAT 0x14
|
||||
#define UART_UFSTAT 0x18
|
||||
#define UART_UMSTAT 0x1C
|
||||
#define UART_UTXH 0x20
|
||||
#define UART_URXH 0x24
|
||||
#define UART_UBRDIV 0x28
|
||||
#define UART_UFRACVAL 0x2C
|
||||
|
||||
static void s5l8950x_uart_update_irq(S5L8950XUartState *s)
|
||||
{
|
||||
if (s->rx_count > 0) {
|
||||
qemu_irq_raise(s->irq);
|
||||
} else {
|
||||
qemu_irq_lower(s->irq);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t s5l8950x_uart_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
||||
uint32_t val = 0;
|
||||
|
||||
switch (offset) {
|
||||
case UART_ULCON:
|
||||
val = s->ulcon;
|
||||
break;
|
||||
case UART_UCON:
|
||||
val = s->ucon;
|
||||
break;
|
||||
case UART_UFCON:
|
||||
val = s->ufcon;
|
||||
break;
|
||||
case UART_UMCON:
|
||||
val = s->umcon;
|
||||
break;
|
||||
case UART_UTRSTAT:
|
||||
/* Bit 0: rx ready, Bit 1: tx buf empty, Bit 2: tx empty */
|
||||
val = 0x6 | (s->rx_count > 0 ? 1 : 0);
|
||||
break;
|
||||
case UART_UERSTAT:
|
||||
val = 0;
|
||||
break;
|
||||
case UART_UFSTAT:
|
||||
/* Return rx count in low bits, no tx fifo used here */
|
||||
val = s->rx_count & 0xf;
|
||||
break;
|
||||
case UART_UMSTAT:
|
||||
val = 0;
|
||||
break;
|
||||
case UART_UTXH:
|
||||
break;
|
||||
case UART_URXH:
|
||||
if (s->rx_count > 0) {
|
||||
val = s->rx_fifo[0];
|
||||
s->rx_count--;
|
||||
memmove(s->rx_fifo, s->rx_fifo + 1, s->rx_count);
|
||||
s5l8950x_uart_update_irq(s);
|
||||
}
|
||||
break;
|
||||
case UART_UBRDIV:
|
||||
val = s->ubrdiv;
|
||||
break;
|
||||
case UART_UFRACVAL:
|
||||
val = s->ufracval;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
break;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
static void s5l8950x_uart_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
||||
unsigned char c;
|
||||
|
||||
switch (offset) {
|
||||
case UART_ULCON:
|
||||
s->ulcon = val;
|
||||
break;
|
||||
case UART_UCON:
|
||||
s->ucon = val;
|
||||
break;
|
||||
case UART_UFCON:
|
||||
s->ufcon = val;
|
||||
break;
|
||||
case UART_UMCON:
|
||||
s->umcon = val;
|
||||
break;
|
||||
case UART_UTRSTAT:
|
||||
case UART_UERSTAT:
|
||||
case UART_UFSTAT:
|
||||
case UART_UMSTAT:
|
||||
/* Read-only registers */
|
||||
break;
|
||||
case UART_UTXH:
|
||||
c = val & 0xFF;
|
||||
qemu_chr_fe_write_all(&s->chr, &c, 1);
|
||||
break;
|
||||
case UART_URXH:
|
||||
break;
|
||||
case UART_UBRDIV:
|
||||
s->ubrdiv = val;
|
||||
break;
|
||||
case UART_UFRACVAL:
|
||||
s->ufracval = val;
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_uart_ops = {
|
||||
.read = s5l8950x_uart_read,
|
||||
.write = s5l8950x_uart_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static int s5l8950x_uart_can_receive(void *opaque)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
||||
|
||||
return sizeof(s->rx_fifo) - s->rx_count;
|
||||
}
|
||||
|
||||
static void s5l8950x_uart_receive(void *opaque, const uint8_t *buf, int size)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(opaque);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < size && s->rx_count < sizeof(s->rx_fifo); i++) {
|
||||
s->rx_fifo[s->rx_count++] = buf[i];
|
||||
}
|
||||
s5l8950x_uart_update_irq(s);
|
||||
}
|
||||
|
||||
static void s5l8950x_uart_reset(DeviceState *dev)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(dev);
|
||||
|
||||
s->ulcon = 0;
|
||||
s->ucon = 0;
|
||||
s->ufcon = 0;
|
||||
s->umcon = 0;
|
||||
s->ubrdiv = 0;
|
||||
s->ufracval = 0;
|
||||
s->rx_count = 0;
|
||||
s5l8950x_uart_update_irq(s);
|
||||
}
|
||||
|
||||
static void s5l8950x_uart_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
S5L8950XUartState *s = S5L8950X_UART(dev);
|
||||
|
||||
qemu_chr_fe_set_handlers(&s->chr, s5l8950x_uart_can_receive,
|
||||
s5l8950x_uart_receive, NULL, NULL,
|
||||
s, NULL, true);
|
||||
}
|
||||
|
||||
static void s5l8950x_uart_init(Object *obj)
|
||||
{
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
S5L8950XUartState *s = S5L8950X_UART(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_uart_ops, s,
|
||||
TYPE_S5L8950X_UART, 0x100);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
}
|
||||
|
||||
static const Property s5l8950x_uart_properties[] = {
|
||||
DEFINE_PROP_CHR("chardev", S5L8950XUartState, chr),
|
||||
};
|
||||
|
||||
static void s5l8950x_uart_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, s5l8950x_uart_reset);
|
||||
dc->realize = s5l8950x_uart_realize;
|
||||
device_class_set_props(dc, s5l8950x_uart_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_uart_info = {
|
||||
.name = TYPE_S5L8950X_UART,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XUartState),
|
||||
.instance_init = s5l8950x_uart_init,
|
||||
.class_init = s5l8950x_uart_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_uart_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_uart_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_uart_register_types)
|
||||
@@ -10,6 +10,9 @@ config GPIO_MPC8XXX
|
||||
config GPIO_PWR
|
||||
bool
|
||||
|
||||
config S5L8950X_GPIO
|
||||
bool
|
||||
|
||||
config SIFIVE_GPIO
|
||||
bool
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
system_ss.add(when: 'CONFIG_GPIO_KEY', if_true: files('gpio_key.c'))
|
||||
system_ss.add(when: 'CONFIG_GPIO_MPC8XXX', if_true: files('mpc8xxx.c'))
|
||||
system_ss.add(when: 'CONFIG_GPIO_PWR', if_true: files('gpio_pwr.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X_GPIO', if_true: files('s5l8950x-gpio.c'))
|
||||
system_ss.add(when: 'CONFIG_PCA9552', if_true: files('pca9552.c'))
|
||||
system_ss.add(when: 'CONFIG_PCA9554', if_true: files('pca9554.c'))
|
||||
system_ss.add(when: 'CONFIG_PL061', if_true: files('pl061.c'))
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
/*
|
||||
* Apple S5L8950X GPIO controller (minimal SecureROM-facing model)
|
||||
*
|
||||
* The A6 SecureROM addresses pins as (bank << 8) | pin. Each pin maps to
|
||||
* one 32-bit register at ((bank * 8) + pin) * 4. Bit 0 is the sampled input
|
||||
* level; the remaining fields configure muxing and pull state.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/arm/s5l8950x.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define S5L8950X_GPIO_REGION_SIZE 0x10000
|
||||
#define S5L8950X_GPIO_NUM_REGS (S5L8950X_GPIO_REGION_SIZE / 4)
|
||||
|
||||
/* Recovery/DFU button sampled by SecureROM as GPIO 0x1906. */
|
||||
#define S5L8950X_GPIO_DFU_OFFSET (((0x19 * 8) + 6) * sizeof(uint32_t))
|
||||
#define S5L8950X_GPIO_INPUT_LEVEL BIT(0)
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XGPIOState, S5L8950X_GPIO)
|
||||
|
||||
struct S5L8950XGPIOState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
uint32_t regs[S5L8950X_GPIO_NUM_REGS];
|
||||
bool force_dfu;
|
||||
};
|
||||
|
||||
static uint64_t s5l8950x_gpio_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
S5L8950XGPIOState *s = opaque;
|
||||
uint32_t value;
|
||||
|
||||
if (offset >= S5L8950X_GPIO_REGION_SIZE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
value = s->regs[offset / sizeof(uint32_t)];
|
||||
if (s->force_dfu && offset == S5L8950X_GPIO_DFU_OFFSET) {
|
||||
value |= S5L8950X_GPIO_INPUT_LEVEL;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void s5l8950x_gpio_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
S5L8950XGPIOState *s = opaque;
|
||||
|
||||
if (offset < S5L8950X_GPIO_REGION_SIZE) {
|
||||
s->regs[offset / sizeof(uint32_t)] = value;
|
||||
} else {
|
||||
qemu_log_mask(LOG_GUEST_ERROR,
|
||||
"%s: invalid write at offset 0x%" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_gpio_ops = {
|
||||
.read = s5l8950x_gpio_read,
|
||||
.write = s5l8950x_gpio_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static const Property s5l8950x_gpio_properties[] = {
|
||||
DEFINE_PROP_BOOL("force-dfu", S5L8950XGPIOState, force_dfu, false),
|
||||
};
|
||||
|
||||
static void s5l8950x_gpio_reset(DeviceState *dev)
|
||||
{
|
||||
S5L8950XGPIOState *s = S5L8950X_GPIO(dev);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
}
|
||||
|
||||
static void s5l8950x_gpio_init(Object *obj)
|
||||
{
|
||||
S5L8950XGPIOState *s = S5L8950X_GPIO(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_gpio_ops, s,
|
||||
TYPE_S5L8950X_GPIO, S5L8950X_GPIO_REGION_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void s5l8950x_gpio_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, s5l8950x_gpio_reset);
|
||||
device_class_set_props(dc, s5l8950x_gpio_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_gpio_info = {
|
||||
.name = TYPE_S5L8950X_GPIO,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XGPIOState),
|
||||
.instance_init = s5l8950x_gpio_init,
|
||||
.class_init = s5l8950x_gpio_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_gpio_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_gpio_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_gpio_register_types)
|
||||
@@ -24,6 +24,7 @@ system_ss.add(when: 'CONFIG_GOLDFISH_PIC', if_true: files('goldfish_pic.c'))
|
||||
system_ss.add(when: 'CONFIG_HEATHROW_PIC', if_true: files('heathrow_pic.c'))
|
||||
system_ss.add(when: 'CONFIG_I8259', if_true: files('i8259_common.c', 'i8259.c'))
|
||||
system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_avic.c', 'imx_gpcv2.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-aic.c'))
|
||||
system_ss.add(when: 'CONFIG_IOAPIC', if_true: files('ioapic_common.c'), if_false: files('ioapic-stub.c'))
|
||||
system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_intc.c'))
|
||||
system_ss.add(when: 'CONFIG_OPENPIC', if_true: files('openpic.c'))
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Apple Interrupt Controller v2 as used by the S5L8950X.
|
||||
*
|
||||
* The A6 SecureROM uses the same register layout later documented for AICv2:
|
||||
* event acknowledge at 0x2004, per-source destinations at 0x3000 and mask
|
||||
* set/clear banks at 0x4100/0x4180.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "hw/core/irq.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_S5L8950X_AIC "s5l8950x-aic"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XAicState, S5L8950X_AIC)
|
||||
|
||||
#define S5L8950X_AIC_NUM_IRQS 256
|
||||
#define S5L8950X_AIC_NUM_BANKS (S5L8950X_AIC_NUM_IRQS / 32)
|
||||
|
||||
#define AIC_REV 0x0000
|
||||
#define AIC_CAP0 0x0004
|
||||
#define AIC_CAP1 0x0008
|
||||
#define AIC_RST 0x000c
|
||||
#define AIC_GLB_CFG 0x0010
|
||||
#define AIC_TIME_LO 0x0020
|
||||
#define AIC_TIME_HI 0x0028
|
||||
#define AIC_WHOAMI 0x2000
|
||||
#define AIC_ACK 0x2004
|
||||
#define AIC_IPI_SET 0x2008
|
||||
#define AIC_IPI_CLR 0x200c
|
||||
#define AIC_SRC_CFG_BASE 0x3000
|
||||
#define AIC_MASK_SET_BASE 0x4100
|
||||
#define AIC_MASK_CLR_BASE 0x4180
|
||||
#define AIC_INT_STATE_BASE 0x4200
|
||||
|
||||
#define AIC_EVENT_EXT 0x00010000
|
||||
|
||||
struct S5L8950XAicState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
uint32_t mask[S5L8950X_AIC_NUM_BANKS];
|
||||
uint32_t pending[S5L8950X_AIC_NUM_BANKS];
|
||||
uint32_t src_cfg[S5L8950X_AIC_NUM_IRQS];
|
||||
uint32_t glb_cfg;
|
||||
qemu_irq irq;
|
||||
qemu_irq fiq;
|
||||
};
|
||||
|
||||
static bool s5l8950x_aic_irq_is_active(S5L8950XAicState *s, int irq)
|
||||
{
|
||||
int bank = irq / 32;
|
||||
uint32_t bit = BIT(irq % 32);
|
||||
|
||||
return (s->pending[bank] & bit) && !(s->mask[bank] & bit) &&
|
||||
(s->src_cfg[irq] & 1);
|
||||
}
|
||||
|
||||
static void s5l8950x_aic_update(S5L8950XAicState *s)
|
||||
{
|
||||
bool active = false;
|
||||
int irq;
|
||||
|
||||
for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) {
|
||||
if (s5l8950x_aic_irq_is_active(s, irq)) {
|
||||
active = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
qemu_set_irq(s->irq, active);
|
||||
qemu_irq_lower(s->fiq);
|
||||
}
|
||||
|
||||
static uint32_t s5l8950x_aic_ack(S5L8950XAicState *s)
|
||||
{
|
||||
int irq;
|
||||
|
||||
for (irq = 0; irq < S5L8950X_AIC_NUM_IRQS; irq++) {
|
||||
if (s5l8950x_aic_irq_is_active(s, irq)) {
|
||||
s->mask[irq / 32] |= BIT(irq % 32);
|
||||
s5l8950x_aic_update(s);
|
||||
return AIC_EVENT_EXT | irq;
|
||||
}
|
||||
}
|
||||
s5l8950x_aic_update(s);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void s5l8950x_aic_set_irq(void *opaque, int irq, int level)
|
||||
{
|
||||
S5L8950XAicState *s = opaque;
|
||||
uint32_t bit = BIT(irq % 32);
|
||||
|
||||
if (level) {
|
||||
s->pending[irq / 32] |= bit;
|
||||
} else {
|
||||
s->pending[irq / 32] &= ~bit;
|
||||
}
|
||||
s5l8950x_aic_update(s);
|
||||
}
|
||||
|
||||
static uint64_t s5l8950x_aic_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
S5L8950XAicState *s = opaque;
|
||||
uint64_t ticks;
|
||||
|
||||
ticks = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
|
||||
NANOSECONDS_PER_SECOND);
|
||||
switch (offset) {
|
||||
case AIC_REV:
|
||||
return 2;
|
||||
case AIC_CAP0:
|
||||
return S5L8950X_AIC_NUM_IRQS;
|
||||
case AIC_CAP1:
|
||||
return 0;
|
||||
case AIC_RST:
|
||||
return 0;
|
||||
case AIC_GLB_CFG:
|
||||
return s->glb_cfg;
|
||||
case AIC_TIME_LO:
|
||||
return (uint32_t)ticks;
|
||||
case AIC_TIME_HI:
|
||||
return (uint32_t)(ticks >> 32);
|
||||
case AIC_WHOAMI:
|
||||
return 0;
|
||||
case AIC_ACK:
|
||||
return s5l8950x_aic_ack(s);
|
||||
case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE +
|
||||
S5L8950X_AIC_NUM_IRQS * 4 - 4:
|
||||
return s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4];
|
||||
case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE +
|
||||
S5L8950X_AIC_NUM_BANKS * 4 - 4:
|
||||
return s->mask[(offset - AIC_MASK_SET_BASE) / 4];
|
||||
case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE +
|
||||
S5L8950X_AIC_NUM_BANKS * 4 - 4:
|
||||
return s->mask[(offset - AIC_MASK_CLR_BASE) / 4];
|
||||
case AIC_INT_STATE_BASE ... AIC_INT_STATE_BASE +
|
||||
S5L8950X_AIC_NUM_BANKS * 4 - 4:
|
||||
return s->pending[(offset - AIC_INT_STATE_BASE) / 4];
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"%s: unimplemented read at 0x%" HWADDR_PRIx "\n",
|
||||
__func__, offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void s5l8950x_aic_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
S5L8950XAicState *s = opaque;
|
||||
|
||||
switch (offset) {
|
||||
case AIC_RST:
|
||||
memset(s->mask, 0xff, sizeof(s->mask));
|
||||
memset(s->pending, 0, sizeof(s->pending));
|
||||
s5l8950x_aic_update(s);
|
||||
break;
|
||||
case AIC_GLB_CFG:
|
||||
s->glb_cfg = value;
|
||||
break;
|
||||
case AIC_IPI_SET:
|
||||
case AIC_IPI_CLR:
|
||||
break;
|
||||
case AIC_SRC_CFG_BASE ... AIC_SRC_CFG_BASE +
|
||||
S5L8950X_AIC_NUM_IRQS * 4 - 4:
|
||||
s->src_cfg[(offset - AIC_SRC_CFG_BASE) / 4] = value;
|
||||
s5l8950x_aic_update(s);
|
||||
break;
|
||||
case AIC_MASK_SET_BASE ... AIC_MASK_SET_BASE +
|
||||
S5L8950X_AIC_NUM_BANKS * 4 - 4:
|
||||
s->mask[(offset - AIC_MASK_SET_BASE) / 4] |= value;
|
||||
s5l8950x_aic_update(s);
|
||||
break;
|
||||
case AIC_MASK_CLR_BASE ... AIC_MASK_CLR_BASE +
|
||||
S5L8950X_AIC_NUM_BANKS * 4 - 4:
|
||||
s->mask[(offset - AIC_MASK_CLR_BASE) / 4] &= ~value;
|
||||
s5l8950x_aic_update(s);
|
||||
break;
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP,
|
||||
"%s: unimplemented write at 0x%" HWADDR_PRIx
|
||||
" = 0x%08" PRIx64 "\n",
|
||||
__func__, offset, value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_aic_ops = {
|
||||
.read = s5l8950x_aic_read,
|
||||
.write = s5l8950x_aic_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void s5l8950x_aic_reset(DeviceState *dev)
|
||||
{
|
||||
S5L8950XAicState *s = S5L8950X_AIC(dev);
|
||||
|
||||
memset(s->mask, 0xff, sizeof(s->mask));
|
||||
memset(s->pending, 0, sizeof(s->pending));
|
||||
memset(s->src_cfg, 0, sizeof(s->src_cfg));
|
||||
s->glb_cfg = 0;
|
||||
s5l8950x_aic_update(s);
|
||||
}
|
||||
|
||||
static void s5l8950x_aic_init(Object *obj)
|
||||
{
|
||||
S5L8950XAicState *s = S5L8950X_AIC(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_aic_ops, s,
|
||||
TYPE_S5L8950X_AIC, 0x8000);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
sysbus_init_irq(sbd, &s->irq);
|
||||
sysbus_init_irq(sbd, &s->fiq);
|
||||
qdev_init_gpio_in(DEVICE(obj), s5l8950x_aic_set_irq,
|
||||
S5L8950X_AIC_NUM_IRQS);
|
||||
}
|
||||
|
||||
static void s5l8950x_aic_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, s5l8950x_aic_reset);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_aic_info = {
|
||||
.name = TYPE_S5L8950X_AIC,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XAicState),
|
||||
.instance_init = s5l8950x_aic_init,
|
||||
.class_init = s5l8950x_aic_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_aic_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_aic_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_aic_register_types)
|
||||
@@ -16,6 +16,7 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
|
||||
system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
|
||||
system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
|
||||
system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-chipid.c', 's5l8950x-pmgr.c'))
|
||||
|
||||
system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_S5L8950X_CHIPID "s5l8950x-chipid"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XChipIdState, S5L8950X_CHIPID)
|
||||
|
||||
struct S5L8950XChipIdState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
};
|
||||
|
||||
static uint64_t s5l8950x_chipid_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
switch (offset) {
|
||||
case 0x00:
|
||||
/* n41ap: CPFM 0x03, SCEP 0x10, BDID 0x00, IBFL 0x00. */
|
||||
return 0x200D;
|
||||
case 0x04:
|
||||
return 0x00;
|
||||
case 0x08:
|
||||
return 0xDEADBEEF;
|
||||
case 0x0C:
|
||||
return 0xCAFEBABE;
|
||||
case 0x10:
|
||||
return 0x1;
|
||||
case 0x14:
|
||||
return 0x1;
|
||||
case 0x18:
|
||||
return 0x1;
|
||||
case 0x1C:
|
||||
return 0x0;
|
||||
case 0x20:
|
||||
return 0x1;
|
||||
case 0x24:
|
||||
/* CPRV 0x20. Also contributes to the synthetic ECID. */
|
||||
return 0x4000;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void s5l8950x_chipid_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write to offset 0x%" HWADDR_PRIx
|
||||
" with value 0x%" PRIx64 "\n", __func__, offset, val);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_chipid_ops = {
|
||||
.read = s5l8950x_chipid_read,
|
||||
.write = s5l8950x_chipid_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void s5l8950x_chipid_init(Object *obj)
|
||||
{
|
||||
S5L8950XChipIdState *s = S5L8950X_CHIPID(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_chipid_ops, s,
|
||||
TYPE_S5L8950X_CHIPID, 0x100);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_chipid_info = {
|
||||
.name = TYPE_S5L8950X_CHIPID,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XChipIdState),
|
||||
.instance_init = s5l8950x_chipid_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_chipid_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_chipid_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_chipid_register_types)
|
||||
@@ -0,0 +1,137 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_S5L8950X_PMGR "s5l8950x-pmgr"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XPmgrState, S5L8950X_PMGR)
|
||||
|
||||
#define PMGR_REGION_SIZE 0x10000
|
||||
#define PMGR_NUM_REGS (PMGR_REGION_SIZE / 4)
|
||||
#define PMGR_GATE_BASE 0x1000
|
||||
#define PMGR_GATE_COUNT 0x50
|
||||
#define PMGR_GATE_END (PMGR_GATE_BASE + PMGR_GATE_COUNT * sizeof(uint32_t))
|
||||
#define PMGR_DOMAIN_CTRL_STRIDE 0x18
|
||||
#define PMGR_DOMAIN_COUNT 9
|
||||
#define PMGR_DOMAIN_STATUS_BASE 0x2010
|
||||
#define PMGR_SPECIAL_DOMAIN_CTRL 0x100
|
||||
#define PMGR_SPECIAL_DOMAIN_STATUS 0x2034
|
||||
#define PMGR_DOMAIN_ACTIVE BIT(30)
|
||||
#define PMGR_PLL0_CTRL 0x60
|
||||
#define PMGR_PLL_LOCKED BIT(29)
|
||||
|
||||
struct S5L8950XPmgrState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
uint32_t regs[PMGR_NUM_REGS];
|
||||
};
|
||||
|
||||
static uint64_t s5l8950x_pmgr_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
S5L8950XPmgrState *s = opaque;
|
||||
uint32_t idx = offset / 4;
|
||||
|
||||
if (idx < PMGR_NUM_REGS) {
|
||||
return s->regs[idx];
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void s5l8950x_pmgr_write(void *opaque, hwaddr offset,
|
||||
uint64_t val, unsigned size)
|
||||
{
|
||||
S5L8950XPmgrState *s = opaque;
|
||||
uint32_t idx = offset / 4;
|
||||
|
||||
if (idx < PMGR_NUM_REGS) {
|
||||
uint32_t reg = (uint32_t)val;
|
||||
|
||||
/*
|
||||
* Gate registers expose the requested state in bits [3:0] and the
|
||||
* hardware-acknowledged state in bits [7:4]. Clocks settle
|
||||
* immediately in this model, so mirror the request on every write.
|
||||
*/
|
||||
if (offset >= PMGR_GATE_BASE && offset < PMGR_GATE_END) {
|
||||
reg = (reg & ~0xf0u) | ((reg & 0xfu) << 4);
|
||||
}
|
||||
if (offset == PMGR_PLL0_CTRL) {
|
||||
reg |= PMGR_PLL_LOCKED;
|
||||
}
|
||||
s->regs[idx] = reg;
|
||||
|
||||
/* Power-domain transitions also complete immediately. */
|
||||
if (offset < PMGR_DOMAIN_CTRL_STRIDE * PMGR_DOMAIN_COUNT &&
|
||||
offset % PMGR_DOMAIN_CTRL_STRIDE == 0) {
|
||||
uint32_t domain = offset / PMGR_DOMAIN_CTRL_STRIDE;
|
||||
uint32_t status = (PMGR_DOMAIN_STATUS_BASE / 4) + domain;
|
||||
|
||||
s->regs[status] = deposit32(s->regs[status], 30, 1,
|
||||
!!(reg & PMGR_DOMAIN_ACTIVE));
|
||||
} else if (offset == PMGR_SPECIAL_DOMAIN_CTRL) {
|
||||
uint32_t status = PMGR_SPECIAL_DOMAIN_STATUS / 4;
|
||||
|
||||
s->regs[status] = deposit32(s->regs[status], 30, 1,
|
||||
!!(reg & PMGR_DOMAIN_ACTIVE));
|
||||
}
|
||||
} else {
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Out of bounds write to offset 0x%" HWADDR_PRIx
|
||||
" with value 0x%" PRIx64 "\n", __func__, offset, val);
|
||||
}
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_pmgr_ops = {
|
||||
.read = s5l8950x_pmgr_read,
|
||||
.write = s5l8950x_pmgr_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void s5l8950x_pmgr_reset(DeviceState *dev)
|
||||
{
|
||||
S5L8950XPmgrState *s = S5L8950X_PMGR(dev);
|
||||
int i;
|
||||
|
||||
/* Initialize all registers to 0xF (all clocks enabled) as reasonable defaults */
|
||||
for (i = 0; i < PMGR_NUM_REGS; i++) {
|
||||
s->regs[i] = 0xF;
|
||||
}
|
||||
|
||||
for (i = PMGR_GATE_BASE / 4; i < PMGR_GATE_END / 4; i++) {
|
||||
s->regs[i] = 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static void s5l8950x_pmgr_init(Object *obj)
|
||||
{
|
||||
S5L8950XPmgrState *s = S5L8950X_PMGR(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_pmgr_ops, s,
|
||||
TYPE_S5L8950X_PMGR, PMGR_REGION_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void s5l8950x_pmgr_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, s5l8950x_pmgr_reset);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_pmgr_info = {
|
||||
.name = TYPE_S5L8950X_PMGR,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XPmgrState),
|
||||
.instance_init = s5l8950x_pmgr_init,
|
||||
.class_init = s5l8950x_pmgr_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_pmgr_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_pmgr_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_pmgr_register_types)
|
||||
@@ -10,6 +10,7 @@ system_ss.add(when: 'CONFIG_CMSDK_APB_TIMER', if_true: files('cmsdk-apb-timer.c'
|
||||
system_ss.add(when: 'CONFIG_RENESAS_TMR', if_true: files('renesas_tmr.c'))
|
||||
system_ss.add(when: 'CONFIG_RENESAS_CMT', if_true: files('renesas_cmt.c'))
|
||||
system_ss.add(when: 'CONFIG_DIGIC', if_true: files('digic-timer.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X', if_true: files('s5l8950x-timer.c'))
|
||||
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_mct.c'))
|
||||
system_ss.add(when: 'CONFIG_EXYNOS4', if_true: files('exynos4210_pwm.c'))
|
||||
system_ss.add(when: 'CONFIG_GRLIB', if_true: files('grlib_gptimer.c'))
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_S5L8950X_TIMER "s5l8950x-timer"
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XTimerState, S5L8950X_TIMER)
|
||||
|
||||
struct S5L8950XTimerState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
uint32_t tcon[4];
|
||||
uint32_t tcnt[4];
|
||||
uint32_t tdata[4];
|
||||
uint32_t tstat[4];
|
||||
};
|
||||
|
||||
#define TIMER_64BIT_LO 0x20
|
||||
#define TIMER_64BIT_HI 0x28
|
||||
|
||||
static uint64_t s5l8950x_timer_read(void *opaque, hwaddr offset, unsigned size)
|
||||
{
|
||||
S5L8950XTimerState *s = opaque;
|
||||
uint64_t ticks;
|
||||
|
||||
ticks = muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), 24000000,
|
||||
NANOSECONDS_PER_SECOND);
|
||||
if (offset == TIMER_64BIT_LO) {
|
||||
return (uint32_t)ticks;
|
||||
}
|
||||
if (offset == TIMER_64BIT_HI) {
|
||||
return (uint32_t)(ticks >> 32);
|
||||
}
|
||||
|
||||
switch (offset) {
|
||||
case 0x00 ... 0x7C: {
|
||||
int channel = offset / 0x20;
|
||||
int reg = offset % 0x20;
|
||||
if (channel >= 4) {
|
||||
break;
|
||||
}
|
||||
switch (reg) {
|
||||
case 0x00: return s->tcon[channel];
|
||||
case 0x08: return s->tdata[channel];
|
||||
case 0x10: return s->tcnt[channel];
|
||||
case 0x14: return s->tstat[channel];
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void s5l8950x_timer_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
|
||||
{
|
||||
S5L8950XTimerState *s = opaque;
|
||||
|
||||
switch (offset) {
|
||||
case 0x00 ... 0x7C: {
|
||||
int channel = offset / 0x20;
|
||||
int reg = offset % 0x20;
|
||||
if (channel >= 4) {
|
||||
break;
|
||||
}
|
||||
switch (reg) {
|
||||
case 0x00: s->tcon[channel] = val; return;
|
||||
case 0x08: s->tdata[channel] = val; return;
|
||||
case 0x10: s->tcnt[channel] = val; return;
|
||||
case 0x14: s->tstat[channel] = val; return;
|
||||
case 0x04: /* TCMD */
|
||||
if (val == 1) {
|
||||
s->tstat[channel] &= ~1; /* Clear stop bit or whatever */
|
||||
} else if (val == 2) {
|
||||
/* Stop */
|
||||
}
|
||||
return;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
||||
return;
|
||||
}
|
||||
|
||||
qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write at offset 0x%" HWADDR_PRIx "\n", __func__, offset);
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_timer_ops = {
|
||||
.read = s5l8950x_timer_read,
|
||||
.write = s5l8950x_timer_write,
|
||||
.endianness = DEVICE_NATIVE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static void s5l8950x_timer_init(Object *obj)
|
||||
{
|
||||
S5L8950XTimerState *s = S5L8950X_TIMER(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_timer_ops, s,
|
||||
"s5l8950x-timer", 0x1000);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void s5l8950x_timer_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_timer_info = {
|
||||
.name = TYPE_S5L8950X_TIMER,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XTimerState),
|
||||
.instance_init = s5l8950x_timer_init,
|
||||
.class_init = s5l8950x_timer_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_timer_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_timer_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_timer_register_types)
|
||||
@@ -1,6 +1,9 @@
|
||||
config USB
|
||||
bool
|
||||
|
||||
config S5L8950X_USB_PHY
|
||||
bool
|
||||
|
||||
config USB_UHCI
|
||||
bool
|
||||
default y if PCI_DEVICES
|
||||
|
||||
@@ -27,6 +27,10 @@ system_ss.add(when: 'CONFIG_USB_XHCI_NEC', if_true: files('hcd-xhci-nec.c'))
|
||||
system_ss.add(when: 'CONFIG_USB_DWC2', if_true: files('hcd-dwc2.c'))
|
||||
system_ss.add(when: 'CONFIG_USB_DWC3', if_true: files('hcd-dwc3.c'))
|
||||
system_ss.add(when: 'CONFIG_USB_CHIPIDEA', if_true: files('chipidea.c'))
|
||||
system_ss.add(when: 'CONFIG_S5L8950X_USB_PHY', if_true: files(
|
||||
's5l8950x-usb-otg.c',
|
||||
's5l8950x-usb-phy.c',
|
||||
))
|
||||
|
||||
system_ss.add(when: 'CONFIG_IMX_USBPHY', if_true: files('imx-usb-phy.c'))
|
||||
system_ss.add(when: 'CONFIG_VT82C686', if_true: files('vt82c686-uhci-pci.c'))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
* Apple S5L8950X USB PHY (minimal SecureROM-facing model)
|
||||
*
|
||||
* The SecureROM samples bit 0 of register 0x28 to determine whether a USB
|
||||
* cable is present before honoring the recovery-button GPIO and entering DFU.
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/arm/s5l8950x.h"
|
||||
#include "hw/core/qdev-properties.h"
|
||||
#include "hw/core/sysbus.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define S5L8950X_USB_PHY_REGION_SIZE 0x1000
|
||||
#define S5L8950X_USB_PHY_NUM_REGS \
|
||||
(S5L8950X_USB_PHY_REGION_SIZE / sizeof(uint32_t))
|
||||
#define S5L8950X_USB_PHY_CABLE_STATUS 0x28
|
||||
#define S5L8950X_USB_PHY_CABLE_PRESENT BIT(0)
|
||||
|
||||
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XUSBPHYState, S5L8950X_USB_PHY)
|
||||
|
||||
struct S5L8950XUSBPHYState {
|
||||
SysBusDevice parent_obj;
|
||||
MemoryRegion iomem;
|
||||
uint32_t regs[S5L8950X_USB_PHY_NUM_REGS];
|
||||
bool cable_connected;
|
||||
};
|
||||
|
||||
static uint64_t s5l8950x_usb_phy_read(void *opaque, hwaddr offset,
|
||||
unsigned size)
|
||||
{
|
||||
S5L8950XUSBPHYState *s = opaque;
|
||||
uint32_t value = s->regs[offset / sizeof(uint32_t)];
|
||||
|
||||
if (offset == S5L8950X_USB_PHY_CABLE_STATUS && s->cable_connected) {
|
||||
value |= S5L8950X_USB_PHY_CABLE_PRESENT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void s5l8950x_usb_phy_write(void *opaque, hwaddr offset,
|
||||
uint64_t value, unsigned size)
|
||||
{
|
||||
S5L8950XUSBPHYState *s = opaque;
|
||||
|
||||
s->regs[offset / sizeof(uint32_t)] = value;
|
||||
}
|
||||
|
||||
static const MemoryRegionOps s5l8950x_usb_phy_ops = {
|
||||
.read = s5l8950x_usb_phy_read,
|
||||
.write = s5l8950x_usb_phy_write,
|
||||
.endianness = DEVICE_LITTLE_ENDIAN,
|
||||
.valid = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
.impl = {
|
||||
.min_access_size = 4,
|
||||
.max_access_size = 4,
|
||||
},
|
||||
};
|
||||
|
||||
static const Property s5l8950x_usb_phy_properties[] = {
|
||||
DEFINE_PROP_BOOL("cable-connected", S5L8950XUSBPHYState,
|
||||
cable_connected, false),
|
||||
};
|
||||
|
||||
static void s5l8950x_usb_phy_reset(DeviceState *dev)
|
||||
{
|
||||
S5L8950XUSBPHYState *s = S5L8950X_USB_PHY(dev);
|
||||
|
||||
memset(s->regs, 0, sizeof(s->regs));
|
||||
}
|
||||
|
||||
static void s5l8950x_usb_phy_init(Object *obj)
|
||||
{
|
||||
S5L8950XUSBPHYState *s = S5L8950X_USB_PHY(obj);
|
||||
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
|
||||
|
||||
memory_region_init_io(&s->iomem, obj, &s5l8950x_usb_phy_ops, s,
|
||||
TYPE_S5L8950X_USB_PHY,
|
||||
S5L8950X_USB_PHY_REGION_SIZE);
|
||||
sysbus_init_mmio(sbd, &s->iomem);
|
||||
}
|
||||
|
||||
static void s5l8950x_usb_phy_class_init(ObjectClass *klass, const void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
|
||||
device_class_set_legacy_reset(dc, s5l8950x_usb_phy_reset);
|
||||
device_class_set_props(dc, s5l8950x_usb_phy_properties);
|
||||
}
|
||||
|
||||
static const TypeInfo s5l8950x_usb_phy_info = {
|
||||
.name = TYPE_S5L8950X_USB_PHY,
|
||||
.parent = TYPE_SYS_BUS_DEVICE,
|
||||
.instance_size = sizeof(S5L8950XUSBPHYState),
|
||||
.instance_init = s5l8950x_usb_phy_init,
|
||||
.class_init = s5l8950x_usb_phy_class_init,
|
||||
};
|
||||
|
||||
static void s5l8950x_usb_phy_register_types(void)
|
||||
{
|
||||
type_register_static(&s5l8950x_usb_phy_info);
|
||||
}
|
||||
|
||||
type_init(s5l8950x_usb_phy_register_types)
|
||||
Reference in New Issue
Block a user