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 */
|
||||
|
||||
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)
|
||||
{
|
||||
clock_update(pll->out, 0);
|
||||
uint64_t freq, ndiv, fdiv, pdiv;
|
||||
|
||||
if (!pll_is_locked(pll)) {
|
||||
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)
|
||||
@ -96,6 +134,26 @@ static const TypeInfo cprman_pll_info = {
|
||||
|
||||
/* 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,
|
||||
unsigned size)
|
||||
{
|
||||
@ -104,6 +162,10 @@ static uint64_t cprman_read(void *opaque, hwaddr offset,
|
||||
size_t idx = offset / sizeof(uint32_t);
|
||||
|
||||
switch (idx) {
|
||||
case R_CM_LOCK:
|
||||
r = get_cm_lock(s);
|
||||
break;
|
||||
|
||||
default:
|
||||
r = s->regs[idx];
|
||||
}
|
||||
|
@ -100,6 +100,14 @@ REG32(A2W_PLLD_FRAC, 0x1240)
|
||||
REG32(A2W_PLLH_FRAC, 0x1260)
|
||||
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
|
||||
* the CPRMAN_PASSWORD magic value in its 8 MSB.
|
||||
|
Loading…
Reference in New Issue
Block a user