Commit ba218127 authored by Gerhard Sittig's avatar Gerhard Sittig Committed by Anatolij Gustschin

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: default avatarGerhard Sittig <gsi@denx.de>
Signed-off-by: default avatarAnatolij Gustschin <agust@denx.de>
parent 7b19f3bc
...@@ -12,6 +12,7 @@ ...@@ -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 { ...@@ -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; return;
} }
if (clk_prepare_enable(clk_diu)) {
np = of_find_node_by_type(NULL, "cpu"); pr_err("Could not enable DIU clock.\n");
if (np) {
const unsigned int *prop =
of_get_property(np, "bus-frequency", NULL);
of_node_put(np);
if (prop) {
busfreq = *prop;
} else {
pr_err("Can't get bus-frequency property\n");
return;
}
} else {
pr_err("Can't find 'cpu' node.\n");
return; return;
} }
/* Pixel Clock configuration */ /*
pr_debug("DIU: Bus Frequency = %lu\n", busfreq); * convert the picoseconds spec into the desired clock rate,
speed = busfreq * 4; /* DIU_DIV ratio is 4 * CSB_CLK / DIU_CLK */ * determine the acceptable clock range for the monitor (+/- 5%),
* do the calculation in steps to avoid integer overflow
/* Calculate the pixel clock with the smallest error */ */
/* calculate the following in steps to avoid overflow */ pr_debug("DIU pixclock in ps - %u\n", pixclock);
pr_debug("DIU pixclock in ps - %d\n", pixclock); pixclock = (1000000000 / pixclock) * 1000;
temp = (1000000000 / pixclock) * 1000; pr_debug("DIU pixclock freq - %u\n", pixclock);
pixclock = temp; epsilon = pixclock / 20; /* pixclock * 0.05 */
pr_debug("DIU pixclock freq - %u\n", pixclock); pr_debug("DIU deviation - %lu\n", epsilon);
minpixclock = pixclock - epsilon;
temp = temp / 20; /* pixclock * 0.05 */ maxpixclock = pixclock + epsilon;
pr_debug("deviation = %d\n", temp); pr_debug("DIU minpixclock - %lu\n", minpixclock);
minpixclock = pixclock - temp; pr_debug("DIU maxpixclock - %lu\n", maxpixclock);
maxpixclock = pixclock + temp;
pr_debug("DIU minpixclock - %lu\n", minpixclock); /*
pr_debug("DIU maxpixclock - %lu\n", maxpixclock); * check whether the DIU supports the desired pixel clock
pixval = speed/pixclock; *
pr_debug("DIU pixval = %lu\n", pixval); * - simply request the desired clock and see what the
* platform's clock driver will make of it, assuming that it
err = LONG_MAX; * will setup the best approximation of the requested value
bestval = pixval; * - try other candidate frequencies in the order of decreasing
pr_debug("DIU bestval = %lu\n", bestval); * preference (i.e. with increasing distance from the desired
* pixel clock, and checking the lower frequency before the
bestfreq = 0; * higher frequency to not overload the hardware) until the
for (i = -1; i <= 1; i++) { * first match is found -- any potential subsequent match
temp = speed / (pixval+i); * would only be as good as the former match or typically
pr_debug("DIU test pixval i=%d, pixval=%lu, temp freq. = %u\n", * would be less preferrable
i, pixval, temp); *
if ((temp < minpixclock) || (temp > maxpixclock)) * the offset increment of pixelclock divided by 64 is an
pr_debug("DIU exceeds monitor range (%lu to %lu)\n", * arbitrary choice -- it's simple to calculate, in the typical
minpixclock, maxpixclock); * case we expect the first check to succeed already, in the
else if (abs(temp - pixclock) < err) { * worst case seven frequencies get tested (the exact center and
pr_debug("Entered the else if block %d\n", i); * three more values each to the left and to the right) before
err = abs(temp - pixclock); * the 5% tolerance window is exceeded, resulting in fast enough
bestval = pixval + i; * execution yet high enough probability of finding a suitable
bestfreq = temp; * 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
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment