hw/misc/bcm2835_cprman: add a PLL skeleton implementation

There are 5 PLLs in the CPRMAN, namely PLL A, C, D, H and B. All of them
take the xosc clock as input and produce a new clock.

This commit adds a skeleton implementation for the PLLs as sub-devices
of the CPRMAN. The PLLs are instantiated and connected internally to the
main oscillator.

Each PLL has 6 registers : CM, A2W_CTRL, A2W_ANA[0,1,2,3], A2W_FRAC. A
write to any of them triggers a call to the (not yet implemented)
pll_update function.

If the main oscillator changes frequency, an update is also triggered.

Reviewed-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Tested-by: Philippe Mathieu-Daudé <f4bug@amsat.org>
Signed-off-by: Luc Michel <luc@lmichel.fr>
Tested-by: Guenter Roeck <linux@roeck-us.net>
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Luc Michel 2020-10-10 15:57:50 +02:00 committed by Peter Maydell
parent fc14176ba2
commit 1e986e25d0
3 changed files with 281 additions and 0 deletions

View File

@ -48,6 +48,52 @@
#include "hw/misc/bcm2835_cprman_internals.h"
#include "trace.h"
/* PLL */
static void pll_update(CprmanPllState *pll)
{
clock_update(pll->out, 0);
}
static void pll_xosc_update(void *opaque)
{
pll_update(CPRMAN_PLL(opaque));
}
static void pll_init(Object *obj)
{
CprmanPllState *s = CPRMAN_PLL(obj);
s->xosc_in = qdev_init_clock_in(DEVICE(s), "xosc-in", pll_xosc_update, s);
s->out = qdev_init_clock_out(DEVICE(s), "out");
}
static const VMStateDescription pll_vmstate = {
.name = TYPE_CPRMAN_PLL,
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_CLOCK(xosc_in, CprmanPllState),
VMSTATE_END_OF_LIST()
}
};
static void pll_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->vmsd = &pll_vmstate;
}
static const TypeInfo cprman_pll_info = {
.name = TYPE_CPRMAN_PLL,
.parent = TYPE_DEVICE,
.instance_size = sizeof(CprmanPllState),
.class_init = pll_class_init,
.instance_init = pll_init,
};
/* CPRMAN "top level" model */
static uint64_t cprman_read(void *opaque, hwaddr offset,
@ -66,6 +112,15 @@ static uint64_t cprman_read(void *opaque, hwaddr offset,
return r;
}
#define CASE_PLL_REGS(pll_) \
case R_CM_ ## 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: \
case R_A2W_ ## pll_ ## _FRAC
static void cprman_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
@ -82,8 +137,31 @@ static void cprman_write(void *opaque, hwaddr offset,
trace_bcm2835_cprman_write(offset, value);
s->regs[idx] = value;
switch (idx) {
CASE_PLL_REGS(PLLA) :
pll_update(&s->plls[CPRMAN_PLLA]);
break;
CASE_PLL_REGS(PLLC) :
pll_update(&s->plls[CPRMAN_PLLC]);
break;
CASE_PLL_REGS(PLLD) :
pll_update(&s->plls[CPRMAN_PLLD]);
break;
CASE_PLL_REGS(PLLH) :
pll_update(&s->plls[CPRMAN_PLLH]);
break;
CASE_PLL_REGS(PLLB) :
pll_update(&s->plls[CPRMAN_PLLB]);
break;
}
}
#undef CASE_PLL_REGS
static const MemoryRegionOps cprman_ops = {
.read = cprman_read,
.write = cprman_write,
@ -106,15 +184,27 @@ static const MemoryRegionOps cprman_ops = {
static void cprman_reset(DeviceState *dev)
{
BCM2835CprmanState *s = CPRMAN(dev);
size_t i;
memset(s->regs, 0, sizeof(s->regs));
for (i = 0; i < CPRMAN_NUM_PLL; i++) {
device_cold_reset(DEVICE(&s->plls[i]));
}
clock_update_hz(s->xosc, s->xosc_freq);
}
static void cprman_init(Object *obj)
{
BCM2835CprmanState *s = CPRMAN(obj);
size_t i;
for (i = 0; i < CPRMAN_NUM_PLL; i++) {
object_initialize_child(obj, PLL_INIT_INFO[i].name,
&s->plls[i], TYPE_CPRMAN_PLL);
set_pll_init_info(s, &s->plls[i], i);
}
s->xosc = clock_new(obj, "xosc");
@ -123,6 +213,22 @@ static void cprman_init(Object *obj)
sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
}
static void cprman_realize(DeviceState *dev, Error **errp)
{
BCM2835CprmanState *s = CPRMAN(dev);
size_t i;
for (i = 0; i < CPRMAN_NUM_PLL; i++) {
CprmanPllState *pll = &s->plls[i];
clock_set_source(pll->xosc_in, s->xosc);
if (!qdev_realize(DEVICE(pll), NULL, errp)) {
return;
}
}
}
static const VMStateDescription cprman_vmstate = {
.name = TYPE_BCM2835_CPRMAN,
.version_id = 1,
@ -142,6 +248,7 @@ static void cprman_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = cprman_realize;
dc->reset = cprman_reset;
dc->vmsd = &cprman_vmstate;
device_class_set_props(dc, cprman_properties);
@ -158,6 +265,7 @@ static const TypeInfo cprman_info = {
static void cprman_register_types(void)
{
type_register_static(&cprman_info);
type_register_static(&cprman_pll_info);
}
type_init(cprman_register_types);

View File

@ -21,6 +21,33 @@ DECLARE_INSTANCE_CHECKER(BCM2835CprmanState, CPRMAN,
#define CPRMAN_NUM_REGS (0x2000 / sizeof(uint32_t))
typedef enum CprmanPll {
CPRMAN_PLLA = 0,
CPRMAN_PLLC,
CPRMAN_PLLD,
CPRMAN_PLLH,
CPRMAN_PLLB,
CPRMAN_NUM_PLL
} CprmanPll;
typedef struct CprmanPllState {
/*< private >*/
DeviceState parent_obj;
/*< public >*/
CprmanPll id;
uint32_t *reg_cm;
uint32_t *reg_a2w_ctrl;
uint32_t *reg_a2w_ana; /* ANA[0] .. ANA[3] */
uint32_t prediv_mask; /* prediv bit in ana[1] */
uint32_t *reg_a2w_frac;
Clock *xosc_in;
Clock *out;
} CprmanPllState;
struct BCM2835CprmanState {
/*< private >*/
SysBusDevice parent_obj;
@ -28,6 +55,8 @@ struct BCM2835CprmanState {
/*< public >*/
MemoryRegion iomem;
CprmanPllState plls[CPRMAN_NUM_PLL];
uint32_t regs[CPRMAN_NUM_REGS];
uint32_t xosc_freq;

View File

@ -12,8 +12,94 @@
#include "hw/registerfields.h"
#include "hw/misc/bcm2835_cprman.h"
#define TYPE_CPRMAN_PLL "bcm2835-cprman-pll"
DECLARE_INSTANCE_CHECKER(CprmanPllState, CPRMAN_PLL,
TYPE_CPRMAN_PLL)
/* Register map */
/* PLLs */
REG32(CM_PLLA, 0x104)
FIELD(CM_PLLA, LOADDSI0, 0, 1)
FIELD(CM_PLLA, HOLDDSI0, 1, 1)
FIELD(CM_PLLA, LOADCCP2, 2, 1)
FIELD(CM_PLLA, HOLDCCP2, 3, 1)
FIELD(CM_PLLA, LOADCORE, 4, 1)
FIELD(CM_PLLA, HOLDCORE, 5, 1)
FIELD(CM_PLLA, LOADPER, 6, 1)
FIELD(CM_PLLA, HOLDPER, 7, 1)
FIELD(CM_PLLx, ANARST, 8, 1)
REG32(CM_PLLC, 0x108)
FIELD(CM_PLLC, LOADCORE0, 0, 1)
FIELD(CM_PLLC, HOLDCORE0, 1, 1)
FIELD(CM_PLLC, LOADCORE1, 2, 1)
FIELD(CM_PLLC, HOLDCORE1, 3, 1)
FIELD(CM_PLLC, LOADCORE2, 4, 1)
FIELD(CM_PLLC, HOLDCORE2, 5, 1)
FIELD(CM_PLLC, LOADPER, 6, 1)
FIELD(CM_PLLC, HOLDPER, 7, 1)
REG32(CM_PLLD, 0x10c)
FIELD(CM_PLLD, LOADDSI0, 0, 1)
FIELD(CM_PLLD, HOLDDSI0, 1, 1)
FIELD(CM_PLLD, LOADDSI1, 2, 1)
FIELD(CM_PLLD, HOLDDSI1, 3, 1)
FIELD(CM_PLLD, LOADCORE, 4, 1)
FIELD(CM_PLLD, HOLDCORE, 5, 1)
FIELD(CM_PLLD, LOADPER, 6, 1)
FIELD(CM_PLLD, HOLDPER, 7, 1)
REG32(CM_PLLH, 0x110)
FIELD(CM_PLLH, LOADPIX, 0, 1)
FIELD(CM_PLLH, LOADAUX, 1, 1)
FIELD(CM_PLLH, LOADRCAL, 2, 1)
REG32(CM_PLLB, 0x170)
FIELD(CM_PLLB, LOADARM, 0, 1)
FIELD(CM_PLLB, HOLDARM, 1, 1)
REG32(A2W_PLLA_CTRL, 0x1100)
FIELD(A2W_PLLx_CTRL, NDIV, 0, 10)
FIELD(A2W_PLLx_CTRL, PDIV, 12, 3)
FIELD(A2W_PLLx_CTRL, PWRDN, 16, 1)
FIELD(A2W_PLLx_CTRL, PRST_DISABLE, 17, 1)
REG32(A2W_PLLC_CTRL, 0x1120)
REG32(A2W_PLLD_CTRL, 0x1140)
REG32(A2W_PLLH_CTRL, 0x1160)
REG32(A2W_PLLB_CTRL, 0x11e0)
REG32(A2W_PLLA_ANA0, 0x1010)
REG32(A2W_PLLA_ANA1, 0x1014)
FIELD(A2W_PLLx_ANA1, FB_PREDIV, 14, 1)
REG32(A2W_PLLA_ANA2, 0x1018)
REG32(A2W_PLLA_ANA3, 0x101c)
REG32(A2W_PLLC_ANA0, 0x1030)
REG32(A2W_PLLC_ANA1, 0x1034)
REG32(A2W_PLLC_ANA2, 0x1038)
REG32(A2W_PLLC_ANA3, 0x103c)
REG32(A2W_PLLD_ANA0, 0x1050)
REG32(A2W_PLLD_ANA1, 0x1054)
REG32(A2W_PLLD_ANA2, 0x1058)
REG32(A2W_PLLD_ANA3, 0x105c)
REG32(A2W_PLLH_ANA0, 0x1070)
REG32(A2W_PLLH_ANA1, 0x1074)
FIELD(A2W_PLLH_ANA1, FB_PREDIV, 11, 1)
REG32(A2W_PLLH_ANA2, 0x1078)
REG32(A2W_PLLH_ANA3, 0x107c)
REG32(A2W_PLLB_ANA0, 0x10f0)
REG32(A2W_PLLB_ANA1, 0x10f4)
REG32(A2W_PLLB_ANA2, 0x10f8)
REG32(A2W_PLLB_ANA3, 0x10fc)
REG32(A2W_PLLA_FRAC, 0x1200)
FIELD(A2W_PLLx_FRAC, FRAC, 0, 20)
REG32(A2W_PLLC_FRAC, 0x1220)
REG32(A2W_PLLD_FRAC, 0x1240)
REG32(A2W_PLLH_FRAC, 0x1260)
REG32(A2W_PLLB_FRAC, 0x12e0)
/*
* This field is common to all registers. Each register write value must match
* the CPRMAN_PASSWORD magic value in its 8 MSB.
@ -21,4 +107,62 @@
FIELD(CPRMAN, PASSWORD, 24, 8)
#define CPRMAN_PASSWORD 0x5a
/* PLL init info */
typedef struct PLLInitInfo {
const char *name;
size_t cm_offset;
size_t a2w_ctrl_offset;
size_t a2w_ana_offset;
uint32_t prediv_mask; /* Prediv bit in ana[1] */
size_t a2w_frac_offset;
} PLLInitInfo;
#define FILL_PLL_INIT_INFO(pll_) \
.cm_offset = R_CM_ ## pll_, \
.a2w_ctrl_offset = R_A2W_ ## pll_ ## _CTRL, \
.a2w_ana_offset = R_A2W_ ## pll_ ## _ANA0, \
.a2w_frac_offset = R_A2W_ ## pll_ ## _FRAC
static const PLLInitInfo PLL_INIT_INFO[] = {
[CPRMAN_PLLA] = {
.name = "plla",
.prediv_mask = R_A2W_PLLx_ANA1_FB_PREDIV_MASK,
FILL_PLL_INIT_INFO(PLLA),
},
[CPRMAN_PLLC] = {
.name = "pllc",
.prediv_mask = R_A2W_PLLx_ANA1_FB_PREDIV_MASK,
FILL_PLL_INIT_INFO(PLLC),
},
[CPRMAN_PLLD] = {
.name = "plld",
.prediv_mask = R_A2W_PLLx_ANA1_FB_PREDIV_MASK,
FILL_PLL_INIT_INFO(PLLD),
},
[CPRMAN_PLLH] = {
.name = "pllh",
.prediv_mask = R_A2W_PLLH_ANA1_FB_PREDIV_MASK,
FILL_PLL_INIT_INFO(PLLH),
},
[CPRMAN_PLLB] = {
.name = "pllb",
.prediv_mask = R_A2W_PLLx_ANA1_FB_PREDIV_MASK,
FILL_PLL_INIT_INFO(PLLB),
},
};
#undef FILL_PLL_CHANNEL_INIT_INFO
static inline void set_pll_init_info(BCM2835CprmanState *s,
CprmanPllState *pll,
CprmanPll id)
{
pll->id = id;
pll->reg_cm = &s->regs[PLL_INIT_INFO[id].cm_offset];
pll->reg_a2w_ctrl = &s->regs[PLL_INIT_INFO[id].a2w_ctrl_offset];
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];
}
#endif