diff options
Diffstat (limited to 'drivers/media/platform/arm')
| -rw-r--r-- | drivers/media/platform/arm/Kconfig | 5 | ||||
| -rw-r--r-- | drivers/media/platform/arm/Makefile | 2 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/Kconfig | 18 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/Makefile | 11 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-capture.c | 959 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-common.h | 310 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-core.c | 917 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-isp.c | 665 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-params.c | 819 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-registers.h | 449 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-resizer.c | 1156 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-stats.c | 323 | ||||
| -rw-r--r-- | drivers/media/platform/arm/mali-c55/mali-c55-tpg.c | 437 |
13 files changed, 6071 insertions, 0 deletions
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..5b084b3c3340 --- /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 ARCH_VEXPRESS || ARCH_RENESAS || COMPILE_TEST + depends on V4L_PLATFORM_DRIVERS + depends on VIDEO_DEV && OF + select GENERIC_PHY_MIPI_DPHY + select MEDIA_CONTROLLER + select V4L2_FWNODE + select V4L2_ISP + select VIDEO_V4L2_SUBDEV_API + select VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_VMALLOC + 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..d5718b0b23e0 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/Makefile @@ -0,0 +1,11 @@ +# SPDX-License-Identifier: GPL-2.0 + +mali-c55-y := mali-c55-capture.o \ + mali-c55-core.o \ + mali-c55-isp.o \ + mali-c55-params.o \ + mali-c55-resizer.o \ + mali-c55-stats.o \ + mali-c55-tpg.o + +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..7aaa5c3f7354 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-capture.c @@ -0,0 +1,959 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Video capture devices + * + * Copyright (C) 2025 Ideas on Board Oy + */ + +#include <linux/cleanup.h> +#include <linux/minmax.h> +#include <linux/lockdep.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_format_info mali_c55_fmts[] = { + /* + * This table is missing some entries which need further work or + * investigation: + * + * 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_XBGR32, + .mbus_codes = { + MEDIA_BUS_FMT_RGB121212_1X36, + MEDIA_BUS_FMT_RGB202020_1X60, + }, + .is_raw = false, + .registers = { + .base_mode = MALI_C55_OUTPUT_RGB32, + .uv_plane = MALI_C55_OUTPUT_PLANE_ALT0 + } + }, + { + .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 + } + }, +}; + +void mali_c55_cap_dev_write(struct mali_c55_cap_dev *cap_dev, unsigned int addr, + u32 val) +{ + mali_c55_ctx_write(cap_dev->mali_c55, addr + cap_dev->reg_offset, val); +} + +static u32 mali_c55_cap_dev_read(struct mali_c55_cap_dev *cap_dev, unsigned int addr) +{ + return mali_c55_ctx_read(cap_dev->mali_c55, addr + cap_dev->reg_offset); +} + +static void mali_c55_cap_dev_update_bits(struct mali_c55_cap_dev *cap_dev, + unsigned int addr, u32 mask, u32 val) +{ + u32 orig, tmp; + + orig = mali_c55_cap_dev_read(cap_dev, addr); + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + mali_c55_cap_dev_write(cap_dev, addr, tmp); +} + +static bool +mali_c55_mbus_code_can_produce_fmt(const struct mali_c55_format_info *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_format_info * +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", +}; + +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_format_info *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->format.format; + cap_fmt = cap_dev->format.info; + + 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->format.format.num_planes) + return -EINVAL; + + for (i = 0; i < cap_dev->format.format.num_planes; i++) + if (sizes[i] < cap_dev->format.format.plane_fmt[i].sizeimage) + return -EINVAL; + } else { + *num_planes = cap_dev->format.format.num_planes; + for (i = 0; i < cap_dev->format.format.num_planes; i++) + sizes[i] = cap_dev->format.format.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->planes_pending = cap_dev->format.format.num_planes; + + for (i = 0; i < cap_dev->format.format.num_planes; i++) { + unsigned long size = cap_dev->format.format.plane_fmt[i].sizeimage; + + vb2_set_plane_payload(vb, i, size); + } + + buf->vb.field = V4L2_FIELD_NONE; + + guard(spinlock)(&cap_dev->buffers.lock); + list_add_tail(&buf->queue, &cap_dev->buffers.input); +} + +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->format.format.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 v4l2_pix_format_mplane *pix_mp; + struct mali_c55_buffer *buf; + dma_addr_t *addrs; + + scoped_guard(spinlock, &cap_dev->buffers.lock) { + buf = list_first_entry_or_null(&cap_dev->buffers.input, + struct mali_c55_buffer, queue); + if (buf) + list_del(&buf->queue); + } + + if (!buf) { + /* + * If we underflow then we can tell the ISP that we don't want + * to write out the next frame. + */ + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_Y_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, + 0x00); + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_UV_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, + 0x00); + return; + } + + pix_mp = &cap_dev->format.format; + + mali_c55_cap_dev_update_bits(cap_dev, MALI_C55_REG_Y_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, + MALI_C55_WRITER_FRAME_WRITE_ENABLE); + if (cap_dev->format.info->registers.uv_plane) + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_UV_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, + MALI_C55_WRITER_FRAME_WRITE_ENABLE); + + addrs = buf->addrs; + mali_c55_cap_dev_write(cap_dev, + MALI_C55_REG_Y_WRITER_BANKS_BASE, + addrs[MALI_C55_PLANE_Y]); + mali_c55_cap_dev_write(cap_dev, + MALI_C55_REG_UV_WRITER_BANKS_BASE, + addrs[MALI_C55_PLANE_UV]); + + mali_c55_cap_dev_write(cap_dev, + MALI_C55_REG_Y_WRITER_OFFSET, + pix_mp->plane_fmt[MALI_C55_PLANE_Y].bytesperline); + mali_c55_cap_dev_write(cap_dev, + MALI_C55_REG_UV_WRITER_OFFSET, + pix_mp->plane_fmt[MALI_C55_PLANE_UV].bytesperline); + + guard(spinlock)(&cap_dev->buffers.processing_lock); + list_add_tail(&buf->queue, &cap_dev->buffers.processing); +} + +/** + * 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 *buf; + + scoped_guard(spinlock, &cap_dev->buffers.processing_lock) { + buf = list_first_entry_or_null(&cap_dev->buffers.processing, + struct mali_c55_buffer, queue); + + /* + * If the stream was stopped, the buffer might have been sent + * back to userspace already. + */ + if (!buf || --buf->planes_pending) + return; + + list_del(&buf->queue); + } + + /* If the other plane is also done... */ + buf->vb.vb2_buf.timestamp = ktime_get_boottime_ns(); + buf->vb.sequence = isp->frame_sequence++; + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +static void mali_c55_cap_dev_stream_disable(struct mali_c55_cap_dev *cap_dev) +{ + mali_c55_cap_dev_update_bits(cap_dev, MALI_C55_REG_Y_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00); + mali_c55_cap_dev_update_bits(cap_dev, MALI_C55_REG_UV_WRITER_MODE, + MALI_C55_WRITER_FRAME_WRITE_MASK, 0x00); +} + +static void mali_c55_cap_dev_stream_enable(struct mali_c55_cap_dev *cap_dev) +{ + /* + * 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. + */ + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_Y_WRITER_BANKS_CONFIG, + MALI_C55_REG_Y_WRITER_MAX_BANKS_MASK, 0); + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_UV_WRITER_BANKS_CONFIG, + MALI_C55_REG_UV_WRITER_MAX_BANKS_MASK, 0); + + mali_c55_set_next_buffer(cap_dev); +} + +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; + + scoped_guard(spinlock, &cap_dev->buffers.lock) { + list_for_each_entry_safe(buf, tmp, &cap_dev->buffers.input, + queue) { + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } + } + + guard(spinlock)(&cap_dev->buffers.processing_lock); + list_for_each_entry_safe(buf, tmp, &cap_dev->buffers.processing, queue) { + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } +} + +static void mali_c55_cap_dev_format_configure(struct mali_c55_cap_dev *cap_dev) +{ + const struct mali_c55_format_info *capture_format = cap_dev->format.info; + const struct v4l2_pix_format_mplane *pix_mp = &cap_dev->format.format; + const struct v4l2_format_info *info; + + info = v4l2_format_info(pix_mp->pixelformat); + if (WARN_ON(!info)) + return; + + mali_c55_cap_dev_write(cap_dev, MALI_C55_REG_Y_WRITER_MODE, + capture_format->registers.base_mode); + mali_c55_cap_dev_write(cap_dev, MALI_C55_REG_ACTIVE_OUT_Y_SIZE, + MALI_C55_REG_ACTIVE_OUT_SIZE_W(pix_mp->width) | + MALI_C55_REG_ACTIVE_OUT_SIZE_H(pix_mp->height)); + + if (info->mem_planes > 1) { + mali_c55_cap_dev_write(cap_dev, MALI_C55_REG_UV_WRITER_MODE, + capture_format->registers.base_mode); + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_UV_WRITER_MODE, + MALI_C55_WRITER_SUBMODE_MASK, + MALI_C55_WRITER_SUBMODE(capture_format->registers.uv_plane)); + + mali_c55_cap_dev_write(cap_dev, MALI_C55_REG_ACTIVE_OUT_UV_SIZE, + 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) { + mali_c55_cap_dev_write(cap_dev, MALI_C55_REG_CS_CONV_CONFIG, + MALI_C55_CS_CONV_MATRIX_MASK); + + if (info->hdiv > 1) + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_CS_CONV_CONFIG, + MALI_C55_CS_CONV_HORZ_DOWNSAMPLE_MASK, + MALI_C55_CS_CONV_HORZ_DOWNSAMPLE_ENABLE); + if (info->vdiv > 1) + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_CS_CONV_CONFIG, + MALI_C55_CS_CONV_VERT_DOWNSAMPLE_MASK, + MALI_C55_CS_CONV_VERT_DOWNSAMPLE_ENABLE); + if (info->hdiv > 1 || info->vdiv > 1) + mali_c55_cap_dev_update_bits(cap_dev, + MALI_C55_REG_CS_CONV_CONFIG, + MALI_C55_CS_CONV_FILTER_MASK, + MALI_C55_CS_CONV_FILTER_ENABLE); + } +} + +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 *rsz = cap_dev->rsz; + struct mali_c55_isp *isp = &mali_c55->isp; + int ret; + + guard(mutex)(&isp->capture_lock); + + ret = pm_runtime_resume_and_get(mali_c55->dev); + if (ret) + goto err_return_buffers; + + ret = video_device_pipeline_alloc_start(&cap_dev->vdev); + if (ret) { + dev_dbg(mali_c55->dev, "%s failed to start media pipeline\n", + cap_dev->vdev.name); + goto err_pm_put; + } + + mali_c55_cap_dev_format_configure(cap_dev); + mali_c55_cap_dev_stream_enable(cap_dev); + + ret = v4l2_subdev_enable_streams(&rsz->sd, MALI_C55_RSZ_SOURCE_PAD, + BIT(0)); + if (ret) + goto err_disable_cap_dev; + + if (mali_c55_pipeline_ready(mali_c55)) { + ret = v4l2_subdev_enable_streams(&mali_c55->isp.sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + if (ret < 0) + goto err_disable_rsz; + } + + return 0; + +err_disable_rsz: + v4l2_subdev_disable_streams(&rsz->sd, MALI_C55_RSZ_SOURCE_PAD, BIT(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_autosuspend(mali_c55->dev); +err_return_buffers: + 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 *rsz = cap_dev->rsz; + struct mali_c55_isp *isp = &mali_c55->isp; + + guard(mutex)(&isp->capture_lock); + + if (mali_c55_pipeline_ready(mali_c55)) { + if (v4l2_subdev_is_streaming(&isp->sd)) + v4l2_subdev_disable_streams(&isp->sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + } + + v4l2_subdev_disable_streams(&rsz->sd, MALI_C55_RSZ_SOURCE_PAD, BIT(0)); + 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_autosuspend(mali_c55->dev); +} + +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, + .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_format_info *capture_format; + const struct v4l2_format_info *info; + struct v4l2_plane_pix_format *plane, *y_plane; + unsigned int padding; + 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); + + 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); + pix_mp->num_planes = info->mem_planes; + memset(pix_mp->plane_fmt, 0, sizeof(pix_mp->plane_fmt)); + + y_plane = &pix_mp->plane_fmt[0]; + y_plane->bytesperline = clamp(y_plane->bytesperline, + info->bpp[0] * pix_mp->width, 65535U); + + /* + * The ISP requires that the stride be aligned to 16-bytes. This is not + * detailed in the documentation but has been verified experimentally. + */ + y_plane->bytesperline = ALIGN(y_plane->bytesperline, 16); + y_plane->sizeimage = y_plane->bytesperline * pix_mp->height; + + padding = y_plane->bytesperline - (pix_mp->width * info->bpp[0]); + + 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) + padding; + 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]; + y_plane->sizeimage += plane->sizeimage; + } + } +} + +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) +{ + mali_c55_try_fmt(pix_mp); + + cap_dev->format.format = *pix_mp; + cap_dev->format.info = mali_c55_format_from_pix(pix_mp->pixelformat); +} + +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->format.format; + + 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)) + 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, +}; + +static int mali_c55_register_cap_dev(struct mali_c55 *mali_c55, + enum mali_c55_cap_devs cap_dev_id) +{ + struct mali_c55_cap_dev *cap_dev = &mali_c55->cap_devs[cap_dev_id]; + struct v4l2_pix_format_mplane pix_mp; + struct video_device *vdev; + struct vb2_queue *vb2q; + int ret; + + vdev = &cap_dev->vdev; + vb2q = &cap_dev->queue; + + cap_dev->mali_c55 = mali_c55; + mutex_init(&cap_dev->lock); + INIT_LIST_HEAD(&cap_dev->buffers.input); + INIT_LIST_HEAD(&cap_dev->buffers.processing); + spin_lock_init(&cap_dev->buffers.lock); + spin_lock_init(&cap_dev->buffers.processing_lock); + + switch (cap_dev_id) { + case MALI_C55_CAP_DEV_FR: + cap_dev->rsz = &mali_c55->resizers[MALI_C55_RSZ_FR]; + cap_dev->reg_offset = MALI_C55_CAP_DEV_FR_REG_OFFSET; + break; + case MALI_C55_CAP_DEV_DS: + cap_dev->rsz = &mali_c55->resizers[MALI_C55_RSZ_DS]; + cap_dev->reg_offset = MALI_C55_CAP_DEV_DS_REG_OFFSET; + break; + default: + 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", + cap_dev->vdev.name); + goto err_cleanup_media_entity; + } + + strscpy(cap_dev->vdev.name, capture_device_names[cap_dev_id], + 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", + cap_dev->vdev.name); + 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); + + return ret; +} + +int mali_c55_register_capture_devs(struct mali_c55 *mali_c55) +{ + int ret; + + ret = mali_c55_register_cap_dev(mali_c55, MALI_C55_CAP_DEV_FR); + if (ret) + return ret; + + if (mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) { + ret = mali_c55_register_cap_dev(mali_c55, MALI_C55_CAP_DEV_DS); + if (ret) { + mali_c55_unregister_capture_devs(mali_c55); + return ret; + } + } + + return 0; +} + +static void mali_c55_unregister_cap_dev(struct mali_c55 *mali_c55, + enum mali_c55_cap_devs cap_dev_id) +{ + struct mali_c55_cap_dev *cap_dev = &mali_c55->cap_devs[cap_dev_id]; + + if (!video_is_registered(&cap_dev->vdev)) + return; + + vb2_video_unregister_device(&cap_dev->vdev); + media_entity_cleanup(&cap_dev->vdev.entity); + mutex_destroy(&cap_dev->lock); +} + +void mali_c55_unregister_capture_devs(struct mali_c55 *mali_c55) +{ + mali_c55_unregister_cap_dev(mali_c55, MALI_C55_CAP_DEV_FR); + if (mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) + mali_c55_unregister_cap_dev(mali_c55, MALI_C55_CAP_DEV_DS); +} 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..31c1deaca146 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-common.h @@ -0,0 +1,310 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ARM Mali-C55 ISP Driver - Common definitions + * + * Copyright (C) 2025 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/reset.h> +#include <linux/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-isp.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 + +#define MALI_C55_NUM_CLKS 3 +#define MALI_C55_NUM_RESETS 3 + +struct device; +struct mali_c55; +struct mali_c55_cap_dev; +struct media_pipeline; +struct mali_c55_params_buffer; +struct platform_device; +struct resource; + +enum mali_c55_isp_pads { + MALI_C55_ISP_PAD_SINK_VIDEO, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + MALI_C55_ISP_PAD_SOURCE_BYPASS, + MALI_C55_ISP_PAD_SOURCE_STATS, + MALI_C55_ISP_PAD_SINK_PARAMS, + MALI_C55_ISP_NUM_PADS, +}; + +struct mali_c55_tpg { + struct mali_c55 *mali_c55; + struct v4l2_subdev sd; + struct media_pad pad; + struct mali_c55_tpg_ctrls { + struct v4l2_ctrl_handler handler; + struct v4l2_ctrl *vblank; + } ctrls; +}; + +struct mali_c55_isp { + struct mali_c55 *mali_c55; + struct v4l2_subdev sd; + struct media_pad pads[MALI_C55_ISP_NUM_PADS]; + struct v4l2_ctrl_handler handler; + struct media_pad *remote_src; + /* Mutex to guard vb2 start/stop streaming */ + struct mutex capture_lock; + unsigned int frame_sequence; +}; + +enum mali_c55_resizer_ids { + MALI_C55_RSZ_FR, + MALI_C55_RSZ_DS, + MALI_C55_NUM_RSZS, +}; + +enum mali_c55_rsz_pads { + MALI_C55_RSZ_SINK_PAD, + MALI_C55_RSZ_SOURCE_PAD, + MALI_C55_RSZ_SINK_BYPASS_PAD, + MALI_C55_RSZ_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_RSZ_NUM_PADS]; + unsigned int num_routes; +}; + +enum mali_c55_cap_devs { + MALI_C55_CAP_DEV_FR, + MALI_C55_CAP_DEV_DS, + MALI_C55_NUM_CAP_DEVS +}; + +struct mali_c55_format_info { + u32 fourcc; + /* + * The output formats can be produced by a couple of different media bus + * formats, depending on how the ISP is configured. + */ + unsigned int mbus_codes[2]; + bool is_raw; + struct { + u32 base_mode; + u32 uv_plane; + } registers; +}; + +struct mali_c55_isp_format_info { + u32 code; + u32 shifted_code; + bool bypass; + u32 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; + unsigned int planes_pending; + struct list_head queue; + dma_addr_t addrs[MALI_C55_NUM_PLANES]; +}; + +struct mali_c55_cap_dev { + struct mali_c55 *mali_c55; + struct mali_c55_resizer *rsz; + struct video_device vdev; + struct media_pad pad; + struct vb2_queue queue; + /* Mutex to provide to vb2 */ + struct mutex lock; + unsigned int reg_offset; + + struct { + const struct mali_c55_format_info *info; + struct v4l2_pix_format_mplane format; + } format; + + struct { + /* Spinlock to guard buffer queue */ + spinlock_t lock; + /* Spinlock to guard the queue of buffers being processed */ + spinlock_t processing_lock; + struct list_head input; + struct list_head processing; + } buffers; +}; + +struct mali_c55_stats_buf { + struct vb2_v4l2_buffer vb; + unsigned int segments_remaining; + struct list_head queue; + bool failed; +}; + +struct mali_c55_params_buf { + struct vb2_v4l2_buffer vb; + struct list_head queue; + struct v4l2_isp_params_buffer *config; +}; + +struct mali_c55_stats { + struct mali_c55 *mali_c55; + struct video_device vdev; + struct vb2_queue queue; + struct media_pad pad; + /* Mutex to provide to vb2 */ + struct mutex lock; + + struct { + /* Spinlock to guard buffer queue */ + spinlock_t lock; + struct list_head queue; + } buffers; +}; + +struct mali_c55_params { + struct mali_c55 *mali_c55; + struct video_device vdev; + struct vb2_queue queue; + struct media_pad pad; + /* Mutex to provide to vb2 */ + struct mutex lock; + + struct { + /* Spinlock to guard buffer queue */ + spinlock_t lock; + struct list_head queue; + } buffers; +}; + +enum mali_c55_config_spaces { + MALI_C55_CONFIG_PONG, + MALI_C55_CONFIG_PING, +}; + +/** + * struct mali_c55_context - Fields relating to a single camera context + * + * @mali_c55: Pointer to the main struct mali_c55 + * @registers: A pointer to some allocated memory holding register + * values to be written to the hardware at frame interrupt + * @base: Base address of the config space in the hardware + * @lock: A spinlock to protect against writes to @registers whilst that + * space is being copied to the hardware + * @list: A list head to facilitate a context queue + */ +struct mali_c55_context { + struct mali_c55 *mali_c55; + u32 *registers; + phys_addr_t base; + /* Spinlock to prevent simultaneous access of register space */ + spinlock_t lock; + struct list_head list; +}; + +struct mali_c55 { + struct device *dev; + void __iomem *base; + struct clk_bulk_data clks[MALI_C55_NUM_CLKS]; + struct reset_control_bulk_data resets[MALI_C55_NUM_RESETS]; + int irqnum; + + u16 capabilities; + bool inline_mode; + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct v4l2_async_notifier notifier; + struct media_pipeline pipe; + + struct mali_c55_tpg tpg; + struct mali_c55_isp isp; + struct mali_c55_resizer resizers[MALI_C55_NUM_RSZS]; + struct mali_c55_cap_dev cap_devs[MALI_C55_NUM_CAP_DEVS]; + struct mali_c55_params params; + struct mali_c55_stats stats; + + struct mali_c55_context context; + u32 next_config; +}; + +void mali_c55_write(struct mali_c55 *mali_c55, unsigned int addr, u32 val); +void mali_c55_cap_dev_write(struct mali_c55_cap_dev *cap_dev, unsigned int addr, + u32 val); +void mali_c55_update_bits(struct mali_c55 *mali_c55, unsigned int addr, + u32 mask, u32 val); +u32 mali_c55_read(struct mali_c55 *mali_c55, unsigned int addr); +void mali_c55_ctx_write(struct mali_c55 *mali_c55, unsigned int addr, u32 val); +u32 mali_c55_ctx_read(struct mali_c55 *mali_c55, unsigned int addr); +void mali_c55_ctx_update_bits(struct mali_c55 *mali_c55, unsigned int addr, + u32 mask, u32 val); + +int mali_c55_config_write(struct mali_c55_context *ctx, + enum mali_c55_config_spaces cfg_space, + bool force_synchronous); + +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); +int mali_c55_register_stats(struct mali_c55 *mali_c55); +void mali_c55_unregister_stats(struct mali_c55 *mali_c55); +int mali_c55_register_params(struct mali_c55 *mali_c55); +void mali_c55_unregister_params(struct mali_c55 *mali_c55); +struct mali_c55_context *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); + +const struct mali_c55_isp_format_info * +mali_c55_isp_fmt_next(const struct mali_c55_isp_format_info *fmt); +const struct mali_c55_isp_format_info * +mali_c55_isp_get_mbus_config_by_code(u32 code); +const struct mali_c55_isp_format_info * +mali_c55_isp_get_mbus_config_by_shifted_code(u32 code); +const struct mali_c55_isp_format_info * +mali_c55_isp_get_mbus_config_by_index(u32 index); +bool mali_c55_pipeline_ready(struct mali_c55 *mali_c55); +void mali_c55_stats_fill_buffer(struct mali_c55 *mali_c55, + enum mali_c55_config_spaces cfg_space); +void mali_c55_params_write_config(struct mali_c55 *mali_c55); + +#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..43b834459ccf --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-core.c @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Core driver code + * + * Copyright (C) 2025 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/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/reset.h> +#include <linux/slab.h> +#include <linux/string.h> + +#include <media/media-entity.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mc.h> +#include <media/videobuf2-core.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 const unsigned int config_space_addrs[] = { + [MALI_C55_CONFIG_PING] = 0x0ab6c, + [MALI_C55_CONFIG_PONG] = 0x22b2c, +}; + +static const char * const mali_c55_clk_names[MALI_C55_NUM_CLKS] = { + "vclk", + "aclk", + "hclk", +}; + +static const char * const mali_c55_reset_names[MALI_C55_NUM_RESETS] = { + "vresetn", + "aresetn", + "hresetn", +}; + +/* + * 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 + * untouched whilst a frame is being processed and the 'inactive' space + * configured ready to be switched to during the blanking period before the next + * 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. For now + * though we will simply use a CPU write and target DMA transfers of the config + * space in the future. + * + * As groundwork for that path 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) +{ + WARN_ON(addr >= MALI_C55_REG_CONFIG_SPACES_OFFSET); + + writel(val, mali_c55->base + addr); +} + +u32 mali_c55_read(struct mali_c55 *mali_c55, unsigned int addr) +{ + WARN_ON(addr >= MALI_C55_REG_CONFIG_SPACES_OFFSET); + + 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, new; + + orig = mali_c55_read(mali_c55, addr); + + new = orig & ~mask; + new |= val & mask; + + if (new != orig) + mali_c55_write(mali_c55, addr, new); +} + +static void __mali_c55_ctx_write(struct mali_c55_context *ctx, + unsigned int addr, u32 val) +{ + addr = (addr - MALI_C55_REG_CONFIG_SPACES_OFFSET) / 4; + ctx->registers[addr] = val; +} + +void mali_c55_ctx_write(struct mali_c55 *mali_c55, unsigned int addr, u32 val) +{ + struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55); + + WARN_ON(addr < MALI_C55_REG_CONFIG_SPACES_OFFSET); + + spin_lock(&ctx->lock); + __mali_c55_ctx_write(ctx, addr, val); + spin_unlock(&ctx->lock); +} + +static u32 __mali_c55_ctx_read(struct mali_c55_context *ctx, unsigned int addr) +{ + addr = (addr - MALI_C55_REG_CONFIG_SPACES_OFFSET) / 4; + return ctx->registers[addr]; +} + +u32 mali_c55_ctx_read(struct mali_c55 *mali_c55, unsigned int addr) +{ + struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55); + u32 val; + + WARN_ON(addr < MALI_C55_REG_CONFIG_SPACES_OFFSET); + + spin_lock(&ctx->lock); + val = __mali_c55_ctx_read(ctx, addr); + spin_unlock(&ctx->lock); + + return val; +} + +void mali_c55_ctx_update_bits(struct mali_c55 *mali_c55, unsigned int addr, + u32 mask, u32 val) +{ + struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55); + u32 orig, tmp; + + WARN_ON(addr < MALI_C55_REG_CONFIG_SPACES_OFFSET); + + spin_lock(&ctx->lock); + + orig = __mali_c55_ctx_read(ctx, addr); + + tmp = orig & ~mask; + tmp |= val & mask; + + if (tmp != orig) + __mali_c55_ctx_write(ctx, addr, tmp); + + spin_unlock(&ctx->lock); +} + +int mali_c55_config_write(struct mali_c55_context *ctx, + enum mali_c55_config_spaces cfg_space, + bool force_synchronous) +{ + struct mali_c55 *mali_c55 = ctx->mali_c55; + + memcpy_toio(mali_c55->base + config_space_addrs[cfg_space], + ctx->registers, MALI_C55_CONFIG_SPACE_SIZE); + + return 0; +} + +struct mali_c55_context *mali_c55_get_active_context(struct mali_c55 *mali_c55) +{ + return &mali_c55->context; +} + +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_RSZS; 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_VIDEO, + &mali_c55->resizers[MALI_C55_RSZ_FR].sd.entity, + MALI_C55_RSZ_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_RSZ_FR].sd.entity, + MALI_C55_RSZ_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_RSZ_SOURCE_PAD, + &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR].vdev.entity, + 0, MEDIA_LNK_FL_ENABLED); + 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_VIDEO, + &mali_c55->resizers[MALI_C55_RSZ_DS].sd.entity, + MALI_C55_RSZ_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_RSZ_SOURCE_PAD, + &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS].vdev.entity, + 0, MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(mali_c55->dev, + "failed to link DS resizer and video device\n"); + goto err_remove_links; + } + } + + ret = media_create_pad_link(&mali_c55->isp.sd.entity, + MALI_C55_ISP_PAD_SOURCE_STATS, + &mali_c55->stats.vdev.entity, 0, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(mali_c55->dev, + "failed to link ISP and 3a stats node\n"); + goto err_remove_links; + } + + ret = media_create_pad_link(&mali_c55->params.vdev.entity, 0, + &mali_c55->isp.sd.entity, + MALI_C55_ISP_PAD_SINK_PARAMS, + MEDIA_LNK_FL_ENABLED); + if (ret) { + dev_err(mali_c55->dev, + "failed to link ISP and parameters video node\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_remove_links(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); + mali_c55_unregister_params(mali_c55); + mali_c55_unregister_stats(mali_c55); +} + +static void mali_c55_swap_next_config(struct mali_c55 *mali_c55) +{ + struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55); + + mali_c55_config_write(ctx, mali_c55->next_config ? + MALI_C55_CONFIG_PING : MALI_C55_CONFIG_PONG, + false); + + mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG, + MALI_C55_REG_MCU_CONFIG_WRITE_MASK, + MALI_C55_MCU_CONFIG_WRITE(mali_c55->next_config)); +} + +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_register_params(mali_c55); + if (ret) + goto err_unregister_entities; + + ret = mali_c55_register_stats(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 int mali_c55_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *subdev, + struct v4l2_async_connection *asc) +{ + struct mali_c55 *mali_c55 = container_of(notifier, + struct mali_c55, notifier); + struct media_pad *pad = &mali_c55->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_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct mali_c55 *mali_c55 = container_of(notifier, + struct mali_c55, notifier); + + return v4l2_device_register_subdev_nodes(&mali_c55->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations mali_c55_notifier_ops = { + .bound = mali_c55_notifier_bound, + .complete = mali_c55_notifier_complete, +}; + +static int mali_c55_parse_endpoint(struct mali_c55 *mali_c55) +{ + struct v4l2_async_connection *asc; + struct fwnode_handle *ep; + + /* + * 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(&mali_c55->notifier, ep, + struct v4l2_async_connection); + fwnode_handle_put(ep); + if (IS_ERR(asc)) { + dev_err(mali_c55->dev, "failed to add remote fwnode\n"); + return PTR_ERR(asc); + } + + return 0; +} + +static int mali_c55_media_frameworks_init(struct mali_c55 *mali_c55) +{ + int ret; + + strscpy(mali_c55->media_dev.model, "ARM Mali-C55 ISP", + sizeof(mali_c55->media_dev.model)); + + 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(mali_c55->dev, &mali_c55->v4l2_dev); + if (ret) { + dev_err(mali_c55->dev, "failed to register V4L2 device\n"); + goto err_unregister_media_device; + }; + + mali_c55->notifier.ops = &mali_c55_notifier_ops; + v4l2_async_nf_init(&mali_c55->notifier, &mali_c55->v4l2_dev); + + ret = mali_c55_register_entities(mali_c55); + if (ret) { + dev_err(mali_c55->dev, "failed to register entities\n"); + goto err_cleanup_nf; + } + + ret = mali_c55_parse_endpoint(mali_c55); + if (ret) + goto err_cleanup_nf; + + ret = v4l2_async_nf_register(&mali_c55->notifier); + if (ret) { + dev_err(mali_c55->dev, "failed to register notifier\n"); + goto err_unregister_entities; + } + + return 0; + +err_unregister_entities: + mali_c55_unregister_entities(mali_c55); +err_cleanup_nf: + v4l2_async_nf_cleanup(&mali_c55->notifier); + 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); + + return ret; +} + +static void mali_c55_media_frameworks_deinit(struct mali_c55 *mali_c55) +{ + v4l2_async_nf_unregister(&mali_c55->notifier); + mali_c55_unregister_entities(mali_c55); + v4l2_async_nf_cleanup(&mali_c55->notifier); + v4l2_device_unregister(&mali_c55->v4l2_dev); + media_device_unregister(&mali_c55->media_dev); + media_device_cleanup(&mali_c55->media_dev); +} + +bool mali_c55_pipeline_ready(struct mali_c55 *mali_c55) +{ + struct mali_c55_cap_dev *fr = &mali_c55->cap_devs[MALI_C55_CAP_DEV_FR]; + struct mali_c55_cap_dev *ds = &mali_c55->cap_devs[MALI_C55_CAP_DEV_DS]; + struct mali_c55_params *params = &mali_c55->params; + struct mali_c55_stats *stats = &mali_c55->stats; + + return vb2_start_streaming_called(&fr->queue) && + (!(mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) || + vb2_start_streaming_called(&ds->queue)) && + vb2_start_streaming_called(¶ms->queue) && + vb2_start_streaming_called(&stats->queue); +} + +static int mali_c55_check_hwcfg(struct mali_c55 *mali_c55) +{ + u32 product, version, revision, capabilities; + + product = mali_c55_read(mali_c55, MALI_C55_REG_PRODUCT); + version = mali_c55_read(mali_c55, MALI_C55_REG_VERSION); + revision = mali_c55_read(mali_c55, MALI_C55_REG_REVISION); + + mali_c55->media_dev.hw_revision = version; + + 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); + + /* + * In its current iteration, the driver only supports inline mode. Given + * we cannot control input data timing in this mode, we cannot guarantee + * that the vertical blanking periods between frames will be long enough + * for us to write configuration data to the ISP during them. For that + * reason we can't really support single config space configuration + * until memory input mode is implemented. + */ + if (!(capabilities & MALI_C55_GPS_PONG_FITTED)) { + dev_err(mali_c55->dev, "Pong config space not fitted.\n"); + return -EINVAL; + } + + mali_c55->capabilities = capabilities & 0xffff; + + return 0; +} + +static irqreturn_t mali_c55_isr(int irq, void *context) +{ + struct device *dev = context; + struct mali_c55 *mali_c55 = dev_get_drvdata(dev); + unsigned long interrupt_status; + u32 curr_config; + unsigned int i; + + interrupt_status = mali_c55_read(mali_c55, + MALI_C55_REG_INTERRUPT_STATUS_VECTOR); + 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, 1); + mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR, 0); + + for_each_set_bit(i, &interrupt_status, MALI_C55_NUM_IRQ_BITS) { + switch (i) { + case MALI_C55_IRQ_ISP_START: + mali_c55_isp_queue_event_sof(mali_c55); + + mali_c55_set_next_buffer(&mali_c55->cap_devs[MALI_C55_CAP_DEV_FR]); + if (mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) + mali_c55_set_next_buffer(&mali_c55->cap_devs[MALI_C55_CAP_DEV_DS]); + + /* + * When the ISP starts a frame we have some work to do: + * + * 1. Copy over the config for the **next** frame + * 2. Read out the metering stats for the **last** frame + */ + + curr_config = mali_c55_read(mali_c55, + MALI_C55_REG_PING_PONG_READ); + curr_config &= MALI_C55_REG_PING_PONG_READ_MASK; + curr_config >>= ffs(MALI_C55_REG_PING_PONG_READ_MASK) - 1; + mali_c55->next_config = curr_config ^ 1; + + /* + * Write the configuration parameters received from + * userspace into the configuration buffer, which will + * be transferred to the 'next' active config space at + * by mali_c55_swap_next_config(). + */ + mali_c55_params_write_config(mali_c55); + + mali_c55_stats_fill_buffer(mali_c55, + mali_c55->next_config ^ 1); + + 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 resource *res) +{ + struct mali_c55_context *ctx = &mali_c55->context; + + ctx->base = res->start; + ctx->mali_c55 = mali_c55; + spin_lock_init(&ctx->lock); + + ctx->registers = kzalloc(MALI_C55_CONFIG_SPACE_SIZE, GFP_KERNEL); + if (!ctx->registers) + return -ENOMEM; + + /* + * The allocated memory is empty, we need to load the default + * register settings. We just read Ping; it's identical to Pong. + */ + memcpy_fromio(ctx->registers, + mali_c55->base + config_space_addrs[MALI_C55_CONFIG_PING], + MALI_C55_CONFIG_SPACE_SIZE); + + /* + * 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_ctx_update_bits(mali_c55, MALI_C55_REG_BYPASS_1, + MALI_C55_REG_BYPASS_1_FE_SQRT, + MALI_C55_REG_BYPASS_1_FE_SQRT); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BYPASS_3, + MALI_C55_REG_BYPASS_3_SQUARE_BE, + MALI_C55_REG_BYPASS_3_SQUARE_BE); + + /* Bypass the temper module */ + mali_c55_ctx_write(mali_c55, MALI_C55_REG_BYPASS_2, + MALI_C55_REG_BYPASS_2_TEMPER); + + /* Disable the temper module's DMA read/write */ + mali_c55_ctx_write(mali_c55, MALI_C55_REG_TEMPER_DMA_IO, 0x0); + + /* Bypass the colour noise reduction */ + mali_c55_ctx_write(mali_c55, MALI_C55_REG_BYPASS_4, + MALI_C55_REG_BYPASS_4_CNR); + + /* Disable the sinter module */ + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_SINTER_CONFIG, + MALI_C55_SINTER_ENABLE_MASK, 0); + + /* Disable the RGB Gamma module for each output */ + mali_c55_ctx_write(mali_c55, MALI_C55_REG_FR_GAMMA_RGB_ENABLE, 0); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_DS_GAMMA_RGB_ENABLE, 0); + + /* Disable the colour correction matrix */ + mali_c55_ctx_write(mali_c55, MALI_C55_REG_CCM_ENABLE, 0); + + return 0; +} + +static void __mali_c55_power_off(struct mali_c55 *mali_c55) +{ + reset_control_bulk_assert(ARRAY_SIZE(mali_c55->resets), mali_c55->resets); + clk_bulk_disable_unprepare(ARRAY_SIZE(mali_c55->clks), mali_c55->clks); +} + +static int __maybe_unused mali_c55_runtime_suspend(struct device *dev) +{ + struct mali_c55 *mali_c55 = dev_get_drvdata(dev); + + if (irq_has_action(mali_c55->irqnum)) + free_irq(mali_c55->irqnum, dev); + __mali_c55_power_off(mali_c55); + + return 0; +} + +static int __mali_c55_power_on(struct mali_c55 *mali_c55) +{ + int ret; + u32 val; + + 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; + } + + ret = reset_control_bulk_deassert(ARRAY_SIZE(mali_c55->resets), + mali_c55->resets); + if (ret) { + dev_err(mali_c55->dev, "failed to deassert resets\n"); + return ret; + } + + /* Use "software only" context management. */ + mali_c55_update_bits(mali_c55, MALI_C55_REG_MCU_CONFIG, + MALI_C55_REG_MCU_CONFIG_OVERRIDE_MASK, 0x01); + + /* + * Mask the interrupts and clear any that were set, then unmask the ones + * that we actually want to handle. + */ + mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_MASK_VECTOR, + MALI_C55_INTERRUPT_MASK_ALL); + mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR_VECTOR, + MALI_C55_INTERRUPT_MASK_ALL); + mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR, 0x01); + mali_c55_write(mali_c55, MALI_C55_REG_INTERRUPT_CLEAR, 0x00); + + mali_c55_update_bits(mali_c55, MALI_C55_REG_INTERRUPT_MASK_VECTOR, + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_ISP_START) | + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_ISP_DONE) | + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_FR_Y_DONE) | + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_FR_UV_DONE) | + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_DS_Y_DONE) | + MALI_C55_INTERRUPT_BIT(MALI_C55_IRQ_DS_UV_DONE), + 0x00); + + /* 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); + + return 0; +} + +static int __maybe_unused mali_c55_runtime_resume(struct device *dev) +{ + struct mali_c55 *mali_c55 = dev_get_drvdata(dev); + int ret; + + ret = __mali_c55_power_on(mali_c55); + if (ret) + return ret; + + /* + * The driver needs to transfer large amounts of register settings to + * the ISP each frame, using either a DMA transfer or memcpy. We use a + * threaded IRQ to avoid disabling interrupts the entire time that's + * happening. + */ + ret = request_threaded_irq(mali_c55->irqnum, NULL, mali_c55_isr, + IRQF_ONESHOT, dev_driver_string(dev), dev); + if (ret) { + __mali_c55_power_off(mali_c55); + dev_err(dev, "failed to request irq\n"); + } + + return ret; +} + +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; + struct resource *res; + int ret; + + mali_c55 = devm_kzalloc(dev, sizeof(*mali_c55), GFP_KERNEL); + if (!mali_c55) + return -ENOMEM; + + mali_c55->dev = dev; + platform_set_drvdata(pdev, mali_c55); + + mali_c55->base = devm_platform_get_and_ioremap_resource(pdev, 0, + &res); + if (IS_ERR(mali_c55->base)) + return dev_err_probe(dev, PTR_ERR(mali_c55->base), + "failed to map IO memory\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"); + + for (unsigned int i = 0; i < ARRAY_SIZE(mali_c55_reset_names); i++) + mali_c55->resets[i].id = mali_c55_reset_names[i]; + + ret = devm_reset_control_bulk_get_optional_shared(dev, + ARRAY_SIZE(mali_c55_reset_names), mali_c55->resets); + if (ret) + return dev_err_probe(dev, ret, "failed to acquire resets\n"); + + of_reserved_mem_device_init(dev); + vb2_dma_contig_set_max_seg_size(dev, UINT_MAX); + + ret = __mali_c55_power_on(mali_c55); + if (ret) + return dev_err_probe(dev, ret, "failed to power on\n"); + + ret = mali_c55_check_hwcfg(mali_c55); + if (ret) + goto err_power_off; + + ret = mali_c55_init_context(mali_c55, res); + if (ret) + goto err_power_off; + + mali_c55->media_dev.dev = dev; + + pm_runtime_set_autosuspend_delay(&pdev->dev, 2000); + pm_runtime_use_autosuspend(&pdev->dev); + pm_runtime_set_active(&pdev->dev); + pm_runtime_enable(&pdev->dev); + + ret = mali_c55_media_frameworks_init(mali_c55); + if (ret) + goto err_free_context_registers; + + pm_runtime_idle(&pdev->dev); + + mali_c55->irqnum = platform_get_irq(pdev, 0); + if (mali_c55->irqnum < 0) { + ret = mali_c55->irqnum; + dev_err(dev, "failed to get interrupt\n"); + goto err_deinit_media_frameworks; + } + + return 0; + +err_deinit_media_frameworks: + mali_c55_media_frameworks_deinit(mali_c55); + pm_runtime_disable(&pdev->dev); +err_free_context_registers: + kfree(mali_c55->context.registers); +err_power_off: + __mali_c55_power_off(mali_c55); + + return ret; +} + +static void mali_c55_remove(struct platform_device *pdev) +{ + struct mali_c55 *mali_c55 = platform_get_drvdata(pdev); + + kfree(mali_c55->context.registers); + mali_c55_media_frameworks_deinit(mali_c55); +} + +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 = mali_c55_of_match, + .pm = &mali_c55_pm_ops, + }, + .probe = mali_c55_probe, + .remove = mali_c55_remove, +}; + +module_platform_driver(mali_c55_driver); + +MODULE_AUTHOR("Daniel Scally <dan.scally@ideasonboard.com>"); +MODULE_AUTHOR("Jacopo Mondi <jacopo.mondi@ideasonboard.com>"); +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..497f25fbdd13 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-isp.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Image signal processor + * + * Copyright (C) 2025 Ideas on Board Oy + */ + +#include <linux/media/arm/mali-c55-config.h> + +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <linux/property.h> +#include <linux/string.h> + +#include <uapi/linux/media/arm/mali-c55-config.h> + +#include <media/media-entity.h> +#include <media/v4l2-common.h> +#include <media/v4l2-ctrls.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_format_info mali_c55_isp_fmts[] = { + { + .code = MEDIA_BUS_FMT_SRGGB20_1X20, + .shifted_code = MEDIA_BUS_FMT_SRGGB16_1X16, + .order = MALI_C55_BAYER_ORDER_RGGB, + .bypass = false, + }, + { + .code = MEDIA_BUS_FMT_SGRBG20_1X20, + .shifted_code = MEDIA_BUS_FMT_SGRBG16_1X16, + .order = MALI_C55_BAYER_ORDER_GRBG, + .bypass = false, + }, + { + .code = MEDIA_BUS_FMT_SGBRG20_1X20, + .shifted_code = MEDIA_BUS_FMT_SGBRG16_1X16, + .order = MALI_C55_BAYER_ORDER_GBRG, + .bypass = false, + }, + { + .code = MEDIA_BUS_FMT_SBGGR20_1X20, + .shifted_code = MEDIA_BUS_FMT_SBGGR16_1X16, + .order = MALI_C55_BAYER_ORDER_BGGR, + .bypass = false, + }, + { + .code = MEDIA_BUS_FMT_RGB202020_1X60, + .shifted_code = 0, /* Not relevant for this format */ + .order = 0, /* Not relevant for this format */ + .bypass = true, + } + /* + * 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_format_info * +mali_c55_isp_get_mbus_config_by_index(u32 index) +{ + if (index < ARRAY_SIZE(mali_c55_isp_fmts)) + return &mali_c55_isp_fmts[index]; + + return NULL; +} + +const struct mali_c55_isp_format_info * +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; +} + +const struct mali_c55_isp_format_info * +mali_c55_isp_get_mbus_config_by_shifted_code(u32 code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mali_c55_isp_fmts); i++) { + if (mali_c55_isp_fmts[i].shifted_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, + const struct v4l2_subdev_state *state) +{ + struct mali_c55_context *ctx = mali_c55_get_active_context(mali_c55); + const struct mali_c55_isp_format_info *cfg; + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *crop; + u32 val; + int ret; + + 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 */ + 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_ctx_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR, + MALI_C55_REG_ACTIVE_WIDTH_MASK, format->width); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BASE_ADDR, + MALI_C55_REG_ACTIVE_HEIGHT_MASK, + format->height << 16); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BAYER_ORDER, + MALI_C55_BAYER_ORDER_MASK, cfg->order); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_INPUT_WIDTH, + MALI_C55_INPUT_WIDTH_MASK, + MALI_C55_INPUT_WIDTH_20BIT); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS, + MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK, + cfg->bypass ? MALI_C55_ISP_RAW_BYPASS_BYPASS_MASK : + 0x00); + + mali_c55_params_write_config(mali_c55); + ret = mali_c55_config_write(ctx, MALI_C55_CONFIG_PING, true); + if (ret) { + dev_err(mali_c55->dev, "failed to write ISP config\n"); + return ret; + } + + mali_c55_write(mali_c55, MALI_C55_REG_INPUT_MODE_REQUEST, + MALI_C55_INPUT_SAFE_START); + + ret = readl_poll_timeout(mali_c55->base + MALI_C55_REG_MODE_STATUS, val, + val, 10 * USEC_PER_MSEC, 250 * USEC_PER_MSEC); + if (ret) { + mali_c55_isp_stop(mali_c55); + dev_err(mali_c55->dev, "timeout starting ISP\n"); + 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_VIDEO) { + 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_format_info *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_VIDEO) { + 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_format_info *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 != MALI_C55_ISP_PAD_SINK_VIDEO) + return v4l2_subdev_get_fmt(sd, state, format); + + sink_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SINK_VIDEO); + + cfg = mali_c55_isp_get_mbus_config_by_code(fmt->code); + sink_fmt->code = cfg ? fmt->code : MEDIA_BUS_FMT_SRGGB20_1X20; + + /* + * Clamp sizes in the accepted limits and clamp the crop rectangle in + * the new sizes. + */ + sink_fmt->width = clamp(fmt->width, MALI_C55_MIN_WIDTH, + MALI_C55_MAX_WIDTH); + sink_fmt->height = clamp(fmt->height, MALI_C55_MIN_HEIGHT, + MALI_C55_MAX_HEIGHT); + + *fmt = *sink_fmt; + + crop = v4l2_subdev_state_get_crop(state, MALI_C55_ISP_PAD_SINK_VIDEO); + crop->left = 0; + crop->top = 0; + crop->width = sink_fmt->width; + crop->height = sink_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. The "field" and "colorspace" fields are + * set in .init_state() and fixed for both source pads, as is the "code" + * field for the processed data source pad. + */ + src_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SOURCE_VIDEO); + 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 = sink_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 != MALI_C55_ISP_PAD_SINK_VIDEO || + 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; + const struct v4l2_mbus_framefmt *fmt; + struct v4l2_rect *crop; + + if (sel->pad != MALI_C55_ISP_PAD_SINK_VIDEO || + sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + fmt = v4l2_subdev_state_get_format(state, MALI_C55_ISP_PAD_SINK_VIDEO); + + sel->r.left = clamp_t(unsigned int, sel->r.left, 0, fmt->width); + sel->r.top = clamp_t(unsigned int, sel->r.top, 0, fmt->height); + sel->r.width = clamp_t(unsigned int, sel->r.width, MALI_C55_MIN_WIDTH, + fmt->width - sel->r.left); + sel->r.height = 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. The crop + * isn't propagated to the bypass source pad, because the bypassed data + * cannot be cropped. + */ + src_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SOURCE_VIDEO); + src_fmt->width = crop->width; + src_fmt->height = crop->height; + + return 0; +} + +static int mali_c55_isp_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct mali_c55_isp *isp = container_of(sd, struct mali_c55_isp, sd); + struct mali_c55 *mali_c55 = isp->mali_c55; + struct v4l2_subdev *src_sd; + struct media_pad *sink_pad; + int ret; + + /* + * We have two source pads, both of which have only a single stream. The + * core v4l2 code already validated those parameters so we can just get + * on with starting the ISP. + */ + + sink_pad = &isp->pads[MALI_C55_ISP_PAD_SINK_VIDEO]; + isp->remote_src = media_pad_remote_pad_unique(sink_pad); + src_sd = media_entity_to_v4l2_subdev(isp->remote_src->entity); + + isp->frame_sequence = 0; + ret = mali_c55_isp_start(mali_c55, state); + 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(src_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_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct mali_c55_isp *isp = container_of(sd, struct mali_c55_isp, sd); + struct mali_c55 *mali_c55 = isp->mali_c55; + struct v4l2_subdev *src_sd; + + if (isp->remote_src) { + src_sd = media_entity_to_v4l2_subdev(isp->remote_src->entity); + v4l2_subdev_disable_streams(src_sd, isp->remote_src->index, + BIT(0)); + } + isp->remote_src = NULL; + + mali_c55_isp_stop(mali_c55); + + 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, + .enable_streams = mali_c55_isp_enable_streams, + .disable_streams = mali_c55_isp_disable_streams, +}; + +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) +{ + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 0, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +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 *state) +{ + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *in_crop; + + sink_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SINK_VIDEO); + src_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SOURCE_VIDEO); + in_crop = v4l2_subdev_state_get_crop(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; + sink_fmt->colorspace = V4L2_COLORSPACE_RAW; + sink_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + sink_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + sink_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + + *v4l2_subdev_state_get_format(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; + src_fmt->colorspace = V4L2_COLORSPACE_SRGB; + src_fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(sink_fmt->colorspace); + src_fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(sink_fmt->colorspace); + src_fmt->quantization = + V4L2_MAP_QUANTIZATION_DEFAULT(false, sink_fmt->colorspace, + sink_fmt->ycbcr_enc); + + in_crop->top = 0; + in_crop->left = 0; + in_crop->width = MALI_C55_DEFAULT_WIDTH; + in_crop->height = MALI_C55_DEFAULT_HEIGHT; + + src_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SOURCE_STATS); + sink_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_ISP_PAD_SINK_PARAMS); + + src_fmt->width = 0; + src_fmt->height = 0; + src_fmt->field = V4L2_FIELD_NONE; + src_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + sink_fmt->width = 0; + sink_fmt->height = 0; + sink_fmt->field = V4L2_FIELD_NONE; + sink_fmt->code = MEDIA_BUS_FMT_METADATA_FIXED; + + return 0; +} + +static const struct v4l2_subdev_internal_ops mali_c55_isp_internal_ops = { + .init_state = mali_c55_isp_init_state, +}; + +static int mali_c55_subdev_link_validate(struct media_link *link) +{ + /* + * Skip validation for the parameters sink pad, as the source is not + * a subdevice. + */ + if (link->sink->index == MALI_C55_ISP_PAD_SINK_PARAMS) + return 0; + + return v4l2_subdev_link_validate(link); +} + +static const struct media_entity_operations mali_c55_isp_media_ops = { + .link_validate = mali_c55_subdev_link_validate, +}; + +static int mali_c55_isp_s_ctrl(struct v4l2_ctrl *ctrl) +{ + /* + * .s_ctrl() is a mandatory operation, but the driver has only a single + * read only control. If we got here, something went badly wrong. + */ + return -EINVAL; +} + +static const struct v4l2_ctrl_ops mali_c55_isp_ctrl_ops = { + .s_ctrl = mali_c55_isp_s_ctrl, +}; + +/* NOT const because the default needs to be filled in at runtime */ +static struct v4l2_ctrl_config mali_c55_isp_v4l2_custom_ctrls[] = { + { + .ops = &mali_c55_isp_ctrl_ops, + .id = V4L2_CID_MALI_C55_CAPABILITIES, + .name = "Mali-C55 ISP Capabilities", + .type = V4L2_CTRL_TYPE_BITMASK, + .min = 0, + .max = MALI_C55_GPS_PONG_FITTED | + MALI_C55_GPS_WDR_FITTED | + MALI_C55_GPS_COMPRESSION_FITTED | + MALI_C55_GPS_TEMPER_FITTED | + MALI_C55_GPS_SINTER_LITE_FITTED | + MALI_C55_GPS_SINTER_FITTED | + MALI_C55_GPS_IRIDIX_LTM_FITTED | + MALI_C55_GPS_IRIDIX_GTM_FITTED | + MALI_C55_GPS_CNR_FITTED | + MALI_C55_GPS_FRSCALER_FITTED | + MALI_C55_GPS_DS_PIPE_FITTED, + .def = 0, + }, +}; + +static int mali_c55_isp_init_controls(struct mali_c55 *mali_c55) +{ + struct v4l2_ctrl_handler *handler = &mali_c55->isp.handler; + struct v4l2_ctrl *capabilities; + int ret; + + ret = v4l2_ctrl_handler_init(handler, 1); + if (ret) + return ret; + + mali_c55_isp_v4l2_custom_ctrls[0].def = mali_c55->capabilities; + + capabilities = v4l2_ctrl_new_custom(handler, + &mali_c55_isp_v4l2_custom_ctrls[0], + NULL); + if (capabilities) + capabilities->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (handler->error) { + dev_err(mali_c55->dev, "failed to register capabilities control\n"); + ret = handler->error; + v4l2_ctrl_handler_free(handler); + return ret; + } + + mali_c55->isp.sd.ctrl_handler = handler; + + return 0; +} + +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 | + MEDIA_PAD_FL_MUST_CONNECT; + isp->pads[MALI_C55_ISP_PAD_SOURCE_VIDEO].flags = MEDIA_PAD_FL_SOURCE; + isp->pads[MALI_C55_ISP_PAD_SOURCE_BYPASS].flags = MEDIA_PAD_FL_SOURCE; + isp->pads[MALI_C55_ISP_PAD_SOURCE_STATS].flags = MEDIA_PAD_FL_SOURCE; + isp->pads[MALI_C55_ISP_PAD_SINK_PARAMS].flags = MEDIA_PAD_FL_SINK; + + ret = media_entity_pads_init(&sd->entity, MALI_C55_ISP_NUM_PADS, + isp->pads); + if (ret) + return ret; + + ret = mali_c55_isp_init_controls(mali_c55); + if (ret) + goto err_cleanup_media_entity; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_free_ctrl_handler; + + ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd); + if (ret) + goto err_cleanup_subdev; + + mutex_init(&isp->capture_lock); + + return 0; + +err_cleanup_subdev: + v4l2_subdev_cleanup(sd); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&isp->handler); +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->capture_lock); + 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-params.c b/drivers/media/platform/arm/mali-c55/mali-c55-params.c new file mode 100644 index 000000000000..082cda4f4f63 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-params.c @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Configuration parameters output device + * + * Copyright (C) 2025 Ideas on Board Oy + */ +#include <linux/media/arm/mali-c55-config.h> +#include <linux/pm_runtime.h> + +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/v4l2-isp.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#include "mali-c55-common.h" +#include "mali-c55-registers.h" + +/** + * union mali_c55_params_block - Generalisation of a parameter block + * + * This union allows the driver to treat a block as a generic pointer to this + * union and safely access the header and block-specific struct without having + * to resort to casting. The header member is accessed first, and the type field + * checked which allows the driver to determine which of the other members + * should be used. The data member at the end allows a pointer to an address + * within the data member of :c:type:`mali_c55_params_buffer` to initialise a + * union variable. + * + * @header: Pointer to the shared header struct embedded as the + * first member of all the possible other members (except + * @data). This member would be accessed first and the type + * field checked to determine which of the other members + * should be accessed. + * @sensor_offs: For header->type == MALI_C55_PARAM_BLOCK_SENSOR_OFFS + * @aexp_hist: For header->type == MALI_C55_PARAM_BLOCK_AEXP_HIST and + * header->type == MALI_C55_PARAM_BLOCK_AEXP_IHIST + * @aexp_weights: For header->type == MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS + * and header->type = MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS + * @digital_gain: For header->type == MALI_C55_PARAM_BLOCK_DIGITAL_GAIN + * @awb_gains: For header->type == MALI_C55_PARAM_BLOCK_AWB_GAINS and + * header->type = MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP + * @awb_config: For header->type == MALI_C55_PARAM_MESH_SHADING_CONFIG + * @shading_config: For header->type == MALI_C55_PARAM_MESH_SHADING_SELECTION + * @shading_selection: For header->type == MALI_C55_PARAM_BLOCK_SENSOR_OFFS + * @data: Allows easy initialisation of a union variable with a + * pointer into a __u8 array. + */ +union mali_c55_params_block { + const struct v4l2_isp_params_block_header *header; + const struct mali_c55_params_sensor_off_preshading *sensor_offs; + const struct mali_c55_params_aexp_hist *aexp_hist; + const struct mali_c55_params_aexp_weights *aexp_weights; + const struct mali_c55_params_digital_gain *digital_gain; + const struct mali_c55_params_awb_gains *awb_gains; + const struct mali_c55_params_awb_config *awb_config; + const struct mali_c55_params_mesh_shading_config *shading_config; + const struct mali_c55_params_mesh_shading_selection *shading_selection; + const __u8 *data; +}; + +typedef void (*mali_c55_params_handler)(struct mali_c55 *mali_c55, + union mali_c55_params_block block); + +#define to_mali_c55_params_buf(vbuf) \ + container_of(vbuf, struct mali_c55_params_buf, vb) + +static void mali_c55_params_sensor_offs(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_sensor_off_preshading *p; + __u32 global_offset; + + p = block.sensor_offs; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BYPASS_3, + MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH, + MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH); + return; + } + + if (!(p->chan00 || p->chan01 || p->chan10 || p->chan11)) + return; + + mali_c55_ctx_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_00, + p->chan00 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_01, + p->chan01 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_10, + p->chan10 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_SENSOR_OFF_PRE_SHA_11, + p->chan11 & MALI_C55_SENSOR_OFF_PRE_SHA_MASK); + + /* + * The average offset is applied as a global offset for the digital + * gain block + */ + global_offset = (p->chan00 + p->chan01 + p->chan10 + p->chan11) >> 2; + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN_OFFSET, + MALI_C55_DIGITAL_GAIN_OFFSET_MASK, + global_offset); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BYPASS_3, + MALI_C55_REG_BYPASS_3_SENSOR_OFFSET_PRE_SH, + 0x00); +} + +static void mali_c55_params_aexp_hist(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_aexp_hist *params; + u32 disable_mask; + u32 disable_val; + u32 base; + + if (block.header->type == MALI_C55_PARAM_BLOCK_AEXP_HIST) { + disable_mask = MALI_C55_AEXP_HIST_DISABLE_MASK; + disable_val = MALI_C55_AEXP_HIST_DISABLE; + base = MALI_C55_REG_AEXP_HIST_BASE; + } else { + disable_mask = MALI_C55_AEXP_IHIST_DISABLE_MASK; + disable_val = MALI_C55_AEXP_IHIST_DISABLE; + base = MALI_C55_REG_AEXP_IHIST_BASE; + } + + params = block.aexp_hist; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + disable_mask, disable_val); + return; + } + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + disable_mask, false); + + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET, + MALI_C55_AEXP_HIST_SKIP_X_MASK, params->skip_x); + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET, + MALI_C55_AEXP_HIST_OFFSET_X_MASK, + MALI_C55_AEXP_HIST_OFFSET_X(params->offset_x)); + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET, + MALI_C55_AEXP_HIST_SKIP_Y_MASK, + MALI_C55_AEXP_HIST_SKIP_Y(params->skip_y)); + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SKIP_OFFSET, + MALI_C55_AEXP_HIST_OFFSET_Y_MASK, + MALI_C55_AEXP_HIST_OFFSET_Y(params->offset_y)); + + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET, + MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK, + params->scale_bottom); + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_SCALE_OFFSET, + MALI_C55_AEXP_HIST_SCALE_TOP_MASK, + MALI_C55_AEXP_HIST_SCALE_TOP(params->scale_top)); + + mali_c55_ctx_update_bits(mali_c55, base + MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET, + MALI_C55_AEXP_HIST_PLANE_MODE_MASK, + params->plane_mode); + + if (block.header->type == MALI_C55_PARAM_BLOCK_AEXP_HIST) + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + MALI_C55_AEXP_HIST_SWITCH_MASK, + MALI_C55_AEXP_HIST_SWITCH(params->tap_point)); +} + +static void +mali_c55_params_aexp_hist_weights(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_aexp_weights *params; + u32 base, val, addr; + + params = block.aexp_weights; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) + return; + + base = block.header->type == MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS ? + MALI_C55_REG_AEXP_HIST_BASE : + MALI_C55_REG_AEXP_IHIST_BASE; + + mali_c55_ctx_update_bits(mali_c55, + base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET, + MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK, + params->nodes_used_horiz); + mali_c55_ctx_update_bits(mali_c55, + base + MALI_C55_AEXP_HIST_NODES_USED_OFFSET, + MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK, + MALI_C55_AEXP_HIST_NODES_USED_VERT(params->nodes_used_vert)); + + /* + * The zone weights array is a 225-element array of u8 values, but that + * is a bit annoying to handle given the ISP expects 32-bit writes. We + * just reinterpret it as 56-element array of 32-bit values for the + * purposes of this transaction. The last register is handled separately + * to stop static analysers worrying about buffer overflow. The 3 bytes + * of additional space at the end of the write is just padding for the + * array of weights in the ISP memory space anyway, so there's no risk + * of overwriting other registers. + */ + for (unsigned int i = 0; i < 56; i++) { + val = ((u32 *)params->zone_weights)[i] + & MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK; + addr = base + MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET + (4 * i); + + mali_c55_ctx_write(mali_c55, addr, val); + } + + val = params->zone_weights[MALI_C55_MAX_ZONES - 1]; + addr = base + MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET + (4 * 56); +} + +static void mali_c55_params_digital_gain(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_digital_gain *dgain; + u32 gain; + + dgain = block.digital_gain; + + /* + * If the block is flagged as disabled we write a gain of 1.0, which in + * Q5.8 format is 256. + */ + gain = block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE ? + 256 : dgain->gain; + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN, + MALI_C55_DIGITAL_GAIN_MASK, + gain); +} + +static void mali_c55_params_awb_gains(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_awb_gains *gains; + u32 gain00, gain01, gain10, gain11; + + gains = block.awb_gains; + + /* + * There are two places AWB gains can be set in the ISP; one affects the + * image output data and the other affects the statistics for the + * AEXP-0 tap point. + */ + u32 addr1 = block.header->type == MALI_C55_PARAM_BLOCK_AWB_GAINS ? + MALI_C55_REG_AWB_GAINS1 : + MALI_C55_REG_AWB_GAINS1_AEXP; + u32 addr2 = block.header->type == MALI_C55_PARAM_BLOCK_AWB_GAINS ? + MALI_C55_REG_AWB_GAINS2 : + MALI_C55_REG_AWB_GAINS2_AEXP; + + /* If the block is flagged disabled, set all of the gains to 1.0 */ + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + gain00 = 256; + gain01 = 256; + gain10 = 256; + gain11 = 256; + } else { + gain00 = gains->gain00; + gain01 = gains->gain01; + gain10 = gains->gain10; + gain11 = gains->gain11; + } + + mali_c55_ctx_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN00_MASK, + gain00); + mali_c55_ctx_update_bits(mali_c55, addr1, MALI_C55_AWB_GAIN01_MASK, + MALI_C55_AWB_GAIN01(gain01)); + mali_c55_ctx_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN10_MASK, + gain10); + mali_c55_ctx_update_bits(mali_c55, addr2, MALI_C55_AWB_GAIN11_MASK, + MALI_C55_AWB_GAIN11(gain11)); +} + +static void mali_c55_params_awb_config(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_awb_config *params; + + params = block.awb_config; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + MALI_C55_AWB_DISABLE_MASK, + MALI_C55_AWB_DISABLE_MASK); + return; + } + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + MALI_C55_AWB_DISABLE_MASK, false); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_STATS_MODE, + MALI_C55_AWB_STATS_MODE_MASK, params->stats_mode); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_WHITE_LEVEL, + MALI_C55_AWB_WHITE_LEVEL_MASK, params->white_level); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_BLACK_LEVEL, + MALI_C55_AWB_BLACK_LEVEL_MASK, params->black_level); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MAX, + MALI_C55_AWB_CR_MAX_MASK, params->cr_max); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CR_MIN, + MALI_C55_AWB_CR_MIN_MASK, params->cr_min); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MAX, + MALI_C55_AWB_CB_MAX_MASK, params->cb_max); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CB_MIN, + MALI_C55_AWB_CB_MIN_MASK, params->cb_min); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED, + MALI_C55_AWB_NODES_USED_HORIZ_MASK, + params->nodes_used_horiz); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_NODES_USED, + MALI_C55_AWB_NODES_USED_VERT_MASK, + MALI_C55_AWB_NODES_USED_VERT(params->nodes_used_vert)); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CR_HIGH, + MALI_C55_AWB_CR_HIGH_MASK, params->cr_high); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CR_LOW, + MALI_C55_AWB_CR_LOW_MASK, params->cr_low); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CB_HIGH, + MALI_C55_AWB_CB_HIGH_MASK, params->cb_high); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_CB_LOW, + MALI_C55_AWB_CB_LOW_MASK, params->cb_low); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG, + MALI_C55_AWB_SWITCH_MASK, + MALI_C55_AWB_SWITCH(params->tap_point)); +} + +static void mali_c55_params_lsc_config(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_mesh_shading_config *params; + unsigned int i; + u32 addr; + + params = block.shading_config; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) { + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_ENABLE_MASK, + false); + return; + } + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_ENABLE_MASK, true); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_MESH_SHOW_MASK, + MALI_C55_MESH_SHADING_MESH_SHOW(params->mesh_show)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_SCALE_MASK, + MALI_C55_MESH_SHADING_SCALE(params->mesh_scale)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_PAGE_R_MASK, + MALI_C55_MESH_SHADING_PAGE_R(params->mesh_page_r)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_PAGE_G_MASK, + MALI_C55_MESH_SHADING_PAGE_G(params->mesh_page_g)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_PAGE_B_MASK, + MALI_C55_MESH_SHADING_PAGE_B(params->mesh_page_b)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_MESH_WIDTH_MASK, + MALI_C55_MESH_SHADING_MESH_WIDTH(params->mesh_width)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG, + MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK, + MALI_C55_MESH_SHADING_MESH_HEIGHT(params->mesh_height)); + + for (i = 0; i < MALI_C55_NUM_MESH_SHADING_ELEMENTS; i++) { + addr = MALI_C55_REG_MESH_SHADING_TABLES + (i * 4); + mali_c55_ctx_write(mali_c55, addr, params->mesh[i]); + } +} + +static void mali_c55_params_lsc_selection(struct mali_c55 *mali_c55, + union mali_c55_params_block block) +{ + const struct mali_c55_params_mesh_shading_selection *params; + + params = block.shading_selection; + + if (block.header->flags & V4L2_ISP_PARAMS_FL_BLOCK_DISABLE) + return; + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK, + MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK, + params->mesh_alpha_bank_r); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK, + MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK, + MALI_C55_MESH_SHADING_ALPHA_BANK_G(params->mesh_alpha_bank_g)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA_BANK, + MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK, + MALI_C55_MESH_SHADING_ALPHA_BANK_B(params->mesh_alpha_bank_b)); + + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA, + MALI_C55_MESH_SHADING_ALPHA_R_MASK, + params->mesh_alpha_r); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA, + MALI_C55_MESH_SHADING_ALPHA_G_MASK, + MALI_C55_MESH_SHADING_ALPHA_G(params->mesh_alpha_g)); + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_ALPHA, + MALI_C55_MESH_SHADING_ALPHA_B_MASK, + MALI_C55_MESH_SHADING_ALPHA_B(params->mesh_alpha_b)); + + mali_c55_ctx_update_bits(mali_c55, + MALI_C55_REG_MESH_SHADING_MESH_STRENGTH, + MALI_c55_MESH_STRENGTH_MASK, + params->mesh_strength); +} + +static const mali_c55_params_handler mali_c55_params_handlers[] = { + [MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = &mali_c55_params_sensor_offs, + [MALI_C55_PARAM_BLOCK_AEXP_HIST] = &mali_c55_params_aexp_hist, + [MALI_C55_PARAM_BLOCK_AEXP_IHIST] = &mali_c55_params_aexp_hist, + [MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS] = &mali_c55_params_aexp_hist_weights, + [MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS] = &mali_c55_params_aexp_hist_weights, + [MALI_C55_PARAM_BLOCK_DIGITAL_GAIN] = &mali_c55_params_digital_gain, + [MALI_C55_PARAM_BLOCK_AWB_GAINS] = &mali_c55_params_awb_gains, + [MALI_C55_PARAM_BLOCK_AWB_CONFIG] = &mali_c55_params_awb_config, + [MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP] = &mali_c55_params_awb_gains, + [MALI_C55_PARAM_MESH_SHADING_CONFIG] = &mali_c55_params_lsc_config, + [MALI_C55_PARAM_MESH_SHADING_SELECTION] = &mali_c55_params_lsc_selection, +}; + +static const struct v4l2_isp_params_block_type_info +mali_c55_params_block_types_info[] = { + [MALI_C55_PARAM_BLOCK_SENSOR_OFFS] = { + .size = sizeof(struct mali_c55_params_sensor_off_preshading), + }, + [MALI_C55_PARAM_BLOCK_AEXP_HIST] = { + .size = sizeof(struct mali_c55_params_aexp_hist), + }, + [MALI_C55_PARAM_BLOCK_AEXP_IHIST] = { + .size = sizeof(struct mali_c55_params_aexp_hist), + }, + [MALI_C55_PARAM_BLOCK_AEXP_HIST_WEIGHTS] = { + .size = sizeof(struct mali_c55_params_aexp_weights), + }, + [MALI_C55_PARAM_BLOCK_AEXP_IHIST_WEIGHTS] = { + .size = sizeof(struct mali_c55_params_aexp_weights), + }, + [MALI_C55_PARAM_BLOCK_DIGITAL_GAIN] = { + .size = sizeof(struct mali_c55_params_digital_gain), + }, + [MALI_C55_PARAM_BLOCK_AWB_GAINS] = { + .size = sizeof(struct mali_c55_params_awb_gains), + }, + [MALI_C55_PARAM_BLOCK_AWB_CONFIG] = { + .size = sizeof(struct mali_c55_params_awb_config), + }, + [MALI_C55_PARAM_BLOCK_AWB_GAINS_AEXP] = { + .size = sizeof(struct mali_c55_params_awb_gains), + }, + [MALI_C55_PARAM_MESH_SHADING_CONFIG] = { + .size = sizeof(struct mali_c55_params_mesh_shading_config), + }, + [MALI_C55_PARAM_MESH_SHADING_SELECTION] = { + .size = sizeof(struct mali_c55_params_mesh_shading_selection), + }, +}; + +static_assert(ARRAY_SIZE(mali_c55_params_handlers) == + ARRAY_SIZE(mali_c55_params_block_types_info)); + +static int mali_c55_params_enum_fmt_meta_out(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + if (f->mbus_code && f->mbus_code != MEDIA_BUS_FMT_METADATA_FIXED) + return -EINVAL; + + f->pixelformat = V4L2_META_FMT_MALI_C55_PARAMS; + + return 0; +} + +static int mali_c55_params_g_fmt_meta_out(struct file *file, void *fh, + struct v4l2_format *f) +{ + static const struct v4l2_meta_format mfmt = { + .dataformat = V4L2_META_FMT_MALI_C55_PARAMS, + .buffersize = v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE), + }; + + f->fmt.meta = mfmt; + + return 0; +} + +static int mali_c55_params_querycap(struct file *file, + void *priv, 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_params_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_enum_fmt_meta_out = mali_c55_params_enum_fmt_meta_out, + .vidioc_g_fmt_meta_out = mali_c55_params_g_fmt_meta_out, + .vidioc_s_fmt_meta_out = mali_c55_params_g_fmt_meta_out, + .vidioc_try_fmt_meta_out = mali_c55_params_g_fmt_meta_out, + .vidioc_querycap = mali_c55_params_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations mali_c55_params_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 int +mali_c55_params_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + if (*num_planes && *num_planes > 1) + return -EINVAL; + + if (sizes[0] && sizes[0] < v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE)) + return -EINVAL; + + *num_planes = 1; + + if (!sizes[0]) + sizes[0] = v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE); + + return 0; +} + +static int mali_c55_params_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mali_c55_params_buf *buf = to_mali_c55_params_buf(vbuf); + + buf->config = kvmalloc(v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE), + GFP_KERNEL); + if (!buf->config) + return -ENOMEM; + + return 0; +} + +static void mali_c55_params_buf_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mali_c55_params_buf *buf = to_mali_c55_params_buf(vbuf); + + kvfree(buf->config); + buf->config = NULL; +} + +static int mali_c55_params_buf_prepare(struct vb2_buffer *vb) +{ + struct mali_c55_params *params = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mali_c55_params_buf *buf = to_mali_c55_params_buf(vbuf); + struct v4l2_isp_params_buffer *config = vb2_plane_vaddr(vb, 0); + struct mali_c55 *mali_c55 = params->mali_c55; + int ret; + + if (config->version != MALI_C55_PARAM_BUFFER_V1) { + dev_dbg(mali_c55->dev, + "Unsupported extensible format version: %u\n", + config->version); + return -EINVAL; + } + + ret = v4l2_isp_params_validate_buffer_size(mali_c55->dev, vb, + v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE)); + if (ret) + return ret; + + /* + * Copy the parameters buffer provided by userspace to the internal + * scratch buffer. This protects against the chance of userspace making + * changed to the buffer content whilst the driver processes it. + */ + + memcpy(buf->config, config, v4l2_isp_params_buffer_size(MALI_C55_PARAMS_MAX_SIZE)); + + return v4l2_isp_params_validate_buffer(mali_c55->dev, vb, buf->config, + mali_c55_params_block_types_info, + ARRAY_SIZE(mali_c55_params_block_types_info)); +} + +static void mali_c55_params_buf_queue(struct vb2_buffer *vb) +{ + struct mali_c55_params *params = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mali_c55_params_buf *buf = to_mali_c55_params_buf(vbuf); + + spin_lock(¶ms->buffers.lock); + list_add_tail(&buf->queue, ¶ms->buffers.queue); + spin_unlock(¶ms->buffers.lock); +} + +static void mali_c55_params_return_buffers(struct mali_c55_params *params, + enum vb2_buffer_state state) +{ + struct mali_c55_params_buf *buf, *tmp; + + guard(spinlock)(¶ms->buffers.lock); + + list_for_each_entry_safe(buf, tmp, ¶ms->buffers.queue, queue) { + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } +} + +static int mali_c55_params_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct mali_c55_params *params = vb2_get_drv_priv(q); + struct mali_c55 *mali_c55 = params->mali_c55; + int ret; + + ret = pm_runtime_resume_and_get(mali_c55->dev); + if (ret) + goto err_return_buffers; + + ret = video_device_pipeline_alloc_start(¶ms->vdev); + if (ret) + goto err_pm_put; + + if (mali_c55_pipeline_ready(mali_c55)) { + ret = v4l2_subdev_enable_streams(&mali_c55->isp.sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + if (ret < 0) + goto err_stop_pipeline; + } + + return 0; + +err_stop_pipeline: + video_device_pipeline_stop(¶ms->vdev); +err_pm_put: + pm_runtime_put_autosuspend(mali_c55->dev); +err_return_buffers: + mali_c55_params_return_buffers(params, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void mali_c55_params_stop_streaming(struct vb2_queue *q) +{ + struct mali_c55_params *params = vb2_get_drv_priv(q); + struct mali_c55 *mali_c55 = params->mali_c55; + struct mali_c55_isp *isp = &mali_c55->isp; + + if (mali_c55_pipeline_ready(mali_c55)) { + if (v4l2_subdev_is_streaming(&isp->sd)) + v4l2_subdev_disable_streams(&isp->sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + } + + video_device_pipeline_stop(¶ms->vdev); + mali_c55_params_return_buffers(params, VB2_BUF_STATE_ERROR); + pm_runtime_put_autosuspend(params->mali_c55->dev); +} + +static const struct vb2_ops mali_c55_params_vb2_ops = { + .queue_setup = mali_c55_params_queue_setup, + .buf_init = mali_c55_params_buf_init, + .buf_cleanup = mali_c55_params_buf_cleanup, + .buf_queue = mali_c55_params_buf_queue, + .buf_prepare = mali_c55_params_buf_prepare, + .start_streaming = mali_c55_params_start_streaming, + .stop_streaming = mali_c55_params_stop_streaming, +}; + +void mali_c55_params_write_config(struct mali_c55 *mali_c55) +{ + struct mali_c55_params *params = &mali_c55->params; + struct v4l2_isp_params_buffer *config; + struct mali_c55_params_buf *buf; + size_t block_offset = 0; + size_t max_offset; + + spin_lock(¶ms->buffers.lock); + + buf = list_first_entry_or_null(¶ms->buffers.queue, + struct mali_c55_params_buf, queue); + if (buf) + list_del(&buf->queue); + spin_unlock(¶ms->buffers.lock); + + if (!buf) + return; + + buf->vb.sequence = mali_c55->isp.frame_sequence; + config = buf->config; + + max_offset = config->data_size; + + /* + * Walk the list of parameter blocks and process them. No validation is + * done here, as the contents of the config buffer are already checked + * when the buffer is queued. + */ + while (max_offset && block_offset < max_offset) { + union mali_c55_params_block block; + mali_c55_params_handler handler; + + block.data = &config->data[block_offset]; + + /* We checked the array index already in .buf_queue() */ + handler = mali_c55_params_handlers[block.header->type]; + handler(mali_c55, block); + + block_offset += block.header->size; + } + + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +void mali_c55_unregister_params(struct mali_c55 *mali_c55) +{ + struct mali_c55_params *params = &mali_c55->params; + + if (!video_is_registered(¶ms->vdev)) + return; + + vb2_video_unregister_device(¶ms->vdev); + media_entity_cleanup(¶ms->vdev.entity); + mutex_destroy(¶ms->lock); +} + +int mali_c55_register_params(struct mali_c55 *mali_c55) +{ + struct mali_c55_params *params = &mali_c55->params; + struct video_device *vdev = ¶ms->vdev; + struct vb2_queue *vb2q = ¶ms->queue; + int ret; + + mutex_init(¶ms->lock); + INIT_LIST_HEAD(¶ms->buffers.queue); + spin_lock_init(¶ms->buffers.lock); + + params->pad.flags = MEDIA_PAD_FL_SOURCE; + ret = media_entity_pads_init(¶ms->vdev.entity, 1, ¶ms->pad); + if (ret) + goto err_destroy_mutex; + + vb2q->type = V4L2_BUF_TYPE_META_OUTPUT; + vb2q->io_modes = VB2_MMAP | VB2_DMABUF; + vb2q->drv_priv = params; + vb2q->mem_ops = &vb2_dma_contig_memops; + vb2q->ops = &mali_c55_params_vb2_ops; + vb2q->buf_struct_size = sizeof(struct mali_c55_params_buf); + vb2q->min_queued_buffers = 1; + vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb2q->lock = ¶ms->lock; + vb2q->dev = mali_c55->dev; + + ret = vb2_queue_init(vb2q); + if (ret) { + dev_err(mali_c55->dev, "params vb2 queue init failed\n"); + goto err_cleanup_entity; + } + + strscpy(params->vdev.name, "mali-c55 3a params", + sizeof(params->vdev.name)); + vdev->release = video_device_release_empty; + vdev->fops = &mali_c55_params_v4l2_fops; + vdev->ioctl_ops = &mali_c55_params_v4l2_ioctl_ops; + vdev->lock = ¶ms->lock; + vdev->v4l2_dev = &mali_c55->v4l2_dev; + vdev->queue = ¶ms->queue; + vdev->device_caps = V4L2_CAP_META_OUTPUT | V4L2_CAP_STREAMING | + V4L2_CAP_IO_MC; + vdev->vfl_dir = VFL_DIR_TX; + video_set_drvdata(vdev, params); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(mali_c55->dev, + "failed to register params video device\n"); + goto err_release_vb2q; + } + + params->mali_c55 = mali_c55; + + return 0; + +err_release_vb2q: + vb2_queue_release(vb2q); +err_cleanup_entity: + media_entity_cleanup(¶ms->vdev.entity); +err_destroy_mutex: + mutex_destroy(¶ms->lock); + + return ret; +} 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..f5a148add1c8 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-registers.h @@ -0,0 +1,449 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * ARM Mali-C55 ISP Driver - Register definitions + * + * Copyright (C) 2025 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_WRITE_MASK BIT(1) +#define MALI_C55_MCU_CONFIG_WRITE(x) ((x) << 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 + +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 +}; + +#define MALI_C55_INTERRUPT_BIT(x) BIT(x) + +#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_VBLANK(x) ((x) << 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) + +#define MALI_C55_REG_1024BIN_HIST 0x054a8 +#define MALI_C55_1024BIN_HIST_SIZE 4096 + +/* 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 << 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_BAYER_ORDER_RGGB 0 +#define MALI_C55_BAYER_ORDER_GRBG 1 +#define MALI_C55_BAYER_ORDER_GBRG 2 +#define MALI_C55_BAYER_ORDER_BGGR 3 + +#define MALI_C55_REG_METERING_CONFIG 0x18ed0 +#define MALI_C55_5BIN_HIST_DISABLE_MASK BIT(0) +#define MALI_C55_5BIN_HIST_SWITCH_MASK GENMASK(2, 1) +#define MALI_C55_5BIN_HIST_SWITCH(x) ((x) << 1) +#define MALI_C55_AF_DISABLE_MASK BIT(4) +#define MALI_C55_AF_SWITCH_MASK BIT(5) +#define MALI_C55_AWB_DISABLE_MASK BIT(8) +#define MALI_C55_AWB_SWITCH_MASK BIT(9) +#define MALI_C55_AWB_SWITCH(x) ((x) << 9) +#define MALI_C55_AEXP_HIST_DISABLE_MASK BIT(12) +#define MALI_C55_AEXP_HIST_DISABLE (0x01 << 12) +#define MALI_C55_AEXP_HIST_SWITCH_MASK GENMASK(14, 13) +#define MALI_C55_AEXP_HIST_SWITCH(x) ((x) << 13) +#define MALI_C55_AEXP_IHIST_DISABLE_MASK BIT(16) +#define MALI_C55_AEXP_IHIST_DISABLE (0x01 << 12) +#define MALI_C55_AEXP_SRC_MASK BIT(24) + +#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_TEST_PATTERN_RGB(x) ((x) << 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 << 16) +#define MALI_C55_INPUT_WIDTH_10BIT (1 << 16) +#define MALI_C55_INPUT_WIDTH_12BIT (2 << 16) +#define MALI_C55_INPUT_WIDTH_14BIT (3 << 16) +#define MALI_C55_INPUT_WIDTH_16BIT (4 << 16) +#define MALI_C55_INPUT_WIDTH_20BIT (5 << 16) +#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_DIGITAL_GAIN 0x1926c +#define MALI_C55_DIGITAL_GAIN_MASK GENMASK(12, 0) +#define MALI_C55_REG_DIGITAL_GAIN_OFFSET 0x19270 +#define MALI_C55_DIGITAL_GAIN_OFFSET_MASK GENMASK(19, 0) + +#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) + +/* Temper DMA */ +#define MALI_C55_REG_TEMPER_DMA_IO 0x1ab78 +#define MALI_C55_TEMPER_DMA_WRITE_ON BIT(0) +#define MALI_C55_TEMPER_DMA_READ_ON BIT(1) + +/* Black Level Correction Configuration */ +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_00 0x1abcc +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_01 0x1abd0 +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_10 0x1abd4 +#define MALI_C55_REG_SENSOR_OFF_PRE_SHA_11 0x1abd8 +#define MALI_C55_SENSOR_OFF_PRE_SHA_MASK 0xfffff + +/* Lens Mesh Shading Configuration */ +#define MALI_C55_REG_MESH_SHADING_TABLES 0x13074 +#define MALI_C55_REG_MESH_SHADING_CONFIG 0x1abfc +#define MALI_C55_MESH_SHADING_ENABLE_MASK BIT(0) +#define MALI_C55_MESH_SHADING_MESH_SHOW_MASK BIT(1) +#define MALI_C55_MESH_SHADING_MESH_SHOW(x) ((x) << 1) +#define MALI_C55_MESH_SHADING_SCALE_MASK GENMASK(4, 2) +#define MALI_C55_MESH_SHADING_SCALE(x) ((x) << 2) +#define MALI_C55_MESH_SHADING_PAGE_R_MASK GENMASK(9, 8) +#define MALI_C55_MESH_SHADING_PAGE_R(x) ((x) << 8) +#define MALI_C55_MESH_SHADING_PAGE_G_MASK GENMASK(11, 10) +#define MALI_C55_MESH_SHADING_PAGE_G(x) ((x) << 10) +#define MALI_C55_MESH_SHADING_PAGE_B_MASK GENMASK(13, 12) +#define MALI_C55_MESH_SHADING_PAGE_B(x) ((x) << 12) +#define MALI_C55_MESH_SHADING_MESH_WIDTH_MASK GENMASK(21, 16) +#define MALI_C55_MESH_SHADING_MESH_WIDTH(x) ((x) << 16) +#define MALI_C55_MESH_SHADING_MESH_HEIGHT_MASK GENMASK(29, 24) +#define MALI_C55_MESH_SHADING_MESH_HEIGHT(x) ((x) << 24) + +#define MALI_C55_REG_MESH_SHADING_ALPHA_BANK 0x1ac04 +#define MALI_C55_MESH_SHADING_ALPHA_BANK_R_MASK GENMASK(2, 0) +#define MALI_C55_MESH_SHADING_ALPHA_BANK_G_MASK GENMASK(5, 3) +#define MALI_C55_MESH_SHADING_ALPHA_BANK_G(x) ((x) << 3) +#define MALI_C55_MESH_SHADING_ALPHA_BANK_B_MASK GENMASK(8, 6) +#define MALI_C55_MESH_SHADING_ALPHA_BANK_B(x) ((x) << 6) +#define MALI_C55_REG_MESH_SHADING_ALPHA 0x1ac08 +#define MALI_C55_MESH_SHADING_ALPHA_R_MASK GENMASK(7, 0) +#define MALI_C55_MESH_SHADING_ALPHA_G_MASK GENMASK(15, 8) +#define MALI_C55_MESH_SHADING_ALPHA_G(x) ((x) << 8) +#define MALI_C55_MESH_SHADING_ALPHA_B_MASK GENMASK(23, 16) +#define MALI_C55_MESH_SHADING_ALPHA_B(x) ((x) << 16) +#define MALI_C55_REG_MESH_SHADING_MESH_STRENGTH 0x1ac0c +#define MALI_c55_MESH_STRENGTH_MASK GENMASK(15, 0) + +/* AWB Gains Configuration */ +#define MALI_C55_REG_AWB_GAINS1 0x1ac10 +#define MALI_C55_AWB_GAIN00_MASK GENMASK(11, 0) +#define MALI_C55_AWB_GAIN01_MASK GENMASK(27, 16) +#define MALI_C55_AWB_GAIN01(x) ((x) << 16) +#define MALI_C55_REG_AWB_GAINS2 0x1ac14 +#define MALI_C55_AWB_GAIN10_MASK GENMASK(11, 0) +#define MALI_C55_AWB_GAIN11_MASK GENMASK(27, 16) +#define MALI_C55_AWB_GAIN11(x) ((x) << 16) +#define MALI_C55_REG_AWB_GAINS1_AEXP 0x1ac18 +#define MALI_C55_REG_AWB_GAINS2_AEXP 0x1ac1c + +/* 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) + +/* AWB Statistics Configuration */ +#define MALI_C55_REG_AWB_STATS_MODE 0x1b29c +#define MALI_C55_AWB_STATS_MODE_MASK BIT(0) +#define MALI_C55_REG_AWB_WHITE_LEVEL 0x1b2a0 +#define MALI_C55_AWB_WHITE_LEVEL_MASK GENMASK(9, 0) +#define MALI_C55_REG_AWB_BLACK_LEVEL 0x1b2a4 +#define MALI_C55_AWB_BLACK_LEVEL_MASK GENMASK(9, 0) +#define MALI_C55_REG_AWB_CR_MAX 0x1b2a8 +#define MALI_C55_AWB_CR_MAX_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CR_MIN 0x1b2ac +#define MALI_C55_AWB_CR_MIN_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CB_MAX 0x1b2b0 +#define MALI_C55_AWB_CB_MAX_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CB_MIN 0x1b2b4 +#define MALI_C55_AWB_CB_MIN_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_NODES_USED 0x1b2c4 +#define MALI_C55_AWB_NODES_USED_HORIZ_MASK GENMASK(7, 0) +#define MALI_C55_AWB_NODES_USED_VERT_MASK GENMASK(15, 8) +#define MALI_C55_AWB_NODES_USED_VERT(x) ((x) << 8) +#define MALI_C55_REG_AWB_CR_HIGH 0x1b2c8 +#define MALI_C55_AWB_CR_HIGH_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CR_LOW 0x1b2cc +#define MALI_C55_AWB_CR_LOW_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CB_HIGH 0x1b2d0 +#define MALI_C55_AWB_CB_HIGH_MASK GENMASK(11, 0) +#define MALI_C55_REG_AWB_CB_LOW 0x1b2d4 +#define MALI_C55_AWB_CB_LOW_MASK GENMASK(11, 0) + +/* AEXP Metering Histogram Configuration */ +#define MALI_C55_REG_AEXP_HIST_BASE 0x1b730 +#define MALI_C55_REG_AEXP_IHIST_BASE 0x1bbac +#define MALI_C55_AEXP_HIST_SKIP_OFFSET 0 +#define MALI_C55_AEXP_HIST_SKIP_X_MASK GENMASK(2, 0) +#define MALI_C55_AEXP_HIST_SKIP_X(x) ((x) << 0) +#define MALI_C55_AEXP_HIST_OFFSET_X_MASK BIT(3) +#define MALI_C55_AEXP_HIST_OFFSET_X(x) ((x) << 3) +#define MALI_C55_AEXP_HIST_SKIP_Y_MASK GENMASK(6, 4) +#define MALI_C55_AEXP_HIST_SKIP_Y(x) ((x) << 4) +#define MALI_C55_AEXP_HIST_OFFSET_Y_MASK BIT(7) +#define MALI_C55_AEXP_HIST_OFFSET_Y(x) ((x) << 7) +#define MALI_C55_AEXP_HIST_SCALE_OFFSET 4 +#define MALI_C55_AEXP_HIST_SCALE_BOTTOM_MASK GENMASK(3, 0) +#define MALI_C55_AEXP_HIST_SCALE_TOP_MASK GENMASK(7, 4) +#define MALI_C55_AEXP_HIST_SCALE_TOP(x) ((x) << 4) +#define MALI_C55_AEXP_HIST_PLANE_MODE_OFFSET 16 +#define MALI_C55_AEXP_HIST_PLANE_MODE_MASK GENMASK(2, 0) +#define MALI_C55_AEXP_HIST_NODES_USED_OFFSET 52 +#define MALI_C55_AEXP_HIST_NODES_USED_HORIZ_MASK GENMASK(7, 0) +#define MALI_C55_AEXP_HIST_NODES_USED_VERT_MASK GENMASK(15, 8) +#define MALI_C55_AEXP_HIST_NODES_USED_VERT(x) ((x) << 8) +#define MALI_C55_AEXP_HIST_ZONE_WEIGHTS_OFFSET 56 +#define MALI_C55_AEXP_HIST_ZONE_WEIGHT_MASK 0x0f0f0f0f + +/* + * 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 0x1c098 +#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_CS_CONV_FILTER_ENABLE (0x01 << 1) +#define MALI_C55_CS_CONV_HORZ_DOWNSAMPLE_ENABLE (0x01 << 2) +#define MALI_C55_CS_CONV_VERT_DOWNSAMPLE_ENABLE (0x01 << 3) +#define MALI_C55_REG_Y_WRITER_MODE 0x1c0ec +#define MALI_C55_REG_UV_WRITER_MODE 0x1c144 +#define MALI_C55_WRITER_MODE_MASK GENMASK(4, 0) +#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_WRITER_SUBMODE_MASK GENMASK(7, 6) +#define MALI_C55_WRITER_SUBMODE(x) ((x) << 6) +#define MALI_C55_OUTPUT_PLANE_ALT0 0 +#define MALI_C55_OUTPUT_PLANE_ALT1 1 +#define MALI_C55_OUTPUT_PLANE_ALT2 2 +#define MALI_C55_WRITER_FRAME_WRITE_MASK BIT(9) +#define MALI_C55_WRITER_FRAME_WRITE_ENABLE (0x01 << 9) +#define MALI_C55_REG_ACTIVE_OUT_Y_SIZE 0x1c0f0 +#define MALI_C55_REG_ACTIVE_OUT_UV_SIZE 0x1c148 +#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 0x1c0f4 +#define MALI_C55_REG_Y_WRITER_BANKS_CONFIG 0x1c108 +#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 0x1c10c +#define MALI_C55_REG_UV_WRITER_BANKS_BASE 0x1c14c +#define MALI_C55_REG_UV_WRITER_BANKS_CONFIG 0x1c160 +#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 0x1c164 + +#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 0x1c028 +#define MALI_C55_CROP_ENABLE BIT(0) +#define MALI_C55_REG_CROP_X_START 0x1c02c +#define MALI_C55_REG_CROP_Y_START 0x1c030 +#define MALI_C55_REG_CROP_X_SIZE 0x1c034 +#define MALI_C55_REG_CROP_Y_SIZE 0x1c038 +#define MALI_C55_REG_SCALER_TIMEOUT_EN 0x1c040 +#define MALI_C55_SCALER_TIMEOUT_EN BIT(4) +#define MALI_C55_SCALER_TIMEOUT(t) ((t) << 16) +#define MALI_C55_REG_SCALER_IN_WIDTH 0x1c044 +#define MALI_C55_REG_SCALER_IN_HEIGHT 0x1c048 +#define MALI_C55_REG_SCALER_OUT_WIDTH 0x1c04c +#define MALI_C55_REG_SCALER_OUT_HEIGHT 0x1c050 +#define MALI_C55_REG_SCALER_HFILT_TINC 0x1c054 +#define MALI_C55_REG_SCALER_HFILT_COEF 0x1c058 +#define MALI_C55_REG_SCALER_VFILT_TINC 0x1c05c +#define MALI_C55_REG_SCALER_VFILT_COEF 0x1c060 + +#define MALI_C55_REG_GAMMA_RGB_ENABLE 0x1c064 +#define MALI_C55_GAMMA_ENABLE_MASK BIT(0) +#define MALI_C55_REG_GAMMA_GAINS_1 0x1c068 +#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 0x1c06c +#define MALI_C55_GAMMA_GAIN_B_MASK GENMASK(11, 0) +#define MALI_C55_REG_GAMMA_OFFSETS_1 0x1c070 +#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 0x1c074 +#define MALI_C55_GAMMA_OFFSET_B_MASK GENMASK(11, 0) + +/* + * A re-definition of an above register. These will usually be written on a per + * capture device basis and handled with mali_c55_cap_dev_write(), but on + * startup is written by core.c + */ +#define MALI_C55_REG_FR_GAMMA_RGB_ENABLE 0x1c064 +#define MALI_C55_REG_DS_GAMMA_RGB_ENABLE 0x1c1d8 + +#define MALI_C55_REG_FR_SCALER_HFILT 0x34a8 +#define MALI_C55_REG_FR_SCALER_VFILT 0x44a8 +#define MALI_C55_REG_DS_SCALER_HFILT 0x14a8 +#define MALI_C55_REG_DS_SCALER_VFILT 0x24a8 + +#endif /* _MALI_C55_REGISTERS_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..a8d739af74b6 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-resizer.c @@ -0,0 +1,1156 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Image signal processor + * + * Copyright (C) 2025 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" + +/* Scaling factor in Q4.20 format. */ +#define MALI_C55_RSZ_SCALER_FACTOR (1U << 20) + +#define MALI_C55_RSZ_COEFS_BANKS 8 +#define MALI_C55_RSZ_COEFS_ENTRIES 64 + +static inline struct mali_c55_resizer * +sd_to_mali_c55_rsz(struct v4l2_subdev *sd) +{ + return container_of(sd, struct mali_c55_resizer, sd); +} + +static const unsigned int +mali_c55_rsz_filter_coeffs_h[MALI_C55_RSZ_COEFS_BANKS] + [MALI_C55_RSZ_COEFS_ENTRIES] = { + { /* 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_rsz_filter_coeffs_v[MALI_C55_RSZ_COEFS_BANKS] + [MALI_C55_RSZ_COEFS_ENTRIES] = { + { /* 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, + } +}; + +static const unsigned int mali_c55_rsz_coef_banks_range_start[] = { + 770, 600, 460, 354, 273, 210, 162, 125 +}; + +/* + * Select the right filter coefficients bank based on the scaler input and the + * scaler output sizes ratio, set by the v4l2 crop and scale selection + * rectangles respectively. + */ +static unsigned int mali_c55_rsz_calculate_bank(struct mali_c55 *mali_c55, + unsigned int rsz_in, + unsigned int rsz_out) +{ + unsigned int rsz_ratio = (rsz_out * 1000U) / rsz_in; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(mali_c55_rsz_coef_banks_range_start); i++) + if (rsz_ratio >= mali_c55_rsz_coef_banks_range_start[i]) + break; + + return i; +} + +static const u32 rsz_non_bypass_src_fmts[] = { + MEDIA_BUS_FMT_RGB121212_1X36, + MEDIA_BUS_FMT_YUV10_1X30 +}; + +static void mali_c55_resizer_program_coefficients(struct mali_c55_resizer *rsz) +{ + struct mali_c55 *mali_c55 = rsz->mali_c55; + unsigned int haddr = rsz->id == MALI_C55_RSZ_FR ? + MALI_C55_REG_FR_SCALER_HFILT : + MALI_C55_REG_DS_SCALER_HFILT; + unsigned int vaddr = rsz->id == MALI_C55_RSZ_FR ? + MALI_C55_REG_FR_SCALER_VFILT : + MALI_C55_REG_DS_SCALER_VFILT; + + for (unsigned int i = 0; i < MALI_C55_RSZ_COEFS_BANKS; i++) { + for (unsigned int j = 0; j < MALI_C55_RSZ_COEFS_ENTRIES; j++) { + mali_c55_write(mali_c55, haddr, + mali_c55_rsz_filter_coeffs_h[i][j]); + mali_c55_write(mali_c55, vaddr, + mali_c55_rsz_filter_coeffs_v[i][j]); + + haddr += sizeof(u32); + vaddr += sizeof(u32); + } + } +} + +static int mali_c55_rsz_program_crop(struct mali_c55_resizer *rsz, + const struct v4l2_subdev_state *state) +{ + const struct v4l2_mbus_framefmt *fmt; + const struct v4l2_rect *crop; + + /* Verify if crop should be enabled. */ + fmt = v4l2_subdev_state_get_format(state, MALI_C55_RSZ_SINK_PAD, 0); + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RSZ_SINK_PAD, 0); + + if (fmt->width == crop->width && fmt->height == crop->height) + return MALI_C55_BYPASS_CROP; + + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_CROP_X_START, + crop->left); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_CROP_Y_START, + crop->top); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_CROP_X_SIZE, + crop->width); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_CROP_Y_SIZE, + crop->height); + + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_CROP_EN, + MALI_C55_CROP_ENABLE); + + return 0; +} + +static int mali_c55_rsz_program_resizer(struct mali_c55_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct mali_c55 *mali_c55 = rsz->mali_c55; + const struct v4l2_rect *crop, *scale; + 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_RSZ_SINK_PAD, 0); + scale = v4l2_subdev_state_get_compose(state, MALI_C55_RSZ_SINK_PAD, 0); + + if (crop->width == scale->width && crop->height == scale->height) + return MALI_C55_BYPASS_SCALER; + + /* Program the scaler coefficients if the scaler is in use. */ + mali_c55_resizer_program_coefficients(rsz); + + /* Program the V/H scaling factor in Q4.20 format. */ + h_scale = crop->width * MALI_C55_RSZ_SCALER_FACTOR; + v_scale = crop->height * MALI_C55_RSZ_SCALER_FACTOR; + + do_div(h_scale, scale->width); + do_div(v_scale, scale->height); + + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_IN_WIDTH, + crop->width); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_IN_HEIGHT, + crop->height); + + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_OUT_WIDTH, + scale->width); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_OUT_HEIGHT, + scale->height); + + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_HFILT_TINC, + h_scale); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_VFILT_TINC, + v_scale); + + /* Select the scaler coefficients bank to use. */ + h_bank = mali_c55_rsz_calculate_bank(mali_c55, crop->width, + scale->width); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_HFILT_COEF, + h_bank); + + v_bank = mali_c55_rsz_calculate_bank(mali_c55, crop->height, + scale->height); + mali_c55_cap_dev_write(rsz->cap_dev, MALI_C55_REG_SCALER_VFILT_COEF, + v_bank); + + return 0; +} + +static void mali_c55_rsz_program(struct mali_c55_resizer *rsz, + struct v4l2_subdev_state *state) +{ + struct mali_c55 *mali_c55 = rsz->mali_c55; + u32 bypass = 0; + + /* Verify if cropping and scaling should be enabled. */ + bypass |= mali_c55_rsz_program_crop(rsz, state); + bypass |= mali_c55_rsz_program_resizer(rsz, state); + + mali_c55_ctx_update_bits(mali_c55, rsz->id == MALI_C55_RSZ_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_rsz_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_RSZ_SINK_PAD; +} + +/* + * When operating in bypass mode, 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. + */ +static u32 mali_c55_rsz_shift_mbus_code(u32 mbus_code) +{ + const struct mali_c55_isp_format_info *fmt = + mali_c55_isp_get_mbus_config_by_code(mbus_code); + + if (!fmt) + return -EINVAL; + + return fmt->shifted_code; +} + +static int __mali_c55_rsz_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + const struct v4l2_subdev_krouting *routing) +{ + struct mali_c55_resizer *rsz = sd_to_mali_c55_rsz(sd); + unsigned int active_sink = UINT_MAX; + struct v4l2_mbus_framefmt *src_fmt; + 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_dbg(rsz->mali_c55->dev, + "Only one route can be active"); + return -EINVAL; + } + + active_sink = route->sink_pad; + } + if (active_sink == UINT_MAX) { + dev_dbg(rsz->mali_c55->dev, "One route has to be active"); + return -EINVAL; + } + + ret = v4l2_subdev_set_routing(sd, state, routing); + if (ret) { + dev_dbg(rsz->mali_c55->dev, "Failed to set routing\n"); + return ret; + } + + fmt = v4l2_subdev_state_get_format(state, active_sink, 0); + fmt->width = MALI_C55_DEFAULT_WIDTH; + fmt->height = MALI_C55_DEFAULT_HEIGHT; + fmt->colorspace = V4L2_COLORSPACE_SRGB; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(false, + fmt->colorspace, + fmt->ycbcr_enc); + fmt->field = V4L2_FIELD_NONE; + + if (active_sink == MALI_C55_RSZ_SINK_PAD) { + struct v4l2_rect *crop, *compose; + + fmt->code = MEDIA_BUS_FMT_RGB121212_1X36; + + crop = v4l2_subdev_state_get_crop(state, active_sink, 0); + compose = v4l2_subdev_state_get_compose(state, active_sink, 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_RSZ_SOURCE_PAD, + 0); + *src_fmt = *fmt; + + /* In the event this is the bypass pad the mbus code needs correcting */ + if (active_sink == MALI_C55_RSZ_SINK_BYPASS_PAD) + src_fmt->code = mali_c55_rsz_shift_mbus_code(src_fmt->code); + + return 0; +} + +static int mali_c55_rsz_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + const struct mali_c55_isp_format_info *fmt; + struct v4l2_mbus_framefmt *sink_fmt; + u32 sink_pad; + + switch (code->pad) { + case MALI_C55_RSZ_SINK_PAD: + if (code->index) + return -EINVAL; + + code->code = MEDIA_BUS_FMT_RGB121212_1X36; + + return 0; + case MALI_C55_RSZ_SOURCE_PAD: + sink_pad = mali_c55_rsz_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_RSZ_SINK_BYPASS_PAD) { + if (code->index) + return -EINVAL; + + code->code = mali_c55_rsz_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(rsz_non_bypass_src_fmts)) + return -EINVAL; + + code->code = rsz_non_bypass_src_fmts[code->index]; + + return 0; + case MALI_C55_RSZ_SINK_BYPASS_PAD: + fmt = mali_c55_isp_get_mbus_config_by_index(code->index); + if (fmt) { + code->code = fmt->code; + return 0; + } + + break; + } + + return -EINVAL; +} + +static int mali_c55_rsz_enum_frame_size(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_frame_size_enum *fse) +{ + const struct mali_c55_isp_format_info *fmt; + struct v4l2_mbus_framefmt *sink_fmt; + struct v4l2_rect *compose; + u32 sink_pad; + + switch (fse->pad) { + case MALI_C55_RSZ_SINK_PAD: + if (fse->index || fse->code != MEDIA_BUS_FMT_RGB121212_1X36) + 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; + case MALI_C55_RSZ_SOURCE_PAD: + sink_pad = mali_c55_rsz_get_active_sink(state); + sink_fmt = v4l2_subdev_state_get_format(state, sink_pad, 0); + + if (sink_pad == MALI_C55_RSZ_SINK_BYPASS_PAD) { + if (fse->index) + return -EINVAL; + + fmt = mali_c55_isp_get_mbus_config_by_shifted_code(fse->code); + if (!fmt) + return -EINVAL; + + fse->min_width = sink_fmt->width; + fse->max_width = sink_fmt->width; + fse->min_height = sink_fmt->height; + fse->max_height = sink_fmt->height; + + return 0; + } + + if ((fse->code != MEDIA_BUS_FMT_RGB121212_1X36 && + fse->code != MEDIA_BUS_FMT_YUV10_1X30) || fse->index > 1) + return -EINVAL; + + compose = v4l2_subdev_state_get_compose(state, + MALI_C55_RSZ_SINK_PAD, + 0); + + fse->min_width = compose->width; + fse->max_width = compose->width; + fse->min_height = compose->height; + fse->max_height = compose->height; + + return 0; + case MALI_C55_RSZ_SINK_BYPASS_PAD: + fmt = mali_c55_isp_get_mbus_config_by_code(fse->code); + if (fse->index || !fmt) + 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; + } + + return -EINVAL; +} + +static int mali_c55_rsz_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_mbus_framefmt *sink_fmt; + unsigned int active_sink; + struct v4l2_rect *rect; + + sink_fmt = v4l2_subdev_state_get_format(state, format->pad, 0); + + /* + * Clamp to min/max and then reset crop and compose rectangles to the + * newly applied size. + */ + sink_fmt->width = clamp_t(unsigned int, fmt->width, + MALI_C55_MIN_WIDTH, MALI_C55_MAX_WIDTH); + sink_fmt->height = clamp_t(unsigned int, fmt->height, + MALI_C55_MIN_HEIGHT, MALI_C55_MAX_HEIGHT); + + /* + * Make sure the media bus code for the bypass pad is one of the + * supported ISP input media bus codes. Default it to SRGGB otherwise. + */ + if (format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD) + sink_fmt->code = mali_c55_isp_get_mbus_config_by_code(fmt->code) ? + fmt->code : MEDIA_BUS_FMT_SRGGB20_1X20; + + *fmt = *sink_fmt; + + if (format->pad == MALI_C55_RSZ_SINK_PAD) { + rect = v4l2_subdev_state_get_crop(state, format->pad); + rect->left = 0; + rect->top = 0; + rect->width = fmt->width; + rect->height = fmt->height; + + rect = v4l2_subdev_state_get_compose(state, format->pad); + rect->left = 0; + rect->top = 0; + rect->width = fmt->width; + rect->height = fmt->height; + } + + /* If format->pad is routed to the source pad, propagate the format. */ + active_sink = mali_c55_rsz_get_active_sink(state); + if (active_sink == format->pad) { + /* If the bypass route is used, downshift the code to 16bpp. */ + if (active_sink == MALI_C55_RSZ_SINK_BYPASS_PAD) + fmt->code = mali_c55_rsz_shift_mbus_code(fmt->code); + + *v4l2_subdev_state_get_format(state, + MALI_C55_RSZ_SOURCE_PAD, 0) = *fmt; + } + + return 0; +} + +static int mali_c55_rsz_set_source_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 *sink_fmt, *src_fmt; + unsigned int active_sink; + + active_sink = mali_c55_rsz_get_active_sink(state); + sink_fmt = v4l2_subdev_state_get_format(state, active_sink, 0); + src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RSZ_SOURCE_PAD); + + if (active_sink == MALI_C55_RSZ_SINK_PAD) { + /* + * Regular processing pipe: RGB121212 can be color-space + * converted to YUV101010. + */ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(rsz_non_bypass_src_fmts); i++) { + if (fmt->code == rsz_non_bypass_src_fmts[i]) + break; + } + + src_fmt->code = i == ARRAY_SIZE(rsz_non_bypass_src_fmts) ? + MEDIA_BUS_FMT_RGB121212_1X36 : fmt->code; + } else { + /* + * Bypass pipe: the source format is the same as the bypass + * sink pad downshifted to 16bpp. + */ + fmt->code = mali_c55_rsz_shift_mbus_code(sink_fmt->code); + } + + *fmt = *src_fmt; + + return 0; +} + +static int mali_c55_rsz_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_RSZ_SINK_PAD || + format->pad == MALI_C55_RSZ_SINK_BYPASS_PAD) + return mali_c55_rsz_set_sink_fmt(sd, state, format); + + return mali_c55_rsz_set_source_fmt(sd, state, format); +} + +static int mali_c55_rsz_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + if (sel->pad != MALI_C55_RSZ_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_RSZ_SINK_PAD) + : *v4l2_subdev_state_get_compose(state, MALI_C55_RSZ_SINK_PAD); + + return 0; +} + +static int mali_c55_rsz_set_crop(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mali_c55_resizer *rsz = sd_to_mali_c55_rsz(sd); + struct v4l2_mbus_framefmt *sink_fmt, *src_fmt; + struct v4l2_rect *crop, *compose; + + sink_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RSZ_SINK_PAD); + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RSZ_SINK_PAD); + compose = v4l2_subdev_state_get_compose(state, MALI_C55_RSZ_SINK_PAD); + + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) { + /* + * At runtime the compose rectangle and output size cannot be + * changed so we need to clamp the crop rectangle such that the + * compose rectangle can fit within it. + */ + crop->left = clamp_t(unsigned int, sel->r.left, 0, + sink_fmt->width - compose->width); + crop->top = clamp_t(unsigned int, sel->r.top, 0, + sink_fmt->height - compose->height); + crop->width = clamp_t(unsigned int, sel->r.width, compose->width, + sink_fmt->width - crop->left); + crop->height = clamp_t(unsigned int, sel->r.height, compose->height, + sink_fmt->height - crop->top); + + mali_c55_rsz_program(rsz, state); + } else { + /* + * If we're not streaming we can utilise the ISP's full range + * and simply need to propagate the selected rectangle to the + * compose target and source pad format. + */ + crop->left = clamp_t(unsigned int, sel->r.left, 0, + sink_fmt->width); + crop->top = clamp_t(unsigned int, sel->r.top, 0, + sink_fmt->height); + crop->width = clamp_t(unsigned int, sel->r.width, + MALI_C55_MIN_WIDTH, + sink_fmt->width - crop->left); + crop->height = clamp_t(unsigned int, sel->r.height, + MALI_C55_MIN_HEIGHT, + sink_fmt->height - crop->top); + + *compose = *crop; + + src_fmt = v4l2_subdev_state_get_format(state, + MALI_C55_RSZ_SOURCE_PAD); + src_fmt->width = compose->width; + src_fmt->height = compose->height; + } + + sel->r = *crop; + return 0; +} + +static int mali_c55_rsz_set_compose(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct mali_c55_resizer *rsz = sd_to_mali_c55_rsz(sd); + struct mali_c55 *mali_c55 = rsz->mali_c55; + struct v4l2_mbus_framefmt *src_fmt; + struct v4l2_rect *compose, *crop; + + /* + * We cannot change the compose rectangle during streaming, as that + * would require a change in the output buffer size. + */ + if (sel->which == V4L2_SUBDEV_FORMAT_ACTIVE && + v4l2_subdev_is_streaming(sd)) + return -EBUSY; + + /* + * In the FR pipe, the scaler is an optional component that may not be + * fitted. + */ + if (rsz->id == MALI_C55_RSZ_FR && + !(mali_c55->capabilities & MALI_C55_GPS_FRSCALER_FITTED)) + return -EINVAL; + + compose = v4l2_subdev_state_get_compose(state, MALI_C55_RSZ_SINK_PAD); + crop = v4l2_subdev_state_get_crop(state, MALI_C55_RSZ_SINK_PAD); + + compose->left = 0; + compose->top = 0; + compose->width = clamp_t(unsigned int, sel->r.width, crop->width / 8, + crop->width); + compose->height = clamp_t(unsigned int, sel->r.height, crop->height / 8, + crop->height); + + sel->r = *compose; + + /* + * We need to be sure to propagate the compose rectangle size to the + * source pad format. + */ + src_fmt = v4l2_subdev_state_get_format(state, MALI_C55_RSZ_SOURCE_PAD); + src_fmt->width = compose->width; + src_fmt->height = compose->height; + + return 0; +} + +static int mali_c55_rsz_set_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + if (sel->pad != MALI_C55_RSZ_SINK_PAD) + return -EINVAL; + + if (sel->target == V4L2_SEL_TGT_CROP) + return mali_c55_rsz_set_crop(sd, state, sel); + + if (sel->target == V4L2_SEL_TGT_COMPOSE) + return mali_c55_rsz_set_compose(sd, state, sel); + + return -EINVAL; +} + +static int mali_c55_rsz_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_rsz_set_routing(sd, state, routing); +} + +static int mali_c55_rsz_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + struct mali_c55_resizer *rsz = sd_to_mali_c55_rsz(sd); + struct mali_c55 *mali_c55 = rsz->mali_c55; + unsigned int sink_pad; + + sink_pad = mali_c55_rsz_get_active_sink(state); + if (sink_pad == MALI_C55_RSZ_SINK_BYPASS_PAD) { + /* Bypass FR pipe processing if the bypass route is active. */ + mali_c55_ctx_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); + return 0; + } + + /* Disable bypass and use regular processing. */ + mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_ISP_RAW_BYPASS, + MALI_C55_ISP_RAW_BYPASS_FR_BYPASS_MASK, 0); + mali_c55_rsz_program(rsz, state); + + return 0; +} + +static int mali_c55_rsz_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + return 0; +} + +static const struct v4l2_subdev_pad_ops mali_c55_resizer_pad_ops = { + .enum_mbus_code = mali_c55_rsz_enum_mbus_code, + .enum_frame_size = mali_c55_rsz_enum_frame_size, + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = mali_c55_rsz_set_fmt, + .get_selection = mali_c55_rsz_get_selection, + .set_selection = mali_c55_rsz_set_selection, + .set_routing = mali_c55_rsz_set_routing, + .enable_streams = mali_c55_rsz_enable_streams, + .disable_streams = mali_c55_rsz_disable_streams, +}; + +static const struct v4l2_subdev_ops mali_c55_resizer_ops = { + .pad = &mali_c55_resizer_pad_ops, +}; + +static int mali_c55_rsz_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct mali_c55_resizer *rsz = sd_to_mali_c55_rsz(sd); + struct v4l2_subdev_route routes[2] = { + { + .sink_pad = MALI_C55_RSZ_SINK_PAD, + .source_pad = MALI_C55_RSZ_SOURCE_PAD, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }, { + .sink_pad = MALI_C55_RSZ_SINK_BYPASS_PAD, + .source_pad = MALI_C55_RSZ_SOURCE_PAD, + }, + }; + struct v4l2_subdev_krouting routing = { + .num_routes = rsz->num_routes, + .routes = routes, + }; + + return __mali_c55_rsz_set_routing(sd, state, &routing); +} + +static const struct v4l2_subdev_internal_ops mali_c55_resizer_internal_ops = { + .init_state = mali_c55_rsz_init_state, +}; + +static int mali_c55_register_resizer(struct mali_c55 *mali_c55, + unsigned int index) +{ + struct mali_c55_resizer *rsz = &mali_c55->resizers[index]; + struct v4l2_subdev *sd = &rsz->sd; + unsigned int num_pads; + int ret; + + rsz->id = index; + 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; + + rsz->pads[MALI_C55_RSZ_SINK_PAD].flags = MEDIA_PAD_FL_SINK; + rsz->pads[MALI_C55_RSZ_SOURCE_PAD].flags = MEDIA_PAD_FL_SOURCE; + + if (rsz->id == MALI_C55_RSZ_FR) { + num_pads = MALI_C55_RSZ_NUM_PADS; + rsz->num_routes = 2; + + rsz->pads[MALI_C55_RSZ_SINK_BYPASS_PAD].flags = + MEDIA_PAD_FL_SINK; + + snprintf(sd->name, sizeof(sd->name), "%s resizer fr", + MALI_C55_DRIVER_NAME); + + } else { + num_pads = MALI_C55_RSZ_NUM_PADS - 1; + rsz->num_routes = 1; + + snprintf(sd->name, sizeof(sd->name), "%s resizer ds", + MALI_C55_DRIVER_NAME); + } + + ret = media_entity_pads_init(&sd->entity, num_pads, rsz->pads); + if (ret) + return ret; + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_media_cleanup; + + ret = v4l2_device_register_subdev(&mali_c55->v4l2_dev, sd); + if (ret) + goto err_subdev_cleanup; + + rsz->cap_dev = &mali_c55->cap_devs[index]; + rsz->mali_c55 = mali_c55; + + return 0; + +err_subdev_cleanup: + v4l2_subdev_cleanup(sd); +err_media_cleanup: + media_entity_cleanup(&sd->entity); + + return ret; +} + +static void mali_c55_unregister_resizer(struct mali_c55_resizer *rsz) +{ + if (!rsz->mali_c55) + return; + + v4l2_device_unregister_subdev(&rsz->sd); + v4l2_subdev_cleanup(&rsz->sd); + media_entity_cleanup(&rsz->sd.entity); +} + +int mali_c55_register_resizers(struct mali_c55 *mali_c55) +{ + int ret; + + ret = mali_c55_register_resizer(mali_c55, MALI_C55_RSZ_FR); + if (ret) + return ret; + + if (mali_c55->capabilities & MALI_C55_GPS_DS_PIPE_FITTED) { + ret = mali_c55_register_resizer(mali_c55, MALI_C55_RSZ_DS); + if (ret) + goto err_unregister_fr; + } + + return 0; + +err_unregister_fr: + mali_c55_unregister_resizer(&mali_c55->resizers[MALI_C55_RSZ_FR]); + + return ret; +} + +void mali_c55_unregister_resizers(struct mali_c55 *mali_c55) +{ + for (unsigned int i = 0; i < MALI_C55_NUM_RSZS; i++) + mali_c55_unregister_resizer(&mali_c55->resizers[i]); +} diff --git a/drivers/media/platform/arm/mali-c55/mali-c55-stats.c b/drivers/media/platform/arm/mali-c55/mali-c55-stats.c new file mode 100644 index 000000000000..655e52a5f288 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-stats.c @@ -0,0 +1,323 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - 3A Statistics capture device + * + * Copyright (C) 2025 Ideas on Board Oy + */ + +#include <linux/container_of.h> +#include <linux/dev_printk.h> +#include <linux/list.h> +#include <linux/media/arm/mali-c55-config.h> +#include <linux/mutex.h> +#include <linux/pm_runtime.h> +#include <linux/spinlock.h> +#include <linux/string.h> + +#include <media/media-entity.h> +#include <media/v4l2-dev.h> +#include <media/v4l2-event.h> +#include <media/v4l2-fh.h> +#include <media/v4l2-ioctl.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> + +#include "mali-c55-common.h" +#include "mali-c55-registers.h" + +static const unsigned int metering_space_addrs[] = { + [MALI_C55_CONFIG_PING] = 0x095ac, + [MALI_C55_CONFIG_PONG] = 0x2156c, +}; + +static int mali_c55_stats_enum_fmt_meta_cap(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (f->index) + return -EINVAL; + + f->pixelformat = V4L2_META_FMT_MALI_C55_STATS; + + return 0; +} + +static int mali_c55_stats_g_fmt_meta_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + static const struct v4l2_meta_format mfmt = { + .dataformat = V4L2_META_FMT_MALI_C55_STATS, + .buffersize = sizeof(struct mali_c55_stats_buffer) + }; + + f->fmt.meta = mfmt; + + return 0; +} + +static int mali_c55_stats_querycap(struct file *file, + void *priv, 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_stats_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_enum_fmt_meta_cap = mali_c55_stats_enum_fmt_meta_cap, + .vidioc_g_fmt_meta_cap = mali_c55_stats_g_fmt_meta_cap, + .vidioc_s_fmt_meta_cap = mali_c55_stats_g_fmt_meta_cap, + .vidioc_try_fmt_meta_cap = mali_c55_stats_g_fmt_meta_cap, + .vidioc_querycap = mali_c55_stats_querycap, + .vidioc_subscribe_event = v4l2_ctrl_subscribe_event, + .vidioc_unsubscribe_event = v4l2_event_unsubscribe, +}; + +static const struct v4l2_file_operations mali_c55_stats_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 int +mali_c55_stats_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + if (*num_planes && *num_planes > 1) + return -EINVAL; + + if (sizes[0] && sizes[0] < sizeof(struct mali_c55_stats_buffer)) + return -EINVAL; + + *num_planes = 1; + + if (!sizes[0]) + sizes[0] = sizeof(struct mali_c55_stats_buffer); + + return 0; +} + +static void mali_c55_stats_buf_queue(struct vb2_buffer *vb) +{ + struct mali_c55_stats *stats = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct mali_c55_stats_buf *buf = container_of(vbuf, + struct mali_c55_stats_buf, vb); + + vb2_set_plane_payload(vb, 0, sizeof(struct mali_c55_stats_buffer)); + buf->segments_remaining = 2; + buf->failed = false; + + spin_lock(&stats->buffers.lock); + list_add_tail(&buf->queue, &stats->buffers.queue); + spin_unlock(&stats->buffers.lock); +} + +static void mali_c55_stats_return_buffers(struct mali_c55_stats *stats, + enum vb2_buffer_state state) +{ + struct mali_c55_stats_buf *buf, *tmp; + + guard(spinlock)(&stats->buffers.lock); + + list_for_each_entry_safe(buf, tmp, &stats->buffers.queue, queue) { + list_del(&buf->queue); + vb2_buffer_done(&buf->vb.vb2_buf, state); + } +} + +static int mali_c55_stats_start_streaming(struct vb2_queue *q, + unsigned int count) +{ + struct mali_c55_stats *stats = vb2_get_drv_priv(q); + struct mali_c55 *mali_c55 = stats->mali_c55; + int ret; + + ret = pm_runtime_resume_and_get(mali_c55->dev); + if (ret) + goto err_return_buffers; + + ret = video_device_pipeline_alloc_start(&stats->vdev); + if (ret) + goto err_pm_put; + + if (mali_c55_pipeline_ready(mali_c55)) { + ret = v4l2_subdev_enable_streams(&mali_c55->isp.sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + if (ret < 0) + goto err_stop_pipeline; + } + + return 0; + +err_stop_pipeline: + video_device_pipeline_stop(&stats->vdev); +err_pm_put: + pm_runtime_put_autosuspend(mali_c55->dev); +err_return_buffers: + mali_c55_stats_return_buffers(stats, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void mali_c55_stats_stop_streaming(struct vb2_queue *q) +{ + struct mali_c55_stats *stats = vb2_get_drv_priv(q); + struct mali_c55 *mali_c55 = stats->mali_c55; + struct mali_c55_isp *isp = &mali_c55->isp; + + if (mali_c55_pipeline_ready(mali_c55)) { + if (v4l2_subdev_is_streaming(&isp->sd)) + v4l2_subdev_disable_streams(&isp->sd, + MALI_C55_ISP_PAD_SOURCE_VIDEO, + BIT(0)); + } + + video_device_pipeline_stop(&stats->vdev); + mali_c55_stats_return_buffers(stats, VB2_BUF_STATE_ERROR); + + pm_runtime_put_autosuspend(stats->mali_c55->dev); +} + +static const struct vb2_ops mali_c55_stats_vb2_ops = { + .queue_setup = mali_c55_stats_queue_setup, + .buf_queue = mali_c55_stats_buf_queue, + .start_streaming = mali_c55_stats_start_streaming, + .stop_streaming = mali_c55_stats_stop_streaming, +}; + +static void mali_c55_stats_cpu_read(struct mali_c55_stats *stats, + struct mali_c55_stats_buf *buf, + enum mali_c55_config_spaces cfg_space) +{ + struct mali_c55 *mali_c55 = stats->mali_c55; + const void __iomem *src; + size_t length; + void *dst; + + src = mali_c55->base + MALI_C55_REG_1024BIN_HIST; + dst = vb2_plane_vaddr(&buf->vb.vb2_buf, 0); + memcpy_fromio(dst, src, MALI_C55_1024BIN_HIST_SIZE); + + src = mali_c55->base + metering_space_addrs[cfg_space]; + dst += MALI_C55_1024BIN_HIST_SIZE; + length = sizeof(struct mali_c55_stats_buffer) - MALI_C55_1024BIN_HIST_SIZE; + memcpy_fromio(dst, src, length); +} + +void mali_c55_stats_fill_buffer(struct mali_c55 *mali_c55, + enum mali_c55_config_spaces cfg_space) +{ + struct mali_c55_stats *stats = &mali_c55->stats; + struct mali_c55_stats_buf *buf = NULL; + + spin_lock(&stats->buffers.lock); + if (!list_empty(&stats->buffers.queue)) { + buf = list_first_entry(&stats->buffers.queue, + struct mali_c55_stats_buf, queue); + list_del(&buf->queue); + } + spin_unlock(&stats->buffers.lock); + + if (!buf) + return; + + buf->vb.sequence = mali_c55->isp.frame_sequence; + buf->vb.vb2_buf.timestamp = ktime_get_boottime_ns(); + + mali_c55_stats_cpu_read(stats, buf, cfg_space); + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); +} + +void mali_c55_unregister_stats(struct mali_c55 *mali_c55) +{ + struct mali_c55_stats *stats = &mali_c55->stats; + + if (!video_is_registered(&stats->vdev)) + return; + + vb2_video_unregister_device(&stats->vdev); + media_entity_cleanup(&stats->vdev.entity); + + mutex_destroy(&stats->lock); +} + +int mali_c55_register_stats(struct mali_c55 *mali_c55) +{ + struct mali_c55_stats *stats = &mali_c55->stats; + struct video_device *vdev = &stats->vdev; + struct vb2_queue *vb2q = &stats->queue; + int ret; + + mutex_init(&stats->lock); + INIT_LIST_HEAD(&stats->buffers.queue); + spin_lock_init(&stats->buffers.lock); + + stats->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&stats->vdev.entity, 1, &stats->pad); + if (ret) + goto err_destroy_mutex; + + vb2q->type = V4L2_BUF_TYPE_META_CAPTURE; + vb2q->io_modes = VB2_MMAP | VB2_DMABUF; + vb2q->drv_priv = stats; + vb2q->mem_ops = &vb2_dma_contig_memops; + vb2q->ops = &mali_c55_stats_vb2_ops; + vb2q->buf_struct_size = sizeof(struct mali_c55_stats_buf); + vb2q->min_queued_buffers = 1; + vb2q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + vb2q->lock = &stats->lock; + vb2q->dev = mali_c55->dev; + + ret = vb2_queue_init(vb2q); + if (ret) { + dev_err(mali_c55->dev, "stats vb2 queue init failed\n"); + goto err_cleanup_entity; + } + + strscpy(stats->vdev.name, "mali-c55 3a stats", sizeof(stats->vdev.name)); + vdev->release = video_device_release_empty; + vdev->fops = &mali_c55_stats_v4l2_fops; + vdev->ioctl_ops = &mali_c55_stats_v4l2_ioctl_ops; + vdev->lock = &stats->lock; + vdev->v4l2_dev = &mali_c55->v4l2_dev; + vdev->queue = &stats->queue; + vdev->device_caps = V4L2_CAP_META_CAPTURE | V4L2_CAP_STREAMING; + vdev->vfl_dir = VFL_DIR_RX; + video_set_drvdata(vdev, stats); + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + dev_err(mali_c55->dev, + "failed to register stats video device\n"); + goto err_release_vb2q; + } + + stats->mali_c55 = mali_c55; + + return 0; + +err_release_vb2q: + vb2_queue_release(vb2q); +err_cleanup_entity: + media_entity_cleanup(&stats->vdev.entity); +err_destroy_mutex: + + mutex_destroy(&stats->lock); + + return ret; +} 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..1af5d2759a83 --- /dev/null +++ b/drivers/media/platform/arm/mali-c55/mali-c55-tpg.c @@ -0,0 +1,437 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * ARM Mali-C55 ISP Driver - Test pattern generator + * + * Copyright (C) 2025 Ideas on Board Oy + */ + +#include <linux/minmax.h> +#include <linux/pm_runtime.h> +#include <linux/string.h> + +#include <media/media-entity.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-event.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_DEFAULT_MIN_VBLANK 66 +#define MALI_C55_TPG_DEFAULT_DEF_VBLANK 626 +#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_SRGGB20_1X20, + MEDIA_BUS_FMT_RGB202020_1X60, +}; + +static void mali_c55_tpg_update_vblank(struct mali_c55_tpg *tpg, + struct v4l2_mbus_framefmt *format) +{ + unsigned int def_vblank; + unsigned int min_vblank; + unsigned int hts; + int tgt_fps; + + 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 (plus one to the numerator to + * ensure the answer is rounded up). + */ + min_vblank = 15 + (120501 / 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) + def_vblank = min_vblank; + else + def_vblank = (MALI_C55_TPG_PIXEL_RATE / hts + / max(rounddown(tgt_fps, 15), 5)) - format->height; + + def_vblank = ALIGN_DOWN(def_vblank, 2); + + __v4l2_ctrl_modify_range(tpg->ctrls.vblank, min_vblank, + MALI_C55_TPG_MAX_VBLANK, 1, def_vblank); + __v4l2_ctrl_s_ctrl(tpg->ctrls.vblank, def_vblank); +} + +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); + int ret = 0; + + if (!pm_runtime_get_if_in_use(mali_c55->dev)) + return 0; + + switch (ctrl->id) { + case V4L2_CID_TEST_PATTERN: + mali_c55_ctx_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, + MALI_C55_VBLANK(ctrl->val)); + break; + default: + ret = -EINVAL; + break; + } + + pm_runtime_put_autosuspend(mali_c55->dev); + + return ret; +} + +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_tpg *tpg, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt; + u32 test_pattern_format; + + /* + * 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(tpg->mali_c55, MALI_C55_REG_BLANKING, + MALI_C55_REG_HBLANK_MASK, + MALI_C55_TPG_FIXED_HBLANK); + mali_c55_update_bits(tpg->mali_c55, MALI_C55_REG_GEN_VIDEO, + MALI_C55_REG_GEN_VIDEO_MULTI_MASK, + MALI_C55_REG_GEN_VIDEO_MULTI_MASK); + + fmt = v4l2_subdev_state_get_format(state, MALI_C55_TPG_SRC_PAD); + + test_pattern_format = fmt->code == MEDIA_BUS_FMT_RGB202020_1X60 ? + 0x01 : 0x0; + + mali_c55_ctx_update_bits(tpg->mali_c55, MALI_C55_REG_TPG_CH0, + MALI_C55_TEST_PATTERN_RGB_MASK, + MALI_C55_TEST_PATTERN_RGB(test_pattern_format)); +} + +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) +{ + unsigned int i; + + if (fse->index > 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(mali_c55_tpg_mbus_codes); i++) { + if (fse->code == mali_c55_tpg_mbus_codes[i]) + break; + } + + if (i == ARRAY_SIZE(mali_c55_tpg_mbus_codes)) + 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; + unsigned int i; + + 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; + + /* + * 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. + */ + 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_update_vblank(tpg, fmt); + + return 0; +} + +static int mali_c55_tpg_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + 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); + + /* + * We only have a source pad and a single stream, and v4l2-core already + * validated both so we don't need to do that. 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 .enable_streams() + * instead, as the same register is also used to indicate the size of + * the data coming from the sensor. + */ + mali_c55_tpg_configure(tpg, state); + __v4l2_ctrl_handler_setup(sd->ctrl_handler); + + mali_c55_ctx_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 int mali_c55_tpg_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, u32 pad, + u64 streams_mask) +{ + 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); + + mali_c55_ctx_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; +} + +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, + .enable_streams = mali_c55_tpg_enable_streams, + .disable_streams = mali_c55_tpg_disable_streams, +}; + +static const struct v4l2_subdev_core_ops mali_c55_isp_core_ops = { + .subscribe_event = v4l2_ctrl_subdev_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static const struct v4l2_subdev_ops mali_c55_tpg_ops = { + .core = &mali_c55_isp_core_ops, + .pad = &mali_c55_tpg_pad_ops, +}; + +static int mali_c55_tpg_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_mbus_framefmt *fmt = + v4l2_subdev_state_get_format(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; + fmt->colorspace = V4L2_COLORSPACE_RAW; + fmt->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(fmt->colorspace); + fmt->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(fmt->colorspace); + fmt->quantization = V4L2_MAP_QUANTIZATION_DEFAULT(false, + fmt->colorspace, + fmt->ycbcr_enc); + + 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_ctrl *pixel_rate; + struct v4l2_ctrl *hblank; + int ret; + + ret = v4l2_ctrl_handler_init(&ctrls->handler, 4); + if (ret) + return ret; + + 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. + */ + 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 (hblank) + hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + ctrls->vblank = v4l2_ctrl_new_std(&ctrls->handler, + &mali_c55_tpg_ctrl_ops, + V4L2_CID_VBLANK, + MALI_C55_TPG_DEFAULT_MIN_VBLANK, + MALI_C55_TPG_MAX_VBLANK, 1, + MALI_C55_TPG_DEFAULT_DEF_VBLANK); + + pixel_rate = v4l2_ctrl_new_std(&ctrls->handler, &mali_c55_tpg_ctrl_ops, + V4L2_CID_PIXEL_RATE, + MALI_C55_TPG_PIXEL_RATE, + MALI_C55_TPG_PIXEL_RATE, 1, + MALI_C55_TPG_PIXEL_RATE); + if (pixel_rate) + pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + + if (ctrls->handler.error) { + dev_err(mali_c55->dev, "Error during v4l2 controls init\n"); + v4l2_ctrl_handler_free(&ctrls->handler); + return ctrls->handler.error; + } + + mali_c55->tpg.sd.ctrl_handler = &ctrls->handler; + mali_c55->tpg.sd.state_lock = ctrls->handler.lock; + + return 0; +} + +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; + + v4l2_subdev_init(sd, &mali_c55_tpg_ops); + sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; + sd->entity.function = MEDIA_ENT_F_CAM_SENSOR; + 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; + ret = media_entity_pads_init(&sd->entity, 1, pad); + if (ret) { + dev_err(mali_c55->dev, + "Failed to initialize media entity pads\n"); + return ret; + } + + ret = mali_c55_tpg_init_controls(mali_c55); + if (ret) { + dev_err(mali_c55->dev, + "Error initialising controls\n"); + goto err_cleanup_media_entity; + } + + ret = v4l2_subdev_init_finalize(sd); + if (ret) + goto err_free_ctrl_handler; + + 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_cleanup_subdev; + } + + /* + * 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_ctx_write(mali_c55, MALI_C55_REG_TPG_R_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_G_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + mali_c55_ctx_write(mali_c55, MALI_C55_REG_TPG_B_BACKGROUND, + MALI_C55_TPG_BACKGROUND_MAX); + + tpg->mali_c55 = mali_c55; + + return 0; + +err_cleanup_subdev: + v4l2_subdev_cleanup(sd); +err_free_ctrl_handler: + v4l2_ctrl_handler_free(&tpg->ctrls.handler); +err_cleanup_media_entity: + media_entity_cleanup(&sd->entity); + + 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_ctrl_handler_free(&tpg->ctrls.handler); + v4l2_subdev_cleanup(&tpg->sd); + media_entity_cleanup(&tpg->sd.entity); +} |
