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