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.
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.h b/include/hw/misc/bcm2835_cprman.h
index f186ab0104..aaf15fb20c 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 22a2500ab0..8a5b9aae67 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 ba82522eb1..2c70a3f317 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[] = {
@@ -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,
@@ -244,10 +359,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 Clock *init_internal_clock(BCM2835CprmanState *s,
const char *name)
@@ -274,10 +393,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 = init_internal_clock(s, "xosc");
memory_region_init_io(&s->iomem, obj, &cprman_ops,
s, "bcm2835-cprman", 0x2000);
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
@@ -295,10 +421,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,
@@ -334,8 +472,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);
--
2.28.0
On 9/25/20 12:17 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
CM.
> a2w_ctrl register. A write to the cm register will trigger an update of
A2W_ctrl? CM.
> the PLL and all its channels, while a write to an a2w_ctrl channel
> register will update the required channel only.
>
> 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.h b/include/hw/misc/bcm2835_cprman.h
> index f186ab0104..aaf15fb20c 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 22a2500ab0..8a5b9aae67 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 ba82522eb1..2c70a3f317 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[] = {
> @@ -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,
> @@ -244,10 +359,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 Clock *init_internal_clock(BCM2835CprmanState *s,
> const char *name)
> @@ -274,10 +393,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 = init_internal_clock(s, "xosc");
>
> memory_region_init_io(&s->iomem, obj, &cprman_ops,
> s, "bcm2835-cprman", 0x2000);
> sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
> @@ -295,10 +421,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,
> @@ -334,8 +472,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);
>
Chapeau!
Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
© 2016 - 2026 Red Hat, Inc.