diff mbox series

[v3,08/15] hw/misc/bcm2835_cprman: add a PLL channel skeleton implementation

Message ID 20201010135759.437903-9-luc@lmichel.fr
State Superseded
Headers show
Series raspi: add the bcm2835 cprman clock manager | expand

Commit Message

Luc Michel Oct. 10, 2020, 1:57 p.m. UTC
PLLs are composed of multiple channels. Each channel outputs one clock
signal. They are modeled as one device taking the PLL generated clock as
input, and outputting a new clock.

A channel shares the CM register with its parent PLL, and has its own
A2W_CTRL register. A write to the CM register will trigger an update of
the PLL and all its channels, while a write to an A2W_CTRL channel
register will update the required channel only.

Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Signed-off-by: Luc Michel <luc@lmichel.fr>
---
 include/hw/misc/bcm2835_cprman.h           |  44 ++++++
 include/hw/misc/bcm2835_cprman_internals.h | 146 +++++++++++++++++++
 hw/misc/bcm2835_cprman.c                   | 155 +++++++++++++++++++--
 3 files changed, 337 insertions(+), 8 deletions(-)

Comments

Philippe Mathieu-Daudé Oct. 10, 2020, 4:05 p.m. UTC | #1
On 10/10/20 3:57 PM, Luc Michel wrote:
> PLLs are composed of multiple channels. Each channel outputs one clock

> signal. They are modeled as one device taking the PLL generated clock as

> input, and outputting a new clock.

> 

> A channel shares the CM register with its parent PLL, and has its own

> A2W_CTRL register. A write to the CM register will trigger an update of

> the PLL and all its channels, while a write to an A2W_CTRL channel

> register will update the required channel only.

> 

> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>

> Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>

> Signed-off-by: Luc Michel <luc@lmichel.fr>

> ---

>   include/hw/misc/bcm2835_cprman.h           |  44 ++++++

>   include/hw/misc/bcm2835_cprman_internals.h | 146 +++++++++++++++++++

>   hw/misc/bcm2835_cprman.c                   | 155 +++++++++++++++++++--

>   3 files changed, 337 insertions(+), 8 deletions(-)

[...]

> diff --git a/include/hw/misc/bcm2835_cprman_internals.h b/include/hw/misc/bcm2835_cprman_internals.h

> index 7aa46c6e18..7409ddb024 100644

> --- a/include/hw/misc/bcm2835_cprman_internals.h

> +++ b/include/hw/misc/bcm2835_cprman_internals.h

> @@ -11,13 +11,16 @@

>   

>   #include "hw/registerfields.h"

>   #include "hw/misc/bcm2835_cprman.h"

>   

>   #define TYPE_CPRMAN_PLL "bcm2835-cprman-pll"

> +#define TYPE_CPRMAN_PLL_CHANNEL "bcm2835-cprman-pll-channel"

>   

>   DECLARE_INSTANCE_CHECKER(CprmanPllState, CPRMAN_PLL,

>                            TYPE_CPRMAN_PLL)

> +DECLARE_INSTANCE_CHECKER(CprmanPllChannelState, CPRMAN_PLL_CHANNEL,

> +                         TYPE_CPRMAN_PLL_CHANNEL)

>   

>   /* Register map */

>   

>   /* PLLs */

>   REG32(CM_PLLA, 0x104)

> @@ -98,10 +101,35 @@ REG32(A2W_PLLA_FRAC, 0x1200)

>   REG32(A2W_PLLC_FRAC, 0x1220)

>   REG32(A2W_PLLD_FRAC, 0x1240)

>   REG32(A2W_PLLH_FRAC, 0x1260)

>   REG32(A2W_PLLB_FRAC, 0x12e0)

>   

> +/* PLL channels */

> +REG32(A2W_PLLA_DSI0, 0x1300)

> +    FIELD(A2W_PLLx_CHANNELy, DIV, 0, 8)

> +    FIELD(A2W_PLLx_CHANNELy, DISABLE, 8, 1)

> +REG32(A2W_PLLA_CORE, 0x1400)

> +REG32(A2W_PLLA_PER, 0x1500)

> +REG32(A2W_PLLA_CCP2, 0x1600)

> +

> +REG32(A2W_PLLC_CORE2, 0x1320)

> +REG32(A2W_PLLC_CORE1, 0x1420)

> +REG32(A2W_PLLC_PER, 0x1520)

> +REG32(A2W_PLLC_CORE0, 0x1620)

> +

> +REG32(A2W_PLLD_DSI0, 0x1340)

> +REG32(A2W_PLLD_CORE, 0x1440)

> +REG32(A2W_PLLD_PER, 0x1540)

> +REG32(A2W_PLLD_DSI1, 0x1640)

> +

> +REG32(A2W_PLLH_AUX, 0x1360)

> +REG32(A2W_PLLH_RCAL, 0x1460)

> +REG32(A2W_PLLH_PIX, 0x1560)

> +REG32(A2W_PLLH_STS, 0x1660)

> +

> +REG32(A2W_PLLB_ARM, 0x13e0)

> +

>   /* misc registers */

>   REG32(CM_LOCK, 0x114)

>       FIELD(CM_LOCK, FLOCKH, 12, 1)

>       FIELD(CM_LOCK, FLOCKD, 11, 1)

>       FIELD(CM_LOCK, FLOCKC, 10, 1)

> @@ -171,6 +199,124 @@ static inline void set_pll_init_info(BCM2835CprmanState *s,

>       pll->reg_a2w_ana = &s->regs[PLL_INIT_INFO[id].a2w_ana_offset];

>       pll->prediv_mask = PLL_INIT_INFO[id].prediv_mask;

>       pll->reg_a2w_frac = &s->regs[PLL_INIT_INFO[id].a2w_frac_offset];

>   }

>   

> +

> +/* PLL channel init info */

> +typedef struct PLLChannelInitInfo {

> +    const char *name;

> +    CprmanPll parent;

> +    size_t cm_offset;

> +    uint32_t cm_hold_mask;

> +    uint32_t cm_load_mask;

> +    size_t a2w_ctrl_offset;

> +    unsigned int fixed_divider;

> +} PLLChannelInitInfo;

> +

> +#define FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_)            \

> +    .parent = CPRMAN_ ## pll_,                                       \

> +    .cm_offset = R_CM_ ## pll_,                                      \

> +    .cm_load_mask = R_CM_ ## pll_ ## _ ## LOAD ## channel_ ## _MASK, \

> +    .a2w_ctrl_offset = R_A2W_ ## pll_ ## _ ## channel_

> +

> +#define FILL_PLL_CHANNEL_INIT_INFO(pll_, channel_)                   \

> +    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),               \

> +    .cm_hold_mask = R_CM_ ## pll_ ## _ ## HOLD ## channel_ ## _MASK, \

> +    .fixed_divider = 1

> +

> +#define FILL_PLL_CHANNEL_INIT_INFO_nohold(pll_, channel_) \

> +    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),    \

> +    .cm_hold_mask = 0

> +

> +static PLLChannelInitInfo PLL_CHANNEL_INIT_INFO[] = {


Hmm I missed this static definition in an header.
As it is local and only include once, I'd prefer match the
rest of the source tree style and name it .c.inc:

-- >8 --
diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c
index 7e415a017c9..9d4c0ee6c73 100644
--- a/hw/misc/bcm2835_cprman.c
+++ b/hw/misc/bcm2835_cprman.c
@@ -48,7 +48,7 @@
  #include "migration/vmstate.h"
  #include "hw/qdev-properties.h"
  #include "hw/misc/bcm2835_cprman.h"
-#include "hw/misc/bcm2835_cprman_internals.h"
+#include "bcm2835_cprman_internals.c.inc"
  #include "trace.h"

  /* PLL */
diff --git a/include/hw/misc/bcm2835_cprman_internals.h 
b/hw/misc/bcm2835_cprman_internals.c.inc
similarity index 100%
rename from include/hw/misc/bcm2835_cprman_internals.h
rename to hw/misc/bcm2835_cprman_internals.c.inc
---
> +    [CPRMAN_PLLA_CHANNEL_DSI0] = {

> +        .name = "plla-dsi0",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLA, DSI0),

> +    },

> +    [CPRMAN_PLLA_CHANNEL_CORE] = {

> +        .name = "plla-core",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLA, CORE),

> +    },

> +    [CPRMAN_PLLA_CHANNEL_PER] = {

> +        .name = "plla-per",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLA, PER),

> +    },

> +    [CPRMAN_PLLA_CHANNEL_CCP2] = {

> +        .name = "plla-ccp2",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLA, CCP2),

> +    },

> +

> +    [CPRMAN_PLLC_CHANNEL_CORE2] = {

> +        .name = "pllc-core2",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE2),

> +    },

> +    [CPRMAN_PLLC_CHANNEL_CORE1] = {

> +        .name = "pllc-core1",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE1),

> +    },

> +    [CPRMAN_PLLC_CHANNEL_PER] = {

> +        .name = "pllc-per",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLC, PER),

> +    },

> +    [CPRMAN_PLLC_CHANNEL_CORE0] = {

> +        .name = "pllc-core0",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE0),

> +    },

> +

> +    [CPRMAN_PLLD_CHANNEL_DSI0] = {

> +        .name = "plld-dsi0",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLD, DSI0),

> +    },

> +    [CPRMAN_PLLD_CHANNEL_CORE] = {

> +        .name = "plld-core",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLD, CORE),

> +    },

> +    [CPRMAN_PLLD_CHANNEL_PER] = {

> +        .name = "plld-per",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLD, PER),

> +    },

> +    [CPRMAN_PLLD_CHANNEL_DSI1] = {

> +        .name = "plld-dsi1",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLD, DSI1),

> +    },

> +

> +    [CPRMAN_PLLH_CHANNEL_AUX] = {

> +        .name = "pllh-aux",

> +        .fixed_divider = 1,

> +        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, AUX),

> +    },

> +    [CPRMAN_PLLH_CHANNEL_RCAL] = {

> +        .name = "pllh-rcal",

> +        .fixed_divider = 10,

> +        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, RCAL),

> +    },

> +    [CPRMAN_PLLH_CHANNEL_PIX] = {

> +        .name = "pllh-pix",

> +        .fixed_divider = 10,

> +        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, PIX),

> +    },

> +

> +    [CPRMAN_PLLB_CHANNEL_ARM] = {

> +        .name = "pllb-arm",

> +        FILL_PLL_CHANNEL_INIT_INFO(PLLB, ARM),

> +    },

> +};

> +

> +#undef FILL_PLL_CHANNEL_INIT_INFO_nohold

> +#undef FILL_PLL_CHANNEL_INIT_INFO

> +#undef FILL_PLL_CHANNEL_INIT_INFO_common

> +

> +static inline void set_pll_channel_init_info(BCM2835CprmanState *s,

> +                                             CprmanPllChannelState *channel,

> +                                             CprmanPllChannel id)

> +{

> +    channel->id = id;

> +    channel->parent = PLL_CHANNEL_INIT_INFO[id].parent;

> +    channel->reg_cm = &s->regs[PLL_CHANNEL_INIT_INFO[id].cm_offset];

> +    channel->hold_mask = PLL_CHANNEL_INIT_INFO[id].cm_hold_mask;

> +    channel->load_mask = PLL_CHANNEL_INIT_INFO[id].cm_load_mask;

> +    channel->reg_a2w_ctrl = &s->regs[PLL_CHANNEL_INIT_INFO[id].a2w_ctrl_offset];

> +    channel->fixed_divider = PLL_CHANNEL_INIT_INFO[id].fixed_divider;

> +}

> +

>   #endif

[...]
Philippe Mathieu-Daudé Oct. 17, 2020, 10 a.m. UTC | #2
On 10/10/20 6:05 PM, Philippe Mathieu-Daudé wrote:
> On 10/10/20 3:57 PM, Luc Michel wrote:

>> PLLs are composed of multiple channels. Each channel outputs one clock

>> signal. They are modeled as one device taking the PLL generated clock as

>> input, and outputting a new clock.

>>

>> A channel shares the CM register with its parent PLL, and has its own

>> A2W_CTRL register. A write to the CM register will trigger an update of

>> the PLL and all its channels, while a write to an A2W_CTRL channel

>> register will update the required channel only.

>>

>> Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>

>> Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>

>> Signed-off-by: Luc Michel <luc@lmichel.fr>

>> ---

>>   include/hw/misc/bcm2835_cprman.h           |  44 ++++++

>>   include/hw/misc/bcm2835_cprman_internals.h | 146 +++++++++++++++++++

>>   hw/misc/bcm2835_cprman.c                   | 155 +++++++++++++++++++--

>>   3 files changed, 337 insertions(+), 8 deletions(-)

[...]

>> +#define FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_)            \

>> +    .parent = CPRMAN_ ## pll_,                                       \

>> +    .cm_offset = R_CM_ ## pll_,                                      \

>> +    .cm_load_mask = R_CM_ ## pll_ ## _ ## LOAD ## channel_ ## _MASK, \

>> +    .a2w_ctrl_offset = R_A2W_ ## pll_ ## _ ## channel_

>> +

>> +#define FILL_PLL_CHANNEL_INIT_INFO(pll_, channel_)                   \

>> +    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),               \

>> +    .cm_hold_mask = R_CM_ ## pll_ ## _ ## HOLD ## channel_ ## _MASK, \

>> +    .fixed_divider = 1

>> +

>> +#define FILL_PLL_CHANNEL_INIT_INFO_nohold(pll_, channel_) \

>> +    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),    \

>> +    .cm_hold_mask = 0

>> +

>> +static PLLChannelInitInfo PLL_CHANNEL_INIT_INFO[] = {

> 

> Hmm I missed this static definition in an header.

> As it is local and only include once, I'd prefer match the

> rest of the source tree style and name it .c.inc:

> 

> -- >8 --

> diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c

> index 7e415a017c9..9d4c0ee6c73 100644

> --- a/hw/misc/bcm2835_cprman.c

> +++ b/hw/misc/bcm2835_cprman.c

> @@ -48,7 +48,7 @@

>   #include "migration/vmstate.h"

>   #include "hw/qdev-properties.h"

>   #include "hw/misc/bcm2835_cprman.h"

> -#include "hw/misc/bcm2835_cprman_internals.h"

> +#include "bcm2835_cprman_internals.c.inc"

>   #include "trace.h"

> 

>   /* PLL */

> diff --git a/include/hw/misc/bcm2835_cprman_internals.h 

> b/hw/misc/bcm2835_cprman_internals.c.inc

> similarity index 100%

> rename from include/hw/misc/bcm2835_cprman_internals.h

> rename to hw/misc/bcm2835_cprman_internals.c.inc

> ---


This can be applied directly by Peter, or can
be cleaned later. This is not a blocker to get
this series merged.
diff mbox series

Patch

diff --git a/include/hw/misc/bcm2835_cprman.h b/include/hw/misc/bcm2835_cprman.h
index 5c442e6ff9..e1a1b33f8b 100644
--- a/include/hw/misc/bcm2835_cprman.h
+++ b/include/hw/misc/bcm2835_cprman.h
@@ -29,10 +29,35 @@  typedef enum CprmanPll {
     CPRMAN_PLLB,
 
     CPRMAN_NUM_PLL
 } CprmanPll;
 
+typedef enum CprmanPllChannel {
+    CPRMAN_PLLA_CHANNEL_DSI0 = 0,
+    CPRMAN_PLLA_CHANNEL_CORE,
+    CPRMAN_PLLA_CHANNEL_PER,
+    CPRMAN_PLLA_CHANNEL_CCP2,
+
+    CPRMAN_PLLC_CHANNEL_CORE2,
+    CPRMAN_PLLC_CHANNEL_CORE1,
+    CPRMAN_PLLC_CHANNEL_PER,
+    CPRMAN_PLLC_CHANNEL_CORE0,
+
+    CPRMAN_PLLD_CHANNEL_DSI0,
+    CPRMAN_PLLD_CHANNEL_CORE,
+    CPRMAN_PLLD_CHANNEL_PER,
+    CPRMAN_PLLD_CHANNEL_DSI1,
+
+    CPRMAN_PLLH_CHANNEL_AUX,
+    CPRMAN_PLLH_CHANNEL_RCAL,
+    CPRMAN_PLLH_CHANNEL_PIX,
+
+    CPRMAN_PLLB_CHANNEL_ARM,
+
+    CPRMAN_NUM_PLL_CHANNEL,
+} CprmanPllChannel;
+
 typedef struct CprmanPllState {
     /*< private >*/
     DeviceState parent_obj;
 
     /*< public >*/
@@ -46,18 +71,37 @@  typedef struct CprmanPllState {
 
     Clock *xosc_in;
     Clock *out;
 } CprmanPllState;
 
+typedef struct CprmanPllChannelState {
+    /*< private >*/
+    DeviceState parent_obj;
+
+    /*< public >*/
+    CprmanPllChannel id;
+    CprmanPll parent;
+
+    uint32_t *reg_cm;
+    uint32_t hold_mask;
+    uint32_t load_mask;
+    uint32_t *reg_a2w_ctrl;
+    int fixed_divider;
+
+    Clock *pll_in;
+    Clock *out;
+} CprmanPllChannelState;
+
 struct BCM2835CprmanState {
     /*< private >*/
     SysBusDevice parent_obj;
 
     /*< public >*/
     MemoryRegion iomem;
 
     CprmanPllState plls[CPRMAN_NUM_PLL];
+    CprmanPllChannelState channels[CPRMAN_NUM_PLL_CHANNEL];
 
     uint32_t regs[CPRMAN_NUM_REGS];
     uint32_t xosc_freq;
 
     Clock *xosc;
diff --git a/include/hw/misc/bcm2835_cprman_internals.h b/include/hw/misc/bcm2835_cprman_internals.h
index 7aa46c6e18..7409ddb024 100644
--- a/include/hw/misc/bcm2835_cprman_internals.h
+++ b/include/hw/misc/bcm2835_cprman_internals.h
@@ -11,13 +11,16 @@ 
 
 #include "hw/registerfields.h"
 #include "hw/misc/bcm2835_cprman.h"
 
 #define TYPE_CPRMAN_PLL "bcm2835-cprman-pll"
+#define TYPE_CPRMAN_PLL_CHANNEL "bcm2835-cprman-pll-channel"
 
 DECLARE_INSTANCE_CHECKER(CprmanPllState, CPRMAN_PLL,
                          TYPE_CPRMAN_PLL)
+DECLARE_INSTANCE_CHECKER(CprmanPllChannelState, CPRMAN_PLL_CHANNEL,
+                         TYPE_CPRMAN_PLL_CHANNEL)
 
 /* Register map */
 
 /* PLLs */
 REG32(CM_PLLA, 0x104)
@@ -98,10 +101,35 @@  REG32(A2W_PLLA_FRAC, 0x1200)
 REG32(A2W_PLLC_FRAC, 0x1220)
 REG32(A2W_PLLD_FRAC, 0x1240)
 REG32(A2W_PLLH_FRAC, 0x1260)
 REG32(A2W_PLLB_FRAC, 0x12e0)
 
+/* PLL channels */
+REG32(A2W_PLLA_DSI0, 0x1300)
+    FIELD(A2W_PLLx_CHANNELy, DIV, 0, 8)
+    FIELD(A2W_PLLx_CHANNELy, DISABLE, 8, 1)
+REG32(A2W_PLLA_CORE, 0x1400)
+REG32(A2W_PLLA_PER, 0x1500)
+REG32(A2W_PLLA_CCP2, 0x1600)
+
+REG32(A2W_PLLC_CORE2, 0x1320)
+REG32(A2W_PLLC_CORE1, 0x1420)
+REG32(A2W_PLLC_PER, 0x1520)
+REG32(A2W_PLLC_CORE0, 0x1620)
+
+REG32(A2W_PLLD_DSI0, 0x1340)
+REG32(A2W_PLLD_CORE, 0x1440)
+REG32(A2W_PLLD_PER, 0x1540)
+REG32(A2W_PLLD_DSI1, 0x1640)
+
+REG32(A2W_PLLH_AUX, 0x1360)
+REG32(A2W_PLLH_RCAL, 0x1460)
+REG32(A2W_PLLH_PIX, 0x1560)
+REG32(A2W_PLLH_STS, 0x1660)
+
+REG32(A2W_PLLB_ARM, 0x13e0)
+
 /* misc registers */
 REG32(CM_LOCK, 0x114)
     FIELD(CM_LOCK, FLOCKH, 12, 1)
     FIELD(CM_LOCK, FLOCKD, 11, 1)
     FIELD(CM_LOCK, FLOCKC, 10, 1)
@@ -171,6 +199,124 @@  static inline void set_pll_init_info(BCM2835CprmanState *s,
     pll->reg_a2w_ana = &s->regs[PLL_INIT_INFO[id].a2w_ana_offset];
     pll->prediv_mask = PLL_INIT_INFO[id].prediv_mask;
     pll->reg_a2w_frac = &s->regs[PLL_INIT_INFO[id].a2w_frac_offset];
 }
 
+
+/* PLL channel init info */
+typedef struct PLLChannelInitInfo {
+    const char *name;
+    CprmanPll parent;
+    size_t cm_offset;
+    uint32_t cm_hold_mask;
+    uint32_t cm_load_mask;
+    size_t a2w_ctrl_offset;
+    unsigned int fixed_divider;
+} PLLChannelInitInfo;
+
+#define FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_)            \
+    .parent = CPRMAN_ ## pll_,                                       \
+    .cm_offset = R_CM_ ## pll_,                                      \
+    .cm_load_mask = R_CM_ ## pll_ ## _ ## LOAD ## channel_ ## _MASK, \
+    .a2w_ctrl_offset = R_A2W_ ## pll_ ## _ ## channel_
+
+#define FILL_PLL_CHANNEL_INIT_INFO(pll_, channel_)                   \
+    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),               \
+    .cm_hold_mask = R_CM_ ## pll_ ## _ ## HOLD ## channel_ ## _MASK, \
+    .fixed_divider = 1
+
+#define FILL_PLL_CHANNEL_INIT_INFO_nohold(pll_, channel_) \
+    FILL_PLL_CHANNEL_INIT_INFO_common(pll_, channel_),    \
+    .cm_hold_mask = 0
+
+static PLLChannelInitInfo PLL_CHANNEL_INIT_INFO[] = {
+    [CPRMAN_PLLA_CHANNEL_DSI0] = {
+        .name = "plla-dsi0",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLA, DSI0),
+    },
+    [CPRMAN_PLLA_CHANNEL_CORE] = {
+        .name = "plla-core",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLA, CORE),
+    },
+    [CPRMAN_PLLA_CHANNEL_PER] = {
+        .name = "plla-per",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLA, PER),
+    },
+    [CPRMAN_PLLA_CHANNEL_CCP2] = {
+        .name = "plla-ccp2",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLA, CCP2),
+    },
+
+    [CPRMAN_PLLC_CHANNEL_CORE2] = {
+        .name = "pllc-core2",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE2),
+    },
+    [CPRMAN_PLLC_CHANNEL_CORE1] = {
+        .name = "pllc-core1",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE1),
+    },
+    [CPRMAN_PLLC_CHANNEL_PER] = {
+        .name = "pllc-per",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLC, PER),
+    },
+    [CPRMAN_PLLC_CHANNEL_CORE0] = {
+        .name = "pllc-core0",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLC, CORE0),
+    },
+
+    [CPRMAN_PLLD_CHANNEL_DSI0] = {
+        .name = "plld-dsi0",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLD, DSI0),
+    },
+    [CPRMAN_PLLD_CHANNEL_CORE] = {
+        .name = "plld-core",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLD, CORE),
+    },
+    [CPRMAN_PLLD_CHANNEL_PER] = {
+        .name = "plld-per",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLD, PER),
+    },
+    [CPRMAN_PLLD_CHANNEL_DSI1] = {
+        .name = "plld-dsi1",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLD, DSI1),
+    },
+
+    [CPRMAN_PLLH_CHANNEL_AUX] = {
+        .name = "pllh-aux",
+        .fixed_divider = 1,
+        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, AUX),
+    },
+    [CPRMAN_PLLH_CHANNEL_RCAL] = {
+        .name = "pllh-rcal",
+        .fixed_divider = 10,
+        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, RCAL),
+    },
+    [CPRMAN_PLLH_CHANNEL_PIX] = {
+        .name = "pllh-pix",
+        .fixed_divider = 10,
+        FILL_PLL_CHANNEL_INIT_INFO_nohold(PLLH, PIX),
+    },
+
+    [CPRMAN_PLLB_CHANNEL_ARM] = {
+        .name = "pllb-arm",
+        FILL_PLL_CHANNEL_INIT_INFO(PLLB, ARM),
+    },
+};
+
+#undef FILL_PLL_CHANNEL_INIT_INFO_nohold
+#undef FILL_PLL_CHANNEL_INIT_INFO
+#undef FILL_PLL_CHANNEL_INIT_INFO_common
+
+static inline void set_pll_channel_init_info(BCM2835CprmanState *s,
+                                             CprmanPllChannelState *channel,
+                                             CprmanPllChannel id)
+{
+    channel->id = id;
+    channel->parent = PLL_CHANNEL_INIT_INFO[id].parent;
+    channel->reg_cm = &s->regs[PLL_CHANNEL_INIT_INFO[id].cm_offset];
+    channel->hold_mask = PLL_CHANNEL_INIT_INFO[id].cm_hold_mask;
+    channel->load_mask = PLL_CHANNEL_INIT_INFO[id].cm_load_mask;
+    channel->reg_a2w_ctrl = &s->regs[PLL_CHANNEL_INIT_INFO[id].a2w_ctrl_offset];
+    channel->fixed_divider = PLL_CHANNEL_INIT_INFO[id].fixed_divider;
+}
+
 #endif
diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c
index 144bcc289d..12fa78181b 100644
--- a/hw/misc/bcm2835_cprman.c
+++ b/hw/misc/bcm2835_cprman.c
@@ -130,10 +130,73 @@  static const TypeInfo cprman_pll_info = {
     .class_init = pll_class_init,
     .instance_init = pll_init,
 };
 
 
+/* PLL channel */
+
+static void pll_channel_update(CprmanPllChannelState *channel)
+{
+    clock_update(channel->out, 0);
+}
+
+/* Update a PLL and all its channels */
+static void pll_update_all_channels(BCM2835CprmanState *s,
+                                    CprmanPllState *pll)
+{
+    size_t i;
+
+    pll_update(pll);
+
+    for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+        CprmanPllChannelState *channel = &s->channels[i];
+        if (channel->parent == pll->id) {
+            pll_channel_update(channel);
+        }
+    }
+}
+
+static void pll_channel_pll_in_update(void *opaque)
+{
+    pll_channel_update(CPRMAN_PLL_CHANNEL(opaque));
+}
+
+static void pll_channel_init(Object *obj)
+{
+    CprmanPllChannelState *s = CPRMAN_PLL_CHANNEL(obj);
+
+    s->pll_in = qdev_init_clock_in(DEVICE(s), "pll-in",
+                                   pll_channel_pll_in_update, s);
+    s->out = qdev_init_clock_out(DEVICE(s), "out");
+}
+
+static const VMStateDescription pll_channel_vmstate = {
+    .name = TYPE_CPRMAN_PLL_CHANNEL,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_CLOCK(pll_in, CprmanPllChannelState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
+static void pll_channel_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &pll_channel_vmstate;
+}
+
+static const TypeInfo cprman_pll_channel_info = {
+    .name = TYPE_CPRMAN_PLL_CHANNEL,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(CprmanPllChannelState),
+    .class_init = pll_channel_class_init,
+    .instance_init = pll_channel_init,
+};
+
+
 /* CPRMAN "top level" model */
 
 static uint32_t get_cm_lock(const BCM2835CprmanState *s)
 {
     static const int CM_LOCK_MAPPING[CPRMAN_NUM_PLL] = {
@@ -172,12 +235,36 @@  static uint64_t cprman_read(void *opaque, hwaddr offset,
 
     trace_bcm2835_cprman_read(offset, r);
     return r;
 }
 
-#define CASE_PLL_REGS(pll_)       \
-    case R_CM_ ## pll_:           \
+static inline void update_pll_and_channels_from_cm(BCM2835CprmanState *s,
+                                                   size_t idx)
+{
+    size_t i;
+
+    for (i = 0; i < CPRMAN_NUM_PLL; i++) {
+        if (PLL_INIT_INFO[i].cm_offset == idx) {
+            pll_update_all_channels(s, &s->plls[i]);
+            return;
+        }
+    }
+}
+
+static inline void update_channel_from_a2w(BCM2835CprmanState *s, size_t idx)
+{
+    size_t i;
+
+    for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+        if (PLL_CHANNEL_INIT_INFO[i].a2w_ctrl_offset == idx) {
+            pll_channel_update(&s->channels[i]);
+            return;
+        }
+    }
+}
+
+#define CASE_PLL_A2W_REGS(pll_) \
     case R_A2W_ ## pll_ ## _CTRL: \
     case R_A2W_ ## pll_ ## _ANA0: \
     case R_A2W_ ## pll_ ## _ANA1: \
     case R_A2W_ ## pll_ ## _ANA2: \
     case R_A2W_ ## pll_ ## _ANA3: \
@@ -198,33 +285,61 @@  static void cprman_write(void *opaque, hwaddr offset,
 
     trace_bcm2835_cprman_write(offset, value);
     s->regs[idx] = value;
 
     switch (idx) {
-    CASE_PLL_REGS(PLLA) :
+    case R_CM_PLLA ... R_CM_PLLH:
+    case R_CM_PLLB:
+        /*
+         * A given CM_PLLx register is shared by both the PLL and the channels
+         * of this PLL.
+         */
+        update_pll_and_channels_from_cm(s, idx);
+        break;
+
+    CASE_PLL_A2W_REGS(PLLA) :
         pll_update(&s->plls[CPRMAN_PLLA]);
         break;
 
-    CASE_PLL_REGS(PLLC) :
+    CASE_PLL_A2W_REGS(PLLC) :
         pll_update(&s->plls[CPRMAN_PLLC]);
         break;
 
-    CASE_PLL_REGS(PLLD) :
+    CASE_PLL_A2W_REGS(PLLD) :
         pll_update(&s->plls[CPRMAN_PLLD]);
         break;
 
-    CASE_PLL_REGS(PLLH) :
+    CASE_PLL_A2W_REGS(PLLH) :
         pll_update(&s->plls[CPRMAN_PLLH]);
         break;
 
-    CASE_PLL_REGS(PLLB) :
+    CASE_PLL_A2W_REGS(PLLB) :
         pll_update(&s->plls[CPRMAN_PLLB]);
         break;
+
+    case R_A2W_PLLA_DSI0:
+    case R_A2W_PLLA_CORE:
+    case R_A2W_PLLA_PER:
+    case R_A2W_PLLA_CCP2:
+    case R_A2W_PLLC_CORE2:
+    case R_A2W_PLLC_CORE1:
+    case R_A2W_PLLC_PER:
+    case R_A2W_PLLC_CORE0:
+    case R_A2W_PLLD_DSI0:
+    case R_A2W_PLLD_CORE:
+    case R_A2W_PLLD_PER:
+    case R_A2W_PLLD_DSI1:
+    case R_A2W_PLLH_AUX:
+    case R_A2W_PLLH_RCAL:
+    case R_A2W_PLLH_PIX:
+    case R_A2W_PLLB_ARM:
+        update_channel_from_a2w(s, idx);
+        break;
     }
 }
 
-#undef CASE_PLL_REGS
+#undef CASE_PLL_A2W_REGS
 
 static const MemoryRegionOps cprman_ops = {
     .read = cprman_read,
     .write = cprman_write,
     .endianness = DEVICE_LITTLE_ENDIAN,
@@ -252,10 +367,14 @@  static void cprman_reset(DeviceState *dev)
 
     for (i = 0; i < CPRMAN_NUM_PLL; i++) {
         device_cold_reset(DEVICE(&s->plls[i]));
     }
 
+    for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+        device_cold_reset(DEVICE(&s->channels[i]));
+    }
+
     clock_update_hz(s->xosc, s->xosc_freq);
 }
 
 static void cprman_init(Object *obj)
 {
@@ -266,10 +385,17 @@  static void cprman_init(Object *obj)
         object_initialize_child(obj, PLL_INIT_INFO[i].name,
                                 &s->plls[i], TYPE_CPRMAN_PLL);
         set_pll_init_info(s, &s->plls[i], i);
     }
 
+    for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+        object_initialize_child(obj, PLL_CHANNEL_INIT_INFO[i].name,
+                                &s->channels[i],
+                                TYPE_CPRMAN_PLL_CHANNEL);
+        set_pll_channel_init_info(s, &s->channels[i], i);
+    }
+
     s->xosc = clock_new(obj, "xosc");
 
     memory_region_init_io(&s->iomem, obj, &cprman_ops,
                           s, "bcm2835-cprman", 0x2000);
     sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
@@ -287,10 +413,22 @@  static void cprman_realize(DeviceState *dev, Error **errp)
 
         if (!qdev_realize(DEVICE(pll), NULL, errp)) {
             return;
         }
     }
+
+    for (i = 0; i < CPRMAN_NUM_PLL_CHANNEL; i++) {
+        CprmanPllChannelState *channel = &s->channels[i];
+        CprmanPll parent = PLL_CHANNEL_INIT_INFO[i].parent;
+        Clock *parent_clk = s->plls[parent].out;
+
+        clock_set_source(channel->pll_in, parent_clk);
+
+        if (!qdev_realize(DEVICE(channel), NULL, errp)) {
+            return;
+        }
+    }
 }
 
 static const VMStateDescription cprman_vmstate = {
     .name = TYPE_BCM2835_CPRMAN,
     .version_id = 1,
@@ -326,8 +464,9 @@  static const TypeInfo cprman_info = {
 
 static void cprman_register_types(void)
 {
     type_register_static(&cprman_info);
     type_register_static(&cprman_pll_info);
+    type_register_static(&cprman_pll_channel_info);
 }
 
 type_init(cprman_register_types);