diff options
Diffstat (limited to 'drivers/staging/media/imx')
-rw-r--r-- | drivers/staging/media/imx/Kconfig | 84 | ||||
-rw-r--r-- | drivers/staging/media/imx/Makefile | 11 | ||||
-rw-r--r-- | drivers/staging/media/imx/gmsl-max9286.c | 3344 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-common.h | 99 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-cap.c | 1795 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-core.c | 621 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-core.h | 411 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-hw.c | 840 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-hw.h | 484 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-isi-m2m.c | 1201 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-media-dev.c | 1079 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-mipi-csi2-sam.c | 1739 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-mipi-csi2.c | 1170 | ||||
-rw-r--r-- | drivers/staging/media/imx/imx8-parallel-csi.c | 837 |
14 files changed, 13715 insertions, 0 deletions
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, ®); + 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, ®); + 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..6ba5b3a86f49 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-cap.c @@ -0,0 +1,1795 @@ +// 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, + PAGE_ALIGN(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, + PAGE_ALIGN((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; + 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_init(&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_init(&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_init(&buf->list); + } + + 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, + PAGE_ALIGN(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,imx8mp-isi")) && + (fse.max_width > ISI_2K || fse.min_width > ISI_2K) && + (isi_cap->id == 1)) + 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,imx8mp-isi") && + fie.width > ISI_2K && isi_cap->id == 1) + 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..bd7381d7bcaa --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.c @@ -0,0 +1,621 @@ +// 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; + unsigned long flags; + u32 status; + + spin_lock_irqsave(&mxc_isi->slock, flags); + + 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_irqrestore(&mxc_isi->slock, flags); + 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"); + + 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 -EPROBE_DEFER; + + 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) { + if (ret != -EPROBE_DEFER) + 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; + } + + mxc_isi->chain = syscon_regmap_lookup_by_phandle(dev->of_node, "isi_chain"); + if (IS_ERR(mxc_isi->chain)) + mxc_isi->chain = NULL; + + 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..c955ec21c8d8 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-core.h @@ -0,0 +1,411 @@ +/* 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 regmap *chain; + + 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..f8e804483a48 --- /dev/null +++ b/drivers/staging/media/imx/imx8-isi-hw.c @@ -0,0 +1,840 @@ +// 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_RGBA32) || + (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); + if (mxc_isi->chain) + regmap_write(mxc_isi->chain, CHNL_CTRL, CHNL_CTRL_CLK_EN_MASK); + mxc_isi->chain_buf = 1; + } else { + val = readl(mxc_isi->regs + CHNL_CTRL); + val &= ~CHNL_CTRL_CHAIN_BUF_MASK; + writel(val, mxc_isi->regs + CHNL_CTRL); + mxc_isi->chain_buf = 0; + } +} + +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_DISABLE << CHNL_CTRL_CLK_EN_OFFSET); + writel(val, mxc_isi->regs + CHNL_CTRL); + + if (mxc_isi->chain_buf && mxc_isi->chain) + regmap_write(mxc_isi->chain, CHNL_CTRL, 0x0); +} +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..54cde426fa0d --- /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 0x6000000 +#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..e292b91e9ecc --- /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_RGBA32, + .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..875c9e92d531 --- /dev/null +++ b/drivers/staging/media/imx/imx8-media-dev.c @@ -0,0 +1,1079 @@ +// 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; + } + + if (mxc_md->media_dev.devnode) + 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..b10abd0d1a04 --- /dev/null +++ b/drivers/staging/media/imx/imx8-mipi-csi2-sam.c @@ -0,0 +1,1739 @@ +// 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 csi_state *state = mipi_sd_to_csi_state(dev); + struct v4l2_capability *cap = (struct v4l2_capability *)args; + strcpy((char *)cap->driver, "csi_sam_subdev"); + cap->bus_info[0] = state->index; + 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); + 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 = ¶llel_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); |