imx-drm: ipu-v3: more clocking fixes

There's no point in using the clk API for this; we end up having to
violate the layering this provides.

Acked-by: Philipp Zabel <p.zabel@pengutronix.de>
Acked-by: Shawn Guo <shawn.guo@linaro.org>
Reviewed-by: Fabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
This commit is contained in:
Russell King 2013-10-20 15:36:35 +01:00
parent 370b181516
commit 0721feeee0

View File

@ -19,9 +19,6 @@
#include <linux/io.h>
#include <linux/err.h>
#include <linux/platform_device.h>
#include <linux/clk.h>
#include <linux/clk-provider.h>
#include <linux/clkdev.h>
#include "imx-ipu-v3.h"
#include "ipu-prv.h"
@ -33,10 +30,7 @@ struct ipu_di {
struct clk *clk_di; /* display input clock */
struct clk *clk_ipu; /* IPU bus clock */
struct clk *clk_di_pixel; /* resulting pixel clock */
struct clk_hw clk_hw_out;
char *clk_name;
bool inuse;
unsigned long clkflags;
struct ipu_soc *ipu;
};
@ -141,130 +135,6 @@ static inline void ipu_di_write(struct ipu_di *di, u32 value, unsigned offset)
writel(value, di->base + offset);
}
static int ipu_di_clk_calc_div(unsigned long inrate, unsigned long outrate)
{
u64 tmp = inrate;
int div;
tmp *= 16;
do_div(tmp, outrate);
div = tmp;
if (div < 0x10)
div = 0x10;
#ifdef WTF_IS_THIS
/*
* Freescale has this in their Kernel. It is neither clear what
* it does nor why it does it
*/
if (div & 0x10)
div &= ~0x7;
else {
/* Round up divider if it gets us closer to desired pix clk */
if ((div & 0xC) == 0xC) {
div += 0x10;
div &= ~0xF;
}
}
#endif
return div;
}
static unsigned long clk_di_recalc_rate(struct clk_hw *hw,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
unsigned long outrate;
u32 div = ipu_di_read(di, DI_BS_CLKGEN0);
if (div < 0x10)
div = 0x10;
outrate = (parent_rate / div) * 16;
return outrate;
}
static long clk_di_round_rate(struct clk_hw *hw, unsigned long rate,
unsigned long *prate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
unsigned long outrate;
int div;
u32 val;
div = ipu_di_clk_calc_div(*prate, rate);
outrate = (*prate / div) * 16;
val = ipu_di_read(di, DI_GENERAL);
if (!(val & DI_GEN_DI_CLK_EXT) && outrate > *prate / 2)
outrate = *prate / 2;
dev_dbg(di->ipu->dev,
"%s: inrate: %ld div: 0x%08x outrate: %ld wanted: %ld\n",
__func__, *prate, div, outrate, rate);
return outrate;
}
static int clk_di_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
int div;
u32 clkgen0;
clkgen0 = ipu_di_read(di, DI_BS_CLKGEN0) & ~0xfff;
div = ipu_di_clk_calc_div(parent_rate, rate);
ipu_di_write(di, clkgen0 | div, DI_BS_CLKGEN0);
dev_dbg(di->ipu->dev, "%s: inrate: %ld desired: %ld div: 0x%08x\n",
__func__, parent_rate, rate, div);
return 0;
}
static u8 clk_di_get_parent(struct clk_hw *hw)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
return val & DI_GEN_DI_CLK_EXT ? 1 : 0;
}
static int clk_di_set_parent(struct clk_hw *hw, u8 index)
{
struct ipu_di *di = container_of(hw, struct ipu_di, clk_hw_out);
u32 val;
val = ipu_di_read(di, DI_GENERAL);
if (index)
val |= DI_GEN_DI_CLK_EXT;
else
val &= ~DI_GEN_DI_CLK_EXT;
ipu_di_write(di, val, DI_GENERAL);
return 0;
}
static struct clk_ops clk_di_ops = {
.round_rate = clk_di_round_rate,
.set_rate = clk_di_set_rate,
.recalc_rate = clk_di_recalc_rate,
.set_parent = clk_di_set_parent,
.get_parent = clk_di_get_parent,
};
static void ipu_di_data_wave_config(struct ipu_di *di,
int wave_gen,
int access_size, int component_size)
@ -528,42 +398,58 @@ static void ipu_di_sync_config_noninterlaced(struct ipu_di *di,
ipu_di_sync_config(di, cfg_vga, 0, ARRAY_SIZE(cfg_vga));
}
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
static void ipu_di_config_clock(struct ipu_di *di,
const struct ipu_di_signal_cfg *sig)
{
u32 reg;
u32 di_gen, vsync_cnt;
u32 div;
u32 h_total, v_total;
int ret;
unsigned long round;
struct clk *parent;
struct clk *clk;
unsigned clkgen0;
uint32_t val;
dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n",
di->id, sig->width, sig->height);
if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0))
return -EINVAL;
dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n",
clk_get_rate(di->clk_ipu),
clk_get_rate(di->clk_di),
sig->pixelclock);
/*
* CLKMODE_EXT means we must use the DI clock: this is needed
* for things like LVDS which needs to feed the DI and LDB with
* the same pixel clock.
*
* For other interfaces, we can arbitarily select between the DI
* specific clock and the internal IPU clock. See DI_GENERAL
* bit 20. We select the IPU clock if it can give us a clock
* rate within 1% of the requested frequency, otherwise we use
* the DI clock.
*/
round = sig->pixelclock;
if (sig->clkflags & IPU_DI_CLKMODE_EXT) {
parent = di->clk_di;
/*
* CLKMODE_EXT means we must use the DI clock: this is
* needed for things like LVDS which needs to feed the
* DI and LDB with the same pixel clock.
*/
clk = di->clk_di;
if (sig->clkflags & IPU_DI_CLKMODE_SYNC) {
/*
* CLKMODE_SYNC means that we want the DI to be
* clocked at the same rate as the parent clock.
* This is needed (eg) for LDB which needs to be
* fed with the same pixel clock. We assume that
* the LDB clock has already been set correctly.
*/
clkgen0 = 1 << 4;
} else {
/*
* We can use the divider. We should really have
* a flag here indicating whether the bridge can
* cope with a fractional divider or not. For the
* time being, let's go for simplicitly and
* reliability.
*/
unsigned long in_rate;
unsigned div;
clk_set_rate(clk, sig->pixelclock);
in_rate = clk_get_rate(clk);
div = (in_rate + sig->pixelclock / 2) / sig->pixelclock;
if (div == 0)
div = 1;
clkgen0 = div << 4;
}
} else {
/*
* For other interfaces, we can arbitarily select between
* the DI specific clock and the internal IPU clock. See
* DI_GENERAL bit 20. We select the IPU clock if it can
* give us a clock rate within 1% of the requested frequency,
* otherwise we use the DI clock.
*/
unsigned long rate, clkrate;
unsigned div, error;
@ -578,54 +464,80 @@ int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
/* Allow a 1% error */
if (error < 1010 && error >= 990) {
parent = di->clk_ipu;
clk = di->clk_ipu;
clkgen0 = div << 4;
} else {
parent = di->clk_di;
unsigned long in_rate;
unsigned div;
ret = clk_set_rate(parent, sig->pixelclock);
if (ret)
dev_err(di->ipu->dev, "Setting of DI clock failed: %d\n", ret);
clk = di->clk_di;
/* Use the integer divisor rate - avoid fractional dividers */
round = rate;
clk_set_rate(clk, sig->pixelclock);
in_rate = clk_get_rate(clk);
div = (in_rate + sig->pixelclock / 2) / sig->pixelclock;
if (div == 0)
div = 1;
clkgen0 = div << 4;
}
}
ret = clk_set_parent(di->clk_di_pixel, parent);
if (ret) {
dev_err(di->ipu->dev,
"setting pixel clock to parent %s failed with %d\n",
__clk_get_name(parent), ret);
return ret;
}
di->clk_di_pixel = clk;
/* Set the divider */
ipu_di_write(di, clkgen0, DI_BS_CLKGEN0);
/*
* CLKMODE_SYNC means that we want the DI to be clocked at the
* same rate as the parent clock. This is needed (eg) for LDB
* which needs to be fed with the same pixel clock.
*
* Note: clk_set_rate(clk, clk_round_rate(clk, rate)) is the
* same as clk_set_rate(clk, rate);
* Set the high/low periods. Bits 24:16 give us the falling edge,
* and bits 8:0 give the rising edge. LSB is fraction, and is
* based on the divider above. We want a 50% duty cycle, so set
* the falling edge to be half the divider.
*/
if (sig->clkflags & IPU_DI_CLKMODE_SYNC)
round = clk_get_rate(parent);
ipu_di_write(di, (clkgen0 >> 4) << 16, DI_BS_CLKGEN1);
ret = clk_set_rate(di->clk_di_pixel, round);
/* Finally select the input clock */
val = ipu_di_read(di, DI_GENERAL) & ~DI_GEN_DI_CLK_EXT;
if (clk == di->clk_di)
val |= DI_GEN_DI_CLK_EXT;
ipu_di_write(di, val, DI_GENERAL);
dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, got %luHz\n",
dev_dbg(di->ipu->dev, "Want %luHz IPU %luHz DI %luHz using %s, %luHz\n",
sig->pixelclock,
clk_get_rate(di->clk_ipu),
clk_get_rate(di->clk_di),
parent == di->clk_di ? "DI" : "IPU",
clk_get_rate(di->clk_di_pixel));
clk == di->clk_di ? "DI" : "IPU",
clk_get_rate(di->clk_di_pixel) / (clkgen0 >> 4));
}
int ipu_di_init_sync_panel(struct ipu_di *di, struct ipu_di_signal_cfg *sig)
{
u32 reg;
u32 di_gen, vsync_cnt;
u32 div;
u32 h_total, v_total;
dev_dbg(di->ipu->dev, "disp %d: panel size = %d x %d\n",
di->id, sig->width, sig->height);
if ((sig->v_sync_width == 0) || (sig->h_sync_width == 0))
return -EINVAL;
h_total = sig->width + sig->h_sync_width + sig->h_start_width +
sig->h_end_width;
v_total = sig->height + sig->v_sync_width + sig->v_start_width +
sig->v_end_width;
dev_dbg(di->ipu->dev, "Clocks: IPU %luHz DI %luHz Needed %luHz\n",
clk_get_rate(di->clk_ipu),
clk_get_rate(di->clk_di),
sig->pixelclock);
mutex_lock(&di_mutex);
ipu_di_config_clock(di, sig);
div = ipu_di_read(di, DI_BS_CLKGEN0) & 0xfff;
div = div / 16; /* Now divider is integer portion */
@ -709,7 +621,11 @@ EXPORT_SYMBOL_GPL(ipu_di_init_sync_panel);
int ipu_di_enable(struct ipu_di *di)
{
int ret = clk_prepare_enable(di->clk_di_pixel);
int ret;
WARN_ON(IS_ERR(di->clk_di_pixel));
ret = clk_prepare_enable(di->clk_di_pixel);
if (ret)
return ret;
@ -721,6 +637,8 @@ EXPORT_SYMBOL_GPL(ipu_di_enable);
int ipu_di_disable(struct ipu_di *di)
{
WARN_ON(IS_ERR(di->clk_di_pixel));
ipu_module_disable(di->ipu, di->module);
clk_disable_unprepare(di->clk_di_pixel);
@ -776,13 +694,6 @@ int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
u32 module, struct clk *clk_ipu)
{
struct ipu_di *di;
int ret;
const char *di_parent[2];
struct clk_init_data init = {
.ops = &clk_di_ops,
.num_parents = 2,
.flags = 0,
};
if (id > 1)
return -ENODEV;
@ -804,45 +715,16 @@ int ipu_di_init(struct ipu_soc *ipu, struct device *dev, int id,
if (!di->base)
return -ENOMEM;
di_parent[0] = __clk_get_name(di->clk_ipu);
di_parent[1] = __clk_get_name(di->clk_di);
ipu_di_write(di, 0x10, DI_BS_CLKGEN0);
init.parent_names = (const char **)&di_parent;
di->clk_name = kasprintf(GFP_KERNEL, "%s_di%d_pixel",
dev_name(dev), id);
if (!di->clk_name)
return -ENOMEM;
init.name = di->clk_name;
di->clk_hw_out.init = &init;
di->clk_di_pixel = clk_register(dev, &di->clk_hw_out);
if (IS_ERR(di->clk_di_pixel)) {
ret = PTR_ERR(di->clk_di_pixel);
goto failed_clk_register;
}
dev_dbg(dev, "DI%d base: 0x%08lx remapped to %p\n",
id, base, di->base);
di->inuse = false;
di->ipu = ipu;
return 0;
failed_clk_register:
kfree(di->clk_name);
return ret;
}
void ipu_di_exit(struct ipu_soc *ipu, int id)
{
struct ipu_di *di = ipu->di_priv[id];
clk_unregister(di->clk_di_pixel);
kfree(di->clk_name);
}