151 lines
4.2 KiB
C
151 lines
4.2 KiB
C
/*
|
|
* (C) Copyright 2010 Juergen Beisert - Pengutronix <kernel@pengutronix.de>
|
|
*
|
|
* 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 <common.h>
|
|
#include <init.h>
|
|
#include <mach/imx-regs.h>
|
|
#include <mach/clock.h>
|
|
#include <io.h>
|
|
|
|
#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();
|
|
}
|