Message ID | 1537951420-24752-1-git-send-email-yong.deng@magewell.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | [v11,1/2] dt-bindings: media: Add Allwinner V3s Camera Sensor Interface (CSI) | expand |
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@cisco.com> > Reviewed-by: Maxime Ripard <maxime.ripard@bootlin.com> > Tested-by: Maxime Ripard <maxime.ripard@bootlin.com> > Signed-off-by: Yong Deng <yong.deng@magewell.com> > --- > 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@gmail.com> > S: Maintained > F: sound/pci/cs5535audio/ > > +CSI DRIVERS FOR ALLWINNER V3s > +M: Yong Deng <yong.deng@magewell.com> > +L: linux-media@vger.kernel.org > +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@shaftnet.org> > 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@magewell.com> > + */ > + > +#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@magewell.com>"); > +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@magewell.com> > + */ > + > +#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@magewell.com> > + */ > + > +#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@magewell.com> > + */ > + > +#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@magewell.com> > + */ > + > +#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__ */
Hi Sakari, On Fri, 28 Sep 2018 13:19:37 +0300 Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > 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. > > ... > > + > > +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. I notice that the release order of intel ipu3 is: cio2_notifier_exit(cio2); cio2_fbpt_exit_dummy(cio2); for (i = 0; i < CIO2_QUEUES; i++) cio2_queue_exit(cio2, &cio2->queue[i]); v4l2_device_unregister(&cio2->v4l2_dev); media_device_unregister(&cio2->media_dev); media_device_cleanup(&cio2->media_dev); mutex_destroy(&cio2->lock); > > 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. Some one suggest this. They want the drver can pass control queries to the subdev. > > > + 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", ... > > + > > +/* > > + * 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) Do you mean: #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; > > + ... > > + > > +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. > I am not sure. The reason is same as ctrl handler. Thanks, Yong
Hi Yong, On Sat, Sep 29, 2018 at 06:18:44PM +0800, Yong wrote: > Hi Sakari, > > On Fri, 28 Sep 2018 13:19:37 +0300 > Sakari Ailus <sakari.ailus@linux.intel.com> wrote: > > > 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. > > > > > ... > > > > + > > > +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. > > I notice that the release order of intel ipu3 is: > cio2_notifier_exit(cio2); > cio2_fbpt_exit_dummy(cio2); > for (i = 0; i < CIO2_QUEUES; i++) > cio2_queue_exit(cio2, &cio2->queue[i]); > v4l2_device_unregister(&cio2->v4l2_dev); > media_device_unregister(&cio2->media_dev); > media_device_cleanup(&cio2->media_dev); > mutex_destroy(&cio2->lock); Well, yeah... this doesn't make a big difference after all. Unregistering the media device first is still preferred. > > > > > 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. > > Some one suggest this. They want the drver can pass control queries to > the subdev. If you need that, it should be done in the user space instead. The imx staging driver does so but it is not supported by the control framework: you may only call v4l2_ctrl_handler_free() for the purpose of freeing the resources of the control handler --- not while it is still accessible from the user space. > > > > > > + 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", > > ... > > > > + > > > +/* > > > + * 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) > > Do you mean: > #if defined(CONFIG_COMPILE_TEST) && !defined(PHYS_OFFSET) Oh, yes. Indeed.
diff --git a/MAINTAINERS b/MAINTAINERS index 9989925f658d..d6c8d13962ab 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3900,6 +3900,14 @@ M: Jaya Kumar <jayakumar.alsa@gmail.com> S: Maintained F: sound/pci/cs5535audio/ +CSI DRIVERS FOR ALLWINNER V3s +M: Yong Deng <yong.deng@magewell.com> +L: linux-media@vger.kernel.org +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@shaftnet.org> 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@magewell.com> + */ + +#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); + 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); + 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)); + csi->media_dev.hw_revision = 0; + + media_device_init(&csi->media_dev); + + ret = v4l2_ctrl_handler_init(&csi->ctrl_handler, 0); + 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; + } + + 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; + } + 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 +#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); + 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@magewell.com>"); +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@magewell.com> + */ + +#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@magewell.com> + */ + +#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@magewell.com> + */ + +#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) +{ + 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)); + 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, +}; + +/* ----------------------------------------------------------------------------- + * 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; + } + + 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; + } + + 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; + } + + 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)) + video_unregister_device(&video->vdev); + + media_entity_cleanup(&video->vdev.entity); +} 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@magewell.com> + */ + +#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__ */