diff --git a/drivers/media/platform/renesas/rcar-isp/Kconfig b/drivers/media/platform/renesas/rcar-isp/Kconfig index 242f6a23851f..bacc15c250fe 100644 --- a/drivers/media/platform/renesas/rcar-isp/Kconfig +++ b/drivers/media/platform/renesas/rcar-isp/Kconfig @@ -5,10 +5,12 @@ config VIDEO_RCAR_ISP depends on V4L_PLATFORM_DRIVERS depends on VIDEO_DEV && OF depends on ARCH_RENESAS || COMPILE_TEST + depends on VIDEO_RENESAS_VSP1 select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select RESET_CONTROLLER select V4L2_FWNODE + select VIDEO_DCT_RPPX1 help Support for Renesas R-Car Image Signal Processor (ISP). Enable this to support the Renesas R-Car Image Signal diff --git a/drivers/media/platform/renesas/rcar-isp/Makefile b/drivers/media/platform/renesas/rcar-isp/Makefile index b542118c831e..c0c80303682c 100644 --- a/drivers/media/platform/renesas/rcar-isp/Makefile +++ b/drivers/media/platform/renesas/rcar-isp/Makefile @@ -1,4 +1,4 @@ # SPDX-License-Identifier: GPL-2.0 -rcar-isp-objs = csisp.o +rcar-isp-objs = csisp.o core.o core-io.o obj-$(CONFIG_VIDEO_RCAR_ISP) += rcar-isp.o diff --git a/drivers/media/platform/renesas/rcar-isp/core-io.c b/drivers/media/platform/renesas/rcar-isp/core-io.c new file mode 100644 index 000000000000..820af506f896 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-isp/core-io.c @@ -0,0 +1,997 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Renesas Electronics Corp. + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2026 Ragnatech AB + */ + +#include +#include +#include +#include +#include + +#include + +#include "risp-core.h" + +#define risp_io_err(d, fmt, arg...) dev_err((d)->core->dev, fmt, ##arg) + +static struct risp_buffer *risp_io_vb2buf(struct vb2_v4l2_buffer *vb) +{ + return container_of(vb, struct risp_buffer, vb); +} + +static int risp_io_open(struct file *file) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + int ret; + + ret = mutex_lock_interruptible(&io->lock); + if (ret) + return ret; + + file->private_data = io; + + ret = v4l2_fh_open(file); + if (ret) + goto err_unlock; + + ret = v4l2_pipeline_pm_get(&io->vdev.entity); + if (ret < 0) + goto err_open; + + mutex_unlock(&io->lock); + + return 0; +err_open: + v4l2_fh_release(file); +err_unlock: + mutex_unlock(&io->lock); + + return ret; +} + +static int risp_io_release(struct file *file) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + int ret; + + mutex_lock(&io->lock); + + ret = _vb2_fop_release(file, NULL); + + v4l2_pipeline_pm_put(&io->vdev.entity); + + mutex_unlock(&io->lock); + + return ret; +} + +static const struct v4l2_file_operations risp_io_fops = { + .owner = THIS_MODULE, + .unlocked_ioctl = video_ioctl2, + .open = risp_io_open, + .release = risp_io_release, + .poll = vb2_fop_poll, + .mmap = vb2_fop_mmap, + .read = vb2_fop_read, +}; + +/* ----------------------------------------------------------------------------- + * Common queue + */ + +static int risp_io_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) + +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vq); + + if (V4L2_TYPE_IS_MULTIPLANAR(vq->type)) { + const struct v4l2_pix_format_mplane *pix = &io->format.fmt.pix_mp; + + if (*nplanes) { + if (*nplanes > pix->num_planes) + return -EINVAL; + + for (unsigned int i = 0; i < pix->num_planes; i++) + if (sizes[i] < pix->plane_fmt[i].sizeimage) + return -EINVAL; + + return 0; + } + + *nplanes = pix->num_planes; + for (unsigned int i = 0; i < pix->num_planes; i++) + sizes[i] = pix->plane_fmt[i].sizeimage; + } else { + if (*nplanes) { + if (sizes[0] < io->format.fmt.meta.buffersize) + return -EINVAL; + + return 0; + } + + *nplanes = 1; + sizes[0] = io->format.fmt.meta.buffersize; + } + + /* Initialize buffer queue */ + INIT_LIST_HEAD(&io->buffers); + + return 0; +}; + +static int risp_io_buffer_prepare_set(struct rcar_isp_core_io *io, + struct vb2_buffer *vb, unsigned int plane, + unsigned long size) +{ + if (vb2_plane_size(vb, plane) < size) { + risp_io_err(io, "Buffer too small (%lu < %lu)\n", + vb2_plane_size(vb, plane), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, plane, size); + + return 0; +} + +static int risp_io_buffer_prepare(struct vb2_buffer *vb) +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vb->vb2_queue); + + if (V4L2_TYPE_IS_MULTIPLANAR(vb->vb2_queue->type)) { + const struct v4l2_pix_format_mplane *pix = &io->format.fmt.pix_mp; + int ret = 0; + + for (unsigned int i = 0; i < pix->num_planes; i++) { + ret = risp_io_buffer_prepare_set(io, vb, i, + pix->plane_fmt[i].sizeimage); + if (ret) + break; + } + + return ret; + } + + return risp_io_buffer_prepare_set(io, vb, 0, + io->format.fmt.meta.buffersize); +} + +static void risp_io_buffer_queue(struct vb2_buffer *vb) +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct risp_buffer *buf = risp_io_vb2buf(vbuf); + + guard(mutex)(&io->core->io_lock); + + list_add_tail(&buf->list, &io->buffers); + + if (risp_core_job_prepare(io->core)) + risp_io_err(io, "Failed to prepare job\n"); +} + +static void risp_io_return_buffers(struct rcar_isp_core_io *io, + enum vb2_buffer_state state) +{ + struct risp_buffer *buf, *node; + + lockdep_assert_held(&io->core->io_lock); + + list_for_each_entry_safe(buf, node, &io->buffers, list) { + vb2_buffer_done(&buf->vb.vb2_buf, state); + list_del(&buf->list); + } +} + +static int risp_io_start_streaming(struct vb2_queue *vq, unsigned int count) +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vq); + int ret; + + scoped_guard(mutex, &io->core->io_lock) { + if (io->core->io[RISP_CORE_INPUT1].format.fmt.pix_mp.width != + io->core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp.width || + io->core->io[RISP_CORE_INPUT1].format.fmt.pix_mp.height != + io->core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp.height) { + risp_io_return_buffers(io, VB2_BUF_STATE_QUEUED); + return -EPIPE; + } + + io->streaming = true; + } + + ret = risp_core_start_streaming(io->core); + if (ret) { + guard(mutex)(&io->core->io_lock); + + risp_io_return_buffers(io, VB2_BUF_STATE_QUEUED); + return ret; + } + + return 0; +} + +static void risp_io_stop_streaming(struct vb2_queue *vq) +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vq); + + scoped_guard(mutex, &io->core->io_lock) { + io->streaming = false; + risp_core_stop_streaming(io->core); + risp_io_return_buffers(io, VB2_BUF_STATE_ERROR); + } + + /* + * Wait for buffers part of the jobs not yet processed. Note that this + * might complete buffers out of order. + */ + vb2_wait_for_all_buffers(&io->queue); +} + +/* ----------------------------------------------------------------------------- + * Common V4L2 IOCTLs + */ + +static int risp_io_querycap(struct file *file, void *priv, + struct v4l2_capability *cap) +{ + struct video_device *vdev = video_devdata(file); + + strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver)); + strscpy(cap->card, vdev->name, sizeof(cap->card)); + + return 0; +} + +/* ----------------------------------------------------------------------------- + * Input Exposure + */ + +static int risp_io_input_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned int sizes[], + struct device *alloc_devs[]) + +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vq); + struct rcar_isp_core *core = io->core; + struct device *bus_master; + int ret; + + ret = risp_io_queue_setup(vq, nbuffers, nplanes, sizes, alloc_devs); + if (ret) + return ret; + + bus_master = vsp1_isp_get_bus_master(core->vspx.dev); + if (IS_ERR_OR_NULL(bus_master)) { + risp_io_err(io, "Missing reference to bus-master device\n"); + return -EINVAL; + } + + /* + * Allocate buffers using the bus_master device associated with the + * VSPX associated to this ISP instance. + */ + alloc_devs[0] = bus_master; + + return 0; +}; + +static const struct vb2_ops risp_io_input_qops = { + .queue_setup = risp_io_input_queue_setup, + .buf_prepare = risp_io_buffer_prepare, + .buf_queue = risp_io_buffer_queue, + .start_streaming = risp_io_start_streaming, + .stop_streaming = risp_io_stop_streaming, +}; + +static const struct v4l2_pix_format_mplane risp_io_input_default_format = { + .width = 1920, + .height = 1080, + .field = V4L2_FIELD_NONE, + .pixelformat = V4L2_PIX_FMT_SGRBG8, + .colorspace = V4L2_COLORSPACE_RAW, + .xfer_func = V4L2_XFER_FUNC_NONE, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .num_planes = 1, + .plane_fmt = { + [0] = { + .sizeimage = 1920 * 1080, + .bytesperline = 1920, + }, + }, +}; + +static const struct risp_io_input_format { + unsigned int fourcc; + unsigned int bpp; +} risp_io_input_formats[] = { + { .fourcc = V4L2_PIX_FMT_SBGGR8, .bpp = 1 }, + { .fourcc = V4L2_PIX_FMT_SGBRG8, .bpp = 1 }, + { .fourcc = V4L2_PIX_FMT_SGRBG8, .bpp = 1 }, + { .fourcc = V4L2_PIX_FMT_SRGGB8, .bpp = 1 }, + { .fourcc = V4L2_PIX_FMT_SBGGR10, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SGBRG10, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SGRBG10, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SRGGB10, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SBGGR12, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SGBRG12, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SGRBG12, .bpp = 2 }, + { .fourcc = V4L2_PIX_FMT_SRGGB12, .bpp = 2 }, +}; + +static void risp_io_input_try_format(struct rcar_isp_core_io *io, + struct v4l2_pix_format_mplane *pix) +{ + unsigned int bpp = 0; + + v4l_bound_align_image(&pix->width, 128, 5120, 2, + &pix->height, 128, 4096, 2, 0); + + for (unsigned int i = 0; i < ARRAY_SIZE(risp_io_input_formats); i++) { + if (risp_io_input_formats[i].fourcc == pix->pixelformat) { + bpp = risp_io_input_formats[i].bpp; + break; + } + } + + if (!bpp) { + pix->pixelformat = risp_io_input_formats[0].fourcc; + bpp = risp_io_input_formats[0].bpp; + } + + pix->field = V4L2_FIELD_NONE; + pix->colorspace = V4L2_COLORSPACE_RAW; + + pix->num_planes = 1; + pix->plane_fmt[0].bytesperline = pix->width * bpp; + pix->plane_fmt[0].sizeimage = pix->plane_fmt[0].bytesperline * pix->height; +} + +static int risp_io_input_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->index >= ARRAY_SIZE(risp_io_input_formats)) + return -EINVAL; + + f->pixelformat = risp_io_input_formats[f->index].fourcc; + + return 0; +} + +static int risp_io_input_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + f->fmt.pix_mp = io->format.fmt.pix_mp; + + return 0; +} + +static int risp_io_input_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (vb2_is_busy(&io->queue)) + return -EBUSY; + + if (f->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + return -EINVAL; + + risp_io_input_try_format(io, &f->fmt.pix_mp); + + io->format.fmt.pix_mp = f->fmt.pix_mp; + + return 0; +} + +static int risp_io_input_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + risp_io_input_try_format(io, &f->fmt.pix_mp); + + return 0; +} + +static int risp_io_input_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + bool found = false; + + if (fsize->index != 0) + return -EINVAL; + + for (unsigned int i = 0; i < ARRAY_SIZE(risp_io_input_formats); i++) { + if (risp_io_input_formats[i].fourcc == fsize->pixel_format) { + found = true; + break; + } + } + + if (!found) + return -EINVAL; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = 128; + fsize->stepwise.max_width = 5120; + fsize->stepwise.step_width = 2; + + fsize->stepwise.min_height = 128; + fsize->stepwise.max_height = 4096; + fsize->stepwise.step_height = 2; + + return 0; +} + +static const struct v4l2_ioctl_ops risp_io_input_ioctl_ops = { + .vidioc_querycap = risp_io_querycap, + + .vidioc_enum_fmt_vid_out = risp_io_input_enum_fmt, + .vidioc_g_fmt_vid_out_mplane = risp_io_input_g_fmt, + .vidioc_s_fmt_vid_out_mplane = risp_io_input_s_fmt, + .vidioc_try_fmt_vid_out_mplane = risp_io_input_try_fmt, + .vidioc_enum_framesizes = risp_io_input_enum_framesizes, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Parameters + * + */ + +static int risp_io_params_buf_init(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct risp_buffer *buf = risp_io_vb2buf(vbuf); + struct rcar_isp_core_io *io = vb2_get_drv_priv(vb->vb2_queue); + struct rcar_isp_core *core = io->core; + size_t size; + int ret; + + memset(&buf->vsp_buffer, 0, sizeof(buf->vsp_buffer)); + + size = RISP_IO_PARAMS_BUF_SIZE; + ret = vsp1_isp_alloc_buffer(core->vspx.dev, size, &buf->vsp_buffer); + if (ret) + return -EINVAL; + + memset(buf->vsp_buffer.cpu_addr, 0, RISP_IO_PARAMS_BUF_SIZE); + + return 0; +} + +static void risp_io_params_buf_cleanup(struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct risp_buffer *buf = risp_io_vb2buf(vbuf); + struct rcar_isp_core_io *io = vb2_get_drv_priv(vb->vb2_queue); + struct rcar_isp_core *core = io->core; + + vsp1_isp_free_buffer(core->vspx.dev, &buf->vsp_buffer); +} + +struct risp_conf_dma_write_desc { + u32 *buf; + u32 base; + unsigned int count; +}; + +static int risp_conf_dma_prepare(void *priv, u32 offset, u32 value) +{ + struct risp_conf_dma_write_desc *desc = priv; + + /* Bounds check, 8 bytes = address (4)+ value (4). */ + if ((desc->count + 1) * 8 > RISP_IO_PARAMS_BUF_SIZE) + return -ENOMEM; + + (*desc->buf++) = desc->base | offset; + (*desc->buf++) = value; + + desc->count++; + + return 0; +} + +static int risp_io_params_buffer_prepare(struct vb2_buffer *vb) +{ + struct rcar_isp_core_io *io = vb2_get_drv_priv(vb->vb2_queue); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct risp_buffer *buf = risp_io_vb2buf(vbuf); + struct risp_conf_dma_write_desc desc; + u32 *cpu_addr; + int ret; + + /* Prepare params. */ + cpu_addr = (u32 *)buf->vsp_buffer.cpu_addr; + + desc.buf = cpu_addr + 2; + desc.base = io->core->rppaddr; + desc.count = 0; + + /* Fill params body. */ + ret = rppx1_params(io->core->rpp, vb, io->format.fmt.meta.buffersize, + risp_conf_dma_prepare, &desc); + if (ret) + return ret; + + /* Fill params header. */ + cpu_addr[0] = desc.count; + cpu_addr[1] = 0x0; + + return 0; +} + +static const struct vb2_ops risp_io_params_qops = { + .queue_setup = risp_io_queue_setup, + .buf_init = risp_io_params_buf_init, + .buf_cleanup = risp_io_params_buf_cleanup, + .buf_prepare = risp_io_params_buffer_prepare, + .buf_queue = risp_io_buffer_queue, + .start_streaming = risp_io_start_streaming, + .stop_streaming = risp_io_stop_streaming, +}; + +static const struct v4l2_meta_format risp_io_params_default_format = { + .dataformat = V4L2_META_FMT_RPPX1_PARAMS, + .buffersize = v4l2_isp_buffer_size(RPPX1_PARAMS_MAX_SIZE), +}; + +static int risp_io_params_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (f->type != V4L2_BUF_TYPE_META_OUTPUT || f->index) + return -EINVAL; + + f->pixelformat = io->format.fmt.meta.dataformat; + + return 0; +} + +static int risp_io_params_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + struct v4l2_meta_format *meta = &f->fmt.meta; + + if (f->type != V4L2_BUF_TYPE_META_OUTPUT) + return -EINVAL; + + *meta = io->format.fmt.meta; + + return 0; +} + +static int risp_io_params_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (vb2_is_busy(&io->queue)) + return -EBUSY; + + return risp_io_params_g_fmt(file, priv, f); +} + +static const struct v4l2_ioctl_ops risp_io_params_ioctl_ops = { + .vidioc_querycap = risp_io_querycap, + + .vidioc_enum_fmt_meta_out = risp_io_params_enum_fmt, + .vidioc_g_fmt_meta_out = risp_io_params_g_fmt, + .vidioc_s_fmt_meta_out = risp_io_params_s_fmt, + .vidioc_try_fmt_meta_out = risp_io_params_g_fmt, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Statistics + */ + +static const struct vb2_ops risp_io_stats_qops = { + .queue_setup = risp_io_queue_setup, + .buf_prepare = risp_io_buffer_prepare, + .buf_queue = risp_io_buffer_queue, + .start_streaming = risp_io_start_streaming, + .stop_streaming = risp_io_stop_streaming, +}; + +static const struct v4l2_meta_format risp_io_stats_default_format = { + .dataformat = V4L2_META_FMT_RPPX1_STATS, + .buffersize = v4l2_isp_buffer_size(RPPX1_STATS_MAX_SIZE), +}; + +static int risp_io_stats_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (f->type != V4L2_BUF_TYPE_META_CAPTURE || f->index) + return -EINVAL; + + f->pixelformat = io->format.fmt.meta.dataformat; + + return 0; +} + +static int risp_io_stats_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + struct v4l2_meta_format *meta = &f->fmt.meta; + + if (f->type != V4L2_BUF_TYPE_META_CAPTURE) + return -EINVAL; + + *meta = io->format.fmt.meta; + + return 0; +} + +static int risp_io_stats_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (vb2_is_busy(&io->queue)) + return -EBUSY; + + return risp_io_stats_g_fmt(file, priv, f); +} + +static const struct v4l2_ioctl_ops risp_io_stats_ioctl_ops = { + .vidioc_querycap = risp_io_querycap, + + .vidioc_enum_fmt_meta_cap = risp_io_stats_enum_fmt, + .vidioc_g_fmt_meta_cap = risp_io_stats_g_fmt, + .vidioc_s_fmt_meta_cap = risp_io_stats_s_fmt, + .vidioc_try_fmt_meta_cap = risp_io_stats_g_fmt, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Video capture + */ + +static const struct vb2_ops risp_io_capture_qops = { + .queue_setup = risp_io_queue_setup, + .buf_prepare = risp_io_buffer_prepare, + .buf_queue = risp_io_buffer_queue, + .start_streaming = risp_io_start_streaming, + .stop_streaming = risp_io_stop_streaming, +}; + +static const struct v4l2_pix_format_mplane risp_io_capture_default_format = { + .width = 1920, + .height = 1080, + .pixelformat = V4L2_PIX_FMT_XBGR32, + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + .num_planes = 1, + .plane_fmt = { + [0] = { + .bytesperline = ALIGN(1920 * 4, 256), + .sizeimage = ALIGN(1920 * 4, 256) * 1080, + }, + }, +}; + +static void risp_io_capture_try_format(struct rcar_isp_core_io *io, + struct v4l2_pix_format_mplane *pix) +{ + v4l_bound_align_image(&pix->width, 128, 5120, 2, + &pix->height, 128, 4096, 2, 0); + + pix->field = V4L2_FIELD_NONE; + pix->colorspace = V4L2_COLORSPACE_SRGB; + pix->ycbcr_enc = V4L2_YCBCR_ENC_601; + pix->xfer_func = V4L2_XFER_FUNC_SRGB; + + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV16M: + pix->quantization = V4L2_QUANTIZATION_LIM_RANGE; + pix->num_planes = 2; + pix->plane_fmt[0].bytesperline = ALIGN(pix->width, 256); + pix->plane_fmt[0].sizeimage = pix->plane_fmt[0].bytesperline * pix->height; + pix->plane_fmt[1].bytesperline = ALIGN(pix->width, 256); + pix->plane_fmt[1].sizeimage = pix->plane_fmt[1].bytesperline * pix->height; + break; + default: + pix->pixelformat = V4L2_PIX_FMT_XBGR32; + pix->quantization = V4L2_QUANTIZATION_FULL_RANGE; + pix->num_planes = 1; + pix->plane_fmt[0].bytesperline = ALIGN(pix->width * 4, 256); + pix->plane_fmt[0].sizeimage = pix->plane_fmt[0].bytesperline * pix->height; + break; + } +} + +static int risp_io_capture_enum_fmt(struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + switch (f->index) { + case 0: + f->pixelformat = V4L2_PIX_FMT_NV16M; + break; + case 1: + f->pixelformat = V4L2_PIX_FMT_XBGR32; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int risp_io_capture_g_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + f->fmt.pix_mp = io->format.fmt.pix_mp; + + return 0; +} + +static int risp_io_capture_s_fmt(struct file *file, void *priv, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + if (vb2_is_busy(&io->queue)) + return -EBUSY; + + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + risp_io_capture_try_format(io, &f->fmt.pix_mp); + + io->format.fmt.pix_mp = f->fmt.pix_mp; + + return 0; +} + +static int risp_io_capture_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct rcar_isp_core_io *io = video_drvdata(file); + + risp_io_capture_try_format(io, &f->fmt.pix_mp); + + return 0; +} + +static int risp_io_capture_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + if (fsize->index != 0) + return -EINVAL; + + switch (fsize->pixel_format) { + case V4L2_PIX_FMT_NV16M: + case V4L2_PIX_FMT_XBGR32: + break; + default: + return -EINVAL; + } + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + + fsize->stepwise.min_width = 128; + fsize->stepwise.max_width = 5120; + fsize->stepwise.step_width = 2; + + fsize->stepwise.min_height = 128; + fsize->stepwise.max_height = 4096; + fsize->stepwise.step_height = 2; + + return 0; +} + +static const struct v4l2_ioctl_ops risp_io_capture_ioctl_ops = { + .vidioc_querycap = risp_io_querycap, + + .vidioc_enum_fmt_vid_cap = risp_io_capture_enum_fmt, + .vidioc_g_fmt_vid_cap_mplane = risp_io_capture_g_fmt, + .vidioc_s_fmt_vid_cap_mplane = risp_io_capture_s_fmt, + .vidioc_try_fmt_vid_cap_mplane = risp_io_capture_try_fmt, + .vidioc_enum_framesizes = risp_io_capture_enum_framesizes, + + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, +}; + +/* ----------------------------------------------------------------------------- + * Create and remove IO video devices + */ + +int risp_core_io_create(struct device *dev, struct rcar_isp_core *core, + struct rcar_isp_core_io *io, unsigned int pad) +{ + struct video_device *vdev = &io->vdev; + struct vb2_queue *q = &io->queue; + int ret; + + switch (pad) { + case RISP_CORE_INPUT1: + snprintf(vdev->name, sizeof(vdev->name), "%s %s input1", + KBUILD_MODNAME, dev_name(dev)); + vdev->vfl_dir = VFL_DIR_TX; + vdev->device_caps = V4L2_CAP_VIDEO_OUTPUT_MPLANE; + vdev->ioctl_ops = &risp_io_input_ioctl_ops; + + q->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + q->ops = &risp_io_input_qops; + + io->pad.flags = MEDIA_PAD_FL_SOURCE; + io->format.fmt.pix_mp = risp_io_input_default_format; + break; + + case RISP_CORE_PARAMS: + snprintf(vdev->name, sizeof(vdev->name), "%s %s params", + KBUILD_MODNAME, dev_name(dev)); + vdev->vfl_dir = VFL_DIR_TX; + vdev->device_caps = V4L2_CAP_META_OUTPUT; + vdev->ioctl_ops = &risp_io_params_ioctl_ops; + + q->type = V4L2_BUF_TYPE_META_OUTPUT; + q->ops = &risp_io_params_qops; + + io->pad.flags = MEDIA_PAD_FL_SOURCE; + io->format.fmt.meta = risp_io_params_default_format; + break; + + case RISP_CORE_STATS: + snprintf(vdev->name, sizeof(vdev->name), "%s %s stats", + KBUILD_MODNAME, dev_name(dev)); + vdev->vfl_dir = VFL_DIR_RX; + vdev->device_caps = V4L2_CAP_META_CAPTURE; + vdev->ioctl_ops = &risp_io_stats_ioctl_ops; + + q->type = V4L2_BUF_TYPE_META_CAPTURE; + q->ops = &risp_io_stats_qops; + + io->pad.flags = MEDIA_PAD_FL_SINK; + io->format.fmt.meta = risp_io_stats_default_format; + break; + + case RISP_CORE_OUTPUT1: + snprintf(vdev->name, sizeof(vdev->name), "%s %s output1", + KBUILD_MODNAME, dev_name(dev)); + vdev->vfl_dir = VFL_DIR_RX; + vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE_MPLANE; + vdev->ioctl_ops = &risp_io_capture_ioctl_ops; + + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->ops = &risp_io_capture_qops; + + io->pad.flags = MEDIA_PAD_FL_SINK; + io->format.fmt.pix_mp = risp_io_capture_default_format; + break; + } + + io->core = core; + + mutex_init(&io->lock); + INIT_LIST_HEAD(&io->buffers); + + /* Create media graph pad. */ + ret = media_entity_pads_init(&io->vdev.entity, 1, &io->pad); + if (ret) + return ret; + + /* Create queue */ + q->io_modes = VB2_MMAP | VB2_DMABUF; + q->lock = &io->lock; + q->drv_priv = io; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct risp_buffer); + q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + q->dev = dev; + + ret = vb2_queue_init(q); + if (ret < 0) { + risp_io_err(io, "Failed to initialize VB2 queue\n"); + return ret; + } + + /* Create video device */ + vdev->v4l2_dev = &core->v4l2_dev; + vdev->queue = &io->queue; + + vdev->release = video_device_release_empty; + vdev->lock = &io->lock; + vdev->fops = &risp_io_fops; + + vdev->device_caps |= V4L2_CAP_STREAMING | V4L2_CAP_IO_MC; + + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); + if (ret) { + risp_io_err(io, "Failed to register video device\n"); + return ret; + } + + video_set_drvdata(&io->vdev, io); + + v4l2_info(&core->v4l2_dev, "Device registered as %s\n", + video_device_node_name(vdev)); + + switch (pad) { + case RISP_CORE_INPUT1: + case RISP_CORE_PARAMS: + ret = media_create_pad_link(&io->vdev.entity, 0, + &core->subdev.entity, pad, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + break; + case RISP_CORE_STATS: + case RISP_CORE_OUTPUT1: + ret = media_create_pad_link(&core->subdev.entity, pad, + &io->vdev.entity, 0, + MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE); + break; + } + + return ret; +} + +void risp_core_io_destroy(struct rcar_isp_core_io *io) +{ + if (!video_is_registered(&io->vdev)) + return; + + vb2_video_unregister_device(&io->vdev); +} diff --git a/drivers/media/platform/renesas/rcar-isp/core.c b/drivers/media/platform/renesas/rcar-isp/core.c new file mode 100644 index 000000000000..f3dc52c13612 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-isp/core.c @@ -0,0 +1,901 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2026 Renesas Electronics Corp. + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2026 Ragnatech AB + */ + +#include +#include +#include + +#include +#include +#include + +#include "risp-core.h" + +#define ISP_CS_STREAMER_MODE_REG 0x7000 +#define ISP_CS_STREAMER_MODE_STREAMER_EN 0xf + +#define ISP_CS_STREAMER_VBLANK_REG 0x7004 +#define ISP_CS_STREAMER_HBLANK_REG 0x7008 + +#define ISP_CS_STREAMER_CONFIG_DMA_CONTROL_REG 0x7100 +#define ISP_CS_STREAMER_CONFIG_DMA_REG_ADDRESS_UPPER_8BIT_MASK GENMASK(31, 24) +#define ISP_CS_STREAMER_CONFIG_DMA_ENABLE0 BIT(0) + +#define ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_REG 0x2100 +#define ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_ENABLE1 BIT(31) +#define ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_CONFIG_DATA_START_REG_ADDRESS_MASK GENMASK(15, 0) + +#define ISP_CS_STREAMER_CONFIG_DMA_CONTROL2_REG 0x2104 + +#define ISP_CORE_ISPCORE_INT_STATUS 0x80000 +#define ISP_CORE_ISPCORE_INT_ENABLE 0x80004 +#define ISPCORE_DMA_IMAGE_FRAME_MODE(i, f) (0x84000 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_PIXEL_POSITION(i, f) (0x84004 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_PIXEL_BITWIDTH_MINUS1(i, f) (0x84008 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_PIXEL_BPP(i, f) (0x8400c + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP0(i, f) (0x84010 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP1(i, f) (0x84014 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP2(i, f) (0x84018 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP3(i, f) (0x8401c + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP0(i, f) (0x84020 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP1(i, f) (0x84024 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP2(i, f) (0x84028 + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP3(i, f) (0x8402c + 0x1000 * (i) + 0x100 * (f)) +#define ISPCORE_DMA_IMAGE_FRAME_AXI_ID(i, f) (0x84030 + 0x1000 * (i) + 0x100 * (f)) + +#define ISPCORE_DMA_IMAGE_FLUSH_OUT_REG(i) (0x84400 + 0x1000 * (i)) +#define ISPCORE_DMA_IMAGE_FLUSH_OUT_PADDING_PIXEL_EOF_MASK GENMASK(31, 16) +#define ISPCORE_DMA_IMAGE_FLUSH_OUT_PADDING_PIXEL_EOF_SHIFT 16 + +#define ISPCORE_DMA_IMAGE_AXI_CONFIG_REG(i) (0x84800 + 0x1000 * (i)) + +static void risp_cs_write(struct rcar_isp_core *core, u32 offset, u32 value) +{ + iowrite32(value, core->csbase + offset); +} + +static u32 risp_cs_read(struct rcar_isp_core *core, u32 offset) +{ + return ioread32(core->csbase + offset); +} + +static void risp_core_write(struct rcar_isp_core *core, u32 offset, u32 value) +{ + iowrite32(value, core->base + offset); +} + +static u32 risp_core_read(struct rcar_isp_core *core, u32 offset) +{ + return ioread32(core->base + offset); +} + +static void risp_core_job_run_params(struct rcar_isp_core *core, + struct vsp1_isp_job_desc *vspx_job, + struct risp_buffer *buf) +{ + u32 *params_buf = (u32 *)buf->vsp_buffer.cpu_addr; + bool have_config = !!params_buf[0]; + u32 ctrl0, ctrl1, ctrl2; + + /* + * If we have a configuration but not asked the VSPX to program it, + * use MMIO to write the configuration. This might be needed to work + * around limitations of the VSPX ConfigDMA, see comment in + * risp_core_job_prepare(). + */ + if (have_config && !vspx_job->config.pairs) { + for (unsigned int i = 0; i < params_buf[0]; i++) + risp_core_write(core, params_buf[2 + i * 2] & 0xffff, + params_buf[3 + i * 2]); + + /* Disable ConfigDMA. */ + have_config = false; + } + + ctrl0 = risp_cs_read(core, ISP_CS_STREAMER_CONFIG_DMA_CONTROL_REG) & + ~ISP_CS_STREAMER_CONFIG_DMA_ENABLE0; + ctrl1 = risp_cs_read(core, ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_REG) & + ~(ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_ENABLE1 | 0xffff); + ctrl2 = 0; + + if (have_config) { + ctrl0 |= ISP_CS_STREAMER_CONFIG_DMA_ENABLE0; + ctrl1 |= ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_ENABLE1 | + (params_buf[2] & 0xffff); + ctrl2 = params_buf[3]; + } + + risp_cs_write(core, ISP_CS_STREAMER_CONFIG_DMA_CONTROL_REG, ctrl0); + risp_cs_write(core, ISP_CS_STREAMER_CONFIG_DMA_CONTROL1_REG, ctrl1); + risp_cs_write(core, ISP_CS_STREAMER_CONFIG_DMA_CONTROL2_REG, ctrl2); +} + +static void risp_core_job_run_output(struct rcar_isp_core *core, + struct risp_buffer *buf) +{ + const struct v4l2_format *fmt = &core->io[RISP_CORE_OUTPUT1].format; + dma_addr_t mem; + u32 reg; + + for (unsigned int frame = 0; frame < 4; frame++) { + reg = ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP0(0, frame); + mem = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 0); + risp_core_write(core, reg, mem); + + /* Only NV16 uses 2 planes. */ + if (fmt->fmt.pix_mp.pixelformat != V4L2_PIX_FMT_NV16M) + continue; + + reg = ISPCORE_DMA_IMAGE_FRAME_BASE_ADDRESS_COMP1(0, frame); + mem = vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, 1); + risp_core_write(core, reg, mem); + } +} + +static void risp_core_job_run(struct rcar_isp_core *core) +{ + struct rcar_isp_job *job; + + lockdep_assert_held(&core->lock); + + /* ISP not yet started, nothing to do. */ + if (!core->streaming) + return; + + /* If we have active buffers in the ISP core, nothing to do. */ + if (core->vspx.job) + return; + + job = list_first_entry_or_null(&core->risp_jobs, + struct rcar_isp_job, + job_queue); + if (!job) + return; + + list_del(&job->job_queue); + + core->vspx.job = job; + + /* Program the ISP register before kicking the VSPX. */ + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + struct risp_buffer *buf = job->buffers[i]; + + switch (i) { + case RISP_CORE_PARAMS: + risp_core_job_run_params(core, &job->vspx_job, buf); + break; + case RISP_CORE_OUTPUT1: + risp_core_job_run_output(core, buf); + break; + } + } + + if (vsp1_isp_job_run(core->vspx.dev, &job->vspx_job)) { + /* + * Release all buffers in this job if running on the VSPX + * failed. Userspace should recover from this, no new jobs are + * scheduled. + */ + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + struct risp_buffer *buf = job->buffers[i]; + + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + vsp1_isp_job_release(core->vspx.dev, &job->vspx_job); + core->vspx.job = NULL; + kfree(job); + + dev_err(core->dev, "Failed to run job"); + } +} + +static int risp_core_pixfmt_to_vspx(u32 pixfmt) +{ + switch (pixfmt) { + case V4L2_PIX_FMT_SBGGR8: + case V4L2_PIX_FMT_SGBRG8: + case V4L2_PIX_FMT_SGRBG8: + case V4L2_PIX_FMT_SRGGB8: + return V4L2_PIX_FMT_GREY; + case V4L2_PIX_FMT_SBGGR10: + case V4L2_PIX_FMT_SGBRG10: + case V4L2_PIX_FMT_SGRBG10: + case V4L2_PIX_FMT_SRGGB10: + return V4L2_PIX_FMT_Y10; + case V4L2_PIX_FMT_SBGGR12: + case V4L2_PIX_FMT_SGBRG12: + case V4L2_PIX_FMT_SGRBG12: + case V4L2_PIX_FMT_SRGGB12: + return V4L2_PIX_FMT_Y12; + default: + return -EINVAL; + } +} + +int risp_core_job_prepare(struct rcar_isp_core *core) +{ + struct vsp1_isp_job_desc *vspx_job; + int vspx_pixfmt = -EINVAL; + struct rcar_isp_job *job; + int ret; + + lockdep_assert_held(&core->io_lock); + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + if (list_empty(&core->io[i].buffers)) + return 0; + } + + /* Memory is released when the job is consumed. */ + job = kzalloc(sizeof(*job), GFP_KERNEL); + if (!job) + return -ENOMEM; + + vspx_job = &job->vspx_job; + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + struct risp_buffer *buf; + + /* + * Extract buffer from the IO queue and save a reference in + * the job description. Buffers will be completed when the + * corresponding frame will be completed by the ISP. + */ + buf = list_first_entry_or_null(&core->io[i].buffers, + struct risp_buffer, list); + /* + * This should not happen as we have checked there is buffers, + * with the lock held, but check the return value anyhow. + */ + if (WARN_ON(!buf)) { + ret = -EINVAL; + goto error_return_buffers; + } + + switch (i) { + case RISP_CORE_INPUT1: { + u32 isp_pixfmt = core->io[i].format.fmt.pix_mp.pixelformat; + + vspx_pixfmt = risp_core_pixfmt_to_vspx(isp_pixfmt); + + vspx_job->img.fmt = core->io[i].format.fmt.pix_mp; + vspx_job->img.fmt.pixelformat = vspx_pixfmt; + vspx_job->img.mem = + vb2_dma_contig_plane_dma_addr(&buf->vb.vb2_buf, + 0); + break; + } + case RISP_CORE_PARAMS: { + u32 *params_buf = (u32 *)buf->vsp_buffer.cpu_addr; + u32 pairs = params_buf[0]; + + /* + * Check config pairs not larger then buffer. + * + * Remove 8 byte header and each pair is 16 bytes. + */ + if (pairs > (RISP_IO_PARAMS_BUF_SIZE - 8) / 16) { + ret = -EINVAL; + goto error_return_buffers; + } + + /* + * Work around undocumented behavior of the ConfigDMA + * interface by using MMIO if 16 or less pairs are to + * be programmed. + * + * Programming 15 or less pairs corrupts the image data + * following the config buffer, programming exactly 16 + * pairs freeze the whole VSPX. + */ + if (pairs <= 16) { + vspx_job->config.pairs = 0; + } else { + vspx_job->config.pairs = pairs; + vspx_job->config.mem = buf->vsp_buffer.dma_addr; + } + break; + } + } + + list_del(&buf->list); + job->buffers[i] = buf; + } + + if (vspx_pixfmt < 0) { + ret = -EINVAL; + goto error_return_buffers; + } + + ret = vsp1_isp_job_prepare(core->vspx.dev, vspx_job); + if (ret) + goto error_return_buffers; + + scoped_guard(spinlock_irqsave, &core->lock) { + list_add_tail(&job->job_queue, &core->risp_jobs); + risp_core_job_run(core); + } + + return 0; + +error_return_buffers: + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + if (!job->buffers[i]) + continue; + + vb2_buffer_done(&job->buffers[i]->vb.vb2_buf, + VB2_BUF_STATE_ERROR); + } + kfree(job); + return ret; +} + +static int risp_core_config_output(struct rcar_isp_core *core, + unsigned int index, + const struct v4l2_pix_format_mplane *pix) +{ + /* For all frame capture slots. */ + for (unsigned int frame = 0; frame < 4; frame++) { + switch (pix->pixelformat) { + case V4L2_PIX_FMT_NV16M: + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_MODE(index, frame), + 1); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_POSITION(index, frame), + 0 << 24 | 0 << 16 | 4 << 8 | 16 << 0); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_BITWIDTH_MINUS1(index, frame), + 0 << 24 | 0 << 16 | 7 << 8 | 7 << 0); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_BPP(index, frame), + 0 << 28 | 0 << 24 | + 0 << 20 | 0 << 16 | + 3 << 12 | 0 << 8 | + 3 << 4 | 0 << 0); + + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP0(index, frame), + pix->plane_fmt[0].bytesperline); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP1(index, frame), + pix->plane_fmt[1].bytesperline); + break; + case V4L2_PIX_FMT_XBGR32: + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_MODE(index, frame), + 0); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_POSITION(index, frame), + 0 << 24 | 0 << 16 | 0 << 8 | 0 << 0); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_BITWIDTH_MINUS1(index, frame), + 0 << 24 | 0 << 16 | 0 << 8 | 23 << 0); + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_PIXEL_BPP(index, frame), + 0 << 28 | 0 << 24 | + 0 << 20 | 0 << 16 | + 0 << 12 | 0 << 8 | + 3 << 4 | 2 << 0); + + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_STRIDE_COMP0(index, frame), + pix->plane_fmt[0].bytesperline); + break; + default: + return -EINVAL; + } + + risp_core_write(core, + ISPCORE_DMA_IMAGE_FRAME_AXI_ID(index, frame), + 0); + } + + /* Set image out flush EOF. */ + risp_core_write(core, ISPCORE_DMA_IMAGE_FLUSH_OUT_REG(index), + pix->plane_fmt[0].bytesperline << + ISPCORE_DMA_IMAGE_FLUSH_OUT_PADDING_PIXEL_EOF_SHIFT); + + /* Enable DMA and set burst length. */ + risp_core_write(core, ISPCORE_DMA_IMAGE_AXI_CONFIG_REG(index), + BIT(31) | 7); + + return 0; +} + +static u32 risp_core_pix2bus(const struct rcar_isp_core_io *io) +{ + switch (io->format.fmt.pix_mp.pixelformat) { + case V4L2_PIX_FMT_SBGGR8: + return MEDIA_BUS_FMT_SBGGR8_1X8; + case V4L2_PIX_FMT_SGBRG8: + return MEDIA_BUS_FMT_SGBRG8_1X8; + case V4L2_PIX_FMT_SGRBG8: + return MEDIA_BUS_FMT_SGRBG8_1X8; + case V4L2_PIX_FMT_SRGGB8: + return MEDIA_BUS_FMT_SRGGB8_1X8; + case V4L2_PIX_FMT_SBGGR10: + return MEDIA_BUS_FMT_SBGGR10_1X10; + case V4L2_PIX_FMT_SGBRG10: + return MEDIA_BUS_FMT_SGBRG10_1X10; + case V4L2_PIX_FMT_SGRBG10: + return MEDIA_BUS_FMT_SGRBG10_1X10; + case V4L2_PIX_FMT_SRGGB10: + return MEDIA_BUS_FMT_SRGGB10_1X10; + case V4L2_PIX_FMT_SBGGR12: + return MEDIA_BUS_FMT_SBGGR12_1X12; + case V4L2_PIX_FMT_SGBRG12: + return MEDIA_BUS_FMT_SGBRG12_1X12; + case V4L2_PIX_FMT_SGRBG12: + return MEDIA_BUS_FMT_SGRBG12_1X12; + case V4L2_PIX_FMT_SRGGB12: + return MEDIA_BUS_FMT_SRGGB12_1X12; + case V4L2_PIX_FMT_XBGR32: + return MEDIA_BUS_FMT_RGB888_1X24; + case V4L2_PIX_FMT_NV16M: + return MEDIA_BUS_FMT_YUYV12_1X24; + default: + return 0; + } +} + +static void risp_core_try_next_job(struct rcar_isp_core *core) +{ + lockdep_assert_held(&core->lock); + + struct rcar_isp_job *job = core->vspx.job; + + /* If the ISP or the VSPX is not done with the job, wait. */ + if (!job || !job->done_isp || !job->done_vspx) + return; + + core->vspx.job = NULL; + kfree(job); + + core->sequence++; + + /* Kickoff processing of next frame (if any). */ + risp_core_job_run(core); +} + +static void risp_core_vspx_frame_end(void *data) +{ + struct rcar_isp_core *core = data; + + guard(spinlock_irqsave)(&core->lock); + + /* + * In tear-down the ISP may report a frame end event but we have already + * freed the job. It is safe to ignore the end of frame event. + */ + if (!core->vspx.job) + return; + + core->vspx.job->done_vspx = true; + risp_core_try_next_job(core); +} + +static int risp_core_power_on(struct rcar_isp_core *core) +{ + int ret; + + ret = pm_runtime_resume_and_get(core->dev); + if (ret < 0) + return ret; + + ret = reset_control_deassert(core->csrstc); + if (ret) + goto err_pm; + + ret = clk_prepare_enable(core->clk); + if (ret) + goto err_csrstc; + + return 0; + +err_csrstc: + reset_control_assert(core->csrstc); +err_pm: + pm_runtime_put(core->dev); + + return ret; +} + +static void risp_core_power_off(struct rcar_isp_core *core) +{ + clk_disable_unprepare(core->clk); + + reset_control_assert(core->csrstc); + + pm_runtime_put(core->dev); +} + +int risp_core_start_streaming(struct rcar_isp_core *core) +{ + struct vsp1_vspx_frame_end vspx_fe = { + .vspx_frame_end = risp_core_vspx_frame_end, + .frame_end_data = core, + }; + + struct v4l2_mbus_framefmt inputfmt = { + .width = core->io[RISP_CORE_INPUT1].format.fmt.pix_mp.width, + .height = core->io[RISP_CORE_INPUT1].format.fmt.pix_mp.height, + .code = risp_core_pix2bus(&core->io[RISP_CORE_INPUT1]), + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_RAW, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = V4L2_QUANTIZATION_FULL_RANGE, + .xfer_func = V4L2_XFER_FUNC_NONE, + }; + + struct v4l2_mbus_framefmt hvout = { + .width = core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp.width, + .height = core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp.height, + .code = risp_core_pix2bus(&core->io[RISP_CORE_OUTPUT1]), + .field = V4L2_FIELD_NONE, + .colorspace = V4L2_COLORSPACE_SRGB, + .ycbcr_enc = V4L2_YCBCR_ENC_601, + .quantization = + core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp.pixelformat == + V4L2_PIX_FMT_XBGR32 ? + V4L2_QUANTIZATION_FULL_RANGE : + V4L2_QUANTIZATION_LIM_RANGE, + .xfer_func = V4L2_XFER_FUNC_SRGB, + }; + int ret; + + scoped_guard(mutex, &core->io_lock) { + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + if (!core->io[i].streaming) + return 0; + } + + /* + * The state core->streaming is protected by core->lock, which + * is not held yet. It is however safe to read it here since + * core->io_lock is held both in risp_core_stop_streaming() and + * here, the only two places the variable is modified. + * + * With this small implied dependency on the two locks for write + * access, the interrupt handler can safely depend sole on the + * spinlock core->lock for read access to core->streaming. + * + * The gain is an interrupt handler which can hold the spinlock + * and a start/stop procedure which can reset the ISP using the + * reset_control_reset() API, The later which can not be called + * from a context that may sleep. + * + * All other locations core->streaming is read and _all_ + * locations where it is written core->lock is held. + */ + if (core->streaming) + return 0; + + ret = risp_core_power_on(core); + if (ret) + return ret; + + /* Reset and wait for ISP core to initialize itself. */ + reset_control_reset(core->rstc); + usleep_range(2000, 4000); + + scoped_guard(spinlock_irqsave, &core->lock) { + risp_core_write(core, ISP_CORE_ISPCORE_INT_ENABLE, 1); + + /* Configure output DMA */ + risp_core_config_output(core, 0, + &core->io[RISP_CORE_OUTPUT1].format.fmt.pix_mp); + + risp_cs_write(core, ISP_CS_STREAMER_VBLANK_REG, inputfmt.width * 25); + risp_cs_write(core, ISP_CS_STREAMER_HBLANK_REG, 64); + + /* Enable ISP Streaming bridge. */ + risp_cs_write(core, ISP_CS_STREAMER_MODE_REG, + ISP_CS_STREAMER_MODE_STREAMER_EN); + + /* Start RPP ISP */ + ret = rppx1_start(core->rpp, &inputfmt, &hvout, NULL); + if (ret) { + risp_core_power_off(core); + return ret; + } + + core->vspx.job = NULL; + core->sequence = 0; + core->streaming = true; + } + + /* Start VSPX */ + vsp1_isp_start_streaming(core->vspx.dev, &vspx_fe); + + scoped_guard(spinlock_irqsave, &core->lock) { + risp_core_job_run(core); + } + } + + return 0; +} + +void risp_core_stop_streaming(struct rcar_isp_core *core) +{ + struct rcar_isp_job *job, *tmp; + + /* + * This function releases buffers and jobs: make sure the queues mutex + * is held. + */ + lockdep_assert_held(&core->io_lock); + + scoped_guard(spinlock_irqsave, &core->lock) { + /* Stop is called by each vdev, only act on the first call. */ + if (!core->streaming) + return; + + /* Stop queueing jobs to VSPX. */ + core->streaming = false; + } + + /* Wait for active VSPX job to finish. */ + for (unsigned int retry = 0; retry <= 10; retry++) { + if (!core->vspx.job) + break; + + usleep_range(2000, 4000); + } + + if (core->vspx.job) + dev_err(core->dev, "Failed to complete running job"); + + /* Free all buffers and switch off the hardware. */ + scoped_guard(spinlock_irqsave, &core->lock) { + /* Free all jobs and buffers. */ + list_for_each_entry_safe(job, tmp, &core->risp_jobs, job_queue) { + vsp1_isp_job_release(core->vspx.dev, &job->vspx_job); + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + struct risp_buffer *buf = job->buffers[i]; + + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); + } + + list_del(&job->job_queue); + kfree(job); + } + + rppx1_stop(core->rpp); + risp_cs_write(core, ISP_CS_STREAMER_MODE_REG, 0); + risp_core_write(core, ISP_CORE_ISPCORE_INT_ENABLE, 0); + } + + vsp1_isp_stop_streaming(core->vspx.dev); + + risp_core_power_off(core); +} + +static irqreturn_t risp_core_irq(int irq, void *data) +{ + struct rcar_isp_core *core = data; + struct rcar_isp_job *job; + u32 status; + + status = risp_core_read(core, ISP_CORE_ISPCORE_INT_STATUS); + if (!(status & BIT(0))) + return IRQ_NONE; + + if (!rppx1_interrupt(core->rpp, &status)) + return IRQ_HANDLED; + + guard(spinlock_irqsave)(&core->lock); + + job = core->vspx.job; + if (!job) + return IRQ_HANDLED; + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + struct risp_buffer *buf; + + buf = job->buffers[i]; + + switch (i) { + case RISP_CORE_STATS: + rppx1_stats_fill_isr(core->rpp, status, + vb2_plane_vaddr(&buf->vb.vb2_buf, 0)); + fallthrough; + case RISP_CORE_OUTPUT1: + case RISP_CORE_INPUT1: + buf->vb.sequence = core->sequence; + buf->vb.vb2_buf.timestamp = ktime_get_ns(); + fallthrough; + case RISP_CORE_PARAMS: + vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE); + break; + } + } + + core->vspx.job->done_isp = true; + risp_core_try_next_job(core); + + return IRQ_HANDLED; +} + +static const struct v4l2_subdev_ops risp_core_subdev_ops = { +}; + +static int risp_core_create_subdev(struct rcar_isp_core *core) +{ + struct v4l2_subdev *subdev = &core->subdev; + int ret; + + subdev->owner = THIS_MODULE; + subdev->dev = core->dev; + v4l2_subdev_init(subdev, &risp_core_subdev_ops); + v4l2_set_subdevdata(subdev, core->dev); + snprintf(subdev->name, sizeof(subdev->name), "%s %s core", + KBUILD_MODNAME, dev_name(core->dev)); + subdev->flags = V4L2_SUBDEV_FL_HAS_DEVNODE; + + subdev->entity.function = MEDIA_ENT_F_VID_MUX; + + core->pads[RISP_CORE_INPUT1].flags = MEDIA_PAD_FL_SINK; + core->pads[RISP_CORE_PARAMS].flags = MEDIA_PAD_FL_SINK; + core->pads[RISP_CORE_STATS].flags = MEDIA_PAD_FL_SOURCE; + core->pads[RISP_CORE_OUTPUT1].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&subdev->entity, RISP_CORE_NUM_PADS, + core->pads); + if (ret) + return ret; + + return 0; +} + +int risp_core_registered(struct rcar_isp_core *core, struct v4l2_subdev *sd) +{ + int ret; + + core->v4l2_dev.mdev = sd->v4l2_dev->mdev; + + /* Register ISP Core subdevice. */ + ret = v4l2_device_register_subdev(&core->v4l2_dev, &core->subdev); + if (ret) + return ret; + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) { + ret = risp_core_io_create(core->dev, core, &core->io[i], i); + if (ret) { + /* It is safe to destroy io node that is not created. */ + for (unsigned int n = 0; n < RISP_CORE_NUM_PADS; n++) + risp_core_io_destroy(&core->io[n]); + + v4l2_device_unregister_subdev(&core->subdev); + + return ret; + } + } + + return 0; +} + +static int risp_core_probe_resources(struct rcar_isp_core *core, + struct platform_device *pdev) +{ + struct platform_device *vspx; + struct device_node *of_vspx; + struct resource *res; + int ret; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); + if (!res) + return -ENODEV; + + core->rppaddr = res->start; + core->base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(core->base)) + return PTR_ERR(core->base); + + ret = platform_get_irq_byname(pdev, "core"); + if (ret < 0) + return -ENODEV; + + ret = devm_request_irq(&pdev->dev, ret, risp_core_irq, IRQF_SHARED, + KBUILD_MODNAME, core); + if (ret) + return ret; + + core->clk = devm_clk_get(&pdev->dev, "core"); + if (IS_ERR(core->clk)) + return -ENODEV; + + core->rstc = devm_reset_control_get(&pdev->dev, "core"); + if (IS_ERR(core->rstc)) + return -ENODEV; + + of_vspx = of_parse_phandle(pdev->dev.of_node, "renesas,vspx", 0); + if (!of_vspx) + return -ENODEV; + + vspx = of_find_device_by_node(of_vspx); + if (!vspx) + return -ENODEV; + + /* Attach to VSP-X */ + core->vspx.dev = &vspx->dev; + + ret = vsp1_isp_init(&vspx->dev); + if (ret < 0) + return ret; + + /* Attach to the RPP library + * + * 1. Start and wait for the ISP to startup. + * 2. Attach the RPP library and talk with the RPP ISP. + * 3. Turn off ISP. + * 4. Fail if the RPP is unhappy with the hardware. + */ + ret = clk_prepare_enable(core->clk); + if (ret) + return ret; + + usleep_range(2000, 4000); + + core->rpp = rppx1_create(core->base, &pdev->dev); + + clk_disable_unprepare(core->clk); + + if (!core->rpp) + return -ENODEV; + + return 0; +} + +int risp_core_probe(struct rcar_isp_core *core, struct platform_device *pdev, + void __iomem *csbase, struct reset_control *csrstc) +{ + int ret; + + core->dev = &pdev->dev; + core->csrstc = csrstc; + core->csbase = csbase; + + ret = risp_core_probe_resources(core, pdev); + if (ret) { + core->base = NULL; + return ret; + } + + ret = v4l2_device_register(core->dev, &core->v4l2_dev); + if (ret) + return ret; + + ret = risp_core_create_subdev(core); + if (ret) + return ret; + + mutex_init(&core->io_lock); + spin_lock_init(&core->lock); + INIT_LIST_HEAD(&core->risp_jobs); + + return 0; +} + +void risp_core_remove(struct rcar_isp_core *core) +{ + /* If we did not probe the ISP core, nothing to do. */ + if (!core->base) + return; + + dev_info(core->dev, "Remove ISP Core\n"); + + for (unsigned int i = 0; i < RISP_CORE_NUM_PADS; i++) + risp_core_io_destroy(&core->io[i]); + + v4l2_device_unregister_subdev(&core->subdev); + + mutex_destroy(&core->io_lock); + rppx1_destroy(core->rpp); +} diff --git a/drivers/media/platform/renesas/rcar-isp/csisp.c b/drivers/media/platform/renesas/rcar-isp/csisp.c index 8fb2cc3b5650..53ce47020d17 100644 --- a/drivers/media/platform/renesas/rcar-isp/csisp.c +++ b/drivers/media/platform/renesas/rcar-isp/csisp.c @@ -11,14 +11,13 @@ */ #include -#include #include -#include #include -#include #include -#include +#include + +#include "risp-core.h" #define ISPINPUTSEL0_REG 0x0008 #define ISPINPUTSEL0_SEL_CSI0 BIT(31) @@ -158,6 +157,7 @@ struct rcar_isp { struct device *dev; void __iomem *csbase; struct reset_control *rstc; + struct rcar_isp_core core; enum rcar_isp_input csi_input; @@ -451,6 +451,21 @@ static int risp_parse_dt(struct rcar_isp *isp) return ret; } +/* ----------------------------------------------------------------------------- + * ISP Core connection + */ + +static int risp_cs_registered(struct v4l2_subdev *sd) +{ + struct rcar_isp *isp = sd_to_isp(sd); + + return risp_core_registered(&isp->core, sd); +} + +static const struct v4l2_subdev_internal_ops risp_cs_internal_ops = { + .registered = risp_cs_registered, +}; + /* ----------------------------------------------------------------------------- * Platform Device Driver */ @@ -477,7 +492,7 @@ static int risp_probe_resources(struct rcar_isp *isp, if (IS_ERR(isp->csbase)) return PTR_ERR(isp->csbase); - isp->rstc = devm_reset_control_get(&pdev->dev, NULL); + isp->rstc = devm_reset_control_get_shared(&pdev->dev, NULL); return PTR_ERR_OR_ZERO(isp->rstc); } @@ -541,14 +556,31 @@ static int risp_probe(struct platform_device *pdev) if (ret) goto error_notifier; + ret = risp_core_probe(&isp->core, pdev, isp->csbase, isp->rstc); + switch (ret) { + case 0: + /* The device have an ISP core. */ + isp->subdev.internal_ops = &risp_cs_internal_ops; + break; + case -ENODEV: + /* The device don't have an ISP core, that is OK. */ + ret = 0; + break; + default: + /* Something went wrong registering the ISP core. */ + goto error_subdev; + } + ret = v4l2_async_register_subdev(&isp->subdev); if (ret < 0) - goto error_subdev; + goto error_core; dev_info(isp->dev, "Using CSI-2 input: %u\n", isp->csi_input); return 0; +error_core: + risp_core_remove(&isp->core); error_subdev: v4l2_subdev_cleanup(&isp->subdev); error_notifier: @@ -564,6 +596,8 @@ static void risp_remove(struct platform_device *pdev) { struct rcar_isp *isp = platform_get_drvdata(pdev); + risp_core_remove(&isp->core); + v4l2_async_nf_unregister(&isp->notifier); v4l2_async_nf_cleanup(&isp->notifier); diff --git a/drivers/media/platform/renesas/rcar-isp/risp-core.h b/drivers/media/platform/renesas/rcar-isp/risp-core.h new file mode 100644 index 000000000000..627a762d6b19 --- /dev/null +++ b/drivers/media/platform/renesas/rcar-isp/risp-core.h @@ -0,0 +1,178 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * Copyright (C) 2026 Renesas Electronics Corp. + * Copyright (C) 2026 Ideas on Board Oy + * Copyright (C) 2026 Ragnatech AB + */ + +#ifndef __RCAR_ISP__ +#define __RCAR_ISP__ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +/* Max 2048 address + value pairs in one VSPX buffer, increase if needed. */ +#define RISP_IO_PARAMS_BUF_SIZE 16384 + +struct rcar_isp_core; + +enum risp_core_pads { + RISP_CORE_INPUT1, + RISP_CORE_PARAMS, + RISP_CORE_STATS, + RISP_CORE_OUTPUT1, + RISP_CORE_NUM_PADS, +}; + +/** + * struct risp_buffer - Describe an IO buffer + * @vb: The VB2 buffer + * @list: List of buffers queued to the IO queue + * @vsp_buffer: Buffer mapped from VSP-X, only used for params IO + */ +struct risp_buffer { + struct vb2_v4l2_buffer vb; + struct list_head list; + struct vsp1_isp_buffer_desc vsp_buffer; +}; + +/** + * struct rcar_isp_core_io - Information for a IO video devices + * @core: Backlink to the common ISP core structure + * + * @lock: Protects @vdev, @pad and @queue + open/close fops + * @vdev: V4L2 video device associated with this IO port + * @pad: Media pad for @vdev + * @queue: VB2 buffers queue for $@vdev + * + * @streaming: Flag to indicate if device is streaming, or not + * @buffers: List of buffers queued to the device + * + * @format: The active V4L2 format + */ +struct rcar_isp_core_io { + struct rcar_isp_core *core; + + struct mutex lock; /* See KDoc block. */ + struct video_device vdev; + struct media_pad pad; + struct vb2_queue queue; + + bool streaming; + struct list_head buffers; + + struct v4l2_format format; +}; + +/** + * struct rcar_isp_job - R-Car ISP job description + * + * Both done_isp and done_vspx shall be set before the job can be considered + * completely done. + * + * @buffers: IO buffers that form a job + * @vspx_job: VSPX job description + * @job_queue: list handle + * @done_isp: Flag to indicate the ISP is done with the job + * @done_vspx: Flag to indicate the VSPX is done with the job + */ +struct rcar_isp_job { + struct risp_buffer *buffers[RISP_CORE_NUM_PADS]; + struct vsp1_isp_job_desc vspx_job; + struct list_head job_queue; + bool done_isp; + bool done_vspx; +}; + +/** + * struct rcar_isp_vspx - R-Car ISP job description + * + * @dev: Device reference to VSPX + * @job: Job currently being processed by VSPX + */ +struct rcar_isp_vspx { + struct device *dev; + struct rcar_isp_job *job; +}; + +/** + * struct rcar_isp_core - ISP Core + * @dev: (OF) device + * @rppaddr: Hardware address of the RPP ISP (from OF) + * @clk: The clock for the ISP CORE + * @rstc: The reset for the ISP Core + * @csrstc: The reset for the ISP Channel Selector + * + * @base: MMIO base of the ISP CORE + * @csbase: MMIO base of the ISP CS + * + * @subdev: V4L2 subdevice to represent the ISP CORE + * @pads: Media pad for @subdev + * + * @v4l2_dev: V4L2 device + * @rpp: Handle to the RPP ISP connected to the ISP CORE + * + * @io_lock: Protect io[*].streaming and io[*].buffers + * @io: Array of IO ports to the ISP CORE + * + * @lock: Protects @vspx, @risp_jobs, @sequence and @streaming + * @vspx: Handle to the resources used by VSPX connected to the ISP CORE + * @risp_jobs: Queue of VSPX transfer jobs + * @sequence: V4L2 buffers sequence number + * @streaming: Tracks if the device is streaming + */ +struct rcar_isp_core { + struct device *dev; + + u32 rppaddr; + struct clk *clk; + + struct reset_control *rstc; + struct reset_control *csrstc; + + void __iomem *base; + void __iomem *csbase; + + struct v4l2_subdev subdev; + struct media_pad pads[RISP_CORE_NUM_PADS]; + + struct v4l2_device v4l2_dev; + struct rppx1 *rpp; + + struct mutex io_lock; /* See KDoc block. */ + struct rcar_isp_core_io io[RISP_CORE_NUM_PADS]; + + spinlock_t lock; /* See KDoc block. */ + struct rcar_isp_vspx vspx; + struct list_head risp_jobs; + unsigned int sequence; + bool streaming; +}; + +int risp_core_probe(struct rcar_isp_core *core, struct platform_device *pdev, + void __iomem *csbase, struct reset_control *csrstc); +void risp_core_remove(struct rcar_isp_core *core); +int risp_core_registered(struct rcar_isp_core *core, struct v4l2_subdev *sd); + +int risp_core_job_prepare(struct rcar_isp_core *core); + +int risp_core_start_streaming(struct rcar_isp_core *core); +void risp_core_stop_streaming(struct rcar_isp_core *core); + +int risp_core_io_create(struct device *dev, struct rcar_isp_core *core, + struct rcar_isp_core_io *io, unsigned int pad); +void risp_core_io_destroy(struct rcar_isp_core_io *io); + +#endif