Thread (51 messages) 51 messages, 10 authors, 2017-12-28

[PATCH v2 1/3] media: V3s: Add support for Allwinner CSI.

From: Mylene JOSSERAND <hidden>
Date: 2017-09-22 08:44:18
Also in: linux-devicetree, linux-media, lkml

Hello Yong,

Thank you for these drivers!

I tested it with an OV5640 camera on an Nanopi M1 plus (Allwinner H3)
and I noticed that I got a frame correctly displayed only on a half of
the frame's size.

It is related to your "sun6i_csi_set_window" function (see
below).
quoted hunk ↗ jump to hunk
Allwinner V3s SoC have two CSI module. CSI0 is used for MIPI interface
and CSI1 is used for parallel interface. This is not documented in
datasheet but by testing and guess.

This patch implement a v4l2 framework driver for it.

Currently, the driver only support the parallel interface. MIPI-CSI2,
ISP's support are not included in this patch.

Signed-off-by: Yong Deng <yong.deng@magewell.com>
---
 drivers/media/platform/Kconfig                   |   1 +
 drivers/media/platform/Makefile                  |   2 +
 drivers/media/platform/sun6i-csi/Kconfig         |   9 +
 drivers/media/platform/sun6i-csi/Makefile        |   3 +
 drivers/media/platform/sun6i-csi/sun6i_csi.c     | 545
+++++++++++++++ drivers/media/platform/sun6i-csi/sun6i_csi.h     |
203 ++++++ drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c | 827
+++++++++++++++++++++++
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h | 206 ++++++
drivers/media/platform/sun6i-csi/sun6i_video.c   | 663
++++++++++++++++++ drivers/media/platform/sun6i-csi/sun6i_video.h
|  61 ++ 10 files changed, 2520 insertions(+) create mode 100644
drivers/media/platform/sun6i-csi/Kconfig create mode 100644
drivers/media/platform/sun6i-csi/Makefile create mode 100644
drivers/media/platform/sun6i-csi/sun6i_csi.c create mode 100644
drivers/media/platform/sun6i-csi/sun6i_csi.h create mode 100644
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.c create mode 100644
drivers/media/platform/sun6i-csi/sun6i_csi_v3s.h create mode 100644
drivers/media/platform/sun6i-csi/sun6i_video.c create mode 100644
drivers/media/platform/sun6i-csi/sun6i_video.h
diff --git a/drivers/media/platform/Kconfig
b/drivers/media/platform/Kconfig index 0c741d1..8371a87 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -143,6 +143,7 @@ source "drivers/media/platform/am437x/Kconfig"
 source "drivers/media/platform/xilinx/Kconfig"
 source "drivers/media/platform/rcar-vin/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
+source "drivers/media/platform/sun6i-csi/Kconfig"
 
<snip>
+static void sun6i_csi_set_format(struct sun6i_csi_dev *sdev)
+{
+	struct sun6i_csi *csi = &sdev->csi;
+	u32 cfg;
+	u32 val;
+
+	regmap_read(sdev->regmap, CSI_CH_CFG_REG, &cfg);
+
+	cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK |
+		 CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN |
+		 CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK |
+		 CSI_CH_CFG_INPUT_SEQ_MASK);
+
+	val = get_csi_input_format(csi->config.code,
csi->config.pixelformat);
+	cfg |= CSI_CH_CFG_INPUT_FMT(val);
+
+	val = get_csi_output_format(csi->config.code,
csi->config.field);
+	cfg |= CSI_CH_CFG_OUTPUT_FMT(val);
+
+	val = get_csi_input_seq(csi->config.code,
csi->config.pixelformat);
+	cfg |= CSI_CH_CFG_INPUT_SEQ(val);
+
+	if (csi->config.field == V4L2_FIELD_TOP)
+		cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0;
+	else if (csi->config.field == V4L2_FIELD_BOTTOM)
+		cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1;
+	else
+		cfg |= CSI_CH_CFG_FIELD_SEL_BOTH;
+
+	regmap_write(sdev->regmap, CSI_CH_CFG_REG, cfg);
+}
+
+static void sun6i_csi_set_window(struct sun6i_csi_dev *sdev)
+{
+	struct sun6i_csi_config *config = &sdev->csi.config;
+	u32 bytesperline_y;
+	u32 bytesperline_c;
+	int *planar_offset = sdev->planar_offset;
+
+	regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
+		     CSI_CH_HSIZE_HOR_LEN(config->width) |
+		     CSI_CH_HSIZE_HOR_START(0));
+	regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
+		     CSI_CH_VSIZE_VER_LEN(config->height) |
+		     CSI_CH_VSIZE_VER_START(0));
In my case, the HOR_LEN and VER_LEN were not correctly configured.

They were configured to width and height (640 * 480) but as I was
using a YUYV format, the pixel's size is 2 bytes so the
horizontal/vertical lines' lengths were divided by 2.

Currently, I fixed that by getting the number of bytes per pixel with
"v4l2_pixformat_get_bpp()":

+       int bytes_pixel;
+
+       bytes_pixel = v4l2_pixformat_get_bpp(config->pixelformat) / 8;
 
        regmap_write(sdev->regmap, CSI_CH_HSIZE_REG,
-                    CSI_CH_HSIZE_HOR_LEN(config->width) |
+                    CSI_CH_HSIZE_HOR_LEN(config->width * bytes_pixel) |
                     CSI_CH_HSIZE_HOR_START(0));
        regmap_write(sdev->regmap, CSI_CH_VSIZE_REG,
-                    CSI_CH_VSIZE_VER_LEN(config->height) |
+                    CSI_CH_VSIZE_VER_LEN(config->height * bytes_pixel)
  | CSI_CH_VSIZE_VER_START(0));

There is certainly a nicer way to handle that.
+
+	planar_offset[0] = 0;
+	switch(config->pixelformat) {
+	case V4L2_PIX_FMT_HM12:
+	case V4L2_PIX_FMT_NV12:
+	case V4L2_PIX_FMT_NV21:
+	case V4L2_PIX_FMT_NV16:
+	case V4L2_PIX_FMT_NV61:
+		bytesperline_y = config->width;
+		bytesperline_c = config->width;
+		planar_offset[1] = bytesperline_y * config->height;
+		planar_offset[2] = -1;
+		break;
+	case V4L2_PIX_FMT_YUV420:
+	case V4L2_PIX_FMT_YVU420:
+		bytesperline_y = config->width;
+		bytesperline_c = config->width / 2;
+		planar_offset[1] = bytesperline_y * config->height;
+		planar_offset[2] = planar_offset[1] +
+				bytesperline_c * config->height / 2;
+		break;
+	case V4L2_PIX_FMT_YUV422P:
+		bytesperline_y = config->width;
+		bytesperline_c = config->width / 2;
+		planar_offset[1] = bytesperline_y * config->height;
+		planar_offset[2] = planar_offset[1] +
+				bytesperline_c * config->height;
+		break;
+	default: /* raw */
+		bytesperline_y =
(v4l2_pixformat_get_bpp(config->pixelformat) *
+				  config->width) / 8;
+		bytesperline_c = 0;
+		planar_offset[1] = -1;
+		planar_offset[2] = -1;
+		break;
+	}
+
+	regmap_write(sdev->regmap, CSI_CH_BUF_LEN_REG,
+		     CSI_CH_BUF_LEN_BUF_LEN_C(bytesperline_c) |
+		     CSI_CH_BUF_LEN_BUF_LEN_Y(bytesperline_y));
+}
+
+static int get_supported_pixformats(struct sun6i_csi *csi,
+				    const u32 **pixformats)
+{
+	if (pixformats != NULL)
+		*pixformats = supported_pixformats;
+
+	return ARRAY_SIZE(supported_pixformats);
+}
+
+static bool is_format_support(struct sun6i_csi *csi, u32 pixformat,
+			      u32 mbus_code)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+
+	return __is_format_support(sdev, pixformat, mbus_code);
+}
+
+static int set_power(struct sun6i_csi *csi, bool enable)
+{
+	struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi);
+	struct regmap *regmap = sdev->regmap;
+	int ret;
+
+	if (!enable) {
+		regmap_update_bits(regmap, CSI_EN_REG,
CSI_EN_CSI_EN, 0); +
+		clk_disable_unprepare(sdev->clk_ram);
+		clk_disable_unprepare(sdev->clk_mod);
+		clk_disable_unprepare(sdev->clk_ahb);
+		reset_control_assert(sdev->rstc_ahb);
+		return 0;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_ahb);
+	if (ret) {
+		dev_err(sdev->dev, "Enable ahb clk err %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_mod);
+	if (ret) {
+		dev_err(sdev->dev, "Enable csi clk err %d\n", ret);
+		return ret;
+	}
+
+	ret = clk_prepare_enable(sdev->clk_ram);
+	if (ret) {
+		dev_err(sdev->dev, "Enable clk_dram_csi clk err
%d\n", ret);
+		return ret;
+	}
+
+	if (!IS_ERR_OR_NULL(sdev->rstc_ahb)) {
+		ret = reset_control_deassert(sdev->rstc_ahb);
+		if (ret) {
+			dev_err(sdev->dev, "reset err %d\n", ret);
+			return ret;
+		}
+	}
+
+	regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN,
CSI_EN_CSI_EN); +
+	return 0;
+}
<snip>

Thank you in advance!

Best regards,

-- 
Myl?ne Josserand, Free Electrons
Embedded Linux and Kernel engineering
http://free-electrons.com
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help