Re: [PATCH v5 05/16] media: mali-c55: Add Mali-C55 ISP driver

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

 



Hi Dan,

On Fri, Jun 21, 2024 at 10:28:56AM +0100, Daniel Scally wrote:
> On 20/06/2024 16:23, Laurent Pinchart wrote:
> > On Thu, Jun 20, 2024 at 03:49:23PM +0100, Daniel Scally wrote:
> >> On 20/06/2024 15:33, Dan Scally wrote:
> >>> On 30/05/2024 22:43, Laurent Pinchart wrote:
> >>>> And now the second part of the review, addressing mali-c55-capture.c and
> >>>> mali-c55-resizer.c. I've reviewed the code from the bottom up, so some
> >>>> messages may be repeated in an order that seems weird. Sorry about that.
> >>>>
> >>>> On Thu, May 30, 2024 at 03:15:10AM +0300, Laurent Pinchart wrote:
> >>>>> On Wed, May 29, 2024 at 04:28:47PM +0100, Daniel Scally wrote:
> >>>>>> Add a driver for Arm's Mali-C55 Image Signal Processor. The driver is
> >>>>>> V4L2 and Media Controller compliant and creates subdevices to manage
> >>>>>> the ISP itself, its internal test pattern generator as well as the
> >>>>>> crop, scaler and output format functionality for each of its two
> >>>>>> output devices.
> >>>>>>
> >>>>>> Acked-by: Nayden Kanchev <nayden.kanchev@xxxxxxx>
> >>>>>> Co-developed-by: Jacopo Mondi <jacopo.mondi@xxxxxxxxxxxxxxxx>
> >>>>>> Signed-off-by: Jacopo Mondi <jacopo.mondi@xxxxxxxxxxxxxxxx>
> >>>>>> Signed-off-by: Daniel Scally <dan.scally@xxxxxxxxxxxxxxxx>
> >>>>>> ---
> >>>>>> Changes in v5:
> >>>>>>
> >>>>>>      - Reworked input formats - previously we allowed representing input data
> >>>>>>        as any 8-16 bit format. Now we only allow input data to be represented
> >>>>>>        by the new 20-bit bayer formats, which is corrected to the equivalent
> >>>>>>        16-bit format in RAW bypass mode.
> >>>>>>      - Stopped bypassing blocks that we haven't added supporting parameters
> >>>>>>        for yet.
> >>>>>>      - Addressed most of Sakari's comments from the list
> >>>>>>
> >>>>>> Changes not yet made in v5:
> >>>>>>
> >>>>>>      - The output pipelines can still be started and stopped independently of
> >>>>>>        one another - I'd like to discuss that more.
> >>>>>>      - the TPG subdev still uses .s_stream() - I need to rebase onto a tree
> >>>>>>        with working .enable_streams() for a single-source-pad subdevice.
> >>>>>>
> >>>>>> Changes in v4:
> >>>>>>
> >>>>>>      - Reworked mali_c55_update_bits() to internally perform the bit-shift
> >>>>> 
> >>>>> I really don't like that, it makes the code very confusing, even more so
> >>>>> as it differs from regmap_update_bits().
> >>>>>
> >>>>> Look at this for instance:
> >>>>>
> >>>>>      mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
> >>>>>                   MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK,
> >>>>>                   MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK);
> >>>>>
> >>>>> It only works by change because MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK is
> >>>>> BIT(0).
> >>>>>
> >>>>> Sorry, I know it will be painful, but this change needs to be reverted.
> >>>>>
> >>>>>>      - Reworked the resizer to allow cropping during streaming
> >>>>>>      - Fixed a bug in NV12 output
> >>>>>>
> >>>>>> Changes in v3:
> >>>>>>
> >>>>>>      - Mostly minor fixes suggested by Sakari
> >>>>>>      - Fixed the sequencing of vb2 buffers to be synchronised across the two
> >>>>>>        capture devices.
> >>>>>>
> >>>>>> Changes in v2:
> >>>>>>
> >>>>>>      - Clock handling
> >>>>>>      - Fixed the warnings raised by the kernel test robot
> >>>>>>
> >>>>>>    drivers/media/platform/Kconfig                |   1 +
> >>>>>>    drivers/media/platform/Makefile               |   1 +
> >>>>>>    drivers/media/platform/arm/Kconfig            |   5 +
> >>>>>>    drivers/media/platform/arm/Makefile           |   2 +
> >>>>>>    drivers/media/platform/arm/mali-c55/Kconfig   |  18 +
> >>>>>>    drivers/media/platform/arm/mali-c55/Makefile  |   9 +
> >>>>>>    .../platform/arm/mali-c55/mali-c55-capture.c  | 951 ++++++++++++++++++
> >>>>>>    .../platform/arm/mali-c55/mali-c55-common.h   | 266 +++++
> >>>>>>    .../platform/arm/mali-c55/mali-c55-core.c     | 767 ++++++++++++++
> >>>>>>    .../platform/arm/mali-c55/mali-c55-isp.c      | 611 +++++++++++
> >>>>>>    .../arm/mali-c55/mali-c55-registers.h         | 258 +++++
> >>>>>>    .../arm/mali-c55/mali-c55-resizer-coefs.h     | 382 +++++++
> >>>>>>    .../platform/arm/mali-c55/mali-c55-resizer.c  | 779 ++++++++++++++
> >>>>>>    .../platform/arm/mali-c55/mali-c55-tpg.c      | 402 ++++++++
> >>>>>>    14 files changed, 4452 insertions(+)
> >>>>>>    create mode 100644 drivers/media/platform/arm/Kconfig
> >>>>>>    create mode 100644 drivers/media/platform/arm/Makefile
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/Kconfig
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/Makefile
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-capture.c
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-common.h
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-core.c
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-resizer-coefs.h
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
> >>>>>>    create mode 100644 drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
> >>>>> 
> >>>>> I've skipped review of capture.c and resizer.c as I already have plenty
> >>>>> of comments for the other files, and it's getting late. I'll try to
> >>>>> review the rest tomorrow.
> >>>>>
> >>>>>> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> >>>>>> index 2d79bfc68c15..c929169766aa 100644
> >>>>>> --- a/drivers/media/platform/Kconfig
> >>>>>> +++ b/drivers/media/platform/Kconfig
> >>>>>> @@ -65,6 +65,7 @@ config VIDEO_MUX
> >>>>>>    source "drivers/media/platform/allegro-dvt/Kconfig"
> >>>>>>    source "drivers/media/platform/amlogic/Kconfig"
> >>>>>>    source "drivers/media/platform/amphion/Kconfig"
> >>>>>> +source "drivers/media/platform/arm/Kconfig"
> >>>>>>    source "drivers/media/platform/aspeed/Kconfig"
> >>>>>>    source "drivers/media/platform/atmel/Kconfig"
> >>>>>>    source "drivers/media/platform/broadcom/Kconfig"
> >>>>>> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> >>>>>> index da17301f7439..9a647abd5218 100644
> >>>>>> --- a/drivers/media/platform/Makefile
> >>>>>> +++ b/drivers/media/platform/Makefile
> >>>>>> @@ -8,6 +8,7 @@
> >>>>>>    obj-y += allegro-dvt/
> >>>>>>    obj-y += amlogic/
> >>>>>>    obj-y += amphion/
> >>>>>> +obj-y += arm/
> >>>>>>    obj-y += aspeed/
> >>>>>>    obj-y += atmel/
> >>>>>>    obj-y += broadcom/
> >>>>>> diff --git a/drivers/media/platform/arm/Kconfig b/drivers/media/platform/arm/Kconfig
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..4f0764c329c7
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/Kconfig
> >>>>>> @@ -0,0 +1,5 @@
> >>>>>> +# SPDX-License-Identifier: GPL-2.0-only
> >>>>>> +
> >>>>>> +comment "ARM media platform drivers"
> >>>>>> +
> >>>>>> +source "drivers/media/platform/arm/mali-c55/Kconfig"
> >>>>>> diff --git a/drivers/media/platform/arm/Makefile b/drivers/media/platform/arm/Makefile
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..8cc4918725ef
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/Makefile
> >>>>>> @@ -0,0 +1,2 @@
> >>>>>> +# SPDX-License-Identifier: GPL-2.0-only
> >>>>>> +obj-y += mali-c55/
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/Kconfig
> >>>>>> b/drivers/media/platform/arm/mali-c55/Kconfig
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..602085e28b01
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/Kconfig
> >>>>>> @@ -0,0 +1,18 @@
> >>>>>> +# SPDX-License-Identifier: GPL-2.0-only
> >>>>>> +config VIDEO_MALI_C55
> >>>>>> +    tristate "ARM Mali-C55 Image Signal Processor driver"
> >>>>>> +    depends on V4L_PLATFORM_DRIVERS
> >>>>>> +    depends on VIDEO_DEV && OF
> >>>>>> +    depends on ARCH_VEXPRESS || COMPILE_TEST
> >>>>>> +    select MEDIA_CONTROLLER
> >>>>>> +    select VIDEO_V4L2_SUBDEV_API
> >>>>>> +    select VIDEOBUF2_DMA_CONTIG
> >>>>>> +    select VIDEOBUF2_VMALLOC
> >>>>>> +    select V4L2_FWNODE
> >>>>>> +    select GENERIC_PHY_MIPI_DPHY
> >>>>> 
> >>>>> Alphabetical order ?
> >>>>>
> >>>>>> +    default n
> >>>>> 
> >>>>> That's the default, you don't have to specify ti.
> >>>>>
> >>>>>> +    help
> >>>>>> +      Enable this to support Arm's Mali-C55 Image Signal Processor.
> >>>>>> +
> >>>>>> +      To compile this driver as a module, choose M here: the module
> >>>>>> +      will be called mali-c55.
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/Makefile
> >>>>>> b/drivers/media/platform/arm/mali-c55/Makefile
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..77dcb2fbf0f4
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/Makefile
> >>>>>> @@ -0,0 +1,9 @@
> >>>>>> +# SPDX-License-Identifier: GPL-2.0
> >>>>>> +
> >>>>>> +mali-c55-y := mali-c55-capture.o \
> >>>>>> +          mali-c55-core.o \
> >>>>>> +          mali-c55-isp.o \
> >>>>>> +          mali-c55-tpg.o \
> >>>>>> +          mali-c55-resizer.o
> >>>>> 
> >>>>> Alphabetical order here too.
> >>>>>
> >>>>>> +
> >>>>>> +obj-$(CONFIG_VIDEO_MALI_C55) += mali-c55.o
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-capture.c
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-capture.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..1d539ac9c498
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-capture.c
> >>>>>> @@ -0,0 +1,951 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Video capture devices
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/cleanup.h>
> >>>>>> +#include <linux/minmax.h>
> >>>>>> +#include <linux/pm_runtime.h>
> >>>>>> +#include <linux/string.h>
> >>>>>> +#include <linux/videodev2.h>
> >>>>>> +
> >>>>>> +#include <media/v4l2-dev.h>
> >>>>>> +#include <media/v4l2-event.h>
> >>>>>> +#include <media/v4l2-ioctl.h>
> >>>>>> +#include <media/v4l2-subdev.h>
> >>>>>> +#include <media/videobuf2-core.h>
> >>>>>> +#include <media/videobuf2-dma-contig.h>
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +#include "mali-c55-registers.h"
> >>>>>> +
> >>>>>> +static const struct mali_c55_fmt mali_c55_fmts[] = {
> >>>>>> +    /*
> >>>>>> +     * This table is missing some entries which need further work or
> >>>>>> +     * investigation:
> >>>>>> +     *
> >>>>>> +     * Base mode 1 is a backwards V4L2_PIX_FMT_XRGB32 with no V4L2 equivalent
> >>>>>> +     * Base mode 5 is "Generic Data"
> >>>>>> +     * Base mode 8 is a backwards V4L2_PIX_FMT_XYUV32 - no V4L2 equivalent
> >>>>>> +     * Base mode 9 seems to have no V4L2 equivalent
> >>>>>> +     * Base mode 17, 19 and 20 describe formats which seem to have no V4L2
> >>>>>> +     * equivalent
> >>>>>> +     */
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_ARGB2101010,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_RGB121212_1X36,
> >>>>>> +            MEDIA_BUS_FMT_RGB202020_1X60,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_A2R10G10B10,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_RGB565,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_RGB121212_1X36,
> >>>>>> +            MEDIA_BUS_FMT_RGB202020_1X60,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RGB565,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_BGR24,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_RGB121212_1X36,
> >>>>>> +            MEDIA_BUS_FMT_RGB202020_1X60,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RGB24,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_YUYV,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_YUY2,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_UYVY,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_UYVY,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_Y210,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_Y210,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    /*
> >>>>>> +     * This is something of a hack, the ISP thinks it's running NV12M but
> >>>>>> +     * by setting uv_plane = 0 we simply discard that planes and only output
> >>>>>> +     * the Y-plane.
> >>>>>> +     */
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_GREY,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_NV12_21,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_NV12M,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_NV12_21,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT1
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_NV21M,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_YUV10_1X30,
> >>>>>> +        },
> >>>>>> +        .is_raw = false,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_NV12_21,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT2
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    /*
> >>>>>> +     * RAW uncompressed formats are all packed in 16 bpp.
> >>>>>> +     * TODO: Expand this list to encompass all possible RAW formats.
> >>>>>> +     */
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_SRGGB16,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_SRGGB16_1X16,
> >>>>>> +        },
> >>>>>> +        .is_raw = true,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RAW16,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_SBGGR16,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_SBGGR16_1X16,
> >>>>>> +        },
> >>>>>> +        .is_raw = true,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RAW16,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_SGBRG16,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_SGBRG16_1X16,
> >>>>>> +        },
> >>>>>> +        .is_raw = true,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RAW16,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .fourcc = V4L2_PIX_FMT_SGRBG16,
> >>>>>> +        .mbus_codes = {
> >>>>>> +            MEDIA_BUS_FMT_SGRBG16_1X16,
> >>>>>> +        },
> >>>>>> +        .is_raw = true,
> >>>>>> +        .registers = {
> >>>>>> +            .base_mode = MALI_C55_OUTPUT_RAW16,
> >>>>>> +            .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0
> >>>>>> +        }
> >>>>>> +    },
> >>>>>> +};
> >>>>>> +
> >>>>>> +static bool mali_c55_mbus_code_can_produce_fmt(const struct mali_c55_fmt *fmt,
> >>>>>> +                           u32 code)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(fmt->mbus_codes); i++) {
> >>>>>> +        if (fmt->mbus_codes[i] == code)
> >>>>>> +            return true;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return false;
> >>>>>> +}
> >>>>>> +
> >>>>>> +bool mali_c55_format_is_raw(unsigned int mbus_code)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_fmts); i++) {
> >>>>>> +        if (mali_c55_fmts[i].mbus_codes[0] == mbus_code)
> >>>>>> +            return mali_c55_fmts[i].is_raw;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return false;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct mali_c55_fmt *mali_c55_format_from_pix(const u32 pixelformat)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_fmts); i++) {
> >>>>>> +        if (mali_c55_fmts[i].fourcc == pixelformat)
> >>>>>> +            return &mali_c55_fmts[i];
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * If we find no matching pixelformat, we'll just default to the first
> >>>>>> +     * one for now.
> >>>>>> +     */
> >>>>>> +
> >>>>>> +    return &mali_c55_fmts[0];
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const char * const capture_device_names[] = {
> >>>>>> +    "mali-c55 fr",
> >>>>>> +    "mali-c55 ds",
> >>>>>> +    "mali-c55 3a stats",
> >>>>>> +    "mali-c55 params",
> >>>> 
> >>>> The last two entries are not used AFAICT, neither here, nor in
> >>>> subsequent patches.
> >>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const char *mali_c55_cap_dev_to_name(struct mali_c55_cap_dev *cap)
> >>>>>> +{
> >>>>>> +    if (cap->reg_offset == MALI_C55_CAP_DEV_FR_REG_OFFSET)
> >>>>>> +        return capture_device_names[0];
> >>>>>> +
> >>>>>> +    if (cap->reg_offset == MALI_C55_CAP_DEV_DS_REG_OFFSET)
> >>>>>> +        return capture_device_names[1];
> >>>>>> +
> >>>>>> +    return "params/stat not supported yet";
> >>>>>> +}
> >>>> 
> >>>> Use cap_dev->vdev.name instead of mali_c55_cap_dev_to_name(cap_dev) and
> >>>> drop this function.
> >>>>
> >>>>>> +
> >>>>>> +static int mali_c55_link_validate(struct media_link *link)
> >>>>>> +{
> >>>>>> +    struct video_device *vdev =
> >>>>>> + media_entity_to_video_device(link->sink->entity);
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = video_get_drvdata(vdev);
> >>>>>> +    struct v4l2_subdev *sd =
> >>>>>> + media_entity_to_v4l2_subdev(link->source->entity);
> >>>>>> +    const struct v4l2_pix_format_mplane *pix_mp;
> >>>>>> +    const struct mali_c55_fmt *cap_fmt;
> >>>>>> +    struct v4l2_subdev_format sd_fmt = {
> >>>>>> +        .which = V4L2_SUBDEV_FORMAT_ACTIVE,
> >>>>>> +        .pad = link->source->index,
> >>>>>> +    };
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &sd_fmt);
> >>>>>> +    if (ret)
> >>>>>> +        return ret;
> >>>>>> +
> >>>>>> +    pix_mp = &cap_dev->mode.pix_mp;
> >>>>>> +    cap_fmt = cap_dev->mode.capture_fmt;
> >>>>>> +
> >>>>>> +    if (sd_fmt.format.width != pix_mp->width ||
> >>>>>> +        sd_fmt.format.height != pix_mp->height) {
> >>>>>> +        dev_dbg(cap_dev->mali_c55->dev,
> >>>>>> +            "link '%s':%u -> '%s':%u not valid: %ux%u != %ux%u\n",
> >>>>>> +            link->source->entity->name, link->source->index,
> >>>>>> +            link->sink->entity->name, link->sink->index,
> >>>>>> +            sd_fmt.format.width, sd_fmt.format.height,
> >>>>>> +            pix_mp->width, pix_mp->height);
> >>>>>> +        return -EPIPE;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (!mali_c55_mbus_code_can_produce_fmt(cap_fmt, sd_fmt.format.code)) {
> >>>>>> +        dev_dbg(cap_dev->mali_c55->dev,
> >>>>>> +            "link '%s':%u -> '%s':%u not valid: mbus_code 0x%04x cannot produce pixel format
> >>>>>> %p4cc\n",
> >>>>>> +            link->source->entity->name, link->source->index,
> >>>>>> +            link->sink->entity->name, link->sink->index,
> >>>>>> +            sd_fmt.format.code, &pix_mp->pixelformat);
> >>>>>> +        return -EPIPE;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct media_entity_operations mali_c55_media_ops = {
> >>>>>> +    .link_validate = mali_c55_link_validate,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_vb2_queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
> >>>>>> +                    unsigned int *num_planes, unsigned int sizes[],
> >>>>>> +                    struct device *alloc_devs[])
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = q->drv_priv;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    if (*num_planes) {
> >>>>>> +        if (*num_planes != cap_dev->mode.pix_mp.num_planes)
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        for (i = 0; i < cap_dev->mode.pix_mp.num_planes; i++)
> >>>>>> +            if (sizes[i] < cap_dev->mode.pix_mp.plane_fmt[i].sizeimage)
> >>>>>> +                return -EINVAL;
> >>>>>> +    } else {
> >>>>>> +        *num_planes = cap_dev->mode.pix_mp.num_planes;
> >>>>>> +        for (i = 0; i < cap_dev->mode.pix_mp.num_planes; i++)
> >>>>>> +            sizes[i] = cap_dev->mode.pix_mp.plane_fmt[i].sizeimage;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_buf_queue(struct vb2_buffer *vb)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = vb2_get_drv_priv(vb->vb2_queue);
> >>>>>> +    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> >>>>>> +    struct mali_c55_buffer *buf = container_of(vbuf,
> >>>>>> +                           struct mali_c55_buffer, vb);
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    buf->plane_done[MALI_C55_PLANE_Y] = false;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * If we're in a single-plane format we flag the other plane as done
> >>>>>> +     * already so it's dequeued appropriately later
> >>>>>> +     */
> >>>>>> +    buf->plane_done[MALI_C55_PLANE_UV] = cap_dev->mode.pix_mp.num_planes <= 1;
> >>>>>> +
> >>>>>> +    for (i = 0; i < cap_dev->mode.pix_mp.num_planes; i++) {
> >>>>>> +        unsigned long size = cap_dev->mode.pix_mp.plane_fmt[i].sizeimage;
> >>>>>> +
> >>>>>> +        vb2_set_plane_payload(vb, i, size);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    spin_lock(&cap_dev->buffers.lock);
> >>>>>> +    list_add_tail(&buf->queue, &cap_dev->buffers.queue);
> >>>>>> +    spin_unlock(&cap_dev->buffers.lock);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_buf_init(struct vb2_buffer *vb)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = vb2_get_drv_priv(vb->vb2_queue);
> >>>>>> +    struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> >>>>>> +    struct mali_c55_buffer *buf = container_of(vbuf,
> >>>>>> +                           struct mali_c55_buffer, vb);
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < cap_dev->mode.pix_mp.num_planes; i++)
> >>>>>> +        buf->addrs[i] = vb2_dma_contig_plane_dma_addr(vb, i);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_set_next_buffer(struct mali_c55_cap_dev *cap_dev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +
> >>>>>> +    guard(spinlock)(&cap_dev->buffers.lock);
> >>>>>> +
> >>>>>> +    cap_dev->buffers.curr = cap_dev->buffers.next;
> >>>>>> +    cap_dev->buffers.next = NULL;
> >>>>>> +
> >>>>>> +    if (!list_empty(&cap_dev->buffers.queue)) {
> >>>>>> +        struct v4l2_pix_format_mplane *pix_mp;
> >>>>>> +        const struct v4l2_format_info *info;
> >>>>>> +        u32 *addrs;
> >>>>>> +
> >>>>>> +        pix_mp = &cap_dev->mode.pix_mp;
> >>>>>> +        info = v4l2_format_info(pix_mp->pixelformat);
> >>>>>> +
> >>>>>> +        mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_WRITER_FRAME_WRITE_MASK, 0x01);
> >>>>>> +        if (cap_dev->mode.capture_fmt->registers.uv_plane)
> >>>>>> +            mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_WRITER_FRAME_WRITE_MASK, 0x01);
> >>>>>> +
> >>>>>> +        cap_dev->buffers.next = list_first_entry(&cap_dev->buffers.queue,
> >>>>>> +                             struct mali_c55_buffer,
> >>>>>> +                             queue);
> >>>>>> +        list_del(&cap_dev->buffers.next->queue);
> >>>>>> +
> >>>>>> +        addrs = cap_dev->buffers.next->addrs;
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_BANKS_BASE(cap_dev->reg_offset),
> >>>>>> +            addrs[MALI_C55_PLANE_Y]);
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_BANKS_BASE(cap_dev->reg_offset),
> >>>>>> +            addrs[MALI_C55_PLANE_UV]);
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_OFFSET(cap_dev->reg_offset),
> >>>>>> +            pix_mp->width * info->bpp[MALI_C55_PLANE_Y]);
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_OFFSET(cap_dev->reg_offset),
> >>>>>> +            pix_mp->width * info->bpp[MALI_C55_PLANE_UV]
> >>>>>> +            / info->hdiv);
> >>>>>> +    } else {
> >>>>>> +        /*
> >>>>>> +         * If we underflow then we can tell the ISP that we don't want
> >>>>>> +         * to write out the next frame.
> >>>>>> +         */
> >>>>>> +        mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00);
> >>>>>> +        mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00);
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_handle_buffer(struct mali_c55_buffer *curr_buf,
> >>>>>> +                   unsigned int framecount)
> >>>>>> +{
> >>>>>> +    curr_buf->vb.vb2_buf.timestamp = ktime_get_boottime_ns();
> >>>>>> +    curr_buf->vb.field = V4L2_FIELD_NONE;
> >>>> 
> >>>> The could be set already when the buffer is queued.
> >>>>
> >>>>>> +    curr_buf->vb.sequence = framecount;
> >>>>>> +    vb2_buffer_done(&curr_buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
> >>>>>> +}
> >>>>>> +
> >>>>>> +/**
> >>>>>> + * mali_c55_set_plane_done - mark the plane as written and process the buffer if
> >>>>>> + *                 both planes are finished.
> >>>>>> + * @cap_dev:  pointer to the fr or ds pipe output
> >>>>>> + * @plane:    the plane to mark as completed
> >>>>>> + *
> >>>>>> + * The Mali C55 ISP has muliplanar outputs for some formats that come with two
> >>>>>> + * separate "buffer write completed" interrupts - we need to flag each plane's
> >>>>>> + * completion and check whether both planes are done - if so, complete the buf
> >>>>>> + * in vb2.
> >>>>>> + */
> >>>>>> +void mali_c55_set_plane_done(struct mali_c55_cap_dev *cap_dev,
> >>>>>> +                 enum mali_c55_planes plane)
> >>>>>> +{
> >>>>>> +    struct mali_c55_isp *isp = &cap_dev->mali_c55->isp;
> >>>>>> +    struct mali_c55_buffer *curr_buf;
> >>>>>> +
> >>>>>> +    guard(spinlock)(&cap_dev->buffers.lock);
> >>>>>> +    curr_buf = cap_dev->buffers.curr;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * This _should_ never happen. If no buffer was available from vb2 then
> >>>>>> +     * we tell the ISP not to bother writing the next frame, which means the
> >>>>>> +     * interrupts that call this function should never trigger. If it does
> >>>>>> +     * happen then one of our assumptions is horribly wrong - complain
> >>>>>> +     * loudly and do nothing.
> >>>>>> +     */
> >>>>>> +    if (!curr_buf) {
> >>>>>> +        dev_err(cap_dev->mali_c55->dev, "%s null buffer in %s()\n",
> >>>>>> +            mali_c55_cap_dev_to_name(cap_dev), __func__);
> >>>>>> +        return;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* If the other plane is also done... */
> >>>>>> +    if (curr_buf->plane_done[~plane & 1]) {
> >>>>>> +        mali_c55_handle_buffer(curr_buf, isp->frame_sequence);
> >>>>>> +        cap_dev->buffers.curr = NULL;
> >>>>>> +        isp->frame_sequence++;
> >>>>>> +    } else {
> >>>>>> +        curr_buf->plane_done[plane] = true;
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_cap_dev_stream_disable(struct mali_c55_cap_dev *cap_dev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                 MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00);
> >>>>>> +    mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                 MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_cap_dev_stream_enable(struct mali_c55_cap_dev *cap_dev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The Mali ISP can hold up to 5 buffer addresses and simply cycle
> >>>>>> +     * through them, but it's not clear to me that the vb2 queue _guarantees_
> >>>>>> +     * it will queue buffers to the driver in a fixed order, and ensuring
> >>>>>> +     * we call vb2_buffer_done() for the right buffer seems to me to add
> >>>>>> +     * pointless complexity given in multi-context mode we'd need to
> >>>>>> +     * re-write those registers every frame anyway...so we tell the ISP to
> >>>>>> +     * use a single register and update it for each frame.
> >>>>>> +     */
> >>>> 
> >>>> A single register sounds prone to error conditions. Is it at least
> >>>> shadowed in the hardware, or do you have to make sure you reprogram it
> >>>> during the vertical blanking only ?
> >>> 
> >>> It would have to be reprogrammed during the vertical blanking if we were running in a
> >>> configuration with a single config space, otherwise you have the time it takes to process a frame
> >>> plus vertical blanking. As I say, it'll have to work like this in multi-context mode anyway.
> >>>
> >>> If we want to use the cycling...is it guaranteed that vb2 buffers will always be queued in order?
> > 
> > In which order ?
> >
> >>>> I'll mostly skip buffer handling in this review, I need to first
> >>>> understand how the hardware operates to make an informed opinion.
> >>>>
> >>>>>> +    mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_BANKS_CONFIG(cap_dev->reg_offset),
> >>>>>> +            MALI_C55_REG_Y_WRITER_MAX_BANKS_MASK, 0);
> >>>>>> +    mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_BANKS_CONFIG(cap_dev->reg_offset),
> >>>>>> +            MALI_C55_REG_UV_WRITER_MAX_BANKS_MASK, 0);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We only queue a buffer in the streamon path if this is the first of
> >>>>>> +     * the capture devices to start streaming. If the ISP is already running
> >>>>>> +     * then we rely on the ISP_START interrupt to queue the first buffer for
> >>>>>> +     * this capture device.
> >>>>>> +     */
> >>>>>> +    if (mali_c55->pipe.start_count == 1)
> >>>>>> +        mali_c55_set_next_buffer(cap_dev);
> >>>> 
> >>>> I think we'll have to revisit buffer handling to make sure it's 100%
> >>>> race-free.
> >>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_cap_dev_return_buffers(struct mali_c55_cap_dev *cap_dev,
> >>>>>> +                        enum vb2_buffer_state state)
> >>>>>> +{
> >>>>>> +    struct mali_c55_buffer *buf, *tmp;
> >>>>>> +
> >>>>>> +    guard(spinlock)(&cap_dev->buffers.lock);
> >>>>>> +
> >>>>>> +    if (cap_dev->buffers.curr) {
> >>>>>> + vb2_buffer_done(&cap_dev->buffers.curr->vb.vb2_buf,
> >>>>>> +                state);
> >>>>>> +        cap_dev->buffers.curr = NULL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (cap_dev->buffers.next) {
> >>>>>> + vb2_buffer_done(&cap_dev->buffers.next->vb.vb2_buf,
> >>>>>> +                state);
> >>>>>> +        cap_dev->buffers.next = NULL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    list_for_each_entry_safe(buf, tmp, &cap_dev->buffers.queue, queue) {
> >>>>>> +        list_del(&buf->queue);
> >>>>>> +        vb2_buffer_done(&buf->vb.vb2_buf, state);
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = q->drv_priv;
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +    struct mali_c55_resizer *rzr = cap_dev->rzr;
> >>>>>> +    struct mali_c55_isp *isp = &mali_c55->isp;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    guard(mutex)(&isp->lock);
> >>>> 
> >>>> What's the reason for using the isp lock here and in
> >>>> mali_c55_vb2_stop_streaming() ? If you need a lock that covers all video
> >>>> nodes in order to synchronize start/stop, you may want to use the
> >>>> graph_mutex of the media device instead.
> >>> 
> >>> It's because I wanted to make sure that the ISP was in a known started/stopped state before
> >>> possibly trying to start/stop it, which can be done from either of the two capture devices. This
> >>> would go away if we were synchronising with the links anyway.
> > 
> > OK.
> >
> >>>>>> +
> >>>>>> +    ret = pm_runtime_resume_and_get(mali_c55->dev);
> >>>>>> +    if (ret)
> >>>>>> +        return ret;
> >>>>>> +
> >>>>>> +    ret = video_device_pipeline_start(&cap_dev->vdev,
> >>>>>> +                      &cap_dev->mali_c55->pipe);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "%s failed to start media pipeline\n",
> >>>>>> +            mali_c55_cap_dev_to_name(cap_dev));
> >>>> 
> >>>> Drop the message or make it dev_dbg() as it can be triggered by
> >>>> userspace.
> >>>>
> >>>>>> +        goto err_pm_put;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    mali_c55_cap_dev_stream_enable(cap_dev);
> >>>>>> +    mali_c55_rzr_start_stream(rzr);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We only start the ISP if we're the only capture device that's
> >>>>>> +     * streaming. Otherwise, it'll already be active.
> >>>>>> +     */
> >>>> 
> >>>> I still think we should use link setup to indicate which video devices
> >>>> userspace plans to use, and then only start when they're all started.
> >>>> That includes stats and parameters buffers. We can continue this
> >>>> discussion in the context of the previous version of the patch series,
> >>>> or here, up to you.
> >>> 
> >>> Let's just continue here. I think I called it "clunky" before; from my perspective it's an
> >>> unnecessary extra step - we can already signal to the driver that we don't want to use the video
> >>> devices by not queuing buffers to them or starting the stream on them and although I understand
> > 
> > By not starting streaming, perhaps, but by not queuing buffers, no. The
> > reason is that there's no synchronization between buffer queues. If you
> > queue
> >
> > Frame	FR	DS
> > --------------------
> > 1	x	x
> > 2	x
> > 3	x	x
> > 4	x	x
> >
> > it will not be distinguishable by the driver from
> >
> > Frame	FR	DS
> > --------------------
> > 1	x	x
> > 2	x	x
> > 3	x	x
> > 4	x
> >
> >>> that that means that one of the two image data capture devices will receive data before the other,
> >>> I don't understand why that's considered to be a problem. Possibly that last part is the stickler;
> >>> can you explain a bit why it's an issue for one capture queue to start earlier than the other?
> > 
> > Because from a userspace point of view, if you want to capture frames
> > from both pipelines, you will expect to receive a buffer from each
> > pipeline for every frame. If that's not guaranteed at stream start, you
> > will then need to implement synchronization code that will drop buffers
> > on one pipeline until you get the first buffer on the other pipeline
> > (assuming you can synchronize them by sequence number). That will be
> > more work, and can introduce latency.
> >
> >>>>>> +    if (mali_c55->pipe.start_count == 1) {
> >>>>>> +        ret = mali_c55_isp_start_stream(isp);
> >>>>>> +        if (ret)
> >>>>>> +            goto err_disable_cap_dev;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_disable_cap_dev:
> >>>>>> +    mali_c55_cap_dev_stream_disable(cap_dev);
> >>>>>> +    video_device_pipeline_stop(&cap_dev->vdev);
> >>>>>> +err_pm_put:
> >>>>>> +    pm_runtime_put(mali_c55->dev);
> >>>>>> +    mali_c55_cap_dev_return_buffers(cap_dev, VB2_BUF_STATE_QUEUED);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_vb2_stop_streaming(struct vb2_queue *q)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = q->drv_priv;
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +    struct mali_c55_resizer *rzr = cap_dev->rzr;
> >>>>>> +    struct mali_c55_isp *isp = &mali_c55->isp;
> >>>>>> +
> >>>>>> +    guard(mutex)(&isp->lock);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * If one of the other capture nodes is streaming, we shouldn't
> >>>>>> +     * disable the ISP here.
> >>>>>> +     */
> >>>>>> +    if (mali_c55->pipe.start_count == 1)
> >>>>>> +        mali_c55_isp_stop_stream(&mali_c55->isp);
> >>>>>> +
> >>>>>> +    mali_c55_rzr_stop_stream(rzr);
> >>>>>> +    mali_c55_cap_dev_stream_disable(cap_dev);
> >>>>>> +    mali_c55_cap_dev_return_buffers(cap_dev, VB2_BUF_STATE_ERROR);
> >>>>>> +    video_device_pipeline_stop(&cap_dev->vdev);
> >>>>>> +    pm_runtime_put(mali_c55->dev);
> >>>> 
> >>>> I think runtime PM autosuspend would be very useful, as it will ensure
> >>>> that stop-reconfigure-start cycles get handled as efficiently as
> >>>> possible without powering the device down. It could be done on top as a
> >>>> separate patch.
> >>> 
> >>> Alright
> >>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct vb2_ops mali_c55_vb2_ops = {
> >>>>>> +    .queue_setup        = &mali_c55_vb2_queue_setup,
> >>>>>> +    .buf_queue        = &mali_c55_buf_queue,
> >>>>>> +    .buf_init        = &mali_c55_buf_init,
> >>>>>> +    .wait_prepare        = vb2_ops_wait_prepare,
> >>>>>> +    .wait_finish        = vb2_ops_wait_finish,
> >>>>>> +    .start_streaming    = &mali_c55_vb2_start_streaming,
> >>>>>> +    .stop_streaming        = &mali_c55_vb2_stop_streaming,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct v4l2_file_operations mali_c55_v4l2_fops = {
> >>>>>> +    .owner = THIS_MODULE,
> >>>>>> +    .unlocked_ioctl = video_ioctl2,
> >>>>>> +    .open = v4l2_fh_open,
> >>>>>> +    .release = vb2_fop_release,
> >>>>>> +    .poll = vb2_fop_poll,
> >>>>>> +    .mmap = vb2_fop_mmap,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static void mali_c55_try_fmt(struct v4l2_pix_format_mplane *pix_mp)
> >>>>>> +{
> >>>>>> +    const struct mali_c55_fmt *capture_format;
> >>>>>> +    const struct v4l2_format_info *info;
> >>>>>> +    struct v4l2_plane_pix_format *plane;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    capture_format = mali_c55_format_from_pix(pix_mp->pixelformat);
> >>>>>> +    pix_mp->pixelformat = capture_format->fourcc;
> >>>>>> +
> >>>>>> +    pix_mp->width = clamp(pix_mp->width, MALI_C55_MIN_WIDTH,
> >>>>>> +                  MALI_C55_MAX_WIDTH);
> >>>>>> +    pix_mp->height = clamp(pix_mp->height, MALI_C55_MIN_HEIGHT,
> >>>>>> +                   MALI_C55_MAX_HEIGHT);
> >>>> 
> >>>> Ah, these clamps are right :-)
> >>> 
> >>> Hurrah!
> >>>
> >>>>>> +
> >>>>>> +    pix_mp->field = V4L2_FIELD_NONE;
> >>>>>> +    pix_mp->colorspace = V4L2_COLORSPACE_DEFAULT;
> >>>>>> +    pix_mp->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> >>>>>> +    pix_mp->quantization = V4L2_QUANTIZATION_DEFAULT;
> >>>>>> +
> >>>>>> +    info = v4l2_format_info(pix_mp->pixelformat);
> >>>> 
> >>>> This function may return NULL. That shouldn't be the case as long as it
> >>>> supports all formats that the C55 driver supports, so I suppose it's
> >>>> safe.
> >>>>
> >>>>>> +    pix_mp->num_planes = info->mem_planes;
> >>>>>> +    memset(pix_mp->plane_fmt, 0, sizeof(pix_mp->plane_fmt));
> >>>>>> +
> >>>>>> +    pix_mp->plane_fmt[0].bytesperline = info->bpp[0] * pix_mp->width;
> >>>> 
> >>>> Does the hardware support configurable line strides ? If so we should
> >>>> support it.
> >>> 
> >>> You have to set the line stride in the DMA writer registers, which we do using this same
> >>> value...might userspace have set bytesperline already then or something? Or is there some other
> >>> place it could be configured?
> > 
> > Userspace can request a specific stride by setting bytesperline, yes. If
> > that's set, you should honour it (and of course adjust it to a
> > reasonable [min, max] range as well as align it based on hardware
> > constraints).
> >
> >>>>>> +    pix_mp->plane_fmt[0].sizeimage = info->bpp[0] * pix_mp->width
> >>>>>> +                       * pix_mp->height;
> >>>> 
> >>>>      pix_mp->plane_fmt[0].sizeimage = pix_mp->plane_fmt[0].bytesperline
> >>>>                         * pix_mp->height;
> >>>>
> >>>>>> +
> >>>>>> +    for (i = 1; i < info->comp_planes; i++) {
> >>>>>> +        plane = &pix_mp->plane_fmt[i];
> >>>>>> +
> >>>>>> +        plane->bytesperline = DIV_ROUND_UP(info->bpp[i] * pix_mp->width,
> >>>>>> +                           info->hdiv);
> >>>>>> +        plane->sizeimage = DIV_ROUND_UP(
> >>>>>> +                    plane->bytesperline * pix_mp->height,
> >>>>>> +                    info->vdiv);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (info->mem_planes == 1) {
> >>>>>> +        for (i = 1; i < info->comp_planes; i++) {
> >>>>>> +            plane = &pix_mp->plane_fmt[i];
> >>>>>> +            pix_mp->plane_fmt[0].sizeimage += plane->sizeimage;
> >>>>>> +        }
> >>>>>> +    }
> >>>> 
> >>>> I'm wondering, could v4l2_fill_pixfmt_mp() help ? It doesn't support
> >>>> configurable strides though :-S Maybe the helper could be improved, if
> >>>> it's close enough to what you need ?
> >>> 
> >>> I'll take a look
> >>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_try_fmt_vid_cap_mplane(struct file *file, void *fh,
> >>>>>> +                       struct v4l2_format *f)
> >>>>>> +{
> >>>>>> +    mali_c55_try_fmt(&f->fmt.pix_mp);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_set_format(struct mali_c55_cap_dev *cap_dev,
> >>>>>> +                struct v4l2_pix_format_mplane *pix_mp)
> >>>>>> +{
> >>>>>> +    const struct mali_c55_fmt *capture_format;
> >>>>>> +    struct mali_c55 *mali_c55 = cap_dev->mali_c55;
> >>>>>> +    const struct v4l2_format_info *info;
> >>>>>> +
> >>>>>> +    mali_c55_try_fmt(pix_mp);
> >>>>>> +    capture_format = mali_c55_format_from_pix(pix_mp->pixelformat);
> >>>>>> +    info = v4l2_format_info(pix_mp->pixelformat);
> >>>>>> +    if (WARN_ON(!info))
> >>>>>> +        return;
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_Y_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +               capture_format->registers.base_mode);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_Y_SIZE(cap_dev->reg_offset),
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_SIZE_W(pix_mp->width) |
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_SIZE_H(pix_mp->height));
> >>>> 
> >>>> Could the register writes be moved to stream start time ?
> >> 
> >> Sorry missed this one. These are writes to the context's registers
> >> buffer, not to the hardware. Does it matter that they're not done at
> >> stream on time?
> > 
> > Writing them here means you'll have to call pm_runtime_resume_and_get()
> > here. If power is then cut off, registers may or may not lose their
> > contents, so you would need to write them at stream on time anyway. I
> > think it's best to move all the hardware configuration at stream on
> > time.
> 
> They won't be lost, because they're not written to the hardware here, only to the registers buffer 
> we allocate in mali_c55_init_context(). They're automatically written to the hardware at stream on 
> time when the config is DMAd over, so unless I'm missing something this is safe as an 
> operation...though possibly the confusion makes them worth moving anyway.

Aahhhh I thought this was writing to the hardware. We really need
separate functions for hardware write and context writes.

Still, I think configuring the hardware at stream on time is the best
option. It will simplify the format operations and make the code
clearer.

> >>>>>> +
> >>>>>> +    if (info->mem_planes > 1) {
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                   capture_format->registers.base_mode);
> >>>>>> +        mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_UV_WRITER_MODE(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_WRITER_SUBMODE_MASK,
> >>>>>> +                capture_format->registers.uv_plane);
> >>>>>> +
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_UV_SIZE(cap_dev->reg_offset),
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_SIZE_W(pix_mp->width) |
> >>>>>> + MALI_C55_REG_ACTIVE_OUT_SIZE_H(pix_mp->height));
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (info->pixel_enc == V4L2_PIXEL_ENC_YUV) {
> >>>>>> +        /*
> >>>>>> +         * TODO: Figure out the colour matrix coefficients and calculate
> >>>>>> +         * and write them here.
> >>>>>> +         */
> >>>> 
> >>>> Ideally they should also be exposed directly to userspace as ISP
> >>>> parameters. I would probably go as far as saying that they should come
> >>>> directly from userspace, and not derived from the colorspace fields.
> >>> 
> >>> Yes I think I agree, I'll drop the todo from here.
> >>>
> >>>>>> +
> >>>>>> +        mali_c55_write(mali_c55,
> >>>>>> + MALI_C55_REG_CS_CONV_CONFIG(cap_dev->reg_offset),
> >>>>>> +                   MALI_C55_CS_CONV_MATRIX_MASK);
> >>>>>> +
> >>>>>> +        if (info->hdiv > 1)
> >>>>>> +            mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_CS_CONV_CONFIG(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_CS_CONV_HORZ_DOWNSAMPLE_MASK, 0x01);
> >>>>>> +        if (info->vdiv > 1)
> >>>>>> +            mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_CS_CONV_CONFIG(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_CS_CONV_VERT_DOWNSAMPLE_MASK, 0x01);
> >>>>>> +        if (info->hdiv > 1 || info->vdiv > 1)
> >>>>>> +            mali_c55_update_bits(mali_c55,
> >>>>>> + MALI_C55_REG_CS_CONV_CONFIG(cap_dev->reg_offset),
> >>>>>> +                MALI_C55_CS_CONV_FILTER_MASK, 0x01);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    cap_dev->mode.pix_mp = *pix_mp;
> >>>>>> +    cap_dev->mode.capture_fmt = capture_format;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_s_fmt_vid_cap_mplane(struct file *file, void *fh,
> >>>>>> +                     struct v4l2_format *f)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = video_drvdata(file);
> >>>>>> +
> >>>>>> +    if (vb2_is_busy(&cap_dev->queue))
> >>>>>> +        return -EBUSY;
> >>>>>> +
> >>>>>> +    mali_c55_set_format(cap_dev, &f->fmt.pix_mp);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_g_fmt_vid_cap_mplane(struct file *file, void *fh,
> >>>>>> +                     struct v4l2_format *f)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = video_drvdata(file);
> >>>>>> +
> >>>>>> +    f->fmt.pix_mp = cap_dev->mode.pix_mp;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_enum_fmt_vid_cap_mplane(struct file *file, void *fh,
> >>>>>> +                        struct v4l2_fmtdesc *f)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev = video_drvdata(file);
> >>>>>> +    unsigned int j = 0;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_fmts); i++) {
> >>>>>> +        if (f->mbus_code &&
> >>>>>> + !mali_c55_mbus_code_can_produce_fmt(&mali_c55_fmts[i],
> >>>>>> +                               f->mbus_code))
> >>>> 
> >>>> Small indentation mistake.
> >>>>
> >>>>>> +            continue;
> >>>>>> +
> >>>>>> +        /* Downscale pipe can't output RAW formats */
> >>>>>> +        if (mali_c55_fmts[i].is_raw &&
> >>>>>> +            cap_dev->reg_offset == MALI_C55_CAP_DEV_DS_REG_OFFSET)
> >>>>>> +            continue;
> >>>>>> +
> >>>>>> +        if (j++ == f->index) {
> >>>>>> +            f->pixelformat = mali_c55_fmts[i].fourcc;
> >>>>>> +            return 0;
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return -EINVAL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_querycap(struct file *file, void *fh,
> >>>>>> +                 struct v4l2_capability *cap)
> >>>>>> +{
> >>>>>> +    strscpy(cap->driver, MALI_C55_DRIVER_NAME, sizeof(cap->driver));
> >>>>>> +    strscpy(cap->card, "ARM Mali-C55 ISP", sizeof(cap->card));
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_ioctl_ops mali_c55_v4l2_ioctl_ops = {
> >>>>>> +    .vidioc_reqbufs = vb2_ioctl_reqbufs,
> >>>>>> +    .vidioc_querybuf = vb2_ioctl_querybuf,
> >>>>>> +    .vidioc_create_bufs = vb2_ioctl_create_bufs,
> >>>>>> +    .vidioc_qbuf = vb2_ioctl_qbuf,
> >>>>>> +    .vidioc_expbuf = vb2_ioctl_expbuf,
> >>>>>> +    .vidioc_dqbuf = vb2_ioctl_dqbuf,
> >>>>>> +    .vidioc_prepare_buf = vb2_ioctl_prepare_buf,
> >>>>>> +    .vidioc_streamon = vb2_ioctl_streamon,
> >>>>>> +    .vidioc_streamoff = vb2_ioctl_streamoff,
> >>>>>> +    .vidioc_try_fmt_vid_cap_mplane = mali_c55_try_fmt_vid_cap_mplane,
> >>>>>> +    .vidioc_s_fmt_vid_cap_mplane = mali_c55_s_fmt_vid_cap_mplane,
> >>>>>> +    .vidioc_g_fmt_vid_cap_mplane = mali_c55_g_fmt_vid_cap_mplane,
> >>>>>> +    .vidioc_enum_fmt_vid_cap = mali_c55_enum_fmt_vid_cap_mplane,
> >>>>>> +    .vidioc_querycap = mali_c55_querycap,
> >>>>>> +    .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> >>>>>> +    .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> >>>>>> +};
> >>>>>> +
> >>>>>> +int mali_c55_register_capture_devs(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct v4l2_pix_format_mplane pix_mp;
> >>>>>> +    struct mali_c55_cap_dev *cap_dev;
> >>>>>> +    struct video_device *vdev;
> >>>>>> +    struct vb2_queue *vb2q;
> >>>>>> +    unsigned int i;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55->cap_devs); i++) {
> >>>> 
> >>>> Moving the inner content to a separate mali_c55_register_capture_dev()
> >>>> function would increase readability I think, and remove usage of gotos.
> >>>> I would probably do the same for unregistration too, for consistency.
> >>>>
> >>>>>> +        cap_dev = &mali_c55->cap_devs[i];
> >>>>>> +        vdev = &cap_dev->vdev;
> >>>>>> +        vb2q = &cap_dev->queue;
> >>>>>> +
> >>>>>> +        /*
> >>>>>> +         * The downscale output pipe is an optional block within the ISP
> >>>>>> +         * so we need to check whether it's actually been fitted or not.
> >>>>>> +         */
> >>>>>> +
> >>>>>> +        if (i == MALI_C55_CAP_DEV_DS &&
> >>>>>> +            !(mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED))
> >>>>>> +            continue;
> >>>> 
> >>>> Given that there's only two capture devices, and one is optional, when
> >>>> moving the inner code to a separate function you could unroll the loop.
> >>>> Up to you.
> >>>>
> >>>>>> +
> >>>>>> +        cap_dev->mali_c55 = mali_c55;
> >>>>>> +        mutex_init(&cap_dev->lock);
> >>>>>> +        INIT_LIST_HEAD(&cap_dev->buffers.queue);
> >>>>>> +
> >>>>>> +        switch (i) {
> >>>>>> +        case MALI_C55_CAP_DEV_FR:
> >>>>>> +            cap_dev->rzr = &mali_c55->resizers[MALI_C55_RZR_FR];
> >>>>>> +            cap_dev->reg_offset = MALI_C55_CAP_DEV_FR_REG_OFFSET;
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_CAP_DEV_DS:
> >>>>>> +            cap_dev->rzr = &mali_c55->resizers[MALI_C55_RZR_DS];
> >>>>>> +            cap_dev->reg_offset = MALI_C55_CAP_DEV_DS_REG_OFFSET;
> >>>>>> +            break;
> >>>>>> +        default:
> >>>> 
> >>>> That can't happen.
> >>>>
> >>>>>> + mutex_destroy(&cap_dev->lock);
> >>>>>> +            ret = -EINVAL;
> >>>>>> +            goto err_destroy_mutex;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        cap_dev->pad.flags = MEDIA_PAD_FL_SINK;
> >>>>>> +        ret = media_entity_pads_init(&cap_dev->vdev.entity, 1, &cap_dev->pad);
> >>>>>> +        if (ret) {
> >>>>>> +            mutex_destroy(&cap_dev->lock);
> >>>>>> +            goto err_destroy_mutex;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        vb2q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
> >>>>>> +        vb2q->io_modes = VB2_MMAP | VB2_DMABUF;
> >>>>>> +        vb2q->drv_priv = cap_dev;
> >>>>>> +        vb2q->mem_ops = &vb2_dma_contig_memops;
> >>>>>> +        vb2q->ops = &mali_c55_vb2_ops;
> >>>>>> +        vb2q->buf_struct_size = sizeof(struct mali_c55_buffer);
> >>>>>> +        vb2q->min_queued_buffers = 1;
> >>>>>> +        vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
> >>>>>> +        vb2q->lock = &cap_dev->lock;
> >>>>>> +        vb2q->dev = mali_c55->dev;
> >>>>>> +
> >>>>>> +        ret = vb2_queue_init(vb2q);
> >>>>>> +        if (ret) {
> >>>>>> +            dev_err(mali_c55->dev, "%s vb2 queue init failed\n",
> >>>>>> +                mali_c55_cap_dev_to_name(cap_dev));
> >>>>>> +            goto err_cleanup_media_entity;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        strscpy(cap_dev->vdev.name, capture_device_names[i],
> >>>>>> +            sizeof(cap_dev->vdev.name));
> >>>>>> +        vdev->release = video_device_release_empty;
> >>>>>> +        vdev->fops = &mali_c55_v4l2_fops;
> >>>>>> +        vdev->ioctl_ops = &mali_c55_v4l2_ioctl_ops;
> >>>>>> +        vdev->lock = &cap_dev->lock;
> >>>>>> +        vdev->v4l2_dev = &mali_c55->v4l2_dev;
> >>>>>> +        vdev->queue = &cap_dev->queue;
> >>>>>> +        vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE |
> >>>>>> +                    V4L2_CAP_STREAMING | V4L2_CAP_IO_MC;
> >>>>>> +        vdev->entity.ops = &mali_c55_media_ops;
> >>>>>> +        video_set_drvdata(vdev, cap_dev);
> >>>>>> +
> >>>>>> +        memset(&pix_mp, 0, sizeof(pix_mp));
> >>>>>> +        pix_mp.pixelformat = V4L2_PIX_FMT_RGB565;
> >>>>>> +        pix_mp.width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +        pix_mp.height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +        mali_c55_set_format(cap_dev, &pix_mp);
> >>>>>> +
> >>>>>> +        ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
> >>>>>> +        if (ret) {
> >>>>>> +            dev_err(mali_c55->dev,
> >>>>>> +                "%s failed to register video device\n",
> >>>>>> +                mali_c55_cap_dev_to_name(cap_dev));
> >>>>>> +            goto err_release_vb2q;
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_release_vb2q:
> >>>>>> +    vb2_queue_release(vb2q);
> >>>>>> +err_cleanup_media_entity:
> >>>>>> +    media_entity_cleanup(&cap_dev->vdev.entity);
> >>>>>> +err_destroy_mutex:
> >>>>>> +    mutex_destroy(&cap_dev->lock);
> >>>>>> +    mali_c55_unregister_capture_devs(mali_c55);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_unregister_capture_devs(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_cap_dev *cap_dev;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55->cap_devs); i++) {
> >>>>>> +        cap_dev = &mali_c55->cap_devs[i];
> >>>>>> +
> >>>>>> +        if (!video_is_registered(&cap_dev->vdev))
> >>>>>> +            continue;
> >>>>>> +
> >>>>>> +        vb2_video_unregister_device(&cap_dev->vdev);
> >>>>>> +        media_entity_cleanup(&cap_dev->vdev.entity);
> >>>>>> +        mutex_destroy(&cap_dev->lock);
> >>>>>> +    }
> >>>>>> +}
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..2d0c4d152beb
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-common.h
> >>>>>> @@ -0,0 +1,266 @@
> >>>>>> +/* SPDX-License-Identifier: GPL-2.0 */
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Common definitions
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#ifndef _MALI_C55_COMMON_H
> >>>>>> +#define _MALI_C55_COMMON_H
> >>>>>> +
> >>>>>> +#include <linux/clk.h>
> >>>>>> +#include <linux/io.h>
> >>>>>> +#include <linux/list.h>
> >>>>>> +#include <linux/mutex.h>
> >>>>>> +#include <linux/scatterlist.h>
> >>>>> 
> >>>>> I don't think this is needed. You're however missing spinlock.h.
> >>>>>
> >>>>>> +#include <linux/videodev2.h>
> >>>>>> +
> >>>>>> +#include <media/media-device.h>
> >>>>>> +#include <media/v4l2-async.h>
> >>>>>> +#include <media/v4l2-ctrls.h>
> >>>>>> +#include <media/v4l2-dev.h>
> >>>>>> +#include <media/v4l2-device.h>
> >>>>>> +#include <media/v4l2-subdev.h>
> >>>>>> +#include <media/videobuf2-core.h>
> >>>>>> +#include <media/videobuf2-v4l2.h>
> >>>>>> +
> >>>>>> +#define MALI_C55_DRIVER_NAME        "mali-c55"
> >>>>>> +
> >>>>>> +/* min and max values for the image sizes */
> >>>>>> +#define MALI_C55_MIN_WIDTH        640U
> >>>>>> +#define MALI_C55_MIN_HEIGHT        480U
> >>>>>> +#define MALI_C55_MAX_WIDTH        8192U
> >>>>>> +#define MALI_C55_MAX_HEIGHT        8192U
> >>>>>> +#define MALI_C55_DEFAULT_WIDTH        1920U
> >>>>>> +#define MALI_C55_DEFAULT_HEIGHT        1080U
> >>>>>> +
> >>>>>> +#define MALI_C55_DEFAULT_MEDIA_BUS_FMT MEDIA_BUS_FMT_RGB121212_1X36
> >>>>>> +
> >>>>>> +struct mali_c55;
> >>>>>> +struct mali_c55_cap_dev;
> >>>>>> +struct platform_device;
> >>>>> 
> >>>>> You should also forward-declare
> >>>>>
> >>>>> struct device;
> >>>>> struct dma_chan;
> >>>>> struct resource;
> >>>>>
> >>>>>> +
> >>>>>> +static const char * const mali_c55_clk_names[] = {
> >>>>>> +    "aclk",
> >>>>>> +    "hclk",
> >>>>>> +};
> >>>>> 
> >>>>> This will end up duplicating the array in each compilation unit, not
> >>>>> great. Move it to mali-c55-core.c. You use it in this file just for its
> >>>>> size, replace that with a macro that defines the size, or allocate
> >>>>> mali_c55.clks dynamically with devm_kcalloc().
> >>>>>
> >>>>>> +
> >>>>>> +enum mali_c55_interrupts {
> >>>>>> +    MALI_C55_IRQ_ISP_START,
> >>>>>> +    MALI_C55_IRQ_ISP_DONE,
> >>>>>> +    MALI_C55_IRQ_MCM_ERROR,
> >>>>>> +    MALI_C55_IRQ_BROKEN_FRAME_ERROR,
> >>>>>> +    MALI_C55_IRQ_MET_AF_DONE,
> >>>>>> +    MALI_C55_IRQ_MET_AEXP_DONE,
> >>>>>> +    MALI_C55_IRQ_MET_AWB_DONE,
> >>>>>> +    MALI_C55_IRQ_AEXP_1024_DONE,
> >>>>>> +    MALI_C55_IRQ_IRIDIX_MET_DONE,
> >>>>>> +    MALI_C55_IRQ_LUT_INIT_DONE,
> >>>>>> +    MALI_C55_IRQ_FR_Y_DONE,
> >>>>>> +    MALI_C55_IRQ_FR_UV_DONE,
> >>>>>> +    MALI_C55_IRQ_DS_Y_DONE,
> >>>>>> +    MALI_C55_IRQ_DS_UV_DONE,
> >>>>>> +    MALI_C55_IRQ_LINEARIZATION_DONE,
> >>>>>> +    MALI_C55_IRQ_RAW_FRONTEND_DONE,
> >>>>>> +    MALI_C55_IRQ_NOISE_REDUCTION_DONE,
> >>>>>> +    MALI_C55_IRQ_IRIDIX_DONE,
> >>>>>> +    MALI_C55_IRQ_BAYER2RGB_DONE,
> >>>>>> +    MALI_C55_IRQ_WATCHDOG_TIMER,
> >>>>>> +    MALI_C55_IRQ_FRAME_COLLISION,
> >>>>>> +    MALI_C55_IRQ_UNUSED,
> >>>>>> +    MALI_C55_IRQ_DMA_ERROR,
> >>>>>> +    MALI_C55_IRQ_INPUT_STOPPED,
> >>>>>> +    MALI_C55_IRQ_MET_AWB_TARGET1_HIT,
> >>>>>> +    MALI_C55_IRQ_MET_AWB_TARGET2_HIT,
> >>>>>> +    MALI_C55_NUM_IRQ_BITS
> >>>>> 
> >>>>> Those are register bits, I think they belong to mali-c55-registers.h,
> >>>>> and should probably be macros instead of an enum.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_isp_pads {
> >>>>>> +    MALI_C55_ISP_PAD_SINK_VIDEO,
> >>>>> 
> >>>>> As there's a single sink pad, maybe MALI_C55_ISP_PAD_SINK ? Ah, you're
> >>>>> probably preparing for ISP parameters support. It's fine.
> >>>>>
> >>>>>> +    MALI_C55_ISP_PAD_SOURCE,
> >>>>> 
> >>>>> Then maybe this should be named MALI_C55_ISP_PAD_SOURCE_VIDEO as I
> >>>>> assume there will be a stats source pad.
> >>>>>
> >>>>>> +    MALI_C55_ISP_PAD_SOURCE_BYPASS,
> >>>>>> +    MALI_C55_ISP_NUM_PADS,
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_tpg {
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    struct v4l2_subdev sd;
> >>>>>> +    struct media_pad pad;
> >>>>>> +    struct mutex lock;
> >>>>>> +    struct mali_c55_tpg_ctrls {
> >>>>>> +        struct v4l2_ctrl_handler handler;
> >>>>>> +        struct v4l2_ctrl *test_pattern;
> >>>>> 
> >>>>> Set but never used. You can drop it.
> >>>>>
> >>>>>> +        struct v4l2_ctrl *hblank;
> >>>>> 
> >>>>> Set and used only once, in the same function. You can make it a local
> >>>>> variable.
> >>>>>
> >>>>>> +        struct v4l2_ctrl *vblank;
> >>>>>> +    } ctrls;
> >>>>>> +};
> >>>>> 
> >>>>> I wonder if this file should be split, with mali-c55-capture.h,
> >>>>> mali-c55-core.h, mali-c55-isp.h, ... I think it could increase
> >>>>> readability by clearly separating the different elements. Up to you.
> >>>>>
> >>>>>> +
> >>>>>> +struct mali_c55_isp {
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    struct v4l2_subdev sd;
> >>>>>> +    struct media_pad pads[MALI_C55_ISP_NUM_PADS];
> >>>>>> +    struct media_pad *remote_src;
> >>>>>> +    struct v4l2_async_notifier notifier;
> >>>>> 
> >>>>> I'm tempted to move the notifier to mali_c55, as it's related to
> >>>>> components external to the whole ISP, not to the ISP subdev itself.
> >>>>> Could you give it a try, to see if it could be done without any drawback
> >>>>> ?
> >>>>>
> >>>>>> +    struct mutex lock;
> >>>>> 
> >>>>> Locks require a comment to explain what they protect. Same below where
> >>>>> applicable (for both mutexes and spinlocks).
> >>>>>
> >>>>>> +    unsigned int frame_sequence;
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_resizer_ids {
> >>>>>> +    MALI_C55_RZR_FR,
> >>>>>> +    MALI_C55_RZR_DS,
> >>>>>> +    MALI_C55_NUM_RZRS,
> >>>>> 
> >>>>> The usual abbreviation for "resizer" in drivers/media/ is "rsz", not
> >>>>> "rzr". I would have said we can leave it as-is as changing it would be a
> >>>>> bit annoying, but I then realized that "rzr" is not just unusual, it's
> >>>>> actually not used at all. Would you mind applying a sed globally ? :-)
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_rzr_pads {
> >>>>> 
> >>>>> Same enums/structs use abbreviations, some don't. Consistency would
> >>>>> help.
> >>>>>
> >>>>>> +    MALI_C55_RZR_SINK_PAD,
> >>>>>> +    MALI_C55_RZR_SOURCE_PAD,
> >>>>>> +    MALI_C55_RZR_SINK_BYPASS_PAD,
> >>>>>> +    MALI_C55_RZR_NUM_PADS
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_resizer {
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    struct mali_c55_cap_dev *cap_dev;
> >>>>>> +    enum mali_c55_resizer_ids id;
> >>>>>> +    struct v4l2_subdev sd;
> >>>>>> +    struct media_pad pads[MALI_C55_RZR_NUM_PADS];
> >>>>>> +    unsigned int num_routes;
> >>>>>> +    bool streaming;
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_cap_devs {
> >>>>>> +    MALI_C55_CAP_DEV_FR,
> >>>>>> +    MALI_C55_CAP_DEV_DS,
> >>>>>> +    MALI_C55_NUM_CAP_DEVS
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_fmt {
> >>>>> 
> >>>>> mali_c55_format_info would be a better name I think, as this stores
> >>>>> format information, not formats.
> >>>>>
> >>>>>> +    u32 fourcc;
> >>>>>> +    unsigned int mbus_codes[2];
> >>>>> 
> >>>>> A comment to explain why we have two media bus codes would be useful.
> >>>>> You can document the whole structure if desired :-)
> >>>>>
> >>>>>> +    bool is_raw;
> >>>>>> +    struct mali_c55_fmt_registers {
> >>>>> 
> >>>>> Make it an anonymous structure, it's never used anywhere else.
> >>>>>
> >>>>>> +        unsigned int base_mode;
> >>>>>> +        unsigned int uv_plane;
> >>>>> 
> >>>>> If those are register field values, use u32 instead of unsigned int.
> >>>>>
> >>>>>> +    } registers;
> >>>>> 
> >>>>> It's funny, we tend to abbreviate different things, I would have used
> >>>>> "regs" here but written "format" in full in the structure name :-)
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_isp_bayer_order {
> >>>>>> +    MALI_C55_BAYER_ORDER_RGGB,
> >>>>>> +    MALI_C55_BAYER_ORDER_GRBG,
> >>>>>> +    MALI_C55_BAYER_ORDER_GBRG,
> >>>>>> +    MALI_C55_BAYER_ORDER_BGGR
> >>>>> 
> >>>>> These are registers values too, they belong to mali-c55-registers.h.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_isp_fmt {
> >>>>> 
> >>>>> mali_c55_isp_format_info
> >>>>>
> >>>>>> +    u32 code;
> >>>>>> +    enum v4l2_pixel_encoding encoding;
> >>>>> 
> >>>>> Here you use v4l2_pixel_encoding, for mali_c55_fmt you use is_raw. Maybe
> >>>>> pick the same option for both structures ?
> >>>>>
> >>>>>> +    enum mali_c55_isp_bayer_order order;
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_planes {
> >>>>>> +    MALI_C55_PLANE_Y,
> >>>>>> +    MALI_C55_PLANE_UV,
> >>>>>> +    MALI_C55_NUM_PLANES
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_buffer {
> >>>>>> +    struct vb2_v4l2_buffer vb;
> >>>>>> +    bool plane_done[MALI_C55_NUM_PLANES];
> >>>>> 
> >>>>> I think tracking the pending state would simplify the logic in
> >>>>> mali_c55_set_plane_done(), which would become
> >>>>>
> >>>>>      curr_buf->plane_pending[plane] = false;
> >>>>>
> >>>>>      if (!curr_buf->plane_pending[0] && !curr_buf->plane_pending[1]) {
> >>>>>          mali_c55_handle_buffer(curr_buf, isp->frame_sequence);
> >>>>>          cap_dev->buffers.curr = NULL;
> >>>>>          isp->frame_sequence++;
> >>>>>      }
> >>>>>
> >>>>> Or a counter may be even easier (and would consume less memory).
> >>>>>
> >>>>>> +    struct list_head queue;
> >>>>>> +    u32 addrs[MALI_C55_NUM_PLANES];
> >>>>> 
> >>>>> This stores DMA addresses, use dma_addr_t.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_cap_dev {
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    struct mali_c55_resizer *rzr;
> >>>>>> +    struct video_device vdev;
> >>>>>> +    struct media_pad pad;
> >>>>>> +    struct vb2_queue queue;
> >>>>>> +    struct mutex lock;
> >>>>>> +    unsigned int reg_offset;
> >>>>> 
> >>>>> Manual handling of the offset everywhere, with parametric macros for the
> >>>>> resizer register addresses, isn't very nice. Introduce resizer-specific
> >>>>> accessors and capture-specific accessors (mali_c55_rsz_write(), ...)
> >>>>> that take a mali_c55_resizer or mali_c55_cap_dev pointer, and handle the
> >>>>> offset there. The register macros should loose their offset parameter.
> >>>>>
> >>>>> You could also use a single set of accessors that would become
> >>>>> path/output-specific (mali_c55_path_write() ? mali_c55_output_write()
> >>>>> ?), that may make the code easier to read.
> >>>>>
> >>>>> You can also replace reg_offset with a void __iomem * base, which would
> >>>>> avoid the computation at runtime.
> >>>>>
> >>>>>> +
> >>>>>> +    struct mali_c55_mode {
> >>>>> 
> >>>>> Make the structure anonymous.
> >>>>>
> >>>>>> +        const struct mali_c55_fmt *capture_fmt;
> >>>>>> +        struct v4l2_pix_format_mplane pix_mp;
> >>>>>> +    } mode;
> >>>>> 
> >>>>> What's a "mode" ? I think I'd name this
> >>>>>
> >>>>>      struct {
> >>>>>          const struct mali_c55_fmt *info;
> >>>>>          struct v4l2_pix_format_mplane format;
> >>>>>      } format;
> >>>>>
> >>>>> Or you could just drop the structure and have
> >>>>>
> >>>>>      const struct mali_c55_fmt *format_info;
> >>>>>      struct v4l2_pix_format_mplane format;
> >>>>>
> >>>>> or something similar.
> >>>>>
> >>>>>> +
> >>>>>> +    struct {
> >>>>>> +        spinlock_t lock;
> >>>>>> +        struct list_head queue;
> >>>>>> +        struct mali_c55_buffer *curr;
> >>>>>> +        struct mali_c55_buffer *next;
> >>>>>> +    } buffers;
> >>>>>> +
> >>>>>> +    bool streaming;
> >>>>>> +};
> >>>>>> +
> >>>>>> +enum mali_c55_config_spaces {
> >>>>>> +    MALI_C55_CONFIG_PING,
> >>>>>> +    MALI_C55_CONFIG_PONG,
> >>>>>> +    MALI_C55_NUM_CONFIG_SPACES
> >>>>> 
> >>>>> The last enumerator is not used.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_ctx {
> >>>>> 
> >>>>> mali_c55_context ?
> >>>>>
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    void *registers;
> >>>>> 
> >>>>> Please document this structure and explain that this field points to a
> >>>>> copy of the register space in system memory, I was about to write you're
> >>>>> missing __iomem :-)
> >>>>>
> >>>>>> +    phys_addr_t base;
> >>>>>> +    spinlock_t lock;
> >>>>>> +    struct list_head list;
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55 {
> >>>>>> +    struct device *dev;
> >>>>>> +    struct resource *res;
> >>>>> 
> >>>>> You could possibly drop this field by passing the physical address of
> >>>>> the register space from mali_c55_probe() to mali_c55_init_context() as a
> >>>>> function parameter.
> >>>>>
> >>>>>> +    void __iomem *base;
> >>>>>> +    struct dma_chan *channel;
> >>>>>> +    struct clk_bulk_data clks[ARRAY_SIZE(mali_c55_clk_names)];
> >>>>>> +
> >>>>>> +    u16 capabilities;
> >>>>>> +    struct media_device media_dev;
> >>>>>> +    struct v4l2_device v4l2_dev;
> >>>>>> +    struct media_pipeline pipe;
> >>>>>> +
> >>>>>> +    struct mali_c55_tpg tpg;
> >>>>>> +    struct mali_c55_isp isp;
> >>>>>> +    struct mali_c55_resizer resizers[MALI_C55_NUM_RZRS];
> >>>>>> +    struct mali_c55_cap_dev cap_devs[MALI_C55_NUM_CAP_DEVS];
> >>>>>> +
> >>>>>> +    struct list_head contexts;
> >>>>>> +    enum mali_c55_config_spaces next_config;
> >>>>>> +};
> >>>>>> +
> >>>>>> +void mali_c55_write(struct mali_c55 *mali_c55, unsigned int addr, u32 val);
> >>>>>> +u32 mali_c55_read(struct mali_c55 *mali_c55, unsigned int addr,
> >>>>>> +          bool force_hardware);
> >>>>>> +void mali_c55_update_bits(struct mali_c55 *mali_c55, unsigned int addr,
> >>>>>> +              u32 mask, u32 val);
> >>>>>> +int mali_c55_config_write(struct mali_c55_ctx *ctx,
> >>>>>> +              enum mali_c55_config_spaces cfg_space);
> >>>>>> +
> >>>>>> +int mali_c55_register_isp(struct mali_c55 *mali_c55);
> >>>>>> +int mali_c55_register_tpg(struct mali_c55 *mali_c55);
> >>>>>> +void mali_c55_unregister_tpg(struct mali_c55 *mali_c55);
> >>>>>> +void mali_c55_unregister_isp(struct mali_c55 *mali_c55);
> >>>>>> +int mali_c55_register_resizers(struct mali_c55 *mali_c55);
> >>>>>> +void mali_c55_unregister_resizers(struct mali_c55 *mali_c55);
> >>>>>> +int mali_c55_register_capture_devs(struct mali_c55 *mali_c55);
> >>>>>> +void mali_c55_unregister_capture_devs(struct mali_c55 *mali_c55);
> >>>>>> +struct mali_c55_ctx *mali_c55_get_active_context(struct mali_c55 *mali_c55);
> >>>>>> +void mali_c55_set_plane_done(struct mali_c55_cap_dev *cap_dev,
> >>>>>> +                 enum mali_c55_planes plane);
> >>>>>> +void mali_c55_set_next_buffer(struct mali_c55_cap_dev *cap_dev);
> >>>>>> +void mali_c55_isp_queue_event_sof(struct mali_c55 *mali_c55);
> >>>>>> +
> >>>>>> +bool mali_c55_format_is_raw(unsigned int mbus_code);
> >>>>>> +void mali_c55_rzr_start_stream(struct mali_c55_resizer *rzr);
> >>>>>> +void mali_c55_rzr_stop_stream(struct mali_c55_resizer *rzr);
> >>>>>> +int mali_c55_isp_start_stream(struct mali_c55_isp *isp);
> >>>>>> +void mali_c55_isp_stop_stream(struct mali_c55_isp *isp);
> >>>>>> +
> >>>>>> +const struct mali_c55_isp_fmt *
> >>>>>> +mali_c55_isp_fmt_next(const struct mali_c55_isp_fmt *fmt);
> >>>>>> +bool mali_c55_isp_is_format_supported(unsigned int mbus_code);
> >>>>>> +#define for_each_mali_isp_fmt(fmt)\
> >>>>> 
> >>>>> #define for_each_mali_isp_fmt(fmt) \
> >>>>>
> >>>>>> +    for ((fmt) = NULL; ((fmt) = mali_c55_isp_fmt_next((fmt)));)
> >>>>> 
> >>>>> Looks like parentheses were on sale :-)
> >>>>>
> >>>>>      for ((fmt) = NULL; (fmt) = mali_c55_isp_fmt_next(fmt); )
> >>>>>
> >>>>> This macro is used in two places only, in the mali-c55-isp.c file where
> >>>>> open-coding the loop without using mali_c55_isp_fmt_next() would be more
> >>>>> efficient, and in mali-c55-resizer.c where a function to return format
> >>>>> i'th would be more efficient. I think you can drop the macro and the
> >>>>> mali_c55_isp_fmt_next() function.
> >>>>>
> >>>>>> +
> >>>>>> +#endif /* _MALI_C55_COMMON_H */
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..50caf5ee7474
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-core.c
> >>>>>> @@ -0,0 +1,767 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Core driver code
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/bitops.h>
> >>>>>> +#include <linux/cleanup.h>
> >>>>>> +#include <linux/clk.h>
> >>>>>> +#include <linux/delay.h>
> >>>>>> +#include <linux/device.h>
> >>>>>> +#include <linux/dmaengine.h>
> >>>>>> +#include <linux/dma-mapping.h>
> >>>>>> +#include <linux/interrupt.h>
> >>>>>> +#include <linux/iopoll.h>
> >>>>>> +#include <linux/ioport.h>
> >>>>>> +#include <linux/mod_devicetable.h>
> >>>>>> +#include <linux/of.h>
> >>>>>> +#include <linux/of_reserved_mem.h>
> >>>>>> +#include <linux/platform_device.h>
> >>>>>> +#include <linux/pm_runtime.h>
> >>>>>> +#include <linux/scatterlist.h>
> >>>>> 
> >>>>> I don't think this is needed.
> >>>>>
> >>>>> Missing slab.h.
> >>>>>
> >>>>>> +#include <linux/string.h>
> >>>>>> +
> >>>>>> +#include <media/media-entity.h>
> >>>>>> +#include <media/v4l2-device.h>
> >>>>>> +#include <media/videobuf2-dma-contig.h>
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +#include "mali-c55-registers.h"
> >>>>>> +
> >>>>>> +static const char * const mali_c55_interrupt_names[] = {
> >>>>>> +    [MALI_C55_IRQ_ISP_START] = "ISP start",
> >>>>>> +    [MALI_C55_IRQ_ISP_DONE] = "ISP done",
> >>>>>> +    [MALI_C55_IRQ_MCM_ERROR] = "Multi-context management error",
> >>>>>> +    [MALI_C55_IRQ_BROKEN_FRAME_ERROR] = "Broken frame error",
> >>>>>> +    [MALI_C55_IRQ_MET_AF_DONE] = "AF metering done",
> >>>>>> +    [MALI_C55_IRQ_MET_AEXP_DONE] = "AEXP metering done",
> >>>>>> +    [MALI_C55_IRQ_MET_AWB_DONE] = "AWB metering done",
> >>>>>> +    [MALI_C55_IRQ_AEXP_1024_DONE] = "AEXP 1024-bit histogram done",
> >>>>>> +    [MALI_C55_IRQ_IRIDIX_MET_DONE] = "Iridix metering done",
> >>>>>> +    [MALI_C55_IRQ_LUT_INIT_DONE] = "LUT memory init done",
> >>>>>> +    [MALI_C55_IRQ_FR_Y_DONE] = "Full resolution Y plane DMA done",
> >>>>>> +    [MALI_C55_IRQ_FR_UV_DONE] = "Full resolution U/V plane DMA done",
> >>>>>> +    [MALI_C55_IRQ_DS_Y_DONE] = "Downscale Y plane DMA done",
> >>>>>> +    [MALI_C55_IRQ_DS_UV_DONE] = "Downscale U/V plane DMA done",
> >>>>>> +    [MALI_C55_IRQ_LINEARIZATION_DONE] = "Linearisation done",
> >>>>>> +    [MALI_C55_IRQ_RAW_FRONTEND_DONE] = "Raw frontend processing done",
> >>>>>> +    [MALI_C55_IRQ_NOISE_REDUCTION_DONE] = "Noise reduction done",
> >>>>>> +    [MALI_C55_IRQ_IRIDIX_DONE] = "Iridix done",
> >>>>>> +    [MALI_C55_IRQ_BAYER2RGB_DONE] = "Bayer to RGB conversion done",
> >>>>>> +    [MALI_C55_IRQ_WATCHDOG_TIMER] = "Watchdog timer timed out",
> >>>>>> +    [MALI_C55_IRQ_FRAME_COLLISION] = "Frame collision error",
> >>>>>> +    [MALI_C55_IRQ_UNUSED] = "IRQ bit unused",
> >>>>>> +    [MALI_C55_IRQ_DMA_ERROR] = "DMA error",
> >>>>>> +    [MALI_C55_IRQ_INPUT_STOPPED] = "Input port safely stopped",
> >>>>>> +    [MALI_C55_IRQ_MET_AWB_TARGET1_HIT] = "AWB metering target 1 address hit",
> >>>>>> +    [MALI_C55_IRQ_MET_AWB_TARGET2_HIT] = "AWB metering target 2 address hit"
> >>>>>> +};
> >>>>>> +
> >>>>>> +static unsigned int config_space_addrs[] = {
> >>>>> 
> >>>>> const
> >>>>>
> >>>>>> +    [MALI_C55_CONFIG_PING] = 0x0AB6C,
> >>>>>> +    [MALI_C55_CONFIG_PONG] = 0x22B2C,
> >>>>> 
> >>>>> Lowercase hex constants.
> >>>>>
> >>>>> Don't the values belong to mali-c55-registers.h ?
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +/* System IO
> >>>>> 
> >>>>> /*
> >>>>>    * System IO
> >>>>>
> >>>>>> + *
> >>>>>> + * The Mali-C55 ISP has up to two configuration register spaces (called 'ping'
> >>>>>> + * and 'pong'), with the  expectation that the 'active' space will be left
> >>>>> 
> >>>>> s/the  /the /
> >>>>>
> >>>>>> + * untouched whilst a frame is being processed and the 'inactive' space
> >>>>>> + * configured ready to be passed during the blanking period before the next
> >>>>> 
> >>>>> s/to be passed/to be switched to/ ?
> >>>>>
> >>>>>> + * frame processing starts. These spaces should ideally be set via DMA transfer
> >>>>>> + * from a buffer rather than through individual register set operations. There
> >>>>>> + * is also a shared global register space which should be set normally. Of
> >>>>>> + * course, the ISP might be included in a system which lacks a suitable DMA
> >>>>>> + * engine, and the second configuration space might not be fitted at all, which
> >>>>>> + * means we need to support four scenarios:
> >>>>>> + *
> >>>>>> + * 1. Multi config space, with DMA engine.
> >>>>>> + * 2. Multi config space, no DMA engine.
> >>>>>> + * 3. Single config space, with DMA engine.
> >>>>>> + * 4. Single config space, no DMA engine.
> >>>>>> + *
> >>>>>> + * The first case is very easy, but the rest present annoying problems. The best
> >>>>>> + * way to solve them seems to be simply to replicate the concept of DMAing over
> >>>>>> + * the configuration buffer even if there's no DMA engine on the board, for
> >>>>>> + * which we rely on memcpy. To facilitate this any read/write call that is made
> >>>>>> + * to an address within those config spaces should infact be directed to a
> >>>>>> + * buffer that was allocated to hold them rather than the IO memory itself. The
> >>>>>> + * actual copy of that buffer to IO mem will happen on interrupt.
> >>>>>> + */
> >>>>>> +
> >>>>>> +void mali_c55_write(struct mali_c55 *mali_c55, unsigned int addr, u32 val)
> >>>>>> +{
> >>>>>> +    struct mali_c55_ctx *ctx = mali_c55_get_active_context(mali_c55);
> >>>>>> +
> >>>>>> +    if (addr >= MALI_C55_REG_CONFIG_SPACES_OFFSET) {
> >>>>>> +        spin_lock(&ctx->lock);
> >>>>>> +        addr = (addr - MALI_C55_REG_CONFIG_SPACES_OFFSET) / 4;
> >>>>>> +        ((u32 *)ctx->registers)[addr] = val;
> >>>>>> +        spin_unlock(&ctx->lock);
> >>>>>> +
> >>>>>> +        return;
> >>>>>> +    }
> >>>>> 
> >>>>> Ouch. This is likely the second comment you really won't like (after the
> >>>>> comment regarding mali_c55_update_bits() at the very top). I apologize
> >>>>> in advance.
> >>>>>
> >>>>> I really don't like this. Directing writes either to hardware registers
> >>>>> or to the shadow registers in the context makes the callers of the
> >>>>> read/write accessors very hard to read. The probe code, for instance,
> >>>>> mixes writes to hardware registers and writes to the context shadow
> >>>>> registers to initialize the value of some of the shadow registers.
> >>>>>
> >>>>> I'd like to split the read/write accessors into functions that access
> >>>>> the hardware registers (that's easy) and functions that access the
> >>>>> shadow registers. I think the latter should receive a mali_c55_ctx
> >>>>> pointer instead of a mali_c55 pointer to prepare for multi-context
> >>>>> support.
> >>>>>
> >>>>> You can add WARN_ON() guards to the two sets of functions, to ensure
> >>>>> that no register from the "other" space gets passed to the wrong
> >>>>> function by mistake.
> >>>>>
> >>>>>> +
> >>>>>> +    writel(val, mali_c55->base + addr);
> >>>>>> +}
> >>>>>> +
> >>>>>> +u32 mali_c55_read(struct mali_c55 *mali_c55, unsigned int addr,
> >>>>>> +          bool force_hardware)
> >>>>> 
> >>>>> force_hardware is never set to true.
> >>>>>
> >>>>>> +{
> >>>>>> +    struct mali_c55_ctx *ctx = mali_c55_get_active_context(mali_c55);
> >>>>>> +    u32 val;
> >>>>>> +
> >>>>>> +    if (addr >= MALI_C55_REG_CONFIG_SPACES_OFFSET && !force_hardware) {
> >>>>>> +        spin_lock(&ctx->lock);
> >>>>>> +        addr = (addr - MALI_C55_REG_CONFIG_SPACES_OFFSET) / 4;
> >>>>>> +        val = ((u32 *)ctx->registers)[addr];
> >>>>>> +        spin_unlock(&ctx->lock);
> >>>>>> +
> >>>>>> +        return val;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return readl(mali_c55->base + addr);
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_update_bits(struct mali_c55 *mali_c55, unsigned int addr,
> >>>>>> +              u32 mask, u32 val)
> >>>>>> +{
> >>>>>> +    u32 orig, tmp;
> >>>>>> +
> >>>>>> +    orig = mali_c55_read(mali_c55, addr, false);
> >>>>>> +
> >>>>>> +    tmp = orig & ~mask;
> >>>>>> +    tmp |= (val << (ffs(mask) - 1)) & mask;
> >>>>>> +
> >>>>>> +    if (tmp != orig)
> >>>>>> +        mali_c55_write(mali_c55, addr, tmp);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_dma_xfer(struct mali_c55_ctx *ctx, dma_addr_t src,
> >>>>>> +                 dma_addr_t dst, enum dma_data_direction dir)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = ctx->mali_c55;
> >>>>>> +    struct dma_async_tx_descriptor *tx;
> >>>>>> +    enum dma_status status;
> >>>>>> +    dma_cookie_t cookie;
> >>>>>> +
> >>>>>> +    tx = dmaengine_prep_dma_memcpy(mali_c55->channel, dst, src,
> >>>>>> +                       MALI_C55_CONFIG_SPACE_SIZE, 0);
> >>>>>> +    if (!tx) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to prep DMA memcpy\n");
> >>>>>> +        return -EIO;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    cookie = dmaengine_submit(tx);
> >>>>>> +    if (dma_submit_error(cookie)) {
> >>>>>> +        dev_err(mali_c55->dev, "error submitting dma transfer\n");
> >>>>>> +        return -EIO;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    status = dma_sync_wait(mali_c55->channel, cookie);
> >>>>> 
> >>>>> I've just realized this performs a busy-wait :-S See the comment in the
> >>>>> probe function about the threaded IRQ handler. I think we'll need to
> >>>>> rework all this. It could be done on top though.
> >>>>>
> >>>>>> +    if (status != DMA_COMPLETE) {
> >>>>>> +        dev_err(mali_c55->dev, "dma transfer failed\n");
> >>>>>> +        return -EIO;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_dma_read(struct mali_c55_ctx *ctx,
> >>>>>> +                 enum mali_c55_config_spaces cfg_space)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = ctx->mali_c55;
> >>>>>> +    struct device *dma_dev = mali_c55->channel->device->dev;
> >>>>>> +    dma_addr_t src = ctx->base + config_space_addrs[cfg_space];
> >>>>>> +    dma_addr_t dst;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    guard(spinlock)(&ctx->lock);
> >>>>>> +
> >>>>>> +    dst = dma_map_single(dma_dev, ctx->registers,
> >>>>>> +                 MALI_C55_CONFIG_SPACE_SIZE, DMA_FROM_DEVICE);
> >>>>>> +    if (dma_mapping_error(dma_dev, dst)) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to map DMA addr\n");
> >>>>>> +        return -EIO;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ret = mali_c55_dma_xfer(ctx, src, dst, DMA_FROM_DEVICE);
> >>>>>> +    dma_unmap_single(dma_dev, dst, MALI_C55_CONFIG_SPACE_SIZE,
> >>>>>> +             DMA_FROM_DEVICE);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_dma_write(struct mali_c55_ctx *ctx,
> >>>>>> +               enum mali_c55_config_spaces cfg_space)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = ctx->mali_c55;
> >>>>>> +    struct device *dma_dev = mali_c55->channel->device->dev;
> >>>>>> +    dma_addr_t dst = ctx->base + config_space_addrs[cfg_space];
> >>>>>> +    dma_addr_t src;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    guard(spinlock)(&ctx->lock);
> >>>>> 
> >>>>> The code below can take a large amount of time, holding a spinlock will
> >>>>> disable interrupts on the local CPU, that's not good :-(
> >>>>>
> >>>>>> +
> >>>>>> +    src = dma_map_single(dma_dev, ctx->registers,
> >>>>>> +                 MALI_C55_CONFIG_SPACE_SIZE, DMA_TO_DEVICE);
> >>>>>> +    if (dma_mapping_error(dma_dev, src)) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to map DMA addr\n");
> >>>>>> +        return -EIO;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ret = mali_c55_dma_xfer(ctx, src, dst, DMA_TO_DEVICE);
> >>>>>> +    dma_unmap_single(dma_dev, src, MALI_C55_CONFIG_SPACE_SIZE,
> >>>>>> +             DMA_TO_DEVICE);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_config_read(struct mali_c55_ctx *ctx,
> >>>>>> +                enum mali_c55_config_spaces cfg_space)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = ctx->mali_c55;
> >>>>>> +
> >>>>>> +    if (mali_c55->channel) {
> >>>>>> +        return mali_c55_dma_read(ctx, cfg_space);
> >>>>> 
> >>>>> As this function is used at probe time only, to initialize the context,
> >>>>> I think DMA is overkill.
> >>>>>
> >>>>>> +    } else {
> >>>>>> +        memcpy_fromio(ctx->registers,
> >>>>>> +                  mali_c55->base + config_space_addrs[cfg_space],
> >>>>>> +                  MALI_C55_CONFIG_SPACE_SIZE);
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +}
> >>>>>> +
> >>>>>> +int mali_c55_config_write(struct mali_c55_ctx *ctx,
> >>>>>> +              enum mali_c55_config_spaces cfg_space)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = ctx->mali_c55;
> >>>>>> +
> >>>>>> +    if (mali_c55->channel) {
> >>>>>> +        return mali_c55_dma_write(ctx, cfg_space);
> >>>>>> +    } else {
> >>>>>> +        memcpy_toio(mali_c55->base + config_space_addrs[cfg_space],
> >>>>>> +                ctx->registers, MALI_C55_CONFIG_SPACE_SIZE);
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>> 
> >>>>> Could you measure the time it typically takes to write the registers
> >>>>> using DMA compared to using memcpy_toio() ?
> >>>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +struct mali_c55_ctx *mali_c55_get_active_context(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    return list_first_entry(&mali_c55->contexts, struct mali_c55_ctx, list);
> >>>>> 
> >>>>> I think it's too early to tell how multi-context support will look like.
> >>>>> I'm fine keeping mali_c55_get_active_context() as changing that would be
> >>>>> very intrusive (even if I think it will need to be changed), but the
> >>>>> list of contexts is neither the mechanism we'll use, nor something we
> >>>>> need now. Drop the list, embed the context in struct mali_c55, and
> >>>>> return the pointer to that single context from this function.
> >>>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_remove_links(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> + media_entity_remove_links(&mali_c55->tpg.sd.entity);
> >>>>>> + media_entity_remove_links(&mali_c55->isp.sd.entity);
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_NUM_RZRS; i++)
> >>>>>> + media_entity_remove_links(&mali_c55->resizers[i].sd.entity);
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_NUM_CAP_DEVS; i++)
> >>>>>> + media_entity_remove_links(&mali_c55->cap_devs[i].vdev.entity);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_create_links(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    /* Test pattern generator to ISP */
> >>>>>> +    ret = media_create_pad_link(&mali_c55->tpg.sd.entity, 0,
> >>>>>> +                    &mali_c55->isp.sd.entity,
> >>>>>> +                    MALI_C55_ISP_PAD_SINK_VIDEO, 0);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to link TPG and ISP\n");
> >>>>>> +        goto err_remove_links;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Full resolution resizer pipe. */
> >>>>>> +    ret = media_create_pad_link(&mali_c55->isp.sd.entity,
> >>>>>> +            MALI_C55_ISP_PAD_SOURCE,
> >>>>>> + &mali_c55->resizers[MALI_C55_RZR_FR].sd.entity,
> >>>>>> +            MALI_C55_RZR_SINK_PAD,
> >>>>>> +            MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to link ISP and FR resizer\n");
> >>>>>> +        goto err_remove_links;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Full resolution bypass. */
> >>>>>> +    ret = media_create_pad_link(&mali_c55->isp.sd.entity,
> >>>>>> +                    MALI_C55_ISP_PAD_SOURCE_BYPASS,
> >>>>>> + &mali_c55->resizers[MALI_C55_RZR_FR].sd.entity,
> >>>>>> +                    MALI_C55_RZR_SINK_BYPASS_PAD,
> >>>>>> +                    MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to link ISP and FR resizer\n");
> >>>>>> +        goto err_remove_links;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Resizer pipe to video capture nodes. */
> >>>>>> +    ret = media_create_pad_link(&mali_c55->resizers[0].sd.entity,
> >>>>>> +            MALI_C55_RZR_SOURCE_PAD,
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR].vdev.entity,
> >>>>>> +            0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev,
> >>>>>> +            "failed to link FR resizer and video device\n");
> >>>>>> +        goto err_remove_links;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* The downscale pipe is an optional hardware block */
> >>>>>> +    if (mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) {
> >>>>>> +        ret = media_create_pad_link(&mali_c55->isp.sd.entity,
> >>>>>> +            MALI_C55_ISP_PAD_SOURCE,
> >>>>>> + &mali_c55->resizers[MALI_C55_RZR_DS].sd.entity,
> >>>>>> +            MALI_C55_RZR_SINK_PAD,
> >>>>>> +            MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> >>>>>> +        if (ret) {
> >>>>>> +            dev_err(mali_c55->dev,
> >>>>>> +                "failed to link ISP and DS resizer\n");
> >>>>>> +            goto err_remove_links;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        ret = media_create_pad_link(&mali_c55->resizers[1].sd.entity,
> >>>>>> +            MALI_C55_RZR_SOURCE_PAD,
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS].vdev.entity,
> >>>>>> +            0, MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE);
> >>>>>> +        if (ret) {
> >>>>>> +            dev_err(mali_c55->dev,
> >>>>>> +                "failed to link DS resizer and video device\n");
> >>>>>> +            goto err_remove_links;
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_remove_links:
> >>>>>> +    mali_c55_remove_links(mali_c55);
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_unregister_entities(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    mali_c55_unregister_tpg(mali_c55);
> >>>>>> +    mali_c55_unregister_isp(mali_c55);
> >>>>>> +    mali_c55_unregister_resizers(mali_c55);
> >>>>>> +    mali_c55_unregister_capture_devs(mali_c55);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_register_entities(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    ret = mali_c55_register_tpg(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        return ret;
> >>>>>> +
> >>>>>> +    ret = mali_c55_register_isp(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_unregister_entities;
> >>>>>> +
> >>>>>> +    ret = mali_c55_register_resizers(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_unregister_entities;
> >>>>>> +
> >>>>>> +    ret = mali_c55_register_capture_devs(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_unregister_entities;
> >>>>>> +
> >>>>>> +    ret = mali_c55_create_links(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_unregister_entities;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_unregister_entities:
> >>>>>> +    mali_c55_unregister_entities(mali_c55);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static u32 mali_c55_check_hwcfg(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    u32 product, version, revision, capabilities;
> >>>>>> +
> >>>>>> +    product = mali_c55_read(mali_c55, MALI_C55_REG_PRODUCT, false);
> >>>>>> +    version = mali_c55_read(mali_c55, MALI_C55_REG_VERSION, false);
> >>>>>> +    revision = mali_c55_read(mali_c55, MALI_C55_REG_REVISION, false);
> >>>>>> +
> >>>>>> +    dev_info(mali_c55->dev, "Detected Mali-C55 ISP %u.%u.%u\n",
> >>>>>> +         product, version, revision);
> >>>>>> +
> >>>>>> +    capabilities = mali_c55_read(mali_c55,
> >>>>>> +                     MALI_C55_REG_GLOBAL_PARAMETER_STATUS,
> >>>>>> +                     false);
> >>>>>> +    mali_c55->capabilities = (capabilities & 0xffff);
> >>>>>> +
> >>>>>> +    /* TODO: Might as well start some debugfs */
> >>>>> 
> >>>>> If it's just to expose the version and capabilities, I think that's
> >>>>> overkill. It's not needed for debug purpose (you can get it from the
> >>>>> kernel log already). debugfs isn't meant to be accessible in production,
> >>>>> so an application that would need access to the information wouldn't be
> >>>>> able to use it.
> >>>>>
> >>>>>> +    dev_info(mali_c55->dev, "Mali-C55 capabilities: 0x%04x\n", capabilities);
> >>>>> 
> >>>>> Combine the two messages into one.
> >>>>>
> >>>>>> +    return version;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_swap_next_config(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_ctx *ctx = mali_c55_get_active_context(mali_c55);
> >>>>>> +    u32 curr_config, next_config;
> >>>>>> +
> >>>>>> +    curr_config = mali_c55_read(mali_c55, MALI_C55_REG_PING_PONG_READ, false);
> >>>>>> +    curr_config = (curr_config & MALI_C55_REG_PING_PONG_READ_MASK)
> >>>>>> +              >> (ffs(MALI_C55_REG_PING_PONG_READ_MASK) - 1);
> >>>>>> +    next_config = curr_config ^ 1;
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
> >>>>>> +                 MALI_C55_REG_MCU_CONFIG_WRITE_MASK, next_config);
> >>>>>> +    mali_c55_config_write(ctx, next_config ?
> >>>>>> +                  MALI_C55_CONFIG_PING : MALI_C55_CONFIG_PONG);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static irqreturn_t mali_c55_isr(int irq, void *context)
> >>>>>> +{
> >>>>>> +    struct device *dev = context;
> >>>>>> +    struct mali_c55 *mali_c55 = dev_get_drvdata(dev);
> >>>>>> +    u32 interrupt_status;
> >>>>>> +    unsigned int i, j;
> >>>>>> +
> >>>>>> +    interrupt_status = mali_c55_read(mali_c55,
> >>>>>> +                     MALI_C55_REG_INTERRUPT_STATUS_VECTOR,
> >>>>>> +                     false);
> >>>>>> +    if (!interrupt_status)
> >>>>>> +        return IRQ_NONE;
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR_VECTOR,
> >>>>>> +               interrupt_status);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR, 0);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR, 1);
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_NUM_IRQ_BITS; i++) {
> >>>>>> +        if (!(interrupt_status & (1 << i)))
> >>>>>> +            continue;
> >>>>>> +
> >>>>>> +        switch (i) {
> >>>>>> +        case MALI_C55_IRQ_ISP_START:
> >>>>>> +            mali_c55_isp_queue_event_sof(mali_c55);
> >>>>>> +
> >>>>>> +            for (j = i; j < MALI_C55_NUM_CAP_DEVS; j++)
> >>>>>> + mali_c55_set_next_buffer(&mali_c55->cap_devs[j]);
> >>>>>> +
> >>>>>> +            mali_c55_swap_next_config(mali_c55);
> >>>>>> +
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_IRQ_ISP_DONE:
> >>>>>> +            /*
> >>>>>> +             * TODO: Where the ISP has no Pong config fitted, we'd
> >>>>>> +             * have to do the mali_c55_swap_next_config() call here.
> >>>>>> +             */
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_IRQ_FR_Y_DONE:
> >>>>>> +            mali_c55_set_plane_done(
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR],
> >>>>>> +                MALI_C55_PLANE_Y);
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_IRQ_FR_UV_DONE:
> >>>>>> +            mali_c55_set_plane_done(
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR],
> >>>>>> +                MALI_C55_PLANE_UV);
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_IRQ_DS_Y_DONE:
> >>>>>> +            mali_c55_set_plane_done(
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS],
> >>>>>> +                MALI_C55_PLANE_Y);
> >>>>>> +            break;
> >>>>>> +        case MALI_C55_IRQ_DS_UV_DONE:
> >>>>>> +            mali_c55_set_plane_done(
> >>>>>> + &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS],
> >>>>>> +                MALI_C55_PLANE_UV);
> >>>>>> +            break;
> >>>>>> +        default:
> >>>>>> +            /*
> >>>>>> +             * Only the above interrupts are currently unmasked. If
> >>>>>> +             * we receive anything else here then something weird
> >>>>>> +             * has gone on.
> >>>>>> +             */
> >>>>>> +            dev_err(dev, "masked interrupt %s triggered\n",
> >>>>>> +                mali_c55_interrupt_names[i]);
> >>>>>> +        }
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return IRQ_HANDLED;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_init_context(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_ctx *ctx;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> >>>>>> +    if (!ctx) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to allocate new context\n");
> >>>>> 
> >>>>> No need for an error message when memory allocation fails.
> >>>>>
> >>>>>> +        return -ENOMEM;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ctx->base = mali_c55->res->start;
> >>>>>> +    ctx->mali_c55 = mali_c55;
> >>>>>> +
> >>>>>> +    ctx->registers = kzalloc(MALI_C55_CONFIG_SPACE_SIZE,
> >>>>>> +                 GFP_KERNEL | GFP_DMA);
> >>>>>> +    if (!ctx->registers) {
> >>>>>> +        ret = -ENOMEM;
> >>>>>> +        goto err_free_ctx;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The allocated memory is empty, we need to load the default
> >>>>>> +     * register settings. We just read Ping; it's identical to Pong.
> >>>>>> +     */
> >>>>>> +    ret = mali_c55_config_read(ctx, MALI_C55_CONFIG_PING);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_free_registers;
> >>>>>> +
> >>>>>> +    list_add_tail(&ctx->list, &mali_c55->contexts);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Some features of the ISP need to be disabled by default and only
> >>>>>> +     * enabled at the same time as they're configured by a parameters buffer
> >>>>>> +     */
> >>>>>> +
> >>>>>> +    /* Bypass the sqrt and square compression and expansion modules */
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BYPASS_1,
> >>>>>> +                 MALI_C55_REG_BYPASS_1_FE_SQRT, 0x01);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BYPASS_3,
> >>>>>> +                 MALI_C55_REG_BYPASS_3_SQUARE_BE, 0x01);
> >>>>>> +
> >>>>>> +    /* Bypass the temper module */
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_BYPASS_2,
> >>>>>> +               MALI_C55_REG_BYPASS_2_TEMPER);
> >>>>>> +
> >>>>>> +    /* Bypass the colour noise reduction  */
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_BYPASS_4,
> >>>>>> +               MALI_C55_REG_BYPASS_4_CNR);
> >>>>>> +
> >>>>>> +    /* Disable the sinter module */
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_SINTER_CONFIG,
> >>>>>> +                 MALI_C55_SINTER_ENABLE_MASK, 0x00);
> >>>>>> +
> >>>>>> +    /* Disable the RGB Gamma module for each output */
> >>>>>> +    mali_c55_write(
> >>>>>> +        mali_c55,
> >>>>>> + MALI_C55_REG_GAMMA_RGB_ENABLE(MALI_C55_CAP_DEV_FR_REG_OFFSET),
> >>>>>> +        0x00);
> >>>>>> +    mali_c55_write(
> >>>>>> +        mali_c55,
> >>>>>> + MALI_C55_REG_GAMMA_RGB_ENABLE(MALI_C55_CAP_DEV_DS_REG_OFFSET),
> >>>>>> +        0x00);
> >>>>>> +
> >>>>>> +    /* Disable the colour correction matrix */
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CCM_ENABLE, 0x00);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_free_registers:
> >>>>>> +    kfree(ctx->registers);
> >>>>>> +err_free_ctx:
> >>>>>> +    kfree(ctx);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_runtime_resume(struct device *dev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = dev_get_drvdata(dev);
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    ret = clk_bulk_prepare_enable(ARRAY_SIZE(mali_c55->clks),
> >>>>>> +                      mali_c55->clks);
> >>>>>> +    if (ret)
> >>>>>> +        dev_err(mali_c55->dev, "failed to enable clocks\n");
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_runtime_suspend(struct device *dev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = dev_get_drvdata(dev);
> >>>>>> +
> >>>>>> + clk_bulk_disable_unprepare(ARRAY_SIZE(mali_c55->clks), mali_c55->clks);
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct dev_pm_ops mali_c55_pm_ops = {
> >>>>>> +    SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> >>>>>> +                pm_runtime_force_resume)
> >>>>>> +    SET_RUNTIME_PM_OPS(mali_c55_runtime_suspend, mali_c55_runtime_resume,
> >>>>>> +               NULL)
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_probe(struct platform_device *pdev)
> >>>>>> +{
> >>>>>> +    struct device *dev = &pdev->dev;
> >>>>>> +    struct mali_c55 *mali_c55;
> >>>>>> +    dma_cap_mask_t mask;
> >>>>>> +    u32 version;
> >>>>>> +    int ret;
> >>>>>> +    u32 val;
> >>>>>> +
> >>>>>> +    mali_c55 = devm_kzalloc(dev, sizeof(*mali_c55), GFP_KERNEL);
> >>>>>> +    if (!mali_c55)
> >>>>>> +        return dev_err_probe(dev, -ENOMEM,
> >>>>>> +                     "failed to allocate memory\n");
> >>>>> 
> >>>>>          return -ENOMEM;
> >>>>>
> >>>>> There's no need to print messages for memory allocation failures.
> >>>>>
> >>>>>> +
> >>>>>> +    mali_c55->dev = dev;
> >>>>>> +    platform_set_drvdata(pdev, mali_c55);
> >>>>>> +
> >>>>>> +    mali_c55->base = devm_platform_get_and_ioremap_resource(pdev, 0,
> >>>>>> +                                &mali_c55->res);
> >>>>>> +    if (IS_ERR(mali_c55->base))
> >>>>>> +        return dev_err_probe(dev, PTR_ERR(mali_c55->base),
> >>>>>> +                     "failed to map IO memory\n");
> >>>>>> +
> >>>>>> +    ret = platform_get_irq(pdev, 0);
> >>>>>> +    if (ret < 0)
> >>>>>> +        return dev_err_probe(dev, ret, "failed to get interrupt num\n");
> >>>>> 
> >>>>> s/ num// or s/num/number/
> >>>>>
> >>>>>> +
> >>>>>> +    ret = devm_request_threaded_irq(&pdev->dev, ret, NULL,
> >>>>>> +                    mali_c55_isr, IRQF_ONESHOT,
> >>>>>> +                    dev_driver_string(&pdev->dev),
> >>>>>> +                    &pdev->dev);
> >>>>> 
> >>>>> Requested the IRQ should be done much lower, after you have initialized
> >>>>> everything, or an IRQ that would fire early would have really bad
> >>>>> consequences.
> >>>>>
> >>>>> A comment to explain why you need a threaded interrupt handler would be
> >>>>> good. I assume it is due only to the need to transfer the registers
> >>>>> using DMA. I wonder if we should then split the interrupt handler in
> >>>>> two, with a non-threaded part for the operations that can run quickly,
> >>>>> and a threaded part for the reprogramming.
> >>>>>
> >>>>> It may also be that we could just start the DMA transfer in the
> >>>>> non-threaded handler without waiting synchronously for it to complete.
> >>>>> That would be a bigger change, and would require checking race
> >>>>> conditions carefully. On the other hand, I'm a bit concerned about the
> >>>>> current implementation, have you tested what happens if the DMA transfer
> >>>>> takes too long to complete, and spans frame boundaries ? This part could
> >>>>> be addressed by a patch on top of this one.
> >>>>>
> >>>>>> +    if (ret)
> >>>>>> +        return dev_err_probe(dev, ret, "failed to request irq\n");
> >>>>>> +
> >>>>>> +    for (unsigned int i = 0; i < ARRAY_SIZE(mali_c55_clk_names); i++)
> >>>>>> +        mali_c55->clks[i].id = mali_c55_clk_names[i];
> >>>>>> +
> >>>>>> +    ret = devm_clk_bulk_get(dev, ARRAY_SIZE(mali_c55->clks), mali_c55->clks);
> >>>>>> +    if (ret)
> >>>>>> +        return dev_err_probe(dev, ret, "failed to acquire clocks\n");
> >>>>>> +
> >>>>>> +    pm_runtime_enable(&pdev->dev);
> >>>>>> +
> >>>>>> +    ret = pm_runtime_resume_and_get(&pdev->dev);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_pm_runtime_disable;
> >>>>>> +
> >>>>>> +    of_reserved_mem_device_init(dev);
> >>>>> 
> >>>>> I'd move this, the vb2_dma_contig_set_max_seg_size() call and the
> >>>>> dma_cap_* calls before pm_runtime_enable() as they don't need the device
> >>>>> to be powered.
> >>>>>
> >>>>>> +    version = mali_c55_check_hwcfg(mali_c55);
> >>>>>> +    vb2_dma_contig_set_max_seg_size(dev, UINT_MAX);
> >>>>>> +
> >>>>>> +    /* Use "software only" context management. */
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
> >>>>>> +                 MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK,
> >>>>>> +                 MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK);
> >>>>> 
> >>>>> You handle that in mali_c55_isp_start(), does the register have to be
> >>>>> set here too ?
> >>>>>
> >>>>>> +
> >>>>>> +    dma_cap_zero(mask);
> >>>>>> +    dma_cap_set(DMA_MEMCPY, mask);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * No error check, because we will just fallback on memcpy if there is
> >>>>>> +     * no usable DMA channel on the system.
> >>>>>> +     */
> >>>>>> +    mali_c55->channel = dma_request_channel(mask, NULL, NULL);
> >>>>>> +
> >>>>>> +    INIT_LIST_HEAD(&mali_c55->contexts);
> >>>>>> +    ret = mali_c55_init_context(mali_c55);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_release_dma_channel;
> >>>>>> +
> >>>>> 
> >>>>> I'd move all the code from here ...
> >>>>>
> >>>>>> +    mali_c55->media_dev.dev = dev;
> >>>>>> +    strscpy(mali_c55->media_dev.model, "ARM Mali-C55 ISP",
> >>>>>> +        sizeof(mali_c55->media_dev.model));
> >>>>>> +    mali_c55->media_dev.hw_revision = version;
> >>>>>> +
> >>>>>> +    media_device_init(&mali_c55->media_dev);
> >>>>>> +    ret = media_device_register(&mali_c55->media_dev);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_cleanup_media_device;
> >>>>>> +
> >>>>>> +    mali_c55->v4l2_dev.mdev = &mali_c55->media_dev;
> >>>>>> +    ret = v4l2_device_register(dev, &mali_c55->v4l2_dev);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(dev, "failed to register V4L2 device\n");
> >>>>>> +        goto err_unregister_media_device;
> >>>>>> +    };
> >>>>>> +
> >>>>>> +    ret = mali_c55_register_entities(mali_c55);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(dev, "failed to register entities\n");
> >>>>>> +        goto err_unregister_v4l2_device;
> >>>>>> +    }
> >>>>> 
> >>>>> ... to here to a separate function, or maybe fold it all in
> >>>>> mali_c55_register_entities() (which should the be renamed). Same thing
> >>>>> for the cleanup code.
> >>>>>
> >>>>>> +
> >>>>>> +    /* Set safe stop to ensure we're in a non-streaming state */
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST,
> >>>>>> +               MALI_C55_INPUT_SAFE_STOP);
> >>>>>> +    readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS,
> >>>>>> +               val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We're ready to process interrupts. Clear any that are set and then
> >>>>>> +     * unmask them for processing.
> >>>>>> +     */
> >>>>>> +    mali_c55_write(mali_c55, 0x30, 0xffffffff);
> >>>>>> +    mali_c55_write(mali_c55, 0x34, 0xffffffff);
> >>>>>> +    mali_c55_write(mali_c55, 0x40, 0x01);
> >>>>>> +    mali_c55_write(mali_c55, 0x40, 0x00);
> >>>>> 
> >>>>> Please replace the register addresses with macros.
> >>>>>
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_MASK_VECTOR, 0x39fc3fc);
> >>>>> 
> >>>>> The value should use the interrupt bits macros.
> >>>>>
> >>>>>> +
> >>>>>> +    pm_runtime_put(&pdev->dev);
> >>>>> 
> >>>>> Once power gets cut, the registers your programmed above may be lost. I
> >>>>> think you should programe them in the runtime PM resume handler.
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_unregister_v4l2_device:
> >>>>>> +    v4l2_device_unregister(&mali_c55->v4l2_dev);
> >>>>>> +err_unregister_media_device:
> >>>>>> +    media_device_unregister(&mali_c55->media_dev);
> >>>>>> +err_cleanup_media_device:
> >>>>>> +    media_device_cleanup(&mali_c55->media_dev);
> >>>>>> +err_release_dma_channel:
> >>>>>> +    dma_release_channel(mali_c55->channel);
> >>>>>> +err_pm_runtime_disable:
> >>>>>> +    pm_runtime_disable(&pdev->dev);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_remove(struct platform_device *pdev)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = platform_get_drvdata(pdev);
> >>>>>> +    struct mali_c55_ctx *ctx, *tmp;
> >>>>>> +
> >>>>>> +    list_for_each_entry_safe(ctx, tmp, &mali_c55->contexts, list) {
> >>>>>> +        list_del(&ctx->list);
> >>>>>> +        kfree(ctx->registers);
> >>>>>> +        kfree(ctx);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    mali_c55_remove_links(mali_c55);
> >>>>>> +    mali_c55_unregister_entities(mali_c55);
> >>>>>> +    v4l2_device_put(&mali_c55->v4l2_dev);
> >>>>>> +    media_device_unregister(&mali_c55->media_dev);
> >>>>>> +    media_device_cleanup(&mali_c55->media_dev);
> >>>>>> +    dma_release_channel(mali_c55->channel);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct of_device_id mali_c55_of_match[] = {
> >>>>>> +    { .compatible = "arm,mali-c55", },
> >>>>>> +    {},
> >>>>> 
> >>>>>      { /* Sentinel */ },
> >>>>>
> >>>>>> +};
> >>>>>> +MODULE_DEVICE_TABLE(of, mali_c55_of_match);
> >>>>>> +
> >>>>>> +static struct platform_driver mali_c55_driver = {
> >>>>>> +    .driver = {
> >>>>>> +        .name = "mali-c55",
> >>>>>> +        .of_match_table = of_match_ptr(mali_c55_of_match),
> >>>>> 
> >>>>> Drop of_match_ptr().
> >>>>>
> >>>>>> +        .pm = &mali_c55_pm_ops,
> >>>>>> +    },
> >>>>>> +    .probe = mali_c55_probe,
> >>>>>> +    .remove_new = mali_c55_remove,
> >>>>>> +};
> >>>>>> +
> >>>>>> +module_platform_driver(mali_c55_driver);
> >>>>> 
> >>>>> Blank line.
> >>>>>
> >>>>>> +MODULE_AUTHOR("Daniel Scally <dan.scally@xxxxxxxxxxxxxxxx>");
> >>>>>> +MODULE_AUTHOR("Jacopo Mondi <jacopo.mondi@xxxxxxxxxxxxxxxx>");
> >>>>>> +MODULE_DESCRIPTION("ARM Mali-C55 ISP platform driver");
> >>>>>> +MODULE_LICENSE("GPL");
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..ea8b7b866e7a
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
> >>>>>> @@ -0,0 +1,611 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Image signal processor
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/delay.h>
> >>>>>> +#include <linux/iopoll.h>
> >>>>>> +#include <linux/property.h>
> >>>>>> +#include <linux/string.h>
> >>>>>> +
> >>>>>> +#include <media/media-entity.h>
> >>>>>> +#include <media/v4l2-common.h>
> >>>>>> +#include <media/v4l2-event.h>
> >>>>>> +#include <media/v4l2-mc.h>
> >>>>>> +#include <media/v4l2-subdev.h>
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +#include "mali-c55-registers.h"
> >>>>>> +
> >>>>>> +static const struct mali_c55_isp_fmt mali_c55_isp_fmts[] = {
> >>>>>> +    {
> >>>>>> +        .code = MEDIA_BUS_FMT_SRGGB20_1X20,
> >>>>>> +        .order = MALI_C55_BAYER_ORDER_RGGB,
> >>>>>> +        .encoding = V4L2_PIXEL_ENC_BAYER,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .code = MEDIA_BUS_FMT_SGRBG20_1X20,
> >>>>>> +        .order = MALI_C55_BAYER_ORDER_GRBG,
> >>>>>> +        .encoding = V4L2_PIXEL_ENC_BAYER,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .code = MEDIA_BUS_FMT_SGBRG20_1X20,
> >>>>>> +        .order = MALI_C55_BAYER_ORDER_GBRG,
> >>>>>> +        .encoding = V4L2_PIXEL_ENC_BAYER,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .code = MEDIA_BUS_FMT_SBGGR20_1X20,
> >>>>>> +        .order = MALI_C55_BAYER_ORDER_BGGR,
> >>>>>> +        .encoding = V4L2_PIXEL_ENC_BAYER,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .code = MEDIA_BUS_FMT_RGB202020_1X60,
> >>>>>> +        .order = 0, /* Not relevant for this format */
> >>>>>> +        .encoding = V4L2_PIXEL_ENC_RGB,
> >>>>>> +    }
> >>>>>> +    /*
> >>>>>> +     * TODO: Support MEDIA_BUS_FMT_YUV20_1X60 here. This is so that we can
> >>>>>> +     * also support YUV input from a sensor passed-through to the output. At
> >>>>>> +     * present we have no mechanism to test that though so it may have to
> >>>>>> +     * wait a while...
> >>>>>> +     */
> >>>>>> +};
> >>>>>> +
> >>>>>> +const struct mali_c55_isp_fmt *
> >>>>>> +mali_c55_isp_fmt_next(const struct mali_c55_isp_fmt *fmt)
> >>>>>> +{
> >>>>>> +    if (!fmt)
> >>>>>> +        fmt = &mali_c55_isp_fmts[0];
> >>>>>> +    else
> >>>>>> +        fmt++;
> >>>>>> +
> >>>>>> +    for (; fmt < &mali_c55_isp_fmts[ARRAY_SIZE(mali_c55_isp_fmts)]; fmt++)
> >>>>>> +        return fmt;
> >>>>> 
> >>>>> That's peculiar.
> >>>>>
> >>>>>      if (!fmt)
> >>>>>          fmt = &mali_c55_isp_fmts[0];
> >>>>>      else if (fmt < &mali_c55_isp_fmts[ARRAY_SIZE(mali_c55_isp_fmts)])
> >>>>>          return ++fmt;
> >>>>>      else
> >>>>>          return NULL;
> >>>>>
> >>>>>> +
> >>>>>> +    return NULL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +bool mali_c55_isp_is_format_supported(unsigned int mbus_code)
> >>>>>> +{
> >>>>>> +    const struct mali_c55_isp_fmt *isp_fmt;
> >>>>>> +
> >>>>>> +    for_each_mali_isp_fmt(isp_fmt) {
> >>>>> 
> >>>>> I would open-code the loop instead of using the macro, like you do
> >>>>> below. It will be more efficient.
> >>>>>
> >>>>>> +        if (isp_fmt->code == mbus_code)
> >>>>>> +            return true;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return false;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct mali_c55_isp_fmt *
> >>>>>> +mali_c55_isp_get_mbus_config_by_code(u32 code)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_isp_fmts); i++) {
> >>>>>> +        if (mali_c55_isp_fmts[i].code == code)
> >>>>>> +            return &mali_c55_isp_fmts[i];
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return NULL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_isp_stop(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    u32 val;
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST, MALI_C55_INPUT_SAFE_STOP);
> >>>>> 
> >>>>>      mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST,
> >>>>>                 MALI_C55_INPUT_SAFE_STOP);
> >>>>>
> >>>>>> + readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS,
> >>>>>> +               val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_start(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_ctx *ctx = mali_c55_get_active_context(mali_c55);
> >>>>>> +    const struct mali_c55_isp_fmt *cfg;
> >>>>>> +    struct v4l2_mbus_framefmt *format;
> >>>>> 
> >>>>> const
> >>>>>
> >>>>>> +    struct v4l2_subdev_state *state;
> >>>>>> +    struct v4l2_rect *crop;
> >>>>> 
> >>>>> const
> >>>>>
> >>>>>> +    struct v4l2_subdev *sd;
> >>>>>> +    u32 val;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    sd = &mali_c55->isp.sd;
> >>>>> 
> >>>>> Assign when declaring the variable.
> >>>>>
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
> >>>>>> +                 MALI_C55_REG_MCU_CONFIG_WRITE_MASK, 0x01);
> >>>>>> +
> >>>>>> +    /* Apply input windowing */
> >>>>>> +    state = v4l2_subdev_get_locked_active_state(sd);
> >>>>> 
> >>>>> Using .enable_streams() (see below) you'll get this for free.
> >>>>>
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    format = v4l2_subdev_state_get_format(state,
> >>>>>> +                          MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    cfg = mali_c55_isp_get_mbus_config_by_code(format->code);
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_HC_START,
> >>>>>> +               MALI_C55_HC_START(crop->left));
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_HC_SIZE,
> >>>>>> +               MALI_C55_HC_SIZE(crop->width));
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_VC_START_SIZE,
> >>>>>> +               MALI_C55_VC_START(crop->top) |
> >>>>>> +               MALI_C55_VC_SIZE(crop->height));
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR,
> >>>>>> +                 MALI_C55_REG_ACTIVE_WIDTH_MASK, format->width);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR,
> >>>>>> +                 MALI_C55_REG_ACTIVE_HEIGHT_MASK, format->height);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BAYER_ORDER,
> >>>>>> +                 MALI_C55_BAYER_ORDER_MASK, cfg->order);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_INPUT_WIDTH,
> >>>>>> +                 MALI_C55_INPUT_WIDTH_MASK,
> >>>>>> +                 MALI_C55_INPUT_WIDTH_20BIT);
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
> >>>>>> +                 MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK,
> >>>>>> +                 cfg->encoding == V4L2_PIXEL_ENC_RGB ?
> >>>>>> +                 MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK : 0x00);
> >>>>>> +
> >>>>>> +    ret = mali_c55_config_write(ctx, MALI_C55_CONFIG_PING);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to DMA config\n");
> >>>>>> +        return ret;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST,
> >>>>>> +               MALI_C55_INPUT_SAFE_START);
> >>>>>> +    readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS,
> >>>>>> +               val, !val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC);
> >>>>> 
> >>>>> Should you return an error in case of timeout ?
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_isp_stop_stream(struct mali_c55_isp *isp)
> >>>>> 
> >>>>> Why is this not handled wired to .s_stream() ? Or better,
> >>>>> .enable_streams() and .disable_streams().
> >>>>>
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = isp->mali_c55;
> >>>>>> +    struct v4l2_subdev *sd;
> >>>>>> +
> >>>>>> +    if (isp->remote_src) {
> >>>>>> +        sd = media_entity_to_v4l2_subdev(isp->remote_src->entity);
> >>>>>> +        v4l2_subdev_disable_streams(sd, isp->remote_src->index, BIT(0));
> >>>>>> +    }
> >>>>>> +    isp->remote_src = NULL;
> >>>>>> +
> >>>>>> +    mali_c55_isp_stop(mali_c55);
> >>>>>> +}
> >>>>>> +
> >>>>>> +int mali_c55_isp_start_stream(struct mali_c55_isp *isp)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = isp->mali_c55;
> >>>>>> +    struct media_pad *sink_pad;
> >>>>>> +    struct v4l2_subdev *sd;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    sink_pad = &isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO];
> >>>>>> +    isp->remote_src = media_pad_remote_pad_unique(sink_pad);
> >>>>>> +    if (IS_ERR(isp->remote_src)) {
> >>>>> 
> >>>>> If you set the MUST_CONNECT flag for the MALI_C55_ISP_PAD_SINK_VIDEO pad
> >>>>> I think you can drop this check.
> >>>>>
> >>>>>> +        dev_err(mali_c55->dev, "Failed to get source for ISP\n");
> >>>>>> +        return PTR_ERR(isp->remote_src);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    sd = media_entity_to_v4l2_subdev(isp->remote_src->entity);
> >>>>>> +
> >>>>>> +    isp->frame_sequence = 0;
> >>>>>> +    ret = mali_c55_isp_start(mali_c55);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "Failed to start ISP\n");
> >>>>>> +        isp->remote_src = NULL;
> >>>>>> +        return ret;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We only support a single input stream, so we can just enable the 1st
> >>>>>> +     * entry in the streams mask.
> >>>>>> +     */
> >>>>>> +    ret = v4l2_subdev_enable_streams(sd, isp->remote_src->index, BIT(0));
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "Failed to start ISP source\n");
> >>>>>> +        mali_c55_isp_stop(mali_c55);
> >>>>>> +        return ret;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_enum_mbus_code(struct v4l2_subdev *sd,
> >>>>>> +                       struct v4l2_subdev_state *state,
> >>>>>> +                       struct v4l2_subdev_mbus_code_enum *code)
> >>>>>> +{
> >>>>>> +    /*
> >>>>>> +     * Only the internal RGB processed format is allowed on the regular
> >>>>>> +     * processing source pad.
> >>>>>> +     */
> >>>>>> +    if (code->pad == MALI_C55_ISP_PAD_SOURCE) {
> >>>>>> +        if (code->index)
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        code->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* On the sink and bypass pads all the supported formats are allowed. */
> >>>>>> +    if (code->index >= ARRAY_SIZE(mali_c55_isp_fmts))
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    code->code = mali_c55_isp_fmts[code->index].code;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_enum_frame_size(struct v4l2_subdev *sd,
> >>>>>> +                    struct v4l2_subdev_state *state,
> >>>>>> +                    struct v4l2_subdev_frame_size_enum *fse)
> >>>>>> +{
> >>>>>> +    const struct mali_c55_isp_fmt *cfg;
> >>>>>> +
> >>>>>> +    if (fse->index > 0)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Only the internal RGB processed format is allowed on the regular
> >>>>>> +     * processing source pad.
> >>>>>> +     *
> >>>>>> +     * On the sink and bypass pads all the supported formats are allowed.
> >>>>>> +     */
> >>>>>> +    if (fse->pad == MALI_C55_ISP_PAD_SOURCE) {
> >>>>>> +        if (fse->code != MEDIA_BUS_FMT_RGB121212_1X36)
> >>>>>> +            return -EINVAL;
> >>>>>> +    } else {
> >>>>>> +        cfg = mali_c55_isp_get_mbus_config_by_code(fse->code);
> >>>>>> +        if (!cfg)
> >>>>>> +            return -EINVAL;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fse->min_width = MALI_C55_MIN_WIDTH;
> >>>>>> +    fse->min_height = MALI_C55_MIN_HEIGHT;
> >>>>>> +    fse->max_width = MALI_C55_MAX_WIDTH;
> >>>>>> +    fse->max_height = MALI_C55_MAX_HEIGHT;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_set_fmt(struct v4l2_subdev *sd,
> >>>>>> +                struct v4l2_subdev_state *state,
> >>>>>> +                struct v4l2_subdev_format *format)
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *fmt = &format->format;
> >>>>>> +    struct v4l2_mbus_framefmt *src_fmt, *sink_fmt;
> >>>>>> +    const struct mali_c55_isp_fmt *cfg;
> >>>>>> +    struct v4l2_rect *crop;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Disallow set_fmt on the source pads; format is fixed and the sizes
> >>>>>> +     * are the result of applying the sink crop rectangle to the sink
> >>>>>> +     * format.
> >>>>>> +     */
> >>>>>> +    if (format->pad)
> >>>>> 
> >>>>>      if (format->pad != MALI_C55_ISP_PAD_SINK_VIDEO)
> >>>>>
> >>>>>> +        return v4l2_subdev_get_fmt(sd, state, format);
> >>>>>> +
> >>>>>> +    cfg = mali_c55_isp_get_mbus_config_by_code(fmt->code);
> >>>>>> +    if (!cfg)
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>>> +    fmt->field = V4L2_FIELD_NONE;
> >>>>> 
> >>>>> Do you intentionally allow the colorspace fields to be overwritten to
> >>>>> any value ? rkisp1_isp_set_src_fmt() and rkisp1_isp_set_sink_fmt() may
> >>>>> show you how this could be handled.
> >>>>>
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Clamp sizes in the accepted limits and clamp the crop rectangle in
> >>>>>> +     * the new sizes.
> >>>>>> +     */
> >>>>>> +    clamp_t(unsigned int, fmt->width,
> >>>>>> +        MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH);
> >>>>>> +    clamp_t(unsigned int, fmt->width,
> >>>>>> +        MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
> >>>>> 
> >>>>> clamp_t() returns a value, which you ignore. Those are no-ops. You meant
> >>>>>
> >>>>>      fmt->width = clamp_t(unsigned int, fmt->width, MALI_C55_MIN_WIDTH,
> >>>>>                   MALI_C55_MAX_WIDTH);
> >>>>>      fmt->height = clamp_t(unsigned int, fmt->height, MALI_C55_MIN_HEIGHT,
> >>>>>                    MALI_C55_MAX_HEIGHT);
> >>>>>
> >>>>> Same for every use of clamp_t() through the whole driver.
> >>>>>
> >>>>> Also, do you need clamp_t() ? I think all values are unsigned int, you
> >>>>> can use clamp().
> >>>>>
> >>>>> Are there any alignment constraints, such a multiples of two for bayer
> >>>>> formats ? Same in all the other locations where applicable.
> >>>>>
> >>>>>> +
> >>>>>> +    sink_fmt = v4l2_subdev_state_get_format(state,
> >>>>>> +                        MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    *sink_fmt = *fmt;
> >>>>>> +
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    crop->left = 0;
> >>>>>> +    crop->top = 0;
> >>>>>> +    crop->width = fmt->width;
> >>>>>> +    crop->height = fmt->height;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Propagate format to source pads. On the 'regular' output pad use
> >>>>>> +     * the internal RGB processed format, while on the bypass pad simply
> >>>>>> +     * replicate the ISP sink format. The sizes on both pads are the same as
> >>>>>> +     * the ISP sink crop rectangle.
> >>>>>> +     */
> >>>>> 
> >>>>> Colorspace information will need to be propagated too.
> >>>>>
> >>>>>> +    src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SOURCE);
> >>>>>> +    src_fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +    src_fmt->width = crop->width;
> >>>>>> +    src_fmt->height = crop->height;
> >>>>>> +
> >>>>>> +    src_fmt = v4l2_subdev_state_get_format(state,
> >>>>>> +                           MALI_C55_ISP_PAD_SOURCE_BYPASS);
> >>>>>> +    src_fmt->code = fmt->code;
> >>>>>> +    src_fmt->width = crop->width;
> >>>>>> +    src_fmt->height = crop->height;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_get_selection(struct v4l2_subdev *sd,
> >>>>>> +                      struct v4l2_subdev_state *state,
> >>>>>> +                      struct v4l2_subdev_selection *sel)
> >>>>>> +{
> >>>>>> +    if (sel->pad || sel->target != V4L2_SEL_TGT_CROP)
> >>>>> 
> >>>>>      sel->pad != MALI_C55_ISP_PAD_SINK_VIDEO
> >>>>>
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    sel->r = *v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_set_selection(struct v4l2_subdev *sd,
> >>>>>> +                      struct v4l2_subdev_state *state,
> >>>>>> +                      struct v4l2_subdev_selection *sel)
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *src_fmt;
> >>>>>> +    struct v4l2_mbus_framefmt *fmt;
> >>>>> 
> >>>>> const
> >>>>>
> >>>>>> +    struct v4l2_rect *crop;
> >>>>>> +
> >>>>>> +    if (sel->pad || sel->target != V4L2_SEL_TGT_CROP)
> >>>>> 
> >>>>> Ditto.
> >>>>>
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    fmt = v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +
> >>>>>> +    clamp_t(unsigned int, sel->r.left, 0, fmt->width);
> >>>>>> +    clamp_t(unsigned int, sel->r.top, 0, fmt->height);
> >>>>>> +    clamp_t(unsigned int, sel->r.width, MALI_C55_MIN_WIDTH,
> >>>>>> +        fmt->width - sel->r.left);
> >>>>>> +    clamp_t(unsigned int, sel->r.height, MALI_C55_MIN_HEIGHT,
> >>>>>> +        fmt->height - sel->r.top);
> >>>>>> +
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    *crop = sel->r;
> >>>>>> +
> >>>>>> +    /* Propagate the crop rectangle sizes to the source pad format. */
> >>>>>> +    src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SOURCE);
> >>>>>> +    src_fmt->width = crop->width;
> >>>>>> +    src_fmt->height = crop->height;
> >>>>> 
> >>>>> Can you confirm that cropping doesn't affect the bypass path ? And maybe
> >>>>> add a comment to mention it.
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_pad_ops mali_c55_isp_pad_ops = {
> >>>>>> +    .enum_mbus_code        = mali_c55_isp_enum_mbus_code,
> >>>>>> +    .enum_frame_size    = mali_c55_isp_enum_frame_size,
> >>>>>> +    .get_fmt        = v4l2_subdev_get_fmt,
> >>>>>> +    .set_fmt        = mali_c55_isp_set_fmt,
> >>>>>> +    .get_selection        = mali_c55_isp_get_selection,
> >>>>>> +    .set_selection        = mali_c55_isp_set_selection,
> >>>>>> +    .link_validate        = v4l2_subdev_link_validate_default,
> >>>>>> +};
> >>>>>> +
> >>>>>> +void mali_c55_isp_queue_event_sof(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct v4l2_event event = {
> >>>>>> +        .type = V4L2_EVENT_FRAME_SYNC,
> >>>>>> +    };
> >>>>>> +
> >>>>>> +    event.u.frame_sync.frame_sequence = mali_c55->isp.frame_sequence;
> >>>>>> +    v4l2_event_queue(mali_c55->isp.sd.devnode, &event);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int
> >>>>>> +mali_c55_isp_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
> >>>>>> +                 struct v4l2_event_subscription *sub)
> >>>>>> +{
> >>>>>> +    if (sub->type != V4L2_EVENT_FRAME_SYNC)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    /* V4L2_EVENT_FRAME_SYNC doesn't require an id, so zero should be set */
> >>>>>> +    if (sub->id != 0)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    return v4l2_event_subscribe(fh, sub, 0, NULL);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_core_ops mali_c55_isp_core_ops = {
> >>>>>> +    .subscribe_event = mali_c55_isp_subscribe_event,
> >>>>>> +    .unsubscribe_event = v4l2_event_subdev_unsubscribe,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_ops mali_c55_isp_ops = {
> >>>>>> +    .pad    = &mali_c55_isp_pad_ops,
> >>>>>> +    .core    = &mali_c55_isp_core_ops,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_isp_init_state(struct v4l2_subdev *sd,
> >>>>>> +                   struct v4l2_subdev_state *sd_state)
> >>>>> 
> >>>>> You name this variable state in every other subdev operation handler.
> >>>>>
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *sink_fmt, *src_fmt;
> >>>>>> +    struct v4l2_rect *in_crop;
> >>>>>> +
> >>>>>> +    sink_fmt = v4l2_subdev_state_get_format(sd_state,
> >>>>>> +                        MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +    src_fmt = v4l2_subdev_state_get_format(sd_state,
> >>>>>> +                           MALI_C55_ISP_PAD_SOURCE);
> >>>>>> +    in_crop = v4l2_subdev_state_get_crop(sd_state,
> >>>>>> +                         MALI_C55_ISP_PAD_SINK_VIDEO);
> >>>>>> +
> >>>>>> +    sink_fmt->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +    sink_fmt->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +    sink_fmt->field = V4L2_FIELD_NONE;
> >>>>>> +    sink_fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>> 
> >>>>> You should initialize the colorspace fields too. Same below.
> >>>>>
> >>>>>> +
> >>>>>> +    *v4l2_subdev_state_get_format(sd_state,
> >>>>>> +                  MALI_C55_ISP_PAD_SOURCE_BYPASS) = *sink_fmt;
> >>>>>> +
> >>>>>> +    src_fmt->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +    src_fmt->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +    src_fmt->field = V4L2_FIELD_NONE;
> >>>>>> +    src_fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +
> >>>>>> +    in_crop->top = 0;
> >>>>>> +    in_crop->left = 0;
> >>>>>> +    in_crop->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +    in_crop->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = {
> >>>>>> +    .init_state = mali_c55_isp_init_state,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct media_entity_operations mali_c55_isp_media_ops = {
> >>>>>> +    .link_validate        = v4l2_subdev_link_validate,
> >>>>> 
> >>>>>      .link_validate = v4l2_subdev_link_validate,
> >>>>>
> >>>>> to match mali_c55_isp_internal_ops.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_isp_notifier_bound(struct v4l2_async_notifier *notifier,
> >>>>>> +                       struct v4l2_subdev *subdev,
> >>>>>> +                       struct v4l2_async_connection *asc)
> >>>>>> +{
> >>>>>> +    struct mali_c55_isp *isp = container_of(notifier,
> >>>>>> +                        struct mali_c55_isp, notifier);
> >>>>>> +    struct mali_c55 *mali_c55 = isp->mali_c55;
> >>>>>> +    struct media_pad *pad = &isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO];
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * By default we'll flag this link enabled and the TPG disabled, but
> >>>>>> +     * no immutable flag because we need to be able to switch between the
> >>>>>> +     * two.
> >>>>>> +     */
> >>>>>> +    ret = v4l2_create_fwnode_links_to_pad(subdev, pad,
> >>>>>> +                          MEDIA_LNK_FL_ENABLED);
> >>>>>> +    if (ret)
> >>>>>> +        dev_err(mali_c55->dev, "failed to create link for %s\n",
> >>>>>> +            subdev->name);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_isp_notifier_complete(struct v4l2_async_notifier *notifier)
> >>>>>> +{
> >>>>>> +    struct mali_c55_isp *isp = container_of(notifier,
> >>>>>> +                        struct mali_c55_isp, notifier);
> >>>>>> +    struct mali_c55 *mali_c55 = isp->mali_c55;
> >>>>>> +
> >>>>>> +    return v4l2_device_register_subdev_nodes(&mali_c55->v4l2_dev);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_async_notifier_operations mali_c55_isp_notifier_ops = {
> >>>>>> +    .bound = mali_c55_isp_notifier_bound,
> >>>>>> +    .complete = mali_c55_isp_notifier_complete,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_isp_parse_endpoint(struct mali_c55_isp *isp)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = isp->mali_c55;
> >>>>>> +    struct v4l2_async_connection *asc;
> >>>>>> +    struct fwnode_handle *ep;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    v4l2_async_nf_init(&isp->notifier, &mali_c55->v4l2_dev);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The ISP should have a single endpoint pointing to some flavour of
> >>>>>> +     * CSI-2 receiver...but for now at least we do want everything to work
> >>>>>> +     * normally even with no sensors connected, as we have the TPG. If we
> >>>>>> +     * don't find a sensor just warn and return success.
> >>>>>> +     */
> >>>>>> +    ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(mali_c55->dev),
> >>>>>> +                         0, 0, 0);
> >>>>>> +    if (!ep) {
> >>>>>> +        dev_warn(mali_c55->dev, "no local endpoint found\n");
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    asc = v4l2_async_nf_add_fwnode_remote(&isp->notifier, ep,
> >>>>>> +                          struct v4l2_async_connection);
> >>>>>> +    if (IS_ERR(asc)) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to add remote fwnode\n");
> >>>>>> +        ret = PTR_ERR(asc);
> >>>>>> +        goto err_put_ep;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    isp->notifier.ops = &mali_c55_isp_notifier_ops;
> >>>>>> +    ret = v4l2_async_nf_register(&isp->notifier);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "failed to register notifier\n");
> >>>>>> +        goto err_cleanup_nf;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fwnode_handle_put(ep);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_cleanup_nf:
> >>>>>> +    v4l2_async_nf_cleanup(&isp->notifier);
> >>>>>> +err_put_ep:
> >>>>>> +    fwnode_handle_put(ep);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +int mali_c55_register_isp(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_isp *isp = &mali_c55->isp;
> >>>>>> +    struct v4l2_subdev *sd = &isp->sd;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    isp->mali_c55 = mali_c55;
> >>>>>> +
> >>>>>> +    v4l2_subdev_init(sd, &mali_c55_isp_ops);
> >>>>>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS;
> >>>>>> +    sd->entity.ops = &mali_c55_isp_media_ops;
> >>>>>> +    sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
> >>>>>> +    sd->internal_ops = &mali_c55_isp_internal_ops;
> >>>>>> +    strscpy(sd->name, MALI_C55_DRIVER_NAME " isp", sizeof(sd->name));
> >>>>>> +
> >>>>>> +    isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO].flags = MEDIA_PAD_FL_SINK;
> >>>>> 
> >>>>> The MUST_CONNECT flag would make sense here.
> >>>>>
> >>>>>> + isp->pads[MALI_C55_ISP_PAD_SOURCE].flags = MEDIA_PAD_FL_SOURCE;
> >>>>>> +    isp->pads[MALI_C55_ISP_PAD_SOURCE_BYPASS].flags = MEDIA_PAD_FL_SOURCE;
> >>>>>> +
> >>>>>> +    ret = media_entity_pads_init(&sd->entity, MALI_C55_ISP_NUM_PADS,
> >>>>>> +                     isp->pads);
> >>>>>> +    if (ret)
> >>>>>> +        return ret;
> >>>>>> +
> >>>>>> +    ret = v4l2_subdev_init_finalize(sd);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_cleanup_media_entity;
> >>>>>> +
> >>>>>> +    ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_cleanup_subdev;
> >>>>>> +
> >>>>>> +    ret = mali_c55_isp_parse_endpoint(isp);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_cleanup_subdev;
> >>>>> 
> >>>>> As noted elsewhere, I think this belongs to mali-c55-core.c.
> >>>>>
> >>>>>> +
> >>>>>> +    mutex_init(&isp->lock);
> >>>>> 
> >>>>> This lock is used in mali-c55-capture.c only, that seems weird.
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_cleanup_subdev:
> >>>>>> +    v4l2_subdev_cleanup(sd);
> >>>>>> +err_cleanup_media_entity:
> >>>>>> +    media_entity_cleanup(&sd->entity);
> >>>>>> +    isp->mali_c55 = NULL;
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_unregister_isp(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_isp *isp = &mali_c55->isp;
> >>>>>> +
> >>>>>> +    if (!isp->mali_c55)
> >>>>>> +        return;
> >>>>>> +
> >>>>>> +    mutex_destroy(&isp->lock);
> >>>>>> +    v4l2_async_nf_unregister(&isp->notifier);
> >>>>>> +    v4l2_async_nf_cleanup(&isp->notifier);
> >>>>>> +    v4l2_device_unregister_subdev(&isp->sd);
> >>>>>> +    v4l2_subdev_cleanup(&isp->sd);
> >>>>>> +    media_entity_cleanup(&isp->sd.entity);
> >>>>>> +}
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..cb27abde2aa5
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
> >>>>>> @@ -0,0 +1,258 @@
> >>>>>> +/* SPDX-License-Identifier: GPL-2.0 */
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Register definitions
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#ifndef _MALI_C55_REGISTERS_H
> >>>>>> +#define _MALI_C55_REGISTERS_H
> >>>>>> +
> >>>>>> +#include <linux/bits.h>
> >>>>>> +
> >>>>>> +/* ISP Common 0x00000 - 0x000ff */
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_API                0x00000
> >>>>>> +#define MALI_C55_REG_PRODUCT                0x00004
> >>>>>> +#define MALI_C55_REG_VERSION                0x00008
> >>>>>> +#define MALI_C55_REG_REVISION                0x0000c
> >>>>>> +#define MALI_C55_REG_PULSE_MODE                0x0003c
> >>>>>> +#define MALI_C55_REG_INPUT_MODE_REQUEST            0x0009c
> >>>>>> +#define MALI_C55_INPUT_SAFE_STOP            0x00
> >>>>>> +#define MALI_C55_INPUT_SAFE_START            0x01
> >>>>>> +#define MALI_C55_REG_MODE_STATUS            0x000a0
> >>>>>> +#define MALI_C55_REG_INTERRUPT_MASK_VECTOR        0x00030
> >>>>>> +#define MALI_C55_INTERRUPT_MASK_ALL            GENMASK(31, 0)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_GLOBAL_MONITOR            0x00050
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_GEN_VIDEO                0x00080
> >>>>>> +#define MALI_C55_REG_GEN_VIDEO_ON_MASK            BIT(0)
> >>>>>> +#define MALI_C55_REG_GEN_VIDEO_MULTI_MASK        BIT(1)
> >>>>>> +#define MALI_C55_REG_GEN_PREFETCH_MASK GENMASK(31, 16)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_MCU_CONFIG                0x00020
> >>>>>> +#define MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK        BIT(0)
> >>>>> 
> >>>>> #define MALI_C55_REG_MCU_CONFIG_OVERRIDE        BIT(0)
> >>>>>
> >>>>> Same in other places where applicable.
> >>>>>
> >>>>>> +#define MALI_C55_REG_MCU_CONFIG_WRITE_MASK        BIT(1)
> >>>>>> +#define MALI_C55_REG_MCU_CONFIG_WRITE_PING        BIT(1)
> >>>>>> +#define MALI_C55_REG_MCU_CONFIG_WRITE_PONG        0x00
> >>>>>> +#define MALI_C55_REG_MULTI_CONTEXT_MODE_MASK        BIT(8)
> >>>>>> +#define MALI_C55_REG_PING_PONG_READ            0x00024
> >>>>>> +#define MALI_C55_REG_PING_PONG_READ_MASK        BIT(2)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_INTERRUPT_CLEAR_VECTOR        0x00034
> >>>>>> +#define MALI_C55_REG_INTERRUPT_CLEAR            0x00040
> >>>>>> +#define MALI_C55_REG_INTERRUPT_STATUS_VECTOR        0x00044
> >>>>>> +#define MALI_C55_REG_GLOBAL_PARAMETER_STATUS        0x00068
> >>>>>> +#define MALI_C55_GPS_PONG_FITTED            BIT(0)
> >>>>>> +#define MALI_C55_GPS_WDR_FITTED                BIT(1)
> >>>>>> +#define MALI_C55_GPS_COMPRESSION_FITTED            BIT(2)
> >>>>>> +#define MALI_C55_GPS_TEMPER_FITTED            BIT(3)
> >>>>>> +#define MALI_C55_GPS_SINTER_LITE_FITTED            BIT(4)
> >>>>>> +#define MALI_C55_GPS_SINTER_FITTED            BIT(5)
> >>>>>> +#define MALI_C55_GPS_IRIDIX_LTM_FITTED            BIT(6)
> >>>>>> +#define MALI_C55_GPS_IRIDIX_GTM_FITTED            BIT(7)
> >>>>>> +#define MALI_C55_GPS_CNR_FITTED                BIT(8)
> >>>>>> +#define MALI_C55_GPS_FRSCALER_FITTED            BIT(9)
> >>>>>> +#define MALI_C55_GPS_DS_PIPE_FITTED            BIT(10)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_BLANKING                0x00084
> >>>>>> +#define MALI_C55_REG_HBLANK_MASK            GENMASK(15, 0)
> >>>>>> +#define MALI_C55_REG_VBLANK_MASK            GENMASK(31, 16)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_HC_START                0x00088
> >>>>>> +#define MALI_C55_HC_START(h)                (((h) & 0xffff) << 16)
> >>>>>> +#define MALI_C55_REG_HC_SIZE                0x0008c
> >>>>>> +#define MALI_C55_HC_SIZE(h)                ((h) & 0xffff)
> >>>>>> +#define MALI_C55_REG_VC_START_SIZE            0x00094
> >>>>>> +#define MALI_C55_VC_START(v)                ((v) & 0xffff)
> >>>>>> +#define MALI_C55_VC_SIZE(v)                (((v) & 0xffff) << 16)
> >>>>>> +
> >>>>>> +/* Ping/Pong Configuration Space */
> >>>>>> +#define MALI_C55_REG_BASE_ADDR                0x18e88
> >>>>>> +#define MALI_C55_REG_BYPASS_0                0x18eac
> >>>>>> +#define MALI_C55_REG_BYPASS_0_VIDEO_TEST        BIT(0)
> >>>>>> +#define MALI_C55_REG_BYPASS_0_INPUT_FMT            BIT(1)
> >>>>>> +#define MALI_C55_REG_BYPASS_0_DECOMPANDER        BIT(2)
> >>>>>> +#define MALI_C55_REG_BYPASS_0_SENSOR_OFFSET_WDR BIT(3)
> >>>>>> +#define MALI_C55_REG_BYPASS_0_GAIN_WDR            BIT(4)
> >>>>>> +#define MALI_C55_REG_BYPASS_0_FRAME_STITCH        BIT(5)
> >>>>>> +#define MALI_C55_REG_BYPASS_1                0x18eb0
> >>>>>> +#define MALI_C55_REG_BYPASS_1_DIGI_GAIN            BIT(0)
> >>>>>> +#define MALI_C55_REG_BYPASS_1_FE_SENSOR_OFFS        BIT(1)
> >>>>>> +#define MALI_C55_REG_BYPASS_1_FE_SQRT            BIT(2)
> >>>>>> +#define MALI_C55_REG_BYPASS_1_RAW_FE            BIT(3)
> >>>>>> +#define MALI_C55_REG_BYPASS_2                0x18eb8
> >>>>>> +#define MALI_C55_REG_BYPASS_2_SINTER            BIT(0)
> >>>>>> +#define MALI_C55_REG_BYPASS_2_TEMPER            BIT(1)
> >>>>>> +#define MALI_C55_REG_BYPASS_3                0x18ebc
> >>>>>> +#define MALI_C55_REG_BYPASS_3_SQUARE_BE            BIT(0)
> >>>>>> +#define MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH BIT(1)
> >>>>>> +#define MALI_C55_REG_BYPASS_3_MESH_SHADING        BIT(3)
> >>>>>> +#define MALI_C55_REG_BYPASS_3_WHITE_BALANCE        BIT(4)
> >>>>>> +#define MALI_C55_REG_BYPASS_3_IRIDIX            BIT(5)
> >>>>>> +#define MALI_C55_REG_BYPASS_3_IRIDIX_GAIN        BIT(6)
> >>>>>> +#define MALI_C55_REG_BYPASS_4                0x18ec0
> >>>>>> +#define MALI_C55_REG_BYPASS_4_DEMOSAIC_RGB        BIT(1)
> >>>>>> +#define MALI_C55_REG_BYPASS_4_PF_CORRECTION        BIT(3)
> >>>>>> +#define MALI_C55_REG_BYPASS_4_CCM            BIT(4)
> >>>>>> +#define MALI_C55_REG_BYPASS_4_CNR            BIT(5)
> >>>>>> +#define MALI_C55_REG_FR_BYPASS                0x18ec4
> >>>>>> +#define MALI_C55_REG_DS_BYPASS                0x18ec8
> >>>>>> +#define MALI_C55_BYPASS_CROP                BIT(0)
> >>>>>> +#define MALI_C55_BYPASS_SCALER                BIT(1)
> >>>>>> +#define MALI_C55_BYPASS_GAMMA_RGB            BIT(2)
> >>>>>> +#define MALI_C55_BYPASS_SHARPEN                BIT(3)
> >>>>>> +#define MALI_C55_BYPASS_CS_CONV                BIT(4)
> >>>>>> +#define MALI_C55_REG_ISP_RAW_BYPASS            0x18ecc
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK        BIT(0)
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK GENMASK(9, 8)
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_RAW_FR_BYPASS        2
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_RGB_FR_BYPASS        1
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_DS_PIPE_DISABLE BIT(1)
> >>>>>> +#define MALI_C55_ISP_RAW_BYPASS_RAW_BYPASS        BIT(0)
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_ACTIVE_WIDTH_MASK            0xffff
> >>>>>> +#define MALI_C55_REG_ACTIVE_HEIGHT_MASK 0xffff0000
> >>>>>> +#define MALI_C55_REG_BAYER_ORDER            0x18e8c
> >>>>>> +#define MALI_C55_BAYER_ORDER_MASK            GENMASK(1, 0)
> >>>>>> +#define MALI_C55_REG_TPG_CH0                0x18ed8
> >>>>>> +#define MALI_C55_TEST_PATTERN_ON_OFF            BIT(0)
> >>>>>> +#define MALI_C55_TEST_PATTERN_RGB_MASK            BIT(1)
> >>>>>> +#define MALI_C55_REG_TPG_R_BACKGROUND            0x18ee0
> >>>>>> +#define MALI_C55_REG_TPG_G_BACKGROUND            0x18ee4
> >>>>>> +#define MALI_C55_REG_TPG_B_BACKGROUND            0x18ee8
> >>>>>> +#define MALI_C55_TPG_BACKGROUND_MAX            0xfffff
> >>>>>> +#define MALI_C55_REG_INPUT_WIDTH            0x18f98
> >>>>>> +#define MALI_C55_INPUT_WIDTH_MASK            GENMASK(18, 16)
> >>>>>> +#define MALI_C55_INPUT_WIDTH_8BIT            0
> >>>>>> +#define MALI_C55_INPUT_WIDTH_10BIT            1
> >>>>>> +#define MALI_C55_INPUT_WIDTH_12BIT            2
> >>>>>> +#define MALI_C55_INPUT_WIDTH_14BIT            3
> >>>>>> +#define MALI_C55_INPUT_WIDTH_16BIT            4
> >>>>>> +#define MALI_C55_INPUT_WIDTH_20BIT            5
> >>>>>> +#define MALI_C55_REG_SPACE_SIZE                0x4000
> >>>>>> +#define MALI_C55_REG_CONFIG_SPACES_OFFSET        0x0ab6c
> >>>>>> +#define MALI_C55_CONFIG_SPACE_SIZE            0x1231c
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_SINTER_CONFIG            0x19348
> >>>>>> +#define MALI_C55_SINTER_VIEW_FILTER_MASK        GENMASK(1, 0)
> >>>>>> +#define MALI_C55_SINTER_SCALE_MODE_MASK GENMASK(3, 2)
> >>>>>> +#define MALI_C55_SINTER_ENABLE_MASK            BIT(4)
> >>>>>> +#define MALI_C55_SINTER_FILTER_SELECT_MASK        BIT(5)
> >>>>>> +#define MALI_C55_SINTER_INT_SELECT_MASK            BIT(6)
> >>>>>> +#define MALI_C55_SINTER_RM_ENABLE_MASK            BIT(7)
> >>>>>> +
> >>>>>> +/* Colour Correction Matrix Configuration */
> >>>>>> +#define MALI_C55_REG_CCM_ENABLE                0x1b07c
> >>>>>> +#define MALI_C55_CCM_ENABLE_MASK            BIT(0)
> >>>>>> +#define MALI_C55_REG_CCM_COEF_R_R            0x1b080
> >>>>>> +#define MALI_C55_REG_CCM_COEF_R_G            0x1b084
> >>>>>> +#define MALI_C55_REG_CCM_COEF_R_B            0x1b088
> >>>>>> +#define MALI_C55_REG_CCM_COEF_G_R            0x1b090
> >>>>>> +#define MALI_C55_REG_CCM_COEF_G_G            0x1b094
> >>>>>> +#define MALI_C55_REG_CCM_COEF_G_B            0x1b098
> >>>>>> +#define MALI_C55_REG_CCM_COEF_B_R            0x1b0a0
> >>>>>> +#define MALI_C55_REG_CCM_COEF_B_G            0x1b0a4
> >>>>>> +#define MALI_C55_REG_CCM_COEF_B_B            0x1b0a8
> >>>>>> +#define MALI_C55_CCM_COEF_MASK                GENMASK(12, 0)
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_GAIN_R            0x1b0b0
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_GAIN_G            0x1b0b4
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_GAIN_B            0x1b0b8
> >>>>>> +#define MALI_C55_CCM_ANTIFOG_GAIN_MASK GENMASK(11, 0)
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_OFFSET_R        0x1b0c0
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_OFFSET_G        0x1b0c4
> >>>>>> +#define MALI_C55_REG_CCM_ANTIFOG_OFFSET_B        0x1b0c8
> >>>>>> +#define MALI_C55_CCM_ANTIFOG_OFFSET_MASK        GENMASK(11, 0)
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * The Mali-C55 ISP has up to two output pipes; known as full resolution and
> >>>>>> + * down scaled. The register space for these is laid out identically, but offset
> >>>>>> + * by 372 bytes.
> >>>>>> + */
> >>>>>> +#define MALI_C55_CAP_DEV_FR_REG_OFFSET        0x0
> >>>>>> +#define MALI_C55_CAP_DEV_DS_REG_OFFSET        0x174
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_CS_CONV_CONFIG(offset)        (0x1c098 + (offset))
> >>>>>> +#define MALI_C55_CS_CONV_MATRIX_MASK            BIT(0)
> >>>>>> +#define MALI_C55_CS_CONV_FILTER_MASK            BIT(1)
> >>>>>> +#define MALI_C55_CS_CONV_HORZ_DOWNSAMPLE_MASK        BIT(2)
> >>>>>> +#define MALI_C55_CS_CONV_VERT_DOWNSAMPLE_MASK        BIT(3)
> >>>>>> +#define MALI_C55_REG_Y_WRITER_MODE(offset)        (0x1c0ec + (offset))
> >>>>>> +#define MALI_C55_REG_UV_WRITER_MODE(offset)        (0x1c144 + (offset))
> >>>>>> +#define MALI_C55_WRITER_MODE_MASK            GENMASK(4, 0)
> >>>>>> +#define MALI_C55_WRITER_SUBMODE_MASK            GENMASK(7, 6)
> >>>>>> +#define MALI_C55_WRITER_FRAME_WRITE_MASK        BIT(9)
> >>>>>> +#define MALI_C55_REG_ACTIVE_OUT_Y_SIZE(offset) (0x1c0f0 + (offset))
> >>>>>> +#define MALI_C55_REG_ACTIVE_OUT_UV_SIZE(offset) (0x1c148 + (offset))
> >>>>>> +#define MALI_C55_REG_ACTIVE_OUT_SIZE_W(w)        ((w) << 0)
> >>>>>> +#define MALI_C55_REG_ACTIVE_OUT_SIZE_H(h)        ((h) << 16)
> >>>>>> +#define MALI_C55_REG_Y_WRITER_BANKS_BASE(offset) (0x1c0f4 + (offset))
> >>>>>> +#define MALI_C55_REG_Y_WRITER_BANKS_CONFIG(offset) (0x1c108 + (offset))
> >>>>>> +#define MALI_C55_REG_Y_WRITER_MAX_BANKS_MASK GENMASK(2, 0)
> >>>>>> +#define MALI_C55_REG_Y_WRITER_BANKS_RESTART        BIT(3)
> >>>>>> +#define MALI_C55_REG_Y_WRITER_OFFSET(offset) (0x1c10c + (offset))
> >>>>>> +#define MALI_C55_REG_UV_WRITER_BANKS_BASE(offset) (0x1c14c + (offset))
> >>>>>> +#define MALI_C55_REG_UV_WRITER_BANKS_CONFIG(offset) (0x1c160 + (offset))
> >>>>>> +#define MALI_C55_REG_UV_WRITER_MAX_BANKS_MASK GENMASK(2, 0)
> >>>>>> +#define MALI_C55_REG_UV_WRITER_BANKS_RESTART        BIT(3)
> >>>>>> +#define MALI_C55_REG_UV_WRITER_OFFSET(offset) (0x1c164 + (offset))
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_TEST_GEN_CH0_OFF_ON
> >>>>>> +#define MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE 0x18edc
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_CROP_EN(offset)            (0x1c028 + (offset))
> >>>>>> +#define MALI_C55_CROP_ENABLE                BIT(0)
> >>>>>> +#define MALI_C55_REG_CROP_X_START(offset)        (0x1c02c + (offset))
> >>>>>> +#define MALI_C55_REG_CROP_Y_START(offset)        (0x1c030 + (offset))
> >>>>>> +#define MALI_C55_REG_CROP_X_SIZE(offset)        (0x1c034 + (offset))
> >>>>>> +#define MALI_C55_REG_CROP_Y_SIZE(offset)        (0x1c038 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_TIMEOUT_EN(offset) (0x1c040 + (offset))
> >>>>>> +#define MALI_C55_SCALER_TIMEOUT_EN            BIT(4)
> >>>>>> +#define MALI_C55_SCALER_TIMEOUT(t)            ((t) << 16)
> >>>>>> +#define MALI_C55_REG_SCALER_IN_WIDTH(offset) (0x1c044 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_IN_HEIGHT(offset) (0x1c048 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_OUT_WIDTH(offset) (0x1c04c + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_OUT_HEIGHT(offset) (0x1c050 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_HFILT_TINC(offset) (0x1c054 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_HFILT_COEF(offset) (0x1c058 + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_VFILT_TINC(offset) (0x1c05c + (offset))
> >>>>>> +#define MALI_C55_REG_SCALER_VFILT_COEF(offset) (0x1c060 + (offset))
> >>>>>> +
> >>>>>> +#define MALI_C55_REG_GAMMA_RGB_ENABLE(offset) (0x1c064 + (offset))
> >>>>>> +#define MALI_C55_GAMMA_ENABLE_MASK            BIT(0)
> >>>>>> +#define MALI_C55_REG_GAMMA_GAINS_1(offset)        (0x1c068 + (offset))
> >>>>>> +#define MALI_C55_GAMMA_GAIN_R_MASK            GENMASK(11, 0)
> >>>>>> +#define MALI_C55_GAMMA_GAIN_G_MASK            GENMASK(27, 16)
> >>>>>> +#define MALI_C55_REG_GAMMA_GAINS_2(offset)        (0x1c06c + (offset))
> >>>>>> +#define MALI_C55_GAMMA_GAIN_B_MASK            GENMASK(11, 0)
> >>>>>> +#define MALI_C55_REG_GAMMA_OFFSETS_1(offset) (0x1c070 + (offset))
> >>>>>> +#define MALI_C55_GAMMA_OFFSET_R_MASK            GENMASK(11, 0)
> >>>>>> +#define MALI_C55_GAMMA_OFFSET_G_MASK            GENMASK(27, 16)
> >>>>>> +#define MALI_C55_REG_GAMMA_OFFSETS_2(offset) (0x1c074 + (offset))
> >>>>>> +#define MALI_C55_GAMMA_OFFSET_B_MASK            GENMASK(11, 0)
> >>>>>> +
> >>>>>> +/* Output DMA Writer */
> >>>>>> +
> >>>>>> +#define MALI_C55_OUTPUT_DISABLED        0
> >>>>>> +#define MALI_C55_OUTPUT_RGB32            1
> >>>>>> +#define MALI_C55_OUTPUT_A2R10G10B10        2
> >>>>>> +#define MALI_C55_OUTPUT_RGB565            3
> >>>>>> +#define MALI_C55_OUTPUT_RGB24            4
> >>>>>> +#define MALI_C55_OUTPUT_GEN32            5
> >>>>>> +#define MALI_C55_OUTPUT_RAW16            6
> >>>>>> +#define MALI_C55_OUTPUT_AYUV            8
> >>>>>> +#define MALI_C55_OUTPUT_Y410            9
> >>>>>> +#define MALI_C55_OUTPUT_YUY2            10
> >>>>>> +#define MALI_C55_OUTPUT_UYVY            11
> >>>>>> +#define MALI_C55_OUTPUT_Y210            12
> >>>>>> +#define MALI_C55_OUTPUT_NV12_21            13
> >>>>>> +#define MALI_C55_OUTPUT_YUV_420_422        17
> >>>>>> +#define MALI_C55_OUTPUT_P210_P010        19
> >>>>>> +#define MALI_C55_OUTPUT_YUV422            20
> >>>>> 
> >>>>> I'd define this just below the MALI_C55_REG_UV_WRITER_MODE register
> >>>>> macro.
> >>>>>
> >>>>>> +
> >>>>>> +#define MALI_C55_OUTPUT_PLANE_ALT0        0
> >>>>>> +#define MALI_C55_OUTPUT_PLANE_ALT1        1
> >>>>>> +#define MALI_C55_OUTPUT_PLANE_ALT2        2
> >>>>> 
> >>>>> Same here ?
> >>>>>
> >>>>>> +
> >>>>>> +#endif /* _MALI_C55_REGISTERS_H */
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer-coefs.h
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-resizer-coefs.h
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..8edae87f1e5f
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer-coefs.h
> >>>>>> @@ -0,0 +1,382 @@
> >>>>>> +/* SPDX-License-Identifier: GPL-2.0 */
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Resizer Coefficients
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#ifndef _MALI_C55_RESIZER_COEFS_H
> >>>>>> +#define _MALI_C55_RESIZER_COEFS_H
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +
> >>>>>> +#define MALI_C55_RESIZER_COEFS_NUM_BANKS    8
> >>>>>> +#define MALI_C55_RESIZER_COEFS_NUM_ENTRIES    64
> >>>>> 
> >>>>> Do these belongs to mali-c55-registers.h ?
> >>>>>
> >>>>>> +
> >>>>>> +static const unsigned int mali_c55_scaler_h_filter_coefficients[8][64] = {
> >>>>>> +    {    /* Bank 0 */
> >>>>>> +        0x24fc0000, 0x0000fc24, 0x27fc0000, 0x0000fc21,
> >>>>>> +        0x28fc0000, 0x0000fd1f, 0x2cfb0000, 0x0000fd1c,
> >>>>>> +        0x2efb0000, 0x0000fd1a, 0x30fb0000, 0x0000fe17,
> >>>>>> +        0x32fb0000, 0x0000fe15, 0x35fb0000, 0x0000fe12,
> >>>>>> +        0x35fc0000, 0x0000ff10, 0x37fc0000, 0x0000ff0e,
> >>>>>> +        0x39fc0000, 0x0000ff0c, 0x3afd0000, 0x0000ff0a,
> >>>>>> +        0x3afe0000, 0x00000008, 0x3cfe0000, 0x00000006,
> >>>>>> +        0x3dff0000, 0x00000004, 0x3d000000, 0x00000003,
> >>>>>> +        0x3c020000, 0x00000002, 0x3d030000, 0x00000000,
> >>>>>> +        0x3d040000, 0x000000ff, 0x3c060000, 0x000000fe,
> >>>>>> +        0x3a080000, 0x000000fe, 0x3a0aff00, 0x000000fd,
> >>>>>> +        0x390cff00, 0x000000fc, 0x370eff00, 0x000000fc,
> >>>>>> +        0x3510ff00, 0x000000fc, 0x3512fe00, 0x000000fb,
> >>>>>> +        0x3215fe00, 0x000000fb, 0x3017fe00, 0x000000fb,
> >>>>>> +        0x2e1afd00, 0x000000fb, 0x2c1cfd00, 0x000000fb,
> >>>>>> +        0x281ffd00, 0x000000fc, 0x2721fc00, 0x000000fc,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 1 */
> >>>>>> +        0x25fb0000, 0x0000fb25, 0x27fb0000, 0x0000fb23,
> >>>>>> +        0x29fb0000, 0x0000fb21, 0x2afc0000, 0x0000fb1f,
> >>>>>> +        0x2cfc0000, 0x0000fb1d, 0x2efc0000, 0x0000fb1b,
> >>>>>> +        0x2ffd0000, 0x0000fb19, 0x2ffe0000, 0x0000fc17,
> >>>>>> +        0x31fe0000, 0x0000fc15, 0x32ff0000, 0x0000fc13,
> >>>>>> +        0x3400ff00, 0x0000fc11, 0x3301ff00, 0x0000fd10,
> >>>>>> +        0x3402ff00, 0x0000fd0e, 0x3503ff00, 0x0000fd0c,
> >>>>>> +        0x3505ff00, 0x0000fd0a, 0x3506fe00, 0x0000fe09,
> >>>>>> +        0x3607fe00, 0x0000fe07, 0x3509fe00, 0x0000fe06,
> >>>>>> +        0x350afd00, 0x0000ff05, 0x350cfd00, 0x0000ff03,
> >>>>>> +        0x340efd00, 0x0000ff02, 0x3310fd00, 0x0000ff01,
> >>>>>> +        0x3411fc00, 0x0000ff00, 0x3213fc00, 0x000000ff,
> >>>>>> +        0x3115fc00, 0x000000fe, 0x2f17fc00, 0x000000fe,
> >>>>>> +        0x2f19fb00, 0x000000fd, 0x2e1bfb00, 0x000000fc,
> >>>>>> +        0x2c1dfb00, 0x000000fc, 0x2a1ffb00, 0x000000fc,
> >>>>>> +        0x2921fb00, 0x000000fb, 0x2723fb00, 0x000000fb,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 2 */
> >>>>>> +        0x1f010000, 0x0000011f, 0x21010000, 0x0000001e,
> >>>>>> +        0x21020000, 0x0000001d, 0x22020000, 0x0000001c,
> >>>>>> +        0x23030000, 0x0000ff1b, 0x2404ff00, 0x0000ff1a,
> >>>>>> +        0x2504ff00, 0x0000ff19, 0x2505ff00, 0x0000ff18,
> >>>>>> +        0x2606ff00, 0x0000fe17, 0x2607ff00, 0x0000fe16,
> >>>>>> +        0x2708ff00, 0x0000fe14, 0x2709ff00, 0x0000fe13,
> >>>>>> +        0x270aff00, 0x0000fe12, 0x280bfe00, 0x0000fe11,
> >>>>>> +        0x280cfe00, 0x0000fe10, 0x280dfe00, 0x0000fe0f,
> >>>>>> +        0x280efe00, 0x0000fe0e, 0x280ffe00, 0x0000fe0d,
> >>>>>> +        0x2810fe00, 0x0000fe0c, 0x2811fe00, 0x0000fe0b,
> >>>>>> +        0x2712fe00, 0x0000ff0a, 0x2713fe00, 0x0000ff09,
> >>>>>> +        0x2714fe00, 0x0000ff08, 0x2616fe00, 0x0000ff07,
> >>>>>> +        0x2617fe00, 0x0000ff06, 0x2518ff00, 0x0000ff05,
> >>>>>> +        0x2519ff00, 0x0000ff04, 0x241aff00, 0x0000ff04,
> >>>>>> +        0x231bff00, 0x00000003, 0x221c0000, 0x00000002,
> >>>>>> +        0x211d0000, 0x00000002, 0x211e0000, 0x00000001,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 3 */
> >>>>>> +        0x1b06ff00, 0x00ff061b, 0x1b07ff00, 0x00ff061a,
> >>>>>> +        0x1c07ff00, 0x00ff051a, 0x1c08ff00, 0x00ff0519,
> >>>>>> +        0x1c09ff00, 0x00ff0419, 0x1d09ff00, 0x00ff0418,
> >>>>>> +        0x1e0aff00, 0x00ff0317, 0x1e0aff00, 0x00ff0317,
> >>>>>> +        0x1e0bff00, 0x00ff0316, 0x1f0cff00, 0x00ff0215,
> >>>>>> +        0x1e0cff00, 0x00000215, 0x1e0dff00, 0x00000214,
> >>>>>> +        0x1e0e0000, 0x00000113, 0x1e0e0000, 0x00000113,
> >>>>>> +        0x1e0f0000, 0x00000112, 0x1f100000, 0x00000011,
> >>>>>> +        0x20100000, 0x00000010, 0x1f110000, 0x00000010,
> >>>>>> +        0x1e120100, 0x0000000f, 0x1e130100, 0x0000000e,
> >>>>>> +        0x1e130100, 0x0000000e, 0x1e140200, 0x0000ff0d,
> >>>>>> +        0x1e150200, 0x0000ff0c, 0x1f1502ff, 0x0000ff0c,
> >>>>>> +        0x1e1603ff, 0x0000ff0b, 0x1e1703ff, 0x0000ff0a,
> >>>>>> +        0x1e1703ff, 0x0000ff0a, 0x1d1804ff, 0x0000ff09,
> >>>>>> +        0x1c1904ff, 0x0000ff09, 0x1c1905ff, 0x0000ff08,
> >>>>>> +        0x1c1a05ff, 0x0000ff07, 0x1b1a06ff, 0x0000ff07,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 4 */
> >>>>>> +        0x17090000, 0x00000917, 0x18090000, 0x00000916,
> >>>>>> +        0x170a0100, 0x00000816, 0x170a0100, 0x00000816,
> >>>>>> +        0x180b0100, 0x00000715, 0x180b0100, 0x00000715,
> >>>>>> +        0x170c0100, 0x00000715, 0x190c0100, 0x00000614,
> >>>>>> +        0x180d0100, 0x00000614, 0x190d0200, 0x00000513,
> >>>>>> +        0x180e0200, 0x00000513, 0x180e0200, 0x00000513,
> >>>>>> +        0x1a0e0200, 0x00000412, 0x190f0200, 0x00000412,
> >>>>>> +        0x190f0300, 0x00000411, 0x18100300, 0x00000411,
> >>>>>> +        0x1a100300, 0x00000310, 0x18110400, 0x00000310,
> >>>>>> +        0x19110400, 0x0000030f, 0x19120400, 0x0000020f,
> >>>>>> +        0x1a120400, 0x0000020e, 0x18130500, 0x0000020e,
> >>>>>> +        0x18130500, 0x0000020e, 0x19130500, 0x0000020d,
> >>>>>> +        0x18140600, 0x0000010d, 0x19140600, 0x0000010c,
> >>>>>> +        0x17150700, 0x0000010c, 0x18150700, 0x0000010b,
> >>>>>> +        0x18150700, 0x0000010b, 0x17160800, 0x0000010a,
> >>>>>> +        0x17160800, 0x0000010a, 0x18160900, 0x00000009,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 5 */
> >>>>>> +        0x120b0300, 0x00030b12, 0x120c0300, 0x00030b11,
> >>>>>> +        0x110c0400, 0x00030b11, 0x110c0400, 0x00030b11,
> >>>>>> +        0x130c0400, 0x00020a11, 0x120d0400, 0x00020a11,
> >>>>>> +        0x110d0500, 0x00020a11, 0x110d0500, 0x00020a11,
> >>>>>> +        0x130d0500, 0x00010911, 0x130e0500, 0x00010910,
> >>>>>> +        0x120e0600, 0x00010910, 0x120e0600, 0x00010910,
> >>>>>> +        0x130e0600, 0x00010810, 0x120f0600, 0x00010810,
> >>>>>> +        0x120f0700, 0x00000810, 0x130f0700, 0x0000080f,
> >>>>>> +        0x140f0700, 0x0000070f, 0x130f0800, 0x0000070f,
> >>>>>> +        0x12100800, 0x0000070f, 0x12100801, 0x0000060f,
> >>>>>> +        0x13100801, 0x0000060e, 0x12100901, 0x0000060e,
> >>>>>> +        0x12100901, 0x0000060e, 0x13100901, 0x0000050e,
> >>>>>> +        0x13110901, 0x0000050d, 0x11110a02, 0x0000050d,
> >>>>>> +        0x11110a02, 0x0000050d, 0x12110a02, 0x0000040d,
> >>>>>> +        0x13110a02, 0x0000040c, 0x11110b03, 0x0000040c,
> >>>>>> +        0x11110b03, 0x0000040c, 0x12110b03, 0x0000030c,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 6 */
> >>>>>> +        0x0b0a0805, 0x00080a0c, 0x0b0a0805, 0x00080a0c,
> >>>>>> +        0x0c0a0805, 0x00080a0b, 0x0c0a0805, 0x00080a0b,
> >>>>>> +        0x0d0a0805, 0x00070a0b, 0x0d0a0805, 0x00070a0b,
> >>>>>> +        0x0d0a0805, 0x00070a0b, 0x0c0a0806, 0x00070a0b,
> >>>>>> +        0x0b0b0806, 0x00070a0b, 0x0c0b0806, 0x0007090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0b0b0906, 0x0007090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0b0b0906, 0x0007090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0c0b0906, 0x0006090b,
> >>>>>> +        0x0c0b0906, 0x0006090b, 0x0c0b0906, 0x0006090b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0b0b0907, 0x0006090b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0b0b0907, 0x0006090b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0c0b0907, 0x0006080b,
> >>>>>> +        0x0b0b0a07, 0x0006080b, 0x0c0b0a07, 0x0006080a,
> >>>>>> +        0x0d0b0a07, 0x0005080a, 0x0d0b0a07, 0x0005080a,
> >>>>>> +        0x0d0b0a07, 0x0005080a, 0x0c0b0a08, 0x0005080a,
> >>>>>> +        0x0c0b0a08, 0x0005080a, 0x0c0b0a08, 0x0005080a,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 7 */
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +        0x0909090a, 0x00090909, 0x0909090a, 0x00090909,
> >>>>>> +    }
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const unsigned int mali_c55_scaler_v_filter_coefficients[8][64] = {
> >>>>>> +    {    /* Bank 0 */
> >>>>>> +        0x2424fc00, 0x000000fc, 0x2721fc00, 0x000000fc,
> >>>>>> +        0x281ffd00, 0x000000fc, 0x2c1cfd00, 0x000000fb,
> >>>>>> +        0x2e1afd00, 0x000000fb, 0x3017fe00, 0x000000fb,
> >>>>>> +        0x3215fe00, 0x000000fb, 0x3512fe00, 0x000000fb,
> >>>>>> +        0x3510ff00, 0x000000fc, 0x370eff00, 0x000000fc,
> >>>>>> +        0x390cff00, 0x000000fc, 0x3a0aff00, 0x000000fd,
> >>>>>> +        0x3a080000, 0x000000fe, 0x3c060000, 0x000000fe,
> >>>>>> +        0x3d040000, 0x000000ff, 0x3d030000, 0x00000000,
> >>>>>> +        0x3c020000, 0x00000002, 0x3d000000, 0x00000003,
> >>>>>> +        0x3dff0000, 0x00000004, 0x3cfe0000, 0x00000006,
> >>>>>> +        0x3afe0000, 0x00000008, 0x3afd0000, 0x0000ff0a,
> >>>>>> +        0x39fc0000, 0x0000ff0c, 0x37fc0000, 0x0000ff0e,
> >>>>>> +        0x35fc0000, 0x0000ff10, 0x35fb0000, 0x0000fe12,
> >>>>>> +        0x32fb0000, 0x0000fe15, 0x30fb0000, 0x0000fe17,
> >>>>>> +        0x2efb0000, 0x0000fd1a, 0x2cfb0000, 0x0000fd1c,
> >>>>>> +        0x28fc0000, 0x0000fd1f, 0x27fc0000, 0x0000fc21,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 1 */
> >>>>>> +        0x2525fb00, 0x000000fb, 0x2723fb00, 0x000000fb,
> >>>>>> +        0x2921fb00, 0x000000fb, 0x2a1ffb00, 0x000000fc,
> >>>>>> +        0x2c1dfb00, 0x000000fc, 0x2e1bfb00, 0x000000fc,
> >>>>>> +        0x2f19fb00, 0x000000fd, 0x2f17fc00, 0x000000fe,
> >>>>>> +        0x3115fc00, 0x000000fe, 0x3213fc00, 0x000000ff,
> >>>>>> +        0x3411fc00, 0x0000ff00, 0x3310fd00, 0x0000ff01,
> >>>>>> +        0x340efd00, 0x0000ff02, 0x350cfd00, 0x0000ff03,
> >>>>>> +        0x350afd00, 0x0000ff05, 0x3509fe00, 0x0000fe06,
> >>>>>> +        0x3607fe00, 0x0000fe07, 0x3506fe00, 0x0000fe09,
> >>>>>> +        0x3505ff00, 0x0000fd0a, 0x3503ff00, 0x0000fd0c,
> >>>>>> +        0x3402ff00, 0x0000fd0e, 0x3301ff00, 0x0000fd10,
> >>>>>> +        0x3400ff00, 0x0000fc11, 0x32ff0000, 0x0000fc13,
> >>>>>> +        0x31fe0000, 0x0000fc15, 0x2ffe0000, 0x0000fc17,
> >>>>>> +        0x2ffd0000, 0x0000fb19, 0x2efc0000, 0x0000fb1b,
> >>>>>> +        0x2cfc0000, 0x0000fb1d, 0x2afc0000, 0x0000fb1f,
> >>>>>> +        0x29fb0000, 0x0000fb21, 0x27fb0000, 0x0000fb23,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 2 */
> >>>>>> +        0x1f1f0100, 0x00000001, 0x211e0000, 0x00000001,
> >>>>>> +        0x211d0000, 0x00000002, 0x221c0000, 0x00000002,
> >>>>>> +        0x231bff00, 0x00000003, 0x241aff00, 0x0000ff04,
> >>>>>> +        0x2519ff00, 0x0000ff04, 0x2518ff00, 0x0000ff05,
> >>>>>> +        0x2617fe00, 0x0000ff06, 0x2616fe00, 0x0000ff07,
> >>>>>> +        0x2714fe00, 0x0000ff08, 0x2713fe00, 0x0000ff09,
> >>>>>> +        0x2712fe00, 0x0000ff0a, 0x2811fe00, 0x0000fe0b,
> >>>>>> +        0x2810fe00, 0x0000fe0c, 0x280ffe00, 0x0000fe0d,
> >>>>>> +        0x280efe00, 0x0000fe0e, 0x280dfe00, 0x0000fe0f,
> >>>>>> +        0x280cfe00, 0x0000fe10, 0x280bfe00, 0x0000fe11,
> >>>>>> +        0x270aff00, 0x0000fe12, 0x2709ff00, 0x0000fe13,
> >>>>>> +        0x2708ff00, 0x0000fe14, 0x2607ff00, 0x0000fe16,
> >>>>>> +        0x2606ff00, 0x0000fe17, 0x2505ff00, 0x0000ff18,
> >>>>>> +        0x2504ff00, 0x0000ff19, 0x2404ff00, 0x0000ff1a,
> >>>>>> +        0x23030000, 0x0000ff1b, 0x22020000, 0x0000001c,
> >>>>>> +        0x21020000, 0x0000001d, 0x21010000, 0x0000001e,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 3 */
> >>>>>> +        0x1b1b06ff, 0x0000ff06, 0x1b1a06ff, 0x0000ff07,
> >>>>>> +        0x1c1a05ff, 0x0000ff07, 0x1c1905ff, 0x0000ff08,
> >>>>>> +        0x1c1904ff, 0x0000ff09, 0x1d1804ff, 0x0000ff09,
> >>>>>> +        0x1e1703ff, 0x0000ff0a, 0x1e1703ff, 0x0000ff0a,
> >>>>>> +        0x1e1603ff, 0x0000ff0b, 0x1f1502ff, 0x0000ff0c,
> >>>>>> +        0x1e150200, 0x0000ff0c, 0x1e140200, 0x0000ff0d,
> >>>>>> +        0x1e130100, 0x0000000e, 0x1e130100, 0x0000000e,
> >>>>>> +        0x1e120100, 0x0000000f, 0x1f110000, 0x00000010,
> >>>>>> +        0x20100000, 0x00000010, 0x1f100000, 0x00000011,
> >>>>>> +        0x1e0f0000, 0x00000112, 0x1e0e0000, 0x00000113,
> >>>>>> +        0x1e0e0000, 0x00000113, 0x1e0dff00, 0x00000214,
> >>>>>> +        0x1e0cff00, 0x00000215, 0x1f0cff00, 0x00ff0215,
> >>>>>> +        0x1e0bff00, 0x00ff0316, 0x1e0aff00, 0x00ff0317,
> >>>>>> +        0x1e0aff00, 0x00ff0317, 0x1d09ff00, 0x00ff0418,
> >>>>>> +        0x1c09ff00, 0x00ff0419, 0x1c08ff00, 0x00ff0519,
> >>>>>> +        0x1c07ff00, 0x00ff051a, 0x1b07ff00, 0x00ff061a,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 4 */
> >>>>>> +        0x17170900, 0x00000009, 0x18160900, 0x00000009,
> >>>>>> +        0x17160800, 0x0000010a, 0x17160800, 0x0000010a,
> >>>>>> +        0x18150700, 0x0000010b, 0x18150700, 0x0000010b,
> >>>>>> +        0x17150700, 0x0000010c, 0x19140600, 0x0000010c,
> >>>>>> +        0x18140600, 0x0000010d, 0x19130500, 0x0000020d,
> >>>>>> +        0x18130500, 0x0000020e, 0x18130500, 0x0000020e,
> >>>>>> +        0x1a120400, 0x0000020e, 0x19120400, 0x0000020f,
> >>>>>> +        0x19110400, 0x0000030f, 0x18110400, 0x00000310,
> >>>>>> +        0x1a100300, 0x00000310, 0x18100300, 0x00000411,
> >>>>>> +        0x190f0300, 0x00000411, 0x190f0200, 0x00000412,
> >>>>>> +        0x1a0e0200, 0x00000412, 0x180e0200, 0x00000513,
> >>>>>> +        0x180e0200, 0x00000513, 0x190d0200, 0x00000513,
> >>>>>> +        0x180d0100, 0x00000614, 0x190c0100, 0x00000614,
> >>>>>> +        0x170c0100, 0x00000715, 0x180b0100, 0x00000715,
> >>>>>> +        0x180b0100, 0x00000715, 0x170a0100, 0x00000816,
> >>>>>> +        0x170a0100, 0x00000816, 0x18090000, 0x00000916,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 5 */
> >>>>>> +        0x12120b03, 0x0000030b, 0x12110b03, 0x0000030c,
> >>>>>> +        0x11110b03, 0x0000040c, 0x11110b03, 0x0000040c,
> >>>>>> +        0x13110a02, 0x0000040c, 0x12110a02, 0x0000040d,
> >>>>>> +        0x11110a02, 0x0000050d, 0x11110a02, 0x0000050d,
> >>>>>> +        0x13110901, 0x0000050d, 0x13100901, 0x0000050e,
> >>>>>> +        0x12100901, 0x0000060e, 0x12100901, 0x0000060e,
> >>>>>> +        0x13100801, 0x0000060e, 0x12100801, 0x0000060f,
> >>>>>> +        0x12100800, 0x0000070f, 0x130f0800, 0x0000070f,
> >>>>>> +        0x140f0700, 0x0000070f, 0x130f0700, 0x0000080f,
> >>>>>> +        0x120f0700, 0x00000810, 0x120f0600, 0x00010810,
> >>>>>> +        0x130e0600, 0x00010810, 0x120e0600, 0x00010910,
> >>>>>> +        0x120e0600, 0x00010910, 0x130e0500, 0x00010910,
> >>>>>> +        0x130d0500, 0x00010911, 0x110d0500, 0x00020a11,
> >>>>>> +        0x110d0500, 0x00020a11, 0x120d0400, 0x00020a11,
> >>>>>> +        0x130c0400, 0x00020a11, 0x110c0400, 0x00030b11,
> >>>>>> +        0x110c0400, 0x00030b11, 0x120c0300, 0x00030b11,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 6 */
> >>>>>> +        0x0b0c0a08, 0x0005080a, 0x0b0c0a08, 0x0005080a,
> >>>>>> +        0x0c0b0a08, 0x0005080a, 0x0c0b0a08, 0x0005080a,
> >>>>>> +        0x0d0b0a07, 0x0005080a, 0x0d0b0a07, 0x0005080a,
> >>>>>> +        0x0d0b0a07, 0x0005080a, 0x0c0b0a07, 0x0006080a,
> >>>>>> +        0x0b0b0a07, 0x0006080b, 0x0c0b0907, 0x0006080b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0b0b0907, 0x0006090b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0b0b0907, 0x0006090b,
> >>>>>> +        0x0b0b0907, 0x0006090b, 0x0c0b0906, 0x0006090b,
> >>>>>> +        0x0c0b0906, 0x0006090b, 0x0c0b0906, 0x0006090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0b0b0906, 0x0007090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0b0b0906, 0x0007090b,
> >>>>>> +        0x0b0b0906, 0x0007090b, 0x0c0b0806, 0x0007090b,
> >>>>>> +        0x0b0b0806, 0x00070a0b, 0x0c0a0806, 0x00070a0b,
> >>>>>> +        0x0d0a0805, 0x00070a0b, 0x0d0a0805, 0x00070a0b,
> >>>>>> +        0x0d0a0805, 0x00070a0b, 0x0c0a0805, 0x00080a0b,
> >>>>>> +        0x0c0a0805, 0x00080a0b, 0x0c0a0805, 0x00080a0b,
> >>>>>> +    },
> >>>>>> +    {    /* Bank 7 */
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +        0x09090909, 0x000a0909, 0x09090909, 0x000a0909,
> >>>>>> +    }
> >>>>>> +};
> >>>>>> +
> >>>>>> +struct mali_c55_resizer_coef_bank {
> >>>>>> +    unsigned int bank;
> >>>>> 
> >>>>> This is always equal to the index of the entry in the
> >>>>> mali_c55_coefficient_banks array, you can drop it.
> >>>>>
> >>>>>> +    unsigned int top;
> >>>>>> +    unsigned int bottom;
> >>>>> 
> >>>>> The bottom value of bank N is always equal to the top value of bank N+1.
> >>>>> You can simplify this by storing a single value.
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct mali_c55_resizer_coef_bank mali_c55_coefficient_banks[] = {
> >>>>>> +    {
> >>>>>> +        .bank = 0,
> >>>>>> +        .top = 1000,
> >>>>>> +        .bottom = 770,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 1,
> >>>>>> +        .top = 769,
> >>>>>> +        .bottom = 600,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 2,
> >>>>>> +        .top = 599,
> >>>>>> +        .bottom = 460,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 3,
> >>>>>> +        .top = 459,
> >>>>>> +        .bottom = 354,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 4,
> >>>>>> +        .top = 353,
> >>>>>> +        .bottom = 273,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 5,
> >>>>>> +        .top = 272,
> >>>>>> +        .bottom = 210,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 6,
> >>>>>> +        .top = 209,
> >>>>>> +        .bottom = 162,
> >>>>>> +    },
> >>>>>> +    {
> >>>>>> +        .bank = 7,
> >>>>>> +        .top = 161,
> >>>>>> +        .bottom = 125,
> >>>>>> +    },
> >>>>>> +};
> >>>>>> +
> >>>>> 
> >>>>> A small comment would be nice, such as
> >>>>>
> >>>>> /* Select a bank of resizer coefficients, based on the scaling ratio. */
> >>>>>
> >>>>>> +static unsigned int mali_c55_calculate_bank_num(struct mali_c55 *mali_c55,
> >>>>> 
> >>>>> This function is related to the resizers. Add "rsz" somewhere in the
> >>>>> function name, and pass a resizer pointer.
> >>>>>
> >>>>>> +                        unsigned int crop,
> >>>>>> +                        unsigned int scale)
> >>>>> 
> >>>>> I think those are the input and output sizes to the scaler. Rename them
> >>>>> to make it clearer.
> >>>>>
> >>>>>> +{
> >>>>>> +    unsigned int tmp;
> >>>>> 
> >>>>> tmp is almost always a bad variable name. Please use a more descriptive
> >>>>> name, size as rsz_ratio.
> >>>>>
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    tmp = (scale * 1000U) / crop;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_coefficient_banks); i++) {
> >>>>>> +        if (tmp >= mali_c55_coefficient_banks[i].bottom &&
> >>>>>> +            tmp <= mali_c55_coefficient_banks[i].top)
> >>>>>> +            return mali_c55_coefficient_banks[i].bank;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We shouldn't ever get here, in theory. As we have no good choices
> >>>>>> +     * simply warn the user and use the first bank of coefficients.
> >>>>>> +     */
> >>>>>> +    dev_warn(mali_c55->dev, "scaling factor outside defined bounds\n");
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>> 
> >>>>> And everything else belongs to mali-c55-resizer.c. Drop this header
> >>>>> file.
> >>>>>
> >>>>>> +
> >>>>>> +#endif /* _MALI_C55_RESIZER_COEFS_H */
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..0a5a2969d3ce
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
> >>>>>> @@ -0,0 +1,779 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Image signal processor
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/math.h>
> >>>>>> +#include <linux/minmax.h>
> >>>>>> +
> >>>>>> +#include <media/media-entity.h>
> >>>>>> +#include <media/v4l2-subdev.h>
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +#include "mali-c55-registers.h"
> >>>>>> +#include "mali-c55-resizer-coefs.h"
> >>>>>> +
> >>>>>> +/* Scaling factor in Q4.20 format. */
> >>>>>> +#define MALI_C55_RZR_SCALER_FACTOR    (1U << 20)
> >>>>>> +
> >>>>>> +static const u32 rzr_non_bypass_src_fmts[] = {
> >>>>>> +    MEDIA_BUS_FMT_RGB121212_1X36,
> >>>>>> +    MEDIA_BUS_FMT_YUV10_1X30
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const char * const mali_c55_resizer_names[] = {
> >>>>>> +    [MALI_C55_RZR_FR] = "resizer fr",
> >>>>>> +    [MALI_C55_RZR_DS] = "resizer ds",
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_program_crop(struct mali_c55_resizer *rzr,
> >>>>>> +                     struct v4l2_subdev_state *state)
> >>>>>> +{
> >>>>>> +    unsigned int reg_offset = rzr->cap_dev->reg_offset;
> >>>>>> +    struct mali_c55 *mali_c55 = rzr->mali_c55;
> >>>>>> +    struct v4l2_mbus_framefmt *fmt;
> >>>>>> +    struct v4l2_rect *crop;
> >>>> 
> >>>> const
> >>>>
> >>>>>> +
> >>>>>> +    /* Verify if crop should be enabled. */
> >>>>>> +    fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD, 0);
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0);
> >>>>>> +
> >>>>>> +    if (fmt->width == crop->width && fmt->height == crop->height)
> >>>>>> +        return MALI_C55_BYPASS_CROP;
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_START(reg_offset),
> >>>>>> +               crop->left);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_START(reg_offset),
> >>>>>> +               crop->top);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CROP_X_SIZE(reg_offset),
> >>>>>> +               crop->width);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CROP_Y_SIZE(reg_offset),
> >>>>>> +               crop->height);
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_CROP_EN(reg_offset),
> >>>>>> +               MALI_C55_CROP_ENABLE);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_program_resizer(struct mali_c55_resizer *rzr,
> >>>>>> +                    struct v4l2_subdev_state *state)
> >>>>>> +{
> >>>>>> +    unsigned int reg_offset = rzr->cap_dev->reg_offset;
> >>>>>> +    struct mali_c55 *mali_c55 = rzr->mali_c55;
> >>>>>> +    struct v4l2_rect *crop, *scale;
> >>>> 
> >>>> const
> >>>>
> >>>> Once "[PATCH v4 0/3] media: v4l2-subdev: Support const-awareness in
> >>>> state accessors" gets merged, the state argument to this function can be
> >>>> made const too. Same for other functions, as applicable.
> >>>>
> >>>>>> +    unsigned int h_bank, v_bank;
> >>>>>> +    u64 h_scale, v_scale;
> >>>>>> +
> >>>>>> +    /* Verify if scaling should be enabled. */
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD, 0);
> >>>>>> +    scale = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD, 0);
> >>>>>> +
> >>>>>> +    if (crop->width == scale->width && crop->height == scale->height)
> >>>>>> +        return MALI_C55_BYPASS_SCALER;
> >>>>>> +
> >>>>>> +    /* Program the V/H scaling factor in Q4.20 format. */
> >>>>>> +    h_scale = crop->width * MALI_C55_RZR_SCALER_FACTOR;
> >>>>>> +    v_scale = crop->height * MALI_C55_RZR_SCALER_FACTOR;
> >>>>>> +
> >>>>>> +    do_div(h_scale, scale->width);
> >>>>>> +    do_div(v_scale, scale->height);
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_IN_WIDTH(reg_offset),
> >>>>>> +               crop->width);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_IN_HEIGHT(reg_offset),
> >>>>>> +               crop->height);
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_OUT_WIDTH(reg_offset),
> >>>>>> +               scale->width);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_OUT_HEIGHT(reg_offset),
> >>>>>> +               scale->height);
> >>>>>> +
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_HFILT_TINC(reg_offset),
> >>>>>> +               h_scale);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_VFILT_TINC(reg_offset),
> >>>>>> +               v_scale);
> >>>>>> +
> >>>>>> +    h_bank = mali_c55_calculate_bank_num(mali_c55, crop->width,
> >>>>>> +                         scale->width);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_HFILT_COEF(reg_offset),
> >>>>>> +               h_bank);
> >>>>>> +
> >>>>>> +    v_bank = mali_c55_calculate_bank_num(mali_c55, crop->height,
> >>>>>> +                         scale->height);
> >>>>>> +    mali_c55_write(mali_c55,
> >>>>>> +               MALI_C55_REG_SCALER_VFILT_COEF(reg_offset),
> >>>>>> +               v_bank);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static void mali_c55_rzr_program(struct mali_c55_resizer *rzr,
> >>>>>> +                 struct v4l2_subdev_state *state)
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = rzr->mali_c55;
> >>>>>> +    u32 bypass = 0;
> >>>>>> +
> >>>>>> +    /* Verify if cropping and scaling should be enabled. */
> >>>>>> +    bypass |= mali_c55_rzr_program_crop(rzr, state);
> >>>>>> +    bypass |= mali_c55_rzr_program_resizer(rzr, state);
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, rzr->id == MALI_C55_RZR_FR ?
> >>>>>> +                 MALI_C55_REG_FR_BYPASS : MALI_C55_REG_DS_BYPASS,
> >>>>>> +                 MALI_C55_BYPASS_CROP | MALI_C55_BYPASS_SCALER,
> >>>>>> +                 bypass);
> >>>>>> +}
> >>>>>> +
> >>>>>> +/*
> >>>>>> + * Inspect the routing table to know which of the two (mutually exclusive)
> >>>>>> + * routes is enabled and return the sink pad id of the active route.
> >>>>>> + */
> >>>>>> +static unsigned int mali_c55_rzr_get_active_sink(struct v4l2_subdev_state *state)
> >>>>>> +{
> >>>>>> +    struct v4l2_subdev_krouting *routing = &state->routing;
> >>>>>> +    struct v4l2_subdev_route *route;
> >>>>>> +
> >>>>>> +    /* A single route is enabled at a time. */
> >>>>>> +    for_each_active_route(routing, route)
> >>>>>> +        return route->sink_pad;
> >>>>>> +
> >>>>>> +    return MALI_C55_RZR_SINK_PAD;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static u32 mali_c55_rzr_shift_mbus_code(u32 mbus_code)
> >>>>>> +{
> >>>>>> +    u32 corrected_code = 0;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The ISP takes input in a 20-bit format, but can only output 16-bit
> >>>>>> +     * RAW bayer data (with the 4 least significant bits from the input
> >>>>>> +     * being lost). Return the 16-bit version of the 20-bit input formats.
> >>>>>> +     */
> >>>>>> +    switch (mbus_code) {
> >>>>>> +    case MEDIA_BUS_FMT_SBGGR20_1X20:
> >>>>>> +        corrected_code = MEDIA_BUS_FMT_SBGGR16_1X16;
> >>>>>> +        break;
> >>>>>> +    case MEDIA_BUS_FMT_SGBRG20_1X20:
> >>>>>> +        corrected_code = MEDIA_BUS_FMT_SGBRG16_1X16;
> >>>>>> +        break;
> >>>>>> +    case MEDIA_BUS_FMT_SGRBG20_1X20:
> >>>>>> +        corrected_code = MEDIA_BUS_FMT_SGRBG16_1X16;
> >>>>>> +        break;
> >>>>>> +    case MEDIA_BUS_FMT_SRGGB20_1X20:
> >>>>>> +        corrected_code = MEDIA_BUS_FMT_SRGGB16_1X16;
> >>>>>> +        break;
> >>>> 
> >>>> Would it make sense to add the shifted code to mali_c55_isp_fmt ?
> >>>>
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return corrected_code;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int __mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
> >>>>>> +                      struct v4l2_subdev_state *state,
> >>>>>> +                      struct v4l2_subdev_krouting *routing)
> >>>> 
> >>>> I think the last argument can be const.
> >>>>
> >>>>>> +{
> >>>>>> +    struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> >>>>>> +                            sd);
> >>>> 
> >>>> A to_mali_c55_resizer() static inline function would be useful. Same for
> >>>> other components, where applicable.
> >>>>
> >>>>>> +    unsigned int active_sink = UINT_MAX;
> >>>>>> +    struct v4l2_mbus_framefmt *src_fmt;
> >>>>>> +    struct v4l2_rect *crop, *compose;
> >>>>>> +    struct v4l2_subdev_route *route;
> >>>>>> +    unsigned int active_routes = 0;
> >>>>>> +    struct v4l2_mbus_framefmt *fmt;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    ret = v4l2_subdev_routing_validate(sd, routing, 0);
> >>>>>> +    if (ret)
> >>>>>> +        return ret;
> >>>>>> +
> >>>>>> +    /* Only a single route can be enabled at a time. */
> >>>>>> +    for_each_active_route(routing, route) {
> >>>>>> +        if (++active_routes > 1) {
> >>>>>> +            dev_err(rzr->mali_c55->dev,
> >>>>>> +                "Only one route can be active");
> >>>> 
> >>>> No kernel log message with a level higher than dev_dbg() from
> >>>> user-controlled paths please, here and where applicable. This is to
> >>>> avoid giving applications an easy way to flood the kernel log.
> >>>>
> >>>>>> +            return -EINVAL;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        active_sink = route->sink_pad;
> >>>>>> +    }
> >>>>>> +    if (active_sink == UINT_MAX) {
> >>>>>> +        dev_err(rzr->mali_c55->dev, "One route has to be active");
> >>>>>> +        return -EINVAL;
> >>>>>> +    }
> >>>> 
> >>>> The recommended handling of invalid routing is to adjust the routing
> >>>> table, not to return errors.
> >>>>
> >>>>>> +
> >>>>>> +    ret = v4l2_subdev_set_routing(sd, state, routing);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(rzr->mali_c55->dev, "Failed to set routing\n");
> >>>>>> +        return ret;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    fmt = v4l2_subdev_state_get_format(state, active_sink, 0);
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, active_sink, 0);
> >>>>>> +    compose = v4l2_subdev_state_get_compose(state, active_sink, 0);
> >>>>>> +
> >>>>>> +    fmt->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +    fmt->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +    fmt->colorspace = V4L2_COLORSPACE_SRGB;
> >>>> 
> >>>> There are other colorspace-related fields.
> >>>>
> >>>>>> +    fmt->field = V4L2_FIELD_NONE;
> >>>> 
> >>>> I wonder if we should really update the sink pad format, or just
> >>>> propagate it. If we update it, I think it should be set to defaults on
> >>>> both sink pads, not just the active sink pad.
> >>>>
> >>>>>> +
> >>>>>> +    if (active_sink == MALI_C55_RZR_SINK_PAD) {
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +
> >>>>>> +        crop->left = crop->top = 0;
> >>>> 
> >>>>          crop->left = 0;
> >>>>          crop->top = 0;
> >>>>
> >>>>>> +        crop->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +        crop->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +
> >>>>>> +        *compose = *crop;
> >>>>>> +    } else {
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Propagate the format to the source pad */
> >>>>>> +    src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD,
> >>>>>> +                           0);
> >>>>>> +    *src_fmt = *fmt;
> >>>>>> +
> >>>>>> +    /* In the event this is the bypass pad the mbus code needs correcting */
> >>>>>> +    if (active_sink == MALI_C55_RZR_SINK_BYPASS_PAD)
> >>>>>> +        src_fmt->code = mali_c55_rzr_shift_mbus_code(src_fmt->code);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_enum_mbus_code(struct v4l2_subdev *sd,
> >>>>>> +                       struct v4l2_subdev_state *state,
> >>>>>> +                       struct v4l2_subdev_mbus_code_enum *code)
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *sink_fmt;
> >>>>>> +    const struct mali_c55_isp_fmt *fmt;
> >>>>>> +    unsigned int index = 0;
> >>>>>> +    u32 sink_pad;
> >>>>>> +
> >>>>>> +    switch (code->pad) {
> >>>>>> +    case MALI_C55_RZR_SINK_PAD:
> >>>>>> +        if (code->index)
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        code->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +
> >>>>>> +        return 0;
> >>>>>> +    case MALI_C55_RZR_SOURCE_PAD:
> >>>>>> +        sink_pad = mali_c55_rzr_get_active_sink(state);
> >>>>>> +        sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0);
> >>>>>> +
> >>>>>> +        /*
> >>>>>> +         * If the active route is from the Bypass sink pad, then the
> >>>>>> +         * source pad is a simple passthrough of the sink format,
> >>>>>> +         * downshifted to 16-bits.
> >>>>>> +         */
> >>>>>> +
> >>>>>> +        if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
> >>>>>> +            if (code->index)
> >>>>>> +                return -EINVAL;
> >>>>>> +
> >>>>>> +            code->code = mali_c55_rzr_shift_mbus_code(sink_fmt->code);
> >>>>>> +            if (!code->code)
> >>>>>> +                return -EINVAL;
> >>>>>> +
> >>>>>> +            return 0;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        /*
> >>>>>> +         * If the active route is from the non-bypass sink then we can
> >>>>>> +         * select either RGB or conversion to YUV.
> >>>>>> +         */
> >>>>>> +
> >>>>>> +        if (code->index >= ARRAY_SIZE(rzr_non_bypass_src_fmts))
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        code->code = rzr_non_bypass_src_fmts[code->index];
> >>>>>> +
> >>>>>> +        return 0;
> >>>>>> +    case MALI_C55_RZR_SINK_BYPASS_PAD:
> >>>>>> +        for_each_mali_isp_fmt(fmt) {
> >>>>>> +            if (index++ == code->index) {
> >>>>>> +                code->code = fmt->code;
> >>>>>> +                return 0;
> >>>>>> +            }
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        break;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return -EINVAL;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_enum_frame_size(struct v4l2_subdev *sd,
> >>>>>> +                    struct v4l2_subdev_state *state,
> >>>>>> +                    struct v4l2_subdev_frame_size_enum *fse)
> >>>>>> +{
> >>>>>> +    if (fse->index)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    fse->max_width = MALI_C55_MAX_WIDTH;
> >>>>>> +    fse->max_height = MALI_C55_MAX_HEIGHT;
> >>>>>> +    fse->min_width = MALI_C55_MIN_WIDTH;
> >>>>>> +    fse->min_height = MALI_C55_MIN_HEIGHT;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_set_sink_fmt(struct v4l2_subdev *sd,
> >>>>>> +                     struct v4l2_subdev_state *state,
> >>>>>> +                     struct v4l2_subdev_format *format)
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *fmt = &format->format;
> >>>>>> +    struct v4l2_rect *rect;
> >>>>>> +    unsigned int sink_pad;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * Clamp to min/max and then reset crop and compose rectangles to the
> >>>>>> +     * newly applied size.
> >>>>>> +     */
> >>>>>> +    clamp_t(unsigned int, fmt->width,
> >>>>>> +        MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH);
> >>>>>> +    clamp_t(unsigned int, fmt->height,
> >>>>>> +        MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
> >>>> 
> >>>> Please check comments for other components related to the colorspace
> >>>> fields, to decide how to handle them here.
> >>>>
> >>>>>> +
> >>>>>> +    sink_pad = mali_c55_rzr_get_active_sink(state);
> >>>>>> +    if (sink_pad == MALI_C55_RZR_SINK_PAD) {
> >>>> 
> >>>> The selection here should depend on format->pad, not the active sink
> >>>> pad.
> >>>>
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +
> >>>>>> +        rect = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD);
> >>>>>> +        rect->left = 0;
> >>>>>> +        rect->top = 0;
> >>>>>> +        rect->width = fmt->width;
> >>>>>> +        rect->height = fmt->height;
> >>>>>> +
> >>>>>> +        rect = v4l2_subdev_state_get_compose(state,
> >>>>>> +                             MALI_C55_RZR_SINK_PAD);
> >>>>>> +        rect->left = 0;
> >>>>>> +        rect->top = 0;
> >>>>>> +        rect->width = fmt->width;
> >>>>>> +        rect->height = fmt->height;
> >>>>>> +    } else {
> >>>>>> +        /*
> >>>>>> +         * Make sure the media bus code is one of the supported
> >>>>>> +         * ISP input media bus codes.
> >>>>>> +         */
> >>>>>> +        if (!mali_c55_isp_is_format_supported(fmt->code))
> >>>>>> +            fmt->code = MALI_C55_DEFAULT_MEDIA_BUS_FMT;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    *v4l2_subdev_state_get_format(state, sink_pad, 0) = *fmt;
> >>>>>> +    *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt;
> >>>> 
> >>>> Propagation to the source pad, however, should depend on the active
> >>>> route. If format->pad is routed to the source pad, you should propagate,
> >>>> otherwise, you shouldn't.
> >>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_set_source_fmt(struct v4l2_subdev *sd,
> >>>>>> +                       struct v4l2_subdev_state *state,
> >>>>>> +                       struct v4l2_subdev_format *format)
> >>>>>> +{
> >>>>>> +    struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> >>>>>> +                            sd);
> >>>>>> +    struct v4l2_mbus_framefmt *fmt = &format->format;
> >>>>>> +    struct v4l2_mbus_framefmt *sink_fmt;
> >>>>>> +    struct v4l2_rect *crop, *compose;
> >>>>>> +    unsigned int sink_pad;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    sink_pad = mali_c55_rzr_get_active_sink(state);
> >>>>>> +    sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0);
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, sink_pad, 0);
> >>>>>> +    compose = v4l2_subdev_state_get_compose(state, sink_pad, 0);
> >>>>>> +
> >>>>>> +    /* FR Bypass pipe. */
> >>>>>> +
> >>>>>> +    if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
> >>>>>> +        /*
> >>>>>> +         * Format on the source pad is the same as the one on the
> >>>>>> +         * sink pad, downshifted to 16-bits.
> >>>>>> +         */
> >>>>>> +        fmt->code = mali_c55_rzr_shift_mbus_code(sink_fmt->code);
> >>>>>> +        if (!fmt->code)
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        /* RAW bypass disables scaling and cropping. */
> >>>>>> +        crop->top = compose->top = 0;
> >>>>>> +        crop->left = compose->left = 0;
> >>>>>> +        fmt->width = crop->width = compose->width = sink_fmt->width;
> >>>>>> +        fmt->height = crop->height = compose->height = sink_fmt->height;
> >>>> 
> >>>> I don't think this is right. This function sets the format on the source
> >>>> pad. Subdevs should propagate formats from the sink to the source, not
> >>>> the other way around.
> >>>>
> >>>> The only parameter that can be modified on the source pad (as far as I
> >>>> understand) is the media bus code. In the bypass path, I understand it's
> >>>> fixed, while in the other path, you can select between RGB and YUV. I
> >>>> think the following code is what you need to implement this function.
> >>>>
> >>>> static int mali_c55_rzr_set_source_fmt(struct v4l2_subdev *sd,
> >>>>                         struct v4l2_subdev_state *state,
> >>>>                         struct v4l2_subdev_format *format)
> >>>> {
> >>>>      struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> >>>>                              sd);
> >>>>      struct v4l2_mbus_framefmt *fmt;
> >>>>
> >>>>      fmt = v4l2_subdev_state_get_format(state, format->pad);
> >>>>
> >>>>      /* In the non-bypass path the output format can be selected. */
> >>>>      if (mali_c55_rzr_get_active_sink(state) == MALI_C55_RZR_SINK_PAD) {
> >>>>          unsigned int i;
> >>>>
> >>>>          fmt->code = format->format.code;
> >>>>
> >>>>          for (i = 0; i < ARRAY_SIZE(rzr_non_bypass_src_fmts); i++) {
> >>>>              if (fmt->code == rzr_non_bypass_src_fmts[i])
> >>>>                  break;
> >>>>          }
> >>>>
> >>>>          if (i == ARRAY_SIZE(rzr_non_bypass_src_fmts))
> >>>>              fmt->code = rzr_non_bypass_src_fmts[0];
> >>>>      }
> >>>>
> >>>>      format->format = *fmt;
> >>>>
> >>>>      return 0;
> >>>> }
> >>>>
> >>>>>> +
> >>>>>> +        *v4l2_subdev_state_get_format(state,
> >>>>>> +                          MALI_C55_RZR_SOURCE_PAD) = *fmt;
> >>>>>> +
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Regular processing pipe. */
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(rzr_non_bypass_src_fmts); i++) {
> >>>>>> +        if (fmt->code == rzr_non_bypass_src_fmts[i])
> >>>>>> +            break;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (i == ARRAY_SIZE(rzr_non_bypass_src_fmts)) {
> >>>>>> +        dev_dbg(rzr->mali_c55->dev,
> >>>>>> +            "Unsupported mbus code 0x%x: using default\n",
> >>>>>> +            fmt->code);
> >>>> 
> >>>> I think you can drop this message.
> >>>>
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The source pad format size comes directly from the sink pad
> >>>>>> +     * compose rectangle.
> >>>>>> +     */
> >>>>>> +    fmt->width = compose->width;
> >>>>>> +    fmt->height = compose->height;
> >>>>>> +
> >>>>>> +    *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD) = *fmt;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_set_fmt(struct v4l2_subdev *sd,
> >>>>>> +                struct v4l2_subdev_state *state,
> >>>>>> +                struct v4l2_subdev_format *format)
> >>>>>> +{
> >>>>>> +    /*
> >>>>>> +     * On sink pads fmt is either fixed for the 'regular' processing
> >>>>>> +     * pad or a RAW format or 20-bit wide RGB/YUV format for the FR bypass
> >>>>>> +     * pad.
> >>>>>> +     *
> >>>>>> +     * On source pad sizes are the result of crop+compose on the sink
> >>>>>> +     * pad sizes, while the format depends on the active route.
> >>>>>> +     */
> >>>>>> +
> >>>>>> +    if (format->pad != MALI_C55_RZR_SOURCE_PAD)
> >>>>>> +        return mali_c55_rzr_set_sink_fmt(sd, state, format);
> >>>>>> +
> >>>>>> +    return mali_c55_rzr_set_source_fmt(sd, state, format);
> >>>> 
> >>>> Nitpicking,
> >>>>
> >>>>      if (format->pad == MALI_C55_RZR_SOURCE_PAD)
> >>>>          return mali_c55_rzr_set_source_fmt(sd, state, format);
> >>>>
> >>>>      return mali_c55_rzr_set_sink_fmt(sd, state, format);
> >>>>
> >>>> to match SOURCE_PAD and source_fmt.
> >>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_get_selection(struct v4l2_subdev *sd,
> >>>>>> +                      struct v4l2_subdev_state *state,
> >>>>>> +                      struct v4l2_subdev_selection *sel)
> >>>>>> +{
> >>>>>> +    if (sel->pad != MALI_C55_RZR_SINK_PAD)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    if (sel->target != V4L2_SEL_TGT_CROP &&
> >>>>>> +        sel->target != V4L2_SEL_TGT_COMPOSE)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    sel->r = sel->target == V4L2_SEL_TGT_CROP
> >>>>>> +           ? *v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD)
> >>>>>> +           : *v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_set_selection(struct v4l2_subdev *sd,
> >>>>>> +                      struct v4l2_subdev_state *state,
> >>>>>> +                      struct v4l2_subdev_selection *sel)
> >>>>>> +{
> >>>>>> +    struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> >>>>>> +                            sd);
> >>>>>> +    struct v4l2_mbus_framefmt *source_fmt;
> >>>>>> +    struct v4l2_mbus_framefmt *sink_fmt;
> >>>>>> +    struct v4l2_rect *crop, *compose;
> >>>>>> +
> >>>>>> +    if (sel->pad != MALI_C55_RZR_SINK_PAD)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    if (sel->target != V4L2_SEL_TGT_CROP &&
> >>>>>> +        sel->target != V4L2_SEL_TGT_COMPOSE)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    source_fmt = v4l2_subdev_state_get_format(state,
> >>>>>> +                          MALI_C55_RZR_SOURCE_PAD);
> >>>>>> +    sink_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RZR_SINK_PAD);
> >>>>>> +    crop = v4l2_subdev_state_get_crop(state, MALI_C55_RZR_SINK_PAD);
> >>>>>> +    compose = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD);
> >>>>>> +
> >>>>>> +    /* RAW bypass disables crop/scaling. */
> >>>>>> +    if (mali_c55_format_is_raw(source_fmt->code)) {
> >>>>>> +        crop->top = compose->top = 0;
> >>>>>> +        crop->left = compose->left = 0;
> >>>>>> +        crop->width = compose->width = sink_fmt->width;
> >>>>>> +        crop->height = compose->height = sink_fmt->height;
> >>>>>> +
> >>>>>> +        sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose;
> >>>>>> +
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* During streaming, it is allowed to only change the crop rectangle. */
> >>>>>> +    if (rzr->streaming && sel->target != V4L2_SEL_TGT_CROP)
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +     /*
> >>>>>> +      * Update the desired target and then clamp the crop rectangle to the
> >>>>>> +      * sink format sizes and the compose size to the crop sizes.
> >>>>>> +      */
> >>>>>> +    if (sel->target == V4L2_SEL_TGT_CROP)
> >>>>>> +        *crop = sel->r;
> >>>>>> +    else
> >>>>>> +        *compose = sel->r;
> >>>>>> +
> >>>>>> +    clamp_t(unsigned int, crop->left, 0, sink_fmt->width);
> >>>>>> +    clamp_t(unsigned int, crop->top, 0, sink_fmt->height);
> >>>>>> +    clamp_t(unsigned int, crop->width, MALI_C55_MIN_WIDTH,
> >>>>>> +        sink_fmt->width - crop->left);
> >>>>>> +    clamp_t(unsigned int, crop->height, MALI_C55_MIN_HEIGHT,
> >>>>>> +        sink_fmt->height - crop->top);
> >>>>>> +
> >>>>>> +    if (rzr->streaming) {
> >>>>>> +        /*
> >>>>>> +         * Apply at runtime a crop rectangle on the resizer's sink only
> >>>>>> +         * if it doesn't require re-programming the scaler output sizes
> >>>>>> +         * as it would require changing the output buffer sizes as well.
> >>>>>> +         */
> >>>>>> +        if (sel->r.width < compose->width ||
> >>>>>> +            sel->r.height < compose->height)
> >>>>>> +            return -EINVAL;
> >>>>>> +
> >>>>>> +        *crop = sel->r;
> >>>>>> +        mali_c55_rzr_program(rzr, state);
> >>>>>> +
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    compose->left = 0;
> >>>>>> +    compose->top = 0;
> >>>>>> +    clamp_t(unsigned int, compose->left, 0, sink_fmt->width);
> >>>>>> +    clamp_t(unsigned int, compose->top, 0, sink_fmt->height);
> >>>>>> +    clamp_t(unsigned int, compose->width, crop->width / 8, crop->width);
> >>>>>> +    clamp_t(unsigned int, compose->height, crop->height / 8, crop->height);
> >>>>>> +
> >>>>>> +    sel->r = sel->target == V4L2_SEL_TGT_CROP ? *crop : *compose;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
> >>>>>> +                    struct v4l2_subdev_state *state,
> >>>>>> +                    enum v4l2_subdev_format_whence which,
> >>>>>> +                    struct v4l2_subdev_krouting *routing)
> >>>>>> +{
> >>>>>> +    if (which == V4L2_SUBDEV_FORMAT_ACTIVE &&
> >>>>>> +        media_entity_is_streaming(&sd->entity))
> >>>>>> +        return -EBUSY;
> >>>>>> +
> >>>>>> +    return __mali_c55_rzr_set_routing(sd, state, routing);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_pad_ops mali_c55_resizer_pad_ops = {
> >>>>>> +    .enum_mbus_code        = mali_c55_rzr_enum_mbus_code,
> >>>>>> +    .enum_frame_size    = mali_c55_rzr_enum_frame_size,
> >>>>>> +    .get_fmt        = v4l2_subdev_get_fmt,
> >>>>>> +    .set_fmt        = mali_c55_rzr_set_fmt,
> >>>>>> +    .get_selection        = mali_c55_rzr_get_selection,
> >>>>>> +    .set_selection        = mali_c55_rzr_set_selection,
> >>>>>> +    .set_routing        = mali_c55_rzr_set_routing,
> >>>>>> +};
> >>>>>> +
> >>>>>> +void mali_c55_rzr_start_stream(struct mali_c55_resizer *rzr)
> >>>> 
> >>>> Could this be handled through the .enable_streams() and
> >>>> .disable_streams() operations ? They ensure that the stream state stored
> >>>> internal is correct. That may not matter much today, but I think it will
> >>>> become increasingly important in the future for the V4L2 core.
> >>>>
> >>>>>> +{
> >>>>>> +    struct mali_c55 *mali_c55 = rzr->mali_c55;
> >>>>>> +    struct v4l2_subdev *sd = &rzr->sd;
> >>>>>> +    struct v4l2_subdev_state *state;
> >>>>>> +    unsigned int sink_pad;
> >>>>>> +
> >>>>>> +    state = v4l2_subdev_lock_and_get_active_state(sd);
> >>>>>> +
> >>>>>> +    sink_pad = mali_c55_rzr_get_active_sink(state);
> >>>>>> +    if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
> >>>>>> +        /* Bypass FR pipe processing if the bypass route is active. */
> >>>>>> +        mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
> >>>>>> + MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK,
> >>>>>> + MALI_C55_ISP_RAW_BYPASS_RAW_FR_BYPASS);
> >>>>>> +        goto unlock_state;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /* Disable bypass and use regular processing. */
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS,
> >>>>>> +                 MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK, 0);
> >>>>>> +    mali_c55_rzr_program(rzr, state);
> >>>>>> +
> >>>>>> +unlock_state:
> >>>>>> +    rzr->streaming = true;
> >>>> 
> >>>> And hopefully you'll be able to replace this with
> >>>> v4l2_subdev_is_streaming(), introduced in "[PATCH v6 00/11] media:
> >>>> subdev: Improve stream enable/disable machinery" (Sakari has sent a pull
> >>>> request for v6.11 yesterday).
> >>>>
> >>>>>> +    v4l2_subdev_unlock_state(state);
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_rzr_stop_stream(struct mali_c55_resizer *rzr)
> >>>>>> +{
> >>>>>> +    struct v4l2_subdev *sd = &rzr->sd;
> >>>>>> +    struct v4l2_subdev_state *state;
> >>>>>> +
> >>>>>> +    state = v4l2_subdev_lock_and_get_active_state(sd);
> >>>>>> +    rzr->streaming = false;
> >>>>>> +    v4l2_subdev_unlock_state(state);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_ops mali_c55_resizer_ops = {
> >>>>>> +    .pad    = &mali_c55_resizer_pad_ops,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_rzr_init_state(struct v4l2_subdev *sd,
> >>>>>> +                   struct v4l2_subdev_state *state)
> >>>>>> +{
> >>>>>> +    struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
> >>>>>> +                            sd);
> >>>>>> +    struct v4l2_subdev_krouting routing = { };
> >>>>>> +    struct v4l2_subdev_route *routes;
> >>>>>> +    unsigned int i;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    routes = kcalloc(rzr->num_routes, sizeof(*routes), GFP_KERNEL);
> >>>>>> +    if (!routes)
> >>>>>> +        return -ENOMEM;
> >>>>>> +
> >>>>>> +    for (i = 0; i < rzr->num_routes; ++i) {
> >>>>>> +        struct v4l2_subdev_route *route = &routes[i];
> >>>>>> +
> >>>>>> +        route->sink_pad = i
> >>>>>> +                ? MALI_C55_RZR_SINK_BYPASS_PAD
> >>>>>> +                : MALI_C55_RZR_SINK_PAD;
> >>>>>> +        route->source_pad = MALI_C55_RZR_SOURCE_PAD;
> >>>>>> +        if (route->sink_pad != MALI_C55_RZR_SINK_BYPASS_PAD)
> >>>>>> +            route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    routing.num_routes = rzr->num_routes;
> >>>>>> +    routing.routes = routes;
> >>>>>> +
> >>>>>> +    ret = __mali_c55_rzr_set_routing(sd, state, &routing);
> >>>>>> +    kfree(routes);
> >>>>>> +
> >>>>>> +    return ret;
> >>>> 
> >>>> I think this could be simplified.
> >>>>
> >>>>      struct v4l2_subdev_route routes[2] = {
> >>>>          {
> >>>>              .sink_pad = MALI_C55_RZR_SINK_PAD,
> >>>>              .source_pad = MALI_C55_RZR_SOURCE_PAD,
> >>>>              .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
> >>>>          }, {
> >>>>              .sink_pad = MALI_C55_RZR_SINK_BYPASS_PAD,
> >>>>              .source_pad = MALI_C55_RZR_SOURCE_PAD,
> >>>>          },
> >>>>      };
> >>>>      struct v4l2_subdev_krouting routing = {
> >>>>          .num_routes = rzr->num_routes,
> >>>>          .routes = routes,
> >>>>      };
> >>>>
> >>>>      return __mali_c55_rzr_set_routing(sd, state, &routing);
> >>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_internal_ops mali_c55_resizer_internal_ops = {
> >>>>>> +    .init_state = mali_c55_rzr_init_state,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static void mali_c55_resizer_program_coefficients(struct mali_c55 *mali_c55,
> >>>>>> +                          unsigned int index)
> >>>>>> +{
> >>>>>> +    const unsigned int scaler_filt_coefmem_addrs[][2] = {
> >>>>>> +        [MALI_C55_RZR_FR] = {
> >>>>>> +            0x034A8, /* hfilt */
> >>>>>> +            0x044A8  /* vfilt */
> >>>>> 
> >>>>> Lowercase hex constants.
> >>>> 
> >>>> And addresses belong to the mali-c55-registers.h file.
> >>>>
> >>>>>> +        },
> >>>>>> +        [MALI_C55_RZR_DS] = {
> >>>>>> +            0x014A8, /* hfilt */
> >>>>>> +            0x024A8  /* vfilt */
> >>>>>> +        },
> >>>>>> +    };
> >>>>>> +    unsigned int haddr = scaler_filt_coefmem_addrs[index][0];
> >>>>>> +    unsigned int vaddr = scaler_filt_coefmem_addrs[index][1];
> >>>>>> +    unsigned int i, j;
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_RESIZER_COEFS_NUM_BANKS; i++) {
> >>>>>> +        for (j = 0; j < MALI_C55_RESIZER_COEFS_NUM_ENTRIES; j++) {
> >>>>>> +            mali_c55_write(mali_c55, haddr,
> >>>>>> + mali_c55_scaler_h_filter_coefficients[i][j]);
> >>>>>> +            mali_c55_write(mali_c55, vaddr,
> >>>>>> + mali_c55_scaler_v_filter_coefficients[i][j]);
> >>>>>> +
> >>>>>> +            haddr += sizeof(u32);
> >>>>>> +            vaddr += sizeof(u32);
> >>>>>> +        }
> >>>>>> +    }
> >>>> 
> >>>> How about memcpy_toio() ? I suppose this function isn't
> >>>> performance sensitive, so maybe usage of mali_c55_write() is better from
> >>>> a consistency point of view.
> >>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +int mali_c55_register_resizers(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_NUM_RZRS; ++i) {
> >>>> 
> >>>> Moving the inner content to a separate mali_c55_register_resizer()
> >>>> function would increase readability I think, and remove usage of gotos.
> >>>> I would probably do the same for unregistration too, for consistency.
> >>>>
> >>>>>> +        struct mali_c55_resizer *rzr = &mali_c55->resizers[i];
> >>>>>> +        struct v4l2_subdev *sd = &rzr->sd;
> >>>>>> +        unsigned int num_pads = MALI_C55_RZR_NUM_PADS;
> >>>>>> +
> >>>>>> +        rzr->id = i;
> >>>>>> +        rzr->streaming = false;
> >>>>>> +
> >>>>>> +        if (rzr->id == MALI_C55_RZR_FR)
> >>>>>> +            rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR];
> >>>>>> +        else
> >>>>>> +            rzr->cap_dev = &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS];
> >>>>>> +
> >>>>>> +        mali_c55_resizer_program_coefficients(mali_c55, i);
> >>>> 
> >>>> Should this be done at stream start, given that power may be cut off
> >>>> between streaming sessions ?
> >>>>
> >>>>>> +
> >>>>>> +        v4l2_subdev_init(sd, &mali_c55_resizer_ops);
> >>>>>> +        sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE
> >>>>>> +                 | V4L2_SUBDEV_FL_STREAMS;
> >>>>>> +        sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
> >>>>>> +        sd->internal_ops = &mali_c55_resizer_internal_ops;
> >>>>>> +        snprintf(sd->name, ARRAY_SIZE(sd->name), "%s %s",
> >>>> 
> >>>>          snprintf(sd->name, ARRAY_SIZE(sd->name), "%s resizer %s",
> >>>>
> >>>> and drop the "resizer " prefix from mali_c55_resizer_names. You can also
> >>>> make mali_c55_resizer_names a local static const variable.
> >>>>
> >>>>>> +             MALI_C55_DRIVER_NAME, mali_c55_resizer_names[i]);
> >>>>>> +
> >>>>>> +        rzr->pads[MALI_C55_RZR_SINK_PAD].flags = MEDIA_PAD_FL_SINK;
> >>>>>> +        rzr->pads[MALI_C55_RZR_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE;
> >>>>>> +
> >>>>>> +        /* Only the FR pipe has a bypass pad. */
> >>>>>> +        if (rzr->id == MALI_C55_RZR_FR) {
> >>>>>> + rzr->pads[MALI_C55_RZR_SINK_BYPASS_PAD].flags =
> >>>>>> +                            MEDIA_PAD_FL_SINK;
> >>>>>> +            rzr->num_routes = 2;
> >>>>>> +        } else {
> >>>>>> +            num_pads -= 1;
> >>>>>> +            rzr->num_routes = 1;
> >>>>>> +        }
> >>>>>> +
> >>>>>> +        ret = media_entity_pads_init(&sd->entity, num_pads, rzr->pads);
> >>>>>> +        if (ret)
> >>>>>> +            return ret;
> >>>>>> +
> >>>>>> +        ret = v4l2_subdev_init_finalize(sd);
> >>>>>> +        if (ret)
> >>>>>> +            goto err_cleanup;
> >>>>>> +
> >>>>>> +        ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
> >>>>>> +        if (ret)
> >>>>>> +            goto err_cleanup;
> >>>>>> +
> >>>>>> +        rzr->mali_c55 = mali_c55;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_cleanup:
> >>>>>> +    for (; i >= 0; --i) {
> >>>>>> +        struct mali_c55_resizer *rzr = &mali_c55->resizers[i];
> >>>>>> +        struct v4l2_subdev *sd = &rzr->sd;
> >>>>>> +
> >>>>>> +        v4l2_subdev_cleanup(sd);
> >>>>>> +        media_entity_cleanup(&sd->entity);
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_unregister_resizers(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < MALI_C55_NUM_RZRS; i++) {
> >>>>>> +        struct mali_c55_resizer *resizer = &mali_c55->resizers[i];
> >>>>>> +
> >>>>>> +        if (!resizer->mali_c55)
> >>>>>> +            continue;
> >>>>>> +
> >>>>>> +        v4l2_device_unregister_subdev(&resizer->sd);
> >>>>>> +        v4l2_subdev_cleanup(&resizer->sd);
> >>>>>> +        media_entity_cleanup(&resizer->sd.entity);
> >>>>>> +    }
> >>>>>> +}
> >>>>>> diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
> >>>>>> b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
> >>>>>> new file mode 100644
> >>>>>> index 000000000000..c7e699741c6d
> >>>>>> --- /dev/null
> >>>>>> +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
> >>>>>> @@ -0,0 +1,402 @@
> >>>>>> +// SPDX-License-Identifier: GPL-2.0
> >>>>>> +/*
> >>>>>> + * ARM Mali-C55 ISP Driver - Test pattern generator
> >>>>>> + *
> >>>>>> + * Copyright (C) 2024 Ideas on Board Oy
> >>>>>> + */
> >>>>>> +
> >>>>>> +#include <linux/minmax.h>
> >>>>>> +#include <linux/string.h>
> >>>>>> +
> >>>>>> +#include <media/media-entity.h>
> >>>>>> +#include <media/v4l2-ctrls.h>
> >>>>>> +#include <media/v4l2-subdev.h>
> >>>>>> +
> >>>>>> +#include "mali-c55-common.h"
> >>>>>> +#include "mali-c55-registers.h"
> >>>>>> +
> >>>>>> +#define MALI_C55_TPG_SRC_PAD        0
> >>>>>> +#define MALI_C55_TPG_FIXED_HBLANK    0x20
> >>>>>> +#define MALI_C55_TPG_MAX_VBLANK        0xFFFF
> >>>>> 
> >>>>> Lowercase hex constants.
> >>>>>
> >>>>>> +#define MALI_C55_TPG_PIXEL_RATE        100000000
> >>>>> 
> >>>>> This should be exposed to applications using the V4L2_CID_PIXEL_RATE
> >>>>> control (read-only).
> >>>>>
> >>>>>> +
> >>>>>> +static const char * const mali_c55_tpg_test_pattern_menu[] = {
> >>>>>> +    "Flat field",
> >>>>>> +    "Horizontal gradient",
> >>>>>> +    "Vertical gradient",
> >>>>>> +    "Vertical bars",
> >>>>>> +    "Arbitrary rectangle",
> >>>>>> +    "White frame on black field"
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const u32 mali_c55_tpg_mbus_codes[] = {
> >>>>>> +    MEDIA_BUS_FMT_SRGGB20_1X20,
> >>>>>> +    MEDIA_BUS_FMT_RGB202020_1X60,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static void __mali_c55_tpg_calc_vblank(struct v4l2_mbus_framefmt *format,
> >>>>>> +                       int *def_vblank, int *min_vblank)
> >>>>> 
> >>>>> unsigned int ?
> >>>>>
> >>>>>> +{
> >>>>>> +    unsigned int hts;
> >>>>>> +    int tgt_fps;
> >>>>>> +    int vblank;
> >>>>>> +
> >>>>>> +    hts = format->width + MALI_C55_TPG_FIXED_HBLANK;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The ISP has minimum vertical blanking requirements that must be
> >>>>>> +     * adhered to by the TPG. The minimum is a function of the Iridix blocks
> >>>>>> +     * clocking requirements and the width of the image and horizontal
> >>>>>> +     * blanking, but if we assume the worst case iVariance and sVariance
> >>>>>> +     * values then it boils down to the below.
> >>>>>> +     */
> >>>>>> +    *min_vblank = 15 + (120500 / hts);
> >>>>> 
> >>>>> I wonder if this should round up.
> >>>>>
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We need to set a sensible default vblank for whatever format height
> >>>>>> +     * we happen to be given from set_fmt(). This function just targets
> >>>>>> +     * an even multiple of 15fps. If we can't get 15fps, let's target 5fps.
> >>>>>> +     * If we can't get 5fps we'll take whatever the minimum vblank gives us.
> >>>>>> +     */
> >>>>>> +    tgt_fps = MALI_C55_TPG_PIXEL_RATE / hts / (format->height + *min_vblank);
> >>>>>> +
> >>>>>> +    if (tgt_fps < 5)
> >>>>>> +        vblank = *min_vblank;
> >>>>>> +    else
> >>>>>> +        vblank = MALI_C55_TPG_PIXEL_RATE / hts
> >>>>>> +               / max(rounddown(tgt_fps, 15), 5);
> >>>>>> +
> >>>>>> +    *def_vblank = ALIGN_DOWN(vblank, 2) - format->height;
> >>>>> 
> >>>>> "vblank = vblank - height" doesn't seem right. The "else" branch stores
> >>>>> a vts in vblank, which doesn't seem right either. Maybe you meant
> >>>>> something like
> >>>>>
> >>>>>      if (tgt_fps < 5)
> >>>>>          def_vts = *min_vblank + format->height;
> >>>>>      else
> >>>>>          def_vts = MALI_C55_TPG_PIXEL_RATE / hts
> >>>>>              / max(rounddown(tgt_fps, 15), 5);
> >>>>>
> >>>>>      *def_vblank = ALIGN_DOWN(def_vts, 2) - format->height;
> >>>>>
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_s_ctrl(struct v4l2_ctrl *ctrl)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg *tpg = container_of(ctrl->handler,
> >>>>>> +                        struct mali_c55_tpg,
> >>>>>> +                        ctrls.handler);
> >>>>>> +    struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
> >>>>>> +
> >>>>> 
> >>>>> Should you return here if the pipeline isn't streaming ?
> >>>>>
> >>>>>> +    switch (ctrl->id) {
> >>>>>> +    case V4L2_CID_TEST_PATTERN:
> >>>>>> +        mali_c55_write(mali_c55, MALI_C55_REG_TEST_GEN_CH0_PATTERN_TYPE,
> >>>>>> +                   ctrl->val);
> >>>>>> +        break;
> >>>>>> +    case V4L2_CID_VBLANK:
> >>>>>> +        mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING,
> >>>>>> +                     MALI_C55_REG_VBLANK_MASK, ctrl->val);
> >>>>>> +        break;
> >>>>>> +    default:
> >>>>>> +        dev_err(mali_c55->dev, "invalid V4L2 control ID\n");
> >>>>>> +        return -EINVAL;
> >>>>> 
> >>>>> Can this happen ?
> >>>>>
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_ctrl_ops mali_c55_tpg_ctrl_ops = {
> >>>>>> +    .s_ctrl = &mali_c55_tpg_s_ctrl,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static void mali_c55_tpg_configure(struct mali_c55 *mali_c55,
> >>>>>> +                   struct v4l2_subdev *sd)
> >>>>>> +{
> >>>>>> +    struct v4l2_subdev_state *state;
> >>>>>> +    struct v4l2_mbus_framefmt *fmt;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * hblank needs setting, but is a read-only control and thus won't be
> >>>>>> +     * called during __v4l2_ctrl_handler_setup(). Do it here instead.
> >>>>>> +     */
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_BLANKING,
> >>>>>> +                 MALI_C55_REG_HBLANK_MASK,
> >>>>>> +                 MALI_C55_TPG_FIXED_HBLANK);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
> >>>>>> +                 MALI_C55_REG_GEN_VIDEO_MULTI_MASK, 0x01);
> >>>>>> +
> >>>>>> +    state = v4l2_subdev_lock_and_get_active_state(sd);
> >>>>>> +    fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
> >>>>>> +                 MALI_C55_TEST_PATTERN_RGB_MASK,
> >>>>>> +                 fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ?
> >>>>>> +                      0x01 : 0x0);
> >>>>>> +
> >>>>>> +    v4l2_subdev_unlock_state(state);
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_s_stream(struct v4l2_subdev *sd, int enable)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
> >>>>>> +    struct mali_c55 *mali_c55 = container_of(tpg, struct mali_c55, tpg);
> >>>>>> +
> >>>>>> +    if (!enable) {
> >>>>>> +        mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
> >>>>>> +                MALI_C55_TEST_PATTERN_ON_OFF, 0x00);
> >>>>>> +        mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
> >>>>>> +                MALI_C55_REG_GEN_VIDEO_ON_MASK, 0x00);
> >>>>>> +        return 0;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * One might reasonably expect the framesize to be set here
> >>>>>> +     * given it's configurable in .set_fmt(), but it's done in the
> >>>>>> +     * ISP subdevice's stream on func instead, as the same register
> >>>>> 
> >>>>> s/func/function/
> >>>>>
> >>>>>> +     * is also used to indicate the size of the data coming from the
> >>>>>> +     * sensor.
> >>>>>> +     */
> >>>>>> +    mali_c55_tpg_configure(mali_c55, sd);
> >>>>> 
> >>>>>      mali_c55_tpg_configure(tpg);
> >>>>>
> >>>>>> + __v4l2_ctrl_handler_setup(sd->ctrl_handler);
> >>>>>> +
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_TPG_CH0,
> >>>>>> +                 MALI_C55_TEST_PATTERN_ON_OFF,
> >>>>>> +                 MALI_C55_TEST_PATTERN_ON_OFF);
> >>>>>> +    mali_c55_update_bits(mali_c55, MALI_C55_REG_GEN_VIDEO,
> >>>>>> +                 MALI_C55_REG_GEN_VIDEO_ON_MASK,
> >>>>>> +                 MALI_C55_REG_GEN_VIDEO_ON_MASK);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_video_ops mali_c55_tpg_video_ops = {
> >>>>>> +    .s_stream = &mali_c55_tpg_s_stream,
> >>>>> 
> >>>>> Can we use .enable_streams() and .disable_streams() ?
> >>>>>
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_enum_mbus_code(struct v4l2_subdev *sd,
> >>>>>> +                       struct v4l2_subdev_state *state,
> >>>>>> +                       struct v4l2_subdev_mbus_code_enum *code)
> >>>>>> +{
> >>>>>> +    if (code->index >= ARRAY_SIZE(mali_c55_tpg_mbus_codes))
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    code->code = mali_c55_tpg_mbus_codes[code->index];
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_enum_frame_size(struct v4l2_subdev *sd,
> >>>>>> +                    struct v4l2_subdev_state *state,
> >>>>>> +                    struct v4l2_subdev_frame_size_enum *fse)
> >>>>>> +{
> >>>>> 
> >>>>> You sohuld verify here that fse->code is a supported value and return
> >>>>> -EINVAL otherwise.
> >>>>>
> >>>>>> +    if (fse->index > 0 || fse->pad > sd->entity.num_pads)
> >>>>> 
> >>>>> Drop the pad check, it's done in the subdev core already.
> >>>>>
> >>>>>> +        return -EINVAL;
> >>>>>> +
> >>>>>> +    fse->min_width = MALI_C55_MIN_WIDTH;
> >>>>>> +    fse->max_width = MALI_C55_MAX_WIDTH;
> >>>>>> +    fse->min_height = MALI_C55_MIN_HEIGHT;
> >>>>>> +    fse->max_height = MALI_C55_MAX_HEIGHT;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_set_fmt(struct v4l2_subdev *sd,
> >>>>>> +                struct v4l2_subdev_state *state,
> >>>>>> +                struct v4l2_subdev_format *format)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg *tpg = container_of(sd, struct mali_c55_tpg, sd);
> >>>>>> +    struct v4l2_mbus_framefmt *fmt = &format->format;
> >>>>>> +    int vblank_def, vblank_min;
> >>>>>> +    unsigned int i;
> >>>>>> +
> >>>>>> +    for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
> >>>>>> +        if (fmt->code == mali_c55_tpg_mbus_codes[i])
> >>>>>> +            break;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
> >>>>>> +        fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * The TPG says that the test frame timing generation logic expects a
> >>>>>> +     * minimum framesize of 4x4 pixels, but given the rest of the ISP can't
> >>>>>> +     * handle anything smaller than 128x128 it seems pointless to allow a
> >>>>>> +     * smaller frame.
> >>>>>> +     */
> >>>>>> +    clamp_t(unsigned int, fmt->width, MALI_C55_MIN_WIDTH,
> >>>>>> +        MALI_C55_MAX_WIDTH);
> >>>>>> +    clamp_t(unsigned int, fmt->height, MALI_C55_MIN_HEIGHT,
> >>>>>> +        MALI_C55_MAX_HEIGHT);
> >>>>>> +
> >>>>>> +    *v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD) = *fmt;
> >>>>> 
> >>>>> You're allowing userspace to set fmt->field, as well as all the
> >>>>> colorspace parameters, to random values. I would instead do something
> >>>>> like
> >>>>>
> >>>>>      for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
> >>>>>          if (format->format.code == mali_c55_tpg_mbus_codes[i])
> >>>>>              break;
> >>>>>      }
> >>>>>
> >>>>>      if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
> >>>>>          format->format.code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>>
> >>>>>      format->format.width = clamp(format->format.width,
> >>>>>                       MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH);
> >>>>>      format->format.height = clamp(format->format.height,
> >>>>>                        MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
> >>>>>
> >>>>>      fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
> >>>>>      fmt->code = format->format.code;
> >>>>>      fmt->width = format->format.width;
> >>>>>      fmt->height = format->format.height;
> >>>>>
> >>>>>      format->format = *fmt;
> >>>>>
> >>>>> Alternatively (which I think I like better),
> >>>>>
> >>>>>      fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
> >>>>>
> >>>>>      fmt->code = format->format.code;
> >>>>>
> >>>>>      for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) {
> >>>>>          if (fmt->code == mali_c55_tpg_mbus_codes[i])
> >>>>>              break;
> >>>>>      }
> >>>>>
> >>>>>      if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes))
> >>>>>          fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>>
> >>>>>      fmt->width = clamp(format->format.width,
> >>>>>                 MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH);
> >>>>>      fmt->height = clamp(format->format.height,
> >>>>>                  MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT);
> >>>>>
> >>>>>      format->format = *fmt;
> >>>>>
> >>>>>> +
> >>>>>> +    if (format->which == V4L2_SUBDEV_FORMAT_TRY)
> >>>>>> +        return 0;
> >>>>>> +
> >>>>>> +    __mali_c55_tpg_calc_vblank(fmt, &vblank_def, &vblank_min);
> >>>>>> +    __v4l2_ctrl_modify_range(tpg->ctrls.vblank, vblank_min,
> >>>>>> +                 MALI_C55_TPG_MAX_VBLANK, 1, vblank_def);
> >>>>>> +    __v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, vblank_def);
> >>>>> 
> >>>>> Move those three calls to a separate function, it will be reused below.
> >>>>> I'd name is mali_c55_tpg_update_vblank(). You can fold
> >>>>> __mali_c55_tpg_calc_vblank() in it.
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_pad_ops mali_c55_tpg_pad_ops = {
> >>>>>> +    .enum_mbus_code        = mali_c55_tpg_enum_mbus_code,
> >>>>>> +    .enum_frame_size    = mali_c55_tpg_enum_frame_size,
> >>>>>> +    .get_fmt        = v4l2_subdev_get_fmt,
> >>>>>> +    .set_fmt        = mali_c55_tpg_set_fmt,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_ops mali_c55_tpg_ops = {
> >>>>>> +    .video    = &mali_c55_tpg_video_ops,
> >>>>>> +    .pad    = &mali_c55_tpg_pad_ops,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_init_state(struct v4l2_subdev *sd,
> >>>>>> +                   struct v4l2_subdev_state *sd_state)
> >>>>> 
> >>>>> You name this variable state in every other subdev operation handler.
> >>>>>
> >>>>>> +{
> >>>>>> +    struct v4l2_mbus_framefmt *fmt =
> >>>>>> +        v4l2_subdev_state_get_format(sd_state, MALI_C55_TPG_SRC_PAD);
> >>>>>> +
> >>>>>> +    fmt->width = MALI_C55_DEFAULT_WIDTH;
> >>>>>> +    fmt->height = MALI_C55_DEFAULT_HEIGHT;
> >>>>>> +    fmt->field = V4L2_FIELD_NONE;
> >>>>>> +    fmt->code = MEDIA_BUS_FMT_SRGGB20_1X20;
> >>>>> 
> >>>>> Initialize the colorspace fields too.
> >>>>>
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +}
> >>>>>> +
> >>>>>> +static const struct v4l2_subdev_internal_ops mali_c55_tpg_internal_ops = {
> >>>>>> +    .init_state = mali_c55_tpg_init_state,
> >>>>>> +};
> >>>>>> +
> >>>>>> +static int mali_c55_tpg_init_controls(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg_ctrls *ctrls = &mali_c55->tpg.ctrls;
> >>>>>> +    struct v4l2_subdev *sd = &mali_c55->tpg.sd;
> >>>>>> +    struct v4l2_mbus_framefmt *format;
> >>>>>> +    struct v4l2_subdev_state *state;
> >>>>>> +    int vblank_def, vblank_min;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    state = v4l2_subdev_lock_and_get_active_state(sd);
> >>>>>> +    format = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD);
> >>>>>> +
> >>>>>> +    ret = v4l2_ctrl_handler_init(&ctrls->handler, 1);
> >>>>> 
> >>>>> You have 3 controls.
> >>>>>
> >>>>>> +    if (ret)
> >>>>>> +        goto err_unlock;
> >>>>>> +
> >>>>>> +    ctrls->test_pattern = v4l2_ctrl_new_std_menu_items(&ctrls->handler,
> >>>>>> +                &mali_c55_tpg_ctrl_ops, V4L2_CID_TEST_PATTERN,
> >>>>>> +                ARRAY_SIZE(mali_c55_tpg_test_pattern_menu) - 1,
> >>>>>> +                0, 3, mali_c55_tpg_test_pattern_menu);
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * We fix hblank at the minimum allowed value and control framerate
> >>>>>> +     * solely through the vblank control.
> >>>>>> +     */
> >>>>>> +    ctrls->hblank = v4l2_ctrl_new_std(&ctrls->handler,
> >>>>>> +                &mali_c55_tpg_ctrl_ops,
> >>>>>> +                V4L2_CID_HBLANK, MALI_C55_TPG_FIXED_HBLANK,
> >>>>>> +                MALI_C55_TPG_FIXED_HBLANK, 1,
> >>>>>> +                MALI_C55_TPG_FIXED_HBLANK);
> >>>>>> +    if (ctrls->hblank)
> >>>>>> +        ctrls->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
> >>>>>> +
> >>>>>> +    __mali_c55_tpg_calc_vblank(format, &vblank_def, &vblank_min);
> >>>>> 
> >>>>> Drop this and initialize the control with default values. You can then
> >>>>> update the value by calling mali_c55_tpg_update_vblank() in
> >>>>> mali_c55_register_tpg().
> >>>>>
> >>>>> The reason is to share the same mutex between the control handler and
> >>>>> the subdev active state without having to add a separate mutex in the
> >>>>> mali_c55_tpg structure. The simplest way to do so is to initialize the
> >>>>> controls first, set sd->state_lock to point to the control handler lock,
> >>>>> and call v4l2_subdev_init_finalize() as the last step. As a consequence,
> >>>>> you can't access the active state when initializing controls.
> >>>>>
> >>>>> You can alternatively keep the lock in mali_c55_tpg and set
> >>>>> sd->state_lock to point to it, but I think that's more complex.
> >>>>>
> >>>>>> +    ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler,
> >>>>>> +                      &mali_c55_tpg_ctrl_ops,
> >>>>>> +                      V4L2_CID_VBLANK, vblank_min,
> >>>>>> +                      MALI_C55_TPG_MAX_VBLANK, 1,
> >>>>>> +                      vblank_def);
> >>>>>> +
> >>>>>> +    if (ctrls->handler.error) {
> >>>>>> +        dev_err(mali_c55->dev, "Error during v4l2 controls init\n");
> >>>>>> +        ret = ctrls->handler.error;
> >>>>>> +        goto err_free_handler;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ctrls->handler.lock = &mali_c55->tpg.lock;
> >>>>> 
> >>>>> Drop this and drop the mutex. The control handler will use its internal
> >>>>> mutex.
> >>>>>
> >>>>>> +    mali_c55->tpg.sd.ctrl_handler = &ctrls->handler;
> >>>>>> +
> >>>>>> +    v4l2_subdev_unlock_state(state);
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_free_handler:
> >>>>>> +    v4l2_ctrl_handler_free(&ctrls->handler);
> >>>>>> +err_unlock:
> >>>>>> +    v4l2_subdev_unlock_state(state);
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +int mali_c55_register_tpg(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg *tpg = &mali_c55->tpg;
> >>>>>> +    struct v4l2_subdev *sd = &tpg->sd;
> >>>>>> +    struct media_pad *pad = &tpg->pad;
> >>>>>> +    int ret;
> >>>>>> +
> >>>>>> +    mutex_init(&tpg->lock);
> >>>>>> +
> >>>>>> +    v4l2_subdev_init(sd, &mali_c55_tpg_ops);
> >>>>>> +    sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
> >>>>>> +    sd->entity.function = MEDIA_ENT_F_CAM_SENSOR;
> >>>>> 
> >>>>> Should we introduce a TPG function ?
> >>>>>
> >>>>>> +    sd->internal_ops = &mali_c55_tpg_internal_ops;
> >>>>>> +    strscpy(sd->name, MALI_C55_DRIVER_NAME " tpg", sizeof(sd->name));
> >>>>>> +
> >>>>>> +    pad->flags = MEDIA_PAD_FL_SOURCE | MEDIA_PAD_FL_MUST_CONNECT;
> >>>>> 
> >>>>> I don't think MEDIA_PAD_FL_MUST_CONNECT is right.
> >>>>>
> >>>>>> +    ret = media_entity_pads_init(&sd->entity, 1, pad);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev,
> >>>>>> +            "Failed to initialize media entity pads\n");
> >>>>>> +        goto err_destroy_mutex;
> >>>>>> +    }
> >>>>>> +
> >>>>> 
> >>>>>      sd->state_lock = sd->ctrl_handler->lock;
> >>>>>
> >>>>> to use the same lock for the controls and the active state. You need to
> >>>>> move this line and the v4l2_subdev_init_finalize() call after
> >>>>> mali_c55_tpg_init_controls() to get the control handler lock initialized
> >>>>> first.
> >>>>>
> >>>>>> +    ret = v4l2_subdev_init_finalize(sd);
> >>>>>> +    if (ret)
> >>>>>> +        goto err_cleanup_media_entity;
> >>>>>> +
> >>>>>> +    ret = mali_c55_tpg_init_controls(mali_c55);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev,
> >>>>>> +            "Error initialising controls\n");
> >>>>>> +        goto err_cleanup_subdev;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd);
> >>>>>> +    if (ret) {
> >>>>>> +        dev_err(mali_c55->dev, "Failed to register tpg subdev\n");
> >>>>>> +        goto err_free_ctrl_handler;
> >>>>>> +    }
> >>>>>> +
> >>>>>> +    /*
> >>>>>> +     * By default the colour settings lead to a very dim image that is
> >>>>>> +     * nearly indistinguishable from black on some monitor settings. Ramp
> >>>>>> +     * them up a bit so the image is brighter.
> >>>>>> +     */
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND,
> >>>>>> +               MALI_C55_TPG_BACKGROUND_MAX);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND,
> >>>>>> +               MALI_C55_TPG_BACKGROUND_MAX);
> >>>>>> +    mali_c55_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND,
> >>>>>> +               MALI_C55_TPG_BACKGROUND_MAX);
> >>>>>> +
> >>>>>> +    tpg->mali_c55 = mali_c55;
> >>>>>> +
> >>>>>> +    return 0;
> >>>>>> +
> >>>>>> +err_free_ctrl_handler:
> >>>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls.handler);
> >>>>>> +err_cleanup_subdev:
> >>>>>> +    v4l2_subdev_cleanup(sd);
> >>>>>> +err_cleanup_media_entity:
> >>>>>> +    media_entity_cleanup(&sd->entity);
> >>>>>> +err_destroy_mutex:
> >>>>>> +    mutex_destroy(&tpg->lock);
> >>>>>> +
> >>>>>> +    return ret;
> >>>>>> +}
> >>>>>> +
> >>>>>> +void mali_c55_unregister_tpg(struct mali_c55 *mali_c55)
> >>>>>> +{
> >>>>>> +    struct mali_c55_tpg *tpg = &mali_c55->tpg;
> >>>>>> +
> >>>>>> +    if (!tpg->mali_c55)
> >>>>>> +        return;
> >>>>>> +
> >>>>>> +    v4l2_device_unregister_subdev(&tpg->sd);
> >>>>>> +    v4l2_subdev_cleanup(&tpg->sd);
> >>>>>> +    media_entity_cleanup(&tpg->sd.entity);
> >>>>>> +    v4l2_ctrl_handler_free(&tpg->ctrls.handler);
> >>>>> 
> >>>>> Free the control handler just after v4l2_device_unregister_subdev() to
> >>>>> match the order in mali_c55_register_tpg().
> >>>>>
> >>>>>> +    mutex_destroy(&tpg->lock);
> >>>>>> +}

-- 
Regards,

Laurent Pinchart




[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux