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 = ¶ms->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 = ¶ms->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(¶ms->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 = ¶ms->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 = ¶ms->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(¶ms->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 = ¶ms->video_dev; > + > + vb2_video_unregister_device(video_dev); > + media_entity_cleanup(&video_dev->entity); > + mutex_destroy(¶ms->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