From: Jacopo Mondi Date: Tue, 17 Jun 2025 07:23:28 +0000 (+0200) Subject: media: vsp1: Add VSPX support X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d06c1a9f348d22478c6bc5684f9c990e15ada1e9;p=thirdparty%2Flinux.git media: vsp1: Add VSPX support Add support for VSPX, a specialized version of the VSP2 that transfers data to the ISP. The VSPX is composed of two RPF units to read data from external memory and an IIF instance that performs transfer towards the ISP. The VSPX is supported through a newly introduced vsp1_vspx.c file that exposes two interfaces: vsp1_vspx interface, declared in vsp1_vspx.h for the vsp1 core to initialize and cleanup the VSPX, and a vsp1_isp interface, declared in include/media/vsp1.h for the ISP driver to control the VSPX operations. Tested-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Link: https://lore.kernel.org/r/20250617-b4-vspx-v13-1-9f4054c1c9af@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- diff --git a/drivers/media/platform/renesas/vsp1/Makefile b/drivers/media/platform/renesas/vsp1/Makefile index de8c802e1d1a1..2057c8f7be477 100644 --- a/drivers/media/platform/renesas/vsp1/Makefile +++ b/drivers/media/platform/renesas/vsp1/Makefile @@ -6,5 +6,6 @@ vsp1-y += vsp1_clu.o vsp1_hsit.o vsp1_lut.o vsp1-y += vsp1_brx.o vsp1_sru.o vsp1_uds.o vsp1-y += vsp1_hgo.o vsp1_hgt.o vsp1_histo.o vsp1-y += vsp1_iif.o vsp1_lif.o vsp1_uif.o +vsp1-y += vsp1_vspx.o obj-$(CONFIG_VIDEO_RENESAS_VSP1) += vsp1.o diff --git a/drivers/media/platform/renesas/vsp1/vsp1.h b/drivers/media/platform/renesas/vsp1/vsp1.h index f97a1a31bfab3..94de2e85792e6 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1.h +++ b/drivers/media/platform/renesas/vsp1/vsp1.h @@ -111,6 +111,7 @@ struct vsp1_device { struct media_entity_operations media_ops; struct vsp1_drm *drm; + struct vsp1_vspx *vspx; }; int vsp1_device_get(struct vsp1_device *vsp1); diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drv.c b/drivers/media/platform/renesas/vsp1/vsp1_drv.c index 49ac3104291a6..b8d06e88c4757 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_drv.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_drv.c @@ -39,6 +39,7 @@ #include "vsp1_uds.h" #include "vsp1_uif.h" #include "vsp1_video.h" +#include "vsp1_vspx.h" /* ----------------------------------------------------------------------------- * Interrupt Handling @@ -491,7 +492,10 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) ret = media_device_register(mdev); } else { - ret = vsp1_drm_init(vsp1); + if (vsp1->info->version == VI6_IP_VERSION_MODEL_VSPX_GEN4) + ret = vsp1_vspx_init(vsp1); + else + ret = vsp1_drm_init(vsp1); } done: @@ -858,6 +862,13 @@ static const struct vsp1_device_info vsp1_device_infos[] = { .uif_count = 2, .wpf_count = 1, .num_bru_inputs = 5, + }, { + .version = VI6_IP_VERSION_MODEL_VSPX_GEN4, + .model = "VSP2-X", + .gen = 4, + .features = VSP1_HAS_IIF, + .rpf_count = 2, + .wpf_count = 1, }, }; diff --git a/drivers/media/platform/renesas/vsp1/vsp1_regs.h b/drivers/media/platform/renesas/vsp1/vsp1_regs.h index 86e47c2d991fb..10cfbcd1b6e0b 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_regs.h +++ b/drivers/media/platform/renesas/vsp1/vsp1_regs.h @@ -799,6 +799,7 @@ #define VI6_IP_VERSION_MODEL_VSPDL_GEN3 (0x19 << 8) #define VI6_IP_VERSION_MODEL_VSPBS_GEN3 (0x1a << 8) #define VI6_IP_VERSION_MODEL_VSPD_GEN4 (0x1c << 8) +#define VI6_IP_VERSION_MODEL_VSPX_GEN4 (0x1d << 8) /* RZ/G2L SoCs have no version register, So use 0x80 as the model version */ #define VI6_IP_VERSION_MODEL_VSPD_RZG2L (0x80 << 8) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c new file mode 100644 index 0000000000000..6551d63ba387a --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c @@ -0,0 +1,632 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * vsp1_vspx.c -- R-Car Gen 4 VSPX + * + * Copyright (C) 2025 Ideas On Board Oy + * Copyright (C) 2025 Renesas Electronics Corporation + */ + +#include "vsp1_vspx.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "vsp1_dl.h" +#include "vsp1_iif.h" +#include "vsp1_pipe.h" +#include "vsp1_rwpf.h" + +/* + * struct vsp1_vspx_pipeline - VSPX pipeline + * @pipe: the VSP1 pipeline + * @partition: the pre-calculated partition used by the pipeline + * @mutex: protects the streaming start/stop sequences + * @lock: protect access to the enabled flag + * @enabled: the enable flag + * @vspx_frame_end: frame end callback + * @frame_end_data: data for the frame end callback + */ +struct vsp1_vspx_pipeline { + struct vsp1_pipeline pipe; + struct vsp1_partition partition; + + /* + * Protects the streaming start/stop sequences. + * + * The start/stop sequences cannot be locked with the 'lock' spinlock + * as they acquire mutexes when handling the pm runtime and the vsp1 + * pipe start/stop operations. Provide a dedicated mutex for this + * reason. + */ + struct mutex mutex; + + /* + * Protects the enable flag. + * + * The enabled flag is contended between the start/stop streaming + * routines and the job_run one, which cannot take a mutex as it is + * called from the ISP irq context. + */ + spinlock_t lock; + bool enabled; + + void (*vspx_frame_end)(void *frame_end_data); + void *frame_end_data; +}; + +static inline struct vsp1_vspx_pipeline * +to_vsp1_vspx_pipeline(struct vsp1_pipeline *pipe) +{ + return container_of(pipe, struct vsp1_vspx_pipeline, pipe); +} + +/* + * struct vsp1_vspx - VSPX device + * @vsp1: the VSP1 device + * @pipe: the VSPX pipeline + */ +struct vsp1_vspx { + struct vsp1_device *vsp1; + struct vsp1_vspx_pipeline pipe; +}; + +/* Apply the given width, height and fourcc to the RWPF's subdevice */ +static int vsp1_vspx_rwpf_set_subdev_fmt(struct vsp1_device *vsp1, + struct vsp1_rwpf *rwpf, + u32 isp_fourcc, + unsigned int width, + unsigned int height) +{ + struct vsp1_entity *ent = &rwpf->entity; + struct v4l2_subdev_format format = {}; + u32 vspx_fourcc; + + switch (isp_fourcc) { + case V4L2_PIX_FMT_GREY: + /* 8 bit RAW Bayer image. */ + vspx_fourcc = V4L2_PIX_FMT_RGB332; + break; + case V4L2_PIX_FMT_Y10: + case V4L2_PIX_FMT_Y12: + case V4L2_PIX_FMT_Y16: + /* 10, 12 and 16 bit RAW Bayer image. */ + vspx_fourcc = V4L2_PIX_FMT_RGB565; + break; + case V4L2_META_FMT_GENERIC_8: + /* ConfigDMA parameters buffer. */ + vspx_fourcc = V4L2_PIX_FMT_XBGR32; + break; + default: + return -EINVAL; + } + + rwpf->fmtinfo = vsp1_get_format_info(vsp1, vspx_fourcc); + + format.which = V4L2_SUBDEV_FORMAT_ACTIVE; + format.pad = RWPF_PAD_SINK; + format.format.width = width; + format.format.height = height; + format.format.field = V4L2_FIELD_NONE; + format.format.code = rwpf->fmtinfo->mbus; + + return v4l2_subdev_call(&ent->subdev, pad, set_fmt, NULL, &format); +} + +/* Configure the RPF->IIF->WPF pipeline for ConfigDMA or RAW image transfer. */ +static int vsp1_vspx_pipeline_configure(struct vsp1_device *vsp1, + dma_addr_t addr, u32 isp_fourcc, + unsigned int width, unsigned int height, + unsigned int stride, + unsigned int iif_sink_pad, + struct vsp1_dl_list *dl, + struct vsp1_dl_body *dlb) +{ + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + struct vsp1_pipeline *pipe = &vspx_pipe->pipe; + struct vsp1_rwpf *rpf0 = pipe->inputs[0]; + int ret; + + ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, rpf0, isp_fourcc, width, + height); + if (ret) + return ret; + + ret = vsp1_vspx_rwpf_set_subdev_fmt(vsp1, pipe->output, isp_fourcc, + width, height); + if (ret) + return ret; + + vsp1_pipeline_calculate_partition(pipe, &pipe->part_table[0], width, 0); + rpf0->format.plane_fmt[0].bytesperline = stride; + rpf0->format.num_planes = 1; + rpf0->mem.addr[0] = addr; + + /* + * Connect RPF0 to the IIF sink pad corresponding to the config or image + * path. + */ + rpf0->entity.sink_pad = iif_sink_pad; + + vsp1_entity_route_setup(&rpf0->entity, pipe, dlb); + vsp1_entity_configure_stream(&rpf0->entity, rpf0->entity.state, pipe, + dl, dlb); + vsp1_entity_configure_partition(&rpf0->entity, pipe, + &pipe->part_table[0], dl, dlb); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Interrupt handling + */ + +static void vsp1_vspx_pipeline_frame_end(struct vsp1_pipeline *pipe, + unsigned int completion) +{ + struct vsp1_vspx_pipeline *vspx_pipe = to_vsp1_vspx_pipeline(pipe); + + scoped_guard(spinlock_irqsave, &pipe->irqlock) { + /* + * Operating the vsp1_pipe in singleshot mode requires to + * manually set the pipeline state to stopped when a transfer + * is completed. + */ + pipe->state = VSP1_PIPELINE_STOPPED; + } + + if (vspx_pipe->vspx_frame_end) + vspx_pipe->vspx_frame_end(vspx_pipe->frame_end_data); +} + +/* ----------------------------------------------------------------------------- + * ISP Driver API (include/media/vsp1.h) + */ + +/** + * vsp1_isp_init() - Initialize the VSPX + * @dev: The VSP1 struct device + * + * Return: %0 on success or a negative error code on failure + */ +int vsp1_isp_init(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + if (!vsp1) + return -EPROBE_DEFER; + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_isp_init); + +/** + * vsp1_isp_get_bus_master - Get VSPX bus master + * @dev: The VSP1 struct device + * + * The VSPX accesses memory through an FCPX instance. When allocating memory + * buffers that will have to be accessed by the VSPX the 'struct device' of + * the FCPX should be used. Use this function to get a reference to it. + * + * Return: a pointer to the bus master's device + */ +struct device *vsp1_isp_get_bus_master(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + + if (!vsp1) + return ERR_PTR(-ENODEV); + + return vsp1->bus_master; +} +EXPORT_SYMBOL_GPL(vsp1_isp_get_bus_master); + +/** + * vsp1_isp_alloc_buffer - Allocate a buffer in the VSPX address space + * @dev: The VSP1 struct device + * @size: The size of the buffer to be allocated by the VSPX + * @buffer_desc: The buffer descriptor. Will be filled with the buffer + * CPU-mapped address, the bus address and the size of the + * allocated buffer + * + * Allocate a buffer that will be later accessed by the VSPX. Buffers allocated + * using vsp1_isp_alloc_buffer() shall be released with a call to + * vsp1_isp_free_buffer(). This function is used by the ISP driver to allocate + * memory for the ConfigDMA parameters buffer. + * + * Return: %0 on success or a negative error code on failure + */ +int vsp1_isp_alloc_buffer(struct device *dev, size_t size, + struct vsp1_isp_buffer_desc *buffer_desc) +{ + struct device *bus_master = vsp1_isp_get_bus_master(dev); + + if (IS_ERR_OR_NULL(bus_master)) + return -ENODEV; + + buffer_desc->cpu_addr = dma_alloc_coherent(bus_master, size, + &buffer_desc->dma_addr, + GFP_KERNEL); + if (!buffer_desc->cpu_addr) + return -ENOMEM; + + buffer_desc->size = size; + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_isp_alloc_buffer); + +/** + * vsp1_isp_free_buffer - Release a buffer allocated by vsp1_isp_alloc_buffer() + * @dev: The VSP1 struct device + * @buffer_desc: The descriptor of the buffer to release as returned by + * vsp1_isp_alloc_buffer() + * + * Release memory in the VSPX address space allocated by + * vsp1_isp_alloc_buffer(). + */ +void vsp1_isp_free_buffer(struct device *dev, + struct vsp1_isp_buffer_desc *buffer_desc) +{ + struct device *bus_master = vsp1_isp_get_bus_master(dev); + + if (IS_ERR_OR_NULL(bus_master)) + return; + + dma_free_coherent(bus_master, buffer_desc->size, buffer_desc->cpu_addr, + buffer_desc->dma_addr); +} + +/** + * vsp1_isp_start_streaming - Start processing VSPX jobs + * @dev: The VSP1 struct device + * @frame_end: The frame end callback description + * + * Start the VSPX and prepare for accepting buffer transfer job requests. + * The caller is responsible for tracking the started state of the VSPX. + * Attempting to start an already started VSPX instance is an error. + * + * Return: %0 on success or a negative error code on failure + */ +int vsp1_isp_start_streaming(struct device *dev, + struct vsp1_vspx_frame_end *frame_end) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + struct vsp1_pipeline *pipe = &vspx_pipe->pipe; + u32 value; + int ret; + + if (!frame_end) + return -EINVAL; + + guard(mutex)(&vspx_pipe->mutex); + + scoped_guard(spinlock_irq, &vspx_pipe->lock) { + if (vspx_pipe->enabled) + return -EBUSY; + } + + vspx_pipe->vspx_frame_end = frame_end->vspx_frame_end; + vspx_pipe->frame_end_data = frame_end->frame_end_data; + + /* Enable the VSP1 and prepare for streaming. */ + vsp1_pipeline_dump(pipe, "VSPX job"); + + ret = vsp1_device_get(vsp1); + if (ret < 0) + return ret; + + /* + * Make sure VSPX is not active. This should never happen in normal + * usage + */ + value = vsp1_read(vsp1, VI6_CMD(0)); + if (value & VI6_CMD_STRCMD) { + dev_err(vsp1->dev, + "%s: Starting of WPF0 already reserved\n", __func__); + ret = -EBUSY; + goto error_put; + } + + value = vsp1_read(vsp1, VI6_STATUS); + if (value & VI6_STATUS_SYS_ACT(0)) { + dev_err(vsp1->dev, + "%s: WPF0 has not entered idle state\n", __func__); + ret = -EBUSY; + goto error_put; + } + + scoped_guard(spinlock_irq, &vspx_pipe->lock) { + vspx_pipe->enabled = true; + } + + return 0; + +error_put: + vsp1_device_put(vsp1); + return ret; +} +EXPORT_SYMBOL_GPL(vsp1_isp_start_streaming); + +/** + * vsp1_isp_stop_streaming - Stop the VSPX + * @dev: The VSP1 struct device + * + * Stop the VSPX operation by stopping the vsp1 pipeline and waiting for the + * last frame in transfer, if any, to complete. + * + * The caller is responsible for tracking the stopped state of the VSPX. + * Attempting to stop an already stopped VSPX instance is a nop. + */ +void vsp1_isp_stop_streaming(struct device *dev) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + struct vsp1_pipeline *pipe = &vspx_pipe->pipe; + + guard(mutex)(&vspx_pipe->mutex); + + scoped_guard(spinlock_irq, &vspx_pipe->lock) { + if (!vspx_pipe->enabled) + return; + + vspx_pipe->enabled = false; + } + + WARN_ON_ONCE(vsp1_pipeline_stop(pipe)); + + vspx_pipe->vspx_frame_end = NULL; + vsp1_dlm_reset(pipe->output->dlm); + vsp1_device_put(vsp1); +} +EXPORT_SYMBOL_GPL(vsp1_isp_stop_streaming); + +/** + * vsp1_isp_job_prepare - Prepare a new buffer transfer job + * @dev: The VSP1 struct device + * @job: The job description + * + * Prepare a new buffer transfer job by populating a display list that will be + * later executed by a call to vsp1_isp_job_run(). All pending jobs must be + * released after stopping the streaming operations with a call to + * vsp1_isp_job_release(). + * + * In order for the VSPX to accept new jobs to prepare the VSPX must have been + * started. + * + * Return: %0 on success or a negative error code on failure + */ +int vsp1_isp_job_prepare(struct device *dev, struct vsp1_isp_job_desc *job) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + struct vsp1_pipeline *pipe = &vspx_pipe->pipe; + const struct v4l2_pix_format_mplane *pix_mp; + struct vsp1_dl_list *second_dl = NULL; + struct vsp1_dl_body *dlb; + struct vsp1_dl_list *dl; + int ret; + + /* + * Transfer the buffers described in the job: an optional ConfigDMA + * parameters buffer and a RAW image. + */ + + job->dl = vsp1_dl_list_get(pipe->output->dlm); + if (!job->dl) + return -ENOMEM; + + dl = job->dl; + dlb = vsp1_dl_list_get_body0(dl); + + /* Configure IIF routing and enable IIF function. */ + vsp1_entity_route_setup(pipe->iif, pipe, dlb); + vsp1_entity_configure_stream(pipe->iif, pipe->iif->state, pipe, + dl, dlb); + + /* Configure WPF0 to enable RPF0 as source. */ + vsp1_entity_route_setup(&pipe->output->entity, pipe, dlb); + vsp1_entity_configure_stream(&pipe->output->entity, + pipe->output->entity.state, pipe, + dl, dlb); + + if (job->config.pairs) { + /* + * Writing less than 17 pairs corrupts the output images ( < 16 + * pairs) or freezes the VSPX operations (= 16 pairs). Only + * allow more than 16 pairs to be written. + */ + if (job->config.pairs <= 16) { + ret = -EINVAL; + goto error_put_dl; + } + + /* + * Configure RPF0 for ConfigDMA data. Transfer the number of + * configuration pairs plus 2 words for the header. + */ + ret = vsp1_vspx_pipeline_configure(vsp1, job->config.mem, + V4L2_META_FMT_GENERIC_8, + job->config.pairs * 2 + 2, 1, + job->config.pairs * 2 + 2, + VSPX_IIF_SINK_PAD_CONFIG, + dl, dlb); + if (ret) + goto error_put_dl; + + second_dl = vsp1_dl_list_get(pipe->output->dlm); + if (!second_dl) { + ret = -ENOMEM; + goto error_put_dl; + } + + dl = second_dl; + dlb = vsp1_dl_list_get_body0(dl); + } + + /* Configure RPF0 for RAW image transfer. */ + pix_mp = &job->img.fmt; + ret = vsp1_vspx_pipeline_configure(vsp1, job->img.mem, + pix_mp->pixelformat, + pix_mp->width, pix_mp->height, + pix_mp->plane_fmt[0].bytesperline, + VSPX_IIF_SINK_PAD_IMG, dl, dlb); + if (ret) + goto error_put_dl; + + if (second_dl) + vsp1_dl_list_add_chain(job->dl, second_dl); + + return 0; + +error_put_dl: + if (second_dl) + vsp1_dl_list_put(second_dl); + vsp1_dl_list_put(job->dl); + job->dl = NULL; + return ret; +} +EXPORT_SYMBOL_GPL(vsp1_isp_job_prepare); + +/** + * vsp1_isp_job_run - Run a buffer transfer job + * @dev: The VSP1 struct device + * @job: The job to be run + * + * Run the display list contained in the job description provided by the caller. + * The job must have been prepared with a call to vsp1_isp_job_prepare() and + * the job's display list shall be valid. + * + * Jobs can be run only on VSPX instances which have been started. Requests + * to run a job after the VSPX has been stopped return -EINVAL and the job + * resources shall be released by the caller with vsp1_isp_job_release(). + * When a job is run successfully all the resources acquired by + * vsp1_isp_job_prepare() are released by this function and no further action + * is required to the caller. + * + * Return: %0 on success or a negative error code on failure + */ +int vsp1_isp_job_run(struct device *dev, struct vsp1_isp_job_desc *job) +{ + struct vsp1_device *vsp1 = dev_get_drvdata(dev); + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + struct vsp1_pipeline *pipe = &vspx_pipe->pipe; + u32 value; + + /* Make sure VSPX is not busy processing a frame. */ + value = vsp1_read(vsp1, VI6_CMD(0)); + if (value) { + dev_err(vsp1->dev, + "%s: Starting of WPF0 already reserved\n", __func__); + return -EBUSY; + } + + scoped_guard(spinlock_irqsave, &vspx_pipe->lock) { + /* + * If a new job is scheduled when the VSPX is stopped, do not + * run it. + */ + if (!vspx_pipe->enabled) + return -EINVAL; + + vsp1_dl_list_commit(job->dl, 0); + + /* + * The display list is now under control of the display list + * manager and will be released automatically when the job + * completes. + */ + job->dl = NULL; + } + + scoped_guard(spinlock_irqsave, &pipe->irqlock) { + vsp1_pipeline_run(pipe); + } + + return 0; +} +EXPORT_SYMBOL_GPL(vsp1_isp_job_run); + +/** + * vsp1_isp_job_release - Release a non processed transfer job + * @dev: The VSP1 struct device + * @job: The job to release + * + * Release a job prepared by a call to vsp1_isp_job_prepare() and not yet + * run. All pending jobs shall be released after streaming has been stopped. + */ +void vsp1_isp_job_release(struct device *dev, + struct vsp1_isp_job_desc *job) +{ + vsp1_dl_list_put(job->dl); +} +EXPORT_SYMBOL_GPL(vsp1_isp_job_release); + +/* ----------------------------------------------------------------------------- + * Initialization and cleanup + */ + +int vsp1_vspx_init(struct vsp1_device *vsp1) +{ + struct vsp1_vspx_pipeline *vspx_pipe; + struct vsp1_pipeline *pipe; + + vsp1->vspx = devm_kzalloc(vsp1->dev, sizeof(*vsp1->vspx), GFP_KERNEL); + if (!vsp1->vspx) + return -ENOMEM; + + vsp1->vspx->vsp1 = vsp1; + + vspx_pipe = &vsp1->vspx->pipe; + vspx_pipe->enabled = false; + + pipe = &vspx_pipe->pipe; + + vsp1_pipeline_init(pipe); + + pipe->partitions = 1; + pipe->part_table = &vspx_pipe->partition; + pipe->interlaced = false; + pipe->frame_end = vsp1_vspx_pipeline_frame_end; + + mutex_init(&vspx_pipe->mutex); + spin_lock_init(&vspx_pipe->lock); + + /* + * Initialize RPF0 as input for VSPX and use it unconditionally for + * now. + */ + pipe->inputs[0] = vsp1->rpf[0]; + pipe->inputs[0]->entity.pipe = pipe; + pipe->inputs[0]->entity.sink = &vsp1->iif->entity; + list_add_tail(&pipe->inputs[0]->entity.list_pipe, &pipe->entities); + + pipe->iif = &vsp1->iif->entity; + pipe->iif->pipe = pipe; + pipe->iif->sink = &vsp1->wpf[0]->entity; + pipe->iif->sink_pad = RWPF_PAD_SINK; + list_add_tail(&pipe->iif->list_pipe, &pipe->entities); + + pipe->output = vsp1->wpf[0]; + pipe->output->entity.pipe = pipe; + list_add_tail(&pipe->output->entity.list_pipe, &pipe->entities); + + return 0; +} + +void vsp1_vspx_cleanup(struct vsp1_device *vsp1) +{ + struct vsp1_vspx_pipeline *vspx_pipe = &vsp1->vspx->pipe; + + mutex_destroy(&vspx_pipe->mutex); +} diff --git a/drivers/media/platform/renesas/vsp1/vsp1_vspx.h b/drivers/media/platform/renesas/vsp1/vsp1_vspx.h new file mode 100644 index 0000000000000..f871bf9e7dece --- /dev/null +++ b/drivers/media/platform/renesas/vsp1/vsp1_vspx.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * vsp1_vspx.h -- R-Car Gen 4 VSPX + * + * Copyright (C) 2025 Ideas On Board Oy + * Copyright (C) 2025 Renesas Electronics Corporation + */ +#ifndef __VSP1_VSPX_H__ +#define __VSP1_VSPX_H__ + +#include "vsp1.h" + +int vsp1_vspx_init(struct vsp1_device *vsp1); +void vsp1_vspx_cleanup(struct vsp1_device *vsp1); + +#endif /* __VSP1_VSPX_H__ */ diff --git a/include/media/vsp1.h b/include/media/vsp1.h index 4ea6352fd63fe..d9b91ff027618 100644 --- a/include/media/vsp1.h +++ b/include/media/vsp1.h @@ -14,6 +14,11 @@ #include struct device; +struct vsp1_dl_list; + +/* ----------------------------------------------------------------------------- + * VSP1 DU interface + */ int vsp1_du_init(struct device *dev); @@ -121,4 +126,88 @@ void vsp1_du_atomic_flush(struct device *dev, unsigned int pipe_index, int vsp1_du_map_sg(struct device *dev, struct sg_table *sgt); void vsp1_du_unmap_sg(struct device *dev, struct sg_table *sgt); +/* ----------------------------------------------------------------------------- + * VSP1 ISP interface + */ + +/** + * struct vsp1_isp_buffer_desc - Describe a buffer allocated by VSPX + * @size: Byte size of the buffer allocated by VSPX + * @cpu_addr: CPU-mapped address of a buffer allocated by VSPX + * @dma_addr: bus address of a buffer allocated by VSPX + */ +struct vsp1_isp_buffer_desc { + size_t size; + void *cpu_addr; + dma_addr_t dma_addr; +}; + +/** + * struct vsp1_isp_job_desc - Describe a VSPX buffer transfer request + * @config: ConfigDMA buffer descriptor + * @config.pairs: number of reg-value pairs in the ConfigDMA buffer + * @config.mem: bus address of the ConfigDMA buffer + * @img: RAW image buffer descriptor + * @img.fmt: RAW image format + * @img.mem: bus address of the RAW image buffer + * @dl: pointer to the display list populated by the VSPX driver in the + * vsp1_isp_job_prepare() function + * + * Describe a transfer request for the VSPX to perform on behalf of the ISP. + * The job descriptor contains an optional ConfigDMA buffer and one RAW image + * buffer. Set config.pairs to 0 if no ConfigDMA buffer should be transferred. + * The minimum number of config.pairs that can be written using ConfigDMA is 17. + * A number of pairs < 16 corrupts the output image. A number of pairs == 16 + * freezes the VSPX operation. If the ISP driver has to write less than 17 pairs + * it shall pad the buffer with writes directed to registers that have no effect + * or avoid using ConfigDMA at all for such small write sequences. + * + * The ISP driver shall pass an instance this type to the vsp1_isp_job_prepare() + * function that will populate the display list pointer @dl using the @config + * and @img descriptors. When the job has to be run on the VSPX, the descriptor + * shall be passed to vsp1_isp_job_run() which consumes the display list. + * + * Job descriptors not yet run shall be released with a call to + * vsp1_isp_job_release() when stopping the streaming in order to properly + * release the resources acquired by vsp1_isp_job_prepare(). + */ +struct vsp1_isp_job_desc { + struct { + unsigned int pairs; + dma_addr_t mem; + } config; + struct { + struct v4l2_pix_format_mplane fmt; + dma_addr_t mem; + } img; + struct vsp1_dl_list *dl; +}; + +/** + * struct vsp1_vspx_frame_end - VSPX frame end callback data + * @vspx_frame_end: Frame end callback. Called after a transfer job has been + * completed. If the job includes both a ConfigDMA and a + * RAW image, the callback is called after both have been + * transferred + * @frame_end_data: Frame end callback data, passed to vspx_frame_end + */ +struct vsp1_vspx_frame_end { + void (*vspx_frame_end)(void *data); + void *frame_end_data; +}; + +int vsp1_isp_init(struct device *dev); +struct device *vsp1_isp_get_bus_master(struct device *dev); +int vsp1_isp_alloc_buffer(struct device *dev, size_t size, + struct vsp1_isp_buffer_desc *buffer_desc); +void vsp1_isp_free_buffer(struct device *dev, + struct vsp1_isp_buffer_desc *buffer_desc); +int vsp1_isp_start_streaming(struct device *dev, + struct vsp1_vspx_frame_end *frame_end); +void vsp1_isp_stop_streaming(struct device *dev); +int vsp1_isp_job_prepare(struct device *dev, + struct vsp1_isp_job_desc *job); +int vsp1_isp_job_run(struct device *dev, struct vsp1_isp_job_desc *job); +void vsp1_isp_job_release(struct device *dev, struct vsp1_isp_job_desc *job); + #endif /* __MEDIA_VSP1_H__ */