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). > 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@xxxxxxxxxxxx> > --- > 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 -- To unsubscribe from this list: send the line "unsubscribe devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html