summaryrefslogtreecommitdiff
path: root/drivers/staging/media
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/staging/media')
-rw-r--r--drivers/staging/media/Makefile2
-rw-r--r--drivers/staging/media/imx/Kconfig84
-rw-r--r--drivers/staging/media/imx/Makefile11
-rw-r--r--drivers/staging/media/imx/gmsl-max9286.c3344
-rw-r--r--drivers/staging/media/imx/imx8-common.h99
-rw-r--r--drivers/staging/media/imx/imx8-isi-cap.c1804
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.c616
-rw-r--r--drivers/staging/media/imx/imx8-isi-core.h409
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.c833
-rw-r--r--drivers/staging/media/imx/imx8-isi-hw.h484
-rw-r--r--drivers/staging/media/imx/imx8-isi-m2m.c1201
-rw-r--r--drivers/staging/media/imx/imx8-media-dev.c1076
-rw-r--r--drivers/staging/media/imx/imx8-mipi-csi2-sam.c1738
-rw-r--r--drivers/staging/media/imx/imx8-mipi-csi2.c1170
-rw-r--r--drivers/staging/media/imx/imx8-parallel-csi.c837
15 files changed, 13707 insertions, 1 deletions
diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile
index 2f1711a8aeed..7cd981ec74e1 100644
--- a/drivers/staging/media/Makefile
+++ b/drivers/staging/media/Makefile
@@ -1,6 +1,6 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_VIDEO_ALLEGRO_DVT) += allegro-dvt/
-obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/
+obj-$(CONFIG_VIDEO_IMX_CAPTURE) += imx/
obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/
obj-$(CONFIG_VIDEO_OMAP4) += omap4iss/
obj-$(CONFIG_VIDEO_SUNXI) += sunxi/
diff --git a/drivers/staging/media/imx/Kconfig b/drivers/staging/media/imx/Kconfig
index 8f1ae50a4abd..be3e8545c86a 100644
--- a/drivers/staging/media/imx/Kconfig
+++ b/drivers/staging/media/imx/Kconfig
@@ -31,3 +31,87 @@ config VIDEO_IMX7_CSI
i.MX6UL/L or i.MX7.
endmenu
endif
+
+config VIDEO_IMX_CAPTURE
+ tristate "i.MX V4L2 media core driver"
+ depends on ARCH_MXC || COMPILE_TEST
+ depends on MEDIA_CONTROLLER && VIDEO_V4L2
+ depends on VIDEO_V4L2_SUBDEV_API
+ depends on HAS_DMA
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_FWNODE
+ help
+ Say yes here to enable support for video4linux media controller
+ driver for the i.MX5/6 SOC.
+
+if VIDEO_IMX_CAPTURE
+menu "i.MX8QXP/QM Camera ISI/MIPI Features support"
+
+config IMX8_MEDIA_DEVICE
+ tristate "IMX8 Media Device Driver"
+ select V4L2_FWNODE
+ default y
+ help
+ This media device is a virtual device which used to manage
+ all modules in image subsystem of imx8qxp/qm platform.
+
+config IMX8_ISI_HW
+ tristate "IMX8 Image Sensor Interface hardware driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ ISI hardware driver is used to export functions to config
+ ISI registers and it is shared by isi capture and mem2mem
+ driver
+
+config IMX8_ISI_CORE
+ tristate "IMX8 Image Sensor Interface Core Driver"
+ depends on IMX8_ISI_CAPTURE && IMX8_ISI_M2M
+ default y
+
+config IMX8_ISI_CAPTURE
+ tristate "IMX8 Image Sensor Interface Capture Device Driver"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ depends on IMX8_ISI_HW
+ select VIDEOBUF2_DMA_CONTIG
+ default y
+
+config IMX8_ISI_M2M
+ tristate "IMX8 Image Sensor Interface Memory to Memory Device Driver"
+ select V4L2_MEM2MEM_DEV
+ depends on IMX8_ISI_HW
+ default y
+
+config IMX8_MIPI_CSI2
+ tristate "IMX8 MIPI CSI2 Controller"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ Enable support for video4linux camera sensor interface driver for
+ i.MX8QM/QXP platform.
+
+config IMX8_MIPI_CSI2_SAM
+ tristate "IMX8 MIPI CSI2 SAMSUNG Controller"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ Enable support for video4linux MIPI CSI2 Samsung driver for
+ i.MX8MN platform.
+
+config IMX8_PARALLEL_CSI
+ tristate "IMX8 Parallel Capture Controller"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ Enable support for video4linux parallel camera sensor interface
+ driver for i.MX8QM/QXP platform.
+
+config GMSL_MAX9286
+ tristate "GMSL MAX8286"
+ depends on VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
+ default y
+ help
+ Enable support for video4linux camera sensor driver for GMSL MAX9286
+
+endmenu
+endif #VIDEO_IMX_CAPTURE
diff --git a/drivers/staging/media/imx/Makefile b/drivers/staging/media/imx/Makefile
index 9bd9e873ba7c..90173fdac967 100644
--- a/drivers/staging/media/imx/Makefile
+++ b/drivers/staging/media/imx/Makefile
@@ -3,6 +3,8 @@ imx6-media-objs := imx-media-dev.o imx-media-internal-sd.o \
imx-ic-common.o imx-ic-prp.o imx-ic-prpencvf.o imx-media-vdic.o \
imx-media-csc-scaler.o
+imx8-capture-objs := imx8-isi-core.o
+
imx-media-common-objs := imx-media-capture.o imx-media-dev-common.o \
imx-media-of.o imx-media-utils.o
@@ -16,3 +18,12 @@ 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
+obj-$(CONFIG_IMX8_MEDIA_DEVICE) += imx8-media-dev.o
+obj-$(CONFIG_IMX8_ISI_CORE) += imx8-capture.o
+obj-$(CONFIG_IMX8_ISI_CAPTURE) += imx8-isi-cap.o
+obj-$(CONFIG_IMX8_ISI_M2M) += imx8-isi-m2m.o
+obj-$(CONFIG_IMX8_ISI_HW) += imx8-isi-hw.o
+obj-$(CONFIG_IMX8_MIPI_CSI2) += imx8-mipi-csi2.o
+obj-$(CONFIG_IMX8_MIPI_CSI2_SAM) += imx8-mipi-csi2-sam.o
+obj-$(CONFIG_IMX8_PARALLEL_CSI) += imx8-parallel-csi.o
+obj-$(CONFIG_GMSL_MAX9286) += gmsl-max9286.o
diff --git a/drivers/staging/media/imx/gmsl-max9286.c b/drivers/staging/media/imx/gmsl-max9286.c
new file mode 100644
index 000000000000..2d48b0f4b925
--- /dev/null
+++ b/drivers/staging/media/imx/gmsl-max9286.c
@@ -0,0 +1,3344 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019 NXP Semiconductor
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+#include <linux/delay.h>
+#include <linux/clk.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <linux/v4l2-mediabus.h>
+#include <linux/of_gpio.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/regulator/consumer.h>
+#include <media/v4l2-subdev.h>
+
+#define MAX9271_MAX_SENSOR_NUM 4
+#define CAMERA_USES_15HZ
+
+#define ADDR_MAX9286 0x6A
+#define ADDR_MAX9271 0x40
+#define ADDR_MAX9271_ALL (ADDR_MAX9271 + 5) /* Broadcast address */
+
+#define MIPI_CSI2_SENS_VC0_PAD_SOURCE 0
+#define MIPI_CSI2_SENS_VC1_PAD_SOURCE 1
+#define MIPI_CSI2_SENS_VC2_PAD_SOURCE 2
+#define MIPI_CSI2_SENS_VC3_PAD_SOURCE 3
+#define MIPI_CSI2_SENS_VCX_PADS_NUM 4
+
+#define MAX_FPS 30
+#define MIN_FPS 15
+#define DEFAULT_FPS 30
+
+#define ADDR_OV_SENSOR 0x30
+#define ADDR_AP_SENSOR 0x5D
+
+/*!
+ * Maintains the information on the current state of the sesor.
+ */
+struct imxdpu_videomode {
+ char name[64]; /* may not be needed */
+
+ u32 pixelclock; /* Hz */
+
+ /* htotal (pixels) = hlen + hfp + hsync + hbp */
+ u32 hlen;
+ u32 hfp;
+ u32 hbp;
+ u32 hsync;
+
+ /* field0 - vtotal (lines) = vlen + vfp + vsync + vbp */
+ u32 vlen;
+ u32 vfp;
+ u32 vbp;
+ u32 vsync;
+
+ /* field1 */
+ u32 vlen1;
+ u32 vfp1;
+ u32 vbp1;
+ u32 vsync1;
+
+ u32 flags;
+
+ u32 format;
+ u32 dest_format; /*buffer format for capture*/
+
+ s16 clip_top;
+ s16 clip_left;
+ u16 clip_width;
+ u16 clip_height;
+};
+
+enum ov10635_mode {
+ ov10635_mode_MIN = 0,
+ ov10635_mode_WXGA_1280_800 = 0,
+ ov10635_mode_720P_1280_720 = 1,
+ ov10635_mode_WVGA_752_480 = 2,
+ ov10635_mode_VGA_640_480 = 3,
+ ov10635_mode_CIF_352_288 = 4,
+ ov10635_mode_QVGA_320_240 = 5,
+ ov10635_mode_MAX = 5,
+};
+
+enum ov10635_frame_rate {
+ OV10635_15_FPS = 0,
+ OV10635_30_FPS,
+};
+
+struct sensor_data {
+ struct v4l2_subdev subdev;
+ struct media_pad pads[MIPI_CSI2_SENS_VCX_PADS_NUM];
+ struct i2c_client *i2c_client;
+ struct v4l2_mbus_framefmt format;
+ enum ov10635_frame_rate current_fr;
+ enum ov10635_mode current_mode;
+ struct v4l2_fract frame_interval;
+
+ /* lock to protect shared members */
+ struct mutex lock;
+ char running;
+
+ /* control settings */
+ int brightness;
+ int hue;
+ int contrast;
+ int saturation;
+ int red;
+ int green;
+ int blue;
+ int ae_mode;
+
+ u32 mclk;
+ u8 mclk_source;
+ struct clk *sensor_clk;
+ int v_channel;
+ bool is_mipi;
+ struct imxdpu_videomode cap_mode;
+
+ unsigned int sensor_num; /* sensor num connect max9271 */
+ unsigned char sensor_is_there; /* Bit 0~3 for 4 cameras
+ * 0b1= is there;
+ * 0b0 = is not there
+ */
+ struct gpio_desc *pwn_gpio;
+};
+
+#define OV10635_REG_PID 0x300A
+#define OV10635_REG_VER 0x300B
+
+struct reg_value {
+ unsigned short reg_addr;
+ unsigned char val;
+ unsigned int delay_ms;
+};
+
+static int ov10635_framerates[] = {
+ [OV10635_15_FPS] = 15,
+ [OV10635_30_FPS] = 30,
+};
+
+static struct reg_value ov10635_init_data[] = {
+ { 0x0103, 0x01, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x300c, 0x61, 0 },
+ { 0x301b, 0xff, 0 },
+ { 0x301c, 0xff, 0 },
+ { 0x301a, 0xff, 0 },
+ { 0x3011, 0x42, 0 },
+ { 0x6900, 0x0c, 0 },
+ { 0x6901, 0x11, 0 },
+ { 0x3503, 0x10, 0 },
+ { 0x3025, 0x03, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ { 0x3600, 0x74, 0 },
+ { 0x3601, 0x2b, 0 },
+ { 0x3612, 0x00, 0 },
+ { 0x3611, 0x67, 0 },
+ { 0x3633, 0xca, 0 },
+ { 0x3602, 0x2f, 0 },
+ { 0x3603, 0x00, 0 },
+ { 0x3630, 0x28, 0 },
+ { 0x3631, 0x16, 0 },
+ { 0x3714, 0x10, 0 },
+ { 0x371d, 0x01, 0 },
+ { 0x4300, 0x38, 0 },
+ { 0x3007, 0x01, 0 },
+ { 0x3024, 0x01, 0 },
+ { 0x3020, 0x0b, 0 },
+ { 0x3702, 0x20, 0 },
+ { 0x3703, 0x48, 0 },
+ { 0x3704, 0x32, 0 },
+ { 0x3709, 0xa8, 0 },
+ { 0x3709, 0xa8, 0 },
+ { 0x370c, 0xc7, 0 },
+ { 0x370d, 0x80, 0 },
+ { 0x3712, 0x00, 0 },
+ { 0x3713, 0x20, 0 },
+ { 0x3715, 0x04, 0 },
+ { 0x381d, 0x40, 0 },
+ { 0x381c, 0x00, 0 },
+ { 0x3822, 0x50, 0 },
+ { 0x3824, 0x50, 0 },
+ { 0x3815, 0x8c, 0 },
+ { 0x3804, 0x05, 0 },
+ { 0x3805, 0x1f, 0 },
+ { 0x3800, 0x00, 0 },
+ { 0x3801, 0x00, 0 },
+ { 0x3806, 0x03, 0 },
+ { 0x3807, 0x29, 0 },
+ { 0x3802, 0x00, 0 },
+ { 0x3803, 0x04, 0 },
+ { 0x3808, 0x05, 0 },
+ { 0x3809, 0x00, 0 },
+ { 0x380a, 0x03, 0 },
+ { 0x380b, 0x20, 0 },
+ { 0x380c, 0x07, 0 },
+ { 0x380d, 0x71, 0 },
+ { 0x6e42, 0x03, 0 },
+ { 0x6e43, 0x48, 0 },
+ { 0x380e, 0x03, 0 },
+ { 0x380f, 0x48, 0 },
+ { 0x3813, 0x02, 0 },
+ { 0x3811, 0x10, 0 },
+ { 0x381f, 0x0c, 0 },
+ { 0x3828, 0x03, 0 },
+ { 0x3829, 0x10, 0 },
+ { 0x382a, 0x10, 0 },
+ { 0x382b, 0x10, 0 },
+ { 0x3621, 0x64, 0 },
+ { 0x5005, 0x08, 0 },
+ { 0x56d5, 0x00, 0 },
+ { 0x56d6, 0x80, 0 },
+ { 0x56d7, 0x00, 0 },
+ { 0x56d8, 0x00, 0 },
+ { 0x56d9, 0x00, 0 },
+ { 0x56da, 0x80, 0 },
+ { 0x56db, 0x00, 0 },
+ { 0x56dc, 0x00, 0 },
+ { 0x56e8, 0x00, 0 },
+ { 0x56e9, 0x7f, 0 },
+ { 0x56ea, 0x00, 0 },
+ { 0x56eb, 0x7f, 0 },
+ { 0x5100, 0x00, 0 },
+ { 0x5101, 0x80, 0 },
+ { 0x5102, 0x00, 0 },
+ { 0x5103, 0x80, 0 },
+ { 0x5104, 0x00, 0 },
+ { 0x5105, 0x80, 0 },
+ { 0x5106, 0x00, 0 },
+ { 0x5107, 0x80, 0 },
+ { 0x5108, 0x00, 0 },
+ { 0x5109, 0x00, 0 },
+ { 0x510a, 0x00, 0 },
+ { 0x510b, 0x00, 0 },
+ { 0x510c, 0x00, 0 },
+ { 0x510d, 0x00, 0 },
+ { 0x510e, 0x00, 0 },
+ { 0x510f, 0x00, 0 },
+ { 0x5110, 0x00, 0 },
+ { 0x5111, 0x80, 0 },
+ { 0x5112, 0x00, 0 },
+ { 0x5113, 0x80, 0 },
+ { 0x5114, 0x00, 0 },
+ { 0x5115, 0x80, 0 },
+ { 0x5116, 0x00, 0 },
+ { 0x5117, 0x80, 0 },
+ { 0x5118, 0x00, 0 },
+ { 0x5119, 0x00, 0 },
+ { 0x511a, 0x00, 0 },
+ { 0x511b, 0x00, 0 },
+ { 0x511c, 0x00, 0 },
+ { 0x511d, 0x00, 0 },
+ { 0x511e, 0x00, 0 },
+ { 0x511f, 0x00, 0 },
+ { 0x56d0, 0x00, 0 },
+ { 0x5006, 0x24, 0 },
+ { 0x5608, 0x0d, 0 },
+ { 0x52d7, 0x06, 0 },
+ { 0x528d, 0x08, 0 },
+ { 0x5293, 0x12, 0 },
+ { 0x52d3, 0x12, 0 },
+ { 0x5288, 0x06, 0 },
+ { 0x5289, 0x20, 0 },
+ { 0x52c8, 0x06, 0 },
+ { 0x52c9, 0x20, 0 },
+ { 0x52cd, 0x04, 0 },
+ { 0x5381, 0x00, 0 },
+ { 0x5382, 0xff, 0 },
+ { 0x5589, 0x76, 0 },
+ { 0x558a, 0x47, 0 },
+ { 0x558b, 0xef, 0 },
+ { 0x558c, 0xc9, 0 },
+ { 0x558d, 0x49, 0 },
+ { 0x558e, 0x30, 0 },
+ { 0x558f, 0x67, 0 },
+ { 0x5590, 0x3f, 0 },
+ { 0x5591, 0xf0, 0 },
+ { 0x5592, 0x10, 0 },
+ { 0x55a2, 0x6d, 0 },
+ { 0x55a3, 0x55, 0 },
+ { 0x55a4, 0xc3, 0 },
+ { 0x55a5, 0xb5, 0 },
+ { 0x55a6, 0x43, 0 },
+ { 0x55a7, 0x38, 0 },
+ { 0x55a8, 0x5f, 0 },
+ { 0x55a9, 0x4b, 0 },
+ { 0x55aa, 0xf0, 0 },
+ { 0x55ab, 0x10, 0 },
+ { 0x5581, 0x52, 0 },
+ { 0x5300, 0x01, 0 },
+ { 0x5301, 0x00, 0 },
+ { 0x5302, 0x00, 0 },
+ { 0x5303, 0x0e, 0 },
+ { 0x5304, 0x00, 0 },
+ { 0x5305, 0x0e, 0 },
+ { 0x5306, 0x00, 0 },
+ { 0x5307, 0x36, 0 },
+ { 0x5308, 0x00, 0 },
+ { 0x5309, 0xd9, 0 },
+ { 0x530a, 0x00, 0 },
+ { 0x530b, 0x0f, 0 },
+ { 0x530c, 0x00, 0 },
+ { 0x530d, 0x2c, 0 },
+ { 0x530e, 0x00, 0 },
+ { 0x530f, 0x59, 0 },
+ { 0x5310, 0x00, 0 },
+ { 0x5311, 0x7b, 0 },
+ { 0x5312, 0x00, 0 },
+ { 0x5313, 0x22, 0 },
+ { 0x5314, 0x00, 0 },
+ { 0x5315, 0xd5, 0 },
+ { 0x5316, 0x00, 0 },
+ { 0x5317, 0x13, 0 },
+ { 0x5318, 0x00, 0 },
+ { 0x5319, 0x18, 0 },
+ { 0x531a, 0x00, 0 },
+ { 0x531b, 0x26, 0 },
+ { 0x531c, 0x00, 0 },
+ { 0x531d, 0xdc, 0 },
+ { 0x531e, 0x00, 0 },
+ { 0x531f, 0x02, 0 },
+ { 0x5320, 0x00, 0 },
+ { 0x5321, 0x24, 0 },
+ { 0x5322, 0x00, 0 },
+ { 0x5323, 0x56, 0 },
+ { 0x5324, 0x00, 0 },
+ { 0x5325, 0x85, 0 },
+ { 0x5326, 0x00, 0 },
+ { 0x5327, 0x20, 0 },
+ { 0x5609, 0x01, 0 },
+ { 0x560a, 0x40, 0 },
+ { 0x560b, 0x01, 0 },
+ { 0x560c, 0x40, 0 },
+ { 0x560d, 0x00, 0 },
+ { 0x560e, 0xfa, 0 },
+ { 0x560f, 0x00, 0 },
+ { 0x5610, 0xfa, 0 },
+ { 0x5611, 0x02, 0 },
+ { 0x5612, 0x80, 0 },
+ { 0x5613, 0x02, 0 },
+ { 0x5614, 0x80, 0 },
+ { 0x5615, 0x01, 0 },
+ { 0x5616, 0x2c, 0 },
+ { 0x5617, 0x01, 0 },
+ { 0x5618, 0x2c, 0 },
+ { 0x563b, 0x01, 0 },
+ { 0x563c, 0x01, 0 },
+ { 0x563d, 0x01, 0 },
+ { 0x563e, 0x01, 0 },
+ { 0x563f, 0x03, 0 },
+ { 0x5640, 0x03, 0 },
+ { 0x5641, 0x03, 0 },
+ { 0x5642, 0x05, 0 },
+ { 0x5643, 0x09, 0 },
+ { 0x5644, 0x05, 0 },
+ { 0x5645, 0x05, 0 },
+ { 0x5646, 0x05, 0 },
+ { 0x5647, 0x05, 0 },
+ { 0x5651, 0x00, 0 },
+ { 0x5652, 0x80, 0 },
+ { 0x521a, 0x01, 0 },
+ { 0x521b, 0x03, 0 },
+ { 0x521c, 0x06, 0 },
+ { 0x521d, 0x0a, 0 },
+ { 0x521e, 0x0e, 0 },
+ { 0x521f, 0x12, 0 },
+ { 0x5220, 0x16, 0 },
+ { 0x5223, 0x02, 0 },
+ { 0x5225, 0x04, 0 },
+ { 0x5227, 0x08, 0 },
+ { 0x5229, 0x0c, 0 },
+ { 0x522b, 0x12, 0 },
+ { 0x522d, 0x18, 0 },
+ { 0x522f, 0x1e, 0 },
+ { 0x5241, 0x04, 0 },
+ { 0x5242, 0x01, 0 },
+ { 0x5243, 0x03, 0 },
+ { 0x5244, 0x06, 0 },
+ { 0x5245, 0x0a, 0 },
+ { 0x5246, 0x0e, 0 },
+ { 0x5247, 0x12, 0 },
+ { 0x5248, 0x16, 0 },
+ { 0x524a, 0x03, 0 },
+ { 0x524c, 0x04, 0 },
+ { 0x524e, 0x08, 0 },
+ { 0x5250, 0x0c, 0 },
+ { 0x5252, 0x12, 0 },
+ { 0x5254, 0x18, 0 },
+ { 0x5256, 0x1e, 0 },
+ { 0x4606, 0x07, 0 },
+ { 0x4607, 0x71, 0 },
+ { 0x460a, 0x02, 0 },
+ { 0x460b, 0x70, 0 },
+ { 0x460c, 0x00, 0 },
+ { 0x4620, 0x0e, 0 },
+ { 0x4700, 0x04, 0 },
+ { 0x4701, 0x00, 0 },
+ { 0x4702, 0x01, 0 },
+ { 0x4004, 0x04, 0 },
+ { 0x4005, 0x18, 0 },
+ { 0x4001, 0x06, 0 },
+ { 0x4050, 0x22, 0 },
+ { 0x4051, 0x24, 0 },
+ { 0x4052, 0x02, 0 },
+ { 0x4057, 0x9c, 0 },
+ { 0x405a, 0x00, 0 },
+ { 0x4202, 0x02, 0 },
+ { 0x3023, 0x10, 0 },
+ { 0x0100, 0x0f, 0 },
+ { 0x0100, 0x0f, 0 },
+ { 0x6f10, 0x07, 0 },
+ { 0x6f11, 0x82, 0 },
+ { 0x6f12, 0x04, 0 },
+ { 0x6f13, 0x00, 0 },
+ { 0x6f14, 0x1f, 0 },
+ { 0x6f15, 0xdd, 0 },
+ { 0x6f16, 0x04, 0 },
+ { 0x6f17, 0x04, 0 },
+ { 0x6f18, 0x36, 0 },
+ { 0x6f19, 0x66, 0 },
+ { 0x6f1a, 0x04, 0 },
+ { 0x6f1b, 0x08, 0 },
+ { 0x6f1c, 0x0c, 0 },
+ { 0x6f1d, 0xe7, 0 },
+ { 0x6f1e, 0x04, 0 },
+ { 0x6f1f, 0x0c, 0 },
+ { 0xd000, 0x19, 0 },
+ { 0xd001, 0xa0, 0 },
+ { 0xd002, 0x00, 0 },
+ { 0xd003, 0x01, 0 },
+ { 0xd004, 0xa9, 0 },
+ { 0xd005, 0xad, 0 },
+ { 0xd006, 0x10, 0 },
+ { 0xd007, 0x40, 0 },
+ { 0xd008, 0x44, 0 },
+ { 0xd009, 0x00, 0 },
+ { 0xd00a, 0x68, 0 },
+ { 0xd00b, 0x00, 0 },
+ { 0xd00c, 0x15, 0 },
+ { 0xd00d, 0x00, 0 },
+ { 0xd00e, 0x00, 0 },
+ { 0xd00f, 0x00, 0 },
+ { 0xd010, 0x19, 0 },
+ { 0xd011, 0xa0, 0 },
+ { 0xd012, 0x00, 0 },
+ { 0xd013, 0x01, 0 },
+ { 0xd014, 0xa9, 0 },
+ { 0xd015, 0xad, 0 },
+ { 0xd016, 0x13, 0 },
+ { 0xd017, 0xd0, 0 },
+ { 0xd018, 0x44, 0 },
+ { 0xd019, 0x00, 0 },
+ { 0xd01a, 0x68, 0 },
+ { 0xd01b, 0x00, 0 },
+ { 0xd01c, 0x15, 0 },
+ { 0xd01d, 0x00, 0 },
+ { 0xd01e, 0x00, 0 },
+ { 0xd01f, 0x00, 0 },
+ { 0xd020, 0x19, 0 },
+ { 0xd021, 0xa0, 0 },
+ { 0xd022, 0x00, 0 },
+ { 0xd023, 0x01, 0 },
+ { 0xd024, 0xa9, 0 },
+ { 0xd025, 0xad, 0 },
+ { 0xd026, 0x14, 0 },
+ { 0xd027, 0xb8, 0 },
+ { 0xd028, 0x44, 0 },
+ { 0xd029, 0x00, 0 },
+ { 0xd02a, 0x68, 0 },
+ { 0xd02b, 0x00, 0 },
+ { 0xd02c, 0x15, 0 },
+ { 0xd02d, 0x00, 0 },
+ { 0xd02e, 0x00, 0 },
+ { 0xd02f, 0x00, 0 },
+ { 0xd030, 0x19, 0 },
+ { 0xd031, 0xa0, 0 },
+ { 0xd032, 0x00, 0 },
+ { 0xd033, 0x01, 0 },
+ { 0xd034, 0xa9, 0 },
+ { 0xd035, 0xad, 0 },
+ { 0xd036, 0x14, 0 },
+ { 0xd037, 0xdc, 0 },
+ { 0xd038, 0x44, 0 },
+ { 0xd039, 0x00, 0 },
+ { 0xd03a, 0x68, 0 },
+ { 0xd03b, 0x00, 0 },
+ { 0xd03c, 0x15, 0 },
+ { 0xd03d, 0x00, 0 },
+ { 0xd03e, 0x00, 0 },
+ { 0xd03f, 0x00, 0 },
+ { 0xd040, 0x9c, 0 },
+ { 0xd041, 0x21, 0 },
+ { 0xd042, 0xff, 0 },
+ { 0xd043, 0xe4, 0 },
+ { 0xd044, 0xd4, 0 },
+ { 0xd045, 0x01, 0 },
+ { 0xd046, 0x48, 0 },
+ { 0xd047, 0x00, 0 },
+ { 0xd048, 0xd4, 0 },
+ { 0xd049, 0x01, 0 },
+ { 0xd04a, 0x50, 0 },
+ { 0xd04b, 0x04, 0 },
+ { 0xd04c, 0xd4, 0 },
+ { 0xd04d, 0x01, 0 },
+ { 0xd04e, 0x60, 0 },
+ { 0xd04f, 0x08, 0 },
+ { 0xd050, 0xd4, 0 },
+ { 0xd051, 0x01, 0 },
+ { 0xd052, 0x70, 0 },
+ { 0xd053, 0x0c, 0 },
+ { 0xd054, 0xd4, 0 },
+ { 0xd055, 0x01, 0 },
+ { 0xd056, 0x80, 0 },
+ { 0xd057, 0x10, 0 },
+ { 0xd058, 0x19, 0 },
+ { 0xd059, 0xc0, 0 },
+ { 0xd05a, 0x00, 0 },
+ { 0xd05b, 0x01, 0 },
+ { 0xd05c, 0xa9, 0 },
+ { 0xd05d, 0xce, 0 },
+ { 0xd05e, 0x02, 0 },
+ { 0xd05f, 0xa4, 0 },
+ { 0xd060, 0x9c, 0 },
+ { 0xd061, 0xa0, 0 },
+ { 0xd062, 0x00, 0 },
+ { 0xd063, 0x00, 0 },
+ { 0xd064, 0x84, 0 },
+ { 0xd065, 0x6e, 0 },
+ { 0xd066, 0x00, 0 },
+ { 0xd067, 0x00, 0 },
+ { 0xd068, 0xd8, 0 },
+ { 0xd069, 0x03, 0 },
+ { 0xd06a, 0x28, 0 },
+ { 0xd06b, 0x76, 0 },
+ { 0xd06c, 0x1a, 0 },
+ { 0xd06d, 0x00, 0 },
+ { 0xd06e, 0x00, 0 },
+ { 0xd06f, 0x01, 0 },
+ { 0xd070, 0xaa, 0 },
+ { 0xd071, 0x10, 0 },
+ { 0xd072, 0x03, 0 },
+ { 0xd073, 0xf0, 0 },
+ { 0xd074, 0x18, 0 },
+ { 0xd075, 0x60, 0 },
+ { 0xd076, 0x00, 0 },
+ { 0xd077, 0x01, 0 },
+ { 0xd078, 0xa8, 0 },
+ { 0xd079, 0x63, 0 },
+ { 0xd07a, 0x07, 0 },
+ { 0xd07b, 0x80, 0 },
+ { 0xd07c, 0xe0, 0 },
+ { 0xd07d, 0xa0, 0 },
+ { 0xd07e, 0x00, 0 },
+ { 0xd07f, 0x04, 0 },
+ { 0xd080, 0x18, 0 },
+ { 0xd081, 0xc0, 0 },
+ { 0xd082, 0x00, 0 },
+ { 0xd083, 0x00, 0 },
+ { 0xd084, 0xa8, 0 },
+ { 0xd085, 0xc6, 0 },
+ { 0xd086, 0x00, 0 },
+ { 0xd087, 0x00, 0 },
+ { 0xd088, 0x8c, 0 },
+ { 0xd089, 0x63, 0 },
+ { 0xd08a, 0x00, 0 },
+ { 0xd08b, 0x00, 0 },
+ { 0xd08c, 0xd4, 0 },
+ { 0xd08d, 0x01, 0 },
+ { 0xd08e, 0x28, 0 },
+ { 0xd08f, 0x14, 0 },
+ { 0xd090, 0xd4, 0 },
+ { 0xd091, 0x01, 0 },
+ { 0xd092, 0x30, 0 },
+ { 0xd093, 0x18, 0 },
+ { 0xd094, 0x07, 0 },
+ { 0xd095, 0xff, 0 },
+ { 0xd096, 0xf8, 0 },
+ { 0xd097, 0xfd, 0 },
+ { 0xd098, 0x9c, 0 },
+ { 0xd099, 0x80, 0 },
+ { 0xd09a, 0x00, 0 },
+ { 0xd09b, 0x03, 0 },
+ { 0xd09c, 0xa5, 0 },
+ { 0xd09d, 0x6b, 0 },
+ { 0xd09e, 0x00, 0 },
+ { 0xd09f, 0xff, 0 },
+ { 0xd0a0, 0x18, 0 },
+ { 0xd0a1, 0xc0, 0 },
+ { 0xd0a2, 0x00, 0 },
+ { 0xd0a3, 0x01, 0 },
+ { 0xd0a4, 0xa8, 0 },
+ { 0xd0a5, 0xc6, 0 },
+ { 0xd0a6, 0x01, 0 },
+ { 0xd0a7, 0x02, 0 },
+ { 0xd0a8, 0xe1, 0 },
+ { 0xd0a9, 0x6b, 0 },
+ { 0xd0aa, 0x58, 0 },
+ { 0xd0ab, 0x00, 0 },
+ { 0xd0ac, 0x84, 0 },
+ { 0xd0ad, 0x8e, 0 },
+ { 0xd0ae, 0x00, 0 },
+ { 0xd0af, 0x00, 0 },
+ { 0xd0b0, 0xe1, 0 },
+ { 0xd0b1, 0x6b, 0 },
+ { 0xd0b2, 0x30, 0 },
+ { 0xd0b3, 0x00, 0 },
+ { 0xd0b4, 0x98, 0 },
+ { 0xd0b5, 0xb0, 0 },
+ { 0xd0b6, 0x00, 0 },
+ { 0xd0b7, 0x00, 0 },
+ { 0xd0b8, 0x8c, 0 },
+ { 0xd0b9, 0x64, 0 },
+ { 0xd0ba, 0x00, 0 },
+ { 0xd0bb, 0x6e, 0 },
+ { 0xd0bc, 0xe5, 0 },
+ { 0xd0bd, 0xa5, 0 },
+ { 0xd0be, 0x18, 0 },
+ { 0xd0bf, 0x00, 0 },
+ { 0xd0c0, 0x10, 0 },
+ { 0xd0c1, 0x00, 0 },
+ { 0xd0c2, 0x00, 0 },
+ { 0xd0c3, 0x06, 0 },
+ { 0xd0c4, 0x95, 0 },
+ { 0xd0c5, 0x8b, 0 },
+ { 0xd0c6, 0x00, 0 },
+ { 0xd0c7, 0x00, 0 },
+ { 0xd0c8, 0x94, 0 },
+ { 0xd0c9, 0xa4, 0 },
+ { 0xd0ca, 0x00, 0 },
+ { 0xd0cb, 0x70, 0 },
+ { 0xd0cc, 0xe5, 0 },
+ { 0xd0cd, 0x65, 0 },
+ { 0xd0ce, 0x60, 0 },
+ { 0xd0cf, 0x00, 0 },
+ { 0xd0d0, 0x0c, 0 },
+ { 0xd0d1, 0x00, 0 },
+ { 0xd0d2, 0x00, 0 },
+ { 0xd0d3, 0x62, 0 },
+ { 0xd0d4, 0x15, 0 },
+ { 0xd0d5, 0x00, 0 },
+ { 0xd0d6, 0x00, 0 },
+ { 0xd0d7, 0x00, 0 },
+ { 0xd0d8, 0x18, 0 },
+ { 0xd0d9, 0x60, 0 },
+ { 0xd0da, 0x80, 0 },
+ { 0xd0db, 0x06, 0 },
+ { 0xd0dc, 0xa8, 0 },
+ { 0xd0dd, 0x83, 0 },
+ { 0xd0de, 0x38, 0 },
+ { 0xd0df, 0x29, 0 },
+ { 0xd0e0, 0xa8, 0 },
+ { 0xd0e1, 0xe3, 0 },
+ { 0xd0e2, 0x40, 0 },
+ { 0xd0e3, 0x08, 0 },
+ { 0xd0e4, 0x8c, 0 },
+ { 0xd0e5, 0x84, 0 },
+ { 0xd0e6, 0x00, 0 },
+ { 0xd0e7, 0x00, 0 },
+ { 0xd0e8, 0xa8, 0 },
+ { 0xd0e9, 0xa3, 0 },
+ { 0xd0ea, 0x40, 0 },
+ { 0xd0eb, 0x09, 0 },
+ { 0xd0ec, 0xa8, 0 },
+ { 0xd0ed, 0xc3, 0 },
+ { 0xd0ee, 0x38, 0 },
+ { 0xd0ef, 0x2a, 0 },
+ { 0xd0f0, 0xd8, 0 },
+ { 0xd0f1, 0x07, 0 },
+ { 0xd0f2, 0x20, 0 },
+ { 0xd0f3, 0x00, 0 },
+ { 0xd0f4, 0x8c, 0 },
+ { 0xd0f5, 0x66, 0 },
+ { 0xd0f6, 0x00, 0 },
+ { 0xd0f7, 0x00, 0 },
+ { 0xd0f8, 0xd8, 0 },
+ { 0xd0f9, 0x05, 0 },
+ { 0xd0fa, 0x18, 0 },
+ { 0xd0fb, 0x00, 0 },
+ { 0xd0fc, 0x18, 0 },
+ { 0xd0fd, 0x60, 0 },
+ { 0xd0fe, 0x00, 0 },
+ { 0xd0ff, 0x01, 0 },
+ { 0xd100, 0x98, 0 },
+ { 0xd101, 0x90, 0 },
+ { 0xd102, 0x00, 0 },
+ { 0xd103, 0x00, 0 },
+ { 0xd104, 0x84, 0 },
+ { 0xd105, 0xae, 0 },
+ { 0xd106, 0x00, 0 },
+ { 0xd107, 0x00, 0 },
+ { 0xd108, 0xa8, 0 },
+ { 0xd109, 0x63, 0 },
+ { 0xd10a, 0x06, 0 },
+ { 0xd10b, 0x4c, 0 },
+ { 0xd10c, 0x9c, 0 },
+ { 0xd10d, 0xc0, 0 },
+ { 0xd10e, 0x00, 0 },
+ { 0xd10f, 0x00, 0 },
+ { 0xd110, 0xd8, 0 },
+ { 0xd111, 0x03, 0 },
+ { 0xd112, 0x30, 0 },
+ { 0xd113, 0x00, 0 },
+ { 0xd114, 0x8c, 0 },
+ { 0xd115, 0x65, 0 },
+ { 0xd116, 0x00, 0 },
+ { 0xd117, 0x6e, 0 },
+ { 0xd118, 0xe5, 0 },
+ { 0xd119, 0x84, 0 },
+ { 0xd11a, 0x18, 0 },
+ { 0xd11b, 0x00, 0 },
+ { 0xd11c, 0x10, 0 },
+ { 0xd11d, 0x00, 0 },
+ { 0xd11e, 0x00, 0 },
+ { 0xd11f, 0x07, 0 },
+ { 0xd120, 0x18, 0 },
+ { 0xd121, 0x80, 0 },
+ { 0xd122, 0x80, 0 },
+ { 0xd123, 0x06, 0 },
+ { 0xd124, 0x94, 0 },
+ { 0xd125, 0x65, 0 },
+ { 0xd126, 0x00, 0 },
+ { 0xd127, 0x70, 0 },
+ { 0xd128, 0xe5, 0 },
+ { 0xd129, 0x43, 0 },
+ { 0xd12a, 0x60, 0 },
+ { 0xd12b, 0x00, 0 },
+ { 0xd12c, 0x0c, 0 },
+ { 0xd12d, 0x00, 0 },
+ { 0xd12e, 0x00, 0 },
+ { 0xd12f, 0x3e, 0 },
+ { 0xd130, 0xa8, 0 },
+ { 0xd131, 0x64, 0 },
+ { 0xd132, 0x38, 0 },
+ { 0xd133, 0x24, 0 },
+ { 0xd134, 0x18, 0 },
+ { 0xd135, 0x80, 0 },
+ { 0xd136, 0x80, 0 },
+ { 0xd137, 0x06, 0 },
+ { 0xd138, 0xa8, 0 },
+ { 0xd139, 0x64, 0 },
+ { 0xd13a, 0x38, 0 },
+ { 0xd13b, 0x24, 0 },
+ { 0xd13c, 0x8c, 0 },
+ { 0xd13d, 0x63, 0 },
+ { 0xd13e, 0x00, 0 },
+ { 0xd13f, 0x00, 0 },
+ { 0xd140, 0xa4, 0 },
+ { 0xd141, 0x63, 0 },
+ { 0xd142, 0x00, 0 },
+ { 0xd143, 0x40, 0 },
+ { 0xd144, 0xbc, 0 },
+ { 0xd145, 0x23, 0 },
+ { 0xd146, 0x00, 0 },
+ { 0xd147, 0x00, 0 },
+ { 0xd148, 0x0c, 0 },
+ { 0xd149, 0x00, 0 },
+ { 0xd14a, 0x00, 0 },
+ { 0xd14b, 0x2a, 0 },
+ { 0xd14c, 0xa8, 0 },
+ { 0xd14d, 0x64, 0 },
+ { 0xd14e, 0x6e, 0 },
+ { 0xd14f, 0x44, 0 },
+ { 0xd150, 0x19, 0 },
+ { 0xd151, 0x00, 0 },
+ { 0xd152, 0x80, 0 },
+ { 0xd153, 0x06, 0 },
+ { 0xd154, 0xa8, 0 },
+ { 0xd155, 0xe8, 0 },
+ { 0xd156, 0x3d, 0 },
+ { 0xd157, 0x05, 0 },
+ { 0xd158, 0x8c, 0 },
+ { 0xd159, 0x67, 0 },
+ { 0xd15a, 0x00, 0 },
+ { 0xd15b, 0x00, 0 },
+ { 0xd15c, 0xb8, 0 },
+ { 0xd15d, 0x63, 0 },
+ { 0xd15e, 0x00, 0 },
+ { 0xd15f, 0x18, 0 },
+ { 0xd160, 0xb8, 0 },
+ { 0xd161, 0x63, 0 },
+ { 0xd162, 0x00, 0 },
+ { 0xd163, 0x98, 0 },
+ { 0xd164, 0xbc, 0 },
+ { 0xd165, 0x03, 0 },
+ { 0xd166, 0x00, 0 },
+ { 0xd167, 0x00, 0 },
+ { 0xd168, 0x10, 0 },
+ { 0xd169, 0x00, 0 },
+ { 0xd16a, 0x00, 0 },
+ { 0xd16b, 0x10, 0 },
+ { 0xd16c, 0xa9, 0 },
+ { 0xd16d, 0x48, 0 },
+ { 0xd16e, 0x67, 0 },
+ { 0xd16f, 0x02, 0 },
+ { 0xd170, 0xb8, 0 },
+ { 0xd171, 0xa3, 0 },
+ { 0xd172, 0x00, 0 },
+ { 0xd173, 0x19, 0 },
+ { 0xd174, 0x8c, 0 },
+ { 0xd175, 0x8a, 0 },
+ { 0xd176, 0x00, 0 },
+ { 0xd177, 0x00, 0 },
+ { 0xd178, 0xa9, 0 },
+ { 0xd179, 0x68, 0 },
+ { 0xd17a, 0x67, 0 },
+ { 0xd17b, 0x03, 0 },
+ { 0xd17c, 0xb8, 0 },
+ { 0xd17d, 0xc4, 0 },
+ { 0xd17e, 0x00, 0 },
+ { 0xd17f, 0x08, 0 },
+ { 0xd180, 0x8c, 0 },
+ { 0xd181, 0x6b, 0 },
+ { 0xd182, 0x00, 0 },
+ { 0xd183, 0x00, 0 },
+ { 0xd184, 0xb8, 0 },
+ { 0xd185, 0x85, 0 },
+ { 0xd186, 0x00, 0 },
+ { 0xd187, 0x98, 0 },
+ { 0xd188, 0xe0, 0 },
+ { 0xd189, 0x63, 0 },
+ { 0xd18a, 0x30, 0 },
+ { 0xd18b, 0x04, 0 },
+ { 0xd18c, 0xe0, 0 },
+ { 0xd18d, 0x64, 0 },
+ { 0xd18e, 0x18, 0 },
+ { 0xd18f, 0x00, 0 },
+ { 0xd190, 0xa4, 0 },
+ { 0xd191, 0x83, 0 },
+ { 0xd192, 0xff, 0 },
+ { 0xd193, 0xff, 0 },
+ { 0xd194, 0xb8, 0 },
+ { 0xd195, 0x64, 0 },
+ { 0xd196, 0x00, 0 },
+ { 0xd197, 0x48, 0 },
+ { 0xd198, 0xd8, 0 },
+ { 0xd199, 0x0a, 0 },
+ { 0xd19a, 0x18, 0 },
+ { 0xd19b, 0x00, 0 },
+ { 0xd19c, 0xd8, 0 },
+ { 0xd19d, 0x0b, 0 },
+ { 0xd19e, 0x20, 0 },
+ { 0xd19f, 0x00, 0 },
+ { 0xd1a0, 0x9c, 0 },
+ { 0xd1a1, 0x60, 0 },
+ { 0xd1a2, 0x00, 0 },
+ { 0xd1a3, 0x00, 0 },
+ { 0xd1a4, 0xd8, 0 },
+ { 0xd1a5, 0x07, 0 },
+ { 0xd1a6, 0x18, 0 },
+ { 0xd1a7, 0x00, 0 },
+ { 0xd1a8, 0xa8, 0 },
+ { 0xd1a9, 0x68, 0 },
+ { 0xd1aa, 0x38, 0 },
+ { 0xd1ab, 0x22, 0 },
+ { 0xd1ac, 0x9c, 0 },
+ { 0xd1ad, 0x80, 0 },
+ { 0xd1ae, 0x00, 0 },
+ { 0xd1af, 0x70, 0 },
+ { 0xd1b0, 0xa8, 0 },
+ { 0xd1b1, 0xe8, 0 },
+ { 0xd1b2, 0x38, 0 },
+ { 0xd1b3, 0x43, 0 },
+ { 0xd1b4, 0xd8, 0 },
+ { 0xd1b5, 0x03, 0 },
+ { 0xd1b6, 0x20, 0 },
+ { 0xd1b7, 0x00, 0 },
+ { 0xd1b8, 0x9c, 0 },
+ { 0xd1b9, 0xa0, 0 },
+ { 0xd1ba, 0x00, 0 },
+ { 0xd1bb, 0x00, 0 },
+ { 0xd1bc, 0xa8, 0 },
+ { 0xd1bd, 0xc8, 0 },
+ { 0xd1be, 0x38, 0 },
+ { 0xd1bf, 0x42, 0 },
+ { 0xd1c0, 0x8c, 0 },
+ { 0xd1c1, 0x66, 0 },
+ { 0xd1c2, 0x00, 0 },
+ { 0xd1c3, 0x00, 0 },
+ { 0xd1c4, 0x9c, 0 },
+ { 0xd1c5, 0xa5, 0 },
+ { 0xd1c6, 0x00, 0 },
+ { 0xd1c7, 0x01, 0 },
+ { 0xd1c8, 0xb8, 0 },
+ { 0xd1c9, 0x83, 0 },
+ { 0xd1ca, 0x00, 0 },
+ { 0xd1cb, 0x08, 0 },
+ { 0xd1cc, 0xa4, 0 },
+ { 0xd1cd, 0xa5, 0 },
+ { 0xd1ce, 0x00, 0 },
+ { 0xd1cf, 0xff, 0 },
+ { 0xd1d0, 0x8c, 0 },
+ { 0xd1d1, 0x67, 0 },
+ { 0xd1d2, 0x00, 0 },
+ { 0xd1d3, 0x00, 0 },
+ { 0xd1d4, 0xe0, 0 },
+ { 0xd1d5, 0x63, 0 },
+ { 0xd1d6, 0x20, 0 },
+ { 0xd1d7, 0x00, 0 },
+ { 0xd1d8, 0xa4, 0 },
+ { 0xd1d9, 0x63, 0 },
+ { 0xd1da, 0xff, 0 },
+ { 0xd1db, 0xff, 0 },
+ { 0xd1dc, 0xbc, 0 },
+ { 0xd1dd, 0x43, 0 },
+ { 0xd1de, 0x00, 0 },
+ { 0xd1df, 0x07, 0 },
+ { 0xd1e0, 0x0c, 0 },
+ { 0xd1e1, 0x00, 0 },
+ { 0xd1e2, 0x00, 0 },
+ { 0xd1e3, 0x5b, 0 },
+ { 0xd1e4, 0xbc, 0 },
+ { 0xd1e5, 0x05, 0 },
+ { 0xd1e6, 0x00, 0 },
+ { 0xd1e7, 0x02, 0 },
+ { 0xd1e8, 0x03, 0 },
+ { 0xd1e9, 0xff, 0 },
+ { 0xd1ea, 0xff, 0 },
+ { 0xd1eb, 0xf6, 0 },
+ { 0xd1ec, 0x9c, 0 },
+ { 0xd1ed, 0xa0, 0 },
+ { 0xd1ee, 0x00, 0 },
+ { 0xd1ef, 0x00, 0 },
+ { 0xd1f0, 0xa8, 0 },
+ { 0xd1f1, 0xa4, 0 },
+ { 0xd1f2, 0x55, 0 },
+ { 0xd1f3, 0x86, 0 },
+ { 0xd1f4, 0x8c, 0 },
+ { 0xd1f5, 0x63, 0 },
+ { 0xd1f6, 0x00, 0 },
+ { 0xd1f7, 0x00, 0 },
+ { 0xd1f8, 0xa8, 0 },
+ { 0xd1f9, 0xc4, 0 },
+ { 0xd1fa, 0x6e, 0 },
+ { 0xd1fb, 0x45, 0 },
+ { 0xd1fc, 0xa8, 0 },
+ { 0xd1fd, 0xe4, 0 },
+ { 0xd1fe, 0x55, 0 },
+ { 0xd1ff, 0x87, 0 },
+ { 0xd200, 0xd8, 0 },
+ { 0xd201, 0x05, 0 },
+ { 0xd202, 0x18, 0 },
+ { 0xd203, 0x00, 0 },
+ { 0xd204, 0x8c, 0 },
+ { 0xd205, 0x66, 0 },
+ { 0xd206, 0x00, 0 },
+ { 0xd207, 0x00, 0 },
+ { 0xd208, 0xa8, 0 },
+ { 0xd209, 0xa4, 0 },
+ { 0xd20a, 0x6e, 0 },
+ { 0xd20b, 0x46, 0 },
+ { 0xd20c, 0xd8, 0 },
+ { 0xd20d, 0x07, 0 },
+ { 0xd20e, 0x18, 0 },
+ { 0xd20f, 0x00, 0 },
+ { 0xd210, 0xa8, 0 },
+ { 0xd211, 0x84, 0 },
+ { 0xd212, 0x55, 0 },
+ { 0xd213, 0x88, 0 },
+ { 0xd214, 0x8c, 0 },
+ { 0xd215, 0x65, 0 },
+ { 0xd216, 0x00, 0 },
+ { 0xd217, 0x00, 0 },
+ { 0xd218, 0xd8, 0 },
+ { 0xd219, 0x04, 0 },
+ { 0xd21a, 0x18, 0 },
+ { 0xd21b, 0x00, 0 },
+ { 0xd21c, 0x03, 0 },
+ { 0xd21d, 0xff, 0 },
+ { 0xd21e, 0xff, 0 },
+ { 0xd21f, 0xce, 0 },
+ { 0xd220, 0x19, 0 },
+ { 0xd221, 0x00, 0 },
+ { 0xd222, 0x80, 0 },
+ { 0xd223, 0x06, 0 },
+ { 0xd224, 0x8c, 0 },
+ { 0xd225, 0x63, 0 },
+ { 0xd226, 0x00, 0 },
+ { 0xd227, 0x00, 0 },
+ { 0xd228, 0xa4, 0 },
+ { 0xd229, 0x63, 0 },
+ { 0xd22a, 0x00, 0 },
+ { 0xd22b, 0x40, 0 },
+ { 0xd22c, 0xbc, 0 },
+ { 0xd22d, 0x23, 0 },
+ { 0xd22e, 0x00, 0 },
+ { 0xd22f, 0x00, 0 },
+ { 0xd230, 0x13, 0 },
+ { 0xd231, 0xff, 0 },
+ { 0xd232, 0xff, 0 },
+ { 0xd233, 0xc8, 0 },
+ { 0xd234, 0x9d, 0 },
+ { 0xd235, 0x00, 0 },
+ { 0xd236, 0x00, 0 },
+ { 0xd237, 0x40, 0 },
+ { 0xd238, 0xa8, 0 },
+ { 0xd239, 0x64, 0 },
+ { 0xd23a, 0x55, 0 },
+ { 0xd23b, 0x86, 0 },
+ { 0xd23c, 0xa8, 0 },
+ { 0xd23d, 0xa4, 0 },
+ { 0xd23e, 0x55, 0 },
+ { 0xd23f, 0x87, 0 },
+ { 0xd240, 0xd8, 0 },
+ { 0xd241, 0x03, 0 },
+ { 0xd242, 0x40, 0 },
+ { 0xd243, 0x00, 0 },
+ { 0xd244, 0xa8, 0 },
+ { 0xd245, 0x64, 0 },
+ { 0xd246, 0x55, 0 },
+ { 0xd247, 0x88, 0 },
+ { 0xd248, 0xd8, 0 },
+ { 0xd249, 0x05, 0 },
+ { 0xd24a, 0x40, 0 },
+ { 0xd24b, 0x00, 0 },
+ { 0xd24c, 0xd8, 0 },
+ { 0xd24d, 0x03, 0 },
+ { 0xd24e, 0x40, 0 },
+ { 0xd24f, 0x00, 0 },
+ { 0xd250, 0x03, 0 },
+ { 0xd251, 0xff, 0 },
+ { 0xd252, 0xff, 0 },
+ { 0xd253, 0xc1, 0 },
+ { 0xd254, 0x19, 0 },
+ { 0xd255, 0x00, 0 },
+ { 0xd256, 0x80, 0 },
+ { 0xd257, 0x06, 0 },
+ { 0xd258, 0x94, 0 },
+ { 0xd259, 0x84, 0 },
+ { 0xd25a, 0x00, 0 },
+ { 0xd25b, 0x72, 0 },
+ { 0xd25c, 0xe5, 0 },
+ { 0xd25d, 0xa4, 0 },
+ { 0xd25e, 0x60, 0 },
+ { 0xd25f, 0x00, 0 },
+ { 0xd260, 0x0c, 0 },
+ { 0xd261, 0x00, 0 },
+ { 0xd262, 0x00, 0 },
+ { 0xd263, 0x3f, 0 },
+ { 0xd264, 0x9d, 0 },
+ { 0xd265, 0x60, 0 },
+ { 0xd266, 0x01, 0 },
+ { 0xd267, 0x00, 0 },
+ { 0xd268, 0x85, 0 },
+ { 0xd269, 0x4e, 0 },
+ { 0xd26a, 0x00, 0 },
+ { 0xd26b, 0x00, 0 },
+ { 0xd26c, 0x98, 0 },
+ { 0xd26d, 0x70, 0 },
+ { 0xd26e, 0x00, 0 },
+ { 0xd26f, 0x00, 0 },
+ { 0xd270, 0x8c, 0 },
+ { 0xd271, 0x8a, 0 },
+ { 0xd272, 0x00, 0 },
+ { 0xd273, 0x6f, 0 },
+ { 0xd274, 0xe5, 0 },
+ { 0xd275, 0x63, 0 },
+ { 0xd276, 0x20, 0 },
+ { 0xd277, 0x00, 0 },
+ { 0xd278, 0x10, 0 },
+ { 0xd279, 0x00, 0 },
+ { 0xd27a, 0x00, 0 },
+ { 0xd27b, 0x07, 0 },
+ { 0xd27c, 0x15, 0 },
+ { 0xd27d, 0x00, 0 },
+ { 0xd27e, 0x00, 0 },
+ { 0xd27f, 0x00, 0 },
+ { 0xd280, 0x8c, 0 },
+ { 0xd281, 0xaa, 0 },
+ { 0xd282, 0x00, 0 },
+ { 0xd283, 0x6e, 0 },
+ { 0xd284, 0xe0, 0 },
+ { 0xd285, 0x63, 0 },
+ { 0xd286, 0x28, 0 },
+ { 0xd287, 0x02, 0 },
+ { 0xd288, 0xe0, 0 },
+ { 0xd289, 0x84, 0 },
+ { 0xd28a, 0x28, 0 },
+ { 0xd28b, 0x02, 0 },
+ { 0xd28c, 0x07, 0 },
+ { 0xd28d, 0xff, 0 },
+ { 0xd28e, 0xf8, 0 },
+ { 0xd28f, 0x66, 0 },
+ { 0xd290, 0xe0, 0 },
+ { 0xd291, 0x63, 0 },
+ { 0xd292, 0x5b, 0 },
+ { 0xd293, 0x06, 0 },
+ { 0xd294, 0x8c, 0 },
+ { 0xd295, 0x6a, 0 },
+ { 0xd296, 0x00, 0 },
+ { 0xd297, 0x77, 0 },
+ { 0xd298, 0xe0, 0 },
+ { 0xd299, 0x63, 0 },
+ { 0xd29a, 0x5b, 0 },
+ { 0xd29b, 0x06, 0 },
+ { 0xd29c, 0xbd, 0 },
+ { 0xd29d, 0x63, 0 },
+ { 0xd29e, 0x00, 0 },
+ { 0xd29f, 0x00, 0 },
+ { 0xd2a0, 0x0c, 0 },
+ { 0xd2a1, 0x00, 0 },
+ { 0xd2a2, 0x00, 0 },
+ { 0xd2a3, 0x3c, 0 },
+ { 0xd2a4, 0x15, 0 },
+ { 0xd2a5, 0x00, 0 },
+ { 0xd2a6, 0x00, 0 },
+ { 0xd2a7, 0x00, 0 },
+ { 0xd2a8, 0x8c, 0 },
+ { 0xd2a9, 0x8a, 0 },
+ { 0xd2aa, 0x00, 0 },
+ { 0xd2ab, 0x78, 0 },
+ { 0xd2ac, 0xb8, 0 },
+ { 0xd2ad, 0x63, 0 },
+ { 0xd2ae, 0x00, 0 },
+ { 0xd2af, 0x88, 0 },
+ { 0xd2b0, 0xe1, 0 },
+ { 0xd2b1, 0x64, 0 },
+ { 0xd2b2, 0x5b, 0 },
+ { 0xd2b3, 0x06, 0 },
+ { 0xd2b4, 0xbd, 0 },
+ { 0xd2b5, 0x6b, 0 },
+ { 0xd2b6, 0x00, 0 },
+ { 0xd2b7, 0x00, 0 },
+ { 0xd2b8, 0x0c, 0 },
+ { 0xd2b9, 0x00, 0 },
+ { 0xd2ba, 0x00, 0 },
+ { 0xd2bb, 0x34, 0 },
+ { 0xd2bc, 0xd4, 0 },
+ { 0xd2bd, 0x01, 0 },
+ { 0xd2be, 0x18, 0 },
+ { 0xd2bf, 0x14, 0 },
+ { 0xd2c0, 0xb9, 0 },
+ { 0xd2c1, 0x6b, 0 },
+ { 0xd2c2, 0x00, 0 },
+ { 0xd2c3, 0x88, 0 },
+ { 0xd2c4, 0x85, 0 },
+ { 0xd2c5, 0x01, 0 },
+ { 0xd2c6, 0x00, 0 },
+ { 0xd2c7, 0x14, 0 },
+ { 0xd2c8, 0xbd, 0 },
+ { 0xd2c9, 0x68, 0 },
+ { 0xd2ca, 0x00, 0 },
+ { 0xd2cb, 0x00, 0 },
+ { 0xd2cc, 0x0c, 0 },
+ { 0xd2cd, 0x00, 0 },
+ { 0xd2ce, 0x00, 0 },
+ { 0xd2cf, 0x2c, 0 },
+ { 0xd2d0, 0xd4, 0 },
+ { 0xd2d1, 0x01, 0 },
+ { 0xd2d2, 0x58, 0 },
+ { 0xd2d3, 0x18, 0 },
+ { 0xd2d4, 0x84, 0 },
+ { 0xd2d5, 0x81, 0 },
+ { 0xd2d6, 0x00, 0 },
+ { 0xd2d7, 0x14, 0 },
+ { 0xd2d8, 0xbd, 0 },
+ { 0xd2d9, 0xa4, 0 },
+ { 0xd2da, 0x01, 0 },
+ { 0xd2db, 0x00, 0 },
+ { 0xd2dc, 0x10, 0 },
+ { 0xd2dd, 0x00, 0 },
+ { 0xd2de, 0x00, 0 },
+ { 0xd2df, 0x05, 0 },
+ { 0xd2e0, 0x84, 0 },
+ { 0xd2e1, 0xc1, 0 },
+ { 0xd2e2, 0x00, 0 },
+ { 0xd2e3, 0x18, 0 },
+ { 0xd2e4, 0x9c, 0 },
+ { 0xd2e5, 0xa0, 0 },
+ { 0xd2e6, 0x01, 0 },
+ { 0xd2e7, 0x00, 0 },
+ { 0xd2e8, 0xd4, 0 },
+ { 0xd2e9, 0x01, 0 },
+ { 0xd2ea, 0x28, 0 },
+ { 0xd2eb, 0x14, 0 },
+ { 0xd2ec, 0x84, 0 },
+ { 0xd2ed, 0xc1, 0 },
+ { 0xd2ee, 0x00, 0 },
+ { 0xd2ef, 0x18, 0 },
+ { 0xd2f0, 0xbd, 0 },
+ { 0xd2f1, 0x66, 0 },
+ { 0xd2f2, 0x00, 0 },
+ { 0xd2f3, 0x00, 0 },
+ { 0xd2f4, 0x0c, 0 },
+ { 0xd2f5, 0x00, 0 },
+ { 0xd2f6, 0x00, 0 },
+ { 0xd2f7, 0x20, 0 },
+ { 0xd2f8, 0x9d, 0 },
+ { 0xd2f9, 0x00, 0 },
+ { 0xd2fa, 0x00, 0 },
+ { 0xd2fb, 0x00, 0 },
+ { 0xd2fc, 0x84, 0 },
+ { 0xd2fd, 0x61, 0 },
+ { 0xd2fe, 0x00, 0 },
+ { 0xd2ff, 0x18, 0 },
+ { 0xd300, 0xbd, 0 },
+ { 0xd301, 0xa3, 0 },
+ { 0xd302, 0x01, 0 },
+ { 0xd303, 0x00, 0 },
+ { 0xd304, 0x10, 0 },
+ { 0xd305, 0x00, 0 },
+ { 0xd306, 0x00, 0 },
+ { 0xd307, 0x03, 0 },
+ { 0xd308, 0x9c, 0 },
+ { 0xd309, 0x80, 0 },
+ { 0xd30a, 0x01, 0 },
+ { 0xd30b, 0x00, 0 },
+ { 0xd30c, 0xd4, 0 },
+ { 0xd30d, 0x01, 0 },
+ { 0xd30e, 0x20, 0 },
+ { 0xd30f, 0x18, 0 },
+ { 0xd310, 0x18, 0 },
+ { 0xd311, 0x60, 0 },
+ { 0xd312, 0x80, 0 },
+ { 0xd313, 0x06, 0 },
+ { 0xd314, 0x85, 0 },
+ { 0xd315, 0x01, 0 },
+ { 0xd316, 0x00, 0 },
+ { 0xd317, 0x14, 0 },
+ { 0xd318, 0xa8, 0 },
+ { 0xd319, 0x83, 0 },
+ { 0xd31a, 0x38, 0 },
+ { 0xd31b, 0x29, 0 },
+ { 0xd31c, 0xa8, 0 },
+ { 0xd31d, 0xc3, 0 },
+ { 0xd31e, 0x40, 0 },
+ { 0xd31f, 0x08, 0 },
+ { 0xd320, 0x8c, 0 },
+ { 0xd321, 0x84, 0 },
+ { 0xd322, 0x00, 0 },
+ { 0xd323, 0x00, 0 },
+ { 0xd324, 0xa8, 0 },
+ { 0xd325, 0xa3, 0 },
+ { 0xd326, 0x38, 0 },
+ { 0xd327, 0x2a, 0 },
+ { 0xd328, 0xa8, 0 },
+ { 0xd329, 0xe3, 0 },
+ { 0xd32a, 0x40, 0 },
+ { 0xd32b, 0x09, 0 },
+ { 0xd32c, 0xe0, 0 },
+ { 0xd32d, 0x64, 0 },
+ { 0xd32e, 0x40, 0 },
+ { 0xd32f, 0x00, 0 },
+ { 0xd330, 0xd8, 0 },
+ { 0xd331, 0x06, 0 },
+ { 0xd332, 0x18, 0 },
+ { 0xd333, 0x00, 0 },
+ { 0xd334, 0x8c, 0 },
+ { 0xd335, 0x65, 0 },
+ { 0xd336, 0x00, 0 },
+ { 0xd337, 0x00, 0 },
+ { 0xd338, 0x84, 0 },
+ { 0xd339, 0x81, 0 },
+ { 0xd33a, 0x00, 0 },
+ { 0xd33b, 0x18, 0 },
+ { 0xd33c, 0xe3, 0 },
+ { 0xd33d, 0xe3, 0 },
+ { 0xd33e, 0x20, 0 },
+ { 0xd33f, 0x00, 0 },
+ { 0xd340, 0xd8, 0 },
+ { 0xd341, 0x07, 0 },
+ { 0xd342, 0xf8, 0 },
+ { 0xd343, 0x00, 0 },
+ { 0xd344, 0x03, 0 },
+ { 0xd345, 0xff, 0 },
+ { 0xd346, 0xff, 0 },
+ { 0xd347, 0x6f, 0 },
+ { 0xd348, 0x18, 0 },
+ { 0xd349, 0x60, 0 },
+ { 0xd34a, 0x00, 0 },
+ { 0xd34b, 0x01, 0 },
+ { 0xd34c, 0x0f, 0 },
+ { 0xd34d, 0xff, 0 },
+ { 0xd34e, 0xff, 0 },
+ { 0xd34f, 0x9d, 0 },
+ { 0xd350, 0x18, 0 },
+ { 0xd351, 0x60, 0 },
+ { 0xd352, 0x80, 0 },
+ { 0xd353, 0x06, 0 },
+ { 0xd354, 0x00, 0 },
+ { 0xd355, 0x00, 0 },
+ { 0xd356, 0x00, 0 },
+ { 0xd357, 0x11, 0 },
+ { 0xd358, 0xa8, 0 },
+ { 0xd359, 0x83, 0 },
+ { 0xd35a, 0x6e, 0 },
+ { 0xd35b, 0x43, 0 },
+ { 0xd35c, 0xe0, 0 },
+ { 0xd35d, 0x6c, 0 },
+ { 0xd35e, 0x28, 0 },
+ { 0xd35f, 0x02, 0 },
+ { 0xd360, 0xe0, 0 },
+ { 0xd361, 0x84, 0 },
+ { 0xd362, 0x28, 0 },
+ { 0xd363, 0x02, 0 },
+ { 0xd364, 0x07, 0 },
+ { 0xd365, 0xff, 0 },
+ { 0xd366, 0xf8, 0 },
+ { 0xd367, 0x30, 0 },
+ { 0xd368, 0xb8, 0 },
+ { 0xd369, 0x63, 0 },
+ { 0xd36a, 0x00, 0 },
+ { 0xd36b, 0x08, 0 },
+ { 0xd36c, 0x03, 0 },
+ { 0xd36d, 0xff, 0 },
+ { 0xd36e, 0xff, 0 },
+ { 0xd36f, 0xc0, 0 },
+ { 0xd370, 0x85, 0 },
+ { 0xd371, 0x4e, 0 },
+ { 0xd372, 0x00, 0 },
+ { 0xd373, 0x00, 0 },
+ { 0xd374, 0x03, 0 },
+ { 0xd375, 0xff, 0 },
+ { 0xd376, 0xff, 0 },
+ { 0xd377, 0xe7, 0 },
+ { 0xd378, 0xd4, 0 },
+ { 0xd379, 0x01, 0 },
+ { 0xd37a, 0x40, 0 },
+ { 0xd37b, 0x18, 0 },
+ { 0xd37c, 0x9c, 0 },
+ { 0xd37d, 0x60, 0 },
+ { 0xd37e, 0x00, 0 },
+ { 0xd37f, 0x00, 0 },
+ { 0xd380, 0x03, 0 },
+ { 0xd381, 0xff, 0 },
+ { 0xd382, 0xff, 0 },
+ { 0xd383, 0xdb, 0 },
+ { 0xd384, 0xd4, 0 },
+ { 0xd385, 0x01, 0 },
+ { 0xd386, 0x18, 0 },
+ { 0xd387, 0x14, 0 },
+ { 0xd388, 0x03, 0 },
+ { 0xd389, 0xff, 0 },
+ { 0xd38a, 0xff, 0 },
+ { 0xd38b, 0xce, 0 },
+ { 0xd38c, 0x9d, 0 },
+ { 0xd38d, 0x6b, 0 },
+ { 0xd38e, 0x00, 0 },
+ { 0xd38f, 0xff, 0 },
+ { 0xd390, 0x03, 0 },
+ { 0xd391, 0xff, 0 },
+ { 0xd392, 0xff, 0 },
+ { 0xd393, 0xc6, 0 },
+ { 0xd394, 0x9c, 0 },
+ { 0xd395, 0x63, 0 },
+ { 0xd396, 0x00, 0 },
+ { 0xd397, 0xff, 0 },
+ { 0xd398, 0xa8, 0 },
+ { 0xd399, 0xe3, 0 },
+ { 0xd39a, 0x38, 0 },
+ { 0xd39b, 0x0f, 0 },
+ { 0xd39c, 0x8c, 0 },
+ { 0xd39d, 0x84, 0 },
+ { 0xd39e, 0x00, 0 },
+ { 0xd39f, 0x00, 0 },
+ { 0xd3a0, 0xa8, 0 },
+ { 0xd3a1, 0xa3, 0 },
+ { 0xd3a2, 0x38, 0 },
+ { 0xd3a3, 0x0e, 0 },
+ { 0xd3a4, 0xa8, 0 },
+ { 0xd3a5, 0xc3, 0 },
+ { 0xd3a6, 0x6e, 0 },
+ { 0xd3a7, 0x42, 0 },
+ { 0xd3a8, 0xd8, 0 },
+ { 0xd3a9, 0x07, 0 },
+ { 0xd3aa, 0x20, 0 },
+ { 0xd3ab, 0x00, 0 },
+ { 0xd3ac, 0x8c, 0 },
+ { 0xd3ad, 0x66, 0 },
+ { 0xd3ae, 0x00, 0 },
+ { 0xd3af, 0x00, 0 },
+ { 0xd3b0, 0xd8, 0 },
+ { 0xd3b1, 0x05, 0 },
+ { 0xd3b2, 0x18, 0 },
+ { 0xd3b3, 0x00, 0 },
+ { 0xd3b4, 0x85, 0 },
+ { 0xd3b5, 0x21, 0 },
+ { 0xd3b6, 0x00, 0 },
+ { 0xd3b7, 0x00, 0 },
+ { 0xd3b8, 0x85, 0 },
+ { 0xd3b9, 0x41, 0 },
+ { 0xd3ba, 0x00, 0 },
+ { 0xd3bb, 0x04, 0 },
+ { 0xd3bc, 0x85, 0 },
+ { 0xd3bd, 0x81, 0 },
+ { 0xd3be, 0x00, 0 },
+ { 0xd3bf, 0x08, 0 },
+ { 0xd3c0, 0x85, 0 },
+ { 0xd3c1, 0xc1, 0 },
+ { 0xd3c2, 0x00, 0 },
+ { 0xd3c3, 0x0c, 0 },
+ { 0xd3c4, 0x86, 0 },
+ { 0xd3c5, 0x01, 0 },
+ { 0xd3c6, 0x00, 0 },
+ { 0xd3c7, 0x10, 0 },
+ { 0xd3c8, 0x44, 0 },
+ { 0xd3c9, 0x00, 0 },
+ { 0xd3ca, 0x48, 0 },
+ { 0xd3cb, 0x00, 0 },
+ { 0xd3cc, 0x9c, 0 },
+ { 0xd3cd, 0x21, 0 },
+ { 0xd3ce, 0x00, 0 },
+ { 0xd3cf, 0x1c, 0 },
+ { 0xd3d0, 0x9c, 0 },
+ { 0xd3d1, 0x21, 0 },
+ { 0xd3d2, 0xff, 0 },
+ { 0xd3d3, 0xfc, 0 },
+ { 0xd3d4, 0xd4, 0 },
+ { 0xd3d5, 0x01, 0 },
+ { 0xd3d6, 0x48, 0 },
+ { 0xd3d7, 0x00, 0 },
+ { 0xd3d8, 0x18, 0 },
+ { 0xd3d9, 0x60, 0 },
+ { 0xd3da, 0x00, 0 },
+ { 0xd3db, 0x01, 0 },
+ { 0xd3dc, 0xa8, 0 },
+ { 0xd3dd, 0x63, 0 },
+ { 0xd3de, 0x07, 0 },
+ { 0xd3df, 0x80, 0 },
+ { 0xd3e0, 0x8c, 0 },
+ { 0xd3e1, 0x63, 0 },
+ { 0xd3e2, 0x00, 0 },
+ { 0xd3e3, 0x68, 0 },
+ { 0xd3e4, 0xbc, 0 },
+ { 0xd3e5, 0x03, 0 },
+ { 0xd3e6, 0x00, 0 },
+ { 0xd3e7, 0x00, 0 },
+ { 0xd3e8, 0x10, 0 },
+ { 0xd3e9, 0x00, 0 },
+ { 0xd3ea, 0x00, 0 },
+ { 0xd3eb, 0x0c, 0 },
+ { 0xd3ec, 0x15, 0 },
+ { 0xd3ed, 0x00, 0 },
+ { 0xd3ee, 0x00, 0 },
+ { 0xd3ef, 0x00, 0 },
+ { 0xd3f0, 0x07, 0 },
+ { 0xd3f1, 0xff, 0 },
+ { 0xd3f2, 0xd9, 0 },
+ { 0xd3f3, 0x98, 0 },
+ { 0xd3f4, 0x15, 0 },
+ { 0xd3f5, 0x00, 0 },
+ { 0xd3f6, 0x00, 0 },
+ { 0xd3f7, 0x00, 0 },
+ { 0xd3f8, 0x18, 0 },
+ { 0xd3f9, 0x60, 0 },
+ { 0xd3fa, 0x80, 0 },
+ { 0xd3fb, 0x06, 0 },
+ { 0xd3fc, 0xa8, 0 },
+ { 0xd3fd, 0x63, 0 },
+ { 0xd3fe, 0xc4, 0 },
+ { 0xd3ff, 0xb8, 0 },
+ { 0xd400, 0x8c, 0 },
+ { 0xd401, 0x63, 0 },
+ { 0xd402, 0x00, 0 },
+ { 0xd403, 0x00, 0 },
+ { 0xd404, 0xbc, 0 },
+ { 0xd405, 0x23, 0 },
+ { 0xd406, 0x00, 0 },
+ { 0xd407, 0x01, 0 },
+ { 0xd408, 0x10, 0 },
+ { 0xd409, 0x00, 0 },
+ { 0xd40a, 0x00, 0 },
+ { 0xd40b, 0x25, 0 },
+ { 0xd40c, 0x9d, 0 },
+ { 0xd40d, 0x00, 0 },
+ { 0xd40e, 0x00, 0 },
+ { 0xd40f, 0x00, 0 },
+ { 0xd410, 0x00, 0 },
+ { 0xd411, 0x00, 0 },
+ { 0xd412, 0x00, 0 },
+ { 0xd413, 0x0b, 0 },
+ { 0xd414, 0xb8, 0 },
+ { 0xd415, 0xe8, 0 },
+ { 0xd416, 0x00, 0 },
+ { 0xd417, 0x02, 0 },
+ { 0xd418, 0x07, 0 },
+ { 0xd419, 0xff, 0 },
+ { 0xd41a, 0xd6, 0 },
+ { 0xd41b, 0x24, 0 },
+ { 0xd41c, 0x15, 0 },
+ { 0xd41d, 0x00, 0 },
+ { 0xd41e, 0x00, 0 },
+ { 0xd41f, 0x00, 0 },
+ { 0xd420, 0x18, 0 },
+ { 0xd421, 0x60, 0 },
+ { 0xd422, 0x80, 0 },
+ { 0xd423, 0x06, 0 },
+ { 0xd424, 0xa8, 0 },
+ { 0xd425, 0x63, 0 },
+ { 0xd426, 0xc4, 0 },
+ { 0xd427, 0xb8, 0 },
+ { 0xd428, 0x8c, 0 },
+ { 0xd429, 0x63, 0 },
+ { 0xd42a, 0x00, 0 },
+ { 0xd42b, 0x00, 0 },
+ { 0xd42c, 0xbc, 0 },
+ { 0xd42d, 0x23, 0 },
+ { 0xd42e, 0x00, 0 },
+ { 0xd42f, 0x01, 0 },
+ { 0xd430, 0x10, 0 },
+ { 0xd431, 0x00, 0 },
+ { 0xd432, 0x00, 0 },
+ { 0xd433, 0x1b, 0 },
+ { 0xd434, 0x9d, 0 },
+ { 0xd435, 0x00, 0 },
+ { 0xd436, 0x00, 0 },
+ { 0xd437, 0x00, 0 },
+ { 0xd438, 0xb8, 0 },
+ { 0xd439, 0xe8, 0 },
+ { 0xd43a, 0x00, 0 },
+ { 0xd43b, 0x02, 0 },
+ { 0xd43c, 0x9c, 0 },
+ { 0xd43d, 0xc0, 0 },
+ { 0xd43e, 0x00, 0 },
+ { 0xd43f, 0x00, 0 },
+ { 0xd440, 0x18, 0 },
+ { 0xd441, 0xa0, 0 },
+ { 0xd442, 0x80, 0 },
+ { 0xd443, 0x06, 0 },
+ { 0xd444, 0xe0, 0 },
+ { 0xd445, 0x67, 0 },
+ { 0xd446, 0x30, 0 },
+ { 0xd447, 0x00, 0 },
+ { 0xd448, 0xa8, 0 },
+ { 0xd449, 0xa5, 0 },
+ { 0xd44a, 0xce, 0 },
+ { 0xd44b, 0xb0, 0 },
+ { 0xd44c, 0x19, 0 },
+ { 0xd44d, 0x60, 0 },
+ { 0xd44e, 0x00, 0 },
+ { 0xd44f, 0x01, 0 },
+ { 0xd450, 0xa9, 0 },
+ { 0xd451, 0x6b, 0 },
+ { 0xd452, 0x06, 0 },
+ { 0xd453, 0x14, 0 },
+ { 0xd454, 0xe0, 0 },
+ { 0xd455, 0x83, 0 },
+ { 0xd456, 0x28, 0 },
+ { 0xd457, 0x00, 0 },
+ { 0xd458, 0x9c, 0 },
+ { 0xd459, 0xc6, 0 },
+ { 0xd45a, 0x00, 0 },
+ { 0xd45b, 0x01, 0 },
+ { 0xd45c, 0xe0, 0 },
+ { 0xd45d, 0x63, 0 },
+ { 0xd45e, 0x18, 0 },
+ { 0xd45f, 0x00, 0 },
+ { 0xd460, 0x8c, 0 },
+ { 0xd461, 0x84, 0 },
+ { 0xd462, 0x00, 0 },
+ { 0xd463, 0x00, 0 },
+ { 0xd464, 0xe0, 0 },
+ { 0xd465, 0xa3, 0 },
+ { 0xd466, 0x58, 0 },
+ { 0xd467, 0x00, 0 },
+ { 0xd468, 0xa4, 0 },
+ { 0xd469, 0xc6, 0 },
+ { 0xd46a, 0x00, 0 },
+ { 0xd46b, 0xff, 0 },
+ { 0xd46c, 0xb8, 0 },
+ { 0xd46d, 0x64, 0 },
+ { 0xd46e, 0x00, 0 },
+ { 0xd46f, 0x18, 0 },
+ { 0xd470, 0xbc, 0 },
+ { 0xd471, 0x46, 0 },
+ { 0xd472, 0x00, 0 },
+ { 0xd473, 0x03, 0 },
+ { 0xd474, 0x94, 0 },
+ { 0xd475, 0x85, 0 },
+ { 0xd476, 0x00, 0 },
+ { 0xd477, 0x00, 0 },
+ { 0xd478, 0xb8, 0 },
+ { 0xd479, 0x63, 0 },
+ { 0xd47a, 0x00, 0 },
+ { 0xd47b, 0x98, 0 },
+ { 0xd47c, 0xe0, 0 },
+ { 0xd47d, 0x64, 0 },
+ { 0xd47e, 0x18, 0 },
+ { 0xd47f, 0x00, 0 },
+ { 0xd480, 0x0f, 0 },
+ { 0xd481, 0xff, 0 },
+ { 0xd482, 0xff, 0 },
+ { 0xd483, 0xf0, 0 },
+ { 0xd484, 0xdc, 0 },
+ { 0xd485, 0x05, 0 },
+ { 0xd486, 0x18, 0 },
+ { 0xd487, 0x00, 0 },
+ { 0xd488, 0x9c, 0 },
+ { 0xd489, 0x68, 0 },
+ { 0xd48a, 0x00, 0 },
+ { 0xd48b, 0x01, 0 },
+ { 0xd48c, 0xa5, 0 },
+ { 0xd48d, 0x03, 0 },
+ { 0xd48e, 0x00, 0 },
+ { 0xd48f, 0xff, 0 },
+ { 0xd490, 0xbc, 0 },
+ { 0xd491, 0x48, 0 },
+ { 0xd492, 0x00, 0 },
+ { 0xd493, 0x01, 0 },
+ { 0xd494, 0x0f, 0 },
+ { 0xd495, 0xff, 0 },
+ { 0xd496, 0xff, 0 },
+ { 0xd497, 0xea, 0 },
+ { 0xd498, 0xb8, 0 },
+ { 0xd499, 0xe8, 0 },
+ { 0xd49a, 0x00, 0 },
+ { 0xd49b, 0x02, 0 },
+ { 0xd49c, 0x18, 0 },
+ { 0xd49d, 0x60, 0 },
+ { 0xd49e, 0x00, 0 },
+ { 0xd49f, 0x01, 0 },
+ { 0xd4a0, 0xa8, 0 },
+ { 0xd4a1, 0x63, 0 },
+ { 0xd4a2, 0x06, 0 },
+ { 0xd4a3, 0x14, 0 },
+ { 0xd4a4, 0x07, 0 },
+ { 0xd4a5, 0xff, 0 },
+ { 0xd4a6, 0xe4, 0 },
+ { 0xd4a7, 0x05, 0 },
+ { 0xd4a8, 0x9c, 0 },
+ { 0xd4a9, 0x83, 0 },
+ { 0xd4aa, 0x00, 0 },
+ { 0xd4ab, 0x10, 0 },
+ { 0xd4ac, 0x85, 0 },
+ { 0xd4ad, 0x21, 0 },
+ { 0xd4ae, 0x00, 0 },
+ { 0xd4af, 0x00, 0 },
+ { 0xd4b0, 0x44, 0 },
+ { 0xd4b1, 0x00, 0 },
+ { 0xd4b2, 0x48, 0 },
+ { 0xd4b3, 0x00, 0 },
+ { 0xd4b4, 0x9c, 0 },
+ { 0xd4b5, 0x21, 0 },
+ { 0xd4b6, 0x00, 0 },
+ { 0xd4b7, 0x04, 0 },
+ { 0xd4b8, 0x18, 0 },
+ { 0xd4b9, 0x60, 0 },
+ { 0xd4ba, 0x00, 0 },
+ { 0xd4bb, 0x01, 0 },
+ { 0xd4bc, 0x9c, 0 },
+ { 0xd4bd, 0x80, 0 },
+ { 0xd4be, 0xff, 0 },
+ { 0xd4bf, 0xff, 0 },
+ { 0xd4c0, 0xa8, 0 },
+ { 0xd4c1, 0x63, 0 },
+ { 0xd4c2, 0x09, 0 },
+ { 0xd4c3, 0xef, 0 },
+ { 0xd4c4, 0xd8, 0 },
+ { 0xd4c5, 0x03, 0 },
+ { 0xd4c6, 0x20, 0 },
+ { 0xd4c7, 0x00, 0 },
+ { 0xd4c8, 0x18, 0 },
+ { 0xd4c9, 0x60, 0 },
+ { 0xd4ca, 0x80, 0 },
+ { 0xd4cb, 0x06, 0 },
+ { 0xd4cc, 0xa8, 0 },
+ { 0xd4cd, 0x63, 0 },
+ { 0xd4ce, 0xc9, 0 },
+ { 0xd4cf, 0xef, 0 },
+ { 0xd4d0, 0xd8, 0 },
+ { 0xd4d1, 0x03, 0 },
+ { 0xd4d2, 0x20, 0 },
+ { 0xd4d3, 0x00, 0 },
+ { 0xd4d4, 0x44, 0 },
+ { 0xd4d5, 0x00, 0 },
+ { 0xd4d6, 0x48, 0 },
+ { 0xd4d7, 0x00, 0 },
+ { 0xd4d8, 0x15, 0 },
+ { 0xd4d9, 0x00, 0 },
+ { 0xd4da, 0x00, 0 },
+ { 0xd4db, 0x00, 0 },
+ { 0xd4dc, 0x18, 0 },
+ { 0xd4dd, 0x80, 0 },
+ { 0xd4de, 0x00, 0 },
+ { 0xd4df, 0x01, 0 },
+ { 0xd4e0, 0xa8, 0 },
+ { 0xd4e1, 0x84, 0 },
+ { 0xd4e2, 0x0a, 0 },
+ { 0xd4e3, 0x12, 0 },
+ { 0xd4e4, 0x8c, 0 },
+ { 0xd4e5, 0x64, 0 },
+ { 0xd4e6, 0x00, 0 },
+ { 0xd4e7, 0x00, 0 },
+ { 0xd4e8, 0xbc, 0 },
+ { 0xd4e9, 0x03, 0 },
+ { 0xd4ea, 0x00, 0 },
+ { 0xd4eb, 0x00, 0 },
+ { 0xd4ec, 0x13, 0 },
+ { 0xd4ed, 0xff, 0 },
+ { 0xd4ee, 0xff, 0 },
+ { 0xd4ef, 0xfe, 0 },
+ { 0xd4f0, 0x15, 0 },
+ { 0xd4f1, 0x00, 0 },
+ { 0xd4f2, 0x00, 0 },
+ { 0xd4f3, 0x00, 0 },
+ { 0xd4f4, 0x44, 0 },
+ { 0xd4f5, 0x00, 0 },
+ { 0xd4f6, 0x48, 0 },
+ { 0xd4f7, 0x00, 0 },
+ { 0xd4f8, 0x15, 0 },
+ { 0xd4f9, 0x00, 0 },
+ { 0xd4fa, 0x00, 0 },
+ { 0xd4fb, 0x00, 0 },
+ { 0xd4fc, 0x00, 0 },
+ { 0xd4fd, 0x00, 0 },
+ { 0xd4fe, 0x00, 0 },
+ { 0xd4ff, 0x00, 0 },
+ { 0xd500, 0x00, 0 },
+ { 0xd501, 0x00, 0 },
+ { 0xd502, 0x00, 0 },
+ { 0xd503, 0x00, 0 },
+ { 0x6f0e, 0x33, 0 },
+ { 0x6f0f, 0x33, 0 },
+ { 0x460e, 0x08, 0 },
+ { 0x460f, 0x01, 0 },
+ { 0x4610, 0x00, 0 },
+ { 0x4611, 0x01, 0 },
+ { 0x4612, 0x00, 0 },
+ { 0x4613, 0x01, 0 },
+ { 0x4605, 0x08, 0 },
+ { 0x4608, 0x00, 0 },
+ { 0x4609, 0x08, 0 },
+ { 0x6804, 0x00, 0 },
+ { 0x6805, 0x06, 0 },
+ { 0x6806, 0x00, 0 },
+ { 0x5120, 0x00, 0 },
+ { 0x3510, 0x00, 0 },
+ { 0x3504, 0x00, 0 },
+ { 0x6800, 0x00, 0 },
+ { 0x6f0d, 0x0f, 0 },
+ { 0x5000, 0xff, 0 },
+ { 0x5001, 0xbf, 0 },
+ { 0x5002, 0x7e, 0 },
+ { 0x5003, 0x0c, 0 },
+ { 0x503d, 0x00, 0 },
+ { 0xc450, 0x01, 0 },
+ { 0xc452, 0x04, 0 },
+ { 0xc453, 0x00, 0 },
+ { 0xc454, 0x00, 0 },
+ { 0xc455, 0x00, 0 },
+ { 0xc456, 0x00, 0 },
+ { 0xc457, 0x00, 0 },
+ { 0xc458, 0x00, 0 },
+ { 0xc459, 0x00, 0 },
+ { 0xc45b, 0x00, 0 },
+ { 0xc45c, 0x00, 0 },
+ { 0xc45d, 0x00, 0 },
+ { 0xc45e, 0x00, 0 },
+ { 0xc45f, 0x00, 0 },
+ { 0xc460, 0x00, 0 },
+ { 0xc461, 0x01, 0 },
+ { 0xc462, 0x01, 0 },
+ { 0xc464, 0x88, 0 },
+ { 0xc465, 0x00, 0 },
+ { 0xc466, 0x8a, 0 },
+ { 0xc467, 0x00, 0 },
+ { 0xc468, 0x86, 0 },
+ { 0xc469, 0x00, 0 },
+ { 0xc46a, 0x40, 0 },
+ { 0xc46b, 0x50, 0 },
+ { 0xc46c, 0x30, 0 },
+ { 0xc46d, 0x28, 0 },
+ { 0xc46e, 0x60, 0 },
+ { 0xc46f, 0x40, 0 },
+ { 0xc47c, 0x01, 0 },
+ { 0xc47d, 0x38, 0 },
+ { 0xc47e, 0x00, 0 },
+ { 0xc47f, 0x00, 0 },
+ { 0xc480, 0x00, 0 },
+ { 0xc481, 0xff, 0 },
+ { 0xc482, 0x00, 0 },
+ { 0xc483, 0x40, 0 },
+ { 0xc484, 0x00, 0 },
+ { 0xc485, 0x18, 0 },
+ { 0xc486, 0x00, 0 },
+ { 0xc487, 0x18, 0 },
+ { 0xc488, 0x34, 0 },
+ { 0xc489, 0x00, 0 },
+ { 0xc48a, 0x34, 0 },
+ { 0xc48b, 0x00, 0 },
+ { 0xc48c, 0x00, 0 },
+ { 0xc48d, 0x04, 0 },
+ { 0xc48e, 0x00, 0 },
+ { 0xc48f, 0x04, 0 },
+ { 0xc490, 0x07, 0 },
+ { 0xc492, 0x20, 0 },
+ { 0xc493, 0x08, 0 },
+ { 0xc498, 0x02, 0 },
+ { 0xc499, 0x00, 0 },
+ { 0xc49a, 0x02, 0 },
+ { 0xc49b, 0x00, 0 },
+ { 0xc49c, 0x02, 0 },
+ { 0xc49d, 0x00, 0 },
+ { 0xc49e, 0x02, 0 },
+ { 0xc49f, 0x60, 0 },
+ { 0xc4a0, 0x03, 0 },
+ { 0xc4a1, 0x00, 0 },
+ { 0xc4a2, 0x04, 0 },
+ { 0xc4a3, 0x00, 0 },
+ { 0xc4a4, 0x00, 0 },
+ { 0xc4a5, 0x10, 0 },
+ { 0xc4a6, 0x00, 0 },
+ { 0xc4a7, 0x40, 0 },
+ { 0xc4a8, 0x00, 0 },
+ { 0xc4a9, 0x80, 0 },
+ { 0xc4aa, 0x0d, 0 },
+ { 0xc4ab, 0x00, 0 },
+ { 0xc4ac, 0x0f, 0 },
+ { 0xc4ad, 0xc0, 0 },
+ { 0xc4b4, 0x01, 0 },
+ { 0xc4b5, 0x01, 0 },
+ { 0xc4b6, 0x00, 0 },
+ { 0xc4b7, 0x01, 0 },
+ { 0xc4b8, 0x00, 0 },
+ { 0xc4b9, 0x01, 0 },
+ { 0xc4ba, 0x01, 0 },
+ { 0xc4bb, 0x00, 0 },
+ { 0xc4bc, 0x01, 0 },
+ { 0xc4bd, 0x60, 0 },
+ { 0xc4be, 0x02, 0 },
+ { 0xc4bf, 0x33, 0 },
+ { 0xc4c8, 0x03, 0 },
+ { 0xc4c9, 0xd0, 0 },
+ { 0xc4ca, 0x0e, 0 },
+ { 0xc4cb, 0x00, 0 },
+ { 0xc4cc, 0x10, 0 },
+ { 0xc4cd, 0x18, 0 },
+ { 0xc4ce, 0x10, 0 },
+ { 0xc4cf, 0x18, 0 },
+ { 0xc4d0, 0x04, 0 },
+ { 0xc4d1, 0x80, 0 },
+ { 0xc4e0, 0x04, 0 },
+ { 0xc4e1, 0x02, 0 },
+ { 0xc4e2, 0x01, 0 },
+ { 0xc4e4, 0x10, 0 },
+ { 0xc4e5, 0x20, 0 },
+ { 0xc4e6, 0x30, 0 },
+ { 0xc4e7, 0x40, 0 },
+ { 0xc4e8, 0x50, 0 },
+ { 0xc4e9, 0x60, 0 },
+ { 0xc4ea, 0x70, 0 },
+ { 0xc4eb, 0x80, 0 },
+ { 0xc4ec, 0x90, 0 },
+ { 0xc4ed, 0xa0, 0 },
+ { 0xc4ee, 0xb0, 0 },
+ { 0xc4ef, 0xc0, 0 },
+ { 0xc4f0, 0xd0, 0 },
+ { 0xc4f1, 0xe0, 0 },
+ { 0xc4f2, 0xf0, 0 },
+ { 0xc4f3, 0x80, 0 },
+ { 0xc4f4, 0x00, 0 },
+ { 0xc4f5, 0x20, 0 },
+ { 0xc4f6, 0x02, 0 },
+ { 0xc4f7, 0x00, 0 },
+ { 0xc4f8, 0x04, 0 },
+ { 0xc4f9, 0x0b, 0 },
+ { 0xc4fa, 0x00, 0 },
+ { 0xc4fb, 0x00, 0 },
+ { 0xc4fc, 0x01, 0 },
+ { 0xc4fd, 0x00, 0 },
+ { 0xc4fe, 0x04, 0 },
+ { 0xc4ff, 0x02, 0 },
+ { 0xc500, 0x48, 0 },
+ { 0xc501, 0x74, 0 },
+ { 0xc502, 0x58, 0 },
+ { 0xc503, 0x80, 0 },
+ { 0xc504, 0x05, 0 },
+ { 0xc505, 0x80, 0 },
+ { 0xc506, 0x03, 0 },
+ { 0xc507, 0x80, 0 },
+ { 0xc508, 0x01, 0 },
+ { 0xc509, 0xc0, 0 },
+ { 0xc50a, 0x01, 0 },
+ { 0xc50b, 0xa0, 0 },
+ { 0xc50c, 0x01, 0 },
+ { 0xc50d, 0x2c, 0 },
+ { 0xc50e, 0x01, 0 },
+ { 0xc50f, 0x0a, 0 },
+ { 0xc510, 0x00, 0 },
+ { 0xc511, 0x01, 0 },
+ { 0xc512, 0x01, 0 },
+ { 0xc513, 0x80, 0 },
+ { 0xc514, 0x04, 0 },
+ { 0xc515, 0x00, 0 },
+ { 0xc518, 0x03, 0 },
+ { 0xc519, 0x48, 0 },
+ { 0xc51a, 0x07, 0 },
+ { 0xc51b, 0x70, 0 },
+ { 0xc2e0, 0x00, 0 },
+ { 0xc2e1, 0x51, 0 },
+ { 0xc2e2, 0x00, 0 },
+ { 0xc2e3, 0xd6, 0 },
+ { 0xc2e4, 0x01, 0 },
+ { 0xc2e5, 0x5e, 0 },
+ { 0xc2e9, 0x01, 0 },
+ { 0xc2ea, 0x7a, 0 },
+ { 0xc2eb, 0x90, 0 },
+ { 0xc2ed, 0x00, 0 },
+ { 0xc2ee, 0x7a, 0 },
+ { 0xc2ef, 0x64, 0 },
+ { 0xc308, 0x00, 0 },
+ { 0xc309, 0x00, 0 },
+ { 0xc30a, 0x00, 0 },
+ { 0xc30c, 0x00, 0 },
+ { 0xc30d, 0x01, 0 },
+ { 0xc30e, 0x00, 0 },
+ { 0xc30f, 0x00, 0 },
+ { 0xc310, 0x01, 0 },
+ { 0xc311, 0x60, 0 },
+ { 0xc312, 0xff, 0 },
+ { 0xc313, 0x08, 0 },
+ { 0xc314, 0x01, 0 },
+ { 0xc315, 0x7f, 0 },
+ { 0xc316, 0xff, 0 },
+ { 0xc317, 0x0b, 0 },
+ { 0xc318, 0x00, 0 },
+ { 0xc319, 0x0c, 0 },
+ { 0xc31a, 0x00, 0 },
+ { 0xc31b, 0xe0, 0 },
+ { 0xc31c, 0x00, 0 },
+ { 0xc31d, 0x14, 0 },
+ { 0xc31e, 0x00, 0 },
+ { 0xc31f, 0xc5, 0 },
+ { 0xc320, 0xff, 0 },
+ { 0xc321, 0x4b, 0 },
+ { 0xc322, 0xff, 0 },
+ { 0xc323, 0xf0, 0 },
+ { 0xc324, 0xff, 0 },
+ { 0xc325, 0xe8, 0 },
+ { 0xc326, 0x00, 0 },
+ { 0xc327, 0x46, 0 },
+ { 0xc328, 0xff, 0 },
+ { 0xc329, 0xd2, 0 },
+ { 0xc32a, 0xff, 0 },
+ { 0xc32b, 0xe4, 0 },
+ { 0xc32c, 0xff, 0 },
+ { 0xc32d, 0xbb, 0 },
+ { 0xc32e, 0x00, 0 },
+ { 0xc32f, 0x61, 0 },
+ { 0xc330, 0xff, 0 },
+ { 0xc331, 0xf9, 0 },
+ { 0xc332, 0x00, 0 },
+ { 0xc333, 0xd9, 0 },
+ { 0xc334, 0x00, 0 },
+ { 0xc335, 0x2e, 0 },
+ { 0xc336, 0x00, 0 },
+ { 0xc337, 0xb1, 0 },
+ { 0xc338, 0xff, 0 },
+ { 0xc339, 0x64, 0 },
+ { 0xc33a, 0xff, 0 },
+ { 0xc33b, 0xeb, 0 },
+ { 0xc33c, 0xff, 0 },
+ { 0xc33d, 0xe8, 0 },
+ { 0xc33e, 0x00, 0 },
+ { 0xc33f, 0x48, 0 },
+ { 0xc340, 0xff, 0 },
+ { 0xc341, 0xd0, 0 },
+ { 0xc342, 0xff, 0 },
+ { 0xc343, 0xed, 0 },
+ { 0xc344, 0xff, 0 },
+ { 0xc345, 0xad, 0 },
+ { 0xc346, 0x00, 0 },
+ { 0xc347, 0x66, 0 },
+ { 0xc348, 0x01, 0 },
+ { 0xc349, 0x00, 0 },
+ { 0x6700, 0x04, 0 },
+ { 0x6701, 0x7b, 0 },
+ { 0x6702, 0xfd, 0 },
+ { 0x6703, 0xf9, 0 },
+ { 0x6704, 0x3d, 0 },
+ { 0x6705, 0x71, 0 },
+ { 0x6706, 0x78, 0 },
+ { 0x6708, 0x05, 0 },
+ { 0x6f06, 0x6f, 0 },
+ { 0x6f07, 0x00, 0 },
+ { 0x6f0a, 0x6f, 0 },
+ { 0x6f0b, 0x00, 0 },
+ { 0x6f00, 0x03, 0 },
+ { 0xc34c, 0x01, 0 },
+ { 0xc34d, 0x00, 0 },
+ { 0xc34e, 0x46, 0 },
+ { 0xc34f, 0x55, 0 },
+ { 0xc350, 0x00, 0 },
+ { 0xc351, 0x40, 0 },
+ { 0xc352, 0x00, 0 },
+ { 0xc353, 0xff, 0 },
+ { 0xc354, 0x04, 0 },
+ { 0xc355, 0x08, 0 },
+ { 0xc356, 0x01, 0 },
+ { 0xc357, 0xef, 0 },
+ { 0xc358, 0x30, 0 },
+ { 0xc359, 0x01, 0 },
+ { 0xc35a, 0x64, 0 },
+ { 0xc35b, 0x46, 0 },
+ { 0xc35c, 0x00, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x3042, 0xf0, 0 },
+ { 0x301b, 0xf0, 0 },
+ { 0x301c, 0xf0, 0 },
+ { 0x301a, 0xf0, 0 },
+ { 0xceb0, 0x00, 0 },
+ { 0xceb1, 0x00, 0 },
+ { 0xceb2, 0x00, 0 },
+ { 0xceb3, 0x00, 0 },
+ { 0xceb4, 0x00, 0 },
+ { 0xceb5, 0x00, 0 },
+ { 0xceb6, 0x00, 0 },
+ { 0xceb7, 0x00, 0 },
+ { 0xc4bc, 0x01, 0 },
+ { 0xc4bd, 0x60, 0 },
+
+ { 0x4709, 0x10, 0 },/* dvp swap */
+ { 0x4300, 0x3a, 0 },/* YUV order UYVY */
+ { 0x3832, 0x01, 0 },/* fsin */
+ { 0x3833, 0x1A, 0 },
+ { 0x3834, 0x03, 0 },
+ { 0x3835, 0x48, 0 },
+ { 0x302E, 0x01, 0 },
+};
+
+struct ov10635_mode_info {
+ enum ov10635_mode mode;
+ u32 width;
+ u32 height;
+ struct reg_value *init_data_ptr;
+ u32 init_data_size;
+};
+
+static struct reg_value ov10635_setting_30fps_WXGA_1280_800[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 1280x800 */
+ { 0x3808, 0x05, 0 },
+ { 0x3809, 0x00, 0 },
+ { 0x380a, 0x03, 0 },
+ { 0x380b, 0x20, 0 },
+};
+
+static struct reg_value ov10635_setting_30fps_720P_1280_720[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 1280x720 */
+ { 0x3808, 0x05, 0 },
+ { 0x3809, 0x00, 0 },
+ { 0x380a, 0x02, 0 },
+ { 0x380b, 0xD0, 0 },
+};
+
+static struct reg_value ov10635_setting_30fps_WVGA_752_480[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 752x480 */
+ { 0x3808, 0x02, 0 },
+ { 0x3809, 0xF0, 0 },
+ { 0x380a, 0x01, 0 },
+ { 0x380b, 0xE0, 0 },
+};
+
+static struct reg_value ov10635_setting_30fps_VGA_640_480[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 640x480 */
+ { 0x3808, 0x02, 0 },
+ { 0x3809, 0x80, 0 },
+ { 0x380a, 0x01, 0 },
+ { 0x380b, 0xE0, 0 },
+};
+
+static struct reg_value ov10635_setting_30fps_CIF_352_288[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 352x288 */
+ { 0x3808, 0x01, 0 },
+ { 0x3809, 0x60, 0 },
+ { 0x380a, 0x01, 0 },
+ { 0x380b, 0x20, 0 },
+};
+
+static struct reg_value ov10635_setting_30fps_QVGA_320_240[] = {
+ { 0x3024, 0x01, 0 },
+ { 0x3003, 0x20, 0 },
+ { 0x3004, 0x21, 0 },
+ { 0x3005, 0x20, 0 },
+ { 0x3006, 0x91, 0 },
+ /* 320x240 */
+ { 0x3808, 0x01, 0 },
+ { 0x3809, 0x40, 0 },
+ { 0x380a, 0x00, 0 },
+ { 0x380b, 0xF0, 0 },
+};
+
+static struct ov10635_mode_info ov10635_mode_info_data[2][ov10635_mode_MAX + 1] = {
+ /* 15fps not support */
+ {
+ { ov10635_mode_WXGA_1280_800, 0, 0, NULL, 0 },
+ { ov10635_mode_720P_1280_720, 0, 0, NULL, 0 },
+ { ov10635_mode_WVGA_752_480, 0, 0, NULL, 0 },
+ { ov10635_mode_VGA_640_480, 0, 0, NULL, 0 },
+ { ov10635_mode_CIF_352_288, 0, 0, NULL, 0},
+ { ov10635_mode_QVGA_320_240, 0, 0, NULL, 0},
+ },
+ /* 30fps */
+ {
+ { ov10635_mode_WXGA_1280_800, 1280, 800,
+ ov10635_setting_30fps_WXGA_1280_800,
+ ARRAY_SIZE(ov10635_setting_30fps_WXGA_1280_800)
+ },
+ { ov10635_mode_720P_1280_720, 1280, 720,
+ ov10635_setting_30fps_720P_1280_720,
+ ARRAY_SIZE(ov10635_setting_30fps_720P_1280_720)
+ },
+ { ov10635_mode_WVGA_752_480, 752, 480,
+ ov10635_setting_30fps_WVGA_752_480,
+ ARRAY_SIZE(ov10635_setting_30fps_WVGA_752_480)
+ },
+ { ov10635_mode_VGA_640_480, 640, 480,
+ ov10635_setting_30fps_VGA_640_480,
+ ARRAY_SIZE(ov10635_setting_30fps_VGA_640_480)
+ },
+ { ov10635_mode_CIF_352_288, 352, 288,
+ ov10635_setting_30fps_CIF_352_288,
+ ARRAY_SIZE(ov10635_setting_30fps_CIF_352_288)
+ },
+ { ov10635_mode_QVGA_320_240, 320, 240,
+ ov10635_setting_30fps_QVGA_320_240,
+ ARRAY_SIZE(ov10635_setting_30fps_QVGA_320_240)
+ },
+ }
+};
+
+static inline struct sensor_data *subdev_to_sensor_data(struct v4l2_subdev *sd)
+{
+ return container_of(sd, struct sensor_data, subdev);
+}
+
+static enum ov10635_frame_rate to_ov10635_frame_rate(struct v4l2_fract *timeperframe)
+{
+ enum ov10635_frame_rate rate;
+ u32 tgt_fps; /* target frames per secound */
+
+ tgt_fps = timeperframe->denominator / timeperframe->numerator;
+
+ if (tgt_fps == 30)
+ rate = OV10635_30_FPS;
+ else if (tgt_fps == 15)
+ rate = OV10635_15_FPS;
+ else
+ rate = -EINVAL;
+
+ return rate;
+}
+
+static inline int ov10635_read_reg(struct sensor_data *max9286_data, int index,
+ unsigned short reg, unsigned char *val)
+{
+ struct i2c_client *client = max9286_data->i2c_client;
+ struct device *dev = &client->dev;
+ unsigned char u8_buf[2] = { 0 };
+ unsigned int buf_len = 2;
+ int retry, timeout = 10;
+ unsigned char u8_val = 0;
+
+ u8_buf[0] = (reg >> 8) & 0xFF;
+ u8_buf[1] = reg & 0xFF;
+
+ client->addr = ADDR_OV_SENSOR + index;
+
+ for (retry = 0; retry < timeout; retry++) {
+ if (i2c_master_send(client, u8_buf, buf_len) < 0) {
+ dev_dbg(dev, "%s:read reg error on send: reg=0x%x, retry = %d.\n", __func__, reg, retry);
+ msleep(5);
+ continue;
+ }
+ if (i2c_master_recv(client, &u8_val, 1) != 1) {
+ dev_dbg(dev, "%s:read reg error on recv: reg=0x%x, retry = %d.\n", __func__, reg, retry);
+ msleep(5);
+ continue;
+ }
+ break;
+ }
+
+ if (retry >= timeout) {
+ dev_info(dev, "%s:read reg error: reg=0x%x.\n", __func__, reg);
+ return -1;
+ }
+
+ *val = u8_val;
+
+ return u8_val;
+}
+
+static inline int ov10635_write_reg(struct sensor_data *max9286_data, int index,
+ unsigned short reg, unsigned char val)
+{
+ struct i2c_client *client = max9286_data->i2c_client;
+ struct device *dev = &client->dev;
+ unsigned char u8_buf[3] = { 0 };
+ unsigned int buf_len = 3;
+ int retry, timeout = 10;
+
+ u8_buf[0] = (reg >> 8) & 0xFF;
+ u8_buf[1] = reg & 0xFF;
+ u8_buf[2] = val;
+
+ client->addr = ADDR_OV_SENSOR + index;
+ for (retry = 0; retry < timeout; retry++) {
+ if (i2c_master_send(client, u8_buf, buf_len) < 0) {
+ dev_dbg(dev, "%s:write reg error: reg=0x%x, val=0x%x, retry = %d.\n", __func__, reg, val, retry);
+ msleep(5);
+ continue;
+ }
+ break;
+ }
+
+ if (retry >= timeout) {
+ dev_info(dev, "%s:write reg error: reg=0x%x, val=0x%x.\n",
+ __func__, reg, val);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int ov10635_check_device(struct sensor_data *max9286_data, int index)
+{
+ struct i2c_client *client = max9286_data->i2c_client;
+ struct device *dev = &client->dev;
+ unsigned char reg = 0;
+
+ ov10635_read_reg(max9286_data, index, OV10635_REG_PID, &reg);
+ if (reg != 0xA6) {
+ dev_err(dev, "%s: OV10635 hasn't been found, reg[0x%x] = 0x%x., index=%d\n",
+ __func__, OV10635_REG_PID, reg, index);
+ return -1;
+ }
+ ov10635_read_reg(max9286_data, index, OV10635_REG_VER, &reg);
+ if (reg != 0x35) {
+ dev_err(dev, "%s: OV10635 hasn't been found, reg[0x%x] = 0x%x.\n",
+ __func__, OV10635_REG_VER, reg);
+ return -1;
+ }
+ dev_info(dev, "%s: OV10635 index=%d was found.\n", __func__, index);
+
+ return 0;
+}
+
+static int ov10635_download_firmware(struct sensor_data *max9286_data, int index,
+ struct reg_value *reg_setting, s32 arysize)
+{
+ register u32 delay_ms = 0;
+ register u16 reg_addr = 0;
+ register u8 val = 0;
+ int i, retval = 0;
+
+ for (i = 0; i < arysize; ++i, ++reg_setting) {
+ delay_ms = reg_setting->delay_ms;
+ reg_addr = reg_setting->reg_addr;
+ val = reg_setting->val;
+
+ retval = ov10635_write_reg(max9286_data, index, reg_addr, val);
+ if (retval < 0)
+ goto err;
+
+ if (delay_ms)
+ msleep(delay_ms);
+ }
+err:
+ return retval;
+}
+
+static int ov10635_initialize(struct sensor_data *max9286_data, int index)
+{
+ struct device *dev = &max9286_data->i2c_client->dev; int i, array_size;
+ int retval;
+
+ dev_info(dev, "%s: index = %d.\n", __func__, index);
+ array_size = ARRAY_SIZE(ov10635_init_data);
+ for (i = 0; i < array_size; i++) {
+ retval = ov10635_write_reg(max9286_data, index,
+ ov10635_init_data[i].reg_addr,
+ ov10635_init_data[i].val);
+ if (retval < 0)
+ break;
+ if (ov10635_init_data[i].delay_ms != 0)
+ msleep(ov10635_init_data[i].delay_ms);
+ }
+
+ return 0;
+}
+
+static inline int max9271_read_reg(struct sensor_data *max9286_data, int index, u8 reg)
+{
+ struct device *dev = &max9286_data->i2c_client->dev;
+ int val;
+ int retry, timeout = 10;
+
+ max9286_data->i2c_client->addr = ADDR_MAX9271 + index;
+ for (retry = 0; retry < timeout; retry++) {
+ val = i2c_smbus_read_byte_data(max9286_data->i2c_client, reg);
+ if (val < 0)
+ msleep(5);
+ else
+ break;
+ }
+
+ if (retry >= timeout) {
+ dev_info(dev, "%s:read reg error: reg=%2x\n", __func__, reg);
+ return -1;
+ }
+
+ return val;
+}
+
+static int max9271_write_reg(struct sensor_data *max9286_data, int index, u8 reg, u8 val)
+{
+ struct i2c_client *client = max9286_data->i2c_client;
+ struct device *dev = &client->dev;
+ s32 ret;
+ int retry, timeout = 10;
+
+ max9286_data->i2c_client->addr = ADDR_MAX9271 + index;
+ for (retry = 0; retry < timeout; retry++) {
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+ if (val < 0)
+ msleep(5);
+ else
+ break;
+ }
+ dev_dbg(dev, "%s: addr %02x reg %02x val %02x\n", __func__, client->addr, reg, val);
+
+ if (retry >= timeout) {
+ dev_info(dev, "%s:write reg error:reg=%2x,val=%2x\n", __func__, reg, val);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* Read one register from a MAX9286 i2c slave device.
+ *
+ * @param *reg: register in the device we wish to access.
+ *
+ * @return 0 if success, an error code otherwise.
+ */
+static inline int max9286_read_reg(struct sensor_data *max9286_data, u8 reg)
+{
+ int val;
+
+ max9286_data->i2c_client->addr = ADDR_MAX9286;
+ val = i2c_smbus_read_byte_data(max9286_data->i2c_client, reg);
+ if (val < 0) {
+ dev_info(&max9286_data->i2c_client->dev,
+ "%s:read reg error: reg=%2x\n", __func__, reg);
+ return -1;
+ }
+ return val;
+}
+
+/* Write one register of a MAX9286 i2c slave device.
+ *
+ * @param *reg: register in the device we wish to access.
+ *
+ * @return 0 if success, an error code otherwise.
+ */
+static int max9286_write_reg(struct sensor_data *max9286_data, u8 reg, u8 val)
+{
+ struct i2c_client *client = max9286_data->i2c_client;
+ struct device *dev = &client->dev;
+ s32 ret;
+
+ client->addr = ADDR_MAX9286;
+ ret = i2c_smbus_write_byte_data(client, reg, val);
+
+ dev_dbg(dev, "addr %02x reg %02x val %02x\n", client->addr, reg, val);
+
+ if (ret < 0) {
+ dev_info(dev, "write reg error:reg=%2x,val=%2x\n", reg, val);
+ return -1;
+ }
+ return 0;
+}
+
+#ifdef debug
+static void max9271_dump_registers(struct sensor_data *max9286_data, int index)
+{
+ unsigned char i;
+
+ pr_info("max9271_dump_registers: index = %d.\r\n", index);
+ for (i = 0; i < 0x20; i++)
+ pr_info("MAX9271 Reg 0x%02x = 0x%x.\r\n",
+ i, max9271_read_reg(max9286_data, index, i));
+}
+
+static void max9286_dump_registers(struct sensor_data *max9286_data)
+{
+ unsigned char i;
+
+ pr_info("Dump MAX9286 registers:\r\n");
+ for (i = 0; i < 0x72; i++)
+ pr_info("MAX9286 Reg 0x%02x = 0x%x.\r\n",
+ i, max9286_read_reg(max9286_data, i));
+}
+#else
+static void max9271_dump_registers(struct sensor_data *max9286_data, int index)
+{
+}
+#endif
+
+static void max9286_hw_reset(struct sensor_data *max9286_data)
+{
+ gpiod_set_value_cansleep(max9286_data->pwn_gpio, 0);
+ udelay(200);
+ gpiod_set_value_cansleep(max9286_data->pwn_gpio, 1);
+ msleep(1);
+}
+
+static int max9286_hardware_preinit(struct sensor_data *max9286_data)
+{
+ u8 reg;
+
+ dev_info(&max9286_data->i2c_client->dev, "In %s()\n", __func__);
+
+ /* Disable CSI Output */
+ max9286_write_reg(max9286_data, 0x15, 0x03);
+
+ /* Enable PRBS test */
+ max9286_write_reg(max9286_data, 0x0E, 0x5F);
+ msleep(10);
+
+ /* Enable Custom Reverse Channel & First Pulse Length STEP 1 */
+ max9286_write_reg(max9286_data, 0x3F, 0x4F);
+ msleep(2); /* STEP 2 */
+
+ /* Reverse Channel Amplitude to mid level and transition time */
+ max9286_write_reg(max9286_data, 0x3B, 0x1E); /* STEP 3 */
+ msleep(2); /* STEP 4 */
+
+ /* Enable MAX9271 Configuration Link */
+ max9271_write_reg(max9286_data, 0, 0x04, 0x43); /* STEP 5 */
+ msleep(2); /* STEP 6 */
+
+ /* Increase serializer reverse channel input thresholds */
+ max9271_write_reg(max9286_data, 0, 0x08, 0x01); /* STEP 7 */
+ msleep(2); /* STEP 8 */
+
+ /* Reverse Channel Amplitude level */
+ max9286_write_reg(max9286_data, 0x3B, 0x19); /* STEP 9 */
+ msleep(5); /* STEP 10 */
+
+ /* Set YUV422 8 bits mode, Double Data Rate, 4 data lane */
+ max9286_write_reg(max9286_data, 0x12, 0xF3); /* STEP 12 */
+
+ max9286_write_reg(max9286_data, 0x01, 0x02); /* STEP 13 */
+ /* Enable All Link 0-3 */
+ max9286_write_reg(max9286_data, 0x00, 0xef); /* STEP 14 */
+
+ /* Frame Sync */
+ /* Automatic Mode */
+ max9286_write_reg(max9286_data, 0x01, 0x02);/* STEP 13 */
+ msleep(200);
+ /* Detect link */
+ max9286_data->sensor_num = 0;
+ reg = max9286_read_reg(max9286_data, 0x49);
+ max9286_data->sensor_is_there = ((reg >> 4) & 0xF) | (reg & 0xF);
+ if (max9286_data->sensor_is_there & (0x1 << 0))
+ max9286_data->sensor_num += 1;
+ if (max9286_data->sensor_is_there & (0x1 << 1))
+ max9286_data->sensor_num += 1;
+ if (max9286_data->sensor_is_there & (0x1 << 2))
+ max9286_data->sensor_num += 1;
+ if (max9286_data->sensor_is_there & (0x1 << 3))
+ max9286_data->sensor_num += 1;
+ pr_info("max9286_mipi: reg = 0x%02x.\n", reg);
+ pr_info("max9286_mipi: sensor number = %d.\n", max9286_data->sensor_num);
+
+ if (max9286_data->sensor_num == 0) {
+ pr_err("%s: no camera connected.\n", __func__);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void max9286_camera_reorder(struct sensor_data *max9286_data)
+{
+ u8 reg;
+
+ reg = 0xE4;
+ if (max9286_data->sensor_num == 1) {
+ switch (max9286_data->sensor_is_there) {
+ case 0x8:
+ reg = 0x27;
+ break;
+ case 0x4:
+ reg = 0xC6;
+ break;
+ case 0x2:
+ reg = 0xE1;
+ break;
+ case 0x1:
+ default:
+ reg = 0xE4;
+ break;
+ }
+ } else if (max9286_data->sensor_num == 2) {
+ switch (max9286_data->sensor_is_there) {
+ case 0xC:
+ reg = 0x4E;
+ break;
+ case 0xA:
+ reg = 0x72;
+ break;
+ case 0x9:
+ reg = 0x78;
+ break;
+ case 0x6:
+ reg = 0xD2;
+ break;
+ case 0x5:
+ reg = 0xD8;
+ break;
+ case 0x3:
+ default:
+ reg = 0xE4;
+ break;
+ }
+ } else if (max9286_data->sensor_num == 3) {
+ switch (max9286_data->sensor_is_there) {
+ case 0xE:
+ reg = 0x93;
+ break;
+ case 0xD:
+ reg = 0x9C;
+ break;
+ case 0xB:
+ reg = 0xB4;
+ break;
+ case 0x7:
+ default:
+ reg = 0xE4;
+ break;
+ }
+ }
+ max9286_write_reg(max9286_data, 0x0B, reg);
+}
+
+static int max9286_hardware_init(struct sensor_data *max9286_data)
+{
+ int retval = 0;
+ int i;
+ u8 reg, sensor_addr = 0;
+
+ dev_info(&max9286_data->i2c_client->dev, "In %s()\n", __func__);
+
+ /* Disable PRBS test */
+ max9286_write_reg(max9286_data, 0x0E, 0x50);
+
+ /* reorder camera */
+ max9286_camera_reorder(max9286_data);
+
+ /* Enable all links */
+ reg = 0xE0 | max9286_data->sensor_is_there;
+ max9286_write_reg(max9286_data, 0x00, reg);
+
+ /* Set up links */
+ sensor_addr = ADDR_OV_SENSOR;
+ max9271_write_reg(max9286_data, 0, 0x07, 0x84);
+ /* STEP 15-46 */
+ reg = 0;
+ for (i = 1; i <= MAX9271_MAX_SENSOR_NUM; i++) {
+ if (((0x1 << (i - 1)) & max9286_data->sensor_is_there) == 0)
+ continue;
+
+ /* Enable Link control channel */
+ reg |= (0x11 << (i - 1));
+ max9286_write_reg(max9286_data, 0x0A, reg);/* STEP 15 */
+
+ /* Set MAX9271 new address for link 0 */
+ max9271_write_reg(max9286_data, 0, 0x00, (ADDR_MAX9271 + i) << 1);
+ msleep(2);
+
+ max9271_write_reg(max9286_data, i, 0x01, ADDR_MAX9286 << 1);
+ max9271_write_reg(max9286_data, i, 0x09, (sensor_addr + i) << 1);
+ max9271_write_reg(max9286_data, i, 0x0A, sensor_addr << 1);
+ max9271_write_reg(max9286_data, i, 0x0B, ADDR_MAX9271_ALL << 1);
+ max9271_write_reg(max9286_data, i, 0x0C, (ADDR_MAX9271 + i) << 1);
+
+ msleep(1);
+ pr_info("max9286_mipi: initialized sensor = 0x%02x.\n", i);
+ max9271_dump_registers(max9286_data, i);
+ }
+ max9286_write_reg(max9286_data, 0x0A, reg);
+ max9286_write_reg(max9286_data, 0x0A, reg);
+
+ /* Disable Local Auto I2C ACK */
+ max9286_write_reg(max9286_data, 0x34, 0x36); /* STEP 48 */
+
+ /* Initialize Camera Sensor */
+ /* STEP 49 */
+ if (max9286_data->sensor_is_there & (0x1 << 0)) {
+ retval = ov10635_check_device(max9286_data, 1);
+ if (retval < 0)
+ return retval;
+ ov10635_initialize(max9286_data, 0);
+ }
+
+ if (max9286_data->sensor_is_there & (0x1 << 1)) {
+ retval = ov10635_check_device(max9286_data, 2);
+ if (retval < 0)
+ return retval;
+ ov10635_initialize(max9286_data, 1);
+ }
+
+ if (max9286_data->sensor_is_there & (0x1 << 2)) {
+ retval = ov10635_check_device(max9286_data, 3);
+ if (retval < 0)
+ return retval;
+ ov10635_initialize(max9286_data, 2);
+ }
+
+ if (max9286_data->sensor_is_there & (0x1 << 3)) {
+ retval = ov10635_check_device(max9286_data, 4);
+ if (retval < 0)
+ return retval;
+ ov10635_initialize(max9286_data, 3);
+ }
+
+ /* Enable Local Auto I2C ACK */
+ max9286_write_reg(max9286_data, 0x34, 0xB6); /* STEP 50 */
+
+ /* MAX9271: Enable Serial Links and Disable Configuration Link */
+ max9271_write_reg(max9286_data, ADDR_MAX9271_ALL - ADDR_MAX9271, 0x04, 0x83); /* STEP 51 */
+ /* Wait for more than 2 frame time */
+ msleep(1000); /* STEP 52 */
+
+ /* Enable CSI output, set virtual channel according to the link number */
+ max9286_write_reg(max9286_data, 0x15, 0x9B); /* STEP 52 */
+ msleep(10);
+ return retval;
+}
+
+static int ov10635_change_mode(struct sensor_data *max9286_data)
+{
+ struct reg_value *reg_setting = NULL;
+ enum ov10635_mode mode = max9286_data->current_mode;
+ enum ov10635_frame_rate rate =
+ to_ov10635_frame_rate(&max9286_data->frame_interval);
+ int arysize = 0, retval = 0;
+
+ if (mode > ov10635_mode_MAX || mode < ov10635_mode_MIN) {
+ pr_err("Wrong ov10635 mode detected!\n");
+ return -1;
+ }
+
+ reg_setting = ov10635_mode_info_data[rate][mode].init_data_ptr;
+ arysize = ov10635_mode_info_data[rate][mode].init_data_size;
+
+ max9286_data->format.width = ov10635_mode_info_data[rate][mode].width;
+ max9286_data->format.height = ov10635_mode_info_data[rate][mode].height;
+
+ if (max9286_data->format.width == 0 ||
+ max9286_data->format.height == 0 ||
+ !reg_setting || arysize == 0) {
+ pr_err("Not support mode=%d %s\n", mode,
+ (rate == 0) ? "15(fps)" : "30(fps)");
+ return -EINVAL;
+ }
+
+ retval = ov10635_download_firmware(max9286_data, 0, reg_setting, arysize);
+
+ return retval;
+}
+
+static int max9286_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+
+ code->code = max9286_data->format.code;
+ return 0;
+}
+
+/*
+ * max9286_enum_framesizes - V4L2 sensor interface handler for
+ * VIDIOC_ENUM_FRAMESIZES ioctl
+ * @s: pointer to standard V4L2 device structure
+ * @fsize: standard V4L2 VIDIOC_ENUM_FRAMESIZES ioctl structure
+ *
+ * Return 0 if successful, otherwise -EINVAL.
+ */
+static int max9286_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ if (fse->index > ov10635_mode_MAX)
+ return -EINVAL;
+
+ fse->max_width = max(ov10635_mode_info_data[0][fse->index].width,
+ ov10635_mode_info_data[1][fse->index].width);
+ fse->min_width = fse->max_width;
+
+ fse->max_height = max(ov10635_mode_info_data[0][fse->index].height,
+ ov10635_mode_info_data[1][fse->index].height);
+ fse->min_height = fse->max_height;
+
+ return 0;
+}
+
+static int max9286_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ int i, j, count;
+
+ if (fie->index < 0 || fie->index > ov10635_mode_MAX)
+ return -EINVAL;
+
+ if (fie->width == 0 || fie->height == 0 || fie->code == 0) {
+ pr_warn("Please assign pixel format, width and height.\n");
+ return -EINVAL;
+ }
+
+ fie->interval.numerator = 1;
+
+ /* TODO Reserved to extension */
+ count = 0;
+ for (i = 0; i < ARRAY_SIZE(ov10635_framerates); i++) {
+ for (j = 0; j < (ov10635_mode_MAX + 1); j++) {
+ if (fie->width == ov10635_mode_info_data[i][j].width &&
+ fie->height == ov10635_mode_info_data[i][j].height &&
+ ov10635_mode_info_data[i][j].init_data_ptr)
+ count++;
+
+ if (fie->index == (count - 1)) {
+ fie->interval.denominator = ov10635_framerates[i];
+ return 0;
+ }
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int max9286_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ if (fmt->pad)
+ return -EINVAL;
+
+ mf->code = max9286_data->format.code;
+ mf->width = max9286_data->format.width;
+ mf->height = max9286_data->format.height;
+ mf->colorspace = max9286_data->format.colorspace;
+ mf->field = max9286_data->format.field;
+ mf->reserved[0] = max9286_data->format.reserved[0];
+
+ return 0;
+}
+
+static struct ov10635_mode_info *get_max_resolution(enum ov10635_frame_rate rate)
+{
+ u32 max_width;
+ enum ov10635_mode mode;
+ int i;
+
+ mode = 0;
+ max_width = ov10635_mode_info_data[rate][0].width;
+
+ for (i = 0; i < (ov10635_mode_MAX + 1); i++) {
+ if (ov10635_mode_info_data[rate][i].width > max_width) {
+ max_width = ov10635_mode_info_data[rate][i].width;
+ mode = i;
+ }
+ }
+ return &ov10635_mode_info_data[rate][mode];
+}
+
+static struct ov10635_mode_info *match(struct v4l2_mbus_framefmt *fmt,
+ enum ov10635_frame_rate rate)
+{
+ struct ov10635_mode_info *info;
+ int i;
+
+ for (i = 0; i < (ov10635_mode_MAX + 1); i++) {
+ if (fmt->width == ov10635_mode_info_data[rate][i].width &&
+ fmt->height == ov10635_mode_info_data[rate][i].height) {
+ info = &ov10635_mode_info_data[rate][i];
+ break;
+ }
+ }
+ if (i == ov10635_mode_MAX + 1)
+ info = NULL;
+
+ return info;
+}
+
+static bool try_to_find_resolution(struct sensor_data *sensor,
+ const enum ov10635_frame_rate fr,
+ struct v4l2_mbus_framefmt *mf)
+{
+ enum ov10635_mode mode = sensor->current_mode;
+ enum ov10635_frame_rate frame_rate = fr;
+ struct device *dev = &sensor->i2c_client->dev;
+ struct ov10635_mode_info *info;
+ bool found = false;
+
+ if ((mf->width == ov10635_mode_info_data[frame_rate][mode].width) &&
+ (mf->height == ov10635_mode_info_data[frame_rate][mode].height)) {
+ info = &ov10635_mode_info_data[frame_rate][mode];
+ found = true;
+ } else {
+ /* get mode info according to frame user's width and height */
+ info = match(mf, frame_rate);
+ if (!info) {
+ frame_rate ^= 0x1;
+ info = match(mf, frame_rate);
+ if (info) {
+ sensor->current_mode = -1;
+ dev_err(dev, "%s %dx%d only support %s(fps)\n",
+ __func__,
+ info->width, info->height,
+ (frame_rate == 0) ? "15fps" : "30fps");
+ return false;
+ }
+ goto max_resolution;
+ }
+ found = true;
+ }
+
+ /* get max resolution to resize */
+max_resolution:
+ if (!found) {
+ frame_rate ^= 0x1;
+ info = get_max_resolution(frame_rate);
+ }
+
+ sensor->current_mode = info->mode;
+ sensor->frame_interval.denominator = (frame_rate) ? 30 : 15;
+ sensor->format.width = info->width;
+ sensor->format.height = info->height;
+
+ return found;
+}
+
+static int max9286_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ enum ov10635_frame_rate frame_rate = max9286_data->current_fr;
+ int ret;
+
+ if (fmt->pad)
+ return -EINVAL;
+
+ mf->code = max9286_data->format.code;
+ mf->colorspace = max9286_data->format.colorspace;
+ mf->field = V4L2_FIELD_NONE;
+
+ try_to_find_resolution(max9286_data, frame_rate, mf);
+
+ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
+ return 0;
+
+ ret = ov10635_change_mode(max9286_data);
+
+ return ret;
+}
+
+static int max9286_get_frame_desc(struct v4l2_subdev *sd, unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ return 0;
+}
+
+static int max9286_set_frame_desc(struct v4l2_subdev *sd,
+ unsigned int pad,
+ struct v4l2_mbus_frame_desc *fd)
+{
+ return 0;
+}
+
+static int max9286_set_power(struct v4l2_subdev *sd, int on)
+{
+ return 0;
+}
+
+static int ov10635_try_frame_interval(struct sensor_data *sensor,
+ struct v4l2_fract *fi,
+ u32 width, u32 height)
+{
+ enum ov10635_frame_rate rate = OV10635_15_FPS;
+ int minfps, maxfps, best_fps, fps;
+ int i;
+
+ minfps = ov10635_framerates[OV10635_15_FPS];
+ maxfps = ov10635_framerates[OV10635_30_FPS];
+
+ if (fi->numerator == 0) {
+ fi->denominator = ov10635_framerates[OV10635_30_FPS];
+ fi->numerator = 1;
+ rate = OV10635_30_FPS;
+ goto out;
+ }
+
+ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator),
+ minfps, maxfps);
+
+ best_fps = minfps;
+ for (i = 0; i < ARRAY_SIZE(ov10635_framerates); i++) {
+ int curr_fps = ov10635_framerates[i];
+
+ if (abs(curr_fps - fps) < abs(best_fps - fps)) {
+ best_fps = curr_fps;
+ rate = i;
+ }
+ }
+
+ fi->numerator = 1;
+ fi->denominator = best_fps;
+
+out:
+ return rate;
+}
+
+static int max9286_g_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+
+ mutex_lock(&max9286_data->lock);
+ fi->interval = max9286_data->frame_interval;
+ mutex_unlock(&max9286_data->lock);
+
+ return 0;
+}
+
+static int max9286_s_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *fi)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+ enum ov10635_mode mode = max9286_data->current_mode;
+ enum ov10635_frame_rate fr = max9286_data->current_fr;
+ struct v4l2_mbus_framefmt mf;
+ bool found = false;
+ int frame_rate, ret = 0;
+
+ if (fi->pad != 0)
+ return -EINVAL;
+
+ mutex_lock(&max9286_data->lock);
+
+ memset(&mf, 0, sizeof(mf));
+ mf.width = ov10635_mode_info_data[fr][mode].width;
+ mf.height = ov10635_mode_info_data[fr][mode].height;
+ frame_rate = ov10635_try_frame_interval(max9286_data, &fi->interval,
+ mf.width, mf.height);
+ if (frame_rate < 0) {
+ fi->interval = max9286_data->frame_interval;
+ goto out;
+ }
+
+ mf.width = ov10635_mode_info_data[frame_rate][mode].width;
+ mf.height = ov10635_mode_info_data[frame_rate][mode].height;
+ found = try_to_find_resolution(max9286_data, frame_rate, &mf);
+ if (!found) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ max9286_data->current_fr = frame_rate;
+ max9286_data->frame_interval = fi->interval;
+
+out:
+ mutex_unlock(&max9286_data->lock);
+ return ret;
+}
+
+static int max9286_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+
+ dev_dbg(sd->dev, "%s\n", __func__);
+ if (enable) {
+ if (!max9286_data->running++) {
+ /*
+ * Enable CSI output, set virtual channel
+ * according to the link number
+ */
+ max9286_write_reg(max9286_data, 0x15, 0x9B);
+ }
+
+ } else {
+
+ if (!--max9286_data->running) {
+ /* Disable CSI Output */
+ max9286_write_reg(max9286_data, 0x15, 0x03);
+ }
+ }
+
+ return 0;
+}
+
+static int max9286_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote,
+ u32 flags)
+{
+ return 0;
+}
+
+static const struct v4l2_subdev_pad_ops max9286_pad_ops = {
+ .enum_mbus_code = max9286_enum_mbus_code,
+ .enum_frame_size = max9286_enum_framesizes,
+ .enum_frame_interval = max9286_enum_frame_interval,
+ .get_fmt = max9286_get_fmt,
+ .set_fmt = max9286_set_fmt,
+ .get_frame_desc = max9286_get_frame_desc,
+ .set_frame_desc = max9286_set_frame_desc,
+};
+
+static const struct v4l2_subdev_core_ops max9286_core_ops = {
+ .s_power = max9286_set_power,
+};
+
+static const struct v4l2_subdev_video_ops max9286_video_ops = {
+ .g_frame_interval = max9286_g_frame_interval,
+ .s_frame_interval = max9286_s_frame_interval,
+ .s_stream = max9286_s_stream,
+};
+
+static const struct v4l2_subdev_ops max9286_subdev_ops = {
+ .core = &max9286_core_ops,
+ .pad = &max9286_pad_ops,
+ .video = &max9286_video_ops,
+};
+
+static const struct media_entity_operations max9286_sd_media_ops = {
+ .link_setup = max9286_link_setup,
+};
+
+ssize_t analog_test_pattern_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+ u8 val = 0;
+
+ ov10635_read_reg(max9286_data, 0, 0x370A, &val);
+ return sprintf(buf, "%s\n", (val & 0x4) ? "enabled" : "disabled");
+}
+
+static ssize_t analog_test_pattern_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+ char enabled[32];
+
+ if (sscanf(buf, "%s", enabled) > 0) {
+ if (strcmp(enabled, "enable") == 0)
+ ov10635_write_reg(max9286_data, 0, 0x370A, 0x4);
+ else
+ ov10635_write_reg(max9286_data, 0, 0x370A, 0x0);
+ return count;
+ }
+ return -EINVAL;
+}
+
+static DEVICE_ATTR_RW(analog_test_pattern);
+
+/*!
+ * max9286 I2C probe function
+ *
+ * @param adapter struct i2c_adapter *
+ * @return Error code indicating success or failure
+ */
+static int max9286_probe(struct i2c_client *client, const struct i2c_device_id *id)
+{
+ struct device *dev = &client->dev;
+ struct sensor_data *max9286_data;
+ struct v4l2_subdev *sd;
+ int retval;
+
+ max9286_data = devm_kzalloc(dev, sizeof(*max9286_data), GFP_KERNEL);
+ if (!max9286_data)
+ return -ENOMEM;
+
+ /* Set initial values for the sensor struct. */
+ max9286_data->sensor_clk = devm_clk_get(dev, "capture_mclk");
+ if (IS_ERR(max9286_data->sensor_clk)) {
+ /* assuming clock enabled by default */
+ dev_err(dev, "clock-frequency missing or invalid\n");
+ return PTR_ERR(max9286_data->sensor_clk);
+ }
+
+ retval = of_property_read_u32(dev->of_node, "mclk", &max9286_data->mclk);
+ if (retval) {
+ dev_err(dev, "mclk missing or invalid\n");
+ return retval;
+ }
+
+ retval = of_property_read_u32(dev->of_node, "mclk_source", (u32 *)&max9286_data->mclk_source);
+ if (retval) {
+ dev_err(dev, "mclk_source missing or invalid\n");
+ return retval;
+ }
+
+ /* request power down pin */
+ max9286_data->pwn_gpio = devm_gpiod_get_optional(dev, "pwn-gpios",
+ GPIOD_OUT_HIGH);
+ if (IS_ERR(max9286_data->pwn_gpio))
+ return PTR_ERR(max9286_data->pwn_gpio);
+
+ max9286_hw_reset(max9286_data);
+
+ clk_prepare_enable(max9286_data->sensor_clk);
+
+ mutex_init(&max9286_data->lock);
+
+ max9286_data->i2c_client = client;
+ max9286_data->format.code = MEDIA_BUS_FMT_YUYV8_1X16;
+ max9286_data->format.width = ov10635_mode_info_data[1][0].width;
+ max9286_data->format.height = ov10635_mode_info_data[1][0].height;
+ max9286_data->format.colorspace = V4L2_COLORSPACE_JPEG;
+
+ /*
+ * Pass mipi phy clock rate Mbps
+ * fcsi2 = PCLk * WIDTH * CHANNELS / LANES
+ * fsci2 = 72MPCLK * 8 bit * 4 channels / 4 lanes
+ */
+ max9286_data->format.reserved[0] = 72 * 8;
+ max9286_data->format.field = V4L2_FIELD_NONE;
+ max9286_data->current_mode = 0;
+ max9286_data->frame_interval.denominator = 30;
+ max9286_data->frame_interval.numerator = 1;
+ max9286_data->is_mipi = 1;
+
+ retval = max9286_read_reg(max9286_data, 0x1e);
+ if (retval != 0x40) {
+ pr_warn("max9286 is not found, chip id reg 0x1e = 0x(%x)\n", retval);
+ clk_disable_unprepare(max9286_data->sensor_clk);
+ return -ENODEV;
+ }
+
+ max9286_hardware_preinit(max9286_data);
+
+ if (max9286_data->sensor_num == 0) {
+ pr_warn("cameras are not found,\n");
+ clk_disable_unprepare(max9286_data->sensor_clk);
+ return -ENODEV;
+ }
+
+ max9286_data->frame_interval.denominator = 30;
+ max9286_data->frame_interval.numerator = 1;
+ max9286_data->v_channel = 0;
+ max9286_data->cap_mode.clip_top = 0;
+ max9286_data->cap_mode.clip_left = 0;
+
+ max9286_data->cap_mode.clip_height = 800;
+ max9286_data->cap_mode.clip_width = 1280;
+
+ max9286_data->cap_mode.hlen = max9286_data->cap_mode.clip_width;
+
+ max9286_data->cap_mode.hfp = 0;
+ max9286_data->cap_mode.hbp = 0;
+ max9286_data->cap_mode.hsync = 625;
+ max9286_data->cap_mode.vlen = 800;
+ max9286_data->cap_mode.vfp = 0;
+ max9286_data->cap_mode.vbp = 0;
+ max9286_data->cap_mode.vsync = 40;
+ max9286_data->cap_mode.vlen1 = 0;
+ max9286_data->cap_mode.vfp1 = 0;
+ max9286_data->cap_mode.vbp1 = 0;
+ max9286_data->cap_mode.vsync1 = 0;
+ max9286_data->cap_mode.pixelclock = 27000000;
+
+ sd = &max9286_data->subdev;
+ v4l2_i2c_subdev_init(sd, client, &max9286_subdev_ops);
+ sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+
+ sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
+ max9286_data->pads[MIPI_CSI2_SENS_VC0_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ max9286_data->pads[MIPI_CSI2_SENS_VC1_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ max9286_data->pads[MIPI_CSI2_SENS_VC2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ max9286_data->pads[MIPI_CSI2_SENS_VC3_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ retval = media_entity_pads_init(&sd->entity, MIPI_CSI2_SENS_VCX_PADS_NUM,
+ max9286_data->pads);
+ if (retval < 0)
+ return retval;
+
+ max9286_data->subdev.entity.ops = &max9286_sd_media_ops;
+ retval = v4l2_async_register_subdev(&max9286_data->subdev);
+ if (retval < 0) {
+ dev_err(dev, "Async register failed, ret=(%d)\n", retval);
+ media_entity_cleanup(&sd->entity);
+ }
+
+ retval = max9286_hardware_init(max9286_data);
+ if (retval < 0) {
+ dev_err(dev, "camera init failed\n");
+ clk_disable_unprepare(max9286_data->sensor_clk);
+ media_entity_cleanup(&sd->entity);
+ v4l2_async_unregister_subdev(sd);
+ return retval;
+ }
+
+ max9286_data->running = 0;
+
+ /* Disable CSI Output */
+ max9286_write_reg(max9286_data, 0x15, 0x03);
+
+ /*Create device attr in sys */
+ retval = device_create_file(&client->dev, &dev_attr_analog_test_pattern);
+ if (retval < 0) {
+ dev_err(dev, "%s: create device file fail\n", __func__);
+ return retval;
+ }
+
+ dev_info(dev, "max9286_mipi is found, name %s\n", sd->name);
+ return retval;
+}
+
+/*!
+ * max9286 I2C detach function
+ *
+ * @param client struct i2c_client *
+ * @return Error code indicating success or failure
+ */
+static int max9286_remove(struct i2c_client *client)
+{
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
+ struct sensor_data *max9286_data = subdev_to_sensor_data(sd);
+
+ clk_disable_unprepare(max9286_data->sensor_clk);
+ device_remove_file(&client->dev, &dev_attr_analog_test_pattern);
+ media_entity_cleanup(&sd->entity);
+ v4l2_async_unregister_subdev(sd);
+
+ return 0;
+}
+
+static const struct i2c_device_id max9286_id[] = {
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, max9286_id);
+
+static const struct of_device_id max9286_of_match[] = {
+ { .compatible = "maxim,max9286_mipi" },
+ { /* sentinel */ }
+};
+
+static struct i2c_driver max9286_i2c_driver = {
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "max9286_mipi",
+ .of_match_table = of_match_ptr(max9286_of_match),
+ },
+ .probe = max9286_probe,
+ .remove = max9286_remove,
+ .id_table = max9286_id,
+};
+
+module_i2c_driver(max9286_i2c_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MAX9286 GSML Deserializer Driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+MODULE_ALIAS("CSI");
diff --git a/drivers/staging/media/imx/imx8-common.h b/drivers/staging/media/imx/imx8-common.h
new file mode 100644
index 000000000000..ddfbcc0fd7bf
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-common.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * V4L2 Capture ISI subdev for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor to memory or DC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#ifndef __MXC_COMMON_H__
+#define __MXC_COMMON_H__
+
+#define ISI_OF_NODE_NAME "isi"
+#define MIPI_CSI2_OF_NODE_NAME "csi"
+#define PARALLEL_OF_NODE_NAME "pcsi"
+
+#define MXC_ISI_MAX_DEVS 8
+#define MXC_MIPI_CSI2_MAX_DEVS 2
+#define MXC_MAX_SENSORS 3
+
+/* ISI PADS */
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC0 0
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC1 1
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC2 2
+#define MXC_ISI_SD_PAD_SINK_MIPI0_VC3 3
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC0 4
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC1 5
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC2 6
+#define MXC_ISI_SD_PAD_SINK_MIPI1_VC3 7
+
+#define MXC_ISI_SD_PAD_SINK_DC0 8
+#define MXC_ISI_SD_PAD_SINK_DC1 9
+#define MXC_ISI_SD_PAD_SINK_HDMI 10
+#define MXC_ISI_SD_PAD_SINK_MEM 11
+#define MXC_ISI_SD_PAD_SOURCE_MEM 12
+#define MXC_ISI_SD_PAD_SOURCE_DC0 13
+#define MXC_ISI_SD_PAD_SOURCE_DC1 14
+#define MXC_ISI_SD_PAD_SINK_PARALLEL_CSI 15
+#define MXC_ISI_SD_PADS_NUM 16
+
+/* MIPI CSI PADS */
+#define MXC_MIPI_CSI2_VC0_PAD_SINK 0
+#define MXC_MIPI_CSI2_VC1_PAD_SINK 1
+#define MXC_MIPI_CSI2_VC2_PAD_SINK 2
+#define MXC_MIPI_CSI2_VC3_PAD_SINK 3
+
+#define MXC_MIPI_CSI2_VC0_PAD_SOURCE 4
+#define MXC_MIPI_CSI2_VC1_PAD_SOURCE 5
+#define MXC_MIPI_CSI2_VC2_PAD_SOURCE 6
+#define MXC_MIPI_CSI2_VC3_PAD_SOURCE 7
+#define MXC_MIPI_CSI2_VCX_PADS_NUM 8
+
+/* Parallel CSI PADS */
+#define MXC_PARALLEL_CSI_PAD_SOURCE 0
+#define MXC_PARALLEL_CSI_PAD_SINK 1
+#define MXC_PARALLEL_CSI_PADS_NUM 2
+
+#define ISI_2K 2048
+
+enum {
+ IN_PORT,
+ SUB_IN_PORT,
+ OUT_PORT,
+ MAX_PORTS,
+};
+
+enum isi_input_interface {
+ ISI_INPUT_INTERFACE_DC0 = 0,
+ ISI_INPUT_INTERFACE_DC1,
+ ISI_INPUT_INTERFACE_MIPI0_CSI2,
+ ISI_INPUT_INTERFACE_MIPI1_CSI2,
+ ISI_INPUT_INTERFACE_HDMI,
+ ISI_INPUT_INTERFACE_MEM,
+ ISI_INPUT_INTERFACE_PARALLEL_CSI,
+ ISI_INPUT_INTERFACE_MAX,
+};
+
+enum isi_input_sub_interface {
+ ISI_INPUT_SUB_INTERFACE_VC0 = 0,
+ ISI_INPUT_SUB_INTERFACE_VC1,
+ ISI_INPUT_SUB_INTERFACE_VC2,
+ ISI_INPUT_SUB_INTERFACE_VC3,
+};
+
+enum isi_output_interface {
+ ISI_OUTPUT_INTERFACE_DC0 = 0,
+ ISI_OUTPUT_INTERFACE_DC1,
+ ISI_OUTPUT_INTERFACE_MEM,
+ ISI_OUTPUT_INTERFACE_MAX,
+};
+
+enum mxc_isi_buf_id {
+ MXC_ISI_BUF1 = 0x0,
+ MXC_ISI_BUF2,
+};
+
+#endif /* MXC_ISI_CORE_H_ */
diff --git a/drivers/staging/media/imx/imx8-isi-cap.c b/drivers/staging/media/imx/imx8-isi-cap.c
new file mode 100644
index 000000000000..ca325eb964b0
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-cap.c
@@ -0,0 +1,1804 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor to memory or DC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+#define sd_to_cap_dev(ptr) container_of(ptr, struct mxc_isi_cap_dev, sd)
+
+struct mxc_isi_fmt mxc_isi_out_formats[] = {
+ {
+ .name = "RGB565",
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = { 16 },
+ .color = MXC_ISI_OUT_FMT_RGB565,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB565_1X16,
+ }, {
+ .name = "RGB24",
+ .fourcc = V4L2_PIX_FMT_RGB24,
+ .depth = { 24 },
+ .color = MXC_ISI_OUT_FMT_BGR32P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ .name = "BGR24",
+ .fourcc = V4L2_PIX_FMT_BGR24,
+ .depth = { 24 },
+ .color = MXC_ISI_OUT_FMT_RGB32P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_BGR888_1X24,
+ }, {
+ .name = "YUYV-16",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = MXC_ISI_OUT_FMT_YUV422_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_YUV444_1P8,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_AYUV8_1X32,
+ }, {
+ .name = "NV12 (YUYV)",
+ .fourcc = V4L2_PIX_FMT_NV12,
+ .depth = { 8, 8 },
+ .color = MXC_ISI_OUT_FMT_YUV420_2P8P,
+ .memplanes = 2,
+ .colplanes = 2,
+ .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16,
+ }, {
+ .name = "YUV444M (Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV444M,
+ .depth = { 8, 8, 8 },
+ .color = MXC_ISI_OUT_FMT_YUV444_3P8P,
+ .memplanes = 3,
+ .colplanes = 3,
+ .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
+ }, {
+ .name = "xBGR32",
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_XRGB32,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }, {
+ .name = "ABGR32",
+ .fourcc = V4L2_PIX_FMT_ABGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_OUT_FMT_ARGB32,
+ .memplanes = 1,
+ .colplanes = 1,
+ .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
+ }
+};
+
+/*
+ * Pixel link input format
+ */
+struct mxc_isi_fmt mxc_isi_src_formats[] = {
+ {
+ .name = "RGB32",
+ .fourcc = V4L2_PIX_FMT_RGB32,
+ .depth = { 32 },
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV32 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV32,
+ .depth = { 32 },
+ .memplanes = 1,
+ .colplanes = 1,
+ }
+};
+
+struct mxc_isi_fmt *mxc_isi_get_format(unsigned int index)
+{
+ return &mxc_isi_out_formats[index];
+}
+
+/*
+ * lookup mxc_isi color format by fourcc or media bus format
+ */
+struct mxc_isi_fmt *mxc_isi_find_format(const u32 *pixelformat,
+ const u32 *mbus_code, int index)
+{
+ struct mxc_isi_fmt *fmt, *def_fmt = NULL;
+ unsigned int i;
+ int id = 0;
+
+ if (index >= (int)ARRAY_SIZE(mxc_isi_out_formats))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pixelformat && fmt->fourcc == *pixelformat)
+ return fmt;
+ if (mbus_code && fmt->mbus_code == *mbus_code)
+ return fmt;
+ if (index == id)
+ def_fmt = fmt;
+ id++;
+ }
+ return def_fmt;
+}
+
+struct mxc_isi_fmt *mxc_isi_get_src_fmt(struct v4l2_subdev_format *sd_fmt)
+{
+ u32 index;
+
+ /* two fmt RGB32 and YUV444 from pixellink */
+ if (sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_1X16 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_YVYU8_2X8 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_AYUV8_1X32 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_UYVY8_2X8 ||
+ sd_fmt->format.code == MEDIA_BUS_FMT_YUYV8_2X8)
+ index = 1;
+ else
+ index = 0;
+ return &mxc_isi_src_formats[index];
+}
+
+static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf)
+{
+ return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf);
+}
+
+/*
+ * mxc_isi_pipeline_enable() - Enable streaming on a pipeline
+ */
+static int mxc_isi_pipeline_enable(struct mxc_isi_cap_dev *isi_cap, bool enable)
+{
+ struct device *dev = &isi_cap->pdev->dev;
+ struct media_entity *entity = &isi_cap->vdev.entity;
+ struct media_device *mdev = entity->graph_obj.mdev;
+ struct media_graph graph;
+ struct v4l2_subdev *subdev;
+ int ret = 0;
+
+ mutex_lock(&mdev->graph_mutex);
+
+ ret = media_graph_walk_init(&graph, entity->graph_obj.mdev);
+ if (ret) {
+ mutex_unlock(&mdev->graph_mutex);
+ return ret;
+ }
+ media_graph_walk_start(&graph, entity);
+
+ while ((entity = media_graph_walk_next(&graph))) {
+ if (!entity) {
+ dev_dbg(dev, "entity is NULL\n");
+ continue;
+ }
+
+ if (!is_media_entity_v4l2_subdev(entity)) {
+ dev_dbg(dev, "%s is no v4l2 subdev\n", entity->name);
+ continue;
+ }
+
+ subdev = media_entity_to_v4l2_subdev(entity);
+ if (!subdev) {
+ dev_dbg(dev, "%s subdev is NULL\n", entity->name);
+ continue;
+ }
+
+ ret = v4l2_subdev_call(subdev, video, s_stream, enable);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ dev_err(dev, "subdev %s s_stream failed\n", subdev->name);
+ break;
+ }
+ }
+ mutex_unlock(&mdev->graph_mutex);
+ media_graph_walk_cleanup(&graph);
+
+ return ret;
+}
+
+static int mxc_isi_update_buf_paddr(struct mxc_isi_buffer *buf, int memplanes)
+{
+ struct frame_addr *paddr = &buf->paddr;
+ struct vb2_buffer *vb2 = &buf->v4l2_buf.vb2_buf;
+
+ paddr->cb = 0;
+ paddr->cr = 0;
+
+ switch (memplanes) {
+ case 3:
+ paddr->cr = vb2_dma_contig_plane_dma_addr(vb2, 2);
+ /* fall through */
+ case 2:
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2, 1);
+ /* fall through */
+ case 1:
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2, 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_cap_dev *isi_cap = mxc_isi->isi_cap;
+ struct device *dev = &isi_cap->pdev->dev;
+ struct mxc_isi_buffer *buf;
+ struct vb2_buffer *vb2;
+
+ if (list_empty(&isi_cap->out_active)) {
+ dev_warn(dev, "trying to access empty active list\n");
+ return;
+ }
+
+ buf = list_first_entry(&isi_cap->out_active, struct mxc_isi_buffer, list);
+
+ /*
+ * Skip frame when buffer number is not match ISI trigger
+ * buffer
+ */
+ if ((is_buf_active(mxc_isi, 1) && buf->id == MXC_ISI_BUF1) ||
+ (is_buf_active(mxc_isi, 2) && buf->id == MXC_ISI_BUF2)) {
+ dev_dbg(dev, "status=0x%x id=%d\n", mxc_isi->status, buf->id);
+ return;
+ }
+
+ if (buf->discard) {
+ list_move_tail(isi_cap->out_active.next, &isi_cap->out_discard);
+ } else {
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ list_del_init(&buf->list);
+ buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE);
+ }
+
+ isi_cap->frame_count++;
+
+ if (list_empty(&isi_cap->out_pending)) {
+ if (list_empty(&isi_cap->out_discard)) {
+ dev_warn(dev, "trying to access empty discard list\n");
+ return;
+ }
+
+ buf = list_first_entry(&isi_cap->out_discard,
+ struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = isi_cap->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active);
+ return;
+ }
+
+ /* ISI channel output buffer */
+ buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = isi_cap->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_cap_frame_write_done);
+
+static int cap_vb2_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers,
+ unsigned int *num_planes,
+ unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *fmt = dst_f->fmt;
+ unsigned long wh;
+ int i;
+
+ if (!fmt)
+ return -EINVAL;
+
+ for (i = 0; i < fmt->memplanes; i++)
+ alloc_devs[i] = &isi_cap->pdev->dev;
+
+ wh = dst_f->width * dst_f->height;
+
+ *num_planes = fmt->memplanes;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ unsigned int size = (wh * fmt->depth[i]) / 8;
+
+ if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12)
+ size >>= 1;
+ sizes[i] = max_t(u32, size, dst_f->sizeimage[i]);
+ }
+ dev_dbg(&isi_cap->pdev->dev, "%s, buf_n=%d, size=%d\n",
+ __func__, *num_buffers, sizes[0]);
+
+ return 0;
+}
+
+static int cap_vb2_buffer_prepare(struct vb2_buffer *vb2)
+{
+ struct vb2_queue *q = vb2->vb2_queue;
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (!isi_cap->dst_f.fmt)
+ return -EINVAL;
+
+ for (i = 0; i < dst_f->fmt->memplanes; i++) {
+ unsigned long size = dst_f->sizeimage[i];
+
+ if (vb2_plane_size(vb2, i) < size) {
+ v4l2_err(&isi_cap->vdev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb2, i), size);
+ return -EINVAL;
+ }
+
+ vb2_set_plane_payload(vb2, i, size);
+ }
+
+ return 0;
+}
+
+static void cap_vb2_buffer_queue(struct vb2_buffer *vb2)
+{
+ struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2);
+ struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf);
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(vb2->vb2_queue);
+ unsigned long flags;
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ mxc_isi_update_buf_paddr(buf, isi_cap->dst_f.fmt->mdataplanes);
+ list_add_tail(&buf->list, &isi_cap->out_pending);
+
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+}
+
+static int cap_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct mxc_isi_buffer *buf;
+ struct vb2_buffer *vb2;
+ unsigned long flags;
+ int i, j;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (count < 2)
+ return -ENOBUFS;
+
+ if (!mxc_isi)
+ return -EINVAL;
+
+ /* Create a buffer for discard operation */
+ for (i = 0; i < isi_cap->pix.num_planes; i++) {
+ isi_cap->discard_size[i] = isi_cap->dst_f.sizeimage[i];
+ isi_cap->discard_buffer[i] =
+ dma_alloc_coherent(&isi_cap->pdev->dev,
+ PAGE_ALIGN(isi_cap->discard_size[i]),
+ &isi_cap->discard_buffer_dma[i],
+ GFP_DMA | GFP_KERNEL);
+ if (!isi_cap->discard_buffer[i]) {
+ for (j = 0; j < i; j++) {
+ dma_free_coherent(&isi_cap->pdev->dev,
+ isi_cap->discard_size[j],
+ isi_cap->discard_buffer[j],
+ isi_cap->discard_buffer_dma[j]);
+ dev_err(&isi_cap->pdev->dev,
+ "alloc dma buffer(%d) fail\n", j);
+ }
+ return -ENOMEM;
+ }
+ dev_dbg(&isi_cap->pdev->dev,
+ "%s: num_plane=%d discard_size=%d discard_buffer=%p\n"
+ , __func__, i,
+ (int)isi_cap->discard_size[i],
+ isi_cap->discard_buffer[i]);
+ }
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ /* add two list member to out_discard list head */
+ isi_cap->buf_discard[0].discard = true;
+ list_add_tail(&isi_cap->buf_discard[0].list, &isi_cap->out_discard);
+
+ isi_cap->buf_discard[1].discard = true;
+ list_add_tail(&isi_cap->buf_discard[1].list, &isi_cap->out_discard);
+
+ /* ISI channel output buffer 1 */
+ buf = list_first_entry(&isi_cap->out_discard, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = 0;
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_discard.next, &isi_cap->out_active);
+
+ /* ISI channel output buffer 2 */
+ buf = list_first_entry(&isi_cap->out_pending, struct mxc_isi_buffer, list);
+ buf->v4l2_buf.sequence = 1;
+ vb2 = &buf->v4l2_buf.vb2_buf;
+ vb2->state = VB2_BUF_STATE_ACTIVE;
+ mxc_isi_channel_set_outbuf(mxc_isi, buf);
+ list_move_tail(isi_cap->out_pending.next, &isi_cap->out_active);
+
+ /* Clear frame count */
+ isi_cap->frame_count = 1;
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ return 0;
+}
+
+static void cap_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct mxc_isi_cap_dev *isi_cap = vb2_get_drv_priv(q);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct mxc_isi_buffer *buf, *tmp;
+ unsigned long flags;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mxc_isi_channel_disable(mxc_isi);
+
+ spin_lock_irqsave(&isi_cap->slock, flags);
+
+ while (!list_empty(&isi_cap->out_active)) {
+ buf = list_entry(isi_cap->out_active.next,
+ struct mxc_isi_buffer, list);
+ list_del(&buf->list);
+ if (buf->discard)
+ continue;
+
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&isi_cap->out_pending)) {
+ buf = list_entry(isi_cap->out_pending.next,
+ struct mxc_isi_buffer, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ while (!list_empty(&isi_cap->out_discard)) {
+ buf = list_entry(isi_cap->out_discard.next,
+ struct mxc_isi_buffer, list);
+ list_del(&buf->list);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &isi_cap->out_active, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ list_for_each_entry_safe(buf, tmp, &isi_cap->out_pending, list) {
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ INIT_LIST_HEAD(&isi_cap->out_active);
+ INIT_LIST_HEAD(&isi_cap->out_pending);
+ INIT_LIST_HEAD(&isi_cap->out_discard);
+
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ for (i = 0; i < isi_cap->pix.num_planes; i++)
+ dma_free_coherent(&isi_cap->pdev->dev,
+ isi_cap->discard_size[i],
+ isi_cap->discard_buffer[i],
+ isi_cap->discard_buffer_dma[i]);
+}
+
+static struct vb2_ops mxc_cap_vb2_qops = {
+ .queue_setup = cap_vb2_queue_setup,
+ .buf_prepare = cap_vb2_buffer_prepare,
+ .buf_queue = cap_vb2_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = cap_vb2_start_streaming,
+ .stop_streaming = cap_vb2_stop_streaming,
+};
+
+/*
+ * V4L2 controls handling
+ */
+static inline struct mxc_isi_cap_dev *ctrl_to_isi_cap(struct v4l2_ctrl *ctrl)
+{
+ return container_of(ctrl->handler, struct mxc_isi_cap_dev, ctrls.handler);
+}
+
+static int mxc_isi_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_cap_dev *isi_cap = ctrl_to_isi_cap(ctrl);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ unsigned long flags;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ spin_lock_irqsave(&mxc_isi->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (ctrl->val < 0)
+ return -EINVAL;
+ mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_VFLIP:
+ if (ctrl->val < 0)
+ return -EINVAL;
+ mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ if (ctrl->val < 0 || ctrl->val > 255)
+ return -EINVAL;
+ mxc_isi->alpha = ctrl->val;
+ mxc_isi->alphaen = 1;
+ break;
+
+ default:
+ dev_err(&isi_cap->pdev->dev,
+ "%s: Not support %d CID\n", __func__, ctrl->id);
+ return -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&mxc_isi->slock, flags);
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops mxc_isi_ctrl_ops = {
+ .s_ctrl = mxc_isi_s_ctrl,
+};
+
+int mxc_isi_ctrls_create(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+
+ if (isi_cap->ctrls.ready)
+ return 0;
+
+ v4l2_ctrl_handler_init(handler, 4);
+
+ ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_ctrl_ops,
+ V4L2_CID_ALPHA_COMPONENT,
+ 0, 0xff, 1, 0);
+
+ if (!handler->error)
+ ctrls->ready = true;
+
+ return handler->error;
+}
+
+void mxc_isi_ctrls_delete(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_cap->ctrls;
+
+ if (ctrls->ready) {
+ v4l2_ctrl_handler_free(&ctrls->handler);
+ ctrls->ready = false;
+ ctrls->alpha = NULL;
+ }
+}
+
+static struct media_pad
+*mxc_isi_get_remote_source_pad(struct v4l2_subdev *subdev)
+{
+ struct media_pad *sink_pad, *source_pad;
+ int i;
+
+ while (1) {
+ source_pad = NULL;
+ for (i = 0; i < subdev->entity.num_pads; i++) {
+ sink_pad = &subdev->entity.pads[i];
+
+ if (sink_pad->flags & MEDIA_PAD_FL_SINK) {
+ source_pad = media_entity_remote_pad(sink_pad);
+ if (source_pad)
+ break;
+ }
+ }
+ /* return first pad point in the loop */
+ return source_pad;
+ }
+
+ if (i == subdev->entity.num_pads)
+ v4l2_err(subdev, "(%d): No remote pad found!\n", __LINE__);
+
+ return NULL;
+}
+
+static struct v4l2_subdev *mxc_get_remote_subdev(struct v4l2_subdev *subdev,
+ const char * const label)
+{
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad */
+ source_pad = mxc_isi_get_remote_source_pad(subdev);
+ if (!source_pad) {
+ v4l2_err(subdev, "%s, No remote pad found!\n", label);
+ return NULL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(subdev, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+static bool is_entity_link_setup(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct video_device *vdev = &isi_cap->vdev;
+ struct v4l2_subdev *csi_sd, *sen_sd;
+
+ if (!vdev->entity.num_links || !isi_cap->sd.entity.num_links)
+ return false;
+
+ csi_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!csi_sd || !csi_sd->entity.num_links)
+ return false;
+
+ sen_sd = mxc_get_remote_subdev(csi_sd, __func__);
+ if (!sen_sd || !sen_sd->entity.num_links)
+ return false;
+
+ return true;
+}
+
+static int mxc_isi_capture_open(struct file *file)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ struct v4l2_subdev *sd;
+ int ret = -EBUSY;
+
+ mutex_lock(&isi_cap->lock);
+ isi_cap->is_link_setup = is_entity_link_setup(isi_cap);
+ if (!isi_cap->is_link_setup) {
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ if (mxc_isi->m2m_enabled) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_cap->id);
+ return ret;
+ }
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ mutex_lock(&isi_cap->lock);
+ ret = v4l2_fh_open(file);
+ if (ret) {
+ mutex_unlock(&isi_cap->lock);
+ return ret;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ pm_runtime_get_sync(dev);
+
+ ret = v4l2_subdev_call(sd, core, s_power, 1);
+ if (ret) {
+ dev_err(dev, "Call subdev s_power fail!\n");
+ pm_runtime_put(dev);
+ return ret;
+ }
+
+ /* increase usage count for ISI channel */
+ mutex_lock(&mxc_isi->lock);
+ atomic_inc(&mxc_isi->usage_count);
+ mxc_isi->m2m_enabled = false;
+ mutex_unlock(&mxc_isi->lock);
+
+ return 0;
+}
+
+static int mxc_isi_capture_release(struct file *file)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ struct device *dev = &isi_cap->pdev->dev;
+ struct v4l2_subdev *sd;
+ int ret = -1;
+
+ if (!isi_cap->is_link_setup)
+ return 0;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ goto label;
+
+ mutex_lock(&isi_cap->lock);
+ ret = _vb2_fop_release(file, NULL);
+ if (ret) {
+ dev_err(dev, "%s fail\n", __func__);
+ mutex_unlock(&isi_cap->lock);
+ goto label;
+ }
+ mutex_unlock(&isi_cap->lock);
+
+ if (atomic_read(&mxc_isi->usage_count) > 0 &&
+ atomic_dec_and_test(&mxc_isi->usage_count))
+ mxc_isi_channel_deinit(mxc_isi);
+
+ ret = v4l2_subdev_call(sd, core, s_power, 0);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ dev_err(dev, "%s s_power fail\n", __func__);
+ goto label;
+ }
+
+label:
+ pm_runtime_put(dev);
+ return (ret) ? ret : 0;
+}
+
+static const struct v4l2_file_operations mxc_isi_capture_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_isi_capture_open,
+ .release = mxc_isi_capture_release,
+ .poll = vb2_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = vb2_fop_mmap,
+};
+
+/*
+ * The video node ioctl operations
+ */
+static int mxc_isi_cap_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+
+ strlcpy(cap->driver, MXC_ISI_CAPTURE, sizeof(cap->driver));
+ strlcpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d",
+ dev_name(&isi_cap->pdev->dev), isi_cap->id);
+
+ cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int mxc_isi_cap_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)ARRAY_SIZE(mxc_isi_out_formats))
+ return -EINVAL;
+
+ fmt = &mxc_isi_out_formats[f->index];
+ if (!fmt)
+ return -EINVAL;
+
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_cap_g_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ pix->width = dst_f->o_width;
+ pix->height = dst_f->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = dst_f->fmt->fourcc;
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ pix->num_planes = dst_f->fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = dst_f->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = dst_f->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_cap_try_fmt_mplane(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ int i;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ v4l2_err(&isi_cap->sd, "format(%.4s) is not support!\n",
+ (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ v4l2_err(&isi_cap->sd, "%s, W/H=(%d, %d) is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/* Update input frame size and formate */
+static int mxc_isi_source_fmt_init(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_frame *src_f = &isi_cap->src_f;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct v4l2_subdev_format src_fmt;
+ struct media_pad *source_pad;
+ struct v4l2_subdev *src_sd;
+ int ret;
+
+ source_pad = mxc_isi_get_remote_source_pad(&isi_cap->sd);
+ if (!source_pad) {
+ v4l2_err(&isi_cap->sd,
+ "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ src_sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!src_sd)
+ return -EINVAL;
+
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ src_fmt.format.code = MEDIA_BUS_FMT_UYVY8_2X8;
+ src_fmt.format.width = dst_f->width;
+ src_fmt.format.height = dst_f->height;
+ ret = v4l2_subdev_call(src_sd, pad, set_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_err(&isi_cap->sd, "set remote fmt fail!\n");
+ return ret;
+ }
+
+ memset(&src_fmt, 0, sizeof(src_fmt));
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(src_sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
+ v4l2_err(&isi_cap->sd, "get remote fmt fail!\n");
+ return ret;
+ }
+
+ /* Pixel link master will transfer format to RGB32 or YUV32 */
+ src_f->fmt = mxc_isi_get_src_fmt(&src_fmt);
+
+ set_frame_bounds(src_f, src_fmt.format.width, src_fmt.format.height);
+
+ if (dst_f->width > src_f->width || dst_f->height > src_f->height) {
+ dev_err(&isi_cap->pdev->dev,
+ "%s: src:(%d,%d), dst:(%d,%d) Not support upscale\n",
+ __func__,
+ src_f->width, src_f->height,
+ dst_f->width, dst_f->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_cap_s_fmt_mplane(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *fmt;
+ int bpl;
+ int i;
+
+ /* Step1: Check format with output support format list.
+ * Step2: Update output frame information.
+ * Step3: Checkout the format whether is supported by remote subdev
+ * Step3.1: If Yes, call remote subdev set_fmt.
+ * Step3.2: If NO, call remote subdev get_fmt.
+ * Step4: Update input frame information.
+ * Step5: Update mxc isi channel configuration.
+ */
+
+ dev_dbg(&isi_cap->pdev->dev, "%s, fmt=0x%X\n", __func__, pix->pixelformat);
+ if (vb2_is_busy(&isi_cap->vb2_q))
+ return -EBUSY;
+
+ /* Check out put format */
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ dev_dbg(&isi_cap->pdev->dev,
+ "format(%.4s) is not support!\n", (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0)
+ return -EINVAL;
+
+ dst_f->fmt = fmt;
+ dst_f->height = pix->height;
+ dst_f->width = pix->width;
+
+ pix->num_planes = fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * pix->height * fmt->depth[i] >> 3);
+ }
+ }
+
+ if (pix->num_planes > 1) {
+ for (i = 0; i < pix->num_planes; i++) {
+ dst_f->bytesperline[i] = pix->plane_fmt[i].bytesperline;
+ dst_f->sizeimage[i] = pix->plane_fmt[i].sizeimage;
+ }
+ } else {
+ dst_f->bytesperline[0] = dst_f->width * dst_f->fmt->depth[0] / 8;
+ dst_f->sizeimage[0] = dst_f->height * dst_f->bytesperline[0];
+ }
+
+ memcpy(&isi_cap->pix, pix, sizeof(*pix));
+ set_frame_bounds(dst_f, pix->width, pix->height);
+
+ return 0;
+}
+
+static int mxc_isi_config_parm(struct mxc_isi_cap_dev *isi_cap)
+{
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ int ret;
+
+ ret = mxc_isi_source_fmt_init(isi_cap);
+ if (ret < 0)
+ return -EINVAL;
+
+ mxc_isi_channel_init(mxc_isi);
+ mxc_isi_channel_config(mxc_isi, &isi_cap->src_f, &isi_cap->dst_f);
+
+ return 0;
+}
+
+static int mxc_isi_cap_g_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_subdev *sd;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ return v4l2_g_parm_cap(video_devdata(file), sd, a);
+}
+
+static int mxc_isi_cap_s_parm(struct file *file, void *fh,
+ struct v4l2_streamparm *a)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct v4l2_subdev *sd;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -ENODEV;
+
+ return v4l2_s_parm_cap(video_devdata(file), sd, a);
+}
+
+
+static int mxc_isi_cap_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ int ret;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_config_parm(isi_cap);
+ if (ret < 0)
+ return ret;
+
+ ret = vb2_ioctl_streamon(file, priv, type);
+ mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled);
+ ret = mxc_isi_pipeline_enable(isi_cap, 1);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ mxc_isi->is_streaming = 1;
+
+ return 0;
+}
+
+static int mxc_isi_cap_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_cap->pdev);
+ int ret;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mxc_isi_pipeline_enable(isi_cap, 0);
+ mxc_isi_channel_disable(mxc_isi);
+ ret = vb2_ioctl_streamoff(file, priv, type);
+
+ mxc_isi->is_streaming = 0;
+
+ return ret;
+}
+
+static int mxc_isi_cap_g_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_frame *f = &isi_cap->src_f;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ switch (s->target) {
+ case V4L2_SEL_TGT_COMPOSE_DEFAULT:
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &isi_cap->dst_f;
+ /* fall through */
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ case V4L2_SEL_TGT_CROP_DEFAULT:
+ s->r.left = 0;
+ s->r.top = 0;
+ s->r.width = f->o_width;
+ s->r.height = f->o_height;
+ return 0;
+
+ case V4L2_SEL_TGT_COMPOSE:
+ f = &isi_cap->dst_f;
+ /* fall through */
+ case V4L2_SEL_TGT_CROP:
+ s->r.left = f->h_off;
+ s->r.top = f->v_off;
+ s->r.width = f->width;
+ s->r.height = f->height;
+ return 0;
+ }
+
+ return -EINVAL;
+}
+
+static int enclosed_rectangle(struct v4l2_rect *a, struct v4l2_rect *b)
+{
+ if (a->left < b->left || a->top < b->top)
+ return 0;
+
+ if (a->left + a->width > b->left + b->width)
+ return 0;
+
+ if (a->top + a->height > b->top + b->height)
+ return 0;
+
+ return 1;
+}
+
+static int mxc_isi_cap_s_selection(struct file *file, void *fh,
+ struct v4l2_selection *s)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct mxc_isi_frame *f;
+ struct v4l2_rect rect = s->r;
+ unsigned long flags;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ if (s->target == V4L2_SEL_TGT_COMPOSE)
+ f = &isi_cap->dst_f;
+ else if (s->target == V4L2_SEL_TGT_CROP)
+ f = &isi_cap->src_f;
+ else
+ return -EINVAL;
+
+ if (s->flags & V4L2_SEL_FLAG_LE &&
+ !enclosed_rectangle(&rect, &s->r))
+ return -ERANGE;
+
+ if (s->flags & V4L2_SEL_FLAG_GE &&
+ !enclosed_rectangle(&s->r, &rect))
+ return -ERANGE;
+
+ s->r = rect;
+ spin_lock_irqsave(&isi_cap->slock, flags);
+ set_frame_crop(f, s->r.left, s->r.top, s->r.width,
+ s->r.height);
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+
+ return 0;
+}
+
+static int mxc_isi_cap_enum_framesizes(struct file *file, void *priv,
+ struct v4l2_frmsizeenum *fsize)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct device_node *parent;
+ struct v4l2_subdev *sd;
+ struct mxc_isi_fmt *fmt;
+ struct v4l2_subdev_frame_size_enum fse = {
+ .index = fsize->index,
+ .which = V4L2_SUBDEV_FORMAT_ACTIVE,
+ };
+ int ret;
+
+ fmt = mxc_isi_find_format(&fsize->pixel_format, NULL, 0);
+ if (!fmt || fmt->fourcc != fsize->pixel_format)
+ return -EINVAL;
+ fse.code = fmt->mbus_code;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd) {
+ v4l2_err(&isi_cap->sd, "Can't find subdev\n");
+ return -ENODEV;
+ }
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_size, NULL, &fse);
+ if (ret)
+ return ret;
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if ((of_device_is_compatible(parent, "fsl,imx8mn-isi")) &&
+ (fse.max_width > ISI_2K || fse.min_width > ISI_2K))
+ return -EINVAL;
+
+ 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_STEPWISE;
+ 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 mxc_isi_cap_enum_frameintervals(struct file *file, void *fh,
+ struct v4l2_frmivalenum *interval)
+{
+ struct mxc_isi_cap_dev *isi_cap = video_drvdata(file);
+ struct device_node *parent;
+ struct v4l2_subdev *sd;
+ struct mxc_isi_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 = mxc_isi_find_format(&interval->pixel_format, NULL, 0);
+ if (!fmt || fmt->fourcc != interval->pixel_format)
+ return -EINVAL;
+ fie.code = fmt->mbus_code;
+
+ sd = mxc_get_remote_subdev(&isi_cap->sd, __func__);
+ if (!sd)
+ return -EINVAL;
+
+ ret = v4l2_subdev_call(sd, pad, enum_frame_interval, NULL, &fie);
+ if (ret)
+ return ret;
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if (of_device_is_compatible(parent, "fsl,imx8mn-isi") &&
+ fie.width > ISI_2K)
+ return -EINVAL;
+
+ interval->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+ interval->discrete = fie.interval;
+
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops mxc_isi_capture_ioctl_ops = {
+ .vidioc_querycap = mxc_isi_cap_querycap,
+
+ .vidioc_enum_fmt_vid_cap = mxc_isi_cap_enum_fmt,
+ .vidioc_try_fmt_vid_cap_mplane = mxc_isi_cap_try_fmt_mplane,
+ .vidioc_s_fmt_vid_cap_mplane = mxc_isi_cap_s_fmt_mplane,
+ .vidioc_g_fmt_vid_cap_mplane = mxc_isi_cap_g_fmt_mplane,
+
+ .vidioc_reqbufs = vb2_ioctl_reqbufs,
+ .vidioc_querybuf = vb2_ioctl_querybuf,
+ .vidioc_qbuf = vb2_ioctl_qbuf,
+ .vidioc_dqbuf = vb2_ioctl_dqbuf,
+ .vidioc_expbuf = vb2_ioctl_expbuf,
+ .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ .vidioc_create_bufs = vb2_ioctl_create_bufs,
+
+ .vidioc_g_parm = mxc_isi_cap_g_parm,
+ .vidioc_s_parm = mxc_isi_cap_s_parm,
+
+ .vidioc_streamon = mxc_isi_cap_streamon,
+ .vidioc_streamoff = mxc_isi_cap_streamoff,
+
+ .vidioc_g_selection = mxc_isi_cap_g_selection,
+ .vidioc_s_selection = mxc_isi_cap_s_selection,
+
+ .vidioc_enum_framesizes = mxc_isi_cap_enum_framesizes,
+ .vidioc_enum_frameintervals = mxc_isi_cap_enum_frameintervals,
+};
+
+/* Capture subdev media entity operations */
+static int mxc_isi_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+
+ if (WARN_ON(!isi_cap))
+ return 0;
+
+ if (!(flags & MEDIA_LNK_FL_ENABLED))
+ return 0;
+
+ /* Add ISI source and sink pad link configuration */
+ if (local->flags & MEDIA_PAD_FL_SOURCE) {
+ switch (local->index) {
+ case MXC_ISI_SD_PAD_SOURCE_DC0:
+ case MXC_ISI_SD_PAD_SOURCE_DC1:
+ break;
+ case MXC_ISI_SD_PAD_SOURCE_MEM:
+ break;
+ default:
+ dev_err(&isi_cap->pdev->dev, "invalid source pad\n");
+ return -EINVAL;
+ }
+ } else if (local->flags & MEDIA_PAD_FL_SINK) {
+ switch (local->index) {
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC3:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC3:
+ case MXC_ISI_SD_PAD_SINK_HDMI:
+ case MXC_ISI_SD_PAD_SINK_DC0:
+ case MXC_ISI_SD_PAD_SINK_DC1:
+ case MXC_ISI_SD_PAD_SINK_MEM:
+ case MXC_ISI_SD_PAD_SINK_PARALLEL_CSI:
+ break;
+ default:
+ dev_err(&isi_cap->pdev->dev,
+ "%s invalid sink pad\n", __func__);
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static const struct media_entity_operations mxc_isi_sd_media_ops = {
+ .link_setup = mxc_isi_link_setup,
+};
+
+static int mxc_isi_subdev_enum_mbus_code(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_mbus_code_enum *code)
+{
+ return 0;
+}
+
+static int mxc_isi_subdev_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f;
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (fmt->pad) {
+ case MXC_ISI_SD_PAD_SOURCE_MEM:
+ case MXC_ISI_SD_PAD_SOURCE_DC0:
+ case MXC_ISI_SD_PAD_SOURCE_DC1:
+ f = &isi_cap->dst_f;
+ break;
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI0_VC3:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC0:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC1:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC2:
+ case MXC_ISI_SD_PAD_SINK_MIPI1_VC3:
+ case MXC_ISI_SD_PAD_SINK_HDMI:
+ case MXC_ISI_SD_PAD_SINK_DC0:
+ case MXC_ISI_SD_PAD_SINK_DC1:
+ case MXC_ISI_SD_PAD_SINK_MEM:
+ f = &isi_cap->src_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ v4l2_err(&isi_cap->sd,
+ "%s, Pad is not support now!\n", __func__);
+ return -1;
+ }
+
+ if (!WARN_ON(!f->fmt))
+ mf->code = f->fmt->mbus_code;
+
+ /* Source/Sink pads crop rectangle size */
+ mf->width = f->width;
+ mf->height = f->height;
+ mf->colorspace = V4L2_COLORSPACE_JPEG;
+
+ mutex_unlock(&isi_cap->lock);
+
+ return 0;
+}
+
+static int mxc_isi_subdev_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct device_node *parent;
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+ struct mxc_isi_frame *dst_f = &isi_cap->dst_f;
+ struct mxc_isi_fmt *out_fmt;
+ int i;
+
+ if (fmt->pad < MXC_ISI_SD_PAD_SOURCE_MEM &&
+ vb2_is_busy(&isi_cap->vb2_q))
+ return -EBUSY;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ out_fmt = &mxc_isi_out_formats[i];
+ if (mf->code == out_fmt->mbus_code)
+ break;
+ }
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ v4l2_err(&isi_cap->sd,
+ "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ parent = of_get_parent(isi_cap->pdev->dev.of_node);
+ if (of_device_is_compatible(parent, "fsl,imx8mn-isi") &&
+ mf->width > ISI_2K)
+ return -EINVAL;
+
+ mutex_lock(&isi_cap->lock);
+ /* update out put frame size and formate */
+ dst_f->fmt = &mxc_isi_out_formats[i];
+ set_frame_bounds(dst_f, mf->width, mf->height);
+ mutex_unlock(&isi_cap->lock);
+
+ dev_dbg(&isi_cap->pdev->dev, "pad%d: code: 0x%x, %dx%d",
+ fmt->pad, mf->code, mf->width, mf->height);
+
+ return 0;
+}
+
+static int mxc_isi_subdev_get_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f = &isi_cap->src_f;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_COMPOSE_BOUNDS:
+ f = &isi_cap->dst_f;
+ /* fall through */
+ case V4L2_SEL_TGT_CROP_BOUNDS:
+ r->width = f->o_width;
+ r->height = f->o_height;
+ r->left = 0;
+ r->top = 0;
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, cfg, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad);
+ f = &isi_cap->dst_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ sel->r = *try_sel;
+ } else {
+ r->left = f->h_off;
+ r->top = f->v_off;
+ r->width = f->width;
+ r->height = f->height;
+ }
+
+ dev_dbg(&isi_cap->pdev->dev,
+ "%s, target %#x: l:%d, t:%d, %dx%d, f_w: %d, f_h: %d",
+ __func__, sel->pad, r->left, r->top, r->width, r->height,
+ f->c_width, f->c_height);
+
+ mutex_unlock(&isi_cap->lock);
+ return 0;
+}
+
+static int mxc_isi_subdev_set_selection(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_selection *sel)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct mxc_isi_frame *f = &isi_cap->src_f;
+ struct v4l2_rect *r = &sel->r;
+ struct v4l2_rect *try_sel;
+ unsigned long flags;
+
+ mutex_lock(&isi_cap->lock);
+
+ switch (sel->target) {
+ case V4L2_SEL_TGT_CROP:
+ try_sel = v4l2_subdev_get_try_crop(sd, cfg, sel->pad);
+ break;
+ case V4L2_SEL_TGT_COMPOSE:
+ try_sel = v4l2_subdev_get_try_compose(sd, cfg, sel->pad);
+ f = &isi_cap->dst_f;
+ break;
+ default:
+ mutex_unlock(&isi_cap->lock);
+ return -EINVAL;
+ }
+
+ if (sel->which == V4L2_SUBDEV_FORMAT_TRY) {
+ *try_sel = sel->r;
+ } else {
+ spin_lock_irqsave(&isi_cap->slock, flags);
+ set_frame_crop(f, r->left, r->top, r->width, r->height);
+ spin_unlock_irqrestore(&isi_cap->slock, flags);
+ }
+
+ dev_dbg(&isi_cap->pdev->dev, "%s, target %#x: (%d,%d)/%dx%d", __func__,
+ sel->target, r->left, r->top, r->width, r->height);
+
+ mutex_unlock(&isi_cap->lock);
+
+ return 0;
+}
+
+static struct v4l2_subdev_pad_ops mxc_isi_subdev_pad_ops = {
+ .enum_mbus_code = mxc_isi_subdev_enum_mbus_code,
+ .get_selection = mxc_isi_subdev_get_selection,
+ .set_selection = mxc_isi_subdev_set_selection,
+ .get_fmt = mxc_isi_subdev_get_fmt,
+ .set_fmt = mxc_isi_subdev_set_fmt,
+};
+
+static struct v4l2_subdev_ops mxc_isi_subdev_ops = {
+ .pad = &mxc_isi_subdev_pad_ops,
+};
+
+static int mxc_isi_register_cap_device(struct mxc_isi_cap_dev *isi_cap,
+ struct v4l2_device *v4l2_dev)
+{
+ struct video_device *vdev = &isi_cap->vdev;
+ struct vb2_queue *q = &isi_cap->vb2_q;
+ int ret = -ENOMEM;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+ memset(vdev, 0, sizeof(*vdev));
+ snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", isi_cap->id);
+
+ vdev->fops = &mxc_isi_capture_fops;
+ vdev->ioctl_ops = &mxc_isi_capture_ioctl_ops;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->minor = -1;
+ vdev->release = video_device_release_empty;
+ vdev->queue = q;
+ vdev->lock = &isi_cap->lock;
+
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE;
+ video_set_drvdata(vdev, isi_cap);
+
+ INIT_LIST_HEAD(&isi_cap->out_pending);
+ INIT_LIST_HEAD(&isi_cap->out_active);
+ INIT_LIST_HEAD(&isi_cap->out_discard);
+
+ memset(q, 0, sizeof(*q));
+ q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ q->drv_priv = isi_cap;
+ q->ops = &mxc_cap_vb2_qops;
+ q->mem_ops = &vb2_dma_contig_memops;
+ q->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+ q->lock = &isi_cap->lock;
+
+ ret = vb2_queue_init(q);
+ if (ret)
+ goto err_free_ctx;
+
+ /* Default configuration */
+ isi_cap->dst_f.width = 1280;
+ isi_cap->dst_f.height = 800;
+ isi_cap->dst_f.fmt = &mxc_isi_out_formats[0];
+ isi_cap->src_f.fmt = isi_cap->dst_f.fmt;
+
+ isi_cap->cap_pad.flags = MEDIA_PAD_FL_SINK;
+ vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
+ ret = media_entity_pads_init(&vdev->entity, 1, &isi_cap->cap_pad);
+ if (ret)
+ goto err_free_ctx;
+
+ ret = mxc_isi_ctrls_create(isi_cap);
+ if (ret)
+ goto err_me_cleanup;
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret)
+ goto err_ctrl_free;
+
+ vdev->ctrl_handler = &isi_cap->ctrls.handler;
+ v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
+ vdev->name, video_device_node_name(vdev));
+
+ return 0;
+
+err_ctrl_free:
+ mxc_isi_ctrls_delete(isi_cap);
+err_me_cleanup:
+ media_entity_cleanup(&vdev->entity);
+err_free_ctx:
+ return ret;
+}
+
+static int mxc_isi_subdev_registered(struct v4l2_subdev *sd)
+{
+ struct mxc_isi_cap_dev *isi_cap = sd_to_cap_dev(sd);
+ int ret;
+
+ if (!isi_cap)
+ return -ENXIO;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ ret = mxc_isi_register_cap_device(isi_cap, sd->v4l2_dev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void mxc_isi_subdev_unregistered(struct v4l2_subdev *sd)
+{
+ struct mxc_isi_cap_dev *isi_cap = v4l2_get_subdevdata(sd);
+ struct video_device *vdev;
+
+ if (!isi_cap)
+ return;
+
+ dev_dbg(&isi_cap->pdev->dev, "%s\n", __func__);
+
+ mutex_lock(&isi_cap->lock);
+ vdev = &isi_cap->vdev;
+ if (video_is_registered(vdev)) {
+ video_unregister_device(vdev);
+ mxc_isi_ctrls_delete(isi_cap);
+ media_entity_cleanup(&vdev->entity);
+ }
+ mutex_unlock(&isi_cap->lock);
+}
+
+static const struct v4l2_subdev_internal_ops mxc_isi_capture_sd_internal_ops = {
+ .registered = mxc_isi_subdev_registered,
+ .unregistered = mxc_isi_subdev_unregistered,
+};
+
+static int isi_cap_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxc_isi_dev *mxc_isi;
+ struct mxc_isi_cap_dev *isi_cap;
+ struct v4l2_subdev *sd;
+ int ret;
+
+ isi_cap = devm_kzalloc(dev, sizeof(*isi_cap), GFP_KERNEL);
+ if (!isi_cap)
+ return -ENOMEM;
+
+ dev->parent = mxc_isi_dev_get_parent(pdev);
+ if (!dev->parent) {
+ dev_info(dev, "deferring %s device registration\n", dev_name(dev));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_isi = mxc_isi_get_hostdata(pdev);
+ if (!mxc_isi) {
+ dev_info(dev, "deferring %s device registration\n", dev_name(dev));
+ return -EPROBE_DEFER;
+ }
+
+ isi_cap->pdev = pdev;
+ isi_cap->id = mxc_isi->id;
+ mxc_isi->isi_cap = isi_cap;
+
+ spin_lock_init(&isi_cap->slock);
+ mutex_init(&isi_cap->lock);
+
+ sd = &isi_cap->sd;
+ v4l2_subdev_init(sd, &mxc_isi_subdev_ops);
+ snprintf(sd->name, sizeof(sd->name), "mxc_isi.%d", isi_cap->id);
+
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_PIXEL_FORMATTER;
+
+ /* ISI Sink pads */
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC2].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI0_VC3].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC2].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MIPI1_VC3].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC0].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_DC1].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_HDMI].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_MEM].flags = MEDIA_PAD_FL_SINK;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SINK_PARALLEL_CSI].flags = MEDIA_PAD_FL_SINK;
+
+ /* ISI source pads */
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_MEM].flags = MEDIA_PAD_FL_SOURCE;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC0].flags = MEDIA_PAD_FL_SOURCE;
+ isi_cap->sd_pads[MXC_ISI_SD_PAD_SOURCE_DC1].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&sd->entity, MXC_ISI_SD_PADS_NUM, isi_cap->sd_pads);
+ if (ret)
+ return ret;
+
+ sd->entity.ops = &mxc_isi_sd_media_ops;
+ sd->internal_ops = &mxc_isi_capture_sd_internal_ops;
+
+ v4l2_set_subdevdata(sd, isi_cap);
+ platform_set_drvdata(pdev, isi_cap);
+
+ pm_runtime_enable(dev);
+ return 0;
+}
+
+static int isi_cap_remove(struct platform_device *pdev)
+{
+ struct mxc_isi_cap_dev *isi_cap = platform_get_drvdata(pdev);
+ struct v4l2_subdev *sd = &isi_cap->sd;
+
+ v4l2_device_unregister_subdev(sd);
+ media_entity_cleanup(&sd->entity);
+ v4l2_set_subdevdata(sd, NULL);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id isi_cap_of_match[] = {
+ {.compatible = "imx-isi-capture",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, isi_cap_of_match);
+
+static struct platform_driver isi_cap_driver = {
+ .probe = isi_cap_probe,
+ .remove = isi_cap_remove,
+ .driver = {
+ .of_match_table = isi_cap_of_match,
+ .name = "isi-capture",
+ },
+};
+module_platform_driver(isi_cap_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface Capture driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI Capture");
+MODULE_VERSION("1.0");
diff --git a/drivers/staging/media/imx/imx8-isi-core.c b/drivers/staging/media/imx/imx8-isi-core.c
new file mode 100644
index 000000000000..ce59ab414aa3
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.c
@@ -0,0 +1,616 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+
+#include "imx8-isi-hw.h"
+
+static const struct soc_device_attribute imx8_soc[] = {
+ {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.1",
+ }, {
+ .soc_id = "i.MX8QXP",
+ .revision = "1.2",
+ }, {
+ .soc_id = "i.MX8QM",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8QM",
+ .revision = "1.1",
+ }, {
+ .soc_id = "i.MX8MN",
+ .revision = "1.0",
+ }, {
+ .soc_id = "i.MX8MP",
+ },
+};
+
+static const struct of_device_id mxc_isi_of_match[];
+
+static irqreturn_t mxc_isi_irq_handler(int irq, void *priv)
+{
+ struct mxc_isi_dev *mxc_isi = priv;
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ u32 status;
+
+ spin_lock(&mxc_isi->slock);
+
+ status = mxc_isi_get_irq_status(mxc_isi);
+ mxc_isi->status = status;
+ mxc_isi_clean_irq_status(mxc_isi, status);
+
+ if (status & CHNL_STS_FRM_STRD_MASK) {
+ if (mxc_isi->m2m_enabled)
+ mxc_isi_m2m_frame_write_done(mxc_isi);
+ else
+ mxc_isi_cap_frame_write_done(mxc_isi);
+ }
+
+ if (status & (CHNL_STS_AXI_WR_ERR_Y_MASK |
+ CHNL_STS_AXI_WR_ERR_U_MASK |
+ CHNL_STS_AXI_WR_ERR_V_MASK))
+ dev_dbg(dev, "%s, IRQ AXI Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->panic_y_buf_en.mask |
+ ier_reg->panic_u_buf_en.mask |
+ ier_reg->panic_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ Panic OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->oflw_y_buf_en.mask |
+ ier_reg->oflw_u_buf_en.mask |
+ ier_reg->oflw_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ OFLW Error stat=0x%X\n", __func__, status);
+
+ if (status & (ier_reg->excs_oflw_y_buf_en.mask |
+ ier_reg->excs_oflw_u_buf_en.mask |
+ ier_reg->excs_oflw_v_buf_en.mask))
+ dev_dbg(dev, "%s, IRQ EXCS OFLW Error stat=0x%X\n", __func__, status);
+
+ spin_unlock(&mxc_isi->slock);
+ return IRQ_HANDLED;
+}
+
+static int disp_mix_sft_rstn(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ return ret;
+}
+
+static int disp_mix_clks_enable(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ return ret;
+}
+
+static int mxc_imx8_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+
+ mxc_isi->clk = devm_clk_get(dev, NULL);
+
+ if (IS_ERR(mxc_isi->clk)) {
+ dev_err(dev, "failed to get isi clk\n");
+ return PTR_ERR(mxc_isi->clk);
+ }
+
+ return 0;
+}
+
+static int mxc_imx8_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(mxc_isi->clk);
+ if (ret < 0) {
+ dev_err(dev, "%s, enable clk error\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mxc_imx8_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ clk_disable_unprepare(mxc_isi->clk);
+}
+
+static struct mxc_isi_dev_ops mxc_imx8_clk_ops = {
+ .clk_get = mxc_imx8_clk_get,
+ .clk_enable = mxc_imx8_clk_enable,
+ .clk_disable = mxc_imx8_clk_disable,
+};
+
+static struct mxc_isi_chan_src mxc_imx8_chan_src = {
+ .src_dc0 = 0,
+ .src_dc1 = 1,
+ .src_mipi0 = 2,
+ .src_mipi1 = 3,
+ .src_hdmi = 4,
+ .src_csi = 4,
+ .src_mem = 5,
+};
+
+/* For i.MX8QM/QXP B0 ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v0 = {
+ .oflw_y_buf_en = { .offset = 16, .mask = 0x10000 },
+ .oflw_u_buf_en = { .offset = 19, .mask = 0x80000 },
+ .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
+
+ .excs_oflw_y_buf_en = { .offset = 17, .mask = 0x20000 },
+ .excs_oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
+ .excs_oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
+
+ .panic_y_buf_en = {.offset = 18, .mask = 0x40000 },
+ .panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
+ .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
+};
+
+/* Panic will assert when the buffers are 50% full */
+static struct mxc_isi_set_thd mxc_imx8_isi_thd_v0 = {
+ .panic_set_thd_y = { .mask = 0x03, .offset = 0, .threshold = 0x2 },
+ .panic_set_thd_u = { .mask = 0x18, .offset = 3, .threshold = 0x2 },
+ .panic_set_thd_v = { .mask = 0xC0, .offset = 6, .threshold = 0x2 },
+};
+
+/* For i.MX8QXP C0 and i.MX8MN ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = {
+ .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 },
+ .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 },
+ .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 },
+
+ .panic_y_buf_en = {.offset = 20, .mask = 0x100000 },
+ .panic_u_buf_en = {.offset = 22, .mask = 0x400000 },
+ .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 },
+};
+
+/* For i.MX8MP ISI IER version */
+static struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = {
+ .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 },
+ .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 },
+ .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 },
+
+ .panic_y_buf_en = {.offset = 19, .mask = 0x80000 },
+ .panic_u_buf_en = {.offset = 21, .mask = 0x200000 },
+ .panic_v_buf_en = {.offset = 23, .mask = 0x800000 },
+};
+
+/* Panic will assert when the buffers are 50% full */
+static struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = {
+ .panic_set_thd_y = { .mask = 0x0000F, .offset = 0, .threshold = 0x7 },
+ .panic_set_thd_u = { .mask = 0x00F00, .offset = 8, .threshold = 0x7 },
+ .panic_set_thd_v = { .mask = 0xF0000, .offset = 16, .threshold = 0x7 },
+};
+
+static struct mxc_isi_plat_data mxc_imx8_data = {
+ .ops = &mxc_imx8_clk_ops,
+ .chan_src = &mxc_imx8_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v0,
+ .set_thd = &mxc_imx8_isi_thd_v0,
+};
+
+static int mxc_imx8mn_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+
+ mxc_isi->clk_disp_axi = devm_clk_get(dev, "disp_axi");
+ if (IS_ERR(mxc_isi->clk_disp_axi)) {
+ dev_err(dev, "failed to get disp_axi clk\n");
+ return PTR_ERR(mxc_isi->clk_disp_axi);
+ }
+
+ mxc_isi->clk_disp_apb = devm_clk_get(dev, "disp_apb");
+ if (IS_ERR(mxc_isi->clk_disp_apb)) {
+ dev_err(dev, "failed to get disp_apb clk\n");
+ return PTR_ERR(mxc_isi->clk_disp_apb);
+ }
+
+ mxc_isi->clk_root_disp_axi = devm_clk_get(dev, "disp_axi_root");
+ if (IS_ERR(mxc_isi->clk_root_disp_axi)) {
+ dev_err(dev, "failed to get disp axi root clk\n");
+ return PTR_ERR(mxc_isi->clk_root_disp_axi);
+ }
+
+ mxc_isi->clk_root_disp_apb = devm_clk_get(dev, "disp_apb_root");
+ if (IS_ERR(mxc_isi->clk_root_disp_apb)) {
+ dev_err(dev, "failed to get disp apb root clk\n");
+ return PTR_ERR(mxc_isi->clk_root_disp_apb);
+ }
+
+ return 0;
+}
+
+static int mxc_imx8mn_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(mxc_isi->clk_disp_axi);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable axi clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_disp_apb);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable abp clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_root_disp_axi);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable axi root clk error\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(mxc_isi->clk_root_disp_apb);
+ if (ret < 0) {
+ dev_err(dev, "prepare and enable apb root clk error\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mxc_imx8mn_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ clk_disable_unprepare(mxc_isi->clk_root_disp_axi);
+ clk_disable_unprepare(mxc_isi->clk_root_disp_apb);
+ clk_disable_unprepare(mxc_isi->clk_disp_axi);
+ clk_disable_unprepare(mxc_isi->clk_disp_apb);
+}
+
+static struct mxc_isi_chan_src mxc_imx8mn_chan_src = {
+ .src_mipi0 = 0,
+ .src_mipi1 = 1,
+ /* For i.MX8MP */
+ .src_mem = 2,
+};
+
+static struct mxc_isi_dev_ops mxc_imx8mn_clk_ops = {
+ .clk_get = mxc_imx8mn_clk_get,
+ .clk_enable = mxc_imx8mn_clk_enable,
+ .clk_disable = mxc_imx8mn_clk_disable,
+};
+
+static struct mxc_isi_plat_data mxc_imx8mn_data = {
+ .ops = &mxc_imx8mn_clk_ops,
+ .chan_src = &mxc_imx8mn_chan_src,
+ .ier_reg = &mxc_imx8_isi_ier_v1,
+ .set_thd = &mxc_imx8_isi_thd_v1,
+};
+
+static int mxc_isi_parse_dt(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct device_node *node = dev->of_node;
+ int ret = 0;
+
+ mxc_isi->id = of_alias_get_id(node, "isi");
+ mxc_isi->chain_buf = of_property_read_bool(node, "fsl,chain_buf");
+
+ ret = of_property_read_u32_array(node, "interface", mxc_isi->interface, 3);
+ if (ret < 0)
+ return ret;
+
+ dev_dbg(dev, "%s, isi_%d,interface(%d, %d, %d)\n", __func__,
+ mxc_isi->id,
+ mxc_isi->interface[0],
+ mxc_isi->interface[1],
+ mxc_isi->interface[2]);
+ return 0;
+}
+
+static int mxc_isi_clk_get(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops && !ops->clk_get)
+ return -EINVAL;
+
+ return ops->clk_get(mxc_isi);
+}
+
+static int mxc_isi_clk_enable(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops && !ops->clk_enable)
+ return -EINVAL;
+
+ return ops->clk_enable(mxc_isi);
+}
+
+static void mxc_isi_clk_disable(struct mxc_isi_dev *mxc_isi)
+{
+ const struct mxc_isi_dev_ops *ops = mxc_isi->pdata->ops;
+
+ if (!ops && !ops->clk_disable)
+ return;
+
+ ops->clk_disable(mxc_isi);
+}
+
+static int mxc_isi_of_parse_resets(struct mxc_isi_dev *mxc_isi)
+{
+ int ret;
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *parent, *child;
+ struct of_phandle_args args;
+ struct reset_control *rstc;
+ const char *compat;
+ uint32_t len, rstc_num = 0;
+
+ ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ parent = args.np;
+ for_each_child_of_node(parent, child) {
+ compat = of_get_property(child, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ rstc = of_reset_control_array_get(child, false, false, true);
+ if (IS_ERR(rstc))
+ continue;
+
+ len = strlen(compat);
+ if (!of_compat_cmp("isi,soft-resetn", compat, len)) {
+ mxc_isi->soft_resetn = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("isi,clk-enable", compat, len)) {
+ mxc_isi->clk_enable = rstc;
+ rstc_num++;
+ } else {
+ dev_warn(dev, "invalid isi reset node: %s\n", compat);
+ }
+ }
+
+ if (!rstc_num) {
+ dev_err(dev, "no invalid reset control exists\n");
+ return -EINVAL;
+ }
+
+ of_node_put(parent);
+ return 0;
+}
+
+static int mxc_isi_soc_match(struct mxc_isi_dev *mxc_isi,
+ const struct soc_device_attribute *data)
+{
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ struct mxc_isi_set_thd *set_thd = mxc_isi->pdata->set_thd;
+ const struct soc_device_attribute *match;
+
+ match = soc_device_match(data);
+ if (!match)
+ return -EINVAL;
+
+ mxc_isi->buf_active_reverse = false;
+
+ if (!strcmp(match->soc_id, "i.MX8QXP") ||
+ !strcmp(match->soc_id, "i.MX8QM")) {
+ /* Chip C0 */
+ if (strcmp(match->revision, "1.1") > 0) {
+ memcpy(ier_reg, &mxc_imx8_isi_ier_v1, sizeof(*ier_reg));
+ memcpy(set_thd, &mxc_imx8_isi_thd_v1, sizeof(*set_thd));
+ mxc_isi->buf_active_reverse = true;
+ }
+ } else if (!strcmp(match->soc_id, "i.MX8MP")) {
+ memcpy(ier_reg, &mxc_imx8_isi_ier_v2, sizeof(*ier_reg));
+ mxc_isi->buf_active_reverse = true;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct mxc_isi_dev *mxc_isi;
+ struct resource *res;
+ const struct of_device_id *of_id;
+ int ret = 0;
+
+
+ mxc_isi = devm_kzalloc(dev, sizeof(*mxc_isi), GFP_KERNEL);
+ if (!mxc_isi)
+ return -ENOMEM;
+
+ mxc_isi->pdev = pdev;
+ of_id = of_match_node(mxc_isi_of_match, dev->of_node);
+ if (!of_id)
+ return -EINVAL;
+
+ mxc_isi->pdata = of_id->data;
+ if (!mxc_isi->pdata) {
+ dev_err(dev, "Can't get platform device data\n");
+ return -EINVAL;
+ }
+
+ ret = mxc_isi_soc_match(mxc_isi, imx8_soc);
+ if (ret < 0) {
+ dev_err(dev, "Can't match soc version\n");
+ return ret;
+ }
+
+ ret = mxc_isi_parse_dt(mxc_isi);
+ if (ret < 0)
+ return ret;
+
+ if (mxc_isi->id >= MXC_ISI_MAX_DEVS || mxc_isi->id < 0) {
+ dev_err(dev, "Invalid driver data or device id (%d)\n",
+ mxc_isi->id);
+ return -EINVAL;
+ }
+
+ spin_lock_init(&mxc_isi->slock);
+ mutex_init(&mxc_isi->lock);
+ atomic_set(&mxc_isi->usage_count, 0);
+
+ if (!of_property_read_bool(dev->of_node, "no-reset-control")) {
+ ret = mxc_isi_of_parse_resets(mxc_isi);
+ if (ret) {
+ dev_warn(dev, "Can not parse reset control\n");
+ return ret;
+ }
+ }
+
+ ret = mxc_isi_clk_get(mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "ISI_%d get clocks fail\n", mxc_isi->id);
+ return ret;
+ }
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ mxc_isi->regs = devm_ioremap_resource(dev, res);
+ if (IS_ERR(mxc_isi->regs)) {
+ dev_err(dev, "Failed to get ISI register map\n");
+ return PTR_ERR(mxc_isi->regs);
+ }
+
+ ret = mxc_isi_clk_enable(mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "ISI_%d enable clocks fail\n", mxc_isi->id);
+ return ret;
+ }
+ disp_mix_sft_rstn(mxc_isi->soft_resetn, false);
+ disp_mix_clks_enable(mxc_isi->clk_enable, true);
+
+ mxc_isi_clean_registers(mxc_isi);
+
+ res = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
+ if (!res) {
+ dev_err(dev, "Failed to get IRQ resource\n");
+ goto err;
+ }
+ ret = devm_request_irq(dev, res->start, mxc_isi_irq_handler,
+ 0, dev_name(dev), mxc_isi);
+ if (ret < 0) {
+ dev_err(dev, "failed to install irq (%d)\n", ret);
+ goto err;
+ }
+
+ mxc_isi_channel_set_chain_buf(mxc_isi);
+
+ ret = of_platform_populate(dev->of_node, NULL, NULL, dev);
+ if (ret < 0)
+ dev_warn(dev, "Populate child platform device fail\n");
+
+ mxc_isi_clk_disable(mxc_isi);
+
+ platform_set_drvdata(pdev, mxc_isi);
+ pm_runtime_enable(dev);
+
+ dev_info(dev, "mxc_isi.%d registered successfully\n", mxc_isi->id);
+ return 0;
+
+err:
+ disp_mix_clks_enable(mxc_isi->clk_enable, false);
+ disp_mix_sft_rstn(mxc_isi->soft_resetn, true);
+ mxc_isi_clk_disable(mxc_isi);
+ return -ENXIO;
+}
+
+static int mxc_isi_remove(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+
+ of_platform_depopulate(dev);
+ pm_runtime_disable(dev);
+
+ return 0;
+}
+
+static int mxc_isi_pm_suspend(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+
+ if (mxc_isi->is_streaming) {
+ dev_warn(dev, "running, prevent entering suspend.\n");
+ return -EAGAIN;
+ }
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int mxc_isi_pm_resume(struct device *dev)
+{
+ return pm_runtime_force_resume(dev);
+}
+
+static int mxc_isi_runtime_suspend(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+
+ disp_mix_clks_enable(mxc_isi->clk_enable, false);
+ mxc_isi_clk_disable(mxc_isi);
+
+ return 0;
+}
+
+static int mxc_isi_runtime_resume(struct device *dev)
+{
+ struct mxc_isi_dev *mxc_isi = dev_get_drvdata(dev);
+ int ret;
+
+ ret = mxc_isi_clk_enable(mxc_isi);
+ if (ret) {
+ dev_err(dev, "%s clk enable fail\n", __func__);
+ return ret;
+ }
+ disp_mix_sft_rstn(mxc_isi->soft_resetn, false);
+ disp_mix_clks_enable(mxc_isi->clk_enable, true);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mxc_isi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mxc_isi_pm_suspend, mxc_isi_pm_resume)
+ SET_RUNTIME_PM_OPS(mxc_isi_runtime_suspend, mxc_isi_runtime_resume, NULL)
+};
+
+static const struct of_device_id mxc_isi_of_match[] = {
+ {.compatible = "fsl,imx8-isi", .data = &mxc_imx8_data },
+ {.compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxc_isi_of_match);
+
+static struct platform_driver mxc_isi_driver = {
+ .probe = mxc_isi_probe,
+ .remove = mxc_isi_remove,
+ .driver = {
+ .of_match_table = mxc_isi_of_match,
+ .name = MXC_ISI_DRIVER_NAME,
+ .pm = &mxc_isi_pm_ops,
+ }
+};
+module_platform_driver(mxc_isi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Subsystem driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI");
+MODULE_VERSION("1.0");
diff --git a/drivers/staging/media/imx/imx8-isi-core.h b/drivers/staging/media/imx/imx8-isi-core.h
new file mode 100644
index 000000000000..dbf9242873c2
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-core.h
@@ -0,0 +1,409 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019-2020 NXP
+ */
+
+#ifndef __MXC_ISI_CORE_H__
+#define __MXC_ISI_CORE_H__
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/mfd/syscon.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/slab.h>
+#include <linux/clk.h>
+#include <media/media-device.h>
+#include <media/media-entity.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+#include <linux/regmap.h>
+#include <linux/reset.h>
+#include <linux/sys_soc.h>
+
+#include "imx8-common.h"
+
+#define MXC_ISI_DRIVER_NAME "mxc-isi"
+#define MXC_ISI_CAPTURE "mxc-isi-cap"
+#define MXC_ISI_M2M "mxc-isi-m2m"
+#define MXC_MAX_PLANES 3
+
+struct mxc_isi_dev;
+
+enum mxc_isi_out_fmt {
+ MXC_ISI_OUT_FMT_RGBA32 = 0x0,
+ MXC_ISI_OUT_FMT_ABGR32,
+ MXC_ISI_OUT_FMT_ARGB32,
+ MXC_ISI_OUT_FMT_RGBX32,
+ MXC_ISI_OUT_FMT_XBGR32,
+ MXC_ISI_OUT_FMT_XRGB32,
+ MXC_ISI_OUT_FMT_RGB32P,
+ MXC_ISI_OUT_FMT_BGR32P,
+ MXC_ISI_OUT_FMT_A2BGR10,
+ MXC_ISI_OUT_FMT_A2RGB10,
+ MXC_ISI_OUT_FMT_RGB565,
+ MXC_ISI_OUT_FMT_RAW8,
+ MXC_ISI_OUT_FMT_RAW10,
+ MXC_ISI_OUT_FMT_RAW10P,
+ MXC_ISI_OUT_FMT_RAW12,
+ MXC_ISI_OUT_FMT_RAW16,
+ MXC_ISI_OUT_FMT_YUV444_1P8P,
+ MXC_ISI_OUT_FMT_YUV444_2P8P,
+ MXC_ISI_OUT_FMT_YUV444_3P8P,
+ MXC_ISI_OUT_FMT_YUV444_1P8,
+ MXC_ISI_OUT_FMT_YUV444_1P10,
+ MXC_ISI_OUT_FMT_YUV444_2P10,
+ MXC_ISI_OUT_FMT_YUV444_3P10,
+ MXC_ISI_OUT_FMT_YUV444_1P10P = 0x18,
+ MXC_ISI_OUT_FMT_YUV444_2P10P,
+ MXC_ISI_OUT_FMT_YUV444_3P10P,
+ MXC_ISI_OUT_FMT_YUV444_1P12 = 0x1C,
+ MXC_ISI_OUT_FMT_YUV444_2P12,
+ MXC_ISI_OUT_FMT_YUV444_3P12,
+ MXC_ISI_OUT_FMT_YUV422_1P8P = 0x20,
+ MXC_ISI_OUT_FMT_YUV422_2P8P,
+ MXC_ISI_OUT_FMT_YUV422_3P8P,
+ MXC_ISI_OUT_FMT_YUV422_1P10 = 0x24,
+ MXC_ISI_OUT_FMT_YUV422_2P10,
+ MXC_ISI_OUT_FMT_YUV422_3P10,
+ MXC_ISI_OUT_FMT_YUV422_1P10P = 0x28,
+ MXC_ISI_OUT_FMT_YUV422_2P10P,
+ MXC_ISI_OUT_FMT_YUV422_3P10P,
+ MXC_ISI_OUT_FMT_YUV422_1P12 = 0x2C,
+ MXC_ISI_OUT_FMT_YUV422_2P12,
+ MXC_ISI_OUT_FMT_YUV422_3P12,
+ MXC_ISI_OUT_FMT_YUV420_2P8P = 0x31,
+ MXC_ISI_OUT_FMT_YUV420_3P8P,
+ MXC_ISI_OUT_FMT_YUV420_2P10 = 0x35,
+ MXC_ISI_OUT_FMT_YUV420_3P10,
+ MXC_ISI_OUT_FMT_YUV420_2P10P = 0x39,
+ MXC_ISI_OUT_FMT_YUV420_3P10P,
+ MXC_ISI_OUT_FMT_YUV420_2P12 = 0x3D,
+ MXC_ISI_OUT_FMT_YUV420_3P12,
+};
+
+enum mxc_isi_in_fmt {
+ MXC_ISI_IN_FMT_BGR8P = 0x0,
+};
+
+enum mxc_isi_m2m_in_fmt {
+ MXC_ISI_M2M_IN_FMT_BGR8P = 0x0,
+ MXC_ISI_M2M_IN_FMT_RGB8P,
+ MXC_ISI_M2M_IN_FMT_XRGB8,
+ MXC_ISI_M2M_IN_FMT_RGBX8,
+ MXC_ISI_M2M_IN_FMT_XBGR8,
+ MXC_ISI_M2M_IN_FMT_RGB565,
+ MXC_ISI_M2M_IN_FMT_A2BGR10,
+ MXC_ISI_M2M_IN_FMT_A2RGB10,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P8P,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P10,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P10P,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P12,
+ MXC_ISI_M2M_IN_FMT_YUV444_1P8,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P8P,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P10,
+ MXC_ISI_M2M_IN_FMT_YUV422_1P10P,
+};
+
+struct mxc_isi_fmt {
+ char *name;
+ u32 mbus_code;
+ u32 fourcc;
+ u32 color;
+ u16 memplanes;
+ u16 colplanes;
+ u8 colorspace;
+ u8 depth[MXC_MAX_PLANES];
+ u16 mdataplanes;
+ u16 flags;
+};
+
+struct mxc_isi_ctrls {
+ struct v4l2_ctrl_handler handler;
+ struct v4l2_ctrl *hflip;
+ struct v4l2_ctrl *vflip;
+ struct v4l2_ctrl *alpha;
+ struct v4l2_ctrl *num_cap_buf;
+ struct v4l2_ctrl *num_out_buf;
+ bool ready;
+};
+
+/**
+ * struct addr - physical address set for DMA
+ * @y: luminance plane physical address
+ * @cb: Cb plane physical address
+ * @cr: Cr plane physical address
+ */
+struct frame_addr {
+ u32 y;
+ u32 cb;
+ u32 cr;
+};
+
+/**
+ * struct mxc_isi_frame - source/target frame properties
+ * o_width: original image width from sensor
+ * o_height: original image height from sensor
+ * c_width: crop image width set by g_selection
+ * c_height: crop image height set by g_selection
+ * h_off: crop horizontal pixel offset
+ * v_off: crop vertical pixel offset
+ * width: out image pixel width
+ * height: out image pixel weight
+ * bytesperline: bytesperline value for each plane
+ * paddr: image frame buffer physical addresses
+ * fmt: color format pointer
+ */
+struct mxc_isi_frame {
+ u32 o_width;
+ u32 o_height;
+ u32 c_width;
+ u32 c_height;
+ u32 h_off;
+ u32 v_off;
+ u32 width;
+ u32 height;
+ unsigned int sizeimage[MXC_MAX_PLANES];
+ unsigned int bytesperline[MXC_MAX_PLANES];
+ struct mxc_isi_fmt *fmt;
+};
+
+struct mxc_isi_roi_alpha {
+ u8 alpha;
+ struct v4l2_rect rect;
+};
+
+struct mxc_isi_buffer {
+ struct vb2_v4l2_buffer v4l2_buf;
+ struct list_head list;
+ struct frame_addr paddr;
+ enum mxc_isi_buf_id id;
+ bool discard;
+};
+
+struct mxc_isi_m2m_dev {
+ struct platform_device *pdev;
+
+ struct video_device vdev;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct v4l2_fh fh;
+ struct v4l2_pix_format_mplane pix;
+
+ struct list_head out_active;
+ struct mxc_isi_ctrls ctrls;
+
+ struct mxc_isi_frame src_f;
+ struct mxc_isi_frame dst_f;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ unsigned int aborting;
+ unsigned int frame_count;
+
+ u32 req_cap_buf_num;
+ u32 req_out_buf_num;
+
+ u8 id;
+};
+
+struct mxc_isi_ctx {
+ struct mxc_isi_m2m_dev *isi_m2m;
+ struct v4l2_fh fh;
+};
+
+struct mxc_isi_chan_src {
+ u32 src_dc0;
+ u32 src_dc1;
+ u32 src_mipi0;
+ u32 src_mipi1;
+ u32 src_hdmi;
+ u32 src_csi;
+ u32 src_mem;
+};
+
+struct mxc_isi_reg {
+ u32 offset;
+ u32 mask;
+};
+
+struct mxc_isi_ier_reg {
+ /* Overflow Y/U/V triggier enable*/
+ struct mxc_isi_reg oflw_y_buf_en;
+ struct mxc_isi_reg oflw_u_buf_en;
+ struct mxc_isi_reg oflw_v_buf_en;
+
+ /* Excess overflow Y/U/V triggier enable*/
+ struct mxc_isi_reg excs_oflw_y_buf_en;
+ struct mxc_isi_reg excs_oflw_u_buf_en;
+ struct mxc_isi_reg excs_oflw_v_buf_en;
+
+ /* Panic Y/U/V triggier enable*/
+ struct mxc_isi_reg panic_y_buf_en;
+ struct mxc_isi_reg panic_v_buf_en;
+ struct mxc_isi_reg panic_u_buf_en;
+};
+
+struct mxc_isi_dev_ops {
+ int (*clk_get)(struct mxc_isi_dev *mxc_isi);
+ int (*clk_enable)(struct mxc_isi_dev *mxc_isi);
+ void (*clk_disable)(struct mxc_isi_dev *mxc_isi);
+};
+
+struct mxc_isi_panic_thd {
+ u32 mask;
+ u32 offset;
+ u32 threshold;
+};
+
+struct mxc_isi_set_thd {
+ struct mxc_isi_panic_thd panic_set_thd_y;
+ struct mxc_isi_panic_thd panic_set_thd_u;
+ struct mxc_isi_panic_thd panic_set_thd_v;
+};
+
+struct mxc_isi_plat_data {
+ struct mxc_isi_dev_ops *ops;
+ struct mxc_isi_chan_src *chan_src;
+ struct mxc_isi_ier_reg *ier_reg;
+ struct mxc_isi_set_thd *set_thd;
+};
+
+struct mxc_isi_cap_dev {
+ struct v4l2_subdev sd;
+ struct video_device vdev;
+ struct v4l2_fh fh;
+ struct vb2_queue vb2_q;
+ struct v4l2_pix_format_mplane pix;
+
+ struct mxc_isi_dev *mxc_isi;
+ struct platform_device *pdev;
+ struct mxc_isi_ctrls ctrls;
+ struct mxc_isi_buffer buf_discard[2];
+
+ struct media_pad cap_pad;
+ struct media_pad sd_pads[MXC_ISI_SD_PADS_NUM];
+
+ struct list_head out_pending;
+ struct list_head out_active;
+ struct list_head out_discard;
+
+ struct mxc_isi_frame src_f;
+ struct mxc_isi_frame dst_f;
+
+ u32 frame_count;
+ u32 id;
+ bool is_link_setup;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ /* dirty buffer */
+ size_t discard_size[MXC_MAX_PLANES];
+ void *discard_buffer[MXC_MAX_PLANES];
+ dma_addr_t discard_buffer_dma[MXC_MAX_PLANES];
+};
+
+struct mxc_isi_dev {
+ /* Pointer to isi capture child device driver data */
+ struct mxc_isi_cap_dev *isi_cap;
+
+ /* Pointer to isi m2m child device driver data */
+ struct mxc_isi_m2m_dev *isi_m2m;
+
+ struct platform_device *pdev;
+
+ /* clk for imx8qxp/qm platform */
+ struct clk *clk;
+
+ /* clks for imx8mn platform */
+ struct clk *clk_disp_axi;
+ struct clk *clk_disp_apb;
+ struct clk *clk_root_disp_axi;
+ struct clk *clk_root_disp_apb;
+
+ const struct mxc_isi_plat_data *pdata;
+
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+
+ struct mutex lock;
+ spinlock_t slock;
+
+ void __iomem *regs;
+
+ u8 chain_buf;
+ u8 alpha;
+ bool m2m_enabled;
+ bool buf_active_reverse;
+
+ /* manage share ISI channel resource */
+ atomic_t usage_count;
+
+ /* scale factor */
+ u32 xfactor;
+ u32 yfactor;
+ u32 pre_dec_x;
+ u32 pre_dec_y;
+
+ u32 status;
+
+ u32 interface[MAX_PORTS];
+ int id;
+
+ unsigned int hflip:1;
+ unsigned int vflip:1;
+ unsigned int cscen:1;
+ unsigned int scale:1;
+ unsigned int alphaen:1;
+ unsigned int crop:1;
+ unsigned int deinterlace:1;
+ unsigned int is_streaming:1;
+};
+
+static inline void set_frame_bounds(struct mxc_isi_frame *f,
+ u32 width, u32 height)
+{
+ f->o_width = width;
+ f->o_height = height;
+ f->c_width = width;
+ f->c_height = height;
+ f->width = width;
+ f->height = height;
+}
+
+static inline void set_frame_out(struct mxc_isi_frame *f,
+ u32 width, u32 height)
+{
+ f->c_width = width;
+ f->c_height = height;
+ f->width = width;
+ f->height = height;
+}
+
+static inline void set_frame_crop(struct mxc_isi_frame *f,
+ u32 left, u32 top, u32 width, u32 height)
+{
+ f->h_off = left;
+ f->v_off = top;
+ f->c_width = width;
+ f->c_height = height;
+}
+#endif /* __MXC_ISI_CORE_H__ */
diff --git a/drivers/staging/media/imx/imx8-isi-hw.c b/drivers/staging/media/imx/imx8-isi-hw.c
new file mode 100644
index 000000000000..3c8a72592d11
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.c
@@ -0,0 +1,833 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+#include <dt-bindings/pinctrl/pads-imx8qxp.h>
+
+#include <linux/module.h>
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface Hardware driver");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("1.0");
+
+#define ISI_DOWNSCALE_THRESHOLD 0x4000
+
+#ifdef DEBUG
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi)
+{
+ struct device *dev = &mxc_isi->pdev->dev;
+ struct {
+ u32 offset;
+ const char *const name;
+ } registers[] = {
+ { 0x00, "CHNL_CTRL" },
+ { 0x04, "CHNL_IMG_CTRL" },
+ { 0x08, "CHNL_OUT_BUF_CTRL" },
+ { 0x0C, "CHNL_IMG_CFG" },
+ { 0x10, "CHNL_IER" },
+ { 0x14, "CHNL_STS" },
+ { 0x18, "CHNL_SCALE_FACTOR" },
+ { 0x1C, "CHNL_SCALE_OFFSET" },
+ { 0x20, "CHNL_CROP_ULC" },
+ { 0x24, "CHNL_CROP_LRC" },
+ { 0x28, "CHNL_CSC_COEFF0" },
+ { 0x2C, "CHNL_CSC_COEFF1" },
+ { 0x30, "CHNL_CSC_COEFF2" },
+ { 0x34, "CHNL_CSC_COEFF3" },
+ { 0x38, "CHNL_CSC_COEFF4" },
+ { 0x3C, "CHNL_CSC_COEFF5" },
+ { 0x40, "CHNL_ROI_0_ALPHA" },
+ { 0x44, "CHNL_ROI_0_ULC" },
+ { 0x48, "CHNL_ROI_0_LRC" },
+ { 0x4C, "CHNL_ROI_1_ALPHA" },
+ { 0x50, "CHNL_ROI_1_ULC" },
+ { 0x54, "CHNL_ROI_1_LRC" },
+ { 0x58, "CHNL_ROI_2_ALPHA" },
+ { 0x5C, "CHNL_ROI_2_ULC" },
+ { 0x60, "CHNL_ROI_2_LRC" },
+ { 0x64, "CHNL_ROI_3_ALPHA" },
+ { 0x68, "CHNL_ROI_3_ULC" },
+ { 0x6C, "CHNL_ROI_3_LRC" },
+ { 0x70, "CHNL_OUT_BUF1_ADDR_Y" },
+ { 0x74, "CHNL_OUT_BUF1_ADDR_U" },
+ { 0x78, "CHNL_OUT_BUF1_ADDR_V" },
+ { 0x7C, "CHNL_OUT_BUF_PITCH" },
+ { 0x80, "CHNL_IN_BUF_ADDR" },
+ { 0x84, "CHNL_IN_BUF_PITCH" },
+ { 0x88, "CHNL_MEM_RD_CTRL" },
+ { 0x8C, "CHNL_OUT_BUF2_ADDR_Y" },
+ { 0x90, "CHNL_OUT_BUF2_ADDR_U" },
+ { 0x94, "CHNL_OUT_BUF2_ADDR_V" },
+ { 0x98, "CHNL_SCL_IMG_CFG" },
+ { 0x9C, "CHNL_FLOW_CTRL" },
+ };
+ u32 i;
+
+ dev_dbg(dev, "ISI CHNLC register dump, isi%d\n", mxc_isi->id);
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 reg = readl(mxc_isi->regs + registers[i].offset);
+ dev_dbg(dev, "%20s[0x%.2x]: %.2x\n",
+ registers[i].name, registers[i].offset, reg);
+ }
+}
+#else
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi)
+{
+}
+#endif
+
+/*
+ * A2,A1, B1, A3, B3, B2,
+ * C2, C1, D1, C3, D3, D2
+ */
+static const u32 coeffs[2][6] = {
+ /* YUV2RGB */
+ { 0x0000012A, 0x012A0198, 0x0730079C,
+ 0x0204012A, 0x01F00000, 0x01800180 },
+
+ /* RGB->YUV */
+ { 0x00810041, 0x07db0019, 0x007007b6,
+ 0x07a20070, 0x001007ee, 0x00800080 },
+};
+
+static void printk_pixelformat(char *prefix, int val)
+{
+ pr_info("%s %c%c%c%c\n", prefix ? prefix : "pixelformat",
+ val & 0xff,
+ (val >> 8) & 0xff,
+ (val >> 16) & 0xff,
+ (val >> 24) & 0xff);
+}
+
+static bool is_rgb(u32 pix_fmt)
+{
+ if ((pix_fmt == V4L2_PIX_FMT_RGB565) ||
+ (pix_fmt == V4L2_PIX_FMT_RGB24) ||
+ (pix_fmt == V4L2_PIX_FMT_RGB32) ||
+ (pix_fmt == V4L2_PIX_FMT_BGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_XRGB32) ||
+ (pix_fmt == V4L2_PIX_FMT_XBGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_BGR24) ||
+ (pix_fmt == V4L2_PIX_FMT_RGBA) ||
+ (pix_fmt == V4L2_PIX_FMT_ABGR32) ||
+ (pix_fmt == V4L2_PIX_FMT_ARGB32))
+ return true;
+ else
+ return false;
+}
+
+static bool is_yuv(u32 pix_fmt)
+{
+ if ((pix_fmt == V4L2_PIX_FMT_YUYV) ||
+ (pix_fmt == V4L2_PIX_FMT_YUV32) ||
+ (pix_fmt == V4L2_PIX_FMT_YUV444M) ||
+ (pix_fmt == V4L2_PIX_FMT_YUV24) ||
+ (pix_fmt == V4L2_PIX_FMT_NV12))
+ return true;
+ else
+ return false;
+}
+
+bool is_buf_active(struct mxc_isi_dev *mxc_isi, int buf_id)
+{
+ u32 status = mxc_isi->status;
+ bool reverse = mxc_isi->buf_active_reverse;
+
+ return (buf_id == 1) ? ((reverse) ? (status & 0x100) : (status & 0x200)) :
+ ((reverse) ? (status & 0x200) : (status & 0x100));
+}
+EXPORT_SYMBOL_GPL(is_buf_active);
+
+static void chain_buf(struct mxc_isi_dev *mxc_isi, struct mxc_isi_frame *frm)
+{
+ u32 val;
+
+ if (frm->o_width > ISI_2K) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ } else if (!mxc_isi->chain_buf) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ }
+}
+
+struct device *mxc_isi_dev_get_parent(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *parent;
+ struct platform_device *parent_pdev;
+
+ if (!pdev)
+ return NULL;
+
+ /* Get parent for isi capture device */
+ parent = of_get_parent(dev->of_node);
+ parent_pdev = of_find_device_by_node(parent);
+ if (!parent_pdev) {
+ of_node_put(parent);
+ return NULL;
+ }
+ of_node_put(parent);
+
+ return &parent_pdev->dev;
+}
+EXPORT_SYMBOL_GPL(mxc_isi_dev_get_parent);
+
+struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev)
+{
+ struct mxc_isi_dev *mxc_isi;
+
+ if (!pdev || !pdev->dev.parent)
+ return NULL;
+
+ mxc_isi = (struct mxc_isi_dev *)dev_get_drvdata(pdev->dev.parent);
+ if (!mxc_isi) {
+ dev_err(&pdev->dev, "Cann't get host data\n");
+ return NULL;
+ }
+
+ return mxc_isi;
+}
+EXPORT_SYMBOL_GPL(mxc_isi_get_hostdata);
+
+void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf)
+{
+ struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf;
+ u32 framecount = buf->v4l2_buf.sequence;
+ struct frame_addr *paddr = &buf->paddr;
+ struct mxc_isi_cap_dev *isi_cap;
+ struct v4l2_pix_format_mplane *pix;
+ int val = 0;
+
+ if (buf->discard) {
+ isi_cap = mxc_isi->isi_cap;
+ pix = &isi_cap->pix;
+ paddr->y = isi_cap->discard_buffer_dma[0];
+ if (pix->num_planes == 2)
+ paddr->cb = isi_cap->discard_buffer_dma[1];
+ if (pix->num_planes == 3) {
+ paddr->cb = isi_cap->discard_buffer_dma[1];
+ paddr->cr = isi_cap->discard_buffer_dma[2];
+ }
+ } else {
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0);
+
+ if (vb2_buf->num_planes == 2)
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1);
+ if (vb2_buf->num_planes == 3) {
+ paddr->cb = vb2_dma_contig_plane_dma_addr(vb2_buf, 1);
+ paddr->cr = vb2_dma_contig_plane_dma_addr(vb2_buf, 2);
+ }
+ }
+
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+
+ if (framecount == 0 || ((is_buf_active(mxc_isi, 2)) && (framecount != 1))) {
+ writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_Y);
+ writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_U);
+ writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF1_ADDR_V);
+ val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK;
+ buf->id = MXC_ISI_BUF1;
+ } else if (framecount == 1 || is_buf_active(mxc_isi, 1)) {
+ writel(paddr->y, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_Y);
+ writel(paddr->cb, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_U);
+ writel(paddr->cr, mxc_isi->regs + CHNL_OUT_BUF2_ADDR_V);
+ val ^= CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK;
+ buf->id = MXC_ISI_BUF2;
+ }
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_outbuf);
+
+void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf)
+{
+ struct vb2_buffer *vb2_buf = &buf->v4l2_buf.vb2_buf;
+ struct frame_addr *paddr = &buf->paddr;
+
+ /* Only support one plane */
+ paddr->y = vb2_dma_contig_plane_dma_addr(vb2_buf, 0);
+ writel(paddr->y, mxc_isi->regs + CHNL_IN_BUF_ADDR);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_m2m_src_addr);
+
+void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= CHNL_CTRL_SW_RST;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ mdelay(5);
+ val &= ~CHNL_CTRL_SW_RST;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_sw_reset);
+
+void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~(CHNL_CTRL_MIPI_VC_ID_MASK |
+ CHNL_CTRL_SRC_INPUT_MASK | CHNL_CTRL_SRC_TYPE_MASK);
+
+ switch (mxc_isi->interface[IN_PORT]) {
+ case ISI_INPUT_INTERFACE_MIPI0_CSI2:
+ val |= mxc_isi->pdata->chan_src->src_mipi0;
+ if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 &&
+ mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0)
+ val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET);
+ break;
+ case ISI_INPUT_INTERFACE_MIPI1_CSI2:
+ val |= mxc_isi->pdata->chan_src->src_mipi1;
+ if (mxc_isi->interface[SUB_IN_PORT] <= CHNL_CTRL_MIPI_VC_ID_VC3 &&
+ mxc_isi->interface[SUB_IN_PORT] >= CHNL_CTRL_MIPI_VC_ID_VC0)
+ val |= (mxc_isi->interface[SUB_IN_PORT] << CHNL_CTRL_MIPI_VC_ID_OFFSET);
+ break;
+ case ISI_INPUT_INTERFACE_DC0:
+ val |= mxc_isi->pdata->chan_src->src_dc0;
+ break;
+ case ISI_INPUT_INTERFACE_DC1:
+ val |= mxc_isi->pdata->chan_src->src_dc1;
+ break;
+ case ISI_INPUT_INTERFACE_HDMI:
+ val |= mxc_isi->pdata->chan_src->src_hdmi;
+ break;
+ case ISI_INPUT_INTERFACE_PARALLEL_CSI:
+ val |= mxc_isi->pdata->chan_src->src_csi;
+ break;
+ case ISI_INPUT_INTERFACE_MEM:
+ val |= mxc_isi->pdata->chan_src->src_mem;
+ val |= (CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET);
+ break;
+ default:
+ dev_err(&mxc_isi->pdev->dev, "invalid interface\n");
+ break;
+ }
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_source_config);
+
+void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_VFLIP_EN_MASK | CHNL_IMG_CTRL_HFLIP_EN_MASK);
+
+ if (mxc_isi->vflip)
+ val |= (CHNL_IMG_CTRL_VFLIP_EN_ENABLE << CHNL_IMG_CTRL_VFLIP_EN_OFFSET);
+ if (mxc_isi->hflip)
+ val |= (CHNL_IMG_CTRL_HFLIP_EN_ENABLE << CHNL_IMG_CTRL_HFLIP_EN_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_set_chain_buf);
+
+void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ struct mxc_isi_fmt *src_fmt = src_f->fmt;
+ struct mxc_isi_fmt *dst_fmt = dst_f->fmt;
+ u32 val, csc = 0;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_FORMAT_MASK |
+ CHNL_IMG_CTRL_YCBCR_MODE_MASK |
+ CHNL_IMG_CTRL_CSC_BYPASS_MASK |
+ CHNL_IMG_CTRL_CSC_MODE_MASK);
+
+ /* set outbuf format */
+ val |= dst_fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET;
+
+ mxc_isi->cscen = 1;
+
+ if (is_yuv(src_fmt->fourcc) && is_rgb(dst_fmt->fourcc)) {
+ /* YUV2RGB */
+ csc = YUV2RGB;
+ /* YCbCr enable??? */
+ val |= (CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB << CHNL_IMG_CTRL_CSC_MODE_OFFSET);
+ val |= (CHNL_IMG_CTRL_YCBCR_MODE_ENABLE << CHNL_IMG_CTRL_YCBCR_MODE_OFFSET);
+ } else if (is_rgb(src_fmt->fourcc) && is_yuv(dst_fmt->fourcc)) {
+ /* RGB2YUV */
+ csc = RGB2YUV;
+ val |= (CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR << CHNL_IMG_CTRL_CSC_MODE_OFFSET);
+ } else {
+ /* Bypass CSC */
+ pr_info("bypass csc\n");
+ mxc_isi->cscen = 0;
+ val |= CHNL_IMG_CTRL_CSC_BYPASS_ENABLE;
+ }
+
+ printk_pixelformat("input fmt", src_fmt->fourcc);
+ printk_pixelformat("output fmt", dst_fmt->fourcc);
+
+ if (mxc_isi->cscen) {
+ writel(coeffs[csc][0], mxc_isi->regs + CHNL_CSC_COEFF0);
+ writel(coeffs[csc][1], mxc_isi->regs + CHNL_CSC_COEFF1);
+ writel(coeffs[csc][2], mxc_isi->regs + CHNL_CSC_COEFF2);
+ writel(coeffs[csc][3], mxc_isi->regs + CHNL_CSC_COEFF3);
+ writel(coeffs[csc][4], mxc_isi->regs + CHNL_CSC_COEFF4);
+ writel(coeffs[csc][5], mxc_isi->regs + CHNL_CSC_COEFF5);
+ }
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi,
+ struct v4l2_rect *rect)
+{
+ u32 val0, val1;
+
+ val0 = (rect->left << 16) | rect->top;
+ writel(val0, mxc_isi->regs + CHNL_ROI_0_ULC);
+ val1 = (rect->width << 16) | rect->height;
+ writel(val0 + val1, mxc_isi->regs + CHNL_ROI_0_LRC);
+}
+
+void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~(CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK | CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK);
+
+ if (mxc_isi->alphaen)
+ val |= ((mxc_isi->alpha << CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET) |
+ (CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE << CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET));
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_panic_threshold(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_set_thd *set_thd = mxc_isi->pdata->set_thd;
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+
+ val &= ~(set_thd->panic_set_thd_y.mask);
+ val |= set_thd->panic_set_thd_y.threshold << set_thd->panic_set_thd_y.offset;
+
+ val &= ~(set_thd->panic_set_thd_u.mask);
+ val |= set_thd->panic_set_thd_u.threshold << set_thd->panic_set_thd_u.offset;
+
+ val &= ~(set_thd->panic_set_thd_v.mask);
+ val |= set_thd->panic_set_thd_v.threshold << set_thd->panic_set_thd_v.offset;
+
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_CTRL);
+}
+
+void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ if (mxc_isi->chain_buf) {
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHAIN_BUF_MASK;
+ val |= (CHNL_CTRL_CHAIN_BUF_2_CHAIN << CHNL_CTRL_CHAIN_BUF_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+ }
+}
+
+void mxc_isi_channel_deinterlace_init(struct mxc_isi_dev *mxc_isi)
+{
+ /* Config for Blending deinterlace */
+}
+
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi)
+{
+ /* de-interlacing method
+ * Weaving-------------Yes
+ * Line Doubling-------No
+ * Blending -----------TODO
+ */
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_DEINT_MASK;
+ if (mxc_isi->deinterlace)
+ val |= mxc_isi->deinterlace << CHNL_IMG_CTRL_DEINT_OFFSET;
+ if (mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN ||
+ mxc_isi->deinterlace == CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD)
+ mxc_isi_channel_deinterlace_init(mxc_isi);
+
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_frame *src_f = &mxc_isi->isi_cap->src_f;
+ struct v4l2_rect crop;
+ u32 val, val0, val1, temp;
+
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_CROP_EN_MASK;
+
+ if ((src_f->o_height == src_f->height) &&
+ (src_f->o_width == src_f->width)) {
+ mxc_isi->crop = 0;
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+ return;
+ }
+
+ if (mxc_isi->scale) {
+ temp = (src_f->h_off << 12) / mxc_isi->xfactor;
+ crop.left = temp >> mxc_isi->pre_dec_x;
+ temp = (src_f->v_off << 12) / mxc_isi->yfactor;
+ crop.top = temp >> mxc_isi->pre_dec_y;
+ temp = (src_f->width << 12) / mxc_isi->xfactor;
+ crop.width = temp >> mxc_isi->pre_dec_x;
+ temp = (src_f->height << 12) / mxc_isi->yfactor;
+ crop.height = temp >> mxc_isi->pre_dec_y;
+ } else {
+ crop.left = src_f->h_off;
+ crop.top = src_f->v_off;
+ crop.width = src_f->width;
+ crop.height = src_f->height;
+ }
+
+ mxc_isi->crop = 1;
+ val |= (CHNL_IMG_CTRL_CROP_EN_ENABLE << CHNL_IMG_CTRL_CROP_EN_OFFSET);
+ val0 = crop.top | (crop.left << CHNL_CROP_ULC_X_OFFSET);
+ val1 = crop.height | (crop.width << CHNL_CROP_LRC_X_OFFSET);
+
+ writel(val0, mxc_isi->regs + CHNL_CROP_ULC);
+ writel((val1 + val0), mxc_isi->regs + CHNL_CROP_LRC);
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+static void mxc_isi_channel_clear_scaling(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val0;
+
+ writel(0x10001000, mxc_isi->regs + CHNL_SCALE_FACTOR);
+
+ val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK);
+ writel(val0, mxc_isi->regs + CHNL_IMG_CTRL);
+}
+
+void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 decx, decy;
+ u32 xscale, yscale;
+ u32 xdec = 0, ydec = 0;
+ u32 val0, val1;
+
+ if (dst_f->height == src_f->height &&
+ dst_f->width == src_f->width) {
+ mxc_isi->scale = 0;
+ mxc_isi_channel_clear_scaling(mxc_isi);
+ dev_dbg(&mxc_isi->pdev->dev, "%s: no scale\n", __func__);
+ return;
+ }
+
+ dev_info(&mxc_isi->pdev->dev, "input_size(%d,%d), output_size(%d,%d)\n",
+ src_f->width, src_f->height, dst_f->width, dst_f->height);
+
+ mxc_isi->scale = 1;
+
+ decx = src_f->width / dst_f->width;
+ decy = src_f->height / dst_f->height;
+
+ if (decx > 1) {
+ /* Down */
+ if (decx >= 2 && decx < 4) {
+ decx = 2;
+ xdec = 1;
+ } else if (decx >= 4 && decx < 8) {
+ decx = 4;
+ xdec = 2;
+ } else if (decx >= 8) {
+ decx = 8;
+ xdec = 3;
+ }
+ xscale = src_f->width * 0x1000 / (dst_f->width * decx);
+ } else {
+ /* Up */
+ xscale = src_f->width * 0x1000 / dst_f->width;
+ }
+
+ if (decy > 1) {
+ if (decy >= 2 && decy < 4) {
+ decy = 2;
+ ydec = 1;
+ } else if (decy >= 4 && decy < 8) {
+ decy = 4;
+ ydec = 2;
+ } else if (decy >= 8) {
+ decy = 8;
+ ydec = 3;
+ }
+ yscale = src_f->height * 0x1000 / (dst_f->height * decy);
+ } else {
+ yscale = src_f->height * 0x1000 / dst_f->height;
+ }
+
+ val0 = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val0 |= CHNL_IMG_CTRL_YCBCR_MODE_MASK;//YCbCr Sandor???
+ val0 &= ~(CHNL_IMG_CTRL_DEC_X_MASK | CHNL_IMG_CTRL_DEC_Y_MASK);
+ val0 |= (xdec << CHNL_IMG_CTRL_DEC_X_OFFSET) |
+ (ydec << CHNL_IMG_CTRL_DEC_Y_OFFSET);
+ writel(val0, mxc_isi->regs + CHNL_IMG_CTRL);
+
+ if (xscale > ISI_DOWNSCALE_THRESHOLD)
+ xscale = ISI_DOWNSCALE_THRESHOLD;
+ if (yscale > ISI_DOWNSCALE_THRESHOLD)
+ yscale = ISI_DOWNSCALE_THRESHOLD;
+
+ val1 = xscale | (yscale << CHNL_SCALE_FACTOR_Y_SCALE_OFFSET);
+
+ writel(val1, mxc_isi->regs + CHNL_SCALE_FACTOR);
+
+ /* Update scale config if scaling enabled */
+ val1 = dst_f->o_width | (dst_f->o_height << CHNL_SCL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val1, mxc_isi->regs + CHNL_SCL_IMG_CFG);
+
+ writel(0, mxc_isi->regs + CHNL_SCALE_OFFSET);
+
+ return;
+}
+
+void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ /* sw reset */
+ mxc_isi_channel_sw_reset(mxc_isi);
+
+ /* Init channel clk first */
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= (CHNL_CTRL_CLK_EN_ENABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_init);
+
+void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ /* sw reset */
+ mxc_isi_channel_sw_reset(mxc_isi);
+
+ /* deinit channel clk first */
+ val = (CHNL_CTRL_CLK_EN_ENABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_deinit);
+
+void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 val;
+
+ /* images having higher than 2048 horizontal resolution */
+ chain_buf(mxc_isi, src_f);
+
+ /* config output frame size and format */
+ val = src_f->o_width | (src_f->o_height << CHNL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_IMG_CFG);
+
+ /* scale size need to equal input size when scaling disabled*/
+ writel(val, mxc_isi->regs + CHNL_SCL_IMG_CFG);
+
+ /* check csc and scaling */
+ mxc_isi_channel_set_csc(mxc_isi, src_f, dst_f);
+
+ mxc_isi_channel_set_scaling(mxc_isi, src_f, dst_f);
+
+ /* select the source input / src type / virtual channel for mipi*/
+ mxc_isi_channel_source_config(mxc_isi);
+
+ /* line pitch */
+ val = dst_f->bytesperline[0];
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+
+ /* TODO */
+ mxc_isi_channel_set_flip(mxc_isi);
+
+ mxc_isi_channel_set_alpha(mxc_isi);
+
+ mxc_isi_channel_set_panic_threshold(mxc_isi);
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~CHNL_CTRL_CHNL_BYPASS_MASK;
+
+ /* Bypass channel */
+ if (!mxc_isi->cscen && !mxc_isi->scale)
+ val |= (CHNL_CTRL_CHNL_BYPASS_ENABLE << CHNL_CTRL_CHNL_BYPASS_OFFSET);
+
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_config);
+
+void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi)
+{
+ u32 status;
+
+ status = mxc_isi_get_irq_status(mxc_isi);
+ mxc_isi_clean_irq_status(mxc_isi, status);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_clean_registers);
+
+void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val |= 0xff << CHNL_CTRL_BLANK_PXL_OFFSET;
+
+ if (m2m_enabled) {
+ val &= ~(CHNL_CTRL_SRC_TYPE_MASK | CHNL_CTRL_SRC_INPUT_MASK);
+ val |= (mxc_isi->pdata->chan_src->src_mem << CHNL_CTRL_SRC_INPUT_OFFSET |
+ CHNL_CTRL_SRC_TYPE_MEMORY << CHNL_CTRL_SRC_TYPE_OFFSET);
+ }
+
+ val &= ~CHNL_CTRL_CHNL_EN_MASK;
+ val |= CHNL_CTRL_CHNL_EN_ENABLE << CHNL_CTRL_CHNL_EN_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+
+ mxc_isi_clean_registers(mxc_isi);
+ mxc_isi_enable_irq(mxc_isi);
+
+ if (m2m_enabled) {
+ mxc_isi_m2m_start_read(mxc_isi);
+ return;
+ }
+
+ dump_isi_regs(mxc_isi);
+ msleep(300);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_enable);
+
+void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ mxc_isi_disable_irq(mxc_isi);
+
+ val = readl(mxc_isi->regs + CHNL_CTRL);
+ val &= ~(CHNL_CTRL_CHNL_EN_MASK | CHNL_CTRL_CLK_EN_MASK);
+ val |= (CHNL_CTRL_CHNL_EN_DISABLE << CHNL_CTRL_CHNL_EN_OFFSET);
+ val |= (CHNL_CTRL_CLK_EN_DISABLE << CHNL_CTRL_CLK_EN_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_channel_disable);
+
+void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_ier_reg *ier_reg = mxc_isi->pdata->ier_reg;
+ u32 val;
+
+ val = CHNL_IER_FRM_RCVD_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_U_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_V_EN_MASK |
+ CHNL_IER_AXI_WR_ERR_Y_EN_MASK;
+
+ /* Y/U/V overflow enable */
+ val |= ier_reg->oflw_y_buf_en.mask |
+ ier_reg->oflw_u_buf_en.mask |
+ ier_reg->oflw_v_buf_en.mask;
+
+ /* Y/U/V excess overflow enable */
+ val |= ier_reg->excs_oflw_y_buf_en.mask |
+ ier_reg->excs_oflw_u_buf_en.mask |
+ ier_reg->excs_oflw_v_buf_en.mask;
+
+ /* Y/U/V panic enable */
+ val |= ier_reg->panic_y_buf_en.mask |
+ ier_reg->panic_u_buf_en.mask |
+ ier_reg->panic_v_buf_en.mask;
+
+ writel(val, mxc_isi->regs + CHNL_IER);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_enable_irq);
+
+void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi)
+{
+ writel(0, mxc_isi->regs + CHNL_IER);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_disable_irq);
+
+u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi)
+{
+ return readl(mxc_isi->regs + CHNL_STS);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_get_irq_status);
+
+void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val)
+{
+ writel(val, mxc_isi->regs + CHNL_STS);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_clean_irq_status);
+
+void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f)
+{
+ u32 val;
+
+ /* source format */
+ val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ val &= ~CHNL_MEM_RD_CTRL_IMG_TYPE_MASK;
+ val |= src_f->fmt->color << CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+
+ /* source image width and height */
+ val = (src_f->width << CHNL_IMG_CFG_WIDTH_OFFSET |
+ src_f->height << CHNL_IMG_CFG_HEIGHT_OFFSET);
+ writel(val, mxc_isi->regs + CHNL_IMG_CFG);
+
+ /* source pitch */
+ val = src_f->bytesperline[0] << CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_IN_BUF_PITCH);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_config_src);
+
+void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f)
+{
+ u32 val;
+
+ /* out format */
+ val = readl(mxc_isi->regs + CHNL_IMG_CTRL);
+ val &= ~CHNL_IMG_CTRL_FORMAT_MASK;
+ val |= dst_f->fmt->color << CHNL_IMG_CTRL_FORMAT_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_IMG_CTRL);
+
+ /* out pitch */
+ val = readl(mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+ val &= ~CHNL_IN_BUF_PITCH_LINE_PITCH_MASK;
+ val |= dst_f->bytesperline[0] << CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_OUT_BUF_PITCH);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_config_dst);
+
+void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi)
+{
+ u32 val;
+
+ val = readl(mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ val &= ~ CHNL_MEM_RD_CTRL_READ_MEM_MASK;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+ udelay(300);
+
+ val |= CHNL_MEM_RD_CTRL_READ_MEM_ENABLE << CHNL_MEM_RD_CTRL_READ_MEM_OFFSET;
+ writel(val, mxc_isi->regs + CHNL_MEM_RD_CTRL);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_start_read);
diff --git a/drivers/staging/media/imx/imx8-isi-hw.h b/drivers/staging/media/imx/imx8-isi-hw.h
new file mode 100644
index 000000000000..c70b409db21d
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-hw.h
@@ -0,0 +1,484 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2019-2020 NXP
+ *
+ */
+
+#ifndef __MXC_ISI_HW_H__
+#define __MXC_ISI_HW_H__
+
+#include <linux/delay.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/platform_device.h>
+#include <linux/videodev2.h>
+
+#include "imx8-isi-core.h"
+
+/* ISI Registers Define */
+/* Channel Control Register */
+#define CHNL_CTRL 0x0
+#define CHNL_CTRL_CHNL_EN_OFFSET 31
+#define CHNL_CTRL_CHNL_EN_MASK 0x80000000
+#define CHNL_CTRL_CHNL_EN_DISABLE 0
+#define CHNL_CTRL_CHNL_EN_ENABLE 1
+#define CHNL_CTRL_CLK_EN_OFFSET 30
+#define CHNL_CTRL_CLK_EN_MASK 0x40000000
+#define CHNL_CTRL_CLK_EN_DISABLE 0
+#define CHNL_CTRL_CLK_EN_ENABLE 1
+#define CHNL_CTRL_CHNL_BYPASS_OFFSET 29
+#define CHNL_CTRL_CHNL_BYPASS_MASK 0x20000000
+#define CHNL_CTRL_CHNL_BYPASS_ENABLE 1
+#define CHNL_CTRL_CHAIN_BUF_OFFSET 25
+#define CHNL_CTRL_CHAIN_BUF_MASK 0x60000
+#define CHNL_CTRL_CHAIN_BUF_NO_CHAIN 0
+#define CHNL_CTRL_CHAIN_BUF_2_CHAIN 1
+#define CHNL_CTRL_SW_RST_OFFSET 24
+#define CHNL_CTRL_SW_RST_MASK 0x1000000
+#define CHNL_CTRL_SW_RST 0x1000000
+#define CHNL_CTRL_BLANK_PXL_OFFSET 16
+#define CHNL_CTRL_MIPI_VC_ID_OFFSET 6
+#define CHNL_CTRL_MIPI_VC_ID_MASK 0xc0
+#define CHNL_CTRL_MIPI_VC_ID_VC0 0
+#define CHNL_CTRL_MIPI_VC_ID_VC1 1
+#define CHNL_CTRL_MIPI_VC_ID_VC2 2
+#define CHNL_CTRL_MIPI_VC_ID_VC3 3
+#define CHNL_CTRL_SRC_TYPE_OFFSET 4
+#define CHNL_CTRL_SRC_TYPE_MASK 0x10
+#define CHNL_CTRL_SRC_TYPE_DEVICE 0
+#define CHNL_CTRL_SRC_TYPE_MEMORY 1
+#define CHNL_CTRL_SRC_INPUT_OFFSET 0
+#define CHNL_CTRL_SRC_INPUT_MASK 0x7
+#define CHNL_CTRL_SRC_INPUT_MEMORY 5
+
+/* Channel Image Control Register */
+#define CHNL_IMG_CTRL 0x4
+#define CHNL_IMG_CTRL_FORMAT_OFFSET 24
+#define CHNL_IMG_CTRL_FORMAT_MASK 0x3F000000
+#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_OFFSET 16
+#define CHNL_IMG_CTRL_GBL_ALPHA_VAL_MASK 0xFF0000
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_OFFSET 15
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_ENABLE 1
+#define CHNL_IMG_CTRL_GBL_ALPHA_EN_MASK 0x8000
+#define CHNL_IMG_CTRL_DEINT_OFFSET 12
+#define CHNL_IMG_CTRL_DEINT_MASK 0x7000
+#define CHNL_IMG_CTRL_DEINT_WEAVE_ODD_EVEN 2
+#define CHNL_IMG_CTRL_DEINT_WEAVE_EVEN_ODD 3
+#define CHNL_IMG_CTRL_DEINT_BLEND_ODD_EVEN 4
+#define CHNL_IMG_CTRL_DEINT_BLEND_EVEN_ODD 5
+#define CHNL_IMG_CTRL_DEINT_LDOUBLE_ODD_EVEN 6
+#define CHNL_IMG_CTRL_DEINT_LDOUBLE_EVEN_ODD 7
+#define CHNL_IMG_CTRL_DEC_X_OFFSET 10
+#define CHNL_IMG_CTRL_DEC_X_MASK 0xC00
+#define CHNL_IMG_CTRL_DEC_X_0 0
+#define CHNL_IMG_CTRL_DEC_X_2 1
+#define CHNL_IMG_CTRL_DEC_X_4 2
+#define CHNL_IMG_CTRL_DEC_X_8 3
+#define CHNL_IMG_CTRL_DEC_Y_OFFSET 8
+#define CHNL_IMG_CTRL_DEC_Y_MASK 0x300
+#define CHNL_IMG_CTRL_DEC_Y_0 0
+#define CHNL_IMG_CTRL_DEC_Y_2 1
+#define CHNL_IMG_CTRL_DEC_Y_4 2
+#define CHNL_IMG_CTRL_DEC_Y_8 3
+#define CHNL_IMG_CTRL_CROP_EN_OFFSET 7
+#define CHNL_IMG_CTRL_CROP_EN_MASK 0x80
+#define CHNL_IMG_CTRL_CROP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_VFLIP_EN_OFFSET 6
+#define CHNL_IMG_CTRL_VFLIP_EN_MASK 0x40
+#define CHNL_IMG_CTRL_VFLIP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_HFLIP_EN_OFFSET 5
+#define CHNL_IMG_CTRL_HFLIP_EN_MASK 0x20
+#define CHNL_IMG_CTRL_HFLIP_EN_ENABLE 1
+#define CHNL_IMG_CTRL_YCBCR_MODE_OFFSET 3
+#define CHNL_IMG_CTRL_YCBCR_MODE_MASK 0x8
+#define CHNL_IMG_CTRL_YCBCR_MODE_ENABLE 1
+#define CHNL_IMG_CTRL_CSC_MODE_OFFSET 1
+#define CHNL_IMG_CTRL_CSC_MODE_MASK 0x6
+#define CHNL_IMG_CTRL_CSC_MODE_YUV2RGB 0
+#define CHNL_IMG_CTRL_CSC_MODE_YCBCR2RGB 1
+#define CHNL_IMG_CTRL_CSC_MODE_RGB2YUV 2
+#define CHNL_IMG_CTRL_CSC_MODE_RGB2YCBCR 3
+#define CHNL_IMG_CTRL_CSC_BYPASS_OFFSET 0
+#define CHNL_IMG_CTRL_CSC_BYPASS_MASK 0x1
+#define CHNL_IMG_CTRL_CSC_BYPASS_ENABLE 0x1
+
+/* Channel Output Buffer Control Register */
+#define CHNL_OUT_BUF_CTRL 0x8
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_OFFSET 15
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF2_ADDR_MASK 0x8000
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_OFFSET 14
+#define CHNL_OUT_BUF_CTRL_LOAD_BUF1_ADDR_MASK 0x4000
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_OFFSET 6
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_MASK 0xC0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_V_PANIC_75 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_OFFSET 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_MASK 0x18
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_U_PANIC_75 3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_OFFSET 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_MASK 0x3
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_NO_PANIC 0
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_25 1
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_50 2
+#define CHNL_OUT_BUF_CTRL_OFLW_PANIC_SET_THD_Y_PANIC_75 3
+
+/* Channel Image Configuration */
+#define CHNL_IMG_CFG 0xC
+#define CHNL_IMG_CFG_HEIGHT_OFFSET 16
+#define CHNL_IMG_CFG_HEIGHT_MASK 0x1FFF0000
+#define CHNL_IMG_CFG_WIDTH_OFFSET 0
+#define CHNL_IMG_CFG_WIDTH_MASK 0x1FFF
+
+/* Channel Interrupt Enable Register */
+#define CHNL_IER 0x10
+#define CHNL_IER_MEM_RD_DONE_EN_OFFSET 31
+#define CHNL_IER_MEM_RD_DONE_EN_MASK 0x80000000
+#define CHNL_IER_MEM_RD_DONE_EN_ENABLE 1
+#define CHNL_IER_LINE_RCVD_EN_OFFSET 30
+#define CHNL_IER_LINE_RCVD_EN_MASK 0x40000000
+#define CHNL_IER_LINE_RCVD_EN_ENABLE 1
+#define CHNL_IER_FRM_RCVD_EN_OFFSET 29
+#define CHNL_IER_FRM_RCVD_EN_MASK 0x20000000
+#define CHNL_IER_FRM_RCVD_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_V_EN_OFFSET 28
+#define CHNL_IER_AXI_WR_ERR_V_EN_MASK 0x10000000
+#define CHNL_IER_AXI_WR_ERR_V_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_U_EN_OFFSET 27
+#define CHNL_IER_AXI_WR_ERR_U_EN_MASK 0x8000000
+#define CHNL_IER_AXI_WR_ERR_U_EN_ENABLE 1
+#define CHNL_IER_AXI_WR_ERR_Y_EN_OFFSET 26
+#define CHNL_IER_AXI_WR_ERR_Y_EN_MASK 0x4000000
+#define CHNL_IER_AXI_WR_ERR_Y_EN_ENABLE 1
+#define CHNL_IER_AXI_RD_ERR_EN_OFFSET 25
+#define CHNL_IER_AXI_RD_ERR_EN_MASK 0x2000000
+#define CHNL_IER_AXI_RD_ERR_EN_ENABLE 1
+
+/* Channel Status Register */
+#define CHNL_STS 0x14
+#define CHNL_STS_MEM_RD_DONE_OFFSET 31
+#define CHNL_STS_MEM_RD_DONE_MASK 0x80000000
+#define CHNL_STS_MEM_RD_DONE_ENABLE 1
+#define CHNL_STS_LINE_STRD_OFFSET 30
+#define CHNL_STS_LINE_STRD_MASK 0x40000000
+#define CHNL_STS_LINE_STRD_ENABLE 1
+#define CHNL_STS_FRM_STRD_OFFSET 29
+#define CHNL_STS_FRM_STRD_MASK 0x20000000
+#define CHNL_STS_FRM_STRD_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_V_OFFSET 28
+#define CHNL_STS_AXI_WR_ERR_V_MASK 0x10000000
+#define CHNL_STS_AXI_WR_ERR_V_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_U_OFFSET 27
+#define CHNL_STS_AXI_WR_ERR_U_MASK 0x8000000
+#define CHNL_STS_AXI_WR_ERR_U_ENABLE 1
+#define CHNL_STS_AXI_WR_ERR_Y_OFFSET 26
+#define CHNL_STS_AXI_WR_ERR_Y_MASK 0x4000000
+#define CHNL_STS_AXI_WR_ERR_Y_ENABLE 1
+#define CHNL_STS_AXI_RD_ERR_OFFSET 25
+#define CHNL_STS_AXI_RD_ERR_MASK 0x2000000
+#define CHNL_STS_AXI_RD_ERR_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_V_BUF_OFFSET 24
+#define CHNL_STS_OFLW_PANIC_V_BUF_MASK 0x1000000
+#define CHNL_STS_OFLW_PANIC_V_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_V_BUF_OFFSET 23
+#define CHNL_STS_EXCS_OFLW_V_BUF_MASK 0x800000
+#define CHNL_STS_EXCS_OFLW_V_BUF_ENABLE 1
+#define CHNL_STS_OFLW_V_BUF_OFFSET 22
+#define CHNL_STS_OFLW_V_BUF_MASK 0x400000
+#define CHNL_STS_OFLW_V_BUF_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_U_BUF_OFFSET 21
+#define CHNL_STS_OFLW_PANIC_U_BUF_MASK 0x200000
+#define CHNL_STS_OFLW_PANIC_U_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_U_BUF_OFFSET 20
+#define CHNL_STS_EXCS_OFLW_U_BUF_MASK 0x100000
+#define CHNL_STS_EXCS_OFLW_U_BUF_ENABLE 1
+#define CHNL_STS_OFLW_U_BUF_OFFSET 19
+#define CHNL_STS_OFLW_U_BUF_MASK 0x80000
+#define CHNL_STS_OFLW_U_BUF_ENABLE 1
+#define CHNL_STS_OFLW_PANIC_Y_BUF_OFFSET 18
+#define CHNL_STS_OFLW_PANIC_Y_BUF_MASK 0x40000
+#define CHNL_STS_OFLW_PANIC_Y_BUF_ENABLE 1
+#define CHNL_STS_EXCS_OFLW_Y_BUF_OFFSET 17
+#define CHNL_STS_EXCS_OFLW_Y_BUF_MASK 0x20000
+#define CHNL_STS_EXCS_OFLW_Y_BUF_ENABLE 1
+#define CHNL_STS_OFLW_Y_BUF_OFFSET 16
+#define CHNL_STS_OFLW_Y_BUF_MASK 0x10000
+#define CHNL_STS_OFLW_Y_BUF_ENABLE 1
+#define CHNL_STS_OFLW_BYTES_OFFSET 0
+#define CHNL_STS_OFLW_BYTES_MASK 0xFF
+
+/* Channel Scale Factor Register */
+#define CHNL_SCALE_FACTOR 0x18
+#define CHNL_SCALE_FACTOR_Y_SCALE_OFFSET 16
+#define CHNL_SCALE_FACTOR_Y_SCALE_MASK 0x3FFF0000
+#define CHNL_SCALE_FACTOR_X_SCALE_OFFSET 0
+#define CHNL_SCALE_FACTOR_X_SCALE_MASK 0x3FFF
+
+/* Channel Scale Offset Register */
+#define CHNL_SCALE_OFFSET 0x1C
+#define CHNL_SCALE_OFFSET_Y_SCALE_OFFSET 16
+#define CHNL_SCALE_OFFSET_Y_SCALE_MASK 0xFFF0000
+#define CHNL_SCALE_OFFSET_X_SCALE_OFFSET 0
+#define CHNL_SCALE_OFFSET_X_SCALE_MASK 0xFFF
+
+/* Channel Crop Upper Left Corner Coordinate Register */
+#define CHNL_CROP_ULC 0x20
+#define CHNL_CROP_ULC_X_OFFSET 16
+#define CHNL_CROP_ULC_X_MASK 0xFFF0000
+#define CHNL_CROP_ULC_Y_OFFSET 0
+#define CHNL_CROP_ULC_Y_MASK 0xFFF
+
+/* Channel Crop Lower Right Corner Coordinate Register */
+#define CHNL_CROP_LRC 0x24
+#define CHNL_CROP_LRC_X_OFFSET 16
+#define CHNL_CROP_LRC_X_MASK 0xFFF0000
+#define CHNL_CROP_LRC_Y_OFFSET 0
+#define CHNL_CROP_LRC_Y_MASK 0xFFF
+
+/* Channel Color Space Conversion Coefficient Register 0 */
+#define CHNL_CSC_COEFF0 0x28
+#define CHNL_CSC_COEFF0_A2_OFFSET 16
+#define CHNL_CSC_COEFF0_A2_MASK 0x7FF0000
+#define CHNL_CSC_COEFF0_A1_OFFSET 0
+#define CHNL_CSC_COEFF0_A1_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 1 */
+#define CHNL_CSC_COEFF1 0x2C
+#define CHNL_CSC_COEFF1_B1_OFFSET 16
+#define CHNL_CSC_COEFF1_B1_MASK 0x7FF0000
+#define CHNL_CSC_COEFF1_A3_OFFSET 0
+#define CHNL_CSC_COEFF1_A3_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 2 */
+#define CHNL_CSC_COEFF2 0x30
+#define CHNL_CSC_COEFF2_B3_OFFSET 16
+#define CHNL_CSC_COEFF2_B3_MASK 0x7FF0000
+#define CHNL_CSC_COEFF2_B2_OFFSET 0
+#define CHNL_CSC_COEFF2_B2_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 3 */
+#define CHNL_CSC_COEFF3 0x34
+#define CHNL_CSC_COEFF3_C2_OFFSET 16
+#define CHNL_CSC_COEFF3_C2_MASK 0x7FF0000
+#define CHNL_CSC_COEFF3_C1_OFFSET 0
+#define CHNL_CSC_COEFF3_C1_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 4 */
+#define CHNL_CSC_COEFF4 0x38
+#define CHNL_CSC_COEFF4_D1_OFFSET 16
+#define CHNL_CSC_COEFF4_D1_MASK 0x1FF0000
+#define CHNL_CSC_COEFF4_C3_OFFSET 0
+#define CHNL_CSC_COEFF4_C3_MASK 0x7FF
+
+/* Channel Color Space Conversion Coefficient Register 5 */
+#define CHNL_CSC_COEFF5 0x3C
+#define CHNL_CSC_COEFF5_D3_OFFSET 16
+#define CHNL_CSC_COEFF5_D3_MASK 0x1FF0000
+#define CHNL_CSC_COEFF5_D2_OFFSET 0
+#define CHNL_CSC_COEFF5_D2_MASK 0x1FF
+
+/* Channel Alpha Value Register for ROI 0 */
+#define CHNL_ROI_0_ALPHA 0x40
+#define CHNL_ROI_0_ALPHA_OFFSET 24
+#define CHNL_ROI_0_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_0_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_0_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 0 */
+#define CHNL_ROI_0_ULC 0x44
+#define CHNL_ROI_0_ULC_X_OFFSET 16
+#define CHNL_ROI_0_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_0_ULC_Y_OFFSET 0
+#define CHNL_ROI_0_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 0 */
+#define CHNL_ROI_0_LRC 0x48
+#define CHNL_ROI_0_LRC_X_OFFSET 16
+#define CHNL_ROI_0_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_0_LRC_Y_OFFSET 0
+#define CHNL_ROI_0_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 1 */
+#define CHNL_ROI_1_ALPHA 0x4C
+#define CHNL_ROI_1_ALPHA_OFFSET 24
+#define CHNL_ROI_1_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_1_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_1_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 1 */
+#define CHNL_ROI_1_ULC 0x50
+#define CHNL_ROI_1_ULC_X_OFFSET 16
+#define CHNL_ROI_1_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_1_ULC_Y_OFFSET 0
+#define CHNL_ROI_1_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 1 */
+#define CHNL_ROI_1_LRC 0x54
+#define CHNL_ROI_1_LRC_X_OFFSET 16
+#define CHNL_ROI_1_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_1_LRC_Y_OFFSET 0
+#define CHNL_ROI_1_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 2 */
+#define CHNL_ROI_2_ALPHA 0x58
+#define CHNL_ROI_2_ALPHA_OFFSET 24
+#define CHNL_ROI_2_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_2_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_2_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 2 */
+#define CHNL_ROI_2_ULC 0x5C
+#define CHNL_ROI_2_ULC_X_OFFSET 16
+#define CHNL_ROI_2_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_2_ULC_Y_OFFSET 0
+#define CHNL_ROI_2_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 2 */
+#define CHNL_ROI_2_LRC 0x60
+#define CHNL_ROI_2_LRC_X_OFFSET 16
+#define CHNL_ROI_2_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_2_LRC_Y_OFFSET 0
+#define CHNL_ROI_2_LRC_Y_MASK 0xFFF
+
+/* Channel Alpha Value Register for ROI 3 */
+#define CHNL_ROI_3_ALPHA 0x64
+#define CHNL_ROI_3_ALPHA_OFFSET 24
+#define CHNL_ROI_3_ALPHA_MASK 0xFF000000
+#define CHNL_ROI_3_ALPHA_EN_OFFSET 16
+#define CHNL_ROI_3_ALPHA_EN_MASK 0x10000
+
+/* Channel Upper Left Coordinate Register for ROI 3 */
+#define CHNL_ROI_3_ULC 0x68
+#define CHNL_ROI_3_ULC_X_OFFSET 16
+#define CHNL_ROI_3_ULC_X_MASK 0xFFF0000
+#define CHNL_ROI_3_ULC_Y_OFFSET 0
+#define CHNL_ROI_3_ULC_Y_MASK 0xFFF
+
+/* Channel Lower Right Coordinate Register for ROI 3 */
+#define CHNL_ROI_3_LRC 0x6C
+#define CHNL_ROI_3_LRC_X_OFFSET 16
+#define CHNL_ROI_3_LRC_X_MASK 0xFFF0000
+#define CHNL_ROI_3_LRC_Y_OFFSET 0
+#define CHNL_ROI_3_LRC_Y_MASK 0xFFF
+
+/* Channel RGB or Luma (Y) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_Y 0x70
+
+/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_U 0x74
+
+/* Channel Chroma (V/Cr) Output Buffer 1 Address */
+#define CHNL_OUT_BUF1_ADDR_V 0x78
+
+/* Channel Output Buffer Pitch */
+#define CHNL_OUT_BUF_PITCH 0x7C
+#define CHNL_OUT_BUF_PITCH_LINE_PITCH_OFFSET 0
+#define CHNL_OUT_BUF_PITCH_LINE_PITCH_MASK 0xFFFF
+
+/* Channel Input Buffer Address */
+#define CHNL_IN_BUF_ADDR 0x80
+
+/* Channel Input Buffer Pitch */
+#define CHNL_IN_BUF_PITCH 0x84
+#define CHNL_IN_BUF_PITCH_FRM_PITCH_OFFSET 16
+#define CHNL_IN_BUF_PITCH_FRM_PITCH_MASK 0xFFFF0000
+#define CHNL_IN_BUF_PITCH_LINE_PITCH_OFFSET 0
+#define CHNL_IN_BUF_PITCH_LINE_PITCH_MASK 0xFFFF
+
+/* Channel Memory Read Control */
+#define CHNL_MEM_RD_CTRL 0x88
+#define CHNL_MEM_RD_CTRL_IMG_TYPE_OFFSET 28
+#define CHNL_MEM_RD_CTRL_IMG_TYPE_MASK 0xF0000000
+#define CHNL_MEM_RD_CTRL_READ_MEM_OFFSET 0
+#define CHNL_MEM_RD_CTRL_READ_MEM_MASK 1
+#define CHNL_MEM_RD_CTRL_READ_MEM_ENABLE 1
+
+/* Channel RGB or Luma (Y) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_Y 0x8C
+
+/* Channel Chroma (U/Cb/UV/CbCr) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_U 0x90
+
+/* Channel Chroma (V/Cr) Output Buffer 2 Address */
+#define CHNL_OUT_BUF2_ADDR_V 0x94
+
+/* Channel scale image config */
+#define CHNL_SCL_IMG_CFG 0x98
+#define CHNL_SCL_IMG_CFG_HEIGHT_OFFSET 16
+#define CHNL_SCL_IMG_CFG_HEIGHT_MASK 0x1FFF0000
+#define CHNL_SCL_IMG_CFG_WIDTH_OFFSET 0
+#define CHNL_SCL_IMG_CFG_WIDTH_MASK 0x1FFF
+
+/* Channel Flow Control Register */
+#define CHNL_FLOW_CTRL 0x9C
+#define CHNL_FLOW_CTRL_FC_DENOM_MASK 0xFF
+#define CHNL_FLOW_CTRL_FC_DENOM_OFFSET 0
+#define CHNL_FLOW_CTRL_FC_NUMER_MASK 0xFF0000
+#define CHNL_FLOW_CTRL_FC_NUMER_OFFSET 0
+
+enum isi_csi_coeff {
+ YUV2RGB = 0,
+ RGB2YUV,
+};
+
+void mxc_isi_channel_init(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_deinit(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_enable(struct mxc_isi_dev *mxc_isi, bool m2m_enabled);
+void mxc_isi_channel_disable(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_cap_frame_write_done(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_sw_reset(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_hw_reset(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_source_config(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_flip(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_alpha(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_chain_buf(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_deinterlace(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_crop(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_memory_image(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_channel_set_panic_threshold(struct mxc_isi_dev *mxc_isi);
+
+void mxc_isi_channel_set_scaling(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_set_outbuf(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf);
+
+void mxc_isi_channel_set_csc(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_config(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_channel_set_alpha_roi0(struct mxc_isi_dev *mxc_isi,
+ struct v4l2_rect *rect);
+void mxc_isi_channel_set_m2m_src_addr(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_buffer *buf);
+
+void mxc_isi_m2m_config_src(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *src_f);
+void mxc_isi_m2m_config_dst(struct mxc_isi_dev *mxc_isi,
+ struct mxc_isi_frame *dst_f);
+
+void mxc_isi_m2m_start_read(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_clean_irq_status(struct mxc_isi_dev *mxc_isi, u32 val);
+void mxc_isi_clean_registers(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_enable_irq(struct mxc_isi_dev *mxc_isi);
+void mxc_isi_disable_irq(struct mxc_isi_dev *mxc_isi);
+void dump_isi_regs(struct mxc_isi_dev *mxc_isi);
+
+u32 mxc_isi_get_irq_status(struct mxc_isi_dev *mxc_isi);
+bool is_buf_active(struct mxc_isi_dev *mxc_isi, int buf_id);
+
+struct device *mxc_isi_dev_get_parent(struct platform_device *pdev);
+struct mxc_isi_dev *mxc_isi_get_hostdata(struct platform_device *pdev);
+#endif /* __MXC_ISI_HW_H__ */
diff --git a/drivers/staging/media/imx/imx8-isi-m2m.c b/drivers/staging/media/imx/imx8-isi-m2m.c
new file mode 100644
index 000000000000..e36a03c094bf
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-isi-m2m.c
@@ -0,0 +1,1201 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform
+ *
+ * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
+ * used to process image from camera sensor or memory to memory or DC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bug.h>
+#include <linux/interrupt.h>
+#include <linux/device.h>
+#include <linux/pm_runtime.h>
+#include <linux/list.h>
+#include <linux/slab.h>
+#include <linux/of_graph.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "imx8-isi-hw.h"
+#include "imx8-common.h"
+
+#define to_isi_buffer(x) \
+ container_of((x), struct mxc_isi_buffer, v4l2_buf)
+
+#define file_to_ctx(file) \
+ container_of(file->private_data, struct mxc_isi_ctx, fh);
+
+#if defined(CONFIG_IMX8_ISI_CAPTURE)
+extern struct mxc_isi_fmt mxc_isi_out_formats[9];
+#else
+static struct mxc_isi_fmt mxc_isi_out_formats[9] = {};
+#endif
+
+struct mxc_isi_fmt mxc_isi_input_formats[] = {
+ /* Pixel link input format */
+ {
+ .name = "XBGR32",
+ .fourcc = V4L2_PIX_FMT_XBGR32,
+ .depth = { 32 },
+ .color = MXC_ISI_M2M_IN_FMT_XRGB8,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "RGB565",
+ .fourcc = V4L2_PIX_FMT_RGB565,
+ .depth = { 16 },
+ .color = MXC_ISI_M2M_IN_FMT_RGB565,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV24 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUV24,
+ .depth = { 24 },
+ .color = MXC_ISI_M2M_IN_FMT_YUV444_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "YUV16 (X-Y-U-V)",
+ .fourcc = V4L2_PIX_FMT_YUYV,
+ .depth = { 16 },
+ .color = MXC_ISI_M2M_IN_FMT_YUV422_1P8P,
+ .memplanes = 1,
+ .colplanes = 1,
+ }, {
+ .name = "RGBA (R-G-B-A)",
+ .fourcc = V4L2_PIX_FMT_RGBA,
+ .depth = { 32 },
+ .color = MXC_ISI_M2M_IN_FMT_XBGR8,
+ .memplanes = 1,
+ .colplanes = 1,
+ }
+};
+
+static struct v4l2_m2m_buffer *to_v4l2_m2m_buffer(struct vb2_v4l2_buffer *vbuf)
+{
+ struct v4l2_m2m_buffer *b;
+
+ b = container_of(vbuf, struct v4l2_m2m_buffer, vb);
+ return b;
+}
+
+void mxc_isi_m2m_frame_write_done(struct mxc_isi_dev *mxc_isi)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_isi->isi_m2m;
+ struct v4l2_fh *fh;
+ struct mxc_isi_ctx *curr_mxc_ctx;
+ struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
+ struct mxc_isi_buffer *src_buf, *dst_buf;
+ struct v4l2_m2m_buffer *b;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ curr_mxc_ctx = v4l2_m2m_get_curr_priv(isi_m2m->m2m_dev);
+ if (!curr_mxc_ctx) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Instance released before the end of transaction\n");
+ return;
+ }
+ fh = &curr_mxc_ctx->fh;
+
+ if (isi_m2m->aborting) {
+ mxc_isi_channel_disable(mxc_isi);
+ dev_warn(&isi_m2m->pdev->dev, "Aborting current job\n");
+ goto job_finish;
+ }
+
+ src_vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx);
+ if (!src_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "No enought source buffers\n");
+ goto job_finish;
+ }
+ src_buf = to_isi_buffer(src_vbuf);
+ v4l2_m2m_src_buf_remove(fh->m2m_ctx);
+ v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE);
+
+ if (!list_empty(&isi_m2m->out_active)) {
+ dst_buf = list_first_entry(&isi_m2m->out_active,
+ struct mxc_isi_buffer, list);
+ dst_vbuf = &dst_buf->v4l2_buf;
+ list_del_init(&dst_buf->list);
+ dst_buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
+ v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE);
+
+ }
+ isi_m2m->frame_count++;
+
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (dst_vbuf) {
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = isi_m2m->frame_count;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+ }
+
+job_finish:
+ v4l2_m2m_job_finish(isi_m2m->m2m_dev, fh->m2m_ctx);
+}
+EXPORT_SYMBOL_GPL(mxc_isi_m2m_frame_write_done);
+
+static void mxc_isi_m2m_device_run(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ struct vb2_v4l2_buffer *vbuf;
+ struct mxc_isi_buffer *src_buf;
+ unsigned long flags;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s enter\n", __func__);
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ /* SRC */
+ vbuf = v4l2_m2m_next_src_buf(fh->m2m_ctx);
+ if (!vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "Null src buf\n");
+ goto unlock;
+ }
+
+ src_buf = to_isi_buffer(vbuf);
+ mxc_isi_channel_set_m2m_src_addr(mxc_isi, src_buf);
+ mxc_isi_channel_enable(mxc_isi, mxc_isi->m2m_enabled);
+
+unlock:
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+}
+
+static int mxc_isi_m2m_job_ready(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ unsigned int num_src_bufs_ready;
+ unsigned int num_dst_bufs_ready;
+ unsigned long flags;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+ num_src_bufs_ready = v4l2_m2m_num_src_bufs_ready(fh->m2m_ctx);
+ num_dst_bufs_ready = v4l2_m2m_num_dst_bufs_ready(fh->m2m_ctx);
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+
+ if (num_src_bufs_ready >= 1 && num_dst_bufs_ready >= 1)
+ return 1;
+ return 0;
+}
+
+static void mxc_isi_m2m_job_abort(void *priv)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+
+ isi_m2m->aborting = 1;
+ dev_dbg(&isi_m2m->pdev->dev, "Abort requested\n");
+}
+
+static struct v4l2_m2m_ops mxc_isi_m2m_ops = {
+ .device_run = mxc_isi_m2m_device_run,
+ .job_ready = mxc_isi_m2m_job_ready,
+ .job_abort = mxc_isi_m2m_job_abort,
+};
+
+static int m2m_vb2_queue_setup(struct vb2_queue *q,
+ unsigned int *num_buffers, unsigned int *num_planes,
+ unsigned int sizes[], struct device *alloc_devs[])
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_frame *frame;
+ struct mxc_isi_fmt *fmt;
+ unsigned long wh;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (q->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ if (*num_buffers < 3) {
+ dev_err(dev, "%s at least need 3 buffer\n", __func__);
+ return -EINVAL;
+ }
+ frame = &isi_m2m->dst_f;
+ isi_m2m->req_cap_buf_num = *num_buffers;
+ } else {
+ if (*num_buffers < 1) {
+ dev_err(dev, "%s at least need one buffer\n", __func__);
+ return -EINVAL;
+ }
+ frame = &isi_m2m->src_f;
+ isi_m2m->req_out_buf_num = *num_buffers;
+ }
+
+ fmt = frame->fmt;
+ if (fmt == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < fmt->memplanes; i++)
+ alloc_devs[i] = &isi_m2m->pdev->dev;
+
+ *num_planes = fmt->memplanes;
+ wh = frame->width * frame->height;
+
+ for (i = 0; i < fmt->memplanes; i++) {
+ unsigned int size = (wh * fmt->depth[i]) >> 3;
+
+ if (i == 1 && fmt->fourcc == V4L2_PIX_FMT_NV12)
+ size >>= 1;
+ sizes[i] = max_t(u32, size, frame->sizeimage[i]);
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s, buf_n=%d, planes[%d]->size=%d\n",
+ __func__, *num_buffers, i, sizes[i]);
+ }
+
+ return 0;
+}
+
+static int m2m_vb2_buffer_prepare(struct vb2_buffer *vb2)
+{
+ struct vb2_queue *vq = vb2->vb2_queue;
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vq);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_frame *frame;
+ int i;
+
+ if (vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ frame = &isi_m2m->dst_f;
+ else
+ frame = &isi_m2m->src_f;
+
+ if (frame == NULL)
+ return -EINVAL;
+
+ for (i = 0; i < frame->fmt->memplanes; i++) {
+ unsigned long size = frame->sizeimage[i];
+
+ if (vb2_plane_size(vb2, i) < size) {
+ dev_err(&isi_m2m->pdev->dev,
+ "User buffer too small (%ld < %ld)\n",
+ vb2_plane_size(vb2, i), size);
+ return -EINVAL;
+ }
+ vb2_set_plane_payload(vb2, i, size);
+ }
+
+ return 0;
+}
+
+static void m2m_vb2_buffer_queue(struct vb2_buffer *vb2)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2);
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(vb2->vb2_queue);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+
+ v4l2_m2m_buf_queue(fh->m2m_ctx, vbuf);
+}
+
+static int m2m_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = &mxc_ctx->fh;
+ struct vb2_v4l2_buffer *dst_vbuf;
+ struct v4l2_m2m_buffer *b;
+ struct mxc_isi_buffer *dst_buf;
+ unsigned long flags;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type))
+ return 0;
+
+ if (count < 2) {
+ dev_err(&isi_m2m->pdev->dev, "Need to at leas 2 buffers\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ /* BUF1 */
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (!dst_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__);
+ goto unlock;
+ }
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = 0;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+
+ /* BUF2 */
+ dst_vbuf = v4l2_m2m_next_dst_buf(fh->m2m_ctx);
+ if (!dst_vbuf) {
+ dev_err(&isi_m2m->pdev->dev, "%d: Null dst buf\n", __LINE__);
+ goto unlock;
+ }
+ dst_vbuf->vb2_buf.state = VB2_BUF_STATE_ACTIVE;
+ dst_buf = to_isi_buffer(dst_vbuf);
+ dst_buf->v4l2_buf.sequence = 1;
+ mxc_isi_channel_set_outbuf(mxc_isi, dst_buf);
+ v4l2_m2m_dst_buf_remove(fh->m2m_ctx);
+ b = to_v4l2_m2m_buffer(dst_vbuf);
+ list_add_tail(&b->list, &isi_m2m->out_active);
+
+ isi_m2m->frame_count = 1;
+ isi_m2m->aborting = 0;
+unlock:
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+
+ return 0;
+}
+
+static void m2m_vb2_stop_streaming(struct vb2_queue *q)
+{
+ struct mxc_isi_ctx *mxc_ctx = vb2_get_drv_priv(q);
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ struct vb2_v4l2_buffer *vb2;
+ struct mxc_isi_buffer *buf;
+ unsigned long flags;
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ while ((vb2 = v4l2_m2m_src_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL)
+ v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR);
+
+ while ((vb2 = v4l2_m2m_dst_buf_remove(mxc_ctx->fh.m2m_ctx)) != NULL)
+ v4l2_m2m_buf_done(vb2, VB2_BUF_STATE_ERROR);
+
+ while (!list_empty(&isi_m2m->out_active)) {
+ buf = list_entry(isi_m2m->out_active.next, struct mxc_isi_buffer, list);
+ list_del(&buf->list);
+ vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_ERROR);
+ }
+
+ INIT_LIST_HEAD(&isi_m2m->out_active);
+
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+}
+
+static struct vb2_ops mxc_m2m_vb2_qops = {
+ .queue_setup = m2m_vb2_queue_setup,
+ .buf_prepare = m2m_vb2_buffer_prepare,
+ .buf_queue = m2m_vb2_buffer_queue,
+ .wait_prepare = vb2_ops_wait_prepare,
+ .wait_finish = vb2_ops_wait_finish,
+ .start_streaming = m2m_vb2_start_streaming,
+ .stop_streaming = m2m_vb2_stop_streaming,
+};
+
+static int mxc_m2m_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct mxc_isi_ctx *mxc_ctx = priv;
+ struct mxc_isi_m2m_dev *isi_m2m = mxc_ctx->isi_m2m;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ src_vq->drv_priv = mxc_ctx;
+ src_vq->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ src_vq->ops = &mxc_m2m_vb2_qops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ src_vq->lock = &isi_m2m->lock;
+ src_vq->dev = &isi_m2m->pdev->dev;
+
+ ret = vb2_queue_init(src_vq);
+ if (ret)
+ return ret;
+
+ dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+ dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
+ dst_vq->drv_priv = mxc_ctx;
+ dst_vq->buf_struct_size = sizeof(struct mxc_isi_buffer);
+ dst_vq->ops = &mxc_m2m_vb2_qops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &isi_m2m->lock;
+ dst_vq->dev = &isi_m2m->pdev->dev;
+
+ ret = vb2_queue_init(dst_vq);
+ return ret;
+}
+
+static int mxc_isi_m2m_open(struct file *file)
+{
+ struct video_device *vdev = video_devdata(file);
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_ctx *mxc_ctx = NULL;
+ int ret = 0;
+
+ if (atomic_read(&mxc_isi->usage_count) > 0) {
+ dev_err(dev, "ISI channel[%d] is busy\n", isi_m2m->id);
+ return -EBUSY;
+ }
+
+ if (mutex_lock_interruptible(&isi_m2m->lock))
+ return -ERESTARTSYS;
+
+ mxc_ctx = kzalloc(sizeof(*mxc_ctx), GFP_KERNEL);
+ if (!mxc_ctx) {
+ ret = -ENOMEM;
+ goto unlock;
+ }
+
+ mxc_ctx->isi_m2m = isi_m2m;
+
+ v4l2_fh_init(&mxc_ctx->fh, vdev);
+ file->private_data = &mxc_ctx->fh;
+
+ mxc_ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(isi_m2m->m2m_dev,
+ mxc_ctx,
+ mxc_m2m_queue_init);
+ if (IS_ERR(mxc_ctx->fh.m2m_ctx)) {
+ dev_err(dev, "v4l2_m2m_ctx_init fail\n");
+ ret = PTR_ERR(mxc_ctx->fh.m2m_ctx);
+ v4l2_fh_exit(&mxc_ctx->fh);
+ kfree(mxc_ctx);
+ goto unlock;
+ }
+ v4l2_fh_add(&mxc_ctx->fh);
+
+ pm_runtime_get_sync(dev);
+ if (atomic_inc_return(&mxc_isi->usage_count) == 1)
+ mxc_isi_channel_init(mxc_isi);
+
+ /* lock host data */
+ mutex_lock(&mxc_isi->lock);
+ mxc_isi->m2m_enabled = true;
+ mutex_unlock(&mxc_isi->lock);
+unlock:
+ mutex_unlock(&isi_m2m->lock);
+ return ret;
+}
+
+static int mxc_isi_m2m_release(struct file *file)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct mxc_isi_ctx *mxc_ctx = file_to_ctx(file);
+
+ v4l2_fh_del(&mxc_ctx->fh);
+ v4l2_fh_exit(&mxc_ctx->fh);
+
+ mutex_lock(&isi_m2m->lock);
+ v4l2_m2m_ctx_release(mxc_ctx->fh.m2m_ctx);
+ mutex_unlock(&isi_m2m->lock);
+
+ kfree(mxc_ctx);
+ if (atomic_dec_and_test(&mxc_isi->usage_count))
+ mxc_isi_channel_deinit(mxc_isi);
+
+ mutex_lock(&mxc_isi->lock);
+ mxc_isi->m2m_enabled = false;
+ mutex_unlock(&mxc_isi->lock);
+
+ pm_runtime_put(dev);
+ return 0;
+}
+
+static const struct v4l2_file_operations mxc_isi_m2m_fops = {
+ .owner = THIS_MODULE,
+ .open = mxc_isi_m2m_open,
+ .release = mxc_isi_m2m_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+static int mxc_isi_m2m_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+
+ strlcpy(cap->driver, MXC_ISI_M2M, sizeof(cap->driver));
+ strlcpy(cap->card, MXC_ISI_M2M, sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s.%d",
+ dev_name(&isi_m2m->pdev->dev), isi_m2m->id);
+ cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+ cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)ARRAY_SIZE(mxc_isi_input_formats))
+ return -EINVAL;
+
+ fmt = &mxc_isi_input_formats[f->index];
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_fmt *fmt;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+ if (f->index >= (int)ARRAY_SIZE(mxc_isi_out_formats))
+ return -EINVAL;
+
+ fmt = &mxc_isi_out_formats[f->index];
+ strncpy(f->description, fmt->name, sizeof(f->description) - 1);
+
+ f->pixelformat = fmt->fourcc;
+
+ return 0;
+}
+
+static int mxc_isi_m2m_try_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt = NULL;
+ int i;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) {
+ fmt = &mxc_isi_input_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_input_formats)) {
+ dev_err(dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ dev_err(dev, "%s, width %d, height %d is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_try_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct device *dev = &isi_m2m->pdev->dev;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt = NULL;
+ int i;
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ dev_err(dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (pix->width <= 0 || pix->height <= 0) {
+ dev_err(dev, "%s, width %d, height %d is not valid\n"
+ , __func__, pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_s_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = file->private_data;
+ struct mxc_isi_frame *frame = &isi_m2m->src_f;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ struct vb2_queue *vq;
+ int bpl, i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ if (vb2_is_busy(vq)) {
+ dev_err(&isi_m2m->pdev->dev, "queue busy\n");
+ return -EBUSY;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_input_formats); i++) {
+ fmt = &mxc_isi_input_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_input_formats)) {
+ dev_dbg(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0)
+ return -EINVAL;
+
+ frame->fmt = fmt;
+ frame->height = pix->height;
+ frame->width = pix->width;
+
+ pix->num_planes = fmt->memplanes;
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0)
+ pix->plane_fmt[i].sizeimage = (pix->width * pix->height *
+ fmt->depth[i] >> 3);
+ }
+
+ frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8;
+ frame->sizeimage[0] = frame->height * frame->bytesperline[0];
+
+ set_frame_bounds(frame, pix->width, pix->height);
+ mxc_isi_m2m_config_src(mxc_isi, frame);
+
+ return 0;
+}
+
+static int mxc_isi_m2m_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct v4l2_fh *fh = file->private_data;
+ struct mxc_isi_frame *frame = &isi_m2m->dst_f;
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_fmt *fmt;
+ struct vb2_queue *vq;
+ int bpl, i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ vq = v4l2_m2m_get_vq(fh->m2m_ctx, f->type);
+ if (!vq)
+ return -EINVAL;
+
+ if (vb2_is_busy(vq)) {
+ dev_err(&isi_m2m->pdev->dev, "queue busy\n");
+ return -EBUSY;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(mxc_isi_out_formats); i++) {
+ fmt = &mxc_isi_out_formats[i];
+ if (pix && fmt->fourcc == pix->pixelformat)
+ break;
+ }
+
+ if (i >= ARRAY_SIZE(mxc_isi_out_formats)) {
+ dev_err(&isi_m2m->pdev->dev, "%s, format is not support!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* update out put frame size and formate */
+ if (pix->height <= 0 || pix->width <= 0) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d)\n",
+ pix->width, pix->height);
+ return -EINVAL;
+ }
+
+ if ((pix->pixelformat == V4L2_PIX_FMT_NV12) && ((pix->width / 4) % 2)) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d) for NV12\n",
+ pix->width, pix->height);
+ return -EINVAL;
+ } else if ((pix->pixelformat != V4L2_PIX_FMT_XBGR32) && (pix->width % 2)) {
+ dev_err(&isi_m2m->pdev->dev,
+ "Invalid width or height(w=%d, h=%d) for %.4s\n",
+ pix->width, pix->height, (char *)&pix->pixelformat);
+ return -EINVAL;
+ }
+
+ frame->fmt = fmt;
+ frame->height = pix->height;
+ frame->width = pix->width;
+
+ pix->num_planes = fmt->memplanes;
+ for (i = 0; i < pix->num_planes; i++) {
+ bpl = pix->plane_fmt[i].bytesperline;
+
+ if ((bpl == 0) || (bpl / (fmt->depth[i] >> 3)) < pix->width)
+ pix->plane_fmt[i].bytesperline =
+ (pix->width * fmt->depth[i]) >> 3;
+
+ if (pix->plane_fmt[i].sizeimage == 0) {
+
+ if ((i == 1) && (pix->pixelformat == V4L2_PIX_FMT_NV12))
+ pix->plane_fmt[i].sizeimage =
+ (pix->width * (pix->height >> 1) * fmt->depth[i] >> 3);
+ else
+ pix->plane_fmt[i].sizeimage = (pix->width * pix->height *
+ fmt->depth[i] >> 3);
+ }
+ }
+
+ if (pix->num_planes > 1) {
+ for (i = 0; i < pix->num_planes; i++) {
+ frame->bytesperline[i] = pix->plane_fmt[i].bytesperline;
+ frame->sizeimage[i] = pix->plane_fmt[i].sizeimage;
+ }
+ } else {
+ frame->bytesperline[0] = frame->width * frame->fmt->depth[0] / 8;
+ frame->sizeimage[0] = frame->height * frame->bytesperline[0];
+ }
+
+ /*memcpy(&isi_m2m->pix, pix, sizeof(*pix));*/
+ memcpy(&isi_m2m->pix, pix, sizeof(*pix));
+
+ set_frame_bounds(frame, pix->width, pix->height);
+ mxc_isi_m2m_config_dst(mxc_isi, frame);
+
+ return 0;
+}
+
+static int mxc_isi_m2m_g_fmt_vid_cap(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *frame = &isi_m2m->dst_f;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ return -EINVAL;
+
+ pix->width = frame->o_width;
+ pix->height = frame->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = frame->fmt->fourcc;
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ pix->num_planes = frame->fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = frame->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = frame->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_g_fmt_vid_out(struct file *file, void *fh,
+ struct v4l2_format *f)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ struct mxc_isi_frame *frame = &isi_m2m->src_f;
+ int i;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
+ return -EINVAL;
+
+ pix->width = frame->o_width;
+ pix->height = frame->o_height;
+ pix->field = V4L2_FIELD_NONE;
+ pix->pixelformat = frame->fmt->fourcc;
+ pix->colorspace = V4L2_COLORSPACE_JPEG;
+ pix->num_planes = frame->fmt->memplanes;
+
+ for (i = 0; i < pix->num_planes; ++i) {
+ pix->plane_fmt[i].bytesperline = frame->bytesperline[i];
+ pix->plane_fmt[i].sizeimage = frame->sizeimage[i];
+ }
+
+ return 0;
+}
+
+static int mxc_isi_m2m_streamon(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ struct mxc_isi_frame *src_f, *dst_f;
+ int ret;
+
+ src_f = &isi_m2m->src_f;
+ dst_f = &isi_m2m->dst_f;
+
+ if ((dst_f->width > src_f->width) ||
+ (dst_f->height > src_f->height)) {
+ dev_err(&isi_m2m->pdev->dev, "%s Not support upscale\n", __func__);
+ return -EINVAL;
+ }
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
+ isi_m2m->frame_count = 0;
+ mxc_isi_channel_config(mxc_isi, src_f, dst_f);
+ }
+
+ ret = v4l2_m2m_ioctl_streamon(file, priv, type);
+
+ return ret;
+}
+
+static int mxc_isi_m2m_streamoff(struct file *file, void *priv,
+ enum v4l2_buf_type type)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = video_drvdata(file);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ int ret;
+
+ if (type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
+ mxc_isi_channel_disable(mxc_isi);
+
+ ret = v4l2_m2m_ioctl_streamoff(file, priv, type);
+
+ return ret;
+}
+
+static const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = {
+ .vidioc_querycap = mxc_isi_m2m_querycap,
+
+ .vidioc_enum_fmt_vid_cap = mxc_isi_m2m_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = mxc_isi_m2m_enum_fmt_vid_out,
+
+ .vidioc_try_fmt_vid_cap_mplane = mxc_isi_m2m_try_fmt_vid_cap,
+ .vidioc_try_fmt_vid_out_mplane = mxc_isi_m2m_try_fmt_vid_out,
+
+ .vidioc_s_fmt_vid_cap_mplane = mxc_isi_m2m_s_fmt_vid_cap,
+ .vidioc_s_fmt_vid_out_mplane = mxc_isi_m2m_s_fmt_vid_out,
+
+ .vidioc_g_fmt_vid_cap_mplane = mxc_isi_m2m_g_fmt_vid_cap,
+ .vidioc_g_fmt_vid_out_mplane = mxc_isi_m2m_g_fmt_vid_out,
+
+ .vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
+ .vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
+ .vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
+ .vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
+ .vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+
+ .vidioc_streamon = mxc_isi_m2m_streamon,
+ .vidioc_streamoff = mxc_isi_m2m_streamoff,
+};
+
+/*
+ * V4L2 controls handling
+ */
+#define ctrl_to_mxc_isi_m2m(__ctrl) \
+ container_of((__ctrl)->handler, struct mxc_isi_m2m_dev, ctrls.handler)
+
+static int mxc_isi_m2m_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl);
+ struct mxc_isi_dev *mxc_isi = mxc_isi_get_hostdata(isi_m2m->pdev);
+ unsigned long flags;
+ int ret = 0;
+
+ dev_dbg(&isi_m2m->pdev->dev, "%s\n", __func__);
+
+ if (ctrl->flags & V4L2_CTRL_FLAG_INACTIVE)
+ return 0;
+
+ spin_lock_irqsave(&mxc_isi->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_HFLIP:
+ if (ctrl->val < 0) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->hflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_VFLIP:
+ if (ctrl->val < 0) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->vflip = (ctrl->val > 0) ? 1 : 0;
+ break;
+
+ case V4L2_CID_ALPHA_COMPONENT:
+ if (ctrl->val < 0 || ctrl->val > 255) {
+ ret = -EINVAL;
+ goto unlock;
+ }
+ mxc_isi->alpha = ctrl->val;
+ mxc_isi->alphaen = 1;
+ break;
+
+ default:
+ dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n", __func__, ctrl->id);
+ ret = -EINVAL;
+ }
+
+unlock:
+ spin_unlock_irqrestore(&mxc_isi->slock, flags);
+ return ret;
+}
+
+static int mxc_isi_m2m_g_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = ctrl_to_mxc_isi_m2m(ctrl);
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&isi_m2m->slock, flags);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+ ctrl->val = isi_m2m->req_cap_buf_num;
+ break;
+ case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
+ ctrl->val = isi_m2m->req_out_buf_num;
+ break;
+ default:
+ dev_err(&isi_m2m->pdev->dev, "%s: Not support %d CID\n",
+ __func__, ctrl->id);
+ ret = -EINVAL;
+ }
+
+ spin_unlock_irqrestore(&isi_m2m->slock, flags);
+ return ret;
+
+}
+
+static const struct v4l2_ctrl_ops mxc_isi_m2m_ctrl_ops = {
+ .s_ctrl = mxc_isi_m2m_s_ctrl,
+ .g_volatile_ctrl = mxc_isi_m2m_g_ctrl,
+};
+
+static int mxc_isi_m2m_ctrls_create(struct mxc_isi_m2m_dev *isi_m2m)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls;
+ struct v4l2_ctrl_handler *handler = &ctrls->handler;
+
+ if (isi_m2m->ctrls.ready)
+ return 0;
+
+ v4l2_ctrl_handler_init(handler, 4);
+
+ ctrls->hflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_HFLIP, 0, 1, 1, 0);
+ ctrls->vflip = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_VFLIP, 0, 1, 1, 0);
+ ctrls->alpha = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_ALPHA_COMPONENT, 0, 0xff, 1, 0);
+ ctrls->num_cap_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 3, 16, 1, 3);
+ ctrls->num_out_buf = v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctrl_ops,
+ V4L2_CID_MIN_BUFFERS_FOR_OUTPUT, 1, 16, 1, 1);
+
+ if (!handler->error)
+ ctrls->ready = true;
+
+ return handler->error;
+
+}
+
+void mxc_isi_m2m_ctrls_delete(struct mxc_isi_m2m_dev *isi_m2m)
+{
+ struct mxc_isi_ctrls *ctrls = &isi_m2m->ctrls;
+
+ if (ctrls->ready) {
+ v4l2_ctrl_handler_free(&ctrls->handler);
+ ctrls->ready = false;
+ ctrls->alpha = NULL;
+ }
+}
+
+static int isi_m2m_probe(struct platform_device *pdev)
+{
+ struct mxc_isi_dev *mxc_isi;
+ struct mxc_isi_m2m_dev *isi_m2m;
+ struct v4l2_device *v4l2_dev;
+ struct video_device *vdev;
+ int ret = -ENOMEM;
+
+ isi_m2m = devm_kzalloc(&pdev->dev, sizeof(*isi_m2m), GFP_KERNEL);
+ if (!isi_m2m)
+ return -ENOMEM;
+ isi_m2m->pdev = pdev;
+
+ pdev->dev.parent = mxc_isi_dev_get_parent(pdev);
+ if (!pdev->dev.parent) {
+ dev_info(&pdev->dev, "deferring %s device registration\n",
+ dev_name(&pdev->dev));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_isi = mxc_isi_get_hostdata(pdev);
+ if (!mxc_isi) {
+ dev_info(&pdev->dev, "deferring %s device registration\n",
+ dev_name(&pdev->dev));
+ return -EPROBE_DEFER;
+ }
+ mxc_isi->isi_m2m = isi_m2m;
+ isi_m2m->id = mxc_isi->id;
+
+ spin_lock_init(&isi_m2m->slock);
+ mutex_init(&isi_m2m->lock);
+
+ /* m2m */
+ isi_m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops);
+ if (IS_ERR(isi_m2m->m2m_dev)) {
+ dev_err(&pdev->dev, "%s fail to get m2m device\n", __func__);
+ return PTR_ERR(isi_m2m->m2m_dev);
+ }
+
+ /* V4L2 device */
+ v4l2_dev = &isi_m2m->v4l2_dev;
+ strlcpy(v4l2_dev->name, "mx8-isi-m2m", sizeof(v4l2_dev->name));
+
+ ret = v4l2_device_register(&pdev->dev, v4l2_dev);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Failed to register v4l2_device\n");
+ return -EINVAL;
+ }
+
+ INIT_LIST_HEAD(&isi_m2m->out_active);
+
+ /* Video device */
+ vdev = &isi_m2m->vdev;
+ memset(vdev, 0, sizeof(*vdev));
+ snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.m2m", isi_m2m->id);
+
+ vdev->fops = &mxc_isi_m2m_fops;
+ vdev->ioctl_ops = &mxc_isi_m2m_ioctl_ops;
+ vdev->v4l2_dev = v4l2_dev;
+ vdev->minor = -1;
+ vdev->release = video_device_release_empty;
+ vdev->vfl_dir = VFL_DIR_M2M;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+
+ ret = mxc_isi_m2m_ctrls_create(isi_m2m);
+ if (ret)
+ goto free_m2m;
+
+ ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "%s fail to register video device\n", __func__);
+ goto ctrl_free;
+ }
+
+ vdev->ctrl_handler = &isi_m2m->ctrls.handler;
+ video_set_drvdata(vdev, isi_m2m);
+ platform_set_drvdata(pdev, isi_m2m);
+ pm_runtime_enable(&pdev->dev);
+
+ dev_info(&pdev->dev, "Register m2m success for ISI.%d\n", isi_m2m->id);
+
+ return 0;
+
+ctrl_free:
+ mxc_isi_m2m_ctrls_delete(isi_m2m);
+free_m2m:
+ v4l2_m2m_release(isi_m2m->m2m_dev);
+ return ret;
+
+}
+
+static int isi_m2m_remove(struct platform_device *pdev)
+{
+ struct mxc_isi_m2m_dev *isi_m2m = platform_get_drvdata(pdev);
+ struct video_device *vdev = &isi_m2m->vdev;
+
+ if (video_is_registered(vdev)) {
+ video_unregister_device(vdev);
+ mxc_isi_m2m_ctrls_delete(isi_m2m);
+ media_entity_cleanup(&vdev->entity);
+ }
+ v4l2_m2m_release(isi_m2m->m2m_dev);
+ v4l2_device_unregister(&isi_m2m->v4l2_dev);
+ pm_runtime_disable(&isi_m2m->pdev->dev);
+
+ return 0;
+}
+
+static const struct of_device_id isi_m2m_of_match[] = {
+ {.compatible = "imx-isi-m2m",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, isi_m2m_of_match);
+
+static struct platform_driver isi_m2m_driver = {
+ .probe = isi_m2m_probe,
+ .remove = isi_m2m_remove,
+ .driver = {
+ .of_match_table = isi_m2m_of_match,
+ .name = "isi-m2m",
+ },
+};
+
+static int __init mxc_isi_m2m_init(void)
+{
+ return platform_driver_register(&isi_m2m_driver);
+}
+late_initcall(mxc_isi_m2m_init);
+
+static void __exit mxc_isi_m2m_exit(void)
+{
+ platform_driver_unregister(&isi_m2m_driver);
+}
+module_exit(mxc_isi_m2m_exit);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("IMX8 Image Sensor Interface memory to memory driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("ISI M2M");
+MODULE_VERSION("1.0");
diff --git a/drivers/staging/media/imx/imx8-media-dev.c b/drivers/staging/media/imx/imx8-media-dev.c
new file mode 100644
index 000000000000..3d1457faadd5
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-media-dev.c
@@ -0,0 +1,1076 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Media Controller Driver for NXP IMX8QXP/QM SOC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <linux/bug.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/i2c.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/list.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <media/v4l2-device.h>
+#include <linux/types.h>
+#include <linux/slab.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-async.h>
+#include <media/v4l2-ctrls.h>
+#include <media/media-device.h>
+
+#include "imx8-common.h"
+
+#define MXC_MD_DRIVER_NAME "mxc-md"
+#define ISI_OF_NODE_NAME "isi"
+#define MIPI_CSI2_OF_NODE_NAME "csi"
+
+#define MXC_MAX_SENSORS 3
+#define MXC_MIPI_CSI2_MAX_DEVS 2
+
+#define MXC_NAME_LENS 32
+
+/*
+ * The subdevices' group IDs.
+ */
+#define GRP_ID_MXC_SENSOR BIT(8)
+#define GRP_ID_MXC_ISI BIT(9)
+#define GRP_ID_MXC_MIPI_CSI2 BIT(11)
+#define GRP_ID_MXC_HDMI_RX BIT(12)
+#define GRP_ID_MXC_MJPEG_DEC BIT(13)
+#define GRP_ID_MXC_MJPEG_ENC BIT(14)
+#define GRP_ID_MXC_PARALLEL_CSI BIT(15)
+
+enum mxc_subdev_index {
+ IDX_SENSOR,
+ IDX_ISI,
+ IDX_MIPI_CSI2,
+ IDX_HDMI_RX,
+ IDX_MJPEG_ENC,
+ IDX_MJPEG_DEC,
+ IDX_PARALLEL_CSI,
+ IDX_MAX,
+};
+
+struct mxc_isi_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+ u32 interface[MAX_PORTS];
+
+ char vdev_name[MXC_NAME_LENS];
+ char sd_name[MXC_NAME_LENS];
+ int id;
+};
+
+struct mxc_mipi_csi2_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+
+ char sd_name[MXC_NAME_LENS];
+ int id;
+ bool vchannel;
+};
+
+struct mxc_parallel_csi_info {
+ struct v4l2_subdev *sd;
+ struct media_entity *entity;
+ struct device_node *node;
+
+ char sd_name[MXC_NAME_LENS];
+ int id;
+};
+
+struct mxc_sensor_info {
+ int id;
+ struct v4l2_subdev *sd;
+ struct v4l2_async_subdev asd;
+ bool mipi_mode;
+};
+
+struct mxc_md {
+ struct mxc_isi_info mxc_isi[MXC_ISI_MAX_DEVS];
+ struct mxc_mipi_csi2_info mipi_csi2[MXC_MIPI_CSI2_MAX_DEVS];
+ struct mxc_parallel_csi_info pcsidev;
+ struct mxc_sensor_info sensor[MXC_MAX_SENSORS];
+
+ int link_status;
+ int num_sensors;
+ int valid_num_sensors;
+ unsigned int nr_isi;
+ bool parallel_csi;
+
+ struct media_device media_dev;
+ struct v4l2_device v4l2_dev;
+ struct platform_device *pdev;
+
+ struct v4l2_async_notifier subdev_notifier;
+ struct v4l2_async_subdev *async_subdevs[MXC_MAX_SENSORS];
+};
+
+static inline struct mxc_md *notifier_to_mxc_md(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct mxc_md, subdev_notifier);
+};
+
+static void mxc_md_unregister_entities(struct mxc_md *mxc_md)
+{
+ struct mxc_parallel_csi_info *pcsidev = &mxc_md->pcsidev;
+ int i;
+
+ for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
+ struct mxc_isi_info *isi = &mxc_md->mxc_isi[i];
+
+ if (!isi->sd)
+ continue;
+ v4l2_device_unregister_subdev(isi->sd);
+ memset(isi, 0, sizeof(*isi));
+ }
+
+ for (i = 0; i < MXC_MIPI_CSI2_MAX_DEVS; i++) {
+ struct mxc_mipi_csi2_info *mipi_csi2 = &mxc_md->mipi_csi2[i];
+ if (!mipi_csi2->sd)
+ continue;
+ v4l2_device_unregister_subdev(mipi_csi2->sd);
+ memset(mipi_csi2, 0, sizeof(*mipi_csi2));
+ }
+
+ if (pcsidev->sd)
+ v4l2_device_unregister_subdev(pcsidev->sd);
+
+ v4l2_info(&mxc_md->v4l2_dev, "Unregistered all entities\n");
+}
+
+static struct media_entity *find_entity_by_name(struct mxc_md *mxc_md,
+ const char *name)
+{
+ struct media_entity *ent = NULL;
+
+ if (!mxc_md || !name)
+ return NULL;
+
+ media_device_for_each_entity(ent, &mxc_md->media_dev) {
+ if (!strcmp(ent->name, name)) {
+ dev_dbg(&mxc_md->pdev->dev,
+ "%s entity is found\n", ent->name);
+ return ent;
+ }
+ }
+
+ return NULL;
+}
+
+static int mxc_md_do_clean(struct mxc_md *mxc_md, struct media_pad *pad)
+{
+ struct device *dev = &mxc_md->pdev->dev;
+ struct media_pad *remote_pad;
+ struct v4l2_subdev *subdev;
+
+ if (!pad->entity->num_links)
+ return 0;
+
+ remote_pad = media_entity_remote_pad(pad);
+ if (remote_pad == NULL) {
+ dev_err(dev, "%s get remote pad fail\n", __func__);
+ return -ENODEV;
+ }
+
+ subdev = media_entity_to_v4l2_subdev(remote_pad->entity);
+ if (subdev == NULL) {
+ dev_err(dev, "%s media entity to v4l2 subdev fail\n", __func__);
+ return -ENODEV;
+ }
+
+ v4l2_device_unregister_subdev(subdev);
+ media_entity_cleanup(&subdev->entity);
+
+ pr_info("clean ISI channel: %s\n", subdev->name);
+
+ return 0;
+}
+
+static int mxc_md_clean_channel(struct mxc_md *mxc_md, int index)
+{
+ struct mxc_sensor_info *sensor = &mxc_md->sensor[index];
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ struct media_pad *local_pad;
+ struct media_entity *local_en;
+ u32 i, mipi_vc = 0;
+ int ret;
+
+ if (mxc_md->mipi_csi2[index].sd) {
+ mipi_csi2 = &mxc_md->mipi_csi2[index];
+
+ if (mipi_csi2->vchannel == true)
+ mipi_vc = 4;
+ else
+ mipi_vc = 1;
+
+ local_en = &mipi_csi2->sd->entity;
+ if (local_en == NULL)
+ return -ENODEV;
+
+ for (i = 0; i < mipi_vc; i++) {
+ local_pad = &local_en->pads[MXC_MIPI_CSI2_VC0_PAD_SOURCE + i];
+ ret = mxc_md_do_clean(mxc_md, local_pad);
+ if (ret < 0)
+ return -ENODEV;
+ }
+ } else if (mxc_md->parallel_csi && !sensor->mipi_mode) {
+ pcsidev = &mxc_md->pcsidev;
+ if (pcsidev->sd == NULL)
+ return -ENODEV;
+
+ local_en = &pcsidev->sd->entity;
+ if (local_en == NULL)
+ return -ENODEV;
+
+ local_pad = &local_en->pads[MXC_PARALLEL_CSI_PAD_SOURCE];
+ ret = mxc_md_do_clean(mxc_md, local_pad);
+ if (ret < 0)
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
+static int mxc_md_clean_unlink_channels(struct mxc_md *mxc_md)
+{
+ struct mxc_sensor_info *sensor;
+ int num_subdevs = mxc_md->num_sensors;
+ int i, ret;
+
+ for (i = 0; i < num_subdevs; i++) {
+ sensor = &mxc_md->sensor[i];
+ if (sensor->sd != NULL)
+ continue;
+
+ ret = mxc_md_clean_channel(mxc_md, i);
+ if (ret < 0) {
+ pr_err("%s: clean channel fail(%d)\n", __func__, i);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static void mxc_md_unregister_all(struct mxc_md *mxc_md)
+{
+ struct mxc_isi_info *mxc_isi;
+ int i;
+
+ for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
+ mxc_isi = &mxc_md->mxc_isi[i];
+ if (!mxc_isi->sd)
+ continue;
+
+ v4l2_device_unregister_subdev(mxc_isi->sd);
+ media_entity_cleanup(&mxc_isi->sd->entity);
+
+ pr_info("unregister ISI channel: %s\n", mxc_isi->sd->name);
+ }
+}
+
+static int mxc_md_create_links(struct mxc_md *mxc_md)
+{
+ struct media_entity *source, *sink;
+ struct mxc_isi_info *mxc_isi;
+ struct mxc_sensor_info *sensor;
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ int num_sensors = mxc_md->num_sensors;
+ int i, j, ret = 0;
+ u16 source_pad, sink_pad;
+ u32 flags;
+ u32 mipi_vc = 0;
+
+ /* Create links between each ISI's subdev and video node */
+ flags = MEDIA_LNK_FL_ENABLED;
+ for (i = 0; i < MXC_ISI_MAX_DEVS; i++) {
+ mxc_isi = &mxc_md->mxc_isi[i];
+ if (!mxc_isi->sd)
+ continue;
+
+ /* Connect ISI source to video device */
+ source = find_entity_by_name(mxc_md, mxc_isi->sd_name);
+ sink = find_entity_by_name(mxc_md, mxc_isi->vdev_name);
+ sink_pad = 0;
+
+ switch (mxc_isi->interface[OUT_PORT]) {
+ case ISI_OUTPUT_INTERFACE_DC0:
+ source_pad = MXC_ISI_SD_PAD_SOURCE_DC0;
+ break;
+ case ISI_OUTPUT_INTERFACE_DC1:
+ source_pad = MXC_ISI_SD_PAD_SOURCE_DC1;
+ break;
+ case ISI_OUTPUT_INTERFACE_MEM:
+ source_pad = MXC_ISI_SD_PAD_SOURCE_MEM;
+ break;
+ default:
+ v4l2_err(&mxc_md->v4l2_dev, "Wrong output interface: %x\n",
+ mxc_isi->interface[OUT_PORT]);
+ return -EINVAL;
+ }
+
+ ret = media_create_pad_link(source, source_pad,
+ sink, sink_pad, flags);
+ if (ret) {
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Failed created link [%s] %c> [%s]\n",
+ source->name, flags ? '=' : '-', sink->name);
+ break;
+ }
+
+ /* Notify capture subdev entity ,ISI cap link setup */
+ ret = media_entity_call(source, link_setup, &source->pads[source_pad],
+ &sink->pads[sink_pad], flags);
+ if (ret) {
+ v4l2_err(&mxc_md->v4l2_dev,
+ "failed call link_setup [%s] %c> [%s]\n",
+ source->name, flags ? '=' : '-', sink->name);
+ break;
+ }
+
+ v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n",
+ source->name, flags ? '=' : '-', sink->name);
+
+ /* Connect MIPI/HDMI/Mem source to ISI sink */
+ sink = find_entity_by_name(mxc_md, mxc_isi->sd_name);
+
+ switch (mxc_isi->interface[IN_PORT]) {
+ case ISI_INPUT_INTERFACE_MIPI0_CSI2:
+ mipi_csi2 = &mxc_md->mipi_csi2[0];
+ if (!mipi_csi2->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+
+ switch (mxc_isi->interface[SUB_IN_PORT]) {
+ case ISI_INPUT_SUB_INTERFACE_VC1:
+ source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC1;
+ break;
+ case ISI_INPUT_SUB_INTERFACE_VC2:
+ source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC2;
+ break;
+ case ISI_INPUT_SUB_INTERFACE_VC3:
+ source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC3;
+ break;
+ default:
+ source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI0_VC0;
+ break;
+ }
+ break;
+
+ case ISI_INPUT_INTERFACE_MIPI1_CSI2:
+ mipi_csi2 = &mxc_md->mipi_csi2[1];
+ if (!mipi_csi2->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+
+ switch (mxc_isi->interface[SUB_IN_PORT]) {
+ case ISI_INPUT_SUB_INTERFACE_VC1:
+ source_pad = MXC_MIPI_CSI2_VC1_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC1;
+ break;
+ case ISI_INPUT_SUB_INTERFACE_VC2:
+ source_pad = MXC_MIPI_CSI2_VC2_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC2;
+ break;
+ case ISI_INPUT_SUB_INTERFACE_VC3:
+ source_pad = MXC_MIPI_CSI2_VC3_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC3;
+ break;
+ default:
+ source_pad = MXC_MIPI_CSI2_VC0_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_MIPI1_VC0;
+ break;
+ }
+ break;
+
+ case ISI_INPUT_INTERFACE_PARALLEL_CSI:
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev->sd)
+ continue;
+ source = find_entity_by_name(mxc_md, pcsidev->sd_name);
+ source_pad = MXC_PARALLEL_CSI_PAD_SOURCE;
+ sink_pad = MXC_ISI_SD_PAD_SINK_PARALLEL_CSI;
+ break;
+
+ case ISI_INPUT_INTERFACE_HDMI:
+ case ISI_INPUT_INTERFACE_DC0:
+ case ISI_INPUT_INTERFACE_DC1:
+ case ISI_INPUT_INTERFACE_MEM:
+ default:
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Not support input interface: %x\n",
+ mxc_isi->interface[IN_PORT]);
+ return -EINVAL;
+ }
+
+ /* Create link MIPI/HDMI to ISI */
+ ret = media_create_pad_link(source, source_pad, sink, sink_pad, flags);
+ if (ret) {
+ v4l2_err(&mxc_md->v4l2_dev,
+ "created link [%s] %c> [%s] fail\n",
+ source->name, flags ? '=' : '-', sink->name);
+ break;
+ }
+
+ /* Notify ISI subdev entity */
+ ret = media_entity_call(sink, link_setup,
+ &sink->pads[sink_pad],
+ &source->pads[source_pad], 0);
+ if (ret)
+ break;
+
+ /* Notify MIPI/HDMI entity */
+ ret = media_entity_call(source, link_setup,
+ &source->pads[source_pad],
+ &sink->pads[sink_pad], 0);
+ if (ret)
+ break;
+
+ v4l2_info(&mxc_md->v4l2_dev, "created link [%s] %c> [%s]\n",
+ source->name, flags ? '=' : '-', sink->name);
+ }
+
+ /* Connect MIPI Sensor to MIPI CSI2 */
+ for (i = 0; i < num_sensors; i++) {
+ sensor = &mxc_md->sensor[i];
+ if (!sensor || !sensor->sd)
+ continue;
+
+ if (mxc_md->parallel_csi && !sensor->mipi_mode) {
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev->sd)
+ continue;
+ source = &sensor->sd->entity;
+ sink = find_entity_by_name(mxc_md, pcsidev->sd_name);
+
+ source_pad = 0;
+ sink_pad = MXC_PARALLEL_CSI_PAD_SINK;
+
+ ret = media_create_pad_link(source,
+ source_pad,
+ sink,
+ sink_pad,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ return ret;
+
+ /* Notify MIPI subdev entity */
+ ret = media_entity_call(sink, link_setup,
+ &sink->pads[sink_pad],
+ &source->pads[source_pad], 0);
+ if (ret)
+ return ret;
+
+ /* Notify MIPI sensor subdev entity */
+ ret = media_entity_call(source, link_setup,
+ &source->pads[source_pad],
+ &sink->pads[sink_pad],
+ 0);
+ if (ret)
+ return ret;
+ v4l2_info(&mxc_md->v4l2_dev,
+ "created link [%s] => [%s]\n",
+ source->name, sink->name);
+ } else if (mxc_md->mipi_csi2[sensor->id].sd) {
+ mipi_csi2 = &mxc_md->mipi_csi2[sensor->id];
+
+ source = &sensor->sd->entity;
+ sink = find_entity_by_name(mxc_md, mipi_csi2->sd_name);
+ source_pad = 0;
+ sink_pad = source_pad;
+
+ mipi_vc = (mipi_csi2->vchannel) ? 4 : 1;
+ for (j = 0; j < mipi_vc; j++) {
+ ret = media_create_pad_link(source,
+ source_pad + j,
+ sink,
+ sink_pad + j,
+ MEDIA_LNK_FL_IMMUTABLE |
+ MEDIA_LNK_FL_ENABLED);
+ if (ret)
+ return ret;
+
+ /* Notify MIPI subdev entity */
+ ret = media_entity_call(sink, link_setup,
+ &sink->pads[sink_pad + j],
+ &source->pads[source_pad + j],
+ 0);
+ if (ret)
+ return ret;
+
+ /* Notify MIPI sensor subdev entity */
+ ret = media_entity_call(source, link_setup,
+ &source->pads[source_pad + j],
+ &sink->pads[sink_pad + j],
+ 0);
+ if (ret)
+ return ret;
+ }
+ v4l2_info(&mxc_md->v4l2_dev,
+ "created link [%s] => [%s]\n",
+ source->name, sink->name);
+ }
+ }
+ dev_info(&mxc_md->pdev->dev, "%s\n", __func__);
+ return 0;
+}
+
+static int subdev_notifier_bound(struct v4l2_async_notifier *notifier,
+ struct v4l2_subdev *sd,
+ struct v4l2_async_subdev *asd)
+{
+ struct mxc_md *mxc_md = notifier_to_mxc_md(notifier);
+ struct mxc_sensor_info *sensor = NULL;
+ int i;
+
+ dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__);
+
+ /* Find platform data for this sensor subdev */
+ for (i = 0; i < ARRAY_SIZE(mxc_md->sensor); i++) {
+ if (mxc_md->sensor[i].asd.match.fwnode ==
+ of_fwnode_handle(sd->dev->of_node)) {
+ sensor = &mxc_md->sensor[i];
+ }
+ }
+
+ if (!sensor)
+ return -EINVAL;
+
+ sd->grp_id = GRP_ID_MXC_SENSOR;
+ sensor->sd = sd;
+ mxc_md->valid_num_sensors++;
+
+ v4l2_info(&mxc_md->v4l2_dev, "Registered sensor subdevice: %s (%d)\n",
+ sd->name, mxc_md->valid_num_sensors);
+
+ return 0;
+}
+
+static int subdev_notifier_complete(struct v4l2_async_notifier *notifier)
+{
+ struct mxc_md *mxc_md = notifier_to_mxc_md(notifier);
+ int ret;
+
+ dev_dbg(&mxc_md->pdev->dev, "%s\n", __func__);
+ mutex_lock(&mxc_md->media_dev.graph_mutex);
+
+ ret = mxc_md_create_links(mxc_md);
+ if (ret < 0)
+ goto unlock;
+
+ mxc_md->link_status = 1;
+
+ ret = v4l2_device_register_subdev_nodes(&mxc_md->v4l2_dev);
+unlock:
+ mutex_unlock(&mxc_md->media_dev.graph_mutex);
+ if (ret < 0) {
+ v4l2_err(&mxc_md->v4l2_dev, "%s error exit\n", __func__);
+ return ret;
+ }
+
+ return media_device_register(&mxc_md->media_dev);
+}
+
+static const struct v4l2_async_notifier_operations sd_async_notifier_ops = {
+ .bound = subdev_notifier_bound,
+ .complete = subdev_notifier_complete,
+};
+
+void mxc_sensor_notify(struct v4l2_subdev *sd, unsigned int notification,
+ void *arg)
+{
+}
+
+static int mxc_md_link_notify(struct media_link *link, unsigned int flags,
+ unsigned int notification)
+{
+ return 0;
+}
+
+static const struct media_device_ops mxc_md_ops = {
+ .link_notify = mxc_md_link_notify,
+};
+
+static struct mxc_isi_info *mxc_md_parse_isi_entity(struct mxc_md *mxc_md,
+ struct device_node *node)
+{
+ struct device *dev = &mxc_md->pdev->dev;
+ struct mxc_isi_info *mxc_isi;
+ struct device_node *child;
+ int ret, id = -1;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ id = of_alias_get_id(node, ISI_OF_NODE_NAME);
+ if (id < 0 || id >= MXC_ISI_MAX_DEVS)
+ return NULL;
+
+ mxc_isi = &mxc_md->mxc_isi[id];
+
+ child = of_get_child_by_name(node, "cap_device");
+ if (!child) {
+ dev_err(dev, "Can not get child node for %s.%d\n",
+ ISI_OF_NODE_NAME, id);
+ return NULL;
+ }
+ of_node_put(child);
+
+ mxc_isi->id = id;
+ mxc_isi->node = child;
+ sprintf(mxc_isi->sd_name, "mxc_isi.%d", mxc_isi->id);
+ sprintf(mxc_isi->vdev_name, "mxc_isi.%d.capture", mxc_isi->id);
+
+ ret = of_property_read_u32_array(node, "interface",
+ mxc_isi->interface, 3);
+ if (ret < 0) {
+ dev_err(dev, "%s node has not interface property\n", child->name);
+ return NULL;
+ }
+
+ return mxc_isi;
+}
+
+static struct mxc_mipi_csi2_info *
+mxc_md_parse_csi_entity(struct mxc_md *mxc_md,
+ struct device_node *node)
+{
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ int id = -1;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ id = of_alias_get_id(node, MIPI_CSI2_OF_NODE_NAME);
+ if (id < 0 || id >= MXC_MIPI_CSI2_MAX_DEVS)
+ return NULL;
+
+ mipi_csi2 = &mxc_md->mipi_csi2[id];
+ if (!mipi_csi2)
+ return NULL;
+
+ mipi_csi2->vchannel = of_property_read_bool(node, "virtual-channel");
+ mipi_csi2->id = id;
+ mipi_csi2->node = node;
+ sprintf(mipi_csi2->sd_name, "mxc-mipi-csi2.%d", mipi_csi2->id);
+
+ return mipi_csi2;
+}
+
+static struct mxc_parallel_csi_info*
+mxc_md_parse_pcsi_entity(struct mxc_md *mxc_md, struct device_node *node)
+{
+ struct mxc_parallel_csi_info *pcsidev;
+
+ if (!mxc_md || !node)
+ return NULL;
+
+ pcsidev = &mxc_md->pcsidev;
+ if (!pcsidev)
+ return NULL;
+
+ pcsidev->node = node;
+ sprintf(pcsidev->sd_name, "mxc-parallel-csi");
+
+ return pcsidev;
+}
+
+static struct v4l2_subdev *get_subdev_by_node(struct device_node *node)
+{
+ struct platform_device *pdev;
+ struct v4l2_subdev *sd = NULL;
+ struct device *dev;
+ void *drvdata;
+
+ pdev = of_find_device_by_node(node);
+ if (!pdev)
+ return NULL;
+
+ dev = &pdev->dev;
+ device_lock(&pdev->dev);
+ if (!dev->driver || !try_module_get(dev->driver->owner))
+ goto dev_unlock;
+
+ drvdata = dev_get_drvdata(dev);
+ if (!drvdata)
+ goto module_put;
+
+ sd = (struct v4l2_subdev *)drvdata;
+
+module_put:
+ module_put(dev->driver->owner);
+dev_unlock:
+ device_unlock(dev);
+ return sd;
+}
+
+static int register_isi_entity(struct mxc_md *mxc_md,
+ struct mxc_isi_info *mxc_isi)
+{
+ struct v4l2_subdev *sd;
+ int ret;
+
+ sd = get_subdev_by_node(mxc_isi->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ mxc_isi->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ if (mxc_isi->id >= MXC_ISI_MAX_DEVS)
+ return -EBUSY;
+
+ sd->grp_id = GRP_ID_MXC_ISI;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ mxc_isi->sd = sd;
+ else
+ v4l2_err(&mxc_md->v4l2_dev, "Failed to register ISI.%d (%d)\n",
+ mxc_isi->id, ret);
+ return ret;
+}
+
+static int register_mipi_csi2_entity(struct mxc_md *mxc_md,
+ struct mxc_mipi_csi2_info *mipi_csi2)
+{
+ struct v4l2_subdev *sd;
+ int ret;
+
+ sd = get_subdev_by_node(mipi_csi2->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ mipi_csi2->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ if (mipi_csi2->id >= MXC_MIPI_CSI2_MAX_DEVS)
+ return -EBUSY;
+
+ sd->grp_id = GRP_ID_MXC_MIPI_CSI2;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ mipi_csi2->sd = sd;
+ else
+ v4l2_err(&mxc_md->v4l2_dev, "Failed to register MIPI-CSI.%d (%d)\n",
+ mipi_csi2->id, ret);
+ return ret;
+}
+
+static int register_parallel_csi_entity(struct mxc_md *mxc_md,
+ struct mxc_parallel_csi_info *pcsidev)
+{
+ struct v4l2_subdev *sd;
+ int ret;
+
+ sd = get_subdev_by_node(pcsidev->node);
+ if (!sd) {
+ dev_info(&mxc_md->pdev->dev,
+ "deferring %s device registration\n",
+ pcsidev->node->name);
+ return -EPROBE_DEFER;
+ }
+
+ sd->grp_id = GRP_ID_MXC_PARALLEL_CSI;
+
+ ret = v4l2_device_register_subdev(&mxc_md->v4l2_dev, sd);
+ if (!ret)
+ pcsidev->sd = sd;
+ else
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Failed to register Parallel (%d)\n", ret);
+ return ret;
+}
+
+
+static int mxc_md_register_platform_entity(struct mxc_md *mxc_md,
+ struct device_node *node,
+ int plat_entity)
+{
+ struct device *dev = &mxc_md->pdev->dev;
+ struct mxc_isi_info *isi;
+ struct mxc_mipi_csi2_info *mipi_csi2;
+ struct mxc_parallel_csi_info *pcsidev;
+ int ret = -EINVAL;
+
+ switch (plat_entity) {
+ case IDX_ISI:
+ isi = mxc_md_parse_isi_entity(mxc_md, node);
+ if (!isi)
+ return -ENODEV;
+ ret = register_isi_entity(mxc_md, isi);
+ break;
+ case IDX_MIPI_CSI2:
+ mipi_csi2 = mxc_md_parse_csi_entity(mxc_md, node);
+ if (!mipi_csi2)
+ return -ENODEV;
+ ret = register_mipi_csi2_entity(mxc_md, mipi_csi2);
+ break;
+ case IDX_PARALLEL_CSI:
+ pcsidev = mxc_md_parse_pcsi_entity(mxc_md, node);
+ if (!pcsidev)
+ return -ENODEV;
+ ret = register_parallel_csi_entity(mxc_md, pcsidev);
+ break;
+ default:
+ dev_err(dev, "Invalid platform entity (%d)", plat_entity);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int mxc_md_register_platform_entities(struct mxc_md *mxc_md,
+ struct device_node *parent)
+{
+ struct device_node *node;
+ int ret = 0;
+
+ for_each_available_child_of_node(parent, node) {
+ int plat_entity = -1;
+
+ if (!of_device_is_available(node))
+ continue;
+
+ /* If driver of any entity isn't ready try all again later. */
+ if (!strcmp(node->name, ISI_OF_NODE_NAME))
+ plat_entity = IDX_ISI;
+ else if (!strcmp(node->name, MIPI_CSI2_OF_NODE_NAME))
+ plat_entity = IDX_MIPI_CSI2;
+ else if (!strcmp(node->name, PARALLEL_OF_NODE_NAME))
+ plat_entity = IDX_PARALLEL_CSI;
+
+ if (plat_entity >= IDX_SENSOR && plat_entity < IDX_MAX) {
+ ret = mxc_md_register_platform_entity(mxc_md, node,
+ plat_entity);
+ if (ret < 0)
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static int register_sensor_entities(struct mxc_md *mxc_md)
+{
+ struct device_node *parent = mxc_md->pdev->dev.of_node;
+ struct device_node *node, *ep, *rem;
+ struct v4l2_fwnode_endpoint endpoint;
+ struct i2c_client *client;
+ int index = 0;
+ int ret;
+
+ mxc_md->num_sensors = 0;
+
+ /* Attach sensors linked to MIPI CSI2 / paralle csi / HDMI Rx */
+ for_each_available_child_of_node(parent, node) {
+ struct device_node *port;
+
+ if (of_node_cmp(node->name, MIPI_CSI2_OF_NODE_NAME) &&
+ of_node_cmp(node->name, PARALLEL_OF_NODE_NAME))
+ continue;
+
+ if (!of_device_is_available(node))
+ continue;
+
+ /* csi2 node have only port */
+ port = of_get_next_child(node, NULL);
+ if (!port)
+ continue;
+
+ /* port can have only endpoint */
+ ep = of_get_next_child(port, NULL);
+ if (!ep)
+ return -EINVAL;
+
+ memset(&endpoint, 0, sizeof(endpoint));
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep), &endpoint);
+ if (WARN_ON(endpoint.base.port >= MXC_MAX_SENSORS || ret)) {
+ v4l2_err(&mxc_md->v4l2_dev,
+ "Failed to get sensor endpoint\n");
+ return -EINVAL;
+ }
+
+ mxc_md->sensor[index].id = endpoint.base.port;
+
+ if (!of_node_cmp(node->name, MIPI_CSI2_OF_NODE_NAME))
+ mxc_md->sensor[index].mipi_mode = true;
+
+ /* remote port---sensor node */
+ rem = of_graph_get_remote_port_parent(ep);
+ of_node_put(ep);
+ if (!rem) {
+ v4l2_info(&mxc_md->v4l2_dev,
+ "Remote device at %s not found\n",
+ ep->full_name);
+ continue;
+ }
+
+ /*
+ * Need to wait sensor driver probed for the first time
+ */
+ client = of_find_i2c_device_by_node(rem);
+ if (!client) {
+ v4l2_info(&mxc_md->v4l2_dev,
+ "Can't find i2c client device for %s\n",
+ of_node_full_name(rem));
+ return -EPROBE_DEFER;
+ }
+
+ mxc_md->sensor[index].asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
+ mxc_md->sensor[index].asd.match.fwnode = of_fwnode_handle(rem);
+ v4l2_async_notifier_add_subdev(&mxc_md->subdev_notifier,
+ &mxc_md->sensor[index].asd);
+ mxc_md->num_sensors++;
+
+ index++;
+ }
+
+ return 0;
+}
+
+static int mxc_md_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *nd = dev->of_node;
+ struct v4l2_device *v4l2_dev;
+ struct mxc_md *mxc_md;
+ int ret;
+
+ mxc_md = devm_kzalloc(dev, sizeof(*mxc_md), GFP_KERNEL);
+ if (!mxc_md)
+ return -ENOMEM;
+
+ mxc_md->pdev = pdev;
+ platform_set_drvdata(pdev, mxc_md);
+
+ mxc_md->parallel_csi = of_property_read_bool(nd, "parallel_csi");
+
+ /* register media device */
+ strlcpy(mxc_md->media_dev.model, "FSL Capture Media Device",
+ sizeof(mxc_md->media_dev.model));
+ mxc_md->media_dev.ops = &mxc_md_ops;
+ mxc_md->media_dev.dev = dev;
+
+ /* register v4l2 device */
+ v4l2_dev = &mxc_md->v4l2_dev;
+ v4l2_dev->mdev = &mxc_md->media_dev;
+ v4l2_dev->notify = mxc_sensor_notify;
+ strlcpy(v4l2_dev->name, "mx8-img-md", sizeof(v4l2_dev->name));
+
+ media_device_init(&mxc_md->media_dev);
+
+ ret = v4l2_device_register(dev, &mxc_md->v4l2_dev);
+ if (ret < 0) {
+ v4l2_err(v4l2_dev, "Failed to register v4l2_device (%d)\n", ret);
+ goto clean_md;
+ }
+
+ v4l2_async_notifier_init(&mxc_md->subdev_notifier);
+ ret = mxc_md_register_platform_entities(mxc_md, dev->of_node);
+ if (ret < 0)
+ goto clean_v4l2;
+
+ ret = register_sensor_entities(mxc_md);
+ if (ret < 0)
+ goto clean_ents;
+
+ if (mxc_md->num_sensors > 0) {
+ mxc_md->subdev_notifier.ops = &sd_async_notifier_ops;
+ mxc_md->valid_num_sensors = 0;
+ mxc_md->link_status = 0;
+
+ ret = v4l2_async_notifier_register(&mxc_md->v4l2_dev,
+ &mxc_md->subdev_notifier);
+ if (ret < 0) {
+ dev_warn(&mxc_md->pdev->dev, "Sensor register failed\n");
+ return ret;
+ }
+
+ if (!mxc_md->link_status) {
+ if (mxc_md->valid_num_sensors > 0) {
+ ret = subdev_notifier_complete(&mxc_md->subdev_notifier);
+ if (ret < 0)
+ goto clean_ents;
+
+ mxc_md_clean_unlink_channels(mxc_md);
+ } else {
+ /* no sensors connected */
+ mxc_md_unregister_all(mxc_md);
+ }
+ }
+ }
+
+ return 0;
+
+clean_ents:
+ mxc_md_unregister_entities(mxc_md);
+clean_v4l2:
+ v4l2_device_unregister(&mxc_md->v4l2_dev);
+clean_md:
+ media_device_cleanup(&mxc_md->media_dev);
+ return ret;
+}
+
+static int mxc_md_remove(struct platform_device *pdev)
+{
+ struct mxc_md *mxc_md = platform_get_drvdata(pdev);
+
+ if (!mxc_md)
+ return 0;
+
+ v4l2_async_notifier_unregister(&mxc_md->subdev_notifier);
+
+ v4l2_device_unregister(&mxc_md->v4l2_dev);
+ mxc_md_unregister_entities(mxc_md);
+ media_device_unregister(&mxc_md->media_dev);
+ media_device_cleanup(&mxc_md->media_dev);
+
+ return 0;
+}
+
+static const struct of_device_id mxc_md_of_match[] = {
+ { .compatible = "fsl,mxc-md",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mxc_md_of_match);
+
+static struct platform_driver mxc_md_driver = {
+ .driver = {
+ .name = MXC_MD_DRIVER_NAME,
+ .of_match_table = mxc_md_of_match,
+ },
+ .probe = mxc_md_probe,
+ .remove = mxc_md_remove,
+};
+
+module_platform_driver(mxc_md_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC Media Device driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" MXC_MD_DRIVER_NAME);
diff --git a/drivers/staging/media/imx/imx8-mipi-csi2-sam.c b/drivers/staging/media/imx/imx8-mipi-csi2-sam.c
new file mode 100644
index 000000000000..02ab671d9ae7
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-mipi-csi2-sam.c
@@ -0,0 +1,1738 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Freescale i.MX8MN/P SoC series MIPI-CSI V3.3 receiver driver
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2019 NXP
+ * Copyright 2020 NXP
+ *
+ * Samsung S5P/EXYNOS SoC series MIPI-CSI receiver driver
+ *
+ * Copyright (C) 2011 - 2013 Samsung Electronics Co., Ltd.
+ * Copyright (C) 2015-2016 Freescale Semiconductor, Inc. All Rights Reserved.
+ * Copyright 2019 NXP
+ * Author: Sylwester Nawrocki <s.nawrocki@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-device.h>
+#include <linux/reset.h>
+
+#if defined(CONFIG_MODVERSIONS) && !defined(MODVERSIONS)
+#define MODVERSIONS
+#endif
+
+#ifdef MODVERSIONS
+#include <config/modversions.h>
+#endif
+
+#define CSIS_DRIVER_NAME "mxc-mipi-csi2-sam"
+#define CSIS_SUBDEV_NAME "mxc-mipi-csi2"
+#define CSIS_MAX_ENTITIES 2
+#define CSIS0_MAX_LANES 4
+#define CSIS1_MAX_LANES 2
+
+#define MIPI_CSIS_OF_NODE_NAME "csi"
+
+#define MIPI_CSIS_VC0_PAD_SINK 0
+#define MIPI_CSIS_VC1_PAD_SINK 1
+#define MIPI_CSIS_VC2_PAD_SINK 2
+#define MIPI_CSIS_VC3_PAD_SINK 3
+
+#define MIPI_CSIS_VC0_PAD_SOURCE 4
+#define MIPI_CSIS_VC1_PAD_SOURCE 5
+#define MIPI_CSIS_VC2_PAD_SOURCE 6
+#define MIPI_CSIS_VC3_PAD_SOURCE 7
+#define MIPI_CSIS_VCX_PADS_NUM 8
+
+#define MIPI_CSIS_DEF_PIX_WIDTH 1920
+#define MIPI_CSIS_DEF_PIX_HEIGHT 1080
+
+/* Register map definition */
+
+/* CSIS version */
+#define MIPI_CSIS_VERSION 0x00
+
+/* CSIS common control */
+#define MIPI_CSIS_CMN_CTRL 0x04
+#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW (1 << 16)
+#define MIPI_CSIS_CMN_CTRL_HDR_MODE (1 << 11)
+#define MIPI_CSIS_CMN_CTRL_INTER_MODE (1 << 10)
+#define MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET 8
+#define MIPI_CSIS_CMN_CTRL_LANE_NR_MASK (3 << 8)
+#define MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL (1 << 2)
+#define MIPI_CSIS_CMN_CTRL_RESET (1 << 1)
+#define MIPI_CSIS_CMN_CTRL_ENABLE (1 << 0)
+
+/* CSIS clock control */
+#define MIPI_CSIS_CLK_CTRL 0x08
+#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH3(x) (x << 28)
+#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH2(x) (x << 24)
+#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH1(x) (x << 20)
+#define MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(x) (x << 16)
+#define MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK (0xf << 4)
+#define MIPI_CSIS_CLK_CTRL_WCLK_SRC (1 << 0)
+
+/* CSIS Interrupt mask */
+#define MIPI_CSIS_INTMSK 0x10
+#define MIPI_CSIS_INTMSK_EVEN_BEFORE (1 << 31)
+#define MIPI_CSIS_INTMSK_EVEN_AFTER (1 << 30)
+#define MIPI_CSIS_INTMSK_ODD_BEFORE (1 << 29)
+#define MIPI_CSIS_INTMSK_ODD_AFTER (1 << 28)
+#define MIPI_CSIS_INTMSK_FRAME_START (1 << 24)
+#define MIPI_CSIS_INTMSK_FRAME_END (1 << 20)
+#define MIPI_CSIS_INTMSK_ERR_SOT_HS (1 << 16)
+#define MIPI_CSIS_INTMSK_ERR_LOST_FS (1 << 12)
+#define MIPI_CSIS_INTMSK_ERR_LOST_FE (1 << 8)
+#define MIPI_CSIS_INTMSK_ERR_OVER (1 << 4)
+#define MIPI_CSIS_INTMSK_ERR_WRONG_CFG (1 << 3)
+#define MIPI_CSIS_INTMSK_ERR_ECC (1 << 2)
+#define MIPI_CSIS_INTMSK_ERR_CRC (1 << 1)
+#define MIPI_CSIS_INTMSK_ERR_UNKNOWN (1 << 0)
+
+/* CSIS Interrupt source */
+#define MIPI_CSIS_INTSRC 0x14
+#define MIPI_CSIS_INTSRC_EVEN_BEFORE (1 << 31)
+#define MIPI_CSIS_INTSRC_EVEN_AFTER (1 << 30)
+#define MIPI_CSIS_INTSRC_EVEN (0x3 << 30)
+#define MIPI_CSIS_INTSRC_ODD_BEFORE (1 << 29)
+#define MIPI_CSIS_INTSRC_ODD_AFTER (1 << 28)
+#define MIPI_CSIS_INTSRC_ODD (0x3 << 28)
+#define MIPI_CSIS_INTSRC_NON_IMAGE_DATA (0xf << 28)
+#define MIPI_CSIS_INTSRC_FRAME_START (1 << 24)
+#define MIPI_CSIS_INTSRC_FRAME_END (1 << 20)
+#define MIPI_CSIS_INTSRC_ERR_SOT_HS (1 << 16)
+#define MIPI_CSIS_INTSRC_ERR_LOST_FS (1 << 12)
+#define MIPI_CSIS_INTSRC_ERR_LOST_FE (1 << 8)
+#define MIPI_CSIS_INTSRC_ERR_OVER (1 << 4)
+#define MIPI_CSIS_INTSRC_ERR_WRONG_CFG (1 << 3)
+#define MIPI_CSIS_INTSRC_ERR_ECC (1 << 2)
+#define MIPI_CSIS_INTSRC_ERR_CRC (1 << 1)
+#define MIPI_CSIS_INTSRC_ERR_UNKNOWN (1 << 0)
+#define MIPI_CSIS_INTSRC_ERRORS 0xfffff
+
+/* D-PHY status control */
+#define MIPI_CSIS_DPHYSTATUS 0x20
+#define MIPI_CSIS_DPHYSTATUS_ULPS_DAT (1 << 8)
+#define MIPI_CSIS_DPHYSTATUS_STOPSTATE_DAT (1 << 4)
+#define MIPI_CSIS_DPHYSTATUS_ULPS_CLK (1 << 1)
+#define MIPI_CSIS_DPHYSTATUS_STOPSTATE_CLK (1 << 0)
+
+/* D-PHY common control */
+#define MIPI_CSIS_DPHYCTRL 0x24
+#define MIPI_CSIS_DPHYCTRL_HSS_MASK (0xff << 24)
+#define MIPI_CSIS_DPHYCTRL_HSS_OFFSET 24
+#define MIPI_CSIS_DPHYCTRL_SCLKS_MASK (0x3 << 22)
+#define MIPI_CSIS_DPHYCTRL_SCLKS_OFFSET 22
+#define MIPI_CSIS_DPHYCTRL_DPDN_SWAP_CLK (1 << 6)
+#define MIPI_CSIS_DPHYCTRL_DPDN_SWAP_DAT (1 << 5)
+#define MIPI_CSIS_DPHYCTRL_ENABLE_DAT (1 << 1)
+#define MIPI_CSIS_DPHYCTRL_ENABLE_CLK (1 << 0)
+#define MIPI_CSIS_DPHYCTRL_ENABLE (0x1f << 0)
+
+/* D-PHY Master and Slave Control register Low */
+#define MIPI_CSIS_DPHYBCTRL_L 0x30
+/* D-PHY Master and Slave Control register High */
+#define MIPI_CSIS_DPHYBCTRL_H 0x34
+/* D-PHY Slave Control register Low */
+#define MIPI_CSIS_DPHYSCTRL_L 0x38
+/* D-PHY Slave Control register High */
+#define MIPI_CSIS_DPHYSCTRL_H 0x3c
+
+
+/* ISP Configuration register */
+#define MIPI_CSIS_ISPCONFIG_CH0 0x40
+#define MIPI_CSIS_ISPCONFIG_CH0_PIXEL_MODE_MASK (0x3 << 12)
+#define MIPI_CSIS_ISPCONFIG_CH0_PIXEL_MODE_SHIFT 12
+
+#define MIPI_CSIS_ISPCONFIG_CH1 0x50
+#define MIPI_CSIS_ISPCONFIG_CH1_PIXEL_MODE_MASK (0x3 << 12)
+#define MIPI_CSIS_ISPCONFIG_CH1_PIXEL_MODE_SHIFT 12
+
+#define MIPI_CSIS_ISPCONFIG_CH2 0x60
+#define MIPI_CSIS_ISPCONFIG_CH2_PIXEL_MODE_MASK (0x3 << 12)
+#define MIPI_CSIS_ISPCONFIG_CH2_PIXEL_MODE_SHIFT 12
+
+#define MIPI_CSIS_ISPCONFIG_CH3 0x70
+#define MIPI_CSIS_ISPCONFIG_CH3_PIXEL_MODE_MASK (0x3 << 12)
+#define MIPI_CSIS_ISPCONFIG_CH3_PIXEL_MODE_SHIFT 12
+
+#define PIXEL_MODE_SINGLE_PIXEL_MODE 0x0
+#define PIXEL_MODE_DUAL_PIXEL_MODE 0x1
+#define PIXEL_MODE_QUAD_PIXEL_MODE 0x2
+#define PIXEL_MODE_INVALID_PIXEL_MODE 0x3
+
+
+#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP_MSK (0xff << 24)
+#define MIPI_CSIS_ISPCFG_MEM_FULL_GAP(x) (x << 24)
+#define MIPI_CSIS_ISPCFG_DOUBLE_CMPNT (1 << 12)
+#define MIPI_CSIS_ISPCFG_ALIGN_32BIT (1 << 11)
+#define MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT (0x1e << 2)
+#define MIPI_CSIS_ISPCFG_FMT_RAW8 (0x2a << 2)
+#define MIPI_CSIS_ISPCFG_FMT_RAW10 (0x2b << 2)
+#define MIPI_CSIS_ISPCFG_FMT_RAW12 (0x2c << 2)
+#define MIPI_CSIS_ISPCFG_FMT_RGB888 (0x24 << 2)
+#define MIPI_CSIS_ISPCFG_FMT_RGB565 (0x22 << 2)
+/* User defined formats, x = 1...4 */
+#define MIPI_CSIS_ISPCFG_FMT_USER(x) ((0x30 + x - 1) << 2)
+#define MIPI_CSIS_ISPCFG_FMT_MASK (0x3f << 2)
+
+/* ISP Image Resolution register */
+#define MIPI_CSIS_ISPRESOL_CH0 0x44
+#define MIPI_CSIS_ISPRESOL_CH1 0x54
+#define MIPI_CSIS_ISPRESOL_CH2 0x64
+#define MIPI_CSIS_ISPRESOL_CH3 0x74
+#define CSIS_MAX_PIX_WIDTH 0xffff
+#define CSIS_MAX_PIX_HEIGHT 0xffff
+
+/* ISP SYNC register */
+#define MIPI_CSIS_ISPSYNC_CH0 0x48
+#define MIPI_CSIS_ISPSYNC_CH1 0x58
+#define MIPI_CSIS_ISPSYNC_CH2 0x68
+#define MIPI_CSIS_ISPSYNC_CH3 0x78
+
+#define MIPI_CSIS_ISPSYNC_HSYNC_LINTV_OFFSET 18
+#define MIPI_CSIS_ISPSYNC_VSYNC_SINTV_OFFSET 12
+#define MIPI_CSIS_ISPSYNC_VSYNC_EINTV_OFFSET 0
+
+#define MIPI_CSIS_FRAME_COUNTER_CH0 0x0100
+#define MIPI_CSIS_FRAME_COUNTER_CH1 0x0104
+#define MIPI_CSIS_FRAME_COUNTER_CH2 0x0108
+#define MIPI_CSIS_FRAME_COUNTER_CH3 0x010C
+
+/* Non-image packet data buffers */
+#define MIPI_CSIS_PKTDATA_ODD 0x2000
+#define MIPI_CSIS_PKTDATA_EVEN 0x3000
+#define MIPI_CSIS_PKTDATA_SIZE SZ_4K
+
+#define DEFAULT_SCLK_CSIS_FREQ 166000000UL
+
+/* display_mix_clk_en_csr */
+#define DISP_MIX_GASKET_0_CTRL 0x00
+#define GASKET_0_CTRL_DATA_TYPE(x) (((x) & (0x3F)) << 8)
+#define GASKET_0_CTRL_DATA_TYPE_MASK ((0x3FUL) << (8))
+
+#define GASKET_0_CTRL_DATA_TYPE_YUV420_8 0x18
+#define GASKET_0_CTRL_DATA_TYPE_YUV420_10 0x19
+#define GASKET_0_CTRL_DATA_TYPE_LE_YUV420_8 0x1a
+#define GASKET_0_CTRL_DATA_TYPE_CS_YUV420_8 0x1c
+#define GASKET_0_CTRL_DATA_TYPE_CS_YUV420_10 0x1d
+#define GASKET_0_CTRL_DATA_TYPE_YUV422_8 0x1e
+#define GASKET_0_CTRL_DATA_TYPE_YUV422_10 0x1f
+#define GASKET_0_CTRL_DATA_TYPE_RGB565 0x22
+#define GASKET_0_CTRL_DATA_TYPE_RGB666 0x23
+#define GASKET_0_CTRL_DATA_TYPE_RGB888 0x24
+#define GASKET_0_CTRL_DATA_TYPE_RAW6 0x28
+#define GASKET_0_CTRL_DATA_TYPE_RAW7 0x29
+#define GASKET_0_CTRL_DATA_TYPE_RAW8 0x2a
+#define GASKET_0_CTRL_DATA_TYPE_RAW10 0x2b
+#define GASKET_0_CTRL_DATA_TYPE_RAW12 0x2c
+#define GASKET_0_CTRL_DATA_TYPE_RAW14 0x2d
+
+#define GASKET_0_CTRL_DUAL_COMP_ENABLE BIT(1)
+#define GASKET_0_CTRL_ENABLE BIT(0)
+
+#define DISP_MIX_GASKET_0_HSIZE 0x04
+#define DISP_MIX_GASKET_0_VSIZE 0x08
+
+struct mipi_csis_event {
+ u32 mask;
+ const char * const name;
+ unsigned int counter;
+};
+
+/**
+ * struct csis_pix_format - CSIS pixel format description
+ * @pix_width_alignment: horizontal pixel alignment, width will be
+ * multiple of 2^pix_width_alignment
+ * @code: corresponding media bus code
+ * @fmt_reg: MIPI_CSIS_CONFIG register value
+ * @data_alignment: MIPI-CSI data alignment in bits
+ */
+struct csis_pix_format {
+ unsigned int pix_width_alignment;
+ u32 code;
+ u32 fmt_reg;
+ u8 data_alignment;
+};
+
+struct csis_pktbuf {
+ u32 *data;
+ unsigned int len;
+};
+
+struct csis_hw_reset1 {
+ struct regmap *src;
+ u8 req_src;
+ u8 rst_bit;
+};
+
+enum {
+ VVCSIOC_RESET = 0x100,
+ VVCSIOC_POWERON,
+ VVCSIOC_POWEROFF,
+ VVCSIOC_STREAMON,
+ VVCSIOC_STREAMOFF,
+ VVCSIOC_S_FMT,
+ VVCSIOC_S_HDR,
+};
+
+struct csi_sam_format {
+ int64_t format;
+ __u32 width;
+ __u32 height;
+};
+
+struct csi_state;
+typedef int (*mipi_csis_phy_reset_t)(struct csi_state *state);
+
+static const struct mipi_csis_event mipi_csis_events[] = {
+ /* Errors */
+ { MIPI_CSIS_INTSRC_ERR_SOT_HS, "SOT Error" },
+ { MIPI_CSIS_INTSRC_ERR_LOST_FS, "Lost Frame Start Error" },
+ { MIPI_CSIS_INTSRC_ERR_LOST_FE, "Lost Frame End Error" },
+ { MIPI_CSIS_INTSRC_ERR_OVER, "FIFO Overflow Error" },
+ { MIPI_CSIS_INTSRC_ERR_ECC, "ECC Error" },
+ { MIPI_CSIS_INTSRC_ERR_CRC, "CRC Error" },
+ { MIPI_CSIS_INTSRC_ERR_UNKNOWN, "Unknown Error" },
+ /* Non-image data receive events */
+ { MIPI_CSIS_INTSRC_EVEN_BEFORE, "Non-image data before even frame" },
+ { MIPI_CSIS_INTSRC_EVEN_AFTER, "Non-image data after even frame" },
+ { MIPI_CSIS_INTSRC_ODD_BEFORE, "Non-image data before odd frame" },
+ { MIPI_CSIS_INTSRC_ODD_AFTER, "Non-image data after odd frame" },
+ /* Frame start/end */
+ { MIPI_CSIS_INTSRC_FRAME_START, "Frame Start" },
+ { MIPI_CSIS_INTSRC_FRAME_END, "Frame End" },
+};
+#define MIPI_CSIS_NUM_EVENTS ARRAY_SIZE(mipi_csis_events)
+
+/**
+ * struct csi_state - the driver's internal state data structure
+ * @lock: mutex serializing the subdev and power management operations,
+ * protecting @format and @flags members
+ * @sd: v4l2_subdev associated with CSIS device instance
+ * @index: the hardware instance index
+ * @pdev: CSIS platform device
+ * @phy: pointer to the CSIS generic PHY
+ * @regs: mmaped I/O registers memory
+ * @supplies: CSIS regulator supplies
+ * @clock: CSIS clocks
+ * @irq: requested s5p-mipi-csis irq number
+ * @flags: the state variable for power and streaming control
+ * @clock_frequency: device bus clock frequency
+ * @hs_settle: HS-RX settle time
+ * @clk_settle: Clk settle time
+ * @num_lanes: number of MIPI-CSI data lanes used
+ * @max_num_lanes: maximum number of MIPI-CSI data lanes supported
+ * @wclk_ext: CSI wrapper clock: 0 - bus clock, 1 - external SCLK_CAM
+ * @csis_fmt: current CSIS pixel format
+ * @format: common media bus format for the source and sink pad
+ * @slock: spinlock protecting structure members below
+ * @pkt_buf: the frame embedded (non-image) data buffer
+ * @events: MIPI-CSIS event (error) counters
+ */
+struct csi_state {
+ struct v4l2_subdev sd;
+ struct mutex lock;
+ struct device *dev;
+ struct v4l2_device v4l2_dev;
+
+ struct media_pad pads[MIPI_CSIS_VCX_PADS_NUM];
+
+ u8 index;
+ struct platform_device *pdev;
+ struct phy *phy;
+ void __iomem *regs;
+ struct clk *mipi_clk;
+ struct clk *disp_axi;
+ struct clk *disp_apb;
+ int irq;
+ u32 flags;
+
+ u32 clk_frequency;
+ u32 hs_settle;
+ u32 clk_settle;
+ u32 num_lanes;
+ u32 max_num_lanes;
+ u8 wclk_ext;
+
+ u8 vchannel;
+ const struct csis_pix_format *csis_fmt;
+ struct v4l2_mbus_framefmt format;
+
+ spinlock_t slock;
+ struct csis_pktbuf pkt_buf;
+ struct mipi_csis_event events[MIPI_CSIS_NUM_EVENTS];
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_async_notifier subdev_notifier;
+ struct v4l2_async_subdev *async_subdevs[2];
+
+ struct csis_hw_reset1 hw_reset;
+ struct regulator *mipi_phy_regulator;
+
+ struct regmap *gasket;
+ struct regmap *mix_gpr;
+
+ struct reset_control *soft_resetn;
+ struct reset_control *clk_enable;
+ struct reset_control *mipi_reset;
+
+ mipi_csis_phy_reset_t phy_reset_fn;
+ bool hdr;
+};
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level (0-2)");
+
+static const struct csis_pix_format mipi_csis_formats[] = {
+ {
+ .code = MEDIA_BUS_FMT_YUYV8_2X8,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_RGB888_1X24,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RGB888,
+ .data_alignment = 24,
+ }, {
+ .code = MEDIA_BUS_FMT_UYVY8_2X8,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_VYUY8_2X8,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW8,
+ .data_alignment = 8,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW10,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW10,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW10,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW10,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW12,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW12,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW12,
+ .data_alignment = 16,
+ }, {
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .fmt_reg = MIPI_CSIS_ISPCFG_FMT_RAW12,
+ .data_alignment = 16,
+ },
+};
+
+#define mipi_csis_write(__csis, __r, __v) writel(__v, __csis->regs + __r)
+#define mipi_csis_read(__csis, __r) readl(__csis->regs + __r)
+
+static void dump_csis_regs(struct csi_state *state, const char *label)
+{
+ struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ { 0x00, "CSIS_VERSION" },
+ { 0x04, "CSIS_CMN_CTRL" },
+ { 0x08, "CSIS_CLK_CTRL" },
+ { 0x10, "CSIS_INTMSK" },
+ { 0x14, "CSIS_INTSRC" },
+ { 0x20, "CSIS_DPHYSTATUS" },
+ { 0x24, "CSIS_DPHYCTRL" },
+ { 0x30, "CSIS_DPHYBCTRL_L" },
+ { 0x34, "CSIS_DPHYBCTRL_H" },
+ { 0x38, "CSIS_DPHYSCTRL_L" },
+ { 0x3C, "CSIS_DPHYSCTRL_H" },
+ { 0x40, "CSIS_ISPCONFIG_CH0" },
+ { 0x50, "CSIS_ISPCONFIG_CH1" },
+ { 0x60, "CSIS_ISPCONFIG_CH2" },
+ { 0x70, "CSIS_ISPCONFIG_CH3" },
+ { 0x44, "CSIS_ISPRESOL_CH0" },
+ { 0x54, "CSIS_ISPRESOL_CH1" },
+ { 0x64, "CSIS_ISPRESOL_CH2" },
+ { 0x74, "CSIS_ISPRESOL_CH3" },
+ { 0x48, "CSIS_ISPSYNC_CH0" },
+ { 0x58, "CSIS_ISPSYNC_CH1" },
+ { 0x68, "CSIS_ISPSYNC_CH2" },
+ { 0x78, "CSIS_ISPSYNC_CH3" },
+ };
+ u32 i;
+
+ v4l2_dbg(2, debug, &state->sd, "--- %s ---\n", label);
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 cfg = mipi_csis_read(state, registers[i].offset);
+ v4l2_dbg(2, debug, &state->sd, "%20s[%x]: 0x%.8x\n", registers[i].name, registers[i].offset, cfg);
+ }
+}
+
+static void dump_gasket_regs(struct csi_state *state, const char *label)
+{
+ struct {
+ u32 offset;
+ const char * const name;
+ } registers[] = {
+ { 0x60, "GPR_GASKET_0_CTRL" },
+ { 0x64, "GPR_GASKET_0_HSIZE" },
+ { 0x68, "GPR_GASKET_0_VSIZE" },
+ };
+ u32 i, cfg;
+
+ v4l2_dbg(2, debug, &state->sd, "--- %s ---\n", label);
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ regmap_read(state->gasket, registers[i].offset, &cfg);
+ v4l2_dbg(2, debug, &state->sd, "%20s[%x]: 0x%.8x\n", registers[i].name, registers[i].offset, cfg);
+ }
+}
+
+static inline struct csi_state *mipi_sd_to_csi_state(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct csi_state, sd);
+}
+
+static inline struct csi_state *notifier_to_mipi_dev(struct v4l2_async_notifier *n)
+{
+ return container_of(n, struct csi_state, subdev_notifier);
+}
+
+static struct media_pad *csis_get_remote_sensor_pad(struct csi_state *state)
+{
+ struct v4l2_subdev *subdev = &state->sd;
+ struct media_pad *sink_pad, *source_pad;
+ int i;
+
+ while (1) {
+ source_pad = NULL;
+ for (i = 0; i < subdev->entity.num_pads; i++) {
+ sink_pad = &subdev->entity.pads[i];
+
+ if (sink_pad->flags & MEDIA_PAD_FL_SINK) {
+ source_pad = media_entity_remote_pad(sink_pad);
+ if (source_pad)
+ break;
+ }
+ }
+ /* return first pad point in the loop */
+ return source_pad;
+ }
+
+ if (i == subdev->entity.num_pads)
+ v4l2_err(&state->sd, "%s, No remote pad found!\n", __func__);
+
+ return NULL;
+}
+
+static struct v4l2_subdev *csis_get_remote_subdev(struct csi_state *state,
+ const char * const label)
+{
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad */
+ source_pad = csis_get_remote_sensor_pad(state);
+ if (!source_pad) {
+ v4l2_err(&state->sd, "%s, No remote pad found!\n", label);
+ return NULL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+static const struct csis_pix_format *find_csis_format(u32 code)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(mipi_csis_formats); i++)
+ if (code == mipi_csis_formats[i].code)
+ return &mipi_csis_formats[i];
+ return NULL;
+}
+
+static void mipi_csis_clean_irq(struct csi_state *state)
+{
+ u32 status;
+
+ status = mipi_csis_read(state, MIPI_CSIS_INTSRC);
+ mipi_csis_write(state, MIPI_CSIS_INTSRC, status);
+
+ status = mipi_csis_read(state, MIPI_CSIS_INTMSK);
+ mipi_csis_write(state, MIPI_CSIS_INTMSK, status);
+}
+
+static void mipi_csis_enable_interrupts(struct csi_state *state, bool on)
+{
+ u32 val;
+
+ mipi_csis_clean_irq(state);
+
+ val = mipi_csis_read(state, MIPI_CSIS_INTMSK);
+ if (on)
+ val |= 0x0FFFFF1F;
+ else
+ val &= ~0x0FFFFF1F;
+ mipi_csis_write(state, MIPI_CSIS_INTMSK, val);
+}
+
+static void mipi_csis_sw_reset(struct csi_state *state)
+{
+ u32 val;
+
+ val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL);
+ val |= MIPI_CSIS_CMN_CTRL_RESET;
+ mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val);
+
+ udelay(20);
+}
+
+static int mipi_csis_phy_init(struct csi_state *state)
+{
+ state->mipi_phy_regulator = devm_regulator_get(state->dev, "mipi-phy");
+ if (IS_ERR(state->mipi_phy_regulator)) {
+ dev_err(state->dev, "Fail to get mipi-phy regulator\n");
+ return PTR_ERR(state->mipi_phy_regulator);
+ }
+
+ regulator_set_voltage(state->mipi_phy_regulator, 1000000, 1000000);
+ return 0;
+}
+
+static void mipi_csis_phy_reset_mx8mn(struct csi_state *state)
+{
+ struct reset_control *reset = state->mipi_reset;
+
+ reset_control_assert(reset);
+ usleep_range(10, 20);
+
+ reset_control_deassert(reset);
+ usleep_range(10, 20);
+
+ /* temporary place */
+ if (state->mix_gpr)
+ regmap_write(state->mix_gpr, 0x138, 0x8d8360);
+}
+
+static void mipi_csis_system_enable(struct csi_state *state, int on)
+{
+ u32 val, mask;
+
+ val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL);
+ if (on)
+ val |= MIPI_CSIS_CMN_CTRL_ENABLE;
+ else
+ val &= ~MIPI_CSIS_CMN_CTRL_ENABLE;
+ mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val);
+
+ val = mipi_csis_read(state, MIPI_CSIS_DPHYCTRL);
+ val &= ~MIPI_CSIS_DPHYCTRL_ENABLE;
+ if (on) {
+ mask = (1 << (state->num_lanes + 1)) - 1;
+ val |= (mask & MIPI_CSIS_DPHYCTRL_ENABLE);
+ }
+ mipi_csis_write(state, MIPI_CSIS_DPHYCTRL, val);
+}
+
+/* Called with the state.lock mutex held */
+static void __mipi_csis_set_format(struct csi_state *state)
+{
+ struct v4l2_mbus_framefmt *mf = &state->format;
+ u32 val;
+
+ v4l2_dbg(1, debug, &state->sd, "fmt: %#x, %d x %d\n",
+ mf->code, mf->width, mf->height);
+
+ /* Color format */
+ val = mipi_csis_read(state, MIPI_CSIS_ISPCONFIG_CH0);
+ val &= ~MIPI_CSIS_ISPCFG_FMT_MASK;
+ val |= state->csis_fmt->fmt_reg;
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH0, val);
+
+ val = mipi_csis_read(state, MIPI_CSIS_ISPCONFIG_CH0);
+ val &= ~MIPI_CSIS_ISPCONFIG_CH0_PIXEL_MODE_MASK;
+ if (state->csis_fmt->fmt_reg == MIPI_CSIS_ISPCFG_FMT_YCBCR422_8BIT)
+ val |= (PIXEL_MODE_DUAL_PIXEL_MODE <<
+ MIPI_CSIS_ISPCONFIG_CH0_PIXEL_MODE_SHIFT);
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH0, val);
+
+ /* Pixel resolution */
+ val = mf->width | (mf->height << 16);
+ mipi_csis_write(state, MIPI_CSIS_ISPRESOL_CH0, val);
+ if (state->hdr) {
+ mipi_csis_write(state, MIPI_CSIS_ISPRESOL_CH1, val);
+ mipi_csis_write(state, MIPI_CSIS_ISPRESOL_CH2, val);
+ mipi_csis_write(state, MIPI_CSIS_ISPRESOL_CH3, val);
+ val = state->csis_fmt->fmt_reg;
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH1, val | 1);
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH2, val | 2);
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH3, val | 3);
+ }
+}
+
+static void mipi_csis_set_hsync_settle(struct csi_state *state)
+{
+ u32 val;
+
+ val = mipi_csis_read(state, MIPI_CSIS_DPHYCTRL);
+ val &= ~MIPI_CSIS_DPHYCTRL_HSS_MASK;
+ val |= (state->hs_settle << 24) | (state->clk_settle << 22);
+ mipi_csis_write(state, MIPI_CSIS_DPHYCTRL, val);
+}
+
+static void mipi_csis_set_params(struct csi_state *state)
+{
+ u32 val;
+
+ val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL);
+ val &= ~MIPI_CSIS_CMN_CTRL_LANE_NR_MASK;
+ val |= (state->num_lanes - 1) << MIPI_CSIS_CMN_CTRL_LANE_NR_OFFSET;
+ val |= MIPI_CSIS_CMN_CTRL_HDR_MODE;
+ mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val);
+
+ __mipi_csis_set_format(state);
+ mipi_csis_set_hsync_settle(state);
+
+ val = mipi_csis_read(state, MIPI_CSIS_ISPCONFIG_CH0);
+ if (state->csis_fmt->data_alignment == 32)
+ val |= MIPI_CSIS_ISPCFG_ALIGN_32BIT;
+ else /* Normal output */
+ val &= ~MIPI_CSIS_ISPCFG_ALIGN_32BIT;
+ mipi_csis_write(state, MIPI_CSIS_ISPCONFIG_CH0, val);
+
+ val = (0 << MIPI_CSIS_ISPSYNC_HSYNC_LINTV_OFFSET) |
+ (0 << MIPI_CSIS_ISPSYNC_VSYNC_SINTV_OFFSET) |
+ (0 << MIPI_CSIS_ISPSYNC_VSYNC_EINTV_OFFSET);
+ mipi_csis_write(state, MIPI_CSIS_ISPSYNC_CH0, val);
+
+ val = mipi_csis_read(state, MIPI_CSIS_CLK_CTRL);
+ val &= ~MIPI_CSIS_CLK_CTRL_WCLK_SRC;
+ if (state->wclk_ext)
+ val |= MIPI_CSIS_CLK_CTRL_WCLK_SRC;
+ val |= MIPI_CSIS_CLK_CTRL_CLKGATE_TRAIL_CH0(15);
+ val &= ~MIPI_CSIS_CLK_CTRL_CLKGATE_EN_MSK;
+ mipi_csis_write(state, MIPI_CSIS_CLK_CTRL, val);
+
+ mipi_csis_write(state, MIPI_CSIS_DPHYBCTRL_L, 0x1f4);
+ mipi_csis_write(state, MIPI_CSIS_DPHYBCTRL_H, 0);
+
+ /* Update the shadow register. */
+ val = mipi_csis_read(state, MIPI_CSIS_CMN_CTRL);
+ val |= (MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW |
+ MIPI_CSIS_CMN_CTRL_UPDATE_SHADOW_CTRL);
+ if (state->hdr) {
+ val |= MIPI_CSIS_CMN_CTRL_HDR_MODE;
+ val |= 0xE0000;
+ }
+
+ mipi_csis_write(state, MIPI_CSIS_CMN_CTRL, val);
+}
+
+static int mipi_csis_clk_enable(struct csi_state *state)
+{
+ struct device *dev = state->dev;
+ int ret;
+
+ ret = clk_prepare_enable(state->mipi_clk);
+ if (ret) {
+ dev_err(dev, "enable mipi_clk failed!\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(state->disp_axi);
+ if (ret) {
+ dev_err(dev, "enable disp_axi clk failed!\n");
+ return ret;
+ }
+
+ ret = clk_prepare_enable(state->disp_apb);
+ if (ret) {
+ dev_err(dev, "enable disp_apb clk failed!\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static void mipi_csis_clk_disable(struct csi_state *state)
+{
+ clk_disable_unprepare(state->mipi_clk);
+ clk_disable_unprepare(state->disp_axi);
+ clk_disable_unprepare(state->disp_apb);
+}
+
+static int mipi_csis_clk_get(struct csi_state *state)
+{
+ struct device *dev = &state->pdev->dev;
+ int ret = true;
+
+ state->mipi_clk = devm_clk_get(dev, "mipi_clk");
+ if (IS_ERR(state->mipi_clk)) {
+ dev_err(dev, "Could not get mipi csi clock\n");
+ return -ENODEV;
+ }
+
+ state->disp_axi = devm_clk_get(dev, "disp_axi");
+ if (IS_ERR(state->disp_axi)) {
+ dev_warn(dev, "Could not get disp_axi clock\n");
+ return -ENODEV;
+ }
+
+ state->disp_apb = devm_clk_get(dev, "disp_apb");
+ if (IS_ERR(state->disp_apb)) {
+ dev_warn(dev, "Could not get disp apb clock\n");
+ return -ENODEV;
+ }
+
+ /* Set clock rate */
+ if (state->clk_frequency) {
+ ret = clk_set_rate(state->mipi_clk, state->clk_frequency);
+ if (ret < 0) {
+ dev_err(dev, "set rate filed, rate=%d\n", state->clk_frequency);
+ return -EINVAL;
+ }
+ } else {
+ dev_WARN(dev, "No clock frequency specified!\n");
+ }
+
+ return 0;
+}
+
+static int disp_mix_sft_rstn(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ return ret;
+}
+
+static int disp_mix_clks_enable(struct reset_control *reset, bool enable)
+{
+ int ret;
+
+ if (!reset)
+ return 0;
+
+ ret = enable ? reset_control_assert(reset) :
+ reset_control_deassert(reset);
+ return ret;
+}
+
+static void disp_mix_gasket_config(struct csi_state *state)
+{
+ struct regmap *gasket = state->gasket;
+ struct csis_pix_format const *fmt = state->csis_fmt;
+ struct v4l2_mbus_framefmt *mf = &state->format;
+ s32 fmt_val = -EINVAL;
+ u32 val;
+
+ switch (fmt->code) {
+ case MEDIA_BUS_FMT_RGB888_1X24:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RGB888;
+ break;
+ case MEDIA_BUS_FMT_YUYV8_2X8:
+ case MEDIA_BUS_FMT_YVYU8_2X8:
+ case MEDIA_BUS_FMT_UYVY8_2X8:
+ case MEDIA_BUS_FMT_VYUY8_2X8:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_YUV422_8;
+ break;
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW8;
+ break;
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW10;
+ break;
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW10;
+ break;
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW10;
+ break;
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW10;
+ break;
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW12;
+ break;
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW12;
+ break;
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW12;
+ break;
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
+ fmt_val = GASKET_0_CTRL_DATA_TYPE_RAW12;
+ break;
+ default:
+ pr_err("gasket not support format %d\n", fmt->code);
+ return;
+ }
+
+ regmap_read(gasket, DISP_MIX_GASKET_0_CTRL, &val);
+ if (fmt_val == GASKET_0_CTRL_DATA_TYPE_YUV422_8)
+ val |= GASKET_0_CTRL_DUAL_COMP_ENABLE;
+ val |= GASKET_0_CTRL_DATA_TYPE(fmt_val);
+ regmap_write(gasket, DISP_MIX_GASKET_0_CTRL, val);
+
+ if (WARN_ON(!mf->width || !mf->height))
+ return;
+
+ regmap_write(gasket, DISP_MIX_GASKET_0_HSIZE, mf->width);
+ regmap_write(gasket, DISP_MIX_GASKET_0_VSIZE, mf->height);
+}
+
+static void disp_mix_gasket_enable(struct csi_state *state, bool enable)
+{
+ struct regmap *gasket = state->gasket;
+
+ if (enable)
+ regmap_update_bits(gasket, DISP_MIX_GASKET_0_CTRL,
+ GASKET_0_CTRL_ENABLE,
+ GASKET_0_CTRL_ENABLE);
+ else
+ regmap_update_bits(gasket, DISP_MIX_GASKET_0_CTRL,
+ GASKET_0_CTRL_ENABLE,
+ 0);
+}
+
+static void mipi_csis_start_stream(struct csi_state *state)
+{
+ mipi_csis_sw_reset(state);
+
+ disp_mix_gasket_config(state);
+ mipi_csis_set_params(state);
+
+ mipi_csis_system_enable(state, true);
+ disp_mix_gasket_enable(state, true);
+ mipi_csis_enable_interrupts(state, true);
+ msleep(5);
+}
+
+static void mipi_csis_stop_stream(struct csi_state *state)
+{
+ mipi_csis_enable_interrupts(state, false);
+ mipi_csis_system_enable(state, false);
+ disp_mix_gasket_enable(state, false);
+}
+
+static void mipi_csis_clear_counters(struct csi_state *state)
+{
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&state->slock, flags);
+ for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++)
+ state->events[i].counter = 0;
+ spin_unlock_irqrestore(&state->slock, flags);
+}
+
+static void mipi_csis_log_counters(struct csi_state *state, bool non_errors)
+{
+ int i = non_errors ? MIPI_CSIS_NUM_EVENTS : MIPI_CSIS_NUM_EVENTS - 4;
+ unsigned long flags;
+
+ spin_lock_irqsave(&state->slock, flags);
+
+ for (i--; i >= 0; i--) {
+ if (state->events[i].counter > 0 || debug)
+ v4l2_info(&state->sd, "%s events: %d\n",
+ state->events[i].name,
+ state->events[i].counter);
+ }
+ spin_unlock_irqrestore(&state->slock, flags);
+}
+
+static int mipi_csi2_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ return 0;
+}
+
+static const struct media_entity_operations mipi_csi2_sd_media_ops = {
+ .link_setup = mipi_csi2_link_setup,
+};
+
+/*
+ * V4L2 subdev operations
+ */
+static int mipi_csis_s_power(struct v4l2_subdev *mipi_sd, int on)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ return v4l2_subdev_call(sen_sd, core, s_power, on);
+}
+
+static int mipi_csis_s_stream(struct v4l2_subdev *mipi_sd, int enable)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+
+ v4l2_dbg(1, debug, mipi_sd, "%s: %d, state: 0x%x\n",
+ __func__, enable, state->flags);
+
+ if (enable) {
+ pm_runtime_get_sync(state->dev);
+ mipi_csis_clear_counters(state);
+ mipi_csis_start_stream(state);
+ dump_csis_regs(state, __func__);
+ dump_gasket_regs(state, __func__);
+ } else {
+ mipi_csis_stop_stream(state);
+ if (debug > 0)
+ mipi_csis_log_counters(state, true);
+ pm_runtime_put(state->dev);
+ }
+
+ return 0;
+}
+
+static int mipi_csis_set_fmt(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_mbus_framefmt *mf = &format->format;
+ struct csis_pix_format const *csis_fmt;
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = csis_get_remote_sensor_pad(state);
+ if (!source_pad) {
+ v4l2_err(&state->sd, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ format->pad = source_pad->index;
+ mf->code = MEDIA_BUS_FMT_UYVY8_2X8;
+ ret = v4l2_subdev_call(sen_sd, pad, set_fmt, NULL, format);
+ if (ret < 0) {
+ v4l2_err(&state->sd, "%s, set sensor format fail\n", __func__);
+ return -EINVAL;
+ }
+
+ csis_fmt = find_csis_format(mf->code);
+ if (!csis_fmt) {
+ csis_fmt = &mipi_csis_formats[0];
+ mf->code = csis_fmt->code;
+ }
+
+ return 0;
+}
+
+static int mipi_csis_get_fmt(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *format)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_mbus_framefmt *mf = &state->format;
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = csis_get_remote_sensor_pad(state);
+ if (!source_pad) {
+ v4l2_err(&state->sd, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ format->pad = source_pad->index;
+ ret = v4l2_subdev_call(sen_sd, pad, get_fmt, NULL, format);
+ if (ret < 0) {
+ v4l2_err(&state->sd, "%s, call get_fmt of subdev failed!\n", __func__);
+ return ret;
+ }
+
+ memcpy(mf, &format->format, sizeof(struct v4l2_mbus_framefmt));
+ return 0;
+}
+
+static int mipi_csis_s_rx_buffer(struct v4l2_subdev *mipi_sd, void *buf,
+ unsigned int *size)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ unsigned long flags;
+
+ *size = min_t(unsigned int, *size, MIPI_CSIS_PKTDATA_SIZE);
+
+ spin_lock_irqsave(&state->slock, flags);
+ state->pkt_buf.data = buf;
+ state->pkt_buf.len = *size;
+ spin_unlock_irqrestore(&state->slock, flags);
+
+ return 0;
+}
+
+static int mipi_csis_s_frame_interval(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ return v4l2_subdev_call(sen_sd, video, s_frame_interval, interval);
+}
+
+static int mipi_csis_g_frame_interval(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ return v4l2_subdev_call(sen_sd, video, g_frame_interval, interval);
+}
+
+static int mipi_csis_enum_framesizes(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_size, NULL, fse);
+}
+
+static int mipi_csis_enum_frameintervals(struct v4l2_subdev *mipi_sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad subdev */
+ sen_sd = csis_get_remote_subdev(state, __func__);
+ if (!sen_sd) {
+ v4l2_err(&state->sd, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_interval, NULL, fie);
+}
+
+static int mipi_csis_log_status(struct v4l2_subdev *mipi_sd)
+{
+ struct csi_state *state = mipi_sd_to_csi_state(mipi_sd);
+
+ mutex_lock(&state->lock);
+ mipi_csis_log_counters(state, true);
+ if (debug) {
+ dump_csis_regs(state, __func__);
+ dump_gasket_regs(state, __func__);
+ }
+ mutex_unlock(&state->lock);
+ return 0;
+}
+
+static int csis_s_fmt(struct v4l2_subdev *sd, struct csi_sam_format *fmt)
+{
+ u32 code;
+ const struct csis_pix_format *csis_format;
+ struct csi_state *state = container_of(sd, struct csi_state, sd);
+
+ switch (fmt->format) {
+ case V4L2_PIX_FMT_SBGGR10:
+ code = MEDIA_BUS_FMT_SBGGR10_1X10;
+ break;
+ case V4L2_PIX_FMT_SGBRG10:
+ code = MEDIA_BUS_FMT_SGBRG10_1X10;
+ break;
+ case V4L2_PIX_FMT_SGRBG10:
+ code = MEDIA_BUS_FMT_SGRBG10_1X10;
+ break;
+ case V4L2_PIX_FMT_SRGGB10:
+ code = MEDIA_BUS_FMT_SRGGB10_1X10;
+ break;
+ case V4L2_PIX_FMT_SBGGR12:
+ code = MEDIA_BUS_FMT_SBGGR12_1X12;
+ break;
+ case V4L2_PIX_FMT_SGBRG12:
+ code = MEDIA_BUS_FMT_SGBRG12_1X12;
+ break;
+ case V4L2_PIX_FMT_SGRBG12:
+ code = MEDIA_BUS_FMT_SGRBG12_1X12;
+ break;
+ case V4L2_PIX_FMT_SRGGB12:
+ code = MEDIA_BUS_FMT_SRGGB12_1X12;
+ break;
+ default:
+ return -EINVAL;
+ }
+ csis_format = find_csis_format(code);
+ if (csis_format == NULL)
+ return -EINVAL;
+
+ state->csis_fmt = csis_format;
+ state->format.width = fmt->width;
+ state->format.height = fmt->height;
+ disp_mix_gasket_config(state);
+ mipi_csis_set_params(state);
+ return 0;
+}
+
+static int csis_s_hdr(struct v4l2_subdev *sd, bool enable)
+{
+ struct csi_state *state = container_of(sd, struct csi_state, sd);
+
+ v4l2_dbg(2, debug, &state->sd, "%s: %d\n", __func__, enable);
+ state->hdr = enable;
+ return 0;
+}
+
+static int csis_ioc_qcap(struct v4l2_subdev *dev, void *args)
+{
+ struct v4l2_capability *cap = (struct v4l2_capability *)args;
+ strcpy((char *)cap->driver, "csi_samsung_subdev");
+ return 0;
+}
+
+#ifdef CONFIG_HARDENED_USERCOPY
+#define USER_TO_KERNEL(TYPE) \
+ do {\
+ TYPE tmp; \
+ arg = (void *)(&tmp); \
+ copy_from_user(arg, arg_user, sizeof(TYPE));\
+ } while (0)
+
+#define KERNEL_TO_USER(TYPE) \
+ copy_to_user(arg_user, arg, sizeof(TYPE));
+#else
+#define USER_TO_KERNEL(TYPE)
+#define KERNEL_TO_USER(TYPE)
+#endif
+static long csis_priv_ioctl(struct v4l2_subdev *sd, unsigned int cmd, void *arg_user)
+{
+ int ret = 1;
+ struct csi_state *state = container_of(sd, struct csi_state, sd);
+ void *arg = arg_user;
+
+ pm_runtime_get_sync(state->dev);
+
+ switch (cmd) {
+ case VVCSIOC_RESET:
+ mipi_csis_sw_reset(state);
+ ret = 0;
+ break;
+ case VVCSIOC_POWERON:
+ ret = mipi_csis_s_power(sd, 1);
+ break;
+ case VVCSIOC_POWEROFF:
+ ret = mipi_csis_s_power(sd, 0);
+ break;
+ case VVCSIOC_STREAMON:
+ ret = mipi_csis_s_stream(sd, 1);
+ break;
+ case VVCSIOC_STREAMOFF:
+ ret = mipi_csis_s_stream(sd, 0);
+ break;
+ case VVCSIOC_S_FMT: {
+ USER_TO_KERNEL(struct csi_sam_format);
+ ret = csis_s_fmt(sd, (struct csi_sam_format *)arg);
+ break;
+ }
+ case VVCSIOC_S_HDR: {
+ USER_TO_KERNEL(bool);
+ ret = csis_s_hdr(sd, *(bool *) arg);
+ break;
+ }
+ case VIDIOC_QUERYCAP:
+ ret = csis_ioc_qcap(sd, arg);
+ break;
+ default:
+ v4l2_err(&state->sd, "unsupported csi-sam command %d.", cmd);
+ ret = -EINVAL;
+ break;
+ }
+ pm_runtime_put(state->dev);
+
+ return ret;
+}
+
+static struct v4l2_subdev_core_ops mipi_csis_core_ops = {
+ .s_power = mipi_csis_s_power,
+ .log_status = mipi_csis_log_status,
+ .ioctl = csis_priv_ioctl,
+};
+
+static struct v4l2_subdev_video_ops mipi_csis_video_ops = {
+ .s_rx_buffer = mipi_csis_s_rx_buffer,
+ .s_stream = mipi_csis_s_stream,
+
+ .g_frame_interval = mipi_csis_g_frame_interval,
+ .s_frame_interval = mipi_csis_s_frame_interval,
+};
+
+static const struct v4l2_subdev_pad_ops mipi_csis_pad_ops = {
+ .enum_frame_size = mipi_csis_enum_framesizes,
+ .enum_frame_interval = mipi_csis_enum_frameintervals,
+ .get_fmt = mipi_csis_get_fmt,
+ .set_fmt = mipi_csis_set_fmt,
+};
+
+static struct v4l2_subdev_ops mipi_csis_subdev_ops = {
+ .core = &mipi_csis_core_ops,
+ .video = &mipi_csis_video_ops,
+ .pad = &mipi_csis_pad_ops,
+};
+
+static irqreturn_t mipi_csis_irq_handler(int irq, void *dev_id)
+{
+ struct csi_state *state = dev_id;
+ struct csis_pktbuf *pktbuf = &state->pkt_buf;
+ unsigned long flags;
+ u32 status;
+
+ status = mipi_csis_read(state, MIPI_CSIS_INTSRC);
+
+ spin_lock_irqsave(&state->slock, flags);
+ if ((status & MIPI_CSIS_INTSRC_NON_IMAGE_DATA) && pktbuf->data) {
+ u32 offset;
+
+ if (status & MIPI_CSIS_INTSRC_EVEN)
+ offset = MIPI_CSIS_PKTDATA_EVEN;
+ else
+ offset = MIPI_CSIS_PKTDATA_ODD;
+
+ memcpy(pktbuf->data, state->regs + offset, pktbuf->len);
+ pktbuf->data = NULL;
+ rmb();
+ }
+
+ /* Update the event/error counters */
+ if ((status & MIPI_CSIS_INTSRC_ERRORS) || debug) {
+ int i;
+ for (i = 0; i < MIPI_CSIS_NUM_EVENTS; i++) {
+ if (!(status & state->events[i].mask))
+ continue;
+ state->events[i].counter++;
+ v4l2_dbg(2, debug, &state->sd, "%s: %d\n",
+ state->events[i].name,
+ state->events[i].counter);
+ }
+ v4l2_dbg(2, debug, &state->sd, "status: %08x\n", status);
+ }
+ spin_unlock_irqrestore(&state->slock, flags);
+
+ mipi_csis_write(state, MIPI_CSIS_INTSRC, status);
+ return IRQ_HANDLED;
+}
+
+static int mipi_csis_parse_dt(struct platform_device *pdev,
+ struct csi_state *state)
+{
+ struct device_node *node = pdev->dev.of_node;
+
+ state->index = of_alias_get_id(node, "csi");
+
+ if (of_property_read_u32(node, "clock-frequency", &state->clk_frequency))
+ state->clk_frequency = DEFAULT_SCLK_CSIS_FREQ;
+
+ if (of_property_read_u32(node, "bus-width", &state->max_num_lanes))
+ return -EINVAL;
+
+ node = of_graph_get_next_endpoint(node, NULL);
+ if (!node) {
+ dev_err(&pdev->dev, "No port node at\n");
+ return -EINVAL;
+ }
+
+ /* Get MIPI CSI-2 bus configration from the endpoint node. */
+ of_property_read_u32(node, "csis-hs-settle", &state->hs_settle);
+ of_property_read_u32(node, "csis-clk-settle", &state->clk_settle);
+ of_property_read_u32(node, "data-lanes", &state->num_lanes);
+
+ state->wclk_ext = of_property_read_bool(node, "csis-wclk");
+
+ of_node_put(node);
+ return 0;
+}
+
+static const struct of_device_id mipi_csis_of_match[];
+
+/* init subdev */
+static int mipi_csis_subdev_init(struct v4l2_subdev *mipi_sd,
+ struct platform_device *pdev,
+ const struct v4l2_subdev_ops *ops)
+{
+ struct csi_state *state = platform_get_drvdata(pdev);
+ int ret = 0;
+
+ v4l2_subdev_init(mipi_sd, ops);
+ mipi_sd->owner = THIS_MODULE;
+ snprintf(mipi_sd->name, sizeof(mipi_sd->name), "%s.%d",
+ CSIS_SUBDEV_NAME, state->index);
+ if (state->index == 0)
+ mipi_sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ mipi_sd->entity.function = MEDIA_ENT_F_IO_V4L;
+ mipi_sd->dev = &pdev->dev;
+
+ state->csis_fmt = &mipi_csis_formats[0];
+ state->format.code = mipi_csis_formats[0].code;
+ state->format.width = MIPI_CSIS_DEF_PIX_WIDTH;
+ state->format.height = MIPI_CSIS_DEF_PIX_HEIGHT;
+
+ /* This allows to retrieve the platform device id by the host driver */
+ v4l2_set_subdevdata(mipi_sd, state);
+
+ return ret;
+}
+
+static int mipi_csis_of_parse_resets(struct csi_state *state)
+{
+ int ret;
+ struct device *dev = state->dev;
+ struct device_node *np = dev->of_node;
+ struct device_node *parent, *child;
+ struct of_phandle_args args;
+ struct reset_control *rstc;
+ const char *compat;
+ uint32_t len, rstc_num = 0;
+
+ ret = of_parse_phandle_with_args(np, "resets", "#reset-cells",
+ 0, &args);
+ if (ret)
+ return ret;
+
+ parent = args.np;
+ for_each_child_of_node(parent, child) {
+ compat = of_get_property(child, "compatible", NULL);
+ if (!compat)
+ continue;
+
+ rstc = of_reset_control_array_get(child, false, false, true);
+ if (IS_ERR(rstc))
+ continue;
+
+ len = strlen(compat);
+ if (!of_compat_cmp("csi,soft-resetn", compat, len)) {
+ state->soft_resetn = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("csi,clk-enable", compat, len)) {
+ state->clk_enable = rstc;
+ rstc_num++;
+ } else if (!of_compat_cmp("csi,mipi-reset", compat, len)) {
+ state->mipi_reset = rstc;
+ rstc_num++;
+ } else {
+ dev_warn(dev, "invalid csis reset node: %s\n", compat);
+ }
+ }
+
+ if (!rstc_num) {
+ dev_err(dev, "no invalid reset control exists\n");
+ return -EINVAL;
+ }
+ of_node_put(parent);
+
+ return 0;
+}
+
+static int mipi_csis_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct v4l2_subdev *mipi_sd;
+ struct resource *mem_res;
+ struct csi_state *state;
+ const struct of_device_id *of_id;
+ mipi_csis_phy_reset_t phy_reset_fn;
+ int ret = -ENOMEM;
+
+ state = devm_kzalloc(dev, sizeof(*state), GFP_KERNEL);
+ if (!state)
+ return -ENOMEM;
+
+ mutex_init(&state->lock);
+ spin_lock_init(&state->slock);
+
+ state->pdev = pdev;
+ mipi_sd = &state->sd;
+ state->dev = dev;
+
+ ret = mipi_csis_parse_dt(pdev, state);
+ if (ret < 0)
+ return ret;
+
+ if (state->num_lanes == 0 || state->num_lanes > state->max_num_lanes) {
+ dev_err(dev, "Unsupported number of data lanes: %d (max. %d)\n",
+ state->num_lanes, state->max_num_lanes);
+ return -EINVAL;
+ }
+
+ ret = mipi_csis_phy_init(state);
+ if (ret < 0)
+ return ret;
+
+ of_id = of_match_node(mipi_csis_of_match, dev->of_node);
+ if (!of_id || !of_id->data) {
+ dev_err(dev, "No match data for %s\n", dev_name(dev));
+ return -EINVAL;
+ }
+ phy_reset_fn = of_id->data;
+ state->phy_reset_fn = phy_reset_fn;
+
+ state->gasket = syscon_regmap_lookup_by_phandle(dev->of_node, "csi-gpr");
+ if (IS_ERR(state->gasket)) {
+ dev_err(dev, "failed to get csi gasket\n");
+ return PTR_ERR(state->gasket);
+ }
+
+ if (!of_property_read_bool(dev->of_node, "no-reset-control")) {
+ ret = mipi_csis_of_parse_resets(state);
+ if (ret < 0) {
+ dev_err(dev, "Can not parse reset control\n");
+ return ret;
+ }
+ }
+
+ state->mix_gpr = syscon_regmap_lookup_by_phandle(dev->of_node, "gpr");
+ if (IS_ERR(state->mix_gpr)) {
+ dev_warn(dev, "failed to get mix gpr\n");
+ state->mix_gpr = NULL;
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ state->regs = devm_ioremap_resource(dev, mem_res);
+ if (IS_ERR(state->regs))
+ return PTR_ERR(state->regs);
+ state->irq = platform_get_irq(pdev, 0);
+ if (state->irq < 0) {
+ dev_err(dev, "Failed to get irq\n");
+ return state->irq;
+ }
+ ret = mipi_csis_clk_get(state);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_csis_clk_enable(state);
+ if (ret < 0)
+ return ret;
+
+ disp_mix_clks_enable(state->clk_enable, true);
+ disp_mix_sft_rstn(state->soft_resetn, false);
+ phy_reset_fn(state);
+
+ /*mipi_csis_clk_disable(state);*/
+ ret = devm_request_irq(dev, state->irq, mipi_csis_irq_handler, 0,
+ dev_name(dev), state);
+ if (ret) {
+ dev_err(dev, "Interrupt request failed\n");
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, state);
+ ret = mipi_csis_subdev_init(&state->sd, pdev, &mipi_csis_subdev_ops);
+ if (ret < 0) {
+ dev_err(dev, "mipi csi subdev init failed\n");
+ return ret;
+ }
+
+ state->pads[MIPI_CSIS_VC0_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ state->pads[MIPI_CSIS_VC1_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ state->pads[MIPI_CSIS_VC2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ state->pads[MIPI_CSIS_VC3_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ state->pads[MIPI_CSIS_VC0_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ state->pads[MIPI_CSIS_VC1_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ state->pads[MIPI_CSIS_VC2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ state->pads[MIPI_CSIS_VC3_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&state->sd.entity, MIPI_CSIS_VCX_PADS_NUM, state->pads);
+ if (ret < 0) {
+ dev_err(dev, "mipi csi entity pad init failed\n");
+ return ret;
+ }
+
+ memcpy(state->events, mipi_csis_events, sizeof(state->events));
+ state->sd.entity.ops = &mipi_csi2_sd_media_ops;
+
+ pm_runtime_enable(dev);
+
+ dev_info(&pdev->dev, "lanes: %d, hs_settle: %d, clk_settle: %d, wclk: %d, freq: %u\n",
+ state->num_lanes, state->hs_settle, state->clk_settle,
+ state->wclk_ext, state->clk_frequency);
+ return 0;
+}
+
+static int mipi_csis_system_suspend(struct device *dev)
+{
+ return pm_runtime_force_suspend(dev);;
+}
+
+static int mipi_csis_system_resume(struct device *dev)
+{
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret < 0) {
+ dev_err(dev, "force resume %s failed!\n", dev_name(dev));
+ return ret;
+ }
+
+ return 0;
+}
+
+static int mipi_csis_runtime_suspend(struct device *dev)
+{
+ struct csi_state *state = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_disable(state->mipi_phy_regulator);
+ if (ret < 0)
+ return ret;
+
+ disp_mix_clks_enable(state->clk_enable, false);
+ mipi_csis_clk_disable(state);
+ return 0;
+}
+
+static int mipi_csis_runtime_resume(struct device *dev)
+{
+ struct csi_state *state = dev_get_drvdata(dev);
+ int ret;
+
+ ret = regulator_enable(state->mipi_phy_regulator);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_csis_clk_enable(state);
+ if (ret < 0)
+ return ret;
+
+ disp_mix_clks_enable(state->clk_enable, true);
+ disp_mix_sft_rstn(state->soft_resetn, false);
+
+ if (state->phy_reset_fn)
+ state->phy_reset_fn(state);
+
+ return 0;
+}
+
+static int mipi_csis_remove(struct platform_device *pdev)
+{
+ struct csi_state *state = platform_get_drvdata(pdev);
+
+ media_entity_cleanup(&state->sd.entity);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static const struct dev_pm_ops mipi_csis_pm_ops = {
+ SET_RUNTIME_PM_OPS(mipi_csis_runtime_suspend, mipi_csis_runtime_resume, NULL)
+ SET_SYSTEM_SLEEP_PM_OPS(mipi_csis_system_suspend, mipi_csis_system_resume)
+};
+
+static const struct of_device_id mipi_csis_of_match[] = {
+ { .compatible = "fsl,imx8mn-mipi-csi",
+ .data = (void *)&mipi_csis_phy_reset_mx8mn,
+ },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mipi_csis_of_match);
+
+static struct platform_driver mipi_csis_driver = {
+ .driver = {
+ .name = CSIS_DRIVER_NAME,
+ .owner = THIS_MODULE,
+ .pm = &mipi_csis_pm_ops,
+ .of_match_table = mipi_csis_of_match,
+ },
+ .probe = mipi_csis_probe,
+ .remove = mipi_csis_remove,
+};
+module_platform_driver(mipi_csis_driver);
+
+MODULE_DESCRIPTION("Freescale MIPI-CSI2 receiver driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/staging/media/imx/imx8-mipi-csi2.c b/drivers/staging/media/imx/imx8-mipi-csi2.c
new file mode 100644
index 000000000000..8881a5a3596c
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-mipi-csi2.c
@@ -0,0 +1,1170 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Capture CSI Subdev for Freescale i.MX8QM/QXP SOC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/memory.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/firmware/imx/sci.h>
+#include <dt-bindings/firmware/imx/rsrc.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+
+#include "imx8-common.h"
+
+#define MXC_MIPI_CSI2_DRIVER_NAME "mxc-mipi-csi2"
+#define MXC_MIPI_CSI2_SUBDEV_NAME MXC_MIPI_CSI2_DRIVER_NAME
+#define MXC_MIPI_CSI2_MAX_LANES 4
+
+/* Subsystem CSR */
+#define CSI2SS_BASE_OFFSET 0x0
+
+#define CSI2SS_PLM_CTRL (CSI2SS_BASE_OFFSET + 0x0)
+#define CSI2SS_PLM_CTRL_PL_CLK_RUN 0x80000000
+#define CSI2SS_PLM_CTRL_VSYNC_OVERRIDE 0x200
+#define CSI2SS_PLM_CTRL_HSYNC_OVERRIDE 0x400
+#define CSI2SS_PLM_CTRL_VALID_OVERRIDE 0x800
+#define CSI2SS_PLM_CTRL_POLARITY_MASK 0x1000
+#define CSI2SS_PLM_CTRL_POLARITY_HIGH 0x1000
+#define CSI2SS_PLM_CTRL_POLARITY_LOW 0x0
+#define CSI2SS_PLM_CTRL_ENABLE_PL 1
+#define CSI2SS_PLM_CTRL_ENABLE_PL_OFFSET 0
+#define CSI2SS_PLM_CTRL_ENABLE_PL_MASK 1
+
+#define CSI2SS_PHY_CTRL (CSI2SS_BASE_OFFSET + 0x4)
+#define CSI2SS_PHY_CTRL_PD 1
+#define CSI2SS_PHY_CTRL_PD_OFFSET 22
+#define CSI2SS_PHY_CTRL_PD_MASK 0x400000
+#define CSI2SS_PHY_CTRL_RTERM_SEL 1
+#define CSI2SS_PHY_CTRL_RTERM_SEL_OFFSET 21
+#define CSI2SS_PHY_CTRL_RTERM_SEL_MASK 0x200000
+#define CSI2SS_PHY_CTRL_RX_HS_SETTLE_OFFSET 4
+#define CSI2SS_PHY_CTRL_RX_HS_SETTLE_MASK 0x3F0
+#define CSI2SS_PHY_CTRL_CONT_CLK_MODE 1
+#define CSI2SS_PHY_CTRL_CONT_CLK_MODE_OFFSET 3
+#define CSI2SS_PHY_CTRL_CONT_CLK_MODE_MASK 0x8
+#define CSI2SS_PHY_CTRL_DDRCLK_EN 1
+#define CSI2SS_PHY_CTRL_DDRCLK_EN_OFFSET 2
+#define CSI2SS_PHY_CTRL_DDRCLK_EN_MASK 0x4
+#define CSI2SS_PHY_CTRL_AUTO_PD_EN 1
+#define CSI2SS_PHY_CTRL_AUTO_PD_EN_OFFSET 1
+#define CSI2SS_PHY_CTRL_AUTO_PD_EN_MASK 0x2
+#define CSI2SS_PHY_CTRL_RX_ENABLE 1
+#define CSI2SS_PHY_CTRL_RX_ENABLE_OFFSET 0
+#define CSI2SS_PHY_CTRL_RX_ENABLE_MASK 0x1
+
+#define CSI2SS_PHY_STATUS (CSI2SS_BASE_OFFSET + 0x8)
+#define CSI2SS_PHY_TEST_STATUS (CSI2SS_BASE_OFFSET + 0x10)
+#define CSI2SS_PHY_TEST_STATUS_D0 (CSI2SS_BASE_OFFSET + 0x14)
+#define CSI2SS_PHY_TEST_STATUS_D1 (CSI2SS_BASE_OFFSET + 0x18)
+#define CSI2SS_PHY_TEST_STATUS_D2 (CSI2SS_BASE_OFFSET + 0x1C)
+#define CSI2SS_PHY_TEST_STATUS_D3 (CSI2SS_BASE_OFFSET + 0x20)
+
+#define CSI2SS_VC_INTERLACED (CSI2SS_BASE_OFFSET + 0x30)
+#define CSI2SS_VC_INTERLACED_VC0 1
+#define CSI2SS_VC_INTERLACED_VC1 2
+#define CSI2SS_VC_INTERLACED_VC2 4
+#define CSI2SS_VC_INTERLACED_VC3 8
+#define CSI2SS_VC_INTERLACED_OFFSET 0
+#define CSI2SS_VC_INTERLACED_MASK 0xF
+
+#define CSI2SS_DATA_TYPE (CSI2SS_BASE_OFFSET + 0x38)
+#define CSI2SS_DATA_TYPE_LEGACY_YUV420_8BIT BIT(2)
+#define CSI2SS_DATA_TYPE_YUV422_8BIT BIT(6)
+#define CSI2SS_DATA_TYPE_YUV422_10BIT BIT(7)
+#define CSI2SS_DATA_TYPE_RGB444 BIT(8)
+#define CSI2SS_DATA_TYPE_RGB555 BIT(9)
+#define CSI2SS_DATA_TYPE_RGB565 BIT(10)
+#define CSI2SS_DATA_TYPE_RGB666 BIT(11)
+#define CSI2SS_DATA_TYPE_RGB888 BIT(12)
+#define CSI2SS_DATA_TYPE_RAW6 BIT(16)
+#define CSI2SS_DATA_TYPE_RAW8 BIT(18)
+#define CSI2SS_DATA_TYPE_RAW10 BIT(19)
+#define CSI2SS_DATA_TYPE_RAW12 BIT(20)
+#define CSI2SS_DATA_TYPE_RAW14 BIT(21)
+
+#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE (CSI2SS_BASE_OFFSET + 0x40)
+#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_ODD 0
+#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_EVEN 1
+#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_OFFSET 0
+#define CSI2SS_YUV420_1ST_LINE_DATA_TYPE_MASK 1
+
+#define CSI2SS_CTRL_CLK_RESET (CSI2SS_BASE_OFFSET + 0x44)
+#define CSI2SS_CTRL_CLK_RESET_EN 1
+#define CSI2SS_CTRL_CLK_RESET_OFFSET 0
+#define CSI2SS_CTRL_CLK_RESET_MASK 1
+#define CSI2SS_CTRL_CLK_RESET_CLK_OFF 1
+#define CSI2SS_CTRL_CLK_RESET_CLK_OFFSET 1
+#define CSI2SS_CTRL_CLK_RESET_CLK_MASK 0x1
+
+#define CSI2SS_STREAM_FENCE_CTRL (CSI2SS_BASE_OFFSET + 0x48)
+#define CSI2SS_STREAM_FENCE_VC0 1
+#define CSI2SS_STREAM_FENCE_VC1 2
+#define CSI2SS_STREAM_FENCE_VC2 4
+#define CSI2SS_STREAM_FENCE_VC3 8
+#define CSI2SS_STREAM_FENCE_CTRL_OFFSET 0
+#define CSI2SS_STREAM_FENCE_CTRL_MASK 0xF
+
+#define CSI2SS_STREAM_FENCE_STATUS (CSI2SS_BASE_OFFSET + 0x4C)
+
+/* CSI-2 controller CSR */
+#define CSI2RX_BASE_OFFSET (0x100)
+
+#define CSI2RX_CFG_NUM_LANES (CSI2RX_BASE_OFFSET + 0x0)
+#define CSI2RX_CFG_NUM_LANES_OFFSET 0
+#define CSI2RX_CFG_NUM_LANES_MASK 0x3
+
+#define CSI2RX_CFG_DISABLE_DATA_LANES (CSI2RX_BASE_OFFSET + 0x4)
+#define CSI2RX_CFG_DISABLE_DATA_LANES_3 8
+#define CSI2RX_CFG_DISABLE_DATA_LANES_2 4
+#define CSI2RX_CFG_DISABLE_DATA_LANES_1 2
+#define CSI2RX_CFG_DISABLE_DATA_LANES_0 1
+#define CSI2RX_CFG_DISABLE_DATA_LANES_OFFSET 0
+#define CSI2RX_CFG_DISABLE_DATA_LANES_MASK 0xF
+
+#define CSI2RX_BIT_ERR (CSI2RX_BASE_OFFSET + 0x8)
+
+#define CSI2RX_IRQ_STATUS (CSI2RX_BASE_OFFSET + 0xC)
+#define CSI2RX_IRQ_STATUS_CRC_ERROR 0x1
+#define CSI2RX_IRQ_STATUS_1BIT_CRC_ERROR 0x2
+#define CSI2RX_IRQ_STATUS_2BIT_CRC_ERROR 0x4
+#define CSI2RX_IRQ_STATUS_ULPS_CHANGE 0x8
+#define CSI2RX_IRQ_STATUS_DPHY_ERRSOTHS 0x10
+#define CSI2RX_IRQ_STATUS_DPHY_ERRSOTSYNC_HS 0x20
+#define CSI2RX_IRQ_STATUS_DPHY_ERRESC 0x40
+#define CSI2RX_IRQ_STATUS_DPHY_ERRSYNCESC 0x80
+#define CSI2RX_IRQ_STATUS_DPHY_ERRCTRL 0x100
+
+#define CSI2RX_IRQ_MASK (CSI2RX_BASE_OFFSET + 0x10)
+#define CSI2RX_IRQ_MASK_CRC_ERROR 0x1
+#define CSI2RX_IRQ_MASK_1BIT_CRC_ERROR 0x2
+#define CSI2RX_IRQ_MASK_2BIT_CRC_ERROR 0x4
+#define CSI2RX_IRQ_MASK_ULPS_CHANGE 0x8
+#define CSI2RX_IRQ_MASK_DPHY_ERRSOTHS 0x10
+#define CSI2RX_IRQ_MASK_DPHY_ERRSOTSYNC_HS 0x20
+#define CSI2RX_IRQ_MASK_DPHY_ERRESC 0x40
+#define CSI2RX_IRQ_MASK_DPHY_ERRSYNCESC 0x80
+#define CSI2RX_IRQ_MASK_DPHY_ERRCTRL 0x100
+
+#define CSI2RX_ULPS_STATUS (CSI2RX_BASE_OFFSET + 0x14)
+#define CSI2RX_ULPS_STATUS_CLK_LANE_ULPS 0x1
+#define CSI2RX_ULPS_STATUS_DAT_LANE0_ULPS 0x2
+#define CSI2RX_ULPS_STATUS_DAT_LANE1_ULPS 0x4
+#define CSI2RX_ULPS_STATUS_DAT_LANE2_ULPS 0x8
+#define CSI2RX_ULPS_STATUS_DAT_LANE3_ULPS 0x10
+#define CSI2RX_ULPS_STATUS_CLK_LANE_MARK 0x20
+#define CSI2RX_ULPS_STATUS_DAT_LANE0_MARK 0x40
+#define CSI2RX_ULPS_STATUS_DAT_LANE1_MARK 0x80
+#define CSI2RX_ULPS_STATUS_DAT_LANE2_MARK 0x100
+#define CSI2RX_ULPS_STATUS_DAT_LANE3_MARK 0x200
+
+#define CSI2RX_PPI_ERRSOT_HS (CSI2RX_BASE_OFFSET + 0x18)
+#define CSI2RX_PPI_ERRSOT_HS_DAT_LANE0 0x1
+#define CSI2RX_PPI_ERRSOT_HS_DAT_LANE1 0x2
+#define CSI2RX_PPI_ERRSOT_HS_DAT_LANE2 0x4
+#define CSI2RX_PPI_ERRSOT_HS_DAT_LANE3 0x8
+
+#define CSI2RX_PPI_ERRSOTSYNC_HS (CSI2RX_BASE_OFFSET + 0x1C)
+#define CSI2RX_PPI_ERRSOTSYNC_HS_DAT_LANE0 0x1
+#define CSI2RX_PPI_ERRSOTSYNC_HS_DAT_LANE1 0x2
+#define CSI2RX_PPI_ERRSOTSYNC_HS_DAT_LANE2 0x4
+#define CSI2RX_PPI_ERRSOTSYNC_HS_DAT_LANE3 0x8
+
+#define CSI2RX_PPI_ERRESC (CSI2RX_BASE_OFFSET + 0x20)
+#define CSI2RX_PPI_ERRESC_DAT_LANE0 0x1
+#define CSI2RX_PPI_ERRESC_DAT_LANE1 0x2
+#define CSI2RX_PPI_ERRESC_DAT_LANE2 0x4
+#define CSI2RX_PPI_ERRESC_DAT_LANE3 0x8
+
+#define CSI2RX_PPI_ERRSYNCESC (CSI2RX_BASE_OFFSET + 0x24)
+#define CSI2RX_PPI_ERRSYNCESC_DAT_LANE0 0x1
+#define CSI2RX_PPI_ERRSYNCESC_DAT_LANE1 0x2
+#define CSI2RX_PPI_ERRSYNCESC_DAT_LANE2 0x4
+#define CSI2RX_PPI_ERRSYNCESC_DAT_LANE3 0x8
+
+#define CSI2RX_PPI_ERRCONTROL (CSI2RX_BASE_OFFSET + 0x28)
+#define CSI2RX_PPI_ERRCONTROL_DAT_LANE0 0x1
+#define CSI2RX_PPI_ERRCONTROL_DAT_LANE1 0x2
+#define CSI2RX_PPI_ERRCONTROL_DAT_LANE2 0x4
+#define CSI2RX_PPI_ERRCONTROL_DAT_LANE3 0x8
+
+#define CSI2RX_CFG_DISABLE_PAYLOAD_0 (CSI2RX_BASE_OFFSET + 0x2C)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_LEGACY_YUV420_8BIT BIT(10)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_YUV422_8BIT BIT(14)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_YUV422_10BIT BIT(15)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RGB444 BIT(16)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RGB555 BIT(17)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RGB565 BIT(18)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RGB666 BIT(19)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RGB888 BIT(20)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW6 BIT(24)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW7 BIT(25)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW8 BIT(26)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW10 BIT(27)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW12 BIT(28)
+#define CSI2RX_CFG_DISABLE_PAYLOAD_TYPE_RAW14 BIT(29)
+
+#define CSI2RX_CFG_DISABLE_PAYLOAD_1 (CSI2RX_BASE_OFFSET + 0x30)
+
+struct csis_hw_reset {
+ struct regmap *src;
+ u8 req_src;
+ u8 rst_val;
+};
+
+struct csis_phy_gpr {
+ struct regmap *gpr;
+ u8 req_src;
+};
+
+struct mxc_mipi_csi2_dev {
+ struct v4l2_subdev sd;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev *sensor_sd;
+
+ struct media_pad pads[MXC_MIPI_CSI2_VCX_PADS_NUM];
+ struct v4l2_mbus_framefmt format;
+
+ void __iomem *csr_regs;
+ void __iomem *base_regs;
+ struct platform_device *pdev;
+ u32 flags;
+ int irq;
+
+ struct clk *clk_core;
+ struct clk *clk_esc;
+ struct clk *clk_pxl;
+
+ struct csis_hw_reset hw_reset;
+ struct csis_phy_gpr phy_gpr;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_async_notifier subdev_notifier;
+ struct v4l2_async_subdev *async_subdevs[2];
+
+ struct device *pd_csi;
+ struct device *pd_isi;
+ struct device_link *pd_csi_link;
+ struct device_link *pd_isi_link;
+
+ struct mutex lock;
+
+ int id;
+ u32 hs_settle;
+ u32 send_level;
+ u32 num_lanes;
+ u8 data_lanes[4];
+ u8 vchannel;
+ u8 running;
+};
+
+struct mxc_hs_info {
+ u32 width;
+ u32 height;
+ u32 frame_rate;
+ u32 val;
+};
+
+enum mxc_mipi_csi2_pm_state {
+ MXC_MIPI_CSI2_PM_POWERED = 0x1,
+ MXC_MIPI_CSI2_PM_SUSPENDED = 0x2,
+ MXC_MIPI_CSI2_RUNTIME_SUSPENDED = 0x4,
+};
+
+/* 0~ 80Mbps: 0xB
+ * 80~250Mbps: 0x8
+ * 250~1.5Gbps: 0x6
+ */
+static u8 rxhs_settle[3] = {0xD, 0xA, 0x7};
+
+static struct mxc_hs_info hs_setting[] = {
+ {2592, 1944, 30, 0x0B},
+ {2592, 1944, 15, 0x10},
+
+ {1920, 1080, 30, 0x0B},
+ {1920, 1080, 15, 0x10},
+
+ {1280, 720, 30, 0x11},
+ {1280, 720, 15, 0x16},
+
+ {1024, 768, 30, 0x11},
+ {1024, 768, 15, 0x23},
+
+ {720, 576, 30, 0x1E},
+ {720, 576, 15, 0x23},
+
+ {720, 480, 30, 0x1E},
+ {720, 480, 15, 0x23},
+
+ {640, 480, 30, 0x1E},
+ {640, 480, 15, 0x23},
+
+ {320, 240, 30, 0x1E},
+ {320, 240, 15, 0x23},
+
+ {176, 144, 30, 0x1E},
+ {176, 144, 15, 0x23},
+};
+
+static struct imx_sc_ipc *pm_ipc_handle;
+
+static inline struct mxc_mipi_csi2_dev *sd_to_mxc_mipi_csi2_dev(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct mxc_mipi_csi2_dev, sd);
+}
+
+/****************************************
+ * rxhs-settle calculate
+ * UI = 1000 / mipi csi phy clock
+ * THS-SETTLE_mim = 85ns + 6 * UI
+ * THS-SETTLE_max = 145ns +10 * UI
+ * THS-SETTLE = (THS-SETTLE_mim + THS-SETTLE_max) / 2
+ * PRG_RXHS_SETTLE = THS-SETTLE / (Tperiod of RxClk_ESC) + 1
+ ****************************************/
+static int calc_hs_settle(struct mxc_mipi_csi2_dev *csi2dev, u32 dphy_clk)
+{
+ u32 esc_rate;
+ u32 hs_settle;
+ u32 rxhs_settle;
+ u32 hs_settle_min;
+ u32 hs_settle_max;
+
+ esc_rate = clk_get_rate(csi2dev->clk_esc) / 1000000;
+ hs_settle_min = 85 + 6 * 1000 / dphy_clk;
+ hs_settle_max = 145 + 10 * 1000 / dphy_clk;
+ hs_settle = (hs_settle_min + hs_settle_max) >> 1;
+ rxhs_settle = hs_settle / (1000 / esc_rate) - 1;
+ return rxhs_settle;
+}
+
+static void mxc_mipi_csi2_reg_dump(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ struct {
+ u32 offset;
+ const char name[32];
+ } registers[] = {
+ { 0x100, "MIPI CSI2 HC num of lanes" },
+ { 0x104, "MIPI CSI2 HC dis lanes" },
+ { 0x108, "MIPI CSI2 HC BIT ERR" },
+ { 0x10C, "MIPI CSI2 HC IRQ STATUS" },
+ { 0x110, "MIPI CSI2 HC IRQ MASK" },
+ { 0x114, "MIPI CSI2 HC ULPS STATUS" },
+ { 0x118, "MIPI CSI2 HC DPHY ErrSotHS" },
+ { 0x11c, "MIPI CSI2 HC DPHY ErrSotSync" },
+ { 0x120, "MIPI CSI2 HC DPHY ErrEsc" },
+ { 0x124, "MIPI CSI2 HC DPHY ErrSyncEsc" },
+ { 0x128, "MIPI CSI2 HC DPHY ErrControl" },
+ { 0x12C, "MIPI CSI2 HC DISABLE_PAYLOAD" },
+ { 0x130, "MIPI CSI2 HC DISABLE_PAYLOAD" },
+ { 0x180, "MIPI CSI2 HC IGNORE_VC" },
+ { 0x184, "MIPI CSI2 HC VID_VC" },
+ { 0x188, "MIPI CSI2 HC FIFO_SEND_LEVEL" },
+ { 0x18C, "MIPI CSI2 HC VID_VSYNC" },
+ { 0x190, "MIPI CSI2 HC VID_SYNC_FP" },
+ { 0x194, "MIPI CSI2 HC VID_HSYNC" },
+ { 0x198, "MIPI CSI2 HC VID_HSYNC_BP" },
+ { 0x000, "MIPI CSI2 CSR PLM_CTRL" },
+ { 0x004, "MIPI CSI2 CSR PHY_CTRL" },
+ { 0x008, "MIPI CSI2 CSR PHY_Status" },
+ { 0x010, "MIPI CSI2 CSR PHY_Test_Status" },
+ { 0x014, "MIPI CSI2 CSR PHY_Test_Status" },
+ { 0x018, "MIPI CSI2 CSR PHY_Test_Status" },
+ { 0x01C, "MIPI CSI2 CSR PHY_Test_Status" },
+ { 0x020, "MIPI CSI2 CSR PHY_Test_Status" },
+ { 0x030, "MIPI CSI2 CSR VC Interlaced" },
+ { 0x038, "MIPI CSI2 CSR Data Type Dis" },
+ { 0x040, "MIPI CSI2 CSR 420 1st type" },
+ { 0x044, "MIPI CSI2 CSR Ctr_Ck_Rst_Ctr" },
+ { 0x048, "MIPI CSI2 CSR Stream Fencing" },
+ { 0x04C, "MIPI CSI2 CSR Stream Fencing" },
+ };
+ u32 i;
+
+ dev_dbg(dev, "MIPI CSI2 CSR and HC register dump, mipi csi%d\n", csi2dev->id);
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 reg = readl(csi2dev->base_regs + registers[i].offset);
+ dev_dbg(dev, "%20s[0x%.3x]: 0x%.3x\n",
+ registers[i].name, registers[i].offset, reg);
+ }
+}
+
+static int mipi_sc_fw_init(struct mxc_mipi_csi2_dev *csi2dev, char enable)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ u32 rsrc_id;
+ int ret;
+
+ ret = imx_scu_get_handle(&pm_ipc_handle);
+ if (ret) {
+ dev_err(dev, "sc_misc_MIPI get ipc handle failed! ret = (%d)\n", ret);
+ return ret;
+ }
+
+ if (csi2dev->id == 1)
+ rsrc_id = IMX_SC_R_CSI_1;
+ else
+ rsrc_id = IMX_SC_R_CSI_0;
+
+ ret = imx_sc_misc_set_control(pm_ipc_handle,
+ rsrc_id, IMX_SC_C_MIPI_RESET, enable);
+ if (ret < 0) {
+ dev_err(dev, "sc_misc_MIPI reset failed! ret = (%d)\n", ret);
+ return ret;
+ }
+
+ msleep(10);
+ return 0;
+}
+
+static uint16_t find_hs_configure(struct v4l2_subdev_format *sd_fmt)
+{
+ struct v4l2_mbus_framefmt *fmt = &sd_fmt->format;
+ u32 frame_rate = fmt->reserved[1];
+ int i;
+
+ if (!fmt)
+ return -EINVAL;
+
+ for (i = 0; i < ARRAY_SIZE(hs_setting); i++) {
+ if (hs_setting[i].width == fmt->width &&
+ hs_setting[i].height == fmt->height &&
+ hs_setting[i].frame_rate == frame_rate)
+ return hs_setting[i].val;
+ }
+
+ if (i == ARRAY_SIZE(hs_setting))
+ pr_err("can not find HS setting for w/h@fps=(%d, %d)@%d\n",
+ fmt->width, fmt->height, frame_rate);
+
+ return -EINVAL;
+}
+
+static void mxc_mipi_csi2_reset(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ u32 val;
+
+ /* Reset MIPI CSI */
+ val = CSI2SS_CTRL_CLK_RESET_EN | CSI2SS_CTRL_CLK_RESET_CLK_OFF;
+ writel(val, csi2dev->csr_regs + CSI2SS_CTRL_CLK_RESET);
+}
+
+static void mxc_mipi_csi2_enable(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ u32 val = 0;
+
+ val = readl(csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+ while (val & CSI2SS_PLM_CTRL_PL_CLK_RUN) {
+ msleep(10);
+ val = readl(csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+ dev_dbg(dev, "Waiting pl clk running, val=0x%x\n", val);
+ }
+
+ /* Enable Pixel link Master*/
+ val = readl(csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+ val |= CSI2SS_PLM_CTRL_ENABLE_PL;
+ writel(val, csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+
+ val |= CSI2SS_PLM_CTRL_VALID_OVERRIDE;
+ writel(val, csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+
+ /* PHY Enable */
+ val = readl(csi2dev->csr_regs + CSI2SS_PHY_CTRL);
+ val &= ~(CSI2SS_PHY_CTRL_PD_MASK | CSI2SS_PLM_CTRL_POLARITY_MASK);
+ writel(val, csi2dev->csr_regs + CSI2SS_PHY_CTRL);
+
+ /* Deassert reset */
+ writel(1, csi2dev->csr_regs + CSI2SS_CTRL_CLK_RESET);
+}
+
+static void mxc_mipi_csi2_disable(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ /* Disable Data lanes */
+ writel(0xf, csi2dev->base_regs + CSI2RX_CFG_DISABLE_DATA_LANES);
+
+ /* Disable Pixel Link */
+ writel(0, csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+
+ /* Disable PHY */
+ writel(0, csi2dev->csr_regs + CSI2SS_PHY_CTRL);
+
+ /* Reset */
+ writel(2, csi2dev->csr_regs + CSI2SS_CTRL_CLK_RESET);
+}
+
+static void mxc_mipi_csi2_csr_config(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ u32 val;
+
+ /* format */
+ val = 0;
+ writel(val, csi2dev->csr_regs + CSI2SS_DATA_TYPE);
+
+ /* polarity */
+ val = readl(csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+ val &= ~(CSI2SS_PLM_CTRL_VSYNC_OVERRIDE |
+ CSI2SS_PLM_CTRL_HSYNC_OVERRIDE |
+ CSI2SS_PLM_CTRL_VALID_OVERRIDE |
+ CSI2SS_PLM_CTRL_POLARITY_MASK);
+
+ writel(val, csi2dev->csr_regs + CSI2SS_PLM_CTRL);
+
+ val = CSI2SS_PHY_CTRL_RX_ENABLE |
+ CSI2SS_PHY_CTRL_DDRCLK_EN << CSI2SS_PHY_CTRL_DDRCLK_EN_OFFSET |
+ CSI2SS_PHY_CTRL_CONT_CLK_MODE << CSI2SS_PHY_CTRL_CONT_CLK_MODE_OFFSET |
+ csi2dev->hs_settle << CSI2SS_PHY_CTRL_RX_HS_SETTLE_OFFSET |
+ CSI2SS_PHY_CTRL_PD << CSI2SS_PHY_CTRL_PD_OFFSET |
+ CSI2SS_PHY_CTRL_RTERM_SEL << CSI2SS_PHY_CTRL_RTERM_SEL_OFFSET |
+ CSI2SS_PHY_CTRL_AUTO_PD_EN << CSI2SS_PHY_CTRL_AUTO_PD_EN_OFFSET;
+
+ writel(val, csi2dev->csr_regs + CSI2SS_PHY_CTRL);
+}
+
+static void mxc_mipi_csi2_hc_config(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ u32 val0, val1;
+ u32 i;
+
+ val0 = 0;
+
+ /* Lanes */
+ writel(csi2dev->num_lanes - 1, csi2dev->base_regs + CSI2RX_CFG_NUM_LANES);
+
+ for (i = 0; i < csi2dev->num_lanes; i++)
+ val0 |= (1 << (csi2dev->data_lanes[i] - 1));
+
+ val1 = 0xF & ~val0;
+ writel(val1, csi2dev->base_regs + CSI2RX_CFG_DISABLE_DATA_LANES);
+
+ /* Mask interrupt */
+ writel(0x1FF, csi2dev->base_regs + CSI2RX_IRQ_MASK);
+
+ /* vid_vc */
+ writel(3, csi2dev->base_regs + 0x184);
+}
+
+static struct media_pad *mxc_csi2_get_remote_sensor_pad(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct v4l2_subdev *subdev = &csi2dev->sd;
+ struct media_pad *sink_pad, *source_pad;
+ int i;
+
+ while (1) {
+ source_pad = NULL;
+ for (i = 0; i < subdev->entity.num_pads; i++) {
+ sink_pad = &subdev->entity.pads[i];
+
+ if (sink_pad->flags & MEDIA_PAD_FL_SINK) {
+ source_pad = media_entity_remote_pad(sink_pad);
+ if (source_pad)
+ break;
+ }
+ }
+ /* return first pad point in the loop */
+ return source_pad;
+ }
+
+ if (i == subdev->entity.num_pads)
+ v4l2_err(&csi2dev->sd, "%s, No remote pad found!\n", __func__);
+
+ return NULL;
+}
+
+static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_mipi_csi2_dev *csi2dev,
+ const char * const label)
+{
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad */
+ source_pad = mxc_csi2_get_remote_sensor_pad(csi2dev);
+ if (!source_pad) {
+ v4l2_err(&csi2dev->sd, "%s, No remote pad found!\n", label);
+ return NULL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(&csi2dev->sd, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+static int mxc_csi2_get_sensor_fmt(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct v4l2_mbus_framefmt *mf = &csi2dev->format;
+ struct v4l2_subdev *sen_sd;
+ struct v4l2_subdev_format src_fmt;
+ struct media_pad *source_pad;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = mxc_csi2_get_remote_sensor_pad(csi2dev);
+ if (!source_pad) {
+ v4l2_err(&csi2dev->sd, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ memset(&src_fmt, 0, sizeof(src_fmt));
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(sen_sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EINVAL;
+
+ /* Update input frame size and formate */
+ memcpy(mf, &src_fmt.format, sizeof(struct v4l2_mbus_framefmt));
+
+ dev_dbg(&csi2dev->pdev->dev, "width=%d, height=%d, fmt.code=0x%x\n",
+ mf->width, mf->height, mf->code);
+
+ /* Get rxhs settle */
+ if (src_fmt.format.reserved[0] != 0) {
+ csi2dev->hs_settle =
+ calc_hs_settle(csi2dev, src_fmt.format.reserved[0]);
+ } else if (src_fmt.format.reserved[1] != 0) {
+ csi2dev->hs_settle = find_hs_configure(&src_fmt);
+ } else {
+ if (src_fmt.format.height * src_fmt.format.width > 1024 * 768)
+ csi2dev->hs_settle = rxhs_settle[2];
+ else if (src_fmt.format.height * src_fmt.format.width < 480 * 320)
+ csi2dev->hs_settle = rxhs_settle[0];
+ else
+ csi2dev->hs_settle = rxhs_settle[1];
+ }
+
+ return 0;
+}
+
+static int mipi_csi2_clk_init(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+
+ csi2dev->clk_core = devm_clk_get(dev, "clk_core");
+ if (IS_ERR(csi2dev->clk_core)) {
+ dev_err(dev, "failed to get csi core clk\n");
+ return PTR_ERR(csi2dev->clk_core);
+ }
+
+ csi2dev->clk_esc = devm_clk_get(dev, "clk_esc");
+ if (IS_ERR(csi2dev->clk_esc)) {
+ dev_err(dev, "failed to get csi esc clk\n");
+ return PTR_ERR(csi2dev->clk_esc);
+ }
+
+ csi2dev->clk_pxl = devm_clk_get(dev, "clk_pxl");
+ if (IS_ERR(csi2dev->clk_pxl)) {
+ dev_err(dev, "failed to get csi pixel link clk\n");
+ return PTR_ERR(csi2dev->clk_pxl);
+ }
+
+ return 0;
+}
+
+static int mipi_csi2_attach_pd(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ struct device_link *link;
+
+ csi2dev->pd_csi = dev_pm_domain_attach_by_name(dev, "pd_csi");
+ if (IS_ERR(csi2dev->pd_csi)) {
+ if (PTR_ERR(csi2dev->pd_csi) != -EPROBE_DEFER) {
+ dev_err(dev, "attach pd_csi domain for csi fail\n");
+ return PTR_ERR(csi2dev->pd_csi);
+ } else {
+ return PTR_ERR(csi2dev->pd_csi);
+ }
+ }
+ link = device_link_add(dev, csi2dev->pd_csi,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ csi2dev->pd_csi_link = link;
+
+ csi2dev->pd_isi = dev_pm_domain_attach_by_name(dev, "pd_isi_ch0");
+ if (IS_ERR(csi2dev->pd_isi)) {
+ if (PTR_ERR(csi2dev->pd_isi) != -EPROBE_DEFER) {
+ dev_err(dev, "attach pd_isi_ch0 domain for csi fail\n");
+ return PTR_ERR(csi2dev->pd_isi);
+ } else {
+ return PTR_ERR(csi2dev->pd_isi);
+ }
+ }
+ link = device_link_add(dev, csi2dev->pd_isi,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ csi2dev->pd_isi_link = link;
+
+ return 0;
+}
+
+static void mipi_csi2_detach_pd(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ device_link_del(csi2dev->pd_csi_link);
+ device_link_del(csi2dev->pd_isi_link);
+ dev_pm_domain_detach(csi2dev->pd_csi, true);
+ dev_pm_domain_detach(csi2dev->pd_isi, true);
+}
+
+static int mipi_csi2_clk_enable(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ int ret;
+
+ ret = clk_prepare_enable(csi2dev->clk_core);
+ if (ret < 0) {
+ dev_err(dev, "%s, pre clk_core error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(csi2dev->clk_esc);
+ if (ret < 0) {
+ dev_err(dev, "%s, prepare clk_esc error\n", __func__);
+ return ret;
+ }
+ ret = clk_prepare_enable(csi2dev->clk_pxl);
+ if (ret < 0) {
+ dev_err(dev, "%s, prepare clk_pxl error\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+static void mipi_csi2_clk_disable(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ clk_disable_unprepare(csi2dev->clk_core);
+ clk_disable_unprepare(csi2dev->clk_esc);
+ clk_disable_unprepare(csi2dev->clk_pxl);
+}
+
+static int mipi_csi2_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+{
+ return 0;
+}
+
+/* mipi csi2 subdev media entity operations */
+static int mipi_csi2_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ /* TODO */
+ /* Add MIPI source and sink pad link configuration */
+ if (local->flags & MEDIA_PAD_FL_SOURCE) {
+ switch (local->index) {
+ case MXC_MIPI_CSI2_VC0_PAD_SOURCE:
+ case MXC_MIPI_CSI2_VC1_PAD_SOURCE:
+ case MXC_MIPI_CSI2_VC2_PAD_SOURCE:
+ case MXC_MIPI_CSI2_VC3_PAD_SOURCE:
+ break;
+ default:
+ return 0;
+ }
+ } else if (local->flags & MEDIA_PAD_FL_SINK) {
+ switch (local->index) {
+ case MXC_MIPI_CSI2_VC0_PAD_SINK:
+ case MXC_MIPI_CSI2_VC1_PAD_SINK:
+ case MXC_MIPI_CSI2_VC2_PAD_SINK:
+ case MXC_MIPI_CSI2_VC3_PAD_SINK:
+ break;
+ default:
+ return 0;
+ }
+ }
+ return 0;
+}
+
+static const struct media_entity_operations mipi_csi2_sd_media_ops = {
+ .link_setup = mipi_csi2_link_setup,
+};
+
+/*
+ * V4L2 subdev operations
+ */
+static int mipi_csi2_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, core, s_power, on);
+}
+
+static int mipi_csi2_g_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, video, g_frame_interval, interval);
+}
+
+static int mipi_csi2_s_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, video, s_frame_interval, interval);
+}
+
+static int mipi_csi2_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct device *dev = &csi2dev->pdev->dev;
+ int ret = 0;
+
+ dev_dbg(&csi2dev->pdev->dev, "%s: %d, csi2dev: 0x%x\n",
+ __func__, enable, csi2dev->flags);
+
+ if (enable) {
+ pm_runtime_get_sync(dev);
+ if (!csi2dev->running++) {
+ mxc_csi2_get_sensor_fmt(csi2dev);
+ mxc_mipi_csi2_hc_config(csi2dev);
+ mxc_mipi_csi2_reset(csi2dev);
+ mxc_mipi_csi2_csr_config(csi2dev);
+ mxc_mipi_csi2_enable(csi2dev);
+ mxc_mipi_csi2_reg_dump(csi2dev);
+ }
+ } else {
+ if (!--csi2dev->running)
+ mxc_mipi_csi2_disable(csi2dev);
+
+ pm_runtime_put(dev);
+ }
+
+ return ret;
+}
+
+static int mipi_csi2_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_size, NULL, fse);
+}
+
+static int mipi_csi2_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_interval, NULL, fie);
+}
+
+static int mipi_csi2_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ mxc_csi2_get_sensor_fmt(csi2dev);
+
+ memcpy(mf, &csi2dev->format, sizeof(struct v4l2_mbus_framefmt));
+ /* Source/Sink pads crop rectangle size */
+
+ return 0;
+}
+
+static int mipi_csi2_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+ struct v4l2_subdev *sen_sd;
+ struct media_pad *source_pad;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = mxc_csi2_get_remote_sensor_pad(csi2dev);
+ if (!source_pad) {
+ v4l2_err(&csi2dev->sd, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ sen_sd = mxc_get_remote_subdev(csi2dev, __func__);
+ if (!sd)
+ return -EINVAL;
+
+ fmt->pad = source_pad->index;
+ ret = v4l2_subdev_call(sen_sd, pad, set_fmt, NULL, fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ return 0;
+}
+
+static const struct v4l2_subdev_internal_ops mipi_csi2_sd_internal_ops = {
+ .open = mipi_csi2_open,
+};
+
+static struct v4l2_subdev_pad_ops mipi_csi2_pad_ops = {
+ .enum_frame_size = mipi_csi2_enum_framesizes,
+ .enum_frame_interval = mipi_csi2_enum_frame_interval,
+ .get_fmt = mipi_csi2_get_fmt,
+ .set_fmt = mipi_csi2_set_fmt,
+};
+
+static struct v4l2_subdev_core_ops mipi_csi2_core_ops = {
+ .s_power = mipi_csi2_s_power,
+};
+
+static struct v4l2_subdev_video_ops mipi_csi2_video_ops = {
+ .g_frame_interval = mipi_csi2_g_frame_interval,
+ .s_frame_interval = mipi_csi2_s_frame_interval,
+ .s_stream = mipi_csi2_s_stream,
+};
+
+static struct v4l2_subdev_ops mipi_csi2_subdev_ops = {
+ .core = &mipi_csi2_core_ops,
+ .video = &mipi_csi2_video_ops,
+ .pad = &mipi_csi2_pad_ops,
+};
+
+static int mipi_csi2_parse_dt(struct mxc_mipi_csi2_dev *csi2dev)
+{
+ struct device *dev = &csi2dev->pdev->dev;
+ struct device_node *node = dev->of_node;
+ struct v4l2_fwnode_endpoint endpoint;
+ u32 i;
+
+ csi2dev->id = of_alias_get_id(node, "csi");
+
+ csi2dev->vchannel = of_property_read_bool(node, "virtual-channel");
+
+ node = of_graph_get_next_endpoint(node, NULL);
+ if (!node) {
+ dev_err(dev, "No port node at %s\n", node->full_name);
+ return -EINVAL;
+ }
+
+ /* Get port node */
+ memset(&endpoint, 0x0, sizeof(endpoint));
+ v4l2_fwnode_endpoint_parse(of_fwnode_handle(node), &endpoint);
+
+ csi2dev->num_lanes = endpoint.bus.mipi_csi2.num_data_lanes;
+ for (i = 0; i < 4; i++)
+ csi2dev->data_lanes[i] = endpoint.bus.mipi_csi2.data_lanes[i];
+
+ of_node_put(node);
+ return 0;
+}
+
+static int mipi_csi2_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *mem_res;
+ struct mxc_mipi_csi2_dev *csi2dev;
+ int ret = -ENOMEM;
+
+ csi2dev = devm_kzalloc(dev, sizeof(*csi2dev), GFP_KERNEL);
+ if (!csi2dev)
+ return -ENOMEM;
+
+ csi2dev->pdev = pdev;
+ mutex_init(&csi2dev->lock);
+
+ ret = mipi_csi2_parse_dt(csi2dev);
+ if (ret < 0)
+ return ret;
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ csi2dev->base_regs = devm_ioremap_resource(dev, mem_res);
+ if (IS_ERR(csi2dev->base_regs)) {
+ dev_err(dev, "Failed to get mipi csi2 HC register\n");
+ return PTR_ERR(csi2dev->base_regs);
+ }
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+ csi2dev->csr_regs = devm_ioremap_resource(dev, mem_res);
+ if (IS_ERR(csi2dev->csr_regs)) {
+ dev_err(dev, "Failed to get mipi CSR register\n");
+ return PTR_ERR(csi2dev->csr_regs);
+ }
+
+ ret = mipi_csi2_clk_init(csi2dev);
+ if (ret < 0)
+ return ret;
+
+ ret = mipi_csi2_attach_pd(csi2dev);
+ if (ret < 0)
+ return ret;
+
+ v4l2_subdev_init(&csi2dev->sd, &mipi_csi2_subdev_ops);
+
+ csi2dev->sd.owner = THIS_MODULE;
+ snprintf(csi2dev->sd.name, sizeof(csi2dev->sd.name), "%s.%d",
+ MXC_MIPI_CSI2_SUBDEV_NAME, csi2dev->id);
+
+ csi2dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ csi2dev->sd.entity.function = MEDIA_ENT_F_IO_V4L;
+ csi2dev->sd.dev = dev;
+
+ csi2dev->pads[MXC_MIPI_CSI2_VC0_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi2dev->pads[MXC_MIPI_CSI2_VC1_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi2dev->pads[MXC_MIPI_CSI2_VC2_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi2dev->pads[MXC_MIPI_CSI2_VC3_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ csi2dev->pads[MXC_MIPI_CSI2_VC0_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ csi2dev->pads[MXC_MIPI_CSI2_VC1_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ csi2dev->pads[MXC_MIPI_CSI2_VC2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ csi2dev->pads[MXC_MIPI_CSI2_VC3_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+ ret = media_entity_pads_init(&csi2dev->sd.entity,
+ MXC_MIPI_CSI2_VCX_PADS_NUM, csi2dev->pads);
+ if (ret < 0)
+ goto e_clkdis;
+
+ csi2dev->sd.entity.ops = &mipi_csi2_sd_media_ops;
+
+ v4l2_set_subdevdata(&csi2dev->sd, pdev);
+ platform_set_drvdata(pdev, csi2dev);
+
+ mipi_sc_fw_init(csi2dev, 1);
+
+ csi2dev->running = 0;
+ pm_runtime_enable(dev);
+
+ dev_info(&pdev->dev, "lanes: %d, name: %s\n",
+ csi2dev->num_lanes, csi2dev->sd.name);
+
+ return 0;
+
+e_clkdis:
+ media_entity_cleanup(&csi2dev->sd.entity);
+ return ret;
+}
+
+static int mipi_csi2_remove(struct platform_device *pdev)
+{
+ struct v4l2_subdev *sd = platform_get_drvdata(pdev);
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+
+ mipi_sc_fw_init(csi2dev, 0);
+ mipi_csi2_detach_pd(csi2dev);
+ media_entity_cleanup(&csi2dev->sd.entity);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int mipi_csi2_pm_suspend(struct device *dev)
+{
+ struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ struct mxc_mipi_csi2_dev *csi2dev = sd_to_mxc_mipi_csi2_dev(sd);
+
+ if (csi2dev->running > 0) {
+ dev_warn(dev, "running, prevent entering suspend.\n");
+ return -EAGAIN;
+ }
+
+ return pm_runtime_force_suspend(dev);
+}
+
+static int mipi_csi2_pm_resume(struct device *dev)
+{
+ return pm_runtime_force_resume(dev);
+}
+
+static int mipi_csi2_runtime_suspend(struct device *dev)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev);
+
+ mipi_csi2_clk_disable(csi2dev);
+ return 0;
+}
+
+static int mipi_csi2_runtime_resume(struct device *dev)
+{
+ struct mxc_mipi_csi2_dev *csi2dev = dev_get_drvdata(dev);
+ int ret;
+
+ ret = mipi_csi2_clk_enable(csi2dev);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static const struct dev_pm_ops mipi_csi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(mipi_csi2_pm_suspend, mipi_csi2_pm_resume)
+ SET_RUNTIME_PM_OPS(mipi_csi2_runtime_suspend, mipi_csi2_runtime_resume, NULL)
+};
+
+static const struct of_device_id mipi_csi2_of_match[] = {
+ { .compatible = "fsl,mxc-mipi-csi2", },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, mipi_csi2_of_match);
+
+static struct platform_driver mipi_csi2_driver = {
+ .driver = {
+ .name = MXC_MIPI_CSI2_DRIVER_NAME,
+ .of_match_table = mipi_csi2_of_match,
+ .pm = &mipi_csi_pm_ops,
+ },
+ .probe = mipi_csi2_probe,
+ .remove = mipi_csi2_remove,
+};
+
+module_platform_driver(mipi_csi2_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC MIPI CSI2 driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" MXC_MIPI_CSI2_DRIVER_NAME);
diff --git a/drivers/staging/media/imx/imx8-parallel-csi.c b/drivers/staging/media/imx/imx8-parallel-csi.c
new file mode 100644
index 000000000000..408082a29192
--- /dev/null
+++ b/drivers/staging/media/imx/imx8-parallel-csi.c
@@ -0,0 +1,837 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * V4L2 Capture CSI Subdev for Freescale i.MX8QM/QXP SOC
+ *
+ * Copyright (c) 2019 NXP Semiconductor
+ *
+ */
+
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/irq.h>
+#include <linux/kernel.h>
+#include <linux/memory.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/regulator/consumer.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-fwnode.h>
+#include <media/v4l2-subdev.h>
+#include <media/v4l2-device.h>
+#include <linux/firmware/imx/sci.h>
+#include <dt-bindings/pinctrl/pads-imx8qxp.h>
+#include <linux/init.h>
+#include <linux/pm_domain.h>
+
+#include "imx8-common.h"
+
+#define MXC_PARALLEL_CSI_DRIVER_NAME "mxc-parallel-csi"
+#define MXC_PARALLEL_CSI_SUBDEV_NAME MXC_PARALLEL_CSI_DRIVER_NAME
+
+#define BIT_U(nr) (1U << (nr))
+#define CI_PI_BASE_OFFSET 0x0U
+
+/* CI_PI INTERFACE Control */
+#define IF_CTRL_REG (CI_PI_BASE_OFFSET + 0x00)
+#define IF_CTRL_REG_PL_ENABLE BIT_U(0)
+#define IF_CTRL_REG_PL_VALID BIT_U(1)
+#define IF_CTRL_REG_PL_ADDR(x) (((x) & 0x7U) << 2)
+#define IF_CTRL_REG_IF_FORCE(x) (((x) & 0x7U) << 5)
+#define IF_CTRL_REG_DATA_TYPE_SEL BIT_U(8)
+#define IF_CTRL_REG_DATA_TYPE(x) (((x) & 0x1FU) << 9)
+
+#define DATA_TYPE_OUT_NULL (0x00)
+#define DATA_TYPE_OUT_RGB (0x04)
+#define DATA_TYPE_OUT_YUV444 (0x08)
+#define DATA_TYPE_OUT_YYU420_ODD (0x10)
+#define DATA_TYPE_OUT_YYU420_EVEN (0x12)
+#define DATA_TYPE_OUT_YYY_ODD (0x18)
+#define DATA_TYPE_OUT_UYVY_EVEN (0x1A)
+#define DATA_TYPE_OUT_RAW (0x1C)
+
+#define IF_CTRL_REG_IF_FORCE_HSYNV_OVERRIDE 0x4
+#define IF_CTRL_REG_IF_FORCE_VSYNV_OVERRIDE 0x2
+#define IF_CTRL_REG_IF_FORCE_DATA_ENABLE_OVERRIDE 0x1
+
+#define IF_CTRL_REG_SET (CI_PI_BASE_OFFSET + 0x04)
+#define IF_CTRL_REG_CLR (CI_PI_BASE_OFFSET + 0x08)
+#define IF_CTRL_REG_TOG (CI_PI_BASE_OFFSET + 0x0C)
+
+/* CSI INTERFACE CONTROL REG */
+#define CSI_CTRL_REG (CI_PI_BASE_OFFSET + 0x10)
+#define CSI_CTRL_REG_CSI_EN BIT_U(0)
+#define CSI_CTRL_REG_PIXEL_CLK_POL BIT_U(1)
+#define CSI_CTRL_REG_HSYNC_POL BIT_U(2)
+#define CSI_CTRL_REG_VSYNC_POL BIT_U(3)
+#define CSI_CTRL_REG_DE_POL BIT_U(4)
+#define CSI_CTRL_REG_PIXEL_DATA_POL BIT_U(5)
+#define CSI_CTRL_REG_CCIR_EXT_VSYNC_EN BIT_U(6)
+#define CSI_CTRL_REG_CCIR_EN BIT_U(7)
+#define CSI_CTRL_REG_CCIR_VIDEO_MODE BIT_U(8)
+#define CSI_CTRL_REG_CCIR_NTSC_EN BIT_U(9)
+#define CSI_CTRL_REG_CCIR_VSYNC_RESET_EN BIT_U(10)
+#define CSI_CTRL_REG_CCIR_ECC_ERR_CORRECT_EN BIT_U(11)
+#define CSI_CTRL_REG_HSYNC_FORCE_EN BIT_U(12)
+#define CSI_CTRL_REG_VSYNC_FORCE_EN BIT_U(13)
+#define CSI_CTRL_REG_GCLK_MODE_EN BIT_U(14)
+#define CSI_CTRL_REG_VALID_SEL BIT_U(15)
+#define CSI_CTRL_REG_RAW_OUT_SEL BIT_U(16)
+#define CSI_CTRL_REG_HSYNC_OUT_SEL BIT_U(17)
+#define CSI_CTRL_REG_HSYNC_PULSE(x) (((x) & 0x7U) << 19)
+#define CSI_CTRL_REG_UV_SWAP_EN BIT_U(22)
+#define CSI_CTRL_REG_DATA_TYPE_IN(x) (((x) & 0xFU) << 23)
+#define CSI_CTRL_REG_MASK_VSYNC_COUNTER(x) (((x) & 0x3U) << 27)
+#define CSI_CTRL_REG_SOFTRST BIT_U(31)
+
+#define DATA_TYPE_IN_UYVY_BT656_8BITS 0x0
+#define DATA_TYPE_IN_UYVY_BT656_10BITS 0x1
+#define DATA_TYPE_IN_RGB_8BITS 0x2
+#define DATA_TYPE_IN_BGR_8BITS 0x3
+#define DATA_TYPE_IN_RGB_24BITS 0x4
+#define DATA_TYPE_IN_YVYU_8BITS 0x5
+#define DATA_TYPE_IN_YUV_8BITS 0x6
+#define DATA_TYPE_IN_YVYU_16BITS 0x7
+#define DATA_TYPE_IN_YUV_24BITS 0x8
+#define DATA_TYPE_IN_BAYER_8BITS 0x9
+#define DATA_TYPE_IN_BAYER_10BITS 0xA
+#define DATA_TYPE_IN_BAYER_12BITS 0xB
+#define DATA_TYPE_IN_BAYER_16BITS 0xC
+
+#define CSI_CTRL_REG_SET (CI_PI_BASE_OFFSET + 0x14)
+#define CSI_CTRL_REG_CLR (CI_PI_BASE_OFFSET + 0x18)
+#define CSI_CTRL_REG_TOG (CI_PI_BASE_OFFSET + 0x1C)
+
+/* CSI interface Status */
+#define CSI_STATUS (CI_PI_BASE_OFFSET + 0x20)
+#define CSI_STATUS_FIELD_TOGGLE BIT_U(0)
+#define CSI_STATUS_ECC_ERROR BIT_U(1)
+
+#define CSI_STATUS_SET (CI_PI_BASE_OFFSET + 0x24)
+#define CSI_STATUS_CLR (CI_PI_BASE_OFFSET + 0x28)
+#define CSI_STATUS_TOG (CI_PI_BASE_OFFSET + 0x2C)
+
+/* CSI INTERFACE CONTROL REG1 */
+#define CSI_CTRL_REG1 (CI_PI_BASE_OFFSET + 0x30)
+#define CSI_CTRL_REG1_PIXEL_WIDTH(v) (((v) & 0xFFFFU) << 0)
+#define CSI_CTRL_REG1_VSYNC_PULSE(v) (((v) & 0xFFFFU) << 16)
+
+#define CSI_CTRL_REG1_SET (CI_PI_BASE_OFFSET + 0x34)
+#define CSI_CTRL_REG1_CLR (CI_PI_BASE_OFFSET + 0x38)
+#define CSI_CTRL_REG1_TOG (CI_PI_BASE_OFFSET + 0x3C)
+
+enum {
+ PI_MODE_INIT,
+ PI_GATE_CLOCK_MODE,
+ PI_CCIR_MODE,
+};
+struct mxc_parallel_csi_dev {
+ struct v4l2_subdev sd;
+ struct v4l2_device v4l2_dev;
+ struct v4l2_subdev *sensor_sd;
+
+ struct media_pad pads[MXC_PARALLEL_CSI_PADS_NUM];
+
+ void __iomem *csr_regs;
+ void __iomem *lpcg_regs;
+ struct platform_device *pdev;
+ u32 flags;
+ int irq;
+
+ struct clk *clk_ipg;
+ struct clk *clk_pixel;
+ bool clk_enable;
+
+ struct v4l2_async_subdev asd;
+ struct v4l2_async_notifier subdev_notifier;
+ struct v4l2_async_subdev *async_subdevs[2];
+ struct v4l2_mbus_framefmt format;
+
+ struct device *pd_pi;
+ struct device *pd_isi;
+ struct device_link *pd_pi_link;
+ struct device_link *pd_isi_link;
+
+ struct mutex lock;
+
+ u8 running;
+ u8 mode;
+ u8 uv_swap;
+ u8 tvdec;
+};
+
+static int debug;
+module_param(debug, int, 0644);
+MODULE_PARM_DESC(debug, "Debug level (0-2)");
+
+static int format;
+module_param(format, int, 0644);
+MODULE_PARM_DESC(format, "Format level (0-2)");
+
+#ifdef DEBUG
+static void mxc_pcsi_regs_dump(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct device *dev = &pcsidev->pdev->dev;
+ struct {
+ u32 offset;
+ const char *const name[32];
+ } registers[] = {
+ { 0x00, "HW_IF_CTRL_REG" },
+ { 0x10, "HW_CSI_CTRL_REG" },
+ { 0x20, "HW_CSI_STATUS" },
+ { 0x30, "HW_CSI_CTRL_REG1" },
+ };
+ u32 i;
+
+ for (i = 0; i < ARRAY_SIZE(registers); i++) {
+ u32 reg = readl(pcsidev->csr_regs + registers[i].offset);
+ dev_dbg(dev, "%20s[0x%.2x]: 0x%.8x\n",
+ registers[i].name, registers[i].offset, reg);
+ }
+}
+#else
+static void mxc_pcsi_regs_dump(struct mxc_parallel_csi_dev *pcsidev) { }
+#endif
+
+static struct mxc_parallel_csi_dev *sd_to_mxc_pcsi_dev(struct v4l2_subdev *sdev)
+{
+ return container_of(sdev, struct mxc_parallel_csi_dev, sd);
+}
+
+static int mxc_pcsi_clk_get(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct device *dev = &pcsidev->pdev->dev;
+
+ pcsidev->clk_pixel = devm_clk_get(dev, "pixel");
+ if (IS_ERR(pcsidev->clk_pixel)) {
+ dev_info(dev, "failed to get parallel csi pixel clk\n");
+ return PTR_ERR(pcsidev->clk_pixel);
+ }
+
+ pcsidev->clk_ipg = devm_clk_get(dev, "ipg");
+ if (IS_ERR(pcsidev->clk_ipg)) {
+ dev_info(dev, "failed to get parallel ipg pixel clk\n");
+ return PTR_ERR(pcsidev->clk_ipg);
+ }
+
+ return 0;
+}
+
+static int mxc_pcsi_attach_pd(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct device *dev = &pcsidev->pdev->dev;
+ struct device_link *link;
+
+ pcsidev->pd_pi = dev_pm_domain_attach_by_name(dev, "pd_pi");
+ if (IS_ERR(pcsidev->pd_pi)) {
+ if (PTR_ERR(pcsidev->pd_pi) != -EPROBE_DEFER) {
+ dev_err(dev, "attach pd_pi domain for pi fail\n");
+ return PTR_ERR(pcsidev->pd_pi);
+ } else {
+ return PTR_ERR(pcsidev->pd_pi);
+ }
+ }
+ link = device_link_add(dev, pcsidev->pd_pi,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ pcsidev->pd_pi_link = link;
+
+ pcsidev->pd_isi = dev_pm_domain_attach_by_name(dev, "pd_isi_ch0");
+ if (IS_ERR(pcsidev->pd_isi)) {
+ if (PTR_ERR(pcsidev->pd_isi) != -EPROBE_DEFER) {
+ dev_err(dev, "attach pd_isi_ch0 domain for pi fail\n");
+ return PTR_ERR(pcsidev->pd_isi);
+ } else {
+ return PTR_ERR(pcsidev->pd_isi);
+ }
+ }
+ link = device_link_add(dev, pcsidev->pd_isi,
+ DL_FLAG_STATELESS |
+ DL_FLAG_PM_RUNTIME);
+ if (IS_ERR(link))
+ return PTR_ERR(link);
+ pcsidev->pd_isi_link = link;
+
+ return 0;
+}
+
+static void mxc_pcsi_detach_pd(struct mxc_parallel_csi_dev *pcsidev)
+{
+ device_link_del(pcsidev->pd_pi_link);
+ device_link_del(pcsidev->pd_isi_link);
+ dev_pm_domain_detach(pcsidev->pd_pi, true);
+ dev_pm_domain_detach(pcsidev->pd_isi, true);
+}
+
+static int mxc_pcsi_clk_enable(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct device *dev = &pcsidev->pdev->dev;
+ int ret;
+
+ if (pcsidev->clk_enable)
+ return 0;
+
+ ret = clk_prepare_enable(pcsidev->clk_pixel);
+ if (ret < 0) {
+ dev_info(dev, "enable pixel clk error (%d)\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(pcsidev->clk_ipg);
+ if (ret < 0) {
+ dev_info(dev, "enable ipg clk error (%d)\n", ret);
+ return ret;
+ }
+ pcsidev->clk_enable = true;
+
+ return 0;
+}
+
+static void mxc_pcsi_clk_disable(struct mxc_parallel_csi_dev *pcsidev)
+{
+ if (!pcsidev->clk_enable)
+ return;
+
+ clk_disable_unprepare(pcsidev->clk_pixel);
+ clk_disable_unprepare(pcsidev->clk_ipg);
+
+ pcsidev->clk_enable = false;
+}
+
+static void mxc_pcsi_sw_reset(struct mxc_parallel_csi_dev *pcsidev)
+{
+ u32 val;
+
+ /* Softwaret Reset */
+ val = CSI_CTRL_REG_SOFTRST;
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET);
+
+ msleep(1);
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR);
+}
+
+static void mxc_pcsi_csr_config(struct mxc_parallel_csi_dev *pcsidev)
+{
+ u32 val;
+
+ /* Software Reset */
+ mxc_pcsi_sw_reset(pcsidev);
+
+ /* Config PL Data Type */
+ val = IF_CTRL_REG_DATA_TYPE(DATA_TYPE_OUT_YUV444);
+ writel(val, pcsidev->csr_regs + IF_CTRL_REG_SET);
+
+ /* Enable sync Force */
+ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN);
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET);
+
+ /* Enable Pixel Link */
+ val = IF_CTRL_REG_PL_ENABLE;
+ writel(val, pcsidev->csr_regs + IF_CTRL_REG_SET);
+
+ /* Enable Pixel Link */
+ val = IF_CTRL_REG_PL_VALID;
+ writel(val, pcsidev->csr_regs + IF_CTRL_REG_SET);
+
+ /* Config CTRL REG */
+ val = readl(pcsidev->csr_regs + CSI_CTRL_REG);
+ val |= (CSI_CTRL_REG_DATA_TYPE_IN(DATA_TYPE_IN_UYVY_BT656_8BITS) |
+ CSI_CTRL_REG_HSYNC_POL |
+ CSI_CTRL_REG_MASK_VSYNC_COUNTER(3) |
+ CSI_CTRL_REG_HSYNC_PULSE(2));
+
+ if (pcsidev->uv_swap)
+ val |= CSI_CTRL_REG_UV_SWAP_EN;
+
+ if (pcsidev->mode & PI_GATE_CLOCK_MODE) {
+ val |= CSI_CTRL_REG_GCLK_MODE_EN;
+ } else if (pcsidev->mode & PI_CCIR_MODE) {
+ val |= (CSI_CTRL_REG_CCIR_EN |
+ CSI_CTRL_REG_CCIR_VSYNC_RESET_EN |
+ CSI_CTRL_REG_CCIR_EXT_VSYNC_EN |
+ CSI_CTRL_REG_CCIR_ECC_ERR_CORRECT_EN);
+ }
+
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG);
+}
+
+static void mxc_pcsi_config_ctrl_reg1(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct device *dev = &pcsidev->pdev->dev;
+ u32 val;
+
+ if (pcsidev->format.width <= 0 || pcsidev->format.height <= 0) {
+ dev_dbg(dev, "%s width/height invalid\n", __func__);
+ return;
+ }
+
+ /* Config Pixel Width */
+ val = (CSI_CTRL_REG1_PIXEL_WIDTH(pcsidev->format.width - 1) |
+ CSI_CTRL_REG1_VSYNC_PULSE(pcsidev->format.width << 1));
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG1);
+}
+
+static void mxc_pcsi_enable_csi(struct mxc_parallel_csi_dev *pcsidev)
+{
+ u32 val;
+
+ /* Enable CSI */
+ val = CSI_CTRL_REG_CSI_EN;
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET);
+
+ /* Disable SYNC Force */
+ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN);
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR);
+}
+
+static void mxc_pcsi_disable_csi(struct mxc_parallel_csi_dev *pcsidev)
+{
+ u32 val;
+
+ /* Enable Sync Force */
+ val = (CSI_CTRL_REG_HSYNC_FORCE_EN | CSI_CTRL_REG_VSYNC_FORCE_EN);
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_SET);
+
+ /* Disable CSI */
+ val = CSI_CTRL_REG_CSI_EN;
+ writel(val, pcsidev->csr_regs + CSI_CTRL_REG_CLR);
+
+ /* Disable Pixel Link */
+ val = IF_CTRL_REG_PL_VALID | IF_CTRL_REG_PL_ENABLE;
+ writel(val, pcsidev->csr_regs + IF_CTRL_REG_CLR);
+}
+
+static struct media_pad *
+mxc_pcsi_get_remote_sensor_pad(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct v4l2_subdev *subdev = &pcsidev->sd;
+ struct media_pad *sink_pad, *source_pad;
+ int i;
+
+ while (1) {
+ source_pad = NULL;
+ for (i = 0; i < subdev->entity.num_pads; i++) {
+ sink_pad = &subdev->entity.pads[i];
+
+ if (sink_pad->flags & MEDIA_PAD_FL_SINK) {
+ source_pad = media_entity_remote_pad(sink_pad);
+ if (source_pad)
+ break;
+ }
+ }
+ /* return first pad point in the loop */
+ return source_pad;
+ }
+
+ if (i == subdev->entity.num_pads)
+ v4l2_err(&pcsidev->v4l2_dev,
+ "%s, No remote pad found!\n", __func__);
+
+ return NULL;
+}
+
+static struct v4l2_subdev *mxc_get_remote_subdev(struct mxc_parallel_csi_dev *pcsidev,
+ const char * const label)
+{
+ struct media_pad *source_pad;
+ struct v4l2_subdev *sen_sd;
+
+ /* Get remote source pad */
+ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev);
+ if (!source_pad) {
+ v4l2_err(&pcsidev->sd, "%s, No remote pad found!\n", label);
+ return NULL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(&pcsidev->sd, "%s, No remote subdev found!\n", label);
+ return NULL;
+ }
+
+ return sen_sd;
+}
+
+static int mxc_pcsi_get_sensor_fmt(struct mxc_parallel_csi_dev *pcsidev)
+{
+ struct v4l2_mbus_framefmt *mf = &pcsidev->format;
+ struct v4l2_subdev *sen_sd;
+ struct media_pad *source_pad;
+ struct v4l2_subdev_format src_fmt;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev);
+ if (!source_pad) {
+ v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ src_fmt.pad = source_pad->index;
+ src_fmt.which = V4L2_SUBDEV_FORMAT_ACTIVE;
+ ret = v4l2_subdev_call(sen_sd, pad, get_fmt, NULL, &src_fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return -EINVAL;
+
+ /* Update input frame size and formate */
+ memcpy(mf, &src_fmt.format, sizeof(struct v4l2_mbus_framefmt));
+
+ if (mf->code == MEDIA_BUS_FMT_YUYV8_2X8 ||
+ mf->code == MEDIA_BUS_FMT_UYVY8_2X8)
+ pcsidev->uv_swap = 1;
+
+ dev_dbg(&pcsidev->pdev->dev,
+ "width=%d, height=%d, fmt.code=0x%x\n",
+ mf->width, mf->height, mf->code);
+
+ return 0;
+}
+
+static int mxc_pcsi_enum_framesizes(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_size_enum *fse)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(pcsidev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_size, NULL, fse);
+}
+
+static int mxc_pcsi_enum_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_frame_interval_enum *fie)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(pcsidev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, pad, enum_frame_interval, NULL, fie);
+}
+
+static int mxc_pcsi_get_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_mbus_framefmt *mf = &fmt->format;
+
+ mxc_pcsi_get_sensor_fmt(pcsidev);
+
+ memcpy(mf, &pcsidev->format, sizeof(struct v4l2_mbus_framefmt));
+ /* Source/Sink pads crop rectangle size */
+
+ return 0;
+}
+
+static int mxc_pcsi_set_fmt(struct v4l2_subdev *sd,
+ struct v4l2_subdev_pad_config *cfg,
+ struct v4l2_subdev_format *fmt)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+ struct media_pad *source_pad;
+ int ret;
+
+ /* Get remote source pad */
+ source_pad = mxc_pcsi_get_remote_sensor_pad(pcsidev);
+ if (!source_pad) {
+ v4l2_err(&pcsidev->v4l2_dev, "%s, No remote pad found!\n", __func__);
+ return -EINVAL;
+ }
+
+ /* Get remote source pad subdev */
+ sen_sd = media_entity_to_v4l2_subdev(source_pad->entity);
+ if (!sen_sd) {
+ v4l2_err(&pcsidev->v4l2_dev, "%s, No remote subdev found!\n", __func__);
+ return -EINVAL;
+ }
+
+ fmt->pad = source_pad->index;
+ ret = v4l2_subdev_call(sen_sd, pad, set_fmt, NULL, fmt);
+ if (ret < 0 && ret != -ENOIOCTLCMD)
+ return ret;
+
+ return 0;
+}
+
+static int mxc_pcsi_s_power(struct v4l2_subdev *sd, int on)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(pcsidev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, core, s_power, on);
+}
+
+static int mxc_pcsi_g_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(pcsidev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, video, g_frame_interval, interval);
+}
+
+static int mxc_pcsi_s_frame_interval(struct v4l2_subdev *sd,
+ struct v4l2_subdev_frame_interval *interval)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct v4l2_subdev *sen_sd;
+
+ sen_sd = mxc_get_remote_subdev(pcsidev, __func__);
+ if (!sen_sd)
+ return -EINVAL;
+
+ return v4l2_subdev_call(sen_sd, video, s_frame_interval, interval);
+}
+
+static int mxc_pcsi_s_stream(struct v4l2_subdev *sd, int enable)
+{
+ struct mxc_parallel_csi_dev *pcsidev = sd_to_mxc_pcsi_dev(sd);
+ struct device *dev = &pcsidev->pdev->dev;
+
+ dev_dbg(dev, "%s: enable = %d\n", __func__, enable);
+
+ if (enable) {
+ pm_runtime_get_sync(dev);
+ if (!pcsidev->running) {
+ mxc_pcsi_get_sensor_fmt(pcsidev);
+ mxc_pcsi_csr_config(pcsidev);
+ mxc_pcsi_config_ctrl_reg1(pcsidev);
+ mxc_pcsi_enable_csi(pcsidev);
+ mxc_pcsi_regs_dump(pcsidev);
+ }
+ pcsidev->running++;
+ } else {
+ if (pcsidev->running)
+ mxc_pcsi_disable_csi(pcsidev);
+ pcsidev->running--;
+ pm_runtime_put(dev);
+ }
+
+ return 0;
+}
+
+static int mxc_pcsi_link_setup(struct media_entity *entity,
+ const struct media_pad *local,
+ const struct media_pad *remote, u32 flags)
+{
+ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(entity);
+ struct platform_device *pdev = v4l2_get_subdevdata(sd);
+
+ if (local->flags & MEDIA_PAD_FL_SOURCE) {
+ switch (local->index) {
+ case MXC_PARALLEL_CSI_PAD_SOURCE:
+ break;
+ default:
+ dev_err(&pdev->dev, "%s invalid source pad\n", __func__);
+ return -EINVAL;
+ }
+ } else if (local->flags & MEDIA_PAD_FL_SINK) {
+ switch (local->index) {
+ case MXC_PARALLEL_CSI_PAD_SINK:
+ break;
+ default:
+ dev_err(&pdev->dev, "%s invalid sink pad\n", __func__);
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static struct v4l2_subdev_pad_ops pcsi_pad_ops = {
+ .enum_frame_size = mxc_pcsi_enum_framesizes,
+ .enum_frame_interval = mxc_pcsi_enum_frame_interval,
+ .get_fmt = mxc_pcsi_get_fmt,
+ .set_fmt = mxc_pcsi_set_fmt,
+};
+
+static struct v4l2_subdev_core_ops pcsi_core_ops = {
+ .s_power = mxc_pcsi_s_power,
+};
+
+static struct v4l2_subdev_video_ops pcsi_video_ops = {
+ .g_frame_interval = mxc_pcsi_g_frame_interval,
+ .s_frame_interval = mxc_pcsi_s_frame_interval,
+ .s_stream = mxc_pcsi_s_stream,
+};
+
+static struct v4l2_subdev_ops pcsi_subdev_ops = {
+ .core = &pcsi_core_ops,
+ .video = &pcsi_video_ops,
+ .pad = &pcsi_pad_ops,
+};
+
+static const struct media_entity_operations mxc_pcsi_sd_media_ops = {
+ .link_setup = mxc_pcsi_link_setup,
+};
+
+static int mxc_parallel_csi_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct resource *mem_res;
+ struct mxc_parallel_csi_dev *pcsidev;
+ int ret;
+
+ pcsidev = devm_kzalloc(dev, sizeof(*pcsidev), GFP_KERNEL);
+ if (!pcsidev)
+ return -ENOMEM;
+
+ pcsidev->pdev = pdev;
+
+ mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ pcsidev->csr_regs = devm_ioremap_resource(dev, mem_res);
+ if (IS_ERR(pcsidev->csr_regs)) {
+ dev_dbg(dev, "Failed to get parallel CSI CSR register\n");
+ return PTR_ERR(pcsidev->csr_regs);
+ }
+
+ ret = mxc_pcsi_clk_get(pcsidev);
+ if (ret < 0)
+ return ret;
+
+ ret = mxc_pcsi_attach_pd(pcsidev);
+ if (ret < 0)
+ return ret;
+
+ v4l2_subdev_init(&pcsidev->sd, &pcsi_subdev_ops);
+
+ pcsidev->mode = PI_GATE_CLOCK_MODE;
+
+ pcsidev->sd.owner = THIS_MODULE;
+ sprintf(pcsidev->sd.name, "%s", MXC_PARALLEL_CSI_SUBDEV_NAME);
+
+ pcsidev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
+ pcsidev->sd.entity.function = MEDIA_ENT_F_IO_V4L;
+
+ pcsidev->sd.dev = dev;
+
+ pcsidev->pads[MXC_PARALLEL_CSI_PAD_SINK].flags = MEDIA_PAD_FL_SINK;
+ pcsidev->pads[MXC_PARALLEL_CSI_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
+
+ ret = media_entity_pads_init(&pcsidev->sd.entity,
+ MXC_PARALLEL_CSI_PADS_NUM,
+ pcsidev->pads);
+ if (ret < 0)
+ goto e_clkdis;
+
+ pcsidev->sd.entity.ops = &mxc_pcsi_sd_media_ops;
+
+ v4l2_set_subdevdata(&pcsidev->sd, pdev);
+ platform_set_drvdata(pdev, pcsidev);
+
+ pcsidev->running = 0;
+ pm_runtime_enable(dev);
+
+ dev_info(dev, "%s probe successfully\n", __func__);
+ return 0;
+
+e_clkdis:
+ media_entity_cleanup(&pcsidev->sd.entity);
+ return ret;
+}
+
+static int mxc_parallel_csi_remove(struct platform_device *pdev)
+{
+ struct mxc_parallel_csi_dev *pcsidev =
+ (struct mxc_parallel_csi_dev *)platform_get_drvdata(pdev);
+
+ mxc_pcsi_detach_pd(pcsidev);
+ media_entity_cleanup(&pcsidev->sd.entity);
+ pm_runtime_disable(&pdev->dev);
+
+ return 0;
+}
+
+static int parallel_csi_pm_suspend(struct device *dev)
+{
+ return pm_runtime_force_suspend(dev);
+}
+
+static int parallel_csi_pm_resume(struct device *dev)
+{
+ return pm_runtime_force_resume(dev);
+}
+
+static int parallel_csi_runtime_suspend(struct device *dev)
+{
+ struct mxc_parallel_csi_dev *pcsidev = dev_get_drvdata(dev);
+
+ mxc_pcsi_clk_disable(pcsidev);
+
+ return 0;
+}
+
+static int parallel_csi_runtime_resume(struct device *dev)
+{
+ struct mxc_parallel_csi_dev *pcsidev = dev_get_drvdata(dev);
+ int ret;
+
+ ret = mxc_pcsi_clk_enable(pcsidev);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static const struct dev_pm_ops parallel_csi_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(parallel_csi_pm_suspend, parallel_csi_pm_resume)
+ SET_RUNTIME_PM_OPS(parallel_csi_runtime_suspend,
+ parallel_csi_runtime_resume,
+ NULL)
+};
+
+static const struct of_device_id parallel_csi_of_match[] = {
+ { .compatible = "fsl,mxc-parallel-csi",},
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, parallel_csi_of_match);
+
+static struct platform_driver parallel_csi_driver = {
+ .driver = {
+ .name = MXC_PARALLEL_CSI_DRIVER_NAME,
+ .of_match_table = parallel_csi_of_match,
+ .pm = &parallel_csi_pm_ops,
+ },
+ .probe = mxc_parallel_csi_probe,
+ .remove = mxc_parallel_csi_remove,
+};
+
+module_platform_driver(parallel_csi_driver);
+
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_DESCRIPTION("MXC PARALLEL CSI driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" MXC_PARALLEL_CSI_DRIVER_NAME);