Files
QEMU-S5L8950X/hw/misc/s5l8950x-clock.c
T
Yaya48 5d9a60a926 hw/arm: add authenticated A6 IMG3 boot lab
Model the A6 crypto, interrupt, USB, and platform blocks needed to boot SecureROM through iBSS into iBEC Recovery.

Add local lab identity, IMG3, and APTicket tooling, patched macOS recovery utilities, UART and GDB access, and English end-user documentation.
2026-09-01 09:51:49 -07:00

138 lines
3.8 KiB
C

/*
* Apple S5L8950X clock/PLL parameter controller.
*
* iBoot programs the ordinary clock registers directly and uses the command
* ports at 0x480..0x4a4 to query PLL parameters. The corresponding status
* ports contain two result bytes plus busy bits. Clock changes complete
* synchronously here; returning a non-zero divider is enough for iBoot to
* derive its early clock tree without inventing asynchronous hardware.
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
#include "hw/arm/s5l8950x.h"
#include "hw/core/sysbus.h"
#include "qom/object.h"
#define S5L8950X_CLOCK_REGION_SIZE 0x1000
#define S5L8950X_CLOCK_NUM_REGS \
(S5L8950X_CLOCK_REGION_SIZE / sizeof(uint32_t))
OBJECT_DECLARE_SIMPLE_TYPE(S5L8950XClockState, S5L8950X_CLOCK)
struct S5L8950XClockState {
SysBusDevice parent_obj;
MemoryRegion iomem;
uint32_t regs[S5L8950X_CLOCK_NUM_REGS];
};
static uint64_t s5l8950x_clock_read(void *opaque, hwaddr offset,
unsigned size)
{
S5L8950XClockState *s = opaque;
return s->regs[offset / sizeof(uint32_t)];
}
static void s5l8950x_clock_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
S5L8950XClockState *s = opaque;
uint32_t reg = value;
uint32_t selector = (reg >> 8) & 0xff;
uint32_t result = 1;
/*
* Parameter 8 describes the divider layout used by iBoot's early clock
* setup. Its bits 2..5 must encode a value in the accepted 4..6 range;
* the other parameters are ordinary non-zero divisors.
*/
if (selector == 8) {
result = 0x10;
}
s->regs[offset / sizeof(uint32_t)] = reg;
/* Read-command ports: publish the result and leave the busy bit clear. */
switch (offset) {
case 0x480:
s->regs[0x490 / 4] =
(s->regs[0x490 / 4] & ~0x0000ff01u) | (result << 8);
break;
case 0x488:
s->regs[0x490 / 4] =
(s->regs[0x490 / 4] & ~0x00ff0010u) | (result << 16);
break;
case 0x498:
s->regs[0x4a8 / 4] =
(s->regs[0x4a8 / 4] & ~0x0000ff01u) | (result << 8);
break;
case 0x4a0:
s->regs[0x4a8 / 4] =
(s->regs[0x4a8 / 4] & ~0x00ff0010u) | (result << 16);
break;
case 0x484:
case 0x48c:
case 0x49c:
case 0x4a4:
/* Write commands are consumed by the clock engine immediately. */
s->regs[offset / 4] = 0;
break;
default:
break;
}
}
static const MemoryRegionOps s5l8950x_clock_ops = {
.read = s5l8950x_clock_read,
.write = s5l8950x_clock_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4,
},
};
static void s5l8950x_clock_reset(DeviceState *dev)
{
S5L8950XClockState *s = S5L8950X_CLOCK(dev);
memset(s->regs, 0, sizeof(s->regs));
s->regs[0x490 / 4] = 0x00010100;
s->regs[0x4a8 / 4] = 0x00010100;
}
static void s5l8950x_clock_init(Object *obj)
{
S5L8950XClockState *s = S5L8950X_CLOCK(obj);
SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
memory_region_init_io(&s->iomem, obj, &s5l8950x_clock_ops, s,
TYPE_S5L8950X_CLOCK,
S5L8950X_CLOCK_REGION_SIZE);
sysbus_init_mmio(sbd, &s->iomem);
}
static void s5l8950x_clock_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
device_class_set_legacy_reset(dc, s5l8950x_clock_reset);
}
static const TypeInfo s5l8950x_clock_info = {
.name = TYPE_S5L8950X_CLOCK,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(S5L8950XClockState),
.instance_init = s5l8950x_clock_init,
.class_init = s5l8950x_clock_class_init,
};
static void s5l8950x_clock_register_types(void)
{
type_register_static(&s5l8950x_clock_info);
}
type_init(s5l8950x_clock_register_types)