Patchwork [v6,2/5] Implement i.MX31 Clock Control Module

login
register
mail settings
Submitter Peter Chubb
Date April 22, 2012, 11:31 p.m.
Message ID <20120422234100.537144816@nicta.com.au>
Download mbox | patch
Permalink /patch/154308/
State New
Headers show

Comments

Peter Chubb - April 22, 2012, 11:31 p.m.
For Linux to be able to work out how fast its clocks are going, so
that timer ticks come approximately at the right time, it needs to
be able to query the clock control module (CCM). 

This is the start of a CCM implementation.  It currently knows only about 
the MCU, HSP and IPG clocks --- i.e., the ones used to feed the periodic 
and general purpose timers.

Signed-off-by: Peter Chubb <peter.chubb@nicta.com.au>

---
 Makefile.target |    2 
 hw/imx.h        |   10 +
 hw/imx_ccm.c    |  312 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 323 insertions(+), 1 deletion(-)
Peter Maydell - April 23, 2012, 4:18 p.m.
On 23 April 2012 00:31, Peter Chubb <peter.chubb@nicta.com.au> wrote:
> +    /* Frequencies precalculated on register changes */
> +    uint32_t pll_refclk_freq;
> +    uint32_t mcu_clk_freq;
> +    uint32_t hsp_clk_freq;
> +    uint32_t ipg_clk_freq;
> +} IMXCCMState;
> +
> +static const VMStateDescription vmstate_imx_ccm = {
> +    .name = "imx-ccm",
> +    .version_id = 1,
> +    .minimum_version_id = 1,
> +    .minimum_version_id_old = 1,
> +    .fields = (VMStateField[]) {
> +        VMSTATE_UINT32(ccmr, IMXCCMState),
> +        VMSTATE_UINT32(pdr0, IMXCCMState),
> +        VMSTATE_UINT32(pdr1, IMXCCMState),
> +        VMSTATE_UINT32(mpctl, IMXCCMState),
> +        VMSTATE_UINT32(spctl, IMXCCMState),
> +        VMSTATE_UINT32_ARRAY(cgr, IMXCCMState, 3),
> +        VMSTATE_UINT32(pmcr0, IMXCCMState),
> +        VMSTATE_UINT32(pmcr1, IMXCCMState),
> +        VMSTATE_UINT32(pll_refclk_freq, IMXCCMState),
> +        VMSTATE_UINT32(mcu_clk_freq, IMXCCMState),
> +        VMSTATE_UINT32(hsp_clk_freq, IMXCCMState),
> +        VMSTATE_UINT32(ipg_clk_freq, IMXCCMState),

Rather than having the *_clk_freq saved and loaded
in the vmstate, I think it would be nicer to have a
post-load-hook that called update_clocks().

> +static uint32_t calc_pll(uint32_t pllreg, uint32_t base_freq)
> +{
> +    int32_t mfn = MFN(pllreg);  /* Numerator */
> +    uint32_t mfi = MFI(pllreg); /* Integer part */
> +    uint32_t mfd = 1 + MFD(pllreg); /* Denominator */
> +    uint32_t pd = 1 + PD(pllreg);   /* Pre-divider */
> +
> +    if (mfi < 5) {
> +        mfi = 5;
> +    }
> +    /* mfn is 10-bit signed twos-complement */
> +    mfn -= (mfn & 0x200);

What is this calculation supposed to do? It doesn't
convert a 10-bit signed twos-complement number into an
int32_t, unless I'm confused... Also, it's a rather
opaque way to write "mfn &= 0x200;".

-- PMM
Peter Chubb - April 23, 2012, 8:54 p.m.
>>>>> "Peter" == Peter Maydell <peter.maydell@linaro.org> writes:

Peter> On 23 April 2012 00:31, Peter Chubb <peter.chubb@nicta.com.au>
Peter> wrote:

Peter> Rather than having the *_clk_freq saved and loaded in the
Peter> vmstate, I think it would be nicer to have a post-load-hook
Peter> that called update_clocks().

OK.


>>    /* mfn is 10-bit signed twos-complement */ 
>> +    mfn -= (mfn & 0x200);

Peter> What is this calculation supposed to do? It doesn't convert a
Peter> 10-bit signed twos-complement number into an int32_t, unless
Peter> I'm confused... Also, it's a rather opaque way to write "mfn &=
Peter> 0x200;".

I'll use a different way to calculate.  Maybe:
     mfn <<= (32-10);
     mfn >>= (32-10);

Peter C
--
Dr Peter Chubb				        peter.chubb AT nicta.com.au
http://www.ssrg.nicta.com.au          Software Systems Research Group/NICTA
Paolo Bonzini - April 24, 2012, 6:58 a.m.
Il 23/04/2012 22:54, Peter Chubb ha scritto:
> Peter> What is this calculation supposed to do? It doesn't convert a
> Peter> 10-bit signed twos-complement number into an int32_t, unless
> Peter> I'm confused... Also, it's a rather opaque way to write "mfn &=
> Peter> 0x200;".
> 
> I'll use a different way to calculate.  Maybe:
>      mfn <<= (32-10);
>      mfn >>= (32-10);

The magic that you wanted is

   mfn |= -(mfn & 0x200);

:) but I do prefer the shifts too.

Paolo
Peter Chubb - April 24, 2012, 7:04 a.m.
>>>>> "Paolo" == Paolo Bonzini <pbonzini@redhat.com> writes:

Paolo> Il 23/04/2012 22:54, Peter Chubb ha scritto:
Peter> What is this calculation supposed to do? It doesn't convert a
Peter> 10-bit signed twos-complement number into an int32_t, unless
Peter> I'm confused... Also, it's a rather opaque way to write "mfn &=
Peter> 0x200;".
>> 
>> I'll use a different way to calculate.  Maybe: 
>> mfn <<= (32-10); 
>> mfn >= (32-10);

Paolo> The magic that you wanted is

Paolo>    mfn |= -(mfn & 0x200);


Yes.  I'd actually been thinking of mfn -= 2 * (mfn & 0x200);
but forgot the 2*.  But the shifts should be faster, and that's wjhat
I'm testing at the moment.


Peter C
--
Dr Peter Chubb				        peter.chubb AT nicta.com.au
http://www.ssrg.nicta.com.au          Software Systems Research Group/NICTA

Patch

Index: qemu-working/Makefile.target
===================================================================
--- qemu-working.orig/Makefile.target	2012-04-23 08:24:52.106056219 +1000
+++ qemu-working/Makefile.target	2012-04-23 08:24:52.945842194 +1000
@@ -399,7 +399,7 @@  obj-arm-y += vexpress.o
 obj-arm-y += strongarm.o
 obj-arm-y += collie.o
 obj-arm-y += pl041.o lm4549.o
-obj-arm-y += imx_serial.o
+obj-arm-y += imx_serial.o imx_ccm.o
 obj-arm-$(CONFIG_FDT) += device_tree.o
 
 obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o
Index: qemu-working/hw/imx_ccm.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ qemu-working/hw/imx_ccm.c	2012-04-23 08:24:52.945842194 +1000
@@ -0,0 +1,312 @@ 
+/*
+ * IMX31 Clock Control Module
+ *
+ * Copyright (C) 2012 NICTA
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * To get the timer frequencies right, we need to emulate at least part of
+ * the CCM.
+ */
+
+#include "hw.h"
+#include "sysbus.h"
+#include "sysemu.h"
+#include "imx.h"
+
+#define CKIH_FREQ 26000000 /* 26MHz crystal input */
+#define CKIL_FREQ    32768 /* nominal 32khz clock */
+
+
+//#define DEBUG_CCM 1
+#ifdef DEBUG_CCM
+#define DPRINTF(fmt, args...) \
+do { printf("imx_ccm: " fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...) do {} while (0)
+#endif
+
+typedef struct {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+
+    uint32_t ccmr;
+    uint32_t pdr0;
+    uint32_t pdr1;
+    uint32_t mpctl;
+    uint32_t spctl;
+    uint32_t cgr[3];
+    uint32_t pmcr0;
+    uint32_t pmcr1;
+
+    /* Frequencies precalculated on register changes */
+    uint32_t pll_refclk_freq;
+    uint32_t mcu_clk_freq;
+    uint32_t hsp_clk_freq;
+    uint32_t ipg_clk_freq;
+} IMXCCMState;
+
+static const VMStateDescription vmstate_imx_ccm = {
+    .name = "imx-ccm",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(ccmr, IMXCCMState),
+        VMSTATE_UINT32(pdr0, IMXCCMState),
+        VMSTATE_UINT32(pdr1, IMXCCMState),
+        VMSTATE_UINT32(mpctl, IMXCCMState),
+        VMSTATE_UINT32(spctl, IMXCCMState),
+        VMSTATE_UINT32_ARRAY(cgr, IMXCCMState, 3),
+        VMSTATE_UINT32(pmcr0, IMXCCMState),
+        VMSTATE_UINT32(pmcr1, IMXCCMState),
+        VMSTATE_UINT32(pll_refclk_freq, IMXCCMState),
+        VMSTATE_UINT32(mcu_clk_freq, IMXCCMState),
+        VMSTATE_UINT32(hsp_clk_freq, IMXCCMState),
+        VMSTATE_UINT32(ipg_clk_freq, IMXCCMState),
+    },
+};
+
+/* CCMR */
+#define CCMR_FPME (1<<0)
+#define CCMR_MPE  (1<<3)
+#define CCMR_MDS  (1<<7)
+#define CCMR_FPMF (1<<26)
+#define CCMR_PRCS (3<<1)
+
+/* PDR0 */
+#define PDR0_MCU_PODF_SHIFT (0)
+#define PDR0_MCU_PODF_MASK (0x7)
+#define PDR0_MAX_PODF_SHIFT (3)
+#define PDR0_MAX_PODF_MASK (0x7)
+#define PDR0_IPG_PODF_SHIFT (6)
+#define PDR0_IPG_PODF_MASK (0x3)
+#define PDR0_NFC_PODF_SHIFT (8)
+#define PDR0_NFC_PODF_MASK (0x7)
+#define PDR0_HSP_PODF_SHIFT (11)
+#define PDR0_HSP_PODF_MASK (0x7)
+#define PDR0_PER_PODF_SHIFT (16)
+#define PDR0_PER_PODF_MASK (0x1f)
+#define PDR0_CSI_PODF_SHIFT (23)
+#define PDR0_CSI_PODF_MASK (0x1ff)
+
+#define EXTRACT(value, name) (((value) >> PDR0_##name##_PODF_SHIFT) \
+                              & PDR0_##name##_PODF_MASK)
+#define INSERT(value, name) (((value) & PDR0_##name##_PODF_MASK) << \
+                             PDR0_##name##_PODF_SHIFT)
+/* PLL control registers */
+#define PD(v) (((v) >> 26) & 0xf)
+#define MFD(v) (((v) >> 16) & 0x3ff)
+#define MFI(v) (((v) >> 10) & 0xf);
+#define MFN(v) ((v) & 0x3ff)
+
+#define PLL_PD(x)               (((x) & 0xf) << 26)
+#define PLL_MFD(x)              (((x) & 0x3ff) << 16)
+#define PLL_MFI(x)              (((x) & 0xf) << 10)
+#define PLL_MFN(x)              (((x) & 0x3ff) << 0)
+
+uint32_t imx_clock_frequency(DeviceState *dev, IMXClk clock)
+{
+    IMXCCMState *s = container_of(dev, IMXCCMState, busdev.qdev);
+
+    switch (clock) {
+    case NOCLK:
+        return 0;
+    case MCU:
+        return s->mcu_clk_freq;
+    case HSP:
+        return s->hsp_clk_freq;
+    case IPG:
+        return s->ipg_clk_freq;
+    case CLK_32k:
+        return CKIL_FREQ;
+    }
+    return 0;
+}
+
+/*
+ * Calculate PLL output frequency
+ */
+static uint32_t calc_pll(uint32_t pllreg, uint32_t base_freq)
+{
+    int32_t mfn = MFN(pllreg);  /* Numerator */
+    uint32_t mfi = MFI(pllreg); /* Integer part */
+    uint32_t mfd = 1 + MFD(pllreg); /* Denominator */
+    uint32_t pd = 1 + PD(pllreg);   /* Pre-divider */
+
+    if (mfi < 5) {
+        mfi = 5;
+    }
+    /* mfn is 10-bit signed twos-complement */
+    mfn -= (mfn & 0x200);
+
+    return ((2 * (base_freq >> 10) * (mfi * mfd + mfn)) /
+            (mfd * pd)) << 10;
+}
+
+static void update_clocks(IMXCCMState *s)
+{
+    /*
+     * If we ever emulate more clocks, this should switch to a data-driven
+     * approach
+     */
+
+    if ((s->ccmr & CCMR_PRCS) == 1) {
+        s->pll_refclk_freq = CKIL_FREQ * 1024;
+    } else {
+        s->pll_refclk_freq = CKIH_FREQ;
+    }
+
+    /* ipg_clk_arm aka MCU clock */
+    if ((s->ccmr & CCMR_MDS) || !(s->ccmr & CCMR_MPE)) {
+        s->mcu_clk_freq = s->pll_refclk_freq;
+    } else {
+        s->mcu_clk_freq = calc_pll(s->mpctl, s->pll_refclk_freq);
+    }
+
+    /* High-speed clock */
+    s->hsp_clk_freq = s->mcu_clk_freq / (1 + EXTRACT(s->pdr0, HSP));
+    s->ipg_clk_freq = s->hsp_clk_freq / (1 + EXTRACT(s->pdr0, IPG));
+
+    DPRINTF("Clocks: mcu %uMHz, HSP %uMHz, IPG %uHz\n",
+            s->mcu_clk_freq / 1000000,
+            s->hsp_clk_freq / 1000000,
+            s->ipg_clk_freq);
+}
+
+static void imx_ccm_reset(DeviceState *dev)
+{
+    IMXCCMState *s = container_of(dev, IMXCCMState, busdev.qdev);
+
+    s->ccmr = 0x074b0b7b;
+    s->pdr0 = 0xff870b48;
+    s->pdr1 = 0x49fcfe7f;
+    s->mpctl = PLL_PD(1) | PLL_MFD(0) | PLL_MFI(6) | PLL_MFN(0);
+    s->cgr[0] = s->cgr[1] = s->cgr[2] = 0xffffffff;
+    s->spctl = PLL_PD(1) | PLL_MFD(4) | PLL_MFI(0xc) | PLL_MFN(1);
+    s->pmcr0 = 0x80209828;
+
+    update_clocks(s);
+}
+
+static uint64_t imx_ccm_read(void *opaque, target_phys_addr_t offset,
+                                unsigned size)
+{
+    IMXCCMState *s = (IMXCCMState *)opaque;
+
+    DPRINTF("read(offset=%x)", offset >> 2);
+    switch (offset >> 2) {
+    case 0: /* CCMR */
+        DPRINTF(" ccmr = 0x%x\n", s->ccmr);
+        return s->ccmr;
+    case 1:
+        DPRINTF(" pdr0 = 0x%x\n", s->pdr0);
+        return s->pdr0;
+    case 2:
+        DPRINTF(" pdr1 = 0x%x\n", s->pdr1);
+        return s->pdr1;
+    case 4:
+        DPRINTF(" mpctl = 0x%x\n", s->mpctl);
+        return s->mpctl;
+    case 6:
+        DPRINTF(" spctl = 0x%x\n", s->spctl);
+        return s->spctl;
+    case 8:
+        DPRINTF(" cgr0 = 0x%x\n", s->cgr[0]);
+        return s->cgr[0];
+    case 9:
+        DPRINTF(" cgr1 = 0x%x\n", s->cgr[1]);
+        return s->cgr[1];
+    case 10:
+        DPRINTF(" cgr2 = 0x%x\n", s->cgr[2]);
+        return s->cgr[2];
+    case 18: /* LTR1 */
+        return 0x00004040;
+    case 23:
+        DPRINTF(" pcmr0 = 0x%x\n", s->pmcr0);
+        return s->pmcr0;
+    }
+    DPRINTF(" return 0\n");
+    return 0;
+}
+
+static void imx_ccm_write(void *opaque, target_phys_addr_t offset,
+                          uint64_t value, unsigned size)
+{
+    IMXCCMState *s = (IMXCCMState *)opaque;
+
+    DPRINTF("write(offset=%x, value = %x)\n",
+            offset >> 2, (unsigned int)value);
+    switch (offset >> 2) {
+    case 0:
+        s->ccmr = CCMR_FPMF | (value & 0x3b6fdfff);
+        break;
+    case 1:
+        s->pdr0 = value & 0xff9f3fff;
+        break;
+    case 2:
+        s->pdr1 = value;
+        break;
+    case 4:
+        s->mpctl = value & 0xbfff3fff;
+        break;
+    case 6:
+        s->spctl = value & 0xbfff3fff;
+        break;
+    case 8:
+        s->cgr[0] = value;
+        return;
+    case 9:
+        s->cgr[1] = value;
+        return;
+    case 10:
+        s->cgr[2] = value;
+        return;
+
+    default:
+        return;
+    }
+    update_clocks(s);
+}
+
+static const struct MemoryRegionOps imx_ccm_ops = {
+    .read = imx_ccm_read,
+    .write = imx_ccm_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+};
+
+static int imx_ccm_init(SysBusDevice *dev)
+{
+    IMXCCMState *s = FROM_SYSBUS(typeof(*s), dev);
+
+    memory_region_init_io(&s->iomem, &imx_ccm_ops, s, "imx_ccm", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+
+    return 0;
+}
+
+static void imx_ccm_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *sbc = SYS_BUS_DEVICE_CLASS(klass);
+
+    sbc->init = imx_ccm_init;
+    dc->reset = imx_ccm_reset;
+    dc->vmsd = &vmstate_imx_ccm;
+    dc->desc = "i.MX Clock Control Module";
+}
+
+static TypeInfo imx_ccm_info = {
+    .name = "imx_ccm",
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IMXCCMState),
+    .class_init = imx_ccm_class_init,
+};
+
+static void imx_ccm_register_types(void)
+{
+    type_register_static(&imx_ccm_info);
+}
+
+type_init(imx_ccm_register_types)
Index: qemu-working/hw/imx.h
===================================================================
--- qemu-working.orig/hw/imx.h	2012-04-23 08:24:52.106056219 +1000
+++ qemu-working/hw/imx.h	2012-04-23 08:24:52.949841175 +1000
@@ -13,4 +13,14 @@ 
 
 void imx_serial_create(int uart, const target_phys_addr_t addr, qemu_irq irq);
 
+typedef enum  {
+    NOCLK,
+    MCU,
+    HSP,
+    IPG,
+    CLK_32k
+} IMXClk;
+
+uint32_t imx_clock_frequency(DeviceState *s, IMXClk clock);
+
 #endif /* IMX_H */