/* * 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 #include "../../arch/arm/clock.h" #include "../../services.h" #include #include #include #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, };