Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0721feee authored by Russell King's avatar Russell King
Browse files

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: default avatarPhilipp Zabel <p.zabel@pengutronix.de>
Acked-by: default avatarShawn Guo <shawn.guo@linaro.org>
Reviewed-by: default avatarFabio Estevam <fabio.estevam@freescale.com>
Signed-off-by: default avatarRussell King <rmk+kernel@arm.linux.org.uk>
parent 370b1815
Loading
Loading
Loading
Loading
+105 −223
Original line number Diff line number Diff line
@@ -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->clkflags & IPU_DI_CLKMODE_EXT) {
		/*
		 * 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->v_sync_width == 0) || (sig->h_sync_width == 0))
		return -EINVAL;
		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;

	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);
			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 {
		/*
	 * 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.
		 * 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;
	} else {
		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;

			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;

			/* Use the integer divisor rate - avoid fractional dividers */
			round = rate;
			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);
}