3560 lines
111 KiB
Diff
3560 lines
111 KiB
Diff
From 8e40238bdd44bb0088d19c053ab2261523c81637 Mon Sep 17 00:00:00 2001
|
|
From: Lionel VITTE <lionel.vitte@st.com>
|
|
Date: Fri, 8 Nov 2019 16:52:41 +0100
|
|
Subject: [PATCH 13/31] ARM stm32mp1 r3 MEDIA
|
|
|
|
---
|
|
Documentation/media/uapi/v4l/subdev-formats.rst | 107 +++
|
|
drivers/media/i2c/Kconfig | 13 +
|
|
drivers/media/i2c/Makefile | 1 +
|
|
drivers/media/i2c/ov5640.c | 1000 +++++++++++++--------
|
|
drivers/media/i2c/st-mipid02.c | 1076 +++++++++++++++++++++++
|
|
drivers/media/platform/Kconfig | 2 +-
|
|
drivers/media/platform/stm32/stm32-cec.c | 96 +-
|
|
drivers/media/platform/stm32/stm32-dcmi.c | 366 ++++++--
|
|
drivers/media/usb/uvc/uvc_queue.c | 15 +-
|
|
drivers/media/usb/uvc/uvc_v4l2.c | 11 +-
|
|
drivers/media/usb/uvc/uvcvideo.h | 2 +
|
|
drivers/media/v4l2-core/v4l2-fwnode.c | 3 +
|
|
include/uapi/linux/media-bus-format.h | 3 +-
|
|
13 files changed, 2266 insertions(+), 429 deletions(-)
|
|
create mode 100644 drivers/media/i2c/st-mipid02.c
|
|
|
|
diff --git a/Documentation/media/uapi/v4l/subdev-formats.rst b/Documentation/media/uapi/v4l/subdev-formats.rst
|
|
index 8e73fcf..44f427c 100644
|
|
--- a/Documentation/media/uapi/v4l/subdev-formats.rst
|
|
+++ b/Documentation/media/uapi/v4l/subdev-formats.rst
|
|
@@ -973,6 +973,113 @@ The following tables list existing packed RGB formats.
|
|
- r\ :sub:`2`
|
|
- r\ :sub:`1`
|
|
- r\ :sub:`0`
|
|
+ * .. _MEDIA-BUS-FMT-BGR888-3X8:
|
|
+
|
|
+ - MEDIA_BUS_FMT_BGR888_3X8
|
|
+ - 0x101b
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ - b\ :sub:`7`
|
|
+ - b\ :sub:`6`
|
|
+ - b\ :sub:`5`
|
|
+ - b\ :sub:`4`
|
|
+ - b\ :sub:`3`
|
|
+ - b\ :sub:`2`
|
|
+ - b\ :sub:`1`
|
|
+ - b\ :sub:`0`
|
|
+ * -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ - g\ :sub:`7`
|
|
+ - g\ :sub:`6`
|
|
+ - g\ :sub:`5`
|
|
+ - g\ :sub:`4`
|
|
+ - g\ :sub:`3`
|
|
+ - g\ :sub:`2`
|
|
+ - g\ :sub:`1`
|
|
+ - g\ :sub:`0`
|
|
+ * -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ -
|
|
+ - r\ :sub:`7`
|
|
+ - r\ :sub:`6`
|
|
+ - r\ :sub:`5`
|
|
+ - r\ :sub:`4`
|
|
+ - r\ :sub:`3`
|
|
+ - r\ :sub:`2`
|
|
+ - r\ :sub:`1`
|
|
+ - r\ :sub:`0`
|
|
* .. _MEDIA-BUS-FMT-GBR888-1X24:
|
|
|
|
- MEDIA_BUS_FMT_GBR888_1X24
|
|
diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
|
|
index 8b1ae1d..6a8e58d 100644
|
|
--- a/drivers/media/i2c/Kconfig
|
|
+++ b/drivers/media/i2c/Kconfig
|
|
@@ -1068,6 +1068,19 @@ config VIDEO_I2C
|
|
To compile this driver as a module, choose M here: the
|
|
module will be called video-i2c
|
|
|
|
+config VIDEO_ST_MIPID02
|
|
+ tristate "STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge"
|
|
+ depends on I2C && VIDEO_V4L2 && VIDEO_V4L2_SUBDEV_API
|
|
+ depends on MEDIA_CAMERA_SUPPORT
|
|
+ select V4L2_FWNODE
|
|
+ help
|
|
+ Support for STMicroelectronics MIPID02 CSI-2 to PARALLEL bridge.
|
|
+ It is used to allow usage of CSI-2 sensor with PARALLEL port
|
|
+ controller.
|
|
+
|
|
+ To compile this driver as a module, choose M here: the
|
|
+ module will be called st-mipid02.
|
|
+
|
|
endmenu
|
|
|
|
menu "Sensors used on soc_camera driver"
|
|
diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
|
|
index 520b3c3..051c68e 100644
|
|
--- a/drivers/media/i2c/Makefile
|
|
+++ b/drivers/media/i2c/Makefile
|
|
@@ -108,5 +108,6 @@ obj-$(CONFIG_VIDEO_OV2659) += ov2659.o
|
|
obj-$(CONFIG_VIDEO_TC358743) += tc358743.o
|
|
obj-$(CONFIG_VIDEO_IMX258) += imx258.o
|
|
obj-$(CONFIG_VIDEO_IMX274) += imx274.o
|
|
+obj-$(CONFIG_VIDEO_ST_MIPID02) += st-mipid02.o
|
|
|
|
obj-$(CONFIG_SDR_MAX2175) += max2175.o
|
|
diff --git a/drivers/media/i2c/ov5640.c b/drivers/media/i2c/ov5640.c
|
|
index 2023df1..62269ac 100644
|
|
--- a/drivers/media/i2c/ov5640.c
|
|
+++ b/drivers/media/i2c/ov5640.c
|
|
@@ -66,6 +66,7 @@
|
|
#define OV5640_REG_TIMING_VTS 0x380e
|
|
#define OV5640_REG_TIMING_TC_REG20 0x3820
|
|
#define OV5640_REG_TIMING_TC_REG21 0x3821
|
|
+#define OV5640_REG_DVP_PCLK_DIVIDER 0x3824
|
|
#define OV5640_REG_AEC_CTRL00 0x3a00
|
|
#define OV5640_REG_AEC_B50_STEP 0x3a08
|
|
#define OV5640_REG_AEC_B60_STEP 0x3a0a
|
|
@@ -82,6 +83,9 @@
|
|
#define OV5640_REG_SIGMADELTA_CTRL0C 0x3c0c
|
|
#define OV5640_REG_FRAME_CTRL01 0x4202
|
|
#define OV5640_REG_FORMAT_CONTROL00 0x4300
|
|
+#define OV5640_REG_VFIFO_HSIZE 0x4602
|
|
+#define OV5640_REG_VFIFO_VSIZE 0x4604
|
|
+#define OV5640_REG_JPG_MODE_SELECT 0x4713
|
|
#define OV5640_REG_POLARITY_CTRL00 0x4740
|
|
#define OV5640_REG_MIPI_CTRL00 0x4800
|
|
#define OV5640_REG_DEBUG_MODE 0x4814
|
|
@@ -94,9 +98,6 @@
|
|
#define OV5640_REG_SDE_CTRL5 0x5585
|
|
#define OV5640_REG_AVG_READOUT 0x56a1
|
|
|
|
-#define OV5640_SCLK2X_ROOT_DIVIDER_DEFAULT 1
|
|
-#define OV5640_SCLK_ROOT_DIVIDER_DEFAULT 2
|
|
-
|
|
enum ov5640_mode_id {
|
|
OV5640_MODE_QCIF_176_144 = 0,
|
|
OV5640_MODE_QVGA_320_240,
|
|
@@ -113,9 +114,19 @@ enum ov5640_mode_id {
|
|
enum ov5640_frame_rate {
|
|
OV5640_15_FPS = 0,
|
|
OV5640_30_FPS,
|
|
+ OV5640_60_FPS,
|
|
OV5640_NUM_FRAMERATES,
|
|
};
|
|
|
|
+enum ov5640_format_mux {
|
|
+ OV5640_FMT_MUX_YUV422 = 0,
|
|
+ OV5640_FMT_MUX_RGB,
|
|
+ OV5640_FMT_MUX_DITHER,
|
|
+ OV5640_FMT_MUX_RAW_DPC,
|
|
+ OV5640_FMT_MUX_SNR_RAW,
|
|
+ OV5640_FMT_MUX_RAW_CIP,
|
|
+};
|
|
+
|
|
struct ov5640_pixfmt {
|
|
u32 code;
|
|
u32 colorspace;
|
|
@@ -127,6 +138,10 @@ static const struct ov5640_pixfmt ov5640_formats[] = {
|
|
{ MEDIA_BUS_FMT_YUYV8_2X8, V4L2_COLORSPACE_SRGB, },
|
|
{ MEDIA_BUS_FMT_RGB565_2X8_LE, V4L2_COLORSPACE_SRGB, },
|
|
{ MEDIA_BUS_FMT_RGB565_2X8_BE, V4L2_COLORSPACE_SRGB, },
|
|
+ { MEDIA_BUS_FMT_SBGGR8_1X8, V4L2_COLORSPACE_SRGB, },
|
|
+ { MEDIA_BUS_FMT_SGBRG8_1X8, V4L2_COLORSPACE_SRGB, },
|
|
+ { MEDIA_BUS_FMT_SGRBG8_1X8, V4L2_COLORSPACE_SRGB, },
|
|
+ { MEDIA_BUS_FMT_SRGGB8_1X8, V4L2_COLORSPACE_SRGB, },
|
|
};
|
|
|
|
/*
|
|
@@ -141,6 +156,7 @@ MODULE_PARM_DESC(virtual_channel,
|
|
static const int ov5640_framerates[] = {
|
|
[OV5640_15_FPS] = 15,
|
|
[OV5640_30_FPS] = 30,
|
|
+ [OV5640_60_FPS] = 60,
|
|
};
|
|
|
|
/* regulator supplies */
|
|
@@ -202,6 +218,7 @@ struct ov5640_ctrls {
|
|
struct v4l2_ctrl *test_pattern;
|
|
struct v4l2_ctrl *hflip;
|
|
struct v4l2_ctrl *vflip;
|
|
+ struct v4l2_ctrl *link_freq;
|
|
};
|
|
|
|
struct ov5640_dev {
|
|
@@ -261,8 +278,7 @@ static inline struct v4l2_subdev *ctrl_to_sd(struct v4l2_ctrl *ctrl)
|
|
static const struct reg_value ov5640_init_setting_30fps_VGA[] = {
|
|
{0x3103, 0x11, 0, 0}, {0x3008, 0x82, 0, 5}, {0x3008, 0x42, 0, 0},
|
|
{0x3103, 0x03, 0, 0}, {0x3017, 0x00, 0, 0}, {0x3018, 0x00, 0, 0},
|
|
- {0x3034, 0x18, 0, 0}, {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0},
|
|
- {0x3037, 0x13, 0, 0}, {0x3630, 0x36, 0, 0},
|
|
+ {0x3630, 0x36, 0, 0},
|
|
{0x3631, 0x0e, 0, 0}, {0x3632, 0xe2, 0, 0}, {0x3633, 0x12, 0, 0},
|
|
{0x3621, 0xe0, 0, 0}, {0x3704, 0xa0, 0, 0}, {0x3703, 0x5a, 0, 0},
|
|
{0x3715, 0x78, 0, 0}, {0x3717, 0x01, 0, 0}, {0x370b, 0x60, 0, 0},
|
|
@@ -289,7 +305,7 @@ static const struct reg_value ov5640_init_setting_30fps_VGA[] = {
|
|
{0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x3000, 0x00, 0, 0},
|
|
{0x3002, 0x1c, 0, 0}, {0x3004, 0xff, 0, 0}, {0x3006, 0xc3, 0, 0},
|
|
{0x302e, 0x08, 0, 0}, {0x4300, 0x3f, 0, 0},
|
|
- {0x501f, 0x00, 0, 0}, {0x4713, 0x03, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
+ {0x501f, 0x00, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
{0x440e, 0x00, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
{0x4837, 0x0a, 0, 0}, {0x3824, 0x02, 0, 0},
|
|
{0x5000, 0xa7, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x5180, 0xff, 0, 0},
|
|
@@ -344,66 +360,8 @@ static const struct reg_value ov5640_init_setting_30fps_VGA[] = {
|
|
{0x3a1f, 0x14, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3c00, 0x04, 0, 300},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_30fps_VGA_640_480[] = {
|
|
- {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x0e, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x3503, 0x00, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_15fps_VGA_640_480[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_30fps_XGA_1024_768[] = {
|
|
- {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x0e, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0}, {0x3503, 0x00, 0, 0},
|
|
- {0x3035, 0x12, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_15fps_XGA_1024_768[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_VGA_640_480[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -416,13 +374,13 @@ static const struct reg_value ov5640_setting_15fps_XGA_1024_768[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_30fps_QVGA_320_240[] = {
|
|
- {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_XGA_1024_768[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -435,13 +393,12 @@ static const struct reg_value ov5640_setting_30fps_QVGA_320_240[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_QVGA_320_240[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_QVGA_320_240[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -454,13 +411,12 @@ static const struct reg_value ov5640_setting_15fps_QVGA_320_240[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_30fps_QCIF_176_144[] = {
|
|
- {0x3035, 0x14, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_QCIF_176_144[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -473,32 +429,12 @@ static const struct reg_value ov5640_setting_30fps_QCIF_176_144[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_QCIF_176_144[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_30fps_NTSC_720_480[] = {
|
|
- {0x3035, 0x12, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_NTSC_720_480[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -511,32 +447,12 @@ static const struct reg_value ov5640_setting_30fps_NTSC_720_480[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_NTSC_720_480[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x3c, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_30fps_PAL_720_576[] = {
|
|
- {0x3035, 0x12, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_PAL_720_576[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -549,33 +465,12 @@ static const struct reg_value ov5640_setting_30fps_PAL_720_576[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_PAL_720_576[] = {
|
|
- {0x3035, 0x22, 0, 0}, {0x3036, 0x38, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x04, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9b, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x38, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x06, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_30fps_720P_1280_720[] = {
|
|
- {0x3008, 0x42, 0, 0},
|
|
- {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0},
|
|
+static const struct reg_value ov5640_setting_720P_1280_720[] = {
|
|
+ {0x3c07, 0x07, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x31, 0, 0},
|
|
{0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -588,34 +483,13 @@ static const struct reg_value ov5640_setting_30fps_720P_1280_720[] = {
|
|
{0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0},
|
|
{0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0},
|
|
{0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0},
|
|
- {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0}, {0x4005, 0x1a, 0, 0},
|
|
- {0x3008, 0x02, 0, 0}, {0x3503, 0, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0}, {0x5001, 0xa3, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_720P_1280_720[] = {
|
|
- {0x3035, 0x41, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x31, 0, 0},
|
|
- {0x3815, 0x31, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0xfa, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x06, 0, 0}, {0x3807, 0xa9, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0},
|
|
- {0x3618, 0x00, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3709, 0x52, 0, 0}, {0x370c, 0x03, 0, 0}, {0x3a02, 0x02, 0, 0},
|
|
- {0x3a03, 0xe4, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0xbc, 0, 0},
|
|
- {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x72, 0, 0}, {0x3a0e, 0x01, 0, 0},
|
|
- {0x3a0d, 0x02, 0, 0}, {0x3a14, 0x02, 0, 0}, {0x3a15, 0xe4, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x02, 0, 0}, {0x4713, 0x02, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0},
|
|
- {0x3824, 0x04, 0, 0}, {0x5001, 0x83, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_30fps_1080P_1920_1080[] = {
|
|
+static const struct reg_value ov5640_setting_1080P_1920_1080[] = {
|
|
{0x3008, 0x42, 0, 0},
|
|
- {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x11, 0, 0},
|
|
{0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -628,10 +502,10 @@ static const struct reg_value ov5640_setting_30fps_1080P_1920_1080[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, {0x3035, 0x11, 0, 0},
|
|
- {0x3036, 0x54, 0, 0}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0x83, 0, 0},
|
|
+ {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0},
|
|
{0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0},
|
|
@@ -640,46 +514,12 @@ static const struct reg_value ov5640_setting_30fps_1080P_1920_1080[] = {
|
|
{0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0},
|
|
{0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0},
|
|
{0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0},
|
|
- {0x3a15, 0x60, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
- {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0},
|
|
+ {0x3a15, 0x60, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
{0x4005, 0x1a, 0, 0}, {0x3008, 0x02, 0, 0},
|
|
- {0x3503, 0, 0, 0},
|
|
-};
|
|
-
|
|
-static const struct reg_value ov5640_setting_15fps_1080P_1920_1080[] = {
|
|
- {0x3008, 0x42, 0, 0},
|
|
- {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3814, 0x11, 0, 0},
|
|
- {0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
- {0x3802, 0x00, 0, 0}, {0x3803, 0x00, 0, 0}, {0x3804, 0x0a, 0, 0},
|
|
- {0x3805, 0x3f, 0, 0}, {0x3806, 0x07, 0, 0}, {0x3807, 0x9f, 0, 0},
|
|
- {0x3810, 0x00, 0, 0},
|
|
- {0x3811, 0x10, 0, 0}, {0x3812, 0x00, 0, 0}, {0x3813, 0x04, 0, 0},
|
|
- {0x3618, 0x04, 0, 0}, {0x3612, 0x29, 0, 0}, {0x3708, 0x21, 0, 0},
|
|
- {0x3709, 0x12, 0, 0}, {0x370c, 0x00, 0, 0}, {0x3a02, 0x03, 0, 0},
|
|
- {0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
- {0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
- {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 0}, {0x3035, 0x21, 0, 0},
|
|
- {0x3036, 0x54, 0, 1}, {0x3c07, 0x07, 0, 0}, {0x3c08, 0x00, 0, 0},
|
|
- {0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
- {0x3800, 0x01, 0, 0}, {0x3801, 0x50, 0, 0}, {0x3802, 0x01, 0, 0},
|
|
- {0x3803, 0xb2, 0, 0}, {0x3804, 0x08, 0, 0}, {0x3805, 0xef, 0, 0},
|
|
- {0x3806, 0x05, 0, 0}, {0x3807, 0xf1, 0, 0},
|
|
- {0x3612, 0x2b, 0, 0}, {0x3708, 0x64, 0, 0},
|
|
- {0x3a02, 0x04, 0, 0}, {0x3a03, 0x60, 0, 0}, {0x3a08, 0x01, 0, 0},
|
|
- {0x3a09, 0x50, 0, 0}, {0x3a0a, 0x01, 0, 0}, {0x3a0b, 0x18, 0, 0},
|
|
- {0x3a0e, 0x03, 0, 0}, {0x3a0d, 0x04, 0, 0}, {0x3a14, 0x04, 0, 0},
|
|
- {0x3a15, 0x60, 0, 0}, {0x4713, 0x02, 0, 0}, {0x4407, 0x04, 0, 0},
|
|
- {0x460b, 0x37, 0, 0}, {0x460c, 0x20, 0, 0}, {0x3824, 0x04, 0, 0},
|
|
- {0x4005, 0x1a, 0, 0}, {0x3008, 0x02, 0, 0}, {0x3503, 0, 0, 0},
|
|
};
|
|
|
|
-static const struct reg_value ov5640_setting_15fps_QSXGA_2592_1944[] = {
|
|
- {0x3035, 0x21, 0, 0}, {0x3036, 0x54, 0, 0}, {0x3c07, 0x08, 0, 0},
|
|
+static const struct reg_value ov5640_setting_QSXGA_2592_1944[] = {
|
|
+ {0x3c07, 0x08, 0, 0},
|
|
{0x3c09, 0x1c, 0, 0}, {0x3c0a, 0x9c, 0, 0}, {0x3c0b, 0x40, 0, 0},
|
|
{0x3814, 0x11, 0, 0},
|
|
{0x3815, 0x11, 0, 0}, {0x3800, 0x00, 0, 0}, {0x3801, 0x00, 0, 0},
|
|
@@ -692,9 +532,9 @@ static const struct reg_value ov5640_setting_15fps_QSXGA_2592_1944[] = {
|
|
{0x3a03, 0xd8, 0, 0}, {0x3a08, 0x01, 0, 0}, {0x3a09, 0x27, 0, 0},
|
|
{0x3a0a, 0x00, 0, 0}, {0x3a0b, 0xf6, 0, 0}, {0x3a0e, 0x03, 0, 0},
|
|
{0x3a0d, 0x04, 0, 0}, {0x3a14, 0x03, 0, 0}, {0x3a15, 0xd8, 0, 0},
|
|
- {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0}, {0x4713, 0x03, 0, 0},
|
|
- {0x4407, 0x04, 0, 0}, {0x460b, 0x35, 0, 0}, {0x460c, 0x22, 0, 0},
|
|
- {0x3824, 0x02, 0, 0}, {0x5001, 0x83, 0, 70},
|
|
+ {0x4001, 0x02, 0, 0}, {0x4004, 0x06, 0, 0},
|
|
+ {0x4407, 0x04, 0, 0},
|
|
+ {0x5001, 0x83, 0, 70},
|
|
};
|
|
|
|
/* power-on sensor init reg table */
|
|
@@ -705,79 +545,43 @@ static const struct ov5640_mode_info ov5640_mode_init_data = {
|
|
};
|
|
|
|
static const struct ov5640_mode_info
|
|
-ov5640_mode_data[OV5640_NUM_FRAMERATES][OV5640_NUM_MODES] = {
|
|
- {
|
|
- {OV5640_MODE_QCIF_176_144, SUBSAMPLING,
|
|
- 176, 1896, 144, 984,
|
|
- ov5640_setting_15fps_QCIF_176_144,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_QCIF_176_144)},
|
|
- {OV5640_MODE_QVGA_320_240, SUBSAMPLING,
|
|
- 320, 1896, 240, 984,
|
|
- ov5640_setting_15fps_QVGA_320_240,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_QVGA_320_240)},
|
|
- {OV5640_MODE_VGA_640_480, SUBSAMPLING,
|
|
- 640, 1896, 480, 1080,
|
|
- ov5640_setting_15fps_VGA_640_480,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_VGA_640_480)},
|
|
- {OV5640_MODE_NTSC_720_480, SUBSAMPLING,
|
|
- 720, 1896, 480, 984,
|
|
- ov5640_setting_15fps_NTSC_720_480,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_NTSC_720_480)},
|
|
- {OV5640_MODE_PAL_720_576, SUBSAMPLING,
|
|
- 720, 1896, 576, 984,
|
|
- ov5640_setting_15fps_PAL_720_576,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_PAL_720_576)},
|
|
- {OV5640_MODE_XGA_1024_768, SUBSAMPLING,
|
|
- 1024, 1896, 768, 1080,
|
|
- ov5640_setting_15fps_XGA_1024_768,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_XGA_1024_768)},
|
|
- {OV5640_MODE_720P_1280_720, SUBSAMPLING,
|
|
- 1280, 1892, 720, 740,
|
|
- ov5640_setting_15fps_720P_1280_720,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_720P_1280_720)},
|
|
- {OV5640_MODE_1080P_1920_1080, SCALING,
|
|
- 1920, 2500, 1080, 1120,
|
|
- ov5640_setting_15fps_1080P_1920_1080,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_1080P_1920_1080)},
|
|
- {OV5640_MODE_QSXGA_2592_1944, SCALING,
|
|
- 2592, 2844, 1944, 1968,
|
|
- ov5640_setting_15fps_QSXGA_2592_1944,
|
|
- ARRAY_SIZE(ov5640_setting_15fps_QSXGA_2592_1944)},
|
|
- }, {
|
|
- {OV5640_MODE_QCIF_176_144, SUBSAMPLING,
|
|
- 176, 1896, 144, 984,
|
|
- ov5640_setting_30fps_QCIF_176_144,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_QCIF_176_144)},
|
|
- {OV5640_MODE_QVGA_320_240, SUBSAMPLING,
|
|
- 320, 1896, 240, 984,
|
|
- ov5640_setting_30fps_QVGA_320_240,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_QVGA_320_240)},
|
|
- {OV5640_MODE_VGA_640_480, SUBSAMPLING,
|
|
- 640, 1896, 480, 1080,
|
|
- ov5640_setting_30fps_VGA_640_480,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_VGA_640_480)},
|
|
- {OV5640_MODE_NTSC_720_480, SUBSAMPLING,
|
|
- 720, 1896, 480, 984,
|
|
- ov5640_setting_30fps_NTSC_720_480,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_NTSC_720_480)},
|
|
- {OV5640_MODE_PAL_720_576, SUBSAMPLING,
|
|
- 720, 1896, 576, 984,
|
|
- ov5640_setting_30fps_PAL_720_576,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_PAL_720_576)},
|
|
- {OV5640_MODE_XGA_1024_768, SUBSAMPLING,
|
|
- 1024, 1896, 768, 1080,
|
|
- ov5640_setting_30fps_XGA_1024_768,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_XGA_1024_768)},
|
|
- {OV5640_MODE_720P_1280_720, SUBSAMPLING,
|
|
- 1280, 1892, 720, 740,
|
|
- ov5640_setting_30fps_720P_1280_720,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_720P_1280_720)},
|
|
- {OV5640_MODE_1080P_1920_1080, SCALING,
|
|
- 1920, 2500, 1080, 1120,
|
|
- ov5640_setting_30fps_1080P_1920_1080,
|
|
- ARRAY_SIZE(ov5640_setting_30fps_1080P_1920_1080)},
|
|
- {OV5640_MODE_QSXGA_2592_1944, -1, 0, 0, 0, 0, NULL, 0},
|
|
- },
|
|
+ov5640_mode_data[OV5640_NUM_MODES] = {
|
|
+ {OV5640_MODE_QCIF_176_144, SUBSAMPLING,
|
|
+ 176, 1896, 144, 984,
|
|
+ ov5640_setting_QCIF_176_144,
|
|
+ ARRAY_SIZE(ov5640_setting_QCIF_176_144)},
|
|
+ {OV5640_MODE_QVGA_320_240, SUBSAMPLING,
|
|
+ 320, 1896, 240, 984,
|
|
+ ov5640_setting_QVGA_320_240,
|
|
+ ARRAY_SIZE(ov5640_setting_QVGA_320_240)},
|
|
+ {OV5640_MODE_VGA_640_480, SUBSAMPLING,
|
|
+ 640, 1896, 480, 1080,
|
|
+ ov5640_setting_VGA_640_480,
|
|
+ ARRAY_SIZE(ov5640_setting_VGA_640_480)},
|
|
+ {OV5640_MODE_NTSC_720_480, SUBSAMPLING,
|
|
+ 720, 1896, 480, 984,
|
|
+ ov5640_setting_NTSC_720_480,
|
|
+ ARRAY_SIZE(ov5640_setting_NTSC_720_480)},
|
|
+ {OV5640_MODE_PAL_720_576, SUBSAMPLING,
|
|
+ 720, 1896, 576, 984,
|
|
+ ov5640_setting_PAL_720_576,
|
|
+ ARRAY_SIZE(ov5640_setting_PAL_720_576)},
|
|
+ {OV5640_MODE_XGA_1024_768, SUBSAMPLING,
|
|
+ 1024, 1896, 768, 1080,
|
|
+ ov5640_setting_XGA_1024_768,
|
|
+ ARRAY_SIZE(ov5640_setting_XGA_1024_768)},
|
|
+ {OV5640_MODE_720P_1280_720, SUBSAMPLING,
|
|
+ 1280, 1892, 720, 740,
|
|
+ ov5640_setting_720P_1280_720,
|
|
+ ARRAY_SIZE(ov5640_setting_720P_1280_720)},
|
|
+ {OV5640_MODE_1080P_1920_1080, SCALING,
|
|
+ 1920, 2500, 1080, 1120,
|
|
+ ov5640_setting_1080P_1920_1080,
|
|
+ ARRAY_SIZE(ov5640_setting_1080P_1920_1080)},
|
|
+ {OV5640_MODE_QSXGA_2592_1944, SCALING,
|
|
+ 2592, 2844, 1944, 1968,
|
|
+ ov5640_setting_QSXGA_2592_1944,
|
|
+ ARRAY_SIZE(ov5640_setting_QSXGA_2592_1944)},
|
|
};
|
|
|
|
static int ov5640_init_slave_id(struct ov5640_dev *sensor)
|
|
@@ -909,27 +713,389 @@ static int ov5640_mod_reg(struct ov5640_dev *sensor, u16 reg,
|
|
return ov5640_write_reg(sensor, reg, val);
|
|
}
|
|
|
|
-/* download ov5640 settings to sensor through i2c */
|
|
-static int ov5640_set_timings(struct ov5640_dev *sensor,
|
|
- const struct ov5640_mode_info *mode)
|
|
+/*
|
|
+ * After trying the various combinations, reading various
|
|
+ * documentations spreaded around the net, and from the various
|
|
+ * feedback, the clock tree is probably as follows:
|
|
+ *
|
|
+ * +--------------+
|
|
+ * | Ext. Clock |
|
|
+ * +-+------------+
|
|
+ * | +----------+
|
|
+ * +->| PLL1 | - reg 0x3036, for the multiplier
|
|
+ * +-+--------+ - reg 0x3037, bits 0-3 for the pre-divider
|
|
+ * | +--------------+
|
|
+ * +->| System Clock | - reg 0x3035, bits 4-7
|
|
+ * +-+------------+
|
|
+ * | +--------------+
|
|
+ * +->| MIPI Divider | - reg 0x3035, bits 0-3
|
|
+ * | +-+------------+
|
|
+ * | +----------------> MIPI SCLK
|
|
+ * | + +-----+
|
|
+ * | +->| / 2 |-------> MIPI BIT CLK
|
|
+ * | +-----+
|
|
+ * | +--------------+
|
|
+ * +->| PLL Root Div | - reg 0x3037, bit 4
|
|
+ * +-+------------+
|
|
+ * | +---------+
|
|
+ * +->| Bit Div | - reg 0x3035, bits 0-3
|
|
+ * +-+-------+
|
|
+ * | +-------------+
|
|
+ * +->| SCLK Div | - reg 0x3108, bits 0-1
|
|
+ * | +-+-----------+
|
|
+ * | +---------------> SCLK
|
|
+ * | +-------------+
|
|
+ * +->| SCLK 2X Div | - reg 0x3108, bits 2-3
|
|
+ * | +-+-----------+
|
|
+ * | +---------------> SCLK 2X
|
|
+ * | +-------------+
|
|
+ * +->| PCLK Div | - reg 0x3108, bits 4-5
|
|
+ * ++------------+
|
|
+ * + +-----------+
|
|
+ * +->| P_DIV | - reg 0x3035, bits 0-3
|
|
+ * +-----+-----+
|
|
+ * +------------> PCLK
|
|
+ *
|
|
+ * This is deviating from the datasheet at least for the register
|
|
+ * 0x3108, since it's said here that the PCLK would be clocked from
|
|
+ * the PLL.
|
|
+ *
|
|
+ * There seems to be also (unverified) constraints:
|
|
+ * - the PLL pre-divider output rate should be in the 4-27MHz range
|
|
+ * - the PLL multiplier output rate should be in the 500-1000MHz range
|
|
+ * - PCLK >= SCLK * 2 in YUV, >= SCLK in Raw or JPEG
|
|
+ *
|
|
+ * In the two latter cases, these constraints are met since our
|
|
+ * factors are hardcoded. If we were to change that, we would need to
|
|
+ * take this into account. The only varying parts are the PLL
|
|
+ * multiplier and the system clock divider, which are shared between
|
|
+ * all these clocks so won't cause any issue.
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * This is supposed to be ranging from 1 to 8, but the value is always
|
|
+ * set to 3 in the vendor kernels.
|
|
+ */
|
|
+#define OV5640_PLL_PREDIV 3
|
|
+
|
|
+#define OV5640_PLL_MULT_MIN 4
|
|
+#define OV5640_PLL_MULT_MAX 252
|
|
+
|
|
+/*
|
|
+ * This is supposed to be ranging from 1 to 16, but the value is
|
|
+ * always set to either 1 or 2 in the vendor kernels.
|
|
+ */
|
|
+#define OV5640_SYSDIV_MIN 1
|
|
+#define OV5640_SYSDIV_MAX 16
|
|
+
|
|
+/*
|
|
+ * Hardcode these values for scaler and non-scaler modes.
|
|
+ * FIXME: to be re-calcualted for 1 data lanes setups
|
|
+ */
|
|
+#define OV5640_MIPI_DIV_PCLK 2
|
|
+#define OV5640_MIPI_DIV_SCLK 1
|
|
+
|
|
+/*
|
|
+ * This is supposed to be ranging from 1 to 2, but the value is always
|
|
+ * set to 2 in the vendor kernels.
|
|
+ */
|
|
+#define OV5640_PLL_ROOT_DIV 2
|
|
+#define OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 BIT(4)
|
|
+
|
|
+/*
|
|
+ * We only supports 8-bit formats at the moment
|
|
+ */
|
|
+#define OV5640_BIT_DIV 2
|
|
+#define OV5640_PLL_CTRL0_MIPI_MODE_8BIT 0x08
|
|
+
|
|
+/*
|
|
+ * This is supposed to be ranging from 1 to 8, but the value is always
|
|
+ * set to 2 in the vendor kernels.
|
|
+ */
|
|
+#define OV5640_SCLK_ROOT_DIV 2
|
|
+
|
|
+/*
|
|
+ * This is hardcoded so that the consistency is maintained between SCLK and
|
|
+ * SCLK 2x.
|
|
+ */
|
|
+#define OV5640_SCLK2X_ROOT_DIV (OV5640_SCLK_ROOT_DIV / 2)
|
|
+
|
|
+/*
|
|
+ * This is supposed to be ranging from 1 to 8, but the value is always
|
|
+ * set to 1 in the vendor kernels.
|
|
+ */
|
|
+#define OV5640_PCLK_ROOT_DIV 1
|
|
+#define OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS 0x00
|
|
+
|
|
+static unsigned long ov5640_compute_sys_clk(struct ov5640_dev *sensor,
|
|
+ u8 pll_prediv, u8 pll_mult,
|
|
+ u8 sysdiv)
|
|
+{
|
|
+ unsigned long sysclk = sensor->xclk_freq / pll_prediv * pll_mult;
|
|
+
|
|
+ /* PLL1 output cannot exceed 1GHz. */
|
|
+ if (sysclk / 1000000 > 1000)
|
|
+ return 0;
|
|
+
|
|
+ return sysclk / sysdiv;
|
|
+}
|
|
+
|
|
+static unsigned long ov5640_calc_sys_clk(struct ov5640_dev *sensor,
|
|
+ unsigned long rate,
|
|
+ u8 *pll_prediv, u8 *pll_mult,
|
|
+ u8 *sysdiv)
|
|
+{
|
|
+ unsigned long best = ~0;
|
|
+ u8 best_sysdiv = 1, best_mult = 1;
|
|
+ u8 _sysdiv, _pll_mult;
|
|
+
|
|
+ for (_sysdiv = OV5640_SYSDIV_MIN;
|
|
+ _sysdiv <= OV5640_SYSDIV_MAX;
|
|
+ _sysdiv++) {
|
|
+ for (_pll_mult = OV5640_PLL_MULT_MIN;
|
|
+ _pll_mult <= OV5640_PLL_MULT_MAX;
|
|
+ _pll_mult++) {
|
|
+ unsigned long _rate;
|
|
+
|
|
+ /*
|
|
+ * The PLL multiplier cannot be odd if above
|
|
+ * 127.
|
|
+ */
|
|
+ if (_pll_mult > 127 && (_pll_mult % 2))
|
|
+ continue;
|
|
+
|
|
+ _rate = ov5640_compute_sys_clk(sensor,
|
|
+ OV5640_PLL_PREDIV,
|
|
+ _pll_mult, _sysdiv);
|
|
+
|
|
+ /*
|
|
+ * We have reached the maximum allowed PLL1 output,
|
|
+ * increase sysdiv.
|
|
+ */
|
|
+ if (!rate)
|
|
+ break;
|
|
+
|
|
+ /*
|
|
+ * Prefer rates above the expected clock rate than
|
|
+ * below, even if that means being less precise.
|
|
+ */
|
|
+ if (_rate < rate)
|
|
+ continue;
|
|
+
|
|
+ if (abs(rate - _rate) < abs(rate - best)) {
|
|
+ best = _rate;
|
|
+ best_sysdiv = _sysdiv;
|
|
+ best_mult = _pll_mult;
|
|
+ }
|
|
+
|
|
+ if (_rate == rate)
|
|
+ goto out;
|
|
+ }
|
|
+ }
|
|
+
|
|
+out:
|
|
+ *sysdiv = best_sysdiv;
|
|
+ *pll_prediv = OV5640_PLL_PREDIV;
|
|
+ *pll_mult = best_mult;
|
|
+
|
|
+ return best;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * ov5640_set_mipi_pclk() - Calculate the clock tree configuration values
|
|
+ * for the MIPI CSI-2 output.
|
|
+ *
|
|
+ * @rate: The requested bandwidth per lane in bytes per second.
|
|
+ * 'Bandwidth Per Lane' is calculated as:
|
|
+ * bpl = HTOT * VTOT * FPS * bpp / num_lanes;
|
|
+ *
|
|
+ * This function use the requested bandwidth to calculate:
|
|
+ * - sample_rate = bpl / (bpp / num_lanes);
|
|
+ * = bpl / (PLL_RDIV * BIT_DIV * PCLK_DIV * MIPI_DIV / num_lanes);
|
|
+ *
|
|
+ * - mipi_sclk = bpl / MIPI_DIV / 2; ( / 2 is for CSI-2 DDR)
|
|
+ *
|
|
+ * with these fixed parameters:
|
|
+ * PLL_RDIV = 2;
|
|
+ * BIT_DIVIDER = 2; (MIPI_BIT_MODE == 8 ? 2 : 2,5);
|
|
+ * PCLK_DIV = 1;
|
|
+ *
|
|
+ * The MIPI clock generation differs for modes that use the scaler and modes
|
|
+ * that do not. In case the scaler is in use, the MIPI_SCLK generates the MIPI
|
|
+ * BIT CLk, and thus:
|
|
+ *
|
|
+ * - mipi_sclk = bpl / MIPI_DIV / 2;
|
|
+ * MIPI_DIV = 1;
|
|
+ *
|
|
+ * For modes that do not go through the scaler, the MIPI BIT CLOCK is generated
|
|
+ * from the pixel clock, and thus:
|
|
+ *
|
|
+ * - sample_rate = bpl / (bpp / num_lanes);
|
|
+ * = bpl / (2 * 2 * 1 * MIPI_DIV / num_lanes);
|
|
+ * = bpl / (4 * MIPI_DIV / num_lanes);
|
|
+ * - MIPI_DIV = bpp / (4 * num_lanes);
|
|
+ *
|
|
+ * FIXME: this have been tested with 16bpp and 2 lanes setup only.
|
|
+ * MIPI_DIV is fixed to value 2, but it -might- be changed according to the
|
|
+ * above formula for setups with 1 lane or image formats with different bpp.
|
|
+ *
|
|
+ * FIXME: this deviates from the sensor manual documentation which is quite
|
|
+ * thin on the MIPI clock tree generation part.
|
|
+ */
|
|
+static int ov5640_set_mipi_pclk(struct ov5640_dev *sensor,
|
|
+ unsigned long rate)
|
|
{
|
|
+ const struct ov5640_mode_info *mode = sensor->current_mode;
|
|
+ u8 prediv, mult, sysdiv;
|
|
+ u8 mipi_div;
|
|
int ret;
|
|
|
|
- ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact);
|
|
- if (ret < 0)
|
|
+ /*
|
|
+ * 1280x720 is reported to use 'SUBSAMPLING' only,
|
|
+ * but according to the sensor manual it goes through the
|
|
+ * scaler before subsampling.
|
|
+ */
|
|
+ if (mode->dn_mode == SCALING ||
|
|
+ (mode->id == OV5640_MODE_720P_1280_720))
|
|
+ mipi_div = OV5640_MIPI_DIV_SCLK;
|
|
+ else
|
|
+ mipi_div = OV5640_MIPI_DIV_PCLK;
|
|
+
|
|
+ ov5640_calc_sys_clk(sensor, rate, &prediv, &mult, &sysdiv);
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0,
|
|
+ 0x0f, OV5640_PLL_CTRL0_MIPI_MODE_8BIT);
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1,
|
|
+ 0xff, sysdiv << 4 | mipi_div);
|
|
+ if (ret)
|
|
return ret;
|
|
|
|
- ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPVO, mode->vact);
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2, 0xff, mult);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3,
|
|
+ 0x1f, OV5640_PLL_CTRL3_PLL_ROOT_DIV_2 | prediv);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER,
|
|
+ 0x30, OV5640_PLL_SYS_ROOT_DIVIDER_BYPASS);
|
|
+}
|
|
+
|
|
+static unsigned long ov5640_calc_pclk(struct ov5640_dev *sensor,
|
|
+ unsigned long rate,
|
|
+ u8 *pll_prediv, u8 *pll_mult, u8 *sysdiv,
|
|
+ u8 *pll_rdiv, u8 *bit_div, u8 *pclk_div)
|
|
+{
|
|
+ unsigned long _rate = rate * OV5640_PLL_ROOT_DIV * OV5640_BIT_DIV *
|
|
+ OV5640_PCLK_ROOT_DIV;
|
|
+
|
|
+ _rate = ov5640_calc_sys_clk(sensor, _rate, pll_prediv, pll_mult,
|
|
+ sysdiv);
|
|
+ *pll_rdiv = OV5640_PLL_ROOT_DIV;
|
|
+ *bit_div = OV5640_BIT_DIV;
|
|
+ *pclk_div = OV5640_PCLK_ROOT_DIV;
|
|
+
|
|
+ return _rate / *pll_rdiv / *bit_div / *pclk_div;
|
|
+}
|
|
+
|
|
+static int ov5640_set_dvp_pclk(struct ov5640_dev *sensor, unsigned long rate)
|
|
+{
|
|
+ const struct ov5640_mode_info *mode = sensor->current_mode;
|
|
+ u8 prediv, mult, sysdiv, pll_rdiv, bit_div, pclk_div;
|
|
+ struct i2c_client *client = sensor->i2c_client;
|
|
+ unsigned int pclk_freq, max_pclk_freq;
|
|
+ u8 dvp_pclk_divider;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * 1280x720 and 1024x768 are reported to use 'SUBSAMPLING' only,
|
|
+ * but they seems to go through the scaler before subsampling.
|
|
+ */
|
|
+ if (mode->dn_mode == SCALING ||
|
|
+ (mode->id == OV5640_MODE_720P_1280_720) ||
|
|
+ (mode->id == OV5640_MODE_XGA_1024_768))
|
|
+ dvp_pclk_divider = 1;
|
|
+ else
|
|
+ dvp_pclk_divider = 2;
|
|
+
|
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_DVP_PCLK_DIVIDER,
|
|
+ dvp_pclk_divider);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+ pclk_freq = rate / dvp_pclk_divider;
|
|
+ max_pclk_freq = sensor->ep.bus.parallel.pclk_max_frequency;
|
|
+
|
|
+ /* clip rate according to optional maximum pixel clock limit */
|
|
+ if (max_pclk_freq && (pclk_freq > max_pclk_freq)) {
|
|
+ rate = max_pclk_freq * dvp_pclk_divider;
|
|
+ dev_dbg(&client->dev, "DVP pixel clock too high (%d > %d Hz), reducing rate...\n",
|
|
+ pclk_freq, max_pclk_freq);
|
|
+ }
|
|
+
|
|
+ ov5640_calc_pclk(sensor, rate, &prediv, &mult, &sysdiv, &pll_rdiv,
|
|
+ &bit_div, &pclk_div);
|
|
+
|
|
+ if (bit_div == 2)
|
|
+ bit_div = 8;
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL0,
|
|
+ 0x0f, bit_div);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ /*
|
|
+ * We need to set sysdiv according to the clock, and to clear
|
|
+ * the MIPI divider.
|
|
+ */
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1,
|
|
+ 0xff, sysdiv << 4);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL2,
|
|
+ 0xff, mult);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL3,
|
|
+ 0x1f, prediv | ((pll_rdiv - 1) << 4));
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ return ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x30,
|
|
+ (ilog2(pclk_div) << 4));
|
|
+}
|
|
+
|
|
+#if 0
|
|
+/* set JPEG framing sizes */
|
|
+static int ov5640_set_jpeg_timings(struct ov5640_dev *sensor,
|
|
+ const struct ov5640_mode_info *mode)
|
|
+{
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * compression mode 3 timing
|
|
+ *
|
|
+ * Data is transmitted with programmable width (VFIFO_HSIZE).
|
|
+ * No padding done. Last line may have less data. Varying
|
|
+ * number of lines per frame, depending on amount of data.
|
|
+ */
|
|
+ ret = ov5640_mod_reg(sensor, OV5640_REG_JPG_MODE_SELECT, 0x7, 0x3);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
- ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_HTS, mode->htot);
|
|
+ ret = ov5640_write_reg16(sensor, OV5640_REG_VFIFO_HSIZE, mode->hact);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
- return ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, mode->vtot);
|
|
+ return ov5640_write_reg16(sensor, OV5640_REG_VFIFO_VSIZE, mode->vact);
|
|
}
|
|
+#endif
|
|
|
|
+/* download ov5640 settings to sensor through i2c */
|
|
static int ov5640_load_regs(struct ov5640_dev *sensor,
|
|
const struct ov5640_mode_info *mode)
|
|
{
|
|
@@ -957,7 +1123,7 @@ static int ov5640_load_regs(struct ov5640_dev *sensor,
|
|
usleep_range(1000 * delay_ms, 1000 * delay_ms + 100);
|
|
}
|
|
|
|
- return ov5640_set_timings(sensor, mode);
|
|
+ return ret;
|
|
}
|
|
|
|
static int ov5640_set_autoexposure(struct ov5640_dev *sensor, bool on)
|
|
@@ -1062,16 +1228,6 @@ static int ov5640_set_stream_dvp(struct ov5640_dev *sensor, bool on)
|
|
|
|
if (on) {
|
|
/*
|
|
- * reset MIPI PCLK/SERCLK divider
|
|
- *
|
|
- * SC PLL CONTRL1 0
|
|
- * - [3..0]: MIPI PCLK/SERCLK divider
|
|
- */
|
|
- ret = ov5640_mod_reg(sensor, OV5640_REG_SC_PLL_CTRL1, 0x0f, 0);
|
|
- if (ret)
|
|
- return ret;
|
|
-
|
|
- /*
|
|
* configure parallel port control lines polarity
|
|
*
|
|
* POLARITY CTRL0
|
|
@@ -1438,14 +1594,44 @@ static int ov5640_set_virtual_channel(struct ov5640_dev *sensor)
|
|
return ov5640_write_reg(sensor, OV5640_REG_DEBUG_MODE, temp);
|
|
}
|
|
|
|
+static int ov5640_set_timings(struct ov5640_dev *sensor,
|
|
+ const struct ov5640_mode_info *mode)
|
|
+{
|
|
+ int ret;
|
|
+#if 0
|
|
+ if (sensor->fmt.code == MEDIA_BUS_FMT_JPEG_1X8) {
|
|
+ ret = ov5640_set_jpeg_timings(sensor, mode);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+ }
|
|
+#endif
|
|
+ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPHO, mode->hact);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_DVPVO, mode->vact);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_HTS, mode->htot);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = ov5640_write_reg16(sensor, OV5640_REG_TIMING_VTS, mode->vtot);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
static const struct ov5640_mode_info *
|
|
ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr,
|
|
int width, int height, bool nearest)
|
|
{
|
|
const struct ov5640_mode_info *mode;
|
|
|
|
- mode = v4l2_find_nearest_size(ov5640_mode_data[fr],
|
|
- ARRAY_SIZE(ov5640_mode_data[fr]),
|
|
+ mode = v4l2_find_nearest_size(ov5640_mode_data,
|
|
+ ARRAY_SIZE(ov5640_mode_data),
|
|
hact, vact,
|
|
width, height);
|
|
|
|
@@ -1453,6 +1639,11 @@ ov5640_find_mode(struct ov5640_dev *sensor, enum ov5640_frame_rate fr,
|
|
(!nearest && (mode->hact != width || mode->vact != height)))
|
|
return NULL;
|
|
|
|
+ /* Only 640x480 can operate at 60fps (for now) */
|
|
+ if (fr == OV5640_60_FPS &&
|
|
+ !(mode->hact == 640 && mode->vact == 480))
|
|
+ return NULL;
|
|
+
|
|
return mode;
|
|
}
|
|
|
|
@@ -1637,8 +1828,12 @@ static int ov5640_set_mode(struct ov5640_dev *sensor)
|
|
enum ov5640_downsize_mode dn_mode, orig_dn_mode;
|
|
bool auto_gain = sensor->ctrls.auto_gain->val == 1;
|
|
bool auto_exp = sensor->ctrls.auto_exp->val == V4L2_EXPOSURE_AUTO;
|
|
+ unsigned long rate;
|
|
int ret;
|
|
|
|
+ if (!orig_mode)
|
|
+ orig_mode = mode;
|
|
+
|
|
dn_mode = mode->dn_mode;
|
|
orig_dn_mode = orig_mode->dn_mode;
|
|
|
|
@@ -1655,6 +1850,23 @@ static int ov5640_set_mode(struct ov5640_dev *sensor)
|
|
goto restore_auto_gain;
|
|
}
|
|
|
|
+ /*
|
|
+ * All the formats we support have 16 bits per pixel, seems to require
|
|
+ * the same rate than YUV, so we can just use 16 bpp all the time.
|
|
+ */
|
|
+ rate = mode->vtot * mode->htot * 16;
|
|
+ rate *= ov5640_framerates[sensor->current_fr];
|
|
+ if (sensor->ep.bus_type == V4L2_MBUS_CSI2) {
|
|
+ rate = rate / sensor->ep.bus.mipi_csi2.num_data_lanes;
|
|
+ ret = ov5640_set_mipi_pclk(sensor, rate);
|
|
+ } else {
|
|
+ rate = rate / sensor->ep.bus.parallel.bus_width;
|
|
+ ret = ov5640_set_dvp_pclk(sensor, rate);
|
|
+ }
|
|
+
|
|
+ if (ret < 0)
|
|
+ return 0;
|
|
+
|
|
if ((dn_mode == SUBSAMPLING && orig_dn_mode == SCALING) ||
|
|
(dn_mode == SCALING && orig_dn_mode == SUBSAMPLING)) {
|
|
/*
|
|
@@ -1678,6 +1890,10 @@ static int ov5640_set_mode(struct ov5640_dev *sensor)
|
|
if (auto_exp)
|
|
ov5640_set_autoexposure(sensor, true);
|
|
|
|
+ ret = ov5640_set_timings(sensor, mode);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
ret = ov5640_set_binning(sensor, dn_mode != SCALING);
|
|
if (ret < 0)
|
|
return ret;
|
|
@@ -1724,8 +1940,8 @@ static int ov5640_restore_mode(struct ov5640_dev *sensor)
|
|
sensor->last_mode = &ov5640_mode_init_data;
|
|
|
|
ret = ov5640_mod_reg(sensor, OV5640_REG_SYS_ROOT_DIVIDER, 0x3f,
|
|
- (ilog2(OV5640_SCLK2X_ROOT_DIVIDER_DEFAULT) << 2) |
|
|
- ilog2(OV5640_SCLK_ROOT_DIVIDER_DEFAULT));
|
|
+ (ilog2(OV5640_SCLK2X_ROOT_DIV) << 2) |
|
|
+ ilog2(OV5640_SCLK_ROOT_DIV));
|
|
if (ret)
|
|
return ret;
|
|
|
|
@@ -1925,34 +2141,39 @@ static int ov5640_try_frame_interval(struct ov5640_dev *sensor,
|
|
u32 width, u32 height)
|
|
{
|
|
const struct ov5640_mode_info *mode;
|
|
- u32 minfps, maxfps, fps;
|
|
- int ret;
|
|
+ enum ov5640_frame_rate rate = OV5640_15_FPS;
|
|
+ int minfps, maxfps, best_fps, fps;
|
|
+ int i;
|
|
|
|
minfps = ov5640_framerates[OV5640_15_FPS];
|
|
- maxfps = ov5640_framerates[OV5640_30_FPS];
|
|
+ maxfps = ov5640_framerates[OV5640_60_FPS];
|
|
|
|
if (fi->numerator == 0) {
|
|
fi->denominator = maxfps;
|
|
fi->numerator = 1;
|
|
- return OV5640_30_FPS;
|
|
+ rate = OV5640_60_FPS;
|
|
+ goto find_mode;
|
|
}
|
|
|
|
- fps = DIV_ROUND_CLOSEST(fi->denominator, fi->numerator);
|
|
+ fps = clamp_val(DIV_ROUND_CLOSEST(fi->denominator, fi->numerator),
|
|
+ minfps, maxfps);
|
|
|
|
- fi->numerator = 1;
|
|
- if (fps > maxfps)
|
|
- fi->denominator = maxfps;
|
|
- else if (fps < minfps)
|
|
- fi->denominator = minfps;
|
|
- else if (2 * fps >= 2 * minfps + (maxfps - minfps))
|
|
- fi->denominator = maxfps;
|
|
- else
|
|
- fi->denominator = minfps;
|
|
+ best_fps = minfps;
|
|
+ for (i = 0; i < ARRAY_SIZE(ov5640_framerates); i++) {
|
|
+ int curr_fps = ov5640_framerates[i];
|
|
+
|
|
+ if (abs(curr_fps - fps) < abs(best_fps - fps)) {
|
|
+ best_fps = curr_fps;
|
|
+ rate = i;
|
|
+ }
|
|
+ }
|
|
|
|
- ret = (fi->denominator == minfps) ? OV5640_15_FPS : OV5640_30_FPS;
|
|
+ fi->numerator = 1;
|
|
+ fi->denominator = best_fps;
|
|
|
|
- mode = ov5640_find_mode(sensor, ret, width, height, false);
|
|
- return mode ? ret : -EINVAL;
|
|
+find_mode:
|
|
+ mode = ov5640_find_mode(sensor, rate, width, height, false);
|
|
+ return mode ? rate : -EINVAL;
|
|
}
|
|
|
|
static int ov5640_get_fmt(struct v4l2_subdev *sd,
|
|
@@ -2013,6 +2234,10 @@ static int ov5640_try_fmt_internal(struct v4l2_subdev *sd,
|
|
return 0;
|
|
}
|
|
|
|
+static const s64 link_freq_menu_items[] = {
|
|
+ 384000000,
|
|
+};
|
|
+
|
|
static int ov5640_set_fmt(struct v4l2_subdev *sd,
|
|
struct v4l2_subdev_pad_config *cfg,
|
|
struct v4l2_subdev_format *format)
|
|
@@ -2061,46 +2286,67 @@ static int ov5640_set_framefmt(struct ov5640_dev *sensor,
|
|
struct v4l2_mbus_framefmt *format)
|
|
{
|
|
int ret = 0;
|
|
- bool is_rgb = false;
|
|
bool is_jpeg = false;
|
|
- u8 val;
|
|
+ u8 fmt, mux;
|
|
|
|
switch (format->code) {
|
|
case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
/* YUV422, UYVY */
|
|
- val = 0x3f;
|
|
+ fmt = 0x3f;
|
|
+ mux = OV5640_FMT_MUX_YUV422;
|
|
break;
|
|
case MEDIA_BUS_FMT_YUYV8_2X8:
|
|
/* YUV422, YUYV */
|
|
- val = 0x30;
|
|
+ fmt = 0x30;
|
|
+ mux = OV5640_FMT_MUX_YUV422;
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB565_2X8_LE:
|
|
/* RGB565 {g[2:0],b[4:0]},{r[4:0],g[5:3]} */
|
|
- val = 0x6F;
|
|
- is_rgb = true;
|
|
+ fmt = 0x6F;
|
|
+ mux = OV5640_FMT_MUX_RGB;
|
|
break;
|
|
case MEDIA_BUS_FMT_RGB565_2X8_BE:
|
|
/* RGB565 {r[4:0],g[5:3]},{g[2:0],b[4:0]} */
|
|
- val = 0x61;
|
|
- is_rgb = true;
|
|
+ fmt = 0x61;
|
|
+ mux = OV5640_FMT_MUX_RGB;
|
|
break;
|
|
case MEDIA_BUS_FMT_JPEG_1X8:
|
|
/* YUV422, YUYV */
|
|
- val = 0x30;
|
|
+ fmt = 0x30;
|
|
+ mux = OV5640_FMT_MUX_YUV422;
|
|
is_jpeg = true;
|
|
break;
|
|
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
|
|
+ /* Raw, BGBG... / GRGR... */
|
|
+ fmt = 0x00;
|
|
+ mux = OV5640_FMT_MUX_RAW_DPC;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
|
|
+ /* Raw bayer, GBGB... / RGRG... */
|
|
+ fmt = 0x01;
|
|
+ mux = OV5640_FMT_MUX_RAW_DPC;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
|
|
+ /* Raw bayer, GRGR... / BGBG... */
|
|
+ fmt = 0x02;
|
|
+ mux = OV5640_FMT_MUX_RAW_DPC;
|
|
+ break;
|
|
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
|
|
+ /* Raw bayer, RGRG... / GBGB... */
|
|
+ fmt = 0x03;
|
|
+ mux = OV5640_FMT_MUX_RAW_DPC;
|
|
+ break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* FORMAT CONTROL00: YUV and RGB formatting */
|
|
- ret = ov5640_write_reg(sensor, OV5640_REG_FORMAT_CONTROL00, val);
|
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_FORMAT_CONTROL00, fmt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* FORMAT MUX CONTROL: ISP YUV or RGB */
|
|
- ret = ov5640_write_reg(sensor, OV5640_REG_ISP_FORMAT_MUX_CTRL,
|
|
- is_rgb ? 0x01 : 0x00);
|
|
+ ret = ov5640_write_reg(sensor, OV5640_REG_ISP_FORMAT_MUX_CTRL, mux);
|
|
if (ret)
|
|
return ret;
|
|
|
|
@@ -2268,10 +2514,41 @@ static int ov5640_set_ctrl_gain(struct ov5640_dev *sensor, bool auto_gain)
|
|
return ret;
|
|
}
|
|
|
|
+static const char * const test_pattern_menu[] = {
|
|
+ "Disabled",
|
|
+ "Color bars",
|
|
+ "Color bars w/ rolling bar",
|
|
+ "Color squares",
|
|
+ "Color squares w/ rolling bar",
|
|
+};
|
|
+
|
|
+#define OV5640_TEST_ENABLE BIT(7)
|
|
+#define OV5640_TEST_ROLLING BIT(6) /* rolling horizontal bar */
|
|
+#define OV5640_TEST_TRANSPARENT BIT(5)
|
|
+#define OV5640_TEST_SQUARE_BW BIT(4) /* black & white squares */
|
|
+#define OV5640_TEST_BAR_STANDARD (0 << 2)
|
|
+#define OV5640_TEST_BAR_VERT_CHANGE_1 (1 << 2)
|
|
+#define OV5640_TEST_BAR_HOR_CHANGE (2 << 2)
|
|
+#define OV5640_TEST_BAR_VERT_CHANGE_2 (3 << 2)
|
|
+#define OV5640_TEST_BAR (0 << 0)
|
|
+#define OV5640_TEST_RANDOM (1 << 0)
|
|
+#define OV5640_TEST_SQUARE (2 << 0)
|
|
+#define OV5640_TEST_BLACK (3 << 0)
|
|
+
|
|
+static const u8 test_pattern_val[] = {
|
|
+ 0,
|
|
+ OV5640_TEST_ENABLE | OV5640_TEST_BAR_VERT_CHANGE_1 |
|
|
+ OV5640_TEST_BAR,
|
|
+ OV5640_TEST_ENABLE | OV5640_TEST_ROLLING |
|
|
+ OV5640_TEST_BAR_VERT_CHANGE_1 | OV5640_TEST_BAR,
|
|
+ OV5640_TEST_ENABLE | OV5640_TEST_SQUARE,
|
|
+ OV5640_TEST_ENABLE | OV5640_TEST_ROLLING | OV5640_TEST_SQUARE,
|
|
+};
|
|
+
|
|
static int ov5640_set_ctrl_test_pattern(struct ov5640_dev *sensor, int value)
|
|
{
|
|
- return ov5640_mod_reg(sensor, OV5640_REG_PRE_ISP_TEST_SET1,
|
|
- 0xa4, value ? 0xa4 : 0);
|
|
+ return ov5640_write_reg(sensor, OV5640_REG_PRE_ISP_TEST_SET1,
|
|
+ test_pattern_val[value]);
|
|
}
|
|
|
|
static int ov5640_set_ctrl_light_freq(struct ov5640_dev *sensor, int value)
|
|
@@ -2399,6 +2676,8 @@ static int ov5640_s_ctrl(struct v4l2_ctrl *ctrl)
|
|
case V4L2_CID_VFLIP:
|
|
ret = ov5640_set_ctrl_vflip(sensor, ctrl->val);
|
|
break;
|
|
+ case V4L2_CID_LINK_FREQ:
|
|
+ return 0;
|
|
default:
|
|
ret = -EINVAL;
|
|
break;
|
|
@@ -2412,11 +2691,6 @@ static const struct v4l2_ctrl_ops ov5640_ctrl_ops = {
|
|
.s_ctrl = ov5640_s_ctrl,
|
|
};
|
|
|
|
-static const char * const test_pattern_menu[] = {
|
|
- "Disabled",
|
|
- "Color bars",
|
|
-};
|
|
-
|
|
static int ov5640_init_controls(struct ov5640_dev *sensor)
|
|
{
|
|
const struct v4l2_ctrl_ops *ops = &ov5640_ctrl_ops;
|
|
@@ -2471,6 +2745,9 @@ static int ov5640_init_controls(struct ov5640_dev *sensor)
|
|
V4L2_CID_POWER_LINE_FREQUENCY_AUTO, 0,
|
|
V4L2_CID_POWER_LINE_FREQUENCY_50HZ);
|
|
|
|
+ ctrls->link_freq = v4l2_ctrl_new_int_menu(hdl, ops, V4L2_CID_LINK_FREQ,
|
|
+ 0, 0, link_freq_menu_items);
|
|
+
|
|
if (hdl->error) {
|
|
ret = hdl->error;
|
|
goto free_ctrls;
|
|
@@ -2501,10 +2778,10 @@ static int ov5640_enum_frame_size(struct v4l2_subdev *sd,
|
|
return -EINVAL;
|
|
|
|
fse->min_width =
|
|
- ov5640_mode_data[0][fse->index].hact;
|
|
+ ov5640_mode_data[fse->index].hact;
|
|
fse->max_width = fse->min_width;
|
|
fse->min_height =
|
|
- ov5640_mode_data[0][fse->index].vact;
|
|
+ ov5640_mode_data[fse->index].vact;
|
|
fse->max_height = fse->min_height;
|
|
|
|
return 0;
|
|
@@ -2569,8 +2846,11 @@ static int ov5640_s_frame_interval(struct v4l2_subdev *sd,
|
|
|
|
frame_rate = ov5640_try_frame_interval(sensor, &fi->interval,
|
|
mode->hact, mode->vact);
|
|
- if (frame_rate < 0)
|
|
- frame_rate = OV5640_15_FPS;
|
|
+ if (frame_rate < 0) {
|
|
+ /* Always return a valid frame interval value */
|
|
+ fi->interval = sensor->frame_interval;
|
|
+ goto out;
|
|
+ }
|
|
|
|
mode = ov5640_find_mode(sensor, frame_rate, mode->hact,
|
|
mode->vact, true);
|
|
@@ -2735,7 +3015,7 @@ static int ov5640_probe(struct i2c_client *client,
|
|
sensor->frame_interval.denominator = ov5640_framerates[OV5640_30_FPS];
|
|
sensor->current_fr = OV5640_30_FPS;
|
|
sensor->current_mode =
|
|
- &ov5640_mode_data[OV5640_30_FPS][OV5640_MODE_VGA_640_480];
|
|
+ &ov5640_mode_data[OV5640_MODE_VGA_640_480];
|
|
sensor->last_mode = sensor->current_mode;
|
|
|
|
sensor->ae_target = 52;
|
|
diff --git a/drivers/media/i2c/st-mipid02.c b/drivers/media/i2c/st-mipid02.c
|
|
new file mode 100644
|
|
index 0000000..7751960
|
|
--- /dev/null
|
|
+++ b/drivers/media/i2c/st-mipid02.c
|
|
@@ -0,0 +1,1076 @@
|
|
+// SPDX-License-Identifier: GPL-2.0
|
|
+/*
|
|
+ * Driver for ST MIPID02 CSI-2 to PARALLEL bridge
|
|
+ *
|
|
+ * Copyright (C) STMicroelectronics SA 2019
|
|
+ * Authors: Mickael Guene <mickael.guene@st.com>
|
|
+ * for STMicroelectronics.
|
|
+ *
|
|
+ *
|
|
+ */
|
|
+
|
|
+#include <linux/clk.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/gpio/consumer.h>
|
|
+#include <linux/i2c.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/of_graph.h>
|
|
+#include <linux/regulator/consumer.h>
|
|
+#include <media/v4l2-async.h>
|
|
+#include <media/v4l2-ctrls.h>
|
|
+#include <media/v4l2-device.h>
|
|
+#include <media/v4l2-fwnode.h>
|
|
+#include <media/v4l2-subdev.h>
|
|
+
|
|
+#define V4L2_MBUS_CSI2_DPHY V4L2_MBUS_CSI2
|
|
+
|
|
+#define MIPID02_CLK_LANE_WR_REG1 0x01
|
|
+#define MIPID02_CLK_LANE_REG1 0x02
|
|
+#define MIPID02_CLK_LANE_REG3 0x04
|
|
+#define MIPID02_DATA_LANE0_REG1 0x05
|
|
+#define MIPID02_DATA_LANE0_REG2 0x06
|
|
+#define MIPID02_DATA_LANE1_REG1 0x09
|
|
+#define MIPID02_DATA_LANE1_REG2 0x0a
|
|
+#define MIPID02_MODE_REG1 0x14
|
|
+#define MIPID02_MODE_REG2 0x15
|
|
+#define MIPID02_DATA_ID_RREG 0x17
|
|
+#define MIPID02_DATA_SELECTION_CTRL 0x19
|
|
+#define MIPID02_PIX_WIDTH_CTRL 0x1e
|
|
+#define MIPID02_PIX_WIDTH_CTRL_EMB 0x1f
|
|
+
|
|
+/* Bits definition for MIPID02_CLK_LANE_REG1 */
|
|
+#define CLK_ENABLE BIT(0)
|
|
+/* Bits definition for MIPID02_CLK_LANE_REG3 */
|
|
+#define CLK_MIPI_CSI BIT(1)
|
|
+/* Bits definition for MIPID02_DATA_LANE0_REG1 */
|
|
+#define DATA_ENABLE BIT(0)
|
|
+/* Bits definition for MIPID02_DATA_LANEx_REG2 */
|
|
+#define DATA_MIPI_CSI BIT(0)
|
|
+/* Bits definition for MIPID02_MODE_REG1 */
|
|
+#define MODE_DATA_SWAP BIT(2)
|
|
+#define MODE_NO_BYPASS BIT(6)
|
|
+/* Bits definition for MIPID02_MODE_REG2 */
|
|
+#define MODE_HSYNC_ACTIVE_HIGH BIT(1)
|
|
+#define MODE_VSYNC_ACTIVE_HIGH BIT(2)
|
|
+/* Bits definition for MIPID02_DATA_SELECTION_CTRL */
|
|
+#define SELECTION_MANUAL_DATA BIT(2)
|
|
+#define SELECTION_MANUAL_WIDTH BIT(3)
|
|
+
|
|
+static const u32 mipid02_supported_fmt_codes[] = {
|
|
+ MEDIA_BUS_FMT_SBGGR8_1X8, MEDIA_BUS_FMT_SGBRG8_1X8,
|
|
+ MEDIA_BUS_FMT_SGRBG8_1X8, MEDIA_BUS_FMT_SRGGB8_1X8,
|
|
+ MEDIA_BUS_FMT_SBGGR10_1X10, MEDIA_BUS_FMT_SGBRG10_1X10,
|
|
+ MEDIA_BUS_FMT_SGRBG10_1X10, MEDIA_BUS_FMT_SRGGB10_1X10,
|
|
+ MEDIA_BUS_FMT_SBGGR12_1X12, MEDIA_BUS_FMT_SGBRG12_1X12,
|
|
+ MEDIA_BUS_FMT_SGRBG12_1X12, MEDIA_BUS_FMT_SRGGB12_1X12,
|
|
+ MEDIA_BUS_FMT_UYVY8_1X16, MEDIA_BUS_FMT_BGR888_1X24,
|
|
+ MEDIA_BUS_FMT_RGB565_2X8_LE, MEDIA_BUS_FMT_RGB565_2X8_BE,
|
|
+ MEDIA_BUS_FMT_YUYV8_2X8, MEDIA_BUS_FMT_UYVY8_2X8,
|
|
+ MEDIA_BUS_FMT_JPEG_1X8
|
|
+};
|
|
+
|
|
+/* regulator supplies */
|
|
+static const char * const mipid02_supply_name[] = {
|
|
+ "VDDE", /* 1.8V digital I/O supply */
|
|
+ "VDDIN", /* 1V8 voltage regulator supply */
|
|
+};
|
|
+
|
|
+#define MIPID02_NUM_SUPPLIES ARRAY_SIZE(mipid02_supply_name)
|
|
+
|
|
+#define MIPID02_SINK_0 0
|
|
+#define MIPID02_SINK_1 1
|
|
+#define MIPID02_SOURCE 2
|
|
+#define MIPID02_PAD_NB 3
|
|
+
|
|
+struct mipid02_dev {
|
|
+ struct i2c_client *i2c_client;
|
|
+ struct regulator_bulk_data supplies[MIPID02_NUM_SUPPLIES];
|
|
+ struct v4l2_subdev sd;
|
|
+ struct media_pad pad[MIPID02_PAD_NB];
|
|
+ struct clk *xclk;
|
|
+ struct gpio_desc *reset_gpio;
|
|
+ /* endpoints info */
|
|
+ struct v4l2_fwnode_endpoint rx;
|
|
+ u64 link_frequency;
|
|
+ struct v4l2_fwnode_endpoint tx;
|
|
+ /* remote source */
|
|
+ struct v4l2_async_subdev asd;
|
|
+ struct v4l2_async_notifier notifier;
|
|
+ struct v4l2_subdev *s_subdev;
|
|
+ /* registers */
|
|
+ struct {
|
|
+ u8 clk_lane_reg1;
|
|
+ u8 data_lane0_reg1;
|
|
+ u8 data_lane1_reg1;
|
|
+ u8 mode_reg1;
|
|
+ u8 mode_reg2;
|
|
+ u8 data_selection_ctrl;
|
|
+ u8 data_id_rreg;
|
|
+ u8 pix_width_ctrl;
|
|
+ u8 pix_width_ctrl_emb;
|
|
+ } r;
|
|
+ /* lock to protect all members below */
|
|
+ struct mutex lock;
|
|
+ bool streaming;
|
|
+ struct v4l2_mbus_framefmt fmt;
|
|
+};
|
|
+
|
|
+static int bpp_from_code(__u32 code)
|
|
+{
|
|
+ switch (code) {
|
|
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
|
|
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
|
|
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
|
|
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
|
|
+ return 8;
|
|
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
|
|
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
|
|
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
|
|
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
|
|
+ return 10;
|
|
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
|
|
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
|
|
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
|
|
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
|
|
+ return 12;
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ case MEDIA_BUS_FMT_YUYV8_2X8:
|
|
+ case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
+ case MEDIA_BUS_FMT_RGB565_2X8_LE:
|
|
+ case MEDIA_BUS_FMT_RGB565_2X8_BE:
|
|
+ return 16;
|
|
+ case MEDIA_BUS_FMT_BGR888_1X24:
|
|
+ return 24;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static u8 data_type_from_code(__u32 code)
|
|
+{
|
|
+ switch (code) {
|
|
+ case MEDIA_BUS_FMT_SBGGR8_1X8:
|
|
+ case MEDIA_BUS_FMT_SGBRG8_1X8:
|
|
+ case MEDIA_BUS_FMT_SGRBG8_1X8:
|
|
+ case MEDIA_BUS_FMT_SRGGB8_1X8:
|
|
+ return 0x2a;
|
|
+ case MEDIA_BUS_FMT_SBGGR10_1X10:
|
|
+ case MEDIA_BUS_FMT_SGBRG10_1X10:
|
|
+ case MEDIA_BUS_FMT_SGRBG10_1X10:
|
|
+ case MEDIA_BUS_FMT_SRGGB10_1X10:
|
|
+ return 0x2b;
|
|
+ case MEDIA_BUS_FMT_SBGGR12_1X12:
|
|
+ case MEDIA_BUS_FMT_SGBRG12_1X12:
|
|
+ case MEDIA_BUS_FMT_SGRBG12_1X12:
|
|
+ case MEDIA_BUS_FMT_SRGGB12_1X12:
|
|
+ return 0x2c;
|
|
+ case MEDIA_BUS_FMT_UYVY8_1X16:
|
|
+ case MEDIA_BUS_FMT_YUYV8_2X8:
|
|
+ case MEDIA_BUS_FMT_UYVY8_2X8:
|
|
+ return 0x1e;
|
|
+ case MEDIA_BUS_FMT_BGR888_1X24:
|
|
+ return 0x24;
|
|
+ case MEDIA_BUS_FMT_RGB565_2X8_LE:
|
|
+ case MEDIA_BUS_FMT_RGB565_2X8_BE:
|
|
+ return 0x22;
|
|
+ default:
|
|
+ return 0;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void init_format(struct v4l2_mbus_framefmt *fmt)
|
|
+{
|
|
+ fmt->code = MEDIA_BUS_FMT_SBGGR8_1X8;
|
|
+ fmt->field = V4L2_FIELD_NONE;
|
|
+ fmt->colorspace = V4L2_COLORSPACE_SRGB;
|
|
+ fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(V4L2_COLORSPACE_SRGB);
|
|
+ fmt->quantization = V4L2_QUANTIZATION_FULL_RANGE;
|
|
+ fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(V4L2_COLORSPACE_SRGB);
|
|
+ fmt->width = 640;
|
|
+ fmt->height = 480;
|
|
+}
|
|
+
|
|
+static __u32 get_fmt_code(__u32 code)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < ARRAY_SIZE(mipid02_supported_fmt_codes); i++) {
|
|
+ if (code == mipid02_supported_fmt_codes[i])
|
|
+ return code;
|
|
+ }
|
|
+
|
|
+ return mipid02_supported_fmt_codes[0];
|
|
+}
|
|
+
|
|
+static __u32 serial_to_parallel_code(__u32 serial)
|
|
+{
|
|
+ if (serial == MEDIA_BUS_FMT_UYVY8_1X16)
|
|
+ return MEDIA_BUS_FMT_UYVY8_2X8;
|
|
+ if (serial == MEDIA_BUS_FMT_BGR888_1X24)
|
|
+ return MEDIA_BUS_FMT_BGR888_3X8;
|
|
+
|
|
+ return serial;
|
|
+}
|
|
+
|
|
+static inline struct mipid02_dev *to_mipid02_dev(struct v4l2_subdev *sd)
|
|
+{
|
|
+ return container_of(sd, struct mipid02_dev, sd);
|
|
+}
|
|
+
|
|
+static int mipid02_read_reg(struct mipid02_dev *bridge, u16 reg, u8 *val)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct i2c_msg msg[2];
|
|
+ u8 buf[2];
|
|
+ int ret;
|
|
+
|
|
+ buf[0] = reg >> 8;
|
|
+ buf[1] = reg & 0xff;
|
|
+
|
|
+ msg[0].addr = client->addr;
|
|
+ msg[0].flags = client->flags;
|
|
+ msg[0].buf = buf;
|
|
+ msg[0].len = sizeof(buf);
|
|
+
|
|
+ msg[1].addr = client->addr;
|
|
+ msg[1].flags = client->flags | I2C_M_RD;
|
|
+ msg[1].buf = val;
|
|
+ msg[1].len = 1;
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, msg, 2);
|
|
+ if (ret < 0) {
|
|
+ dev_dbg(&client->dev, "%s: %x i2c_transfer, reg: %x => %d\n",
|
|
+ __func__, client->addr, reg, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_write_reg(struct mipid02_dev *bridge, u16 reg, u8 val)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct i2c_msg msg;
|
|
+ u8 buf[3];
|
|
+ int ret;
|
|
+
|
|
+ buf[0] = reg >> 8;
|
|
+ buf[1] = reg & 0xff;
|
|
+ buf[2] = val;
|
|
+
|
|
+ msg.addr = client->addr;
|
|
+ msg.flags = client->flags;
|
|
+ msg.buf = buf;
|
|
+ msg.len = sizeof(buf);
|
|
+
|
|
+ ret = i2c_transfer(client->adapter, &msg, 1);
|
|
+ if (ret < 0) {
|
|
+ dev_dbg(&client->dev, "%s: i2c_transfer, reg: %x => %d\n",
|
|
+ __func__, reg, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_get_regulators(struct mipid02_dev *bridge)
|
|
+{
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < MIPID02_NUM_SUPPLIES; i++)
|
|
+ bridge->supplies[i].supply = mipid02_supply_name[i];
|
|
+
|
|
+ return devm_regulator_bulk_get(&bridge->i2c_client->dev,
|
|
+ MIPID02_NUM_SUPPLIES,
|
|
+ bridge->supplies);
|
|
+}
|
|
+
|
|
+static void mipid02_apply_reset(struct mipid02_dev *bridge)
|
|
+{
|
|
+ gpiod_set_value_cansleep(bridge->reset_gpio, 0);
|
|
+ usleep_range(5000, 10000);
|
|
+ gpiod_set_value_cansleep(bridge->reset_gpio, 1);
|
|
+ usleep_range(5000, 10000);
|
|
+ gpiod_set_value_cansleep(bridge->reset_gpio, 0);
|
|
+ usleep_range(5000, 10000);
|
|
+}
|
|
+
|
|
+static int mipid02_set_power_on(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_prepare_enable(bridge->xclk);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "%s: failed to enable clock\n", __func__);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = regulator_bulk_enable(MIPID02_NUM_SUPPLIES,
|
|
+ bridge->supplies);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "%s: failed to enable regulators\n",
|
|
+ __func__);
|
|
+ goto xclk_off;
|
|
+ }
|
|
+
|
|
+ if (bridge->reset_gpio) {
|
|
+ dev_dbg(&client->dev, "apply reset");
|
|
+ mipid02_apply_reset(bridge);
|
|
+ } else {
|
|
+ dev_dbg(&client->dev, "don't apply reset");
|
|
+ usleep_range(5000, 10000);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+xclk_off:
|
|
+ clk_disable_unprepare(bridge->xclk);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void mipid02_set_power_off(struct mipid02_dev *bridge)
|
|
+{
|
|
+ regulator_bulk_disable(MIPID02_NUM_SUPPLIES, bridge->supplies);
|
|
+ clk_disable_unprepare(bridge->xclk);
|
|
+}
|
|
+
|
|
+static int mipid02_detect(struct mipid02_dev *bridge)
|
|
+{
|
|
+ u8 reg;
|
|
+
|
|
+ /*
|
|
+ * There is no version registers. Just try to read register
|
|
+ * MIPID02_CLK_LANE_WR_REG1.
|
|
+ */
|
|
+ return mipid02_read_reg(bridge, MIPID02_CLK_LANE_WR_REG1, ®);
|
|
+}
|
|
+
|
|
+static u32 mipid02_get_link_freq_from_cid_link_freq(struct mipid02_dev *bridge,
|
|
+ struct v4l2_subdev *subdev)
|
|
+{
|
|
+ struct v4l2_querymenu qm = {.id = V4L2_CID_LINK_FREQ, };
|
|
+ struct v4l2_ctrl *ctrl;
|
|
+ int ret;
|
|
+
|
|
+ ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_LINK_FREQ);
|
|
+ if (!ctrl)
|
|
+ return 0;
|
|
+ qm.index = v4l2_ctrl_g_ctrl(ctrl);
|
|
+
|
|
+ ret = v4l2_querymenu(subdev->ctrl_handler, &qm);
|
|
+ if (ret)
|
|
+ return 0;
|
|
+
|
|
+ return qm.value;
|
|
+}
|
|
+
|
|
+static u32 mipid02_get_link_freq_from_cid_pixel_rate(struct mipid02_dev *bridge,
|
|
+ struct v4l2_subdev *subdev)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint *ep = &bridge->rx;
|
|
+ struct v4l2_ctrl *ctrl;
|
|
+ u32 pixel_clock;
|
|
+ u32 bpp = bpp_from_code(bridge->fmt.code);
|
|
+
|
|
+ ctrl = v4l2_ctrl_find(subdev->ctrl_handler, V4L2_CID_PIXEL_RATE);
|
|
+ if (!ctrl)
|
|
+ return 0;
|
|
+ pixel_clock = v4l2_ctrl_g_ctrl_int64(ctrl);
|
|
+
|
|
+ return pixel_clock * bpp / (2 * ep->bus.mipi_csi2.num_data_lanes);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * We need to know link frequency to setup clk_lane_reg1 timings. Link frequency
|
|
+ * will be computed using connected device V4L2_CID_PIXEL_RATE, bit per pixel
|
|
+ * and number of lanes.
|
|
+ */
|
|
+static int mipid02_configure_from_rx_speed(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct v4l2_subdev *subdev = bridge->s_subdev;
|
|
+ u32 link_freq;
|
|
+
|
|
+ link_freq = mipid02_get_link_freq_from_cid_link_freq(bridge, subdev);
|
|
+ if (!link_freq) {
|
|
+ link_freq = mipid02_get_link_freq_from_cid_pixel_rate(bridge,
|
|
+ subdev);
|
|
+ if (!link_freq) {
|
|
+ dev_err(&client->dev, "Failed to get link frequency");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ dev_dbg(&client->dev, "detect link_freq = %d Hz", link_freq);
|
|
+ bridge->r.clk_lane_reg1 |= (2000000000 / link_freq) << 2;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_configure_clk_lane(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct v4l2_fwnode_endpoint *ep = &bridge->rx;
|
|
+ bool *polarities = ep->bus.mipi_csi2.lane_polarities;
|
|
+
|
|
+ /* midid02 doesn't support clock lane remapping */
|
|
+ if (ep->bus.mipi_csi2.clock_lane != 0) {
|
|
+ dev_err(&client->dev, "clk lane must be map to lane 0\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+ bridge->r.clk_lane_reg1 |= (polarities[0] << 1) | CLK_ENABLE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_configure_data0_lane(struct mipid02_dev *bridge, int nb,
|
|
+ bool are_lanes_swap, bool *polarities)
|
|
+{
|
|
+ bool are_pin_swap = are_lanes_swap ? polarities[2] : polarities[1];
|
|
+
|
|
+ if (nb == 1 && are_lanes_swap)
|
|
+ return 0;
|
|
+
|
|
+ /*
|
|
+ * data lane 0 as pin swap polarity reversed compared to clock and
|
|
+ * data lane 1
|
|
+ */
|
|
+ if (!are_pin_swap)
|
|
+ bridge->r.data_lane0_reg1 = 1 << 1;
|
|
+ bridge->r.data_lane0_reg1 |= DATA_ENABLE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_configure_data1_lane(struct mipid02_dev *bridge, int nb,
|
|
+ bool are_lanes_swap, bool *polarities)
|
|
+{
|
|
+ bool are_pin_swap = are_lanes_swap ? polarities[1] : polarities[2];
|
|
+
|
|
+ if (nb == 1 && !are_lanes_swap)
|
|
+ return 0;
|
|
+
|
|
+ if (are_pin_swap)
|
|
+ bridge->r.data_lane1_reg1 = 1 << 1;
|
|
+ bridge->r.data_lane1_reg1 |= DATA_ENABLE;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_configure_from_rx(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint *ep = &bridge->rx;
|
|
+ bool are_lanes_swap = ep->bus.mipi_csi2.data_lanes[0] == 2;
|
|
+ bool *polarities = ep->bus.mipi_csi2.lane_polarities;
|
|
+ int nb = ep->bus.mipi_csi2.num_data_lanes;
|
|
+ int ret;
|
|
+
|
|
+ ret = mipid02_configure_clk_lane(bridge);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = mipid02_configure_data0_lane(bridge, nb, are_lanes_swap,
|
|
+ polarities);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ ret = mipid02_configure_data1_lane(bridge, nb, are_lanes_swap,
|
|
+ polarities);
|
|
+ if (ret)
|
|
+ return ret;
|
|
+
|
|
+ bridge->r.mode_reg1 |= are_lanes_swap ? MODE_DATA_SWAP : 0;
|
|
+ bridge->r.mode_reg1 |= (nb - 1) << 1;
|
|
+
|
|
+ return mipid02_configure_from_rx_speed(bridge);
|
|
+}
|
|
+
|
|
+static int mipid02_configure_from_tx(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint *ep = &bridge->tx;
|
|
+
|
|
+ bridge->r.data_selection_ctrl = SELECTION_MANUAL_WIDTH;
|
|
+ bridge->r.pix_width_ctrl = ep->bus.parallel.bus_width;
|
|
+ bridge->r.pix_width_ctrl_emb = ep->bus.parallel.bus_width;
|
|
+ if (ep->bus.parallel.flags & V4L2_MBUS_HSYNC_ACTIVE_HIGH)
|
|
+ bridge->r.mode_reg2 |= MODE_HSYNC_ACTIVE_HIGH;
|
|
+ if (ep->bus.parallel.flags & V4L2_MBUS_VSYNC_ACTIVE_HIGH)
|
|
+ bridge->r.mode_reg2 |= MODE_VSYNC_ACTIVE_HIGH;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_configure_from_code(struct mipid02_dev *bridge)
|
|
+{
|
|
+ u8 data_type;
|
|
+
|
|
+ bridge->r.data_id_rreg = 0;
|
|
+
|
|
+ if (bridge->fmt.code != MEDIA_BUS_FMT_JPEG_1X8) {
|
|
+ bridge->r.data_selection_ctrl |= SELECTION_MANUAL_DATA;
|
|
+
|
|
+ data_type = data_type_from_code(bridge->fmt.code);
|
|
+ if (!data_type)
|
|
+ return -EINVAL;
|
|
+ bridge->r.data_id_rreg = data_type;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int mipid02_stream_disable(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int ret;
|
|
+
|
|
+ /* Disable all lanes */
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG1, 0);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG1, 0);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG1, 0);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+error:
|
|
+ if (ret)
|
|
+ dev_err(&client->dev, "failed to stream off %d", ret);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_stream_enable(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int ret = -EINVAL;
|
|
+
|
|
+ if (!bridge->s_subdev)
|
|
+ goto error;
|
|
+
|
|
+ memset(&bridge->r, 0, sizeof(bridge->r));
|
|
+ /* build registers content */
|
|
+ ret = mipid02_configure_from_rx(bridge);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_configure_from_tx(bridge);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_configure_from_code(bridge);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+
|
|
+ /* write mipi registers */
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG1,
|
|
+ bridge->r.clk_lane_reg1);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_CLK_LANE_REG3, CLK_MIPI_CSI);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG1,
|
|
+ bridge->r.data_lane0_reg1);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE0_REG2,
|
|
+ DATA_MIPI_CSI);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG1,
|
|
+ bridge->r.data_lane1_reg1);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_LANE1_REG2,
|
|
+ DATA_MIPI_CSI);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_MODE_REG1,
|
|
+ MODE_NO_BYPASS | bridge->r.mode_reg1);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_MODE_REG2,
|
|
+ bridge->r.mode_reg2);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_ID_RREG,
|
|
+ bridge->r.data_id_rreg);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_DATA_SELECTION_CTRL,
|
|
+ bridge->r.data_selection_ctrl);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_PIX_WIDTH_CTRL,
|
|
+ bridge->r.pix_width_ctrl);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+ ret = mipid02_write_reg(bridge, MIPID02_PIX_WIDTH_CTRL_EMB,
|
|
+ bridge->r.pix_width_ctrl_emb);
|
|
+ if (ret)
|
|
+ goto error;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error:
|
|
+ dev_err(&client->dev, "failed to stream on %d", ret);
|
|
+ mipid02_stream_disable(bridge);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_s_stream(struct v4l2_subdev *sd, int enable)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int ret = 0;
|
|
+
|
|
+ dev_dbg(&client->dev, "%s : requested %d / current = %d", __func__,
|
|
+ enable, bridge->streaming);
|
|
+ mutex_lock(&bridge->lock);
|
|
+
|
|
+ if (bridge->streaming == enable)
|
|
+ goto out;
|
|
+
|
|
+ ret = enable ? mipid02_stream_enable(bridge) :
|
|
+ mipid02_stream_disable(bridge);
|
|
+ if (!ret)
|
|
+ bridge->streaming = enable;
|
|
+
|
|
+out:
|
|
+ dev_dbg(&client->dev, "%s current now = %d / %d", __func__,
|
|
+ bridge->streaming, ret);
|
|
+ mutex_unlock(&bridge->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_enum_mbus_code(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_mbus_code_enum *code)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+ int ret = 0;
|
|
+
|
|
+ switch (code->pad) {
|
|
+ case MIPID02_SINK_0:
|
|
+ if (code->index >= ARRAY_SIZE(mipid02_supported_fmt_codes))
|
|
+ ret = -EINVAL;
|
|
+ else
|
|
+ code->code = mipid02_supported_fmt_codes[code->index];
|
|
+ break;
|
|
+ case MIPID02_SOURCE:
|
|
+ if (code->index == 0)
|
|
+ code->code = serial_to_parallel_code(bridge->fmt.code);
|
|
+ else
|
|
+ ret = -EINVAL;
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ }
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_get_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct v4l2_mbus_framefmt *mbus_fmt = &format->format;
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct v4l2_mbus_framefmt *fmt;
|
|
+
|
|
+ dev_dbg(&client->dev, "%s probe %d", __func__, format->pad);
|
|
+
|
|
+ if (format->pad >= MIPID02_PAD_NB)
|
|
+ return -EINVAL;
|
|
+ /* second CSI-2 pad not yet supported */
|
|
+ if (format->pad == MIPID02_SINK_1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ fmt = v4l2_subdev_get_try_format(&bridge->sd, cfg, format->pad);
|
|
+ else
|
|
+ fmt = &bridge->fmt;
|
|
+
|
|
+ mutex_lock(&bridge->lock);
|
|
+
|
|
+ *mbus_fmt = *fmt;
|
|
+ /* code may need to be converted for source */
|
|
+ if (format->pad == MIPID02_SOURCE)
|
|
+ mbus_fmt->code = serial_to_parallel_code(mbus_fmt->code);
|
|
+
|
|
+ mutex_unlock(&bridge->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mipid02_set_fmt_source(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+
|
|
+ /* source pad mirror active sink pad */
|
|
+ format->format = bridge->fmt;
|
|
+ /* but code may need to be converted */
|
|
+ format->format.code = serial_to_parallel_code(format->format.code);
|
|
+
|
|
+ /* only apply format for V4L2_SUBDEV_FORMAT_TRY case */
|
|
+ if (format->which != V4L2_SUBDEV_FORMAT_TRY)
|
|
+ return;
|
|
+
|
|
+ *v4l2_subdev_get_try_format(sd, cfg, format->pad) = format->format;
|
|
+}
|
|
+
|
|
+static void mipid02_set_fmt_sink(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+ struct v4l2_mbus_framefmt *fmt;
|
|
+
|
|
+ format->format.code = get_fmt_code(format->format.code);
|
|
+
|
|
+ if (format->which == V4L2_SUBDEV_FORMAT_TRY)
|
|
+ fmt = v4l2_subdev_get_try_format(sd, cfg, format->pad);
|
|
+ else
|
|
+ fmt = &bridge->fmt;
|
|
+
|
|
+ *fmt = format->format;
|
|
+}
|
|
+
|
|
+static int mipid02_set_fmt(struct v4l2_subdev *sd,
|
|
+ struct v4l2_subdev_pad_config *cfg,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int ret = 0;
|
|
+
|
|
+ dev_dbg(&client->dev, "%s for %d", __func__, format->pad);
|
|
+
|
|
+ if (format->pad >= MIPID02_PAD_NB)
|
|
+ return -EINVAL;
|
|
+ /* second CSI-2 pad not yet supported */
|
|
+ if (format->pad == MIPID02_SINK_1)
|
|
+ return -EINVAL;
|
|
+
|
|
+ mutex_lock(&bridge->lock);
|
|
+
|
|
+ if (bridge->streaming) {
|
|
+ ret = -EBUSY;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ if (format->pad == MIPID02_SOURCE)
|
|
+ mipid02_set_fmt_source(sd, cfg, format);
|
|
+ else
|
|
+ mipid02_set_fmt_sink(sd, cfg, format);
|
|
+
|
|
+error:
|
|
+ mutex_unlock(&bridge->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static const struct v4l2_subdev_video_ops mipid02_video_ops = {
|
|
+ .s_stream = mipid02_s_stream,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_pad_ops mipid02_pad_ops = {
|
|
+ .enum_mbus_code = mipid02_enum_mbus_code,
|
|
+ .get_fmt = mipid02_get_fmt,
|
|
+ .set_fmt = mipid02_set_fmt,
|
|
+};
|
|
+
|
|
+static const struct v4l2_subdev_ops mipid02_subdev_ops = {
|
|
+ .video = &mipid02_video_ops,
|
|
+ .pad = &mipid02_pad_ops,
|
|
+};
|
|
+
|
|
+static const struct media_entity_operations mipid02_subdev_entity_ops = {
|
|
+ .link_validate = v4l2_subdev_link_validate,
|
|
+};
|
|
+
|
|
+static int mipid02_async_bound(struct v4l2_async_notifier *notifier,
|
|
+ struct v4l2_subdev *s_subdev,
|
|
+ struct v4l2_async_subdev *asd)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(notifier->sd);
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ int source_pad;
|
|
+ int ret;
|
|
+
|
|
+ dev_dbg(&client->dev, "sensor_async_bound call %p", s_subdev);
|
|
+
|
|
+ source_pad = media_entity_get_fwnode_pad(&s_subdev->entity,
|
|
+ s_subdev->fwnode,
|
|
+ MEDIA_PAD_FL_SOURCE);
|
|
+ if (source_pad < 0) {
|
|
+ dev_err(&client->dev, "Couldn't find output pad for subdev %s\n",
|
|
+ s_subdev->name);
|
|
+ return source_pad;
|
|
+ }
|
|
+
|
|
+ ret = media_create_pad_link(&s_subdev->entity, source_pad,
|
|
+ &bridge->sd.entity, 0,
|
|
+ MEDIA_LNK_FL_ENABLED |
|
|
+ MEDIA_LNK_FL_IMMUTABLE);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "Couldn't create media link %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ bridge->s_subdev = s_subdev;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void mipid02_async_unbind(struct v4l2_async_notifier *notifier,
|
|
+ struct v4l2_subdev *s_subdev,
|
|
+ struct v4l2_async_subdev *asd)
|
|
+{
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(notifier->sd);
|
|
+
|
|
+ bridge->s_subdev = NULL;
|
|
+}
|
|
+
|
|
+static const struct v4l2_async_notifier_operations mipid02_notifier_ops = {
|
|
+ .bound = mipid02_async_bound,
|
|
+ .unbind = mipid02_async_unbind,
|
|
+};
|
|
+
|
|
+static int mipid02_parse_rx_ep(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct device_node *ep_node;
|
|
+ int ret;
|
|
+
|
|
+ /* parse rx (endpoint 0) */
|
|
+ ep_node = of_graph_get_endpoint_by_regs(bridge->i2c_client->dev.of_node,
|
|
+ 0, 0);
|
|
+ if (!ep_node) {
|
|
+ dev_err(&client->dev, "unable to find port0 ep");
|
|
+ ret = -EINVAL;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "Could not parse v4l2 endpoint %d\n",
|
|
+ ret);
|
|
+ goto error_of_node_put;
|
|
+ }
|
|
+
|
|
+ /* do some sanity checks */
|
|
+ if (ep.bus.mipi_csi2.num_data_lanes > 2) {
|
|
+ dev_err(&client->dev, "max supported data lanes is 2 / got %d",
|
|
+ ep.bus.mipi_csi2.num_data_lanes);
|
|
+ ret = -EINVAL;
|
|
+ goto error_of_node_put;
|
|
+ }
|
|
+
|
|
+ /* register it for later use */
|
|
+ bridge->rx = ep;
|
|
+
|
|
+ /* register async notifier so we get noticed when sensor is connected */
|
|
+ bridge->asd.match.fwnode =
|
|
+ fwnode_graph_get_remote_port_parent(of_fwnode_handle(ep_node));
|
|
+ bridge->asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
|
|
+ of_node_put(ep_node);
|
|
+ bridge->notifier.subdevs =
|
|
+ devm_kzalloc(&bridge->i2c_client->dev,
|
|
+ sizeof(*bridge->notifier.subdevs),
|
|
+ GFP_KERNEL);
|
|
+ if (!bridge->notifier.subdevs)
|
|
+ return -ENOMEM;
|
|
+ bridge->notifier.subdevs[0] = &bridge->asd;
|
|
+ bridge->notifier.num_subdevs = 1;
|
|
+ bridge->notifier.ops = &mipid02_notifier_ops;
|
|
+
|
|
+ ret = v4l2_async_subdev_notifier_register(&bridge->sd,
|
|
+ &bridge->notifier);
|
|
+ if (ret)
|
|
+ v4l2_async_notifier_cleanup(&bridge->notifier);
|
|
+
|
|
+ return ret;
|
|
+
|
|
+error_of_node_put:
|
|
+ of_node_put(ep_node);
|
|
+error:
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_parse_tx_ep(struct mipid02_dev *bridge)
|
|
+{
|
|
+ struct v4l2_fwnode_endpoint ep = { .bus_type = V4L2_MBUS_PARALLEL };
|
|
+ struct i2c_client *client = bridge->i2c_client;
|
|
+ struct device_node *ep_node;
|
|
+ int ret;
|
|
+
|
|
+ /* parse tx (endpoint 2) */
|
|
+ ep_node = of_graph_get_endpoint_by_regs(bridge->i2c_client->dev.of_node,
|
|
+ 2, 0);
|
|
+ if (!ep_node) {
|
|
+ dev_err(&client->dev, "unable to find port1 ep");
|
|
+ ret = -EINVAL;
|
|
+ goto error;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_fwnode_endpoint_parse(of_fwnode_handle(ep_node), &ep);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "Could not parse v4l2 endpoint\n");
|
|
+ goto error_of_node_put;
|
|
+ }
|
|
+
|
|
+ of_node_put(ep_node);
|
|
+ bridge->tx = ep;
|
|
+
|
|
+ return 0;
|
|
+
|
|
+error_of_node_put:
|
|
+ of_node_put(ep_node);
|
|
+error:
|
|
+
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int mipid02_probe(struct i2c_client *client)
|
|
+{
|
|
+ struct device *dev = &client->dev;
|
|
+ struct mipid02_dev *bridge;
|
|
+ u32 clk_freq;
|
|
+ int ret;
|
|
+
|
|
+ bridge = devm_kzalloc(dev, sizeof(*bridge), GFP_KERNEL);
|
|
+ if (!bridge)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ init_format(&bridge->fmt);
|
|
+
|
|
+ bridge->i2c_client = client;
|
|
+ v4l2_i2c_subdev_init(&bridge->sd, client, &mipid02_subdev_ops);
|
|
+
|
|
+ /* got and check clock */
|
|
+ bridge->xclk = devm_clk_get(dev, "xclk");
|
|
+ if (IS_ERR(bridge->xclk)) {
|
|
+ dev_err(dev, "failed to get xclk\n");
|
|
+ return PTR_ERR(bridge->xclk);
|
|
+ }
|
|
+
|
|
+ clk_freq = clk_get_rate(bridge->xclk);
|
|
+ if (clk_freq < 6000000 || clk_freq > 27000000) {
|
|
+ dev_err(dev, "xclk freq must be in 6-27 Mhz range. got %d Hz\n",
|
|
+ clk_freq);
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ bridge->reset_gpio = devm_gpiod_get_optional(dev, "reset",
|
|
+ GPIOD_OUT_HIGH);
|
|
+
|
|
+ ret = mipid02_get_regulators(bridge);
|
|
+ if (ret) {
|
|
+ dev_err(dev, "failed to get regulators %d", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ mutex_init(&bridge->lock);
|
|
+ bridge->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
|
|
+ bridge->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
|
|
+ bridge->sd.entity.ops = &mipid02_subdev_entity_ops;
|
|
+ bridge->pad[0].flags = MEDIA_PAD_FL_SINK;
|
|
+ bridge->pad[1].flags = MEDIA_PAD_FL_SINK;
|
|
+ bridge->pad[2].flags = MEDIA_PAD_FL_SOURCE;
|
|
+ ret = media_entity_pads_init(&bridge->sd.entity, MIPID02_PAD_NB,
|
|
+ bridge->pad);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "pads init failed %d", ret);
|
|
+ goto mutex_cleanup;
|
|
+ }
|
|
+
|
|
+ /* enable clock, power and reset device if available */
|
|
+ ret = mipid02_set_power_on(bridge);
|
|
+ if (ret)
|
|
+ goto entity_cleanup;
|
|
+
|
|
+ ret = mipid02_detect(bridge);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to detect mipid02 %d", ret);
|
|
+ goto power_off;
|
|
+ }
|
|
+
|
|
+ ret = mipid02_parse_tx_ep(bridge);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to parse tx %d", ret);
|
|
+ goto power_off;
|
|
+ }
|
|
+
|
|
+ ret = mipid02_parse_rx_ep(bridge);
|
|
+ if (ret) {
|
|
+ dev_err(&client->dev, "failed to parse rx %d", ret);
|
|
+ goto power_off;
|
|
+ }
|
|
+
|
|
+ ret = v4l2_async_register_subdev(&bridge->sd);
|
|
+ if (ret < 0) {
|
|
+ dev_err(&client->dev, "v4l2_async_register_subdev failed %d",
|
|
+ ret);
|
|
+ goto unregister_notifier;
|
|
+ }
|
|
+
|
|
+ dev_info(&client->dev, "mipid02 device probe successfully");
|
|
+
|
|
+ return 0;
|
|
+
|
|
+unregister_notifier:
|
|
+ v4l2_async_notifier_unregister(&bridge->notifier);
|
|
+ v4l2_async_notifier_cleanup(&bridge->notifier);
|
|
+power_off:
|
|
+ mipid02_set_power_off(bridge);
|
|
+entity_cleanup:
|
|
+ media_entity_cleanup(&bridge->sd.entity);
|
|
+mutex_cleanup:
|
|
+ mutex_destroy(&bridge->lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int mipid02_remove(struct i2c_client *client)
|
|
+{
|
|
+ struct v4l2_subdev *sd = i2c_get_clientdata(client);
|
|
+ struct mipid02_dev *bridge = to_mipid02_dev(sd);
|
|
+
|
|
+ v4l2_async_notifier_unregister(&bridge->notifier);
|
|
+ v4l2_async_notifier_cleanup(&bridge->notifier);
|
|
+ v4l2_async_unregister_subdev(&bridge->sd);
|
|
+ mipid02_set_power_off(bridge);
|
|
+ media_entity_cleanup(&bridge->sd.entity);
|
|
+ mutex_destroy(&bridge->lock);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct of_device_id mipid02_dt_ids[] = {
|
|
+ { .compatible = "st,st-mipid02" },
|
|
+ { /* sentinel */ }
|
|
+};
|
|
+MODULE_DEVICE_TABLE(of, mipid02_dt_ids);
|
|
+
|
|
+static struct i2c_driver mipid02_i2c_driver = {
|
|
+ .driver = {
|
|
+ .name = "st-mipid02",
|
|
+ .of_match_table = mipid02_dt_ids,
|
|
+ },
|
|
+ .probe_new = mipid02_probe,
|
|
+ .remove = mipid02_remove,
|
|
+};
|
|
+
|
|
+module_i2c_driver(mipid02_i2c_driver);
|
|
+
|
|
+MODULE_AUTHOR("Mickael Guene <mickael.guene@st.com>");
|
|
+MODULE_DESCRIPTION("STMicroelectronics MIPID02 CSI-2 bridge driver");
|
|
+MODULE_LICENSE("GPL v2");
|
|
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
|
|
index 54fe90a..6f0721e 100644
|
|
--- a/drivers/media/platform/Kconfig
|
|
+++ b/drivers/media/platform/Kconfig
|
|
@@ -111,7 +111,7 @@ config VIDEO_S3C_CAMIF
|
|
|
|
config VIDEO_STM32_DCMI
|
|
tristate "STM32 Digital Camera Memory Interface (DCMI) support"
|
|
- depends on VIDEO_V4L2 && OF
|
|
+ depends on VIDEO_V4L2 && OF && MEDIA_CONTROLLER
|
|
depends on ARCH_STM32 || COMPILE_TEST
|
|
select VIDEOBUF2_DMA_CONTIG
|
|
select V4L2_FWNODE
|
|
diff --git a/drivers/media/platform/stm32/stm32-cec.c b/drivers/media/platform/stm32/stm32-cec.c
|
|
index 7c496bc..1e657fe 100644
|
|
--- a/drivers/media/platform/stm32/stm32-cec.c
|
|
+++ b/drivers/media/platform/stm32/stm32-cec.c
|
|
@@ -11,7 +11,9 @@
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
+#include <linux/pinctrl/consumer.h>
|
|
#include <linux/platform_device.h>
|
|
+#include <linux/pm_runtime.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include <media/cec.h>
|
|
@@ -56,6 +58,13 @@
|
|
#define ALL_TX_IT (TXEND | TXBR | TXACKE | TXERR | TXUDR | ARBLST)
|
|
#define ALL_RX_IT (RXEND | RXBR | RXACKE | RXOVR)
|
|
|
|
+/*
|
|
+ * 400 ms is the time it takes for one 16 byte message to be
|
|
+ * transferred and 5 is the maximum number of retries. Add
|
|
+ * another 100 ms as a margin.
|
|
+ */
|
|
+#define CEC_XFER_TIMEOUT_MS (5 * 400 + 100)
|
|
+
|
|
struct stm32_cec {
|
|
struct cec_adapter *adap;
|
|
struct device *dev;
|
|
@@ -68,6 +77,9 @@ struct stm32_cec {
|
|
struct cec_msg rx_msg;
|
|
struct cec_msg tx_msg;
|
|
int tx_cnt;
|
|
+ u32 c_reg;
|
|
+ u32 ie_reg;
|
|
+ u32 cfg_reg;
|
|
};
|
|
|
|
static void cec_hw_init(struct stm32_cec *cec)
|
|
@@ -174,6 +186,9 @@ static int stm32_cec_adap_enable(struct cec_adapter *adap, bool enable)
|
|
dev_err(cec->dev, "fail to enable cec clock\n");
|
|
|
|
clk_enable(cec->clk_hdmi_cec);
|
|
+
|
|
+ cec_hw_init(cec);
|
|
+
|
|
regmap_update_bits(cec->regmap, CEC_CR, CECEN, CECEN);
|
|
} else {
|
|
clk_disable(cec->clk_cec);
|
|
@@ -188,7 +203,11 @@ static int stm32_cec_adap_log_addr(struct cec_adapter *adap, u8 logical_addr)
|
|
{
|
|
struct stm32_cec *cec = adap->priv;
|
|
u32 oar = (1 << logical_addr) << 16;
|
|
+ u32 val;
|
|
|
|
+ /* Poll every 100µs the register CEC_CR to wait end of transmission */
|
|
+ regmap_read_poll_timeout(cec->regmap, CEC_CR, val, !(val & TXSOM),
|
|
+ 100, CEC_XFER_TIMEOUT_MS * 1000);
|
|
regmap_update_bits(cec->regmap, CEC_CR, CECEN, 0);
|
|
|
|
if (logical_addr == CEC_LOG_ADDR_INVALID)
|
|
@@ -260,8 +279,8 @@ static int stm32_cec_probe(struct platform_device *pdev)
|
|
if (IS_ERR(mmio))
|
|
return PTR_ERR(mmio);
|
|
|
|
- cec->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "cec", mmio,
|
|
- &stm32_cec_regmap_cfg);
|
|
+ cec->regmap = devm_regmap_init_mmio(&pdev->dev, mmio,
|
|
+ &stm32_cec_regmap_cfg);
|
|
|
|
if (IS_ERR(cec->regmap))
|
|
return PTR_ERR(cec->regmap);
|
|
@@ -315,8 +334,6 @@ static int stm32_cec_probe(struct platform_device *pdev)
|
|
return ret;
|
|
}
|
|
|
|
- cec_hw_init(cec);
|
|
-
|
|
platform_set_drvdata(pdev, cec);
|
|
|
|
return 0;
|
|
@@ -334,6 +351,76 @@ static int stm32_cec_remove(struct platform_device *pdev)
|
|
return 0;
|
|
}
|
|
|
|
+static __maybe_unused int cec_runtime_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_cec *cec = dev_get_drvdata(dev);
|
|
+
|
|
+ clk_disable(cec->clk_cec);
|
|
+ clk_disable(cec->clk_hdmi_cec);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int cec_runtime_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_cec *cec = dev_get_drvdata(dev);
|
|
+ int ret;
|
|
+
|
|
+ ret = clk_enable(cec->clk_cec);
|
|
+ if (ret) {
|
|
+ dev_err(cec->dev, "fail to enable cec clock\n");
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ret = clk_enable(cec->clk_hdmi_cec);
|
|
+ if (ret)
|
|
+ dev_err(cec->dev, "fail to enable hdmi cec clock\n");
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static __maybe_unused int cec_suspend(struct device *dev)
|
|
+{
|
|
+ struct stm32_cec *cec = dev_get_drvdata(dev);
|
|
+
|
|
+ /* change pinctrl state */
|
|
+ pinctrl_pm_select_sleep_state(dev);
|
|
+
|
|
+ /* save resgisters settings to cec context */
|
|
+ regmap_read(cec->regmap, CEC_CR, &cec->c_reg);
|
|
+ regmap_read(cec->regmap, CEC_IER, &cec->ie_reg);
|
|
+ regmap_read(cec->regmap, CEC_CFGR, &cec->cfg_reg);
|
|
+
|
|
+ /* disable clock */
|
|
+ pm_runtime_force_suspend(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static __maybe_unused int cec_resume(struct device *dev)
|
|
+{
|
|
+ struct stm32_cec *cec = dev_get_drvdata(dev);
|
|
+
|
|
+ /* clock enable */
|
|
+ pm_runtime_force_resume(dev);
|
|
+
|
|
+ /* restore from cec context registers settings */
|
|
+ regmap_write(cec->regmap, CEC_CFGR, cec->cfg_reg);
|
|
+ regmap_write(cec->regmap, CEC_IER, cec->ie_reg);
|
|
+ regmap_write(cec->regmap, CEC_CR, cec->c_reg);
|
|
+
|
|
+ /* restore pinctl default state */
|
|
+ pinctrl_pm_select_default_state(dev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static const struct dev_pm_ops cec_pm_ops = {
|
|
+ SET_SYSTEM_SLEEP_PM_OPS(cec_suspend, cec_resume)
|
|
+ SET_RUNTIME_PM_OPS(cec_runtime_suspend,
|
|
+ cec_runtime_resume, NULL)
|
|
+};
|
|
+
|
|
static const struct of_device_id stm32_cec_of_match[] = {
|
|
{ .compatible = "st,stm32-cec" },
|
|
{ /* end node */ }
|
|
@@ -346,6 +433,7 @@ static struct platform_driver stm32_cec_driver = {
|
|
.driver = {
|
|
.name = CEC_NAME,
|
|
.of_match_table = stm32_cec_of_match,
|
|
+ .pm = &cec_pm_ops,
|
|
},
|
|
};
|
|
|
|
diff --git a/drivers/media/platform/stm32/stm32-dcmi.c b/drivers/media/platform/stm32/stm32-dcmi.c
|
|
index 18d0b56..0a59f44 100644
|
|
--- a/drivers/media/platform/stm32/stm32-dcmi.c
|
|
+++ b/drivers/media/platform/stm32/stm32-dcmi.c
|
|
@@ -95,13 +95,18 @@ enum state {
|
|
#define MIN_HEIGHT 16U
|
|
#define MAX_HEIGHT 2592U
|
|
|
|
+/* DMA can sustain YUV 720p@15fps max */
|
|
+#define MAX_DMA_BANDWIDTH (1280 * 720 * 2 * 15)
|
|
+
|
|
#define TIMEOUT_MS 1000
|
|
|
|
-struct dcmi_graph_entity {
|
|
- struct device_node *node;
|
|
+#define OVERRUN_ERROR_THRESHOLD 3
|
|
|
|
+struct dcmi_graph_entity {
|
|
struct v4l2_async_subdev asd;
|
|
- struct v4l2_subdev *subdev;
|
|
+
|
|
+ struct device_node *remote_node;
|
|
+ struct v4l2_subdev *source;
|
|
};
|
|
|
|
struct dcmi_format {
|
|
@@ -167,6 +172,10 @@ struct stm32_dcmi {
|
|
|
|
/* Ensure DMA operations atomicity */
|
|
struct mutex dma_lock;
|
|
+
|
|
+ struct media_device mdev;
|
|
+ struct media_pad vid_cap_pad;
|
|
+ struct media_pipeline pipeline;
|
|
};
|
|
|
|
static inline struct stm32_dcmi *notifier_to_dcmi(struct v4l2_async_notifier *n)
|
|
@@ -446,11 +455,13 @@ static irqreturn_t dcmi_irq_thread(int irq, void *arg)
|
|
|
|
spin_lock_irq(&dcmi->irqlock);
|
|
|
|
- if ((dcmi->misr & IT_OVR) || (dcmi->misr & IT_ERR)) {
|
|
- dcmi->errors_count++;
|
|
- if (dcmi->misr & IT_OVR)
|
|
- dcmi->overrun_count++;
|
|
+ if (dcmi->misr & IT_OVR) {
|
|
+ dcmi->overrun_count++;
|
|
+ if (dcmi->overrun_count > OVERRUN_ERROR_THRESHOLD)
|
|
+ dcmi->errors_count++;
|
|
}
|
|
+ if (dcmi->misr & IT_ERR)
|
|
+ dcmi->errors_count++;
|
|
|
|
if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG &&
|
|
dcmi->misr & IT_FRAME) {
|
|
@@ -576,6 +587,144 @@ static void dcmi_buf_queue(struct vb2_buffer *vb)
|
|
spin_unlock_irq(&dcmi->irqlock);
|
|
}
|
|
|
|
+static struct media_entity *dcmi_find_source(struct stm32_dcmi *dcmi)
|
|
+{
|
|
+ struct media_entity *entity = &dcmi->vdev->entity;
|
|
+ struct media_pad *pad;
|
|
+
|
|
+ /* Walk searching for entity having no sink */
|
|
+ while (1) {
|
|
+ pad = &entity->pads[0];
|
|
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
|
|
+ break;
|
|
+
|
|
+ pad = media_entity_remote_pad(pad);
|
|
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = pad->entity;
|
|
+ }
|
|
+
|
|
+ return entity;
|
|
+}
|
|
+
|
|
+static int dcmi_pipeline_s_fmt(struct stm32_dcmi *dcmi,
|
|
+ struct v4l2_subdev_pad_config *pad_cfg,
|
|
+ struct v4l2_subdev_format *format)
|
|
+{
|
|
+ struct media_entity *entity = &dcmi->entity.source->entity;
|
|
+ struct v4l2_subdev *subdev;
|
|
+ struct media_pad *sink_pad = NULL;
|
|
+ struct media_pad *src_pad = NULL;
|
|
+ struct media_pad *pad = NULL;
|
|
+ struct v4l2_subdev_format fmt = *format;
|
|
+ bool found = false;
|
|
+ int ret;
|
|
+
|
|
+ /*
|
|
+ * Starting from sensor subdevice, walk within
|
|
+ * pipeline and set format on each subdevice
|
|
+ */
|
|
+ while (1) {
|
|
+ unsigned int i;
|
|
+
|
|
+ /* Search if current entity has a source pad */
|
|
+ for (i = 0; i < entity->num_pads; i++) {
|
|
+ pad = &entity->pads[i];
|
|
+ if (pad->flags & MEDIA_PAD_FL_SOURCE) {
|
|
+ src_pad = pad;
|
|
+ found = true;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (!found)
|
|
+ break;
|
|
+
|
|
+ subdev = media_entity_to_v4l2_subdev(entity);
|
|
+
|
|
+ /* Propagate format on sink pad if any, otherwise source pad */
|
|
+ if (sink_pad)
|
|
+ pad = sink_pad;
|
|
+
|
|
+ dev_dbg(dcmi->dev, "\"%s\":%d pad format set to 0x%x %ux%u\n",
|
|
+ subdev->name, pad->index, format->format.code,
|
|
+ format->format.width, format->format.height);
|
|
+
|
|
+ fmt.pad = pad->index;
|
|
+ ret = v4l2_subdev_call(subdev, pad, set_fmt, pad_cfg, &fmt);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dcmi->dev, "%s: Failed to set format 0x%x %ux%u on \"%s\":%d pad (%d)\n",
|
|
+ __func__, format->format.code,
|
|
+ format->format.width, format->format.height,
|
|
+ subdev->name, pad->index, ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ if (fmt.format.code != format->format.code ||
|
|
+ fmt.format.width != format->format.width ||
|
|
+ fmt.format.height != format->format.height) {
|
|
+ dev_dbg(dcmi->dev, "\"%s\":%d pad format has been changed to 0x%x %ux%u\n",
|
|
+ subdev->name, pad->index, fmt.format.code,
|
|
+ fmt.format.width, fmt.format.height);
|
|
+ }
|
|
+
|
|
+ /* Walk to next entity */
|
|
+ sink_pad = media_entity_remote_pad(src_pad);
|
|
+ if (!sink_pad || !is_media_entity_v4l2_subdev(sink_pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = sink_pad->entity;
|
|
+ }
|
|
+ *format = fmt;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmi_pipeline_s_stream(struct stm32_dcmi *dcmi, int state)
|
|
+{
|
|
+ struct media_entity *entity = &dcmi->vdev->entity;
|
|
+ struct v4l2_subdev *subdev;
|
|
+ struct media_pad *pad;
|
|
+ int ret;
|
|
+
|
|
+ /* Start/stop all entities within pipeline */
|
|
+ while (1) {
|
|
+ pad = &entity->pads[0];
|
|
+ if (!(pad->flags & MEDIA_PAD_FL_SINK))
|
|
+ break;
|
|
+
|
|
+ pad = media_entity_remote_pad(pad);
|
|
+ if (!pad || !is_media_entity_v4l2_subdev(pad->entity))
|
|
+ break;
|
|
+
|
|
+ entity = pad->entity;
|
|
+ subdev = media_entity_to_v4l2_subdev(entity);
|
|
+
|
|
+ ret = v4l2_subdev_call(subdev, video, s_stream, state);
|
|
+ if (ret < 0 && ret != -ENOIOCTLCMD) {
|
|
+ dev_err(dcmi->dev, "%s: \"%s\" failed to %s streaming (%d)\n",
|
|
+ __func__, subdev->name,
|
|
+ state ? "start" : "stop", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dcmi->dev, "\"%s\" is %s\n",
|
|
+ subdev->name, state ? "started" : "stopped");
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int dcmi_pipeline_start(struct stm32_dcmi *dcmi)
|
|
+{
|
|
+ return dcmi_pipeline_s_stream(dcmi, 1);
|
|
+}
|
|
+
|
|
+static void dcmi_pipeline_stop(struct stm32_dcmi *dcmi)
|
|
+{
|
|
+ dcmi_pipeline_s_stream(dcmi, 0);
|
|
+}
|
|
+
|
|
static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
{
|
|
struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq);
|
|
@@ -590,14 +739,17 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
goto err_release_buffers;
|
|
}
|
|
|
|
- /* Enable stream on the sub device */
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 1);
|
|
- if (ret && ret != -ENOIOCTLCMD) {
|
|
- dev_err(dcmi->dev, "%s: Failed to start streaming, subdev streamon error",
|
|
- __func__);
|
|
+ ret = media_pipeline_start(&dcmi->vdev->entity, &dcmi->pipeline);
|
|
+ if (ret < 0) {
|
|
+ dev_err(dcmi->dev, "%s: Failed to start streaming, media pipeline start error (%d)\n",
|
|
+ __func__, ret);
|
|
goto err_pm_put;
|
|
}
|
|
|
|
+ ret = dcmi_pipeline_start(dcmi);
|
|
+ if (ret)
|
|
+ goto err_media_pipeline_stop;
|
|
+
|
|
spin_lock_irq(&dcmi->irqlock);
|
|
|
|
/* Set bus width */
|
|
@@ -635,8 +787,31 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
dcmi_set_crop(dcmi);
|
|
|
|
/* Enable jpeg capture */
|
|
- if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG)
|
|
- reg_set(dcmi->regs, DCMI_CR, CR_CM);/* Snapshot mode */
|
|
+ if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG) {
|
|
+ unsigned int rate;
|
|
+ struct v4l2_streamparm p = {
|
|
+ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE
|
|
+ };
|
|
+ struct v4l2_fract frame_interval = {1, 30};
|
|
+
|
|
+ ret = v4l2_g_parm_cap(dcmi->vdev, dcmi->entity.source, &p);
|
|
+ if (!ret)
|
|
+ frame_interval = p.parm.capture.timeperframe;
|
|
+
|
|
+ rate = dcmi->fmt.fmt.pix.sizeimage *
|
|
+ frame_interval.denominator / frame_interval.numerator;
|
|
+
|
|
+ /*
|
|
+ * If rate exceed DMA capabilities, switch to snapshot mode
|
|
+ * to ensure that current DMA transfer is elapsed before
|
|
+ * capturing a new JPEG.
|
|
+ */
|
|
+ if (rate > MAX_DMA_BANDWIDTH) {
|
|
+ reg_set(dcmi->regs, DCMI_CR, CR_CM);/* Snapshot mode */
|
|
+ dev_dbg(dcmi->dev, "Capture rate is too high for continuous mode (%d > %d bytes/s), switch to snapshot mode\n",
|
|
+ rate, MAX_DMA_BANDWIDTH);
|
|
+ }
|
|
+ }
|
|
|
|
/* Enable dcmi */
|
|
reg_set(dcmi->regs, DCMI_CR, CR_ENABLE);
|
|
@@ -669,16 +844,22 @@ static int dcmi_start_streaming(struct vb2_queue *vq, unsigned int count)
|
|
if (ret) {
|
|
dev_err(dcmi->dev, "%s: Start streaming failed, cannot start capture\n",
|
|
__func__);
|
|
- goto err_subdev_streamoff;
|
|
+ goto err_pipeline_stop;
|
|
}
|
|
|
|
/* Enable interruptions */
|
|
- reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
|
|
+ if (dcmi->sd_format->fourcc == V4L2_PIX_FMT_JPEG)
|
|
+ reg_set(dcmi->regs, DCMI_IER, IT_FRAME | IT_OVR | IT_ERR);
|
|
+ else
|
|
+ reg_set(dcmi->regs, DCMI_IER, IT_OVR | IT_ERR);
|
|
|
|
return 0;
|
|
|
|
-err_subdev_streamoff:
|
|
- v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0);
|
|
+err_pipeline_stop:
|
|
+ dcmi_pipeline_stop(dcmi);
|
|
+
|
|
+err_media_pipeline_stop:
|
|
+ media_pipeline_stop(&dcmi->vdev->entity);
|
|
|
|
err_pm_put:
|
|
pm_runtime_put(dcmi->dev);
|
|
@@ -703,13 +884,10 @@ static void dcmi_stop_streaming(struct vb2_queue *vq)
|
|
{
|
|
struct stm32_dcmi *dcmi = vb2_get_drv_priv(vq);
|
|
struct dcmi_buf *buf, *node;
|
|
- int ret;
|
|
|
|
- /* Disable stream on the sub device */
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, video, s_stream, 0);
|
|
- if (ret && ret != -ENOIOCTLCMD)
|
|
- dev_err(dcmi->dev, "%s: Failed to stop streaming, subdev streamoff error (%d)\n",
|
|
- __func__, ret);
|
|
+ dcmi_pipeline_stop(dcmi);
|
|
+
|
|
+ media_pipeline_stop(&dcmi->vdev->entity);
|
|
|
|
spin_lock_irq(&dcmi->irqlock);
|
|
|
|
@@ -850,7 +1028,7 @@ static int dcmi_try_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f,
|
|
}
|
|
|
|
v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code);
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad, set_fmt,
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad, set_fmt,
|
|
&pad_cfg, &format);
|
|
if (ret < 0)
|
|
return ret;
|
|
@@ -927,8 +1105,7 @@ static int dcmi_set_fmt(struct stm32_dcmi *dcmi, struct v4l2_format *f)
|
|
mf->width = sd_framesize.width;
|
|
mf->height = sd_framesize.height;
|
|
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad,
|
|
- set_fmt, NULL, &format);
|
|
+ ret = dcmi_pipeline_s_fmt(dcmi, NULL, &format);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
@@ -984,7 +1161,7 @@ static int dcmi_get_sensor_format(struct stm32_dcmi *dcmi,
|
|
};
|
|
int ret;
|
|
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad, get_fmt, NULL, &fmt);
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad, get_fmt, NULL, &fmt);
|
|
if (ret)
|
|
return ret;
|
|
|
|
@@ -1013,7 +1190,7 @@ static int dcmi_set_sensor_format(struct stm32_dcmi *dcmi,
|
|
}
|
|
|
|
v4l2_fill_mbus_format(&format.format, pix, sd_fmt->mbus_code);
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad, set_fmt,
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad, set_fmt,
|
|
&pad_cfg, &format);
|
|
if (ret < 0)
|
|
return ret;
|
|
@@ -1036,7 +1213,7 @@ static int dcmi_get_sensor_bounds(struct stm32_dcmi *dcmi,
|
|
/*
|
|
* Get sensor bounds first
|
|
*/
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad, get_selection,
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad, get_selection,
|
|
NULL, &bounds);
|
|
if (!ret)
|
|
*r = bounds.r;
|
|
@@ -1217,7 +1394,7 @@ static int dcmi_enum_framesizes(struct file *file, void *fh,
|
|
|
|
fse.code = sd_fmt->mbus_code;
|
|
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad, enum_frame_size,
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad, enum_frame_size,
|
|
NULL, &fse);
|
|
if (ret)
|
|
return ret;
|
|
@@ -1234,7 +1411,7 @@ static int dcmi_g_parm(struct file *file, void *priv,
|
|
{
|
|
struct stm32_dcmi *dcmi = video_drvdata(file);
|
|
|
|
- return v4l2_g_parm_cap(video_devdata(file), dcmi->entity.subdev, p);
|
|
+ return v4l2_g_parm_cap(video_devdata(file), dcmi->entity.source, p);
|
|
}
|
|
|
|
static int dcmi_s_parm(struct file *file, void *priv,
|
|
@@ -1242,7 +1419,7 @@ static int dcmi_s_parm(struct file *file, void *priv,
|
|
{
|
|
struct stm32_dcmi *dcmi = video_drvdata(file);
|
|
|
|
- return v4l2_s_parm_cap(video_devdata(file), dcmi->entity.subdev, p);
|
|
+ return v4l2_s_parm_cap(video_devdata(file), dcmi->entity.source, p);
|
|
}
|
|
|
|
static int dcmi_enum_frameintervals(struct file *file, void *fh,
|
|
@@ -1264,7 +1441,7 @@ static int dcmi_enum_frameintervals(struct file *file, void *fh,
|
|
|
|
fie.code = sd_fmt->mbus_code;
|
|
|
|
- ret = v4l2_subdev_call(dcmi->entity.subdev, pad,
|
|
+ ret = v4l2_subdev_call(dcmi->entity.source, pad,
|
|
enum_frame_interval, NULL, &fie);
|
|
if (ret)
|
|
return ret;
|
|
@@ -1284,7 +1461,7 @@ MODULE_DEVICE_TABLE(of, stm32_dcmi_of_match);
|
|
static int dcmi_open(struct file *file)
|
|
{
|
|
struct stm32_dcmi *dcmi = video_drvdata(file);
|
|
- struct v4l2_subdev *sd = dcmi->entity.subdev;
|
|
+ struct v4l2_subdev *sd = dcmi->entity.source;
|
|
int ret;
|
|
|
|
if (mutex_lock_interruptible(&dcmi->lock))
|
|
@@ -1315,7 +1492,7 @@ static int dcmi_open(struct file *file)
|
|
static int dcmi_release(struct file *file)
|
|
{
|
|
struct stm32_dcmi *dcmi = video_drvdata(file);
|
|
- struct v4l2_subdev *sd = dcmi->entity.subdev;
|
|
+ struct v4l2_subdev *sd = dcmi->entity.source;
|
|
bool fh_singular;
|
|
int ret;
|
|
|
|
@@ -1402,6 +1579,12 @@ static int dcmi_set_default_fmt(struct stm32_dcmi *dcmi)
|
|
return 0;
|
|
}
|
|
|
|
+/*
|
|
+ * FIXME: For the time being we only support subdevices
|
|
+ * which expose RGB & YUV "parallel form" mbus code (_2X8).
|
|
+ * Nevertheless, this allows to support serial source subdevices
|
|
+ * and serial to parallel bridges which conform to this.
|
|
+ */
|
|
static const struct dcmi_format dcmi_formats[] = {
|
|
{
|
|
.fourcc = V4L2_PIX_FMT_RGB565,
|
|
@@ -1426,7 +1609,7 @@ static int dcmi_formats_init(struct stm32_dcmi *dcmi)
|
|
{
|
|
const struct dcmi_format *sd_fmts[ARRAY_SIZE(dcmi_formats)];
|
|
unsigned int num_fmts = 0, i, j;
|
|
- struct v4l2_subdev *subdev = dcmi->entity.subdev;
|
|
+ struct v4l2_subdev *subdev = dcmi->entity.source;
|
|
struct v4l2_subdev_mbus_code_enum mbus_code = {
|
|
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
|
|
};
|
|
@@ -1440,12 +1623,20 @@ static int dcmi_formats_init(struct stm32_dcmi *dcmi)
|
|
/* Code supported, have we got this fourcc yet? */
|
|
for (j = 0; j < num_fmts; j++)
|
|
if (sd_fmts[j]->fourcc ==
|
|
- dcmi_formats[i].fourcc)
|
|
+ dcmi_formats[i].fourcc) {
|
|
/* Already available */
|
|
+ dev_dbg(dcmi->dev, "Skipping fourcc/code: %4.4s/0x%x\n",
|
|
+ (char *)&sd_fmts[j]->fourcc,
|
|
+ mbus_code.code);
|
|
break;
|
|
- if (j == num_fmts)
|
|
+ }
|
|
+ if (j == num_fmts) {
|
|
/* New */
|
|
sd_fmts[num_fmts++] = dcmi_formats + i;
|
|
+ dev_dbg(dcmi->dev, "Supported fourcc/code: %4.4s/0x%x\n",
|
|
+ (char *)&sd_fmts[num_fmts - 1]->fourcc,
|
|
+ sd_fmts[num_fmts - 1]->mbus_code);
|
|
+ }
|
|
}
|
|
mbus_code.index++;
|
|
}
|
|
@@ -1472,7 +1663,7 @@ static int dcmi_formats_init(struct stm32_dcmi *dcmi)
|
|
static int dcmi_framesizes_init(struct stm32_dcmi *dcmi)
|
|
{
|
|
unsigned int num_fsize = 0;
|
|
- struct v4l2_subdev *subdev = dcmi->entity.subdev;
|
|
+ struct v4l2_subdev *subdev = dcmi->entity.source;
|
|
struct v4l2_subdev_frame_size_enum fse = {
|
|
.which = V4L2_SUBDEV_FORMAT_ACTIVE,
|
|
.code = dcmi->sd_format->mbus_code,
|
|
@@ -1519,7 +1710,20 @@ static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier)
|
|
struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier);
|
|
int ret;
|
|
|
|
- dcmi->vdev->ctrl_handler = dcmi->entity.subdev->ctrl_handler;
|
|
+ /*
|
|
+ * Now that the graph is complete,
|
|
+ * we search for the source subdevice
|
|
+ * in order to expose it through V4L2 interface
|
|
+ */
|
|
+ dcmi->entity.source =
|
|
+ media_entity_to_v4l2_subdev(dcmi_find_source(dcmi));
|
|
+ if (!dcmi->entity.source) {
|
|
+ dev_err(dcmi->dev, "Source subdevice not found\n");
|
|
+ return -ENODEV;
|
|
+ }
|
|
+
|
|
+ dcmi->vdev->ctrl_handler = dcmi->entity.source->ctrl_handler;
|
|
+
|
|
ret = dcmi_formats_init(dcmi);
|
|
if (ret) {
|
|
dev_err(dcmi->dev, "No supported mediabus format found\n");
|
|
@@ -1544,14 +1748,6 @@ static int dcmi_graph_notify_complete(struct v4l2_async_notifier *notifier)
|
|
return ret;
|
|
}
|
|
|
|
- ret = video_register_device(dcmi->vdev, VFL_TYPE_GRABBER, -1);
|
|
- if (ret) {
|
|
- dev_err(dcmi->dev, "Failed to register video device\n");
|
|
- return ret;
|
|
- }
|
|
-
|
|
- dev_dbg(dcmi->dev, "Device registered as %s\n",
|
|
- video_device_node_name(dcmi->vdev));
|
|
return 0;
|
|
}
|
|
|
|
@@ -1572,12 +1768,31 @@ static int dcmi_graph_notify_bound(struct v4l2_async_notifier *notifier,
|
|
struct v4l2_async_subdev *asd)
|
|
{
|
|
struct stm32_dcmi *dcmi = notifier_to_dcmi(notifier);
|
|
+ unsigned int ret;
|
|
+ int src_pad;
|
|
|
|
- dev_dbg(dcmi->dev, "Subdev %s bound\n", subdev->name);
|
|
+ dev_dbg(dcmi->dev, "Subdev \"%s\" bound\n", subdev->name);
|
|
|
|
- dcmi->entity.subdev = subdev;
|
|
+ /*
|
|
+ * Link this sub-device to DCMI, it could be
|
|
+ * a parallel camera sensor or a bridge
|
|
+ */
|
|
+ src_pad = media_entity_get_fwnode_pad(&subdev->entity,
|
|
+ subdev->fwnode,
|
|
+ MEDIA_PAD_FL_SOURCE);
|
|
+
|
|
+ ret = media_create_pad_link(&subdev->entity, src_pad,
|
|
+ &dcmi->vdev->entity, 0,
|
|
+ MEDIA_LNK_FL_IMMUTABLE |
|
|
+ MEDIA_LNK_FL_ENABLED);
|
|
+ if (ret)
|
|
+ dev_err(dcmi->dev, "Failed to create media pad link with subdev \"%s\"\n",
|
|
+ subdev->name);
|
|
+ else
|
|
+ dev_dbg(dcmi->dev, "DCMI is now linked to \"%s\"\n",
|
|
+ subdev->name);
|
|
|
|
- return 0;
|
|
+ return ret;
|
|
}
|
|
|
|
static const struct v4l2_async_notifier_operations dcmi_graph_notify_ops = {
|
|
@@ -1601,7 +1816,7 @@ static int dcmi_graph_parse(struct stm32_dcmi *dcmi, struct device_node *node)
|
|
return -EINVAL;
|
|
|
|
/* Remote node to connect */
|
|
- dcmi->entity.node = remote;
|
|
+ dcmi->entity.remote_node = remote;
|
|
dcmi->entity.asd.match_type = V4L2_ASYNC_MATCH_FWNODE;
|
|
dcmi->entity.asd.match.fwnode = of_fwnode_handle(remote);
|
|
return 0;
|
|
@@ -1622,7 +1837,7 @@ static int dcmi_graph_init(struct stm32_dcmi *dcmi)
|
|
/* Register the subdevices notifier. */
|
|
subdevs = devm_kzalloc(dcmi->dev, sizeof(*subdevs), GFP_KERNEL);
|
|
if (!subdevs) {
|
|
- of_node_put(dcmi->entity.node);
|
|
+ of_node_put(dcmi->entity.remote_node);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
@@ -1635,7 +1850,7 @@ static int dcmi_graph_init(struct stm32_dcmi *dcmi)
|
|
ret = v4l2_async_notifier_register(&dcmi->v4l2_dev, &dcmi->notifier);
|
|
if (ret < 0) {
|
|
dev_err(dcmi->dev, "Notifier registration failed\n");
|
|
- of_node_put(dcmi->entity.node);
|
|
+ of_node_put(dcmi->entity.remote_node);
|
|
return ret;
|
|
}
|
|
|
|
@@ -1746,10 +1961,19 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
|
|
q = &dcmi->queue;
|
|
|
|
+ dcmi->v4l2_dev.mdev = &dcmi->mdev;
|
|
+
|
|
+ /* Initialize media device */
|
|
+ strscpy(dcmi->mdev.model, DRV_NAME, sizeof(dcmi->mdev.model));
|
|
+ snprintf(dcmi->mdev.bus_info, sizeof(dcmi->mdev.bus_info),
|
|
+ "platform:%s", DRV_NAME);
|
|
+ dcmi->mdev.dev = &pdev->dev;
|
|
+ media_device_init(&dcmi->mdev);
|
|
+
|
|
/* Initialize the top-level structure */
|
|
ret = v4l2_device_register(&pdev->dev, &dcmi->v4l2_dev);
|
|
if (ret)
|
|
- goto err_dma_release;
|
|
+ goto err_media_device_cleanup;
|
|
|
|
dcmi->vdev = video_device_alloc();
|
|
if (!dcmi->vdev) {
|
|
@@ -1769,6 +1993,25 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
V4L2_CAP_READWRITE;
|
|
video_set_drvdata(dcmi->vdev, dcmi);
|
|
|
|
+ /* Media entity pads */
|
|
+ dcmi->vid_cap_pad.flags = MEDIA_PAD_FL_SINK;
|
|
+ ret = media_entity_pads_init(&dcmi->vdev->entity,
|
|
+ 1, &dcmi->vid_cap_pad);
|
|
+ if (ret) {
|
|
+ dev_err(dcmi->dev, "Failed to init media entity pad\n");
|
|
+ goto err_device_release;
|
|
+ }
|
|
+ dcmi->vdev->entity.flags |= MEDIA_ENT_FL_DEFAULT;
|
|
+
|
|
+ ret = video_register_device(dcmi->vdev, VFL_TYPE_GRABBER, -1);
|
|
+ if (ret) {
|
|
+ dev_err(dcmi->dev, "Failed to register video device\n");
|
|
+ goto err_media_entity_cleanup;
|
|
+ }
|
|
+
|
|
+ dev_dbg(dcmi->dev, "Device registered as %s\n",
|
|
+ video_device_node_name(dcmi->vdev));
|
|
+
|
|
/* Buffer queue */
|
|
q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
|
|
q->io_modes = VB2_MMAP | VB2_READ | VB2_DMABUF;
|
|
@@ -1784,12 +2027,12 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
ret = vb2_queue_init(q);
|
|
if (ret < 0) {
|
|
dev_err(&pdev->dev, "Failed to initialize vb2 queue\n");
|
|
- goto err_device_release;
|
|
+ goto err_media_entity_cleanup;
|
|
}
|
|
|
|
ret = dcmi_graph_init(dcmi);
|
|
if (ret < 0)
|
|
- goto err_device_release;
|
|
+ goto err_media_entity_cleanup;
|
|
|
|
/* Reset device */
|
|
ret = reset_control_assert(dcmi->rstc);
|
|
@@ -1814,11 +2057,14 @@ static int dcmi_probe(struct platform_device *pdev)
|
|
|
|
return 0;
|
|
|
|
+err_media_entity_cleanup:
|
|
+ media_entity_cleanup(&dcmi->vdev->entity);
|
|
err_device_release:
|
|
video_device_release(dcmi->vdev);
|
|
err_device_unregister:
|
|
v4l2_device_unregister(&dcmi->v4l2_dev);
|
|
-err_dma_release:
|
|
+err_media_device_cleanup:
|
|
+ media_device_cleanup(&dcmi->mdev);
|
|
dma_release_channel(dcmi->dma_chan);
|
|
|
|
return ret;
|
|
@@ -1831,7 +2077,9 @@ static int dcmi_remove(struct platform_device *pdev)
|
|
pm_runtime_disable(&pdev->dev);
|
|
|
|
v4l2_async_notifier_unregister(&dcmi->notifier);
|
|
+ media_entity_cleanup(&dcmi->vdev->entity);
|
|
v4l2_device_unregister(&dcmi->v4l2_dev);
|
|
+ media_device_cleanup(&dcmi->mdev);
|
|
|
|
dma_release_channel(dcmi->dma_chan);
|
|
|
|
diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c
|
|
index fecccb5..89a7839 100644
|
|
--- a/drivers/media/usb/uvc/uvc_queue.c
|
|
+++ b/drivers/media/usb/uvc/uvc_queue.c
|
|
@@ -224,7 +224,7 @@ int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type,
|
|
int ret;
|
|
|
|
queue->queue.type = type;
|
|
- queue->queue.io_modes = VB2_MMAP | VB2_USERPTR;
|
|
+ queue->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ;
|
|
queue->queue.drv_priv = queue;
|
|
queue->queue.buf_struct_size = sizeof(struct uvc_buffer);
|
|
queue->queue.mem_ops = &vb2_vmalloc_memops;
|
|
@@ -357,6 +357,19 @@ int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type)
|
|
return ret;
|
|
}
|
|
|
|
+ssize_t uvc_queue_read(struct uvc_video_queue *queue, struct file *file,
|
|
+ char __user *buf, size_t count, loff_t *ppos)
|
|
+{
|
|
+ ssize_t ret;
|
|
+
|
|
+ mutex_lock(&queue->mutex);
|
|
+ ret = vb2_read(&queue->queue, buf, count, ppos,
|
|
+ file->f_flags & O_NONBLOCK);
|
|
+ mutex_unlock(&queue->mutex);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma)
|
|
{
|
|
return vb2_mmap(&queue->queue, vma);
|
|
diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c
|
|
index 18a7384..242d886 100644
|
|
--- a/drivers/media/usb/uvc/uvc_v4l2.c
|
|
+++ b/drivers/media/usb/uvc/uvc_v4l2.c
|
|
@@ -594,7 +594,8 @@ static int uvc_ioctl_querycap(struct file *file, void *fh,
|
|
strlcpy(cap->driver, "uvcvideo", sizeof(cap->driver));
|
|
strlcpy(cap->card, vdev->name, sizeof(cap->card));
|
|
usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
|
|
- cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
|
|
+ cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING |
|
|
+ V4L2_CAP_READWRITE
|
|
| chain->caps;
|
|
|
|
return 0;
|
|
@@ -1433,8 +1434,12 @@ static long uvc_v4l2_compat_ioctl32(struct file *file,
|
|
static ssize_t uvc_v4l2_read(struct file *file, char __user *data,
|
|
size_t count, loff_t *ppos)
|
|
{
|
|
- uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_read: not implemented.\n");
|
|
- return -EINVAL;
|
|
+ struct uvc_fh *handle = file->private_data;
|
|
+ struct uvc_streaming *stream = handle->stream;
|
|
+
|
|
+ uvc_trace(UVC_TRACE_CALLS, "uvc_v4l2_read\n");
|
|
+
|
|
+ return uvc_queue_read(&stream->queue, file, data, count, ppos);
|
|
}
|
|
|
|
static int uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma)
|
|
diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h
|
|
index a738486..a7e843a 100644
|
|
--- a/drivers/media/usb/uvc/uvcvideo.h
|
|
+++ b/drivers/media/usb/uvc/uvcvideo.h
|
|
@@ -704,6 +704,8 @@ int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type);
|
|
void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect);
|
|
struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue,
|
|
struct uvc_buffer *buf);
|
|
+ssize_t uvc_queue_read(struct uvc_video_queue *queue, struct file *file,
|
|
+ char __user *buf, size_t count, loff_t *ppos);
|
|
int uvc_queue_mmap(struct uvc_video_queue *queue,
|
|
struct vm_area_struct *vma);
|
|
__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file,
|
|
diff --git a/drivers/media/v4l2-core/v4l2-fwnode.c b/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
index 169bdbb..505338e 100644
|
|
--- a/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
+++ b/drivers/media/v4l2-core/v4l2-fwnode.c
|
|
@@ -158,6 +158,9 @@ static void v4l2_fwnode_endpoint_parse_parallel_bus(
|
|
flags |= v ? V4L2_MBUS_DATA_ENABLE_HIGH :
|
|
V4L2_MBUS_DATA_ENABLE_LOW;
|
|
|
|
+ if (!fwnode_property_read_u32(fwnode, "pclk-max-frequency", &v))
|
|
+ bus->pclk_max_frequency = v;
|
|
+
|
|
bus->flags = flags;
|
|
|
|
}
|
|
diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
|
|
index d6a5a3b..2a6b253 100644
|
|
--- a/include/uapi/linux/media-bus-format.h
|
|
+++ b/include/uapi/linux/media-bus-format.h
|
|
@@ -34,7 +34,7 @@
|
|
|
|
#define MEDIA_BUS_FMT_FIXED 0x0001
|
|
|
|
-/* RGB - next is 0x101b */
|
|
+/* RGB - next is 0x101c */
|
|
#define MEDIA_BUS_FMT_RGB444_1X12 0x1016
|
|
#define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE 0x1001
|
|
#define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE 0x1002
|
|
@@ -50,6 +50,7 @@
|
|
#define MEDIA_BUS_FMT_RGB666_1X24_CPADHI 0x1015
|
|
#define MEDIA_BUS_FMT_RGB666_1X7X3_SPWG 0x1010
|
|
#define MEDIA_BUS_FMT_BGR888_1X24 0x1013
|
|
+#define MEDIA_BUS_FMT_BGR888_3X8 0x101b
|
|
#define MEDIA_BUS_FMT_GBR888_1X24 0x1014
|
|
#define MEDIA_BUS_FMT_RGB888_1X24 0x100a
|
|
#define MEDIA_BUS_FMT_RGB888_2X12_BE 0x100b
|
|
--
|
|
2.7.4
|
|
|