aboutsummaryrefslogtreecommitdiff
path: root/hw/misc/bcm2835_cprman.c
diff options
context:
space:
mode:
authorLuc Michel <luc@lmichel.fr>2020-10-10 15:57:51 +0200
committerPeter Maydell <peter.maydell@linaro.org>2020-10-27 11:10:44 +0000
commit6d2b874cf1a6f595df805835325e9124c26f3dbf (patch)
tree9faf20be2ff086dbabf8a3f00bec418cc270dd9b /hw/misc/bcm2835_cprman.c
parent1e986e25d03c0d579843c4d3e2915b2f4ac8e47f (diff)
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>
Diffstat (limited to 'hw/misc/bcm2835_cprman.c')
-rw-r--r--hw/misc/bcm2835_cprman.c64
1 files changed, 63 insertions, 1 deletions
diff --git a/hw/misc/bcm2835_cprman.c b/hw/misc/bcm2835_cprman.c
index b86f5901b8..144bcc289d 100644
--- a/hw/misc/bcm2835_cprman.c
+++ b/hw/misc/bcm2835_cprman.c
@@ -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];
}