2008-12-18 13:26:09

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH 0/4 v4] i.MX31: dmaengine and framebuffer drivers

Hi,

This is version 4 of dmaengine and framebuffer drivers for i.MX31.

Changes since version 3: fixed idmac_issue_pending() to not reset the
current buffer, fixed return value from idmac_tx_submit(), fixed
framebuffer panning.

Changes since version 2: now uses a tasklet to clean up completed
transaction descriptors, as suggested by Dan Williams.

Changes since version 1: rebased on the updated dmaengine framework.

I'd really like to get this in for 2.6.29, so, please, review and comment.

Based on linux-next as of commit f0e0e8954d03b414927650a6129ace01d554afdc.

Thanks
Guennadi
---
Guennadi Liakhovetski


2008-12-18 22:58:51

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Hi Guennadi,

On Thu, Dec 18, 2008 at 02:26:19PM +0100, Guennadi Liakhovetski wrote:
> From: Guennadi Liakhovetski <[email protected]>
>
> i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
> Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
> Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
> (PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
> CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
> and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
> supported over dmaengine and irq-chip APIs respectively.
>
> IDMAC is a specialised DMA controller, its DMA channels cannot be used for
> general-purpose operations, even though it might be possible to configure
> a memory-to-memory channel for memcpy operation. This driver will not work
> with generic dmaengine clients, clients, wishing to use it must use
> respective wrapper structures, they also must specify which channels they
> require, as channels are hard-wired to specific IPU functions.

As a place for this driver /me votes for drivers/dma/ Though it does not
seem like a perfect place for it, it still uses the API provided there.

A general note: Can we get rid of the function names starting with an
underscore?

More comments inline

Regards,
Sascha

>
> Based on original driver from Freescale, Copyright preserved.
>
> Signed-off-by: Guennadi Liakhovetski <[email protected]>
> ---
>

<snip>

> diff --git a/drivers/mfd/ipu/ipu_idmac.c b/drivers/mfd/ipu/ipu_idmac.c
> new file mode 100644
> index 0000000..85319f8
> --- /dev/null
> +++ b/drivers/mfd/ipu/ipu_idmac.c
> @@ -0,0 +1,1662 @@
> +/*
> + * Copyright (C) 2008
> + * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
> + *
> + * Copyright (C) 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/platform_device.h>
> +#include <linux/err.h>
> +#include <linux/spinlock.h>
> +#include <linux/delay.h>
> +#include <linux/list.h>
> +#include <linux/clk.h>
> +#include <linux/vmalloc.h>
> +#include <linux/string.h>
> +#include <linux/interrupt.h>
> +
> +#include <mach/ipu.h>
> +
> +#include <asm/io.h>

s/asm/linux

> +
> +#include "ipu_intern.h"
> +
> +#define FS_VF_IN_VALID 0x00000002
> +#define FS_ENC_IN_VALID 0x00000001
> +
> +/*
> + * There can be only one, we could allocate it dynamically, but then we'd have
> + * to add an extra parameter to some functions, and use something as ugly as
> + * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));

still you use it in one place

> + * in the ISR
> + */
> +static struct ipu ipu_data;
> +
> +#define to_ipu(id) container_of(id, struct ipu, idmac)
> +
> +static u32 __idmac_read_icreg(struct ipu *ipu, unsigned long reg)
> +{
> + return __raw_readl(ipu->reg_ic + reg);
> +}
> +
> +#define idmac_read_icreg(ipu, reg) __idmac_read_icreg(ipu, reg - IC_CONF)
> +
> +static void __idmac_write_icreg(struct ipu *ipu, u32 value, unsigned long reg)
> +{
> + __raw_writel(value, ipu->reg_ic + reg);
> +}
> +
> +#define idmac_write_icreg(ipu, v, reg) __idmac_write_icreg(ipu, v, reg - IC_CONF)
> +
> +static u32 idmac_read_ipureg(struct ipu *ipu, unsigned long reg)
> +{
> + return __raw_readl(ipu->reg_ipu + reg);
> +}
> +
> +static void idmac_write_ipureg(struct ipu *ipu, u32 value, unsigned long reg)
> +{
> + __raw_writel(value, ipu->reg_ipu + reg);
> +}
> +
> +/*****************************************************************************
> + * IPU / IC common functions
> + */
> +static void dump_idmac_reg(struct ipu *ipu)
> +{
> + dev_dbg(ipu->dev, "IDMAC_CONF 0x%x, IC_CONF 0x%x, IDMAC_CHA_EN 0x%x, "
> + "IDMAC_CHA_PRI 0x%x, IDMAC_CHA_BUSY 0x%x\n",
> + idmac_read_icreg(ipu, IDMAC_CONF),
> + idmac_read_icreg(ipu, IC_CONF),
> + idmac_read_icreg(ipu, IDMAC_CHA_EN),
> + idmac_read_icreg(ipu, IDMAC_CHA_PRI),
> + idmac_read_icreg(ipu, IDMAC_CHA_BUSY));
> + dev_dbg(ipu->dev, "BUF0_RDY 0x%x, BUF1_RDY 0x%x, CUR_BUF 0x%x, "
> + "DB_MODE 0x%x, TASKS_STAT 0x%x\n",
> + idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY),
> + idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY),
> + idmac_read_ipureg(ipu, IPU_CHA_CUR_BUF),
> + idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL),
> + idmac_read_ipureg(ipu, IPU_TASKS_STAT));
> +}
> +
> +static uint32_t bytes_per_pixel(enum pixel_fmt fmt)
> +{
> + switch (fmt) {
> + case IPU_PIX_FMT_GENERIC: /* generic data */
> + case IPU_PIX_FMT_RGB332:
> + case IPU_PIX_FMT_YUV420P:
> + case IPU_PIX_FMT_YUV422P:
> + default:
> + return 1;
> + case IPU_PIX_FMT_RGB565:
> + case IPU_PIX_FMT_YUYV:
> + case IPU_PIX_FMT_UYVY:
> + return 2;
> + case IPU_PIX_FMT_BGR24:
> + case IPU_PIX_FMT_RGB24:
> + return 3;
> + case IPU_PIX_FMT_GENERIC_32: /* generic data */
> + case IPU_PIX_FMT_BGR32:
> + case IPU_PIX_FMT_RGB32:
> + case IPU_PIX_FMT_ABGR32:
> + return 4;
> + }
> +}
> +
> +/* Enable / disable direct write to memory by the Camera Sensor Interface */
> +static void _ipu_ic_enable_task(struct ipu *ipu, enum ipu_channel channel)
> +{
> + uint32_t ic_conf, mask;
> +
> + switch (channel) {
> + case IDMAC_IC_0:
> + mask = IC_CONF_PRPENC_EN;
> + break;
> + case IDMAC_IC_7:
> + mask = IC_CONF_RWS_EN | IC_CONF_PRPENC_EN;
> + break;
> + default:
> + return;
> + }
> + ic_conf = idmac_read_icreg(ipu, IC_CONF) | mask;
> + idmac_write_icreg(ipu, ic_conf, IC_CONF);
> +}
> +
> +static void _ipu_ic_disable_task(struct ipu *ipu, enum ipu_channel channel)
> +{
> + uint32_t ic_conf, mask;
> +
> + switch (channel) {
> + case IDMAC_IC_0:
> + mask = IC_CONF_PRPENC_EN;
> + break;
> + case IDMAC_IC_7:
> + mask = IC_CONF_RWS_EN | IC_CONF_PRPENC_EN;
> + break;
> + default:
> + return;
> + }
> + ic_conf = idmac_read_icreg(ipu, IC_CONF) & ~mask;
> + idmac_write_icreg(ipu, ic_conf, IC_CONF);
> +}
> +
> +static uint32_t _ipu_channel_status(struct ipu *ipu, enum ipu_channel channel)
> +{
> + uint32_t stat = TASK_STAT_IDLE;
> + uint32_t task_stat_reg = idmac_read_ipureg(ipu, IPU_TASKS_STAT);
> +
> + switch (channel) {
> + case IDMAC_IC_7:
> + stat = (task_stat_reg & TSTAT_CSI2MEM_MASK) >>
> + TSTAT_CSI2MEM_OFFSET;
> + break;
> + case IDMAC_IC_0:
> + case IDMAC_SDC_0:
> + case IDMAC_SDC_1:
> + default:
> + break;
> + }
> + return stat;
> +}
> +
> +static void _ipu_ch_param_set_size(uint32_t *params,
> + uint32_t pixel_fmt, uint16_t width,
> + uint16_t height, uint16_t stride)
> +{
> + uint32_t u_offset = 0;
> + uint32_t v_offset = 0;
> +
> + params[3] = (uint32_t)((width - 1) << 12) |
> + ((uint32_t)(height - 1) << 24);
> + params[4] = (uint32_t)(height - 1) >> 8;
> + params[7] = (uint32_t)(stride - 1) << 3;
> +
> + switch (pixel_fmt) {
> + case IPU_PIX_FMT_GENERIC:
> + /*Represents 8-bit Generic data */
> + params[7] |= 3 | (7UL << (81 - 64)) | (31L << (89 - 64)); /* BPP & PFS */
> + params[8] = 2; /* SAT = use 32-bit access */
> + break;
> + case IPU_PIX_FMT_GENERIC_32:
> + /*Represents 32-bit Generic data */
> + params[7] |= (7UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */
> + params[8] = 2; /* SAT = use 32-bit access */
> + break;
> + case IPU_PIX_FMT_RGB565:
> + params[7] |= 2L | (4UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */
> + params[8] = 2 | /* SAT = 32-bit access */
> + (0UL << (99 - 96)) | /* Red bit offset */
> + (5UL << (104 - 96)) | /* Green bit offset */
> + (11UL << (109 - 96)) | /* Blue bit offset */
> + (16UL << (114 - 96)) | /* Alpha bit offset */
> + (4UL << (119 - 96)) | /* Red bit width - 1 */
> + (5UL << (122 - 96)) | /* Green bit width - 1 */
> + (4UL << (125 - 96)); /* Blue bit width - 1 */
> + break;
> + case IPU_PIX_FMT_BGR24:
> + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); /* 24 BPP & RGB PFS */
> + params[8] = 2 | /* SAT = 32-bit access */
> + (8UL << (104 - 96)) | /* Green bit offset */
> + (16UL << (109 - 96)) | /* Blue bit offset */
> + (24UL << (114 - 96)) | /* Alpha bit offset */
> + (7UL << (119 - 96)) | /* Red bit width - 1 */
> + (7UL << (122 - 96)) | /* Green bit width - 1 */
> + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
> + break;
> + case IPU_PIX_FMT_RGB24:
> + params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); /* 24 BPP & RGB PFS */
> + params[8] = 2 | /* SAT = 32-bit access */
> + (16UL << (99 - 96)) | /* Red bit offset */
> + (8UL << (104 - 96)) | /* Green bit offset */
> + (0UL << (109 - 96)) | /* Blue bit offset */
> + (24UL << (114 - 96)) | /* Alpha bit offset */
> + (7UL << (119 - 96)) | /* Red bit width - 1 */
> + (7UL << (122 - 96)) | /* Green bit width - 1 */
> + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
> + break;
> + case IPU_PIX_FMT_BGRA32:
> + case IPU_PIX_FMT_BGR32:
> + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); /* BPP & PFS */
> + params[8] = 2 | /* SAT = 32-bit access */
> + (8UL << (99 - 96)) | /* Red bit offset */
> + (16UL << (104 - 96)) | /* Green bit offset */
> + (24UL << (109 - 96)) | /* Blue bit offset */
> + (0UL << (114 - 96)) | /* Alpha bit offset */
> + (7UL << (119 - 96)) | /* Red bit width - 1 */
> + (7UL << (122 - 96)) | /* Green bit width - 1 */
> + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
> + params[9] = 7; /* Alpha bit width - 1 */
> + break;
> + case IPU_PIX_FMT_RGBA32:
> + case IPU_PIX_FMT_RGB32:
> + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64));
> + params[8] = 2 | /* SAT = 32-bit access */
> + (24UL << (99 - 96)) | /* Red bit offset */
> + (16UL << (104 - 96)) | /* Green bit offset */
> + (8UL << (109 - 96)) | /* Blue bit offset */
> + (0UL << (114 - 96)) | /* Alpha bit offset */
> + (7UL << (119 - 96)) | /* Red bit width - 1 */
> + (7UL << (122 - 96)) | /* Green bit width - 1 */
> + (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
> + params[9] = 7; /* Alpha bit width - 1 */
> + break;
> + case IPU_PIX_FMT_ABGR32:
> + params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64));
> + params[8] = 2 | /* SAT = 32-bit access */
> + (0UL << (99 - 96)) | /* Alpha bit offset */
> + (8UL << (104 - 96)) | /* Blue bit offset */
> + (16UL << (109 - 96)) | /* Green bit offset */
> + (24UL << (114 - 96)) | /* Red bit offset */
> + (7UL << (119 - 96)) | /* Alpha bit width - 1 */
> + (7UL << (122 - 96)) | /* Blue bit width - 1 */
> + (uint32_t) (7UL << (125 - 96)); /* Green bit width - 1 */
> + params[9] = 7; /* Red bit width - 1 */
> + break;
> + case IPU_PIX_FMT_UYVY:
> + params[7] |= 2 | (6UL << 17) | (7 << (89 - 64));
> + params[8] = 2; /* SAT = 32-bit access */
> + break;
> + case IPU_PIX_FMT_YUV420P2:
> + case IPU_PIX_FMT_YUV420P:
> + params[7] |= 3 | (3UL << 17) | (7 << (89 - 64));
> + params[8] = 2; /* SAT = 32-bit access */
> + u_offset = stride * height;
> + v_offset = u_offset + u_offset / 4;
> + break;
> + case IPU_PIX_FMT_YVU422P:
> + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64));
> + params[8] = 2; /* SAT = 32-bit access */
> + v_offset = stride * height;
> + u_offset = v_offset + v_offset / 2;
> + break;
> + case IPU_PIX_FMT_YUV422P:
> + params[7] |= 3 | (2UL << 17) | (7 << (89 - 64));
> + params[8] = 2; /* SAT = 32-bit access */
> + u_offset = stride * height;
> + v_offset = u_offset + u_offset / 2;
> + break;
> + default:
> + dev_err(ipu_data.dev, "mxc ipu: unimplemented pixel format\n");
> + break;
> + }
> +
> + params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32));
> + params[2] = u_offset >> (64 - 53);
> + params[2] |= v_offset << (79 - 64);
> + params[3] |= v_offset >> (96 - 79);
> +}

This is so unreadable. Why not use a struct type for the params, like
this:

struct ipu_params {
u32 xv:10;
u32 yv:10;
u32 xb:12;
u32 yb:12;
u32 res:2;
u32 nsb:1;
u32 lnpb:6;
[...]
} __attribute__ ((__packed__));



> +
> +static void _ipu_ch_param_set_burst_size(uint32_t *params,
> + uint16_t burst_pixels)
> +{
> + params[7] &= ~(0x3FL << (89 - 64));
> + params[7] |= (uint32_t)(burst_pixels - 1) << (89 - 64);
> +};
> +
> +static void _ipu_ch_param_set_buffer(uint32_t *params,
> + dma_addr_t buf0, dma_addr_t buf1)
> +{
> + params[5] = buf0;
> + params[6] = buf1;
> +};
> +
> +static void _ipu_ch_param_set_rotation(uint32_t *params,
> + enum ipu_rotate_mode rot)
> +{
> + params[7] |= (uint32_t)rot << (84 - 64);
> +};
> +
> +static void _ipu_write_param_mem(uint32_t addr, uint32_t *data,
> + uint32_t numWords)
> +{
> + for (; numWords > 0; numWords--) {

num_words

> + dev_dbg(ipu_data.dev,
> + "write param mem - addr = 0x%08X, data = 0x%08X\n",
> + addr, *data);
> + idmac_write_ipureg(&ipu_data, addr, IPU_IMA_ADDR);
> + idmac_write_ipureg(&ipu_data, *data++, IPU_IMA_DATA);
> + addr++;
> + if ((addr & 0x7) == 5) {
> + addr &= ~0x7; /* set to word 0 */
> + addr += 8; /* increment to next row */
> + }
> + }
> +}
> +
> +static bool _calc_resize_coeffs(uint32_t in_size, uint32_t out_size,
> + uint32_t *resize_coeff,
> + uint32_t *downsize_coeff)
> +{
> + uint32_t temp_size;
> + uint32_t temp_downsize;
> +
> + /* Cannot downsize more than 8:1 */
> + if ((out_size << 3) < in_size)
> + return false;

We normally use 0 for success, but the return value isn't checked
anyway...

> +
> + /* compute downsizing coefficient */
> + temp_downsize = 0;
> + temp_size = in_size;
> + while ((temp_size >= out_size * 2) && (temp_downsize < 2)) {
> + temp_size >>= 1;
> + temp_downsize++;
> + }
> + *downsize_coeff = temp_downsize;
> +
> + /*
> + * compute resizing coefficient using the following formula:
> + * resize_coeff = M*(SI -1)/(SO - 1)
> + * where M = 2^13, SI - input size, SO - output size
> + */
> + *resize_coeff = (8192L * (temp_size - 1)) / (out_size - 1);
> + if (*resize_coeff >= 16384L) {
> + dev_err(ipu_data.dev, "Warning! Overflow on resize coeff.\n");
> + *resize_coeff = 0x3FFF;
> + }
> +
> + dev_dbg(ipu_data.dev, "resizing from %u -> %u pixels, "
> + "downsize=%u, resize=%u.%lu (reg=%u)\n", in_size, out_size,
> + *downsize_coeff, *resize_coeff >= 8192L ? 1 : 0,
> + ((*resize_coeff & 0x1FFF) * 10000L) / 8192L, *resize_coeff);
> +
> + return true;
> +}
> +
> +static enum ipu_color_space format_to_colorspace(enum pixel_fmt fmt)
> +{
> + switch (fmt) {
> + case IPU_PIX_FMT_RGB565:
> + case IPU_PIX_FMT_BGR24:
> + case IPU_PIX_FMT_RGB24:
> + case IPU_PIX_FMT_BGR32:
> + case IPU_PIX_FMT_RGB32:
> + return IPU_COLORSPACE_RGB;
> + default:
> + return IPU_COLORSPACE_YCBCR;
> + }
> +}
> +
> +static int _ipu_ic_init_prpenc(struct ipu *ipu,
> + union ipu_channel_param *params, bool src_is_csi)
> +{
> + uint32_t reg, ic_conf;
> + uint32_t downsize_coeff, resize_coeff;
> + enum ipu_color_space in_fmt, out_fmt;
> +
> + /* Setup vertical resizing */
> + _calc_resize_coeffs(params->video.in_height,
> + params->video.out_height,
> + &resize_coeff, &downsize_coeff);
> + reg = (downsize_coeff << 30) | (resize_coeff << 16);
> +
> + /* Setup horizontal resizing */
> + _calc_resize_coeffs(params->video.in_width,
> + params->video.out_width,
> + &resize_coeff, &downsize_coeff);
> + reg |= (downsize_coeff << 14) | resize_coeff;
> +
> + /* Setup color space conversion */
> + in_fmt = format_to_colorspace(params->video.in_pixel_fmt);
> + out_fmt = format_to_colorspace(params->video.out_pixel_fmt);
> +
> + /*
> + * Colourspace conversion unsupported yet - see _init_csc() in
> + * Freescale sources
> + */
> + if (in_fmt != out_fmt) {
> + dev_err(ipu->dev, "Colourspace conversion unsupported!\n");
> + return -EOPNOTSUPP;
> + }
> +
> + idmac_write_icreg(ipu, reg, IC_PRP_ENC_RSC);
> +
> + ic_conf = idmac_read_icreg(ipu, IC_CONF);
> +
> + if (src_is_csi)
> + ic_conf &= ~IC_CONF_RWS_EN;
> + else
> + ic_conf |= IC_CONF_RWS_EN;
> +
> + idmac_write_icreg(ipu, ic_conf, IC_CONF);
> +
> + return 0;
> +}
> +
> +static uint32_t dma_param_addr(uint32_t dma_ch)
> +{
> + /* Channel Parameter Memory */
> + return 0x10000 | (dma_ch << 4);
> +};
> +
> +static void _ipu_channel_set_priority(struct ipu *ipu, enum ipu_channel channel, bool prio)
> +{
> + u32 reg = idmac_read_icreg(ipu, IDMAC_CHA_PRI);
> +
> + if (prio)
> + reg |= 1UL << channel;
> + else
> + reg &= ~(1UL << channel);
> +
> + idmac_write_icreg(ipu, reg, IDMAC_CHA_PRI);
> +
> + dump_idmac_reg(ipu);
> +}
> +
> +static uint32_t _ipu_channel_conf_check(struct ipu *ipu, enum ipu_channel channel)
> +{
> + uint32_t ipu_conf;
> +
> + ipu_conf = idmac_read_ipureg(ipu, IPU_CONF);
> + if (WARN(!ipu_conf, "Uninitialized IPU_CONF!\n"))
> + /* Should not happen. Error out here. */
> + clk_enable(ipu->ipu_clk);

Erroring out by enabling the clock? This looks strange.

> +
> + if (ipu->channel_init_mask & (1L << channel))
> + dev_err(ipu->dev, "Warning: channel already initialized %d\n",
> + channel);
> +
> + return ipu_conf;
> +}

This function is called only once and it basically reads a register and prints
warnings if something is wrong. No need to make it an extra function.

> +
> +static uint32_t _ipu_channel_conf_mask(enum ipu_channel channel)
> +{
> + uint32_t mask;
> +
> + switch (channel) {
> + case IDMAC_IC_0:
> + mask = IPU_CONF_CSI_EN | IPU_CONF_IC_EN;
> + break;
> + case IDMAC_IC_7:
> + mask = IPU_CONF_CSI_EN | IPU_CONF_IC_EN;
> + break;

These two cases can be joined.

> + case IDMAC_SDC_0:
> + mask = IPU_CONF_SDC_EN | IPU_CONF_DI_EN;
> + break;
> + case IDMAC_SDC_1:
> + mask = IPU_CONF_SDC_EN | IPU_CONF_DI_EN;
> + break;

dito

> + default:
> + mask = 0;
> + break;
> + }
> +
> + return mask;
> +}
> +
> +/**
> + * Enable an IPU channel.
> + *
> + * @param channel Input parameter for the logical channel ID.
> + *
> + * @return Returns 0 on success or negative error code on failure.
> + */
> +static int ipu_enable_channel(struct idmac *idmac, struct idmac_channel *ichan)
> +{
> + struct ipu *ipu = to_ipu(idmac);
> + enum ipu_channel channel = ichan->dma_chan.chan_id;
> + uint32_t reg;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ipu->lock, flags);
> +
> + /* Reset to buffer 0 */
> + idmac_write_ipureg(ipu, 1UL << channel, IPU_CHA_CUR_BUF);
> + ichan->active_buffer = 0;
> + ichan->status = IPU_CHANNEL_ENABLED;
> +
> + switch (channel) {
> + case IDMAC_SDC_0:
> + case IDMAC_SDC_1:
> + case IDMAC_IC_7:
> + _ipu_channel_set_priority(ipu, channel, true);
> + default:
> + break;
> + }
> +
> + reg = idmac_read_icreg(ipu, IDMAC_CHA_EN);
> +
> + idmac_write_icreg(ipu, reg | (1UL << channel), IDMAC_CHA_EN);
> +
> + _ipu_ic_enable_task(ipu, channel);
> +
> + spin_unlock_irqrestore(&ipu->lock, flags);
> + return 0;
> +}
> +
> +/**
> + * Initialize a buffer for logical IPU channel.
> + *
> + * @param channel Input parameter for the logical channel ID.
> + *
> + * @param pixel_fmt Input parameter for pixel format of buffer. Pixel
> + * format is a FOURCC ASCII code.
> + *
> + * @param width Input parameter for width of buffer in pixels.
> + *
> + * @param height Input parameter for height of buffer in pixels.
> + *
> + * @param stride Input parameter for stride length of buffer
> + * in pixels.
> + *
> + * @param rot_mode Input parameter for rotation setting of buffer.
> + * A rotation setting other than \b IPU_ROTATE_VERT_FLIP
> + * should only be used for input buffers of rotation
> + * channels.
> + *
> + * @param phyaddr_0 Input parameter buffer 0 physical address.
> + *
> + * @param phyaddr_1 Input parameter buffer 1 physical address.
> + * Setting this to a value other than NULL enables
> + * double buffering mode.
> + *
> + * @return Returns 0 on success or negative error code on failure.
> + */
> +static int ipu_init_channel_buffer(struct idmac_channel *ichan,
> + enum pixel_fmt pixel_fmt,
> + uint16_t width, uint16_t height,
> + uint32_t stride,
> + enum ipu_rotate_mode rot_mode,
> + dma_addr_t phyaddr_0, dma_addr_t phyaddr_1)
> +{
> + enum ipu_channel channel = ichan->dma_chan.chan_id;
> + struct idmac *idmac = to_idmac(ichan->dma_chan.device);
> + struct ipu *ipu = to_ipu(idmac);
> + uint32_t params[10] = {0};
> + unsigned long flags;
> + uint32_t reg;
> + uint32_t stride_bytes;
> +
> + stride_bytes = stride * bytes_per_pixel(pixel_fmt);
> +
> + if (stride_bytes % 4) {
> + dev_err(ipu->dev,
> + "Stride length must be 32-bit aligned, stride = %d, bytes = %d\n",
> + stride, stride_bytes);
> + return -EINVAL;
> + }
> + /* IC channel's stride must be a multiple of 8 pixels */
> + if ((channel <= 13) && (stride % 8)) {
> + dev_err(ipu->dev, "Stride must be 8 pixel multiple\n");
> + return -EINVAL;
> + }
> + /* Build parameter memory data for DMA channel */
> + _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride_bytes);
> + _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1);
> + _ipu_ch_param_set_rotation(params, rot_mode);
> + /* Some channels (rotation) have restriction on burst length */
> + switch (channel) {
> + case IDMAC_IC_7: /* Hangs with burst 8, 16, other values
> + invalid - Table 44-30 */
> +/*
> + _ipu_ch_param_set_burst_size(params, 8);
> + */
> + break;
> + case IDMAC_SDC_0:
> + case IDMAC_SDC_1:
> + /* In original code only IPU_PIX_FMT_RGB565 was setting burst */
> + _ipu_ch_param_set_burst_size(params, 16);
> + break;
> + case IDMAC_IC_0:
> + default:
> + break;
> + }
> +
> + /* In fact, channel is not running yet, so, don't have to protect */
> + spin_lock_irqsave(&ipu->lock, flags);

Please remove either the locking or the comment, but this doesn't help
anyone.

> +
> + _ipu_write_param_mem(dma_param_addr(channel), params, 10);
> +
> + reg = idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL);
> +
> + if (phyaddr_1)
> + reg |= 1UL << channel;
> + else
> + reg &= ~(1UL << channel);
> +
> + idmac_write_ipureg(ipu, reg, IPU_CHA_DB_MODE_SEL);
> +
> + ichan->status = IPU_CHANNEL_READY;
> +
> + spin_unlock_irqrestore(ipu->lock, flags);
> +
> + return 0;
> +}
> +
> +/**
> + * Set a channel's buffer as ready.
> + *
> + * @param channel Input parameter for the logical channel ID.
> + *
> + * @param type Input parameter which buffer to initialize.
> + *
> + * @param bufNum Input parameter for which buffer number set to

buffer_n

> + * ready state.
> + *
> + * @return Returns 0 on success or negative error code on failure.
> + */
> +static void ipu_select_buffer(enum ipu_channel channel, int buffer_n)
> +{
> + /* No locking - this is a write-one-to-set register, cleared by IPU */

It's a write-only operation, not a read-modify-write operation, so no
locking is required anyway.

> + if (buffer_n == 0)
> + /* Mark buffer 0 as ready. */
> + idmac_write_ipureg(&ipu_data, 1UL << channel, IPU_CHA_BUF0_RDY);
> + else
> + /* Mark buffer 1 as ready. */
> + idmac_write_ipureg(&ipu_data, 1UL << channel, IPU_CHA_BUF1_RDY);
> +}
> +
> +/**
> + * Update the physical address of a buffer for a logical IPU channel.
> + *
> + * @param channel Input parameter for the logical channel ID.
> + *
> + * @param type Input parameter which buffer to initialize.
> + *
> + * @param bufNum Input parameter for which buffer number to update.
> + * 0 or 1 are the only valid values.

buffer_n

> + *
> + * @param phyaddr Input parameter buffer physical address.
> + *
> + * @return Returns 0 on success or negative error code on failure. This
> + * function will fail if the buffer is set to ready.
> + */
> +/* Called under spin_lock(_irqsave)(&ichan->lock) */
> +static int ipu_update_channel_buffer(enum ipu_channel channel,
> + int buffer_n, dma_addr_t phyaddr)
> +{
> + uint32_t reg;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&ipu_data.lock, flags);
> +
> + if (buffer_n == 0) {
> + reg = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF0_RDY);
> + if (reg & (1UL << channel)) {
> + spin_unlock_irqrestore(&ipu_data.lock, flags);
> + return -EACCES;
> + }
> +
> + /* 44.3.3.1.9 - Row Number 1 (WORD1, offset 0) */
> + idmac_write_ipureg(&ipu_data, dma_param_addr(channel) +
> + 0x0008UL, IPU_IMA_ADDR);
> + idmac_write_ipureg(&ipu_data, phyaddr, IPU_IMA_DATA);
> + } else {
> + reg = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF1_RDY);
> + if (reg & (1UL << channel)) {
> + spin_unlock_irqrestore(&ipu_data.lock, flags);
> + return -EACCES;
> + }
> +
> + /* Check if double-buffering is already enabled */
> + reg = idmac_read_ipureg(&ipu_data, IPU_CHA_DB_MODE_SEL);
> +
> + if (!(reg & (1UL << channel)))
> + idmac_write_ipureg(&ipu_data, reg | (1UL << channel),
> + IPU_CHA_DB_MODE_SEL);
> +
> + /* 44.3.3.1.9 - Row Number 1 (WORD1, offset 1) */
> + idmac_write_ipureg(&ipu_data, dma_param_addr(channel) +
> + 0x0009UL, IPU_IMA_ADDR);
> + idmac_write_ipureg(&ipu_data, phyaddr, IPU_IMA_DATA);
> + }
> +
> + spin_unlock_irqrestore(&ipu_data.lock, flags);
> +
> + return 0;
> +}
> +
> +/* Called under spin_lock_irqsave(&ichan->lock) */
> +static int ipu_submit_channel_buffers(struct idmac_channel *ichan,
> + struct idmac_tx_desc *desc)
> +{
> + struct scatterlist *sg;
> + int i, ret;
> +
> + for (i = 0, sg = desc->sg; i < 2 && sg; i++) {
> + if (!ichan->sg[i]) {
> + ichan->sg[i] = sg;
> +
> + /*
> + * On first invocation this shouldn't be necessary, the
> + * call to ipu_init_channel_buffer() above will set
> + * addresses for us, so we could make it conditional
> + * on status >= IPU_CHANNEL_ENABLED, but doing it again
> + * shouldn't hurt either.
> + */
> + ret = ipu_update_channel_buffer(ichan->dma_chan.chan_id, i,
> + sg_dma_address(sg));
> + if (ret < 0)
> + return ret;
> +
> + ipu_select_buffer(ichan->dma_chan.chan_id, i);
> +
> + sg = sg_next(sg);
> + }
> + }
> +
> + return ret;
> +}

I wonder why the compiler does not warn about ret being used unitialized
here.
The return value of this function should be checked.

> +
> +static dma_cookie_t idmac_tx_submit(struct dma_async_tx_descriptor *tx)
> +{
> + struct idmac_tx_desc *desc = to_tx_desc(tx);
> + struct idmac_channel *ichan = to_idmac_chan(tx->chan);
> + struct idmac *idmac = to_idmac(tx->chan->device);
> + struct ipu *ipu = to_ipu(idmac);
> + dma_cookie_t cookie;
> + unsigned long flags;
> +
> + /* Sanity check */
> + if (!list_empty(&desc->list)) {
> + /* The descriptor doesn't belong to client */
> + dev_err(&ichan->dma_chan.dev->device,
> + "Descriptor %p not prepared!\n", tx);
> + return -EBUSY;
> + }
> +
> + mutex_lock(&ichan->chan_mutex);
> +
> + if (ichan->status < IPU_CHANNEL_READY) {
> + struct idmac_video_param *video = &ichan->params.video;
> + /*
> + * Initial buffer assignment - the first two sg-entries from
> + * the descriptor will end up in the IDMAC buffers
> + */
> + dma_addr_t dma_1 = sg_is_last(desc->sg) ? 0 :
> + sg_dma_address(&desc->sg[1]);
> +
> + WARN_ON(ichan->sg[0] || ichan->sg[1]);
> +
> + cookie = ipu_init_channel_buffer(ichan,
> + video->out_pixel_fmt,
> + video->out_width,
> + video->out_height,
> + video->out_stride,
> + IPU_ROTATE_NONE,
> + sg_dma_address(&desc->sg[0]),
> + dma_1);
> + if (cookie < 0)
> + goto out;
> + }
> +
> + /* ipu->lock can be taken under ichan->lock, but not v.v. */
> + spin_lock_irqsave(&ichan->lock, flags);
> +
> + /* submit_buffers() atomically verifies and fills empty sg slots */
> + ipu_submit_channel_buffers(ichan, desc);
> +
> + spin_unlock_irqrestore(&ichan->lock, flags);
> +
> + cookie = ichan->dma_chan.cookie;
> +
> + if (++cookie < 0)
> + cookie = 1;
> +
> + /* from dmaengine.h: "last cookie value returned to client" */
> + ichan->dma_chan.cookie = cookie;
> + tx->cookie = cookie;
> + spin_lock_irqsave(&ichan->lock, flags);
> + list_add_tail(&desc->list, &ichan->queue);
> + spin_unlock_irqrestore(&ichan->lock, flags);
> +
> + if (ichan->status < IPU_CHANNEL_ENABLED) {
> + int ret = ipu_enable_channel(idmac, ichan);
> + if (ret < 0) {
> + cookie = ret;
> + spin_lock_irqsave(&ichan->lock, flags);
> + list_del_init(&desc->list);
> + spin_unlock_irqrestore(&ichan->lock, flags);
> + tx->cookie = cookie;
> + ichan->dma_chan.cookie = cookie;
> + }
> + }
> +
> + dump_idmac_reg(ipu);
> +
> +out:
> + mutex_unlock(&ichan->chan_mutex);
> +
> + return cookie;
> +}
> +
> +/* Called with ichan->chan_mutex held */
> +static int idmac_desc_alloc(struct idmac_channel *ichan, int n)
> +{
> + struct idmac_tx_desc *desc = vmalloc(n * sizeof(struct idmac_tx_desc));
> + struct idmac *idmac = to_idmac(ichan->dma_chan.device);
> +
> + if (!desc)
> + return -ENOMEM;
> +
> + /* No interrupts, just disable the tasklet for a moment */
> + tasklet_disable(&to_ipu(idmac)->tasklet);
> +
> + ichan->n_tx_desc = n;
> + ichan->desc = desc;
> + INIT_LIST_HEAD(&ichan->queue);
> + INIT_LIST_HEAD(&ichan->free_list);
> +
> + while (n--) {
> + struct dma_async_tx_descriptor *txd = &desc->txd;
> +
> + memset(txd, 0, sizeof(*txd));
> + dma_async_tx_descriptor_init(txd, &ichan->dma_chan);
> + txd->tx_submit = idmac_tx_submit;
> + txd->chan = &ichan->dma_chan;
> + INIT_LIST_HEAD(&txd->tx_list);
> +
> + list_add(&desc->list, &ichan->free_list);
> +
> + desc++;
> + }
> +
> + tasklet_enable(&to_ipu(idmac)->tasklet);
> +
> + return 0;
> +}
> +
> +/**
> + * This function is called to initialize a logical IPU channel.
> + *
> + * @param channel Input parameter for the logical channel ID to initalize.
> + *
> + * @param params Input parameter containing union of channel initialization
> + * parameters.
> + *
> + * @return This function returns 0 on success or negative error code on fail
> + */
> +static int ipu_init_channel(struct idmac *idmac, struct idmac_channel *ichan)
> +{
> + union ipu_channel_param *params = &ichan->params;
> + uint32_t ipu_conf;
> + enum ipu_channel channel = ichan->dma_chan.chan_id;
> + unsigned long flags;
> + uint32_t reg;
> + struct ipu *ipu = to_ipu(idmac);
> + int ret = 0, n_desc = 0;
> +
> + dev_dbg(ipu->dev, "init channel = %d\n", channel);
> +
> + if (channel != IDMAC_SDC_0 && channel != IDMAC_SDC_1 &&
> + channel != IDMAC_IC_7)
> + return -EINVAL;
> +
> + spin_lock_irqsave(&ipu->lock, flags);
> +
> + ipu_conf = _ipu_channel_conf_check(ipu, channel);
> +
> + switch (channel) {
> + case IDMAC_IC_7:
> + n_desc = 16;
> + reg = idmac_read_icreg(ipu, IC_CONF);
> + idmac_write_icreg(ipu, reg & ~IC_CONF_CSI_MEM_WR_EN, IC_CONF);
> + break;
> + case IDMAC_IC_0:
> + n_desc = 16;
> + reg = idmac_read_ipureg(ipu, IPU_FS_PROC_FLOW);
> + idmac_write_ipureg(ipu, reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW);
> + ret = _ipu_ic_init_prpenc(ipu, params, true);
> + break;
> + case IDMAC_SDC_0:
> + case IDMAC_SDC_1:
> + n_desc = 4;
> + default:
> + break;
> + }
> +
> + ipu->channel_init_mask |= 1L << channel;
> +
> + /* Enable IPU sub module */
> + ipu_conf |= _ipu_channel_conf_mask(channel);
> + idmac_write_ipureg(ipu, ipu_conf, IPU_CONF);
> +
> + spin_unlock_irqrestore(&ipu->lock, flags);
> +
> + if (n_desc && !ichan->desc)
> + ret = idmac_desc_alloc(ichan, n_desc);
> +
> + dump_idmac_reg(ipu);
> +
> + return ret;
> +}
> +
> +/**
> + * This function is called to uninitialize a logical IPU channel.
> + *
> + * @param channel Input parameter for the logical channel ID to uninitalize.
> + */
> +static void ipu_uninit_channel(struct idmac *idmac, struct idmac_channel *ichan)
> +{
> + enum ipu_channel channel = ichan->dma_chan.chan_id;
> + unsigned long flags;
> + uint32_t reg;
> + uint32_t mask = 0;
> + uint32_t ipu_conf;
> + struct ipu *ipu = to_ipu(idmac);
> +
> + spin_lock_irqsave(&ipu->lock, flags);
> +
> + if (!(ipu->channel_init_mask & (1L << channel))) {
> + dev_err(ipu->dev, "Channel already uninitialized %d\n",
> + channel);
> + spin_unlock_irqrestore(&ipu->lock, flags);
> + return;
> + }
> +
> + /* Make sure channel is disabled */
> + if (mask & idmac_read_icreg(ipu, IDMAC_CHA_EN)) {
> + dev_err(ipu->dev,
> + "Channel %d is not disabled, disable first\n", channel);
> + spin_unlock_irqrestore(&ipu->lock, flags);
> + return;
> + }

The channel *is* disabled since this function is called from
idmac_free_chan_resources() which calls __idmac_terminate_all()
beforehand which in turn calls ipu_disable_channel().

> +
> + /* Reset the double buffer */
> + reg = idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL);
> + idmac_write_ipureg(ipu, reg & ~mask, IPU_CHA_DB_MODE_SEL);
> +
> + ichan->sec_chan_en = false;
> +
> + switch (channel) {
> + case IDMAC_IC_7:
> + reg = idmac_read_icreg(ipu, IC_CONF);
> + idmac_write_icreg(ipu, reg & ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN),
> + IC_CONF);
> + break;
> + case IDMAC_IC_0:
> + reg = idmac_read_icreg(ipu, IC_CONF);
> + idmac_write_icreg(ipu, reg & ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1),
> + IC_CONF);
> + break;
> + case IDMAC_SDC_0:
> + case IDMAC_SDC_1:
> + default:
> + break;
> + }
> +
> + ipu->channel_init_mask &= ~(1L << channel);
> +
> + ipu_conf = idmac_read_ipureg(ipu, IPU_CONF);
> + ipu_conf &= ~_ipu_channel_conf_mask(channel);
> + idmac_write_ipureg(ipu, ipu_conf, IPU_CONF);
> + if (!ipu_conf)
> + clk_disable(ipu->ipu_clk);
> +
> + spin_unlock_irqrestore(&ipu->lock, flags);
> +
> + ichan->n_tx_desc = 0;
> + vfree(ichan->desc);
> + ichan->desc = NULL;
> +}
> +
> +/**
> + * This function disables a logical channel.
> + *
> + * @param channel Input parameter for the logical channel ID.
> + *
> + * @param wait_for_stop Flag to set whether to wait for channel end
> + * of frame or return immediately.
> + *
> + * @return This function returns 0 on success or negative error code on
> + * fail.
> + */
> +static int ipu_disable_channel(struct idmac *idmac, enum ipu_channel channel,
> + bool wait_for_stop)
> +{
> + struct ipu *ipu = to_ipu(idmac);
> + uint32_t reg;
> + unsigned long flags;
> + uint32_t chan_mask = 1UL << channel;
> + uint32_t timeout;
> + uint32_t eof_intr;
> +
> + if (wait_for_stop && channel != IDMAC_SDC_1 && channel != IDMAC_SDC_0) {
> + timeout = 40;
> + /* This waiting always fails. Related to spurious irq problem */
> + while ((idmac_read_icreg(ipu, IDMAC_CHA_BUSY) & chan_mask) ||
> + (_ipu_channel_status(ipu, channel) == TASK_STAT_ACTIVE)) {
> + timeout--;
> + msleep(10);
> +
> + if (!timeout) {
> + dev_dbg(ipu->dev,
> + "Warning: timeout waiting for channel %u to "
> + "stop: buf0_rdy = 0x%08X, buf1_rdy = 0x%08X, "
> + "busy = 0x%08X, tstat = 0x%08X\n", channel,
> + idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY),
> + idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY),
> + idmac_read_icreg(ipu, IDMAC_CHA_BUSY),
> + idmac_read_ipureg(ipu, IPU_TASKS_STAT));
> + break;
> + }
> + }
> + dev_dbg(ipu->dev, "timeout = %d * 10ms\n", 40 - timeout);
> + }
> + /* SDC BG and FG must be disabled before DMA is disabled */
> + if (wait_for_stop && (channel == IDMAC_SDC_0 ||
> + channel == IDMAC_SDC_1)) {
> + if (channel == IDMAC_SDC_0)
> + eof_intr = IPU_IRQ_SDC_BG_EOF;
> + else
> + eof_intr = IPU_IRQ_SDC_FG_EOF;
> +
> + for (timeout = 5;
> + timeout && !ipu_irq_status(eof_intr); timeout--)
> + msleep(5);
> + }
> +
> + spin_lock_irqsave(&ipu->lock, flags);
> +
> + /* Disable IC task */
> + _ipu_ic_disable_task(ipu, channel);
> +
> + /* Disable DMA channel(s) */
> + reg = idmac_read_icreg(ipu, IDMAC_CHA_EN);
> + idmac_write_icreg(ipu, reg & ~chan_mask, IDMAC_CHA_EN);
> +
> + /*
> + * Problem (observed with channel DMAIC_7): after enabling the channel
> + * and initialising buffers, there comes an interrupt with current still
> + * pointing at buffer 0, whereas it should use buffer 0 first and only
> + * generate an interrupt when it is done, then current should already
> + * point to buffer 1. This spurious interrupt also comes on channel
> + * DMASDC_0. With DMAIC_7 normally, is we just leave the ISR after the
> + * first interrupt, there comes the second with current correctly
> + * pointing to buffer 1 this time. But sometimes this second interrupt
> + * doesn't come and the channel hangs. Clearing BUFx_RDY when disabling
> + * the channel seems to prevent the channel from hanging, but it doesn't
> + * prevent the spurious interrupt. This might also be unsafe. Think
> + * about the IDMAC controller trying to switch to a buffer, when we
> + * clear the ready bit, and re-enable it a moment later.
> + */
> + reg = idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY);
> + idmac_write_ipureg(ipu, 0, IPU_CHA_BUF0_RDY);
> + idmac_write_ipureg(ipu, reg & ~(1UL << channel), IPU_CHA_BUF0_RDY);
> +
> + reg = idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY);
> + idmac_write_ipureg(ipu, 0, IPU_CHA_BUF1_RDY);
> + idmac_write_ipureg(ipu, reg & ~(1UL << channel), IPU_CHA_BUF1_RDY);
> +
> + spin_unlock_irqrestore(&ipu->lock, flags);
> +
> + return 0;
> +}
> +
> +/*
> + * We have several possibilities here:
> + * current BUF next BUF
> + *
> + * not last sg next not last sg
> + * not last sg next last sg
> + * last sg first sg from next descriptor
> + * last sg NULL
> + *
> + * Besides, the descriptor queue might be empty or not. We process all these
> + * cases carefully.
> + */
> +static irqreturn_t idmac_interrupt(int irq, void *dev_id)
> +{
> + struct idmac_channel *ichan = dev_id;
> + unsigned int chan_id = ichan->dma_chan.chan_id;
> + struct scatterlist **sg, *sgnext, *sgnew = NULL;
> + /* Next transfer descriptor */
> + struct idmac_tx_desc *desc = NULL, *descnew;
> + dma_async_tx_callback callback;
> + void *callback_param;
> + bool done = false;
> + u32 ready0 = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF0_RDY),
> + ready1 = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF1_RDY),
> + curbuf = idmac_read_ipureg(&ipu_data, IPU_CHA_CUR_BUF);
> +
> + /* IDMAC has cleared the respective BUFx_RDY bit, we manage the buffer */
> +
> + pr_debug("IDMAC irq %d\n", irq);
> + /* Other interrupts do not interfere with this channel */
> + spin_lock(&ichan->lock);
> +
> + if (unlikely(chan_id != IDMAC_SDC_0 && chan_id != IDMAC_SDC_1 &&
> + ((curbuf >> chan_id) & 1) == ichan->active_buffer)) {
> + int i = 100;
> +
> + /* This doesn't help. See comment in ipu_disable_channel() */
> + while (--i) {
> + curbuf = idmac_read_ipureg(&ipu_data, IPU_CHA_CUR_BUF);
> + if (((curbuf >> chan_id) & 1) != ichan->active_buffer)
> + break;
> + cpu_relax();
> + }
> +
> + if (!i) {
> + spin_unlock(&ichan->lock);
> + dev_err(ichan->dma_chan.device->dev,
> + "IRQ on active buffer on channel %x, active "
> + "%d, ready %x, %x, current %x!\n", chan_id,
> + ichan->active_buffer, ready0, ready1, curbuf);
> + return IRQ_NONE;
> + }
> + }
> +
> + if (unlikely((ichan->active_buffer && (ready1 >> chan_id) & 1) ||
> + (!ichan->active_buffer && (ready0 >> chan_id) & 1)
> + )) {
> + spin_unlock(&ichan->lock);
> + dev_dbg(ichan->dma_chan.device->dev,
> + "IRQ with active buffer still ready on channel %x, "
> + "active %d, ready %x, %x!\n", chan_id,
> + ichan->active_buffer, ready0, ready1);
> + return IRQ_NONE;
> + }
> +
> + if (unlikely(list_empty(&ichan->queue))) {
> + spin_unlock(&ichan->lock);
> + dev_err(ichan->dma_chan.device->dev,
> + "IRQ without queued buffers on channel %x, active %d, "
> + "ready %x, %x!\n", chan_id,
> + ichan->active_buffer, ready0, ready1);
> + return IRQ_NONE;
> + }
> +
> + /*
> + * active_buffer is a software flag, it shows which buffer we are
> + * currently expecting back from the hardware, IDMAC should be
> + * processing the other buffer already
> + */
> + sg = &ichan->sg[ichan->active_buffer];
> + sgnext = ichan->sg[!ichan->active_buffer];
> +
> + /*
> + * if sgnext == NULL sg must be the last element in a scatterlist and
> + * queue must be empty
> + */
> + if (unlikely(!sgnext)) {
> + if (unlikely(sg_next(*sg))) {
> + dev_err(ichan->dma_chan.device->dev,
> + "Broken buffer-update locking on channel %x!\n",
> + chan_id);
> + /* We'll let the user catch up */
> + } else {
> + /* Underrun */
> + _ipu_ic_disable_task(&ipu_data, chan_id);
> + dev_dbg(ichan->dma_chan.device->dev,
> + "Underrun on channel %x\n", chan_id);
> + ichan->status = IPU_CHANNEL_READY;
> + /* Continue to check for complete descriptor */
> + }
> + }
> +
> + desc = list_entry(ichan->queue.next, struct idmac_tx_desc, list);
> +
> + /* First calculate and submit the next sg element */
> + if (likely(sgnext))
> + sgnew = sg_next(sgnext);
> +
> + if (unlikely(!sgnew)) {
> + /* Start a new scatterlist, if any queued */
> + if (likely(desc->list.next != &ichan->queue)) {
> + descnew = list_entry(desc->list.next,
> + struct idmac_tx_desc, list);
> + sgnew = &descnew->sg[0];
> + }
> + }
> +
> + if (unlikely(!sg_next(*sg)) || !sgnext) {
> + /*
> + * Last element in scatterlist done, remove from the queue,
> + * _init for debugging
> + */
> + list_del_init(&desc->list);
> + done = true;
> + }
> +
> + *sg = sgnew;
> +
> + if (likely(sgnew)) {
> + int ret;
> +
> + ret = ipu_update_channel_buffer(chan_id, ichan->active_buffer,
> + sg_dma_address(*sg));
> + if (ret < 0)
> + dev_err(ichan->dma_chan.device->dev,
> + "Failed to update buffer on channel %x buffer %d!\n",
> + chan_id, ichan->active_buffer);
> + else
> + ipu_select_buffer(chan_id, ichan->active_buffer);
> + }
> +
> + /* Flip the active buffer - even if update above failed */
> + ichan->active_buffer = !ichan->active_buffer;
> + if (done)
> + ichan->completed = desc->txd.cookie;
> +
> + callback = desc->txd.callback;
> + callback_param = desc->txd.callback_param;
> +
> + spin_unlock(&ichan->lock);
> +
> + if (done && (desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
> + callback(callback_param);
> +
> + return IRQ_HANDLED;
> +}
> +
> +static void ipu_gc_tasklet(unsigned long arg)
> +{
> + struct ipu *ipu = (struct ipu *)arg;
> + int i;
> +
> + for (i = 0; i < IPU_CHANNELS_NUM; i++) {
> + struct idmac_channel *ichan = ipu->channel + i;
> + struct idmac_tx_desc *desc;
> + unsigned long flags;
> + int j;
> +
> + for (j = 0; j < ichan->n_tx_desc; j++) {
> + desc = ichan->desc + j;
> + spin_lock_irqsave(&ichan->lock, flags);
> + if (async_tx_test_ack(&desc->txd)) {
> + list_move(&desc->list, &ichan->free_list);
> + async_tx_clear_ack(&desc->txd);
> + }
> + spin_unlock_irqrestore(&ichan->lock, flags);
> + }
> + }
> +}
> +
> +/*
> + * At the time .device_alloc_chan_resources() method is called, we cannot know,
> + * whether the client will accept the channel. Thus we must only check, if we
> + * can satisfy client's request but the only real criterion to verify, whether
> + * the client has accepted our offer is the client_count. That's why we have to
> + * perform the rest of our allocation tasks on the first call to this function.
> + */
> +static struct dma_async_tx_descriptor *idmac_prep_slave_sg(struct dma_chan *chan,
> + struct scatterlist *sgl, unsigned int sg_len,
> + enum dma_data_direction direction, unsigned long tx_flags)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> + struct idmac_tx_desc *desc = NULL;
> + struct dma_async_tx_descriptor *txd = NULL;
> + unsigned long flags;
> +
> + /* We only can handle these three channels so far */
> + if (ichan->dma_chan.chan_id != IDMAC_SDC_0 && ichan->dma_chan.chan_id != IDMAC_SDC_1 &&
> + ichan->dma_chan.chan_id != IDMAC_IC_7)
> + return NULL;
> +
> + if (direction != DMA_FROM_DEVICE && direction != DMA_TO_DEVICE) {
> + dev_err(chan->device->dev, "Invalid DMA direction %d!\n", direction);
> + return NULL;
> + }
> +
> + mutex_lock(&ichan->chan_mutex);
> +
> + spin_lock_irqsave(&ichan->lock, flags);
> + if (!list_empty(&ichan->free_list)) {
> + desc = list_entry(ichan->free_list.next,
> + struct idmac_tx_desc, list);
> +
> + list_del_init(&desc->list);
> +
> + desc->sg_len = sg_len;
> + desc->sg = sgl;
> + txd = &desc->txd;
> + txd->flags = tx_flags;
> + }
> + spin_unlock_irqrestore(&ichan->lock, flags);
> +
> + mutex_unlock(&ichan->chan_mutex);
> +
> + tasklet_schedule(&to_ipu(to_idmac(chan->device))->tasklet);
> +
> + return txd;
> +}
> +
> +/* Re-select the current buffer and re-activate the channel */
> +static void idmac_issue_pending(struct dma_chan *chan)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> + struct idmac *idmac = to_idmac(chan->device);
> + struct ipu *ipu = to_ipu(idmac);
> + unsigned long flags;
> +
> + /* This is not always needed, but doesn't hurt either */
> + spin_lock_irqsave(&ipu->lock, flags);
> + ipu_select_buffer(ichan->dma_chan.chan_id, ichan->active_buffer);
> + spin_unlock_irqrestore(&ipu->lock, flags);
> +
> + /*
> + * Might need to perform some parts of initialisation from
> + * ipu_enable_channel(), but not all, we do not want to reset to buffer
> + * 0, don't need to set priority again either, but re-enabling the task
> + * and the channel might be a good idea.
> + */
> +}
> +
> +static void __idmac_terminate_all(struct dma_chan *chan)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> + struct idmac *idmac = to_idmac(chan->device);
> + unsigned long flags;
> + int i;
> +
> + ipu_disable_channel(idmac, chan->chan_id,
> + ichan->status >= IPU_CHANNEL_ENABLED);
> +
> + /* ichan->queue is modified in ISR, have to spinlock */
> + tasklet_disable(&to_ipu(idmac)->tasklet);
> +
> + spin_lock_irqsave(&ichan->lock, flags);
> + list_splice_init(&ichan->queue, &ichan->free_list);
> +
> + if (ichan->desc)
> + for (i = 0; i < ichan->n_tx_desc; i++) {
> + struct idmac_tx_desc *desc = ichan->desc + i;
> + if (list_empty(&desc->list))
> + /* Descriptor was prepared, but not submitted */
> + list_add(&desc->list,
> + &ichan->free_list);
> +
> + async_tx_clear_ack(&desc->txd);
> + }
> +
> + ichan->sg[0] = NULL;
> + ichan->sg[1] = NULL;
> + spin_unlock_irqrestore(&ichan->lock, flags);
> +
> + tasklet_enable(&to_ipu(idmac)->tasklet);
> +
> + ichan->status = IPU_CHANNEL_INITIALIZED;
> +}
> +
> +static void idmac_terminate_all(struct dma_chan *chan)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> +
> + mutex_lock(&ichan->chan_mutex);
> +
> + __idmac_terminate_all(chan);
> +
> + mutex_unlock(&ichan->chan_mutex);
> +}
> +
> +static int idmac_alloc_chan_resources(struct dma_chan *chan)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> + struct idmac *idmac = to_idmac(chan->device);
> + int ret;
> +
> + /* dmaengine.c now guarantees to only offer free channels */
> + BUG_ON(chan->client_count > 1);
> + WARN_ON(ichan->status != IPU_CHANNEL_FREE);
> +
> + /* The channel is free yet, no need to protect? */
> + mutex_lock(&ichan->chan_mutex);

No, this is called under a mutex from dmaengine.c, so you won't get
called twice + ipu_init_channel grabs a spinlock.

> +
> + chan->cookie = 1;
> + ichan->completed = -ENXIO;
> +
> + ret = request_irq(ichan->eof_irq, idmac_interrupt, 0,
> + "idmac", ichan);
> + if (ret < 0)
> + goto out;
> +
> + ret = ipu_init_channel(idmac, ichan);
> + if (ret < 0) {
> + free_irq(ichan->eof_irq, ichan);
> + goto out;
> + }
> +
> + ichan->status = IPU_CHANNEL_INITIALIZED;
> +
> +out:
> + mutex_unlock(&ichan->chan_mutex);
> +
> + dev_dbg(&ichan->dma_chan.dev->device, "Found channel 0x%x, irq %d\n",
> + ichan->dma_chan.chan_id, ichan->eof_irq);

As this is also the error path, 'Found channel' seems wrong.

> +
> + return 0;

Always return 0?

> +}
> +
> +static void idmac_free_chan_resources(struct dma_chan *chan)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> + struct idmac *idmac = to_idmac(chan->device);
> +
> + mutex_lock(&ichan->chan_mutex);
> +
> + __idmac_terminate_all(chan);
> +
> + if (ichan->status > IPU_CHANNEL_FREE)
> + free_irq(ichan->eof_irq, ichan);
> +
> + ichan->status = IPU_CHANNEL_FREE;
> +
> + /*
> + * The channel should be uninitialized by now already, but the original
> + * Freescale driver did it again, and it shouldn't hurt
> + */

I doubt that since this is the only place where you call
ipu_uninit_channel().

> + ipu_uninit_channel(idmac, ichan);
> +
> + mutex_unlock(&ichan->chan_mutex);
> +
> + tasklet_schedule(&to_ipu(idmac)->tasklet);
> +}
> +
> +static enum dma_status idmac_is_tx_complete(struct dma_chan *chan,
> + dma_cookie_t cookie, dma_cookie_t *done, dma_cookie_t *used)
> +{
> + struct idmac_channel *ichan = to_idmac_chan(chan);
> +
> + if (done)
> + *done = ichan->completed;
> + if (used)
> + *used = chan->cookie;
> + if (cookie != chan->cookie)
> + return DMA_ERROR;
> + return DMA_SUCCESS;
> +}
> +
> +static int __init ipu_idmac_init(struct ipu *ipu)
> +{
> + struct idmac *idmac = &ipu->idmac;
> + struct dma_device *dma = &idmac->dma;
> + int i;
> +
> + dma_cap_set(DMA_SLAVE, dma->cap_mask);
> + dma_cap_set(DMA_PRIVATE, dma->cap_mask);
> +
> + /* Compulsory common fields */
> + dma->dev = ipu->dev;
> + dma->device_alloc_chan_resources = idmac_alloc_chan_resources;
> + dma->device_free_chan_resources = idmac_free_chan_resources;
> + dma->device_is_tx_complete = idmac_is_tx_complete;
> + dma->device_issue_pending = idmac_issue_pending;
> +
> + /* Compulsory for DMA_SLAVE fields */
> + dma->device_prep_slave_sg = idmac_prep_slave_sg;
> + dma->device_terminate_all = idmac_terminate_all;
> +
> + INIT_LIST_HEAD(&dma->channels);
> + for (i = 0; i < IPU_CHANNELS_NUM; i++) {
> + struct idmac_channel *ichan = ipu->channel + i;
> + struct dma_chan *dma_chan = &ichan->dma_chan;
> +
> + spin_lock_init(&ichan->lock);
> + mutex_init(&ichan->chan_mutex);

Having two locking methods for one dma channel looks like a recipe for
trouble.

> +
> + ichan->eof_irq = MXC_IPU_INT_BASE + i;
> + ichan->status = IPU_CHANNEL_FREE;
> + ichan->sec_chan_en = false;
> + ichan->completed = -ENXIO;
> +
> + dma_chan->device = &idmac->dma;
> + dma_chan->cookie = 1;
> + dma_chan->chan_id = i;
> + list_add_tail(&ichan->dma_chan.device_node, &dma->channels);
> + }
> +
> + idmac_write_icreg(ipu, 0x00000070, IDMAC_CONF);
> +
> + return dma_async_device_register(&idmac->dma);
> +}
> +
> +static void ipu_idmac_exit(struct ipu *ipu)
> +{
> + int i;
> + struct idmac *idmac = &ipu->idmac;
> +
> + for (i = 0; i < IPU_CHANNELS_NUM; i++) {
> + struct idmac_channel *ichan = ipu->channel + i;
> +
> + idmac_terminate_all(&ichan->dma_chan);
> + idmac_prep_slave_sg(&ichan->dma_chan, NULL, 0, DMA_NONE, 0);
> + }
> +
> + dma_async_device_unregister(&idmac->dma);
> +}
> +
> +/*****************************************************************************
> + * IPU common probe / remove
> + */
> +
> +static int ipu_probe(struct platform_device *pdev)
> +{
> + struct ipu_platform_data *pdata = pdev->dev.platform_data;
> + struct resource *mem_ipu, *mem_ic;
> + int ret;
> +
> + spin_lock_init(&ipu_data.lock);
> +
> + mem_ipu = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> + mem_ic = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> + if (!pdata || !mem_ipu || !mem_ic)
> + return -EINVAL;
> +
> + ipu_data.dev = &pdev->dev;
> +
> + platform_set_drvdata(pdev, &ipu_data);
> +
> + ret = platform_get_irq(pdev, 0);
> + if (ret < 0)
> + goto err_noirq;
> +
> + ipu_data.irq_fn = ret;
> + ret = platform_get_irq(pdev, 1);
> + if (ret < 0)
> + goto err_noirq;
> +
> + ipu_data.irq_err = ret;
> + ipu_data.irq_base = pdata->irq_base;
> +
> + dev_dbg(&pdev->dev, "fn irq %u, err irq %u, irq-base %u\n",
> + ipu_data.irq_fn, ipu_data.irq_err, ipu_data.irq_base);
> +
> + /* Remap IPU common registers */
> + ipu_data.reg_ipu = ioremap(mem_ipu->start,
> + mem_ipu->end - mem_ipu->start + 1);
> + if (!ipu_data.reg_ipu) {
> + ret = -ENOMEM;
> + goto err_ioremap_ipu;
> + }
> +
> + /* Remap Image Converter and Image DMA Controller registers */
> + ipu_data.reg_ic = ioremap(mem_ic->start,
> + mem_ic->end - mem_ic->start + 1);
> + if (!ipu_data.reg_ic) {
> + ret = -ENOMEM;
> + goto err_ioremap_ic;
> + }
> +
> + /* Get IPU clock */
> + ipu_data.ipu_clk = clk_get(&pdev->dev, "ipu_clk");
> + if (IS_ERR(ipu_data.ipu_clk)) {
> + ret = PTR_ERR(ipu_data.ipu_clk);
> + goto err_clk_get;
> + }
> +
> + /* Make sure IPU HSP clock is running */
> + clk_enable(ipu_data.ipu_clk);

You start the clock in the probe function unconditionally and disable
it again when all channels are disabled. I suppose you can't access the
IPU registers when the clock is disabled, right? If this is the case
then _ipu_channel_conf_check() above should not warn when the clock has
to be (re-)enabled, because that's the case everytime all channels are
freed and then a new one gets registered.

> +
> + /* Disable all interrupts */
> + idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_1);
> + idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_2);
> + idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_3);
> + idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_4);
> + idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_5);
> +
> + dev_dbg(&pdev->dev, "%s @ 0x%08lx, fn irq %u, err irq %u\n", pdev->name,
> + (unsigned long)mem_ipu->start, ipu_data.irq_fn, ipu_data.irq_err);
> +
> + ret = ipu_irq_attach_irq(&ipu_data, pdev);
> + if (ret < 0)
> + goto err_attach_irq;
> +
> + /* Initialize DMA engine */
> + ret = ipu_idmac_init(&ipu_data);
> + if (ret < 0)
> + goto err_idmac_init;
> +
> + tasklet_init(&ipu_data.tasklet, ipu_gc_tasklet, (unsigned long)&ipu_data);
> +
> + ipu_data.dev = &pdev->dev;
> +
> + dev_dbg(ipu_data.dev, "IPU initialized\n");
> +
> + return 0;
> +
> +err_idmac_init:
> +err_attach_irq:
> + ipu_irq_detach_irq(&ipu_data, pdev);
> + clk_put(ipu_data.ipu_clk);
> +err_clk_get:
> + iounmap(ipu_data.reg_ic);
> +err_ioremap_ic:
> + iounmap(ipu_data.reg_ipu);
> +err_ioremap_ipu:
> +err_noirq:
> + dev_err(&pdev->dev, "Failed to probe IPU: %d\n", ret);
> + return ret;
> +}
> +
> +static int ipu_remove(struct platform_device *pdev)
> +{
> + struct ipu *ipu = platform_get_drvdata(pdev);
> +
> + ipu_idmac_exit(ipu);
> + ipu_irq_detach_irq(ipu, pdev);
> + clk_put(ipu->ipu_clk);
> + iounmap(ipu->reg_ic);
> + iounmap(ipu->reg_ipu);
> + tasklet_kill(&ipu->tasklet);
> + platform_set_drvdata(pdev, NULL);
> +
> + return 0;
> +}
> +
> +/*
> + * We need two MEM resources - with IPU-common and Image Converter registers,
> + * including PF_CONF and IDMAC_* registers, and two IRQs - function and error
> + */
> +static struct platform_driver ipu_platform_driver = {
> + .driver = {
> + .name = "ipu-core",
> + .owner = THIS_MODULE,
> + },
> + .remove = ipu_remove,
> +};
> +
> +static int __init ipu_init(void)
> +{
> + return platform_driver_probe(&ipu_platform_driver, ipu_probe);
> +}
> +subsys_initcall(ipu_init);
> +
> +MODULE_DESCRIPTION("IPU core driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
> +MODULE_ALIAS("platform:ipu-core");

<snip>

> +
> +#endif
> diff --git a/drivers/mfd/ipu/ipu_irq.c b/drivers/mfd/ipu/ipu_irq.c
> new file mode 100644
> index 0000000..3239d6b
> --- /dev/null
> +++ b/drivers/mfd/ipu/ipu_irq.c
> @@ -0,0 +1,277 @@
> +/*
> + * Copyright (C) 2008
> + * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 as
> + * published by the Free Software Foundation.
> + */
> +
> +#include <linux/init.h>
> +#include <linux/err.h>
> +#include <linux/spinlock.h>
> +#include <linux/delay.h>
> +#include <linux/clk.h>
> +#include <linux/irq.h>
> +
> +#include <asm/io.h>

s/asm/linux

> +
> +#include <mach/ipu.h>
> +
> +#include "ipu_intern.h"
> +
> +/*
> + * Register read / write - shall be inlined by the compiler
> + */
> +static u32 ipu_read_reg(struct ipu *ipu, unsigned long reg)
> +{
> + return __raw_readl(ipu->reg_ipu + reg);
> +}
> +
> +static void ipu_write_reg(struct ipu *ipu, u32 value, unsigned long reg)
> +{
> + __raw_writel(value, ipu->reg_ipu + reg);
> +}
> +
> +
> +/*
> + * IPU IRQ chip driver
> + */
> +
> +#define IPU_IRQ_NR_FN_BANKS 3
> +#define IPU_IRQ_NR_ERR_BANKS 2
> +
> +struct ipu_irq_bank {
> + int nr_irqs;
> + unsigned int control;
> + unsigned int status;
> + unsigned int irq_base;
> + spinlock_t lock;
> + struct ipu *ipu;
> +};
> +
> +static struct ipu_irq_bank irq_bank_fn[IPU_IRQ_NR_FN_BANKS] = {
> + /* 3 groups of functional interrupts */
> + {
> + .nr_irqs = 32,
> + .control = IPU_INT_CTRL_1,
> + .status = IPU_INT_STAT_1,
> + }, {
> + .nr_irqs = 32,
> + .control = IPU_INT_CTRL_2,
> + .status = IPU_INT_STAT_2,
> + }, {
> + .nr_irqs = 24,
> + .control = IPU_INT_CTRL_3,
> + .status = IPU_INT_STAT_3,
> + },
> +};
> +
> +static struct ipu_irq_bank irq_bank_err[IPU_IRQ_NR_ERR_BANKS] = {
> + /* 2 groups of error interrupts */
> + {
> + .nr_irqs = 32,
> + .control = IPU_INT_CTRL_4,
> + .status = IPU_INT_STAT_4,
> + }, {
> + .nr_irqs = 17,
> + .control = IPU_INT_CTRL_5,
> + .status = IPU_INT_STAT_5,
> + },
> +};
> +
> +#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
> +#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
> +#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)

Do we really need an interrupt handler behind each status bit the ipu
has to offer? This looks quite expensive

> +
> +static void ipu_irq_unmask(unsigned int irq)
> +{
> + struct ipu_irq_bank *bank = get_irq_chip_data(irq);
> + uint32_t reg;
> + unsigned long lock_flags;
> +
> + spin_lock_irqsave(&bank->lock, lock_flags);
> +
> + reg = ipu_read_reg(bank->ipu, bank->control);
> + reg |= (1UL << (irq - bank->irq_base));
> + ipu_write_reg(bank->ipu, reg, bank->control);
> +
> + spin_unlock_irqrestore(&bank->lock, lock_flags);
> +}
> +
> +static void ipu_irq_mask(unsigned int irq)
> +{
> + struct ipu_irq_bank *bank = get_irq_chip_data(irq);
> + uint32_t reg;
> + unsigned long lock_flags;
> +
> + spin_lock_irqsave(&bank->lock, lock_flags);
> +
> + reg = ipu_read_reg(bank->ipu, bank->control);
> + reg &= ~(1UL << (irq - bank->irq_base));
> + ipu_write_reg(bank->ipu, reg, bank->control);
> +
> + spin_unlock_irqrestore(&bank->lock, lock_flags);
> +}
> +
> +static void ipu_irq_ack(unsigned int irq)
> +{
> + struct ipu_irq_bank *bank = get_irq_chip_data(irq);
> +
> + ipu_write_reg(bank->ipu, 1UL << (irq - bank->irq_base), bank->status);
> +}
> +
> +/**
> + * Returns the current interrupt status for the specified IRQ.
> + *
> + * @param irq Interrupt line to get status for.
> + *
> + * @return Returns true if the interrupt is pending/asserted or false if
> + * the interrupt is not pending.
> + */
> +bool ipu_irq_status(uint32_t irq)
> +{
> + struct ipu_irq_bank *bank = get_irq_chip_data(irq);
> +
> + if (ipu_read_reg(bank->ipu, bank->status) &
> + (1UL << (irq - bank->irq_base)))
> + return true;
> + else
> + return false;
> +}
> +
> +/* Chained IRQ handler for IPU error interrupt */
> +static void ipu_irq_err(unsigned int irq, struct irq_desc *desc)
> +{
> + struct ipu *ipu = get_irq_data(irq);
> + u32 status;
> + int i, line;
> +
> + for (i = 0; i < IPU_IRQ_NR_ERR_BANKS; i++) {
> + status = ipu_read_reg(ipu, irq_bank_err[i].status);
> + /*
> + * Don't think we have to clear all interrupts here, thy will
> + * be acked by ->handle_irq() (handle_level_irq). However, we
> + * might want to clear unhandled interrupts after the loop...
> + */
> + status &= ipu_read_reg(ipu, irq_bank_err[i].control);
> + while ((line = ffs(status))) {
> + status &= ~(1UL << (line - 1));
> + generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
> + }
> + }
> +}
> +
> +/* Chained IRQ handler for IPU function interrupt */
> +static void ipu_irq_fn(unsigned int irq, struct irq_desc *desc)
> +{
> + struct ipu *ipu = get_irq_data(irq);
> + u32 status;
> + int i, line;
> +
> + for (i = 0; i < IPU_IRQ_NR_FN_BANKS; i++) {
> + status = ipu_read_reg(ipu, irq_bank_fn[i].status);
> + /* Not clearing all interrupts, see above */
> + status &= ipu_read_reg(ipu, irq_bank_fn[i].control);
> + while ((line = ffs(status))) {
> + status &= ~(1UL << (line - 1));
> + generic_handle_irq(irq_bank_fn[i].irq_base + line - 1);
> + }
> + }
> +}
> +
> +static struct irq_chip ipu_irq_chip = {
> + .name = "ipu_irq",
> + .ack = ipu_irq_ack,
> + .mask = ipu_irq_mask,
> + .unmask = ipu_irq_unmask,
> +};
> +
> +/* Install the IRQ handler */
> +int ipu_irq_attach_irq(struct ipu *ipu, struct platform_device *dev)
> +{
> + struct ipu_platform_data *pdata = dev->dev.platform_data;
> + unsigned int irq, irq_base, i;
> +
> + irq_base = pdata->irq_base;
> +
> + for (i = 0; i < IPU_IRQ_NR_FN_BANKS; i++) {
> + irq_bank_fn[i].ipu = ipu;
> + irq_bank_fn[i].irq_base = irq_base;
> + spin_lock_init(&irq_bank_fn[i].lock);
> +
> + dev_dbg(&dev->dev, "IPU-IRQ: Setting up %d function irqs bank "
> + "%d at %u\n", irq_bank_fn[i].nr_irqs, i, irq_base);
> +
> + for (irq = irq_base; irq < irq_base + irq_bank_fn[i].nr_irqs;
> + irq++) {
> + int ret = set_irq_chip(irq, &ipu_irq_chip);
> + if (ret < 0)
> + return ret;
> + ret = set_irq_chip_data(irq, irq_bank_fn + i);
> + if (ret < 0)
> + return ret;
> + set_irq_handler(irq, handle_level_irq);
> +#ifdef CONFIG_ARM
> + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
> +#endif
> + }
> + spin_lock_init(&irq_bank_fn[i].lock);
> + irq_base += irq_bank_fn[i].nr_irqs;
> + }
> +
> + for (i = 0; i < IPU_IRQ_NR_ERR_BANKS; i++) {
> + irq_bank_err[i].ipu = ipu;
> + irq_bank_err[i].irq_base = irq_base;
> + spin_lock_init(&irq_bank_fn[i].lock);
> +
> + dev_dbg(&dev->dev, "IPU-IRQ: Setting up %d error irqs bank "
> + "%d at %u\n", irq_bank_err[i].nr_irqs, i, irq_base);
> +
> + for (irq = irq_base; irq < irq_base + irq_bank_err[i].nr_irqs;
> + irq++) {
> + int ret = set_irq_chip(irq, &ipu_irq_chip);
> + if (ret < 0)
> + return ret;
> + ret = set_irq_chip_data(irq, irq_bank_err + i);
> + if (ret < 0)
> + return ret;
> + set_irq_handler(irq, handle_level_irq);
> +#ifdef CONFIG_ARM
> + set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
> +#endif
> + }
> + spin_lock_init(&irq_bank_err[i].lock);
> + irq_base += irq_bank_err[i].nr_irqs;
> + }
> +
> + set_irq_data(ipu->irq_fn, ipu);
> + set_irq_chained_handler(ipu->irq_fn, ipu_irq_fn);
> +
> + set_irq_data(ipu->irq_err, ipu);
> + set_irq_chained_handler(ipu->irq_err, ipu_irq_err);
> +
> + return 0;
> +}
> +
> +void ipu_irq_detach_irq(struct ipu *ipu, struct platform_device *dev)
> +{
> + struct ipu_platform_data *pdata = dev->dev.platform_data;
> + unsigned int irq, irq_base;
> +
> + irq_base = pdata->irq_base;
> +
> + set_irq_chained_handler(ipu->irq_fn, NULL);
> + set_irq_data(ipu->irq_fn, NULL);
> +
> + set_irq_chained_handler(ipu->irq_err, NULL);
> + set_irq_data(ipu->irq_err, NULL);
> +
> + for (irq = irq_base; irq < irq_base + IPU_IRQ_NR_IRQS; irq++) {
> +#ifdef CONFIG_ARM
> + set_irq_flags(irq, 0);
> +#endif
> + set_irq_chip(irq, NULL);
> + set_irq_chip_data(irq, NULL);
> + }
> +}
> --
> 1.5.4
>
>

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-22 11:56:32

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Hi Sascha,

Thanks for the review and for the comments! I'm fixing them all, except
for a couple points which I'm not sure I agree with:

On Thu, 18 Dec 2008, Sascha Hauer wrote:

> Hi Guennadi,
>
> On Thu, Dec 18, 2008 at 02:26:19PM +0100, Guennadi Liakhovetski wrote:
> > From: Guennadi Liakhovetski <[email protected]>
> >
> > i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
> > Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
> > Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
> > (PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
> > CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
> > and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
> > supported over dmaengine and irq-chip APIs respectively.
> >
> > IDMAC is a specialised DMA controller, its DMA channels cannot be used for
> > general-purpose operations, even though it might be possible to configure
> > a memory-to-memory channel for memcpy operation. This driver will not work
> > with generic dmaengine clients, clients, wishing to use it must use
> > respective wrapper structures, they also must specify which channels they
> > require, as channels are hard-wired to specific IPU functions.
>
> As a place for this driver /me votes for drivers/dma/ Though it does not
> seem like a perfect place for it, it still uses the API provided there.

Yes, I also considered that. Dan, would you accept that? What makes it
defferent from other drivers/dma drivers, is that this one also has an irq
driver.

> A general note: Can we get rid of the function names starting with an
> underscore?

Yeah... They are used pretty consistent now throughout the driver, and are
used for functions internal, static, maybe only called once... But if you
prefer, I can remove underscores, sure.

> > +/*
> > + * There can be only one, we could allocate it dynamically, but then we'd have
> > + * to add an extra parameter to some functions, and use something as ugly as
> > + * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));
>
> still you use it in one place

It is used in more than one place - implicitly in others. I did try to use
the pointer everywhere in the new code where it wasn't too expensive, to
make it at least easier if anyone ever decides to switch to dynamic
allocation. So, I wouldn't throw away all those "ugly" calculations and
replace them with the static variable, but if you think that'd be better,
I can do that.

> > + spin_lock_init(&ichan->lock);
> > + mutex_init(&ichan->chan_mutex);
>
> Having two locking methods for one dma channel looks like a recipe for
> trouble.

Why? You wouldn't take a mutex under spinlock, and the other way round is
fine:-). I think both are needed - spinlock for ISR, atomic contexts for
atomic operations like modifying registers, lists. And the mutex for
sleeping contexts.

> > +#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
> > +#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
> > +#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)
>
> Do we really need an interrupt handler behind each status bit the ipu
> has to offer? This looks quite expensive

What are you worried about? The size of irq_desc[]? Well, we could make
error interrupts dependent on a CONFIG_ macro, and let drivers that need
them select that option, because so far they are not used. Otherwise, I
think it is good to have all those bits on separate IRQs. What would you
do? Request only two IRQs - function and error and then demux them
manually, reinventing the generic irq subsystem? Doesn't seem very optimal
to me...

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-22 18:38:16

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Mon, Dec 22, 2008 at 12:56:29PM +0100, Guennadi Liakhovetski wrote:
> Hi Sascha,
>
> Thanks for the review and for the comments! I'm fixing them all, except
> for a couple points which I'm not sure I agree with:
>
> On Thu, 18 Dec 2008, Sascha Hauer wrote:
>
> > Hi Guennadi,
> >
> > On Thu, Dec 18, 2008 at 02:26:19PM +0100, Guennadi Liakhovetski wrote:
> > > From: Guennadi Liakhovetski <[email protected]>
> > >
> > > i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
> > > Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
> > > Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
> > > (PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
> > > CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
> > > and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
> > > supported over dmaengine and irq-chip APIs respectively.
> > >
> > > IDMAC is a specialised DMA controller, its DMA channels cannot be used for
> > > general-purpose operations, even though it might be possible to configure
> > > a memory-to-memory channel for memcpy operation. This driver will not work
> > > with generic dmaengine clients, clients, wishing to use it must use
> > > respective wrapper structures, they also must specify which channels they
> > > require, as channels are hard-wired to specific IPU functions.
> >
> > As a place for this driver /me votes for drivers/dma/ Though it does not
> > seem like a perfect place for it, it still uses the API provided there.
>
> Yes, I also considered that. Dan, would you accept that? What makes it
> defferent from other drivers/dma drivers, is that this one also has an irq
> driver.
>
> > A general note: Can we get rid of the function names starting with an
> > underscore?
>
> Yeah... They are used pretty consistent now throughout the driver, and are
> used for functions internal, static, maybe only called once... But if you
> prefer, I can remove underscores, sure.
>
> > > +/*
> > > + * There can be only one, we could allocate it dynamically, but then we'd have
> > > + * to add an extra parameter to some functions, and use something as ugly as
> > > + * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));
> >
> > still you use it in one place
>
> It is used in more than one place - implicitly in others. I did try to use
> the pointer everywhere in the new code where it wasn't too expensive, to
> make it at least easier if anyone ever decides to switch to dynamic
> allocation. So, I wouldn't throw away all those "ugly" calculations and
> replace them with the static variable, but if you think that'd be better,
> I can do that.

No, it was more the comment that was irritating me.

>
> > > + spin_lock_init(&ichan->lock);
> > > + mutex_init(&ichan->chan_mutex);
> >
> > Having two locking methods for one dma channel looks like a recipe for
> > trouble.
>
> Why? You wouldn't take a mutex under spinlock, and the other way round is
> fine:-). I think both are needed - spinlock for ISR, atomic contexts for
> atomic operations like modifying registers, lists. And the mutex for
> sleeping contexts.

I just remember reading some document which said that using too many
locks is not a good idea. Well I guess you're prepared for bug reports
;)

>
> > > +#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
> > > +#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
> > > +#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)
> >
> > Do we really need an interrupt handler behind each status bit the ipu
> > has to offer? This looks quite expensive
>
> What are you worried about? The size of irq_desc[]? Well, we could make
> error interrupts dependent on a CONFIG_ macro, and let drivers that need
> them select that option, because so far they are not used.

Sounds good, they are unused in the Freescale BSP aswell-

> Otherwise, I
> think it is good to have all those bits on separate IRQs. What would you
> do? Request only two IRQs - function and error and then demux them
> manually, reinventing the generic irq subsystem? Doesn't seem very optimal
> to me...

No indeed not. My concern is that of these 137 interrupts only 7 are
used in the Freescale BSP which shoud be quite feature complete.
I think we should check the potential use of these interrupts before
adding ~10k of struct irq_desc to the kernel.
What about the *_EOF interrupts? They are now demultiplexed in a chained
interrupt handler and then merged back together to a single handler in
ipu_idmac.c. Is there a potential use of these interrupts outside this file?

I just had a short look at your framebuffer driver. Am I understanding
it right that you setup two descriptors and pass them to the DMA engine
alternately? Wouldn't it be easier to pass just one descriptor and chain
the end to the beginning? I mean both descriptors read from the same
memory anyway.
Another thing is the overlay framebuffer. I think it's mainly useful to
display video streams. Maybe it's better to implement this as a v4l
device as unlike the framebuffer API the v4l API is designed to handle
different image buffers.
These things just popped up in my mind, I don't know if they are
practical, I still have a quite limited understanding of the whole
imaging stuff on the MX31.

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-22 20:03:49

by Robert Schwebel

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Mon, Dec 22, 2008 at 07:37:53PM +0100, Sascha Hauer wrote:
> Another thing is the overlay framebuffer. I think it's mainly useful
> to display video streams.

Right. The idea is that you can push a video stream into /dev/fb1 (for
example by using the gstreamer fbdev sink) while displaying widgets from
a GUI toolkit on /dev/fb0.

> Maybe it's better to implement this as a v4l device as unlike the
> framebuffer API the v4l API is designed to handle different image
> buffers.

Huh? v4l is image-frames-to-userspace, not vice versa.

rsc
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-22 20:09:59

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

As you surely have seen, I just posted v5 of the patchset.

On Mon, 22 Dec 2008, Sascha Hauer wrote:

> On Mon, Dec 22, 2008 at 12:56:29PM +0100, Guennadi Liakhovetski wrote:
> > Hi Sascha,
> >
> > Thanks for the review and for the comments! I'm fixing them all, except
> > for a couple points which I'm not sure I agree with:
> >
> > On Thu, 18 Dec 2008, Sascha Hauer wrote:
> >
> > > Hi Guennadi,
> > >
> > > On Thu, Dec 18, 2008 at 02:26:19PM +0100, Guennadi Liakhovetski wrote:
> > > > From: Guennadi Liakhovetski <[email protected]>
> > > >
> > > > i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
> > > > Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
> > > > Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
> > > > (PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
> > > > CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
> > > > and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
> > > > supported over dmaengine and irq-chip APIs respectively.
> > > >
> > > > IDMAC is a specialised DMA controller, its DMA channels cannot be used for
> > > > general-purpose operations, even though it might be possible to configure
> > > > a memory-to-memory channel for memcpy operation. This driver will not work
> > > > with generic dmaengine clients, clients, wishing to use it must use
> > > > respective wrapper structures, they also must specify which channels they
> > > > require, as channels are hard-wired to specific IPU functions.
> > >
> > > As a place for this driver /me votes for drivers/dma/ Though it does not
> > > seem like a perfect place for it, it still uses the API provided there.
> >
> > Yes, I also considered that. Dan, would you accept that? What makes it
> > defferent from other drivers/dma drivers, is that this one also has an irq
> > driver.
> >
> > > A general note: Can we get rid of the function names starting with an
> > > underscore?
> >
> > Yeah... They are used pretty consistent now throughout the driver, and are
> > used for functions internal, static, maybe only called once... But if you
> > prefer, I can remove underscores, sure.
> >
> > > > +/*
> > > > + * There can be only one, we could allocate it dynamically, but then we'd have
> > > > + * to add an extra parameter to some functions, and use something as ugly as
> > > > + * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));
> > >
> > > still you use it in one place
> >
> > It is used in more than one place - implicitly in others. I did try to use
> > the pointer everywhere in the new code where it wasn't too expensive, to
> > make it at least easier if anyone ever decides to switch to dynamic
> > allocation. So, I wouldn't throw away all those "ugly" calculations and
> > replace them with the static variable, but if you think that'd be better,
> > I can do that.
>
> No, it was more the comment that was irritating me.

Hm, well, it could be removed of course, I hope this wouldn't be a
K.O. criterium:-)

> > > > + spin_lock_init(&ichan->lock);
> > > > + mutex_init(&ichan->chan_mutex);
> > >
> > > Having two locking methods for one dma channel looks like a recipe for
> > > trouble.
> >
> > Why? You wouldn't take a mutex under spinlock, and the other way round is
> > fine:-). I think both are needed - spinlock for ISR, atomic contexts for
> > atomic operations like modifying registers, lists. And the mutex for
> > sleeping contexts.
>
> I just remember reading some document which said that using too many
> locks is not a good idea. Well I guess you're prepared for bug reports
> ;)

Sure, you can start now:-)

> > > > +#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
> > > > +#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
> > > > +#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)
> > >
> > > Do we really need an interrupt handler behind each status bit the ipu
> > > has to offer? This looks quite expensive
> >
> > What are you worried about? The size of irq_desc[]? Well, we could make
> > error interrupts dependent on a CONFIG_ macro, and let drivers that need
> > them select that option, because so far they are not used.
>
> Sounds good, they are unused in the Freescale BSP aswell-
>
> > Otherwise, I
> > think it is good to have all those bits on separate IRQs. What would you
> > do? Request only two IRQs - function and error and then demux them
> > manually, reinventing the generic irq subsystem? Doesn't seem very optimal
> > to me...
>
> No indeed not. My concern is that of these 137 interrupts only 7 are
> used in the Freescale BSP which shoud be quite feature complete.
> I think we should check the potential use of these interrupts before
> adding ~10k of struct irq_desc to the kernel.
> What about the *_EOF interrupts? They are now demultiplexed in a chained
> interrupt handler and then merged back together to a single handler in
> ipu_idmac.c. Is there a potential use of these interrupts outside this file?

Ok, so, what would we like to have there? We agree that the proper way to
serve them is a irq-chip driver, right? We could make error IRQs and
new frame IRQs optional. That would leave 56 IRQs, would that be ok?

> I just had a short look at your framebuffer driver. Am I understanding
> it right that you setup two descriptors and pass them to the DMA engine
> alternately? Wouldn't it be easier to pass just one descriptor and chain
> the end to the beginning? I mean both descriptors read from the same
> memory anyway.

No, addresses are not necessarily the same. With panning addresses are
different.

> Another thing is the overlay framebuffer. I think it's mainly useful to
> display video streams. Maybe it's better to implement this as a v4l
> device as unlike the framebuffer API the v4l API is designed to handle
> different image buffers.
> These things just popped up in my mind, I don't know if they are
> practical, I still have a quite limited understanding of the whole
> imaging stuff on the MX31.

You mean an output v4l device? I think overlays are handled by framebuffer
drivers... But I'm also not quite sure about it, however, handling overlay
as another framebuffer seems logical to me.

If there are no other problems with v5, could we maybe take it as a basis
and then I would submit a patch to reduce the number of IRQs?

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 10:09:32

by Dmitry Krivoschekov

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Robert Schwebel wrote:
> On Mon, Dec 22, 2008 at 07:37:53PM +0100, Sascha Hauer wrote:
>> Another thing is the overlay framebuffer. I think it's mainly useful
>> to display video streams.
>
> Right. The idea is that you can push a video stream into /dev/fb1 (for
> example by using the gstreamer fbdev sink) while displaying widgets from
> a GUI toolkit on /dev/fb0.
>
>> Maybe it's better to implement this as a v4l device as unlike the
>> framebuffer API the v4l API is designed to handle different image
>> buffers.
>
> Huh? v4l is image-frames-to-userspace, not vice versa.
>
...and vice versa too, v4l2 is not about input devices only but it is
also related to output devices, v4l2 spec even has a special chapter
dedicated to overlays [1]. Moreover, you can find such a driver within
Freescale's BSP [2]. Another example is Omap's videoout driver [3].


Dmitry

[1]http://v4l2spec.bytesex.org/spec-single/v4l2.html#OVERLAY
[2]http://opensource.freescale.com/git?p=linux-2.6-mx.git;a=blob;f=drivers/media/video/mxc/output/mxc_v4l2_output.c;h=309700bb9d1f3b3f9dcac8a48a16d0775b8ed93a;hb=refs/heads/bsp-imx31ads-rel5
[3]http://git.omapzoom.org/?p=omapkernel.git;a=blob;f=drivers/media/video/omap/omap24xxvout.c;h=25ac1319bae58751cd1a543aa0f320159e10a475;hb=HEAD

2008-12-23 10:50:27

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> As you surely have seen, I just posted v5 of the patchset.
>
> On Mon, 22 Dec 2008, Sascha Hauer wrote:
>
> > On Mon, Dec 22, 2008 at 12:56:29PM +0100, Guennadi Liakhovetski wrote:
> > > Hi Sascha,
> > >
> > > Thanks for the review and for the comments! I'm fixing them all, except
> > > for a couple points which I'm not sure I agree with:
> > >
> > > On Thu, 18 Dec 2008, Sascha Hauer wrote:
> > >
> > > > Hi Guennadi,
> > > >
> > > > On Thu, Dec 18, 2008 at 02:26:19PM +0100, Guennadi Liakhovetski wrote:
> > > > > From: Guennadi Liakhovetski <[email protected]>
> > > > >
> > > > > i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
> > > > > Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
> > > > > Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
> > > > > (PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
> > > > > CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
> > > > > and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
> > > > > supported over dmaengine and irq-chip APIs respectively.
> > > > >
> > > > > IDMAC is a specialised DMA controller, its DMA channels cannot be used for
> > > > > general-purpose operations, even though it might be possible to configure
> > > > > a memory-to-memory channel for memcpy operation. This driver will not work
> > > > > with generic dmaengine clients, clients, wishing to use it must use
> > > > > respective wrapper structures, they also must specify which channels they
> > > > > require, as channels are hard-wired to specific IPU functions.
> > > >
> > > > As a place for this driver /me votes for drivers/dma/ Though it does not
> > > > seem like a perfect place for it, it still uses the API provided there.
> > >
> > > Yes, I also considered that. Dan, would you accept that? What makes it
> > > defferent from other drivers/dma drivers, is that this one also has an irq
> > > driver.
> > >
> > > > A general note: Can we get rid of the function names starting with an
> > > > underscore?
> > >
> > > Yeah... They are used pretty consistent now throughout the driver, and are
> > > used for functions internal, static, maybe only called once... But if you
> > > prefer, I can remove underscores, sure.
> > >
> > > > > +/*
> > > > > + * There can be only one, we could allocate it dynamically, but then we'd have
> > > > > + * to add an extra parameter to some functions, and use something as ugly as
> > > > > + * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));
> > > >
> > > > still you use it in one place
> > >
> > > It is used in more than one place - implicitly in others. I did try to use
> > > the pointer everywhere in the new code where it wasn't too expensive, to
> > > make it at least easier if anyone ever decides to switch to dynamic
> > > allocation. So, I wouldn't throw away all those "ugly" calculations and
> > > replace them with the static variable, but if you think that'd be better,
> > > I can do that.
> >
> > No, it was more the comment that was irritating me.
>
> Hm, well, it could be removed of course, I hope this wouldn't be a
> K.O. criterium:-)

Of course not ;)

>
> > > > > + spin_lock_init(&ichan->lock);
> > > > > + mutex_init(&ichan->chan_mutex);
> > > >
> > > > Having two locking methods for one dma channel looks like a recipe for
> > > > trouble.
> > >
> > > Why? You wouldn't take a mutex under spinlock, and the other way round is
> > > fine:-). I think both are needed - spinlock for ISR, atomic contexts for
> > > atomic operations like modifying registers, lists. And the mutex for
> > > sleeping contexts.
> >
> > I just remember reading some document which said that using too many
> > locks is not a good idea. Well I guess you're prepared for bug reports
> > ;)
>
> Sure, you can start now:-)
>
> > > > > +#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
> > > > > +#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
> > > > > +#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)
> > > >
> > > > Do we really need an interrupt handler behind each status bit the ipu
> > > > has to offer? This looks quite expensive
> > >
> > > What are you worried about? The size of irq_desc[]? Well, we could make
> > > error interrupts dependent on a CONFIG_ macro, and let drivers that need
> > > them select that option, because so far they are not used.
> >
> > Sounds good, they are unused in the Freescale BSP aswell-
> >
> > > Otherwise, I
> > > think it is good to have all those bits on separate IRQs. What would you
> > > do? Request only two IRQs - function and error and then demux them
> > > manually, reinventing the generic irq subsystem? Doesn't seem very optimal
> > > to me...
> >
> > No indeed not. My concern is that of these 137 interrupts only 7 are
> > used in the Freescale BSP which shoud be quite feature complete.
> > I think we should check the potential use of these interrupts before
> > adding ~10k of struct irq_desc to the kernel.
> > What about the *_EOF interrupts? They are now demultiplexed in a chained
> > interrupt handler and then merged back together to a single handler in
> > ipu_idmac.c. Is there a potential use of these interrupts outside this file?
>
> Ok, so, what would we like to have there? We agree that the proper way to
> serve them is a irq-chip driver, right?

In case of the *_EOF interrupts when they can be used outside the idmac
driver then yes. If not then not for the reasons I explained.

> We could make error IRQs and
> new frame IRQs optional. That would leave 56 IRQs, would that be ok?
>
> > I just had a short look at your framebuffer driver. Am I understanding
> > it right that you setup two descriptors and pass them to the DMA engine
> > alternately? Wouldn't it be easier to pass just one descriptor and chain
> > the end to the beginning? I mean both descriptors read from the same
> > memory anyway.
>
> No, addresses are not necessarily the same. With panning addresses are
> different.

Ah, now I see. The model with alternating descriptors is only used for
panning.


>
> > Another thing is the overlay framebuffer. I think it's mainly useful to
> > display video streams. Maybe it's better to implement this as a v4l
> > device as unlike the framebuffer API the v4l API is designed to handle
> > different image buffers.
> > These things just popped up in my mind, I don't know if they are
> > practical, I still have a quite limited understanding of the whole
> > imaging stuff on the MX31.
>
> You mean an output v4l device? I think overlays are handled by framebuffer
> drivers... But I'm also not quite sure about it, however, handling overlay
> as another framebuffer seems logical to me.

Well the DMA engine seems to suggest that frames should be passed around
whereas the framebuffer API only has a single frame. That would fit
better into the v4l API. Also the IPU can do things like colourspace
conversion and hw scaling which would fit into the V4L API.
OTOH there are not many examples of drivers doing this (ivtv, zoran in
kernel and an Omap driver found on lists). And I haven't found any
application supporting a V4L output device.
BTW is the overlay framebuffer useful in it's current implementation?
There seems to be no way to adjust the x/y offset or the blending modes.

>
> If there are no other problems with v5, could we maybe take it as a basis
> and then I would submit a patch to reduce the number of IRQs?

Please understand my concerns with this driver. It's a quite complex
beast and experience shows that once a driver is in the kernel it is far
more complicated to change it than to do it right the first way. You
know that I'm also interested in having a MX31 framebuffer (and camera)
driver in kernel but I want to make sure that it works properly and leaves
room for feature enhancements without having to refactor the whole
driver.
It would be good if someone else could throw his 2 cents into this
discussion.

Sascha


--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 10:52:56

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 01:09:10PM +0300, Dmitry Krivoschekov wrote:
> Robert Schwebel wrote:
> > On Mon, Dec 22, 2008 at 07:37:53PM +0100, Sascha Hauer wrote:
> >> Another thing is the overlay framebuffer. I think it's mainly useful
> >> to display video streams.
> >
> > Right. The idea is that you can push a video stream into /dev/fb1 (for
> > example by using the gstreamer fbdev sink) while displaying widgets from
> > a GUI toolkit on /dev/fb0.
> >
> >> Maybe it's better to implement this as a v4l device as unlike the
> >> framebuffer API the v4l API is designed to handle different image
> >> buffers.
> >
> > Huh? v4l is image-frames-to-userspace, not vice versa.
> >
> ...and vice versa too, v4l2 is not about input devices only but it is
> also related to output devices, v4l2 spec even has a special chapter
> dedicated to overlays [1]. Moreover, you can find such a driver within
> Freescale's BSP [2]. Another example is Omap's videoout driver [3].

Are you aware of any application making use of this feature?

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 11:21:58

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Hi Sascha

On Tue, 23 Dec 2008, Sascha Hauer wrote:

> On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> >
> > Ok, so, what would we like to have there? We agree that the proper way to
> > serve them is a irq-chip driver, right?
>
> In case of the *_EOF interrupts when they can be used outside the idmac
> driver then yes. If not then not for the reasons I explained.

Wait a minute, are you suggesting to handle interrupts that are exported
to client drivers and that are "internal" to ipu_idmac differently? Like
exported once - properly using the irq chip machinery, and internal once
just demux in the driver hiding them from the kernel?... Or have I
misunderstood you? If this is indeed what you mean, then that doesn't
sound like a good idea to me, sorry. Like you configure a chained handler,
then when it is called on an IRQ, you check if the reason is bits 0, 1, or
2 you call generic_handle_irq(), for other bits you handle them
internally... grrrr... And, in fact, I don't see many internal interrupts
there - maybe only those from IPU_INT_STAT_3 related to CM? You see, we
could split interrupts further: support for EOF irqs - unconditional,
error, NF, and IPU_INT_STAT_3 IRQs - under 3 config variables, maybe even
split IPU_INT_STAT_3 further in submodules... But this would clutter the
Kconfig. Ok, I could add an own Kconfig under drivers/dma/ipu. But I
really do not think we should export some interrupts to the irq_desc array
and handle others internally. So, what granularity would you like to see
there?

> > > Another thing is the overlay framebuffer. I think it's mainly useful to
> > > display video streams. Maybe it's better to implement this as a v4l
> > > device as unlike the framebuffer API the v4l API is designed to handle
> > > different image buffers.
> > > These things just popped up in my mind, I don't know if they are
> > > practical, I still have a quite limited understanding of the whole
> > > imaging stuff on the MX31.
> >
> > You mean an output v4l device? I think overlays are handled by framebuffer
> > drivers... But I'm also not quite sure about it, however, handling overlay
> > as another framebuffer seems logical to me.
>
> Well the DMA engine seems to suggest that frames should be passed around
> whereas the framebuffer API only has a single frame. That would fit
> better into the v4l API. Also the IPU can do things like colourspace
> conversion and hw scaling which would fit into the V4L API.
> OTOH there are not many examples of drivers doing this (ivtv, zoran in
> kernel and an Omap driver found on lists). And I haven't found any
> application supporting a V4L output device.
> BTW is the overlay framebuffer useful in it's current implementation?
> There seems to be no way to adjust the x/y offset or the blending modes.

No - that's what the comment in the commit says:

This is a framebuffer driver for i.MX31 SoCs. It only supports synchronous
displays, overlay support is included but has never been tested.

[Oops, just noticed - in v5 framebuffer I again have the "panning not
tested" clause, which is not true, and the comment has been fixed in v4:-(
So, if we decide to take v5, we'd have to take the comment from v4]

So, I could just throw overlay completely out. OTOH, if anyone ever wants
to use it, they'll have something to start with.

> > If there are no other problems with v5, could we maybe take it as a basis
> > and then I would submit a patch to reduce the number of IRQs?
>
> Please understand my concerns with this driver. It's a quite complex
> beast and experience shows that once a driver is in the kernel it is far
> more complicated to change it than to do it right the first way. You
> know that I'm also interested in having a MX31 framebuffer (and camera)
> driver in kernel but I want to make sure that it works properly and leaves
> room for feature enhancements without having to refactor the whole
> driver.

Well, not quite so. At least with my workflow it takes more work to
regenerate a complete patch-series, than to create incremental patches.
For me it would be definitely easier to have a version of the driver
upstream to then only create incremental patches for it. Also, it would be
easier for others to test it. And consider the constantly moving target
effect - you know it as well as I do, rebasing your off-tree patches to
new kernel versions is a considerable amount of work too.

> It would be good if someone else could throw his 2 cents into this
> discussion.

Yes, eventually, the drivers will have to go over dma and framebuffer
trees...

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 11:32:22

by Dmitry Krivoschekov

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Sascha Hauer wrote:
> On Tue, Dec 23, 2008 at 01:09:10PM +0300, Dmitry Krivoschekov wrote:
>> Robert Schwebel wrote:
>>> On Mon, Dec 22, 2008 at 07:37:53PM +0100, Sascha Hauer wrote:
>>>> Another thing is the overlay framebuffer. I think it's mainly useful
>>>> to display video streams.
>>> Right. The idea is that you can push a video stream into /dev/fb1 (for
>>> example by using the gstreamer fbdev sink) while displaying widgets from
>>> a GUI toolkit on /dev/fb0.
>>>
>>>> Maybe it's better to implement this as a v4l device as unlike the
>>>> framebuffer API the v4l API is designed to handle different image
>>>> buffers.
>>> Huh? v4l is image-frames-to-userspace, not vice versa.
>>>
>> ...and vice versa too, v4l2 is not about input devices only but it is
>> also related to output devices, v4l2 spec even has a special chapter
>> dedicated to overlays [1]. Moreover, you can find such a driver within
>> Freescale's BSP [2]. Another example is Omap's videoout driver [3].
>
> Are you aware of any application making use of this feature?

IIRC, mplayer is able to output on a v4l2 device. Also, Freescale's
i.MX31 ADS BSP contained a bunch of V4L2 unit tests, including v4l2 output.

Dmitry


[1]
http://www.freescale.com/webapp/sps/site/overview.jsp?code=CW_BSP_ARM&fsrch=1
>
> Sascha
>

2008-12-23 12:08:27

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 02:32:08PM +0300, Dmitry Krivoschekov wrote:
> Sascha Hauer wrote:
> > On Tue, Dec 23, 2008 at 01:09:10PM +0300, Dmitry Krivoschekov wrote:
> >> Robert Schwebel wrote:
> >>> On Mon, Dec 22, 2008 at 07:37:53PM +0100, Sascha Hauer wrote:
> >>>> Another thing is the overlay framebuffer. I think it's mainly useful
> >>>> to display video streams.
> >>> Right. The idea is that you can push a video stream into /dev/fb1 (for
> >>> example by using the gstreamer fbdev sink) while displaying widgets from
> >>> a GUI toolkit on /dev/fb0.
> >>>
> >>>> Maybe it's better to implement this as a v4l device as unlike the
> >>>> framebuffer API the v4l API is designed to handle different image
> >>>> buffers.
> >>> Huh? v4l is image-frames-to-userspace, not vice versa.
> >>>
> >> ...and vice versa too, v4l2 is not about input devices only but it is
> >> also related to output devices, v4l2 spec even has a special chapter
> >> dedicated to overlays [1]. Moreover, you can find such a driver within
> >> Freescale's BSP [2]. Another example is Omap's videoout driver [3].
> >
> > Are you aware of any application making use of this feature?
>
> IIRC, mplayer is able to output on a v4l2 device.

Yes, but only on some MPEG decoder cards. Raw frames are not supported.

> Also, Freescale's
> i.MX31 ADS BSP contained a bunch of V4L2 unit tests, including v4l2 output.

Ah ok, didn't know that

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 12:13:19

by Robert Schwebel

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 11:50:06AM +0100, Sascha Hauer wrote:
> > You mean an output v4l device? I think overlays are handled by framebuffer
> > drivers... But I'm also not quite sure about it, however, handling overlay
> > as another framebuffer seems logical to me.
>
> Well the DMA engine seems to suggest that frames should be passed around
> whereas the framebuffer API only has a single frame. That would fit
> better into the v4l API. Also the IPU can do things like colourspace
> conversion and hw scaling which would fit into the V4L API.

Looks like a candidate for gstreamer on the userspace end. Can it be
decoupled enough to make proper plugins out of it?

> BTW is the overlay framebuffer useful in it's current implementation?
> There seems to be no way to adjust the x/y offset or the blending
> modes.

The API Eric Miao just posted for the PXA looks sane to me.

> > If there are no other problems with v5, could we maybe take it as a
> > basis and then I would submit a patch to reduce the number of IRQs?
>
> Please understand my concerns with this driver. It's a quite complex
> beast and experience shows that once a driver is in the kernel it is
> far more complicated to change it than to do it right the first way.

Especially when it comes to userspace visible things.

> You know that I'm also interested in having a MX31 framebuffer (and
> camera) driver in kernel but I want to make sure that it works
> properly and leaves room for feature enhancements without having to
> refactor the whole driver.

Yup, looks like it would be better to cook it another round instead of
trying to bring in a half-tested driver with brute-force.

rsc
--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 12:45:26

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Hi Robert,

On Tue, 23 Dec 2008, Robert Schwebel wrote:

> On Tue, Dec 23, 2008 at 11:50:06AM +0100, Sascha Hauer wrote:
> > > You mean an output v4l device? I think overlays are handled by framebuffer
> > > drivers... But I'm also not quite sure about it, however, handling overlay
> > > as another framebuffer seems logical to me.
> >
> > Well the DMA engine seems to suggest that frames should be passed around
> > whereas the framebuffer API only has a single frame. That would fit
> > better into the v4l API. Also the IPU can do things like colourspace
> > conversion and hw scaling which would fit into the V4L API.
>
> Looks like a candidate for gstreamer on the userspace end. Can it be
> decoupled enough to make proper plugins out of it?

I think, the question is rather "can a driver be written for IPU to
support some sane hardware-neutral image data manipulation API, like v4l?"
Then you can start writing any user-space apps on top of that API. So, as
long as there is such an API, I think, we can put it on IPU, yes. My
problems ATM is - no use-case. What concerns overlay, it is not used in
the current application, as for image format conversion from the camera -
I only have a Bayer camera to test with. I even cannot test monochrome
sanely, because of a lack of a cable:-) And Bayer and monochrome don't
make good candidates for such conversions - they are not supported by the
IPU.

> > BTW is the overlay framebuffer useful in it's current implementation?
> > There seems to be no way to adjust the x/y offset or the blending
> > modes.
>
> The API Eric Miao just posted for the PXA looks sane to me.

I'll have a look, but as I said: -ENOUSER:-)

> > > If there are no other problems with v5, could we maybe take it as a
> > > basis and then I would submit a patch to reduce the number of IRQs?
> >
> > Please understand my concerns with this driver. It's a quite complex
> > beast and experience shows that once a driver is in the kernel it is
> > far more complicated to change it than to do it right the first way.
>
> Especially when it comes to userspace visible things.
>
> > You know that I'm also interested in having a MX31 framebuffer (and
> > camera) driver in kernel but I want to make sure that it works
> > properly and leaves room for feature enhancements without having to
> > refactor the whole driver.
>
> Yup, looks like it would be better to cook it another round instead of
> trying to bring in a half-tested driver with brute-force.

Ok, no problem. Let's just decide

1. is the drivers/dma the final location.
2. do dmaengine maintainers accept it in present form or require any
amendments.
3. which interrupts we make visible by default for irq_desc[] and what
granularity we want to enable the rest (we could even just make
CONFIG_ALL_IPU_IRQS and be 95% sure noone will ever need them.)
4. throw away every trace of overlay support - if anyone ever needs it
they should be able to dig it out in ML archives.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 12:50:27

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 12:21:54PM +0100, Guennadi Liakhovetski wrote:
> Hi Sascha
>
> On Tue, 23 Dec 2008, Sascha Hauer wrote:
>
> > On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> > >
> > > Ok, so, what would we like to have there? We agree that the proper way to
> > > serve them is a irq-chip driver, right?
> >
> > In case of the *_EOF interrupts when they can be used outside the idmac
> > driver then yes. If not then not for the reasons I explained.
>
> Wait a minute, are you suggesting to handle interrupts that are exported
> to client drivers and that are "internal" to ipu_idmac differently? Like
> exported once - properly using the irq chip machinery, and internal once
> just demux in the driver hiding them from the kernel?... Or have I
> misunderstood you? If this is indeed what you mean, then that doesn't
> sound like a good idea to me, sorry. Like you configure a chained handler,
> then when it is called on an IRQ, you check if the reason is bits 0, 1, or
> 2 you call generic_handle_irq(), for other bits you handle them
> internally... grrrr...

I'm not suggesting that. The *_EOF registers are all 32 bits on the *same*
register. And these 32 interrupts are inherently occupied by ipu_idmac.c
as you cannot request a idmac channel without requesting this interrupt.
So at the moment you are passing 32 bits of the same register through a
chained interrupt handler and *all* these interrupts go to the very same
interrupt handler.
For the corresponding 32 error interrupts it's probably also the idmac
engine that has to react to these interrupts, not the drivers using it.
Not sure about the remaining assorted interrupts.

> And, in fact, I don't see many internal interrupts
> there - maybe only those from IPU_INT_STAT_3 related to CM? You see, we
> could split interrupts further: support for EOF irqs - unconditional,
> error, NF, and IPU_INT_STAT_3 IRQs - under 3 config variables, maybe even
> split IPU_INT_STAT_3 further in submodules... But this would clutter the
> Kconfig. Ok, I could add an own Kconfig under drivers/dma/ipu. But I
> really do not think we should export some interrupts to the irq_desc array
> and handle others internally. So, what granularity would you like to see
> there?
>
> > > > Another thing is the overlay framebuffer. I think it's mainly useful to
> > > > display video streams. Maybe it's better to implement this as a v4l
> > > > device as unlike the framebuffer API the v4l API is designed to handle
> > > > different image buffers.
> > > > These things just popped up in my mind, I don't know if they are
> > > > practical, I still have a quite limited understanding of the whole
> > > > imaging stuff on the MX31.
> > >
> > > You mean an output v4l device? I think overlays are handled by framebuffer
> > > drivers... But I'm also not quite sure about it, however, handling overlay
> > > as another framebuffer seems logical to me.
> >
> > Well the DMA engine seems to suggest that frames should be passed around
> > whereas the framebuffer API only has a single frame. That would fit
> > better into the v4l API. Also the IPU can do things like colourspace
> > conversion and hw scaling which would fit into the V4L API.
> > OTOH there are not many examples of drivers doing this (ivtv, zoran in
> > kernel and an Omap driver found on lists). And I haven't found any
> > application supporting a V4L output device.
> > BTW is the overlay framebuffer useful in it's current implementation?
> > There seems to be no way to adjust the x/y offset or the blending modes.
>
> No - that's what the comment in the commit says:
>
> This is a framebuffer driver for i.MX31 SoCs. It only supports synchronous
> displays, overlay support is included but has never been tested.

No, it's not working. The overlay framebuffer maybe there, but it's
configured to be invisible.

>
> [Oops, just noticed - in v5 framebuffer I again have the "panning not
> tested" clause, which is not true, and the comment has been fixed in v4:-(
> So, if we decide to take v5, we'd have to take the comment from v4]
>
> So, I could just throw overlay completely out. OTOH, if anyone ever wants
> to use it, they'll have something to start with.

And in the meantime we maintain known to be broken code?

>
> > > If there are no other problems with v5, could we maybe take it as a basis
> > > and then I would submit a patch to reduce the number of IRQs?
> >
> > Please understand my concerns with this driver. It's a quite complex
> > beast and experience shows that once a driver is in the kernel it is far
> > more complicated to change it than to do it right the first way. You
> > know that I'm also interested in having a MX31 framebuffer (and camera)
> > driver in kernel but I want to make sure that it works properly and leaves
> > room for feature enhancements without having to refactor the whole
> > driver.
>
> Well, not quite so. At least with my workflow it takes more work to
> regenerate a complete patch-series, than to create incremental patches.
> For me it would be definitely easier to have a version of the driver
> upstream to then only create incremental patches for it. Also, it would be
> easier for others to test it. And consider the constantly moving target
> effect - you know it as well as I do, rebasing your off-tree patches to
> new kernel versions is a considerable amount of work too.
>
> > It would be good if someone else could throw his 2 cents into this
> > discussion.
>
> Yes, eventually, the drivers will have to go over dma and framebuffer
> trees...


>
> Thanks
> Guennadi
> ---
> Guennadi Liakhovetski, Ph.D.
> Freelance Open-Source Software Developer
>

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 12:56:23

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 01:45:14PM +0100, Guennadi Liakhovetski wrote:
> Hi Robert,
>
> On Tue, 23 Dec 2008, Robert Schwebel wrote:
>
> > On Tue, Dec 23, 2008 at 11:50:06AM +0100, Sascha Hauer wrote:
> > > > You mean an output v4l device? I think overlays are handled by framebuffer
> > > > drivers... But I'm also not quite sure about it, however, handling overlay
> > > > as another framebuffer seems logical to me.
> > >
> > > Well the DMA engine seems to suggest that frames should be passed around
> > > whereas the framebuffer API only has a single frame. That would fit
> > > better into the v4l API. Also the IPU can do things like colourspace
> > > conversion and hw scaling which would fit into the V4L API.
> >
> > Looks like a candidate for gstreamer on the userspace end. Can it be
> > decoupled enough to make proper plugins out of it?
>
> I think, the question is rather "can a driver be written for IPU to
> support some sane hardware-neutral image data manipulation API, like v4l?"
> Then you can start writing any user-space apps on top of that API. So, as
> long as there is such an API, I think, we can put it on IPU, yes. My
> problems ATM is - no use-case. What concerns overlay, it is not used in
> the current application, as for image format conversion from the camera -
> I only have a Bayer camera to test with. I even cannot test monochrome
> sanely, because of a lack of a cable:-) And Bayer and monochrome don't
> make good candidates for such conversions - they are not supported by the
> IPU.
>
> > > BTW is the overlay framebuffer useful in it's current implementation?
> > > There seems to be no way to adjust the x/y offset or the blending
> > > modes.
> >
> > The API Eric Miao just posted for the PXA looks sane to me.
>
> I'll have a look, but as I said: -ENOUSER:-)
>
> > > > If there are no other problems with v5, could we maybe take it as a
> > > > basis and then I would submit a patch to reduce the number of IRQs?
> > >
> > > Please understand my concerns with this driver. It's a quite complex
> > > beast and experience shows that once a driver is in the kernel it is
> > > far more complicated to change it than to do it right the first way.
> >
> > Especially when it comes to userspace visible things.
> >
> > > You know that I'm also interested in having a MX31 framebuffer (and
> > > camera) driver in kernel but I want to make sure that it works
> > > properly and leaves room for feature enhancements without having to
> > > refactor the whole driver.
> >
> > Yup, looks like it would be better to cook it another round instead of
> > trying to bring in a half-tested driver with brute-force.
>
> Ok, no problem. Let's just decide
>
> 1. is the drivers/dma the final location.
> 2. do dmaengine maintainers accept it in present form or require any
> amendments.
> 3. which interrupts we make visible by default for irq_desc[] and what
> granularity we want to enable the rest (we could even just make
> CONFIG_ALL_IPU_IRQS and be 95% sure noone will ever need them.)
> 4. throw away every trace of overlay support - if anyone ever needs it
> they should be able to dig it out in ML archives.

Or from the Freescale BSP. This sounds good to me. It would also enable
someone to write a v4l2 based overlay driver without interfering with
the existing framebuffer driver.

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 13:02:57

by Valentin Longchamp

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

Sascha Hauer wrote:
> On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
>
>> If there are no other problems with v5, could we maybe take it as a basis
>> and then I would submit a patch to reduce the number of IRQs?
>
> Please understand my concerns with this driver. It's a quite complex
> beast and experience shows that once a driver is in the kernel it is far
> more complicated to change it than to do it right the first way. You
> know that I'm also interested in having a MX31 framebuffer (and camera)
> driver in kernel but I want to make sure that it works properly and leaves
> room for feature enhancements without having to refactor the whole
> driver.
> It would be good if someone else could throw his 2 cents into this
> discussion.
>

I have been on holiday since last week and I don't really have time to
comment on all this until my return from great ski weeks, but I have
been following the discussion very attentively and I will for sure test
and comment on these patches (camera is very important for our
application) as soon as I get back to work in January.

It's a bit tricky with this merge window in the "middle" of holidays for me.

Valentin

--
Valentin Longchamp, PhD Student, EPFL-STI-LSRO1
[email protected], Phone: +41216937827
http://people.epfl.ch/valentin.longchamp
MEA3485, Station 9, CH-1015 Lausanne

2008-12-23 13:14:18

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, 23 Dec 2008, Sascha Hauer wrote:

> On Tue, Dec 23, 2008 at 12:21:54PM +0100, Guennadi Liakhovetski wrote:
> > Hi Sascha
> >
> > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> >
> > > On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> > > >
> > > > Ok, so, what would we like to have there? We agree that the proper way to
> > > > serve them is a irq-chip driver, right?
> > >
> > > In case of the *_EOF interrupts when they can be used outside the idmac
> > > driver then yes. If not then not for the reasons I explained.
> >
> > Wait a minute, are you suggesting to handle interrupts that are exported
> > to client drivers and that are "internal" to ipu_idmac differently? Like
> > exported once - properly using the irq chip machinery, and internal once
> > just demux in the driver hiding them from the kernel?... Or have I
> > misunderstood you? If this is indeed what you mean, then that doesn't
> > sound like a good idea to me, sorry. Like you configure a chained handler,
> > then when it is called on an IRQ, you check if the reason is bits 0, 1, or
> > 2 you call generic_handle_irq(), for other bits you handle them
> > internally... grrrr...
>
> I'm not suggesting that. The *_EOF registers are all 32 bits on the *same*
> register. And these 32 interrupts are inherently occupied by ipu_idmac.c
> as you cannot request a idmac channel without requesting this interrupt.
> So at the moment you are passing 32 bits of the same register through a
> chained interrupt handler and *all* these interrupts go to the very same
> interrupt handler.
> For the corresponding 32 error interrupts it's probably also the idmac
> engine that has to react to these interrupts, not the drivers using it.
> Not sure about the remaining assorted interrupts.

Yes, I understand that. So, you're suggesting to put all EOF interrupts
under 1 irq-number. Just for example: now I have

167: 351 ipu_irq idmac
174: 752 ipu_irq idmac
175: 0 ipu_irq idmac
230: 1 ipu_irq csi

where 167 - camera IRQs, 174 - framebuffer (panning), 175 - overlay (ok,
this one will go), 230 - CSI_EOF from IPU_INT_STAT_3 (ok, it is not used
ATM, can go too).

So, there are at least two valid users of EOF interrupts. And you're
suggesting to put them on one interrupt, and even not as a shared IRQ with
two handlers - one interrupt with one handler with multiple sources /
users behind it... What likely will happen someone will add support for
YUV / RGB cameras and will want to use hardware processing for them, like
encoding and / or rotation, which will add DMA channels DMAIC_0, DMAIC_8,
DMAIC_6, DMAIC_10... and you want to put them all on one IRQ?... What we
could improve in the above /proc/interrupts output is register interrupts
with different names to make distinguishing them easier. But I really
would not like to put them all together.

> > This is a framebuffer driver for i.MX31 SoCs. It only supports synchronous
> > displays, overlay support is included but has never been tested.
>
> No, it's not working. The overlay framebuffer maybe there, but it's
> configured to be invisible.

Good, will remove it then.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 14:03:34

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 02:14:12PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 23 Dec 2008, Sascha Hauer wrote:
>
> > On Tue, Dec 23, 2008 at 12:21:54PM +0100, Guennadi Liakhovetski wrote:
> > > Hi Sascha
> > >
> > > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> > >
> > > > On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> > > > >
> > > > > Ok, so, what would we like to have there? We agree that the proper way to
> > > > > serve them is a irq-chip driver, right?
> > > >
> > > > In case of the *_EOF interrupts when they can be used outside the idmac
> > > > driver then yes. If not then not for the reasons I explained.
> > >
> > > Wait a minute, are you suggesting to handle interrupts that are exported
> > > to client drivers and that are "internal" to ipu_idmac differently? Like
> > > exported once - properly using the irq chip machinery, and internal once
> > > just demux in the driver hiding them from the kernel?... Or have I
> > > misunderstood you? If this is indeed what you mean, then that doesn't
> > > sound like a good idea to me, sorry. Like you configure a chained handler,
> > > then when it is called on an IRQ, you check if the reason is bits 0, 1, or
> > > 2 you call generic_handle_irq(), for other bits you handle them
> > > internally... grrrr...
> >
> > I'm not suggesting that. The *_EOF registers are all 32 bits on the *same*
> > register. And these 32 interrupts are inherently occupied by ipu_idmac.c
> > as you cannot request a idmac channel without requesting this interrupt.
> > So at the moment you are passing 32 bits of the same register through a
> > chained interrupt handler and *all* these interrupts go to the very same
> > interrupt handler.
> > For the corresponding 32 error interrupts it's probably also the idmac
> > engine that has to react to these interrupts, not the drivers using it.
> > Not sure about the remaining assorted interrupts.
>
> Yes, I understand that. So, you're suggesting to put all EOF interrupts
> under 1 irq-number. Just for example: now I have
>
> 167: 351 ipu_irq idmac
> 174: 752 ipu_irq idmac
> 175: 0 ipu_irq idmac
> 230: 1 ipu_irq csi
>
> where 167 - camera IRQs, 174 - framebuffer (panning), 175 - overlay (ok,
> this one will go), 230 - CSI_EOF from IPU_INT_STAT_3 (ok, it is not used
> ATM, can go too).
>
> So, there are at least two valid users of EOF interrupts.

No, there are no two users, there is *one* user, namely idmac, as
the output from /proc/interrupts clearly shows. The users are *not* the
client drivers but the idmac code.

Hm, what else can I say that you understand what I mean?

Why would you dispatch one physical interrupt to 32 interrupts (only
counting the *_EOF irqs) which all lead back to the same interrupt handler?

The client drivers are not interested in the interrupts, only the idmac
code is. Even if they were, you provided a callback function for this
case in your idmac interrupt handling code:

+ if (done && (desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
+ callback(callback_param);



> And you're
> suggesting to put them on one interrupt, and even not as a shared IRQ with
> two handlers - one interrupt with one handler with multiple sources /
> users behind it... What likely will happen someone will add support for
> YUV / RGB cameras and will want to use hardware processing for them, like
> encoding and / or rotation, which will add DMA channels DMAIC_0, DMAIC_8,
> DMAIC_6, DMAIC_10...

Who is going to handle these channels along with the interrupts? Right,
ipu_idmac.c.


> and you want to put them all on one IRQ?... What we
> could improve in the above /proc/interrupts output is register interrupts
> with different names to make distinguishing them easier.

No, the output of /proc/interrupts shows the correct situation. You can
take this as a hint for what is going wrong here.

> But I really
> would not like to put them all together.
>
> > > This is a framebuffer driver for i.MX31 SoCs. It only supports synchronous
> > > displays, overlay support is included but has never been tested.
> >
> > No, it's not working. The overlay framebuffer maybe there, but it's
> > configured to be invisible.
>
> Good, will remove it then.
>
> Thanks
> Guennadi
> ---
> Guennadi Liakhovetski, Ph.D.
> Freelance Open-Source Software Developer
>

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 14:55:42

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, 23 Dec 2008, Sascha Hauer wrote:

> On Tue, Dec 23, 2008 at 02:14:12PM +0100, Guennadi Liakhovetski wrote:
> > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> >
> > > On Tue, Dec 23, 2008 at 12:21:54PM +0100, Guennadi Liakhovetski wrote:
> > > > Hi Sascha
> > > >
> > > > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> > > >
> > > > > On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> > > > > >
> > > > > > Ok, so, what would we like to have there? We agree that the proper way to
> > > > > > serve them is a irq-chip driver, right?
> > > > >
> > > > > In case of the *_EOF interrupts when they can be used outside the idmac
> > > > > driver then yes. If not then not for the reasons I explained.
> > > >
> > > > Wait a minute, are you suggesting to handle interrupts that are exported
> > > > to client drivers and that are "internal" to ipu_idmac differently? Like
> > > > exported once - properly using the irq chip machinery, and internal once
> > > > just demux in the driver hiding them from the kernel?... Or have I
> > > > misunderstood you? If this is indeed what you mean, then that doesn't
> > > > sound like a good idea to me, sorry. Like you configure a chained handler,
> > > > then when it is called on an IRQ, you check if the reason is bits 0, 1, or
> > > > 2 you call generic_handle_irq(), for other bits you handle them
> > > > internally... grrrr...
> > >
> > > I'm not suggesting that. The *_EOF registers are all 32 bits on the *same*
> > > register. And these 32 interrupts are inherently occupied by ipu_idmac.c
> > > as you cannot request a idmac channel without requesting this interrupt.
> > > So at the moment you are passing 32 bits of the same register through a
> > > chained interrupt handler and *all* these interrupts go to the very same
> > > interrupt handler.
> > > For the corresponding 32 error interrupts it's probably also the idmac
> > > engine that has to react to these interrupts, not the drivers using it.
> > > Not sure about the remaining assorted interrupts.
> >
> > Yes, I understand that. So, you're suggesting to put all EOF interrupts
> > under 1 irq-number. Just for example: now I have
> >
> > 167: 351 ipu_irq idmac
> > 174: 752 ipu_irq idmac
> > 175: 0 ipu_irq idmac
> > 230: 1 ipu_irq csi
> >
> > where 167 - camera IRQs, 174 - framebuffer (panning), 175 - overlay (ok,
> > this one will go), 230 - CSI_EOF from IPU_INT_STAT_3 (ok, it is not used
> > ATM, can go too).
> >
> > So, there are at least two valid users of EOF interrupts.
>
> No, there are no two users, there is *one* user, namely idmac, as
> the output from /proc/interrupts clearly shows. The users are *not* the
> client drivers but the idmac code.
>
> Hm, what else can I say that you understand what I mean?
>
> Why would you dispatch one physical interrupt to 32 interrupts (only
> counting the *_EOF irqs) which all lead back to the same interrupt handler?
>
> The client drivers are not interested in the interrupts, only the idmac
> code is. Even if they were, you provided a callback function for this
> case in your idmac interrupt handling code:
>
> + if (done && (desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
> + callback(callback_param);

Ok, fair enough, you can see all *_EOF interrupts as being consumed by one
user - the dma channel driver. So then it is similar to a serial (or
whatever else) driver handling multiple instances of the hardware on one
system.

Let's consider what we would get, if we don't use the irq chip API:

1. we request two interrupts - function and error - normally using
request_irq in the ipu irq driver

2. in the dma channel driver you have to register new clients with the ipu
irq driver so it can call the dma channel driver EOF irq handler with
the correct argument - the number of interrupting DMA channel

3. on an interrupt you have to implement masking / unmasking / acking of
interrupts, list locking, whatever it takes yourself

so you end up with an API similar to ipu_request_irq() from Freescale...
Yes, you can allocate your interrupt descriptors dynamically, you can save
a few more bytes in every descriptor object... But is it all worth it?

Ok, we can make this differently:

1. I create a static constant array of virtual irq number <-> ipu irq bit
maps, with currently only two elements - for the framebuffer and the
camera, and use it in irq demultiplexing.

2. I increment NR_IRQS by two and put a comment explaining where to look
for implementing support for more IPU interrupts.

3. as new users come and implement support for more IPU interrupts, they
add entries to the array and increment NR_IRQS.

This way we add only as many irq descriptors as we really use, but the
mapping becomes absolutely internal, so looking in /proc/interupts gives
you little understanding of whose interrupts you see there.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 16:09:54

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 03:55:36PM +0100, Guennadi Liakhovetski wrote:
> On Tue, 23 Dec 2008, Sascha Hauer wrote:
>
> > On Tue, Dec 23, 2008 at 02:14:12PM +0100, Guennadi Liakhovetski wrote:
> > > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> > >
> > > > On Tue, Dec 23, 2008 at 12:21:54PM +0100, Guennadi Liakhovetski wrote:
> > > > > Hi Sascha
> > > > >
> > > > > On Tue, 23 Dec 2008, Sascha Hauer wrote:
> > > > >
> > > > > > On Mon, Dec 22, 2008 at 09:10:03PM +0100, Guennadi Liakhovetski wrote:
> > > > > > >
> > > > > > > Ok, so, what would we like to have there? We agree that the proper way to
> > > > > > > serve them is a irq-chip driver, right?
> > > > > >
> > > > > > In case of the *_EOF interrupts when they can be used outside the idmac
> > > > > > driver then yes. If not then not for the reasons I explained.
> > > > >
> > > > > Wait a minute, are you suggesting to handle interrupts that are exported
> > > > > to client drivers and that are "internal" to ipu_idmac differently? Like
> > > > > exported once - properly using the irq chip machinery, and internal once
> > > > > just demux in the driver hiding them from the kernel?... Or have I
> > > > > misunderstood you? If this is indeed what you mean, then that doesn't
> > > > > sound like a good idea to me, sorry. Like you configure a chained handler,
> > > > > then when it is called on an IRQ, you check if the reason is bits 0, 1, or
> > > > > 2 you call generic_handle_irq(), for other bits you handle them
> > > > > internally... grrrr...
> > > >
> > > > I'm not suggesting that. The *_EOF registers are all 32 bits on the *same*
> > > > register. And these 32 interrupts are inherently occupied by ipu_idmac.c
> > > > as you cannot request a idmac channel without requesting this interrupt.
> > > > So at the moment you are passing 32 bits of the same register through a
> > > > chained interrupt handler and *all* these interrupts go to the very same
> > > > interrupt handler.
> > > > For the corresponding 32 error interrupts it's probably also the idmac
> > > > engine that has to react to these interrupts, not the drivers using it.
> > > > Not sure about the remaining assorted interrupts.
> > >
> > > Yes, I understand that. So, you're suggesting to put all EOF interrupts
> > > under 1 irq-number. Just for example: now I have
> > >
> > > 167: 351 ipu_irq idmac
> > > 174: 752 ipu_irq idmac
> > > 175: 0 ipu_irq idmac
> > > 230: 1 ipu_irq csi
> > >
> > > where 167 - camera IRQs, 174 - framebuffer (panning), 175 - overlay (ok,
> > > this one will go), 230 - CSI_EOF from IPU_INT_STAT_3 (ok, it is not used
> > > ATM, can go too).
> > >
> > > So, there are at least two valid users of EOF interrupts.
> >
> > No, there are no two users, there is *one* user, namely idmac, as
> > the output from /proc/interrupts clearly shows. The users are *not* the
> > client drivers but the idmac code.
> >
> > Hm, what else can I say that you understand what I mean?
> >
> > Why would you dispatch one physical interrupt to 32 interrupts (only
> > counting the *_EOF irqs) which all lead back to the same interrupt handler?
> >
> > The client drivers are not interested in the interrupts, only the idmac
> > code is. Even if they were, you provided a callback function for this
> > case in your idmac interrupt handling code:
> >
> > + if (done && (desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
> > + callback(callback_param);
>
> Ok, fair enough, you can see all *_EOF interrupts as being consumed by one
> user - the dma channel driver. So then it is similar to a serial (or
> whatever else) driver handling multiple instances of the hardware on one
> system.
>
> Let's consider what we would get, if we don't use the irq chip API:
>
> 1. we request two interrupts - function and error - normally using
> request_irq in the ipu irq driver
>
> 2. in the dma channel driver you have to register new clients with the ipu
> irq driver so it can call the dma channel driver EOF irq handler with
> the correct argument - the number of interrupting DMA channel
>
> 3. on an interrupt you have to implement masking / unmasking / acking of
> interrupts, list locking, whatever it takes yourself
>
> so you end up with an API similar to ipu_request_irq() from Freescale...
> Yes, you can allocate your interrupt descriptors dynamically, you can save
> a few more bytes in every descriptor object... But is it all worth it?
>
> Ok, we can make this differently:
>
> 1. I create a static constant array of virtual irq number <-> ipu irq bit
> maps, with currently only two elements - for the framebuffer and the
> camera, and use it in irq demultiplexing.
>
> 2. I increment NR_IRQS by two and put a comment explaining where to look
> for implementing support for more IPU interrupts.
>
> 3. as new users come and implement support for more IPU interrupts, they
> add entries to the array and increment NR_IRQS.
>
> This way we add only as many irq descriptors as we really use, but the
> mapping becomes absolutely internal, so looking in /proc/interupts gives
> you little understanding of whose interrupts you see there.

You're still not getting it. Consider an implementation of the chained
interrupt handler like this:

static struct ipu_irq_bank irq_bank_fn[IPU_IRQ_NR_FN_BANKS] = {
{
.nr_irqs = 1, /* IDMAC EOF and NF irq */
}, {
.nr_irqs = 24, /* misc interrupts the IPU is not
* interested in, but clients drivers
* may be.
*/
},
};

static struct ipu_irq_bank irq_bank_err[IPU_IRQ_NR_ERR_BANKS] = {
/* 2 groups of error interrupts */
{
.nr_irqs = 1, /* IDMAC channel error irq */
}, {
.nr_irqs = 17, /* misc interrupts the IPU is not
* interested in, but clients drivers
* may be.
*/
},
};

/* Chained IRQ handler for IPU error interrupt */
static void ipu_irq_err(unsigned int irq, struct irq_desc *desc)
{
struct ipu *ipu = get_irq_data(irq);
u32 status;
int i, line;

/* These interrupts are handled exclusively by the IPU code */
if (ipu_read_reg(ipu, IPU_INT_STAT_4))
generic_handle_irq(IPU_IRQ);

status = ipu_read_reg(ipu, IPU_INT_STAT_5);
while ((line = ffs(status))) {
status &= ~(1UL << (line - 1));
generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
}
}

/* Chained IRQ handler for IPU function interrupt */
static void ipu_irq_fn(unsigned int irq, struct irq_desc *desc)
{
struct ipu *ipu = get_irq_data(irq);
u32 status;
int i, line;

/* These interrupts are handled exclusively by the IPU code */
if (ipu_read_reg(ipu, IPU_INT_STAT_1) || ipu_read_reg(ipu, IPU_INT_STAT_2))
generic_handle_irq(IPU_ERR_IRQ);

status = ipu_read_reg(ipu, IPU_INT_STAT_3);
while ((line = ffs(status))) {
status &= ~(1UL << (line - 1));
generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
}
}


and in ipu_idmac.c:

request_irq(IPU_ERR_IRQ, &ipu_err_handler);
request_irq(IPU_IRQ, &ipu_irq);


That said I'm not sure whether we need a chained interrupt handler at all.
Looking at the remaining interrupts it seems that for example the
CSI_EOF (IPU_INT_STAT3 bit 5) interrupt is redundant to the corresponding
channels interrupt. In the camera driver you already realized that it's
the dma end event you're interested in, not the CSI_EOF event. Not sure
if that holds for the other interrupts though.

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-23 16:33:17

by Guennadi Liakhovetski

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, 23 Dec 2008, Sascha Hauer wrote:

> You're still not getting it. Consider an implementation of the chained
> interrupt handler like this:
>
> static struct ipu_irq_bank irq_bank_fn[IPU_IRQ_NR_FN_BANKS] = {
> {
> .nr_irqs = 1, /* IDMAC EOF and NF irq */
> }, {
> .nr_irqs = 24, /* misc interrupts the IPU is not
> * interested in, but clients drivers
> * may be.
> */
> },
> };
>
> static struct ipu_irq_bank irq_bank_err[IPU_IRQ_NR_ERR_BANKS] = {
> /* 2 groups of error interrupts */
> {
> .nr_irqs = 1, /* IDMAC channel error irq */
> }, {
> .nr_irqs = 17, /* misc interrupts the IPU is not
> * interested in, but clients drivers
> * may be.
> */
> },
> };
>
> /* Chained IRQ handler for IPU error interrupt */
> static void ipu_irq_err(unsigned int irq, struct irq_desc *desc)
> {
> struct ipu *ipu = get_irq_data(irq);
> u32 status;
> int i, line;
>
> /* These interrupts are handled exclusively by the IPU code */
> if (ipu_read_reg(ipu, IPU_INT_STAT_4))
> generic_handle_irq(IPU_IRQ);
>
> status = ipu_read_reg(ipu, IPU_INT_STAT_5);
> while ((line = ffs(status))) {
> status &= ~(1UL << (line - 1));
> generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
> }
> }
>
> /* Chained IRQ handler for IPU function interrupt */
> static void ipu_irq_fn(unsigned int irq, struct irq_desc *desc)
> {
> struct ipu *ipu = get_irq_data(irq);
> u32 status;
> int i, line;
>
> /* These interrupts are handled exclusively by the IPU code */
> if (ipu_read_reg(ipu, IPU_INT_STAT_1) || ipu_read_reg(ipu, IPU_INT_STAT_2))
> generic_handle_irq(IPU_ERR_IRQ);
>
> status = ipu_read_reg(ipu, IPU_INT_STAT_3);
> while ((line = ffs(status))) {
> status &= ~(1UL << (line - 1));
> generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
> }
> }
>
>
> and in ipu_idmac.c:
>
> request_irq(IPU_ERR_IRQ, &ipu_err_handler);
> request_irq(IPU_IRQ, &ipu_irq);

These all are just slightly different variations - with this one you'd
have to scan dma channels in your dma irq handler, which I currently don't
have to do.

> That said I'm not sure whether we need a chained interrupt handler at all.
> Looking at the remaining interrupts it seems that for example the
> CSI_EOF (IPU_INT_STAT3 bit 5) interrupt is redundant to the corresponding
> channels interrupt. In the camera driver you already realized that it's
> the dma end event you're interested in, not the CSI_EOF event. Not sure
> if that holds for the other interrupts though.

I think the version with a lookup table is optimal - it uses standard
mechanisms for irq handling and doesn't waste RAM. The disadvantage -
every addition will need driver modification...

But ok, this is taking way too long. Just tell me which way you'd like to
have it and I'll do it.

Thanks
Guennadi
---
Guennadi Liakhovetski, Ph.D.
Freelance Open-Source Software Developer

2008-12-23 17:15:02

by Sascha Hauer

[permalink] [raw]
Subject: Re: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

On Tue, Dec 23, 2008 at 05:33:11PM +0100, Guennadi Liakhovetski wrote:
>
> These all are just slightly different variations - with this one you'd
> have to scan dma channels in your dma irq handler, which I currently don't
> have to do.
>
> > That said I'm not sure whether we need a chained interrupt handler at all.
> > Looking at the remaining interrupts it seems that for example the
> > CSI_EOF (IPU_INT_STAT3 bit 5) interrupt is redundant to the corresponding
> > channels interrupt. In the camera driver you already realized that it's
> > the dma end event you're interested in, not the CSI_EOF event. Not sure
> > if that holds for the other interrupts though.
>
> I think the version with a lookup table is optimal - it uses standard
> mechanisms for irq handling and doesn't waste RAM. The disadvantage -
> every addition will need driver modification...
>
> But ok, this is taking way too long. Just tell me which way you'd like to
> have it and I'll do it.

Preferably a way which does not involve introducing 100+ unused
interrupts.

Sascha

--
Pengutronix e.K. | |
Industrial Linux Solutions | http://www.pengutronix.de/ |
Peiner Str. 6-8, 31137 Hildesheim, Germany | Phone: +49-5121-206917-0 |
Amtsgericht Hildesheim, HRA 2686 | Fax: +49-5121-206917-5555 |

2008-12-18 13:26:29

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH 1/4 v4] dmaengine: add async_tx_clear_ack() macro

From: Guennadi Liakhovetski <[email protected]>

To complete the DMA_CTRL_ACK handling API add a async_tx_clear_ack()
macro.

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---

No change since previous version. Included for completeness.

include/linux/dmaengine.h | 5 +++++
1 files changed, 5 insertions(+), 0 deletions(-)

diff --git a/include/linux/dmaengine.h b/include/linux/dmaengine.h
index 64dea2a..c820a3f 100644
--- a/include/linux/dmaengine.h
+++ b/include/linux/dmaengine.h
@@ -287,6 +287,11 @@ static inline void async_tx_ack(struct dma_async_tx_descriptor *tx)
tx->flags |= DMA_CTRL_ACK;
}

+static inline void async_tx_clear_ack(struct dma_async_tx_descriptor *tx)
+{
+ tx->flags &= ~DMA_CTRL_ACK;
+}
+
static inline bool async_tx_test_ack(struct dma_async_tx_descriptor *tx)
{
return (tx->flags & DMA_CTRL_ACK) == DMA_CTRL_ACK;
--
1.5.4

2008-12-18 13:26:46

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH 2/4 v4] i.MX31: Image Processing Unit DMA and IRQ drivers

From: Guennadi Liakhovetski <[email protected]>

i.MX3x SoCs contain an Image Processing Unit, consisting of a Control
Module (CM), Display Interface (DI), Synchronous Display Controller (SDC),
Asynchronous Display Controller (ADC), Image Converter (IC), Post-Filter
(PF), Camera Sensor Interface (CSI), and an Image DMA Controller (IDMAC).
CM contains, among other blocks, an Interrupt Generator (IG) and a Clock
and Reset Control Unit (CRCU). This driver serves IDMAC and IG. They are
supported over dmaengine and irq-chip APIs respectively.

IDMAC is a specialised DMA controller, its DMA channels cannot be used for
general-purpose operations, even though it might be possible to configure
a memory-to-memory channel for memcpy operation. This driver will not work
with generic dmaengine clients, clients, wishing to use it must use
respective wrapper structures, they also must specify which channels they
require, as channels are hard-wired to specific IPU functions.

Based on original driver from Freescale, Copyright preserved.

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---

For changes see [PATCH 0/4]

arch/arm/plat-mxc/include/mach/ipu.h | 180 ++++
arch/arm/plat-mxc/include/mach/mx31.h | 139 +++-
drivers/mfd/Kconfig | 16 +
drivers/mfd/Makefile | 4 +-
drivers/mfd/ipu/Makefile | 5 +
drivers/mfd/ipu/ipu_idmac.c | 1662 +++++++++++++++++++++++++++++++++
drivers/mfd/ipu/ipu_intern.h | 174 ++++
drivers/mfd/ipu/ipu_irq.c | 277 ++++++
8 files changed, 2455 insertions(+), 2 deletions(-)
create mode 100644 arch/arm/plat-mxc/include/mach/ipu.h
create mode 100644 drivers/mfd/ipu/Makefile
create mode 100644 drivers/mfd/ipu/ipu_idmac.c
create mode 100644 drivers/mfd/ipu/ipu_intern.h
create mode 100644 drivers/mfd/ipu/ipu_irq.c

diff --git a/arch/arm/plat-mxc/include/mach/ipu.h b/arch/arm/plat-mxc/include/mach/ipu.h
new file mode 100644
index 0000000..18538b4
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/ipu.h
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * Copyright (C) 2005-2007 Freescale Semiconductor, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IPU_H_
+#define _IPU_H_
+
+#include <linux/types.h>
+#include <linux/dmaengine.h>
+
+/* IPU DMA Controller channel definitions. */
+enum ipu_channel {
+ IDMAC_IC_0 = 0, /* IC (encoding task) to memory */
+ IDMAC_IC_1 = 1, /* IC (viewfinder task) to memory */
+ IDMAC_ADC_0 = 1,
+ IDMAC_IC_2 = 2,
+ IDMAC_ADC_1 = 2,
+ IDMAC_IC_3 = 3,
+ IDMAC_IC_4 = 4,
+ IDMAC_IC_5 = 5,
+ IDMAC_IC_6 = 6,
+ IDMAC_IC_7 = 7, /* IC (sensor data) to memory */
+ IDMAC_IC_8 = 8,
+ IDMAC_IC_9 = 9,
+ IDMAC_IC_10 = 10,
+ IDMAC_IC_11 = 11,
+ IDMAC_IC_12 = 12,
+ IDMAC_IC_13 = 13,
+ IDMAC_SDC_0 = 14, /* Background synchronous display data */
+ IDMAC_SDC_1 = 15, /* Foreground data (overlay) */
+ IDMAC_SDC_2 = 16,
+ IDMAC_SDC_3 = 17,
+ IDMAC_ADC_2 = 18,
+ IDMAC_ADC_3 = 19,
+ IDMAC_ADC_4 = 20,
+ IDMAC_ADC_5 = 21,
+ IDMAC_ADC_6 = 22,
+ IDMAC_ADC_7 = 23,
+ IDMAC_PF_0 = 24,
+ IDMAC_PF_1 = 25,
+ IDMAC_PF_2 = 26,
+ IDMAC_PF_3 = 27,
+ IDMAC_PF_4 = 28,
+ IDMAC_PF_5 = 29,
+ IDMAC_PF_6 = 30,
+ IDMAC_PF_7 = 31,
+};
+
+/* Order significant! */
+enum ipu_channel_status {
+ IPU_CHANNEL_FREE,
+ IPU_CHANNEL_INITIALIZED,
+ IPU_CHANNEL_READY,
+ IPU_CHANNEL_ENABLED,
+};
+
+#define IPU_CHANNELS_NUM 32
+
+enum pixel_fmt {
+ /* 1 byte */
+ IPU_PIX_FMT_GENERIC,
+ IPU_PIX_FMT_RGB332,
+ IPU_PIX_FMT_YUV420P,
+ IPU_PIX_FMT_YUV422P,
+ IPU_PIX_FMT_YUV420P2,
+ IPU_PIX_FMT_YVU422P,
+ /* 2 bytes */
+ IPU_PIX_FMT_RGB565,
+ IPU_PIX_FMT_RGB666,
+ IPU_PIX_FMT_BGR666,
+ IPU_PIX_FMT_YUYV,
+ IPU_PIX_FMT_UYVY,
+ /* 3 bytes */
+ IPU_PIX_FMT_RGB24,
+ IPU_PIX_FMT_BGR24,
+ /* 4 bytes */
+ IPU_PIX_FMT_GENERIC_32,
+ IPU_PIX_FMT_RGB32,
+ IPU_PIX_FMT_BGR32,
+ IPU_PIX_FMT_ABGR32,
+ IPU_PIX_FMT_BGRA32,
+ IPU_PIX_FMT_RGBA32,
+};
+
+enum ipu_color_space {
+ IPU_COLORSPACE_RGB,
+ IPU_COLORSPACE_YCBCR,
+ IPU_COLORSPACE_YUV
+};
+
+/**
+ * Enumeration of IPU rotation modes
+ */
+enum ipu_rotate_mode {
+ /* Note the enum values correspond to BAM value */
+ IPU_ROTATE_NONE = 0,
+ IPU_ROTATE_VERT_FLIP = 1,
+ IPU_ROTATE_HORIZ_FLIP = 2,
+ IPU_ROTATE_180 = 3,
+ IPU_ROTATE_90_RIGHT = 4,
+ IPU_ROTATE_90_RIGHT_VFLIP = 5,
+ IPU_ROTATE_90_RIGHT_HFLIP = 6,
+ IPU_ROTATE_90_LEFT = 7,
+};
+
+struct ipu_platform_data {
+ unsigned int irq_base;
+};
+
+/**
+ * Enumeration of DI ports for ADC.
+ */
+enum display_port {
+ DISP0,
+ DISP1,
+ DISP2,
+ DISP3
+};
+
+struct idmac_video_param {
+ unsigned short in_width;
+ unsigned short in_height;
+ uint32_t in_pixel_fmt;
+ unsigned short out_width;
+ unsigned short out_height;
+ uint32_t out_pixel_fmt;
+ unsigned short out_stride;
+ bool graphics_combine_en;
+ bool global_alpha_en;
+ bool key_color_en;
+ enum display_port disp;
+ unsigned short out_left;
+ unsigned short out_top;
+};
+
+/*
+ * Union of initialization parameters for a logical channel. So far only video
+ * parameters are used.
+ */
+union ipu_channel_param {
+ struct idmac_video_param video;
+};
+
+struct idmac_tx_desc {
+ struct dma_async_tx_descriptor txd;
+ struct scatterlist *sg; /* scatterlist for this */
+ unsigned int sg_len; /* tx-descriptor. */
+ struct list_head list;
+};
+
+struct idmac_channel {
+ struct dma_chan dma_chan;
+ dma_cookie_t completed; /* last completed cookie */
+ union ipu_channel_param params;
+ enum ipu_channel link; /* input channel, linked to the output */
+ enum ipu_channel_status status;
+ void *client; /* Only one client per channel */
+ unsigned int n_tx_desc;
+ struct idmac_tx_desc *desc; /* allocated tx-descriptors */
+ struct scatterlist *sg[2]; /* scatterlist elements in buffer-0 and -1 */
+ struct list_head free_list; /* free tx-descriptors */
+ struct list_head queue; /* queued tx-descriptors */
+ spinlock_t lock; /* protects sg[0,1], queue */
+ struct mutex chan_mutex; /* protects status, cookie, free_list */
+ unsigned int eof_irq;
+ bool sec_chan_en;
+ int active_buffer;
+};
+
+#define to_tx_desc(tx) container_of(tx, struct idmac_tx_desc, txd)
+#define to_idmac_chan(c) container_of(c, struct idmac_channel, dma_chan)
+
+#endif
diff --git a/arch/arm/plat-mxc/include/mach/mx31.h b/arch/arm/plat-mxc/include/mach/mx31.h
index 0536f89..a47f862 100644
--- a/arch/arm/plat-mxc/include/mach/mx31.h
+++ b/arch/arm/plat-mxc/include/mach/mx31.h
@@ -319,9 +319,146 @@

#define MXC_GPIO_INT_BASE MXC_MAX_INT_LINES
#define MXC_MAX_GPIO_LINES (GPIO_NUM_PIN * GPIO_PORT_NUM)
+
+#define MXC_IPU_INT_BASE (MXC_MAX_INT_LINES + MXC_MAX_GPIO_LINES)
+
+#define IPU_IRQ_PRP_ENC_OUT_EOF (MXC_IPU_INT_BASE + 0)
+#define IPU_IRQ_PRP_VF_OUT_EOF (MXC_IPU_INT_BASE + 1)
+#define IPU_IRQ_PP_OUT_EOF (MXC_IPU_INT_BASE + 2)
+#define IPU_IRQ_PRP_GRAPH_IN_EOF (MXC_IPU_INT_BASE + 3)
+#define IPU_IRQ_PP_GRAPH_IN_EOF (MXC_IPU_INT_BASE + 4)
+#define IPU_IRQ_PP_IN_EOF (MXC_IPU_INT_BASE + 5)
+#define IPU_IRQ_PRP_IN_EOF (MXC_IPU_INT_BASE + 6)
+#define IPU_IRQ_SENSOR_OUT_EOF (MXC_IPU_INT_BASE + 7)
+#define IPU_IRQ_PRP_ENC_ROT_OUT_EOF (MXC_IPU_INT_BASE + 8)
+#define IPU_IRQ_PRP_VF_ROT_OUT_EOF (MXC_IPU_INT_BASE + 9)
+#define IPU_IRQ_PRP_ENC_ROT_IN_EOF (MXC_IPU_INT_BASE + 10)
+#define IPU_IRQ_PRP_VF_ROT_IN_EOF (MXC_IPU_INT_BASE + 11)
+#define IPU_IRQ_PP_ROT_OUT_EOF (MXC_IPU_INT_BASE + 12)
+#define IPU_IRQ_PP_ROT_IN_EOF (MXC_IPU_INT_BASE + 13)
+#define IPU_IRQ_SDC_BG_EOF (MXC_IPU_INT_BASE + 14)
+#define IPU_IRQ_SDC_FG_EOF (MXC_IPU_INT_BASE + 15)
+#define IPU_IRQ_SDC_MASK_EOF (MXC_IPU_INT_BASE + 16)
+#define IPU_IRQ_SDC_BG_PART_EOF (MXC_IPU_INT_BASE + 17)
+#define IPU_IRQ_ADC_SYS1_WR_EOF (MXC_IPU_INT_BASE + 18)
+#define IPU_IRQ_ADC_SYS2_WR_EOF (MXC_IPU_INT_BASE + 19)
+#define IPU_IRQ_ADC_SYS1_CMD_EOF (MXC_IPU_INT_BASE + 20)
+#define IPU_IRQ_ADC_SYS2_CMD_EOF (MXC_IPU_INT_BASE + 21)
+#define IPU_IRQ_ADC_SYS1_RD_EOF (MXC_IPU_INT_BASE + 22)
+#define IPU_IRQ_ADC_SYS2_RD_EOF (MXC_IPU_INT_BASE + 23)
+#define IPU_IRQ_PF_QP_IN_EOF (MXC_IPU_INT_BASE + 24)
+#define IPU_IRQ_PF_BSP_IN_EOF (MXC_IPU_INT_BASE + 25)
+#define IPU_IRQ_PF_Y_IN_EOF (MXC_IPU_INT_BASE + 26)
+#define IPU_IRQ_PF_U_IN_EOF (MXC_IPU_INT_BASE + 27)
+#define IPU_IRQ_PF_V_IN_EOF (MXC_IPU_INT_BASE + 28)
+#define IPU_IRQ_PF_Y_OUT_EOF (MXC_IPU_INT_BASE + 29)
+#define IPU_IRQ_PF_U_OUT_EOF (MXC_IPU_INT_BASE + 30)
+#define IPU_IRQ_PF_V_OUT_EOF (MXC_IPU_INT_BASE + 31)
+
+#define IPU_IRQ_PRP_ENC_OUT_NF (MXC_IPU_INT_BASE + 32)
+#define IPU_IRQ_PRP_VF_OUT_NF (MXC_IPU_INT_BASE + 33)
+#define IPU_IRQ_PP_OUT_NF (MXC_IPU_INT_BASE + 34)
+#define IPU_IRQ_PRP_GRAPH_IN_NF (MXC_IPU_INT_BASE + 35)
+#define IPU_IRQ_PP_GRAPH_IN_NF (MXC_IPU_INT_BASE + 36)
+#define IPU_IRQ_PP_IN_NF (MXC_IPU_INT_BASE + 37)
+#define IPU_IRQ_PRP_IN_NF (MXC_IPU_INT_BASE + 38)
+#define IPU_IRQ_SENSOR_OUT_NF (MXC_IPU_INT_BASE + 39)
+#define IPU_IRQ_PRP_ENC_ROT_OUT_NF (MXC_IPU_INT_BASE + 40)
+#define IPU_IRQ_PRP_VF_ROT_OUT_NF (MXC_IPU_INT_BASE + 41)
+#define IPU_IRQ_PRP_ENC_ROT_IN_NF (MXC_IPU_INT_BASE + 42)
+#define IPU_IRQ_PRP_VF_ROT_IN_NF (MXC_IPU_INT_BASE + 43)
+#define IPU_IRQ_PP_ROT_OUT_NF (MXC_IPU_INT_BASE + 44)
+#define IPU_IRQ_PP_ROT_IN_NF (MXC_IPU_INT_BASE + 45)
+#define IPU_IRQ_SDC_FG_NF (MXC_IPU_INT_BASE + 46)
+#define IPU_IRQ_SDC_BG_NF (MXC_IPU_INT_BASE + 47)
+#define IPU_IRQ_SDC_MASK_NF (MXC_IPU_INT_BASE + 48)
+#define IPU_IRQ_SDC_BG_PART_NF (MXC_IPU_INT_BASE + 49)
+#define IPU_IRQ_ADC_SYS1_WR_NF (MXC_IPU_INT_BASE + 50)
+#define IPU_IRQ_ADC_SYS2_WR_NF (MXC_IPU_INT_BASE + 51)
+#define IPU_IRQ_ADC_SYS1_CMD_NF (MXC_IPU_INT_BASE + 52)
+#define IPU_IRQ_ADC_SYS2_CMD_NF (MXC_IPU_INT_BASE + 53)
+#define IPU_IRQ_ADC_SYS1_RD_NF (MXC_IPU_INT_BASE + 54)
+#define IPU_IRQ_ADC_SYS2_RD_NF (MXC_IPU_INT_BASE + 55)
+#define IPU_IRQ_PF_QP_IN_NF (MXC_IPU_INT_BASE + 56)
+#define IPU_IRQ_PF_BSP_IN_NF (MXC_IPU_INT_BASE + 57)
+#define IPU_IRQ_PF_Y_IN_NF (MXC_IPU_INT_BASE + 58)
+#define IPU_IRQ_PF_U_IN_NF (MXC_IPU_INT_BASE + 59)
+#define IPU_IRQ_PF_V_IN_NF (MXC_IPU_INT_BASE + 60)
+#define IPU_IRQ_PF_Y_OUT_NF (MXC_IPU_INT_BASE + 61)
+#define IPU_IRQ_PF_U_OUT_NF (MXC_IPU_INT_BASE + 62)
+#define IPU_IRQ_PF_V_OUT_NF (MXC_IPU_INT_BASE + 63)
+
+#define IPU_IRQ_BREAKRQ (MXC_IPU_INT_BASE + 64)
+#define IPU_IRQ_SDC_BG_OUT_EOF (MXC_IPU_INT_BASE + 65)
+#define IPU_IRQ_SDC_FG_OUT_EOF (MXC_IPU_INT_BASE + 66)
+#define IPU_IRQ_SDC_MASK_OUT_EOF (MXC_IPU_INT_BASE + 67)
+#define IPU_IRQ_ADC_SERIAL_DATA_OUT (MXC_IPU_INT_BASE + 68)
+#define IPU_IRQ_SENSOR_NF (MXC_IPU_INT_BASE + 69)
+#define IPU_IRQ_SENSOR_EOF (MXC_IPU_INT_BASE + 70)
+#define IPU_IRQ_SDC_DISP3_VSYNC (MXC_IPU_INT_BASE + 80)
+#define IPU_IRQ_ADC_DISP0_VSYNC (MXC_IPU_INT_BASE + 81)
+#define IPU_IRQ_ADC_DISP12_VSYNC (MXC_IPU_INT_BASE + 82)
+#define IPU_IRQ_ADC_PRP_EOF (MXC_IPU_INT_BASE + 83)
+#define IPU_IRQ_ADC_PP_EOF (MXC_IPU_INT_BASE + 84)
+#define IPU_IRQ_ADC_SYS1_EOF (MXC_IPU_INT_BASE + 85)
+#define IPU_IRQ_ADC_SYS2_EOF (MXC_IPU_INT_BASE + 86)
+
+#define IPU_IRQ_PRP_ENC_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 96)
+#define IPU_IRQ_PRP_VF_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 97)
+#define IPU_IRQ_PP_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 98)
+#define IPU_IRQ_PRP_GRAPH_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 99)
+#define IPU_IRQ_PP_GRAPH_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 100)
+#define IPU_IRQ_PP_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 101)
+#define IPU_IRQ_PRP_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 102)
+#define IPU_IRQ_SENSOR_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 103)
+#define IPU_IRQ_PRP_ENC_ROT_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 104)
+#define IPU_IRQ_PRP_VF_ROT_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 105)
+#define IPU_IRQ_PRP_ENC_ROT_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 106)
+#define IPU_IRQ_PRP_VF_ROT_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 107)
+#define IPU_IRQ_PP_ROT_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 108)
+#define IPU_IRQ_PP_ROT_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 109)
+#define IPU_IRQ_SDC_FG_NFB4EOF_ERR (MXC_IPU_INT_BASE + 110)
+#define IPU_IRQ_SDC_BG_NFB4EOF_ERR (MXC_IPU_INT_BASE + 111)
+#define IPU_IRQ_SDC_MASK_NFB4EOF_ERR (MXC_IPU_INT_BASE + 112)
+#define IPU_IRQ_SDC_BG_PART_NFB4EOF_ERR (MXC_IPU_INT_BASE + 113)
+#define IPU_IRQ_ADC_SYS1_WR_NFB4EOF_ERR (MXC_IPU_INT_BASE + 114)
+#define IPU_IRQ_ADC_SYS2_WR_NFB4EOF_ERR (MXC_IPU_INT_BASE + 115)
+#define IPU_IRQ_ADC_SYS1_CMD_NFB4EOF_ERR (MXC_IPU_INT_BASE + 116)
+#define IPU_IRQ_ADC_SYS2_CMD_NFB4EOF_ERR (MXC_IPU_INT_BASE + 117)
+#define IPU_IRQ_ADC_SYS1_RD_NFB4EOF_ERR (MXC_IPU_INT_BASE + 118)
+#define IPU_IRQ_ADC_SYS2_RD_NFB4EOF_ERR (MXC_IPU_INT_BASE + 119)
+#define IPU_IRQ_PF_QP_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 120)
+#define IPU_IRQ_PF_BSP_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 121)
+#define IPU_IRQ_PF_Y_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 122)
+#define IPU_IRQ_PF_U_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 123)
+#define IPU_IRQ_PF_V_IN_NFB4EOF_ERR (MXC_IPU_INT_BASE + 124)
+#define IPU_IRQ_PF_Y_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 125)
+#define IPU_IRQ_PF_U_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 126)
+#define IPU_IRQ_PF_V_OUT_NFB4EOF_ERR (MXC_IPU_INT_BASE + 127)
+
+#define IPU_IRQ_BAYER_BUFOVF_ERR (MXC_IPU_INT_BASE + 128)
+#define IPU_IRQ_ENC_BUFOVF_ERR (MXC_IPU_INT_BASE + 129)
+#define IPU_IRQ_VF_BUFOVF_ERR (MXC_IPU_INT_BASE + 130)
+#define IPU_IRQ_ADC_PP_TEAR_ERR (MXC_IPU_INT_BASE + 131)
+#define IPU_IRQ_ADC_SYS1_TEAR_ERR (MXC_IPU_INT_BASE + 132)
+#define IPU_IRQ_ADC_SYS2_TEAR_ERR (MXC_IPU_INT_BASE + 133)
+#define IPU_IRQ_SDC_BGD_ERR (MXC_IPU_INT_BASE + 134)
+#define IPU_IRQ_SDC_FGD_ERR (MXC_IPU_INT_BASE + 135)
+#define IPU_IRQ_SDC_MASKD_ERR (MXC_IPU_INT_BASE + 136)
+#define IPU_IRQ_BAYER_FRM_LOST_ERR (MXC_IPU_INT_BASE + 137)
+#define IPU_IRQ_ENC_FRM_LOST_ERR (MXC_IPU_INT_BASE + 138)
+#define IPU_IRQ_VF_FRM_LOST_ERR (MXC_IPU_INT_BASE + 139)
+#define IPU_IRQ_ADC_LOCK_ERR (MXC_IPU_INT_BASE + 140)
+#define IPU_IRQ_DI_LLA_LOCK_ERR (MXC_IPU_INT_BASE + 141)
+#define IPU_IRQ_AHB_M1_ERR (MXC_IPU_INT_BASE + 142)
+#define IPU_IRQ_AHB_M12_ERR (MXC_IPU_INT_BASE + 143)
+
+#define MXC_MAX_IPU_INTS 144
+
#define MXC_MAX_VIRTUAL_INTS 16

-#define NR_IRQS (MXC_MAX_INT_LINES + MXC_MAX_GPIO_LINES + MXC_MAX_VIRTUAL_INTS)
+#define NR_IRQS (MXC_MAX_INT_LINES + MXC_MAX_GPIO_LINES + MXC_MAX_IPU_INTS + \
+ MXC_MAX_VIRTUAL_INTS)

/*!
* Number of GPIO port as defined in the IC Spec
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 8cd3dd9..13ad6e9 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -153,6 +153,22 @@ config MFD_WM8350_I2C
I2C as the control interface. Additional options must be
selected to enable support for the functionality of the chip.

+config MX3_IPU
+ bool "MX3x Image Processing Unit"
+ depends on ARCH_MX3
+ select DMA_ENGINE
+ default y
+ help
+ If you plan to use the Image Processing unit in the i.MX3x, say
+ Y here. If unsure, select Y.
+
+config IPU_DEBUG
+ depends on MX3_IPU
+ bool "IPU debugging"
+ default n
+ help
+ Say y to enable debugging output
+
endmenu

menu "Multimedia Capabilities Port drivers"
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 9a5ad8a..9c7e28d 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -31,4 +31,6 @@ obj-$(CONFIG_MCP_UCB1200) += ucb1x00-assabet.o
endif
obj-$(CONFIG_UCB1400_CORE) += ucb1400_core.o

-obj-$(CONFIG_PMIC_DA903X) += da903x.o
\ No newline at end of file
+obj-$(CONFIG_PMIC_DA903X) += da903x.o
+
+obj-$(CONFIG_MX3_IPU) += ipu/
diff --git a/drivers/mfd/ipu/Makefile b/drivers/mfd/ipu/Makefile
new file mode 100644
index 0000000..9e85672
--- /dev/null
+++ b/drivers/mfd/ipu/Makefile
@@ -0,0 +1,5 @@
+ifeq ($(CONFIG_IPU_DEBUG),y)
+ EXTRA_CFLAGS += -DDEBUG
+endif
+
+obj-y += ipu_irq.o ipu_idmac.o
diff --git a/drivers/mfd/ipu/ipu_idmac.c b/drivers/mfd/ipu/ipu_idmac.c
new file mode 100644
index 0000000..85319f8
--- /dev/null
+++ b/drivers/mfd/ipu/ipu_idmac.c
@@ -0,0 +1,1662 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * Copyright (C) 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/list.h>
+#include <linux/clk.h>
+#include <linux/vmalloc.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+
+#include <mach/ipu.h>
+
+#include <asm/io.h>
+
+#include "ipu_intern.h"
+
+#define FS_VF_IN_VALID 0x00000002
+#define FS_ENC_IN_VALID 0x00000001
+
+/*
+ * There can be only one, we could allocate it dynamically, but then we'd have
+ * to add an extra parameter to some functions, and use something as ugly as
+ * struct ipu *ipu = to_ipu(to_idmac(ichan->dma_chan.device));
+ * in the ISR
+ */
+static struct ipu ipu_data;
+
+#define to_ipu(id) container_of(id, struct ipu, idmac)
+
+static u32 __idmac_read_icreg(struct ipu *ipu, unsigned long reg)
+{
+ return __raw_readl(ipu->reg_ic + reg);
+}
+
+#define idmac_read_icreg(ipu, reg) __idmac_read_icreg(ipu, reg - IC_CONF)
+
+static void __idmac_write_icreg(struct ipu *ipu, u32 value, unsigned long reg)
+{
+ __raw_writel(value, ipu->reg_ic + reg);
+}
+
+#define idmac_write_icreg(ipu, v, reg) __idmac_write_icreg(ipu, v, reg - IC_CONF)
+
+static u32 idmac_read_ipureg(struct ipu *ipu, unsigned long reg)
+{
+ return __raw_readl(ipu->reg_ipu + reg);
+}
+
+static void idmac_write_ipureg(struct ipu *ipu, u32 value, unsigned long reg)
+{
+ __raw_writel(value, ipu->reg_ipu + reg);
+}
+
+/*****************************************************************************
+ * IPU / IC common functions
+ */
+static void dump_idmac_reg(struct ipu *ipu)
+{
+ dev_dbg(ipu->dev, "IDMAC_CONF 0x%x, IC_CONF 0x%x, IDMAC_CHA_EN 0x%x, "
+ "IDMAC_CHA_PRI 0x%x, IDMAC_CHA_BUSY 0x%x\n",
+ idmac_read_icreg(ipu, IDMAC_CONF),
+ idmac_read_icreg(ipu, IC_CONF),
+ idmac_read_icreg(ipu, IDMAC_CHA_EN),
+ idmac_read_icreg(ipu, IDMAC_CHA_PRI),
+ idmac_read_icreg(ipu, IDMAC_CHA_BUSY));
+ dev_dbg(ipu->dev, "BUF0_RDY 0x%x, BUF1_RDY 0x%x, CUR_BUF 0x%x, "
+ "DB_MODE 0x%x, TASKS_STAT 0x%x\n",
+ idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY),
+ idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY),
+ idmac_read_ipureg(ipu, IPU_CHA_CUR_BUF),
+ idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL),
+ idmac_read_ipureg(ipu, IPU_TASKS_STAT));
+}
+
+static uint32_t bytes_per_pixel(enum pixel_fmt fmt)
+{
+ switch (fmt) {
+ case IPU_PIX_FMT_GENERIC: /* generic data */
+ case IPU_PIX_FMT_RGB332:
+ case IPU_PIX_FMT_YUV420P:
+ case IPU_PIX_FMT_YUV422P:
+ default:
+ return 1;
+ case IPU_PIX_FMT_RGB565:
+ case IPU_PIX_FMT_YUYV:
+ case IPU_PIX_FMT_UYVY:
+ return 2;
+ case IPU_PIX_FMT_BGR24:
+ case IPU_PIX_FMT_RGB24:
+ return 3;
+ case IPU_PIX_FMT_GENERIC_32: /* generic data */
+ case IPU_PIX_FMT_BGR32:
+ case IPU_PIX_FMT_RGB32:
+ case IPU_PIX_FMT_ABGR32:
+ return 4;
+ }
+}
+
+/* Enable / disable direct write to memory by the Camera Sensor Interface */
+static void _ipu_ic_enable_task(struct ipu *ipu, enum ipu_channel channel)
+{
+ uint32_t ic_conf, mask;
+
+ switch (channel) {
+ case IDMAC_IC_0:
+ mask = IC_CONF_PRPENC_EN;
+ break;
+ case IDMAC_IC_7:
+ mask = IC_CONF_RWS_EN | IC_CONF_PRPENC_EN;
+ break;
+ default:
+ return;
+ }
+ ic_conf = idmac_read_icreg(ipu, IC_CONF) | mask;
+ idmac_write_icreg(ipu, ic_conf, IC_CONF);
+}
+
+static void _ipu_ic_disable_task(struct ipu *ipu, enum ipu_channel channel)
+{
+ uint32_t ic_conf, mask;
+
+ switch (channel) {
+ case IDMAC_IC_0:
+ mask = IC_CONF_PRPENC_EN;
+ break;
+ case IDMAC_IC_7:
+ mask = IC_CONF_RWS_EN | IC_CONF_PRPENC_EN;
+ break;
+ default:
+ return;
+ }
+ ic_conf = idmac_read_icreg(ipu, IC_CONF) & ~mask;
+ idmac_write_icreg(ipu, ic_conf, IC_CONF);
+}
+
+static uint32_t _ipu_channel_status(struct ipu *ipu, enum ipu_channel channel)
+{
+ uint32_t stat = TASK_STAT_IDLE;
+ uint32_t task_stat_reg = idmac_read_ipureg(ipu, IPU_TASKS_STAT);
+
+ switch (channel) {
+ case IDMAC_IC_7:
+ stat = (task_stat_reg & TSTAT_CSI2MEM_MASK) >>
+ TSTAT_CSI2MEM_OFFSET;
+ break;
+ case IDMAC_IC_0:
+ case IDMAC_SDC_0:
+ case IDMAC_SDC_1:
+ default:
+ break;
+ }
+ return stat;
+}
+
+static void _ipu_ch_param_set_size(uint32_t *params,
+ uint32_t pixel_fmt, uint16_t width,
+ uint16_t height, uint16_t stride)
+{
+ uint32_t u_offset = 0;
+ uint32_t v_offset = 0;
+
+ params[3] = (uint32_t)((width - 1) << 12) |
+ ((uint32_t)(height - 1) << 24);
+ params[4] = (uint32_t)(height - 1) >> 8;
+ params[7] = (uint32_t)(stride - 1) << 3;
+
+ switch (pixel_fmt) {
+ case IPU_PIX_FMT_GENERIC:
+ /*Represents 8-bit Generic data */
+ params[7] |= 3 | (7UL << (81 - 64)) | (31L << (89 - 64)); /* BPP & PFS */
+ params[8] = 2; /* SAT = use 32-bit access */
+ break;
+ case IPU_PIX_FMT_GENERIC_32:
+ /*Represents 32-bit Generic data */
+ params[7] |= (7UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */
+ params[8] = 2; /* SAT = use 32-bit access */
+ break;
+ case IPU_PIX_FMT_RGB565:
+ params[7] |= 2L | (4UL << (81 - 64)) | (7L << (89 - 64)); /* BPP & PFS */
+ params[8] = 2 | /* SAT = 32-bit access */
+ (0UL << (99 - 96)) | /* Red bit offset */
+ (5UL << (104 - 96)) | /* Green bit offset */
+ (11UL << (109 - 96)) | /* Blue bit offset */
+ (16UL << (114 - 96)) | /* Alpha bit offset */
+ (4UL << (119 - 96)) | /* Red bit width - 1 */
+ (5UL << (122 - 96)) | /* Green bit width - 1 */
+ (4UL << (125 - 96)); /* Blue bit width - 1 */
+ break;
+ case IPU_PIX_FMT_BGR24:
+ params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); /* 24 BPP & RGB PFS */
+ params[8] = 2 | /* SAT = 32-bit access */
+ (8UL << (104 - 96)) | /* Green bit offset */
+ (16UL << (109 - 96)) | /* Blue bit offset */
+ (24UL << (114 - 96)) | /* Alpha bit offset */
+ (7UL << (119 - 96)) | /* Red bit width - 1 */
+ (7UL << (122 - 96)) | /* Green bit width - 1 */
+ (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
+ break;
+ case IPU_PIX_FMT_RGB24:
+ params[7] |= 1 | (4UL << (81 - 64)) | (7L << (89 - 64)); /* 24 BPP & RGB PFS */
+ params[8] = 2 | /* SAT = 32-bit access */
+ (16UL << (99 - 96)) | /* Red bit offset */
+ (8UL << (104 - 96)) | /* Green bit offset */
+ (0UL << (109 - 96)) | /* Blue bit offset */
+ (24UL << (114 - 96)) | /* Alpha bit offset */
+ (7UL << (119 - 96)) | /* Red bit width - 1 */
+ (7UL << (122 - 96)) | /* Green bit width - 1 */
+ (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
+ break;
+ case IPU_PIX_FMT_BGRA32:
+ case IPU_PIX_FMT_BGR32:
+ params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64)); /* BPP & PFS */
+ params[8] = 2 | /* SAT = 32-bit access */
+ (8UL << (99 - 96)) | /* Red bit offset */
+ (16UL << (104 - 96)) | /* Green bit offset */
+ (24UL << (109 - 96)) | /* Blue bit offset */
+ (0UL << (114 - 96)) | /* Alpha bit offset */
+ (7UL << (119 - 96)) | /* Red bit width - 1 */
+ (7UL << (122 - 96)) | /* Green bit width - 1 */
+ (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
+ params[9] = 7; /* Alpha bit width - 1 */
+ break;
+ case IPU_PIX_FMT_RGBA32:
+ case IPU_PIX_FMT_RGB32:
+ params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64));
+ params[8] = 2 | /* SAT = 32-bit access */
+ (24UL << (99 - 96)) | /* Red bit offset */
+ (16UL << (104 - 96)) | /* Green bit offset */
+ (8UL << (109 - 96)) | /* Blue bit offset */
+ (0UL << (114 - 96)) | /* Alpha bit offset */
+ (7UL << (119 - 96)) | /* Red bit width - 1 */
+ (7UL << (122 - 96)) | /* Green bit width - 1 */
+ (uint32_t) (7UL << (125 - 96)); /* Blue bit width - 1 */
+ params[9] = 7; /* Alpha bit width - 1 */
+ break;
+ case IPU_PIX_FMT_ABGR32:
+ params[7] |= 0 | (4UL << (81 - 64)) | (7 << (89 - 64));
+ params[8] = 2 | /* SAT = 32-bit access */
+ (0UL << (99 - 96)) | /* Alpha bit offset */
+ (8UL << (104 - 96)) | /* Blue bit offset */
+ (16UL << (109 - 96)) | /* Green bit offset */
+ (24UL << (114 - 96)) | /* Red bit offset */
+ (7UL << (119 - 96)) | /* Alpha bit width - 1 */
+ (7UL << (122 - 96)) | /* Blue bit width - 1 */
+ (uint32_t) (7UL << (125 - 96)); /* Green bit width - 1 */
+ params[9] = 7; /* Red bit width - 1 */
+ break;
+ case IPU_PIX_FMT_UYVY:
+ params[7] |= 2 | (6UL << 17) | (7 << (89 - 64));
+ params[8] = 2; /* SAT = 32-bit access */
+ break;
+ case IPU_PIX_FMT_YUV420P2:
+ case IPU_PIX_FMT_YUV420P:
+ params[7] |= 3 | (3UL << 17) | (7 << (89 - 64));
+ params[8] = 2; /* SAT = 32-bit access */
+ u_offset = stride * height;
+ v_offset = u_offset + u_offset / 4;
+ break;
+ case IPU_PIX_FMT_YVU422P:
+ params[7] |= 3 | (2UL << 17) | (7 << (89 - 64));
+ params[8] = 2; /* SAT = 32-bit access */
+ v_offset = stride * height;
+ u_offset = v_offset + v_offset / 2;
+ break;
+ case IPU_PIX_FMT_YUV422P:
+ params[7] |= 3 | (2UL << 17) | (7 << (89 - 64));
+ params[8] = 2; /* SAT = 32-bit access */
+ u_offset = stride * height;
+ v_offset = u_offset + u_offset / 2;
+ break;
+ default:
+ dev_err(ipu_data.dev, "mxc ipu: unimplemented pixel format\n");
+ break;
+ }
+
+ params[1] = (1UL << (46 - 32)) | (u_offset << (53 - 32));
+ params[2] = u_offset >> (64 - 53);
+ params[2] |= v_offset << (79 - 64);
+ params[3] |= v_offset >> (96 - 79);
+}
+
+static void _ipu_ch_param_set_burst_size(uint32_t *params,
+ uint16_t burst_pixels)
+{
+ params[7] &= ~(0x3FL << (89 - 64));
+ params[7] |= (uint32_t)(burst_pixels - 1) << (89 - 64);
+};
+
+static void _ipu_ch_param_set_buffer(uint32_t *params,
+ dma_addr_t buf0, dma_addr_t buf1)
+{
+ params[5] = buf0;
+ params[6] = buf1;
+};
+
+static void _ipu_ch_param_set_rotation(uint32_t *params,
+ enum ipu_rotate_mode rot)
+{
+ params[7] |= (uint32_t)rot << (84 - 64);
+};
+
+static void _ipu_write_param_mem(uint32_t addr, uint32_t *data,
+ uint32_t numWords)
+{
+ for (; numWords > 0; numWords--) {
+ dev_dbg(ipu_data.dev,
+ "write param mem - addr = 0x%08X, data = 0x%08X\n",
+ addr, *data);
+ idmac_write_ipureg(&ipu_data, addr, IPU_IMA_ADDR);
+ idmac_write_ipureg(&ipu_data, *data++, IPU_IMA_DATA);
+ addr++;
+ if ((addr & 0x7) == 5) {
+ addr &= ~0x7; /* set to word 0 */
+ addr += 8; /* increment to next row */
+ }
+ }
+}
+
+static bool _calc_resize_coeffs(uint32_t in_size, uint32_t out_size,
+ uint32_t *resize_coeff,
+ uint32_t *downsize_coeff)
+{
+ uint32_t temp_size;
+ uint32_t temp_downsize;
+
+ /* Cannot downsize more than 8:1 */
+ if ((out_size << 3) < in_size)
+ return false;
+
+ /* compute downsizing coefficient */
+ temp_downsize = 0;
+ temp_size = in_size;
+ while ((temp_size >= out_size * 2) && (temp_downsize < 2)) {
+ temp_size >>= 1;
+ temp_downsize++;
+ }
+ *downsize_coeff = temp_downsize;
+
+ /*
+ * compute resizing coefficient using the following formula:
+ * resize_coeff = M*(SI -1)/(SO - 1)
+ * where M = 2^13, SI - input size, SO - output size
+ */
+ *resize_coeff = (8192L * (temp_size - 1)) / (out_size - 1);
+ if (*resize_coeff >= 16384L) {
+ dev_err(ipu_data.dev, "Warning! Overflow on resize coeff.\n");
+ *resize_coeff = 0x3FFF;
+ }
+
+ dev_dbg(ipu_data.dev, "resizing from %u -> %u pixels, "
+ "downsize=%u, resize=%u.%lu (reg=%u)\n", in_size, out_size,
+ *downsize_coeff, *resize_coeff >= 8192L ? 1 : 0,
+ ((*resize_coeff & 0x1FFF) * 10000L) / 8192L, *resize_coeff);
+
+ return true;
+}
+
+static enum ipu_color_space format_to_colorspace(enum pixel_fmt fmt)
+{
+ switch (fmt) {
+ case IPU_PIX_FMT_RGB565:
+ case IPU_PIX_FMT_BGR24:
+ case IPU_PIX_FMT_RGB24:
+ case IPU_PIX_FMT_BGR32:
+ case IPU_PIX_FMT_RGB32:
+ return IPU_COLORSPACE_RGB;
+ default:
+ return IPU_COLORSPACE_YCBCR;
+ }
+}
+
+static int _ipu_ic_init_prpenc(struct ipu *ipu,
+ union ipu_channel_param *params, bool src_is_csi)
+{
+ uint32_t reg, ic_conf;
+ uint32_t downsize_coeff, resize_coeff;
+ enum ipu_color_space in_fmt, out_fmt;
+
+ /* Setup vertical resizing */
+ _calc_resize_coeffs(params->video.in_height,
+ params->video.out_height,
+ &resize_coeff, &downsize_coeff);
+ reg = (downsize_coeff << 30) | (resize_coeff << 16);
+
+ /* Setup horizontal resizing */
+ _calc_resize_coeffs(params->video.in_width,
+ params->video.out_width,
+ &resize_coeff, &downsize_coeff);
+ reg |= (downsize_coeff << 14) | resize_coeff;
+
+ /* Setup color space conversion */
+ in_fmt = format_to_colorspace(params->video.in_pixel_fmt);
+ out_fmt = format_to_colorspace(params->video.out_pixel_fmt);
+
+ /*
+ * Colourspace conversion unsupported yet - see _init_csc() in
+ * Freescale sources
+ */
+ if (in_fmt != out_fmt) {
+ dev_err(ipu->dev, "Colourspace conversion unsupported!\n");
+ return -EOPNOTSUPP;
+ }
+
+ idmac_write_icreg(ipu, reg, IC_PRP_ENC_RSC);
+
+ ic_conf = idmac_read_icreg(ipu, IC_CONF);
+
+ if (src_is_csi)
+ ic_conf &= ~IC_CONF_RWS_EN;
+ else
+ ic_conf |= IC_CONF_RWS_EN;
+
+ idmac_write_icreg(ipu, ic_conf, IC_CONF);
+
+ return 0;
+}
+
+static uint32_t dma_param_addr(uint32_t dma_ch)
+{
+ /* Channel Parameter Memory */
+ return 0x10000 | (dma_ch << 4);
+};
+
+static void _ipu_channel_set_priority(struct ipu *ipu, enum ipu_channel channel, bool prio)
+{
+ u32 reg = idmac_read_icreg(ipu, IDMAC_CHA_PRI);
+
+ if (prio)
+ reg |= 1UL << channel;
+ else
+ reg &= ~(1UL << channel);
+
+ idmac_write_icreg(ipu, reg, IDMAC_CHA_PRI);
+
+ dump_idmac_reg(ipu);
+}
+
+static uint32_t _ipu_channel_conf_check(struct ipu *ipu, enum ipu_channel channel)
+{
+ uint32_t ipu_conf;
+
+ ipu_conf = idmac_read_ipureg(ipu, IPU_CONF);
+ if (WARN(!ipu_conf, "Uninitialized IPU_CONF!\n"))
+ /* Should not happen. Error out here. */
+ clk_enable(ipu->ipu_clk);
+
+ if (ipu->channel_init_mask & (1L << channel))
+ dev_err(ipu->dev, "Warning: channel already initialized %d\n",
+ channel);
+
+ return ipu_conf;
+}
+
+static uint32_t _ipu_channel_conf_mask(enum ipu_channel channel)
+{
+ uint32_t mask;
+
+ switch (channel) {
+ case IDMAC_IC_0:
+ mask = IPU_CONF_CSI_EN | IPU_CONF_IC_EN;
+ break;
+ case IDMAC_IC_7:
+ mask = IPU_CONF_CSI_EN | IPU_CONF_IC_EN;
+ break;
+ case IDMAC_SDC_0:
+ mask = IPU_CONF_SDC_EN | IPU_CONF_DI_EN;
+ break;
+ case IDMAC_SDC_1:
+ mask = IPU_CONF_SDC_EN | IPU_CONF_DI_EN;
+ break;
+ default:
+ mask = 0;
+ break;
+ }
+
+ return mask;
+}
+
+/**
+ * Enable an IPU channel.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ *
+ * @return Returns 0 on success or negative error code on failure.
+ */
+static int ipu_enable_channel(struct idmac *idmac, struct idmac_channel *ichan)
+{
+ struct ipu *ipu = to_ipu(idmac);
+ enum ipu_channel channel = ichan->dma_chan.chan_id;
+ uint32_t reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipu->lock, flags);
+
+ /* Reset to buffer 0 */
+ idmac_write_ipureg(ipu, 1UL << channel, IPU_CHA_CUR_BUF);
+ ichan->active_buffer = 0;
+ ichan->status = IPU_CHANNEL_ENABLED;
+
+ switch (channel) {
+ case IDMAC_SDC_0:
+ case IDMAC_SDC_1:
+ case IDMAC_IC_7:
+ _ipu_channel_set_priority(ipu, channel, true);
+ default:
+ break;
+ }
+
+ reg = idmac_read_icreg(ipu, IDMAC_CHA_EN);
+
+ idmac_write_icreg(ipu, reg | (1UL << channel), IDMAC_CHA_EN);
+
+ _ipu_ic_enable_task(ipu, channel);
+
+ spin_unlock_irqrestore(&ipu->lock, flags);
+ return 0;
+}
+
+/**
+ * Initialize a buffer for logical IPU channel.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ *
+ * @param pixel_fmt Input parameter for pixel format of buffer. Pixel
+ * format is a FOURCC ASCII code.
+ *
+ * @param width Input parameter for width of buffer in pixels.
+ *
+ * @param height Input parameter for height of buffer in pixels.
+ *
+ * @param stride Input parameter for stride length of buffer
+ * in pixels.
+ *
+ * @param rot_mode Input parameter for rotation setting of buffer.
+ * A rotation setting other than \b IPU_ROTATE_VERT_FLIP
+ * should only be used for input buffers of rotation
+ * channels.
+ *
+ * @param phyaddr_0 Input parameter buffer 0 physical address.
+ *
+ * @param phyaddr_1 Input parameter buffer 1 physical address.
+ * Setting this to a value other than NULL enables
+ * double buffering mode.
+ *
+ * @return Returns 0 on success or negative error code on failure.
+ */
+static int ipu_init_channel_buffer(struct idmac_channel *ichan,
+ enum pixel_fmt pixel_fmt,
+ uint16_t width, uint16_t height,
+ uint32_t stride,
+ enum ipu_rotate_mode rot_mode,
+ dma_addr_t phyaddr_0, dma_addr_t phyaddr_1)
+{
+ enum ipu_channel channel = ichan->dma_chan.chan_id;
+ struct idmac *idmac = to_idmac(ichan->dma_chan.device);
+ struct ipu *ipu = to_ipu(idmac);
+ uint32_t params[10] = {0};
+ unsigned long flags;
+ uint32_t reg;
+ uint32_t stride_bytes;
+
+ stride_bytes = stride * bytes_per_pixel(pixel_fmt);
+
+ if (stride_bytes % 4) {
+ dev_err(ipu->dev,
+ "Stride length must be 32-bit aligned, stride = %d, bytes = %d\n",
+ stride, stride_bytes);
+ return -EINVAL;
+ }
+ /* IC channel's stride must be a multiple of 8 pixels */
+ if ((channel <= 13) && (stride % 8)) {
+ dev_err(ipu->dev, "Stride must be 8 pixel multiple\n");
+ return -EINVAL;
+ }
+ /* Build parameter memory data for DMA channel */
+ _ipu_ch_param_set_size(params, pixel_fmt, width, height, stride_bytes);
+ _ipu_ch_param_set_buffer(params, phyaddr_0, phyaddr_1);
+ _ipu_ch_param_set_rotation(params, rot_mode);
+ /* Some channels (rotation) have restriction on burst length */
+ switch (channel) {
+ case IDMAC_IC_7: /* Hangs with burst 8, 16, other values
+ invalid - Table 44-30 */
+/*
+ _ipu_ch_param_set_burst_size(params, 8);
+ */
+ break;
+ case IDMAC_SDC_0:
+ case IDMAC_SDC_1:
+ /* In original code only IPU_PIX_FMT_RGB565 was setting burst */
+ _ipu_ch_param_set_burst_size(params, 16);
+ break;
+ case IDMAC_IC_0:
+ default:
+ break;
+ }
+
+ /* In fact, channel is not running yet, so, don't have to protect */
+ spin_lock_irqsave(&ipu->lock, flags);
+
+ _ipu_write_param_mem(dma_param_addr(channel), params, 10);
+
+ reg = idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL);
+
+ if (phyaddr_1)
+ reg |= 1UL << channel;
+ else
+ reg &= ~(1UL << channel);
+
+ idmac_write_ipureg(ipu, reg, IPU_CHA_DB_MODE_SEL);
+
+ ichan->status = IPU_CHANNEL_READY;
+
+ spin_unlock_irqrestore(ipu->lock, flags);
+
+ return 0;
+}
+
+/**
+ * Set a channel's buffer as ready.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ *
+ * @param type Input parameter which buffer to initialize.
+ *
+ * @param bufNum Input parameter for which buffer number set to
+ * ready state.
+ *
+ * @return Returns 0 on success or negative error code on failure.
+ */
+static void ipu_select_buffer(enum ipu_channel channel, int buffer_n)
+{
+ /* No locking - this is a write-one-to-set register, cleared by IPU */
+ if (buffer_n == 0)
+ /* Mark buffer 0 as ready. */
+ idmac_write_ipureg(&ipu_data, 1UL << channel, IPU_CHA_BUF0_RDY);
+ else
+ /* Mark buffer 1 as ready. */
+ idmac_write_ipureg(&ipu_data, 1UL << channel, IPU_CHA_BUF1_RDY);
+}
+
+/**
+ * Update the physical address of a buffer for a logical IPU channel.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ *
+ * @param type Input parameter which buffer to initialize.
+ *
+ * @param bufNum Input parameter for which buffer number to update.
+ * 0 or 1 are the only valid values.
+ *
+ * @param phyaddr Input parameter buffer physical address.
+ *
+ * @return Returns 0 on success or negative error code on failure. This
+ * function will fail if the buffer is set to ready.
+ */
+/* Called under spin_lock(_irqsave)(&ichan->lock) */
+static int ipu_update_channel_buffer(enum ipu_channel channel,
+ int buffer_n, dma_addr_t phyaddr)
+{
+ uint32_t reg;
+ unsigned long flags;
+
+ spin_lock_irqsave(&ipu_data.lock, flags);
+
+ if (buffer_n == 0) {
+ reg = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF0_RDY);
+ if (reg & (1UL << channel)) {
+ spin_unlock_irqrestore(&ipu_data.lock, flags);
+ return -EACCES;
+ }
+
+ /* 44.3.3.1.9 - Row Number 1 (WORD1, offset 0) */
+ idmac_write_ipureg(&ipu_data, dma_param_addr(channel) +
+ 0x0008UL, IPU_IMA_ADDR);
+ idmac_write_ipureg(&ipu_data, phyaddr, IPU_IMA_DATA);
+ } else {
+ reg = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF1_RDY);
+ if (reg & (1UL << channel)) {
+ spin_unlock_irqrestore(&ipu_data.lock, flags);
+ return -EACCES;
+ }
+
+ /* Check if double-buffering is already enabled */
+ reg = idmac_read_ipureg(&ipu_data, IPU_CHA_DB_MODE_SEL);
+
+ if (!(reg & (1UL << channel)))
+ idmac_write_ipureg(&ipu_data, reg | (1UL << channel),
+ IPU_CHA_DB_MODE_SEL);
+
+ /* 44.3.3.1.9 - Row Number 1 (WORD1, offset 1) */
+ idmac_write_ipureg(&ipu_data, dma_param_addr(channel) +
+ 0x0009UL, IPU_IMA_ADDR);
+ idmac_write_ipureg(&ipu_data, phyaddr, IPU_IMA_DATA);
+ }
+
+ spin_unlock_irqrestore(&ipu_data.lock, flags);
+
+ return 0;
+}
+
+/* Called under spin_lock_irqsave(&ichan->lock) */
+static int ipu_submit_channel_buffers(struct idmac_channel *ichan,
+ struct idmac_tx_desc *desc)
+{
+ struct scatterlist *sg;
+ int i, ret;
+
+ for (i = 0, sg = desc->sg; i < 2 && sg; i++) {
+ if (!ichan->sg[i]) {
+ ichan->sg[i] = sg;
+
+ /*
+ * On first invocation this shouldn't be necessary, the
+ * call to ipu_init_channel_buffer() above will set
+ * addresses for us, so we could make it conditional
+ * on status >= IPU_CHANNEL_ENABLED, but doing it again
+ * shouldn't hurt either.
+ */
+ ret = ipu_update_channel_buffer(ichan->dma_chan.chan_id, i,
+ sg_dma_address(sg));
+ if (ret < 0)
+ return ret;
+
+ ipu_select_buffer(ichan->dma_chan.chan_id, i);
+
+ sg = sg_next(sg);
+ }
+ }
+
+ return ret;
+}
+
+static dma_cookie_t idmac_tx_submit(struct dma_async_tx_descriptor *tx)
+{
+ struct idmac_tx_desc *desc = to_tx_desc(tx);
+ struct idmac_channel *ichan = to_idmac_chan(tx->chan);
+ struct idmac *idmac = to_idmac(tx->chan->device);
+ struct ipu *ipu = to_ipu(idmac);
+ dma_cookie_t cookie;
+ unsigned long flags;
+
+ /* Sanity check */
+ if (!list_empty(&desc->list)) {
+ /* The descriptor doesn't belong to client */
+ dev_err(&ichan->dma_chan.dev->device,
+ "Descriptor %p not prepared!\n", tx);
+ return -EBUSY;
+ }
+
+ mutex_lock(&ichan->chan_mutex);
+
+ if (ichan->status < IPU_CHANNEL_READY) {
+ struct idmac_video_param *video = &ichan->params.video;
+ /*
+ * Initial buffer assignment - the first two sg-entries from
+ * the descriptor will end up in the IDMAC buffers
+ */
+ dma_addr_t dma_1 = sg_is_last(desc->sg) ? 0 :
+ sg_dma_address(&desc->sg[1]);
+
+ WARN_ON(ichan->sg[0] || ichan->sg[1]);
+
+ cookie = ipu_init_channel_buffer(ichan,
+ video->out_pixel_fmt,
+ video->out_width,
+ video->out_height,
+ video->out_stride,
+ IPU_ROTATE_NONE,
+ sg_dma_address(&desc->sg[0]),
+ dma_1);
+ if (cookie < 0)
+ goto out;
+ }
+
+ /* ipu->lock can be taken under ichan->lock, but not v.v. */
+ spin_lock_irqsave(&ichan->lock, flags);
+
+ /* submit_buffers() atomically verifies and fills empty sg slots */
+ ipu_submit_channel_buffers(ichan, desc);
+
+ spin_unlock_irqrestore(&ichan->lock, flags);
+
+ cookie = ichan->dma_chan.cookie;
+
+ if (++cookie < 0)
+ cookie = 1;
+
+ /* from dmaengine.h: "last cookie value returned to client" */
+ ichan->dma_chan.cookie = cookie;
+ tx->cookie = cookie;
+ spin_lock_irqsave(&ichan->lock, flags);
+ list_add_tail(&desc->list, &ichan->queue);
+ spin_unlock_irqrestore(&ichan->lock, flags);
+
+ if (ichan->status < IPU_CHANNEL_ENABLED) {
+ int ret = ipu_enable_channel(idmac, ichan);
+ if (ret < 0) {
+ cookie = ret;
+ spin_lock_irqsave(&ichan->lock, flags);
+ list_del_init(&desc->list);
+ spin_unlock_irqrestore(&ichan->lock, flags);
+ tx->cookie = cookie;
+ ichan->dma_chan.cookie = cookie;
+ }
+ }
+
+ dump_idmac_reg(ipu);
+
+out:
+ mutex_unlock(&ichan->chan_mutex);
+
+ return cookie;
+}
+
+/* Called with ichan->chan_mutex held */
+static int idmac_desc_alloc(struct idmac_channel *ichan, int n)
+{
+ struct idmac_tx_desc *desc = vmalloc(n * sizeof(struct idmac_tx_desc));
+ struct idmac *idmac = to_idmac(ichan->dma_chan.device);
+
+ if (!desc)
+ return -ENOMEM;
+
+ /* No interrupts, just disable the tasklet for a moment */
+ tasklet_disable(&to_ipu(idmac)->tasklet);
+
+ ichan->n_tx_desc = n;
+ ichan->desc = desc;
+ INIT_LIST_HEAD(&ichan->queue);
+ INIT_LIST_HEAD(&ichan->free_list);
+
+ while (n--) {
+ struct dma_async_tx_descriptor *txd = &desc->txd;
+
+ memset(txd, 0, sizeof(*txd));
+ dma_async_tx_descriptor_init(txd, &ichan->dma_chan);
+ txd->tx_submit = idmac_tx_submit;
+ txd->chan = &ichan->dma_chan;
+ INIT_LIST_HEAD(&txd->tx_list);
+
+ list_add(&desc->list, &ichan->free_list);
+
+ desc++;
+ }
+
+ tasklet_enable(&to_ipu(idmac)->tasklet);
+
+ return 0;
+}
+
+/**
+ * This function is called to initialize a logical IPU channel.
+ *
+ * @param channel Input parameter for the logical channel ID to initalize.
+ *
+ * @param params Input parameter containing union of channel initialization
+ * parameters.
+ *
+ * @return This function returns 0 on success or negative error code on fail
+ */
+static int ipu_init_channel(struct idmac *idmac, struct idmac_channel *ichan)
+{
+ union ipu_channel_param *params = &ichan->params;
+ uint32_t ipu_conf;
+ enum ipu_channel channel = ichan->dma_chan.chan_id;
+ unsigned long flags;
+ uint32_t reg;
+ struct ipu *ipu = to_ipu(idmac);
+ int ret = 0, n_desc = 0;
+
+ dev_dbg(ipu->dev, "init channel = %d\n", channel);
+
+ if (channel != IDMAC_SDC_0 && channel != IDMAC_SDC_1 &&
+ channel != IDMAC_IC_7)
+ return -EINVAL;
+
+ spin_lock_irqsave(&ipu->lock, flags);
+
+ ipu_conf = _ipu_channel_conf_check(ipu, channel);
+
+ switch (channel) {
+ case IDMAC_IC_7:
+ n_desc = 16;
+ reg = idmac_read_icreg(ipu, IC_CONF);
+ idmac_write_icreg(ipu, reg & ~IC_CONF_CSI_MEM_WR_EN, IC_CONF);
+ break;
+ case IDMAC_IC_0:
+ n_desc = 16;
+ reg = idmac_read_ipureg(ipu, IPU_FS_PROC_FLOW);
+ idmac_write_ipureg(ipu, reg & ~FS_ENC_IN_VALID, IPU_FS_PROC_FLOW);
+ ret = _ipu_ic_init_prpenc(ipu, params, true);
+ break;
+ case IDMAC_SDC_0:
+ case IDMAC_SDC_1:
+ n_desc = 4;
+ default:
+ break;
+ }
+
+ ipu->channel_init_mask |= 1L << channel;
+
+ /* Enable IPU sub module */
+ ipu_conf |= _ipu_channel_conf_mask(channel);
+ idmac_write_ipureg(ipu, ipu_conf, IPU_CONF);
+
+ spin_unlock_irqrestore(&ipu->lock, flags);
+
+ if (n_desc && !ichan->desc)
+ ret = idmac_desc_alloc(ichan, n_desc);
+
+ dump_idmac_reg(ipu);
+
+ return ret;
+}
+
+/**
+ * This function is called to uninitialize a logical IPU channel.
+ *
+ * @param channel Input parameter for the logical channel ID to uninitalize.
+ */
+static void ipu_uninit_channel(struct idmac *idmac, struct idmac_channel *ichan)
+{
+ enum ipu_channel channel = ichan->dma_chan.chan_id;
+ unsigned long flags;
+ uint32_t reg;
+ uint32_t mask = 0;
+ uint32_t ipu_conf;
+ struct ipu *ipu = to_ipu(idmac);
+
+ spin_lock_irqsave(&ipu->lock, flags);
+
+ if (!(ipu->channel_init_mask & (1L << channel))) {
+ dev_err(ipu->dev, "Channel already uninitialized %d\n",
+ channel);
+ spin_unlock_irqrestore(&ipu->lock, flags);
+ return;
+ }
+
+ /* Make sure channel is disabled */
+ if (mask & idmac_read_icreg(ipu, IDMAC_CHA_EN)) {
+ dev_err(ipu->dev,
+ "Channel %d is not disabled, disable first\n", channel);
+ spin_unlock_irqrestore(&ipu->lock, flags);
+ return;
+ }
+
+ /* Reset the double buffer */
+ reg = idmac_read_ipureg(ipu, IPU_CHA_DB_MODE_SEL);
+ idmac_write_ipureg(ipu, reg & ~mask, IPU_CHA_DB_MODE_SEL);
+
+ ichan->sec_chan_en = false;
+
+ switch (channel) {
+ case IDMAC_IC_7:
+ reg = idmac_read_icreg(ipu, IC_CONF);
+ idmac_write_icreg(ipu, reg & ~(IC_CONF_RWS_EN | IC_CONF_PRPENC_EN),
+ IC_CONF);
+ break;
+ case IDMAC_IC_0:
+ reg = idmac_read_icreg(ipu, IC_CONF);
+ idmac_write_icreg(ipu, reg & ~(IC_CONF_PRPENC_EN | IC_CONF_PRPENC_CSC1),
+ IC_CONF);
+ break;
+ case IDMAC_SDC_0:
+ case IDMAC_SDC_1:
+ default:
+ break;
+ }
+
+ ipu->channel_init_mask &= ~(1L << channel);
+
+ ipu_conf = idmac_read_ipureg(ipu, IPU_CONF);
+ ipu_conf &= ~_ipu_channel_conf_mask(channel);
+ idmac_write_ipureg(ipu, ipu_conf, IPU_CONF);
+ if (!ipu_conf)
+ clk_disable(ipu->ipu_clk);
+
+ spin_unlock_irqrestore(&ipu->lock, flags);
+
+ ichan->n_tx_desc = 0;
+ vfree(ichan->desc);
+ ichan->desc = NULL;
+}
+
+/**
+ * This function disables a logical channel.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ *
+ * @param wait_for_stop Flag to set whether to wait for channel end
+ * of frame or return immediately.
+ *
+ * @return This function returns 0 on success or negative error code on
+ * fail.
+ */
+static int ipu_disable_channel(struct idmac *idmac, enum ipu_channel channel,
+ bool wait_for_stop)
+{
+ struct ipu *ipu = to_ipu(idmac);
+ uint32_t reg;
+ unsigned long flags;
+ uint32_t chan_mask = 1UL << channel;
+ uint32_t timeout;
+ uint32_t eof_intr;
+
+ if (wait_for_stop && channel != IDMAC_SDC_1 && channel != IDMAC_SDC_0) {
+ timeout = 40;
+ /* This waiting always fails. Related to spurious irq problem */
+ while ((idmac_read_icreg(ipu, IDMAC_CHA_BUSY) & chan_mask) ||
+ (_ipu_channel_status(ipu, channel) == TASK_STAT_ACTIVE)) {
+ timeout--;
+ msleep(10);
+
+ if (!timeout) {
+ dev_dbg(ipu->dev,
+ "Warning: timeout waiting for channel %u to "
+ "stop: buf0_rdy = 0x%08X, buf1_rdy = 0x%08X, "
+ "busy = 0x%08X, tstat = 0x%08X\n", channel,
+ idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY),
+ idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY),
+ idmac_read_icreg(ipu, IDMAC_CHA_BUSY),
+ idmac_read_ipureg(ipu, IPU_TASKS_STAT));
+ break;
+ }
+ }
+ dev_dbg(ipu->dev, "timeout = %d * 10ms\n", 40 - timeout);
+ }
+ /* SDC BG and FG must be disabled before DMA is disabled */
+ if (wait_for_stop && (channel == IDMAC_SDC_0 ||
+ channel == IDMAC_SDC_1)) {
+ if (channel == IDMAC_SDC_0)
+ eof_intr = IPU_IRQ_SDC_BG_EOF;
+ else
+ eof_intr = IPU_IRQ_SDC_FG_EOF;
+
+ for (timeout = 5;
+ timeout && !ipu_irq_status(eof_intr); timeout--)
+ msleep(5);
+ }
+
+ spin_lock_irqsave(&ipu->lock, flags);
+
+ /* Disable IC task */
+ _ipu_ic_disable_task(ipu, channel);
+
+ /* Disable DMA channel(s) */
+ reg = idmac_read_icreg(ipu, IDMAC_CHA_EN);
+ idmac_write_icreg(ipu, reg & ~chan_mask, IDMAC_CHA_EN);
+
+ /*
+ * Problem (observed with channel DMAIC_7): after enabling the channel
+ * and initialising buffers, there comes an interrupt with current still
+ * pointing at buffer 0, whereas it should use buffer 0 first and only
+ * generate an interrupt when it is done, then current should already
+ * point to buffer 1. This spurious interrupt also comes on channel
+ * DMASDC_0. With DMAIC_7 normally, is we just leave the ISR after the
+ * first interrupt, there comes the second with current correctly
+ * pointing to buffer 1 this time. But sometimes this second interrupt
+ * doesn't come and the channel hangs. Clearing BUFx_RDY when disabling
+ * the channel seems to prevent the channel from hanging, but it doesn't
+ * prevent the spurious interrupt. This might also be unsafe. Think
+ * about the IDMAC controller trying to switch to a buffer, when we
+ * clear the ready bit, and re-enable it a moment later.
+ */
+ reg = idmac_read_ipureg(ipu, IPU_CHA_BUF0_RDY);
+ idmac_write_ipureg(ipu, 0, IPU_CHA_BUF0_RDY);
+ idmac_write_ipureg(ipu, reg & ~(1UL << channel), IPU_CHA_BUF0_RDY);
+
+ reg = idmac_read_ipureg(ipu, IPU_CHA_BUF1_RDY);
+ idmac_write_ipureg(ipu, 0, IPU_CHA_BUF1_RDY);
+ idmac_write_ipureg(ipu, reg & ~(1UL << channel), IPU_CHA_BUF1_RDY);
+
+ spin_unlock_irqrestore(&ipu->lock, flags);
+
+ return 0;
+}
+
+/*
+ * We have several possibilities here:
+ * current BUF next BUF
+ *
+ * not last sg next not last sg
+ * not last sg next last sg
+ * last sg first sg from next descriptor
+ * last sg NULL
+ *
+ * Besides, the descriptor queue might be empty or not. We process all these
+ * cases carefully.
+ */
+static irqreturn_t idmac_interrupt(int irq, void *dev_id)
+{
+ struct idmac_channel *ichan = dev_id;
+ unsigned int chan_id = ichan->dma_chan.chan_id;
+ struct scatterlist **sg, *sgnext, *sgnew = NULL;
+ /* Next transfer descriptor */
+ struct idmac_tx_desc *desc = NULL, *descnew;
+ dma_async_tx_callback callback;
+ void *callback_param;
+ bool done = false;
+ u32 ready0 = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF0_RDY),
+ ready1 = idmac_read_ipureg(&ipu_data, IPU_CHA_BUF1_RDY),
+ curbuf = idmac_read_ipureg(&ipu_data, IPU_CHA_CUR_BUF);
+
+ /* IDMAC has cleared the respective BUFx_RDY bit, we manage the buffer */
+
+ pr_debug("IDMAC irq %d\n", irq);
+ /* Other interrupts do not interfere with this channel */
+ spin_lock(&ichan->lock);
+
+ if (unlikely(chan_id != IDMAC_SDC_0 && chan_id != IDMAC_SDC_1 &&
+ ((curbuf >> chan_id) & 1) == ichan->active_buffer)) {
+ int i = 100;
+
+ /* This doesn't help. See comment in ipu_disable_channel() */
+ while (--i) {
+ curbuf = idmac_read_ipureg(&ipu_data, IPU_CHA_CUR_BUF);
+ if (((curbuf >> chan_id) & 1) != ichan->active_buffer)
+ break;
+ cpu_relax();
+ }
+
+ if (!i) {
+ spin_unlock(&ichan->lock);
+ dev_err(ichan->dma_chan.device->dev,
+ "IRQ on active buffer on channel %x, active "
+ "%d, ready %x, %x, current %x!\n", chan_id,
+ ichan->active_buffer, ready0, ready1, curbuf);
+ return IRQ_NONE;
+ }
+ }
+
+ if (unlikely((ichan->active_buffer && (ready1 >> chan_id) & 1) ||
+ (!ichan->active_buffer && (ready0 >> chan_id) & 1)
+ )) {
+ spin_unlock(&ichan->lock);
+ dev_dbg(ichan->dma_chan.device->dev,
+ "IRQ with active buffer still ready on channel %x, "
+ "active %d, ready %x, %x!\n", chan_id,
+ ichan->active_buffer, ready0, ready1);
+ return IRQ_NONE;
+ }
+
+ if (unlikely(list_empty(&ichan->queue))) {
+ spin_unlock(&ichan->lock);
+ dev_err(ichan->dma_chan.device->dev,
+ "IRQ without queued buffers on channel %x, active %d, "
+ "ready %x, %x!\n", chan_id,
+ ichan->active_buffer, ready0, ready1);
+ return IRQ_NONE;
+ }
+
+ /*
+ * active_buffer is a software flag, it shows which buffer we are
+ * currently expecting back from the hardware, IDMAC should be
+ * processing the other buffer already
+ */
+ sg = &ichan->sg[ichan->active_buffer];
+ sgnext = ichan->sg[!ichan->active_buffer];
+
+ /*
+ * if sgnext == NULL sg must be the last element in a scatterlist and
+ * queue must be empty
+ */
+ if (unlikely(!sgnext)) {
+ if (unlikely(sg_next(*sg))) {
+ dev_err(ichan->dma_chan.device->dev,
+ "Broken buffer-update locking on channel %x!\n",
+ chan_id);
+ /* We'll let the user catch up */
+ } else {
+ /* Underrun */
+ _ipu_ic_disable_task(&ipu_data, chan_id);
+ dev_dbg(ichan->dma_chan.device->dev,
+ "Underrun on channel %x\n", chan_id);
+ ichan->status = IPU_CHANNEL_READY;
+ /* Continue to check for complete descriptor */
+ }
+ }
+
+ desc = list_entry(ichan->queue.next, struct idmac_tx_desc, list);
+
+ /* First calculate and submit the next sg element */
+ if (likely(sgnext))
+ sgnew = sg_next(sgnext);
+
+ if (unlikely(!sgnew)) {
+ /* Start a new scatterlist, if any queued */
+ if (likely(desc->list.next != &ichan->queue)) {
+ descnew = list_entry(desc->list.next,
+ struct idmac_tx_desc, list);
+ sgnew = &descnew->sg[0];
+ }
+ }
+
+ if (unlikely(!sg_next(*sg)) || !sgnext) {
+ /*
+ * Last element in scatterlist done, remove from the queue,
+ * _init for debugging
+ */
+ list_del_init(&desc->list);
+ done = true;
+ }
+
+ *sg = sgnew;
+
+ if (likely(sgnew)) {
+ int ret;
+
+ ret = ipu_update_channel_buffer(chan_id, ichan->active_buffer,
+ sg_dma_address(*sg));
+ if (ret < 0)
+ dev_err(ichan->dma_chan.device->dev,
+ "Failed to update buffer on channel %x buffer %d!\n",
+ chan_id, ichan->active_buffer);
+ else
+ ipu_select_buffer(chan_id, ichan->active_buffer);
+ }
+
+ /* Flip the active buffer - even if update above failed */
+ ichan->active_buffer = !ichan->active_buffer;
+ if (done)
+ ichan->completed = desc->txd.cookie;
+
+ callback = desc->txd.callback;
+ callback_param = desc->txd.callback_param;
+
+ spin_unlock(&ichan->lock);
+
+ if (done && (desc->txd.flags & DMA_PREP_INTERRUPT) && callback)
+ callback(callback_param);
+
+ return IRQ_HANDLED;
+}
+
+static void ipu_gc_tasklet(unsigned long arg)
+{
+ struct ipu *ipu = (struct ipu *)arg;
+ int i;
+
+ for (i = 0; i < IPU_CHANNELS_NUM; i++) {
+ struct idmac_channel *ichan = ipu->channel + i;
+ struct idmac_tx_desc *desc;
+ unsigned long flags;
+ int j;
+
+ for (j = 0; j < ichan->n_tx_desc; j++) {
+ desc = ichan->desc + j;
+ spin_lock_irqsave(&ichan->lock, flags);
+ if (async_tx_test_ack(&desc->txd)) {
+ list_move(&desc->list, &ichan->free_list);
+ async_tx_clear_ack(&desc->txd);
+ }
+ spin_unlock_irqrestore(&ichan->lock, flags);
+ }
+ }
+}
+
+/*
+ * At the time .device_alloc_chan_resources() method is called, we cannot know,
+ * whether the client will accept the channel. Thus we must only check, if we
+ * can satisfy client's request but the only real criterion to verify, whether
+ * the client has accepted our offer is the client_count. That's why we have to
+ * perform the rest of our allocation tasks on the first call to this function.
+ */
+static struct dma_async_tx_descriptor *idmac_prep_slave_sg(struct dma_chan *chan,
+ struct scatterlist *sgl, unsigned int sg_len,
+ enum dma_data_direction direction, unsigned long tx_flags)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+ struct idmac_tx_desc *desc = NULL;
+ struct dma_async_tx_descriptor *txd = NULL;
+ unsigned long flags;
+
+ /* We only can handle these three channels so far */
+ if (ichan->dma_chan.chan_id != IDMAC_SDC_0 && ichan->dma_chan.chan_id != IDMAC_SDC_1 &&
+ ichan->dma_chan.chan_id != IDMAC_IC_7)
+ return NULL;
+
+ if (direction != DMA_FROM_DEVICE && direction != DMA_TO_DEVICE) {
+ dev_err(chan->device->dev, "Invalid DMA direction %d!\n", direction);
+ return NULL;
+ }
+
+ mutex_lock(&ichan->chan_mutex);
+
+ spin_lock_irqsave(&ichan->lock, flags);
+ if (!list_empty(&ichan->free_list)) {
+ desc = list_entry(ichan->free_list.next,
+ struct idmac_tx_desc, list);
+
+ list_del_init(&desc->list);
+
+ desc->sg_len = sg_len;
+ desc->sg = sgl;
+ txd = &desc->txd;
+ txd->flags = tx_flags;
+ }
+ spin_unlock_irqrestore(&ichan->lock, flags);
+
+ mutex_unlock(&ichan->chan_mutex);
+
+ tasklet_schedule(&to_ipu(to_idmac(chan->device))->tasklet);
+
+ return txd;
+}
+
+/* Re-select the current buffer and re-activate the channel */
+static void idmac_issue_pending(struct dma_chan *chan)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+ struct idmac *idmac = to_idmac(chan->device);
+ struct ipu *ipu = to_ipu(idmac);
+ unsigned long flags;
+
+ /* This is not always needed, but doesn't hurt either */
+ spin_lock_irqsave(&ipu->lock, flags);
+ ipu_select_buffer(ichan->dma_chan.chan_id, ichan->active_buffer);
+ spin_unlock_irqrestore(&ipu->lock, flags);
+
+ /*
+ * Might need to perform some parts of initialisation from
+ * ipu_enable_channel(), but not all, we do not want to reset to buffer
+ * 0, don't need to set priority again either, but re-enabling the task
+ * and the channel might be a good idea.
+ */
+}
+
+static void __idmac_terminate_all(struct dma_chan *chan)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+ struct idmac *idmac = to_idmac(chan->device);
+ unsigned long flags;
+ int i;
+
+ ipu_disable_channel(idmac, chan->chan_id,
+ ichan->status >= IPU_CHANNEL_ENABLED);
+
+ /* ichan->queue is modified in ISR, have to spinlock */
+ tasklet_disable(&to_ipu(idmac)->tasklet);
+
+ spin_lock_irqsave(&ichan->lock, flags);
+ list_splice_init(&ichan->queue, &ichan->free_list);
+
+ if (ichan->desc)
+ for (i = 0; i < ichan->n_tx_desc; i++) {
+ struct idmac_tx_desc *desc = ichan->desc + i;
+ if (list_empty(&desc->list))
+ /* Descriptor was prepared, but not submitted */
+ list_add(&desc->list,
+ &ichan->free_list);
+
+ async_tx_clear_ack(&desc->txd);
+ }
+
+ ichan->sg[0] = NULL;
+ ichan->sg[1] = NULL;
+ spin_unlock_irqrestore(&ichan->lock, flags);
+
+ tasklet_enable(&to_ipu(idmac)->tasklet);
+
+ ichan->status = IPU_CHANNEL_INITIALIZED;
+}
+
+static void idmac_terminate_all(struct dma_chan *chan)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+
+ mutex_lock(&ichan->chan_mutex);
+
+ __idmac_terminate_all(chan);
+
+ mutex_unlock(&ichan->chan_mutex);
+}
+
+static int idmac_alloc_chan_resources(struct dma_chan *chan)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+ struct idmac *idmac = to_idmac(chan->device);
+ int ret;
+
+ /* dmaengine.c now guarantees to only offer free channels */
+ BUG_ON(chan->client_count > 1);
+ WARN_ON(ichan->status != IPU_CHANNEL_FREE);
+
+ /* The channel is free yet, no need to protect? */
+ mutex_lock(&ichan->chan_mutex);
+
+ chan->cookie = 1;
+ ichan->completed = -ENXIO;
+
+ ret = request_irq(ichan->eof_irq, idmac_interrupt, 0,
+ "idmac", ichan);
+ if (ret < 0)
+ goto out;
+
+ ret = ipu_init_channel(idmac, ichan);
+ if (ret < 0) {
+ free_irq(ichan->eof_irq, ichan);
+ goto out;
+ }
+
+ ichan->status = IPU_CHANNEL_INITIALIZED;
+
+out:
+ mutex_unlock(&ichan->chan_mutex);
+
+ dev_dbg(&ichan->dma_chan.dev->device, "Found channel 0x%x, irq %d\n",
+ ichan->dma_chan.chan_id, ichan->eof_irq);
+
+ return 0;
+}
+
+static void idmac_free_chan_resources(struct dma_chan *chan)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+ struct idmac *idmac = to_idmac(chan->device);
+
+ mutex_lock(&ichan->chan_mutex);
+
+ __idmac_terminate_all(chan);
+
+ if (ichan->status > IPU_CHANNEL_FREE)
+ free_irq(ichan->eof_irq, ichan);
+
+ ichan->status = IPU_CHANNEL_FREE;
+
+ /*
+ * The channel should be uninitialized by now already, but the original
+ * Freescale driver did it again, and it shouldn't hurt
+ */
+ ipu_uninit_channel(idmac, ichan);
+
+ mutex_unlock(&ichan->chan_mutex);
+
+ tasklet_schedule(&to_ipu(idmac)->tasklet);
+}
+
+static enum dma_status idmac_is_tx_complete(struct dma_chan *chan,
+ dma_cookie_t cookie, dma_cookie_t *done, dma_cookie_t *used)
+{
+ struct idmac_channel *ichan = to_idmac_chan(chan);
+
+ if (done)
+ *done = ichan->completed;
+ if (used)
+ *used = chan->cookie;
+ if (cookie != chan->cookie)
+ return DMA_ERROR;
+ return DMA_SUCCESS;
+}
+
+static int __init ipu_idmac_init(struct ipu *ipu)
+{
+ struct idmac *idmac = &ipu->idmac;
+ struct dma_device *dma = &idmac->dma;
+ int i;
+
+ dma_cap_set(DMA_SLAVE, dma->cap_mask);
+ dma_cap_set(DMA_PRIVATE, dma->cap_mask);
+
+ /* Compulsory common fields */
+ dma->dev = ipu->dev;
+ dma->device_alloc_chan_resources = idmac_alloc_chan_resources;
+ dma->device_free_chan_resources = idmac_free_chan_resources;
+ dma->device_is_tx_complete = idmac_is_tx_complete;
+ dma->device_issue_pending = idmac_issue_pending;
+
+ /* Compulsory for DMA_SLAVE fields */
+ dma->device_prep_slave_sg = idmac_prep_slave_sg;
+ dma->device_terminate_all = idmac_terminate_all;
+
+ INIT_LIST_HEAD(&dma->channels);
+ for (i = 0; i < IPU_CHANNELS_NUM; i++) {
+ struct idmac_channel *ichan = ipu->channel + i;
+ struct dma_chan *dma_chan = &ichan->dma_chan;
+
+ spin_lock_init(&ichan->lock);
+ mutex_init(&ichan->chan_mutex);
+
+ ichan->eof_irq = MXC_IPU_INT_BASE + i;
+ ichan->status = IPU_CHANNEL_FREE;
+ ichan->sec_chan_en = false;
+ ichan->completed = -ENXIO;
+
+ dma_chan->device = &idmac->dma;
+ dma_chan->cookie = 1;
+ dma_chan->chan_id = i;
+ list_add_tail(&ichan->dma_chan.device_node, &dma->channels);
+ }
+
+ idmac_write_icreg(ipu, 0x00000070, IDMAC_CONF);
+
+ return dma_async_device_register(&idmac->dma);
+}
+
+static void ipu_idmac_exit(struct ipu *ipu)
+{
+ int i;
+ struct idmac *idmac = &ipu->idmac;
+
+ for (i = 0; i < IPU_CHANNELS_NUM; i++) {
+ struct idmac_channel *ichan = ipu->channel + i;
+
+ idmac_terminate_all(&ichan->dma_chan);
+ idmac_prep_slave_sg(&ichan->dma_chan, NULL, 0, DMA_NONE, 0);
+ }
+
+ dma_async_device_unregister(&idmac->dma);
+}
+
+/*****************************************************************************
+ * IPU common probe / remove
+ */
+
+static int ipu_probe(struct platform_device *pdev)
+{
+ struct ipu_platform_data *pdata = pdev->dev.platform_data;
+ struct resource *mem_ipu, *mem_ic;
+ int ret;
+
+ spin_lock_init(&ipu_data.lock);
+
+ mem_ipu = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mem_ic = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ if (!pdata || !mem_ipu || !mem_ic)
+ return -EINVAL;
+
+ ipu_data.dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, &ipu_data);
+
+ ret = platform_get_irq(pdev, 0);
+ if (ret < 0)
+ goto err_noirq;
+
+ ipu_data.irq_fn = ret;
+ ret = platform_get_irq(pdev, 1);
+ if (ret < 0)
+ goto err_noirq;
+
+ ipu_data.irq_err = ret;
+ ipu_data.irq_base = pdata->irq_base;
+
+ dev_dbg(&pdev->dev, "fn irq %u, err irq %u, irq-base %u\n",
+ ipu_data.irq_fn, ipu_data.irq_err, ipu_data.irq_base);
+
+ /* Remap IPU common registers */
+ ipu_data.reg_ipu = ioremap(mem_ipu->start,
+ mem_ipu->end - mem_ipu->start + 1);
+ if (!ipu_data.reg_ipu) {
+ ret = -ENOMEM;
+ goto err_ioremap_ipu;
+ }
+
+ /* Remap Image Converter and Image DMA Controller registers */
+ ipu_data.reg_ic = ioremap(mem_ic->start,
+ mem_ic->end - mem_ic->start + 1);
+ if (!ipu_data.reg_ic) {
+ ret = -ENOMEM;
+ goto err_ioremap_ic;
+ }
+
+ /* Get IPU clock */
+ ipu_data.ipu_clk = clk_get(&pdev->dev, "ipu_clk");
+ if (IS_ERR(ipu_data.ipu_clk)) {
+ ret = PTR_ERR(ipu_data.ipu_clk);
+ goto err_clk_get;
+ }
+
+ /* Make sure IPU HSP clock is running */
+ clk_enable(ipu_data.ipu_clk);
+
+ /* Disable all interrupts */
+ idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_1);
+ idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_2);
+ idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_3);
+ idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_4);
+ idmac_write_ipureg(&ipu_data, 0, IPU_INT_CTRL_5);
+
+ dev_dbg(&pdev->dev, "%s @ 0x%08lx, fn irq %u, err irq %u\n", pdev->name,
+ (unsigned long)mem_ipu->start, ipu_data.irq_fn, ipu_data.irq_err);
+
+ ret = ipu_irq_attach_irq(&ipu_data, pdev);
+ if (ret < 0)
+ goto err_attach_irq;
+
+ /* Initialize DMA engine */
+ ret = ipu_idmac_init(&ipu_data);
+ if (ret < 0)
+ goto err_idmac_init;
+
+ tasklet_init(&ipu_data.tasklet, ipu_gc_tasklet, (unsigned long)&ipu_data);
+
+ ipu_data.dev = &pdev->dev;
+
+ dev_dbg(ipu_data.dev, "IPU initialized\n");
+
+ return 0;
+
+err_idmac_init:
+err_attach_irq:
+ ipu_irq_detach_irq(&ipu_data, pdev);
+ clk_put(ipu_data.ipu_clk);
+err_clk_get:
+ iounmap(ipu_data.reg_ic);
+err_ioremap_ic:
+ iounmap(ipu_data.reg_ipu);
+err_ioremap_ipu:
+err_noirq:
+ dev_err(&pdev->dev, "Failed to probe IPU: %d\n", ret);
+ return ret;
+}
+
+static int ipu_remove(struct platform_device *pdev)
+{
+ struct ipu *ipu = platform_get_drvdata(pdev);
+
+ ipu_idmac_exit(ipu);
+ ipu_irq_detach_irq(ipu, pdev);
+ clk_put(ipu->ipu_clk);
+ iounmap(ipu->reg_ic);
+ iounmap(ipu->reg_ipu);
+ tasklet_kill(&ipu->tasklet);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+/*
+ * We need two MEM resources - with IPU-common and Image Converter registers,
+ * including PF_CONF and IDMAC_* registers, and two IRQs - function and error
+ */
+static struct platform_driver ipu_platform_driver = {
+ .driver = {
+ .name = "ipu-core",
+ .owner = THIS_MODULE,
+ },
+ .remove = ipu_remove,
+};
+
+static int __init ipu_init(void)
+{
+ return platform_driver_probe(&ipu_platform_driver, ipu_probe);
+}
+subsys_initcall(ipu_init);
+
+MODULE_DESCRIPTION("IPU core driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Guennadi Liakhovetski <[email protected]>");
+MODULE_ALIAS("platform:ipu-core");
diff --git a/drivers/mfd/ipu/ipu_intern.h b/drivers/mfd/ipu/ipu_intern.h
new file mode 100644
index 0000000..740e7ee
--- /dev/null
+++ b/drivers/mfd/ipu/ipu_intern.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * Copyright (C) 2005-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _IPU_INTERN_H_
+#define _IPU_INTERN_H_
+
+#include <linux/dmaengine.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+/* IPU Common registers */
+#define IPU_CONF 0x00
+#define IPU_CHA_BUF0_RDY 0x04
+#define IPU_CHA_BUF1_RDY 0x08
+#define IPU_CHA_DB_MODE_SEL 0x0C
+#define IPU_CHA_CUR_BUF 0x10
+#define IPU_FS_PROC_FLOW 0x14
+#define IPU_FS_DISP_FLOW 0x18
+#define IPU_TASKS_STAT 0x1C
+#define IPU_IMA_ADDR 0x20
+#define IPU_IMA_DATA 0x24
+#define IPU_INT_CTRL_1 0x28
+#define IPU_INT_CTRL_2 0x2C
+#define IPU_INT_CTRL_3 0x30
+#define IPU_INT_CTRL_4 0x34
+#define IPU_INT_CTRL_5 0x38
+#define IPU_INT_STAT_1 0x3C
+#define IPU_INT_STAT_2 0x40
+#define IPU_INT_STAT_3 0x44
+#define IPU_INT_STAT_4 0x48
+#define IPU_INT_STAT_5 0x4C
+#define IPU_BRK_CTRL_1 0x50
+#define IPU_BRK_CTRL_2 0x54
+#define IPU_BRK_STAT 0x58
+#define IPU_DIAGB_CTRL 0x5C
+
+/* IPU_CONF Register bits */
+#define IPU_CONF_CSI_EN 0x00000001
+#define IPU_CONF_IC_EN 0x00000002
+#define IPU_CONF_ROT_EN 0x00000004
+#define IPU_CONF_PF_EN 0x00000008
+#define IPU_CONF_SDC_EN 0x00000010
+#define IPU_CONF_ADC_EN 0x00000020
+#define IPU_CONF_DI_EN 0x00000040
+#define IPU_CONF_DU_EN 0x00000080
+#define IPU_CONF_PXL_ENDIAN 0x00000100
+
+/* Image Converter Registers */
+#define IC_CONF 0x88
+#define IC_PRP_ENC_RSC 0x8C
+#define IC_PRP_VF_RSC 0x90
+#define IC_PP_RSC 0x94
+#define IC_CMBP_1 0x98
+#define IC_CMBP_2 0x9C
+#define PF_CONF 0xA0
+#define IDMAC_CONF 0xA4
+#define IDMAC_CHA_EN 0xA8
+#define IDMAC_CHA_PRI 0xAC
+#define IDMAC_CHA_BUSY 0xB0
+
+/* Image Converter Register bits */
+#define IC_CONF_PRPENC_EN 0x00000001
+#define IC_CONF_PRPENC_CSC1 0x00000002
+#define IC_CONF_PRPENC_ROT_EN 0x00000004
+#define IC_CONF_PRPVF_EN 0x00000100
+#define IC_CONF_PRPVF_CSC1 0x00000200
+#define IC_CONF_PRPVF_CSC2 0x00000400
+#define IC_CONF_PRPVF_CMB 0x00000800
+#define IC_CONF_PRPVF_ROT_EN 0x00001000
+#define IC_CONF_PP_EN 0x00010000
+#define IC_CONF_PP_CSC1 0x00020000
+#define IC_CONF_PP_CSC2 0x00040000
+#define IC_CONF_PP_CMB 0x00080000
+#define IC_CONF_PP_ROT_EN 0x00100000
+#define IC_CONF_IC_GLB_LOC_A 0x10000000
+#define IC_CONF_KEY_COLOR_EN 0x20000000
+#define IC_CONF_RWS_EN 0x40000000
+#define IC_CONF_CSI_MEM_WR_EN 0x80000000
+
+#define IDMA_CHAN_INVALID 0x000000FF
+#define IDMA_IC_0 0x00000001
+#define IDMA_IC_1 0x00000002
+#define IDMA_IC_2 0x00000004
+#define IDMA_IC_3 0x00000008
+#define IDMA_IC_4 0x00000010
+#define IDMA_IC_5 0x00000020
+#define IDMA_IC_6 0x00000040
+#define IDMA_IC_7 0x00000080
+#define IDMA_IC_8 0x00000100
+#define IDMA_IC_9 0x00000200
+#define IDMA_IC_10 0x00000400
+#define IDMA_IC_11 0x00000800
+#define IDMA_IC_12 0x00001000
+#define IDMA_IC_13 0x00002000
+#define IDMA_SDC_BG 0x00004000
+#define IDMA_SDC_FG 0x00008000
+#define IDMA_SDC_MASK 0x00010000
+#define IDMA_SDC_PARTIAL 0x00020000
+#define IDMA_ADC_SYS1_WR 0x00040000
+#define IDMA_ADC_SYS2_WR 0x00080000
+#define IDMA_ADC_SYS1_CMD 0x00100000
+#define IDMA_ADC_SYS2_CMD 0x00200000
+#define IDMA_ADC_SYS1_RD 0x00400000
+#define IDMA_ADC_SYS2_RD 0x00800000
+#define IDMA_PF_QP 0x01000000
+#define IDMA_PF_BSP 0x02000000
+#define IDMA_PF_Y_IN 0x04000000
+#define IDMA_PF_U_IN 0x08000000
+#define IDMA_PF_V_IN 0x10000000
+#define IDMA_PF_Y_OUT 0x20000000
+#define IDMA_PF_U_OUT 0x40000000
+#define IDMA_PF_V_OUT 0x80000000
+
+#define TSTAT_PF_H264_PAUSE 0x00000001
+#define TSTAT_CSI2MEM_MASK 0x0000000C
+#define TSTAT_CSI2MEM_OFFSET 2
+#define TSTAT_VF_MASK 0x00000600
+#define TSTAT_VF_OFFSET 9
+#define TSTAT_VF_ROT_MASK 0x000C0000
+#define TSTAT_VF_ROT_OFFSET 18
+#define TSTAT_ENC_MASK 0x00000180
+#define TSTAT_ENC_OFFSET 7
+#define TSTAT_ENC_ROT_MASK 0x00030000
+#define TSTAT_ENC_ROT_OFFSET 16
+#define TSTAT_PP_MASK 0x00001800
+#define TSTAT_PP_OFFSET 11
+#define TSTAT_PP_ROT_MASK 0x00300000
+#define TSTAT_PP_ROT_OFFSET 20
+#define TSTAT_PF_MASK 0x00C00000
+#define TSTAT_PF_OFFSET 22
+#define TSTAT_ADCSYS1_MASK 0x03000000
+#define TSTAT_ADCSYS1_OFFSET 24
+#define TSTAT_ADCSYS2_MASK 0x0C000000
+#define TSTAT_ADCSYS2_OFFSET 26
+
+#define TASK_STAT_IDLE 0
+#define TASK_STAT_ACTIVE 1
+#define TASK_STAT_WAIT4READY 2
+
+struct idmac {
+ struct dma_device dma;
+};
+
+struct ipu {
+ void __iomem *reg_ipu;
+ void __iomem *reg_ic;
+ unsigned int irq_fn; /* IPU Function IRQ to the CPU */
+ unsigned int irq_err; /* IPU Error IRQ to the CPU */
+ unsigned int irq_base; /* Beginning of the IPU IRQ range */
+ unsigned long channel_init_mask;
+ spinlock_t lock;
+ struct clk *ipu_clk;
+ struct device *dev;
+ struct idmac idmac;
+ struct idmac_channel channel[IPU_CHANNELS_NUM];
+ struct tasklet_struct tasklet;
+};
+
+#define to_idmac(d) container_of(d, struct idmac, dma)
+
+extern int ipu_irq_attach_irq(struct ipu *ipu, struct platform_device *dev);
+extern void ipu_irq_detach_irq(struct ipu *ipu, struct platform_device *dev);
+
+extern bool ipu_irq_status(uint32_t irq);
+
+#endif
diff --git a/drivers/mfd/ipu/ipu_irq.c b/drivers/mfd/ipu/ipu_irq.c
new file mode 100644
index 0000000..3239d6b
--- /dev/null
+++ b/drivers/mfd/ipu/ipu_irq.c
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/spinlock.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/irq.h>
+
+#include <asm/io.h>
+
+#include <mach/ipu.h>
+
+#include "ipu_intern.h"
+
+/*
+ * Register read / write - shall be inlined by the compiler
+ */
+static u32 ipu_read_reg(struct ipu *ipu, unsigned long reg)
+{
+ return __raw_readl(ipu->reg_ipu + reg);
+}
+
+static void ipu_write_reg(struct ipu *ipu, u32 value, unsigned long reg)
+{
+ __raw_writel(value, ipu->reg_ipu + reg);
+}
+
+
+/*
+ * IPU IRQ chip driver
+ */
+
+#define IPU_IRQ_NR_FN_BANKS 3
+#define IPU_IRQ_NR_ERR_BANKS 2
+
+struct ipu_irq_bank {
+ int nr_irqs;
+ unsigned int control;
+ unsigned int status;
+ unsigned int irq_base;
+ spinlock_t lock;
+ struct ipu *ipu;
+};
+
+static struct ipu_irq_bank irq_bank_fn[IPU_IRQ_NR_FN_BANKS] = {
+ /* 3 groups of functional interrupts */
+ {
+ .nr_irqs = 32,
+ .control = IPU_INT_CTRL_1,
+ .status = IPU_INT_STAT_1,
+ }, {
+ .nr_irqs = 32,
+ .control = IPU_INT_CTRL_2,
+ .status = IPU_INT_STAT_2,
+ }, {
+ .nr_irqs = 24,
+ .control = IPU_INT_CTRL_3,
+ .status = IPU_INT_STAT_3,
+ },
+};
+
+static struct ipu_irq_bank irq_bank_err[IPU_IRQ_NR_ERR_BANKS] = {
+ /* 2 groups of error interrupts */
+ {
+ .nr_irqs = 32,
+ .control = IPU_INT_CTRL_4,
+ .status = IPU_INT_STAT_4,
+ }, {
+ .nr_irqs = 17,
+ .control = IPU_INT_CTRL_5,
+ .status = IPU_INT_STAT_5,
+ },
+};
+
+#define IPU_IRQ_NR_FN_IRQS (32 + 32 + 24)
+#define IPU_IRQ_NR_ERR_IRQS (32 + 17)
+#define IPU_IRQ_NR_IRQS (IPU_IRQ_NR_ERR_IRQS + IPU_IRQ_NR_FN_IRQS)
+
+static void ipu_irq_unmask(unsigned int irq)
+{
+ struct ipu_irq_bank *bank = get_irq_chip_data(irq);
+ uint32_t reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&bank->lock, lock_flags);
+
+ reg = ipu_read_reg(bank->ipu, bank->control);
+ reg |= (1UL << (irq - bank->irq_base));
+ ipu_write_reg(bank->ipu, reg, bank->control);
+
+ spin_unlock_irqrestore(&bank->lock, lock_flags);
+}
+
+static void ipu_irq_mask(unsigned int irq)
+{
+ struct ipu_irq_bank *bank = get_irq_chip_data(irq);
+ uint32_t reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&bank->lock, lock_flags);
+
+ reg = ipu_read_reg(bank->ipu, bank->control);
+ reg &= ~(1UL << (irq - bank->irq_base));
+ ipu_write_reg(bank->ipu, reg, bank->control);
+
+ spin_unlock_irqrestore(&bank->lock, lock_flags);
+}
+
+static void ipu_irq_ack(unsigned int irq)
+{
+ struct ipu_irq_bank *bank = get_irq_chip_data(irq);
+
+ ipu_write_reg(bank->ipu, 1UL << (irq - bank->irq_base), bank->status);
+}
+
+/**
+ * Returns the current interrupt status for the specified IRQ.
+ *
+ * @param irq Interrupt line to get status for.
+ *
+ * @return Returns true if the interrupt is pending/asserted or false if
+ * the interrupt is not pending.
+ */
+bool ipu_irq_status(uint32_t irq)
+{
+ struct ipu_irq_bank *bank = get_irq_chip_data(irq);
+
+ if (ipu_read_reg(bank->ipu, bank->status) &
+ (1UL << (irq - bank->irq_base)))
+ return true;
+ else
+ return false;
+}
+
+/* Chained IRQ handler for IPU error interrupt */
+static void ipu_irq_err(unsigned int irq, struct irq_desc *desc)
+{
+ struct ipu *ipu = get_irq_data(irq);
+ u32 status;
+ int i, line;
+
+ for (i = 0; i < IPU_IRQ_NR_ERR_BANKS; i++) {
+ status = ipu_read_reg(ipu, irq_bank_err[i].status);
+ /*
+ * Don't think we have to clear all interrupts here, thy will
+ * be acked by ->handle_irq() (handle_level_irq). However, we
+ * might want to clear unhandled interrupts after the loop...
+ */
+ status &= ipu_read_reg(ipu, irq_bank_err[i].control);
+ while ((line = ffs(status))) {
+ status &= ~(1UL << (line - 1));
+ generic_handle_irq(irq_bank_err[i].irq_base + line - 1);
+ }
+ }
+}
+
+/* Chained IRQ handler for IPU function interrupt */
+static void ipu_irq_fn(unsigned int irq, struct irq_desc *desc)
+{
+ struct ipu *ipu = get_irq_data(irq);
+ u32 status;
+ int i, line;
+
+ for (i = 0; i < IPU_IRQ_NR_FN_BANKS; i++) {
+ status = ipu_read_reg(ipu, irq_bank_fn[i].status);
+ /* Not clearing all interrupts, see above */
+ status &= ipu_read_reg(ipu, irq_bank_fn[i].control);
+ while ((line = ffs(status))) {
+ status &= ~(1UL << (line - 1));
+ generic_handle_irq(irq_bank_fn[i].irq_base + line - 1);
+ }
+ }
+}
+
+static struct irq_chip ipu_irq_chip = {
+ .name = "ipu_irq",
+ .ack = ipu_irq_ack,
+ .mask = ipu_irq_mask,
+ .unmask = ipu_irq_unmask,
+};
+
+/* Install the IRQ handler */
+int ipu_irq_attach_irq(struct ipu *ipu, struct platform_device *dev)
+{
+ struct ipu_platform_data *pdata = dev->dev.platform_data;
+ unsigned int irq, irq_base, i;
+
+ irq_base = pdata->irq_base;
+
+ for (i = 0; i < IPU_IRQ_NR_FN_BANKS; i++) {
+ irq_bank_fn[i].ipu = ipu;
+ irq_bank_fn[i].irq_base = irq_base;
+ spin_lock_init(&irq_bank_fn[i].lock);
+
+ dev_dbg(&dev->dev, "IPU-IRQ: Setting up %d function irqs bank "
+ "%d at %u\n", irq_bank_fn[i].nr_irqs, i, irq_base);
+
+ for (irq = irq_base; irq < irq_base + irq_bank_fn[i].nr_irqs;
+ irq++) {
+ int ret = set_irq_chip(irq, &ipu_irq_chip);
+ if (ret < 0)
+ return ret;
+ ret = set_irq_chip_data(irq, irq_bank_fn + i);
+ if (ret < 0)
+ return ret;
+ set_irq_handler(irq, handle_level_irq);
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+#endif
+ }
+ spin_lock_init(&irq_bank_fn[i].lock);
+ irq_base += irq_bank_fn[i].nr_irqs;
+ }
+
+ for (i = 0; i < IPU_IRQ_NR_ERR_BANKS; i++) {
+ irq_bank_err[i].ipu = ipu;
+ irq_bank_err[i].irq_base = irq_base;
+ spin_lock_init(&irq_bank_fn[i].lock);
+
+ dev_dbg(&dev->dev, "IPU-IRQ: Setting up %d error irqs bank "
+ "%d at %u\n", irq_bank_err[i].nr_irqs, i, irq_base);
+
+ for (irq = irq_base; irq < irq_base + irq_bank_err[i].nr_irqs;
+ irq++) {
+ int ret = set_irq_chip(irq, &ipu_irq_chip);
+ if (ret < 0)
+ return ret;
+ ret = set_irq_chip_data(irq, irq_bank_err + i);
+ if (ret < 0)
+ return ret;
+ set_irq_handler(irq, handle_level_irq);
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, IRQF_VALID | IRQF_PROBE);
+#endif
+ }
+ spin_lock_init(&irq_bank_err[i].lock);
+ irq_base += irq_bank_err[i].nr_irqs;
+ }
+
+ set_irq_data(ipu->irq_fn, ipu);
+ set_irq_chained_handler(ipu->irq_fn, ipu_irq_fn);
+
+ set_irq_data(ipu->irq_err, ipu);
+ set_irq_chained_handler(ipu->irq_err, ipu_irq_err);
+
+ return 0;
+}
+
+void ipu_irq_detach_irq(struct ipu *ipu, struct platform_device *dev)
+{
+ struct ipu_platform_data *pdata = dev->dev.platform_data;
+ unsigned int irq, irq_base;
+
+ irq_base = pdata->irq_base;
+
+ set_irq_chained_handler(ipu->irq_fn, NULL);
+ set_irq_data(ipu->irq_fn, NULL);
+
+ set_irq_chained_handler(ipu->irq_err, NULL);
+ set_irq_data(ipu->irq_err, NULL);
+
+ for (irq = irq_base; irq < irq_base + IPU_IRQ_NR_IRQS; irq++) {
+#ifdef CONFIG_ARM
+ set_irq_flags(irq, 0);
+#endif
+ set_irq_chip(irq, NULL);
+ set_irq_chip_data(irq, NULL);
+ }
+}
--
1.5.4

2008-12-18 13:27:31

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH 4/4 v4] i.MX31: platform bindings and initialisation for IPU and framebuffer drivers

From: Guennadi Liakhovetski <[email protected]>

This patch includes platform device data for IPU and framebuffer devices
and pin initialisation for the pcm037 platform.

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---
arch/arm/mach-mx3/devices.c | 89 +++++++++++++++++++++++++++++++++++++++++++
arch/arm/mach-mx3/pcm037.c | 75 +++++++++++++++++++++++++++++++++++-
2 files changed, 163 insertions(+), 1 deletions(-)

diff --git a/arch/arm/mach-mx3/devices.c b/arch/arm/mach-mx3/devices.c
index a6bdcc0..5bc4ec1 100644
--- a/arch/arm/mach-mx3/devices.c
+++ b/arch/arm/mach-mx3/devices.c
@@ -21,8 +21,11 @@
#include <linux/platform_device.h>
#include <linux/serial.h>
#include <linux/gpio.h>
+
#include <mach/hardware.h>
#include <mach/imx-uart.h>
+#include <mach/ipu.h>
+#include <mach/mx3fb.h>

static struct resource uart0[] = {
{
@@ -145,3 +148,89 @@ int __init mxc_register_gpios(void)
{
return mxc_gpio_init(imx_gpio_ports, ARRAY_SIZE(imx_gpio_ports));
}
+
+/* i.MX31 Image Processing Unit */
+
+/* The resource order is important! */
+static struct resource mx3_ipu_rsrc[] = {
+ {
+ .start = IPU_CTRL_BASE_ADDR,
+ .end = IPU_CTRL_BASE_ADDR + 0x5F,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = IPU_CTRL_BASE_ADDR + 0x88,
+ .end = IPU_CTRL_BASE_ADDR + 0xB3,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = MXC_INT_IPU_SYN,
+ .end = MXC_INT_IPU_SYN,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .start = MXC_INT_IPU_ERR,
+ .end = MXC_INT_IPU_ERR,
+ .flags = IORESOURCE_IRQ,
+ }
+};
+
+static struct ipu_platform_data mx3_ipu_data = {
+ .irq_base = MXC_MAX_INT_LINES + MXC_MAX_GPIO_LINES,
+};
+
+static struct platform_device mx3_ipu = {
+ .name = "ipu-core",
+ .id = -1,
+ .resource = mx3_ipu_rsrc,
+ .num_resources = ARRAY_SIZE(mx3_ipu_rsrc),
+ .dev = {
+ .platform_data = &mx3_ipu_data,
+ },
+};
+
+static bool ipu_registered = false;
+
+static struct resource fb_resources[] = {
+ {
+ .start = IPU_CTRL_BASE_ADDR + 0xB4,
+ .end = IPU_CTRL_BASE_ADDR + 0x1BF,
+ .flags = IORESOURCE_MEM,
+ }, {
+ .start = IPU_IRQ_SDC_BG_EOF,
+ .end = IPU_IRQ_SDC_BG_EOF,
+ .flags = IORESOURCE_IRQ,
+ }, {
+ .start = IPU_IRQ_SDC_FG_EOF,
+ .end = IPU_IRQ_SDC_FG_EOF,
+ .flags = IORESOURCE_IRQ,
+ },
+};
+
+static struct mx3fb_platform_data mx3fb_data = {
+ .dma_dev = &mx3_ipu.dev,
+};
+
+static struct platform_device mx3_fb = {
+ .name = "mx3_sdc_fb",
+ .id = -1,
+ .num_resources = ARRAY_SIZE(fb_resources),
+ .resource = fb_resources,
+ .dev = {
+ .platform_data = &mx3fb_data,
+ .coherent_dma_mask = 0xffffffff,
+ },
+};
+
+int __init mx3_register_fb(const char *name, const struct fb_videomode *modes,
+ int num_modes)
+{
+ if (!ipu_registered) {
+ int ret = platform_device_register(&mx3_ipu);
+ if (ret < 0)
+ return ret;
+ ipu_registered = true;
+ }
+
+ mx3fb_data.name = name;
+ mx3fb_data.mode = modes;
+ mx3fb_data.num_modes = num_modes;
+ return platform_device_register(&mx3_fb);
+}
diff --git a/arch/arm/mach-mx3/pcm037.c b/arch/arm/mach-mx3/pcm037.c
index 4ebfceb..07ff9f7 100644
--- a/arch/arm/mach-mx3/pcm037.c
+++ b/arch/arm/mach-mx3/pcm037.c
@@ -26,15 +26,17 @@
#include <linux/gpio.h>
#include <linux/smc911x.h>

-#include <mach/hardware.h>
#include <asm/mach-types.h>
#include <asm/mach/arch.h>
#include <asm/mach/time.h>
#include <asm/mach/map.h>
+
+#include <mach/hardware.h>
#include <mach/common.h>
#include <mach/imx-uart.h>
#include <mach/iomux-mx3.h>
#include <mach/board-pcm037.h>
+#include <mach/mx3fb.h>

#include "devices.h"

@@ -95,6 +97,47 @@ static struct platform_device *devices[] __initdata = {
&pcm037_eth,
};

+static const struct fb_videomode fb_modedb[] = {
+ {
+ /* 320x240 @ 60 Hz */
+ .name = "CMEL-OLED QVGA",
+ .refresh = 60,
+ .xres = 240,
+ .yres = 320,
+ .pixclock = /*217014*//*868056*/185925,
+ .left_margin = 9,
+ .right_margin = 16,
+ .upper_margin = 7,
+ .lower_margin = 9,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = /*FB_SYNC_HOR_HIGH_ACT | */
+ /*FB_SYNC_OE_ACT_HIGH | S6E63D6 datasheet p.92 - active low. */
+ /*FB_SYNC_VERT_HIGH_ACT |*/
+ FB_SYNC_CLK_INVERT |
+ FB_SYNC_CLK_IDLE_EN | FB_SYNC_CLK_SEL_EN,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* 240x320 @ 60 Hz */
+ .name = "Sharp-LQ035Q7DH06-QVGA",
+ .refresh = 60,
+ .xres = 240,
+ .yres = 320,
+ .pixclock = 185925,
+ .left_margin = 9,
+ .right_margin = 16,
+ .upper_margin = 7,
+ .lower_margin = 9,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE |
+ FB_SYNC_CLK_INVERT | FB_SYNC_CLK_IDLE_EN,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ },
+};
+
/*
* Board specific initialization.
*/
@@ -118,6 +161,36 @@ static void __init mxc_board_init(void)
mxc_iomux_mode(IOMUX_MODE(MX31_PIN_GPIO3_1, IOMUX_CONFIG_GPIO));
if (!gpio_request(MX31_PIN_GPIO3_1, "pcm037-eth"))
gpio_direction_input(MX31_PIN_GPIO3_1);
+
+ /* Display Interface #3 */
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD0, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD1, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD2, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD3, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD4, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD5, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD6, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD7, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD8, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD9, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD10, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD11, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD12, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD13, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD14, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD15, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD16, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_LD17, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_VSYNC3, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_HSYNC, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_FPSHIFT, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_DRDY0, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_D3_REV, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_CONTRAST, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_D3_SPL, IOMUX_CONFIG_FUNC));
+ mxc_iomux_mode(IOMUX_MODE(MX31_PIN_D3_CLS, IOMUX_CONFIG_FUNC));
+
+ mx3_register_fb(fb_modedb[1].name, fb_modedb, ARRAY_SIZE(fb_modedb));
}

/*
--
1.5.4

2008-12-18 13:27:07

by Guennadi Liakhovetski

[permalink] [raw]
Subject: [PATCH 3/4 v4] i.MX31: framebuffer driver

From: Guennadi Liakhovetski <[email protected]>

This is a framebuffer driver for i.MX31 SoCs. It only supports synchronous
displays, panning tested with directfb examples, works well. Overlay
support is included but has never been tested. Based on original driver
from Freescale, Copyright and authorship in mx3fb.c preserved.

Signed-off-by: Guennadi Liakhovetski <[email protected]>
---
arch/arm/plat-mxc/include/mach/mx3fb.h | 38 +
drivers/video/Kconfig | 12 +
drivers/video/Makefile | 1 +
drivers/video/mx3fb.c | 1879 ++++++++++++++++++++++++++++++++
4 files changed, 1930 insertions(+), 0 deletions(-)
create mode 100644 arch/arm/plat-mxc/include/mach/mx3fb.h
create mode 100644 drivers/video/mx3fb.c

diff --git a/arch/arm/plat-mxc/include/mach/mx3fb.h b/arch/arm/plat-mxc/include/mach/mx3fb.h
new file mode 100644
index 0000000..91f03a4
--- /dev/null
+++ b/arch/arm/plat-mxc/include/mach/mx3fb.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef __ASM_ARCH_MX3FB_H__
+#define __ASM_ARCH_MX3FB_H__
+
+#include <linux/device.h>
+#include <linux/fb.h>
+
+/* Proprietary FB_SYNC_ flags */
+#define FB_SYNC_OE_ACT_HIGH 0x80000000
+#define FB_SYNC_CLK_INVERT 0x40000000
+#define FB_SYNC_DATA_INVERT 0x20000000
+#define FB_SYNC_CLK_IDLE_EN 0x10000000
+#define FB_SYNC_SHARP_MODE 0x08000000
+#define FB_SYNC_SWAP_RGB 0x04000000
+#define FB_SYNC_CLK_SEL_EN 0x02000000
+
+/**
+ * @dma_dev pointer to the dma-device, used for dma-slave connection
+ * @mode pointer to a platform-provided per mxc_register_fb() videomode
+ */
+struct mx3fb_platform_data {
+ struct device *dma_dev;
+ const char *name;
+ const struct fb_videomode *mode;
+ int num_modes;
+};
+
+extern int mx3_register_fb(const char *, const struct fb_videomode *, int);
+
+#endif
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig
index 5611a4f..cd9a8c2 100644
--- a/drivers/video/Kconfig
+++ b/drivers/video/Kconfig
@@ -2117,6 +2117,18 @@ config FB_PRE_INIT_FB
Select this option if display contents should be inherited as set by
the bootloader.

+config FB_MX3
+ tristate "MX3 Framebuffer support"
+ depends on FB && MX3_IPU
+ select FB_CFB_FILLRECT
+ select FB_CFB_COPYAREA
+ select FB_CFB_IMAGEBLIT
+ default y
+ help
+ This is a framebuffer device for the i.MX31 LCD Controller. So
+ far only synchronous displays are supported. If you plan to use
+ an LCD display with your i.MX31 system, say Y here.
+
source "drivers/video/omap/Kconfig"

source "drivers/video/backlight/Kconfig"
diff --git a/drivers/video/Makefile b/drivers/video/Makefile
index e39e33e..8953190 100644
--- a/drivers/video/Makefile
+++ b/drivers/video/Makefile
@@ -132,6 +132,7 @@ obj-$(CONFIG_FB_VGA16) += vga16fb.o
obj-$(CONFIG_FB_OF) += offb.o
obj-$(CONFIG_FB_BF54X_LQ043) += bf54x-lq043fb.o
obj-$(CONFIG_FB_BFIN_T350MCQB) += bfin-t350mcqb-fb.o
+obj-$(CONFIG_FB_MX3) += mx3fb.o

# the test framebuffer is last
obj-$(CONFIG_FB_VIRTUAL) += vfb.o
diff --git a/drivers/video/mx3fb.c b/drivers/video/mx3fb.c
new file mode 100644
index 0000000..f3b48b9
--- /dev/null
+++ b/drivers/video/mx3fb.c
@@ -0,0 +1,1879 @@
+/*
+ * Copyright (C) 2008
+ * Guennadi Liakhovetski, DENX Software Engineering, <[email protected]>
+ *
+ * Copyright 2004-2007 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/fb.h>
+#include <linux/delay.h>
+#include <linux/init.h>
+#include <linux/ioport.h>
+#include <linux/dma-mapping.h>
+#include <linux/dmaengine.h>
+#include <linux/console.h>
+#include <linux/clk.h>
+#include <linux/mutex.h>
+
+#include <mach/hardware.h>
+#include <mach/ipu.h>
+#include <mach/mx3fb.h>
+
+#include <asm/io.h>
+#include <asm/uaccess.h>
+
+#define MX3FB_NAME "mx3_sdc_fb"
+
+#define MX3FB_REG_OFFSET 0xB4
+
+/* SDC Registers */
+#define SDC_COM_CONF (0xB4 - MX3FB_REG_OFFSET)
+#define SDC_GW_CTRL (0xB8 - MX3FB_REG_OFFSET)
+#define SDC_FG_POS (0xBC - MX3FB_REG_OFFSET)
+#define SDC_BG_POS (0xC0 - MX3FB_REG_OFFSET)
+#define SDC_CUR_POS (0xC4 - MX3FB_REG_OFFSET)
+#define SDC_PWM_CTRL (0xC8 - MX3FB_REG_OFFSET)
+#define SDC_CUR_MAP (0xCC - MX3FB_REG_OFFSET)
+#define SDC_HOR_CONF (0xD0 - MX3FB_REG_OFFSET)
+#define SDC_VER_CONF (0xD4 - MX3FB_REG_OFFSET)
+#define SDC_SHARP_CONF_1 (0xD8 - MX3FB_REG_OFFSET)
+#define SDC_SHARP_CONF_2 (0xDC - MX3FB_REG_OFFSET)
+
+/* Register bits */
+#define SDC_COM_TFT_COLOR 0x00000001UL
+#define SDC_COM_FG_EN 0x00000010UL
+#define SDC_COM_GWSEL 0x00000020UL
+#define SDC_COM_GLB_A 0x00000040UL
+#define SDC_COM_KEY_COLOR_G 0x00000080UL
+#define SDC_COM_BG_EN 0x00000200UL
+#define SDC_COM_SHARP 0x00001000UL
+
+#define SDC_V_SYNC_WIDTH_L 0x00000001UL
+
+/* Display Interface registers */
+#define DI_DISP_IF_CONF (0x0124 - MX3FB_REG_OFFSET)
+#define DI_DISP_SIG_POL (0x0128 - MX3FB_REG_OFFSET)
+#define DI_SER_DISP1_CONF (0x012C - MX3FB_REG_OFFSET)
+#define DI_SER_DISP2_CONF (0x0130 - MX3FB_REG_OFFSET)
+#define DI_HSP_CLK_PER (0x0134 - MX3FB_REG_OFFSET)
+#define DI_DISP0_TIME_CONF_1 (0x0138 - MX3FB_REG_OFFSET)
+#define DI_DISP0_TIME_CONF_2 (0x013C - MX3FB_REG_OFFSET)
+#define DI_DISP0_TIME_CONF_3 (0x0140 - MX3FB_REG_OFFSET)
+#define DI_DISP1_TIME_CONF_1 (0x0144 - MX3FB_REG_OFFSET)
+#define DI_DISP1_TIME_CONF_2 (0x0148 - MX3FB_REG_OFFSET)
+#define DI_DISP1_TIME_CONF_3 (0x014C - MX3FB_REG_OFFSET)
+#define DI_DISP2_TIME_CONF_1 (0x0150 - MX3FB_REG_OFFSET)
+#define DI_DISP2_TIME_CONF_2 (0x0154 - MX3FB_REG_OFFSET)
+#define DI_DISP2_TIME_CONF_3 (0x0158 - MX3FB_REG_OFFSET)
+#define DI_DISP3_TIME_CONF (0x015C - MX3FB_REG_OFFSET)
+#define DI_DISP0_DB0_MAP (0x0160 - MX3FB_REG_OFFSET)
+#define DI_DISP0_DB1_MAP (0x0164 - MX3FB_REG_OFFSET)
+#define DI_DISP0_DB2_MAP (0x0168 - MX3FB_REG_OFFSET)
+#define DI_DISP0_CB0_MAP (0x016C - MX3FB_REG_OFFSET)
+#define DI_DISP0_CB1_MAP (0x0170 - MX3FB_REG_OFFSET)
+#define DI_DISP0_CB2_MAP (0x0174 - MX3FB_REG_OFFSET)
+#define DI_DISP1_DB0_MAP (0x0178 - MX3FB_REG_OFFSET)
+#define DI_DISP1_DB1_MAP (0x017C - MX3FB_REG_OFFSET)
+#define DI_DISP1_DB2_MAP (0x0180 - MX3FB_REG_OFFSET)
+#define DI_DISP1_CB0_MAP (0x0184 - MX3FB_REG_OFFSET)
+#define DI_DISP1_CB1_MAP (0x0188 - MX3FB_REG_OFFSET)
+#define DI_DISP1_CB2_MAP (0x018C - MX3FB_REG_OFFSET)
+#define DI_DISP2_DB0_MAP (0x0190 - MX3FB_REG_OFFSET)
+#define DI_DISP2_DB1_MAP (0x0194 - MX3FB_REG_OFFSET)
+#define DI_DISP2_DB2_MAP (0x0198 - MX3FB_REG_OFFSET)
+#define DI_DISP2_CB0_MAP (0x019C - MX3FB_REG_OFFSET)
+#define DI_DISP2_CB1_MAP (0x01A0 - MX3FB_REG_OFFSET)
+#define DI_DISP2_CB2_MAP (0x01A4 - MX3FB_REG_OFFSET)
+#define DI_DISP3_B0_MAP (0x01A8 - MX3FB_REG_OFFSET)
+#define DI_DISP3_B1_MAP (0x01AC - MX3FB_REG_OFFSET)
+#define DI_DISP3_B2_MAP (0x01B0 - MX3FB_REG_OFFSET)
+#define DI_DISP_ACC_CC (0x01B4 - MX3FB_REG_OFFSET)
+#define DI_DISP_LLA_CONF (0x01B8 - MX3FB_REG_OFFSET)
+#define DI_DISP_LLA_DATA (0x01BC - MX3FB_REG_OFFSET)
+
+/* DI_DISP_SIG_POL bits */
+#define DI_D3_VSYNC_POL_SHIFT 28
+#define DI_D3_HSYNC_POL_SHIFT 27
+#define DI_D3_DRDY_SHARP_POL_SHIFT 26
+#define DI_D3_CLK_POL_SHIFT 25
+#define DI_D3_DATA_POL_SHIFT 24
+
+/* DI_DISP_IF_CONF bits */
+#define DI_D3_CLK_IDLE_SHIFT 26
+#define DI_D3_CLK_SEL_SHIFT 25
+#define DI_D3_DATAMSK_SHIFT 24
+
+enum ipu_panel {
+ IPU_PANEL_SHARP_TFT,
+ IPU_PANEL_TFT,
+};
+
+struct ipu_di_signal_cfg {
+ unsigned datamask_en:1;
+ unsigned clksel_en:1;
+ unsigned clkidle_en:1;
+ unsigned data_pol:1; /* true = inverted */
+ unsigned clk_pol:1; /* true = rising edge */
+ unsigned enable_pol:1;
+ unsigned Hsync_pol:1; /* true = active high */
+ unsigned Vsync_pol:1;
+};
+
+static const struct fb_videomode mx3fb_modedb[] = {
+ {
+ /* 240x320 @ 60 Hz */
+ .name = "Sharp-QVGA",
+ .refresh = 60,
+ .xres = 240,
+ .yres = 320,
+ .pixclock = 185925,
+ .left_margin = 9,
+ .right_margin = 16,
+ .upper_margin = 7,
+ .lower_margin = 9,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE |
+ FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT |
+ FB_SYNC_CLK_IDLE_EN,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* 240x33 @ 60 Hz */
+ .name = "Sharp-CLI",
+ .refresh = 60,
+ .xres = 240,
+ .yres = 33,
+ .pixclock = 185925,
+ .left_margin = 9,
+ .right_margin = 16,
+ .upper_margin = 7,
+ .lower_margin = 9 + 287,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = FB_SYNC_HOR_HIGH_ACT | FB_SYNC_SHARP_MODE |
+ FB_SYNC_CLK_INVERT | FB_SYNC_DATA_INVERT |
+ FB_SYNC_CLK_IDLE_EN,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* 640x480 @ 60 Hz */
+ .name = "NEC-VGA",
+ .refresh = 60,
+ .xres = 640,
+ .yres = 480,
+ .pixclock = 38255,
+ .left_margin = 144,
+ .right_margin = 0,
+ .upper_margin = 34,
+ .lower_margin = 40,
+ .hsync_len = 1,
+ .vsync_len = 1,
+ .sync = FB_SYNC_VERT_HIGH_ACT | FB_SYNC_OE_ACT_HIGH,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* NTSC TV output */
+ .name = "TV-NTSC",
+ .refresh = 60,
+ .xres = 640,
+ .yres = 480,
+ .pixclock = 37538,
+ .left_margin = 38,
+ .right_margin = 858 - 640 - 38 - 3,
+ .upper_margin = 36,
+ .lower_margin = 518 - 480 - 36 - 1,
+ .hsync_len = 3,
+ .vsync_len = 1,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* PAL TV output */
+ .name = "TV-PAL",
+ .refresh = 50,
+ .xres = 640,
+ .yres = 480,
+ .pixclock = 37538,
+ .left_margin = 38,
+ .right_margin = 960 - 640 - 38 - 32,
+ .upper_margin = 32,
+ .lower_margin = 555 - 480 - 32 - 3,
+ .hsync_len = 32,
+ .vsync_len = 3,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ }, {
+ /* TV output VGA mode, 640x480 @ 65 Hz */
+ .name = "TV-VGA",
+ .refresh = 60,
+ .xres = 640,
+ .yres = 480,
+ .pixclock = 40574,
+ .left_margin = 35,
+ .right_margin = 45,
+ .upper_margin = 9,
+ .lower_margin = 1,
+ .hsync_len = 46,
+ .vsync_len = 5,
+ .sync = 0,
+ .vmode = FB_VMODE_NONINTERLACED,
+ .flag = 0,
+ },
+};
+
+struct mx3fb_data {
+ struct fb_info *fbi;
+ struct fb_info *fbi_ovl;
+ int backlight_level;
+ void __iomem *reg_base;
+ spinlock_t lock;
+ struct device *dev;
+
+ uint32_t h_start_width;
+ uint32_t v_start_width;
+
+ /* IDMAC / dmaengine interface */
+ struct idmac_channel *idmac_channel[2]; /* We need 2 channels */
+};
+
+struct dma_chan_request {
+ struct mx3fb_data *mx3fb;
+ enum ipu_channel id;
+};
+
+/* MX3 specific framebuffer information. */
+struct mx3fb_info {
+ int blank;
+ enum ipu_channel ipu_ch;
+ uint32_t cur_ipu_buf;
+
+ u32 pseudo_palette[16];
+
+ struct completion flip_cmpl;
+ struct mutex mutex; /* Protects fb-ops */
+ struct mx3fb_data *mx3fb;
+ struct idmac_channel *idmac_channel;
+ struct dma_async_tx_descriptor *txd;
+ dma_cookie_t cookie;
+ struct scatterlist sg[2];
+};
+
+/* Allocated overlay buffer */
+struct mx3fb_alloc_list {
+ struct list_head list;
+ dma_addr_t phy_addr;
+ void *cpu_addr;
+ size_t size;
+};
+
+/* A list of overlay buffers */
+static LIST_HEAD(fb_alloc_list);
+
+static void mx3fb_dma_done(void *);
+
+/* Used fb-mode and bpp. Can be set on kernel command line, therefore file-static. */
+static const char *fb_mode;
+static unsigned long default_bpp = 16;
+
+static u32 mx3fb_read_reg(struct mx3fb_data *mx3fb, unsigned long reg)
+{
+ return __raw_readl(mx3fb->reg_base + reg);
+}
+
+static void mx3fb_write_reg(struct mx3fb_data *mx3fb, u32 value, unsigned long reg)
+{
+ __raw_writel(value, mx3fb->reg_base + reg);
+}
+
+static const uint32_t di_mappings[] = {
+ 0x1600AAAA, 0x00E05555, 0x00070000, 3, /* RGB888 */
+ 0x0005000F, 0x000B000F, 0x0011000F, 1, /* RGB666 */
+ 0x0011000F, 0x000B000F, 0x0005000F, 1, /* BGR666 */
+ 0x0004003F, 0x000A000F, 0x000F003F, 1 /* RGB565 */
+};
+
+static void sdc_fb_init(struct mx3fb_info *fbi)
+{
+ struct mx3fb_data *mx3fb = fbi->mx3fb;
+ uint32_t reg;
+
+ reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
+
+ /* Also enable foreground for overlay */
+ if (mx3fb->fbi_ovl && fbi == mx3fb->fbi_ovl->par)
+ reg |= SDC_COM_FG_EN;
+
+ mx3fb_write_reg(mx3fb, reg | SDC_COM_BG_EN, SDC_COM_CONF);
+}
+
+/* Returns enabled flag before uninit */
+static uint32_t sdc_fb_uninit(struct mx3fb_info *fbi)
+{
+ struct mx3fb_data *mx3fb = fbi->mx3fb;
+ uint32_t reg, chan_mask;
+
+ reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
+
+ /*
+ * Don't we have to automatically disable overlay when disabling
+ * background? Attention: cannot test mx3fb->fbi_ovl->par, must
+ * test mx3fb->fbi->par, because at the time this function is
+ * called for the first time fbi_ovl is not assigned yet.
+ */
+ if (fbi == mx3fb->fbi->par)
+ chan_mask = SDC_COM_BG_EN;
+ else
+ chan_mask = SDC_COM_FG_EN;
+
+ mx3fb_write_reg(mx3fb, reg & ~chan_mask, SDC_COM_CONF);
+
+ return reg & chan_mask;
+}
+
+static void sdc_enable_channel(struct mx3fb_info *mx3_fbi)
+{
+ struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;
+ struct idmac_channel *ichan = mx3_fbi->idmac_channel;
+ struct dma_chan *dma_chan = &ichan->dma_chan;
+ unsigned long flags;
+ dma_cookie_t cookie;
+
+ dev_dbg(mx3fb->dev, "mx3fbi %p, desc %p, sg %p\n", mx3_fbi,
+ to_tx_desc(mx3_fbi->txd), to_tx_desc(mx3_fbi->txd)->sg);
+
+ /* This enables the channel */
+ if (mx3_fbi->cookie < 0) {
+ mx3_fbi->txd = dma_chan->device->device_prep_slave_sg(dma_chan,
+ &mx3_fbi->sg[0], 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT);
+ if (!mx3_fbi->txd) {
+ dev_err(mx3fb->dev, "Cannot allocate descriptor on %d\n",
+ dma_chan->chan_id);
+ return;
+ }
+
+ mx3_fbi->txd->callback_param = mx3_fbi->txd;
+ mx3_fbi->txd->callback = mx3fb_dma_done;
+
+ cookie = mx3_fbi->txd->tx_submit(mx3_fbi->txd);
+ dev_dbg(mx3fb->dev, "%d: Submit %p #%d [%c]\n", __LINE__,
+ mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+');
+ } else {
+ if (!mx3_fbi->txd || !mx3_fbi->txd->tx_submit) {
+ dev_err(mx3fb->dev, "Cannot enable channel %d\n",
+ dma_chan->chan_id);
+ return;
+ }
+
+ /* Just re-activate the same buffer */
+ dma_async_issue_pending(dma_chan);
+ cookie = mx3_fbi->cookie;
+ dev_dbg(mx3fb->dev, "%d: Re-submit %p #%d [%c]\n", __LINE__,
+ mx3_fbi->txd, cookie, list_empty(&ichan->queue) ? '-' : '+');
+ }
+
+ if (cookie >= 0) {
+ spin_lock_irqsave(&mx3fb->lock, flags);
+ sdc_fb_init(mx3_fbi);
+ mx3_fbi->cookie = cookie;
+ spin_unlock_irqrestore(&mx3fb->lock, flags);
+ }
+
+ /*
+ * Attention! Without this msleep the channel keeps generating
+ * interrupts. Next sdc_set_brightness() is going to be called
+ * from mx3fb_blank().
+ */
+ msleep(2);
+}
+
+static void sdc_disable_channel(struct mx3fb_info *mx3_fbi)
+{
+ struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;
+ uint32_t enabled;
+ unsigned long flags;
+
+ spin_lock_irqsave(&mx3fb->lock, flags);
+
+ enabled = sdc_fb_uninit(mx3_fbi);
+
+ spin_unlock_irqrestore(&mx3fb->lock, flags);
+
+ mx3_fbi->txd->chan->device->device_terminate_all(mx3_fbi->txd->chan);
+ mx3_fbi->txd = NULL;
+ mx3_fbi->cookie = -EINVAL;
+}
+
+/**
+ * Set the window position of the foreground or background plane modes.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ * @param x_pos The X coordinate position to place window at.
+ * The position is relative to the top left corner.
+ * @param y_pos The Y coordinate position to place window at.
+ * The position is relative to the top left corner.
+ * @return This function returns 0 on success or negative error code on fail
+ */
+static int sdc_set_window_pos(struct mx3fb_data *mx3fb, enum ipu_channel channel,
+ int16_t x_pos, int16_t y_pos)
+{
+ x_pos += mx3fb->h_start_width;
+ y_pos += mx3fb->v_start_width;
+
+ switch (channel) {
+ case IDMAC_SDC_0:
+ mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_BG_POS);
+ break;
+ case IDMAC_SDC_1:
+ mx3fb_write_reg(mx3fb, (x_pos << 16) | y_pos, SDC_FG_POS);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * Initialize a synchronous LCD panel.
+ *
+ * @param panel The type of panel.
+ * @param pixel_clk Desired pixel clock frequency in Hz.
+ * @param pixel_fmt Input parameter for pixel format of buffer. Pixel
+ * format is a FOURCC ASCII code.
+ * @param width The width of panel in pixels.
+ * @param height The height of panel in pixels.
+ * @param hStartWidth The number of pixel clocks between the HSYNC
+ * signal pulse and the start of valid data.
+ * @param hSyncWidth The width of the HSYNC signal in units of pixel
+ * clocks.
+ * @param hEndWidth The number of pixel clocks between the end of
+ * valid data and the HSYNC signal for next line.
+ * @param vStartWidth The number of lines between the VSYNC
+ * signal pulse and the start of valid data.
+ * @param vSyncWidth The width of the VSYNC signal in units of lines
+ * @param vEndWidth The number of lines between the end of valid
+ * data and the VSYNC signal for next frame.
+ * @param sig Bitfield of signal polarities for LCD interface.
+ * @return This function returns 0 on success or negative error code on
+ * fail.
+ */
+static int sdc_init_panel(struct mx3fb_data *mx3fb, enum ipu_panel panel,
+ uint32_t pixel_clk,
+ uint16_t width, uint16_t height,
+ enum pixel_fmt pixel_fmt,
+ uint16_t h_start_width, uint16_t h_sync_width,
+ uint16_t h_end_width, uint16_t v_start_width,
+ uint16_t v_sync_width, uint16_t v_end_width,
+ struct ipu_di_signal_cfg sig)
+{
+ unsigned long lock_flags;
+ uint32_t reg;
+ uint32_t old_conf;
+ uint32_t div;
+ struct clk *ipu_clk;
+
+ dev_dbg(mx3fb->dev, "panel size = %d x %d", width, height);
+
+ if (v_sync_width == 0 || h_sync_width == 0)
+ return -EINVAL;
+
+ /* Init panel size and blanking periods */
+ reg = ((uint32_t) (h_sync_width - 1) << 26) |
+ ((uint32_t) (width + h_start_width + h_end_width - 1) << 16);
+ mx3fb_write_reg(mx3fb, reg, SDC_HOR_CONF);
+
+#ifdef DEBUG
+ printk(" hor_conf %x,", reg);
+#endif
+
+ reg = ((uint32_t) (v_sync_width - 1) << 26) | SDC_V_SYNC_WIDTH_L |
+ ((uint32_t) (height + v_start_width + v_end_width - 1) << 16);
+ mx3fb_write_reg(mx3fb, reg, SDC_VER_CONF);
+
+#ifdef DEBUG
+ printk(" ver_conf %x\n", reg);
+#endif
+
+ mx3fb->h_start_width = h_start_width;
+ mx3fb->v_start_width = v_start_width;
+
+ switch (panel) {
+ case IPU_PANEL_SHARP_TFT:
+ mx3fb_write_reg(mx3fb, 0x00FD0102L, SDC_SHARP_CONF_1);
+ mx3fb_write_reg(mx3fb, 0x00F500F4L, SDC_SHARP_CONF_2);
+ mx3fb_write_reg(mx3fb, SDC_COM_SHARP | SDC_COM_TFT_COLOR, SDC_COM_CONF);
+ break;
+ case IPU_PANEL_TFT:
+ mx3fb_write_reg(mx3fb, SDC_COM_TFT_COLOR, SDC_COM_CONF);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /* Init clocking */
+
+ /*
+ * Calculate divider: fractional part is 4 bits so simply multiple by
+ * 2^4 to get fractional part, as long as we stay under ~250MHz and on
+ * i.MX31 it (HSP_CLK) is <= 178MHz. Currently 128.267MHz
+ */
+ dev_dbg(mx3fb->dev, "pixel clk = %d\n", pixel_clk);
+
+ ipu_clk = clk_get(mx3fb->dev, "ipu_clk");
+ div = clk_get_rate(ipu_clk) * 16 / pixel_clk;
+ clk_put(ipu_clk);
+
+ if (div < 0x40) { /* Divider less than 4 */
+ dev_dbg(mx3fb->dev,
+ "InitPanel() - Pixel clock divider less than 4\n");
+ div = 0x40;
+ }
+
+ spin_lock_irqsave(&mx3fb->lock, lock_flags);
+
+ /*
+ * DISP3_IF_CLK_DOWN_WR is half the divider value and 2 fraction bits
+ * fewer. Subtract 1 extra from DISP3_IF_CLK_DOWN_WR based on timing
+ * debug. DISP3_IF_CLK_UP_WR is 0
+ */
+ mx3fb_write_reg(mx3fb, (((div / 8) - 1) << 22) | div, DI_DISP3_TIME_CONF);
+
+ /* DI settings */
+ old_conf = mx3fb_read_reg(mx3fb, DI_DISP_IF_CONF) & 0x78FFFFFF;
+ old_conf |= sig.datamask_en << DI_D3_DATAMSK_SHIFT |
+ sig.clksel_en << DI_D3_CLK_SEL_SHIFT |
+ sig.clkidle_en << DI_D3_CLK_IDLE_SHIFT;
+ mx3fb_write_reg(mx3fb, old_conf, DI_DISP_IF_CONF);
+
+ old_conf = mx3fb_read_reg(mx3fb, DI_DISP_SIG_POL) & 0xE0FFFFFF;
+ old_conf |= sig.data_pol << DI_D3_DATA_POL_SHIFT |
+ sig.clk_pol << DI_D3_CLK_POL_SHIFT |
+ sig.enable_pol << DI_D3_DRDY_SHARP_POL_SHIFT |
+ sig.Hsync_pol << DI_D3_HSYNC_POL_SHIFT |
+ sig.Vsync_pol << DI_D3_VSYNC_POL_SHIFT;
+ mx3fb_write_reg(mx3fb, old_conf, DI_DISP_SIG_POL);
+
+ switch (pixel_fmt) {
+ case IPU_PIX_FMT_RGB24:
+ mx3fb_write_reg(mx3fb, di_mappings[0], DI_DISP3_B0_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[1], DI_DISP3_B1_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[2], DI_DISP3_B2_MAP);
+ mx3fb_write_reg(mx3fb, mx3fb_read_reg(mx3fb, DI_DISP_ACC_CC) |
+ ((di_mappings[3] - 1) << 12), DI_DISP_ACC_CC);
+ break;
+ case IPU_PIX_FMT_RGB666:
+ mx3fb_write_reg(mx3fb, di_mappings[4], DI_DISP3_B0_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[5], DI_DISP3_B1_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[6], DI_DISP3_B2_MAP);
+ mx3fb_write_reg(mx3fb, mx3fb_read_reg(mx3fb, DI_DISP_ACC_CC) |
+ ((di_mappings[7] - 1) << 12), DI_DISP_ACC_CC);
+ break;
+ case IPU_PIX_FMT_BGR666:
+ mx3fb_write_reg(mx3fb, di_mappings[8], DI_DISP3_B0_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[9], DI_DISP3_B1_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[10], DI_DISP3_B2_MAP);
+ mx3fb_write_reg(mx3fb, mx3fb_read_reg(mx3fb, DI_DISP_ACC_CC) |
+ ((di_mappings[11] - 1) << 12), DI_DISP_ACC_CC);
+ break;
+ default:
+ mx3fb_write_reg(mx3fb, di_mappings[12], DI_DISP3_B0_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[13], DI_DISP3_B1_MAP);
+ mx3fb_write_reg(mx3fb, di_mappings[14], DI_DISP3_B2_MAP);
+ mx3fb_write_reg(mx3fb, mx3fb_read_reg(mx3fb, DI_DISP_ACC_CC) |
+ ((di_mappings[15] - 1) << 12), DI_DISP_ACC_CC);
+ break;
+ }
+
+ spin_unlock_irqrestore(&mx3fb->lock, lock_flags);
+
+ dev_dbg(mx3fb->dev, "DI_DISP_IF_CONF = 0x%08X\n",
+ mx3fb_read_reg(mx3fb, DI_DISP_IF_CONF));
+ dev_dbg(mx3fb->dev, "DI_DISP_SIG_POL = 0x%08X\n",
+ mx3fb_read_reg(mx3fb, DI_DISP_SIG_POL));
+ dev_dbg(mx3fb->dev, "DI_DISP3_TIME_CONF = 0x%08X\n",
+ mx3fb_read_reg(mx3fb, DI_DISP3_TIME_CONF));
+
+ return 0;
+}
+
+/**
+ * Set the transparent color key for SDC graphic plane.
+ *
+ * @param channel Input parameter for the logical channel ID.
+ * @param enable Boolean to enable or disable color key
+ * @param colorKey 24-bit RGB color to use as transparent color key.
+ * @return This function returns 0 on success or negative error code on fail
+ */
+static int sdc_set_color_key(struct mx3fb_data *mx3fb, enum ipu_channel channel,
+ bool enable, uint32_t color_key)
+{
+ uint32_t reg, sdc_conf;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&mx3fb->lock, lock_flags);
+
+ sdc_conf = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
+ if (channel == IDMAC_SDC_0)
+ sdc_conf &= ~SDC_COM_GWSEL;
+ else
+ sdc_conf |= SDC_COM_GWSEL;
+
+ if (enable) {
+ reg = mx3fb_read_reg(mx3fb, SDC_GW_CTRL) & 0xFF000000L;
+ mx3fb_write_reg(mx3fb, reg | (color_key & 0x00FFFFFFL),
+ SDC_GW_CTRL);
+
+ sdc_conf |= SDC_COM_KEY_COLOR_G;
+ } else {
+ sdc_conf &= ~SDC_COM_KEY_COLOR_G;
+ }
+ mx3fb_write_reg(mx3fb, sdc_conf, SDC_COM_CONF);
+
+ spin_unlock_irqrestore(&mx3fb->lock, lock_flags);
+
+ return 0;
+}
+
+/**
+ * Set the foreground and background plane global alpha blending modes.
+ *
+ * @param enable Boolean to enable or disable global alpha
+ * blending. If disabled, per pixel blending is used.
+ * @param alpha Global alpha value.
+ * @return This function returns 0 on success or negative error code on fail
+ */
+static int sdc_set_global_alpha(struct mx3fb_data *mx3fb, bool enable, uint8_t alpha)
+{
+ uint32_t reg;
+ unsigned long lock_flags;
+
+ spin_lock_irqsave(&mx3fb->lock, lock_flags);
+
+ if (enable) {
+ reg = mx3fb_read_reg(mx3fb, SDC_GW_CTRL) & 0x00FFFFFFL;
+ mx3fb_write_reg(mx3fb, reg | ((uint32_t) alpha << 24), SDC_GW_CTRL);
+
+ reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
+ mx3fb_write_reg(mx3fb, reg | SDC_COM_GLB_A, SDC_COM_CONF);
+ } else {
+ reg = mx3fb_read_reg(mx3fb, SDC_COM_CONF);
+ mx3fb_write_reg(mx3fb, reg & ~SDC_COM_GLB_A, SDC_COM_CONF);
+ }
+
+ spin_unlock_irqrestore(&mx3fb->lock, lock_flags);
+
+ return 0;
+}
+
+static void sdc_set_brightness(struct mx3fb_data *mx3fb, uint8_t value)
+{
+ /* This might be board-specific */
+ mx3fb_write_reg(mx3fb, 0x03000000UL | value << 16, SDC_PWM_CTRL);
+ return;
+}
+
+static uint32_t bpp_to_pixfmt(int bpp)
+{
+ uint32_t pixfmt = 0;
+ switch (bpp) {
+ case 24:
+ pixfmt = IPU_PIX_FMT_BGR24;
+ break;
+ case 32:
+ pixfmt = IPU_PIX_FMT_BGR32;
+ break;
+ case 16:
+ pixfmt = IPU_PIX_FMT_RGB565;
+ break;
+ }
+ return pixfmt;
+}
+
+static int mx3fb_blank(int blank, struct fb_info *fbi);
+static int mx3fb_map_video_memory(struct fb_info *fbi);
+static int mx3fb_unmap_video_memory(struct fb_info *fbi);
+
+/*
+ * Set fixed framebuffer parameters based on variable settings.
+ *
+ * @param info framebuffer information pointer
+ */
+static int mx3fb_set_fix(struct fb_info *fbi)
+{
+ struct fb_fix_screeninfo *fix = &fbi->fix;
+ struct fb_var_screeninfo *var = &fbi->var;
+ struct mx3fb_info *mx3_fbi = fbi->par;
+
+ if (mx3_fbi->ipu_ch == IDMAC_SDC_1)
+ strncpy(fix->id, "DISP3 FG", 8);
+ else
+ strncpy(fix->id, "DISP3 BG", 8);
+
+ fix->line_length = var->xres_virtual * var->bits_per_pixel / 8;
+
+ fix->type = FB_TYPE_PACKED_PIXELS;
+ fix->accel = FB_ACCEL_NONE;
+ fix->visual = FB_VISUAL_TRUECOLOR;
+ fix->xpanstep = 1;
+ fix->ypanstep = 1;
+
+ return 0;
+}
+
+static void mx3fb_dma_done(void *arg)
+{
+ struct idmac_tx_desc *tx_desc = to_tx_desc(arg);
+ struct dma_chan *chan = tx_desc->txd.chan;
+ struct idmac_channel *ichannel = to_idmac_chan(chan);
+ struct mx3fb_data *mx3fb = ichannel->client;
+ struct mx3fb_info *mx3_fbi = mx3fb->fbi->par;
+ struct mx3fb_info *mx3_fbi_ovl = mx3fb->fbi_ovl ? mx3fb->fbi_ovl->par :
+ NULL;
+ struct mx3fb_info *mx3_fbi_cur;
+
+ dev_dbg(mx3fb->dev, "irq %d callback\n", ichannel->eof_irq);
+
+ if (ichannel == mx3_fbi->idmac_channel) {
+ mx3_fbi_cur = mx3_fbi;
+ } else if (mx3_fbi_ovl && ichannel == mx3_fbi_ovl->idmac_channel) {
+ mx3_fbi_cur = mx3_fbi_ovl;
+ } else {
+ WARN(1, "Cannot identify channel!\n");
+ return;
+ }
+
+ /* We only need one interrupt, it will be re-enabled as needed */
+ disable_irq(ichannel->eof_irq);
+
+ complete(&mx3_fbi_cur->flip_cmpl);
+}
+
+/*
+ * Set framebuffer parameters and change the operating mode. This function is
+ * either called at the very beginning when setting things up, or it cleans up
+ * any existing buffer settings itself
+ *
+ * @param fbi framebuffer information pointer
+ */
+static int mx3fb_set_par(struct fb_info *fbi)
+{
+ u32 mem_len;
+ struct ipu_di_signal_cfg sig_cfg;
+ enum ipu_panel mode = IPU_PANEL_TFT;
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;
+ struct idmac_channel *ichan = mx3_fbi->idmac_channel;
+ struct idmac_video_param *video = &ichan->params.video;
+ struct scatterlist *sg = mx3_fbi->sg;
+ size_t screen_size;
+
+ dev_dbg(mx3fb->dev, "%s [%c]\n", __func__, list_empty(&ichan->queue) ? '-' : '+');
+
+ mutex_lock(&mx3_fbi->mutex);
+
+ /* Total cleanup */
+ if (mx3_fbi->txd)
+ sdc_disable_channel(mx3_fbi);
+
+ mx3fb_set_fix(fbi);
+
+ mem_len = fbi->var.yres_virtual * fbi->fix.line_length;
+ if (mem_len > fbi->fix.smem_len) {
+ if (fbi->fix.smem_start)
+ mx3fb_unmap_video_memory(fbi);
+
+ fbi->fix.smem_len = mem_len;
+ if (mx3fb_map_video_memory(fbi) < 0) {
+ mutex_unlock(&mx3_fbi->mutex);
+ return -ENOMEM;
+ }
+ }
+
+ screen_size = fbi->fix.line_length * fbi->var.yres;
+
+ sg_init_table(&sg[0], 1);
+ sg_init_table(&sg[1], 1);
+
+ sg_dma_address(&sg[0]) = fbi->fix.smem_start;
+ sg_set_page(&sg[0], virt_to_page(fbi->screen_base),
+ fbi->fix.smem_len,
+ offset_in_page(fbi->screen_base));
+
+ if (mx3_fbi->ipu_ch == IDMAC_SDC_0) {
+ memset(&sig_cfg, 0, sizeof(sig_cfg));
+ if (fbi->var.sync & FB_SYNC_HOR_HIGH_ACT)
+ sig_cfg.Hsync_pol = true;
+ if (fbi->var.sync & FB_SYNC_VERT_HIGH_ACT)
+ sig_cfg.Vsync_pol = true;
+ if (fbi->var.sync & FB_SYNC_CLK_INVERT)
+ sig_cfg.clk_pol = true;
+ if (fbi->var.sync & FB_SYNC_DATA_INVERT)
+ sig_cfg.data_pol = true;
+ if (fbi->var.sync & FB_SYNC_OE_ACT_HIGH)
+ sig_cfg.enable_pol = true;
+ if (fbi->var.sync & FB_SYNC_CLK_IDLE_EN)
+ sig_cfg.clkidle_en = true;
+ if (fbi->var.sync & FB_SYNC_CLK_SEL_EN)
+ sig_cfg.clksel_en = true;
+ if (fbi->var.sync & FB_SYNC_SHARP_MODE)
+ mode = IPU_PANEL_SHARP_TFT;
+
+ dev_dbg(fbi->device, "pixclock = %ul Hz\n",
+ (u32) (PICOS2KHZ(fbi->var.pixclock) * 1000UL));
+
+ if (sdc_init_panel(mx3fb, mode,
+ (PICOS2KHZ(fbi->var.pixclock)) * 1000UL,
+ fbi->var.xres, fbi->var.yres,
+ (fbi->var.sync & FB_SYNC_SWAP_RGB) ?
+ IPU_PIX_FMT_BGR666 : IPU_PIX_FMT_RGB666,
+ fbi->var.left_margin,
+ fbi->var.hsync_len,
+ fbi->var.right_margin +
+ fbi->var.hsync_len,
+ fbi->var.upper_margin,
+ fbi->var.vsync_len,
+ fbi->var.lower_margin +
+ fbi->var.vsync_len, sig_cfg) != 0) {
+ mutex_unlock(&mx3_fbi->mutex);
+ dev_err(fbi->device,
+ "mx3fb: Error initializing panel.\n");
+ return -EINVAL;
+ }
+ }
+
+ sdc_set_window_pos(mx3fb, mx3_fbi->ipu_ch, 0, 0);
+
+ mx3_fbi->cur_ipu_buf = 0;
+
+ video->out_pixel_fmt = bpp_to_pixfmt(fbi->var.bits_per_pixel);
+ video->out_width = fbi->var.xres;
+ video->out_height = fbi->var.yres;
+ video->out_stride = fbi->var.xres_virtual;
+
+ if (mx3_fbi->blank == FB_BLANK_UNBLANK)
+ sdc_enable_channel(mx3_fbi);
+
+ mutex_unlock(&mx3_fbi->mutex);
+
+ return 0;
+}
+
+/*
+ * Check framebuffer variable parameters and adjust to valid values.
+ *
+ * @param var framebuffer variable parameters
+ *
+ * @param info framebuffer information pointer
+ */
+static int mx3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi)
+{
+ u32 vtotal;
+ u32 htotal;
+
+ dev_dbg(fbi->device, "%s\n", __func__);
+
+ if (var->xres_virtual < var->xres)
+ var->xres_virtual = var->xres;
+ if (var->yres_virtual < var->yres)
+ var->yres_virtual = var->yres;
+
+ if ((var->bits_per_pixel != 32) && (var->bits_per_pixel != 24) &&
+ (var->bits_per_pixel != 16))
+ var->bits_per_pixel = default_bpp;
+
+ switch (var->bits_per_pixel) {
+ case 16:
+ var->red.length = 5;
+ var->red.offset = 11;
+ var->red.msb_right = 0;
+
+ var->green.length = 6;
+ var->green.offset = 5;
+ var->green.msb_right = 0;
+
+ var->blue.length = 5;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 24:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 0;
+ var->transp.offset = 0;
+ var->transp.msb_right = 0;
+ break;
+ case 32:
+ var->red.length = 8;
+ var->red.offset = 16;
+ var->red.msb_right = 0;
+
+ var->green.length = 8;
+ var->green.offset = 8;
+ var->green.msb_right = 0;
+
+ var->blue.length = 8;
+ var->blue.offset = 0;
+ var->blue.msb_right = 0;
+
+ var->transp.length = 8;
+ var->transp.offset = 24;
+ var->transp.msb_right = 0;
+ break;
+ }
+
+ if (var->pixclock < 1000) {
+ htotal = var->xres + var->right_margin + var->hsync_len +
+ var->left_margin;
+ vtotal = var->yres + var->lower_margin + var->vsync_len +
+ var->upper_margin;
+ var->pixclock = (vtotal * htotal * 6UL) / 100UL;
+ var->pixclock = KHZ2PICOS(var->pixclock);
+ dev_dbg(fbi->device, "pixclock set for 60Hz refresh = %u ps\n",
+ var->pixclock);
+ }
+
+ var->height = -1;
+ var->width = -1;
+ var->grayscale = 0;
+
+ /* Copy nonstd field to/from sync for fbset usage */
+ var->sync |= var->nonstd;
+ var->nonstd |= var->sync;
+
+ return 0;
+}
+
+static u_int _chan_to_field(u_int chan, struct fb_bitfield *bf)
+{
+ chan &= 0xffff;
+ chan >>= 16 - bf->length;
+ return chan << bf->offset;
+}
+
+static int mx3fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
+ u_int trans, struct fb_info *fbi)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ unsigned int val;
+ int ret = 1;
+
+ dev_dbg(fbi->device, "%s\n", __func__);
+
+ mutex_lock(&mx3_fbi->mutex);
+ /*
+ * If greyscale is true, then we convert the RGB value
+ * to greyscale no matter what visual we are using.
+ */
+ if (fbi->var.grayscale)
+ red = green = blue = (19595 * red + 38470 * green +
+ 7471 * blue) >> 16;
+ switch (fbi->fix.visual) {
+ case FB_VISUAL_TRUECOLOR:
+ /*
+ * 16-bit True Colour. We encode the RGB value
+ * according to the RGB bitfield information.
+ */
+ if (regno < 16) {
+ u32 *pal = fbi->pseudo_palette;
+
+ val = _chan_to_field(red, &fbi->var.red);
+ val |= _chan_to_field(green, &fbi->var.green);
+ val |= _chan_to_field(blue, &fbi->var.blue);
+
+ pal[regno] = val;
+
+ ret = 0;
+ }
+ break;
+
+ case FB_VISUAL_STATIC_PSEUDOCOLOR:
+ case FB_VISUAL_PSEUDOCOLOR:
+ break;
+ }
+ mutex_unlock(&mx3_fbi->mutex);
+
+ return ret;
+}
+
+/*
+ * Function to handle custom ioctls for MX3 framebuffer.
+ *
+ * @param inode inode struct
+ *
+ * @param file file struct
+ *
+ * @param cmd Ioctl command to handle
+ *
+ * @param arg User pointer to command arguments
+ *
+ * @param fbi framebuffer information pointer
+ */
+static int mx3fb_ioctl_ovl(struct fb_info *fbi, unsigned int cmd,
+ unsigned long arg)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ int retval = 0;
+ int __user *argp = (void __user *)arg;
+ struct mx3fb_alloc_list *mem;
+ int size;
+ unsigned long offset;
+
+ switch (cmd) {
+ case FBIO_ALLOC:
+ if (get_user(size, argp))
+ return -EFAULT;
+
+ mem = kzalloc(sizeof(*mem), GFP_KERNEL);
+ if (mem == NULL)
+ return -ENOMEM;
+
+ mem->size = PAGE_ALIGN(size);
+
+ mem->cpu_addr = dma_alloc_coherent(fbi->device, size,
+ &mem->phy_addr,
+ GFP_DMA);
+ if (mem->cpu_addr == NULL) {
+ kfree(mem);
+ return -ENOMEM;
+ }
+
+ mutex_lock(&mx3_fbi->mutex);
+ list_add(&mem->list, &fb_alloc_list);
+ mutex_unlock(&mx3_fbi->mutex);
+
+ dev_dbg(fbi->device, "allocated %d bytes @ 0x%08X\n",
+ mem->size, mem->phy_addr);
+
+ if (put_user(mem->phy_addr, argp))
+ return -EFAULT;
+
+ break;
+ case FBIO_FREE:
+ if (get_user(offset, argp))
+ return -EFAULT;
+
+ retval = -EINVAL;
+ mutex_lock(&mx3_fbi->mutex);
+ list_for_each_entry(mem, &fb_alloc_list, list) {
+ if (mem->phy_addr == offset) {
+ list_del(&mem->list);
+ dma_free_coherent(fbi->device,
+ mem->size,
+ mem->cpu_addr,
+ mem->phy_addr);
+ kfree(mem);
+ retval = 0;
+ break;
+ }
+ }
+ mutex_unlock(&mx3_fbi->mutex);
+
+ break;
+ default:
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+
+/*
+ * mx3fb_blank():
+ * Blank the display.
+ */
+static int mx3fb_blank(int blank, struct fb_info *fbi)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ struct mx3fb_data *mx3fb = mx3_fbi->mx3fb;
+
+ dev_dbg(fbi->device, "%s\n", __func__);
+
+ dev_dbg(fbi->device, "blank = %d\n", blank);
+
+ if (mx3_fbi->blank == blank)
+ return 0;
+
+ mutex_lock(&mx3_fbi->mutex);
+ mx3_fbi->blank = blank;
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ sdc_disable_channel(mx3_fbi);
+ sdc_set_brightness(mx3fb, 0);
+ break;
+ case FB_BLANK_UNBLANK:
+ sdc_enable_channel(mx3_fbi);
+ sdc_set_brightness(mx3fb, mx3fb->backlight_level);
+ break;
+ }
+ mutex_unlock(&mx3_fbi->mutex);
+
+ return 0;
+}
+
+/*
+ * mx3fb_blank_ovl():
+ * Blank the display.
+ */
+static int mx3fb_blank_ovl(int blank, struct fb_info *fbi)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+
+ dev_dbg(fbi->device, "ovl blank = %d\n", blank);
+
+ if (mx3_fbi->blank == blank)
+ return 0;
+
+ mutex_lock(&mx3_fbi->mutex);
+ mx3_fbi->blank = blank;
+
+ switch (blank) {
+ case FB_BLANK_POWERDOWN:
+ case FB_BLANK_VSYNC_SUSPEND:
+ case FB_BLANK_HSYNC_SUSPEND:
+ case FB_BLANK_NORMAL:
+ sdc_disable_channel(mx3_fbi);
+ break;
+ case FB_BLANK_UNBLANK:
+ sdc_enable_channel(mx3_fbi);
+ break;
+ }
+ mutex_unlock(&mx3_fbi->mutex);
+
+ return 0;
+}
+
+/*
+ * Pan or Wrap the Display
+ *
+ * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
+ *
+ * @param var Variable screen buffer information
+ * @param info Framebuffer information pointer
+ */
+static int mx3fb_pan_display(struct fb_var_screeninfo *var,
+ struct fb_info *fbi)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ u32 y_bottom;
+ unsigned long base;
+ off_t offset;
+ dma_cookie_t cookie;
+ struct scatterlist *sg = mx3_fbi->sg;
+ struct dma_chan *dma_chan = &mx3_fbi->idmac_channel->dma_chan;
+ struct dma_async_tx_descriptor *txd;
+ int ret;
+
+ dev_dbg(fbi->device, "%s [%c]\n", __func__,
+ list_empty(&mx3_fbi->idmac_channel->queue) ? '-' : '+');
+
+ if (var->xoffset > 0) {
+ dev_dbg(fbi->device, "x panning not supported\n");
+ return -EINVAL;
+ }
+
+ if (fbi->var.xoffset == var->xoffset &&
+ fbi->var.yoffset == var->yoffset)
+ return 0; /* No change, do nothing */
+
+ y_bottom = var->yoffset;
+
+ if (!(var->vmode & FB_VMODE_YWRAP))
+ y_bottom += var->yres;
+
+ if (y_bottom > fbi->var.yres_virtual)
+ return -EINVAL;
+
+ mutex_lock(&mx3_fbi->mutex);
+
+ offset = (var->yoffset * var->xres_virtual + var->xoffset) *
+ (var->bits_per_pixel / 8);
+ base = fbi->fix.smem_start + offset;
+
+ dev_dbg(fbi->device, "Updating SDC BG buf %d address=0x%08lX\n",
+ mx3_fbi->cur_ipu_buf, base);
+
+ /*
+ * We enable the End of Frame interrupt, which will free a tx-descriptor,
+ * which we will need for the next device_prep_slave_sg(). The
+ * IRQ-handler will disable the IRQ again.
+ */
+ init_completion(&mx3_fbi->flip_cmpl);
+ enable_irq(mx3_fbi->idmac_channel->eof_irq);
+
+ ret = wait_for_completion_timeout(&mx3_fbi->flip_cmpl, HZ / 10);
+ if (ret <= 0) {
+ mutex_unlock(&mx3_fbi->mutex);
+ dev_info(fbi->device, "Panning failed due to %s\n", ret < 0 ?
+ "user interrupt" : "timeout");
+ return ret ? : -ETIMEDOUT;
+ }
+
+ mx3_fbi->cur_ipu_buf = !mx3_fbi->cur_ipu_buf;
+
+ sg_dma_address(&sg[mx3_fbi->cur_ipu_buf]) = base;
+ sg_set_page(&sg[mx3_fbi->cur_ipu_buf],
+ virt_to_page(fbi->screen_base + offset), fbi->fix.smem_len,
+ offset_in_page(fbi->screen_base + offset));
+
+ txd = dma_chan->device->device_prep_slave_sg(dma_chan, sg +
+ mx3_fbi->cur_ipu_buf, 1, DMA_TO_DEVICE, DMA_PREP_INTERRUPT);
+ if (!txd) {
+ dev_err(fbi->device,
+ "Error preparing a DMA transaction descriptor.\n");
+ mutex_unlock(&mx3_fbi->mutex);
+ return -EIO;
+ }
+
+ txd->callback_param = txd;
+ txd->callback = mx3fb_dma_done;
+
+ /*
+ * Emulate original mx3fb behaviour: each new call to idmac_tx_submit()
+ * should switch to another buffer
+ */
+ cookie = txd->tx_submit(txd);
+ dev_dbg(fbi->device, "%d: Submit %p #%d\n", __LINE__, txd, cookie);
+ if (cookie < 0) {
+ dev_err(fbi->device,
+ "Error updating SDC buf %d to address=0x%08lX\n",
+ mx3_fbi->cur_ipu_buf, base);
+ mutex_unlock(&mx3_fbi->mutex);
+ return -EIO;
+ }
+
+ if (mx3_fbi->txd)
+ async_tx_ack(mx3_fbi->txd);
+ mx3_fbi->txd = txd;
+
+ fbi->var.xoffset = var->xoffset;
+ fbi->var.yoffset = var->yoffset;
+
+ if (var->vmode & FB_VMODE_YWRAP)
+ fbi->var.vmode |= FB_VMODE_YWRAP;
+ else
+ fbi->var.vmode &= ~FB_VMODE_YWRAP;
+
+ mutex_unlock(&mx3_fbi->mutex);
+
+ dev_dbg(fbi->device, "Update complete\n");
+
+ return 0;
+}
+
+/*
+ * Function to handle custom mmap for MX3 overlay framebuffer.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @param vma Pointer to vm_area_struct
+ */
+static int mx3fb_mmap(struct fb_info *fbi, struct vm_area_struct *vma)
+{
+ struct mx3fb_info *mx3_fbi = fbi->par;
+ bool found = false;
+ size_t len;
+ unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
+ struct mx3fb_alloc_list *mem;
+
+ mutex_lock(&mx3_fbi->mutex);
+
+ if (offset < fbi->fix.smem_len) {
+ /* mapping framebuffer memory */
+ len = fbi->fix.smem_len - offset;
+ vma->vm_pgoff = (fbi->fix.smem_start + offset) >> PAGE_SHIFT;
+ } else {
+ list_for_each_entry(mem, &fb_alloc_list, list) {
+ if (offset == mem->phy_addr) {
+ found = true;
+ len = mem->size;
+ break;
+ }
+ }
+ if (!found) {
+ mutex_unlock(&mx3_fbi->mutex);
+ return -EINVAL;
+ }
+ }
+
+ len = PAGE_ALIGN(len);
+ if (vma->vm_end - vma->vm_start > len) {
+ mutex_unlock(&mx3_fbi->mutex);
+ return -EINVAL;
+ }
+
+ /* make buffers write-thru cacheable */
+ vma->vm_page_prot = __pgprot(pgprot_val(vma->vm_page_prot) &
+ ~L_PTE_BUFFERABLE);
+
+ vma->vm_flags |= VM_IO | VM_RESERVED;
+
+ if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vma->vm_end - vma->vm_start, vma->vm_page_prot)) {
+ mutex_unlock(&mx3_fbi->mutex);
+ dev_dbg(fbi->device, "mmap remap_pfn_range failed\n");
+ return -ENOBUFS;
+
+ }
+
+ mutex_unlock(&mx3_fbi->mutex);
+
+ return 0;
+}
+
+/**
+ * This structure contains the pointers to the control functions that are
+ * invoked by the core framebuffer driver to perform operations like
+ * blitting, rectangle filling, copy regions and cursor definition.
+ */
+static struct fb_ops mx3fb_ops = {
+ .owner = THIS_MODULE,
+ .fb_set_par = mx3fb_set_par,
+ .fb_check_var = mx3fb_check_var,
+ .fb_setcolreg = mx3fb_setcolreg,
+ .fb_pan_display = mx3fb_pan_display,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_blank = mx3fb_blank,
+};
+
+static struct fb_ops mx3fb_ovl_ops = {
+ .owner = THIS_MODULE,
+ .fb_set_par = mx3fb_set_par,
+ .fb_check_var = mx3fb_check_var,
+ .fb_setcolreg = mx3fb_setcolreg,
+ .fb_pan_display = mx3fb_pan_display,
+ .fb_ioctl = mx3fb_ioctl_ovl,
+ .fb_mmap = mx3fb_mmap,
+ .fb_fillrect = cfb_fillrect,
+ .fb_copyarea = cfb_copyarea,
+ .fb_imageblit = cfb_imageblit,
+ .fb_blank = mx3fb_blank_ovl,
+};
+
+#ifdef CONFIG_PM
+/*
+ * Power management hooks. Note that we won't be called from IRQ context,
+ * unlike the blank functions above, so we may sleep.
+ */
+
+/*
+ * Suspends the framebuffer and blanks the screen. Power management support
+ */
+static int mx3fb_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ struct mx3fb_data *drv_data = platform_get_drvdata(pdev);
+ struct mx3fb_info *mx3_fbi = drv_data->fbi->par;
+ struct mx3fb_info *mx3_fbi_ovl = drv_data->fbi_ovl->par;
+
+ acquire_console_sem();
+ fb_set_suspend(drv_data->fbi, 1);
+ fb_set_suspend(drv_data->fbi_ovl, 1);
+ release_console_sem();
+
+ if (mx3_fbi_ovl->blank == FB_BLANK_UNBLANK)
+ sdc_disable_channel(mx3_fbi_ovl);
+
+ if (mx3_fbi->blank == FB_BLANK_UNBLANK) {
+ sdc_disable_channel(mx3_fbi);
+ sdc_set_brightness(mx3fb, 0);
+
+ }
+ return 0;
+}
+
+/*
+ * Resumes the framebuffer and unblanks the screen. Power management support
+ */
+static int mx3fb_resume(struct platform_device *pdev)
+{
+ struct mx3fb_data *drv_data = platform_get_drvdata(pdev);
+ struct mx3fb_info *mx3_fbi = drv_data->fbi->par;
+ struct mx3fb_info *mx3_fbi_ovl = drv_data->fbi_ovl->par;
+
+ if (mx3_fbi->blank == FB_BLANK_UNBLANK) {
+ sdc_enable_channel(mx3_fbi);
+ sdc_set_brightness(mx3fb, drv_data->backlight_level);
+ }
+
+ if (mx3_fbi_ovl->blank == FB_BLANK_UNBLANK)
+ sdc_enable_channel(mx3_fbi_ovl);
+
+ acquire_console_sem();
+ fb_set_suspend(drv_data->fbi, 0);
+ fb_set_suspend(drv_data->fbi_ovl, 0);
+ release_console_sem();
+
+ return 0;
+}
+#else
+#define mx3fb_suspend NULL
+#define mx3fb_resume NULL
+#endif
+
+/*
+ * Main framebuffer functions
+ */
+
+/**
+ * Allocates the DRAM memory for the frame buffer. This buffer is remapped
+ * into a non-cached, non-buffered, memory region to allow palette and pixel
+ * writes to occur without flushing the cache. Once this area is remapped,
+ * all virtual memory access to the video memory should occur at the new region.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @return Error code indicating success or failure
+ */
+static int mx3fb_map_video_memory(struct fb_info *fbi)
+{
+ int retval = 0;
+ dma_addr_t addr;
+
+ fbi->screen_base = dma_alloc_writecombine(fbi->device,
+ fbi->fix.smem_len,
+ &addr, GFP_DMA);
+
+ if (!fbi->screen_base) {
+ dev_err(fbi->device, "Cannot allocate %u bytes framebuffer memory\n",
+ fbi->fix.smem_len);
+ retval = -EBUSY;
+ goto err0;
+ }
+
+ fbi->fix.smem_start = addr;
+
+ dev_dbg(fbi->device, "allocated fb @ p=0x%08x, v=0x%p, size=%d.\n",
+ (uint32_t) fbi->fix.smem_start, fbi->screen_base, fbi->fix.smem_len);
+
+ fbi->screen_size = fbi->fix.smem_len;
+
+ /* Clear the screen */
+ memset((char *)fbi->screen_base, 0, fbi->fix.smem_len);
+
+ return 0;
+
+err0:
+ fbi->fix.smem_len = 0;
+ fbi->fix.smem_start = 0;
+ fbi->screen_base = NULL;
+ return retval;
+}
+
+/**
+ * De-allocates the DRAM memory for the frame buffer.
+ *
+ * @param fbi framebuffer information pointer
+ *
+ * @return Error code indicating success or failure
+ */
+static int mx3fb_unmap_video_memory(struct fb_info *fbi)
+{
+ dma_free_writecombine(fbi->device, fbi->fix.smem_len,
+ fbi->screen_base, fbi->fix.smem_start);
+
+ fbi->screen_base = 0;
+ fbi->fix.smem_start = 0;
+ fbi->fix.smem_len = 0;
+ return 0;
+}
+
+/**
+ * Initializes the framebuffer information pointer. After allocating
+ * sufficient memory for the framebuffer structure, the fields are
+ * filled with custom information passed in from the configurable
+ * structures. This includes information such as bits per pixel,
+ * color maps, screen width/height and RGBA offsets.
+ *
+ * @return Framebuffer structure initialized with our information
+ */
+static struct fb_info *mx3fb_init_fbinfo(struct device *dev, struct fb_ops *ops)
+{
+ struct fb_info *fbi;
+ struct mx3fb_info *mx3fbi;
+ int ret;
+
+ /*
+ * Allocate sufficient memory for the fb structure
+ */
+ fbi = framebuffer_alloc(sizeof(struct mx3fb_info), dev);
+ if (!fbi)
+ return NULL;
+
+ mx3fbi = fbi->par;
+ mx3fbi->cookie = -EINVAL;
+ mx3fbi->cur_ipu_buf = 0;
+
+ fbi->var.activate = FB_ACTIVATE_NOW;
+
+ fbi->fbops = ops;
+ fbi->flags = FBINFO_FLAG_DEFAULT;
+ fbi->pseudo_palette = mx3fbi->pseudo_palette;
+
+ mutex_init(&mx3fbi->mutex);
+
+ /*
+ * Allocate colormap
+ */
+ ret = fb_alloc_cmap(&fbi->cmap, 16, 0);
+ if (ret < 0) {
+ framebuffer_release(fbi);
+ return NULL;
+ }
+
+ return fbi;
+}
+
+static int init_fb_chan(struct mx3fb_data *mx3fb, struct idmac_channel *ichan)
+{
+ struct device *dev = mx3fb->dev;
+ struct mx3fb_platform_data *mx3fb_pdata = dev->platform_data;
+ const char *name = mx3fb_pdata->name;
+ unsigned int irq;
+ struct fb_info *fbi;
+ struct mx3fb_info *mx3fbi;
+ const struct fb_videomode *mode;
+ int ret, num_modes;
+
+ irq = ichan->eof_irq;
+
+ switch (ichan->dma_chan.chan_id) {
+ case IDMAC_SDC_0:
+ fbi = mx3fb_init_fbinfo(dev, &mx3fb_ops);
+ if (!fbi)
+ return -ENOMEM;
+
+ if (!fb_mode)
+ fb_mode = name;
+
+ if (!fb_mode) {
+ ret = -EINVAL;
+ goto emode;
+ }
+
+ if (mx3fb_pdata->mode && mx3fb_pdata->num_modes) {
+ mode = mx3fb_pdata->mode;
+ num_modes = mx3fb_pdata->num_modes;
+ } else {
+ mode = mx3fb_modedb;
+ num_modes = ARRAY_SIZE(mx3fb_modedb);
+ }
+
+ if (!fb_find_mode(&fbi->var, fbi, fb_mode, mode,
+ num_modes, NULL, default_bpp)) {
+ ret = -EBUSY;
+ goto emode;
+ }
+
+ fb_videomode_to_modelist(mode, num_modes, &fbi->modelist);
+
+ /* Default Y virtual size is 2x panel size */
+ fbi->var.yres_virtual = fbi->var.yres * 2;
+
+ mx3fb->fbi = fbi;
+
+ /* set Display Interface clock period */
+ mx3fb_write_reg(mx3fb, 0x00100010L, DI_HSP_CLK_PER);
+ /* Might need to trigger HSP clock change - see 44.3.3.8.5 */
+
+ sdc_set_brightness(mx3fb, 255);
+ sdc_set_global_alpha(mx3fb, true, 0xFF);
+ sdc_set_color_key(mx3fb, IDMAC_SDC_0, false, 0);
+
+ break;
+ case IDMAC_SDC_1:
+ /* We know, that background has been allocated already! */
+ fbi = mx3fb_init_fbinfo(dev, &mx3fb_ovl_ops);
+ if (!fbi)
+ return -ENOMEM;
+
+ /* Default Y virtual size is 2x panel size */
+ fbi->var = mx3fb->fbi->var;
+ /* This shouldn't be necessary, it is already set up above */
+ fbi->var.yres_virtual = mx3fb->fbi->var.yres * 2;
+
+ mx3fb->fbi_ovl = fbi;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ mx3fbi = fbi->par;
+ mx3fbi->idmac_channel = ichan;
+ mx3fbi->ipu_ch = ichan->dma_chan.chan_id;
+ mx3fbi->mx3fb = mx3fb;
+ mx3fbi->blank = FB_BLANK_NORMAL;
+
+ init_completion(&mx3fbi->flip_cmpl);
+ disable_irq(ichan->eof_irq);
+ dev_dbg(mx3fb->dev, "disabling irq %d\n", ichan->eof_irq);
+ ret = mx3fb_set_par(fbi);
+ if (ret < 0)
+ goto esetpar;
+
+ /* Overlay stays blanked by default */
+ if (ichan->dma_chan.chan_id == IDMAC_SDC_0) {
+ mx3fb_blank(FB_BLANK_UNBLANK, fbi);
+
+ dev_info(dev, "mx3fb: fb registered, using mode %s [%c]\n",
+ fb_mode, list_empty(&ichan->queue) ? '-' : '+');
+ }
+
+
+ ret = register_framebuffer(fbi);
+ if (ret < 0)
+ goto erfb;
+
+ return 0;
+
+erfb:
+esetpar:
+emode:
+ fb_dealloc_cmap(&fbi->cmap);
+ framebuffer_release(fbi);
+
+ return ret;
+}
+
+static bool chan_filter(struct dma_chan *chan, void *arg)
+{
+ struct dma_chan_request *rq = arg;
+ struct device *dev;
+ struct mx3fb_platform_data *mx3fb_pdata;
+
+ if (!rq)
+ return false;
+
+ dev = rq->mx3fb->dev;
+ mx3fb_pdata = dev->platform_data;
+
+ return rq->id == chan->chan_id &&
+ mx3fb_pdata->dma_dev == chan->device->dev;
+}
+
+static void release_fbi(struct fb_info *fbi)
+{
+ mx3fb_unmap_video_memory(fbi);
+
+ fb_dealloc_cmap(&fbi->cmap);
+
+ unregister_framebuffer(fbi);
+ framebuffer_release(fbi);
+}
+
+/**
+ * Probe routine for the framebuffer driver. It is called during the
+ * driver binding process. The following functions are performed in
+ * this routine: Framebuffer initialization, Memory allocation and
+ * mapping, Framebuffer registration, IPU initialization.
+ *
+ * @return Appropriate error code to the kernel common code
+ */
+static int mx3fb_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ int ret;
+ struct resource *sdc_reg;
+ struct mx3fb_data *mx3fb;
+ dma_cap_mask_t mask;
+ struct dma_chan *chan;
+ struct dma_chan_request rq;
+
+ /*
+ * Display Interface (DI) and Synchronous Display Controller (SDC)
+ * registers
+ */
+ sdc_reg = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!sdc_reg)
+ return -EINVAL;
+
+ mx3fb = kzalloc(sizeof(*mx3fb), GFP_KERNEL);
+ if (!mx3fb)
+ return -ENOMEM;
+
+ spin_lock_init(&mx3fb->lock);
+
+ mx3fb->reg_base = ioremap(sdc_reg->start, resource_size(sdc_reg));
+ if (!mx3fb->reg_base) {
+ ret = -ENOMEM;
+ goto eremap;
+ }
+
+ pr_debug("Remapped %x to %x at %p\n", sdc_reg->start, sdc_reg->end,
+ mx3fb->reg_base);
+
+ /* IDMAC interface */
+ dmaengine_get();
+
+ mx3fb->dev = dev;
+ platform_set_drvdata(pdev, mx3fb);
+
+ rq.mx3fb = mx3fb;
+
+ /*
+ * Request background - it must be the first to guarantee channel
+ * requesting order. If background allocation fails, there is no
+ * reason to allocate overlay, however, if overlay fails, we still
+ * can use background.
+ */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_PRIVATE, mask);
+ rq.id = IDMAC_SDC_0;
+ chan = dma_request_channel(mask, chan_filter, &rq);
+ if (!chan) {
+ ret = -EBUSY;
+ goto ersdc0;
+ }
+ mx3fb->idmac_channel[0] = to_idmac_chan(chan);
+ mx3fb->idmac_channel[0]->client = mx3fb;
+
+ ret = init_fb_chan(mx3fb, to_idmac_chan(chan));
+ if (ret < 0)
+ goto eisdc0;
+
+ /* ...and foreground channels */
+ dma_cap_zero(mask);
+ dma_cap_set(DMA_SLAVE, mask);
+ dma_cap_set(DMA_PRIVATE, mask);
+ rq.id = IDMAC_SDC_1;
+ chan = dma_request_channel(mask, chan_filter, &rq);
+ if (!chan) {
+ ret = -EBUSY;
+ goto ersdc1;
+ }
+ mx3fb->idmac_channel[1] = to_idmac_chan(chan);
+ mx3fb->idmac_channel[1]->client = mx3fb;
+
+ ret = init_fb_chan(mx3fb, to_idmac_chan(chan));
+ if (ret < 0)
+ goto eisdc1;
+
+ mx3fb->backlight_level = 255;
+
+ return 0;
+
+eisdc1:
+ dma_release_channel(&mx3fb->idmac_channel[1]->dma_chan);
+ersdc1:
+ release_fbi(mx3fb->fbi);
+eisdc0:
+ dma_release_channel(&mx3fb->idmac_channel[0]->dma_chan);
+ersdc0:
+ dmaengine_put();
+ iounmap(mx3fb->reg_base);
+eremap:
+ kfree(mx3fb);
+ dev_err(dev, "mx3fb: failed to register fb\n");
+ return ret;
+}
+
+static int mx3fb_remove(struct platform_device *dev)
+{
+ struct mx3fb_data *mx3fb = platform_get_drvdata(dev);
+ struct fb_info *fbi = mx3fb->fbi;
+
+ if (fbi)
+ release_fbi(fbi);
+
+ fbi = mx3fb->fbi_ovl;
+ if (fbi)
+ release_fbi(fbi);
+
+ dma_release_channel(&mx3fb->idmac_channel[1]->dma_chan);
+ dma_release_channel(&mx3fb->idmac_channel[0]->dma_chan);
+ dmaengine_put();
+
+ iounmap(mx3fb->reg_base);
+ kfree(mx3fb);
+ return 0;
+}
+
+static struct platform_driver mx3fb_driver = {
+ .driver = {
+ .name = MX3FB_NAME,
+ },
+ .probe = mx3fb_probe,
+ .remove = mx3fb_remove,
+ .suspend = mx3fb_suspend,
+ .resume = mx3fb_resume,
+};
+
+/*
+ * Parse user specified options (`video=mx3fb:')
+ * example:
+ * video=mx3fb:bpp=16
+ */
+static int mx3fb_setup(char *options)
+{
+ char *opt;
+ if (!options || !*options)
+ return 0;
+ while ((opt = strsep(&options, ",")) != NULL) {
+ if (!*opt)
+ continue;
+ if (!strncmp(opt, "bpp=", 4))
+ default_bpp = simple_strtoul(opt + 4, NULL, 0);
+ else
+ fb_mode = opt;
+ }
+ return 0;
+}
+
+static int __init mx3fb_init(void)
+{
+ int ret = 0;
+#ifndef MODULE
+ char *option = NULL;
+
+ if (fb_get_options("mx3fb", &option))
+ return -ENODEV;
+ mx3fb_setup(option);
+#endif
+
+ ret = platform_driver_register(&mx3fb_driver);
+ return ret;
+}
+
+static void __exit mx3fb_exit(void)
+{
+ platform_driver_unregister(&mx3fb_driver);
+}
+
+module_init(mx3fb_init);
+module_exit(mx3fb_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MX3 framebuffer driver");
+MODULE_ALIAS("platform:" MX3FB_NAME);
--
1.5.4