diff mbox

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

Message ID 20120403020306.634180426@nicta.com.au
State New
Headers show

Commit Message

Peter Chubb April 3, 2012, 1:55 a.m. UTC
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        |   14 ++
 hw/imx_ccm.c    |  334 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 349 insertions(+), 1 deletion(-)

Comments

Peter Maydell April 5, 2012, 5:31 p.m. UTC | #1
On 3 April 2012 02:55, Peter Chubb <peter.chubb@nicta.com.au> wrote:
> ===================================================================
> --- qemu-working.orig/hw/imx.h  2012-04-03 11:48:48.088706634 +1000
> +++ qemu-working/hw/imx.h       2012-04-03 11:48:48.776708322 +1000
> @@ -13,4 +13,18 @@
>
>  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_timer_frequency(DeviceState *s, IMXClk clock);
> +void imx_timer_create(const char * const name,
> +                      const target_phys_addr_t addr,
> +                      qemu_irq irq,
> +                      DeviceState *ccm);
> +

These function prototypes should be in the other patch, surely?

> +/* 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 (7)

Some consistency about whether you're using 7 or 0x7 for
masks would be nice.
> +
> +/*
> + *  Set up the clocks the way U-Boot does.
> + */
> +static void imx_ccm_uboot_reset(IMXCCMState *s)
> +{
> +    s->ccmr = (0x074b0bf5 | CCMR_MPE) & ~CCMR_MDS;
> +    s->pdr0 = INSERT(0xff1, CSI) |
> +        INSERT(7, PER) | INSERT(3, HSP) | INSERT(5, NFC) | INSERT(1, IPG) |
> +        INSERT(3, MAX) | INSERT(0, MCU);
> +    s->mpctl = PLL_PD(0) | PLL_MFD(0xe) | PLL_MFI(9) | PLL_MFN(0xd);
> +}
> +
> +/*
> + * Set up the clocks the way a hardware reset or power-on does
> + */
> +static void imx_ccm_hw_reset(IMXCCMState *s)
> +{
> +    s->ccmr = 0x074b0b7b;
> +    s->pdr0 = 0xff870b48;
> +    s->mpctl = PLL_PD(1) | PLL_MFD(0) | PLL_MFI(6) | PLL_MFN(0);
> +}
> +
> +static void imx_ccm_reset(DeviceState *dev)
> +{
> +    IMXCCMState *s = container_of(dev, IMXCCMState, busdev.qdev);
> +
> +    s->cgr[0] = s->cgr[1] = s->cgr[2] = 0xffffffff;
> +    s->pmcr0 = 0x80209828;
> +    s->pdr1 = 0x49fcfe7f;
> +    s->spctl = PLL_PD(1) | PLL_MFD(4) | PLL_MFI(0xc) | PLL_MFN(1);
> +
> +    /*
> +     * Really should predicate this on arm_boot_info->is_linux
> +     * but I don't know how to do that.
> +     */
> +    if (1) {
> +        imx_ccm_uboot_reset(s);
> +    } else {
> +        imx_ccm_hw_reset(s);
> +    }
> +    update_clocks(s);
> +}

Urgh. Device models need to act like the device. Setting up
devices the way u-boot happens to initialise them should be
done somewhere else, ie driven by our built in bootloader,
if we do it at all.

However, the kernel's Documentation/arm/booting.txt says
nothing about the bootloader having to mess with clocks
so I would rather prefer to hold the line of:
 * fix your kernel not to care
 * or load an actual u-boot binary into emulated RAM
   or emulated flash, and let that do the setup
because I can see board-specific tweaks to the bootloader
getting rapidly out of hand if we're not careful...

-- PMM
Peter Chubb April 10, 2012, 1:24 a.m. UTC | #2
>>>>> "Peter" == Peter Maydell <peter.maydell@linaro.org> writes:

Peter> On 3 April 2012 02:55, Peter Chubb <peter.chubb@nicta.com.au>
Peter> wrote:
> +
> +uint32_t imx_timer_frequency(DeviceState *s, IMXClk clock);
> +void imx_timer_create(const char * const name,
> +                      const target_phys_addr_t addr,
> +                      qemu_irq irq,
> +                      DeviceState *ccm);
> +

Peter> These function prototypes should be in the other patch, surely?

Yes. At least, the second one does.  Thanks, moved to the imx_timer.c patch.

>> +/* 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 (7)

Peter> Some consistency about whether you're using 7 or 0x7 for masks
Peter> would be nice.

Done.

> +static void imx_ccm_reset(DeviceState *dev)
> +{
> +    IMXCCMState *s = container_of(dev, IMXCCMState, busdev.qdev);
> +
> +    s->cgr[0] = s->cgr[1] = s->cgr[2] = 0xffffffff;
> +    s->pmcr0 = 0x80209828;
> +    s->pdr1 = 0x49fcfe7f;
> +    s->spctl = PLL_PD(1) | PLL_MFD(4) | PLL_MFI(0xc) | PLL_MFN(1);
> +
> +    /*
> +     * Really should predicate this on arm_boot_info->is_linux
> +     * but I don't know how to do that.
> +     */
> +    if (1) {
> +        imx_ccm_uboot_reset(s);
> +    } else {
> +        imx_ccm_hw_reset(s);
> +    }
> +    update_clocks(s);
> +}
> +

Peter> Urgh. Device models need to act like the device. Setting up
Peter> devices the way u-boot happens to initialise them should be
Peter> done somewhere else, ie driven by our built in bootloader, if
Peter> we do it at all.

I'd like to be able to do
  arm-system-qemu -M kzm -nographic -kernel zImage
and have it work the same as if it'd been booted on the real
hardware.

For now I've removed the u-boot stuff.

Is there a sane way to add to the built-in bootloader?  

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

Patch

Index: qemu-working/Makefile.target
===================================================================
--- qemu-working.orig/Makefile.target	2012-04-03 11:48:48.088706634 +1000
+++ qemu-working/Makefile.target	2012-04-03 11:48:48.776708322 +1000
@@ -393,7 +393,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.h
===================================================================
--- qemu-working.orig/hw/imx.h	2012-04-03 11:48:48.088706634 +1000
+++ qemu-working/hw/imx.h	2012-04-03 11:48:48.776708322 +1000
@@ -13,4 +13,18 @@ 
 
 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_timer_frequency(DeviceState *s, IMXClk clock);
+void imx_timer_create(const char * const name,
+                      const target_phys_addr_t addr,
+                      qemu_irq irq,
+                      DeviceState *ccm);
+
 #endif /* IMX_H */
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-03 11:48:48.776708322 +1000
@@ -0,0 +1,334 @@ 
+/*
+ * 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 (7)
+#define PDR0_HSP_PODF_SHIFT (11)
+#define PDR0_HSP_PODF_MASK (7)
+#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_timer_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);
+}
+
+/*
+ *  Set up the clocks the way U-Boot does.
+ */
+static void imx_ccm_uboot_reset(IMXCCMState *s)
+{
+    s->ccmr = (0x074b0bf5 | CCMR_MPE) & ~CCMR_MDS;
+    s->pdr0 = INSERT(0xff1, CSI) |
+        INSERT(7, PER) | INSERT(3, HSP) | INSERT(5, NFC) | INSERT(1, IPG) |
+        INSERT(3, MAX) | INSERT(0, MCU);
+    s->mpctl = PLL_PD(0) | PLL_MFD(0xe) | PLL_MFI(9) | PLL_MFN(0xd);
+}
+
+/*
+ * Set up the clocks the way a hardware reset or power-on does
+ */
+static void imx_ccm_hw_reset(IMXCCMState *s)
+{
+    s->ccmr = 0x074b0b7b;
+    s->pdr0 = 0xff870b48;
+    s->mpctl = PLL_PD(1) | PLL_MFD(0) | PLL_MFI(6) | PLL_MFN(0);
+}
+
+static void imx_ccm_reset(DeviceState *dev)
+{
+    IMXCCMState *s = container_of(dev, IMXCCMState, busdev.qdev);
+
+    s->cgr[0] = s->cgr[1] = s->cgr[2] = 0xffffffff;
+    s->pmcr0 = 0x80209828;
+    s->pdr1 = 0x49fcfe7f;
+    s->spctl = PLL_PD(1) | PLL_MFD(4) | PLL_MFI(0xc) | PLL_MFN(1);
+
+    /*
+     * Really should predicate this on arm_boot_info->is_linux
+     * but I don't know how to do that.
+     */
+    if (1) {
+        imx_ccm_uboot_reset(s);
+    } else {
+        imx_ccm_hw_reset(s);
+    }
+    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 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 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)