Hi Yong, Thanks for the update! This looks pretty good; some small bits to fix still, please see my comments below. On Wed, Sep 26, 2018 at 04:43:40PM +0800, Yong Deng wrote: > Allwinner V3s SoC features two CSI module. CSI0 is used for MIPI CSI-2 > interface and CSI1 is used for parallel interface. This is not > documented in datasheet but by test 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. > > Reviewed-by: Hans Verkuil <hans.verkuil@xxxxxxxxx> > Reviewed-by: Maxime Ripard <maxime.ripard@xxxxxxxxxxx> > Tested-by: Maxime Ripard <maxime.ripard@xxxxxxxxxxx> > Signed-off-by: Yong Deng <yong.deng@xxxxxxxxxxxx> > --- > MAINTAINERS | 8 + > drivers/media/platform/Kconfig | 1 + > drivers/media/platform/Makefile | 2 + > drivers/media/platform/sunxi/sun6i-csi/Kconfig | 9 + > drivers/media/platform/sunxi/sun6i-csi/Makefile | 3 + > drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c | 915 +++++++++++++++++++++ > drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h | 135 +++ > .../media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h | 196 +++++ > .../media/platform/sunxi/sun6i-csi/sun6i_video.c | 675 +++++++++++++++ > .../media/platform/sunxi/sun6i-csi/sun6i_video.h | 38 + > 10 files changed, 1982 insertions(+) > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Kconfig > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/Makefile > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c > create mode 100644 drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h > > diff --git a/MAINTAINERS b/MAINTAINERS > index 9989925f658d..d6c8d13962ab 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -3900,6 +3900,14 @@ M: Jaya Kumar <jayakumar.alsa@xxxxxxxxx> > S: Maintained > F: sound/pci/cs5535audio/ > > +CSI DRIVERS FOR ALLWINNER V3s > +M: Yong Deng <yong.deng@xxxxxxxxxxxx> > +L: linux-media@xxxxxxxxxxxxxxx > +T: git git://linuxtv.org/media_tree.git > +S: Maintained > +F: drivers/media/platform/sunxi/sun6i-csi/ > +F: Documentation/devicetree/bindings/media/sun6i-csi.txt > + > CW1200 WLAN driver > M: Solomon Peachy <pizza@xxxxxxxxxxxx> > S: Maintained > diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig > index f6275874ec9e..3c8b15ea957f 100644 > --- a/drivers/media/platform/Kconfig > +++ b/drivers/media/platform/Kconfig > @@ -137,6 +137,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/sunxi/sun6i-csi/Kconfig" > > config VIDEO_TI_CAL > tristate "TI CAL (Camera Adaptation Layer) driver" > diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile > index 6ab6200dd9c9..2662a230f681 100644 > --- a/drivers/media/platform/Makefile > +++ b/drivers/media/platform/Makefile > @@ -98,3 +98,5 @@ obj-$(CONFIG_VIDEO_QCOM_VENUS) += qcom/venus/ > obj-y += meson/ > > obj-y += cros-ec-cec/ > + > +obj-$(CONFIG_VIDEO_SUN6I_CSI) += sunxi/sun6i-csi/ > diff --git a/drivers/media/platform/sunxi/sun6i-csi/Kconfig b/drivers/media/platform/sunxi/sun6i-csi/Kconfig > new file mode 100644 > index 000000000000..018e3ec788c0 > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/Kconfig > @@ -0,0 +1,9 @@ > +config VIDEO_SUN6I_CSI > + tristate "Allwinner V3s Camera Sensor Interface driver" > + depends on VIDEO_V4L2 && COMMON_CLK && VIDEO_V4L2_SUBDEV_API && HAS_DMA > + depends on ARCH_SUNXI || COMPILE_TEST > + select VIDEOBUF2_DMA_CONTIG > + select REGMAP_MMIO > + select V4L2_FWNODE > + help > + Support for the Allwinner Camera Sensor Interface Controller on V3s. > diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile > new file mode 100644 > index 000000000000..213cb6be9e9c > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile > @@ -0,0 +1,3 @@ > +sun6i-csi-y += sun6i_video.o sun6i_csi.o > + > +obj-$(CONFIG_VIDEO_SUN6I_CSI) += sun6i-csi.o > diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c > new file mode 100644 > index 000000000000..db200764d712 > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c > @@ -0,0 +1,915 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) > + * All rights reserved. > + * Author: Yong Deng <yong.deng@xxxxxxxxxxxx> > + */ > + > +#include <linux/clk.h> > +#include <linux/delay.h> > +#include <linux/dma-mapping.h> > +#include <linux/err.h> > +#include <linux/fs.h> > +#include <linux/interrupt.h> > +#include <linux/io.h> > +#include <linux/ioctl.h> > +#include <linux/module.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/pm_runtime.h> > +#include <linux/regmap.h> > +#include <linux/reset.h> > +#include <linux/sched.h> > +#include <linux/sizes.h> > +#include <linux/slab.h> > + > +#include "sun6i_csi.h" > +#include "sun6i_csi_reg.h" > + > +#define MODULE_NAME "sun6i-csi" > + > +struct sun6i_csi_dev { > + struct sun6i_csi csi; > + struct device *dev; > + > + struct regmap *regmap; > + struct clk *clk_mod; > + struct clk *clk_ram; > + struct reset_control *rstc_bus; > + > + int planar_offset[3]; > +}; > + > +static inline struct sun6i_csi_dev *sun6i_csi_to_dev(struct sun6i_csi *csi) > +{ > + return container_of(csi, struct sun6i_csi_dev, csi); > +} > + > +/* TODO add 10&12 bit YUV, RGB support */ > +bool sun6i_csi_is_format_supported(struct sun6i_csi *csi, > + u32 pixformat, u32 mbus_code) > +{ > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); > + > + /* > + * Some video receivers have the ability to be compatible with > + * 8bit and 16bit bus width. > + * Identify the media bus format from device tree. > + */ > + if ((sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_PARALLEL > + || sdev->csi.v4l2_ep.bus_type == V4L2_MBUS_BT656) > + && sdev->csi.v4l2_ep.bus.parallel.bus_width == 16) { > + switch (pixformat) { > + 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: > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YVU420: > + case V4L2_PIX_FMT_YUV422P: > + switch (mbus_code) { > + case MEDIA_BUS_FMT_UYVY8_1X16: > + case MEDIA_BUS_FMT_VYUY8_1X16: > + case MEDIA_BUS_FMT_YUYV8_1X16: > + case MEDIA_BUS_FMT_YVYU8_1X16: > + return true; > + default: > + dev_dbg(sdev->dev, "Unsupported mbus code: 0x%x\n", > + mbus_code); > + break; > + } > + break; > + default: > + dev_dbg(sdev->dev, "Unsupported pixformat: 0x%x\n", > + pixformat); > + break; > + } > + return false; > + } > + > + switch (pixformat) { > + case V4L2_PIX_FMT_SBGGR8: > + return (mbus_code == MEDIA_BUS_FMT_SBGGR8_1X8); > + case V4L2_PIX_FMT_SGBRG8: > + return (mbus_code == MEDIA_BUS_FMT_SGBRG8_1X8); > + case V4L2_PIX_FMT_SGRBG8: > + return (mbus_code == MEDIA_BUS_FMT_SGRBG8_1X8); > + case V4L2_PIX_FMT_SRGGB8: > + return (mbus_code == MEDIA_BUS_FMT_SRGGB8_1X8); > + case V4L2_PIX_FMT_SBGGR10: > + return (mbus_code == MEDIA_BUS_FMT_SBGGR10_1X10); > + case V4L2_PIX_FMT_SGBRG10: > + return (mbus_code == MEDIA_BUS_FMT_SGBRG10_1X10); > + case V4L2_PIX_FMT_SGRBG10: > + return (mbus_code == MEDIA_BUS_FMT_SGRBG10_1X10); > + case V4L2_PIX_FMT_SRGGB10: > + return (mbus_code == MEDIA_BUS_FMT_SRGGB10_1X10); > + case V4L2_PIX_FMT_SBGGR12: > + return (mbus_code == MEDIA_BUS_FMT_SBGGR12_1X12); > + case V4L2_PIX_FMT_SGBRG12: > + return (mbus_code == MEDIA_BUS_FMT_SGBRG12_1X12); > + case V4L2_PIX_FMT_SGRBG12: > + return (mbus_code == MEDIA_BUS_FMT_SGRBG12_1X12); > + case V4L2_PIX_FMT_SRGGB12: > + return (mbus_code == MEDIA_BUS_FMT_SRGGB12_1X12); > + > + case V4L2_PIX_FMT_YUYV: > + return (mbus_code == MEDIA_BUS_FMT_YUYV8_2X8); > + case V4L2_PIX_FMT_YVYU: > + return (mbus_code == MEDIA_BUS_FMT_YVYU8_2X8); > + case V4L2_PIX_FMT_UYVY: > + return (mbus_code == MEDIA_BUS_FMT_UYVY8_2X8); > + case V4L2_PIX_FMT_VYUY: > + return (mbus_code == MEDIA_BUS_FMT_VYUY8_2X8); > + > + 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: > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YVU420: > + case V4L2_PIX_FMT_YUV422P: > + switch (mbus_code) { > + case MEDIA_BUS_FMT_UYVY8_2X8: > + case MEDIA_BUS_FMT_VYUY8_2X8: > + case MEDIA_BUS_FMT_YUYV8_2X8: > + case MEDIA_BUS_FMT_YVYU8_2X8: > + return true; > + default: > + dev_dbg(sdev->dev, "Unsupported mbus code: 0x%x\n", > + mbus_code); > + break; > + } > + break; > + default: > + dev_dbg(sdev->dev, "Unsupported pixformat: 0x%x\n", pixformat); > + break; > + } > + > + return false; > +} > + > +int sun6i_csi_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); > + reset_control_assert(sdev->rstc_bus); > + return 0; > + } > + > + 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); > + goto clk_mod_disable; > + } > + > + ret = reset_control_deassert(sdev->rstc_bus); > + if (ret) { > + dev_err(sdev->dev, "reset err %d\n", ret); > + goto clk_ram_disable; > + } > + > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN); > + > + return 0; > + > +clk_ram_disable: > + clk_disable_unprepare(sdev->clk_ram); > +clk_mod_disable: > + clk_disable_unprepare(sdev->clk_mod); > + return ret; > +} > + > +static enum csi_input_fmt get_csi_input_format(struct sun6i_csi_dev *sdev, > + u32 mbus_code, u32 pixformat) > +{ > + /* bayer */ > + if ((mbus_code & 0xF000) == 0x3000) > + return CSI_INPUT_FORMAT_RAW; > + > + switch (pixformat) { > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_YVYU: > + case V4L2_PIX_FMT_UYVY: > + case V4L2_PIX_FMT_VYUY: > + return CSI_INPUT_FORMAT_RAW; > + default: > + break; > + } > + > + /* not support YUV420 input format yet */ > + dev_dbg(sdev->dev, "Select YUV422 as default input format of CSI.\n"); > + return CSI_INPUT_FORMAT_YUV422; > +} > + > +static enum csi_output_fmt get_csi_output_format(struct sun6i_csi_dev *sdev, > + u32 pixformat, u32 field) > +{ > + bool buf_interlaced = false; > + > + if (field == V4L2_FIELD_INTERLACED > + || field == V4L2_FIELD_INTERLACED_TB > + || field == V4L2_FIELD_INTERLACED_BT) > + buf_interlaced = true; > + > + switch (pixformat) { > + case V4L2_PIX_FMT_SBGGR8: > + case V4L2_PIX_FMT_SGBRG8: > + case V4L2_PIX_FMT_SGRBG8: > + case V4L2_PIX_FMT_SRGGB8: > + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; > + case V4L2_PIX_FMT_SBGGR10: > + case V4L2_PIX_FMT_SGBRG10: > + case V4L2_PIX_FMT_SGRBG10: > + case V4L2_PIX_FMT_SRGGB10: > + return buf_interlaced ? CSI_FRAME_RAW_10 : CSI_FIELD_RAW_10; > + case V4L2_PIX_FMT_SBGGR12: > + case V4L2_PIX_FMT_SGBRG12: > + case V4L2_PIX_FMT_SGRBG12: > + case V4L2_PIX_FMT_SRGGB12: > + return buf_interlaced ? CSI_FRAME_RAW_12 : CSI_FIELD_RAW_12; > + > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_YVYU: > + case V4L2_PIX_FMT_UYVY: > + case V4L2_PIX_FMT_VYUY: > + return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; > + > + case V4L2_PIX_FMT_HM12: > + return buf_interlaced ? CSI_FRAME_MB_YUV420 : > + CSI_FIELD_MB_YUV420; > + case V4L2_PIX_FMT_NV12: > + case V4L2_PIX_FMT_NV21: > + return buf_interlaced ? CSI_FRAME_UV_CB_YUV420 : > + CSI_FIELD_UV_CB_YUV420; > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YVU420: > + return buf_interlaced ? CSI_FRAME_PLANAR_YUV420 : > + CSI_FIELD_PLANAR_YUV420; > + case V4L2_PIX_FMT_NV16: > + case V4L2_PIX_FMT_NV61: > + return buf_interlaced ? CSI_FRAME_UV_CB_YUV422 : > + CSI_FIELD_UV_CB_YUV422; > + case V4L2_PIX_FMT_YUV422P: > + return buf_interlaced ? CSI_FRAME_PLANAR_YUV422 : > + CSI_FIELD_PLANAR_YUV422; > + default: > + dev_warn(sdev->dev, "Unsupported pixformat: 0x%x\n", pixformat); > + break; > + } > + > + return CSI_FIELD_RAW_8; > +} > + > +static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_dev *sdev, > + u32 mbus_code, u32 pixformat) > +{ > + > + switch (pixformat) { > + case V4L2_PIX_FMT_HM12: > + case V4L2_PIX_FMT_NV12: > + case V4L2_PIX_FMT_NV16: > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YUV422P: > + switch (mbus_code) { > + case MEDIA_BUS_FMT_UYVY8_2X8: > + case MEDIA_BUS_FMT_UYVY8_1X16: > + return CSI_INPUT_SEQ_UYVY; > + case MEDIA_BUS_FMT_VYUY8_2X8: > + case MEDIA_BUS_FMT_VYUY8_1X16: > + return CSI_INPUT_SEQ_VYUY; > + case MEDIA_BUS_FMT_YUYV8_2X8: > + case MEDIA_BUS_FMT_YUYV8_1X16: > + return CSI_INPUT_SEQ_YUYV; > + case MEDIA_BUS_FMT_YVYU8_1X16: > + case MEDIA_BUS_FMT_YVYU8_2X8: > + return CSI_INPUT_SEQ_YVYU; > + default: > + dev_warn(sdev->dev, "Unsupported mbus code: 0x%x\n", > + mbus_code); > + break; > + } > + break; > + case V4L2_PIX_FMT_NV21: > + case V4L2_PIX_FMT_NV61: > + case V4L2_PIX_FMT_YVU420: > + switch (mbus_code) { > + case MEDIA_BUS_FMT_UYVY8_2X8: > + case MEDIA_BUS_FMT_UYVY8_1X16: > + return CSI_INPUT_SEQ_VYUY; > + case MEDIA_BUS_FMT_VYUY8_2X8: > + case MEDIA_BUS_FMT_VYUY8_1X16: > + return CSI_INPUT_SEQ_UYVY; > + case MEDIA_BUS_FMT_YUYV8_2X8: > + case MEDIA_BUS_FMT_YUYV8_1X16: > + return CSI_INPUT_SEQ_YVYU; > + case MEDIA_BUS_FMT_YVYU8_1X16: > + case MEDIA_BUS_FMT_YVYU8_2X8: > + return CSI_INPUT_SEQ_YUYV; > + default: > + dev_warn(sdev->dev, "Unsupported mbus code: 0x%x\n", > + mbus_code); > + break; > + } > + break; > + > + case V4L2_PIX_FMT_YUYV: > + return CSI_INPUT_SEQ_YUYV; > + > + default: > + dev_warn(sdev->dev, "Unsupported pixformat: 0x%x, defaulting to YUYV\n", > + pixformat); > + break; > + } > + > + return CSI_INPUT_SEQ_YUYV; > +} > + > +static void sun6i_csi_setup_bus(struct sun6i_csi_dev *sdev) > +{ > + struct v4l2_fwnode_endpoint *endpoint = &sdev->csi.v4l2_ep; > + struct sun6i_csi *csi = &sdev->csi; > + unsigned char bus_width; > + u32 flags; > + u32 cfg; > + bool input_interlaced = false; > + > + if (csi->config.field == V4L2_FIELD_INTERLACED > + || csi->config.field == V4L2_FIELD_INTERLACED_TB > + || csi->config.field == V4L2_FIELD_INTERLACED_BT) > + input_interlaced = true; > + > + bus_width = endpoint->bus.parallel.bus_width; > + > + regmap_read(sdev->regmap, CSI_IF_CFG_REG, &cfg); > + > + cfg &= ~(CSI_IF_CFG_CSI_IF_MASK | CSI_IF_CFG_MIPI_IF_MASK | > + CSI_IF_CFG_IF_DATA_WIDTH_MASK | > + CSI_IF_CFG_CLK_POL_MASK | CSI_IF_CFG_VREF_POL_MASK | > + CSI_IF_CFG_HREF_POL_MASK | CSI_IF_CFG_FIELD_MASK | > + CSI_IF_CFG_SRC_TYPE_MASK); > + > + if (input_interlaced) > + cfg |= CSI_IF_CFG_SRC_TYPE_INTERLACED; > + else > + cfg |= CSI_IF_CFG_SRC_TYPE_PROGRESSED; > + > + switch (endpoint->bus_type) { > + case V4L2_MBUS_PARALLEL: > + cfg |= CSI_IF_CFG_MIPI_IF_CSI; > + > + flags = endpoint->bus.parallel.flags; > + > + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_YUV422_16BIT : > + CSI_IF_CFG_CSI_IF_YUV422_INTLV; > + > + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) > + cfg |= CSI_IF_CFG_FIELD_POSITIVE; > + > + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) > + cfg |= CSI_IF_CFG_VREF_POL_POSITIVE; > + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) > + cfg |= CSI_IF_CFG_HREF_POL_POSITIVE; > + > + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) > + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; > + break; > + case V4L2_MBUS_BT656: > + cfg |= CSI_IF_CFG_MIPI_IF_CSI; > + > + flags = endpoint->bus.parallel.flags; > + > + cfg |= (bus_width == 16) ? CSI_IF_CFG_CSI_IF_BT1120 : > + CSI_IF_CFG_CSI_IF_BT656; > + > + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) > + cfg |= CSI_IF_CFG_FIELD_POSITIVE; > + > + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) > + cfg |= CSI_IF_CFG_CLK_POL_FALLING_EDGE; > + break; > + default: > + dev_warn(sdev->dev, "Unsupported bus type: %d\n", > + endpoint->bus_type); > + break; > + } > + > + switch (bus_width) { > + case 8: > + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_8BIT; > + break; > + case 10: > + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_10BIT; > + break; > + case 12: > + cfg |= CSI_IF_CFG_IF_DATA_WIDTH_12BIT; > + break; > + case 16: /* No need to configure DATA_WIDTH for 16bit */ > + break; > + default: > + dev_warn(sdev->dev, "Unsupported bus width: %d\n", bus_width); %u > + break; > + } > + > + regmap_write(sdev->regmap, CSI_IF_CFG_REG, cfg); > +} > + > +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(sdev, csi->config.code, > + csi->config.pixelformat); > + cfg |= CSI_CH_CFG_INPUT_FMT(val); > + > + val = get_csi_output_format(sdev, csi->config.pixelformat, > + csi->config.field); > + cfg |= CSI_CH_CFG_OUTPUT_FMT(val); > + > + val = get_csi_input_seq(sdev, 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; > + u32 width = config->width; > + u32 height = config->height; > + u32 hor_len = width; > + > + switch (config->pixelformat) { > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_YVYU: > + case V4L2_PIX_FMT_UYVY: > + case V4L2_PIX_FMT_VYUY: > + dev_dbg(sdev->dev, > + "Horizontal length should be 2 times of width for packed YUV formats!\n"); > + hor_len = width * 2; > + break; > + default: > + break; > + } > + > + regmap_write(sdev->regmap, CSI_CH_HSIZE_REG, > + CSI_CH_HSIZE_HOR_LEN(hor_len) | > + CSI_CH_HSIZE_HOR_START(0)); > + regmap_write(sdev->regmap, CSI_CH_VSIZE_REG, > + CSI_CH_VSIZE_VER_LEN(height) | > + CSI_CH_VSIZE_VER_START(0)); > + > + 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 = width; > + bytesperline_c = width; > + planar_offset[1] = bytesperline_y * height; > + planar_offset[2] = -1; > + break; > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YVU420: > + bytesperline_y = width; > + bytesperline_c = width / 2; > + planar_offset[1] = bytesperline_y * height; > + planar_offset[2] = planar_offset[1] + > + bytesperline_c * height / 2; > + break; > + case V4L2_PIX_FMT_YUV422P: > + bytesperline_y = width; > + bytesperline_c = width / 2; > + planar_offset[1] = bytesperline_y * height; > + planar_offset[2] = planar_offset[1] + > + bytesperline_c * height; > + break; > + default: /* raw */ > + dev_dbg(sdev->dev, > + "Calculating pixelformat(0x%x)'s bytesperline as a packed format\n", > + config->pixelformat); > + bytesperline_y = (sun6i_csi_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)); > +} > + > +int sun6i_csi_update_config(struct sun6i_csi *csi, > + struct sun6i_csi_config *config) > +{ > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); > + > + if (config == NULL) > + return -EINVAL; > + > + memcpy(&csi->config, config, sizeof(csi->config)); > + > + sun6i_csi_setup_bus(sdev); > + sun6i_csi_set_format(sdev); > + sun6i_csi_set_window(sdev); > + > + return 0; > +} > + > +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr) > +{ > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); > + > + regmap_write(sdev->regmap, CSI_CH_F0_BUFA_REG, > + (addr + sdev->planar_offset[0]) >> 2); > + if (sdev->planar_offset[1] != -1) > + regmap_write(sdev->regmap, CSI_CH_F1_BUFA_REG, > + (addr + sdev->planar_offset[1]) >> 2); > + if (sdev->planar_offset[2] != -1) > + regmap_write(sdev->regmap, CSI_CH_F2_BUFA_REG, > + (addr + sdev->planar_offset[2]) >> 2); > +} > + > +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable) > +{ > + struct sun6i_csi_dev *sdev = sun6i_csi_to_dev(csi); > + struct regmap *regmap = sdev->regmap; > + > + if (!enable) { > + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, 0); > + regmap_write(regmap, CSI_CH_INT_EN_REG, 0); > + return; > + } > + > + regmap_write(regmap, CSI_CH_INT_STA_REG, 0xFF); > + regmap_write(regmap, CSI_CH_INT_EN_REG, > + CSI_CH_INT_EN_HB_OF_INT_EN | > + CSI_CH_INT_EN_FIFO2_OF_INT_EN | > + CSI_CH_INT_EN_FIFO1_OF_INT_EN | > + CSI_CH_INT_EN_FIFO0_OF_INT_EN | > + CSI_CH_INT_EN_FD_INT_EN | > + CSI_CH_INT_EN_CD_INT_EN); > + > + regmap_update_bits(regmap, CSI_CAP_REG, CSI_CAP_CH0_VCAP_ON, > + CSI_CAP_CH0_VCAP_ON); > +} > + > +/* ----------------------------------------------------------------------------- > + * Media Controller and V4L2 > + */ > +static int sun6i_csi_link_entity(struct sun6i_csi *csi, > + struct media_entity *entity, > + struct fwnode_handle *fwnode) > +{ > + struct media_entity *sink; > + struct media_pad *sink_pad; > + int src_pad_index; > + int ret; > + > + ret = media_entity_get_fwnode_pad(entity, fwnode, MEDIA_PAD_FL_SOURCE); > + if (ret < 0) { > + dev_err(csi->dev, "%s: no source pad in external entity %s\n", > + __func__, entity->name); > + return -EINVAL; > + } > + > + src_pad_index = ret; > + > + sink = &csi->video.vdev.entity; > + sink_pad = &csi->video.pad; > + > + dev_dbg(csi->dev, "creating %s:%u -> %s:%u link\n", > + entity->name, src_pad_index, sink->name, sink_pad->index); > + ret = media_create_pad_link(entity, src_pad_index, sink, > + sink_pad->index, > + MEDIA_LNK_FL_ENABLED | > + MEDIA_LNK_FL_IMMUTABLE); > + if (ret < 0) { > + dev_err(csi->dev, "failed to create %s:%u -> %s:%u link\n", > + entity->name, src_pad_index, > + sink->name, sink_pad->index); > + return ret; > + } > + > + return 0; > +} > + > +static int sun6i_subdev_notify_complete(struct v4l2_async_notifier *notifier) > +{ > + struct sun6i_csi *csi = container_of(notifier, struct sun6i_csi, > + notifier); > + struct v4l2_device *v4l2_dev = &csi->v4l2_dev; > + struct v4l2_subdev *sd; > + int ret; > + > + dev_dbg(csi->dev, "notify complete, all subdevs registered\n"); > + > + if (notifier->num_subdevs != 1) > + return -EINVAL; > + > + sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list); > + if (sd == NULL) > + return -EINVAL; > + > + ret = sun6i_csi_link_entity(csi, &sd->entity, sd->fwnode); > + if (ret < 0) > + return ret; > + > + ret = v4l2_device_register_subdev_nodes(&csi->v4l2_dev); > + if (ret < 0) > + return ret; > + > + return media_device_register(&csi->media_dev); > +} > + > +static const struct v4l2_async_notifier_operations sun6i_csi_async_ops = { > + .complete = sun6i_subdev_notify_complete, > +}; > + > +static int sun6i_csi_fwnode_parse(struct device *dev, > + struct v4l2_fwnode_endpoint *vep, > + struct v4l2_async_subdev *asd) > +{ > + struct sun6i_csi *csi = dev_get_drvdata(dev); > + > + if (vep->base.port || vep->base.id) { > + dev_warn(dev, "Only support a single port with one endpoint\n"); > + return -ENOTCONN; > + } > + > + switch (vep->bus_type) { > + case V4L2_MBUS_PARALLEL: > + case V4L2_MBUS_BT656: > + csi->v4l2_ep = *vep; > + return 0; > + default: > + dev_err(dev, "Unsupported media bus type\n"); > + return -ENOTCONN; > + } > +} > + > +static void sun6i_csi_v4l2_cleanup(struct sun6i_csi *csi) > +{ > + v4l2_async_notifier_unregister(&csi->notifier); > + v4l2_async_notifier_cleanup(&csi->notifier); > + sun6i_video_cleanup(&csi->video); > + v4l2_device_unregister(&csi->v4l2_dev); > + media_device_unregister(&csi->media_dev); Please unregister the media device as first. You're missing freeing the control handler. > + media_device_cleanup(&csi->media_dev); > +} > + > +static int sun6i_csi_v4l2_init(struct sun6i_csi *csi) > +{ > + int ret; > + > + csi->media_dev.dev = csi->dev; > + strlcpy(csi->media_dev.model, "Allwinner Video Capture Device", > + sizeof(csi->media_dev.model)); strscpy, please. > + csi->media_dev.hw_revision = 0; > + > + media_device_init(&csi->media_dev); > + > + ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 0); Do you need controls? The driver doesn't appear to register any. > + if (ret) { > + dev_err(csi->dev, "V4L2 controls handler init failed (%d)\n", > + ret); > + goto clean_media; > + } > + > + csi->v4l2_dev.mdev = &csi->media_dev; > + csi->v4l2_dev.ctrl_handler = &csi->ctrl_handler; > + ret = v4l2_device_register(csi->dev, &csi->v4l2_dev); > + if (ret) { > + dev_err(csi->dev, "V4L2 device registration failed (%d)\n", > + ret); > + goto clean_media; If you think you need controls --- the control handler needs to be freed hence in error handling; you need a new label. > + } > + > + ret = sun6i_video_init(&csi->video, csi, "sun6i-csi"); > + if (ret) > + goto unreg_v4l2; > + > + ret = v4l2_async_notifier_parse_fwnode_endpoints( > + csi->dev, &csi->notifier, sizeof(struct v4l2_async_subdev), > + sun6i_csi_fwnode_parse); > + if (ret) > + goto clean_video; > + > + csi->notifier.ops = &sun6i_csi_async_ops; > + > + ret = v4l2_async_notifier_register(&csi->v4l2_dev, &csi->notifier); > + if (ret) { > + dev_err(csi->dev, "notifier registration failed\n"); > + goto clean_notifier; > + } > + > + return 0; > + > +clean_notifier: > + v4l2_async_notifier_cleanup(&csi->notifier); > +clean_video: > + sun6i_video_cleanup(&csi->video); > +unreg_v4l2: > + v4l2_device_unregister(&csi->v4l2_dev); > +clean_media: > + media_device_cleanup(&csi->media_dev); > + > + return ret; > +} > + > +/* ----------------------------------------------------------------------------- > + * Resources and IRQ > + */ > +static irqreturn_t sun6i_csi_isr(int irq, void *dev_id) > +{ > + struct sun6i_csi_dev *sdev = (struct sun6i_csi_dev *)dev_id; > + struct regmap *regmap = sdev->regmap; > + u32 status; > + > + regmap_read(regmap, CSI_CH_INT_STA_REG, &status); > + > + if (!(status & 0xFF)) > + return IRQ_NONE; > + > + if ((status & CSI_CH_INT_STA_FIFO0_OF_PD) || > + (status & CSI_CH_INT_STA_FIFO1_OF_PD) || > + (status & CSI_CH_INT_STA_FIFO2_OF_PD) || > + (status & CSI_CH_INT_STA_HB_OF_PD)) { > + regmap_write(regmap, CSI_CH_INT_STA_REG, status); > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); > + regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, > + CSI_EN_CSI_EN); > + return IRQ_HANDLED; > + } > + > + if (status & CSI_CH_INT_STA_FD_PD) > + sun6i_video_frame_done(&sdev->csi.video); > + > + regmap_write(regmap, CSI_CH_INT_STA_REG, status); > + > + return IRQ_HANDLED; > +} > + > +static const struct regmap_config sun6i_csi_regmap_config = { > + .reg_bits = 32, > + .reg_stride = 4, > + .val_bits = 32, > + .max_register = 0x1000, > +}; > + > +static int sun6i_csi_resource_request(struct sun6i_csi_dev *sdev, > + struct platform_device *pdev) > +{ > + struct resource *res; > + void __iomem *io_base; > + int ret; > + int irq; > + > + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); > + io_base = devm_ioremap_resource(&pdev->dev, res); > + if (IS_ERR(io_base)) > + return PTR_ERR(io_base); > + > + sdev->regmap = devm_regmap_init_mmio_clk(&pdev->dev, "bus", io_base, > + &sun6i_csi_regmap_config); > + if (IS_ERR(sdev->regmap)) { > + dev_err(&pdev->dev, "Failed to init register map\n"); > + return PTR_ERR(sdev->regmap); > + } > + > + sdev->clk_mod = devm_clk_get(&pdev->dev, "mod"); > + if (IS_ERR(sdev->clk_mod)) { > + dev_err(&pdev->dev, "Unable to acquire csi clock\n"); > + return PTR_ERR(sdev->clk_mod); > + } > + > + sdev->clk_ram = devm_clk_get(&pdev->dev, "ram"); > + if (IS_ERR(sdev->clk_ram)) { > + dev_err(&pdev->dev, "Unable to acquire dram-csi clock\n"); > + return PTR_ERR(sdev->clk_ram); > + } > + > + sdev->rstc_bus = devm_reset_control_get_shared(&pdev->dev, NULL); > + if (IS_ERR(sdev->rstc_bus)) { > + dev_err(&pdev->dev, "Cannot get reset controller\n"); > + return PTR_ERR(sdev->rstc_bus); > + } > + > + irq = platform_get_irq(pdev, 0); > + if (irq < 0) { > + dev_err(&pdev->dev, "No csi IRQ specified\n"); > + ret = -ENXIO; > + return ret; > + } > + > + ret = devm_request_irq(&pdev->dev, irq, sun6i_csi_isr, 0, MODULE_NAME, > + sdev); > + if (ret) { > + dev_err(&pdev->dev, "Cannot request csi IRQ\n"); > + return ret; > + } A newline would be nice here. > + return 0; > +} > + > +/* > + * PHYS_OFFSET isn't available on all architectures. In order to > + * accomodate for COMPILE_TEST, let's define it to something dumb. > + */ > +#ifndef PHYS_OFFSET How about: #if !defined(CONFIG_COMPILE_TEST) && !defined(PHYS_OFFSET) Or something alike. That avoids things going horribly wrong if PHYS_OFFSET is undefined for a different reason. > +#define PHYS_OFFSET 0 > +#endif > + > +static int sun6i_csi_probe(struct platform_device *pdev) > +{ > + struct sun6i_csi_dev *sdev; > + int ret; > + > + sdev = devm_kzalloc(&pdev->dev, sizeof(*sdev), GFP_KERNEL); > + if (!sdev) > + return -ENOMEM; > + > + sdev->dev = &pdev->dev; > + /* The DMA bus has the memory mapped at 0 */ > + sdev->dev->dma_pfn_offset = PHYS_OFFSET >> PAGE_SHIFT; > + > + ret = sun6i_csi_resource_request(sdev, pdev); > + if (ret) > + return ret; > + > + platform_set_drvdata(pdev, sdev); > + > + sdev->csi.dev = &pdev->dev; > + ret = sun6i_csi_v4l2_init(&sdev->csi); return sun6...(); > + if (ret) > + return ret; > + > + return 0; > +} > + > +static int sun6i_csi_remove(struct platform_device *pdev) > +{ > + struct sun6i_csi_dev *sdev = platform_get_drvdata(pdev); > + > + sun6i_csi_v4l2_cleanup(&sdev->csi); > + > + return 0; > +} > + > +static const struct of_device_id sun6i_csi_of_match[] = { > + { .compatible = "allwinner,sun8i-v3s-csi", }, > + {}, > +}; > +MODULE_DEVICE_TABLE(of, sun6i_csi_of_match); > + > +static struct platform_driver sun6i_csi_platform_driver = { > + .probe = sun6i_csi_probe, > + .remove = sun6i_csi_remove, > + .driver = { > + .name = MODULE_NAME, > + .of_match_table = of_match_ptr(sun6i_csi_of_match), > + }, > +}; > +module_platform_driver(sun6i_csi_platform_driver); > + > +MODULE_DESCRIPTION("Allwinner V3s Camera Sensor Interface driver"); > +MODULE_AUTHOR("Yong Deng <yong.deng@xxxxxxxxxxxx>"); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h > new file mode 100644 > index 000000000000..bd9be36aabfe > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h > @@ -0,0 +1,135 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) > + * All rights reserved. > + * Author: Yong Deng <yong.deng@xxxxxxxxxxxx> > + */ > + > +#ifndef __SUN6I_CSI_H__ > +#define __SUN6I_CSI_H__ > + > +#include <media/v4l2-ctrls.h> > +#include <media/v4l2-device.h> > +#include <media/v4l2-fwnode.h> > + > +#include "sun6i_video.h" > + > +struct sun6i_csi; > + > +/** > + * struct sun6i_csi_config - configs for sun6i csi > + * @pixelformat: v4l2 pixel format (V4L2_PIX_FMT_*) > + * @code: media bus format code (MEDIA_BUS_FMT_*) > + * @field: used interlacing type (enum v4l2_field) > + * @width: frame width > + * @height: frame height > + */ > +struct sun6i_csi_config { > + u32 pixelformat; > + u32 code; > + u32 field; > + u32 width; > + u32 height; > +}; > + > +struct sun6i_csi { > + struct device *dev; > + struct v4l2_ctrl_handler ctrl_handler; > + struct v4l2_device v4l2_dev; > + struct media_device media_dev; > + > + struct v4l2_async_notifier notifier; > + > + /* video port settings */ > + struct v4l2_fwnode_endpoint v4l2_ep; > + > + struct sun6i_csi_config config; > + > + struct sun6i_video video; > +}; > + > +/** > + * sun6i_csi_is_format_supported() - check if the format supported by csi > + * @csi: pointer to the csi > + * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*) > + * @mbus_code: media bus format code (MEDIA_BUS_FMT_*) > + */ > +bool sun6i_csi_is_format_supported(struct sun6i_csi *csi, u32 pixformat, > + u32 mbus_code); > + > +/** > + * sun6i_csi_set_power() - power on/off the csi > + * @csi: pointer to the csi > + * @enable: on/off > + */ > +int sun6i_csi_set_power(struct sun6i_csi *csi, bool enable); > + > +/** > + * sun6i_csi_update_config() - update the csi register setttings > + * @csi: pointer to the csi > + * @config: see struct sun6i_csi_config > + */ > +int sun6i_csi_update_config(struct sun6i_csi *csi, > + struct sun6i_csi_config *config); > + > +/** > + * sun6i_csi_update_buf_addr() - update the csi frame buffer address > + * @csi: pointer to the csi > + * @addr: frame buffer's physical address > + */ > +void sun6i_csi_update_buf_addr(struct sun6i_csi *csi, dma_addr_t addr); > + > +/** > + * sun6i_csi_set_stream() - start/stop csi streaming > + * @csi: pointer to the csi > + * @enable: start/stop > + */ > +void sun6i_csi_set_stream(struct sun6i_csi *csi, bool enable); > + > +/* get bpp form v4l2 pixformat */ > +static inline int sun6i_csi_get_bpp(unsigned int pixformat) > +{ > + switch (pixformat) { > + case V4L2_PIX_FMT_SBGGR8: > + case V4L2_PIX_FMT_SGBRG8: > + case V4L2_PIX_FMT_SGRBG8: > + case V4L2_PIX_FMT_SRGGB8: > + return 8; > + case V4L2_PIX_FMT_SBGGR10: > + case V4L2_PIX_FMT_SGBRG10: > + case V4L2_PIX_FMT_SGRBG10: > + case V4L2_PIX_FMT_SRGGB10: > + return 10; > + case V4L2_PIX_FMT_SBGGR12: > + case V4L2_PIX_FMT_SGBRG12: > + case V4L2_PIX_FMT_SGRBG12: > + case V4L2_PIX_FMT_SRGGB12: > + case V4L2_PIX_FMT_HM12: > + case V4L2_PIX_FMT_NV12: > + case V4L2_PIX_FMT_NV21: > + case V4L2_PIX_FMT_YUV420: > + case V4L2_PIX_FMT_YVU420: > + return 12; > + case V4L2_PIX_FMT_YUYV: > + case V4L2_PIX_FMT_YVYU: > + case V4L2_PIX_FMT_UYVY: > + case V4L2_PIX_FMT_VYUY: > + case V4L2_PIX_FMT_NV16: > + case V4L2_PIX_FMT_NV61: > + case V4L2_PIX_FMT_YUV422P: > + return 16; > + case V4L2_PIX_FMT_RGB24: > + case V4L2_PIX_FMT_BGR24: > + return 24; > + case V4L2_PIX_FMT_RGB32: > + case V4L2_PIX_FMT_BGR32: > + return 32; > + default: > + WARN(1, "Unsupported pixformat: 0x%x\n", pixformat); > + break; > + } > + > + return 0; > +} > + > +#endif /* __SUN6I_CSI_H__ */ > diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h > new file mode 100644 > index 000000000000..3a55836a5a4d > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h > @@ -0,0 +1,196 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) > + * All rights reserved. > + * Author: Yong Deng <yong.deng@xxxxxxxxxxxx> > + */ > + > +#ifndef __SUN6I_CSI_REG_H__ > +#define __SUN6I_CSI_REG_H__ > + > +#include <linux/kernel.h> > + > +#define CSI_EN_REG 0x0 > +#define CSI_EN_VER_EN BIT(30) > +#define CSI_EN_CSI_EN BIT(0) > + > +#define CSI_IF_CFG_REG 0x4 > +#define CSI_IF_CFG_SRC_TYPE_MASK BIT(21) > +#define CSI_IF_CFG_SRC_TYPE_PROGRESSED ((0 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) > +#define CSI_IF_CFG_SRC_TYPE_INTERLACED ((1 << 21) & CSI_IF_CFG_SRC_TYPE_MASK) > +#define CSI_IF_CFG_FPS_DS_EN BIT(20) > +#define CSI_IF_CFG_FIELD_MASK BIT(19) > +#define CSI_IF_CFG_FIELD_NEGATIVE ((0 << 19) & CSI_IF_CFG_FIELD_MASK) > +#define CSI_IF_CFG_FIELD_POSITIVE ((1 << 19) & CSI_IF_CFG_FIELD_MASK) > +#define CSI_IF_CFG_VREF_POL_MASK BIT(18) > +#define CSI_IF_CFG_VREF_POL_NEGATIVE ((0 << 18) & CSI_IF_CFG_VREF_POL_MASK) > +#define CSI_IF_CFG_VREF_POL_POSITIVE ((1 << 18) & CSI_IF_CFG_VREF_POL_MASK) > +#define CSI_IF_CFG_HREF_POL_MASK BIT(17) > +#define CSI_IF_CFG_HREF_POL_NEGATIVE ((0 << 17) & CSI_IF_CFG_HREF_POL_MASK) > +#define CSI_IF_CFG_HREF_POL_POSITIVE ((1 << 17) & CSI_IF_CFG_HREF_POL_MASK) > +#define CSI_IF_CFG_CLK_POL_MASK BIT(16) > +#define CSI_IF_CFG_CLK_POL_RISING_EDGE ((0 << 16) & CSI_IF_CFG_CLK_POL_MASK) > +#define CSI_IF_CFG_CLK_POL_FALLING_EDGE ((1 << 16) & CSI_IF_CFG_CLK_POL_MASK) > +#define CSI_IF_CFG_IF_DATA_WIDTH_MASK GENMASK(10, 8) > +#define CSI_IF_CFG_IF_DATA_WIDTH_8BIT ((0 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) > +#define CSI_IF_CFG_IF_DATA_WIDTH_10BIT ((1 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) > +#define CSI_IF_CFG_IF_DATA_WIDTH_12BIT ((2 << 8) & CSI_IF_CFG_IF_DATA_WIDTH_MASK) > +#define CSI_IF_CFG_MIPI_IF_MASK BIT(7) > +#define CSI_IF_CFG_MIPI_IF_CSI (0 << 7) > +#define CSI_IF_CFG_MIPI_IF_MIPI (1 << 7) > +#define CSI_IF_CFG_CSI_IF_MASK GENMASK(4, 0) > +#define CSI_IF_CFG_CSI_IF_YUV422_INTLV ((0 << 0) & CSI_IF_CFG_CSI_IF_MASK) > +#define CSI_IF_CFG_CSI_IF_YUV422_16BIT ((1 << 0) & CSI_IF_CFG_CSI_IF_MASK) > +#define CSI_IF_CFG_CSI_IF_BT656 ((4 << 0) & CSI_IF_CFG_CSI_IF_MASK) > +#define CSI_IF_CFG_CSI_IF_BT1120 ((5 << 0) & CSI_IF_CFG_CSI_IF_MASK) > + > +#define CSI_CAP_REG 0x8 > +#define CSI_CAP_CH0_CAP_MASK_MASK GENMASK(5, 2) > +#define CSI_CAP_CH0_CAP_MASK(count) ((count << 2) & CSI_CAP_CH0_CAP_MASK_MASK) > +#define CSI_CAP_CH0_VCAP_ON BIT(1) > +#define CSI_CAP_CH0_SCAP_ON BIT(0) > + > +#define CSI_SYNC_CNT_REG 0xc > +#define CSI_FIFO_THRS_REG 0x10 > +#define CSI_BT656_HEAD_CFG_REG 0x14 > +#define CSI_PTN_LEN_REG 0x30 > +#define CSI_PTN_ADDR_REG 0x34 > +#define CSI_VER_REG 0x3c > + > +#define CSI_CH_CFG_REG 0x44 > +#define CSI_CH_CFG_INPUT_FMT_MASK GENMASK(23, 20) > +#define CSI_CH_CFG_INPUT_FMT(fmt) ((fmt << 20) & CSI_CH_CFG_INPUT_FMT_MASK) > +#define CSI_CH_CFG_OUTPUT_FMT_MASK GENMASK(19, 16) > +#define CSI_CH_CFG_OUTPUT_FMT(fmt) ((fmt << 16) & CSI_CH_CFG_OUTPUT_FMT_MASK) > +#define CSI_CH_CFG_VFLIP_EN BIT(13) > +#define CSI_CH_CFG_HFLIP_EN BIT(12) > +#define CSI_CH_CFG_FIELD_SEL_MASK GENMASK(11, 10) > +#define CSI_CH_CFG_FIELD_SEL_FIELD0 ((0 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) > +#define CSI_CH_CFG_FIELD_SEL_FIELD1 ((1 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) > +#define CSI_CH_CFG_FIELD_SEL_BOTH ((2 << 10) & CSI_CH_CFG_FIELD_SEL_MASK) > +#define CSI_CH_CFG_INPUT_SEQ_MASK GENMASK(9, 8) > +#define CSI_CH_CFG_INPUT_SEQ(seq) ((seq << 8) & CSI_CH_CFG_INPUT_SEQ_MASK) > + > +#define CSI_CH_SCALE_REG 0x4c > +#define CSI_CH_SCALE_QUART_EN BIT(0) > + > +#define CSI_CH_F0_BUFA_REG 0x50 > + > +#define CSI_CH_F1_BUFA_REG 0x58 > + > +#define CSI_CH_F2_BUFA_REG 0x60 > + > +#define CSI_CH_STA_REG 0x6c > +#define CSI_CH_STA_FIELD_STA_MASK BIT(2) > +#define CSI_CH_STA_FIELD_STA_FIELD0 ((0 << 2) & CSI_CH_STA_FIELD_STA_MASK) > +#define CSI_CH_STA_FIELD_STA_FIELD1 ((1 << 2) & CSI_CH_STA_FIELD_STA_MASK) > +#define CSI_CH_STA_VCAP_STA BIT(1) > +#define CSI_CH_STA_SCAP_STA BIT(0) > + > +#define CSI_CH_INT_EN_REG 0x70 > +#define CSI_CH_INT_EN_VS_INT_EN BIT(7) > +#define CSI_CH_INT_EN_HB_OF_INT_EN BIT(6) > +#define CSI_CH_INT_EN_MUL_ERR_INT_EN BIT(5) > +#define CSI_CH_INT_EN_FIFO2_OF_INT_EN BIT(4) > +#define CSI_CH_INT_EN_FIFO1_OF_INT_EN BIT(3) > +#define CSI_CH_INT_EN_FIFO0_OF_INT_EN BIT(2) > +#define CSI_CH_INT_EN_FD_INT_EN BIT(1) > +#define CSI_CH_INT_EN_CD_INT_EN BIT(0) > + > +#define CSI_CH_INT_STA_REG 0x74 > +#define CSI_CH_INT_STA_VS_PD BIT(7) > +#define CSI_CH_INT_STA_HB_OF_PD BIT(6) > +#define CSI_CH_INT_STA_MUL_ERR_PD BIT(5) > +#define CSI_CH_INT_STA_FIFO2_OF_PD BIT(4) > +#define CSI_CH_INT_STA_FIFO1_OF_PD BIT(3) > +#define CSI_CH_INT_STA_FIFO0_OF_PD BIT(2) > +#define CSI_CH_INT_STA_FD_PD BIT(1) > +#define CSI_CH_INT_STA_CD_PD BIT(0) > + > +#define CSI_CH_FLD1_VSIZE_REG 0x78 > + > +#define CSI_CH_HSIZE_REG 0x80 > +#define CSI_CH_HSIZE_HOR_LEN_MASK GENMASK(28, 16) > +#define CSI_CH_HSIZE_HOR_LEN(len) ((len << 16) & CSI_CH_HSIZE_HOR_LEN_MASK) > +#define CSI_CH_HSIZE_HOR_START_MASK GENMASK(12, 0) > +#define CSI_CH_HSIZE_HOR_START(start) ((start << 0) & CSI_CH_HSIZE_HOR_START_MASK) > + > +#define CSI_CH_VSIZE_REG 0x84 > +#define CSI_CH_VSIZE_VER_LEN_MASK GENMASK(28, 16) > +#define CSI_CH_VSIZE_VER_LEN(len) ((len << 16) & CSI_CH_VSIZE_VER_LEN_MASK) > +#define CSI_CH_VSIZE_VER_START_MASK GENMASK(12, 0) > +#define CSI_CH_VSIZE_VER_START(start) ((start << 0) & CSI_CH_VSIZE_VER_START_MASK) > + > +#define CSI_CH_BUF_LEN_REG 0x88 > +#define CSI_CH_BUF_LEN_BUF_LEN_C_MASK GENMASK(29, 16) > +#define CSI_CH_BUF_LEN_BUF_LEN_C(len) ((len << 16) & CSI_CH_BUF_LEN_BUF_LEN_C_MASK) > +#define CSI_CH_BUF_LEN_BUF_LEN_Y_MASK GENMASK(13, 0) > +#define CSI_CH_BUF_LEN_BUF_LEN_Y(len) ((len << 0) & CSI_CH_BUF_LEN_BUF_LEN_Y_MASK) > + > +#define CSI_CH_FLIP_SIZE_REG 0x8c > +#define CSI_CH_FLIP_SIZE_VER_LEN_MASK GENMASK(28, 16) > +#define CSI_CH_FLIP_SIZE_VER_LEN(len) ((len << 16) & CSI_CH_FLIP_SIZE_VER_LEN_MASK) > +#define CSI_CH_FLIP_SIZE_VALID_LEN_MASK GENMASK(12, 0) > +#define CSI_CH_FLIP_SIZE_VALID_LEN(len) ((len << 0) & CSI_CH_FLIP_SIZE_VALID_LEN_MASK) > + > +#define CSI_CH_FRM_CLK_CNT_REG 0x90 > +#define CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 > +#define CSI_CH_FIFO_STAT_REG 0x98 > +#define CSI_CH_PCLK_STAT_REG 0x9c > + > +/* > + * csi input data format > + */ > +enum csi_input_fmt { > + CSI_INPUT_FORMAT_RAW = 0, > + CSI_INPUT_FORMAT_YUV422 = 3, > + CSI_INPUT_FORMAT_YUV420 = 4, > +}; > + > +/* > + * csi output data format > + */ > +enum csi_output_fmt { > + /* only when input format is RAW */ > + CSI_FIELD_RAW_8 = 0, > + CSI_FIELD_RAW_10 = 1, > + CSI_FIELD_RAW_12 = 2, > + CSI_FIELD_RGB565 = 4, > + CSI_FIELD_RGB888 = 5, > + CSI_FIELD_PRGB888 = 6, > + CSI_FRAME_RAW_8 = 8, > + CSI_FRAME_RAW_10 = 9, > + CSI_FRAME_RAW_12 = 10, > + CSI_FRAME_RGB565 = 12, > + CSI_FRAME_RGB888 = 13, > + CSI_FRAME_PRGB888 = 14, > + > + /* only when input format is YUV422 */ > + CSI_FIELD_PLANAR_YUV422 = 0, > + CSI_FIELD_PLANAR_YUV420 = 1, > + CSI_FRAME_PLANAR_YUV420 = 2, > + CSI_FRAME_PLANAR_YUV422 = 3, > + CSI_FIELD_UV_CB_YUV422 = 4, > + CSI_FIELD_UV_CB_YUV420 = 5, > + CSI_FRAME_UV_CB_YUV420 = 6, > + CSI_FRAME_UV_CB_YUV422 = 7, > + CSI_FIELD_MB_YUV422 = 8, > + CSI_FIELD_MB_YUV420 = 9, > + CSI_FRAME_MB_YUV420 = 10, > + CSI_FRAME_MB_YUV422 = 11, > + CSI_FIELD_UV_CB_YUV422_10 = 12, > + CSI_FIELD_UV_CB_YUV420_10 = 13, > +}; > + > +/* > + * csi YUV input data sequence > + */ > +enum csi_input_seq { > + /* only when input format is YUV422 */ > + CSI_INPUT_SEQ_YUYV = 0, > + CSI_INPUT_SEQ_YVYU, > + CSI_INPUT_SEQ_UYVY, > + CSI_INPUT_SEQ_VYUY, > +}; > + > +#endif /* __SUN6I_CSI_REG_H__ */ > diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c > new file mode 100644 > index 000000000000..524dc93814d2 > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c > @@ -0,0 +1,675 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) > + * All rights reserved. > + * Author: Yong Deng <yong.deng@xxxxxxxxxxxx> > + */ > + > +#include <linux/of.h> > + > +#include <media/v4l2-device.h> > +#include <media/v4l2-event.h> > +#include <media/v4l2-ioctl.h> > +#include <media/v4l2-mc.h> > +#include <media/videobuf2-dma-contig.h> > +#include <media/videobuf2-v4l2.h> > + > +#include "sun6i_csi.h" > +#include "sun6i_video.h" > + > +/* This is got from BSP sources. */ > +#define MIN_WIDTH (32) > +#define MIN_HEIGHT (32) > +#define MAX_WIDTH (4800) > +#define MAX_HEIGHT (4800) > + > +struct sun6i_csi_buffer { > + struct vb2_v4l2_buffer vb; > + struct list_head list; > + > + dma_addr_t dma_addr; > + bool queued_to_csi; > +}; > + > +static const u32 supported_pixformats[] = { > + V4L2_PIX_FMT_SBGGR8, > + V4L2_PIX_FMT_SGBRG8, > + V4L2_PIX_FMT_SGRBG8, > + V4L2_PIX_FMT_SRGGB8, > + V4L2_PIX_FMT_SBGGR10, > + V4L2_PIX_FMT_SGBRG10, > + V4L2_PIX_FMT_SGRBG10, > + V4L2_PIX_FMT_SRGGB10, > + V4L2_PIX_FMT_SBGGR12, > + V4L2_PIX_FMT_SGBRG12, > + V4L2_PIX_FMT_SGRBG12, > + V4L2_PIX_FMT_SRGGB12, > + V4L2_PIX_FMT_YUYV, > + V4L2_PIX_FMT_YVYU, > + V4L2_PIX_FMT_UYVY, > + V4L2_PIX_FMT_VYUY, > + V4L2_PIX_FMT_HM12, > + V4L2_PIX_FMT_NV12, > + V4L2_PIX_FMT_NV21, > + V4L2_PIX_FMT_YUV420, > + V4L2_PIX_FMT_YVU420, > + V4L2_PIX_FMT_NV16, > + V4L2_PIX_FMT_NV61, > + V4L2_PIX_FMT_YUV422P, > +}; > + > +static bool > +is_pixformat_valid(unsigned int pixformat) Fits on a single line. > +{ > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(supported_pixformats); i++) > + if (supported_pixformats[i] == pixformat) > + return true; > + > + return false; > +} > + > +static struct v4l2_subdev * > +sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) > +{ > + struct media_pad *remote; > + > + remote = media_entity_remote_pad(&video->pad); > + > + if (!remote || !is_media_entity_v4l2_subdev(remote->entity)) > + return NULL; > + > + if (pad) > + *pad = remote->index; > + > + return media_entity_to_v4l2_subdev(remote->entity); > +} > + > +static int sun6i_video_queue_setup(struct vb2_queue *vq, > + unsigned int *nbuffers, unsigned int *nplanes, > + unsigned int sizes[], > + struct device *alloc_devs[]) > +{ > + struct sun6i_video *video = vb2_get_drv_priv(vq); > + unsigned int size = video->fmt.fmt.pix.sizeimage; > + > + if (*nplanes) > + return sizes[0] < size ? -EINVAL : 0; > + > + *nplanes = 1; > + sizes[0] = size; > + > + return 0; > +} > + > +static int sun6i_video_buffer_prepare(struct vb2_buffer *vb) > +{ > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > + struct sun6i_csi_buffer *buf = > + container_of(vbuf, struct sun6i_csi_buffer, vb); > + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue); > + unsigned long size = video->fmt.fmt.pix.sizeimage; > + > + if (vb2_plane_size(vb, 0) < size) { > + v4l2_err(video->vdev.v4l2_dev, "buffer too small (%lu < %lu)\n", > + vb2_plane_size(vb, 0), size); > + return -EINVAL; > + } > + > + vb2_set_plane_payload(vb, 0, size); > + > + buf->dma_addr = vb2_dma_contig_plane_dma_addr(vb, 0); > + > + vbuf->field = video->fmt.fmt.pix.field; > + > + return 0; > +} > + > +static int sun6i_video_start_streaming(struct vb2_queue *vq, unsigned int count) > +{ > + struct sun6i_video *video = vb2_get_drv_priv(vq); > + struct sun6i_csi_buffer *buf; > + struct sun6i_csi_buffer *next_buf; > + struct sun6i_csi_config config; > + struct v4l2_subdev *subdev; > + unsigned long flags; > + int ret; > + > + video->sequence = 0; > + > + ret = media_pipeline_start(&video->vdev.entity, &video->vdev.pipe); > + if (ret < 0) > + goto clear_dma_queue; > + > + if (video->mbus_code == 0) { > + ret = -EINVAL; > + goto stop_media_pipeline; > + } > + > + subdev = sun6i_video_remote_subdev(video, NULL); > + if (!subdev) > + goto stop_media_pipeline; > + > + config.pixelformat = video->fmt.fmt.pix.pixelformat; > + config.code = video->mbus_code; > + config.field = video->fmt.fmt.pix.field; > + config.width = video->fmt.fmt.pix.width; > + config.height = video->fmt.fmt.pix.height; > + > + ret = sun6i_csi_update_config(video->csi, &config); > + if (ret < 0) > + goto stop_media_pipeline; > + > + spin_lock_irqsave(&video->dma_queue_lock, flags); > + > + buf = list_first_entry(&video->dma_queue, > + struct sun6i_csi_buffer, list); > + buf->queued_to_csi = true; > + sun6i_csi_update_buf_addr(video->csi, buf->dma_addr); > + > + sun6i_csi_set_stream(video->csi, true); > + > + /* > + * CSI will lookup the next dma buffer for next frame before the > + * the current frame done IRQ triggered. This is not documented > + * but reported by Ondřej Jirman. > + * The BSP code has workaround for this too. It skip to mark the > + * first buffer as frame done for VB2 and pass the second buffer > + * to CSI in the first frame done ISR call. Then in second frame > + * done ISR call, it mark the first buffer as frame done for VB2 > + * and pass the third buffer to CSI. And so on. The bad thing is > + * that the first buffer will be written twice and the first frame > + * is dropped even the queued buffer is sufficient. > + * So, I make some improvement here. Pass the next buffer to CSI > + * just follow starting the CSI. In this case, the first frame > + * will be stored in first buffer, second frame in second buffer. > + * This method is used to avoid dropping the first frame, it > + * would also drop frame when lacking of queued buffer. > + */ > + next_buf = list_next_entry(buf, list); > + next_buf->queued_to_csi = true; > + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); > + > + spin_unlock_irqrestore(&video->dma_queue_lock, flags); > + > + ret = v4l2_subdev_call(subdev, video, s_stream, 1); > + if (ret < 0) > + goto stop_csi_stream; > + > + return 0; > + > +stop_csi_stream: > + sun6i_csi_set_stream(video->csi, false); > +stop_media_pipeline: > + media_pipeline_stop(&video->vdev.entity); > +clear_dma_queue: > + spin_lock_irqsave(&video->dma_queue_lock, flags); > + list_for_each_entry(buf, &video->dma_queue, list) > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); > + INIT_LIST_HEAD(&video->dma_queue); > + spin_unlock_irqrestore(&video->dma_queue_lock, flags); > + > + return ret; > +} > + > +static void sun6i_video_stop_streaming(struct vb2_queue *vq) > +{ > + struct sun6i_video *video = vb2_get_drv_priv(vq); > + struct v4l2_subdev *subdev; > + unsigned long flags; > + struct sun6i_csi_buffer *buf; > + > + subdev = sun6i_video_remote_subdev(video, NULL); > + if (subdev) > + v4l2_subdev_call(subdev, video, s_stream, 0); > + > + sun6i_csi_set_stream(video->csi, false); > + > + media_pipeline_stop(&video->vdev.entity); > + > + /* Release all active buffers */ > + spin_lock_irqsave(&video->dma_queue_lock, flags); > + list_for_each_entry(buf, &video->dma_queue, list) > + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); > + INIT_LIST_HEAD(&video->dma_queue); > + spin_unlock_irqrestore(&video->dma_queue_lock, flags); > +} > + > +static void sun6i_video_buffer_queue(struct vb2_buffer *vb) > +{ > + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); > + struct sun6i_csi_buffer *buf = > + container_of(vbuf, struct sun6i_csi_buffer, vb); > + struct sun6i_video *video = vb2_get_drv_priv(vb->vb2_queue); > + unsigned long flags; > + > + spin_lock_irqsave(&video->dma_queue_lock, flags); > + buf->queued_to_csi = false; > + list_add_tail(&buf->list, &video->dma_queue); > + spin_unlock_irqrestore(&video->dma_queue_lock, flags); > +} > + > +void sun6i_video_frame_done(struct sun6i_video *video) > +{ > + struct sun6i_csi_buffer *buf; > + struct sun6i_csi_buffer *next_buf; > + struct vb2_v4l2_buffer *vbuf; > + > + spin_lock(&video->dma_queue_lock); > + > + buf = list_first_entry(&video->dma_queue, > + struct sun6i_csi_buffer, list); > + if (list_is_last(&buf->list, &video->dma_queue)) { > + dev_dbg(video->csi->dev, "Frame droped!\n"); > + goto unlock; > + } > + > + next_buf = list_next_entry(buf, list); > + /* If a new buffer (#next_buf) had not been queued to CSI, the old > + * buffer (#buf) is still holding by CSI for storing the next > + * frame. So, we queue a new buffer (#next_buf) to CSI then wait > + * for next ISR call. > + */ > + if (!next_buf->queued_to_csi) { > + next_buf->queued_to_csi = true; > + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); > + dev_dbg(video->csi->dev, "Frame droped!\n"); > + goto unlock; > + } > + > + list_del(&buf->list); > + vbuf = &buf->vb; > + vbuf->vb2_buf.timestamp = ktime_get_ns(); > + vbuf->sequence = video->sequence; > + vb2_buffer_done(&vbuf->vb2_buf, VB2_BUF_STATE_DONE); > + > + /* Prepare buffer for next frame but one. */ > + if (!list_is_last(&next_buf->list, &video->dma_queue)) { > + next_buf = list_next_entry(next_buf, list); > + next_buf->queued_to_csi = true; > + sun6i_csi_update_buf_addr(video->csi, next_buf->dma_addr); > + } else { > + dev_dbg(video->csi->dev, "Next frame will be dropped!\n"); > + } > + > +unlock: > + video->sequence++; > + spin_unlock(&video->dma_queue_lock); > +} > + > +static const struct vb2_ops sun6i_csi_vb2_ops = { > + .queue_setup = sun6i_video_queue_setup, > + .wait_prepare = vb2_ops_wait_prepare, > + .wait_finish = vb2_ops_wait_finish, > + .buf_prepare = sun6i_video_buffer_prepare, > + .start_streaming = sun6i_video_start_streaming, > + .stop_streaming = sun6i_video_stop_streaming, > + .buf_queue = sun6i_video_buffer_queue, > +}; > + > +static int vidioc_querycap(struct file *file, void *priv, > + struct v4l2_capability *cap) > +{ > + struct sun6i_video *video = video_drvdata(file); > + > + strlcpy(cap->driver, "sun6i-video", sizeof(cap->driver)); > + strlcpy(cap->card, video->vdev.name, sizeof(cap->card)); strscpy(), please. > + snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s", > + video->csi->dev->of_node->name); > + > + return 0; > +} > + > +static int vidioc_enum_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_fmtdesc *f) > +{ > + u32 index = f->index; > + > + if (index >= ARRAY_SIZE(supported_pixformats)) > + return -EINVAL; > + > + f->pixelformat = supported_pixformats[index]; > + > + return 0; > +} > + > +static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *fmt) > +{ > + struct sun6i_video *video = video_drvdata(file); > + > + *fmt = video->fmt; > + > + return 0; > +} > + > +static int sun6i_video_try_fmt(struct sun6i_video *video, > + struct v4l2_format *f) > +{ > + struct v4l2_pix_format *pixfmt = &f->fmt.pix; > + int bpp; > + > + if (!is_pixformat_valid(pixfmt->pixelformat)) > + pixfmt->pixelformat = supported_pixformats[0]; > + > + v4l_bound_align_image(&pixfmt->width, MIN_WIDTH, MAX_WIDTH, 1, > + &pixfmt->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); > + > + bpp = sun6i_csi_get_bpp(pixfmt->pixelformat); > + pixfmt->bytesperline = (pixfmt->width * bpp) >> 3; > + pixfmt->sizeimage = pixfmt->bytesperline * pixfmt->height; > + > + if (pixfmt->field == V4L2_FIELD_ANY) > + pixfmt->field = V4L2_FIELD_NONE; > + > + pixfmt->colorspace = V4L2_COLORSPACE_RAW; > + pixfmt->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; > + pixfmt->quantization = V4L2_QUANTIZATION_DEFAULT; > + pixfmt->xfer_func = V4L2_XFER_FUNC_DEFAULT; > + > + return 0; > +} > + > +static int sun6i_video_set_fmt(struct sun6i_video *video, struct v4l2_format *f) > +{ > + int ret; > + > + ret = sun6i_video_try_fmt(video, f); > + if (ret) > + return ret; > + > + video->fmt = *f; > + > + return 0; > +} > + > +static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *f) > +{ > + struct sun6i_video *video = video_drvdata(file); > + > + if (vb2_is_busy(&video->vb2_vidq)) > + return -EBUSY; > + > + return sun6i_video_set_fmt(video, f); > +} > + > +static int vidioc_try_fmt_vid_cap(struct file *file, void *priv, > + struct v4l2_format *f) > +{ > + struct sun6i_video *video = video_drvdata(file); > + > + return sun6i_video_try_fmt(video, f); > +} > + > +static int vidioc_enum_input(struct file *file, void *fh, > + struct v4l2_input *inp) > +{ > + if (inp->index != 0) > + return -EINVAL; > + > + strlcpy(inp->name, "camera", sizeof(inp->name)); > + inp->type = V4L2_INPUT_TYPE_CAMERA; > + > + return 0; > +} > + > +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) > +{ > + *i = 0; > + > + return 0; > +} > + > +static int vidioc_s_input(struct file *file, void *fh, unsigned int i) > +{ > + if (i != 0) > + return -EINVAL; > + > + return 0; > +} > + > +static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { > + .vidioc_querycap = vidioc_querycap, > + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, > + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, > + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, > + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, > + > + .vidioc_enum_input = vidioc_enum_input, > + .vidioc_s_input = vidioc_s_input, > + .vidioc_g_input = vidioc_g_input, > + > + .vidioc_reqbufs = vb2_ioctl_reqbufs, > + .vidioc_querybuf = vb2_ioctl_querybuf, > + .vidioc_qbuf = vb2_ioctl_qbuf, > + .vidioc_expbuf = vb2_ioctl_expbuf, > + .vidioc_dqbuf = vb2_ioctl_dqbuf, > + .vidioc_create_bufs = vb2_ioctl_create_bufs, > + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, > + .vidioc_streamon = vb2_ioctl_streamon, > + .vidioc_streamoff = vb2_ioctl_streamoff, > + > + .vidioc_log_status = v4l2_ctrl_log_status, > + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, > + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, Do you need events? The driver doesn't seem to be using controls for anything and I see no trace of events otherwise either. > +}; > + > +/* ----------------------------------------------------------------------------- > + * V4L2 file operations > + */ > +static int sun6i_video_open(struct file *file) > +{ > + struct sun6i_video *video = video_drvdata(file); > + int ret; > + > + if (mutex_lock_interruptible(&video->lock)) > + return -ERESTARTSYS; > + > + ret = v4l2_fh_open(file); > + if (ret < 0) > + goto unlock; > + > + ret = v4l2_pipeline_pm_use(&video->vdev.entity, 1); > + if (ret < 0) > + goto fh_release; > + > + /* check if already powered */ > + if (!v4l2_fh_is_singular_file(file)) > + goto unlock; > + > + ret = sun6i_csi_set_power(video->csi, true); > + if (ret < 0) > + goto fh_release; > + > + mutex_unlock(&video->lock); > + return 0; > + > +fh_release: > + v4l2_fh_release(file); > +unlock: > + mutex_unlock(&video->lock); > + return ret; > +} > + > +static int sun6i_video_close(struct file *file) > +{ > + struct sun6i_video *video = video_drvdata(file); > + bool last_fh; > + > + mutex_lock(&video->lock); > + > + last_fh = v4l2_fh_is_singular_file(file); > + > + _vb2_fop_release(file, NULL); > + > + v4l2_pipeline_pm_use(&video->vdev.entity, 0); > + > + if (last_fh) > + sun6i_csi_set_power(video->csi, false); > + > + mutex_unlock(&video->lock); > + > + return 0; > +} > + > +static const struct v4l2_file_operations sun6i_video_fops = { > + .owner = THIS_MODULE, > + .open = sun6i_video_open, > + .release = sun6i_video_close, > + .unlocked_ioctl = video_ioctl2, > + .mmap = vb2_fop_mmap, > + .poll = vb2_fop_poll > +}; > + > +/* ----------------------------------------------------------------------------- > + * Media Operations > + */ > +static int sun6i_video_link_validate_get_format(struct media_pad *pad, > + struct v4l2_subdev_format *fmt) > +{ > + if (is_media_entity_v4l2_subdev(pad->entity)) { > + struct v4l2_subdev *sd = > + media_entity_to_v4l2_subdev(pad->entity); > + > + fmt->which = V4L2_SUBDEV_FORMAT_ACTIVE; > + fmt->pad = pad->index; > + return v4l2_subdev_call(sd, pad, get_fmt, NULL, fmt); > + } > + > + return -EINVAL; > +} > + > +static int sun6i_video_link_validate(struct media_link *link) > +{ > + struct video_device *vdev = container_of(link->sink->entity, > + struct video_device, entity); > + struct sun6i_video *video = video_get_drvdata(vdev); > + struct v4l2_subdev_format source_fmt; > + int ret; > + > + video->mbus_code = 0; > + > + if (!media_entity_remote_pad(link->sink->entity->pads)) { > + dev_info(video->csi->dev, > + "video node %s pad not connected\n", vdev->name); > + return -ENOTCONN; -ENOLINK > + } > + > + ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); > + if (ret < 0) > + return ret; > + > + if (!sun6i_csi_is_format_supported(video->csi, > + video->fmt.fmt.pix.pixelformat, > + source_fmt.format.code)) { > + dev_err(video->csi->dev, > + "Unsupported mbus code: 0x%x\n", > + source_fmt.format.code); > + return -EINVAL; -EPIPE, please. > + } > + > + if (source_fmt.format.width != video->fmt.fmt.pix.width || > + source_fmt.format.height != video->fmt.fmt.pix.height) { > + dev_err(video->csi->dev, > + "Wrong width or height %ux%u (%ux%u expected)\n", > + video->fmt.fmt.pix.width, video->fmt.fmt.pix.height, > + source_fmt.format.width, source_fmt.format.height); > + return -EINVAL; Same here. Please see: <URL:https://hverkuil.home.xs4all.nl/spec/uapi/v4l/vidioc-streamon.html> > + } > + > + video->mbus_code = source_fmt.format.code; > + > + return 0; > +} > + > +static const struct media_entity_operations sun6i_video_media_ops = { > + .link_validate = sun6i_video_link_validate > +}; > + > +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi, > + const char *name) > +{ > + struct video_device *vdev = &video->vdev; > + struct vb2_queue *vidq = &video->vb2_vidq; > + struct v4l2_format fmt = { 0 }; > + int ret; > + > + video->csi = csi; > + > + /* Initialize the media entity... */ > + video->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; > + vdev->entity.ops = &sun6i_video_media_ops; > + ret = media_entity_pads_init(&vdev->entity, 1, &video->pad); > + if (ret < 0) > + return ret; > + > + mutex_init(&video->lock); > + > + INIT_LIST_HEAD(&video->dma_queue); > + spin_lock_init(&video->dma_queue_lock); > + > + video->sequence = 0; > + > + /* Setup default format */ > + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + fmt.fmt.pix.pixelformat = supported_pixformats[0]; > + fmt.fmt.pix.width = 1280; > + fmt.fmt.pix.height = 720; > + fmt.fmt.pix.field = V4L2_FIELD_NONE; > + sun6i_video_set_fmt(video, &fmt); > + > + /* Initialize videobuf2 queue */ > + vidq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; > + vidq->io_modes = VB2_MMAP | VB2_DMABUF; > + vidq->drv_priv = video; > + vidq->buf_struct_size = sizeof(struct sun6i_csi_buffer); > + vidq->ops = &sun6i_csi_vb2_ops; > + vidq->mem_ops = &vb2_dma_contig_memops; > + vidq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; > + vidq->lock = &video->lock; > + /* Make sure non-dropped frame */ > + vidq->min_buffers_needed = 3; > + vidq->dev = csi->dev; > + > + ret = vb2_queue_init(vidq); > + if (ret) { > + v4l2_err(&csi->v4l2_dev, "vb2_queue_init failed: %d\n", ret); > + goto error; > + } > + > + /* Register video device */ > + strlcpy(vdev->name, name, sizeof(vdev->name)); > + vdev->release = video_device_release_empty; > + vdev->fops = &sun6i_video_fops; > + vdev->ioctl_ops = &sun6i_video_ioctl_ops; > + vdev->vfl_type = VFL_TYPE_GRABBER; > + vdev->vfl_dir = VFL_DIR_RX; > + vdev->v4l2_dev = &csi->v4l2_dev; > + vdev->queue = vidq; > + vdev->lock = &video->lock; > + vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; > + video_set_drvdata(vdev, video); > + > + ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1); > + if (ret < 0) { > + v4l2_err(&csi->v4l2_dev, > + "video_register_device failed: %d\n", ret); > + goto error; > + } > + > + return 0; > + > +error: > + sun6i_video_cleanup(video); > + return ret; > +} > + > +void sun6i_video_cleanup(struct sun6i_video *video) > +{ > + if (video_is_registered(&video->vdev)) No need for that check; it's done in video_unregister_device() anyway. > + video_unregister_device(&video->vdev); > + > + media_entity_cleanup(&video->vdev.entity); vb2_queue_release() here? Add mutex_destroy(&video->lock), too. > +} > diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h > new file mode 100644 > index 000000000000..b9cd919c24ac > --- /dev/null > +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h > @@ -0,0 +1,38 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) > + * All rights reserved. > + * Author: Yong Deng <yong.deng@xxxxxxxxxxxx> > + */ > + > +#ifndef __SUN6I_VIDEO_H__ > +#define __SUN6I_VIDEO_H__ > + > +#include <media/v4l2-dev.h> > +#include <media/videobuf2-core.h> > + > +struct sun6i_csi; > + > +struct sun6i_video { > + struct video_device vdev; > + struct media_pad pad; > + struct sun6i_csi *csi; > + > + struct mutex lock; > + > + struct vb2_queue vb2_vidq; > + spinlock_t dma_queue_lock; > + struct list_head dma_queue; > + > + unsigned int sequence; > + struct v4l2_format fmt; > + u32 mbus_code; > +}; > + > +int sun6i_video_init(struct sun6i_video *video, struct sun6i_csi *csi, > + const char *name); > +void sun6i_video_cleanup(struct sun6i_video *video); > + > +void sun6i_video_frame_done(struct sun6i_video *video); > + > +#endif /* __SUN6I_VIDEO_H__ */ -- Kind regards, Sakari Ailus sakari.ailus@xxxxxxxxxxxxxxx