summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRui Miguel Silva <rui.silva@linaro.org>2018-02-09 11:37:43 +0000
committerBryan O'Donoghue <bryan.odonoghue@linaro.org>2019-05-01 11:51:22 +0100
commit4440c43f08d138aec584472d5a5eb414235d5b42 (patch)
tree5f36c44f8196ff03faefca8bf90aa17fc7e4f528
parent07eac8bf4d32bb315370245fc65a01872ab8471d (diff)
staging: media: add IMX7 CSI capture and control driver
Add a capture and CSI control driver to support camera capturing and streaming in a IMX7 arch type. Signed-off-by: Rui Miguel Silva <rui.silva@linaro.org>
-rw-r--r--drivers/staging/media/imx/Makefile2
-rw-r--r--drivers/staging/media/imx/imx7-media-csi.c1895
2 files changed, 1897 insertions, 0 deletions
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index 9c3ed21735f2..c11d51259af1 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -11,4 +11,6 @@ obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx-media-ic.o
obj-$(CONFIG_VIDEO_IMX_CSI) += imx-media-csi.o
obj-$(CONFIG_VIDEO_IMX_CSI) += imx6-mipi-csi2.o
+
+obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-media-csi.o
obj-$(CONFIG_VIDEO_IMX7_CSI) += imx7-mipi-csis.o
diff --git a/drivers/staging/media/imx/imx7-media-csi.c b/drivers/staging/media/imx/imx7-media-csi.c
new file mode 100644
index 000000000000..33ad62edac4e
--- /dev/null
+++ b/drivers/staging/media/imx/imx7-media-csi.c
@@ -0,0 +1,1895 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Video Capture dev for Freescale i.MX7 SoC
+ *
+ * Copyright (C) 2018 Linaro Ltd
+ * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ *
+ */
+
+#include <asm/dma.h>
+#include <linux/clk.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/fs.h>
+#include <linux/gcd.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/mfd/syscon.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/time.h>
+#include <linux/media-bus-format.h>
+
+#include <media/v4l2-common.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#define IMX7_CAM_DRV_NAME "imx7-csi"
+#define IMX7_CAM_VERSION "0.0.1"
+#define IMX7_CAM_DRIVER_DESCRIPTION "i.IMX7_CSI"
+
+#define MAX_VIDEO_MEM 64
+
+/* reset values */
+#define CSICR1_RESET_VAL 0x40000800
+#define CSICR2_RESET_VAL 0x0
+#define CSICR3_RESET_VAL 0x0
+
+/* csi control reg 1 */
+#define BIT_SWAP16_EN BIT(31)
+#define BIT_EXT_VSYNC BIT(30)
+#define BIT_EOF_INT_EN BIT(29)
+#define BIT_PRP_IF_EN BIT(28)
+#define BIT_CCIR_MODE BIT(27)
+#define BIT_COF_INT_EN BIT(26)
+#define BIT_SF_OR_INTEN BIT(25)
+#define BIT_RF_OR_INTEN BIT(24)
+#define BIT_SFF_DMA_DONE_INTEN BIT(22)
+#define BIT_STATFF_INTEN BIT(21)
+#define BIT_FB2_DMA_DONE_INTEN BIT(20)
+#define BIT_FB1_DMA_DONE_INTEN BIT(19)
+#define BIT_RXFF_INTEN BIT(18)
+#define BIT_SOF_POL BIT(17)
+#define BIT_SOF_INTEN BIT(16)
+#define BIT_MCLKDIV (0xF << 12)
+#define BIT_HSYNC_POL BIT(11)
+#define BIT_CCIR_EN BIT(10)
+#define BIT_MCLKEN BIT(9)
+#define BIT_FCC BIT(8)
+#define BIT_PACK_DIR BIT(7)
+#define BIT_CLR_STATFIFO BIT(6)
+#define BIT_CLR_RXFIFO BIT(5)
+#define BIT_GCLK_MODE BIT(4)
+#define BIT_INV_DATA BIT(3)
+#define BIT_INV_PCLK BIT(2)
+#define BIT_REDGE BIT(1)
+#define BIT_PIXEL_BIT BIT(0)
+
+#define SHIFT_MCLKDIV 12
+
+/* control reg 3 */
+#define BIT_FRMCNT (0xFFFF << 16)
+#define BIT_FRMCNT_RST BIT(15)
+#define BIT_DMA_REFLASH_RFF BIT(14)
+#define BIT_DMA_REFLASH_SFF BIT(13)
+#define BIT_DMA_REQ_EN_RFF BIT(12)
+#define BIT_DMA_REQ_EN_SFF BIT(11)
+#define BIT_STATFF_LEVEL (0x7 << 8)
+#define BIT_HRESP_ERR_EN BIT(7)
+#define BIT_RXFF_LEVEL (0x7 << 4)
+#define BIT_TWO_8BIT_SENSOR BIT(3)
+#define BIT_ZERO_PACK_EN BIT(2)
+#define BIT_ECC_INT_EN BIT(1)
+#define BIT_ECC_AUTO_EN BIT(0)
+
+#define SHIFT_FRMCNT 16
+#define SHIFT_RXFIFO_LEVEL 4
+
+/* csi status reg */
+#define BIT_ADDR_CH_ERR_INT BIT(28)
+#define BIT_FIELD0_INT BIT(27)
+#define BIT_FIELD1_INT BIT(26)
+#define BIT_SFF_OR_INT BIT(25)
+#define BIT_RFF_OR_INT BIT(24)
+#define BIT_DMA_TSF_DONE_SFF BIT(22)
+#define BIT_STATFF_INT BIT(21)
+#define BIT_DMA_TSF_DONE_FB2 BIT(20)
+#define BIT_DMA_TSF_DONE_FB1 BIT(19)
+#define BIT_RXFF_INT BIT(18)
+#define BIT_EOF_INT BIT(17)
+#define BIT_SOF_INT BIT(16)
+#define BIT_F2_INT BIT(15)
+#define BIT_F1_INT BIT(14)
+#define BIT_COF_INT BIT(13)
+#define BIT_HRESP_ERR_INT BIT(7)
+#define BIT_ECC_INT BIT(1)
+#define BIT_DRDY BIT(0)
+
+/* csi control reg 18 */
+#define BIT_CSI_ENABLE BIT(31)
+#define BIT_MIPI_DATA_FORMAT_RAW8 (0x2a << 25)
+#define BIT_MIPI_DATA_FORMAT_RAW10 (0x2b << 25)
+#define BIT_MIPI_DATA_FORMAT_YUV422_8B (0x1e << 25)
+#define BIT_MIPI_DATA_FORMAT_MASK (0x3F << 25)
+#define BIT_MIPI_DATA_FORMAT_OFFSET 25
+#define BIT_DATA_FROM_MIPI BIT(22)
+#define BIT_MIPI_YU_SWAP BIT(21)
+#define BIT_MIPI_DOUBLE_CMPNT BIT(20)
+#define BIT_BASEADDR_CHG_ERR_EN BIT(9)
+#define BIT_BASEADDR_SWITCH_SEL BIT(5)
+#define BIT_BASEADDR_SWITCH_EN BIT(4)
+#define BIT_PARALLEL24_EN BIT(3)
+#define BIT_DEINTERLACE_EN BIT(2)
+#define BIT_TVDECODER_IN_EN BIT(1)
+#define BIT_NTSC_EN BIT(0)
+
+#define CSI_MCLK_VF 1
+#define CSI_MCLK_ENC 2
+#define CSI_MCLK_RAW 4
+#define CSI_MCLK_I2C 8
+
+#define CSI_CSICR1 0x0
+#define CSI_CSICR2 0x4
+#define CSI_CSICR3 0x8
+#define CSI_STATFIFO 0xC
+#define CSI_CSIRXFIFO 0x10
+#define CSI_CSIRXCNT 0x14
+#define CSI_CSISR 0x18
+
+#define CSI_CSIDBG 0x1C
+#define CSI_CSIDMASA_STATFIFO 0x20
+#define CSI_CSIDMATS_STATFIFO 0x24
+#define CSI_CSIDMASA_FB1 0x28
+#define CSI_CSIDMASA_FB2 0x2C
+#define CSI_CSIFBUF_PARA 0x30
+#define CSI_CSIIMAG_PARA 0x34
+
+#define CSI_CSICR18 0x48
+#define CSI_CSICR19 0x4c
+
+#define NUM_FORMATS ARRAY_SIZE(formats)
+#define IMX7X_MAX_SENSORS 1
+
+/*
+ * Basic structures
+ */
+struct imx7_fmt {
+ char name[32];
+ u32 fourcc; /* v4l2 format id */
+ u32 pixelformat;
+ u32 mbus_code;
+ int bpp;
+};
+
+static struct imx7_fmt formats[] = {
+ {
+ .name = "UYVY-16",
+ .fourcc = V4L2_PIX_FMT_UYVY,
+ .pixelformat = V4L2_PIX_FMT_UYVY,
+ .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .bpp = 2,
+ }, {
+ .name = "YUYV-16",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .pixelformat = V4L2_PIX_FMT_YUYV,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .bpp = 2,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .pixelformat = V4L2_PIX_FMT_YUV32,
+ .mbus_code = MEDIA_BUS_FMT_AYUV8_1X32,
+ .bpp = 4,
+ }, {
+ .name = "RAWRGB8 (SBGGR8)",
+ .fourcc = V4L2_PIX_FMT_SBGGR8,
+ .pixelformat = V4L2_PIX_FMT_SBGGR8,
+ .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .bpp = 1,
+ }, {
+ .name = "RAWRGB10 (SBGGR10)",
+ .fourcc = V4L2_PIX_FMT_SBGGR10,
+ .pixelformat = V4L2_PIX_FMT_SBGGR10,
+ .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .bpp = 2,
+ }
+};
+
+struct imx7_buf_internal {
+ struct list_head queue;
+ int bufnum;
+ bool discard;
+};
+
+/* buffer for one video frame */
+struct imx7_buffer {
+ struct vb2_v4l2_buffer vb;
+ struct imx7_buf_internal internal;
+};
+
+struct imx7_csi_mux {
+ struct regmap *gpr;
+ u8 req_gpr;
+ u8 req_bit;
+};
+
+struct imx7_csi_dev {
+ struct device *dev;
+ struct video_device *vdev;
+ struct v4l2_subdev *sd;
+ struct v4l2_device v4l2_dev;
+ struct media_device media_dev;
+
+ struct vb2_queue vb2_vidq;
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ /* clock */
+ struct clk *clk_disp_axi;
+ struct clk *clk_disp_dcic;
+ struct clk *clk_csi_mclk;
+
+ void __iomem *regbase;
+ int irq;
+
+ u32 type;
+ u32 bytesperline;
+ v4l2_std_id std;
+ struct imx7_fmt *fmt;
+ struct v4l2_pix_format pix;
+ u32 mbus_code;
+
+ unsigned int frame_count;
+
+ struct list_head capture;
+ struct list_head active_bufs;
+ struct list_head discard;
+
+ void *discard_buffer;
+ dma_addr_t discard_buffer_dma;
+ size_t discard_size;
+ struct imx7_buf_internal buf_discard[2];
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_async_notifier subdev_notifier;
+ struct v4l2_async_subdev *async_subdevs[2];
+
+ bool csi_mux_mipi;
+ const bool *rx_fifo_rst;
+ struct imx7_csi_mux csi_mux;
+};
+
+static const struct of_device_id imx7_csi_dt_ids[];
+
+static int csi_read(struct imx7_csi_dev *csi, unsigned int offset)
+{
+ return __raw_readl(csi->regbase + offset);
+}
+
+static void csi_write(struct imx7_csi_dev *csi, unsigned int value,
+ unsigned int offset)
+{
+ __raw_writel(value, csi->regbase + offset);
+}
+
+static struct imx7_csi_dev *notifier_to_imx7_dev(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct imx7_csi_dev, subdev_notifier);
+}
+
+static struct imx7_fmt *format_by_fourcc(int fourcc)
+{
+ int i;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].pixelformat == fourcc)
+ return formats + i;
+ }
+
+ return NULL;
+}
+
+static struct imx7_fmt *format_by_mbus(u32 code)
+{
+ int i;
+
+ for (i = 0; i < NUM_FORMATS; i++) {
+ if (formats[i].mbus_code == code)
+ return formats + i;
+ }
+
+ return NULL;
+}
+
+static struct imx7_buffer *imx7_ibuf_to_buf(struct imx7_buf_internal *int_buf)
+{
+ return container_of(int_buf, struct imx7_buffer, internal);
+}
+
+static void csi_clk_enable(struct imx7_csi_dev *csi_dev)
+{
+ clk_prepare_enable(csi_dev->clk_disp_axi);
+ clk_prepare_enable(csi_dev->clk_disp_dcic);
+ clk_prepare_enable(csi_dev->clk_csi_mclk);
+}
+
+static void csi_clk_disable(struct imx7_csi_dev *csi_dev)
+{
+ clk_disable_unprepare(csi_dev->clk_csi_mclk);
+ clk_disable_unprepare(csi_dev->clk_disp_dcic);
+ clk_disable_unprepare(csi_dev->clk_disp_axi);
+}
+
+static void csihw_reset(struct imx7_csi_dev *csi_dev)
+{
+ __raw_writel(__raw_readl(csi_dev->regbase + CSI_CSICR3)
+ | BIT_FRMCNT_RST,
+ csi_dev->regbase + CSI_CSICR3);
+
+ __raw_writel(CSICR1_RESET_VAL, csi_dev->regbase + CSI_CSICR1);
+ __raw_writel(CSICR2_RESET_VAL, csi_dev->regbase + CSI_CSICR2);
+ __raw_writel(CSICR3_RESET_VAL, csi_dev->regbase + CSI_CSICR3);
+}
+
+static void csisw_reset(struct imx7_csi_dev *csi_dev)
+{
+ int cr1, cr3, cr18, isr;
+
+ /* Disable csi */
+ cr18 = csi_read(csi_dev, CSI_CSICR18);
+ cr18 &= ~BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+
+ /* Clear RX FIFO */
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 & ~BIT_FCC, CSI_CSICR1);
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 | BIT_CLR_RXFIFO, CSI_CSICR1);
+
+ /* DMA reflash */
+ cr3 = csi_read(csi_dev, CSI_CSICR3);
+ cr3 |= BIT_DMA_REFLASH_RFF | BIT_FRMCNT_RST;
+ csi_write(csi_dev, cr3, CSI_CSICR3);
+
+ usleep_range(2000, 3000);
+
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 | BIT_FCC, CSI_CSICR1);
+
+ isr = csi_read(csi_dev, CSI_CSISR);
+ csi_write(csi_dev, isr, CSI_CSISR);
+
+ /* Ensable csi */
+ cr18 |= BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+}
+
+static void csi_init_interface(struct imx7_csi_dev *csi_dev)
+{
+ unsigned int val = 0;
+ unsigned int imag_para;
+
+ val = BIT_SOF_POL | BIT_REDGE | BIT_GCLK_MODE | BIT_HSYNC_POL |
+ BIT_FCC | 1 << SHIFT_MCLKDIV | BIT_MCLKEN;
+ __raw_writel(val, csi_dev->regbase + CSI_CSICR1);
+
+ imag_para = (640 << 16) | 960;
+ __raw_writel(imag_para, csi_dev->regbase + CSI_CSIIMAG_PARA);
+
+ val = BIT_DMA_REFLASH_RFF;
+ __raw_writel(val, csi_dev->regbase + CSI_CSICR3);
+}
+
+static void csi_enable_int(struct imx7_csi_dev *csi_dev, int arg)
+{
+ unsigned long cr1 = __raw_readl(csi_dev->regbase + CSI_CSICR1);
+
+ cr1 |= BIT_SOF_INTEN;
+ cr1 |= BIT_RFF_OR_INT;
+ if (arg == 1) {
+ /* still capture needs DMA interrupt */
+ cr1 |= BIT_FB1_DMA_DONE_INTEN;
+ cr1 |= BIT_FB2_DMA_DONE_INTEN;
+ }
+ __raw_writel(cr1, csi_dev->regbase + CSI_CSICR1);
+}
+
+static void csi_disable_int(struct imx7_csi_dev *csi_dev)
+{
+ unsigned long cr1 = __raw_readl(csi_dev->regbase + CSI_CSICR1);
+
+ cr1 &= ~BIT_SOF_INTEN;
+ cr1 &= ~BIT_RFF_OR_INT;
+ cr1 &= ~BIT_FB1_DMA_DONE_INTEN;
+ cr1 &= ~BIT_FB2_DMA_DONE_INTEN;
+ __raw_writel(cr1, csi_dev->regbase + CSI_CSICR1);
+}
+
+static void csi_enable(struct imx7_csi_dev *csi_dev, int arg)
+{
+ unsigned long cr = __raw_readl(csi_dev->regbase + CSI_CSICR18);
+
+ if (arg == 1)
+ cr |= BIT_CSI_ENABLE;
+ else
+ cr &= ~BIT_CSI_ENABLE;
+ __raw_writel(cr, csi_dev->regbase + CSI_CSICR18);
+}
+
+static void csi_buf_stride_set(struct imx7_csi_dev *csi_dev, u32 stride)
+{
+ __raw_writel(stride, csi_dev->regbase + CSI_CSIFBUF_PARA);
+}
+
+static void csi_deinterlace_enable(struct imx7_csi_dev *csi_dev, bool enable)
+{
+ unsigned long cr18 = __raw_readl(csi_dev->regbase + CSI_CSICR18);
+
+ if (enable)
+ cr18 |= BIT_DEINTERLACE_EN;
+ else
+ cr18 &= ~BIT_DEINTERLACE_EN;
+
+ __raw_writel(cr18, csi_dev->regbase + CSI_CSICR18);
+}
+
+static void csi_deinterlace_mode(struct imx7_csi_dev *csi_dev, int mode)
+{
+ unsigned long cr18 = __raw_readl(csi_dev->regbase + CSI_CSICR18);
+
+ if (mode == V4L2_STD_NTSC)
+ cr18 |= BIT_NTSC_EN;
+ else
+ cr18 &= ~BIT_NTSC_EN;
+
+ __raw_writel(cr18, csi_dev->regbase + CSI_CSICR18);
+}
+
+static void csi_tvdec_enable(struct imx7_csi_dev *csi_dev, bool enable)
+{
+ unsigned long cr18 = __raw_readl(csi_dev->regbase + CSI_CSICR18);
+ unsigned long cr1 = __raw_readl(csi_dev->regbase + CSI_CSICR1);
+
+ if (enable) {
+ cr18 |= (BIT_TVDECODER_IN_EN |
+ BIT_BASEADDR_SWITCH_EN |
+ BIT_BASEADDR_SWITCH_SEL |
+ BIT_BASEADDR_CHG_ERR_EN);
+ cr1 |= BIT_CCIR_MODE;
+ cr1 &= ~(BIT_SOF_POL | BIT_REDGE);
+ } else {
+ cr18 &= ~(BIT_TVDECODER_IN_EN |
+ BIT_BASEADDR_SWITCH_EN |
+ BIT_BASEADDR_SWITCH_SEL |
+ BIT_BASEADDR_CHG_ERR_EN);
+ cr1 &= ~BIT_CCIR_MODE;
+ cr1 |= BIT_SOF_POL | BIT_REDGE;
+ }
+
+ __raw_writel(cr18, csi_dev->regbase + CSI_CSICR18);
+ __raw_writel(cr1, csi_dev->regbase + CSI_CSICR1);
+}
+
+static void csi_dmareq_rff_enable(struct imx7_csi_dev *csi_dev)
+{
+ unsigned long cr3 = __raw_readl(csi_dev->regbase + CSI_CSICR3);
+ unsigned long cr2 = __raw_readl(csi_dev->regbase + CSI_CSICR2);
+
+ /* Burst Type of DMA Transfer from RxFIFO. INCR16 */
+ cr2 |= 0xC0000000;
+
+ cr3 |= BIT_DMA_REQ_EN_RFF;
+ cr3 |= BIT_HRESP_ERR_EN;
+ cr3 &= ~BIT_RXFF_LEVEL;
+ cr3 |= 0x2 << 4;
+
+ __raw_writel(cr3, csi_dev->regbase + CSI_CSICR3);
+ __raw_writel(cr2, csi_dev->regbase + CSI_CSICR2);
+}
+
+static void csi_dmareq_rff_disable(struct imx7_csi_dev *csi_dev)
+{
+ unsigned long cr3 = __raw_readl(csi_dev->regbase + CSI_CSICR3);
+
+ cr3 &= ~BIT_DMA_REQ_EN_RFF;
+ cr3 &= ~BIT_HRESP_ERR_EN;
+ __raw_writel(cr3, csi_dev->regbase + CSI_CSICR3);
+}
+
+static void csi_set_imagpara(struct imx7_csi_dev *csi, int width, int height)
+{
+ int imag_para = 0;
+ unsigned long cr3 = __raw_readl(csi->regbase + CSI_CSICR3);
+
+ imag_para = (width << 16) | height;
+ __raw_writel(imag_para, csi->regbase + CSI_CSIIMAG_PARA);
+
+ /* reflash the embedded DMA controller */
+ __raw_writel(cr3 | BIT_DMA_REFLASH_RFF, csi->regbase + CSI_CSICR3);
+}
+
+static void csi_error_recovery(struct imx7_csi_dev *csi_dev)
+{
+ u32 cr1, cr3, cr18;
+ /* software reset */
+
+ /* Disable csi */
+ cr18 = csi_read(csi_dev, CSI_CSICR18);
+ cr18 &= ~BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+
+ /* Clear RX FIFO */
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 & ~BIT_FCC, CSI_CSICR1);
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 | BIT_CLR_RXFIFO, CSI_CSICR1);
+
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ csi_write(csi_dev, cr1 | BIT_FCC, CSI_CSICR1);
+
+ /* DMA reflash */
+ cr3 = csi_read(csi_dev, CSI_CSICR3);
+ cr3 |= BIT_DMA_REFLASH_RFF;
+ csi_write(csi_dev, cr3, CSI_CSICR3);
+
+ /* Ensable csi */
+ cr18 |= BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+}
+
+/*
+ * Videobuf operations
+ */
+static int imx7_videobuf_setup(struct vb2_queue *vq, unsigned int *count,
+ unsigned int *num_planes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct imx7_csi_dev *csi_dev = vb2_get_drv_priv(vq);
+
+ alloc_devs[0] = csi_dev->dev;
+
+ sizes[0] = csi_dev->pix.sizeimage;
+
+ dev_dbg(csi_dev->dev, "count=%d, size=%d\n", *count, sizes[0]);
+
+ if (*count == 0)
+ *count = 32;
+ if (!*num_planes &&
+ sizes[0] * *count > MAX_VIDEO_MEM * 1024 * 1024)
+ *count = (MAX_VIDEO_MEM * 1024 * 1024) / sizes[0];
+
+ *num_planes = 1;
+
+ return 0;
+}
+
+static int imx7_videobuf_prepare(struct vb2_buffer *vb)
+{
+ struct imx7_csi_dev *csi_dev = vb2_get_drv_priv(vb->vb2_queue);
+
+ dev_dbg(csi_dev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__,
+ vb, vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+
+#ifdef DEBUG
+ /*
+ * This can be useful if you want to see if we actually fill
+ * the buffer with something
+ */
+ if (vb2_plane_vaddr(vb, 0))
+ memset((void *)vb2_plane_vaddr(vb, 0),
+ 0xaa, vb2_get_plane_payload(vb, 0));
+#endif
+ if (vb2_plane_size(vb, 0) < csi_dev->pix.sizeimage) {
+ dev_info(csi_dev->dev,
+ "data will not fit into plane (%lu < %lu)\n",
+ vb2_plane_size(vb, 0), (long)csi_dev->pix.sizeimage);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb, 0, csi_dev->pix.sizeimage);
+ if (vb2_plane_vaddr(vb, 0) &&
+ vb2_get_plane_payload(vb, 0) > vb2_plane_size(vb, 0))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void imx7_videobuf_queue(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+ struct imx7_csi_dev *csi_dev = vb2_get_drv_priv(vb->vb2_queue);
+ struct imx7_buffer *buf = container_of(vbuf, struct imx7_buffer, vb);
+ unsigned long flags;
+
+ dev_dbg(csi_dev->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(&csi_dev->slock, flags);
+
+ list_add_tail(&buf->internal.queue, &csi_dev->capture);
+
+ spin_unlock_irqrestore(&csi_dev->slock, flags);
+}
+
+static void imx7_update_csi_buf(struct imx7_csi_dev *csi_dev,
+ unsigned long phys, int bufnum)
+{
+ if (bufnum == 1)
+ csi_write(csi_dev, phys, CSI_CSIDMASA_FB2);
+ else
+ csi_write(csi_dev, phys, CSI_CSIDMASA_FB1);
+}
+
+static void imx7_csi_init(struct imx7_csi_dev *csi_dev)
+{
+ csi_clk_enable(csi_dev);
+ csihw_reset(csi_dev);
+ csi_init_interface(csi_dev);
+ csi_dmareq_rff_disable(csi_dev);
+}
+
+static void imx7_csi_deinit(struct imx7_csi_dev *csi_dev)
+{
+ csihw_reset(csi_dev);
+ csi_init_interface(csi_dev);
+ csi_dmareq_rff_disable(csi_dev);
+ csi_clk_disable(csi_dev);
+}
+
+static int imx7_csi_enable(struct imx7_csi_dev *csi_dev)
+{
+ struct v4l2_pix_format *pix = &csi_dev->pix;
+ unsigned long flags;
+ unsigned long val;
+ int timeout, timeout2;
+
+ csisw_reset(csi_dev);
+
+ if (pix->field == V4L2_FIELD_INTERLACED)
+ csi_tvdec_enable(csi_dev, true);
+
+ /* For mipi csi input only */
+ if (csi_dev->csi_mux_mipi) {
+ csi_dmareq_rff_enable(csi_dev);
+ csi_enable_int(csi_dev, 1);
+ csi_enable(csi_dev, 1);
+ return 0;
+ }
+
+ local_irq_save(flags);
+ for (timeout = 10000000; timeout > 0; timeout--) {
+ if (!(csi_read(csi_dev, CSI_CSISR) & BIT_SOF_INT)) {
+ cpu_relax();
+ continue;
+ }
+
+ val = csi_read(csi_dev, CSI_CSICR3);
+ csi_write(csi_dev, val | BIT_DMA_REFLASH_RFF,
+ CSI_CSICR3);
+ /* Wait DMA reflash done */
+ for (timeout2 = 1000000; timeout2 > 0; timeout2--) {
+ if (csi_read(csi_dev, CSI_CSICR3) & BIT_DMA_REFLASH_RFF)
+ cpu_relax();
+ else
+ break;
+ }
+
+ if (timeout2 <= 0) {
+ pr_err("timeout when wait for reflash done.\n");
+ local_irq_restore(flags);
+ return -ETIME;
+ }
+
+ /*
+ * For iimx7l csi, DMA FIFO will auto start when sensor
+ * ready to work,so DMA should enable right after FIFO
+ * reset, otherwise dma will lost data and image will split.
+ */
+
+ csi_dmareq_rff_enable(csi_dev);
+ csi_enable_int(csi_dev, 1);
+ csi_enable(csi_dev, 1);
+ break;
+ }
+ if (timeout <= 0) {
+ pr_err("timeout when wait for SOF\n");
+ local_irq_restore(flags);
+ return -ETIME;
+ }
+ local_irq_restore(flags);
+
+ return 0;
+}
+
+static void imx7_csi_disable(struct imx7_csi_dev *csi_dev)
+{
+ struct v4l2_pix_format *pix = &csi_dev->pix;
+
+ csi_dmareq_rff_disable(csi_dev);
+ csi_disable_int(csi_dev);
+
+ /* set CSI_CSIDMASA_FB1 and CSI_CSIDMASA_FB2 to default value */
+ csi_write(csi_dev, 0, CSI_CSIDMASA_FB1);
+ csi_write(csi_dev, 0, CSI_CSIDMASA_FB2);
+
+ csi_buf_stride_set(csi_dev, 0);
+
+ if (pix->field == V4L2_FIELD_INTERLACED) {
+ csi_deinterlace_enable(csi_dev, false);
+ csi_tvdec_enable(csi_dev, false);
+ }
+
+ csi_enable(csi_dev, 0);
+}
+
+static int imx7_configure_csi(struct imx7_csi_dev *csi_dev)
+{
+ struct v4l2_pix_format *pix = &csi_dev->pix;
+ u32 cr1, cr18;
+ u32 width;
+
+ if (pix->field == V4L2_FIELD_INTERLACED) {
+ csi_deinterlace_enable(csi_dev, true);
+ csi_buf_stride_set(csi_dev, csi_dev->pix.width);
+ csi_deinterlace_mode(csi_dev, csi_dev->std);
+ } else {
+ csi_deinterlace_enable(csi_dev, false);
+ csi_buf_stride_set(csi_dev, 0);
+ }
+
+ switch (csi_dev->fmt->pixelformat) {
+ case V4L2_PIX_FMT_YUV32:
+ case V4L2_PIX_FMT_SBGGR8:
+ width = pix->width;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ width = pix->width * 2;
+ break;
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_YUYV:
+ if (csi_dev->csi_mux_mipi)
+ width = pix->width;
+ else
+ width = pix->width * 2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ csi_set_imagpara(csi_dev, width, pix->height);
+
+ if (!csi_dev->csi_mux_mipi)
+ return 0;
+
+ cr1 = csi_read(csi_dev, CSI_CSICR1);
+ cr1 &= ~BIT_GCLK_MODE;
+
+ cr18 = csi_read(csi_dev, CSI_CSICR18);
+ cr18 &= BIT_MIPI_DATA_FORMAT_MASK;
+ cr18 |= BIT_DATA_FROM_MIPI;
+
+ switch (csi_dev->fmt->pixelformat) {
+ case V4L2_PIX_FMT_UYVY:
+ case V4L2_PIX_FMT_YUYV:
+ cr18 |= BIT_MIPI_DATA_FORMAT_YUV422_8B;
+ break;
+ case V4L2_PIX_FMT_SBGGR8:
+ cr18 |= BIT_MIPI_DATA_FORMAT_RAW8;
+ break;
+ case V4L2_PIX_FMT_SBGGR10:
+ cr18 |= BIT_MIPI_DATA_FORMAT_RAW10;
+ cr1 |= BIT_PIXEL_BIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ csi_write(csi_dev, cr1, CSI_CSICR1);
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+
+ return 0;
+}
+
+static int imx7_start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+ struct imx7_csi_dev *csi_dev = vb2_get_drv_priv(vq);
+ struct vb2_buffer *vb;
+ struct imx7_buffer *buf;
+ unsigned long phys;
+ unsigned long flags;
+
+ if (count < 2)
+ return -ENOBUFS;
+
+ csi_dev->discard_size = csi_dev->pix.sizeimage;
+ csi_dev->discard_buffer = dma_alloc_coherent(csi_dev->v4l2_dev.dev,
+ PAGE_ALIGN(csi_dev->discard_size),
+ &csi_dev->discard_buffer_dma,
+ GFP_DMA | GFP_KERNEL);
+ if (!csi_dev->discard_buffer)
+ return -ENOMEM;
+
+ spin_lock_irqsave(&csi_dev->slock, flags);
+
+ csi_dev->buf_discard[0].discard = true;
+ list_add_tail(&csi_dev->buf_discard[0].queue,
+ &csi_dev->discard);
+
+ csi_dev->buf_discard[1].discard = true;
+ list_add_tail(&csi_dev->buf_discard[1].queue,
+ &csi_dev->discard);
+
+ /* csi buf 0 */
+ buf = list_first_entry(&csi_dev->capture, struct imx7_buffer,
+ internal.queue);
+ buf->internal.bufnum = 0;
+ vb = &buf->vb.vb2_buf;
+ vb->state = VB2_BUF_STATE_ACTIVE;
+
+ phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+
+ imx7_update_csi_buf(csi_dev, phys, buf->internal.bufnum);
+ list_move_tail(csi_dev->capture.next, &csi_dev->active_bufs);
+
+ /* csi buf 1 */
+ buf = list_first_entry(&csi_dev->capture, struct imx7_buffer,
+ internal.queue);
+ buf->internal.bufnum = 1;
+ vb = &buf->vb.vb2_buf;
+ vb->state = VB2_BUF_STATE_ACTIVE;
+
+ phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+ imx7_update_csi_buf(csi_dev, phys, buf->internal.bufnum);
+ list_move_tail(csi_dev->capture.next, &csi_dev->active_bufs);
+
+ spin_unlock_irqrestore(&csi_dev->slock, flags);
+
+ return imx7_csi_enable(csi_dev);
+}
+
+static void imx7_stop_streaming(struct vb2_queue *vq)
+{
+ struct imx7_csi_dev *csi_dev = vb2_get_drv_priv(vq);
+ unsigned long flags;
+ struct imx7_buffer *buf, *tmp;
+ void *b;
+
+ imx7_csi_disable(csi_dev);
+
+ spin_lock_irqsave(&csi_dev->slock, flags);
+
+ list_for_each_entry_safe(buf, tmp, &csi_dev->capture, internal.queue) {
+ list_del_init(&buf->internal.queue);
+ if (buf->vb.vb2_buf.state == VB2_BUF_STATE_ACTIVE)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &csi_dev->active_bufs,
+ internal.queue) {
+ list_del_init(&buf->internal.queue);
+
+ if (buf->vb.vb2_buf.state == VB2_BUF_STATE_ACTIVE)
+ vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ INIT_LIST_HEAD(&csi_dev->capture);
+ INIT_LIST_HEAD(&csi_dev->active_bufs);
+ INIT_LIST_HEAD(&csi_dev->discard);
+
+ b = csi_dev->discard_buffer;
+ csi_dev->discard_buffer = NULL;
+
+ spin_unlock_irqrestore(&csi_dev->slock, flags);
+
+ dma_free_coherent(csi_dev->v4l2_dev.dev, csi_dev->discard_size, b,
+ csi_dev->discard_buffer_dma);
+}
+
+static struct vb2_ops imx7_videobuf_ops = {
+ .queue_setup = imx7_videobuf_setup,
+ .buf_prepare = imx7_videobuf_prepare,
+ .buf_queue = imx7_videobuf_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = imx7_start_streaming,
+ .stop_streaming = imx7_stop_streaming,
+};
+
+static void imx7_csi_frame_done(struct imx7_csi_dev *csi_dev, int bufnum,
+ bool err)
+{
+ struct imx7_buf_internal *ibuf;
+ struct imx7_buffer *buf;
+ struct vb2_buffer *vb;
+ unsigned long phys;
+
+ ibuf = list_first_entry(&csi_dev->active_bufs, struct imx7_buf_internal,
+ queue);
+
+ if (ibuf->discard) {
+ /*
+ * Discard buffer must not be returned to user space.
+ * Just return it to the discard queue.
+ */
+ list_move_tail(csi_dev->active_bufs.next, &csi_dev->discard);
+ } else {
+ buf = imx7_ibuf_to_buf(ibuf);
+
+ vb = &buf->vb.vb2_buf;
+ phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+ if (bufnum == 1) {
+ if (csi_read(csi_dev, CSI_CSIDMASA_FB2) != phys) {
+ dev_err(csi_dev->dev, "%lx != %x\n", phys,
+ csi_read(csi_dev, CSI_CSIDMASA_FB2));
+ }
+ } else {
+ if (csi_read(csi_dev, CSI_CSIDMASA_FB1) != phys) {
+ dev_err(csi_dev->dev, "%lx != %x\n", phys,
+ csi_read(csi_dev, CSI_CSIDMASA_FB1));
+ }
+ }
+ dev_dbg(csi_dev->dev, "%s (vb=0x%p) 0x%p %lu\n", __func__, vb,
+ vb2_plane_vaddr(vb, 0), vb2_get_plane_payload(vb, 0));
+
+ list_del_init(&buf->internal.queue);
+ vb->timestamp = ktime_get_ns();
+ to_vb2_v4l2_buffer(vb)->sequence = csi_dev->frame_count;
+ if (err)
+ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
+ else
+ vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
+ }
+
+ csi_dev->frame_count++;
+
+ /* Config discard buffer to active_bufs */
+ if (list_empty(&csi_dev->capture)) {
+ if (list_empty(&csi_dev->discard)) {
+ dev_warn(csi_dev->dev, "%s: trying to access empty discard list\n",
+ __func__);
+ return;
+ }
+
+ ibuf = list_first_entry(&csi_dev->discard,
+ struct imx7_buf_internal, queue);
+ ibuf->bufnum = bufnum;
+
+ list_move_tail(csi_dev->discard.next, &csi_dev->active_bufs);
+
+ imx7_update_csi_buf(csi_dev, csi_dev->discard_buffer_dma,
+ bufnum);
+ return;
+ }
+
+ buf = list_first_entry(&csi_dev->capture, struct imx7_buffer,
+ internal.queue);
+
+ buf->internal.bufnum = bufnum;
+
+ list_move_tail(csi_dev->capture.next, &csi_dev->active_bufs);
+
+ vb = &buf->vb.vb2_buf;
+ vb->state = VB2_BUF_STATE_ACTIVE;
+
+ phys = vb2_dma_contig_plane_dma_addr(vb, 0);
+ imx7_update_csi_buf(csi_dev, phys, bufnum);
+}
+
+static irqreturn_t imx7_csi_irq_handler(int irq, void *data)
+{
+ struct imx7_csi_dev *csi_dev = data;
+ unsigned long status;
+ u32 cr3, cr18;
+
+ spin_lock(&csi_dev->slock);
+
+ status = csi_read(csi_dev, CSI_CSISR);
+ csi_write(csi_dev, status, CSI_CSISR);
+
+ if (list_empty(&csi_dev->active_bufs)) {
+ dev_warn(csi_dev->dev, "called while active list is empty\n");
+ spin_unlock(&csi_dev->slock);
+ return IRQ_HANDLED;
+ }
+
+ if (status & BIT_RFF_OR_INT) {
+ dev_warn(csi_dev->dev, "Rx fifo overflow\n");
+ if (*csi_dev->rx_fifo_rst)
+ csi_error_recovery(csi_dev);
+ }
+
+ if (status & BIT_HRESP_ERR_INT) {
+ dev_warn(csi_dev->dev, "Hresponse error detected\n");
+ csi_error_recovery(csi_dev);
+ }
+
+ if (status & BIT_ADDR_CH_ERR_INT) {
+ /* Disable csi */
+ cr18 = csi_read(csi_dev, CSI_CSICR18);
+ cr18 &= ~BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+
+ /* DMA reflash */
+ cr3 = csi_read(csi_dev, CSI_CSICR3);
+ cr3 |= BIT_DMA_REFLASH_RFF;
+ csi_write(csi_dev, cr3, CSI_CSICR3);
+
+ /* Ensable csi */
+ cr18 |= BIT_CSI_ENABLE;
+ csi_write(csi_dev, cr18, CSI_CSICR18);
+ }
+
+ if ((status & BIT_DMA_TSF_DONE_FB1) &&
+ (status & BIT_DMA_TSF_DONE_FB2)) {
+ /*
+ * For both FB1 and FB2 interrupter bits set case,
+ * CSI DMA is work in one of FB1 and FB2 buffer,
+ * but software can not know the state.
+ * Skip it to avoid base address updated
+ * when csi work in field0 and field1 will write to
+ * new base address.
+ */
+ } else if (status & BIT_DMA_TSF_DONE_FB1) {
+ imx7_csi_frame_done(csi_dev, 0, false);
+ } else if (status & BIT_DMA_TSF_DONE_FB2) {
+ imx7_csi_frame_done(csi_dev, 1, false);
+ }
+
+ spin_unlock(&csi_dev->slock);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * File operations for the device
+ */
+static int imx7_csi_open(struct file *file)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ int ret = 0;
+
+ file->private_data = csi_dev;
+
+ if (mutex_lock_interruptible(&csi_dev->lock))
+ return -ERESTARTSYS;
+
+ ret = v4l2_fh_open(file);
+ if (ret < 0)
+ goto unlock;
+
+ pm_runtime_get_sync(csi_dev->dev);
+
+ v4l2_subdev_call(sd, core, s_power, 1);
+
+ imx7_csi_init(csi_dev);
+
+unlock:
+ mutex_unlock(&csi_dev->lock);
+
+ return ret;
+}
+
+static int imx7_csi_release(struct file *file)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ struct vb2_queue *vq = &csi_dev->vb2_vidq;
+
+ mutex_lock(&csi_dev->lock);
+
+ imx7_stop_streaming(vq);
+ vb2_queue_release(vq);
+
+ imx7_csi_deinit(csi_dev);
+ v4l2_subdev_call(sd, core, s_power, 0);
+
+ mutex_unlock(&csi_dev->lock);
+
+ pm_runtime_put_sync_suspend(csi_dev->dev);
+ v4l2_fh_release(file);
+
+ return 0;
+}
+
+static struct v4l2_file_operations imx7_csi_fops = {
+ .owner = THIS_MODULE,
+ .open = imx7_csi_open,
+ .release = imx7_csi_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2, /* V4L2 ioctl handler */
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * Video node IOCTLs
+ */
+static int imx7_vidioc_enum_input(struct file *file, void *priv,
+ struct v4l2_input *inp)
+{
+ if (inp->index != 0)
+ return -EINVAL;
+
+ /* default is camera */
+ inp->type = V4L2_INPUT_TYPE_CAMERA;
+ strlcpy(inp->name, "Camera", sizeof(inp->name));
+
+ return 0;
+}
+
+static int imx7_vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+ *i = 0;
+
+ return 0;
+}
+
+static int imx7_vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+ if (i > 0)
+ return -EINVAL;
+
+ return 0;
+}
+
+static int imx7_vidioc_querystd(struct file *file, void *priv, v4l2_std_id *a)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, video, querystd, a);
+}
+
+static int imx7_vidioc_s_std(struct file *file, void *priv, v4l2_std_id a)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, video, s_std, a);
+}
+
+static int imx7_vidioc_g_std(struct file *file, void *priv, v4l2_std_id *a)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, video, g_std, a);
+}
+
+static int imx7_vidioc_reqbufs(struct file *file, void *priv,
+ struct v4l2_requestbuffers *p)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+
+ return vb2_reqbufs(&csi_dev->vb2_vidq, p);
+}
+
+static int imx7_vidioc_querybuf(struct file *file, void *priv,
+ struct v4l2_buffer *p)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct vb2_buffer *vb;
+ int ret;
+
+ ret = vb2_querybuf(&csi_dev->vb2_vidq, p);
+
+ if (!ret) {
+ /* return physical address */
+ vb = csi_dev->vb2_vidq.bufs[p->index];
+ if (p->flags & V4L2_BUF_FLAG_MAPPED)
+ p->m.offset = vb2_dma_contig_plane_dma_addr(vb, 0);
+ }
+ return ret;
+}
+
+static int imx7_vidioc_qbuf(struct file *file, void *priv,
+ struct v4l2_buffer *p)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+
+ return vb2_qbuf(&csi_dev->vb2_vidq, p);
+}
+
+static int imx7_vidioc_dqbuf(struct file *file, void *priv,
+ struct v4l2_buffer *p)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+
+ return vb2_dqbuf(&csi_dev->vb2_vidq, p, file->f_flags & O_NONBLOCK);
+}
+
+static int imx7_vidioc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ struct v4l2_subdev_mbus_code_enum code = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ .index = f->index,
+ };
+ struct imx7_fmt *fmt;
+ int ret;
+
+ ret = v4l2_subdev_call(sd, pad, enum_mbus_code, NULL, &code);
+ if (ret < 0) {
+ /* no more formats */
+ dev_info(csi_dev->dev, "No more fmt\n");
+ return -EINVAL;
+ }
+
+ fmt = format_by_mbus(code.code);
+ if (!fmt) {
+ dev_err(csi_dev->dev, "mbus (0x%08x) invalid.\n", code.code);
+ return -EINVAL;
+ }
+
+ strlcpy(f->description, fmt->name, sizeof(f->description));
+ f->pixelformat = fmt->pixelformat;
+
+ return 0;
+}
+
+static int imx7_vidioc_try_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ struct v4l2_pix_format *pix = &f->fmt.pix;
+ struct v4l2_subdev_format format = {
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ struct imx7_fmt *fmt;
+ int ret;
+
+ fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ if (!fmt) {
+ dev_err(csi_dev->dev, "Fourcc format (0x%08x) invalid.",
+ f->fmt.pix.pixelformat);
+ return -EINVAL;
+ }
+
+ v4l2_fill_mbus_format(&format.format, pix, fmt->mbus_code);
+ ret = v4l2_subdev_call(sd, pad, set_fmt, NULL, &format);
+ if (ret < 0)
+ return ret;
+
+ v4l2_fill_pix_format(pix, &format.format);
+
+ if (pix->field != V4L2_FIELD_INTERLACED)
+ pix->field = V4L2_FIELD_NONE;
+
+ pix->sizeimage = fmt->bpp * pix->height * pix->width;
+ pix->bytesperline = fmt->bpp * pix->width;
+
+ return 0;
+}
+
+/*
+ * The real work of figuring out a workable format.
+ */
+static int imx7_vidioc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ int ret;
+
+ ret = imx7_vidioc_try_fmt_vid_cap(file, csi_dev, f);
+ if (ret < 0)
+ return ret;
+
+ csi_dev->fmt = format_by_fourcc(f->fmt.pix.pixelformat);
+ csi_dev->mbus_code = csi_dev->fmt->mbus_code;
+ csi_dev->pix = f->fmt.pix;
+ csi_dev->type = f->type;
+
+ dev_dbg(csi_dev->dev, "set to pixelformat '%4.6s' colorspace: %d\n",
+ (char *)&csi_dev->fmt->name, csi_dev->pix.colorspace);
+
+ /* Config csi */
+ imx7_configure_csi(csi_dev);
+
+ return 0;
+}
+
+static int imx7_vidioc_g_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+
+ f->fmt.pix = csi_dev->pix;
+ f->fmt.pix.pixelformat = csi_dev->fmt->pixelformat;
+
+ return 0;
+}
+
+static int imx7_vidioc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+
+ WARN_ON(priv != file->private_data);
+
+ /* cap->name is set by the friendly caller:-> */
+ strlcpy(cap->driver, IMX7_CAM_DRV_NAME, sizeof(cap->driver));
+ strlcpy(cap->card, IMX7_CAM_DRIVER_DESCRIPTION, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ dev_name(csi_dev->dev));
+
+ cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int imx7_vidioc_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type i)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ int ret;
+
+ if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ ret = vb2_streamon(&csi_dev->vb2_vidq, i);
+ if (!ret)
+ v4l2_subdev_call(sd, video, s_stream, 1);
+
+ return ret;
+}
+
+static int imx7_vidioc_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type i)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ /*
+ * This calls buf_release from host driver's videobuf_queue_ops for all
+ * remaining buffers. When the last buffer is freed, stop capture
+ */
+ vb2_streamoff(&csi_dev->vb2_vidq, i);
+
+ v4l2_subdev_call(sd, video, s_stream, 0);
+
+ return 0;
+}
+
+static int imx7_vidioc_cropcap(struct file *file, void *fh,
+ struct v4l2_cropcap *a)
+{
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ return -ENODATA;
+}
+
+static int imx7_vidioc_g_crop(struct file *file, void *priv,
+ struct v4l2_crop *a)
+{
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ return -ENODATA;
+}
+
+static int imx7_vidioc_s_crop(struct file *file, void *priv,
+ const struct v4l2_crop *a)
+{
+ if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+ return -EINVAL;
+
+ return -ENODATA;
+}
+
+static int imx7_vidioc_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, video, g_parm, a);
+}
+
+static int imx7_vidioc_s_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+
+ return v4l2_subdev_call(sd, video, s_parm, a);
+}
+
+static int imx7_vidioc_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ struct imx7_fmt *fmt;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = fsize->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ fmt = format_by_fourcc(fsize->pixel_format);
+ if (!fmt)
+ return -EINVAL;
+
+ if (fmt->pixelformat != fsize->pixel_format)
+ return -EINVAL;
+
+ fse.code = fmt->mbus_code;
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_size, NULL, &fse);
+ if (ret)
+ return ret;
+
+ if (fse.min_width == fse.max_width &&
+ fse.min_height == fse.max_height) {
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->discrete.width = fse.min_width;
+ fsize->discrete.height = fse.min_height;
+ return 0;
+ }
+
+ fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+ fsize->stepwise.min_width = fse.min_width;
+ fsize->stepwise.max_width = fse.max_width;
+ fsize->stepwise.min_height = fse.min_height;
+ fsize->stepwise.max_height = fse.max_height;
+ fsize->stepwise.step_width = 1;
+ fsize->stepwise.step_height = 1;
+
+ return 0;
+}
+
+static int imx7_vidioc_enum_frameintervals(struct file *file, void *priv,
+ struct v4l2_frmivalenum *interval)
+{
+ struct imx7_csi_dev *csi_dev = video_drvdata(file);
+ struct v4l2_subdev *sd = csi_dev->sd;
+ struct imx7_fmt *fmt;
+ struct v4l2_subdev_frame_interval_enum fie = {
+ .index = interval->index,
+ .width = interval->width,
+ .height = interval->height,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ fmt = format_by_fourcc(interval->pixel_format);
+ if (!fmt)
+ return -EINVAL;
+
+ if (fmt->pixelformat != interval->pixel_format)
+ return -EINVAL;
+
+ fie.code = fmt->mbus_code;
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_interval, NULL, &fie);
+ if (ret)
+ return ret;
+
+ interval->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ interval->discrete = fie.interval;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops imx7_csi_ioctl_ops = {
+ .vidioc_querycap = imx7_vidioc_querycap,
+ .vidioc_enum_fmt_vid_cap = imx7_vidioc_enum_fmt_vid_cap,
+ .vidioc_try_fmt_vid_cap = imx7_vidioc_try_fmt_vid_cap,
+ .vidioc_g_fmt_vid_cap = imx7_vidioc_g_fmt_vid_cap,
+ .vidioc_s_fmt_vid_cap = imx7_vidioc_s_fmt_vid_cap,
+ .vidioc_cropcap = imx7_vidioc_cropcap,
+ .vidioc_s_crop = imx7_vidioc_s_crop,
+ .vidioc_g_crop = imx7_vidioc_g_crop,
+ .vidioc_reqbufs = imx7_vidioc_reqbufs,
+ .vidioc_querybuf = imx7_vidioc_querybuf,
+ .vidioc_qbuf = imx7_vidioc_qbuf,
+ .vidioc_dqbuf = imx7_vidioc_dqbuf,
+ .vidioc_g_std = imx7_vidioc_g_std,
+ .vidioc_s_std = imx7_vidioc_s_std,
+ .vidioc_querystd = imx7_vidioc_querystd,
+ .vidioc_enum_input = imx7_vidioc_enum_input,
+ .vidioc_g_input = imx7_vidioc_g_input,
+ .vidioc_s_input = imx7_vidioc_s_input,
+ .vidioc_streamon = imx7_vidioc_streamon,
+ .vidioc_streamoff = imx7_vidioc_streamoff,
+ .vidioc_g_parm = imx7_vidioc_g_parm,
+ .vidioc_s_parm = imx7_vidioc_s_parm,
+ .vidioc_enum_framesizes = imx7_vidioc_enum_framesizes,
+ .vidioc_enum_frameintervals = imx7_vidioc_enum_frameintervals,
+};
+
+static int imx7_subdev_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct imx7_csi_dev *csi_dev = notifier_to_imx7_dev(notifier);
+ struct v4l2_async_subdev *casd = &csi_dev->asd;
+ int ret;
+
+ /* Find platform data for this sensor subdev */
+ if (casd->match.fwnode.fwnode == of_fwnode_handle(subdev->dev->of_node))
+ csi_dev->sd = subdev;
+
+ if (!csi_dev->sd)
+ return -EINVAL;
+
+ v4l2_info(&csi_dev->v4l2_dev, "Registered subdevice: %s\n",
+ subdev->name);
+
+ ret = v4l2_ctrl_add_handler(&csi_dev->ctrl_handler,
+ subdev->ctrl_handler,
+ NULL);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void imx7_subdev_notifier_unbind(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *subdev,
+ struct v4l2_async_subdev *asd)
+{
+ struct imx7_csi_dev *csi_dev = notifier_to_imx7_dev(notifier);
+
+ vb2_queue_release(&csi_dev->vb2_vidq);
+}
+
+static int imx7_probe_complete(struct v4l2_async_notifier *notifier)
+{
+ struct imx7_csi_dev *csi_dev = notifier_to_imx7_dev(notifier);
+
+ return v4l2_device_register_subdev_nodes(&csi_dev->v4l2_dev);
+}
+
+static int imx7_csi_mux_sel(struct imx7_csi_dev *csi_dev)
+{
+ struct device_node *np = csi_dev->dev->of_node;
+ struct device_node *node;
+ phandle phandle;
+ u32 out_val[3];
+ int ret;
+
+ ret = of_property_read_u32_array(np, "csi-mux-mipi", out_val, 3);
+ if (ret) {
+ dev_err(csi_dev->dev, "no csi-mux-mipi property found: %d\n",
+ ret);
+ csi_dev->csi_mux_mipi = false;
+ return ret;
+ }
+
+ phandle = *out_val;
+
+ node = of_find_node_by_phandle(phandle);
+ if (!node) {
+ ret = PTR_ERR(node);
+ dev_err(csi_dev->dev, "not find gpr node by phandle: %d\n",
+ ret);
+ return ret;
+ }
+
+ csi_dev->csi_mux.gpr = syscon_node_to_regmap(node);
+ if (IS_ERR(csi_dev->csi_mux.gpr)) {
+ ret = PTR_ERR(csi_dev->csi_mux.gpr);
+ dev_err(csi_dev->dev, "failed to get gpr regmap: %d\n", ret);
+ }
+ if (ret < 0)
+ goto node_put;
+
+ csi_dev->csi_mux.req_gpr = out_val[1];
+ csi_dev->csi_mux.req_bit = out_val[2];
+
+ regmap_update_bits(csi_dev->csi_mux.gpr, csi_dev->csi_mux.req_gpr,
+ 1 << csi_dev->csi_mux.req_bit,
+ 1 << csi_dev->csi_mux.req_bit);
+
+ csi_dev->csi_mux_mipi = true;
+
+node_put:
+ of_node_put(node);
+
+ return ret;
+}
+
+static int imx7x_register_subdevs(struct imx7_csi_dev *csi_dev)
+{
+ struct device_node *parent = csi_dev->dev->of_node;
+ struct device_node *node, *port, *rem;
+ int ret;
+
+ /* Attach sensors linked to csi receivers */
+ for_each_available_child_of_node(parent, node) {
+ if (of_node_cmp(node->name, "port"))
+ continue;
+
+ /* The csi node can have only port subnode. */
+ port = of_get_next_child(node, NULL);
+ if (!port)
+ continue;
+ rem = of_graph_get_remote_port_parent(port);
+ of_node_put(port);
+
+ if (!rem) {
+ v4l2_info(&csi_dev->v4l2_dev, "Remote device at %s not found\n",
+ port->full_name);
+ return -EINVAL;
+ }
+
+ v4l2_info(&csi_dev->v4l2_dev, "Remote device at %s found\n",
+ rem->name);
+
+ csi_dev->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ csi_dev->asd.match.fwnode.fwnode = of_fwnode_handle(rem);
+ csi_dev->async_subdevs[0] = &csi_dev->asd;
+
+ of_node_put(rem);
+ break;
+ }
+
+ csi_dev->subdev_notifier.subdevs = csi_dev->async_subdevs;
+ csi_dev->subdev_notifier.num_subdevs = 1;
+ csi_dev->subdev_notifier.bound = imx7_subdev_notifier_bound;
+ csi_dev->subdev_notifier.unbind = imx7_subdev_notifier_unbind;
+ csi_dev->subdev_notifier.complete = imx7_probe_complete;
+
+ ret = v4l2_async_notifier_register(&csi_dev->v4l2_dev,
+ &csi_dev->subdev_notifier);
+ if (ret)
+ dev_err(csi_dev->dev, "Error register async notifier\n");
+
+ return ret;
+}
+
+static int imx7_csi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ const struct of_device_id *of_id;
+ struct imx7_csi_dev *csi_dev;
+ struct video_device *vdev;
+ struct resource *res;
+ struct vb2_queue *q;
+ int ret = 0;
+
+ dev_info(dev, "initialising\n");
+
+ /* Prepare our private structure */
+ csi_dev = devm_kzalloc(dev, sizeof(*csi_dev), GFP_KERNEL);
+ if (!csi_dev)
+ return -ENOMEM;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ csi_dev->irq = platform_get_irq(pdev, 0);
+ if (!res || csi_dev->irq < 0) {
+ dev_err(dev, "Missing platform resources data\n");
+ return -ENODEV;
+ }
+
+ csi_dev->regbase = devm_ioremap_resource(dev, res);
+ if (IS_ERR(csi_dev->regbase)) {
+ dev_err(dev, "Failed platform resources map\n");
+ return -ENODEV;
+ }
+
+ /* init video dma queues */
+ INIT_LIST_HEAD(&csi_dev->capture);
+ INIT_LIST_HEAD(&csi_dev->active_bufs);
+ INIT_LIST_HEAD(&csi_dev->discard);
+
+ csi_dev->clk_disp_axi = devm_clk_get(dev, "disp-axi");
+ if (IS_ERR(csi_dev->clk_disp_axi)) {
+ dev_err(dev, "Could not get csi axi clock\n");
+ return -ENODEV;
+ }
+
+ csi_dev->clk_disp_dcic = devm_clk_get(dev, "disp_dcic");
+ if (IS_ERR(csi_dev->clk_disp_dcic)) {
+ dev_err(dev, "Could not get disp dcic clock\n");
+ return -ENODEV;
+ }
+
+ csi_dev->clk_csi_mclk = devm_clk_get(dev, "csi_mclk");
+ if (IS_ERR(csi_dev->clk_csi_mclk)) {
+ dev_err(dev, "Could not get csi mclk clock\n");
+ return -ENODEV;
+ }
+
+ csi_dev->dev = dev;
+
+ imx7_csi_mux_sel(csi_dev);
+
+ of_id = of_match_node(imx7_csi_dt_ids, csi_dev->dev->of_node);
+ if (!of_id)
+ return -EINVAL;
+ csi_dev->rx_fifo_rst = of_id->data;
+
+ snprintf(csi_dev->v4l2_dev.name, sizeof(csi_dev->v4l2_dev.name), "CSI");
+
+ /*
+ * this is to make sure the sensor already added its controls and that
+ * we can add them in the bound of the mipi subdev. it will be removed
+ *when moving to a media controller approach
+ *
+ */
+ msleep(1000);
+
+ ret = v4l2_device_register(dev, &csi_dev->v4l2_dev);
+ if (ret < 0) {
+ dev_err(dev, "v4l2_device_register() failed: %d\n", ret);
+ return -ENODEV;
+ }
+
+ /* initialize locks */
+ mutex_init(&csi_dev->lock);
+ spin_lock_init(&csi_dev->slock);
+
+ /* Allocate memory for video device */
+ vdev = video_device_alloc();
+ if (!vdev) {
+ ret = -ENOMEM;
+ goto unregister_dev;
+ }
+
+ snprintf(vdev->name, sizeof(vdev->name), "imx7-csi");
+
+ vdev->v4l2_dev = &csi_dev->v4l2_dev;
+ vdev->fops = &imx7_csi_fops;
+ vdev->ioctl_ops = &imx7_csi_ioctl_ops;
+ vdev->release = video_device_release;
+ vdev->lock = &csi_dev->lock;
+
+ vdev->queue = &csi_dev->vb2_vidq;
+
+ csi_dev->vdev = vdev;
+
+ video_set_drvdata(csi_dev->vdev, csi_dev);
+
+ v4l2_ctrl_handler_init(&csi_dev->ctrl_handler, 0);
+ vdev->ctrl_handler = &csi_dev->ctrl_handler;
+
+ mutex_lock(&csi_dev->lock);
+
+ ret = video_register_device(csi_dev->vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ video_device_release(csi_dev->vdev);
+ mutex_unlock(&csi_dev->lock);
+ goto unregister_dev;
+ }
+ dev_info(dev, "video device registered\n");
+
+ /* install interrupt handler */
+ if (devm_request_irq(dev, csi_dev->irq, imx7_csi_irq_handler, 0, "csi",
+ (void *)csi_dev)) {
+ mutex_unlock(&csi_dev->lock);
+ dev_err(dev, "Request CSI IRQ failed.\n");
+ ret = -ENODEV;
+ goto unregister_video;
+ }
+
+ q = &csi_dev->vb2_vidq;
+
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR;
+ q->drv_priv = csi_dev;
+ q->ops = &imx7_videobuf_ops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct imx7_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->min_buffers_needed = 2;
+ q->lock = &csi_dev->lock;
+ q->dev = csi_dev->dev;
+
+ ret = vb2_queue_init(q);
+ if (ret < 0)
+ return -ENOMEM;
+
+ mutex_unlock(&csi_dev->lock);
+
+ ret = imx7x_register_subdevs(csi_dev);
+ if (ret < 0)
+ goto unregister_video;
+
+ csi_dev->fmt = format_by_fourcc(V4L2_PIX_FMT_SBGGR10);
+
+ pm_runtime_enable(csi_dev->dev);
+
+ return 0;
+
+unregister_video:
+ video_unregister_device(csi_dev->vdev);
+
+unregister_dev:
+ v4l2_device_unregister(&csi_dev->v4l2_dev);
+ v4l2_ctrl_handler_free(&csi_dev->ctrl_handler);
+ mutex_destroy(&csi_dev->lock);
+
+ return ret;
+}
+
+static int imx7_csi_remove(struct platform_device *pdev)
+{
+ struct v4l2_device *v4l2_dev = dev_get_drvdata(&pdev->dev);
+ struct imx7_csi_dev *csi_dev;
+
+ csi_dev = container_of(v4l2_dev, struct imx7_csi_dev, v4l2_dev);
+
+ v4l2_async_notifier_unregister(&csi_dev->subdev_notifier);
+
+ video_unregister_device(csi_dev->vdev);
+ v4l2_device_unregister(&csi_dev->v4l2_dev);
+ v4l2_ctrl_handler_free(&csi_dev->ctrl_handler);
+ mutex_destroy(&csi_dev->lock);
+
+ pm_runtime_disable(csi_dev->dev);
+ return 0;
+}
+
+static int imx7_csi_runtime_suspend(struct device *dev)
+{
+ return 0;
+}
+
+static int imx7_csi_runtime_resume(struct device *dev)
+{
+ return 0;
+}
+
+static const struct dev_pm_ops imx7_csi_pm_ops = {
+ SET_RUNTIME_PM_OPS(imx7_csi_runtime_suspend, imx7_csi_runtime_resume,
+ NULL)
+};
+
+static const u8 imx7_fifo_rst = true;
+static const u8 imx7l_fifo_rst;
+
+static const struct of_device_id imx7_csi_dt_ids[] = {
+ { .compatible = "fsl,imx7-csi",
+ .data = &imx7_fifo_rst,
+ },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, imx7_csi_dt_ids);
+
+static struct platform_driver imx7_csi_driver = {
+ .driver = {
+ .name = IMX7_CAM_DRV_NAME,
+ .of_match_table = of_match_ptr(imx7_csi_dt_ids),
+ .pm = &imx7_csi_pm_ops,
+ },
+ .probe = imx7_csi_probe,
+ .remove = imx7_csi_remove,
+};
+module_platform_driver(imx7_csi_driver);
+
+MODULE_DESCRIPTION("i.IMX7x SoC Camera Host driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");
+MODULE_VERSION(IMX7_CAM_VERSION);