From patchwork Tue Dec 15 14:01:26 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alan Carvalho de Assis X-Patchwork-Id: 67636 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id nBFE1a47001187 for ; Tue, 15 Dec 2009 14:01:36 GMT Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1760249AbZLOOBf (ORCPT ); Tue, 15 Dec 2009 09:01:35 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1760247AbZLOOBf (ORCPT ); Tue, 15 Dec 2009 09:01:35 -0500 Received: from mail-yw0-f182.google.com ([209.85.211.182]:49278 "EHLO mail-yw0-f182.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753994AbZLOOBd (ORCPT ); Tue, 15 Dec 2009 09:01:33 -0500 Received: by ywh12 with SMTP id 12so4370608ywh.21 for ; Tue, 15 Dec 2009 06:01:32 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=domainkey-signature:received:received:from:to:cc:subject:date :message-id:x-mailer; bh=bUsSmtSBPoSKjD5SS7f/wI5AFKtCyhJ/xgabJJz160U=; b=qv67ni/NBQX8bFvf+PlKDlCnIdQXvl6y0+3aadXaB9DA+HaVCVtsFcj33TNX3ffhUN wVTcLsJsIkA0ZvHWEeqrv0gKkGr6Loo8/+F252sNGKftFkU913ZeJ2FY2j0Re+LvdOIA 1oHImhMz5Ty1P8Koi+DT0O/IJsAx7Yjh838m8= DomainKey-Signature: a=rsa-sha1; c=nofws; d=gmail.com; s=gamma; h=from:to:cc:subject:date:message-id:x-mailer; b=VDWLPOpG7sV5N+E7OdSwLvS/qengfwHglEH0WDIkjNfkaLcemSdErGfPFF5r54/lP0 BKXvwlmlgryug9OoI6cVQUFZZXJGGLeI4mJyqBgxR3kFtSnPpBYV24kx4KBYNQIWx6MS mlg+WCvFxjUYnEpzbJ4TMeEteE72Sykq0TBIE= Received: by 10.90.40.17 with SMTP id n17mr1901439agn.16.1260885692274; Tue, 15 Dec 2009 06:01:32 -0800 (PST) Received: from localhost.localdomain ([200.251.188.130]) by mx.google.com with ESMTPS id 7sm1935035ywf.55.2009.12.15.06.01.29 (version=TLSv1/SSLv3 cipher=RC4-MD5); Tue, 15 Dec 2009 06:01:31 -0800 (PST) From: Alan Carvalho de Assis To: linux-media@vger.kernel.org Cc: s.hauer@pengutronix.de, mchehab@infradead.org, Alan Carvalho de Assis Subject: [PATCH] RFC: mx27: Add soc_camera support Date: Tue, 15 Dec 2009 12:01:26 -0200 Message-Id: <1260885686-8478-1-git-send-email-acassis@gmail.com> X-Mailer: git-send-email 1.6.0.4 Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org diff --git a/arch/arm/mach-mx2/clock_imx27.c b/arch/arm/mach-mx2/clock_imx27.c index b010bf9..1ad0408 100644 --- a/arch/arm/mach-mx2/clock_imx27.c +++ b/arch/arm/mach-mx2/clock_imx27.c @@ -642,7 +642,7 @@ static struct clk_lookup lookups[] = { _REGISTER_CLOCK("spi_imx.1", NULL, cspi2_clk) _REGISTER_CLOCK("spi_imx.2", NULL, cspi3_clk) _REGISTER_CLOCK("imx-fb.0", NULL, lcdc_clk) - _REGISTER_CLOCK(NULL, "csi", csi_clk) + _REGISTER_CLOCK("mx27-camera.0", NULL, csi_clk) _REGISTER_CLOCK("fsl-usb2-udc", "usb", usb_clk) _REGISTER_CLOCK("fsl-usb2-udc", "usb_ahb", usb_clk1) _REGISTER_CLOCK("mxc-ehci.0", "usb", usb_clk) diff --git a/arch/arm/mach-mx2/devices.c b/arch/arm/mach-mx2/devices.c index 3d398ce..d47ea55 100644 --- a/arch/arm/mach-mx2/devices.c +++ b/arch/arm/mach-mx2/devices.c @@ -31,6 +31,7 @@ #include #include #include +#include #include #include @@ -39,6 +40,37 @@ #include "devices.h" +#ifdef CONFIG_MACH_MX27 +static struct resource mx27_camera_resources[] = { + { + .start = CSI_BASE_ADDR, + .end = CSI_BASE_ADDR + 0x1f, + .flags = IORESOURCE_MEM, + }, { + .start = EMMA_PRP_BASE_ADDR, + .end = EMMA_PRP_BASE_ADDR + 0x1f, + .flags = IORESOURCE_MEM, + }, { + .start = MXC_INT_CSI, + .end = MXC_INT_CSI, + .flags = IORESOURCE_IRQ, + },{ + .start = MXC_INT_EMMAPRP, + .end = MXC_INT_EMMAPRP, + .flags = IORESOURCE_IRQ, + }, +}; +struct platform_device mx27_camera_device = { + .name = "mx27-camera", + .id = 0, + .num_resources = ARRAY_SIZE(mx27_camera_resources), + .resource = mx27_camera_resources, + .dev = { + .coherent_dma_mask = 0xffffffff, + }, +}; +#endif + /* * SPI master controller * diff --git a/arch/arm/mach-mx2/devices.h b/arch/arm/mach-mx2/devices.h index 97306aa..58ce4dc 100644 --- a/arch/arm/mach-mx2/devices.h +++ b/arch/arm/mach-mx2/devices.h @@ -20,6 +20,7 @@ extern struct platform_device mxc_i2c_device1; extern struct platform_device mxc_sdhc_device0; extern struct platform_device mxc_sdhc_device1; extern struct platform_device mxc_otg_udc_device; +extern struct platform_device mx27_camera_device; extern struct platform_device mxc_otg_host; extern struct platform_device mxc_usbh1; extern struct platform_device mxc_usbh2; diff --git a/arch/arm/plat-mxc/include/mach/imx_cam.h b/arch/arm/plat-mxc/include/mach/imx_cam.h new file mode 100644 index 0000000..2d704ae --- /dev/null +++ b/arch/arm/plat-mxc/include/mach/imx_cam.h @@ -0,0 +1,47 @@ +/* + imx-cam.h - i.MX27 camera driver header file + + Copyright (C) 2003, Intel Corporation + Copyright (C) 2008, Sascha Hauer + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +*/ + +#ifndef __ASM_ARCH_CAMERA_H_ +#define __ASM_ARCH_CAMERA_H_ + +#define MX27_CAMERA_SWAP16 (1 << 0) +#define MX27_CAMERA_EXT_VSYNC (1 << 1) +#define MX27_CAMERA_CCIR (1 << 2) +#define MX27_CAMERA_CCIR_INTERLACE (1 << 3) +#define MX27_CAMERA_HSYNC_HIGH (1 << 4) +#define MX27_CAMERA_GATED_CLOCK (1 << 5) +#define MX27_CAMERA_INV_DATA (1 << 6) +#define MX27_CAMERA_PCLK_SAMPLE_RISING (1 << 7) +#define MX27_CAMERA_PACK_DIR_MSB (1 << 8) + +struct mx27_camera_platform_data { + int (*init)(struct platform_device *); + int (*exit)(struct platform_device *); + + unsigned long flags; + + unsigned long clk; +}; + +extern int mx27_init_camera(struct mx27_camera_platform_data *); + +#endif /* __ASM_ARCH_CAMERA_H_ */ + diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index e6186b3..8c7c826 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -909,6 +909,19 @@ config VIDEO_OMAP2 ---help--- This is a v4l2 driver for the TI OMAP2 camera capture interface +config MX2_VIDEO + bool + +config VIDEO_MX27 + tristate "i.MX27 Quick Capture Interface driver" + depends on VIDEO_DEV && MACH_MX27 + select SOC_CAMERA + select VIDEOBUF_DMA_CONTIG + select MX2_VIDEO + ---help--- + This is a v4l2 driver for the i.MX27 Capture Interface + + # # USB Multimedia device configuration # diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index e541932..b720c0e 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -118,6 +118,8 @@ obj-$(CONFIG_VIDEO_CX2341X) += cx2341x.o obj-$(CONFIG_VIDEO_CAFE_CCIC) += cafe_ccic.o +obj-$(CONFIG_VIDEO_MXC_IPU_CAMERA) += mxc/capture/ + obj-$(CONFIG_USB_DABUSB) += dabusb.o obj-$(CONFIG_USB_OV511) += ov511.o obj-$(CONFIG_USB_SE401) += se401.o @@ -152,6 +154,7 @@ obj-$(CONFIG_SOC_CAMERA_PLATFORM) += soc_camera_platform.o # soc-camera host drivers have to be linked after camera drivers obj-$(CONFIG_VIDEO_MX1) += mx1_camera.o obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o +obj-$(CONFIG_VIDEO_MX27) += mx27_camera.o obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o diff --git a/drivers/media/video/mx27_camera.c b/drivers/media/video/mx27_camera.c new file mode 100644 index 0000000..426fcf4 --- /dev/null +++ b/drivers/media/video/mx27_camera.c @@ -0,0 +1,1224 @@ +/* + * V4L2 Driver for MX27 camera host + * + * Copyright (C) 2008, Sascha Hauer, Pengutronix + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#define MX27_CAM_DRV_NAME "mx27-camera" +#define MX27_CAM_VERSION_CODE KERNEL_VERSION(0, 0, 5) /* FIXME: Whats this? */ + +static const char *mx27_cam_driver_description = "i.MX27_Camera"; + +/* reset values */ +#define CSICR1_RESET_VAL 0x40000800 +#define CSICR2_RESET_VAL 0x0 +#define CSICR3_RESET_VAL 0x0 + +/* csi control reg 1 */ +#define CSICR1_SWAP16_EN (1 << 31) +#define CSICR1_EXT_VSYNC (1 << 30) +#define CSICR1_EOF_INTEN (1 << 29) +#define CSICR1_PRP_IF_EN (1 << 28) +#define CSICR1_CCIR_MODE (1 << 27) +#define CSICR1_COF_INTEN (1 << 26) +#define CSICR1_SF_OR_INTEN (1 << 25) +#define CSICR1_RF_OR_INTEN (1 << 24) +#define CSICR1_STATFF_LEVEL (3 << 22) +#define CSICR1_STATFF_INTEN (1 << 21) +#define CSICR1_RXFF_LEVEL(l) (((l) & 3) << 19) +#define CSICR1_RXFF_INTEN (1 << 18) +#define CSICR1_SOF_POL (1 << 17) +#define CSICR1_SOF_INTEN (1 << 16) +#define CSICR1_MCLKDIV(d) (((d) & 0xF) << 12) +#define CSICR1_HSYNC_POL (1 << 11) +#define CSICR1_CCIR_EN (1 << 10) +#define CSICR1_MCLKEN (1 << 9) +#define CSICR1_FCC (1 << 8) +#define CSICR1_PACK_DIR (1 << 7) +#define CSICR1_CLR_STATFIFO (1 << 6) +#define CSICR1_CLR_RXFIFO (1 << 5) +#define CSICR1_GCLK_MODE (1 << 4) +#define CSICR1_INV_DATA (1 << 3) +#define CSICR1_INV_PCLK (1 << 2) +#define CSICR1_REDGE (1 << 1) + +#define SHIFT_STATFF_LEVEL 22 +#define SHIFT_RXFF_LEVEL 19 +#define SHIFT_MCLKDIV 12 + +/* control reg 3 */ +#define CSICR3_FRMCNT (0xFFFF << 16) +#define CSICR3_FRMCNT_RST (1 << 15) +#define CSICR3_CSI_SUP (1 << 3) +#define CSICR3_ZERO_PACK_EN (1 << 2) +#define CSICR3_ECC_INT_EN (1 << 1) +#define CSICR3_ECC_AUTO_EN (1 << 0) + +#define SHIFT_FRMCNT 16 + +/* csi status reg */ +#define CSISR_SFF_OR_INT (1 << 25) +#define CSISR_RFF_OR_INT (1 << 24) +#define CSISR_STATFF_INT (1 << 21) +#define CSISR_RXFF_INT (1 << 18) +#define CSISR_EOF_INT (1 << 17) +#define CSISR_SOF_INT (1 << 16) +#define CSISR_F2_INT (1 << 15) +#define CSISR_F1_INT (1 << 14) +#define CSISR_COF_INT (1 << 13) +#define CSISR_ECC_INT (1 << 1) +#define CSISR_DRDY (1 << 0) + +#define CSICR1 0x00 +#define CSICR2 0x04 +#define CSISR 0x08 +#define CSISTATFIFO 0x0c +#define CSIRFIFO 0x10 +#define CSIRXCNT 0x14 +#define CSICR3 0x1C + +/* EMMA PrP */ +#define PRP_CNTL 0x00 +#define PRP_INTR_CNTL 0x04 +#define PRP_INTRSTATUS 0x08 +#define PRP_SOURCE_Y_PTR 0x0c +#define PRP_SOURCE_CB_PTR 0x10 +#define PRP_SOURCE_CR_PTR 0x14 +#define PRP_DEST_RGB1_PTR 0x18 +#define PRP_DEST_RGB2_PTR 0x1c +#define PRP_DEST_Y_PTR 0x20 +#define PRP_DEST_CB_PTR 0x24 +#define PRP_DEST_CR_PTR 0x28 +#define PRP_SRC_FRAME_SIZE 0x2c +#define PRP_DEST_CH1_LINE_STRIDE 0x30 +#define PRP_SRC_PIXEL_FORMAT_CNTL 0x34 +#define PRP_CH1_PIXEL_FORMAT_CNTL 0x38 +#define PRP_CH1_OUT_IMAGE_SIZE 0x3c +#define PRP_CH2_OUT_IMAGE_SIZE 0x40 +#define PRP_SRC_LINE_STRIDE 0x44 +#define PRP_CSC_COEF_012 0x48 +#define PRP_CSC_COEF_345 0x4c +#define PRP_CSC_COEF_678 0x50 +#define PRP_CH1_RZ_HORI_COEF1 0x54 +#define PRP_CH1_RZ_HORI_COEF2 0x58 +#define PRP_CH1_RZ_HORI_VALID 0x5c +#define PRP_CH1_RZ_VERT_COEF1 0x60 +#define PRP_CH1_RZ_VERT_COEF2 0x64 +#define PRP_CH1_RZ_VERT_VALID 0x68 +#define PRP_CH2_RZ_HORI_COEF1 0x6c +#define PRP_CH2_RZ_HORI_COEF2 0x70 +#define PRP_CH2_RZ_HORI_VALID 0x74 +#define PRP_CH2_RZ_VERT_COEF1 0x78 +#define PRP_CH2_RZ_VERT_COEF2 0x7c +#define PRP_CH2_RZ_VERT_VALID 0x80 + +#define PRP_CNTL_CH1EN (1 << 0) +#define PRP_CNTL_CH2EN (1 << 1) +#define PRP_CNTL_CSIEN (1 << 2) +#define PRP_CNTL_DATA_IN_YUV420 (0 << 3) +#define PRP_CNTL_DATA_IN_YUV422 (1 << 3) +#define PRP_CNTL_DATA_IN_RGB16 (2 << 3) +#define PRP_CNTL_DATA_IN_RGB32 (3 << 3) +#define PRP_CNTL_CH1_OUT_RGB8 (0 << 5) +#define PRP_CNTL_CH1_OUT_RGB16 (1 << 5) +#define PRP_CNTL_CH1_OUT_RGB32 (2 << 5) +#define PRP_CNTL_CH1_OUT_YUV422 (3 << 5) +#define PRP_CNTL_CH2_OUT_YUV420 (0 << 7) +#define PRP_CNTL_CH2_OUT_YUV422 (1 << 7) +#define PRP_CNTL_CH2_OUT_YUV444 (2 << 7) +#define PRP_CNTL_CH1_LEN (1 << 9) +#define PRP_CNTL_CH2_LEN (1 << 10) +#define PRP_CNTL_SKIP_FRAME (1 << 11) +#define PRP_CNTL_SWRST (1 << 12) +#define PRP_CNTL_CLKEN (1 << 13) +#define PRP_CNTL_WEN (1 << 14) +#define PRP_CNTL_CH1BYP (1 << 15) +#define PRP_CNTL_IN_TSKIP(x) ((x) << 16) +#define PRP_CNTL_CH1_TSKIP(x) ((x) << 19) +#define PRP_CNTL_CH2_TSKIP(x) ((x) << 22) +#define PRP_CNTL_INPUT_FIFO_LEVEL(x) ((x) << 25) +#define PRP_CNTL_RZ_FIFO_LEVEL(x) ((x) << 27) +#define PRP_CNTL_CH2B1EN (1 << 29) +#define PRP_CNTL_CH2B2EN (1 << 30) +#define PRP_CNTL_CH2FEN (1 << 31) + +/* IRQ Enable and status register */ +#define PRP_INTR_RDERR (1 << 0) +#define PRP_INTR_CH1WERR (1 << 1) +#define PRP_INTR_CH2WERR (1 << 2) +#define PRP_INTR_CH1FC (1 << 3) +#define PRP_INTR_CH2FC (1 << 5) +#define PRP_INTR_LBOVF (1 << 7) +#define PRP_INTR_CH2OVF (1 << 8) + +#define mx27_camera_emma(pcdev) (pcdev->use_emma) + +/* Currently we do not need irqs. All we need is DMA callback + * Leave it here for reference for some time. + */ +#undef MX27_CAMERA_USE_IRQ + +struct mx27_camera_dev { + struct device *dev; + struct soc_camera_device *icd; + struct clk *clk_csi, *clk_emma; + + unsigned int irq_csi, irq_emma; + void __iomem *base_csi, *base_emma; + + struct mx27_camera_platform_data *pdata; + struct resource *res_csi, *res_emma; + unsigned long platform_flags; + + struct list_head capture; + struct list_head active_bufs; + + spinlock_t lock; + + int dma; + struct mx27_buffer *active; + + int use_emma; + + unsigned int csicr1; + + void __iomem *discard_buffer; + dma_addr_t discard_buffer_dma; + size_t discard_size; +}; + +/* buffer for one video frame */ +struct mx27_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + const struct soc_camera_data_format *fmt; + + int bufnum; +}; + +static DEFINE_MUTEX(camera_lock); + +static int mclk_get_divisor(struct mx27_camera_dev *pcdev) +{ + dev_info(pcdev->dev, "%s not implemented. Running at max speed\n", + __func__); + +#if 0 + unsigned int mclk = pcdev->pdata->clk_csi; + unsigned int pclk = clk_get_rate(pcdev->clk_csi); + int i; + + dev_dbg(pcdev->dev, "%s: %ld %ld\n", __func__, mclk, pclk); + + for (i = 0; i < 0xf; i++) + if ((i + 1) * 2 * mclk <= pclk) + break; + return i; +#endif + return 0; +} + +static void mx27_camera_deactivate(struct mx27_camera_dev *pcdev) +{ + clk_disable(pcdev->clk_csi); + writel(0, pcdev->base_csi + CSICR1); + if (mx27_camera_emma(pcdev)) + writel(0, pcdev->base_emma + PRP_CNTL); +} + +/* The following two functions absolutely depend on the fact, that + * there can be only one camera on mx27 quick capture interface */ +static int mx27_camera_add_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct mx27_camera_dev *pcdev = ici->priv; + int ret; + u32 csicr1; + + mutex_lock(&camera_lock); + + if (pcdev->icd) { + ret = -EBUSY; + goto ebusy; + } + + dev_info(&icd->dev, "Camera driver attached to camera %d\n", + icd->devnum); + + clk_enable(pcdev->clk_csi); + + csicr1 = CSICR1_MCLKDIV(mclk_get_divisor(pcdev)) | + CSICR1_MCLKEN; + if (mx27_camera_emma(pcdev)) { + csicr1 |= CSICR1_PRP_IF_EN | CSICR1_FCC | + CSICR1_RXFF_LEVEL(0); + } else + csicr1 |= CSICR1_SOF_INTEN | CSICR1_RXFF_LEVEL(2); + + pcdev->csicr1 = csicr1; + writel(pcdev->csicr1, pcdev->base_csi + CSICR1); + + pcdev->icd = icd; + +ebusy: + mutex_unlock(&camera_lock); + + return ret; +} + +static void mx27_camera_remove_device(struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct mx27_camera_dev *pcdev = ici->priv; + + BUG_ON(icd != pcdev->icd); + + dev_info(&icd->dev, "Camera driver detached from camera %d\n", + icd->devnum); + + mx27_camera_deactivate(pcdev); + + if (pcdev->discard_buffer) { + dma_free_coherent(NULL, pcdev->discard_size, + pcdev->discard_buffer, + pcdev->discard_buffer_dma); + } + pcdev->discard_buffer = 0; + + pcdev->icd = NULL; +} + +static void mx27_camera_dma_enable(struct mx27_camera_dev *pcdev) +{ + u32 tmp; + + imx_dma_enable(pcdev->dma); + + tmp = readl(pcdev->base_csi + CSICR1); + tmp |= CSICR1_RF_OR_INTEN; + writel(tmp, pcdev->base_csi + CSICR1); +} + +static irqreturn_t mx27_camera_irq(int irq_csi, void *data) +{ + struct mx27_camera_dev *pcdev = data; + u32 status = readl(pcdev->base_csi + CSISR); + + if (status & CSISR_SOF_INT && pcdev->active) { + u32 tmp; + + tmp = readl(pcdev->base_csi + CSICR1); + tmp |= CSICR1_CLR_RXFIFO; + writel(tmp, pcdev->base_csi + CSICR1); + mx27_camera_dma_enable(pcdev); + writel(CSISR_SOF_INT | CSISR_RFF_OR_INT, + pcdev->base_csi + CSISR); + status &= ~CSISR_RFF_OR_INT; + } + + writel(CSISR_SOF_INT | CSISR_RFF_OR_INT, pcdev->base_csi + CSISR); + + return IRQ_HANDLED; +} + +static unsigned int vid_limit = 16; /* Video memory limit, in Mb */ + +/* + * Videobuf operations + */ +static int mx27_videobuf_setup(struct videobuf_queue *vq, unsigned int *count, + unsigned int *size) +{ + struct soc_camera_device *icd = vq->priv_data; + + dev_dbg(&icd->dev, "count=%d, size=%d\n", *count, *size); + + *size = icd->user_width * icd->user_height * + ((icd->current_fmt->depth + 7) >> 3); + + if (0 == *count) + *count = 32; + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct mx27_buffer *buf) +{ + struct soc_camera_device *icd = vq->priv_data; + + BUG_ON(in_interrupt()); + + dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, + &buf->vb, buf->vb.baddr, buf->vb.bsize); + + /* This waits until this buffer is out of danger, i.e., until it is no + * longer in STATE_QUEUED or STATE_ACTIVE */ + videobuf_waiton(&buf->vb, 0, 0); + + videobuf_dma_contig_free(vq, &buf->vb); + dev_dbg(&icd->dev, "%s freed\n", __func__); + + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +static int mx27_videobuf_prepare(struct videobuf_queue *vq, + struct videobuf_buffer *vb, enum v4l2_field field) +{ + struct soc_camera_device *icd = vq->priv_data; + struct mx27_buffer *buf = container_of(vb, struct mx27_buffer, vb); + int ret = 0; + +#ifdef DEBUG + /* This can be useful if you want to see if we actually fill + * the buffer with something */ + memset((void *)vb->baddr, 0xaa, vb->bsize); +#endif + + dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + if (buf->fmt != icd->current_fmt || + vb->width != icd->user_width || + vb->height != icd->user_height || + vb->field != field) { + buf->fmt = icd->current_fmt; + vb->width = icd->user_width; + vb->height = icd->user_height; + vb->field = field; + vb->state = VIDEOBUF_NEEDS_INIT; + } + + vb->size = vb->width * vb->height * ((buf->fmt->depth + 7) >> 3); + if (vb->baddr && vb->bsize < vb->size) { + ret = -EINVAL; + goto out; + } + + if (vb->state == VIDEOBUF_NEEDS_INIT) { + ret = videobuf_iolock(vq, vb, NULL); + if (ret) + goto fail; + + vb->state = VIDEOBUF_PREPARED; + } + + return 0; + +fail: + free_buffer(vq, buf); +out: + return ret; +} + +#define CSI_BASE_ADDR 0x80000000 /* FIXME: get from resources */ + +static void mx27_videobuf_queue(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct soc_camera_device *icd = vq->priv_data; + struct soc_camera_host *ici = + to_soc_camera_host(icd->dev.parent); + struct mx27_camera_dev *pcdev = ici->priv; + struct mx27_buffer *buf = container_of(vb, struct mx27_buffer, vb); + unsigned long flags; + int ret; + + dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + spin_lock_irqsave(&pcdev->lock, flags); + + if (mx27_camera_emma(pcdev)) { + list_add_tail(&vb->queue, &pcdev->capture); + vb->state = VIDEOBUF_QUEUED; + } else { + list_add_tail(&vb->queue, &pcdev->capture); + vb->state = VIDEOBUF_ACTIVE; + + if (!pcdev->active) { + ret = imx_dma_setup_single(pcdev->dma, + videobuf_to_dma_contig(vb), vb->size, + CSI_BASE_ADDR + 0x10, DMA_MODE_READ); + if (ret) { + vb->state = VIDEOBUF_ERROR; + wake_up(&vb->done); + goto out; + } + + pcdev->active = buf; + } + } + +out: + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static void mx27_videobuf_release(struct videobuf_queue *vq, + struct videobuf_buffer *vb) +{ + struct mx27_buffer *buf = container_of(vb, struct mx27_buffer, vb); + +#ifdef DEBUG + struct soc_camera_device *icd = vq->priv_data; + + dev_dbg(&icd->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + switch (vb->state) { + case VIDEOBUF_ACTIVE: + dev_info(&icd->dev, "%s (active)\n", __func__); + break; + case VIDEOBUF_QUEUED: + dev_info(&icd->dev, "%s (queued)\n", __func__); + break; + case VIDEOBUF_PREPARED: + dev_info(&icd->dev, "%s (prepared)\n", __func__); + break; + default: + dev_info(&icd->dev, "%s (unknown) %d\n", __func__, + vb->state); + break; + } +#endif + + free_buffer(vq, buf); +} + +static struct videobuf_queue_ops mx27_videobuf_ops = { + .buf_setup = mx27_videobuf_setup, + .buf_prepare = mx27_videobuf_prepare, + .buf_queue = mx27_videobuf_queue, + .buf_release = mx27_videobuf_release, +}; + +static void mx27_camera_init_videobuf(struct videobuf_queue *q, + struct soc_camera_device *icd) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + struct mx27_camera_dev *pcdev = ici->priv; + + videobuf_queue_dma_contig_init(q, &mx27_videobuf_ops, pcdev->dev, + &pcdev->lock, V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_NONE, sizeof(struct mx27_buffer), icd); +} + +#define MX27_BUS_FLAGS (SOCAM_DATAWIDTH_8 | \ + SOCAM_MASTER | \ + SOCAM_VSYNC_ACTIVE_HIGH | \ + SOCAM_HSYNC_ACTIVE_HIGH | \ + SOCAM_HSYNC_ACTIVE_LOW | \ + SOCAM_PCLK_SAMPLE_RISING | \ + SOCAM_PCLK_SAMPLE_FALLING | \ + SOCAM_DATA_ACTIVE_HIGH | \ + SOCAM_DATA_ACTIVE_LOW) + + +static int mx27_camera_emma_prp_reset(struct mx27_camera_dev *pcdev) +{ + unsigned int cntl; + + cntl = readl(pcdev->base_emma + PRP_CNTL); + writel(PRP_CNTL_SWRST, pcdev->base_emma + PRP_CNTL); + while (readl(pcdev->base_emma + PRP_CNTL) & PRP_CNTL_SWRST) + barrier(); + + return 0; +} + +static int mx27_camera_set_bus_param(struct soc_camera_device *icd, + __u32 pixfmt) +{ + struct soc_camera_host *ici = + to_soc_camera_host(icd->dev.parent); + struct mx27_camera_dev *pcdev = ici->priv; + unsigned long camera_flags, common_flags; + int ret = 0; + u32 csicr1 = pcdev->csicr1; + + camera_flags = icd->ops->query_bus_param(icd); + + common_flags = soc_camera_bus_param_compatible(camera_flags, + MX27_BUS_FLAGS); + if (!common_flags) + return -EINVAL; + + if ((common_flags & SOCAM_HSYNC_ACTIVE_HIGH) && + (common_flags & SOCAM_HSYNC_ACTIVE_LOW)) { + if (pcdev->platform_flags & MX27_CAMERA_HSYNC_HIGH) + common_flags &= ~SOCAM_HSYNC_ACTIVE_LOW; + else + common_flags &= ~SOCAM_HSYNC_ACTIVE_HIGH; + } + + if ((common_flags & SOCAM_PCLK_SAMPLE_RISING) && + (common_flags & SOCAM_PCLK_SAMPLE_FALLING)) { + if (pcdev->platform_flags & MX27_CAMERA_PCLK_SAMPLE_RISING) + common_flags &= ~SOCAM_PCLK_SAMPLE_FALLING; + else + common_flags &= ~SOCAM_PCLK_SAMPLE_RISING; + } + + ret = icd->ops->set_bus_param(icd, common_flags); + if (ret < 0) + return ret; + + if (common_flags & SOCAM_PCLK_SAMPLE_FALLING) + csicr1 |= CSICR1_INV_PCLK; + if (common_flags & SOCAM_HSYNC_ACTIVE_HIGH) + csicr1 |= CSICR1_HSYNC_POL; + if (pcdev->platform_flags & MX27_CAMERA_SWAP16) + csicr1 |= CSICR1_SWAP16_EN; + if (pcdev->platform_flags & MX27_CAMERA_EXT_VSYNC) + csicr1 |= CSICR1_EXT_VSYNC; + if (pcdev->platform_flags & MX27_CAMERA_CCIR) + csicr1 |= CSICR1_CCIR_EN; + if (pcdev->platform_flags & MX27_CAMERA_CCIR_INTERLACE) + csicr1 |= CSICR1_CCIR_MODE; + if (pcdev->platform_flags & MX27_CAMERA_GATED_CLOCK) + csicr1 |= CSICR1_GCLK_MODE; + if (pcdev->platform_flags & MX27_CAMERA_INV_DATA) + csicr1 |= CSICR1_INV_DATA; + if (pcdev->platform_flags & MX27_CAMERA_PACK_DIR_MSB) + csicr1 |= CSICR1_PACK_DIR; + + if (mx27_camera_emma(pcdev)) { + int bytesperline = (icd->user_width * icd->current_fmt->depth) >> 3; + + if (mx27_camera_emma_prp_reset(pcdev)) + return -ENODEV; + + if (pcdev->discard_buffer) + dma_free_coherent(NULL, pcdev->discard_size, + pcdev->discard_buffer, + pcdev->discard_buffer_dma); + + /* I didn't manage to properly enable/disable the prp + * on a per frame basis during running transfers, + * thus we allocate a buffer here and use it to + * discard frames when no buffer is available. + * Feel free to work on this ;) + */ + pcdev->discard_size = icd->user_height * bytesperline; + pcdev->discard_buffer = dma_alloc_coherent(NULL, + pcdev->discard_size, &pcdev->discard_buffer_dma, + GFP_KERNEL); + if (!pcdev->discard_buffer) + return -ENOMEM; + + writel(pcdev->discard_buffer_dma, + pcdev->base_emma + PRP_DEST_RGB1_PTR); + writel(pcdev->discard_buffer_dma, + pcdev->base_emma + PRP_DEST_RGB2_PTR); + + /* We only use the EMMA engine to get rid of the f**king + * DMA Engine. No color space consversion at the moment. + * We adjust incoming and outgoing pixelformat to rgb16 + * and adjust the bytesperline accordingly. + */ + writel(PRP_CNTL_CH1EN | + PRP_CNTL_CSIEN | + PRP_CNTL_DATA_IN_RGB16 | + PRP_CNTL_CH1_OUT_RGB16 | + PRP_CNTL_CH1_LEN | + PRP_CNTL_CH1BYP | + PRP_CNTL_CH1_TSKIP(0) | + PRP_CNTL_IN_TSKIP(0), + pcdev->base_emma + PRP_CNTL); + + writel(((bytesperline >> 1) << 16) | icd->user_height, + pcdev->base_emma + PRP_SRC_FRAME_SIZE); + writel(((bytesperline >> 1) << 16) | icd->user_height, + pcdev->base_emma + PRP_CH1_OUT_IMAGE_SIZE); + writel(bytesperline, + pcdev->base_emma + PRP_DEST_CH1_LINE_STRIDE); + writel(0x2ca00565, + pcdev->base_emma + PRP_SRC_PIXEL_FORMAT_CNTL); + writel(0x2ca00565, + pcdev->base_emma + PRP_CH1_PIXEL_FORMAT_CNTL); + + /* Enable interrupts */ + writel(PRP_INTR_RDERR | + PRP_INTR_CH1WERR | + PRP_INTR_CH2WERR | + PRP_INTR_CH1FC | + PRP_INTR_CH2FC | + PRP_INTR_LBOVF | + PRP_INTR_CH2OVF + , pcdev->base_emma + PRP_INTR_CNTL); + } + + pcdev->csicr1 = csicr1; + + writel(csicr1, pcdev->base_csi + CSICR1); + + return 0; +} + +static int mx27_camera_set_crop(struct soc_camera_device *icd, + struct v4l2_crop *a) +{ + struct v4l2_rect *rect = &a->c; + //struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + //struct mx27_camera_dev *mx27_cam = ici->priv; + struct v4l2_subdev *sd = soc_camera_to_subdev(icd); + struct v4l2_format f = {.type = V4L2_BUF_TYPE_VIDEO_CAPTURE}; + struct v4l2_pix_format *pix = &f.fmt.pix; + int ret; + + soc_camera_limit_side(&rect->left, &rect->width, 0, 2, 4096); + soc_camera_limit_side(&rect->top, &rect->height, 0, 2, 4096); + + ret = v4l2_subdev_call(sd, video, s_crop, a); + if (ret < 0) + return ret; + + /* The capture device might have changed its output */ + ret = v4l2_subdev_call(sd, video, g_fmt, &f); + if (ret < 0) + return ret; + + dev_dbg(icd->dev.parent, "Sensor cropped %dx%d\n", + pix->width, pix->height); + + icd->user_width = pix->width; + icd->user_height = pix->height; + + return ret; +} + +static int mx27_camera_set_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + struct soc_camera_host *ici = to_soc_camera_host(icd->dev.parent); + const struct soc_camera_format_xlate *xlate; + __u32 pixfmt = f->fmt.pix.pixelformat; + struct v4l2_format cam_f = *f; + int ret; + + xlate = soc_camera_xlate_by_fourcc(icd, pixfmt); + if (!xlate) { + dev_warn(icd->dev.parent, "Format %x not found\n", pixfmt); + return -EINVAL; + } + + cam_f.fmt.pix.pixelformat = xlate->cam_fmt->fourcc; + ret = ici->ops->set_fmt(icd, &cam_f); + + if (!ret) { + icd->buswidth = xlate->buswidth; + icd->current_fmt = xlate->host_fmt; + } + + return ret; +} + +static int mx27_camera_try_fmt(struct soc_camera_device *icd, + struct v4l2_format *f) +{ + return 0; +} + +static int mx27_camera_querycap(struct soc_camera_host *ici, + struct v4l2_capability *cap) +{ + /* cap->name is set by the friendly caller:-> */ + strlcpy(cap->card, mx27_cam_driver_description, sizeof(cap->card)); + cap->version = MX27_CAM_VERSION_CODE; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + + return 0; +} + +static int mx27_camera_reqbufs(struct soc_camera_file *icf, + struct v4l2_requestbuffers *p) +{ + int i; + + for (i = 0; i < p->count; i++) { + struct mx27_buffer *buf = container_of(icf->vb_vidq.bufs[i], + struct mx27_buffer, vb); + INIT_LIST_HEAD(&buf->vb.queue); + } + + return 0; +} + +static void mx27_camera_frame_done(struct mx27_camera_dev *pcdev, int state) +{ + struct videobuf_buffer *vb; + struct mx27_buffer *buf; + int ret; + + if (!pcdev->active) { + dev_err(pcdev->dev, "%s called with no active buffer!\n", + __func__); + return; + } + + vb = &pcdev->active->vb; + buf = container_of(vb, struct mx27_buffer, vb); + WARN_ON(list_empty(&vb->queue)); + dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%08lx %d\n", __func__, + vb, vb->baddr, vb->bsize); + + /* _init is used to debug races, see comment in pxa_camera_reqbufs() */ + list_del_init(&vb->queue); + vb->state = state; + do_gettimeofday(&vb->ts); + vb->field_count++; + + wake_up(&vb->done); + + if (list_empty(&pcdev->capture)) { + pcdev->active = NULL; + return; + } + + pcdev->active = list_entry(pcdev->capture.next, + struct mx27_buffer, vb.queue); + + vb = &pcdev->active->vb; + vb->state = VIDEOBUF_ACTIVE; + + ret = imx_dma_setup_single(pcdev->dma, videobuf_to_dma_contig(vb), + vb->size, CSI_BASE_ADDR + 0x10, DMA_MODE_READ); + if (ret) { + vb->state = VIDEOBUF_ERROR; + wake_up(&vb->done); + return; + } +} + +static void mx27_camera_dma_err_callback(int channel, void *data, int err) +{ + struct mx27_camera_dev *pcdev = data; + unsigned long flags; + + spin_lock_irqsave(&pcdev->lock, flags); + + mx27_camera_frame_done(pcdev, VIDEOBUF_ERROR); + + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static void mx27_camera_dma_callback(int channel, void *data) +{ + struct mx27_camera_dev *pcdev = data; + unsigned long flags; + + spin_lock_irqsave(&pcdev->lock, flags); + + mx27_camera_frame_done(pcdev, VIDEOBUF_DONE); + + spin_unlock_irqrestore(&pcdev->lock, flags); +} + +static unsigned int mx27_camera_poll(struct file *file, poll_table *pt) +{ + struct soc_camera_file *icf = file->private_data; + struct mx27_buffer *buf; + + buf = list_entry(icf->vb_vidq.stream.next, struct mx27_buffer, + vb.stream); + + poll_wait(file, &buf->vb.done, pt); + + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + return POLLIN | POLLRDNORM; + + return 0; +} + +static struct soc_camera_host_ops mx27_soc_camera_host_ops = { + .owner = THIS_MODULE, + .add = mx27_camera_add_device, + .remove = mx27_camera_remove_device, + .set_fmt = mx27_camera_set_fmt, + .set_crop = mx27_camera_set_crop, + .try_fmt = mx27_camera_try_fmt, + .init_videobuf = mx27_camera_init_videobuf, + .reqbufs = mx27_camera_reqbufs, + .poll = mx27_camera_poll, + .querycap = mx27_camera_querycap, + .set_bus_param = mx27_camera_set_bus_param, +}; + +static void mx27_camera_frame_done_emma(struct mx27_camera_dev *pcdev, + int bufnum, int state) +{ + struct mx27_buffer *buf; + struct videobuf_buffer *vb; + unsigned long phys; + + if (!list_empty(&pcdev->active_bufs)) { + buf = list_entry(pcdev->active_bufs.next, + struct mx27_buffer, vb.queue); + + if (buf->bufnum == bufnum) { + vb = &buf->vb; +#ifdef DEBUG + phys = videobuf_to_dma_contig(vb); + if (readl(pcdev->base_emma + PRP_DEST_RGB1_PTR + + 4 * bufnum) != phys) { + dev_err(pcdev->dev, "%p != %p\n", phys, + readl(pcdev->base_emma + + PRP_DEST_RGB1_PTR + + 4 * bufnum)); + } +#endif + dev_dbg(pcdev->dev, "%s (vb=0x%p) 0x%08lx %d\n", + __func__, vb, vb->baddr, vb->bsize); + + list_del(&vb->queue); + vb->state = state; + do_gettimeofday(&vb->ts); + vb->field_count++; + + wake_up(&vb->done); + } + } + + if (list_empty(&pcdev->capture)) { + writel(pcdev->discard_buffer_dma, pcdev->base_emma + + PRP_DEST_RGB1_PTR + 4 * bufnum); + return; + } + + buf = list_entry(pcdev->capture.next, + struct mx27_buffer, vb.queue); + + buf->bufnum = bufnum; + + list_move_tail(pcdev->capture.next, &pcdev->active_bufs); + + vb = &buf->vb; + vb->state = VIDEOBUF_ACTIVE; + + phys = videobuf_to_dma_contig(vb); + writel(phys, pcdev->base_emma + PRP_DEST_RGB1_PTR + 4 * bufnum); +} + +static irqreturn_t mx27_camera_emma_irq(int irq_emma, void *data) +{ + struct mx27_camera_dev *pcdev = data; + unsigned int status = readl(pcdev->base_emma + PRP_INTRSTATUS); + + if (status & (1 << 6)) + mx27_camera_frame_done_emma(pcdev, 0, VIDEOBUF_DONE); + if (status & (1 << 5)) + mx27_camera_frame_done_emma(pcdev, 1, VIDEOBUF_DONE); + if (status & (1 << 7)) { + uint32_t cntl; + cntl = readl(pcdev->base_emma + PRP_CNTL); + writel(cntl & ~PRP_CNTL_CH1EN, pcdev->base_emma + PRP_CNTL); + writel(cntl, pcdev->base_emma + PRP_CNTL); + } + + writel(status, pcdev->base_emma + PRP_INTRSTATUS); + + return IRQ_HANDLED; +} + +/* Should be allocated dynamically too, but we have only one. */ +static struct soc_camera_host mx27_soc_camera_host = { + .drv_name = MX27_CAM_DRV_NAME, + .ops = &mx27_soc_camera_host_ops, +}; + +int mx27_camera_emma_init(struct mx27_camera_dev *pcdev) +{ + struct resource *res_emma = pcdev->res_emma; + int err = 0; + + if (!request_mem_region(res_emma->start, resource_size(res_emma), + MX27_CAM_DRV_NAME)) { + err = -EBUSY; + goto out; + } + + pcdev->base_emma = ioremap(res_emma->start, resource_size(res_emma)); + if (!pcdev->base_emma) { + err = -ENOMEM; + goto exit_release; + } + + err = request_irq(pcdev->irq_emma, mx27_camera_emma_irq, 0, + MX27_CAM_DRV_NAME, pcdev); + if (err) { + dev_err(pcdev->dev, "Camera EMMA interrupt register failed \n"); + goto exit_iounmap; + } + + pcdev->clk_emma = clk_get(NULL, "emma"); + if (IS_ERR(pcdev->clk_emma)) { + err = PTR_ERR(pcdev->clk_emma); + goto exit_free_irq; + } + + clk_enable(pcdev->clk_emma); + + err = mx27_camera_emma_prp_reset(pcdev); + if (err) + goto exit_clk_emma_put; + + return err; + +exit_clk_emma_put: + clk_disable(pcdev->clk_emma); + clk_put(pcdev->clk_emma); +exit_free_irq: + free_irq(pcdev->irq_emma, pcdev); +exit_iounmap: + iounmap(pcdev->base_emma); +exit_release: + release_mem_region(res_emma->start, resource_size(res_emma)); +out: + return err; +} + +#define DMA_REQ_CSI_RX 31 /* FIXME: Add this to a resource */ + +static int mx27_camera_probe(struct platform_device *pdev) +{ + struct mx27_camera_dev *pcdev; + struct resource *res_csi, *res_emma; + void __iomem *base_csi; + unsigned int irq_csi, irq_emma; + int err = 0; + + dev_info(&pdev->dev, "initialising\n"); + + res_csi = platform_get_resource(pdev, IORESOURCE_MEM, 0); + irq_csi = platform_get_irq(pdev, 0); + if (!res_csi || !irq_csi) { + dev_err(&pdev->dev, "No platform irq\n"); + err = -ENODEV; + goto exit; + } + + pcdev = kzalloc(sizeof(*pcdev), GFP_KERNEL); + if (!pcdev) { + dev_err(&pdev->dev, "Could not allocate pcdev\n"); + err = -ENOMEM; + goto exit; + } + + pcdev->clk_csi = clk_get(&pdev->dev, NULL); + if (IS_ERR(pcdev->clk_csi)) { + err = PTR_ERR(pcdev->clk_csi); + goto exit_kfree; + } + + dev_info(&pdev->dev, "Camera clock frequency: %ld\n", + clk_get_rate(pcdev->clk_csi)); + + /* Initialize DMA */ + pcdev->dma = imx_dma_request_by_prio("CSI RX DMA", DMA_PRIO_HIGH); + if (pcdev->dma < 0) { + dev_err(&pdev->dev, + "mxc_v4l_read failed to request DMA channel\n"); + err = -EIO; + goto exit_clk_put; + } + + err = imx_dma_setup_handlers(pcdev->dma, mx27_camera_dma_callback, + mx27_camera_dma_err_callback, pcdev); + if (err != 0) { + dev_err(&pdev->dev, + "mxc_v4l_read failed to set DMA callback\n"); + err = -EIO; + goto exit_dma_free; + } + + imx_dma_config_channel(pcdev->dma, + IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_FIFO, + IMX_DMA_MEMSIZE_32 | IMX_DMA_TYPE_LINEAR, + DMA_REQ_CSI_RX, 1); + + imx_dma_config_burstlen(pcdev->dma, 64); + + dev_set_drvdata(&pdev->dev, pcdev); + pcdev->res_csi = res_csi; + + pcdev->pdata = pdev->dev.platform_data; + pcdev->platform_flags = pcdev->pdata->flags; + + /* FIXME: clk_round_rate first! */ + clk_set_rate(pcdev->clk_csi, pcdev->pdata->clk * 2); + + INIT_LIST_HEAD(&pcdev->capture); + INIT_LIST_HEAD(&pcdev->active_bufs); + spin_lock_init(&pcdev->lock); + + /* + * Request the regions. + */ + if (!request_mem_region(res_csi->start, resource_size(res_csi), + MX27_CAM_DRV_NAME)) { + err = -EBUSY; + goto exit_dma_free; + } + + base_csi = ioremap(res_csi->start, resource_size(res_csi)); + if (!base_csi) { + err = -ENOMEM; + goto exit_release; + } + pcdev->irq_csi = irq_csi; + pcdev->base_csi = base_csi; + pcdev->dev = &pdev->dev; + + pcdev->pdata->init(pdev); + + err = request_irq(pcdev->irq_csi, mx27_camera_irq, 0, MX27_CAM_DRV_NAME, + pcdev); + if (err) { + dev_err(pcdev->dev, "Camera interrupt register failed \n"); + goto exit_iounmap; + } + + mx27_soc_camera_host.priv = pcdev; + mx27_soc_camera_host.v4l2_dev.dev = &pdev->dev; + mx27_soc_camera_host.nr = pdev->id; + + /* EMMA support */ + res_emma = platform_get_resource(pdev, IORESOURCE_MEM, 1); + irq_emma = platform_get_irq(pdev, 1); + + if (res_emma && irq_emma) { + dev_info(&pdev->dev, "Using EMMA\n"); + pcdev->use_emma = 1; + pcdev->res_emma = res_emma; + pcdev->irq_emma = irq_emma; + if (mx27_camera_emma_init(pcdev)) + goto exit_free_irq; + } + + err = soc_camera_host_register(&mx27_soc_camera_host); + if (err) + goto exit_free_emma; + + return 0; + +exit_free_emma: + free_irq(pcdev->irq_emma, pcdev); + clk_disable(pcdev->clk_emma); + clk_put(pcdev->clk_emma); + iounmap(pcdev->base_emma); + release_mem_region(res_emma->start, resource_size(res_emma)); +exit_free_irq: + free_irq(pcdev->irq_csi, pcdev); +exit_iounmap: + iounmap(base_csi); +exit_release: + release_mem_region(res_csi->start, resource_size(res_csi)); +exit_dma_free: + imx_dma_free(pcdev->dma); +exit_clk_put: + clk_put(pcdev->clk_csi); +exit_kfree: + kfree(pcdev); +exit: + return err; +} + +static int __devexit mx27_camera_remove(struct platform_device *pdev) +{ + struct mx27_camera_dev *pcdev = platform_get_drvdata(pdev); + struct resource *res; + + clk_put(pcdev->clk_csi); + imx_dma_free(pcdev->dma); + free_irq(pcdev->irq_csi, pcdev); + if (mx27_camera_emma(pcdev)) + free_irq(pcdev->irq_emma, pcdev); + + soc_camera_host_unregister(&mx27_soc_camera_host); + + iounmap(pcdev->base_csi); + + if (mx27_camera_emma(pcdev)) { + clk_disable(pcdev->clk_emma); + clk_put(pcdev->clk_emma); + iounmap(pcdev->base_emma); + res = pcdev->res_emma; + release_mem_region(res->start, resource_size(res)); + } + + pcdev->pdata->exit(pdev); + + res = pcdev->res_csi; + release_mem_region(res->start, resource_size(res)); + + kfree(pcdev); + + dev_info(&pdev->dev, "MX27 Camera driver unloaded\n"); + + return 0; +} + +static struct platform_driver mx27_camera_driver = { + .driver = { + .name = MX27_CAM_DRV_NAME, + }, + .probe = mx27_camera_probe, + .remove = __exit_p(mx27_camera_remove), +}; + + +static int __devinit mx27_camera_init(void) +{ + return platform_driver_register(&mx27_camera_driver); +} + +static void __exit mx27_camera_exit(void) +{ + return platform_driver_unregister(&mx27_camera_driver); +} + +module_init(mx27_camera_init); +module_exit(mx27_camera_exit); + +MODULE_DESCRIPTION("i.MX27 SoC Camera Host driver"); +MODULE_AUTHOR("Sascha Hauer "); +MODULE_LICENSE("GPL");