Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,5 @@
# SPDX-License-Identifier: GPL-2.0-only
comment "ARM media platform drivers"
source "drivers/media/platform/arm/mali-c55/Kconfig"
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-y += mali-c55/
@@ -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.
@@ -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
@@ -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);
}
@@ -0,0 +1,312 @@
/* 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);
void mali_c55_params_init_isp_config(struct mali_c55 *mali_c55,
const struct v4l2_subdev_state *state);
#endif /* _MALI_C55_COMMON_H */
@@ -0,0 +1,891 @@
// 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/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(&params->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);
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) {
dev_err_probe(dev, ret, "failed to power on\n");
goto err_release_mem;
}
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_pm_runtime_disable;
pm_runtime_idle(&pdev->dev);
mali_c55->irqnum = platform_get_irq(pdev, 0);
if (mali_c55->irqnum < 0) {
ret = mali_c55->irqnum;
goto err_deinit_media_frameworks;
}
return 0;
err_deinit_media_frameworks:
mali_c55_media_frameworks_deinit(mali_c55);
err_pm_runtime_disable:
pm_runtime_set_suspended(&pdev->dev);
pm_runtime_disable(&pdev->dev);
kfree(mali_c55->context.registers);
err_power_off:
__mali_c55_power_off(mali_c55);
err_release_mem:
of_reserved_mem_device_release(dev);
return ret;
}
static void mali_c55_remove(struct platform_device *pdev)
{
struct mali_c55 *mali_c55 = platform_get_drvdata(pdev);
mali_c55_media_frameworks_deinit(mali_c55);
if (!pm_runtime_suspended(&pdev->dev)) {
__mali_c55_power_off(mali_c55);
pm_runtime_set_suspended(&pdev->dev);
}
pm_runtime_disable(&pdev->dev);
kfree(mali_c55->context.registers);
of_reserved_mem_device_release(&pdev->dev);
}
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");
@@ -0,0 +1,646 @@
// 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);
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 default ISP configuration and the apply configurations from
* the first available parameters buffer.
*/
mali_c55_params_init_isp_config(mali_c55, state);
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);
if (IS_ERR(isp->remote_src)) {
ret = PTR_ERR(isp->remote_src);
dev_err(mali_c55->dev, "Failed to get remote source pad: %d\n", ret);
isp->remote_src = NULL;
return ret;
}
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;
sd->dev = mali_c55->dev;
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);
}
@@ -0,0 +1,940 @@
// 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_BLOCK_AWB_CONFIG
* @shading_config: For header->type == MALI_C55_PARAM_MESH_SHADING_CONFIG
* @shading_selection: For header->type == MALI_C55_PARAM_MESH_SHADING_SELECTION
* @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;
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(&params->buffers.lock);
list_add_tail(&buf->queue, &params->buffers.queue);
spin_unlock(&params->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)(&params->buffers.lock);
list_for_each_entry_safe(buf, tmp, &params->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(&params->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(&params->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(&params->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(&params->buffers.lock);
buf = list_first_entry_or_null(&params->buffers.queue,
struct mali_c55_params_buf, queue);
if (buf)
list_del(&buf->queue);
spin_unlock(&params->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_params_init_isp_config(struct mali_c55 *mali_c55,
const struct v4l2_subdev_state *state)
{
const struct mali_c55_isp_format_info *cfg;
const struct v4l2_mbus_framefmt *format;
const struct v4l2_rect *crop;
/* 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);
/*
* 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 sensor offset correction (BLS) module */
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);
/* Configure 1x digital gain. */
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_DIGITAL_GAIN,
MALI_C55_DIGITAL_GAIN_MASK, 256);
/* Set all AWB gains to 1x. at both AWB configuration points*/
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS1,
MALI_C55_AWB_GAIN00_MASK, 256);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS1,
MALI_C55_AWB_GAIN01_MASK,
MALI_C55_AWB_GAIN01(256));
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS2,
MALI_C55_AWB_GAIN10_MASK, 256);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS2,
MALI_C55_AWB_GAIN11_MASK,
MALI_C55_AWB_GAIN11(256));
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS1_AEXP,
MALI_C55_AWB_GAIN00_MASK, 256);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS1_AEXP,
MALI_C55_AWB_GAIN01_MASK,
MALI_C55_AWB_GAIN01(256));
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS2_AEXP,
MALI_C55_AWB_GAIN10_MASK, 256);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_AWB_GAINS2_AEXP,
MALI_C55_AWB_GAIN11_MASK,
MALI_C55_AWB_GAIN11(256));
/* Bypass mesh shading corrections (LSC). */
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_MESH_SHADING_CONFIG,
MALI_C55_MESH_SHADING_ENABLE_MASK,
false);
/* 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);
/* Disable IRIDIX module. */
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_BYPASS_3,
MALI_C55_REG_BYPASS_3_IRIDIX,
MALI_C55_REG_BYPASS_3_IRIDIX);
/* Bypass the colour noise reduction and the PF modules */
mali_c55_ctx_write(mali_c55, MALI_C55_REG_BYPASS_4,
MALI_C55_REG_BYPASS_4_CNR |
MALI_C55_REG_BYPASS_4_PF_CORRECTION);
/* 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);
/* Disable AWB stats. */
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
MALI_C55_AWB_DISABLE_MASK,
MALI_C55_AWB_DISABLE_MASK);
/* Disable auto-exposure 1024-bin histograms at both tap points. */
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
MALI_C55_AEXP_HIST_DISABLE_MASK,
MALI_C55_AEXP_HIST_DISABLE);
mali_c55_ctx_update_bits(mali_c55, MALI_C55_REG_METERING_CONFIG,
MALI_C55_AEXP_IHIST_DISABLE_MASK,
MALI_C55_AEXP_IHIST_DISABLE);
}
void mali_c55_unregister_params(struct mali_c55 *mali_c55)
{
struct mali_c55_params *params = &mali_c55->params;
if (!video_is_registered(&params->vdev))
return;
vb2_video_unregister_device(&params->vdev);
media_entity_cleanup(&params->vdev.entity);
mutex_destroy(&params->lock);
}
int mali_c55_register_params(struct mali_c55 *mali_c55)
{
struct mali_c55_params *params = &mali_c55->params;
struct video_device *vdev = &params->vdev;
struct vb2_queue *vb2q = &params->queue;
int ret;
mutex_init(&params->lock);
INIT_LIST_HEAD(&params->buffers.queue);
spin_lock_init(&params->buffers.lock);
params->pad.flags = MEDIA_PAD_FL_SOURCE;
ret = media_entity_pads_init(&params->vdev.entity, 1, &params->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 = &params->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 = &params->lock;
vdev->v4l2_dev = &mali_c55->v4l2_dev;
vdev->queue = &params->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(&params->vdev.entity);
err_destroy_mutex:
mutex_destroy(&params->lock);
return ret;
}
@@ -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_GAIN BIT(5)
#define MALI_C55_REG_BYPASS_3_IRIDIX 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 */
File diff suppressed because it is too large Load Diff
@@ -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;
}
@@ -0,0 +1,438 @@
// 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;
sd->dev = mali_c55->dev;
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);
}