Re: [PATCH v3 3/4] staging: media: Add support for the Allwinner A31 ISP

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Paul,

Thanks for the set.

A few comments below.

On Fri, Apr 15, 2022 at 05:37:07PM +0200, Paul Kocialkowski wrote:
> Some Allwinner platforms come with an Image Signal Processor, which
> supports various features in order to enhance and transform data
> received by image sensors into good-looking pictures. In most cases,
> the data is raw bayer, which gets internally converted to RGB and
> finally YUV, which is what the hardware produces.
> 
> This driver supports ISPs that are similar to the A31 ISP, which was
> the first standalone ISP found in Allwinner platforms. Simpler ISP
> blocks were found in the A10 and A20, where they are tied to a CSI
> controller. Newer generations of Allwinner SoCs (starting with the
> H6, H616, etc) come with a new camera subsystem and revised ISP.
> Even though these previous and next-generation ISPs are somewhat
> similar to the A31 ISP, they have enough significant differences to
> be out of the scope of this driver.
> 
> While the ISP supports many features, including 3A and many
> enhancement blocks, this implementation is limited to the following:
> - V3s (V3/S3) platform support;
> - Bayer media bus formats as input;
> - Semi-planar YUV (NV12/NV21) as output;
> - Debayering with per-component gain and offset configuration;
> - 2D noise filtering with configurable coefficients.
> 
> Since many features are missing from the associated uAPI, the driver
> is aimed to integrate staging until all features are properly
> described.
> 
> On the technical side, it uses the v4l2 and media controller APIs,
> with a video node for capture, a processor subdev and a video node
> for parameters submission. A specific uAPI structure and associated
> v4l2 meta format are used to configure parameters of the supported
> modules.
> 
> One particular thing about the hardware is that configuration for
> module registers needs to be stored in a DMA buffer and gets copied
> to actual registers by the hardware at the next vsync, when instructed
> by a flag. This is handled by the "state" mechanism in the driver.
> 
> Signed-off-by: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> ---
>  drivers/staging/media/sunxi/Kconfig           |   1 +
>  drivers/staging/media/sunxi/Makefile          |   1 +
>  drivers/staging/media/sunxi/sun6i-isp/Kconfig |  15 +
>  .../staging/media/sunxi/sun6i-isp/Makefile    |   4 +
>  .../staging/media/sunxi/sun6i-isp/TODO.txt    |   6 +

This should be called TODO (without .txt).

I understand this is an online ISP. How do you schedule the video buffer
queues? Say, what happens if it's time to set up buffers for a frame and
there's a buffer queued in the parameter queue but not in the image data
queue? Or the other way around?

>  .../staging/media/sunxi/sun6i-isp/sun6i_isp.c | 556 +++++++++++++
>  .../staging/media/sunxi/sun6i-isp/sun6i_isp.h |  85 ++
>  .../media/sunxi/sun6i-isp/sun6i_isp_capture.c | 749 ++++++++++++++++++
>  .../media/sunxi/sun6i-isp/sun6i_isp_capture.h |  78 ++
>  .../media/sunxi/sun6i-isp/sun6i_isp_params.c  | 573 ++++++++++++++
>  .../media/sunxi/sun6i-isp/sun6i_isp_params.h  |  52 ++
>  .../media/sunxi/sun6i-isp/sun6i_isp_proc.c    | 578 ++++++++++++++
>  .../media/sunxi/sun6i-isp/sun6i_isp_proc.h    |  66 ++
>  .../media/sunxi/sun6i-isp/sun6i_isp_reg.h     | 275 +++++++
>  .../sunxi/sun6i-isp/uapi/sun6i-isp-config.h   |  43 +
>  15 files changed, 3082 insertions(+)
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/Kconfig
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/Makefile
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/TODO.txt
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h
>  create mode 100644 drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h
> 
> diff --git a/drivers/staging/media/sunxi/Kconfig b/drivers/staging/media/sunxi/Kconfig
> index 4549a135741f..62a486aba88b 100644
> --- a/drivers/staging/media/sunxi/Kconfig
> +++ b/drivers/staging/media/sunxi/Kconfig
> @@ -12,5 +12,6 @@ config VIDEO_SUNXI
>  if VIDEO_SUNXI
>  
>  source "drivers/staging/media/sunxi/cedrus/Kconfig"
> +source "drivers/staging/media/sunxi/sun6i-isp/Kconfig"
>  
>  endif
> diff --git a/drivers/staging/media/sunxi/Makefile b/drivers/staging/media/sunxi/Makefile
> index b87140b0e15f..3d20b2f0e644 100644
> --- a/drivers/staging/media/sunxi/Makefile
> +++ b/drivers/staging/media/sunxi/Makefile
> @@ -1,2 +1,3 @@
>  # SPDX-License-Identifier: GPL-2.0
>  obj-$(CONFIG_VIDEO_SUNXI_CEDRUS)	+= cedrus/
> +obj-$(CONFIG_VIDEO_SUN6I_ISP)		+= sun6i-isp/
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/Kconfig b/drivers/staging/media/sunxi/sun6i-isp/Kconfig
> new file mode 100644
> index 000000000000..68dcae9cd7d7
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config VIDEO_SUN6I_ISP
> +	tristate "Allwinner A31 Image Signal Processor (ISP) Driver"
> +	depends on V4L_PLATFORM_DRIVERS && VIDEO_DEV
> +	depends on ARCH_SUNXI || COMPILE_TEST
> +	depends on PM && COMMON_CLK && HAS_DMA
> +	select MEDIA_CONTROLLER
> +	select VIDEO_V4L2_SUBDEV_API
> +	select VIDEOBUF2_DMA_CONTIG
> +	select VIDEOBUF2_VMALLOC
> +	select V4L2_FWNODE
> +	select REGMAP_MMIO
> +	help
> +	   Support for the Allwinner A31 Image Signal Processor (ISP), also
> +	   found on other platforms such as the A80, A83T or V3/V3s.
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/Makefile b/drivers/staging/media/sunxi/sun6i-isp/Makefile
> new file mode 100644
> index 000000000000..da1034785144
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/Makefile
> @@ -0,0 +1,4 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +sun6i-isp-y += sun6i_isp.o sun6i_isp_proc.o sun6i_isp_capture.o sun6i_isp_params.o
> +
> +obj-$(CONFIG_VIDEO_SUN6I_ISP) += sun6i-isp.o
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/TODO.txt b/drivers/staging/media/sunxi/sun6i-isp/TODO.txt
> new file mode 100644
> index 000000000000..1e3236edc1ab
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/TODO.txt
> @@ -0,0 +1,6 @@
> +Unstaging requirements:
> +- Add uAPI support and documentation for the configuration of all the hardware
> +  modules and description of the statistics data structures;
> +- Add support for statistics reporting;
> +- Add userspace support in libcamera which demonstrates the ability to receive
> +  statistics and adapt hardware modules configuration accordingly;
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c
> new file mode 100644
> index 000000000000..118eee625eba
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.c
> @@ -0,0 +1,556 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/err.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <linux/reset.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-mc.h>
> +
> +#include "sun6i_isp.h"
> +#include "sun6i_isp_capture.h"
> +#include "sun6i_isp_params.h"
> +#include "sun6i_isp_proc.h"
> +#include "sun6i_isp_reg.h"
> +
> +/* Helpers */
> +
> +u32 sun6i_isp_load_read(struct sun6i_isp_device *isp_dev, u32 offset)
> +{
> +	u32 *data = (u32 *)(isp_dev->tables.load.data + offset);
> +
> +	return *data;
> +}
> +
> +void sun6i_isp_load_write(struct sun6i_isp_device *isp_dev, u32 offset,
> +			  u32 value)
> +{
> +	u32 *data = (u32 *)(isp_dev->tables.load.data + offset);
> +
> +	*data = value;
> +}
> +
> +/* State */
> +
> +/*
> + * The ISP works with a load buffer, which gets copied to the actual registers
> + * by the hardware before processing a frame when a specific flag is set.
> + * This is represented by tracking the ISP state in the different parts of
> + * the code with explicit sync points:
> + * - state update: to update the load buffer for the next frame if necessary;
> + * - state complete: to indicate that the state update was applied.
> + */
> +
> +static void sun6i_isp_state_ready(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +	u32 value;
> +
> +	regmap_read(regmap, SUN6I_ISP_FE_CTRL_REG, &value);
> +	value |= SUN6I_ISP_FE_CTRL_PARA_READY;
> +	regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG, value);
> +}
> +
> +static void sun6i_isp_state_complete(struct sun6i_isp_device *isp_dev)
> +{
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&isp_dev->state_lock, flags);
> +
> +	sun6i_isp_capture_state_complete(isp_dev);
> +	sun6i_isp_params_state_complete(isp_dev);
> +
> +	spin_unlock_irqrestore(&isp_dev->state_lock, flags);
> +}
> +
> +void sun6i_isp_state_update(struct sun6i_isp_device *isp_dev, bool ready_hold)
> +{
> +	bool update = false;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&isp_dev->state_lock, flags);
> +
> +	sun6i_isp_capture_state_update(isp_dev, &update);
> +	sun6i_isp_params_state_update(isp_dev, &update);
> +
> +	if (update && !ready_hold)
> +		sun6i_isp_state_ready(isp_dev);
> +
> +	spin_unlock_irqrestore(&isp_dev->state_lock, flags);
> +}
> +
> +/* Tables */
> +
> +static int sun6i_isp_table_setup(struct sun6i_isp_device *isp_dev,
> +				 struct sun6i_isp_table *table)
> +{
> +	table->data = dma_alloc_coherent(isp_dev->dev, table->size,
> +					 &table->address, GFP_KERNEL);
> +	if (!table->data)
> +		return -ENOMEM;
> +
> +	return 0;
> +}
> +
> +static void sun6i_isp_table_cleanup(struct sun6i_isp_device *isp_dev,
> +				    struct sun6i_isp_table *table)
> +{
> +	dma_free_coherent(isp_dev->dev, table->size, table->data,
> +			  table->address);
> +}
> +
> +void sun6i_isp_tables_configure(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +
> +	regmap_write(regmap, SUN6I_ISP_REG_LOAD_ADDR_REG,
> +		     SUN6I_ISP_ADDR_VALUE(isp_dev->tables.load.address));
> +
> +	regmap_write(regmap, SUN6I_ISP_REG_SAVE_ADDR_REG,
> +		     SUN6I_ISP_ADDR_VALUE(isp_dev->tables.save.address));
> +
> +	regmap_write(regmap, SUN6I_ISP_LUT_TABLE_ADDR_REG,
> +		     SUN6I_ISP_ADDR_VALUE(isp_dev->tables.lut.address));
> +
> +	regmap_write(regmap, SUN6I_ISP_DRC_TABLE_ADDR_REG,
> +		     SUN6I_ISP_ADDR_VALUE(isp_dev->tables.drc.address));
> +
> +	regmap_write(regmap, SUN6I_ISP_STATS_ADDR_REG,
> +		     SUN6I_ISP_ADDR_VALUE(isp_dev->tables.stats.address));
> +}
> +
> +static int sun6i_isp_tables_setup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_tables *tables = &isp_dev->tables;
> +	int ret;
> +
> +	/* Sizes are hardcoded for now but actually depend on the platform. */

Would it be cleaner to have them defined in a platform-specific way, e.g.
in a struct you obtain using device_get_match_data()?

> +
> +	tables->load.size = 0x1000;
> +	ret = sun6i_isp_table_setup(isp_dev, &tables->load);
> +	if (ret)
> +		return ret;
> +
> +	tables->save.size = 0x1000;
> +	ret = sun6i_isp_table_setup(isp_dev, &tables->save);
> +	if (ret)
> +		return ret;
> +
> +	tables->lut.size = 0xe00;
> +	ret = sun6i_isp_table_setup(isp_dev, &tables->lut);
> +	if (ret)
> +		return ret;
> +
> +	tables->drc.size = 0x600;
> +	ret = sun6i_isp_table_setup(isp_dev, &tables->drc);
> +	if (ret)
> +		return ret;
> +
> +	tables->stats.size = 0x2100;
> +	ret = sun6i_isp_table_setup(isp_dev, &tables->stats);

You can return already here.

> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static void sun6i_isp_tables_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_tables *tables = &isp_dev->tables;
> +
> +	sun6i_isp_table_cleanup(isp_dev, &tables->stats);
> +	sun6i_isp_table_cleanup(isp_dev, &tables->drc);
> +	sun6i_isp_table_cleanup(isp_dev, &tables->lut);
> +	sun6i_isp_table_cleanup(isp_dev, &tables->save);
> +	sun6i_isp_table_cleanup(isp_dev, &tables->load);
> +}
> +
> +/* Media */
> +
> +static const struct media_device_ops sun6i_isp_media_ops = {
> +	.link_notify = v4l2_pipeline_link_notify,
> +};
> +
> +/* V4L2 */
> +
> +static int sun6i_isp_v4l2_setup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_v4l2 *v4l2 = &isp_dev->v4l2;
> +	struct v4l2_device *v4l2_dev = &v4l2->v4l2_dev;
> +	struct media_device *media_dev = &v4l2->media_dev;
> +	struct device *dev = isp_dev->dev;
> +	int ret;
> +
> +	/* Media Device */
> +
> +	strscpy(media_dev->model, SUN6I_ISP_DESCRIPTION,
> +		sizeof(media_dev->model));
> +	snprintf(media_dev->bus_info, sizeof(media_dev->bus_info),
> +		 "platform:%s", dev_name(dev));

This is no longer needed, see commit b0e38610f40a0f89e34939d2c7420590d67d86a3
.

> +	media_dev->ops = &sun6i_isp_media_ops;
> +	media_dev->hw_revision = 0;
> +	media_dev->dev = dev;
> +
> +	media_device_init(media_dev);
> +
> +	ret = media_device_register(media_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to register media device\n");
> +		return ret;
> +	}
> +
> +	/* V4L2 Control Handler */
> +
> +	ret = v4l2_ctrl_handler_init(&v4l2->ctrl_handler, 0);

I suppose you intend to add controls later on?

> +	if (ret) {
> +		dev_err(dev, "failed to init v4l2 control handler\n");
> +		goto error_media;
> +	}
> +
> +	/* V4L2 Device */
> +
> +	v4l2_dev->mdev = media_dev;
> +	v4l2_dev->ctrl_handler = &v4l2->ctrl_handler;
> +
> +	ret = v4l2_device_register(dev, v4l2_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to register v4l2 device\n");
> +		goto error_v4l2_ctrl;
> +	}
> +
> +	return 0;
> +
> +error_v4l2_ctrl:
> +	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
> +
> +error_media:
> +	media_device_unregister(media_dev);
> +	media_device_cleanup(media_dev);
> +
> +	return ret;
> +}

> +static void sun6i_isp_v4l2_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_v4l2 *v4l2 = &isp_dev->v4l2;
> +
> +	media_device_unregister(&v4l2->media_dev);
> +	v4l2_device_unregister(&v4l2->v4l2_dev);
> +	v4l2_ctrl_handler_free(&v4l2->ctrl_handler);
> +	media_device_cleanup(&v4l2->media_dev);
> +}
> +
> +/* Platform */
> +
> +static irqreturn_t sun6i_isp_interrupt(int irq, void *private)
> +{
> +	struct sun6i_isp_device *isp_dev = private;
> +	struct regmap *regmap = isp_dev->regmap;
> +	u32 status = 0, enable = 0;
> +
> +	regmap_read(regmap, SUN6I_ISP_FE_INT_STA_REG, &status);
> +	regmap_read(regmap, SUN6I_ISP_FE_INT_EN_REG, &enable);
> +
> +	if (!status)
> +		return IRQ_NONE;
> +	else if (!(status & enable))
> +		goto complete;
> +
> +	/*
> +	 * The ISP working cycle starts with a params-load, which makes the
> +	 * state from the load buffer active. Then it starts processing the
> +	 * frame and gives a finish interrupt. Soon after that, the next state
> +	 * coming from the load buffer will be applied for the next frame,
> +	 * giving a params-load as well.
> +	 *
> +	 * Because both frame finish and params-load are received almost
> +	 * at the same time (one ISR call), handle them in chronology order.
> +	 */
> +
> +	if (status & SUN6I_ISP_FE_INT_STA_FINISH)
> +		sun6i_isp_capture_finish(isp_dev);
> +
> +	if (status & SUN6I_ISP_FE_INT_STA_PARA_LOAD) {
> +		sun6i_isp_state_complete(isp_dev);
> +		sun6i_isp_state_update(isp_dev, false);
> +	}
> +
> +complete:
> +	regmap_write(regmap, SUN6I_ISP_FE_INT_STA_REG, status);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int sun6i_isp_suspend(struct device *dev)
> +{
> +	struct sun6i_isp_device *isp_dev = dev_get_drvdata(dev);
> +
> +	reset_control_assert(isp_dev->reset);
> +	clk_disable_unprepare(isp_dev->clock_ram);
> +	clk_disable_unprepare(isp_dev->clock_mod);
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_resume(struct device *dev)
> +{
> +	struct sun6i_isp_device *isp_dev = dev_get_drvdata(dev);
> +	int ret;
> +
> +	ret = reset_control_deassert(isp_dev->reset);
> +	if (ret) {
> +		dev_err(dev, "failed to deassert reset\n");
> +		return ret;
> +	}
> +
> +	ret = clk_prepare_enable(isp_dev->clock_mod);
> +	if (ret) {
> +		dev_err(dev, "failed to enable module clock\n");
> +		goto error_reset;
> +	}
> +
> +	ret = clk_prepare_enable(isp_dev->clock_ram);
> +	if (ret) {
> +		dev_err(dev, "failed to enable ram clock\n");
> +		goto error_clock_mod;
> +	}
> +
> +	return 0;
> +
> +error_clock_mod:
> +	clk_disable_unprepare(isp_dev->clock_mod);
> +
> +error_reset:
> +	reset_control_assert(isp_dev->reset);
> +
> +	return ret;
> +}
> +
> +static const struct dev_pm_ops sun6i_isp_pm_ops = {
> +	.runtime_suspend	= sun6i_isp_suspend,
> +	.runtime_resume		= sun6i_isp_resume,
> +};
> +
> +static const struct regmap_config sun6i_isp_regmap_config = {
> +	.reg_bits       = 32,
> +	.reg_stride     = 4,
> +	.val_bits       = 32,
> +	.max_register	= 0x400,
> +};
> +
> +static int sun6i_isp_resources_setup(struct sun6i_isp_device *isp_dev,
> +				     struct platform_device *platform_dev)
> +{
> +	struct device *dev = isp_dev->dev;
> +	void __iomem *io_base;
> +	int irq;
> +	int ret;
> +
> +	/* Registers */
> +
> +	io_base = devm_platform_ioremap_resource(platform_dev, 0);
> +	if (IS_ERR(io_base))
> +		return PTR_ERR(io_base);
> +
> +	isp_dev->regmap = devm_regmap_init_mmio_clk(dev, "bus", io_base,
> +						    &sun6i_isp_regmap_config);
> +	if (IS_ERR(isp_dev->regmap)) {
> +		dev_err(dev, "failed to init register map\n");
> +		return PTR_ERR(isp_dev->regmap);
> +	}
> +
> +	/* Clocks */
> +
> +	isp_dev->clock_mod = devm_clk_get(dev, "mod");
> +	if (IS_ERR(isp_dev->clock_mod)) {
> +		dev_err(dev, "failed to acquire module clock\n");
> +		return PTR_ERR(isp_dev->clock_mod);
> +	}
> +
> +	isp_dev->clock_ram = devm_clk_get(dev, "ram");
> +	if (IS_ERR(isp_dev->clock_ram)) {
> +		dev_err(dev, "failed to acquire ram clock\n");
> +		return PTR_ERR(isp_dev->clock_ram);
> +	}
> +
> +	ret = clk_set_rate_exclusive(isp_dev->clock_mod, 297000000);

Is this also specific to the model?

> +	if (ret) {
> +		dev_err(dev, "failed to set mod clock rate\n");
> +		return ret;
> +	}
> +
> +	/* Reset */
> +
> +	isp_dev->reset = devm_reset_control_get_shared(dev, NULL);
> +	if (IS_ERR(isp_dev->reset)) {
> +		dev_err(dev, "failed to acquire reset\n");
> +		ret = PTR_ERR(isp_dev->reset);
> +		goto error_clock_rate_exclusive;
> +	}
> +
> +	/* Interrupt */
> +
> +	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_isp_interrupt, IRQF_SHARED,
> +			       SUN6I_ISP_NAME, isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to request interrupt\n");
> +		goto error_clock_rate_exclusive;
> +	}
> +
> +	/* Runtime PM */
> +
> +	pm_runtime_enable(dev);
> +
> +	return 0;
> +
> +error_clock_rate_exclusive:
> +	clk_rate_exclusive_put(isp_dev->clock_mod);
> +
> +	return ret;
> +}
> +
> +static void sun6i_isp_resources_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct device *dev = isp_dev->dev;
> +
> +	pm_runtime_disable(dev);
> +	clk_rate_exclusive_put(isp_dev->clock_mod);
> +}
> +
> +static int sun6i_isp_probe(struct platform_device *platform_dev)
> +{
> +	struct sun6i_isp_device *isp_dev;
> +	struct device *dev = &platform_dev->dev;
> +	int ret;
> +
> +	isp_dev = devm_kzalloc(dev, sizeof(*isp_dev), GFP_KERNEL);
> +	if (!isp_dev)
> +		return -ENOMEM;
> +
> +	isp_dev->dev = dev;
> +	platform_set_drvdata(platform_dev, isp_dev);
> +
> +	spin_lock_init(&isp_dev->state_lock);
> +
> +	ret = sun6i_isp_resources_setup(isp_dev, platform_dev);
> +	if (ret)
> +		return ret;
> +
> +	ret = sun6i_isp_tables_setup(isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to setup tables\n");
> +		goto error_resources;
> +	}
> +
> +	ret = sun6i_isp_v4l2_setup(isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to setup v4l2\n");
> +		goto error_tables;
> +	}
> +
> +	ret = sun6i_isp_proc_setup(isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to setup proc\n");
> +		goto error_v4l2;
> +	}
> +
> +	ret = sun6i_isp_capture_setup(isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to setup capture\n");
> +		goto error_proc;
> +	}
> +
> +	ret = sun6i_isp_params_setup(isp_dev);
> +	if (ret) {
> +		dev_err(dev, "failed to setup params\n");
> +		goto error_capture;
> +	}
> +
> +	return 0;
> +
> +error_capture:
> +	sun6i_isp_capture_cleanup(isp_dev);
> +
> +error_proc:
> +	sun6i_isp_proc_cleanup(isp_dev);
> +
> +error_v4l2:
> +	sun6i_isp_v4l2_cleanup(isp_dev);
> +
> +error_tables:
> +	sun6i_isp_tables_cleanup(isp_dev);
> +
> +error_resources:
> +	sun6i_isp_resources_cleanup(isp_dev);
> +
> +	return ret;
> +}
> +
> +static int sun6i_isp_remove(struct platform_device *platform_dev)
> +{
> +	struct sun6i_isp_device *isp_dev = platform_get_drvdata(platform_dev);
> +
> +	sun6i_isp_params_cleanup(isp_dev);
> +	sun6i_isp_capture_cleanup(isp_dev);
> +	sun6i_isp_proc_cleanup(isp_dev);
> +	sun6i_isp_v4l2_cleanup(isp_dev);
> +	sun6i_isp_tables_cleanup(isp_dev);
> +	sun6i_isp_resources_cleanup(isp_dev);
> +
> +	return 0;
> +}
> +
> +/*
> + * History of sun6i-isp:
> + * - sun4i-a10-isp: initial ISP tied to the CSI0 controller,
> + *   apparently unused in software implementations;
> + * - sun6i-a31-isp: separate ISP loosely based on sun4i-a10-isp,
> + *   adding extra modules and features;
> + * - sun9i-a80-isp: based on sun6i-a31-isp with some register offset changes
> + *   and new modules like saturation and cnr;
> + * - sun8i-a23-isp/sun8i-h3-isp: based on sun9i-a80-isp with most modules
> + *   related to raw removed;
> + * - sun8i-a83t-isp: based on sun9i-a80-isp with some register offset changes
> + * - sun8i-v3s-isp: based on sun8i-a83t-isp with a new disc module;
> + */
> +
> +static const struct of_device_id sun6i_isp_of_match[] = {
> +	{ .compatible	= "allwinner,sun8i-v3s-isp" },
> +	{},
> +};
> +
> +MODULE_DEVICE_TABLE(of, sun6i_isp_of_match);
> +
> +static struct platform_driver sun6i_isp_platform_driver = {
> +	.probe	= sun6i_isp_probe,
> +	.remove	= sun6i_isp_remove,
> +	.driver	= {
> +		.name		= SUN6I_ISP_NAME,
> +		.of_match_table	= of_match_ptr(sun6i_isp_of_match),
> +		.pm		= &sun6i_isp_pm_ops,
> +	},
> +};
> +
> +module_platform_driver(sun6i_isp_platform_driver);
> +
> +MODULE_DESCRIPTION("Allwinner A31 Image Signal Processor driver");
> +MODULE_AUTHOR("Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h
> new file mode 100644
> index 000000000000..60017606babe
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp.h
> @@ -0,0 +1,85 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#ifndef _SUN6I_ISP_H_
> +#define _SUN6I_ISP_H_
> +
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-device.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "sun6i_isp_capture.h"
> +#include "sun6i_isp_params.h"
> +#include "sun6i_isp_proc.h"
> +
> +#define SUN6I_ISP_NAME			"sun6i-isp"
> +#define SUN6I_ISP_DESCRIPTION		"Allwinner A31 ISP Device"
> +
> +enum sun6i_isp_port {
> +	SUN6I_ISP_PORT_CSI0	= 0,
> +	SUN6I_ISP_PORT_CSI1	= 1,
> +};
> +
> +struct sun6i_isp_buffer {
> +	struct vb2_v4l2_buffer	v4l2_buffer;
> +	struct list_head	list;
> +};
> +
> +struct sun6i_isp_v4l2 {
> +	struct v4l2_device		v4l2_dev;
> +	struct v4l2_ctrl_handler	ctrl_handler;
> +	struct media_device		media_dev;
> +};
> +
> +struct sun6i_isp_table {
> +	void		*data;
> +	dma_addr_t	address;
> +	unsigned int	size;
> +};
> +
> +struct sun6i_isp_tables {
> +	struct sun6i_isp_table	load;
> +	struct sun6i_isp_table	save;
> +
> +	struct sun6i_isp_table	lut;
> +	struct sun6i_isp_table	drc;
> +	struct sun6i_isp_table	stats;
> +};
> +
> +struct sun6i_isp_device {
> +	struct device			*dev;
> +
> +	struct sun6i_isp_tables		tables;
> +
> +	struct sun6i_isp_v4l2		v4l2;
> +	struct sun6i_isp_proc		proc;
> +	struct sun6i_isp_capture	capture;
> +	struct sun6i_isp_params		params;
> +
> +	struct regmap			*regmap;
> +	struct clk			*clock_mod;
> +	struct clk			*clock_ram;
> +	struct reset_control		*reset;
> +
> +	spinlock_t			state_lock; /* State helpers lock. */
> +};
> +
> +/* Helpers */
> +
> +u32 sun6i_isp_load_read(struct sun6i_isp_device *isp_dev, u32 offset);
> +void sun6i_isp_load_write(struct sun6i_isp_device *isp_dev, u32 offset,
> +			  u32 value);
> +u32 sun6i_isp_address_value(dma_addr_t address);
> +
> +/* State */
> +
> +void sun6i_isp_state_update(struct sun6i_isp_device *isp_dev, bool ready_hold);
> +
> +/* Tables */
> +
> +void sun6i_isp_tables_configure(struct sun6i_isp_device *isp_dev);
> +
> +#endif
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c
> new file mode 100644
> index 000000000000..5cd069891461
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.c
> @@ -0,0 +1,749 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#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_isp.h"
> +#include "sun6i_isp_capture.h"
> +#include "sun6i_isp_proc.h"
> +#include "sun6i_isp_reg.h"
> +
> +/* Helpers */
> +
> +void sun6i_isp_capture_dimensions(struct sun6i_isp_device *isp_dev,
> +				  unsigned int *width, unsigned int *height)
> +{
> +	if (width)
> +		*width = isp_dev->capture.format.fmt.pix.width;
> +	if (height)
> +		*height = isp_dev->capture.format.fmt.pix.height;
> +}
> +
> +void sun6i_isp_capture_format(struct sun6i_isp_device *isp_dev,
> +			      u32 *pixelformat)
> +{
> +	if (pixelformat)
> +		*pixelformat = isp_dev->capture.format.fmt.pix.pixelformat;
> +}
> +
> +/* Format */
> +
> +static const struct sun6i_isp_capture_format sun6i_isp_capture_formats[] = {
> +	{
> +		.pixelformat		= V4L2_PIX_FMT_NV12,
> +		.output_format		= SUN6I_ISP_OUTPUT_FMT_YUV420SP,
> +	},
> +	{
> +		.pixelformat		= V4L2_PIX_FMT_NV21,
> +		.output_format		= SUN6I_ISP_OUTPUT_FMT_YVU420SP,
> +	},
> +};
> +
> +const struct sun6i_isp_capture_format *
> +sun6i_isp_capture_format_find(u32 pixelformat)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(sun6i_isp_capture_formats); i++)
> +		if (sun6i_isp_capture_formats[i].pixelformat == pixelformat)
> +			return &sun6i_isp_capture_formats[i];
> +
> +	return NULL;
> +}
> +
> +/* Capture */
> +
> +static void
> +sun6i_isp_capture_buffer_configure(struct sun6i_isp_device *isp_dev,
> +				   struct sun6i_isp_buffer *isp_buffer)
> +{
> +	const struct v4l2_format_info *info;
> +	struct vb2_buffer *vb2_buffer;
> +	unsigned int width, height;
> +	unsigned int width_aligned;
> +	dma_addr_t address;
> +	u32 pixelformat;
> +
> +	vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf;
> +	address = vb2_dma_contig_plane_dma_addr(vb2_buffer, 0);
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_Y_ADDR0_REG,
> +			     SUN6I_ISP_ADDR_VALUE(address));
> +
> +	sun6i_isp_capture_dimensions(isp_dev, &width, &height);
> +	sun6i_isp_capture_format(isp_dev, &pixelformat);
> +
> +	info = v4l2_format_info(pixelformat);
> +	if (WARN_ON(!info))
> +		return;
> +
> +	/* Stride needs to be aligned to 4. */
> +	width_aligned = ALIGN(width, 2);
> +
> +	if (info->comp_planes > 1) {
> +		address += info->bpp[0] * width_aligned * height;
> +
> +		sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_U_ADDR0_REG,
> +				     SUN6I_ISP_ADDR_VALUE(address));
> +	}
> +
> +	if (info->comp_planes > 2) {
> +		address += info->bpp[1] *
> +			   DIV_ROUND_UP(width_aligned, info->hdiv) *
> +			   DIV_ROUND_UP(height, info->vdiv);
> +
> +		sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_V_ADDR0_REG,
> +				     SUN6I_ISP_ADDR_VALUE(address));
> +	}
> +}
> +
> +void sun6i_isp_capture_configure(struct sun6i_isp_device *isp_dev)
> +{
> +	unsigned int width, height;
> +	unsigned int stride_luma, stride_chroma = 0;
> +	unsigned int stride_luma_div4, stride_chroma_div4;
> +	const struct sun6i_isp_capture_format *format;
> +	const struct v4l2_format_info *info;
> +	u32 pixelformat;
> +
> +	sun6i_isp_capture_dimensions(isp_dev, &width, &height);
> +	sun6i_isp_capture_format(isp_dev, &pixelformat);
> +
> +	format = sun6i_isp_capture_format_find(pixelformat);
> +	if (WARN_ON(!format))
> +		return;
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_SIZE_CFG_REG,
> +			     SUN6I_ISP_MCH_SIZE_CFG_WIDTH(width) |
> +			     SUN6I_ISP_MCH_SIZE_CFG_HEIGHT(height));
> +
> +	info = v4l2_format_info(pixelformat);
> +	if (WARN_ON(!info))
> +		return;
> +
> +	stride_luma = width * info->bpp[0];
> +	stride_luma_div4 = DIV_ROUND_UP(stride_luma, 4);
> +
> +	if (info->comp_planes > 1) {
> +		stride_chroma = width * info->bpp[1] / info->hdiv;
> +		stride_chroma_div4 = DIV_ROUND_UP(stride_chroma, 4);
> +	}
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MCH_CFG_REG,
> +			     SUN6I_ISP_MCH_CFG_EN |
> +			     SUN6I_ISP_MCH_CFG_OUTPUT_FMT(format->output_format) |
> +			     SUN6I_ISP_MCH_CFG_STRIDE_Y_DIV4(stride_luma_div4) |
> +			     SUN6I_ISP_MCH_CFG_STRIDE_UV_DIV4(stride_chroma_div4));
> +}
> +
> +/* State */
> +
> +static void sun6i_isp_capture_state_cleanup(struct sun6i_isp_device *isp_dev,
> +					    bool error)
> +{
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	struct sun6i_isp_buffer **isp_buffer_states[] = {
> +		&state->pending, &state->current, &state->complete,
> +	};
> +	struct sun6i_isp_buffer *isp_buffer;
> +	struct vb2_buffer *vb2_buffer;
> +	unsigned long flags;
> +	unsigned int i;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	for (i = 0; i < ARRAY_SIZE(isp_buffer_states); i++) {
> +		isp_buffer = *isp_buffer_states[i];
> +		if (!isp_buffer)
> +			continue;
> +
> +		vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf;
> +		vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR :
> +				VB2_BUF_STATE_QUEUED);
> +
> +		*isp_buffer_states[i] = NULL;
> +	}
> +
> +	list_for_each_entry(isp_buffer, &state->queue, list) {
> +		vb2_buffer = &isp_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_isp_capture_state_update(struct sun6i_isp_device *isp_dev,
> +				    bool *update)
> +{
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	struct sun6i_isp_buffer *isp_buffer;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	if (list_empty(&state->queue))
> +		goto complete;
> +
> +	if (state->pending)
> +		goto complete;
> +
> +	isp_buffer = list_first_entry(&state->queue, struct sun6i_isp_buffer,
> +				      list);
> +
> +	sun6i_isp_capture_buffer_configure(isp_dev, isp_buffer);
> +
> +	list_del(&isp_buffer->list);
> +
> +	state->pending = isp_buffer;
> +
> +	if (update)
> +		*update = true;
> +
> +complete:
> +	spin_unlock_irqrestore(&state->lock, flags);
> +}
> +
> +void sun6i_isp_capture_state_complete(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_capture_state *state = &isp_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_isp_buffer *isp_buffer = state->complete;
> +		struct vb2_buffer *vb2_buffer =
> +			&isp_buffer->v4l2_buffer.vb2_buf;
> +
> +		vb2_buffer->timestamp = ktime_get_ns();
> +		isp_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_isp_capture_finish(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +	state->sequence++;
> +	spin_unlock_irqrestore(&state->lock, flags);
> +}
> +
> +/* Queue */
> +
> +static int sun6i_isp_capture_queue_setup(struct vb2_queue *queue,
> +					 unsigned int *buffers_count,
> +					 unsigned int *planes_count,
> +					 unsigned int sizes[],
> +					 struct device *alloc_devs[])
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	unsigned int size = isp_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_isp_capture_buffer_prepare(struct vb2_buffer *vb2_buffer)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		vb2_get_drv_priv(vb2_buffer->vb2_queue);
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	unsigned int size = isp_dev->capture.format.fmt.pix.sizeimage;
> +
> +	if (vb2_plane_size(vb2_buffer, 0) < size) {
> +		v4l2_err(v4l2_dev, "buffer too small (%lu < %u)\n",
> +			 vb2_plane_size(vb2_buffer, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb2_buffer, 0, size);
> +
> +	return 0;
> +}
> +
> +static void sun6i_isp_capture_buffer_queue(struct vb2_buffer *vb2_buffer)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		vb2_get_drv_priv(vb2_buffer->vb2_queue);
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(vb2_buffer);
> +	struct sun6i_isp_buffer *isp_buffer =
> +		container_of(v4l2_buffer, struct sun6i_isp_buffer, v4l2_buffer);
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +	list_add_tail(&isp_buffer->list, &state->queue);
> +	spin_unlock_irqrestore(&state->lock, flags);
> +
> +	/* Update the state to schedule our buffer as soon as possible. */
> +	if (state->streaming)
> +		sun6i_isp_state_update(isp_dev, false);
> +}
> +
> +static int sun6i_isp_capture_start_streaming(struct vb2_queue *queue,
> +					     unsigned int count)
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	struct video_device *video_dev = &isp_dev->capture.video_dev;
> +	struct v4l2_subdev *subdev = &isp_dev->proc.subdev;
> +	int ret;
> +
> +	state->sequence = 0;
> +
> +	ret = media_pipeline_start(&video_dev->entity, &video_dev->pipe);
> +	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;
> +
> +	media_pipeline_stop(&video_dev->entity);
> +
> +error_state:
> +	sun6i_isp_capture_state_cleanup(isp_dev, false);
> +
> +	return ret;
> +}
> +
> +static void sun6i_isp_capture_stop_streaming(struct vb2_queue *queue)
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	struct sun6i_isp_capture_state *state = &isp_dev->capture.state;
> +	struct video_device *video_dev = &isp_dev->capture.video_dev;
> +	struct v4l2_subdev *subdev = &isp_dev->proc.subdev;
> +
> +	v4l2_subdev_call(subdev, video, s_stream, 0);
> +
> +	state->streaming = false;
> +
> +	media_pipeline_stop(&video_dev->entity);
> +
> +	sun6i_isp_capture_state_cleanup(isp_dev, true);
> +}
> +
> +static const struct vb2_ops sun6i_isp_capture_queue_ops = {
> +	.queue_setup		= sun6i_isp_capture_queue_setup,
> +	.buf_prepare		= sun6i_isp_capture_buffer_prepare,
> +	.buf_queue		= sun6i_isp_capture_buffer_queue,
> +	.start_streaming	= sun6i_isp_capture_start_streaming,
> +	.stop_streaming		= sun6i_isp_capture_stop_streaming,
> +	.wait_prepare		= vb2_ops_wait_prepare,
> +	.wait_finish		= vb2_ops_wait_finish,
> +};
> +
> +/* Video Device */
> +
> +static void sun6i_isp_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;
> +	unsigned int width_aligned;
> +	unsigned int i;
> +
> +	v4l_bound_align_image(&pix_format->width, SUN6I_ISP_CAPTURE_WIDTH_MIN,
> +			      SUN6I_ISP_CAPTURE_WIDTH_MAX, 1,
> +			      &pix_format->height, SUN6I_ISP_CAPTURE_HEIGHT_MIN,
> +			      SUN6I_ISP_CAPTURE_HEIGHT_MAX, 1, 0);
> +
> +	if (!sun6i_isp_capture_format_find(pix_format->pixelformat))
> +		pix_format->pixelformat =
> +			sun6i_isp_capture_formats[0].pixelformat;
> +
> +	info = v4l2_format_info(pix_format->pixelformat);
> +	if (WARN_ON(!info))
> +		return;
> +
> +	width = pix_format->width;
> +	height = pix_format->height;
> +
> +	/* Stride needs to be aligned to 4. */
> +	width_aligned = ALIGN(width, 2);
> +
> +	pix_format->bytesperline = width_aligned * info->bpp[0];
> +	pix_format->sizeimage = 0;
> +
> +	for (i = 0; i < info->comp_planes; i++) {
> +		unsigned int hdiv = (i == 0) ? 1 : info->hdiv;
> +		unsigned int vdiv = (i == 0) ? 1 : info->vdiv;
> +
> +		pix_format->sizeimage += info->bpp[i] *
> +					 DIV_ROUND_UP(width_aligned, hdiv) *
> +					 DIV_ROUND_UP(height, vdiv);
> +	}
> +
> +	pix_format->field = V4L2_FIELD_NONE;
> +
> +	pix_format->colorspace = V4L2_COLORSPACE_RAW;
> +	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_isp_capture_querycap(struct file *file, void *private,
> +				      struct v4l2_capability *capability)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +	struct video_device *video_dev = &isp_dev->capture.video_dev;
> +
> +	strscpy(capability->driver, SUN6I_ISP_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(isp_dev->dev));
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_capture_enum_fmt(struct file *file, void *private,
> +				      struct v4l2_fmtdesc *fmtdesc)
> +{
> +	u32 index = fmtdesc->index;
> +
> +	if (index >= ARRAY_SIZE(sun6i_isp_capture_formats))
> +		return -EINVAL;
> +
> +	fmtdesc->pixelformat = sun6i_isp_capture_formats[index].pixelformat;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_capture_g_fmt(struct file *file, void *private,
> +				   struct v4l2_format *format)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +
> +	*format = isp_dev->capture.format;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_capture_s_fmt(struct file *file, void *private,
> +				   struct v4l2_format *format)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +
> +	if (vb2_is_busy(&isp_dev->capture.queue))
> +		return -EBUSY;
> +
> +	sun6i_isp_capture_format_prepare(format);
> +
> +	isp_dev->capture.format = *format;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_capture_try_fmt(struct file *file, void *private,
> +				     struct v4l2_format *format)
> +{
> +	sun6i_isp_capture_format_prepare(format);
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_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_isp_capture_g_input(struct file *file, void *private,
> +				     unsigned int *index)
> +{
> +	*index = 0;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_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_isp_capture_ioctl_ops = {
> +	.vidioc_querycap		= sun6i_isp_capture_querycap,
> +
> +	.vidioc_enum_fmt_vid_cap	= sun6i_isp_capture_enum_fmt,
> +	.vidioc_g_fmt_vid_cap		= sun6i_isp_capture_g_fmt,
> +	.vidioc_s_fmt_vid_cap		= sun6i_isp_capture_s_fmt,
> +	.vidioc_try_fmt_vid_cap		= sun6i_isp_capture_try_fmt,
> +
> +	.vidioc_enum_input		= sun6i_isp_capture_enum_input,
> +	.vidioc_g_input			= sun6i_isp_capture_g_input,
> +	.vidioc_s_input			= sun6i_isp_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,
> +
> +	.vidioc_log_status		= v4l2_ctrl_log_status,
> +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> +};
> +
> +static int sun6i_isp_capture_open(struct file *file)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +	struct video_device *video_dev = &isp_dev->capture.video_dev;
> +	struct mutex *lock = &isp_dev->capture.lock;
> +	int ret;
> +
> +	if (mutex_lock_interruptible(lock))
> +		return -ERESTARTSYS;
> +
> +	ret = v4l2_pipeline_pm_get(&video_dev->entity);

Do you need this?

Drivers should primarily depend on runtime PM, this is only needed for
compatibility reasons. Instead I'd like to see sensor drivers being moved
to runtime PM.

> +	if (ret)
> +		goto error_mutex;
> +
> +	ret = v4l2_fh_open(file);
> +	if (ret)
> +		goto error_pipeline;
> +
> +	mutex_unlock(lock);
> +
> +	return 0;
> +
> +error_pipeline:
> +	v4l2_pipeline_pm_put(&video_dev->entity);
> +
> +error_mutex:
> +	mutex_unlock(lock);
> +
> +	return ret;
> +}
> +
> +static int sun6i_isp_capture_release(struct file *file)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +	struct video_device *video_dev = &isp_dev->capture.video_dev;
> +	struct mutex *lock = &isp_dev->capture.lock;
> +
> +	mutex_lock(lock);
> +
> +	_vb2_fop_release(file, NULL);
> +	v4l2_pipeline_pm_put(&video_dev->entity);
> +
> +	mutex_unlock(lock);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_file_operations sun6i_isp_capture_fops = {
> +	.owner		= THIS_MODULE,
> +	.open		= sun6i_isp_capture_open,
> +	.release	= sun6i_isp_capture_release,
> +	.unlocked_ioctl	= video_ioctl2,
> +	.poll		= vb2_fop_poll,
> +	.mmap		= vb2_fop_mmap,
> +};
> +
> +/* Media Entity */
> +
> +static int sun6i_isp_capture_link_validate(struct media_link *link)
> +{
> +	struct video_device *video_dev =
> +		media_entity_to_video_device(link->sink->entity);
> +	struct sun6i_isp_device *isp_dev = video_get_drvdata(video_dev);
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	unsigned int capture_width, capture_height;
> +	unsigned int proc_width, proc_height;
> +
> +	sun6i_isp_capture_dimensions(isp_dev, &capture_width, &capture_height);
> +	sun6i_isp_proc_dimensions(isp_dev, &proc_width, &proc_height);
> +
> +	/* No cropping/scaling is supported (yet). */
> +	if (capture_width != proc_width || capture_height != proc_height) {
> +		v4l2_err(v4l2_dev,
> +			 "invalid input/output dimensions: %ux%u/%ux%u\n",
> +			 proc_width, proc_height, capture_width,
> +			 capture_height);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct media_entity_operations sun6i_isp_capture_entity_ops = {
> +	.link_validate	= sun6i_isp_capture_link_validate,
> +};
> +
> +/* Capture */
> +
> +int sun6i_isp_capture_setup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_capture *capture = &isp_dev->capture;
> +	struct sun6i_isp_capture_state *state = &capture->state;
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	struct v4l2_subdev *proc_subdev = &isp_dev->proc.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 = &capture->format;
> +	struct v4l2_pix_format *pix_format = &format->fmt.pix;
> +	int ret;
> +
> +	/* State */
> +
> +	INIT_LIST_HEAD(&state->queue);
> +	spin_lock_init(&state->lock);
> +
> +	/* Media Entity */
> +
> +	video_dev->entity.ops = &sun6i_isp_capture_entity_ops;
> +
> +	/* Media Pads */
> +
> +	pad->flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
> +
> +	ret = media_entity_pads_init(&video_dev->entity, 1, pad);
> +	if (ret)
> +		goto error_mutex;

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_isp_buffer);
> +	queue->ops = &sun6i_isp_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 = isp_dev->dev;
> +	queue->drv_priv = isp_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_isp_capture_formats[0].pixelformat;
> +	pix_format->width = 1280;
> +	pix_format->height = 720;
> +
> +	sun6i_isp_capture_format_prepare(format);
> +
> +	/* Video Device */
> +
> +	strscpy(video_dev->name, SUN6I_ISP_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_isp_capture_fops;
> +	video_dev->ioctl_ops = &sun6i_isp_capture_ioctl_ops;
> +	video_dev->v4l2_dev = v4l2_dev;
> +	video_dev->queue = queue;
> +	video_dev->lock = &capture->lock;
> +
> +	video_set_drvdata(video_dev, isp_dev);
> +
> +	ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		v4l2_err(v4l2_dev, "failed to register video device: %d\n",
> +			 ret);
> +		goto error_media_entity;
> +	}
> +
> +	v4l2_info(v4l2_dev, "device %s registered as %s\n", video_dev->name,
> +		  video_device_node_name(video_dev));

This isn't really driver specific. I'd drop it.

> +
> +	/* Media Pad Link */
> +
> +	ret = media_create_pad_link(&proc_subdev->entity,
> +				    SUN6I_ISP_PROC_PAD_SOURCE,
> +				    &video_dev->entity, 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",
> +			 proc_subdev->entity.name, SUN6I_ISP_PROC_PAD_SOURCE,
> +			 video_dev->entity.name, 0);

This error message printing would be better to be added to
media_create_pad_link().

> +		goto error_video_device;
> +	}
> +
> +	return 0;
> +
> +error_video_device:
> +	vb2_video_unregister_device(video_dev);
> +
> +error_media_entity:
> +	media_entity_cleanup(&video_dev->entity);
> +
> +error_mutex:
> +	mutex_destroy(&capture->lock);
> +
> +	return ret;
> +}
> +
> +void sun6i_isp_capture_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_capture *capture = &isp_dev->capture;
> +	struct video_device *video_dev = &capture->video_dev;
> +
> +	vb2_video_unregister_device(video_dev);
> +	media_entity_cleanup(&video_dev->entity);
> +	mutex_destroy(&capture->lock);
> +}
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h
> new file mode 100644
> index 000000000000..0e3e4fa7a0f4
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_capture.h
> @@ -0,0 +1,78 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#ifndef _SUN6I_ISP_CAPTURE_H_
> +#define _SUN6I_ISP_CAPTURE_H_
> +
> +#include <media/v4l2-device.h>
> +
> +#define SUN6I_ISP_CAPTURE_NAME		"sun6i-isp-capture"
> +
> +#define SUN6I_ISP_CAPTURE_WIDTH_MIN	16
> +#define SUN6I_ISP_CAPTURE_WIDTH_MAX	3264
> +#define SUN6I_ISP_CAPTURE_HEIGHT_MIN	16
> +#define SUN6I_ISP_CAPTURE_HEIGHT_MAX	2448
> +
> +struct sun6i_isp_device;
> +
> +struct sun6i_isp_capture_format {
> +	u32	pixelformat;
> +	u8	output_format;
> +};
> +
> +#undef current
> +struct sun6i_isp_capture_state {
> +	struct list_head		queue;
> +	spinlock_t			lock; /* Queue and buffers lock. */
> +
> +	struct sun6i_isp_buffer		*pending;
> +	struct sun6i_isp_buffer		*current;
> +	struct sun6i_isp_buffer		*complete;
> +
> +	unsigned int			sequence;
> +	bool				streaming;
> +};
> +
> +struct sun6i_isp_capture {
> +	struct sun6i_isp_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_isp_capture_dimensions(struct sun6i_isp_device *isp_dev,
> +				  unsigned int *width, unsigned int *height);
> +void sun6i_isp_capture_format(struct sun6i_isp_device *isp_dev,
> +			      u32 *pixelformat);
> +
> +/* Format */
> +
> +const struct sun6i_isp_capture_format *
> +sun6i_isp_capture_format_find(u32 pixelformat);
> +
> +/* Capture */
> +
> +void sun6i_isp_capture_configure(struct sun6i_isp_device *isp_dev);
> +
> +/* State */
> +
> +void sun6i_isp_capture_state_update(struct sun6i_isp_device *isp_dev,
> +				    bool *update);
> +void sun6i_isp_capture_state_complete(struct sun6i_isp_device *isp_dev);
> +void sun6i_isp_capture_finish(struct sun6i_isp_device *isp_dev);
> +
> +/* Capture */
> +
> +int sun6i_isp_capture_setup(struct sun6i_isp_device *isp_dev);
> +void sun6i_isp_capture_cleanup(struct sun6i_isp_device *isp_dev);
> +
> +#endif
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c
> new file mode 100644
> index 000000000000..8f6ee73b3bdc
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.c
> @@ -0,0 +1,573 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-ioctl.h>
> +#include <media/v4l2-mc.h>
> +#include <media/videobuf2-vmalloc.h>
> +#include <media/videobuf2-v4l2.h>
> +
> +#include "sun6i_isp.h"
> +#include "sun6i_isp_params.h"
> +#include "sun6i_isp_reg.h"
> +#include "uapi/sun6i-isp-config.h"
> +
> +/* Params */
> +
> +static const struct sun6i_isp_params_config sun6i_isp_params_config_default = {
> +	.modules_used = SUN6I_ISP_MODULE_BAYER,
> +
> +	.bayer = {
> +		.offset_r	= 32,
> +		.offset_gr	= 32,
> +		.offset_gb	= 32,
> +		.offset_b	= 32,
> +
> +		.gain_r		= 256,
> +		.gain_gr	= 256,
> +		.gain_gb	= 256,
> +		.gain_b		= 256,
> +
> +	},
> +
> +	.bdnf = {
> +		.in_dis_min		= 8,
> +		.in_dis_max		= 16,
> +
> +		.coefficients_g		= { 15, 4, 1 },
> +		.coefficients_rb	= { 15, 4 },
> +	},
> +};
> +
> +static void sun6i_isp_params_configure_ob(struct sun6i_isp_device *isp_dev)
> +{
> +	unsigned int width, height;
> +
> +	sun6i_isp_proc_dimensions(isp_dev, &width, &height);
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SIZE_REG,
> +			     SUN6I_ISP_OB_SIZE_WIDTH(width) |
> +			     SUN6I_ISP_OB_SIZE_HEIGHT(height));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_VALID_REG,
> +			     SUN6I_ISP_OB_VALID_WIDTH(width) |
> +			     SUN6I_ISP_OB_VALID_HEIGHT(height));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_OB_SRC0_VALID_START_REG,
> +			     SUN6I_ISP_OB_SRC0_VALID_START_HORZ(0) |
> +			     SUN6I_ISP_OB_SRC0_VALID_START_VERT(0));
> +}
> +
> +static void sun6i_isp_params_configure_ae(struct sun6i_isp_device *isp_dev)
> +{
> +	/* These are default values that need to be set to get an output. */
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_AE_CFG_REG,
> +			     SUN6I_ISP_AE_CFG_LOW_BRI_TH(0xff) |
> +			     SUN6I_ISP_AE_CFG_HORZ_NUM(8) |
> +			     SUN6I_ISP_AE_CFG_HIGH_BRI_TH(0xf00) |
> +			     SUN6I_ISP_AE_CFG_VERT_NUM(8));
> +}
> +
> +static void
> +sun6i_isp_params_configure_bayer(struct sun6i_isp_device *isp_dev,
> +				 const struct sun6i_isp_params_config *config)
> +{
> +	const struct sun6i_isp_params_config_bayer *bayer = &config->bayer;
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET0_REG,
> +			     SUN6I_ISP_BAYER_OFFSET0_R(bayer->offset_r) |
> +			     SUN6I_ISP_BAYER_OFFSET0_GR(bayer->offset_gr));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_OFFSET1_REG,
> +			     SUN6I_ISP_BAYER_OFFSET1_GB(bayer->offset_gb) |
> +			     SUN6I_ISP_BAYER_OFFSET1_B(bayer->offset_b));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN0_REG,
> +			     SUN6I_ISP_BAYER_GAIN0_R(bayer->gain_r) |
> +			     SUN6I_ISP_BAYER_GAIN0_GR(bayer->gain_gr));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BAYER_GAIN1_REG,
> +			     SUN6I_ISP_BAYER_GAIN1_GB(bayer->gain_gb) |
> +			     SUN6I_ISP_BAYER_GAIN1_B(bayer->gain_b));
> +}
> +
> +static void sun6i_isp_params_configure_wb(struct sun6i_isp_device *isp_dev)
> +{
> +	/* These are default values that need to be set to get an output. */
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN0_REG,
> +			     SUN6I_ISP_WB_GAIN0_R(256) |
> +			     SUN6I_ISP_WB_GAIN0_GR(256));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_GAIN1_REG,
> +			     SUN6I_ISP_WB_GAIN1_GB(256) |
> +			     SUN6I_ISP_WB_GAIN1_B(256));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_WB_CFG_REG,
> +			     SUN6I_ISP_WB_CFG_CLIP(0xfff));
> +}
> +
> +static void sun6i_isp_params_configure_base(struct sun6i_isp_device *isp_dev)
> +{
> +	sun6i_isp_params_configure_ae(isp_dev);
> +	sun6i_isp_params_configure_ob(isp_dev);
> +	sun6i_isp_params_configure_wb(isp_dev);
> +}
> +
> +static void
> +sun6i_isp_params_configure_bdnf(struct sun6i_isp_device *isp_dev,
> +				const struct sun6i_isp_params_config *config)
> +{
> +	const struct sun6i_isp_params_config_bdnf *bdnf = &config->bdnf;
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_CFG_REG,
> +			     SUN6I_ISP_BDNF_CFG_IN_DIS_MIN(bdnf->in_dis_min) |
> +			     SUN6I_ISP_BDNF_CFG_IN_DIS_MAX(bdnf->in_dis_max));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_RB_REG,
> +			     SUN6I_ISP_BDNF_COEF_RB(0, bdnf->coefficients_rb[0]) |
> +			     SUN6I_ISP_BDNF_COEF_RB(1, bdnf->coefficients_rb[1]) |
> +			     SUN6I_ISP_BDNF_COEF_RB(2, bdnf->coefficients_rb[2]) |
> +			     SUN6I_ISP_BDNF_COEF_RB(3, bdnf->coefficients_rb[3]) |
> +			     SUN6I_ISP_BDNF_COEF_RB(4, bdnf->coefficients_rb[4]));
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_BDNF_COEF_G_REG,
> +			     SUN6I_ISP_BDNF_COEF_G(0, bdnf->coefficients_g[0]) |
> +			     SUN6I_ISP_BDNF_COEF_G(1, bdnf->coefficients_g[1]) |
> +			     SUN6I_ISP_BDNF_COEF_G(2, bdnf->coefficients_g[2]) |
> +			     SUN6I_ISP_BDNF_COEF_G(3, bdnf->coefficients_g[3]) |
> +			     SUN6I_ISP_BDNF_COEF_G(4, bdnf->coefficients_g[4]) |
> +			     SUN6I_ISP_BDNF_COEF_G(5, bdnf->coefficients_g[5]) |
> +			     SUN6I_ISP_BDNF_COEF_G(6, bdnf->coefficients_g[6]));
> +}
> +
> +static void
> +sun6i_isp_params_configure_modules(struct sun6i_isp_device *isp_dev,
> +				   const struct sun6i_isp_params_config *config)
> +{
> +	u32 value;
> +
> +	if (config->modules_used & SUN6I_ISP_MODULE_BDNF)
> +		sun6i_isp_params_configure_bdnf(isp_dev, config);
> +
> +	if (config->modules_used & SUN6I_ISP_MODULE_BAYER)
> +		sun6i_isp_params_configure_bayer(isp_dev, config);
> +
> +	value = sun6i_isp_load_read(isp_dev, SUN6I_ISP_MODULE_EN_REG);
> +	/* Clear all modules but keep input configuration. */
> +	value &= SUN6I_ISP_MODULE_EN_SRC0 | SUN6I_ISP_MODULE_EN_SRC1;
> +
> +	if (config->modules_used & SUN6I_ISP_MODULE_BDNF)
> +		value |= SUN6I_ISP_MODULE_EN_BDNF;
> +
> +	/* Bayer stage is always enabled. */
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODULE_EN_REG, value);
> +}
> +
> +void sun6i_isp_params_configure(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	sun6i_isp_params_configure_base(isp_dev);
> +
> +	/* Default config is only applied at the very first stream start. */
> +	if (state->configured)
> +		goto complete;
> +
> +	 sun6i_isp_params_configure_modules(isp_dev,

Indentation. Doesn't checkpatch.pl complain?

> +					    &sun6i_isp_params_config_default);
> +
> +	state->configured = true;
> +
> +complete:
> +	spin_unlock_irqrestore(&state->lock, flags);
> +}
> +
> +/* State */
> +
> +static void sun6i_isp_params_state_cleanup(struct sun6i_isp_device *isp_dev,
> +					   bool error)
> +{
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	struct sun6i_isp_buffer *isp_buffer;
> +	struct vb2_buffer *vb2_buffer;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	if (state->pending) {
> +		vb2_buffer = &state->pending->v4l2_buffer.vb2_buf;
> +		vb2_buffer_done(vb2_buffer, error ? VB2_BUF_STATE_ERROR :
> +				VB2_BUF_STATE_QUEUED);
> +	}
> +
> +	list_for_each_entry(isp_buffer, &state->queue, list) {
> +		vb2_buffer = &isp_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_isp_params_state_update(struct sun6i_isp_device *isp_dev,
> +				   bool *update)
> +{
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	struct sun6i_isp_buffer *isp_buffer;
> +	struct vb2_buffer *vb2_buffer;
> +	const struct sun6i_isp_params_config *config;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	if (list_empty(&state->queue))
> +		goto complete;
> +
> +	if (state->pending)
> +		goto complete;
> +
> +	isp_buffer = list_first_entry(&state->queue, struct sun6i_isp_buffer,
> +				      list);
> +
> +	vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf;
> +	config = vb2_plane_vaddr(vb2_buffer, 0);
> +
> +	sun6i_isp_params_configure_modules(isp_dev, config);
> +
> +	list_del(&isp_buffer->list);
> +
> +	state->pending = isp_buffer;
> +
> +	if (update)
> +		*update = true;
> +
> +complete:
> +	spin_unlock_irqrestore(&state->lock, flags);
> +}
> +
> +void sun6i_isp_params_state_complete(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	struct sun6i_isp_buffer *isp_buffer;
> +	struct vb2_buffer *vb2_buffer;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +
> +	if (!state->pending)
> +		goto complete;
> +
> +	isp_buffer = state->pending;
> +	vb2_buffer = &isp_buffer->v4l2_buffer.vb2_buf;
> +
> +	vb2_buffer->timestamp = ktime_get_ns();
> +
> +	/* Parameters will be applied starting from the next frame. */
> +	isp_buffer->v4l2_buffer.sequence = isp_dev->capture.state.sequence + 1;
> +
> +	vb2_buffer_done(vb2_buffer, VB2_BUF_STATE_DONE);
> +
> +	state->pending = NULL;
> +
> +complete:
> +	spin_unlock_irqrestore(&state->lock, flags);
> +}
> +
> +/* Queue */
> +
> +static int sun6i_isp_params_queue_setup(struct vb2_queue *queue,
> +					unsigned int *buffers_count,
> +					unsigned int *planes_count,
> +					unsigned int sizes[],
> +					struct device *alloc_devs[])
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	unsigned int size = isp_dev->params.format.fmt.meta.buffersize;
> +
> +	if (*planes_count)
> +		return sizes[0] < size ? -EINVAL : 0;
> +
> +	*planes_count = 1;
> +	sizes[0] = size;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_params_buffer_prepare(struct vb2_buffer *vb2_buffer)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		vb2_get_drv_priv(vb2_buffer->vb2_queue);
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	unsigned int size = isp_dev->params.format.fmt.meta.buffersize;
> +
> +	if (vb2_plane_size(vb2_buffer, 0) < size) {
> +		v4l2_err(v4l2_dev, "buffer too small (%lu < %u)\n",
> +			 vb2_plane_size(vb2_buffer, 0), size);
> +		return -EINVAL;
> +	}
> +
> +	vb2_set_plane_payload(vb2_buffer, 0, size);
> +
> +	return 0;
> +}
> +
> +static void sun6i_isp_params_buffer_queue(struct vb2_buffer *vb2_buffer)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		vb2_get_drv_priv(vb2_buffer->vb2_queue);
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	struct vb2_v4l2_buffer *v4l2_buffer = to_vb2_v4l2_buffer(vb2_buffer);
> +	struct sun6i_isp_buffer *isp_buffer =
> +		container_of(v4l2_buffer, struct sun6i_isp_buffer, v4l2_buffer);
> +	bool capture_streaming = isp_dev->capture.state.streaming;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&state->lock, flags);
> +	list_add_tail(&isp_buffer->list, &state->queue);
> +	spin_unlock_irqrestore(&state->lock, flags);
> +
> +	if (state->streaming && capture_streaming)
> +		sun6i_isp_state_update(isp_dev, false);
> +}
> +
> +static int sun6i_isp_params_start_streaming(struct vb2_queue *queue,
> +					    unsigned int count)
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +	bool capture_streaming = isp_dev->capture.state.streaming;
> +
> +	state->streaming = true;
> +
> +	/*
> +	 * Update the state as soon as possible if capture is streaming,
> +	 * otherwise it will be applied when capture starts streaming.
> +	 */
> +
> +	if (capture_streaming)
> +		sun6i_isp_state_update(isp_dev, false);
> +
> +	return 0;
> +}
> +
> +static void sun6i_isp_params_stop_streaming(struct vb2_queue *queue)
> +{
> +	struct sun6i_isp_device *isp_dev = vb2_get_drv_priv(queue);
> +	struct sun6i_isp_params_state *state = &isp_dev->params.state;
> +
> +	state->streaming = false;
> +	sun6i_isp_params_state_cleanup(isp_dev, true);
> +}
> +
> +static const struct vb2_ops sun6i_isp_params_queue_ops = {
> +	.queue_setup		= sun6i_isp_params_queue_setup,
> +	.buf_prepare		= sun6i_isp_params_buffer_prepare,
> +	.buf_queue		= sun6i_isp_params_buffer_queue,
> +	.start_streaming	= sun6i_isp_params_start_streaming,
> +	.stop_streaming		= sun6i_isp_params_stop_streaming,
> +	.wait_prepare		= vb2_ops_wait_prepare,
> +	.wait_finish		= vb2_ops_wait_finish,
> +};
> +
> +/* Video Device */
> +
> +static int sun6i_isp_params_querycap(struct file *file, void *private,
> +				     struct v4l2_capability *capability)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +	struct video_device *video_dev = &isp_dev->params.video_dev;
> +
> +	strscpy(capability->driver, SUN6I_ISP_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(isp_dev->dev));

This is no longer needed with commit
2a792fd5cf669d379d82354a99998d9ae9ff7d14 .

> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_params_enum_fmt(struct file *file, void *private,
> +				     struct v4l2_fmtdesc *fmtdesc)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +	struct v4l2_meta_format *params_format =
> +		&isp_dev->params.format.fmt.meta;
> +
> +	if (fmtdesc->index > 0)
> +		return -EINVAL;
> +
> +	fmtdesc->pixelformat = params_format->dataformat;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_params_g_fmt(struct file *file, void *private,
> +				  struct v4l2_format *format)
> +{
> +	struct sun6i_isp_device *isp_dev = video_drvdata(file);
> +
> +	*format = isp_dev->params.format;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_ioctl_ops sun6i_isp_params_ioctl_ops = {
> +	.vidioc_querycap		= sun6i_isp_params_querycap,
> +
> +	.vidioc_enum_fmt_meta_out	= sun6i_isp_params_enum_fmt,
> +	.vidioc_g_fmt_meta_out		= sun6i_isp_params_g_fmt,
> +	.vidioc_s_fmt_meta_out		= sun6i_isp_params_g_fmt,
> +	.vidioc_try_fmt_meta_out	= sun6i_isp_params_g_fmt,
> +
> +	.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,
> +
> +	.vidioc_log_status		= v4l2_ctrl_log_status,
> +	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
> +};
> +
> +static const struct v4l2_file_operations sun6i_isp_params_fops = {
> +	.owner		= THIS_MODULE,
> +	.unlocked_ioctl	= video_ioctl2,
> +	.open		= v4l2_fh_open,
> +	.release	= vb2_fop_release,
> +	.mmap		= vb2_fop_mmap,
> +	.poll		= vb2_fop_poll,
> +};
> +
> +/* Params */
> +
> +int sun6i_isp_params_setup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_params *params = &isp_dev->params;
> +	struct sun6i_isp_params_state *state = &params->state;
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	struct v4l2_subdev *proc_subdev = &isp_dev->proc.subdev;
> +	struct video_device *video_dev = &params->video_dev;
> +	struct vb2_queue *queue = &isp_dev->params.queue;
> +	struct media_pad *pad = &isp_dev->params.pad;
> +	struct v4l2_format *format = &isp_dev->params.format;
> +	struct v4l2_meta_format *params_format = &format->fmt.meta;
> +	int ret;
> +
> +	/* State */
> +
> +	INIT_LIST_HEAD(&state->queue);
> +	spin_lock_init(&state->lock);
> +
> +	/* Media Pads */
> +
> +	pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
> +
> +	ret = media_entity_pads_init(&video_dev->entity, 1, pad);
> +	if (ret)
> +		goto error_mutex;
> +
> +	/* Queue */
> +
> +	mutex_init(&params->lock);
> +
> +	queue->type = V4L2_BUF_TYPE_META_OUTPUT;
> +	queue->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
> +	queue->buf_struct_size = sizeof(struct sun6i_isp_buffer);
> +	queue->ops = &sun6i_isp_params_queue_ops;
> +	queue->mem_ops = &vb2_vmalloc_memops;
> +	queue->min_buffers_needed = 1;
> +	queue->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> +	queue->lock = &params->lock;
> +	queue->dev = isp_dev->dev;
> +	queue->drv_priv = isp_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;
> +	params_format->dataformat = V4L2_META_FMT_SUN6I_ISP_PARAMS;
> +	params_format->buffersize = sizeof(struct sun6i_isp_params_config);
> +
> +	/* Video Device */
> +
> +	strscpy(video_dev->name, SUN6I_ISP_PARAMS_NAME,
> +		sizeof(video_dev->name));
> +	video_dev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING;
> +	video_dev->vfl_dir = VFL_DIR_TX;
> +	video_dev->release = video_device_release_empty;
> +	video_dev->fops = &sun6i_isp_params_fops;
> +	video_dev->ioctl_ops = &sun6i_isp_params_ioctl_ops;
> +	video_dev->v4l2_dev = v4l2_dev;
> +	video_dev->queue = queue;
> +	video_dev->lock = &params->lock;
> +
> +	video_set_drvdata(video_dev, isp_dev);
> +
> +	ret = video_register_device(video_dev, VFL_TYPE_VIDEO, -1);
> +	if (ret) {
> +		v4l2_err(v4l2_dev, "failed to register video device: %d\n",
> +			 ret);
> +		goto error_media_entity;
> +	}
> +
> +	v4l2_info(v4l2_dev, "device %s registered as %s\n", video_dev->name,
> +		  video_device_node_name(video_dev));
> +
> +	/* Media Pad Link */
> +
> +	ret = media_create_pad_link(&video_dev->entity, 0,
> +				    &proc_subdev->entity,
> +				    SUN6I_ISP_PROC_PAD_SINK_PARAMS,
> +				    MEDIA_LNK_FL_ENABLED |
> +				    MEDIA_LNK_FL_IMMUTABLE);
> +	if (ret < 0) {
> +		v4l2_err(v4l2_dev, "failed to create %s:%u -> %s:%u link\n",
> +			 video_dev->entity.name, 0, proc_subdev->entity.name,
> +			 SUN6I_ISP_PROC_PAD_SINK_PARAMS);
> +		goto error_video_device;
> +	}
> +
> +	return 0;
> +
> +error_video_device:
> +	vb2_video_unregister_device(video_dev);
> +
> +error_media_entity:
> +	media_entity_cleanup(&video_dev->entity);
> +
> +error_mutex:
> +	mutex_destroy(&params->lock);
> +
> +	return ret;
> +}
> +
> +void sun6i_isp_params_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct sun6i_isp_params *params = &isp_dev->params;
> +	struct video_device *video_dev = &params->video_dev;
> +
> +	vb2_video_unregister_device(video_dev);
> +	media_entity_cleanup(&video_dev->entity);
> +	mutex_destroy(&params->lock);
> +}
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h
> new file mode 100644
> index 000000000000..50f10f879c42
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_params.h
> @@ -0,0 +1,52 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#ifndef _SUN6I_ISP_PARAMS_H_
> +#define _SUN6I_ISP_PARAMS_H_
> +
> +#include <media/v4l2-device.h>
> +
> +#define SUN6I_ISP_PARAMS_NAME		"sun6i-isp-params"
> +
> +struct sun6i_isp_device;
> +
> +struct sun6i_isp_params_state {
> +	struct list_head		queue; /* Queue and buffers lock. */
> +	spinlock_t			lock;
> +
> +	struct sun6i_isp_buffer		*pending;
> +
> +	bool				configured;
> +	bool				streaming;
> +};
> +
> +struct sun6i_isp_params {
> +	struct sun6i_isp_params_state	state;
> +
> +	struct video_device		video_dev;
> +	struct vb2_queue		queue;
> +	struct mutex			lock; /* Queue lock. */
> +	struct media_pad		pad;
> +
> +	struct v4l2_format		format;
> +};
> +
> +/* Params */
> +
> +void sun6i_isp_params_configure(struct sun6i_isp_device *isp_dev);
> +
> +/* State */
> +
> +void sun6i_isp_params_state_update(struct sun6i_isp_device *isp_dev,
> +				   bool *update);
> +void sun6i_isp_params_state_complete(struct sun6i_isp_device *isp_dev);
> +
> +/* Params */
> +
> +int sun6i_isp_params_setup(struct sun6i_isp_device *isp_dev);
> +void sun6i_isp_params_cleanup(struct sun6i_isp_device *isp_dev);
> +
> +#endif
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c
> new file mode 100644
> index 000000000000..edaf5ee8b61c
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.c
> @@ -0,0 +1,578 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#include <linux/pm_runtime.h>
> +#include <linux/regmap.h>
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#include "sun6i_isp.h"
> +#include "sun6i_isp_capture.h"
> +#include "sun6i_isp_params.h"
> +#include "sun6i_isp_proc.h"
> +#include "sun6i_isp_reg.h"
> +
> +/* Helpers */
> +
> +void sun6i_isp_proc_dimensions(struct sun6i_isp_device *isp_dev,
> +			       unsigned int *width, unsigned int *height)
> +{
> +	if (width)
> +		*width = isp_dev->proc.mbus_format.width;
> +	if (height)
> +		*height = isp_dev->proc.mbus_format.height;
> +}
> +
> +/* Format */
> +
> +static const struct sun6i_isp_proc_format sun6i_isp_proc_formats[] = {
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR8_1X8,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_BGGR,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG8_1X8,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_GBRG,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG8_1X8,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_GRBG,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB8_1X8,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_RGGB,
> +	},
> +
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SBGGR10_1X10,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_BGGR,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SGBRG10_1X10,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_GBRG,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SGRBG10_1X10,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_GRBG,
> +	},
> +	{
> +		.mbus_code	= MEDIA_BUS_FMT_SRGGB10_1X10,
> +		.input_format	= SUN6I_ISP_INPUT_FMT_RAW_RGGB,
> +	},
> +};
> +
> +const struct sun6i_isp_proc_format *sun6i_isp_proc_format_find(u32 mbus_code)
> +{
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(sun6i_isp_proc_formats); i++)
> +		if (sun6i_isp_proc_formats[i].mbus_code == mbus_code)
> +			return &sun6i_isp_proc_formats[i];
> +
> +	return NULL;
> +}
> +
> +/* Processor */
> +
> +static void sun6i_isp_proc_irq_enable(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG,
> +		     SUN6I_ISP_FE_INT_EN_FINISH |
> +		     SUN6I_ISP_FE_INT_EN_START |
> +		     SUN6I_ISP_FE_INT_EN_PARA_SAVE |
> +		     SUN6I_ISP_FE_INT_EN_PARA_LOAD |
> +		     SUN6I_ISP_FE_INT_EN_SRC0_FIFO |
> +		     SUN6I_ISP_FE_INT_EN_ROT_FINISH);
> +}
> +
> +static void sun6i_isp_proc_irq_disable(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG, 0);
> +}
> +
> +static void sun6i_isp_proc_irq_clear(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_INT_EN_REG, 0);
> +	regmap_write(regmap, SUN6I_ISP_FE_INT_STA_REG,
> +		     SUN6I_ISP_FE_INT_STA_CLEAR);
> +}
> +
> +static void sun6i_isp_proc_enable(struct sun6i_isp_device *isp_dev,
> +				  struct sun6i_isp_proc_source *source)
> +{
> +	struct sun6i_isp_proc *proc = &isp_dev->proc;
> +	struct regmap *regmap = isp_dev->regmap;
> +	u8 mode;
> +
> +	/* Frontend */
> +
> +	if (source == &proc->source_csi0)
> +		mode = SUN6I_ISP_SRC_MODE_CSI(0);
> +	else
> +		mode = SUN6I_ISP_SRC_MODE_CSI(1);
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_CFG_REG,
> +		     SUN6I_ISP_FE_CFG_EN | SUN6I_ISP_FE_CFG_SRC0_MODE(mode));
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG,
> +		     SUN6I_ISP_FE_CTRL_VCAP_EN | SUN6I_ISP_FE_CTRL_PARA_READY);
> +}
> +
> +static void sun6i_isp_proc_disable(struct sun6i_isp_device *isp_dev)
> +{
> +	struct regmap *regmap = isp_dev->regmap;
> +
> +	/* Frontend */
> +
> +	regmap_write(regmap, SUN6I_ISP_FE_CTRL_REG, 0);
> +	regmap_write(regmap, SUN6I_ISP_FE_CFG_REG, 0);
> +}
> +
> +static void sun6i_isp_proc_configure(struct sun6i_isp_device *isp_dev)
> +{
> +	struct v4l2_mbus_framefmt *mbus_format = &isp_dev->proc.mbus_format;
> +	const struct sun6i_isp_proc_format *format;
> +	u32 value;
> +
> +	/* Module */
> +
> +	value = sun6i_isp_load_read(isp_dev, SUN6I_ISP_MODULE_EN_REG);
> +	value |= SUN6I_ISP_MODULE_EN_SRC0;
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODULE_EN_REG, value);
> +
> +	/* Input */
> +
> +	format = sun6i_isp_proc_format_find(mbus_format->code);
> +	if (WARN_ON(!format))
> +		return;
> +
> +	sun6i_isp_load_write(isp_dev, SUN6I_ISP_MODE_REG,
> +			     SUN6I_ISP_MODE_INPUT_FMT(format->input_format) |
> +			     SUN6I_ISP_MODE_INPUT_YUV_SEQ(format->input_yuv_seq) |
> +			     SUN6I_ISP_MODE_SHARP(1) |
> +			     SUN6I_ISP_MODE_HIST(2));
> +}
> +
> +/* V4L2 Subdev */
> +
> +static int sun6i_isp_proc_s_stream(struct v4l2_subdev *subdev, int on)
> +{
> +	struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev);
> +	struct sun6i_isp_proc *proc = &isp_dev->proc;
> +	struct media_entity *proc_entity = &proc->subdev.entity;
> +	struct device *dev = isp_dev->dev;
> +	struct sun6i_isp_proc_source *source;
> +	struct v4l2_subdev *source_subdev;
> +	struct media_link *link;
> +	/* Initialize to 0 to use both in disable label (ret != 0) and off. */
> +	int ret = 0;
> +
> +	/* Source */
> +
> +	link = media_entity_get_single_enabled_link(proc_entity,
> +						    SUN6I_ISP_PROC_PAD_SINK_CSI);
> +	if (IS_ERR(link)) {
> +		dev_err(dev,
> +			"zero or more than a single source connected to the bridge\n");
> +		return PTR_ERR(link);
> +	}
> +
> +	source_subdev = media_entity_to_v4l2_subdev(link->source->entity);
> +
> +	if (source_subdev == proc->source_csi0.subdev)
> +		source = &proc->source_csi0;
> +	else
> +		source = &proc->source_csi1;
> +
> +	if (!on) {
> +		sun6i_isp_proc_irq_disable(isp_dev);
> +		v4l2_subdev_call(source_subdev, video, s_stream, 0);
> +		goto disable;
> +	}
> +
> +	/* PM */
> +
> +	ret = pm_runtime_resume_and_get(dev);
> +	if (ret < 0)
> +		return ret;
> +
> +	/* Clear */
> +
> +	sun6i_isp_proc_irq_clear(isp_dev);
> +
> +	/* Configure */
> +
> +	sun6i_isp_tables_configure(isp_dev);
> +	sun6i_isp_params_configure(isp_dev);
> +	sun6i_isp_proc_configure(isp_dev);
> +	sun6i_isp_capture_configure(isp_dev);
> +
> +	/* State Update */
> +
> +	sun6i_isp_state_update(isp_dev, true);
> +
> +	/* Enable */
> +
> +	sun6i_isp_proc_irq_enable(isp_dev);
> +	sun6i_isp_proc_enable(isp_dev, source);
> +
> +	ret = v4l2_subdev_call(source_subdev, video, s_stream, 1);
> +	if (ret && ret != -ENOIOCTLCMD) {
> +		sun6i_isp_proc_irq_disable(isp_dev);
> +		goto disable;
> +	}
> +
> +	return 0;
> +
> +disable:
> +	sun6i_isp_proc_disable(isp_dev);
> +
> +	pm_runtime_put(dev);
> +
> +	return ret;
> +}
> +
> +static const struct v4l2_subdev_video_ops sun6i_isp_proc_video_ops = {
> +	.s_stream	= sun6i_isp_proc_s_stream,
> +};
> +
> +static void
> +sun6i_isp_proc_mbus_format_prepare(struct v4l2_mbus_framefmt *mbus_format)
> +{
> +	if (!sun6i_isp_proc_format_find(mbus_format->code))
> +		mbus_format->code = sun6i_isp_proc_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_isp_proc_init_cfg(struct v4l2_subdev *subdev,
> +				   struct v4l2_subdev_state *state)
> +{
> +	struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev);
> +	unsigned int pad = SUN6I_ISP_PROC_PAD_SINK_CSI;
> +	struct v4l2_mbus_framefmt *mbus_format =
> +		v4l2_subdev_get_try_format(subdev, state, pad);
> +	struct mutex *lock = &isp_dev->proc.lock;
> +
> +	mutex_lock(lock);
> +
> +	mbus_format->code = sun6i_isp_proc_formats[0].mbus_code;
> +	mbus_format->width = 1280;
> +	mbus_format->height = 720;
> +
> +	sun6i_isp_proc_mbus_format_prepare(mbus_format);
> +
> +	mutex_unlock(lock);
> +
> +	return 0;
> +}
> +
> +static int
> +sun6i_isp_proc_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_isp_proc_formats))
> +		return -EINVAL;
> +
> +	code_enum->code = sun6i_isp_proc_formats[code_enum->index].mbus_code;
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_proc_get_fmt(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_format *format)
> +{
> +	struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *mbus_format = &format->format;
> +	struct mutex *lock = &isp_dev->proc.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 = isp_dev->proc.mbus_format;
> +
> +	mutex_unlock(lock);
> +
> +	return 0;
> +}
> +
> +static int sun6i_isp_proc_set_fmt(struct v4l2_subdev *subdev,
> +				  struct v4l2_subdev_state *state,
> +				  struct v4l2_subdev_format *format)
> +{
> +	struct sun6i_isp_device *isp_dev = v4l2_get_subdevdata(subdev);
> +	struct v4l2_mbus_framefmt *mbus_format = &format->format;
> +	struct mutex *lock = &isp_dev->proc.lock;
> +
> +	mutex_lock(lock);
> +
> +	sun6i_isp_proc_mbus_format_prepare(mbus_format);
> +
> +	if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> +		*v4l2_subdev_get_try_format(subdev, state, format->pad) =
> +			*mbus_format;
> +	else
> +		isp_dev->proc.mbus_format = *mbus_format;
> +
> +	mutex_unlock(lock);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_subdev_pad_ops sun6i_isp_proc_pad_ops = {
> +	.init_cfg	= sun6i_isp_proc_init_cfg,
> +	.enum_mbus_code	= sun6i_isp_proc_enum_mbus_code,
> +	.get_fmt	= sun6i_isp_proc_get_fmt,
> +	.set_fmt	= sun6i_isp_proc_set_fmt,
> +};
> +
> +const struct v4l2_subdev_ops sun6i_isp_proc_subdev_ops = {

This can be static, can't it?

> +	.video	= &sun6i_isp_proc_video_ops,
> +	.pad	= &sun6i_isp_proc_pad_ops,
> +};
> +
> +/* Media Entity */
> +
> +static const struct media_entity_operations sun6i_isp_proc_entity_ops = {
> +	.link_validate	= v4l2_subdev_link_validate,
> +};
> +
> +/* V4L2 Async */
> +
> +static int sun6i_isp_proc_link(struct sun6i_isp_device *isp_dev,
> +			       int sink_pad_index,
> +			       struct v4l2_subdev *remote_subdev, bool enabled)
> +{
> +	struct device *dev = isp_dev->dev;
> +	struct v4l2_subdev *subdev = &isp_dev->proc.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_isp_proc_notifier_bound(struct v4l2_async_notifier *notifier,
> +					 struct v4l2_subdev *remote_subdev,
> +					 struct v4l2_async_subdev *async_subdev)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		container_of(notifier, struct sun6i_isp_device, proc.notifier);
> +	struct sun6i_isp_proc_async_subdev *proc_async_subdev =
> +		container_of(async_subdev, struct sun6i_isp_proc_async_subdev,
> +			     async_subdev);
> +	struct sun6i_isp_proc *proc = &isp_dev->proc;
> +	struct sun6i_isp_proc_source *source = proc_async_subdev->source;
> +	bool enabled;
> +
> +	switch (source->endpoint.base.port) {
> +	case SUN6I_ISP_PORT_CSI0:
> +		source = &proc->source_csi0;
> +		enabled = true;
> +		break;
> +	case SUN6I_ISP_PORT_CSI1:
> +		source = &proc->source_csi1;
> +		enabled = !proc->source_csi0.expected;
> +		break;
> +	default:
> +		break;
> +	}
> +
> +	source->subdev = remote_subdev;
> +
> +	return sun6i_isp_proc_link(isp_dev, SUN6I_ISP_PROC_PAD_SINK_CSI,
> +				   remote_subdev, enabled);
> +}
> +
> +static int
> +sun6i_isp_proc_notifier_complete(struct v4l2_async_notifier *notifier)
> +{
> +	struct sun6i_isp_device *isp_dev =
> +		container_of(notifier, struct sun6i_isp_device, proc.notifier);
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	int ret;
> +
> +	ret = v4l2_device_register_subdev_nodes(v4l2_dev);
> +	if (ret)
> +		return ret;
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_async_notifier_operations
> +sun6i_isp_proc_notifier_ops = {
> +	.bound		= sun6i_isp_proc_notifier_bound,
> +	.complete	= sun6i_isp_proc_notifier_complete,
> +};
> +
> +/* Processor */
> +
> +static int sun6i_isp_proc_source_setup(struct sun6i_isp_device *isp_dev,
> +				       struct sun6i_isp_proc_source *source,
> +				       u32 port)
> +{
> +	struct device *dev = isp_dev->dev;
> +	struct v4l2_async_notifier *notifier = &isp_dev->proc.notifier;
> +	struct v4l2_fwnode_endpoint *endpoint = &source->endpoint;
> +	struct sun6i_isp_proc_async_subdev *proc_async_subdev;
> +	struct fwnode_handle *handle = NULL;
> +	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;
> +
> +	proc_async_subdev =
> +		v4l2_async_nf_add_fwnode_remote(notifier, handle,
> +						struct
> +						sun6i_isp_proc_async_subdev);
> +	if (IS_ERR(proc_async_subdev)) {
> +		ret = PTR_ERR(proc_async_subdev);
> +		goto complete;
> +	}
> +
> +	proc_async_subdev->source = source;
> +
> +	source->expected = true;
> +
> +complete:
> +	fwnode_handle_put(handle);
> +
> +	return ret;
> +}
> +
> +int sun6i_isp_proc_setup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct device *dev = isp_dev->dev;
> +	struct sun6i_isp_proc *proc = &isp_dev->proc;
> +	struct v4l2_device *v4l2_dev = &isp_dev->v4l2.v4l2_dev;
> +	struct v4l2_async_notifier *notifier = &proc->notifier;
> +	struct v4l2_subdev *subdev = &proc->subdev;
> +	struct media_pad *pads = proc->pads;
> +	int ret;
> +
> +	mutex_init(&proc->lock);
> +
> +	/* V4L2 Subdev */
> +
> +	v4l2_subdev_init(subdev, &sun6i_isp_proc_subdev_ops);
> +	strscpy(subdev->name, SUN6I_ISP_PROC_NAME, sizeof(subdev->name));
> +	subdev->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> +	subdev->owner = THIS_MODULE;
> +	subdev->dev = dev;
> +
> +	v4l2_set_subdevdata(subdev, isp_dev);
> +
> +	/* Media Entity */
> +
> +	subdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
> +	subdev->entity.ops = &sun6i_isp_proc_entity_ops;
> +
> +	/* Media Pads */
> +
> +	pads[SUN6I_ISP_PROC_PAD_SINK_CSI].flags = MEDIA_PAD_FL_SINK |
> +						  MEDIA_PAD_FL_MUST_CONNECT;
> +	pads[SUN6I_ISP_PROC_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK |
> +						     MEDIA_PAD_FL_MUST_CONNECT;
> +	pads[SUN6I_ISP_PROC_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> +
> +	ret = media_entity_pads_init(&subdev->entity, SUN6I_ISP_PROC_PAD_COUNT,
> +				     pads);
> +	if (ret)
> +		return ret;
> +
> +	/* V4L2 Subdev */
> +
> +	ret = v4l2_device_register_subdev(v4l2_dev, subdev);
> +	if (ret < 0) {
> +		v4l2_err(v4l2_dev, "failed to register v4l2 subdev: %d\n", ret);
> +		goto error_media_entity;
> +	}
> +
> +	/* V4L2 Async */
> +
> +	v4l2_async_nf_init(notifier);
> +	notifier->ops = &sun6i_isp_proc_notifier_ops;
> +
> +	sun6i_isp_proc_source_setup(isp_dev, &proc->source_csi0,
> +				    SUN6I_ISP_PORT_CSI0);
> +	sun6i_isp_proc_source_setup(isp_dev, &proc->source_csi1,
> +				    SUN6I_ISP_PORT_CSI1);
> +
> +	ret = v4l2_async_nf_register(v4l2_dev, notifier);
> +	if (ret) {
> +		v4l2_err(v4l2_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);
> +
> +	v4l2_device_unregister_subdev(subdev);
> +
> +error_media_entity:
> +	media_entity_cleanup(&subdev->entity);
> +
> +	return ret;
> +}
> +
> +void sun6i_isp_proc_cleanup(struct sun6i_isp_device *isp_dev)
> +{
> +	struct v4l2_async_notifier *notifier = &isp_dev->proc.notifier;
> +	struct v4l2_subdev *subdev = &isp_dev->proc.subdev;
> +
> +	v4l2_async_nf_unregister(notifier);
> +	v4l2_async_nf_cleanup(notifier);
> +
> +	v4l2_device_unregister_subdev(subdev);
> +	media_entity_cleanup(&subdev->entity);
> +}
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h
> new file mode 100644
> index 000000000000..c5c274e21ad5
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_proc.h
> @@ -0,0 +1,66 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#ifndef _SUN6I_ISP_PROC_H_
> +#define _SUN6I_ISP_PROC_H_
> +
> +#include <media/v4l2-device.h>
> +#include <media/v4l2-fwnode.h>
> +
> +#define SUN6I_ISP_PROC_NAME		"sun6i-isp-proc"
> +
> +enum sun6i_isp_proc_pad {
> +	SUN6I_ISP_PROC_PAD_SINK_CSI	= 0,
> +	SUN6I_ISP_PROC_PAD_SINK_PARAMS	= 1,
> +	SUN6I_ISP_PROC_PAD_SOURCE	= 2,
> +	SUN6I_ISP_PROC_PAD_COUNT	= 3,
> +};
> +
> +struct sun6i_isp_device;
> +
> +struct sun6i_isp_proc_format {
> +	u32	mbus_code;
> +	u8	input_format;
> +	u8	input_yuv_seq;
> +};
> +
> +struct sun6i_isp_proc_source {
> +	struct v4l2_subdev		*subdev;
> +	struct v4l2_fwnode_endpoint	endpoint;
> +	bool				expected;
> +};
> +
> +struct sun6i_isp_proc_async_subdev {
> +	struct v4l2_async_subdev	async_subdev;
> +	struct sun6i_isp_proc_source	*source;
> +};
> +
> +struct sun6i_isp_proc {
> +	struct v4l2_subdev		subdev;
> +	struct media_pad		pads[3];
> +	struct v4l2_async_notifier	notifier;
> +	struct v4l2_mbus_framefmt	mbus_format;
> +	struct mutex			lock; /* Mbus format lock. */
> +
> +	struct sun6i_isp_proc_source	source_csi0;
> +	struct sun6i_isp_proc_source	source_csi1;
> +};
> +
> +/* Helpers */
> +
> +void sun6i_isp_proc_dimensions(struct sun6i_isp_device *isp_dev,
> +			       unsigned int *width, unsigned int *height);
> +
> +/* Format */
> +
> +const struct sun6i_isp_proc_format *sun6i_isp_proc_format_find(u32 mbus_code);
> +
> +/* Proc */
> +
> +int sun6i_isp_proc_setup(struct sun6i_isp_device *isp_dev);
> +void sun6i_isp_proc_cleanup(struct sun6i_isp_device *isp_dev);
> +
> +#endif
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h
> new file mode 100644
> index 000000000000..83b9cdab2134
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/sun6i_isp_reg.h
> @@ -0,0 +1,275 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2021-2022 Bootlin
> + * Author: Paul Kocialkowski <paul.kocialkowski@xxxxxxxxxxx>
> + */
> +
> +#ifndef _SUN6I_ISP_REG_H_
> +#define _SUN6I_ISP_REG_H_
> +
> +#include <linux/kernel.h>
> +
> +#define SUN6I_ISP_ADDR_VALUE(a)			((a) >> 2)
> +
> +/* Frontend */
> +
> +#define SUN6I_ISP_SRC_MODE_DRAM			0
> +#define SUN6I_ISP_SRC_MODE_CSI(n)		(1 + (n))
> +
> +#define SUN6I_ISP_FE_CFG_REG			0x0
> +#define SUN6I_ISP_FE_CFG_EN			BIT(0)
> +#define SUN6I_ISP_FE_CFG_SRC0_MODE(v)		(((v) << 8) & GENMASK(9, 8))
> +#define SUN6I_ISP_FE_CFG_SRC1_MODE(v)		(((v) << 16) & GENMASK(17, 16))
> +
> +#define SUN6I_ISP_FE_CTRL_REG			0x4
> +#define SUN6I_ISP_FE_CTRL_SCAP_EN		BIT(0)
> +#define SUN6I_ISP_FE_CTRL_VCAP_EN		BIT(1)
> +#define SUN6I_ISP_FE_CTRL_PARA_READY		BIT(2)
> +#define SUN6I_ISP_FE_CTRL_LUT_UPDATE		BIT(3)
> +#define SUN6I_ISP_FE_CTRL_LENS_UPDATE		BIT(4)
> +#define SUN6I_ISP_FE_CTRL_GAMMA_UPDATE		BIT(5)
> +#define SUN6I_ISP_FE_CTRL_DRC_UPDATE		BIT(6)
> +#define SUN6I_ISP_FE_CTRL_DISC_UPDATE		BIT(7)
> +#define SUN6I_ISP_FE_CTRL_OUTPUT_SPEED_CTRL(v)	(((v) << 16) & GENMASK(17, 16))
> +#define SUN6I_ISP_FE_CTRL_VCAP_READ_START	BIT(31)
> +
> +#define SUN6I_ISP_FE_INT_EN_REG			0x8
> +#define SUN6I_ISP_FE_INT_EN_FINISH		BIT(0)
> +#define SUN6I_ISP_FE_INT_EN_START		BIT(1)
> +#define SUN6I_ISP_FE_INT_EN_PARA_SAVE		BIT(2)
> +#define SUN6I_ISP_FE_INT_EN_PARA_LOAD		BIT(3)
> +#define SUN6I_ISP_FE_INT_EN_SRC0_FIFO		BIT(4)
> +#define SUN6I_ISP_FE_INT_EN_SRC1_FIFO		BIT(5)
> +#define SUN6I_ISP_FE_INT_EN_ROT_FINISH		BIT(6)
> +#define SUN6I_ISP_FE_INT_EN_LINE_NUM_START	BIT(7)
> +
> +#define SUN6I_ISP_FE_INT_STA_REG		0xc
> +#define SUN6I_ISP_FE_INT_STA_CLEAR		0xff
> +#define SUN6I_ISP_FE_INT_STA_FINISH		BIT(0)
> +#define SUN6I_ISP_FE_INT_STA_START		BIT(1)
> +#define SUN6I_ISP_FE_INT_STA_PARA_SAVE		BIT(2)
> +#define SUN6I_ISP_FE_INT_STA_PARA_LOAD		BIT(3)
> +#define SUN6I_ISP_FE_INT_STA_SRC0_FIFO		BIT(4)
> +#define SUN6I_ISP_FE_INT_STA_SRC1_FIFO		BIT(5)
> +#define SUN6I_ISP_FE_INT_STA_ROT_FINISH		BIT(6)
> +#define SUN6I_ISP_FE_INT_STA_LINE_NUM_START	BIT(7)
> +
> +/* Only since sun9i-a80-isp. */
> +#define SUN6I_ISP_FE_INT_LINE_NUM_REG		0x18
> +#define SUN6I_ISP_FE_ROT_OF_CFG_REG		0x1c
> +
> +/* Buffers/tables */
> +
> +#define SUN6I_ISP_REG_LOAD_ADDR_REG		0x20
> +#define SUN6I_ISP_REG_SAVE_ADDR_REG		0x24
> +
> +#define SUN6I_ISP_LUT_TABLE_ADDR_REG		0x28
> +#define SUN6I_ISP_DRC_TABLE_ADDR_REG		0x2c
> +#define SUN6I_ISP_STATS_ADDR_REG		0x30
> +
> +/* SRAM */
> +
> +#define SUN6I_ISP_SRAM_RW_OFFSET_REG		0x38
> +#define SUN6I_ISP_SRAM_RW_DATA_REG		0x3c
> +
> +/* Global */
> +
> +#define SUN6I_ISP_MODULE_EN_REG			0x40
> +#define SUN6I_ISP_MODULE_EN_AE			BIT(0)
> +#define SUN6I_ISP_MODULE_EN_OBC			BIT(1)
> +#define SUN6I_ISP_MODULE_EN_DPC_LUT		BIT(2)
> +#define SUN6I_ISP_MODULE_EN_DPC_OTF		BIT(3)
> +#define SUN6I_ISP_MODULE_EN_BDNF		BIT(4)
> +#define SUN6I_ISP_MODULE_EN_AWB			BIT(6)
> +#define SUN6I_ISP_MODULE_EN_WB			BIT(7)
> +#define SUN6I_ISP_MODULE_EN_LSC			BIT(8)
> +#define SUN6I_ISP_MODULE_EN_BGC			BIT(9)
> +#define SUN6I_ISP_MODULE_EN_SAP			BIT(10)
> +#define SUN6I_ISP_MODULE_EN_AF			BIT(11)
> +#define SUN6I_ISP_MODULE_EN_RGB2RGB		BIT(12)
> +#define SUN6I_ISP_MODULE_EN_RGB_DRC		BIT(13)
> +#define SUN6I_ISP_MODULE_EN_TDNF		BIT(15)
> +#define SUN6I_ISP_MODULE_EN_AFS			BIT(16)
> +#define SUN6I_ISP_MODULE_EN_HIST		BIT(17)
> +#define SUN6I_ISP_MODULE_EN_YUV_GAIN_OFFSET	BIT(18)
> +#define SUN6I_ISP_MODULE_EN_YUV_DRC		BIT(19)
> +#define SUN6I_ISP_MODULE_EN_TG			BIT(20)
> +#define SUN6I_ISP_MODULE_EN_ROT			BIT(21)
> +#define SUN6I_ISP_MODULE_EN_CONTRAST		BIT(22)
> +#define SUN6I_ISP_MODULE_EN_SATU		BIT(24)
> +#define SUN6I_ISP_MODULE_EN_SRC1		BIT(30)
> +#define SUN6I_ISP_MODULE_EN_SRC0		BIT(31)
> +
> +#define SUN6I_ISP_MODE_REG			0x44
> +#define SUN6I_ISP_MODE_INPUT_FMT(v)		((v) & GENMASK(2, 0))
> +#define SUN6I_ISP_MODE_INPUT_YUV_SEQ(v)		(((v) << 3) & GENMASK(4, 3))
> +#define SUN6I_ISP_MODE_OTF_DPC(v)		(((v) << 16) & BIT(16))
> +#define SUN6I_ISP_MODE_SHARP(v)			(((v) << 17) & BIT(17))
> +#define SUN6I_ISP_MODE_HIST(v)			(((v) << 20) & GENMASK(21, 20))
> +
> +#define SUN6I_ISP_INPUT_FMT_YUV420		0
> +#define SUN6I_ISP_INPUT_FMT_YUV422		1
> +#define SUN6I_ISP_INPUT_FMT_RAW_BGGR		4
> +#define SUN6I_ISP_INPUT_FMT_RAW_RGGB		5
> +#define SUN6I_ISP_INPUT_FMT_RAW_GBRG		6
> +#define SUN6I_ISP_INPUT_FMT_RAW_GRBG		7
> +
> +#define SUN6I_ISP_INPUT_YUV_SEQ_YUYV		0
> +#define SUN6I_ISP_INPUT_YUV_SEQ_YVYU		1
> +#define SUN6I_ISP_INPUT_YUV_SEQ_UYVY		2
> +#define SUN6I_ISP_INPUT_YUV_SEQ_VYUY		3
> +
> +#define SUN6I_ISP_IN_CFG_REG			0x48
> +#define SUN6I_ISP_IN_CFG_STRIDE_DIV16(v)	((v) & GENMASK(10, 0))
> +
> +#define SUN6I_ISP_IN_LUMA_RGB_ADDR0_REG		0x4c
> +#define SUN6I_ISP_IN_CHROMA_ADDR0_REG		0x50
> +#define SUN6I_ISP_IN_LUMA_RGB_ADDR1_REG		0x54
> +#define SUN6I_ISP_IN_CHROMA_ADDR1_REG		0x58
> +
> +/* AE */
> +
> +#define SUN6I_ISP_AE_CFG_REG			0x60
> +#define SUN6I_ISP_AE_CFG_LOW_BRI_TH(v)		((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_AE_CFG_HORZ_NUM(v)		(((v) << 12) & GENMASK(15, 12))
> +#define SUN6I_ISP_AE_CFG_HIGH_BRI_TH(v)		(((v) << 16) & GENMASK(27, 16))
> +#define SUN6I_ISP_AE_CFG_VERT_NUM(v)		(((v) << 28) & GENMASK(31, 28))
> +
> +#define SUN6I_ISP_AE_SIZE_REG			0x64
> +#define SUN6I_ISP_AE_SIZE_WIDTH(v)		((v) & GENMASK(10, 0))
> +#define SUN6I_ISP_AE_SIZE_HEIGHT(v)		(((v) << 16) & GENMASK(26, 16))
> +
> +#define SUN6I_ISP_AE_POS_REG			0x68
> +#define SUN6I_ISP_AE_POS_HORZ_START(v)		((v) & GENMASK(10, 0))
> +#define SUN6I_ISP_AE_POS_VERT_START(v)		(((v) << 16) & GENMASK(26, 16))
> +
> +/* OB */
> +
> +#define SUN6I_ISP_OB_SIZE_REG			0x78
> +#define SUN6I_ISP_OB_SIZE_WIDTH(v)		((v) & GENMASK(13, 0))
> +#define SUN6I_ISP_OB_SIZE_HEIGHT(v)		(((v) << 16) & GENMASK(29, 16))
> +
> +#define SUN6I_ISP_OB_VALID_REG			0x7c
> +#define SUN6I_ISP_OB_VALID_WIDTH(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_OB_VALID_HEIGHT(v)		(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_OB_SRC0_VALID_START_REG	0x80
> +#define SUN6I_ISP_OB_SRC0_VALID_START_HORZ(v)	((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_OB_SRC0_VALID_START_VERT(v)	(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_OB_SRC1_VALID_START_REG	0x84
> +#define SUN6I_ISP_OB_SRC1_VALID_START_HORZ(v)	((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_OB_SRC1_VALID_START_VERT(v)	(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_OB_SPRITE_REG			0x88
> +#define SUN6I_ISP_OB_SPRITE_WIDTH(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_OB_SPRITE_HEIGHT(v)		(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_OB_SPRITE_START_REG		0x8c
> +#define SUN6I_ISP_OB_SPRITE_START_HORZ(v)	((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_OB_SPRITE_START_VERT(v)	(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_OB_CFG_REG			0x90
> +#define SUN6I_ISP_OB_HORZ_POS_REG		0x94
> +#define SUN6I_ISP_OB_VERT_PARA_REG		0x98
> +#define SUN6I_ISP_OB_OFFSET_FIXED_REG		0x9c
> +
> +/* BDNF */
> +
> +#define SUN6I_ISP_BDNF_CFG_REG			0xcc
> +#define SUN6I_ISP_BDNF_CFG_IN_DIS_MIN(v)	((v) & GENMASK(7, 0))
> +#define SUN6I_ISP_BDNF_CFG_IN_DIS_MAX(v)	(((v) << 16) & GENMASK(23, 16))
> +
> +#define SUN6I_ISP_BDNF_COEF_RB_REG		0xd0
> +#define SUN6I_ISP_BDNF_COEF_RB(i, v)		(((v) << (4 * (i))) & \
> +						 GENMASK(4 * (i) + 3, 4 * (i)))
> +
> +#define SUN6I_ISP_BDNF_COEF_G_REG		0xd4
> +#define SUN6I_ISP_BDNF_COEF_G(i, v)		(((v) << (4 * (i))) & \
> +						 GENMASK(4 * (i) + 3, 4 * (i)))
> +
> +/* Bayer */
> +
> +#define SUN6I_ISP_BAYER_OFFSET0_REG		0xe0
> +#define SUN6I_ISP_BAYER_OFFSET0_R(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_BAYER_OFFSET0_GR(v)		(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_BAYER_OFFSET1_REG		0xe4
> +#define SUN6I_ISP_BAYER_OFFSET1_GB(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_BAYER_OFFSET1_B(v)		(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_BAYER_GAIN0_REG		0xe8
> +#define SUN6I_ISP_BAYER_GAIN0_R(v)		((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_BAYER_GAIN0_GR(v)		(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_BAYER_GAIN1_REG		0xec
> +#define SUN6I_ISP_BAYER_GAIN1_GB(v)		((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_BAYER_GAIN1_B(v)		(((v) << 16) & GENMASK(27, 16))
> +
> +/* WB */
> +
> +#define SUN6I_ISP_WB_GAIN0_REG			0x140
> +#define SUN6I_ISP_WB_GAIN0_R(v)			((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_WB_GAIN0_GR(v)		(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_WB_GAIN1_REG			0x144
> +#define SUN6I_ISP_WB_GAIN1_GB(v)		((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_WB_GAIN1_B(v)			(((v) << 16) & GENMASK(27, 16))
> +
> +#define SUN6I_ISP_WB_CFG_REG			0x148
> +#define SUN6I_ISP_WB_CFG_CLIP(v)		((v) & GENMASK(11, 0))
> +
> +/* Global */
> +
> +#define SUN6I_ISP_MCH_SIZE_CFG_REG		0x1e0
> +#define SUN6I_ISP_MCH_SIZE_CFG_WIDTH(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_MCH_SIZE_CFG_HEIGHT(v)	(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_MCH_SCALE_CFG_REG		0x1e4
> +#define SUN6I_ISP_MCH_SCALE_CFG_X_RATIO(v)	((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_MCH_SCALE_CFG_Y_RATIO(v)	(((v) << 16) & GENMASK(27, 16))
> +#define SUN6I_ISP_MCH_SCALE_CFG_WEIGHT_SHIFT(v)	(((v) << 28) & GENMASK(31, 28))
> +
> +#define SUN6I_ISP_SCH_SIZE_CFG_REG		0x1e8
> +#define SUN6I_ISP_SCH_SIZE_CFG_WIDTH(v)		((v) & GENMASK(12, 0))
> +#define SUN6I_ISP_SCH_SIZE_CFG_HEIGHT(v)	(((v) << 16) & GENMASK(28, 16))
> +
> +#define SUN6I_ISP_SCH_SCALE_CFG_REG		0x1ec
> +#define SUN6I_ISP_SCH_SCALE_CFG_X_RATIO(v)	((v) & GENMASK(11, 0))
> +#define SUN6I_ISP_SCH_SCALE_CFG_Y_RATIO(v)	(((v) << 16) & GENMASK(27, 16))
> +#define SUN6I_ISP_SCH_SCALE_CFG_WEIGHT_SHIFT(v)	(((v) << 28) & GENMASK(31, 28))
> +
> +#define SUN6I_ISP_MCH_CFG_REG			0x1f0
> +#define SUN6I_ISP_MCH_CFG_EN			BIT(0)
> +#define SUN6I_ISP_MCH_CFG_SCALE_EN		BIT(1)
> +#define SUN6I_ISP_MCH_CFG_OUTPUT_FMT(v)		(((v) << 2) & GENMASK(4, 2))
> +#define SUN6I_ISP_MCH_CFG_MIRROR_EN		BIT(5)
> +#define SUN6I_ISP_MCH_CFG_FLIP_EN		BIT(6)
> +#define SUN6I_ISP_MCH_CFG_STRIDE_Y_DIV4(v)	(((v) << 8) & GENMASK(18, 8))
> +#define SUN6I_ISP_MCH_CFG_STRIDE_UV_DIV4(v)	(((v) << 20) & GENMASK(30, 20))
> +
> +#define SUN6I_ISP_OUTPUT_FMT_YUV420SP		0
> +#define SUN6I_ISP_OUTPUT_FMT_YUV422SP		1
> +#define SUN6I_ISP_OUTPUT_FMT_YVU420SP		2
> +#define SUN6I_ISP_OUTPUT_FMT_YVU422SP		3
> +#define SUN6I_ISP_OUTPUT_FMT_YUV420P		4
> +#define SUN6I_ISP_OUTPUT_FMT_YUV422P		5
> +#define SUN6I_ISP_OUTPUT_FMT_YVU420P		6
> +#define SUN6I_ISP_OUTPUT_FMT_YVU422P		7
> +
> +#define SUN6I_ISP_SCH_CFG_REG			0x1f4
> +
> +#define SUN6I_ISP_MCH_Y_ADDR0_REG		0x1f8
> +#define SUN6I_ISP_MCH_U_ADDR0_REG		0x1fc
> +#define SUN6I_ISP_MCH_V_ADDR0_REG		0x200
> +#define SUN6I_ISP_MCH_Y_ADDR1_REG		0x204
> +#define SUN6I_ISP_MCH_U_ADDR1_REG		0x208
> +#define SUN6I_ISP_MCH_V_ADDR1_REG		0x20c
> +#define SUN6I_ISP_SCH_Y_ADDR0_REG		0x210
> +#define SUN6I_ISP_SCH_U_ADDR0_REG		0x214
> +#define SUN6I_ISP_SCH_V_ADDR0_REG		0x218
> +#define SUN6I_ISP_SCH_Y_ADDR1_REG		0x21c
> +#define SUN6I_ISP_SCH_U_ADDR1_REG		0x220
> +#define SUN6I_ISP_SCH_V_ADDR1_REG		0x224
> +
> +#endif
> diff --git a/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h b/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h
> new file mode 100644
> index 000000000000..fd2a0820aa98
> --- /dev/null
> +++ b/drivers/staging/media/sunxi/sun6i-isp/uapi/sun6i-isp-config.h
> @@ -0,0 +1,43 @@
> +/* SPDX-License-Identifier: ((GPL-2.0+ WITH Linux-syscall-note) OR MIT) */
> +/*
> + * Allwinner A31 ISP Configuration
> + */
> +
> +#ifndef _UAPI_SUN6I_ISP_CONFIG_H
> +#define _UAPI_SUN6I_ISP_CONFIG_H
> +
> +#include <linux/types.h>
> +
> +#define V4L2_META_FMT_SUN6I_ISP_PARAMS v4l2_fourcc('S', '6', 'I', 'P') /* Allwinner A31 ISP Parameters */
> +
> +#define SUN6I_ISP_MODULE_BAYER			(1U << 0)
> +#define SUN6I_ISP_MODULE_BDNF			(1U << 1)
> +
> +struct sun6i_isp_params_config_bayer {
> +	__u16	offset_r;
> +	__u16	offset_gr;
> +	__u16	offset_gb;
> +	__u16	offset_b;
> +
> +	__u16	gain_r;
> +	__u16	gain_gr;
> +	__u16	gain_gb;
> +	__u16	gain_b;
> +};
> +
> +struct sun6i_isp_params_config_bdnf {
> +	__u8	in_dis_min; // 8
> +	__u8	in_dis_max; // 10

Are these default values or something else? Better documentation was in the
TODO.txt file already.

> +
> +	__u8	coefficients_g[7];
> +	__u8	coefficients_rb[5];
> +};
> +
> +struct sun6i_isp_params_config {
> +	__u32					modules_used;
> +
> +	struct sun6i_isp_params_config_bayer	bayer;
> +	struct sun6i_isp_params_config_bdnf	bdnf;
> +};
> +
> +#endif /* _UAPI_SUN6I_ISP_CONFIG_H */

-- 
Kind regards,

Sakari Ailus




[Index of Archives]     [Linux Driver Development]     [Linux Driver Backports]     [DMA Engine]     [Linux GPIO]     [Linux SPI]     [Video for Linux]     [Linux USB Devel]     [Linux Coverity]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]
  Powered by Linux