blob: df90152cd7b0a24119bd61fe2249e4be91a97e2d [file]
/*
* Copyright 2017, Data61
* Commonwealth Scientific and Industrial Research Organisation (CSIRO)
* ABN 41 687 119 230.
*
* This software may be distributed and modified according to the terms of
* the BSD 2-Clause license. Note that NO WARRANTY is provided.
* See "LICENSE_BSD2.txt" for details.
*
* @TAG(DATA61_BSD)
*/
#include <platsupport/i2c.h>
#include "../../arch/arm/clock.h"
#include "../../services.h"
#include <assert.h>
#include <string.h>
#include <utils/util.h>
#define CCM_PADDR 0x020C4000
#define CCM_SIZE 0x1000
#define CCM_ANALOG_PADDR 0x020C8000
#define CCM_ANALOG_SIZE 0x1000
/* Generic PLL */
#define PLL_LOCK BIT(31)
#define PLL_BYPASS BIT(16)
/* SYS PLL */
#define PLL_ARM_DIV_MASK 0x7F
#define PLL_ARM_ENABLE BIT(13)
/* ENET PLL */
#define PLL_ENET_DIV_MASK 0x3
#define PLL_ENET_ENABLE BIT(13)
/* USB */
#define PLL_USB_DIV_MASK 0x3
#define PLL_EN_USB_CLKS BIT(6)
#define PLL_USB_ENABLE BIT(13)
#define PLL_USB_POWER BIT(12)
/* Also known as PLL_SYS */
#define PLL2_PADDR 0x020C8030
#define PLL2_CTRL_LOCK BIT(31)
#define PLL2_CTRL_PDFOFFSET_EN BIT(18)
#define PLL2_CTRL_BYPASS BIT(16)
#define PLL2_CTRL_BYPASS_SRC(x) ((x) << 14)
#define PLL2_CTRL_ENABLE BIT(13)
#define PLL2_CTRL_PWR_DOWN BIT(12)
#define PLL2_CTRL_DIVSEL BIT(0)
#define PLL2_SS_STOP(x) ((x) << 16)
#define PLL2_SS_EN BIT(15)
#define PLL2_SS_STEP(x) ((x) << 0)
#define PLL_CLKGATE BIT(31)
#define PLL_STABLE BIT(30)
#define PLL_FRAC(x) ((x) << 24)
#define CLKGATE_OFF 0x0
#define CLKGATE_ON_RUN 0x2
#define CLKGATE_ON_ALL 0x3
#define CLKGATE_MASK CLKGATE_ON_ALL
#define CLKO1_SRC_AHB (0xBU << 0)
#define CLKO1_SRC_IPG (0xCU << 0)
#define CLKO1_SRC_MASK (0xFU << 0)
#define CLKO1_ENABLE (1U << 7)
#define CLKO2_SRC_MMDC_CH0 (0U << 21)
#define CLKO2_SRC_MASK (0x1FU << 16)
#define CLKO2_ENABLE (1U << 24)
#define CLKO_SEL (1U << 8)
struct ccm_regs {
uint32_t ccr; /* 0x000 */
uint32_t ccdr; /* 0x004 */
uint32_t csr; /* 0x008 */
uint32_t ccsr; /* 0x00C */
uint32_t cacrr; /* 0x010 */
uint32_t cbcdr; /* 0x014 */
uint32_t cbcmr; /* 0x018 */
uint32_t cscmr1; /* 0x01C */
uint32_t cscmr2; /* 0x020 */
uint32_t cscdr1; /* 0x024 */
uint32_t cs1cdr; /* 0x028 */
uint32_t cs2cdr; /* 0x02C */
uint32_t cdcdr; /* 0x030 */
uint32_t chsccdr; /* 0x034 */
uint32_t cscdr2; /* 0x038 */
uint32_t cscdr3; /* 0x03C */
uint32_t res0[2];
uint32_t cdhipr; /* 0x048 */
uint32_t res1[1];
uint32_t ctor; /* 0x050 */
uint32_t clpcr; /* 0x054 */
uint32_t cisr; /* 0x058 */
uint32_t cimr; /* 0x05C */
uint32_t ccosr; /* 0x060 */
uint32_t cgpr; /* 0x064 */
uint32_t ccgr[7]; /* 0x068 */
uint32_t res2[1];
uint32_t cmeor; /* 0x088 */
};
typedef struct {
uint32_t val;
uint32_t set;
uint32_t clr;
uint32_t tog;
} alg_sct_t;
struct ccm_alg_usbphy_regs {
alg_sct_t vbus_detect; /* +0x00 */
alg_sct_t chrg_detect; /* +0x10 */
uint32_t vbus_detect_stat; /* +0x20 */
uint32_t res0[3];
uint32_t chrg_detect_stat; /* +0x30 */
uint32_t res1[3];
uint32_t res2[4];
alg_sct_t misc; /* +0x50 */
};
struct ccm_alg_regs {
/* PLL_ARM */
alg_sct_t pll_arm; /* 0x000 */
/* PLL_USB * 2 */
alg_sct_t pll_usb[2]; /* 0x010 */
/* PLL_SYS */
alg_sct_t pll_sys; /* 0x030 */
uint32_t pll_sys_ss; /* 0x040 */
uint32_t res0[3];
uint32_t pll_sys_num; /* 0x050 */
uint32_t res1[3];
uint32_t pll_sys_denom; /* 0x060 */
uint32_t res2[3];
/* PLL_AUDIO */
alg_sct_t pll_audio; /* 0x070 */
uint32_t pll_audio_num; /* 0x080 */
uint32_t res3[3];
uint32_t pll_audio_denom; /* 0x090 */
uint32_t res4[3];
/* PLL_VIDIO */
alg_sct_t pll_video; /* 0x0A0 */
uint32_t pll_video_num; /* 0x0B0 */
uint32_t res5[3];
uint32_t pll_video_denom; /* 0x0C0 */
uint32_t res6[3];
/* PLL_MLB */
alg_sct_t pll_mlb; /* 0x0D0 */
/* PLL_ENET */
alg_sct_t pll_enet; /* 0x0E0 */
/* PDF_480 */
alg_sct_t pfd_480; /* 0x0F0 */
/* PDF_528 */
alg_sct_t pfd_528; /* 0x100 */
uint32_t res7[16];
/* MISC0 */
alg_sct_t misc0; /* 0x150 */
uint32_t res8[4];
/* MISC2 */
alg_sct_t misc2; /* 0x170 */
uint32_t res9[11];
/* USB phy control - Implemented here for the sake of
* componentisation since it shares the same register space */
struct ccm_alg_usbphy_regs phy1; /* 0x1a0 */
struct ccm_alg_usbphy_regs phy2; /* 0x200 */
uint32_t digprog; /* 0x260 */
};
static volatile struct clock_regs {
struct ccm_regs * ccm;
struct ccm_alg_regs * alg;
} clk_regs = {.ccm = NULL, .alg = NULL};
struct pll2_regs {
uint32_t ctrl;
uint32_t ctrl_s;
uint32_t ctrl_c;
uint32_t ctrl_t;
uint32_t ss;
uint32_t res0[3];
uint32_t num;
uint32_t res1[3];
uint32_t denom;
uint32_t res2[3];
};
static struct clock master_clk = { CLK_OPS_DEFAULT(MASTER) };
/* ARM_CLK */
static freq_t
_arm_get_freq(clk_t* clk)
{
uint32_t div;
uint32_t fout, fin;
div = clk_regs.alg->pll_arm.val;
div &= PLL_ARM_DIV_MASK;
fin = clk_get_freq(clk->parent);
fout = fin * div / 2;
return fout;
}
static freq_t
_arm_set_freq(clk_t* clk, freq_t hz)
{
uint32_t div;
uint32_t fin;
uint32_t v;
fin = clk_get_freq(clk->parent);
div = 2 * hz / fin;
div = INRANGE(54, div, 108);
/* bypass on during clock manipulation */
clk_regs.alg->pll_arm.set = PLL_BYPASS;
/* Set the divisor */
v = clk_regs.alg->pll_arm.val & ~(PLL_ARM_DIV_MASK);
v |= div;
clk_regs.alg->pll_arm.val = v;
/* wait for lock */
while (!(clk_regs.alg->pll_arm.val & PLL_LOCK));
/* bypass off */
clk_regs.alg->pll_arm.clr = PLL_BYPASS;
return clk_get_freq(clk);
}
static void
_arm_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_arm_init(clk_t* clk)
{
if (clk->priv == NULL) {
clk_t* parent;
parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MASTER);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock arm_clk = { CLK_OPS(ARM, arm, NULL) };
/* ENET_CLK */
static freq_t
_enet_get_freq(clk_t* clk)
{
uint32_t div;
uint32_t fin;
fin = clk_get_freq(clk->parent);
div = clk_regs.alg->pll_enet.val;
div &= PLL_ENET_DIV_MASK;
switch (div) {
case 3:
return 5 * fin;
case 2:
return 4 * fin;
case 1:
return 2 * fin;
case 0:
return 1 * fin;
default:
return 0 * fin;
}
}
static freq_t
_enet_set_freq(clk_t* clk, freq_t hz)
{
uint32_t div, fin;
uint32_t v;
if (clk_regs.alg == NULL) {
return clk_get_freq(clk);
}
fin = clk_get_freq(clk->parent);
if (hz >= 5 * fin) {
div = 3;
} else if (hz >= 4 * fin) {
div = 2;
} else if (hz >= 2 * fin) {
div = 1;
} else if (hz >= 1 * fin) {
div = 0;
} else {
div = 0;
}
/* bypass on */
clk_regs.alg->pll_enet.set = PLL_BYPASS;
v = PLL_ENET_ENABLE | PLL_BYPASS;
clk_regs.alg->pll_enet.val = v;
/* Change the frequency */
v = clk_regs.alg->pll_enet.val & ~(PLL_ENET_DIV_MASK);
v |= div;
clk_regs.alg->pll_enet.val = v;
while (!(clk_regs.alg->pll_enet.val & PLL_LOCK));
/* bypass off */
clk_regs.alg->pll_enet.clr = PLL_BYPASS;
printf("Set ENET frequency to %ld Mhz... ", (long int)clk_get_freq(clk) / MHZ);
return clk_get_freq(clk);
}
static void
_enet_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_enet_init(clk_t* clk)
{
if (clk->priv == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MASTER);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock enet_clk = { CLK_OPS(ENET, enet, NULL) };
/* PLL2_CLK */
static freq_t
_pll2_get_freq(clk_t* clk)
{
uint32_t p, s;
struct pll2_regs *regs;
regs = (struct pll2_regs*)((uint32_t)clk_regs.alg + (PLL2_PADDR & 0xfff));
assert((regs->ctrl & PLL2_CTRL_LOCK) != 0);
assert((regs->ctrl & PLL2_CTRL_BYPASS) == 0);
assert((regs->ctrl & PLL2_CTRL_PWR_DOWN) == 0);
/* pdf offset? */
p = clk_get_freq(clk->parent);
if (regs->ctrl & PLL2_CTRL_DIVSEL) {
s = 22;
} else {
s = 20;
}
return p * s;
}
static freq_t
_pll2_set_freq(clk_t* clk, freq_t hz)
{
uint32_t s;
if (clk_regs.alg == NULL) {
return clk_get_freq(clk);
}
s = hz / clk_get_freq(clk->parent);
(void)s; /* TODO implement */
assert(hz == 528 * MHZ);
return clk_get_freq(clk);
}
static void
_pll2_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_pll2_init(clk_t* clk)
{
if (clk->parent == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MASTER);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock pll2_clk = { CLK_OPS(PLL2, pll2, NULL) };
/* MMDC_CH0_CLK */
static freq_t
_mmdc_ch0_get_freq(clk_t* clk)
{
return clk_get_freq(clk->parent);
}
static freq_t
_mmdc_ch0_set_freq(clk_t* clk, freq_t hz)
{
/* TODO there is a mux here */
assert(hz == 528 * MHZ);
return clk_set_freq(clk->parent, hz);
}
static void
_mmdc_ch0_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_mmdc_ch0_init(clk_t* clk)
{
if (clk->parent == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_PLL2);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock mmdc_ch0_clk = { CLK_OPS(MMDC_CH0, mmdc_ch0, NULL) };
/* AHB_CLK_ROOT */
static freq_t
_ahb_get_freq(clk_t* clk)
{
return clk_get_freq(clk->parent) / 4;
}
static freq_t
_ahb_set_freq(clk_t* clk, freq_t hz)
{
return clk_set_freq(clk->parent, hz * 4);
}
static void
_ahb_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_ahb_init(clk_t* clk)
{
if (clk->parent == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MMDC_CH0);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock ahb_clk = { CLK_OPS(AHB, ahb, NULL) };
/* IPG_CLK_ROOT */
static freq_t
_ipg_get_freq(clk_t* clk)
{
return clk_get_freq(clk->parent) / 2;
};
static freq_t
_ipg_set_freq(clk_t* clk, freq_t hz)
{
return clk_set_freq(clk->parent, hz * 2);
};
static void
_ipg_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_ipg_init(clk_t* clk)
{
if (clk->parent == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_AHB);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
return clk;
}
static struct clock ipg_clk = { CLK_OPS(IPG, ipg, NULL) };
/* USB_CLK */
static freq_t
_usb_get_freq(clk_t* clk)
{
volatile alg_sct_t* pll_usb;
pll_usb = clk_regs.alg->pll_usb;
if (clk->id == CLK_USB2) {
pll_usb++;
} else if (clk->id != CLK_USB1) {
assert(0);
return 0;
}
if (pll_usb->val & ~PLL_BYPASS) {
if (pll_usb->val & (PLL_USB_ENABLE | PLL_USB_POWER | PLL_EN_USB_CLKS)) {
uint32_t div = (pll_usb->val & PLL_USB_DIV_MASK) ? 22 : 20;
return clk_get_freq(clk->parent) * div;
}
}
/* Not enabled or in bypass mode...
* We should only be in bypass when changing Fout */
return 0;
}
static freq_t
_usb_set_freq(clk_t* clk, freq_t hz UNUSED)
{
assert(!"Not implemented");
return clk_get_freq(clk);
}
static void
_usb_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_usb_init(clk_t* clk)
{
volatile alg_sct_t* pll_usb;
if (clk->parent == NULL) {
clk_t* parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MASTER);
clk_register_child(parent, clk);
clk->priv = (void*)&clk_regs;
}
if (clk_regs.alg == NULL) {
ZF_LOGF("clk_regs.alg is NULL: Clocks likely not initialised properly");
return NULL;
}
/* While we are here, gate the clocks */
pll_usb = clk_regs.alg->pll_usb;
if (clk->id == CLK_USB2) {
pll_usb++;
} else if (clk->id != CLK_USB1) {
assert(0);
return NULL;
}
pll_usb->clr = PLL_BYPASS;
pll_usb->set = PLL_USB_ENABLE | PLL_USB_POWER | PLL_EN_USB_CLKS;
clk_gate_enable(clk_get_clock_sys(clk), usboh3, CLKGATE_ON);
return clk;
}
static struct clock usb1_clk = { CLK_OPS(USB1, usb, NULL) };
static struct clock usb2_clk = { CLK_OPS(USB2, usb, NULL) };
/* clkox */
static freq_t
_clko_get_freq(clk_t* clk)
{
uint32_t fin = clk_get_freq(clk->parent);
uint32_t div;
switch (clk->id) {
case CLK_CLKO1:
div = (clk_regs.ccm->ccosr >> 4) & 0x7;
break;
case CLK_CLKO2:
div = (clk_regs.ccm->ccosr >> 21) & 0x7;
break;
default:
assert(!"Invalid clock");
return -1;
}
return fin / (div + 1);
}
static freq_t
_clko_set_freq(clk_t* clk, freq_t hz)
{
uint32_t fin = clk_get_freq(clk->parent);
uint32_t div = (fin / hz) + 1;
uint32_t v = clk_regs.ccm->ccosr;
if (div > 0x7) {
div = 0x7;
}
switch (clk->id) {
case CLK_CLKO1:
v &= ~(0x7U << 4);
v |= div << 4;
break;
case CLK_CLKO2:
v &= ~(0x7U << 21);
v |= div << 21;
break;
default:
assert(!"Invalid clock");
return -1;
}
clk_regs.ccm->ccosr = v;
return clk_get_freq(clk);
}
static void
_clko_recal(clk_t* clk UNUSED)
{
assert(0);
}
static clk_t*
_clko_init(clk_t* clk)
{
assert(clk_get_clock_sys(clk));
if (clk->parent == NULL) {
/* We currently only support 1 src, but there are many to choose from */
clk_t* parent;
uint32_t v = clk_regs.ccm->ccosr;
switch (clk->id) {
case CLK_CLKO1:
parent = clk_get_clock(clk_get_clock_sys(clk), CLK_IPG);
/* set source */
v &= ~CLKO1_SRC_MASK;
v |= CLKO1_SRC_IPG;
/* Enable */
v |= CLKO1_ENABLE;
/* Output to CCM_CLKO1 output */
v &= ~CLKO_SEL;
break;
case CLK_CLKO2:
/* set source */
parent = clk_get_clock(clk_get_clock_sys(clk), CLK_MMDC_CH0);
v &= ~CLKO2_SRC_MASK;
v |= CLKO2_SRC_MMDC_CH0;
/* Enable */
v |= CLKO2_ENABLE;
break;
default:
assert(!"Invalid clock for operation");
return NULL;
}
clk_regs.ccm->ccosr = v;
clk_register_child(parent, clk);
}
return clk;
}
static struct clock clko1_clk = { CLK_OPS(CLKO1, clko, NULL) };
static struct clock clko2_clk = { CLK_OPS(CLKO2, clko, NULL) };
static int
imx6_gate_enable(clock_sys_t* clock_sys, enum clock_gate gate, enum clock_gate_mode mode)
{
assert(clk_regs.ccm);
assert(mode == CLKGATE_ON);
(void)assert(gate >= 0);
assert(gate < 112);
uint32_t v;
uint32_t reg = gate / 16;
uint32_t shift = (gate & 0xf) * 2;
v = clk_regs.ccm->ccgr[reg];
v &= ~(CLKGATE_MASK << shift);
v |= (CLKGATE_ON_ALL << shift);
clk_regs.ccm->ccgr[reg] = v;
return 0;
}
int
clock_sys_init(ps_io_ops_t* o, clock_sys_t* clock_sys)
{
MAP_IF_NULL(o, CCM , clk_regs.ccm);
MAP_IF_NULL(o, CCM_ANALOG, clk_regs.alg);
clock_sys->priv = (void*)&clk_regs;
clock_sys->get_clock = &ps_get_clock;
clock_sys->gate_enable = &imx6_gate_enable;
return 0;
}
void
clk_print_clock_tree(clock_sys_t* sys)
{
clk_t *clk = clk_get_clock(sys, CLK_MASTER);
clk_print_tree(clk, "");
}
clk_t* ps_clocks[] = {
[CLK_MASTER] = &master_clk,
[CLK_PLL2 ] = &pll2_clk,
[CLK_MMDC_CH0] = &mmdc_ch0_clk,
[CLK_AHB] = &ahb_clk,
[CLK_IPG] = &ipg_clk,
[CLK_ARM] = &arm_clk,
[CLK_ENET] = &enet_clk,
[CLK_USB1] = &usb1_clk,
[CLK_USB2] = &usb2_clk,
[CLK_CLKO1] = &clko1_clk,
[CLK_CLKO2] = &clko2_clk,
};
/* These frequencies are NOT the recommended
* frequencies. They are to be used when we
* need to make assumptions about what u-boot
* has left us with. */
freq_t ps_freq_default[] = {
[CLK_MASTER] = 24 * MHZ,
[CLK_PLL2 ] = 528 * MHZ,
[CLK_MMDC_CH0] = 528 * MHZ,
[CLK_AHB] = 132 * MHZ,
[CLK_IPG] = 66 * MHZ,
[CLK_ARM] = 792 * MHZ,
[CLK_ENET] = 48 * MHZ,
[CLK_USB1] = 480 * MHZ,
[CLK_USB2] = 480 * MHZ,
[CLK_CLKO1] = 66 * MHZ,
[CLK_CLKO2] = 528 * MHZ,
};