138f2cfbbSNolan Leake /* 238f2cfbbSNolan Leake * BCM2835 Power Management emulation 338f2cfbbSNolan Leake * 438f2cfbbSNolan Leake * Copyright (C) 2017 Marcin Chojnacki <marcinch7@gmail.com> 538f2cfbbSNolan Leake * Copyright (C) 2021 Nolan Leake <nolan@sigbus.net> 638f2cfbbSNolan Leake * 738f2cfbbSNolan Leake * This work is licensed under the terms of the GNU GPL, version 2 or later. 838f2cfbbSNolan Leake * See the COPYING file in the top-level directory. 938f2cfbbSNolan Leake */ 1038f2cfbbSNolan Leake 1138f2cfbbSNolan Leake #include "qemu/osdep.h" 1238f2cfbbSNolan Leake #include "qemu/log.h" 1338f2cfbbSNolan Leake #include "qemu/module.h" 1438f2cfbbSNolan Leake #include "hw/misc/bcm2835_powermgt.h" 1538f2cfbbSNolan Leake #include "migration/vmstate.h" 1638f2cfbbSNolan Leake #include "sysemu/runstate.h" 1738f2cfbbSNolan Leake 1838f2cfbbSNolan Leake #define PASSWORD 0x5a000000 1938f2cfbbSNolan Leake #define PASSWORD_MASK 0xff000000 2038f2cfbbSNolan Leake 2138f2cfbbSNolan Leake #define R_RSTC 0x1c 2238f2cfbbSNolan Leake #define V_RSTC_RESET 0x20 2338f2cfbbSNolan Leake #define R_RSTS 0x20 2438f2cfbbSNolan Leake #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */ 2538f2cfbbSNolan Leake #define R_WDOG 0x24 2638f2cfbbSNolan Leake 2738f2cfbbSNolan Leake static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset, 2838f2cfbbSNolan Leake unsigned size) 2938f2cfbbSNolan Leake { 3038f2cfbbSNolan Leake BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque; 3138f2cfbbSNolan Leake uint32_t res = 0; 3238f2cfbbSNolan Leake 3338f2cfbbSNolan Leake switch (offset) { 3438f2cfbbSNolan Leake case R_RSTC: 3538f2cfbbSNolan Leake res = s->rstc; 3638f2cfbbSNolan Leake break; 3738f2cfbbSNolan Leake case R_RSTS: 3838f2cfbbSNolan Leake res = s->rsts; 3938f2cfbbSNolan Leake break; 4038f2cfbbSNolan Leake case R_WDOG: 4138f2cfbbSNolan Leake res = s->wdog; 4238f2cfbbSNolan Leake break; 4338f2cfbbSNolan Leake 4438f2cfbbSNolan Leake default: 4538f2cfbbSNolan Leake qemu_log_mask(LOG_UNIMP, 4638f2cfbbSNolan Leake "bcm2835_powermgt_read: Unknown offset 0x%08"HWADDR_PRIx 4738f2cfbbSNolan Leake "\n", offset); 4838f2cfbbSNolan Leake res = 0; 4938f2cfbbSNolan Leake break; 5038f2cfbbSNolan Leake } 5138f2cfbbSNolan Leake 5238f2cfbbSNolan Leake return res; 5338f2cfbbSNolan Leake } 5438f2cfbbSNolan Leake 5538f2cfbbSNolan Leake static void bcm2835_powermgt_write(void *opaque, hwaddr offset, 5638f2cfbbSNolan Leake uint64_t value, unsigned size) 5738f2cfbbSNolan Leake { 5838f2cfbbSNolan Leake BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque; 5938f2cfbbSNolan Leake 6038f2cfbbSNolan Leake if ((value & PASSWORD_MASK) != PASSWORD) { 6138f2cfbbSNolan Leake qemu_log_mask(LOG_GUEST_ERROR, 6238f2cfbbSNolan Leake "bcm2835_powermgt_write: Bad password 0x%"PRIx64 6338f2cfbbSNolan Leake " at offset 0x%08"HWADDR_PRIx"\n", 6438f2cfbbSNolan Leake value, offset); 6538f2cfbbSNolan Leake return; 6638f2cfbbSNolan Leake } 6738f2cfbbSNolan Leake 6838f2cfbbSNolan Leake value = value & ~PASSWORD_MASK; 6938f2cfbbSNolan Leake 7038f2cfbbSNolan Leake switch (offset) { 7138f2cfbbSNolan Leake case R_RSTC: 7238f2cfbbSNolan Leake s->rstc = value; 7338f2cfbbSNolan Leake if (value & V_RSTC_RESET) { 7438f2cfbbSNolan Leake if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) { 7538f2cfbbSNolan Leake qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); 7638f2cfbbSNolan Leake } else { 7738f2cfbbSNolan Leake qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); 7838f2cfbbSNolan Leake } 7938f2cfbbSNolan Leake } 8038f2cfbbSNolan Leake break; 8138f2cfbbSNolan Leake case R_RSTS: 8238f2cfbbSNolan Leake qemu_log_mask(LOG_UNIMP, 8338f2cfbbSNolan Leake "bcm2835_powermgt_write: RSTS\n"); 8438f2cfbbSNolan Leake s->rsts = value; 8538f2cfbbSNolan Leake break; 8638f2cfbbSNolan Leake case R_WDOG: 8738f2cfbbSNolan Leake qemu_log_mask(LOG_UNIMP, 8838f2cfbbSNolan Leake "bcm2835_powermgt_write: WDOG\n"); 8938f2cfbbSNolan Leake s->wdog = value; 9038f2cfbbSNolan Leake break; 9138f2cfbbSNolan Leake 9238f2cfbbSNolan Leake default: 9338f2cfbbSNolan Leake qemu_log_mask(LOG_UNIMP, 9438f2cfbbSNolan Leake "bcm2835_powermgt_write: Unknown offset 0x%08"HWADDR_PRIx 9538f2cfbbSNolan Leake "\n", offset); 9638f2cfbbSNolan Leake break; 9738f2cfbbSNolan Leake } 9838f2cfbbSNolan Leake } 9938f2cfbbSNolan Leake 10038f2cfbbSNolan Leake static const MemoryRegionOps bcm2835_powermgt_ops = { 10138f2cfbbSNolan Leake .read = bcm2835_powermgt_read, 10238f2cfbbSNolan Leake .write = bcm2835_powermgt_write, 10338f2cfbbSNolan Leake .endianness = DEVICE_NATIVE_ENDIAN, 10438f2cfbbSNolan Leake .impl.min_access_size = 4, 10538f2cfbbSNolan Leake .impl.max_access_size = 4, 10638f2cfbbSNolan Leake }; 10738f2cfbbSNolan Leake 10838f2cfbbSNolan Leake static const VMStateDescription vmstate_bcm2835_powermgt = { 10938f2cfbbSNolan Leake .name = TYPE_BCM2835_POWERMGT, 11038f2cfbbSNolan Leake .version_id = 1, 11138f2cfbbSNolan Leake .minimum_version_id = 1, 112e4ea952fSRichard Henderson .fields = (const VMStateField[]) { 11338f2cfbbSNolan Leake VMSTATE_UINT32(rstc, BCM2835PowerMgtState), 11438f2cfbbSNolan Leake VMSTATE_UINT32(rsts, BCM2835PowerMgtState), 11538f2cfbbSNolan Leake VMSTATE_UINT32(wdog, BCM2835PowerMgtState), 11638f2cfbbSNolan Leake VMSTATE_END_OF_LIST() 11738f2cfbbSNolan Leake } 11838f2cfbbSNolan Leake }; 11938f2cfbbSNolan Leake 12038f2cfbbSNolan Leake static void bcm2835_powermgt_init(Object *obj) 12138f2cfbbSNolan Leake { 12238f2cfbbSNolan Leake BCM2835PowerMgtState *s = BCM2835_POWERMGT(obj); 12338f2cfbbSNolan Leake 12438f2cfbbSNolan Leake memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s, 12538f2cfbbSNolan Leake TYPE_BCM2835_POWERMGT, 0x200); 12638f2cfbbSNolan Leake sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); 12738f2cfbbSNolan Leake } 12838f2cfbbSNolan Leake 12938f2cfbbSNolan Leake static void bcm2835_powermgt_reset(DeviceState *dev) 13038f2cfbbSNolan Leake { 13138f2cfbbSNolan Leake BCM2835PowerMgtState *s = BCM2835_POWERMGT(dev); 13238f2cfbbSNolan Leake 13338f2cfbbSNolan Leake /* https://elinux.org/BCM2835_registers#PM */ 13438f2cfbbSNolan Leake s->rstc = 0x00000102; 13538f2cfbbSNolan Leake s->rsts = 0x00001000; 13638f2cfbbSNolan Leake s->wdog = 0x00000000; 13738f2cfbbSNolan Leake } 13838f2cfbbSNolan Leake 13938f2cfbbSNolan Leake static void bcm2835_powermgt_class_init(ObjectClass *klass, void *data) 14038f2cfbbSNolan Leake { 14138f2cfbbSNolan Leake DeviceClass *dc = DEVICE_CLASS(klass); 14238f2cfbbSNolan Leake 143*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, bcm2835_powermgt_reset); 14438f2cfbbSNolan Leake dc->vmsd = &vmstate_bcm2835_powermgt; 14538f2cfbbSNolan Leake } 14638f2cfbbSNolan Leake 1475e78c98bSBernhard Beschow static const TypeInfo bcm2835_powermgt_info = { 14838f2cfbbSNolan Leake .name = TYPE_BCM2835_POWERMGT, 14938f2cfbbSNolan Leake .parent = TYPE_SYS_BUS_DEVICE, 15038f2cfbbSNolan Leake .instance_size = sizeof(BCM2835PowerMgtState), 15138f2cfbbSNolan Leake .class_init = bcm2835_powermgt_class_init, 15238f2cfbbSNolan Leake .instance_init = bcm2835_powermgt_init, 15338f2cfbbSNolan Leake }; 15438f2cfbbSNolan Leake 15538f2cfbbSNolan Leake static void bcm2835_powermgt_register_types(void) 15638f2cfbbSNolan Leake { 15738f2cfbbSNolan Leake type_register_static(&bcm2835_powermgt_info); 15838f2cfbbSNolan Leake } 15938f2cfbbSNolan Leake 16038f2cfbbSNolan Leake type_init(bcm2835_powermgt_register_types) 161