powerpc/mpc512x: improve DIU related clock setup

adapt the DIU clock initialization to the COMMON_CLK approach:
device tree based clock lookup, prepare and unprepare for clocks,
work with frequencies not dividers, call the appropriate clk_*()
routines and don't access CCM registers

the "best clock" determination now completely relies on the
platform's clock driver to pick a frequency close to what the
caller requests, and merely checks whether the desired frequency
was met (fits the tolerance of the monitor)

this approach shall succeed upon first try in the usual case,
will test a few less desirable yet acceptable frequencies in
edge cases, and will fallback to "best effort" if none of the
previously tried frequencies pass the test

provide a fallback clock lookup approach in case the OF based clock
lookup for the DIU fails, this allows for successful operation in
the presence of an outdated device tree which lacks clock specs

Cc: Anatolij Gustschin <agust@denx.de>
Cc: linuxppc-dev@lists.ozlabs.org
Signed-off-by: Gerhard Sittig <gsi@denx.de>
Signed-off-by: Anatolij Gustschin <agust@denx.de>
This commit is contained in:
Gerhard Sittig
2013-11-30 23:51:36 +01:00
committed by Anatolij Gustschin
parent 7b19f3bcef
commit ba2181271f

View File

@@ -12,6 +12,7 @@
* (at your option) any later version. * (at your option) any later version.
*/ */
#include <linux/clk.h>
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/io.h> #include <linux/io.h>
#include <linux/irq.h> #include <linux/irq.h>
@@ -68,98 +69,112 @@ struct fsl_diu_shared_fb {
bool in_use; bool in_use;
}; };
#define DIU_DIV_MASK 0x000000ff /* receives a pixel clock spec in pico seconds, adjusts the DIU clock rate */
static void mpc512x_set_pixel_clock(unsigned int pixclock) static void mpc512x_set_pixel_clock(unsigned int pixclock)
{ {
unsigned long bestval, bestfreq, speed, busfreq;
unsigned long minpixclock, maxpixclock, pixval;
struct mpc512x_ccm __iomem *ccm;
struct device_node *np; struct device_node *np;
u32 temp; struct clk *clk_diu;
long err; unsigned long epsilon, minpixclock, maxpixclock;
int i; unsigned long offset, want, got, delta;
np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-clock"); /* lookup and enable the DIU clock */
np = of_find_compatible_node(NULL, NULL, "fsl,mpc5121-diu");
if (!np) { if (!np) {
pr_err("Can't find clock control module.\n"); pr_err("Could not find DIU device tree node.\n");
return; return;
} }
clk_diu = of_clk_get(np, 0);
ccm = of_iomap(np, 0); if (IS_ERR(clk_diu)) {
/* backwards compat with device trees that lack clock specs */
clk_diu = clk_get_sys(np->name, "ipg");
}
of_node_put(np); of_node_put(np);
if (!ccm) { if (IS_ERR(clk_diu)) {
pr_err("Can't map clock control module reg.\n"); pr_err("Could not lookup DIU clock.\n");
return;
}
if (clk_prepare_enable(clk_diu)) {
pr_err("Could not enable DIU clock.\n");
return; return;
} }
np = of_find_node_by_type(NULL, "cpu"); /*
if (np) { * convert the picoseconds spec into the desired clock rate,
const unsigned int *prop = * determine the acceptable clock range for the monitor (+/- 5%),
of_get_property(np, "bus-frequency", NULL); * do the calculation in steps to avoid integer overflow
*/
of_node_put(np); pr_debug("DIU pixclock in ps - %u\n", pixclock);
if (prop) { pixclock = (1000000000 / pixclock) * 1000;
busfreq = *prop;
} else {
pr_err("Can't get bus-frequency property\n");
return;
}
} else {
pr_err("Can't find 'cpu' node.\n");
return;
}
/* Pixel Clock configuration */
pr_debug("DIU: Bus Frequency = %lu\n", busfreq);
speed = busfreq * 4; /* DIU_DIV ratio is 4 * CSB_CLK / DIU_CLK */
/* Calculate the pixel clock with the smallest error */
/* calculate the following in steps to avoid overflow */
pr_debug("DIU pixclock in ps - %d\n", pixclock);
temp = (1000000000 / pixclock) * 1000;
pixclock = temp;
pr_debug("DIU pixclock freq - %u\n", pixclock); pr_debug("DIU pixclock freq - %u\n", pixclock);
epsilon = pixclock / 20; /* pixclock * 0.05 */
temp = temp / 20; /* pixclock * 0.05 */ pr_debug("DIU deviation - %lu\n", epsilon);
pr_debug("deviation = %d\n", temp); minpixclock = pixclock - epsilon;
minpixclock = pixclock - temp; maxpixclock = pixclock + epsilon;
maxpixclock = pixclock + temp;
pr_debug("DIU minpixclock - %lu\n", minpixclock); pr_debug("DIU minpixclock - %lu\n", minpixclock);
pr_debug("DIU maxpixclock - %lu\n", maxpixclock); pr_debug("DIU maxpixclock - %lu\n", maxpixclock);
pixval = speed/pixclock;
pr_debug("DIU pixval = %lu\n", pixval);
err = LONG_MAX; /*
bestval = pixval; * check whether the DIU supports the desired pixel clock
pr_debug("DIU bestval = %lu\n", bestval); *
* - simply request the desired clock and see what the
bestfreq = 0; * platform's clock driver will make of it, assuming that it
for (i = -1; i <= 1; i++) { * will setup the best approximation of the requested value
temp = speed / (pixval+i); * - try other candidate frequencies in the order of decreasing
pr_debug("DIU test pixval i=%d, pixval=%lu, temp freq. = %u\n", * preference (i.e. with increasing distance from the desired
i, pixval, temp); * pixel clock, and checking the lower frequency before the
if ((temp < minpixclock) || (temp > maxpixclock)) * higher frequency to not overload the hardware) until the
pr_debug("DIU exceeds monitor range (%lu to %lu)\n", * first match is found -- any potential subsequent match
minpixclock, maxpixclock); * would only be as good as the former match or typically
else if (abs(temp - pixclock) < err) { * would be less preferrable
pr_debug("Entered the else if block %d\n", i); *
err = abs(temp - pixclock); * the offset increment of pixelclock divided by 64 is an
bestval = pixval + i; * arbitrary choice -- it's simple to calculate, in the typical
bestfreq = temp; * case we expect the first check to succeed already, in the
* worst case seven frequencies get tested (the exact center and
* three more values each to the left and to the right) before
* the 5% tolerance window is exceeded, resulting in fast enough
* execution yet high enough probability of finding a suitable
* value, while the error rate will be in the order of single
* percents
*/
for (offset = 0; offset <= epsilon; offset += pixclock / 64) {
want = pixclock - offset;
pr_debug("DIU checking clock - %lu\n", want);
clk_set_rate(clk_diu, want);
got = clk_get_rate(clk_diu);
delta = abs(pixclock - got);
if (delta < epsilon)
break;
if (!offset)
continue;
want = pixclock + offset;
pr_debug("DIU checking clock - %lu\n", want);
clk_set_rate(clk_diu, want);
got = clk_get_rate(clk_diu);
delta = abs(pixclock - got);
if (delta < epsilon)
break;
} }
if (offset <= epsilon) {
pr_debug("DIU clock accepted - %lu\n", want);
pr_debug("DIU pixclock want %u, got %lu, delta %lu, eps %lu\n",
pixclock, got, delta, epsilon);
return;
} }
pr_warn("DIU pixclock auto search unsuccessful\n");
pr_debug("DIU chose = %lx\n", bestval); /*
pr_debug("DIU error = %ld\n NomPixClk ", err); * what is the most appropriate action to take when the search
pr_debug("DIU: Best Freq = %lx\n", bestfreq); * for an available pixel clock which is acceptable to the
/* Modify DIU_DIV in CCM SCFR1 */ * monitor has failed? disable the DIU (clock) or just provide
temp = in_be32(&ccm->scfr1); * a "best effort"? we go with the latter
pr_debug("DIU: Current value of SCFR1: 0x%08x\n", temp); */
temp &= ~DIU_DIV_MASK; pr_warn("DIU pixclock best effort fallback (backend's choice)\n");
temp |= (bestval & DIU_DIV_MASK); clk_set_rate(clk_diu, pixclock);
out_be32(&ccm->scfr1, temp); got = clk_get_rate(clk_diu);
pr_debug("DIU: Modified value of SCFR1: 0x%08x\n", temp); delta = abs(pixclock - got);
iounmap(ccm); pr_debug("DIU pixclock want %u, got %lu, delta %lu, eps %lu\n",
pixclock, got, delta, epsilon);
} }
static enum fsl_diu_monitor_port static enum fsl_diu_monitor_port