From: Albert Wang <twang13@marvell.com>
To: g.liakhovetski@gmx.de, corbet@lwn.net
Cc: linux-media@vger.kernel.org, Albert Wang <twang13@marvell.com>
Subject: [PATCH 1/7] media: mmp_camera: Add V4l2 camera driver for Marvell PXA910/PXA688/PXA2128 CCIC
Date: Wed, 11 Jul 2012 22:22:29 +0800 [thread overview]
Message-ID: <1342016549-23084-1-git-send-email-twang13@marvell.com> (raw)
This v4l2 camera driver is based on soc-camera and videobuf2 framework
Support Marvell MMP Soc family TD-PXA910/MMP2-PXA688/MMP3-PXA2128 CCIC
Support Dual CCIC controllers on PXA688/PXA2128
Support MIPI-CSI2 mode and DVP-Parallel mode
Change-Id: I0d6156e29a278108dfb26dc1e891ca069e31666c
Signed-off-by: Albert Wang <twang13@marvell.com>
---
arch/arm/mach-mmp/include/mach/camera.h | 21 +
arch/arm/mach-mmp/include/mach/regs-apmu.h | 7 +-
drivers/media/video/Kconfig | 9 +
drivers/media/video/Makefile | 1 +
drivers/media/video/mmp_camera.c | 1134 ++++++++++++++++++++++++++++
drivers/media/video/mmp_camera.h | 241 ++++++
6 files changed, 1412 insertions(+), 1 deletions(-)
create mode 100644 arch/arm/mach-mmp/include/mach/camera.h
create mode 100644 drivers/media/video/mmp_camera.c
create mode 100644 drivers/media/video/mmp_camera.h
diff --git a/arch/arm/mach-mmp/include/mach/camera.h b/arch/arm/mach-mmp/include/mach/camera.h
new file mode 100644
index 0000000..d13cd91
--- /dev/null
+++ b/arch/arm/mach-mmp/include/mach/camera.h
@@ -0,0 +1,21 @@
+#ifndef __ASM_ARCH_CAMERA_H__
+#define __ASM_ARCH_CAMERA_H__
+
+struct mmp_cam_pdata {
+ struct clk *clk[3]; /* CCIC_GATE, CCIC_RST, CCIC_DBG clocks */
+ char *name;
+ int clk_enabled;
+ int dphy[3]; /* DPHY: CSI2_DPHY3, CSI2_DPHY5, CSI2_DPHY6 */
+ int bus_type;
+ int mipi_enabled; /* MIPI enabled flag */
+ int lane; /* ccic used lane number; 0 means DVP mode */
+ int dma_burst;
+ int mclk_min;
+ int mclk_src;
+ int mclk_div;
+ int (*init_clk)(struct device *dev, int init);
+ void (*enable_clk)(struct device *dev, int on);
+};
+
+#endif
+
diff --git a/arch/arm/mach-mmp/include/mach/regs-apmu.h b/arch/arm/mach-mmp/include/mach/regs-apmu.h
index 8447ac6..5616340 100644
--- a/arch/arm/mach-mmp/include/mach/regs-apmu.h
+++ b/arch/arm/mach-mmp/include/mach/regs-apmu.h
@@ -19,7 +19,12 @@
/* Clock Reset Control */
#define APMU_IRE APMU_REG(0x048)
#define APMU_LCD APMU_REG(0x04c)
-#define APMU_CCIC APMU_REG(0x050)
+#define APMU_CCIC_GATE APMU_REG(0x028)
+#define APMU_CCIC_RST APMU_REG(0x050)
+#define APMU_CCIC_DBG APMU_REG(0x088)
+#define APMU_CCIC2_GATE APMU_REG(0x118)
+#define APMU_CCIC2_RST APMU_REG(0x0f4)
+#define APMU_CCIC2_DBG APMU_REG(0x088)
#define APMU_SDH0 APMU_REG(0x054)
#define APMU_SDH1 APMU_REG(0x058)
#define APMU_USB APMU_REG(0x05c)
diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig
index ce1e7ba..244d418 100644
--- a/drivers/media/video/Kconfig
+++ b/drivers/media/video/Kconfig
@@ -1087,6 +1087,15 @@ config VIDEO_PXA27x
---help---
This is a v4l2 driver for the PXA27x Quick Capture Interface
+config VIDEO_MMP
+ tristate "Marvell MMP CCIC driver based on SOC_CAMERA"
+ depends on VIDEO_DEV && SOC_CAMERA
+ select VIDEOBUF2_DMA_CONTIG
+ ---help---
+ This is a v4l2 driver for the Marvell PXA910/PXA688/PXA2128 CCIC
+ To compile this driver as a module, choose M here: the module will
+ be called mmp_camera.
+
config VIDEO_SH_MOBILE_CSI2
tristate "SuperH Mobile MIPI CSI-2 Interface driver"
depends on VIDEO_DEV && SOC_CAMERA && HAVE_CLK
diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile
index a6282a3..12defa7 100644
--- a/drivers/media/video/Makefile
+++ b/drivers/media/video/Makefile
@@ -178,6 +178,7 @@ obj-$(CONFIG_VIDEO_MX1) += mx1_camera.o
obj-$(CONFIG_VIDEO_MX2) += mx2_camera.o
obj-$(CONFIG_VIDEO_MX3) += mx3_camera.o
obj-$(CONFIG_VIDEO_PXA27x) += pxa_camera.o
+obj-$(CONFIG_VIDEO_MMP) += mmp_camera.o
obj-$(CONFIG_VIDEO_SH_MOBILE_CSI2) += sh_mobile_csi2.o
obj-$(CONFIG_VIDEO_SH_MOBILE_CEU) += sh_mobile_ceu_camera.o
obj-$(CONFIG_VIDEO_OMAP1) += omap1_camera.o
diff --git a/drivers/media/video/mmp_camera.c b/drivers/media/video/mmp_camera.c
new file mode 100644
index 0000000..2e6cc5c
--- /dev/null
+++ b/drivers/media/video/mmp_camera.c
@@ -0,0 +1,1134 @@
+/*
+ * V4L2 Driver for Marvell Mobile SoC PXA910/PXA688/PXA2128 CCIC
+ * (CMOS Camera Interface Controller)
+ *
+ * This driver is based on soc_camera and videobuf2 framework,
+ * but part of the low level register function is base on cafe-driver.c
+ *
+ * Copyright 2006 One Laptop Per Child Association, Inc.
+ * Copyright 2006-7 Jonathan Corbet <corbet@lwn.net>
+ *
+ * Copyright (C) 2011-2012, Marvell International Ltd.
+ * Kassey Lee <ygli@marvell.com>
+ * Angela Wan <jwan@marvell.com>
+ * Albert Wang <twang13@marvell.com>
+ * Lei Wen <leiwen@marvell.com>
+ * Fangsuo Wu <fswu@marvell.com>
+ * Sarah Zhang <xiazh@marvell.com>
+ *
+ * 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 <linux/clk.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/version.h>
+#include <linux/platform_device.h>
+#include <linux/time.h>
+#include <linux/videodev2.h>
+
+#include <media/soc_camera.h>
+#include <media/soc_mediabus.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-dev.h>
+#include <media/videobuf2-dma-contig.h>
+#include <media/v4l2-chip-ident.h>
+
+#include <mach/regs-apmu.h>
+#include <mach/camera.h>
+
+#include "mmp_camera.h"
+
+#define MMP_CAM_DRV_NAME "mmp-camera"
+
+static const struct soc_mbus_pixelfmt ccic_formats[] = {
+ {
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .name = "YUV422PACKED",
+ .bits_per_sample = 8,
+ .packing = SOC_MBUS_PACKING_2X8_PADLO,
+ .order = SOC_MBUS_ORDER_LE,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV422P,
+ .name = "YUV422PLANAR",
+ .bits_per_sample = 8,
+ .packing = SOC_MBUS_PACKING_2X8_PADLO,
+ .order = SOC_MBUS_ORDER_LE,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YUV420,
+ .name = "YUV420PLANAR",
+ .bits_per_sample = 12,
+ .packing = SOC_MBUS_PACKING_NONE,
+ .order = SOC_MBUS_ORDER_LE,
+ },
+ {
+ .fourcc = V4L2_PIX_FMT_YVU420,
+ .name = "YVU420PLANAR",
+ .bits_per_sample = 12,
+ .packing = SOC_MBUS_PACKING_NONE,
+ .order = SOC_MBUS_ORDER_LE,
+ },
+};
+
+static void ccic_config_phy(struct mmp_camera_dev *pcdev, int enable)
+{
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+ struct device *dev = &pcdev->pdev->dev;
+
+ if (pdata->bus_type == V4L2_MBUS_CSI2_LANES && enable) {
+ dev_dbg(dev, "camera: DPHY3=0x%x, DPHY5=0x%x, DPHY6=0x%x\n",
+ pdata->dphy[0], pdata->dphy[1], pdata->dphy[2]);
+ ccic_reg_write(pcdev, REG_CSI2_DPHY3, pdata->dphy[0]);
+ ccic_reg_write(pcdev, REG_CSI2_DPHY6, pdata->dphy[2]);
+ ccic_reg_write(pcdev, REG_CSI2_DPHY5, pdata->dphy[1]);
+ if (pdata->mipi_enabled == 0) {
+ /*
+ * 0x41 actives 1 lane
+ * 0x43 actives 2 lanes
+ */
+ if (pdata->lane == 1)
+ ccic_reg_write(pcdev, REG_CSI2_CTRL0, 0x41);
+ else if (pdata->lane == 2)
+ ccic_reg_write(pcdev, REG_CSI2_CTRL0, 0x43);
+ pdata->mipi_enabled = 1;
+ }
+ } else {
+ ccic_reg_write(pcdev, REG_CSI2_DPHY3, 0x0);
+ ccic_reg_write(pcdev, REG_CSI2_DPHY6, 0x0);
+ ccic_reg_write(pcdev, REG_CSI2_DPHY5, 0x0);
+ ccic_reg_write(pcdev, REG_CSI2_CTRL0, 0x0);
+ pdata->mipi_enabled = 0;
+ }
+}
+
+static void ccic_enable_clk(struct mmp_camera_dev *pcdev)
+{
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+ struct device *dev = &pcdev->pdev->dev;
+ int ctrl1 = 0;
+
+ pdata->enable_clk(&pcdev->pdev->dev, 1);
+ ccic_reg_write(pcdev, REG_CLKCTRL,
+ (pdata->mclk_src << 29) | pdata->mclk_div);
+ dev_dbg(dev, "camera: set sensor mclk = %d MHz\n", pdata->mclk_min);
+
+ switch (pdata->dma_burst) {
+ case 128:
+ ctrl1 |= C1_DMAB128;
+ break;
+ case 256:
+ ctrl1 |= C1_DMAB256;
+ break;
+ }
+ ccic_reg_write(pcdev, REG_CTRL1, ctrl1 | C1_RESERVED | C1_DMAPOSTED);
+ if (pdata->bus_type != V4L2_MBUS_CSI2_LANES)
+ ccic_reg_write(pcdev, REG_CTRL3, 0x4);
+}
+
+static void ccic_disable_clk(struct mmp_camera_dev *pcdev)
+{
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+
+ ccic_reg_write(pcdev, REG_CLKCTRL, 0x0);
+ /*
+ * Bit[5:1] reserved and should not be changed
+ */
+ ccic_reg_write(pcdev, REG_CTRL1, C1_RESERVED);
+ pdata->enable_clk(&pcdev->pdev->dev, 0);
+}
+
+static int ccic_config_image(struct mmp_camera_dev *pcdev)
+{
+ struct v4l2_pix_format *fmt = &pcdev->pix_format;
+ struct device *dev = &pcdev->pdev->dev;
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+ u32 widthy = 0, widthuv = 0, imgsz_h, imgsz_w;
+ int ret = 0;
+
+ dev_dbg(dev, "camera: bytesperline = %d; height = %d\n",
+ fmt->bytesperline, fmt->sizeimage / fmt->bytesperline);
+ imgsz_h = (fmt->height << IMGSZ_V_SHIFT) & IMGSZ_V_MASK;
+ imgsz_w = fmt->bytesperline & IMGSZ_H_MASK;
+
+ if (fmt->pixelformat == V4L2_PIX_FMT_YUV420
+ || fmt->pixelformat == V4L2_PIX_FMT_YVU420)
+ imgsz_w = (fmt->bytesperline * 4 / 3) & IMGSZ_H_MASK;
+ else if (fmt->pixelformat == V4L2_PIX_FMT_JPEG)
+ imgsz_h = (fmt->sizeimage / fmt->bytesperline) << IMGSZ_V_SHIFT;
+
+ switch (fmt->pixelformat) {
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_UYVY:
+ widthy = fmt->width * 2;
+ widthuv = fmt->width * 2;
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ widthy = fmt->width * 2;
+ widthuv = 0;
+ break;
+ case V4L2_PIX_FMT_JPEG:
+ widthy = fmt->bytesperline;
+ widthuv = fmt->bytesperline;
+ break;
+ case V4L2_PIX_FMT_YUV422P:
+ widthy = fmt->width;
+ widthuv = fmt->width / 2;
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ widthy = fmt->width;
+ widthuv = fmt->width / 2;
+ break;
+ default:
+ break;
+ }
+
+ ccic_reg_write(pcdev, REG_IMGPITCH, widthuv << 16 | widthy);
+ ccic_reg_write(pcdev, REG_IMGSIZE, imgsz_h | imgsz_w);
+ ccic_reg_write(pcdev, REG_IMGOFFSET, 0x0);
+
+ /*
+ * Tell the controller about the image format we are using.
+ */
+ switch (fmt->pixelformat) {
+ case V4L2_PIX_FMT_YUV422P:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_YUV | C0_YUV_PLANAR | C0_YUVE_YVYU, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_YVU420:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_YUV | C0_YUV_420PL | C0_YUVE_YVYU, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_UYVY, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_YUYV, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_JPEG:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_YUV | C0_YUV_PACKED | C0_YUVE_YUYV, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_RGB444:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_RGB | C0_RGBF_444 | C0_RGB4_XRGB, C0_DF_MASK);
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ ccic_reg_write_mask(pcdev, REG_CTRL0,
+ C0_DF_RGB | C0_RGBF_565 | C0_RGB5_BGGR, C0_DF_MASK);
+ break;
+ default:
+ dev_err(dev, "camera: unknown format: %c\n", fmt->pixelformat);
+ break;
+ }
+
+ /*
+ * Make sure it knows we want to use hsync/vsync.
+ */
+ ccic_reg_write_mask(pcdev, REG_CTRL0, C0_SIF_HVSYNC, C0_SIFM_MASK);
+ /*
+ * This field controls the generation of EOF(DVP only)
+ */
+ if (pdata->bus_type != V4L2_MBUS_CSI2_LANES)
+ ccic_reg_set_bit(pcdev, REG_CTRL0,
+ C0_EOF_VSYNC | C0_VEDGE_CTRL);
+
+ return ret;
+}
+
+static void ccic_frameirq_enable(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_write(pcdev, REG_IRQSTAT, FRAMEIRQS_EOF);
+ ccic_reg_write(pcdev, REG_IRQSTAT, FRAMEIRQS_SOF);
+ ccic_reg_set_bit(pcdev, REG_IRQMASK, FRAMEIRQS_EOF);
+ ccic_reg_set_bit(pcdev, REG_IRQMASK, FRAMEIRQS_SOF);
+}
+
+static void ccic_frameirq_disable(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_clear_bit(pcdev, REG_IRQMASK, FRAMEIRQS_EOF);
+ ccic_reg_clear_bit(pcdev, REG_IRQMASK, FRAMEIRQS_SOF);
+}
+
+static void ccic_start(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_set_bit(pcdev, REG_CTRL0, C0_ENABLE);
+}
+
+static void ccic_stop(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_clear_bit(pcdev, REG_CTRL0, C0_ENABLE);
+}
+
+static void ccic_stop_dma(struct mmp_camera_dev *pcdev)
+{
+ ccic_stop(pcdev);
+ ccic_frameirq_disable(pcdev);
+}
+
+static void ccic_power_up(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_clear_bit(pcdev, REG_CTRL1, C1_PWRDWN);
+}
+
+static void ccic_power_down(struct mmp_camera_dev *pcdev)
+{
+ ccic_reg_set_bit(pcdev, REG_CTRL1, C1_PWRDWN);
+}
+
+/*
+ * Fetch buffer from list, if single mode, we reserve the last buffer
+ * until new buffer is got, or fetch directly
+ */
+static void mmp_set_contig_buffer(struct mmp_camera_dev *pcdev, int frame)
+{
+ struct mmp_buffer *buf;
+ struct v4l2_pix_format *fmt = &pcdev->pix_format;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ if (list_empty(&pcdev->buffers)) {
+ /*
+ * If there are no available buffers, go into single mode
+ */
+ buf = pcdev->vb_bufs[frame ^ 0x1];
+ set_bit(CF_SINGLE_BUF, &pcdev->flags);
+ pcdev->frame_state.singles++;
+ } else {
+ /*
+ * OK, we have a buffer we can use.
+ */
+ buf = list_first_entry(&pcdev->buffers, struct mmp_buffer,
+ queue);
+ list_del_init(&buf->queue);
+ clear_bit(CF_SINGLE_BUF, &pcdev->flags);
+ }
+
+ pcdev->vb_bufs[frame] = buf;
+ ccic_reg_write(pcdev, REG_Y0BAR + (frame << 2), buf->yuv_p.y);
+ if (fmt->pixelformat == V4L2_PIX_FMT_YUV422P
+ || fmt->pixelformat == V4L2_PIX_FMT_YUV420
+ || fmt->pixelformat == V4L2_PIX_FMT_YVU420) {
+ ccic_reg_write(pcdev, REG_U0BAR + (frame << 2), buf->yuv_p.u);
+ ccic_reg_write(pcdev, REG_V0BAR + (frame << 2), buf->yuv_p.v);
+ }
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+}
+
+static void mmp_dma_setup(struct mmp_camera_dev *pcdev)
+{
+ int frame;
+
+ pcdev->nbufs = MAX_DMA_BUFS;
+ for (frame = 0; frame < pcdev->nbufs; frame++)
+ mmp_set_contig_buffer(pcdev, frame);
+
+ /*
+ * CCIC use Two Buffers mode
+ */
+ if (pcdev->nbufs == 2)
+ ccic_reg_set_bit(pcdev, REG_CTRL1, C1_TWOBUFS);
+}
+
+void ccic_ctlr_reset(struct mmp_camera_dev *pcdev)
+{
+ unsigned long val;
+
+ /*
+ * Used CCIC2
+ */
+ if (pcdev->pdev->id) {
+ val = readl(APMU_CCIC2_RST);
+ writel(val & ~0x2, APMU_CCIC2_RST);
+ writel(val | 0x2, APMU_CCIC2_RST);
+ }
+
+ val = readl(APMU_CCIC_RST);
+ writel(val & ~0x2, APMU_CCIC_RST);
+ writel(val | 0x2, APMU_CCIC_RST);
+}
+
+/*
+ * Get everything ready, and start grabbing frames.
+ */
+static int mmp_read_setup(struct mmp_camera_dev *pcdev)
+{
+ ccic_config_phy(pcdev, 1);
+ ccic_frameirq_enable(pcdev);
+ mmp_dma_setup(pcdev);
+ ccic_start(pcdev);
+ pcdev->state = S_STREAMING;
+
+ return 0;
+}
+
+static int mmp_videobuf_setup(struct vb2_queue *vq,
+ const struct v4l2_format *fmt,
+ u32 *count, u32 *num_planes,
+ unsigned int sizes[], void *alloc_ctxs[])
+{
+ struct soc_camera_device *icd = container_of(vq,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
+ icd->current_fmt->host_fmt);
+
+ int minbufs = 2;
+ if (*count < minbufs)
+ *count = minbufs;
+
+ if (bytes_per_line < 0)
+ return bytes_per_line;
+
+ *num_planes = 1;
+ sizes[0] = pcdev->pix_format.sizeimage;
+ alloc_ctxs[0] = pcdev->vb_alloc_ctx;
+ dev_dbg(&pcdev->pdev->dev, "count = %d, size = %u\n", *count, sizes[0]);
+
+ return 0;
+}
+
+static int mmp_videobuf_prepare(struct vb2_buffer *vb)
+{
+ struct soc_camera_device *icd = container_of(vb->vb2_queue,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct mmp_buffer *buf = container_of(vb, struct mmp_buffer, vb_buf);
+ unsigned long size;
+ unsigned long flags = 0;
+ int bytes_per_line = soc_mbus_bytes_per_line(icd->user_width,
+ icd->current_fmt->host_fmt);
+
+ if (bytes_per_line < 0)
+ return bytes_per_line;
+
+ dev_dbg(&pcdev->pdev->dev, "%s; (vb = 0x%p), 0x%p, %lu\n", __func__,
+ vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ /*
+ * Added list head initialization on alloc
+ */
+ WARN(!list_empty(&buf->queue), "Buffer %p on queue!\n", vb);
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+ BUG_ON(NULL == icd->current_fmt);
+ size = vb2_plane_size(vb, 0);
+ vb2_set_plane_payload(vb, 0, size);
+
+ return 0;
+}
+
+static void mmp_videobuf_queue(struct vb2_buffer *vb)
+{
+ struct soc_camera_device *icd = container_of(vb->vb2_queue,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct mmp_buffer *buf = container_of(vb, struct mmp_buffer, vb_buf);
+ unsigned long flags = 0;
+ int start;
+ dma_addr_t dma_handle;
+ u32 base_size = icd->user_width * icd->user_height;
+
+ mutex_lock(&pcdev->s_mutex);
+ dma_handle = vb2_dma_contig_plane_dma_addr(vb, 0);
+ BUG_ON(!dma_handle);
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ /*
+ * Wait until two buffers already queued to the list
+ * then start DMA
+ */
+ start = (pcdev->state == S_BUFWAIT) && !list_empty(&pcdev->buffers);
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+
+ if (pcdev->pix_format.pixelformat == V4L2_PIX_FMT_YUV422P) {
+ buf->yuv_p.y = dma_handle;
+ buf->yuv_p.u = buf->yuv_p.y + base_size;
+ buf->yuv_p.v = buf->yuv_p.u + base_size / 2;
+ } else if (pcdev->pix_format.pixelformat == V4L2_PIX_FMT_YUV420) {
+ buf->yuv_p.y = dma_handle;
+ buf->yuv_p.u = buf->yuv_p.y + base_size;
+ buf->yuv_p.v = buf->yuv_p.u + base_size / 4;
+ } else if (pcdev->pix_format.pixelformat == V4L2_PIX_FMT_YVU420) {
+ buf->yuv_p.y = dma_handle;
+ buf->yuv_p.v = buf->yuv_p.y + base_size;
+ buf->yuv_p.u = buf->yuv_p.v + base_size / 4;
+ } else {
+ buf->yuv_p.y = dma_handle;
+ }
+
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ list_add_tail(&buf->queue, &pcdev->buffers);
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+
+ if (start)
+ mmp_read_setup(pcdev);
+ mutex_unlock(&pcdev->s_mutex);
+}
+
+static void mmp_videobuf_cleanup(struct vb2_buffer *vb)
+{
+ struct mmp_buffer *buf = container_of(vb, struct mmp_buffer, vb_buf);
+ struct soc_camera_device *icd = container_of(vb->vb2_queue,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ unsigned long flags = 0;
+
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ /*
+ * queue list must be initialized before del
+ */
+ if (buf->list_init_flag)
+ list_del_init(&buf->queue);
+ buf->list_init_flag = 0;
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+}
+
+/*
+ * only the list that queued could be initialized
+ */
+static int mmp_videobuf_init(struct vb2_buffer *vb)
+{
+ struct mmp_buffer *buf = container_of(vb, struct mmp_buffer, vb_buf);
+ INIT_LIST_HEAD(&buf->queue);
+ buf->list_init_flag = 1;
+
+ return 0;
+}
+
+static int mmp_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct soc_camera_device *icd = container_of(vq,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ unsigned long flags = 0;
+ int ret = 0, frame;
+
+ mutex_lock(&pcdev->s_mutex);
+ if (count < 2) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ if (pcdev->state != S_IDLE) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ /*
+ * Videobuf2 sneakily hoards all the buffers and won't
+ * give them to us until *after* streaming starts. But
+ * we can't actually start streaming until we have a
+ * destination. So go into a wait state and hope they
+ * give us buffers soon.
+ */
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ if (list_empty(&pcdev->buffers)) {
+ pcdev->state = S_BUFWAIT;
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+ ret = 0;
+ goto out_unlock;
+ }
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+ ret = mmp_read_setup(pcdev);
+out_unlock:
+ for (frame = 0; frame < pcdev->nbufs; frame++)
+ clear_bit(CF_FRAME_SOF0 + frame, &pcdev->flags);
+ mutex_unlock(&pcdev->s_mutex);
+
+ return ret;
+}
+
+static int mmp_stop_streaming(struct vb2_queue *vq)
+{
+ struct soc_camera_device *icd = container_of(vq,
+ struct soc_camera_device, vb2_vidq);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ unsigned long flags = 0;
+ int ret = 0;
+
+ mutex_lock(&pcdev->s_mutex);
+ if (pcdev->state == S_BUFWAIT) {
+ /* They never gave us buffers */
+ pcdev->state = S_IDLE;
+ goto out_unlock;
+ }
+
+ if (pcdev->state != S_STREAMING) {
+ ret = -EINVAL;
+ goto out_unlock;
+ }
+
+ ccic_stop_dma(pcdev);
+ pcdev->state = S_IDLE;
+ ccic_ctlr_reset(pcdev);
+
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ INIT_LIST_HEAD(&pcdev->buffers);
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+out_unlock:
+ mutex_unlock(&pcdev->s_mutex);
+
+ return ret;
+}
+
+static struct vb2_ops mmp_videobuf_ops = {
+ .queue_setup = mmp_videobuf_setup,
+ .buf_prepare = mmp_videobuf_prepare,
+ .buf_queue = mmp_videobuf_queue,
+ .buf_cleanup = mmp_videobuf_cleanup,
+ .buf_init = mmp_videobuf_init,
+ .start_streaming = mmp_start_streaming,
+ .stop_streaming = mmp_stop_streaming,
+ .wait_prepare = soc_camera_unlock,
+ .wait_finish = soc_camera_lock,
+};
+
+static int mmp_camera_init_videobuf(struct vb2_queue *q,
+ struct soc_camera_device *icd)
+{
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_USERPTR | VB2_MMAP;
+ q->drv_priv = icd;
+ q->ops = &mmp_videobuf_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct mmp_buffer);
+
+ return vb2_queue_init(q);
+}
+
+/*
+ * Hand a completed buffer back to user space.
+ */
+static void mmp_buffer_done(struct mmp_camera_dev *pcdev,
+ int frame, struct vb2_buffer *vbuf)
+{
+ vbuf->v4l2_buf.bytesused = pcdev->pix_format.sizeimage;
+ vb2_set_plane_payload(vbuf, 0, pcdev->pix_format.sizeimage);
+ vb2_buffer_done(vbuf, VB2_BUF_STATE_DONE);
+}
+
+/*
+ * Interrupt handler stuff
+ */
+static inline void mmp_frame_complete(struct mmp_camera_dev *pcdev, int frame)
+{
+ struct mmp_buffer *buf;
+ unsigned long flags = 0;
+
+ pcdev->frame_state.frames++;
+ /*
+ * "This should never happen"
+ */
+ if (pcdev->state != S_STREAMING)
+ return;
+
+ spin_lock_irqsave(&pcdev->list_lock, flags);
+ buf = pcdev->vb_bufs[frame];
+ if (!test_bit(CF_SINGLE_BUF, &pcdev->flags)) {
+ pcdev->frame_state.delivered++;
+ mmp_buffer_done(pcdev, frame, &buf->vb_buf);
+ }
+ spin_unlock_irqrestore(&pcdev->list_lock, flags);
+ mmp_set_contig_buffer(pcdev, frame);
+}
+
+static irqreturn_t mmp_camera_frameirq(int irq, void *data)
+{
+ struct mmp_camera_dev *pcdev = data;
+ u32 irqs, frame;
+
+ irqs = ccic_reg_read(pcdev, REG_IRQSTAT);
+ if (!(irqs & FRAMEIRQS))
+ return IRQ_NONE;
+
+ /*
+ * Disable camera frame irq
+ */
+ ccic_reg_write(pcdev, REG_IRQSTAT, irqs);
+
+ for (frame = 0; frame < pcdev->nbufs; frame++)
+ if (irqs & (IRQ_SOF0 << frame))
+ set_bit(CF_FRAME_SOF0 + frame, &pcdev->flags);
+
+ for (frame = 0; frame < pcdev->nbufs; frame++)
+ if (irqs & (IRQ_EOF0 << frame) &&
+ test_bit(CF_FRAME_SOF0 + frame, &pcdev->flags)) {
+ mmp_frame_complete(pcdev, frame);
+ clear_bit(CF_FRAME_SOF0 + frame, &pcdev->flags);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int mmp_camera_add_device(struct soc_camera_device *icd)
+{
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ int ret = 0;
+
+ if (pcdev->icd)
+ return -EBUSY;
+
+ pcdev->frame_state.frames = 0;
+ pcdev->frame_state.singles = 0;
+ pcdev->frame_state.delivered = 0;
+
+ pcdev->icd = icd;
+ pcdev->state = S_IDLE;
+ ccic_power_up(pcdev);
+ ccic_enable_clk(pcdev);
+ ccic_stop(pcdev);
+ /*
+ * Mask all interrupts.
+ */
+ ccic_reg_write(pcdev, REG_IRQMASK, 0);
+ ret = v4l2_subdev_call(sd, core, init, 0);
+ /*
+ * When v4l2_subdev_call return -ENOIOCTLCMD,
+ * means No ioctl command
+ */
+ if ((ret < 0) && (ret != -ENOIOCTLCMD) && (ret != -ENODEV)) {
+ dev_info(icd->parent,
+ "camera: Failed to initialize subdev: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mmp_camera_remove_device(struct soc_camera_device *icd)
+{
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+
+ BUG_ON(icd != pcdev->icd);
+
+ dev_err(&pcdev->pdev->dev,
+ "Release %d frames, %d singles, %d delivered\n",
+ pcdev->frame_state.frames, pcdev->frame_state.singles,
+ pcdev->frame_state.delivered);
+ ccic_config_phy(pcdev, 0);
+ ccic_disable_clk(pcdev);
+ ccic_power_down(pcdev);
+ pcdev->icd = NULL;
+}
+
+static int mmp_camera_set_bus_param(struct soc_camera_device *icd)
+{
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct device *dev = &pcdev->pdev->dev;
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct v4l2_mbus_config cfg;
+ int ret = 0;
+
+ ret = v4l2_subdev_call(sd, video, g_mbus_config, &cfg);
+ if (ret < 0) {
+ dev_err(dev, "%s %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ ret = v4l2_subdev_call(sd, video, s_mbus_config, &cfg);
+ if (ret < 0) {
+ dev_err(dev, "%s %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mmp_camera_set_fmt(struct soc_camera_device *icd,
+ struct v4l2_format *f)
+{
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+ struct device *dev = &pcdev->pdev->dev;
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ const struct soc_camera_format_xlate *xlate = NULL;
+ struct v4l2_mbus_framefmt mf;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct v4l2_subdev_frame_interval inter;
+ int ret = 0;
+
+ dev_dbg(dev, "camera: set_fmt: %c, width = %u, height = %u\n",
+ pix->pixelformat, pix->width, pix->height);
+ xlate = soc_camera_xlate_by_fourcc(icd, pix->pixelformat);
+ if (!xlate) {
+ dev_err(dev, "camera: format: %c not found\n",
+ pix->pixelformat);
+ return -EINVAL;
+ }
+
+ mf.width = pix->width;
+ mf.height = pix->height;
+ mf.field = V4L2_FIELD_NONE;
+ mf.colorspace = pix->colorspace;
+ mf.code = xlate->code;
+
+ ret = v4l2_subdev_call(sd, video, s_mbus_fmt, &mf);
+ if (ret < 0) {
+ dev_err(dev, "camera: set_fmt failed %d\n", __LINE__);
+ return ret;
+ }
+
+ if (mf.code != xlate->code) {
+ dev_err(dev, "camera: wrong code %s %d\n", __func__, __LINE__);
+ return -EINVAL;
+ }
+
+ /*
+ * To get frame_rate
+ */
+ inter.pad = pdata->mclk_min;
+ ret = v4l2_subdev_call(sd, video, g_frame_interval, &inter);
+ if (ret < 0) {
+ dev_err(dev, "camera: Can't get frame rate %s %d\n",
+ __func__, __LINE__);
+ pcdev->frame_rate = 0;
+ } else
+ pcdev->frame_rate =
+ inter.interval.numerator / inter.interval.denominator;
+
+ pix->width = mf.width;
+ pix->height = mf.height;
+ pix->field = mf.field;
+ pix->colorspace = mf.colorspace;
+ pcdev->pix_format.sizeimage = pix->sizeimage;
+ icd->current_fmt = xlate;
+
+ memcpy(&(pcdev->pix_format), pix, sizeof(struct v4l2_pix_format));
+ ret = ccic_config_image(pcdev);
+
+ return ret;
+}
+
+static int mmp_camera_try_fmt(struct soc_camera_device *icd,
+ struct v4l2_format *f)
+{
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct device *dev = &pcdev->pdev->dev;
+ const struct soc_camera_format_xlate *xlate;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct v4l2_mbus_framefmt mf;
+ __u32 pixfmt = pix->pixelformat;
+ int ret = 0;
+
+ xlate = soc_camera_xlate_by_fourcc(icd, pixfmt);
+ if (!xlate) {
+ dev_err(dev, "camera: format: %c not found\n",
+ pix->pixelformat);
+ return -EINVAL;
+ }
+
+ pix->bytesperline = soc_mbus_bytes_per_line(pix->width,
+ xlate->host_fmt);
+ if (pix->bytesperline < 0)
+ return pix->bytesperline;
+ if (pix->pixelformat == V4L2_PIX_FMT_JPEG) {
+ /*
+ * Todo: soc_camera_try_fmt could clear
+ * sizeimage, we can't get the value from
+ * userspace, just hard coding
+ */
+ pix->bytesperline = 2048;
+ } else
+ pix->sizeimage = pix->height * pix->bytesperline;
+
+ /*
+ * limit to sensor capabilities
+ */
+ mf.width = pix->width;
+ mf.height = pix->height;
+ mf.field = V4L2_FIELD_NONE;
+ mf.colorspace = pix->colorspace;
+ mf.code = xlate->code;
+
+ ret = v4l2_subdev_call(sd, video, try_mbus_fmt, &mf);
+ if (ret < 0)
+ return ret;
+
+ pix->width = mf.width;
+ pix->height = mf.height;
+ pix->colorspace = mf.colorspace;
+
+ switch (mf.field) {
+ case V4L2_FIELD_ANY:
+ case V4L2_FIELD_NONE:
+ pix->field = V4L2_FIELD_NONE;
+ break;
+ default:
+ dev_err(dev, "camera: Field type %d unsupported.\n", mf.field);
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static unsigned int mmp_camera_poll(struct file *file, poll_table *pt)
+{
+ struct soc_camera_device *icd = file->private_data;
+
+ return vb2_poll(&icd->vb2_vidq, file, pt);
+}
+
+static int mmp_camera_querycap(struct soc_camera_host *ici,
+ struct v4l2_capability *cap)
+{
+ struct v4l2_dbg_chip_ident id;
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct soc_camera_device *icd = pcdev->icd;
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct device *dev = &pcdev->pdev->dev;
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+ int ret = 0;
+
+ cap->version = KERNEL_VERSION(0, 0, 5);
+ cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ ret = v4l2_subdev_call(sd, core, g_chip_ident, &id);
+ if (ret < 0) {
+ dev_err(dev, "%s %d\n", __func__, __LINE__);
+ return ret;
+ }
+
+ strcpy(cap->card, pdata->name);
+ strcpy(cap->driver, (const char *)id.ident);
+
+ return 0;
+}
+
+static int mmp_camera_set_parm(struct soc_camera_device *icd,
+ struct v4l2_streamparm *para)
+{
+ return 0;
+}
+
+static int mmp_camera_get_formats(struct soc_camera_device *icd, u32 idx,
+ struct soc_camera_format_xlate *xlate)
+{
+ struct v4l2_subdev *sd = soc_camera_to_subdev(icd);
+ struct soc_camera_host *ici = to_soc_camera_host(icd->parent);
+ struct mmp_camera_dev *pcdev = ici->priv;
+ struct device *dev = &pcdev->pdev->dev;
+ enum v4l2_mbus_pixelcode code;
+ const struct soc_mbus_pixelfmt *fmt;
+ int formats = 0, ret = 0, i;
+
+ ret = v4l2_subdev_call(sd, video, enum_mbus_fmt, idx, &code);
+ if (ret < 0)
+ /*
+ * No more formats
+ */
+ return 0;
+
+ fmt = soc_mbus_get_fmtdesc(code);
+ if (!fmt) {
+ dev_err(dev, "camera: Invalid format #%u: %d\n", idx, code);
+ return 0;
+ }
+
+ switch (code) {
+ /*
+ * Refer to mbus_fmt struct
+ */
+ case V4L2_MBUS_FMT_UYVY8_2X8:
+ /*
+ * Add support for YUV420 and YUV422P
+ */
+ formats = ARRAY_SIZE(ccic_formats);
+ if (xlate) {
+ for (i = 0; i < ARRAY_SIZE(ccic_formats); i++) {
+ xlate->host_fmt = &ccic_formats[i];
+ xlate->code = code;
+ xlate++;
+ }
+ }
+ return formats;
+ case V4L2_MBUS_FMT_JPEG_1X8:
+ if (xlate)
+ dev_err(dev, "camera: Providing format: %s\n",
+ fmt->name);
+ break;
+ default:
+ /*
+ * camera controller can not support
+ * this format, which might supported by the sensor
+ */
+ dev_warn(dev, "camera: Not support fmt: %s\n", fmt->name);
+ return 0;
+ }
+
+ formats++;
+ if (xlate) {
+ xlate->host_fmt = fmt;
+ xlate->code = code;
+ xlate++;
+ }
+
+ return formats;
+}
+
+static struct soc_camera_host_ops mmp_soc_camera_host_ops = {
+ .owner = THIS_MODULE,
+ .add = mmp_camera_add_device,
+ .remove = mmp_camera_remove_device,
+ .set_fmt = mmp_camera_set_fmt,
+ .try_fmt = mmp_camera_try_fmt,
+ .set_parm = mmp_camera_set_parm,
+ .init_videobuf2 = mmp_camera_init_videobuf,
+ .poll = mmp_camera_poll,
+ .querycap = mmp_camera_querycap,
+ .set_bus_param = mmp_camera_set_bus_param,
+ .get_formats = mmp_camera_get_formats,
+};
+
+static int __devinit mmp_camera_probe(struct platform_device *pdev)
+{
+ struct mmp_camera_dev *pcdev;
+ struct mmp_cam_pdata *pdata;
+ struct resource *res;
+ void __iomem *base;
+ int irq;
+ int err = 0;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata || !pdata->init_clk || !pdata->enable_clk)
+ return -EINVAL;
+
+ dev_info(&pdev->dev, "camera: probing CCIC%d\n", pdev->id + 1);
+
+ pcdev = devm_kzalloc(&pdev->dev, sizeof(*pcdev), GFP_KERNEL);
+ if (!pcdev) {
+ dev_err(&pdev->dev, "camera: Could not allocate pcdev\n");
+ return -ENOMEM;
+ }
+
+ pcdev->pdev = pdev;
+
+ err = pdata->init_clk(&pdev->dev, 1);
+ if (err)
+ goto exit_clk;
+
+ INIT_LIST_HEAD(&pcdev->buffers);
+
+ spin_lock_init(&pcdev->list_lock);
+ mutex_init(&pcdev->s_mutex);
+
+ /*
+ * Request the regions and ioremap
+ */
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ base = devm_request_and_ioremap(&pdev->dev, res);
+ if (!base) {
+ dev_err(&pdev->dev,
+ "camera: Failed to request and remap io memory\n");
+ return -ENXIO;
+ }
+
+ pcdev->res = res;
+ pcdev->base = base;
+
+ /*
+ * Request irq
+ */
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "camera: Failed to get irq resource\n");
+ return -ENXIO;
+ }
+
+ pcdev->irq = irq;
+ err = devm_request_irq(&pdev->dev, pcdev->irq, mmp_camera_frameirq,
+ IRQF_SHARED, MMP_CAM_DRV_NAME, pcdev);
+ if (err) {
+ dev_err(&pdev->dev, "camera: Interrupt request failed\n");
+ goto exit_clk;
+ }
+
+ ccic_enable_clk(pcdev);
+ pcdev->soc_host.drv_name = MMP_CAM_DRV_NAME;
+ pcdev->soc_host.ops = &mmp_soc_camera_host_ops;
+ pcdev->soc_host.priv = pcdev;
+ pcdev->soc_host.v4l2_dev.dev = &pdev->dev;
+ pcdev->soc_host.nr = pdev->id;
+ pcdev->vb_alloc_ctx = (struct vb2_alloc_ctx *)
+ vb2_dma_contig_init_ctx(&pdev->dev);
+ if (IS_ERR(pcdev->vb_alloc_ctx)) {
+ err = PTR_ERR(pcdev->vb_alloc_ctx);
+ goto exit_clk;
+ }
+
+ err = soc_camera_host_register(&pcdev->soc_host);
+ if (err)
+ goto exit_free_ctx;
+
+ return 0;
+
+exit_free_ctx:
+ vb2_dma_contig_cleanup_ctx(pcdev->vb_alloc_ctx);
+exit_clk:
+ pdata->init_clk(&pdev->dev, 0);
+
+ return err;
+}
+
+static int __devexit mmp_camera_remove(struct platform_device *pdev)
+{
+
+ struct soc_camera_host *soc_host = to_soc_camera_host(&pdev->dev);
+ struct mmp_camera_dev *pcdev = container_of(soc_host,
+ struct mmp_camera_dev, soc_host);
+ struct mmp_cam_pdata *pdata = pcdev->pdev->dev.platform_data;
+
+ pdata->init_clk(&pdev->dev, 0);
+ ccic_power_down(pcdev);
+ soc_camera_host_unregister(soc_host);
+ vb2_dma_contig_cleanup_ctx(pcdev->vb_alloc_ctx);
+ pcdev->vb_alloc_ctx = NULL;
+ dev_info(&pdev->dev, "camera: MMP Camera driver unloaded\n");
+
+ return 0;
+}
+
+static struct platform_driver mmp_camera_driver = {
+ .driver = {
+ .name = MMP_CAM_DRV_NAME,
+ },
+ .probe = mmp_camera_probe,
+ .remove = __devexit_p(mmp_camera_remove),
+};
+
+module_platform_driver(mmp_camera_driver);
+
+MODULE_DESCRIPTION("Marvell MMP CMOS Camera Interface Controller driver");
+MODULE_AUTHOR("Kassey Lee <ygli@marvell.com>");
+MODULE_AUTHOR("Angela Wan <jwan@marvell.com>");
+MODULE_AUTHOR("Albert Wang <twang13@marvell.com>");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("Video");
+
diff --git a/drivers/media/video/mmp_camera.h b/drivers/media/video/mmp_camera.h
new file mode 100644
index 0000000..106892a
--- /dev/null
+++ b/drivers/media/video/mmp_camera.h
@@ -0,0 +1,241 @@
+/*
+ * Register definitions for the m88alp01 camera interface.
+ * Offsets in bytes as given in the spec.
+ *
+ * Copyright 2006 One Laptop Per Child Association, Inc.
+ * Written by Jonathan Corbet <corbet@lwn.net>
+ *
+ * Copyright (C) 2011-2012, Marvell International Ltd.
+ * Modified by Kassey Lee <ygli@marvell.com>
+ * Albert Wang <twang13@marvell.com>
+ *
+ * This file may be distributed under the terms of the GNU General
+ * Public License, version 2.
+ */
+#ifndef __MMP_CAMERA_H
+#define __MMP_CAMERA_H
+
+/*
+ * Y.U.V. reg define
+ */
+#define REG_Y0BAR 0x00
+#define REG_Y1BAR 0x04
+#define REG_Y2BAR 0x08
+#define REG_U0BAR 0x0c
+#define REG_U1BAR 0x10
+#define REG_U2BAR 0x14
+#define REG_V0BAR 0x18
+#define REG_V1BAR 0x1C
+#define REG_V2BAR 0x20
+
+/*
+ * MIPI enable
+ */
+#define REG_CSI2_CTRL0 0x100
+#define REG_CSI2_DPHY3 0x12c
+#define REG_CSI2_DPHY5 0x134
+#define REG_CSI2_DPHY6 0x138
+
+#define REG_IMGPITCH 0x24 /* Image pitch register */
+#define IMGP_YP_SHFT 2 /* Y pitch params */
+#define IMGP_YP_MASK 0x00003ffc /* Y pitch field */
+#define IMGP_UVP_SHFT 18 /* UV pitch (planar) */
+#define IMGP_UVP_MASK 0x3ffc0000
+
+#define REG_IRQSTATRAW 0x28 /* RAW IRQ Status */
+
+#define REG_IRQMASK 0x2c /* IRQ mask - same bits as IRQSTAT */
+
+#define REG_IRQSTAT 0x30 /* IRQ status / clear */
+#define IRQ_EOF0 0x00000001 /* End of frame 0 */
+#define IRQ_EOF1 0x00000002 /* End of frame 1 */
+#define IRQ_EOF2 0x00000004 /* End of frame 2 */
+#define IRQ_SOF0 0x00000008 /* Start of frame 0 */
+#define IRQ_SOF1 0x00000010 /* Start of frame 1 */
+#define IRQ_SOF2 0x00000020 /* Start of frame 2 */
+#define IRQ_OVERFLOW 0x00000040 /* FIFO overflow */
+#define FRAMEIRQS_EOF (IRQ_EOF0 | IRQ_EOF1 | IRQ_EOF2)
+#define FRAMEIRQS_SOF (IRQ_SOF0 | IRQ_SOF1 | IRQ_SOF2)
+#define FRAMEIRQS (IRQ_EOF0 | IRQ_EOF1 | IRQ_EOF2 | \
+ IRQ_SOF0 | IRQ_SOF1 | IRQ_SOF2)
+#define ALLIRQS (FRAMEIRQS | IRQ_OVERFLOW)
+
+#define REG_IMGSIZE 0x34 /* Image size */
+#define IMGSZ_V_MASK 0x1fff0000
+#define IMGSZ_V_SHIFT 16
+#define IMGSZ_H_MASK 0x00003fff
+
+#define REG_IMGOFFSET 0x38 /* IMage offset */
+
+#define REG_CTRL0 0x3c /* Control 0 */
+#define C0_ENABLE 0x00000001 /* Makes the whole thing go */
+/* Mask for all the format bits */
+#define C0_DF_MASK 0x00fffffc /* Bits 2-23 */
+/* RGB ordering */
+#define C0_RGB4_RGBX 0x00000000
+#define C0_RGB4_XRGB 0x00000004
+#define C0_RGB4_BGRX 0x00000008
+#define C0_RGB4_XBGR 0x0000000c
+#define C0_RGB5_RGGB 0x00000000
+#define C0_RGB5_GRBG 0x00000004
+#define C0_RGB5_GBRG 0x00000008
+#define C0_RGB5_BGGR 0x0000000c
+/* Spec has two fields for DIN and DOUT, but they must match, so
+ combine them here. */
+#define C0_DF_YUV 0x00000000 /* Data is YUV */
+#define C0_DF_RGB 0x000000a0 /* Data is RGB */
+#define C0_DF_BAYER 0x00000140 /* Data is Bayer */
+/* 8-8-8 must be missing from the below - ask */
+#define C0_RGBF_565 0x00000000
+#define C0_RGBF_444 0x00000800
+#define C0_RGB_BGR 0x00001000 /* Blue comes first */
+#define C0_YUV_PLANAR 0x00000000 /* YUV 422 planar format */
+#define C0_YUV_PACKED 0x00008000 /* YUV 422 packed format */
+#define C0_YUV_420PL 0x0000a000 /* YUV 420 planar format */
+/* Think that 420 packed must be 111 - ask */
+#define C0_YUVE_YUYV 0x00000000 /* Y1CbY0Cr */
+#define C0_YUVE_YVYU 0x00010000 /* Y1CrY0Cb */
+#define C0_YUVE_VYUY 0x00020000 /* CrY1CbY0 */
+#define C0_YUVE_UYVY 0x00030000 /* CbY1CrY0 */
+#define C0_YUVE_XYUV 0x00000000 /* 420: .YUV */
+#define C0_YUVE_XYVU 0x00010000 /* 420: .YVU */
+#define C0_YUVE_XUVY 0x00020000 /* 420: .UVY */
+#define C0_YUVE_XVUY 0x00030000 /* 420: .VUY */
+/* Bayer bits 18,19 if needed */
+#define C0_HPOL_LOW 0x01000000 /* HSYNC polarity active low */
+#define C0_VPOL_LOW 0x02000000 /* VSYNC polarity active low */
+#define C0_VCLK_LOW 0x04000000 /* VCLK on falling edge */
+#define C0_DOWNSCALE 0x08000000 /* Enable downscaler */
+#define C0_SIFM_MASK 0xc0000000 /* SIF mode bits */
+#define C0_SIF_HVSYNC 0x00000000 /* Use H/VSYNC */
+#define C0_SOF_NOSYNC 0x40000000 /* Use inband active signaling */
+#define C0_EOF_VSYNC 0x00400000 /* Generate EOF by VSYNC */
+#define C0_VEDGE_CTRL 0x00800000 /* Detecting falling edge of VSYNC */
+
+#define REG_CTRL1 0x40 /* Control 1 */
+#define C1_RESERVED 0x0000003c /* Reserved and shouldn't be changed */
+#define C1_444ALPHA 0x00f00000 /* Alpha field in RGB444 */
+#define C1_ALPHA_SHFT 20
+#define C1_DMAB64 0x00000000 /* 64-byte DMA burst */
+#define C1_DMAB128 0x02000000 /* 128-byte DMA burst */
+#define C1_DMAB256 0x04000000 /* 256-byte DMA burst */
+#define C1_DMAB_MASK 0x06000000
+#define C1_TWOBUFS 0x08000000 /* Use only two DMA buffers */
+#define C1_PWRDWN 0x10000000 /* Power down */
+#define C1_DMAPOSTED 0x40000000 /* DMA Posted Select */
+
+#define REG_CTRL3 0x1ec /* CCIC parallel mode */
+
+#define REG_LNNUM 0x60 /* Lines num DMA filled */
+
+#define REG_CLKCTRL 0x88 /* Clock control */
+#define CLK_DIV_MASK 0x0000ffff /* Upper bits RW "reserved" */
+
+/*
+ * Indicate flags for CCIC frame buffer state
+ * 0 - No available buffer, will enter signle buffer mode
+ * 1:3 - Normal mode, indicate which frame buffer is used in CCIC
+ */
+#define CF_SINGLE_BUF 0
+#define CF_FRAME_SOF0 1
+
+/*
+ * CCIC can support at most 3 frame buffers
+ * 2 - Use Two Buffers mode
+ * 3 - Use Three Buffers mode
+ */
+#define MAX_DMA_BUFS 2
+
+/*
+ * Basic frame states
+ */
+struct mmp_frame_state {
+ int frames;
+ int singles;
+ int delivered;
+};
+
+struct yuv_pointer_t {
+ dma_addr_t y;
+ dma_addr_t u;
+ dma_addr_t v;
+};
+
+/*
+ * buffer for one video frame
+ */
+struct mmp_buffer {
+ /*
+ * common v4l buffer stuff -- must be first
+ */
+ struct vb2_buffer vb_buf;
+ struct yuv_pointer_t yuv_p;
+ struct list_head queue;
+ struct page *page;
+ size_t bsize;
+ int list_init_flag;
+};
+
+enum mmp_camera_state {
+ S_IDLE, /* Just hanging around */
+ S_STREAMING, /* Streaming data */
+ S_BUFWAIT /* streaming requested but no buffers yet */
+};
+
+struct mmp_camera_dev {
+ struct soc_camera_host soc_host;
+ struct soc_camera_device *icd;
+ unsigned int irq;
+ void __iomem *base;
+ struct platform_device *pdev;
+ struct resource *res;
+ struct list_head buffers; /* Available frames */
+ spinlock_t list_lock;
+ struct mutex s_mutex; /* Access to this structure */
+ struct v4l2_pix_format pix_format;
+ unsigned long flags; /* Indicate frame buffer state */
+ struct mmp_frame_state frame_state;
+ enum mmp_camera_state state;
+ struct mmp_buffer *vb_bufs[MAX_DMA_BUFS];
+ unsigned int nbufs; /* How many bufs are used for ccic */
+ struct vb2_alloc_ctx *vb_alloc_ctx;
+ int frame_rate;
+};
+
+/*
+ * Device register I/O
+ */
+static inline u32 ccic_reg_read(struct mmp_camera_dev *pcdev, unsigned int reg)
+{
+ return ioread32(pcdev->base + reg);
+}
+
+static inline void ccic_reg_write(struct mmp_camera_dev *pcdev,
+ unsigned int reg, u32 val)
+{
+ iowrite32(val, pcdev->base + reg);
+}
+
+static inline void ccic_reg_write_mask(struct mmp_camera_dev *pcdev,
+ unsigned int reg, u32 val, u32 mask)
+{
+ u32 v = ccic_reg_read(pcdev, reg);
+
+ v = (v & ~mask) | (val & mask);
+ ccic_reg_write(pcdev, reg, v);
+}
+
+static inline void ccic_reg_set_bit(struct mmp_camera_dev *pcdev,
+ unsigned int reg, u32 val)
+{
+ ccic_reg_write_mask(pcdev, reg, val, val);
+}
+
+static inline void ccic_reg_clear_bit(struct mmp_camera_dev *pcdev,
+ unsigned int reg, u32 val)
+{
+ ccic_reg_write_mask(pcdev, reg, 0, val);
+}
+
+#endif
+
--
1.7.0.4
next reply other threads:[~2012-07-11 14:24 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2012-07-11 14:22 Albert Wang [this message]
2012-07-14 17:14 ` [PATCH 1/7] media: mmp_camera: Add V4l2 camera driver for Marvell PXA910/PXA688/PXA2128 CCIC Jonathan Corbet
2012-07-16 12:44 ` Guennadi Liakhovetski
[not found] ` <477F20668A386D41ADCC57781B1F7043083AB33977@SC-VEXCH1.marvell.com>
2012-07-16 17:18 ` Guennadi Liakhovetski
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1342016549-23084-1-git-send-email-twang13@marvell.com \
--to=twang13@marvell.com \
--cc=corbet@lwn.net \
--cc=g.liakhovetski@gmx.de \
--cc=linux-media@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.