/* * (C) Copyright 2010 Juergen Beisert - Pengutronix * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #ifdef CONFIG_ARCH_IMX23 # define HW_CLKCTRL_DIS_LCDIF 0x060 # define CLKCTRL_DIS_LCDIF_GATE (1 << 31) # define CLKCTRL_DIS_LCDIF_BUSY (1 << 29) # define MASK_DIS_LCDIF_DIV 0xfff # define SET_DIS_LCDIF_DIV(x) ((x) & MASK_DIS_LCDIF_DIV) # define GET_DIS_LCDIF_DIV(x) ((x) & MASK_DIS_LCDIF_DIV) # define HW_CLKCTRL_FRAC 0xf0 # define MASK_PIXFRAC 0x3f # define GET_PIXFRAC(x) (((x) >> 16) & MASK_PIXFRAC) # define SET_PIXFRAC(x) (((x) & MASK_PIXFRAC) << 16) # define CLKCTRL_FRAC_CLKGATEPIX (1 << 23) # define HW_CLKCTRL_CLKSEQ 0x110 # define CLKCTRL_CLKSEQ_BYPASS_DIS_LCDIF (1 << 1) #endif #ifdef CONFIG_ARCH_IMX28 # define HW_CLKCTRL_DIS_LCDIF 0x120 # define CLKCTRL_DIS_LCDIF_GATE (1 << 31) # define CLKCTRL_DIS_LCDIF_BUSY (1 << 29) # define MASK_DIS_LCDIF_DIV 0x1fff # define SET_DIS_LCDIF_DIV(x) ((x) & MASK_DIS_LCDIF_DIV) # define GET_DIS_LCDIF_DIV(x) ((x) & MASK_DIS_LCDIF_DIV) /* note: On i.MX28 this is called 'FRAC1' */ # define HW_CLKCTRL_FRAC 0x1c0 # define MASK_PIXFRAC 0x3f # define GET_PIXFRAC(x) ((x) & MASK_PIXFRAC) # define SET_PIXFRAC(x) ((x) & MASK_PIXFRAC) # define CLKCTRL_FRAC_CLKGATEPIX (1 << 7) # define HW_CLKCTRL_CLKSEQ 0x1d0 # define CLKCTRL_CLKSEQ_BYPASS_DIS_LCDIF (1 << 14) #endif unsigned imx_get_lcdifclk(void) { unsigned rate = (imx_get_mpllclk() / 1000) * 18U; unsigned div; div = GET_PIXFRAC(readl(IMX_CCM_BASE + HW_CLKCTRL_FRAC)); if (div != 0U) { rate /= div; div = GET_DIS_LCDIF_DIV(readl(IMX_CCM_BASE + HW_CLKCTRL_DIS_LCDIF)); if (div != 0U) rate /= div; else pr_debug("LCDIF clock has divisor 0!\n"); } else pr_debug("LCDIF clock has frac divisor 0!\n"); return rate * 1000; } /* * The source of the pixel clock can be the external 24 MHz crystal or the * internal PLL running at 480 MHz. In order to support at least VGA sized * displays/resolutions this routine forces the PLL as the clock source. */ unsigned imx_set_lcdifclk(unsigned nc) { unsigned frac, best_frac = 0, div, best_div = 0, result; int delta, best_delta = 0xffffff; unsigned i, parent_rate = imx_get_mpllclk() / 1000; uint32_t reg; #define SH_DIV(NOM, DEN, LSH) ((((NOM) / (DEN)) << (LSH)) + \ DIV_ROUND_CLOSEST(((NOM) % (DEN)) << (LSH), DEN)) #define SHIFT 4 nc /= 1000; nc <<= SHIFT; for (frac = 18; frac <= 35; ++frac) { for (div = 1; div <= 255; ++div) { result = DIV_ROUND_CLOSEST(parent_rate * SH_DIV(18U, frac, SHIFT), div); delta = nc - result; if (abs(delta) < abs(best_delta)) { best_delta = delta; best_frac = frac; best_div = div; } } } if (best_delta == 0xffffff) { pr_debug("Unable to match the pixelclock\n"); return 0; } pr_debug("Programming PFD=%u,DIV=%u ref_pix=%u MHz PIXCLK=%u kHz\n", best_frac, best_div, 480 * 18 / best_frac, 480000 * 18 / best_frac / best_div); reg = readl(IMX_CCM_BASE + HW_CLKCTRL_FRAC); reg &= ~SET_PIXFRAC(MASK_PIXFRAC); reg |= SET_PIXFRAC(best_frac); writel(reg, IMX_CCM_BASE + HW_CLKCTRL_FRAC); writel(reg & ~CLKCTRL_FRAC_CLKGATEPIX, IMX_CCM_BASE + HW_CLKCTRL_FRAC); reg = readl(IMX_CCM_BASE + HW_CLKCTRL_DIS_LCDIF) & ~MASK_DIS_LCDIF_DIV; reg &= ~CLKCTRL_DIS_LCDIF_GATE; reg |= SET_DIS_LCDIF_DIV(best_div); writel(reg, IMX_CCM_BASE + HW_CLKCTRL_DIS_LCDIF); /* Wait for divider update */ for (i = 0; i < 10000; i++) { if (!(readl(IMX_CCM_BASE + HW_CLKCTRL_DIS_LCDIF) & CLKCTRL_DIS_LCDIF_BUSY)) break; } if (i >= 10000) { pr_debug("Setting LCD clock failed\n"); return 0; } writel(CLKCTRL_CLKSEQ_BYPASS_DIS_LCDIF, IMX_CCM_BASE + HW_CLKCTRL_CLKSEQ + BIT_CLR); return imx_get_lcdifclk(); }