1 /* 2 * BCM2835 Power Management emulation 3 * 4 * Copyright (C) 2017 Marcin Chojnacki <marcinch7@gmail.com> 5 * Copyright (C) 2021 Nolan Leake <nolan@sigbus.net> 6 * 7 * This work is licensed under the terms of the GNU GPL, version 2 or later. 8 * See the COPYING file in the top-level directory. 9 */ 10 11 #include "qemu/osdep.h" 12 #include "qemu/log.h" 13 #include "qemu/module.h" 14 #include "hw/misc/bcm2835_powermgt.h" 15 #include "migration/vmstate.h" 16 #include "sysemu/runstate.h" 17 18 #define PASSWORD 0x5a000000 19 #define PASSWORD_MASK 0xff000000 20 21 #define R_RSTC 0x1c 22 #define V_RSTC_RESET 0x20 23 #define R_RSTS 0x20 24 #define V_RSTS_POWEROFF 0x555 /* Linux uses partition 63 to indicate halt. */ 25 #define R_WDOG 0x24 26 27 static uint64_t bcm2835_powermgt_read(void *opaque, hwaddr offset, 28 unsigned size) 29 { 30 BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque; 31 uint32_t res = 0; 32 33 switch (offset) { 34 case R_RSTC: 35 res = s->rstc; 36 break; 37 case R_RSTS: 38 res = s->rsts; 39 break; 40 case R_WDOG: 41 res = s->wdog; 42 break; 43 44 default: 45 qemu_log_mask(LOG_UNIMP, 46 "bcm2835_powermgt_read: Unknown offset 0x%08"HWADDR_PRIx 47 "\n", offset); 48 res = 0; 49 break; 50 } 51 52 return res; 53 } 54 55 static void bcm2835_powermgt_write(void *opaque, hwaddr offset, 56 uint64_t value, unsigned size) 57 { 58 BCM2835PowerMgtState *s = (BCM2835PowerMgtState *)opaque; 59 60 if ((value & PASSWORD_MASK) != PASSWORD) { 61 qemu_log_mask(LOG_GUEST_ERROR, 62 "bcm2835_powermgt_write: Bad password 0x%"PRIx64 63 " at offset 0x%08"HWADDR_PRIx"\n", 64 value, offset); 65 return; 66 } 67 68 value = value & ~PASSWORD_MASK; 69 70 switch (offset) { 71 case R_RSTC: 72 s->rstc = value; 73 if (value & V_RSTC_RESET) { 74 if ((s->rsts & 0xfff) == V_RSTS_POWEROFF) { 75 qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN); 76 } else { 77 qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); 78 } 79 } 80 break; 81 case R_RSTS: 82 qemu_log_mask(LOG_UNIMP, 83 "bcm2835_powermgt_write: RSTS\n"); 84 s->rsts = value; 85 break; 86 case R_WDOG: 87 qemu_log_mask(LOG_UNIMP, 88 "bcm2835_powermgt_write: WDOG\n"); 89 s->wdog = value; 90 break; 91 92 default: 93 qemu_log_mask(LOG_UNIMP, 94 "bcm2835_powermgt_write: Unknown offset 0x%08"HWADDR_PRIx 95 "\n", offset); 96 break; 97 } 98 } 99 100 static const MemoryRegionOps bcm2835_powermgt_ops = { 101 .read = bcm2835_powermgt_read, 102 .write = bcm2835_powermgt_write, 103 .endianness = DEVICE_NATIVE_ENDIAN, 104 .impl.min_access_size = 4, 105 .impl.max_access_size = 4, 106 }; 107 108 static const VMStateDescription vmstate_bcm2835_powermgt = { 109 .name = TYPE_BCM2835_POWERMGT, 110 .version_id = 1, 111 .minimum_version_id = 1, 112 .fields = (const VMStateField[]) { 113 VMSTATE_UINT32(rstc, BCM2835PowerMgtState), 114 VMSTATE_UINT32(rsts, BCM2835PowerMgtState), 115 VMSTATE_UINT32(wdog, BCM2835PowerMgtState), 116 VMSTATE_END_OF_LIST() 117 } 118 }; 119 120 static void bcm2835_powermgt_init(Object *obj) 121 { 122 BCM2835PowerMgtState *s = BCM2835_POWERMGT(obj); 123 124 memory_region_init_io(&s->iomem, obj, &bcm2835_powermgt_ops, s, 125 TYPE_BCM2835_POWERMGT, 0x200); 126 sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem); 127 } 128 129 static void bcm2835_powermgt_reset(DeviceState *dev) 130 { 131 BCM2835PowerMgtState *s = BCM2835_POWERMGT(dev); 132 133 /* https://elinux.org/BCM2835_registers#PM */ 134 s->rstc = 0x00000102; 135 s->rsts = 0x00001000; 136 s->wdog = 0x00000000; 137 } 138 139 static void bcm2835_powermgt_class_init(ObjectClass *klass, void *data) 140 { 141 DeviceClass *dc = DEVICE_CLASS(klass); 142 143 device_class_set_legacy_reset(dc, bcm2835_powermgt_reset); 144 dc->vmsd = &vmstate_bcm2835_powermgt; 145 } 146 147 static const TypeInfo bcm2835_powermgt_info = { 148 .name = TYPE_BCM2835_POWERMGT, 149 .parent = TYPE_SYS_BUS_DEVICE, 150 .instance_size = sizeof(BCM2835PowerMgtState), 151 .class_init = bcm2835_powermgt_class_init, 152 .instance_init = bcm2835_powermgt_init, 153 }; 154 155 static void bcm2835_powermgt_register_types(void) 156 { 157 type_register_static(&bcm2835_powermgt_info); 158 } 159 160 type_init(bcm2835_powermgt_register_types) 161