bcc181b0ad
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> Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
322 lines
8.1 KiB
C
322 lines
8.1 KiB
C
/*
|
|
* 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
|
|
|
|
static int imx_ccm_post_load(void *opaque, int version_id);
|
|
|
|
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),
|
|
},
|
|
.post_load = imx_ccm_post_load,
|
|
};
|
|
|
|
/* 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 <<= 32 - 10;
|
|
mfn >>= 32 - 10;
|
|
|
|
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 int imx_ccm_post_load(void *opaque, int version_id)
|
|
{
|
|
IMXCCMState *s = (IMXCCMState *)opaque;
|
|
|
|
update_clocks(s);
|
|
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)
|