mirror of
https://github.com/qemu/qemu.git
synced 2025-08-15 13:47:03 +00:00
hw/misc/bcm2835_cprman: implement PLLs behaviour
The CPRMAN PLLs generate a clock based on a prescaler, a multiplier and a divider. The prescaler doubles the parent (xosc) frequency, then the multiplier/divider are applied. The multiplier has an integer and a fractional part. This commit also implements the CPRMAN CM_LOCK register. This register reports which PLL is currently locked. We consider a PLL has being locked as soon as it is enabled (on real hardware, there is a delay after turning a PLL on, for it to stabilize). 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:
parent
1e986e25d0
commit
6d2b874cf1
@ -50,9 +50,47 @@
|
|||||||
|
|
||||||
/* PLL */
|
/* PLL */
|
||||||
|
|
||||||
|
static bool pll_is_locked(const CprmanPllState *pll)
|
||||||
|
{
|
||||||
|
return !FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PWRDN)
|
||||||
|
&& !FIELD_EX32(*pll->reg_cm, CM_PLLx, ANARST);
|
||||||
|
}
|
||||||
|
|
||||||
static void pll_update(CprmanPllState *pll)
|
static void pll_update(CprmanPllState *pll)
|
||||||
{
|
{
|
||||||
|
uint64_t freq, ndiv, fdiv, pdiv;
|
||||||
|
|
||||||
|
if (!pll_is_locked(pll)) {
|
||||||
clock_update(pll->out, 0);
|
clock_update(pll->out, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pdiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, PDIV);
|
||||||
|
|
||||||
|
if (!pdiv) {
|
||||||
|
clock_update(pll->out, 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ndiv = FIELD_EX32(*pll->reg_a2w_ctrl, A2W_PLLx_CTRL, NDIV);
|
||||||
|
fdiv = FIELD_EX32(*pll->reg_a2w_frac, A2W_PLLx_FRAC, FRAC);
|
||||||
|
|
||||||
|
if (pll->reg_a2w_ana[1] & pll->prediv_mask) {
|
||||||
|
/* The prescaler doubles the parent frequency */
|
||||||
|
ndiv *= 2;
|
||||||
|
fdiv *= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We have a multiplier with an integer part (ndiv) and a fractional part
|
||||||
|
* (fdiv), and a divider (pdiv).
|
||||||
|
*/
|
||||||
|
freq = clock_get_hz(pll->xosc_in) *
|
||||||
|
((ndiv << R_A2W_PLLx_FRAC_FRAC_LENGTH) + fdiv);
|
||||||
|
freq /= pdiv;
|
||||||
|
freq >>= R_A2W_PLLx_FRAC_FRAC_LENGTH;
|
||||||
|
|
||||||
|
clock_update_hz(pll->out, freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void pll_xosc_update(void *opaque)
|
static void pll_xosc_update(void *opaque)
|
||||||
@ -96,6 +134,26 @@ static const TypeInfo cprman_pll_info = {
|
|||||||
|
|
||||||
/* CPRMAN "top level" model */
|
/* CPRMAN "top level" model */
|
||||||
|
|
||||||
|
static uint32_t get_cm_lock(const BCM2835CprmanState *s)
|
||||||
|
{
|
||||||
|
static const int CM_LOCK_MAPPING[CPRMAN_NUM_PLL] = {
|
||||||
|
[CPRMAN_PLLA] = R_CM_LOCK_FLOCKA_SHIFT,
|
||||||
|
[CPRMAN_PLLC] = R_CM_LOCK_FLOCKC_SHIFT,
|
||||||
|
[CPRMAN_PLLD] = R_CM_LOCK_FLOCKD_SHIFT,
|
||||||
|
[CPRMAN_PLLH] = R_CM_LOCK_FLOCKH_SHIFT,
|
||||||
|
[CPRMAN_PLLB] = R_CM_LOCK_FLOCKB_SHIFT,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t r = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < CPRMAN_NUM_PLL; i++) {
|
||||||
|
r |= pll_is_locked(&s->plls[i]) << CM_LOCK_MAPPING[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
static uint64_t cprman_read(void *opaque, hwaddr offset,
|
static uint64_t cprman_read(void *opaque, hwaddr offset,
|
||||||
unsigned size)
|
unsigned size)
|
||||||
{
|
{
|
||||||
@ -104,6 +162,10 @@ static uint64_t cprman_read(void *opaque, hwaddr offset,
|
|||||||
size_t idx = offset / sizeof(uint32_t);
|
size_t idx = offset / sizeof(uint32_t);
|
||||||
|
|
||||||
switch (idx) {
|
switch (idx) {
|
||||||
|
case R_CM_LOCK:
|
||||||
|
r = get_cm_lock(s);
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
r = s->regs[idx];
|
r = s->regs[idx];
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,14 @@ REG32(A2W_PLLD_FRAC, 0x1240)
|
|||||||
REG32(A2W_PLLH_FRAC, 0x1260)
|
REG32(A2W_PLLH_FRAC, 0x1260)
|
||||||
REG32(A2W_PLLB_FRAC, 0x12e0)
|
REG32(A2W_PLLB_FRAC, 0x12e0)
|
||||||
|
|
||||||
|
/* misc registers */
|
||||||
|
REG32(CM_LOCK, 0x114)
|
||||||
|
FIELD(CM_LOCK, FLOCKH, 12, 1)
|
||||||
|
FIELD(CM_LOCK, FLOCKD, 11, 1)
|
||||||
|
FIELD(CM_LOCK, FLOCKC, 10, 1)
|
||||||
|
FIELD(CM_LOCK, FLOCKB, 9, 1)
|
||||||
|
FIELD(CM_LOCK, FLOCKA, 8, 1)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This field is common to all registers. Each register write value must match
|
* This field is common to all registers. Each register write value must match
|
||||||
* the CPRMAN_PASSWORD magic value in its 8 MSB.
|
* the CPRMAN_PASSWORD magic value in its 8 MSB.
|
||||||
|
Loading…
Reference in New Issue
Block a user