diff --git a/hw/arm/bcm2835_peripherals.c b/hw/arm/bcm2835_peripherals.c
index 965f4c1f3d..c887969795 100644
--- a/hw/arm/bcm2835_peripherals.c
+++ b/hw/arm/bcm2835_peripherals.c
@@ -62,6 +62,11 @@ static void bcm2835_peripherals_init(Object *obj)
     sysbus_init_child_obj(obj, "systimer", &s->systmr, sizeof(s->systmr),
                           TYPE_BCM2835_SYSTIMER);
 
+    /* Clock / Power / Reset */
+    object_initialize(&s->cprman, sizeof(s->cprman), TYPE_BCM2835_CPRMAN);
+    object_property_add_child(obj, "cprman", OBJECT(&s->cprman), NULL);
+    qdev_set_parent_bus(DEVICE(&s->cprman), sysbus_get_default());
+
     /* UART0 */
     sysbus_init_child_obj(obj, "uart0", &s->uart0, sizeof(s->uart0),
                           TYPE_PL011);
@@ -191,6 +196,19 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp)
         qdev_get_gpio_in_named(DEVICE(&s->ic), BCM2835_IC_ARM_IRQ,
                                INTERRUPT_ARM_TIMER));
 
+    /* Clock / Power / Reset */
+    object_property_set_bool(OBJECT(&s->cprman), true, "realized", &err);
+    if (err) {
+        error_propagate(errp, err);
+        return;
+    }
+    memory_region_add_subregion(&s->peri_mr, CPRMAN_OFFSET,
+                sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->cprman), 0));
+    memory_region_add_subregion(&s->peri_mr, CM_OFFSET,
+                sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->cprman), 1));
+    memory_region_add_subregion(&s->peri_mr, A2W_OFFSET,
+                sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->cprman), 2));
+
     /* UART0 */
     qdev_prop_set_chr(DEVICE(&s->uart0), "chardev", serial_hd(0));
     object_property_set_bool(OBJECT(&s->uart0), true, "realized", &err);
@@ -372,8 +390,6 @@ static void bcm2835_peripherals_realize(DeviceState *dev, Error **errp)
     }
 
     create_unimp(s, &s->armtmr, "bcm2835-sp804", ARMCTRL_TIMER0_1_OFFSET, 0x40);
-    create_unimp(s, &s->cprman, "bcm2835-cprman", CPRMAN_OFFSET, 0x1000);
-    create_unimp(s, &s->a2w, "bcm2835-a2w", A2W_OFFSET, 0x1000);
     create_unimp(s, &s->i2s, "bcm2835-i2s", I2S_OFFSET, 0x100);
     create_unimp(s, &s->smi, "bcm2835-smi", SMI_OFFSET, 0x100);
     create_unimp(s, &s->spi[0], "bcm2835-spi0", SPI0_OFFSET, 0x20);
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs
index c89f3816a5..64e717e6b3 100644
--- a/hw/misc/Makefile.objs
+++ b/hw/misc/Makefile.objs
@@ -53,6 +53,7 @@ common-obj-$(CONFIG_OMAP) += omap_tap.o
 common-obj-$(CONFIG_RASPI) += bcm2835_mbox.o
 common-obj-$(CONFIG_RASPI) += bcm2835_property.o
 common-obj-$(CONFIG_RASPI) += bcm2835_rng.o
+common-obj-$(CONFIG_RASPI) += bcm2835_cprman.o
 common-obj-$(CONFIG_RASPI) += bcm2835_thermal.o
 common-obj-$(CONFIG_SLAVIO) += slavio_misc.o
 common-obj-$(CONFIG_ZYNQ) += zynq_slcr.o
diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c
new file mode 100644
index 0000000000..6c3b5b6837
--- /dev/null
+++ b/hw/misc/bcm2835_cprman.c
@@ -0,0 +1,383 @@
+/*
+ * BCM2835 Clock/Power/Reset Manager subsystem (poor man's version)
+ *
+ * Copyright (C) 2018 Guenter Roeck <linux@roeck-us.net>
+ * Copyright (C) 2018 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "sysemu/runstate.h"
+#include "hw/registerfields.h"
+#include "hw/misc/bcm2835_cprman.h"
+#include "trace.h"
+
+#define CPRMAN_PASSWD 'Z'
+
+FIELD(CPRMAN, PASSWD,   24, 8)
+
+REG32(PM_RSTC,          0x1c)
+REG32(PM_RSTS,          0x20)
+REG32(PM_WDOG,          0x24)
+
+static const char *pm_name(hwaddr addr)
+{
+    addr &= ~3;
+    switch (addr) {
+    case A_PM_RSTC: return "RST_CLR";
+    case A_PM_RSTS: return "RST_SET";
+    case A_PM_WDOG: return "WDG";
+    default:
+        return "UNKN";
+    }
+}
+
+static uint64_t bcm2835_cprman_pm_read(void *opaque, hwaddr addr,
+                                    unsigned size)
+{
+    uint32_t res = 0;
+
+    trace_bcm2835_cprman_read(size << 3, addr, "PM", pm_name(addr), "", res);
+
+    return res;
+}
+
+static void bcm2835_cprman_pm_write(void *opaque, hwaddr addr,
+                                    uint64_t value, unsigned size)
+{
+    const char *name;
+
+    if (FIELD_EX32(value, CPRMAN, PASSWD) != CPRMAN_PASSWD) {
+        qemu_log_mask(LOG_GUEST_ERROR, "[CPRMAN]: password key error w%02d"
+                                       " *0x%04"HWADDR_PRIx" = 0x%"PRIx64"\n",
+                      size << 3, addr, value);
+        return;
+    }
+    value &= ~R_CPRMAN_PASSWD_MASK;
+
+    name = pm_name(addr);
+    trace_bcm2835_cprman_write_pm(addr, name, value);
+    if (addr == A_PM_RSTC && value & 0x20) { /* TODO remove 0x20 magic */
+        qemu_system_shutdown_request(SHUTDOWN_CAUSE_GUEST_SHUTDOWN);
+    }
+}
+
+static const MemoryRegionOps bcm2835_cprman_pm_ops = {
+    .read = bcm2835_cprman_pm_read,
+    .write = bcm2835_cprman_pm_write,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+REG32(CM_CTL,               0)
+FIELD(CM_CTL, SRC,       0, 4)
+FIELD(CM_CTL, ENABLE,    4, 1)
+FIELD(CM_CTL, KILL,      5, 1)
+FIELD(CM_CTL, GATE,      6, 1)
+FIELD(CM_CTL, BUSY,      7, 1)
+FIELD(CM_CTL, BUSYD,     8, 1)
+FIELD(CM_CTL, FRAC,      9, 1)
+
+REG32(CM_DIV,               4)
+FIELD(CM_DIV, FRAC,      0, 12)
+FIELD(CM_DIV, INTEGER,  12, 12)
+
+REG32(CM_OSCCOUNT,      0x100)
+REG32(CM_LOCK,          0x114)
+REG32(CM_EVENT,         0x118)
+REG32(CM_PLLB,          0x170)
+
+/* Bits used by R_CM_CTL_SRC_MASK */
+enum cprman_clock_source {
+    SRC_GND = 0,
+    SRC_OSC = 1,
+    SRC_TEST_DBG0 = 2,
+    SRC_TEST_DBG1 = 3,
+    SRC_PLLA = 4,
+    SRC_PLLC_CORE0 = 5,
+    SRC_PLLD = 6,
+    SRC_PLLH_AUX = 7,
+    SRC_PLLC_CORE1 = 8,
+    SRC_PLLC_CORE2 = 9
+};
+
+static const char *src_name(int src)
+{
+    static const char *src_names[16] = {
+        [SRC_GND] = "GND",
+        [SRC_OSC] = "OSC",
+        [SRC_PLLA] = "PLLA",
+        [SRC_PLLC_CORE0] = "PLLC_CORE0",
+        [SRC_PLLD] = "PLLD",
+        [SRC_PLLH_AUX] = "PLLH_AUX",
+        [SRC_PLLC_CORE1] = "PLLC_CORE1",
+        [SRC_PLLC_CORE2] = "PLLC_CORE2",
+    };
+    return src_names[src] ? src_names[src] : "UNKN";
+}
+
+static const char *ctldiv_names[0x200 / 4] = {
+    [0] = "GENERIC",
+    [1] = "VPU",
+    [2] = "SYS",
+    [3] = "PERIA",
+    [4] = "PERII",
+    [5] = "H264",
+    [6] = "ISP",
+    [7] = "V3D",
+    [8] = "CAM0",
+    [9] = "CAM1",
+    [10] = "CCP2",
+    [11] = "DSI0E",
+    [12] = "DSI0P",
+    [13] = "DPI",
+    [14] = "GP0",
+    [15] = "GP1",
+    [16] = "GP2",
+    [17] = "HSM",
+    [18] = "OTP",
+    [19] = "PCM",
+    [20] = "PWM",
+    [21] = "SLIM",
+    [22] = "SMI",
+    [24] = "TCNT",
+    [25] = "TEC",
+    [26] = "TD0",
+    [27] = "TD1",
+    [28] = "TSENS",
+    [29] = "TIMER",
+    [30] = "UART",
+    [31] = "VEC",
+    [43] = "DSI1E",
+    [44] = "DSI1P",
+    [45] = "DFT",
+    [50] = "PULSE",
+    [53] = "SDC",
+    [54] = "ARM",
+    [55] = "AVEO",
+    [56] = "EMMC",
+};
+
+static const char *cm_name(hwaddr addr)
+{
+    int idx;
+
+    addr &= ~3;
+    switch (addr) {
+    case A_CM_OSCCOUNT: return "OSCCOUNT";
+    case 0x104 ... 0x110:
+    case A_CM_PLLB: return "PLLx";
+    case A_CM_LOCK: return "LOCK";
+    case A_CM_EVENT: return "EVENT";
+    default:
+        idx = addr / 8;
+        return ctldiv_names[idx] ? ctldiv_names[idx] : "UNKN";
+    }
+}
+
+static uint32_t scale(uint32_t value)
+{
+    return (1000ull * value) >> 10;
+}
+
+/*
+ * Available information suggests that CPRMAN registers have default
+ * values which are not overwritten by ROMMON (u-boot). The hardware
+ * default values are unknown at this time.
+ * The default values selected here are necessary and sufficient
+ * to boot Linux directly (on raspi2 and raspi3). The selected
+ * values enable all clocks and set clock rates to match their
+ * parent rates.
+ */
+static uint64_t bcm2835_cprman_cm_read(void *opaque, hwaddr addr,
+                                       unsigned size)
+{
+    uint32_t res = 0;
+
+    if (addr == A_CM_LOCK) {
+        res = 0b11111 << 8; /* all locked! */
+    } else {
+        switch (addr & 0xf) {
+        case A_CM_CTL:
+            res = SRC_OSC | R_CM_CTL_ENABLE_MASK;
+            break;
+        case A_CM_DIV:
+            res = FIELD_DP32(0, CM_DIV, INTEGER, 1);
+            break;
+        default:
+            qemu_log_mask(LOG_UNIMP, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+                          __func__, addr);
+        }
+    }
+    trace_bcm2835_cprman_read(size << 3, addr, "CM", cm_name(addr), "", res);
+
+    return res;
+}
+
+static void bcm2835_cprman_cm_write(void *opaque, hwaddr addr,
+                                    uint64_t value, unsigned size)
+{
+    const char *name;
+
+    if (FIELD_EX32(value, CPRMAN, PASSWD) != CPRMAN_PASSWD) {
+        qemu_log_mask(LOG_GUEST_ERROR, "[CPRMAN]: password key error w%02d"
+                                       " *0x%04"HWADDR_PRIx" = 0x%"PRIx64"\n",
+                      size << 3, addr, value);
+        return;
+    }
+    value &= ~R_CPRMAN_PASSWD_MASK;
+
+    name = cm_name(addr);
+    switch (addr) {
+    case A_CM_OSCCOUNT:
+    case 0x104 ... 0x110:
+    case A_CM_PLLB:
+    case A_CM_LOCK:
+    case A_CM_EVENT:
+        trace_bcm2835_cprman_write_cm_generic(name, value);
+        break;
+    default:
+        switch (addr & 0xf) {
+        case A_CM_CTL:
+            trace_bcm2835_cprman_write_cm_ctl(name, src_name(value & 0xf),
+                                FIELD_EX32(value, CM_CTL, ENABLE));
+            break;
+        case A_CM_DIV:
+            trace_bcm2835_cprman_write_cm_div(name,
+                                FIELD_EX32(value, CM_DIV, INTEGER),
+                                scale(FIELD_EX32(value, CM_DIV, FRAC)));
+        }
+    }
+}
+
+static const MemoryRegionOps bcm2835_cprman_cm_ops = {
+    .read = bcm2835_cprman_cm_read,
+    .write = bcm2835_cprman_cm_write,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+REG32(A2W_PLL_CTRL, 0x00)
+FIELD(A2W_PLL_CTRL, NDIV,                   0, 12)
+FIELD(A2W_PLL_CTRL, PDIV,                   12, 3)
+FIELD(A2W_PLL_CTRL, POWER_DOWN,             16, 1)
+FIELD(A2W_PLL_CTRL, POWER_RESET_DISABLE,    17, 1)
+
+REG32(A2W_PLL_ANA0, 0x10)
+
+FIELD(A2W_PLL_FRAC, DIV,                    0, 20)
+
+FIELD(A2W_PLL_CHAN, DIV,                    0, 8)
+FIELD(A2W_PLL_CHAN, DISABLE,                8, 1)
+
+static const char *a2w_name(hwaddr addr)
+{
+    if (addr >= 0x300) {
+        return "CHANx";
+    }
+    if (addr >= 0x200) {
+        return "FRACx";
+    }
+    switch (addr & 0x1f) {
+    case A_A2W_PLL_CTRL:
+        return "CTRL";
+    case A_A2W_PLL_ANA0:
+        return "ANA0";
+    default:
+        return "UNKN";
+    }
+}
+
+static const char *pll_name(int idx)
+{
+    static const char *pll_names[8] = {
+        [0] = "PLLA",
+        [1] = "PLLC",
+        [2] = "PLLD",
+        [3] = "PLLH",
+        [7] = "PLLB",
+    };
+    return pll_names[idx] ? pll_names[idx] : "UNKN";
+}
+
+static uint64_t bcm2835_cprman_a2w_read(void *opaque, hwaddr addr,
+                                        unsigned size)
+{
+    uint32_t res = 0;
+
+    if (addr < 0x200) {
+        /* Power */
+        switch (addr & 0x1f) {
+        case A_A2W_PLL_CTRL:
+            res = R_A2W_PLL_CTRL_POWER_DOWN_MASK; /* On */
+            break;
+        case A_A2W_PLL_ANA0:
+            break;
+        }
+    } else {
+        /* addr < 0x300 is FREQ, else CHANNEL */
+        qemu_log_mask(LOG_UNIMP, "%s: bad offset 0x%" HWADDR_PRIx "\n",
+                      __func__, addr);
+    }
+    trace_bcm2835_cprman_read(size << 3, addr, "A2W", a2w_name(addr),
+                              pll_name((addr >> 5) & 7), res);
+
+    return res;
+}
+
+static void bcm2835_cprman_a2w_write(void *opaque, hwaddr addr,
+                                     uint64_t value, unsigned size)
+{
+    if (FIELD_EX32(value, CPRMAN, PASSWD) != CPRMAN_PASSWD) {
+        qemu_log_mask(LOG_GUEST_ERROR, "[CPRMAN]: password key error w%02d"
+                                       " *0x%04"HWADDR_PRIx" = 0x%"PRIx64"\n",
+                      size << 3, addr, value);
+        return;
+    }
+    value &= ~R_CPRMAN_PASSWD_MASK;
+
+    trace_bcm2835_cprman_write_a2w(addr, a2w_name(addr),
+                                   pll_name((addr >> 5) & 7), value);
+}
+
+static const MemoryRegionOps bcm2835_cprman_a2w_ops = {
+    .read = bcm2835_cprman_a2w_read,
+    .write = bcm2835_cprman_a2w_write,
+    .impl.max_access_size = 4,
+    .valid.min_access_size = 4,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static void bcm2835_cprman_init(Object *obj)
+{
+    BCM2835CprmanState *s = BCM2835_CPRMAN(obj);
+
+    memory_region_init_io(&s->iomem.pm, obj, &bcm2835_cprman_pm_ops, s,
+                          TYPE_BCM2835_CPRMAN "-pm", 0x1000);
+    memory_region_init_io(&s->iomem.cm, obj, &bcm2835_cprman_cm_ops, s,
+                          TYPE_BCM2835_CPRMAN "-cm", 0x1000);
+    memory_region_init_io(&s->iomem.a2w, obj, &bcm2835_cprman_a2w_ops, s,
+                          TYPE_BCM2835_CPRMAN "-a2w", 0x1000);
+    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem.pm);
+    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem.cm);
+    sysbus_init_mmio(SYS_BUS_DEVICE(s), &s->iomem.a2w);
+}
+
+static TypeInfo bcm2835_cprman_info = {
+    .name          = TYPE_BCM2835_CPRMAN,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(BCM2835CprmanState),
+    .instance_init = bcm2835_cprman_init,
+};
+
+static void bcm2835_cprman_register_types(void)
+{
+    type_register_static(&bcm2835_cprman_info);
+}
+
+type_init(bcm2835_cprman_register_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index 1deb1d08c1..30d33e2e1d 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -149,3 +149,11 @@ bcm2835_mbox_write(unsigned int size, uint64_t addr, uint64_t value) "mbox write
 bcm2835_mbox_read(unsigned int size, uint64_t addr, uint64_t value) "mbox read sz:%u addr:0x%"PRIx64" data:0x%"PRIx64
 bcm2835_mbox_irq(unsigned level) "mbox irq:ARM level:%u"
 bcm2835_mbox_property(uint32_t tag, uint32_t bufsize, size_t resplen) "mbox property tag:0x%08x in_sz:%u out_sz:%zu"
+
+# bcm2835_cprman.c
+bcm2835_cprman_read(unsigned size, uint64_t offset, const char *reg_type, const char *reg_name1, const char *reg_name2, uint32_t value) "cprman: rd%u @0x%03" PRIx64 " %s %s.%s val:0x%x"
+bcm2835_cprman_write_pm(uint64_t offset, const char *reg_name, uint32_t value) "pm: wr @0x%03" PRIx64 " %s val:0x%x"
+bcm2835_cprman_write_cm_generic(const char *reg_name, uint32_t val) "cprman: wr %s 0x%08x"
+bcm2835_cprman_write_cm_div(const char *reg_name, uint32_t v0, uint32_t v1) "cprman: wr %s.DIV float:%u.%u"
+bcm2835_cprman_write_cm_ctl(const char *reg_name, const char *src, uint32_t ena) "cprman: wr %s.CTL src:%s enabled:%u"
+bcm2835_cprman_write_a2w(uint64_t offset, const char *reg_name, const char *pll_name, uint32_t value) "a2w: wr @0x%03" PRIx64 " %s.%s val:0x%x"
diff --git a/include/hw/arm/bcm2835_peripherals.h b/include/hw/arm/bcm2835_peripherals.h
index 5b9fc89453..b4360ca1a8 100644
--- a/include/hw/arm/bcm2835_peripherals.h
+++ b/include/hw/arm/bcm2835_peripherals.h
@@ -19,6 +19,7 @@
 #include "hw/intc/bcm2835_ic.h"
 #include "hw/misc/bcm2835_property.h"
 #include "hw/misc/bcm2835_rng.h"
+#include "hw/misc/bcm2835_cprman.h"
 #include "hw/misc/bcm2835_mbox.h"
 #include "hw/misc/bcm2835_thermal.h"
 #include "hw/sd/sdhci.h"
@@ -42,8 +43,7 @@ typedef struct BCM2835PeripheralState {
 
     BCM2835SysTimerState systmr;
     UnimplementedDeviceState armtmr;
-    UnimplementedDeviceState cprman;
-    UnimplementedDeviceState a2w;
+    BCM2835CprmanState cprman;
     PL011State uart0;
     BCM2835AuxState aux;
     BCM2835FBState fb;
diff --git a/include/hw/misc/bcm2835_cprman.h b/include/hw/misc/bcm2835_cprman.h
new file mode 100644
index 0000000000..4dd2c8323b
--- /dev/null
+++ b/include/hw/misc/bcm2835_cprman.h
@@ -0,0 +1,32 @@
+/*
+ * BCM2835 Clock/Power/Reset Manager subsystem (poor man's version)
+ *
+ * Copyright (C) 2018 Guenter Roeck <linux@roeck-us.net>
+ * Copyright (C) 2018 Philippe Mathieu-Daudé <f4bug@amsat.org>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+
+#ifndef BCM2835_CPRMAN_H
+#define BCM2835_CPRMAN_H
+
+#include "hw/sysbus.h"
+
+#define TYPE_BCM2835_CPRMAN "bcm2835-cprman"
+#define BCM2835_CPRMAN(obj) \
+        OBJECT_CHECK(BCM2835CprmanState, (obj), TYPE_BCM2835_CPRMAN)
+
+typedef struct {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    /*< public >*/
+
+    struct {
+        MemoryRegion pm;
+        MemoryRegion cm;
+        MemoryRegion a2w;
+    } iomem;
+} BCM2835CprmanState;
+
+#endif
