/* * Apple S5L8950X I2C controller (minimal polled-transfer model) * * Early iBoot uses the three controllers while bringing up board devices. * The target devices are not modelled yet, so reads return zero, but command * FIFO writes complete synchronously and without a NAK. This is sufficient * to preserve the firmware's normal timeout/error paths while allowing the * boot chain to continue through board discovery. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "hw/arm/s5l8950x.h" #include "hw/core/irq.h" #include "hw/core/sysbus.h" #include "qom/object.h" #define S5L8950X_I2C_REGION_SIZE 0x1000 #define S5L8950X_I2C_STATUS 0x14 #define S5L8950X_I2C_STATUS_DONE BIT(27) OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XI2CState, S5L8950X_I2C) struct S5L8950XI2CState { SysBusDevice parent_obj; MemoryRegion iomem; qemu_irq irq; uint32_t regs[S5L8950X_I2C_REGION_SIZE / sizeof(uint32_t)]; }; static uint64_t s5l8950x_i2c_read(void *opaque, hwaddr offset, unsigned size) { S5L8950XI2CState *s = opaque; uint32_t value = s->regs[offset / sizeof(uint32_t)]; if (offset == S5L8950X_I2C_STATUS) { value |= S5L8950X_I2C_STATUS_DONE; } return value; } static void s5l8950x_i2c_write(void *opaque, hwaddr offset, uint64_t value, unsigned size) { S5L8950XI2CState *s = opaque; if (offset == S5L8950X_I2C_STATUS) { s->regs[offset / sizeof(uint32_t)] &= ~value; if (!(s->regs[offset / sizeof(uint32_t)] & S5L8950X_I2C_STATUS_DONE)) { qemu_irq_lower(s->irq); } return; } s->regs[offset / sizeof(uint32_t)] = value; if (offset == 0) { s->regs[S5L8950X_I2C_STATUS / sizeof(uint32_t)] |= S5L8950X_I2C_STATUS_DONE; qemu_irq_raise(s->irq); } } static const MemoryRegionOps s5l8950x_i2c_ops = { .read = s5l8950x_i2c_read, .write = s5l8950x_i2c_write, .endianness = DEVICE_LITTLE_ENDIAN, .valid = { .min_access_size = 4, .max_access_size = 4, }, }; static void s5l8950x_i2c_reset(DeviceState *dev) { S5L8950XI2CState *s = S5L8950X_I2C(dev); memset(s->regs, 0, sizeof(s->regs)); qemu_irq_lower(s->irq); } static void s5l8950x_i2c_init(Object *obj) { S5L8950XI2CState *s = S5L8950X_I2C(obj); SysBusDevice *sbd = SYS_BUS_DEVICE(obj); memory_region_init_io(&s->iomem, obj, &s5l8950x_i2c_ops, s, TYPE_S5L8950X_I2C, S5L8950X_I2C_REGION_SIZE); sysbus_init_mmio(sbd, &s->iomem); sysbus_init_irq(sbd, &s->irq); } static void s5l8950x_i2c_class_init(ObjectClass *klass, const void *data) { DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_legacy_reset(dc, s5l8950x_i2c_reset); } static const TypeInfo s5l8950x_i2c_info = { .name = TYPE_S5L8950X_I2C, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(S5L8950XI2CState), .instance_init = s5l8950x_i2c_init, .class_init = s5l8950x_i2c_class_init, }; static void s5l8950x_i2c_register_types(void) { type_register_static(&s5l8950x_i2c_info); } type_init(s5l8950x_i2c_register_types)