/* $NetBSD: imx6_ccm.c,v 1.5 2016/11/24 03:59:36 hkenken Exp $ */ /* * Copyright (c) 2010-2012, 2014 Genetec Corporation. All rights reserved. * Written by Hashimoto Kenichi for Genetec Corporation. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY GENETEC CORPORATION ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GENETEC CORPORATION * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * Clock Controller Module (CCM) for i.MX6 */ #include __KERNEL_RCSID(0, "$NetBSD: imx6_ccm.c,v 1.5 2016/11/24 03:59:36 hkenken Exp $"); #include "opt_imx.h" #include "opt_imx6clk.h" #include "locators.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct imxccm_softc { device_t sc_dev; bus_space_tag_t sc_iot; bus_space_handle_t sc_ioh; /* for sysctl */ struct sysctllog *sc_log; int sc_sysctlnode_pll1_arm; int sc_sysctlnode_pll2_sys; int sc_sysctlnode_pll3_usb1; int sc_sysctlnode_pll7_usb2; int sc_sysctlnode_pll4_audio; int sc_sysctlnode_pll5_video; int sc_sysctlnode_pll6_enet; /* int sc_sysctlnode_pll8_mlb; */ int sc_sysctlnode_arm; int sc_sysctlnode_periph; int sc_sysctlnode_ahb; int sc_sysctlnode_ipg; int sc_sysctlnode_axi; }; struct imxccm_softc *ccm_softc; static int imxccm_match(device_t, cfdata_t, void *); static void imxccm_attach(device_t, device_t, void *); static int imxccm_sysctl_freq_helper(SYSCTLFN_PROTO); static int imxccm_sysctl_setup(struct imxccm_softc *); CFATTACH_DECL_NEW(imxccm, sizeof(struct imxccm_softc), imxccm_match, imxccm_attach, NULL, NULL); static int imxccm_match(device_t parent, cfdata_t cfdata, void *aux) { struct axi_attach_args *aa = aux; if (ccm_softc != NULL) return 0; if (aa->aa_addr == IMX6_AIPS1_BASE + AIPS1_CCM_BASE) return 1; return 0; } static void imxccm_attach(device_t parent, device_t self, void *aux) { struct imxccm_softc * const sc = device_private(self); struct axi_attach_args *aa = aux; bus_space_tag_t iot = aa->aa_iot; ccm_softc = sc; sc->sc_dev = self; sc->sc_iot = iot; sc->sc_log = NULL; if (bus_space_map(iot, aa->aa_addr, IMX6_CCM_SIZE, 0, &sc->sc_ioh)) { aprint_error(": can't map registers\n"); return; } aprint_normal(": Clock Control Module\n"); aprint_naive("\n"); imxccm_sysctl_setup(sc); aprint_verbose_dev(self, "PLL_ARM clock=%d\n", imx6_get_clock(IMX6CLK_PLL1)); aprint_verbose_dev(self, "PLL_SYS clock=%d\n", imx6_get_clock(IMX6CLK_PLL2)); aprint_verbose_dev(self, "PLL_USB1 clock=%d\n", imx6_get_clock(IMX6CLK_PLL3)); aprint_verbose_dev(self, "PLL_USB2 clock=%d\n", imx6_get_clock(IMX6CLK_PLL7)); aprint_verbose_dev(self, "PLL_AUDIO clock=%d\n", imx6_get_clock(IMX6CLK_PLL4)); aprint_verbose_dev(self, "PLL_VIDEO clock=%d\n", imx6_get_clock(IMX6CLK_PLL5)); aprint_verbose_dev(self, "PLL_ENET clock=%d\n", imx6_get_clock(IMX6CLK_PLL6)); aprint_verbose_dev(self, "PLL_MLB clock=%d\n", imx6_get_clock(IMX6CLK_PLL7)); aprint_verbose_dev(self, "IMX6CLK_PLL2_PFD0=%d\n", imx6_get_clock(IMX6CLK_PLL2_PFD0)); aprint_verbose_dev(self, "IMX6CLK_PLL2_PFD1=%d\n", imx6_get_clock(IMX6CLK_PLL2_PFD1)); aprint_verbose_dev(self, "IMX6CLK_PLL2_PFD2=%d\n", imx6_get_clock(IMX6CLK_PLL2_PFD2)); aprint_verbose_dev(self, "IMX6CLK_PLL3_PFD0=%d\n", imx6_get_clock(IMX6CLK_PLL3_PFD0)); aprint_verbose_dev(self, "IMX6CLK_PLL3_PFD1=%d\n", imx6_get_clock(IMX6CLK_PLL3_PFD1)); aprint_verbose_dev(self, "IMX6CLK_PLL3_PFD2=%d\n", imx6_get_clock(IMX6CLK_PLL3_PFD2)); aprint_verbose_dev(self, "IMX6CLK_PLL3_PFD3=%d\n", imx6_get_clock(IMX6CLK_PLL3_PFD3)); aprint_verbose_dev(self, "IMX6CLK_ARM_ROOT=%d\n", imx6_get_clock(IMX6CLK_ARM_ROOT)); aprint_verbose_dev(self, "IMX6CLK_PERIPH=%d\n", imx6_get_clock(IMX6CLK_PERIPH)); aprint_verbose_dev(self, "IMX6CLK_AHB=%d\n", imx6_get_clock(IMX6CLK_AHB)); aprint_verbose_dev(self, "IMX6CLK_IPG=%d\n", imx6_get_clock(IMX6CLK_IPG)); aprint_verbose_dev(self, "IMX6CLK_AXI=%d\n", imx6_get_clock(IMX6CLK_AXI)); aprint_verbose_dev(self, "IMX6CLK_USDHC1=%d\n", imx6_get_clock(IMX6CLK_USDHC1)); aprint_verbose_dev(self, "IMX6CLK_USDHC2=%d\n", imx6_get_clock(IMX6CLK_USDHC2)); aprint_verbose_dev(self, "IMX6CLK_USDHC3=%d\n", imx6_get_clock(IMX6CLK_USDHC3)); aprint_verbose_dev(self, "IMX6CLK_USDHC4=%d\n", imx6_get_clock(IMX6CLK_USDHC4)); } static int imxccm_sysctl_setup(struct imxccm_softc *sc) { const struct sysctlnode *node, *imxnode, *freqnode, *pllnode; int rv; rv = sysctl_createv(&sc->sc_log, 0, NULL, &node, CTLFLAG_PERMANENT, CTLTYPE_NODE, "machdep", NULL, NULL, 0, NULL, 0, CTL_MACHDEP, CTL_EOL); if (rv != 0) goto fail; rv = sysctl_createv(&sc->sc_log, 0, &node, &imxnode, 0, CTLTYPE_NODE, "imx6", NULL, NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; rv = sysctl_createv(&sc->sc_log, 0, &imxnode, &freqnode, 0, CTLTYPE_NODE, "frequency", NULL, NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &pllnode, 0, CTLTYPE_NODE, "pll", NULL, NULL, 0, NULL, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, CTLFLAG_READWRITE, CTLTYPE_INT, "arm", SYSCTL_DESCR("frequency of ARM clock (PLL1)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll1_arm= node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "system", SYSCTL_DESCR("frequency of system clock (PLL2)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll2_sys = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "usb1", SYSCTL_DESCR("frequency of USB1 clock (PLL3)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll3_usb1 = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "usb2", SYSCTL_DESCR("frequency of USB2 clock (PLL7)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll7_usb2 = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "audio", SYSCTL_DESCR("frequency of AUDIO clock (PLL4)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll4_audio = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "video", SYSCTL_DESCR("frequency of VIDEO clock (PLL5)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll5_video = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "enet", SYSCTL_DESCR("frequency of ENET clock (PLL6)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll6_enet = node->sysctl_num; #if 0 rv = sysctl_createv(&sc->sc_log, 0, &pllnode, &node, 0, CTLTYPE_INT, "mlb", SYSCTL_DESCR("frequency of MediaLinkBus clock (PLL8)"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_pll8_mlb = node->sysctl_num; #endif rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node, CTLFLAG_READWRITE, CTLTYPE_INT, "arm", SYSCTL_DESCR("frequency of ARM Root clock"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_arm = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node, 0, CTLTYPE_INT, "peripheral", SYSCTL_DESCR("current frequency of Peripheral clock"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_periph = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node, 0, CTLTYPE_INT, "ahb", SYSCTL_DESCR("current frequency of AHB clock"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_ahb = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node, 0, CTLTYPE_INT, "ipg", SYSCTL_DESCR("current frequency of IPG clock"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_ipg = node->sysctl_num; rv = sysctl_createv(&sc->sc_log, 0, &freqnode, &node, 0, CTLTYPE_INT, "axi", SYSCTL_DESCR("current frequency of AXI clock"), imxccm_sysctl_freq_helper, 0, (void *)sc, 0, CTL_CREATE, CTL_EOL); if (rv != 0) goto fail; sc->sc_sysctlnode_axi = node->sysctl_num; return 0; fail: aprint_error_dev(sc->sc_dev, "cannot initialize sysctl (err=%d)\n", rv); sysctl_teardown(&sc->sc_log); sc->sc_log = NULL; return -1; } static int imxccm_sysctl_freq_helper(SYSCTLFN_ARGS) { struct sysctlnode node; struct imxccm_softc *sc; int value, ovalue, err; node = *rnode; sc = node.sysctl_data; /* for sysctl read */ if (rnode->sysctl_num == sc->sc_sysctlnode_pll1_arm) value = imx6_get_clock(IMX6CLK_PLL1); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll2_sys) value = imx6_get_clock(IMX6CLK_PLL2); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll3_usb1) value = imx6_get_clock(IMX6CLK_PLL3); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll7_usb2) value = imx6_get_clock(IMX6CLK_PLL7); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll4_audio) value = imx6_get_clock(IMX6CLK_PLL4); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll5_video) value = imx6_get_clock(IMX6CLK_PLL5); else if (rnode->sysctl_num == sc->sc_sysctlnode_pll6_enet) value = imx6_get_clock(IMX6CLK_PLL6); #if 0 else if (rnode->sysctl_num == sc->sc_sysctlnode_pll8_mlb) value = imx6_get_clock(IMX6CLK_PLL8); #endif else if (rnode->sysctl_num == sc->sc_sysctlnode_arm) value = imx6_get_clock(IMX6CLK_ARM_ROOT); else if (rnode->sysctl_num == sc->sc_sysctlnode_periph) value = imx6_get_clock(IMX6CLK_PERIPH); else if (rnode->sysctl_num == sc->sc_sysctlnode_ipg) value = imx6_get_clock(IMX6CLK_IPG); else if (rnode->sysctl_num == sc->sc_sysctlnode_axi) value = imx6_get_clock(IMX6CLK_AXI); else return EOPNOTSUPP; #ifdef SYSCTL_BY_MHZ value /= 1000 * 1000; /* Hz -> MHz */ #endif ovalue = value; node.sysctl_data = &value; err = sysctl_lookup(SYSCTLFN_CALL(&node)); if (err != 0 || newp == NULL) return err; /* for sysctl write */ if (value == ovalue) return 0; #ifdef SYSCTL_BY_MHZ value *= 1000 * 1000; /* MHz -> Hz */ #endif if (rnode->sysctl_num == sc->sc_sysctlnode_arm) return imx6_set_clock(IMX6CLK_ARM_ROOT, value); return 0; } uint32_t imx6_ccm_read(uint32_t reg) { if (ccm_softc == NULL) return 0; return bus_space_read_4(ccm_softc->sc_iot, ccm_softc->sc_ioh, reg); } void imx6_ccm_write(uint32_t reg, uint32_t val) { if (ccm_softc == NULL) return; bus_space_write_4(ccm_softc->sc_iot, ccm_softc->sc_ioh, reg, val); } int imx6_set_clock(enum imx6_clock clk, uint32_t freq) { uint32_t v; if (ccm_softc == NULL) return 0; switch (clk) { case IMX6CLK_ARM_ROOT: { uint32_t pll; int cacrr; for (cacrr = 7; cacrr >= 0; cacrr--) { pll = (uint64_t)freq * (cacrr + 1) * 2 / IMX6_OSC_FREQ; if (pll >= 54 && pll <= 108) { v = imx6_ccm_read(CCM_CACRR); v &= ~CCM_CACRR_ARM_PODF; imx6_ccm_write(CCM_CACRR, v | __SHIFTIN(cacrr, CCM_CACRR_ARM_PODF)); v = imx6_ccm_read(CCM_ANALOG_PLL_ARM); v &= ~CCM_ANALOG_PLL_ARM_DIV_SELECT; imx6_ccm_write(CCM_ANALOG_PLL_ARM, v | __SHIFTIN(pll, CCM_ANALOG_PLL_ARM_DIV_SELECT)); v = imx6_get_clock(IMX6CLK_ARM_ROOT); cpufreq_set_all(v); a9tmr_update_freq(v / IMX6_PERIPHCLK_N); return 0; } } return EINVAL; } break; default: aprint_error_dev(ccm_softc->sc_dev, "clock %d: not supported yet\n", clk); return EINVAL; } return 0; } uint32_t imx6_get_clock(enum imx6_clock clk) { uint32_t d, denom, num, sel, v; uint64_t freq; if (ccm_softc == NULL) return 0; switch (clk) { /* CLOCK SWITCHER */ case IMX6CLK_PLL1: v = imx6_ccm_read(CCM_ANALOG_PLL_ARM); freq = IMX6_OSC_FREQ * (v & CCM_ANALOG_PLL_ARM_DIV_SELECT) / 2; break; case IMX6CLK_PLL2: v = imx6_ccm_read(CCM_ANALOG_PLL_SYS); freq = IMX6_OSC_FREQ * ((v & CCM_ANALOG_PLL_SYS_DIV_SELECT) ? 22 : 20); break; case IMX6CLK_PLL3: v = imx6_ccm_read(CCM_ANALOG_PLL_USB1); freq = IMX6_OSC_FREQ * ((v & CCM_ANALOG_PLL_USB1_DIV_SELECT) ? 22 : 20); break; case IMX6CLK_PLL4: v = imx6_ccm_read(CCM_ANALOG_PLL_AUDIO); d = __SHIFTOUT(v, CCM_ANALOG_PLL_AUDIO_DIV_SELECT); num = imx6_ccm_read(CCM_ANALOG_PLL_AUDIO_NUM); denom = imx6_ccm_read(CCM_ANALOG_PLL_AUDIO_DENOM); freq = (uint64_t)IMX6_OSC_FREQ * (d + num / denom); d = __SHIFTOUT(v, CCM_ANALOG_PLL_AUDIO_POST_DIV_SELECT); freq = freq >> (2 - d); break; case IMX6CLK_PLL5: v = imx6_ccm_read(CCM_ANALOG_PLL_VIDEO); d = __SHIFTOUT(v, CCM_ANALOG_PLL_VIDEO_DIV_SELECT); num = imx6_ccm_read(CCM_ANALOG_PLL_VIDEO_NUM); denom = imx6_ccm_read(CCM_ANALOG_PLL_VIDEO_DENOM); freq = (uint64_t)IMX6_OSC_FREQ * (d + num / denom); d = __SHIFTOUT(v, CCM_ANALOG_PLL_VIDEO_POST_DIV_SELECT); freq = freq >> (2 - d); break; case IMX6CLK_PLL6: v = imx6_ccm_read(CCM_ANALOG_PLL_ENET); switch (v & CCM_ANALOG_PLL_ENET_DIV_SELECT_MASK) { case 0: freq = 25 * 1000 * 1000; break; case 1: freq = 50 * 1000 * 1000; break; case 2: freq = 100 * 1000 * 1000; break; case 3: freq = 125 * 1000 * 1000; break; } break; case IMX6CLK_PLL7: v = imx6_ccm_read(CCM_ANALOG_PLL_USB2); freq = IMX6_OSC_FREQ * ((v & CCM_ANALOG_PLL_USBn_DIV_SELECT(1)) ? 22 : 20); break; #if 0 case IMX6CLK_PLL8: /* XXX notyet */ break; #endif case IMX6CLK_PLL2_PFD0: freq = imx6_get_clock(IMX6CLK_PLL2); v = imx6_ccm_read(CCM_ANALOG_PFD_528); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_528_PFD0_FRAC); break; case IMX6CLK_PLL2_PFD1: freq = imx6_get_clock(IMX6CLK_PLL2); v = imx6_ccm_read(CCM_ANALOG_PFD_528); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_528_PFD1_FRAC); break; case IMX6CLK_PLL2_PFD2: freq = imx6_get_clock(IMX6CLK_PLL2); v = imx6_ccm_read(CCM_ANALOG_PFD_528); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_528_PFD2_FRAC); break; case IMX6CLK_PLL3_PFD3: freq = imx6_get_clock(IMX6CLK_PLL3); v = imx6_ccm_read(CCM_ANALOG_PFD_480); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480_PFD3_FRAC); break; case IMX6CLK_PLL3_PFD2: freq = imx6_get_clock(IMX6CLK_PLL3); v = imx6_ccm_read(CCM_ANALOG_PFD_480); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480_PFD2_FRAC); break; case IMX6CLK_PLL3_PFD1: freq = imx6_get_clock(IMX6CLK_PLL3); v = imx6_ccm_read(CCM_ANALOG_PFD_480); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480_PFD1_FRAC); break; case IMX6CLK_PLL3_PFD0: freq = imx6_get_clock(IMX6CLK_PLL3); v = imx6_ccm_read(CCM_ANALOG_PFD_480); freq = freq * 18 / __SHIFTOUT(v, CCM_ANALOG_PFD_480_PFD0_FRAC); break; /* CLOCK ROOT GEN */ case IMX6CLK_ARM_ROOT: freq = imx6_get_clock(IMX6CLK_PLL1); v = __SHIFTOUT(imx6_ccm_read(CCM_CACRR), CCM_CACRR_ARM_PODF); freq = freq / (v + 1); break; case IMX6CLK_PERIPH: v = imx6_ccm_read(CCM_CBCDR); if (v & CCM_CBCDR_PERIPH_CLK_SEL) { v = imx6_ccm_read(CCM_CBCMR); sel = __SHIFTOUT(v, CCM_CBCMR_PERIPH_CLK2_SEL); switch (sel) { case 0: freq = imx6_get_clock(IMX6CLK_PLL3); break; case 1: case 2: freq = IMX6_OSC_FREQ; break; case 3: freq = 0; aprint_error_dev(ccm_softc->sc_dev, "IMX6CLK_PERIPH: CCM_CBCMR:CCM_CBCMR_PERIPH_CLK2_SEL is set reserved value\n"); break; } } else { v = imx6_ccm_read(CCM_CBCMR); sel = __SHIFTOUT(v, CCM_CBCMR_PRE_PERIPH_CLK_SEL); switch (sel) { case 0: freq = imx6_get_clock(IMX6CLK_PLL2); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL2_PFD0); break; case 3: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2) / 2; break; } } break; case IMX6CLK_AHB: freq = imx6_get_clock(IMX6CLK_PERIPH); v = imx6_ccm_read(CCM_CBCDR); freq = freq / (__SHIFTOUT(v, CCM_CBCDR_AHB_PODF) + 1); break; case IMX6CLK_IPG: freq = imx6_get_clock(IMX6CLK_AHB); v = imx6_ccm_read(CCM_CBCDR); freq = freq / (__SHIFTOUT(v, CCM_CBCDR_IPG_PODF) + 1); break; case IMX6CLK_AXI: v = imx6_ccm_read(CCM_CBCDR); if (v & CCM_CBCDR_AXI_SEL) { if (v & CCM_CBCDR_AXI_ALT_SEL) { freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); } else { freq = imx6_get_clock(IMX6CLK_PLL3_PFD1); } } else { freq = imx6_get_clock(IMX6CLK_PERIPH); freq = freq / (__SHIFTOUT(v, CCM_CBCDR_AXI_PODF) + 1); } break; case IMX6CLK_USDHC1: v = imx6_ccm_read(CCM_CSCMR1); freq = imx6_get_clock((v & CCM_CSCMR1_USDHC1_CLK_SEL) ? IMX6CLK_PLL2_PFD0 : IMX6CLK_PLL2_PFD2); v = imx6_ccm_read(CCM_CSCDR1); freq = freq / (__SHIFTOUT(v, CCM_CSCDR1_USDHC1_PODF) + 1); break; case IMX6CLK_USDHC2: v = imx6_ccm_read(CCM_CSCMR1); freq = imx6_get_clock((v & CCM_CSCMR1_USDHC2_CLK_SEL) ? IMX6CLK_PLL2_PFD0 : IMX6CLK_PLL2_PFD2); v = imx6_ccm_read(CCM_CSCDR1); freq = freq / (__SHIFTOUT(v, CCM_CSCDR1_USDHC2_PODF) + 1); break; case IMX6CLK_USDHC3: v = imx6_ccm_read(CCM_CSCMR1); freq = imx6_get_clock((v & CCM_CSCMR1_USDHC3_CLK_SEL) ? IMX6CLK_PLL2_PFD0 : IMX6CLK_PLL2_PFD2); v = imx6_ccm_read(CCM_CSCDR1); freq = freq / (__SHIFTOUT(v, CCM_CSCDR1_USDHC3_PODF) + 1); break; case IMX6CLK_USDHC4: v = imx6_ccm_read(CCM_CSCMR1); freq = imx6_get_clock((v & CCM_CSCMR1_USDHC4_CLK_SEL) ? IMX6CLK_PLL2_PFD0 : IMX6CLK_PLL2_PFD2); v = imx6_ccm_read(CCM_CSCDR1); freq = freq / (__SHIFTOUT(v, CCM_CSCDR1_USDHC4_PODF) + 1); break; case IMX6CLK_PERCLK: freq = imx6_get_clock(IMX6CLK_IPG); v = imx6_ccm_read(CCM_CSCMR1); freq = freq / (__SHIFTOUT(v, CCM_CSCMR1_PERCLK_PODF) + 1); break; case IMX6CLK_MMDC_CH1_CLK_ROOT: freq = imx6_get_clock(IMX6CLK_MMDC_CH1); v = __SHIFTOUT(imx6_ccm_read(CCM_CBCDR), CCM_CBCDR_MMDC_CH1_AXI_PODF); freq = freq / (v + 1); break; case IMX6CLK_MMDC_CH0_CLK_ROOT: freq = imx6_get_clock(IMX6CLK_MMDC_CH0); v = __SHIFTOUT(imx6_ccm_read(CCM_CBCDR), CCM_CBCDR_MMDC_CH0_AXI_PODF); freq = freq / (v + 1); break; case IMX6CLK_MMDC_CH0: case IMX6CLK_MMDC_CH1: v = imx6_ccm_read(CCM_CBCMR); sel = (clk == IMX6CLK_MMDC_CH0) ? __SHIFTOUT(v, CCM_CBCMR_PRE_PERIPH_CLK_SEL) : __SHIFTOUT(v, CCM_CBCMR_PRE_PERIPH2_CLK_SEL); switch (sel) { case 0: freq = imx6_get_clock(IMX6CLK_PLL2); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL2_PFD0); break; case 3: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2) / 2; break; } break; case IMX6CLK_IPU1_HSP_CLK_ROOT: v = imx6_ccm_read(CCM_CSCDR3); switch (__SHIFTOUT(v, CCM_CSCDR3_IPU1_HSP_CLK_SEL)) { case 0: freq = imx6_get_clock(IMX6CLK_MMDC_CH0); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL3) / 4; break; case 3: freq = imx6_get_clock(IMX6CLK_PLL3_PFD1); break; } v = __SHIFTOUT(v, CCM_CSCDR3_IPU1_HSP_CLK_SEL); freq = freq / (v + 1); break; case IMX6CLK_IPU2_HSP_CLK_ROOT: v = imx6_ccm_read(CCM_CSCDR3); switch (__SHIFTOUT(v, CCM_CSCDR3_IPU2_HSP_CLK_SEL)) { case 0: freq = imx6_get_clock(IMX6CLK_MMDC_CH0); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL3) / 4; break; case 3: freq = imx6_get_clock(IMX6CLK_PLL3_PFD1); break; } v = __SHIFTOUT(v, CCM_CSCDR3_IPU2_HSP_CLK_SEL); freq = freq / (v + 1); break; case IMX6CLK_IPU1_DI0_CLK_ROOT: case IMX6CLK_IPU1_DI1_CLK_ROOT: v = imx6_ccm_read(CCM_CHSCCDR); sel = (clk == IMX6CLK_IPU1_DI0_CLK_ROOT) ? __SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI0_CLK_SEL) : __SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI1_CLK_SEL); switch (sel) { case 0: sel = (clk == IMX6CLK_IPU1_DI0_CLK_ROOT) ? __SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI0_PRE_CLK_SEL) : __SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI1_PRE_CLK_SEL); switch (sel) { case 0: freq = imx6_get_clock(IMX6CLK_MMDC_CH0); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL3); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL5); break; case 3: freq = imx6_get_clock(IMX6CLK_PLL2_PFD0); break; case 4: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 5: freq = imx6_get_clock(IMX6CLK_PLL3_PFD1); break; default: /* reserved */ freq = 0; } if (clk == IMX6CLK_IPU1_DI0_CLK_ROOT) freq = freq / (__SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI0_PODF) + 1); else freq = freq / (__SHIFTOUT(v, CCM_CHSCCDR_IPU1_DI1_PODF) + 1); break; case 1: case 2: /* IPP_DI[01]_CLK is an external clock */ freq = 0; break; case 3: freq = imx6_get_clock(IMX6CLK_LDB_DI0_IPU); break; case 4: freq = imx6_get_clock(IMX6CLK_LDB_DI1_IPU); break; default: /* reserved */ freq = 0; } break; case IMX6CLK_LDB_DI0_SERIAL_CLK_ROOT: case IMX6CLK_LDB_DI1_SERIAL_CLK_ROOT: v = imx6_ccm_read(CCM_CS2CDR); sel = (clk == IMX6CLK_LDB_DI0_SERIAL_CLK_ROOT) ? __SHIFTOUT(v, CCM_CS2CDR_LDB_DI0_CLK_SEL) : __SHIFTOUT(v, CCM_CS2CDR_LDB_DI1_CLK_SEL); switch (sel) { case 0: freq = imx6_get_clock(IMX6CLK_PLL5); break; case 1: freq = imx6_get_clock(IMX6CLK_PLL2_PFD0); break; case 2: freq = imx6_get_clock(IMX6CLK_PLL2_PFD2); break; case 3: freq = imx6_get_clock(IMX6CLK_MMDC_CH1); break; case 4: freq = imx6_get_clock(IMX6CLK_PLL3); break; case 5: default: /* reserved */ freq = 0; } break; case IMX6CLK_LDB_DI0_IPU: freq = imx6_get_clock(IMX6CLK_LDB_DI0_SERIAL_CLK_ROOT); v = imx6_ccm_read(CCM_CSCMR2); if (!ISSET(v, CCM_CSCMR2_LDB_DI0_IPU_DIV)) freq *= 2; freq /= 7; break; case IMX6CLK_LDB_DI1_IPU: freq = imx6_get_clock(IMX6CLK_LDB_DI1_SERIAL_CLK_ROOT); v = imx6_ccm_read(CCM_CSCMR2); if (!ISSET(v, CCM_CSCMR2_LDB_DI1_IPU_DIV)) freq *= 2; freq /= 7; break; default: aprint_error_dev(ccm_softc->sc_dev, "clock %d: not supported yet\n", clk); return 0; } return freq; } int imx6_pll_power(uint32_t pllreg, int on, uint32_t en) { uint32_t v; int timeout; switch (pllreg) { case CCM_ANALOG_PLL_USB1: case CCM_ANALOG_PLL_USB2: v = imx6_ccm_read(pllreg); if (on) { v |= en; v &= ~CCM_ANALOG_PLL_USBn_BYPASS; } else { v &= ~en; } imx6_ccm_write(pllreg, v); return 0; case CCM_ANALOG_PLL_ENET: v = imx6_ccm_read(pllreg); if (on) v &= ~CCM_ANALOG_PLL_ENET_POWERDOWN; else v |= CCM_ANALOG_PLL_ENET_POWERDOWN; imx6_ccm_write(pllreg, v); for (timeout = 100000; timeout > 0; timeout--) { if (imx6_ccm_read(pllreg) & CCM_ANALOG_PLL_ENET_LOCK) break; } if (timeout <= 0) break; v |= CCM_ANALOG_PLL_ENET_ENABLE; if (on) { v &= ~CCM_ANALOG_PLL_ENET_BYPASS; imx6_ccm_write(pllreg, v); v |= en; } else { v &= ~en; } imx6_ccm_write(pllreg, v); return 0; case CCM_ANALOG_PLL_ARM: case CCM_ANALOG_PLL_SYS: case CCM_ANALOG_PLL_AUDIO: case CCM_ANALOG_PLL_VIDEO: case CCM_ANALOG_PLL_MLB: /* notyet */ default: break; } return -1; }