+ }
+
+ return 0;
+}
+
+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");
+ 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);
+
+ 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;
Drop this...
+ }
+
+ return 0;
And return ret here.
+}
+
+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");
+
+ 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 = mali_c55_interrupt_init(pdev);
+ if (ret)
+ return dev_err_probe(dev, ret,
+ "failed to initialise interrupts\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);
+ 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);
+
+ 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;
+
+ 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;
+ }
+
+ /* 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);
+
+ /*
+ * For now, few of the ISP's features are supported (pretty much just
+ * debayering and the colour space conversion). Flag the others as being
+ * bypassed so that they don't interfere.
+ *
+ * TODO: Support more features!
+ */
+ mali_c55_write(mali_c55, 0x18eac, 0x3c);
+ mali_c55_write(mali_c55, 0x18eb0, 0xf);
+ mali_c55_write(mali_c55, 0x18eb8, 0x3);
+ mali_c55_write(mali_c55, 0x18ebc, 0x7b);
+ mali_c55_write(mali_c55, 0x18ec0, 0x38);
+ mali_c55_write(mali_c55, MALI_C55_REG_FR_BYPASS, 0xf);
+ mali_c55_write(mali_c55, MALI_C55_REG_DS_BYPASS, 0xf);
+
+ /*
+ * 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);
+ mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_MASK_VECTOR, 0x39fc3fc);
+
+ pm_runtime_put(&pdev->dev);
+
+ 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",
+ },
+ {},
+};
+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),
+ .pm = &mali_c55_pm_ops,
+ },
+ .probe = mali_c55_probe,
+ .remove_new = mali_c55_remove,
+};
+
+module_platform_driver(mali_c55_driver);
+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..04196dee9665
--- /dev/null
+++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c
@@ -0,0 +1,682 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM Mali-C55 ISP Driver - Image signal processor
+ *
+ * Copyright (C) 2023 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-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_SRGGB8_1X8,
+ .bitwidth = MALI_C55_INPUT_WIDTH_8BIT,
+ .order = MALI_C55_BAYER_ORDER_RGGB,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG8_1X8,
+ .bitwidth = MALI_C55_INPUT_WIDTH_8BIT,
+ .order = MALI_C55_BAYER_ORDER_GRBG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG8_1X8,
+ .bitwidth = MALI_C55_INPUT_WIDTH_8BIT,
+ .order = MALI_C55_BAYER_ORDER_GBRG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR8_1X8,
+ .bitwidth = MALI_C55_INPUT_WIDTH_8BIT,
+ .order = MALI_C55_BAYER_ORDER_BGGR,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB10_1X10,
+ .bitwidth = MALI_C55_INPUT_WIDTH_10BIT,
+ .order = MALI_C55_BAYER_ORDER_RGGB,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG10_1X10,
+ .bitwidth = MALI_C55_INPUT_WIDTH_10BIT,
+ .order = MALI_C55_BAYER_ORDER_GRBG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG10_1X10,
+ .bitwidth = MALI_C55_INPUT_WIDTH_10BIT,
+ .order = MALI_C55_BAYER_ORDER_GBRG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR10_1X10,
+ .bitwidth = MALI_C55_INPUT_WIDTH_10BIT,
+ .order = MALI_C55_BAYER_ORDER_BGGR,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB12_1X12,
+ .bitwidth = MALI_C55_INPUT_WIDTH_12BIT,
+ .order = MALI_C55_BAYER_ORDER_RGGB,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG12_1X12,
+ .bitwidth = MALI_C55_INPUT_WIDTH_12BIT,
+ .order = MALI_C55_BAYER_ORDER_GRBG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG12_1X12,
+ .bitwidth = MALI_C55_INPUT_WIDTH_12BIT,
+ .order = MALI_C55_BAYER_ORDER_GBRG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR12_1X12,
+ .bitwidth = MALI_C55_INPUT_WIDTH_12BIT,
+ .order = MALI_C55_BAYER_ORDER_BGGR,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB14_1X14,
+ .bitwidth = MALI_C55_INPUT_WIDTH_14BIT,
+ .order = MALI_C55_BAYER_ORDER_RGGB,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG14_1X14,
+ .bitwidth = MALI_C55_INPUT_WIDTH_14BIT,
+ .order = MALI_C55_BAYER_ORDER_GRBG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG14_1X14,
+ .bitwidth = MALI_C55_INPUT_WIDTH_14BIT,
+ .order = MALI_C55_BAYER_ORDER_GBRG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR14_1X14,
+ .bitwidth = MALI_C55_INPUT_WIDTH_14BIT,
+ .order = MALI_C55_BAYER_ORDER_BGGR,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SRGGB16_1X16,
+ .bitwidth = MALI_C55_INPUT_WIDTH_16BIT,
+ .order = MALI_C55_BAYER_ORDER_RGGB,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGRBG16_1X16,
+ .bitwidth = MALI_C55_INPUT_WIDTH_16BIT,
+ .order = MALI_C55_BAYER_ORDER_GRBG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SGBRG16_1X16,
+ .bitwidth = MALI_C55_INPUT_WIDTH_16BIT,
+ .order = MALI_C55_BAYER_ORDER_GBRG,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_SBGGR16_1X16,
+ .bitwidth = MALI_C55_INPUT_WIDTH_16BIT,
+ .order = MALI_C55_BAYER_ORDER_BGGR,
+ .encoding = V4L2_PIXEL_ENC_BAYER,
+ },
+ {
+ .code = MEDIA_BUS_FMT_RGB202020_1X60,
+ .bitwidth = MALI_C55_INPUT_WIDTH_20BIT,
+ .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;
fmt++;
+
+ for (; fmt < &mali_c55_isp_fmts[ARRAY_SIZE(mali_c55_isp_fmts)]; ++fmt)
Here, too?
+ return fmt;
+
+ 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) {
+ 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);
+ 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;
+ struct v4l2_subdev_state *state;
+ struct v4l2_rect *crop;
+ struct v4l2_subdev *sd;
+ u32 val;
+ int ret;
+
+ sd = &mali_c55->isp.sd;
+
+ mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG,
+ MALI_C55_REG_MCU_CONFIG_WRITE_MASK,
+ MALI_C55_REG_MCU_CONFIG_WRITE_PING);
+
+ /* Apply input windowing */
+ state = v4l2_subdev_get_locked_active_state(sd);
+ 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 << 16);
+ 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, cfg->bitwidth << 16);
+
+ 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);
+
+ return 0;
+}
+
+int mali_c55_isp_s_stream(struct mali_c55_isp *isp, int enable)
+{
+ struct mali_c55 *mali_c55 = isp->mali_c55;
+ struct media_pad *source_pad;
+ struct media_pad *sink_pad;
+ int ret;
+
+ if (!enable) {
+ if (isp->source)
+ v4l2_subdev_call(isp->source, video, s_stream, false);
+ isp->source = NULL;
+
+ mali_c55_isp_stop(mali_c55);
+
+ return 0;
+ }
+
+ sink_pad = &isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO];
+ source_pad = media_pad_remote_pad_unique(sink_pad);
+ if (IS_ERR(source_pad)) {
+ dev_err(mali_c55->dev, "Failed to get source for ISP\n");
+ return PTR_ERR(source_pad);
+ }
+
+ isp->source = media_entity_to_v4l2_subdev(source_pad->entity);
+
+ ret = mali_c55_isp_start(mali_c55);
+ if (ret) {
+ dev_err(mali_c55->dev, "Failed to start ISP\n");
+ isp->source = NULL;
+ return ret;
+ }
+
+ ret = v4l2_subdev_call(isp->source, video, s_stream, true);
+ 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;
+ const struct mali_c55_isp_fmt *cfg;
+ struct v4l2_mbus_framefmt *src_fmt;
+ 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)
+ 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_SRGGB12_1X12;
+ fmt->field = V4L2_FIELD_NONE;
+
+ /*
+ * 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);
+
+ *v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SINK_VIDEO) = *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.
+ */
+ 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)
+ 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;
+ struct v4l2_rect *crop;
+
+ if (sel->pad || sel->target != V4L2_SEL_TGT_CROP)
+ 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;
+
+ 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,
+};
+
+static const struct v4l2_subdev_ops mali_c55_isp_ops = {
+ .pad = &mali_c55_isp_pad_ops,
+};
+
+static int mali_c55_isp_init_state(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *sd_state)
+{
+ 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_SRGGB12_1X12;
+
+ *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,
+};
+
+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;
+ unsigned int pad;
+ int ret;
+
+ for (pad = 0; pad < subdev->entity.num_pads; pad++)
+ if (subdev->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE)
Could you use v4l2_create_fwnode_links_to_pad() instead?
+ break;
+
+ if (pad == subdev->entity.num_pads) {
+ dev_err(mali_c55->dev, "failed to find source pad for %s\n",
+ subdev->name);
+ return -ENOTCONN;
+ }
+
+ /*
+ * 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 = media_create_pad_link(&subdev->entity, pad,
+ &isp->sd.entity,
+ MALI_C55_ISP_PAD_SINK_VIDEO,
+ 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;
+ sd->entity.ops = &mali_c55_isp_media_ops;
+ sd->entity.function = MEDIA_ENT_F_PROC_VIDEO_ISP;
+ sd->owner = THIS_MODULE;
+ 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;
+ 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;
+
+ mutex_init(&isp->lock);
+
+ 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_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..9bfdffd33ec0
--- /dev/null
+++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h
@@ -0,0 +1,180 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ARM Mali-C55 ISP Driver - Register definitions
+ *
+ * Copyright (C) 2023 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 0xffffffff
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_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_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 << 8)
+#define MALI_C55_ISP_RAW_BYPASS_RGB_FR_BYPASS (1 << 8)
+#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_C5_TEST_PATTERN_RGB 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_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))
+
+/* 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
+
+#define MALI_C55_OUTPUT_PLANE_ALT0 0
+#define MALI_C55_OUTPUT_PLANE_ALT1 1
+#define MALI_C55_OUTPUT_PLANE_ALT2 2
+
+#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..c2ec33a599ce
--- /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) 2023 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
+
+static unsigned int mali_c55_scaler_h_filter_coefficients[8][64] = {
const.
+ { /* 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 unsigned int mali_c55_scaler_v_filter_coefficients[8][64] = {
const.
+ { /* 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;
+ unsigned int top;
+ unsigned int bottom;
+};
+
+static struct mali_c55_resizer_coef_bank mali_c55_coefficient_banks[] = {
const.
+ {
+ .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,
+ },
+};
+
+static unsigned int mali_c55_calculate_bank_num(struct mali_c55 *mali_c55,
+ unsigned int crop,
+ unsigned int scale)
+{
+ unsigned int tmp;
+ unsigned int i;
+
+ tmp = (scale * 1000) / 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;
+}
+
+#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..5c15a57446a1
--- /dev/null
+++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c
@@ -0,0 +1,678 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM Mali-C55 ISP Driver - Image signal processor
+ *
+ * Copyright (C) 2023 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 1048576
+
+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",
+};
+
+/*
+ * 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 int __mali_c55_rzr_set_routing(struct v4l2_subdev *sd,
+ struct v4l2_subdev_state *state,
+ struct v4l2_subdev_krouting *routing)
+{
+ struct mali_c55_resizer *rzr = container_of(sd, struct mali_c55_resizer,
+ sd);
+ unsigned int active_sink = UINT_MAX;
+ 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");
+ 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;
+ }
+
+ 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;
+ fmt->field = V4L2_FIELD_NONE;
+
+ if (active_sink == MALI_C55_RZR_SINK_PAD) {
+ fmt->code = MEDIA_BUS_FMT_RGB121212_1X36;
+
+ crop->left = crop->top = 0;
+ crop->width = MALI_C55_DEFAULT_WIDTH;
+ crop->height = MALI_C55_DEFAULT_HEIGHT;
+
+ *compose = *crop;
+ } else {
+ fmt->code = MEDIA_BUS_FMT_SRGGB12_1X12;
+ }
+
+ /* Propagate the format to the source pad */
+ *v4l2_subdev_state_get_format(state, MALI_C55_RZR_SOURCE_PAD, 0) = *fmt;
+
+ 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.
+ */
+
+ if (sink_pad == MALI_C55_RZR_SINK_BYPASS_PAD) {
+ if (code->index)
+ return -EINVAL;
+
+ code->code = sink_fmt->code;
+ 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);
+
+ sink_pad = mali_c55_rzr_get_active_sink(state);
+ if (sink_pad == MALI_C55_RZR_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;
+
+ 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.
+ */
+ fmt->code = sink_fmt->code;
+
+ /* 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;
+
+ *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);
+ 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);
+}
+
+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 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;
+ }
+
+ /*
+ * 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);
+
+ 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)
+{
+ struct mali_c55 *mali_c55 = rzr->mali_c55;
+ struct v4l2_subdev *sd = &rzr->sd;
+ struct v4l2_subdev_state *state;
+ struct v4l2_mbus_framefmt *fmt;
+ struct v4l2_rect *crop, *scale;
+ unsigned int reg_offset;
+ unsigned int sink_pad;
+ u32 bypass = 0;
+
+ reg_offset = rzr->cap_dev->reg_offset;
+
+ 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);
+
+ /* Verify if cropping and scaling 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);
+ scale = v4l2_subdev_state_get_compose(state, MALI_C55_RZR_SINK_PAD, 0);
+
+ if (fmt->width == crop->width && fmt->height == crop->height)
+ bypass |= MALI_C55_BYPASS_CROP;
+ if (crop->width == scale->width && crop->height == scale->height)
+ bypass |= MALI_C55_BYPASS_SCALER;
+
+ if (!(bypass & MALI_C55_BYPASS_CROP)) {
+ mali_c55_write(mali_c55, MALI_C55_REG_CROP_EN(reg_offset),
+ MALI_C55_CROP_ENABLE);
+ 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);
+ }
+
+ if (!(bypass & MALI_C55_BYPASS_SCALER)) {
+ /* Program the V/H scaling factor in Q4.20 format. */
+ u64 h_scale = crop->width * MALI_C55_RZR_SCALER_FACTOR;
+ u64 v_scale = crop->height * MALI_C55_RZR_SCALER_FACTOR;
+ unsigned int h_bank, v_bank;
+
+ 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);
+ }
+
+ 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);
+
+unlock_state:
+ 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;
+}
+
+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)
+{
+ unsigned int scaler_filt_coefmem_addrs[][2] = {
+ [MALI_C55_RZR_FR] = {
+ 0x034A8, /* hfilt */
+ 0x044A8 /* vfilt */
+ },
+ [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 += 4;
+ vaddr += 4;
+ }
+ }
+}
+
+int mali_c55_register_resizers(struct mali_c55 *mali_c55)
+{
+ unsigned int i;
+ int ret;
+
+ for (i = 0; i < MALI_C55_NUM_RZRS; ++i) {
+ 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;
+
+ 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);
+
+ 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->owner = THIS_MODULE;
+ sd->internal_ops = &mali_c55_resizer_internal_ops;
+ snprintf(sd->name, ARRAY_SIZE(sd->name), "%s %s",
+ 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..34b8948328c0
--- /dev/null
+++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c
@@ -0,0 +1,425 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * ARM Mali-C55 ISP Driver - Test pattern generator
+ *
+ * Copyright (C) 2023 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
+#define MALI_C55_TPG_PIXEL_RATE 100000000
+
+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_SRGGB16_1X16,
+ /*
+ * This is a lie. In RGB mode the Test Pattern Generator actually output
+ * 16-bits-per-colour data. However, RGB data follows one of the Bypass
+ * paths which has a 12-bit limit at the insertion point, meaning it
+ * would be truncated there to match the internal 12-bit format that
+ * would be output from the debayering block. The same is true of RGB
+ * data output by a sensor and streamed to the ISP's input port, however
+ * in that case the ISP's input port requires that data be converted to
+ * a 20-bit MSB aligned format. Given:
+ *
+ * 1. Our chosen topology represents the TPG as a subdevice
+ * linked to the ISP's input port.
+ * 2. We need to restrict the ISP's sink pad to only accepting that
+ * 20-bit RGB format from sensors / CSI-2 receivers.
+ * 3. All the data ultimately ends up in the same format anyway and
+ * these data from the TPG are purely internal to the ISP
+ *
+ * It seems best to reduce the programming complexity by simply
+ * pretending that the TPG outputs data in the same format that the ISP
+ * input port requires, even though it doesn't really.
+ */
+ 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 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);
+
+ /*
+ * 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;
+}
+
+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);
+
+ 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 << 16);
+ break;
+ default:
+ dev_err(mali_c55->dev, "invalid V4L2 control ID\n");
+ return -EINVAL;
+ }
+
+ 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,
+ MALI_C55_REG_GEN_VIDEO_MULTI_MASK);
+
+ 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_C5_TEST_PATTERN_RGB,
+ fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ?
+ MALI_C5_TEST_PATTERN_RGB : 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);
+ } else {
+ /*
+ * 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 .s_stream() instead, as the same register is
+ * also used to indicate the size of the data coming from the
+ * sensor.
+ */
+ mali_c55_tpg_configure(mali_c55, sd);
+ __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,
+};
+
+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->pad >= sd->entity.num_pads)
+ return -EINVAL;
+
+ 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)
+{
+ if (fse->index > 0 || fse->pad > sd->entity.num_pads)
+ 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_SRGGB16_1X16;
+
+ /*
+ * 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;
+
+ __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);
+
+ 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)
+{
+ struct v4l2_mbus_framefmt *fmt;
+
+ 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_SRGGB16_1X16;
+
+ 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);
+ 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);
+ 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;
+ 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;
+ sd->owner = THIS_MODULE;
+ 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;
+ 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;
+ }
+
+ 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);
+ mutex_destroy(&tpg->lock);
+}