Thread (21 messages) 21 messages, 4 authors, 2011-03-01

[PATCH 3/8] Add a mfd IPUv3 driver

From: arnd@arndb.de (Arnd Bergmann)
Date: 2011-02-28 17:12:12
Also in: linux-fbdev, lkml

Hi Sascha,

I've had a brief look around the driver. It looks reasonable in general,
but the division into subdrivers feels a bit ad-hoc. If all the components
are built into a single module, I don't see the need for separating the
data by functional units by file. It seems simple enough to turn this
into a layered driver with multiple sub-devices each derived from a 
platform_device on its own.

On Monday 28 February 2011, Sascha Hauer wrote:
 arch/arm/plat-mxc/include/mach/ipu-v3.h |   49 +++
 drivers/video/Kconfig                   |    2 +
 drivers/video/Makefile                  |    1 +
 drivers/video/imx-ipu-v3/Kconfig        |   10 +
 drivers/video/imx-ipu-v3/Makefile       |    3 +
 drivers/video/imx-ipu-v3/ipu-common.c   |  666 +++++++++++++++++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-cpmem.c    |  612 ++++++++++++++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-dc.c       |  364 +++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-di.c       |  550 +++++++++++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-dmfc.c     |  355 ++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-dp.c       |  476 ++++++++++++++++++++++
 drivers/video/imx-ipu-v3/ipu-prv.h      |  216 ++++++++++
 include/video/imx-ipu-v3.h              |  219 ++++++++++
I wonder if this is something that would fit better in drivers/gpu instead
of drivers/video. We recently discussed the benefits of KMS vs fb drivers,
and I think it would be good to be prepared for making this a KMS driver
in the long run, even if the immediate target has to be a simple frame buffer
driver.
+#include "ipu-prv.h"
+
+static struct clk *ipu_clk;
+static struct device *ipu_dev;
+
+static DEFINE_SPINLOCK(ipu_lock);
+static DEFINE_MUTEX(ipu_channel_lock);
+void __iomem *ipu_cm_reg;
+void __iomem *ipu_idmac_reg;
+
+static int ipu_use_count;
+
+static struct ipu_channel channels[64];
Keeping these global limits you to just one ipu, and makes
understanding the code a little harder. How about moving
these variables into a struct ipu_data (or similar) that
is referenced from the platform_device?
+EXPORT_SYMBOL(ipu_idmac_put);
Why not EXPORT_SYMBOL_GPL?
+static LIST_HEAD(ipu_irq_handlers);
+
+static void ipu_irq_update_irq_mask(void)
+{
+	struct ipu_irq_handler *handler;
+	int i;
+
+	DECLARE_IPU_IRQ_BITMAP(irqs);
+
+	bitmap_zero(irqs, IPU_IRQ_COUNT);
+
+	list_for_each_entry(handler, &ipu_irq_handlers, list)
+		bitmap_or(irqs, irqs, handler->ipu_irqs, IPU_IRQ_COUNT);
+
+	for (i = 0; i < BITS_TO_LONGS(IPU_IRQ_COUNT); i++)
+		ipu_cm_write(irqs[i], IPU_INT_CTRL(i + 1));
+}
+
+int ipu_irq_add_handler(struct ipu_irq_handler *ipuirq)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&ipu_lock, flags);
+
+	list_add_tail(&ipuirq->list, &ipu_irq_handlers);
+	ipu_irq_update_irq_mask();
+
+	spin_unlock_irqrestore(&ipu_lock, flags);
+	return 0;
+}
+EXPORT_SYMBOL(ipu_irq_add_handler);
The interrupt logic needs some comments. What are you trying to achieve here?
+int ipu_wait_for_interrupt(int interrupt, int timeout_ms)
+{
+	struct ipu_irq_handler handler;
+	DECLARE_COMPLETION_ONSTACK(completion);
+	int ret;
+
+	bitmap_zero(handler.ipu_irqs, IPU_IRQ_COUNT);
+	bitmap_set(handler.ipu_irqs, interrupt, 1);
+
+	ipu_cm_write(1 << (interrupt % 32), IPU_INT_STAT(interrupt / 32 + 1));
+
+	handler.handler = ipu_completion_handler;
+	handler.context = &completion;
+	ipu_irq_add_handler(&handler);
+
+	ret = wait_for_completion_timeout(&completion,
+			msecs_to_jiffies(timeout_ms));
+
+	ipu_irq_remove_handler(&handler);
+
+	if (ret > 0)
+		ret = 0;
+	else
+		ret = -ETIMEDOUT;
+
+	return ret;
+}
+EXPORT_SYMBOL(ipu_wait_for_interrupt);
If I understand this correctly, this is a very complicated way to implement
something that could be done with a single wait_event_timeout() and
wake_up_interruptible_all() from the irq handler.
+static irqreturn_t ipu_irq_handler(int irq, void *desc)
+{
+	DECLARE_IPU_IRQ_BITMAP(status);
+	struct ipu_irq_handler *handler;
+	int i;
+
+	for (i = 0; i < BITS_TO_LONGS(IPU_IRQ_COUNT); i++) {
+		status[i] = ipu_cm_read(IPU_INT_STAT(i + 1));
+		ipu_cm_write(status[i], IPU_INT_STAT(i + 1));
+	}
+
+	list_for_each_entry(handler, &ipu_irq_handlers, list) {
+		DECLARE_IPU_IRQ_BITMAP(tmp);
+		if (bitmap_and(tmp, status, handler->ipu_irqs, IPU_IRQ_COUNT))
+			handler->handler(tmp, handler->context);
+	}
+
+	return IRQ_HANDLED;
+}
This needs to take spin_lock_irqsave before walking the ipu_irq_handlers
list, in order to prevent another CPU from modifying the list
while you are in the handler.
+static int ipu_reset(void)
+{
+	int timeout = 10000;
+	u32 val;
+
+	/* hard reset the IPU */
+	val = readl(MX51_IO_ADDRESS(MX51_SRC_BASE_ADDR));
+	val |= 1 << 3;
+	writel(val, MX51_IO_ADDRESS(MX51_SRC_BASE_ADDR));
+
+	ipu_cm_write(0x807FFFFF, IPU_MEM_RST);
+
+	while (ipu_cm_read(IPU_MEM_RST) & 0x80000000) {
+		if (!timeout--)
+			return -ETIME;
+		udelay(100);
+	}
You have a timeout of over one second with udelay, which
is not acceptable on many systems. AFAICT, the function 
can sleep, so you can replace udelay with msleep(1), and
you should use a better logic to determine the end of the
loop:

	unsigned long timeout = jiffies + msecs_to_jiffies(1000);

	while (ipu_cm_read(IPU_MEM_RST) & 0x80000000) {
		if (time_after(jiffies, timeout))
			return -ETIME;
		msleep(1);
	}
+static u32 *ipu_cpmem_base;
+static struct device *ipu_dev;
+
+struct ipu_ch_param_word {
+	u32 data[5];
+	u32 res[3];
+};
+
+struct ipu_ch_param {
+	struct ipu_ch_param_word word[2];
+};
Same comment as for the previous file
+
+static void __iomem *ipu_dc_reg;
+static void __iomem *ipu_dc_tmpl_reg;
+static struct device *ipu_dev;
+
+struct ipu_dc {
+	unsigned int di; /* The display interface number assigned to this dc channel */
+	unsigned int channel_offset;
+};
+
+static struct ipu_dc dc_channels[10];
And here again.
+static void ipu_dc_link_event(int chan, int event, int addr, int priority)
+{
+	u32 reg;
+
+	reg = __raw_readl(DC_RL_CH(chan, event));
+	reg &= ~(0xFFFF << (16 * (event & 0x1)));
+	reg |= ((addr << 8) | priority) << (16 * (event & 0x1));
+	__raw_writel(reg, DC_RL_CH(chan, event));
+}
Better use readl/writel instead of __raw_readl/__raw_writel, throughout the
code.
+int ipu_di_init(struct platform_device *pdev, int id, unsigned long base,
+		u32 module, struct clk *ipu_clk);
+void ipu_di_exit(struct platform_device *pdev, int id);
+
+int ipu_dmfc_init(struct platform_device *pdev, unsigned long base,
+		struct clk *ipu_clk);
+void ipu_dmfc_exit(struct platform_device *pdev);
+
+int ipu_dp_init(struct platform_device *pdev, unsigned long base);
+void ipu_dp_exit(struct platform_device *pdev);
+
+int ipu_dc_init(struct platform_device *pdev, unsigned long base,
+		unsigned long template_base);
+void ipu_dc_exit(struct platform_device *pdev);
+
+int ipu_cpmem_init(struct platform_device *pdev, unsigned long base);
+void ipu_cpmem_exit(struct platform_device *pdev);
If you make the main driver an mfd device, the sub-drivers could become
real platform drivers, which can structure the layering in a more modular
way.
E.g. instead of a single module init function, each subdriver can be
a module by itself and look at the resources associated with the
platform device it matches.

	Arnd
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help