diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2022-12-13 11:36:58 -0800 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2022-12-13 11:36:58 -0800 |
commit | cdb9d3537711939e4d8fd0de2889c966f88346eb (patch) | |
tree | 2a118e0930d3e08c6303b579e89f0df388cba4e6 /drivers/media/platform/sunxi | |
parent | 102f9d3d455870844c47b82322c2dfc0a35eb745 (diff) | |
parent | 3178804c64ef7c8c87a53cd5bba0b2942dd64fec (diff) |
Merge tag 'media/v6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media
Pull media updates from Mauro Carvalho Chehab:
- DVB core changes to avoid refcount troubles and UAF
- DVB API/core has gained support for DVB-C2 and DVB-S2X
- New sensor drivers: ov08x40, ov4689.c, st-vgxy61 and tc358746.c
- Removal of an unused sensor driver: s5k4ecgx
- Move microchip_csi2dc to a new directory, named after the
manufacturer
- Add media controller support to Microship drivers
- Old Atmel/Microship drivers that don't use media controler got moved
to staging
- New drivers added for Renesas RZ/G2L CRU and MIPI CSI-2 support
- Allwinner A31 camera sensor driver code was now split into a bridge
and a separate processor driver
- Added a virtual stateless decoder driver in order to test core
support for stateless drivers and test userspace apps using it
- removed platform-based support for ov9650, as this is not used
anymore
- atomisp now uses videobuf2 and supports normal mmap mode
- the imx7-media-csi driver got promoted from staging
- rcar-vin driver has gained support for gen3 UDS (Up Down Scaler)
- most i2c drivers now use I2C .probe_new() kAPI
- lots of drivers fixes, cleanups and improvements
* tag 'media/v6.2-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (544 commits)
media: s5c73m3: Switch to GPIO descriptors
media: i2c: s5k5baf: switch to using gpiod API
media: i2c: s5k6a3: switch to using gpiod API
media: imx: remove code for non-existing config IMX_GPT_ICAP
media: si470x: Fix use-after-free in si470x_int_in_callback()
media: staging: stkwebcam: Restore MEDIA_{USB,CAMERA}_SUPPORT dependencies
media: coda: Add check for kmalloc
media: coda: Add check for dcoda_iram_alloc
dt-bindings: media: s5c73m3: Fix reset-gpio descriptor
media: dt-bindings: allwinner: h6-vpu-g2: Add IOMMU reference property
media: s5k4ecgx: Delete driver
media: s5k4ecgx: Switch to GPIO descriptors
media: Switch to use dev_err_probe() helper
headers: Remove some left-over license text in include/uapi/linux/v4l2-*
headers: Remove some left-over license text in include/uapi/linux/dvb/
media: usb: pwc-uncompress: Use flex array destination for memcpy()
media: s5p-mfc: Fix to handle reference queue during finishing
media: s5p-mfc: Clear workbit to handle error condition
media: s5p-mfc: Fix in register read and write for H264
media: imx: Use get_mbus_config instead of parsing upstream DT endpoints
...
Diffstat (limited to 'drivers/media/platform/sunxi')
12 files changed, 2451 insertions, 1779 deletions
diff --git a/drivers/media/platform/sunxi/sun6i-csi/Makefile b/drivers/media/platform/sunxi/sun6i-csi/Makefile index e7e315347804..87e7a715140a 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/Makefile +++ b/drivers/media/platform/sunxi/sun6i-csi/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only -sun6i-csi-y += sun6i_video.o sun6i_csi.o +sun6i-csi-y += sun6i_csi.o sun6i_csi_bridge.o sun6i_csi_capture.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 index 8b99c17e8403..e3e6650181c8 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.c @@ -1,18 +1,14 @@ // 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> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.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/of_device.h> @@ -20,561 +16,56 @@ #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 <media/v4l2-device.h> #include <media/v4l2-mc.h> #include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #include "sun6i_csi_reg.h" -/* Helpers */ +/* ISP */ -/* TODO add 10&12 bit YUV, RGB support */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code) +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev) { - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - - /* - * Some video receivers have the ability to be compatible with - * 8bit and 16bit bus width. - * Identify the media bus format from device tree. - */ - if ((v4l2->v4l2_ep.bus_type == V4L2_MBUS_PARALLEL - || v4l2->v4l2_ep.bus_type == V4L2_MBUS_BT656) - && v4l2->v4l2_ep.bus.parallel.bus_width == 16) { - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - 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(csi_dev->dev, - "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } - return false; - } + if (csi_dev->v4l2_dev && csi_dev->v4l2_dev != v4l2_dev) + return -EINVAL; - 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_NV12_16L16: - 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(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_RGB565: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_LE); - case V4L2_PIX_FMT_RGB565X: - return (mbus_code == MEDIA_BUS_FMT_RGB565_2X8_BE); - - case V4L2_PIX_FMT_JPEG: - return (mbus_code == MEDIA_BUS_FMT_JPEG_1X8); - - default: - dev_dbg(csi_dev->dev, "Unsupported pixformat: 0x%x\n", - pixformat); - break; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = v4l2_dev->mdev; - return false; + return sun6i_csi_capture_setup(csi_dev); } -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable) +static int sun6i_csi_isp_detect(struct sun6i_csi_device *csi_dev) { struct device *dev = csi_dev->dev; - struct regmap *regmap = csi_dev->regmap; - int ret; - - if (!enable) { - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, 0); - pm_runtime_put(dev); + struct fwnode_handle *handle; + /* + * ISP is not available if not connected via fwnode graph. + * This will also check that the remote parent node is available. + */ + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), + SUN6I_CSI_PORT_ISP, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!handle) return 0; - } - - ret = pm_runtime_resume_and_get(dev); - if (ret < 0) - return ret; - - regmap_update_bits(regmap, CSI_EN_REG, CSI_EN_CSI_EN, CSI_EN_CSI_EN); - - return 0; -} - -static enum csi_input_fmt get_csi_input_format(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* non-YUV */ - if ((mbus_code & 0xF000) != 0x2000) - 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(csi_dev->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_device *csi_dev, 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_NV12_16L16: - 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; - - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - return buf_interlaced ? CSI_FRAME_RGB565 : CSI_FIELD_RGB565; - - case V4L2_PIX_FMT_JPEG: - return buf_interlaced ? CSI_FRAME_RAW_8 : CSI_FIELD_RAW_8; - - default: - dev_warn(csi_dev->dev, "Unsupported pixformat: 0x%x\n", pixformat); - break; - } + fwnode_handle_put(handle); - return CSI_FIELD_RAW_8; -} - -static enum csi_input_seq get_csi_input_seq(struct sun6i_csi_device *csi_dev, - u32 mbus_code, u32 pixformat) -{ - /* Input sequence does not apply to non-YUV formats */ - if ((mbus_code & 0xF000) != 0x2000) + if (!IS_ENABLED(CONFIG_VIDEO_SUN6I_ISP)) { + dev_warn(dev, + "ISP link is detected but not enabled in kernel config!"); return 0; - - switch (pixformat) { - case V4L2_PIX_FMT_NV12_16L16: - 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(csi_dev->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(csi_dev->dev, "Unsupported mbus code: 0x%x\n", - mbus_code); - break; - } - break; - - case V4L2_PIX_FMT_YUYV: - return CSI_INPUT_SEQ_YUYV; - - default: - dev_warn(csi_dev->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_device *csi_dev) -{ - struct v4l2_fwnode_endpoint *endpoint = &csi_dev->v4l2.v4l2_ep; - struct sun6i_csi_config *config = &csi_dev->config; - unsigned char bus_width; - u32 flags; - u32 cfg; - bool input_interlaced = false; - - if (config->field == V4L2_FIELD_INTERLACED - || config->field == V4L2_FIELD_INTERLACED_TB - || config->field == V4L2_FIELD_INTERLACED_BT) - input_interlaced = true; - - bus_width = endpoint->bus.parallel.bus_width; - - regmap_read(csi_dev->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(csi_dev->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(csi_dev->dev, "Unsupported bus width: %u\n", bus_width); - break; - } - - regmap_write(csi_dev->regmap, CSI_IF_CFG_REG, cfg); -} - -static void sun6i_csi_set_format(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 cfg; - u32 val; - - regmap_read(csi_dev->regmap, CSI_CH_CFG_REG, &cfg); - - cfg &= ~(CSI_CH_CFG_INPUT_FMT_MASK | - CSI_CH_CFG_OUTPUT_FMT_MASK | CSI_CH_CFG_VFLIP_EN | - CSI_CH_CFG_HFLIP_EN | CSI_CH_CFG_FIELD_SEL_MASK | - CSI_CH_CFG_INPUT_SEQ_MASK); - - val = get_csi_input_format(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_FMT(val); - - val = get_csi_output_format(csi_dev, config->pixelformat, - config->field); - cfg |= CSI_CH_CFG_OUTPUT_FMT(val); - - val = get_csi_input_seq(csi_dev, config->code, - config->pixelformat); - cfg |= CSI_CH_CFG_INPUT_SEQ(val); - - if (config->field == V4L2_FIELD_TOP) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD0; - else if (config->field == V4L2_FIELD_BOTTOM) - cfg |= CSI_CH_CFG_FIELD_SEL_FIELD1; - else - cfg |= CSI_CH_CFG_FIELD_SEL_BOTH; - - regmap_write(csi_dev->regmap, CSI_CH_CFG_REG, cfg); -} - -static void sun6i_csi_set_window(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_csi_config *config = &csi_dev->config; - u32 bytesperline_y; - u32 bytesperline_c; - int *planar_offset = csi_dev->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(csi_dev->dev, - "Horizontal length should be 2 times of width for packed YUV formats!\n"); - hor_len = width * 2; - break; - default: - break; - } - - regmap_write(csi_dev->regmap, CSI_CH_HSIZE_REG, - CSI_CH_HSIZE_HOR_LEN(hor_len) | - CSI_CH_HSIZE_HOR_START(0)); - regmap_write(csi_dev->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_NV12_16L16: - 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(csi_dev->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(csi_dev->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_device *csi_dev, - struct sun6i_csi_config *config) -{ - if (!config) - return -EINVAL; - - memcpy(&csi_dev->config, config, sizeof(csi_dev->config)); - - sun6i_csi_setup_bus(csi_dev); - sun6i_csi_set_format(csi_dev); - sun6i_csi_set_window(csi_dev); + csi_dev->isp_available = true; return 0; } -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr) -{ - regmap_write(csi_dev->regmap, CSI_CH_F0_BUFA_REG, - (addr + csi_dev->planar_offset[0]) >> 2); - if (csi_dev->planar_offset[1] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F1_BUFA_REG, - (addr + csi_dev->planar_offset[1]) >> 2); - if (csi_dev->planar_offset[2] != -1) - regmap_write(csi_dev->regmap, CSI_CH_F2_BUFA_REG, - (addr + csi_dev->planar_offset[2]) >> 2); -} - -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, bool enable) -{ - struct regmap *regmap = csi_dev->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 */ static const struct media_device_ops sun6i_csi_media_ops = { @@ -583,103 +74,11 @@ static const struct media_device_ops sun6i_csi_media_ops = { /* V4L2 */ -static int sun6i_csi_link_entity(struct sun6i_csi_device *csi_dev, - 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->dev, - "%s: no source pad in external entity %s\n", __func__, - entity->name); - return -EINVAL; - } - - src_pad_index = ret; - - sink = &csi_dev->video.video_dev.entity; - sink_pad = &csi_dev->video.pad; - - dev_dbg(csi_dev->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->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_device *csi_dev = - container_of(notifier, struct sun6i_csi_device, - v4l2.notifier); - struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; - struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_subdev *sd; - int ret; - - dev_dbg(csi_dev->dev, "notify complete, all subdevs registered\n"); - - sd = list_first_entry(&v4l2_dev->subdevs, struct v4l2_subdev, list); - if (!sd) - return -EINVAL; - - ret = sun6i_csi_link_entity(csi_dev, &sd->entity, sd->fwnode); - if (ret < 0) - return ret; - - ret = v4l2_device_register_subdev_nodes(v4l2_dev); - if (ret < 0) - return ret; - - return 0; -} - -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_device *csi_dev = 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_dev->v4l2.v4l2_ep = *vep; - return 0; - default: - dev_err(dev, "Unsupported media bus type\n"); - return -ENOTCONN; - } -} - static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) { struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; struct media_device *media_dev = &v4l2->media_dev; struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev; - struct v4l2_async_notifier *notifier = &v4l2->notifier; struct device *dev = csi_dev->dev; int ret; @@ -709,42 +108,11 @@ static int sun6i_csi_v4l2_setup(struct sun6i_csi_device *csi_dev) goto error_media; } - /* Video */ - - ret = sun6i_video_setup(csi_dev); - if (ret) - goto error_v4l2_device; - - /* V4L2 Async */ - - v4l2_async_nf_init(notifier); - notifier->ops = &sun6i_csi_async_ops; - - ret = v4l2_async_nf_parse_fwnode_endpoints(dev, notifier, - sizeof(struct - v4l2_async_subdev), - sun6i_csi_fwnode_parse); - if (ret) - goto error_video; - - ret = v4l2_async_nf_register(v4l2_dev, notifier); - if (ret) { - dev_err(dev, "failed to register v4l2 async notifier: %d\n", - ret); - goto error_v4l2_async_notifier; - } + csi_dev->v4l2_dev = v4l2_dev; + csi_dev->media_dev = media_dev; return 0; -error_v4l2_async_notifier: - v4l2_async_nf_cleanup(notifier); - -error_video: - sun6i_video_cleanup(csi_dev); - -error_v4l2_device: - v4l2_device_unregister(&v4l2->v4l2_dev); - error_media: media_device_unregister(media_dev); media_device_cleanup(media_dev); @@ -757,9 +125,6 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) struct sun6i_csi_v4l2 *v4l2 = &csi_dev->v4l2; media_device_unregister(&v4l2->media_dev); - v4l2_async_nf_unregister(&v4l2->notifier); - v4l2_async_nf_cleanup(&v4l2->notifier); - sun6i_video_cleanup(csi_dev); v4l2_device_unregister(&v4l2->v4l2_dev); media_device_cleanup(&v4l2->media_dev); } @@ -769,29 +134,39 @@ static void sun6i_csi_v4l2_cleanup(struct sun6i_csi_device *csi_dev) static irqreturn_t sun6i_csi_interrupt(int irq, void *private) { struct sun6i_csi_device *csi_dev = private; + bool capture_streaming = csi_dev->capture.state.streaming; struct regmap *regmap = csi_dev->regmap; - u32 status; + u32 status = 0, enable = 0; - regmap_read(regmap, CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_STA_REG, &status); + regmap_read(regmap, SUN6I_CSI_CH_INT_EN_REG, &enable); - if (!(status & 0xFF)) + if (!status) 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); + else if (!(status & enable) || !capture_streaming) + goto complete; + + if ((status & SUN6I_CSI_CH_INT_STA_FIFO0_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO1_OF) || + (status & SUN6I_CSI_CH_INT_STA_FIFO2_OF) || + (status & SUN6I_CSI_CH_INT_STA_HB_OF)) { + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, + SUN6I_CSI_EN_CSI_EN, SUN6I_CSI_EN_CSI_EN); return IRQ_HANDLED; } - if (status & CSI_CH_INT_STA_FD_PD) - sun6i_video_frame_done(csi_dev); + if (status & SUN6I_CSI_CH_INT_STA_FD) + sun6i_csi_capture_frame_done(csi_dev); + + if (status & SUN6I_CSI_CH_INT_STA_VS) + sun6i_csi_capture_sync(csi_dev); - regmap_write(regmap, CSI_CH_INT_STA_REG, status); +complete: + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, status); return IRQ_HANDLED; } @@ -913,13 +288,12 @@ static int sun6i_csi_resources_setup(struct sun6i_csi_device *csi_dev, irq = platform_get_irq(platform_dev, 0); if (irq < 0) { - dev_err(dev, "failed to get interrupt\n"); ret = -ENXIO; goto error_clock_rate_exclusive; } - ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, 0, SUN6I_CSI_NAME, - csi_dev); + ret = devm_request_irq(dev, irq, sun6i_csi_interrupt, IRQF_SHARED, + SUN6I_CSI_NAME, csi_dev); if (ret) { dev_err(dev, "failed to request interrupt\n"); goto error_clock_rate_exclusive; @@ -960,12 +334,41 @@ static int sun6i_csi_probe(struct platform_device *platform_dev) if (ret) return ret; - ret = sun6i_csi_v4l2_setup(csi_dev); + ret = sun6i_csi_isp_detect(csi_dev); if (ret) goto error_resources; + /* + * Register our own v4l2 and media devices when there is no ISP around. + * Otherwise the ISP will use async subdev registration with our bridge, + * which will provide v4l2 and media devices that are used to register + * the video interface. + */ + if (!csi_dev->isp_available) { + ret = sun6i_csi_v4l2_setup(csi_dev); + if (ret) + goto error_resources; + } + + ret = sun6i_csi_bridge_setup(csi_dev); + if (ret) + goto error_v4l2; + + if (!csi_dev->isp_available) { + ret = sun6i_csi_capture_setup(csi_dev); + if (ret) + goto error_bridge; + } + return 0; +error_bridge: + sun6i_csi_bridge_cleanup(csi_dev); + +error_v4l2: + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + error_resources: sun6i_csi_resources_cleanup(csi_dev); @@ -976,7 +379,12 @@ static int sun6i_csi_remove(struct platform_device *pdev) { struct sun6i_csi_device *csi_dev = platform_get_drvdata(pdev); - sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_capture_cleanup(csi_dev); + sun6i_csi_bridge_cleanup(csi_dev); + + if (!csi_dev->isp_available) + sun6i_csi_v4l2_cleanup(csi_dev); + sun6i_csi_resources_cleanup(csi_dev); return 0; @@ -1030,4 +438,5 @@ module_platform_driver(sun6i_csi_platform_driver); MODULE_DESCRIPTION("Allwinner A31 Camera Sensor Interface driver"); MODULE_AUTHOR("Yong Deng <yong.deng@magewell.com>"); +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@bootlin.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 index bab705678280..bc3f0dae35df 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi.h @@ -1,166 +1,63 @@ /* 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> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_H__ -#define __SUN6I_CSI_H__ +#ifndef _SUN6I_CSI_H_ +#define _SUN6I_CSI_H_ #include <media/v4l2-device.h> -#include <media/v4l2-fwnode.h> #include <media/videobuf2-v4l2.h> -#include "sun6i_video.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_capture.h" #define SUN6I_CSI_NAME "sun6i-csi" #define SUN6I_CSI_DESCRIPTION "Allwinner A31 CSI Device" +enum sun6i_csi_port { + SUN6I_CSI_PORT_PARALLEL = 0, + SUN6I_CSI_PORT_MIPI_CSI2 = 1, + SUN6I_CSI_PORT_ISP = 2, +}; + struct sun6i_csi_buffer { struct vb2_v4l2_buffer v4l2_buffer; struct list_head list; - - dma_addr_t dma_addr; - bool queued_to_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_v4l2 { 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_device { struct device *dev; + struct v4l2_device *v4l2_dev; + struct media_device *media_dev; - struct sun6i_csi_config config; struct sun6i_csi_v4l2 v4l2; - struct sun6i_video video; + struct sun6i_csi_bridge bridge; + struct sun6i_csi_capture capture; struct regmap *regmap; struct clk *clock_mod; struct clk *clock_ram; struct reset_control *reset; - int planar_offset[3]; + bool isp_available; }; struct sun6i_csi_variant { unsigned long clock_mod_rate; }; -/** - * sun6i_csi_is_format_supported() - check if the format supported by csi - * @csi_dev: pointer to the csi device - * @pixformat: v4l2 pixel format (V4L2_PIX_FMT_*) - * @mbus_code: media bus format code (MEDIA_BUS_FMT_*) - * - * Return: true if format is supported, false otherwise. - */ -bool sun6i_csi_is_format_supported(struct sun6i_csi_device *csi_dev, - u32 pixformat, u32 mbus_code); - -/** - * sun6i_csi_set_power() - power on/off the csi - * @csi_dev: pointer to the csi device - * @enable: on/off - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_set_power(struct sun6i_csi_device *csi_dev, bool enable); - -/** - * sun6i_csi_update_config() - update the csi register settings - * @csi_dev: pointer to the csi device - * @config: see struct sun6i_csi_config - * - * Return: 0 if successful, error code otherwise. - */ -int sun6i_csi_update_config(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_config *config); - -/** - * sun6i_csi_update_buf_addr() - update the csi frame buffer address - * @csi_dev: pointer to the csi device - * @addr: frame buffer's physical address - */ -void sun6i_csi_update_buf_addr(struct sun6i_csi_device *csi_dev, - dma_addr_t addr); - -/** - * sun6i_csi_set_stream() - start/stop csi streaming - * @csi_dev: pointer to the csi device - * @enable: start/stop - */ -void sun6i_csi_set_stream(struct sun6i_csi_device *csi_dev, 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: - case V4L2_PIX_FMT_JPEG: - 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_NV12_16L16: - 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: - case V4L2_PIX_FMT_RGB565: - case V4L2_PIX_FMT_RGB565X: - 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; - } +/* ISP */ - return 0; -} +int sun6i_csi_isp_complete(struct sun6i_csi_device *csi_dev, + struct v4l2_device *v4l2_dev); -#endif /* __SUN6I_CSI_H__ */ +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c new file mode 100644 index 000000000000..ebfc870d2af5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.c @@ -0,0 +1,868 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/pm_runtime.h> +#include <linux/regmap.h> +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#include "sun6i_csi.h" +#include "sun6i_csi_bridge.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->bridge.mbus_format.width; + if (height) + *height = csi_dev->bridge.mbus_format.height; +} + +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field) +{ + if (mbus_code) + *mbus_code = csi_dev->bridge.mbus_format.code; + if (field) + *field = csi_dev->bridge.mbus_format.field; +} + +/* Format */ + +static const struct sun6i_csi_bridge_format sun6i_csi_bridge_formats[] = { + /* Bayer */ + { + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* RGB */ + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + { + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, + /* YUV422 */ + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_YVYU, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_YUYV, + }, + { + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + }, + { + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + .input_format = SUN6I_CSI_INPUT_FMT_YUV422, + .input_yuv_seq = SUN6I_CSI_INPUT_YUV_SEQ_VYUY, + .input_yuv_seq_invert = SUN6I_CSI_INPUT_YUV_SEQ_UYVY, + }, + /* Compressed */ + { + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + .input_format = SUN6I_CSI_INPUT_FMT_RAW, + }, +}; + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_bridge_formats); i++) + if (sun6i_csi_bridge_formats[i].mbus_code == mbus_code) + return &sun6i_csi_bridge_formats[i]; + + return NULL; +} + +/* Bridge */ + +static void sun6i_csi_bridge_irq_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, + SUN6I_CSI_CH_INT_EN_VS | + SUN6I_CSI_CH_INT_EN_HB_OF | + SUN6I_CSI_CH_INT_EN_FIFO2_OF | + SUN6I_CSI_CH_INT_EN_FIFO1_OF | + SUN6I_CSI_CH_INT_EN_FIFO0_OF | + SUN6I_CSI_CH_INT_EN_FD | + SUN6I_CSI_CH_INT_EN_CD); +} + +static void sun6i_csi_bridge_irq_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); +} + +static void sun6i_csi_bridge_irq_clear(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_write(regmap, SUN6I_CSI_CH_INT_EN_REG, 0); + regmap_write(regmap, SUN6I_CSI_CH_INT_STA_REG, + SUN6I_CSI_CH_INT_STA_CLEAR); +} + +static void sun6i_csi_bridge_enable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, + SUN6I_CSI_EN_CSI_EN); + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, + SUN6I_CSI_CAP_VCAP_ON); +} + +static void sun6i_csi_bridge_disable(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + + regmap_update_bits(regmap, SUN6I_CSI_CAP_REG, SUN6I_CSI_CAP_VCAP_ON, 0); + regmap_update_bits(regmap, SUN6I_CSI_EN_REG, SUN6I_CSI_EN_CSI_EN, 0); +} + +static void +sun6i_csi_bridge_configure_parallel(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct regmap *regmap = csi_dev->regmap; + struct v4l2_fwnode_endpoint *endpoint = + &csi_dev->bridge.source_parallel.endpoint; + unsigned char bus_width = endpoint->bus.parallel.bus_width; + unsigned int flags = endpoint->bus.parallel.flags; + u32 field; + u32 value = SUN6I_CSI_IF_CFG_IF_CSI; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED | + SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(1) | + SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + switch (endpoint->bus_type) { + case V4L2_MBUS_PARALLEL: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_VSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_HSYNC_ACTIVE_LOW) + value |= SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_RISING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + case V4L2_MBUS_BT656: + if (bus_width == 16) + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT1120; + else + value |= SUN6I_CSI_IF_CFG_IF_CSI_BT656; + + if (flags & V4L2_MBUS_FIELD_EVEN_LOW) + value |= SUN6I_CSI_IF_CFG_FIELD_NEGATIVE; + else + value |= SUN6I_CSI_IF_CFG_FIELD_POSITIVE; + + if (flags & V4L2_MBUS_PCLK_SAMPLE_FALLING) + value |= SUN6I_CSI_IF_CFG_CLK_POL_RISING; + else + value |= SUN6I_CSI_IF_CFG_CLK_POL_FALLING; + break; + default: + dev_warn(dev, "unsupported bus type: %d\n", endpoint->bus_type); + break; + } + + switch (bus_width) { + case 8: + /* 16-bit YUV formats use a doubled width in 8-bit mode. */ + case 16: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_8; + break; + case 10: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_10; + break; + case 12: + value |= SUN6I_CSI_IF_CFG_DATA_WIDTH_12; + break; + default: + dev_warn(dev, "unsupported bus width: %u\n", bus_width); + break; + } + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void +sun6i_csi_bridge_configure_mipi_csi2(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + u32 value = SUN6I_CSI_IF_CFG_IF_MIPI; + u32 field; + + sun6i_csi_bridge_format(csi_dev, NULL, &field); + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED; + else + value |= SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE; + + regmap_write(regmap, SUN6I_CSI_IF_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure_format(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + bool capture_streaming = csi_dev->capture.state.streaming; + const struct sun6i_csi_bridge_format *bridge_format; + const struct sun6i_csi_capture_format *capture_format; + u32 mbus_code, field, pixelformat; + u8 input_format, input_yuv_seq, output_format; + u32 value = 0; + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &field); + + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return; + + input_format = bridge_format->input_format; + input_yuv_seq = bridge_format->input_yuv_seq; + + if (capture_streaming) { + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return; + + if (capture_format->input_format_raw) + input_format = SUN6I_CSI_INPUT_FMT_RAW; + + if (capture_format->input_yuv_seq_invert) + input_yuv_seq = bridge_format->input_yuv_seq_invert; + + if (field == V4L2_FIELD_INTERLACED || + field == V4L2_FIELD_INTERLACED_TB || + field == V4L2_FIELD_INTERLACED_BT) + output_format = capture_format->output_format_field; + else + output_format = capture_format->output_format_frame; + + value |= SUN6I_CSI_CH_CFG_OUTPUT_FMT(output_format); + } + + value |= SUN6I_CSI_CH_CFG_INPUT_FMT(input_format); + value |= SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(input_yuv_seq); + + if (field == V4L2_FIELD_TOP) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0; + else if (field == V4L2_FIELD_BOTTOM) + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1; + else + value |= SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER; + + regmap_write(regmap, SUN6I_CSI_CH_CFG_REG, value); +} + +static void sun6i_csi_bridge_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source) +{ + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + + if (source == &bridge->source_parallel) + sun6i_csi_bridge_configure_parallel(csi_dev); + else + sun6i_csi_bridge_configure_mipi_csi2(csi_dev); + + sun6i_csi_bridge_configure_format(csi_dev); +} + +/* V4L2 Subdev */ + +static int sun6i_csi_bridge_s_stream(struct v4l2_subdev *subdev, int on) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct media_pad *local_pad = &bridge->pads[SUN6I_CSI_BRIDGE_PAD_SINK]; + bool capture_streaming = csi_dev->capture.state.streaming; + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge_source *source; + struct v4l2_subdev *source_subdev; + struct media_pad *remote_pad; + int ret; + + /* Source */ + + remote_pad = media_pad_remote_pad_unique(local_pad); + if (IS_ERR(remote_pad)) { + dev_err(dev, + "zero or more than a single source connected to the bridge\n"); + return PTR_ERR(remote_pad); + } + + source_subdev = media_entity_to_v4l2_subdev(remote_pad->entity); + + if (source_subdev == bridge->source_parallel.subdev) + source = &bridge->source_parallel; + else + source = &bridge->source_mipi_csi2; + + if (!on) { + v4l2_subdev_call(source_subdev, video, s_stream, 0); + ret = 0; + goto disable; + } + + /* PM */ + + ret = pm_runtime_resume_and_get(dev); + if (ret < 0) + return ret; + + /* Clear */ + + sun6i_csi_bridge_irq_clear(csi_dev); + + /* Configure */ + + sun6i_csi_bridge_configure(csi_dev, source); + + if (capture_streaming) + sun6i_csi_capture_configure(csi_dev); + + /* State Update */ + + if (capture_streaming) + sun6i_csi_capture_state_update(csi_dev); + + /* Enable */ + + if (capture_streaming) + sun6i_csi_bridge_irq_enable(csi_dev); + + sun6i_csi_bridge_enable(csi_dev); + + ret = v4l2_subdev_call(source_subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto disable; + + return 0; + +disable: + if (capture_streaming) + sun6i_csi_bridge_irq_disable(csi_dev); + + sun6i_csi_bridge_disable(csi_dev); + + pm_runtime_put(dev); + + return ret; +} + +static const struct v4l2_subdev_video_ops sun6i_csi_bridge_video_ops = { + .s_stream = sun6i_csi_bridge_s_stream, +}; + +static void +sun6i_csi_bridge_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format) +{ + if (!sun6i_csi_bridge_format_find(mbus_format->code)) + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + + mbus_format->field = V4L2_FIELD_NONE; + mbus_format->colorspace = V4L2_COLORSPACE_RAW; + mbus_format->quantization = V4L2_QUANTIZATION_DEFAULT; + mbus_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_bridge_init_cfg(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + unsigned int pad = SUN6I_CSI_BRIDGE_PAD_SINK; + struct v4l2_mbus_framefmt *mbus_format = + v4l2_subdev_get_try_format(subdev, state, pad); + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + mbus_format->code = sun6i_csi_bridge_formats[0].mbus_code; + mbus_format->width = 1280; + mbus_format->height = 720; + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + mutex_unlock(lock); + + return 0; +} + +static int +sun6i_csi_bridge_enum_mbus_code(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code_enum) +{ + if (code_enum->index >= ARRAY_SIZE(sun6i_csi_bridge_formats)) + return -EINVAL; + + code_enum->code = sun6i_csi_bridge_formats[code_enum->index].mbus_code; + + return 0; +} + +static int sun6i_csi_bridge_get_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *mbus_format = *v4l2_subdev_get_try_format(subdev, state, + format->pad); + else + *mbus_format = csi_dev->bridge.mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static int sun6i_csi_bridge_set_fmt(struct v4l2_subdev *subdev, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct sun6i_csi_device *csi_dev = v4l2_get_subdevdata(subdev); + struct v4l2_mbus_framefmt *mbus_format = &format->format; + struct mutex *lock = &csi_dev->bridge.lock; + + mutex_lock(lock); + + sun6i_csi_bridge_mbus_format_prepare(mbus_format); + + if (format->which == V4L2_SUBDEV_FORMAT_TRY) + *v4l2_subdev_get_try_format(subdev, state, format->pad) = + *mbus_format; + else + csi_dev->bridge.mbus_format = *mbus_format; + + mutex_unlock(lock); + + return 0; +} + +static const struct v4l2_subdev_pad_ops sun6i_csi_bridge_pad_ops = { + .init_cfg = sun6i_csi_bridge_init_cfg, + .enum_mbus_code = sun6i_csi_bridge_enum_mbus_code, + .get_fmt = sun6i_csi_bridge_get_fmt, + .set_fmt = sun6i_csi_bridge_set_fmt, +}; + +static const struct v4l2_subdev_ops sun6i_csi_bridge_subdev_ops = { + .video = &sun6i_csi_bridge_video_ops, + .pad = &sun6i_csi_bridge_pad_ops, +}; + +/* Media Entity */ + +static const struct media_entity_operations sun6i_csi_bridge_entity_ops = { + .link_validate = v4l2_subdev_link_validate, +}; + +/* V4L2 Async */ + +static int sun6i_csi_bridge_link(struct sun6i_csi_device *csi_dev, + int sink_pad_index, + struct v4l2_subdev *remote_subdev, + bool enabled) +{ + struct device *dev = csi_dev->dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct media_entity *sink_entity = &subdev->entity; + struct media_entity *source_entity = &remote_subdev->entity; + int source_pad_index; + int ret; + + /* Get the first remote source pad. */ + ret = media_entity_get_fwnode_pad(source_entity, remote_subdev->fwnode, + MEDIA_PAD_FL_SOURCE); + if (ret < 0) { + dev_err(dev, "missing source pad in external entity %s\n", + source_entity->name); + return -EINVAL; + } + + source_pad_index = ret; + + dev_dbg(dev, "creating %s:%u -> %s:%u link\n", source_entity->name, + source_pad_index, sink_entity->name, sink_pad_index); + + ret = media_create_pad_link(source_entity, source_pad_index, + sink_entity, sink_pad_index, + enabled ? MEDIA_LNK_FL_ENABLED : 0); + if (ret < 0) { + dev_err(dev, "failed to create %s:%u -> %s:%u link\n", + source_entity->name, source_pad_index, + sink_entity->name, sink_pad_index); + return ret; + } + + return 0; +} + +static int +sun6i_csi_bridge_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *remote_subdev, + struct v4l2_async_subdev *async_subdev) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev = + container_of(async_subdev, struct sun6i_csi_bridge_async_subdev, + async_subdev); + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct sun6i_csi_bridge_source *source = bridge_async_subdev->source; + bool enabled = false; + int ret; + + switch (source->endpoint.base.port) { + case SUN6I_CSI_PORT_PARALLEL: + enabled = true; + break; + case SUN6I_CSI_PORT_MIPI_CSI2: + enabled = !bridge->source_parallel.expected; + break; + default: + break; + } + + source->subdev = remote_subdev; + + if (csi_dev->isp_available) { + /* + * Hook to the first available remote subdev to get v4l2 and + * media devices and register the capture device then. + */ + ret = sun6i_csi_isp_complete(csi_dev, remote_subdev->v4l2_dev); + if (ret) + return ret; + } + + return sun6i_csi_bridge_link(csi_dev, SUN6I_CSI_BRIDGE_PAD_SINK, + remote_subdev, enabled); +} + +static int +sun6i_csi_bridge_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct sun6i_csi_device *csi_dev = + container_of(notifier, struct sun6i_csi_device, + bridge.notifier); + struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; + + if (csi_dev->isp_available) + return 0; + + return v4l2_device_register_subdev_nodes(v4l2_dev); +} + +static const struct v4l2_async_notifier_operations +sun6i_csi_bridge_notifier_ops = { + .bound = sun6i_csi_bridge_notifier_bound, + .complete = sun6i_csi_bridge_notifier_complete, +}; + +/* Bridge */ + +static int sun6i_csi_bridge_source_setup(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_bridge_source *source, + u32 port, + enum v4l2_mbus_type *bus_types) +{ + struct device *dev = csi_dev->dev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + struct v4l2_fwnode_endpoint *endpoint = &source->endpoint; + struct sun6i_csi_bridge_async_subdev *bridge_async_subdev; + struct fwnode_handle *handle; + int ret; + + handle = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), port, 0, 0); + if (!handle) + return -ENODEV; + + ret = v4l2_fwnode_endpoint_parse(handle, endpoint); + if (ret) + goto complete; + + if (bus_types) { + bool valid = false; + unsigned int i; + + for (i = 0; bus_types[i] != V4L2_MBUS_INVALID; i++) { + if (endpoint->bus_type == bus_types[i]) { + valid = true; + break; + } + } + + if (!valid) { + dev_err(dev, "unsupported bus type for port %d\n", + port); + ret = -EINVAL; + goto complete; + } + } + + bridge_async_subdev = + v4l2_async_nf_add_fwnode_remote(notifier, handle, + struct + sun6i_csi_bridge_async_subdev); + if (IS_ERR(bridge_async_subdev)) { + ret = PTR_ERR(bridge_async_subdev); + goto complete; + } + + bridge_async_subdev->source = source; + + source->expected = true; + +complete: + fwnode_handle_put(handle); + + return ret; +} + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev) +{ + struct device *dev = csi_dev->dev; + struct sun6i_csi_bridge *bridge = &csi_dev->bridge; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *subdev = &bridge->subdev; + struct v4l2_async_notifier *notifier = &bridge->notifier; + struct media_pad *pads = bridge->pads; + enum v4l2_mbus_type parallel_mbus_types[] = { + V4L2_MBUS_PARALLEL, + V4L2_MBUS_BT656, + V4L2_MBUS_INVALID + }; + int ret; + + mutex_init(&bridge->lock); + + /* V4L2 Subdev */ + + v4l2_subdev_init(subdev, &sun6i_csi_bridge_subdev_ops); + strscpy(subdev->name, SUN6I_CSI_BRIDGE_NAME, sizeof(subdev->name)); + subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + subdev->owner = THIS_MODULE; + subdev->dev = dev; + + v4l2_set_subdevdata(subdev, csi_dev); + + /* Media Entity */ + + subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + subdev->entity.ops = &sun6i_csi_bridge_entity_ops; + + /* Media Pads */ + + pads[SUN6I_CSI_BRIDGE_PAD_SINK].flags = MEDIA_PAD_FL_SINK; + pads[SUN6I_CSI_BRIDGE_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&subdev->entity, + SUN6I_CSI_BRIDGE_PAD_COUNT, pads); + if (ret < 0) + return ret; + + /* V4L2 Subdev */ + + if (csi_dev->isp_available) + ret = v4l2_async_register_subdev(subdev); + else + ret = v4l2_device_register_subdev(v4l2_dev, subdev); + + if (ret) { + dev_err(dev, "failed to register v4l2 subdev: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Async */ + + v4l2_async_nf_init(notifier); + notifier->ops = &sun6i_csi_bridge_notifier_ops; + + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_parallel, + SUN6I_CSI_PORT_PARALLEL, + parallel_mbus_types); + sun6i_csi_bridge_source_setup(csi_dev, &bridge->source_mipi_csi2, + SUN6I_CSI_PORT_MIPI_CSI2, NULL); + + if (csi_dev->isp_available) + ret = v4l2_async_subdev_nf_register(subdev, notifier); + else + ret = v4l2_async_nf_register(v4l2_dev, notifier); + if (ret) { + dev_err(dev, "failed to register v4l2 async notifier: %d\n", + ret); + goto error_v4l2_async_notifier; + } + + return 0; + +error_v4l2_async_notifier: + v4l2_async_nf_cleanup(notifier); + + if (csi_dev->isp_available) + v4l2_async_unregister_subdev(subdev); + else + v4l2_device_unregister_subdev(subdev); + +error_media_entity: + media_entity_cleanup(&subdev->entity); + + return ret; +} + +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + struct v4l2_async_notifier *notifier = &csi_dev->bridge.notifier; + + v4l2_async_nf_unregister(notifier); + v4l2_async_nf_cleanup(notifier); + + v4l2_device_unregister_subdev(subdev); + + media_entity_cleanup(&subdev->entity); +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h new file mode 100644 index 000000000000..ee592a14b9c5 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_bridge.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CSI_BRIDGE_H_ +#define _SUN6I_CSI_BRIDGE_H_ + +#include <media/v4l2-device.h> +#include <media/v4l2-fwnode.h> + +#define SUN6I_CSI_BRIDGE_NAME "sun6i-csi-bridge" + +enum sun6i_csi_bridge_pad { + SUN6I_CSI_BRIDGE_PAD_SINK = 0, + SUN6I_CSI_BRIDGE_PAD_SOURCE = 1, + SUN6I_CSI_BRIDGE_PAD_COUNT = 2, +}; + +struct sun6i_csi_device; + +struct sun6i_csi_bridge_format { + u32 mbus_code; + u8 input_format; + u8 input_yuv_seq; + u8 input_yuv_seq_invert; +}; + +struct sun6i_csi_bridge_source { + struct v4l2_subdev *subdev; + struct v4l2_fwnode_endpoint endpoint; + bool expected; +}; + +struct sun6i_csi_bridge_async_subdev { + struct v4l2_async_subdev async_subdev; + struct sun6i_csi_bridge_source *source; +}; + +struct sun6i_csi_bridge { + struct v4l2_subdev subdev; + struct v4l2_async_notifier notifier; + struct media_pad pads[2]; + struct v4l2_mbus_framefmt mbus_format; + struct mutex lock; /* Mbus format lock. */ + + struct sun6i_csi_bridge_source source_parallel; + struct sun6i_csi_bridge_source source_mipi_csi2; +}; + +/* Helpers */ + +void sun6i_csi_bridge_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_bridge_format(struct sun6i_csi_device *csi_dev, + u32 *mbus_code, u32 *field); + +/* Format */ + +const struct sun6i_csi_bridge_format * +sun6i_csi_bridge_format_find(u32 mbus_code); + +/* Bridge */ + +int sun6i_csi_bridge_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_bridge_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c new file mode 100644 index 000000000000..6d34f5c0768f --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.c @@ -0,0 +1,1102 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#include <linux/of.h> +#include <linux/regmap.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_csi_bridge.h" +#include "sun6i_csi_capture.h" +#include "sun6i_csi_reg.h" + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height) +{ + if (width) + *width = csi_dev->capture.format.fmt.pix.width; + if (height) + *height = csi_dev->capture.format.fmt.pix.height; +} + +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field) +{ + if (pixelformat) + *pixelformat = csi_dev->capture.format.fmt.pix.pixelformat; + + if (field) + *field = csi_dev->capture.format.fmt.pix.field; +} + +/* Format */ + +static const struct sun6i_csi_capture_format sun6i_csi_capture_formats[] = { + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565, + }, + /* YUV422 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + .input_format_raw = true, + .hsize_len_factor = 2, + }, + { + .pixelformat = V4L2_PIX_FMT_NV16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV61, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP, + .input_yuv_seq_invert = true, + }, + { + .pixelformat = V4L2_PIX_FMT_YUV422P, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P, + }, + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_NV12_16L16, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB, + }, + { + .pixelformat = V4L2_PIX_FMT_NV12, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + }, + { + .pixelformat = V4L2_PIX_FMT_NV21, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP, + .input_yuv_seq_invert = true, + }, + + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + }, + { + .pixelformat = V4L2_PIX_FMT_YVU420, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P, + .input_yuv_seq_invert = true, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .output_format_frame = SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8, + .output_format_field = SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8, + }, +}; + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_formats); i++) + if (sun6i_csi_capture_formats[i].pixelformat == pixelformat) + return &sun6i_csi_capture_formats[i]; + + return NULL; +} + +/* RAW formats need an exact match between pixel and mbus formats. */ +static const +struct sun6i_csi_capture_format_match sun6i_csi_capture_format_matches[] = { + /* YUV420 */ + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YUYV, + .mbus_code = MEDIA_BUS_FMT_YUYV8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_YVYU, + .mbus_code = MEDIA_BUS_FMT_YVYU8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_UYVY, + .mbus_code = MEDIA_BUS_FMT_UYVY8_1X16, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_2X8, + }, + { + .pixelformat = V4L2_PIX_FMT_VYUY, + .mbus_code = MEDIA_BUS_FMT_VYUY8_1X16, + }, + /* RGB */ + { + .pixelformat = V4L2_PIX_FMT_RGB565, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_LE, + }, + { + .pixelformat = V4L2_PIX_FMT_RGB565X, + .mbus_code = MEDIA_BUS_FMT_RGB565_2X8_BE, + }, + /* Bayer */ + { + .pixelformat = V4L2_PIX_FMT_SBGGR8, + .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG8, + .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB8, + .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR10, + .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG10, + .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG10, + .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB10, + .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10, + }, + { + .pixelformat = V4L2_PIX_FMT_SBGGR12, + .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGBRG12, + .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SGRBG12, + .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12, + }, + { + .pixelformat = V4L2_PIX_FMT_SRGGB12, + .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12, + }, + /* Compressed */ + { + .pixelformat = V4L2_PIX_FMT_JPEG, + .mbus_code = MEDIA_BUS_FMT_JPEG_1X8, + }, +}; + +static bool sun6i_csi_capture_format_match(u32 pixelformat, u32 mbus_code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(sun6i_csi_capture_format_matches); i++) { + const struct sun6i_csi_capture_format_match *match = + &sun6i_csi_capture_format_matches[i]; + + if (match->pixelformat == pixelformat && + match->mbus_code == mbus_code) + return true; + } + + return false; +} + +/* Capture */ + +static void +sun6i_csi_capture_buffer_configure(struct sun6i_csi_device *csi_dev, + struct sun6i_csi_buffer *csi_buffer) +{ + struct regmap *regmap = csi_dev->regmap; + const struct v4l2_format_info *info; + struct vb2_buffer *vb2_buffer; + unsigned int width, height; + dma_addr_t address; + u32 pixelformat; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + address = vb2_dma_contig_plane_dma_addr(vb2_buffer, 0); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO0_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, NULL); + + info = v4l2_format_info(pixelformat); + /* Unsupported formats are single-plane, so we can stop here. */ + if (!info) + return; + + if (info->comp_planes > 1) { + address += info->bpp[0] * width * height; + + regmap_write(regmap, SUN6I_CSI_CH_FIFO1_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } + + if (info->comp_planes > 2) { + address += info->bpp[1] * DIV_ROUND_UP(width, info->hdiv) * + DIV_ROUND_UP(height, info->vdiv); + + regmap_write(regmap, SUN6I_CSI_CH_FIFO2_ADDR_REG, + SUN6I_CSI_ADDR_VALUE(address)); + } +} + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev) +{ + struct regmap *regmap = csi_dev->regmap; + const struct sun6i_csi_capture_format *format; + const struct v4l2_format_info *info; + u32 hsize_len, vsize_len; + u32 luma_line, chroma_line = 0; + u32 pixelformat, field; + u32 width, height; + + sun6i_csi_capture_dimensions(csi_dev, &width, &height); + sun6i_csi_capture_format(csi_dev, &pixelformat, &field); + + format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!format)) + return; + + hsize_len = width; + vsize_len = height; + + /* + * When using 8-bit raw input/output (for packed YUV), we need to adapt + * the width to account for the difference in bpp when it's not 8-bit. + */ + if (format->hsize_len_factor) + hsize_len *= format->hsize_len_factor; + + regmap_write(regmap, SUN6I_CSI_CH_HSIZE_REG, + SUN6I_CSI_CH_HSIZE_LEN(hsize_len) | + SUN6I_CSI_CH_HSIZE_START(0)); + + regmap_write(regmap, SUN6I_CSI_CH_VSIZE_REG, + SUN6I_CSI_CH_VSIZE_LEN(vsize_len) | + SUN6I_CSI_CH_VSIZE_START(0)); + + switch (pixelformat) { + case V4L2_PIX_FMT_RGB565X: + luma_line = width * 2; + break; + case V4L2_PIX_FMT_NV12_16L16: + luma_line = width; + chroma_line = width; + break; + case V4L2_PIX_FMT_JPEG: + luma_line = width; + break; + default: + info = v4l2_format_info(pixelformat); + if (WARN_ON(!info)) + return; + + luma_line = width * info->bpp[0]; + + if (info->comp_planes > 1) + chroma_line = width * info->bpp[1] / info->hdiv; + break; + } + + regmap_write(regmap, SUN6I_CSI_CH_BUF_LEN_REG, + SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(chroma_line) | + SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(luma_line)); +} + +/* State */ + +static void sun6i_csi_capture_state_cleanup(struct sun6i_csi_device *csi_dev, + bool error) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer **csi_buffer_states[] = { + &state->pending, &state->current, &state->complete, + }; + struct sun6i_csi_buffer *csi_buffer; + struct vb2_buffer *vb2_buffer; + unsigned long flags; + unsigned int i; + + spin_lock_irqsave(&state->lock, flags); + + for (i = 0; i < ARRAY_SIZE(csi_buffer_states); i++) { + csi_buffer = *csi_buffer_states[i]; + if (!csi_buffer) + continue; + + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + + *csi_buffer_states[i] = NULL; + } + + list_for_each_entry(csi_buffer, &state->queue, list) { + vb2_buffer = &csi_buffer->v4l2_buffer.vb2_buf; + vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + } + + INIT_LIST_HEAD(&state->queue); + + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct sun6i_csi_buffer *csi_buffer; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (list_empty(&state->queue)) + goto complete; + + if (state->pending) + goto complete; + + csi_buffer = list_first_entry(&state->queue, struct sun6i_csi_buffer, + list); + + sun6i_csi_capture_buffer_configure(csi_dev, csi_buffer); + + list_del(&csi_buffer->list); + + state->pending = csi_buffer; + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +static void sun6i_csi_capture_state_complete(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + + if (!state->pending) + goto complete; + + state->complete = state->current; + state->current = state->pending; + state->pending = NULL; + + if (state->complete) { + struct sun6i_csi_buffer *csi_buffer = state->complete; + struct vb2_buffer *vb2_buffer = + &csi_buffer->v4l2_buffer.vb2_buf; + + vb2_buffer->timestamp = ktime_get_ns(); + csi_buffer->v4l2_buffer.sequence = state->sequence; + + vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE); + + state->complete = NULL; + } + +complete: + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + state->sequence++; + spin_unlock_irqrestore(&state->lock, flags); +} + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev) +{ + sun6i_csi_capture_state_complete(csi_dev); + sun6i_csi_capture_state_update(csi_dev); +} + +/* Queue */ + +static int sun6i_csi_capture_queue_setup(struct vb2_queue *queue, + unsigned int *buffers_count, + unsigned int *planes_count, + unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + unsigned int size = csi_dev->capture.format.fmt.pix.sizeimage; + + if (*planes_count) + return sizes[0] < size ? -EINVAL : 0; + + *planes_count = 1; + sizes[0] = size; + + return 0; +} + +static int sun6i_csi_capture_buffer_prepare(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + unsigned long size = capture->format.fmt.pix.sizeimage; + + if (vb2_plane_size(buffer, 0) < size) { + v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", + vb2_plane_size(buffer, 0), size); + return -EINVAL; + } + + vb2_set_plane_payload(buffer, 0, size); + + v4l2_buffer->field = capture->format.fmt.pix.field; + + return 0; +} + +static void sun6i_csi_capture_buffer_queue(struct vb2_buffer *buffer) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); + struct sun6i_csi_buffer *csi_buffer = + container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); + unsigned long flags; + + spin_lock_irqsave(&state->lock, flags); + list_add_tail(&csi_buffer->list, &state->queue); + spin_unlock_irqrestore(&state->lock, flags); +} + +static int sun6i_csi_capture_start_streaming(struct vb2_queue *queue, + unsigned int count) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + int ret; + + state->sequence = 0; + + ret = video_device_pipeline_alloc_start(video_dev); + if (ret < 0) + goto error_state; + + state->streaming = true; + + ret = v4l2_subdev_call(subdev, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + goto error_streaming; + + return 0; + +error_streaming: + state->streaming = false; + + video_device_pipeline_stop(video_dev); + +error_state: + sun6i_csi_capture_state_cleanup(csi_dev, false); + + return ret; +} + +static void sun6i_csi_capture_stop_streaming(struct vb2_queue *queue) +{ + struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); + struct sun6i_csi_capture_state *state = &csi_dev->capture.state; + struct video_device *video_dev = &csi_dev->capture.video_dev; + struct v4l2_subdev *subdev = &csi_dev->bridge.subdev; + + v4l2_subdev_call(subdev, video, s_stream, 0); + + state->streaming = false; + + video_device_pipeline_stop(video_dev); + + sun6i_csi_capture_state_cleanup(csi_dev, true); +} + +static const struct vb2_ops sun6i_csi_capture_queue_ops = { + .queue_setup = sun6i_csi_capture_queue_setup, + .buf_prepare = sun6i_csi_capture_buffer_prepare, + .buf_queue = sun6i_csi_capture_buffer_queue, + .start_streaming = sun6i_csi_capture_start_streaming, + .stop_streaming = sun6i_csi_capture_stop_streaming, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, +}; + +/* V4L2 Device */ + +static void sun6i_csi_capture_format_prepare(struct v4l2_format *format) +{ + struct v4l2_pix_format *pix_format = &format->fmt.pix; + const struct v4l2_format_info *info; + unsigned int width, height; + + v4l_bound_align_image(&pix_format->width, SUN6I_CSI_CAPTURE_WIDTH_MIN, + SUN6I_CSI_CAPTURE_WIDTH_MAX, 1, + &pix_format->height, SUN6I_CSI_CAPTURE_HEIGHT_MIN, + SUN6I_CSI_CAPTURE_HEIGHT_MAX, 1, 0); + + if (!sun6i_csi_capture_format_find(pix_format->pixelformat)) + pix_format->pixelformat = + sun6i_csi_capture_formats[0].pixelformat; + + width = pix_format->width; + height = pix_format->height; + + info = v4l2_format_info(pix_format->pixelformat); + + switch (pix_format->pixelformat) { + case V4L2_PIX_FMT_NV12_16L16: + pix_format->bytesperline = width * 12 / 8; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + case V4L2_PIX_FMT_JPEG: + pix_format->bytesperline = width; + pix_format->sizeimage = pix_format->bytesperline * height; + break; + default: + v4l2_fill_pixfmt(pix_format, pix_format->pixelformat, + width, height); + break; + } + + if (pix_format->field == V4L2_FIELD_ANY) + pix_format->field = V4L2_FIELD_NONE; + + if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) + pix_format->colorspace = V4L2_COLORSPACE_JPEG; + else if (info && info->pixel_enc == V4L2_PIXEL_ENC_BAYER) + pix_format->colorspace = V4L2_COLORSPACE_RAW; + else + pix_format->colorspace = V4L2_COLORSPACE_SRGB; + + pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; + pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int sun6i_csi_capture_querycap(struct file *file, void *private, + struct v4l2_capability *capability) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct video_device *video_dev = &csi_dev->capture.video_dev; + + strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); + strscpy(capability->card, video_dev->name, sizeof(capability->card)); + snprintf(capability->bus_info, sizeof(capability->bus_info), + "platform:%s", dev_name(csi_dev->dev)); + + return 0; +} + +static int sun6i_csi_capture_enum_fmt(struct file *file, void *private, + struct v4l2_fmtdesc *fmtdesc) +{ + u32 index = fmtdesc->index; + + if (index >= ARRAY_SIZE(sun6i_csi_capture_formats)) + return -EINVAL; + + fmtdesc->pixelformat = sun6i_csi_capture_formats[index].pixelformat; + + return 0; +} + +static int sun6i_csi_capture_g_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + + *format = csi_dev->capture.format; + + return 0; +} + +static int sun6i_csi_capture_s_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + if (vb2_is_busy(&capture->queue)) + return -EBUSY; + + sun6i_csi_capture_format_prepare(format); + + csi_dev->capture.format = *format; + + return 0; +} + +static int sun6i_csi_capture_try_fmt(struct file *file, void *private, + struct v4l2_format *format) +{ + sun6i_csi_capture_format_prepare(format); + + return 0; +} + +static int sun6i_csi_capture_enum_input(struct file *file, void *private, + struct v4l2_input *input) +{ + if (input->index != 0) + return -EINVAL; + + input->type = V4L2_INPUT_TYPE_CAMERA; + strscpy(input->name, "Camera", sizeof(input->name)); + + return 0; +} + +static int sun6i_csi_capture_g_input(struct file *file, void *private, + unsigned int *index) +{ + *index = 0; + + return 0; +} + +static int sun6i_csi_capture_s_input(struct file *file, void *private, + unsigned int index) +{ + if (index != 0) + return -EINVAL; + + return 0; +} + +static const struct v4l2_ioctl_ops sun6i_csi_capture_ioctl_ops = { + .vidioc_querycap = sun6i_csi_capture_querycap, + + .vidioc_enum_fmt_vid_cap = sun6i_csi_capture_enum_fmt, + .vidioc_g_fmt_vid_cap = sun6i_csi_capture_g_fmt, + .vidioc_s_fmt_vid_cap = sun6i_csi_capture_s_fmt, + .vidioc_try_fmt_vid_cap = sun6i_csi_capture_try_fmt, + + .vidioc_enum_input = sun6i_csi_capture_enum_input, + .vidioc_g_input = sun6i_csi_capture_g_input, + .vidioc_s_input = sun6i_csi_capture_s_input, + + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* V4L2 File */ + +static int sun6i_csi_capture_open(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + int ret = 0; + + if (mutex_lock_interruptible(&capture->lock)) + return -ERESTARTSYS; + + ret = v4l2_pipeline_pm_get(&capture->video_dev.entity); + if (ret < 0) + goto error_lock; + + ret = v4l2_fh_open(file); + if (ret < 0) + goto error_pipeline; + + mutex_unlock(&capture->lock); + + return 0; + +error_pipeline: + v4l2_pipeline_pm_put(&capture->video_dev.entity); + +error_lock: + mutex_unlock(&capture->lock); + + return ret; +} + +static int sun6i_csi_capture_close(struct file *file) +{ + struct sun6i_csi_device *csi_dev = video_drvdata(file); + struct sun6i_csi_capture *capture = &csi_dev->capture; + + mutex_lock(&capture->lock); + + _vb2_fop_release(file, NULL); + v4l2_pipeline_pm_put(&capture->video_dev.entity); + + mutex_unlock(&capture->lock); + + return 0; +} + +static const struct v4l2_file_operations sun6i_csi_capture_fops = { + .owner = THIS_MODULE, + .open = sun6i_csi_capture_open, + .release = sun6i_csi_capture_close, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll +}; + +/* Media Entity */ + +static int sun6i_csi_capture_link_validate(struct media_link *link) +{ + struct video_device *video_dev = + media_entity_to_video_device(link->sink->entity); + struct sun6i_csi_device *csi_dev = video_get_drvdata(video_dev); + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + const struct sun6i_csi_capture_format *capture_format; + const struct sun6i_csi_bridge_format *bridge_format; + unsigned int capture_width, capture_height; + unsigned int bridge_width, bridge_height; + const struct v4l2_format_info *format_info; + u32 pixelformat, capture_field; + u32 mbus_code, bridge_field; + bool match; + + sun6i_csi_capture_dimensions(csi_dev, &capture_width, &capture_height); + + sun6i_csi_capture_format(csi_dev, &pixelformat, &capture_field); + capture_format = sun6i_csi_capture_format_find(pixelformat); + if (WARN_ON(!capture_format)) + return -EINVAL; + + sun6i_csi_bridge_dimensions(csi_dev, &bridge_width, &bridge_height); + + sun6i_csi_bridge_format(csi_dev, &mbus_code, &bridge_field); + bridge_format = sun6i_csi_bridge_format_find(mbus_code); + if (WARN_ON(!bridge_format)) + return -EINVAL; + + /* No cropping/scaling is supported. */ + if (capture_width != bridge_width || capture_height != bridge_height) { + v4l2_err(v4l2_dev, + "invalid input/output dimensions: %ux%u/%ux%u\n", + bridge_width, bridge_height, capture_width, + capture_height); + return -EINVAL; + } + + format_info = v4l2_format_info(pixelformat); + /* Some formats are not listed. */ + if (!format_info) + return 0; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_BAYER && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_RGB && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_RAW) + goto invalid; + + if (format_info->pixel_enc == V4L2_PIXEL_ENC_YUV) { + if (bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV420 && + bridge_format->input_format != SUN6I_CSI_INPUT_FMT_YUV422) + goto invalid; + + /* YUV420 input can't produce YUV422 output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_YUV420 && + format_info->vdiv == 1) + goto invalid; + } + + /* With raw input mode, we need a 1:1 match between input and output. */ + if (bridge_format->input_format == SUN6I_CSI_INPUT_FMT_RAW || + capture_format->input_format_raw) { + match = sun6i_csi_capture_format_match(pixelformat, mbus_code); + if (!match) + goto invalid; + } + + return 0; + +invalid: + v4l2_err(v4l2_dev, "invalid input/output format combination\n"); + return -EINVAL; +} + +static const struct media_entity_operations sun6i_csi_capture_media_ops = { + .link_validate = sun6i_csi_capture_link_validate +}; + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct sun6i_csi_capture_state *state = &capture->state; + struct v4l2_device *v4l2_dev = csi_dev->v4l2_dev; + struct v4l2_subdev *bridge_subdev = &csi_dev->bridge.subdev; + struct video_device *video_dev = &capture->video_dev; + struct vb2_queue *queue = &capture->queue; + struct media_pad *pad = &capture->pad; + struct v4l2_format *format = &csi_dev->capture.format; + struct v4l2_pix_format *pix_format = &format->fmt.pix; + int ret; + + /* This may happen with multiple bridge notifier bound calls. */ + if (state->setup) + return 0; + + /* State */ + + INIT_LIST_HEAD(&state->queue); + spin_lock_init(&state->lock); + + /* Media Entity */ + + video_dev->entity.ops = &sun6i_csi_capture_media_ops; + + /* Media Pad */ + + pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + + ret = media_entity_pads_init(&video_dev->entity, 1, pad); + if (ret < 0) + return ret; + + /* Queue */ + + mutex_init(&capture->lock); + + queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + queue->io_modes = VB2_MMAP | VB2_DMABUF; + queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); + queue->ops = &sun6i_csi_capture_queue_ops; + queue->mem_ops = &vb2_dma_contig_memops; + queue->min_buffers_needed = 2; + queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + queue->lock = &capture->lock; + queue->dev = csi_dev->dev; + queue->drv_priv = csi_dev; + + ret = vb2_queue_init(queue); + if (ret) { + v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); + goto error_media_entity; + } + + /* V4L2 Format */ + + format->type = queue->type; + pix_format->pixelformat = sun6i_csi_capture_formats[0].pixelformat; + pix_format->width = 1280; + pix_format->height = 720; + pix_format->field = V4L2_FIELD_NONE; + + sun6i_csi_capture_format_prepare(format); + + /* Video Device */ + + strscpy(video_dev->name, SUN6I_CSI_CAPTURE_NAME, + sizeof(video_dev->name)); + video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; + video_dev->vfl_dir = VFL_DIR_RX; + video_dev->release = video_device_release_empty; + video_dev->fops = &sun6i_csi_capture_fops; + video_dev->ioctl_ops = &sun6i_csi_capture_ioctl_ops; + video_dev->v4l2_dev = v4l2_dev; + video_dev->queue = queue; + video_dev->lock = &capture->lock; + + video_set_drvdata(video_dev, csi_dev); + + ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to register video device: %d\n", + ret); + goto error_media_entity; + } + + /* Media Pad Link */ + + ret = media_create_pad_link(&bridge_subdev->entity, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + &video_dev->entity, 0, + csi_dev->isp_available ? 0 : + MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret < 0) { + v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n", + bridge_subdev->entity.name, + SUN6I_CSI_BRIDGE_PAD_SOURCE, + video_dev->entity.name, 0); + goto error_video_device; + } + + state->setup = true; + + return 0; + +error_video_device: + vb2_video_unregister_device(video_dev); + +error_media_entity: + media_entity_cleanup(&video_dev->entity); + + mutex_destroy(&capture->lock); + + return ret; +} + +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev) +{ + struct sun6i_csi_capture *capture = &csi_dev->capture; + struct video_device *video_dev = &capture->video_dev; + + /* This may happen if async registration failed to complete. */ + if (!capture->state.setup) + return; + + vb2_video_unregister_device(video_dev); + media_entity_cleanup(&video_dev->entity); + mutex_destroy(&capture->lock); + + capture->state.setup = false; +} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h new file mode 100644 index 000000000000..3ee5ccefbd10 --- /dev/null +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_capture.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (c) 2011-2018 Magewell Electronics Co., Ltd. (Nanjing) + * Author: Yong Deng <yong.deng@magewell.com> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> + */ + +#ifndef _SUN6I_CAPTURE_H_ +#define _SUN6I_CAPTURE_H_ + +#include <media/v4l2-device.h> + +#define SUN6I_CSI_CAPTURE_NAME "sun6i-csi-capture" + +#define SUN6I_CSI_CAPTURE_WIDTH_MIN 32 +#define SUN6I_CSI_CAPTURE_WIDTH_MAX 4800 +#define SUN6I_CSI_CAPTURE_HEIGHT_MIN 32 +#define SUN6I_CSI_CAPTURE_HEIGHT_MAX 4800 + +struct sun6i_csi_device; + +struct sun6i_csi_capture_format { + u32 pixelformat; + u8 output_format_field; + u8 output_format_frame; + bool input_yuv_seq_invert; + bool input_format_raw; + u32 hsize_len_factor; +}; + +struct sun6i_csi_capture_format_match { + u32 pixelformat; + u32 mbus_code; +}; + +#undef current +struct sun6i_csi_capture_state { + struct list_head queue; + spinlock_t lock; /* Queue and buffers lock. */ + + struct sun6i_csi_buffer *pending; + struct sun6i_csi_buffer *current; + struct sun6i_csi_buffer *complete; + + unsigned int sequence; + bool streaming; + bool setup; +}; + +struct sun6i_csi_capture { + struct sun6i_csi_capture_state state; + + struct video_device video_dev; + struct vb2_queue queue; + struct mutex lock; /* Queue lock. */ + struct media_pad pad; + + struct v4l2_format format; +}; + +/* Helpers */ + +void sun6i_csi_capture_dimensions(struct sun6i_csi_device *csi_dev, + unsigned int *width, unsigned int *height); +void sun6i_csi_capture_format(struct sun6i_csi_device *csi_dev, + u32 *pixelformat, u32 *field); + +/* Format */ + +const +struct sun6i_csi_capture_format *sun6i_csi_capture_format_find(u32 pixelformat); + +/* Capture */ + +void sun6i_csi_capture_configure(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_state_update(struct sun6i_csi_device *csi_dev); + +/* State */ + +void sun6i_csi_capture_sync(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_frame_done(struct sun6i_csi_device *csi_dev); + +/* Capture */ + +int sun6i_csi_capture_setup(struct sun6i_csi_device *csi_dev); +void sun6i_csi_capture_cleanup(struct sun6i_csi_device *csi_dev); + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h index 703fa14bb313..e01c5b9c2d60 100644 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h +++ b/drivers/media/platform/sunxi/sun6i-csi/sun6i_csi_reg.h @@ -1,196 +1,184 @@ /* 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> + * Copyright 2021-2022 Bootlin + * Author: Paul Kocialkowski <paul.kocialkowski@bootlin.com> */ -#ifndef __SUN6I_CSI_REG_H__ -#define __SUN6I_CSI_REG_H__ +#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 BIT(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__ */ +#define SUN6I_CSI_ADDR_VALUE(a) ((a) >> 2) + +#define SUN6I_CSI_EN_REG 0x0 +#define SUN6I_CSI_EN_VER_EN BIT(30) +#define SUN6I_CSI_EN_PTN_CYCLE(v) (((v) << 16) & GENMASK(23, 16)) +#define SUN6I_CSI_EN_SRAM_PWDN BIT(8) +#define SUN6I_CSI_EN_PTN_START BIT(4) +#define SUN6I_CSI_EN_CLK_CNT_SPL_VSYNC BIT(3) +#define SUN6I_CSI_EN_CLK_CNT_EN BIT(2) +#define SUN6I_CSI_EN_PTN_GEN_EN BIT(1) +#define SUN6I_CSI_EN_CSI_EN BIT(0) + +/* Note that Allwinner manuals and code invert positive/negative definitions. */ + +#define SUN6I_CSI_IF_CFG_REG 0x4 +#define SUN6I_CSI_IF_CFG_FIELD_DT_PCLK_SHIFT(v) (((v) << 24) & GENMASK(27, 24)) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_PROGRESSIVE (0 << 21) +#define SUN6I_CSI_IF_CFG_SRC_TYPE_INTERLACED (1 << 21) +#define SUN6I_CSI_IF_CFG_FPS_DS BIT(20) +#define SUN6I_CSI_IF_CFG_FIELD_POSITIVE (0 << 19) +#define SUN6I_CSI_IF_CFG_FIELD_NEGATIVE (1 << 19) +#define SUN6I_CSI_IF_CFG_VREF_POL_POSITIVE (0 << 18) +#define SUN6I_CSI_IF_CFG_VREF_POL_NEGATIVE (1 << 18) +#define SUN6I_CSI_IF_CFG_HREF_POL_POSITIVE (0 << 17) +#define SUN6I_CSI_IF_CFG_HREF_POL_NEGATIVE (1 << 17) +#define SUN6I_CSI_IF_CFG_CLK_POL_FALLING (0 << 16) +#define SUN6I_CSI_IF_CFG_CLK_POL_RISING (1 << 16) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD_VSYNC (0 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_FIELD (1 << 14) +#define SUN6I_CSI_IF_CFG_FIELD_DT_VSYNC (2 << 14) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8 (0 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_10 (1 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_12 (2 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_8_PLUS_2 (3 << 8) +#define SUN6I_CSI_IF_CFG_DATA_WIDTH_2_TIMES_8 (4 << 8) +#define SUN6I_CSI_IF_CFG_IF_CSI (0 << 7) +#define SUN6I_CSI_IF_CFG_IF_MIPI (1 << 7) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_RAW (0 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_YUV_COMBINED (1 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT656 (4 << 0) +#define SUN6I_CSI_IF_CFG_IF_CSI_BT1120 (5 << 0) + +#define SUN6I_CSI_CAP_REG 0x8 +#define SUN6I_CSI_CAP_MASK(v) (((v) << 2) & GENMASK(5, 2)) +#define SUN6I_CSI_CAP_VCAP_ON BIT(1) +#define SUN6I_CSI_CAP_SCAP_ON BIT(0) + +#define SUN6I_CSI_SYNC_CNT_REG 0xc +#define SUN6I_CSI_FIFO_THRS_REG 0x10 +#define SUN6I_CSI_BT656_HEAD_CFG_REG 0x14 + +#define SUN6I_CSI_PTN_LEN_REG 0x30 +#define SUN6I_CSI_PTN_ADDR_REG 0x34 +#define SUN6I_CSI_VER_REG 0x3c + +#define SUN6I_CSI_CH_CFG_REG 0x44 +#define SUN6I_CSI_CH_CFG_PAD_VAL(v) (((v) << 24) & GENMASK(31, 24)) +#define SUN6I_CSI_CH_CFG_INPUT_FMT(v) (((v) << 20) & GENMASK(23, 20)) +#define SUN6I_CSI_CH_CFG_OUTPUT_FMT(v) (((v) << 16) & GENMASK(19, 16)) +#define SUN6I_CSI_CH_CFG_VFLIP_EN BIT(13) +#define SUN6I_CSI_CH_CFG_HFLIP_EN BIT(12) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD0 (0 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_FIELD1 (1 << 10) +#define SUN6I_CSI_CH_CFG_FIELD_SEL_EITHER (2 << 10) +#define SUN6I_CSI_CH_CFG_INPUT_YUV_SEQ(v) (((v) << 8) & GENMASK(9, 8)) + +#define SUN6I_CSI_INPUT_FMT_RAW 0 +#define SUN6I_CSI_INPUT_FMT_YUV422 3 +#define SUN6I_CSI_INPUT_FMT_YUV420 4 + +/* Note that Allwinner manuals and code invert frame/field definitions. */ + +/* RAW */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_8 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_10 1 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RAW_12 2 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB565 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_RGB888 5 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_PRGB888 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_8 8 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_10 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RAW_12 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB565 12 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_RGB888 13 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_PRGB888 14 + +/* YUV */ +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422P 0 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420P 1 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420P 2 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422P 3 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP 4 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP 5 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420SP 6 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422SP 7 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422MB 8 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420MB 9 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV420MB 10 +#define SUN6I_CSI_OUTPUT_FMT_FIELD_YUV422MB 11 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV422SP_10 12 +#define SUN6I_CSI_OUTPUT_FMT_FRAME_YUV420SP_10 13 + +/* YUV Planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_YUYV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_YVYU 1 +#define SUN6I_CSI_INPUT_YUV_SEQ_UYVY 2 +#define SUN6I_CSI_INPUT_YUV_SEQ_VYUY 3 + +/* YUV Semi-planar */ +#define SUN6I_CSI_INPUT_YUV_SEQ_UV 0 +#define SUN6I_CSI_INPUT_YUV_SEQ_VU 1 + +#define SUN6I_CSI_CH_SCALE_REG 0x4c +#define SUN6I_CSI_CH_SCALE_QUART_EN BIT(0) + +#define SUN6I_CSI_CH_FIFO0_ADDR_REG 0x50 +#define SUN6I_CSI_CH_FIFO1_ADDR_REG 0x58 +#define SUN6I_CSI_CH_FIFO2_ADDR_REG 0x60 + +#define SUN6I_CSI_CH_STA_REG 0x6c +#define SUN6I_CSI_CH_STA_FIELD BIT(2) +#define SUN6I_CSI_CH_STA_VCAP BIT(1) +#define SUN6I_CSI_CH_STA_SCAP BIT(0) + +#define SUN6I_CSI_CH_INT_EN_REG 0x70 +#define SUN6I_CSI_CH_INT_EN_VS BIT(7) +#define SUN6I_CSI_CH_INT_EN_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_EN_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_EN_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_EN_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_EN_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_EN_FD BIT(1) +#define SUN6I_CSI_CH_INT_EN_CD BIT(0) + +#define SUN6I_CSI_CH_INT_STA_REG 0x74 +#define SUN6I_CSI_CH_INT_STA_CLEAR 0xff +#define SUN6I_CSI_CH_INT_STA_VS BIT(7) +#define SUN6I_CSI_CH_INT_STA_HB_OF BIT(6) +#define SUN6I_CSI_CH_INT_STA_MUL_ERR BIT(5) +#define SUN6I_CSI_CH_INT_STA_FIFO2_OF BIT(4) +#define SUN6I_CSI_CH_INT_STA_FIFO1_OF BIT(3) +#define SUN6I_CSI_CH_INT_STA_FIFO0_OF BIT(2) +#define SUN6I_CSI_CH_INT_STA_FD BIT(1) +#define SUN6I_CSI_CH_INT_STA_CD BIT(0) + +#define SUN6I_CSI_CH_FLD1_VSIZE_REG 0x78 +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLD1_VSIZE_VER_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_HSIZE_REG 0x80 +#define SUN6I_CSI_CH_HSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_HSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_VSIZE_REG 0x84 +#define SUN6I_CSI_CH_VSIZE_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_VSIZE_START(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_BUF_LEN_REG 0x88 +#define SUN6I_CSI_CH_BUF_LEN_CHROMA_LINE(v) (((v) << 16) & GENMASK(29, 16)) +#define SUN6I_CSI_CH_BUF_LEN_LUMA_LINE(v) ((v) & GENMASK(13, 0)) + +#define SUN6I_CSI_CH_FLIP_SIZE_REG 0x8c +#define SUN6I_CSI_CH_FLIP_SIZE_VER_LEN(v) (((v) << 16) & GENMASK(28, 16)) +#define SUN6I_CSI_CH_FLIP_SIZE_VALID_LEN(v) ((v) & GENMASK(12, 0)) + +#define SUN6I_CSI_CH_FRM_CLK_CNT_REG 0x90 +#define SUN6I_CSI_CH_ACC_ITNL_CLK_CNT_REG 0x94 +#define SUN6I_CSI_CH_FIFO_STAT_REG 0x98 +#define SUN6I_CSI_CH_PCLK_STAT_REG 0x9c + +#endif diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c deleted file mode 100644 index 791583d23a65..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.c +++ /dev/null @@ -1,733 +0,0 @@ -// 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) - -/* Helpers */ - -static struct v4l2_subdev * -sun6i_video_remote_subdev(struct sun6i_video *video, u32 *pad) -{ - struct media_pad *remote; - - remote = media_pad_remote_pad_first(&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); -} - -/* Format */ - -static const u32 sun6i_video_formats[] = { - 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_NV12_16L16, - 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, - V4L2_PIX_FMT_RGB565, - V4L2_PIX_FMT_RGB565X, - V4L2_PIX_FMT_JPEG, -}; - -static bool sun6i_video_format_check(u32 format) -{ - unsigned int i; - - for (i = 0; i < ARRAY_SIZE(sun6i_video_formats); i++) - if (sun6i_video_formats[i] == format) - return true; - - return false; -} - -/* Video */ - -static void sun6i_video_buffer_configure(struct sun6i_csi_device *csi_dev, - struct sun6i_csi_buffer *csi_buffer) -{ - csi_buffer->queued_to_csi = true; - sun6i_csi_update_buf_addr(csi_dev, csi_buffer->dma_addr); -} - -static void sun6i_video_configure(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_config config = { 0 }; - - config.pixelformat = video->format.fmt.pix.pixelformat; - config.code = video->mbus_code; - config.field = video->format.fmt.pix.field; - config.width = video->format.fmt.pix.width; - config.height = video->format.fmt.pix.height; - - sun6i_csi_update_config(csi_dev, &config); -} - -/* Queue */ - -static int sun6i_video_queue_setup(struct vb2_queue *queue, - unsigned int *buffers_count, - unsigned int *planes_count, - unsigned int sizes[], - struct device *alloc_devs[]) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - unsigned int size = video->format.fmt.pix.sizeimage; - - if (*planes_count) - return sizes[0] < size ? -EINVAL : 0; - - *planes_count = 1; - sizes[0] = size; - - return 0; -} - -static int sun6i_video_buffer_prepare(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long size = video->format.fmt.pix.sizeimage; - - if (vb2_plane_size(buffer, 0) < size) { - v4l2_err(v4l2_dev, "buffer too small (%lu < %lu)\n", - vb2_plane_size(buffer, 0), size); - return -EINVAL; - } - - vb2_set_plane_payload(buffer, 0, size); - - csi_buffer->dma_addr = vb2_dma_contig_plane_dma_addr(buffer, 0); - v4l2_buffer->field = video->format.fmt.pix.field; - - return 0; -} - -static void sun6i_video_buffer_queue(struct vb2_buffer *buffer) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(buffer->vb2_queue); - struct sun6i_video *video = &csi_dev->video; - struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(buffer); - struct sun6i_csi_buffer *csi_buffer = - container_of(v4l2_buffer, struct sun6i_csi_buffer, v4l2_buffer); - unsigned long flags; - - spin_lock_irqsave(&video->dma_queue_lock, flags); - csi_buffer->queued_to_csi = false; - list_add_tail(&csi_buffer->list, &video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -static int sun6i_video_start_streaming(struct vb2_queue *queue, - unsigned int count) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct v4l2_subdev *subdev; - unsigned long flags; - int ret; - - video->sequence = 0; - - ret = video_device_pipeline_alloc_start(video_dev); - if (ret < 0) - goto error_dma_queue_flush; - - if (video->mbus_code == 0) { - ret = -EINVAL; - goto error_media_pipeline; - } - - subdev = sun6i_video_remote_subdev(video, NULL); - if (!subdev) { - ret = -EINVAL; - goto error_media_pipeline; - } - - sun6i_video_configure(csi_dev); - - spin_lock_irqsave(&video->dma_queue_lock, flags); - - buf = list_first_entry(&video->dma_queue, - struct sun6i_csi_buffer, list); - sun6i_video_buffer_configure(csi_dev, buf); - - sun6i_csi_set_stream(csi_dev, true); - - /* - * CSI will lookup the next dma buffer for next frame before 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); - sun6i_video_buffer_configure(csi_dev, next_buf); - - spin_unlock_irqrestore(&video->dma_queue_lock, flags); - - ret = v4l2_subdev_call(subdev, video, s_stream, 1); - if (ret && ret != -ENOIOCTLCMD) - goto error_stream; - - return 0; - -error_stream: - sun6i_csi_set_stream(csi_dev, false); - -error_media_pipeline: - video_device_pipeline_stop(video_dev); - -error_dma_queue_flush: - spin_lock_irqsave(&video->dma_queue_lock, flags); - list_for_each_entry(buf, &video->dma_queue, list) - vb2_buffer_done(&buf->v4l2_buffer.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 *queue) -{ - struct sun6i_csi_device *csi_dev = vb2_get_drv_priv(queue); - struct sun6i_video *video = &csi_dev->video; - 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(csi_dev, false); - - video_device_pipeline_stop(&video->video_dev); - - /* 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->v4l2_buffer.vb2_buf, VB2_BUF_STATE_ERROR); - INIT_LIST_HEAD(&video->dma_queue); - spin_unlock_irqrestore(&video->dma_queue_lock, flags); -} - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct sun6i_csi_buffer *buf; - struct sun6i_csi_buffer *next_buf; - struct vb2_v4l2_buffer *v4l2_buffer; - - 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(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - 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) { - sun6i_video_buffer_configure(csi_dev, next_buf); - dev_dbg(csi_dev->dev, "Frame dropped!\n"); - goto complete; - } - - list_del(&buf->list); - v4l2_buffer = &buf->v4l2_buffer; - v4l2_buffer->vb2_buf.timestamp = ktime_get_ns(); - v4l2_buffer->sequence = video->sequence; - vb2_buffer_done(&v4l2_buffer->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); - sun6i_video_buffer_configure(csi_dev, next_buf); - } else { - dev_dbg(csi_dev->dev, "Next frame will be dropped!\n"); - } - -complete: - video->sequence++; - spin_unlock(&video->dma_queue_lock); -} - -static const struct vb2_ops sun6i_video_queue_ops = { - .queue_setup = sun6i_video_queue_setup, - .buf_prepare = sun6i_video_buffer_prepare, - .buf_queue = sun6i_video_buffer_queue, - .start_streaming = sun6i_video_start_streaming, - .stop_streaming = sun6i_video_stop_streaming, - .wait_prepare = vb2_ops_wait_prepare, - .wait_finish = vb2_ops_wait_finish, -}; - -/* V4L2 Device */ - -static int sun6i_video_querycap(struct file *file, void *private, - struct v4l2_capability *capability) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct video_device *video_dev = &csi_dev->video.video_dev; - - strscpy(capability->driver, SUN6I_CSI_NAME, sizeof(capability->driver)); - strscpy(capability->card, video_dev->name, sizeof(capability->card)); - snprintf(capability->bus_info, sizeof(capability->bus_info), - "platform:%s", dev_name(csi_dev->dev)); - - return 0; -} - -static int sun6i_video_enum_fmt(struct file *file, void *private, - struct v4l2_fmtdesc *fmtdesc) -{ - u32 index = fmtdesc->index; - - if (index >= ARRAY_SIZE(sun6i_video_formats)) - return -EINVAL; - - fmtdesc->pixelformat = sun6i_video_formats[index]; - - return 0; -} - -static int sun6i_video_g_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - *format = video->format; - - return 0; -} - -static int sun6i_video_format_try(struct sun6i_video *video, - struct v4l2_format *format) -{ - struct v4l2_pix_format *pix_format = &format->fmt.pix; - int bpp; - - if (!sun6i_video_format_check(pix_format->pixelformat)) - pix_format->pixelformat = sun6i_video_formats[0]; - - v4l_bound_align_image(&pix_format->width, MIN_WIDTH, MAX_WIDTH, 1, - &pix_format->height, MIN_HEIGHT, MAX_WIDTH, 1, 1); - - bpp = sun6i_csi_get_bpp(pix_format->pixelformat); - pix_format->bytesperline = (pix_format->width * bpp) >> 3; - pix_format->sizeimage = pix_format->bytesperline * pix_format->height; - - if (pix_format->field == V4L2_FIELD_ANY) - pix_format->field = V4L2_FIELD_NONE; - - if (pix_format->pixelformat == V4L2_PIX_FMT_JPEG) - pix_format->colorspace = V4L2_COLORSPACE_JPEG; - else - pix_format->colorspace = V4L2_COLORSPACE_SRGB; - - pix_format->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; - pix_format->quantization = V4L2_QUANTIZATION_DEFAULT; - pix_format->xfer_func = V4L2_XFER_FUNC_DEFAULT; - - return 0; -} - -static int sun6i_video_format_set(struct sun6i_video *video, - struct v4l2_format *format) -{ - int ret; - - ret = sun6i_video_format_try(video, format); - if (ret) - return ret; - - video->format = *format; - - return 0; -} - -static int sun6i_video_s_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - if (vb2_is_busy(&video->queue)) - return -EBUSY; - - return sun6i_video_format_set(video, format); -} - -static int sun6i_video_try_fmt(struct file *file, void *private, - struct v4l2_format *format) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - - return sun6i_video_format_try(video, format); -} - -static int sun6i_video_enum_input(struct file *file, void *private, - struct v4l2_input *input) -{ - if (input->index != 0) - return -EINVAL; - - input->type = V4L2_INPUT_TYPE_CAMERA; - strscpy(input->name, "Camera", sizeof(input->name)); - - return 0; -} - -static int sun6i_video_g_input(struct file *file, void *private, - unsigned int *index) -{ - *index = 0; - - return 0; -} - -static int sun6i_video_s_input(struct file *file, void *private, - unsigned int index) -{ - if (index != 0) - return -EINVAL; - - return 0; -} - -static const struct v4l2_ioctl_ops sun6i_video_ioctl_ops = { - .vidioc_querycap = sun6i_video_querycap, - - .vidioc_enum_fmt_vid_cap = sun6i_video_enum_fmt, - .vidioc_g_fmt_vid_cap = sun6i_video_g_fmt, - .vidioc_s_fmt_vid_cap = sun6i_video_s_fmt, - .vidioc_try_fmt_vid_cap = sun6i_video_try_fmt, - - .vidioc_enum_input = sun6i_video_enum_input, - .vidioc_g_input = sun6i_video_g_input, - .vidioc_s_input = sun6i_video_s_input, - - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, -}; - -/* V4L2 File */ - -static int sun6i_video_open(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - int ret = 0; - - if (mutex_lock_interruptible(&video->lock)) - return -ERESTARTSYS; - - ret = v4l2_fh_open(file); - if (ret < 0) - goto error_lock; - - ret = v4l2_pipeline_pm_get(&video->video_dev.entity); - if (ret < 0) - goto error_v4l2_fh; - - /* Power on at first open. */ - if (v4l2_fh_is_singular_file(file)) { - ret = sun6i_csi_set_power(csi_dev, true); - if (ret < 0) - goto error_v4l2_fh; - } - - mutex_unlock(&video->lock); - - return 0; - -error_v4l2_fh: - v4l2_fh_release(file); - -error_lock: - mutex_unlock(&video->lock); - - return ret; -} - -static int sun6i_video_close(struct file *file) -{ - struct sun6i_csi_device *csi_dev = video_drvdata(file); - struct sun6i_video *video = &csi_dev->video; - bool last_close; - - mutex_lock(&video->lock); - - last_close = v4l2_fh_is_singular_file(file); - - _vb2_fop_release(file, NULL); - v4l2_pipeline_pm_put(&video->video_dev.entity); - - /* Power off at last close. */ - if (last_close) - sun6i_csi_set_power(csi_dev, 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 Entity */ - -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_csi_device *csi_dev = video_get_drvdata(vdev); - struct sun6i_video *video = &csi_dev->video; - struct v4l2_subdev_format source_fmt; - int ret; - - video->mbus_code = 0; - - if (!media_pad_remote_pad_first(link->sink->entity->pads)) { - dev_info(csi_dev->dev, "video node %s pad not connected\n", - vdev->name); - return -ENOLINK; - } - - ret = sun6i_video_link_validate_get_format(link->source, &source_fmt); - if (ret < 0) - return ret; - - if (!sun6i_csi_is_format_supported(csi_dev, - video->format.fmt.pix.pixelformat, - source_fmt.format.code)) { - dev_err(csi_dev->dev, - "Unsupported pixformat: 0x%x with mbus code: 0x%x!\n", - video->format.fmt.pix.pixelformat, - source_fmt.format.code); - return -EPIPE; - } - - if (source_fmt.format.width != video->format.fmt.pix.width || - source_fmt.format.height != video->format.fmt.pix.height) { - dev_err(csi_dev->dev, - "Wrong width or height %ux%u (%ux%u expected)\n", - video->format.fmt.pix.width, video->format.fmt.pix.height, - source_fmt.format.width, source_fmt.format.height); - return -EPIPE; - } - - 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 -}; - -/* Video */ - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct v4l2_device *v4l2_dev = &csi_dev->v4l2.v4l2_dev; - struct video_device *video_dev = &video->video_dev; - struct vb2_queue *queue = &video->queue; - struct media_pad *pad = &video->pad; - struct v4l2_format format = { 0 }; - struct v4l2_pix_format *pix_format = &format.fmt.pix; - int ret; - - /* Media Entity */ - - video_dev->entity.ops = &sun6i_video_media_ops; - - /* Media Pad */ - - pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; - - ret = media_entity_pads_init(&video_dev->entity, 1, pad); - if (ret < 0) - return ret; - - /* DMA queue */ - - INIT_LIST_HEAD(&video->dma_queue); - spin_lock_init(&video->dma_queue_lock); - - video->sequence = 0; - - /* Queue */ - - mutex_init(&video->lock); - - queue->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; - queue->io_modes = VB2_MMAP | VB2_DMABUF; - queue->buf_struct_size = sizeof(struct sun6i_csi_buffer); - queue->ops = &sun6i_video_queue_ops; - queue->mem_ops = &vb2_dma_contig_memops; - queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; - queue->lock = &video->lock; - queue->dev = csi_dev->dev; - queue->drv_priv = csi_dev; - - /* Make sure non-dropped frame. */ - queue->min_buffers_needed = 3; - - ret = vb2_queue_init(queue); - if (ret) { - v4l2_err(v4l2_dev, "failed to initialize vb2 queue: %d\n", ret); - goto error_media_entity; - } - - /* V4L2 Format */ - - format.type = queue->type; - pix_format->pixelformat = sun6i_video_formats[0]; - pix_format->width = 1280; - pix_format->height = 720; - pix_format->field = V4L2_FIELD_NONE; - - sun6i_video_format_set(video, &format); - - /* Video Device */ - - strscpy(video_dev->name, SUN6I_CSI_NAME, sizeof(video_dev->name)); - video_dev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING; - video_dev->vfl_dir = VFL_DIR_RX; - video_dev->release = video_device_release_empty; - video_dev->fops = &sun6i_video_fops; - video_dev->ioctl_ops = &sun6i_video_ioctl_ops; - video_dev->v4l2_dev = v4l2_dev; - video_dev->queue = queue; - video_dev->lock = &video->lock; - - video_set_drvdata(video_dev, csi_dev); - - ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1); - if (ret < 0) { - v4l2_err(v4l2_dev, "failed to register video device: %d\n", - ret); - goto error_media_entity; - } - - return 0; - -error_media_entity: - media_entity_cleanup(&video_dev->entity); - - mutex_destroy(&video->lock); - - return ret; -} - -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev) -{ - struct sun6i_video *video = &csi_dev->video; - struct video_device *video_dev = &video->video_dev; - - vb2_video_unregister_device(video_dev); - media_entity_cleanup(&video_dev->entity); - mutex_destroy(&video->lock); -} diff --git a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h b/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h deleted file mode 100644 index a917d2da6deb..000000000000 --- a/drivers/media/platform/sunxi/sun6i-csi/sun6i_video.h +++ /dev/null @@ -1,35 +0,0 @@ -/* 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_device; - -struct sun6i_video { - struct video_device video_dev; - struct vb2_queue queue; - struct mutex lock; /* Queue lock. */ - struct media_pad pad; - - struct list_head dma_queue; - spinlock_t dma_queue_lock; /* DMA queue lock. */ - - struct v4l2_format format; - u32 mbus_code; - unsigned int sequence; -}; - -int sun6i_video_setup(struct sun6i_csi_device *csi_dev); -void sun6i_video_cleanup(struct sun6i_csi_device *csi_dev); - -void sun6i_video_frame_done(struct sun6i_csi_device *csi_dev); - -#endif /* __SUN6I_VIDEO_H__ */ diff --git a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c index 30d6c0c5161f..484ac5f054d5 100644 --- a/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun6i-mipi-csi2/sun6i_mipi_csi2.c @@ -498,6 +498,7 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -519,8 +520,10 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN6I_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN6I_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN6I_MIPI_CSI2_PAD_COUNT, pads); @@ -533,12 +536,17 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) notifier->ops = &sun6i_mipi_csi2_notifier_ops; ret = sun6i_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -549,7 +557,8 @@ static int sun6i_mipi_csi2_bridge_setup(struct sun6i_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); diff --git a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c index b032ec13a683..d993c09a4820 100644 --- a/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c +++ b/drivers/media/platform/sunxi/sun8i-a83t-mipi-csi2/sun8i_a83t_mipi_csi2.c @@ -536,6 +536,7 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) struct v4l2_async_notifier *notifier = &bridge->notifier; struct media_pad *pads = bridge->pads; struct device *dev = csi2_dev->dev; + bool notifier_registered = false; int ret; mutex_init(&bridge->lock); @@ -557,8 +558,10 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) /* Media Pads */ - pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK; - pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SINK].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + pads[SUN8I_A83T_MIPI_CSI2_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE | + MEDIA_PAD_FL_MUST_CONNECT; ret = media_entity_pads_init(&subdev->entity, SUN8I_A83T_MIPI_CSI2_PAD_COUNT, pads); @@ -571,12 +574,17 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) notifier->ops = &sun8i_a83t_mipi_csi2_notifier_ops; ret = sun8i_a83t_mipi_csi2_bridge_source_setup(csi2_dev); - if (ret) + if (ret && ret != -ENODEV) goto error_v4l2_notifier_cleanup; - ret = v4l2_async_subdev_nf_register(subdev, notifier); - if (ret < 0) - goto error_v4l2_notifier_cleanup; + /* Only register the notifier when a sensor is connected. */ + if (ret != -ENODEV) { + ret = v4l2_async_subdev_nf_register(subdev, notifier); + if (ret < 0) + goto error_v4l2_notifier_cleanup; + + notifier_registered = true; + } /* V4L2 Subdev */ @@ -587,7 +595,8 @@ sun8i_a83t_mipi_csi2_bridge_setup(struct sun8i_a83t_mipi_csi2_device *csi2_dev) return 0; error_v4l2_notifier_unregister: - v4l2_async_nf_unregister(notifier); + if (notifier_registered) + v4l2_async_nf_unregister(notifier); error_v4l2_notifier_cleanup: v4l2_async_nf_cleanup(notifier); |