/* * 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)) /* * SecureROM packs these four board straps into bits [11:8] of the * boot-policy word at 0x3f106000. Profile 2 is the authenticated A6 * production path used by this n41ap machine. */ #define S5L8950X_GPIO_STRAP_0_OFFSET (((0x02 * 8) + 2) * sizeof(uint32_t)) #define S5L8950X_GPIO_STRAP_1_OFFSET (((0x19 * 8) + 5) * sizeof(uint32_t)) #define S5L8950X_GPIO_STRAP_2_OFFSET (((0x1a * 8) + 0) * sizeof(uint32_t)) #define S5L8950X_GPIO_STRAP_3_OFFSET (((0x1a * 8) + 1) * sizeof(uint32_t)) /* iBEC samples GPIO 1 as an active-low boot/power button. */ #define S5L8950X_GPIO_BOOT_BUTTON_OFFSET 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; uint8_t board_straps; }; 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 (offset == S5L8950X_GPIO_BOOT_BUTTON_OFFSET) { /* * No host button is currently wired up: expose the released level so * iBEC does not mistake the zero-filled register file for a button * held continuously and power the device off after its timeout. */ value |= S5L8950X_GPIO_INPUT_LEVEL; } if (s->force_dfu && offset == S5L8950X_GPIO_DFU_OFFSET) { value |= S5L8950X_GPIO_INPUT_LEVEL; } if ((offset == S5L8950X_GPIO_STRAP_0_OFFSET && (s->board_straps & BIT(0))) || (offset == S5L8950X_GPIO_STRAP_1_OFFSET && (s->board_straps & BIT(1))) || (offset == S5L8950X_GPIO_STRAP_2_OFFSET && (s->board_straps & BIT(2))) || (offset == S5L8950X_GPIO_STRAP_3_OFFSET && (s->board_straps & BIT(3)))) { 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), DEFINE_PROP_UINT8("board-straps", S5L8950XGPIOState, board_straps, 0), }; 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)