From 9764401bf6f8a20eb11c2e78470f20fee91a9ea7 Mon Sep 17 00:00:00 2001 From: chenchangcheng Date: Sat, 10 May 2025 14:18:03 +0800 Subject: [PATCH 001/310] media: uvcvideo: Fix bandwidth issue for Alcor camera Some broken device return wrong dwMaxPayloadTransferSize fields as follows: [ 218.632537] uvcvideo: Device requested 2752512 B/frame bandwidth. [ 218.632598] uvcvideo: No fast enough alt setting for requested bandwidth. When dwMaxPayloadTransferSize is greater than maxpsize, it will prevent the camera from starting. So use the bandwidth of maxpsize. Signed-off-by: chenchangcheng Reviewed-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250510061803.811433-1-ccc194101@163.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_video.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index e3567aeb0007..11769a1832d2 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -262,6 +262,15 @@ static void uvc_fixup_video_ctrl(struct uvc_streaming *stream, ctrl->dwMaxPayloadTransferSize = bandwidth; } + + if (stream->intf->num_altsetting > 1 && + ctrl->dwMaxPayloadTransferSize > stream->maxpsize) { + dev_warn_ratelimited(&stream->intf->dev, + "UVC non compliance: the max payload transmission size (%u) exceeds the size of the ep max packet (%u). Using the max size.\n", + ctrl->dwMaxPayloadTransferSize, + stream->maxpsize); + ctrl->dwMaxPayloadTransferSize = stream->maxpsize; + } } static size_t uvc_video_ctrl_size(struct uvc_streaming *stream) From 649c033711d7fd6e1d5d69e4cfc3fceca7de2867 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 2 May 2025 07:48:28 +0000 Subject: [PATCH 002/310] media: uvcvideo: Set V4L2_CTRL_FLAG_DISABLED during queryctrl errors To implement VIDIOC_QUERYCTRL, we need to know the minimum, maximum, step and flags of the control. For some of the controls, this involves querying the actual hardware. Some non-compliant cameras produce errors when we query them. These error can be triggered every time, sometimes, or when other controls do not have the "right value". Right now, we populate that error to userspace. When an error happens, the v4l2 framework does not copy the v4l2_queryctrl struct to userspace. Also, userspace apps are not ready to handle any other error than -EINVAL. One of the main usecases of VIDIOC_QUERYCTRL is enumerating the controls of a device. This is done using the V4L2_CTRL_FLAG_NEXT_CTRL flag. In that usecase, a non-compliant control will make it almost impossible to enumerate all controls of the device. A control with an invalid max/min/step/flags is better than non being able to enumerate the rest of the controls. This patch: - Retries for an extra attempt to read the control, to avoid spurious errors. More attempts do not seem to produce better results in the tested hardware. - Makes VIDIOC_QUERYCTRL return 0 for -EIO errors. - Introduces a warning in dmesg so we can have a trace of what has happened and sets the V4L2_CTRL_FLAG_DISABLED. - Makes sure we keep returning V4L2_CTRL_FLAG_DISABLED for all the next attempts to query that control (other operations have the same functionality as now). Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250502-uvc-eaccess-v8-1-0b8b58ac1142@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 55 +++++++++++++++++++++++++++----- drivers/media/usb/uvc/uvcvideo.h | 2 ++ 2 files changed, 49 insertions(+), 8 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 44b6513c5264..f24272d483a2 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1483,14 +1483,28 @@ static u32 uvc_get_ctrl_bitmap(struct uvc_control *ctrl, return ~0; } +/* + * Maximum retry count to avoid spurious errors with controls. Increasing this + * value does no seem to produce better results in the tested hardware. + */ +#define MAX_QUERY_RETRIES 2 + static int __uvc_queryctrl_boundaries(struct uvc_video_chain *chain, struct uvc_control *ctrl, struct uvc_control_mapping *mapping, struct v4l2_query_ext_ctrl *v4l2_ctrl) { if (!ctrl->cached) { - int ret = uvc_ctrl_populate_cache(chain, ctrl); - if (ret < 0) + unsigned int retries; + int ret; + + for (retries = 0; retries < MAX_QUERY_RETRIES; retries++) { + ret = uvc_ctrl_populate_cache(chain, ctrl); + if (ret != -EIO) + break; + } + + if (ret) return ret; } @@ -1567,6 +1581,7 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, { struct uvc_control_mapping *master_map = NULL; struct uvc_control *master_ctrl = NULL; + int ret; memset(v4l2_ctrl, 0, sizeof(*v4l2_ctrl)); v4l2_ctrl->id = mapping->id; @@ -1587,18 +1602,31 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, __uvc_find_control(ctrl->entity, mapping->master_id, &master_map, &master_ctrl, 0, 0); if (master_ctrl && (master_ctrl->info.flags & UVC_CTRL_FLAG_GET_CUR)) { + unsigned int retries; s32 val; int ret; if (WARN_ON(uvc_ctrl_mapping_is_compound(master_map))) return -EIO; - ret = __uvc_ctrl_get(chain, master_ctrl, master_map, &val); - if (ret < 0) - return ret; + for (retries = 0; retries < MAX_QUERY_RETRIES; retries++) { + ret = __uvc_ctrl_get(chain, master_ctrl, master_map, + &val); + if (!ret) + break; + if (ret < 0 && ret != -EIO) + return ret; + } - if (val != mapping->master_manual) - v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; + if (ret == -EIO) { + dev_warn_ratelimited(&chain->dev->udev->dev, + "UVC non compliance: Error %d querying master control %x (%s)\n", + ret, master_map->id, + uvc_map_get_name(master_map)); + } else { + if (val != mapping->master_manual) + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_INACTIVE; + } } v4l2_ctrl->elem_size = uvc_mapping_v4l2_size(mapping); @@ -1613,7 +1641,18 @@ static int __uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, return 0; } - return __uvc_queryctrl_boundaries(chain, ctrl, mapping, v4l2_ctrl); + ret = __uvc_queryctrl_boundaries(chain, ctrl, mapping, v4l2_ctrl); + if (ret && !mapping->disabled) { + dev_warn(&chain->dev->udev->dev, + "UVC non compliance: permanently disabling control %x (%s), due to error %d\n", + mapping->id, uvc_map_get_name(mapping), ret); + mapping->disabled = true; + } + + if (mapping->disabled) + v4l2_ctrl->flags |= V4L2_CTRL_FLAG_DISABLED; + + return 0; } int uvc_query_v4l2_ctrl(struct uvc_video_chain *chain, diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index b9f8eb62ba1d..11d6e3c2ebdf 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -134,6 +134,8 @@ struct uvc_control_mapping { s32 master_manual; u32 slave_ids[2]; + bool disabled; + const struct uvc_control_mapping *(*filter_mapping) (struct uvc_video_chain *chain, struct uvc_control *ctrl); From cdf8f4c78ab0c2dc98dd5b017b0a31590c3e046d Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 23 May 2025 14:28:41 +0000 Subject: [PATCH 003/310] media: uvcvideo: Populate all errors in uvc_probe() Now we are replacing most of the error codes with -ENODEV. Instead, Populate the error code from the functions called by uvc_probe(). Take this opportunity to replace a generic error code from uvc_scan_device() into something more meaningful. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20250523-uvc-followup-v2-1-487541656e83@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index da24a655ab68..04552da11435 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1866,7 +1866,7 @@ static int uvc_scan_device(struct uvc_device *dev) if (list_empty(&dev->chains)) { dev_info(&dev->udev->dev, "No valid video chain found.\n"); - return -1; + return -ENODEV; } /* Add GPIO entity to the first chain. */ @@ -2239,7 +2239,6 @@ static int uvc_probe(struct usb_interface *intf, /* Parse the Video Class control descriptor. */ ret = uvc_parse_control(dev); if (ret < 0) { - ret = -ENODEV; uvc_dbg(dev, PROBE, "Unable to parse UVC descriptors\n"); goto error; } @@ -2275,22 +2274,19 @@ static int uvc_probe(struct usb_interface *intf, goto error; /* Scan the device for video chains. */ - if (uvc_scan_device(dev) < 0) { - ret = -ENODEV; + ret = uvc_scan_device(dev); + if (ret < 0) goto error; - } /* Initialize controls. */ - if (uvc_ctrl_init_device(dev) < 0) { - ret = -ENODEV; + ret = uvc_ctrl_init_device(dev); + if (ret < 0) goto error; - } /* Register video device nodes. */ - if (uvc_register_chains(dev) < 0) { - ret = -ENODEV; + ret = uvc_register_chains(dev); + if (ret < 0) goto error; - } #ifdef CONFIG_MEDIA_CONTROLLER /* Register the media device node */ From 0aa112e4a880ff809ecd3426d9c7c9e1f4ad3826 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 9 May 2025 18:24:13 +0000 Subject: [PATCH 004/310] media: uvcvideo: Refactor uvc_ctrl_set_handle() Today uvc_ctrl_set_handle() covers two use-uses: setting the handle and clearing the handle. The only common code between the two cases is the lockdep_assert_held. The code looks cleaner if we split these two usecases in two functions. We also take this opportunity to use pending_async_ctrls from ctrl where possible. Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250509-uvc-followup-v1-1-73bcde30d2b5@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 68 ++++++++++++++++---------------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index f24272d483a2..303b7509ec47 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -1851,48 +1851,48 @@ static void uvc_ctrl_send_slave_event(struct uvc_video_chain *chain, uvc_ctrl_send_event(chain, handle, ctrl, mapping, val, changes); } -static int uvc_ctrl_set_handle(struct uvc_fh *handle, struct uvc_control *ctrl, - struct uvc_fh *new_handle) +static int uvc_ctrl_set_handle(struct uvc_control *ctrl, struct uvc_fh *handle) { + int ret; + lockdep_assert_held(&handle->chain->ctrl_mutex); - if (new_handle) { - int ret; + if (ctrl->handle) { + dev_warn_ratelimited(&handle->stream->dev->udev->dev, + "UVC non compliance: Setting an async control with a pending operation."); - if (ctrl->handle) - dev_warn_ratelimited(&handle->stream->dev->udev->dev, - "UVC non compliance: Setting an async control with a pending operation."); - - if (new_handle == ctrl->handle) + if (ctrl->handle == handle) return 0; - if (ctrl->handle) { - WARN_ON(!ctrl->handle->pending_async_ctrls); - if (ctrl->handle->pending_async_ctrls) - ctrl->handle->pending_async_ctrls--; - ctrl->handle = new_handle; - handle->pending_async_ctrls++; - return 0; - } - - ret = uvc_pm_get(handle->chain->dev); - if (ret) - return ret; - - ctrl->handle = new_handle; - handle->pending_async_ctrls++; + WARN_ON(!ctrl->handle->pending_async_ctrls); + if (ctrl->handle->pending_async_ctrls) + ctrl->handle->pending_async_ctrls--; + ctrl->handle = handle; + ctrl->handle->pending_async_ctrls++; return 0; } - /* Cannot clear the handle for a control not owned by us.*/ - if (WARN_ON(ctrl->handle != handle)) - return -EINVAL; + ret = uvc_pm_get(handle->chain->dev); + if (ret) + return ret; - ctrl->handle = NULL; - if (WARN_ON(!handle->pending_async_ctrls)) + ctrl->handle = handle; + ctrl->handle->pending_async_ctrls++; + return 0; +} + +static int uvc_ctrl_clear_handle(struct uvc_control *ctrl) +{ + lockdep_assert_held(&ctrl->handle->chain->ctrl_mutex); + + if (WARN_ON(!ctrl->handle->pending_async_ctrls)) { + ctrl->handle = NULL; return -EINVAL; - handle->pending_async_ctrls--; - uvc_pm_put(handle->chain->dev); + } + + ctrl->handle->pending_async_ctrls--; + uvc_pm_put(ctrl->handle->chain->dev); + ctrl->handle = NULL; return 0; } @@ -1910,7 +1910,7 @@ void uvc_ctrl_status_event(struct uvc_video_chain *chain, handle = ctrl->handle; if (handle) - uvc_ctrl_set_handle(handle, ctrl, NULL); + uvc_ctrl_clear_handle(ctrl); list_for_each_entry(mapping, &ctrl->info.mappings, list) { s32 value; @@ -2200,7 +2200,7 @@ static int uvc_ctrl_commit_entity(struct uvc_device *dev, if (!rollback && handle && !ret && ctrl->info.flags & UVC_CTRL_FLAG_ASYNCHRONOUS) - ret = uvc_ctrl_set_handle(handle, ctrl, handle); + ret = uvc_ctrl_set_handle(ctrl, handle); if (ret < 0 && !rollback) { if (err_ctrl) @@ -3310,7 +3310,7 @@ void uvc_ctrl_cleanup_fh(struct uvc_fh *handle) for (unsigned int i = 0; i < entity->ncontrols; ++i) { if (entity->controls[i].handle != handle) continue; - uvc_ctrl_set_handle(handle, &entity->controls[i], NULL); + uvc_ctrl_clear_handle(&entity->controls[i]); } } From 7e666b1aaccadf7ba3ba8c0d0eed5bf876b14d8e Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 9 May 2025 18:24:14 +0000 Subject: [PATCH 005/310] media: uvcvideo: Refactor uvc_queue_streamon Do uvc_pm_get before we call uvc_queue_streamon. Although the current code is correct, uvc_ioctl_streamon is allways called after uvc_pm_get, this change makes the code more resiliant to future changes. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250509-uvc-followup-v1-2-73bcde30d2b5@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_v4l2.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 668a4e9d772c..862b4e34e5b6 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -853,15 +853,16 @@ static int uvc_ioctl_streamon(struct file *file, void *fh, if (handle->is_streaming) return 0; - ret = uvc_queue_streamon(&stream->queue, type); + ret = uvc_pm_get(stream->dev); if (ret) return ret; - ret = uvc_pm_get(stream->dev); + ret = uvc_queue_streamon(&stream->queue, type); if (ret) { - uvc_queue_streamoff(&stream->queue, type); + uvc_pm_put(stream->dev); return ret; } + handle->is_streaming = true; return 0; From 7651fab885ff8af11833671626722053dacb489a Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Fri, 9 May 2025 18:24:15 +0000 Subject: [PATCH 006/310] media: uvcvideo: Refactor uvc_v4l2_compat_ioctl32 Declaring a variable for doing automatic cleanup is not a very common pattern. Replace the cleanup macro with manual cleanup to make the code simpler. Suggested-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250509-uvc-followup-v1-3-73bcde30d2b5@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_v4l2.c | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 862b4e34e5b6..1abdf1ea3995 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -1382,11 +1382,9 @@ static int uvc_v4l2_put_xu_query(const struct uvc_xu_control_query *kp, #define UVCIOC_CTRL_MAP32 _IOWR('u', 0x20, struct uvc_xu_control_mapping32) #define UVCIOC_CTRL_QUERY32 _IOWR('u', 0x21, struct uvc_xu_control_query32) -DEFINE_FREE(uvc_pm_put, struct uvc_device *, if (_T) uvc_pm_put(_T)) static long uvc_v4l2_compat_ioctl32(struct file *file, unsigned int cmd, unsigned long arg) { - struct uvc_device *uvc_device __free(uvc_pm_put) = NULL; struct uvc_fh *handle = file->private_data; union { struct uvc_xu_control_mapping xmap; @@ -1399,38 +1397,38 @@ static long uvc_v4l2_compat_ioctl32(struct file *file, if (ret) return ret; - uvc_device = handle->stream->dev; - switch (cmd) { case UVCIOC_CTRL_MAP32: ret = uvc_v4l2_get_xu_mapping(&karg.xmap, up); if (ret) - return ret; + break; ret = uvc_ioctl_xu_ctrl_map(handle->chain, &karg.xmap); if (ret) - return ret; + break; ret = uvc_v4l2_put_xu_mapping(&karg.xmap, up); if (ret) - return ret; - + break; break; case UVCIOC_CTRL_QUERY32: ret = uvc_v4l2_get_xu_query(&karg.xqry, up); if (ret) - return ret; + break; ret = uvc_xu_ctrl_query(handle->chain, &karg.xqry); if (ret) - return ret; + break; ret = uvc_v4l2_put_xu_query(&karg.xqry, up); if (ret) - return ret; + break; break; default: - return -ENOIOCTLCMD; + ret = -ENOIOCTLCMD; + break; } + uvc_pm_put(handle->stream->dev); + return ret; } #endif From 53b0b80e5240fec7c5a420bffb310edc83faf4fd Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 2 Jun 2025 18:21:57 +0000 Subject: [PATCH 007/310] media: uvcvideo: Add quirk for HP Webcam HD 2300 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HP Webcam HD 2300 does not seem to flip the FID bit according to spec. Device Descriptor: bLength 18 bDescriptorType 1 bcdUSB 2.00 bDeviceClass 239 Miscellaneous Device bDeviceSubClass 2 [unknown] bDeviceProtocol 1 Interface Association bMaxPacketSize0 64 idVendor 0x03f0 HP, Inc idProduct 0xe207 HP Webcam HD 2300 bcdDevice 10.20 iManufacturer 3 Hewlett Packard iProduct 1 HP Webcam HD 2300 iSerial 0 bNumConfigurations 1 Reported-by: Michaël Melchiore Closes: https://lore.kernel.org/linux-media/CA+q66aRvTigH15cUyfvzPJ2mfsDFMt=CjuYNwvAZb29w8b1KDA@mail.gmail.com Signed-off-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250602-uvc-hp-quirk-v1-1-7047d94d679f@chromium.org Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 04552da11435..916c2d13e268 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2510,6 +2510,15 @@ static const struct uvc_device_info uvc_quirk_force_y8 = { * Sort these by vendor/product ID. */ static const struct usb_device_id uvc_ids[] = { + /* HP Webcam HD 2300 */ + { .match_flags = USB_DEVICE_ID_MATCH_DEVICE + | USB_DEVICE_ID_MATCH_INT_INFO, + .idVendor = 0x03f0, + .idProduct = 0xe207, + .bInterfaceClass = USB_CLASS_VIDEO, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 0, + .driver_info = (kernel_ulong_t)&uvc_quirk_stream_no_fid }, /* Quanta ACER HD User Facing */ { .match_flags = USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_INFO, From 782b6a718651eda3478b1824b37a8b3185d2740c Mon Sep 17 00:00:00 2001 From: Youngjun Lee Date: Tue, 10 Jun 2025 21:41:07 +0900 Subject: [PATCH 008/310] media: uvcvideo: Fix 1-byte out-of-bounds read in uvc_parse_format() The buffer length check before calling uvc_parse_format() only ensured that the buffer has at least 3 bytes (buflen > 2), buf the function accesses buffer[3], requiring at least 4 bytes. This can lead to an out-of-bounds read if the buffer has exactly 3 bytes. Fix it by checking that the buffer has at least 4 bytes in uvc_parse_format(). Signed-off-by: Youngjun Lee Reviewed-by: Laurent Pinchart Fixes: c0efd232929c ("V4L/DVB (8145a): USB Video Class driver") Cc: stable@vger.kernel.org Reviewed-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250610124107.37360-1-yjjuny.lee@samsung.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 916c2d13e268..62eb45435d8b 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -344,6 +344,9 @@ static int uvc_parse_format(struct uvc_device *dev, u8 ftype; int ret; + if (buflen < 4) + return -EINVAL; + format->type = buffer[2]; format->index = buffer[3]; format->frames = frames; From f42eddf44fbfc8c00b2ffb7779a8c11cf92d2b29 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 30 May 2025 16:41:47 +0100 Subject: [PATCH 009/310] media: dt-bindings: media: renesas,fcp: Document RZ/V2N SoC The FCPVD block on the RZ/V2N SoC is identical to the one found on the RZ/G2L SoC. No driver changes are required, as `renesas,fcpv` will be used as a fallback compatible string on the RZ/V2N SoC. Signed-off-by: Lad Prabhakar Acked-by: Rob Herring (Arm) Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20250530154148.374663-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/renesas,fcp.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/media/renesas,fcp.yaml b/Documentation/devicetree/bindings/media/renesas,fcp.yaml index 7bf1266223e8..cf92dfe69637 100644 --- a/Documentation/devicetree/bindings/media/renesas,fcp.yaml +++ b/Documentation/devicetree/bindings/media/renesas,fcp.yaml @@ -30,6 +30,7 @@ properties: - renesas,r9a07g043u-fcpvd # RZ/G2UL - renesas,r9a07g044-fcpvd # RZ/G2{L,LC} - renesas,r9a07g054-fcpvd # RZ/V2L + - renesas,r9a09g056-fcpvd # RZ/V2N - renesas,r9a09g057-fcpvd # RZ/V2H(P) - const: renesas,fcpv # Generic FCP for VSP fallback From 2632f288ab1180c42c9379893357b8b7744fbf92 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Fri, 30 May 2025 16:41:48 +0100 Subject: [PATCH 010/310] media: dt-bindings: media: renesas,vsp1: Document RZ/V2N SoC The VSPD block on the RZ/V2N SoC is identical to the one found on the RZ/G2L SoC. No driver changes are required, as `renesas,r9a07g044-vsp2` will be used as a fallback compatible string on the RZ/V2N SoC. Signed-off-by: Lad Prabhakar Reviewed-by: Kieran Bingham Reviewed-by: Laurent Pinchart Acked-by: Rob Herring (Arm) Reviewed-by: Geert Uytterhoeven Link: https://lore.kernel.org/r/20250530154148.374663-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/renesas,vsp1.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/media/renesas,vsp1.yaml b/Documentation/devicetree/bindings/media/renesas,vsp1.yaml index fcf7219b1f40..07a97dd87a5b 100644 --- a/Documentation/devicetree/bindings/media/renesas,vsp1.yaml +++ b/Documentation/devicetree/bindings/media/renesas,vsp1.yaml @@ -25,6 +25,7 @@ properties: - enum: - renesas,r9a07g043u-vsp2 # RZ/G2UL - renesas,r9a07g054-vsp2 # RZ/V2L + - renesas,r9a09g056-vsp2 # RZ/V2N - renesas,r9a09g057-vsp2 # RZ/V2H(P) - const: renesas,r9a07g044-vsp2 # RZ/G2L fallback From 797567e81d201ee478f8efa5004804374fc2cbaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 12 Jun 2025 19:59:01 +0200 Subject: [PATCH 011/310] media: rcar-csi2: Clarify usage of mbps and msps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The helper function to deal with calculating the link speed is designed in such a way that it returns the correct type bps (bits per second) for D-PHY and sps (symbols per second) for C-PHY. And for historical reasons the function kept the name mbps. This is confusing, fix it by having the function only deal with bps values as this is the most common use-case and convert bps to sps in the only function where it is needed to configure the C-PHY. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250612175904.1126717-2-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 9979de4f6ef1..358e7470befc 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -975,10 +975,6 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp, mbps = div_u64(freq * 2, MEGA); - /* Adjust for C-PHY, divide by 2.8. */ - if (priv->cphy) - mbps = div_u64(mbps * 5, 14); - return mbps; } @@ -1203,9 +1199,13 @@ static int rcsi2_wait_phy_start_v4h(struct rcar_csi2 *priv, u32 match) return -ETIMEDOUT; } -static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int msps) +static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) { const struct rcsi2_cphy_setting *conf; + int msps; + + /* Adjust for C-PHY symbols, divide by 2.8. */ + msps = div_u64(mbps * 5, 14); for (conf = cphy_setting_table_r8a779g0; conf->msps != 0; conf++) { if (conf->msps > msps) @@ -1301,7 +1301,7 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, const struct rcar_csi2_format *format; const struct v4l2_mbus_framefmt *fmt; unsigned int lanes; - int msps; + int mbps; int ret; /* Use the format on the sink pad to compute the receiver config. */ @@ -1314,9 +1314,9 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, if (ret) return ret; - msps = rcsi2_calc_mbps(priv, format->bpp, lanes); - if (msps < 0) - return msps; + mbps = rcsi2_calc_mbps(priv, format->bpp, lanes); + if (mbps < 0) + return mbps; /* Reset LINK and PHY*/ rcsi2_write(priv, V4H_CSI2_RESETN_REG, 0); @@ -1352,7 +1352,7 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, rcsi2_write16(priv, V4H_PPI_RW_COMMON_CFG_REG, 0x0003); /* C-PHY settings */ - ret = rcsi2_c_phy_setting_v4h(priv, msps); + ret = rcsi2_c_phy_setting_v4h(priv, mbps); if (ret) return ret; @@ -1363,7 +1363,7 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, return 0; } -static int rcsi2_d_phy_setting_v4m(struct rcar_csi2 *priv, int data_rate) +static int rcsi2_d_phy_setting_v4m(struct rcar_csi2 *priv, int mbps) { unsigned int timeout; int ret; From c927b7b5acfa48471959fb3da9bcc37a11202406 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 12 Jun 2025 19:59:02 +0200 Subject: [PATCH 012/310] media: rcar-csi2: Rework macros to access AFE lanes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each AFE lane has a set of control and offset registers. The registers themself are undocumented but later datasheets program more of them with magic values. Before exploding the driver with more defines to name them all rework the AFE ones to be able to address all controls and offsets. Also move a stray define that is out of sequence. There is no functional change. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250612175904.1126717-3-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 32 +++++++++++----------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 358e7470befc..cdd358b4a973 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -177,15 +177,14 @@ struct rcar_csi2; #define V4H_PPI_RW_OFFSETCAL_CFG_0_REG 0x21ca0 /* V4H CORE registers */ -#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_REG(n) (0x22040 + ((n) * 2)) /* n = 0 - 15 */ -#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_REG(n) (0x22440 + ((n) * 2)) /* n = 0 - 15 */ -#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_REG(n) (0x22840 + ((n) * 2)) /* n = 0 - 15 */ -#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_REG(n) (0x22c40 + ((n) * 2)) /* n = 0 - 15 */ -#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_REG(n) (0x23040 + ((n) * 2)) /* n = 0 - 15 */ + +#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, n) (0x22040 + ((l) * 0x400) + ((n) * 2)) +#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_3_REG(l, n) (0x22060 + ((l) * 0x400) + ((n) * 2)) +#define V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_4_REG(l, n) (0x22080 + ((l) * 0x400) + ((n) * 2)) + #define V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(n) (0x23840 + ((n) * 2)) /* n = 0 - 11 */ #define V4H_CORE_DIG_RW_COMMON_REG(n) (0x23880 + ((n) * 2)) /* n = 0 - 15 */ #define V4H_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_REG(n) (0x239e0 + ((n) * 2)) /* n = 0 - 3 */ -#define V4H_CORE_DIG_CLANE_1_RW_HS_TX_6_REG 0x2a60c /* V4H C-PHY */ #define V4H_CORE_DIG_RW_TRIO0_REG(n) (0x22100 + ((n) * 2)) /* n = 0 - 3 */ @@ -197,6 +196,7 @@ struct rcar_csi2; #define V4H_CORE_DIG_CLANE_1_RW_CFG_0_REG 0x2a400 #define V4H_CORE_DIG_CLANE_1_RW_LP_0_REG 0x2a480 #define V4H_CORE_DIG_CLANE_1_RW_HS_RX_REG(n) (0x2a500 + ((n) * 2)) /* n = 0 - 6 */ +#define V4H_CORE_DIG_CLANE_1_RW_HS_TX_6_REG 0x2a60c #define V4H_CORE_DIG_CLANE_2_RW_CFG_0_REG 0x2a800 #define V4H_CORE_DIG_CLANE_2_RW_LP_0_REG 0x2a880 #define V4H_CORE_DIG_CLANE_2_RW_HS_RX_REG(n) (0x2a900 + ((n) * 2)) /* n = 0 - 6 */ @@ -1246,11 +1246,11 @@ static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) rcsi2_write16(priv, V4H_CORE_DIG_CLANE_1_RW_HS_RX_REG(2), conf->rx2); rcsi2_write16(priv, V4H_CORE_DIG_CLANE_2_RW_HS_RX_REG(2), conf->rx2); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_REG(2), 0x0001); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_REG(2), 0); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_REG(2), 0x0001); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_REG(2), 0x0001); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_REG(2), 0); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 2), 0x0001); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(1, 2), 0); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(2, 2), 0x0001); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(3, 2), 0x0001); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(4, 2), 0); rcsi2_write16(priv, V4H_CORE_DIG_RW_TRIO0_REG(0), conf->trio0); rcsi2_write16(priv, V4H_CORE_DIG_RW_TRIO1_REG(0), conf->trio0); @@ -1267,13 +1267,13 @@ static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) /* Configure data line order. */ rsci2_set_line_order(priv, priv->line_orders[0], V4H_CORE_DIG_CLANE_0_RW_CFG_0_REG, - V4H_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_REG(9)); + V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 9)); rsci2_set_line_order(priv, priv->line_orders[1], V4H_CORE_DIG_CLANE_1_RW_CFG_0_REG, - V4H_CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_REG(9)); + V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(1, 9)); rsci2_set_line_order(priv, priv->line_orders[2], V4H_CORE_DIG_CLANE_2_RW_CFG_0_REG, - V4H_CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_REG(9)); + V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(2, 9)); /* TODO: This registers is not documented. */ rcsi2_write16(priv, V4H_CORE_DIG_CLANE_1_RW_HS_TX_6_REG, 0x5000); @@ -1289,8 +1289,8 @@ static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) } /* C-PHY setting - analog programing*/ - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_REG(9), conf->lane29); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_REG(7), conf->lane27); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 9), conf->lane29); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 7), conf->lane27); return 0; } From 6f9126b24b0a01219f706be7e3c6cd12dd2ce2cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 12 Jun 2025 19:59:03 +0200 Subject: [PATCH 013/310] media: rcar-csi2: Update start procedure for V4H MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare for adding D-PHY support to the driver by first updating the generic startup procedure that covers both C-PHY and D-PHY operations. The starting procedure where updated in later versions of the datasheet. Most of the configuration is only documented as tables of magic values in the documentation. Each step is however marked with a T marker, inject these markers in the comments to make it easier to map driver to documentation. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250612175904.1126717-4-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 84 +++++++++++++++------- 1 file changed, 58 insertions(+), 26 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index cdd358b4a973..7ba637d8683b 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -1199,7 +1199,8 @@ static int rcsi2_wait_phy_start_v4h(struct rcar_csi2 *priv, u32 match) return -ETIMEDOUT; } -static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) +static const struct rcsi2_cphy_setting * +rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) { const struct rcsi2_cphy_setting *conf; int msps; @@ -1214,7 +1215,7 @@ static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) if (!conf->msps) { dev_err(priv->dev, "Unsupported PHY speed for msps setting (%u Msps)", msps); - return -ERANGE; + return NULL; } /* C-PHY specific */ @@ -1278,27 +1279,14 @@ static int rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) /* TODO: This registers is not documented. */ rcsi2_write16(priv, V4H_CORE_DIG_CLANE_1_RW_HS_TX_6_REG, 0x5000); - /* Leave Shutdown mode */ - rcsi2_write(priv, V4H_DPHY_RSTZ_REG, BIT(0)); - rcsi2_write(priv, V4H_PHY_SHUTDOWNZ_REG, BIT(0)); - - /* Wait for calibration */ - if (rcsi2_wait_phy_start_v4h(priv, V4H_ST_PHYST_ST_PHY_READY)) { - dev_err(priv->dev, "PHY calibration failed\n"); - return -ETIMEDOUT; - } - - /* C-PHY setting - analog programing*/ - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 9), conf->lane29); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 7), conf->lane27); - - return 0; + return conf; } static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) { const struct rcar_csi2_format *format; + const struct rcsi2_cphy_setting *cphy; const struct v4l2_mbus_framefmt *fmt; unsigned int lanes; int mbps; @@ -1318,24 +1306,35 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, if (mbps < 0) return mbps; - /* Reset LINK and PHY*/ + /* T0: Reset LINK and PHY*/ rcsi2_write(priv, V4H_CSI2_RESETN_REG, 0); rcsi2_write(priv, V4H_DPHY_RSTZ_REG, 0); rcsi2_write(priv, V4H_PHY_SHUTDOWNZ_REG, 0); - /* PHY static setting */ - rcsi2_write(priv, V4H_PHY_EN_REG, V4H_PHY_EN_ENABLE_CLK); + /* T1: PHY static setting */ + rcsi2_write(priv, V4H_PHY_EN_REG, V4H_PHY_EN_ENABLE_CLK | + V4H_PHY_EN_ENABLE_0 | V4H_PHY_EN_ENABLE_1 | + V4H_PHY_EN_ENABLE_2 | V4H_PHY_EN_ENABLE_3); rcsi2_write(priv, V4H_FLDC_REG, 0); rcsi2_write(priv, V4H_FLDD_REG, 0); rcsi2_write(priv, V4H_IDIC_REG, 0); rcsi2_write(priv, V4H_PHY_MODE_REG, V4H_PHY_MODE_CPHY); rcsi2_write(priv, V4H_N_LANES_REG, lanes - 1); - /* Reset CSI2 */ + rcsi2_write(priv, V4M_FRXM_REG, + V4M_FRXM_FORCERXMODE_0 | V4M_FRXM_FORCERXMODE_1 | + V4M_FRXM_FORCERXMODE_2 | V4M_FRXM_FORCERXMODE_3); + rcsi2_write(priv, V4M_OVR1_REG, + V4M_OVR1_FORCERXMODE_0 | V4M_OVR1_FORCERXMODE_1 | + V4M_OVR1_FORCERXMODE_2 | V4M_OVR1_FORCERXMODE_3); + + /* T2: Reset CSI2 */ rcsi2_write(priv, V4H_CSI2_RESETN_REG, BIT(0)); /* Registers static setting through APB */ /* Common setting */ + rcsi2_write16(priv, V4H_PPI_STARTUP_RW_COMMON_DPHY_REG(10), 0x0030); + rcsi2_write16(priv, V4H_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_REG(2), 0x1444); rcsi2_write16(priv, V4H_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_REG(0), 0x1bfd); rcsi2_write16(priv, V4H_PPI_STARTUP_RW_COMMON_STARTUP_1_1_REG, 0x0233); rcsi2_write16(priv, V4H_PPI_STARTUP_RW_COMMON_DPHY_REG(6), 0x0027); @@ -1350,15 +1349,48 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, rcsi2_write16(priv, V4H_PPI_RW_LPDCOCAL_COARSE_CFG_REG, 0x0105); rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(6), 0x1000); rcsi2_write16(priv, V4H_PPI_RW_COMMON_CFG_REG, 0x0003); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(0), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(1), 0x0400); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(3), 0x41f6); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(0), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(3), 0x43f6); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(6), 0x3000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(7), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(6), 0x7000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(7), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(5), 0x4000); - /* C-PHY settings */ - ret = rcsi2_c_phy_setting_v4h(priv, mbps); - if (ret) - return ret; + /* T3: PHY settings */ + cphy = rcsi2_c_phy_setting_v4h(priv, mbps); + if (!cphy) + return -ERANGE; + /* T4: Leave Shutdown mode */ + rcsi2_write(priv, V4H_DPHY_RSTZ_REG, BIT(0)); + rcsi2_write(priv, V4H_PHY_SHUTDOWNZ_REG, BIT(0)); + + /* T5: Wait for calibration */ + if (rcsi2_wait_phy_start_v4h(priv, V4H_ST_PHYST_ST_PHY_READY)) { + dev_err(priv->dev, "PHY calibration failed\n"); + return -ETIMEDOUT; + } + + /* T6: Analog programming */ + for (unsigned int l = 0; l < 3; l++) { + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 9), + cphy->lane29); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 7), + cphy->lane27); + } + + /* T7: Wait for stop state */ rcsi2_wait_phy_start_v4h(priv, V4H_ST_PHYST_ST_STOPSTATE_0 | V4H_ST_PHYST_ST_STOPSTATE_1 | - V4H_ST_PHYST_ST_STOPSTATE_2); + V4H_ST_PHYST_ST_STOPSTATE_2 | + V4H_ST_PHYST_ST_STOPSTATE_3); + + /* T8: De-assert FRXM */ + rcsi2_write(priv, V4M_FRXM_REG, 0); return 0; } From 40823233ca30b89af8e5ccd58effddb997c98fb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 12 Jun 2025 19:59:04 +0200 Subject: [PATCH 014/310] media: rcar-csi2: Add D-PHY support for V4H MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add D-PHY support for V4H in addition to the already supported C-PHY. The common start-up procedure for C-PHY and D-PHY is shared, only PHY setup differ. Extend the V4H setup with D-PHY support as documented in the datasheet (Rev.1.21). Most of the start-up procedure is only documented as magic values in tables, there is little documentation to make the settings more clear. Wherever possible formulas or lookup tables are used as they are documented in the datasheet. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250612175904.1126717-5-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 230 ++++++++++++++++++++- 1 file changed, 220 insertions(+), 10 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 7ba637d8683b..0350eb76df36 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -172,6 +172,7 @@ struct rcar_csi2; #define V4H_PPI_RW_LPDCOCAL_TWAIT_CONFIG_REG 0x21c0a #define V4H_PPI_RW_LPDCOCAL_VT_CONFIG_REG 0x21c0c #define V4H_PPI_RW_LPDCOCAL_COARSE_CFG_REG 0x21c10 +#define V4H_PPI_RW_DDLCAL_CFG_n_REG(n) (0x21c40 + ((n) * 2)) /* n = 0 - 7 */ #define V4H_PPI_RW_COMMON_CFG_REG 0x21c6c #define V4H_PPI_RW_TERMCAL_CFG_0_REG 0x21c80 #define V4H_PPI_RW_OFFSETCAL_CFG_0_REG 0x21ca0 @@ -185,6 +186,13 @@ struct rcar_csi2; #define V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(n) (0x23840 + ((n) * 2)) /* n = 0 - 11 */ #define V4H_CORE_DIG_RW_COMMON_REG(n) (0x23880 + ((n) * 2)) /* n = 0 - 15 */ #define V4H_CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_REG(n) (0x239e0 + ((n) * 2)) /* n = 0 - 3 */ +#define V4H_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM_REG 0x23fe0 + +#define V4H_CORE_DIG_DLANE_l_RW_CFG_n_REG(l, n) (0x26000 + ((l) * 0x400) + ((n) * 2)) +#define V4H_CORE_DIG_DLANE_l_RW_LP_n_REG(l, n) (0x26080 + ((l) * 0x400) + ((n) * 2)) +#define V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, n) (0x26100 + ((l) * 0x400) + ((n) * 2)) +#define V4H_CORE_DIG_DLANE_CLK_RW_LP_n_REG(n) V4H_CORE_DIG_DLANE_l_RW_LP_n_REG(4, (n)) +#define V4H_CORE_DIG_DLANE_CLK_RW_HS_RX_n_REG(n) V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(4, (n)) /* V4H C-PHY */ #define V4H_CORE_DIG_RW_TRIO0_REG(n) (0x22100 + ((n) * 2)) /* n = 0 - 3 */ @@ -1282,11 +1290,193 @@ rcsi2_c_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) return conf; } +struct rcsi2_d_phy_setting_v4h_lut_value { + unsigned int mbps; + unsigned char cfg_1; + unsigned char cfg_5_94; + unsigned char cfg_5_30; + unsigned char lane_ctrl_2_8; + unsigned char rw_hs_rx_3_83; + unsigned char rw_hs_rx_3_20; + unsigned char rw_hs_rx_6; + unsigned char rw_hs_rx_1; +}; + +static const struct rcsi2_d_phy_setting_v4h_lut_value * +rcsi2_d_phy_setting_v4h_lut_lookup(int mbps) +{ + static const struct rcsi2_d_phy_setting_v4h_lut_value values[] = { + { 4500, 0x3f, 0x07, 0x00, 0x01, 0x02, 0x01, 0x0d, 0x10 }, + { 4000, 0x47, 0x08, 0x01, 0x01, 0x05, 0x01, 0x0f, 0x0d }, + { 3600, 0x4f, 0x09, 0x01, 0x01, 0x06, 0x01, 0x10, 0x0b }, + { 3230, 0x57, 0x0a, 0x01, 0x01, 0x06, 0x01, 0x12, 0x09 }, + { 3000, 0x47, 0x08, 0x00, 0x00, 0x03, 0x01, 0x0f, 0x0c }, + { 2700, 0x4f, 0x09, 0x01, 0x00, 0x06, 0x01, 0x10, 0x0b }, + { 2455, 0x57, 0x0a, 0x01, 0x00, 0x06, 0x01, 0x12, 0x09 }, + { 2250, 0x5f, 0x0b, 0x01, 0x00, 0x08, 0x01, 0x13, 0x08 }, + { 2077, 0x67, 0x0c, 0x01, 0x00, 0x06, 0x02, 0x15, 0x0d }, + { 1929, 0x6f, 0x0d, 0x02, 0x00, 0x06, 0x02, 0x17, 0x0d }, + { 1800, 0x77, 0x0e, 0x02, 0x00, 0x06, 0x02, 0x18, 0x0d }, + { 1688, 0x7f, 0x0f, 0x02, 0x00, 0x08, 0x02, 0x1a, 0x0d }, + { 1588, 0x87, 0x10, 0x02, 0x00, 0x08, 0x02, 0x1b, 0x0d }, + { 1500, 0x8f, 0x11, 0x03, 0x00, 0x08, 0x02, 0x1d, 0x0c }, + }; + + for (unsigned int i = 0; i < ARRAY_SIZE(values); i++) + if (mbps >= values[i].mbps) + return &values[i]; + + return NULL; +} + +static int rcsi2_d_phy_setting_v4h(struct rcar_csi2 *priv, int mbps) +{ + const struct rcsi2_d_phy_setting_v4h_lut_value *lut = + rcsi2_d_phy_setting_v4h_lut_lookup(mbps); + u16 val; + + rcsi2_write16(priv, V4H_CORE_DIG_RW_COMMON_REG(7), 0x0000); + rcsi2_write16(priv, V4H_PPI_STARTUP_RW_COMMON_DPHY_REG(7), mbps > 1500 ? 0x0028 : 0x0068); + rcsi2_write16(priv, V4H_PPI_STARTUP_RW_COMMON_DPHY_REG(8), 0x0050); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(0), 0x0063); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(7), 0x1132); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(1), 0x1340); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(2), 0x4b13); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(4), 0x000a); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(6), 0x800a); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(7), 0x1109); + + if (mbps > 1500) { + val = DIV_ROUND_UP(5 * mbps, 64); + rcsi2_write16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(3), val); + } + + if (lut) { + rcsi2_modify16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(1), + lut->cfg_1, 0x00ff); + rcsi2_modify16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(5), + lut->cfg_5_94 << 4, 0x03f0); + rcsi2_modify16(priv, V4H_PPI_RW_DDLCAL_CFG_n_REG(5), + lut->cfg_5_30 << 0, 0x000f); + + for (unsigned int l = 0; l < 5; l++) + rcsi2_modify16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 8), + lut->lane_ctrl_2_8 << 12, 0x1000); + } + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_LP_n_REG(l, 0), 0x463c); + + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(0, 2), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(1, 2), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(2, 2), 0x0001); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(3, 2), 0x0000); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(4, 2), 0x0000); + + rcsi2_write16(priv, V4H_CORE_DIG_RW_COMMON_REG(6), 0x0009); + + val = mbps > 1500 ? 0x0800 : 0x0802; + for (unsigned int l = 0; l < 5; l++) + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 12), val); + + val = mbps > 1500 ? 0x0000 : 0x0002; + for (unsigned int l = 0; l < 5; l++) + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 13), val); + + if (mbps >= 80) { + /* 2560: 6, 1280: 5, 640: 4, 320: 3, 160: 2, 80: 1 */ + val = ilog2(mbps / 80) + 1; + rcsi2_modify16(priv, + V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(2, 9), + val << 5, 0xe0); + } + + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_CLK_RW_HS_RX_n_REG(0), 0x091c); + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_CLK_RW_HS_RX_n_REG(7), 0x3b06); + + val = DIV_ROUND_UP(1200, mbps) + 12; + for (unsigned int l = 0; l < 4; l++) + rcsi2_modify16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 0), val << 8, 0xf0); + + val = mbps > 1500 ? 0x0004 : 0x0008; + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_CFG_n_REG(l, 1), val); + + val = mbps > 2500 ? 0x669a : mbps > 1500 ? 0xe69a : 0xe69b; + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 2), val); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_LP_n_REG(l, 0), 0x163c); + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_CLK_RW_LP_n_REG(0), 0x163c); + + if (lut) { + for (unsigned int l = 0; l < 4; l++) + rcsi2_modify16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 1), + lut->rw_hs_rx_1, 0xff); + } + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 3), 0x9209); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 4), 0x0096); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 5), 0x0100); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 6), 0x2d02); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_write16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 7), 0x1b06); + + if (lut) { + /* + * Documentation LUT have two values but document writing both + * values in a single write. + */ + for (unsigned int l = 0; l < 4; l++) + rcsi2_modify16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 3), + lut->rw_hs_rx_3_83 << 3 | lut->rw_hs_rx_3_20, 0x1ff); + + for (unsigned int l = 0; l < 4; l++) + rcsi2_modify16(priv, V4H_CORE_DIG_DLANE_l_RW_HS_RX_n_REG(l, 6), + lut->rw_hs_rx_6 << 8, 0xff00); + } + + static const u16 deskew_fine[] = { + 0x0404, 0x040c, 0x0414, 0x041c, 0x0423, 0x0429, 0x0430, 0x043a, + 0x0445, 0x044a, 0x0450, 0x045a, 0x0465, 0x0469, 0x0472, 0x047a, + 0x0485, 0x0489, 0x0490, 0x049a, 0x04a4, 0x04ac, 0x04b4, 0x04bc, + 0x04c4, 0x04cc, 0x04d4, 0x04dc, 0x04e4, 0x04ec, 0x04f4, 0x04fc, + 0x0504, 0x050c, 0x0514, 0x051c, 0x0523, 0x0529, 0x0530, 0x053a, + 0x0545, 0x054a, 0x0550, 0x055a, 0x0565, 0x0569, 0x0572, 0x057a, + 0x0585, 0x0589, 0x0590, 0x059a, 0x05a4, 0x05ac, 0x05b4, 0x05bc, + 0x05c4, 0x05cc, 0x05d4, 0x05dc, 0x05e4, 0x05ec, 0x05f4, 0x05fc, + 0x0604, 0x060c, 0x0614, 0x061c, 0x0623, 0x0629, 0x0632, 0x063a, + 0x0645, 0x064a, 0x0650, 0x065a, 0x0665, 0x0669, 0x0672, 0x067a, + 0x0685, 0x0689, 0x0690, 0x069a, 0x06a4, 0x06ac, 0x06b4, 0x06bc, + 0x06c4, 0x06cc, 0x06d4, 0x06dc, 0x06e4, 0x06ec, 0x06f4, 0x06fc, + 0x0704, 0x070c, 0x0714, 0x071c, 0x0723, 0x072a, 0x0730, 0x073a, + 0x0745, 0x074a, 0x0750, 0x075a, 0x0765, 0x0769, 0x0772, 0x077a, + 0x0785, 0x0789, 0x0790, 0x079a, 0x07a4, 0x07ac, 0x07b4, 0x07bc, + 0x07c4, 0x07cc, 0x07d4, 0x07dc, 0x07e4, 0x07ec, 0x07f4, 0x07fc, + }; + + for (unsigned int i = 0; i < ARRAY_SIZE(deskew_fine); i++) { + rcsi2_write16(priv, V4H_CORE_DIG_COMMON_RW_DESKEW_FINE_MEM_REG, + deskew_fine[i]); + } + + return 0; +} + static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, struct v4l2_subdev_state *state) { + const struct rcsi2_cphy_setting *cphy = NULL; const struct rcar_csi2_format *format; - const struct rcsi2_cphy_setting *cphy; const struct v4l2_mbus_framefmt *fmt; unsigned int lanes; int mbps; @@ -1318,7 +1508,8 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, rcsi2_write(priv, V4H_FLDC_REG, 0); rcsi2_write(priv, V4H_FLDD_REG, 0); rcsi2_write(priv, V4H_IDIC_REG, 0); - rcsi2_write(priv, V4H_PHY_MODE_REG, V4H_PHY_MODE_CPHY); + rcsi2_write(priv, V4H_PHY_MODE_REG, + priv->cphy ? V4H_PHY_MODE_CPHY : V4H_PHY_MODE_DPHY); rcsi2_write(priv, V4H_N_LANES_REG, lanes - 1); rcsi2_write(priv, V4M_FRXM_REG, @@ -1361,9 +1552,15 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_REG(5), 0x4000); /* T3: PHY settings */ - cphy = rcsi2_c_phy_setting_v4h(priv, mbps); - if (!cphy) - return -ERANGE; + if (priv->cphy) { + cphy = rcsi2_c_phy_setting_v4h(priv, mbps); + if (!cphy) + return -ERANGE; + } else { + ret = rcsi2_d_phy_setting_v4h(priv, mbps); + if (ret) + return ret; + } /* T4: Leave Shutdown mode */ rcsi2_write(priv, V4H_DPHY_RSTZ_REG, BIT(0)); @@ -1376,11 +1573,23 @@ static int rcsi2_start_receiver_v4h(struct rcar_csi2 *priv, } /* T6: Analog programming */ - for (unsigned int l = 0; l < 3; l++) { - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 9), - cphy->lane29); - rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 7), - cphy->lane27); + if (priv->cphy) { + for (unsigned int l = 0; l < 3; l++) { + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 9), + cphy->lane29); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 7), + cphy->lane27); + } + } else { + u16 val_2_9 = mbps > 2500 ? 0x14 : mbps > 1500 ? 0x04 : 0x00; + u16 val_2_15 = mbps > 1500 ? 0x03 : 0x00; + + for (unsigned int l = 0; l < 5; l++) { + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 9), + val_2_9); + rcsi2_write16(priv, V4H_CORE_DIG_IOCTRL_RW_AFE_LANEl_CTRL_2_REG(l, 15), + val_2_15); + } } /* T7: Wait for stop state */ @@ -2245,6 +2454,7 @@ static const struct rcar_csi2_info rcar_csi2_info_r8a779g0 = { .start_receiver = rcsi2_start_receiver_v4h, .use_isp = true, .support_cphy = true, + .support_dphy = true, }; static const struct rcsi2_register_layout rcsi2_registers_v4m = { From 1b83fa6eb9706be4023bce88e952902acf8646ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:22 +0200 Subject: [PATCH 015/310] media: rcar-vin: Use correct count of remote subdevices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When extending the driver with Gen4 support the iteration of over possible remote subdevices changed from being R-Car CSI-2 Rx only to also cover R-Car CSISP instances. In two loops updating the bounds variable was missed. This had no ill effect as the count the two values have always been the same in the past. Fix it by looking at the array size. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-2-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 846ae7989b1d..cf5830d7d7b1 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -213,7 +213,7 @@ static int rvin_group_entity_to_remote_id(struct rvin_group *group, sd = media_entity_to_v4l2_subdev(entity); - for (i = 0; i < RVIN_REMOTES_MAX; i++) + for (i = 0; i < ARRAY_SIZE(group->remotes); i++) if (group->remotes[i].subdev == sd) return i; @@ -262,7 +262,7 @@ static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, mutex_lock(&vin->group->lock); - for (i = 0; i < RVIN_CSI_MAX; i++) { + for (i = 0; i < ARRAY_SIZE(vin->group->remotes); i++) { if (vin->group->remotes[i].asc != asc) continue; vin->group->remotes[i].subdev = NULL; @@ -284,7 +284,7 @@ static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, mutex_lock(&vin->group->lock); - for (i = 0; i < RVIN_CSI_MAX; i++) { + for (i = 0; i < ARRAY_SIZE(vin->group->remotes); i++) { if (vin->group->remotes[i].asc != asc) continue; vin->group->remotes[i].subdev = subdev; From df45bd925efe8b7708cbc549cffa81fcb0e9586e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:23 +0200 Subject: [PATCH 016/310] media: rcar-vin: Store platform info with group structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the transition of Gen2 to use groups are complete the platform specific information can be retrieved from the group instead of being duplicated in each VIN's private data structure. Prepare for this by already adding the information to the group structure so it can be used without first having to find the group from a VIN instances private data. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-3-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-core.c | 1 + drivers/media/platform/renesas/rcar-vin/rcar-vin.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index cf5830d7d7b1..66efe075adae 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -156,6 +156,7 @@ static int rvin_group_get(struct rvin_dev *vin, } kref_init(&group->refcount); + group->info = vin->info; rvin_group_data = group; } diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 83d1b2734c41..313703cd1103 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -242,6 +242,7 @@ struct rvin_dev { * @lock: protects the count, notifier, vin and csi members * @count: number of enabled VIN instances found in DT * @notifier: group notifier for CSI-2 async connections + * @info: Platform dependent information about the VIN instances * @vin: VIN instances which are part of the group * @link_setup: Callback to create all links for the media graph * @remotes: array of pairs of async connection and subdev pointers @@ -255,6 +256,7 @@ struct rvin_group { struct mutex lock; unsigned int count; struct v4l2_async_notifier notifier; + const struct rvin_info *info; struct rvin_dev *vin[RCAR_VIN_NUM]; int (*link_setup)(struct rvin_dev *vin); From bad694f877954a0daa9eda02b935c983725d5f6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:24 +0200 Subject: [PATCH 017/310] media: rcar-vin: Change link setup argument MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The link setup callback once acted on each VIN instance, and expected to be called once for each VIN instance. This have changed as the driver grew support for later hardware generations and the callback is now expected to setup links for all VIN in the group. The argument to the callback has however remained a pointer to a single VIN instance. This pointer was then used to get the group structure. Fix this and pass the group as the single argument to the link setup callback making the expectation of the function clear. There is no intentional change in behavior. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-4-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 37 ++++++++++--------- .../platform/renesas/rcar-vin/rcar-vin.h | 2 +- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 66efe075adae..73d713868391 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -65,7 +65,7 @@ static void rvin_group_cleanup(struct rvin_group *group) } static int rvin_group_init(struct rvin_group *group, struct rvin_dev *vin, - int (*link_setup)(struct rvin_dev *), + int (*link_setup)(struct rvin_group *), const struct media_device_ops *ops) { struct media_device *mdev = &group->mdev; @@ -115,7 +115,7 @@ static void rvin_group_release(struct kref *kref) } static int rvin_group_get(struct rvin_dev *vin, - int (*link_setup)(struct rvin_dev *), + int (*link_setup)(struct rvin_group *), const struct media_device_ops *ops) { struct rvin_group *group; @@ -247,7 +247,7 @@ static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) } } - return vin->group->link_setup(vin); + return vin->group->link_setup(vin->group); } static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, @@ -910,35 +910,35 @@ static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id, return 0; } -static int rvin_csi2_setup_links(struct rvin_dev *vin) +static int rvin_csi2_setup_links(struct rvin_group *group) { const struct rvin_group_route *route; unsigned int id; int ret = -EINVAL; /* Create all media device links between VINs and CSI-2's. */ - mutex_lock(&vin->group->lock); - for (route = vin->info->routes; route->chsel; route++) { + mutex_lock(&group->lock); + for (route = group->info->routes; route->chsel; route++) { /* Check that VIN' master is part of the group. */ - if (!vin->group->vin[route->master]) + if (!group->vin[route->master]) continue; /* Check that CSI-2 is part of the group. */ - if (!vin->group->remotes[route->csi].subdev) + if (!group->remotes[route->csi].subdev) continue; for (id = route->master; id < route->master + 4; id++) { /* Check that VIN is part of the group. */ - if (!vin->group->vin[id]) + if (!group->vin[id]) continue; - ret = rvin_csi2_create_link(vin->group, id, route); + ret = rvin_csi2_create_link(group, id, route); if (ret) goto out; } } out: - mutex_unlock(&vin->group->lock); + mutex_unlock(&group->lock); return ret; } @@ -992,30 +992,31 @@ static int rvin_csi2_init(struct rvin_dev *vin) * ISP */ -static int rvin_isp_setup_links(struct rvin_dev *vin) +static int rvin_isp_setup_links(struct rvin_group *group) { unsigned int i; int ret = -EINVAL; /* Create all media device links between VINs and ISP's. */ - mutex_lock(&vin->group->lock); + mutex_lock(&group->lock); for (i = 0; i < RCAR_VIN_NUM; i++) { struct media_pad *source_pad, *sink_pad; struct media_entity *source, *sink; unsigned int source_slot = i / 8; unsigned int source_idx = i % 8 + 1; + struct rvin_dev *vin = group->vin[i]; - if (!vin->group->vin[i]) + if (!vin) continue; /* Check that ISP is part of the group. */ - if (!vin->group->remotes[source_slot].subdev) + if (!group->remotes[source_slot].subdev) continue; - source = &vin->group->remotes[source_slot].subdev->entity; + source = &group->remotes[source_slot].subdev->entity; source_pad = &source->pads[source_idx]; - sink = &vin->group->vin[i]->vdev.entity; + sink = &vin->vdev.entity; sink_pad = &sink->pads[0]; /* Skip if link already exists. */ @@ -1031,7 +1032,7 @@ static int rvin_isp_setup_links(struct rvin_dev *vin) break; } } - mutex_unlock(&vin->group->lock); + mutex_unlock(&group->lock); return ret; } diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 313703cd1103..cb8e8fa54f96 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -259,7 +259,7 @@ struct rvin_group { const struct rvin_info *info; struct rvin_dev *vin[RCAR_VIN_NUM]; - int (*link_setup)(struct rvin_dev *vin); + int (*link_setup)(struct rvin_group *group); struct { struct v4l2_async_connection *asc; From b38ba9b160e0605cb500059e48ba3d1fc92fef19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:25 +0200 Subject: [PATCH 018/310] media: rcar-vin: Use error labels in probe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare for more failed probe conditions that needs cleanup by converting the error path to use labels. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-5-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-core.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 73d713868391..59751ec23a15 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -1405,15 +1405,18 @@ static int rcar_vin_probe(struct platform_device *pdev) vin->scaler = vin->info->scaler; } - if (ret) { - rvin_dma_unregister(vin); - return ret; - } + if (ret) + goto err_dma; pm_suspend_ignore_children(&pdev->dev, true); pm_runtime_enable(&pdev->dev); return 0; + +err_dma: + rvin_dma_unregister(vin); + + return ret; } static void rcar_vin_remove(struct platform_device *pdev) From d568581b07f4ace5916e9b8b85fa2100f5716952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:26 +0200 Subject: [PATCH 019/310] media: rcar-vin: Generate a VIN group ID for Gen2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare to move Gen2 and earlier models to media controller by generating a unique VIN group id for each VIN instance. On Gen3 and Gen4 it is important to have a specific id in the group as media graph routes depend on this. On Gen2 and earlier models all that will matter is to have a unique id in the range. Break out the id generation to a own function keeping the logic for Gen3 and Gen4 while generating a sequential id for Gen2 models. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-6-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 85 ++++++++++++++----- 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 59751ec23a15..55957dd95576 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -10,6 +10,7 @@ * Based on the soc-camera rcar_vin driver */ +#include #include #include #include @@ -55,6 +56,7 @@ * be only one group for all instances. */ +static DEFINE_IDA(rvin_ida); static DEFINE_MUTEX(rvin_group_lock); static struct rvin_group *rvin_group_data; @@ -119,23 +121,8 @@ static int rvin_group_get(struct rvin_dev *vin, const struct media_device_ops *ops) { struct rvin_group *group; - u32 id; int ret; - /* Make sure VIN id is present and sane */ - ret = of_property_read_u32(vin->dev->of_node, "renesas,id", &id); - if (ret) { - vin_err(vin, "%pOF: No renesas,id property found\n", - vin->dev->of_node); - return -EINVAL; - } - - if (id >= RCAR_VIN_NUM) { - vin_err(vin, "%pOF: Invalid renesas,id '%u'\n", - vin->dev->of_node, id); - return -EINVAL; - } - /* Join or create a VIN group */ mutex_lock(&rvin_group_lock); if (rvin_group_data) { @@ -165,16 +152,15 @@ static int rvin_group_get(struct rvin_dev *vin, /* Add VIN to group */ mutex_lock(&group->lock); - if (group->vin[id]) { - vin_err(vin, "Duplicate renesas,id property value %u\n", id); + if (group->vin[vin->id]) { + vin_err(vin, "Duplicate renesas,id property value %u\n", vin->id); mutex_unlock(&group->lock); kref_put(&group->refcount, rvin_group_release); return -EINVAL; } - group->vin[id] = vin; + group->vin[vin->id] = vin; - vin->id = id; vin->group = group; vin->v4l2_dev.mdev = &group->mdev; @@ -1363,6 +1349,56 @@ static const struct of_device_id rvin_of_id_table[] = { }; MODULE_DEVICE_TABLE(of, rvin_of_id_table); +static int rvin_id_get(struct rvin_dev *vin) +{ + u32 oid; + int id; + + switch (vin->info->model) { + case RCAR_GEN3: + case RCAR_GEN4: + if (of_property_read_u32(vin->dev->of_node, "renesas,id", &oid)) { + vin_err(vin, "%pOF: No renesas,id property found\n", + vin->dev->of_node); + return -EINVAL; + } + + if (oid < 0 || oid >= RCAR_VIN_NUM) { + vin_err(vin, "%pOF: Invalid renesas,id '%u'\n", + vin->dev->of_node, oid); + return -EINVAL; + } + + vin->id = oid; + break; + default: + id = ida_alloc_range(&rvin_ida, 0, RCAR_VIN_NUM - 1, + GFP_KERNEL); + if (id < 0) { + vin_err(vin, "%pOF: Failed to allocate VIN group ID\n", + vin->dev->of_node); + return -EINVAL; + } + + vin->id = id; + break; + } + + return 0; +} + +static void rvin_id_put(struct rvin_dev *vin) +{ + switch (vin->info->model) { + case RCAR_GEN3: + case RCAR_GEN4: + break; + default: + ida_free(&rvin_ida, vin->id); + break; + } +} + static int rcar_vin_probe(struct platform_device *pdev) { struct rvin_dev *vin; @@ -1390,6 +1426,11 @@ static int rcar_vin_probe(struct platform_device *pdev) platform_set_drvdata(pdev, vin); + if (rvin_id_get(vin)) { + ret = -EINVAL; + goto err_dma; + } + if (vin->info->use_isp) { ret = rvin_isp_init(vin); } else if (vin->info->use_mc) { @@ -1406,13 +1447,15 @@ static int rcar_vin_probe(struct platform_device *pdev) } if (ret) - goto err_dma; + goto err_id; pm_suspend_ignore_children(&pdev->dev, true); pm_runtime_enable(&pdev->dev); return 0; +err_id: + rvin_id_put(vin); err_dma: rvin_dma_unregister(vin); @@ -1434,6 +1477,8 @@ static void rcar_vin_remove(struct platform_device *pdev) else rvin_parallel_cleanup(vin); + rvin_id_put(vin); + rvin_dma_unregister(vin); } From a3e4aad92429f8b66ef6492d4e5caff9d11a94ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:27 +0200 Subject: [PATCH 020/310] media: rcar-vin: Prepare for unifying all v4l-async notifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The R-Car VIN driver is needlessly complex and uses more then one v4l-async notifier to attach to all its subdevices. Prepare for unifying them by moving rvin_parallel_parse_of() to where it needs to be when they are unified. The function is moved verbatim and there is no change in behavior. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-7-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 106 +++++++++--------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 55957dd95576..95621c11eecc 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -338,6 +338,59 @@ static void rvin_group_notifier_cleanup(struct rvin_dev *vin) } } +static int rvin_parallel_parse_of(struct rvin_dev *vin) +{ + struct fwnode_handle *ep, *fwnode; + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_UNKNOWN, + }; + struct v4l2_async_connection *asc; + int ret; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), 0, 0, 0); + if (!ep) + return 0; + + fwnode = fwnode_graph_get_remote_endpoint(ep); + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + fwnode_handle_put(ep); + if (ret) { + vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); + ret = -EINVAL; + goto out; + } + + switch (vep.bus_type) { + case V4L2_MBUS_PARALLEL: + case V4L2_MBUS_BT656: + vin_dbg(vin, "Found %s media bus\n", + vep.bus_type == V4L2_MBUS_PARALLEL ? + "PARALLEL" : "BT656"); + vin->parallel.mbus_type = vep.bus_type; + vin->parallel.bus = vep.bus.parallel; + break; + default: + vin_err(vin, "Unknown media bus type\n"); + ret = -EINVAL; + goto out; + } + + asc = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, + struct v4l2_async_connection); + if (IS_ERR(asc)) { + ret = PTR_ERR(asc); + goto out; + } + + vin->parallel.asc = asc; + + vin_dbg(vin, "Add parallel OF device %pOF\n", to_of_node(fwnode)); +out: + fwnode_handle_put(fwnode); + + return ret; +} + static int rvin_group_notifier_init(struct rvin_dev *vin, unsigned int port, unsigned int max_id) { @@ -636,59 +689,6 @@ static const struct v4l2_async_notifier_operations rvin_parallel_notify_ops = { .complete = rvin_parallel_notify_complete, }; -static int rvin_parallel_parse_of(struct rvin_dev *vin) -{ - struct fwnode_handle *ep, *fwnode; - struct v4l2_fwnode_endpoint vep = { - .bus_type = V4L2_MBUS_UNKNOWN, - }; - struct v4l2_async_connection *asc; - int ret; - - ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), 0, 0, 0); - if (!ep) - return 0; - - fwnode = fwnode_graph_get_remote_endpoint(ep); - ret = v4l2_fwnode_endpoint_parse(ep, &vep); - fwnode_handle_put(ep); - if (ret) { - vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); - ret = -EINVAL; - goto out; - } - - switch (vep.bus_type) { - case V4L2_MBUS_PARALLEL: - case V4L2_MBUS_BT656: - vin_dbg(vin, "Found %s media bus\n", - vep.bus_type == V4L2_MBUS_PARALLEL ? - "PARALLEL" : "BT656"); - vin->parallel.mbus_type = vep.bus_type; - vin->parallel.bus = vep.bus.parallel; - break; - default: - vin_err(vin, "Unknown media bus type\n"); - ret = -EINVAL; - goto out; - } - - asc = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, - struct v4l2_async_connection); - if (IS_ERR(asc)) { - ret = PTR_ERR(asc); - goto out; - } - - vin->parallel.asc = asc; - - vin_dbg(vin, "Add parallel OF device %pOF\n", to_of_node(fwnode)); -out: - fwnode_handle_put(fwnode); - - return ret; -} - static void rvin_parallel_cleanup(struct rvin_dev *vin) { v4l2_async_nf_unregister(&vin->notifier); From 11e14a525765db6a71c5c9291140a28682ff3b1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:28 +0200 Subject: [PATCH 021/310] media: rcar-vin: Improve error paths for parallel devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use the __free(fwnode_handle) hooks to free the endpoints when the function exits to simplify the error paths and make the intent more clear. While at it correct the error message when failing to parse an endpoint to report the correct node. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-8-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 28 +++++++------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 95621c11eecc..28070ada7f60 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -340,24 +340,20 @@ static void rvin_group_notifier_cleanup(struct rvin_dev *vin) static int rvin_parallel_parse_of(struct rvin_dev *vin) { - struct fwnode_handle *ep, *fwnode; + struct fwnode_handle *fwnode __free(fwnode_handle) = NULL; + struct fwnode_handle *ep __free(fwnode_handle) = NULL; struct v4l2_fwnode_endpoint vep = { .bus_type = V4L2_MBUS_UNKNOWN, }; struct v4l2_async_connection *asc; - int ret; ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(vin->dev), 0, 0, 0); if (!ep) return 0; - fwnode = fwnode_graph_get_remote_endpoint(ep); - ret = v4l2_fwnode_endpoint_parse(ep, &vep); - fwnode_handle_put(ep); - if (ret) { - vin_err(vin, "Failed to parse %pOF\n", to_of_node(fwnode)); - ret = -EINVAL; - goto out; + if (v4l2_fwnode_endpoint_parse(ep, &vep)) { + vin_err(vin, "Failed to parse %pOF\n", to_of_node(ep)); + return -EINVAL; } switch (vep.bus_type) { @@ -371,24 +367,20 @@ static int rvin_parallel_parse_of(struct rvin_dev *vin) break; default: vin_err(vin, "Unknown media bus type\n"); - ret = -EINVAL; - goto out; + return -EINVAL; } + fwnode = fwnode_graph_get_remote_endpoint(ep); asc = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, struct v4l2_async_connection); - if (IS_ERR(asc)) { - ret = PTR_ERR(asc); - goto out; - } + if (IS_ERR(asc)) + return PTR_ERR(asc); vin->parallel.asc = asc; vin_dbg(vin, "Add parallel OF device %pOF\n", to_of_node(fwnode)); -out: - fwnode_handle_put(fwnode); - return ret; + return 0; } static int rvin_group_notifier_init(struct rvin_dev *vin, unsigned int port, From 856b49c71ae58adeb0836c30a22c830593583141 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:29 +0200 Subject: [PATCH 022/310] media: rcar-vin: Merge all notifiers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The VIN usage of v4l-async is complex and stems from organic growth of the driver of supporting both private local subdevices (Gen2, Gen3) and subdevices shared between all VIN instances (Gen3 and Gen4). The driver used a separate notifier for each VIN for the private local ones, and a shared group notifier for the shared ones. This was complex and lead to subtle bugs when unbinding and later rebinding subdevices in one of the notifiers having to handle different edge cases depending on if it also had subdevices in the other notifiers etc. To simplify this have the Gen2 devices allocate and form a VIN group too. This way all subdevices on all models can be collect in a single group notifier. Then there is only a single complete callback for all where the video devices and subdevice nodes can be registered etc. Signed-off-by: Niklas Söderlund Tested-by: Tomi Valkeinen Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-9-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 260 ++++++++---------- .../platform/renesas/rcar-vin/rcar-vin.h | 2 - 2 files changed, 108 insertions(+), 154 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 28070ada7f60..b3558c3c44d1 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -43,6 +43,9 @@ #define v4l2_dev_to_vin(d) container_of(d, struct rvin_dev, v4l2_dev) +static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, + struct v4l2_subdev *subdev); + /* ----------------------------------------------------------------------------- * Gen3 Group Allocator */ @@ -233,7 +236,10 @@ static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) } } - return vin->group->link_setup(vin->group); + if (vin->group->link_setup) + return vin->group->link_setup(vin->group); + + return 0; } static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, @@ -241,20 +247,32 @@ static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, struct v4l2_async_connection *asc) { struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - unsigned int i; + struct rvin_group *group = vin->group; - for (i = 0; i < RCAR_VIN_NUM; i++) - if (vin->group->vin[i]) - rvin_v4l2_unregister(vin->group->vin[i]); + for (unsigned int i = 0; i < RCAR_VIN_NUM; i++) { + if (group->vin[i]) + rvin_v4l2_unregister(group->vin[i]); + } mutex_lock(&vin->group->lock); - for (i = 0; i < ARRAY_SIZE(vin->group->remotes); i++) { - if (vin->group->remotes[i].asc != asc) + for (unsigned int i = 0; i < RCAR_VIN_NUM; i++) { + if (!group->vin[i] || group->vin[i]->parallel.asc != asc) continue; - vin->group->remotes[i].subdev = NULL; + + group->vin[i]->parallel.subdev = NULL; + + vin_dbg(group->vin[i], "Unbind parallel subdev %s\n", + subdev->name); + } + + for (unsigned int i = 0; i < ARRAY_SIZE(group->remotes); i++) { + if (group->remotes[i].asc != asc) + continue; + + group->remotes[i].subdev = NULL; + vin_dbg(vin, "Unbind %s from slot %u\n", subdev->name, i); - break; } mutex_unlock(&vin->group->lock); @@ -267,21 +285,38 @@ static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, struct v4l2_async_connection *asc) { struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - unsigned int i; + struct rvin_group *group = vin->group; - mutex_lock(&vin->group->lock); + guard(mutex)(&group->lock); - for (i = 0; i < ARRAY_SIZE(vin->group->remotes); i++) { - if (vin->group->remotes[i].asc != asc) + for (unsigned int i = 0; i < RCAR_VIN_NUM; i++) { + int ret; + + if (!group->vin[i] || group->vin[i]->parallel.asc != asc) continue; - vin->group->remotes[i].subdev = subdev; - vin_dbg(vin, "Bound %s to slot %u\n", subdev->name, i); - break; + + ret = rvin_parallel_subdevice_attach(group->vin[i], subdev); + if (ret) + return ret; + + v4l2_set_subdev_hostdata(subdev, group->vin[i]); + + vin_dbg(group->vin[i], "Bound subdev %s\n", subdev->name); + + return 0; } - mutex_unlock(&vin->group->lock); + for (unsigned int i = 0; i < ARRAY_SIZE(group->remotes); i++) { + if (vin->group->remotes[i].asc != asc) + continue; - return 0; + vin->group->remotes[i].subdev = subdev; + vin_dbg(vin, "Bound %s to slot %u\n", subdev->name, i); + + return 0; + } + + return -ENODEV; } static const struct v4l2_async_notifier_operations rvin_group_notify_ops = { @@ -371,7 +406,7 @@ static int rvin_parallel_parse_of(struct rvin_dev *vin) } fwnode = fwnode_graph_get_remote_endpoint(ep); - asc = v4l2_async_nf_add_fwnode(&vin->notifier, fwnode, + asc = v4l2_async_nf_add_fwnode(&vin->group->notifier, fwnode, struct v4l2_async_connection); if (IS_ERR(asc)) return PTR_ERR(asc); @@ -417,6 +452,12 @@ static int rvin_group_notifier_init(struct rvin_dev *vin, unsigned int port, if (!(vin_mask & BIT(i))) continue; + /* Parse local subdevice. */ + ret = rvin_parallel_parse_of(vin->group->vin[i]); + if (ret) + return ret; + + /* Parse shared subdevices. */ for (id = 0; id < max_id; id++) { if (vin->group->remotes[id].asc) continue; @@ -596,124 +637,6 @@ static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, return 0; } -static void rvin_parallel_subdevice_detach(struct rvin_dev *vin) -{ - rvin_v4l2_unregister(vin); - vin->parallel.subdev = NULL; - - if (!vin->info->use_mc) - rvin_free_controls(vin); -} - -static int rvin_parallel_notify_complete(struct v4l2_async_notifier *notifier) -{ - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - struct media_entity *source; - struct media_entity *sink; - int ret; - - ret = v4l2_device_register_subdev_nodes(&vin->v4l2_dev); - if (ret < 0) { - vin_err(vin, "Failed to register subdev nodes\n"); - return ret; - } - - if (!video_is_registered(&vin->vdev)) { - ret = rvin_v4l2_register(vin); - if (ret < 0) - return ret; - } - - if (!vin->info->use_mc) - return 0; - - /* If we're running with media-controller, link the subdevs. */ - source = &vin->parallel.subdev->entity; - sink = &vin->vdev.entity; - - ret = media_create_pad_link(source, vin->parallel.source_pad, - sink, vin->parallel.sink_pad, 0); - if (ret) - vin_err(vin, "Error adding link from %s to %s: %d\n", - source->name, sink->name, ret); - - return ret; -} - -static void rvin_parallel_notify_unbind(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_connection *asc) -{ - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - - vin_dbg(vin, "unbind parallel subdev %s\n", subdev->name); - - mutex_lock(&vin->lock); - rvin_parallel_subdevice_detach(vin); - mutex_unlock(&vin->lock); -} - -static int rvin_parallel_notify_bound(struct v4l2_async_notifier *notifier, - struct v4l2_subdev *subdev, - struct v4l2_async_connection *asc) -{ - struct rvin_dev *vin = v4l2_dev_to_vin(notifier->v4l2_dev); - int ret; - - mutex_lock(&vin->lock); - ret = rvin_parallel_subdevice_attach(vin, subdev); - mutex_unlock(&vin->lock); - if (ret) - return ret; - - v4l2_set_subdev_hostdata(subdev, vin); - - vin_dbg(vin, "bound subdev %s source pad: %u sink pad: %u\n", - subdev->name, vin->parallel.source_pad, - vin->parallel.sink_pad); - - return 0; -} - -static const struct v4l2_async_notifier_operations rvin_parallel_notify_ops = { - .bound = rvin_parallel_notify_bound, - .unbind = rvin_parallel_notify_unbind, - .complete = rvin_parallel_notify_complete, -}; - -static void rvin_parallel_cleanup(struct rvin_dev *vin) -{ - v4l2_async_nf_unregister(&vin->notifier); - v4l2_async_nf_cleanup(&vin->notifier); -} - -static int rvin_parallel_init(struct rvin_dev *vin) -{ - int ret; - - v4l2_async_nf_init(&vin->notifier, &vin->v4l2_dev); - - ret = rvin_parallel_parse_of(vin); - if (ret) - return ret; - - if (!vin->parallel.asc) - return -ENODEV; - - vin_dbg(vin, "Found parallel subdevice %pOF\n", - to_of_node(vin->parallel.asc->match.fwnode)); - - vin->notifier.ops = &rvin_parallel_notify_ops; - ret = v4l2_async_nf_register(&vin->notifier); - if (ret < 0) { - vin_err(vin, "Notifier registration failed\n"); - v4l2_async_nf_cleanup(&vin->notifier); - return ret; - } - - return 0; -} - /* ----------------------------------------------------------------------------- * CSI-2 */ @@ -888,11 +811,52 @@ static int rvin_csi2_create_link(struct rvin_group *group, unsigned int id, return 0; } +static int rvin_parallel_setup_links(struct rvin_group *group) +{ + u32 flags = MEDIA_LNK_FL_ENABLED | MEDIA_LNK_FL_IMMUTABLE; + + guard(mutex)(&group->lock); + + /* If the group also has links don't enable the link. */ + for (unsigned int i = 0; i < ARRAY_SIZE(group->remotes); i++) { + if (group->remotes[i].subdev) { + flags = 0; + break; + } + } + + /* Create links. */ + for (unsigned int i = 0; i < RCAR_VIN_NUM; i++) { + struct rvin_dev *vin = group->vin[i]; + struct media_entity *source; + struct media_entity *sink; + int ret; + + /* Nothing to do if there is no VIN or parallel subdev. */ + if (!vin || !vin->parallel.subdev) + continue; + + source = &vin->parallel.subdev->entity; + sink = &vin->vdev.entity; + + ret = media_create_pad_link(source, vin->parallel.source_pad, + sink, 0, flags); + if (ret) + return ret; + } + + return 0; +} + static int rvin_csi2_setup_links(struct rvin_group *group) { const struct rvin_group_route *route; unsigned int id; - int ret = -EINVAL; + int ret; + + ret = rvin_parallel_setup_links(group); + if (ret) + return ret; /* Create all media device links between VINs and CSI-2's. */ mutex_lock(&group->lock); @@ -923,9 +887,7 @@ static int rvin_csi2_setup_links(struct rvin_group *group) static void rvin_csi2_cleanup(struct rvin_dev *vin) { - rvin_parallel_cleanup(vin); rvin_group_notifier_cleanup(vin); - rvin_group_put(vin); rvin_free_controls(vin); } @@ -946,18 +908,11 @@ static int rvin_csi2_init(struct rvin_dev *vin) if (ret) goto err_controls; - /* It's OK to not have a parallel subdevice. */ - ret = rvin_parallel_init(vin); - if (ret && ret != -ENODEV) - goto err_group; - ret = rvin_group_notifier_init(vin, 1, RVIN_CSI_MAX); if (ret) - goto err_parallel; + goto err_group; return 0; -err_parallel: - rvin_parallel_cleanup(vin); err_group: rvin_group_put(vin); err_controls: @@ -1018,7 +973,6 @@ static int rvin_isp_setup_links(struct rvin_group *group) static void rvin_isp_cleanup(struct rvin_dev *vin) { rvin_group_notifier_cleanup(vin); - rvin_group_put(vin); rvin_free_controls(vin); } @@ -1432,7 +1386,9 @@ static int rcar_vin_probe(struct platform_device *pdev) rvin_group_id_to_master(vin->id) == vin->id) vin->scaler = vin->info->scaler; } else { - ret = rvin_parallel_init(vin); + ret = rvin_group_get(vin, NULL, NULL); + if (!ret) + ret = rvin_group_notifier_init(vin, 0, 0); if (vin->info->scaler) vin->scaler = vin->info->scaler; @@ -1466,8 +1422,8 @@ static void rcar_vin_remove(struct platform_device *pdev) rvin_isp_cleanup(vin); else if (vin->info->use_mc) rvin_csi2_cleanup(vin); - else - rvin_parallel_cleanup(vin); + + rvin_group_put(vin); rvin_id_put(vin); diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index cb8e8fa54f96..38ae2bd20b72 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -149,7 +149,6 @@ struct rvin_info { * @vdev: V4L2 video device associated with VIN * @v4l2_dev: V4L2 device * @ctrl_handler: V4L2 control handler - * @notifier: V4L2 asynchronous subdevs notifier * * @parallel: parallel input subdevice descriptor * @@ -189,7 +188,6 @@ struct rvin_dev { struct video_device vdev; struct v4l2_device v4l2_dev; struct v4l2_ctrl_handler ctrl_handler; - struct v4l2_async_notifier notifier; struct rvin_parallel_entity parallel; From 257d994a6f81630b56f6393ed9ad8dbfbe10ff20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:30 +0200 Subject: [PATCH 023/310] media: rcar-vin: Always create a media pad MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prepare for Gen2 media graph support by always initializing a media pad for the VIN device. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-10-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-core.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index b3558c3c44d1..7367b5c993cd 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -895,10 +895,6 @@ static int rvin_csi2_init(struct rvin_dev *vin) { int ret; - vin->pad.flags = MEDIA_PAD_FL_SINK; - ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); - if (ret) - return ret; ret = rvin_create_controls(vin, NULL); if (ret < 0) @@ -980,10 +976,6 @@ static int rvin_isp_init(struct rvin_dev *vin) { int ret; - vin->pad.flags = MEDIA_PAD_FL_SINK; - ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); - if (ret) - return ret; ret = rvin_create_controls(vin, NULL); if (ret < 0) @@ -1377,6 +1369,11 @@ static int rcar_vin_probe(struct platform_device *pdev) goto err_dma; } + vin->pad.flags = MEDIA_PAD_FL_SINK; + ret = media_entity_pads_init(&vin->vdev.entity, 1, &vin->pad); + if (ret) + goto err_id; + if (vin->info->use_isp) { ret = rvin_isp_init(vin); } else if (vin->info->use_mc) { From a4b278e92de02ef63874e98719c25912546518a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:31 +0200 Subject: [PATCH 024/310] media: rcar-vin: Remove NTSC workaround MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On Gen2 where sub-devices where not exposed to user-space the field TB/BT ordering was controlled by a hack in the VIN driver. Before converting it to media device model where the subdevice is exposed remove that hack. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-11-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-dma.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 5c08ee2c9807..4fb33359bb0f 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -700,9 +700,6 @@ static int rvin_setup(struct rvin_dev *vin) case V4L2_FIELD_INTERLACED: /* Default to TB */ vnmc = VNMC_IM_FULL; - /* Use BT if video standard can be read and is 60 Hz format */ - if (!vin->info->use_mc && vin->std & V4L2_STD_525_60) - vnmc = VNMC_IM_FULL | VNMC_FOC; break; case V4L2_FIELD_INTERLACED_TB: vnmc = VNMC_IM_FULL; From f9e4d4b1ce389b44942a59bf0878921922d8604d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:32 +0200 Subject: [PATCH 025/310] media: rcar-vin: Only expose VIN controls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Before moving Gen2 to media controller simplify the creation of controls by not exposing the sub-device controls on the video device. This could be done while enabling media controller but doing it separately reduces the changes needed to do so. The rework also allows the cleanup and remove paths to be simplified by folding all special cases into the only remaining call site. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-12-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 89 +++++-------------- 1 file changed, 21 insertions(+), 68 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 7367b5c993cd..74fc90cf5800 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -365,14 +365,6 @@ static int rvin_group_parse_of(struct rvin_dev *vin, unsigned int port, return ret; } -static void rvin_group_notifier_cleanup(struct rvin_dev *vin) -{ - if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { - v4l2_async_nf_unregister(&vin->group->notifier); - v4l2_async_nf_cleanup(&vin->group->notifier); - } -} - static int rvin_parallel_parse_of(struct rvin_dev *vin) { struct fwnode_handle *fwnode __free(fwnode_handle) = NULL; @@ -510,11 +502,11 @@ static void rvin_free_controls(struct rvin_dev *vin) vin->vdev.ctrl_handler = NULL; } -static int rvin_create_controls(struct rvin_dev *vin, struct v4l2_subdev *subdev) +static int rvin_create_controls(struct rvin_dev *vin) { int ret; - ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 16); + ret = v4l2_ctrl_handler_init(&vin->ctrl_handler, 1); if (ret < 0) return ret; @@ -528,16 +520,6 @@ static int rvin_create_controls(struct rvin_dev *vin, struct v4l2_subdev *subdev return ret; } - /* For the non-MC mode add controls from the subdevice. */ - if (subdev) { - ret = v4l2_ctrl_add_handler(&vin->ctrl_handler, - subdev->ctrl_handler, NULL, true); - if (ret < 0) { - rvin_free_controls(vin); - return ret; - } - } - vin->vdev.ctrl_handler = &vin->ctrl_handler; return 0; @@ -627,11 +609,6 @@ static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, if (ret < 0 && ret != -ENOIOCTLCMD) return ret; - /* Add the controls */ - ret = rvin_create_controls(vin, subdev); - if (ret < 0) - return ret; - vin->parallel.subdev = subdev; return 0; @@ -885,34 +862,17 @@ static int rvin_csi2_setup_links(struct rvin_group *group) return ret; } -static void rvin_csi2_cleanup(struct rvin_dev *vin) -{ - rvin_group_notifier_cleanup(vin); - rvin_free_controls(vin); -} - static int rvin_csi2_init(struct rvin_dev *vin) { int ret; - - ret = rvin_create_controls(vin, NULL); - if (ret < 0) - return ret; - ret = rvin_group_get(vin, rvin_csi2_setup_links, &rvin_csi2_media_ops); if (ret) - goto err_controls; + return ret; ret = rvin_group_notifier_init(vin, 1, RVIN_CSI_MAX); if (ret) - goto err_group; - - return 0; -err_group: - rvin_group_put(vin); -err_controls: - rvin_free_controls(vin); + rvin_group_put(vin); return ret; } @@ -966,34 +926,17 @@ static int rvin_isp_setup_links(struct rvin_group *group) return ret; } -static void rvin_isp_cleanup(struct rvin_dev *vin) -{ - rvin_group_notifier_cleanup(vin); - rvin_free_controls(vin); -} - static int rvin_isp_init(struct rvin_dev *vin) { int ret; - - ret = rvin_create_controls(vin, NULL); - if (ret < 0) - return ret; - ret = rvin_group_get(vin, rvin_isp_setup_links, NULL); if (ret) - goto err_controls; + return ret; ret = rvin_group_notifier_init(vin, 2, RVIN_ISP_MAX); if (ret) - goto err_group; - - return 0; -err_group: - rvin_group_put(vin); -err_controls: - rvin_free_controls(vin); + rvin_group_put(vin); return ret; } @@ -1374,6 +1317,10 @@ static int rcar_vin_probe(struct platform_device *pdev) if (ret) goto err_id; + ret = rvin_create_controls(vin); + if (ret < 0) + goto err_id; + if (vin->info->use_isp) { ret = rvin_isp_init(vin); } else if (vin->info->use_mc) { @@ -1392,13 +1339,15 @@ static int rcar_vin_probe(struct platform_device *pdev) } if (ret) - goto err_id; + goto err_ctrl; pm_suspend_ignore_children(&pdev->dev, true); pm_runtime_enable(&pdev->dev); return 0; +err_ctrl: + rvin_free_controls(vin); err_id: rvin_id_put(vin); err_dma: @@ -1415,13 +1364,17 @@ static void rcar_vin_remove(struct platform_device *pdev) rvin_v4l2_unregister(vin); - if (vin->info->use_isp) - rvin_isp_cleanup(vin); - else if (vin->info->use_mc) - rvin_csi2_cleanup(vin); + if (vin->info->use_isp || vin->info->use_mc) { + if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { + v4l2_async_nf_unregister(&vin->group->notifier); + v4l2_async_nf_cleanup(&vin->group->notifier); + } + } rvin_group_put(vin); + rvin_free_controls(vin); + rvin_id_put(vin); rvin_dma_unregister(vin); From 6d2efcb0979ae50176ad8340e27b930ccddfe9b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:33 +0200 Subject: [PATCH 026/310] media: rcar-vin: Enable media-graph on Gen2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete the conversion from soc_camera to a full fledge media controller enabled devices for all supported generations of the device. All work is already done as this is already supported on Gen3, and later. All that is missing is removing all special cases for the non media-graph call paths and use the common ones in their place. The one change that stands out is dropping the doubling of the height in the Gen2 scaler setup, rvin_scaler_gen2(). In the Gen2 non-MC world the VIN size was set to match the video source subdevices, and if that was a TOP/BOTTOM video source it needed to be doubled for the scaler to function properly. In the MC world this is now handled by user-space configuration of the pipeline and the adjustment is not needed. Mark the completion of converting from soc_camera by injecting an attribution of myself in the header. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-13-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-core.c | 162 ++---- .../platform/renesas/rcar-vin/rcar-dma.c | 20 +- .../platform/renesas/rcar-vin/rcar-v4l2.c | 470 +----------------- .../platform/renesas/rcar-vin/rcar-vin.h | 10 +- 4 files changed, 44 insertions(+), 618 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-core.c b/drivers/media/platform/renesas/rcar-vin/rcar-core.c index 74fc90cf5800..f73729f59671 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-core.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-core.c @@ -2,12 +2,11 @@ /* * Driver for Renesas R-Car VIN * + * Copyright (C) 2025 Niklas Söderlund * Copyright (C) 2016 Renesas Electronics Corp. * Copyright (C) 2011-2013 Renesas Solutions Corp. * Copyright (C) 2013 Cogent Embedded, Inc., * Copyright (C) 2008 Magnus Damm - * - * Based on the soc-camera rcar_vin driver */ #include @@ -43,9 +42,6 @@ #define v4l2_dev_to_vin(d) container_of(d, struct rvin_dev, v4l2_dev) -static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, - struct v4l2_subdev *subdev); - /* ----------------------------------------------------------------------------- * Gen3 Group Allocator */ @@ -236,10 +232,7 @@ static int rvin_group_notify_complete(struct v4l2_async_notifier *notifier) } } - if (vin->group->link_setup) - return vin->group->link_setup(vin->group); - - return 0; + return vin->group->link_setup(vin->group); } static void rvin_group_notify_unbind(struct v4l2_async_notifier *notifier, @@ -290,18 +283,18 @@ static int rvin_group_notify_bound(struct v4l2_async_notifier *notifier, guard(mutex)(&group->lock); for (unsigned int i = 0; i < RCAR_VIN_NUM; i++) { - int ret; + struct rvin_dev *pvin = group->vin[i]; - if (!group->vin[i] || group->vin[i]->parallel.asc != asc) + if (!pvin || pvin->parallel.asc != asc) continue; - ret = rvin_parallel_subdevice_attach(group->vin[i], subdev); - if (ret) - return ret; + pvin->parallel.source_pad = 0; + for (unsigned int pad = 0; pad < subdev->entity.num_pads; pad++) + if (subdev->entity.pads[pad].flags & MEDIA_PAD_FL_SOURCE) + pvin->parallel.source_pad = pad; - v4l2_set_subdev_hostdata(subdev, group->vin[i]); - - vin_dbg(group->vin[i], "Bound subdev %s\n", subdev->name); + pvin->parallel.subdev = subdev; + vin_dbg(pvin, "Bound subdev %s\n", subdev->name); return 0; } @@ -525,95 +518,6 @@ static int rvin_create_controls(struct rvin_dev *vin) return 0; } -/* ----------------------------------------------------------------------------- - * Async notifier - */ - -static int rvin_find_pad(struct v4l2_subdev *sd, int direction) -{ - unsigned int pad; - - if (sd->entity.num_pads <= 1) - return 0; - - for (pad = 0; pad < sd->entity.num_pads; pad++) - if (sd->entity.pads[pad].flags & direction) - return pad; - - return -EINVAL; -} - -/* ----------------------------------------------------------------------------- - * Parallel async notifier - */ - -/* The vin lock should be held when calling the subdevice attach and detach */ -static int rvin_parallel_subdevice_attach(struct rvin_dev *vin, - struct v4l2_subdev *subdev) -{ - struct v4l2_subdev_mbus_code_enum code = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - }; - int ret; - - /* Find source and sink pad of remote subdevice */ - ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SOURCE); - if (ret < 0) - return ret; - vin->parallel.source_pad = ret; - - ret = rvin_find_pad(subdev, MEDIA_PAD_FL_SINK); - vin->parallel.sink_pad = ret < 0 ? 0 : ret; - - if (vin->info->use_mc) { - vin->parallel.subdev = subdev; - return 0; - } - - /* Find compatible subdevices mbus format */ - vin->mbus_code = 0; - code.index = 0; - code.pad = vin->parallel.source_pad; - while (!vin->mbus_code && - !v4l2_subdev_call(subdev, pad, enum_mbus_code, NULL, &code)) { - code.index++; - switch (code.code) { - case MEDIA_BUS_FMT_YUYV8_1X16: - case MEDIA_BUS_FMT_UYVY8_1X16: - case MEDIA_BUS_FMT_UYVY8_2X8: - case MEDIA_BUS_FMT_UYVY10_2X10: - case MEDIA_BUS_FMT_RGB888_1X24: - vin->mbus_code = code.code; - vin_dbg(vin, "Found media bus format for %s: %d\n", - subdev->name, vin->mbus_code); - break; - default: - break; - } - } - - if (!vin->mbus_code) { - vin_err(vin, "Unsupported media bus format for %s\n", - subdev->name); - return -EINVAL; - } - - /* Read tvnorms */ - ret = v4l2_subdev_call(subdev, video, g_tvnorms, &vin->vdev.tvnorms); - if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) - return ret; - - /* Read standard */ - vin->std = V4L2_STD_UNKNOWN; - ret = v4l2_subdev_call(subdev, video, g_std, &vin->std); - if (ret < 0 && ret != -ENOIOCTLCMD) - return ret; - - vin->parallel.subdev = subdev; - - return 0; -} - /* ----------------------------------------------------------------------------- * CSI-2 */ @@ -971,7 +875,7 @@ static int __maybe_unused rvin_resume(struct device *dev) * as we don't know if and in which order the master VINs will * be resumed. */ - if (vin->info->use_mc) { + if (vin->info->model == RCAR_GEN3) { unsigned int master_id = rvin_group_id_to_master(vin->id); struct rvin_dev *master = vin->group->vin[master_id]; int ret; @@ -993,7 +897,6 @@ static int __maybe_unused rvin_resume(struct device *dev) static const struct rvin_info rcar_info_h1 = { .model = RCAR_H1, - .use_mc = false, .max_width = 2048, .max_height = 2048, .scaler = rvin_scaler_gen2, @@ -1001,7 +904,6 @@ static const struct rvin_info rcar_info_h1 = { static const struct rvin_info rcar_info_m1 = { .model = RCAR_M1, - .use_mc = false, .max_width = 2048, .max_height = 2048, .scaler = rvin_scaler_gen2, @@ -1009,7 +911,6 @@ static const struct rvin_info rcar_info_m1 = { static const struct rvin_info rcar_info_gen2 = { .model = RCAR_GEN2, - .use_mc = false, .max_width = 2048, .max_height = 2048, .scaler = rvin_scaler_gen2, @@ -1024,7 +925,6 @@ static const struct rvin_group_route rcar_info_r8a774e1_routes[] = { static const struct rvin_info rcar_info_r8a774e1 = { .model = RCAR_GEN3, - .use_mc = true, .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a774e1_routes, @@ -1040,7 +940,6 @@ static const struct rvin_group_route rcar_info_r8a7795_routes[] = { static const struct rvin_info rcar_info_r8a7795 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1058,7 +957,6 @@ static const struct rvin_group_route rcar_info_r8a7796_routes[] = { static const struct rvin_info rcar_info_r8a7796 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1076,7 +974,6 @@ static const struct rvin_group_route rcar_info_r8a77965_routes[] = { static const struct rvin_info rcar_info_r8a77965 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1091,7 +988,6 @@ static const struct rvin_group_route rcar_info_r8a77970_routes[] = { static const struct rvin_info rcar_info_r8a77970 = { .model = RCAR_GEN3, - .use_mc = true, .max_width = 4096, .max_height = 4096, .routes = rcar_info_r8a77970_routes, @@ -1105,7 +1001,6 @@ static const struct rvin_group_route rcar_info_r8a77980_routes[] = { static const struct rvin_info rcar_info_r8a77980 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1119,7 +1014,6 @@ static const struct rvin_group_route rcar_info_r8a77990_routes[] = { static const struct rvin_info rcar_info_r8a77990 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1133,7 +1027,6 @@ static const struct rvin_group_route rcar_info_r8a77995_routes[] = { static const struct rvin_info rcar_info_r8a77995 = { .model = RCAR_GEN3, - .use_mc = true, .nv12 = true, .max_width = 4096, .max_height = 4096, @@ -1143,7 +1036,6 @@ static const struct rvin_info rcar_info_r8a77995 = { static const struct rvin_info rcar_info_gen4 = { .model = RCAR_GEN4, - .use_mc = true, .use_isp = true, .nv12 = true, .raw10 = true, @@ -1321,21 +1213,27 @@ static int rcar_vin_probe(struct platform_device *pdev) if (ret < 0) goto err_id; - if (vin->info->use_isp) { - ret = rvin_isp_init(vin); - } else if (vin->info->use_mc) { - ret = rvin_csi2_init(vin); + switch (vin->info->model) { + case RCAR_GEN3: + case RCAR_GEN4: + if (vin->info->use_isp) { + ret = rvin_isp_init(vin); + } else { + ret = rvin_csi2_init(vin); - if (vin->info->scaler && - rvin_group_id_to_master(vin->id) == vin->id) - vin->scaler = vin->info->scaler; - } else { - ret = rvin_group_get(vin, NULL, NULL); + if (vin->info->scaler && + rvin_group_id_to_master(vin->id) == vin->id) + vin->scaler = vin->info->scaler; + } + break; + default: + ret = rvin_group_get(vin, rvin_parallel_setup_links, NULL); if (!ret) ret = rvin_group_notifier_init(vin, 0, 0); if (vin->info->scaler) vin->scaler = vin->info->scaler; + break; } if (ret) @@ -1364,11 +1262,9 @@ static void rcar_vin_remove(struct platform_device *pdev) rvin_v4l2_unregister(vin); - if (vin->info->use_isp || vin->info->use_mc) { - if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { - v4l2_async_nf_unregister(&vin->group->notifier); - v4l2_async_nf_cleanup(&vin->group->notifier); - } + if (&vin->v4l2_dev == vin->group->notifier.v4l2_dev) { + v4l2_async_nf_unregister(&vin->group->notifier); + v4l2_async_nf_cleanup(&vin->group->notifier); } rvin_group_put(vin); diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 4fb33359bb0f..d4faa5a4e757 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -2,12 +2,11 @@ /* * Driver for Renesas R-Car VIN * + * Copyright (C) 2025 Niklas Söderlund * Copyright (C) 2016 Renesas Electronics Corp. * Copyright (C) 2011-2013 Renesas Solutions Corp. * Copyright (C) 2013 Cogent Embedded, Inc., * Copyright (C) 2008 Magnus Damm - * - * Based on the soc-camera rcar_vin driver */ #include @@ -555,17 +554,12 @@ static void rvin_set_coeff(struct rvin_dev *vin, unsigned short xs) void rvin_scaler_gen2(struct rvin_dev *vin) { - unsigned int crop_height; u32 xs, ys; /* Set scaling coefficient */ - crop_height = vin->crop.height; - if (V4L2_FIELD_HAS_BOTH(vin->format.field)) - crop_height *= 2; - ys = 0; - if (crop_height != vin->compose.height) - ys = (4096 * crop_height) / vin->compose.height; + if (vin->crop.height != vin->compose.height) + ys = (4096 * vin->crop.height) / vin->compose.height; rvin_write(vin, ys, VNYS_REG); xs = 0; @@ -1294,14 +1288,6 @@ static int rvin_set_stream(struct rvin_dev *vin, int on) struct media_pad *pad; int ret; - /* No media controller used, simply pass operation to subdevice. */ - if (!vin->info->use_mc) { - ret = v4l2_subdev_call(vin->parallel.subdev, video, s_stream, - on); - - return ret == -ENOIOCTLCMD ? 0 : ret; - } - pad = media_pad_remote_pad_first(&vin->pad); if (!pad) return -EPIPE; diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index db091af57c19..3a4c7433f061 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -2,12 +2,11 @@ /* * Driver for Renesas R-Car VIN * + * Copyright (C) 2025 Niklas Söderlund * Copyright (C) 2016 Renesas Electronics Corp. * Copyright (C) 2011-2013 Renesas Solutions Corp. * Copyright (C) 2013 Cogent Embedded, Inc., * Copyright (C) 2008 Magnus Damm - * - * Based on the soc-camera rcar_vin driver */ #include @@ -230,101 +229,6 @@ static void rvin_format_align(struct rvin_dev *vin, struct v4l2_pix_format *pix) * V4L2 */ -static int rvin_reset_format(struct rvin_dev *vin) -{ - struct v4l2_subdev_format fmt = { - .which = V4L2_SUBDEV_FORMAT_ACTIVE, - .pad = vin->parallel.source_pad, - }; - int ret; - - ret = v4l2_subdev_call(vin_to_source(vin), pad, get_fmt, NULL, &fmt); - if (ret) - return ret; - - v4l2_fill_pix_format(&vin->format, &fmt.format); - - vin->crop.top = 0; - vin->crop.left = 0; - vin->crop.width = vin->format.width; - vin->crop.height = vin->format.height; - - /* Make use of the hardware interlacer by default. */ - if (vin->format.field == V4L2_FIELD_ALTERNATE) { - vin->format.field = V4L2_FIELD_INTERLACED; - vin->format.height *= 2; - } - - rvin_format_align(vin, &vin->format); - - vin->compose.top = 0; - vin->compose.left = 0; - vin->compose.width = vin->format.width; - vin->compose.height = vin->format.height; - - return 0; -} - -static int rvin_try_format(struct rvin_dev *vin, u32 which, - struct v4l2_pix_format *pix, - struct v4l2_rect *src_rect) -{ - struct v4l2_subdev *sd = vin_to_source(vin); - struct v4l2_subdev_state *sd_state; - static struct lock_class_key key; - struct v4l2_subdev_format format = { - .which = which, - .pad = vin->parallel.source_pad, - }; - enum v4l2_field field; - u32 width, height; - int ret; - - /* - * FIXME: Drop this call, drivers are not supposed to use - * __v4l2_subdev_state_alloc(). - */ - sd_state = __v4l2_subdev_state_alloc(sd, "rvin:state->lock", &key); - if (IS_ERR(sd_state)) - return PTR_ERR(sd_state); - - if (!rvin_format_from_pixel(vin, pix->pixelformat)) - pix->pixelformat = RVIN_DEFAULT_FORMAT; - - v4l2_fill_mbus_format(&format.format, pix, vin->mbus_code); - - /* Allow the video device to override field and to scale */ - field = pix->field; - width = pix->width; - height = pix->height; - - ret = v4l2_subdev_call(sd, pad, set_fmt, sd_state, &format); - if (ret < 0 && ret != -ENOIOCTLCMD) - goto done; - ret = 0; - - v4l2_fill_pix_format(pix, &format.format); - - if (src_rect) { - src_rect->top = 0; - src_rect->left = 0; - src_rect->width = pix->width; - src_rect->height = pix->height; - } - - if (field != V4L2_FIELD_ANY) - pix->field = field; - - pix->width = width; - pix->height = height; - - rvin_format_align(vin, pix); -done: - __v4l2_subdev_state_free(sd_state); - - return ret; -} - static int rvin_querycap(struct file *file, void *priv, struct v4l2_capability *cap) { @@ -333,42 +237,6 @@ static int rvin_querycap(struct file *file, void *priv, return 0; } -static int rvin_try_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) -{ - struct rvin_dev *vin = video_drvdata(file); - - return rvin_try_format(vin, V4L2_SUBDEV_FORMAT_TRY, &f->fmt.pix, NULL); -} - -static int rvin_s_fmt_vid_cap(struct file *file, void *priv, - struct v4l2_format *f) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_rect fmt_rect, src_rect; - int ret; - - if (vb2_is_busy(&vin->queue)) - return -EBUSY; - - ret = rvin_try_format(vin, V4L2_SUBDEV_FORMAT_ACTIVE, &f->fmt.pix, - &src_rect); - if (ret) - return ret; - - vin->format = f->fmt.pix; - - fmt_rect.top = 0; - fmt_rect.left = 0; - fmt_rect.width = vin->format.width; - fmt_rect.height = vin->format.height; - - v4l2_rect_map_inside(&vin->crop, &src_rect); - v4l2_rect_map_inside(&vin->compose, &fmt_rect); - - return 0; -} - static int rvin_g_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { @@ -465,6 +333,7 @@ static int rvin_enum_fmt_vid_cap(struct file *file, void *priv, static int rvin_remote_rectangle(struct rvin_dev *vin, struct v4l2_rect *rect) { + struct media_pad *pad = media_pad_remote_pad_first(&vin->pad); struct v4l2_subdev_format fmt = { .which = V4L2_SUBDEV_FORMAT_ACTIVE, }; @@ -472,18 +341,11 @@ static int rvin_remote_rectangle(struct rvin_dev *vin, struct v4l2_rect *rect) unsigned int index; int ret; - if (vin->info->use_mc) { - struct media_pad *pad = media_pad_remote_pad_first(&vin->pad); + if (!pad) + return -EINVAL; - if (!pad) - return -EINVAL; - - sd = media_entity_to_v4l2_subdev(pad->entity); - index = pad->index; - } else { - sd = vin_to_source(vin); - index = vin->parallel.source_pad; - } + sd = media_entity_to_v4l2_subdev(pad->entity); + index = pad->index; fmt.pad = index; ret = v4l2_subdev_call(sd, pad, get_fmt, NULL, &fmt); @@ -623,113 +485,6 @@ static int rvin_s_selection(struct file *file, void *fh, return 0; } -static int rvin_g_parm(struct file *file, void *priv, - struct v4l2_streamparm *parm) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - return v4l2_g_parm_cap(&vin->vdev, sd, parm); -} - -static int rvin_s_parm(struct file *file, void *priv, - struct v4l2_streamparm *parm) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - return v4l2_s_parm_cap(&vin->vdev, sd, parm); -} - -static int rvin_g_pixelaspect(struct file *file, void *priv, - int type, struct v4l2_fract *f) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - if (type != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - - return v4l2_subdev_call(sd, video, g_pixelaspect, f); -} - -static int rvin_enum_input(struct file *file, void *priv, - struct v4l2_input *i) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - if (i->index != 0) - return -EINVAL; - - ret = v4l2_subdev_call(sd, video, g_input_status, &i->status); - if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) - return ret; - - i->type = V4L2_INPUT_TYPE_CAMERA; - - if (v4l2_subdev_has_op(sd, pad, dv_timings_cap)) { - i->capabilities = V4L2_IN_CAP_DV_TIMINGS; - i->std = 0; - } else { - i->capabilities = V4L2_IN_CAP_STD; - i->std = vin->vdev.tvnorms; - } - - strscpy(i->name, "Camera", sizeof(i->name)); - - return 0; -} - -static int rvin_g_input(struct file *file, void *priv, unsigned int *i) -{ - *i = 0; - return 0; -} - -static int rvin_s_input(struct file *file, void *priv, unsigned int i) -{ - if (i > 0) - return -EINVAL; - return 0; -} - -static int rvin_querystd(struct file *file, void *priv, v4l2_std_id *a) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - return v4l2_subdev_call(sd, video, querystd, a); -} - -static int rvin_s_std(struct file *file, void *priv, v4l2_std_id a) -{ - struct rvin_dev *vin = video_drvdata(file); - int ret; - - ret = v4l2_subdev_call(vin_to_source(vin), video, s_std, a); - if (ret < 0) - return ret; - - vin->std = a; - - /* Changing the standard will change the width/height */ - return rvin_reset_format(vin); -} - -static int rvin_g_std(struct file *file, void *priv, v4l2_std_id *a) -{ - struct rvin_dev *vin = video_drvdata(file); - - if (v4l2_subdev_has_op(vin_to_source(vin), pad, dv_timings_cap)) - return -ENOIOCTLCMD; - - *a = vin->std; - - return 0; -} - static int rvin_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { @@ -740,167 +495,6 @@ static int rvin_subscribe_event(struct v4l2_fh *fh, return v4l2_ctrl_subscribe_event(fh, sub); } -static int rvin_enum_dv_timings(struct file *file, void *priv_fh, - struct v4l2_enum_dv_timings *timings) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - if (timings->pad) - return -EINVAL; - - timings->pad = vin->parallel.sink_pad; - - ret = v4l2_subdev_call(sd, pad, enum_dv_timings, timings); - - timings->pad = 0; - - return ret; -} - -static int rvin_s_dv_timings(struct file *file, void *priv_fh, - struct v4l2_dv_timings *timings) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - ret = v4l2_subdev_call(sd, pad, s_dv_timings, - vin->parallel.sink_pad, timings); - if (ret) - return ret; - - /* Changing the timings will change the width/height */ - return rvin_reset_format(vin); -} - -static int rvin_g_dv_timings(struct file *file, void *priv_fh, - struct v4l2_dv_timings *timings) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - return v4l2_subdev_call(sd, pad, g_dv_timings, - vin->parallel.sink_pad, timings); -} - -static int rvin_query_dv_timings(struct file *file, void *priv_fh, - struct v4l2_dv_timings *timings) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - - return v4l2_subdev_call(sd, pad, query_dv_timings, - vin->parallel.sink_pad, timings); -} - -static int rvin_dv_timings_cap(struct file *file, void *priv_fh, - struct v4l2_dv_timings_cap *cap) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - if (cap->pad) - return -EINVAL; - - cap->pad = vin->parallel.sink_pad; - - ret = v4l2_subdev_call(sd, pad, dv_timings_cap, cap); - - cap->pad = 0; - - return ret; -} - -static int rvin_g_edid(struct file *file, void *fh, struct v4l2_edid *edid) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - if (edid->pad) - return -EINVAL; - - edid->pad = vin->parallel.sink_pad; - - ret = v4l2_subdev_call(sd, pad, get_edid, edid); - - edid->pad = 0; - - return ret; -} - -static int rvin_s_edid(struct file *file, void *fh, struct v4l2_edid *edid) -{ - struct rvin_dev *vin = video_drvdata(file); - struct v4l2_subdev *sd = vin_to_source(vin); - int ret; - - if (edid->pad) - return -EINVAL; - - edid->pad = vin->parallel.sink_pad; - - ret = v4l2_subdev_call(sd, pad, set_edid, edid); - - edid->pad = 0; - - return ret; -} - -static const struct v4l2_ioctl_ops rvin_ioctl_ops = { - .vidioc_querycap = rvin_querycap, - .vidioc_try_fmt_vid_cap = rvin_try_fmt_vid_cap, - .vidioc_g_fmt_vid_cap = rvin_g_fmt_vid_cap, - .vidioc_s_fmt_vid_cap = rvin_s_fmt_vid_cap, - .vidioc_enum_fmt_vid_cap = rvin_enum_fmt_vid_cap, - - .vidioc_g_selection = rvin_g_selection, - .vidioc_s_selection = rvin_s_selection, - - .vidioc_g_parm = rvin_g_parm, - .vidioc_s_parm = rvin_s_parm, - - .vidioc_g_pixelaspect = rvin_g_pixelaspect, - - .vidioc_enum_input = rvin_enum_input, - .vidioc_g_input = rvin_g_input, - .vidioc_s_input = rvin_s_input, - - .vidioc_dv_timings_cap = rvin_dv_timings_cap, - .vidioc_enum_dv_timings = rvin_enum_dv_timings, - .vidioc_g_dv_timings = rvin_g_dv_timings, - .vidioc_s_dv_timings = rvin_s_dv_timings, - .vidioc_query_dv_timings = rvin_query_dv_timings, - - .vidioc_g_edid = rvin_g_edid, - .vidioc_s_edid = rvin_s_edid, - - .vidioc_querystd = rvin_querystd, - .vidioc_g_std = rvin_g_std, - .vidioc_s_std = rvin_s_std, - - .vidioc_reqbufs = vb2_ioctl_reqbufs, - .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_querybuf = vb2_ioctl_querybuf, - .vidioc_qbuf = vb2_ioctl_qbuf, - .vidioc_dqbuf = vb2_ioctl_dqbuf, - .vidioc_expbuf = vb2_ioctl_expbuf, - .vidioc_prepare_buf = vb2_ioctl_prepare_buf, - .vidioc_streamon = vb2_ioctl_streamon, - .vidioc_streamoff = vb2_ioctl_streamoff, - - .vidioc_log_status = v4l2_ctrl_log_status, - .vidioc_subscribe_event = rvin_subscribe_event, - .vidioc_unsubscribe_event = v4l2_event_unsubscribe, -}; - -/* ----------------------------------------------------------------------------- - * V4L2 Media Controller - */ - static void rvin_mc_try_format(struct rvin_dev *vin, struct v4l2_pix_format *pix) { @@ -979,19 +573,6 @@ static const struct v4l2_ioctl_ops rvin_mc_ioctl_ops = { * File Operations */ -static int rvin_power_parallel(struct rvin_dev *vin, bool on) -{ - struct v4l2_subdev *sd = vin_to_source(vin); - int power = on ? 1 : 0; - int ret; - - ret = v4l2_subdev_call(sd, core, s_power, power); - if (ret < 0 && ret != -ENOIOCTLCMD && ret != -ENODEV) - return ret; - - return 0; -} - static int rvin_open(struct file *file) { struct rvin_dev *vin = video_drvdata(file); @@ -1011,11 +592,7 @@ static int rvin_open(struct file *file) if (ret) goto err_unlock; - if (vin->info->use_mc) - ret = v4l2_pipeline_pm_get(&vin->vdev.entity); - else if (v4l2_fh_is_singular_file(file)) - ret = rvin_power_parallel(vin, true); - + ret = v4l2_pipeline_pm_get(&vin->vdev.entity); if (ret < 0) goto err_open; @@ -1027,10 +604,7 @@ static int rvin_open(struct file *file) return 0; err_power: - if (vin->info->use_mc) - v4l2_pipeline_pm_put(&vin->vdev.entity); - else if (v4l2_fh_is_singular_file(file)) - rvin_power_parallel(vin, false); + v4l2_pipeline_pm_put(&vin->vdev.entity); err_open: v4l2_fh_release(file); err_unlock: @@ -1044,23 +618,14 @@ static int rvin_open(struct file *file) static int rvin_release(struct file *file) { struct rvin_dev *vin = video_drvdata(file); - bool fh_singular; int ret; mutex_lock(&vin->lock); - /* Save the singular status before we call the clean-up helper */ - fh_singular = v4l2_fh_is_singular_file(file); - /* the release helper will cleanup any on-going streaming */ ret = _vb2_fop_release(file, NULL); - if (vin->info->use_mc) { - v4l2_pipeline_pm_put(&vin->vdev.entity); - } else { - if (fh_singular) - rvin_power_parallel(vin, false); - } + v4l2_pipeline_pm_put(&vin->vdev.entity); mutex_unlock(&vin->lock); @@ -1113,12 +678,6 @@ static void rvin_notify(struct v4l2_subdev *sd, container_of(sd->v4l2_dev, struct rvin_dev, v4l2_dev); unsigned int i; - /* If no media controller, no need to route the event. */ - if (!vin->info->use_mc) { - rvin_notify_video_device(vin, notification, arg); - return; - } - group = vin->group; for (i = 0; i < RCAR_VIN_NUM; i++) { @@ -1153,7 +712,8 @@ int rvin_v4l2_register(struct rvin_dev *vin) vdev->lock = &vin->lock; vdev->fops = &rvin_fops; vdev->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING | - V4L2_CAP_READWRITE; + V4L2_CAP_READWRITE | V4L2_CAP_IO_MC; + vdev->ioctl_ops = &rvin_mc_ioctl_ops; /* Set a default format */ vin->format.pixelformat = RVIN_DEFAULT_FORMAT; @@ -1162,14 +722,6 @@ int rvin_v4l2_register(struct rvin_dev *vin) vin->format.field = RVIN_DEFAULT_FIELD; vin->format.colorspace = RVIN_DEFAULT_COLORSPACE; - if (vin->info->use_mc) { - vdev->device_caps |= V4L2_CAP_IO_MC; - vdev->ioctl_ops = &rvin_mc_ioctl_ops; - } else { - vdev->ioctl_ops = &rvin_ioctl_ops; - rvin_reset_format(vin); - } - rvin_format_align(vin, &vin->format); ret = video_register_device(&vin->vdev, VFL_TYPE_VIDEO, -1); diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h index 38ae2bd20b72..74bef5b8adad 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-vin.h +++ b/drivers/media/platform/renesas/rcar-vin/rcar-vin.h @@ -2,12 +2,11 @@ /* * Driver for Renesas R-Car VIN * + * Copyright (C) 2025 Niklas Söderlund * Copyright (C) 2016 Renesas Electronics Corp. * Copyright (C) 2011-2013 Renesas Solutions Corp. * Copyright (C) 2013 Cogent Embedded, Inc., * Copyright (C) 2008 Magnus Damm - * - * Based on the soc-camera rcar_vin driver */ #ifndef __RCAR_VIN__ @@ -79,8 +78,6 @@ struct rvin_video_format { * @mbus_type: media bus type * @bus: media bus parallel configuration * @source_pad: source pad of remote subdevice - * @sink_pad: sink pad of remote subdevice - * */ struct rvin_parallel_entity { struct v4l2_async_connection *asc; @@ -90,7 +87,6 @@ struct rvin_parallel_entity { struct v4l2_mbus_config_parallel bus; unsigned int source_pad; - unsigned int sink_pad; }; /** @@ -117,7 +113,6 @@ struct rvin_group_route { /** * struct rvin_info - Information about the particular VIN implementation * @model: VIN model - * @use_mc: use media controller instead of controlling subdevice * @use_isp: the VIN is connected to the ISP and not to the CSI-2 * @nv12: support outputting NV12 pixel format * @raw10: support outputting RAW10 pixel format @@ -129,7 +124,6 @@ struct rvin_group_route { */ struct rvin_info { enum model_id model; - bool use_mc; bool use_isp; bool nv12; bool raw10; @@ -176,7 +170,6 @@ struct rvin_info { * @crop: active cropping * @compose: active composing * @scaler: Optional scaler - * @std: active video standard of the video source * * @alpha: Alpha component to fill in for supported pixel formats */ @@ -218,7 +211,6 @@ struct rvin_dev { struct v4l2_rect crop; struct v4l2_rect compose; void (*scaler)(struct rvin_dev *vin); - v4l2_std_id std; unsigned int alpha; }; From 4d2c3d70799f5eb210003613766bbd113bbebc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Fri, 13 Jun 2025 17:34:34 +0200 Subject: [PATCH 027/310] media: rcar-vin: Fold event notifier into only user MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With Gen2 converted to use the common media device there is only one caller left for the helper to notify a video device of an event, fold it in. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250613153434.2001800-14-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-v4l2.c | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index 3a4c7433f061..9b1e3a9d3249 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -656,18 +656,6 @@ void rvin_v4l2_unregister(struct rvin_dev *vin) video_unregister_device(&vin->vdev); } -static void rvin_notify_video_device(struct rvin_dev *vin, - unsigned int notification, void *arg) -{ - switch (notification) { - case V4L2_DEVICE_NOTIFY_EVENT: - v4l2_event_queue(&vin->vdev, arg); - break; - default: - break; - } -} - static void rvin_notify(struct v4l2_subdev *sd, unsigned int notification, void *arg) { @@ -693,7 +681,13 @@ static void rvin_notify(struct v4l2_subdev *sd, if (remote != sd) continue; - rvin_notify_video_device(vin, notification, arg); + switch (notification) { + case V4L2_DEVICE_NOTIFY_EVENT: + v4l2_event_queue(&vin->vdev, arg); + break; + default: + break; + } } } From ed0234c8458b3149f15e496b48a1c9874dd24a1b Mon Sep 17 00:00:00 2001 From: Alex Guo Date: Sun, 15 Jun 2025 21:33:53 -0400 Subject: [PATCH 028/310] media: dvb-frontends: w7090p: fix null-ptr-deref in w7090p_tuner_write_serpar and w7090p_tuner_read_serpar In w7090p_tuner_write_serpar, msg is controlled by user. When msg[0].buf is null and msg[0].len is zero, former checks on msg[0].buf would be passed. If accessing msg[0].buf[2] without sanity check, null pointer deref would happen. We add check on msg[0].len to prevent crash. Similar commit: commit 0ed554fd769a ("media: dvb-usb: az6027: fix null-ptr-deref in az6027_i2c_xfer()") Signed-off-by: Alex Guo Link: https://lore.kernel.org/r/20250616013353.738790-1-alexguo1023@gmail.com Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-frontends/dib7000p.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/dvb-frontends/dib7000p.c b/drivers/media/dvb-frontends/dib7000p.c index b40daf242046..2c2354dd547d 100644 --- a/drivers/media/dvb-frontends/dib7000p.c +++ b/drivers/media/dvb-frontends/dib7000p.c @@ -2193,6 +2193,8 @@ static int w7090p_tuner_write_serpar(struct i2c_adapter *i2c_adap, struct i2c_ms struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); u8 n_overflow = 1; u16 i = 1000; + if (msg[0].len < 3) + return -EOPNOTSUPP; u16 serpar_num = msg[0].buf[0]; while (n_overflow == 1 && i) { @@ -2212,6 +2214,8 @@ static int w7090p_tuner_read_serpar(struct i2c_adapter *i2c_adap, struct i2c_msg struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); u8 n_overflow = 1, n_empty = 1; u16 i = 1000; + if (msg[0].len < 1 || msg[1].len < 2) + return -EOPNOTSUPP; u16 serpar_num = msg[0].buf[0]; u16 read_word; From ce5cac69b2edac3e3246fee03e8f4c2a1075238b Mon Sep 17 00:00:00 2001 From: Alex Guo Date: Sun, 15 Jun 2025 21:32:31 -0400 Subject: [PATCH 029/310] media: dvb-frontends: dib7090p: fix null-ptr-deref in dib7090p_rw_on_apb() In dib7090p_rw_on_apb, msg is controlled by user. When msg[0].buf is null and msg[0].len is zero, former checks on msg[0].buf would be passed. If accessing msg[0].buf[2] without sanity check, null pointer deref would happen. We add check on msg[0].len to prevent crash. Similar issue occurs when access msg[1].buf[0] and msg[1].buf[1]. Similar commit: commit 0ed554fd769a ("media: dvb-usb: az6027: fix null-ptr-deref in az6027_i2c_xfer()") Signed-off-by: Alex Guo Link: https://lore.kernel.org/r/20250616013231.730221-1-alexguo1023@gmail.com Signed-off-by: Mauro Carvalho Chehab --- drivers/media/dvb-frontends/dib7000p.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/dvb-frontends/dib7000p.c b/drivers/media/dvb-frontends/dib7000p.c index 2c2354dd547d..7d3a994b7cc4 100644 --- a/drivers/media/dvb-frontends/dib7000p.c +++ b/drivers/media/dvb-frontends/dib7000p.c @@ -2260,8 +2260,12 @@ static int dib7090p_rw_on_apb(struct i2c_adapter *i2c_adap, u16 word; if (num == 1) { /* write */ + if (msg[0].len < 3) + return -EOPNOTSUPP; dib7000p_write_word(state, apb_address, ((msg[0].buf[1] << 8) | (msg[0].buf[2]))); } else { + if (msg[1].len < 2) + return -EOPNOTSUPP; word = dib7000p_read_word(state, apb_address); msg[1].buf[0] = (word >> 8) & 0xff; msg[1].buf[1] = (word) & 0xff; From 13518ae3203fdbb86d05b9e9916d4907cea518a4 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:50 +0200 Subject: [PATCH 030/310] media: dvb-frontends/cxd2820r: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/dvb-frontends/cxd2820r_core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/dvb-frontends/cxd2820r_core.c b/drivers/media/dvb-frontends/cxd2820r_core.c index c3d8ced6c3ba..a31a8a6a4946 100644 --- a/drivers/media/dvb-frontends/cxd2820r_core.c +++ b/drivers/media/dvb-frontends/cxd2820r_core.c @@ -433,7 +433,7 @@ static int cxd2820r_gpio_direction_output(struct gpio_chip *chip, unsigned nr, return cxd2820r_gpio(&priv->fe, gpio); } -static void cxd2820r_gpio_set(struct gpio_chip *chip, unsigned nr, int val) +static int cxd2820r_gpio_set(struct gpio_chip *chip, unsigned int nr, int val) { struct cxd2820r_priv *priv = gpiochip_get_data(chip); struct i2c_client *client = priv->client[0]; @@ -446,7 +446,7 @@ static void cxd2820r_gpio_set(struct gpio_chip *chip, unsigned nr, int val) (void) cxd2820r_gpio(&priv->fe, gpio); - return; + return 0; } static int cxd2820r_gpio_get(struct gpio_chip *chip, unsigned nr) @@ -651,7 +651,7 @@ static int cxd2820r_probe(struct i2c_client *client) priv->gpio_chip.parent = &client->dev; priv->gpio_chip.owner = THIS_MODULE; priv->gpio_chip.direction_output = cxd2820r_gpio_direction_output; - priv->gpio_chip.set = cxd2820r_gpio_set; + priv->gpio_chip.set_rv = cxd2820r_gpio_set; priv->gpio_chip.get = cxd2820r_gpio_get; priv->gpio_chip.base = -1; /* Dynamic allocation */ priv->gpio_chip.ngpio = GPIO_COUNT; From 94bf84c7fe2e6fa17a50c1296551a53fd2a29e37 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:51 +0200 Subject: [PATCH 031/310] media: solo6x10: remove unneeded GPIO direction setters GPIO core already handles missing direction_input/output() callbacks. The ones in this driver also effectively return magic numbers which is not optimal either (the number accidentally corresponds with -EPERM which is different from the number GPIOLIB returns and so inconsistent). Just remove them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/pci/solo6x10/solo6x10-gpio.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/drivers/media/pci/solo6x10/solo6x10-gpio.c b/drivers/media/pci/solo6x10/solo6x10-gpio.c index 084c30760e45..f51f865c69bd 100644 --- a/drivers/media/pci/solo6x10/solo6x10-gpio.c +++ b/drivers/media/pci/solo6x10/solo6x10-gpio.c @@ -116,18 +116,6 @@ static int solo_gpiochip_get_direction(struct gpio_chip *chip, return -1; } -static int solo_gpiochip_direction_input(struct gpio_chip *chip, - unsigned int offset) -{ - return -1; -} - -static int solo_gpiochip_direction_output(struct gpio_chip *chip, - unsigned int offset, int value) -{ - return -1; -} - static int solo_gpiochip_get(struct gpio_chip *chip, unsigned int offset) { @@ -167,8 +155,6 @@ int solo_gpio_init(struct solo_dev *solo_dev) solo_dev->gpio_dev.can_sleep = 0; solo_dev->gpio_dev.get_direction = solo_gpiochip_get_direction; - solo_dev->gpio_dev.direction_input = solo_gpiochip_direction_input; - solo_dev->gpio_dev.direction_output = solo_gpiochip_direction_output; solo_dev->gpio_dev.get = solo_gpiochip_get; solo_dev->gpio_dev.set = solo_gpiochip_set; From bb4954b4abf61ae9d0ff230e035f95ea8fb22457 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:52 +0200 Subject: [PATCH 032/310] media: solo6x10: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/pci/solo6x10/solo6x10-gpio.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/media/pci/solo6x10/solo6x10-gpio.c b/drivers/media/pci/solo6x10/solo6x10-gpio.c index f51f865c69bd..b16a8453a62a 100644 --- a/drivers/media/pci/solo6x10/solo6x10-gpio.c +++ b/drivers/media/pci/solo6x10/solo6x10-gpio.c @@ -127,8 +127,8 @@ static int solo_gpiochip_get(struct gpio_chip *chip, return 1 & (ret >> (offset + 8)); } -static void solo_gpiochip_set(struct gpio_chip *chip, - unsigned int offset, int value) +static int solo_gpiochip_set(struct gpio_chip *chip, + unsigned int offset, int value) { struct solo_dev *solo_dev = gpiochip_get_data(chip); @@ -136,6 +136,8 @@ static void solo_gpiochip_set(struct gpio_chip *chip, solo_gpio_set(solo_dev, 1 << (offset + 8)); else solo_gpio_clear(solo_dev, 1 << (offset + 8)); + + return 0; } #endif @@ -156,7 +158,7 @@ int solo_gpio_init(struct solo_dev *solo_dev) solo_dev->gpio_dev.get_direction = solo_gpiochip_get_direction; solo_dev->gpio_dev.get = solo_gpiochip_get; - solo_dev->gpio_dev.set = solo_gpiochip_set; + solo_dev->gpio_dev.set_rv = solo_gpiochip_set; ret = gpiochip_add_data(&solo_dev->gpio_dev, solo_dev); From c4406dc9bc8ccab8c3dd9790fe27b525ab79ae3c Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:53 +0200 Subject: [PATCH 033/310] media: i2c: ds90ub953: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/i2c/ds90ub953.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c index 59bd92388845..242dbf6d48f3 100644 --- a/drivers/media/i2c/ds90ub953.c +++ b/drivers/media/i2c/ds90ub953.c @@ -317,14 +317,13 @@ static int ub953_gpio_get(struct gpio_chip *gc, unsigned int offset) return !!(v & UB953_REG_GPIO_PIN_STS_GPIO_STS(offset)); } -static void ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +static int ub953_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct ub953_data *priv = gpiochip_get_data(gc); - regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, - UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), - value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : - 0); + return regmap_update_bits(priv->regmap, UB953_REG_LOCAL_GPIO_DATA, + UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset), + value ? UB953_REG_LOCAL_GPIO_DATA_GPIO_OUT_SRC(offset) : 0); } static int ub953_gpio_of_xlate(struct gpio_chip *gc, @@ -362,7 +361,7 @@ static int ub953_gpiochip_probe(struct ub953_data *priv) gc->direction_input = ub953_gpio_direction_in; gc->direction_output = ub953_gpio_direction_out; gc->get = ub953_gpio_get; - gc->set = ub953_gpio_set; + gc->set_rv = ub953_gpio_set; gc->of_xlate = ub953_gpio_of_xlate; gc->of_gpio_n_cells = 2; From 94a45a567a2237239f07dc81d03f840e3a6d5d30 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:54 +0200 Subject: [PATCH 034/310] media: i2c: ds90ub913: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/i2c/ds90ub913.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c index 6d3f8617ef13..c34ffeae5f50 100644 --- a/drivers/media/i2c/ds90ub913.c +++ b/drivers/media/i2c/ds90ub913.c @@ -203,9 +203,9 @@ static int ub913_gpio_direction_out(struct gpio_chip *gc, unsigned int offset, 0)); } -static void ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) +static int ub913_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { - ub913_gpio_direction_out(gc, offset, value); + return ub913_gpio_direction_out(gc, offset, value); } static int ub913_gpio_of_xlate(struct gpio_chip *gc, @@ -235,7 +235,7 @@ static int ub913_gpiochip_probe(struct ub913_data *priv) gc->ngpio = UB913_NUM_GPIOS; gc->get_direction = ub913_gpio_get_direction; gc->direction_output = ub913_gpio_direction_out; - gc->set = ub913_gpio_set; + gc->set_rv = ub913_gpio_set; gc->of_xlate = ub913_gpio_of_xlate; gc->of_gpio_n_cells = 2; From 7019a876aeaeffe3853d6d379215c3cc92839f5f Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:55 +0200 Subject: [PATCH 035/310] media: i2c: max96717: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/i2c/max96717.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c index 3746729366ac..a503a55fe8af 100644 --- a/drivers/media/i2c/max96717.c +++ b/drivers/media/i2c/max96717.c @@ -297,13 +297,13 @@ static int max96717_gpiochip_get(struct gpio_chip *gpiochip, return !!(val & MAX96717_GPIO_OUT); } -static void max96717_gpiochip_set(struct gpio_chip *gpiochip, - unsigned int offset, int value) +static int max96717_gpiochip_set(struct gpio_chip *gpiochip, + unsigned int offset, int value) { struct max96717_priv *priv = gpiochip_get_data(gpiochip); - cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), - MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL); + return cci_update_bits(priv->regmap, MAX96717_GPIO_REG_A(offset), + MAX96717_GPIO_OUT, MAX96717_GPIO_OUT, NULL); } static int max96717_gpio_get_direction(struct gpio_chip *gpiochip, @@ -355,7 +355,7 @@ static int max96717_gpiochip_probe(struct max96717_priv *priv) gc->get_direction = max96717_gpio_get_direction; gc->direction_input = max96717_gpio_direction_in; gc->direction_output = max96717_gpio_direction_out; - gc->set = max96717_gpiochip_set; + gc->set_rv = max96717_gpiochip_set; gc->get = max96717_gpiochip_get; gc->of_gpio_n_cells = 2; From 9e2c1e8cfb8e83f891cdc16220adf162a3d93066 Mon Sep 17 00:00:00 2001 From: Bartosz Golaszewski Date: Mon, 16 Jun 2025 09:18:56 +0200 Subject: [PATCH 036/310] media: i2c: max9286: use new GPIO line value setter callbacks struct gpio_chip now has callbacks for setting line values that return an integer, allowing to indicate failures. Convert the driver to using them. Signed-off-by: Bartosz Golaszewski Signed-off-by: Hans Verkuil --- drivers/media/i2c/max9286.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/max9286.c b/drivers/media/i2c/max9286.c index 9fc4e130a273..1d0b5f56f989 100644 --- a/drivers/media/i2c/max9286.c +++ b/drivers/media/i2c/max9286.c @@ -1193,12 +1193,12 @@ static int max9286_gpio_set(struct max9286_priv *priv, unsigned int offset, MAX9286_0X0F_RESERVED | priv->gpio_state); } -static void max9286_gpiochip_set(struct gpio_chip *chip, - unsigned int offset, int value) +static int max9286_gpiochip_set(struct gpio_chip *chip, + unsigned int offset, int value) { struct max9286_priv *priv = gpiochip_get_data(chip); - max9286_gpio_set(priv, offset, value); + return max9286_gpio_set(priv, offset, value); } static int max9286_gpiochip_get(struct gpio_chip *chip, unsigned int offset) @@ -1220,7 +1220,7 @@ static int max9286_register_gpio(struct max9286_priv *priv) gpio->owner = THIS_MODULE; gpio->ngpio = 2; gpio->base = -1; - gpio->set = max9286_gpiochip_set; + gpio->set_rv = max9286_gpiochip_set; gpio->get = max9286_gpiochip_get; gpio->can_sleep = true; From ad7fbb1624e8d1ef7e219cb76ce9d7edcabc68ee Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Thu, 13 Mar 2025 13:30:34 +0200 Subject: [PATCH 037/310] media: v4l2-core: Replace the check for firmware registered I2C devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the check for firmware registered I²C devices as the firmware node independently on type should be retrieved via dev_fwnode(). Signed-off-by: Andy Shevchenko Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-i2c.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/v4l2-core/v4l2-i2c.c b/drivers/media/v4l2-core/v4l2-i2c.c index 586c46544255..ffc64e10fcae 100644 --- a/drivers/media/v4l2-core/v4l2-i2c.c +++ b/drivers/media/v4l2-core/v4l2-i2c.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -24,7 +25,7 @@ void v4l2_i2c_subdev_unregister(struct v4l2_subdev *sd) * registered by us, and would not be * re-created by just probing the V4L2 driver. */ - if (client && !client->dev.of_node && !client->dev.fwnode) + if (client && !dev_fwnode(&client->dev)) i2c_unregister_device(client); } From 065eb4d262473037ef6f7a409ed8da489bc64c4e Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sat, 29 Mar 2025 15:26:42 +0100 Subject: [PATCH 038/310] media: flexcop-i2c: Constify struct i2c_algorithm 'struct i2c_algorithm' is not modified in this driver. Constifying this structure moves some data to a read-only section, so increase overall security, especially when the structure holds some function pointers. On a x86_64, with allmodconfig, as an example: Before: ====== text data bss dec hex filename 6645 216 16 6877 1add drivers/media/common/b2c2/flexcop-i2c.o After: ===== text data bss dec hex filename 6721 160 16 6897 1af1 drivers/media/common/b2c2/flexcop-i2c.o Signed-off-by: Christophe JAILLET Signed-off-by: Hans Verkuil --- drivers/media/common/b2c2/flexcop-i2c.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/common/b2c2/flexcop-i2c.c b/drivers/media/common/b2c2/flexcop-i2c.c index 1f1eaa807811..21edf870d927 100644 --- a/drivers/media/common/b2c2/flexcop-i2c.c +++ b/drivers/media/common/b2c2/flexcop-i2c.c @@ -209,7 +209,7 @@ static u32 flexcop_i2c_func(struct i2c_adapter *adapter) return I2C_FUNC_I2C; } -static struct i2c_algorithm flexcop_algo = { +static const struct i2c_algorithm flexcop_algo = { .master_xfer = flexcop_master_xfer, .functionality = flexcop_i2c_func, }; From 7e40e0bb778907b2441bff68d73c3eb6b6cd319f Mon Sep 17 00:00:00 2001 From: Ludwig Disterhof Date: Mon, 28 Apr 2025 20:16:50 +0200 Subject: [PATCH 039/310] media: usbtv: Lock resolution while streaming When an program is streaming (ffplay) and another program (qv4l2) changes the TV standard from NTSC to PAL, the kernel crashes due to trying to copy to unmapped memory. Changing from NTSC to PAL increases the resolution in the usbtv struct, but the video plane buffer isn't adjusted, so it overflows. Fixes: 0e0fe3958fdd13d ("[media] usbtv: Add support for PAL video source") Cc: stable@vger.kernel.org Signed-off-by: Ludwig Disterhof Signed-off-by: Hans Verkuil [hverkuil: call vb2_is_busy instead of vb2_is_streaming] --- drivers/media/usb/usbtv/usbtv-video.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/usb/usbtv/usbtv-video.c b/drivers/media/usb/usbtv/usbtv-video.c index be22a9697197..de0328100a60 100644 --- a/drivers/media/usb/usbtv/usbtv-video.c +++ b/drivers/media/usb/usbtv/usbtv-video.c @@ -73,6 +73,10 @@ static int usbtv_configure_for_norm(struct usbtv *usbtv, v4l2_std_id norm) } if (params) { + if (vb2_is_busy(&usbtv->vb2q) && + (usbtv->width != params->cap_width || + usbtv->height != params->cap_height)) + return -EBUSY; usbtv->width = params->cap_width; usbtv->height = params->cap_height; usbtv->n_chunks = usbtv->width * usbtv->height From 75f278ab52c6748d5ea14a0dca46bd007a8b0871 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Fri, 9 May 2025 02:17:50 +0100 Subject: [PATCH 040/310] media: saa7164: Remove unused functions saa7164_cmd_signal() last use was removed in 2009 by commit 39e469ab6dee ("V4L/DVB (12940): SAA7164: IRQ / message timeout related change") saa7164_buffer_display() was added in 2010 by commit add3f580a434 ("[media] saa7164: convert buffering structs to be more generic") but is unused. Remove them. Signed-off-by: Dr. David Alan Gilbert Signed-off-by: Hans Verkuil --- drivers/media/pci/saa7164/saa7164-buffer.c | 20 ---------------- drivers/media/pci/saa7164/saa7164-cmd.c | 28 ---------------------- drivers/media/pci/saa7164/saa7164.h | 2 -- 3 files changed, 50 deletions(-) diff --git a/drivers/media/pci/saa7164/saa7164-buffer.c b/drivers/media/pci/saa7164/saa7164-buffer.c index 89c5b79a5b24..7820e4f47fd5 100644 --- a/drivers/media/pci/saa7164/saa7164-buffer.c +++ b/drivers/media/pci/saa7164/saa7164-buffer.c @@ -52,26 +52,6 @@ * | etc */ -void saa7164_buffer_display(struct saa7164_buffer *buf) -{ - struct saa7164_dev *dev = buf->port->dev; - int i; - - dprintk(DBGLVL_BUF, "%s() buffer @ 0x%p nr=%d\n", - __func__, buf, buf->idx); - dprintk(DBGLVL_BUF, " pci_cpu @ 0x%p dma @ 0x%08llx len = 0x%x\n", - buf->cpu, (long long)buf->dma, buf->pci_size); - dprintk(DBGLVL_BUF, " pt_cpu @ 0x%p pt_dma @ 0x%08llx len = 0x%x\n", - buf->pt_cpu, (long long)buf->pt_dma, buf->pt_size); - - /* Format the Page Table Entries to point into the data buffer */ - for (i = 0 ; i < SAA7164_PT_ENTRIES; i++) { - - dprintk(DBGLVL_BUF, " pt[%02d] = 0x%p -> 0x%llx\n", - i, buf->pt_cpu, (u64)*(buf->pt_cpu)); - - } -} /* Allocate a new buffer structure and associated PCI space in bytes. * len must be a multiple of sizeof(u64) */ diff --git a/drivers/media/pci/saa7164/saa7164-cmd.c b/drivers/media/pci/saa7164/saa7164-cmd.c index 42bd8e76005b..a95662776ee8 100644 --- a/drivers/media/pci/saa7164/saa7164-cmd.c +++ b/drivers/media/pci/saa7164/saa7164-cmd.c @@ -295,34 +295,6 @@ static int saa7164_cmd_wait(struct saa7164_dev *dev, u8 seqno) return ret; } -void saa7164_cmd_signal(struct saa7164_dev *dev, u8 seqno) -{ - int i; - dprintk(DBGLVL_CMD, "%s()\n", __func__); - - mutex_lock(&dev->lock); - for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) { - if (dev->cmds[i].inuse == 1) { - dprintk(DBGLVL_CMD, - "seqno %d inuse, sig = %d, t/out = %d\n", - dev->cmds[i].seqno, - dev->cmds[i].signalled, - dev->cmds[i].timeout); - } - } - - for (i = 0; i < SAA_CMD_MAX_MSG_UNITS; i++) { - if ((dev->cmds[i].inuse == 1) && ((i == 0) || - (dev->cmds[i].signalled) || (dev->cmds[i].timeout))) { - dprintk(DBGLVL_CMD, "%s(seqno=%d) calling wake_up\n", - __func__, i); - dev->cmds[i].signalled = 1; - wake_up(&dev->cmds[i].wait); - } - } - mutex_unlock(&dev->lock); -} - int saa7164_cmd_send(struct saa7164_dev *dev, u8 id, enum tmComResCmd command, u16 controlselector, u16 size, void *buf) { diff --git a/drivers/media/pci/saa7164/saa7164.h b/drivers/media/pci/saa7164/saa7164.h index eede47b686a3..e1bac1fe19d3 100644 --- a/drivers/media/pci/saa7164/saa7164.h +++ b/drivers/media/pci/saa7164/saa7164.h @@ -508,7 +508,6 @@ int saa7164_bus_get(struct saa7164_dev *dev, struct tmComResInfo* msg, int saa7164_cmd_send(struct saa7164_dev *dev, u8 id, enum tmComResCmd command, u16 controlselector, u16 size, void *buf); -void saa7164_cmd_signal(struct saa7164_dev *dev, u8 seqno); int saa7164_irq_dequeue(struct saa7164_dev *dev); /* ----------------------------------------------------------- */ @@ -570,7 +569,6 @@ extern int saa7164_dvb_unregister(struct saa7164_port *port); extern struct saa7164_buffer *saa7164_buffer_alloc( struct saa7164_port *port, u32 len); extern int saa7164_buffer_dealloc(struct saa7164_buffer *buf); -extern void saa7164_buffer_display(struct saa7164_buffer *buf); extern int saa7164_buffer_activate(struct saa7164_buffer *buf, int i); extern int saa7164_buffer_cfg_port(struct saa7164_port *port); extern struct saa7164_user_buffer *saa7164_buffer_alloc_user( From b5ae5a79825ba8037b0be3ef677a24de8c063abf Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 22 May 2025 10:09:54 +0200 Subject: [PATCH 041/310] media: usb: hdpvr: disable zero-length read messages This driver passes the length of an i2c_msg directly to usb_control_msg(). If the message is now a read and of length 0, it violates the USB protocol and a warning will be printed. Enable the I2C_AQ_NO_ZERO_LEN_READ quirk for this adapter thus forbidding 0-length read messages altogether. Signed-off-by: Wolfram Sang Signed-off-by: Hans Verkuil --- drivers/media/usb/hdpvr/hdpvr-i2c.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/usb/hdpvr/hdpvr-i2c.c b/drivers/media/usb/hdpvr/hdpvr-i2c.c index 070559b01b01..54956a8ff15e 100644 --- a/drivers/media/usb/hdpvr/hdpvr-i2c.c +++ b/drivers/media/usb/hdpvr/hdpvr-i2c.c @@ -165,10 +165,16 @@ static const struct i2c_algorithm hdpvr_algo = { .functionality = hdpvr_functionality, }; +/* prevent invalid 0-length usb_control_msg */ +static const struct i2c_adapter_quirks hdpvr_quirks = { + .flags = I2C_AQ_NO_ZERO_LEN_READ, +}; + static const struct i2c_adapter hdpvr_i2c_adapter_template = { .name = "Hauppauge HD PVR I2C", .owner = THIS_MODULE, .algo = &hdpvr_algo, + .quirks = &hdpvr_quirks, }; static int hdpvr_activate_ir(struct hdpvr_device *dev) From 1cb109d8863da75c22458b90c43b4e61b994e332 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Thu, 22 May 2025 10:09:55 +0200 Subject: [PATCH 042/310] media: usb: hdpvr: use I2C core to handle only supported messages The HW can only do write-then-read transactions. This is a common limitation, so we can add an adapter quirk flag to let the I2C core enforce the checks instead of open coding them. Signed-off-by: Wolfram Sang Signed-off-by: Hans Verkuil --- drivers/media/usb/hdpvr/hdpvr-i2c.c | 28 ++++------------------------ 1 file changed, 4 insertions(+), 24 deletions(-) diff --git a/drivers/media/usb/hdpvr/hdpvr-i2c.c b/drivers/media/usb/hdpvr/hdpvr-i2c.c index 54956a8ff15e..9eacc85e3f11 100644 --- a/drivers/media/usb/hdpvr/hdpvr-i2c.c +++ b/drivers/media/usb/hdpvr/hdpvr-i2c.c @@ -124,32 +124,12 @@ static int hdpvr_transfer(struct i2c_adapter *i2c_adapter, struct i2c_msg *msgs, else retval = hdpvr_i2c_write(dev, 1, addr, msgs[0].buf, msgs[0].len); - } else if (num == 2) { - if (msgs[0].addr != msgs[1].addr) { - v4l2_warn(&dev->v4l2_dev, "refusing 2-phase i2c xfer with conflicting target addresses\n"); - retval = -EINVAL; - goto out; - } - - if ((msgs[0].flags & I2C_M_RD) || !(msgs[1].flags & I2C_M_RD)) { - v4l2_warn(&dev->v4l2_dev, "refusing complex xfer with r0=%d, r1=%d\n", - msgs[0].flags & I2C_M_RD, - msgs[1].flags & I2C_M_RD); - retval = -EINVAL; - goto out; - } - - /* - * Write followed by atomic read is the only complex xfer that - * we actually support here. - */ + } else { + /* do write-then-read */ retval = hdpvr_i2c_read(dev, 1, addr, msgs[0].buf, msgs[0].len, msgs[1].buf, msgs[1].len); - } else { - v4l2_warn(&dev->v4l2_dev, "refusing %d-phase i2c xfer\n", num); } -out: mutex_unlock(&dev->i2c_mutex); return retval ? retval : num; @@ -165,9 +145,9 @@ static const struct i2c_algorithm hdpvr_algo = { .functionality = hdpvr_functionality, }; -/* prevent invalid 0-length usb_control_msg */ +/* prevent invalid 0-length usb_control_msg and support only write-then-read */ static const struct i2c_adapter_quirks hdpvr_quirks = { - .flags = I2C_AQ_NO_ZERO_LEN_READ, + .flags = I2C_AQ_NO_ZERO_LEN_READ | I2C_AQ_COMB_WRITE_THEN_READ, }; static const struct i2c_adapter hdpvr_i2c_adapter_template = { From d8fbfcab95ffab466aba475127ae677a4c7160fb Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Wed, 28 May 2025 14:32:04 +0300 Subject: [PATCH 043/310] media: cx18: Replace custom implementation of list_entry_is_head() Besides list_entry_is_past_end() is the same as list_entry_is_head(), it's implemented in the list namespace. Fix both of the issue by replacing the custom version with list_entry_is_head() calls. Signed-off-by: Andy Shevchenko Signed-off-by: Hans Verkuil --- drivers/media/pci/cx18/cx18-driver.h | 12 ------------ drivers/media/pci/cx18/cx18-fileops.c | 2 +- drivers/media/pci/cx18/cx18-ioctl.c | 2 +- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/drivers/media/pci/cx18/cx18-driver.h b/drivers/media/pci/cx18/cx18-driver.h index af05bde75816..485ca9747c4c 100644 --- a/drivers/media/pci/cx18/cx18-driver.h +++ b/drivers/media/pci/cx18/cx18-driver.h @@ -271,18 +271,6 @@ struct cx18_options { #define CX18_SLICED_TYPE_WSS_625 (5) #define CX18_SLICED_TYPE_VPS (7) -/** - * list_entry_is_past_end - check if a previous loop cursor is off list end - * @pos: the type * previously used as a loop cursor. - * @head: the head for your list. - * @member: the name of the list_head within the struct. - * - * Check if the entry's list_head is the head of the list, thus it's not a - * real entry but was the loop cursor that walked past the end - */ -#define list_entry_is_past_end(pos, head, member) \ - (&pos->member == (head)) - struct cx18_vb2_buffer { /* Common video buffer sub-system struct */ struct vb2_v4l2_buffer vb; diff --git a/drivers/media/pci/cx18/cx18-fileops.c b/drivers/media/pci/cx18/cx18-fileops.c index 315577d71d95..cefa91b37f89 100644 --- a/drivers/media/pci/cx18/cx18-fileops.c +++ b/drivers/media/pci/cx18/cx18-fileops.c @@ -371,7 +371,7 @@ static size_t cx18_copy_mdl_to_user(struct cx18_stream *s, mdl->curr_buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, list); - if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) { + if (list_entry_is_head(mdl->curr_buf, &mdl->buf_list, list)) { /* * For some reason we've exhausted the buffers, but the MDL * object still said some data was unread. diff --git a/drivers/media/pci/cx18/cx18-ioctl.c b/drivers/media/pci/cx18/cx18-ioctl.c index 1817b9ed042c..9a1512b1ccaa 100644 --- a/drivers/media/pci/cx18/cx18-ioctl.c +++ b/drivers/media/pci/cx18/cx18-ioctl.c @@ -764,7 +764,7 @@ static int cx18_process_idx_data(struct cx18_stream *s, struct cx18_mdl *mdl, mdl->curr_buf = list_first_entry(&mdl->buf_list, struct cx18_buffer, list); - if (list_entry_is_past_end(mdl->curr_buf, &mdl->buf_list, list)) { + if (list_entry_is_head(mdl->curr_buf, &mdl->buf_list, list)) { /* * For some reason we've exhausted the buffers, but the MDL * object still said some data was unread. From aef89c0b2417da79cb2062a95476288f9f203ab0 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 28 May 2025 23:22:14 +0300 Subject: [PATCH 044/310] media: gspca: Add bounds checking to firmware parser This sd_init() function reads the firmware. The firmware data holds a series of records and the function reads each record and sends the data to the device. The request_ihex_firmware() function calls ihex_validate_fw() which ensures that the total length of all the records won't read out of bounds of the fw->data[]. However, a potential issue is if there is a single very large record (larger than PAGE_SIZE) and that would result in memory corruption. Generally we trust the firmware, but it's always better to double check. Fixes: 49b61ec9b5af ("[media] gspca: Add new vicam subdriver") Cc: stable@vger.kernel.org Signed-off-by: Dan Carpenter Signed-off-by: Hans Verkuil --- drivers/media/usb/gspca/vicam.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/media/usb/gspca/vicam.c b/drivers/media/usb/gspca/vicam.c index d98343fd33fe..91e177aa8136 100644 --- a/drivers/media/usb/gspca/vicam.c +++ b/drivers/media/usb/gspca/vicam.c @@ -227,6 +227,7 @@ static int sd_init(struct gspca_dev *gspca_dev) const struct ihex_binrec *rec; const struct firmware *fw; u8 *firmware_buf; + int len; ret = request_ihex_firmware(&fw, VICAM_FIRMWARE, &gspca_dev->dev->dev); @@ -241,9 +242,14 @@ static int sd_init(struct gspca_dev *gspca_dev) goto exit; } for (rec = (void *)fw->data; rec; rec = ihex_next_binrec(rec)) { - memcpy(firmware_buf, rec->data, be16_to_cpu(rec->len)); + len = be16_to_cpu(rec->len); + if (len > PAGE_SIZE) { + ret = -EINVAL; + break; + } + memcpy(firmware_buf, rec->data, len); ret = vicam_control_msg(gspca_dev, 0xff, 0, 0, firmware_buf, - be16_to_cpu(rec->len)); + len); if (ret < 0) break; } From 42a5c1dfaaab6e48b867aa41d9d4d0a5b09b2a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20T=C5=AFma?= Date: Mon, 2 Jun 2025 11:27:51 +0200 Subject: [PATCH 045/310] media: mgb4: Enumerate only the available timings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enumerate only the available (as given by the sysfs setup - our "EDID replacement") timings, not all theoretically possible. This is the video outputs part of the previous inputs patch that somehow got "lost in translation". Signed-off-by: Martin Tůma Signed-off-by: Hans Verkuil --- drivers/media/pci/mgb4/mgb4_vout.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c index 14c5725bd4d8..c179c425e167 100644 --- a/drivers/media/pci/mgb4/mgb4_vout.c +++ b/drivers/media/pci/mgb4/mgb4_vout.c @@ -492,7 +492,14 @@ static int vidioc_s_dv_timings(struct file *file, void *fh, static int vidioc_enum_dv_timings(struct file *file, void *fh, struct v4l2_enum_dv_timings *timings) { - return v4l2_enum_dv_timings_cap(timings, &video_timings_cap, NULL, NULL); + struct mgb4_vout_dev *voutdev = video_drvdata(file); + + if (timings->index != 0) + return -EINVAL; + + get_timings(voutdev, &timings->timings); + + return 0; } static int vidioc_dv_timings_cap(struct file *file, void *fh, From 7af160aea26c7dc9e6734d19306128cce156ec40 Mon Sep 17 00:00:00 2001 From: Gui-Dong Han Date: Fri, 6 Jun 2025 03:04:59 +0000 Subject: [PATCH 046/310] media: rainshadow-cec: fix TOCTOU race condition in rain_interrupt() In the interrupt handler rain_interrupt(), the buffer full check on rain->buf_len is performed before acquiring rain->buf_lock. This creates a Time-of-Check to Time-of-Use (TOCTOU) race condition, as rain->buf_len is concurrently accessed and modified in the work handler rain_irq_work_handler() under the same lock. Multiple interrupt invocations can race, with each reading buf_len before it becomes full and then proceeding. This can lead to both interrupts attempting to write to the buffer, incrementing buf_len beyond its capacity (DATA_SIZE) and causing a buffer overflow. Fix this bug by moving the spin_lock() to before the buffer full check. This ensures that the check and the subsequent buffer modification are performed atomically, preventing the race condition. An corresponding spin_unlock() is added to the overflow path to correctly release the lock. This possible bug was found by an experimental static analysis tool developed by our team. Fixes: 0f314f6c2e77 ("[media] rainshadow-cec: new RainShadow Tech HDMI CEC driver") Cc: stable@vger.kernel.org Signed-off-by: Gui-Dong Han Signed-off-by: Hans Verkuil --- drivers/media/cec/usb/rainshadow/rainshadow-cec.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/cec/usb/rainshadow/rainshadow-cec.c b/drivers/media/cec/usb/rainshadow/rainshadow-cec.c index ee870ea1a886..6f8d6797c614 100644 --- a/drivers/media/cec/usb/rainshadow/rainshadow-cec.c +++ b/drivers/media/cec/usb/rainshadow/rainshadow-cec.c @@ -171,11 +171,12 @@ static irqreturn_t rain_interrupt(struct serio *serio, unsigned char data, { struct rain *rain = serio_get_drvdata(serio); + spin_lock(&rain->buf_lock); if (rain->buf_len == DATA_SIZE) { + spin_unlock(&rain->buf_lock); dev_warn_once(rain->dev, "buffer overflow\n"); return IRQ_HANDLED; } - spin_lock(&rain->buf_lock); rain->buf_len++; rain->buf[rain->buf_wr_idx] = data; rain->buf_wr_idx = (rain->buf_wr_idx + 1) & 0xff; From a4950419459ad5acfc4816d8c28fccf62830028c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 9 Jun 2025 16:35:57 +0200 Subject: [PATCH 047/310] MAINTAINERS: .mailmap: Update Hans de Goede's email address I'm moving all my kernel work over to using my kernel.org email address. Update .mailmap and MAINTAINER entries still using hdegoede@redhat.com. Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- .mailmap | 1 + MAINTAINERS | 72 ++++++++++++++++++++++++++--------------------------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/.mailmap b/.mailmap index c8a21d72241f..645294560b19 100644 --- a/.mailmap +++ b/.mailmap @@ -282,6 +282,7 @@ Gustavo Padovan Gustavo Padovan Hamza Mahfooz Hanjun Guo +Hans de Goede Hans Verkuil Hans Verkuil Harry Yoo <42.hyeyoo@gmail.com> diff --git a/MAINTAINERS b/MAINTAINERS index a92290fffa16..e8f3dc93a569 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -207,7 +207,7 @@ X: arch/*/include/uapi/ X: include/uapi/ ABIT UGURU 1,2 HARDWARE MONITOR DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-hwmon@vger.kernel.org S: Maintained F: drivers/hwmon/abituguru.c @@ -371,7 +371,7 @@ S: Maintained F: drivers/platform/x86/quickstart.c ACPI SERIAL MULTI INSTANTIATE DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/serial-multi-instantiate.c @@ -3551,7 +3551,7 @@ F: arch/arm64/boot/Makefile F: scripts/make_fit.py ARM64 PLATFORM DRIVERS -M: Hans de Goede +M: Hans de Goede M: Ilpo Järvinen R: Bryan O'Donoghue L: platform-driver-x86@vger.kernel.org @@ -3712,7 +3712,7 @@ F: drivers/platform/x86/asus*.c F: drivers/platform/x86/eeepc*.c ASUS TF103C DOCK DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git @@ -5613,14 +5613,14 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/peter.chen/usb.git F: drivers/usb/chipidea/ CHIPONE ICN8318 I2C TOUCHSCREEN DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/input/touchscreen/chipone,icn8318.yaml F: drivers/input/touchscreen/chipone_icn8318.c CHIPONE ICN8505 I2C TOUCHSCREEN DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: drivers/input/touchscreen/chipone_icn8505.c @@ -6917,7 +6917,7 @@ F: include/dt-bindings/pmu/exynos_ppmu.h F: include/linux/devfreq-event.h DEVICE RESOURCE MANAGEMENT HELPERS -M: Hans de Goede +M: Hans de Goede R: Matti Vaittinen S: Maintained F: include/linux/devm-helpers.h @@ -7516,7 +7516,7 @@ F: drivers/gpu/drm/gud/ F: include/drm/gud.h DRM DRIVER FOR GRAIN MEDIA GM12U320 PROJECTORS -M: Hans de Goede +M: Hans de Goede S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git F: drivers/gpu/drm/tiny/gm12u320.c @@ -7916,7 +7916,7 @@ F: drivers/gpu/drm/ci/xfails/vkms* F: drivers/gpu/drm/vkms/ DRM DRIVER FOR VIRTUALBOX VIRTUAL GPU -M: Hans de Goede +M: Hans de Goede L: dri-devel@lists.freedesktop.org S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git @@ -8317,7 +8317,7 @@ F: drivers/gpu/drm/panel/ F: include/drm/drm_panel.h DRM PRIVACY-SCREEN CLASS -M: Hans de Goede +M: Hans de Goede L: dri-devel@lists.freedesktop.org S: Maintained T: git https://gitlab.freedesktop.org/drm/misc/kernel.git @@ -10221,7 +10221,7 @@ S: Maintained F: Documentation/devicetree/bindings/connector/gocontroll,moduline-module-slot.yaml GOODIX TOUCHSCREEN -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: drivers/input/touchscreen/goodix* @@ -10260,7 +10260,7 @@ F: include/dt-bindings/clock/google,gs101.h K: [gG]oogle.?[tT]ensor GPD POCKET FAN DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/gpd-pocket-fan.c @@ -11421,7 +11421,7 @@ F: drivers/i2c/busses/i2c-via.c F: drivers/i2c/busses/i2c-viapro.c I2C/SMBUS INTEL CHT WHISKEY COVE PMIC DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-i2c@vger.kernel.org S: Maintained F: drivers/i2c/busses/i2c-cht-wc.c @@ -12011,13 +12011,13 @@ S: Supported F: sound/soc/intel/ INTEL ATOMISP2 DUMMY / POWER-MANAGEMENT DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/intel/atomisp2/pm.c INTEL ATOMISP2 LED DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/intel/atomisp2/led.c @@ -13678,7 +13678,7 @@ S: Maintained F: drivers/platform/x86/lenovo-wmi-hotkey-utilities.c LETSKETCH HID TABLET DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git @@ -13728,7 +13728,7 @@ F: drivers/ata/sata_gemini.c F: drivers/ata/sata_gemini.h LIBATA SATA AHCI PLATFORM devices support -M: Hans de Goede +M: Hans de Goede L: linux-ide@vger.kernel.org S: Maintained F: drivers/ata/ahci_platform.c @@ -14098,7 +14098,7 @@ F: Documentation/admin-guide/ldm.rst F: block/partitions/ldm.* LOGITECH HID GAMING KEYBOARDS -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/hid/hid.git @@ -14780,7 +14780,7 @@ F: Documentation/devicetree/bindings/power/supply/maxim,max17040.yaml F: drivers/power/supply/max17040_battery.c MAXIM MAX17042 FAMILY FUEL GAUGE DRIVERS -R: Hans de Goede +R: Hans de Goede R: Krzysztof Kozlowski R: Marek Szyprowski R: Sebastian Krzyszkowiak @@ -15582,7 +15582,7 @@ Q: https://patchwork.kernel.org/project/netdevbpf/list/ F: drivers/net/ethernet/mellanox/mlxfw/ MELLANOX HARDWARE PLATFORM SUPPORT -M: Hans de Goede +M: Hans de Goede M: Ilpo Järvinen M: Vadim Pasternak L: platform-driver-x86@vger.kernel.org @@ -16538,7 +16538,7 @@ S: Maintained F: drivers/platform/surface/surface_gpe.c MICROSOFT SURFACE HARDWARE PLATFORM SUPPORT -M: Hans de Goede +M: Hans de Goede M: Ilpo Järvinen M: Maximilian Luz L: platform-driver-x86@vger.kernel.org @@ -17706,7 +17706,7 @@ F: tools/include/nolibc/ F: tools/testing/selftests/nolibc/ NOVATEK NVT-TS I2C TOUCHSCREEN DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/input/touchscreen/novatek,nvt-ts.yaml @@ -22708,7 +22708,7 @@ K: fu[57]40 K: [^@]sifive SILEAD TOUCHSCREEN DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org L: platform-driver-x86@vger.kernel.org S: Maintained @@ -22741,7 +22741,7 @@ F: Documentation/devicetree/bindings/i3c/silvaco,i3c-master.yaml F: drivers/i3c/master/svc-i3c-master.c SIMPLEFB FB DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-fbdev@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/display/simple-framebuffer.yaml @@ -22870,7 +22870,7 @@ F: Documentation/hwmon/emc2103.rst F: drivers/hwmon/emc2103.c SMSC SCH5627 HARDWARE MONITOR DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-hwmon@vger.kernel.org S: Supported F: Documentation/hwmon/sch5627.rst @@ -23525,7 +23525,7 @@ S: Supported F: Documentation/process/stable-kernel-rules.rst STAGING - ATOMISP DRIVER -M: Hans de Goede +M: Hans de Goede M: Mauro Carvalho Chehab R: Sakari Ailus L: linux-media@vger.kernel.org @@ -23822,7 +23822,7 @@ F: arch/m68k/sun3*/ F: drivers/net/ethernet/i825xx/sun3* SUN4I LOW RES ADC ATTACHED TABLET KEYS DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/input/allwinner,sun4i-a10-lradc-keys.yaml @@ -25590,7 +25590,7 @@ F: Documentation/hid/hiddev.rst F: drivers/hid/usbhid/ USB INTEL XHCI ROLE MUX DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/roles/intel-xhci-usb-role-switch.c @@ -25781,7 +25781,7 @@ F: Documentation/firmware-guide/acpi/intel-pmc-mux.rst F: drivers/usb/typec/mux/intel_pmc_mux.c USB TYPEC PI3USB30532 MUX DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-usb@vger.kernel.org S: Maintained F: drivers/usb/typec/mux/pi3usb30532.c @@ -25810,7 +25810,7 @@ F: drivers/usb/host/uhci* USB VIDEO CLASS M: Laurent Pinchart -M: Hans de Goede +M: Hans de Goede L: linux-media@vger.kernel.org S: Maintained W: http://www.ideasonboard.org/uvc/ @@ -26341,7 +26341,7 @@ F: include/uapi/linux/virtio_snd.h F: sound/virtio/* VIRTUAL BOX GUEST DEVICE DRIVER -M: Hans de Goede +M: Hans de Goede M: Arnd Bergmann M: Greg Kroah-Hartman S: Maintained @@ -26350,7 +26350,7 @@ F: include/linux/vbox_utils.h F: include/uapi/linux/vbox*.h VIRTUAL BOX SHARED FOLDER VFS DRIVER -M: Hans de Goede +M: Hans de Goede L: linux-fsdevel@vger.kernel.org S: Maintained F: fs/vboxsf/* @@ -26604,7 +26604,7 @@ F: drivers/mmc/host/wbsd.* WACOM PROTOCOL 4 SERIAL TABLETS M: Julian Squires -M: Hans de Goede +M: Hans de Goede L: linux-input@vger.kernel.org S: Maintained F: drivers/input/tablet/wacom_serial4.c @@ -26771,7 +26771,7 @@ F: include/linux/wwan.h F: include/uapi/linux/wwan.h X-POWERS AXP288 PMIC DRIVERS -M: Hans de Goede +M: Hans de Goede S: Maintained F: drivers/acpi/pmic/intel_pmic_xpower.c N: axp288 @@ -26863,14 +26863,14 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip.git x86/mm F: arch/x86/mm/ X86 PLATFORM ANDROID TABLETS DSDT FIXUP DRIVER -M: Hans de Goede +M: Hans de Goede L: platform-driver-x86@vger.kernel.org S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/pdx86/platform-drivers-x86.git F: drivers/platform/x86/x86-android-tablets/ X86 PLATFORM DRIVERS -M: Hans de Goede +M: Hans de Goede M: Ilpo Järvinen L: platform-driver-x86@vger.kernel.org S: Maintained From aa17c036a01654bb863f2f7793d01046ce9eef91 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Fri, 13 Jun 2025 09:15:57 +0200 Subject: [PATCH 048/310] media: exynos4-is: fimc-is: Fully open-code compatible for grepping It is very useful to find driver implementing compatibles with `git grep compatible`, so driver should not use defines for that string, even if this means string will be effectively duplicated. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Hans Verkuil --- drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c | 2 +- drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h | 2 -- drivers/media/platform/samsung/exynos4-is/fimc-is.c | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c index b243cbb1d010..b5b37b6f8fa8 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.c @@ -131,7 +131,7 @@ static const struct dev_pm_ops fimc_is_i2c_pm_ops = { }; static const struct of_device_id fimc_is_i2c_of_match[] = { - { .compatible = FIMC_IS_I2C_COMPATIBLE }, + { .compatible = "samsung,exynos4212-i2c-isp" }, { }, }; diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h index a23bd20be6c8..69d597e5c297 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is-i2c.h @@ -6,7 +6,5 @@ * Sylwester Nawrocki */ -#define FIMC_IS_I2C_COMPATIBLE "samsung,exynos4212-i2c-isp" - int fimc_is_register_i2c_driver(void); void fimc_is_unregister_i2c_driver(void); diff --git a/drivers/media/platform/samsung/exynos4-is/fimc-is.c b/drivers/media/platform/samsung/exynos4-is/fimc-is.c index 2e8fe9e49735..ed7b7ca16f71 100644 --- a/drivers/media/platform/samsung/exynos4-is/fimc-is.c +++ b/drivers/media/platform/samsung/exynos4-is/fimc-is.c @@ -207,7 +207,7 @@ static int fimc_is_register_subdevs(struct fimc_is *is) if (ret < 0) return ret; - for_each_compatible_node(i2c_bus, NULL, FIMC_IS_I2C_COMPATIBLE) { + for_each_compatible_node(i2c_bus, NULL, "samsung,exynos4212-i2c-isp") { for_each_available_child_of_node(i2c_bus, child) { ret = fimc_is_parse_sensor_config(is, index, child); From 487b12d6d0fc335d6fc4cb71f2358bb0eb0c1271 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Sat, 14 Jun 2025 21:37:23 +0100 Subject: [PATCH 049/310] media: amphion: Remove unused functions The functions: vpu_color_get_default() vpu_color_check_full_range() vpu_color_check_primaries() vpu_color_check_transfers() vpu_color_check_matrix() have been unused since 2022's commit 1925665ef403 ("media: amphion: remove redundant check of colorspace in venc_s_fmt") The (empty) function vpu_mbox_enable_rx() has been unused since it was added in 2022 by the commit 61cbf1c1fa6d ("media: amphion: implement vpu core communication based on mailbox") Remove them. Signed-off-by: Dr. David Alan Gilbert Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vpu_color.c | 73 -------------------- drivers/media/platform/amphion/vpu_helpers.h | 5 -- drivers/media/platform/amphion/vpu_mbox.c | 4 -- drivers/media/platform/amphion/vpu_mbox.h | 1 - 4 files changed, 83 deletions(-) diff --git a/drivers/media/platform/amphion/vpu_color.c b/drivers/media/platform/amphion/vpu_color.c index 4ae435cbc5cd..7c0ab8289a7b 100644 --- a/drivers/media/platform/amphion/vpu_color.c +++ b/drivers/media/platform/amphion/vpu_color.c @@ -108,76 +108,3 @@ u32 vpu_color_cvrt_full_range_i2v(u32 full_range) return V4L2_QUANTIZATION_LIM_RANGE; } - -int vpu_color_check_primaries(u32 primaries) -{ - return vpu_color_cvrt_primaries_v2i(primaries) ? 0 : -EINVAL; -} - -int vpu_color_check_transfers(u32 transfers) -{ - return vpu_color_cvrt_transfers_v2i(transfers) ? 0 : -EINVAL; -} - -int vpu_color_check_matrix(u32 matrix) -{ - return vpu_color_cvrt_matrix_v2i(matrix) ? 0 : -EINVAL; -} - -int vpu_color_check_full_range(u32 full_range) -{ - int ret = -EINVAL; - - switch (full_range) { - case V4L2_QUANTIZATION_FULL_RANGE: - case V4L2_QUANTIZATION_LIM_RANGE: - ret = 0; - break; - default: - break; - } - - return ret; -} - -int vpu_color_get_default(u32 primaries, u32 *ptransfers, u32 *pmatrix, u32 *pfull_range) -{ - u32 transfers; - u32 matrix; - u32 full_range; - - switch (primaries) { - case V4L2_COLORSPACE_REC709: - transfers = V4L2_XFER_FUNC_709; - matrix = V4L2_YCBCR_ENC_709; - break; - case V4L2_COLORSPACE_470_SYSTEM_M: - case V4L2_COLORSPACE_470_SYSTEM_BG: - case V4L2_COLORSPACE_SMPTE170M: - transfers = V4L2_XFER_FUNC_709; - matrix = V4L2_YCBCR_ENC_601; - break; - case V4L2_COLORSPACE_SMPTE240M: - transfers = V4L2_XFER_FUNC_SMPTE240M; - matrix = V4L2_YCBCR_ENC_SMPTE240M; - break; - case V4L2_COLORSPACE_BT2020: - transfers = V4L2_XFER_FUNC_709; - matrix = V4L2_YCBCR_ENC_BT2020; - break; - default: - transfers = V4L2_XFER_FUNC_DEFAULT; - matrix = V4L2_YCBCR_ENC_DEFAULT; - break; - } - full_range = V4L2_QUANTIZATION_LIM_RANGE; - - if (ptransfers) - *ptransfers = transfers; - if (pmatrix) - *pmatrix = matrix; - if (pfull_range) - *pfull_range = full_range; - - return 0; -} diff --git a/drivers/media/platform/amphion/vpu_helpers.h b/drivers/media/platform/amphion/vpu_helpers.h index 0eaddb07190d..84d16eb39650 100644 --- a/drivers/media/platform/amphion/vpu_helpers.h +++ b/drivers/media/platform/amphion/vpu_helpers.h @@ -54,10 +54,6 @@ static inline u8 vpu_helper_read_byte(struct vpu_buffer *stream_buffer, u32 pos) return pdata[pos % stream_buffer->length]; } -int vpu_color_check_primaries(u32 primaries); -int vpu_color_check_transfers(u32 transfers); -int vpu_color_check_matrix(u32 matrix); -int vpu_color_check_full_range(u32 full_range); u32 vpu_color_cvrt_primaries_v2i(u32 primaries); u32 vpu_color_cvrt_primaries_i2v(u32 primaries); u32 vpu_color_cvrt_transfers_v2i(u32 transfers); @@ -66,7 +62,6 @@ u32 vpu_color_cvrt_matrix_v2i(u32 matrix); u32 vpu_color_cvrt_matrix_i2v(u32 matrix); u32 vpu_color_cvrt_full_range_v2i(u32 full_range); u32 vpu_color_cvrt_full_range_i2v(u32 full_range); -int vpu_color_get_default(u32 primaries, u32 *ptransfers, u32 *pmatrix, u32 *pfull_range); int vpu_find_dst_by_src(struct vpu_pair *pairs, u32 cnt, u32 src); int vpu_find_src_by_dst(struct vpu_pair *pairs, u32 cnt, u32 dst); diff --git a/drivers/media/platform/amphion/vpu_mbox.c b/drivers/media/platform/amphion/vpu_mbox.c index c2963b8deb48..b2ac8de6a2d9 100644 --- a/drivers/media/platform/amphion/vpu_mbox.c +++ b/drivers/media/platform/amphion/vpu_mbox.c @@ -109,7 +109,3 @@ void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data) mbox_send_message(core->tx_data.ch, &data); mbox_send_message(core->tx_type.ch, &type); } - -void vpu_mbox_enable_rx(struct vpu_dev *dev) -{ -} diff --git a/drivers/media/platform/amphion/vpu_mbox.h b/drivers/media/platform/amphion/vpu_mbox.h index 79cfd874e92b..8b7aea4f606c 100644 --- a/drivers/media/platform/amphion/vpu_mbox.h +++ b/drivers/media/platform/amphion/vpu_mbox.h @@ -11,6 +11,5 @@ int vpu_mbox_request(struct vpu_core *core); void vpu_mbox_free(struct vpu_core *core); void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data); void vpu_mbox_send_type(struct vpu_core *core, u32 type); -void vpu_mbox_enable_rx(struct vpu_dev *dev); #endif From 9314891df119442a6ec1518b3d872c330e2bf1a1 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Mon, 16 Jun 2025 00:58:01 +0100 Subject: [PATCH 050/310] media: platform: ti: Remove unused vpdma_update_dma_addr vpdma_update_dma_addr() was added in 2016 as part of commit 2f88703a0bfd ("[media] media: ti-vpe: vpdma: Add multi-instance and multi-client support") but has remained unused. Remove it. I did see that there was a VIP driver submitted in 2020 that doesn't seem to have got merged which did use this (and a bunch of other unused functions). Signed-off-by: Dr. David Alan Gilbert Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/vpe/vpdma.c | 32 --------------------------- drivers/media/platform/ti/vpe/vpdma.h | 3 --- 2 files changed, 35 deletions(-) diff --git a/drivers/media/platform/ti/vpe/vpdma.c b/drivers/media/platform/ti/vpe/vpdma.c index da90d7f03f82..bb8a8bd7980c 100644 --- a/drivers/media/platform/ti/vpe/vpdma.c +++ b/drivers/media/platform/ti/vpe/vpdma.c @@ -552,38 +552,6 @@ EXPORT_SYMBOL(vpdma_submit_descs); static void dump_dtd(struct vpdma_dtd *dtd); -void vpdma_update_dma_addr(struct vpdma_data *vpdma, - struct vpdma_desc_list *list, dma_addr_t dma_addr, - void *write_dtd, int drop, int idx) -{ - struct vpdma_dtd *dtd = list->buf.addr; - dma_addr_t write_desc_addr; - int offset; - - dtd += idx; - vpdma_unmap_desc_buf(vpdma, &list->buf); - - dtd->start_addr = dma_addr; - - /* Calculate write address from the offset of write_dtd from start - * of the list->buf - */ - offset = (void *)write_dtd - list->buf.addr; - write_desc_addr = list->buf.dma_addr + offset; - - if (drop) - dtd->desc_write_addr = dtd_desc_write_addr(write_desc_addr, - 1, 1, 0); - else - dtd->desc_write_addr = dtd_desc_write_addr(write_desc_addr, - 1, 0, 0); - - vpdma_map_desc_buf(vpdma, &list->buf); - - dump_dtd(dtd); -} -EXPORT_SYMBOL(vpdma_update_dma_addr); - void vpdma_set_max_size(struct vpdma_data *vpdma, int reg_addr, u32 width, u32 height) { diff --git a/drivers/media/platform/ti/vpe/vpdma.h b/drivers/media/platform/ti/vpe/vpdma.h index 393fcbb3cb40..e4d7941c6207 100644 --- a/drivers/media/platform/ti/vpe/vpdma.h +++ b/drivers/media/platform/ti/vpe/vpdma.h @@ -222,9 +222,6 @@ void vpdma_free_desc_list(struct vpdma_desc_list *list); int vpdma_submit_descs(struct vpdma_data *vpdma, struct vpdma_desc_list *list, int list_num); bool vpdma_list_busy(struct vpdma_data *vpdma, int list_num); -void vpdma_update_dma_addr(struct vpdma_data *vpdma, - struct vpdma_desc_list *list, dma_addr_t dma_addr, - void *write_dtd, int drop, int idx); /* VPDMA hardware list funcs */ int vpdma_hwlist_alloc(struct vpdma_data *vpdma, void *priv); From c3021d6a80ff05034dfee494115ec71f1954e311 Mon Sep 17 00:00:00 2001 From: "Dr. David Alan Gilbert" Date: Mon, 16 Jun 2025 01:23:59 +0100 Subject: [PATCH 051/310] media: v4l2-jpeg: Remove unused v4l2_jpeg_parse_* wrappers The functions: v4l2_jpeg_parse_huffman_tables() v4l2_jpeg_parse_quantization_tables() v4l2_jpeg_parse_scan_header() and v4l2_jpeg_parse_frame_header() were added in 2020 by commit 50733b5b9102 ("media: add v4l2 JPEG helpers") but have remained unused. Remove them. They're all just wrappers around an underlying set of helpers, which are all still called via v4l2_jpeg_parse_header(). Signed-off-by: Dr. David Alan Gilbert Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-jpeg.c | 80 ----------------------------- include/media/v4l2-jpeg.h | 9 ---- 2 files changed, 89 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-jpeg.c b/drivers/media/v4l2-core/v4l2-jpeg.c index 6e2647323522..36a0f1a1b0d9 100644 --- a/drivers/media/v4l2-core/v4l2-jpeg.c +++ b/drivers/media/v4l2-core/v4l2-jpeg.c @@ -711,83 +711,3 @@ int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out) return marker; } EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_header); - -/** - * v4l2_jpeg_parse_frame_header - parse frame header - * @buf: address of the frame header, after the SOF0 marker - * @len: length of the frame header - * @frame_header: returns the parsed frame header - * - * Returns 0 or negative error if parsing failed. - */ -int v4l2_jpeg_parse_frame_header(void *buf, size_t len, - struct v4l2_jpeg_frame_header *frame_header) -{ - struct jpeg_stream stream; - - stream.curr = buf; - stream.end = stream.curr + len; - return jpeg_parse_frame_header(&stream, SOF0, frame_header); -} -EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_frame_header); - -/** - * v4l2_jpeg_parse_scan_header - parse scan header - * @buf: address of the scan header, after the SOS marker - * @len: length of the scan header - * @scan_header: returns the parsed scan header - * - * Returns 0 or negative error if parsing failed. - */ -int v4l2_jpeg_parse_scan_header(void *buf, size_t len, - struct v4l2_jpeg_scan_header *scan_header) -{ - struct jpeg_stream stream; - - stream.curr = buf; - stream.end = stream.curr + len; - return jpeg_parse_scan_header(&stream, scan_header); -} -EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_scan_header); - -/** - * v4l2_jpeg_parse_quantization_tables - parse quantization tables segment - * @buf: address of the quantization table segment, after the DQT marker - * @len: length of the quantization table segment - * @precision: sample precision (P) in bits per component - * @q_tables: returns four references into the buffer for the - * four possible quantization table destinations - * - * Returns 0 or negative error if parsing failed. - */ -int v4l2_jpeg_parse_quantization_tables(void *buf, size_t len, u8 precision, - struct v4l2_jpeg_reference *q_tables) -{ - struct jpeg_stream stream; - - stream.curr = buf; - stream.end = stream.curr + len; - return jpeg_parse_quantization_tables(&stream, precision, q_tables); -} -EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_quantization_tables); - -/** - * v4l2_jpeg_parse_huffman_tables - parse huffman tables segment - * @buf: address of the Huffman table segment, after the DHT marker - * @len: length of the Huffman table segment - * @huffman_tables: returns four references into the buffer for the - * four possible Huffman table destinations, in - * the order DC0, DC1, AC0, AC1 - * - * Returns 0 or negative error if parsing failed. - */ -int v4l2_jpeg_parse_huffman_tables(void *buf, size_t len, - struct v4l2_jpeg_reference *huffman_tables) -{ - struct jpeg_stream stream; - - stream.curr = buf; - stream.end = stream.curr + len; - return jpeg_parse_huffman_tables(&stream, huffman_tables); -} -EXPORT_SYMBOL_GPL(v4l2_jpeg_parse_huffman_tables); diff --git a/include/media/v4l2-jpeg.h b/include/media/v4l2-jpeg.h index b65658a02e3c..62dda1560275 100644 --- a/include/media/v4l2-jpeg.h +++ b/include/media/v4l2-jpeg.h @@ -169,15 +169,6 @@ struct v4l2_jpeg_header { int v4l2_jpeg_parse_header(void *buf, size_t len, struct v4l2_jpeg_header *out); -int v4l2_jpeg_parse_frame_header(void *buf, size_t len, - struct v4l2_jpeg_frame_header *frame_header); -int v4l2_jpeg_parse_scan_header(void *buf, size_t len, - struct v4l2_jpeg_scan_header *scan_header); -int v4l2_jpeg_parse_quantization_tables(void *buf, size_t len, u8 precision, - struct v4l2_jpeg_reference *q_tables); -int v4l2_jpeg_parse_huffman_tables(void *buf, size_t len, - struct v4l2_jpeg_reference *huffman_tables); - extern const u8 v4l2_jpeg_zigzag_scan_index[V4L2_JPEG_PIXELS_IN_BLOCK]; extern const u8 v4l2_jpeg_ref_table_luma_qt[V4L2_JPEG_PIXELS_IN_BLOCK]; extern const u8 v4l2_jpeg_ref_table_chroma_qt[V4L2_JPEG_PIXELS_IN_BLOCK]; From d8192cc01a11e40fa18da44c00c25df5c7beac53 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 11 Jun 2025 19:37:13 +0100 Subject: [PATCH 052/310] media: tc358743: Add support for 972Mbit/s link freq Adds register setups for running the CSI lanes at 972Mbit/s, which allows 1080P50 UYVY down 2 lanes. Signed-off-by: Dave Stevenson Signed-off-by: Hans Verkuil --- drivers/media/i2c/tc358743.c | 48 +++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 3d6703b75bfa..b87db3de20db 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1993,6 +1993,7 @@ static int tc358743_probe_of(struct tc358743_state *state) /* * The CSI bps per lane must be between 62.5 Mbps and 1 Gbps. * The default is 594 Mbps for 4-lane 1080p60 or 2-lane 720p60. + * 972 Mbps allows 1080P50 UYVY over 2-lane. */ bps_pr_lane = 2 * endpoint.link_frequencies[0]; if (bps_pr_lane < 62500000U || bps_pr_lane > 1000000000U) { @@ -2006,23 +2007,42 @@ static int tc358743_probe_of(struct tc358743_state *state) state->pdata.refclk_hz * state->pdata.pll_prd; /* - * FIXME: These timings are from REF_02 for 594 Mbps per lane (297 MHz - * link frequency). In principle it should be possible to calculate + * FIXME: These timings are from REF_02 for 594 or 972 Mbps per lane + * (297 MHz or 486 MHz link frequency). + * In principle it should be possible to calculate * them based on link frequency and resolution. */ - if (bps_pr_lane != 594000000U) + switch (bps_pr_lane) { + default: dev_warn(dev, "untested bps per lane: %u bps\n", bps_pr_lane); - state->pdata.lineinitcnt = 0xe80; - state->pdata.lptxtimecnt = 0x003; - /* tclk-preparecnt: 3, tclk-zerocnt: 20 */ - state->pdata.tclk_headercnt = 0x1403; - state->pdata.tclk_trailcnt = 0x00; - /* ths-preparecnt: 3, ths-zerocnt: 1 */ - state->pdata.ths_headercnt = 0x0103; - state->pdata.twakeup = 0x4882; - state->pdata.tclk_postcnt = 0x008; - state->pdata.ths_trailcnt = 0x2; - state->pdata.hstxvregcnt = 0; + fallthrough; + case 594000000U: + state->pdata.lineinitcnt = 0xe80; + state->pdata.lptxtimecnt = 0x003; + /* tclk-preparecnt: 3, tclk-zerocnt: 20 */ + state->pdata.tclk_headercnt = 0x1403; + state->pdata.tclk_trailcnt = 0x00; + /* ths-preparecnt: 3, ths-zerocnt: 1 */ + state->pdata.ths_headercnt = 0x0103; + state->pdata.twakeup = 0x4882; + state->pdata.tclk_postcnt = 0x008; + state->pdata.ths_trailcnt = 0x2; + state->pdata.hstxvregcnt = 0; + break; + case 972000000U: + state->pdata.lineinitcnt = 0x1b58; + state->pdata.lptxtimecnt = 0x007; + /* tclk-preparecnt: 6, tclk-zerocnt: 40 */ + state->pdata.tclk_headercnt = 0x2806; + state->pdata.tclk_trailcnt = 0x00; + /* ths-preparecnt: 6, ths-zerocnt: 8 */ + state->pdata.ths_headercnt = 0x0806; + state->pdata.twakeup = 0x4268; + state->pdata.tclk_postcnt = 0x008; + state->pdata.ths_trailcnt = 0x5; + state->pdata.hstxvregcnt = 0; + break; + } state->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_LOW); From 86addd25314a1e77dbdcfddfeed0bab2f27da0e2 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 11 Jun 2025 19:37:14 +0100 Subject: [PATCH 053/310] media: tc358743: Increase FIFO trigger level to 374 The existing fixed value of 16 worked for UYVY 720P60 over 2 lanes at 594MHz, or UYVY 1080P60 over 4 lanes. (RGB888 1080P60 needs 6 lanes at 594MHz). It doesn't allow for lower resolutions to work as the FIFO underflows. 374 is required for 1080P24 or 1080P30 UYVY over 2 lanes @ 972Mbit/s, but >374 means that the FIFO underflows on 1080P50 UYVY over 2 lanes @ 972Mbit/s. Whilst it would be nice to compute it, the required information isn't published by Toshiba. Signed-off-by: Dave Stevenson Signed-off-by: Hans Verkuil --- drivers/media/i2c/tc358743.c | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index b87db3de20db..ddba8c392ead 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1972,8 +1972,19 @@ static int tc358743_probe_of(struct tc358743_state *state) state->pdata.refclk_hz = clk_get_rate(refclk); state->pdata.ddc5v_delay = DDC5V_DELAY_100_MS; state->pdata.enable_hdcp = false; - /* A FIFO level of 16 should be enough for 2-lane 720p60 at 594 MHz. */ - state->pdata.fifo_level = 16; + /* + * Ideally the FIFO trigger level should be set based on the input and + * output data rates, but the calculations required are buried in + * Toshiba's register settings spreadsheet. + * A value of 16 works with a 594Mbps data rate for 720p60 (using 2 + * lanes) and 1080p60 (using 4 lanes), but fails when the data rate + * is increased, or a lower pixel clock is used that result in CSI + * reading out faster than the data is arriving. + * + * A value of 374 works with both those modes at 594Mbps, and with most + * modes on 972Mbps. + */ + state->pdata.fifo_level = 374; /* * The PLL input clock is obtained by dividing refclk by pll_prd. * It must be between 6 MHz and 40 MHz, lower frequency is better. From 303d81635e1d9c949b370215cc94526ed81f2e3d Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 11 Jun 2025 19:37:15 +0100 Subject: [PATCH 054/310] media: tc358743: Check I2C succeeded during probe The probe for the TC358743 reads the CHIPID register from the device and compares it to the expected value of 0. If the I2C request fails then that also returns 0, so the driver loads thinking that the device is there. Generally I2C communications are reliable so there is limited need to check the return value on every transfer, therefore only amend the one read during probe to check for I2C errors. Signed-off-by: Dave Stevenson Signed-off-by: Hans Verkuil --- drivers/media/i2c/tc358743.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index ddba8c392ead..4d3dc61a9b8b 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -114,7 +114,7 @@ static inline struct tc358743_state *to_state(struct v4l2_subdev *sd) /* --------------- I2C --------------- */ -static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) +static int i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) { struct tc358743_state *state = to_state(sd); struct i2c_client *client = state->i2c_client; @@ -140,6 +140,7 @@ static void i2c_rd(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) v4l2_err(sd, "%s: reading register 0x%x from 0x%x failed: %d\n", __func__, reg, client->addr, err); } + return err != ARRAY_SIZE(msgs); } static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) @@ -196,15 +197,24 @@ static void i2c_wr(struct v4l2_subdev *sd, u16 reg, u8 *values, u32 n) } } -static noinline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n) +static noinline u32 i2c_rdreg_err(struct v4l2_subdev *sd, u16 reg, u32 n, + int *err) { + int error; __le32 val = 0; - i2c_rd(sd, reg, (u8 __force *)&val, n); + error = i2c_rd(sd, reg, (u8 __force *)&val, n); + if (err) + *err = error; return le32_to_cpu(val); } +static inline u32 i2c_rdreg(struct v4l2_subdev *sd, u16 reg, u32 n) +{ + return i2c_rdreg_err(sd, reg, n, NULL); +} + static noinline void i2c_wrreg(struct v4l2_subdev *sd, u16 reg, u32 val, u32 n) { __le32 raw = cpu_to_le32(val); @@ -233,6 +243,13 @@ static u16 i2c_rd16(struct v4l2_subdev *sd, u16 reg) return i2c_rdreg(sd, reg, 2); } +static int i2c_rd16_err(struct v4l2_subdev *sd, u16 reg, u16 *value) +{ + int err; + *value = i2c_rdreg_err(sd, reg, 2, &err); + return err; +} + static void i2c_wr16(struct v4l2_subdev *sd, u16 reg, u16 val) { i2c_wrreg(sd, reg, val, 2); @@ -2092,6 +2109,7 @@ static int tc358743_probe(struct i2c_client *client) struct tc358743_platform_data *pdata = client->dev.platform_data; struct v4l2_subdev *sd; u16 irq_mask = MASK_HDMI_MSK | MASK_CSI_MSK; + u16 chipid; int err; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) @@ -2123,7 +2141,8 @@ static int tc358743_probe(struct i2c_client *client) sd->flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | V4L2_SUBDEV_FL_HAS_EVENTS; /* i2c access */ - if ((i2c_rd16(sd, CHIPID) & MASK_CHIPID) != 0) { + if (i2c_rd16_err(sd, CHIPID, &chipid) || + (chipid & MASK_CHIPID) != 0) { v4l2_info(sd, "not a TC358743 on address 0x%x\n", client->addr << 1); return -ENODEV; From 377cc006a364dfdab2f3f221cfad63a9265200b8 Mon Sep 17 00:00:00 2001 From: Dave Stevenson Date: Wed, 11 Jun 2025 19:37:16 +0100 Subject: [PATCH 055/310] media: tc358743: Return an appropriate colorspace from tc358743_set_fmt When calling tc358743_set_fmt, the code was calling tc358743_get_fmt to choose a valid format. However that sets the colorspace based on information read back from the chip, not the colour format requested. The result was that if you called try or set format for UYVY when the current format was RGB3 then you would get told SRGB, and try RGB3 when current was UYVY and you would get told SMPTE170M. The value programmed in the VI_REP register for the colorspace is always set by this driver, therefore there is no need to read back the value, and never set to REC709. Return the colorspace based on the format set/tried instead. Signed-off-by: Dave Stevenson Signed-off-by: Hans Verkuil --- drivers/media/i2c/tc358743.c | 44 ++++++++++++++---------------------- 1 file changed, 17 insertions(+), 27 deletions(-) diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 4d3dc61a9b8b..37ebc760f73b 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -1708,12 +1708,23 @@ static int tc358743_enum_mbus_code(struct v4l2_subdev *sd, return 0; } +static u32 tc358743_g_colorspace(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB888_1X24: + return V4L2_COLORSPACE_SRGB; + case MEDIA_BUS_FMT_UYVY8_1X16: + return V4L2_COLORSPACE_SMPTE170M; + default: + return 0; + } +} + static int tc358743_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { struct tc358743_state *state = to_state(sd); - u8 vi_rep = i2c_rd8(sd, VI_REP); if (format->pad != 0) return -EINVAL; @@ -1723,23 +1734,7 @@ static int tc358743_get_fmt(struct v4l2_subdev *sd, format->format.height = state->timings.bt.height; format->format.field = V4L2_FIELD_NONE; - switch (vi_rep & MASK_VOUT_COLOR_SEL) { - case MASK_VOUT_COLOR_RGB_FULL: - case MASK_VOUT_COLOR_RGB_LIMITED: - format->format.colorspace = V4L2_COLORSPACE_SRGB; - break; - case MASK_VOUT_COLOR_601_YCBCR_LIMITED: - case MASK_VOUT_COLOR_601_YCBCR_FULL: - format->format.colorspace = V4L2_COLORSPACE_SMPTE170M; - break; - case MASK_VOUT_COLOR_709_YCBCR_FULL: - case MASK_VOUT_COLOR_709_YCBCR_LIMITED: - format->format.colorspace = V4L2_COLORSPACE_REC709; - break; - default: - format->format.colorspace = 0; - break; - } + format->format.colorspace = tc358743_g_colorspace(format->format.code); return 0; } @@ -1753,19 +1748,14 @@ static int tc358743_set_fmt(struct v4l2_subdev *sd, u32 code = format->format.code; /* is overwritten by get_fmt */ int ret = tc358743_get_fmt(sd, sd_state, format); - format->format.code = code; + if (code == MEDIA_BUS_FMT_RGB888_1X24 || + code == MEDIA_BUS_FMT_UYVY8_1X16) + format->format.code = code; + format->format.colorspace = tc358743_g_colorspace(format->format.code); if (ret) return ret; - switch (code) { - case MEDIA_BUS_FMT_RGB888_1X24: - case MEDIA_BUS_FMT_UYVY8_1X16: - break; - default: - return -EINVAL; - } - if (format->which == V4L2_SUBDEV_FORMAT_TRY) return 0; From d2c063388df512b42fd8578768adb1846905e064 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Fri, 23 Aug 2024 17:02:15 +0800 Subject: [PATCH 056/310] media: samsung: exynos4-is: Use for_each_available_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Hans Verkuil --- .../platform/samsung/exynos4-is/media-dev.c | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/drivers/media/platform/samsung/exynos4-is/media-dev.c b/drivers/media/platform/samsung/exynos4-is/media-dev.c index b5ee3c547789..c781853586fd 100644 --- a/drivers/media/platform/samsung/exynos4-is/media-dev.c +++ b/drivers/media/platform/samsung/exynos4-is/media-dev.c @@ -482,15 +482,12 @@ static int fimc_md_parse_one_endpoint(struct fimc_md *fmd, static int fimc_md_parse_port_node(struct fimc_md *fmd, struct device_node *port) { - struct device_node *ep; int ret; - for_each_child_of_node(port, ep) { + for_each_child_of_node_scoped(port, ep) { ret = fimc_md_parse_one_endpoint(fmd, ep); - if (ret < 0) { - of_node_put(ep); + if (ret < 0) return ret; - } } return 0; @@ -501,7 +498,6 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd) { struct device_node *parent = fmd->pdev->dev.of_node; struct device_node *ports = NULL; - struct device_node *node; int ret; /* @@ -518,7 +514,7 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd) fmd->num_sensors = 0; /* Attach sensors linked to MIPI CSI-2 receivers */ - for_each_available_child_of_node(parent, node) { + for_each_available_child_of_node_scoped(parent, node) { struct device_node *port; if (!of_node_name_eq(node, "csis")) @@ -530,10 +526,8 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd) ret = fimc_md_parse_port_node(fmd, port); of_node_put(port); - if (ret < 0) { - of_node_put(node); + if (ret < 0) goto cleanup; - } } /* Attach sensors listed in the parallel-ports node */ @@ -541,12 +535,10 @@ static int fimc_md_register_sensor_entities(struct fimc_md *fmd) if (!ports) goto rpm_put; - for_each_child_of_node(ports, node) { + for_each_child_of_node_scoped(ports, node) { ret = fimc_md_parse_port_node(fmd, node); - if (ret < 0) { - of_node_put(node); + if (ret < 0) goto cleanup; - } } of_node_put(ports); @@ -736,10 +728,9 @@ static int fimc_md_register_platform_entity(struct fimc_md *fmd, static int fimc_md_register_platform_entities(struct fimc_md *fmd, struct device_node *parent) { - struct device_node *node; int ret = 0; - for_each_available_child_of_node(parent, node) { + for_each_available_child_of_node_scoped(parent, node) { struct platform_device *pdev; int plat_entity = -1; @@ -762,10 +753,8 @@ static int fimc_md_register_platform_entities(struct fimc_md *fmd, ret = fimc_md_register_platform_entity(fmd, pdev, plat_entity); put_device(&pdev->dev); - if (ret < 0) { - of_node_put(node); + if (ret < 0) break; - } } return ret; From a7569179d3b9b341db28dc6b82f9d6507c66ec2d Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Fri, 23 Aug 2024 17:02:16 +0800 Subject: [PATCH 057/310] media: c8sectpfe: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Hans Verkuil --- .../st/sti/c8sectpfe/c8sectpfe-core.c | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c index 602c37cbe177..89bd15a4d26a 100644 --- a/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c +++ b/drivers/media/platform/st/sti/c8sectpfe/c8sectpfe-core.c @@ -658,7 +658,7 @@ static irqreturn_t c8sectpfe_error_irq_handler(int irq, void *priv) static int c8sectpfe_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct device_node *child, *np = dev->of_node; + struct device_node *np = dev->of_node; struct c8sectpfei *fei; struct resource *res; int ret, index = 0; @@ -742,17 +742,15 @@ static int c8sectpfe_probe(struct platform_device *pdev) return PTR_ERR(fei->pinctrl); } - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { struct device_node *i2c_bus; fei->channel_data[index] = devm_kzalloc(dev, sizeof(struct channel_info), GFP_KERNEL); - if (!fei->channel_data[index]) { - ret = -ENOMEM; - goto err_node_put; - } + if (!fei->channel_data[index]) + return -ENOMEM; tsin = fei->channel_data[index]; @@ -761,7 +759,7 @@ static int c8sectpfe_probe(struct platform_device *pdev) ret = of_property_read_u32(child, "tsin-num", &tsin->tsin_id); if (ret) { dev_err(&pdev->dev, "No tsin_num found\n"); - goto err_node_put; + return ret; } /* sanity check value */ @@ -769,8 +767,7 @@ static int c8sectpfe_probe(struct platform_device *pdev) dev_err(&pdev->dev, "tsin-num %d specified greater than number\n\tof input block hw in SoC! (%d)", tsin->tsin_id, fei->hw_stats.num_ib); - ret = -EINVAL; - goto err_node_put; + return -EINVAL; } tsin->invert_ts_clk = of_property_read_bool(child, @@ -786,22 +783,20 @@ static int c8sectpfe_probe(struct platform_device *pdev) &tsin->dvb_card); if (ret) { dev_err(&pdev->dev, "No dvb-card found\n"); - goto err_node_put; + return ret; } i2c_bus = of_parse_phandle(child, "i2c-bus", 0); if (!i2c_bus) { dev_err(&pdev->dev, "No i2c-bus found\n"); - ret = -ENODEV; - goto err_node_put; + return -ENODEV; } tsin->i2c_adapter = of_find_i2c_adapter_by_node(i2c_bus); of_node_put(i2c_bus); if (!tsin->i2c_adapter) { dev_err(&pdev->dev, "No i2c adapter found\n"); - ret = -ENODEV; - goto err_node_put; + return -ENODEV; } /* Acquire reset GPIO and activate it */ @@ -813,7 +808,7 @@ static int c8sectpfe_probe(struct platform_device *pdev) if (ret && ret != -EBUSY) { dev_err(dev, "Can't request tsin%d reset gpio\n", fei->channel_data[index]->tsin_id); - goto err_node_put; + return ret; } if (!ret) { @@ -855,10 +850,6 @@ static int c8sectpfe_probe(struct platform_device *pdev) c8sectpfe_debugfs_init(fei); return 0; - -err_node_put: - of_node_put(child); - return ret; } static void c8sectpfe_remove(struct platform_device *pdev) @@ -897,16 +888,15 @@ static void c8sectpfe_remove(struct platform_device *pdev) static int configure_channels(struct c8sectpfei *fei) { int index = 0, ret; - struct device_node *child, *np = fei->dev->of_node; + struct device_node *np = fei->dev->of_node; /* iterate round each tsin and configure memdma descriptor and IB hw */ - for_each_child_of_node(np, child) { + for_each_child_of_node_scoped(np, child) { ret = configure_memdma_and_inputblock(fei, fei->channel_data[index]); if (ret) { dev_err(fei->dev, "configure_memdma_and_inputblock failed\n"); - of_node_put(child); goto err_unmap; } index++; From 5211354f2cc081c236dc0c21d04d09c1ccb48b88 Mon Sep 17 00:00:00 2001 From: Jinjie Ruan Date: Fri, 23 Aug 2024 17:02:17 +0800 Subject: [PATCH 058/310] media: xilinx-vipp: Use for_each_child_of_node_scoped() Avoids the need for manual cleanup of_node_put() in early exits from the loop. Signed-off-by: Jinjie Ruan Signed-off-by: Hans Verkuil --- drivers/media/platform/xilinx/xilinx-vipp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/xilinx/xilinx-vipp.c b/drivers/media/platform/xilinx/xilinx-vipp.c index 024b439feec9..30675f681410 100644 --- a/drivers/media/platform/xilinx/xilinx-vipp.c +++ b/drivers/media/platform/xilinx/xilinx-vipp.c @@ -450,7 +450,6 @@ static int xvip_graph_dma_init_one(struct xvip_composite_device *xdev, static int xvip_graph_dma_init(struct xvip_composite_device *xdev) { struct device_node *ports; - struct device_node *port; int ret = 0; ports = of_get_child_by_name(xdev->dev->of_node, "ports"); @@ -459,12 +458,10 @@ static int xvip_graph_dma_init(struct xvip_composite_device *xdev) return -EINVAL; } - for_each_child_of_node(ports, port) { + for_each_child_of_node_scoped(ports, port) { ret = xvip_graph_dma_init_one(xdev, port); - if (ret) { - of_node_put(port); + if (ret) break; - } } of_node_put(ports); From 57b5a302b5d529db96ddc52fbccec005092ebb3d Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Tue, 29 Oct 2024 14:47:05 +0200 Subject: [PATCH 059/310] media: raspberrypi: cfe: Fix min_reqbufs_allocation The driver checks if "vq->max_num_buffers + *nbuffers < 3", but vq->max_num_buffers is (by default) 32, so the check is never true. Nor does the check make sense. The original code in the BSP kernel was "vq->num_buffers + *nbuffers < 3", but got mangled along the way to upstream. The intention was to make sure that at least 3 buffers are allocated. Fix this by removing the bad lines and setting q->min_reqbufs_allocation to three. Signed-off-by: Tomi Valkeinen Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- drivers/media/platform/raspberrypi/rp1-cfe/cfe.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/drivers/media/platform/raspberrypi/rp1-cfe/cfe.c b/drivers/media/platform/raspberrypi/rp1-cfe/cfe.c index fcadb2143c88..62dca76b468d 100644 --- a/drivers/media/platform/raspberrypi/rp1-cfe/cfe.c +++ b/drivers/media/platform/raspberrypi/rp1-cfe/cfe.c @@ -1024,9 +1024,6 @@ static int cfe_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, cfe_dbg(cfe, "%s: [%s] type:%u\n", __func__, node_desc[node->id].name, node->buffer_queue.type); - if (vq->max_num_buffers + *nbuffers < 3) - *nbuffers = 3 - vq->max_num_buffers; - if (*nplanes) { if (sizes[0] < size) { cfe_err(cfe, "sizes[0] %i < size %u\n", sizes[0], size); @@ -1998,6 +1995,7 @@ static int cfe_register_node(struct cfe_device *cfe, int id) q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; q->lock = &node->lock; q->min_queued_buffers = 1; + q->min_reqbufs_allocation = 3; q->dev = &cfe->pdev->dev; ret = vb2_queue_init(q); From 47e0452b40af6bf821a96e34d95fd5915fe4c7a6 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Thu, 15 May 2025 16:07:48 +0800 Subject: [PATCH 060/310] media: media/test_drivers: Replace open-coded parity calculation with parity8() Refactor parity calculations to use the standard parity8() helper. This change eliminates redundant implementations. Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Signed-off-by: Kuan-Wei Chiu Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- drivers/media/test-drivers/vivid/vivid-vbi-gen.c | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c index 70a4024d461e..e0f4151bda18 100644 --- a/drivers/media/test-drivers/vivid/vivid-vbi-gen.c +++ b/drivers/media/test-drivers/vivid/vivid-vbi-gen.c @@ -5,6 +5,7 @@ * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved. */ +#include #include #include #include @@ -165,12 +166,7 @@ static const u8 vivid_cc_sequence2[30] = { static u8 calc_parity(u8 val) { - unsigned i; - unsigned tot = 0; - - for (i = 0; i < 7; i++) - tot += (val & (1 << i)) ? 1 : 0; - return val | ((tot & 1) ? 0 : 0x80); + return val | (parity8(val) ? 0 : 0x80); } static void vivid_vbi_gen_set_time_of_day(u8 *packet) From df698f3d10871a6ca8446d8edb87a89433e4029e Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Thu, 15 May 2025 16:09:33 +0800 Subject: [PATCH 061/310] media: pci: cx18-av-vbi: Replace open-coded parity calculation with parity8() Refactor parity calculations to use the standard parity8() helper. This change eliminates redundant implementations. Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Signed-off-by: Kuan-Wei Chiu Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- drivers/media/pci/cx18/cx18-av-vbi.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/media/pci/cx18/cx18-av-vbi.c b/drivers/media/pci/cx18/cx18-av-vbi.c index 65281d40c681..1a113aad9cd4 100644 --- a/drivers/media/pci/cx18/cx18-av-vbi.c +++ b/drivers/media/pci/cx18/cx18-av-vbi.c @@ -8,6 +8,7 @@ */ +#include #include "cx18-driver.h" /* @@ -56,15 +57,6 @@ struct vbi_anc_data { /* u8 fill[]; Variable number of fill bytes */ }; -static int odd_parity(u8 c) -{ - c ^= (c >> 4); - c ^= (c >> 2); - c ^= (c >> 1); - - return c & 1; -} - static int decode_vps(u8 *dst, u8 *p) { static const u8 biphase_tbl[] = { @@ -278,7 +270,7 @@ int cx18_av_decode_vbi_line(struct v4l2_subdev *sd, break; case 6: sdid = V4L2_SLICED_CAPTION_525; - err = !odd_parity(p[0]) || !odd_parity(p[1]); + err = !parity8(p[0]) || !parity8(p[1]); break; case 9: sdid = V4L2_SLICED_VPS; From 118f2834b27ffe06a1badcd74fa35bb14cf1d339 Mon Sep 17 00:00:00 2001 From: Kuan-Wei Chiu Date: Thu, 15 May 2025 16:11:09 +0800 Subject: [PATCH 062/310] media: saa7115: Replace open-coded parity calculation with parity8() Refactor parity calculations to use the standard parity8() helper. This change eliminates redundant implementations. Co-developed-by: Yu-Chun Lin Signed-off-by: Yu-Chun Lin Signed-off-by: Kuan-Wei Chiu Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- drivers/media/i2c/saa7115.c | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/drivers/media/i2c/saa7115.c b/drivers/media/i2c/saa7115.c index a1c71187e773..b8b8f206ec3a 100644 --- a/drivers/media/i2c/saa7115.c +++ b/drivers/media/i2c/saa7115.c @@ -25,6 +25,7 @@ #include "saa711x_regs.h" +#include #include #include #include @@ -664,15 +665,6 @@ static const unsigned char saa7115_init_misc[] = { 0x00, 0x00 }; -static int saa711x_odd_parity(u8 c) -{ - c ^= (c >> 4); - c ^= (c >> 2); - c ^= (c >> 1); - - return c & 1; -} - static int saa711x_decode_vps(u8 *dst, u8 *p) { static const u8 biphase_tbl[] = { @@ -1227,7 +1219,7 @@ static int saa711x_decode_vbi_line(struct v4l2_subdev *sd, struct v4l2_decode_vb vbi->type = V4L2_SLICED_TELETEXT_B; break; case 4: - if (!saa711x_odd_parity(p[0]) || !saa711x_odd_parity(p[1])) + if (!parity8(p[0]) || !parity8(p[1])) return 0; vbi->type = V4L2_SLICED_CAPTION_525; break; From 8e5e3da9c63295e5a21ba70e63f17f2a790529c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 16 Jun 2025 20:57:20 +0200 Subject: [PATCH 063/310] media: rcar-vin: Fold interrupt helpers into only callers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The call sites using the interrupt helper functions have all been reworked to only one for each. Fold each of them into the only call sites left. This fixes a possible interrupt loss in case an interrupt occurs between reading VNINTS_REG in rvin_get_interrupt_status() and reading it again in rvin_ack_interrupt(). While at it rename the variable holding the current interrupt status to make the code easier to read. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616185722.980722-2-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rcar-vin/rcar-dma.c | 27 +++++-------------- 1 file changed, 6 insertions(+), 21 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index d4faa5a4e757..e1dae46b06d4 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -903,21 +903,6 @@ static int rvin_setup(struct rvin_dev *vin) return 0; } -static void rvin_disable_interrupts(struct rvin_dev *vin) -{ - rvin_write(vin, 0, VNIE_REG); -} - -static u32 rvin_get_interrupt_status(struct rvin_dev *vin) -{ - return rvin_read(vin, VNINTS_REG); -} - -static void rvin_ack_interrupt(struct rvin_dev *vin) -{ - rvin_write(vin, rvin_read(vin, VNINTS_REG), VNINTS_REG); -} - static bool rvin_capture_active(struct rvin_dev *vin) { return rvin_read(vin, VNMS_REG) & VNMS_CA; @@ -1040,22 +1025,22 @@ static void rvin_capture_stop(struct rvin_dev *vin) static irqreturn_t rvin_irq(int irq, void *data) { struct rvin_dev *vin = data; - u32 int_status, vnms; + u32 status, vnms; int slot; unsigned int handled = 0; unsigned long flags; spin_lock_irqsave(&vin->qlock, flags); - int_status = rvin_get_interrupt_status(vin); - if (!int_status) + status = rvin_read(vin, VNINTS_REG); + if (!status) goto done; - rvin_ack_interrupt(vin); + rvin_write(vin, status, VNINTS_REG); handled = 1; /* Nothing to do if nothing was captured. */ - if (!(int_status & VNINTS_FIS)) + if (!(status & VNINTS_FIS)) goto done; /* Nothing to do if not running. */ @@ -1400,7 +1385,7 @@ void rvin_stop_streaming(struct rvin_dev *vin) rvin_set_stream(vin, 0); /* disable interrupts */ - rvin_disable_interrupts(vin); + rvin_write(vin, 0, VNIE_REG); /* Return unprocessed buffers from hardware. */ for (unsigned int i = 0; i < HW_BUFFER_NUM; i++) { From 82bdeae10e3e0e88770f34813c7a0c67b1737d5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 16 Jun 2025 20:57:21 +0200 Subject: [PATCH 064/310] media: rcar-vin: Check for correct capture interrupt event MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Depending on if the capture session deals with fields or whole frames interrupts can be generated at an end of field, or end of frame event. The interrupt mask is setup to generate an interrupt on one of the two events depending on what is needed when the VIN is started. The end of field bit is set in both cases so controlling the mask that generates an interrupt have been enough to control the two use-cases. Before extending the interrupt handler to deal with other types of interrupt events it is needs to extended to "capture complete" check for correct the use-case in operation. Without this the simplification in the handler can result in corrupted frames when the mask on what type of events can generate an interrupt generated can no longer be assumed to only be an "capture complete" event. Which bit is checked matches which bit is enabled at configuration time as which event can generate an interrupt for "capture complete". There is no functional change. While at it switch to use the BIT() macro to describe the bit positions for the interrupt functions. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616185722.980722-3-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-vin/rcar-dma.c | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index e1dae46b06d4..19ff190f0fb2 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -114,11 +114,12 @@ #define VNFC_S_FRAME (1 << 0) /* Video n Interrupt Enable Register bits */ -#define VNIE_FIE (1 << 4) -#define VNIE_EFE (1 << 1) +#define VNIE_FIE BIT(4) +#define VNIE_EFE BIT(1) /* Video n Interrupt Status Register bits */ -#define VNINTS_FIS (1 << 4) +#define VNINTS_FIS BIT(4) +#define VNINTS_EFS BIT(1) /* Video n Data Mode Register bits */ #define VNDMR_A8BIT(n) (((n) & 0xff) << 24) @@ -1025,7 +1026,7 @@ static void rvin_capture_stop(struct rvin_dev *vin) static irqreturn_t rvin_irq(int irq, void *data) { struct rvin_dev *vin = data; - u32 status, vnms; + u32 capture, status, vnms; int slot; unsigned int handled = 0; unsigned long flags; @@ -1040,7 +1041,10 @@ static irqreturn_t rvin_irq(int irq, void *data) handled = 1; /* Nothing to do if nothing was captured. */ - if (!(status & VNINTS_FIS)) + capture = vin->format.field == V4L2_FIELD_NONE || + vin->format.field == V4L2_FIELD_ALTERNATE ? + VNINTS_FIS : VNINTS_EFS; + if (!(status & capture)) goto done; /* Nothing to do if not running. */ From d883f2e7f47a39873c957175c9e1cc7df093447f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Mon, 16 Jun 2025 20:57:22 +0200 Subject: [PATCH 065/310] media: rcar-vin: Generate FRAME_SYNC events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable the VSYNC Rising Edge Detection interrupt and generate a FRAME_SYNC event form it. The interrupt is available on all supported models of the VIN (Gen2, Gen3 and Gen4). Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616185722.980722-4-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../media/platform/renesas/rcar-vin/rcar-dma.c | 17 +++++++++++++++++ .../media/platform/renesas/rcar-vin/rcar-v4l2.c | 2 ++ 2 files changed, 19 insertions(+) diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c index 19ff190f0fb2..b619d1436a41 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-dma.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-dma.c @@ -13,6 +13,7 @@ #include #include +#include #include #include "rcar-vin.h" @@ -114,10 +115,14 @@ #define VNFC_S_FRAME (1 << 0) /* Video n Interrupt Enable Register bits */ +#define VNIE_VFE BIT(17) +#define VNIE_VRE BIT(16) #define VNIE_FIE BIT(4) #define VNIE_EFE BIT(1) /* Video n Interrupt Status Register bits */ +#define VNINTS_VFS BIT(17) +#define VNINTS_VRS BIT(16) #define VNINTS_FIS BIT(4) #define VNINTS_EFS BIT(1) @@ -889,6 +894,8 @@ static int rvin_setup(struct rvin_dev *vin) /* Progressive or interlaced mode */ interrupts = progressive ? VNIE_FIE : VNIE_EFE; + /* Enable VSYNC Rising Edge Detection. */ + interrupts |= VNIE_VRE; /* Ack interrupts */ rvin_write(vin, interrupts, VNINTS_REG); @@ -1040,6 +1047,16 @@ static irqreturn_t rvin_irq(int irq, void *data) rvin_write(vin, status, VNINTS_REG); handled = 1; + /* Signal Start of Frame. */ + if (status & VNINTS_VRS) { + struct v4l2_event event = { + .type = V4L2_EVENT_FRAME_SYNC, + .u.frame_sync.frame_sequence = vin->sequence, + }; + + v4l2_event_queue(&vin->vdev, &event); + } + /* Nothing to do if nothing was captured. */ capture = vin->format.field == V4L2_FIELD_NONE || vin->format.field == V4L2_FIELD_ALTERNATE ? diff --git a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c index 9b1e3a9d3249..62eddf3a35fc 100644 --- a/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c +++ b/drivers/media/platform/renesas/rcar-vin/rcar-v4l2.c @@ -489,6 +489,8 @@ static int rvin_subscribe_event(struct v4l2_fh *fh, const struct v4l2_event_subscription *sub) { switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 2, NULL); case V4L2_EVENT_SOURCE_CHANGE: return v4l2_event_subscribe(fh, sub, 4, NULL); } From 6c1dedf805ecd304236a83a2057ed803fb6b32f6 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 16 Jun 2025 14:14:24 +0200 Subject: [PATCH 066/310] media: rcar-fcp: Add rcar_fcp_soft_reset() Add a function to perform soft reset of the FCP. It is intended to support the correct stop procedure of the VSPX-FCPVX and VSPD-FCPD pairs according to section "62.3.7.3 Reset Operation" of the R-Car Hardware Manual at revision 1.20. Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616-vspx-reset-v2-1-6cc12ed7e9bb@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-fcp.c | 36 +++++++++++++++++++++++ include/media/rcar-fcp.h | 5 ++++ 2 files changed, 41 insertions(+) diff --git a/drivers/media/platform/renesas/rcar-fcp.c b/drivers/media/platform/renesas/rcar-fcp.c index cee9bbce4e3a..f90c86bbce6e 100644 --- a/drivers/media/platform/renesas/rcar-fcp.c +++ b/drivers/media/platform/renesas/rcar-fcp.c @@ -9,6 +9,8 @@ #include #include +#include +#include #include #include #include @@ -19,14 +21,25 @@ #include +#define RCAR_FCP_REG_RST 0x0010 +#define RCAR_FCP_REG_RST_SOFTRST BIT(0) +#define RCAR_FCP_REG_STA 0x0018 +#define RCAR_FCP_REG_STA_ACT BIT(0) + struct rcar_fcp_device { struct list_head list; struct device *dev; + void __iomem *base; }; static LIST_HEAD(fcp_devices); static DEFINE_MUTEX(fcp_lock); +static inline void rcar_fcp_write(struct rcar_fcp_device *fcp, u32 reg, u32 val) +{ + iowrite32(val, fcp->base + reg); +} + /* ----------------------------------------------------------------------------- * Public API */ @@ -117,6 +130,25 @@ void rcar_fcp_disable(struct rcar_fcp_device *fcp) } EXPORT_SYMBOL_GPL(rcar_fcp_disable); +int rcar_fcp_soft_reset(struct rcar_fcp_device *fcp) +{ + u32 value; + int ret; + + if (!fcp) + return 0; + + rcar_fcp_write(fcp, RCAR_FCP_REG_RST, RCAR_FCP_REG_RST_SOFTRST); + ret = readl_poll_timeout(fcp->base + RCAR_FCP_REG_STA, + value, !(value & RCAR_FCP_REG_STA_ACT), + 1, 100); + if (ret) + dev_err(fcp->dev, "Failed to soft-reset\n"); + + return ret; +} +EXPORT_SYMBOL_GPL(rcar_fcp_soft_reset); + /* ----------------------------------------------------------------------------- * Platform Driver */ @@ -131,6 +163,10 @@ static int rcar_fcp_probe(struct platform_device *pdev) fcp->dev = &pdev->dev; + fcp->base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(fcp->base)) + return PTR_ERR(fcp->base); + dma_set_max_seg_size(fcp->dev, UINT_MAX); pm_runtime_enable(&pdev->dev); diff --git a/include/media/rcar-fcp.h b/include/media/rcar-fcp.h index 179240fb163b..6ac9be9f675e 100644 --- a/include/media/rcar-fcp.h +++ b/include/media/rcar-fcp.h @@ -18,6 +18,7 @@ void rcar_fcp_put(struct rcar_fcp_device *fcp); struct device *rcar_fcp_get_device(struct rcar_fcp_device *fcp); int rcar_fcp_enable(struct rcar_fcp_device *fcp); void rcar_fcp_disable(struct rcar_fcp_device *fcp); +int rcar_fcp_soft_reset(struct rcar_fcp_device *fcp); #else static inline struct rcar_fcp_device *rcar_fcp_get(const struct device_node *np) { @@ -33,6 +34,10 @@ static inline int rcar_fcp_enable(struct rcar_fcp_device *fcp) return 0; } static inline void rcar_fcp_disable(struct rcar_fcp_device *fcp) { } +static inline int rcar_fcp_soft_reset(struct rcar_fcp_device *fcp) +{ + return 0; +} #endif #endif /* __MEDIA_RCAR_FCP_H__ */ From 9b7e558c5c72b67ae884709088026efc6b3b5b4a Mon Sep 17 00:00:00 2001 From: Koji Matsuoka Date: Mon, 16 Jun 2025 14:14:25 +0200 Subject: [PATCH 067/310] media: vsp1: Reset FCP after VSPD According to the R-Car Gen3 H/W manual v2.40, and R-Car Gen4 H/W manual v1.20, the FCP must be reset after resetting the VSPD, except for the VSPDL. Do so. Signed-off-by: Koji Matsuoka Signed-off-by: LUU HOAI Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616-vspx-reset-v2-2-6cc12ed7e9bb@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/vsp1/vsp1_drv.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drv.c b/drivers/media/platform/renesas/vsp1/vsp1_drv.c index 8270a9d207cb..49ac3104291a 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_drv.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_drv.c @@ -33,6 +33,7 @@ #include "vsp1_lif.h" #include "vsp1_lut.h" #include "vsp1_pipe.h" +#include "vsp1_regs.h" #include "vsp1_rwpf.h" #include "vsp1_sru.h" #include "vsp1_uds.h" @@ -502,7 +503,9 @@ static int vsp1_create_entities(struct vsp1_device *vsp1) int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index) { + u32 version = vsp1->version & VI6_IP_VERSION_MODEL_MASK; unsigned int timeout; + int ret = 0; u32 status; status = vsp1_read(vsp1, VI6_STATUS); @@ -523,7 +526,11 @@ int vsp1_reset_wpf(struct vsp1_device *vsp1, unsigned int index) return -ETIMEDOUT; } - return 0; + if (version == VI6_IP_VERSION_MODEL_VSPD_GEN3 || + version == VI6_IP_VERSION_MODEL_VSPD_GEN4) + ret = rcar_fcp_soft_reset(vsp1->fcp); + + return ret; } static int vsp1_device_init(struct vsp1_device *vsp1) From 30e77ed16bdc79f55b06192a1f5445bd0515fb54 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 16 Jun 2025 18:30:37 +0200 Subject: [PATCH 068/310] media: vsp1: vsp1_dl: Detect double list release In order to detect invalid usage pattern such as double list_put() calls, add an 'allocated' flag to each display list. Set the flag whenever a list is get() and clear it when the list is put(). Warn if a list not marked as allocated is returned to the pool of available display lists. Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616-vsp1_dl_list_count-v2-1-7d3f43fb1306@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/vsp1/vsp1_dl.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c index bb8228b19824..18617cbb1687 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_dl.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -176,6 +176,7 @@ struct vsp1_dl_cmd_pool { * @bodies: list of extra display list bodies * @pre_cmd: pre command to be issued through extended dl header * @post_cmd: post command to be issued through extended dl header + * @allocated: flag to detect double list release * @has_chain: if true, indicates that there's a partition chain * @chain: entry in the display list partition chain * @flags: display list flags, a combination of VSP1_DL_FRAME_END_* @@ -194,6 +195,8 @@ struct vsp1_dl_list { struct vsp1_dl_ext_cmd *pre_cmd; struct vsp1_dl_ext_cmd *post_cmd; + bool allocated; + bool has_chain; struct list_head chain; @@ -617,6 +620,7 @@ struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm) * display list can assert list_empty() if it is not in a chain. */ INIT_LIST_HEAD(&dl->chain); + dl->allocated = true; } spin_unlock_irqrestore(&dlm->lock, flags); @@ -657,6 +661,13 @@ static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) */ dl->body0->num_entries = 0; + /* + * Return the display list to the 'free' pool. If the list had already + * been returned be loud about it. + */ + WARN_ON_ONCE(!dl->allocated); + dl->allocated = false; + list_add_tail(&dl->list, &dl->dlm->free); } From fe2a3493c597869322c2e68ada77c44a84a42e81 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 16 Jun 2025 18:30:38 +0200 Subject: [PATCH 069/310] media: vsp1: vsp1_dl: Count display lists To detect leaks of display lists, store in the display list manager the number of allocated display lists when the manager is created and verify that when the display manager is reset the same number of lists is available in the free list. Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250616-vsp1_dl_list_count-v2-2-7d3f43fb1306@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/vsp1/vsp1_dl.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c index 18617cbb1687..c660f8539ff5 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_dl.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -215,6 +215,7 @@ struct vsp1_dl_list { * @pending: list waiting to be queued to the hardware * @pool: body pool for the display list bodies * @cmdpool: commands pool for extended display list + * @list_count: number of allocated display lists */ struct vsp1_dl_manager { unsigned int index; @@ -229,6 +230,8 @@ struct vsp1_dl_manager { struct vsp1_dl_body_pool *pool; struct vsp1_dl_cmd_pool *cmdpool; + + size_t list_count; }; /* ----------------------------------------------------------------------------- @@ -1078,6 +1081,7 @@ void vsp1_dlm_setup(struct vsp1_device *vsp1) void vsp1_dlm_reset(struct vsp1_dl_manager *dlm) { unsigned long flags; + size_t list_count; spin_lock_irqsave(&dlm->lock, flags); @@ -1085,8 +1089,11 @@ void vsp1_dlm_reset(struct vsp1_dl_manager *dlm) __vsp1_dl_list_put(dlm->queued); __vsp1_dl_list_put(dlm->pending); + list_count = list_count_nodes(&dlm->free); spin_unlock_irqrestore(&dlm->lock, flags); + WARN_ON_ONCE(list_count != dlm->list_count); + dlm->active = NULL; dlm->queued = NULL; dlm->pending = NULL; @@ -1156,6 +1163,8 @@ struct vsp1_dl_manager *vsp1_dlm_create(struct vsp1_device *vsp1, list_add_tail(&dl->list, &dlm->free); } + dlm->list_count = prealloc; + if (vsp1_feature(vsp1, VSP1_HAS_EXT_DL)) { dlm->cmdpool = vsp1_dl_cmd_pool_create(vsp1, VSP1_EXTCMD_AUTOFLD, prealloc); From d06c1a9f348d22478c6bc5684f9c990e15ada1e9 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Tue, 17 Jun 2025 09:23:28 +0200 Subject: [PATCH 070/310] media: vsp1: Add VSPX support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- drivers/media/platform/renesas/vsp1/Makefile | 1 + drivers/media/platform/renesas/vsp1/vsp1.h | 1 + .../media/platform/renesas/vsp1/vsp1_drv.c | 13 +- .../media/platform/renesas/vsp1/vsp1_regs.h | 1 + .../media/platform/renesas/vsp1/vsp1_vspx.c | 632 ++++++++++++++++++ .../media/platform/renesas/vsp1/vsp1_vspx.h | 16 + include/media/vsp1.h | 89 +++ 7 files changed, 752 insertions(+), 1 deletion(-) create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_vspx.c create mode 100644 drivers/media/platform/renesas/vsp1/vsp1_vspx.h diff --git a/drivers/media/platform/renesas/vsp1/Makefile b/drivers/media/platform/renesas/vsp1/Makefile index de8c802e1d1a..2057c8f7be47 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 f97a1a31bfab..94de2e85792e 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 49ac3104291a..b8d06e88c475 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 86e47c2d991f..10cfbcd1b6e0 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 000000000000..6551d63ba387 --- /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 000000000000..f871bf9e7dec --- /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 4ea6352fd63f..d9b91ff02761 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__ */ From f3bc718228d0a2d102a1b20ef63b0cadbf6219f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Ha=C5=82asa?= Date: Fri, 9 May 2025 09:58:09 +0200 Subject: [PATCH 071/310] media: nxp: imx8-isi: Simplify a couple of error messages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The error messages in the mxc_isi_crossbar_enable_streams() and mxc_isi_crossbar_disable_streams() functions are similar, with a single word difference between them. The word is moved out of the format string to a separate dev_err() argument to try and save memory through string de-duplication. The total savings are however small, as the .data size reduction is partly offset by a .text size increase, with a total saving of 8 bytes in total on an ARM64 platforms. They also come at the cost of making the error message difficult to grep, which outweights the gains. Simplify the error messages to make them grep-able. Signed-off-by: Krzysztof Hałasa Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/m3plgi9pwu.fsf@t19.piap.pl Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx8-isi/imx8-isi-crossbar.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c index 93a55c97cd17..b6c7f114f9ed 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -352,9 +352,8 @@ static int mxc_isi_crossbar_enable_streams(struct v4l2_subdev *sd, sink_streams); if (ret) { dev_err(xbar->isi->dev, - "failed to %s streams 0x%llx on '%s':%u: %d\n", - "enable", sink_streams, remote_sd->name, - remote_pad, ret); + "failed to enable streams 0x%llx on '%s':%u: %d\n", + sink_streams, remote_sd->name, remote_pad, ret); mxc_isi_crossbar_gasket_disable(xbar, sink_pad); return ret; } @@ -392,9 +391,8 @@ static int mxc_isi_crossbar_disable_streams(struct v4l2_subdev *sd, sink_streams); if (ret) dev_err(xbar->isi->dev, - "failed to %s streams 0x%llx on '%s':%u: %d\n", - "disable", sink_streams, remote_sd->name, - remote_pad, ret); + "failed to disable streams 0x%llx on '%s':%u: %d\n", + sink_streams, remote_sd->name, remote_pad, ret); mxc_isi_crossbar_gasket_disable(xbar, sink_pad); } From 4d78051cd6a7ce8e88d4e6dfed8ee0e133116287 Mon Sep 17 00:00:00 2001 From: Stefan Klug Date: Fri, 23 May 2025 17:14:30 +0200 Subject: [PATCH 072/310] media: rkisp1: Properly handle result of rkisp1_params_init_vb2_queue() Properly handle the return of rkisp1_params_init_vb2_queue(). It is very unlikely that this ever fails without code changes but should be handled anyways. While at it rename the error label for easier extension in the upcoming patch. Signed-off-by: Stefan Klug Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250523-supported-params-and-wdr-v3-1-7283b8536694@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/rockchip/rkisp1/rkisp1-params.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index b28f4140c8a3..24a8de697f2b 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -2763,7 +2763,9 @@ int rkisp1_params_register(struct rkisp1_device *rkisp1) vdev->queue = &node->buf_queue; vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_META_OUTPUT; vdev->vfl_dir = VFL_DIR_TX; - rkisp1_params_init_vb2_queue(vdev->queue, params); + ret = rkisp1_params_init_vb2_queue(vdev->queue, params); + if (ret) + goto err_media; params->metafmt = &rkisp1_params_formats[RKISP1_PARAMS_FIXED]; @@ -2777,18 +2779,18 @@ int rkisp1_params_register(struct rkisp1_device *rkisp1) node->pad.flags = MEDIA_PAD_FL_SOURCE; ret = media_entity_pads_init(&vdev->entity, 1, &node->pad); if (ret) - goto error; + goto err_media; ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(rkisp1->dev, "failed to register %s, ret=%d\n", vdev->name, ret); - goto error; + goto err_media; } return 0; -error: +err_media: media_entity_cleanup(&vdev->entity); mutex_destroy(&node->vlock); return ret; From 7c8c957ef12c41968adb66d785ce1dd5fb2f96e7 Mon Sep 17 00:00:00 2001 From: Stefan Klug Date: Fri, 23 May 2025 17:14:31 +0200 Subject: [PATCH 073/310] media: rkisp1: Add RKISP1_CID_SUPPORTED_PARAMS_BLOCKS control Add a RKISP1_CID_SUPPORTED_PARAMS_BLOCKS V4L2 control to be able to query the parameters blocks supported by the current kernel on the current hardware from user space. Signed-off-by: Stefan Klug Reviewed-by: Paul Elder Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250523-supported-params-and-wdr-v3-2-7283b8536694@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkisp1/rkisp1-common.h | 2 + .../platform/rockchip/rkisp1/rkisp1-params.c | 49 ++++++++++++++++++- include/uapi/linux/rkisp1-config.h | 11 +++++ include/uapi/linux/v4l2-controls.h | 6 +++ 4 files changed, 67 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h index ca952fd0829b..5f187f9efc7b 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-common.h @@ -415,6 +415,8 @@ struct rkisp1_params { spinlock_t config_lock; /* locks the buffers list 'params' */ struct list_head params; + struct v4l2_ctrl_handler ctrls; + const struct v4l2_meta_format *metafmt; enum v4l2_quantization quantization; diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index 24a8de697f2b..4db0ca8d86db 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -2736,6 +2736,44 @@ static int rkisp1_params_init_vb2_queue(struct vb2_queue *q, return vb2_queue_init(q); } +static int rkisp1_params_ctrl_init(struct rkisp1_params *params) +{ + struct v4l2_ctrl_config ctrl_config = { + .id = RKISP1_CID_SUPPORTED_PARAMS_BLOCKS, + .name = "Supported Params Blocks", + .type = V4L2_CTRL_TYPE_BITMASK, + .flags = V4L2_CTRL_FLAG_READ_ONLY, + }; + int ret; + + v4l2_ctrl_handler_init(¶ms->ctrls, 1); + + for (unsigned int i = 0; i < ARRAY_SIZE(rkisp1_ext_params_handlers); i++) { + const struct rkisp1_ext_params_handler *block_handler; + + block_handler = &rkisp1_ext_params_handlers[i]; + ctrl_config.max |= BIT(i); + + if ((params->rkisp1->info->features & block_handler->features) != + block_handler->features) + continue; + + ctrl_config.def |= BIT(i); + } + + v4l2_ctrl_new_custom(¶ms->ctrls, &ctrl_config, NULL); + + params->vnode.vdev.ctrl_handler = ¶ms->ctrls; + + if (params->ctrls.error) { + ret = params->ctrls.error; + v4l2_ctrl_handler_free(¶ms->ctrls); + return ret; + } + + return 0; +} + int rkisp1_params_register(struct rkisp1_device *rkisp1) { struct rkisp1_params *params = &rkisp1->params; @@ -2781,15 +2819,23 @@ int rkisp1_params_register(struct rkisp1_device *rkisp1) if (ret) goto err_media; + ret = rkisp1_params_ctrl_init(params); + if (ret) { + dev_err(rkisp1->dev, "Control initialization error %d\n", ret); + goto err_media; + } + ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1); if (ret) { dev_err(rkisp1->dev, "failed to register %s, ret=%d\n", vdev->name, ret); - goto err_media; + goto err_ctrl; } return 0; +err_ctrl: + v4l2_ctrl_handler_free(¶ms->ctrls); err_media: media_entity_cleanup(&vdev->entity); mutex_destroy(&node->vlock); @@ -2806,6 +2852,7 @@ void rkisp1_params_unregister(struct rkisp1_device *rkisp1) return; vb2_video_unregister_device(vdev); + v4l2_ctrl_handler_free(¶ms->ctrls); media_entity_cleanup(&vdev->entity); mutex_destroy(&node->vlock); } diff --git a/include/uapi/linux/rkisp1-config.h b/include/uapi/linux/rkisp1-config.h index 2d995f3c1ca3..5ca4d5961c5b 100644 --- a/include/uapi/linux/rkisp1-config.h +++ b/include/uapi/linux/rkisp1-config.h @@ -1086,6 +1086,9 @@ enum rkisp1_ext_params_block_type { #define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0) #define RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE (1U << 1) +/* A bitmask of parameters blocks supported on the current hardware. */ +#define RKISP1_CID_SUPPORTED_PARAMS_BLOCKS (V4L2_CID_USER_RKISP1_BASE + 0x01) + /** * struct rkisp1_ext_params_block_header - RkISP1 extensible parameters block * header @@ -1520,6 +1523,14 @@ enum rksip1_ext_param_buffer_version { * V4L2 control. If such control is not available, userspace should assume only * RKISP1_EXT_PARAM_BUFFER_V1 is supported by the driver. * + * The read-only V4L2 control ``RKISP1_CID_SUPPORTED_PARAMS_BLOCKS`` can be used + * to query the blocks supported by the device. It contains a bitmask where each + * bit represents the availability of the corresponding entry from the + * :c:type:`rkisp1_ext_params_block_type` enum. The current and default values + * of the control represents the blocks supported by the device instance, while + * the maximum value represents the blocks supported by the kernel driver, + * independently of the device instance. + * * For each ISP block that userspace wants to configure, a block-specific * structure is appended to the @data buffer, one after the other without gaps * in between nor overlaps. Userspace shall populate the @data_size field with diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h index 72e32814ea83..f836512e9deb 100644 --- a/include/uapi/linux/v4l2-controls.h +++ b/include/uapi/linux/v4l2-controls.h @@ -222,6 +222,12 @@ enum v4l2_colorfx { */ #define V4L2_CID_USER_UVC_BASE (V4L2_CID_USER_BASE + 0x11e0) +/* + * The base for Rockchip ISP1 driver controls. + * We reserve 16 controls for this driver. + */ +#define V4L2_CID_USER_RKISP1_BASE (V4L2_CID_USER_BASE + 0x1220) + /* MPEG-class control IDs */ /* The MPEG controls are applicable to all codec controls * and the 'MPEG' part of the define is historical */ From cd403e8aed6caad87967d2c135b57d92ba8e5544 Mon Sep 17 00:00:00 2001 From: Jai Luthra Date: Tue, 10 Jun 2025 17:55:27 +0530 Subject: [PATCH 074/310] media: rockchip: rkisp1: Add support for Wide Dynamic Range RKISP supports a basic Wide Dynamic Range (WDR) module since the first iteration (v1.0) of the ISP. Add support for enabling and configuring it using extensible parameters. Also, to ease programming, switch to using macro variables for defining the tonemapping curve register addresses. Reviewed-by: Stefan Klug Tested-by: Stefan Klug Reviewed-by: Paul Elder Reviewed-by: Laurent Pinchart Signed-off-by: Jai Luthra Link: https://lore.kernel.org/r/20250610-wdr-latest-v4-1-b69d0ac17ce9@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/rockchip/rkisp1/rkisp1-params.c | 93 +++++++++++++++++ .../platform/rockchip/rkisp1/rkisp1-regs.h | 99 +++++-------------- include/uapi/linux/rkisp1-config.h | 95 +++++++++++++++++- 3 files changed, 212 insertions(+), 75 deletions(-) diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c index 4db0ca8d86db..f1585f8fa0f4 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-params.c @@ -5,6 +5,7 @@ * Copyright (C) 2017 Rockchip Electronics Co., Ltd. */ +#include #include #include @@ -60,6 +61,7 @@ union rkisp1_ext_params_config { struct rkisp1_ext_params_afc_config afc; struct rkisp1_ext_params_compand_bls_config compand_bls; struct rkisp1_ext_params_compand_curve_config compand_curve; + struct rkisp1_ext_params_wdr_config wdr; }; enum rkisp1_params_formats { @@ -1348,6 +1350,73 @@ rkisp1_compand_compress_config(struct rkisp1_params *params, arg->x); } +static void rkisp1_wdr_config(struct rkisp1_params *params, + const struct rkisp1_cif_isp_wdr_config *arg) +{ + unsigned int i; + u32 value; + + value = rkisp1_read(params->rkisp1, RKISP1_CIF_ISP_WDR_CTRL) + & ~(RKISP1_CIF_ISP_WDR_USE_IREF | + RKISP1_CIF_ISP_WDR_COLOR_SPACE_SELECT | + RKISP1_CIF_ISP_WDR_CR_MAPPING_DISABLE | + RKISP1_CIF_ISP_WDR_USE_Y9_8 | + RKISP1_CIF_ISP_WDR_USE_RGB7_8 | + RKISP1_CIF_ISP_WDR_DISABLE_TRANSIENT | + RKISP1_CIF_ISP_WDR_RGB_FACTOR_MASK); + + /* Colorspace and chrominance mapping */ + if (arg->use_rgb_colorspace) + value |= RKISP1_CIF_ISP_WDR_COLOR_SPACE_SELECT; + + if (!arg->use_rgb_colorspace && arg->bypass_chroma_mapping) + value |= RKISP1_CIF_ISP_WDR_CR_MAPPING_DISABLE; + + /* Illumination reference */ + if (arg->use_iref) { + value |= RKISP1_CIF_ISP_WDR_USE_IREF; + + if (arg->iref_config.use_y9_8) + value |= RKISP1_CIF_ISP_WDR_USE_Y9_8; + + if (arg->iref_config.use_rgb7_8) + value |= RKISP1_CIF_ISP_WDR_USE_RGB7_8; + + if (arg->iref_config.disable_transient) + value |= RKISP1_CIF_ISP_WDR_DISABLE_TRANSIENT; + + value |= FIELD_PREP(RKISP1_CIF_ISP_WDR_RGB_FACTOR_MASK, + min(arg->iref_config.rgb_factor, + RKISP1_CIF_ISP_WDR_RGB_FACTOR_MAX)); + } + + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_WDR_CTRL, value); + + /* RGB and Luminance offsets */ + value = FIELD_PREP(RKISP1_CIF_ISP_WDR_RGB_OFFSET_MASK, + arg->rgb_offset) + | FIELD_PREP(RKISP1_CIF_ISP_WDR_LUM_OFFSET_MASK, + arg->luma_offset); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_WDR_OFFSET, value); + + /* DeltaMin */ + value = FIELD_PREP(RKISP1_CIF_ISP_WDR_DMIN_THRESH_MASK, + arg->dmin_thresh) + | FIELD_PREP(RKISP1_CIF_ISP_WDR_DMIN_STRENGTH_MASK, + min(arg->dmin_strength, + RKISP1_CIF_ISP_WDR_DMIN_STRENGTH_MAX)); + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_WDR_DELTAMIN, value); + + /* Tone curve */ + for (i = 0; i < RKISP1_CIF_ISP_WDR_CURVE_NUM_DY_REGS; i++) + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_WDR_TONECURVE(i), + arg->tone_curve.dY[i]); + for (i = 0; i < RKISP1_CIF_ISP_WDR_CURVE_NUM_COEFF; i++) + rkisp1_write(params->rkisp1, RKISP1_CIF_ISP_WDR_TONECURVE_YM(i), + arg->tone_curve.ym[i] & + RKISP1_CIF_ISP_WDR_TONE_CURVE_YM_MASK); +} + static void rkisp1_isp_isr_other_config(struct rkisp1_params *params, const struct rkisp1_params_cfg *new_params) @@ -2005,6 +2074,25 @@ static void rkisp1_ext_params_compand_compress(struct rkisp1_params *params, RKISP1_CIF_ISP_COMPAND_CTRL_COMPRESS_ENABLE); } +static void rkisp1_ext_params_wdr(struct rkisp1_params *params, + const union rkisp1_ext_params_config *block) +{ + const struct rkisp1_ext_params_wdr_config *wdr = &block->wdr; + + if (wdr->header.flags & RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE) { + rkisp1_param_clear_bits(params, RKISP1_CIF_ISP_WDR_CTRL, + RKISP1_CIF_ISP_WDR_CTRL_ENABLE); + return; + } + + rkisp1_wdr_config(params, &wdr->config); + + if ((wdr->header.flags & RKISP1_EXT_PARAMS_FL_BLOCK_ENABLE) && + !(params->enabled_blocks & BIT(wdr->header.type))) + rkisp1_param_set_bits(params, RKISP1_CIF_ISP_WDR_CTRL, + RKISP1_CIF_ISP_WDR_CTRL_ENABLE); +} + typedef void (*rkisp1_block_handler)(struct rkisp1_params *params, const union rkisp1_ext_params_config *config); @@ -2118,6 +2206,11 @@ static const struct rkisp1_ext_params_handler { .group = RKISP1_EXT_PARAMS_BLOCK_GROUP_OTHERS, .features = RKISP1_FEATURE_COMPAND, }, + [RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR] = { + .size = sizeof(struct rkisp1_ext_params_wdr_config), + .handler = rkisp1_ext_params_wdr, + .group = RKISP1_EXT_PARAMS_BLOCK_GROUP_OTHERS, + }, }; static void rkisp1_ext_params_config(struct rkisp1_params *params, diff --git a/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h b/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h index 139177db9c6d..fbeb186cde0d 100644 --- a/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h +++ b/drivers/media/platform/rockchip/rkisp1/rkisp1-regs.h @@ -703,6 +703,27 @@ #define RKISP1_CIF_ISP_COMPAND_CTRL_SOFT_RESET_FLAG BIT(2) #define RKISP1_CIF_ISP_COMPAND_CTRL_BLS_ENABLE BIT(3) +/* WDR */ +/* ISP_WDR_CTRL */ +#define RKISP1_CIF_ISP_WDR_CTRL_ENABLE BIT(0) +#define RKISP1_CIF_ISP_WDR_COLOR_SPACE_SELECT BIT(1) +#define RKISP1_CIF_ISP_WDR_CR_MAPPING_DISABLE BIT(2) +#define RKISP1_CIF_ISP_WDR_USE_IREF BIT(3) +#define RKISP1_CIF_ISP_WDR_USE_Y9_8 BIT(4) +#define RKISP1_CIF_ISP_WDR_USE_RGB7_8 BIT(5) +#define RKISP1_CIF_ISP_WDR_DISABLE_TRANSIENT BIT(6) +#define RKISP1_CIF_ISP_WDR_RGB_FACTOR_MASK GENMASK(11, 8) +#define RKISP1_CIF_ISP_WDR_RGB_FACTOR_MAX 8U +/* ISP_WDR_TONE_CURVE_YM */ +#define RKISP1_CIF_ISP_WDR_TONE_CURVE_YM_MASK GENMASK(12, 0) +/* ISP_WDR_OFFSET */ +#define RKISP1_CIF_ISP_WDR_RGB_OFFSET_MASK GENMASK(11, 0) +#define RKISP1_CIF_ISP_WDR_LUM_OFFSET_MASK GENMASK(27, 16) +/* ISP_WDR_DELTAMIN */ +#define RKISP1_CIF_ISP_WDR_DMIN_THRESH_MASK GENMASK(11, 0) +#define RKISP1_CIF_ISP_WDR_DMIN_STRENGTH_MASK GENMASK(20, 16) +#define RKISP1_CIF_ISP_WDR_DMIN_STRENGTH_MAX 16U + /* =================================================================== */ /* CIF Registers */ /* =================================================================== */ @@ -1295,82 +1316,12 @@ #define RKISP1_CIF_ISP_WDR_BASE 0x00002a00 #define RKISP1_CIF_ISP_WDR_CTRL (RKISP1_CIF_ISP_WDR_BASE + 0x00000000) -#define RKISP1_CIF_ISP_WDR_TONECURVE_1 (RKISP1_CIF_ISP_WDR_BASE + 0x00000004) -#define RKISP1_CIF_ISP_WDR_TONECURVE_2 (RKISP1_CIF_ISP_WDR_BASE + 0x00000008) -#define RKISP1_CIF_ISP_WDR_TONECURVE_3 (RKISP1_CIF_ISP_WDR_BASE + 0x0000000c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_4 (RKISP1_CIF_ISP_WDR_BASE + 0x00000010) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_0 (RKISP1_CIF_ISP_WDR_BASE + 0x00000014) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_1 (RKISP1_CIF_ISP_WDR_BASE + 0x00000018) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_2 (RKISP1_CIF_ISP_WDR_BASE + 0x0000001c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_3 (RKISP1_CIF_ISP_WDR_BASE + 0x00000020) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_4 (RKISP1_CIF_ISP_WDR_BASE + 0x00000024) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_5 (RKISP1_CIF_ISP_WDR_BASE + 0x00000028) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_6 (RKISP1_CIF_ISP_WDR_BASE + 0x0000002c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_7 (RKISP1_CIF_ISP_WDR_BASE + 0x00000030) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_8 (RKISP1_CIF_ISP_WDR_BASE + 0x00000034) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_9 (RKISP1_CIF_ISP_WDR_BASE + 0x00000038) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_10 (RKISP1_CIF_ISP_WDR_BASE + 0x0000003c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_11 (RKISP1_CIF_ISP_WDR_BASE + 0x00000040) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_12 (RKISP1_CIF_ISP_WDR_BASE + 0x00000044) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_13 (RKISP1_CIF_ISP_WDR_BASE + 0x00000048) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_14 (RKISP1_CIF_ISP_WDR_BASE + 0x0000004c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_15 (RKISP1_CIF_ISP_WDR_BASE + 0x00000050) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_16 (RKISP1_CIF_ISP_WDR_BASE + 0x00000054) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_17 (RKISP1_CIF_ISP_WDR_BASE + 0x00000058) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_18 (RKISP1_CIF_ISP_WDR_BASE + 0x0000005c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_19 (RKISP1_CIF_ISP_WDR_BASE + 0x00000060) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_20 (RKISP1_CIF_ISP_WDR_BASE + 0x00000064) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_21 (RKISP1_CIF_ISP_WDR_BASE + 0x00000068) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_22 (RKISP1_CIF_ISP_WDR_BASE + 0x0000006c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_23 (RKISP1_CIF_ISP_WDR_BASE + 0x00000070) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_24 (RKISP1_CIF_ISP_WDR_BASE + 0x00000074) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_25 (RKISP1_CIF_ISP_WDR_BASE + 0x00000078) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_26 (RKISP1_CIF_ISP_WDR_BASE + 0x0000007c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_27 (RKISP1_CIF_ISP_WDR_BASE + 0x00000080) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_28 (RKISP1_CIF_ISP_WDR_BASE + 0x00000084) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_29 (RKISP1_CIF_ISP_WDR_BASE + 0x00000088) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_30 (RKISP1_CIF_ISP_WDR_BASE + 0x0000008c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_31 (RKISP1_CIF_ISP_WDR_BASE + 0x00000090) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_32 (RKISP1_CIF_ISP_WDR_BASE + 0x00000094) +#define RKISP1_CIF_ISP_WDR_TONECURVE(n) (RKISP1_CIF_ISP_WDR_BASE + 0x00000004 + (n) * 4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM(n) (RKISP1_CIF_ISP_WDR_BASE + 0x00000014 + (n) * 4) #define RKISP1_CIF_ISP_WDR_OFFSET (RKISP1_CIF_ISP_WDR_BASE + 0x00000098) #define RKISP1_CIF_ISP_WDR_DELTAMIN (RKISP1_CIF_ISP_WDR_BASE + 0x0000009c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_1_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000a0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_2_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000a4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_3_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000a8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_4_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000ac) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_0_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000b0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_1_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000b4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_2_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000b8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_3_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000bc) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_4_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000c0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_5_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000c4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_6_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000c8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_7_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000cc) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_8_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000d0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_9_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000d4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_10_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000d8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_11_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000dc) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_12_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000e0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_13_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000e4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_14_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000e8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_15_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000ec) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_16_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000f0) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_17_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000f4) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_18_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000f8) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_19_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x000000fc) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_20_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000100) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_21_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000104) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_22_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000108) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_23_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000010c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_24_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000110) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_25_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000114) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_26_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000118) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_27_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000011c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_28_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000120) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_29_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000124) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_30_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000128) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_31_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x0000012c) -#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_32_SHD (RKISP1_CIF_ISP_WDR_BASE + 0x00000130) +#define RKISP1_CIF_ISP_WDR_TONECURVE_SHD(n) (RKISP1_CIF_ISP_WDR_BASE + 0x000000a0 + (n) * 4) +#define RKISP1_CIF_ISP_WDR_TONECURVE_YM_SHD(n) (RKISP1_CIF_ISP_WDR_BASE + 0x000000b0 + (n) * 4) #define RKISP1_CIF_ISP_HIST_BASE_V12 0x00002c00 #define RKISP1_CIF_ISP_HIST_CTRL_V12 (RKISP1_CIF_ISP_HIST_BASE_V12 + 0x00000000) diff --git a/include/uapi/linux/rkisp1-config.h b/include/uapi/linux/rkisp1-config.h index 5ca4d5961c5b..3b060ea6eed7 100644 --- a/include/uapi/linux/rkisp1-config.h +++ b/include/uapi/linux/rkisp1-config.h @@ -169,6 +169,13 @@ */ #define RKISP1_CIF_ISP_COMPAND_NUM_POINTS 64 +/* + * Wide Dynamic Range + */ +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_INTERV 32 +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_COEFF (RKISP1_CIF_ISP_WDR_CURVE_NUM_INTERV + 1) +#define RKISP1_CIF_ISP_WDR_CURVE_NUM_DY_REGS 4 + /* * Measurement types */ @@ -889,6 +896,72 @@ struct rkisp1_cif_isp_compand_curve_config { __u32 y[RKISP1_CIF_ISP_COMPAND_NUM_POINTS]; }; +/** + * struct rkisp1_cif_isp_wdr_tone_curve - Tone mapping curve definition for WDR. + * + * @dY: the dYn increments for horizontal (input) axis of the tone curve. + * each 3-bit dY value represents an increment of 2**(value+3). + * dY[0] bits 0:2 is increment dY1, bit 3 unused + * dY[0] bits 4:6 is increment dY2, bit 7 unused + * ... + * dY[0] bits 28:30 is increment dY8, bit 31 unused + * ... and so on till dY[3] bits 28:30 is increment dY32, bit 31 unused. + * @ym: the Ym values for the vertical (output) axis of the tone curve. + * each value is 13 bit. + */ +struct rkisp1_cif_isp_wdr_tone_curve { + __u32 dY[RKISP1_CIF_ISP_WDR_CURVE_NUM_DY_REGS]; + __u16 ym[RKISP1_CIF_ISP_WDR_CURVE_NUM_COEFF]; +}; + +/** + * struct rkisp1_cif_isp_wdr_iref_config - Illumination reference config for WDR. + * + * Use illumination reference value as described below, instead of only the + * luminance (Y) value for tone mapping and gain calculations: + * IRef = (rgb_factor * RGBMax_tr + (8 - rgb_factor) * Y)/8 + * + * @rgb_factor: defines how much influence the RGBmax approach has in + * comparison to Y (valid values are 0..8). + * @use_y9_8: use Y*9/8 for maximum value calculation along with the + * default of R, G, B for noise reduction. + * @use_rgb7_8: decrease RGBMax by 7/8 for noise reduction. + * @disable_transient: disable transient calculation between Y and RGBY_max. + */ +struct rkisp1_cif_isp_wdr_iref_config { + __u8 rgb_factor; + __u8 use_y9_8; + __u8 use_rgb7_8; + __u8 disable_transient; +}; + +/** + * struct rkisp1_cif_isp_wdr_config - Configuration for wide dynamic range. + * + * @tone_curve: tone mapping curve. + * @iref_config: illumination reference configuration. (when use_iref is true) + * @rgb_offset: RGB offset value for RGB operation mode. (12 bits) + * @luma_offset: luminance offset value for RGB operation mode. (12 bits) + * @dmin_thresh: lower threshold for deltaMin value. (12 bits) + * @dmin_strength: strength factor for deltaMin. (valid range is 0x00..0x10) + * @use_rgb_colorspace: use RGB instead of luminance/chrominance colorspace. + * @bypass_chroma_mapping: disable chrominance mapping (only valid if + * use_rgb_colorspace = 0) + * @use_iref: use illumination reference instead of Y for tone mapping + * and gain calculations. + */ +struct rkisp1_cif_isp_wdr_config { + struct rkisp1_cif_isp_wdr_tone_curve tone_curve; + struct rkisp1_cif_isp_wdr_iref_config iref_config; + __u16 rgb_offset; + __u16 luma_offset; + __u16 dmin_thresh; + __u8 dmin_strength; + __u8 use_rgb_colorspace; + __u8 bypass_chroma_mapping; + __u8 use_iref; +}; + /*---------- PART2: Measurement Statistics ------------*/ /** @@ -1059,6 +1132,7 @@ struct rkisp1_stat_buffer { * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS: BLS in the compand block * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND: Companding expand curve * @RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS: Companding compress curve + * @RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR: Wide dynamic range */ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_BLS, @@ -1081,6 +1155,7 @@ enum rkisp1_ext_params_block_type { RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_BLS, RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_EXPAND, RKISP1_EXT_PARAMS_BLOCK_TYPE_COMPAND_COMPRESS, + RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR, }; #define RKISP1_EXT_PARAMS_FL_BLOCK_DISABLE (1U << 0) @@ -1463,6 +1538,23 @@ struct rkisp1_ext_params_compand_curve_config { struct rkisp1_cif_isp_compand_curve_config config; } __attribute__((aligned(8))); +/** + * struct rkisp1_ext_params_wdr_config - RkISP1 extensible params + * Wide dynamic range config + * + * RkISP1 extensible parameters WDR block. + * Identified by :c:type:`RKISP1_EXT_PARAMS_BLOCK_TYPE_WDR` + * + * @header: The RkISP1 extensible parameters header, see + * :c:type:`rkisp1_ext_params_block_header` + * @config: WDR configuration, see + * :c:type:`rkisp1_cif_isp_wdr_config` + */ +struct rkisp1_ext_params_wdr_config { + struct rkisp1_ext_params_block_header header; + struct rkisp1_cif_isp_wdr_config config; +} __attribute__((aligned(8))); + /* * The rkisp1_ext_params_compand_curve_config structure is counted twice as it * is used for both the COMPAND_EXPAND and COMPAND_COMPRESS block types. @@ -1487,7 +1579,8 @@ struct rkisp1_ext_params_compand_curve_config { sizeof(struct rkisp1_ext_params_afc_config) +\ sizeof(struct rkisp1_ext_params_compand_bls_config) +\ sizeof(struct rkisp1_ext_params_compand_curve_config) +\ - sizeof(struct rkisp1_ext_params_compand_curve_config)) + sizeof(struct rkisp1_ext_params_compand_curve_config) +\ + sizeof(struct rkisp1_ext_params_wdr_config)) /** * enum rksip1_ext_param_buffer_version - RkISP1 extensible parameters version From 038d27acf987c52879a08d7e8160907372cbeb35 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Fri, 6 Jun 2025 12:05:33 +0300 Subject: [PATCH 075/310] media: imx-mipi-csis: Use CSI-2 data type macros from mipi-csi2.h The imx-mipi-csis defines custom macros for the CSI-2 data types, duplicating the centralized macros defines in mipi-csi2.h. Replace them with the latter. Reviewed-by: Rui Miguel Silva Reviewed-by: Stefan Klug Reviewed-by: Frank Li Link: https://lore.kernel.org/r/20250606090533.10711-1-laurent.pinchart@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx-mipi-csis.c | 68 ++++++++-------------- 1 file changed, 25 insertions(+), 43 deletions(-) diff --git a/drivers/media/platform/nxp/imx-mipi-csis.c b/drivers/media/platform/nxp/imx-mipi-csis.c index d060eadebc7a..2beb5f43c2c0 100644 --- a/drivers/media/platform/nxp/imx-mipi-csis.c +++ b/drivers/media/platform/nxp/imx-mipi-csis.c @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -229,25 +230,6 @@ #define DEFAULT_SCLK_CSIS_FREQ 166000000UL -/* MIPI CSI-2 Data Types */ -#define MIPI_CSI2_DATA_TYPE_YUV420_8 0x18 -#define MIPI_CSI2_DATA_TYPE_YUV420_10 0x19 -#define MIPI_CSI2_DATA_TYPE_LE_YUV420_8 0x1a -#define MIPI_CSI2_DATA_TYPE_CS_YUV420_8 0x1c -#define MIPI_CSI2_DATA_TYPE_CS_YUV420_10 0x1d -#define MIPI_CSI2_DATA_TYPE_YUV422_8 0x1e -#define MIPI_CSI2_DATA_TYPE_YUV422_10 0x1f -#define MIPI_CSI2_DATA_TYPE_RGB565 0x22 -#define MIPI_CSI2_DATA_TYPE_RGB666 0x23 -#define MIPI_CSI2_DATA_TYPE_RGB888 0x24 -#define MIPI_CSI2_DATA_TYPE_RAW6 0x28 -#define MIPI_CSI2_DATA_TYPE_RAW7 0x29 -#define MIPI_CSI2_DATA_TYPE_RAW8 0x2a -#define MIPI_CSI2_DATA_TYPE_RAW10 0x2b -#define MIPI_CSI2_DATA_TYPE_RAW12 0x2c -#define MIPI_CSI2_DATA_TYPE_RAW14 0x2d -#define MIPI_CSI2_DATA_TYPE_USER(x) (0x30 + (x)) - struct mipi_csis_event { bool debug; u32 mask; @@ -357,116 +339,116 @@ static const struct csis_pix_format mipi_csis_formats[] = { { .code = MEDIA_BUS_FMT_UYVY8_1X16, .output = MEDIA_BUS_FMT_UYVY8_1X16, - .data_type = MIPI_CSI2_DATA_TYPE_YUV422_8, + .data_type = MIPI_CSI2_DT_YUV422_8B, .width = 16, }, /* RGB formats. */ { .code = MEDIA_BUS_FMT_RGB565_1X16, .output = MEDIA_BUS_FMT_RGB565_1X16, - .data_type = MIPI_CSI2_DATA_TYPE_RGB565, + .data_type = MIPI_CSI2_DT_RGB565, .width = 16, }, { .code = MEDIA_BUS_FMT_BGR888_1X24, .output = MEDIA_BUS_FMT_RGB888_1X24, - .data_type = MIPI_CSI2_DATA_TYPE_RGB888, + .data_type = MIPI_CSI2_DT_RGB888, .width = 24, }, /* RAW (Bayer and greyscale) formats. */ { .code = MEDIA_BUS_FMT_SBGGR8_1X8, .output = MEDIA_BUS_FMT_SBGGR8_1X8, - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, }, { .code = MEDIA_BUS_FMT_SGBRG8_1X8, .output = MEDIA_BUS_FMT_SGBRG8_1X8, - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, }, { .code = MEDIA_BUS_FMT_SGRBG8_1X8, .output = MEDIA_BUS_FMT_SGRBG8_1X8, - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, }, { .code = MEDIA_BUS_FMT_SRGGB8_1X8, .output = MEDIA_BUS_FMT_SRGGB8_1X8, - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, }, { .code = MEDIA_BUS_FMT_Y8_1X8, .output = MEDIA_BUS_FMT_Y8_1X8, - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, }, { .code = MEDIA_BUS_FMT_SBGGR10_1X10, .output = MEDIA_BUS_FMT_SBGGR10_1X10, - .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .data_type = MIPI_CSI2_DT_RAW10, .width = 10, }, { .code = MEDIA_BUS_FMT_SGBRG10_1X10, .output = MEDIA_BUS_FMT_SGBRG10_1X10, - .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .data_type = MIPI_CSI2_DT_RAW10, .width = 10, }, { .code = MEDIA_BUS_FMT_SGRBG10_1X10, .output = MEDIA_BUS_FMT_SGRBG10_1X10, - .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .data_type = MIPI_CSI2_DT_RAW10, .width = 10, }, { .code = MEDIA_BUS_FMT_SRGGB10_1X10, .output = MEDIA_BUS_FMT_SRGGB10_1X10, - .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .data_type = MIPI_CSI2_DT_RAW10, .width = 10, }, { .code = MEDIA_BUS_FMT_Y10_1X10, .output = MEDIA_BUS_FMT_Y10_1X10, - .data_type = MIPI_CSI2_DATA_TYPE_RAW10, + .data_type = MIPI_CSI2_DT_RAW10, .width = 10, }, { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .output = MEDIA_BUS_FMT_SBGGR12_1X12, - .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .data_type = MIPI_CSI2_DT_RAW12, .width = 12, }, { .code = MEDIA_BUS_FMT_SGBRG12_1X12, .output = MEDIA_BUS_FMT_SGBRG12_1X12, - .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .data_type = MIPI_CSI2_DT_RAW12, .width = 12, }, { .code = MEDIA_BUS_FMT_SGRBG12_1X12, .output = MEDIA_BUS_FMT_SGRBG12_1X12, - .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .data_type = MIPI_CSI2_DT_RAW12, .width = 12, }, { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .output = MEDIA_BUS_FMT_SRGGB12_1X12, - .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .data_type = MIPI_CSI2_DT_RAW12, .width = 12, }, { .code = MEDIA_BUS_FMT_Y12_1X12, .output = MEDIA_BUS_FMT_Y12_1X12, - .data_type = MIPI_CSI2_DATA_TYPE_RAW12, + .data_type = MIPI_CSI2_DT_RAW12, .width = 12, }, { .code = MEDIA_BUS_FMT_SBGGR14_1X14, .output = MEDIA_BUS_FMT_SBGGR14_1X14, - .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .data_type = MIPI_CSI2_DT_RAW14, .width = 14, }, { .code = MEDIA_BUS_FMT_SGBRG14_1X14, .output = MEDIA_BUS_FMT_SGBRG14_1X14, - .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .data_type = MIPI_CSI2_DT_RAW14, .width = 14, }, { .code = MEDIA_BUS_FMT_SGRBG14_1X14, .output = MEDIA_BUS_FMT_SGRBG14_1X14, - .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .data_type = MIPI_CSI2_DT_RAW14, .width = 14, }, { .code = MEDIA_BUS_FMT_SRGGB14_1X14, .output = MEDIA_BUS_FMT_SRGGB14_1X14, - .data_type = MIPI_CSI2_DATA_TYPE_RAW14, + .data_type = MIPI_CSI2_DT_RAW14, .width = 14, }, /* JPEG */ @@ -494,7 +476,7 @@ static const struct csis_pix_format mipi_csis_formats[] = { * SoC that can support quad pixel mode, this will have to be * revisited. */ - .data_type = MIPI_CSI2_DATA_TYPE_RAW8, + .data_type = MIPI_CSI2_DT_RAW8, .width = 8, } }; @@ -583,7 +565,7 @@ static void __mipi_csis_set_format(struct mipi_csis_device *csis, * * TODO: Verify which other formats require DUAL (or QUAD) modes. */ - if (csis_fmt->data_type == MIPI_CSI2_DATA_TYPE_YUV422_8) + if (csis_fmt->data_type == MIPI_CSI2_DT_YUV422_8B) val |= MIPI_CSIS_ISPCFG_PIXEL_MODE_DUAL; val |= MIPI_CSIS_ISPCFG_FMT(csis_fmt->data_type); From c4891010d8194bda9c268cc9330ba9e342d7c4b6 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:39 -0400 Subject: [PATCH 076/310] media: dt-bindings: Add binding doc for i.MX8QXP and i.MX8QM ISI Add binding documentation for i.MX8QXP and i.MX8QM ISI. The clock-names, power-domains, and ports differ significantly from the existing nxp,imx8-isi.yaml. Create a new file to avoid complex if-else branches. Add new file to MAINTAINERS. Reviewed-by: Rob Herring (Arm) Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-1-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../bindings/media/fsl,imx8qm-isi.yaml | 117 ++++++++++++++++++ .../bindings/media/fsl,imx8qxp-isi.yaml | 106 ++++++++++++++++ MAINTAINERS | 1 + 3 files changed, 224 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/fsl,imx8qm-isi.yaml create mode 100644 Documentation/devicetree/bindings/media/fsl,imx8qxp-isi.yaml diff --git a/Documentation/devicetree/bindings/media/fsl,imx8qm-isi.yaml b/Documentation/devicetree/bindings/media/fsl,imx8qm-isi.yaml new file mode 100644 index 000000000000..93f527e223af --- /dev/null +++ b/Documentation/devicetree/bindings/media/fsl,imx8qm-isi.yaml @@ -0,0 +1,117 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/fsl,imx8qm-isi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: i.MX8QM Image Sensing Interface + +maintainers: + - Frank Li + +description: + The Image Sensing Interface (ISI) combines image processing pipelines with + DMA engines to process and capture frames originating from a variety of + sources. The inputs to the ISI go through Pixel Link interfaces, and their + number and nature is SoC-dependent. They cover both capture interfaces (MIPI + CSI-2 RX, HDMI RX, ...) and display engine outputs for writeback support. + +properties: + compatible: + enum: + - fsl,imx8qm-isi + + reg: + maxItems: 1 + + clocks: + maxItems: 8 + + clock-names: + items: + - const: per0 + - const: per1 + - const: per2 + - const: per3 + - const: per4 + - const: per5 + - const: per6 + - const: per7 + + interrupts: + maxItems: 8 + + power-domains: + maxItems: 8 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: MIPI CSI-2 RX 0 + port@3: + $ref: /schemas/graph.yaml#/properties/port + description: MIPI CSI-2 RX 1 + port@4: + $ref: /schemas/graph.yaml#/properties/port + description: HDMI RX + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - power-domains + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + image-controller@58100000 { + compatible = "fsl,imx8qm-isi"; + reg = <0x58100000 0x80000>; + interrupts = , + , + , + , + , + , + , + ; + clocks = <&pdma0_lpcg IMX_LPCG_CLK_0>, + <&pdma1_lpcg IMX_LPCG_CLK_0>, + <&pdma2_lpcg IMX_LPCG_CLK_0>, + <&pdma3_lpcg IMX_LPCG_CLK_0>, + <&pdma4_lpcg IMX_LPCG_CLK_0>, + <&pdma5_lpcg IMX_LPCG_CLK_0>, + <&pdma6_lpcg IMX_LPCG_CLK_0>, + <&pdma7_lpcg IMX_LPCG_CLK_0>; + clock-names = "per0", "per1", "per2", "per3", + "per4", "per5", "per6", "per7"; + power-domains = <&pd IMX_SC_R_ISI_CH0>, <&pd IMX_SC_R_ISI_CH1>, + <&pd IMX_SC_R_ISI_CH2>, <&pd IMX_SC_R_ISI_CH3>, + <&pd IMX_SC_R_ISI_CH4>, <&pd IMX_SC_R_ISI_CH5>, + <&pd IMX_SC_R_ISI_CH6>, <&pd IMX_SC_R_ISI_CH7>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + endpoint { + remote-endpoint = <&mipi_csi0_out>; + }; + }; + }; + }; +... diff --git a/Documentation/devicetree/bindings/media/fsl,imx8qxp-isi.yaml b/Documentation/devicetree/bindings/media/fsl,imx8qxp-isi.yaml new file mode 100644 index 000000000000..bb41996bd2e3 --- /dev/null +++ b/Documentation/devicetree/bindings/media/fsl,imx8qxp-isi.yaml @@ -0,0 +1,106 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/fsl,imx8qxp-isi.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: i.MX8QXP Image Sensing Interface + +maintainers: + - Frank Li + +description: + The Image Sensing Interface (ISI) combines image processing pipelines with + DMA engines to process and capture frames originating from a variety of + sources. The inputs to the ISI go through Pixel Link interfaces, and their + number and nature is SoC-dependent. They cover both capture interfaces (MIPI + CSI-2 RX, HDMI RX, ...) and display engine outputs for writeback support. + +properties: + compatible: + enum: + - fsl,imx8qxp-isi + + reg: + maxItems: 1 + + clocks: + maxItems: 6 + + clock-names: + items: + - const: per0 + - const: per1 + - const: per2 + - const: per3 + - const: per4 + - const: per5 + + interrupts: + maxItems: 6 + + power-domains: + maxItems: 6 + + ports: + $ref: /schemas/graph.yaml#/properties/ports + properties: + port@2: + $ref: /schemas/graph.yaml#/properties/port + description: MIPI CSI-2 RX 0 + port@6: + $ref: /schemas/graph.yaml#/properties/port + description: CSI-2 Parallel RX + +required: + - compatible + - reg + - clocks + - clock-names + - interrupts + - power-domains + - ports + +additionalProperties: false + +examples: + - | + #include + #include + #include + #include + #include + + image-controller@58100000 { + compatible = "fsl,imx8qxp-isi"; + reg = <0x58100000 0x60000>; + interrupts = , + , + , + , + , + ; + clocks = <&pdma0_lpcg IMX_LPCG_CLK_0>, + <&pdma1_lpcg IMX_LPCG_CLK_0>, + <&pdma2_lpcg IMX_LPCG_CLK_0>, + <&pdma3_lpcg IMX_LPCG_CLK_0>, + <&pdma4_lpcg IMX_LPCG_CLK_0>, + <&pdma5_lpcg IMX_LPCG_CLK_0>; + clock-names = "per0", "per1", "per2", "per3", "per4", "per5"; + power-domains = <&pd IMX_SC_R_ISI_CH0>, <&pd IMX_SC_R_ISI_CH1>, + <&pd IMX_SC_R_ISI_CH2>, <&pd IMX_SC_R_ISI_CH3>, + <&pd IMX_SC_R_ISI_CH4>, <&pd IMX_SC_R_ISI_CH5>; + + ports { + #address-cells = <1>; + #size-cells = <0>; + + port@2 { + reg = <2>; + endpoint { + remote-endpoint = <&mipi_csi0_out>; + }; + }; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index e8f3dc93a569..da34c72270e7 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -17906,6 +17906,7 @@ NXP i.MX 8M ISI DRIVER M: Laurent Pinchart L: linux-media@vger.kernel.org S: Maintained +F: Documentation/devicetree/bindings/media/fsl,imx8*-isi.yaml F: Documentation/devicetree/bindings/media/nxp,imx8-isi.yaml F: drivers/media/platform/nxp/imx8-isi/ From 2021b8d51cdb514da22f2c9bc05e123d19277d1d Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:40 -0400 Subject: [PATCH 077/310] media: nxp: imx8-isi: Allow num_sources to be greater than num_sink Allow num_sources (drvdata: num_channels) to be greater than num_sink (drvdata: num_ports + 1). ISI support stream multiplexing, such as differentiates multiple cameras from a single 2-lane MIPI input, or duplicates input stream into multiple outputs. So num_channels may be greater than num_ports at some platform. Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-2-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c index b6c7f114f9ed..ede6cc74c023 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-crossbar.c @@ -188,11 +188,12 @@ static int mxc_isi_crossbar_init_state(struct v4l2_subdev *sd, * Create a 1:1 mapping between pixel link inputs and outputs to * pipelines by default. */ - routes = kcalloc(xbar->num_sources, sizeof(*routes), GFP_KERNEL); + routing.num_routes = min(xbar->num_sinks - 1, xbar->num_sources); + routes = kcalloc(routing.num_routes, sizeof(*routes), GFP_KERNEL); if (!routes) return -ENOMEM; - for (i = 0; i < xbar->num_sources; ++i) { + for (i = 0; i < routing.num_routes; ++i) { struct v4l2_subdev_route *route = &routes[i]; route->sink_pad = i; @@ -200,7 +201,6 @@ static int mxc_isi_crossbar_init_state(struct v4l2_subdev *sd, route->flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE; } - routing.num_routes = xbar->num_sources; routing.routes = routes; ret = __mxc_isi_crossbar_set_routing(sd, state, &routing); @@ -451,7 +451,7 @@ int mxc_isi_crossbar_init(struct mxc_isi_dev *isi) * the memory input. */ xbar->num_sinks = isi->pdata->num_ports + 1; - xbar->num_sources = isi->pdata->num_ports; + xbar->num_sources = isi->pdata->num_channels; num_pads = xbar->num_sinks + xbar->num_sources; xbar->pads = kcalloc(num_pads, sizeof(*xbar->pads), GFP_KERNEL); From 66ede6d71d4e8782754b1dffd587877715015b5e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:41 -0400 Subject: [PATCH 078/310] media: nxp: imx8-isi: Remove unused offset in mxc_isi_reg and use BIT() macro for mask Preserve clarity by removing the unused 'offset' field in struct mxc_isi_reg, as it duplicates information already indicated by the mask and remains unused. Improve readability by replacing hex value masks with the BIT() macro. No functional change. Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-3-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/nxp/imx8-isi/imx8-isi-core.c | 25 ++++++++++--------- .../platform/nxp/imx8-isi/imx8-isi-core.h | 1 - 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index 1e79b1211b60..ecfc95882f90 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -3,6 +3,7 @@ * Copyright 2019-2020 NXP */ +#include #include #include #include @@ -247,24 +248,24 @@ static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi) /* For i.MX8QXP C0 and i.MX8MN ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { - .oflw_y_buf_en = { .offset = 19, .mask = 0x80000 }, - .oflw_u_buf_en = { .offset = 21, .mask = 0x200000 }, - .oflw_v_buf_en = { .offset = 23, .mask = 0x800000 }, + .oflw_y_buf_en = { .mask = BIT(19) }, + .oflw_u_buf_en = { .mask = BIT(21) }, + .oflw_v_buf_en = { .mask = BIT(23) }, - .panic_y_buf_en = {.offset = 20, .mask = 0x100000 }, - .panic_u_buf_en = {.offset = 22, .mask = 0x400000 }, - .panic_v_buf_en = {.offset = 24, .mask = 0x1000000 }, + .panic_y_buf_en = { .mask = BIT(20) }, + .panic_u_buf_en = { .mask = BIT(22) }, + .panic_v_buf_en = { .mask = BIT(24) }, }; /* For i.MX8MP ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { - .oflw_y_buf_en = { .offset = 18, .mask = 0x40000 }, - .oflw_u_buf_en = { .offset = 20, .mask = 0x100000 }, - .oflw_v_buf_en = { .offset = 22, .mask = 0x400000 }, + .oflw_y_buf_en = { .mask = BIT(18) }, + .oflw_u_buf_en = { .mask = BIT(20) }, + .oflw_v_buf_en = { .mask = BIT(22) }, - .panic_y_buf_en = {.offset = 19, .mask = 0x80000 }, - .panic_u_buf_en = {.offset = 21, .mask = 0x200000 }, - .panic_v_buf_en = {.offset = 23, .mask = 0x800000 }, + .panic_y_buf_en = { .mask = BIT(19) }, + .panic_u_buf_en = { .mask = BIT(21) }, + .panic_v_buf_en = { .mask = BIT(23) }, }; /* Panic will assert when the buffers are 50% full */ diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h index 9c7fe9e5f941..e7534a80af7b 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h @@ -114,7 +114,6 @@ struct mxc_isi_buffer { }; struct mxc_isi_reg { - u32 offset; u32 mask; }; From 60b8de2b9b4be249c47e100e692d240d661e056b Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:42 -0400 Subject: [PATCH 079/310] media: nxp: imx8-isi: Use devm_clk_bulk_get_all() to fetch clocks Use devm_clk_bulk_get_all() helper to simplify clock handle code. No functional changes intended. Signed-off-by: Frank Li Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-4-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/nxp/imx8-isi/imx8-isi-core.c | 46 ++----------------- .../platform/nxp/imx8-isi/imx8-isi-core.h | 3 +- 2 files changed, 6 insertions(+), 43 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index ecfc95882f90..015350c6f278 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -275,11 +275,6 @@ static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = { .panic_set_thd_v = { .mask = 0xf0000, .offset = 16, .threshold = 0x7 }, }; -static const struct clk_bulk_data mxc_imx8mn_clks[] = { - { .id = "axi" }, - { .id = "apb" }, -}; - static const struct mxc_isi_plat_data mxc_imx8mn_data = { .model = MXC_ISI_IMX8MN, .num_ports = 1, @@ -287,8 +282,6 @@ static const struct mxc_isi_plat_data mxc_imx8mn_data = { .reg_offset = 0, .ier_reg = &mxc_imx8_isi_ier_v1, .set_thd = &mxc_imx8_isi_thd_v1, - .clks = mxc_imx8mn_clks, - .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = false, .gasket_ops = &mxc_imx8_gasket_ops, .has_36bit_dma = false, @@ -301,8 +294,6 @@ static const struct mxc_isi_plat_data mxc_imx8mp_data = { .reg_offset = 0x2000, .ier_reg = &mxc_imx8_isi_ier_v2, .set_thd = &mxc_imx8_isi_thd_v1, - .clks = mxc_imx8mn_clks, - .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = true, .gasket_ops = &mxc_imx8_gasket_ops, .has_36bit_dma = true, @@ -315,8 +306,6 @@ static const struct mxc_isi_plat_data mxc_imx8ulp_data = { .reg_offset = 0x0, .ier_reg = &mxc_imx8_isi_ier_v2, .set_thd = &mxc_imx8_isi_thd_v1, - .clks = mxc_imx8mn_clks, - .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = true, .has_36bit_dma = false, }; @@ -328,8 +317,6 @@ static const struct mxc_isi_plat_data mxc_imx93_data = { .reg_offset = 0, .ier_reg = &mxc_imx8_isi_ier_v2, .set_thd = &mxc_imx8_isi_thd_v1, - .clks = mxc_imx8mn_clks, - .num_clks = ARRAY_SIZE(mxc_imx8mn_clks), .buf_active_reverse = true, .gasket_ops = &mxc_imx93_gasket_ops, .has_36bit_dma = false, @@ -386,7 +373,7 @@ static int mxc_isi_runtime_suspend(struct device *dev) { struct mxc_isi_dev *isi = dev_get_drvdata(dev); - clk_bulk_disable_unprepare(isi->pdata->num_clks, isi->clks); + clk_bulk_disable_unprepare(isi->num_clks, isi->clks); return 0; } @@ -396,7 +383,7 @@ static int mxc_isi_runtime_resume(struct device *dev) struct mxc_isi_dev *isi = dev_get_drvdata(dev); int ret; - ret = clk_bulk_prepare_enable(isi->pdata->num_clks, isi->clks); + ret = clk_bulk_prepare_enable(isi->num_clks, isi->clks); if (ret) { dev_err(dev, "Failed to enable clocks (%d)\n", ret); return ret; @@ -414,27 +401,6 @@ static const struct dev_pm_ops mxc_isi_pm_ops = { * Probe, remove & driver */ -static int mxc_isi_clk_get(struct mxc_isi_dev *isi) -{ - unsigned int size = isi->pdata->num_clks - * sizeof(*isi->clks); - int ret; - - isi->clks = devm_kmemdup(isi->dev, isi->pdata->clks, size, GFP_KERNEL); - if (!isi->clks) - return -ENOMEM; - - ret = devm_clk_bulk_get(isi->dev, isi->pdata->num_clks, - isi->clks); - if (ret < 0) { - dev_err(isi->dev, "Failed to acquire clocks: %d\n", - ret); - return ret; - } - - return 0; -} - static int mxc_isi_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -457,11 +423,9 @@ static int mxc_isi_probe(struct platform_device *pdev) if (!isi->pipes) return -ENOMEM; - ret = mxc_isi_clk_get(isi); - if (ret < 0) { - dev_err(dev, "Failed to get clocks\n"); - return ret; - } + isi->num_clks = devm_clk_bulk_get_all(dev, &isi->clks); + if (isi->num_clks < 0) + return dev_err_probe(dev, isi->num_clks, "Failed to get clocks\n"); isi->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(isi->regs)) { diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h index e7534a80af7b..bd3cfe5fbe06 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h @@ -169,8 +169,6 @@ struct mxc_isi_plat_data { const struct mxc_isi_ier_reg *ier_reg; const struct mxc_isi_set_thd *set_thd; const struct mxc_gasket_ops *gasket_ops; - const struct clk_bulk_data *clks; - unsigned int num_clks; bool buf_active_reverse; bool has_36bit_dma; }; @@ -282,6 +280,7 @@ struct mxc_isi_dev { void __iomem *regs; struct clk_bulk_data *clks; + int num_clks; struct regmap *gasket; struct mxc_isi_crossbar crossbar; From 73a40554f979eb0e441dbc9df4b84effdedb1e87 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:43 -0400 Subject: [PATCH 080/310] media: nxp: imx8-isi: Remove redundant check for dma_set_mask_and_coherent() dma_set_mask_and_coherent() never return failure when mask bigger than 32bit. See commit f7ae20f2fc4e ("docs: dma: correct dma_set_mask() sample code") So remove return value check for dma_set_mask_and_coherent(). Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-5-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index 015350c6f278..073ea5912de3 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -444,11 +444,7 @@ static int mxc_isi_probe(struct platform_device *pdev) } dma_size = isi->pdata->has_36bit_dma ? 36 : 32; - ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); - if (ret) { - dev_err(dev, "failed to set DMA mask\n"); - return ret; - } + dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_size)); pm_runtime_enable(dev); From dee8521f698742732507520cfb122de6df447e72 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:44 -0400 Subject: [PATCH 081/310] media: nxp: imx8-isi: Use dev_err_probe() to simplify code Use dev_err_probe() simplify code. No functional changes intended. Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-6-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx8-isi/imx8-isi-core.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index 073ea5912de3..4ab2749e460d 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -428,19 +428,16 @@ static int mxc_isi_probe(struct platform_device *pdev) return dev_err_probe(dev, isi->num_clks, "Failed to get clocks\n"); isi->regs = devm_platform_ioremap_resource(pdev, 0); - if (IS_ERR(isi->regs)) { - dev_err(dev, "Failed to get ISI register map\n"); - return PTR_ERR(isi->regs); - } + if (IS_ERR(isi->regs)) + return dev_err_probe(dev, PTR_ERR(isi->regs), + "Failed to get ISI register map\n"); if (isi->pdata->gasket_ops) { isi->gasket = syscon_regmap_lookup_by_phandle(dev->of_node, "fsl,blk-ctrl"); - if (IS_ERR(isi->gasket)) { - ret = PTR_ERR(isi->gasket); - dev_err(dev, "failed to get gasket: %d\n", ret); - return ret; - } + if (IS_ERR(isi->gasket)) + return dev_err_probe(dev, PTR_ERR(isi->gasket), + "failed to get gasket\n"); } dma_size = isi->pdata->has_36bit_dma ? 36 : 32; From 859278460faa4e52e4b01c8b997bab57af460067 Mon Sep 17 00:00:00 2001 From: Robert Chiras Date: Thu, 22 May 2025 13:56:45 -0400 Subject: [PATCH 082/310] media: imx8-isi: Add support for i.MX8QM and i.MX8QXP Add compatibles and platform data for i.MX8QM and i.MX8QXP platforms. i.MX8QM's IER register layout is difference with i.MX8QXP. Signed-off-by: Robert Chiras Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-7-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/nxp/imx8-isi/imx8-isi-core.c | 43 ++++++++++++++++++- .../platform/nxp/imx8-isi/imx8-isi-core.h | 2 + 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c index 4ab2749e460d..981648a03113 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.c @@ -246,7 +246,7 @@ static void mxc_isi_v4l2_cleanup(struct mxc_isi_dev *isi) /* Panic will assert when the buffers are 50% full */ -/* For i.MX8QXP C0 and i.MX8MN ISI IER version */ +/* For i.MX8MN ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { .oflw_y_buf_en = { .mask = BIT(19) }, .oflw_u_buf_en = { .mask = BIT(21) }, @@ -257,7 +257,7 @@ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v1 = { .panic_v_buf_en = { .mask = BIT(24) }, }; -/* For i.MX8MP ISI IER version */ +/* For i.MX8QXP C0 and i.MX8MP ISI IER version */ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { .oflw_y_buf_en = { .mask = BIT(18) }, .oflw_u_buf_en = { .mask = BIT(20) }, @@ -268,6 +268,21 @@ static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_v2 = { .panic_v_buf_en = { .mask = BIT(23) }, }; +/* For i.MX8QM ISI IER version */ +static const struct mxc_isi_ier_reg mxc_imx8_isi_ier_qm = { + .oflw_y_buf_en = { .mask = BIT(16) }, + .oflw_u_buf_en = { .mask = BIT(19) }, + .oflw_v_buf_en = { .mask = BIT(22) }, + + .excs_oflw_y_buf_en = { .mask = BIT(17) }, + .excs_oflw_u_buf_en = { .mask = BIT(20) }, + .excs_oflw_v_buf_en = { .mask = BIT(23) }, + + .panic_y_buf_en = { .mask = BIT(18) }, + .panic_u_buf_en = { .mask = BIT(21) }, + .panic_v_buf_en = { .mask = BIT(24) }, +}; + /* Panic will assert when the buffers are 50% full */ static const struct mxc_isi_set_thd mxc_imx8_isi_thd_v1 = { .panic_set_thd_y = { .mask = 0x0000f, .offset = 0, .threshold = 0x7 }, @@ -322,6 +337,28 @@ static const struct mxc_isi_plat_data mxc_imx93_data = { .has_36bit_dma = false, }; +static const struct mxc_isi_plat_data mxc_imx8qm_data = { + .model = MXC_ISI_IMX8QM, + .num_ports = 5, + .num_channels = 8, + .reg_offset = 0x10000, + .ier_reg = &mxc_imx8_isi_ier_qm, + .set_thd = &mxc_imx8_isi_thd_v1, + .buf_active_reverse = true, + .has_36bit_dma = false, +}; + +static const struct mxc_isi_plat_data mxc_imx8qxp_data = { + .model = MXC_ISI_IMX8QXP, + .num_ports = 5, + .num_channels = 6, + .reg_offset = 0x10000, + .ier_reg = &mxc_imx8_isi_ier_v2, + .set_thd = &mxc_imx8_isi_thd_v1, + .buf_active_reverse = true, + .has_36bit_dma = false, +}; + /* ----------------------------------------------------------------------------- * Power management */ @@ -499,6 +536,8 @@ static void mxc_isi_remove(struct platform_device *pdev) static const struct of_device_id mxc_isi_of_match[] = { { .compatible = "fsl,imx8mn-isi", .data = &mxc_imx8mn_data }, { .compatible = "fsl,imx8mp-isi", .data = &mxc_imx8mp_data }, + { .compatible = "fsl,imx8qm-isi", .data = &mxc_imx8qm_data }, + { .compatible = "fsl,imx8qxp-isi", .data = &mxc_imx8qxp_data }, { .compatible = "fsl,imx8ulp-isi", .data = &mxc_imx8ulp_data }, { .compatible = "fsl,imx93-isi", .data = &mxc_imx93_data }, { /* sentinel */ }, diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h index bd3cfe5fbe06..206995bedca4 100644 --- a/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-core.h @@ -157,6 +157,8 @@ struct mxc_gasket_ops { enum model { MXC_ISI_IMX8MN, MXC_ISI_IMX8MP, + MXC_ISI_IMX8QM, + MXC_ISI_IMX8QXP, MXC_ISI_IMX8ULP, MXC_ISI_IMX93, }; From 1d2d96f5998a8a489919ff9504547b5f5845b967 Mon Sep 17 00:00:00 2001 From: Robert Chiras Date: Thu, 22 May 2025 13:56:46 -0400 Subject: [PATCH 083/310] media: dt-bindings: nxp,imx8mq-mipi-csi2: Add i.MX8QM(QXP) compatible strings Add compatible strings for i.MX8QM/i.MX8QXP platform. Remove fsl,mipi-phy-gpr from required properties and add new reg space, since i.MX8QM and i.MX8QXP use dedicate control and status register(csr) space. Keep the same restriction for other compatible strings. Signed-off-by: Robert Chiras Reviewed-by: Laurent Pinchart Reviewed-by: Rob Herring (Arm) Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-8-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../bindings/media/nxp,imx8mq-mipi-csi2.yaml | 38 +++++++++++++++++-- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml index 2a14e3b0e004..3389bab266a9 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8mq-mipi-csi2.yaml @@ -16,11 +16,19 @@ description: |- properties: compatible: - enum: - - fsl,imx8mq-mipi-csi2 + oneOf: + - enum: + - fsl,imx8mq-mipi-csi2 + - fsl,imx8qxp-mipi-csi2 + - items: + - const: fsl,imx8qm-mipi-csi2 + - const: fsl,imx8qxp-mipi-csi2 reg: - maxItems: 1 + items: + - description: MIPI CSI-2 RX host controller register. + - description: MIPI CSI-2 control and status register (csr). + minItems: 1 clocks: items: @@ -46,6 +54,7 @@ properties: - description: CORE_RESET reset register bit definition - description: PHY_REF_RESET reset register bit definition - description: ESC_RESET reset register bit definition + minItems: 1 fsl,mipi-phy-gpr: description: | @@ -113,9 +122,30 @@ required: - clock-names - power-domains - resets - - fsl,mipi-phy-gpr - ports +allOf: + - if: + properties: + compatible: + contains: + enum: + - fsl,imx8qxp-mipi-csi2 + then: + properties: + reg: + minItems: 2 + resets: + maxItems: 1 + else: + properties: + reg: + maxItems: 1 + resets: + minItems: 3 + required: + - fsl,mipi-phy-gpr + additionalProperties: false examples: From 382d53e9cefb3ca3fd0330ea609b94cb544e86b5 Mon Sep 17 00:00:00 2001 From: "Guoniu.zhou" Date: Thu, 22 May 2025 13:56:47 -0400 Subject: [PATCH 084/310] media: imx8mq-mipi-csi2: Add imx8mq_plat_data for different compatible strings Introduce `imx8mq_plat_data` along with enable/disable callback operations to facilitate support for new chips. No functional changes. Signed-off-by: Guoniu.zhou Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-9-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 58 ++++++++++++++----- 1 file changed, 44 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index a8bcf60e2f37..59ec7107b450 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -62,6 +62,8 @@ #define CSI2RX_CFG_VID_P_FIFO_SEND_LEVEL 0x188 #define CSI2RX_CFG_DISABLE_PAYLOAD_1 0x130 +struct csi_state; + enum { ST_POWERED = 1, ST_STREAMING = 2, @@ -83,11 +85,10 @@ static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = { #define CSI2_NUM_CLKS ARRAY_SIZE(imx8mq_mipi_csi_clk_id) -#define GPR_CSI2_1_RX_ENABLE BIT(13) -#define GPR_CSI2_1_VID_INTFC_ENB BIT(12) -#define GPR_CSI2_1_HSEL BIT(10) -#define GPR_CSI2_1_CONT_CLK_MODE BIT(8) -#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2) +struct imx8mq_plat_data { + int (*enable)(struct csi_state *state, u32 hs_settle); + void (*disable)(struct csi_state *state); +}; /* * The send level configures the number of entries that must accumulate in @@ -106,6 +107,7 @@ static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = { struct csi_state { struct device *dev; + const struct imx8mq_plat_data *pdata; void __iomem *regs; struct clk_bulk_data clks[CSI2_NUM_CLKS]; struct reset_control *rst; @@ -137,6 +139,34 @@ struct csi2_pix_format { u8 width; }; +/* ----------------------------------------------------------------------------- + * i.MX8MQ GPR + */ + +#define GPR_CSI2_1_RX_ENABLE BIT(13) +#define GPR_CSI2_1_VID_INTFC_ENB BIT(12) +#define GPR_CSI2_1_HSEL BIT(10) +#define GPR_CSI2_1_CONT_CLK_MODE BIT(8) +#define GPR_CSI2_1_S_PRG_RXHS_SETTLE(x) (((x) & 0x3f) << 2) + +static int imx8mq_gpr_enable(struct csi_state *state, u32 hs_settle) +{ + regmap_update_bits(state->phy_gpr, + state->phy_gpr_reg, + 0x3fff, + GPR_CSI2_1_RX_ENABLE | + GPR_CSI2_1_VID_INTFC_ENB | + GPR_CSI2_1_HSEL | + GPR_CSI2_1_CONT_CLK_MODE | + GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle)); + + return 0; +} + +static const struct imx8mq_plat_data imx8mq_data = { + .enable = imx8mq_gpr_enable, +}; + static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = { /* RAW (Bayer and greyscale) formats. */ { @@ -371,14 +401,9 @@ static int imx8mq_mipi_csi_start_stream(struct csi_state *state, if (ret) return ret; - regmap_update_bits(state->phy_gpr, - state->phy_gpr_reg, - 0x3fff, - GPR_CSI2_1_RX_ENABLE | - GPR_CSI2_1_VID_INTFC_ENB | - GPR_CSI2_1_HSEL | - GPR_CSI2_1_CONT_CLK_MODE | - GPR_CSI2_1_S_PRG_RXHS_SETTLE(hs_settle)); + ret = state->pdata->enable(state, hs_settle); + if (ret) + return ret; return 0; } @@ -386,6 +411,9 @@ static int imx8mq_mipi_csi_start_stream(struct csi_state *state, static void imx8mq_mipi_csi_stop_stream(struct csi_state *state) { imx8mq_mipi_csi_write(state, CSI2RX_CFG_DISABLE_DATA_LANES, 0xf); + + if (state->pdata->disable) + state->pdata->disable(state); } /* ----------------------------------------------------------------------------- @@ -876,6 +904,8 @@ static int imx8mq_mipi_csi_probe(struct platform_device *pdev) state->dev = dev; + state->pdata = of_device_get_match_data(dev); + ret = imx8mq_mipi_csi_parse_dt(state); if (ret < 0) { dev_err(dev, "Failed to parse device tree: %d\n", ret); @@ -953,7 +983,7 @@ static void imx8mq_mipi_csi_remove(struct platform_device *pdev) } static const struct of_device_id imx8mq_mipi_csi_of_match[] = { - { .compatible = "fsl,imx8mq-mipi-csi2", }, + { .compatible = "fsl,imx8mq-mipi-csi2", .data = &imx8mq_data }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match); From 642b70d526ab8daa8f256dfc1eb6bf27c3290cc6 Mon Sep 17 00:00:00 2001 From: Frank Li Date: Thu, 22 May 2025 13:56:48 -0400 Subject: [PATCH 085/310] media: imx8mq-mipi-csi2: Add support for i.MX8QXP Add support for i.MX8QXP, which has a dedicated control and status register (CSR) space. Enable obtaining the second register space and initializing PHY and link settings accordingly. Reviewed-by: Laurent Pinchart Signed-off-by: Frank Li Link: https://lore.kernel.org/r/20250522-8qxp_camera-v5-10-d4be869fdb7e@nxp.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 111 ++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 59ec7107b450..6501843ae72d 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -5,6 +5,7 @@ * Copyright (C) 2021 Purism SPC */ +#include #include #include #include @@ -88,6 +89,7 @@ static const char * const imx8mq_mipi_csi_clk_id[CSI2_NUM_CLKS] = { struct imx8mq_plat_data { int (*enable)(struct csi_state *state, u32 hs_settle); void (*disable)(struct csi_state *state); + bool use_reg_csr; }; /* @@ -167,6 +169,95 @@ static const struct imx8mq_plat_data imx8mq_data = { .enable = imx8mq_gpr_enable, }; +/* ----------------------------------------------------------------------------- + * i.MX8QXP + */ + +#define CSI2SS_PL_CLK_INTERVAL_US 100 +#define CSI2SS_PL_CLK_TIMEOUT_US 100000 + +#define CSI2SS_PLM_CTRL 0x0 +#define CSI2SS_PLM_CTRL_ENABLE_PL BIT(0) +#define CSI2SS_PLM_CTRL_VSYNC_OVERRIDE BIT(9) +#define CSI2SS_PLM_CTRL_HSYNC_OVERRIDE BIT(10) +#define CSI2SS_PLM_CTRL_VALID_OVERRIDE BIT(11) +#define CSI2SS_PLM_CTRL_POLARITY_HIGH BIT(12) +#define CSI2SS_PLM_CTRL_PL_CLK_RUN BIT(31) + +#define CSI2SS_PHY_CTRL 0x4 +#define CSI2SS_PHY_CTRL_RX_ENABLE BIT(0) +#define CSI2SS_PHY_CTRL_AUTO_PD_EN BIT(1) +#define CSI2SS_PHY_CTRL_DDRCLK_EN BIT(2) +#define CSI2SS_PHY_CTRL_CONT_CLK_MODE BIT(3) +#define CSI2SS_PHY_CTRL_RX_HS_SETTLE_MASK GENMASK(9, 4) +#define CSI2SS_PHY_CTRL_RTERM_SEL BIT(21) +#define CSI2SS_PHY_CTRL_PD BIT(22) + +#define CSI2SS_DATA_TYPE_DISABLE_BF 0x38 +#define CSI2SS_DATA_TYPE_DISABLE_BF_MASK GENMASK(23, 0) + +#define CSI2SS_CTRL_CLK_RESET 0x44 +#define CSI2SS_CTRL_CLK_RESET_EN BIT(0) + +static int imx8qxp_gpr_enable(struct csi_state *state, u32 hs_settle) +{ + int ret; + u32 val; + + /* Clear format */ + regmap_clear_bits(state->phy_gpr, CSI2SS_DATA_TYPE_DISABLE_BF, + CSI2SS_DATA_TYPE_DISABLE_BF_MASK); + + regmap_write(state->phy_gpr, CSI2SS_PLM_CTRL, 0x0); + + regmap_write(state->phy_gpr, CSI2SS_PHY_CTRL, + FIELD_PREP(CSI2SS_PHY_CTRL_RX_HS_SETTLE_MASK, hs_settle) | + CSI2SS_PHY_CTRL_RX_ENABLE | CSI2SS_PHY_CTRL_DDRCLK_EN | + CSI2SS_PHY_CTRL_CONT_CLK_MODE | CSI2SS_PHY_CTRL_PD | + CSI2SS_PHY_CTRL_RTERM_SEL | CSI2SS_PHY_CTRL_AUTO_PD_EN); + + ret = regmap_read_poll_timeout(state->phy_gpr, CSI2SS_PLM_CTRL, + val, !(val & CSI2SS_PLM_CTRL_PL_CLK_RUN), + CSI2SS_PL_CLK_INTERVAL_US, + CSI2SS_PL_CLK_TIMEOUT_US); + + if (ret) { + dev_err(state->dev, "Timeout waiting for Pixel-Link clock\n"); + return ret; + } + + /* Enable Pixel link Master */ + regmap_set_bits(state->phy_gpr, CSI2SS_PLM_CTRL, + CSI2SS_PLM_CTRL_ENABLE_PL | CSI2SS_PLM_CTRL_VALID_OVERRIDE); + + /* PHY Enable */ + regmap_clear_bits(state->phy_gpr, CSI2SS_PHY_CTRL, + CSI2SS_PHY_CTRL_PD | CSI2SS_PLM_CTRL_POLARITY_HIGH); + + /* Release Reset */ + regmap_set_bits(state->phy_gpr, CSI2SS_CTRL_CLK_RESET, CSI2SS_CTRL_CLK_RESET_EN); + + return ret; +} + +static void imx8qxp_gpr_disable(struct csi_state *state) +{ + /* Disable Pixel Link */ + regmap_write(state->phy_gpr, CSI2SS_PLM_CTRL, 0x0); + + /* Disable PHY */ + regmap_write(state->phy_gpr, CSI2SS_PHY_CTRL, 0x0); + + regmap_clear_bits(state->phy_gpr, CSI2SS_CTRL_CLK_RESET, + CSI2SS_CTRL_CLK_RESET_EN); +}; + +static const struct imx8mq_plat_data imx8qxp_data = { + .enable = imx8qxp_gpr_enable, + .disable = imx8qxp_gpr_disable, + .use_reg_csr = true, +}; + static const struct csi2_pix_format imx8mq_mipi_csi_formats[] = { /* RAW (Bayer and greyscale) formats. */ { @@ -865,6 +956,25 @@ static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) return PTR_ERR(state->rst); } + if (state->pdata->use_reg_csr) { + const struct regmap_config regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, + }; + void __iomem *base; + + base = devm_platform_ioremap_resource(to_platform_device(dev), 1); + if (IS_ERR(base)) + return dev_err_probe(dev, IS_ERR(base), "Missing CSR register\n"); + + state->phy_gpr = devm_regmap_init_mmio(dev, base, ®map_config); + if (IS_ERR(state->phy_gpr)) + return dev_err_probe(dev, PTR_ERR(state->phy_gpr), + "Failed to init CSI MMIO regmap\n"); + return 0; + } + ret = of_property_read_u32_array(np, "fsl,mipi-phy-gpr", out_val, ARRAY_SIZE(out_val)); if (ret) { @@ -984,6 +1094,7 @@ static void imx8mq_mipi_csi_remove(struct platform_device *pdev) static const struct of_device_id imx8mq_mipi_csi_of_match[] = { { .compatible = "fsl,imx8mq-mipi-csi2", .data = &imx8mq_data }, + { .compatible = "fsl,imx8qxp-mipi-csi2", .data = &imx8qxp_data }, { /* sentinel */ }, }; MODULE_DEVICE_TABLE(of, imx8mq_mipi_csi_of_match); From ba4452b0c0a3191617764d3453f45ea48ed544c9 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Mon, 12 May 2025 10:01:36 +0800 Subject: [PATCH 086/310] media: amphion: Add H264 and HEVC profile and level control For format H264 and HEVC, the firmware can report the parsed profile idc and level idc to driver, these information may be useful. Implement the H264 and HEVC profile and level control to report them. Signed-off-by: Ming Qian Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vdec.c | 59 +++++++++ drivers/media/platform/amphion/vpu_defs.h | 12 ++ drivers/media/platform/amphion/vpu_helpers.c | 123 +++++++++++++++++++ drivers/media/platform/amphion/vpu_helpers.h | 7 ++ drivers/media/platform/amphion/vpu_malone.c | 5 +- 5 files changed, 205 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index 85d518823159..55067d9405c2 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -232,6 +232,35 @@ static int vdec_ctrl_init(struct vpu_inst *inst) V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE, 0, 1, 1, 0); + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, NULL, + V4L2_CID_MPEG_VIDEO_H264_PROFILE, + V4L2_MPEG_VIDEO_H264_PROFILE_MULTIVIEW_HIGH, + ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED) | + (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)), + V4L2_MPEG_VIDEO_H264_PROFILE_MAIN); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, NULL, + V4L2_CID_MPEG_VIDEO_H264_LEVEL, + V4L2_MPEG_VIDEO_H264_LEVEL_6_2, + 0, + V4L2_MPEG_VIDEO_H264_LEVEL_4_0); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, NULL, + V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10, + ~((1 << V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN) | + (1 << V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10)), + V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN); + + v4l2_ctrl_new_std_menu(&inst->ctrl_handler, NULL, + V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2, + 0, + V4L2_MPEG_VIDEO_HEVC_LEVEL_4); + ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler, &vdec_ctrl_ops, V4L2_CID_MIN_BUFFERS_FOR_CAPTURE, 1, 32, 1, 2); if (ctrl) @@ -1166,6 +1195,35 @@ static void vdec_clear_slots(struct vpu_inst *inst) } } +static void vdec_update_v4l2_ctrl(struct vpu_inst *inst, u32 id, u32 val) +{ + struct v4l2_ctrl *ctrl = v4l2_ctrl_find(&inst->ctrl_handler, id); + + if (ctrl) + v4l2_ctrl_s_ctrl(ctrl, val); +} + +static void vdec_update_v4l2_profile_level(struct vpu_inst *inst, struct vpu_dec_codec_info *hdr) +{ + switch (inst->out_format.pixfmt) { + case V4L2_PIX_FMT_H264: + case V4L2_PIX_FMT_H264_MVC: + vdec_update_v4l2_ctrl(inst, V4L2_CID_MPEG_VIDEO_H264_PROFILE, + vpu_get_h264_v4l2_profile(hdr)); + vdec_update_v4l2_ctrl(inst, V4L2_CID_MPEG_VIDEO_H264_LEVEL, + vpu_get_h264_v4l2_level(hdr)); + break; + case V4L2_PIX_FMT_HEVC: + vdec_update_v4l2_ctrl(inst, V4L2_CID_MPEG_VIDEO_HEVC_PROFILE, + vpu_get_hevc_v4l2_profile(hdr)); + vdec_update_v4l2_ctrl(inst, V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, + vpu_get_hevc_v4l2_level(hdr)); + break; + default: + return; + } +} + static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info *hdr) { struct vdec_t *vdec = inst->priv; @@ -1189,6 +1247,7 @@ static void vdec_event_seq_hdr(struct vpu_inst *inst, struct vpu_dec_codec_info vdec_init_crop(inst); vdec_init_mbi(inst); vdec_init_dcp(inst); + vdec_update_v4l2_profile_level(inst, hdr); if (!vdec->seq_hdr_found) { vdec->seq_tag = vdec->codec_info.tag; if (vdec->is_source_changed) { diff --git a/drivers/media/platform/amphion/vpu_defs.h b/drivers/media/platform/amphion/vpu_defs.h index 428d988cf2f7..f56245ae2205 100644 --- a/drivers/media/platform/amphion/vpu_defs.h +++ b/drivers/media/platform/amphion/vpu_defs.h @@ -134,6 +134,7 @@ struct vpu_dec_codec_info { u32 decoded_height; struct v4l2_fract frame_rate; u32 dsp_asp_ratio; + u32 profile_idc; u32 level_idc; u32 bit_depth_luma; u32 bit_depth_chroma; @@ -147,6 +148,17 @@ struct vpu_dec_codec_info { u32 mbi_size; u32 dcp_size; u32 stride; + union { + struct { + u32 constraint_set5_flag : 1; + u32 constraint_set4_flag : 1; + u32 constraint_set3_flag : 1; + u32 constraint_set2_flag : 1; + u32 constraint_set1_flag : 1; + u32 constraint_set0_flag : 1; + }; + u32 constraint_set_flags; + }; }; struct vpu_dec_pic_info { diff --git a/drivers/media/platform/amphion/vpu_helpers.c b/drivers/media/platform/amphion/vpu_helpers.c index d12310af9ebc..886d5632388e 100644 --- a/drivers/media/platform/amphion/vpu_helpers.c +++ b/drivers/media/platform/amphion/vpu_helpers.c @@ -509,3 +509,126 @@ const char *vpu_codec_state_name(enum vpu_codec_state state) } return ""; } + +struct codec_id_mapping { + u32 id; + u32 v4l2_id; +}; + +static struct codec_id_mapping h264_profiles[] = { + {66, V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE}, + {77, V4L2_MPEG_VIDEO_H264_PROFILE_MAIN}, + {88, V4L2_MPEG_VIDEO_H264_PROFILE_EXTENDED}, + {100, V4L2_MPEG_VIDEO_H264_PROFILE_HIGH} +}; + +static struct codec_id_mapping h264_levels[] = { + {10, V4L2_MPEG_VIDEO_H264_LEVEL_1_0}, + {9, V4L2_MPEG_VIDEO_H264_LEVEL_1B}, + {11, V4L2_MPEG_VIDEO_H264_LEVEL_1_1}, + {12, V4L2_MPEG_VIDEO_H264_LEVEL_1_2}, + {13, V4L2_MPEG_VIDEO_H264_LEVEL_1_3}, + {20, V4L2_MPEG_VIDEO_H264_LEVEL_2_0}, + {21, V4L2_MPEG_VIDEO_H264_LEVEL_2_1}, + {22, V4L2_MPEG_VIDEO_H264_LEVEL_2_2}, + {30, V4L2_MPEG_VIDEO_H264_LEVEL_3_0}, + {31, V4L2_MPEG_VIDEO_H264_LEVEL_3_1}, + {32, V4L2_MPEG_VIDEO_H264_LEVEL_3_2}, + {40, V4L2_MPEG_VIDEO_H264_LEVEL_4_0}, + {41, V4L2_MPEG_VIDEO_H264_LEVEL_4_1}, + {42, V4L2_MPEG_VIDEO_H264_LEVEL_4_2}, + {50, V4L2_MPEG_VIDEO_H264_LEVEL_5_0}, + {51, V4L2_MPEG_VIDEO_H264_LEVEL_5_1}, + {52, V4L2_MPEG_VIDEO_H264_LEVEL_5_2}, + {60, V4L2_MPEG_VIDEO_H264_LEVEL_6_0}, + {61, V4L2_MPEG_VIDEO_H264_LEVEL_6_1}, + {62, V4L2_MPEG_VIDEO_H264_LEVEL_6_2} +}; + +static struct codec_id_mapping hevc_profiles[] = { + {1, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN}, + {2, V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10} +}; + +static struct codec_id_mapping hevc_levels[] = { + {30, V4L2_MPEG_VIDEO_HEVC_LEVEL_1}, + {60, V4L2_MPEG_VIDEO_HEVC_LEVEL_2}, + {63, V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1}, + {90, V4L2_MPEG_VIDEO_HEVC_LEVEL_3}, + {93, V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1}, + {120, V4L2_MPEG_VIDEO_HEVC_LEVEL_4}, + {123, V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1}, + {150, V4L2_MPEG_VIDEO_HEVC_LEVEL_5}, + {153, V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1}, + {156, V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2}, + {180, V4L2_MPEG_VIDEO_HEVC_LEVEL_6}, + {183, V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1}, + {186, V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2} +}; + +static u32 vpu_find_v4l2_id(u32 id, struct codec_id_mapping *array, u32 array_sz) +{ + u32 i; + + if (!array || !array_sz) + return 0; + + for (i = 0; i < array_sz; i++) { + if (id == array[i].id) + return array[i].v4l2_id; + } + + return 0; +} + +u32 vpu_get_h264_v4l2_profile(struct vpu_dec_codec_info *hdr) +{ + if (!hdr) + return 0; + + /* + * In H.264 Document section A.2.1.1 Constrained Baseline profile + * Conformance of a bitstream to the Constrained Baseline profile is indicated by + * profile_idc being equal to 66 with constraint_set1_flag being equal to 1. + */ + if (hdr->profile_idc == 66 && hdr->constraint_set1_flag) + return V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE; + + return vpu_find_v4l2_id(hdr->profile_idc, h264_profiles, ARRAY_SIZE(h264_profiles)); +} + +u32 vpu_get_h264_v4l2_level(struct vpu_dec_codec_info *hdr) +{ + if (!hdr) + return 0; + + /* + * In H.264 Document section 7.4.2.1.1 Sequence parameter set data semantics + * If profile_idc is equal to 66, 77, or 88 and level_idc is equal to 11, + * constraint_set3_flag equal to 1 indicates that the coded video sequence + * obeys all constraints specified in Annex A for level 1b + * and constraint_set3_flag equal to 0 indicates that the coded video sequence + * obeys all constraints specified in Annex A for level 1.1. + */ + if (hdr->level_idc == 11 && hdr->constraint_set3_flag && + (hdr->profile_idc == 66 || hdr->profile_idc == 77 || hdr->profile_idc == 88)) + return V4L2_MPEG_VIDEO_H264_LEVEL_1B; + + return vpu_find_v4l2_id(hdr->level_idc, h264_levels, ARRAY_SIZE(h264_levels)); +} + +u32 vpu_get_hevc_v4l2_profile(struct vpu_dec_codec_info *hdr) +{ + if (!hdr) + return 0; + + return vpu_find_v4l2_id(hdr->profile_idc, hevc_profiles, ARRAY_SIZE(hevc_profiles)); +} + +u32 vpu_get_hevc_v4l2_level(struct vpu_dec_codec_info *hdr) +{ + if (!hdr) + return 0; + + return vpu_find_v4l2_id(hdr->level_idc, hevc_levels, ARRAY_SIZE(hevc_levels)); +} diff --git a/drivers/media/platform/amphion/vpu_helpers.h b/drivers/media/platform/amphion/vpu_helpers.h index 84d16eb39650..76fba0d6090c 100644 --- a/drivers/media/platform/amphion/vpu_helpers.h +++ b/drivers/media/platform/amphion/vpu_helpers.h @@ -6,6 +6,8 @@ #ifndef _AMPHION_VPU_HELPERS_H #define _AMPHION_VPU_HELPERS_H +#include "vpu_defs.h" + struct vpu_pair { u32 src; u32 dst; @@ -65,4 +67,9 @@ u32 vpu_color_cvrt_full_range_i2v(u32 full_range); int vpu_find_dst_by_src(struct vpu_pair *pairs, u32 cnt, u32 src); int vpu_find_src_by_dst(struct vpu_pair *pairs, u32 cnt, u32 dst); + +u32 vpu_get_h264_v4l2_profile(struct vpu_dec_codec_info *hdr); +u32 vpu_get_h264_v4l2_level(struct vpu_dec_codec_info *hdr); +u32 vpu_get_hevc_v4l2_profile(struct vpu_dec_codec_info *hdr); +u32 vpu_get_hevc_v4l2_level(struct vpu_dec_codec_info *hdr); #endif diff --git a/drivers/media/platform/amphion/vpu_malone.c b/drivers/media/platform/amphion/vpu_malone.c index feca7d4220ed..ba688566dffd 100644 --- a/drivers/media/platform/amphion/vpu_malone.c +++ b/drivers/media/platform/amphion/vpu_malone.c @@ -908,7 +908,8 @@ static void vpu_malone_unpack_seq_hdr(struct vpu_rpc_event *pkt, info->frame_rate.numerator = 1000; info->frame_rate.denominator = pkt->data[8]; info->dsp_asp_ratio = pkt->data[9]; - info->level_idc = pkt->data[10]; + info->profile_idc = (pkt->data[10] >> 8) & 0xff; + info->level_idc = pkt->data[10] & 0xff; info->bit_depth_luma = pkt->data[13]; info->bit_depth_chroma = pkt->data[14]; info->chroma_fmt = pkt->data[15]; @@ -925,6 +926,8 @@ static void vpu_malone_unpack_seq_hdr(struct vpu_rpc_event *pkt, info->pixfmt = V4L2_PIX_FMT_NV12M_10BE_8L128; else info->pixfmt = V4L2_PIX_FMT_NV12M_8L128; + if (pkt->hdr.num > 28) + info->constraint_set_flags = pkt->data[28]; if (info->frame_rate.numerator && info->frame_rate.denominator) { unsigned long n, d; From 51ad3b570ea7b1916ff4db993f1aa22bb48fdac6 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Wed, 21 May 2025 09:54:07 +0800 Subject: [PATCH 087/310] media: imx-jpeg: Account for data_offset when getting image address Applications may set data_offset when it refers to an output queue. So driver need to account for it when getting the start address of input image in the plane. Meanwhile the mxc-jpeg codec requires the address (plane address + data_offset) to be 16-aligned. Fixes: 2db16c6ed72c ("media: imx-jpeg: Add V4L2 driver for i.MX8 JPEG Encoder/Decoder") Signed-off-by: Ming Qian Reviewed-by: Frank Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../media/platform/nxp/imx-jpeg/mxc-jpeg.c | 47 ++++++++++++++----- .../media/platform/nxp/imx-jpeg/mxc-jpeg.h | 1 + 2 files changed, 37 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c index 5c17bc58181e..8681dd193033 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.c @@ -598,6 +598,27 @@ static void _bswap16(u16 *a) *a = ((*a & 0x00FF) << 8) | ((*a & 0xFF00) >> 8); } +static dma_addr_t mxc_jpeg_get_plane_dma_addr(struct vb2_buffer *buf, unsigned int plane_no) +{ + if (plane_no >= buf->num_planes) + return 0; + return vb2_dma_contig_plane_dma_addr(buf, plane_no) + buf->planes[plane_no].data_offset; +} + +static void *mxc_jpeg_get_plane_vaddr(struct vb2_buffer *buf, unsigned int plane_no) +{ + if (plane_no >= buf->num_planes) + return NULL; + return vb2_plane_vaddr(buf, plane_no) + buf->planes[plane_no].data_offset; +} + +static unsigned long mxc_jpeg_get_plane_payload(struct vb2_buffer *buf, unsigned int plane_no) +{ + if (plane_no >= buf->num_planes) + return 0; + return vb2_get_plane_payload(buf, plane_no) - buf->planes[plane_no].data_offset; +} + static void print_mxc_buf(struct mxc_jpeg_dev *jpeg, struct vb2_buffer *buf, unsigned long len) { @@ -610,11 +631,11 @@ static void print_mxc_buf(struct mxc_jpeg_dev *jpeg, struct vb2_buffer *buf, return; for (plane_no = 0; plane_no < buf->num_planes; plane_no++) { - payload = vb2_get_plane_payload(buf, plane_no); + payload = mxc_jpeg_get_plane_payload(buf, plane_no); if (len == 0) len = payload; - dma_addr = vb2_dma_contig_plane_dma_addr(buf, plane_no); - vaddr = vb2_plane_vaddr(buf, plane_no); + dma_addr = mxc_jpeg_get_plane_dma_addr(buf, plane_no); + vaddr = mxc_jpeg_get_plane_vaddr(buf, plane_no); v4l2_dbg(3, debug, &jpeg->v4l2_dev, "plane %d (vaddr=%p dma_addr=%x payload=%ld):", plane_no, vaddr, dma_addr, payload); @@ -712,16 +733,15 @@ static void mxc_jpeg_addrs(struct mxc_jpeg_desc *desc, struct mxc_jpeg_q_data *q_data; q_data = mxc_jpeg_get_q_data(ctx, raw_buf->type); - desc->buf_base0 = vb2_dma_contig_plane_dma_addr(raw_buf, 0); + desc->buf_base0 = mxc_jpeg_get_plane_dma_addr(raw_buf, 0); desc->buf_base1 = 0; if (img_fmt == STM_CTRL_IMAGE_FORMAT(MXC_JPEG_YUV420)) { if (raw_buf->num_planes == 2) - desc->buf_base1 = vb2_dma_contig_plane_dma_addr(raw_buf, 1); + desc->buf_base1 = mxc_jpeg_get_plane_dma_addr(raw_buf, 1); else desc->buf_base1 = desc->buf_base0 + q_data->sizeimage[0]; } - desc->stm_bufbase = vb2_dma_contig_plane_dma_addr(jpeg_buf, 0) + - offset; + desc->stm_bufbase = mxc_jpeg_get_plane_dma_addr(jpeg_buf, 0) + offset; } static bool mxc_jpeg_is_extended_sequential(const struct mxc_jpeg_fmt *fmt) @@ -1029,8 +1049,8 @@ static irqreturn_t mxc_jpeg_dec_irq(int irq, void *priv) vb2_set_plane_payload(&dst_buf->vb2_buf, 1, payload); } dev_dbg(dev, "Decoding finished, payload size: %ld + %ld\n", - vb2_get_plane_payload(&dst_buf->vb2_buf, 0), - vb2_get_plane_payload(&dst_buf->vb2_buf, 1)); + mxc_jpeg_get_plane_payload(&dst_buf->vb2_buf, 0), + mxc_jpeg_get_plane_payload(&dst_buf->vb2_buf, 1)); } /* short preview of the results */ @@ -1889,8 +1909,8 @@ static int mxc_jpeg_parse(struct mxc_jpeg_ctx *ctx, struct vb2_buffer *vb) struct mxc_jpeg_sof *psof = NULL; struct mxc_jpeg_sos *psos = NULL; struct mxc_jpeg_src_buf *jpeg_src_buf = vb2_to_mxc_buf(vb); - u8 *src_addr = (u8 *)vb2_plane_vaddr(vb, 0); - u32 size = vb2_get_plane_payload(vb, 0); + u8 *src_addr = (u8 *)mxc_jpeg_get_plane_vaddr(vb, 0); + u32 size = mxc_jpeg_get_plane_payload(vb, 0); int ret; memset(&header, 0, sizeof(header)); @@ -2027,6 +2047,11 @@ static int mxc_jpeg_buf_prepare(struct vb2_buffer *vb) i, vb2_plane_size(vb, i), sizeimage); return -EINVAL; } + if (!IS_ALIGNED(mxc_jpeg_get_plane_dma_addr(vb, i), MXC_JPEG_ADDR_ALIGNMENT)) { + dev_err(dev, "planes[%d] address is not %d aligned\n", + i, MXC_JPEG_ADDR_ALIGNMENT); + return -EINVAL; + } } if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) { vb2_set_plane_payload(vb, 0, 0); diff --git a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h index fdde45f7e163..44e46face6d1 100644 --- a/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h +++ b/drivers/media/platform/nxp/imx-jpeg/mxc-jpeg.h @@ -30,6 +30,7 @@ #define MXC_JPEG_MAX_PLANES 2 #define MXC_JPEG_PATTERN_WIDTH 128 #define MXC_JPEG_PATTERN_HEIGHT 64 +#define MXC_JPEG_ADDR_ALIGNMENT 16 enum mxc_jpeg_enc_state { MXC_JPEG_ENCODING = 0, /* jpeg encode phase */ From b245bd0eb43f8f12353d9a61228eb4a61eff267b Mon Sep 17 00:00:00 2001 From: Frank Li Date: Fri, 11 Apr 2025 17:36:00 -0400 Subject: [PATCH 088/310] dt-bindings: media: convert fsl-vdoa.txt to yaml format Convert fsl-vdoa.txt to yaml format. Additional changes: - Add irq.h and imx6qdl-clock.h in example. Signed-off-by: Frank Li Reviewed-by: Rob Herring (Arm) Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../bindings/media/fsl,imx6q-vdoa.yaml | 42 +++++++++++++++++++ .../devicetree/bindings/media/fsl-vdoa.txt | 21 ---------- 2 files changed, 42 insertions(+), 21 deletions(-) create mode 100644 Documentation/devicetree/bindings/media/fsl,imx6q-vdoa.yaml delete mode 100644 Documentation/devicetree/bindings/media/fsl-vdoa.txt diff --git a/Documentation/devicetree/bindings/media/fsl,imx6q-vdoa.yaml b/Documentation/devicetree/bindings/media/fsl,imx6q-vdoa.yaml new file mode 100644 index 000000000000..511ac0d67a7f --- /dev/null +++ b/Documentation/devicetree/bindings/media/fsl,imx6q-vdoa.yaml @@ -0,0 +1,42 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/fsl,imx6q-vdoa.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Freescale Video Data Order Adapter + +description: + The Video Data Order Adapter (VDOA) is present on the i.MX6q. Its sole purpose + is to reorder video data from the macroblock tiled order produced by the CODA + 960 VPU to the conventional raster-scan order for scanout. + +maintainers: + - Frank Li + +properties: + compatible: + const: "fsl,imx6q-vdoa" + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + clocks: + maxItems: 1 + +additionalProperties: false + +examples: + - | + #include + #include + + vdoa@21e4000 { + compatible = "fsl,imx6q-vdoa"; + reg = <0x021e4000 0x4000>; + interrupts = <0 18 IRQ_TYPE_LEVEL_HIGH>; + clocks = <&clks IMX6QDL_CLK_VDOA>; + }; diff --git a/Documentation/devicetree/bindings/media/fsl-vdoa.txt b/Documentation/devicetree/bindings/media/fsl-vdoa.txt deleted file mode 100644 index 6c5628530bb7..000000000000 --- a/Documentation/devicetree/bindings/media/fsl-vdoa.txt +++ /dev/null @@ -1,21 +0,0 @@ -Freescale Video Data Order Adapter -================================== - -The Video Data Order Adapter (VDOA) is present on the i.MX6q. Its sole purpose -is to reorder video data from the macroblock tiled order produced by the CODA -960 VPU to the conventional raster-scan order for scanout. - -Required properties: -- compatible: must be "fsl,imx6q-vdoa" -- reg: the register base and size for the device registers -- interrupts: the VDOA interrupt -- clocks: the vdoa clock - -Example: - -vdoa@21e4000 { - compatible = "fsl,imx6q-vdoa"; - reg = <0x021e4000 0x4000>; - interrupts = <0 18 IRQ_TYPE_LEVEL_HIGH>; - clocks = <&clks IMX6QDL_CLK_VDOA>; -}; From 193cd0fd5a6055b88a3b2860c43a647362f9e7c4 Mon Sep 17 00:00:00 2001 From: Jernej Skrabec Date: Fri, 23 May 2025 17:43:59 +0200 Subject: [PATCH 089/310] media: cedrus: Add support for additional output formats If VPU supports untiled output, it actually supports several different YUV 4:2:0 layouts, namely NV12, NV21, YUV420 and YVU420. Add support for all of them. Signed-off-by: Jernej Skrabec Reviewed-by: Paul Kocialkowski Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil [hverkuil: add 'default' case to switch to fix warning with old compiler] --- .../staging/media/sunxi/cedrus/cedrus_hw.c | 19 ++++++++++++++++++- .../staging/media/sunxi/cedrus/cedrus_video.c | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c index 32af0e96e762..444fb53878d1 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_hw.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_hw.c @@ -86,9 +86,26 @@ void cedrus_dst_format_set(struct cedrus_dev *dev, switch (fmt->pixelformat) { case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: chroma_size = ALIGN(width, 16) * ALIGN(height, 16) / 2; - reg = VE_PRIMARY_OUT_FMT_NV12; + switch (fmt->pixelformat) { + case V4L2_PIX_FMT_NV12: + reg = VE_PRIMARY_OUT_FMT_NV12; + break; + case V4L2_PIX_FMT_NV21: + reg = VE_PRIMARY_OUT_FMT_NV21; + break; + case V4L2_PIX_FMT_YUV420: + reg = VE_PRIMARY_OUT_FMT_YU12; + break; + case V4L2_PIX_FMT_YVU420: + default: + reg = VE_PRIMARY_OUT_FMT_YV12; + break; + } cedrus_write(dev, VE_PRIMARY_OUT_FMT, reg); reg = chroma_size / 2; diff --git a/drivers/staging/media/sunxi/cedrus/cedrus_video.c b/drivers/staging/media/sunxi/cedrus/cedrus_video.c index 77f78266f406..9fae2c7493d0 100644 --- a/drivers/staging/media/sunxi/cedrus/cedrus_video.c +++ b/drivers/staging/media/sunxi/cedrus/cedrus_video.c @@ -64,6 +64,21 @@ static struct cedrus_format cedrus_formats[] = { .pixelformat = V4L2_PIX_FMT_NV12_32L32, .directions = CEDRUS_DECODE_DST, }, + { + .pixelformat = V4L2_PIX_FMT_NV21, + .directions = CEDRUS_DECODE_DST, + .capabilities = CEDRUS_CAPABILITY_UNTILED, + }, + { + .pixelformat = V4L2_PIX_FMT_YUV420, + .directions = CEDRUS_DECODE_DST, + .capabilities = CEDRUS_CAPABILITY_UNTILED, + }, + { + .pixelformat = V4L2_PIX_FMT_YVU420, + .directions = CEDRUS_DECODE_DST, + .capabilities = CEDRUS_CAPABILITY_UNTILED, + }, }; #define CEDRUS_FORMATS_COUNT ARRAY_SIZE(cedrus_formats) @@ -140,6 +155,9 @@ void cedrus_prepare_format(struct v4l2_pix_format *pix_fmt) break; case V4L2_PIX_FMT_NV12: + case V4L2_PIX_FMT_NV21: + case V4L2_PIX_FMT_YUV420: + case V4L2_PIX_FMT_YVU420: /* 16-aligned stride. */ bytesperline = ALIGN(width, 16); From 01350185fe02ae3ea2c12d578e06af0d5186f33e Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Mon, 17 Feb 2025 16:46:54 -0500 Subject: [PATCH 090/310] media: verisilicon: Fix AV1 decoder clock frequency The desired clock frequency was correctly set to 400MHz in the device tree but was lowered by the driver to 300MHz breaking 4K 60Hz content playback. Fix the issue by removing the driver call to clk_set_rate(), which reduce the amount of board specific code. Fixes: 003afda97c65 ("media: verisilicon: Enable AV1 decoder on rk3588") Cc: stable@vger.kernel.org Reviewed-by: Benjamin Gaignard Reviewed-by: Philipp Zabel Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/rockchip_vpu_hw.c | 9 --------- 1 file changed, 9 deletions(-) diff --git a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c index acd29fa41d2d..02673be9878e 100644 --- a/drivers/media/platform/verisilicon/rockchip_vpu_hw.c +++ b/drivers/media/platform/verisilicon/rockchip_vpu_hw.c @@ -17,7 +17,6 @@ #define RK3066_ACLK_MAX_FREQ (300 * 1000 * 1000) #define RK3288_ACLK_MAX_FREQ (400 * 1000 * 1000) -#define RK3588_ACLK_MAX_FREQ (300 * 1000 * 1000) #define ROCKCHIP_VPU981_MIN_SIZE 64 @@ -454,13 +453,6 @@ static int rk3066_vpu_hw_init(struct hantro_dev *vpu) return 0; } -static int rk3588_vpu981_hw_init(struct hantro_dev *vpu) -{ - /* Bump ACLKs to max. possible freq. to improve performance. */ - clk_set_rate(vpu->clocks[0].clk, RK3588_ACLK_MAX_FREQ); - return 0; -} - static int rockchip_vpu_hw_init(struct hantro_dev *vpu) { /* Bump ACLK to max. possible freq. to improve performance. */ @@ -821,7 +813,6 @@ const struct hantro_variant rk3588_vpu981_variant = { .codec_ops = rk3588_vpu981_codec_ops, .irqs = rk3588_vpu981_irqs, .num_irqs = ARRAY_SIZE(rk3588_vpu981_irqs), - .init = rk3588_vpu981_hw_init, .clk_names = rk3588_vpu981_vpu_clk_names, .num_clocks = ARRAY_SIZE(rk3588_vpu981_vpu_clk_names) }; From ff8c5622f9f7c644e995d013af320b59e4d61b93 Mon Sep 17 00:00:00 2001 From: Nicolas Dufresne Date: Thu, 8 May 2025 17:00:15 -0400 Subject: [PATCH 091/310] media: rkvdec: Restore iommu addresses on errors On errors, the rkvdec chip self resets. This can clear the addresses programmed in the iommu. This case is signaled by the RKVDEC_SOFTRESET_RDY status bit. Since the iommu framework does not have a restore functionality, and as recommended by the iommu subsystem maintainers, this patch restores the iommu programming by attaching and detaching an empty domain, which will clear and restore the default domain. Suggested-by: Detlev Casanova Tested-by: Detlev Casanova Reviewed-by: Detlev Casanova Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/staging/media/rkvdec/rkvdec.c | 43 ++++++++++++++++++++++----- drivers/staging/media/rkvdec/rkvdec.h | 1 + 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/drivers/staging/media/rkvdec/rkvdec.c b/drivers/staging/media/rkvdec/rkvdec.c index 3367902f22de..d707088ec0dc 100644 --- a/drivers/staging/media/rkvdec/rkvdec.c +++ b/drivers/staging/media/rkvdec/rkvdec.c @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -1055,24 +1056,42 @@ static void rkvdec_v4l2_cleanup(struct rkvdec_dev *rkvdec) v4l2_device_unregister(&rkvdec->v4l2_dev); } +static void rkvdec_iommu_restore(struct rkvdec_dev *rkvdec) +{ + if (rkvdec->empty_domain) { + /* + * To rewrite mapping into the attached IOMMU core, attach a new empty domain that + * will program an empty table, then detach it to restore the default domain and + * all cached mappings. + * This is safely done in this interrupt handler to make sure no memory get mapped + * through the IOMMU while the empty domain is attached. + */ + iommu_attach_device(rkvdec->empty_domain, rkvdec->dev); + iommu_detach_device(rkvdec->empty_domain, rkvdec->dev); + } +} + static irqreturn_t rkvdec_irq_handler(int irq, void *priv) { struct rkvdec_dev *rkvdec = priv; + struct rkvdec_ctx *ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); enum vb2_buffer_state state; u32 status; status = readl(rkvdec->regs + RKVDEC_REG_INTERRUPT); - state = (status & RKVDEC_RDY_STA) ? - VB2_BUF_STATE_DONE : VB2_BUF_STATE_ERROR; - writel(0, rkvdec->regs + RKVDEC_REG_INTERRUPT); - if (cancel_delayed_work(&rkvdec->watchdog_work)) { - struct rkvdec_ctx *ctx; - ctx = v4l2_m2m_get_curr_priv(rkvdec->m2m_dev); - rkvdec_job_finish(ctx, state); + if (status & RKVDEC_RDY_STA) { + state = VB2_BUF_STATE_DONE; + } else { + state = VB2_BUF_STATE_ERROR; + if (status & RKVDEC_SOFTRESET_RDY) + rkvdec_iommu_restore(rkvdec); } + if (cancel_delayed_work(&rkvdec->watchdog_work)) + rkvdec_job_finish(ctx, state); + return IRQ_HANDLED; } @@ -1140,6 +1159,13 @@ static int rkvdec_probe(struct platform_device *pdev) return ret; } + if (iommu_get_domain_for_dev(&pdev->dev)) { + rkvdec->empty_domain = iommu_paging_domain_alloc(rkvdec->dev); + + if (!rkvdec->empty_domain) + dev_warn(rkvdec->dev, "cannot alloc new empty domain\n"); + } + vb2_dma_contig_set_max_seg_size(&pdev->dev, DMA_BIT_MASK(32)); irq = platform_get_irq(pdev, 0); @@ -1179,6 +1205,9 @@ static void rkvdec_remove(struct platform_device *pdev) rkvdec_v4l2_cleanup(rkvdec); pm_runtime_disable(&pdev->dev); pm_runtime_dont_use_autosuspend(&pdev->dev); + + if (rkvdec->empty_domain) + iommu_domain_free(rkvdec->empty_domain); } #ifdef CONFIG_PM diff --git a/drivers/staging/media/rkvdec/rkvdec.h b/drivers/staging/media/rkvdec/rkvdec.h index 9a9f4fced7a1..f6e8bf38add3 100644 --- a/drivers/staging/media/rkvdec/rkvdec.h +++ b/drivers/staging/media/rkvdec/rkvdec.h @@ -110,6 +110,7 @@ struct rkvdec_dev { void __iomem *regs; struct mutex vdev_lock; /* serializes ioctls */ struct delayed_work watchdog_work; + struct iommu_domain *empty_domain; }; struct rkvdec_ctx { From fc5f8aec77704373ee804b5dba0e0e5029c0f180 Mon Sep 17 00:00:00 2001 From: Haoxiang Li Date: Thu, 27 Feb 2025 15:44:51 +0800 Subject: [PATCH 092/310] media: imx: fix a potential memory leak in imx_media_csc_scaler_device_init() Add video_device_release() in label 'err_m2m' to release the memory allocated by video_device_alloc() and prevent potential memory leaks. Remove the reduntant code in label 'err_m2m'. Fixes: a8ef0488cc59 ("media: imx: add csc/scaler mem2mem device") Cc: stable@vger.kernel.org Signed-off-by: Haoxiang Li Reviewed-by: Dan Carpenter Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/staging/media/imx/imx-media-csc-scaler.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/media/imx/imx-media-csc-scaler.c b/drivers/staging/media/imx/imx-media-csc-scaler.c index e5e08c6f79f2..19fd31cb9bb0 100644 --- a/drivers/staging/media/imx/imx-media-csc-scaler.c +++ b/drivers/staging/media/imx/imx-media-csc-scaler.c @@ -912,7 +912,7 @@ imx_media_csc_scaler_device_init(struct imx_media_dev *md) return &priv->vdev; err_m2m: - video_set_drvdata(vfd, NULL); + video_device_release(vfd); err_vfd: kfree(priv); return ERR_PTR(ret); From 70c37d403e7eba64d3eaadb8549d8f483912b6d1 Mon Sep 17 00:00:00 2001 From: Christophe JAILLET Date: Sun, 25 May 2025 10:11:44 +0200 Subject: [PATCH 093/310] media: verisilicon: Use __set_bit() with local bitmaps The 'used' and 'new' bitmaps are local to this function, so there is no need to use atomic access because concurrency can not happen. Use the non-atomic __set_bit() to save a few cycles. Signed-off-by: Christophe JAILLET Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/hantro_h264.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro_h264.c b/drivers/media/platform/verisilicon/hantro_h264.c index 4e9a0ecf5c13..2414782f1eb6 100644 --- a/drivers/media/platform/verisilicon/hantro_h264.c +++ b/drivers/media/platform/verisilicon/hantro_h264.c @@ -325,12 +325,12 @@ static void update_dpb(struct hantro_ctx *ctx) continue; *cdpb = *ndpb; - set_bit(j, used); + __set_bit(j, used); break; } if (j == ARRAY_SIZE(ctx->h264_dec.dpb)) - set_bit(i, new); + __set_bit(i, new); } /* For entries that could not be matched, use remaining free slots. */ @@ -349,7 +349,7 @@ static void update_dpb(struct hantro_ctx *ctx) cdpb = &ctx->h264_dec.dpb[j]; *cdpb = *ndpb; - set_bit(j, used); + __set_bit(j, used); } } From 401fb1955f915694808e77471cd1014d4fa2683e Mon Sep 17 00:00:00 2001 From: Frank Li Date: Wed, 21 May 2025 13:34:03 -0400 Subject: [PATCH 094/310] media: dt-bindings: nxp,imx8-jpeg: Add compatible strings for IMX95 JPEG Add compatible strings "nxp,imx95-jpgdec" and "nxp,imx95-jpgenc", which are backward compatible with "nxp,imx8qxp-jpgdec" and "nxp,imx8qxp-jpegenc". i.MX95 just need one power domain which combine wrap and all slots together. Reduce minItems of power-domains to 1 for i.MX95 and keep the same restriction for others. Reviewed-by: Krzysztof Kozlowski Signed-off-by: Frank Li Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../bindings/media/nxp,imx8-jpeg.yaml | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml index 2be30c5fdc83..4cba42ba7cf7 100644 --- a/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml +++ b/Documentation/devicetree/bindings/media/nxp,imx8-jpeg.yaml @@ -22,10 +22,14 @@ properties: - nxp,imx8qxp-jpgdec - nxp,imx8qxp-jpgenc - items: - - const: nxp,imx8qm-jpgdec + - enum: + - nxp,imx8qm-jpgdec + - nxp,imx95-jpgdec - const: nxp,imx8qxp-jpgdec - items: - - const: nxp,imx8qm-jpgenc + - enum: + - nxp,imx8qm-jpgenc + - nxp,imx95-jpgenc - const: nxp,imx8qxp-jpgenc reg: @@ -48,7 +52,7 @@ properties: description: List of phandle and PM domain specifier as documented in Documentation/devicetree/bindings/power/power_domain.txt - minItems: 2 # Wrapper and 1 slot + minItems: 1 # Wrapper and all slots maxItems: 5 # Wrapper and 4 slots required: @@ -58,6 +62,24 @@ required: - interrupts - power-domains +allOf: + - if: + properties: + compatible: + contains: + enum: + - nxp,imx95-jpgenc + - nxp,imx95-jpgdec + then: + properties: + power-domains: + maxItems: 1 + else: + properties: + power-domains: + minItems: 2 # Wrapper and 1 slot + + additionalProperties: false examples: From 803b9eabc649c778986449eb0596e5ffeb7a8aed Mon Sep 17 00:00:00 2001 From: James Cowgill Date: Wed, 4 Jun 2025 14:38:48 +0000 Subject: [PATCH 095/310] media: v4l2-ctrls: Fix H264 SEPARATE_COLOUR_PLANE check The `separate_colour_plane_flag` element is only present in the SPS if `chroma_format_idc == 3`, so the corresponding flag should be disabled whenever that is not the case and not just on profiles where `chroma_format_idc` is not present. Fixes: b32e48503df0 ("media: controls: Validate H264 stateless controls") Signed-off-by: James Cowgill Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index 90d25329661e..b45809a82f9a 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -968,12 +968,12 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx, p_h264_sps->flags &= ~V4L2_H264_SPS_FLAG_QPPRIME_Y_ZERO_TRANSFORM_BYPASS; - - if (p_h264_sps->chroma_format_idc < 3) - p_h264_sps->flags &= - ~V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE; } + if (p_h264_sps->chroma_format_idc < 3) + p_h264_sps->flags &= + ~V4L2_H264_SPS_FLAG_SEPARATE_COLOUR_PLANE; + if (p_h264_sps->flags & V4L2_H264_SPS_FLAG_FRAME_MBS_ONLY) p_h264_sps->flags &= ~V4L2_H264_SPS_FLAG_MB_ADAPTIVE_FRAME_FIELD; From c898efdd6ecc4a9f9f4d4114370e6ed477f66b8a Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Fri, 13 Jun 2025 14:21:51 +0300 Subject: [PATCH 096/310] media: rc: ir-spi: allocate buffer dynamically Replace the static transmit buffer with a dynamically allocated one, removing the limit imposed on the number of pulses to transmit. Calculate the number of pulses for each duration in the received buffer ahead of time, while also adding up the total pulses, to be able to allocate a buffer that perfectly fits the total number of pulses, then populate it. Signed-off-by: Cosmin Tanislav Signed-off-by: Sean Young Signed-off-by: Hans Verkuil --- drivers/media/rc/ir-spi.c | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 8fc8e496e6aa..50e30e2fae22 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -21,13 +21,11 @@ #define IR_SPI_DRIVER_NAME "ir-spi" #define IR_SPI_DEFAULT_FREQUENCY 38000 -#define IR_SPI_MAX_BUFSIZE 4096 struct ir_spi_data { u32 freq; bool negated; - u16 tx_buf[IR_SPI_MAX_BUFSIZE]; u16 pulse; u16 space; @@ -43,37 +41,42 @@ static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int coun unsigned int len = 0; struct ir_spi_data *idata = dev->priv; struct spi_transfer xfer; + u16 *tx_buf; /* convert the pulse/space signal to raw binary signal */ for (i = 0; i < count; i++) { - unsigned int periods; + buffer[i] = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); + len += buffer[i]; + } + + tx_buf = kmalloc_array(len, sizeof(*tx_buf), GFP_KERNEL); + if (!tx_buf) + return -ENOMEM; + + len = 0; + for (i = 0; i < count; i++) { int j; u16 val; - periods = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); - - if (len + periods >= IR_SPI_MAX_BUFSIZE) - return -EINVAL; - /* * The first value in buffer is a pulse, so that 0, 2, 4, ... * contain a pulse duration. On the contrary, 1, 3, 5, ... * contain a space duration. */ val = (i % 2) ? idata->space : idata->pulse; - for (j = 0; j < periods; j++) - idata->tx_buf[len++] = val; + for (j = 0; j < buffer[i]; j++) + tx_buf[len++] = val; } memset(&xfer, 0, sizeof(xfer)); xfer.speed_hz = idata->freq * 16; - xfer.len = len * sizeof(*idata->tx_buf); - xfer.tx_buf = idata->tx_buf; + xfer.len = len * sizeof(*tx_buf); + xfer.tx_buf = tx_buf; ret = regulator_enable(idata->regulator); if (ret) - return ret; + goto err_free_tx_buf; ret = spi_sync_transfer(idata->spi, &xfer, 1); if (ret) @@ -81,6 +84,10 @@ static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int coun regulator_disable(idata->regulator); +err_free_tx_buf: + + kfree(tx_buf); + return ret ? ret : count; } From 032a68ccb7834b28d51c11bbfe1c2db02e65ad28 Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Fri, 13 Jun 2025 14:21:52 +0300 Subject: [PATCH 097/310] media: rc: ir-spi: constrain carrier frequency Carrier frequency is currently unconstrained, allowing the SPI transfer to be allocated and filled only for it to be later rejected by the SPI controller since the frequency is too large. Add a check to constrain the carrier frequency inside ir_spi_set_tx_carrier(). Also, move the number of bits per pulse to a macro since it is not used in multiple places. Signed-off-by: Cosmin Tanislav Signed-off-by: Sean Young Signed-off-by: Hans Verkuil --- drivers/media/rc/ir-spi.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 50e30e2fae22..0b54ad74cec0 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -21,6 +21,7 @@ #define IR_SPI_DRIVER_NAME "ir-spi" #define IR_SPI_DEFAULT_FREQUENCY 38000 +#define IR_SPI_BITS_PER_PULSE 16 struct ir_spi_data { u32 freq; @@ -70,7 +71,7 @@ static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int coun memset(&xfer, 0, sizeof(xfer)); - xfer.speed_hz = idata->freq * 16; + xfer.speed_hz = idata->freq * IR_SPI_BITS_PER_PULSE; xfer.len = len * sizeof(*tx_buf); xfer.tx_buf = tx_buf; @@ -98,6 +99,9 @@ static int ir_spi_set_tx_carrier(struct rc_dev *dev, u32 carrier) if (!carrier) return -EINVAL; + if (carrier > idata->spi->max_speed_hz / IR_SPI_BITS_PER_PULSE) + return -EINVAL; + idata->freq = carrier; return 0; From c0b1da281d84d33281fc49289f0c7f8aada450ff Mon Sep 17 00:00:00 2001 From: Cosmin Tanislav Date: Fri, 13 Jun 2025 14:21:53 +0300 Subject: [PATCH 098/310] media: rc: ir-spi: avoid overflow in multiplication Switch to u64 arithmetic and use DIV_ROUND_CLOSEST_ULL() to avoid the overflow. buffer[i] is unsigned int and is limited by the lirc core to IR_MAX_DURATION, which is 500000. idata->freq is u32, which has a max value of 0xFFFFFFFF. In the case where buffer[i] is 500000, idata->freq overflows the u32 multiplication for any values >= 8590. 0xFFFFFFFF / 500000 ~= 8589 By casting buffer[i] to u64, idata->freq can be any u32 value without overflowing the multiplication. 0xFFFFFFFFFFFFFFFF / 500000 ~= 36893488147419 (> 4294967295) The result of the final operation will fit back into the unsigned int limits without any issues. 500000 * 0xFFFFFFFF / 1000000 = 0x80000000 (< 0xFFFFFFFF) Signed-off-by: Cosmin Tanislav Signed-off-by: Sean Young Signed-off-by: Hans Verkuil --- drivers/media/rc/ir-spi.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/rc/ir-spi.c b/drivers/media/rc/ir-spi.c index 0b54ad74cec0..392441e0c116 100644 --- a/drivers/media/rc/ir-spi.c +++ b/drivers/media/rc/ir-spi.c @@ -46,7 +46,8 @@ static int ir_spi_tx(struct rc_dev *dev, unsigned int *buffer, unsigned int coun /* convert the pulse/space signal to raw binary signal */ for (i = 0; i < count; i++) { - buffer[i] = DIV_ROUND_CLOSEST(buffer[i] * idata->freq, 1000000); + buffer[i] = DIV_ROUND_CLOSEST_ULL((u64)buffer[i] * idata->freq, + 1000000); len += buffer[i]; } From d629ca4f18f89c3c4db3bb589568e8010b34b514 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 31 Mar 2025 10:01:52 +0300 Subject: [PATCH 099/310] media: i2c: max96717: Drop unused field assignment The driver is not using gpio_chip::of_xlate() callback and hence the of_gpio_n_cells assignment is unused. Drop it. With that done, remove unneeded OF dependency and extend compile test coverage. Signed-off-by: Andy Shevchenko Reviewed-by: Julien Massot Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 2 +- drivers/media/i2c/max96717.c | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index e68202954a8f..ddd91ea9165c 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -1684,7 +1684,7 @@ config VIDEO_MAX96714 config VIDEO_MAX96717 tristate "Maxim MAX96717 GMSL2 Serializer support" - depends on OF && I2C && VIDEO_DEV && COMMON_CLK + depends on I2C && VIDEO_DEV && COMMON_CLK select I2C_MUX select MEDIA_CONTROLLER select GPIOLIB diff --git a/drivers/media/i2c/max96717.c b/drivers/media/i2c/max96717.c index a503a55fe8af..015e42fbe246 100644 --- a/drivers/media/i2c/max96717.c +++ b/drivers/media/i2c/max96717.c @@ -357,7 +357,6 @@ static int max96717_gpiochip_probe(struct max96717_priv *priv) gc->direction_output = max96717_gpio_direction_out; gc->set_rv = max96717_gpiochip_set; gc->get = max96717_gpiochip_get; - gc->of_gpio_n_cells = 2; /* Disable GPIO forwarding */ for (i = 0; i < gc->ngpio; i++) From 72aa1c5f1d0feceb9bbdbe59e0c0e412de42408b Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 18 Oct 2024 15:20:08 +0300 Subject: [PATCH 100/310] media: dt-bindings: mipi-ccs: Refer to video-interface-devices.yaml Refer to video-interface-devices.yaml instead of documenting the common properties here. Signed-off-by: Sakari Ailus Acked-by: Rob Herring (Arm) Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/i2c/mipi-ccs.yaml | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml b/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml index f8ace8cbccdb..bc664a016396 100644 --- a/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml +++ b/Documentation/devicetree/bindings/media/i2c/mipi-ccs.yaml @@ -23,6 +23,9 @@ description: More detailed documentation can be found in Documentation/devicetree/bindings/media/video-interfaces.txt . +allOf: + - $ref: /schemas/media/video-interface-devices.yaml# + properties: compatible: oneOf: @@ -58,16 +61,10 @@ properties: documentation. maxItems: 1 - flash-leds: - description: Flash LED phandles. See ../video-interfaces.txt for details. - - lens-focus: - description: Lens focus controller phandles. See ../video-interfaces.txt - for details. + flash-leds: true + lens-focus: true rotation: - description: Rotation of the sensor. See ../video-interfaces.txt for - details. enum: [ 0, 180 ] port: From 5a0abb8909b9dcf347fce1d201ac6686ac33fd64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 8 May 2025 10:37:45 +0200 Subject: [PATCH 101/310] media: v4l2-common: Reduce warnings about missing V4L2_CID_LINK_FREQ control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When operating a pipeline with a missing V4L2_CID_LINK_FREQ control this two line warning is printed each time the pipeline is started. Reduce this excessive logging by only warning once for the missing control. Signed-off-by: Niklas Söderlund Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index bd160a8c9efe..e1fc8fe43b74 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -505,10 +505,10 @@ s64 __v4l2_get_link_freq_ctrl(struct v4l2_ctrl_handler *handler, freq = div_u64(v4l2_ctrl_g_ctrl_int64(ctrl) * mul, div); - pr_warn("%s: Link frequency estimated using pixel rate: result might be inaccurate\n", - __func__); - pr_warn("%s: Consider implementing support for V4L2_CID_LINK_FREQ in the transmitter driver\n", - __func__); + pr_warn_once("%s: Link frequency estimated using pixel rate: result might be inaccurate\n", + __func__); + pr_warn_once("%s: Consider implementing support for V4L2_CID_LINK_FREQ in the transmitter driver\n", + __func__); } return freq > 0 ? freq : -EINVAL; From 72da392727ea9799e3a5aecd8cf7663647bdb7d2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 14 May 2025 18:37:54 +0200 Subject: [PATCH 102/310] media: ipu-bridge: Add link-frequency to the GC0310 entry Add the missing link-frequency to the ipu_supported_sensors[] array's Galaxy Core GC0310 entry. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 83e682e1a4b7..382cb7270537 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -55,7 +55,7 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Himax HM2172 */ IPU_SENSOR_CONFIG("HIMX2172", 1, 384000000), /* GalaxyCore GC0310 */ - IPU_SENSOR_CONFIG("INT0310", 0), + IPU_SENSOR_CONFIG("INT0310", 1, 55692000), /* Omnivision OV5693 */ IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Omnivision OV2740 */ From 9fbf2ee81f8c15b67af840bc039170836d4c7961 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Wed, 14 May 2025 12:51:01 +0200 Subject: [PATCH 103/310] media: i2c: imx415: Request the sensor clock without a name Request the sensor clock without specifying a name so that the driver behaves as described in the imx415 bindings. Signed-off-by: Matthias Fend Reviewed-by: Jai Luthra Reviewed-by: Michael Riesch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx415.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/imx415.c b/drivers/media/i2c/imx415.c index 9f37779bd611..278e743646ea 100644 --- a/drivers/media/i2c/imx415.c +++ b/drivers/media/i2c/imx415.c @@ -1251,7 +1251,7 @@ static int imx415_parse_hw_config(struct imx415 *sensor) return dev_err_probe(sensor->dev, PTR_ERR(sensor->reset), "failed to get reset GPIO\n"); - sensor->clk = devm_clk_get(sensor->dev, "inck"); + sensor->clk = devm_clk_get(sensor->dev, NULL); if (IS_ERR(sensor->clk)) return dev_err_probe(sensor->dev, PTR_ERR(sensor->clk), "failed to get clock\n"); From 4743c1fad068998a3be030310128c744d536599f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 24 May 2025 11:14:36 +0200 Subject: [PATCH 104/310] media: i2c: imx214: Reorder imx214_parse_fwnode call MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorder imx214_parse_fwnode call to reduce goto paths in upcoming patches. No functional change intended. Acked-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: André Apitzsch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx214.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index dd7bc45523d8..0199195dcb7d 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -1261,10 +1261,6 @@ static int imx214_probe(struct i2c_client *client) struct imx214 *imx214; int ret; - ret = imx214_parse_fwnode(dev); - if (ret) - return ret; - imx214 = devm_kzalloc(dev, sizeof(*imx214), GFP_KERNEL); if (!imx214) return -ENOMEM; @@ -1295,6 +1291,10 @@ static int imx214_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(imx214->regmap), "failed to initialize CCI\n"); + ret = imx214_parse_fwnode(dev); + if (ret) + return ret; + v4l2_i2c_subdev_init(&imx214->sd, client, &imx214_subdev_ops); imx214->sd.internal_ops = &imx214_internal_ops; From a55a5e616b67af845fc62dca997fe16e7b71c1dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 24 May 2025 11:14:37 +0200 Subject: [PATCH 105/310] media: i2c: imx214: Prepare for variable clock frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move clock frequency related parameters out of the constant register sequences, such that the hard coded external clock frequency can be replaced by a variable in the upcoming patches. Acked-by: Ricardo Ribalda Reviewed-by: Laurent Pinchart Signed-off-by: André Apitzsch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx214.c | 54 ++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 23 deletions(-) diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index 0199195dcb7d..c564c9ca8248 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -299,16 +299,6 @@ static const struct cci_reg_sequence mode_4096x2304[] = { { IMX214_REG_DIG_CROP_WIDTH, 4096 }, { IMX214_REG_DIG_CROP_HEIGHT, 2304 }, - { IMX214_REG_VTPXCK_DIV, 5 }, - { IMX214_REG_VTSYCK_DIV, 2 }, - { IMX214_REG_PREPLLCK_VT_DIV, 3 }, - { IMX214_REG_PLL_VT_MPY, 150 }, - { IMX214_REG_OPPXCK_DIV, 10 }, - { IMX214_REG_OPSYCK_DIV, 1 }, - { IMX214_REG_PLL_MULT_DRIV, IMX214_PLL_SINGLE }, - - { IMX214_REG_REQ_LINK_BIT_RATE, IMX214_LINK_BIT_RATE_MBPS(4800) }, - { CCI_REG8(0x3A03), 0x09 }, { CCI_REG8(0x3A04), 0x50 }, { CCI_REG8(0x3A05), 0x01 }, @@ -362,16 +352,6 @@ static const struct cci_reg_sequence mode_1920x1080[] = { { IMX214_REG_DIG_CROP_WIDTH, 1920 }, { IMX214_REG_DIG_CROP_HEIGHT, 1080 }, - { IMX214_REG_VTPXCK_DIV, 5 }, - { IMX214_REG_VTSYCK_DIV, 2 }, - { IMX214_REG_PREPLLCK_VT_DIV, 3 }, - { IMX214_REG_PLL_VT_MPY, 150 }, - { IMX214_REG_OPPXCK_DIV, 10 }, - { IMX214_REG_OPSYCK_DIV, 1 }, - { IMX214_REG_PLL_MULT_DRIV, IMX214_PLL_SINGLE }, - - { IMX214_REG_REQ_LINK_BIT_RATE, IMX214_LINK_BIT_RATE_MBPS(4800) }, - { CCI_REG8(0x3A03), 0x04 }, { CCI_REG8(0x3A04), 0xF8 }, { CCI_REG8(0x3A05), 0x02 }, @@ -405,9 +385,6 @@ static const struct cci_reg_sequence mode_table_common[] = { /* ATR setting */ { IMX214_REG_ATR_FAST_MOVE, 2 }, - /* external clock setting */ - { IMX214_REG_EXCK_FREQ, IMX214_EXCK_FREQ(IMX214_DEFAULT_CLK_FREQ / 1000000) }, - /* global setting */ /* basic config */ { IMX214_REG_MASK_CORR_FRAMES, IMX214_CORR_FRAMES_MASK }, @@ -777,6 +754,24 @@ static int imx214_entity_init_state(struct v4l2_subdev *subdev, return 0; } +static int imx214_configure_pll(struct imx214 *imx214) +{ + int ret = 0; + + cci_write(imx214->regmap, IMX214_REG_VTPXCK_DIV, 5, &ret); + cci_write(imx214->regmap, IMX214_REG_VTSYCK_DIV, 2, &ret); + cci_write(imx214->regmap, IMX214_REG_PREPLLCK_VT_DIV, 3, &ret); + cci_write(imx214->regmap, IMX214_REG_PLL_VT_MPY, 150, &ret); + cci_write(imx214->regmap, IMX214_REG_OPPXCK_DIV, 10, &ret); + cci_write(imx214->regmap, IMX214_REG_OPSYCK_DIV, 1, &ret); + cci_write(imx214->regmap, IMX214_REG_PLL_MULT_DRIV, + IMX214_PLL_SINGLE, &ret); + cci_write(imx214->regmap, IMX214_REG_EXCK_FREQ, + IMX214_EXCK_FREQ(IMX214_DEFAULT_CLK_FREQ / 1000000), &ret); + + return ret; +} + static int imx214_update_digital_gain(struct imx214 *imx214, u32 val) { int ret = 0; @@ -1020,6 +1015,19 @@ static int imx214_start_streaming(struct imx214 *imx214) return ret; } + ret = imx214_configure_pll(imx214); + if (ret) { + dev_err(imx214->dev, "failed to configure PLL: %d\n", ret); + return ret; + } + + ret = cci_write(imx214->regmap, IMX214_REG_REQ_LINK_BIT_RATE, + IMX214_LINK_BIT_RATE_MBPS(4800), NULL); + if (ret) { + dev_err(imx214->dev, "failed to configure link bit rate\n"); + return ret; + } + ret = cci_write(imx214->regmap, IMX214_REG_CSI_LANE_MODE, IMX214_CSI_4_LANE_MODE, NULL); if (ret) { From b9b359662952ff29cac0a7a7ffb7ae079dc836ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 24 May 2025 11:14:38 +0200 Subject: [PATCH 106/310] media: i2c: imx214: Make use of CCS PLL calculator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calculate PLL parameters based on clock frequency and link frequency. Acked-by: Ricardo Ribalda Signed-off-by: André Apitzsch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 1 + drivers/media/i2c/imx214.c | 213 ++++++++++++++++++++++++++++++------- 2 files changed, 175 insertions(+), 39 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index ddd91ea9165c..43e39bf22b6b 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -141,6 +141,7 @@ config VIDEO_IMX214 depends on GPIOLIB select REGMAP_I2C select V4L2_CCI_I2C + select VIDEO_CCS_PLL help This is a Video4Linux2 sensor driver for the Sony IMX214 camera. diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index c564c9ca8248..fd03650a5b2b 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -20,6 +20,8 @@ #include #include +#include "ccs-pll.h" + /* Chip ID */ #define IMX214_REG_CHIP_ID CCI_REG16(0x0016) #define IMX214_CHIP_ID 0x0214 @@ -34,7 +36,6 @@ #define IMX214_DEFAULT_LINK_FREQ 600000000 /* Keep wrong link frequency for backward compatibility */ #define IMX214_DEFAULT_LINK_FREQ_LEGACY 480000000 -#define IMX214_DEFAULT_PIXEL_RATE ((IMX214_DEFAULT_LINK_FREQ * 8LL) / 10) #define IMX214_FPS 30 /* V-TIMING internal */ @@ -84,6 +85,7 @@ #define IMX214_CSI_DATA_FORMAT_RAW10 0x0A0A #define IMX214_CSI_DATA_FORMAT_COMP6 0x0A06 #define IMX214_CSI_DATA_FORMAT_COMP8 0x0A08 +#define IMX214_BITS_PER_PIXEL_MASK 0xFF #define IMX214_REG_CSI_LANE_MODE CCI_REG8(0x0114) #define IMX214_CSI_2_LANE_MODE 1 @@ -249,6 +251,10 @@ struct imx214 { struct clk *xclk; struct regmap *regmap; + struct ccs_pll pll; + + struct v4l2_fwnode_endpoint bus_cfg; + struct v4l2_subdev sd; struct media_pad pad; @@ -758,16 +764,22 @@ static int imx214_configure_pll(struct imx214 *imx214) { int ret = 0; - cci_write(imx214->regmap, IMX214_REG_VTPXCK_DIV, 5, &ret); - cci_write(imx214->regmap, IMX214_REG_VTSYCK_DIV, 2, &ret); - cci_write(imx214->regmap, IMX214_REG_PREPLLCK_VT_DIV, 3, &ret); - cci_write(imx214->regmap, IMX214_REG_PLL_VT_MPY, 150, &ret); - cci_write(imx214->regmap, IMX214_REG_OPPXCK_DIV, 10, &ret); - cci_write(imx214->regmap, IMX214_REG_OPSYCK_DIV, 1, &ret); + cci_write(imx214->regmap, IMX214_REG_VTPXCK_DIV, + imx214->pll.vt_bk.pix_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_VTSYCK_DIV, + imx214->pll.vt_bk.sys_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_PREPLLCK_VT_DIV, + imx214->pll.vt_fr.pre_pll_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_PLL_VT_MPY, + imx214->pll.vt_fr.pll_multiplier, &ret); + cci_write(imx214->regmap, IMX214_REG_OPPXCK_DIV, + imx214->pll.op_bk.pix_clk_div, &ret); + cci_write(imx214->regmap, IMX214_REG_OPSYCK_DIV, + imx214->pll.op_bk.sys_clk_div, &ret); cci_write(imx214->regmap, IMX214_REG_PLL_MULT_DRIV, IMX214_PLL_SINGLE, &ret); cci_write(imx214->regmap, IMX214_REG_EXCK_FREQ, - IMX214_EXCK_FREQ(IMX214_DEFAULT_CLK_FREQ / 1000000), &ret); + IMX214_EXCK_FREQ(imx214->pll.ext_clk_freq_hz / 1000000), &ret); return ret; } @@ -872,9 +884,6 @@ static const struct v4l2_ctrl_ops imx214_ctrl_ops = { static int imx214_ctrls_init(struct imx214 *imx214) { - static const s64 link_freq[] = { - IMX214_DEFAULT_LINK_FREQ - }; static const struct v4l2_area unit_size = { .width = 1120, .height = 1120, @@ -895,15 +904,14 @@ static int imx214_ctrls_init(struct imx214 *imx214) if (ret) return ret; - imx214->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, NULL, - V4L2_CID_PIXEL_RATE, 0, - IMX214_DEFAULT_PIXEL_RATE, 1, - IMX214_DEFAULT_PIXEL_RATE); + imx214->pixel_rate = + v4l2_ctrl_new_std(ctrl_hdlr, NULL, V4L2_CID_PIXEL_RATE, 1, + INT_MAX, 1, 1); imx214->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, NULL, V4L2_CID_LINK_FREQ, - ARRAY_SIZE(link_freq) - 1, - 0, link_freq); + imx214->bus_cfg.nr_of_link_frequencies - 1, + 0, imx214->bus_cfg.link_frequencies); if (imx214->link_freq) imx214->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; @@ -1006,6 +1014,7 @@ static int imx214_start_streaming(struct imx214 *imx214) const struct v4l2_mbus_framefmt *fmt; struct v4l2_subdev_state *state; const struct imx214_mode *mode; + int bit_rate_mbps; int ret; ret = cci_multi_reg_write(imx214->regmap, mode_table_common, @@ -1021,8 +1030,10 @@ static int imx214_start_streaming(struct imx214 *imx214) return ret; } + bit_rate_mbps = (imx214->pll.pixel_rate_csi / 1000000) + * imx214->pll.bits_per_pixel; ret = cci_write(imx214->regmap, IMX214_REG_REQ_LINK_BIT_RATE, - IMX214_LINK_BIT_RATE_MBPS(4800), NULL); + IMX214_LINK_BIT_RATE_MBPS(bit_rate_mbps), NULL); if (ret) { dev_err(imx214->dev, "failed to configure link bit rate\n"); return ret; @@ -1105,6 +1116,109 @@ static int imx214_s_stream(struct v4l2_subdev *subdev, int enable) return ret; } +static int imx214_pll_calculate(struct imx214 *imx214, struct ccs_pll *pll, + unsigned int link_freq) +{ + struct ccs_pll_limits limits = { + .min_ext_clk_freq_hz = 6000000, + .max_ext_clk_freq_hz = 27000000, + + .vt_fr = { + .min_pre_pll_clk_div = 1, + .max_pre_pll_clk_div = 15, + /* Value is educated guess as we don't have a spec */ + .min_pll_ip_clk_freq_hz = 6000000, + /* Value is educated guess as we don't have a spec */ + .max_pll_ip_clk_freq_hz = 12000000, + .min_pll_multiplier = 12, + .max_pll_multiplier = 1200, + .min_pll_op_clk_freq_hz = 338000000, + .max_pll_op_clk_freq_hz = 1200000000, + }, + .vt_bk = { + .min_sys_clk_div = 2, + .max_sys_clk_div = 4, + .min_pix_clk_div = 5, + .max_pix_clk_div = 10, + .min_pix_clk_freq_hz = 30000000, + .max_pix_clk_freq_hz = 120000000, + }, + .op_bk = { + .min_sys_clk_div = 1, + .max_sys_clk_div = 2, + .min_pix_clk_div = 6, + .max_pix_clk_div = 10, + .min_pix_clk_freq_hz = 30000000, + .max_pix_clk_freq_hz = 120000000, + }, + + .min_line_length_pck_bin = IMX214_PPL_DEFAULT, + .min_line_length_pck = IMX214_PPL_DEFAULT, + }; + unsigned int num_lanes = imx214->bus_cfg.bus.mipi_csi2.num_data_lanes; + + /* + * There are no documented constraints on the sys clock frequency, for + * either branch. Recover them based on the PLL output clock frequency + * and sys_clk_div limits on one hand, and the pix clock frequency and + * the pix_clk_div limits on the other hand. + */ + limits.vt_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.vt_bk.max_sys_clk_div, + limits.vt_bk.min_pix_clk_freq_hz * limits.vt_bk.min_pix_clk_div); + limits.vt_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.vt_bk.min_sys_clk_div, + limits.vt_bk.max_pix_clk_freq_hz * limits.vt_bk.max_pix_clk_div); + + limits.op_bk.min_sys_clk_freq_hz = + max(limits.vt_fr.min_pll_op_clk_freq_hz / limits.op_bk.max_sys_clk_div, + limits.op_bk.min_pix_clk_freq_hz * limits.op_bk.min_pix_clk_div); + limits.op_bk.max_sys_clk_freq_hz = + min(limits.vt_fr.max_pll_op_clk_freq_hz / limits.op_bk.min_sys_clk_div, + limits.op_bk.max_pix_clk_freq_hz * limits.op_bk.max_pix_clk_div); + + memset(pll, 0, sizeof(*pll)); + + pll->bus_type = CCS_PLL_BUS_TYPE_CSI2_DPHY; + pll->op_lanes = num_lanes; + pll->vt_lanes = num_lanes; + pll->csi2.lanes = num_lanes; + + pll->binning_horizontal = 1; + pll->binning_vertical = 1; + pll->scale_m = 1; + pll->scale_n = 1; + pll->bits_per_pixel = + IMX214_CSI_DATA_FORMAT_RAW10 & IMX214_BITS_PER_PIXEL_MASK; + pll->flags = CCS_PLL_FLAG_LANE_SPEED_MODEL; + pll->link_freq = link_freq; + pll->ext_clk_freq_hz = clk_get_rate(imx214->xclk); + + return ccs_pll_calculate(imx214->dev, &limits, pll); +} + +static int imx214_pll_update(struct imx214 *imx214) +{ + u64 link_freq; + int ret; + + link_freq = imx214->bus_cfg.link_frequencies[imx214->link_freq->val]; + ret = imx214_pll_calculate(imx214, &imx214->pll, link_freq); + if (ret) { + dev_err(imx214->dev, "PLL calculations failed: %d\n", ret); + return ret; + } + + ret = v4l2_ctrl_s_ctrl_int64(imx214->pixel_rate, + imx214->pll.pixel_rate_pixel_array); + if (ret) { + dev_err(imx214->dev, "failed to set pixel rate\n"); + return ret; + } + + return 0; +} + static int imx214_get_frame_interval(struct v4l2_subdev *subdev, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_frame_interval *fival) @@ -1211,12 +1325,10 @@ static int imx214_identify_module(struct imx214 *imx214) return 0; } -static int imx214_parse_fwnode(struct device *dev) +static int imx214_parse_fwnode(struct device *dev, struct imx214 *imx214) { + struct v4l2_fwnode_endpoint *bus_cfg = &imx214->bus_cfg; struct fwnode_handle *endpoint; - struct v4l2_fwnode_endpoint bus_cfg = { - .bus_type = V4L2_MBUS_CSI2_DPHY, - }; unsigned int i; int ret; @@ -1224,42 +1336,52 @@ static int imx214_parse_fwnode(struct device *dev) if (!endpoint) return dev_err_probe(dev, -EINVAL, "endpoint node not found\n"); - ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); + bus_cfg->bus_type = V4L2_MBUS_CSI2_DPHY; + ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, bus_cfg); + fwnode_handle_put(endpoint); if (ret) { dev_err_probe(dev, ret, "parsing endpoint node failed\n"); - goto done; + goto error; } /* Check the number of MIPI CSI2 data lanes */ - if (bus_cfg.bus.mipi_csi2.num_data_lanes != 4) { + if (bus_cfg->bus.mipi_csi2.num_data_lanes != 4) { ret = dev_err_probe(dev, -EINVAL, "only 4 data lanes are currently supported\n"); - goto done; + goto error; } - if (bus_cfg.nr_of_link_frequencies != 1) + if (bus_cfg->nr_of_link_frequencies != 1) dev_warn(dev, "Only one link-frequency supported, please review your DT. Continuing anyway\n"); - for (i = 0; i < bus_cfg.nr_of_link_frequencies; i++) { - if (bus_cfg.link_frequencies[i] == IMX214_DEFAULT_LINK_FREQ) + for (i = 0; i < bus_cfg->nr_of_link_frequencies; i++) { + u64 freq = bus_cfg->link_frequencies[i]; + struct ccs_pll pll; + + if (!imx214_pll_calculate(imx214, &pll, freq)) break; - if (bus_cfg.link_frequencies[i] == - IMX214_DEFAULT_LINK_FREQ_LEGACY) { + if (freq == IMX214_DEFAULT_LINK_FREQ_LEGACY) { dev_warn(dev, "link-frequencies %d not supported, please review your DT. Continuing anyway\n", IMX214_DEFAULT_LINK_FREQ); + freq = IMX214_DEFAULT_LINK_FREQ; + if (imx214_pll_calculate(imx214, &pll, freq)) + continue; + bus_cfg->link_frequencies[i] = freq; break; } } - if (i == bus_cfg.nr_of_link_frequencies) + if (i == bus_cfg->nr_of_link_frequencies) ret = dev_err_probe(dev, -EINVAL, - "link-frequencies %d not supported, please review your DT\n", - IMX214_DEFAULT_LINK_FREQ); + "link-frequencies %lld not supported, please review your DT\n", + bus_cfg->nr_of_link_frequencies ? + bus_cfg->link_frequencies[0] : 0); -done: - v4l2_fwnode_endpoint_free(&bus_cfg); - fwnode_handle_put(endpoint); + return 0; + +error: + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); return ret; } @@ -1299,7 +1421,7 @@ static int imx214_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(imx214->regmap), "failed to initialize CCI\n"); - ret = imx214_parse_fwnode(dev); + ret = imx214_parse_fwnode(dev, imx214); if (ret) return ret; @@ -1310,7 +1432,9 @@ static int imx214_probe(struct i2c_client *client) * Enable power initially, to avoid warnings * from clk_disable on power_off */ - imx214_power_on(imx214->dev); + ret = imx214_power_on(imx214->dev); + if (ret < 0) + goto error_fwnode; ret = imx214_identify_module(imx214); if (ret) @@ -1341,6 +1465,12 @@ static int imx214_probe(struct i2c_client *client) pm_runtime_set_active(imx214->dev); pm_runtime_enable(imx214->dev); + ret = imx214_pll_update(imx214); + if (ret < 0) { + dev_err_probe(dev, ret, "failed to update PLL\n"); + goto error_subdev_cleanup; + } + ret = v4l2_async_register_subdev_sensor(&imx214->sd); if (ret < 0) { dev_err_probe(dev, ret, @@ -1366,6 +1496,9 @@ static int imx214_probe(struct i2c_client *client) error_power_off: imx214_power_off(imx214->dev); +error_fwnode: + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); + return ret; } @@ -1378,6 +1511,8 @@ static void imx214_remove(struct i2c_client *client) v4l2_subdev_cleanup(sd); media_entity_cleanup(&imx214->sd.entity); v4l2_ctrl_handler_free(&imx214->ctrls); + v4l2_fwnode_endpoint_free(&imx214->bus_cfg); + pm_runtime_disable(&client->dev); if (!pm_runtime_status_suspended(&client->dev)) { imx214_power_off(imx214->dev); From 5ecc7b0b48f2dbe0e4d601084e956cddc57acc5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 24 May 2025 11:14:39 +0200 Subject: [PATCH 107/310] media: dt-bindings: sony,imx214: Deprecate property clock-frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deprecate the clock-frequency property in favor of assigned-clock-rates. While at it, re-order properties according to coding style and fix the link-frequency in the example. See commit acc294519f17 ("media: i2c: imx214: Fix link frequency validation"). Reviewed-by: Laurent Pinchart Acked-by: Conor Dooley Signed-off-by: André Apitzsch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../bindings/media/i2c/sony,imx214.yaml | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx214.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx214.yaml index 0162eec8ca99..aea99ebf8e9e 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx214.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx214.yaml @@ -33,20 +33,21 @@ properties: clock-frequency: description: Frequency of the xclk clock in Hz. + deprecated: true enable-gpios: description: GPIO descriptor for the enable pin. maxItems: 1 - vdddo-supply: - description: Chip digital IO regulator (1.8V). - vdda-supply: description: Chip analog regulator (2.7V). vddd-supply: description: Chip digital core regulator (1.12V). + vdddo-supply: + description: Chip digital IO regulator (1.8V). + flash-leds: true lens-focus: true @@ -84,11 +85,10 @@ required: - compatible - reg - clocks - - clock-frequency - enable-gpios - - vdddo-supply - vdda-supply - vddd-supply + - vdddo-supply - port unevaluatedProperties: false @@ -104,22 +104,25 @@ examples: camera-sensor@1a { compatible = "sony,imx214"; reg = <0x1a>; - vdddo-supply = <&pm8994_lvs1>; - vddd-supply = <&camera_vddd_1v12>; - vdda-supply = <&pm8994_l17>; - lens-focus = <&ad5820>; - enable-gpios = <&msmgpio 25 GPIO_ACTIVE_HIGH>; + clocks = <&camera_clk>; - clock-frequency = <24000000>; + assigned-clocks = <&camera_clk>; + assigned-clock-rates = <24000000>; + + enable-gpios = <&msmgpio 25 GPIO_ACTIVE_HIGH>; + + vdda-supply = <&pm8994_l17>; + vddd-supply = <&camera_vddd_1v12>; + vdddo-supply = <&pm8994_lvs1>; + + lens-focus = <&ad5820>; port { imx214_ep: endpoint { data-lanes = <1 2 3 4>; - link-frequencies = /bits/ 64 <480000000>; + link-frequencies = /bits/ 64 <600000000>; remote-endpoint = <&csiphy0_ep>; }; }; }; }; - -... From 2db8bd2efd97b957d7fca8abe137255068e02fb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 24 May 2025 11:14:40 +0200 Subject: [PATCH 108/310] media: i2c: imx214: Remove hard-coded external clock frequency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead rely on the rate set on the clock (using assigned-clock-rates etc.) Reviewed-by: Laurent Pinchart Signed-off-by: André Apitzsch Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx214.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/drivers/media/i2c/imx214.c b/drivers/media/i2c/imx214.c index fd03650a5b2b..a0cef9e61b41 100644 --- a/drivers/media/i2c/imx214.c +++ b/drivers/media/i2c/imx214.c @@ -32,7 +32,6 @@ #define IMX214_REG_FAST_STANDBY_CTRL CCI_REG8(0x0106) -#define IMX214_DEFAULT_CLK_FREQ 24000000 #define IMX214_DEFAULT_LINK_FREQ 600000000 /* Keep wrong link frequency for backward compatibility */ #define IMX214_DEFAULT_LINK_FREQ_LEGACY 480000000 @@ -1402,11 +1401,6 @@ static int imx214_probe(struct i2c_client *client) return dev_err_probe(dev, PTR_ERR(imx214->xclk), "failed to get xclk\n"); - ret = clk_set_rate(imx214->xclk, IMX214_DEFAULT_CLK_FREQ); - if (ret) - return dev_err_probe(dev, ret, - "failed to set xclk frequency\n"); - ret = imx214_get_regulators(dev, imx214); if (ret < 0) return dev_err_probe(dev, ret, "failed to get regulators\n"); From bbcc96df756a0db077b7d1f4e83922241e24486b Mon Sep 17 00:00:00 2001 From: Martin Hecht Date: Thu, 15 May 2025 16:51:50 +0200 Subject: [PATCH 109/310] MAINTAINERS: Update my email address to gmail.com Replace my corporate email address by @gmail.com. Signed-off-by: Martin Hecht Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index da34c72270e7..276c5a0b2dc5 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -821,7 +821,7 @@ F: drivers/media/platform/allegro-dvt/ ALLIED VISION ALVIUM CAMERA DRIVER M: Tommaso Merciai -M: Martin Hecht +M: Martin Hecht L: linux-media@vger.kernel.org S: Maintained F: Documentation/devicetree/bindings/media/i2c/alliedvision,alvium-csi2.yaml From 3bd71a34baf620b2a85b5b63255a07a4ab194da0 Mon Sep 17 00:00:00 2001 From: Mathis Foerst Date: Thu, 22 May 2025 16:35:05 +0200 Subject: [PATCH 110/310] media: dt-bindings: mt9m114: Add slew-rate DT-binding The MT9M114 supports the different slew rates (0 to 7) on the output pads. At the moment, this is hardcoded to 7 (the fastest rate). The user might want to change this values due to EMC requirements. Add the 'slew-rate' property to the MT9M114 DT-bindings for selecting the desired slew rate. Signed-off-by: Mathis Foerst Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/i2c/onnn,mt9m114.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml b/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml index f6b87892068a..a89f740214f7 100644 --- a/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml +++ b/Documentation/devicetree/bindings/media/i2c/onnn,mt9m114.yaml @@ -70,6 +70,15 @@ properties: - bus-type - link-frequencies + slew-rate: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Slew rate ot the output pads DOUT[7:0], LINE_VALID, FRAME_VALID and + PIXCLK. Higher values imply steeper voltage-flanks on the pads. + minimum: 0 + maximum: 7 + default: 7 + required: - compatible - reg From 208119b4a116d2fa708ee4bbe4e12dfd26f03309 Mon Sep 17 00:00:00 2001 From: Mathis Foerst Date: Thu, 22 May 2025 16:35:06 +0200 Subject: [PATCH 111/310] media: mt9m114: Bypass PLL if required The MT9M114 sensor has an internal PLL that generates the required SYSCLK from EXTCLK. It also has the option to bypass the PLL and use EXTCLK directly as SYSCLK. The current driver implementation uses a hardcoded PLL configuration that requires a specific EXTCLK frequency. Depending on the available clocks, it can be desirable to use a different PLL configuration or to bypass it. The link-frequency of the output bus (Parallel or MIPI-CSI) is configured in the device tree. Check if EXTCLK can be used as SYSCLK to achieve this link-frequency. If yes, bypass the PLL. Otherwise, (as before) check if EXTCLK and the default PLL configuration provide the required SYSCLK to achieve the link-frequency. If yes, use the PLL. If no, throw an error. Signed-off-by: Mathis Foerst Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 70 +++++++++++++++++++++++++++---------- 1 file changed, 51 insertions(+), 19 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 5f0b0ad8f885..c3ec2eb0b134 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -261,6 +261,7 @@ #define MT9M114_CAM_PGA_PGA_CONTROL CCI_REG16(0xc95e) #define MT9M114_CAM_SYSCTL_PLL_ENABLE CCI_REG8(0xc97e) #define MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE BIT(0) +#define MT9M114_CAM_SYSCTL_PLL_DISABLE_VALUE 0x00 #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N CCI_REG16(0xc980) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(m, n) (((n) << 8) | (m)) #define MT9M114_CAM_SYSCTL_PLL_DIVIDER_P CCI_REG16(0xc982) @@ -377,6 +378,7 @@ struct mt9m114 { struct gpio_desc *reset; struct regulator_bulk_data supplies[3]; struct v4l2_fwnode_endpoint bus_cfg; + bool bypass_pll; struct { unsigned int m; @@ -743,14 +745,21 @@ static int mt9m114_initialize(struct mt9m114 *sensor) } /* Configure the PLL. */ - cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_ENABLE, - MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE, &ret); - cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N, - MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(sensor->pll.m, - sensor->pll.n), - &ret); - cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P, - MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p), &ret); + if (sensor->bypass_pll) { + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_ENABLE, + MT9M114_CAM_SYSCTL_PLL_DISABLE_VALUE, &ret); + } else { + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_ENABLE, + MT9M114_CAM_SYSCTL_PLL_ENABLE_VALUE, &ret); + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_M_N, + MT9M114_CAM_SYSCTL_PLL_DIVIDER_VALUE(sensor->pll.m, + sensor->pll.n), + &ret); + cci_write(sensor->regmap, MT9M114_CAM_SYSCTL_PLL_DIVIDER_P, + MT9M114_CAM_SYSCTL_PLL_DIVIDER_P_VALUE(sensor->pll.p), + &ret); + } + cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_PIXCLK, sensor->pixrate, &ret); @@ -2235,9 +2244,22 @@ static const struct dev_pm_ops mt9m114_pm_ops = { * Probe & Remove */ +static int mt9m114_verify_link_frequency(struct mt9m114 *sensor, + unsigned int pixrate) +{ + unsigned int link_freq = sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY + ? pixrate * 8 : pixrate * 2; + + if (sensor->bus_cfg.nr_of_link_frequencies != 1 || + sensor->bus_cfg.link_frequencies[0] != link_freq) + return -EINVAL; + + return 0; +} + static int mt9m114_clk_init(struct mt9m114 *sensor) { - unsigned int link_freq; + unsigned int pixrate; /* Hardcode the PLL multiplier and dividers to default settings. */ sensor->pll.m = 32; @@ -2249,19 +2271,29 @@ static int mt9m114_clk_init(struct mt9m114 *sensor) * for 16-bit per pixel, transmitted in DDR over a single lane. For * parallel mode, the sensor ouputs one pixel in two PIXCLK cycles. */ - sensor->pixrate = clk_get_rate(sensor->clk) * sensor->pll.m - / ((sensor->pll.n + 1) * (sensor->pll.p + 1)); - link_freq = sensor->bus_cfg.bus_type == V4L2_MBUS_CSI2_DPHY - ? sensor->pixrate * 8 : sensor->pixrate * 2; - - if (sensor->bus_cfg.nr_of_link_frequencies != 1 || - sensor->bus_cfg.link_frequencies[0] != link_freq) { - dev_err(&sensor->client->dev, "Unsupported DT link-frequencies\n"); - return -EINVAL; + /* + * Check if EXTCLK fits the configured link frequency. Bypass the PLL + * in this case. + */ + pixrate = clk_get_rate(sensor->clk) / 2; + if (mt9m114_verify_link_frequency(sensor, pixrate) == 0) { + sensor->pixrate = pixrate; + sensor->bypass_pll = true; + return 0; } - return 0; + /* Check if the PLL configuration fits the configured link frequency. */ + pixrate = clk_get_rate(sensor->clk) * sensor->pll.m + / ((sensor->pll.n + 1) * (sensor->pll.p + 1)); + if (mt9m114_verify_link_frequency(sensor, pixrate) == 0) { + sensor->pixrate = pixrate; + sensor->bypass_pll = false; + return 0; + } + + dev_err(&sensor->client->dev, "Unsupported DT link-frequencies\n"); + return -EINVAL; } static int mt9m114_identify(struct mt9m114 *sensor) From 3aa7c3da1f7d23ecc653c29de4cfb5da45efc49d Mon Sep 17 00:00:00 2001 From: Mathis Foerst Date: Thu, 22 May 2025 16:35:07 +0200 Subject: [PATCH 112/310] media: mt9m114: Factor out mt9m114_configure_pa The function mt9m114_configure writes the configuration registers of both, the pixel array (pa) and the image flow processor (ifp). This is undesirable if only the config of the pa should be changed without affecting the ifp. Factor out the function mt9m114_configure_pa() that just writes the pa-configuration. Signed-off-by: Mathis Foerst Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 95 +++++++++++++++++++++---------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index c3ec2eb0b134..6c80c6926aef 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -790,41 +790,25 @@ static int mt9m114_initialize(struct mt9m114 *sensor) return 0; } -static int mt9m114_configure(struct mt9m114 *sensor, - struct v4l2_subdev_state *pa_state, - struct v4l2_subdev_state *ifp_state) +static int mt9m114_configure_pa(struct mt9m114 *sensor, + struct v4l2_subdev_state *state) { - const struct v4l2_mbus_framefmt *pa_format; - const struct v4l2_rect *pa_crop; - const struct mt9m114_format_info *ifp_info; - const struct v4l2_mbus_framefmt *ifp_format; - const struct v4l2_rect *ifp_crop; - const struct v4l2_rect *ifp_compose; + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *crop; unsigned int hratio, vratio; - u64 output_format; u64 read_mode; - int ret = 0; + int ret; - pa_format = v4l2_subdev_state_get_format(pa_state, 0); - pa_crop = v4l2_subdev_state_get_crop(pa_state, 0); - - ifp_format = v4l2_subdev_state_get_format(ifp_state, 1); - ifp_info = mt9m114_format_info(sensor, 1, ifp_format->code); - ifp_crop = v4l2_subdev_state_get_crop(ifp_state, 0); - ifp_compose = v4l2_subdev_state_get_compose(ifp_state, 0); + format = v4l2_subdev_state_get_format(state, 0); + crop = v4l2_subdev_state_get_crop(state, 0); ret = cci_read(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, &read_mode, NULL); if (ret < 0) return ret; - ret = cci_read(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, - &output_format, NULL); - if (ret < 0) - return ret; - - hratio = pa_crop->width / pa_format->width; - vratio = pa_crop->height / pa_format->height; + hratio = crop->width / format->width; + vratio = crop->height / format->height; /* * Pixel array crop and binning. The CAM_SENSOR_CFG_CPIPE_LAST_ROW @@ -833,15 +817,15 @@ static int mt9m114_configure(struct mt9m114 *sensor, * example sensor modes. */ cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_START, - pa_crop->left, &ret); + crop->left, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_START, - pa_crop->top, &ret); + crop->top, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_X_ADDR_END, - pa_crop->width + pa_crop->left - 1, &ret); + crop->width + crop->left - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_Y_ADDR_END, - pa_crop->height + pa_crop->top - 1, &ret); + crop->height + crop->top - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CFG_CPIPE_LAST_ROW, - (pa_crop->height - 4) / vratio - 1, &ret); + (crop->height - 4) / vratio - 1, &ret); read_mode &= ~(MT9M114_CAM_SENSOR_CONTROL_X_READ_OUT_MASK | MT9M114_CAM_SENSOR_CONTROL_Y_READ_OUT_MASK); @@ -854,6 +838,29 @@ static int mt9m114_configure(struct mt9m114 *sensor, cci_write(sensor->regmap, MT9M114_CAM_SENSOR_CONTROL_READ_MODE, read_mode, &ret); + return ret; +} + +static int mt9m114_configure_ifp(struct mt9m114 *sensor, + struct v4l2_subdev_state *state) +{ + const struct mt9m114_format_info *info; + const struct v4l2_mbus_framefmt *format; + const struct v4l2_rect *crop; + const struct v4l2_rect *compose; + u64 output_format; + int ret = 0; + + format = v4l2_subdev_state_get_format(state, 1); + info = mt9m114_format_info(sensor, 1, format->code); + crop = v4l2_subdev_state_get_crop(state, 0); + compose = v4l2_subdev_state_get_compose(state, 0); + + ret = cci_read(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, + &output_format, NULL); + if (ret < 0) + return ret; + /* * Color pipeline (IFP) cropping and scaling. Subtract 4 from the left * and top coordinates to compensate for the lines and columns removed @@ -861,18 +868,18 @@ static int mt9m114_configure(struct mt9m114 *sensor, * not in the hardware. */ cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_XOFFSET, - ifp_crop->left - 4, &ret); + crop->left - 4, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_YOFFSET, - ifp_crop->top - 4, &ret); + crop->top - 4, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_WIDTH, - ifp_crop->width, &ret); + crop->width, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_WINDOW_HEIGHT, - ifp_crop->height, &ret); + crop->height, &ret); cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_WIDTH, - ifp_compose->width, &ret); + compose->width, &ret); cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_HEIGHT, - ifp_compose->height, &ret); + compose->height, &ret); /* AWB and AE windows, use the full frame. */ cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XSTART, @@ -880,18 +887,18 @@ static int mt9m114_configure(struct mt9m114 *sensor, cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_XEND, - ifp_compose->width - 1, &ret); + compose->width - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AWB_CLIP_WINDOW_YEND, - ifp_compose->height - 1, &ret); + compose->height - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YSTART, 0, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_XEND, - ifp_compose->width / 5 - 1, &ret); + compose->width / 5 - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_STAT_AE_INITIAL_WINDOW_YEND, - ifp_compose->height / 5 - 1, &ret); + compose->height / 5 - 1, &ret); cci_write(sensor->regmap, MT9M114_CAM_CROP_CROPMODE, MT9M114_CAM_CROP_MODE_AWB_AUTO_CROP_EN | @@ -903,7 +910,7 @@ static int mt9m114_configure(struct mt9m114 *sensor, MT9M114_CAM_OUTPUT_FORMAT_FORMAT_MASK | MT9M114_CAM_OUTPUT_FORMAT_SWAP_BYTES | MT9M114_CAM_OUTPUT_FORMAT_SWAP_RED_BLUE); - output_format |= ifp_info->output_format; + output_format |= info->output_format; cci_write(sensor->regmap, MT9M114_CAM_OUTPUT_FORMAT, output_format, &ret); @@ -934,7 +941,11 @@ static int mt9m114_start_streaming(struct mt9m114 *sensor, if (ret) return ret; - ret = mt9m114_configure(sensor, pa_state, ifp_state); + ret = mt9m114_configure_ifp(sensor, ifp_state); + if (ret) + goto error; + + ret = mt9m114_configure_pa(sensor, pa_state); if (ret) goto error; From 298d1471cf83d5a2a05970e41822a2403f451086 Mon Sep 17 00:00:00 2001 From: Mathis Foerst Date: Thu, 22 May 2025 16:35:10 +0200 Subject: [PATCH 113/310] media: mt9m114: Fix deadlock in get_frame_interval/set_frame_interval Getting / Setting the frame interval using the V4L2 subdev pad ops get_frame_interval/set_frame_interval causes a deadlock, as the subdev state is locked in the [1] but also in the driver itself. In [2] it's described that the caller is responsible to acquire and release the lock in this case. Therefore, acquiring the lock in the driver is wrong. Remove the lock acquisitions/releases from mt9m114_ifp_get_frame_interval() and mt9m114_ifp_set_frame_interval(). [1] drivers/media/v4l2-core/v4l2-subdev.c - line 1129 [2] Documentation/driver-api/media/v4l2-subdev.rst Fixes: 24d756e914fc ("media: i2c: Add driver for onsemi MT9M114 camera sensor") Cc: stable@vger.kernel.org Signed-off-by: Mathis Foerst Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/mt9m114.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/media/i2c/mt9m114.c b/drivers/media/i2c/mt9m114.c index 6c80c6926aef..3f540ca40f3c 100644 --- a/drivers/media/i2c/mt9m114.c +++ b/drivers/media/i2c/mt9m114.c @@ -1619,13 +1619,9 @@ static int mt9m114_ifp_get_frame_interval(struct v4l2_subdev *sd, if (interval->which != V4L2_SUBDEV_FORMAT_ACTIVE) return -EINVAL; - mutex_lock(sensor->ifp.hdl.lock); - ival->numerator = 1; ival->denominator = sensor->ifp.frame_rate; - mutex_unlock(sensor->ifp.hdl.lock); - return 0; } @@ -1644,8 +1640,6 @@ static int mt9m114_ifp_set_frame_interval(struct v4l2_subdev *sd, if (interval->which != V4L2_SUBDEV_FORMAT_ACTIVE) return -EINVAL; - mutex_lock(sensor->ifp.hdl.lock); - if (ival->numerator != 0 && ival->denominator != 0) sensor->ifp.frame_rate = min_t(unsigned int, ival->denominator / ival->numerator, @@ -1659,8 +1653,6 @@ static int mt9m114_ifp_set_frame_interval(struct v4l2_subdev *sd, if (sensor->streaming) ret = mt9m114_set_frame_rate(sensor); - mutex_unlock(sensor->ifp.hdl.lock); - return ret; } From 3c607baf68639d6bfe1a336523c4c9597f4b512a Mon Sep 17 00:00:00 2001 From: Dongcheng Yan Date: Wed, 21 May 2025 15:15:19 +0800 Subject: [PATCH 114/310] media: i2c: set lt6911uxe's reset_gpio to GPIOD_OUT_LOW reset_gpio needs to be an output and set to GPIOD_OUT_LOW, to ensure lt6911uxe is in reset state during probe. This issue was found on the onboard lt6911uxe, where the reset_pin was not reset, causing the lt6911uxe to fail to probe. Fixes: e49563c3be09d4 ("media: i2c: add lt6911uxe hdmi bridge driver") Cc: stable@vger.kernel.org Signed-off-by: Dongcheng Yan Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/lt6911uxe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/lt6911uxe.c b/drivers/media/i2c/lt6911uxe.c index 24857d683fcf..bdefdd157e69 100644 --- a/drivers/media/i2c/lt6911uxe.c +++ b/drivers/media/i2c/lt6911uxe.c @@ -600,7 +600,7 @@ static int lt6911uxe_probe(struct i2c_client *client) v4l2_i2c_subdev_init(<6911uxe->sd, client, <6911uxe_subdev_ops); - lt6911uxe->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_IN); + lt6911uxe->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); if (IS_ERR(lt6911uxe->reset_gpio)) return dev_err_probe(dev, PTR_ERR(lt6911uxe->reset_gpio), "failed to get reset gpio\n"); From 4e89c747358fdc368a2aa0a2cba21100d5f7821e Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 20 May 2025 14:17:44 +0100 Subject: [PATCH 115/310] media: i2c: Defer ov5670_probe() if endpoint not found The OV5670 is found on a Dell 7212, coupled to Intel's CIO2 CSI-2 receiver. The ACPI tables don't describe the connection properly and so this is one of the sensors that needs to wait for the ipu-bridge to set up the graph with software nodes. Defer probe if an endpoint isn't found to allow time for that. Signed-off-by: Daniel Scally Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5670.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/ov5670.c b/drivers/media/i2c/ov5670.c index c54bbc207189..b9efb2d2276a 100644 --- a/drivers/media/i2c/ov5670.c +++ b/drivers/media/i2c/ov5670.c @@ -2688,10 +2688,15 @@ static int ov5670_probe(struct i2c_client *client) if (ret) return dev_err_probe(&client->dev, ret, "GPIO probe failed\n"); - /* Graph Endpoint */ + /* + * Graph Endpoint. If it's missing we defer rather than fail, as this + * sensor is known to co-exist on systems with the IPU3 and so it might + * be created by the ipu-bridge. + */ handle = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); if (!handle) - return dev_err_probe(&client->dev, -ENXIO, "Endpoint for node get failed\n"); + return dev_err_probe(&client->dev, -EPROBE_DEFER, + "Endpoint for node get failed\n"); ov5670->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; ov5670->endpoint.bus.mipi_csi2.num_data_lanes = 2; From bca5cfbb694d66a1c482d0c347eee80f6afbc870 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 20 May 2025 14:17:45 +0100 Subject: [PATCH 116/310] platform/x86: int3472: Add board data for Dell 7212 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Dell 7212 Rugged Extreme Tablet pairs an OV5670 sensor with the Intel IPU3 ISP. The sensor is powered by a TPS68470 PMIC, and so we need some board data to describe how to configure the GPIOs and regulators to run the sensor. Signed-off-by: Daniel Scally Acked-by: Ilpo Järvinen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../x86/intel/int3472/tps68470_board_data.c | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/drivers/platform/x86/intel/int3472/tps68470_board_data.c b/drivers/platform/x86/intel/int3472/tps68470_board_data.c index 322237e056f3..71357a036292 100644 --- a/drivers/platform/x86/intel/int3472/tps68470_board_data.c +++ b/drivers/platform/x86/intel/int3472/tps68470_board_data.c @@ -129,6 +129,109 @@ static const struct tps68470_regulator_platform_data surface_go_tps68470_pdata = }, }; +/* Settings for Dell 7212 Tablet */ + +static struct regulator_consumer_supply int3479_vsio_consumer_supplies[] = { + REGULATOR_SUPPLY("avdd", "i2c-INT3479:00"), +}; + +static struct regulator_consumer_supply int3479_aux1_consumer_supplies[] = { + REGULATOR_SUPPLY("dvdd", "i2c-INT3479:00"), +}; + +static struct regulator_consumer_supply int3479_aux2_consumer_supplies[] = { + REGULATOR_SUPPLY("dovdd", "i2c-INT3479:00"), +}; + +static const struct regulator_init_data dell_7212_tps68470_core_reg_init_data = { + .constraints = { + .min_uV = 1200000, + .max_uV = 1200000, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 0, + .consumer_supplies = NULL, +}; + +static const struct regulator_init_data dell_7212_tps68470_ana_reg_init_data = { + .constraints = { + .min_uV = 2815200, + .max_uV = 2815200, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 0, + .consumer_supplies = NULL, +}; + +static const struct regulator_init_data dell_7212_tps68470_vcm_reg_init_data = { + .constraints = { + .min_uV = 2815200, + .max_uV = 2815200, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 0, + .consumer_supplies = NULL, +}; + +static const struct regulator_init_data dell_7212_tps68470_vio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = 0, + .consumer_supplies = NULL, +}; + +static const struct regulator_init_data dell_7212_tps68470_vsio_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(int3479_vsio_consumer_supplies), + .consumer_supplies = int3479_vsio_consumer_supplies, +}; + +static const struct regulator_init_data dell_7212_tps68470_aux1_reg_init_data = { + .constraints = { + .min_uV = 1213200, + .max_uV = 1213200, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(int3479_aux1_consumer_supplies), + .consumer_supplies = int3479_aux1_consumer_supplies, +}; + +static const struct regulator_init_data dell_7212_tps68470_aux2_reg_init_data = { + .constraints = { + .min_uV = 1800600, + .max_uV = 1800600, + .apply_uV = 1, + .valid_ops_mask = REGULATOR_CHANGE_STATUS, + }, + .num_consumer_supplies = ARRAY_SIZE(int3479_aux2_consumer_supplies), + .consumer_supplies = int3479_aux2_consumer_supplies, +}; + +static const struct tps68470_regulator_platform_data dell_7212_tps68470_pdata = { + .reg_init_data = { + [TPS68470_CORE] = &dell_7212_tps68470_core_reg_init_data, + [TPS68470_ANA] = &dell_7212_tps68470_ana_reg_init_data, + [TPS68470_VCM] = &dell_7212_tps68470_vcm_reg_init_data, + [TPS68470_VIO] = &dell_7212_tps68470_vio_reg_init_data, + [TPS68470_VSIO] = &dell_7212_tps68470_vsio_reg_init_data, + [TPS68470_AUX1] = &dell_7212_tps68470_aux1_reg_init_data, + [TPS68470_AUX2] = &dell_7212_tps68470_aux2_reg_init_data, + }, +}; + static struct gpiod_lookup_table surface_go_int347a_gpios = { .dev_id = "i2c-INT347A:00", .table = { @@ -146,6 +249,15 @@ static struct gpiod_lookup_table surface_go_int347e_gpios = { } }; +static struct gpiod_lookup_table dell_7212_int3479_gpios = { + .dev_id = "i2c-INT3479:00", + .table = { + GPIO_LOOKUP("tps68470-gpio", 3, "reset", GPIO_ACTIVE_LOW), + GPIO_LOOKUP("tps68470-gpio", 4, "powerdown", GPIO_ACTIVE_LOW), + { } + } +}; + static const struct int3472_tps68470_board_data surface_go_tps68470_board_data = { .dev_name = "i2c-INT3472:05", .tps68470_regulator_pdata = &surface_go_tps68470_pdata, @@ -166,6 +278,15 @@ static const struct int3472_tps68470_board_data surface_go3_tps68470_board_data }, }; +static const struct int3472_tps68470_board_data dell_7212_tps68470_board_data = { + .dev_name = "i2c-INT3472:05", + .tps68470_regulator_pdata = &dell_7212_tps68470_pdata, + .n_gpiod_lookups = 1, + .tps68470_gpio_lookup_tables = { + &dell_7212_int3479_gpios, + }, +}; + static const struct dmi_system_id int3472_tps68470_board_data_table[] = { { .matches = { @@ -188,6 +309,13 @@ static const struct dmi_system_id int3472_tps68470_board_data_table[] = { }, .driver_data = (void *)&surface_go3_tps68470_board_data, }, + { + .matches = { + DMI_EXACT_MATCH(DMI_SYS_VENDOR, "Dell Inc."), + DMI_EXACT_MATCH(DMI_PRODUCT_NAME, "Latitude 7212 Rugged Extreme Tablet"), + }, + .driver_data = (void *)&dell_7212_tps68470_board_data, + }, { } }; From 484f8bec3ddb453321ef0b8621c25de6ce3d0302 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Tue, 20 May 2025 14:17:46 +0100 Subject: [PATCH 117/310] media: ipu-bridge: Add _HID for OV5670 The OV5670 is found on Dell 7212 tablets paired with an IPU3 ISP and needs to be connected by the ipu-bridge. Add it to the list of supported devices. Signed-off-by: Daniel Scally Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 382cb7270537..855862e3cad5 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -60,6 +60,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), /* Omnivision OV2740 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), + /* Omnivision OV5670 */ + IPU_SENSOR_CONFIG("INT3479", 1, 422400000), /* Omnivision OV8865 */ IPU_SENSOR_CONFIG("INT347A", 1, 360000000), /* Omnivision OV7251 */ From d0016b2a00d448151285da2f9163e0bd41ee350f Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 5 Jun 2025 11:39:45 +0300 Subject: [PATCH 118/310] media: ipu3-cio2: Use V4L2 subdev active state Replace storage of the active format in the cio2_queue structure with usage of V4L2 subdev active state. This simplifies locking in the driver, and drops usage of the deprecated subdev .open() internal operation. Signed-off-by: Laurent Pinchart [Sakari Ailus: keep fmt_default non-static to keep clang happy.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu3/ipu3-cio2.c | 82 +++++++++--------------- drivers/media/pci/intel/ipu3/ipu3-cio2.h | 2 - 2 files changed, 32 insertions(+), 52 deletions(-) diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.c b/drivers/media/pci/intel/ipu3/ipu3-cio2.c index 16fde96c9fb2..a87f105beb5e 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.c +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.c @@ -358,6 +358,8 @@ static int cio2_hw_init(struct cio2_device *cio2, struct cio2_queue *q) static const int FBPT_WIDTH = DIV_ROUND_UP(CIO2_MAX_LOPS, CIO2_FBPT_SUBENTRY_UNIT); const u32 num_buffers1 = CIO2_MAX_BUFFERS - 1; + struct v4l2_subdev_state *state; + const struct v4l2_mbus_framefmt *format; const struct ipu3_cio2_fmt *fmt; void __iomem *const base = cio2->base; u8 lanes, csi2bus = q->csi2.port; @@ -365,7 +367,13 @@ static int cio2_hw_init(struct cio2_device *cio2, struct cio2_queue *q) struct cio2_csi2_timing timing = { 0 }; int i, r; - fmt = cio2_find_format(NULL, &q->subdev_fmt.code); + state = v4l2_subdev_lock_and_get_active_state(&q->subdev); + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SINK); + + fmt = cio2_find_format(NULL, &format->code); + + v4l2_subdev_unlock_state(state); + if (!fmt) return -EINVAL; @@ -1194,9 +1202,9 @@ static int cio2_subdev_subscribe_event(struct v4l2_subdev *sd, return v4l2_event_subscribe(fh, sub, 0, NULL); } -static int cio2_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) +static int cio2_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) { - struct v4l2_mbus_framefmt *format; const struct v4l2_mbus_framefmt fmt_default = { .width = 1936, .height = 1096, @@ -1207,42 +1215,23 @@ static int cio2_subdev_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) .quantization = V4L2_QUANTIZATION_DEFAULT, .xfer_func = V4L2_XFER_FUNC_DEFAULT, }; + struct v4l2_mbus_framefmt *format; - /* Initialize try_fmt */ - format = v4l2_subdev_state_get_format(fh->state, CIO2_PAD_SINK); + /* Initialize the format on the sink and source pads. */ + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SINK); *format = fmt_default; /* same as sink */ - format = v4l2_subdev_state_get_format(fh->state, CIO2_PAD_SOURCE); + format = v4l2_subdev_state_get_format(state, CIO2_PAD_SOURCE); *format = fmt_default; return 0; } -static int cio2_subdev_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *fmt) -{ - struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev); - - mutex_lock(&q->subdev_lock); - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) - fmt->format = *v4l2_subdev_state_get_format(sd_state, - fmt->pad); - else - fmt->format = q->subdev_fmt; - - mutex_unlock(&q->subdev_lock); - - return 0; -} - static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *fmt) { - struct cio2_queue *q = container_of(sd, struct cio2_queue, subdev); struct v4l2_mbus_framefmt *mbus; u32 mbus_code = fmt->format.code; unsigned int i; @@ -1252,12 +1241,7 @@ static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, * source always propagates from sink */ if (fmt->pad == CIO2_PAD_SOURCE) - return cio2_subdev_get_fmt(sd, sd_state, fmt); - - if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) - mbus = v4l2_subdev_state_get_format(sd_state, fmt->pad); - else - mbus = &q->subdev_fmt; + return v4l2_subdev_get_fmt(sd, sd_state, fmt); fmt->format.code = formats[0].mbus_code; @@ -1272,9 +1256,12 @@ static int cio2_subdev_set_fmt(struct v4l2_subdev *sd, fmt->format.height = min(fmt->format.height, CIO2_IMAGE_MAX_HEIGHT); fmt->format.field = V4L2_FIELD_NONE; - mutex_lock(&q->subdev_lock); + mbus = v4l2_subdev_state_get_format(sd_state, CIO2_PAD_SINK); + *mbus = fmt->format; + + /* Propagate the format to the source pad. */ + mbus = v4l2_subdev_state_get_format(sd_state, CIO2_PAD_SOURCE); *mbus = fmt->format; - mutex_unlock(&q->subdev_lock); return 0; } @@ -1345,12 +1332,12 @@ static const struct v4l2_subdev_core_ops cio2_subdev_core_ops = { }; static const struct v4l2_subdev_internal_ops cio2_subdev_internal_ops = { - .open = cio2_subdev_open, + .init_state = cio2_subdev_init_state, }; static const struct v4l2_subdev_pad_ops cio2_subdev_pad_ops = { .link_validate = v4l2_subdev_link_validate_default, - .get_fmt = cio2_subdev_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = cio2_subdev_set_fmt, .enum_mbus_code = cio2_subdev_enum_mbus_code, }; @@ -1502,28 +1489,18 @@ static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q) { static const u32 default_width = 1936; static const u32 default_height = 1096; - const struct ipu3_cio2_fmt dflt_fmt = formats[0]; struct device *dev = &cio2->pci_dev->dev; struct video_device *vdev = &q->vdev; struct vb2_queue *vbq = &q->vbq; struct v4l2_subdev *subdev = &q->subdev; - struct v4l2_mbus_framefmt *fmt; int r; /* Initialize miscellaneous variables */ mutex_init(&q->lock); - mutex_init(&q->subdev_lock); - - /* Initialize formats to default values */ - fmt = &q->subdev_fmt; - fmt->width = default_width; - fmt->height = default_height; - fmt->code = dflt_fmt.mbus_code; - fmt->field = V4L2_FIELD_NONE; q->format.width = default_width; q->format.height = default_height; - q->format.pixelformat = dflt_fmt.fourcc; + q->format.pixelformat = formats[0].fourcc; q->format.colorspace = V4L2_COLORSPACE_RAW; q->format.field = V4L2_FIELD_NONE; q->format.num_planes = 1; @@ -1567,9 +1544,16 @@ static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q) CIO2_ENTITY_NAME " %td", q - cio2->queue); subdev->entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; v4l2_set_subdevdata(subdev, cio2); + + r = v4l2_subdev_init_finalize(subdev); + if (r) { + dev_err(dev, "failed to initialize subdev (%d)\n", r); + goto fail_subdev; + } + r = v4l2_device_register_subdev(&cio2->v4l2_dev, subdev); if (r) { - dev_err(dev, "failed initialize subdev (%d)\n", r); + dev_err(dev, "failed to register subdev (%d)\n", r); goto fail_subdev; } @@ -1626,7 +1610,6 @@ static int cio2_queue_init(struct cio2_device *cio2, struct cio2_queue *q) fail_subdev_media_entity: cio2_fbpt_exit(q, dev); fail_fbpt: - mutex_destroy(&q->subdev_lock); mutex_destroy(&q->lock); return r; @@ -1639,7 +1622,6 @@ static void cio2_queue_exit(struct cio2_device *cio2, struct cio2_queue *q) v4l2_device_unregister_subdev(&q->subdev); media_entity_cleanup(&q->subdev.entity); cio2_fbpt_exit(q, &cio2->pci_dev->dev); - mutex_destroy(&q->subdev_lock); mutex_destroy(&q->lock); } diff --git a/drivers/media/pci/intel/ipu3/ipu3-cio2.h b/drivers/media/pci/intel/ipu3/ipu3-cio2.h index d7cb7dae665b..19258190936a 100644 --- a/drivers/media/pci/intel/ipu3/ipu3-cio2.h +++ b/drivers/media/pci/intel/ipu3/ipu3-cio2.h @@ -351,9 +351,7 @@ struct cio2_queue { /* Subdev, /dev/v4l-subdevX */ struct v4l2_subdev subdev; - struct mutex subdev_lock; /* Serialise acces to subdev_fmt field */ struct media_pad subdev_pads[CIO2_PADS]; - struct v4l2_mbus_framefmt subdev_fmt; atomic_t frame_sequence; /* Video device, /dev/videoX */ From 784e010ed1e1c5ab7d5344dba6928c8a458f4105 Mon Sep 17 00:00:00 2001 From: Jason Chen Date: Tue, 3 Jun 2025 12:39:23 +0800 Subject: [PATCH 119/310] media: ipu-bridge: Add support for additional link frequencies Support two additional frequencies for the ov08x40 sensor using 2 lanes Signed-off-by: Jason Chen Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 855862e3cad5..c80af394ce18 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -80,7 +80,7 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { /* Omnivision OV08A10 */ IPU_SENSOR_CONFIG("OVTI08A1", 1, 500000000), /* Omnivision OV08x40 */ - IPU_SENSOR_CONFIG("OVTI08F4", 1, 400000000), + IPU_SENSOR_CONFIG("OVTI08F4", 3, 400000000, 749000000, 800000000), /* Omnivision OV13B10 */ IPU_SENSOR_CONFIG("OVTI13B1", 1, 560000000), IPU_SENSOR_CONFIG("OVTIDB10", 1, 560000000), From 5a0400aca5fa7c6b8ba456c311a460e733571c88 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 8 May 2025 18:55:38 +0300 Subject: [PATCH 120/310] media: v4l2-ctrls: Don't reset handler's error in v4l2_ctrl_handler_free() It's a common pattern in drivers to free the control handler's resources and then return the handler's error code on drivers' error handling paths. Alas, the v4l2_ctrl_handler_free() function also zeroes the error field, effectively indicating successful return to the caller. There's no apparent need to touch the error field while releasing the control handler's resources and cleaning up stale pointers. Not touching the handler's error field is a more certain way to address this problem than changing all the users, in which case the pattern would be likely to re-emerge in new drivers. Do just that, don't touch the control handler's error field in v4l2_ctrl_handler_free(). Fixes: 0996517cf8ea ("V4L/DVB: v4l2: Add new control handling framework") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Reviewed-by: Hans Verkuil Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index b45809a82f9a..d28596c720d8 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1661,7 +1661,6 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) kvfree(hdl->buckets); hdl->buckets = NULL; hdl->cached = NULL; - hdl->error = 0; mutex_unlock(hdl->lock); mutex_destroy(&hdl->_lock); } From 04f541cef2dba78b17c83ad8b5a0742012399530 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 19 Jun 2025 12:21:51 +0300 Subject: [PATCH 121/310] media: v4l2-ctrls: Return the handler's error in v4l2_ctrl_handler_free() v4l2_ctrl_handler_free() used to return void but changing this to int, returning the handler's error code, enables the drivers to simply return the handler's error in this common error handling pattern: if (handler->error) return v4l2_ctrl_handler_free(handler); Suggested-by: Laurent Pinchart Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ctrls-core.c | 11 ++++++++--- include/media/v4l2-ctrls.h | 4 +++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c index d28596c720d8..98b960775e87 100644 --- a/drivers/media/v4l2-core/v4l2-ctrls-core.c +++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c @@ -1631,14 +1631,17 @@ int v4l2_ctrl_handler_init_class(struct v4l2_ctrl_handler *hdl, EXPORT_SYMBOL(v4l2_ctrl_handler_init_class); /* Free all controls and control refs */ -void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) +int v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) { struct v4l2_ctrl_ref *ref, *next_ref; struct v4l2_ctrl *ctrl, *next_ctrl; struct v4l2_subscribed_event *sev, *next_sev; - if (hdl == NULL || hdl->buckets == NULL) - return; + if (!hdl) + return 0; + + if (!hdl->buckets) + return hdl->error; v4l2_ctrl_handler_free_request(hdl); @@ -1663,6 +1666,8 @@ void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl) hdl->cached = NULL; mutex_unlock(hdl->lock); mutex_destroy(&hdl->_lock); + + return hdl->error; } EXPORT_SYMBOL(v4l2_ctrl_handler_free); diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h index 3a87096e064f..c32c46286441 100644 --- a/include/media/v4l2-ctrls.h +++ b/include/media/v4l2-ctrls.h @@ -579,8 +579,10 @@ int v4l2_ctrl_handler_init_class(struct v4l2_ctrl_handler *hdl, * @hdl: The control handler. * * Does nothing if @hdl == NULL. + * + * Return: @hdl's error field or 0 if @hdl is NULL. */ -void v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl); +int v4l2_ctrl_handler_free(struct v4l2_ctrl_handler *hdl); /** * v4l2_ctrl_lock() - Helper function to lock the handler From bd227d0fc25c46c7fda43fe62cf7b04e9d0f0baa Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 19 Jun 2025 12:23:48 +0300 Subject: [PATCH 122/310] media: Documentation: Document new v4l2_ctrl_handler_free() behaviour v4l2_ctrl_handler_free() no longer resets the handler's error code. Document it. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Hans Verkuil Signed-off-by: Hans Verkuil --- Documentation/driver-api/media/v4l2-controls.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Documentation/driver-api/media/v4l2-controls.rst b/Documentation/driver-api/media/v4l2-controls.rst index b2e91804829b..fc04907589ab 100644 --- a/Documentation/driver-api/media/v4l2-controls.rst +++ b/Documentation/driver-api/media/v4l2-controls.rst @@ -110,6 +110,7 @@ For sub-device drivers: v4l2_ctrl_handler_free(&foo->ctrl_handler); +:c:func:`v4l2_ctrl_handler_free` does not touch the handler's ``error`` field. 2) Add controls: @@ -191,12 +192,8 @@ These functions are typically called right after the V4L2_CID_TEST_PATTERN, ARRAY_SIZE(test_pattern) - 1, 0, 0, test_pattern); ... - if (foo->ctrl_handler.error) { - int err = foo->ctrl_handler.error; - - v4l2_ctrl_handler_free(&foo->ctrl_handler); - return err; - } + if (foo->ctrl_handler.error) + return v4l2_ctrl_handler_free(&foo->ctrl_handler); The :c:func:`v4l2_ctrl_new_std` function returns the v4l2_ctrl pointer to the new control, but if you do not need to access the pointer outside the From 0d0ccb91c92fb202f0c97ee32b63fc848e659fc8 Mon Sep 17 00:00:00 2001 From: Olivier Benjamin Date: Fri, 20 Jun 2025 17:21:32 +0200 Subject: [PATCH 123/310] dt-bindings: media: ov8858: inherit video-interface-devices properties Update the OV8858 binding to inherit properties defined in the video-interface-devices binding. Acked-by: Rob Herring (Arm) Signed-off-by: Olivier Benjamin Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml index a65f921ec0fd..491f2931e6bc 100644 --- a/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml +++ b/Documentation/devicetree/bindings/media/i2c/ovti,ov8858.yaml @@ -15,6 +15,8 @@ description: | controlled through an I2C-compatible SCCB bus. The sensor transmits images on a MIPI CSI-2 output interface with up to 4 data lanes. +$ref: /schemas/media/video-interface-devices.yaml# + properties: compatible: const: ovti,ov8858 @@ -69,7 +71,7 @@ required: - clocks - port -additionalProperties: false +unevaluatedProperties: false examples: - | From 77369edb3c61ecd791d4de1f38ce867400bcfcc1 Mon Sep 17 00:00:00 2001 From: Olivier Benjamin Date: Fri, 20 Jun 2025 17:21:33 +0200 Subject: [PATCH 124/310] dt-bindings: media: imx258: inherit video-interface-devices properties Update the IMX258 binding to inherit properties defined in the video-interface-devices binding. Acked-by: Rob Herring (Arm) Signed-off-by: Olivier Benjamin Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/i2c/sony,imx258.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/i2c/sony,imx258.yaml b/Documentation/devicetree/bindings/media/i2c/sony,imx258.yaml index 975c1d77c8e5..421b935b52bc 100644 --- a/Documentation/devicetree/bindings/media/i2c/sony,imx258.yaml +++ b/Documentation/devicetree/bindings/media/i2c/sony,imx258.yaml @@ -18,6 +18,8 @@ description: |- The camera module does not expose the model through registers, so the exact model needs to be specified. +$ref: /schemas/media/video-interface-devices.yaml# + properties: compatible: enum: @@ -81,7 +83,7 @@ required: - reg - port -additionalProperties: false +unevaluatedProperties: false examples: - | From 0c92c49fc688cfadacc47ae99b06a31237702e9e Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 21 Jun 2025 16:00:52 +0200 Subject: [PATCH 125/310] media: ivsc: Fix crash at shutdown due to missing mei_cldev_disable() calls Both the ACE and CSI driver are missing a mei_cldev_disable() call in their remove() function. This causes the mei_cl client to stay part of the mei_device->file_list list even though its memory is freed by mei_cl_bus_dev_release() calling kfree(cldev->cl). This leads to a use-after-free when mei_vsc_remove() runs mei_stop() which first removes all mei bus devices calling mei_ace_remove() and mei_csi_remove() followed by mei_cl_bus_dev_release() and then calls mei_cl_all_disconnect() which walks over mei_device->file_list dereferecing the just freed cldev->cl. And mei_vsc_remove() it self is run at shutdown because of the platform_device_unregister(tp->pdev) in vsc_tp_shutdown() When building a kernel with KASAN this leads to the following KASAN report: [ 106.634504] ================================================================== [ 106.634623] BUG: KASAN: slab-use-after-free in mei_cl_set_disconnected (drivers/misc/mei/client.c:783) mei [ 106.634683] Read of size 4 at addr ffff88819cb62018 by task systemd-shutdow/1 [ 106.634729] [ 106.634767] Tainted: [E]=UNSIGNED_MODULE [ 106.634770] Hardware name: Dell Inc. XPS 16 9640/09CK4V, BIOS 1.12.0 02/10/2025 [ 106.634773] Call Trace: [ 106.634777] ... [ 106.634871] kasan_report (mm/kasan/report.c:221 mm/kasan/report.c:636) [ 106.634901] mei_cl_set_disconnected (drivers/misc/mei/client.c:783) mei [ 106.634921] mei_cl_all_disconnect (drivers/misc/mei/client.c:2165 (discriminator 4)) mei [ 106.634941] mei_reset (drivers/misc/mei/init.c:163) mei ... [ 106.635042] mei_stop (drivers/misc/mei/init.c:348) mei [ 106.635062] mei_vsc_remove (drivers/misc/mei/mei_dev.h:784 drivers/misc/mei/platform-vsc.c:393) mei_vsc [ 106.635066] platform_remove (drivers/base/platform.c:1424) Add the missing mei_cldev_disable() calls so that the mei_cl gets removed from mei_device->file_list before it is freed to fix this. Fixes: 78876f71b3e9 ("media: pci: intel: ivsc: Add ACE submodule") Fixes: 29006e196a56 ("media: pci: intel: ivsc: Add CSI submodule") Cc: stable@vger.kernel.org Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ivsc/mei_ace.c | 2 ++ drivers/media/pci/intel/ivsc/mei_csi.c | 2 ++ 2 files changed, 4 insertions(+) diff --git a/drivers/media/pci/intel/ivsc/mei_ace.c b/drivers/media/pci/intel/ivsc/mei_ace.c index 3622271c71c8..50d18b627e15 100644 --- a/drivers/media/pci/intel/ivsc/mei_ace.c +++ b/drivers/media/pci/intel/ivsc/mei_ace.c @@ -529,6 +529,8 @@ static void mei_ace_remove(struct mei_cl_device *cldev) ace_set_camera_owner(ace, ACE_CAMERA_IVSC); + mei_cldev_disable(cldev); + mutex_destroy(&ace->lock); } diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c index 92d871a378ba..955f687e5d59 100644 --- a/drivers/media/pci/intel/ivsc/mei_csi.c +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -760,6 +760,8 @@ static void mei_csi_remove(struct mei_cl_device *cldev) pm_runtime_disable(&cldev->dev); + mei_cldev_disable(cldev); + mutex_destroy(&csi->lock); } From 23983ae6fcb2c8766435a5ce2b5a34624c303fcb Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 23 Jun 2025 09:59:48 +0300 Subject: [PATCH 126/310] media: ivsc: Remove Wentong's e-mail address Remove Wentong's e-mail address from MODULE_AUTHOR() macro as the current e-mail address is no longer valid. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ivsc/mei_ace.c | 2 +- drivers/media/pci/intel/ivsc/mei_csi.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/pci/intel/ivsc/mei_ace.c b/drivers/media/pci/intel/ivsc/mei_ace.c index 50d18b627e15..98310b8511b1 100644 --- a/drivers/media/pci/intel/ivsc/mei_ace.c +++ b/drivers/media/pci/intel/ivsc/mei_ace.c @@ -576,7 +576,7 @@ static struct mei_cl_driver mei_ace_driver = { module_mei_cl_driver(mei_ace_driver); -MODULE_AUTHOR("Wentong Wu "); +MODULE_AUTHOR("Wentong Wu"); MODULE_AUTHOR("Zhifeng Wang "); MODULE_DESCRIPTION("Device driver for IVSC ACE"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/pci/intel/ivsc/mei_csi.c b/drivers/media/pci/intel/ivsc/mei_csi.c index 955f687e5d59..c2917e156345 100644 --- a/drivers/media/pci/intel/ivsc/mei_csi.c +++ b/drivers/media/pci/intel/ivsc/mei_csi.c @@ -785,7 +785,7 @@ static struct mei_cl_driver mei_csi_driver = { module_mei_cl_driver(mei_csi_driver); MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); -MODULE_AUTHOR("Wentong Wu "); +MODULE_AUTHOR("Wentong Wu"); MODULE_AUTHOR("Zhifeng Wang "); MODULE_DESCRIPTION("Device driver for IVSC CSI"); MODULE_LICENSE("GPL"); From ea200a16d6c7dbfd37d5761078d2453c038a37f1 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 27 Mar 2024 13:17:33 +0200 Subject: [PATCH 127/310] media: ov2740: Remove shorthand variables Remove two variables in ov2740_init_control() that are used as a shorthand for where the information is really located. Make the code more readable by removing them. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov2740.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c index 6cf461e3373c..4e959534e6e7 100644 --- a/drivers/media/i2c/ov2740.c +++ b/drivers/media/i2c/ov2740.c @@ -766,11 +766,9 @@ static int ov2740_init_controls(struct ov2740 *ov2740) { struct i2c_client *client = v4l2_get_subdevdata(&ov2740->sd); struct v4l2_ctrl_handler *ctrl_hdlr; - const struct ov2740_mode *cur_mode; s64 exposure_max, h_blank, pixel_rate; u32 vblank_min, vblank_max, vblank_default; struct v4l2_fwnode_device_properties props; - int size; int ret; ctrl_hdlr = &ov2740->ctrl_handler; @@ -778,12 +776,10 @@ static int ov2740_init_controls(struct ov2740 *ov2740) if (ret) return ret; - cur_mode = ov2740->cur_mode; - size = ARRAY_SIZE(link_freq_menu_items); - ov2740->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, &ov2740_ctrl_ops, - V4L2_CID_LINK_FREQ, size - 1, + V4L2_CID_LINK_FREQ, + ARRAY_SIZE(link_freq_menu_items) - 1, ov2740->supported_modes->link_freq_index, link_freq_menu_items); if (ov2740->link_freq) @@ -794,14 +790,14 @@ static int ov2740_init_controls(struct ov2740 *ov2740) V4L2_CID_PIXEL_RATE, 0, pixel_rate, 1, pixel_rate); - vblank_min = cur_mode->vts_min - cur_mode->height; - vblank_max = cur_mode->vts_max - cur_mode->height; - vblank_default = cur_mode->vts_def - cur_mode->height; + vblank_min = ov2740->cur_mode->vts_min - ov2740->cur_mode->height; + vblank_max = ov2740->cur_mode->vts_max - ov2740->cur_mode->height; + vblank_default = ov2740->cur_mode->vts_def - ov2740->cur_mode->height; ov2740->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_VBLANK, vblank_min, vblank_max, 1, vblank_default); - h_blank = cur_mode->hts - cur_mode->width; + h_blank = ov2740->cur_mode->hts - ov2740->cur_mode->width; ov2740->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_HBLANK, h_blank, h_blank, 1, h_blank); @@ -814,7 +810,7 @@ static int ov2740_init_controls(struct ov2740 *ov2740) v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_DIGITAL_GAIN, OV2740_DGTL_GAIN_MIN, OV2740_DGTL_GAIN_MAX, OV2740_DGTL_GAIN_STEP, OV2740_DGTL_GAIN_DEFAULT); - exposure_max = cur_mode->vts_def - OV2740_EXPOSURE_MAX_MARGIN; + exposure_max = ov2740->cur_mode->vts_def - OV2740_EXPOSURE_MAX_MARGIN; ov2740->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, V4L2_CID_EXPOSURE, OV2740_EXPOSURE_MIN, exposure_max, From 29d77da94173e7a828caeedecef93c5365c6fddc Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:09:50 +0200 Subject: [PATCH 128/310] media: ipu-bridge: Add Toshiba T4KA3 HID to list of supported sensor The Xiaomi Mi Pad 2 tablet uses a Toshiba T4KA3 sensor, using a Xiaomi specific Hardware-ID of "XMMC0003" add this to the sensor-list of the ipu-bridge code. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index c80af394ce18..5a8e3a339b22 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -88,6 +88,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("OVTI2680", 1, 331200000), /* Omnivision OV8856 */ IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), + /* Toshiba T4KA3 */ + IPU_SENSOR_CONFIG("XMCC0003", 1, 321468000), }; static const struct ipu_property_names prop_names = { From dd8bb4a2ecd3bb2a5a64cb58a6ff5c4d9c400c46 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:09:51 +0200 Subject: [PATCH 129/310] media: ipu-bridge: Add Onsemi MT9M114 HID to list of supported sensors Add INT33F0 ACPI HID for the Onsemi MT9M114 sensor to the list of supported sensors. This sensor is found on the Asus T100TA using this HID. Tested-by: Hans de Goede # Asus T100TA with MT9M114 Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 5a8e3a339b22..2124fad0570b 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -58,6 +58,8 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { IPU_SENSOR_CONFIG("INT0310", 1, 55692000), /* Omnivision OV5693 */ IPU_SENSOR_CONFIG("INT33BE", 1, 419200000), + /* Onsemi MT9M114 */ + IPU_SENSOR_CONFIG("INT33F0", 1, 384000000), /* Omnivision OV2740 */ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), /* Omnivision OV5670 */ From 2371517601aa2b7189a10236328dff0993b0294f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hanne-Lotta=20M=C3=A4enp=C3=A4=C3=A4?= Date: Thu, 12 Jun 2025 20:27:03 +0300 Subject: [PATCH 130/310] media: Documentation: Improve grammar, formatting in Video4Linux MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix typos, punctuation and improve grammar and formatting in documentation for Video4Linux (V4L). Signed-off-by: Hanne-Lotta Mäenpää Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/biblio.rst | 2 +- .../media/v4l/dev-sliced-vbi.rst | 4 ++-- .../media/v4l/ext-ctrls-fm-rx.rst | 11 +++------- .../media/v4l/ext-ctrls-fm-tx.rst | 21 +++++++------------ .../media/v4l/pixfmt-srggb12p.rst | 4 ++-- .../media/v4l/pixfmt-srggb14p.rst | 2 +- 6 files changed, 16 insertions(+), 28 deletions(-) diff --git a/Documentation/userspace-api/media/v4l/biblio.rst b/Documentation/userspace-api/media/v4l/biblio.rst index 35674eeae20d..856acf6a890c 100644 --- a/Documentation/userspace-api/media/v4l/biblio.rst +++ b/Documentation/userspace-api/media/v4l/biblio.rst @@ -150,7 +150,7 @@ ITU-T.81 ======== -:title: ITU-T Recommendation T.81 "Information Technology --- Digital Compression and Coding of Continous-Tone Still Images --- Requirements and Guidelines" +:title: ITU-T Recommendation T.81 "Information Technology --- Digital Compression and Coding of Continuous-Tone Still Images --- Requirements and Guidelines" :author: International Telecommunication Union (http://www.itu.int) diff --git a/Documentation/userspace-api/media/v4l/dev-sliced-vbi.rst b/Documentation/userspace-api/media/v4l/dev-sliced-vbi.rst index 42cdb0a9f786..96e0e85a822c 100644 --- a/Documentation/userspace-api/media/v4l/dev-sliced-vbi.rst +++ b/Documentation/userspace-api/media/v4l/dev-sliced-vbi.rst @@ -48,7 +48,7 @@ capabilities, and they may support :ref:`control` ioctls. The :ref:`video standard ` ioctls provide information vital to program a sliced VBI device, therefore must be supported. -.. _sliced-vbi-format-negotitation: +.. _sliced-vbi-format-negotiation: Sliced VBI Format Negotiation ============================= @@ -377,7 +377,7 @@ Sliced VBI Data in MPEG Streams If a device can produce an MPEG output stream, it may be capable of providing -:ref:`negotiated sliced VBI services ` +:ref:`negotiated sliced VBI services ` as data embedded in the MPEG stream. Users or applications control this sliced VBI data insertion with the :ref:`V4L2_CID_MPEG_STREAM_VBI_FMT ` diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-fm-rx.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-fm-rx.rst index b6cfc0e823d2..ccd439e9e0e3 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-fm-rx.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-fm-rx.rst @@ -64,17 +64,12 @@ FM_RX Control IDs broadcasts speech. If the transmitter doesn't make this distinction, then it will be set. -``V4L2_CID_TUNE_DEEMPHASIS`` - (enum) - -enum v4l2_deemphasis - +``V4L2_CID_TUNE_DEEMPHASIS (enum)`` Configures the de-emphasis value for reception. A de-emphasis filter is applied to the broadcast to accentuate the high audio frequencies. Depending on the region, a time constant of either 50 - or 75 useconds is used. The enum v4l2_deemphasis defines possible - values for de-emphasis. Here they are: - - + or 75 microseconds is used. The enum v4l2_deemphasis defines possible + values for de-emphasis. They are: .. flat-table:: :header-rows: 0 diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-fm-tx.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-fm-tx.rst index 04c997c9a4c3..cb40cf4cc3ec 100644 --- a/Documentation/userspace-api/media/v4l/ext-ctrls-fm-tx.rst +++ b/Documentation/userspace-api/media/v4l/ext-ctrls-fm-tx.rst @@ -104,7 +104,7 @@ FM_TX Control IDs ``V4L2_CID_AUDIO_LIMITER_RELEASE_TIME (integer)`` Sets the audio deviation limiter feature release time. Unit is in - useconds. Step and range are driver-specific. + microseconds. Step and range are driver-specific. ``V4L2_CID_AUDIO_LIMITER_DEVIATION (integer)`` Configures audio frequency deviation level in Hz. The range and step @@ -121,16 +121,16 @@ FM_TX Control IDs range and step are driver-specific. ``V4L2_CID_AUDIO_COMPRESSION_THRESHOLD (integer)`` - Sets the threshold level for audio compression freature. It is a dB + Sets the threshold level for audio compression feature. It is a dB value. The range and step are driver-specific. ``V4L2_CID_AUDIO_COMPRESSION_ATTACK_TIME (integer)`` - Sets the attack time for audio compression feature. It is a useconds + Sets the attack time for audio compression feature. It is a microseconds value. The range and step are driver-specific. ``V4L2_CID_AUDIO_COMPRESSION_RELEASE_TIME (integer)`` Sets the release time for audio compression feature. It is a - useconds value. The range and step are driver-specific. + microseconds value. The range and step are driver-specific. ``V4L2_CID_PILOT_TONE_ENABLED (boolean)`` Enables or disables the pilot tone generation feature. @@ -143,17 +143,12 @@ FM_TX Control IDs Configures pilot tone frequency value. Unit is in Hz. The range and step are driver-specific. -``V4L2_CID_TUNE_PREEMPHASIS`` - (enum) - -enum v4l2_preemphasis - +``V4L2_CID_TUNE_PREEMPHASIS (enum)`` Configures the pre-emphasis value for broadcasting. A pre-emphasis filter is applied to the broadcast to accentuate the high audio frequencies. Depending on the region, a time constant of either 50 - or 75 useconds is used. The enum v4l2_preemphasis defines possible - values for pre-emphasis. Here they are: - - + or 75 microseconds is used. The enum v4l2_preemphasis defines possible + values for pre-emphasis. They are: .. flat-table:: :header-rows: 0 @@ -166,8 +161,6 @@ enum v4l2_preemphasis - * - ``V4L2_PREEMPHASIS_75_uS`` - A pre-emphasis of 75 uS is used. - - ``V4L2_CID_TUNE_POWER_LEVEL (integer)`` Sets the output power level for signal transmission. Unit is in dBuV. Range and step are driver-specific. diff --git a/Documentation/userspace-api/media/v4l/pixfmt-srggb12p.rst b/Documentation/userspace-api/media/v4l/pixfmt-srggb12p.rst index 7c3810ff783c..8c03aedcc00e 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-srggb12p.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-srggb12p.rst @@ -6,7 +6,7 @@ .. _v4l2-pix-fmt-sgrbg12p: ******************************************************************************************************************************* -V4L2_PIX_FMT_SRGGB12P ('pRCC'), V4L2_PIX_FMT_SGRBG12P ('pgCC'), V4L2_PIX_FMT_SGBRG12P ('pGCC'), V4L2_PIX_FMT_SBGGR12P ('pBCC'), +V4L2_PIX_FMT_SRGGB12P ('pRCC'), V4L2_PIX_FMT_SGRBG12P ('pgCC'), V4L2_PIX_FMT_SGBRG12P ('pGCC'), V4L2_PIX_FMT_SBGGR12P ('pBCC') ******************************************************************************************************************************* @@ -20,7 +20,7 @@ Description These four pixel formats are packed raw sRGB / Bayer formats with 12 bits per colour. Every two consecutive samples are packed into three bytes. Each of the first two bytes contain the 8 high order bits of -the pixels, and the third byte contains the four least significants +the pixels, and the third byte contains the four least significant bits of each pixel, in the same order. Each n-pixel row contains n/2 green samples and n/2 blue or red diff --git a/Documentation/userspace-api/media/v4l/pixfmt-srggb14p.rst b/Documentation/userspace-api/media/v4l/pixfmt-srggb14p.rst index 3572e42adb22..f4f53d7dbdeb 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-srggb14p.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-srggb14p.rst @@ -24,7 +24,7 @@ These four pixel formats are packed raw sRGB / Bayer formats with 14 bits per colour. Every four consecutive samples are packed into seven bytes. Each of the first four bytes contain the eight high order bits of the pixels, and the three following bytes contains the six least -significants bits of each pixel, in the same order. +significant bits of each pixel, in the same order. Each n-pixel row contains n/2 green samples and n/2 blue or red samples, with alternating green-red and green-blue rows. They are conventionally From 76f0c8244692b49bf80abe66b678e04f62b9900a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:00:32 +0200 Subject: [PATCH 131/310] media: ov5693: Improve error logging when fwnode is not found The ov5693 driver waits for the endpoint fwnode to show up in case this fwnode is created by a bridge-driver. It does this by returning -EPROBE_DEFER, but it does not use dev_err_probe() so no reason for deferring gets registered. After 30 seconds the kernel logs a warning that the probe is still deferred, which looks like this: [ 33.951709] i2c i2c-INT33BE:00: deferred probe pending: (reason unknown) Use dev_err_probe() when returning -EPROBE_DEFER to register the probe deferral reason changing the error to: deferred probe pending: waiting for fwnode graph endpoint Also update the comment to not refer to the no longer existing cio2-bridge code. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov5693.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov5693.c b/drivers/media/i2c/ov5693.c index 46b9ce111676..485efd15257e 100644 --- a/drivers/media/i2c/ov5693.c +++ b/drivers/media/i2c/ov5693.c @@ -1222,9 +1222,14 @@ static int ov5693_check_hwcfg(struct ov5693_device *ov5693) unsigned int i; int ret; + /* + * Sometimes the fwnode graph is initialized by the bridge driver + * Bridge drivers doing this may also add GPIO mappings, wait for this. + */ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!endpoint) - return -EPROBE_DEFER; /* Could be provided by cio2-bridge */ + return dev_err_probe(ov5693->dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); fwnode_handle_put(endpoint); From 826f6fd67691861b3dc2cbbd1ea7c8bbb5bfe1b2 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:00:33 +0200 Subject: [PATCH 132/310] media: ov7251: Improve error logging when fwnode is not found The ov7251 driver waits for the endpoint fwnode to show up in case this fwnode is created by a bridge-driver. It does this by returning -EPROBE_DEFER, but it does not use dev_err_probe() so no reason for deferring gets registered. After 30 seconds the kernel logs a warning that the probe is still deferred, which looks like this: [ 33.952052] i2c i2c-INT347E:00: deferred probe pending: (reason unknown) Use dev_err_probe() when returning -EPROBE_DEFER to register the probe deferral reason changing the error to: deferred probe pending: waiting for fwnode graph endpoint Also update the comment to not refer to the no longer existing cio2-bridge code. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov7251.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov7251.c b/drivers/media/i2c/ov7251.c index 3226888d77e9..31a42d81e970 100644 --- a/drivers/media/i2c/ov7251.c +++ b/drivers/media/i2c/ov7251.c @@ -1486,9 +1486,14 @@ static int ov7251_check_hwcfg(struct ov7251 *ov7251) unsigned int i, j; int ret; + /* + * Sometimes the fwnode graph is initialized by the bridge driver + * Bridge drivers doing this may also add GPIO mappings, wait for this. + */ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); if (!endpoint) - return -EPROBE_DEFER; /* could be provided by cio2-bridge */ + return dev_err_probe(ov7251->dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); ret = v4l2_fwnode_endpoint_alloc_parse(endpoint, &bus_cfg); fwnode_handle_put(endpoint); From fabb0595b58f8c288017816eee5fe8aed4a4dfef Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:00:34 +0200 Subject: [PATCH 133/310] media: ov8865: Improve error logging when fwnode is not found The ov8865 driver waits for the endpoint fwnode to show up in case this fwnode is created by a bridge-driver. It does this by returning -EPROBE_DEFER, but it does not use dev_err_probe() so no reason for deferring gets registered. After 30 seconds the kernel logs a warning that the probe is still deferred, which looks like this: [ 33.952061] i2c i2c-INT347A:00: deferred probe pending: (reason unknown) Use dev_err_probe() when returning -EPROBE_DEFER to register the probe deferral reason changing the error to: deferred probe pending: waiting for fwnode graph endpoint Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov8865.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov8865.c b/drivers/media/i2c/ov8865.c index 95ffe7536aa6..a2138f7988aa 100644 --- a/drivers/media/i2c/ov8865.c +++ b/drivers/media/i2c/ov8865.c @@ -2991,7 +2991,8 @@ static int ov8865_probe(struct i2c_client *client) handle = fwnode_graph_get_next_endpoint(dev_fwnode(dev), NULL); if (!handle) - return -EPROBE_DEFER; + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); sensor->endpoint.bus_type = V4L2_MBUS_CSI2_DPHY; From 9316e398e2e93b67f6adc9b0e78771580767bd0c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 23 Jun 2025 13:00:35 +0200 Subject: [PATCH 134/310] media: ipu-bridge: Improve error logging when waiting for IVSC to become ready The ipu-bridge code waits for the IVSC to become ready (on platforms with an IVSC chip). It does this by returning -EPROBE_DEFER, but it does not use dev_err_probe() so no reason for deferring gets registered. After 30 seconds the kernel logs a warning that the probe is still deferred, which looks like this: [ 33.951709] pci 0000:00:14.3: deferred probe pending: (reason unknown) Use dev_err_probe() when returning -EPROBE_DEFER to register the probe deferral reason changing the error to: deferred probe pending: waiting for IVSC to become ready to help with debugging why drivers are not binding if the iVSC does not become ready for some reason. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu-bridge.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c index 2124fad0570b..4e579352ab2c 100644 --- a/drivers/media/pci/intel/ipu-bridge.c +++ b/drivers/media/pci/intel/ipu-bridge.c @@ -815,7 +815,8 @@ int ipu_bridge_init(struct device *dev, return 0; if (!ipu_bridge_ivsc_is_ready()) - return -EPROBE_DEFER; + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for IVSC to become ready\n"); bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); if (!bridge) From feec4b07c820f322c6b5b6b52b8ef2ca4cc05ad2 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Thu, 12 Jun 2025 08:54:10 +0200 Subject: [PATCH 135/310] media: dw9714: coding style fixes Just some minor coding style fixes reported by checkpatch. Signed-off-by: Matthias Fend Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/dw9714.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c index 2ddd7daa79e2..1392cddab738 100644 --- a/drivers/media/i2c/dw9714.c +++ b/drivers/media/i2c/dw9714.c @@ -144,7 +144,7 @@ static int dw9714_probe(struct i2c_client *client) dw9714_dev = devm_kzalloc(&client->dev, sizeof(*dw9714_dev), GFP_KERNEL); - if (dw9714_dev == NULL) + if (!dw9714_dev) return -ENOMEM; dw9714_dev->vcc = devm_regulator_get(&client->dev, "vcc"); @@ -247,7 +247,7 @@ static int __maybe_unused dw9714_vcm_suspend(struct device *dev) * The lens position is gradually moved in units of DW9714_CTRL_STEPS, * to make the movements smoothly. */ -static int __maybe_unused dw9714_vcm_resume(struct device *dev) +static int __maybe_unused dw9714_vcm_resume(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct v4l2_subdev *sd = i2c_get_clientdata(client); @@ -271,7 +271,7 @@ static int __maybe_unused dw9714_vcm_resume(struct device *dev) DW9714_VAL(val, DW9714_DEFAULT_S)); if (ret) dev_err_ratelimited(dev, "%s I2C failure: %d", - __func__, ret); + __func__, ret); usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); } From 1eefe42e9de503e422a9c925eebdbd215ee28966 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Thu, 12 Jun 2025 08:54:11 +0200 Subject: [PATCH 136/310] media: dw9714: move power sequences to dedicated functions Move the power-up and power-down sequences to their own functions. This is a preparation for the upcoming powerdown pin support. Signed-off-by: Matthias Fend Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/dw9714.c | 44 +++++++++++++++++++++++++------------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c index 1392cddab738..2abd4fb5e7c0 100644 --- a/drivers/media/i2c/dw9714.c +++ b/drivers/media/i2c/dw9714.c @@ -137,6 +137,24 @@ static int dw9714_init_controls(struct dw9714_device *dev_vcm) return hdl->error; } +static int dw9714_power_up(struct dw9714_device *dw9714_dev) +{ + int ret; + + ret = regulator_enable(dw9714_dev->vcc); + if (ret) + return ret; + + usleep_range(1000, 2000); + + return 0; +} + +static int dw9714_power_down(struct dw9714_device *dw9714_dev) +{ + return regulator_disable(dw9714_dev->vcc); +} + static int dw9714_probe(struct i2c_client *client) { struct dw9714_device *dw9714_dev; @@ -151,13 +169,10 @@ static int dw9714_probe(struct i2c_client *client) if (IS_ERR(dw9714_dev->vcc)) return PTR_ERR(dw9714_dev->vcc); - rval = regulator_enable(dw9714_dev->vcc); - if (rval < 0) { - dev_err(&client->dev, "failed to enable vcc: %d\n", rval); - return rval; - } - - usleep_range(1000, 2000); + rval = dw9714_power_up(dw9714_dev); + if (rval) + return dev_err_probe(&client->dev, rval, + "failed to power up: %d\n", rval); v4l2_i2c_subdev_init(&dw9714_dev->sd, client, &dw9714_ops); dw9714_dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | @@ -185,7 +200,7 @@ static int dw9714_probe(struct i2c_client *client) return 0; err_cleanup: - regulator_disable(dw9714_dev->vcc); + dw9714_power_down(dw9714_dev); v4l2_ctrl_handler_free(&dw9714_dev->ctrls_vcm); media_entity_cleanup(&dw9714_dev->sd.entity); @@ -200,10 +215,10 @@ static void dw9714_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); if (!pm_runtime_status_suspended(&client->dev)) { - ret = regulator_disable(dw9714_dev->vcc); + ret = dw9714_power_down(dw9714_dev); if (ret) { dev_err(&client->dev, - "Failed to disable vcc: %d\n", ret); + "Failed to power down: %d\n", ret); } } pm_runtime_set_suspended(&client->dev); @@ -234,9 +249,9 @@ static int __maybe_unused dw9714_vcm_suspend(struct device *dev) usleep_range(DW9714_CTRL_DELAY_US, DW9714_CTRL_DELAY_US + 10); } - ret = regulator_disable(dw9714_dev->vcc); + ret = dw9714_power_down(dw9714_dev); if (ret) - dev_err(dev, "Failed to disable vcc: %d\n", ret); + dev_err(dev, "Failed to power down: %d\n", ret); return ret; } @@ -257,12 +272,11 @@ static int __maybe_unused dw9714_vcm_resume(struct device *dev) if (pm_runtime_suspended(&client->dev)) return 0; - ret = regulator_enable(dw9714_dev->vcc); + ret = dw9714_power_up(dw9714_dev); if (ret) { - dev_err(dev, "Failed to enable vcc: %d\n", ret); + dev_err(dev, "Failed to power up: %d\n", ret); return ret; } - usleep_range(1000, 2000); for (val = dw9714_dev->current_val % DW9714_CTRL_STEPS; val < dw9714_dev->current_val + DW9714_CTRL_STEPS - 1; From 03dca1842421b068d6a65b8ae16e2191882c7753 Mon Sep 17 00:00:00 2001 From: Matthias Fend Date: Thu, 12 Jun 2025 08:54:12 +0200 Subject: [PATCH 137/310] media: dw9714: add support for powerdown pin Add support for the powerdown pin (xSD), which can be used to put the VCM driver into power down mode. This is useful, for example, if the VCM driver's power supply cannot be controlled. The use of the powerdown pin is optional. Signed-off-by: Matthias Fend Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 2 +- drivers/media/i2c/dw9714.c | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 43e39bf22b6b..4b4c199da6ea 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -792,7 +792,7 @@ config VIDEO_AK7375 config VIDEO_DW9714 tristate "DW9714 lens voice coil support" - depends on I2C && VIDEO_DEV + depends on GPIOLIB && I2C && VIDEO_DEV select MEDIA_CONTROLLER select VIDEO_V4L2_SUBDEV_API select V4L2_ASYNC diff --git a/drivers/media/i2c/dw9714.c b/drivers/media/i2c/dw9714.c index 2abd4fb5e7c0..1e7ad355a388 100644 --- a/drivers/media/i2c/dw9714.c +++ b/drivers/media/i2c/dw9714.c @@ -2,6 +2,7 @@ // Copyright (c) 2015--2017 Intel Corporation. #include +#include #include #include #include @@ -38,6 +39,7 @@ struct dw9714_device { struct v4l2_subdev sd; u16 current_val; struct regulator *vcc; + struct gpio_desc *powerdown_gpio; }; static inline struct dw9714_device *to_dw9714_vcm(struct v4l2_ctrl *ctrl) @@ -145,6 +147,8 @@ static int dw9714_power_up(struct dw9714_device *dw9714_dev) if (ret) return ret; + gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 0); + usleep_range(1000, 2000); return 0; @@ -152,6 +156,8 @@ static int dw9714_power_up(struct dw9714_device *dw9714_dev) static int dw9714_power_down(struct dw9714_device *dw9714_dev) { + gpiod_set_value_cansleep(dw9714_dev->powerdown_gpio, 1); + return regulator_disable(dw9714_dev->vcc); } @@ -169,6 +175,14 @@ static int dw9714_probe(struct i2c_client *client) if (IS_ERR(dw9714_dev->vcc)) return PTR_ERR(dw9714_dev->vcc); + dw9714_dev->powerdown_gpio = devm_gpiod_get_optional(&client->dev, + "powerdown", + GPIOD_OUT_HIGH); + if (IS_ERR(dw9714_dev->powerdown_gpio)) + return dev_err_probe(&client->dev, + PTR_ERR(dw9714_dev->powerdown_gpio), + "could not get powerdown gpio\n"); + rval = dw9714_power_up(dw9714_dev); if (rval) return dev_err_probe(&client->dev, rval, From 5b69a84005b686365e9cacb2ca942271ed74867a Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Wed, 11 Jun 2025 10:48:30 +0200 Subject: [PATCH 138/310] media: i2c: vd55g1: Fix RATE macros not being expressed in bps As a bit rate is expressed in bps, use MEGA instead of HZ_PER_MHZ. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/vd55g1.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 25e2fc88a036..78dd22d9cab0 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -129,8 +129,8 @@ #define VD55G1_FWPATCH_REVISION_MINOR 9 #define VD55G1_XCLK_FREQ_MIN (6 * HZ_PER_MHZ) #define VD55G1_XCLK_FREQ_MAX (27 * HZ_PER_MHZ) -#define VD55G1_MIPI_RATE_MIN (250 * HZ_PER_MHZ) -#define VD55G1_MIPI_RATE_MAX (1200 * HZ_PER_MHZ) +#define VD55G1_MIPI_RATE_MIN (250 * MEGA) +#define VD55G1_MIPI_RATE_MAX (1200 * MEGA) static const u8 patch_array[] = { 0x44, 0x03, 0x09, 0x02, 0xe6, 0x01, 0x42, 0x00, 0xea, 0x01, 0x42, 0x00, From 5931eed35cb632ff8b7e0b0cc91abc6014c64045 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Wed, 11 Jun 2025 10:48:31 +0200 Subject: [PATCH 139/310] media: i2c: vd55g1: Fix return code in vd55g1_enable_streams error path Enable stream was returning success even if an error occurred, fix it by modifying the err_rpm_put return value to -EINVAL. Signed-off-by: Benjamin Mugnier Fixes: e56616d7b23c ("media: i2c: Add driver for ST VD55G1 camera sensor") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/vd55g1.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 78dd22d9cab0..336dc3c85ac9 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -1084,7 +1084,7 @@ static int vd55g1_enable_streams(struct v4l2_subdev *sd, err_rpm_put: pm_runtime_put(sensor->dev); - return 0; + return -EINVAL; } static int vd55g1_disable_streams(struct v4l2_subdev *sd, From df2f8fd91bde57d5d5aca6adddf7e988f2e8c60e Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Wed, 11 Jun 2025 10:48:32 +0200 Subject: [PATCH 140/310] media: i2c: vd55g1: Setup sensor external clock before patching Proper clock configuration is required to advance through FSM states. Prior than this having a different clock value than default sensor's value was used (12 MHz) could prevent the sensor from booting. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/vd55g1.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index 336dc3c85ac9..dec6e3e231d5 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -1038,8 +1038,6 @@ static int vd55g1_enable_streams(struct v4l2_subdev *sd, if (ret < 0) return ret; - vd55g1_write(sensor, VD55G1_REG_EXT_CLOCK, sensor->xclk_freq, &ret); - /* Configure output */ vd55g1_write(sensor, VD55G1_REG_MIPI_DATA_RATE, sensor->mipi_rate, &ret); @@ -1613,6 +1611,9 @@ static int vd55g1_power_on(struct device *dev) goto disable_clock; } + /* Setup clock now to advance through system FSM states */ + vd55g1_write(sensor, VD55G1_REG_EXT_CLOCK, sensor->xclk_freq, &ret); + ret = vd55g1_patch(sensor); if (ret) { dev_err(dev, "Sensor patch failed %d\n", ret); From 3062098a2224a0e90b8a20f7b40063ab7ffd5b19 Mon Sep 17 00:00:00 2001 From: Benjamin Mugnier Date: Wed, 11 Jun 2025 10:48:33 +0200 Subject: [PATCH 141/310] media: i2c: vd55g1: Use first index of mbus codes array as default Factorize code and prevent future errors in case of media bus codes change. Rename VD55G1_DEFAULT_MODE to VD55G1_MODE_DEF to mimic other macros and reduce vd55g1_update_img_pad_format() name to vd55g1_update_pad_fmt() to stay within the 80 characters limit. Signed-off-by: Benjamin Mugnier Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/vd55g1.c | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/drivers/media/i2c/vd55g1.c b/drivers/media/i2c/vd55g1.c index dec6e3e231d5..c0754fd03b1d 100644 --- a/drivers/media/i2c/vd55g1.c +++ b/drivers/media/i2c/vd55g1.c @@ -111,9 +111,9 @@ #define VD55G1_WIDTH 804 #define VD55G1_HEIGHT 704 -#define VD55G1_DEFAULT_MODE 0 +#define VD55G1_MODE_DEF 0 #define VD55G1_NB_GPIOS 4 -#define VD55G1_MEDIA_BUS_FMT_DEF MEDIA_BUS_FMT_Y8_1X8 +#define VD55G1_MBUS_CODE_DEF 0 #define VD55G1_DGAIN_DEF 256 #define VD55G1_AGAIN_DEF 19 #define VD55G1_EXPO_MAX_TERM 64 @@ -883,10 +883,9 @@ static int vd55g1_apply_cold_start(struct vd55g1 *sensor, return ret; } -static void vd55g1_update_img_pad_format(struct vd55g1 *sensor, - const struct vd55g1_mode *mode, - u32 code, - struct v4l2_mbus_framefmt *fmt) +static void vd55g1_update_pad_fmt(struct vd55g1 *sensor, + const struct vd55g1_mode *mode, u32 code, + struct v4l2_mbus_framefmt *fmt) { fmt->code = code; fmt->width = mode->width; @@ -1229,8 +1228,8 @@ static int vd55g1_set_pad_fmt(struct v4l2_subdev *sd, width, height, sd_fmt->format.width, sd_fmt->format.height); - vd55g1_update_img_pad_format(sensor, new_mode, sd_fmt->format.code, - &sd_fmt->format); + vd55g1_update_pad_fmt(sensor, new_mode, sd_fmt->format.code, + &sd_fmt->format); /* * Use binning to maximize the crop rectangle size, and centre it in the @@ -1260,7 +1259,6 @@ static int vd55g1_set_pad_fmt(struct v4l2_subdev *sd, static int vd55g1_init_state(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state) { - unsigned int def_mode = VD55G1_DEFAULT_MODE; struct vd55g1 *sensor = to_vd55g1(sd); struct v4l2_subdev_format fmt = { 0 }; struct v4l2_subdev_route routes[] = { @@ -1277,8 +1275,9 @@ static int vd55g1_init_state(struct v4l2_subdev *sd, if (ret) return ret; - vd55g1_update_img_pad_format(sensor, &vd55g1_supported_modes[def_mode], - VD55G1_MEDIA_BUS_FMT_DEF, &fmt.format); + vd55g1_update_pad_fmt(sensor, &vd55g1_supported_modes[VD55G1_MODE_DEF], + vd55g1_mbus_codes[VD55G1_MBUS_CODE_DEF].code, + &fmt.format); return vd55g1_set_pad_fmt(sd, sd_state, &fmt); } From 43f661a77c3ff117530d90e2e714570f3b4ae2b3 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 19 Jun 2025 23:47:08 +0300 Subject: [PATCH 142/310] media: v4l2-subdev: Limit the number of active routes to V4L2_FRAME_DESC_ENTRY_MAX Drivers that implement routing need to report a frame descriptor accordingly, with up to one entry per route. The number of frame descriptor entries is fixed to V4L2_FRAME_DESC_ENTRY_MAX, currently equal to 8. Multiple drivers therefore limit the number of routes to V4L2_FRAME_DESC_ENTRY_MAX, with a note indicating that the limit should be lifted when frame descriptor entries will be allocated dynamically. Duplicating the check in multiple drivers isn't ideal. Move it to the VIDIOC_SUBDEV_S_ROUTING handling code in the v4l2-subdev core. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Tomi Valkeinen Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-subdev.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index a3074f469b15..c69d1aff701f 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -1004,6 +1004,7 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, struct v4l2_subdev_route *routes = (struct v4l2_subdev_route *)(uintptr_t)routing->routes; struct v4l2_subdev_krouting krouting = {}; + unsigned int num_active_routes = 0; unsigned int i; if (!v4l2_subdev_enable_streams_api) @@ -1041,8 +1042,21 @@ static long subdev_do_ioctl(struct file *file, unsigned int cmd, void *arg, if (!(pads[route->source_pad].flags & MEDIA_PAD_FL_SOURCE)) return -EINVAL; + + if (route->flags & V4L2_SUBDEV_ROUTE_FL_ACTIVE) + num_active_routes++; } + /* + * Drivers that implement routing need to report a frame + * descriptor accordingly, with up to one entry per route. Until + * the frame descriptors entries get allocated dynamically, + * limit the number of active routes to + * V4L2_FRAME_DESC_ENTRY_MAX. + */ + if (num_active_routes > V4L2_FRAME_DESC_ENTRY_MAX) + return -E2BIG; + /* * If the driver doesn't support setting routing, just return * the routing table. From 5fe204033d9d7add4fc125c14ae230befcc17dd7 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 19 Jun 2025 23:47:09 +0300 Subject: [PATCH 143/310] media: i2c: ds90ub913: Drop check on number of active routes The subdev core now limits the number of active routes to V4L2_FRAME_DESC_ENTRY_MAX. Drop the duplicated check in the ds90ub913 driver. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Tomi Valkeinen Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ds90ub913.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/media/i2c/ds90ub913.c b/drivers/media/i2c/ds90ub913.c index c34ffeae5f50..bc74499b0a96 100644 --- a/drivers/media/i2c/ds90ub913.c +++ b/drivers/media/i2c/ds90ub913.c @@ -337,14 +337,6 @@ static int _ub913_set_routing(struct v4l2_subdev *sd, unsigned int i; int ret; - /* - * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until - * frame desc is made dynamically allocated. - */ - - if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) - return -EINVAL; - ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); if (ret) From a3e3fcff07c54ee938a98fb7a311bb4eea6ae1c9 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 19 Jun 2025 23:47:10 +0300 Subject: [PATCH 144/310] media: i2c: ds90ub953: Drop check on number of active routes The subdev core now limits the number of active routes to V4L2_FRAME_DESC_ENTRY_MAX. Drop the duplicated check in the ds90ub953 driver. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Tomi Valkeinen Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ds90ub953.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/media/i2c/ds90ub953.c b/drivers/media/i2c/ds90ub953.c index 242dbf6d48f3..a865bfc89500 100644 --- a/drivers/media/i2c/ds90ub953.c +++ b/drivers/media/i2c/ds90ub953.c @@ -399,14 +399,6 @@ static int _ub953_set_routing(struct v4l2_subdev *sd, }; int ret; - /* - * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until - * frame desc is made dynamically allocated. - */ - - if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) - return -EINVAL; - ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); if (ret) From 137000ce88757781f5604c5dd7076f09e47e9708 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 19 Jun 2025 23:47:11 +0300 Subject: [PATCH 145/310] media: i2c: ds90ub960: Drop check on number of active routes The subdev core now limits the number of active routes to V4L2_FRAME_DESC_ENTRY_MAX. Drop the duplicated check in the ds90ub960 driver. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Tomi Valkeinen Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ds90ub960.c | 8 -------- 1 file changed, 8 deletions(-) diff --git a/drivers/media/i2c/ds90ub960.c b/drivers/media/i2c/ds90ub960.c index 082fc62b0f5b..3156f6d6c6de 100644 --- a/drivers/media/i2c/ds90ub960.c +++ b/drivers/media/i2c/ds90ub960.c @@ -3861,14 +3861,6 @@ static int _ub960_set_routing(struct v4l2_subdev *sd, }; int ret; - /* - * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until - * frame desc is made dynamically allocated. - */ - - if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) - return -E2BIG; - ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1 | V4L2_SUBDEV_ROUTING_NO_SINK_STREAM_MIX); From 35392e855abf7d02ad3b061cbc75c7c7c37f0577 Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Thu, 19 Jun 2025 23:47:12 +0300 Subject: [PATCH 146/310] media: i2c: max96714: Drop check on number of active routes The subdev core now limits the number of active routes to V4L2_FRAME_DESC_ENTRY_MAX. Drop the duplicated check in the max96714 driver. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Tomi Valkeinen Reviewed-by: Lad Prabhakar Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/max96714.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/drivers/media/i2c/max96714.c b/drivers/media/i2c/max96714.c index 3cc1b1ae47d1..e3e625e6f11a 100644 --- a/drivers/media/i2c/max96714.c +++ b/drivers/media/i2c/max96714.c @@ -370,13 +370,6 @@ static int _max96714_set_routing(struct v4l2_subdev *sd, }; int ret; - /* - * Note: we can only support up to V4L2_FRAME_DESC_ENTRY_MAX, until - * frame desc is made dynamically allocated. - */ - if (routing->num_routes > V4L2_FRAME_DESC_ENTRY_MAX) - return -EINVAL; - ret = v4l2_subdev_routing_validate(sd, routing, V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); if (ret) From 3ae896614000902090fa42489a34fa54b720012d Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 26 Aug 2024 12:24:04 +0200 Subject: [PATCH 147/310] media: pisp_be: Drop reference to non-existing function A comment in the pisp_be driver references the pispbe_schedule_internal() function which doesn't exist. Drop it. Reviewed-by: Laurent Pinchart Reviewed-by: Naushir Patuck Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/raspberrypi/pisp_be/pisp_be.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index 7596ae1f7de6..b1449245f394 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -368,10 +368,7 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, ret = pispbe_get_planes_addr(addrs, buf[MAIN_INPUT_NODE], &pispbe->node[MAIN_INPUT_NODE]); if (ret <= 0) { - /* - * This shouldn't happen; pispbe_schedule_internal should insist - * on an input. - */ + /* Shouldn't happen, we have validated an input is available. */ dev_warn(pispbe->dev, "ISP-BE missing input\n"); hw_en->bayer_enables = 0; hw_en->rgb_enables = 0; From a773b6141610e625abedaff29ac674a75c77a609 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 26 Aug 2024 12:29:50 +0200 Subject: [PATCH 148/310] media: pisp_be: Remove config validation from schedule() The config parameters buffer is already validated in pisp_be_validate_config() at .buf_prepare() time. However some of the same validations are also performed at pispbe_schedule() time. In particular the function checks that: 1) config.num_tiles is valid 2) At least one of the BAYER or RGB input is enabled The input config validation is already performed in pisp_be_validate_config() and while job.hw_enables is modified by pispbe_xlate_addrs(), the function only resets the input masks if - there is no input buffer available, but pispbe_prepare_job() fails before calling pispbe_xlate_addrs() in this case - bayer_enable is 0, but in this case rgb_enable is valid as guaranteed by pisp_be_validate_config() - only outputs are reset in rgb_enable For this reasons there is no need to repeat the check at pispbe_schedule() time. The num_tiles validation can be moved to pisp_be_validate_config() as well. As num_tiles is a u32 it can'be be < 0, so change the sanity check accordingly. Reviewed-by: Laurent Pinchart Reviewed-by: Naushir Patuck Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- .../platform/raspberrypi/pisp_be/pisp_be.c | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index b1449245f394..92c452891d6c 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -588,24 +588,6 @@ static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy) pispbe->hw_busy = true; spin_unlock_irqrestore(&pispbe->hw_lock, flags); - if (job.config->num_tiles <= 0 || - job.config->num_tiles > PISP_BACK_END_NUM_TILES || - !((job.hw_enables.bayer_enables | job.hw_enables.rgb_enables) & - PISP_BE_BAYER_ENABLE_INPUT)) { - /* - * Bad job. We can't let it proceed as it could lock up - * the hardware, or worse! - * - * For now, just force num_tiles to 0, which causes the - * H/W to do something bizarre but survivable. It - * increments (started,done) counters by more than 1, - * but we seem to survive... - */ - dev_dbg(pispbe->dev, "Bad job: invalid number of tiles: %u\n", - job.config->num_tiles); - job.config->num_tiles = 0; - } - pispbe_queue_job(pispbe, &job); return; @@ -703,6 +685,13 @@ static int pisp_be_validate_config(struct pispbe_dev *pispbe, return -EIO; } + if (config->num_tiles == 0 || + config->num_tiles > PISP_BACK_END_NUM_TILES) { + dev_dbg(dev, "%s: Invalid number of tiles: %d\n", __func__, + config->num_tiles); + return -EINVAL; + } + /* Ensure output config strides and buffer sizes match the V4L2 formats. */ fmt = &pispbe->node[TDN_OUTPUT_NODE].format; if (bayer_enables & PISP_BE_BAYER_ENABLE_TDN_OUTPUT) { From 972eed08261fb1048afea8e8f7db05b5531eed66 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 5 Aug 2024 16:55:35 +0200 Subject: [PATCH 149/310] media: pisp_be: Split jobs creation and scheduling Currently the 'pispbe_schedule()' function does two things: 1) Tries to assemble a job by inspecting all the video node queues to make sure all the required buffers are available 2) Submit the job to the hardware The pispbe_schedule() function is called at: - video device start_streaming() time - video device qbuf() time - irq handler As assembling a job requires inspecting all queues, it is a rather time consuming operation which is better not run in IRQ context. To avoid executing the time consuming job creation in interrupt context split the job creation and job scheduling in two distinct operations. When a well-formed job is created, append it to the newly introduced 'pispbe->job_queue' where it will be dequeued from by the scheduling routine. As the per-node 'ready_queue' buffer list is only accessed in vb2 ops callbacks, protected by the node->queue_lock mutex, it is not necessary to guard it with a dedicated spinlock so drop it. Also use the spin_lock_irq() variant in all functions not called from an IRQ context where the spin_lock_irqsave() version was used. Reviewed-by: Naushir Patuck Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- .../platform/raspberrypi/pisp_be/pisp_be.c | 161 ++++++++++-------- 1 file changed, 87 insertions(+), 74 deletions(-) diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index 92c452891d6c..ccc6cb99868b 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -161,8 +162,6 @@ struct pispbe_node { struct mutex node_lock; /* vb2_queue lock */ struct mutex queue_lock; - /* Protect pispbe_node->ready_queue and pispbe_buffer->ready_list */ - spinlock_t ready_lock; struct list_head ready_queue; struct vb2_queue queue; struct v4l2_format format; @@ -190,6 +189,8 @@ struct pispbe_hw_enables { /* Records a job configuration and memory addresses. */ struct pispbe_job_descriptor { + struct list_head queue; + struct pispbe_buffer *buffers[PISPBE_NUM_NODES]; dma_addr_t hw_dma_addrs[N_HW_ADDRESSES]; struct pisp_be_tiles_config *config; struct pispbe_hw_enables hw_enables; @@ -215,8 +216,10 @@ struct pispbe_dev { unsigned int sequence; u32 streaming_map; struct pispbe_job queued_job, running_job; - spinlock_t hw_lock; /* protects "hw_busy" flag and streaming_map */ + /* protects "hw_busy" flag, streaming_map and job_queue */ + spinlock_t hw_lock; bool hw_busy; /* non-zero if a job is queued or is being started */ + struct list_head job_queue; int irq; u32 hw_version; u8 done, started; @@ -440,42 +443,48 @@ static void pispbe_xlate_addrs(struct pispbe_dev *pispbe, * For Output0, Output1, Tdn and Stitch, a buffer only needs to be * available if the blocks are enabled in the config. * - * Needs to be called with hw_lock held. + * If all the buffers required to form a job are available, append the + * job descriptor to the job queue to be later queued to the HW. * * Returns 0 if a job has been successfully prepared, < 0 otherwise. */ -static int pispbe_prepare_job(struct pispbe_dev *pispbe, - struct pispbe_job_descriptor *job) +static int pispbe_prepare_job(struct pispbe_dev *pispbe) { + struct pispbe_job_descriptor __free(kfree) *job = NULL; struct pispbe_buffer *buf[PISPBE_NUM_NODES] = {}; + unsigned int streaming_map; unsigned int config_index; struct pispbe_node *node; - unsigned long flags; - lockdep_assert_held(&pispbe->hw_lock); + lockdep_assert_irqs_enabled(); - memset(job, 0, sizeof(struct pispbe_job_descriptor)); + scoped_guard(spinlock_irq, &pispbe->hw_lock) { + static const u32 mask = BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE); - if (((BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE)) & - pispbe->streaming_map) != - (BIT(CONFIG_NODE) | BIT(MAIN_INPUT_NODE))) - return -ENODEV; + if ((pispbe->streaming_map & mask) != mask) + return -ENODEV; + + /* + * Take a copy of streaming_map: nodes activated after this + * point are ignored when preparing this job. + */ + streaming_map = pispbe->streaming_map; + } + + job = kzalloc(sizeof(*job), GFP_KERNEL); + if (!job) + return -ENOMEM; node = &pispbe->node[CONFIG_NODE]; - spin_lock_irqsave(&node->ready_lock, flags); buf[CONFIG_NODE] = list_first_entry_or_null(&node->ready_queue, struct pispbe_buffer, ready_list); - if (buf[CONFIG_NODE]) { - list_del(&buf[CONFIG_NODE]->ready_list); - pispbe->queued_job.buf[CONFIG_NODE] = buf[CONFIG_NODE]; - } - spin_unlock_irqrestore(&node->ready_lock, flags); - - /* Exit early if no config buffer has been queued. */ if (!buf[CONFIG_NODE]) return -ENODEV; + list_del(&buf[CONFIG_NODE]->ready_list); + job->buffers[CONFIG_NODE] = buf[CONFIG_NODE]; + config_index = buf[CONFIG_NODE]->vb.vb2_buf.index; job->config = &pispbe->config[config_index]; job->tiles = pispbe->config_dma_addr + @@ -495,7 +504,7 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, continue; buf[i] = NULL; - if (!(pispbe->streaming_map & BIT(i))) + if (!(streaming_map & BIT(i))) continue; if ((!(rgb_en & PISP_BE_RGB_ENABLE_OUTPUT0) && @@ -522,25 +531,28 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, node = &pispbe->node[i]; /* Pull a buffer from each V4L2 queue to form the queued job */ - spin_lock_irqsave(&node->ready_lock, flags); buf[i] = list_first_entry_or_null(&node->ready_queue, struct pispbe_buffer, ready_list); if (buf[i]) { list_del(&buf[i]->ready_list); - pispbe->queued_job.buf[i] = buf[i]; + job->buffers[i] = buf[i]; } - spin_unlock_irqrestore(&node->ready_lock, flags); if (!buf[i] && !ignore_buffers) goto err_return_buffers; } - pispbe->queued_job.valid = true; - /* Convert buffers to DMA addresses for the hardware */ pispbe_xlate_addrs(pispbe, job, buf); + scoped_guard(spinlock_irq, &pispbe->hw_lock) { + list_add_tail(&job->queue, &pispbe->job_queue); + } + + /* Set job to NULL to avoid automatic release due to __free(). */ + job = NULL; + return 0; err_return_buffers: @@ -551,33 +563,37 @@ static int pispbe_prepare_job(struct pispbe_dev *pispbe, continue; /* Return the buffer to the ready_list queue */ - spin_lock_irqsave(&n->ready_lock, flags); list_add(&buf[i]->ready_list, &n->ready_queue); - spin_unlock_irqrestore(&n->ready_lock, flags); } - memset(&pispbe->queued_job, 0, sizeof(pispbe->queued_job)); - return -ENODEV; } static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy) { - struct pispbe_job_descriptor job; - unsigned long flags; - int ret; + struct pispbe_job_descriptor *job; - spin_lock_irqsave(&pispbe->hw_lock, flags); + scoped_guard(spinlock_irqsave, &pispbe->hw_lock) { + if (clear_hw_busy) + pispbe->hw_busy = false; - if (clear_hw_busy) - pispbe->hw_busy = false; + if (pispbe->hw_busy) + return; - if (pispbe->hw_busy) - goto unlock_and_return; + job = list_first_entry_or_null(&pispbe->job_queue, + struct pispbe_job_descriptor, + queue); + if (!job) + return; - ret = pispbe_prepare_job(pispbe, &job); - if (ret) - goto unlock_and_return; + list_del(&job->queue); + + for (unsigned int i = 0; i < PISPBE_NUM_NODES; i++) + pispbe->queued_job.buf[i] = job->buffers[i]; + pispbe->queued_job.valid = true; + + pispbe->hw_busy = true; + } /* * We can kick the job off without the hw_lock, as this can @@ -585,16 +601,8 @@ static void pispbe_schedule(struct pispbe_dev *pispbe, bool clear_hw_busy) * only when the following job has been queued and an interrupt * is rised. */ - pispbe->hw_busy = true; - spin_unlock_irqrestore(&pispbe->hw_lock, flags); - - pispbe_queue_job(pispbe, &job); - - return; - -unlock_and_return: - /* No job has been queued, just release the lock and return. */ - spin_unlock_irqrestore(&pispbe->hw_lock, flags); + pispbe_queue_job(pispbe, job); + kfree(job); } static void pispbe_isr_jobdone(struct pispbe_dev *pispbe, @@ -846,18 +854,16 @@ static void pispbe_node_buffer_queue(struct vb2_buffer *buf) container_of(vbuf, struct pispbe_buffer, vb); struct pispbe_node *node = vb2_get_drv_priv(buf->vb2_queue); struct pispbe_dev *pispbe = node->pispbe; - unsigned long flags; dev_dbg(pispbe->dev, "%s: for node %s\n", __func__, NODE_NAME(node)); - spin_lock_irqsave(&node->ready_lock, flags); list_add_tail(&buffer->ready_list, &node->ready_queue); - spin_unlock_irqrestore(&node->ready_lock, flags); /* * Every time we add a buffer, check if there's now some work for the hw * to do. */ - pispbe_schedule(pispbe, false); + if (!pispbe_prepare_job(pispbe)) + pispbe_schedule(pispbe, false); } static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) @@ -865,17 +871,16 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) struct pispbe_node *node = vb2_get_drv_priv(q); struct pispbe_dev *pispbe = node->pispbe; struct pispbe_buffer *buf, *tmp; - unsigned long flags; int ret; ret = pm_runtime_resume_and_get(pispbe->dev); if (ret < 0) goto err_return_buffers; - spin_lock_irqsave(&pispbe->hw_lock, flags); - node->pispbe->streaming_map |= BIT(node->id); - node->pispbe->sequence = 0; - spin_unlock_irqrestore(&pispbe->hw_lock, flags); + scoped_guard(spinlock_irq, &pispbe->hw_lock) { + node->pispbe->streaming_map |= BIT(node->id); + node->pispbe->sequence = 0; + } dev_dbg(pispbe->dev, "%s: for node %s (count %u)\n", __func__, NODE_NAME(node), count); @@ -883,17 +888,16 @@ static int pispbe_node_start_streaming(struct vb2_queue *q, unsigned int count) node->pispbe->streaming_map); /* Maybe we're ready to run. */ - pispbe_schedule(pispbe, false); + if (!pispbe_prepare_job(pispbe)) + pispbe_schedule(pispbe, false); return 0; err_return_buffers: - spin_lock_irqsave(&pispbe->hw_lock, flags); list_for_each_entry_safe(buf, tmp, &node->ready_queue, ready_list) { list_del(&buf->ready_list); vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_QUEUED); } - spin_unlock_irqrestore(&pispbe->hw_lock, flags); return ret; } @@ -902,8 +906,9 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q) { struct pispbe_node *node = vb2_get_drv_priv(q); struct pispbe_dev *pispbe = node->pispbe; + struct pispbe_job_descriptor *job, *temp; struct pispbe_buffer *buf; - unsigned long flags; + LIST_HEAD(tmp_list); /* * Now this is a bit awkward. In a simple M2M device we could just wait @@ -915,11 +920,7 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q) * This may return buffers out of order. */ dev_dbg(pispbe->dev, "%s: for node %s\n", __func__, NODE_NAME(node)); - spin_lock_irqsave(&pispbe->hw_lock, flags); do { - unsigned long flags1; - - spin_lock_irqsave(&node->ready_lock, flags1); buf = list_first_entry_or_null(&node->ready_queue, struct pispbe_buffer, ready_list); @@ -927,15 +928,26 @@ static void pispbe_node_stop_streaming(struct vb2_queue *q) list_del(&buf->ready_list); vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); } - spin_unlock_irqrestore(&node->ready_lock, flags1); } while (buf); - spin_unlock_irqrestore(&pispbe->hw_lock, flags); vb2_wait_for_all_buffers(&node->queue); - spin_lock_irqsave(&pispbe->hw_lock, flags); + spin_lock_irq(&pispbe->hw_lock); pispbe->streaming_map &= ~BIT(node->id); - spin_unlock_irqrestore(&pispbe->hw_lock, flags); + + if (pispbe->streaming_map == 0) { + /* + * If all nodes have stopped streaming release all jobs + * without holding the lock. + */ + list_splice_init(&pispbe->job_queue, &tmp_list); + } + spin_unlock_irq(&pispbe->hw_lock); + + list_for_each_entry_safe(job, temp, &tmp_list, queue) { + list_del(&job->queue); + kfree(job); + } pm_runtime_mark_last_busy(pispbe->dev); pm_runtime_put_autosuspend(pispbe->dev); @@ -1393,7 +1405,6 @@ static int pispbe_init_node(struct pispbe_dev *pispbe, unsigned int id) mutex_init(&node->node_lock); mutex_init(&node->queue_lock); INIT_LIST_HEAD(&node->ready_queue); - spin_lock_init(&node->ready_lock); node->format.type = node->buf_type; pispbe_node_def_fmt(node); @@ -1677,6 +1688,8 @@ static int pispbe_probe(struct platform_device *pdev) if (!pispbe) return -ENOMEM; + INIT_LIST_HEAD(&pispbe->job_queue); + dev_set_drvdata(&pdev->dev, pispbe); pispbe->dev = &pdev->dev; platform_set_drvdata(pdev, pispbe); From e9bb2eacc7222ff8210903eb3b7d56709cc53228 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Mon, 26 Aug 2024 15:30:54 +0200 Subject: [PATCH 150/310] media: pisp_be: Fix pm_runtime underrun in probe During the probe() routine, the PiSP BE driver needs to power up the interface in order to identify and initialize the hardware. The driver resumes the interface by calling the pispbe_runtime_resume() function directly, without going through the pm_runtime helpers, but later suspends it by calling pm_runtime_put_autosuspend(). This causes a PM usage count imbalance at probe time, notified by the runtime_pm framework with the below message in the system log: pispbe 1000880000.pisp_be: Runtime PM usage count underflow! Fix this by resuming the interface using the pm runtime helpers instead of calling the resume function directly and use the pm_runtime framework in the probe() error path. While at it, remove manual suspend of the interface in the remove() function. The driver cannot be unloaded if in use, so simply disable runtime pm. To simplify the implementation, make the driver depend on PM as the RPI5 platform where the ISP is integrated in uses the PM framework by default. Fixes: 12187bd5d4f8 ("media: raspberrypi: Add support for PiSP BE") Cc: stable@vger.kernel.org Tested-by: Naushir Patuck Reviewed-by: Naushir Patuck Reviewed-by: Laurent Pinchart Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/raspberrypi/pisp_be/Kconfig | 1 + drivers/media/platform/raspberrypi/pisp_be/pisp_be.c | 5 ++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/raspberrypi/pisp_be/Kconfig b/drivers/media/platform/raspberrypi/pisp_be/Kconfig index 46765a2e4c4d..a9e51fd94aad 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/Kconfig +++ b/drivers/media/platform/raspberrypi/pisp_be/Kconfig @@ -3,6 +3,7 @@ config VIDEO_RASPBERRYPI_PISP_BE depends on V4L_PLATFORM_DRIVERS depends on VIDEO_DEV depends on ARCH_BCM2835 || COMPILE_TEST + depends on PM select VIDEO_V4L2_SUBDEV_API select MEDIA_CONTROLLER select VIDEOBUF2_DMA_CONTIG diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index ccc6cb99868b..be794a123620 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -1725,7 +1725,7 @@ static int pispbe_probe(struct platform_device *pdev) pm_runtime_use_autosuspend(pispbe->dev); pm_runtime_enable(pispbe->dev); - ret = pispbe_runtime_resume(pispbe->dev); + ret = pm_runtime_resume_and_get(pispbe->dev); if (ret) goto pm_runtime_disable_err; @@ -1747,7 +1747,7 @@ static int pispbe_probe(struct platform_device *pdev) disable_devs_err: pispbe_destroy_devices(pispbe); pm_runtime_suspend_err: - pispbe_runtime_suspend(pispbe->dev); + pm_runtime_put(pispbe->dev); pm_runtime_disable_err: pm_runtime_dont_use_autosuspend(pispbe->dev); pm_runtime_disable(pispbe->dev); @@ -1761,7 +1761,6 @@ static void pispbe_remove(struct platform_device *pdev) pispbe_destroy_devices(pispbe); - pispbe_runtime_suspend(pispbe->dev); pm_runtime_dont_use_autosuspend(pispbe->dev); pm_runtime_disable(pispbe->dev); } From aa89281bbc0b61610c96074c6390aed44474ebd0 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Thu, 12 Jun 2025 16:58:21 +0200 Subject: [PATCH 151/310] media: pisp_be: Use clamp() and define max sizes Use the clamp() function from minmax.h and provide a define for the max sizes as they will be used in subsequent patches. Reviewed-by: Daniel Scally Reviewed-by: Stefan Klug Signed-off-by: Jacopo Mondi Signed-off-by: Hans Verkuil --- drivers/media/platform/raspberrypi/pisp_be/pisp_be.c | 11 +++++++---- include/uapi/linux/media/raspberrypi/pisp_be_config.h | 9 +++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c index be794a123620..b30891718d8d 100644 --- a/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c +++ b/drivers/media/platform/raspberrypi/pisp_be/pisp_be.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -1112,10 +1113,12 @@ static void pispbe_try_format(struct v4l2_format *f, struct pispbe_node *node) f->fmt.pix_mp.pixelformat = fmt->fourcc; f->fmt.pix_mp.num_planes = fmt->num_planes; f->fmt.pix_mp.field = V4L2_FIELD_NONE; - f->fmt.pix_mp.width = max(min(f->fmt.pix_mp.width, 65536u), - PISP_BACK_END_MIN_TILE_WIDTH); - f->fmt.pix_mp.height = max(min(f->fmt.pix_mp.height, 65536u), - PISP_BACK_END_MIN_TILE_HEIGHT); + f->fmt.pix_mp.width = clamp(f->fmt.pix_mp.width, + PISP_BACK_END_MIN_TILE_WIDTH, + PISP_BACK_END_MAX_TILE_WIDTH); + f->fmt.pix_mp.height = clamp(f->fmt.pix_mp.height, + PISP_BACK_END_MIN_TILE_HEIGHT, + PISP_BACK_END_MAX_TILE_HEIGHT); /* * Fill in the actual colour space when the requested one was diff --git a/include/uapi/linux/media/raspberrypi/pisp_be_config.h b/include/uapi/linux/media/raspberrypi/pisp_be_config.h index cbeb714f4d61..2ad3b90684d7 100644 --- a/include/uapi/linux/media/raspberrypi/pisp_be_config.h +++ b/include/uapi/linux/media/raspberrypi/pisp_be_config.h @@ -21,10 +21,11 @@ /* preferred byte alignment for outputs */ #define PISP_BACK_END_OUTPUT_MAX_ALIGN 64u -/* minimum allowed tile width anywhere in the pipeline */ -#define PISP_BACK_END_MIN_TILE_WIDTH 16u -/* minimum allowed tile width anywhere in the pipeline */ -#define PISP_BACK_END_MIN_TILE_HEIGHT 16u +/* minimum allowed tile sizes anywhere in the pipeline */ +#define PISP_BACK_END_MIN_TILE_WIDTH 16u +#define PISP_BACK_END_MIN_TILE_HEIGHT 16u +#define PISP_BACK_END_MAX_TILE_WIDTH 65536u +#define PISP_BACK_END_MAX_TILE_HEIGHT 65536u #define PISP_BACK_END_NUM_OUTPUTS 2 #define PISP_BACK_END_HOG_OUTPUT 1 From 095e5d400cbf7df3f84fbdca08bfe9abb5a4f3ee Mon Sep 17 00:00:00 2001 From: Tommaso Merciai Date: Mon, 26 May 2025 09:52:33 +0200 Subject: [PATCH 152/310] media: rzg2l-cru: Fix typo in rzg2l_cru_of_id_table struct Correct the misnamed .data member for the RZ/G2L CRU. Rename `rzgl2_cru_info` to `rzg2l_cru_info` to match the intended naming convention. Signed-off-by: Tommaso Merciai Reviewed-by: Geert Uytterhoeven Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250526075236.13489-1-tommaso.merciai.xr@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c index 5fa73ab2db53..2af3d976e2c8 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -403,7 +403,7 @@ static const u16 rzg2l_cru_regs[] = { [ICnDMR] = 0x26c, }; -static const struct rzg2l_cru_info rzgl2_cru_info = { +static const struct rzg2l_cru_info rzg2l_cru_info = { .max_width = 2800, .max_height = 4095, .image_conv = ICnMC, @@ -422,7 +422,7 @@ static const struct of_device_id rzg2l_cru_of_id_table[] = { }, { .compatible = "renesas,rzg2l-cru", - .data = &rzgl2_cru_info, + .data = &rzg2l_cru_info, }, { /* sentinel */ } }; From 8b7b5e1bfb72c52dd2cfd82cc14dc4e66c6b94a9 Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 29 Apr 2025 10:16:08 +0100 Subject: [PATCH 153/310] media: renesas: rzg2l-cru: Simplify FIFO empty check Collapse FIFO empty helper into a single return statement by removing the redundant `if (amnfifopntr_w == amnfifopntr_r_y) return true;` path. Make `rzg2l_fifo_empty()` directly return `amnfifopntr_w == amnfifopntr_r_y` to improve readability without changing behavior. Reported-by: Dan Carpenter Closes: https://lore.kernel.org/all/aAtQThCibZCROETx@stanley.mountain/ Signed-off-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Reviewed-by: Geert Uytterhoeven Reviewed-by: Tommaso Merciai Tested-by: Tommaso Merciai Link: https://lore.kernel.org/r/20250429091609.9947-2-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 067c6af14e95..3d0810b3c35e 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -345,8 +345,6 @@ bool rzg2l_fifo_empty(struct rzg2l_cru_dev *cru) amnfifopntr_w = amnfifopntr & AMnFIFOPNTR_FIFOWPNTR; amnfifopntr_r_y = (amnfifopntr & AMnFIFOPNTR_FIFORPNTR_Y) >> 16; - if (amnfifopntr_w == amnfifopntr_r_y) - return true; return amnfifopntr_w == amnfifopntr_r_y; } From e5dd01ea96107ff5fc3f13b333302292ea2e627f Mon Sep 17 00:00:00 2001 From: Lad Prabhakar Date: Tue, 29 Apr 2025 10:16:09 +0100 Subject: [PATCH 154/310] media: renesas: rzg2l-cru: Fix typo in rzg3e_fifo_empty name Correct the misnamed FIFO-empty helper for the RZ/G3E CRU. Rename `rz3e_fifo_empty` to `rzg3e_fifo_empty` to match the intended naming convention. Reported-by: Biju Das Closes: https://lore.kernel.org/all/TY3PR01MB11346E57A3DF8D8A90A405E4686812@TY3PR01MB11346.jpnprd01.prod.outlook.com/ Signed-off-by: Lad Prabhakar Reviewed-by: Geert Uytterhoeven Reviewed-by: Tommaso Merciai Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250429091609.9947-3-prabhakar.mahadev-lad.rj@bp.renesas.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c | 2 +- drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h | 2 +- drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c index 2af3d976e2c8..806acc8f9728 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-core.c @@ -366,7 +366,7 @@ static const struct rzg2l_cru_info rzg3e_cru_info = { .irq_handler = rzg3e_cru_irq, .enable_interrupts = rzg3e_cru_enable_interrupts, .disable_interrupts = rzg3e_cru_disable_interrupts, - .fifo_empty = rz3e_fifo_empty, + .fifo_empty = rzg3e_fifo_empty, .csi_setup = rzg3e_cru_csi2_setup, }; diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h index c30f3b281284..56feda6e6207 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -199,7 +199,7 @@ void rzg3e_cru_enable_interrupts(struct rzg2l_cru_dev *cru); void rzg3e_cru_disable_interrupts(struct rzg2l_cru_dev *cru); bool rzg2l_fifo_empty(struct rzg2l_cru_dev *cru); -bool rz3e_fifo_empty(struct rzg2l_cru_dev *cru); +bool rzg3e_fifo_empty(struct rzg2l_cru_dev *cru); void rzg2l_cru_csi2_setup(struct rzg2l_cru_dev *cru, const struct rzg2l_cru_ip_format *ip_fmt, u8 csi_vc); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 3d0810b3c35e..ab35caf97c51 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -323,7 +323,7 @@ static int rzg2l_cru_initialize_image_conv(struct rzg2l_cru_dev *cru, return 0; } -bool rz3e_fifo_empty(struct rzg2l_cru_dev *cru) +bool rzg3e_fifo_empty(struct rzg2l_cru_dev *cru) { u32 amnfifopntr = rzg2l_cru_read(cru, AMnFIFOPNTR); From 9f0fb0309485e96d5b45e71e901cd994be4d63dc Mon Sep 17 00:00:00 2001 From: Laurent Pinchart Date: Mon, 16 Jun 2025 16:46:38 +0300 Subject: [PATCH 155/310] media: vsp1: Use lockdep assertions to enforce documented conventions A few functions have documented locking conventions. Documentation is nice, but runtime checks are better. Enforce the conventions with lockdep assertions. Signed-off-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Reviewed-by: Kieran Bingham Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/vsp1/vsp1_dl.c | 5 +++++ drivers/media/platform/renesas/vsp1/vsp1_pipe.c | 3 +++ 2 files changed, 8 insertions(+) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_dl.c b/drivers/media/platform/renesas/vsp1/vsp1_dl.c index c660f8539ff5..d732b4ed1180 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_dl.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_dl.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -612,6 +613,8 @@ struct vsp1_dl_list *vsp1_dl_list_get(struct vsp1_dl_manager *dlm) struct vsp1_dl_list *dl = NULL; unsigned long flags; + lockdep_assert_not_held(&dlm->lock); + spin_lock_irqsave(&dlm->lock, flags); if (!list_empty(&dlm->free)) { @@ -639,6 +642,8 @@ static void __vsp1_dl_list_put(struct vsp1_dl_list *dl) if (!dl) return; + lockdep_assert_held(&dl->dlm->lock); + /* * Release any linked display-lists which were chained for a single * hardware operation. diff --git a/drivers/media/platform/renesas/vsp1/vsp1_pipe.c b/drivers/media/platform/renesas/vsp1/vsp1_pipe.c index 3cbb768cf6ad..5d769cc42fe1 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_pipe.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_pipe.c @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -473,6 +474,8 @@ void vsp1_pipeline_run(struct vsp1_pipeline *pipe) { struct vsp1_device *vsp1 = pipe->output->entity.vsp1; + lockdep_assert_held(&pipe->irqlock); + if (pipe->state == VSP1_PIPELINE_STOPPED) { vsp1_write(vsp1, VI6_CMD(pipe->output->entity.index), VI6_CMD_STRCMD); From f57e365573757ea2752d0c17cb58cdfdd61a43e6 Mon Sep 17 00:00:00 2001 From: Jacopo Mondi Date: Wed, 18 Jun 2025 17:42:47 +0200 Subject: [PATCH 156/310] media: vsp1: Add missing export.h As reported by the Kernel Test Robot, the newly merged vspx driver exports a few symbols but doesn't include the export.h header. While at it, include the header file in vsp1_drm.c which exports symbols as well. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202506181950.r9PRdV59-lkp@intel.com/ Signed-off-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250618-vspx-include-export-v1-1-95a2da4ec465@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/vsp1/vsp1_drm.c | 1 + drivers/media/platform/renesas/vsp1/vsp1_vspx.c | 1 + 2 files changed, 2 insertions(+) diff --git a/drivers/media/platform/renesas/vsp1/vsp1_drm.c b/drivers/media/platform/renesas/vsp1/vsp1_drm.c index fe55e8747b05..15d266439564 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_drm.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_drm.c @@ -9,6 +9,7 @@ #include #include +#include #include #include diff --git a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c index 6551d63ba387..a754b92232bd 100644 --- a/drivers/media/platform/renesas/vsp1/vsp1_vspx.c +++ b/drivers/media/platform/renesas/vsp1/vsp1_vspx.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include #include From 78584431e2cea6b60909cfa23c90ac8b33ab4198 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 30 Jun 2025 23:27:34 +0100 Subject: [PATCH 157/310] media: v4l2: Add Renesas Camera Receiver Unit pixel formats The Renesas Camera Receiver Unit in the RZ/V2H SoC can output RAW data captured from an image sensor without conversion to an RGB/YUV format. In that case the data are packed into 64-bit blocks, with a variable amount of padding in the most significant bits depending on the bitdepth of the data. Add new V4L2 pixel format codes for the new formats, along with documentation to describe them. Reviewed-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Reviewed-by: Jacopo Mondi Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250630222734.2712390-1-dan.scally@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/pixfmt-bayer.rst | 1 + .../media/v4l/pixfmt-rawnn-cru.rst | 143 ++++++++++++++++++ drivers/media/v4l2-core/v4l2-common.c | 6 + drivers/media/v4l2-core/v4l2-ioctl.c | 4 + include/uapi/linux/videodev2.h | 6 + 5 files changed, 160 insertions(+) create mode 100644 Documentation/userspace-api/media/v4l/pixfmt-rawnn-cru.rst diff --git a/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst b/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst index ed3eb432967d..b5ca501842b0 100644 --- a/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst +++ b/Documentation/userspace-api/media/v4l/pixfmt-bayer.rst @@ -19,6 +19,7 @@ orders. See also `the Wikipedia article on Bayer filter .. toctree:: :maxdepth: 1 + pixfmt-rawnn-cru pixfmt-srggb8 pixfmt-srggb8-pisp-comp pixfmt-srggb10 diff --git a/Documentation/userspace-api/media/v4l/pixfmt-rawnn-cru.rst b/Documentation/userspace-api/media/v4l/pixfmt-rawnn-cru.rst new file mode 100644 index 000000000000..db81f1cfe0f5 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/pixfmt-rawnn-cru.rst @@ -0,0 +1,143 @@ +.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later + +.. _v4l2-pix-fmt-raw-cru10: +.. _v4l2-pix-fmt-raw-cru12: +.. _v4l2-pix-fmt-raw-cru14: +.. _v4l2-pix-fmt-raw-cru20: + +********************************************************************************************************************************** +V4L2_PIX_FMT_RAW_CRU10 ('CR10'), V4L2_PIX_FMT_RAW_CRU12 ('CR12'), V4L2_PIX_FMT_RAW_CRU14 ('CR14'), V4L2_PIX_FMT_RAW_CRU20 ('CR20') +********************************************************************************************************************************** + +=============================================================== +Renesas RZ/V2H Camera Receiver Unit 64-bit packed pixel formats +=============================================================== + +| V4L2_PIX_FMT_RAW_CRU10 (CR10) +| V4L2_PIX_FMT_RAW_CRU12 (CR12) +| V4L2_PIX_FMT_RAW_CRU14 (CR14) +| V4L2_PIX_FMT_RAW_CRU20 (CR20) + +Description +=========== + +These pixel formats are some of the RAW outputs for the Camera Receiver Unit in +the Renesas RZ/V2H SoC. They are raw formats which pack pixels contiguously into +64-bit units, with the 4 or 8 most significant bits padded. + +**Byte Order** + +.. flat-table:: RAW formats + :header-rows: 2 + :stub-columns: 0 + :widths: 36 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 + :fill-cells: + + * - :rspan:`1` Pixel Format Code + - :cspan:`63` Data organization + * - 63 + - 62 + - 61 + - 60 + - 59 + - 58 + - 57 + - 56 + - 55 + - 54 + - 53 + - 52 + - 51 + - 50 + - 49 + - 48 + - 47 + - 46 + - 45 + - 44 + - 43 + - 42 + - 41 + - 40 + - 39 + - 38 + - 37 + - 36 + - 35 + - 34 + - 33 + - 32 + - 31 + - 30 + - 29 + - 28 + - 27 + - 26 + - 25 + - 24 + - 23 + - 22 + - 21 + - 20 + - 19 + - 18 + - 17 + - 16 + - 15 + - 14 + - 13 + - 12 + - 11 + - 10 + - 9 + - 8 + - 7 + - 6 + - 5 + - 4 + - 3 + - 2 + - 1 + - 0 + * - V4L2_PIX_FMT_RAW_CRU10 + - 0 + - 0 + - 0 + - 0 + - :cspan:`9` P5 + - :cspan:`9` P4 + - :cspan:`9` P3 + - :cspan:`9` P2 + - :cspan:`9` P1 + - :cspan:`9` P0 + * - V4L2_PIX_FMT_RAW_CRU12 + - 0 + - 0 + - 0 + - 0 + - :cspan:`11` P4 + - :cspan:`11` P3 + - :cspan:`11` P2 + - :cspan:`11` P1 + - :cspan:`11` P0 + * - V4L2_PIX_FMT_RAW_CRU14 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - :cspan:`13` P3 + - :cspan:`13` P2 + - :cspan:`13` P1 + - :cspan:`13` P0 + * - V4L2_PIX_FMT_RAW_CRU20 + - 0 + - 0 + - 0 + - 0 + - :cspan:`19` P2 + - :cspan:`19` P1 + - :cspan:`19` P0 diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index e1fc8fe43b74..df40756ac8f6 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -344,6 +344,12 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_SGBRG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGRBG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SRGGB12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + + /* Renesas Camera Data Receiver Unit formats, bayer order agnostic */ + { .format = V4L2_PIX_FMT_RAW_CRU10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 6, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_RAW_CRU12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 5, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_RAW_CRU14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_RAW_CRU20, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 3, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, }; unsigned int i; diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 650dc1956f73..be94a79b976e 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1413,6 +1413,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_SGBRG10DPCM8: descr = "8-bit Bayer GBGB/RGRG (DPCM)"; break; case V4L2_PIX_FMT_SGRBG10DPCM8: descr = "8-bit Bayer GRGR/BGBG (DPCM)"; break; case V4L2_PIX_FMT_SRGGB10DPCM8: descr = "8-bit Bayer RGRG/GBGB (DPCM)"; break; + case V4L2_PIX_FMT_RAW_CRU10: descr = "10-bit Raw CRU Packed"; break; case V4L2_PIX_FMT_SBGGR12: descr = "12-bit Bayer BGBG/GRGR"; break; case V4L2_PIX_FMT_SGBRG12: descr = "12-bit Bayer GBGB/RGRG"; break; case V4L2_PIX_FMT_SGRBG12: descr = "12-bit Bayer GRGR/BGBG"; break; @@ -1421,6 +1422,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_SGBRG12P: descr = "12-bit Bayer GBGB/RGRG Packed"; break; case V4L2_PIX_FMT_SGRBG12P: descr = "12-bit Bayer GRGR/BGBG Packed"; break; case V4L2_PIX_FMT_SRGGB12P: descr = "12-bit Bayer RGRG/GBGB Packed"; break; + case V4L2_PIX_FMT_RAW_CRU12: descr = "12-bit Raw CRU Packed"; break; case V4L2_PIX_FMT_SBGGR14: descr = "14-bit Bayer BGBG/GRGR"; break; case V4L2_PIX_FMT_SGBRG14: descr = "14-bit Bayer GBGB/RGRG"; break; case V4L2_PIX_FMT_SGRBG14: descr = "14-bit Bayer GRGR/BGBG"; break; @@ -1429,10 +1431,12 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_PIX_FMT_SGBRG14P: descr = "14-bit Bayer GBGB/RGRG Packed"; break; case V4L2_PIX_FMT_SGRBG14P: descr = "14-bit Bayer GRGR/BGBG Packed"; break; case V4L2_PIX_FMT_SRGGB14P: descr = "14-bit Bayer RGRG/GBGB Packed"; break; + case V4L2_PIX_FMT_RAW_CRU14: descr = "14-bit Raw CRU Packed"; break; case V4L2_PIX_FMT_SBGGR16: descr = "16-bit Bayer BGBG/GRGR"; break; case V4L2_PIX_FMT_SGBRG16: descr = "16-bit Bayer GBGB/RGRG"; break; case V4L2_PIX_FMT_SGRBG16: descr = "16-bit Bayer GRGR/BGBG"; break; case V4L2_PIX_FMT_SRGGB16: descr = "16-bit Bayer RGRG/GBGB"; break; + case V4L2_PIX_FMT_RAW_CRU20: descr = "14-bit Raw CRU Packed"; break; case V4L2_PIX_FMT_SN9C20X_I420: descr = "GSPCA SN9C20X I420"; break; case V4L2_PIX_FMT_SPCA501: descr = "GSPCA SPCA501"; break; case V4L2_PIX_FMT_SPCA505: descr = "GSPCA SPCA505"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 9e3b366d5fc7..6f7bd38dd5aa 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -840,6 +840,12 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_PISP_COMP2_BGGR v4l2_fourcc('P', 'C', '2', 'B') /* PiSP 8-bit mode 2 compressed BGGR bayer */ #define V4L2_PIX_FMT_PISP_COMP2_MONO v4l2_fourcc('P', 'C', '2', 'M') /* PiSP 8-bit mode 2 compressed monochrome */ +/* Renesas RZ/V2H CRU packed formats. 64-bit units with contiguous pixels */ +#define V4L2_PIX_FMT_RAW_CRU10 v4l2_fourcc('C', 'R', '1', '0') +#define V4L2_PIX_FMT_RAW_CRU12 v4l2_fourcc('C', 'R', '1', '2') +#define V4L2_PIX_FMT_RAW_CRU14 v4l2_fourcc('C', 'R', '1', '4') +#define V4L2_PIX_FMT_RAW_CRU20 v4l2_fourcc('C', 'R', '2', '0') + /* SDR formats - used only for Software Defined Radio devices */ #define V4L2_SDR_FMT_CU8 v4l2_fourcc('C', 'U', '0', '8') /* IQ u8 */ #define V4L2_SDR_FMT_CU16LE v4l2_fourcc('C', 'U', '1', '6') /* IQ u16le */ From d225bdb6ede7595cddead722503aa9ec7b0a646d Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 25 Jun 2025 10:20:28 +0100 Subject: [PATCH 158/310] media: rzg2l-cru: Add vidioc_enum_framesizes() Add a callback to implement the VIDIOC_ENUM_FRAMESIZES ioctl for the CRU driver. Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250625-rzg2l-cru-v6-2-a9099ed26c14@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rzg2l-cru/rzg2l-video.c | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index ab35caf97c51..042a6bd44697 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -1029,6 +1029,31 @@ static int rzg2l_cru_enum_fmt_vid_cap(struct file *file, void *priv, return 0; } +static int rzg2l_cru_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + struct rzg2l_cru_dev *cru = video_drvdata(file); + const struct rzg2l_cru_info *info = cru->info; + const struct rzg2l_cru_ip_format *fmt; + + if (fsize->index) + return -EINVAL; + + fmt = rzg2l_cru_ip_format_to_fmt(fsize->pixel_format); + if (!fmt) + return -EINVAL; + + fsize->type = V4L2_FRMIVAL_TYPE_CONTINUOUS; + fsize->stepwise.min_width = RZG2L_CRU_MIN_INPUT_WIDTH; + fsize->stepwise.max_width = info->max_width; + fsize->stepwise.step_width = 1; + fsize->stepwise.min_height = RZG2L_CRU_MIN_INPUT_HEIGHT; + fsize->stepwise.max_height = info->max_height; + fsize->stepwise.step_height = 1; + + return 0; +} + static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { .vidioc_querycap = rzg2l_cru_querycap, .vidioc_try_fmt_vid_cap = rzg2l_cru_try_fmt_vid_cap, @@ -1045,6 +1070,7 @@ static const struct v4l2_ioctl_ops rzg2l_cru_ioctl_ops = { .vidioc_prepare_buf = vb2_ioctl_prepare_buf, .vidioc_streamon = vb2_ioctl_streamon, .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_enum_framesizes = rzg2l_cru_enum_framesizes, }; /* ----------------------------------------------------------------------------- From ac261abe73e3db462340645a84291ec384df7191 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 25 Jun 2025 10:20:29 +0100 Subject: [PATCH 159/310] media: platform: rzg2l-cru: Use v4l2_get_link_freq() The rzg2l_csi2_calc_mbps() function currently tries to calculate the link frequency for a CSI2 bus using the V4L2_CID_PIXEL_RATE control of the remote subdevice. Switch the function to v4l2_get_link_freq() which correctly targets V4L2_CID_LINK_FREQ before falling back on V4L2_CID_PIXEL_RATE if the former is unavailable. Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Reviewed-by: Lad Prabhakar Tested-by: Lad Prabhakar Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250625-rzg2l-cru-v6-3-a9099ed26c14@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c index 9243306e2aa9..cec165551183 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -282,15 +282,18 @@ static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) const struct rzg2l_csi2_format *format; const struct v4l2_mbus_framefmt *fmt; struct v4l2_subdev_state *state; - struct v4l2_ctrl *ctrl; + struct media_pad *remote_pad; u64 mbps; + s64 ret; - /* Read the pixel rate control from remote. */ - ctrl = v4l2_ctrl_find(source->ctrl_handler, V4L2_CID_PIXEL_RATE); - if (!ctrl) { - dev_err(csi2->dev, "no pixel rate control in subdev %s\n", - source->name); - return -EINVAL; + if (!csi2->remote_source) + return -ENODEV; + + remote_pad = media_pad_remote_pad_unique(&csi2->pads[RZG2L_CSI2_SINK]); + if (IS_ERR(remote_pad)) { + dev_err(csi2->dev, "can't get source pad of %s (%ld)\n", + csi2->remote_source->name, PTR_ERR(remote_pad)); + return PTR_ERR(remote_pad); } state = v4l2_subdev_lock_and_get_active_state(&csi2->subdev); @@ -298,12 +301,16 @@ static int rzg2l_csi2_calc_mbps(struct rzg2l_csi2 *csi2) format = rzg2l_csi2_code_to_fmt(fmt->code); v4l2_subdev_unlock_state(state); - /* - * Calculate hsfreq in Mbps - * hsfreq = (pixel_rate * bits_per_sample) / number_of_lanes - */ - mbps = v4l2_ctrl_g_ctrl_int64(ctrl) * format->bpp; - do_div(mbps, csi2->lanes * 1000000); + /* Read the link frequency from remote subdevice. */ + ret = v4l2_get_link_freq(remote_pad, format->bpp, csi2->lanes * 2); + if (ret < 0) { + dev_err(csi2->dev, "can't retrieve link freq from subdev %s\n", + source->name); + return -EINVAL; + } + + mbps = ret * 2; + do_div(mbps, 1000000); return mbps; } From ace92ccef0c9bee4fad192b151c1aed9eef7f7c9 Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 25 Jun 2025 10:20:30 +0100 Subject: [PATCH 160/310] media: platform: rzg2l-cru: Use v4l2_fill_pixfmt() Rather than open-code a calculation of the format's bytesperline and sizeimage, use the v4l2_fill_pixfmt() helper. This makes it easier to support the CRU packed pixel formats without over complicating the driver. This change makes the .bpp member of struct rzg2l_cru_ip_format superfluous - remove them. Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Reviewed-by: Lad Prabhakar Tested-by: Lad Prabhakar Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250625-rzg2l-cru-v6-4-a9099ed26c14@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h | 2 -- drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c | 5 ----- drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c | 10 +--------- 3 files changed, 1 insertion(+), 16 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h index 56feda6e6207..bba174973ac3 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -68,7 +68,6 @@ struct rzg2l_cru_ip { * @datatype: MIPI CSI2 data type * @format: 4CC format identifier (V4L2_PIX_FMT_*) * @icndmr: ICnDMR register value - * @bpp: bytes per pixel * @yuv: Flag to indicate whether the format is YUV-based. */ struct rzg2l_cru_ip_format { @@ -76,7 +75,6 @@ struct rzg2l_cru_ip_format { u32 datatype; u32 format; u32 icndmr; - u8 bpp; bool yuv; }; diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c index 7836c7cd53dc..15fc1028082a 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -16,7 +16,6 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .code = MEDIA_BUS_FMT_UYVY8_1X16, .datatype = MIPI_CSI2_DT_YUV422_8B, .format = V4L2_PIX_FMT_UYVY, - .bpp = 2, .icndmr = ICnDMR_YCMODE_UYVY, .yuv = true, }, @@ -24,7 +23,6 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .code = MEDIA_BUS_FMT_SBGGR8_1X8, .format = V4L2_PIX_FMT_SBGGR8, .datatype = MIPI_CSI2_DT_RAW8, - .bpp = 1, .icndmr = 0, .yuv = false, }, @@ -32,7 +30,6 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .code = MEDIA_BUS_FMT_SGBRG8_1X8, .format = V4L2_PIX_FMT_SGBRG8, .datatype = MIPI_CSI2_DT_RAW8, - .bpp = 1, .icndmr = 0, .yuv = false, }, @@ -40,7 +37,6 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .code = MEDIA_BUS_FMT_SGRBG8_1X8, .format = V4L2_PIX_FMT_SGRBG8, .datatype = MIPI_CSI2_DT_RAW8, - .bpp = 1, .icndmr = 0, .yuv = false, }, @@ -48,7 +44,6 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .code = MEDIA_BUS_FMT_SRGGB8_1X8, .format = V4L2_PIX_FMT_SRGGB8, .datatype = MIPI_CSI2_DT_RAW8, - .bpp = 1, .icndmr = 0, .yuv = false, }, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index 042a6bd44697..e151e6597609 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -939,15 +939,7 @@ static void rzg2l_cru_format_align(struct rzg2l_cru_dev *cru, v4l_bound_align_image(&pix->width, 320, info->max_width, 1, &pix->height, 240, info->max_height, 2, 0); - if (info->has_stride) { - u32 stride = clamp(pix->bytesperline, pix->width * fmt->bpp, - RZG2L_CRU_STRIDE_MAX); - pix->bytesperline = round_up(stride, RZG2L_CRU_STRIDE_ALIGN); - } else { - pix->bytesperline = pix->width * fmt->bpp; - } - - pix->sizeimage = pix->bytesperline * pix->height; + v4l2_fill_pixfmt(pix, pix->pixelformat, pix->width, pix->height); dev_dbg(cru->dev, "Format %ux%u bpl: %u size: %u\n", pix->width, pix->height, pix->bytesperline, pix->sizeimage); From 907122f552089da25fe8f3d072d1222bd99dec9b Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Mon, 30 Jun 2025 16:06:51 +0100 Subject: [PATCH 161/310] media: rzg2l-cru: Support multiple mbus codes per pixel format As a preliminary step for supporting the CRU pixel formats, extend the driver such that multiple media bus codes can support each of the output pixel formats. Reviewed-by: Lad Prabhakar Reviewed-by: Laurent Pinchart Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250630150651.2698237-1-dan.scally@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rzg2l-cru/rzg2l-cru.h | 10 ++- .../platform/renesas/rzg2l-cru/rzg2l-ip.c | 67 +++++++++++++++---- .../platform/renesas/rzg2l-cru/rzg2l-video.c | 2 +- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h index bba174973ac3..be95b41c37df 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-cru.h @@ -64,14 +64,18 @@ struct rzg2l_cru_ip { /** * struct rzg2l_cru_ip_format - CRU IP format - * @code: Media bus code + * @codes: Array of up to four media bus codes * @datatype: MIPI CSI2 data type * @format: 4CC format identifier (V4L2_PIX_FMT_*) * @icndmr: ICnDMR register value * @yuv: Flag to indicate whether the format is YUV-based. */ struct rzg2l_cru_ip_format { - u32 code; + /* + * RAW output formats might be produced by RAW media codes with any one + * of the 4 common bayer patterns. + */ + u32 codes[4]; u32 datatype; u32 format; u32 icndmr; @@ -190,6 +194,8 @@ struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru); const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code); const struct rzg2l_cru_ip_format *rzg2l_cru_ip_format_to_fmt(u32 format); const struct rzg2l_cru_ip_format *rzg2l_cru_ip_index_to_fmt(u32 index); +bool rzg2l_cru_ip_fmt_supports_mbus_code(const struct rzg2l_cru_ip_format *fmt, + unsigned int code); void rzg2l_cru_enable_interrupts(struct rzg2l_cru_dev *cru); void rzg2l_cru_disable_interrupts(struct rzg2l_cru_dev *cru); diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c index 15fc1028082a..fbbcdb96c457 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -13,35 +13,45 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { { - .code = MEDIA_BUS_FMT_UYVY8_1X16, + .codes = { + MEDIA_BUS_FMT_UYVY8_1X16, + }, .datatype = MIPI_CSI2_DT_YUV422_8B, .format = V4L2_PIX_FMT_UYVY, .icndmr = ICnDMR_YCMODE_UYVY, .yuv = true, }, { - .code = MEDIA_BUS_FMT_SBGGR8_1X8, + .codes = { + MEDIA_BUS_FMT_SBGGR8_1X8, + }, .format = V4L2_PIX_FMT_SBGGR8, .datatype = MIPI_CSI2_DT_RAW8, .icndmr = 0, .yuv = false, }, { - .code = MEDIA_BUS_FMT_SGBRG8_1X8, + .codes = { + MEDIA_BUS_FMT_SGBRG8_1X8, + }, .format = V4L2_PIX_FMT_SGBRG8, .datatype = MIPI_CSI2_DT_RAW8, .icndmr = 0, .yuv = false, }, { - .code = MEDIA_BUS_FMT_SGRBG8_1X8, + .codes = { + MEDIA_BUS_FMT_SGRBG8_1X8, + }, .format = V4L2_PIX_FMT_SGRBG8, .datatype = MIPI_CSI2_DT_RAW8, .icndmr = 0, .yuv = false, }, { - .code = MEDIA_BUS_FMT_SRGGB8_1X8, + .codes = { + MEDIA_BUS_FMT_SRGGB8_1X8, + }, .format = V4L2_PIX_FMT_SRGGB8, .datatype = MIPI_CSI2_DT_RAW8, .icndmr = 0, @@ -51,11 +61,14 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code) { - unsigned int i; + unsigned int i, j; - for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++) - if (rzg2l_cru_ip_formats[i].code == code) - return &rzg2l_cru_ip_formats[i]; + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++) { + for (j = 0; j < ARRAY_SIZE(rzg2l_cru_ip_formats[i].codes); j++) { + if (rzg2l_cru_ip_formats[i].codes[j] == code) + return &rzg2l_cru_ip_formats[i]; + } + } return NULL; } @@ -80,6 +93,17 @@ const struct rzg2l_cru_ip_format *rzg2l_cru_ip_index_to_fmt(u32 index) return &rzg2l_cru_ip_formats[index]; } +bool rzg2l_cru_ip_fmt_supports_mbus_code(const struct rzg2l_cru_ip_format *fmt, + unsigned int code) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(fmt->codes); i++) + if (fmt->codes[i] == code) + return true; + + return false; +} struct v4l2_mbus_framefmt *rzg2l_cru_ip_get_src_fmt(struct rzg2l_cru_dev *cru) { struct v4l2_subdev_state *state; @@ -157,7 +181,7 @@ static int rzg2l_cru_ip_set_format(struct v4l2_subdev *sd, sink_format = v4l2_subdev_state_get_format(state, fmt->pad); if (!rzg2l_cru_ip_code_to_fmt(fmt->format.code)) - sink_format->code = rzg2l_cru_ip_formats[0].code; + sink_format->code = rzg2l_cru_ip_formats[0].codes[0]; else sink_format->code = fmt->format.code; @@ -183,11 +207,26 @@ static int rzg2l_cru_ip_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_mbus_code_enum *code) { - if (code->index >= ARRAY_SIZE(rzg2l_cru_ip_formats)) - return -EINVAL; + unsigned int index = code->index; + unsigned int i, j; - code->code = rzg2l_cru_ip_formats[code->index].code; - return 0; + for (i = 0; i < ARRAY_SIZE(rzg2l_cru_ip_formats); i++) { + const struct rzg2l_cru_ip_format *fmt = &rzg2l_cru_ip_formats[i]; + + for (j = 0; j < ARRAY_SIZE(fmt->codes); j++) { + if (!fmt->codes[j]) + continue; + + if (!index) { + code->code = fmt->codes[j]; + return 0; + } + + index--; + } + } + + return -EINVAL; } static int rzg2l_cru_ip_enum_frame_size(struct v4l2_subdev *sd, diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c index e151e6597609..a8817a7066b2 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-video.c @@ -1145,7 +1145,7 @@ static int rzg2l_cru_video_link_validate(struct media_link *link) if (fmt.format.width != cru->format.width || fmt.format.height != cru->format.height || fmt.format.field != cru->format.field || - video_fmt->code != fmt.format.code) + !rzg2l_cru_ip_fmt_supports_mbus_code(video_fmt, fmt.format.code)) return -EPIPE; return 0; From 88193ae66b042925aee3ee7ffeeeaddb70131e7f Mon Sep 17 00:00:00 2001 From: Daniel Scally Date: Wed, 25 Jun 2025 10:20:32 +0100 Subject: [PATCH 162/310] media: platform: rzg2l-cru: Add support for RAW10/12/14 data Add support to the rzg2l-cru driver to capture 10/12/14 bit bayer data and output it into the CRU's 64-bit packed pixel format. Reviewed-by: Jacopo Mondi Reviewed-by: Laurent Pinchart Reviewed-by: Lad Prabhakar Signed-off-by: Daniel Scally Link: https://lore.kernel.org/r/20250625-rzg2l-cru-v6-6-a9099ed26c14@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- .../platform/renesas/rzg2l-cru/rzg2l-csi2.c | 12 +++++++ .../platform/renesas/rzg2l-cru/rzg2l-ip.c | 36 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c index cec165551183..1520211e7418 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-csi2.c @@ -232,6 +232,18 @@ static const struct rzg2l_csi2_format rzg2l_csi2_formats[] = { { .code = MEDIA_BUS_FMT_SGBRG8_1X8, .bpp = 8, }, { .code = MEDIA_BUS_FMT_SGRBG8_1X8, .bpp = 8, }, { .code = MEDIA_BUS_FMT_SRGGB8_1X8, .bpp = 8, }, + { .code = MEDIA_BUS_FMT_SBGGR10_1X10, .bpp = 10, }, + { .code = MEDIA_BUS_FMT_SGBRG10_1X10, .bpp = 10, }, + { .code = MEDIA_BUS_FMT_SGRBG10_1X10, .bpp = 10, }, + { .code = MEDIA_BUS_FMT_SRGGB10_1X10, .bpp = 10, }, + { .code = MEDIA_BUS_FMT_SBGGR12_1X12, .bpp = 12, }, + { .code = MEDIA_BUS_FMT_SGBRG12_1X12, .bpp = 12, }, + { .code = MEDIA_BUS_FMT_SGRBG12_1X12, .bpp = 12, }, + { .code = MEDIA_BUS_FMT_SRGGB12_1X12, .bpp = 12, }, + { .code = MEDIA_BUS_FMT_SBGGR14_1X14, .bpp = 14, }, + { .code = MEDIA_BUS_FMT_SGBRG14_1X14, .bpp = 14, }, + { .code = MEDIA_BUS_FMT_SGRBG14_1X14, .bpp = 14, }, + { .code = MEDIA_BUS_FMT_SRGGB14_1X14, .bpp = 14, }, }; static inline struct rzg2l_csi2 *sd_to_csi2(struct v4l2_subdev *sd) diff --git a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c index fbbcdb96c457..5f2c87858bfe 100644 --- a/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c +++ b/drivers/media/platform/renesas/rzg2l-cru/rzg2l-ip.c @@ -57,6 +57,42 @@ static const struct rzg2l_cru_ip_format rzg2l_cru_ip_formats[] = { .icndmr = 0, .yuv = false, }, + { + .codes = { + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10 + }, + .format = V4L2_PIX_FMT_RAW_CRU10, + .datatype = MIPI_CSI2_DT_RAW10, + .icndmr = 0, + .yuv = false, + }, + { + .codes = { + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12 + }, + .format = V4L2_PIX_FMT_RAW_CRU12, + .datatype = MIPI_CSI2_DT_RAW12, + .icndmr = 0, + .yuv = false, + }, + { + .codes = { + MEDIA_BUS_FMT_SBGGR14_1X14, + MEDIA_BUS_FMT_SGBRG14_1X14, + MEDIA_BUS_FMT_SGRBG14_1X14, + MEDIA_BUS_FMT_SRGGB14_1X14 + }, + .format = V4L2_PIX_FMT_RAW_CRU14, + .datatype = MIPI_CSI2_DT_RAW14, + .icndmr = 0, + .yuv = false, + }, }; const struct rzg2l_cru_ip_format *rzg2l_cru_ip_code_to_fmt(unsigned int code) From 377dc500d253f0b26732b2cb062e89668aef890a Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:29:14 +0000 Subject: [PATCH 163/310] media: venus: vdec: Clamp param smaller than 1fps and bigger than 240. The driver uses "whole" fps in all its calculations (e.g. in load_per_instance()). Those calculation expect an fps bigger than 1, and not big enough to overflow. Clamp the value if the user provides a param that will result in an invalid fps. Reported-by: Hans Verkuil Closes: https://lore.kernel.org/linux-media/f11653a7-bc49-48cd-9cdb-1659147453e4@xs4all.nl/T/#m91cd962ac942834654f94c92206e2f85ff7d97f0 Fixes: 7472c1c69138 ("[media] media: venus: vdec: add video decoder files") Cc: stable@vger.kernel.org Tested-by: Bryan O'Donoghue # qrb5615-rb5 Reviewed-by: Bryan O'Donoghue Signed-off-by: Ricardo Ribalda [bod: Change "parm" to "param"] Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/core.h | 2 ++ drivers/media/platform/qcom/venus/vdec.c | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/venus/core.h b/drivers/media/platform/qcom/venus/core.h index b412e0c5515a..5b1ba1c69adb 100644 --- a/drivers/media/platform/qcom/venus/core.h +++ b/drivers/media/platform/qcom/venus/core.h @@ -28,6 +28,8 @@ #define VIDC_RESETS_NUM_MAX 2 #define VIDC_MAX_HIER_CODING_LAYER 6 +#define VENUS_MAX_FPS 240 + extern int venus_fw_debug; struct freq_tbl { diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index 99ce5fd41577..fca27be61f4b 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -481,11 +481,10 @@ static int vdec_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; do_div(us_per_frame, timeperframe->denominator); - if (!us_per_frame) - return -EINVAL; - + us_per_frame = clamp(us_per_frame, 1, USEC_PER_SEC); fps = (u64)USEC_PER_SEC; do_div(fps, us_per_frame); + fps = min(VENUS_MAX_FPS, fps); inst->fps = fps; inst->timeperframe = *timeperframe; From 417c01b92ec278a1118a05c6ad8a796eaa0c9c52 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:29:15 +0000 Subject: [PATCH 164/310] media: venus: venc: Clamp param smaller than 1fps and bigger than 240 The driver uses "whole" fps in all its calculations (e.g. in load_per_instance()). Those calculation expect an fps bigger than 1, and not big enough to overflow. Clamp the param if the user provides a value that will result in an invalid fps. Reported-by: Hans Verkuil Closes: https://lore.kernel.org/linux-media/f11653a7-bc49-48cd-9cdb-1659147453e4@xs4all.nl/T/#m91cd962ac942834654f94c92206e2f85ff7d97f0 Fixes: aaaa93eda64b ("[media] media: venus: venc: add video encoder files") Cc: stable@vger.kernel.org Signed-off-by: Ricardo Ribalda [bod: Change "parm" to "param"] Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/venc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c index c7f8e37dba9b..b9ccee870c3d 100644 --- a/drivers/media/platform/qcom/venus/venc.c +++ b/drivers/media/platform/qcom/venus/venc.c @@ -411,11 +411,10 @@ static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) us_per_frame = timeperframe->numerator * (u64)USEC_PER_SEC; do_div(us_per_frame, timeperframe->denominator); - if (!us_per_frame) - return -EINVAL; - + us_per_frame = clamp(us_per_frame, 1, USEC_PER_SEC); fps = (u64)USEC_PER_SEC; do_div(fps, us_per_frame); + fps = min(VENUS_MAX_FPS, fps); inst->timeperframe = *timeperframe; inst->fps = fps; From 3a905e52c97ba79b7d9abf22005bab55c12eb620 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:29:17 +0000 Subject: [PATCH 165/310] media: venus: venc: Make the range of us_per_frame explicit Fps bigger than 0.000232829 fps, this fits in a 32 bit us_per_frame. There is no need to do a 64 bit division here. Also, the driver only works with whole fps. Found with cocci: drivers/media/platform/qcom/venus/venc.c:418:1-7: WARNING: do_div() does a 64-by-32 division, please consider using div64_u64 instead. Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Signed-off-by: Ricardo Ribalda Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/venc.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/venus/venc.c b/drivers/media/platform/qcom/venus/venc.c index b9ccee870c3d..c0a0ccdded80 100644 --- a/drivers/media/platform/qcom/venus/venc.c +++ b/drivers/media/platform/qcom/venus/venc.c @@ -412,8 +412,7 @@ static int venc_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) do_div(us_per_frame, timeperframe->denominator); us_per_frame = clamp(us_per_frame, 1, USEC_PER_SEC); - fps = (u64)USEC_PER_SEC; - do_div(fps, us_per_frame); + fps = USEC_PER_SEC / (u32)us_per_frame; fps = min(VENUS_MAX_FPS, fps); inst->timeperframe = *timeperframe; From c056064a207c914c8e50d1a6312a5fa65dcbae5f Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:29:18 +0000 Subject: [PATCH 166/310] media: venus: vdec: Make the range of us_per_frame explicit Fps bigger than 0.000232829 fps, this fits in a 32 bit us_per_frame. There is no need to do a 64 bit division here. Also, the driver only works with whole fps. Found by cocci: drivers/media/platform/qcom/venus/vdec.c:488:1-7: WARNING: do_div() does a 64-by-32 division, please consider using div64_u64 instead. Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Bryan O'Donoghue # qrb5615-rb5 Signed-off-by: Ricardo Ribalda Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/vdec.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/venus/vdec.c b/drivers/media/platform/qcom/venus/vdec.c index fca27be61f4b..29b0d6a5303d 100644 --- a/drivers/media/platform/qcom/venus/vdec.c +++ b/drivers/media/platform/qcom/venus/vdec.c @@ -482,8 +482,7 @@ static int vdec_s_parm(struct file *file, void *fh, struct v4l2_streamparm *a) do_div(us_per_frame, timeperframe->denominator); us_per_frame = clamp(us_per_frame, 1, USEC_PER_SEC); - fps = (u64)USEC_PER_SEC; - do_div(fps, us_per_frame); + fps = USEC_PER_SEC / (u32)us_per_frame; fps = min(VENUS_MAX_FPS, fps); inst->fps = fps; From b179234b5e59013f8772da17b273f463f9720bdd Mon Sep 17 00:00:00 2001 From: Renjiang Han Date: Thu, 12 Jun 2025 07:53:51 +0530 Subject: [PATCH 167/310] media: venus: pm_helpers: use opp-table for the frequency Some platforms (such as qcs615 and sc7180) use the same core but have different frequency tables. Using the opp-table allows us to separate the core description from the frequency data and supports the use of fallback compatibles. Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Signed-off-by: Renjiang Han Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/venus/pm_helpers.c | 58 +++++++++++-------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/drivers/media/platform/qcom/venus/pm_helpers.c b/drivers/media/platform/qcom/venus/pm_helpers.c index 409aa9bd0b5d..8dd5a9b0d060 100644 --- a/drivers/media/platform/qcom/venus/pm_helpers.c +++ b/drivers/media/platform/qcom/venus/pm_helpers.c @@ -41,16 +41,14 @@ static int core_clks_get(struct venus_core *core) static int core_clks_enable(struct venus_core *core) { const struct venus_resources *res = core->res; - const struct freq_tbl *freq_tbl = core->res->freq_tbl; - unsigned int freq_tbl_size = core->res->freq_tbl_size; - unsigned long freq; + struct device *dev = core->dev; + unsigned long freq = 0; + struct dev_pm_opp *opp; unsigned int i; int ret; - if (!freq_tbl) - return -EINVAL; - - freq = freq_tbl[freq_tbl_size - 1].freq; + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + dev_pm_opp_put(opp); for (i = 0; i < res->clks_num; i++) { if (IS_V6(core)) { @@ -636,7 +634,9 @@ static int decide_core(struct venus_inst *inst) u32 min_coreid, min_load, cur_inst_load; u32 min_lp_coreid, min_lp_load, cur_inst_lp_load; struct hfi_videocores_usage_type cu; - unsigned long max_freq; + unsigned long max_freq = ULONG_MAX; + struct device *dev = core->dev; + struct dev_pm_opp *opp; int ret = 0; if (legacy_binding) { @@ -659,7 +659,8 @@ static int decide_core(struct venus_inst *inst) cur_inst_lp_load *= inst->clk_data.low_power_freq; /*TODO : divide this inst->load by work_route */ - max_freq = core->res->freq_tbl[0].freq; + opp = dev_pm_opp_find_freq_floor(dev, &max_freq); + dev_pm_opp_put(opp); min_loaded_core(inst, &min_coreid, &min_load, false); min_loaded_core(inst, &min_lp_coreid, &min_lp_load, true); @@ -949,7 +950,10 @@ static int core_resets_get(struct venus_core *core) static int core_get_v4(struct venus_core *core) { struct device *dev = core->dev; + const struct freq_tbl *freq_tbl = core->res->freq_tbl; + unsigned int num_rows = core->res->freq_tbl_size; const struct venus_resources *res = core->res; + unsigned int i; int ret; ret = core_clks_get(core); @@ -986,9 +990,17 @@ static int core_get_v4(struct venus_core *core) if (core->res->opp_pmdomain) { ret = devm_pm_opp_of_add_table(dev); - if (ret && ret != -ENODEV) { - dev_err(dev, "invalid OPP table in device tree\n"); - return ret; + if (ret) { + if (ret == -ENODEV) { + for (i = 0; i < num_rows; i++) { + ret = dev_pm_opp_add(dev, freq_tbl[i].freq, 0); + if (ret) + return ret; + } + } else { + dev_err(dev, "invalid OPP table in device tree\n"); + return ret; + } } } @@ -1078,11 +1090,11 @@ static unsigned long calculate_inst_freq(struct venus_inst *inst, static int load_scale_v4(struct venus_inst *inst) { struct venus_core *core = inst->core; - const struct freq_tbl *table = core->res->freq_tbl; - unsigned int num_rows = core->res->freq_tbl_size; struct device *dev = core->dev; unsigned long freq = 0, freq_core1 = 0, freq_core2 = 0; + unsigned long max_freq = ULONG_MAX; unsigned long filled_len = 0; + struct dev_pm_opp *opp; int i, ret = 0; for (i = 0; i < inst->num_input_bufs; i++) @@ -1108,20 +1120,18 @@ static int load_scale_v4(struct venus_inst *inst) freq = max(freq_core1, freq_core2); - if (freq > table[0].freq) { - dev_dbg(dev, VDBGL "requested clock rate: %lu scaling clock rate : %lu\n", - freq, table[0].freq); + opp = dev_pm_opp_find_freq_floor(dev, &max_freq); + dev_pm_opp_put(opp); - freq = table[0].freq; + if (freq > max_freq) { + dev_dbg(dev, VDBGL "requested clock rate: %lu scaling clock rate : %lu\n", + freq, max_freq); + freq = max_freq; goto set_freq; } - for (i = num_rows - 1 ; i >= 0; i--) { - if (freq <= table[i].freq) { - freq = table[i].freq; - break; - } - } + opp = dev_pm_opp_find_freq_ceil(dev, &freq); + dev_pm_opp_put(opp); set_freq: From 3200144a2fa4209dc084a19941b9b203b43580f0 Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Fri, 6 Jun 2025 17:25:22 +0200 Subject: [PATCH 168/310] media: venus: protect against spurious interrupts during probe Make sure the interrupt handler is initialized before the interrupt is registered. If the IRQ is registered before hfi_create(), it's possible that an interrupt fires before the handler setup is complete, leading to a NULL dereference. This error condition has been observed during system boot on Rb3Gen2. Fixes: af2c3834c8ca ("[media] media: venus: adding core part and helper functions") Cc: stable@vger.kernel.org Signed-off-by: Jorge Ramirez-Ortiz Reviewed-by: Bryan O'Donoghue Reviewed-by: Vikash Garodia Reviewed-by: Dikshita Agarwal Tested-by: Dikshita Agarwal # RB5 Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/core.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c index d305d74bb152..5bd99d0aafe4 100644 --- a/drivers/media/platform/qcom/venus/core.c +++ b/drivers/media/platform/qcom/venus/core.c @@ -424,13 +424,13 @@ static int venus_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&core->work, venus_sys_error_handler); init_waitqueue_head(&core->sys_err_done); - ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, venus_isr_thread, - IRQF_TRIGGER_HIGH | IRQF_ONESHOT, - "venus", core); + ret = hfi_create(core, &venus_core_ops); if (ret) goto err_core_put; - ret = hfi_create(core, &venus_core_ops); + ret = devm_request_threaded_irq(dev, core->irq, hfi_isr, venus_isr_thread, + IRQF_TRIGGER_HIGH | IRQF_ONESHOT, + "venus", core); if (ret) goto err_core_put; From 49befc830daa743e051a65468c05c2ff9e8580e6 Mon Sep 17 00:00:00 2001 From: Vedang Nagar Date: Mon, 19 May 2025 12:42:21 +0530 Subject: [PATCH 169/310] media: venus: Add a check for packet size after reading from shared memory Add a check to ensure that the packet size does not exceed the number of available words after reading the packet header from shared memory. This ensures that the size provided by the firmware is safe to process and prevent potential out-of-bounds memory access. Fixes: d96d3f30c0f2 ("[media] media: venus: hfi: add Venus HFI files") Cc: stable@vger.kernel.org Signed-off-by: Vedang Nagar Co-developed-by: Dikshita Agarwal Signed-off-by: Dikshita Agarwal Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/hfi_venus.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/media/platform/qcom/venus/hfi_venus.c b/drivers/media/platform/qcom/venus/hfi_venus.c index b5f2ea879950..c982f4527bb0 100644 --- a/drivers/media/platform/qcom/venus/hfi_venus.c +++ b/drivers/media/platform/qcom/venus/hfi_venus.c @@ -239,6 +239,7 @@ static int venus_write_queue(struct venus_hfi_device *hdev, static int venus_read_queue(struct venus_hfi_device *hdev, struct iface_queue *queue, void *pkt, u32 *tx_req) { + struct hfi_pkt_hdr *pkt_hdr = NULL; struct hfi_queue_header *qhdr; u32 dwords, new_rd_idx; u32 rd_idx, wr_idx, type, qsize; @@ -304,6 +305,9 @@ static int venus_read_queue(struct venus_hfi_device *hdev, memcpy(pkt, rd_ptr, len); memcpy(pkt + len, queue->qmem.kva, new_rd_idx << 2); } + pkt_hdr = (struct hfi_pkt_hdr *)(pkt); + if ((pkt_hdr->size >> 2) != dwords) + return -EINVAL; } else { /* bad packet received, dropping */ new_rd_idx = qhdr->write_idx; From 06d6770ff0d8cc8dfd392329a8cc03e2a83e7289 Mon Sep 17 00:00:00 2001 From: Vedang Nagar Date: Mon, 19 May 2025 12:42:22 +0530 Subject: [PATCH 170/310] media: venus: Fix OOB read due to missing payload bound check Currently, The event_seq_changed() handler processes a variable number of properties sent by the firmware. The number of properties is indicated by the firmware and used to iterate over the payload. However, the payload size is not being validated against the actual message length. This can lead to out-of-bounds memory access if the firmware provides a property count that exceeds the data available in the payload. Such a condition can result in kernel crashes or potential information leaks if memory beyond the buffer is accessed. Fix this by properly validating the remaining size of the payload before each property access and updating bounds accordingly as properties are parsed. This ensures that property parsing is safely bounded within the received message buffer and protects against malformed or malicious firmware behavior. Fixes: 09c2845e8fe4 ("[media] media: venus: hfi: add Host Firmware Interface (HFI)") Cc: stable@vger.kernel.org Signed-off-by: Vedang Nagar Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Co-developed-by: Dikshita Agarwal Signed-off-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/hfi_msgs.c | 83 ++++++++++++++------ 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/drivers/media/platform/qcom/venus/hfi_msgs.c b/drivers/media/platform/qcom/venus/hfi_msgs.c index 0a041b4db9ef..cf0d97cbc463 100644 --- a/drivers/media/platform/qcom/venus/hfi_msgs.c +++ b/drivers/media/platform/qcom/venus/hfi_msgs.c @@ -33,8 +33,9 @@ static void event_seq_changed(struct venus_core *core, struct venus_inst *inst, struct hfi_buffer_requirements *bufreq; struct hfi_extradata_input_crop *crop; struct hfi_dpb_counts *dpb_count; + u32 ptype, rem_bytes; + u32 size_read = 0; u8 *data_ptr; - u32 ptype; inst->error = HFI_ERR_NONE; @@ -44,86 +45,118 @@ static void event_seq_changed(struct venus_core *core, struct venus_inst *inst, break; default: inst->error = HFI_ERR_SESSION_INVALID_PARAMETER; - goto done; + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); + return; } event.event_type = pkt->event_data1; num_properties_changed = pkt->event_data2; - if (!num_properties_changed) { - inst->error = HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; - goto done; - } + if (!num_properties_changed) + goto error; data_ptr = (u8 *)&pkt->ext_event_data[0]; + rem_bytes = pkt->shdr.hdr.size - sizeof(*pkt); + do { + if (rem_bytes < sizeof(u32)) + goto error; ptype = *((u32 *)data_ptr); + + data_ptr += sizeof(u32); + rem_bytes -= sizeof(u32); + switch (ptype) { case HFI_PROPERTY_PARAM_FRAME_SIZE: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_framesize)) + goto error; + frame_sz = (struct hfi_framesize *)data_ptr; event.width = frame_sz->width; event.height = frame_sz->height; - data_ptr += sizeof(*frame_sz); + size_read = sizeof(struct hfi_framesize); break; case HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_profile_level)) + goto error; + profile_level = (struct hfi_profile_level *)data_ptr; event.profile = profile_level->profile; event.level = profile_level->level; - data_ptr += sizeof(*profile_level); + size_read = sizeof(struct hfi_profile_level); break; case HFI_PROPERTY_PARAM_VDEC_PIXEL_BITDEPTH: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_bit_depth)) + goto error; + pixel_depth = (struct hfi_bit_depth *)data_ptr; event.bit_depth = pixel_depth->bit_depth; - data_ptr += sizeof(*pixel_depth); + size_read = sizeof(struct hfi_bit_depth); break; case HFI_PROPERTY_PARAM_VDEC_PIC_STRUCT: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_pic_struct)) + goto error; + pic_struct = (struct hfi_pic_struct *)data_ptr; event.pic_struct = pic_struct->progressive_only; - data_ptr += sizeof(*pic_struct); + size_read = sizeof(struct hfi_pic_struct); break; case HFI_PROPERTY_PARAM_VDEC_COLOUR_SPACE: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_colour_space)) + goto error; + colour_info = (struct hfi_colour_space *)data_ptr; event.colour_space = colour_info->colour_space; - data_ptr += sizeof(*colour_info); + size_read = sizeof(struct hfi_colour_space); break; case HFI_PROPERTY_CONFIG_VDEC_ENTROPY: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(u32)) + goto error; + event.entropy_mode = *(u32 *)data_ptr; - data_ptr += sizeof(u32); + size_read = sizeof(u32); break; case HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_buffer_requirements)) + goto error; + bufreq = (struct hfi_buffer_requirements *)data_ptr; event.buf_count = hfi_bufreq_get_count_min(bufreq, ver); - data_ptr += sizeof(*bufreq); + size_read = sizeof(struct hfi_buffer_requirements); break; case HFI_INDEX_EXTRADATA_INPUT_CROP: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_extradata_input_crop)) + goto error; + crop = (struct hfi_extradata_input_crop *)data_ptr; event.input_crop.left = crop->left; event.input_crop.top = crop->top; event.input_crop.width = crop->width; event.input_crop.height = crop->height; - data_ptr += sizeof(*crop); + size_read = sizeof(struct hfi_extradata_input_crop); break; case HFI_PROPERTY_PARAM_VDEC_DPB_COUNTS: - data_ptr += sizeof(u32); + if (rem_bytes < sizeof(struct hfi_dpb_counts)) + goto error; + dpb_count = (struct hfi_dpb_counts *)data_ptr; event.buf_count = dpb_count->fw_min_cnt; - data_ptr += sizeof(*dpb_count); + size_read = sizeof(struct hfi_dpb_counts); break; default: + size_read = 0; break; } + data_ptr += size_read; + rem_bytes -= size_read; num_properties_changed--; } while (num_properties_changed > 0); -done: + inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); + return; + +error: + inst->error = HFI_ERR_SESSION_INSUFFICIENT_RESOURCES; inst->ops->event_notify(inst, EVT_SYS_EVENT_CHANGE, &event); } From 640803003cd903cea73dc6a86bf6963e238e2b3f Mon Sep 17 00:00:00 2001 From: Jorge Ramirez-Ortiz Date: Thu, 19 Jun 2025 09:48:30 +0200 Subject: [PATCH 171/310] media: venus: hfi: explicitly release IRQ during teardown Ensure the IRQ is disabled - and all pending handlers completed - before dismantling the interrupt routing and clearing related pointers. This prevents any possibility of the interrupt triggering after the handler context has been invalidated. Fixes: d96d3f30c0f2 ("[media] media: venus: hfi: add Venus HFI files") Cc: stable@vger.kernel.org Signed-off-by: Jorge Ramirez-Ortiz Reviewed-by: Dikshita Agarwal Tested-by: Dikshita Agarwal # RB5 Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/hfi_venus.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/qcom/venus/hfi_venus.c b/drivers/media/platform/qcom/venus/hfi_venus.c index c982f4527bb0..cec7f5964d3d 100644 --- a/drivers/media/platform/qcom/venus/hfi_venus.c +++ b/drivers/media/platform/qcom/venus/hfi_venus.c @@ -1682,6 +1682,7 @@ void venus_hfi_destroy(struct venus_core *core) venus_interface_queues_release(hdev); mutex_destroy(&hdev->lock); kfree(hdev); + disable_irq(core->irq); core->ops = NULL; } From ee3b94f22638e0f7a1893d95d87b08698b680052 Mon Sep 17 00:00:00 2001 From: Konrad Dybcio Date: Sat, 31 May 2025 14:22:00 +0200 Subject: [PATCH 172/310] media: venus: Fix MSM8998 frequency table Fill in the correct data for the production SKU. Fixes: 193b3dac29a4 ("media: venus: add msm8998 support") Cc: stable@vger.kernel.org Signed-off-by: Konrad Dybcio Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Reviewed-by: Dmitry Baryshkov Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/venus/core.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/drivers/media/platform/qcom/venus/core.c b/drivers/media/platform/qcom/venus/core.c index 5bd99d0aafe4..4c049c694d9c 100644 --- a/drivers/media/platform/qcom/venus/core.c +++ b/drivers/media/platform/qcom/venus/core.c @@ -709,11 +709,11 @@ static const struct venus_resources msm8996_res = { }; static const struct freq_tbl msm8998_freq_table[] = { - { 1944000, 465000000 }, /* 4k UHD @ 60 (decode only) */ - { 972000, 465000000 }, /* 4k UHD @ 30 */ - { 489600, 360000000 }, /* 1080p @ 60 */ - { 244800, 186000000 }, /* 1080p @ 30 */ - { 108000, 100000000 }, /* 720p @ 30 */ + { 1728000, 533000000 }, /* 4k UHD @ 60 (decode only) */ + { 1036800, 444000000 }, /* 2k @ 120 */ + { 829440, 355200000 }, /* 4k @ 44 */ + { 489600, 269330000 },/* 4k @ 30 */ + { 108000, 200000000 }, /* 1080p @ 60 */ }; static const struct reg_val msm8998_reg_preset[] = { From 7c452ffda30c0460c568273993a3d3c611486467 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:47 +0530 Subject: [PATCH 173/310] media: iris: Skip destroying internal buffer if not dequeued Firmware might hold the DPB buffers for reference in case of sequence change, so skip destroying buffers for which QUEUED flag is not removed. Cc: stable@vger.kernel.org Fixes: 73702f45db81 ("media: iris: allocate, initialize and queue internal buffers") Reviewed-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/iris/iris_buffer.c | 20 ++++++++++++++++++- .../media/platform/qcom/iris/iris_buffer.h | 3 ++- drivers/media/platform/qcom/iris/iris_vdec.c | 4 ++-- drivers/media/platform/qcom/iris/iris_vidc.c | 4 ++-- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.c b/drivers/media/platform/qcom/iris/iris_buffer.c index e5c5a564fcb8..981fedb000ed 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_buffer.c @@ -376,7 +376,7 @@ int iris_destroy_internal_buffer(struct iris_inst *inst, struct iris_buffer *buf return 0; } -int iris_destroy_internal_buffers(struct iris_inst *inst, u32 plane) +static int iris_destroy_internal_buffers(struct iris_inst *inst, u32 plane, bool force) { const struct iris_platform_data *platform_data = inst->core->iris_platform_data; struct iris_buffer *buf, *next; @@ -396,6 +396,14 @@ int iris_destroy_internal_buffers(struct iris_inst *inst, u32 plane) for (i = 0; i < len; i++) { buffers = &inst->buffers[internal_buf_type[i]]; list_for_each_entry_safe(buf, next, &buffers->list, list) { + /* + * during stream on, skip destroying internal(DPB) buffer + * if firmware did not return it. + * during close, destroy all buffers irrespectively. + */ + if (!force && buf->attr & BUF_ATTR_QUEUED) + continue; + ret = iris_destroy_internal_buffer(inst, buf); if (ret) return ret; @@ -405,6 +413,16 @@ int iris_destroy_internal_buffers(struct iris_inst *inst, u32 plane) return 0; } +int iris_destroy_all_internal_buffers(struct iris_inst *inst, u32 plane) +{ + return iris_destroy_internal_buffers(inst, plane, true); +} + +int iris_destroy_dequeued_internal_buffers(struct iris_inst *inst, u32 plane) +{ + return iris_destroy_internal_buffers(inst, plane, false); +} + static int iris_release_internal_buffers(struct iris_inst *inst, enum iris_buffer_type buffer_type) { diff --git a/drivers/media/platform/qcom/iris/iris_buffer.h b/drivers/media/platform/qcom/iris/iris_buffer.h index c36b6347b077..00825ad2dc3a 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_buffer.h @@ -106,7 +106,8 @@ void iris_get_internal_buffers(struct iris_inst *inst, u32 plane); int iris_create_internal_buffers(struct iris_inst *inst, u32 plane); int iris_queue_internal_buffers(struct iris_inst *inst, u32 plane); int iris_destroy_internal_buffer(struct iris_inst *inst, struct iris_buffer *buffer); -int iris_destroy_internal_buffers(struct iris_inst *inst, u32 plane); +int iris_destroy_all_internal_buffers(struct iris_inst *inst, u32 plane); +int iris_destroy_dequeued_internal_buffers(struct iris_inst *inst, u32 plane); int iris_alloc_and_queue_persist_bufs(struct iris_inst *inst); int iris_alloc_and_queue_input_int_bufs(struct iris_inst *inst); int iris_queue_buffer(struct iris_inst *inst, struct iris_buffer *buf); diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index 4143acedfc57..9c049b9671cc 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -408,7 +408,7 @@ int iris_vdec_streamon_input(struct iris_inst *inst) iris_get_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); - ret = iris_destroy_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + ret = iris_destroy_dequeued_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); if (ret) return ret; @@ -496,7 +496,7 @@ int iris_vdec_streamon_output(struct iris_inst *inst) iris_get_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); - ret = iris_destroy_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + ret = iris_destroy_dequeued_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); if (ret) return ret; diff --git a/drivers/media/platform/qcom/iris/iris_vidc.c b/drivers/media/platform/qcom/iris/iris_vidc.c index ca0f4e310f77..663f5602b5ad 100644 --- a/drivers/media/platform/qcom/iris/iris_vidc.c +++ b/drivers/media/platform/qcom/iris/iris_vidc.c @@ -233,8 +233,8 @@ int iris_close(struct file *filp) iris_session_close(inst); iris_inst_change_state(inst, IRIS_INST_DEINIT); iris_v4l2_fh_deinit(inst); - iris_destroy_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); - iris_destroy_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + iris_destroy_all_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + iris_destroy_all_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); iris_remove_session(inst); mutex_unlock(&inst->lock); mutex_destroy(&inst->ctx_q_lock); From d2abb1ff5a3c13321d407ee19865d0d8d834c7c6 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:48 +0530 Subject: [PATCH 174/310] media: iris: Verify internal buffer release on close Validate all internal buffers queued to firmware are released back to driver on close. This helps ensure buffer lifecycle correctness and aids in debugging any resporce leaks. Cc: stable@vger.kernel.org Fixes: 73702f45db81 ("media: iris: allocate, initialize and queue internal buffers") Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vidc.c | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_vidc.c b/drivers/media/platform/qcom/iris/iris_vidc.c index 663f5602b5ad..a8144595cc78 100644 --- a/drivers/media/platform/qcom/iris/iris_vidc.c +++ b/drivers/media/platform/qcom/iris/iris_vidc.c @@ -221,6 +221,33 @@ static void iris_session_close(struct iris_inst *inst) iris_wait_for_session_response(inst, false); } +static void iris_check_num_queued_internal_buffers(struct iris_inst *inst, u32 plane) +{ + const struct iris_platform_data *platform_data = inst->core->iris_platform_data; + struct iris_buffer *buf, *next; + struct iris_buffers *buffers; + const u32 *internal_buf_type; + u32 internal_buffer_count, i; + u32 count = 0; + + if (V4L2_TYPE_IS_OUTPUT(plane)) { + internal_buf_type = platform_data->dec_ip_int_buf_tbl; + internal_buffer_count = platform_data->dec_ip_int_buf_tbl_size; + } else { + internal_buf_type = platform_data->dec_op_int_buf_tbl; + internal_buffer_count = platform_data->dec_op_int_buf_tbl_size; + } + + for (i = 0; i < internal_buffer_count; i++) { + buffers = &inst->buffers[internal_buf_type[i]]; + list_for_each_entry_safe(buf, next, &buffers->list, list) + count++; + if (count) + dev_err(inst->core->dev, "%d buffer of type %d not released", + count, internal_buf_type[i]); + } +} + int iris_close(struct file *filp) { struct iris_inst *inst = iris_get_inst(filp, NULL); @@ -235,6 +262,8 @@ int iris_close(struct file *filp) iris_v4l2_fh_deinit(inst); iris_destroy_all_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); iris_destroy_all_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); + iris_check_num_queued_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + iris_check_num_queued_internal_buffers(inst, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE); iris_remove_session(inst); mutex_unlock(&inst->lock); mutex_destroy(&inst->ctx_q_lock); From 8aadfd445373b74de4a5cd36736843ae01856636 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:49 +0530 Subject: [PATCH 175/310] media: iris: Update CAPTURE format info based on OUTPUT format Update the width, height and buffer size of CAPTURE based on the resolution set to OUTPUT via VIDIOC_S_FMT. This is required to set the updated capture resolution to firmware when S_FMT is called only for OUTPUT. Cc: stable@vger.kernel.org Fixes: b530b95de22c ("media: iris: implement s_fmt, g_fmt and try_fmt ioctls") Reviewed-by: Bryan O'Donoghue Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vdec.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index 9c049b9671cc..d342f733feb9 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -171,6 +171,11 @@ int iris_vdec_s_fmt(struct iris_inst *inst, struct v4l2_format *f) output_fmt->fmt.pix_mp.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc; output_fmt->fmt.pix_mp.quantization = f->fmt.pix_mp.quantization; + /* Update capture format based on new ip w/h */ + output_fmt->fmt.pix_mp.width = ALIGN(f->fmt.pix_mp.width, 128); + output_fmt->fmt.pix_mp.height = ALIGN(f->fmt.pix_mp.height, 32); + inst->buffers[BUF_OUTPUT].size = iris_get_buffer_size(inst, BUF_OUTPUT); + inst->crop.left = 0; inst->crop.top = 0; inst->crop.width = f->fmt.pix_mp.width; From caf2055487694b6cb52f2ecb161c6c5de660dd72 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:50 +0530 Subject: [PATCH 176/310] media: iris: Avoid updating frame size to firmware during reconfig During reconfig, the firmware sends the resolution aligned to 8 bytes. If the driver sends the same resolution back to the firmware the resolution will be aligned to 16 bytes not 8. The alignment mismatch would then subsequently cause the firmware to send another redundant sequence change event. Fix this by not setting the resolution property during reconfig. Cc: stable@vger.kernel.org Fixes: 3a19d7b9e08b ("media: iris: implement set properties to firmware during streamon") Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../platform/qcom/iris/iris_hfi_gen1_command.c | 15 ++++++++------- drivers/media/platform/qcom/iris/iris_state.c | 2 +- drivers/media/platform/qcom/iris/iris_state.h | 1 + 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index 64f887d9a17d..2a86c27443ea 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -546,14 +546,15 @@ static int iris_hfi_gen1_set_resolution(struct iris_inst *inst) struct hfi_framesize fs; int ret; - fs.buffer_type = HFI_BUFFER_INPUT; - fs.width = inst->fmt_src->fmt.pix_mp.width; - fs.height = inst->fmt_src->fmt.pix_mp.height; - - ret = hfi_gen1_set_property(inst, ptype, &fs, sizeof(fs)); - if (ret) - return ret; + if (!iris_drc_pending(inst)) { + fs.buffer_type = HFI_BUFFER_INPUT; + fs.width = inst->fmt_src->fmt.pix_mp.width; + fs.height = inst->fmt_src->fmt.pix_mp.height; + ret = hfi_gen1_set_property(inst, ptype, &fs, sizeof(fs)); + if (ret) + return ret; + } fs.buffer_type = HFI_BUFFER_OUTPUT2; fs.width = inst->fmt_dst->fmt.pix_mp.width; fs.height = inst->fmt_dst->fmt.pix_mp.height; diff --git a/drivers/media/platform/qcom/iris/iris_state.c b/drivers/media/platform/qcom/iris/iris_state.c index 5976e926c83d..104e1687ad39 100644 --- a/drivers/media/platform/qcom/iris/iris_state.c +++ b/drivers/media/platform/qcom/iris/iris_state.c @@ -245,7 +245,7 @@ int iris_inst_sub_state_change_pause(struct iris_inst *inst, u32 plane) return iris_inst_change_sub_state(inst, 0, set_sub_state); } -static inline bool iris_drc_pending(struct iris_inst *inst) +bool iris_drc_pending(struct iris_inst *inst) { return inst->sub_state & IRIS_INST_SUB_DRC && inst->sub_state & IRIS_INST_SUB_DRC_LAST; diff --git a/drivers/media/platform/qcom/iris/iris_state.h b/drivers/media/platform/qcom/iris/iris_state.h index 78c61aac5e7e..e718386dbe04 100644 --- a/drivers/media/platform/qcom/iris/iris_state.h +++ b/drivers/media/platform/qcom/iris/iris_state.h @@ -140,5 +140,6 @@ int iris_inst_sub_state_change_drain_last(struct iris_inst *inst); int iris_inst_sub_state_change_drc_last(struct iris_inst *inst); int iris_inst_sub_state_change_pause(struct iris_inst *inst, u32 plane); bool iris_allow_cmd(struct iris_inst *inst, u32 cmd); +bool iris_drc_pending(struct iris_inst *inst); #endif From f3516f856d1f11d9dff7d72491d474a2bae1cf8e Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:51 +0530 Subject: [PATCH 177/310] media: iris: Drop port check for session property response Currently, port check enforces that session property response must arrive only on the BITSTREAM port. However, firmware can send some responses on other port as well. Remove the strict port validation to correctly handle session property responses from the firmware. Cc: stable@vger.kernel.org Fixes: 3a19d7b9e08b ("media: iris: implement set properties to firmware during streamon") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index b75a01641d5d..d1a2a497a7b2 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -636,9 +636,6 @@ static int iris_hfi_gen2_handle_session_property(struct iris_inst *inst, { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); - if (pkt->port != HFI_PORT_BITSTREAM) - return 0; - if (pkt->flags & HFI_FW_FLAGS_INFORMATION) return 0; From 2781662dee7bbb9675e5440f5dff4e3991dc5624 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:52 +0530 Subject: [PATCH 178/310] media: iris: Prevent HFI queue writes when core is in deinit state The current check only considers the core error state before allowing writes to the HFI queues. However, the core can also transition to the deinit state due to a system error triggered by the response thread. In such cases, writing to the HFI queues should not be allowed. Fix this by adding a check for the core deinit state, ensuring that writes are rejected when core is not in a valid state. Cc: stable@vger.kernel.org Fixes: fb583a214337 ("media: iris: introduce host firmware interface with necessary hooks") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Reviewed-by: Bryan O'Donoghue Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_queue.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_queue.c b/drivers/media/platform/qcom/iris/iris_hfi_queue.c index fac7df0c4d1a..221dcd09e1e1 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_queue.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_queue.c @@ -113,7 +113,7 @@ int iris_hfi_queue_cmd_write_locked(struct iris_core *core, void *pkt, u32 pkt_s { struct iris_iface_q_info *q_info = &core->command_queue; - if (core->state == IRIS_CORE_ERROR) + if (core->state == IRIS_CORE_ERROR || core->state == IRIS_CORE_DEINIT) return -EINVAL; if (!iris_hfi_queue_write(q_info, pkt, pkt_size)) { From 03e29ab0e94831fcca2f62c96121fd14263b399b Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:53 +0530 Subject: [PATCH 179/310] media: iris: Remove error check for non-zero v4l2 controls Remove the check for non-zero number of v4l2 controls as some SOCs might not expose any capability which requires v4l2 control. Cc: stable@vger.kernel.org Fixes: 33be1dde17e3 ("media: iris: implement iris v4l2_ctrl_ops") Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index b690578256d5..6a514af8108e 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -84,8 +84,6 @@ int iris_ctrls_init(struct iris_inst *inst) if (iris_get_v4l2_id(cap[idx].cap_id)) num_ctrls++; } - if (!num_ctrls) - return -EINVAL; /* Adding 1 to num_ctrls to include V4L2_CID_MIN_BUFFERS_FOR_CAPTURE */ From a693b4a3e7a95c010bedef4c8b3122bd8b0961b7 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:54 +0530 Subject: [PATCH 180/310] media: iris: Remove deprecated property setting to firmware HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER is deprecated and no longer supported on current firmware, remove setting the same to firmware. Cc: stable@vger.kernel.org Fixes: 79865252acb6 ("media: iris: enable video driver probe of SM8250 SoC") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 4 ---- drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c | 8 -------- drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h | 1 - drivers/media/platform/qcom/iris/iris_platform_common.h | 2 +- drivers/media/platform/qcom/iris/iris_platform_sm8250.c | 9 --------- 5 files changed, 1 insertion(+), 23 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index 6a514af8108e..915de101fcba 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -17,8 +17,6 @@ static inline bool iris_valid_cap_id(enum platform_inst_fw_cap_type cap_id) static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) { switch (id) { - case V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER: - return DEBLOCK; case V4L2_CID_MPEG_VIDEO_H264_PROFILE: return PROFILE; case V4L2_CID_MPEG_VIDEO_H264_LEVEL: @@ -34,8 +32,6 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return 0; switch (cap_id) { - case DEBLOCK: - return V4L2_CID_MPEG_VIDEO_DECODER_MPEG4_DEBLOCK_FILTER; case PROFILE: return V4L2_CID_MPEG_VIDEO_H264_PROFILE; case LEVEL: diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index 2a86c27443ea..ce855a20ce4b 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -490,14 +490,6 @@ iris_hfi_gen1_packet_session_set_property(struct hfi_session_set_property_pkt *p packet->shdr.hdr.size += sizeof(u32) + sizeof(*wm); break; } - case HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER: { - struct hfi_enable *en = prop_data; - u32 *in = pdata; - - en->enable = *in; - packet->shdr.hdr.size += sizeof(u32) + sizeof(*en); - break; - } default: return -EINVAL; } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h index 9f246816a286..e178604855c1 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h @@ -65,7 +65,6 @@ #define HFI_PROPERTY_CONFIG_BUFFER_REQUIREMENTS 0x202001 -#define HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER 0x1200001 #define HFI_PROPERTY_PARAM_VDEC_DPB_COUNTS 0x120300e #define HFI_PROPERTY_CONFIG_VDEC_ENTROPY 0x1204004 diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index ac76d9e1ef9c..1dab276431c7 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -89,7 +89,7 @@ enum platform_inst_fw_cap_type { CODED_FRAMES, BIT_DEPTH, RAP_FRAME, - DEBLOCK, + TIER, INST_FW_CAP_MAX, }; diff --git a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c index 5c86fd7b7b6f..543fa2661539 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c +++ b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c @@ -30,15 +30,6 @@ static struct platform_inst_fw_cap inst_fw_cap_sm8250[] = { .hfi_id = HFI_PROPERTY_PARAM_WORK_MODE, .set = iris_set_stage, }, - { - .cap_id = DEBLOCK, - .min = 0, - .max = 1, - .step_or_mask = 1, - .value = 0, - .hfi_id = HFI_PROPERTY_CONFIG_VDEC_POST_LOOP_DEBLOCKER, - .set = iris_set_u32, - }, }; static struct platform_inst_caps platform_inst_cap_sm8250 = { From c314a28cdabe45f050fe7bd403ddeaf4b9c960d1 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:55 +0530 Subject: [PATCH 181/310] media: iris: Fix missing function pointer initialization The function pointers responsible for setting firmware properties were never initialized in the instance capability structure, causing it to remain NULL. As a result, the firmware properties were not being set correctly. Fix this by properly assigning the function pointers from the core capability to the instance capability, ensuring that the properties are correctly applied to the firmware. Cc: stable@vger.kernel.org Fixes: 3a19d7b9e08b ("media: iris: implement set properties to firmware during streamon") Acked-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index 915de101fcba..13f5cf0d0e8a 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -157,6 +157,7 @@ void iris_session_init_caps(struct iris_core *core) core->inst_fw_caps[cap_id].value = caps[i].value; core->inst_fw_caps[cap_id].flags = caps[i].flags; core->inst_fw_caps[cap_id].hfi_id = caps[i].hfi_id; + core->inst_fw_caps[cap_id].set = caps[i].set; } } From 0f837559ccdd275c5a059e6ac4d5034b03409f1d Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:56 +0530 Subject: [PATCH 182/310] media: iris: Fix NULL pointer dereference A warning reported by smatch indicated a possible null pointer dereference where one of the arguments to API "iris_hfi_gen2_handle_system_error" could sometimes be null. To fix this, add a check to validate that the argument passed is not null before accessing its members. Cc: stable@vger.kernel.org Fixes: fb583a214337 ("media: iris: introduce host firmware interface with necessary hooks") Reported-by: Dan Carpenter Closes: https://lore.kernel.org/linux-media/634cc9b8-f099-4b54-8556-d879fb2b5169@stanley.mountain/ Acked-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index d1a2a497a7b2..4488540d1d41 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -265,7 +265,8 @@ static int iris_hfi_gen2_handle_system_error(struct iris_core *core, { struct iris_inst *instance; - dev_err(core->dev, "received system error of type %#x\n", pkt->type); + if (pkt) + dev_err(core->dev, "received system error of type %#x\n", pkt->type); core->state = IRIS_CORE_ERROR; From 58edc8a68de7af45a4c815636595daa530f02c13 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:57 +0530 Subject: [PATCH 183/310] media: iris: Fix typo in depth variable Correct a typo from "dpeth" to "depth". Cc: stable@vger.kernel.org Fixes: 3a19d7b9e08b ("media: iris: implement set properties to firmware during streamon") Acked-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index a908b41e2868..802fa62c26eb 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -178,7 +178,7 @@ static int iris_hfi_gen2_set_crop_offsets(struct iris_inst *inst) sizeof(u64)); } -static int iris_hfi_gen2_set_bit_dpeth(struct iris_inst *inst) +static int iris_hfi_gen2_set_bit_depth(struct iris_inst *inst) { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 port = iris_hfi_gen2_get_port(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); @@ -378,7 +378,7 @@ static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 p {HFI_PROP_BITSTREAM_RESOLUTION, iris_hfi_gen2_set_bitstream_resolution }, {HFI_PROP_CROP_OFFSETS, iris_hfi_gen2_set_crop_offsets }, {HFI_PROP_CODED_FRAMES, iris_hfi_gen2_set_coded_frames }, - {HFI_PROP_LUMA_CHROMA_BIT_DEPTH, iris_hfi_gen2_set_bit_dpeth }, + {HFI_PROP_LUMA_CHROMA_BIT_DEPTH, iris_hfi_gen2_set_bit_depth }, {HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT, iris_hfi_gen2_set_min_output_count }, {HFI_PROP_PIC_ORDER_CNT_TYPE, iris_hfi_gen2_set_picture_order_count }, {HFI_PROP_SIGNAL_COLOR_INFO, iris_hfi_gen2_set_colorspace }, From 9bf58db157139abcd60e425e5718c8e6a917f9dc Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:58 +0530 Subject: [PATCH 184/310] media: iris: Track flush responses to prevent premature completion Currently, two types of flush commands are queued to the firmware, the first flush queued as part of sequence change, does not wait for a response, while the second flush queued as part of stop, expects a completion response before proceeding further. Due to timing issue, the flush response corresponding to the first command could arrive after the second flush is issued. This casuses the driver to incorrectly assume that the second flush has completed, leading to the premature signaling of flush_completion. To address this, introduce a counter to track the number of pending flush responses and signal flush completion only when all expected responses are received. Cc: stable@vger.kernel.org Fixes: 11712ce70f8e ("media: iris: implement vb2 streaming ops") Reviewed-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../platform/qcom/iris/iris_hfi_gen1_command.c | 4 +++- .../platform/qcom/iris/iris_hfi_gen1_response.c | 17 +++++++++++------ .../media/platform/qcom/iris/iris_instance.h | 2 ++ 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index ce855a20ce4b..bd9d86220e61 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -208,8 +208,10 @@ static int iris_hfi_gen1_session_stop(struct iris_inst *inst, u32 plane) flush_pkt.flush_type = flush_type; ret = iris_hfi_queue_cmd_write(core, &flush_pkt, flush_pkt.shdr.hdr.size); - if (!ret) + if (!ret) { + inst->flush_responses_pending++; ret = iris_wait_for_session_response(inst, true); + } } return ret; diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index b72d503dd740..271e14469223 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -207,7 +207,8 @@ static void iris_hfi_gen1_event_seq_changed(struct iris_inst *inst, flush_pkt.shdr.hdr.pkt_type = HFI_CMD_SESSION_FLUSH; flush_pkt.shdr.session_id = inst->session_id; flush_pkt.flush_type = HFI_FLUSH_OUTPUT; - iris_hfi_queue_cmd_write(inst->core, &flush_pkt, flush_pkt.shdr.hdr.size); + if (!iris_hfi_queue_cmd_write(inst->core, &flush_pkt, flush_pkt.shdr.hdr.size)) + inst->flush_responses_pending++; } iris_vdec_src_change(inst); @@ -408,7 +409,9 @@ static void iris_hfi_gen1_session_ftb_done(struct iris_inst *inst, void *packet) flush_pkt.shdr.hdr.pkt_type = HFI_CMD_SESSION_FLUSH; flush_pkt.shdr.session_id = inst->session_id; flush_pkt.flush_type = HFI_FLUSH_OUTPUT; - iris_hfi_queue_cmd_write(core, &flush_pkt, flush_pkt.shdr.hdr.size); + if (!iris_hfi_queue_cmd_write(core, &flush_pkt, flush_pkt.shdr.hdr.size)) + inst->flush_responses_pending++; + iris_inst_sub_state_change_drain_last(inst); return; @@ -558,7 +561,6 @@ static void iris_hfi_gen1_handle_response(struct iris_core *core, void *response const struct iris_hfi_gen1_response_pkt_info *pkt_info; struct device *dev = core->dev; struct hfi_session_pkt *pkt; - struct completion *done; struct iris_inst *inst; bool found = false; u32 i; @@ -619,9 +621,12 @@ static void iris_hfi_gen1_handle_response(struct iris_core *core, void *response if (shdr->error_type != HFI_ERR_NONE) iris_inst_change_state(inst, IRIS_INST_ERROR); - done = pkt_info->pkt == HFI_MSG_SESSION_FLUSH ? - &inst->flush_completion : &inst->completion; - complete(done); + if (pkt_info->pkt == HFI_MSG_SESSION_FLUSH) { + if (!(--inst->flush_responses_pending)) + complete(&inst->flush_completion); + } else { + complete(&inst->completion); + } } mutex_unlock(&inst->lock); diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index caa3c6507006..06a7f1174ad5 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -27,6 +27,7 @@ * @crop: structure of crop info * @completion: structure of signal completions * @flush_completion: structure of signal completions for flush cmd + * @flush_responses_pending: counter to track number of pending flush responses * @fw_caps: array of supported instance firmware capabilities * @buffers: array of different iris buffers * @fw_min_count: minimnum count of buffers needed by fw @@ -57,6 +58,7 @@ struct iris_inst { struct iris_hfi_rect_desc crop; struct completion completion; struct completion flush_completion; + u32 flush_responses_pending; struct platform_inst_fw_cap fw_caps[INST_FW_CAP_MAX]; struct iris_buffers buffers[BUF_TYPE_MAX]; u32 fw_min_count; From 91c6d55b477e1b66578c268214e915dff9f5ea57 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:08:59 +0530 Subject: [PATCH 185/310] media: iris: Fix buffer preparation failure during resolution change When the resolution changes, the driver internally updates the width and height, but the client continue to queue buffers with the older resolution until the last flag is received. This results in a mismatch when the buffers are prepared, causing failure due to outdated size. Introduce a check to prevent size validation during buffer preparation if a resolution reconfiguration is in progress, to handle this. Cc: stable@vger.kernel.org Fixes: 17f2a485ca67 ("media: iris: implement vb2 ops for buf_queue and firmware response") Reviewed-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vb2.c | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vb2.c b/drivers/media/platform/qcom/iris/iris_vb2.c index cdf11feb590b..b3bde10eb6d2 100644 --- a/drivers/media/platform/qcom/iris/iris_vb2.c +++ b/drivers/media/platform/qcom/iris/iris_vb2.c @@ -259,13 +259,14 @@ int iris_vb2_buf_prepare(struct vb2_buffer *vb) return -EINVAL; } - if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && - vb2_plane_size(vb, 0) < iris_get_buffer_size(inst, BUF_OUTPUT)) - return -EINVAL; - if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && - vb2_plane_size(vb, 0) < iris_get_buffer_size(inst, BUF_INPUT)) - return -EINVAL; - + if (!(inst->sub_state & IRIS_INST_SUB_DRC)) { + if (vb->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE && + vb2_plane_size(vb, 0) < iris_get_buffer_size(inst, BUF_OUTPUT)) + return -EINVAL; + if (vb->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && + vb2_plane_size(vb, 0) < iris_get_buffer_size(inst, BUF_INPUT)) + return -EINVAL; + } return 0; } From 7adc11e6abf619d0bb0c05918d5da5b9d4bcb81e Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:00 +0530 Subject: [PATCH 186/310] media: iris: Send V4L2_BUF_FLAG_ERROR for capture buffers with 0 filled length Firmware sends capture buffers with 0 filled length which are not to be displayed and should be dropped by client. To achieve the same, add V4L2_BUF_FLAG_ERROR to such buffers by making sure: - These 0 length buffers are not returned as result of flush. - Its not a buffer with LAST flag enabled which will also have 0 filled length. Cc: stable@vger.kernel.org Fixes: d09100763bed ("media: iris: add support for drain sequence") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index 4488540d1d41..d2cede2fe1b5 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -378,6 +378,11 @@ static int iris_hfi_gen2_handle_output_buffer(struct iris_inst *inst, buf->flags = iris_hfi_gen2_get_driver_buffer_flags(inst, hfi_buffer->flags); + if (!buf->data_size && inst->state == IRIS_INST_STREAMING && + !(hfi_buffer->flags & HFI_BUF_FW_FLAG_LAST)) { + buf->flags |= V4L2_BUF_FLAG_ERROR; + } + return 0; } From 1e27e9ffce59ac41cde71673d74eb368a533cdb2 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:01 +0530 Subject: [PATCH 187/310] media: iris: Skip flush on first sequence change Add a condition to skip the flush operation during the first sequence change event. At this point, the capture queue is not streaming, making the flush unnecessary. Cc: stable@vger.kernel.org Fixes: 84e17adae3e3 ("media: iris: add support for dynamic resolution change") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index 271e14469223..aaad32a70b9e 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -200,7 +200,7 @@ static void iris_hfi_gen1_event_seq_changed(struct iris_inst *inst, iris_hfi_gen1_read_changed_params(inst, pkt); - if (inst->state != IRIS_INST_ERROR) { + if (inst->state != IRIS_INST_ERROR && !(inst->sub_state & IRIS_INST_SUB_FIRST_IPSC)) { reinit_completion(&inst->flush_completion); flush_pkt.shdr.hdr.size = sizeof(struct hfi_session_flush_pkt); From b7a898184e74a8261c34f1265139ac1799ee4e1c Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:02 +0530 Subject: [PATCH 188/310] media: iris: Remove unnecessary re-initialization of flush completion Currently, The flush completion signal is being re-initialized even though no response is expected during a sequence change. Simplify the code by removing re-initialization of flush completion signal as it is redundant. Cc: stable@vger.kernel.org Fixes: 84e17adae3e3 ("media: iris: add support for dynamic resolution change") Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index aaad32a70b9e..c8c0aa23536b 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -201,7 +201,6 @@ static void iris_hfi_gen1_event_seq_changed(struct iris_inst *inst, iris_hfi_gen1_read_changed_params(inst, pkt); if (inst->state != IRIS_INST_ERROR && !(inst->sub_state & IRIS_INST_SUB_FIRST_IPSC)) { - reinit_completion(&inst->flush_completion); flush_pkt.shdr.hdr.size = sizeof(struct hfi_session_flush_pkt); flush_pkt.shdr.hdr.pkt_type = HFI_CMD_SESSION_FLUSH; From b791dcfcba3a0c46fb3e2decab31d2340c5dc313 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:03 +0530 Subject: [PATCH 189/310] media: iris: Add handling for corrupt and drop frames Firmware attach DATACORRUPT/DROP buffer flags for the frames which needs to be dropped, handle it by setting VB2_BUF_STATE_ERROR for these buffers before calling buf_done. Reviewed-by: Bryan O'Donoghue Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_buffer.c | 11 ++++++++--- .../media/platform/qcom/iris/iris_hfi_gen1_defines.h | 2 ++ .../media/platform/qcom/iris/iris_hfi_gen1_response.c | 6 ++++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.c b/drivers/media/platform/qcom/iris/iris_buffer.c index 981fedb000ed..018334512bae 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_buffer.c @@ -611,10 +611,13 @@ int iris_vb2_buffer_done(struct iris_inst *inst, struct iris_buffer *buf) vb2 = &vbuf->vb2_buf; - if (buf->flags & V4L2_BUF_FLAG_ERROR) + if (buf->flags & V4L2_BUF_FLAG_ERROR) { state = VB2_BUF_STATE_ERROR; - else - state = VB2_BUF_STATE_DONE; + vb2_set_plane_payload(vb2, 0, 0); + vb2->timestamp = 0; + v4l2_m2m_buf_done(vbuf, state); + return 0; + } vbuf->flags |= buf->flags; @@ -634,6 +637,8 @@ int iris_vb2_buffer_done(struct iris_inst *inst, struct iris_buffer *buf) v4l2_m2m_mark_stopped(m2m_ctx); } } + + state = VB2_BUF_STATE_DONE; vb2->timestamp = buf->timestamp; v4l2_m2m_buf_done(vbuf, state); diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h index e178604855c1..adffcead58ea 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h @@ -116,6 +116,8 @@ #define HFI_FRAME_NOTCODED 0x7f002000 #define HFI_FRAME_YUV 0x7f004000 #define HFI_UNUSED_PICT 0x10000000 +#define HFI_BUFFERFLAG_DATACORRUPT 0x00000008 +#define HFI_BUFFERFLAG_DROP_FRAME 0x20000000 struct hfi_pkt_hdr { u32 size; diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index c8c0aa23536b..14d8bef62b60 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -483,6 +483,12 @@ static void iris_hfi_gen1_session_ftb_done(struct iris_inst *inst, void *packet) buf->attr |= BUF_ATTR_DEQUEUED; buf->attr |= BUF_ATTR_BUFFER_DONE; + if (hfi_flags & HFI_BUFFERFLAG_DATACORRUPT) + flags |= V4L2_BUF_FLAG_ERROR; + + if (hfi_flags & HFI_BUFFERFLAG_DROP_FRAME) + flags |= V4L2_BUF_FLAG_ERROR; + buf->flags |= flags; iris_vb2_buffer_done(inst, buf); From ac72ad9deafa4b8e8cbb3ce27a95906b915f802f Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:04 +0530 Subject: [PATCH 190/310] media: iris: Add handling for no show frames Firmware sends the picture type as NO_SHOW for frames which are not supposed to be displayed, add handling for the same in driver to drop them. Acked-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h | 1 + drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 806f8bb7f505..666061a612c3 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -113,6 +113,7 @@ enum hfi_picture_type { HFI_PICTURE_I = 0x00000008, HFI_PICTURE_CRA = 0x00000010, HFI_PICTURE_BLA = 0x00000020, + HFI_PICTURE_NOSHOW = 0x00000040, }; enum hfi_buffer_type { diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index d2cede2fe1b5..b6d0ff860d78 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -91,7 +91,9 @@ static int iris_hfi_gen2_get_driver_buffer_flags(struct iris_inst *inst, u32 hfi struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 driver_flags = 0; - if (inst_hfi_gen2->hfi_frame_info.picture_type & keyframe) + if (inst_hfi_gen2->hfi_frame_info.picture_type & HFI_PICTURE_NOSHOW) + driver_flags |= V4L2_BUF_FLAG_ERROR; + else if (inst_hfi_gen2->hfi_frame_info.picture_type & keyframe) driver_flags |= V4L2_BUF_FLAG_KEYFRAME; else if (inst_hfi_gen2->hfi_frame_info.picture_type & HFI_PICTURE_P) driver_flags |= V4L2_BUF_FLAG_PFRAME; From f15cb8652b4f49e7cd217f572b29aa64c21e8970 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:05 +0530 Subject: [PATCH 191/310] media: iris: Improve last flag handling Improve the handling of the V4L2_BUF_FLAG_LAST flag in the driver: - Ensure that the last flag is not sent multiple times. - Attach the last flag to the first capture buffer returned during flush, triggered by a sequence change, addressing cases where the firmware does not set the last flag. Reviewed-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_buffer.c | 1 + drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c | 7 ++++++- drivers/media/platform/qcom/iris/iris_instance.h | 2 ++ drivers/media/platform/qcom/iris/iris_vb2.c | 3 ++- drivers/media/platform/qcom/iris/iris_vdec.c | 2 ++ 5 files changed, 13 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.c b/drivers/media/platform/qcom/iris/iris_buffer.c index 018334512bae..7dbac74b1a8d 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_buffer.c @@ -636,6 +636,7 @@ int iris_vb2_buffer_done(struct iris_inst *inst, struct iris_buffer *buf) v4l2_event_queue_fh(&inst->fh, &ev); v4l2_m2m_mark_stopped(m2m_ctx); } + inst->last_buffer_dequeued = true; } state = VB2_BUF_STATE_DONE; diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index 14d8bef62b60..926acee1f48c 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -457,7 +457,12 @@ static void iris_hfi_gen1_session_ftb_done(struct iris_inst *inst, void *packet) timestamp_us = timestamp_hi; timestamp_us = (timestamp_us << 32) | timestamp_lo; } else { - flags |= V4L2_BUF_FLAG_LAST; + if (pkt->stream_id == 1 && !inst->last_buffer_dequeued) { + if (iris_drc_pending(inst)) { + flags |= V4L2_BUF_FLAG_LAST; + inst->last_buffer_dequeued = true; + } + } } buf->timestamp = timestamp_us; diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index 06a7f1174ad5..5ec6368b2af7 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -43,6 +43,7 @@ * @sequence_out: a sequence counter for output queue * @tss: timestamp metadata * @metadata_idx: index for metadata buffer + * @last_buffer_dequeued: a flag to indicate that last buffer is sent by driver */ struct iris_inst { @@ -74,6 +75,7 @@ struct iris_inst { u32 sequence_out; struct iris_ts_metadata tss[VIDEO_MAX_FRAME]; u32 metadata_idx; + bool last_buffer_dequeued; }; #endif diff --git a/drivers/media/platform/qcom/iris/iris_vb2.c b/drivers/media/platform/qcom/iris/iris_vb2.c index b3bde10eb6d2..8b17c7c39487 100644 --- a/drivers/media/platform/qcom/iris/iris_vb2.c +++ b/drivers/media/platform/qcom/iris/iris_vb2.c @@ -305,7 +305,7 @@ void iris_vb2_buf_queue(struct vb2_buffer *vb2) goto exit; } - if (V4L2_TYPE_IS_CAPTURE(vb2->vb2_queue->type)) { + if (!inst->last_buffer_dequeued && V4L2_TYPE_IS_CAPTURE(vb2->vb2_queue->type)) { if ((inst->sub_state & IRIS_INST_SUB_DRC && inst->sub_state & IRIS_INST_SUB_DRC_LAST) || (inst->sub_state & IRIS_INST_SUB_DRAIN && @@ -319,6 +319,7 @@ void iris_vb2_buf_queue(struct vb2_buffer *vb2) v4l2_event_queue_fh(&inst->fh, &eos); v4l2_m2m_mark_stopped(m2m_ctx); } + inst->last_buffer_dequeued = true; goto exit; } } diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index d342f733feb9..de4e3fe8ed5a 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -487,6 +487,8 @@ static int iris_vdec_process_streamon_output(struct iris_inst *inst) if (ret) return ret; + inst->last_buffer_dequeued = false; + return iris_inst_change_sub_state(inst, clear_sub_state, 0); } From 0aebab4968efad155511a7d1584bb2ab74d8bc42 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:06 +0530 Subject: [PATCH 192/310] media: iris: Remove redundant buffer count check in stream off Currently, the stream off process checks the count of buffers in v4l2_m2m_queues using v4l2_m2m_for_each_src_buf_safe and v4l2_m2m_for_each_dst_buf_safe APIs. If the count is non-zero, it returns an error. This check is redundant as the V4L2 framework already handles buffer management internally. Remove the unnecessary buffer count check in stream off, simplifying the process and relying on V4L2's internal mechanisms for buffer management. Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_vdec.c | 36 -------------------- 1 file changed, 36 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index de4e3fe8ed5a..ce97c555192a 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -244,35 +244,6 @@ void iris_vdec_src_change(struct iris_inst *inst) v4l2_event_queue_fh(&inst->fh, &event); } -static int iris_vdec_get_num_queued_buffers(struct iris_inst *inst, - enum iris_buffer_type type) -{ - struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; - struct v4l2_m2m_buffer *buffer, *n; - struct iris_buffer *buf; - u32 count = 0; - - switch (type) { - case BUF_INPUT: - v4l2_m2m_for_each_src_buf_safe(m2m_ctx, buffer, n) { - buf = to_iris_buffer(&buffer->vb); - if (!(buf->attr & BUF_ATTR_QUEUED)) - continue; - count++; - } - return count; - case BUF_OUTPUT: - v4l2_m2m_for_each_dst_buf_safe(m2m_ctx, buffer, n) { - buf = to_iris_buffer(&buffer->vb); - if (!(buf->attr & BUF_ATTR_QUEUED)) - continue; - count++; - } - return count; - default: - return count; - } -} static void iris_vdec_flush_deferred_buffers(struct iris_inst *inst, enum iris_buffer_type type) @@ -321,7 +292,6 @@ int iris_vdec_session_streamoff(struct iris_inst *inst, u32 plane) { const struct iris_hfi_command_ops *hfi_ops = inst->core->hfi_ops; enum iris_buffer_type buffer_type; - u32 count; int ret; switch (plane) { @@ -339,12 +309,6 @@ int iris_vdec_session_streamoff(struct iris_inst *inst, u32 plane) if (ret) goto error; - count = iris_vdec_get_num_queued_buffers(inst, buffer_type); - if (count) { - ret = -EINVAL; - goto error; - } - ret = iris_inst_state_change_streamoff(inst, plane); if (ret) goto error; From c7501fa5fb85acd54e65c763b1796bf67701eb3c Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:07 +0530 Subject: [PATCH 193/310] media: iris: Add a comment to explain usage of MBPS Add a comment to explain usage of MBPS and define a macro for 8K resolution for better readability Acked-by: Vikash Garodia Reviewed-by: Bryan O'Donoghue Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_platform_common.h | 2 ++ drivers/media/platform/qcom/iris/iris_platform_gen2.c | 4 ++-- drivers/media/platform/qcom/iris/iris_platform_sm8250.c | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 1dab276431c7..3e0ae87526a0 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -21,6 +21,7 @@ struct iris_inst; #define DEFAULT_MAX_HOST_BUF_COUNT 64 #define DEFAULT_MAX_HOST_BURST_BUF_COUNT 256 #define DEFAULT_FPS 30 +#define NUM_MBS_8K ((8192 * 4352) / 256) enum stage_type { STAGE_1 = 1, @@ -172,6 +173,7 @@ struct iris_platform_data { struct ubwc_config_data *ubwc_config; u32 num_vpp_pipe; u32 max_session_count; + /* max number of macroblocks per frame supported */ u32 max_core_mbpf; const u32 *input_config_params; unsigned int input_config_params_size; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index 1e69ba15db0f..deb7037e8e86 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -248,7 +248,7 @@ struct iris_platform_data sm8550_data = { .ubwc_config = &ubwc_config_sm8550, .num_vpp_pipe = 4, .max_session_count = 16, - .max_core_mbpf = ((8192 * 4352) / 256) * 2, + .max_core_mbpf = NUM_MBS_8K * 2, .input_config_params = sm8550_vdec_input_config_params, .input_config_params_size = @@ -308,7 +308,7 @@ struct iris_platform_data sm8650_data = { .ubwc_config = &ubwc_config_sm8550, .num_vpp_pipe = 4, .max_session_count = 16, - .max_core_mbpf = ((8192 * 4352) / 256) * 2, + .max_core_mbpf = NUM_MBS_8K * 2, .input_config_params = sm8550_vdec_input_config_params, .input_config_params_size = diff --git a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c index 543fa2661539..8183e4e95fa4 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c +++ b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c @@ -127,7 +127,7 @@ struct iris_platform_data sm8250_data = { .hw_response_timeout = HW_RESPONSE_TIMEOUT_VALUE, .num_vpp_pipe = 4, .max_session_count = 16, - .max_core_mbpf = (8192 * 4352) / 256, + .max_core_mbpf = NUM_MBS_8K, .input_config_params = sm8250_vdec_input_config_param_default, .input_config_params_size = From fde6161d91bb72791b515bae5741c17e4ab6f8e8 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:08 +0530 Subject: [PATCH 194/310] media: iris: Add HEVC and VP9 formats for decoder Extend the decoder driver's supported formats to include HEVC (H.265) and VP9. This change updates the format enumeration (VIDIOC_ENUM_FMT) and allows setting these formats via VIDIOC_S_FMT. Reviewed-by: Bryan O'Donoghue Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../qcom/iris/iris_hfi_gen1_command.c | 15 +++- .../qcom/iris/iris_hfi_gen1_defines.h | 2 + .../qcom/iris/iris_hfi_gen2_command.c | 14 +++- .../qcom/iris/iris_hfi_gen2_defines.h | 3 + .../media/platform/qcom/iris/iris_instance.h | 2 + drivers/media/platform/qcom/iris/iris_vdec.c | 69 +++++++++++++++++-- drivers/media/platform/qcom/iris/iris_vdec.h | 11 +++ drivers/media/platform/qcom/iris/iris_vidc.c | 3 - 8 files changed, 108 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index bd9d86220e61..dbb1b1dab097 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -88,16 +88,29 @@ static int iris_hfi_gen1_sys_pc_prep(struct iris_core *core) static int iris_hfi_gen1_session_open(struct iris_inst *inst) { struct hfi_session_open_pkt packet; + u32 codec = 0; int ret; if (inst->state != IRIS_INST_DEINIT) return -EALREADY; + switch (inst->codec) { + case V4L2_PIX_FMT_H264: + codec = HFI_VIDEO_CODEC_H264; + break; + case V4L2_PIX_FMT_HEVC: + codec = HFI_VIDEO_CODEC_HEVC; + break; + case V4L2_PIX_FMT_VP9: + codec = HFI_VIDEO_CODEC_VP9; + break; + } + packet.shdr.hdr.size = sizeof(struct hfi_session_open_pkt); packet.shdr.hdr.pkt_type = HFI_CMD_SYS_SESSION_INIT; packet.shdr.session_id = inst->session_id; packet.session_domain = HFI_SESSION_TYPE_DEC; - packet.session_codec = HFI_VIDEO_CODEC_H264; + packet.session_codec = codec; reinit_completion(&inst->completion); diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h index adffcead58ea..d4d119ca98b0 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_defines.h @@ -13,6 +13,8 @@ #define HFI_SESSION_TYPE_DEC 2 #define HFI_VIDEO_CODEC_H264 0x00000002 +#define HFI_VIDEO_CODEC_HEVC 0x00002000 +#define HFI_VIDEO_CODEC_VP9 0x00004000 #define HFI_ERR_NONE 0x0 diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index 802fa62c26eb..f23be2340658 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -416,7 +416,19 @@ static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 p static int iris_hfi_gen2_session_set_codec(struct iris_inst *inst) { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); - u32 codec = HFI_CODEC_DECODE_AVC; + u32 codec = 0; + + switch (inst->codec) { + case V4L2_PIX_FMT_H264: + codec = HFI_CODEC_DECODE_AVC; + break; + case V4L2_PIX_FMT_HEVC: + codec = HFI_CODEC_DECODE_HEVC; + break; + case V4L2_PIX_FMT_VP9: + codec = HFI_CODEC_DECODE_VP9; + break; + } iris_hfi_gen2_packet_session_property(inst, HFI_PROP_CODEC, diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 666061a612c3..283d2f27e4c8 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -104,6 +104,9 @@ enum hfi_color_format { enum hfi_codec_type { HFI_CODEC_DECODE_AVC = 1, HFI_CODEC_ENCODE_AVC = 2, + HFI_CODEC_DECODE_HEVC = 3, + HFI_CODEC_ENCODE_HEVC = 4, + HFI_CODEC_DECODE_VP9 = 5, }; enum hfi_picture_type { diff --git a/drivers/media/platform/qcom/iris/iris_instance.h b/drivers/media/platform/qcom/iris/iris_instance.h index 5ec6368b2af7..0e1f5799b72d 100644 --- a/drivers/media/platform/qcom/iris/iris_instance.h +++ b/drivers/media/platform/qcom/iris/iris_instance.h @@ -43,6 +43,7 @@ * @sequence_out: a sequence counter for output queue * @tss: timestamp metadata * @metadata_idx: index for metadata buffer + * @codec: codec type * @last_buffer_dequeued: a flag to indicate that last buffer is sent by driver */ @@ -75,6 +76,7 @@ struct iris_inst { u32 sequence_out; struct iris_ts_metadata tss[VIDEO_MAX_FRAME]; u32 metadata_idx; + u32 codec; bool last_buffer_dequeued; }; diff --git a/drivers/media/platform/qcom/iris/iris_vdec.c b/drivers/media/platform/qcom/iris/iris_vdec.c index ce97c555192a..d670b51c5839 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.c +++ b/drivers/media/platform/qcom/iris/iris_vdec.c @@ -32,6 +32,7 @@ int iris_vdec_inst_init(struct iris_inst *inst) f->fmt.pix_mp.width = DEFAULT_WIDTH; f->fmt.pix_mp.height = DEFAULT_HEIGHT; f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_H264; + inst->codec = f->fmt.pix_mp.pixelformat; f->fmt.pix_mp.num_planes = 1; f->fmt.pix_mp.plane_fmt[0].bytesperline = 0; f->fmt.pix_mp.plane_fmt[0].sizeimage = iris_get_buffer_size(inst, BUF_INPUT); @@ -67,14 +68,67 @@ void iris_vdec_inst_deinit(struct iris_inst *inst) kfree(inst->fmt_src); } +static const struct iris_fmt iris_vdec_formats[] = { + [IRIS_FMT_H264] = { + .pixfmt = V4L2_PIX_FMT_H264, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_HEVC] = { + .pixfmt = V4L2_PIX_FMT_HEVC, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, + [IRIS_FMT_VP9] = { + .pixfmt = V4L2_PIX_FMT_VP9, + .type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE, + }, +}; + +static const struct iris_fmt * +find_format(struct iris_inst *inst, u32 pixfmt, u32 type) +{ + unsigned int size = ARRAY_SIZE(iris_vdec_formats); + const struct iris_fmt *fmt = iris_vdec_formats; + unsigned int i; + + for (i = 0; i < size; i++) { + if (fmt[i].pixfmt == pixfmt) + break; + } + + if (i == size || fmt[i].type != type) + return NULL; + + return &fmt[i]; +} + +static const struct iris_fmt * +find_format_by_index(struct iris_inst *inst, u32 index, u32 type) +{ + const struct iris_fmt *fmt = iris_vdec_formats; + unsigned int size = ARRAY_SIZE(iris_vdec_formats); + + if (index >= size || fmt[index].type != type) + return NULL; + + return &fmt[index]; +} + int iris_vdec_enum_fmt(struct iris_inst *inst, struct v4l2_fmtdesc *f) { + const struct iris_fmt *fmt; + switch (f->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - f->pixelformat = V4L2_PIX_FMT_H264; + fmt = find_format_by_index(inst, f->index, f->type); + if (!fmt) + return -EINVAL; + + f->pixelformat = fmt->pixfmt; f->flags = V4L2_FMT_FLAG_COMPRESSED | V4L2_FMT_FLAG_DYN_RESOLUTION; break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (f->index) + return -EINVAL; f->pixelformat = V4L2_PIX_FMT_NV12; break; default: @@ -88,13 +142,15 @@ int iris_vdec_try_fmt(struct iris_inst *inst, struct v4l2_format *f) { struct v4l2_pix_format_mplane *pixmp = &f->fmt.pix_mp; struct v4l2_m2m_ctx *m2m_ctx = inst->m2m_ctx; + const struct iris_fmt *fmt; struct v4l2_format *f_inst; struct vb2_queue *src_q; memset(pixmp->reserved, 0, sizeof(pixmp->reserved)); + fmt = find_format(inst, pixmp->pixelformat, f->type); switch (f->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - if (f->fmt.pix_mp.pixelformat != V4L2_PIX_FMT_H264) { + if (!fmt) { f_inst = inst->fmt_src; f->fmt.pix_mp.width = f_inst->fmt.pix_mp.width; f->fmt.pix_mp.height = f_inst->fmt.pix_mp.height; @@ -102,7 +158,7 @@ int iris_vdec_try_fmt(struct iris_inst *inst, struct v4l2_format *f) } break; case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: - if (f->fmt.pix_mp.pixelformat != V4L2_PIX_FMT_NV12) { + if (!fmt) { f_inst = inst->fmt_dst; f->fmt.pix_mp.pixelformat = f_inst->fmt.pix_mp.pixelformat; f->fmt.pix_mp.width = f_inst->fmt.pix_mp.width; @@ -145,13 +201,14 @@ int iris_vdec_s_fmt(struct iris_inst *inst, struct v4l2_format *f) switch (f->type) { case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: - if (f->fmt.pix_mp.pixelformat != V4L2_PIX_FMT_H264) + if (!(find_format(inst, f->fmt.pix_mp.pixelformat, f->type))) return -EINVAL; fmt = inst->fmt_src; fmt->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; - - codec_align = DEFAULT_CODEC_ALIGNMENT; + fmt->fmt.pix_mp.pixelformat = f->fmt.pix_mp.pixelformat; + inst->codec = fmt->fmt.pix_mp.pixelformat; + codec_align = inst->codec == V4L2_PIX_FMT_HEVC ? 32 : 16; fmt->fmt.pix_mp.width = ALIGN(f->fmt.pix_mp.width, codec_align); fmt->fmt.pix_mp.height = ALIGN(f->fmt.pix_mp.height, codec_align); fmt->fmt.pix_mp.num_planes = 1; diff --git a/drivers/media/platform/qcom/iris/iris_vdec.h b/drivers/media/platform/qcom/iris/iris_vdec.h index b24932dc511a..cd7aab66dc7c 100644 --- a/drivers/media/platform/qcom/iris/iris_vdec.h +++ b/drivers/media/platform/qcom/iris/iris_vdec.h @@ -8,6 +8,17 @@ struct iris_inst; +enum iris_fmt_type { + IRIS_FMT_H264, + IRIS_FMT_HEVC, + IRIS_FMT_VP9, +}; + +struct iris_fmt { + u32 pixfmt; + u32 type; +}; + int iris_vdec_inst_init(struct iris_inst *inst); void iris_vdec_inst_deinit(struct iris_inst *inst); int iris_vdec_enum_fmt(struct iris_inst *inst, struct v4l2_fmtdesc *f); diff --git a/drivers/media/platform/qcom/iris/iris_vidc.c b/drivers/media/platform/qcom/iris/iris_vidc.c index a8144595cc78..c417e8c31f80 100644 --- a/drivers/media/platform/qcom/iris/iris_vidc.c +++ b/drivers/media/platform/qcom/iris/iris_vidc.c @@ -278,9 +278,6 @@ static int iris_enum_fmt(struct file *filp, void *fh, struct v4l2_fmtdesc *f) { struct iris_inst *inst = iris_get_inst(filp, NULL); - if (f->index) - return -EINVAL; - return iris_vdec_enum_fmt(inst, f); } From f44ef2d4058bdde343431f05a7cbcb9982c4b8f6 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:09 +0530 Subject: [PATCH 195/310] media: iris: Add platform capabilities for HEVC and VP9 decoders Add platform capabilities for HEVC and VP9 codecs in decoder driver with related hooks. Reviewed-by: Bryan O'Donoghue Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_ctrls.c | 28 +++- .../qcom/iris/iris_hfi_gen2_command.c | 28 +++- .../qcom/iris/iris_hfi_gen2_defines.h | 1 + .../qcom/iris/iris_hfi_gen2_response.c | 34 ++++- .../platform/qcom/iris/iris_platform_common.h | 8 +- .../platform/qcom/iris/iris_platform_gen2.c | 80 ++++++++++- .../qcom/iris/iris_platform_qcs8300.h | 126 ++++++++++++++---- 7 files changed, 266 insertions(+), 39 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_ctrls.c b/drivers/media/platform/qcom/iris/iris_ctrls.c index 13f5cf0d0e8a..9136b723c0f2 100644 --- a/drivers/media/platform/qcom/iris/iris_ctrls.c +++ b/drivers/media/platform/qcom/iris/iris_ctrls.c @@ -18,9 +18,19 @@ static enum platform_inst_fw_cap_type iris_get_cap_id(u32 id) { switch (id) { case V4L2_CID_MPEG_VIDEO_H264_PROFILE: - return PROFILE; + return PROFILE_H264; + case V4L2_CID_MPEG_VIDEO_HEVC_PROFILE: + return PROFILE_HEVC; + case V4L2_CID_MPEG_VIDEO_VP9_PROFILE: + return PROFILE_VP9; case V4L2_CID_MPEG_VIDEO_H264_LEVEL: - return LEVEL; + return LEVEL_H264; + case V4L2_CID_MPEG_VIDEO_HEVC_LEVEL: + return LEVEL_HEVC; + case V4L2_CID_MPEG_VIDEO_VP9_LEVEL: + return LEVEL_VP9; + case V4L2_CID_MPEG_VIDEO_HEVC_TIER: + return TIER; default: return INST_FW_CAP_MAX; } @@ -32,10 +42,20 @@ static u32 iris_get_v4l2_id(enum platform_inst_fw_cap_type cap_id) return 0; switch (cap_id) { - case PROFILE: + case PROFILE_H264: return V4L2_CID_MPEG_VIDEO_H264_PROFILE; - case LEVEL: + case PROFILE_HEVC: + return V4L2_CID_MPEG_VIDEO_HEVC_PROFILE; + case PROFILE_VP9: + return V4L2_CID_MPEG_VIDEO_VP9_PROFILE; + case LEVEL_H264: return V4L2_CID_MPEG_VIDEO_H264_LEVEL; + case LEVEL_HEVC: + return V4L2_CID_MPEG_VIDEO_HEVC_LEVEL; + case LEVEL_VP9: + return V4L2_CID_MPEG_VIDEO_VP9_LEVEL; + case TIER: + return V4L2_CID_MPEG_VIDEO_HEVC_TIER; default: return 0; } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index f23be2340658..8c91d336ff7e 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -295,7 +295,19 @@ static int iris_hfi_gen2_set_profile(struct iris_inst *inst) { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 port = iris_hfi_gen2_get_port(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); - u32 profile = inst->fw_caps[PROFILE].value; + u32 profile = 0; + + switch (inst->codec) { + case V4L2_PIX_FMT_HEVC: + profile = inst->fw_caps[PROFILE_HEVC].value; + break; + case V4L2_PIX_FMT_VP9: + profile = inst->fw_caps[PROFILE_VP9].value; + break; + case V4L2_PIX_FMT_H264: + profile = inst->fw_caps[PROFILE_H264].value; + break; + } inst_hfi_gen2->src_subcr_params.profile = profile; @@ -312,7 +324,19 @@ static int iris_hfi_gen2_set_level(struct iris_inst *inst) { struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); u32 port = iris_hfi_gen2_get_port(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); - u32 level = inst->fw_caps[LEVEL].value; + u32 level = 0; + + switch (inst->codec) { + case V4L2_PIX_FMT_HEVC: + level = inst->fw_caps[LEVEL_HEVC].value; + break; + case V4L2_PIX_FMT_VP9: + level = inst->fw_caps[LEVEL_VP9].value; + break; + case V4L2_PIX_FMT_H264: + level = inst->fw_caps[LEVEL_H264].value; + break; + } inst_hfi_gen2->src_subcr_params.level = level; diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h index 283d2f27e4c8..5f13dc11bea5 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_defines.h @@ -46,6 +46,7 @@ #define HFI_PROP_CROP_OFFSETS 0x03000105 #define HFI_PROP_PROFILE 0x03000107 #define HFI_PROP_LEVEL 0x03000108 +#define HFI_PROP_TIER 0x03000109 #define HFI_PROP_STAGE 0x0300010a #define HFI_PROP_PIPE 0x0300010b #define HFI_PROP_LUMA_CHROMA_BIT_DEPTH 0x0300010f diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index b6d0ff860d78..8e54962414ae 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -571,8 +571,21 @@ static void iris_hfi_gen2_read_input_subcr_params(struct iris_inst *inst) inst->crop.width = pixmp_ip->width - ((subsc_params.crop_offsets[1] >> 16) & 0xFFFF) - inst->crop.left; - inst->fw_caps[PROFILE].value = subsc_params.profile; - inst->fw_caps[LEVEL].value = subsc_params.level; + switch (inst->codec) { + case V4L2_PIX_FMT_HEVC: + inst->fw_caps[PROFILE_HEVC].value = subsc_params.profile; + inst->fw_caps[LEVEL_HEVC].value = subsc_params.level; + break; + case V4L2_PIX_FMT_VP9: + inst->fw_caps[PROFILE_VP9].value = subsc_params.profile; + inst->fw_caps[LEVEL_VP9].value = subsc_params.level; + break; + case V4L2_PIX_FMT_H264: + inst->fw_caps[PROFILE_H264].value = subsc_params.profile; + inst->fw_caps[LEVEL_H264].value = subsc_params.level; + break; + } + inst->fw_caps[POC].value = subsc_params.pic_order_cnt; if (subsc_params.bit_depth != BIT_DEPTH_8 || @@ -796,8 +809,21 @@ static void iris_hfi_gen2_init_src_change_param(struct iris_inst *inst) full_range, video_format, video_signal_type_present_flag); - subsc_params->profile = inst->fw_caps[PROFILE].value; - subsc_params->level = inst->fw_caps[LEVEL].value; + switch (inst->codec) { + case V4L2_PIX_FMT_HEVC: + subsc_params->profile = inst->fw_caps[PROFILE_HEVC].value; + subsc_params->level = inst->fw_caps[LEVEL_HEVC].value; + break; + case V4L2_PIX_FMT_VP9: + subsc_params->profile = inst->fw_caps[PROFILE_VP9].value; + subsc_params->level = inst->fw_caps[LEVEL_VP9].value; + break; + case V4L2_PIX_FMT_H264: + subsc_params->profile = inst->fw_caps[PROFILE_H264].value; + subsc_params->level = inst->fw_caps[LEVEL_H264].value; + break; + } + subsc_params->pic_order_cnt = inst->fw_caps[POC].value; subsc_params->bit_depth = inst->fw_caps[BIT_DEPTH].value; if (inst->fw_caps[CODED_FRAMES].value == diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 3e0ae87526a0..71d23214f224 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -81,8 +81,12 @@ struct platform_inst_caps { }; enum platform_inst_fw_cap_type { - PROFILE = 1, - LEVEL, + PROFILE_H264 = 1, + PROFILE_HEVC, + PROFILE_VP9, + LEVEL_H264, + LEVEL_HEVC, + LEVEL_VP9, INPUT_BUF_HOST_MAX_COUNT, STAGE, PIPE, diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index deb7037e8e86..c2cded2876b7 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -17,7 +17,7 @@ static struct platform_inst_fw_cap inst_fw_cap_sm8550[] = { { - .cap_id = PROFILE, + .cap_id = PROFILE_H264, .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, .max = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH, .step_or_mask = BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | @@ -31,7 +31,29 @@ static struct platform_inst_fw_cap inst_fw_cap_sm8550[] = { .set = iris_set_u32_enum, }, { - .cap_id = LEVEL, + .cap_id = PROFILE_HEVC, + .min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .max = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN) | + BIT(V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE), + .value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .hfi_id = HFI_PROP_PROFILE, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = PROFILE_VP9, + .min = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .max = V4L2_MPEG_VIDEO_VP9_PROFILE_2, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_VP9_PROFILE_0) | + BIT(V4L2_MPEG_VIDEO_VP9_PROFILE_2), + .value = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .hfi_id = HFI_PROP_PROFILE, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = LEVEL_H264, .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, .max = V4L2_MPEG_VIDEO_H264_LEVEL_6_2, .step_or_mask = BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) | @@ -59,6 +81,60 @@ static struct platform_inst_fw_cap inst_fw_cap_sm8550[] = { .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, + { + .cap_id = LEVEL_HEVC, + .min = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + .max = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_2) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_3) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_4) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2), + .value = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1, + .hfi_id = HFI_PROP_LEVEL, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = LEVEL_VP9, + .min = V4L2_MPEG_VIDEO_VP9_LEVEL_1_0, + .max = V4L2_MPEG_VIDEO_VP9_LEVEL_6_0, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_1_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_1_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_6_0), + .value = V4L2_MPEG_VIDEO_VP9_LEVEL_6_0, + .hfi_id = HFI_PROP_LEVEL, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = TIER, + .min = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN, + .max = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_TIER_MAIN) | + BIT(V4L2_MPEG_VIDEO_HEVC_TIER_HIGH), + .value = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, + .hfi_id = HFI_PROP_TIER, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, { .cap_id = INPUT_BUF_HOST_MAX_COUNT, .min = DEFAULT_MAX_HOST_BUF_COUNT, diff --git a/drivers/media/platform/qcom/iris/iris_platform_qcs8300.h b/drivers/media/platform/qcom/iris/iris_platform_qcs8300.h index f82355d72fcf..a8d66ed388a3 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_qcs8300.h +++ b/drivers/media/platform/qcom/iris/iris_platform_qcs8300.h @@ -5,48 +5,124 @@ static struct platform_inst_fw_cap inst_fw_cap_qcs8300[] = { { - .cap_id = PROFILE, + .cap_id = PROFILE_H264, .min = V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE, .max = V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH, .step_or_mask = BIT(V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) | - BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH) | - BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | - BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | - BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH), + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_HIGH) | + BIT(V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_HIGH), .value = V4L2_MPEG_VIDEO_H264_PROFILE_HIGH, .hfi_id = HFI_PROP_PROFILE, .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, { - .cap_id = LEVEL, + .cap_id = PROFILE_HEVC, + .min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .max = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN) | + BIT(V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE), + .value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, + .hfi_id = HFI_PROP_PROFILE, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = PROFILE_VP9, + .min = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .max = V4L2_MPEG_VIDEO_VP9_PROFILE_2, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_VP9_PROFILE_0) | + BIT(V4L2_MPEG_VIDEO_VP9_PROFILE_2), + .value = V4L2_MPEG_VIDEO_VP9_PROFILE_0, + .hfi_id = HFI_PROP_PROFILE, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = LEVEL_H264, .min = V4L2_MPEG_VIDEO_H264_LEVEL_1_0, .max = V4L2_MPEG_VIDEO_H264_LEVEL_6_2, .step_or_mask = BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_2) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_0) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_1) | - BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_2), + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1B) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_1_3) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_2_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_3_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_4_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_0) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_1) | + BIT(V4L2_MPEG_VIDEO_H264_LEVEL_6_2), .value = V4L2_MPEG_VIDEO_H264_LEVEL_6_1, .hfi_id = HFI_PROP_LEVEL, .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, .set = iris_set_u32_enum, }, + { + .cap_id = LEVEL_HEVC, + .min = V4L2_MPEG_VIDEO_HEVC_LEVEL_1, + .max = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_2) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_3) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_4) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1) | + BIT(V4L2_MPEG_VIDEO_HEVC_LEVEL_6_2), + .value = V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1, + .hfi_id = HFI_PROP_LEVEL, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = LEVEL_VP9, + .min = V4L2_MPEG_VIDEO_VP9_LEVEL_1_0, + .max = V4L2_MPEG_VIDEO_VP9_LEVEL_6_0, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_1_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_1_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_2_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_2_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_3_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_3_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_4_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_4_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_0) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_1) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_5_2) | + BIT(V4L2_MPEG_VIDEO_VP9_LEVEL_6_0), + .value = V4L2_MPEG_VIDEO_VP9_LEVEL_6_0, + .hfi_id = HFI_PROP_LEVEL, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, + { + .cap_id = TIER, + .min = V4L2_MPEG_VIDEO_HEVC_TIER_MAIN, + .max = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, + .step_or_mask = BIT(V4L2_MPEG_VIDEO_HEVC_TIER_MAIN) | + BIT(V4L2_MPEG_VIDEO_HEVC_TIER_HIGH), + .value = V4L2_MPEG_VIDEO_HEVC_TIER_HIGH, + .hfi_id = HFI_PROP_TIER, + .flags = CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU, + .set = iris_set_u32_enum, + }, { .cap_id = INPUT_BUF_HOST_MAX_COUNT, .min = DEFAULT_MAX_HOST_BUF_COUNT, From 820ac7fe2796958cb3c398c050fb213d290afd19 Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:10 +0530 Subject: [PATCH 196/310] media: iris: Set mandatory properties for HEVC and VP9 decoders. Subscribe and set mandatory properties to the firmware for HEVC and VP9 decoders. Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../platform/qcom/iris/iris_hfi_common.h | 1 + .../qcom/iris/iris_hfi_gen1_command.c | 4 +- .../qcom/iris/iris_hfi_gen2_command.c | 97 +++++++++++++-- .../qcom/iris/iris_hfi_gen2_response.c | 7 ++ .../platform/qcom/iris/iris_platform_common.h | 16 ++- .../platform/qcom/iris/iris_platform_gen2.c | 114 +++++++++++++++--- .../platform/qcom/iris/iris_platform_sm8250.c | 4 +- 7 files changed, 203 insertions(+), 40 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_common.h b/drivers/media/platform/qcom/iris/iris_hfi_common.h index b2c541367fc6..9e6aadb83783 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_common.h +++ b/drivers/media/platform/qcom/iris/iris_hfi_common.h @@ -140,6 +140,7 @@ struct hfi_subscription_params { u32 color_info; u32 profile; u32 level; + u32 tier; }; u32 iris_hfi_get_v4l2_color_primaries(u32 hfi_primaries); diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index dbb1b1dab097..2e3f5a6b2ff1 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -776,8 +776,8 @@ static int iris_hfi_gen1_session_set_config_params(struct iris_inst *inst, u32 p iris_hfi_gen1_set_bufsize}, }; - config_params = core->iris_platform_data->input_config_params; - config_params_size = core->iris_platform_data->input_config_params_size; + config_params = core->iris_platform_data->input_config_params_default; + config_params_size = core->iris_platform_data->input_config_params_default_size; if (V4L2_TYPE_IS_OUTPUT(plane)) { for (i = 0; i < config_params_size; i++) { diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c index 8c91d336ff7e..7ca5ae13d62b 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_command.c @@ -391,11 +391,28 @@ static int iris_hfi_gen2_set_linear_stride_scanline(struct iris_inst *inst) sizeof(u64)); } +static int iris_hfi_gen2_set_tier(struct iris_inst *inst) +{ + struct iris_inst_hfi_gen2 *inst_hfi_gen2 = to_iris_inst_hfi_gen2(inst); + u32 port = iris_hfi_gen2_get_port(V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE); + u32 tier = inst->fw_caps[TIER].value; + + inst_hfi_gen2->src_subcr_params.tier = tier; + + return iris_hfi_gen2_session_set_property(inst, + HFI_PROP_TIER, + HFI_HOST_FLAGS_NONE, + port, + HFI_PAYLOAD_U32_ENUM, + &tier, + sizeof(u32)); +} + static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 plane) { struct iris_core *core = inst->core; - u32 config_params_size, i, j; - const u32 *config_params; + u32 config_params_size = 0, i, j; + const u32 *config_params = NULL; int ret; static const struct iris_hfi_prop_type_handle prop_type_handle_arr[] = { @@ -410,11 +427,27 @@ static int iris_hfi_gen2_session_set_config_params(struct iris_inst *inst, u32 p {HFI_PROP_LEVEL, iris_hfi_gen2_set_level }, {HFI_PROP_COLOR_FORMAT, iris_hfi_gen2_set_colorformat }, {HFI_PROP_LINEAR_STRIDE_SCANLINE, iris_hfi_gen2_set_linear_stride_scanline }, + {HFI_PROP_TIER, iris_hfi_gen2_set_tier }, }; if (V4L2_TYPE_IS_OUTPUT(plane)) { - config_params = core->iris_platform_data->input_config_params; - config_params_size = core->iris_platform_data->input_config_params_size; + switch (inst->codec) { + case V4L2_PIX_FMT_H264: + config_params = core->iris_platform_data->input_config_params_default; + config_params_size = + core->iris_platform_data->input_config_params_default_size; + break; + case V4L2_PIX_FMT_HEVC: + config_params = core->iris_platform_data->input_config_params_hevc; + config_params_size = + core->iris_platform_data->input_config_params_hevc_size; + break; + case V4L2_PIX_FMT_VP9: + config_params = core->iris_platform_data->input_config_params_vp9; + config_params_size = + core->iris_platform_data->input_config_params_vp9_size; + break; + } } else { config_params = core->iris_platform_data->output_config_params; config_params_size = core->iris_platform_data->output_config_params_size; @@ -584,8 +617,8 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan struct hfi_subscription_params subsc_params; u32 prop_type, payload_size, payload_type; struct iris_core *core = inst->core; - const u32 *change_param; - u32 change_param_size; + const u32 *change_param = NULL; + u32 change_param_size = 0; u32 payload[32] = {0}; u32 hfi_port = 0, i; int ret; @@ -596,8 +629,23 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan return 0; } - change_param = core->iris_platform_data->input_config_params; - change_param_size = core->iris_platform_data->input_config_params_size; + switch (inst->codec) { + case V4L2_PIX_FMT_H264: + change_param = core->iris_platform_data->input_config_params_default; + change_param_size = + core->iris_platform_data->input_config_params_default_size; + break; + case V4L2_PIX_FMT_HEVC: + change_param = core->iris_platform_data->input_config_params_hevc; + change_param_size = + core->iris_platform_data->input_config_params_hevc_size; + break; + case V4L2_PIX_FMT_VP9: + change_param = core->iris_platform_data->input_config_params_vp9; + change_param_size = + core->iris_platform_data->input_config_params_vp9_size; + break; + } payload[0] = HFI_MODE_PORT_SETTINGS_CHANGE; @@ -644,6 +692,11 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan payload_size = sizeof(u32); payload_type = HFI_PAYLOAD_U32; break; + case HFI_PROP_LUMA_CHROMA_BIT_DEPTH: + payload[0] = subsc_params.bit_depth; + payload_size = sizeof(u32); + payload_type = HFI_PAYLOAD_U32; + break; case HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT: payload[0] = subsc_params.fw_min_count; payload_size = sizeof(u32); @@ -669,6 +722,11 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan payload_size = sizeof(u32); payload_type = HFI_PAYLOAD_U32; break; + case HFI_PROP_TIER: + payload[0] = subsc_params.tier; + payload_size = sizeof(u32); + payload_type = HFI_PAYLOAD_U32; + break; default: prop_type = 0; ret = -EINVAL; @@ -695,8 +753,8 @@ static int iris_hfi_gen2_subscribe_change_param(struct iris_inst *inst, u32 plan static int iris_hfi_gen2_subscribe_property(struct iris_inst *inst, u32 plane) { struct iris_core *core = inst->core; - u32 subscribe_prop_size, i; - const u32 *subcribe_prop; + u32 subscribe_prop_size = 0, i; + const u32 *subcribe_prop = NULL; u32 payload[32] = {0}; payload[0] = HFI_MODE_PROPERTY; @@ -705,8 +763,23 @@ static int iris_hfi_gen2_subscribe_property(struct iris_inst *inst, u32 plane) subscribe_prop_size = core->iris_platform_data->dec_input_prop_size; subcribe_prop = core->iris_platform_data->dec_input_prop; } else { - subscribe_prop_size = core->iris_platform_data->dec_output_prop_size; - subcribe_prop = core->iris_platform_data->dec_output_prop; + switch (inst->codec) { + case V4L2_PIX_FMT_H264: + subcribe_prop = core->iris_platform_data->dec_output_prop_avc; + subscribe_prop_size = + core->iris_platform_data->dec_output_prop_avc_size; + break; + case V4L2_PIX_FMT_HEVC: + subcribe_prop = core->iris_platform_data->dec_output_prop_hevc; + subscribe_prop_size = + core->iris_platform_data->dec_output_prop_hevc_size; + break; + case V4L2_PIX_FMT_VP9: + subcribe_prop = core->iris_platform_data->dec_output_prop_vp9; + subscribe_prop_size = + core->iris_platform_data->dec_output_prop_vp9_size; + break; + } } for (i = 0; i < subscribe_prop_size; i++) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c index 8e54962414ae..a8c30fc5c0d0 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen2_response.c @@ -587,6 +587,7 @@ static void iris_hfi_gen2_read_input_subcr_params(struct iris_inst *inst) } inst->fw_caps[POC].value = subsc_params.pic_order_cnt; + inst->fw_caps[TIER].value = subsc_params.tier; if (subsc_params.bit_depth != BIT_DEPTH_8 || !(subsc_params.coded_frames & HFI_BITMASK_FRAME_MBS_ONLY_FLAG)) { @@ -668,6 +669,9 @@ static int iris_hfi_gen2_handle_session_property(struct iris_inst *inst, inst_hfi_gen2->src_subcr_params.crop_offsets[0] = pkt->payload[0]; inst_hfi_gen2->src_subcr_params.crop_offsets[1] = pkt->payload[1]; break; + case HFI_PROP_LUMA_CHROMA_BIT_DEPTH: + inst_hfi_gen2->src_subcr_params.bit_depth = pkt->payload[0]; + break; case HFI_PROP_CODED_FRAMES: inst_hfi_gen2->src_subcr_params.coded_frames = pkt->payload[0]; break; @@ -686,6 +690,9 @@ static int iris_hfi_gen2_handle_session_property(struct iris_inst *inst, case HFI_PROP_LEVEL: inst_hfi_gen2->src_subcr_params.level = pkt->payload[0]; break; + case HFI_PROP_TIER: + inst_hfi_gen2->src_subcr_params.tier = pkt->payload[0]; + break; case HFI_PROP_PICTURE_TYPE: inst_hfi_gen2->hfi_frame_info.picture_type = pkt->payload[0]; break; diff --git a/drivers/media/platform/qcom/iris/iris_platform_common.h b/drivers/media/platform/qcom/iris/iris_platform_common.h index 71d23214f224..adafdce8a856 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_common.h +++ b/drivers/media/platform/qcom/iris/iris_platform_common.h @@ -179,14 +179,22 @@ struct iris_platform_data { u32 max_session_count; /* max number of macroblocks per frame supported */ u32 max_core_mbpf; - const u32 *input_config_params; - unsigned int input_config_params_size; + const u32 *input_config_params_default; + unsigned int input_config_params_default_size; + const u32 *input_config_params_hevc; + unsigned int input_config_params_hevc_size; + const u32 *input_config_params_vp9; + unsigned int input_config_params_vp9_size; const u32 *output_config_params; unsigned int output_config_params_size; const u32 *dec_input_prop; unsigned int dec_input_prop_size; - const u32 *dec_output_prop; - unsigned int dec_output_prop_size; + const u32 *dec_output_prop_avc; + unsigned int dec_output_prop_avc_size; + const u32 *dec_output_prop_hevc; + unsigned int dec_output_prop_hevc_size; + const u32 *dec_output_prop_vp9; + unsigned int dec_output_prop_vp9_size; const u32 *dec_ip_int_buf_tbl; unsigned int dec_ip_int_buf_tbl_size; const u32 *dec_op_int_buf_tbl; diff --git a/drivers/media/platform/qcom/iris/iris_platform_gen2.c b/drivers/media/platform/qcom/iris/iris_platform_gen2.c index c2cded2876b7..d3026b2bcb70 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_gen2.c +++ b/drivers/media/platform/qcom/iris/iris_platform_gen2.c @@ -257,9 +257,10 @@ static struct tz_cp_config tz_cp_config_sm8550 = { .cp_nonpixel_size = 0x24800000, }; -static const u32 sm8550_vdec_input_config_params[] = { +static const u32 sm8550_vdec_input_config_params_default[] = { HFI_PROP_BITSTREAM_RESOLUTION, HFI_PROP_CROP_OFFSETS, + HFI_PROP_LUMA_CHROMA_BIT_DEPTH, HFI_PROP_CODED_FRAMES, HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT, HFI_PROP_PIC_ORDER_CNT_TYPE, @@ -268,6 +269,26 @@ static const u32 sm8550_vdec_input_config_params[] = { HFI_PROP_SIGNAL_COLOR_INFO, }; +static const u32 sm8550_vdec_input_config_param_hevc[] = { + HFI_PROP_BITSTREAM_RESOLUTION, + HFI_PROP_CROP_OFFSETS, + HFI_PROP_LUMA_CHROMA_BIT_DEPTH, + HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT, + HFI_PROP_PROFILE, + HFI_PROP_LEVEL, + HFI_PROP_TIER, + HFI_PROP_SIGNAL_COLOR_INFO, +}; + +static const u32 sm8550_vdec_input_config_param_vp9[] = { + HFI_PROP_BITSTREAM_RESOLUTION, + HFI_PROP_CROP_OFFSETS, + HFI_PROP_LUMA_CHROMA_BIT_DEPTH, + HFI_PROP_BUFFER_FW_MIN_OUTPUT_COUNT, + HFI_PROP_PROFILE, + HFI_PROP_LEVEL, +}; + static const u32 sm8550_vdec_output_config_params[] = { HFI_PROP_COLOR_FORMAT, HFI_PROP_LINEAR_STRIDE_SCANLINE, @@ -277,11 +298,19 @@ static const u32 sm8550_vdec_subscribe_input_properties[] = { HFI_PROP_NO_OUTPUT, }; -static const u32 sm8550_vdec_subscribe_output_properties[] = { +static const u32 sm8550_vdec_subscribe_output_properties_avc[] = { HFI_PROP_PICTURE_TYPE, HFI_PROP_CABAC_SESSION, }; +static const u32 sm8550_vdec_subscribe_output_properties_hevc[] = { + HFI_PROP_PICTURE_TYPE, +}; + +static const u32 sm8550_vdec_subscribe_output_properties_vp9[] = { + HFI_PROP_PICTURE_TYPE, +}; + static const u32 sm8550_dec_ip_int_buf_tbl[] = { BUF_BIN, BUF_COMV, @@ -325,18 +354,33 @@ struct iris_platform_data sm8550_data = { .num_vpp_pipe = 4, .max_session_count = 16, .max_core_mbpf = NUM_MBS_8K * 2, - .input_config_params = - sm8550_vdec_input_config_params, - .input_config_params_size = - ARRAY_SIZE(sm8550_vdec_input_config_params), + .input_config_params_default = + sm8550_vdec_input_config_params_default, + .input_config_params_default_size = + ARRAY_SIZE(sm8550_vdec_input_config_params_default), + .input_config_params_hevc = + sm8550_vdec_input_config_param_hevc, + .input_config_params_hevc_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_hevc), + .input_config_params_vp9 = + sm8550_vdec_input_config_param_vp9, + .input_config_params_vp9_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), .output_config_params = sm8550_vdec_output_config_params, .output_config_params_size = ARRAY_SIZE(sm8550_vdec_output_config_params), .dec_input_prop = sm8550_vdec_subscribe_input_properties, .dec_input_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_input_properties), - .dec_output_prop = sm8550_vdec_subscribe_output_properties, - .dec_output_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties), + .dec_output_prop_avc = sm8550_vdec_subscribe_output_properties_avc, + .dec_output_prop_avc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_avc), + .dec_output_prop_hevc = sm8550_vdec_subscribe_output_properties_hevc, + .dec_output_prop_hevc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_hevc), + .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, + .dec_output_prop_vp9_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), @@ -385,18 +429,33 @@ struct iris_platform_data sm8650_data = { .num_vpp_pipe = 4, .max_session_count = 16, .max_core_mbpf = NUM_MBS_8K * 2, - .input_config_params = - sm8550_vdec_input_config_params, - .input_config_params_size = - ARRAY_SIZE(sm8550_vdec_input_config_params), + .input_config_params_default = + sm8550_vdec_input_config_params_default, + .input_config_params_default_size = + ARRAY_SIZE(sm8550_vdec_input_config_params_default), + .input_config_params_hevc = + sm8550_vdec_input_config_param_hevc, + .input_config_params_hevc_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_hevc), + .input_config_params_vp9 = + sm8550_vdec_input_config_param_vp9, + .input_config_params_vp9_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), .output_config_params = sm8550_vdec_output_config_params, .output_config_params_size = ARRAY_SIZE(sm8550_vdec_output_config_params), .dec_input_prop = sm8550_vdec_subscribe_input_properties, .dec_input_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_input_properties), - .dec_output_prop = sm8550_vdec_subscribe_output_properties, - .dec_output_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties), + .dec_output_prop_avc = sm8550_vdec_subscribe_output_properties_avc, + .dec_output_prop_avc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_avc), + .dec_output_prop_hevc = sm8550_vdec_subscribe_output_properties_hevc, + .dec_output_prop_hevc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_hevc), + .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, + .dec_output_prop_vp9_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), @@ -441,18 +500,33 @@ struct iris_platform_data qcs8300_data = { .num_vpp_pipe = 2, .max_session_count = 16, .max_core_mbpf = ((4096 * 2176) / 256) * 4, - .input_config_params = - sm8550_vdec_input_config_params, - .input_config_params_size = - ARRAY_SIZE(sm8550_vdec_input_config_params), + .input_config_params_default = + sm8550_vdec_input_config_params_default, + .input_config_params_default_size = + ARRAY_SIZE(sm8550_vdec_input_config_params_default), + .input_config_params_hevc = + sm8550_vdec_input_config_param_hevc, + .input_config_params_hevc_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_hevc), + .input_config_params_vp9 = + sm8550_vdec_input_config_param_vp9, + .input_config_params_vp9_size = + ARRAY_SIZE(sm8550_vdec_input_config_param_vp9), .output_config_params = sm8550_vdec_output_config_params, .output_config_params_size = ARRAY_SIZE(sm8550_vdec_output_config_params), .dec_input_prop = sm8550_vdec_subscribe_input_properties, .dec_input_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_input_properties), - .dec_output_prop = sm8550_vdec_subscribe_output_properties, - .dec_output_prop_size = ARRAY_SIZE(sm8550_vdec_subscribe_output_properties), + .dec_output_prop_avc = sm8550_vdec_subscribe_output_properties_avc, + .dec_output_prop_avc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_avc), + .dec_output_prop_hevc = sm8550_vdec_subscribe_output_properties_hevc, + .dec_output_prop_hevc_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_hevc), + .dec_output_prop_vp9 = sm8550_vdec_subscribe_output_properties_vp9, + .dec_output_prop_vp9_size = + ARRAY_SIZE(sm8550_vdec_subscribe_output_properties_vp9), .dec_ip_int_buf_tbl = sm8550_dec_ip_int_buf_tbl, .dec_ip_int_buf_tbl_size = ARRAY_SIZE(sm8550_dec_ip_int_buf_tbl), diff --git a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c index 8183e4e95fa4..8d0816a67ae0 100644 --- a/drivers/media/platform/qcom/iris/iris_platform_sm8250.c +++ b/drivers/media/platform/qcom/iris/iris_platform_sm8250.c @@ -128,9 +128,9 @@ struct iris_platform_data sm8250_data = { .num_vpp_pipe = 4, .max_session_count = 16, .max_core_mbpf = NUM_MBS_8K, - .input_config_params = + .input_config_params_default = sm8250_vdec_input_config_param_default, - .input_config_params_size = + .input_config_params_default_size = ARRAY_SIZE(sm8250_vdec_input_config_param_default), .dec_ip_int_buf_tbl = sm8250_dec_ip_int_buf_tbl, From e1f5d32608ec3c04e41c2c467b99c9a4a3f42d5b Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:11 +0530 Subject: [PATCH 197/310] media: iris: Add internal buffer calculation for HEVC and VP9 decoders Add internal buffer count and size calculations for HEVC and VP9 decoders. Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil [hverkuil: add __maybe_unused to size_mp2d_lb_fe_top_data] --- .../media/platform/qcom/iris/iris_buffer.c | 3 + .../platform/qcom/iris/iris_vpu_buffer.c | 397 +++++++++++++++++- .../platform/qcom/iris/iris_vpu_buffer.h | 42 +- 3 files changed, 430 insertions(+), 12 deletions(-) diff --git a/drivers/media/platform/qcom/iris/iris_buffer.c b/drivers/media/platform/qcom/iris/iris_buffer.c index 7dbac74b1a8d..6425e4919e3b 100644 --- a/drivers/media/platform/qcom/iris/iris_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_buffer.c @@ -205,6 +205,9 @@ static u32 iris_bitstream_buffer_size(struct iris_inst *inst) if (num_mbs > NUM_MBS_4K) { div_factor = 4; base_res_mbs = caps->max_mbpf; + } else { + if (inst->codec == V4L2_PIX_FMT_VP9) + div_factor = 1; } /* diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c index dce25e410d80..f92fd39fe310 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.c +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.c @@ -31,6 +31,42 @@ static u32 hfi_buffer_bin_h264d(u32 frame_width, u32 frame_height, u32 num_vpp_p return size_h264d_hw_bin_buffer(n_aligned_w, n_aligned_h, num_vpp_pipes); } +static u32 size_h265d_hw_bin_buffer(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 product = frame_width * frame_height; + u32 size_yuv, size_bin_hdr, size_bin_res; + + size_yuv = (product <= BIN_BUFFER_THRESHOLD) ? + ((BIN_BUFFER_THRESHOLD * 3) >> 1) : ((product * 3) >> 1); + size_bin_hdr = size_yuv * H265_CABAC_HDR_RATIO_HD_TOT; + size_bin_res = size_yuv * H265_CABAC_RES_RATIO_HD_TOT; + size_bin_hdr = ALIGN(size_bin_hdr / num_vpp_pipes, DMA_ALIGNMENT) * num_vpp_pipes; + size_bin_res = ALIGN(size_bin_res / num_vpp_pipes, DMA_ALIGNMENT) * num_vpp_pipes; + + return size_bin_hdr + size_bin_res; +} + +static u32 hfi_buffer_bin_vp9d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 _size_yuv = ALIGN(frame_width, 16) * ALIGN(frame_height, 16) * 3 / 2; + u32 _size = ALIGN(((max_t(u32, _size_yuv, ((BIN_BUFFER_THRESHOLD * 3) >> 1)) * + VPX_DECODER_FRAME_BIN_HDR_BUDGET / VPX_DECODER_FRAME_BIN_DENOMINATOR * + VPX_DECODER_FRAME_CONCURENCY_LVL) / num_vpp_pipes), DMA_ALIGNMENT) + + ALIGN(((max_t(u32, _size_yuv, ((BIN_BUFFER_THRESHOLD * 3) >> 1)) * + VPX_DECODER_FRAME_BIN_RES_BUDGET / VPX_DECODER_FRAME_BIN_DENOMINATOR * + VPX_DECODER_FRAME_CONCURENCY_LVL) / num_vpp_pipes), DMA_ALIGNMENT); + + return _size * num_vpp_pipes; +} + +static u32 hfi_buffer_bin_h265d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 n_aligned_w = ALIGN(frame_width, 16); + u32 n_aligned_h = ALIGN(frame_height, 16); + + return size_h265d_hw_bin_buffer(n_aligned_w, n_aligned_h, num_vpp_pipes); +} + static u32 hfi_buffer_comv_h264d(u32 frame_width, u32 frame_height, u32 _comv_bufcount) { u32 frame_height_in_mbs = DIV_ROUND_UP(frame_height, 16); @@ -55,6 +91,17 @@ static u32 hfi_buffer_comv_h264d(u32 frame_width, u32 frame_height, u32 _comv_bu return (size_colloc * (_comv_bufcount)) + 512; } +static u32 hfi_buffer_comv_h265d(u32 frame_width, u32 frame_height, u32 _comv_bufcount) +{ + u32 frame_height_in_mbs = (frame_height + 15) >> 4; + u32 frame_width_in_mbs = (frame_width + 15) >> 4; + u32 _size; + + _size = ALIGN(((frame_width_in_mbs * frame_height_in_mbs) << 8), 512); + + return (_size * (_comv_bufcount)) + 512; +} + static u32 size_h264d_bse_cmd_buf(u32 frame_height) { u32 height = ALIGN(frame_height, 32); @@ -63,6 +110,44 @@ static u32 size_h264d_bse_cmd_buf(u32 frame_height) SIZE_H264D_BSE_CMD_PER_BUF; } +static u32 size_h265d_bse_cmd_buf(u32 frame_width, u32 frame_height) +{ + u32 _size = ALIGN(((ALIGN(frame_width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(frame_height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS)) * + NUM_HW_PIC_BUF, DMA_ALIGNMENT); + _size = min_t(u32, _size, H265D_MAX_SLICE + 1); + _size = 2 * _size * SIZE_H265D_BSE_CMD_PER_BUF; + + return _size; +} + +static u32 hfi_buffer_persist_h265d(u32 rpu_enabled) +{ + return ALIGN((SIZE_SLIST_BUF_H265 * NUM_SLIST_BUF_H265 + + H265_NUM_FRM_INFO * H265_DISPLAY_BUF_SIZE + + H265_NUM_TILE * sizeof(u32) + + NUM_HW_PIC_BUF * SIZE_SEI_USERDATA + + rpu_enabled * NUM_HW_PIC_BUF * SIZE_DOLBY_RPU_METADATA), + DMA_ALIGNMENT); +} + +static inline +u32 hfi_iris3_vp9d_comv_size(void) +{ + return (((8192 + 63) >> 6) * ((4320 + 63) >> 6) * 8 * 8 * 2 * 8); +} + +static u32 hfi_buffer_persist_vp9d(void) +{ + return ALIGN(VP9_NUM_PROBABILITY_TABLE_BUF * VP9_PROB_TABLE_SIZE, DMA_ALIGNMENT) + + ALIGN(hfi_iris3_vp9d_comv_size(), DMA_ALIGNMENT) + + ALIGN(MAX_SUPERFRAME_HEADER_LEN, DMA_ALIGNMENT) + + ALIGN(VP9_UDC_HEADER_BUF_SIZE, DMA_ALIGNMENT) + + ALIGN(VP9_NUM_FRAME_INFO_BUF * CCE_TILE_OFFSET_SIZE, DMA_ALIGNMENT) + + ALIGN(VP9_NUM_FRAME_INFO_BUF * VP9_FRAME_INFO_BUF_SIZE, DMA_ALIGNMENT) + + HDR10_HIST_EXTRADATA_SIZE; +} + static u32 size_h264d_vpp_cmd_buf(u32 frame_height) { u32 size, height = ALIGN(frame_height, 32); @@ -83,17 +168,45 @@ static u32 hfi_buffer_persist_h264d(void) static u32 hfi_buffer_non_comv_h264d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) { - u32 size_bse, size_vpp, size; - - size_bse = size_h264d_bse_cmd_buf(frame_height); - size_vpp = size_h264d_vpp_cmd_buf(frame_height); - size = ALIGN(size_bse, DMA_ALIGNMENT) + + u32 size_bse = size_h264d_bse_cmd_buf(frame_height); + u32 size_vpp = size_h264d_vpp_cmd_buf(frame_height); + u32 size = ALIGN(size_bse, DMA_ALIGNMENT) + ALIGN(size_vpp, DMA_ALIGNMENT) + ALIGN(SIZE_HW_PIC(SIZE_H264D_HW_PIC_T), DMA_ALIGNMENT); return ALIGN(size, DMA_ALIGNMENT); } +static u32 size_h265d_vpp_cmd_buf(u32 frame_width, u32 frame_height) +{ + u32 _size = ALIGN(((ALIGN(frame_width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(frame_height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS)) * + NUM_HW_PIC_BUF, DMA_ALIGNMENT); + _size = min_t(u32, _size, H265D_MAX_SLICE + 1); + _size = ALIGN(_size, 4); + _size = 2 * _size * SIZE_H265D_VPP_CMD_PER_BUF; + if (_size > VPP_CMD_MAX_SIZE) + _size = VPP_CMD_MAX_SIZE; + + return _size; +} + +static u32 hfi_buffer_non_comv_h265d(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + u32 _size_bse = size_h265d_bse_cmd_buf(frame_width, frame_height); + u32 _size_vpp = size_h265d_vpp_cmd_buf(frame_width, frame_height); + u32 _size = ALIGN(_size_bse, DMA_ALIGNMENT) + + ALIGN(_size_vpp, DMA_ALIGNMENT) + + ALIGN(NUM_HW_PIC_BUF * 20 * 22 * 4, DMA_ALIGNMENT) + + ALIGN(2 * sizeof(u16) * + (ALIGN(frame_width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS) * + (ALIGN(frame_height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS), DMA_ALIGNMENT) + + ALIGN(SIZE_HW_PIC(SIZE_H265D_HW_PIC_T), DMA_ALIGNMENT) + + HDR10_HIST_EXTRADATA_SIZE; + + return ALIGN(_size, DMA_ALIGNMENT); +} + static u32 size_vpss_lb(u32 frame_width, u32 frame_height) { u32 opb_lb_wr_llb_y_buffer_size, opb_lb_wr_llb_uv_buffer_size; @@ -119,6 +232,203 @@ static u32 size_vpss_lb(u32 frame_width, u32 frame_height) opb_lb_wr_llb_y_buffer_size; } +static inline +u32 size_h265d_lb_fe_top_data(u32 frame_width, u32 frame_height) +{ + return MAX_FE_NBR_DATA_LUMA_LINE_BUFFER_SIZE * + (ALIGN(frame_width, 64) + 8) * 2; +} + +static inline +u32 size_h265d_lb_fe_top_ctrl(u32 frame_width, u32 frame_height) +{ + return MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * + (ALIGN(frame_width, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS); +} + +static inline +u32 size_h265d_lb_fe_left_ctrl(u32 frame_width, u32 frame_height) +{ + return MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE * + (ALIGN(frame_height, LCU_MAX_SIZE_PELS) / LCU_MIN_SIZE_PELS); +} + +static inline +u32 size_h265d_lb_se_top_ctrl(u32 frame_width, u32 frame_height) +{ + return (LCU_MAX_SIZE_PELS / 8 * (128 / 8)) * ((frame_width + 15) >> 4); +} + +static inline +u32 size_h265d_lb_se_left_ctrl(u32 frame_width, u32 frame_height) +{ + return max_t(u32, ((frame_height + 16 - 1) / 8) * + MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE, + max_t(u32, ((frame_height + 32 - 1) / 8) * + MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE, + ((frame_height + 64 - 1) / 8) * + MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE)); +} + +static inline +u32 size_h265d_lb_pe_top_data(u32 frame_width, u32 frame_height) +{ + return MAX_PE_NBR_DATA_LCU64_LINE_BUFFER_SIZE * + (ALIGN(frame_width, LCU_MIN_SIZE_PELS) / LCU_MIN_SIZE_PELS); +} + +static inline +u32 size_h265d_lb_vsp_top(u32 frame_width, u32 frame_height) +{ + return ((frame_width + 63) >> 6) * 128; +} + +static inline +u32 size_h265d_lb_vsp_left(u32 frame_width, u32 frame_height) +{ + return ((frame_height + 63) >> 6) * 128; +} + +static inline +u32 size_h265d_lb_recon_dma_metadata_wr(u32 frame_width, u32 frame_height) +{ + return size_h264d_lb_recon_dma_metadata_wr(frame_height); +} + +static inline +u32 size_h265d_qp(u32 frame_width, u32 frame_height) +{ + return size_h264d_qp(frame_width, frame_height); +} + +static inline +u32 hfi_buffer_line_h265d(u32 frame_width, u32 frame_height, bool is_opb, u32 num_vpp_pipes) +{ + u32 vpss_lb_size = 0, _size; + + _size = ALIGN(size_h265d_lb_fe_top_data(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_h265d_lb_fe_top_ctrl(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_h265d_lb_fe_left_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_h265d_lb_se_left_ctrl(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_h265d_lb_se_top_ctrl(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_h265d_lb_pe_top_data(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_h265d_lb_vsp_top(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_h265d_lb_vsp_left(frame_width, frame_height), + DMA_ALIGNMENT) * num_vpp_pipes + + ALIGN(size_h265d_lb_recon_dma_metadata_wr(frame_width, frame_height), + DMA_ALIGNMENT) * 4 + + ALIGN(size_h265d_qp(frame_width, frame_height), DMA_ALIGNMENT); + if (is_opb) + vpss_lb_size = size_vpss_lb(frame_width, frame_height); + + return ALIGN((_size + vpss_lb_size), DMA_ALIGNMENT); +} + +static inline +u32 size_vpxd_lb_fe_left_ctrl(u32 frame_width, u32 frame_height) +{ + return max_t(u32, ((frame_height + 15) >> 4) * + MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE, + max_t(u32, ((frame_height + 31) >> 5) * + MAX_FE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE, + ((frame_height + 63) >> 6) * + MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE)); +} + +static inline +u32 size_vpxd_lb_fe_top_ctrl(u32 frame_width, u32 frame_height) +{ + return ((ALIGN(frame_width, 64) + 8) * 10 * 2); +} + +static inline +u32 size_vpxd_lb_se_top_ctrl(u32 frame_width, u32 frame_height) +{ + return ((frame_width + 15) >> 4) * MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE; +} + +static inline +u32 size_vpxd_lb_se_left_ctrl(u32 frame_width, u32 frame_height) +{ + return max_t(u32, ((frame_height + 15) >> 4) * + MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE, + max_t(u32, ((frame_height + 31) >> 5) * + MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE, + ((frame_height + 63) >> 6) * + MAX_SE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE)); +} + +static inline +u32 size_vpxd_lb_recon_dma_metadata_wr(u32 frame_width, u32 frame_height) +{ + return ALIGN((ALIGN(frame_height, 8) / (4 / 2)) * 64, + BUFFER_ALIGNMENT_32_BYTES); +} + +static inline __maybe_unused +u32 size_mp2d_lb_fe_top_data(u32 frame_width, u32 frame_height) +{ + return ((ALIGN(frame_width, 16) + 8) * 10 * 2); +} + +static inline +u32 size_vp9d_lb_fe_top_data(u32 frame_width, u32 frame_height) +{ + return (ALIGN(ALIGN(frame_width, 8), 64) + 8) * 10 * 2; +} + +static inline +u32 size_vp9d_lb_pe_top_data(u32 frame_width, u32 frame_height) +{ + return ((ALIGN(ALIGN(frame_width, 8), 64) >> 6) * 176); +} + +static inline +u32 size_vp9d_lb_vsp_top(u32 frame_width, u32 frame_height) +{ + return (((ALIGN(ALIGN(frame_width, 8), 64) >> 6) * 64 * 8) + 256); +} + +static inline +u32 size_vp9d_qp(u32 frame_width, u32 frame_height) +{ + return size_h264d_qp(frame_width, frame_height); +} + +static inline +u32 hfi_iris3_vp9d_lb_size(u32 frame_width, u32 frame_height, u32 num_vpp_pipes) +{ + return ALIGN(size_vpxd_lb_fe_left_ctrl(frame_width, frame_height), DMA_ALIGNMENT) * + num_vpp_pipes + + ALIGN(size_vpxd_lb_se_left_ctrl(frame_width, frame_height), DMA_ALIGNMENT) * + num_vpp_pipes + + ALIGN(size_vp9d_lb_vsp_top(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_vpxd_lb_fe_top_ctrl(frame_width, frame_height), DMA_ALIGNMENT) + + 2 * ALIGN(size_vpxd_lb_recon_dma_metadata_wr(frame_width, frame_height), + DMA_ALIGNMENT) + + ALIGN(size_vpxd_lb_se_top_ctrl(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_vp9d_lb_pe_top_data(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_vp9d_lb_fe_top_data(frame_width, frame_height), DMA_ALIGNMENT) + + ALIGN(size_vp9d_qp(frame_width, frame_height), DMA_ALIGNMENT); +} + +static inline +u32 hfi_buffer_line_vp9d(u32 frame_width, u32 frame_height, u32 _yuv_bufcount_min, bool is_opb, + u32 num_vpp_pipes) +{ + u32 vpss_lb_size = 0; + u32 _lb_size; + + _lb_size = hfi_iris3_vp9d_lb_size(frame_width, frame_height, num_vpp_pipes); + + if (is_opb) + vpss_lb_size = size_vpss_lb(frame_width, frame_height); + + return _lb_size + vpss_lb_size + 4096; +} + static u32 hfi_buffer_line_h264d(u32 frame_width, u32 frame_height, bool is_opb, u32 num_vpp_pipes) { @@ -148,7 +458,14 @@ static u32 iris_vpu_dec_bin_size(struct iris_inst *inst) u32 height = f->fmt.pix_mp.height; u32 width = f->fmt.pix_mp.width; - return hfi_buffer_bin_h264d(width, height, num_vpp_pipes); + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_bin_h264d(width, height, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_bin_h265d(width, height, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_VP9) + return hfi_buffer_bin_vp9d(width, height, num_vpp_pipes); + + return 0; } static u32 iris_vpu_dec_comv_size(struct iris_inst *inst) @@ -158,12 +475,24 @@ static u32 iris_vpu_dec_comv_size(struct iris_inst *inst) u32 height = f->fmt.pix_mp.height; u32 width = f->fmt.pix_mp.width; - return hfi_buffer_comv_h264d(width, height, num_comv); + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_comv_h264d(width, height, num_comv); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_comv_h265d(width, height, num_comv); + + return 0; } static u32 iris_vpu_dec_persist_size(struct iris_inst *inst) { - return hfi_buffer_persist_h264d(); + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_persist_h264d(); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_persist_h265d(0); + else if (inst->codec == V4L2_PIX_FMT_VP9) + return hfi_buffer_persist_vp9d(); + + return 0; } static u32 iris_vpu_dec_dpb_size(struct iris_inst *inst) @@ -181,7 +510,12 @@ static u32 iris_vpu_dec_non_comv_size(struct iris_inst *inst) u32 height = f->fmt.pix_mp.height; u32 width = f->fmt.pix_mp.width; - return hfi_buffer_non_comv_h264d(width, height, num_vpp_pipes); + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_non_comv_h264d(width, height, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_non_comv_h265d(width, height, num_vpp_pipes); + + return 0; } static u32 iris_vpu_dec_line_size(struct iris_inst *inst) @@ -191,11 +525,20 @@ static u32 iris_vpu_dec_line_size(struct iris_inst *inst) u32 height = f->fmt.pix_mp.height; u32 width = f->fmt.pix_mp.width; bool is_opb = false; + u32 out_min_count = inst->buffers[BUF_OUTPUT].min_count; if (iris_split_mode_enabled(inst)) is_opb = true; - return hfi_buffer_line_h264d(width, height, is_opb, num_vpp_pipes); + if (inst->codec == V4L2_PIX_FMT_H264) + return hfi_buffer_line_h264d(width, height, is_opb, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_HEVC) + return hfi_buffer_line_h265d(width, height, is_opb, num_vpp_pipes); + else if (inst->codec == V4L2_PIX_FMT_VP9) + return hfi_buffer_line_vp9d(width, height, out_min_count, is_opb, + num_vpp_pipes); + + return 0; } static u32 iris_vpu_dec_scratch1_size(struct iris_inst *inst) @@ -205,6 +548,24 @@ static u32 iris_vpu_dec_scratch1_size(struct iris_inst *inst) iris_vpu_dec_line_size(inst); } +static int output_min_count(struct iris_inst *inst) +{ + int output_min_count = 4; + + /* fw_min_count > 0 indicates reconfig event has already arrived */ + if (inst->fw_min_count) { + if (iris_split_mode_enabled(inst) && inst->codec == V4L2_PIX_FMT_VP9) + return min_t(u32, 4, inst->fw_min_count); + else + return inst->fw_min_count; + } + + if (inst->codec == V4L2_PIX_FMT_VP9) + output_min_count = 9; + + return output_min_count; +} + struct iris_vpu_buf_type_handle { enum iris_buffer_type type; u32 (*handle)(struct iris_inst *inst); @@ -238,6 +599,19 @@ int iris_vpu_buf_size(struct iris_inst *inst, enum iris_buffer_type buffer_type) return size; } +static u32 internal_buffer_count(struct iris_inst *inst, + enum iris_buffer_type buffer_type) +{ + if (buffer_type == BUF_BIN || buffer_type == BUF_LINE || + buffer_type == BUF_PERSIST) { + return 1; + } else if (buffer_type == BUF_COMV || buffer_type == BUF_NON_COMV) { + if (inst->codec == V4L2_PIX_FMT_H264 || inst->codec == V4L2_PIX_FMT_HEVC) + return 1; + } + return 0; +} + static inline int iris_vpu_dpb_count(struct iris_inst *inst) { if (iris_split_mode_enabled(inst)) { @@ -254,12 +628,13 @@ int iris_vpu_buf_count(struct iris_inst *inst, enum iris_buffer_type buffer_type case BUF_INPUT: return MIN_BUFFERS; case BUF_OUTPUT: - return inst->fw_min_count; + return output_min_count(inst); case BUF_BIN: case BUF_COMV: case BUF_NON_COMV: case BUF_LINE: case BUF_PERSIST: + return internal_buffer_count(inst, buffer_type); case BUF_SCRATCH_1: return 1; /* internal buffer count needed by firmware is 1 */ case BUF_DPB: diff --git a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h index 62af6ea6ba1f..ee95fd20b794 100644 --- a/drivers/media/platform/qcom/iris/iris_vpu_buffer.h +++ b/drivers/media/platform/qcom/iris/iris_vpu_buffer.h @@ -13,6 +13,10 @@ struct iris_inst; #define DMA_ALIGNMENT 256 #define NUM_HW_PIC_BUF 32 +#define LCU_MAX_SIZE_PELS 64 +#define LCU_MIN_SIZE_PELS 16 +#define HDR10_HIST_EXTRADATA_SIZE (4 * 1024) + #define SIZE_HW_PIC(size_per_buf) (NUM_HW_PIC_BUF * (size_per_buf)) #define MAX_TILE_COLUMNS 32 @@ -28,11 +32,47 @@ struct iris_inst; #define SIZE_SLIST_BUF_H264 512 #define H264_DISPLAY_BUF_SIZE 3328 #define H264_NUM_FRM_INFO 66 +#define H265_NUM_TILE_COL 32 +#define H265_NUM_TILE_ROW 128 +#define H265_NUM_TILE (H265_NUM_TILE_ROW * H265_NUM_TILE_COL + 1) +#define SIZE_H265D_BSE_CMD_PER_BUF (16 * sizeof(u32)) -#define SIZE_SEI_USERDATA 4096 +#define NUM_SLIST_BUF_H265 (80 + 20) +#define SIZE_SLIST_BUF_H265 (BIT(10)) +#define H265_DISPLAY_BUF_SIZE (3072) +#define H265_NUM_FRM_INFO (48) +#define VP9_NUM_FRAME_INFO_BUF 32 +#define VP9_NUM_PROBABILITY_TABLE_BUF (VP9_NUM_FRAME_INFO_BUF + 4) +#define VP9_PROB_TABLE_SIZE (3840) +#define VP9_FRAME_INFO_BUF_SIZE (6144) +#define BUFFER_ALIGNMENT_32_BYTES 32 +#define CCE_TILE_OFFSET_SIZE ALIGN(32 * 4 * 4, BUFFER_ALIGNMENT_32_BYTES) +#define MAX_SUPERFRAME_HEADER_LEN (34) +#define MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 64 +#define MAX_FE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE 64 +#define MAX_FE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE 64 +#define MAX_SE_NBR_CTRL_LCU16_LINE_BUFFER_SIZE (128 / 8) +#define MAX_SE_NBR_CTRL_LCU32_LINE_BUFFER_SIZE (128 / 8) +#define VP9_UDC_HEADER_BUF_SIZE (3 * 128) + +#define SIZE_SEI_USERDATA 4096 +#define SIZE_DOLBY_RPU_METADATA (41 * 1024) #define H264_CABAC_HDR_RATIO_HD_TOT 1 #define H264_CABAC_RES_RATIO_HD_TOT 3 +#define H265D_MAX_SLICE 1200 +#define SIZE_H265D_HW_PIC_T SIZE_H264D_HW_PIC_T +#define H265_CABAC_HDR_RATIO_HD_TOT 2 +#define H265_CABAC_RES_RATIO_HD_TOT 2 +#define SIZE_H265D_VPP_CMD_PER_BUF (256) + +#define VPX_DECODER_FRAME_CONCURENCY_LVL (2) +#define VPX_DECODER_FRAME_BIN_HDR_BUDGET 1 +#define VPX_DECODER_FRAME_BIN_RES_BUDGET 3 +#define VPX_DECODER_FRAME_BIN_DENOMINATOR 2 + +#define VPX_DECODER_FRAME_BIN_RES_BUDGET_RATIO (3 / 2) + #define SIZE_H264D_HW_PIC_T (BIT(11)) #define MAX_FE_NBR_CTRL_LCU64_LINE_BUFFER_SIZE 64 From 478c4478610d307b710e69b858243bcd78f522de Mon Sep 17 00:00:00 2001 From: Dikshita Agarwal Date: Fri, 9 May 2025 14:09:12 +0530 Subject: [PATCH 198/310] media: iris: Add codec specific check for VP9 decoder drain handling Add a codec specific for the VP9 decoder to ensure that a non-null buffer is sent to the firmware during drain. The firmware enforces a check for VP9 decoder that the number of buffers queued and dequeued on the output plane should match. When a null buffer is sent, the firmware does not return a response for it, leading to a count mismatch and an assertion failure from the firmware. Acked-by: Vikash Garodia Tested-by: Neil Armstrong # on SM8550-QRD Tested-by: Neil Armstrong # on SM8550-HDK Tested-by: Neil Armstrong # on SM8650-QRD Tested-by: Neil Armstrong # on SM8650-HDK Signed-off-by: Dikshita Agarwal Tested-by: Vikash Garodia # on sa8775p-ride Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c | 2 ++ drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c index 2e3f5a6b2ff1..5fc30d54af4d 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_command.c @@ -401,6 +401,8 @@ static int iris_hfi_gen1_session_drain(struct iris_inst *inst, u32 plane) ip_pkt.shdr.hdr.pkt_type = HFI_CMD_SESSION_EMPTY_BUFFER; ip_pkt.shdr.session_id = inst->session_id; ip_pkt.flags = HFI_BUFFERFLAG_EOS; + if (inst->codec == V4L2_PIX_FMT_VP9) + ip_pkt.packet_buffer = 0xdeadb000; return iris_hfi_queue_cmd_write(inst->core, &ip_pkt, ip_pkt.shdr.hdr.size); } diff --git a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c index 926acee1f48c..8d1ce8a19a45 100644 --- a/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c +++ b/drivers/media/platform/qcom/iris/iris_hfi_gen1_response.c @@ -348,6 +348,10 @@ static void iris_hfi_gen1_session_etb_done(struct iris_inst *inst, void *packet) struct iris_buffer *buf = NULL; bool found = false; + /* EOS buffer sent via drain won't be in v4l2 buffer list */ + if (pkt->packet_buffer == 0xdeadb000) + return; + v4l2_m2m_for_each_src_buf_safe(m2m_ctx, m2m_buffer, n) { buf = to_iris_buffer(&m2m_buffer->vb); if (buf->index == pkt->input_tag) { From f6940b79fcb6e9bae200cb24452b2880b9d1d7dd Mon Sep 17 00:00:00 2001 From: Akhil P Oommen Date: Wed, 11 Jun 2025 11:13:44 +0530 Subject: [PATCH 199/310] media: iris: Fix opp scaling of power domains Pass PD_FLAG_REQUIRED_OPP flag to allow opp framework to scale the rpmpd power domains. Signed-off-by: Akhil P Oommen Reviewed-by: Dikshita Agarwal Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/iris/iris_probe.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/iris/iris_probe.c b/drivers/media/platform/qcom/iris/iris_probe.c index 9a7ce142f700..4e6e92357968 100644 --- a/drivers/media/platform/qcom/iris/iris_probe.c +++ b/drivers/media/platform/qcom/iris/iris_probe.c @@ -53,7 +53,7 @@ static int iris_init_power_domains(struct iris_core *core) struct dev_pm_domain_attach_data iris_opp_pd_data = { .pd_names = core->iris_platform_data->opp_pd_tbl, .num_pd_names = core->iris_platform_data->opp_pd_tbl_size, - .pd_flags = PD_FLAG_DEV_LINK_ON, + .pd_flags = PD_FLAG_DEV_LINK_ON | PD_FLAG_REQUIRED_OPP, }; ret = devm_pm_domain_attach_list(core->dev, &iris_pd_data, &core->pmdomain_tbl); From 0e7691fb51720b602c8bc16dd03c66d5b9fd4dc1 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 2 May 2025 23:41:40 +0300 Subject: [PATCH 200/310] dt-bindings: media: qcom,x1e80100-camss: Tighten the property regex pattern There are 4 CSIPHY ports on X1E80100, it'd be preferred to tinker a too loose the regexp, which covers all these port names. Signed-off-by: Vladimir Zapolskiy Acked-by: Bryan O'Donoghue Reviewed-by: Krzysztof Kozlowski Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/qcom,x1e80100-camss.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index 113565cf2a99..680f3f514132 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -133,7 +133,7 @@ properties: CSI input ports. patternProperties: - "^port@[0-3]+$": + "^port@[0-3]$": $ref: /schemas/graph.yaml#/$defs/port-base unevaluatedProperties: false From 7b1747f8ad5d5d00c429bb9128025e6868005745 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 2 May 2025 23:41:41 +0300 Subject: [PATCH 201/310] dt-bindings: media: qcom,x1e80100-camss: Add optional bus-type property Since CSIPHY IP on modern Qualcomm SoCs supports D-PHY and C-PHY interfaces, it might be necessary to specify it explicitly for some particular devices. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/qcom,x1e80100-camss.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index 680f3f514132..2e7455bd75ec 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -153,6 +153,11 @@ properties: minItems: 1 maxItems: 4 + bus-type: + enum: + - 1 # MEDIA_BUS_TYPE_CSI2_CPHY + - 4 # MEDIA_BUS_TYPE_CSI2_DPHY + required: - clock-lanes - data-lanes From b8ef764aff5be003c149ea593e4caa99253cac5c Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Fri, 2 May 2025 23:41:42 +0300 Subject: [PATCH 202/310] dt-bindings: media: qcom,x1e80100-camss: Remove clock-lanes port property Since clock lanes under CSIPHY are hard-wired and non-selectable, it makes sense to remove this port property. The change follows the same logic as found in commit 336136e197e2 ("media: dt-bindings: media: camss: Remove clock-lane property"). Signed-off-by: Vladimir Zapolskiy Reviewed-by: Krzysztof Kozlowski Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/qcom,x1e80100-camss.yaml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index 2e7455bd75ec..c101e42f22ac 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -146,9 +146,6 @@ properties: unevaluatedProperties: false properties: - clock-lanes: - maxItems: 1 - data-lanes: minItems: 1 maxItems: 4 @@ -159,7 +156,6 @@ properties: - 4 # MEDIA_BUS_TYPE_CSI2_DPHY required: - - clock-lanes - data-lanes required: @@ -362,7 +358,6 @@ examples: port@0 { reg = <0>; csiphy_ep0: endpoint { - clock-lanes = <7>; data-lanes = <0 1>; remote-endpoint = <&sensor_ep>; }; From 1da245b6b73436be0d9936bb472f8a55900193cb Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 24 Jun 2025 12:42:53 +0300 Subject: [PATCH 203/310] dt-bindings: media: qcom,x1e80100-camss: Fix isp unit address According to the devicetree specification a unit address shall match the first address value of the reg property. Reviewed-by: Bryan O'Donoghue Signed-off-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../devicetree/bindings/media/qcom,x1e80100-camss.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml index c101e42f22ac..b075341caafc 100644 --- a/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml +++ b/Documentation/devicetree/bindings/media/qcom,x1e80100-camss.yaml @@ -190,7 +190,7 @@ examples: #address-cells = <2>; #size-cells = <2>; - camss: isp@acb6000 { + camss: isp@acb7000 { compatible = "qcom,x1e80100-camss"; reg = <0 0x0acb7000 0 0x2000>, From 164202f682030cf8702f11cb63ccbcf2a81b2e75 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Mon, 26 May 2025 19:28:39 -0400 Subject: [PATCH 204/310] media: qcom: camss: Power pipeline only when streaming The libcamera plugin for Pipewire may keep an open file descriptor to the video device, even while streaming. This simplifies its operation, as it only needs to keep track of a number instead of a file path. When the video device is open but not streaming, the pipeline can be powered off. Move the pipeline power management to the prepare_streaming and unprepare_streaming functions. Signed-off-by: Richard Acayan Reviewed-by: Bryan O'Donoghue Tested-by: Bryan O'Donoghue Reviewed-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- .../media/platform/qcom/camss/camss-video.c | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-video.c b/drivers/media/platform/qcom/camss/camss-video.c index aa021fd5e123..8d05802d1735 100644 --- a/drivers/media/platform/qcom/camss/camss-video.c +++ b/drivers/media/platform/qcom/camss/camss-video.c @@ -225,6 +225,21 @@ static int video_check_format(struct camss_video *video) return 0; } +static int video_prepare_streaming(struct vb2_queue *q) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + int ret; + + ret = v4l2_pipeline_pm_get(&vdev->entity); + if (ret < 0) { + dev_err(video->camss->dev, "Failed to power up pipeline: %d\n", + ret); + } + + return ret; +} + static int video_start_streaming(struct vb2_queue *q, unsigned int count) { struct camss_video *video = vb2_get_drv_priv(q); @@ -308,13 +323,23 @@ static void video_stop_streaming(struct vb2_queue *q) video->ops->flush_buffers(video, VB2_BUF_STATE_ERROR); } +static void video_unprepare_streaming(struct vb2_queue *q) +{ + struct camss_video *video = vb2_get_drv_priv(q); + struct video_device *vdev = &video->vdev; + + v4l2_pipeline_pm_put(&vdev->entity); +} + static const struct vb2_ops msm_video_vb2_q_ops = { .queue_setup = video_queue_setup, .buf_init = video_buf_init, .buf_prepare = video_buf_prepare, .buf_queue = video_buf_queue, + .prepare_streaming = video_prepare_streaming, .start_streaming = video_start_streaming, .stop_streaming = video_stop_streaming, + .unprepare_streaming = video_unprepare_streaming, }; /* ----------------------------------------------------------------------------- @@ -599,20 +624,10 @@ static int video_open(struct file *file) file->private_data = vfh; - ret = v4l2_pipeline_pm_get(&vdev->entity); - if (ret < 0) { - dev_err(video->camss->dev, "Failed to power up pipeline: %d\n", - ret); - goto error_pm_use; - } - mutex_unlock(&video->lock); return 0; -error_pm_use: - v4l2_fh_release(file); - error_alloc: mutex_unlock(&video->lock); @@ -621,12 +636,8 @@ static int video_open(struct file *file) static int video_release(struct file *file) { - struct video_device *vdev = video_devdata(file); - vb2_fop_release(file); - v4l2_pipeline_pm_put(&vdev->entity); - file->private_data = NULL; return 0; From 868423c834a29981fe3a77d32caf645c6b91a4c5 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Thu, 12 Jun 2025 09:07:15 +0100 Subject: [PATCH 205/310] media: qcom: camss: csiphy-3ph: Fix inadvertent dropping of SDM660/SDM670 phy init The moving of init sequence hook from gen2() to subdev_init() doesn't account for gen1 devices such as SDM660 and SDM670. The switch should find the right offset for gen2 PHYs only, not reject gen1. Remove the default error case to restore gen1 CSIPHY support. Cc: stable@vger.kernel.org Fixes: fbce0ca24c3a ("media: qcom: camss: csiphy-3ph: Move CSIPHY variables to data field inside csiphy struct") Signed-off-by: Bryan O'Donoghue Reviewed-by: Vladimir Zapolskiy Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c index f732a76de93e..88c0ba495c32 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy-3ph-1-0.c @@ -849,8 +849,7 @@ static int csiphy_init(struct csiphy_device *csiphy) regs->offset = 0x1000; break; default: - WARN(1, "unknown csiphy version\n"); - return -ENODEV; + break; } return 0; From 69080ec3d0daba8a894025476c98ab16b5a505a4 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 May 2025 17:23:45 +0300 Subject: [PATCH 206/310] media: qcom: camss: cleanup media device allocated resource on error path A call to media_device_init() requires media_device_cleanup() counterpart to complete cleanup and release any allocated resources. This has been done in the driver .remove() right from the beginning, but error paths on .probe() shall also be fixed. Fixes: a1d7c116fcf7 ("media: camms: Add core files") Cc: stable@vger.kernel.org Signed-off-by: Vladimir Zapolskiy Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 06f42875702f..f76773dbd296 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -3625,7 +3625,7 @@ static int camss_probe(struct platform_device *pdev) ret = v4l2_device_register(camss->dev, &camss->v4l2_dev); if (ret < 0) { dev_err(dev, "Failed to register V4L2 device: %d\n", ret); - goto err_genpd_cleanup; + goto err_media_device_cleanup; } v4l2_async_nf_init(&camss->notifier, &camss->v4l2_dev); @@ -3680,6 +3680,8 @@ static int camss_probe(struct platform_device *pdev) v4l2_device_unregister(&camss->v4l2_dev); v4l2_async_nf_cleanup(&camss->notifier); pm_runtime_disable(dev); +err_media_device_cleanup: + media_device_cleanup(&camss->media_dev); err_genpd_cleanup: camss_genpd_cleanup(camss); From ed4b86828d67609ca9c120265e7c5d3f71686ffa Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 May 2025 17:23:46 +0300 Subject: [PATCH 207/310] media: qcom: camss: remove duplicated csiphy_formats_sc7280 data It's sufficient to have just one previously set csiphy_formats_sdm845 data. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss-csiphy.c | 5 ----- drivers/media/platform/qcom/camss/camss-csiphy.h | 1 - drivers/media/platform/qcom/camss/camss.c | 10 +++++----- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.c b/drivers/media/platform/qcom/camss/camss-csiphy.c index c622efcc92ff..2de97f58f9ae 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.c +++ b/drivers/media/platform/qcom/camss/camss-csiphy.c @@ -103,11 +103,6 @@ const struct csiphy_formats csiphy_formats_8x96 = { .formats = formats_8x96 }; -const struct csiphy_formats csiphy_formats_sc7280 = { - .nformats = ARRAY_SIZE(formats_sdm845), - .formats = formats_sdm845 -}; - const struct csiphy_formats csiphy_formats_sdm845 = { .nformats = ARRAY_SIZE(formats_sdm845), .formats = formats_sdm845 diff --git a/drivers/media/platform/qcom/camss/camss-csiphy.h b/drivers/media/platform/qcom/camss/camss-csiphy.h index ab91273303b9..895f80003c44 100644 --- a/drivers/media/platform/qcom/camss/camss-csiphy.h +++ b/drivers/media/platform/qcom/camss/camss-csiphy.h @@ -126,7 +126,6 @@ void msm_csiphy_unregister_entity(struct csiphy_device *csiphy); extern const struct csiphy_formats csiphy_formats_8x16; extern const struct csiphy_formats csiphy_formats_8x96; -extern const struct csiphy_formats csiphy_formats_sc7280; extern const struct csiphy_formats csiphy_formats_sdm845; extern const struct csiphy_hw_ops csiphy_ops_2ph_1_0; diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index f76773dbd296..8c844ebf9cb6 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -1481,7 +1481,7 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { .csiphy = { .id = 0, .hw_ops = &csiphy_ops_3ph_1_0, - .formats = &csiphy_formats_sc7280 + .formats = &csiphy_formats_sdm845, } }, /* CSIPHY1 */ @@ -1496,7 +1496,7 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { .csiphy = { .id = 1, .hw_ops = &csiphy_ops_3ph_1_0, - .formats = &csiphy_formats_sc7280 + .formats = &csiphy_formats_sdm845, } }, /* CSIPHY2 */ @@ -1511,7 +1511,7 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { .csiphy = { .id = 2, .hw_ops = &csiphy_ops_3ph_1_0, - .formats = &csiphy_formats_sc7280 + .formats = &csiphy_formats_sdm845, } }, /* CSIPHY3 */ @@ -1526,7 +1526,7 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { .csiphy = { .id = 3, .hw_ops = &csiphy_ops_3ph_1_0, - .formats = &csiphy_formats_sc7280 + .formats = &csiphy_formats_sdm845, } }, /* CSIPHY4 */ @@ -1541,7 +1541,7 @@ static const struct camss_subdev_resources csiphy_res_7280[] = { .csiphy = { .id = 4, .hw_ops = &csiphy_ops_3ph_1_0, - .formats = &csiphy_formats_sc7280 + .formats = &csiphy_formats_sdm845, } }, }; From 0d9f889fed6ba5d27f1f6740e9af4583f68088d9 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 May 2025 17:23:48 +0300 Subject: [PATCH 208/310] media: qcom: camss: register camss media device before subdevices A media device can and at least for sake of simplicity should be registered before V4L2 devices including the ones added on async completion. The change removes the second and out of camss_probe() media device registration path, and it allows to get a working ISP media device independently from connected or not sensor devices. Signed-off-by: Vladimir Zapolskiy Acked-by: Neil Armstrong Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 25 ++++++++++------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 8c844ebf9cb6..3e1bd02f5adc 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -3418,11 +3418,7 @@ static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async) } } - ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); - if (ret < 0) - return ret; - - return media_device_register(&camss->media_dev); + return v4l2_device_register_subdev_nodes(&camss->v4l2_dev); } static const struct v4l2_async_notifier_operations camss_subdev_notifier_ops = { @@ -3646,6 +3642,12 @@ static int camss_probe(struct platform_device *pdev) if (ret < 0) goto err_register_subdevs; + ret = media_device_register(&camss->media_dev); + if (ret < 0) { + dev_err(dev, "Failed to register media device: %d\n", ret); + goto err_register_subdevs; + } + if (num_subdevs) { camss->notifier.ops = &camss_subdev_notifier_ops; @@ -3654,26 +3656,21 @@ static int camss_probe(struct platform_device *pdev) dev_err(dev, "Failed to register async subdev nodes: %d\n", ret); - goto err_register_subdevs; + goto err_media_device_unregister; } } else { ret = v4l2_device_register_subdev_nodes(&camss->v4l2_dev); if (ret < 0) { dev_err(dev, "Failed to register subdev nodes: %d\n", ret); - goto err_register_subdevs; - } - - ret = media_device_register(&camss->media_dev); - if (ret < 0) { - dev_err(dev, "Failed to register media device: %d\n", - ret); - goto err_register_subdevs; + goto err_media_device_unregister; } } return 0; +err_media_device_unregister: + media_device_unregister(&camss->media_dev); err_register_subdevs: camss_unregister_entities(camss); err_v4l2_device_unregister: From 6181e48f53628613293c458a54c0a8b42169bc98 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 May 2025 17:23:50 +0300 Subject: [PATCH 209/310] media: qcom: camss: simplify camss_subdev_notifier_complete() function For sake of code simplicity and readability reduce the function code by one level of indentation, the change is non-functional. Signed-off-by: Vladimir Zapolskiy Reviewed-by: Bryan O'Donoghue [bod: Fixed indentation error] Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index 3e1bd02f5adc..e1d64c8f42ae 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -3386,35 +3386,35 @@ static int camss_subdev_notifier_complete(struct v4l2_async_notifier *async) struct camss *camss = container_of(async, struct camss, notifier); struct v4l2_device *v4l2_dev = &camss->v4l2_dev; struct v4l2_subdev *sd; - int ret; list_for_each_entry(sd, &v4l2_dev->subdevs, list) { - if (sd->host_priv) { - struct media_entity *sensor = &sd->entity; - struct csiphy_device *csiphy = - (struct csiphy_device *) sd->host_priv; - struct media_entity *input = &csiphy->subdev.entity; - unsigned int i; + struct csiphy_device *csiphy = sd->host_priv; + struct media_entity *input, *sensor; + unsigned int i; + int ret; - for (i = 0; i < sensor->num_pads; i++) { - if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) - break; - } - if (i == sensor->num_pads) { - dev_err(camss->dev, - "No source pad in external entity\n"); - return -EINVAL; - } + if (!csiphy) + continue; - ret = media_create_pad_link(sensor, i, - input, MSM_CSIPHY_PAD_SINK, - MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); - if (ret < 0) { - camss_link_err(camss, sensor->name, - input->name, - ret); - return ret; - } + input = &csiphy->subdev.entity; + sensor = &sd->entity; + + for (i = 0; i < sensor->num_pads; i++) { + if (sensor->pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + if (i == sensor->num_pads) { + dev_err(camss->dev, + "No source pad in external entity\n"); + return -EINVAL; + } + + ret = media_create_pad_link(sensor, i, input, + MSM_CSIPHY_PAD_SINK, + MEDIA_LNK_FL_IMMUTABLE | MEDIA_LNK_FL_ENABLED); + if (ret < 0) { + camss_link_err(camss, sensor->name, input->name, ret); + return ret; } } From 49ce78f176d2110ab6cf42fa68b4c9cd8de3abe8 Mon Sep 17 00:00:00 2001 From: Vladimir Zapolskiy Date: Tue, 13 May 2025 17:23:53 +0300 Subject: [PATCH 210/310] MAINTAINERS: add myself as a CAMSS patch reviewer Add myself as a review of Qualcomm CAMSS subsystem patches. Signed-off-by: Vladimir Zapolskiy Acked-by: Bryan O'Donoghue Signed-off-by: Bryan O'Donoghue Signed-off-by: Hans Verkuil --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 276c5a0b2dc5..cd6bf6764333 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20341,6 +20341,7 @@ QUALCOMM CAMERA SUBSYSTEM DRIVER M: Robert Foss M: Todor Tomov M: Bryan O'Donoghue +R: Vladimir Zapolskiy L: linux-media@vger.kernel.org S: Maintained F: Documentation/admin-guide/media/qcom_camss.rst From c26e8dcd9d4e86d788c5bf7a5dd0ea70a95ab067 Mon Sep 17 00:00:00 2001 From: Bryan O'Donoghue Date: Thu, 26 Jun 2025 02:24:33 +0100 Subject: [PATCH 211/310] media: qcom: camss: Remove extraneous -supply postfix on supply names The -supply postfix is wrong but wasn't noticed on the CRD devices or indeed the Dell devices, however on Lenovo devices the error comes up. Fixes: 1830cf0f56c3 ("media: qcom: camss: Add x1e80100 specific support") Cc: stable@vger.kernel.org Signed-off-by: Bryan O'Donoghue [bod: reworded commit log per Konrad's feedback] Reviewed-by: Konrad Dybcio Signed-off-by: Hans Verkuil --- drivers/media/platform/qcom/camss/camss.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/media/platform/qcom/camss/camss.c b/drivers/media/platform/qcom/camss/camss.c index e1d64c8f42ae..e08e70b93824 100644 --- a/drivers/media/platform/qcom/camss/camss.c +++ b/drivers/media/platform/qcom/camss/camss.c @@ -2486,8 +2486,8 @@ static const struct resources_icc icc_res_sm8550[] = { static const struct camss_subdev_resources csiphy_res_x1e80100[] = { /* CSIPHY0 */ { - .regulators = { "vdd-csiphy-0p8-supply", - "vdd-csiphy-1p2-supply" }, + .regulators = { "vdd-csiphy-0p8", + "vdd-csiphy-1p2" }, .clock = { "csiphy0", "csiphy0_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -2501,8 +2501,8 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY1 */ { - .regulators = { "vdd-csiphy-0p8-supply", - "vdd-csiphy-1p2-supply" }, + .regulators = { "vdd-csiphy-0p8", + "vdd-csiphy-1p2" }, .clock = { "csiphy1", "csiphy1_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -2516,8 +2516,8 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY2 */ { - .regulators = { "vdd-csiphy-0p8-supply", - "vdd-csiphy-1p2-supply" }, + .regulators = { "vdd-csiphy-0p8", + "vdd-csiphy-1p2" }, .clock = { "csiphy2", "csiphy2_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, @@ -2531,8 +2531,8 @@ static const struct camss_subdev_resources csiphy_res_x1e80100[] = { }, /* CSIPHY4 */ { - .regulators = { "vdd-csiphy-0p8-supply", - "vdd-csiphy-1p2-supply" }, + .regulators = { "vdd-csiphy-0p8", + "vdd-csiphy-1p2" }, .clock = { "csiphy4", "csiphy4_timer" }, .clock_rate = { { 300000000, 400000000, 480000000 }, { 266666667, 400000000 } }, From b7fe4c0019b12d60ece3e99eeb0ccfd5a1d103e5 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:17 +0800 Subject: [PATCH 212/310] media: staging/ipu7: add Intel IPU7 PCI device driver Intel Image Processing Unit 7th Gen includes input and processing systems and the hardware presents itself as a single PCI device in system same as IPU6. The IPU7 PCI device driver basically does PCI configurations, basic hardware configuration by its buttress interfaces, loads the firmware binary, register the auxiliary device which serve for the ISYS device driver. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-bus.c | 158 + drivers/staging/media/ipu7/ipu7-bus.h | 69 + .../staging/media/ipu7/ipu7-buttress-regs.h | 461 +++ drivers/staging/media/ipu7/ipu7-buttress.c | 1192 +++++++ drivers/staging/media/ipu7/ipu7-buttress.h | 77 + .../staging/media/ipu7/ipu7-platform-regs.h | 82 + drivers/staging/media/ipu7/ipu7.c | 2786 +++++++++++++++++ drivers/staging/media/ipu7/ipu7.h | 242 ++ 8 files changed, 5067 insertions(+) create mode 100644 drivers/staging/media/ipu7/ipu7-bus.c create mode 100644 drivers/staging/media/ipu7/ipu7-bus.h create mode 100644 drivers/staging/media/ipu7/ipu7-buttress-regs.h create mode 100644 drivers/staging/media/ipu7/ipu7-buttress.c create mode 100644 drivers/staging/media/ipu7/ipu7-buttress.h create mode 100644 drivers/staging/media/ipu7/ipu7-platform-regs.h create mode 100644 drivers/staging/media/ipu7/ipu7.c create mode 100644 drivers/staging/media/ipu7/ipu7.h diff --git a/drivers/staging/media/ipu7/ipu7-bus.c b/drivers/staging/media/ipu7/ipu7-bus.c new file mode 100644 index 000000000000..7da44fde002a --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-bus.c @@ -0,0 +1,158 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-boot.h" +#include "ipu7-dma.h" + +static int bus_pm_runtime_suspend(struct device *dev) +{ + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + int ret; + + ret = pm_generic_runtime_suspend(dev); + if (ret) + return ret; + + ret = ipu_buttress_powerdown(dev, adev->ctrl); + if (!ret) + return 0; + + dev_err(dev, "power down failed!\n"); + + /* Powering down failed, attempt to resume device now */ + ret = pm_generic_runtime_resume(dev); + if (!ret) + return -EBUSY; + + return -EIO; +} + +static int bus_pm_runtime_resume(struct device *dev) +{ + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + int ret; + + ret = ipu_buttress_powerup(dev, adev->ctrl); + if (ret) + return ret; + + ret = pm_generic_runtime_resume(dev); + if (ret) + goto out_err; + + return 0; + +out_err: + ipu_buttress_powerdown(dev, adev->ctrl); + + return -EBUSY; +} + +static struct dev_pm_domain ipu7_bus_pm_domain = { + .ops = { + .runtime_suspend = bus_pm_runtime_suspend, + .runtime_resume = bus_pm_runtime_resume, + }, +}; + +static DEFINE_MUTEX(ipu7_bus_mutex); +static void ipu7_bus_release(struct device *dev) +{ + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + + kfree(adev->pdata); + kfree(adev); +} + +struct ipu7_bus_device * +ipu7_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, const struct ipu_buttress_ctrl *ctrl, + const char *name) +{ + struct auxiliary_device *auxdev; + struct ipu7_bus_device *adev; + struct ipu7_device *isp = pci_get_drvdata(pdev); + int ret; + + adev = kzalloc(sizeof(*adev), GFP_KERNEL); + if (!adev) + return ERR_PTR(-ENOMEM); + + adev->isp = isp; + adev->ctrl = ctrl; + adev->pdata = pdata; + auxdev = &adev->auxdev; + auxdev->name = name; + auxdev->id = (pci_domain_nr(pdev->bus) << 16) | + PCI_DEVID(pdev->bus->number, pdev->devfn); + + auxdev->dev.parent = parent; + auxdev->dev.release = ipu7_bus_release; + + ret = auxiliary_device_init(auxdev); + if (ret < 0) { + dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n", + ret); + kfree(adev); + return ERR_PTR(ret); + } + + dev_pm_domain_set(&auxdev->dev, &ipu7_bus_pm_domain); + + pm_runtime_forbid(&adev->auxdev.dev); + pm_runtime_enable(&adev->auxdev.dev); + + return adev; +} + +int ipu7_bus_add_device(struct ipu7_bus_device *adev) +{ + struct auxiliary_device *auxdev = &adev->auxdev; + int ret; + + ret = auxiliary_device_add(auxdev); + if (ret) { + auxiliary_device_uninit(auxdev); + return ret; + } + + mutex_lock(&ipu7_bus_mutex); + list_add(&adev->list, &adev->isp->devices); + mutex_unlock(&ipu7_bus_mutex); + + pm_runtime_allow(&auxdev->dev); + + return 0; +} + +void ipu7_bus_del_devices(struct pci_dev *pdev) +{ + struct ipu7_device *isp = pci_get_drvdata(pdev); + struct ipu7_bus_device *adev, *save; + + mutex_lock(&ipu7_bus_mutex); + + list_for_each_entry_safe(adev, save, &isp->devices, list) { + pm_runtime_disable(&adev->auxdev.dev); + list_del(&adev->list); + auxiliary_device_delete(&adev->auxdev); + auxiliary_device_uninit(&adev->auxdev); + } + + mutex_unlock(&ipu7_bus_mutex); +} diff --git a/drivers/staging/media/ipu7/ipu7-bus.h b/drivers/staging/media/ipu7/ipu7-bus.h new file mode 100644 index 000000000000..45157df16e90 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-bus.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_BUS_H +#define IPU7_BUS_H + +#include +#include +#include +#include +#include +#include +#include + +#include "abi/ipu7_fw_boot_abi.h" + +#include "ipu7-syscom.h" + +struct pci_dev; +struct ipu_buttress_ctrl; +struct ipu7_mmu; +struct ipu7_device; + +enum ipu7_subsys { + IPU_IS = 0, + IPU_PS = 1, + IPU_SUBSYS_NUM = 2, +}; + +struct ipu7_bus_device { + struct auxiliary_device auxdev; + const struct auxiliary_driver *auxdrv; + const struct ipu7_auxdrv_data *auxdrv_data; + struct list_head list; + enum ipu7_subsys subsys; + void *pdata; + struct ipu7_mmu *mmu; + struct ipu7_device *isp; + const struct ipu_buttress_ctrl *ctrl; + u64 dma_mask; + struct sg_table fw_sgt; + u32 fw_entry; + struct ipu7_syscom_context *syscom; + struct ia_gofo_boot_config *boot_config; + dma_addr_t boot_config_dma_addr; + u32 boot_config_size; +}; + +struct ipu7_auxdrv_data { + irqreturn_t (*isr)(struct ipu7_bus_device *adev); + irqreturn_t (*isr_threaded)(struct ipu7_bus_device *adev); + bool wake_isr_thread; +}; + +#define to_ipu7_bus_device(_dev) \ + container_of(to_auxiliary_dev(_dev), struct ipu7_bus_device, auxdev) +#define auxdev_to_adev(_auxdev) \ + container_of(_auxdev, struct ipu7_bus_device, auxdev) +#define ipu7_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) + +struct ipu7_bus_device * +ipu7_bus_initialize_device(struct pci_dev *pdev, struct device *parent, + void *pdata, const struct ipu_buttress_ctrl *ctrl, + const char *name); +int ipu7_bus_add_device(struct ipu7_bus_device *adev); +void ipu7_bus_del_devices(struct pci_dev *pdev); +#endif diff --git a/drivers/staging/media/ipu7/ipu7-buttress-regs.h b/drivers/staging/media/ipu7/ipu7-buttress-regs.h new file mode 100644 index 000000000000..3eafd6a3813d --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-buttress-regs.h @@ -0,0 +1,461 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_BUTTRESS_REGS_H +#define IPU7_BUTTRESS_REGS_H + +#define BUTTRESS_REG_IRQ_STATUS 0x2000 +#define BUTTRESS_REG_IRQ_STATUS_UNMASKED 0x2004 +#define BUTTRESS_REG_IRQ_ENABLE 0x2008 +#define BUTTRESS_REG_IRQ_CLEAR 0x200c +#define BUTTRESS_REG_IRQ_MASK 0x2010 +#define BUTTRESS_REG_TSC_CMD 0x2014 +#define BUTTRESS_REG_TSC_CTL 0x2018 +#define BUTTRESS_REG_TSC_LO 0x201c +#define BUTTRESS_REG_TSC_HI 0x2020 + +/* valid for PTL */ +#define BUTTRESS_REG_PB_TIMESTAMP_LO 0x2030 +#define BUTTRESS_REG_PB_TIMESTAMP_HI 0x2034 +#define BUTTRESS_REG_PB_TIMESTAMP_VALID 0x2038 + +#define BUTTRESS_REG_PS_WORKPOINT_REQ 0x2100 +#define BUTTRESS_REG_IS_WORKPOINT_REQ 0x2104 +#define BUTTRESS_REG_PS_WORKPOINT_DOMAIN_REQ 0x2108 +#define BUTTRESS_REG_PS_DOMAINS_STATUS 0x2110 +#define BUTTRESS_REG_PWR_STATUS 0x2114 +#define BUTTRESS_REG_PS_WORKPOINT_REQ_SHADOW 0x2120 +#define BUTTRESS_REG_IS_WORKPOINT_REQ_SHADOW 0x2124 +#define BUTTRESS_REG_PS_WORKPOINT_DOMAIN_REQ_SHADOW 0x2128 +#define BUTTRESS_REG_ISPS_WORKPOINT_DOWNLOAD 0x212c +#define BUTTRESS_REG_PG_FLOW_OVERRIDE 0x2180 +#define BUTTRESS_REG_GLOBAL_OVERRIDE_UNGATE_CTL 0x2184 +#define BUTTRESS_REG_PWR_FSM_CTL 0x2188 +#define BUTTRESS_REG_IDLE_WDT 0x218c +#define BUTTRESS_REG_PS_PWR_DOMAIN_EVENTQ_EN 0x2190 +#define BUTTRESS_REG_PS_PWR_DOMAIN_EVENTQ_ADDR 0x2194 +#define BUTTRESS_REG_PS_PWR_DOMAIN_EVENTQ_DATA 0x2198 +#define BUTTRESS_REG_POWER_EN_DELAY 0x219c +#define IPU7_BUTTRESS_REG_LTR_CONTROL 0x21a0 +#define IPU7_BUTTRESS_REG_NDE_CONTROL 0x21a4 +#define IPU7_BUTTRESS_REG_INT_FRM_PUNIT 0x21a8 +#define IPU8_BUTTRESS_REG_LTR_CONTROL 0x21a4 +#define IPU8_BUTTRESS_REG_NDE_CONTROL 0x21a8 +#define IPU8_BUTTRESS_REG_INT_FRM_PUNIT 0x21ac +#define BUTTRESS_REG_SLEEP_LEVEL_CFG 0x21b0 +#define BUTTRESS_REG_SLEEP_LEVEL_STS 0x21b4 +#define BUTTRESS_REG_DVFS_FSM_STATUS 0x21b8 +#define BUTTRESS_REG_PS_PLL_ENABLE 0x21bc +#define BUTTRESS_REG_D2D_CTL 0x21d4 +#define BUTTRESS_REG_IB_CLK_CTL 0x21d8 +#define BUTTRESS_REG_IB_CRO_CLK_CTL 0x21dc +#define BUTTRESS_REG_FUNC_FUSES 0x21e0 +#define BUTTRESS_REG_ISOCH_CTL 0x21e4 +#define BUTTRESS_REG_WORKPOINT_CTL 0x21f0 +#define BUTTRESS_REG_DRV_IS_UCX_CONTROL_STATUS 0x2200 +#define BUTTRESS_REG_DRV_IS_UCX_START_ADDR 0x2204 +#define BUTTRESS_REG_DRV_PS_UCX_CONTROL_STATUS 0x2208 +#define BUTTRESS_REG_DRV_PS_UCX_START_ADDR 0x220c +#define BUTTRESS_REG_DRV_UCX_RESET_CFG 0x2210 + +/* configured by CSE */ +#define BUTTRESS_REG_CSE_IS_UCX_CONTROL_STATUS 0x2300 +#define BUTTRESS_REG_CSE_IS_UCX_START_ADDR 0x2304 +#define BUTTRESS_REG_CSE_PS_UCX_CONTROL_STATUS 0x2308 +#define BUTTRESS_REG_CSE_PS_UCX_START_ADDR 0x230c + +#define BUTTRESS_REG_CAMERA_MASK 0x2310 +#define BUTTRESS_REG_FW_CTL 0x2314 +#define BUTTRESS_REG_SECURITY_CTL 0x2318 +#define BUTTRESS_REG_FUNCTIONAL_FW_SETUP 0x231c +#define BUTTRESS_REG_FW_BASE 0x2320 +#define BUTTRESS_REG_FW_BASE_LIMIT 0x2324 +#define BUTTRESS_REG_FW_SCRATCH_BASE 0x2328 +#define BUTTRESS_REG_FW_SCRATCH_LIMIT 0x232c +#define BUTTRESS_REG_CSE_ACTION 0x2330 + +/* configured by SW */ +#define BUTTRESS_REG_FW_RESET_CTL 0x2334 +#define BUTTRESS_REG_FW_SOURCE_SIZE 0x2338 +#define BUTTRESS_REG_FW_SOURCE_BASE 0x233c + +#define BUTTRESS_REG_IPU_SEC_CP_LSB 0x2400 +#define BUTTRESS_REG_IPU_SEC_CP_MSB 0x2404 +#define BUTTRESS_REG_IPU_SEC_WAC_LSB 0x2408 +#define BUTTRESS_REG_IPU_SEC_WAC_MSB 0x240c +#define BUTTRESS_REG_IPU_SEC_RAC_LSB 0x2410 +#define BUTTRESS_REG_IPU_SEC_RAC_MSB 0x2414 +#define BUTTRESS_REG_IPU_DRV_CP_LSB 0x2418 +#define BUTTRESS_REG_IPU_DRV_CP_MSB 0x241c +#define BUTTRESS_REG_IPU_DRV_WAC_LSB 0x2420 +#define BUTTRESS_REG_IPU_DRV_WAC_MSB 0x2424 +#define BUTTRESS_REG_IPU_DRV_RAC_LSB 0x2428 +#define BUTTRESS_REG_IPU_DRV_RAC_MSB 0x242c +#define BUTTRESS_REG_IPU_FW_CP_LSB 0x2430 +#define BUTTRESS_REG_IPU_FW_CP_MSB 0x2434 +#define BUTTRESS_REG_IPU_FW_WAC_LSB 0x2438 +#define BUTTRESS_REG_IPU_FW_WAC_MSB 0x243c +#define BUTTRESS_REG_IPU_FW_RAC_LSB 0x2440 +#define BUTTRESS_REG_IPU_FW_RAC_MSB 0x2444 +#define BUTTRESS_REG_IPU_BIOS_SEC_CP_LSB 0x2448 +#define BUTTRESS_REG_IPU_BIOS_SEC_CP_MSB 0x244c +#define BUTTRESS_REG_IPU_BIOS_SEC_WAC_LSB 0x2450 +#define BUTTRESS_REG_IPU_BIOS_SEC_WAC_MSB 0x2454 +#define BUTTRESS_REG_IPU_BIOS_SEC_RAC_LSB 0x2458 +#define BUTTRESS_REG_IPU_BIOS_SEC_RAC_MSB 0x245c +#define BUTTRESS_REG_IPU_DFD_CP_LSB 0x2460 +#define BUTTRESS_REG_IPU_DFD_CP_MSB 0x2464 +#define BUTTRESS_REG_IPU_DFD_WAC_LSB 0x2468 +#define BUTTRESS_REG_IPU_DFD_WAC_MSB 0x246c +#define BUTTRESS_REG_IPU_DFD_RAC_LSB 0x2470 +#define BUTTRESS_REG_IPU_DFD_RAC_MSB 0x2474 +#define BUTTRESS_REG_CSE2IUDB0 0x2500 +#define BUTTRESS_REG_CSE2IUDATA0 0x2504 +#define BUTTRESS_REG_CSE2IUCSR 0x2508 +#define BUTTRESS_REG_IU2CSEDB0 0x250c +#define BUTTRESS_REG_IU2CSEDATA0 0x2510 +#define BUTTRESS_REG_IU2CSECSR 0x2514 +#define BUTTRESS_REG_CSE2IUDB0_CR_SHADOW 0x2520 +#define BUTTRESS_REG_CSE2IUDATA0_CR_SHADOW 0x2524 +#define BUTTRESS_REG_CSE2IUCSR_CR_SHADOW 0x2528 +#define BUTTRESS_REG_IU2CSEDB0_CR_SHADOW 0x252c +#define BUTTRESS_REG_DVFS_FSM_SURVIVABILITY 0x2900 +#define BUTTRESS_REG_FLOWS_FSM_SURVIVABILITY 0x2904 +#define BUTTRESS_REG_FABRICS_FSM_SURVIVABILITY 0x2908 +#define BUTTRESS_REG_PS_SUB1_PM_FSM_SURVIVABILITY 0x290c +#define BUTTRESS_REG_PS_SUB0_PM_FSM_SURVIVABILITY 0x2910 +#define BUTTRESS_REG_PS_PM_FSM_SURVIVABILITY 0x2914 +#define BUTTRESS_REG_IS_PM_FSM_SURVIVABILITY 0x2918 +#define BUTTRESS_REG_FLR_RST_FSM_SURVIVABILITY 0x291c +#define BUTTRESS_REG_FW_RST_FSM_SURVIVABILITY 0x2920 +#define BUTTRESS_REG_RESETPREP_FSM_SURVIVABILITY 0x2924 +#define BUTTRESS_REG_POWER_FSM_DOMAIN_STATUS 0x3000 +#define BUTTRESS_REG_IDLEREQ_STATUS1 0x3004 +#define BUTTRESS_REG_POWER_FSM_STATUS_IS_PS 0x3008 +#define BUTTRESS_REG_POWER_ACK_B_STATUS 0x300c +#define BUTTRESS_REG_DOMAIN_RETENTION_CTL 0x3010 +#define BUTTRESS_REG_CG_CTRL_BITS 0x3014 +#define BUTTRESS_REG_IS_IFC_STATUS0 0x3018 +#define BUTTRESS_REG_IS_IFC_STATUS1 0x301c +#define BUTTRESS_REG_PS_IFC_STATUS0 0x3020 +#define BUTTRESS_REG_PS_IFC_STATUS1 0x3024 +#define BUTTRESS_REG_BTRS_IFC_STATUS0 0x3028 +#define BUTTRESS_REG_BTRS_IFC_STATUS1 0x302c +#define BUTTRESS_REG_IPU_SKU 0x3030 +#define BUTTRESS_REG_PS_IDLEACK 0x3034 +#define BUTTRESS_REG_IS_IDLEACK 0x3038 +#define BUTTRESS_REG_SPARE_REGS_0 0x303c +#define BUTTRESS_REG_SPARE_REGS_1 0x3040 +#define BUTTRESS_REG_SPARE_REGS_2 0x3044 +#define BUTTRESS_REG_SPARE_REGS_3 0x3048 +#define BUTTRESS_REG_IUNIT_ACV 0x304c +#define BUTTRESS_REG_CHICKEN_BITS 0x3050 +#define BUTTRESS_REG_SBENDPOINT_CFG 0x3054 +#define BUTTRESS_REG_ECC_ERR_LOG 0x3058 +#define BUTTRESS_REG_POWER_FSM_STATUS 0x3070 +#define BUTTRESS_REG_RESET_FSM_STATUS 0x3074 +#define BUTTRESS_REG_IDLE_STATUS 0x3078 +#define BUTTRESS_REG_IDLEACK_STATUS 0x307c +#define BUTTRESS_REG_IPU_DEBUG 0x3080 + +#define BUTTRESS_REG_FW_BOOT_PARAMS0 0x4000 +#define BUTTRESS_REG_FW_BOOT_PARAMS1 0x4004 +#define BUTTRESS_REG_FW_BOOT_PARAMS2 0x4008 +#define BUTTRESS_REG_FW_BOOT_PARAMS3 0x400c +#define BUTTRESS_REG_FW_BOOT_PARAMS4 0x4010 +#define BUTTRESS_REG_FW_BOOT_PARAMS5 0x4014 +#define BUTTRESS_REG_FW_BOOT_PARAMS6 0x4018 +#define BUTTRESS_REG_FW_BOOT_PARAMS7 0x401c +#define BUTTRESS_REG_FW_BOOT_PARAMS8 0x4020 +#define BUTTRESS_REG_FW_BOOT_PARAMS9 0x4024 +#define BUTTRESS_REG_FW_BOOT_PARAMS10 0x4028 +#define BUTTRESS_REG_FW_BOOT_PARAMS11 0x402c +#define BUTTRESS_REG_FW_BOOT_PARAMS12 0x4030 +#define BUTTRESS_REG_FW_BOOT_PARAMS13 0x4034 +#define BUTTRESS_REG_FW_BOOT_PARAMS14 0x4038 +#define BUTTRESS_REG_FW_BOOT_PARAMS15 0x403c + +#define BUTTRESS_FW_BOOT_PARAMS_ENTRY(i) \ + (BUTTRESS_REG_FW_BOOT_PARAMS0 + ((i) * 4U)) +#define BUTTRESS_REG_FW_GP(i) (0x4040 + 0x4 * (i)) +#define BUTTRESS_REG_FPGA_SUPPORT(i) (0x40c0 + 0x4 * (i)) + +#define BUTTRESS_REG_FW_GP8 0x4060 +#define BUTTRESS_REG_FW_GP24 0x40a0 + +#define BUTTRESS_REG_GPIO_0_PADCFG_ADDR_CR 0x4100 +#define BUTTRESS_REG_GPIO_1_PADCFG_ADDR_CR 0x4104 +#define BUTTRESS_REG_GPIO_2_PADCFG_ADDR_CR 0x4108 +#define BUTTRESS_REG_GPIO_3_PADCFG_ADDR_CR 0x410c +#define BUTTRESS_REG_GPIO_4_PADCFG_ADDR_CR 0x4110 +#define BUTTRESS_REG_GPIO_5_PADCFG_ADDR_CR 0x4114 +#define BUTTRESS_REG_GPIO_6_PADCFG_ADDR_CR 0x4118 +#define BUTTRESS_REG_GPIO_7_PADCFG_ADDR_CR 0x411c +#define BUTTRESS_REG_GPIO_ENABLE 0x4140 +#define BUTTRESS_REG_GPIO_VALUE_CR 0x4144 + +#define BUTTRESS_REG_IS_MEM_CORRECTABLE_ERROR_STATUS 0x5000 +#define BUTTRESS_REG_IS_MEM_FATAL_ERROR_STATUS 0x5004 +#define BUTTRESS_REG_IS_MEM_NON_FATAL_ERROR_STATUS 0x5008 +#define BUTTRESS_REG_IS_MEM_CHECK_PASSED 0x500c +#define BUTTRESS_REG_IS_MEM_ERROR_INJECT 0x5010 +#define BUTTRESS_REG_IS_MEM_ERROR_CLEAR 0x5014 +#define BUTTRESS_REG_PS_MEM_CORRECTABLE_ERROR_STATUS 0x5040 +#define BUTTRESS_REG_PS_MEM_FATAL_ERROR_STATUS 0x5044 +#define BUTTRESS_REG_PS_MEM_NON_FATAL_ERROR_STATUS 0x5048 +#define BUTTRESS_REG_PS_MEM_CHECK_PASSED 0x504c +#define BUTTRESS_REG_PS_MEM_ERROR_INJECT 0x5050 +#define BUTTRESS_REG_PS_MEM_ERROR_CLEAR 0x5054 + +#define BUTTRESS_REG_IS_AB_REGION_MIN_ADDRESS(i) (0x6000 + 0x8 * (i)) +#define BUTTRESS_REG_IS_AB_REGION_MAX_ADDRESS(i) (0x6004 + 0x8 * (i)) +#define BUTTRESS_REG_IS_AB_VIOLATION_LOG0 0x6080 +#define BUTTRESS_REG_IS_AB_VIOLATION_LOG1 0x6084 +#define BUTTRESS_REG_PS_AB_REGION_MIN_ADDRESS(i) (0x6100 + 0x8 * (i)) +#define BUTTRESS_REG_PS_AB_REGION_MAX_ADDRESS0 (0x6104 + 0x8 * (i)) +#define BUTTRESS_REG_PS_AB_VIOLATION_LOG0 0x6180 +#define BUTTRESS_REG_PS_AB_VIOLATION_LOG1 0x6184 +#define BUTTRESS_REG_PS_DEBUG_AB_VIOLATION_LOG0 0x6200 +#define BUTTRESS_REG_PS_DEBUG_AB_VIOLATION_LOG1 0x6204 +#define BUTTRESS_REG_IS_DEBUG_AB_VIOLATION_LOG0 0x6208 +#define BUTTRESS_REG_IS_DEBUG_AB_VIOLATION_LOG1 0x620c +#define BUTTRESS_REG_IB_DVP_AB_VIOLATION_LOG0 0x6210 +#define BUTTRESS_REG_IB_DVP_AB_VIOLATION_LOG1 0x6214 +#define BUTTRESS_REG_IB_ATB2DTF_AB_VIOLATION_LOG0 0x6218 +#define BUTTRESS_REG_IB_ATB2DTF_AB_VIOLATION_LOG1 0x621c +#define BUTTRESS_REG_AB_ENABLE 0x6220 +#define BUTTRESS_REG_AB_DEFAULT_ACCESS 0x6230 + +/* Indicates CSE has received an IPU driver IPC transaction */ +#define BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE BIT(0) +/* Indicates an IPC transaction from CSE has arrived */ +#define BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING BIT(1) +/* Indicates a CSR update from CSE has arrived */ +#define BUTTRESS_IRQ_CSE_CSR_SET BIT(2) +/* Indicates an interrupt set by Punit (not in use at this time) */ +#define BUTTRESS_IRQ_PUNIT_2_IUNIT_IRQ BIT(3) +/* Indicates an SAI violation was detected on access to IB registers */ +#define BUTTRESS_IRQ_SAI_VIOLATION BIT(4) +/* Indicates a transaction to IS was not able to pass the access blocker */ +#define BUTTRESS_IRQ_IS_AB_VIOLATION BIT(5) +/* Indicates a transaction to PS was not able to pass the access blocker */ +#define BUTTRESS_IRQ_PS_AB_VIOLATION BIT(6) +/* Indicates an error response was detected by the IB config NoC */ +#define BUTTRESS_IRQ_IB_CFG_NOC_ERR_IRQ BIT(7) +/* Indicates an error response was detected by the IB data NoC */ +#define BUTTRESS_IRQ_IB_DATA_NOC_ERR_IRQ BIT(8) +/* Transaction to DVP regs was not able to pass the access blocker */ +#define BUTTRESS_IRQ_IB_DVP_AB_VIOLATION BIT(9) +/* Transaction to ATB2DTF regs was not able to pass the access blocker */ +#define BUTTRESS_IRQ_ATB2DTF_AB_VIOLATION BIT(10) +/* Transaction to IS debug regs was not able to pass the access blocker */ +#define BUTTRESS_IRQ_IS_DEBUG_AB_VIOLATION BIT(11) +/* Transaction to PS debug regs was not able to pass the access blocker */ +#define BUTTRESS_IRQ_PS_DEBUG_AB_VIOLATION BIT(12) +/* Indicates timeout occurred waiting for a response from a target */ +#define BUTTRESS_IRQ_IB_CFG_NOC_TIMEOUT_IRQ BIT(13) +/* Set when any correctable ECC error input wire to buttress is set */ +#define BUTTRESS_IRQ_ECC_CORRECTABLE BIT(14) +/* Any noncorrectable-nonfatal ECC error input wire to buttress is set */ +#define BUTTRESS_IRQ_ECC_NONCORRECTABLE_NONFATAL BIT(15) +/* Set when any noncorrectable-fatal ECC error input wire to buttress is set */ +#define BUTTRESS_IRQ_ECC_NONCORRECTABLE_FATAL BIT(16) +/* Set when timeout occurred waiting for a response from a target */ +#define BUTTRESS_IRQ_IS_CFG_NOC_TIMEOUT_IRQ BIT(17) +#define BUTTRESS_IRQ_PS_CFG_NOC_TIMEOUT_IRQ BIT(18) +#define BUTTRESS_IRQ_LB_CFG_NOC_TIMEOUT_IRQ BIT(19) +/* IS FW double exception event */ +#define BUTTRESS_IRQ_IS_UC_PFATAL_ERROR BIT(26) +/* PS FW double exception event */ +#define BUTTRESS_IRQ_PS_UC_PFATAL_ERROR BIT(27) +/* IS FW watchdog event */ +#define BUTTRESS_IRQ_IS_WATCHDOG BIT(28) +/* PS FW watchdog event */ +#define BUTTRESS_IRQ_PS_WATCHDOG BIT(29) +/* IS IRC irq out */ +#define BUTTRESS_IRQ_IS_IRQ BIT(30) +/* PS IRC irq out */ +#define BUTTRESS_IRQ_PS_IRQ BIT(31) + +/* buttress irq */ +#define BUTTRESS_PWR_STATUS_HH_STATE_IDLE 0U +#define BUTTRESS_PWR_STATUS_HH_STATE_IN_PRGS 1U +#define BUTTRESS_PWR_STATUS_HH_STATE_DONE 2U +#define BUTTRESS_PWR_STATUS_HH_STATE_ERR 3U + +#define BUTTRESS_TSC_CMD_START_TSC_SYNC BIT(0) +#define BUTTRESS_PWR_STATUS_HH_STATUS_SHIFT 11 +#define BUTTRESS_PWR_STATUS_HH_STATUS_MASK (0x3U << 11) +#define BUTTRESS_TSW_WA_SOFT_RESET BIT(8) +/* new for PTL */ +#define BUTTRESS_SEL_PB_TIMESTAMP BIT(9) +#define BUTTRESS_IRQS (BUTTRESS_IRQ_IS_IRQ | \ + BUTTRESS_IRQ_PS_IRQ | \ + BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING | \ + BUTTRESS_IRQ_CSE_CSR_SET | \ + BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE | \ + BUTTRESS_IRQ_PUNIT_2_IUNIT_IRQ) + +/* Iunit to CSE regs */ +#define BUTTRESS_IU2CSEDB0_BUSY BIT(31) +#define BUTTRESS_IU2CSEDB0_SHORT_FORMAT_SHIFT 27 +#define BUTTRESS_IU2CSEDB0_CLIENT_ID_SHIFT 10 +#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2 + +#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2 +#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3 +#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16 + +#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1) +#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2) +#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4) + +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0) +#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1) +#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3) +#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4) +#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5) + +/* 0x20 == NACK, 0xf == unknown command */ +#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20 +#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK 0xffff + +/* IS/PS freq control */ +#define BUTTRESS_IS_FREQ_CTL_RATIO_MASK 0xffU +#define BUTTRESS_PS_FREQ_CTL_RATIO_MASK 0xffU + +#define IPU7_IS_FREQ_MAX 450 +#define IPU7_IS_FREQ_MIN 50 +#define IPU7_PS_FREQ_MAX 750 +#define BUTTRESS_PS_FREQ_RATIO_STEP 25U +/* valid for IPU8 */ +#define BUTTRESS_IS_FREQ_RATIO_STEP 25U + +/* IS: 400mhz, PS: 500mhz */ +#define IPU7_IS_FREQ_CTL_DEFAULT_RATIO 0x1b +#define IPU7_PS_FREQ_CTL_DEFAULT_RATIO 0x14 +/* IS: 400mhz, PS: 400mhz */ +#define IPU8_IS_FREQ_CTL_DEFAULT_RATIO 0x10 +#define IPU8_PS_FREQ_CTL_DEFAULT_RATIO 0x10 + +#define IPU_FREQ_CTL_CDYN 0x80 +#define IPU_FREQ_CTL_RATIO_SHIFT 0x0 +#define IPU_FREQ_CTL_CDYN_SHIFT 0x8 + +/* buttree power status */ +#define IPU_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 0 +#define IPU_BUTTRESS_PWR_STATE_IS_PWR_MASK \ + (0x3U << IPU_BUTTRESS_PWR_STATE_IS_PWR_SHIFT) + +#define IPU_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 4 +#define IPU_BUTTRESS_PWR_STATE_PS_PWR_MASK \ + (0x3U << IPU_BUTTRESS_PWR_STATE_PS_PWR_SHIFT) + +#define IPU_BUTTRESS_PWR_STATE_DN_DONE 0x0 +#define IPU_BUTTRESS_PWR_STATE_UP_PROCESS 0x1 +#define IPU_BUTTRESS_PWR_STATE_DN_PROCESS 0x2 +#define IPU_BUTTRESS_PWR_STATE_UP_DONE 0x3 + +#define BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3 +#define BUTTRESS_PWR_STATE_IS_PWR_MASK (0x3 << 3) + +#define BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6 +#define BUTTRESS_PWR_STATE_PS_PWR_MASK (0x3 << 6) + +#define PS_FSM_CG BIT(3) + +#define BUTTRESS_OVERRIDE_IS_CLK BIT(1) +#define BUTTRESS_OVERRIDE_PS_CLK BIT(2) +/* ps_pll only valid for ipu8 */ +#define BUTTRESS_OWN_ACK_PS_PLL BIT(8) +#define BUTTRESS_OWN_ACK_IS_CLK BIT(9) +#define BUTTRESS_OWN_ACK_PS_CLK BIT(10) + +/* FW reset ctrl */ +#define BUTTRESS_FW_RESET_CTL_START BIT(0) +#define BUTTRESS_FW_RESET_CTL_DONE BIT(1) + +/* security */ +#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16) +#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0) + +#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0) +#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1) +#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3) + +/* D2D */ +#define BUTTRESS_D2D_PWR_EN BIT(0) +#define BUTTRESS_D2D_PWR_ACK BIT(4) + +/* NDE */ +#define NDE_VAL_MASK GENMASK(9, 0) +#define NDE_SCALE_MASK GENMASK(12, 10) +#define NDE_VALID_MASK BIT(13) +#define NDE_RESVEC_MASK GENMASK(19, 16) +#define NDE_IN_VBLANK_DIS_MASK BIT(31) + +#define BUTTRESS_NDE_VAL_ACTIVE 48 +#define BUTTRESS_NDE_SCALE_ACTIVE 2 +#define BUTTRESS_NDE_VALID_ACTIVE 1 + +#define BUTTRESS_NDE_VAL_DEFAULT 1023 +#define BUTTRESS_NDE_SCALE_DEFAULT 2 +#define BUTTRESS_NDE_VALID_DEFAULT 0 + +/* IS and PS UCX control */ +#define UCX_CTL_RESET BIT(0) +#define UCX_CTL_RUN BIT(1) +#define UCX_CTL_WAKEUP BIT(2) +#define UCX_CTL_SPARE GENMASK(7, 3) +#define UCX_STS_PWR GENMASK(17, 16) +#define UCX_STS_SLEEPING BIT(18) + +/* offset from PHY base */ +#define PHY_CSI_CFG 0xc0 +#define PHY_CSI_RCOMP_CONTROL 0xc8 +#define PHY_CSI_BSCAN_EXCLUDE 0xd8 + +#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) +#define PHY_DPHY_DLL_OVRD(x) (0x14c + 0x100 * (x)) +#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) +#define PHY_CPHY_RX_CONTROL2(x) (0x114 + 0x100 * (x)) +#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) +#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) + +/* PB registers */ +#define INTERRUPT_STATUS 0x0 +#define BTRS_LOCAL_INTERRUPT_MASK 0x4 +#define GLOBAL_INTERRUPT_MASK 0x8 +#define HM_ATS 0xc +#define ATS_ERROR_LOG1 0x10 +#define ATS_ERROR_LOG2 0x14 +#define ATS_ERROR_CLEAR 0x18 +#define CFI_0_ERROR_LOG 0x1c +#define CFI_0_ERROR_CLEAR 0x20 +#define HASH_CONFIG 0x2c +#define TLBID_HASH_ENABLE_31_0 0x30 +#define TLBID_HASH_ENABLE_63_32 0x34 +#define TLBID_HASH_ENABLE_95_64 0x38 +#define TLBID_HASH_ENABLE_127_96 0x3c +#define CFI_1_ERROR_LOGGING 0x40 +#define CFI_1_ERROR_CLEAR 0x44 +#define IMR_ERROR_LOGGING_LOW 0x48 +#define IMR_ERROR_LOGGING_HIGH 0x4c +#define IMR_ERROR_CLEAR 0x50 +#define PORT_ARBITRATION_WEIGHTS 0x54 +#define IMR_ERROR_LOGGING_CFI_1_LOW 0x58 +#define IMR_ERROR_LOGGING_CFI_1_HIGH 0x5c +#define IMR_ERROR_CLEAR_CFI_1 0x60 +#define BAR2_MISC_CONFIG 0x64 +#define RSP_ID_CONFIG_AXI2CFI_0 0x68 +#define RSP_ID_CONFIG_AXI2CFI_1 0x6c +#define PB_DRIVER_PCODE_MAILBOX_STATUS 0x70 +#define PB_DRIVER_PCODE_MAILBOX_INTERFACE 0x74 +#define PORT_ARBITRATION_WEIGHTS_ATS 0x78 + +#endif /* IPU7_BUTTRESS_REGS_H */ diff --git a/drivers/staging/media/ipu7/ipu7-buttress.c b/drivers/staging/media/ipu7/ipu7-buttress.c new file mode 100644 index 000000000000..e5707f5e300b --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-buttress.c @@ -0,0 +1,1192 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-buttress.h" +#include "ipu7-buttress-regs.h" + +#define BOOTLOADER_STATUS_OFFSET BUTTRESS_REG_FW_BOOT_PARAMS7 + +#define BOOTLOADER_MAGIC_KEY 0xb00710adU + +#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 +#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 +#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE + +#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10U + +#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) + +#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) +#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) +#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC +#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) +#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) + +#define BUTTRESS_IPC_RESET_RETRY 2000U +#define BUTTRESS_CSE_IPC_RESET_RETRY 4U +#define BUTTRESS_IPC_CMD_SEND_RETRY 1U + +struct ipu7_ipc_buttress_msg { + u32 cmd; + u32 expected_resp; + bool require_resp; + u8 cmd_size; +}; + +static const u32 ipu7_adev_irq_mask[2] = { + BUTTRESS_IRQ_IS_IRQ, + BUTTRESS_IRQ_PS_IRQ +}; + +int ipu_buttress_ipc_reset(struct ipu7_device *isp, + struct ipu_buttress_ipc *ipc) +{ + unsigned int retries = BUTTRESS_IPC_RESET_RETRY; + struct ipu_buttress *b = &isp->buttress; + struct device *dev = &isp->pdev->dev; + u32 val = 0, csr_in_clr; + + if (!isp->secure_mode) { + dev_dbg(dev, "Skip IPC reset for non-secure mode\n"); + return 0; + } + + mutex_lock(&b->ipc_mutex); + + /* Clear-by-1 CSR (all bits), corresponding internal states. */ + val = readl(isp->base + ipc->csr_in); + writel(val, isp->base + ipc->csr_in); + + /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ + writel(ENTRY, isp->base + ipc->csr_out); + /* + * Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + */ + csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | + BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | + BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; + + do { + usleep_range(400, 500); + val = readl(isp->base + ipc->csr_in); + switch (val) { + case ENTRY | EXIT: + case ENTRY | EXIT | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_COMP_ACTIONS_RST_PHASE2). + * 2) Set peer CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + */ + writel(ENTRY | EXIT, isp->base + ipc->csr_in); + writel(QUERY, isp->base + ipc->csr_out); + break; + case ENTRY: + case ENTRY | QUERY: + /* + * 1) Clear-by-1 CSR bits + * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1. + */ + writel(ENTRY | QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + case EXIT: + case EXIT | QUERY: + /* + * Clear-by-1 CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * 1) Clear incoming doorbell. + * 2) Clear-by-1 all CSR bits EXCEPT following + * bits: + * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. + * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. + * C. Possibly custom bits, depending on + * their role. + * 3) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE2. + */ + writel(EXIT, isp->base + ipc->csr_in); + writel(0, isp->base + ipc->db0_in); + writel(csr_in_clr, isp->base + ipc->csr_in); + writel(EXIT, isp->base + ipc->csr_out); + + /* + * Read csr_in again to make sure if RST_PHASE2 is done. + * If csr_in is QUERY, it should be handled again. + */ + usleep_range(200, 300); + val = readl(isp->base + ipc->csr_in); + if (val & QUERY) { + dev_dbg(dev, + "RST_PHASE2 retry csr_in = %x\n", val); + break; + } + mutex_unlock(&b->ipc_mutex); + return 0; + case QUERY: + /* + * 1) Clear-by-1 CSR bit + * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. + * 2) Set peer CSR bit + * IPC_PEER_COMP_ACTIONS_RST_PHASE1 + */ + writel(QUERY, isp->base + ipc->csr_in); + writel(ENTRY, isp->base + ipc->csr_out); + break; + default: + dev_dbg_ratelimited(dev, "Unexpected CSR 0x%x\n", val); + break; + } + } while (retries--); + + mutex_unlock(&b->ipc_mutex); + dev_err(dev, "Timed out while waiting for CSE\n"); + + return -ETIMEDOUT; +} + +static void ipu_buttress_ipc_validity_close(struct ipu7_device *isp, + struct ipu_buttress_ipc *ipc) +{ + writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); +} + +static int +ipu_buttress_ipc_validity_open(struct ipu7_device *isp, + struct ipu_buttress_ipc *ipc) +{ + unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; + void __iomem *addr; + int ret; + u32 val; + + writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, + isp->base + ipc->csr_out); + + addr = isp->base + ipc->csr_in; + ret = readl_poll_timeout(addr, val, val & mask, 200, + BUTTRESS_IPC_VALIDITY_TIMEOUT_US); + if (ret) { + dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); + ipu_buttress_ipc_validity_close(isp, ipc); + } + + return ret; +} + +static void ipu_buttress_ipc_recv(struct ipu7_device *isp, + struct ipu_buttress_ipc *ipc, u32 *ipc_msg) +{ + if (ipc_msg) + *ipc_msg = readl(isp->base + ipc->data0_in); + writel(0, isp->base + ipc->db0_in); +} + +static int ipu_buttress_ipc_send_msg(struct ipu7_device *isp, + struct ipu7_ipc_buttress_msg *msg) +{ + unsigned long tx_timeout_jiffies, rx_timeout_jiffies; + unsigned int retry = BUTTRESS_IPC_CMD_SEND_RETRY; + struct ipu_buttress *b = &isp->buttress; + struct ipu_buttress_ipc *ipc = &b->cse; + struct device *dev = &isp->pdev->dev; + int tout; + u32 val; + int ret; + + mutex_lock(&b->ipc_mutex); + + ret = ipu_buttress_ipc_validity_open(isp, ipc); + if (ret) { + dev_err(dev, "IPC validity open failed\n"); + goto out; + } + + tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); + rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); + +try: + reinit_completion(&ipc->send_complete); + if (msg->require_resp) + reinit_completion(&ipc->recv_complete); + + dev_dbg(dev, "IPC command: 0x%x\n", msg->cmd); + writel(msg->cmd, isp->base + ipc->data0_out); + val = BUTTRESS_IU2CSEDB0_BUSY | msg->cmd_size; + writel(val, isp->base + ipc->db0_out); + + tout = wait_for_completion_timeout(&ipc->send_complete, + tx_timeout_jiffies); + if (!tout) { + dev_err(dev, "send IPC response timeout\n"); + if (!retry--) { + ret = -ETIMEDOUT; + goto out; + } + + /* Try again if CSE is not responding on first try */ + writel(0, isp->base + ipc->db0_out); + goto try; + } + + if (!msg->require_resp) { + ret = -EIO; + goto out; + } + + tout = wait_for_completion_timeout(&ipc->recv_complete, + rx_timeout_jiffies); + if (!tout) { + dev_err(dev, "recv IPC response timeout\n"); + ret = -ETIMEDOUT; + goto out; + } + + if (ipc->nack_mask && + (ipc->recv_data & ipc->nack_mask) == ipc->nack) { + dev_err(dev, "IPC NACK for cmd 0x%x\n", msg->cmd); + ret = -EIO; + goto out; + } + + if (ipc->recv_data != msg->expected_resp) { + dev_err(dev, + "expected resp: 0x%x, IPC response: 0x%x\n", + msg->expected_resp, ipc->recv_data); + ret = -EIO; + goto out; + } + + dev_dbg(dev, "IPC commands done\n"); + +out: + ipu_buttress_ipc_validity_close(isp, ipc); + mutex_unlock(&b->ipc_mutex); + + return ret; +} + +static int ipu_buttress_ipc_send(struct ipu7_device *isp, + u32 ipc_msg, u32 size, bool require_resp, + u32 expected_resp) +{ + struct ipu7_ipc_buttress_msg msg = { + .cmd = ipc_msg, + .cmd_size = size, + .require_resp = require_resp, + .expected_resp = expected_resp, + }; + + return ipu_buttress_ipc_send_msg(isp, &msg); +} + +static irqreturn_t ipu_buttress_call_isr(struct ipu7_bus_device *adev) +{ + irqreturn_t ret = IRQ_WAKE_THREAD; + + if (!adev || !adev->auxdrv || !adev->auxdrv_data) + return IRQ_NONE; + + if (adev->auxdrv_data->isr) + ret = adev->auxdrv_data->isr(adev); + + if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) + ret = IRQ_NONE; + + return ret; +} + +irqreturn_t ipu_buttress_isr(int irq, void *isp_ptr) +{ + struct ipu7_device *isp = isp_ptr; + struct ipu7_bus_device *adev[] = { isp->isys, isp->psys }; + struct ipu_buttress *b = &isp->buttress; + struct device *dev = &isp->pdev->dev; + irqreturn_t ret = IRQ_NONE; + u32 pb_irq, pb_local_irq; + u32 disable_irqs = 0; + u32 irq_status; + unsigned int i; + + pm_runtime_get_noresume(dev); + + pb_irq = readl(isp->pb_base + INTERRUPT_STATUS); + writel(pb_irq, isp->pb_base + INTERRUPT_STATUS); + + /* check btrs ATS, CFI and IMR errors, BIT(0) is unused for IPU */ + pb_local_irq = readl(isp->pb_base + BTRS_LOCAL_INTERRUPT_MASK); + if (pb_local_irq & ~BIT(0)) { + dev_warn(dev, "PB interrupt status 0x%x local 0x%x\n", pb_irq, + pb_local_irq); + dev_warn(dev, "Details: %x %x %x %x %x %x %x %x\n", + readl(isp->pb_base + ATS_ERROR_LOG1), + readl(isp->pb_base + ATS_ERROR_LOG2), + readl(isp->pb_base + CFI_0_ERROR_LOG), + readl(isp->pb_base + CFI_1_ERROR_LOGGING), + readl(isp->pb_base + IMR_ERROR_LOGGING_LOW), + readl(isp->pb_base + IMR_ERROR_LOGGING_HIGH), + readl(isp->pb_base + IMR_ERROR_LOGGING_CFI_1_LOW), + readl(isp->pb_base + IMR_ERROR_LOGGING_CFI_1_HIGH)); + } + + irq_status = readl(isp->base + BUTTRESS_REG_IRQ_STATUS); + if (!irq_status) { + pm_runtime_put_noidle(dev); + return IRQ_NONE; + } + + do { + writel(irq_status, isp->base + BUTTRESS_REG_IRQ_CLEAR); + + for (i = 0; i < ARRAY_SIZE(ipu7_adev_irq_mask); i++) { + irqreturn_t r = ipu_buttress_call_isr(adev[i]); + + if (!(irq_status & ipu7_adev_irq_mask[i])) + continue; + + if (r == IRQ_WAKE_THREAD) { + ret = IRQ_WAKE_THREAD; + disable_irqs |= ipu7_adev_irq_mask[i]; + } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { + ret = IRQ_HANDLED; + } + } + + if (irq_status & (BUTTRESS_IRQS | BUTTRESS_IRQ_SAI_VIOLATION) && + ret == IRQ_NONE) + ret = IRQ_HANDLED; + + if (irq_status & BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING) { + dev_dbg(dev, "BUTTRESS_IRQ_IPC_FROM_CSE_IS_WAITING\n"); + ipu_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); + complete(&b->cse.recv_complete); + } + + if (irq_status & BUTTRESS_IRQ_CSE_CSR_SET) + dev_dbg(dev, "BUTTRESS_IRQ_CSE_CSR_SET\n"); + + if (irq_status & BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE) { + dev_dbg(dev, "BUTTRESS_IRQ_IPC_EXEC_DONE_BY_CSE\n"); + complete(&b->cse.send_complete); + } + + if (irq_status & BUTTRESS_IRQ_PUNIT_2_IUNIT_IRQ) + dev_dbg(dev, "BUTTRESS_IRQ_PUNIT_2_IUNIT_IRQ\n"); + + if (irq_status & BUTTRESS_IRQ_SAI_VIOLATION && + ipu_buttress_get_secure_mode(isp)) + dev_err(dev, "BUTTRESS_IRQ_SAI_VIOLATION\n"); + + irq_status = readl(isp->base + BUTTRESS_REG_IRQ_STATUS); + } while (irq_status); + + if (disable_irqs) + writel(BUTTRESS_IRQS & ~disable_irqs, + isp->base + BUTTRESS_REG_IRQ_ENABLE); + + pm_runtime_put(dev); + + return ret; +} + +irqreturn_t ipu_buttress_isr_threaded(int irq, void *isp_ptr) +{ + struct ipu7_device *isp = isp_ptr; + struct ipu7_bus_device *adev[] = { isp->isys, isp->psys }; + const struct ipu7_auxdrv_data *drv_data = NULL; + irqreturn_t ret = IRQ_NONE; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu7_adev_irq_mask) && adev[i]; i++) { + drv_data = adev[i]->auxdrv_data; + if (!drv_data) + continue; + + if (drv_data->wake_isr_thread && + drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) + ret = IRQ_HANDLED; + } + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_IRQ_ENABLE); + + return ret; +} + +static int isys_d2d_power(struct device *dev, bool on) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + int ret = 0; + u32 target = on ? BUTTRESS_D2D_PWR_ACK : 0U; + u32 val; + + dev_dbg(dev, "power %s isys d2d.\n", on ? "UP" : "DOWN"); + val = readl(isp->base + BUTTRESS_REG_D2D_CTL); + if ((val & BUTTRESS_D2D_PWR_ACK) == target) { + dev_info(dev, "d2d already in %s state.\n", + on ? "UP" : "DOWN"); + return 0; + } + + val = on ? val | BUTTRESS_D2D_PWR_EN : val & (~BUTTRESS_D2D_PWR_EN); + writel(val, isp->base + BUTTRESS_REG_D2D_CTL); + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_D2D_CTL, + val, (val & BUTTRESS_D2D_PWR_ACK) == target, + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_err(dev, "power %s d2d timeout. status: 0x%x\n", + on ? "UP" : "DOWN", val); + + return ret; +} + +static void isys_nde_control(struct device *dev, bool on) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + u32 val, value, scale, valid, resvec; + u32 nde_reg; + + if (on) { + value = BUTTRESS_NDE_VAL_ACTIVE; + scale = BUTTRESS_NDE_SCALE_ACTIVE; + valid = BUTTRESS_NDE_VALID_ACTIVE; + } else { + value = BUTTRESS_NDE_VAL_DEFAULT; + scale = BUTTRESS_NDE_SCALE_DEFAULT; + valid = BUTTRESS_NDE_VALID_DEFAULT; + } + + /* only set the fabrics resource ownership for ipu8 */ + nde_reg = is_ipu8(isp->hw_ver) ? IPU8_BUTTRESS_REG_NDE_CONTROL : + IPU7_BUTTRESS_REG_NDE_CONTROL; + resvec = is_ipu8(isp->hw_ver) ? 0x2 : 0xe; + val = FIELD_PREP(NDE_VAL_MASK, value) | + FIELD_PREP(NDE_SCALE_MASK, scale) | + FIELD_PREP(NDE_VALID_MASK, valid) | + FIELD_PREP(NDE_RESVEC_MASK, resvec); + + writel(val, isp->base + nde_reg); +} + +static int ipu7_buttress_powerup(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + u32 val, exp_sts; + int ret = 0; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + exp_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + if (ctrl->subsys_id == IPU_IS) { + ret = isys_d2d_power(dev, true); + if (ret) + goto out_power; + isys_nde_control(dev, true); + } + + /* request clock resource ownership */ + val = readl(isp->base + BUTTRESS_REG_SLEEP_LEVEL_CFG); + val |= ctrl->ovrd_clk; + writel(val, isp->base + BUTTRESS_REG_SLEEP_LEVEL_CFG); + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SLEEP_LEVEL_STS, + val, (val & ctrl->own_clk_ack), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_warn(dev, "request clk ownership timeout. status 0x%x\n", + val); + + val = ctrl->ratio << ctrl->ratio_shift | ctrl->cdyn << ctrl->cdyn_shift; + + dev_dbg(dev, "set 0x%x to %s_WORKPOINT_REQ.\n", val, + ctrl->subsys_id == IPU_IS ? "IS" : "PS"); + writel(val, isp->base + ctrl->freq_ctl); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATUS, + val, ((val & ctrl->pwr_sts_mask) == exp_sts), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(dev, "%s power up timeout with status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); + goto out_power; + } + + dev_dbg(dev, "%s power up successfully. status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); + + /* release clock resource ownership */ + val = readl(isp->base + BUTTRESS_REG_SLEEP_LEVEL_CFG); + val &= ~ctrl->ovrd_clk; + writel(val, isp->base + BUTTRESS_REG_SLEEP_LEVEL_CFG); + +out_power: + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +static int ipu7_buttress_powerdown(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + u32 val, exp_sts; + int ret = 0; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + + exp_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + val = 0x8U << ctrl->ratio_shift; + + dev_dbg(dev, "set 0x%x to %s_WORKPOINT_REQ.\n", val, + ctrl->subsys_id == IPU_IS ? "IS" : "PS"); + writel(val, isp->base + ctrl->freq_ctl); + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATUS, + val, ((val & ctrl->pwr_sts_mask) == exp_sts), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(dev, "%s power down timeout with status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); + goto out_power; + } + + dev_dbg(dev, "%s power down successfully. status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); +out_power: + if (ctrl->subsys_id == IPU_IS && !ret) { + isys_d2d_power(dev, false); + isys_nde_control(dev, false); + } + + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +static int ipu8_buttress_powerup(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + u32 sleep_level_reg = BUTTRESS_REG_SLEEP_LEVEL_STS; + u32 val, exp_sts; + int ret = 0; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + exp_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; + if (ctrl->subsys_id == IPU_IS) { + ret = isys_d2d_power(dev, true); + if (ret) + goto out_power; + isys_nde_control(dev, true); + } + + /* request ps_pll when psys freq > 400Mhz */ + if (ctrl->subsys_id == IPU_PS && ctrl->ratio > 0x10) { + writel(1, isp->base + BUTTRESS_REG_PS_PLL_ENABLE); + ret = readl_poll_timeout(isp->base + sleep_level_reg, + val, (val & ctrl->own_clk_ack), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) + dev_warn(dev, "ps_pll req ack timeout. status 0x%x\n", + val); + } + + val = ctrl->ratio << ctrl->ratio_shift | ctrl->cdyn << ctrl->cdyn_shift; + dev_dbg(dev, "set 0x%x to %s_WORKPOINT_REQ.\n", val, + ctrl->subsys_id == IPU_IS ? "IS" : "PS"); + writel(val, isp->base + ctrl->freq_ctl); + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATUS, + val, ((val & ctrl->pwr_sts_mask) == exp_sts), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(dev, "%s power up timeout with status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); + goto out_power; + } + + dev_dbg(dev, "%s power up successfully. status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); +out_power: + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +static int ipu8_buttress_powerdown(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + u32 val, exp_sts; + int ret = 0; + + if (!ctrl) + return 0; + + mutex_lock(&isp->buttress.power_mutex); + exp_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; + + if (ctrl->subsys_id == IPU_PS) + val = 0x10U << ctrl->ratio_shift; + else + val = 0x8U << ctrl->ratio_shift; + + dev_dbg(dev, "set 0x%x to %s_WORKPOINT_REQ.\n", val, + ctrl->subsys_id == IPU_IS ? "IS" : "PS"); + writel(val, isp->base + ctrl->freq_ctl); + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATUS, + val, ((val & ctrl->pwr_sts_mask) == exp_sts), + 100, BUTTRESS_POWER_TIMEOUT_US); + if (ret) { + dev_err(dev, "%s power down timeout with status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); + goto out_power; + } + + dev_dbg(dev, "%s power down successfully. status: 0x%x\n", + ctrl->subsys_id == IPU_IS ? "IS" : "PS", val); +out_power: + if (ctrl->subsys_id == IPU_IS && !ret) { + isys_d2d_power(dev, false); + isys_nde_control(dev, false); + } + + if (ctrl->subsys_id == IPU_PS) { + val = readl(isp->base + BUTTRESS_REG_SLEEP_LEVEL_STS); + if (val & ctrl->own_clk_ack) + writel(0, isp->base + BUTTRESS_REG_PS_PLL_ENABLE); + } + mutex_unlock(&isp->buttress.power_mutex); + + return ret; +} + +int ipu_buttress_powerup(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + + if (is_ipu8(isp->hw_ver)) + return ipu8_buttress_powerup(dev, ctrl); + + return ipu7_buttress_powerup(dev, ctrl); +} + +int ipu_buttress_powerdown(struct device *dev, + const struct ipu_buttress_ctrl *ctrl) +{ + struct ipu7_device *isp = to_ipu7_bus_device(dev)->isp; + + if (is_ipu8(isp->hw_ver)) + return ipu8_buttress_powerdown(dev, ctrl); + + return ipu7_buttress_powerdown(dev, ctrl); +} + +bool ipu_buttress_get_secure_mode(struct ipu7_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + + return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; +} + +bool ipu_buttress_auth_done(struct ipu7_device *isp) +{ + u32 val; + + if (!isp->secure_mode) + return true; + + val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); + val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); + + return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_auth_done, "INTEL_IPU7"); + +int ipu_buttress_get_isys_freq(struct ipu7_device *isp, u32 *freq) +{ + u32 reg_val; + int ret; + + ret = pm_runtime_get_sync(&isp->isys->auxdev.dev); + if (ret < 0) { + pm_runtime_put(&isp->isys->auxdev.dev); + dev_err(&isp->pdev->dev, "Runtime PM failed (%d)\n", ret); + return ret; + } + + reg_val = readl(isp->base + BUTTRESS_REG_IS_WORKPOINT_REQ); + + pm_runtime_put(&isp->isys->auxdev.dev); + + if (is_ipu8(isp->hw_ver)) + *freq = (reg_val & BUTTRESS_IS_FREQ_CTL_RATIO_MASK) * 25; + else + *freq = (reg_val & BUTTRESS_IS_FREQ_CTL_RATIO_MASK) * 50 / 3; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_get_isys_freq, "INTEL_IPU7"); + +int ipu_buttress_get_psys_freq(struct ipu7_device *isp, u32 *freq) +{ + u32 reg_val; + int ret; + + ret = pm_runtime_get_sync(&isp->psys->auxdev.dev); + if (ret < 0) { + pm_runtime_put(&isp->psys->auxdev.dev); + dev_err(&isp->pdev->dev, "Runtime PM failed (%d)\n", ret); + return ret; + } + + reg_val = readl(isp->base + BUTTRESS_REG_PS_WORKPOINT_REQ); + + pm_runtime_put(&isp->psys->auxdev.dev); + + reg_val &= BUTTRESS_PS_FREQ_CTL_RATIO_MASK; + *freq = BUTTRESS_PS_FREQ_RATIO_STEP * reg_val; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_get_psys_freq, "INTEL_IPU7"); + +int ipu_buttress_reset_authentication(struct ipu7_device *isp) +{ + struct device *dev = &isp->pdev->dev; + int ret; + u32 val; + + if (!isp->secure_mode) { + dev_dbg(dev, "Skip auth for non-secure mode\n"); + return 0; + } + + writel(BUTTRESS_FW_RESET_CTL_START, isp->base + + BUTTRESS_REG_FW_RESET_CTL); + + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, + val & BUTTRESS_FW_RESET_CTL_DONE, 500, + BUTTRESS_CSE_FWRESET_TIMEOUT_US); + if (ret) { + dev_err(dev, "Time out while resetting authentication state\n"); + return ret; + } + + dev_dbg(dev, "FW reset for authentication done\n"); + writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); + /* leave some time for HW restore */ + usleep_range(800, 1000); + + return 0; +} + +int ipu_buttress_authenticate(struct ipu7_device *isp) +{ + struct ipu_buttress *b = &isp->buttress; + struct device *dev = &isp->pdev->dev; + u32 data, mask, done, fail; + int ret; + + if (!isp->secure_mode) { + dev_dbg(dev, "Skip auth for non-secure mode\n"); + return 0; + } + + mutex_lock(&b->auth_mutex); + + if (ipu_buttress_auth_done(isp)) { + ret = 0; + goto out_unlock; + } + + /* + * BUTTRESS_REG_FW_SOURCE_BASE needs to be set with FW CPD + * package address for secure mode. + */ + + writel(isp->cpd_fw->size, isp->base + BUTTRESS_REG_FW_SOURCE_SIZE); + writel(sg_dma_address(isp->psys->fw_sgt.sgl), + isp->base + BUTTRESS_REG_FW_SOURCE_BASE); + + /* + * Write boot_load into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(dev, "Sending BOOT_LOAD to CSE\n"); + ret = ipu_buttress_ipc_send(isp, BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); + if (ret) { + dev_err(dev, "CSE boot_load failed\n"); + goto out_unlock; + } + + mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; + done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; + fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(dev, "CSE boot_load timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(dev, "CSE auth failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + ret = readl_poll_timeout(isp->base + BOOTLOADER_STATUS_OFFSET, + data, data == BOOTLOADER_MAGIC_KEY, 500, + BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); + if (ret) { + dev_err(dev, "Unexpected magic number 0x%x\n", data); + goto out_unlock; + } + + /* + * Write authenticate_run into IU2CSEDATA0 + * Write sizeof(boot_load) | 0x2 << CLIENT_ID to + * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as + */ + dev_info(dev, "Sending AUTHENTICATE_RUN to CSE\n"); + ret = ipu_buttress_ipc_send(isp, BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, + 1, true, + BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); + if (ret) { + dev_err(dev, "CSE authenticate_run failed\n"); + goto out_unlock; + } + + done = BUTTRESS_SECURITY_CTL_AUTH_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, + ((data & mask) == done || + (data & mask) == fail), 500, + BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); + if (ret) { + dev_err(dev, "CSE authenticate timeout\n"); + goto out_unlock; + } + + if ((data & mask) == fail) { + dev_err(dev, "CSE boot_load failed\n"); + ret = -EINVAL; + goto out_unlock; + } + + dev_info(dev, "CSE authenticate_run done\n"); + +out_unlock: + mutex_unlock(&b->auth_mutex); + + return ret; +} + +static int ipu_buttress_send_tsc_request(struct ipu7_device *isp) +{ + u32 val, mask, done; + int ret; + + mask = BUTTRESS_PWR_STATUS_HH_STATUS_MASK; + + writel(BUTTRESS_TSC_CMD_START_TSC_SYNC, + isp->base + BUTTRESS_REG_TSC_CMD); + + val = readl(isp->base + BUTTRESS_REG_PWR_STATUS); + val = FIELD_GET(mask, val); + if (val == BUTTRESS_PWR_STATUS_HH_STATE_ERR) { + dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); + return -EINVAL; + } + + done = BUTTRESS_PWR_STATUS_HH_STATE_DONE; + ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATUS, val, + FIELD_GET(mask, val) == done, 500, + BUTTRESS_TSC_SYNC_TIMEOUT_US); + if (ret) + dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); + + return ret; +} + +int ipu_buttress_start_tsc_sync(struct ipu7_device *isp) +{ + void __iomem *base = isp->base; + unsigned int i; + u32 val; + + if (is_ipu8(isp->hw_ver)) { + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + val = readl(base + BUTTRESS_REG_PB_TIMESTAMP_VALID); + if (val == 1) + return 0; + usleep_range(40, 50); + } + + dev_err(&isp->pdev->dev, "PB HH sync failed (valid %u)\n", val); + return -ETIMEDOUT; + } + + if (is_ipu7p5(isp->hw_ver)) { + val = readl(base + BUTTRESS_REG_TSC_CTL); + val |= BUTTRESS_SEL_PB_TIMESTAMP; + writel(val, base + BUTTRESS_REG_TSC_CTL); + + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + val = readl(base + BUTTRESS_REG_PB_TIMESTAMP_VALID); + if (val == 1) + return 0; + usleep_range(40, 50); + } + + dev_err(&isp->pdev->dev, "PB HH sync failed (valid %u)\n", val); + + return -ETIMEDOUT; + } + + for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { + int ret; + + ret = ipu_buttress_send_tsc_request(isp); + if (ret != -ETIMEDOUT) + return ret; + + val = readl(base + BUTTRESS_REG_TSC_CTL); + val = val | BUTTRESS_TSW_WA_SOFT_RESET; + writel(val, base + BUTTRESS_REG_TSC_CTL); + val = val & (~BUTTRESS_TSW_WA_SOFT_RESET); + writel(val, base + BUTTRESS_REG_TSC_CTL); + } + + dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); + + return -ETIMEDOUT; +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_start_tsc_sync, "INTEL_IPU7"); + +void ipu_buttress_tsc_read(struct ipu7_device *isp, u64 *val) +{ + unsigned long flags; + u32 tsc_hi, tsc_lo; + + local_irq_save(flags); + if (is_ipu7(isp->hw_ver)) { + tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); + tsc_hi = readl(isp->base + BUTTRESS_REG_TSC_HI); + } else { + tsc_lo = readl(isp->base + BUTTRESS_REG_PB_TIMESTAMP_LO); + tsc_hi = readl(isp->base + BUTTRESS_REG_PB_TIMESTAMP_HI); + } + *val = (u64)tsc_hi << 32 | tsc_lo; + local_irq_restore(flags); +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_tsc_read, "INTEL_IPU7"); + +u64 ipu_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu7_device *isp) +{ + u64 ns = ticks * 10000; + + /* + * converting TSC tick count to ns is calculated by: + * Example (TSC clock frequency is 19.2MHz): + * ns = ticks * 1000 000 000 / 19.2Mhz + * = ticks * 1000 000 000 / 19200000Hz + * = ticks * 10000 / 192 ns + */ + return div_u64(ns, isp->buttress.ref_clk); +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_tsc_ticks_to_ns, "INTEL_IPU7"); + +/* trigger uc control to wakeup fw */ +void ipu_buttress_wakeup_is_uc(const struct ipu7_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_DRV_IS_UCX_CONTROL_STATUS); + val |= UCX_CTL_WAKEUP; + writel(val, isp->base + BUTTRESS_REG_DRV_IS_UCX_CONTROL_STATUS); +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_wakeup_is_uc, "INTEL_IPU7"); + +void ipu_buttress_wakeup_ps_uc(const struct ipu7_device *isp) +{ + u32 val; + + val = readl(isp->base + BUTTRESS_REG_DRV_PS_UCX_CONTROL_STATUS); + val |= UCX_CTL_WAKEUP; + writel(val, isp->base + BUTTRESS_REG_DRV_PS_UCX_CONTROL_STATUS); +} +EXPORT_SYMBOL_NS_GPL(ipu_buttress_wakeup_ps_uc, "INTEL_IPU7"); + +static const struct x86_cpu_id ipu_misc_cfg_exclusion[] = { + X86_MATCH_VFM_STEPS(INTEL_PANTHERLAKE_L, 0x1, 0x1, 0), + {}, +}; + +static void ipu_buttress_setup(struct ipu7_device *isp) +{ + struct device *dev = &isp->pdev->dev; + u32 val; + + /* program PB BAR */ +#define WRXREQOP_OVRD_VAL_MASK GENMASK(22, 19) + writel(0, isp->pb_base + GLOBAL_INTERRUPT_MASK); + val = readl(isp->pb_base + BAR2_MISC_CONFIG); + if (is_ipu7(isp->hw_ver) || x86_match_cpu(ipu_misc_cfg_exclusion)) + val |= 0x100U; + else + val |= FIELD_PREP(WRXREQOP_OVRD_VAL_MASK, 0xf) | + BIT(18) | 0x100U; + + writel(val, isp->pb_base + BAR2_MISC_CONFIG); + val = readl(isp->pb_base + BAR2_MISC_CONFIG); + + if (is_ipu8(isp->hw_ver)) { + writel(BIT(13), isp->pb_base + TLBID_HASH_ENABLE_63_32); + writel(BIT(9), isp->pb_base + TLBID_HASH_ENABLE_95_64); + dev_dbg(dev, "IPU8 TLBID_HASH %x %x\n", + readl(isp->pb_base + TLBID_HASH_ENABLE_63_32), + readl(isp->pb_base + TLBID_HASH_ENABLE_95_64)); + } else if (is_ipu7p5(isp->hw_ver)) { + writel(BIT(14), isp->pb_base + TLBID_HASH_ENABLE_63_32); + writel(BIT(9), isp->pb_base + TLBID_HASH_ENABLE_95_64); + dev_dbg(dev, "IPU7P5 TLBID_HASH %x %x\n", + readl(isp->pb_base + TLBID_HASH_ENABLE_63_32), + readl(isp->pb_base + TLBID_HASH_ENABLE_95_64)); + } else { + writel(BIT(22), isp->pb_base + TLBID_HASH_ENABLE_63_32); + writel(BIT(1), isp->pb_base + TLBID_HASH_ENABLE_127_96); + dev_dbg(dev, "TLBID_HASH %x %x\n", + readl(isp->pb_base + TLBID_HASH_ENABLE_63_32), + readl(isp->pb_base + TLBID_HASH_ENABLE_127_96)); + } + + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_IRQ_CLEAR); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_IRQ_MASK); + writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_IRQ_ENABLE); + /* LNL SW workaround for PS PD hang when PS sub-domain during PD */ + writel(PS_FSM_CG, isp->base + BUTTRESS_REG_CG_CTRL_BITS); +} + +void ipu_buttress_restore(struct ipu7_device *isp) +{ + struct ipu_buttress *b = &isp->buttress; + + ipu_buttress_setup(isp); + + writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_IDLE_WDT); +} + +int ipu_buttress_init(struct ipu7_device *isp) +{ + int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; + struct ipu_buttress *b = &isp->buttress; + struct device *dev = &isp->pdev->dev; + u32 val; + + mutex_init(&b->power_mutex); + mutex_init(&b->auth_mutex); + mutex_init(&b->cons_mutex); + mutex_init(&b->ipc_mutex); + init_completion(&b->cse.send_complete); + init_completion(&b->cse.recv_complete); + + b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; + b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; + b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; + b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; + b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; + b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; + b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; + b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; + + isp->secure_mode = ipu_buttress_get_secure_mode(isp); + val = readl(isp->base + BUTTRESS_REG_IPU_SKU); + dev_info(dev, "IPU%u SKU %u in %s mode mask 0x%x\n", val & 0xfU, + (val >> 4) & 0x7U, isp->secure_mode ? "secure" : "non-secure", + readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); + b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_IDLE_WDT); + b->ref_clk = 384; + + ipu_buttress_setup(isp); + + /* Retry couple of times in case of CSE initialization is delayed */ + do { + ret = ipu_buttress_ipc_reset(isp, &b->cse); + if (ret) { + dev_warn(dev, "IPC reset protocol failed, retrying\n"); + } else { + dev_dbg(dev, "IPC reset done\n"); + return 0; + } + } while (ipc_reset_retry--); + + dev_err(dev, "IPC reset protocol failed\n"); + + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); + + return ret; +} + +void ipu_buttress_exit(struct ipu7_device *isp) +{ + struct ipu_buttress *b = &isp->buttress; + + writel(0, isp->base + BUTTRESS_REG_IRQ_ENABLE); + mutex_destroy(&b->power_mutex); + mutex_destroy(&b->auth_mutex); + mutex_destroy(&b->cons_mutex); + mutex_destroy(&b->ipc_mutex); +} diff --git a/drivers/staging/media/ipu7/ipu7-buttress.h b/drivers/staging/media/ipu7/ipu7-buttress.h new file mode 100644 index 000000000000..8da7dd612575 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-buttress.h @@ -0,0 +1,77 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_BUTTRESS_H +#define IPU7_BUTTRESS_H + +#include +#include +#include +#include + +struct device; +struct ipu7_device; + +struct ipu_buttress_ctrl { + u32 subsys_id; + u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; + u32 ratio; + u32 ratio_shift; + u32 cdyn; + u32 cdyn_shift; + u32 ovrd_clk; + u32 own_clk_ack; +}; + +struct ipu_buttress_ipc { + struct completion send_complete; + struct completion recv_complete; + u32 nack; + u32 nack_mask; + u32 recv_data; + u32 csr_out; + u32 csr_in; + u32 db0_in; + u32 db0_out; + u32 data0_out; + u32 data0_in; +}; + +struct ipu_buttress { + struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; + struct ipu_buttress_ipc cse; + u32 psys_min_freq; + u32 wdt_cached_value; + u8 psys_force_ratio; + bool force_suspend; + u32 ref_clk; +}; + +int ipu_buttress_ipc_reset(struct ipu7_device *isp, + struct ipu_buttress_ipc *ipc); +int ipu_buttress_powerup(struct device *dev, + const struct ipu_buttress_ctrl *ctrl); +int ipu_buttress_powerdown(struct device *dev, + const struct ipu_buttress_ctrl *ctrl); +bool ipu_buttress_get_secure_mode(struct ipu7_device *isp); +int ipu_buttress_authenticate(struct ipu7_device *isp); +int ipu_buttress_reset_authentication(struct ipu7_device *isp); +bool ipu_buttress_auth_done(struct ipu7_device *isp); +int ipu_buttress_get_isys_freq(struct ipu7_device *isp, u32 *freq); +int ipu_buttress_get_psys_freq(struct ipu7_device *isp, u32 *freq); +int ipu_buttress_start_tsc_sync(struct ipu7_device *isp); +void ipu_buttress_tsc_read(struct ipu7_device *isp, u64 *val); +u64 ipu_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu7_device *isp); + +irqreturn_t ipu_buttress_isr(int irq, void *isp_ptr); +irqreturn_t ipu_buttress_isr_threaded(int irq, void *isp_ptr); +int ipu_buttress_init(struct ipu7_device *isp); +void ipu_buttress_exit(struct ipu7_device *isp); +void ipu_buttress_csi_port_config(struct ipu7_device *isp, + u32 legacy, u32 combo); +void ipu_buttress_restore(struct ipu7_device *isp); +void ipu_buttress_wakeup_is_uc(const struct ipu7_device *isp); +void ipu_buttress_wakeup_ps_uc(const struct ipu7_device *isp); +#endif /* IPU7_BUTTRESS_H */ diff --git a/drivers/staging/media/ipu7/ipu7-platform-regs.h b/drivers/staging/media/ipu7/ipu7-platform-regs.h new file mode 100644 index 000000000000..eeadc886a8cf --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-platform-regs.h @@ -0,0 +1,82 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2018 - 2025 Intel Corporation + */ + +#ifndef IPU7_PLATFORM_REGS_H +#define IPU7_PLATFORM_REGS_H + +#define IS_BASE 0x230000 +#define IS_UC_CTRL_BASE (IS_BASE + 0x0) + +#define PS_BASE 0x130000 +#define PS_UC_CTRL_BASE (PS_BASE + 0x0) + +/* + * bit 0: IRQ from FW, + * bit 1, 2 and 3: IRQ from HW + */ +#define TO_SW_IRQ_MASK 0xf +#define TO_SW_IRQ_FW BIT(0) + +#define FW_CODE_BASE 0x0 +#define FW_DATA_BASE 0x4 +#define PRINTF_EN_THROUGH_TRACE 0x3004 +#define PRINTF_EN_DIRECTLY_TO_DDR 0x3008 +#define PRINTF_DDR_BASE_ADDR 0x300c +#define PRINTF_DDR_SIZE 0x3010 +#define PRINTF_DDR_NEXT_ADDR 0x3014 +#define PRINTF_STATUS 0x3018 +#define PRINTF_AXI_CNTL 0x301c +#define PRINTF_MSG_LENGTH 0x3020 +#define TO_SW_IRQ_CNTL_EDGE 0x4000 +#define TO_SW_IRQ_CNTL_MASK_N 0x4004 +#define TO_SW_IRQ_CNTL_STATUS 0x4008 +#define TO_SW_IRQ_CNTL_CLEAR 0x400c +#define TO_SW_IRQ_CNTL_ENABLE 0x4010 +#define TO_SW_IRQ_CNTL_LEVEL_NOT_PULSE 0x4014 +#define ERR_IRQ_CNTL_EDGE 0x4018 +#define ERR_IRQ_CNTL_MASK_N 0x401c +#define ERR_IRQ_CNTL_STATUS 0x4020 +#define ERR_IRQ_CNTL_CLEAR 0x4024 +#define ERR_IRQ_CNTL_ENABLE 0x4028 +#define ERR_IRQ_CNTL_LEVEL_NOT_PULSE 0x402c +#define LOCAL_DMEM_BASE_ADDR 0x1300000 + +/* + * IS_UC_TO_SW irqs + * bit 0: IRQ from local FW + * bit 1~3: IRQ from HW + */ +#define IS_UC_TO_SW_IRQ_MASK 0xf + +#define IPU_ISYS_SPC_OFFSET 0x210000 +#define IPU7_PSYS_SPC_OFFSET 0x118000 +#define IPU_ISYS_DMEM_OFFSET 0x200000 +#define IPU_PSYS_DMEM_OFFSET 0x100000 + +#define IPU7_ISYS_CSI_PORT_NUM 4 + +/* IRQ-related registers in PSYS */ +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_EDGE 0x134000 +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_MASK 0x134004 +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_STATUS 0x134008 +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_CLEAR 0x13400c +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_ENABLE 0x134010 +#define IPU_REG_PSYS_TO_SW_IRQ_CNTL_LEVEL_NOT_PULSE 0x134014 +#define IRQ_FROM_LOCAL_FW BIT(0) + +/* + * psys subdomains power request regs + */ +enum ipu7_device_buttress_psys_domain_pos { + IPU_PSYS_SUBDOMAIN_LB = 0, + IPU_PSYS_SUBDOMAIN_BB = 1, +}; + +#define IPU7_PSYS_DOMAIN_POWER_MASK (BIT(IPU_PSYS_SUBDOMAIN_LB) | \ + BIT(IPU_PSYS_SUBDOMAIN_BB)) +#define IPU8_PSYS_DOMAIN_POWER_MASK BIT(IPU_PSYS_SUBDOMAIN_LB) +#define IPU_PSYS_DOMAIN_POWER_IN_PROGRESS BIT(31) + +#endif /* IPU7_PLATFORM_REGS_H */ diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c new file mode 100644 index 000000000000..b3c4789fa5c8 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7.c @@ -0,0 +1,2786 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "abi/ipu7_fw_common_abi.h" + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-buttress.h" +#include "ipu7-buttress-regs.h" +#include "ipu7-cpd.h" +#include "ipu7-dma.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-mmu.h" +#include "ipu7-platform-regs.h" + +#define IPU_PCI_BAR 0 +#define IPU_PCI_PBBAR 4 + +static const unsigned int ipu7_csi_offsets[] = { + IPU_CSI_PORT_A_ADDR_OFFSET, + IPU_CSI_PORT_B_ADDR_OFFSET, + IPU_CSI_PORT_C_ADDR_OFFSET, + IPU_CSI_PORT_D_ADDR_OFFSET, +}; + +static struct ipu_isys_internal_pdata ipu7p5_isys_ipdata = { + .csi2 = { + .gpreg = IS_IO_CSI2_GPREGS_BASE, + }, + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU7P5_IS_MMU_NUM, + .mmu_hw = { + { + .name = "IS_FW_RD", + .offset = IPU7P5_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20005101, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU7P5_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 1, 0, 0 + }, + .zlx_conf = { + 0x0, + }, + .uao_p_num = IPU7P5_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004c, + 0x0000004d, + 0x00000000, + }, + }, + { + .name = "IS_FW_WR", + .offset = IPU7P5_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7P5_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20005001, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU7P5_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, + }, + .zlx_conf = { + 0x0, + 0x00010101, + 0x00010101, + 0x0, + }, + .uao_p_num = IPU7P5_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004a, + 0x0000004b, + 0x00000000, + }, + }, + { + .name = "IS_DATA_WR_ISOC", + .offset = IPU7P5_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20004e01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .zlx_nr = IPU7P5_IS_ZLX_M0_NUM, + .zlx_axi_pool = { + 0x00000f10, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + }, + .uao_p_num = IPU7P5_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { + 0x00000041, + 0x00000042, + 0x00000043, + 0x00000044, + 0x00000041, + 0x00000042, + 0x00000043, + 0x00000044, + 0x00000041, + 0x00000042, + 0x00000043, + 0x00000044, + 0x00000041, + 0x00000042, + 0x00000043, + 0x00000044, + }, + }, + { + .name = "IS_DATA_WR_SNOOP", + .offset = IPU7P5_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7P5_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7P5_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20004f01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7P5_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .zlx_nr = IPU7P5_IS_ZLX_M1_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + }, + .uao_p_num = IPU7P5_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { + 0x00000045, + 0x00000046, + 0x00000047, + 0x00000048, + 0x00000045, + 0x00000046, + 0x00000047, + 0x00000048, + 0x00000045, + 0x00000046, + 0x00000047, + 0x00000048, + 0x00000045, + 0x00000046, + 0x00000047, + 0x00000048, + }, + }, + }, + .cdc_fifos = 3, + .cdc_fifo_threshold = {6, 8, 2}, + .dmem_offset = IPU_ISYS_DMEM_OFFSET, + .spc_offset = IPU_ISYS_SPC_OFFSET, + }, + .isys_dma_overshoot = IPU_ISYS_OVERALLOC_MIN, +}; + +static struct ipu_psys_internal_pdata ipu7p5_psys_ipdata = { + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU7P5_PS_MMU_NUM, + .mmu_hw = { + { + .name = "PS_FW_RD", + .offset = IPU7P5_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004001, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000d, + 0x0000000f, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000016, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001a, + 0x0000001a, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .zlx_nr = IPU7P5_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 1, 0, 0, 1, 1, 0, 0, + 0, 1, 1, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00000000, + 0x00010101, + 0x00000000, + 0x00000000, + 0x00010101, + 0x00010101, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00010101, + 0x00010101, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + .uao_p_num = IPU7P5_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { + 0x0000002e, + 0x00000035, + 0x00000036, + 0x00000031, + 0x00000037, + 0x00000038, + 0x00000039, + 0x00000032, + 0x00000033, + 0x0000003a, + 0x0000003b, + 0x0000003c, + 0x00000034, + 0x0, + 0x0, + 0x0, + }, + }, + { + .name = "PS_FW_WR", + .offset = IPU7P5_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20003e01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000010, + 0x00000010, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + }, + .zlx_nr = IPU7P5_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00000000, + 0x00010101, + 0x00010101, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + .uao_p_num = IPU7P5_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { + 0x0000002e, + 0x0000002f, + 0x00000030, + 0x00000031, + 0x00000032, + 0x00000033, + 0x00000034, + 0x0, + 0x0, + 0x0, + }, + }, + { + .name = "PS_DATA_RD", + .offset = IPU7P5_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20003f01, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000b, + 0x0000000d, + 0x0000000f, + 0x00000013, + 0x00000017, + 0x00000019, + 0x0000001b, + 0x0000001d, + 0x0000001f, + 0x0000002b, + 0x00000033, + 0x0000003f, + 0x00000047, + 0x00000049, + 0x0000004b, + 0x0000004c, + 0x0000004d, + 0x0000004e, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + 0x00000020, + 0x00000022, + 0x00000024, + 0x00000026, + 0x00000028, + 0x0000002a, + }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00030303, + 0x00010101, + 0x00010101, + 0x00030202, + 0x00010101, + 0x00010101, + 0x00030303, + 0x00030303, + 0x00010101, + 0x00030800, + 0x00030500, + 0x00020101, + 0x00042000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00020400, + 0x00010101, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + .uao_p_num = IPU7P5_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { + 0x0000001c, + 0x0000001d, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + 0x00000022, + 0x00000023, + 0x00000024, + 0x00000025, + 0x00000026, + 0x00000027, + 0x00000028, + 0x00000029, + 0x0000002a, + 0x0000002b, + 0x0000002c, + 0x0000002d, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + }, + { + .name = "PS_DATA_WR", + .offset = IPU7P5_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7P5_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7P5_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20003d01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7P5_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000006, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + 0x00000020, + 0x00000022, + 0x00000024, + 0x00000028, + 0x0000002a, + 0x00000036, + 0x0000003e, + 0x00000040, + 0x00000042, + 0x0000004e, + 0x00000056, + 0x0000005c, + 0x00000068, + 0x00000070, + 0x00000076, + 0x00000077, + 0x00000078, + 0x00000079, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000006, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + 0x00000020, + 0x00000022, + 0x00000024, + 0x00000028, + 0x0000002a, + 0x00000036, + 0x0000003e, + 0x00000040, + 0x00000042, + 0x0000004e, + 0x00000056, + 0x0000005c, + 0x00000068, + 0x00000070, + 0x00000076, + 0x00000077, + 0x00000078, + 0x00000079, + }, + .zlx_nr = IPU7P5_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { + 0x00000f50, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00010102, + 0x00030103, + 0x00030103, + 0x00010101, + 0x00010101, + 0x00030101, + 0x00010101, + 0x38010101, + 0x00000000, + 0x00000000, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x00030303, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + .uao_p_num = IPU7P5_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { + 0x00000000, + 0x00000001, + 0x00000002, + 0x00000003, + 0x00000004, + 0x00000005, + 0x00000006, + 0x00000007, + 0x00000008, + 0x00000009, + 0x0000000a, + 0x0000000b, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000015, + 0x00000016, + 0x00000017, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001b, + 0x00000000, + 0x00000000, + 0x00000000, + 0x00000000, + }, + }, + }, + .dmem_offset = IPU_PSYS_DMEM_OFFSET, + }, +}; + +static struct ipu_isys_internal_pdata ipu7_isys_ipdata = { + .csi2 = { + .gpreg = IS_IO_CSI2_GPREGS_BASE, + }, + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU7_IS_MMU_NUM, + .mmu_hw = { + { + .name = "IS_FW_RD", + .offset = IPU7_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20006701, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU7_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 0, 0, 0 + }, + .zlx_conf = { + 0x0, 0x0, 0x0, 0x0, + }, + .uao_p_num = IPU7_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { + 0x00000061, + 0x00000064, + 0x00000065, + }, + }, + { + .name = "IS_FW_WR", + .offset = IPU7_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU7_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20006801, + .refill = 0x00002524, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU7_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, + }, + .zlx_conf = { + 0x0, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { + 0x00000061, + 0x00000062, + 0x00000063, + }, + }, + { + .name = "IS_DATA_WR_ISOC", + .offset = IPU7_IS_MMU_M0_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M0_OFFSET, + .uao_offset = IPU7_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20006601, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x3, 0x6, 0x8, 0xa, 0xc, 0xe, 0x10, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, 0xa, 0xc, 0xe, + }, + .zlx_nr = IPU7_IS_ZLX_M0_NUM, + .zlx_axi_pool = { + 0x00000f10, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004a, + 0x0000004b, + 0x0000004c, + 0x0000004d, + 0x0000004e, + 0x0000004f, + 0x00000050, + }, + }, + { + .name = "IS_DATA_WR_SNOOP", + .offset = IPU7_IS_MMU_M1_OFFSET, + .zlx_offset = IPU7_IS_ZLX_M1_OFFSET, + .uao_offset = IPU7_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20006901, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU7_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU7_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x3, 0x6, 0x9, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, + 0x22, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, + }, + .zlx_nr = IPU7_IS_ZLX_M1_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010103, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { + 0x00000051, + 0x00000052, + 0x00000053, + 0x00000054, + 0x00000055, + 0x00000056, + 0x00000057, + 0x00000058, + 0x00000059, + 0x0000005a, + 0x0000005b, + 0x0000005c, + 0x0000005d, + 0x0000005e, + 0x0000005f, + 0x00000060, + }, + }, + }, + .cdc_fifos = 3, + .cdc_fifo_threshold = {6, 8, 2}, + .dmem_offset = IPU_ISYS_DMEM_OFFSET, + .spc_offset = IPU_ISYS_SPC_OFFSET, + }, + .isys_dma_overshoot = IPU_ISYS_OVERALLOC_MIN, +}; + +static struct ipu_psys_internal_pdata ipu7_psys_ipdata = { + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU7_PS_MMU_NUM, + .mmu_hw = { + { + .name = "PS_FW_RD", + .offset = IPU7_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20004801, + .refill = 0x00002726, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xf, 0x11, 0x12, 0x13, 0x14, + 0x16, 0x18, 0x19, 0x1a, 0x1a, + 0x1a, 0x1a, 0x1a, 0x1a, 0x1a, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + }, + .zlx_nr = IPU7_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x0, + }, + .uao_p_num = IPU7_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { + 0x00000036, + 0x0000003d, + 0x0000003e, + 0x00000039, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x0000003a, + 0x0000003b, + 0x00000042, + 0x00000043, + 0x00000044, + 0x0000003c, + }, + }, + { + .name = "PS_FW_WR", + .offset = IPU7_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20004601, + .refill = 0x00002322, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0, 0x8, 0xa, 0xc, 0xd, + 0xe, 0xf, 0x10, 0x10, 0x10, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + }, + .zlx_nr = IPU7_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, 0, 0, 0, 0, + 0, 0, + }, + .zlx_conf = { + 0x0, + 0x00010101, + 0x00010101, + }, + .uao_p_num = IPU7_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { + 0x00000036, + 0x00000037, + 0x00000038, + 0x00000039, + 0x0000003a, + 0x0000003b, + 0x0000003c, + }, + }, + { + .name = "PS_DATA_RD", + .offset = IPU7_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20004701, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x4, 0x6, 0x8, 0xb, + 0xd, 0xf, 0x11, 0x13, 0x15, + 0x17, 0x23, 0x2b, 0x37, 0x3f, + 0x41, 0x43, 0x44, 0x45, 0x46, + 0x47, 0x48, 0x49, 0x4a, 0x4b, + 0x4c, 0x4d, 0x4e, 0x4f, 0x50, + 0x51, 0x52, 0x53, 0x55, 0x57, + 0x59, 0x5b, 0x5d, 0x5f, 0x61, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x00030303, + 0x00010101, + 0x00010101, + 0x00030202, + 0x00010101, + 0x00010101, + 0x00010101, + 0x00030800, + 0x00030500, + 0x00020101, + 0x00042000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00020400, + 0x00010101, + }, + .uao_p_num = IPU7_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { + 0x00000022, + 0x00000023, + 0x00000024, + 0x00000025, + 0x00000026, + 0x00000027, + 0x00000028, + 0x00000029, + 0x0000002a, + 0x0000002b, + 0x0000002c, + 0x0000002d, + 0x0000002e, + 0x0000002f, + 0x00000030, + 0x00000031, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + 0x00000032, + 0x00000033, + 0x00000034, + 0x00000035, + }, + }, + { + .name = "PS_DATA_WR", + .offset = IPU7_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU7_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU7_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20004501, + .refill = 0x00002120, + .collapse_en_bitmap = 0x0, + .l1_block = IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU7_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x2, 0x6, 0xa, 0xc, + 0xe, 0x10, 0x12, 0x14, 0x16, + 0x18, 0x1a, 0x1c, 0x1e, 0x20, + 0x22, 0x24, 0x26, 0x32, 0x3a, + 0x3c, 0x3e, 0x4a, 0x52, 0x58, + 0x64, 0x6c, 0x72, 0x7e, 0x86, + 0x8c, 0x8d, 0x8e, 0x8f, 0x90, + 0x91, 0x92, 0x94, 0x96, 0x98, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, 0x6, 0x8, + 0xa, 0xc, 0xe, 0x10, 0x12, + 0x14, 0x16, 0x18, 0x1a, 0x1c, + 0x1e, 0x20, 0x22, 0x24, 0x26, + 0x28, 0x2a, 0x2c, 0x2e, 0x30, + 0x32, 0x34, 0x36, 0x38, 0x3a, + 0x3c, 0x3e, 0x40, 0x42, 0x44, + 0x46, 0x48, 0x4a, 0x4c, 0x4e, + }, + .zlx_nr = IPU7_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { + 0x00000f50, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 0, 0, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, + }, + .zlx_conf = { + 0x00010102, + 0x00030103, + 0x00030103, + 0x00010101, + 0x00010101, + 0x00030101, + 0x00010101, + 0x38010101, + 0x0, + 0x0, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x38010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00010101, + 0x00010101, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x00042000, + 0x00031000, + 0x00031000, + 0x0, + 0x0, + }, + .uao_p_num = IPU7_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { + 0x00000000, + 0x00000001, + 0x00000002, + 0x00000003, + 0x00000004, + 0x00000005, + 0x00000006, + 0x00000007, + 0x00000008, + 0x00000009, + 0x0000000a, + 0x0000000b, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000015, + 0x00000016, + 0x00000017, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001b, + 0x0000001c, + 0x0000001d, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + }, + }, + }, + .dmem_offset = IPU_PSYS_DMEM_OFFSET, + }, +}; + +static struct ipu_isys_internal_pdata ipu8_isys_ipdata = { + .csi2 = { + .gpreg = IPU8_IS_IO_CSI2_GPREGS_BASE, + }, + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU8_IS_MMU_NUM, + .mmu_hw = { + { + .name = "IS_FW_RD", + .offset = IPU8_IS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU8_IS_ZLX_UC_RD_OFFSET, + .uao_offset = IPU8_IS_UAO_UC_RD_OFFSET, + .info_bits = 0x20005101, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_IS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU8_IS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_IS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU8_IS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU8_IS_ZLX_UC_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 1, 0, 0 + }, + .zlx_conf = { + 0, 2, 0, 0 + }, + .uao_p_num = IPU8_IS_UAO_UC_RD_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004c, + 0x0000004d, + 0x00000000, + }, + }, + { + .name = "IS_FW_WR", + .offset = IPU8_IS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU8_IS_ZLX_UC_WR_OFFSET, + .uao_offset = IPU8_IS_UAO_UC_WR_OFFSET, + .info_bits = 0x20005001, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_IS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU8_IS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_IS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU8_IS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0x0, 0x8, 0xa, + }, + .l2_block_sz = { + 0x0, 0x2, 0x4, + }, + .zlx_nr = IPU8_IS_ZLX_UC_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, + }, + .zlx_conf = { + 0x0, + 0x2, + 0x2, + 0x0, + }, + .uao_p_num = IPU8_IS_UAO_UC_WR_PLANENUM, + .uao_p2tlb = { + 0x00000049, + 0x0000004a, + 0x0000004b, + 0x00000000, + }, + }, + { + .name = "IS_DATA_WR_ISOC", + .offset = IPU8_IS_MMU_M0_OFFSET, + .zlx_offset = IPU8_IS_ZLX_M0_OFFSET, + .uao_offset = IPU8_IS_UAO_M0_WR_OFFSET, + .info_bits = 0x20004e01, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_IS_MMU_M0_L1_BLOCKNR_REG, + .l2_block = IPU8_IS_MMU_M0_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_IS_MMU_M0_STREAM_NUM, + .nr_l2streams = IPU8_IS_MMU_M0_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .zlx_nr = IPU8_IS_ZLX_M0_NUM, + .zlx_axi_pool = { + 0x00000f10, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + }, + .uao_p_num = IPU8_IS_UAO_M0_WR_PLANENUM, + .uao_p2tlb = { + 0x0000003b, + 0x0000003c, + 0x0000003d, + 0x0000003e, + 0x0000003b, + 0x0000003c, + 0x0000003d, + 0x0000003e, + 0x0000003b, + 0x0000003c, + 0x0000003d, + 0x0000003e, + 0x0000003b, + 0x0000003c, + 0x0000003d, + 0x0000003e, + }, + }, + { + .name = "IS_DATA_WR_SNOOP", + .offset = IPU8_IS_MMU_M1_OFFSET, + .zlx_offset = IPU8_IS_ZLX_M1_OFFSET, + .uao_offset = IPU8_IS_UAO_M1_WR_OFFSET, + .info_bits = 0x20004f01, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_IS_MMU_M1_L1_BLOCKNR_REG, + .l2_block = IPU8_IS_MMU_M1_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_IS_MMU_M1_STREAM_NUM, + .nr_l2streams = IPU8_IS_MMU_M1_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + }, + .zlx_nr = IPU8_IS_ZLX_M1_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + }, + .uao_p_num = IPU8_IS_UAO_M1_WR_PLANENUM, + .uao_p2tlb = { + 0x0000003f, + 0x00000040, + 0x00000041, + 0x00000042, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x00000042, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x00000042, + 0x0000003f, + 0x00000040, + 0x00000041, + 0x00000042, + }, + }, + { + .name = "IS_UPIPE", + .offset = IPU8_IS_MMU_UPIPE_OFFSET, + .zlx_offset = IPU8_IS_ZLX_UPIPE_OFFSET, + .uao_offset = IPU8_IS_UAO_UPIPE_OFFSET, + .info_bits = 0x20005201, + .refill = 0x00002928, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_IS_MMU_UPIPE_L1_BLOCKNR_REG, + .l2_block = IPU8_IS_MMU_UPIPE_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_IS_MMU_UPIPE_STREAM_NUM, + .nr_l2streams = IPU8_IS_MMU_UPIPE_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + }, + .zlx_nr = IPU8_IS_ZLX_UPIPE_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, + }, + .zlx_conf = { + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + 0x3, + }, + .uao_p_num = IPU8_IS_UAO_UPIPE_PLANENUM, + .uao_p2tlb = { + 0x00000043, + 0x00000044, + 0x00000045, + 0x00000046, + 0x00000047, + 0x00000048, + }, + }, + }, + .cdc_fifos = 3, + .cdc_fifo_threshold = {6, 8, 2}, + .dmem_offset = IPU_ISYS_DMEM_OFFSET, + .spc_offset = IPU_ISYS_SPC_OFFSET, + }, + .isys_dma_overshoot = IPU_ISYS_OVERALLOC_MIN, +}; + +static struct ipu_psys_internal_pdata ipu8_psys_ipdata = { + .hw_variant = { + .offset = IPU_UNIFIED_OFFSET, + .nr_mmus = IPU8_PS_MMU_NUM, + .mmu_hw = { + { + .name = "PS_FW_RD", + .offset = IPU8_PS_MMU_FW_RD_OFFSET, + .zlx_offset = IPU8_PS_ZLX_FW_RD_OFFSET, + .uao_offset = IPU8_PS_UAO_FW_RD_OFFSET, + .info_bits = 0x20003a01, + .refill = 0x00002726, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_PS_MMU_FW_RD_L1_BLOCKNR_REG, + .l2_block = IPU8_PS_MMU_FW_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_PS_MMU_FW_RD_STREAM_NUM, + .nr_l2streams = IPU8_PS_MMU_FW_RD_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000008, + 0x0000000a, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x00000018, + 0x00000018, + 0x00000018, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + }, + .zlx_nr = IPU8_PS_ZLX_FW_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 0, 1, 0, 0, 1, 1, 0, 0, + 0, 0, 0, 0, + }, + .zlx_conf = { + 0x0, + 0x2, + 0x0, + 0x0, + 0x2, + 0x2, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + 0x0, + }, + .uao_p_num = IPU8_PS_UAO_FW_RD_PLANENUM, + .uao_p2tlb = { + 0x0000002d, + 0x00000032, + 0x00000033, + 0x00000030, + 0x00000034, + 0x00000035, + 0x00000036, + 0x00000031, + 0x0, + 0x0, + 0x0, + 0x0, + }, + }, + { + .name = "PS_FW_WR", + .offset = IPU8_PS_MMU_FW_WR_OFFSET, + .zlx_offset = IPU8_PS_ZLX_FW_WR_OFFSET, + .uao_offset = IPU8_PS_UAO_FW_WR_OFFSET, + .info_bits = 0x20003901, + .refill = 0x00002524, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_PS_MMU_FW_WR_L1_BLOCKNR_REG, + .l2_block = IPU8_PS_MMU_FW_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_PS_MMU_FW_WR_STREAM_NUM, + .nr_l2streams = IPU8_PS_MMU_FW_WR_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000010, + 0x00000010, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + }, + .zlx_nr = IPU8_PS_ZLX_FW_WR_NUM, + .zlx_axi_pool = { + 0x00000f20, + }, + .zlx_en = { + 0, 1, 1, 0, 0, 0, 0, 0, + }, + .zlx_conf = { + 0x0, 0x2, 0x2, 0x0, + 0x0, 0x0, 0x0, 0x0, + }, + .uao_p_num = IPU8_PS_UAO_FW_WR_PLANENUM, + .uao_p2tlb = { + 0x0000002d, + 0x0000002e, + 0x0000002f, + 0x00000030, + 0x00000031, + 0x0, + 0x0, + 0x0, + }, + }, + { + .name = "PS_DATA_RD", + .offset = IPU8_PS_MMU_SRT_RD_OFFSET, + .zlx_offset = IPU8_PS_ZLX_DATA_RD_OFFSET, + .uao_offset = IPU8_PS_UAO_SRT_RD_OFFSET, + .info_bits = 0x20003801, + .refill = 0x00002322, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_PS_MMU_SRT_RD_L1_BLOCKNR_REG, + .l2_block = IPU8_PS_MMU_SRT_RD_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_PS_MMU_SRT_RD_STREAM_NUM, + .nr_l2streams = IPU8_PS_MMU_SRT_RD_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000014, + 0x00000018, + 0x0000001c, + 0x0000001e, + 0x00000022, + 0x00000024, + 0x00000026, + 0x00000028, + 0x0000002a, + 0x0000002c, + 0x0000002e, + 0x00000030, + 0x00000032, + 0x00000036, + 0x0000003a, + 0x0000003c, + 0x0000003c, + 0x0000003c, + 0x0000003c, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + 0x00000020, + 0x00000022, + 0x00000024, + 0x00000026, + 0x00000028, + 0x0000002a, + 0x0000002c, + 0x0000002e, + 0x00000030, + 0x00000032, + }, + .zlx_nr = IPU8_PS_ZLX_DATA_RD_NUM, + .zlx_axi_pool = { + 0x00000f30, + }, + .zlx_en = { + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 0, 0, + 0, 0, + }, + .zlx_conf = { + 0x6, 0x3, 0x3, 0x6, + 0x2, 0x2, 0x6, 0x6, + 0x6, 0x3, 0x6, 0x3, + 0x3, 0x2, 0x2, 0x2, + 0x2, 0x2, 0x2, 0x6, + 0x6, 0x3, 0x0, 0x0, + 0x0, 0x0, + }, + .uao_p_num = IPU8_PS_UAO_SRT_RD_PLANENUM, + .uao_p2tlb = { + 0x00000017, + 0x00000018, + 0x00000019, + 0x0000001a, + 0x0000001b, + 0x0000001c, + 0x0000001d, + 0x0000001e, + 0x0000001f, + 0x00000020, + 0x00000021, + 0x00000022, + 0x00000023, + 0x00000024, + 0x00000025, + 0x00000026, + 0x00000027, + 0x00000028, + 0x00000029, + 0x0000002a, + 0x0000002b, + 0x0000002c, + 0x0, + 0x0, + 0x0, + 0x0, + }, + }, + { + .name = "PS_DATA_WR", + .offset = IPU8_PS_MMU_SRT_WR_OFFSET, + .zlx_offset = IPU8_PS_ZLX_DATA_WR_OFFSET, + .uao_offset = IPU8_PS_UAO_SRT_WR_OFFSET, + .info_bits = 0x20003701, + .refill = 0x00002120, + .collapse_en_bitmap = 0x1, + .at_sp_arb_cfg = 0x1, + .l1_block = IPU8_PS_MMU_SRT_WR_L1_BLOCKNR_REG, + .l2_block = IPU8_PS_MMU_SRT_WR_L2_BLOCKNR_REG, + .nr_l1streams = IPU8_PS_MMU_SRT_WR_STREAM_NUM, + .nr_l2streams = IPU8_PS_MMU_SRT_WR_STREAM_NUM, + .l1_block_sz = { + 0x00000000, + 0x00000002, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001c, + 0x0000001e, + 0x00000022, + 0x00000024, + 0x00000028, + 0x0000002a, + 0x0000002e, + 0x00000030, + 0x00000032, + 0x00000036, + 0x00000038, + 0x0000003a, + 0x0000003a, + 0x0000003a, + }, + .l2_block_sz = { + 0x00000000, + 0x00000002, + 0x00000004, + 0x00000006, + 0x00000008, + 0x0000000a, + 0x0000000c, + 0x0000000e, + 0x00000010, + 0x00000012, + 0x00000014, + 0x00000016, + 0x00000018, + 0x0000001a, + 0x0000001c, + 0x0000001e, + 0x00000020, + 0x00000022, + 0x00000024, + 0x00000026, + 0x00000028, + 0x0000002a, + 0x0000002c, + 0x0000002e, + 0x00000030, + 0x00000032, + }, + .zlx_nr = IPU8_PS_ZLX_DATA_WR_NUM, + .zlx_axi_pool = { + 0x00000f50, + }, + .zlx_en = { + 1, 1, 1, 0, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 0, + 0, 0, + }, + .zlx_conf = { + 0x3, + 0x6, + 0x38000002, + 0x38000000, + 0x3, + 0x38000002, + 0x38000002, + 0x38000002, + 0x38000002, + 0x38000002, + 0x38000002, + 0x6, + 0x3, + 0x6, + 0x3, + 0x6, + 0x3, + 0x6, + 0x3, + 0x3, + 0x6, + 0x3, + 0x3, + 0x0, + 0x0, + 0x0, + }, + .uao_p_num = IPU8_PS_UAO_SRT_WR_PLANENUM, + .uao_p2tlb = { + 0x00000000, + 0x00000001, + 0x00000002, + 0x00000003, + 0x00000004, + 0x00000005, + 0x00000006, + 0x00000007, + 0x00000008, + 0x00000009, + 0x0000000a, + 0x0000000b, + 0x0000000c, + 0x0000000d, + 0x0000000e, + 0x0000000f, + 0x00000010, + 0x00000011, + 0x00000012, + 0x00000013, + 0x00000014, + 0x00000015, + 0x00000016, + 0x00000000, + 0x00000000, + 0x00000000, + }, + }, + }, + .dmem_offset = IPU_PSYS_DMEM_OFFSET, + }, +}; + +static const struct ipu_buttress_ctrl ipu7_isys_buttress_ctrl = { + .subsys_id = IPU_IS, + .ratio = IPU7_IS_FREQ_CTL_DEFAULT_RATIO, + .ratio_shift = IPU_FREQ_CTL_RATIO_SHIFT, + .cdyn = IPU_FREQ_CTL_CDYN, + .cdyn_shift = IPU_FREQ_CTL_CDYN_SHIFT, + .freq_ctl = BUTTRESS_REG_IS_WORKPOINT_REQ, + .pwr_sts_shift = IPU_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, + .pwr_sts_mask = IPU_BUTTRESS_PWR_STATE_IS_PWR_MASK, + .pwr_sts_on = IPU_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU_BUTTRESS_PWR_STATE_DN_DONE, + .ovrd_clk = BUTTRESS_OVERRIDE_IS_CLK, + .own_clk_ack = BUTTRESS_OWN_ACK_IS_CLK, +}; + +static const struct ipu_buttress_ctrl ipu7_psys_buttress_ctrl = { + .subsys_id = IPU_PS, + .ratio = IPU7_PS_FREQ_CTL_DEFAULT_RATIO, + .ratio_shift = IPU_FREQ_CTL_RATIO_SHIFT, + .cdyn = IPU_FREQ_CTL_CDYN, + .cdyn_shift = IPU_FREQ_CTL_CDYN_SHIFT, + .freq_ctl = BUTTRESS_REG_PS_WORKPOINT_REQ, + .pwr_sts_shift = IPU_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, + .pwr_sts_mask = IPU_BUTTRESS_PWR_STATE_PS_PWR_MASK, + .pwr_sts_on = IPU_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU_BUTTRESS_PWR_STATE_DN_DONE, + .ovrd_clk = BUTTRESS_OVERRIDE_PS_CLK, + .own_clk_ack = BUTTRESS_OWN_ACK_PS_CLK, +}; + +static const struct ipu_buttress_ctrl ipu8_isys_buttress_ctrl = { + .subsys_id = IPU_IS, + .ratio = IPU8_IS_FREQ_CTL_DEFAULT_RATIO, + .ratio_shift = IPU_FREQ_CTL_RATIO_SHIFT, + .cdyn = IPU_FREQ_CTL_CDYN, + .cdyn_shift = IPU_FREQ_CTL_CDYN_SHIFT, + .freq_ctl = BUTTRESS_REG_IS_WORKPOINT_REQ, + .pwr_sts_shift = IPU_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, + .pwr_sts_mask = IPU_BUTTRESS_PWR_STATE_IS_PWR_MASK, + .pwr_sts_on = IPU_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU_BUTTRESS_PWR_STATE_DN_DONE, +}; + +static const struct ipu_buttress_ctrl ipu8_psys_buttress_ctrl = { + .subsys_id = IPU_PS, + .ratio = IPU8_PS_FREQ_CTL_DEFAULT_RATIO, + .ratio_shift = IPU_FREQ_CTL_RATIO_SHIFT, + .cdyn = IPU_FREQ_CTL_CDYN, + .cdyn_shift = IPU_FREQ_CTL_CDYN_SHIFT, + .freq_ctl = BUTTRESS_REG_PS_WORKPOINT_REQ, + .pwr_sts_shift = IPU_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, + .pwr_sts_mask = IPU_BUTTRESS_PWR_STATE_PS_PWR_MASK, + .pwr_sts_on = IPU_BUTTRESS_PWR_STATE_UP_DONE, + .pwr_sts_off = IPU_BUTTRESS_PWR_STATE_DN_DONE, + .own_clk_ack = BUTTRESS_OWN_ACK_PS_PLL, +}; + +void ipu_internal_pdata_init(struct ipu_isys_internal_pdata *isys_ipdata, + struct ipu_psys_internal_pdata *psys_ipdata) +{ + isys_ipdata->csi2.nports = ARRAY_SIZE(ipu7_csi_offsets); + isys_ipdata->csi2.offsets = ipu7_csi_offsets; + isys_ipdata->num_parallel_streams = IPU7_ISYS_NUM_STREAMS; + psys_ipdata->hw_variant.spc_offset = IPU7_PSYS_SPC_OFFSET; +} + +static int ipu7_isys_check_fwnode_graph(struct fwnode_handle *fwnode) +{ + struct fwnode_handle *endpoint; + + if (IS_ERR_OR_NULL(fwnode)) + return -EINVAL; + + endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); + if (endpoint) { + fwnode_handle_put(endpoint); + return 0; + } + + return ipu7_isys_check_fwnode_graph(fwnode->secondary); +} + +static struct ipu7_bus_device * +ipu7_isys_init(struct pci_dev *pdev, struct device *parent, + const struct ipu_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu_isys_internal_pdata *ipdata, + unsigned int nr) +{ + struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); + struct ipu7_bus_device *isys_adev; + struct device *dev = &pdev->dev; + struct ipu7_isys_pdata *pdata; + int ret; + + ret = ipu7_isys_check_fwnode_graph(fwnode); + if (ret) { + if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { + dev_err(dev, + "fwnode graph has no endpoints connection\n"); + return ERR_PTR(-EINVAL); + } + + ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb); + if (ret) { + dev_err_probe(dev, ret, "IPU bridge init failed\n"); + return ERR_PTR(ret); + } + } + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + pdata->ipdata = ipdata; + + isys_adev = ipu7_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU_ISYS_NAME); + if (IS_ERR(isys_adev)) { + dev_err_probe(dev, PTR_ERR(isys_adev), + "ipu7_bus_initialize_device isys failed\n"); + kfree(pdata); + return ERR_CAST(isys_adev); + } + + isys_adev->mmu = ipu7_mmu_init(dev, base, ISYS_MMID, + &ipdata->hw_variant); + if (IS_ERR(isys_adev->mmu)) { + dev_err_probe(dev, PTR_ERR(isys_adev->mmu), + "ipu7_mmu_init(isys_adev->mmu) failed\n"); + put_device(&isys_adev->auxdev.dev); + kfree(pdata); + return ERR_CAST(isys_adev->mmu); + } + + isys_adev->mmu->dev = &isys_adev->auxdev.dev; + isys_adev->subsys = IPU_IS; + + ret = ipu7_bus_add_device(isys_adev); + if (ret) { + kfree(pdata); + return ERR_PTR(ret); + } + + return isys_adev; +} + +static struct ipu7_bus_device * +ipu7_psys_init(struct pci_dev *pdev, struct device *parent, + const struct ipu_buttress_ctrl *ctrl, void __iomem *base, + const struct ipu_psys_internal_pdata *ipdata, unsigned int nr) +{ + struct ipu7_bus_device *psys_adev; + struct ipu7_psys_pdata *pdata; + int ret; + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + pdata->base = base; + pdata->ipdata = ipdata; + + psys_adev = ipu7_bus_initialize_device(pdev, parent, pdata, ctrl, + IPU_PSYS_NAME); + if (IS_ERR(psys_adev)) { + dev_err_probe(&pdev->dev, PTR_ERR(psys_adev), + "ipu7_bus_initialize_device psys failed\n"); + kfree(pdata); + return ERR_CAST(psys_adev); + } + + psys_adev->mmu = ipu7_mmu_init(&pdev->dev, base, PSYS_MMID, + &ipdata->hw_variant); + if (IS_ERR(psys_adev->mmu)) { + dev_err_probe(&pdev->dev, PTR_ERR(psys_adev->mmu), + "ipu7_mmu_init(psys_adev->mmu) failed\n"); + put_device(&psys_adev->auxdev.dev); + kfree(pdata); + return ERR_CAST(psys_adev->mmu); + } + + psys_adev->mmu->dev = &psys_adev->auxdev.dev; + psys_adev->subsys = IPU_PS; + + ret = ipu7_bus_add_device(psys_adev); + if (ret) { + kfree(pdata); + return ERR_PTR(ret); + } + + return psys_adev; +} + +static struct ia_gofo_msg_log_info_ts fw_error_log[IPU_SUBSYS_NUM]; +void ipu7_dump_fw_error_log(const struct ipu7_bus_device *adev) +{ + void __iomem *reg = adev->isp->base + ((adev->subsys == IPU_IS) ? + BUTTRESS_REG_FW_GP24 : + BUTTRESS_REG_FW_GP8); + + memcpy_fromio(&fw_error_log[adev->subsys], reg, + sizeof(fw_error_log[adev->subsys])); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dump_fw_error_log, "INTEL_IPU7"); + +static int ipu7_pci_config_setup(struct pci_dev *dev) +{ + u16 pci_command; + int ret; + + pci_read_config_word(dev, PCI_COMMAND, &pci_command); + pci_command |= PCI_COMMAND_MEMORY | PCI_COMMAND_MASTER; + pci_write_config_word(dev, PCI_COMMAND, pci_command); + + ret = pci_enable_msi(dev); + if (ret) + dev_err(&dev->dev, "Failed to enable msi (%d)\n", ret); + + return ret; +} + +static int ipu7_map_fw_code_region(struct ipu7_bus_device *sys, + void *data, size_t size) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + struct sg_table *sgt = &sys->fw_sgt; + struct ipu7_device *isp = adev->isp; + struct pci_dev *pdev = isp->pdev; + unsigned long n_pages, i; + unsigned long attr = 0; + struct page **pages; + int ret; + + n_pages = PFN_UP(size); + + pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); + if (!pages) + return -ENOMEM; + + for (i = 0; i < n_pages; i++) { + struct page *p = vmalloc_to_page(data); + + if (!p) { + ret = -ENODEV; + goto out; + } + + pages[i] = p; + data += PAGE_SIZE; + } + + ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, size, + GFP_KERNEL); + if (ret) { + ret = -ENOMEM; + goto out; + } + + if (!isp->secure_mode) + attr |= DMA_ATTR_RESERVE_REGION; + + ret = dma_map_sgtable(&pdev->dev, sgt, DMA_BIDIRECTIONAL, 0); + if (ret < 0) { + dev_err(dev, "map fw code[%lu pages %u nents] failed\n", + n_pages, sgt->nents); + ret = -ENOMEM; + sg_free_table(sgt); + goto out; + } + + ret = ipu7_dma_map_sgtable(sys, sgt, DMA_BIDIRECTIONAL, attr); + if (ret) { + dma_unmap_sgtable(&pdev->dev, sgt, DMA_BIDIRECTIONAL, 0); + sg_free_table(sgt); + goto out; + } + + ipu7_dma_sync_sgtable(sys, sgt); + + dev_dbg(dev, "fw code region mapped at 0x%pad entries %d\n", + &sgt->sgl->dma_address, sgt->nents); + +out: + kfree(pages); + + return ret; +} + +static void ipu7_unmap_fw_code_region(struct ipu7_bus_device *sys) +{ + struct pci_dev *pdev = sys->isp->pdev; + struct sg_table *sgt = &sys->fw_sgt; + + ipu7_dma_unmap_sgtable(sys, sgt, DMA_BIDIRECTIONAL, 0); + dma_unmap_sgtable(&pdev->dev, sgt, DMA_BIDIRECTIONAL, 0); + sg_free_table(sgt); +} + +static int ipu7_init_fw_code_region_by_sys(struct ipu7_bus_device *sys, + const char *sys_name) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu7_device *isp = sys->isp; + int ret; + + /* Copy FW binaries to specific location. */ + ret = ipu7_cpd_copy_binary(isp->cpd_fw->data, sys_name, + isp->fw_code_region, &sys->fw_entry); + if (ret) { + dev_err(dev, "%s binary not found.\n", sys_name); + return ret; + } + + ret = pm_runtime_get_sync(dev); + if (ret < 0) { + dev_err(dev, "Failed to get runtime PM\n"); + return ret; + } + + ret = ipu7_mmu_hw_init(sys->mmu); + if (ret) { + dev_err(dev, "Failed to set mmu hw\n"); + pm_runtime_put(dev); + return ret; + } + + /* Map code region. */ + ret = ipu7_map_fw_code_region(sys, isp->fw_code_region, + IPU_FW_CODE_REGION_SIZE); + if (ret) + dev_err(dev, "Failed to map fw code region for %s.\n", + sys_name); + + ipu7_mmu_hw_cleanup(sys->mmu); + pm_runtime_put(dev); + + return ret; +} + +static int ipu7_init_fw_code_region(struct ipu7_device *isp) +{ + int ret; + + /* + * Allocate and map memory for FW execution. + * Not required in secure mode, in which FW runs in IMR. + */ + isp->fw_code_region = vmalloc(IPU_FW_CODE_REGION_SIZE); + if (!isp->fw_code_region) + return -ENOMEM; + + ret = ipu7_init_fw_code_region_by_sys(isp->isys, "isys"); + if (ret) + goto fail_init; + + ret = ipu7_init_fw_code_region_by_sys(isp->psys, "psys"); + if (ret) + goto fail_init; + + return 0; + +fail_init: + vfree(isp->fw_code_region); + + return ret; +} + +static int ipu7_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct ipu_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; + struct fwnode_handle *fwnode = dev_fwnode(&pdev->dev); + const struct ipu_buttress_ctrl *isys_buttress_ctrl; + const struct ipu_buttress_ctrl *psys_buttress_ctrl; + struct ipu_isys_internal_pdata *isys_ipdata; + struct ipu_psys_internal_pdata *psys_ipdata; + unsigned int dma_mask = IPU_DMA_MASK; + struct device *dev = &pdev->dev; + void __iomem *isys_base = NULL; + void __iomem *psys_base = NULL; + phys_addr_t phys, pb_phys; + struct ipu7_device *isp; + u32 is_es; + int ret; + + if (!fwnode || fwnode_property_read_u32(fwnode, "is_es", &is_es)) + is_es = 0; + + isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); + if (!isp) + return -ENOMEM; + + dev_set_name(dev, "intel-ipu7"); + isp->pdev = pdev; + INIT_LIST_HEAD(&isp->devices); + + ret = pcim_enable_device(pdev); + if (ret) + return dev_err_probe(dev, ret, "Enable PCI device failed\n"); + + dev_info(dev, "Device 0x%x (rev: 0x%x)\n", + pdev->device, pdev->revision); + + phys = pci_resource_start(pdev, IPU_PCI_BAR); + pb_phys = pci_resource_start(pdev, IPU_PCI_PBBAR); + dev_info(dev, "IPU7 PCI BAR0 base %pap BAR2 base %pap\n", + &phys, &pb_phys); + + isp->base = pcim_iomap_region(pdev, IPU_PCI_BAR, IPU_NAME); + if (IS_ERR(isp->base)) + return dev_err_probe(dev, PTR_ERR(isp->base), + "Failed to I/O memory remapping bar %u\n", + IPU_PCI_BAR); + + isp->pb_base = pcim_iomap_region(pdev, IPU_PCI_PBBAR, IPU_NAME); + if (IS_ERR(isp->pb_base)) + return dev_err_probe(dev, PTR_ERR(isp->pb_base), + "Failed to I/O memory remapping bar %u\n", + IPU_PCI_PBBAR); + + dev_info(dev, "IPU7 PCI BAR0 mapped at %p\n BAR2 mapped at %p\n", + isp->base, isp->pb_base); + + pci_set_drvdata(pdev, isp); + pci_set_master(pdev); + + switch (id->device) { + case IPU7_PCI_ID: + isp->hw_ver = IPU_VER_7; + isp->cpd_fw_name = IPU7_FIRMWARE_NAME; + isys_ipdata = &ipu7_isys_ipdata; + psys_ipdata = &ipu7_psys_ipdata; + isys_buttress_ctrl = &ipu7_isys_buttress_ctrl; + psys_buttress_ctrl = &ipu7_psys_buttress_ctrl; + break; + case IPU7P5_PCI_ID: + isp->hw_ver = IPU_VER_7P5; + isp->cpd_fw_name = IPU7P5_FIRMWARE_NAME; + isys_ipdata = &ipu7p5_isys_ipdata; + psys_ipdata = &ipu7p5_psys_ipdata; + isys_buttress_ctrl = &ipu7_isys_buttress_ctrl; + psys_buttress_ctrl = &ipu7_psys_buttress_ctrl; + break; + case IPU8_PCI_ID: + isp->hw_ver = IPU_VER_8; + isp->cpd_fw_name = IPU8_FIRMWARE_NAME; + isys_ipdata = &ipu8_isys_ipdata; + psys_ipdata = &ipu8_psys_ipdata; + isys_buttress_ctrl = &ipu8_isys_buttress_ctrl; + psys_buttress_ctrl = &ipu8_psys_buttress_ctrl; + break; + default: + WARN(1, "Unsupported IPU device"); + return -ENODEV; + } + + ipu_internal_pdata_init(isys_ipdata, psys_ipdata); + + isys_base = isp->base + isys_ipdata->hw_variant.offset; + psys_base = isp->base + psys_ipdata->hw_variant.offset; + + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(dma_mask)); + if (ret) + return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); + + dma_set_max_seg_size(dev, UINT_MAX); + + ret = ipu7_pci_config_setup(pdev); + if (ret) + return ret; + + ret = ipu_buttress_init(isp); + if (ret) + return ret; + + dev_info(dev, "firmware cpd file: %s\n", isp->cpd_fw_name); + + ret = request_firmware(&isp->cpd_fw, isp->cpd_fw_name, dev); + if (ret) { + dev_err_probe(dev, ret, + "Requesting signed firmware %s failed\n", + isp->cpd_fw_name); + goto buttress_exit; + } + + ret = ipu7_cpd_validate_cpd_file(isp, isp->cpd_fw->data, + isp->cpd_fw->size); + if (ret) { + dev_err_probe(dev, ret, "Failed to validate cpd\n"); + goto out_ipu_bus_del_devices; + } + + isys_ctrl = devm_kmemdup(dev, isys_buttress_ctrl, + sizeof(*isys_buttress_ctrl), GFP_KERNEL); + if (!isys_ctrl) { + ret = -ENOMEM; + goto out_ipu_bus_del_devices; + } + + isp->isys = ipu7_isys_init(pdev, dev, isys_ctrl, isys_base, + isys_ipdata, 0); + if (IS_ERR(isp->isys)) { + ret = PTR_ERR(isp->isys); + goto out_ipu_bus_del_devices; + } + + psys_ctrl = devm_kmemdup(dev, psys_buttress_ctrl, + sizeof(*psys_buttress_ctrl), GFP_KERNEL); + if (!psys_ctrl) { + ret = -ENOMEM; + goto out_ipu_bus_del_devices; + } + + isp->psys = ipu7_psys_init(pdev, &isp->isys->auxdev.dev, + psys_ctrl, psys_base, + psys_ipdata, 0); + if (IS_ERR(isp->psys)) { + ret = PTR_ERR(isp->psys); + goto out_ipu_bus_del_devices; + } + + ret = devm_request_threaded_irq(dev, pdev->irq, + ipu_buttress_isr, + ipu_buttress_isr_threaded, + IRQF_SHARED, IPU_NAME, isp); + if (ret) + goto out_ipu_bus_del_devices; + + if (!isp->secure_mode) { + ret = ipu7_init_fw_code_region(isp); + if (ret) + goto out_ipu_bus_del_devices; + } else { + ret = pm_runtime_get_sync(&isp->psys->auxdev.dev); + if (ret < 0) { + dev_err(&isp->psys->auxdev.dev, + "Failed to get runtime PM\n"); + goto out_ipu_bus_del_devices; + } + + ret = ipu7_mmu_hw_init(isp->psys->mmu); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "Failed to init MMU hardware\n"); + goto out_ipu_bus_del_devices; + } + + ret = ipu7_map_fw_code_region(isp->psys, + (void *)isp->cpd_fw->data, + isp->cpd_fw->size); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "failed to map fw image\n"); + goto out_ipu_bus_del_devices; + } + + ret = ipu_buttress_authenticate(isp); + if (ret) { + dev_err_probe(&isp->pdev->dev, ret, + "FW authentication failed\n"); + goto out_ipu_bus_del_devices; + } + + ipu7_mmu_hw_cleanup(isp->psys->mmu); + pm_runtime_put(&isp->psys->auxdev.dev); + } + + pm_runtime_put_noidle(dev); + pm_runtime_allow(dev); + + isp->ipu7_bus_ready_to_probe = true; + + return 0; + +out_ipu_bus_del_devices: + if (!IS_ERR_OR_NULL(isp->isys) && isp->isys->fw_sgt.nents) + ipu7_unmap_fw_code_region(isp->isys); + if (!IS_ERR_OR_NULL(isp->psys) && isp->psys->fw_sgt.nents) + ipu7_unmap_fw_code_region(isp->psys); + if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) + ipu7_mmu_cleanup(isp->psys->mmu); + if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) + ipu7_mmu_cleanup(isp->isys->mmu); + if (!IS_ERR_OR_NULL(isp->psys)) + pm_runtime_put(&isp->psys->auxdev.dev); + ipu7_bus_del_devices(pdev); + release_firmware(isp->cpd_fw); +buttress_exit: + ipu_buttress_exit(isp); + + return ret; +} + +static void ipu7_pci_remove(struct pci_dev *pdev) +{ + struct ipu7_device *isp = pci_get_drvdata(pdev); + + if (!IS_ERR_OR_NULL(isp->isys) && isp->isys->fw_sgt.nents) + ipu7_unmap_fw_code_region(isp->isys); + if (!IS_ERR_OR_NULL(isp->psys) && isp->psys->fw_sgt.nents) + ipu7_unmap_fw_code_region(isp->psys); + + if (!IS_ERR_OR_NULL(isp->fw_code_region)) + vfree(isp->fw_code_region); + + ipu7_bus_del_devices(pdev); + + pm_runtime_forbid(&pdev->dev); + pm_runtime_get_noresume(&pdev->dev); + + ipu_buttress_exit(isp); + + release_firmware(isp->cpd_fw); + + ipu7_mmu_cleanup(isp->psys->mmu); + ipu7_mmu_cleanup(isp->isys->mmu); +} + +static void ipu7_pci_reset_prepare(struct pci_dev *pdev) +{ + struct ipu7_device *isp = pci_get_drvdata(pdev); + + dev_warn(&pdev->dev, "FLR prepare\n"); + pm_runtime_forbid(&isp->pdev->dev); +} + +static void ipu7_pci_reset_done(struct pci_dev *pdev) +{ + struct ipu7_device *isp = pci_get_drvdata(pdev); + + ipu_buttress_restore(isp); + if (isp->secure_mode) + ipu_buttress_reset_authentication(isp); + + isp->ipc_reinit = true; + pm_runtime_allow(&isp->pdev->dev); + + dev_warn(&pdev->dev, "FLR completed\n"); +} + +/* + * PCI base driver code requires driver to provide these to enable + * PCI device level PM state transitions (D0<->D3) + */ +static int ipu7_suspend(struct device *dev) +{ + return 0; +} + +static int ipu7_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ipu7_device *isp = pci_get_drvdata(pdev); + struct ipu_buttress *b = &isp->buttress; + int ret; + + isp->secure_mode = ipu_buttress_get_secure_mode(isp); + dev_info(dev, "IPU7 in %s mode\n", + isp->secure_mode ? "secure" : "non-secure"); + + ipu_buttress_restore(isp); + + ret = ipu_buttress_ipc_reset(isp, &b->cse); + if (ret) + dev_err(dev, "IPC reset protocol failed!\n"); + + ret = pm_runtime_get_sync(&isp->psys->auxdev.dev); + if (ret < 0) { + dev_err(dev, "Failed to get runtime PM\n"); + return 0; + } + + ret = ipu_buttress_authenticate(isp); + if (ret) + dev_err(dev, "FW authentication failed(%d)\n", ret); + + pm_runtime_put(&isp->psys->auxdev.dev); + + return 0; +} + +static int ipu7_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct ipu7_device *isp = pci_get_drvdata(pdev); + int ret; + + ipu_buttress_restore(isp); + + if (isp->ipc_reinit) { + struct ipu_buttress *b = &isp->buttress; + + isp->ipc_reinit = false; + ret = ipu_buttress_ipc_reset(isp, &b->cse); + if (ret) + dev_err(dev, "IPC reset protocol failed!\n"); + } + + return 0; +} + +static const struct dev_pm_ops ipu7_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(&ipu7_suspend, &ipu7_resume) + SET_RUNTIME_PM_OPS(&ipu7_suspend, /* Same as in suspend flow */ + &ipu7_runtime_resume, + NULL) +}; + +static const struct pci_device_id ipu7_pci_tbl[] = { + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU7_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU7P5_PCI_ID)}, + {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU8_PCI_ID)}, + {0,} +}; +MODULE_DEVICE_TABLE(pci, ipu7_pci_tbl); + +static const struct pci_error_handlers pci_err_handlers = { + .reset_prepare = ipu7_pci_reset_prepare, + .reset_done = ipu7_pci_reset_done, +}; + +static struct pci_driver ipu7_pci_driver = { + .name = IPU_NAME, + .id_table = ipu7_pci_tbl, + .probe = ipu7_pci_probe, + .remove = ipu7_pci_remove, + .driver = { + .pm = &ipu7_pm_ops, + }, + .err_handler = &pci_err_handlers, +}; + +module_pci_driver(ipu7_pci_driver); + +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +MODULE_AUTHOR("Bingbu Cao "); +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Qingwu Zhang "); +MODULE_AUTHOR("Intel"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel ipu7 pci driver"); diff --git a/drivers/staging/media/ipu7/ipu7.h b/drivers/staging/media/ipu7/ipu7.h new file mode 100644 index 000000000000..ac8ac0689468 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7.h @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_H +#define IPU7_H + +#include +#include +#include + +#include "ipu7-buttress.h" + +struct ipu7_bus_device; +struct pci_dev; +struct firmware; + +#define IPU_NAME "intel-ipu7" +#define IPU_MEDIA_DEV_MODEL_NAME "ipu7" + +#define IPU7_FIRMWARE_NAME "intel/ipu/ipu7_fw.bin" +#define IPU7P5_FIRMWARE_NAME "intel/ipu/ipu7ptl_fw.bin" +#define IPU8_FIRMWARE_NAME "intel/ipu/ipu8_fw.bin" + +#define IPU7_ISYS_NUM_STREAMS 12 + +#define IPU7_PCI_ID 0x645d +#define IPU7P5_PCI_ID 0xb05d +#define IPU8_PCI_ID 0xd719 + +#define FW_LOG_BUF_SIZE (2 * 1024 * 1024) + +enum ipu_version { + IPU_VER_INVALID = 0, + IPU_VER_7 = 1, + IPU_VER_7P5 = 2, + IPU_VER_8 = 3, +}; + +static inline bool is_ipu7p5(u8 hw_ver) +{ + return hw_ver == IPU_VER_7P5; +} + +static inline bool is_ipu7(u8 hw_ver) +{ + return hw_ver == IPU_VER_7; +} + +static inline bool is_ipu8(u8 hw_ver) +{ + return hw_ver == IPU_VER_8; +} + +#define IPU_UNIFIED_OFFSET 0 + +/* + * ISYS DMA can overshoot. For higher resolutions over allocation is one line + * but it must be at minimum 1024 bytes. Value could be different in + * different versions / generations thus provide it via platform data. + */ +#define IPU_ISYS_OVERALLOC_MIN 1024 + +#define IPU_FW_CODE_REGION_SIZE 0x1000000 /* 16MB */ +#define IPU_FW_CODE_REGION_START 0x4000000 /* 64MB */ +#define IPU_FW_CODE_REGION_END (IPU_FW_CODE_REGION_START + \ + IPU_FW_CODE_REGION_SIZE) /* 80MB */ + +struct ipu7_device { + struct pci_dev *pdev; + struct list_head devices; + struct ipu7_bus_device *isys; + struct ipu7_bus_device *psys; + struct ipu_buttress buttress; + + const struct firmware *cpd_fw; + const char *cpd_fw_name; + /* Only for non-secure mode. */ + void *fw_code_region; + + void __iomem *base; + void __iomem *pb_base; + u8 hw_ver; + bool ipc_reinit; + bool secure_mode; + bool ipu7_bus_ready_to_probe; +}; + +#define IPU_DMA_MASK 39 +#define IPU_LIB_CALL_TIMEOUT_MS 2000 +#define IPU_PSYS_CMD_TIMEOUT_MS 2000 +#define IPU_PSYS_OPEN_CLOSE_TIMEOUT_US 50 +#define IPU_PSYS_OPEN_CLOSE_RETRY (10000 / IPU_PSYS_OPEN_CLOSE_TIMEOUT_US) + +#define IPU_ISYS_NAME "isys" +#define IPU_PSYS_NAME "psys" + +#define IPU_MMU_ADDR_BITS 32 +/* FW is accessible within the first 2 GiB only in non-secure mode. */ +#define IPU_MMU_ADDR_BITS_NON_SECURE 31 + +#define IPU7_IS_MMU_NUM 4U +#define IPU7_PS_MMU_NUM 4U +#define IPU7P5_IS_MMU_NUM 4U +#define IPU7P5_PS_MMU_NUM 4U +#define IPU8_IS_MMU_NUM 5U +#define IPU8_PS_MMU_NUM 4U +#define IPU_MMU_MAX_NUM 5U /* max(IS, PS) */ +#define IPU_MMU_MAX_TLB_L1_STREAMS 40U +#define IPU_MMU_MAX_TLB_L2_STREAMS 40U +#define IPU_ZLX_MAX_NUM 32U +#define IPU_ZLX_POOL_NUM 8U +#define IPU_UAO_PLANE_MAX_NUM 64U + +/* + * To maximize the IOSF utlization, IPU need to send requests in bursts. + * At the DMA interface with the buttress, there are CDC FIFOs with burst + * collection capability. CDC FIFO burst collectors have a configurable + * threshold and is configured based on the outcome of performance measurements. + * + * isys has 3 ports with IOSF interface for VC0, VC1 and VC2 + * psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2 + * + * Threshold values are pre-defined and are arrived at after performance + * evaluations on a type of IPU + */ +#define IPU_MAX_VC_IOSF_PORTS 4 + +/* + * IPU must configure correct arbitration mechanism related to the IOSF VC + * requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on + * stall and 1 means stall until the request is completed. + */ +#define IPU_BTRS_ARB_MODE_TYPE_REARB 0 +#define IPU_BTRS_ARB_MODE_TYPE_STALL 1 + +/* Currently chosen arbitration mechanism for VC0 */ +#define IPU_BTRS_ARB_STALL_MODE_VC0 IPU_BTRS_ARB_MODE_TYPE_REARB + +/* Currently chosen arbitration mechanism for VC1 */ +#define IPU_BTRS_ARB_STALL_MODE_VC1 IPU_BTRS_ARB_MODE_TYPE_REARB + +/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ +#define IPU_MMUV2_L2_RANGE (1024 * PAGE_SIZE) +/* Max L2 blocks per stream */ +#define IPU_MMUV2_MAX_L2_BLOCKS 2 +/* Max L1 blocks per stream */ +#define IPU_MMUV2_MAX_L1_BLOCKS 16 +#define IPU_MMUV2_TRASH_RANGE (IPU_MMUV2_L2_RANGE * \ + IPU_MMUV2_MAX_L2_BLOCKS) +/* Entries per L1 block */ +#define MMUV2_ENTRIES_PER_L1_BLOCK 16 +#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) +#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU_MMUV2_L2_RANGE + +struct ipu7_mmu_hw { + char name[32]; + + void __iomem *base; + void __iomem *zlx_base; + void __iomem *uao_base; + + u32 offset; + u32 zlx_offset; + u32 uao_offset; + + u32 info_bits; + u32 refill; + u32 collapse_en_bitmap; + u32 at_sp_arb_cfg; + + u32 l1_block; + u32 l2_block; + + u8 nr_l1streams; + u8 nr_l2streams; + u32 l1_block_sz[IPU_MMU_MAX_TLB_L1_STREAMS]; + u32 l2_block_sz[IPU_MMU_MAX_TLB_L2_STREAMS]; + + u8 zlx_nr; + u32 zlx_axi_pool[IPU_ZLX_POOL_NUM]; + u32 zlx_en[IPU_ZLX_MAX_NUM]; + u32 zlx_conf[IPU_ZLX_MAX_NUM]; + + u32 uao_p_num; + u32 uao_p2tlb[IPU_UAO_PLANE_MAX_NUM]; +}; + +struct ipu7_mmu_pdata { + u32 nr_mmus; + struct ipu7_mmu_hw mmu_hw[IPU_MMU_MAX_NUM]; + int mmid; +}; + +struct ipu7_isys_csi2_pdata { + void __iomem *base; +}; + +struct ipu7_isys_internal_csi2_pdata { + u32 nports; + u32 const *offsets; + u32 gpreg; +}; + +struct ipu7_hw_variants { + unsigned long offset; + u32 nr_mmus; + struct ipu7_mmu_hw mmu_hw[IPU_MMU_MAX_NUM]; + u8 cdc_fifos; + u8 cdc_fifo_threshold[IPU_MAX_VC_IOSF_PORTS]; + u32 dmem_offset; + u32 spc_offset; /* SPC offset from psys base */ +}; + +struct ipu_isys_internal_pdata { + struct ipu7_isys_internal_csi2_pdata csi2; + struct ipu7_hw_variants hw_variant; + u32 num_parallel_streams; + u32 isys_dma_overshoot; +}; + +struct ipu7_isys_pdata { + void __iomem *base; + const struct ipu_isys_internal_pdata *ipdata; +}; + +struct ipu_psys_internal_pdata { + struct ipu7_hw_variants hw_variant; +}; + +struct ipu7_psys_pdata { + void __iomem *base; + const struct ipu_psys_internal_pdata *ipdata; +}; + +int request_cpd_fw(const struct firmware **firmware_p, const char *name, + struct device *device); +void ipu_internal_pdata_init(struct ipu_isys_internal_pdata *isys_ipdata, + struct ipu_psys_internal_pdata *psys_ipdata); +void ipu7_dump_fw_error_log(const struct ipu7_bus_device *adev); +#endif /* IPU7_H */ From 71d81c25683a7cace187cdeeb232b51bb72a0d04 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:18 +0800 Subject: [PATCH 213/310] media: staging/ipu7: add IPU7 DMA APIs and MMU mapping The Intel IPU7 has internal microprocessor which runs the firmware The microprocessor accesses system DRAM by its internal 32-bit virtual address space. Driver should setup the MMU table and expose the DMA APIs for memory buffer mapping. This patch adds the IPU MMU and a DMA mapping implementation to setup the internal MMU hardware. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-dma.c | 477 ++++++++++++++ drivers/staging/media/ipu7/ipu7-dma.h | 46 ++ drivers/staging/media/ipu7/ipu7-mmu.c | 853 ++++++++++++++++++++++++++ drivers/staging/media/ipu7/ipu7-mmu.h | 414 +++++++++++++ 4 files changed, 1790 insertions(+) create mode 100644 drivers/staging/media/ipu7/ipu7-dma.c create mode 100644 drivers/staging/media/ipu7/ipu7-dma.h create mode 100644 drivers/staging/media/ipu7/ipu7-mmu.c create mode 100644 drivers/staging/media/ipu7/ipu7-mmu.h diff --git a/drivers/staging/media/ipu7/ipu7-dma.c b/drivers/staging/media/ipu7/ipu7-dma.c new file mode 100644 index 000000000000..a118b41b2f34 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-dma.c @@ -0,0 +1,477 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-dma.h" +#include "ipu7-mmu.h" + +struct vm_info { + struct list_head list; + struct page **pages; + dma_addr_t ipu7_iova; + void *vaddr; + unsigned long size; +}; + +static struct vm_info *get_vm_info(struct ipu7_mmu *mmu, dma_addr_t iova) +{ + struct vm_info *info, *save; + + list_for_each_entry_safe(info, save, &mmu->vma_list, list) { + if (iova >= info->ipu7_iova && + iova < (info->ipu7_iova + info->size)) + return info; + } + + return NULL; +} + +static void __clear_buffer(struct page *page, size_t size, unsigned long attrs) +{ + void *ptr; + + if (!page) + return; + /* + * Ensure that the allocated pages are zeroed, and that any data + * lurking in the kernel direct-mapped region is invalidated. + */ + ptr = page_address(page); + memset(ptr, 0, size); + if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) + clflush_cache_range(ptr, size); +} + +static struct page **__alloc_buffer(size_t size, gfp_t gfp, unsigned long attrs) +{ + unsigned int count = PHYS_PFN(size); + unsigned int array_size = count * sizeof(struct page *); + struct page **pages; + int i = 0; + + pages = kvzalloc(array_size, GFP_KERNEL); + if (!pages) + return NULL; + + gfp |= __GFP_NOWARN; + + while (count) { + int j, order = __fls(count); + + pages[i] = alloc_pages(gfp, order); + while (!pages[i] && order) + pages[i] = alloc_pages(gfp, --order); + if (!pages[i]) + goto error; + + if (order) { + split_page(pages[i], order); + j = 1U << order; + while (j--) + pages[i + j] = pages[i] + j; + } + + __clear_buffer(pages[i], PAGE_SIZE << order, attrs); + i += 1U << order; + count -= 1U << order; + } + + return pages; +error: + while (i--) + if (pages[i]) + __free_pages(pages[i], 0); + kvfree(pages); + return NULL; +} + +static void __free_buffer(struct page **pages, size_t size, unsigned long attrs) +{ + unsigned int count = PHYS_PFN(size); + unsigned int i; + + for (i = 0; i < count && pages[i]; i++) { + __clear_buffer(pages[i], PAGE_SIZE, attrs); + __free_pages(pages[i], 0); + } + + kvfree(pages); +} + +void ipu7_dma_sync_single(struct ipu7_bus_device *sys, dma_addr_t dma_handle, + size_t size) +{ + void *vaddr; + u32 offset; + struct vm_info *info; + struct ipu7_mmu *mmu = sys->mmu; + + info = get_vm_info(mmu, dma_handle); + if (WARN_ON(!info)) + return; + + offset = dma_handle - info->ipu7_iova; + if (WARN_ON(size > (info->size - offset))) + return; + + vaddr = info->vaddr + offset; + clflush_cache_range(vaddr, size); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_sync_single, "INTEL_IPU7"); + +void ipu7_dma_sync_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + unsigned int nents) +{ + struct scatterlist *sg; + int i; + + for_each_sg(sglist, sg, nents, i) + clflush_cache_range(sg_virt(sg), sg->length); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_sync_sg, "INTEL_IPU7"); + +void ipu7_dma_sync_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt) +{ + ipu7_dma_sync_sg(sys, sgt->sgl, sgt->orig_nents); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_sync_sgtable, "INTEL_IPU7"); + +void *ipu7_dma_alloc(struct ipu7_bus_device *sys, size_t size, + dma_addr_t *dma_handle, gfp_t gfp, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct pci_dev *pdev = sys->isp->pdev; + dma_addr_t pci_dma_addr, ipu7_iova; + struct ipu7_mmu *mmu = sys->mmu; + struct vm_info *info; + unsigned long count; + struct page **pages; + struct iova *iova; + unsigned int i; + int ret; + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return NULL; + + size = PAGE_ALIGN(size); + count = PHYS_PFN(size); + + iova = alloc_iova(&mmu->dmap->iovad, count, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (!iova) + goto out_kfree; + + pages = __alloc_buffer(size, gfp, attrs); + if (!pages) + goto out_free_iova; + + dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n", + size, iova->pfn_lo, iova->pfn_hi); + for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) { + pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0, + PAGE_SIZE, DMA_BIDIRECTIONAL, + attrs); + dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n", + &pci_dma_addr); + if (dma_mapping_error(&pdev->dev, pci_dma_addr)) { + dev_err(dev, "pci_dma_mapping for page[%d] failed", i); + goto out_unmap; + } + + ret = ipu7_mmu_map(mmu->dmap->mmu_info, + PFN_PHYS(iova->pfn_lo + i), pci_dma_addr, + PAGE_SIZE); + if (ret) { + dev_err(dev, "ipu7_mmu_map for pci_dma[%d] %pad failed", + i, &pci_dma_addr); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, + PAGE_SIZE, DMA_BIDIRECTIONAL, + attrs); + goto out_unmap; + } + } + + info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL); + if (!info->vaddr) + goto out_unmap; + + *dma_handle = PFN_PHYS(iova->pfn_lo); + + info->pages = pages; + info->ipu7_iova = *dma_handle; + info->size = size; + list_add(&info->list, &mmu->vma_list); + + return info->vaddr; + +out_unmap: + while (i--) { + ipu7_iova = PFN_PHYS(iova->pfn_lo + i); + pci_dma_addr = ipu7_mmu_iova_to_phys(mmu->dmap->mmu_info, + ipu7_iova); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, + DMA_BIDIRECTIONAL, attrs); + + ipu7_mmu_unmap(mmu->dmap->mmu_info, ipu7_iova, PAGE_SIZE); + } + + __free_buffer(pages, size, attrs); + +out_free_iova: + __free_iova(&mmu->dmap->iovad, iova); +out_kfree: + kfree(info); + + return NULL; +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_alloc, "INTEL_IPU7"); + +void ipu7_dma_free(struct ipu7_bus_device *sys, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs) +{ + struct ipu7_mmu *mmu = sys->mmu; + struct pci_dev *pdev = sys->isp->pdev; + struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle)); + dma_addr_t pci_dma_addr, ipu7_iova; + struct vm_info *info; + struct page **pages; + unsigned int i; + + if (WARN_ON(!iova)) + return; + + info = get_vm_info(mmu, dma_handle); + if (WARN_ON(!info)) + return; + + if (WARN_ON(!info->vaddr)) + return; + + if (WARN_ON(!info->pages)) + return; + + list_del(&info->list); + + size = PAGE_ALIGN(size); + + pages = info->pages; + + vunmap(vaddr); + + for (i = 0; i < PHYS_PFN(size); i++) { + ipu7_iova = PFN_PHYS(iova->pfn_lo + i); + pci_dma_addr = ipu7_mmu_iova_to_phys(mmu->dmap->mmu_info, + ipu7_iova); + dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, + DMA_BIDIRECTIONAL, attrs); + } + + ipu7_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + + __free_buffer(pages, size, attrs); + + mmu->tlb_invalidate(mmu); + + __free_iova(&mmu->dmap->iovad, iova); + + kfree(info); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_free, "INTEL_IPU7"); + +int ipu7_dma_mmap(struct ipu7_bus_device *sys, struct vm_area_struct *vma, + void *addr, dma_addr_t iova, size_t size, + unsigned long attrs) +{ + struct ipu7_mmu *mmu = sys->mmu; + size_t count = PFN_UP(size); + struct vm_info *info; + size_t i; + int ret; + + info = get_vm_info(mmu, iova); + if (!info) + return -EFAULT; + + if (!info->vaddr) + return -EFAULT; + + if (vma->vm_start & ~PAGE_MASK) + return -EINVAL; + + if (size > info->size) + return -EFAULT; + + for (i = 0; i < count; i++) { + ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i), + info->pages[i]); + if (ret < 0) + return ret; + } + + return 0; +} + +void ipu7_dma_unmap_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu7_mmu *mmu = sys->mmu; + struct iova *iova = find_iova(&mmu->dmap->iovad, + PHYS_PFN(sg_dma_address(sglist))); + struct scatterlist *sg; + dma_addr_t pci_dma_addr; + unsigned int i; + + if (!nents) + return; + + if (WARN_ON(!iova)) + return; + + /* + * Before IPU7 mmu unmap, return the pci dma address back to sg + * assume the nents is less than orig_nents as the least granule + * is 1 SZ_4K page + */ + dev_dbg(dev, "trying to unmap concatenated %u ents\n", nents); + for_each_sg(sglist, sg, nents, i) { + dev_dbg(dev, "unmap sg[%d] %pad size %u\n", i, + &sg_dma_address(sg), sg_dma_len(sg)); + pci_dma_addr = ipu7_mmu_iova_to_phys(mmu->dmap->mmu_info, + sg_dma_address(sg)); + dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n", + &pci_dma_addr, i); + sg_dma_address(sg) = pci_dma_addr; + } + + dev_dbg(dev, "ipu7_mmu_unmap low pfn %lu high pfn %lu\n", + iova->pfn_lo, iova->pfn_hi); + ipu7_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + + mmu->tlb_invalidate(mmu); + __free_iova(&mmu->dmap->iovad, iova); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_unmap_sg, "INTEL_IPU7"); + +int ipu7_dma_map_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs) +{ + struct device *dev = &sys->auxdev.dev; + struct ipu7_mmu *mmu = sys->mmu; + struct scatterlist *sg; + struct iova *iova; + size_t npages = 0; + unsigned long iova_addr; + int i; + + for_each_sg(sglist, sg, nents, i) { + if (sg->offset) { + dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n", + i, sg->offset); + return -EFAULT; + } + } + + for_each_sg(sglist, sg, nents, i) + npages += PFN_UP(sg_dma_len(sg)); + + dev_dbg(dev, "dmamap trying to map %d ents %zu pages\n", + nents, npages); + + if (attrs & DMA_ATTR_RESERVE_REGION) { + /* + * Reserve iova with size aligned to IPU_FW_CODE_REGION_SIZE. + * Only apply for non-secure mode. + */ + unsigned long lo, hi; + + lo = iova_pfn(&mmu->dmap->iovad, IPU_FW_CODE_REGION_START); + hi = iova_pfn(&mmu->dmap->iovad, IPU_FW_CODE_REGION_END) - 1U; + iova = reserve_iova(&mmu->dmap->iovad, lo, hi); + if (!iova) { + dev_err(dev, "Reserve iova[%lx:%lx] failed.\n", lo, hi); + return -ENOMEM; + } + dev_dbg(dev, "iova[%lx:%lx] reserved for FW code.\n", lo, hi); + } else { + iova = alloc_iova(&mmu->dmap->iovad, npages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), + 0); + if (!iova) + return 0; + } + + dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, + iova->pfn_hi); + + iova_addr = iova->pfn_lo; + for_each_sg(sglist, sg, nents, i) { + phys_addr_t iova_pa; + int ret; + + iova_pa = PFN_PHYS(iova_addr); + dev_dbg(dev, "mapping entry %d: iova %pap phy %pap size %d\n", + i, &iova_pa, &sg_dma_address(sg), sg_dma_len(sg)); + + ret = ipu7_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), + sg_dma_address(sg), + PAGE_ALIGN(sg_dma_len(sg))); + if (ret) + goto out_fail; + + sg_dma_address(sg) = PFN_PHYS(iova_addr); + + iova_addr += PFN_UP(sg_dma_len(sg)); + } + + dev_dbg(dev, "dmamap %d ents %zu pages mapped\n", nents, npages); + + return nents; + +out_fail: + ipu7_dma_unmap_sg(sys, sglist, i, dir, attrs); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_map_sg, "INTEL_IPU7"); + +int ipu7_dma_map_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) +{ + int nents; + + nents = ipu7_dma_map_sg(sys, sgt->sgl, sgt->nents, dir, attrs); + if (nents < 0) + return nents; + + sgt->nents = nents; + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_map_sgtable, "INTEL_IPU7"); + +void ipu7_dma_unmap_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs) +{ + ipu7_dma_unmap_sg(sys, sgt->sgl, sgt->nents, dir, attrs); +} +EXPORT_SYMBOL_NS_GPL(ipu7_dma_unmap_sgtable, "INTEL_IPU7"); diff --git a/drivers/staging/media/ipu7/ipu7-dma.h b/drivers/staging/media/ipu7/ipu7-dma.h new file mode 100644 index 000000000000..fe789af5e664 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-dma.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (C) 2013--2025 Intel Corporation */ + +#ifndef IPU7_DMA_H +#define IPU7_DMA_H + +#include +#include +#include +#include +#include + +#include "ipu7-bus.h" + +#define DMA_ATTR_RESERVE_REGION BIT(31) +struct ipu7_mmu_info; + +struct ipu7_dma_mapping { + struct ipu7_mmu_info *mmu_info; + struct iova_domain iovad; +}; + +void ipu7_dma_sync_single(struct ipu7_bus_device *sys, dma_addr_t dma_handle, + size_t size); +void ipu7_dma_sync_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + unsigned int nents); +void ipu7_dma_sync_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt); +void *ipu7_dma_alloc(struct ipu7_bus_device *sys, size_t size, + dma_addr_t *dma_handle, gfp_t gfp, + unsigned long attrs); +void ipu7_dma_free(struct ipu7_bus_device *sys, size_t size, void *vaddr, + dma_addr_t dma_handle, unsigned long attrs); +int ipu7_dma_mmap(struct ipu7_bus_device *sys, struct vm_area_struct *vma, + void *addr, dma_addr_t iova, size_t size, + unsigned long attrs); +int ipu7_dma_map_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs); +void ipu7_dma_unmap_sg(struct ipu7_bus_device *sys, struct scatterlist *sglist, + int nents, enum dma_data_direction dir, + unsigned long attrs); +int ipu7_dma_map_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs); +void ipu7_dma_unmap_sgtable(struct ipu7_bus_device *sys, struct sg_table *sgt, + enum dma_data_direction dir, unsigned long attrs); +#endif /* IPU7_DMA_H */ diff --git a/drivers/staging/media/ipu7/ipu7-mmu.c b/drivers/staging/media/ipu7/ipu7-mmu.c new file mode 100644 index 000000000000..ded1986eb8ba --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-mmu.c @@ -0,0 +1,853 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-dma.h" +#include "ipu7-mmu.h" +#include "ipu7-platform-regs.h" + +#define ISP_PAGE_SHIFT 12 +#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) +#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1U)) + +#define ISP_L1PT_SHIFT 22 +#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) + +#define ISP_L2PT_SHIFT 12 +#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) + +#define ISP_L1PT_PTES 1024U +#define ISP_L2PT_PTES 1024U + +#define ISP_PADDR_SHIFT 12 + +#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ +#define REG_INFO 0x0008 + +#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) + +#define MMU_TLB_INVALIDATE_TIMEOUT 2000 + +static __maybe_unused void mmu_irq_handler(struct ipu7_mmu *mmu) +{ + unsigned int i; + u32 irq_cause; + + for (i = 0; i < mmu->nr_mmus; i++) { + irq_cause = readl(mmu->mmu_hw[i].base + MMU_REG_IRQ_CAUSE); + pr_info("mmu %s irq_cause = 0x%x", mmu->mmu_hw[i].name, + irq_cause); + writel(0x1ffff, mmu->mmu_hw[i].base + MMU_REG_IRQ_CLEAR); + } +} + +static void tlb_invalidate(struct ipu7_mmu *mmu) +{ + unsigned long flags; + unsigned int i; + int ret; + u32 val; + + spin_lock_irqsave(&mmu->ready_lock, flags); + if (!mmu->ready) { + spin_unlock_irqrestore(&mmu->ready_lock, flags); + return; + } + + for (i = 0; i < mmu->nr_mmus; i++) { + writel(0xffffffffU, mmu->mmu_hw[i].base + + MMU_REG_INVALIDATE_0); + + /* Need check with HW, use l1streams or l2streams */ + if (mmu->mmu_hw[i].nr_l2streams > 32) + writel(0xffffffffU, mmu->mmu_hw[i].base + + MMU_REG_INVALIDATE_1); + + /* + * The TLB invalidation is a "single cycle" (IOMMU clock cycles) + * When the actual MMIO write reaches the IPU TLB Invalidate + * register, wmb() will force the TLB invalidate out if the CPU + * attempts to update the IOMMU page table (or sooner). + */ + wmb(); + + /* wait invalidation done */ + ret = readl_poll_timeout_atomic(mmu->mmu_hw[i].base + + MMU_REG_INVALIDATION_STATUS, + val, !(val & 0x1U), 500, + MMU_TLB_INVALIDATE_TIMEOUT); + if (ret) + dev_err(mmu->dev, "MMU[%u] TLB invalidate failed\n", i); + } + + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} + +static dma_addr_t map_single(struct ipu7_mmu_info *mmu_info, void *ptr) +{ + dma_addr_t dma; + + dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL); + if (dma_mapping_error(mmu_info->dev, dma)) + return 0; + + return dma; +} + +static int get_dummy_page(struct ipu7_mmu_info *mmu_info) +{ + void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + + if (!pt) + return -ENOMEM; + + dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt); + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map dummy page\n"); + goto err_free_page; + } + + mmu_info->dummy_page = pt; + mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT; + + return 0; + +err_free_page: + free_page((unsigned long)pt); + return -ENOMEM; +} + +static void free_dummy_page(struct ipu7_mmu_info *mmu_info) +{ + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->dummy_page_pteval), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_page); +} + +static int alloc_dummy_l2_pt(struct ipu7_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + unsigned int i; + + if (!pt) + return -ENOMEM; + + dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt); + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map l2pt page\n"); + goto err_free_page; + } + + for (i = 0; i < ISP_L2PT_PTES; i++) + pt[i] = mmu_info->dummy_page_pteval; + + mmu_info->dummy_l2_pt = pt; + mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT; + + return 0; + +err_free_page: + free_page((unsigned long)pt); + return -ENOMEM; +} + +static void free_dummy_l2_pt(struct ipu7_mmu_info *mmu_info) +{ + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_l2_pt); +} + +static u32 *alloc_l1_pt(struct ipu7_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + dma_addr_t dma; + unsigned int i; + + if (!pt) + return NULL; + + dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt); + + for (i = 0; i < ISP_L1PT_PTES; i++) + pt[i] = mmu_info->dummy_l2_pteval; + + dma = map_single(mmu_info, pt); + if (!dma) { + dev_err(mmu_info->dev, "Failed to map l1pt page\n"); + goto err_free_page; + } + + mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT; + dev_dbg(mmu_info->dev, "l1 pt %p mapped at %pad\n", pt, &dma); + + return pt; + +err_free_page: + free_page((unsigned long)pt); + return NULL; +} + +static u32 *alloc_l2_pt(struct ipu7_mmu_info *mmu_info) +{ + u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); + unsigned int i; + + if (!pt) + return NULL; + + dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); + + for (i = 0; i < ISP_L1PT_PTES; i++) + pt[i] = mmu_info->dummy_page_pteval; + + return pt; +} + +static void l2_unmap(struct ipu7_mmu_info *mmu_info, unsigned long iova, + phys_addr_t dummy, size_t size) +{ + unsigned int l2_entries; + unsigned int l2_idx; + unsigned long flags; + u32 l1_idx; + u32 *l2_pt; + + spin_lock_irqsave(&mmu_info->lock, flags); + for (l1_idx = iova >> ISP_L1PT_SHIFT; + size > 0U && l1_idx < ISP_L1PT_PTES; l1_idx++) { + dev_dbg(mmu_info->dev, + "unmapping l2 pgtable (l1 index %u (iova 0x%8.8lx))\n", + l1_idx, iova); + + if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) { + dev_err(mmu_info->dev, + "unmap not mapped iova 0x%8.8lx l1 index %u\n", + iova, l1_idx); + continue; + } + l2_pt = mmu_info->l2_pts[l1_idx]; + + l2_entries = 0; + for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; + size > 0U && l2_idx < ISP_L2PT_PTES; l2_idx++) { + phys_addr_t pteval = TBL_PHYS_ADDR(l2_pt[l2_idx]); + + dev_dbg(mmu_info->dev, + "unmap l2 index %u with pteval 0x%p\n", + l2_idx, &pteval); + l2_pt[l2_idx] = mmu_info->dummy_page_pteval; + + iova += ISP_PAGE_SIZE; + size -= ISP_PAGE_SIZE; + + l2_entries++; + } + + WARN_ON_ONCE(!l2_entries); + clflush_cache_range(&l2_pt[l2_idx - l2_entries], + sizeof(l2_pt[0]) * l2_entries); + } + + WARN_ON_ONCE(size); + spin_unlock_irqrestore(&mmu_info->lock, flags); +} + +static int l2_map(struct ipu7_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + struct device *dev = mmu_info->dev; + unsigned int l2_entries; + u32 *l2_pt, *l2_virt; + unsigned int l2_idx; + unsigned long flags; + size_t mapped = 0; + dma_addr_t dma; + u32 l1_entry; + u32 l1_idx; + int err = 0; + + spin_lock_irqsave(&mmu_info->lock, flags); + + paddr = ALIGN(paddr, ISP_PAGE_SIZE); + for (l1_idx = iova >> ISP_L1PT_SHIFT; + size && l1_idx < ISP_L1PT_PTES; l1_idx++) { + dev_dbg(dev, + "mapping l2 page table for l1 index %u (iova %8.8x)\n", + l1_idx, (u32)iova); + + l1_entry = mmu_info->l1_pt[l1_idx]; + if (l1_entry == mmu_info->dummy_l2_pteval) { + l2_virt = mmu_info->l2_pts[l1_idx]; + if (likely(!l2_virt)) { + l2_virt = alloc_l2_pt(mmu_info); + if (!l2_virt) { + err = -ENOMEM; + goto error; + } + } + + dma = map_single(mmu_info, l2_virt); + if (!dma) { + dev_err(dev, "Failed to map l2pt page\n"); + free_page((unsigned long)l2_virt); + err = -EINVAL; + goto error; + } + + l1_entry = dma >> ISP_PADDR_SHIFT; + + dev_dbg(dev, "page for l1_idx %u %p allocated\n", + l1_idx, l2_virt); + mmu_info->l1_pt[l1_idx] = l1_entry; + mmu_info->l2_pts[l1_idx] = l2_virt; + + clflush_cache_range(&mmu_info->l1_pt[l1_idx], + sizeof(mmu_info->l1_pt[l1_idx])); + } + + l2_pt = mmu_info->l2_pts[l1_idx]; + l2_entries = 0; + + for (l2_idx = (iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; + size && l2_idx < ISP_L2PT_PTES; l2_idx++) { + l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT; + + dev_dbg(dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx, + l2_pt[l2_idx]); + + iova += ISP_PAGE_SIZE; + paddr += ISP_PAGE_SIZE; + mapped += ISP_PAGE_SIZE; + size -= ISP_PAGE_SIZE; + + l2_entries++; + } + + WARN_ON_ONCE(!l2_entries); + clflush_cache_range(&l2_pt[l2_idx - l2_entries], + sizeof(l2_pt[0]) * l2_entries); + } + + spin_unlock_irqrestore(&mmu_info->lock, flags); + + return 0; + +error: + spin_unlock_irqrestore(&mmu_info->lock, flags); + /* unroll mapping in case something went wrong */ + if (mapped) + l2_unmap(mmu_info, iova - mapped, paddr - mapped, mapped); + + return err; +} + +static int __ipu7_mmu_map(struct ipu7_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + u32 iova_start = round_down(iova, ISP_PAGE_SIZE); + u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE); + + dev_dbg(mmu_info->dev, + "mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr %pap\n", + iova_start, iova_end, size, &paddr); + + return l2_map(mmu_info, iova_start, paddr, size); +} + +static void __ipu7_mmu_unmap(struct ipu7_mmu_info *mmu_info, + unsigned long iova, size_t size) +{ + l2_unmap(mmu_info, iova, 0, size); +} + +static int allocate_trash_buffer(struct ipu7_mmu *mmu) +{ + unsigned int n_pages = PFN_UP(IPU_MMUV2_TRASH_RANGE); + unsigned long iova_addr; + struct iova *iova; + unsigned int i; + dma_addr_t dma; + int ret; + + /* Allocate 8MB in iova range */ + iova = alloc_iova(&mmu->dmap->iovad, n_pages, + PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); + if (!iova) { + dev_err(mmu->dev, "cannot allocate iova range for trash\n"); + return -ENOMEM; + } + + dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0, + PAGE_SIZE, DMA_BIDIRECTIONAL); + if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) { + dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n"); + ret = -ENOMEM; + goto out_free_iova; + } + + mmu->pci_trash_page = dma; + + /* + * Map the 8MB iova address range to the same physical trash page + * mmu->trash_page which is already reserved at the probe + */ + iova_addr = iova->pfn_lo; + for (i = 0; i < n_pages; i++) { + ret = ipu7_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), + mmu->pci_trash_page, PAGE_SIZE); + if (ret) { + dev_err(mmu->dev, + "mapping trash buffer range failed\n"); + goto out_unmap; + } + + iova_addr++; + } + + mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo); + dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n", + mmu->mmid, (unsigned int)mmu->iova_trash_page); + return 0; + +out_unmap: + ipu7_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page, + PAGE_SIZE, DMA_BIDIRECTIONAL); +out_free_iova: + __free_iova(&mmu->dmap->iovad, iova); + return ret; +} + +static void __mmu_at_init(struct ipu7_mmu *mmu) +{ + struct ipu7_mmu_info *mmu_info; + unsigned int i; + + mmu_info = mmu->dmap->mmu_info; + for (i = 0; i < mmu->nr_mmus; i++) { + struct ipu7_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; + unsigned int j; + + /* Write page table address per MMU */ + writel((phys_addr_t)mmu_info->l1_pt_dma, + mmu_hw->base + MMU_REG_PAGE_TABLE_BASE_ADDR); + dev_dbg(mmu->dev, "mmu %s base was set as %x\n", mmu_hw->name, + readl(mmu_hw->base + MMU_REG_PAGE_TABLE_BASE_ADDR)); + + /* Set info bits and axi_refill per MMU */ + writel(mmu_hw->info_bits, + mmu_hw->base + MMU_REG_USER_INFO_BITS); + writel(mmu_hw->refill, mmu_hw->base + MMU_REG_AXI_REFILL_IF_ID); + writel(mmu_hw->collapse_en_bitmap, + mmu_hw->base + MMU_REG_COLLAPSE_ENABLE_BITMAP); + + dev_dbg(mmu->dev, "mmu %s info_bits was set as %x\n", + mmu_hw->name, + readl(mmu_hw->base + MMU_REG_USER_INFO_BITS)); + + if (mmu_hw->at_sp_arb_cfg) + writel(mmu_hw->at_sp_arb_cfg, + mmu_hw->base + MMU_REG_AT_SP_ARB_CFG); + + /* default irq configuration */ + writel(0x3ff, mmu_hw->base + MMU_REG_IRQ_MASK); + writel(0x3ff, mmu_hw->base + MMU_REG_IRQ_ENABLE); + + /* Configure MMU TLB stream configuration for L1/L2 */ + for (j = 0; j < mmu_hw->nr_l1streams; j++) { + writel(mmu_hw->l1_block_sz[j], mmu_hw->base + + mmu_hw->l1_block + 4U * j); + } + + for (j = 0; j < mmu_hw->nr_l2streams; j++) { + writel(mmu_hw->l2_block_sz[j], mmu_hw->base + + mmu_hw->l2_block + 4U * j); + } + + for (j = 0; j < mmu_hw->uao_p_num; j++) { + if (!mmu_hw->uao_p2tlb[j]) + continue; + writel(mmu_hw->uao_p2tlb[j], mmu_hw->uao_base + 4U * j); + } + } +} + +static void __mmu_zlx_init(struct ipu7_mmu *mmu) +{ + unsigned int i; + + dev_dbg(mmu->dev, "mmu zlx init\n"); + + for (i = 0; i < mmu->nr_mmus; i++) { + struct ipu7_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; + unsigned int j; + + dev_dbg(mmu->dev, "mmu %s zlx init\n", mmu_hw->name); + for (j = 0; j < IPU_ZLX_POOL_NUM; j++) { + if (!mmu_hw->zlx_axi_pool[j]) + continue; + writel(mmu_hw->zlx_axi_pool[j], + mmu_hw->zlx_base + ZLX_REG_AXI_POOL + j * 0x4U); + } + + for (j = 0; j < mmu_hw->zlx_nr; j++) { + if (!mmu_hw->zlx_conf[j]) + continue; + + writel(mmu_hw->zlx_conf[j], + mmu_hw->zlx_base + ZLX_REG_CONF + j * 0x8U); + } + + for (j = 0; j < mmu_hw->zlx_nr; j++) { + if (!mmu_hw->zlx_en[j]) + continue; + + writel(mmu_hw->zlx_en[j], + mmu_hw->zlx_base + ZLX_REG_EN + j * 0x8U); + } + } +} + +int ipu7_mmu_hw_init(struct ipu7_mmu *mmu) +{ + unsigned long flags; + + dev_dbg(mmu->dev, "IPU mmu hardware init\n"); + + /* Initialise the each MMU and ZLX */ + __mmu_at_init(mmu); + __mmu_zlx_init(mmu); + + if (!mmu->trash_page) { + int ret; + + mmu->trash_page = alloc_page(GFP_KERNEL); + if (!mmu->trash_page) { + dev_err(mmu->dev, "insufficient memory for trash buffer\n"); + return -ENOMEM; + } + + ret = allocate_trash_buffer(mmu); + if (ret) { + __free_page(mmu->trash_page); + mmu->trash_page = NULL; + dev_err(mmu->dev, "trash buffer allocation failed\n"); + return ret; + } + } + + spin_lock_irqsave(&mmu->ready_lock, flags); + mmu->ready = true; + spin_unlock_irqrestore(&mmu->ready_lock, flags); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_mmu_hw_init, "INTEL_IPU7"); + +static struct ipu7_mmu_info *ipu7_mmu_alloc(struct ipu7_device *isp) +{ + struct ipu7_mmu_info *mmu_info; + int ret; + + mmu_info = kzalloc(sizeof(*mmu_info), GFP_KERNEL); + if (!mmu_info) + return NULL; + + if (isp->secure_mode) { + mmu_info->aperture_start = IPU_FW_CODE_REGION_END; + mmu_info->aperture_end = + (dma_addr_t)DMA_BIT_MASK(IPU_MMU_ADDR_BITS); + } else { + mmu_info->aperture_start = IPU_FW_CODE_REGION_START; + mmu_info->aperture_end = + (dma_addr_t)DMA_BIT_MASK(IPU_MMU_ADDR_BITS_NON_SECURE); + } + + mmu_info->pgsize_bitmap = SZ_4K; + mmu_info->dev = &isp->pdev->dev; + + ret = get_dummy_page(mmu_info); + if (ret) + goto err_free_info; + + ret = alloc_dummy_l2_pt(mmu_info); + if (ret) + goto err_free_dummy_page; + + mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts)); + if (!mmu_info->l2_pts) + goto err_free_dummy_l2_pt; + + /* + * We always map the L1 page table (a single page as well as + * the L2 page tables). + */ + mmu_info->l1_pt = alloc_l1_pt(mmu_info); + if (!mmu_info->l1_pt) + goto err_free_l2_pts; + + spin_lock_init(&mmu_info->lock); + + dev_dbg(mmu_info->dev, "domain initialised\n"); + + return mmu_info; + +err_free_l2_pts: + vfree(mmu_info->l2_pts); +err_free_dummy_l2_pt: + free_dummy_l2_pt(mmu_info); +err_free_dummy_page: + free_dummy_page(mmu_info); +err_free_info: + kfree(mmu_info); + + return NULL; +} + +void ipu7_mmu_hw_cleanup(struct ipu7_mmu *mmu) +{ + unsigned long flags; + + spin_lock_irqsave(&mmu->ready_lock, flags); + mmu->ready = false; + spin_unlock_irqrestore(&mmu->ready_lock, flags); +} +EXPORT_SYMBOL_NS_GPL(ipu7_mmu_hw_cleanup, "INTEL_IPU7"); + +static struct ipu7_dma_mapping *alloc_dma_mapping(struct ipu7_device *isp) +{ + struct ipu7_dma_mapping *dmap; + unsigned long base_pfn; + + dmap = kzalloc(sizeof(*dmap), GFP_KERNEL); + if (!dmap) + return NULL; + + dmap->mmu_info = ipu7_mmu_alloc(isp); + if (!dmap->mmu_info) { + kfree(dmap); + return NULL; + } + + /* 0~64M is forbidden for uctile controller */ + base_pfn = max_t(unsigned long, 1, + PFN_DOWN(dmap->mmu_info->aperture_start)); + init_iova_domain(&dmap->iovad, SZ_4K, base_pfn); + dmap->mmu_info->dmap = dmap; + + dev_dbg(&isp->pdev->dev, "alloc mapping\n"); + + iova_cache_get(); + + return dmap; +} + +phys_addr_t ipu7_mmu_iova_to_phys(struct ipu7_mmu_info *mmu_info, + dma_addr_t iova) +{ + phys_addr_t phy_addr; + unsigned long flags; + u32 *l2_pt; + + spin_lock_irqsave(&mmu_info->lock, flags); + l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT]; + phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT]; + phy_addr <<= ISP_PAGE_SHIFT; + spin_unlock_irqrestore(&mmu_info->lock, flags); + + return phy_addr; +} + +void ipu7_mmu_unmap(struct ipu7_mmu_info *mmu_info, unsigned long iova, + size_t size) +{ + unsigned int min_pagesz; + + dev_dbg(mmu_info->dev, "unmapping iova 0x%lx size 0x%zx\n", iova, size); + + /* find out the minimum page size supported */ + min_pagesz = 1U << __ffs(mmu_info->pgsize_bitmap); + + /* + * The virtual address and the size of the mapping must be + * aligned (at least) to the size of the smallest page supported + * by the hardware + */ + if (!IS_ALIGNED(iova | size, min_pagesz)) { + dev_err(mmu_info->dev, + "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n", + iova, size, min_pagesz); + return; + } + + __ipu7_mmu_unmap(mmu_info, iova, size); +} + +int ipu7_mmu_map(struct ipu7_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size) +{ + unsigned int min_pagesz; + + if (mmu_info->pgsize_bitmap == 0UL) + return -ENODEV; + + /* find out the minimum page size supported */ + min_pagesz = 1U << __ffs(mmu_info->pgsize_bitmap); + + /* + * both the virtual address and the physical one, as well as + * the size of the mapping, must be aligned (at least) to the + * size of the smallest page supported by the hardware + */ + if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) { + dev_err(mmu_info->dev, + "unaligned: iova %lx pa %pa size %zx min_pagesz %x\n", + iova, &paddr, size, min_pagesz); + return -EINVAL; + } + + dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n", + iova, &paddr, size); + + return __ipu7_mmu_map(mmu_info, iova, paddr, size); +} + +static void ipu7_mmu_destroy(struct ipu7_mmu *mmu) +{ + struct ipu7_dma_mapping *dmap = mmu->dmap; + struct ipu7_mmu_info *mmu_info = dmap->mmu_info; + struct iova *iova; + u32 l1_idx; + + if (mmu->iova_trash_page) { + iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page)); + if (iova) { + /* unmap and free the trash buffer iova */ + ipu7_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo), + PFN_PHYS(iova_size(iova))); + __free_iova(&dmap->iovad, iova); + } else { + dev_err(mmu->dev, "trash buffer iova not found.\n"); + } + + mmu->iova_trash_page = 0; + dma_unmap_page(mmu_info->dev, mmu->pci_trash_page, + PAGE_SIZE, DMA_BIDIRECTIONAL); + mmu->pci_trash_page = 0; + __free_page(mmu->trash_page); + } + + for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { + if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) { + dma_unmap_single(mmu_info->dev, + TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->l2_pts[l1_idx]); + } + } + + vfree(mmu_info->l2_pts); + free_dummy_page(mmu_info); + dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma), + PAGE_SIZE, DMA_BIDIRECTIONAL); + free_page((unsigned long)mmu_info->dummy_l2_pt); + free_page((unsigned long)mmu_info->l1_pt); + kfree(mmu_info); +} + +struct ipu7_mmu *ipu7_mmu_init(struct device *dev, + void __iomem *base, int mmid, + const struct ipu7_hw_variants *hw) +{ + struct ipu7_device *isp = pci_get_drvdata(to_pci_dev(dev)); + struct ipu7_mmu_pdata *pdata; + struct ipu7_mmu *mmu; + unsigned int i; + + if (hw->nr_mmus > IPU_MMU_MAX_NUM) + return ERR_PTR(-EINVAL); + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < hw->nr_mmus; i++) { + struct ipu7_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; + const struct ipu7_mmu_hw *src_mmu = &hw->mmu_hw[i]; + + if (src_mmu->nr_l1streams > IPU_MMU_MAX_TLB_L1_STREAMS || + src_mmu->nr_l2streams > IPU_MMU_MAX_TLB_L2_STREAMS) + return ERR_PTR(-EINVAL); + + *pdata_mmu = *src_mmu; + pdata_mmu->base = base + src_mmu->offset; + pdata_mmu->zlx_base = base + src_mmu->zlx_offset; + pdata_mmu->uao_base = base + src_mmu->uao_offset; + } + + mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); + if (!mmu) + return ERR_PTR(-ENOMEM); + + mmu->mmid = mmid; + mmu->mmu_hw = pdata->mmu_hw; + mmu->nr_mmus = hw->nr_mmus; + mmu->tlb_invalidate = tlb_invalidate; + mmu->ready = false; + INIT_LIST_HEAD(&mmu->vma_list); + spin_lock_init(&mmu->ready_lock); + + mmu->dmap = alloc_dma_mapping(isp); + if (!mmu->dmap) { + dev_err(dev, "can't alloc dma mapping\n"); + return ERR_PTR(-ENOMEM); + } + + return mmu; +} + +void ipu7_mmu_cleanup(struct ipu7_mmu *mmu) +{ + struct ipu7_dma_mapping *dmap = mmu->dmap; + + ipu7_mmu_destroy(mmu); + mmu->dmap = NULL; + iova_cache_put(); + put_iova_domain(&dmap->iovad); + kfree(dmap); +} diff --git a/drivers/staging/media/ipu7/ipu7-mmu.h b/drivers/staging/media/ipu7/ipu7-mmu.h new file mode 100644 index 000000000000..d85bb8ffc711 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-mmu.h @@ -0,0 +1,414 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_MMU_H +#define IPU7_MMU_H + +#include +#include +#include +#include + +struct device; +struct page; +struct ipu7_hw_variants; +struct ipu7_mmu; +struct ipu7_mmu_info; + +#define ISYS_MMID 0x1 +#define PSYS_MMID 0x0 + +/* IPU7 for LNL */ +/* IS MMU Cmd RD */ +#define IPU7_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +/* IS MMU Cmd WR */ +#define IPU7_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +/* IS MMU Data WR Snoop */ +#define IPU7_IS_MMU_M0_OFFSET 0x276000 +#define IPU7_IS_MMU_M0_STREAM_NUM 8 +#define IPU7_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M0_L2_BLOCKNR_REG 0x74 + +/* IS MMU Data WR ISOC */ +#define IPU7_IS_MMU_M1_OFFSET 0x277000 +#define IPU7_IS_MMU_M1_STREAM_NUM 16 +#define IPU7_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW RD */ +#define IPU7_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7_PS_MMU_FW_RD_STREAM_NUM 20 +#define IPU7_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_RD_L2_BLOCKNR_REG 0xa4 + +/* PS MMU FW WR */ +#define IPU7_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +/* PS MMU FW Data RD VC0 */ +#define IPU7_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7_PS_MMU_SRT_RD_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xf4 + +/* PS MMU FW Data WR VC0 */ +#define IPU7_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7_PS_MMU_SRT_WR_STREAM_NUM 40 +#define IPU7_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xf4 + +/* IS UAO UC RD */ +#define IPU7_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7_IS_UAO_UC_RD_PLANENUM 4 + +/* IS UAO UC WR */ +#define IPU7_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7_IS_UAO_UC_WR_PLANENUM 4 + +/* IS UAO M0 WR */ +#define IPU7_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7_IS_UAO_M0_WR_PLANENUM 8 + +/* IS UAO M1 WR */ +#define IPU7_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7_IS_UAO_M1_WR_PLANENUM 16 + +/* PS UAO FW RD */ +#define IPU7_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7_PS_UAO_FW_RD_PLANENUM 20 + +/* PS UAO FW WR */ +#define IPU7_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7_PS_UAO_FW_WR_PLANENUM 16 + +/* PS UAO SRT RD */ +#define IPU7_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7_PS_UAO_SRT_RD_PLANENUM 40 + +/* PS UAO SRT WR */ +#define IPU7_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7_PS_UAO_SRT_WR_PLANENUM 40 + +#define IPU7_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7_IS_ZLX_UC_RD_NUM 4 +#define IPU7_IS_ZLX_UC_WR_NUM 4 +#define IPU7_IS_ZLX_M0_NUM 8 +#define IPU7_IS_ZLX_M1_NUM 16 + +#define IPU7_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7_PS_ZLX_DATA_RD_NUM 32 +#define IPU7_PS_ZLX_DATA_WR_NUM 32 +#define IPU7_PS_ZLX_FW_RD_NUM 16 +#define IPU7_PS_ZLX_FW_WR_NUM 10 + +/* IPU7P5 for PTL */ +/* IS MMU Cmd RD */ +#define IPU7P5_IS_MMU_FW_RD_OFFSET 0x274000 +#define IPU7P5_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +/* IS MMU Cmd WR */ +#define IPU7P5_IS_MMU_FW_WR_OFFSET 0x275000 +#define IPU7P5_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU7P5_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +/* IS MMU Data WR Snoop */ +#define IPU7P5_IS_MMU_M0_OFFSET 0x276000 +#define IPU7P5_IS_MMU_M0_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M0_L2_BLOCKNR_REG 0x94 + +/* IS MMU Data WR ISOC */ +#define IPU7P5_IS_MMU_M1_OFFSET 0x277000 +#define IPU7P5_IS_MMU_M1_STREAM_NUM 16 +#define IPU7P5_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU7P5_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW RD */ +#define IPU7P5_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU7P5_PS_MMU_FW_RD_STREAM_NUM 16 +#define IPU7P5_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_RD_L2_BLOCKNR_REG 0x94 + +/* PS MMU FW WR */ +#define IPU7P5_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU7P5_PS_MMU_FW_WR_STREAM_NUM 10 +#define IPU7P5_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x7c + +/* PS MMU FW Data RD VC0 */ +#define IPU7P5_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU7P5_PS_MMU_SRT_RD_STREAM_NUM 22 +#define IPU7P5_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xac + +/* PS MMU FW Data WR VC0 */ +#define IPU7P5_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU7P5_PS_MMU_SRT_WR_STREAM_NUM 32 +#define IPU7P5_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU7P5_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xd4 + +/* IS UAO UC RD */ +#define IPU7P5_IS_UAO_UC_RD_OFFSET 0x27c000 +#define IPU7P5_IS_UAO_UC_RD_PLANENUM 4 + +/* IS UAO UC WR */ +#define IPU7P5_IS_UAO_UC_WR_OFFSET 0x27d000 +#define IPU7P5_IS_UAO_UC_WR_PLANENUM 4 + +/* IS UAO M0 WR */ +#define IPU7P5_IS_UAO_M0_WR_OFFSET 0x27e000 +#define IPU7P5_IS_UAO_M0_WR_PLANENUM 16 + +/* IS UAO M1 WR */ +#define IPU7P5_IS_UAO_M1_WR_OFFSET 0x27f000 +#define IPU7P5_IS_UAO_M1_WR_PLANENUM 16 + +/* PS UAO FW RD */ +#define IPU7P5_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU7P5_PS_UAO_FW_RD_PLANENUM 16 + +/* PS UAO FW WR */ +#define IPU7P5_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU7P5_PS_UAO_FW_WR_PLANENUM 10 + +/* PS UAO SRT RD */ +#define IPU7P5_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU7P5_PS_UAO_SRT_RD_PLANENUM 22 + +/* PS UAO SRT WR */ +#define IPU7P5_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU7P5_PS_UAO_SRT_WR_PLANENUM 32 + +#define IPU7P5_IS_ZLX_UC_RD_OFFSET 0x278000 +#define IPU7P5_IS_ZLX_UC_WR_OFFSET 0x279000 +#define IPU7P5_IS_ZLX_M0_OFFSET 0x27a000 +#define IPU7P5_IS_ZLX_M1_OFFSET 0x27b000 +#define IPU7P5_IS_ZLX_UC_RD_NUM 4 +#define IPU7P5_IS_ZLX_UC_WR_NUM 4 +#define IPU7P5_IS_ZLX_M0_NUM 16 +#define IPU7P5_IS_ZLX_M1_NUM 16 + +#define IPU7P5_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU7P5_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU7P5_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU7P5_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU7P5_PS_ZLX_DATA_RD_NUM 22 +#define IPU7P5_PS_ZLX_DATA_WR_NUM 32 +#define IPU7P5_PS_ZLX_FW_RD_NUM 16 +#define IPU7P5_PS_ZLX_FW_WR_NUM 10 + +/* IS MMU Cmd RD */ +#define IPU8_IS_MMU_FW_RD_OFFSET 0x270000 +#define IPU8_IS_MMU_FW_RD_STREAM_NUM 3 +#define IPU8_IS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU8_IS_MMU_FW_RD_L2_BLOCKNR_REG 0x60 + +/* IS MMU Cmd WR */ +#define IPU8_IS_MMU_FW_WR_OFFSET 0x271000 +#define IPU8_IS_MMU_FW_WR_STREAM_NUM 3 +#define IPU8_IS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU8_IS_MMU_FW_WR_L2_BLOCKNR_REG 0x60 + +/* IS MMU Data WR Snoop */ +#define IPU8_IS_MMU_M0_OFFSET 0x272000 +#define IPU8_IS_MMU_M0_STREAM_NUM 16 +#define IPU8_IS_MMU_M0_L1_BLOCKNR_REG 0x54 +#define IPU8_IS_MMU_M0_L2_BLOCKNR_REG 0x94 + +/* IS MMU Data WR ISOC */ +#define IPU8_IS_MMU_M1_OFFSET 0x273000 +#define IPU8_IS_MMU_M1_STREAM_NUM 16 +#define IPU8_IS_MMU_M1_L1_BLOCKNR_REG 0x54 +#define IPU8_IS_MMU_M1_L2_BLOCKNR_REG 0x94 + +/* IS MMU UPIPE ISOC */ +#define IPU8_IS_MMU_UPIPE_OFFSET 0x274000 +#define IPU8_IS_MMU_UPIPE_STREAM_NUM 6 +#define IPU8_IS_MMU_UPIPE_L1_BLOCKNR_REG 0x54 +#define IPU8_IS_MMU_UPIPE_L2_BLOCKNR_REG 0x6c + +/* PS MMU FW RD */ +#define IPU8_PS_MMU_FW_RD_OFFSET 0x148000 +#define IPU8_PS_MMU_FW_RD_STREAM_NUM 12 +#define IPU8_PS_MMU_FW_RD_L1_BLOCKNR_REG 0x54 +#define IPU8_PS_MMU_FW_RD_L2_BLOCKNR_REG 0x84 + +/* PS MMU FW WR */ +#define IPU8_PS_MMU_FW_WR_OFFSET 0x149000 +#define IPU8_PS_MMU_FW_WR_STREAM_NUM 8 +#define IPU8_PS_MMU_FW_WR_L1_BLOCKNR_REG 0x54 +#define IPU8_PS_MMU_FW_WR_L2_BLOCKNR_REG 0x74 + +/* PS MMU FW Data RD VC0 */ +#define IPU8_PS_MMU_SRT_RD_OFFSET 0x14a000 +#define IPU8_PS_MMU_SRT_RD_STREAM_NUM 26 +#define IPU8_PS_MMU_SRT_RD_L1_BLOCKNR_REG 0x54 +#define IPU8_PS_MMU_SRT_RD_L2_BLOCKNR_REG 0xbc + +/* PS MMU FW Data WR VC0 */ +#define IPU8_PS_MMU_SRT_WR_OFFSET 0x14b000 +#define IPU8_PS_MMU_SRT_WR_STREAM_NUM 26 +#define IPU8_PS_MMU_SRT_WR_L1_BLOCKNR_REG 0x54 +#define IPU8_PS_MMU_SRT_WR_L2_BLOCKNR_REG 0xbc + +/* IS UAO UC RD */ +#define IPU8_IS_UAO_UC_RD_OFFSET 0x27a000 +#define IPU8_IS_UAO_UC_RD_PLANENUM 4 + +/* IS UAO UC WR */ +#define IPU8_IS_UAO_UC_WR_OFFSET 0x27b000 +#define IPU8_IS_UAO_UC_WR_PLANENUM 4 + +/* IS UAO M0 WR */ +#define IPU8_IS_UAO_M0_WR_OFFSET 0x27c000 +#define IPU8_IS_UAO_M0_WR_PLANENUM 16 + +/* IS UAO M1 WR */ +#define IPU8_IS_UAO_M1_WR_OFFSET 0x27d000 +#define IPU8_IS_UAO_M1_WR_PLANENUM 16 + +/* IS UAO UPIPE */ +#define IPU8_IS_UAO_UPIPE_OFFSET 0x27e000 +#define IPU8_IS_UAO_UPIPE_PLANENUM 6 + +/* PS UAO FW RD */ +#define IPU8_PS_UAO_FW_RD_OFFSET 0x156000 +#define IPU8_PS_UAO_FW_RD_PLANENUM 12 + +/* PS UAO FW WR */ +#define IPU8_PS_UAO_FW_WR_OFFSET 0x157000 +#define IPU8_PS_UAO_FW_WR_PLANENUM 8 + +/* PS UAO SRT RD */ +#define IPU8_PS_UAO_SRT_RD_OFFSET 0x154000 +#define IPU8_PS_UAO_SRT_RD_PLANENUM 26 + +/* PS UAO SRT WR */ +#define IPU8_PS_UAO_SRT_WR_OFFSET 0x155000 +#define IPU8_PS_UAO_SRT_WR_PLANENUM 26 + +#define IPU8_IS_ZLX_UC_RD_OFFSET 0x275000 +#define IPU8_IS_ZLX_UC_WR_OFFSET 0x276000 +#define IPU8_IS_ZLX_M0_OFFSET 0x277000 +#define IPU8_IS_ZLX_M1_OFFSET 0x278000 +#define IPU8_IS_ZLX_UPIPE_OFFSET 0x279000 +#define IPU8_IS_ZLX_UC_RD_NUM 4 +#define IPU8_IS_ZLX_UC_WR_NUM 4 +#define IPU8_IS_ZLX_M0_NUM 16 +#define IPU8_IS_ZLX_M1_NUM 16 +#define IPU8_IS_ZLX_UPIPE_NUM 6 + +#define IPU8_PS_ZLX_DATA_RD_OFFSET 0x14e000 +#define IPU8_PS_ZLX_DATA_WR_OFFSET 0x14f000 +#define IPU8_PS_ZLX_FW_RD_OFFSET 0x150000 +#define IPU8_PS_ZLX_FW_WR_OFFSET 0x151000 +#define IPU8_PS_ZLX_DATA_RD_NUM 26 +#define IPU8_PS_ZLX_DATA_WR_NUM 26 +#define IPU8_PS_ZLX_FW_RD_NUM 12 +#define IPU8_PS_ZLX_FW_WR_NUM 8 + +#define MMU_REG_INVALIDATE_0 0x00 +#define MMU_REG_INVALIDATE_1 0x04 +#define MMU_REG_PAGE_TABLE_BASE_ADDR 0x08 +#define MMU_REG_USER_INFO_BITS 0x0c +#define MMU_REG_AXI_REFILL_IF_ID 0x10 +#define MMU_REG_PW_EN_BITMAP 0x14 +#define MMU_REG_COLLAPSE_ENABLE_BITMAP 0x18 +#define MMU_REG_GENERAL_REG 0x1c +#define MMU_REG_AT_SP_ARB_CFG 0x20 +#define MMU_REG_INVALIDATION_STATUS 0x24 +#define MMU_REG_IRQ_LEVEL_NO_PULSE 0x28 +#define MMU_REG_IRQ_MASK 0x2c +#define MMU_REG_IRQ_ENABLE 0x30 +#define MMU_REG_IRQ_EDGE 0x34 +#define MMU_REG_IRQ_CLEAR 0x38 +#define MMU_REG_IRQ_CAUSE 0x3c +#define MMU_REG_CG_CTRL_BITS 0x40 +#define MMU_REG_RD_FIFOS_STATUS 0x44 +#define MMU_REG_WR_FIFOS_STATUS 0x48 +#define MMU_REG_COMMON_FIFOS_STATUS 0x4c +#define MMU_REG_FSM_STATUS 0x50 + +#define ZLX_REG_AXI_POOL 0x0 +#define ZLX_REG_EN 0x20 +#define ZLX_REG_CONF 0x24 +#define ZLX_REG_CG_CTRL 0x900 +#define ZLX_REG_FORCE_BYPASS 0x904 + +struct ipu7_mmu_info { + struct device *dev; + + u32 *l1_pt; + u32 l1_pt_dma; + u32 **l2_pts; + + u32 *dummy_l2_pt; + u32 dummy_l2_pteval; + void *dummy_page; + u32 dummy_page_pteval; + + dma_addr_t aperture_start; + dma_addr_t aperture_end; + unsigned long pgsize_bitmap; + + spinlock_t lock; /* Serialize access to users */ + struct ipu7_dma_mapping *dmap; +}; + +struct ipu7_mmu { + struct list_head node; + + struct ipu7_mmu_hw *mmu_hw; + unsigned int nr_mmus; + unsigned int mmid; + + phys_addr_t pgtbl; + struct device *dev; + + struct ipu7_dma_mapping *dmap; + struct list_head vma_list; + + struct page *trash_page; + dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */ + dma_addr_t iova_trash_page; /* IOVA for IPU child nodes to use */ + + bool ready; + spinlock_t ready_lock; /* Serialize access to bool ready */ + + void (*tlb_invalidate)(struct ipu7_mmu *mmu); +}; + +struct ipu7_mmu *ipu7_mmu_init(struct device *dev, + void __iomem *base, int mmid, + const struct ipu7_hw_variants *hw); +void ipu7_mmu_cleanup(struct ipu7_mmu *mmu); +int ipu7_mmu_hw_init(struct ipu7_mmu *mmu); +void ipu7_mmu_hw_cleanup(struct ipu7_mmu *mmu); +int ipu7_mmu_map(struct ipu7_mmu_info *mmu_info, unsigned long iova, + phys_addr_t paddr, size_t size); +void ipu7_mmu_unmap(struct ipu7_mmu_info *mmu_info, unsigned long iova, + size_t size); +phys_addr_t ipu7_mmu_iova_to_phys(struct ipu7_mmu_info *mmu_info, + dma_addr_t iova); +#endif From cc5de519299d35fe34905303186abd8cac252163 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:19 +0800 Subject: [PATCH 214/310] media: staging/ipu7: add firmware parse, syscom interface and boot IPU7 irmware is generated and released as signed Code Partition Directory (CPD) format file which is aligned with the SPI flash code partition definition. The driver parses the CPD based on the layout. Syscom is an inter-process(or) communication mechanism between an IPU and host. Syscom uses message queues for message exchange between IPU and host. Each message queue has its consumer and producer, host queue messages to firmware as the producer and then firmware to dequeue the messages as consumer and vice versa. IPU and host use shared registers or memory to reside the read and write indices which are updated by consumer and producer. IPU7 firmware defined its boot sequence, driver setup the boot configuration and program the boot parameters to start and run the firmware. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-boot.c | 430 +++++++++++++++++++++++ drivers/staging/media/ipu7/ipu7-boot.h | 25 ++ drivers/staging/media/ipu7/ipu7-cpd.c | 276 +++++++++++++++ drivers/staging/media/ipu7/ipu7-cpd.h | 16 + drivers/staging/media/ipu7/ipu7-syscom.c | 78 ++++ drivers/staging/media/ipu7/ipu7-syscom.h | 35 ++ 6 files changed, 860 insertions(+) create mode 100644 drivers/staging/media/ipu7/ipu7-boot.c create mode 100644 drivers/staging/media/ipu7/ipu7-boot.h create mode 100644 drivers/staging/media/ipu7/ipu7-cpd.c create mode 100644 drivers/staging/media/ipu7/ipu7-cpd.h create mode 100644 drivers/staging/media/ipu7/ipu7-syscom.c create mode 100644 drivers/staging/media/ipu7/ipu7-syscom.h diff --git a/drivers/staging/media/ipu7/ipu7-boot.c b/drivers/staging/media/ipu7/ipu7-boot.c new file mode 100644 index 000000000000..d7901ff78b38 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-boot.c @@ -0,0 +1,430 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "abi/ipu7_fw_boot_abi.h" + +#include "ipu7.h" +#include "ipu7-boot.h" +#include "ipu7-bus.h" +#include "ipu7-buttress-regs.h" +#include "ipu7-dma.h" +#include "ipu7-platform-regs.h" +#include "ipu7-syscom.h" + +#define IPU_FW_START_STOP_TIMEOUT 2000 +#define IPU_BOOT_CELL_RESET_TIMEOUT (2 * USEC_PER_SEC) +#define BOOT_STATE_IS_CRITICAL(s) IA_GOFO_FW_BOOT_STATE_IS_CRITICAL(s) +#define BOOT_STATE_IS_READY(s) ((s) == IA_GOFO_FW_BOOT_STATE_READY) +#define BOOT_STATE_IS_INACTIVE(s) ((s) == IA_GOFO_FW_BOOT_STATE_INACTIVE) + +struct ipu7_boot_context { + u32 base; + u32 dmem_address; + u32 status_ctrl_reg; + u32 fw_start_address_reg; + u32 fw_code_base_reg; +}; + +static const struct ipu7_boot_context contexts[IPU_SUBSYS_NUM] = { + { + /* ISYS */ + .dmem_address = IPU_ISYS_DMEM_OFFSET, + .status_ctrl_reg = BUTTRESS_REG_DRV_IS_UCX_CONTROL_STATUS, + .fw_start_address_reg = BUTTRESS_REG_DRV_IS_UCX_START_ADDR, + .fw_code_base_reg = IS_UC_CTRL_BASE + }, + { + /* PSYS */ + .dmem_address = IPU_PSYS_DMEM_OFFSET, + .status_ctrl_reg = BUTTRESS_REG_DRV_PS_UCX_CONTROL_STATUS, + .fw_start_address_reg = BUTTRESS_REG_DRV_PS_UCX_START_ADDR, + .fw_code_base_reg = PS_UC_CTRL_BASE + } +}; + +static u32 get_fw_boot_reg_addr(const struct ipu7_bus_device *adev, + enum ia_gofo_buttress_reg_id reg) +{ + u32 base = (adev->subsys == IPU_IS) ? 0U : (u32)IA_GOFO_FW_BOOT_ID_MAX; + + return BUTTRESS_FW_BOOT_PARAMS_ENTRY(base + (u32)reg); +} + +static void write_fw_boot_param(const struct ipu7_bus_device *adev, + enum ia_gofo_buttress_reg_id reg, + u32 val) +{ + void __iomem *base = adev->isp->base; + + dev_dbg(&adev->auxdev.dev, + "write boot param reg: %d addr: %x val: 0x%x\n", + reg, get_fw_boot_reg_addr(adev, reg), val); + writel(val, base + get_fw_boot_reg_addr(adev, reg)); +} + +static u32 read_fw_boot_param(const struct ipu7_bus_device *adev, + enum ia_gofo_buttress_reg_id reg) +{ + void __iomem *base = adev->isp->base; + + return readl(base + get_fw_boot_reg_addr(adev, reg)); +} + +static int ipu7_boot_cell_reset(const struct ipu7_bus_device *adev) +{ + const struct ipu7_boot_context *ctx = &contexts[adev->subsys]; + const struct device *dev = &adev->auxdev.dev; + u32 ucx_ctrl_status = ctx->status_ctrl_reg; + u32 timeout = IPU_BOOT_CELL_RESET_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 val, val2; + int ret; + + dev_dbg(dev, "cell enter reset...\n"); + val = readl(base + ucx_ctrl_status); + dev_dbg(dev, "cell_ctrl_reg addr = 0x%x, val = 0x%x\n", + ucx_ctrl_status, val); + + dev_dbg(dev, "force cell reset...\n"); + val |= UCX_CTL_RESET; + val &= ~UCX_CTL_RUN; + + dev_dbg(dev, "write status_ctrl_reg(0x%x) to 0x%x\n", + ucx_ctrl_status, val); + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell enter reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + dev_dbg(dev, "cell exit reset...\n"); + val = readl(base + ucx_ctrl_status); + WARN((!(val & UCX_CTL_RESET) || val & UCX_CTL_RUN), + "cell status 0x%x", val); + + val &= ~(UCX_CTL_RESET | UCX_CTL_RUN); + dev_dbg(dev, "write status_ctrl_reg(0x%x) to 0x%x\n", + ucx_ctrl_status, val); + writel(val, base + ucx_ctrl_status); + + ret = readl_poll_timeout(base + ucx_ctrl_status, val2, + (val2 & 0x3U) == (val & 0x3U), 100, timeout); + if (ret) { + dev_err(dev, "cell exit reset timeout. status: 0x%x\n", val2); + return -ETIMEDOUT; + } + + return 0; +} + +static void ipu7_boot_cell_start(const struct ipu7_bus_device *adev) +{ + const struct ipu7_boot_context *ctx = &contexts[adev->subsys]; + void __iomem *base = adev->isp->base; + const struct device *dev = &adev->auxdev.dev; + u32 val; + + dev_dbg(dev, "starting cell...\n"); + val = readl(base + ctx->status_ctrl_reg); + WARN_ON(val & (UCX_CTL_RESET | UCX_CTL_RUN)); + + val &= ~UCX_CTL_RESET; + val |= UCX_CTL_RUN; + dev_dbg(dev, "write status_ctrl_reg(0x%x) to 0x%x\n", + ctx->status_ctrl_reg, val); + writel(val, base + ctx->status_ctrl_reg); +} + +static void ipu7_boot_cell_stop(const struct ipu7_bus_device *adev) +{ + const struct ipu7_boot_context *ctx = &contexts[adev->subsys]; + void __iomem *base = adev->isp->base; + const struct device *dev = &adev->auxdev.dev; + u32 val; + + dev_dbg(dev, "stopping cell...\n"); + + val = readl(base + ctx->status_ctrl_reg); + val &= ~UCX_CTL_RUN; + dev_dbg(dev, "write status_ctrl_reg(0x%x) to 0x%x\n", + ctx->status_ctrl_reg, val); + writel(val, base + ctx->status_ctrl_reg); + + /* Wait for uC transactions complete */ + usleep_range(10, 20); + + val = readl(base + ctx->status_ctrl_reg); + val |= UCX_CTL_RESET; + dev_dbg(dev, "write status_ctrl_reg(0x%x) to 0x%x\n", + ctx->status_ctrl_reg, val); + writel(val, base + ctx->status_ctrl_reg); +} + +static int ipu7_boot_cell_init(const struct ipu7_bus_device *adev) +{ + const struct ipu7_boot_context *ctx = &contexts[adev->subsys]; + void __iomem *base = adev->isp->base; + + dev_dbg(&adev->auxdev.dev, "write fw_start_address_reg(0x%x) to 0x%x\n", + ctx->fw_start_address_reg, adev->fw_entry); + writel(adev->fw_entry, base + ctx->fw_start_address_reg); + + return ipu7_boot_cell_reset(adev); +} + +static void init_boot_config(struct ia_gofo_boot_config *boot_config, + u32 length, u8 major) +{ + /* syscom version, new syscom2 version */ + boot_config->length = length; + boot_config->config_version.major = 1U; + boot_config->config_version.minor = 0U; + boot_config->config_version.subminor = 0U; + boot_config->config_version.patch = 0U; + + /* msg version for task interface */ + boot_config->client_version_support.num_versions = 1U; + boot_config->client_version_support.versions[0].major = major; + boot_config->client_version_support.versions[0].minor = 0U; + boot_config->client_version_support.versions[0].subminor = 0U; + boot_config->client_version_support.versions[0].patch = 0U; +} + +int ipu7_boot_init_boot_config(struct ipu7_bus_device *adev, + struct syscom_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major) +{ + u32 total_queue_size_aligned = 0; + struct ipu7_syscom_context *syscom = adev->syscom; + struct ia_gofo_boot_config *boot_config; + struct syscom_queue_params_config *cfgs; + struct device *dev = &adev->auxdev.dev; + struct syscom_config_s *syscfg; + dma_addr_t queue_mem_dma_ptr; + void *queue_mem_ptr; + unsigned int i; + + dev_dbg(dev, "boot config queues_nr: %d freq: %u sys_conf: 0x%pad\n", + num_queues, uc_freq, &subsys_config); + /* Allocate boot config. */ + adev->boot_config_size = + sizeof(*cfgs) * num_queues + sizeof(*boot_config); + adev->boot_config = ipu7_dma_alloc(adev, adev->boot_config_size, + &adev->boot_config_dma_addr, + GFP_KERNEL, 0); + if (!adev->boot_config) { + dev_err(dev, "Failed to allocate boot config.\n"); + return -ENOMEM; + } + + boot_config = adev->boot_config; + memset(boot_config, 0, sizeof(struct ia_gofo_boot_config)); + init_boot_config(boot_config, adev->boot_config_size, major); + boot_config->subsys_config = subsys_config; + + boot_config->uc_tile_frequency = uc_freq; + boot_config->uc_tile_frequency_units = + IA_GOFO_FW_BOOT_UC_FREQUENCY_UNITS_MHZ; + boot_config->syscom_context_config.max_output_queues = + syscom->num_output_queues; + boot_config->syscom_context_config.max_input_queues = + syscom->num_input_queues; + + ipu7_dma_sync_single(adev, adev->boot_config_dma_addr, + adev->boot_config_size); + + for (i = 0; i < num_queues; i++) { + u32 queue_size = qconfigs[i].max_capacity * + qconfigs[i].token_size_in_bytes; + + queue_size = ALIGN(queue_size, 64U); + total_queue_size_aligned += queue_size; + qconfigs[i].queue_size = queue_size; + } + + /* Allocate queue memory */ + syscom->queue_mem = ipu7_dma_alloc(adev, total_queue_size_aligned, + &syscom->queue_mem_dma_addr, + GFP_KERNEL, 0); + if (!syscom->queue_mem) { + dev_err(dev, "Failed to allocate queue memory.\n"); + return -ENOMEM; + } + syscom->queue_mem_size = total_queue_size_aligned; + + syscfg = &boot_config->syscom_context_config; + cfgs = ipu7_syscom_get_queue_config(syscfg); + queue_mem_ptr = syscom->queue_mem; + queue_mem_dma_ptr = syscom->queue_mem_dma_addr; + for (i = 0; i < num_queues; i++) { + cfgs[i].token_array_mem = queue_mem_dma_ptr; + cfgs[i].max_capacity = qconfigs[i].max_capacity; + cfgs[i].token_size_in_bytes = qconfigs[i].token_size_in_bytes; + qconfigs[i].token_array_mem = queue_mem_ptr; + queue_mem_dma_ptr += qconfigs[i].queue_size; + queue_mem_ptr += qconfigs[i].queue_size; + } + + ipu7_dma_sync_single(adev, syscom->queue_mem_dma_addr, + total_queue_size_aligned); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_boot_init_boot_config, "INTEL_IPU7"); + +void ipu7_boot_release_boot_config(struct ipu7_bus_device *adev) +{ + struct ipu7_syscom_context *syscom = adev->syscom; + + if (syscom->queue_mem) { + ipu7_dma_free(adev, syscom->queue_mem_size, + syscom->queue_mem, + syscom->queue_mem_dma_addr, 0); + syscom->queue_mem = NULL; + syscom->queue_mem_dma_addr = 0; + } + + if (adev->boot_config) { + ipu7_dma_free(adev, adev->boot_config_size, + adev->boot_config, + adev->boot_config_dma_addr, 0); + adev->boot_config = NULL; + adev->boot_config_dma_addr = 0; + } +} +EXPORT_SYMBOL_NS_GPL(ipu7_boot_release_boot_config, "INTEL_IPU7"); + +int ipu7_boot_start_fw(const struct ipu7_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + u32 timeout = IPU_FW_START_STOP_TIMEOUT; + void __iomem *base = adev->isp->base; + u32 boot_state, last_boot_state; + u32 indices_addr, msg_ver, id; + int ret; + + ret = ipu7_boot_cell_init(adev); + if (ret) + return ret; + + dev_dbg(dev, "start booting fw...\n"); + /* store "uninit" state to syscom/boot state reg */ + write_fw_boot_param(adev, IA_GOFO_FW_BOOT_STATE_ID, + IA_GOFO_FW_BOOT_STATE_UNINIT); + /* + * Set registers to zero + * (not strictly required, but recommended for diagnostics) + */ + write_fw_boot_param(adev, + IA_GOFO_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID, 0); + write_fw_boot_param(adev, IA_GOFO_FW_BOOT_MESSAGING_VERSION_ID, 0); + /* store firmware configuration address */ + write_fw_boot_param(adev, IA_GOFO_FW_BOOT_CONFIG_ID, + adev->boot_config_dma_addr); + + /* Kick uC, then wait for boot complete */ + ipu7_boot_cell_start(adev); + + last_boot_state = IA_GOFO_FW_BOOT_STATE_UNINIT; + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IA_GOFO_FW_BOOT_STATE_ID); + if (boot_state != last_boot_state) { + dev_dbg(dev, "boot state changed from 0x%x to 0x%x\n", + last_boot_state, boot_state); + last_boot_state = boot_state; + } + if (BOOT_STATE_IS_CRITICAL(boot_state) || + BOOT_STATE_IS_READY(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (BOOT_STATE_IS_CRITICAL(boot_state)) { + ipu7_dump_fw_error_log(adev); + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!BOOT_STATE_IS_READY(boot_state)) { + dev_err(dev, "fw boot timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + dev_dbg(dev, "fw boot done.\n"); + + /* Get FW syscom queue indices addr */ + id = IA_GOFO_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID; + indices_addr = read_fw_boot_param(adev, id); + adev->syscom->queue_indices = base + indices_addr; + dev_dbg(dev, "fw queue indices offset is 0x%x\n", indices_addr); + + /* Get message version. */ + msg_ver = read_fw_boot_param(adev, + IA_GOFO_FW_BOOT_MESSAGING_VERSION_ID); + dev_dbg(dev, "ipu message version is 0x%08x\n", msg_ver); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_boot_start_fw, "INTEL_IPU7"); + +int ipu7_boot_stop_fw(const struct ipu7_bus_device *adev) +{ + const struct device *dev = &adev->auxdev.dev; + u32 timeout = IPU_FW_START_STOP_TIMEOUT; + u32 boot_state; + + boot_state = read_fw_boot_param(adev, IA_GOFO_FW_BOOT_STATE_ID); + if (BOOT_STATE_IS_CRITICAL(boot_state) || + !BOOT_STATE_IS_READY(boot_state)) { + dev_err(dev, "fw not ready for shutdown, state 0x%x\n", + boot_state); + return -EBUSY; + } + + /* Issue shutdown to start shutdown process */ + dev_dbg(dev, "stopping fw...\n"); + write_fw_boot_param(adev, IA_GOFO_FW_BOOT_STATE_ID, + IA_GOFO_FW_BOOT_STATE_SHUTDOWN_CMD); + while (timeout--) { + boot_state = read_fw_boot_param(adev, + IA_GOFO_FW_BOOT_STATE_ID); + if (BOOT_STATE_IS_CRITICAL(boot_state) || + BOOT_STATE_IS_INACTIVE(boot_state)) + break; + usleep_range(1000, 1200); + } + + if (BOOT_STATE_IS_CRITICAL(boot_state)) { + ipu7_dump_fw_error_log(adev); + dev_err(dev, "critical boot state error 0x%x\n", boot_state); + return -EINVAL; + } else if (!BOOT_STATE_IS_INACTIVE(boot_state)) { + dev_err(dev, "stop fw timeout. state: 0x%x\n", boot_state); + return -ETIMEDOUT; + } + + ipu7_boot_cell_stop(adev); + dev_dbg(dev, "stop fw done.\n"); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_boot_stop_fw, "INTEL_IPU7"); + +u32 ipu7_boot_get_boot_state(const struct ipu7_bus_device *adev) +{ + return read_fw_boot_param(adev, IA_GOFO_FW_BOOT_STATE_ID); +} +EXPORT_SYMBOL_NS_GPL(ipu7_boot_get_boot_state, "INTEL_IPU7"); diff --git a/drivers/staging/media/ipu7/ipu7-boot.h b/drivers/staging/media/ipu7/ipu7-boot.h new file mode 100644 index 000000000000..5600be849931 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-boot.h @@ -0,0 +1,25 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2022 - 2025 Intel Corporation + */ + +#ifndef IPU7_BOOT_H +#define IPU7_BOOT_H + +#include + +struct ipu7_bus_device; +struct syscom_queue_config; + +#define FW_QUEUE_CONFIG_SIZE(num_queues) \ + (sizeof(struct syscom_queue_config) * (num_queues)) + +int ipu7_boot_init_boot_config(struct ipu7_bus_device *adev, + struct syscom_queue_config *qconfigs, + int num_queues, u32 uc_freq, + dma_addr_t subsys_config, u8 major); +void ipu7_boot_release_boot_config(struct ipu7_bus_device *adev); +int ipu7_boot_start_fw(const struct ipu7_bus_device *adev); +int ipu7_boot_stop_fw(const struct ipu7_bus_device *adev); +u32 ipu7_boot_get_boot_state(const struct ipu7_bus_device *adev); +#endif diff --git a/drivers/staging/media/ipu7/ipu7-cpd.c b/drivers/staging/media/ipu7/ipu7-cpd.c new file mode 100644 index 000000000000..4f49fb57eae4 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-cpd.c @@ -0,0 +1,276 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2015 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-cpd.h" + +/* $CPD */ +#define CPD_HDR_MARK 0x44504324 + +/* Maximum size is 4K DWORDs or 16KB */ +#define MAX_MANIFEST_SIZE (SZ_4K * sizeof(u32)) + +#define CPD_MANIFEST_IDX 0 +#define CPD_BINARY_START_IDX 1U +#define CPD_METADATA_START_IDX 2U +#define CPD_BINARY_NUM 2U /* ISYS + PSYS */ +/* + * Entries include: + * 1 manifest entry. + * 1 metadata entry for each sub system(ISYS and PSYS). + * 1 binary entry for each sub system(ISYS and PSYS). + */ +#define CPD_ENTRY_NUM (CPD_BINARY_NUM * 2U + 1U) + +#define CPD_METADATA_ATTR 0xa +#define CPD_METADATA_IPL 0x1c +#define ONLINE_METADATA_SIZE 128U +#define ONLINE_METADATA_LINES 6U + +struct ipu7_cpd_hdr { + u32 hdr_mark; + u32 ent_cnt; + u8 hdr_ver; + u8 ent_ver; + u8 hdr_len; + u8 rsvd; + u8 partition_name[4]; + u32 crc32; +} __packed; + +struct ipu7_cpd_ent { + u8 name[12]; + u32 offset; + u32 len; + u8 rsvd[4]; +} __packed; + +struct ipu7_cpd_metadata_hdr { + u32 type; + u32 len; +} __packed; + +struct ipu7_cpd_metadata_attr { + struct ipu7_cpd_metadata_hdr hdr; + u8 compression_type; + u8 encryption_type; + u8 rsvd[2]; + u32 uncompressed_size; + u32 compressed_size; + u32 module_id; + u8 hash[48]; +} __packed; + +struct ipu7_cpd_metadata_ipl { + struct ipu7_cpd_metadata_hdr hdr; + u32 param[4]; + u8 rsvd[8]; +} __packed; + +struct ipu7_cpd_metadata { + struct ipu7_cpd_metadata_attr attr; + struct ipu7_cpd_metadata_ipl ipl; +} __packed; + +static inline struct ipu7_cpd_ent *ipu7_cpd_get_entry(const void *cpd, int idx) +{ + const struct ipu7_cpd_hdr *cpd_hdr = cpd; + + return ((struct ipu7_cpd_ent *)((u8 *)cpd + cpd_hdr->hdr_len)) + idx; +} + +#define ipu7_cpd_get_manifest(cpd) ipu7_cpd_get_entry(cpd, 0) + +static struct ipu7_cpd_metadata *ipu7_cpd_get_metadata(const void *cpd, int idx) +{ + struct ipu7_cpd_ent *cpd_ent = + ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2); + + return (struct ipu7_cpd_metadata *)((u8 *)cpd + cpd_ent->offset); +} + +static int ipu7_cpd_validate_cpd(struct ipu7_device *isp, + const void *cpd, unsigned long data_size) +{ + const struct ipu7_cpd_hdr *cpd_hdr = cpd; + struct device *dev = &isp->pdev->dev; + struct ipu7_cpd_ent *ent; + unsigned int i; + u8 len; + + len = cpd_hdr->hdr_len; + + /* Ensure cpd hdr is within moduledata */ + if (data_size < len) { + dev_err(dev, "Invalid CPD moduledata size\n"); + return -EINVAL; + } + + /* Check for CPD file marker */ + if (cpd_hdr->hdr_mark != CPD_HDR_MARK) { + dev_err(dev, "Invalid CPD header marker\n"); + return -EINVAL; + } + + /* Sanity check for CPD entry header */ + if (cpd_hdr->ent_cnt != CPD_ENTRY_NUM) { + dev_err(dev, "Invalid CPD entry number %d\n", + cpd_hdr->ent_cnt); + return -EINVAL; + } + if ((data_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) { + dev_err(dev, "Invalid CPD entry headers\n"); + return -EINVAL; + } + + /* Ensure that all entries are within moduledata */ + ent = (struct ipu7_cpd_ent *)(((u8 *)cpd_hdr) + len); + for (i = 0; i < cpd_hdr->ent_cnt; i++) { + if (data_size < ent->offset || + data_size - ent->offset < ent->len) { + dev_err(dev, "Invalid CPD entry %d\n", i); + return -EINVAL; + } + ent++; + } + + return 0; +} + +static int ipu7_cpd_validate_metadata(struct ipu7_device *isp, + const void *cpd, int idx) +{ + const struct ipu7_cpd_ent *cpd_ent = + ipu7_cpd_get_entry(cpd, CPD_METADATA_START_IDX + idx * 2); + const struct ipu7_cpd_metadata *metadata = + ipu7_cpd_get_metadata(cpd, idx); + struct device *dev = &isp->pdev->dev; + + /* Sanity check for metadata size */ + if (cpd_ent->len != sizeof(struct ipu7_cpd_metadata)) { + dev_err(dev, "Invalid metadata size\n"); + return -EINVAL; + } + + /* Validate type and length of metadata sections */ + if (metadata->attr.hdr.type != CPD_METADATA_ATTR) { + dev_err(dev, "Invalid metadata attr type (%d)\n", + metadata->attr.hdr.type); + return -EINVAL; + } + if (metadata->attr.hdr.len != sizeof(struct ipu7_cpd_metadata_attr)) { + dev_err(dev, "Invalid metadata attr size (%d)\n", + metadata->attr.hdr.len); + return -EINVAL; + } + if (metadata->ipl.hdr.type != CPD_METADATA_IPL) { + dev_err(dev, "Invalid metadata ipl type (%d)\n", + metadata->ipl.hdr.type); + return -EINVAL; + } + if (metadata->ipl.hdr.len != sizeof(struct ipu7_cpd_metadata_ipl)) { + dev_err(dev, "Invalid metadata ipl size (%d)\n", + metadata->ipl.hdr.len); + return -EINVAL; + } + + return 0; +} + +int ipu7_cpd_validate_cpd_file(struct ipu7_device *isp, const void *cpd_file, + unsigned long cpd_file_size) +{ + struct device *dev = &isp->pdev->dev; + struct ipu7_cpd_ent *ent; + unsigned int i; + int ret; + char *buf; + + ret = ipu7_cpd_validate_cpd(isp, cpd_file, cpd_file_size); + if (ret) { + dev_err(dev, "Invalid CPD in file\n"); + return -EINVAL; + } + + /* Sanity check for manifest size */ + ent = ipu7_cpd_get_manifest(cpd_file); + if (ent->len > MAX_MANIFEST_SIZE) { + dev_err(dev, "Invalid manifest size\n"); + return -EINVAL; + } + + /* Validate metadata */ + for (i = 0; i < CPD_BINARY_NUM; i++) { + ret = ipu7_cpd_validate_metadata(isp, cpd_file, i); + if (ret) { + dev_err(dev, "Invalid metadata%d\n", i); + return ret; + } + } + + /* Get fw binary version. */ + buf = kmalloc(ONLINE_METADATA_SIZE, GFP_KERNEL); + if (!buf) + return -ENOMEM; + for (i = 0; i < CPD_BINARY_NUM; i++) { + char *lines[ONLINE_METADATA_LINES]; + char *info = buf; + unsigned int l; + + ent = ipu7_cpd_get_entry(cpd_file, + CPD_BINARY_START_IDX + i * 2U); + memcpy(info, (u8 *)cpd_file + ent->offset + ent->len - + ONLINE_METADATA_SIZE, ONLINE_METADATA_SIZE); + for (l = 0; l < ONLINE_METADATA_LINES; l++) { + lines[l] = strsep((char **)&info, "\n"); + if (!lines[l]) + break; + } + if (l < ONLINE_METADATA_LINES) { + dev_err(dev, "Failed to parse fw binary%d info.\n", i); + continue; + } + dev_info(dev, "FW binary%d info:\n", i); + dev_info(dev, "Name: %s\n", lines[1]); + dev_info(dev, "Version: %s\n", lines[2]); + dev_info(dev, "Timestamp: %s\n", lines[3]); + dev_info(dev, "Commit: %s\n", lines[4]); + } + kfree(buf); + + return 0; +} +EXPORT_SYMBOL_NS_GPL(ipu7_cpd_validate_cpd_file, "INTEL_IPU7"); + +int ipu7_cpd_copy_binary(const void *cpd, const char *name, + void *code_region, u32 *entry) +{ + unsigned int i; + + for (i = 0; i < CPD_BINARY_NUM; i++) { + const struct ipu7_cpd_ent *binary = + ipu7_cpd_get_entry(cpd, CPD_BINARY_START_IDX + i * 2U); + const struct ipu7_cpd_metadata *metadata = + ipu7_cpd_get_metadata(cpd, i); + + if (!strncmp(binary->name, name, sizeof(binary->name))) { + memcpy(code_region + metadata->ipl.param[0], + cpd + binary->offset, binary->len); + *entry = metadata->ipl.param[2]; + return 0; + } + } + + return -ENOENT; +} +EXPORT_SYMBOL_NS_GPL(ipu7_cpd_copy_binary, "INTEL_IPU7"); diff --git a/drivers/staging/media/ipu7/ipu7-cpd.h b/drivers/staging/media/ipu7/ipu7-cpd.h new file mode 100644 index 000000000000..b4178848c6b9 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-cpd.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2015 - 2025 Intel Corporation + */ + +#ifndef IPU7_CPD_H +#define IPU7_CPD_H + +struct ipu7_device; + +int ipu7_cpd_validate_cpd_file(struct ipu7_device *isp, + const void *cpd_file, + unsigned long cpd_file_size); +int ipu7_cpd_copy_binary(const void *cpd, const char *name, + void *code_region, u32 *entry); +#endif /* IPU7_CPD_H */ diff --git a/drivers/staging/media/ipu7/ipu7-syscom.c b/drivers/staging/media/ipu7/ipu7-syscom.c new file mode 100644 index 000000000000..3f9f9c5c3cce --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-syscom.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include + +#include "abi/ipu7_fw_syscom_abi.h" + +#include "ipu7.h" +#include "ipu7-syscom.h" + +static void __iomem *ipu7_syscom_get_indices(struct ipu7_syscom_context *ctx, + u32 q) +{ + return ctx->queue_indices + (q * sizeof(struct syscom_queue_indices_s)); +} + +void *ipu7_syscom_get_token(struct ipu7_syscom_context *ctx, int q) +{ + struct syscom_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_syscom_get_indices(ctx, q); + u32 write_index = readl(queue_indices + + offsetof(struct syscom_queue_indices_s, + write_index)); + u32 read_index = readl(queue_indices + + offsetof(struct syscom_queue_indices_s, + read_index)); + void *token = NULL; + + if (q < ctx->num_output_queues) { + /* Output queue */ + bool empty = (write_index == read_index); + + if (!empty) + token = queue_params->token_array_mem + + read_index * + queue_params->token_size_in_bytes; + } else { + /* Input queue */ + bool full = (read_index == ((write_index + 1U) % + (u32)queue_params->max_capacity)); + + if (!full) + token = queue_params->token_array_mem + + write_index * queue_params->token_size_in_bytes; + } + return token; +} +EXPORT_SYMBOL_NS_GPL(ipu7_syscom_get_token, "INTEL_IPU7"); + +void ipu7_syscom_put_token(struct ipu7_syscom_context *ctx, int q) +{ + struct syscom_queue_config *queue_params = &ctx->queue_configs[q]; + void __iomem *queue_indices = ipu7_syscom_get_indices(ctx, q); + u32 offset, index; + + if (q < ctx->num_output_queues) + /* Output queue */ + offset = offsetof(struct syscom_queue_indices_s, read_index); + + else + /* Input queue */ + offset = offsetof(struct syscom_queue_indices_s, write_index); + + index = readl(queue_indices + offset); + writel((index + 1U) % queue_params->max_capacity, + queue_indices + offset); +} +EXPORT_SYMBOL_NS_GPL(ipu7_syscom_put_token, "INTEL_IPU7"); + +struct syscom_queue_params_config * +ipu7_syscom_get_queue_config(struct syscom_config_s *config) +{ + return (struct syscom_queue_params_config *)(&config[1]); +} +EXPORT_SYMBOL_NS_GPL(ipu7_syscom_get_queue_config, "INTEL_IPU7"); diff --git a/drivers/staging/media/ipu7/ipu7-syscom.h b/drivers/staging/media/ipu7/ipu7-syscom.h new file mode 100644 index 000000000000..e1fbe3b7914e --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-syscom.h @@ -0,0 +1,35 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_SYSCOM_H +#define IPU7_SYSCOM_H + +#include + +struct syscom_config_s; +struct syscom_queue_params_config; + +struct syscom_queue_config { + void *token_array_mem; + u32 queue_size; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct ipu7_syscom_context { + u16 num_input_queues; + u16 num_output_queues; + struct syscom_queue_config *queue_configs; + void __iomem *queue_indices; + dma_addr_t queue_mem_dma_addr; + void *queue_mem; + u32 queue_mem_size; +}; + +void ipu7_syscom_put_token(struct ipu7_syscom_context *ctx, int q); +void *ipu7_syscom_get_token(struct ipu7_syscom_context *ctx, int q); +struct syscom_queue_params_config * +ipu7_syscom_get_queue_config(struct syscom_config_s *config); +#endif From 2788a049ba59fb2f90cd0d8658a8a01ce9b2797b Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:20 +0800 Subject: [PATCH 215/310] media: staging/ipu7: add IPU7 firmware ABI headers IPU7 firmware defines the ABIs between firmware and software, this patch adds a series of ABI headers. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- .../staging/media/ipu7/abi/ipu7_fw_boot_abi.h | 163 ++++++ .../media/ipu7/abi/ipu7_fw_common_abi.h | 175 +++++++ .../media/ipu7/abi/ipu7_fw_config_abi.h | 19 + .../media/ipu7/abi/ipu7_fw_insys_config_abi.h | 19 + .../staging/media/ipu7/abi/ipu7_fw_isys_abi.h | 412 ++++++++++++++++ .../staging/media/ipu7/abi/ipu7_fw_msg_abi.h | 465 ++++++++++++++++++ .../media/ipu7/abi/ipu7_fw_psys_config_abi.h | 24 + .../media/ipu7/abi/ipu7_fw_syscom_abi.h | 49 ++ 8 files changed, 1326 insertions(+) create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_boot_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_common_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_config_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_insys_config_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_isys_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_msg_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_psys_config_abi.h create mode 100644 drivers/staging/media/ipu7/abi/ipu7_fw_syscom_abi.h diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_boot_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_boot_abi.h new file mode 100644 index 000000000000..a1519c4fe661 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_boot_abi.h @@ -0,0 +1,163 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_BOOT_ABI_H +#define IPU7_FW_BOOT_ABI_H + +#include "ipu7_fw_common_abi.h" +#include "ipu7_fw_syscom_abi.h" + +#define IA_GOFO_FWLOG_SEVERITY_CRIT (0U) +#define IA_GOFO_FWLOG_SEVERITY_ERROR (1U) +#define IA_GOFO_FWLOG_SEVERITY_WARNING (2U) +#define IA_GOFO_FWLOG_SEVERITY_INFO (3U) +#define IA_GOFO_FWLOG_SEVERITY_DEBUG (4U) +#define IA_GOFO_FWLOG_SEVERITY_VERBOSE (5U) +#define IA_GOFO_FWLOG_MAX_LOGGER_SOURCES (64U) + +#define LOGGER_CONFIG_CHANNEL_ENABLE_HWPRINTF_BITMASK BIT(0) +#define LOGGER_CONFIG_CHANNEL_ENABLE_SYSCOM_BITMASK BIT(1) +#define LOGGER_CONFIG_CHANNEL_ENABLE_ALL_BITMASK \ + (LOGGER_CONFIG_CHANNEL_ENABLE_HWPRINTF_BITMASK | \ + LOGGER_CONFIG_CHANNEL_ENABLE_SYSCOM_BITMASK) + +struct ia_gofo_logger_config { + u8 use_source_severity; + u8 source_severity[IA_GOFO_FWLOG_MAX_LOGGER_SOURCES]; + u8 use_channels_enable_bitmask; + u8 channels_enable_bitmask; + u8 padding[1]; + ia_gofo_addr_t hw_printf_buffer_base_addr; + u32 hw_printf_buffer_size_bytes; +}; + +#pragma pack(push, 1) + +#define IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP \ + ((u32)IA_GOFO_FW_BOOT_ID_MAX) +#define IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_IS_OFFSET (0U) +#define IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_PS_OFFSET \ + ((IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_IS_OFFSET) + \ + (u32)(IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP)) +#define IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_PRIMARY_OFFSET (0U) +#define IA_GOFO_CCG_IPU_BUTTRESS_FW_BOOT_PARAMS_SECONDARY_OFFSET (0x3000U / 4U) +#define IA_GOFO_HKR_IPU_BUTTRESS_FW_BOOT_PARAMS_SECONDARY_OFFSET \ + (IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP * 2U) +#define IA_GOFO_HKR_HIF_BUTTRESS_FW_BOOT_PARAMS_SECONDARY_OFFSET \ + (IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP) +#define IA_GOFO_CCG_IPU_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX \ + (IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP * 4U) +#define IA_GOFO_HKR_IPU_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX \ + (IA_GOFO_BUTTRESS_FW_BOOT_PARAMS_MAX_REG_IDX_PER_APP * 4U) + +#define IA_GOFO_BOOT_RESERVED_SIZE (58U) +#define IA_GOFO_BOOT_SECONDARY_RESERVED_SIZE (IA_GOFO_BOOT_RESERVED_SIZE) +#define IA_GOFO_BOOT_SECONDARY_RESERVED_FIELDS \ + (sizeof(ia_gofo_addr_t) + sizeof(ia_gofo_addr_t) + sizeof(u32)) + +enum ia_gofo_buttress_reg_id { + IA_GOFO_FW_BOOT_CONFIG_ID = 0, + IA_GOFO_FW_BOOT_STATE_ID = 1, + IA_GOFO_FW_BOOT_RESERVED1_ID = IA_GOFO_FW_BOOT_STATE_ID, + IA_GOFO_FW_BOOT_SYSCOM_QUEUE_INDICES_BASE_ID = 2, + IA_GOFO_FW_BOOT_UNTRUSTED_ADDR_MIN_ID = 3, + IA_GOFO_FW_BOOT_RESERVED0_ID = IA_GOFO_FW_BOOT_UNTRUSTED_ADDR_MIN_ID, + IA_GOFO_FW_BOOT_MESSAGING_VERSION_ID = 4, + IA_GOFO_FW_BOOT_ID_MAX +}; + +enum ia_gofo_boot_uc_tile_frequency_units { + IA_GOFO_FW_BOOT_UC_FREQUENCY_UNITS_MHZ = 0, + IA_GOFO_FW_BOOT_UC_FREQUENCY_UNITS_HZ = 1, + IA_GOFO_FW_BOOT_UC_FREQUENCY_UNITS_N +}; + +#define IA_GOFO_FW_BOOT_STATE_IS_CRITICAL(boot_state) \ + (0xdead0000U == ((boot_state) & 0xffff0000U)) + +struct ia_gofo_boot_config { + u32 length; + struct ia_gofo_version_s config_version; + struct ia_gofo_msg_version_list client_version_support; + ia_gofo_addr_t pkg_dir; + ia_gofo_addr_t subsys_config; + u32 uc_tile_frequency; + u16 checksum; + u8 uc_tile_frequency_units; + u8 padding[1]; + u32 reserved[IA_GOFO_BOOT_RESERVED_SIZE]; + struct syscom_config_s syscom_context_config; +}; + +struct ia_gofo_secondary_boot_config { + u32 length; + struct ia_gofo_version_s config_version; + struct ia_gofo_msg_version_list client_version_support; + u8 reserved1[IA_GOFO_BOOT_SECONDARY_RESERVED_FIELDS]; + u16 checksum; + u8 padding[2]; + u32 reserved2[IA_GOFO_BOOT_SECONDARY_RESERVED_SIZE]; + struct syscom_config_s syscom_context_config; +}; + +#pragma pack(pop) + +#define IA_GOFO_WDT_TIMEOUT_ERR 0xdead0401U +#define IA_GOFO_MEM_FATAL_DME_ERR 0xdead0801U +#define IA_GOFO_MEM_UNCORRECTABLE_LOCAL_ERR 0xdead0802U +#define IA_GOFO_MEM_UNCORRECTABLE_DIRTY_ERR 0xdead0803U +#define IA_GOFO_MEM_UNCORRECTABLE_DTAG_ERR 0xdead0804U +#define IA_GOFO_MEM_UNCORRECTABLE_CACHE_ERR 0xdead0805U +#define IA_GOFO_DOUBLE_EXCEPTION_ERR 0xdead0806U +#define IA_GOFO_BIST_DMEM_FAULT_DETECTION_ERR 0xdead1000U +#define IA_GOFO_BIST_DATA_INTEGRITY_FAILURE 0xdead1010U + +enum ia_gofo_boot_state { + IA_GOFO_FW_BOOT_STATE_SECONDARY_BOOT_CONFIG_READY = 0x57a7b000U, + IA_GOFO_FW_BOOT_STATE_UNINIT = 0x57a7e000U, + IA_GOFO_FW_BOOT_STATE_STARTING_0 = 0x57a7d000U, + IA_GOFO_FW_BOOT_STATE_CACHE_INIT_DONE = 0x57a7d010U, + IA_GOFO_FW_BOOT_STATE_MEM_INIT_DONE = 0x57a7d020U, + IA_GOFO_FW_BOOT_STATE_STACK_INIT_DONE = 0x57a7d030U, + IA_GOFO_FW_BOOT_STATE_EARLY_BOOT_DONE = 0x57a7d100U, + IA_GOFO_FW_BOOT_STATE_BOOT_CONFIG_START = 0x57a7d200U, + IA_GOFO_FW_BOOT_STATE_QUEUE_INIT_DONE = 0x57a7d300U, + IA_GOFO_FW_BOOT_STATE_READY = 0x57a7e100U, + IA_GOFO_FW_BOOT_STATE_CRIT_UNSPECIFIED = 0xdead0001U, + IA_GOFO_FW_BOOT_STATE_CRIT_CFG_PTR = 0xdead0101U, + IA_GOFO_FW_BOOT_STATE_CRIT_CFG_VERSION = 0xdead0201U, + IA_GOFO_FW_BOOT_STATE_CRIT_MSG_VERSION = 0xdead0301U, + IA_GOFO_FW_BOOT_STATE_CRIT_WDT_TIMEOUT = IA_GOFO_WDT_TIMEOUT_ERR, + IA_GOFO_FW_BOOT_STATE_WRONG_DATA_SECTION_UNPACKING = 0xdead0501U, + IA_GOFO_FW_BOOT_STATE_WRONG_RO_DATA_SECTION_UNPACKING = 0xdead0601U, + IA_GOFO_FW_BOOT_STATE_INVALID_UNTRUSTED_ADDR_MIN = 0xdead0701U, + IA_GOFO_FW_BOOT_STATE_CRIT_MEM_FATAL_DME = IA_GOFO_MEM_FATAL_DME_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_MEM_UNCORRECTABLE_LOCAL = + IA_GOFO_MEM_UNCORRECTABLE_LOCAL_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_MEM_UNCORRECTABLE_DIRTY = + IA_GOFO_MEM_UNCORRECTABLE_DIRTY_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_MEM_UNCORRECTABLE_DTAG = + IA_GOFO_MEM_UNCORRECTABLE_DTAG_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_MEM_UNCORRECTABLE_CACHE = + IA_GOFO_MEM_UNCORRECTABLE_CACHE_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_DOUBLE_EXCEPTION = + IA_GOFO_DOUBLE_EXCEPTION_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_BIST_DMEM_FAULT_DETECTION_ERR = + IA_GOFO_BIST_DMEM_FAULT_DETECTION_ERR, + IA_GOFO_FW_BOOT_STATE_CRIT_DATA_INTEGRITY_FAILURE = 0xdead1010U, + IA_GOFO_FW_BOOT_STATE_CRIT_STACK_CHK_FAILURE = 0xdead1011U, + IA_GOFO_FW_BOOT_STATE_CRIT_SYSCOM_CONTEXT_INTEGRITY_FAILURE = + 0xdead1012U, + IA_GOFO_FW_BOOT_STATE_CRIT_MPU_CONFIG_FAILURE = 0xdead1013U, + IA_GOFO_FW_BOOT_STATE_CRIT_SHARED_BUFFER_FAILURE = 0xdead1014U, + IA_GOFO_FW_BOOT_STATE_CRIT_CMEM_FAILURE = 0xdead1015U, + IA_GOFO_FW_BOOT_STATE_SHUTDOWN_CMD = 0x57a7f001U, + IA_GOFO_FW_BOOT_STATE_SHUTDOWN_START = 0x57a7e200U, + IA_GOFO_FW_BOOT_STATE_INACTIVE = 0x57a7e300U, + IA_GOFO_FW_BOOT_HW_CMD_ACK_TIMEOUT = 0x57a7e400U, + IA_GOFO_FW_BOOT_SYSTEM_CYCLES_ERROR = 0x57a7e500U +}; + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_common_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_common_abi.h new file mode 100644 index 000000000000..7bb6fac585a3 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_common_abi.h @@ -0,0 +1,175 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_COMMOM_ABI_H +#define IPU7_FW_COMMOM_ABI_H + +#include + +#pragma pack(push, 1) +typedef u32 ia_gofo_addr_t; + +#define IA_GOFO_ADDR_NULL (0U) + +struct ia_gofo_version_s { + u8 patch; + u8 subminor; + u8 minor; + u8 major; +}; + +#define IA_GOFO_MSG_VERSION_INIT(major_val, minor_val, subminor_val, patch_val)\ + {.major = (major_val), .minor = (minor_val), .subminor = \ + (subminor_val), .patch = (patch_val)} + +#define IA_GOFO_MSG_VERSION_LIST_MAX_ENTRIES (3U) +#define IA_GOFO_MSG_RESERVED_SIZE (3U) + +struct ia_gofo_msg_version_list { + u8 num_versions; + u8 reserved[IA_GOFO_MSG_RESERVED_SIZE]; + struct ia_gofo_version_s versions[IA_GOFO_MSG_VERSION_LIST_MAX_ENTRIES]; +}; + +#pragma pack(pop) + +#define TLV_TYPE_PADDING (0U) + +#pragma pack(push, 1) + +#define IA_GOFO_ABI_BITS_PER_BYTE (8U) + +struct ia_gofo_tlv_header { + u16 tlv_type; + u16 tlv_len32; +}; + +struct ia_gofo_tlv_list { + u16 num_elems; + u16 head_offset; +}; + +#define TLV_ITEM_ALIGNMENT ((u32)sizeof(u32)) +#define TLV_MSG_ALIGNMENT ((u32)sizeof(u64)) +#define TLV_LIST_ALIGNMENT TLV_ITEM_ALIGNMENT +#pragma pack(pop) + +#define IA_GOFO_MODULO(dividend, divisor) ((dividend) % (divisor)) + +#define IA_GOFO_MSG_ERR_MAX_DETAILS (4U) +#define IA_GOFO_MSG_ERR_OK (0U) +#define IA_GOFO_MSG_ERR_UNSPECIFED (0xffffffffU) +#define IA_GOFO_MSG_ERR_GROUP_UNSPECIFIED (0U) +#define IA_GOFO_MSG_ERR_IS_OK(err) (IA_GOFO_MSG_ERR_OK == (err).err_code) + +#pragma pack(push, 1) +struct ia_gofo_msg_err { + u32 err_group; + u32 err_code; + u32 err_detail[IA_GOFO_MSG_ERR_MAX_DETAILS]; +}; + +#pragma pack(pop) + +#define IA_GOFO_MSG_ERR_GROUP_APP_EXT_START (16U) +#define IA_GOFO_MSG_ERR_GROUP_MAX (31U) +#define IA_GOFO_MSG_ERR_GROUP_INTERNAL_START (IA_GOFO_MSG_ERR_GROUP_MAX + 1U) +#define IA_GOFO_MSG_ERR_GROUP_RESERVED IA_GOFO_MSG_ERR_GROUP_UNSPECIFIED +#define IA_GOFO_MSG_ERR_GROUP_GENERAL 1 + +enum ia_gofo_msg_err_general { + IA_GOFO_MSG_ERR_GENERAL_OK = IA_GOFO_MSG_ERR_OK, + IA_GOFO_MSG_ERR_GENERAL_MSG_TOO_SMALL = 1, + IA_GOFO_MSG_ERR_GENERAL_MSG_TOO_LARGE = 2, + IA_GOFO_MSG_ERR_GENERAL_DEVICE_STATE = 3, + IA_GOFO_MSG_ERR_GENERAL_ALIGNMENT = 4, + IA_GOFO_MSG_ERR_GENERAL_INDIRECT_REF_PTR_INVALID = 5, + IA_GOFO_MSG_ERR_GENERAL_INVALID_MSG_TYPE = 6, + IA_GOFO_MSG_ERR_GENERAL_SYSCOM_FAIL = 7, + IA_GOFO_MSG_ERR_GENERAL_N +}; + +#pragma pack(push, 1) +#define IA_GOFO_MSG_TYPE_RESERVED 0 +#define IA_GOFO_MSG_TYPE_INDIRECT 1 +#define IA_GOFO_MSG_TYPE_LOG 2 +#define IA_GOFO_MSG_TYPE_GENERAL_ERR 3 + +struct ia_gofo_msg_header { + struct ia_gofo_tlv_header tlv_header; + struct ia_gofo_tlv_list msg_options; + u64 user_token; +}; + +struct ia_gofo_msg_header_ack { + struct ia_gofo_msg_header header; + struct ia_gofo_msg_err err; + +}; + +struct ia_gofo_msg_general_err { + struct ia_gofo_msg_header_ack header; +}; + +#pragma pack(pop) + +#pragma pack(push, 1) +enum ia_gofo_msg_link_streaming_mode { + IA_GOFO_MSG_LINK_STREAMING_MODE_SOFF = 0, + IA_GOFO_MSG_LINK_STREAMING_MODE_DOFF = 1, + IA_GOFO_MSG_LINK_STREAMING_MODE_BCLM = 2, + IA_GOFO_MSG_LINK_STREAMING_MODE_BCSM_FIX = 3, + IA_GOFO_MSG_LINK_STREAMING_MODE_N +}; + +enum ia_gofo_soc_pbk_instance_id { + IA_GOFO_SOC_PBK_ID0 = 0, + IA_GOFO_SOC_PBK_ID1 = 1, + IA_GOFO_SOC_PBK_ID_N +}; + +#define IA_GOFO_MSG_LINK_PBK_MAX_SLOTS (2U) + +struct ia_gofo_msg_indirect { + struct ia_gofo_msg_header header; + struct ia_gofo_tlv_header ref_header; + ia_gofo_addr_t ref_msg_ptr; +}; + +#pragma pack(pop) + +#pragma pack(push, 1) +#define IA_GOFO_MSG_LOG_MAX_PARAMS (4U) +#define IA_GOFO_MSG_LOG_DOC_FMT_ID_MIN (0U) + +#define IA_GOFO_MSG_LOG_DOC_FMT_ID_MAX (4095U) +#define IA_GOFO_MSG_LOG_FMT_ID_INVALID (0xfffffffU) + +struct ia_gofo_msg_log_info { + u16 log_counter; + u8 msg_parameter_types; + /* [0:0] is_out_of_order, [1:3] logger_channel, [4:7] reserved */ + u8 logger_opts; + u32 fmt_id; + u32 params[IA_GOFO_MSG_LOG_MAX_PARAMS]; +}; + +struct ia_gofo_msg_log_info_ts { + u64 msg_ts; + struct ia_gofo_msg_log_info log_info; +}; + +struct ia_gofo_msg_log { + struct ia_gofo_msg_header header; + struct ia_gofo_msg_log_info_ts log_info_ts; +}; + +#pragma pack(pop) + +#define IA_GOFO_MSG_ABI_OUT_ACK_QUEUE_ID (0U) +#define IA_GOFO_MSG_ABI_OUT_LOG_QUEUE_ID (1U) +#define IA_GOFO_MSG_ABI_IN_DEV_QUEUE_ID (2U) + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_config_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_config_abi.h new file mode 100644 index 000000000000..c3f62aaedd86 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_config_abi.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_CONFIG_ABI_H +#define IPU7_FW_CONFIG_ABI_H + +#include + +#define IPU_CONFIG_ABI_WDT_TIMER_DISABLED 0U +#define IPU_CONFIG_ABI_CMD_TIMER_DISABLED 0U + +struct ipu7_wdt_abi { + u32 wdt_timer1_us; + u32 wdt_timer2_us; +}; + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_insys_config_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_insys_config_abi.h new file mode 100644 index 000000000000..f161a605c500 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_insys_config_abi.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2021 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_INSYS_CONFIG_ABI_H +#define IPU7_FW_INSYS_CONFIG_ABI_H + +#include "ipu7_fw_boot_abi.h" +#include "ipu7_fw_config_abi.h" +#include "ipu7_fw_isys_abi.h" + +struct ipu7_insys_config { + u32 timeout_val_ms; + struct ia_gofo_logger_config logger_config; + struct ipu7_wdt_abi wdt_config; +}; + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_isys_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_isys_abi.h new file mode 100644 index 000000000000..c42d0b7a2627 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_isys_abi.h @@ -0,0 +1,412 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_ISYS_ABI_H +#define IPU7_FW_ISYS_ABI_H + +#include "ipu7_fw_common_abi.h" +#include "ipu7_fw_isys_abi.h" + +#define IPU_INSYS_MAX_OUTPUT_QUEUES (3U) +#define IPU_INSYS_STREAM_ID_MAX (16U) + +#define IPU_INSYS_MAX_INPUT_QUEUES (IPU_INSYS_STREAM_ID_MAX + 1U) +#define IPU_INSYS_OUTPUT_FIRST_QUEUE (0U) +#define IPU_INSYS_OUTPUT_LAST_QUEUE (IPU_INSYS_MAX_OUTPUT_QUEUES - 1U) +#define IPU_INSYS_OUTPUT_MSG_QUEUE (IPU_INSYS_OUTPUT_FIRST_QUEUE) +#define IPU_INSYS_OUTPUT_LOG_QUEUE (IPU_INSYS_OUTPUT_FIRST_QUEUE + 1U) +#define IPU_INSYS_OUTPUT_RESERVED_QUEUE (IPU_INSYS_OUTPUT_LAST_QUEUE) +#define IPU_INSYS_INPUT_FIRST_QUEUE (IPU_INSYS_MAX_OUTPUT_QUEUES) +#define IPU_INSYS_INPUT_LAST_QUEUE \ + (IPU_INSYS_INPUT_FIRST_QUEUE + IPU_INSYS_MAX_INPUT_QUEUES - 1U) +#define IPU_INSYS_INPUT_DEV_QUEUE (IPU_INSYS_INPUT_FIRST_QUEUE) +#define IPU_INSYS_INPUT_MSG_QUEUE (IPU_INSYS_INPUT_FIRST_QUEUE + 1U) +#define IPU_INSYS_INPUT_MSG_MAX_QUEUE (IPU_INSYS_MAX_INPUT_QUEUES - 1U) + +#define MAX_OPINS_FOR_SINGLE_IPINS (3U) +#define DEV_SEND_QUEUE_SIZE (IPU_INSYS_STREAM_ID_MAX) + +#define PIN_PLANES_MAX (4U) + +#define INSYS_MSG_ERR_STREAM_INSUFFICIENT_RESOURCES_INPUT \ + INSYS_MSG_ERR_STREAM_INSUFFICIENT_RESOURCES + +typedef u64 ipu7_insys_return_token; + +enum ipu7_insys_resp_type { + IPU_INSYS_RESP_TYPE_STREAM_OPEN_DONE = 0, + IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK = 1, + IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK = 2, + IPU_INSYS_RESP_TYPE_STREAM_ABORT_ACK = 3, + IPU_INSYS_RESP_TYPE_STREAM_FLUSH_ACK = 4, + IPU_INSYS_RESP_TYPE_STREAM_CLOSE_ACK = 5, + IPU_INSYS_RESP_TYPE_PIN_DATA_READY = 6, + IPU_INSYS_RESP_TYPE_FRAME_SOF = 7, + IPU_INSYS_RESP_TYPE_FRAME_EOF = 8, + IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE = 9, + IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE = 10, + IPU_INSYS_RESP_TYPE_PWM_IRQ = 11, + N_IPU_INSYS_RESP_TYPE +}; + +enum ipu7_insys_send_type { + IPU_INSYS_SEND_TYPE_STREAM_OPEN = 0, + IPU_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE = 1, + IPU_INSYS_SEND_TYPE_STREAM_CAPTURE = 2, + IPU_INSYS_SEND_TYPE_STREAM_ABORT = 3, + IPU_INSYS_SEND_TYPE_STREAM_FLUSH = 4, + IPU_INSYS_SEND_TYPE_STREAM_CLOSE = 5, + N_IPU_INSYS_SEND_TYPE +}; + +enum ipu7_insys_mipi_vc { + IPU_INSYS_MIPI_VC_0 = 0, + IPU_INSYS_MIPI_VC_1 = 1, + IPU_INSYS_MIPI_VC_2 = 2, + IPU_INSYS_MIPI_VC_3 = 3, + IPU_INSYS_MIPI_VC_4 = 4, + IPU_INSYS_MIPI_VC_5 = 5, + IPU_INSYS_MIPI_VC_6 = 6, + IPU_INSYS_MIPI_VC_7 = 7, + IPU_INSYS_MIPI_VC_8 = 8, + IPU_INSYS_MIPI_VC_9 = 9, + IPU_INSYS_MIPI_VC_10 = 10, + IPU_INSYS_MIPI_VC_11 = 11, + IPU_INSYS_MIPI_VC_12 = 12, + IPU_INSYS_MIPI_VC_13 = 13, + IPU_INSYS_MIPI_VC_14 = 14, + IPU_INSYS_MIPI_VC_15 = 15, + N_IPU_INSYS_MIPI_VC +}; + +enum ipu7_insys_mipi_port { + IPU_INSYS_MIPI_PORT_0 = 0, + IPU_INSYS_MIPI_PORT_1 = 1, + IPU_INSYS_MIPI_PORT_2 = 2, + IPU_INSYS_MIPI_PORT_3 = 3, + IPU_INSYS_MIPI_PORT_4 = 4, + IPU_INSYS_MIPI_PORT_5 = 5, + NA_IPU_INSYS_MIPI_PORT +}; + +enum ipu7_insys_frame_format_type { + IPU_INSYS_FRAME_FORMAT_NV11 = 0, + IPU_INSYS_FRAME_FORMAT_NV12 = 1, + IPU_INSYS_FRAME_FORMAT_NV12_16 = 2, + IPU_INSYS_FRAME_FORMAT_NV12_TILEY = 3, + IPU_INSYS_FRAME_FORMAT_NV16 = 4, + IPU_INSYS_FRAME_FORMAT_NV21 = 5, + IPU_INSYS_FRAME_FORMAT_NV61 = 6, + IPU_INSYS_FRAME_FORMAT_YV12 = 7, + IPU_INSYS_FRAME_FORMAT_YV16 = 8, + IPU_INSYS_FRAME_FORMAT_YUV420 = 9, + IPU_INSYS_FRAME_FORMAT_YUV420_10 = 10, + IPU_INSYS_FRAME_FORMAT_YUV420_12 = 11, + IPU_INSYS_FRAME_FORMAT_YUV420_14 = 12, + IPU_INSYS_FRAME_FORMAT_YUV420_16 = 13, + IPU_INSYS_FRAME_FORMAT_YUV422 = 14, + IPU_INSYS_FRAME_FORMAT_YUV422_16 = 15, + IPU_INSYS_FRAME_FORMAT_UYVY = 16, + IPU_INSYS_FRAME_FORMAT_YUYV = 17, + IPU_INSYS_FRAME_FORMAT_YUV444 = 18, + IPU_INSYS_FRAME_FORMAT_YUV_LINE = 19, + IPU_INSYS_FRAME_FORMAT_RAW8 = 20, + IPU_INSYS_FRAME_FORMAT_RAW10 = 21, + IPU_INSYS_FRAME_FORMAT_RAW12 = 22, + IPU_INSYS_FRAME_FORMAT_RAW14 = 23, + IPU_INSYS_FRAME_FORMAT_RAW16 = 24, + IPU_INSYS_FRAME_FORMAT_RGB565 = 25, + IPU_INSYS_FRAME_FORMAT_PLANAR_RGB888 = 26, + IPU_INSYS_FRAME_FORMAT_RGBA888 = 27, + IPU_INSYS_FRAME_FORMAT_QPLANE6 = 28, + IPU_INSYS_FRAME_FORMAT_BINARY_8 = 29, + IPU_INSYS_FRAME_FORMAT_Y_8 = 30, + IPU_INSYS_FRAME_FORMAT_ARGB888 = 31, + IPU_INSYS_FRAME_FORMAT_BGRA888 = 32, + IPU_INSYS_FRAME_FORMAT_ABGR888 = 33, + N_IPU_INSYS_FRAME_FORMAT +}; + +#define IPU_INSYS_FRAME_FORMAT_RAW (IPU_INSYS_FRAME_FORMAT_RAW16) +#define N_IPU_INSYS_MIPI_DATA_TYPE 0x40 + +enum ipu7_insys_mipi_dt_rename_mode { + IPU_INSYS_MIPI_DT_NO_RENAME = 0, + IPU_INSYS_MIPI_DT_RENAMED_MODE = 1, + N_IPU_INSYS_MIPI_DT_MODE +}; + +#define IPU_INSYS_SEND_MSG_ENABLED 1U +#define IPU_INSYS_SEND_MSG_DISABLED 0U + +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF BIT(0) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF BIT(1) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF BIT(2) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF BIT(3) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED BIT(4) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF_DISCARDED BIT(5) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED BIT(6) +#define IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF_DISCARDED BIT(7) +#define IPU_INSYS_STREAM_SYNC_MSG_ENABLE_MSG_SEND_RESP ( \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_EOF_DISCARDED) +#define IPU_INSYS_STREAM_SYNC_MSG_ENABLE_MSG_SEND_IRQ ( \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED | \ + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_EOF_DISCARDED) + +#define IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE BIT(0) +#define IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE BIT(1) +#define IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK BIT(2) +#define IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK BIT(3) +#define IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK BIT(4) +#define IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK BIT(5) +#define IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK BIT(6) +#define IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK BIT(7) +#define IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK BIT(8) +#define IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK BIT(9) +#define IPU_INSYS_STREAM_ENABLE_MSG_SEND_RESP ( \ + IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_OPEN_DONE | \ + IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_START_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_CLOSE_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_FLUSH_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_RESP_STREAM_ABORT_ACK) +#define IPU_INSYS_STREAM_ENABLE_MSG_SEND_IRQ ( \ + IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_OPEN_DONE | \ + IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_START_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_CLOSE_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_FLUSH_ACK | \ + IPU_INSYS_STREAM_MSG_SEND_IRQ_STREAM_ABORT_ACK) + +#define IPU_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK BIT(0) +#define IPU_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK BIT(1) +#define IPU_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE BIT(2) +#define IPU_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE BIT(3) +#define IPU_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY BIT(4) +#define IPU_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY BIT(5) +#define IPU_INSYS_FRAME_ENABLE_MSG_SEND_RESP ( \ + IPU_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_ACK | \ + IPU_INSYS_FRAME_MSG_SEND_RESP_CAPTURE_DONE | \ + IPU_INSYS_FRAME_MSG_SEND_RESP_PIN_DATA_READY) +#define IPU_INSYS_FRAME_ENABLE_MSG_SEND_IRQ ( \ + IPU_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_ACK | \ + IPU_INSYS_FRAME_MSG_SEND_IRQ_CAPTURE_DONE | \ + IPU_INSYS_FRAME_MSG_SEND_IRQ_PIN_DATA_READY) + +enum ipu7_insys_output_link_dest { + IPU_INSYS_OUTPUT_LINK_DEST_MEM = 0, + IPU_INSYS_OUTPUT_LINK_DEST_PSYS = 1, + IPU_INSYS_OUTPUT_LINK_DEST_IPU_EXTERNAL = 2 +}; + +enum ipu7_insys_dpcm_type { + IPU_INSYS_DPCM_TYPE_DISABLED = 0, + IPU_INSYS_DPCM_TYPE_10_8_10 = 1, + IPU_INSYS_DPCM_TYPE_12_8_12 = 2, + IPU_INSYS_DPCM_TYPE_12_10_12 = 3, + N_IPU_INSYS_DPCM_TYPE +}; + +enum ipu7_insys_dpcm_predictor { + IPU_INSYS_DPCM_PREDICTOR_1 = 0, + IPU_INSYS_DPCM_PREDICTOR_2 = 1, + N_IPU_INSYS_DPCM_PREDICTOR +}; + +enum ipu7_insys_send_queue_token_flag { + IPU_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE = 0, + IPU_INSYS_SEND_QUEUE_TOKEN_FLAG_FLUSH_FORCE = 1 +}; + +#pragma pack(push, 1) +struct ipu7_insys_resolution { + u32 width; + u32 height; +}; + +struct ipu7_insys_capture_output_pin_payload { + u64 user_token; + ia_gofo_addr_t addr; + u8 pad[4]; +}; + +struct ipu7_insys_output_link { + u32 buffer_lines; + u16 foreign_key; + u16 granularity_pointer_update; + u8 msg_link_streaming_mode; + u8 pbk_id; + u8 pbk_slot_id; + u8 dest; + u8 use_sw_managed; + u8 is_snoop; + u8 pad[2]; +}; + +struct ipu7_insys_output_cropping { + u16 line_top; + u16 line_bottom; +}; + +struct ipu7_insys_output_dpcm { + u8 enable; + u8 type; + u8 predictor; + u8 pad; +}; + +struct ipu7_insys_output_pin { + struct ipu7_insys_output_link link; + struct ipu7_insys_output_cropping crop; + struct ipu7_insys_output_dpcm dpcm; + u32 stride; + u16 ft; + u8 send_irq; + u8 input_pin_id; + u8 early_ack_en; + u8 pad[3]; +}; + +struct ipu7_insys_input_pin { + struct ipu7_insys_resolution input_res; + u16 sync_msg_map; + u8 dt; + u8 disable_mipi_unpacking; + u8 dt_rename_mode; + u8 mapped_dt; + u8 pad[2]; +}; + +struct ipu7_insys_stream_cfg { + struct ipu7_insys_input_pin input_pins[4]; + struct ipu7_insys_output_pin output_pins[4]; + u16 stream_msg_map; + u8 port_id; + u8 vc; + u8 nof_input_pins; + u8 nof_output_pins; + u8 pad[2]; +}; + +struct ipu7_insys_buffset { + struct ipu7_insys_capture_output_pin_payload output_pins[4]; + u8 capture_msg_map; + u8 frame_id; + u8 skip_frame; + u8 pad[5]; +}; + +struct ipu7_insys_resp { + u64 buf_id; + struct ipu7_insys_capture_output_pin_payload pin; + struct ia_gofo_msg_err error_info; + u32 timestamp[2]; + u8 type; + u8 msg_link_streaming_mode; + u8 stream_id; + u8 pin_id; + u8 frame_id; + u8 skip_frame; + u8 pad[2]; +}; + +struct ipu7_insys_resp_queue_token { + struct ipu7_insys_resp resp_info; +}; + +struct ipu7_insys_send_queue_token { + u64 buf_handle; + ia_gofo_addr_t addr; + u16 stream_id; + u8 send_type; + u8 flag; +}; + +#pragma pack(pop) + +enum insys_msg_err_stream { + INSYS_MSG_ERR_STREAM_OK = IA_GOFO_MSG_ERR_OK, + INSYS_MSG_ERR_STREAM_STREAM_ID = 1, + INSYS_MSG_ERR_STREAM_MAX_OPINS = 2, + INSYS_MSG_ERR_STREAM_MAX_IPINS = 3, + INSYS_MSG_ERR_STREAM_STREAM_MESSAGES_MAP = 4, + INSYS_MSG_ERR_STREAM_SYNC_MESSAGES_MAP = 5, + INSYS_MSG_ERR_STREAM_SENSOR_TYPE = 6, + INSYS_MSG_ERR_STREAM_FOREIGN_KEY = 7, + INSYS_MSG_ERR_STREAM_STREAMING_MODE = 8, + INSYS_MSG_ERR_STREAM_DPCM_EN = 9, + INSYS_MSG_ERR_STREAM_DPCM_TYPE = 10, + INSYS_MSG_ERR_STREAM_DPCM_PREDICTOR = 11, + INSYS_MSG_ERR_STREAM_GRANULARITY_POINTER_UPDATE = 12, + INSYS_MSG_ERR_STREAM_MPF_LUT_ENTRY_RESOURCES_BUSY = 13, + INSYS_MSG_ERR_STREAM_MPF_DEV_ID = 14, + INSYS_MSG_ERR_STREAM_BUFFER_LINES = 15, + INSYS_MSG_ERR_STREAM_IPIN_ID = 16, + INSYS_MSG_ERR_STREAM_DATA_TYPE = 17, + INSYS_MSG_ERR_STREAM_STREAMING_PROTOCOL_STATE = 18, + INSYS_MSG_ERR_STREAM_SYSCOM_FLUSH = 19, + INSYS_MSG_ERR_STREAM_MIPI_VC = 20, + INSYS_MSG_ERR_STREAM_STREAM_SRC = 21, + INSYS_MSG_ERR_STREAM_PBK_ID = 22, + INSYS_MSG_ERR_STREAM_CMD_QUEUE_DEALLOCATE = 23, + INSYS_MSG_ERR_STREAM_INSUFFICIENT_RESOURCES = 24, + INSYS_MSG_ERR_STREAM_IPIN_CONFIGURATION = 25, + INSYS_MSG_ERR_STREAM_INVALID_STATE = 26, + INSYS_MSG_ERR_STREAM_SW_MANAGED = 27, + INSYS_MSG_ERR_STREAM_PBK_SLOT_ID = 28, + INSYS_MSG_ERR_STREAM_FLUSH_TIMEOUT = 29, + INSYS_MSG_ERR_STREAM_IPIN_WIDTH = 30, + INSYS_MSG_ERR_STREAM_IPIN_HEIGHT = 31, + INSYS_MSG_ERR_STREAM_OUTPUT_PIN_EARLY_ACK_EN = 32, + INSYS_MSG_ERR_STREAM_INCONSISTENT_PARAMS = 33, + INSYS_MSG_ERR_STREAM_PLANE_COUNT = 34, + INSYS_MSG_ERR_STREAM_FRAME_FORMAT_TYPE = 35, + INSYS_MSG_ERR_STREAM_INSUFFICIENT_RESOURCES_OUTPUT = 36, + INSYS_MSG_ERR_STREAM_WIDTH_OUTPUT_SIZE = 37, + INSYS_MSG_ERR_STREAM_CLOSED = 38, + INSYS_MSG_ERR_STREAM_N +}; + +enum insys_msg_err_capture { + INSYS_MSG_ERR_CAPTURE_OK = IA_GOFO_MSG_ERR_OK, + INSYS_MSG_ERR_CAPTURE_STREAM_ID = 1, + INSYS_MSG_ERR_CAPTURE_PAYLOAD_PTR = 2, + INSYS_MSG_ERR_CAPTURE_MEM_SLOT = 3, + INSYS_MSG_ERR_CAPTURE_STREAMING_MODE = 4, + INSYS_MSG_ERR_CAPTURE_AVAILABLE_CMD_SLOT = 5, + INSYS_MSG_ERR_CAPTURE_CONSUMED_CMD_SLOT = 6, + INSYS_MSG_ERR_CAPTURE_CMD_SLOT_PAYLOAD_PTR = 7, + INSYS_MSG_ERR_CAPTURE_CMD_PREPARE = 8, + INSYS_MSG_ERR_CAPTURE_OUTPUT_PIN = 9, + INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP = 10, + INSYS_MSG_ERR_CAPTURE_FRAME_MESSAGES_MAP = 11, + INSYS_MSG_ERR_CAPTURE_TIMEOUT = 12, + INSYS_MSG_ERR_CAPTURE_INVALID_STREAM_STATE = 13, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MULTIBIT_PH_ERROR_DETECTED = 14, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PAYLOAD_CRC_ERROR = 15, + INSYS_MSG_ERR_CAPTURE_HW_ERR_INPUT_DATA_LOSS_ELASTIC_FIFO_OVFL = 16, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PIXEL_BUFFER_OVERFLOW = 17, + INSYS_MSG_ERR_CAPTURE_HW_ERR_BAD_FRAME_DIM = 18, + INSYS_MSG_ERR_CAPTURE_HW_ERR_PHY_SYNC_ERR = 19, + INSYS_MSG_ERR_CAPTURE_HW_ERR_SECURE_TOUCH = 20, + INSYS_MSG_ERR_CAPTURE_HW_ERR_MASTER_SLAVE_SYNC_ERR = 21, + INSYS_MSG_ERR_CAPTURE_FRAME_SKIP_ERR = 22, + INSYS_MSG_ERR_CAPTURE_FE_INPUT_FIFO_OVERFLOW_ERR = 23, + INSYS_MSG_ERR_CAPTURE_CMD_SUBMIT_TO_HW = 24, + INSYS_MSG_ERR_CAPTURE_N +}; + +enum insys_msg_err_groups { + INSYS_MSG_ERR_GROUP_RESERVED = IA_GOFO_MSG_ERR_GROUP_RESERVED, + INSYS_MSG_ERR_GROUP_GENERAL = IA_GOFO_MSG_ERR_GROUP_GENERAL, + INSYS_MSG_ERR_GROUP_STREAM = 2, + INSYS_MSG_ERR_GROUP_CAPTURE = 3, + INSYS_MSG_ERR_GROUP_N, +}; + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_msg_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_msg_abi.h new file mode 100644 index 000000000000..8a78dd0936df --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_msg_abi.h @@ -0,0 +1,465 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_MSG_ABI_H +#define IPU7_FW_MSG_ABI_H + +#include "ipu7_fw_common_abi.h" + +#pragma pack(push, 1) +enum ipu7_msg_type { + IPU_MSG_TYPE_RESERVED = IA_GOFO_MSG_TYPE_RESERVED, + IPU_MSG_TYPE_INDIRECT = IA_GOFO_MSG_TYPE_INDIRECT, + IPU_MSG_TYPE_DEV_LOG = IA_GOFO_MSG_TYPE_LOG, + IPU_MSG_TYPE_GENERAL_ERR = IA_GOFO_MSG_TYPE_GENERAL_ERR, + IPU_MSG_TYPE_DEV_OPEN = 4, + IPU_MSG_TYPE_DEV_OPEN_ACK = 5, + IPU_MSG_TYPE_GRAPH_OPEN = 6, + IPU_MSG_TYPE_GRAPH_OPEN_ACK = 7, + IPU_MSG_TYPE_TASK_REQ = 8, + IPU_MSG_TYPE_TASK_DONE = 9, + IPU_MSG_TYPE_GRAPH_CLOSE = 10, + IPU_MSG_TYPE_GRAPH_CLOSE_ACK = 11, + IPU_MSG_TYPE_DEV_CLOSE = 12, + IPU_MSG_TYPE_DEV_CLOSE_ACK = 13, + IPU_MSG_TYPE_TERM_EVENT = 14, + IPU_MSG_TYPE_N, +}; + +#define IPU_MSG_MAX_NODE_TERMS (64U) +#define IPU_MSG_MAX_FRAGS (7U) + +enum ipu7_msg_node_type { + IPU_MSG_NODE_TYPE_PAD = 0, + IPU_MSG_NODE_TYPE_BASE, + IPU_MSG_NODE_TYPE_N +}; + +#define IPU_MSG_NODE_MAX_DEVICES (128U) +#define DEB_NUM_UINT32 (IPU_MSG_NODE_MAX_DEVICES / (sizeof(u32) * 8U)) + +typedef u32 ipu7_msg_teb_t[2]; +typedef u32 ipu7_msg_deb_t[DEB_NUM_UINT32]; + +#define IPU_MSG_NODE_MAX_ROUTE_ENABLES (128U) +#define RBM_NUM_UINT32 (IPU_MSG_NODE_MAX_ROUTE_ENABLES / (sizeof(u32) * 8U)) + +typedef u32 ipu7_msg_rbm_t[RBM_NUM_UINT32]; + +enum ipu7_msg_node_profile_type { + IPU_MSG_NODE_PROFILE_TYPE_PAD = 0, + IPU_MSG_NODE_PROFILE_TYPE_BASE, + IPU_MSG_NODE_PROFILE_TYPE_CB, + IPU_MSG_NODE_PROFILE_TYPE_N +}; + +struct ipu7_msg_node_profile { + struct ia_gofo_tlv_header tlv_header; + ipu7_msg_teb_t teb; +}; + +struct ipu7_msg_cb_profile { + struct ipu7_msg_node_profile profile_base; + ipu7_msg_deb_t deb; + ipu7_msg_rbm_t rbm; + ipu7_msg_rbm_t reb; +}; + +#define IPU_MSG_NODE_MAX_PROFILES (2U) +#define IPU_MSG_NODE_DEF_PROFILE_IDX (0U) +#define IPU_MSG_NODE_RSRC_ID_EXT_IP (0xffU) + +#define IPU_MSG_NODE_DONT_CARE_TEB_HI (0xffffffffU) +#define IPU_MSG_NODE_DONT_CARE_TEB_LO (0xffffffffU) +#define IPU_MSG_NODE_RSRC_ID_IS (0xfeU) + +struct ipu7_msg_node { + struct ia_gofo_tlv_header tlv_header; + u8 node_rsrc_id; + u8 node_ctx_id; + u8 num_frags; + u8 reserved[1]; + struct ia_gofo_tlv_list profiles_list; + struct ia_gofo_tlv_list terms_list; + struct ia_gofo_tlv_list node_options; +}; + +enum ipu7_msg_node_option_types { + IPU_MSG_NODE_OPTION_TYPES_PADDING = 0, + IPU_MSG_NODE_OPTION_TYPES_N +}; + +#pragma pack(pop) + +#pragma pack(push, 1) + +enum ipu7_msg_link_type { + IPU_MSG_LINK_TYPE_PAD = 0, + IPU_MSG_LINK_TYPE_GENERIC = 1, + IPU_MSG_LINK_TYPE_N +}; + +enum ipu7_msg_link_option_types { + IPU_MSG_LINK_OPTION_TYPES_PADDING = 0, + IPU_MSG_LINK_OPTION_TYPES_CMPRS = 1, + IPU_MSG_LINK_OPTION_TYPES_N +}; + +enum ipu7_msg_link_cmprs_option_bit_depth { + IPU_MSG_LINK_CMPRS_OPTION_8BPP = 0, + IPU_MSG_LINK_CMPRS_OPTION_10BPP = 1, + IPU_MSG_LINK_CMPRS_OPTION_12BPP = 2, +}; + +#define IPU_MSG_LINK_CMPRS_SPACE_SAVING_DENOM (128U) +#define IPU_MSG_LINK_CMPRS_LOSSY_CFG_PAYLOAD_SIZE (5U) +#define IPU_MSG_LINK_CMPRS_SPACE_SAVING_NUM_MAX \ + (IPU_MSG_LINK_CMPRS_SPACE_SAVING_DENOM - 1U) + +struct ipu7_msg_link_cmprs_plane_desc { + u8 plane_enable; + u8 cmprs_enable; + u8 encoder_plane_id; + u8 decoder_plane_id; + u8 cmprs_is_lossy; + u8 cmprs_is_footprint; + u8 bit_depth; + u8 space_saving_numerator; + u32 pixels_offset; + u32 ts_offset; + u32 tile_row_to_tile_row_stride; + u32 rows_of_tiles; + u32 lossy_cfg[IPU_MSG_LINK_CMPRS_LOSSY_CFG_PAYLOAD_SIZE]; +}; + +#define IPU_MSG_LINK_CMPRS_MAX_PLANES (2U) +#define IPU_MSG_LINK_CMPRS_NO_ALIGN_INTERVAL (0U) +#define IPU_MSG_LINK_CMPRS_MIN_ALIGN_INTERVAL (16U) +#define IPU_MSG_LINK_CMPRS_MAX_ALIGN_INTERVAL (1024U) +struct ipu7_msg_link_cmprs_option { + struct ia_gofo_tlv_header header; + u32 cmprs_buf_size; + u16 align_interval; + u8 reserved[2]; + struct ipu7_msg_link_cmprs_plane_desc plane_descs[2]; +}; + +struct ipu7_msg_link_ep { + u8 node_ctx_id; + u8 term_id; +}; + +struct ipu7_msg_link_ep_pair { + struct ipu7_msg_link_ep ep_src; + struct ipu7_msg_link_ep ep_dst; +}; + +#define IPU_MSG_LINK_FOREIGN_KEY_NONE (65535U) +#define IPU_MSG_LINK_FOREIGN_KEY_MAX (64U) +#define IPU_MSG_LINK_PBK_ID_DONT_CARE (255U) +#define IPU_MSG_LINK_PBK_SLOT_ID_DONT_CARE (255U) +#define IPU_MSG_LINK_TERM_ID_DONT_CARE (0xffU) + +struct ipu7_msg_link { + struct ia_gofo_tlv_header tlv_header; + struct ipu7_msg_link_ep_pair endpoints; + u16 foreign_key; + u8 streaming_mode; + u8 pbk_id; + u8 pbk_slot_id; + u8 delayed_link; + u8 reserved[2]; + struct ia_gofo_tlv_list link_options; +}; + +#pragma pack(pop) + +enum ipu7_msg_dev_state { + IPU_MSG_DEV_STATE_CLOSED = 0, + IPU_MSG_DEV_STATE_OPEN_WAIT = 1, + IPU_MSG_DEV_STATE_OPEN = 2, + IPU_MSG_DEV_STATE_CLOSE_WAIT = 3, + IPU_MSG_DEV_STATE_N +}; + +enum ipu7_msg_graph_state { + IPU_MSG_GRAPH_STATE_CLOSED = 0, + IPU_MSG_GRAPH_STATE_OPEN_WAIT = 1, + IPU_MSG_GRAPH_STATE_OPEN = 2, + IPU_MSG_GRAPH_STATE_CLOSE_WAIT = 3, + IPU_MSG_GRAPH_STATE_N +}; + +enum ipu7_msg_task_state { + IPU_MSG_TASK_STATE_DONE = 0, + IPU_MSG_TASK_STATE_WAIT_DONE = 1, + IPU_MSG_TASK_STATE_N +}; + +enum ipu7_msg_err_groups { + IPU_MSG_ERR_GROUP_RESERVED = IA_GOFO_MSG_ERR_GROUP_RESERVED, + IPU_MSG_ERR_GROUP_GENERAL = IA_GOFO_MSG_ERR_GROUP_GENERAL, + IPU_MSG_ERR_GROUP_DEVICE = 2, + IPU_MSG_ERR_GROUP_GRAPH = 3, + IPU_MSG_ERR_GROUP_TASK = 4, + IPU_MSG_ERR_GROUP_N, +}; + +#pragma pack(push, 1) +struct ipu7_msg_task { + struct ia_gofo_msg_header header; + u8 graph_id; + u8 profile_idx; + u8 node_ctx_id; + u8 frame_id; + u8 frag_id; + u8 req_done_msg; + u8 req_done_irq; + u8 reserved[1]; + ipu7_msg_teb_t payload_reuse_bm; + ia_gofo_addr_t term_buffers[IPU_MSG_MAX_NODE_TERMS]; +}; + +struct ipu7_msg_task_done { + struct ia_gofo_msg_header_ack header; + u8 graph_id; + u8 frame_id; + u8 node_ctx_id; + u8 profile_idx; + u8 frag_id; + u8 reserved[3]; +}; + +enum ipu7_msg_err_task { + IPU_MSG_ERR_TASK_OK = IA_GOFO_MSG_ERR_OK, + IPU_MSG_ERR_TASK_GRAPH_ID = 1, + IPU_MSG_ERR_TASK_NODE_CTX_ID = 2, + IPU_MSG_ERR_TASK_PROFILE_IDX = 3, + IPU_MSG_ERR_TASK_CTX_MEMORY_TASK = 4, + IPU_MSG_ERR_TASK_TERM_PAYLOAD_PTR = 5, + IPU_MSG_ERR_TASK_FRAME_ID = 6, + IPU_MSG_ERR_TASK_FRAG_ID = 7, + IPU_MSG_ERR_TASK_EXEC_EXT = 8, + IPU_MSG_ERR_TASK_EXEC_SBX = 9, + IPU_MSG_ERR_TASK_EXEC_INT = 10, + IPU_MSG_ERR_TASK_EXEC_UNKNOWN = 11, + IPU_MSG_ERR_TASK_PRE_EXEC = 12, + IPU_MSG_ERR_TASK_N +}; + +#pragma pack(pop) + +#pragma pack(push, 1) +enum ipu7_msg_term_type { + IPU_MSG_TERM_TYPE_PAD = 0, + IPU_MSG_TERM_TYPE_BASE, + IPU_MSG_TERM_TYPE_N, +}; + +#define IPU_MSG_TERM_EVENT_TYPE_NONE 0U +#define IPU_MSG_TERM_EVENT_TYPE_PROGRESS 1U +#define IPU_MSG_TERM_EVENT_TYPE_N (IPU_MSG_TERM_EVENT_TYPE_PROGRESS + 1U) + +struct ipu7_msg_term { + struct ia_gofo_tlv_header tlv_header; + u8 term_id; + u8 event_req_bm; + u8 reserved[2]; + u32 payload_size; + struct ia_gofo_tlv_list term_options; +}; + +enum ipu7_msg_term_option_types { + IPU_MSG_TERM_OPTION_TYPES_PADDING = 0, + IPU_MSG_TERM_OPTION_TYPES_N +}; + +struct ipu7_msg_term_event { + struct ia_gofo_msg_header header; + u8 graph_id; + u8 frame_id; + u8 node_ctx_id; + u8 profile_idx; + u8 frag_id; + u8 term_id; + u8 event_type; + u8 reserved[1]; + u64 event_ts; +}; + +#pragma pack(pop) + +#pragma pack(push, 1) +#define IPU_MSG_DEVICE_SEND_MSG_ENABLED 1U +#define IPU_MSG_DEVICE_SEND_MSG_DISABLED 0U + +#define IPU_MSG_DEVICE_OPEN_SEND_RESP BIT(0) +#define IPU_MSG_DEVICE_OPEN_SEND_IRQ BIT(1) + +#define IPU_MSG_DEVICE_CLOSE_SEND_RESP BIT(0) +#define IPU_MSG_DEVICE_CLOSE_SEND_IRQ BIT(1) + +struct ipu7_msg_dev_open { + struct ia_gofo_msg_header header; + u32 max_graphs; + u8 dev_msg_map; + u8 enable_power_gating; + u8 reserved[2]; +}; + +struct ipu7_msg_dev_open_ack { + struct ia_gofo_msg_header_ack header; +}; + +struct ipu7_msg_dev_close { + struct ia_gofo_msg_header header; + u8 dev_msg_map; + u8 reserved[7]; +}; + +struct ipu7_msg_dev_close_ack { + struct ia_gofo_msg_header_ack header; +}; + +enum ipu7_msg_err_device { + IPU_MSG_ERR_DEVICE_OK = IA_GOFO_MSG_ERR_OK, + IPU_MSG_ERR_DEVICE_MAX_GRAPHS = 1, + IPU_MSG_ERR_DEVICE_MSG_MAP = 2, + IPU_MSG_ERR_DEVICE_N +}; + +#pragma pack(pop) + +#pragma pack(push, 1) +#define IPU_MSG_GRAPH_ID_UNKNOWN (0xffU) +#define IPU_MSG_GRAPH_SEND_MSG_ENABLED 1U +#define IPU_MSG_GRAPH_SEND_MSG_DISABLED 0U + +#define IPU_MSG_GRAPH_OPEN_SEND_RESP BIT(0) +#define IPU_MSG_GRAPH_OPEN_SEND_IRQ BIT(1) + +#define IPU_MSG_GRAPH_CLOSE_SEND_RESP BIT(0) +#define IPU_MSG_GRAPH_CLOSE_SEND_IRQ BIT(1) + +struct ipu7_msg_graph_open { + struct ia_gofo_msg_header header; + struct ia_gofo_tlv_list nodes; + struct ia_gofo_tlv_list links; + u8 graph_id; + u8 graph_msg_map; + u8 reserved[6]; +}; + +enum ipu7_msg_graph_ack_option_types { + IPU_MSG_GRAPH_ACK_OPTION_TYPES_PADDING = 0, + IPU_MSG_GRAPH_ACK_TASK_Q_INFO, + IPU_MSG_GRAPH_ACK_OPTION_TYPES_N +}; + +struct ipu7_msg_graph_open_ack_task_q_info { + struct ia_gofo_tlv_header header; + u8 node_ctx_id; + u8 q_id; + u8 reserved[2]; +}; + +struct ipu7_msg_graph_open_ack { + struct ia_gofo_msg_header_ack header; + u8 graph_id; + u8 reserved[7]; +}; + +struct ipu7_msg_graph_close { + struct ia_gofo_msg_header header; + u8 graph_id; + u8 graph_msg_map; + u8 reserved[6]; +}; + +struct ipu7_msg_graph_close_ack { + struct ia_gofo_msg_header_ack header; + u8 graph_id; + u8 reserved[7]; +}; + +enum ipu7_msg_err_graph { + IPU_MSG_ERR_GRAPH_OK = IA_GOFO_MSG_ERR_OK, + IPU_MSG_ERR_GRAPH_GRAPH_STATE = 1, + IPU_MSG_ERR_GRAPH_MAX_GRAPHS = 2, + IPU_MSG_ERR_GRAPH_GRAPH_ID = 3, + IPU_MSG_ERR_GRAPH_NODE_CTX_ID = 4, + IPU_MSG_ERR_GRAPH_NODE_RSRC_ID = 5, + IPU_MSG_ERR_GRAPH_PROFILE_IDX = 6, + IPU_MSG_ERR_GRAPH_TERM_ID = 7, + IPU_MSG_ERR_GRAPH_TERM_PAYLOAD_SIZE = 8, + IPU_MSG_ERR_GRAPH_LINK_NODE_CTX_ID = 9, + IPU_MSG_ERR_GRAPH_LINK_TERM_ID = 10, + IPU_MSG_ERR_GRAPH_PROFILE_TYPE = 11, + IPU_MSG_ERR_GRAPH_NUM_FRAGS = 12, + IPU_MSG_ERR_GRAPH_QUEUE_ID_USAGE = 13, + IPU_MSG_ERR_GRAPH_QUEUE_OPEN = 14, + IPU_MSG_ERR_GRAPH_QUEUE_CLOSE = 15, + IPU_MSG_ERR_GRAPH_QUEUE_ID_TASK_REQ_MISMATCH = 16, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_FGRAPH = 17, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_NODE = 18, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_NODE_PROFILE = 19, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_TERM = 20, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_LINK = 21, + IPU_MSG_ERR_GRAPH_CTX_MSG_MAP = 22, + IPU_MSG_ERR_GRAPH_CTX_FOREIGN_KEY = 23, + IPU_MSG_ERR_GRAPH_CTX_STREAMING_MODE = 24, + IPU_MSG_ERR_GRAPH_CTX_PBK_RSRC = 25, + IPU_MSG_ERR_GRAPH_UNSUPPORTED_EVENT_TYPE = 26, + IPU_MSG_ERR_GRAPH_TOO_MANY_EVENTS = 27, + IPU_MSG_ERR_GRAPH_CTX_MEMORY_CMPRS = 28, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_ALIGN_INTERVAL = 29, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_PLANE_ID = 30, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_UNSUPPORTED_MODE = 31, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_BIT_DEPTH = 32, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_STRIDE_ALIGNMENT = 33, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_SUB_BUFFER_ALIGNMENT = 34, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_LAYOUT_ORDER = 35, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_LAYOUT_OVERLAP = 36, + IPU_MSG_ERR_GRAPH_CTX_CMPRS_BUFFER_TOO_SMALL = 37, + IPU_MSG_ERR_GRAPH_CTX_DELAYED_LINK = 38, + IPU_MSG_ERR_GRAPH_N +}; + +#pragma pack(pop) + +#define FWPS_MSG_ABI_MAX_INPUT_QUEUES (60U) +#define FWPS_MSG_ABI_MAX_OUTPUT_QUEUES (2U) +#define FWPS_MSG_ABI_MAX_QUEUES \ + (FWPS_MSG_ABI_MAX_OUTPUT_QUEUES + FWPS_MSG_ABI_MAX_INPUT_QUEUES) + +#define FWPS_MSG_ABI_OUT_ACK_QUEUE_ID (IA_GOFO_MSG_ABI_OUT_ACK_QUEUE_ID) +#define FWPS_MSG_ABI_OUT_LOG_QUEUE_ID (IA_GOFO_MSG_ABI_OUT_LOG_QUEUE_ID) +#if (FWPS_MSG_ABI_OUT_LOG_QUEUE_ID >= FWPS_MSG_ABI_MAX_OUTPUT_QUEUES) +#error "Maximum output queues configuration is too small to fit ACK and LOG \ +queues" +#endif +#define FWPS_MSG_ABI_IN_DEV_QUEUE_ID (IA_GOFO_MSG_ABI_IN_DEV_QUEUE_ID) +#define FWPS_MSG_ABI_IN_RESERVED_QUEUE_ID (3U) +#define FWPS_MSG_ABI_IN_FIRST_TASK_QUEUE_ID \ + (FWPS_MSG_ABI_IN_RESERVED_QUEUE_ID + 1U) + +#if (FWPS_MSG_ABI_IN_FIRST_TASK_QUEUE_ID >= FWPS_MSG_ABI_MAX_INPUT_QUEUES) +#error "Maximum queues configuration is too small to fit minimum number of \ +useful queues" +#endif + +#define FWPS_MSG_ABI_IN_LAST_TASK_QUEUE_ID (FWPS_MSG_ABI_MAX_QUEUES - 1U) +#define FWPS_MSG_ABI_IN_MAX_TASK_QUEUES \ + (FWPS_MSG_ABI_IN_LAST_TASK_QUEUE_ID - \ + FWPS_MSG_ABI_IN_FIRST_TASK_QUEUE_ID + 1U) +#define FWPS_MSG_ABI_OUT_FIRST_QUEUE_ID (FWPS_MSG_ABI_OUT_ACK_QUEUE_ID) +#define FWPS_MSG_ABI_OUT_LAST_QUEUE_ID (FWPS_MSG_ABI_MAX_OUTPUT_QUEUES - 1U) +#define FWPS_MSG_ABI_IN_FIRST_QUEUE_ID (FWPS_MSG_ABI_IN_DEV_QUEUE_ID) +#define FWPS_MSG_ABI_IN_LAST_QUEUE_ID (FWPS_MSG_ABI_IN_LAST_TASK_QUEUE_ID) + +#define FWPS_MSG_HOST2FW_MAX_SIZE (2U * 1024U) +#define FWPS_MSG_FW2HOST_MAX_SIZE (256U) + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_psys_config_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_psys_config_abi.h new file mode 100644 index 000000000000..0af04c8c6a88 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_psys_config_abi.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_PSYS_CONFIG_ABI_H_INCLUDED__ +#define IPU7_PSYS_CONFIG_ABI_H_INCLUDED__ + +#include + +#include "ipu7_fw_boot_abi.h" +#include "ipu7_fw_config_abi.h" + +struct ipu7_psys_config { + u32 use_debug_manifest; + u32 timeout_val_ms; + u32 compression_support_enabled; + struct ia_gofo_logger_config logger_config; + struct ipu7_wdt_abi wdt_config; + u8 ipu_psys_debug_bitmask; + u8 padding[3]; +}; + +#endif diff --git a/drivers/staging/media/ipu7/abi/ipu7_fw_syscom_abi.h b/drivers/staging/media/ipu7/abi/ipu7_fw_syscom_abi.h new file mode 100644 index 000000000000..bfa5258d5b97 --- /dev/null +++ b/drivers/staging/media/ipu7/abi/ipu7_fw_syscom_abi.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_SYSCOM_ABI_H +#define IPU7_FW_SYSCOM_ABI_H + +#include + +#include "ipu7_fw_common_abi.h" + +#pragma pack(push, 1) +#define SYSCOM_QUEUE_MIN_CAPACITY 2U + +struct syscom_queue_params_config { + ia_gofo_addr_t token_array_mem; + u16 token_size_in_bytes; + u16 max_capacity; +}; + +struct syscom_config_s { + u16 max_output_queues; + u16 max_input_queues; +}; + +#pragma pack(pop) + +static inline struct syscom_queue_params_config * +syscom_config_get_queue_configs(struct syscom_config_s *config) +{ + return (struct syscom_queue_params_config *)(&config[1]); +} + +static inline const struct syscom_queue_params_config * +syscom_config_get_queue_configs_const(const struct syscom_config_s *config) +{ + return (const struct syscom_queue_params_config *)(&config[1]); +} + +#pragma pack(push, 1) +struct syscom_queue_indices_s { + u32 read_index; + u32 write_index; +}; + +#pragma pack(pop) + +#endif From a516d36bdc3d8373f904af57c95e76d6f921cf1c Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:21 +0800 Subject: [PATCH 216/310] media: staging/ipu7: add IPU7 input system device driver The main input system driver mainly cover the basic hardware setup, v4l2 devices registration, firmware stream interfaces and interrupt handling. Input system CSI2 receiver is exposed as a v4l2 sub-device. Each CSI2 sub-device represent one single CSI2 hardware port which be linked with external sub-device such camera sensor by linked with ISYS CSI2's sink pad. The CSI2 source pad is linked to the sink pad of video capture device. Register V4L2 video device and setup the VB2 queues to support video capture. Video streaming callback will trigger the input system driver to construct a input system stream configuration for firmware based on data type and stream ID and then queue buffers to firmware to do capture. IPU7 CSI-2 D-PHY hardware is a Synopsys DWC MIPI CSI2 Rx IP, the driver program the DPHY to receive MIPI data from camera sensors. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-fw-isys.c | 301 +++++ drivers/staging/media/ipu7/ipu7-fw-isys.h | 39 + .../staging/media/ipu7/ipu7-isys-csi-phy.c | 1034 ++++++++++++++ .../staging/media/ipu7/ipu7-isys-csi-phy.h | 16 + .../staging/media/ipu7/ipu7-isys-csi2-regs.h | 1197 +++++++++++++++++ drivers/staging/media/ipu7/ipu7-isys-csi2.c | 543 ++++++++ drivers/staging/media/ipu7/ipu7-isys-csi2.h | 64 + drivers/staging/media/ipu7/ipu7-isys-queue.c | 829 ++++++++++++ drivers/staging/media/ipu7/ipu7-isys-queue.h | 72 + drivers/staging/media/ipu7/ipu7-isys-subdev.c | 348 +++++ drivers/staging/media/ipu7/ipu7-isys-subdev.h | 53 + drivers/staging/media/ipu7/ipu7-isys-video.c | 1112 +++++++++++++++ drivers/staging/media/ipu7/ipu7-isys-video.h | 117 ++ drivers/staging/media/ipu7/ipu7-isys.c | 1166 ++++++++++++++++ drivers/staging/media/ipu7/ipu7-isys.h | 140 ++ drivers/staging/media/ipu7/ipu7.c | 6 +- 16 files changed, 7033 insertions(+), 4 deletions(-) create mode 100644 drivers/staging/media/ipu7/ipu7-fw-isys.c create mode 100644 drivers/staging/media/ipu7/ipu7-fw-isys.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-csi-phy.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys-csi-phy.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-csi2.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys-csi2.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-queue.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys-queue.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-subdev.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys-subdev.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys-video.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys-video.h create mode 100644 drivers/staging/media/ipu7/ipu7-isys.c create mode 100644 drivers/staging/media/ipu7/ipu7-isys.h diff --git a/drivers/staging/media/ipu7/ipu7-fw-isys.c b/drivers/staging/media/ipu7/ipu7-fw-isys.c new file mode 100644 index 000000000000..e4b9c364572b --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-fw-isys.c @@ -0,0 +1,301 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include + +#include "abi/ipu7_fw_insys_config_abi.h" +#include "abi/ipu7_fw_isys_abi.h" + +#include "ipu7.h" +#include "ipu7-boot.h" +#include "ipu7-bus.h" +#include "ipu7-dma.h" +#include "ipu7-fw-isys.h" +#include "ipu7-isys.h" +#include "ipu7-platform-regs.h" +#include "ipu7-syscom.h" + +static const char * const send_msg_types[N_IPU_INSYS_SEND_TYPE] = { + "STREAM_OPEN", + "STREAM_START_AND_CAPTURE", + "STREAM_CAPTURE", + "STREAM_ABORT", + "STREAM_FLUSH", + "STREAM_CLOSE" +}; + +int ipu7_fw_isys_complex_cmd(struct ipu7_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type) +{ + struct ipu7_syscom_context *ctx = isys->adev->syscom; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu7_insys_send_queue_token *token; + + if (send_type >= N_IPU_INSYS_SEND_TYPE) + return -EINVAL; + + dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]); + + /* + * Time to flush cache in case we have some payload. Not all messages + * have that + */ + if (cpu_mapped_buf) + clflush_cache_range(cpu_mapped_buf, size); + + token = ipu7_syscom_get_token(ctx, stream_handle + + IPU_INSYS_INPUT_MSG_QUEUE); + if (!token) + return -EBUSY; + + token->addr = dma_mapped_buf; + token->buf_handle = (unsigned long)cpu_mapped_buf; + token->send_type = send_type; + token->stream_id = stream_handle; + token->flag = IPU_INSYS_SEND_QUEUE_TOKEN_FLAG_NONE; + + ipu7_syscom_put_token(ctx, stream_handle + IPU_INSYS_INPUT_MSG_QUEUE); + /* now wakeup FW */ + ipu_buttress_wakeup_is_uc(isys->adev->isp); + + return 0; +} + +int ipu7_fw_isys_simple_cmd(struct ipu7_isys *isys, + const unsigned int stream_handle, u16 send_type) +{ + return ipu7_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, + send_type); +} + +int ipu7_fw_isys_init(struct ipu7_isys *isys) +{ + struct syscom_queue_config *queue_configs; + struct ipu7_bus_device *adev = isys->adev; + struct device *dev = &adev->auxdev.dev; + struct ipu7_insys_config *isys_config; + struct ipu7_syscom_context *syscom; + dma_addr_t isys_config_dma_addr; + unsigned int i, num_queues; + u32 freq; + u8 major; + int ret; + + /* Allocate and init syscom context. */ + syscom = devm_kzalloc(dev, sizeof(struct ipu7_syscom_context), + GFP_KERNEL); + if (!syscom) + return -ENOMEM; + + adev->syscom = syscom; + syscom->num_input_queues = IPU_INSYS_MAX_INPUT_QUEUES; + syscom->num_output_queues = IPU_INSYS_MAX_OUTPUT_QUEUES; + num_queues = syscom->num_input_queues + syscom->num_output_queues; + queue_configs = devm_kzalloc(dev, FW_QUEUE_CONFIG_SIZE(num_queues), + GFP_KERNEL); + if (!queue_configs) { + ipu7_fw_isys_release(isys); + return -ENOMEM; + } + syscom->queue_configs = queue_configs; + queue_configs[IPU_INSYS_OUTPUT_MSG_QUEUE].max_capacity = + IPU_ISYS_SIZE_RECV_QUEUE; + queue_configs[IPU_INSYS_OUTPUT_MSG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU_INSYS_OUTPUT_LOG_QUEUE].max_capacity = + IPU_ISYS_SIZE_LOG_QUEUE; + queue_configs[IPU_INSYS_OUTPUT_LOG_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_resp); + queue_configs[IPU_INSYS_OUTPUT_RESERVED_QUEUE].max_capacity = 0; + queue_configs[IPU_INSYS_OUTPUT_RESERVED_QUEUE].token_size_in_bytes = 0; + + queue_configs[IPU_INSYS_INPUT_DEV_QUEUE].max_capacity = + IPU_ISYS_MAX_STREAMS; + queue_configs[IPU_INSYS_INPUT_DEV_QUEUE].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + + for (i = IPU_INSYS_INPUT_MSG_QUEUE; i < num_queues; i++) { + queue_configs[i].max_capacity = IPU_ISYS_SIZE_SEND_QUEUE; + queue_configs[i].token_size_in_bytes = + sizeof(struct ipu7_insys_send_queue_token); + } + + /* Allocate ISYS subsys config. */ + isys_config = ipu7_dma_alloc(adev, sizeof(struct ipu7_insys_config), + &isys_config_dma_addr, GFP_KERNEL, 0); + if (!isys_config) { + dev_err(dev, "Failed to allocate isys subsys config.\n"); + ipu7_fw_isys_release(isys); + return -ENOMEM; + } + isys->subsys_config = isys_config; + isys->subsys_config_dma_addr = isys_config_dma_addr; + memset(isys_config, 0, sizeof(struct ipu7_insys_config)); + isys_config->logger_config.use_source_severity = 0; + isys_config->logger_config.use_channels_enable_bitmask = 1; + isys_config->logger_config.channels_enable_bitmask = + LOGGER_CONFIG_CHANNEL_ENABLE_SYSCOM_BITMASK; + isys_config->logger_config.hw_printf_buffer_base_addr = 0U; + isys_config->logger_config.hw_printf_buffer_size_bytes = 0U; + isys_config->wdt_config.wdt_timer1_us = 0; + isys_config->wdt_config.wdt_timer2_us = 0; + ret = ipu_buttress_get_isys_freq(adev->isp, &freq); + if (ret) { + dev_err(dev, "Failed to get ISYS frequency.\n"); + ipu7_fw_isys_release(isys); + return ret; + } + + ipu7_dma_sync_single(adev, isys_config_dma_addr, + sizeof(struct ipu7_insys_config)); + + major = is_ipu8(adev->isp->hw_ver) ? 2U : 1U; + ret = ipu7_boot_init_boot_config(adev, queue_configs, num_queues, + freq, isys_config_dma_addr, major); + if (ret) + ipu7_fw_isys_release(isys); + + return ret; +} + +void ipu7_fw_isys_release(struct ipu7_isys *isys) +{ + struct ipu7_bus_device *adev = isys->adev; + + ipu7_boot_release_boot_config(adev); + if (isys->subsys_config) { + ipu7_dma_free(adev, + sizeof(struct ipu7_insys_config), + isys->subsys_config, + isys->subsys_config_dma_addr, 0); + isys->subsys_config = NULL; + isys->subsys_config_dma_addr = 0; + } +} + +int ipu7_fw_isys_open(struct ipu7_isys *isys) +{ + return ipu7_boot_start_fw(isys->adev); +} + +int ipu7_fw_isys_close(struct ipu7_isys *isys) +{ + return ipu7_boot_stop_fw(isys->adev); +} + +struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu7_isys *isys) +{ + return (struct ipu7_insys_resp *) + ipu7_syscom_get_token(isys->adev->syscom, + IPU_INSYS_OUTPUT_MSG_QUEUE); +} + +void ipu7_fw_isys_put_resp(struct ipu7_isys *isys) +{ + ipu7_syscom_put_token(isys->adev->syscom, IPU_INSYS_OUTPUT_MSG_QUEUE); +} + +void ipu7_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu7_insys_stream_cfg *cfg) +{ + unsigned int i; + + dev_dbg(dev, "---------------------------\n"); + dev_dbg(dev, "IPU_FW_ISYS_STREAM_CFG_DATA\n"); + + dev_dbg(dev, ".port id %d\n", cfg->port_id); + dev_dbg(dev, ".vc %d\n", cfg->vc); + dev_dbg(dev, ".nof_input_pins = %d\n", cfg->nof_input_pins); + dev_dbg(dev, ".nof_output_pins = %d\n", cfg->nof_output_pins); + dev_dbg(dev, ".stream_msg_map = 0x%x\n", cfg->stream_msg_map); + + for (i = 0; i < cfg->nof_input_pins; i++) { + dev_dbg(dev, ".input_pin[%d]:\n", i); + dev_dbg(dev, "\t.dt = 0x%0x\n", + cfg->input_pins[i].dt); + dev_dbg(dev, "\t.disable_mipi_unpacking = %d\n", + cfg->input_pins[i].disable_mipi_unpacking); + dev_dbg(dev, "\t.dt_rename_mode = %d\n", + cfg->input_pins[i].dt_rename_mode); + dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", + cfg->input_pins[i].mapped_dt); + dev_dbg(dev, "\t.input_res = %d x %d\n", + cfg->input_pins[i].input_res.width, + cfg->input_pins[i].input_res.height); + dev_dbg(dev, "\t.sync_msg_map = 0x%x\n", + cfg->input_pins[i].sync_msg_map); + } + + for (i = 0; i < cfg->nof_output_pins; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.input_pin_id = %d\n", + cfg->output_pins[i].input_pin_id); + dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); + dev_dbg(dev, "\t.send_irq = %d\n", + cfg->output_pins[i].send_irq); + dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); + + dev_dbg(dev, "\t.link.buffer_lines = %d\n", + cfg->output_pins[i].link.buffer_lines); + dev_dbg(dev, "\t.link.foreign_key = %d\n", + cfg->output_pins[i].link.foreign_key); + dev_dbg(dev, "\t.link.granularity_pointer_update = %d\n", + cfg->output_pins[i].link.granularity_pointer_update); + dev_dbg(dev, "\t.link.msg_link_streaming_mode = %d\n", + cfg->output_pins[i].link.msg_link_streaming_mode); + dev_dbg(dev, "\t.link.pbk_id = %d\n", + cfg->output_pins[i].link.pbk_id); + dev_dbg(dev, "\t.link.pbk_slot_id = %d\n", + cfg->output_pins[i].link.pbk_slot_id); + dev_dbg(dev, "\t.link.dest = %d\n", + cfg->output_pins[i].link.dest); + dev_dbg(dev, "\t.link.use_sw_managed = %d\n", + cfg->output_pins[i].link.use_sw_managed); + dev_dbg(dev, "\t.link.is_snoop = %d\n", + cfg->output_pins[i].link.is_snoop); + + dev_dbg(dev, "\t.crop.line_top = %d\n", + cfg->output_pins[i].crop.line_top); + dev_dbg(dev, "\t.crop.line_bottom = %d\n", + cfg->output_pins[i].crop.line_bottom); + + dev_dbg(dev, "\t.dpcm_enable = %d\n", + cfg->output_pins[i].dpcm.enable); + dev_dbg(dev, "\t.dpcm.type = %d\n", + cfg->output_pins[i].dpcm.type); + dev_dbg(dev, "\t.dpcm.predictor = %d\n", + cfg->output_pins[i].dpcm.predictor); + } + dev_dbg(dev, "---------------------------\n"); +} + +void ipu7_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu7_insys_buffset *buf, + unsigned int outputs) +{ + unsigned int i; + + dev_dbg(dev, "--------------------------\n"); + dev_dbg(dev, "IPU_ISYS_BUFF_SET\n"); + dev_dbg(dev, ".capture_msg_map = %d\n", buf->capture_msg_map); + dev_dbg(dev, ".frame_id = %d\n", buf->frame_id); + dev_dbg(dev, ".skip_frame = %d\n", buf->skip_frame); + + for (i = 0; i < outputs; i++) { + dev_dbg(dev, ".output_pin[%d]:\n", i); + dev_dbg(dev, "\t.user_token = %llx\n", + buf->output_pins[i].user_token); + dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); + } + dev_dbg(dev, "---------------------------\n"); +} diff --git a/drivers/staging/media/ipu7/ipu7-fw-isys.h b/drivers/staging/media/ipu7/ipu7-fw-isys.h new file mode 100644 index 000000000000..b556feda6b08 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-fw-isys.h @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_FW_ISYS_H +#define IPU7_FW_ISYS_H + +#include + +#include "abi/ipu7_fw_isys_abi.h" + +struct device; +struct ipu7_insys_buffset; +struct ipu7_insys_stream_cfg; +struct ipu7_isys; + +/* From here on type defines not coming from the ISYSAPI interface */ + +int ipu7_fw_isys_init(struct ipu7_isys *isys); +void ipu7_fw_isys_release(struct ipu7_isys *isys); +int ipu7_fw_isys_open(struct ipu7_isys *isys); +int ipu7_fw_isys_close(struct ipu7_isys *isys); + +void ipu7_fw_isys_dump_stream_cfg(struct device *dev, + struct ipu7_insys_stream_cfg *cfg); +void ipu7_fw_isys_dump_frame_buff_set(struct device *dev, + struct ipu7_insys_buffset *buf, + unsigned int outputs); +int ipu7_fw_isys_simple_cmd(struct ipu7_isys *isys, + const unsigned int stream_handle, u16 send_type); +int ipu7_fw_isys_complex_cmd(struct ipu7_isys *isys, + const unsigned int stream_handle, + void *cpu_mapped_buf, + dma_addr_t dma_mapped_buf, + size_t size, u16 send_type); +struct ipu7_insys_resp *ipu7_fw_isys_get_resp(struct ipu7_isys *isys); +void ipu7_fw_isys_put_resp(struct ipu7_isys *isys); +#endif diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c new file mode 100644 index 000000000000..4407750c7f40 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c @@ -0,0 +1,1034 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-buttress.h" +#include "ipu7-isys.h" +#include "ipu7-isys-csi2.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-platform-regs.h" +#include "ipu7-isys-csi-phy.h" + +#define PORT_A 0U +#define PORT_B 1U +#define PORT_C 2U +#define PORT_D 3U + +#define N_DATA_IDS 8U +static DECLARE_BITMAP(data_ids, N_DATA_IDS); + +struct ddlcal_counter_ref_s { + u16 min_mbps; + u16 max_mbps; + + u16 ddlcal_counter_ref; +}; + +struct ddlcal_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lanex_hsrx_cdphy_sel_fast; + u16 ddlcal_max_phase; + u16 phase_bound; + u16 ddlcal_dll_fbk; + u16 ddlcal_ddl_coarse_bank; + u16 fjump_deskew; + u16 min_eye_opening_deskew; +}; + +struct i_thssettle_params { + u16 min_mbps; + u16 max_mbps; + u16 i_thssettle; +}; + + /* lane2 for 4l3t, lane1 for 2l2t */ +struct oa_lane_clk_div_params { + u16 min_mbps; + u16 max_mbps; + u16 oa_lane_hsrx_hs_clk_div; +}; + +struct cdr_fbk_cap_prog_params { + u16 min_mbps; + u16 max_mbps; + u16 val; +}; + +static const struct ddlcal_counter_ref_s table0[] = { + { 1500, 1999, 118 }, + { 2000, 2499, 157 }, + { 2500, 3499, 196 }, + { 3500, 4499, 274 }, + { 4500, 4500, 352 }, + { } +}; + +static const struct ddlcal_params table1[] = { + { 1500, 1587, 0, 143, 167, 17, 3, 4, 29 }, + { 1588, 1687, 0, 135, 167, 15, 3, 4, 27 }, + { 1688, 1799, 0, 127, 135, 15, 2, 4, 26 }, + { 1800, 1928, 0, 119, 135, 13, 2, 3, 24 }, + { 1929, 2076, 0, 111, 135, 13, 2, 3, 23 }, + { 2077, 2249, 0, 103, 135, 11, 2, 3, 21 }, + { 2250, 2454, 0, 95, 103, 11, 1, 3, 19 }, + { 2455, 2699, 0, 87, 103, 9, 1, 3, 18 }, + { 2700, 2999, 0, 79, 103, 9, 1, 2, 16 }, + { 3000, 3229, 0, 71, 71, 7, 1, 2, 15 }, + { 3230, 3599, 1, 87, 103, 9, 1, 3, 18 }, + { 3600, 3999, 1, 79, 103, 9, 1, 2, 16 }, + { 4000, 4499, 1, 71, 103, 7, 1, 2, 15 }, + { 4500, 4500, 1, 63, 71, 7, 0, 2, 13 }, + { } +}; + +static const struct i_thssettle_params table2[] = { + { 80, 124, 24 }, + { 125, 249, 20 }, + { 250, 499, 16 }, + { 500, 749, 14 }, + { 750, 1499, 13 }, + { 1500, 4500, 12 }, + { } +}; + +static const struct oa_lane_clk_div_params table6[] = { + { 80, 159, 0x1 }, + { 160, 319, 0x2 }, + { 320, 639, 0x3 }, + { 640, 1279, 0x4 }, + { 1280, 2560, 0x5 }, + { 2561, 4500, 0x6 }, + { } +}; + +static const struct cdr_fbk_cap_prog_params table7[] = { + { 80, 919, 0 }, + { 920, 1029, 1 }, + { 1030, 1169, 2 }, + { 1170, 1349, 3 }, + { 1350, 1589, 4 }, + { 1590, 1949, 5 }, + { 1950, 2499, 6 }, + { } +}; + +static void dwc_phy_write(struct ipu7_isys *isys, u32 id, u32 addr, u16 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IS_IO_CDPHY_BASE(id); + + dev_dbg(&isys->adev->auxdev.dev, "phy write: reg 0x%zx = data 0x%04x", + base + addr - isys_base, data); + writew(data, base + addr); +} + +static u16 dwc_phy_read(struct ipu7_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IS_IO_CDPHY_BASE(id); + u16 data; + + data = readw(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "phy read: reg 0x%zx = data 0x%04x", + base + addr - isys_base, data); + + return data; +} + +static void dwc_csi_write(struct ipu7_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IS_IO_CSI2_HOST_BASE(id); + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "csi write: reg 0x%zx = data 0x%08x", + base + addr - isys_base, data); + writel(data, base + addr); + dev_dbg(dev, "csi read: reg 0x%zx = data 0x%08x", + base + addr - isys_base, readl(base + addr)); +} + +static void gpreg_write(struct ipu7_isys *isys, u32 id, u32 addr, u32 data) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg = isys->pdata->ipdata->csi2.gpreg; + void __iomem *base = isys_base + gpreg + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + + dev_dbg(dev, "gpreg write: reg 0x%zx = data 0x%08x", + base + addr - isys_base, data); + writel(data, base + addr); + dev_dbg(dev, "gpreg read: reg 0x%zx = data 0x%08x", + base + addr - isys_base, readl(base + addr)); +} + +static u32 dwc_csi_read(struct ipu7_isys *isys, u32 id, u32 addr) +{ + void __iomem *isys_base = isys->pdata->base; + void __iomem *base = isys_base + IS_IO_CSI2_HOST_BASE(id); + u32 data; + + data = readl(base + addr); + dev_dbg(&isys->adev->auxdev.dev, "csi read: reg 0x%zx = data 0x%x", + base + addr - isys_base, data); + + return data; +} + +static void dwc_phy_write_mask(struct ipu7_isys *isys, u32 id, u32 addr, + u16 val, u8 lo, u8 hi) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + WARN_ON(hi > 15); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_phy_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_phy_write(isys, id, addr, temp); +} + +static void dwc_csi_write_mask(struct ipu7_isys *isys, u32 id, u32 addr, + u32 val, u8 hi, u8 lo) +{ + u32 temp, mask; + + WARN_ON(lo > hi); + + mask = ((~0U - (1U << lo) + 1U)) & (~0U >> (31 - hi)); + temp = dwc_csi_read(isys, id, addr); + temp &= ~mask; + temp |= (val << lo) & mask; + dwc_csi_write(isys, id, addr, temp); +} + +static void ipu7_isys_csi_ctrl_cfg(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; + u32 id, lanes, phy_mode; + u32 val; + + id = csi2->port; + lanes = csi2->nlanes; + phy_mode = csi2->phy_mode; + dev_dbg(dev, "csi-%d controller init with %u lanes, phy mode %u", + id, lanes, phy_mode); + + val = dwc_csi_read(isys, id, VERSION); + dev_dbg(dev, "csi-%d controller version = 0x%x", id, val); + + /* num of active data lanes */ + dwc_csi_write(isys, id, N_LANES, lanes - 1); + dwc_csi_write(isys, id, CDPHY_MODE, phy_mode); + dwc_csi_write(isys, id, VC_EXTENSION, 0); + + /* only mask PHY_FATAL and PKT_FATAL interrupts */ + dwc_csi_write(isys, id, INT_MSK_PHY_FATAL, 0xff); + dwc_csi_write(isys, id, INT_MSK_PKT_FATAL, 0x3); + dwc_csi_write(isys, id, INT_MSK_PHY, 0x0); + dwc_csi_write(isys, id, INT_MSK_LINE, 0x0); + dwc_csi_write(isys, id, INT_MSK_BNDRY_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, INT_MSK_SEQ_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, INT_MSK_CRC_FRAME_FATAL, 0x0); + dwc_csi_write(isys, id, INT_MSK_PLD_CRC_FATAL, 0x0); + dwc_csi_write(isys, id, INT_MSK_DATA_ID, 0x0); + dwc_csi_write(isys, id, INT_MSK_ECC_CORRECTED, 0x0); +} + +static void ipu7_isys_csi_phy_reset(struct ipu7_isys *isys, u32 id) +{ + dwc_csi_write(isys, id, PHY_SHUTDOWNZ, 0); + dwc_csi_write(isys, id, DPHY_RSTZ, 0); + dwc_csi_write(isys, id, CSI2_RESETN, 0); + gpreg_write(isys, id, PHY_RESET, 0); + gpreg_write(isys, id, PHY_SHUTDOWN, 0); +} + +/* 8 Data ID monitors, each Data ID is composed by pair of VC and data type */ +static int __dids_config(struct ipu7_isys_csi2 *csi2, u32 id, u8 vc, u8 dt) +{ + struct ipu7_isys *isys = csi2->isys; + u32 reg, n; + u8 lo, hi; + int ret; + + dev_dbg(&isys->adev->auxdev.dev, "config CSI-%u with vc:%u dt:0x%02x\n", + id, vc, dt); + + dwc_csi_write(isys, id, VC_EXTENSION, 0x0); + n = find_first_zero_bit(data_ids, N_DATA_IDS); + if (n == N_DATA_IDS) + return -ENOSPC; + + ret = test_and_set_bit(n, data_ids); + if (ret) + return -EBUSY; + + reg = n < 4 ? DATA_IDS_VC_1 : DATA_IDS_VC_2; + lo = (n % 4) * 8; + hi = lo + 4; + dwc_csi_write_mask(isys, id, reg, vc & GENMASK(4, 0), hi, lo); + + reg = n < 4 ? DATA_IDS_1 : DATA_IDS_2; + lo = (n % 4) * 8; + hi = lo + 5; + dwc_csi_write_mask(isys, id, reg, dt & GENMASK(5, 0), hi, lo); + + return 0; +} + +static int ipu7_isys_csi_ctrl_dids_config(struct ipu7_isys_csi2 *csi2, u32 id) +{ + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc desc; + struct v4l2_subdev *ext_sd; + struct media_pad *pad; + unsigned int i; + int ret; + + pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + if (IS_ERR(pad)) { + dev_warn(dev, "can't get remote source pad of %s (%ld)\n", + csi2->asd.sd.name, PTR_ERR(pad)); + return PTR_ERR(pad); + } + + ext_sd = media_entity_to_v4l2_subdev(pad->entity); + if (WARN(!ext_sd, "Failed to get subdev for entity %s\n", + pad->entity->name)) + return -ENODEV; + + ret = v4l2_subdev_call(ext_sd, pad, get_frame_desc, pad->index, &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_warn(dev, "Unsupported frame descriptor type\n"); + return -EINVAL; + } + + for (i = 0; i < desc.num_entries; i++) { + desc_entry = &desc.entry[i]; + if (desc_entry->bus.csi2.vc < IPU7_NR_OF_CSI2_VC) { + ret = __dids_config(csi2, id, desc_entry->bus.csi2.vc, + desc_entry->bus.csi2.dt); + if (ret) + return ret; + } + } + + return 0; +} + +#define CDPHY_TIMEOUT 5000000U +static int ipu7_isys_phy_ready(struct ipu7_isys *isys, u32 id) +{ + void __iomem *isys_base = isys->pdata->base; + u32 gpreg_offset = isys->pdata->ipdata->csi2.gpreg; + void __iomem *gpreg = isys_base + gpreg_offset + 0x1000 * id; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + u32 phy_ready; + u32 reg, rext; + int ret; + + dev_dbg(dev, "waiting phy ready...\n"); + ret = readl_poll_timeout(gpreg + PHY_READY, phy_ready, + phy_ready & BIT(0) && phy_ready != ~0U, + 100, CDPHY_TIMEOUT); + dev_dbg(dev, "phy %u ready = 0x%08x\n", id, readl(gpreg + PHY_READY)); + dev_dbg(dev, "csi %u PHY_RX = 0x%08x\n", id, + dwc_csi_read(isys, id, PHY_RX)); + dev_dbg(dev, "csi %u PHY_STOPSTATE = 0x%08x\n", id, + dwc_csi_read(isys, id, PHY_STOPSTATE)); + dev_dbg(dev, "csi %u PHY_CAL = 0x%08x\n", id, + dwc_csi_read(isys, id, PHY_CAL)); + for (i = 0; i < 4U; i++) { + reg = CORE_DIG_DLANE_0_R_HS_RX_0 + (i * 0x400U); + dev_dbg(dev, "phy %u DLANE%u skewcal = 0x%04x\n", + id, i, dwc_phy_read(isys, id, reg)); + } + dev_dbg(dev, "phy %u DDLCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5)); + dev_dbg(dev, "phy %u TERMCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, PPI_R_TERMCAL_DEBUG_0)); + dev_dbg(dev, "phy %u LPDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, PPI_R_LPDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u HSDCOCAL = 0x%04x\n", id, + dwc_phy_read(isys, id, PPI_R_HSDCOCAL_DEBUG_RB)); + dev_dbg(dev, "phy %u LPDCOCAL_VT = 0x%04x\n", id, + dwc_phy_read(isys, id, PPI_R_LPDCOCAL_DEBUG_VT)); + + if (!ret) { + if (id) { + dev_dbg(dev, "ignore phy %u rext\n", id); + return 0; + } + + rext = dwc_phy_read(isys, id, + CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15) & 0xfU; + dev_dbg(dev, "phy %u rext value = %u\n", id, rext); + isys->phy_rext_cal = (rext ? rext : 5); + + return 0; + } + + dev_err(dev, "wait phy ready timeout!\n"); + + return ret; +} + +static int lookup_table1(u64 mbps) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(table1); i++) { + if (mbps >= table1[i].min_mbps && mbps <= table1[i].max_mbps) + return i; + } + + return -ENXIO; +} + +static const u16 deskew_fine_mem[] = { + 0x0404, 0x040c, 0x0414, 0x041c, + 0x0423, 0x0429, 0x0430, 0x043a, + 0x0445, 0x044a, 0x0450, 0x045a, + 0x0465, 0x0469, 0x0472, 0x047a, + 0x0485, 0x0489, 0x0490, 0x049a, + 0x04a4, 0x04ac, 0x04b4, 0x04bc, + 0x04c4, 0x04cc, 0x04d4, 0x04dc, + 0x04e4, 0x04ec, 0x04f4, 0x04fc, + 0x0504, 0x050c, 0x0514, 0x051c, + 0x0523, 0x0529, 0x0530, 0x053a, + 0x0545, 0x054a, 0x0550, 0x055a, + 0x0565, 0x0569, 0x0572, 0x057a, + 0x0585, 0x0589, 0x0590, 0x059a, + 0x05a4, 0x05ac, 0x05b4, 0x05bc, + 0x05c4, 0x05cc, 0x05d4, 0x05dc, + 0x05e4, 0x05ec, 0x05f4, 0x05fc, + 0x0604, 0x060c, 0x0614, 0x061c, + 0x0623, 0x0629, 0x0632, 0x063a, + 0x0645, 0x064a, 0x0650, 0x065a, + 0x0665, 0x0669, 0x0672, 0x067a, + 0x0685, 0x0689, 0x0690, 0x069a, + 0x06a4, 0x06ac, 0x06b4, 0x06bc, + 0x06c4, 0x06cc, 0x06d4, 0x06dc, + 0x06e4, 0x06ec, 0x06f4, 0x06fc, + 0x0704, 0x070c, 0x0714, 0x071c, + 0x0723, 0x072a, 0x0730, 0x073a, + 0x0745, 0x074a, 0x0750, 0x075a, + 0x0765, 0x0769, 0x0772, 0x077a, + 0x0785, 0x0789, 0x0790, 0x079a, + 0x07a4, 0x07ac, 0x07b4, 0x07bc, + 0x07c4, 0x07cc, 0x07d4, 0x07dc, + 0x07e4, 0x07ec, 0x07f4, 0x07fc, +}; + +static void ipu7_isys_dphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + u16 hsrxval0 = 0; + u16 hsrxval1 = 0; + u16 hsrxval2 = 0; + int index; + u16 reg; + u16 val; + u32 i; + + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_7, 0, 0, 9); + if (mbps > 1500) + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_7, + 40, 0, 7); + else + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_7, + 104, 0, 7); + + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_8, 80, 0, 7); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_0, 191, 0, 9); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_7, 34, 7, 12); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_1, 38, 8, 15); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_2, 4, 12, 15); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_2, 2, 10, 11); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_2, 1, 8, 8); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_2, 38, 0, 7); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_2, 1, 9, 9); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_4, 10, 0, 9); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_6, 20, 0, 9); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_7, 19, 0, 6); + + for (i = 0; i < ARRAY_SIZE(table0); i++) { + if (mbps >= table0[i].min_mbps && mbps <= table0[i].max_mbps) { + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_3, + table0[i].ddlcal_counter_ref, + 0, 9); + break; + } + } + + index = lookup_table1(mbps); + if (index >= 0) { + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_1, + table1[index].phase_bound, 0, 7); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_dll_fbk, 4, 9); + dwc_phy_write_mask(isys, id, PPI_RW_DDLCAL_CFG_5, + table1[index].ddlcal_ddl_coarse_bank, 0, 3); + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8; + val = table1[index].oa_lanex_hsrx_cdphy_sel_fast; + for (i = 0; i < lanes + 1; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 12, 12); + } + + reg = CORE_DIG_DLANE_0_RW_LP_0; + for (i = 0; i < lanes; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, + 0, 0, 0); + if (!is_ipu7(isys->adev->isp->hw_ver) || + id == PORT_B || id == PORT_C) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 0, 0, 0); + } else { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, + 1, 0, 0); + } + + if (lanes == 4 && is_ipu7(isys->adev->isp->hw_ver)) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 0, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_6, 1, 0, 2); + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_6, 1, 3, 5); + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), !val, 3, 3); + } + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13; + val = (mbps > 1500) ? 0 : 1; + for (i = 0; i < lanes + 1; i++) { + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 3, 3); + } + + if (!is_ipu7(isys->adev->isp->hw_ver) || id == PORT_B || id == PORT_C) + reg = CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9; + else + reg = CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9; + + for (i = 0; i < ARRAY_SIZE(table6); i++) { + if (mbps >= table6[i].min_mbps && mbps <= table6[i].max_mbps) { + dwc_phy_write_mask(isys, id, reg, + table6[i].oa_lane_hsrx_hs_clk_div, + 5, 7); + break; + } + } + + if (aggregation) { + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_0, 1, + 1, 1); + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + + val = (id == PORT_A) ? 3 : 0; + reg = CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, val, 3, 4); + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15; + dwc_phy_write_mask(isys, id, reg, 3, 3, 4); + } + + dwc_phy_write_mask(isys, id, CORE_DIG_DLANE_CLK_RW_HS_RX_0, 28, 0, 7); + dwc_phy_write_mask(isys, id, CORE_DIG_DLANE_CLK_RW_HS_RX_7, 6, 0, 7); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_0; + for (i = 0; i < ARRAY_SIZE(table2); i++) { + if (mbps >= table2[i].min_mbps && mbps <= table2[i].max_mbps) { + u8 j; + + for (j = 0; j < lanes; j++) + dwc_phy_write_mask(isys, id, reg + (j * 0x400), + table2[i].i_thssettle, + 8, 15); + break; + } + } + + /* deskew */ + for (i = 0; i < lanes; i++) { + reg = CORE_DIG_DLANE_0_RW_CFG_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 1500) ? 0x1 : 0x2), 2, 3); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + ((mbps > 2500) ? 0 : 1), 15, 15); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 7, 9, 12); + + reg = CORE_DIG_DLANE_0_RW_LP_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 12, 15); + + reg = CORE_DIG_DLANE_0_RW_LP_2; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 0); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 16, 0, 7); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_3; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 2); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].fjump_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 3, 8); + } + + reg = CORE_DIG_DLANE_0_RW_HS_RX_4; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 150, 0, 15); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 0, 7); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 8, 15); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 2, 0, 7); + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].min_eye_opening_deskew; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, + 8, 15); + } + reg = CORE_DIG_DLANE_0_RW_HS_RX_7; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 13, 13); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 0, 15, 15); + + reg = CORE_DIG_DLANE_0_RW_HS_RX_9; + index = lookup_table1(mbps); + if (index >= 0) { + val = table1[index].ddlcal_max_phase; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), + val, 0, 7); + } + } + + dwc_phy_write_mask(isys, id, CORE_DIG_DLANE_CLK_RW_LP_0, 1, 12, 15); + dwc_phy_write_mask(isys, id, CORE_DIG_DLANE_CLK_RW_LP_2, 0, 0, 0); + + for (i = 0; i < ARRAY_SIZE(deskew_fine_mem); i++) + dwc_phy_write_mask(isys, id, CORE_DIG_COMMON_RW_DESKEW_FINE_MEM, + deskew_fine_mem[i], 0, 15); + + if (mbps > 1500) { + hsrxval0 = 4; + hsrxval2 = 3; + } + + if (mbps > 2500) + hsrxval1 = 2; + + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval0, 0, 2); + if (lanes == 4 && is_ipu7(isys->adev->isp->hw_ver)) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval0, 0, 2); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval0, 0, 2); + } + + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9, + hsrxval1, 3, 4); + if (lanes == 4 && is_ipu7(isys->adev->isp->hw_ver)) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9, + hsrxval1, 3, 4); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9, + hsrxval1, 3, 4); + } + + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15, + hsrxval2, 0, 2); + if (lanes == 4 && is_ipu7(isys->adev->isp->hw_ver)) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15, + hsrxval2, 0, 2); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15, + hsrxval2, 0, 2); + } + + /* force and override rext */ + if (isys->phy_rext_cal && id) { + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8, + isys->phy_rext_cal, 0, 3); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 1, 11, 11); + } +} + +static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, + bool aggregation, u64 mbps) +{ + u8 trios = 2; + u16 coarse_target; + u16 deass_thresh; + u16 delay_thresh; + u16 reset_thresh; + u16 cap_prog = 6U; + u16 reg; + u16 val; + u32 i; + u32 r; + + if (is_ipu7p5(isys->adev->isp->hw_ver)) + val = 0x15; + else + val = 0x155; + + if (is_ipu7(isys->adev->isp->hw_ver)) + trios = 3; + + dwc_phy_write_mask(isys, id, CORE_DIG_RW_COMMON_7, val, 0, 9); + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_7, 104, 0, 7); + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_8, 16, 0, 7); + + reg = CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 6, 8, 11); + + val = (mbps > 900U) ? 1U : 0U; + for (i = 0; i < trios; i++) { + reg = CORE_DIG_CLANE_0_RW_HS_RX_0; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 1, 0, 0); + dwc_phy_write_mask(isys, id, reg + (i * 0x400), val, 1, 1); + + reg = CORE_DIG_CLANE_0_RW_HS_RX_1; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = CORE_DIG_CLANE_0_RW_HS_RX_5; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 38, 0, 15); + + reg = CORE_DIG_CLANE_0_RW_HS_RX_6; + dwc_phy_write_mask(isys, id, reg + (i * 0x400), 10, 0, 15); + } + + /* + * Below 900Msps, always use the same value. + * The formula is suitable for data rate 80-3500Msps. + * Timebase (us) = 1, DIV = 32, TDDL (UI) = 0.5 + */ + if (mbps >= 80U) + coarse_target = DIV_ROUND_UP_ULL(mbps, 16) - 1; + else + coarse_target = 56; + + for (i = 0; i < trios; i++) { + reg = CORE_DIG_CLANE_0_RW_HS_RX_2 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, coarse_target, 0, 15); + } + + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2, 1, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2, 0, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2, 1, 0, 0); + + if (!is_ipu7p5(isys->adev->isp->hw_ver) && lanes == 4) { + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2, + 1, 0, 0); + dwc_phy_write_mask(isys, id, + CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2, + 0, 0, 0); + } + + for (i = 0; i < trios; i++) { + reg = CORE_DIG_RW_TRIO0_0 + i * 0x400; + dwc_phy_write_mask(isys, id, reg, 1, 6, 8); + dwc_phy_write_mask(isys, id, reg, 1, 3, 5); + dwc_phy_write_mask(isys, id, reg, 2, 0, 2); + } + + deass_thresh = (u16)div_u64_rem(7 * 1000 * 6, mbps * 5U, &r) + 1; + if (r != 0) + deass_thresh++; + + reg = CORE_DIG_RW_TRIO0_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + deass_thresh, 0, 7); + + delay_thresh = + ((224U - (9U * 7U)) * 1000U) / (5U * mbps) - 7U; + + if (delay_thresh < 1) + delay_thresh = 1; + + reg = CORE_DIG_RW_TRIO0_1; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + delay_thresh, 0, 15); + + reset_thresh = (u16)div_u64_rem(2U * 5U * mbps, 7U * 1000U, &r); + if (!r) + reset_thresh--; + + if (reset_thresh < 1) + reset_thresh = 1; + + reg = CORE_DIG_RW_TRIO0_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, + reset_thresh, 9, 11); + + reg = CORE_DIG_CLANE_0_RW_LP_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 1, 12, 15); + + reg = CORE_DIG_CLANE_0_RW_LP_2; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 0, 0, 0); + + reg = CORE_DIG_CLANE_0_RW_HS_RX_0; + for (i = 0; i < trios; i++) + dwc_phy_write_mask(isys, id, reg + 0x400 * i, 12, 2, 6); + + for (i = 0; i < ARRAY_SIZE(table7); i++) { + if (mbps >= table7[i].min_mbps && mbps <= table7[i].max_mbps) { + cap_prog = table7[i].val; + break; + } + } + + for (i = 0; i < (lanes + 1); i++) { + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, 4U, 0, 2); + dwc_phy_write_mask(isys, id, reg, 0U, 3, 4); + + reg = CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 + 0x400 * i; + dwc_phy_write_mask(isys, id, reg, cap_prog, 10, 12); + } +} + +static int ipu7_isys_phy_config(struct ipu7_isys *isys, u8 id, u8 lanes, + bool aggregation) +{ + struct device *dev = &isys->adev->auxdev.dev; + u32 phy_mode; + s64 link_freq; + u64 mbps; + + if (aggregation) + link_freq = ipu7_isys_csi2_get_link_freq(&isys->csi2[0]); + else + link_freq = ipu7_isys_csi2_get_link_freq(&isys->csi2[id]); + + if (link_freq < 0) { + dev_err(dev, "get link freq failed (%lld)\n", link_freq); + return link_freq; + } + + mbps = div_u64(link_freq, 500000); + dev_dbg(dev, "config phy %u with lanes %u aggregation %d mbps %lld\n", + id, lanes, aggregation, mbps); + + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_10, 48, 0, 7); + dwc_phy_write_mask(isys, id, CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2, + 1, 12, 13); + dwc_phy_write_mask(isys, id, CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0, + 63, 2, 7); + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_STARTUP_1_1, + 563, 0, 11); + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_2, 5, 0, 7); + /* bypass the RCAL state (bit6) */ + if (aggregation && id != PORT_A) + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_2, 0x45, + 0, 7); + + dwc_phy_write_mask(isys, id, PPI_STARTUP_RW_COMMON_DPHY_6, 39, 0, 7); + dwc_phy_write_mask(isys, id, PPI_CALIBCTRL_RW_COMMON_BG_0, 500, 0, 8); + dwc_phy_write_mask(isys, id, PPI_RW_TERMCAL_CFG_0, 38, 0, 6); + dwc_phy_write_mask(isys, id, PPI_RW_OFFSETCAL_CFG_0, 7, 0, 4); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_TIMEBASE, 153, 0, 9); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_NREF, 800, 0, 10); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_NREF_RANGE, 27, 0, 4); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_TWAIT_CONFIG, 47, 0, 8); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_TWAIT_CONFIG, 127, 9, 15); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_VT_CONFIG, 47, 7, 15); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_VT_CONFIG, 27, 2, 6); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_VT_CONFIG, 3, 0, 1); + dwc_phy_write_mask(isys, id, PPI_RW_LPDCOCAL_COARSE_CFG, 1, 0, 1); + dwc_phy_write_mask(isys, id, PPI_RW_COMMON_CFG, 3, 0, 1); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 10, 10); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 1, 10, 10); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1, + 0, 15, 15); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3, + 3, 8, 9); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0, + 0, 15, 15); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6, + 7, 12, 14); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7, + 0, 8, 10); + dwc_phy_write_mask(isys, id, CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5, + 0, 8, 8); + + if (aggregation) + phy_mode = isys->csi2[0].phy_mode; + else + phy_mode = isys->csi2[id].phy_mode; + + if (phy_mode == PHY_MODE_DPHY) { + ipu7_isys_dphy_config(isys, id, lanes, aggregation, mbps); + } else if (phy_mode == PHY_MODE_CPHY) { + ipu7_isys_cphy_config(isys, id, lanes, aggregation, mbps); + } else { + dev_err(dev, "unsupported phy mode %d!\n", + isys->csi2[id].phy_mode); + } + + return 0; +} + +int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_isys *isys = csi2->isys; + u32 lanes = csi2->nlanes; + bool aggregation = false; + u32 id = csi2->port; + int ret; + + /* lanes remapping for aggregation (port AB) mode */ + if (!is_ipu7(isys->adev->isp->hw_ver) && lanes > 2 && id == PORT_A) { + aggregation = true; + lanes = 2; + } + + ipu7_isys_csi_phy_reset(isys, id); + gpreg_write(isys, id, PHY_CLK_LANE_CONTROL, 0x1); + gpreg_write(isys, id, PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, id, PHY_LANE_CONTROL_EN, (1U << lanes) - 1U); + gpreg_write(isys, id, PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, id, PHY_MODE, csi2->phy_mode); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ipu7_isys_csi_phy_reset(isys, PORT_B); + gpreg_write(isys, PORT_B, PHY_CLK_LANE_CONTROL, 0x0); + gpreg_write(isys, PORT_B, PHY_LANE_CONTROL_EN, 0x3); + gpreg_write(isys, PORT_B, PHY_CLK_LANE_FORCE_CONTROL, 0x2); + gpreg_write(isys, PORT_B, PHY_LANE_FORCE_CONTROL, 0xf); + gpreg_write(isys, PORT_B, PHY_MODE, csi2->phy_mode); + } + + ipu7_isys_csi_ctrl_cfg(csi2); + ipu7_isys_csi_ctrl_dids_config(csi2, id); + + ret = ipu7_isys_phy_config(isys, id, lanes, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, id, PHY_RESET, 1); + gpreg_write(isys, id, PHY_SHUTDOWN, 1); + dwc_csi_write(isys, id, DPHY_RSTZ, 1); + dwc_csi_write(isys, id, PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, id, CSI2_RESETN, 1); + + ret = ipu7_isys_phy_ready(isys, id); + if (ret < 0) + return ret; + + gpreg_write(isys, id, PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, id, PHY_CLK_LANE_FORCE_CONTROL, 0); + + /* config PORT_B if aggregation mode */ + if (aggregation) { + ret = ipu7_isys_phy_config(isys, PORT_B, 2, aggregation); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, PHY_RESET, 1); + gpreg_write(isys, PORT_B, PHY_SHUTDOWN, 1); + dwc_csi_write(isys, PORT_B, DPHY_RSTZ, 1); + dwc_csi_write(isys, PORT_B, PHY_SHUTDOWNZ, 1); + dwc_csi_write(isys, PORT_B, CSI2_RESETN, 1); + ret = ipu7_isys_phy_ready(isys, PORT_B); + if (ret < 0) + return ret; + + gpreg_write(isys, PORT_B, PHY_LANE_FORCE_CONTROL, 0); + gpreg_write(isys, PORT_B, PHY_CLK_LANE_FORCE_CONTROL, 0); + } + + return 0; +} + +void ipu7_isys_csi_phy_powerdown(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_isys *isys = csi2->isys; + + ipu7_isys_csi_phy_reset(isys, csi2->port); + if (!is_ipu7(isys->adev->isp->hw_ver) && + csi2->nlanes > 2U && csi2->port == PORT_A) + ipu7_isys_csi_phy_reset(isys, PORT_B); +} diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.h b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.h new file mode 100644 index 000000000000..dfdcb61540c4 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_CSI_PHY_H +#define IPU7_ISYS_CSI_PHY_H + +struct ipu7_isys; + +#define PHY_MODE_DPHY 0U +#define PHY_MODE_CPHY 1U + +int ipu7_isys_csi_phy_powerup(struct ipu7_isys_csi2 *csi2); +void ipu7_isys_csi_phy_powerdown(struct ipu7_isys_csi2 *csi2); +#endif diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h b/drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h new file mode 100644 index 000000000000..aad52c44a005 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-csi2-regs.h @@ -0,0 +1,1197 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2020 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_CSI2_REG_H +#define IPU7_ISYS_CSI2_REG_H + +/* IS main regs base */ +#define IS_MAIN_BASE 0x240000 +#define IS_MAIN_S2B_BASE (IS_MAIN_BASE + 0x22000) +#define IS_MAIN_B2O_BASE (IS_MAIN_BASE + 0x26000) +#define IS_MAIN_ISD_M0_BASE (IS_MAIN_BASE + 0x2b000) +#define IS_MAIN_ISD_M1_BASE (IS_MAIN_BASE + 0x2b100) +#define IS_MAIN_ISD_INT_BASE (IS_MAIN_BASE + 0x2b200) +#define IS_MAIN_GDA_BASE (IS_MAIN_BASE + 0x32000) +#define IS_MAIN_GPREGS_MAIN_BASE (IS_MAIN_BASE + 0x32500) +#define IS_MAIN_IRQ_CTRL_BASE (IS_MAIN_BASE + 0x32700) +#define IS_MAIN_PWM_CTRL_BASE (IS_MAIN_BASE + 0x32b00) + +#define S2B_IRQ_COMMON_0_CTL_STATUS (IS_MAIN_S2B_BASE + 0x1c) +#define S2B_IRQ_COMMON_0_CTL_CLEAR (IS_MAIN_S2B_BASE + 0x20) +#define S2B_IRQ_COMMON_0_CTL_ENABLE (IS_MAIN_S2B_BASE + 0x24) +#define S2B_IID_IRQ_CTL_STATUS(iid) (IS_MAIN_S2B_BASE + 0x94 + \ + 0x100 * (iid)) + +#define B2O_IRQ_COMMON_0_CTL_STATUS (IS_MAIN_B2O_BASE + 0x30) +#define B2O_IRQ_COMMON_0_CTL_CLEAR (IS_MAIN_B2O_BASE + 0x34) +#define B2O_IRQ_COMMON_0_CTL_ENABLE (IS_MAIN_B2O_BASE + 0x38) +#define B2O_IID_IRQ_CTL_STATUS(oid) (IS_MAIN_B2O_BASE + 0x3dc + \ + 0x200 * (oid)) + +#define ISD_M0_IRQ_CTL_STATUS (IS_MAIN_ISD_M0_BASE + 0x1c) +#define ISD_M0_IRQ_CTL_CLEAR (IS_MAIN_ISD_M0_BASE + 0x20) +#define ISD_M0_IRQ_CTL_ENABLE (IS_MAIN_ISD_M0_BASE + 0x24) + +#define ISD_M1_IRQ_CTL_STATUS (IS_MAIN_ISD_M1_BASE + 0x1c) +#define ISD_M1_IRQ_CTL_CLEAR (IS_MAIN_ISD_M1_BASE + 0x20) +#define ISD_M1_IRQ_CTL_ENABLE (IS_MAIN_ISD_M1_BASE + 0x24) + +#define ISD_INT_IRQ_CTL_STATUS (IS_MAIN_ISD_INT_BASE + 0x1c) +#define ISD_INT_IRQ_CTL_CLEAR (IS_MAIN_ISD_INT_BASE + 0x20) +#define ISD_INT_IRQ_CTL_ENABLE (IS_MAIN_ISD_INT_BASE + 0x24) + +#define GDA_IRQ_CTL_STATUS (IS_MAIN_GDA_BASE + 0x1c) +#define GDA_IRQ_CTL_CLEAR (IS_MAIN_GDA_BASE + 0x20) +#define GDA_IRQ_CTL_ENABLE (IS_MAIN_GDA_BASE + 0x24) + +#define IS_MAIN_IRQ_CTL_EDGE IS_MAIN_IRQ_CTRL_BASE +#define IS_MAIN_IRQ_CTL_MASK (IS_MAIN_IRQ_CTRL_BASE + 0x4) +#define IS_MAIN_IRQ_CTL_STATUS (IS_MAIN_IRQ_CTRL_BASE + 0x8) +#define IS_MAIN_IRQ_CTL_CLEAR (IS_MAIN_IRQ_CTRL_BASE + 0xc) +#define IS_MAIN_IRQ_CTL_ENABLE (IS_MAIN_IRQ_CTRL_BASE + 0x10) +#define IS_MAIN_IRQ_CTL_LEVEL_NOT_PULSE (IS_MAIN_IRQ_CTRL_BASE + 0x14) + +/* IS IO regs base */ +#define IS_PHY_NUM 4U +#define IS_IO_BASE 0x280000 + +/* dwc csi cdphy registers */ +#define IS_IO_CDPHY_BASE(i) (IS_IO_BASE + 0x10000 * (i)) +#define PPI_STARTUP_RW_COMMON_DPHY_0 0x1800 +#define PPI_STARTUP_RW_COMMON_DPHY_1 0x1802 +#define PPI_STARTUP_RW_COMMON_DPHY_2 0x1804 +#define PPI_STARTUP_RW_COMMON_DPHY_3 0x1806 +#define PPI_STARTUP_RW_COMMON_DPHY_4 0x1808 +#define PPI_STARTUP_RW_COMMON_DPHY_5 0x180a +#define PPI_STARTUP_RW_COMMON_DPHY_6 0x180c +#define PPI_STARTUP_RW_COMMON_DPHY_7 0x180e +#define PPI_STARTUP_RW_COMMON_DPHY_8 0x1810 +#define PPI_STARTUP_RW_COMMON_DPHY_9 0x1812 +#define PPI_STARTUP_RW_COMMON_DPHY_A 0x1814 +#define PPI_STARTUP_RW_COMMON_DPHY_10 0x1820 +#define PPI_STARTUP_RW_COMMON_STARTUP_1_1 0x1822 +#define PPI_STARTUP_RW_COMMON_STARTUP_1_2 0x1824 +#define PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_0 0x1840 +#define PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_1 0x1842 +#define PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_2 0x1844 +#define PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_3 0x1846 +#define PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_4 0x1848 +#define PPI_CALIBCTRL_R_COMMON_CALIBCTRL_2_5 0x184a +#define PPI_CALIBCTRL_RW_COMMON_BG_0 0x184c +#define PPI_CALIBCTRL_RW_COMMON_CALIBCTRL_2_7 0x184e +#define PPI_CALIBCTRL_RW_ADC_CFG_0 0x1850 +#define PPI_CALIBCTRL_RW_ADC_CFG_1 0x1852 +#define PPI_CALIBCTRL_R_ADC_DEBUG 0x1854 +#define PPI_RW_LPDCOCAL_TOP_OVERRIDE 0x1c00 +#define PPI_RW_LPDCOCAL_TIMEBASE 0x1c02 +#define PPI_RW_LPDCOCAL_NREF 0x1c04 +#define PPI_RW_LPDCOCAL_NREF_RANGE 0x1c06 +#define PPI_RW_LPDCOCAL_NREF_TRIGGER_MAN 0x1c08 +#define PPI_RW_LPDCOCAL_TWAIT_CONFIG 0x1c0a +#define PPI_RW_LPDCOCAL_VT_CONFIG 0x1c0c +#define PPI_R_LPDCOCAL_DEBUG_RB 0x1c0e +#define PPI_RW_LPDCOCAL_COARSE_CFG 0x1c10 +#define PPI_R_LPDCOCAL_DEBUG_COARSE_RB 0x1c12 +#define PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_0_RB 0x1c14 +#define PPI_R_LPDCOCAL_DEBUG_COARSE_MEAS_1_RB 0x1c16 +#define PPI_R_LPDCOCAL_DEBUG_COARSE_FWORD_RB 0x1c18 +#define PPI_R_LPDCOCAL_DEBUG_MEASURE_CURR_ERROR 0x1c1a +#define PPI_R_LPDCOCAL_DEBUG_MEASURE_LAST_ERROR 0x1c1c +#define PPI_R_LPDCOCAL_DEBUG_VT 0x1c1e +#define PPI_RW_LB_TIMEBASE_CONFIG 0x1c20 +#define PPI_RW_LB_STARTCMU_CONFIG 0x1c22 +#define PPI_R_LBPULSE_COUNTER_RB 0x1c24 +#define PPI_R_LB_START_CMU_RB 0x1c26 +#define PPI_RW_LB_DPHY_BURST_START 0x1c28 +#define PPI_RW_LB_CPHY_BURST_START 0x1c2a +#define PPI_RW_DDLCAL_CFG_0 0x1c40 +#define PPI_RW_DDLCAL_CFG_1 0x1c42 +#define PPI_RW_DDLCAL_CFG_2 0x1c44 +#define PPI_RW_DDLCAL_CFG_3 0x1c46 +#define PPI_RW_DDLCAL_CFG_4 0x1c48 +#define PPI_RW_DDLCAL_CFG_5 0x1c4a +#define PPI_RW_DDLCAL_CFG_6 0x1c4c +#define PPI_RW_DDLCAL_CFG_7 0x1c4e +#define PPI_R_DDLCAL_DEBUG_0 0x1c50 +#define PPI_R_DDLCAL_DEBUG_1 0x1c52 +#define PPI_RW_PARITY_TEST 0x1c60 +#define PPI_RW_STARTUP_OVR_0 0x1c62 +#define PPI_RW_STARTUP_STATE_OVR_1 0x1c64 +#define PPI_RW_DTB_SELECTOR 0x1c66 +#define PPI_RW_DPHY_CLK_SPARE 0x1c6a +#define PPI_RW_COMMON_CFG 0x1c6c +#define PPI_RW_TERMCAL_CFG_0 0x1c80 +#define PPI_R_TERMCAL_DEBUG_0 0x1c82 +#define PPI_RW_TERMCAL_CTRL_0 0x1c84 +#define PPI_RW_OFFSETCAL_CFG_0 0x1ca0 +#define PPI_R_OFFSETCAL_DEBUG_LANE0 0x1ca2 +#define PPI_R_OFFSETCAL_DEBUG_LANE1 0x1ca4 +#define PPI_R_OFFSETCAL_DEBUG_LANE2 0x1ca6 +#define PPI_R_OFFSETCAL_DEBUG_LANE3 0x1ca8 +#define PPI_R_OFFSETCAL_DEBUG_LANE4 0x1caa +#define PPI_RW_HSDCOCAL_CFG_O 0x1d00 +#define PPI_RW_HSDCOCAL_CFG_1 0x1d02 +#define PPI_RW_HSDCOCAL_CFG_2 0x1d04 +#define PPI_RW_HSDCOCAL_CFG_3 0x1d06 +#define PPI_RW_HSDCOCAL_CFG_4 0x1d08 +#define PPI_RW_HSDCOCAL_CFG_5 0x1d0a +#define PPI_RW_HSDCOCAL_CFG_6 0x1d0c +#define PPI_RW_HSDCOCAL_CFG_7 0x1d0e +#define PPI_RW_HSDCOCAL_CFG_8 0x1d10 +#define PPI_R_HSDCOCAL_DEBUG_RB 0x1d12 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_0 0x2000 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_1 0x2002 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_2 0x2004 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_3 0x2006 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_4 0x2008 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_5 0x200a +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_6 0x200c +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_7 0x200e +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE0_OVR_0_8 0x2010 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_9 0x2012 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_10 0x2014 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_11 0x2016 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_12 0x2018 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_13 0x201a +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_14 0x201c +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE0_OVR_0_15 0x201e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_0 0x2020 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_1 0x2022 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_2 0x2024 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_3 0x2026 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_4 0x2028 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_5 0x202a +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_6 0x202c +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_7 0x202e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_8 0x2030 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_1_9 0x2032 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_10 0x2034 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_11 0x2036 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_12 0x2038 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_13 0x203a +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_14 0x203c +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_1_15 0x203e +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_0 0x2040 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_1 0x2042 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_2 0x2044 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_3 0x2046 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_4 0x2048 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_5 0x204a +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_6 0x204c +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_7 0x204e +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_8 0x2050 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_9 0x2052 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_10 0x2054 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_11 0x2056 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_12 0x2058 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_13 0x205a +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_14 0x205c +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_2_15 0x205e +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_0 0x2060 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_1 0x2062 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_2 0x2064 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_3 0x2066 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_4 0x2068 +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_5 0x206a +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_6 0x206c +#define CORE_DIG_IOCTRL_RW_AFE_LANE0_CTRL_3_7 0x206e +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_8 0x2070 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_9 0x2072 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_10 0x2074 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_11 0x2076 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_12 0x2078 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_13 0x207a +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_14 0x207c +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_3_15 0x207e +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_0 0x2080 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_1 0x2082 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_2 0x2084 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_3 0x2086 +#define CORE_DIG_IOCTRL_R_AFE_LANE0_CTRL_4_4 0x2088 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_0 0x20a0 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_1 0x20a2 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_2 0x20a4 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE0_OVR_5_3 0x20a6 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE0_OVR_5_4 0x20a8 +#define CORE_DIG_RW_TRIO0_0 0x2100 +#define CORE_DIG_RW_TRIO0_1 0x2102 +#define CORE_DIG_RW_TRIO0_2 0x2104 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_0 0x2400 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_1 0x2402 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_2 0x2404 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_3 0x2406 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_4 0x2408 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_5 0x240a +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_6 0x240c +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_7 0x240e +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE1_OVR_0_8 0x2410 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_9 0x2412 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_10 0x2414 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_11 0x2416 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_12 0x2418 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_13 0x241a +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_14 0x241c +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE1_OVR_0_15 0x241e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_0 0x2420 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_1 0x2422 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_2 0x2424 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_3 0x2426 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_4 0x2428 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_5 0x242a +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_6 0x242c +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_7 0x242e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_8 0x2430 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_1_9 0x2432 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_10 0x2434 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_11 0x2436 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_12 0x2438 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_13 0x243a +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_14 0x243c +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_1_15 0x243e +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_0 0x2440 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_1 0x2442 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_2 0x2444 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_3 0x2446 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_4 0x2448 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_5 0x244a +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_6 0x244c +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_7 0x244e +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_8 0x2450 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_9 0x2452 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_10 0x2454 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_11 0x2456 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_12 0x2458 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_13 0x245a +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_14 0x245c +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_2_15 0x245e +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_0 0x2460 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_1 0x2462 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_2 0x2464 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_3 0x2466 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_4 0x2468 +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_5 0x246a +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_6 0x246c +#define CORE_DIG_IOCTRL_RW_AFE_LANE1_CTRL_3_7 0x246e +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_8 0x2470 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_9 0x2472 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_10 0x2474 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_11 0x2476 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_12 0x2478 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_13 0x247a +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_14 0x247c +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_3_15 0x247e +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_0 0x2480 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_1 0x2482 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_2 0x2484 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_3 0x2486 +#define CORE_DIG_IOCTRL_R_AFE_LANE1_CTRL_4_4 0x2488 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_0 0x24a0 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_1 0x24a2 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_2 0x24a4 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE1_OVR_5_3 0x24a6 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE1_OVR_5_4 0x24a8 +#define CORE_DIG_RW_TRIO1_0 0x2500 +#define CORE_DIG_RW_TRIO1_1 0x2502 +#define CORE_DIG_RW_TRIO1_2 0x2504 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_0 0x2800 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_1 0x2802 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_2 0x2804 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_3 0x2806 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_4 0x2808 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_5 0x280a +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_6 0x280c +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_7 0x280e +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE2_OVR_0_8 0x2810 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_9 0x2812 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_10 0x2814 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_11 0x2816 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_12 0x2818 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_13 0x281a +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_14 0x281c +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE2_OVR_0_15 0x281e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_0 0x2820 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_1 0x2822 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_2 0x2824 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_3 0x2826 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_4 0x2828 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_5 0x282a +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_6 0x282c +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_7 0x282e +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_8 0x2830 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_1_9 0x2832 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_10 0x2834 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_11 0x2836 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_12 0x2838 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_13 0x283a +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_14 0x283c +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_1_15 0x283e +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_0 0x2840 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_1 0x2842 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_2 0x2844 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_3 0x2846 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_4 0x2848 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_5 0x284a +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_6 0x284c +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_7 0x284e +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_8 0x2850 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_9 0x2852 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_10 0x2854 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_11 0x2856 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_12 0x2858 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_13 0x285a +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_14 0x285c +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_2_15 0x285e +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_0 0x2860 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_1 0x2862 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_2 0x2864 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_3 0x2866 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_4 0x2868 +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_5 0x286a +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_6 0x286c +#define CORE_DIG_IOCTRL_RW_AFE_LANE2_CTRL_3_7 0x286e +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_8 0x2870 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_9 0x2872 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_10 0x2874 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_11 0x2876 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_12 0x2878 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_13 0x287a +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_14 0x287c +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_3_15 0x287e +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_0 0x2880 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_1 0x2882 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_2 0x2884 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_3 0x2886 +#define CORE_DIG_IOCTRL_R_AFE_LANE2_CTRL_4_4 0x2888 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_0 0x28a0 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_1 0x28a2 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_2 0x28a4 +#define CORE_DIG_IOCTRL_RW_CPHY_PPI_LANE2_OVR_5_3 0x28a6 +#define CORE_DIG_IOCTRL_R_CPHY_PPI_LANE2_OVR_5_4 0x28a8 +#define CORE_DIG_RW_TRIO2_0 0x2900 +#define CORE_DIG_RW_TRIO2_1 0x2902 +#define CORE_DIG_RW_TRIO2_2 0x2904 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_0 0x2c00 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_1 0x2c02 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_2 0x2c04 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_3 0x2c06 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_4 0x2c08 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_5 0x2c0a +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_6 0x2c0c +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_7 0x2c0e +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_LANE3_OVR_0_8 0x2c10 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_9 0x2c12 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_10 0x2c14 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_11 0x2c16 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_12 0x2c18 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_13 0x2c1a +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_14 0x2c1c +#define CORE_DIG_IOCTRL_R_DPHY_PPI_LANE3_OVR_0_15 0x2c1e +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_0 0x2c40 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_1 0x2c42 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_2 0x2c44 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_3 0x2c46 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_4 0x2c48 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_5 0x2c4a +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_6 0x2c4c +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_7 0x2c4e +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_8 0x2c50 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_9 0x2c52 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_10 0x2c54 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_11 0x2c56 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_12 0x2c58 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_13 0x2c5a +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_14 0x2c5c +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_2_15 0x2c5e +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_0 0x2c60 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_1 0x2c62 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_2 0x2c64 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_3 0x2c66 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_4 0x2c68 +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_5 0x2c6a +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_6 0x2c6c +#define CORE_DIG_IOCTRL_RW_AFE_LANE3_CTRL_3_7 0x2c6e +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_8 0x2c70 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_9 0x2c72 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_10 0x2c74 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_11 0x2c76 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_12 0x2c78 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_13 0x2c7a +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_14 0x2c7c +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_3_15 0x2c7e +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_0 0x2c80 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_1 0x2c82 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_2 0x2c84 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_3 0x2c86 +#define CORE_DIG_IOCTRL_R_AFE_LANE3_CTRL_4_4 0x2c88 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_0 0x3040 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_1 0x3042 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_2 0x3044 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_3 0x3046 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_4 0x3048 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_5 0x304a +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_6 0x304c +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_7 0x304e +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_8 0x3050 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_9 0x3052 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_10 0x3054 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_11 0x3056 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_12 0x3058 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_13 0x305a +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_14 0x305c +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_2_15 0x305e +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_0 0x3060 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_1 0x3062 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_2 0x3064 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_3 0x3066 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_4 0x3068 +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_5 0x306a +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_6 0x306c +#define CORE_DIG_IOCTRL_RW_AFE_LANE4_CTRL_3_7 0x306e +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_8 0x3070 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_9 0x3072 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_10 0x3074 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_11 0x3076 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_12 0x3078 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_13 0x307a +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_14 0x307c +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_3_15 0x307e +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_0 0x3080 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_1 0x3082 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_2 0x3084 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_3 0x3086 +#define CORE_DIG_IOCTRL_R_AFE_LANE4_CTRL_4_4 0x3088 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_0 0x3400 +#define CORE_DIG_IOCTRL_RW_DPHY_PPI_CLK_OVR_0_1 0x3402 +#define CORE_DIG_IOCTRL_R_DPHY_PPI_CLK_OVR_0_2 0x3404 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_0 0x3800 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_1 0x3802 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_2 0x3804 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_3 0x3806 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_4 0x3808 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_5 0x380a +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_6 0x380c +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_7 0x380e +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_8 0x3810 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_9 0x3812 +#define CORE_DIG_IOCTRL_RW_COMMON_PPI_OVR_0_10 0x3814 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_11 0x3816 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_12 0x3818 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_13 0x381a +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_14 0x381c +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_0_15 0x381e +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_0 0x3820 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_1 0x3822 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_2 0x3824 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_3 0x3826 +#define CORE_DIG_IOCTRL_R_COMMON_PPI_OVR_1_4 0x3828 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_0 0x3840 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_1 0x3842 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_2 0x3844 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_3 0x3846 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_4 0x3848 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_5 0x384a +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_6 0x384c +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_7 0x384e +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_8 0x3850 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_9 0x3852 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_10 0x3854 +#define CORE_DIG_IOCTRL_RW_AFE_CB_CTRL_2_11 0x3856 +#define CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_12 0x3858 +#define CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_13 0x385a +#define CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_14 0x385c +#define CORE_DIG_IOCTRL_R_AFE_CB_CTRL_2_15 0x385e +#define CORE_DIG_IOCTRL_R_AFE_CB_CTRL_3_0 0x3860 +#define CORE_DIG_RW_COMMON_0 0x3880 +#define CORE_DIG_RW_COMMON_1 0x3882 +#define CORE_DIG_RW_COMMON_2 0x3884 +#define CORE_DIG_RW_COMMON_3 0x3886 +#define CORE_DIG_RW_COMMON_4 0x3888 +#define CORE_DIG_RW_COMMON_5 0x388a +#define CORE_DIG_RW_COMMON_6 0x388c +#define CORE_DIG_RW_COMMON_7 0x388e +#define CORE_DIG_RW_COMMON_8 0x3890 +#define CORE_DIG_RW_COMMON_9 0x3892 +#define CORE_DIG_RW_COMMON_10 0x3894 +#define CORE_DIG_RW_COMMON_11 0x3896 +#define CORE_DIG_RW_COMMON_12 0x3898 +#define CORE_DIG_RW_COMMON_13 0x389a +#define CORE_DIG_RW_COMMON_14 0x389c +#define CORE_DIG_RW_COMMON_15 0x389e +#define CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_0 0x39e0 +#define CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_1 0x39e2 +#define CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_2 0x39e4 +#define CORE_DIG_ANACTRL_RW_COMMON_ANACTRL_3 0x39e6 +#define CORE_DIG_COMMON_RW_DESKEW_FINE_MEM 0x3fe0 +#define CORE_DIG_COMMON_R_DESKEW_FINE_MEM 0x3fe2 +#define PPI_RW_DPHY_LANE0_LBERT_0 0x4000 +#define PPI_RW_DPHY_LANE0_LBERT_1 0x4002 +#define PPI_R_DPHY_LANE0_LBERT_0 0x4004 +#define PPI_R_DPHY_LANE0_LBERT_1 0x4006 +#define PPI_RW_DPHY_LANE0_SPARE 0x4008 +#define PPI_RW_DPHY_LANE1_LBERT_0 0x4400 +#define PPI_RW_DPHY_LANE1_LBERT_1 0x4402 +#define PPI_R_DPHY_LANE1_LBERT_0 0x4404 +#define PPI_R_DPHY_LANE1_LBERT_1 0x4406 +#define PPI_RW_DPHY_LANE1_SPARE 0x4408 +#define PPI_RW_DPHY_LANE2_LBERT_0 0x4800 +#define PPI_RW_DPHY_LANE2_LBERT_1 0x4802 +#define PPI_R_DPHY_LANE2_LBERT_0 0x4804 +#define PPI_R_DPHY_LANE2_LBERT_1 0x4806 +#define PPI_RW_DPHY_LANE2_SPARE 0x4808 +#define PPI_RW_DPHY_LANE3_LBERT_0 0x4c00 +#define PPI_RW_DPHY_LANE3_LBERT_1 0x4c02 +#define PPI_R_DPHY_LANE3_LBERT_0 0x4c04 +#define PPI_R_DPHY_LANE3_LBERT_1 0x4c06 +#define PPI_RW_DPHY_LANE3_SPARE 0x4c08 +#define CORE_DIG_DLANE_0_RW_CFG_0 0x6000 +#define CORE_DIG_DLANE_0_RW_CFG_1 0x6002 +#define CORE_DIG_DLANE_0_RW_CFG_2 0x6004 +#define CORE_DIG_DLANE_0_RW_LP_0 0x6080 +#define CORE_DIG_DLANE_0_RW_LP_1 0x6082 +#define CORE_DIG_DLANE_0_RW_LP_2 0x6084 +#define CORE_DIG_DLANE_0_R_LP_0 0x60a0 +#define CORE_DIG_DLANE_0_R_LP_1 0x60a2 +#define CORE_DIG_DLANE_0_R_HS_TX_0 0x60e0 +#define CORE_DIG_DLANE_0_RW_HS_RX_0 0x6100 +#define CORE_DIG_DLANE_0_RW_HS_RX_1 0x6102 +#define CORE_DIG_DLANE_0_RW_HS_RX_2 0x6104 +#define CORE_DIG_DLANE_0_RW_HS_RX_3 0x6106 +#define CORE_DIG_DLANE_0_RW_HS_RX_4 0x6108 +#define CORE_DIG_DLANE_0_RW_HS_RX_5 0x610a +#define CORE_DIG_DLANE_0_RW_HS_RX_6 0x610c +#define CORE_DIG_DLANE_0_RW_HS_RX_7 0x610e +#define CORE_DIG_DLANE_0_RW_HS_RX_8 0x6110 +#define CORE_DIG_DLANE_0_RW_HS_RX_9 0x6112 +#define CORE_DIG_DLANE_0_R_HS_RX_0 0x6120 +#define CORE_DIG_DLANE_0_R_HS_RX_1 0x6122 +#define CORE_DIG_DLANE_0_R_HS_RX_2 0x6124 +#define CORE_DIG_DLANE_0_R_HS_RX_3 0x6126 +#define CORE_DIG_DLANE_0_R_HS_RX_4 0x6128 +#define CORE_DIG_DLANE_0_RW_HS_TX_0 0x6200 +#define CORE_DIG_DLANE_0_RW_HS_TX_1 0x6202 +#define CORE_DIG_DLANE_0_RW_HS_TX_2 0x6204 +#define CORE_DIG_DLANE_0_RW_HS_TX_3 0x6206 +#define CORE_DIG_DLANE_0_RW_HS_TX_4 0x6208 +#define CORE_DIG_DLANE_0_RW_HS_TX_5 0x620a +#define CORE_DIG_DLANE_0_RW_HS_TX_6 0x620c +#define CORE_DIG_DLANE_0_RW_HS_TX_7 0x620e +#define CORE_DIG_DLANE_0_RW_HS_TX_8 0x6210 +#define CORE_DIG_DLANE_0_RW_HS_TX_9 0x6212 +#define CORE_DIG_DLANE_0_RW_HS_TX_10 0x6214 +#define CORE_DIG_DLANE_0_RW_HS_TX_11 0x6216 +#define CORE_DIG_DLANE_0_RW_HS_TX_12 0x6218 +#define CORE_DIG_DLANE_1_RW_CFG_0 0x6400 +#define CORE_DIG_DLANE_1_RW_CFG_1 0x6402 +#define CORE_DIG_DLANE_1_RW_CFG_2 0x6404 +#define CORE_DIG_DLANE_1_RW_LP_0 0x6480 +#define CORE_DIG_DLANE_1_RW_LP_1 0x6482 +#define CORE_DIG_DLANE_1_RW_LP_2 0x6484 +#define CORE_DIG_DLANE_1_R_LP_0 0x64a0 +#define CORE_DIG_DLANE_1_R_LP_1 0x64a2 +#define CORE_DIG_DLANE_1_R_HS_TX_0 0x64e0 +#define CORE_DIG_DLANE_1_RW_HS_RX_0 0x6500 +#define CORE_DIG_DLANE_1_RW_HS_RX_1 0x6502 +#define CORE_DIG_DLANE_1_RW_HS_RX_2 0x6504 +#define CORE_DIG_DLANE_1_RW_HS_RX_3 0x6506 +#define CORE_DIG_DLANE_1_RW_HS_RX_4 0x6508 +#define CORE_DIG_DLANE_1_RW_HS_RX_5 0x650a +#define CORE_DIG_DLANE_1_RW_HS_RX_6 0x650c +#define CORE_DIG_DLANE_1_RW_HS_RX_7 0x650e +#define CORE_DIG_DLANE_1_RW_HS_RX_8 0x6510 +#define CORE_DIG_DLANE_1_RW_HS_RX_9 0x6512 +#define CORE_DIG_DLANE_1_R_HS_RX_0 0x6520 +#define CORE_DIG_DLANE_1_R_HS_RX_1 0x6522 +#define CORE_DIG_DLANE_1_R_HS_RX_2 0x6524 +#define CORE_DIG_DLANE_1_R_HS_RX_3 0x6526 +#define CORE_DIG_DLANE_1_R_HS_RX_4 0x6528 +#define CORE_DIG_DLANE_1_RW_HS_TX_0 0x6600 +#define CORE_DIG_DLANE_1_RW_HS_TX_1 0x6602 +#define CORE_DIG_DLANE_1_RW_HS_TX_2 0x6604 +#define CORE_DIG_DLANE_1_RW_HS_TX_3 0x6606 +#define CORE_DIG_DLANE_1_RW_HS_TX_4 0x6608 +#define CORE_DIG_DLANE_1_RW_HS_TX_5 0x660a +#define CORE_DIG_DLANE_1_RW_HS_TX_6 0x660c +#define CORE_DIG_DLANE_1_RW_HS_TX_7 0x660e +#define CORE_DIG_DLANE_1_RW_HS_TX_8 0x6610 +#define CORE_DIG_DLANE_1_RW_HS_TX_9 0x6612 +#define CORE_DIG_DLANE_1_RW_HS_TX_10 0x6614 +#define CORE_DIG_DLANE_1_RW_HS_TX_11 0x6616 +#define CORE_DIG_DLANE_1_RW_HS_TX_12 0x6618 +#define CORE_DIG_DLANE_2_RW_CFG_0 0x6800 +#define CORE_DIG_DLANE_2_RW_CFG_1 0x6802 +#define CORE_DIG_DLANE_2_RW_CFG_2 0x6804 +#define CORE_DIG_DLANE_2_RW_LP_0 0x6880 +#define CORE_DIG_DLANE_2_RW_LP_1 0x6882 +#define CORE_DIG_DLANE_2_RW_LP_2 0x6884 +#define CORE_DIG_DLANE_2_R_LP_0 0x68a0 +#define CORE_DIG_DLANE_2_R_LP_1 0x68a2 +#define CORE_DIG_DLANE_2_R_HS_TX_0 0x68e0 +#define CORE_DIG_DLANE_2_RW_HS_RX_0 0x6900 +#define CORE_DIG_DLANE_2_RW_HS_RX_1 0x6902 +#define CORE_DIG_DLANE_2_RW_HS_RX_2 0x6904 +#define CORE_DIG_DLANE_2_RW_HS_RX_3 0x6906 +#define CORE_DIG_DLANE_2_RW_HS_RX_4 0x6908 +#define CORE_DIG_DLANE_2_RW_HS_RX_5 0x690a +#define CORE_DIG_DLANE_2_RW_HS_RX_6 0x690c +#define CORE_DIG_DLANE_2_RW_HS_RX_7 0x690e +#define CORE_DIG_DLANE_2_RW_HS_RX_8 0x6910 +#define CORE_DIG_DLANE_2_RW_HS_RX_9 0x6912 +#define CORE_DIG_DLANE_2_R_HS_RX_0 0x6920 +#define CORE_DIG_DLANE_2_R_HS_RX_1 0x6922 +#define CORE_DIG_DLANE_2_R_HS_RX_2 0x6924 +#define CORE_DIG_DLANE_2_R_HS_RX_3 0x6926 +#define CORE_DIG_DLANE_2_R_HS_RX_4 0x6928 +#define CORE_DIG_DLANE_2_RW_HS_TX_0 0x6a00 +#define CORE_DIG_DLANE_2_RW_HS_TX_1 0x6a02 +#define CORE_DIG_DLANE_2_RW_HS_TX_2 0x6a04 +#define CORE_DIG_DLANE_2_RW_HS_TX_3 0x6a06 +#define CORE_DIG_DLANE_2_RW_HS_TX_4 0x6a08 +#define CORE_DIG_DLANE_2_RW_HS_TX_5 0x6a0a +#define CORE_DIG_DLANE_2_RW_HS_TX_6 0x6a0c +#define CORE_DIG_DLANE_2_RW_HS_TX_7 0x6a0e +#define CORE_DIG_DLANE_2_RW_HS_TX_8 0x6a10 +#define CORE_DIG_DLANE_2_RW_HS_TX_9 0x6a12 +#define CORE_DIG_DLANE_2_RW_HS_TX_10 0x6a14 +#define CORE_DIG_DLANE_2_RW_HS_TX_11 0x6a16 +#define CORE_DIG_DLANE_2_RW_HS_TX_12 0x6a18 +#define CORE_DIG_DLANE_3_RW_CFG_0 0x6c00 +#define CORE_DIG_DLANE_3_RW_CFG_1 0x6c02 +#define CORE_DIG_DLANE_3_RW_CFG_2 0x6c04 +#define CORE_DIG_DLANE_3_RW_LP_0 0x6c80 +#define CORE_DIG_DLANE_3_RW_LP_1 0x6c82 +#define CORE_DIG_DLANE_3_RW_LP_2 0x6c84 +#define CORE_DIG_DLANE_3_R_LP_0 0x6ca0 +#define CORE_DIG_DLANE_3_R_LP_1 0x6ca2 +#define CORE_DIG_DLANE_3_R_HS_TX_0 0x6ce0 +#define CORE_DIG_DLANE_3_RW_HS_RX_0 0x6d00 +#define CORE_DIG_DLANE_3_RW_HS_RX_1 0x6d02 +#define CORE_DIG_DLANE_3_RW_HS_RX_2 0x6d04 +#define CORE_DIG_DLANE_3_RW_HS_RX_3 0x6d06 +#define CORE_DIG_DLANE_3_RW_HS_RX_4 0x6d08 +#define CORE_DIG_DLANE_3_RW_HS_RX_5 0x6d0a +#define CORE_DIG_DLANE_3_RW_HS_RX_6 0x6d0c +#define CORE_DIG_DLANE_3_RW_HS_RX_7 0x6d0e +#define CORE_DIG_DLANE_3_RW_HS_RX_8 0x6d10 +#define CORE_DIG_DLANE_3_RW_HS_RX_9 0x6d12 +#define CORE_DIG_DLANE_3_R_HS_RX_0 0x6d20 +#define CORE_DIG_DLANE_3_R_HS_RX_1 0x6d22 +#define CORE_DIG_DLANE_3_R_HS_RX_2 0x6d24 +#define CORE_DIG_DLANE_3_R_HS_RX_3 0x6d26 +#define CORE_DIG_DLANE_3_R_HS_RX_4 0x6d28 +#define CORE_DIG_DLANE_3_RW_HS_TX_0 0x6e00 +#define CORE_DIG_DLANE_3_RW_HS_TX_1 0x6e02 +#define CORE_DIG_DLANE_3_RW_HS_TX_2 0x6e04 +#define CORE_DIG_DLANE_3_RW_HS_TX_3 0x6e06 +#define CORE_DIG_DLANE_3_RW_HS_TX_4 0x6e08 +#define CORE_DIG_DLANE_3_RW_HS_TX_5 0x6e0a +#define CORE_DIG_DLANE_3_RW_HS_TX_6 0x6e0c +#define CORE_DIG_DLANE_3_RW_HS_TX_7 0x6e0e +#define CORE_DIG_DLANE_3_RW_HS_TX_8 0x6e10 +#define CORE_DIG_DLANE_3_RW_HS_TX_9 0x6e12 +#define CORE_DIG_DLANE_3_RW_HS_TX_10 0x6e14 +#define CORE_DIG_DLANE_3_RW_HS_TX_11 0x6e16 +#define CORE_DIG_DLANE_3_RW_HS_TX_12 0x6e18 +#define CORE_DIG_DLANE_CLK_RW_CFG_0 0x7000 +#define CORE_DIG_DLANE_CLK_RW_CFG_1 0x7002 +#define CORE_DIG_DLANE_CLK_RW_CFG_2 0x7004 +#define CORE_DIG_DLANE_CLK_RW_LP_0 0x7080 +#define CORE_DIG_DLANE_CLK_RW_LP_1 0x7082 +#define CORE_DIG_DLANE_CLK_RW_LP_2 0x7084 +#define CORE_DIG_DLANE_CLK_R_LP_0 0x70a0 +#define CORE_DIG_DLANE_CLK_R_LP_1 0x70a2 +#define CORE_DIG_DLANE_CLK_R_HS_TX_0 0x70e0 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_0 0x7100 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_1 0x7102 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_2 0x7104 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_3 0x7106 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_4 0x7108 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_5 0x710a +#define CORE_DIG_DLANE_CLK_RW_HS_RX_6 0x710c +#define CORE_DIG_DLANE_CLK_RW_HS_RX_7 0x710e +#define CORE_DIG_DLANE_CLK_RW_HS_RX_8 0x7110 +#define CORE_DIG_DLANE_CLK_RW_HS_RX_9 0x7112 +#define CORE_DIG_DLANE_CLK_R_HS_RX_0 0x7120 +#define CORE_DIG_DLANE_CLK_R_HS_RX_1 0x7122 +#define CORE_DIG_DLANE_CLK_R_HS_RX_2 0x7124 +#define CORE_DIG_DLANE_CLK_R_HS_RX_3 0x7126 +#define CORE_DIG_DLANE_CLK_R_HS_RX_4 0x7128 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_0 0x7200 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_1 0x7202 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_2 0x7204 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_3 0x7206 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_4 0x7208 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_5 0x720a +#define CORE_DIG_DLANE_CLK_RW_HS_TX_6 0x720c +#define CORE_DIG_DLANE_CLK_RW_HS_TX_7 0x720e +#define CORE_DIG_DLANE_CLK_RW_HS_TX_8 0x7210 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_9 0x7212 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_10 0x7214 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_11 0x7216 +#define CORE_DIG_DLANE_CLK_RW_HS_TX_12 0x7218 +#define PPI_RW_CPHY_TRIO0_LBERT_0 0x8000 +#define PPI_RW_CPHY_TRIO0_LBERT_1 0x8002 +#define PPI_R_CPHY_TRIO0_LBERT_0 0x8004 +#define PPI_R_CPHY_TRIO0_LBERT_1 0x8006 +#define PPI_RW_CPHY_TRIO0_SPARE 0x8008 +#define PPI_RW_CPHY_TRIO1_LBERT_0 0x8400 +#define PPI_RW_CPHY_TRIO1_LBERT_1 0x8402 +#define PPI_R_CPHY_TRIO1_LBERT_0 0x8404 +#define PPI_R_CPHY_TRIO1_LBERT_1 0x8406 +#define PPI_RW_CPHY_TRIO1_SPARE 0x8408 +#define PPI_RW_CPHY_TRIO2_LBERT_0 0x8800 +#define PPI_RW_CPHY_TRIO2_LBERT_1 0x8802 +#define PPI_R_CPHY_TRIO2_LBERT_0 0x8804 +#define PPI_R_CPHY_TRIO2_LBERT_1 0x8806 +#define PPI_RW_CPHY_TRIO2_SPARE 0x8808 +#define CORE_DIG_CLANE_0_RW_CFG_0 0xa000 +#define CORE_DIG_CLANE_0_RW_CFG_2 0xa004 +#define CORE_DIG_CLANE_0_RW_LP_0 0xa080 +#define CORE_DIG_CLANE_0_RW_LP_1 0xa082 +#define CORE_DIG_CLANE_0_RW_LP_2 0xa084 +#define CORE_DIG_CLANE_0_R_LP_0 0xa0a0 +#define CORE_DIG_CLANE_0_R_LP_1 0xa0a2 +#define CORE_DIG_CLANE_0_RW_HS_RX_0 0xa100 +#define CORE_DIG_CLANE_0_RW_HS_RX_1 0xa102 +#define CORE_DIG_CLANE_0_RW_HS_RX_2 0xa104 +#define CORE_DIG_CLANE_0_RW_HS_RX_3 0xa106 +#define CORE_DIG_CLANE_0_RW_HS_RX_4 0xa108 +#define CORE_DIG_CLANE_0_RW_HS_RX_5 0xa10a +#define CORE_DIG_CLANE_0_RW_HS_RX_6 0xa10c +#define CORE_DIG_CLANE_0_R_RX_0 0xa120 +#define CORE_DIG_CLANE_0_R_RX_1 0xa122 +#define CORE_DIG_CLANE_0_R_TX_0 0xa124 +#define CORE_DIG_CLANE_0_R_RX_2 0xa126 +#define CORE_DIG_CLANE_0_R_RX_3 0xa128 +#define CORE_DIG_CLANE_0_RW_HS_TX_0 0xa200 +#define CORE_DIG_CLANE_0_RW_HS_TX_1 0xa202 +#define CORE_DIG_CLANE_0_RW_HS_TX_2 0xa204 +#define CORE_DIG_CLANE_0_RW_HS_TX_3 0xa206 +#define CORE_DIG_CLANE_0_RW_HS_TX_4 0xa208 +#define CORE_DIG_CLANE_0_RW_HS_TX_5 0xa20a +#define CORE_DIG_CLANE_0_RW_HS_TX_6 0xa20c +#define CORE_DIG_CLANE_0_RW_HS_TX_7 0xa20e +#define CORE_DIG_CLANE_0_RW_HS_TX_8 0xa210 +#define CORE_DIG_CLANE_0_RW_HS_TX_9 0xa212 +#define CORE_DIG_CLANE_0_RW_HS_TX_10 0xa214 +#define CORE_DIG_CLANE_0_RW_HS_TX_11 0xa216 +#define CORE_DIG_CLANE_0_RW_HS_TX_12 0xa218 +#define CORE_DIG_CLANE_0_RW_HS_TX_13 0xa21a +#define CORE_DIG_CLANE_1_RW_CFG_0 0xa400 +#define CORE_DIG_CLANE_1_RW_CFG_2 0xa404 +#define CORE_DIG_CLANE_1_RW_LP_0 0xa480 +#define CORE_DIG_CLANE_1_RW_LP_1 0xa482 +#define CORE_DIG_CLANE_1_RW_LP_2 0xa484 +#define CORE_DIG_CLANE_1_R_LP_0 0xa4a0 +#define CORE_DIG_CLANE_1_R_LP_1 0xa4a2 +#define CORE_DIG_CLANE_1_RW_HS_RX_0 0xa500 +#define CORE_DIG_CLANE_1_RW_HS_RX_1 0xa502 +#define CORE_DIG_CLANE_1_RW_HS_RX_2 0xa504 +#define CORE_DIG_CLANE_1_RW_HS_RX_3 0xa506 +#define CORE_DIG_CLANE_1_RW_HS_RX_4 0xa508 +#define CORE_DIG_CLANE_1_RW_HS_RX_5 0xa50a +#define CORE_DIG_CLANE_1_RW_HS_RX_6 0xa50c +#define CORE_DIG_CLANE_1_R_RX_0 0xa520 +#define CORE_DIG_CLANE_1_R_RX_1 0xa522 +#define CORE_DIG_CLANE_1_R_TX_0 0xa524 +#define CORE_DIG_CLANE_1_R_RX_2 0xa526 +#define CORE_DIG_CLANE_1_R_RX_3 0xa528 +#define CORE_DIG_CLANE_1_RW_HS_TX_0 0xa600 +#define CORE_DIG_CLANE_1_RW_HS_TX_1 0xa602 +#define CORE_DIG_CLANE_1_RW_HS_TX_2 0xa604 +#define CORE_DIG_CLANE_1_RW_HS_TX_3 0xa606 +#define CORE_DIG_CLANE_1_RW_HS_TX_4 0xa608 +#define CORE_DIG_CLANE_1_RW_HS_TX_5 0xa60a +#define CORE_DIG_CLANE_1_RW_HS_TX_6 0xa60c +#define CORE_DIG_CLANE_1_RW_HS_TX_7 0xa60e +#define CORE_DIG_CLANE_1_RW_HS_TX_8 0xa610 +#define CORE_DIG_CLANE_1_RW_HS_TX_9 0xa612 +#define CORE_DIG_CLANE_1_RW_HS_TX_10 0xa614 +#define CORE_DIG_CLANE_1_RW_HS_TX_11 0xa616 +#define CORE_DIG_CLANE_1_RW_HS_TX_12 0xa618 +#define CORE_DIG_CLANE_1_RW_HS_TX_13 0xa61a +#define CORE_DIG_CLANE_2_RW_CFG_0 0xa800 +#define CORE_DIG_CLANE_2_RW_CFG_2 0xa804 +#define CORE_DIG_CLANE_2_RW_LP_0 0xa880 +#define CORE_DIG_CLANE_2_RW_LP_1 0xa882 +#define CORE_DIG_CLANE_2_RW_LP_2 0xa884 +#define CORE_DIG_CLANE_2_R_LP_0 0xa8a0 +#define CORE_DIG_CLANE_2_R_LP_1 0xa8a2 +#define CORE_DIG_CLANE_2_RW_HS_RX_0 0xa900 +#define CORE_DIG_CLANE_2_RW_HS_RX_1 0xa902 +#define CORE_DIG_CLANE_2_RW_HS_RX_2 0xa904 +#define CORE_DIG_CLANE_2_RW_HS_RX_3 0xa906 +#define CORE_DIG_CLANE_2_RW_HS_RX_4 0xa908 +#define CORE_DIG_CLANE_2_RW_HS_RX_5 0xa90a +#define CORE_DIG_CLANE_2_RW_HS_RX_6 0xa90c +#define CORE_DIG_CLANE_2_R_RX_0 0xa920 +#define CORE_DIG_CLANE_2_R_RX_1 0xa922 +#define CORE_DIG_CLANE_2_R_TX_0 0xa924 +#define CORE_DIG_CLANE_2_R_RX_2 0xa926 +#define CORE_DIG_CLANE_2_R_RX_3 0xa928 +#define CORE_DIG_CLANE_2_RW_HS_TX_0 0xaa00 +#define CORE_DIG_CLANE_2_RW_HS_TX_1 0xaa02 +#define CORE_DIG_CLANE_2_RW_HS_TX_2 0xaa04 +#define CORE_DIG_CLANE_2_RW_HS_TX_3 0xaa06 +#define CORE_DIG_CLANE_2_RW_HS_TX_4 0xaa08 +#define CORE_DIG_CLANE_2_RW_HS_TX_5 0xaa0a +#define CORE_DIG_CLANE_2_RW_HS_TX_6 0xaa0c +#define CORE_DIG_CLANE_2_RW_HS_TX_7 0xaa0e +#define CORE_DIG_CLANE_2_RW_HS_TX_8 0xaa10 +#define CORE_DIG_CLANE_2_RW_HS_TX_9 0xaa12 +#define CORE_DIG_CLANE_2_RW_HS_TX_10 0xaa14 +#define CORE_DIG_CLANE_2_RW_HS_TX_11 0xaa16 +#define CORE_DIG_CLANE_2_RW_HS_TX_12 0xaa18 +#define CORE_DIG_CLANE_2_RW_HS_TX_13 0xaa1a + +/* dwc csi host controller registers */ +#define IS_IO_CSI2_HOST_BASE(i) (IS_IO_BASE + 0x40000 + \ + 0x2000 * (i)) +#define VERSION 0x0 +#define N_LANES 0x4 +#define CSI2_RESETN 0x8 +#define INT_ST_MAIN 0xc +#define DATA_IDS_1 0x10 +#define DATA_IDS_2 0x14 +#define CDPHY_MODE 0x1c +#define DATA_IDS_VC_1 0x30 +#define DATA_IDS_VC_2 0x34 +#define PHY_SHUTDOWNZ 0x40 +#define DPHY_RSTZ 0x44 +#define PHY_RX 0x48 +#define PHY_STOPSTATE 0x4c +#define PHY_TEST_CTRL0 0x50 +#define PHY_TEST_CTRL1 0x54 +#define PPI_PG_PATTERN_VRES 0x60 +#define PPI_PG_PATTERN_HRES 0x64 +#define PPI_PG_CONFIG 0x68 +#define PPI_PG_ENABLE 0x6c +#define PPI_PG_STATUS 0x70 +#define VC_EXTENSION 0xc8 +#define PHY_CAL 0xcc +#define INT_ST_PHY_FATAL 0xe0 +#define INT_MSK_PHY_FATAL 0xe4 +#define INT_FORCE_PHY_FATAL 0xe8 +#define INT_ST_PKT_FATAL 0xf0 +#define INT_MSK_PKT_FATAL 0xf4 +#define INT_FORCE_PKT_FATAL 0xf8 +#define INT_ST_PHY 0x110 +#define INT_MSK_PHY 0x114 +#define INT_FORCE_PHY 0x118 +#define INT_ST_LINE 0x130 +#define INT_MSK_LINE 0x134 +#define INT_FORCE_LINE 0x138 +#define INT_ST_BNDRY_FRAME_FATAL 0x280 +#define INT_MSK_BNDRY_FRAME_FATAL 0x284 +#define INT_FORCE_BNDRY_FRAME_FATAL 0x288 +#define INT_ST_SEQ_FRAME_FATAL 0x290 +#define INT_MSK_SEQ_FRAME_FATAL 0x294 +#define INT_FORCE_SEQ_FRAME_FATAL 0x298 +#define INT_ST_CRC_FRAME_FATAL 0x2a0 +#define INT_MSK_CRC_FRAME_FATAL 0x2a4 +#define INT_FORCE_CRC_FRAME_FATAL 0x2a8 +#define INT_ST_PLD_CRC_FATAL 0x2b0 +#define INT_MSK_PLD_CRC_FATAL 0x2b4 +#define INT_FORCE_PLD_CRC_FATAL 0x2b8 +#define INT_ST_DATA_ID 0x2c0 +#define INT_MSK_DATA_ID 0x2c4 +#define INT_FORCE_DATA_ID 0x2c8 +#define INT_ST_ECC_CORRECTED 0x2d0 +#define INT_MSK_ECC_CORRECTED 0x2d4 +#define INT_FORCE_ECC_CORRECTED 0x2d8 +#define SCRAMBLING 0x300 +#define SCRAMBLING_SEED1 0x304 +#define SCRAMBLING_SEED2 0x308 +#define SCRAMBLING_SEED3 0x30c +#define SCRAMBLING_SEED4 0x310 +#define SCRAMBLING 0x300 + +#define IS_IO_CSI2_ADPL_PORT_BASE(i) (IS_IO_BASE + 0x40800 + \ + 0x2000 * (i)) +#define CSI2_ADPL_INPUT_MODE 0x0 +#define CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN 0x4 +#define CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_ADDR 0x8 +#define CSI2_ADPL_CSI_RX_ERR_IRQ_STATUS 0xc +#define CSI2_ADPL_IRQ_CTL_COMMON_STATUS 0xa4 +#define CSI2_ADPL_IRQ_CTL_COMMON_CLEAR 0xa8 +#define CSI2_ADPL_IRQ_CTL_COMMON_ENABLE 0xac +#define CSI2_ADPL_IRQ_CTL_FS_STATUS 0xbc +#define CSI2_ADPL_IRQ_CTL_FS_CLEAR 0xc0 +#define CSI2_ADPL_IRQ_CTL_FS_ENABLE 0xc4 +#define CSI2_ADPL_IRQ_CTL_FE_STATUS 0xc8 +#define CSI2_ADPL_IRQ_CTL_FE_CLEAR 0xcc +#define CSI2_ADPL_IRQ_CTL_FE_ENABLE 0xd0 + +/* software control the legacy csi irq */ +#define IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(i) (IS_IO_BASE + 0x40c00 + \ + 0x2000 * (i)) +#define IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(i) (IS_IO_BASE + 0x40d00 + \ + 0x2000 * (i)) +#define IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE (IS_IO_BASE + 0x49000) +#define IS_IO_CSI2_IRQ_CTRL_BASE (IS_IO_BASE + 0x4e100) + +#define IRQ_CTL_EDGE 0x0 +#define IRQ_CTL_MASK 0x4 +#define IRQ_CTL_STATUS 0x8 +#define IRQ_CTL_CLEAR 0xc +#define IRQ_CTL_ENABLE 0x10 +/* FE irq for PTL */ +#define IRQ1_CTL_MASK 0x14 +#define IRQ1_CTL_STATUS 0x18 +#define IRQ1_CTL_CLEAR 0x1c +#define IRQ1_CTL_ENABLE 0x20 + +/* software to set the clock gate to use the port or mgc */ +#define IS_IO_GPREGS_BASE (IS_IO_BASE + 0x49200) +#define SRST_PORT_ARB 0x0 +#define SRST_MGC 0x4 +#define SRST_WIDTH_CONV 0x8 +#define SRST_CSI_IRQ 0xc +#define SRST_CSI_LEGACY_IRQ 0x10 +#define CLK_EN_TXCLKESC 0x14 +#define CLK_DIV_FACTOR_IS_CLK 0x18 +#define CLK_DIV_FACTOR_APB_CLK 0x1c +#define CSI_PORT_CLK_GATE 0x20 +#define CSI_PORTAB_AGGREGATION 0x24 +#define MGC_CLK_GATE 0x2c +#define CG_CTRL_BITS 0x3c +#define SPARE_RW 0xf8 +#define SPARE_RO 0xfc + +#define IS_IO_CSI2_MPF_PORT_BASE(i) (IS_IO_BASE + 0x53000 + \ + 0x1000 * (i)) +#define MPF_16_IRQ_CNTRL_STATUS 0x238 +#define MPF_16_IRQ_CNTRL_CLEAR 0x23c +#define MPF_16_IRQ_CNTRL_ENABLE 0x240 + +/* software config the phy */ +#define IS_IO_CSI2_GPREGS_BASE (IS_IO_BASE + 0x53400) +#define IPU8_IS_IO_CSI2_GPREGS_BASE (IS_IO_BASE + 0x40e00) +#define CSI_ADAPT_LAYER_SRST 0x0 +#define MPF_SRST_RST 0x4 +#define CSI_ERR_IRQ_CTRL_SRST 0x8 +#define CSI_SYNC_RC_SRST 0xc +#define CSI_CG_CTRL_BITS 0x10 +#define SOC_CSI2HOST_SELECT 0x14 +#define PHY_RESET 0x18 +#define PHY_SHUTDOWN 0x1c +#define PHY_MODE 0x20 +#define PHY_READY 0x24 +#define PHY_CLK_LANE_FORCE_CONTROL 0x28 +#define PHY_CLK_LANE_CONTROL 0x2c +#define PHY_CLK_LANE_STATUS 0x30 +#define PHY_LANE_RX_ESC_REQ 0x34 +#define PHY_LANE_RX_ESC_DATA 0x38 +#define PHY_LANE_TURNDISABLE 0x3c +#define PHY_LANE_DIRECTION 0x40 +#define PHY_LANE_FORCE_CONTROL 0x44 +#define PHY_LANE_CONTROL_EN 0x48 +#define PHY_LANE_CONTROL_DATAWIDTH 0x4c +#define PHY_LANE_STATUS 0x50 +#define PHY_LANE_ERR 0x54 +#define PHY_LANE_RXALP 0x58 +#define PHY_LANE_RXALP_NIBBLE 0x5c +#define PHY_PARITY_ERROR 0x60 +#define PHY_DEBUG_REGS_CLK_GATE_EN 0x64 +#define SPARE_RW 0xf8 +#define SPARE_RO 0xfc + +/* software not touch */ +#define PORT_ARB_BASE (IS_IO_BASE + 0x4e000) +#define PORT_ARB_IRQ_CTL_STATUS 0x4 +#define PORT_ARB_IRQ_CTL_CLEAR 0x8 +#define PORT_ARB_IRQ_CTL_ENABLE 0xc + +#define MGC_PPC 4U +#define MGC_DTYPE_RAW(i) (((i) - 8) / 2) +#define IS_IO_MGC_BASE (IS_IO_BASE + 0x48000) +#define MGC_KICK 0x0 +#define MGC_ASYNC_STOP 0x4 +#define MGC_PORT_OFFSET 0x100 +#define MGC_CSI_PORT_MAP(i) (0x8 + (i) * 0x4) +#define MGC_MG_PORT(i) (IS_IO_MGC_BASE + \ + (i) * MGC_PORT_OFFSET) +/* per mgc instance */ +#define MGC_MG_CSI_ADAPT_LAYER_TYPE 0x28 +#define MGC_MG_MODE 0x2c +#define MGC_MG_INIT_COUNTER 0x30 +#define MGC_MG_MIPI_VC 0x34 +#define MGC_MG_MIPI_DTYPES 0x38 +#define MGC_MG_MULTI_DTYPES_MODE 0x3c +#define MGC_MG_NOF_FRAMES 0x40 +#define MGC_MG_FRAME_DIM 0x44 +#define MGC_MG_HBLANK 0x48 +#define MGC_MG_VBLANK 0x4c +#define MGC_MG_TPG_MODE 0x50 +#define MGC_MG_TPG_R0 0x54 +#define MGC_MG_TPG_G0 0x58 +#define MGC_MG_TPG_B0 0x5c +#define MGC_MG_TPG_R1 0x60 +#define MGC_MG_TPG_G1 0x64 +#define MGC_MG_TPG_B1 0x68 +#define MGC_MG_TPG_FACTORS 0x6c +#define MGC_MG_TPG_MASKS 0x70 +#define MGC_MG_TPG_XY_MASK 0x74 +#define MGC_MG_TPG_TILE_DIM 0x78 +#define MGC_MG_PRBS_LFSR_INIT_0 0x7c +#define MGC_MG_PRBS_LFSR_INIT_1 0x80 +#define MGC_MG_SYNC_STOP_POINT 0x84 +#define MGC_MG_SYNC_STOP_POINT_LOC 0x88 +#define MGC_MG_ERR_INJECT 0x8c +#define MGC_MG_ERR_LOCATION 0x90 +#define MGC_MG_DTO_SPEED_CTRL_EN 0x94 +#define MGC_MG_DTO_SPEED_CTRL_INCR_VAL 0x98 +#define MGC_MG_HOR_LOC_STTS 0x9c +#define MGC_MG_VER_LOC_STTS 0xa0 +#define MGC_MG_FRAME_NUM_STTS 0xa4 +#define MGC_MG_BUSY_STTS 0xa8 +#define MGC_MG_STOPPED_STTS 0xac +/* tile width and height in pixels for Chess board and Color palette */ +#define MGC_TPG_TILE_WIDTH 64U +#define MGC_TPG_TILE_HEIGHT 64U + +#define IPU_CSI_PORT_A_ADDR_OFFSET 0x0 +#define IPU_CSI_PORT_B_ADDR_OFFSET 0x0 +#define IPU_CSI_PORT_C_ADDR_OFFSET 0x0 +#define IPU_CSI_PORT_D_ADDR_OFFSET 0x0 + +/* + * 0 - CSI RX Port 0 interrupt; + * 1 - MPF Port 0 interrupt; + * 2 - CSI RX Port 1 interrupt; + * 3 - MPF Port 1 interrupt; + * 4 - CSI RX Port 2 interrupt; + * 5 - MPF Port 2 interrupt; + * 6 - CSI RX Port 3 interrupt; + * 7 - MPF Port 3 interrupt; + * 8 - Port ARB FIFO 0 overflow; + * 9 - Port ARB FIFO 1 overflow; + * 10 - Port ARB FIFO 2 overflow; + * 11 - Port ARB FIFO 3 overflow; + * 12 - isys_cfgnoc_err_probe_intl; + * 13-15 - reserved + */ +#define IPU7_CSI_IS_IO_IRQ_MASK 0xffff + +/* Adapter layer irq */ +#define IPU7_CSI_ADPL_IRQ_MASK 0xffff + +/* sw irq from legacy irq control + * legacy irq status + * IPU7 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 sync interrupt + * 2 - CSI Port 1 error interrupt + * 3 - CSI Port 1 sync interrupt + * 4 - CSI Port 2 error interrupt + * 5 - CSI Port 2 sync interrupt + * 6 - CSI Port 3 error interrupt + * 7 - CSI Port 3 sync interrupt + * IPU7P5 + * 0 - CSI Port 0 error interrupt + * 1 - CSI Port 0 fs interrupt + * 2 - CSI Port 0 fe interrupt + * 3 - CSI Port 1 error interrupt + * 4 - CSI Port 1 fs interrupt + * 5 - CSI Port 1 fe interrupt + * 6 - CSI Port 2 error interrupt + * 7 - CSI Port 2 fs interrupt + * 8 - CSI Port 2 fe interrupt + */ +#define IPU7_CSI_RX_LEGACY_IRQ_MASK 0x1ff + +/* legacy error status per port + * 0 - Error handler FIFO full; + * 1 - Reserved Short Packet encoding detected; + * 2 - Reserved Long Packet encoding detected; + * 3 - Received packet is too short (fewer data words than specified in header); + * 4 - Received packet is too long (more data words than specified in header); + * 5 - Short packet discarded due to errors; + * 6 - Long packet discarded due to errors; + * 7 - CSI Combo Rx interrupt; + * 8 - IDI CDC FIFO overflow; remaining bits are reserved and tied to 0; + */ +#define IPU7_CSI_RX_ERROR_IRQ_MASK 0xfff + +/* + * 0 - VC0 frame start received + * 1 - VC0 frame end received + * 2 - VC1 frame start received + * 3 - VC1 frame end received + * 4 - VC2 frame start received + * 5 - VC2 frame end received + * 6 - VC3 frame start received + * 7 - VC3 frame end received + * 8 - VC4 frame start received + * 9 - VC4 frame end received + * 10 - VC5 frame start received + * 11 - VC5 frame end received + * 12 - VC6 frame start received + * 13 - VC6 frame end received + * 14 - VC7 frame start received + * 15 - VC7 frame end received + * 16 - VC8 frame start received + * 17 - VC8 frame end received + * 18 - VC9 frame start received + * 19 - VC9 frame end received + * 20 - VC10 frame start received + * 21 - VC10 frame end received + * 22 - VC11 frame start received + * 23 - VC11 frame end received + * 24 - VC12 frame start received + * 25 - VC12 frame end received + * 26 - VC13 frame start received + * 27 - VC13 frame end received + * 28 - VC14 frame start received + * 29 - VC14 frame end received + * 30 - VC15 frame start received + * 31 - VC15 frame end received + */ + +#define IPU7_CSI_RX_SYNC_IRQ_MASK 0x0 +#define IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK 0x0 + +#define CSI_RX_NUM_ERRORS_IN_IRQ 12U +#define CSI_RX_NUM_SYNC_IN_IRQ 32U + +enum CSI_FE_MODE_TYPE { + CSI_FE_DPHY_MODE = 0, + CSI_FE_CPHY_MODE = 1, +}; + +enum CSI_FE_INPUT_MODE { + CSI_SENSOR_INPUT = 0, + CSI_MIPIGEN_INPUT = 1, +}; + +enum MGC_CSI_ADPL_TYPE { + MGC_MAPPED_2_LANES = 0, + MGC_MAPPED_4_LANES = 1, +}; + +enum CSI2HOST_SELECTION { + CSI2HOST_SEL_SOC = 0, + CSI2HOST_SEL_CSI2HOST = 1, +}; + +#define IPU7_ISYS_LEGACY_IRQ_CSI2(port) (0x3 << (port)) +#define IPU7P5_ISYS_LEGACY_IRQ_CSI2(port) (0x7 << (port)) + +/* ---------------------------------------------------------------- */ +#define CSI_REG_BASE 0x220000 +#define CSI_REG_BASE_PORT(id) ((id) * 0x1000) + +/* CSI Port General Purpose Registers */ +#define CSI_REG_PORT_GPREG_SRST 0x0 +#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 +#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 + +#define CSI_RX_NUM_IRQ 32U + +#define IPU7_CSI_RX_SYNC_FS_VC 0x55555555 +#define IPU7_CSI_RX_SYNC_FE_VC 0xaaaaaaaa +#define IPU7P5_CSI_RX_SYNC_FS_VC 0xffff +#define IPU7P5_CSI_RX_SYNC_FE_VC 0xffff + +#endif /* IPU7_ISYS_CSI2_REG_H */ diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi2.c b/drivers/staging/media/ipu7/ipu7-isys-csi2.c new file mode 100644 index 000000000000..9c16ae9a0e5b --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-csi2.c @@ -0,0 +1,543 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-isys.h" +#include "ipu7-isys-csi2.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-isys-csi-phy.h" + +static const u32 csi2_supported_codes[] = { + MEDIA_BUS_FMT_Y10_1X10, + MEDIA_BUS_FMT_RGB565_1X16, + MEDIA_BUS_FMT_RGB888_1X24, + MEDIA_BUS_FMT_UYVY8_1X16, + MEDIA_BUS_FMT_YUYV8_1X16, + MEDIA_BUS_FMT_YUYV10_1X20, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SBGGR12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SRGGB8_1X8, + 0, +}; + +s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2) +{ + struct media_pad *src_pad; + + src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); + if (IS_ERR(src_pad)) { + dev_err(&csi2->isys->adev->auxdev.dev, + "can't get source pad of %s (%ld)\n", + csi2->asd.sd.name, PTR_ERR(src_pad)); + return PTR_ERR(src_pad); + } + + return v4l2_get_link_freq(src_pad, 0, 0); +} + +static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, + struct v4l2_event_subscription *sub) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd); + struct device *dev = &csi2->isys->adev->auxdev.dev; + + dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n", + sub->type, sub->id); + + switch (sub->type) { + case V4L2_EVENT_FRAME_SYNC: + return v4l2_event_subscribe(fh, sub, 10, NULL); + case V4L2_EVENT_CTRL: + return v4l2_ctrl_subscribe_event(fh, sub); + default: + return -EINVAL; + } +} + +static const struct v4l2_subdev_core_ops csi2_sd_core_ops = { + .subscribe_event = csi2_subscribe_event, + .unsubscribe_event = v4l2_event_subdev_unsubscribe, +}; + +static void csi2_irq_enable(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_device *isp = csi2->isys->adev->isp; + unsigned int offset, mask; + + /* enable CSI2 legacy error irq */ + offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IRQ_CTL_ENABLE); + + /* enable CSI2 legacy sync irq */ + offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IRQ_CTL_CLEAR); + writel(mask, csi2->base + offset + IRQ_CTL_MASK); + writel(mask, csi2->base + offset + IRQ_CTL_ENABLE); + + mask = IPU7P5_CSI_RX_SYNC_FE_IRQ_MASK; + if (!is_ipu7(isp->hw_ver)) { + writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR); + writel(mask, csi2->base + offset + IRQ1_CTL_MASK); + writel(mask, csi2->base + offset + IRQ1_CTL_ENABLE); + } +} + +static void csi2_irq_disable(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_device *isp = csi2->isys->adev->isp; + unsigned int offset, mask; + + /* disable CSI2 legacy error irq */ + offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + writel(mask, csi2->base + offset + IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IRQ_CTL_MASK); + writel(0, csi2->base + offset + IRQ_CTL_ENABLE); + + /* disable CSI2 legacy sync irq */ + offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + mask = IPU7_CSI_RX_SYNC_IRQ_MASK; + writel(mask, csi2->base + offset + IRQ_CTL_CLEAR); + writel(0, csi2->base + offset + IRQ_CTL_MASK); + writel(0, csi2->base + offset + IRQ_CTL_ENABLE); + + if (!is_ipu7(isp->hw_ver)) { + writel(mask, csi2->base + offset + IRQ1_CTL_CLEAR); + writel(0, csi2->base + offset + IRQ1_CTL_MASK); + writel(0, csi2->base + offset + IRQ1_CTL_ENABLE); + } +} + +static void ipu7_isys_csi2_disable_stream(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_isys *isys = csi2->isys; + void __iomem *isys_base = isys->pdata->base; + + ipu7_isys_csi_phy_powerdown(csi2); + + writel(0x4, isys_base + IS_IO_GPREGS_BASE + CLK_DIV_FACTOR_APB_CLK); + csi2_irq_disable(csi2); +} + +static int ipu7_isys_csi2_enable_stream(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_isys *isys = csi2->isys; + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *isys_base = isys->pdata->base; + unsigned int port, nlanes, offset; + int ret; + + port = csi2->port; + nlanes = csi2->nlanes; + + offset = IS_IO_GPREGS_BASE; + writel(0x2, isys_base + offset + CLK_DIV_FACTOR_APB_CLK); + dev_dbg(dev, "port %u CLK_GATE = 0x%04x DIV_FACTOR_APB_CLK=0x%04x\n", + port, readl(isys_base + offset + CSI_PORT_CLK_GATE), + readl(isys_base + offset + CLK_DIV_FACTOR_APB_CLK)); + if (port == 0U && nlanes == 4U && !is_ipu7(isys->adev->isp->hw_ver)) { + dev_dbg(dev, "CSI port %u in aggregation mode\n", port); + writel(0x1, isys_base + offset + CSI_PORTAB_AGGREGATION); + } + + /* input is coming from CSI receiver (sensor) */ + offset = IS_IO_CSI2_ADPL_PORT_BASE(port); + writel(CSI_SENSOR_INPUT, isys_base + offset + CSI2_ADPL_INPUT_MODE); + writel(1, isys_base + offset + CSI2_ADPL_CSI_RX_ERR_IRQ_CLEAR_EN); + + ret = ipu7_isys_csi_phy_powerup(csi2); + if (ret) { + dev_err(dev, "CSI-%d PHY power up failed %d\n", port, ret); + return ret; + } + + csi2_irq_enable(csi2); + + return 0; +} + +static int ipu7_isys_csi2_set_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + struct device *dev = &asd->isys->adev->auxdev.dev; + struct v4l2_mbus_framefmt *sink_ffmt; + struct v4l2_mbus_framefmt *src_ffmt; + struct v4l2_rect *crop; + + if (sel->pad == IPU7_CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP) + return -EINVAL; + + sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, + sel->pad, + sel->stream); + if (!sink_ffmt) + return -EINVAL; + + src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream); + if (!src_ffmt) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); + if (!crop) + return -EINVAL; + + /* Only vertical cropping is supported */ + sel->r.left = 0; + sel->r.width = sink_ffmt->width; + /* Non-bayer formats can't be single line cropped */ + if (!ipu7_isys_is_bayer_format(sink_ffmt->code)) + sel->r.top &= ~1U; + sel->r.height = clamp(sel->r.height & ~1U, IPU_ISYS_MIN_HEIGHT, + sink_ffmt->height - sel->r.top); + *crop = sel->r; + + /* update source pad format */ + src_ffmt->width = sel->r.width; + src_ffmt->height = sel->r.height; + if (ipu7_isys_is_bayer_format(sink_ffmt->code)) + src_ffmt->code = ipu7_isys_convert_bayer_order(sink_ffmt->code, + sel->r.left, + sel->r.top); + dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n", + sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height, + src_ffmt->code); + + return 0; +} + +static int ipu7_isys_csi2_get_sel(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + struct v4l2_mbus_framefmt *sink_ffmt; + struct v4l2_rect *crop; + int ret = 0; + + if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK) + return -EINVAL; + + sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, + sel->pad, + sel->stream); + if (!sink_ffmt) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream); + if (!crop) + return -EINVAL; + + switch (sel->target) { + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_CROP_BOUNDS: + sel->r.left = 0; + sel->r.top = 0; + sel->r.width = sink_ffmt->width; + sel->r.height = sink_ffmt->height; + break; + case V4L2_SEL_TGT_CROP: + sel->r = *crop; + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* + * Maximum stream ID is 63 for now, as we use u64 bitmask to represent a set + * of streams. + */ +#define CSI2_SUBDEV_MAX_STREAM_ID 63 + +static int ipu7_isys_csi2_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd); + struct v4l2_subdev *r_sd; + struct media_pad *rp; + u32 sink_pad, sink_stream; + int ret, i; + + if (!csi2->stream_count) { + dev_dbg(&csi2->isys->adev->auxdev.dev, + "stream on CSI2-%u with %u lanes\n", csi2->port, + csi2->nlanes); + ret = ipu7_isys_csi2_enable_stream(csi2); + if (ret) + return ret; + } + + for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) { + if (streams_mask & BIT_ULL(i)) + break; + } + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i, + &sink_pad, &sink_stream); + if (ret) + return ret; + + rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(rp->entity); + + ret = v4l2_subdev_enable_streams(r_sd, rp->index, + BIT_ULL(sink_stream)); + if (!ret) { + csi2->stream_count++; + return 0; + } + + if (!csi2->stream_count) + ipu7_isys_csi2_disable_stream(csi2); + + return ret; +} + +static int ipu7_isys_csi2_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + struct ipu7_isys_csi2 *csi2 = to_ipu7_isys_csi2(asd); + struct v4l2_subdev *r_sd; + struct media_pad *rp; + u32 sink_pad, sink_stream; + int ret, i; + + for (i = 0; i <= CSI2_SUBDEV_MAX_STREAM_ID; i++) { + if (streams_mask & BIT_ULL(i)) + break; + } + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, pad, i, + &sink_pad, &sink_stream); + if (ret) + return ret; + + rp = media_pad_remote_pad_first(&sd->entity.pads[IPU7_CSI2_PAD_SINK]); + r_sd = media_entity_to_v4l2_subdev(rp->entity); + + v4l2_subdev_disable_streams(r_sd, rp->index, BIT_ULL(sink_stream)); + + if (--csi2->stream_count) + return 0; + + dev_dbg(&csi2->isys->adev->auxdev.dev, + "stream off CSI2-%u with %u lanes\n", csi2->port, csi2->nlanes); + + ipu7_isys_csi2_disable_stream(csi2); + + return 0; +} + +static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = { + .get_fmt = v4l2_subdev_get_fmt, + .set_fmt = ipu7_isys_subdev_set_fmt, + .get_selection = ipu7_isys_csi2_get_sel, + .set_selection = ipu7_isys_csi2_set_sel, + .enum_mbus_code = ipu7_isys_subdev_enum_mbus_code, + .enable_streams = ipu7_isys_csi2_enable_streams, + .disable_streams = ipu7_isys_csi2_disable_streams, + .set_routing = ipu7_isys_subdev_set_routing, +}; + +static const struct v4l2_subdev_ops csi2_sd_ops = { + .core = &csi2_sd_core_ops, + .pad = &csi2_sd_pad_ops, +}; + +static const struct media_entity_operations csi2_entity_ops = { + .link_validate = v4l2_subdev_link_validate, + .has_pad_interdep = v4l2_subdev_has_pad_interdep, +}; + +void ipu7_isys_csi2_cleanup(struct ipu7_isys_csi2 *csi2) +{ + if (!csi2->isys) + return; + + v4l2_device_unregister_subdev(&csi2->asd.sd); + v4l2_subdev_cleanup(&csi2->asd.sd); + ipu7_isys_subdev_cleanup(&csi2->asd); + csi2->isys = NULL; +} + +int ipu7_isys_csi2_init(struct ipu7_isys_csi2 *csi2, + struct ipu7_isys *isys, + void __iomem *base, unsigned int index) +{ + struct device *dev = &isys->adev->auxdev.dev; + int ret; + + csi2->isys = isys; + csi2->base = base; + csi2->port = index; + + if (!is_ipu7(isys->adev->isp->hw_ver)) + csi2->legacy_irq_mask = 0x7U << (index * 3U); + else + csi2->legacy_irq_mask = 0x3U << (index * 2U); + + dev_dbg(dev, "csi-%d legacy irq mask = 0x%x\n", index, + csi2->legacy_irq_mask); + + csi2->asd.sd.entity.ops = &csi2_entity_ops; + csi2->asd.isys = isys; + + ret = ipu7_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0, + IPU7_NR_OF_CSI2_SINK_PADS, + IPU7_NR_OF_CSI2_SRC_PADS); + if (ret) + return ret; + + csi2->asd.source = (int)index; + csi2->asd.supported_codes = csi2_supported_codes; + snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name), + IPU_ISYS_ENTITY_PREFIX " CSI2 %u", index); + v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd); + + ret = v4l2_subdev_init_finalize(&csi2->asd.sd); + if (ret) { + dev_err(dev, "failed to init v4l2 subdev (%d)\n", ret); + goto isys_subdev_cleanup; + } + + ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd); + if (ret) { + dev_err(dev, "failed to register v4l2 subdev (%d)\n", ret); + goto v4l2_subdev_cleanup; + } + + return 0; + +v4l2_subdev_cleanup: + v4l2_subdev_cleanup(&csi2->asd.sd); +isys_subdev_cleanup: + ipu7_isys_subdev_cleanup(&csi2->asd); + + return ret; +} + +void ipu7_isys_csi2_sof_event_by_stream(struct ipu7_isys_stream *stream) +{ + struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd); + struct device *dev = &stream->isys->adev->auxdev.dev; + struct video_device *vdev = csi2->asd.sd.devnode; + struct v4l2_event ev = { + .type = V4L2_EVENT_FRAME_SYNC, + }; + + ev.id = stream->vc; + ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence); + v4l2_event_queue(vdev, &ev); + + dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n", + csi2->port, ev.u.frame_sync.frame_sequence, stream->vc); +} + +void ipu7_isys_csi2_eof_event_by_stream(struct ipu7_isys_stream *stream) +{ + struct ipu7_isys_csi2 *csi2 = ipu7_isys_subdev_to_csi2(stream->asd); + struct device *dev = &stream->isys->adev->auxdev.dev; + u32 frame_sequence = atomic_read(&stream->sequence); + + dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n", + csi2->port, frame_sequence); +} + +int ipu7_isys_csi2_get_remote_desc(u32 source_stream, + struct ipu7_isys_csi2 *csi2, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry, + int *nr_queues) +{ + struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct v4l2_mbus_frame_desc desc; + struct v4l2_subdev *source; + struct media_pad *pad; + unsigned int i; + int ret; + + source = media_entity_to_v4l2_subdev(source_entity); + if (!source) + return -EPIPE; + + pad = media_pad_remote_pad_first(&csi2->asd.pad[IPU7_CSI2_PAD_SINK]); + if (!pad) + return -EPIPE; + + ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc); + if (ret) + return ret; + + if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { + dev_err(dev, "Unsupported frame descriptor type\n"); + return -EINVAL; + } + + for (i = 0; i < desc.num_entries; i++) { + if (source_stream == desc.entry[i].stream) { + desc_entry = &desc.entry[i]; + break; + } + } + + if (!desc_entry) { + dev_err(dev, "Failed to find stream %u from remote subdev\n", + source_stream); + return -EINVAL; + } + + if (desc_entry->bus.csi2.vc >= IPU7_NR_OF_CSI2_VC) { + dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc); + return -EINVAL; + } + + *entry = *desc_entry; + + for (i = 0; i < desc.num_entries; i++) { + if (desc_entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc) + (*nr_queues)++; + } + + return 0; +} diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi2.h b/drivers/staging/media/ipu7/ipu7-isys-csi2.h new file mode 100644 index 000000000000..6c23b80f92a2 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-csi2.h @@ -0,0 +1,64 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_CSI2_H +#define IPU7_ISYS_CSI2_H + +#include +#include + +#include "ipu7-isys-subdev.h" +#include "ipu7-isys-video.h" + +struct ipu7_isys; +struct ipu7_isys_csi2_pdata; +struct ipu7_isys_stream; + +#define IPU7_NR_OF_CSI2_VC 16U +#define INVALID_VC_ID -1 +#define IPU7_NR_OF_CSI2_SINK_PADS 1U +#define IPU7_CSI2_PAD_SINK 0U +#define IPU7_NR_OF_CSI2_SRC_PADS 8U +#define IPU7_CSI2_PAD_SRC 1U +#define IPU7_NR_OF_CSI2_PADS (IPU7_NR_OF_CSI2_SINK_PADS + \ + IPU7_NR_OF_CSI2_SRC_PADS) + +/* + * struct ipu7_isys_csi2 + * + * @nlanes: number of lanes in the receiver + */ +struct ipu7_isys_csi2 { + struct ipu7_isys_subdev asd; + struct ipu7_isys_csi2_pdata *pdata; + struct ipu7_isys *isys; + struct ipu7_isys_video av[IPU7_NR_OF_CSI2_SRC_PADS]; + + void __iomem *base; + u32 receiver_errors; + u32 legacy_irq_mask; + unsigned int nlanes; + unsigned int port; + unsigned int phy_mode; + unsigned int stream_count; +}; + +#define ipu7_isys_subdev_to_csi2(__sd) \ + container_of(__sd, struct ipu7_isys_csi2, asd) + +#define to_ipu7_isys_csi2(__asd) container_of(__asd, struct ipu7_isys_csi2, asd) + +s64 ipu7_isys_csi2_get_link_freq(struct ipu7_isys_csi2 *csi2); +int ipu7_isys_csi2_init(struct ipu7_isys_csi2 *csi2, struct ipu7_isys *isys, + void __iomem *base, unsigned int index); +void ipu7_isys_csi2_cleanup(struct ipu7_isys_csi2 *csi2); +void ipu7_isys_csi2_sof_event_by_stream(struct ipu7_isys_stream *stream); +void ipu7_isys_csi2_eof_event_by_stream(struct ipu7_isys_stream *stream); +int ipu7_isys_csi2_get_remote_desc(u32 source_stream, + struct ipu7_isys_csi2 *csi2, + struct media_entity *source_entity, + struct v4l2_mbus_frame_desc_entry *entry, + int *nr_queues); +#endif /* IPU7_ISYS_CSI2_H */ diff --git a/drivers/staging/media/ipu7/ipu7-isys-queue.c b/drivers/staging/media/ipu7/ipu7-isys-queue.c new file mode 100644 index 000000000000..7046c29141f8 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-queue.c @@ -0,0 +1,829 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "abi/ipu7_fw_isys_abi.h" + +#include "ipu7-bus.h" +#include "ipu7-dma.h" +#include "ipu7-fw-isys.h" +#include "ipu7-isys.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-isys-video.h" +#include "ipu7-platform-regs.h" + +#define IPU_MAX_FRAME_COUNTER (U8_MAX + 1) + +static int ipu7_isys_buf_init(struct vb2_buffer *vb) +{ + struct ipu7_isys *isys = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu7_isys_video_buffer *ivb = + vb2_buffer_to_ipu7_isys_video_buffer(vvb); + int ret; + + ret = ipu7_dma_map_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); + if (ret) + return ret; + + ivb->dma_addr = sg_dma_address(sg->sgl); + + return 0; +} + +static void ipu7_isys_buf_cleanup(struct vb2_buffer *vb) +{ + struct ipu7_isys *isys = vb2_get_drv_priv(vb->vb2_queue); + struct sg_table *sg = vb2_dma_sg_plane_desc(vb, 0); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu7_isys_video_buffer *ivb = + vb2_buffer_to_ipu7_isys_video_buffer(vvb); + + ivb->dma_addr = 0; + ipu7_dma_unmap_sgtable(isys->adev, sg, DMA_TO_DEVICE, 0); +} + +static int ipu7_isys_queue_setup(struct vb2_queue *q, unsigned int *num_buffers, + unsigned int *num_planes, unsigned int sizes[], + struct device *alloc_devs[]) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 size = av->pix_fmt.sizeimage; + + /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ + if (!*num_planes) { + sizes[0] = size; + } else if (sizes[0] < size) { + dev_dbg(dev, "%s: queue setup: size %u < %u\n", + av->vdev.name, sizes[0], size); + return -EINVAL; + } + + *num_planes = 1; + + return 0; +} + +static int ipu7_isys_buf_prepare(struct vb2_buffer *vb) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + u32 bytesperline = av->pix_fmt.bytesperline; + u32 height = av->pix_fmt.height; + + dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", + av->vdev.name, av->pix_fmt.sizeimage, vb2_plane_size(vb, 0)); + + if (av->pix_fmt.sizeimage > vb2_plane_size(vb, 0)) + return -EINVAL; + + dev_dbg(dev, "buffer: %s: bytesperline %u, height %u\n", + av->vdev.name, bytesperline, height); + vb2_set_plane_payload(vb, 0, bytesperline * height); + + return 0; +} + +/* + * Queue a buffer list back to incoming or active queues. The buffers + * are removed from the buffer list. + */ +void ipu7_isys_buffer_list_queue(struct ipu7_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state) +{ + struct ipu7_isys_buffer *ib, *ib_safe; + unsigned long flags; + bool first = true; + + if (!bl) + return; + + WARN_ON_ONCE(!bl->nbufs); + WARN_ON_ONCE(op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE && + op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING); + + list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { + struct ipu7_isys_video *av; + + struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib); + struct ipu7_isys_queue *aq = + vb2_queue_to_isys_queue(vb->vb2_queue); + + av = ipu7_isys_queue_to_video(aq); + spin_lock_irqsave(&aq->lock, flags); + list_del(&ib->head); + if (op_flags & IPU_ISYS_BUFFER_LIST_FL_ACTIVE) + list_add(&ib->head, &aq->active); + else if (op_flags & IPU_ISYS_BUFFER_LIST_FL_INCOMING) + list_add_tail(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (op_flags & IPU_ISYS_BUFFER_LIST_FL_SET_STATE) + vb2_buffer_done(vb, state); + + if (first) { + dev_dbg(&av->isys->adev->auxdev.dev, + "queue buf list %p flags %lx, s %d, %d bufs\n", + bl, op_flags, state, bl->nbufs); + first = false; + } + + bl->nbufs--; + } + + WARN_ON(bl->nbufs); +} + +/* + * flush_firmware_streamon_fail() - Flush in cases where requests may + * have been queued to firmware and the *firmware streamon fails for a + * reason or another. + */ +static void flush_firmware_streamon_fail(struct ipu7_isys_stream *stream) +{ + struct ipu7_isys_queue *aq; + unsigned long flags; + + lockdep_assert_held(&stream->mutex); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_buffer *ib, *ib_safe; + + spin_lock_irqsave(&aq->lock, flags); + list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { + struct vb2_buffer *vb = + ipu7_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + if (av->streaming) { + dev_dbg(dev, + "%s: queue buffer %u back to incoming\n", + av->vdev.name, vb->index); + /* Queue already streaming, return to driver. */ + list_add(&ib->head, &aq->incoming); + continue; + } + /* Queue not yet streaming, return to user. */ + dev_dbg(dev, "%s: return %u back to videobuf2\n", + av->vdev.name, vb->index); + vb2_buffer_done(ipu7_isys_buffer_to_vb2_buffer(ib), + VB2_BUF_STATE_QUEUED); + } + spin_unlock_irqrestore(&aq->lock, flags); + } +} + +/* + * Attempt obtaining a buffer list from the incoming queues, a list of buffers + * that contains one entry from each video buffer queue. If a buffer can't be + * obtained from every queue, the buffers are returned back to the queue. + */ +static int buffer_list_get(struct ipu7_isys_stream *stream, + struct ipu7_isys_buffer_list *bl) +{ + unsigned long buf_flag = IPU_ISYS_BUFFER_LIST_FL_INCOMING; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu7_isys_queue *aq; + unsigned long flags; + + bl->nbufs = 0; + INIT_LIST_HEAD(&bl->head); + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu7_isys_buffer *ib; + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->incoming)) { + spin_unlock_irqrestore(&aq->lock, flags); + if (!list_empty(&bl->head)) + ipu7_isys_buffer_list_queue(bl, buf_flag, 0); + return -ENODATA; + } + + ib = list_last_entry(&aq->incoming, + struct ipu7_isys_buffer, head); + + dev_dbg(dev, "buffer: %s: buffer %u\n", + ipu7_isys_queue_to_video(aq)->vdev.name, + ipu7_isys_buffer_to_vb2_buffer(ib)->index); + list_del(&ib->head); + list_add(&ib->head, &bl->head); + spin_unlock_irqrestore(&aq->lock, flags); + + bl->nbufs++; + } + + dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); + + return 0; +} + +static void ipu7_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, + struct ipu7_insys_buffset *set) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu7_isys_video_buffer *ivb = + vb2_buffer_to_ipu7_isys_video_buffer(vvb); + + set->output_pins[aq->fw_output].addr = ivb->dma_addr; + set->output_pins[aq->fw_output].user_token = (uintptr_t)set; +} + +/* + * Convert a buffer list to a isys fw ABI framebuffer set. The + * buffer list is not modified. + */ +#define IPU_ISYS_FRAME_NUM_THRESHOLD (30) +void ipu7_isys_buffer_to_fw_frame_buff(struct ipu7_insys_buffset *set, + struct ipu7_isys_stream *stream, + struct ipu7_isys_buffer_list *bl) +{ + struct ipu7_isys_buffer *ib; + u32 buf_id; + + WARN_ON(!bl->nbufs); + + set->skip_frame = 0; + set->capture_msg_map = IPU_INSYS_FRAME_ENABLE_MSG_SEND_RESP | + IPU_INSYS_FRAME_ENABLE_MSG_SEND_IRQ; + + buf_id = atomic_fetch_inc(&stream->buf_id); + set->frame_id = buf_id % IPU_MAX_FRAME_COUNTER; + + list_for_each_entry(ib, &bl->head, head) { + struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib); + + ipu7_isys_buf_to_fw_frame_buf_pin(vb, set); + } +} + +/* Start streaming for real. The buffer list must be available. */ +static int ipu7_isys_stream_start(struct ipu7_isys_video *av, + struct ipu7_isys_buffer_list *bl, bool error) +{ + struct ipu7_isys_stream *stream = av->stream; + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu7_isys_buffer_list __bl; + int ret; + + mutex_lock(&stream->isys->stream_mutex); + + ret = ipu7_isys_video_set_streaming(av, 1, bl); + mutex_unlock(&stream->isys->stream_mutex); + if (ret) + goto out_requeue; + + stream->streaming = 1; + + bl = &__bl; + + do { + struct ipu7_insys_buffset *buf = NULL; + struct isys_fw_msgs *msg; + enum ipu7_insys_send_type send_type = + IPU_INSYS_SEND_TYPE_STREAM_CAPTURE; + + ret = buffer_list_get(stream, bl); + if (ret < 0) + break; + + msg = ipu7_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + buf = &msg->fw_msg.frame; + + ipu7_isys_buffer_to_fw_frame_buff(buf, stream, bl); + + ipu7_fw_isys_dump_frame_buff_set(dev, buf, + stream->nr_output_pins); + + ipu7_isys_buffer_list_queue(bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE, + 0); + + ret = ipu7_fw_isys_complex_cmd(stream->isys, + stream->stream_handle, buf, + msg->dma_addr, sizeof(*buf), + send_type); + } while (!WARN_ON(ret)); + + return 0; + +out_requeue: + if (bl && bl->nbufs) + ipu7_isys_buffer_list_queue(bl, + IPU_ISYS_BUFFER_LIST_FL_INCOMING | + (error ? + IPU_ISYS_BUFFER_LIST_FL_SET_STATE : + 0), error ? VB2_BUF_STATE_ERROR : + VB2_BUF_STATE_QUEUED); + flush_firmware_streamon_fail(stream); + + return ret; +} + +static void buf_queue(struct vb2_buffer *vb) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); + struct ipu7_isys_video_buffer *ivb = + vb2_buffer_to_ipu7_isys_video_buffer(vvb); + struct media_pipeline *media_pipe = + media_entity_pipeline(&av->vdev.entity); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_stream *stream = av->stream; + struct ipu7_isys_buffer *ib = &ivb->ib; + struct ipu7_insys_buffset *buf = NULL; + struct ipu7_isys_buffer_list bl; + struct isys_fw_msgs *msg; + unsigned long flags; + dma_addr_t dma; + int ret; + + dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); + + dma = ivb->dma_addr; + dev_dbg(dev, "iova: iova %pad\n", &dma); + + spin_lock_irqsave(&aq->lock, flags); + list_add(&ib->head, &aq->incoming); + spin_unlock_irqrestore(&aq->lock, flags); + + if (!media_pipe || !vb->vb2_queue->start_streaming_called) { + dev_dbg(dev, "media pipeline is not ready for %s\n", + av->vdev.name); + return; + } + + mutex_lock(&stream->mutex); + + if (stream->nr_streaming != stream->nr_queues) { + dev_dbg(dev, "not streaming yet, adding to incoming\n"); + goto out; + } + + /* + * We just put one buffer to the incoming list of this queue + * (above). Let's see whether all queues in the pipeline would + * have a buffer. + */ + ret = buffer_list_get(stream, &bl); + if (ret < 0) { + dev_dbg(dev, "No buffers available\n"); + goto out; + } + + msg = ipu7_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out; + } + + buf = &msg->fw_msg.frame; + + ipu7_isys_buffer_to_fw_frame_buff(buf, stream, &bl); + + ipu7_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); + + if (!stream->streaming) { + ret = ipu7_isys_stream_start(av, &bl, true); + if (ret) + dev_err(dev, "stream start failed.\n"); + goto out; + } + + /* + * We must queue the buffers in the buffer list to the + * appropriate video buffer queues BEFORE passing them to the + * firmware since we could get a buffer event back before we + * have queued them ourselves to the active queue. + */ + ipu7_isys_buffer_list_queue(&bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + + ret = ipu7_fw_isys_complex_cmd(stream->isys, stream->stream_handle, + buf, msg->dma_addr, sizeof(*buf), + IPU_INSYS_SEND_TYPE_STREAM_CAPTURE); + if (ret < 0) + dev_err(dev, "send stream capture failed\n"); + +out: + mutex_unlock(&stream->mutex); +} + +static int ipu7_isys_link_fmt_validate(struct ipu7_isys_queue *aq) +{ + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *remote_pad = + media_pad_remote_pad_first(av->vdev.entity.pads); + struct v4l2_mbus_framefmt format; + struct v4l2_subdev *sd; + u32 r_stream, code; + int ret; + + if (!remote_pad) + return -ENOTCONN; + + sd = media_entity_to_v4l2_subdev(remote_pad->entity); + r_stream = ipu7_isys_get_src_stream_by_src_pad(sd, remote_pad->index); + + ret = ipu7_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, + &format); + if (ret) { + dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", + sd->entity.name, remote_pad->index, r_stream); + return ret; + } + + if (format.width != av->pix_fmt.width || + format.height != av->pix_fmt.height) { + dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", + av->pix_fmt.width, av->pix_fmt.height, format.width, + format.height); + return -EINVAL; + } + + code = ipu7_isys_get_isys_format(av->pix_fmt.pixelformat)->code; + if (format.code != code) { + dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", + code, format.code); + return -EINVAL; + } + + return 0; +} + +static void return_buffers(struct ipu7_isys_queue *aq, + enum vb2_buffer_state state) +{ + struct ipu7_isys_buffer *ib; + struct vb2_buffer *vb; + unsigned long flags; + + spin_lock_irqsave(&aq->lock, flags); + /* + * Something went wrong (FW crash / HW hang / not all buffers + * returned from isys) if there are still buffers queued in active + * queue. We have to clean up places a bit. + */ + while (!list_empty(&aq->active)) { + ib = list_last_entry(&aq->active, struct ipu7_isys_buffer, + head); + vb = ipu7_isys_buffer_to_vb2_buffer(ib); + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + } + + while (!list_empty(&aq->incoming)) { + ib = list_last_entry(&aq->incoming, struct ipu7_isys_buffer, + head); + vb = ipu7_isys_buffer_to_vb2_buffer(ib); + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + vb2_buffer_done(vb, state); + + spin_lock_irqsave(&aq->lock, flags); + } + + spin_unlock_irqrestore(&aq->lock, flags); +} + +static void ipu7_isys_stream_cleanup(struct ipu7_isys_video *av) +{ + video_device_pipeline_stop(&av->vdev); + ipu7_isys_put_stream(av->stream); + av->stream = NULL; +} + +static int start_streaming(struct vb2_queue *q, unsigned int count) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + const struct ipu7_isys_pixelformat *pfmt = + ipu7_isys_get_isys_format(av->pix_fmt.pixelformat); + struct ipu7_isys_buffer_list __bl, *bl = NULL; + struct ipu7_isys_stream *stream; + struct media_entity *source_entity = NULL; + int nr_queues, ret; + + dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", + av->vdev.name, av->pix_fmt.width, av->pix_fmt.height, + pfmt->css_pixelformat); + + ret = ipu7_isys_setup_video(av, &source_entity, &nr_queues); + if (ret < 0) { + dev_dbg(dev, "failed to setup video\n"); + goto out_return_buffers; + } + + ret = ipu7_isys_link_fmt_validate(aq); + if (ret) { + dev_dbg(dev, + "%s: link format validation failed (%d)\n", + av->vdev.name, ret); + goto out_pipeline_stop; + } + + stream = av->stream; + mutex_lock(&stream->mutex); + if (!stream->nr_streaming) { + ret = ipu7_isys_video_prepare_stream(av, source_entity, + nr_queues); + if (ret) { + mutex_unlock(&stream->mutex); + goto out_pipeline_stop; + } + } + + stream->nr_streaming++; + dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, + stream->nr_queues); + + list_add(&aq->node, &stream->queues); + + if (stream->nr_streaming != stream->nr_queues) + goto out; + + bl = &__bl; + ret = buffer_list_get(stream, bl); + if (ret < 0) { + dev_warn(dev, "no buffer available, DRIVER BUG?\n"); + goto out; + } + + ret = ipu7_isys_fw_open(av->isys); + if (ret) + goto out_stream_start; + + ipu7_isys_setup_hw(av->isys); + + ret = ipu7_isys_stream_start(av, bl, false); + if (ret) + goto out_isys_fw_close; + +out: + mutex_unlock(&stream->mutex); + + return 0; + +out_isys_fw_close: + ipu7_isys_fw_close(av->isys); + +out_stream_start: + list_del(&aq->node); + stream->nr_streaming--; + mutex_unlock(&stream->mutex); + +out_pipeline_stop: + ipu7_isys_stream_cleanup(av); + +out_return_buffers: + return_buffers(aq, VB2_BUF_STATE_QUEUED); + + return ret; +} + +static void stop_streaming(struct vb2_queue *q) +{ + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(q); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct ipu7_isys_stream *stream = av->stream; + + mutex_lock(&stream->mutex); + mutex_lock(&av->isys->stream_mutex); + if (stream->nr_streaming == stream->nr_queues && stream->streaming) + ipu7_isys_video_set_streaming(av, 0, NULL); + mutex_unlock(&av->isys->stream_mutex); + + stream->nr_streaming--; + list_del(&aq->node); + stream->streaming = 0; + + mutex_unlock(&stream->mutex); + + ipu7_isys_stream_cleanup(av); + + return_buffers(aq, VB2_BUF_STATE_ERROR); + + ipu7_isys_fw_close(av->isys); +} + +static unsigned int +get_sof_sequence_by_timestamp(struct ipu7_isys_stream *stream, u64 time) +{ + struct ipu7_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + + /* + * The timestamp is invalid as no TSC in some FPGA platform, + * so get the sequence from pipeline directly in this case. + */ + if (time == 0) + return atomic_read(&stream->sequence) - 1; + + for (i = 0; i < IPU_ISYS_MAX_PARALLEL_SOF; i++) + if (time == stream->seq[i].timestamp) { + dev_dbg(dev, "SOF: using seq nr %u for ts %llu\n", + stream->seq[i].sequence, time); + return stream->seq[i].sequence; + } + + dev_dbg(dev, "SOF: looking for %llu\n", time); + for (i = 0; i < IPU_ISYS_MAX_PARALLEL_SOF; i++) + dev_dbg(dev, "SOF: sequence %u, timestamp value %llu\n", + stream->seq[i].sequence, stream->seq[i].timestamp); + dev_dbg(dev, "SOF sequence number not found\n"); + + return atomic_read(&stream->sequence) - 1; +} + +static u64 get_sof_ns_delta(struct ipu7_isys_video *av, u64 time) +{ + struct ipu7_bus_device *adev = av->isys->adev; + struct ipu7_device *isp = adev->isp; + u64 delta, tsc_now; + + ipu_buttress_tsc_read(isp, &tsc_now); + if (!tsc_now) + return 0; + + delta = tsc_now - time; + + return ipu_buttress_tsc_ticks_to_ns(delta, isp); +} + +static void ipu7_isys_buf_calc_sequence_time(struct ipu7_isys_buffer *ib, + u64 time) +{ + struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib); + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct ipu7_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_stream *stream = av->stream; + u64 ns; + u32 sequence; + + ns = ktime_get_ns() - get_sof_ns_delta(av, time); + sequence = get_sof_sequence_by_timestamp(stream, time); + + vbuf->vb2_buf.timestamp = ns; + vbuf->sequence = sequence; + + dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", + av->vdev.name, ktime_get_ns(), sequence); + dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, + vbuf->vb2_buf.timestamp); +} + +static void ipu7_isys_queue_buf_done(struct ipu7_isys_buffer *ib) +{ + struct vb2_buffer *vb = ipu7_isys_buffer_to_vb2_buffer(ib); + + if (atomic_read(&ib->str2mmio_flag)) { + vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); + /* + * Operation on buffer is ended with error and will be reported + * to the userspace when it is de-queued + */ + atomic_set(&ib->str2mmio_flag, 0); + } else { + vb2_buffer_done(vb, VB2_BUF_STATE_DONE); + } +} + +void ipu7_isys_queue_buf_ready(struct ipu7_isys_stream *stream, + struct ipu7_insys_resp *info) +{ + struct ipu7_isys_queue *aq = stream->output_pins[info->pin_id].aq; + u64 time = ((u64)info->timestamp[1] << 32 | info->timestamp[0]); + struct ipu7_isys *isys = stream->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct ipu7_isys_buffer *ib; + struct vb2_buffer *vb; + unsigned long flags; + bool first = true; + struct vb2_v4l2_buffer *buf; + + dev_dbg(dev, "buffer: %s: received buffer %8.8x %d\n", + ipu7_isys_queue_to_video(aq)->vdev.name, info->pin.addr, + info->frame_id); + + spin_lock_irqsave(&aq->lock, flags); + if (list_empty(&aq->active)) { + spin_unlock_irqrestore(&aq->lock, flags); + dev_err(dev, "active queue empty\n"); + return; + } + + list_for_each_entry_reverse(ib, &aq->active, head) { + struct ipu7_isys_video_buffer *ivb; + struct vb2_v4l2_buffer *vvb; + dma_addr_t addr; + + vb = ipu7_isys_buffer_to_vb2_buffer(ib); + vvb = to_vb2_v4l2_buffer(vb); + ivb = vb2_buffer_to_ipu7_isys_video_buffer(vvb); + addr = ivb->dma_addr; + + if (info->pin.addr != addr) { + if (first) + dev_err(dev, "Unexpected buffer address %pad\n", + &addr); + + first = false; + continue; + } + + dev_dbg(dev, "buffer: found buffer %pad\n", &addr); + + buf = to_vb2_v4l2_buffer(vb); + buf->field = V4L2_FIELD_NONE; + + list_del(&ib->head); + spin_unlock_irqrestore(&aq->lock, flags); + + ipu7_isys_buf_calc_sequence_time(ib, time); + + ipu7_isys_queue_buf_done(ib); + + return; + } + + dev_err(dev, "Failed to find a matching video buffer\n"); + + spin_unlock_irqrestore(&aq->lock, flags); +} + +static const struct vb2_ops ipu7_isys_queue_ops = { + .queue_setup = ipu7_isys_queue_setup, + .buf_init = ipu7_isys_buf_init, + .buf_prepare = ipu7_isys_buf_prepare, + .buf_cleanup = ipu7_isys_buf_cleanup, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .buf_queue = buf_queue, +}; + +int ipu7_isys_queue_init(struct ipu7_isys_queue *aq) +{ + struct ipu7_isys *isys = ipu7_isys_queue_to_video(aq)->isys; + struct ipu7_isys_video *av = ipu7_isys_queue_to_video(aq); + struct ipu7_bus_device *adev = isys->adev; + int ret; + + if (!aq->vbq.io_modes) + aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; + + aq->vbq.drv_priv = isys; + aq->vbq.ops = &ipu7_isys_queue_ops; + aq->vbq.lock = &av->mutex; + aq->vbq.mem_ops = &vb2_dma_sg_memops; + aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + aq->vbq.min_queued_buffers = 1; + aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; + + ret = vb2_queue_init(&aq->vbq); + if (ret) + return ret; + + aq->dev = &adev->auxdev.dev; + aq->vbq.dev = &adev->isp->pdev->dev; + spin_lock_init(&aq->lock); + INIT_LIST_HEAD(&aq->active); + INIT_LIST_HEAD(&aq->incoming); + + return 0; +} diff --git a/drivers/staging/media/ipu7/ipu7-isys-queue.h b/drivers/staging/media/ipu7/ipu7-isys-queue.h new file mode 100644 index 000000000000..0cb08a38f756 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-queue.h @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_QUEUE_H +#define IPU7_ISYS_QUEUE_H + +#include +#include +#include +#include + +#include + +struct device; +struct ipu7_isys_stream; +struct ipu7_insys_resp; +struct ipu7_insys_buffset; + +struct ipu7_isys_queue { + struct vb2_queue vbq; + struct list_head node; + struct device *dev; + spinlock_t lock; + struct list_head active; + struct list_head incoming; + unsigned int fw_output; +}; + +struct ipu7_isys_buffer { + struct list_head head; + atomic_t str2mmio_flag; +}; + +struct ipu7_isys_video_buffer { + struct vb2_v4l2_buffer vb_v4l2; + struct ipu7_isys_buffer ib; + dma_addr_t dma_addr; +}; + +#define IPU_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) +#define IPU_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) +#define IPU_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2) + +struct ipu7_isys_buffer_list { + struct list_head head; + unsigned int nbufs; +}; + +#define vb2_queue_to_isys_queue(__vb2) \ + container_of(__vb2, struct ipu7_isys_queue, vbq) + +#define ipu7_isys_to_isys_video_buffer(__ib) \ + container_of(__ib, struct ipu7_isys_video_buffer, ib) + +#define vb2_buffer_to_ipu7_isys_video_buffer(__vvb) \ + container_of(__vvb, struct ipu7_isys_video_buffer, vb_v4l2) + +#define ipu7_isys_buffer_to_vb2_buffer(__ib) \ + (&ipu7_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) + +void ipu7_isys_buffer_list_queue(struct ipu7_isys_buffer_list *bl, + unsigned long op_flags, + enum vb2_buffer_state state); +void ipu7_isys_buffer_to_fw_frame_buff(struct ipu7_insys_buffset *set, + struct ipu7_isys_stream *stream, + struct ipu7_isys_buffer_list *bl); +void ipu7_isys_queue_buf_ready(struct ipu7_isys_stream *stream, + struct ipu7_insys_resp *info); +int ipu7_isys_queue_init(struct ipu7_isys_queue *aq); +#endif /* IPU7_ISYS_QUEUE_H */ diff --git a/drivers/staging/media/ipu7/ipu7-isys-subdev.c b/drivers/staging/media/ipu7/ipu7-isys-subdev.c new file mode 100644 index 000000000000..98b6ef6a2f21 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.c @@ -0,0 +1,348 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "ipu7-bus.h" +#include "ipu7-isys.h" +#include "ipu7-isys-subdev.h" + +unsigned int ipu7_isys_mbus_code_to_mipi(u32 code) +{ + switch (code) { + case MEDIA_BUS_FMT_RGB565_1X16: + return MIPI_CSI2_DT_RGB565; + case MEDIA_BUS_FMT_RGB888_1X24: + return MIPI_CSI2_DT_RGB888; + case MEDIA_BUS_FMT_YUYV10_1X20: + return MIPI_CSI2_DT_YUV422_10B; + case MEDIA_BUS_FMT_UYVY8_1X16: + case MEDIA_BUS_FMT_YUYV8_1X16: + return MIPI_CSI2_DT_YUV422_8B; + case MEDIA_BUS_FMT_SBGGR12_1X12: + case MEDIA_BUS_FMT_SGBRG12_1X12: + case MEDIA_BUS_FMT_SGRBG12_1X12: + case MEDIA_BUS_FMT_SRGGB12_1X12: + return MIPI_CSI2_DT_RAW12; + case MEDIA_BUS_FMT_Y10_1X10: + case MEDIA_BUS_FMT_SBGGR10_1X10: + case MEDIA_BUS_FMT_SGBRG10_1X10: + case MEDIA_BUS_FMT_SGRBG10_1X10: + case MEDIA_BUS_FMT_SRGGB10_1X10: + return MIPI_CSI2_DT_RAW10; + case MEDIA_BUS_FMT_SBGGR8_1X8: + case MEDIA_BUS_FMT_SGBRG8_1X8: + case MEDIA_BUS_FMT_SGRBG8_1X8: + case MEDIA_BUS_FMT_SRGGB8_1X8: + return MIPI_CSI2_DT_RAW8; + default: + WARN_ON(1); + return 0xff; + } +} + +bool ipu7_isys_is_bayer_format(u32 code) +{ + switch (ipu7_isys_mbus_code_to_mipi(code)) { + case MIPI_CSI2_DT_RAW8: + case MIPI_CSI2_DT_RAW10: + case MIPI_CSI2_DT_RAW12: + case MIPI_CSI2_DT_RAW14: + case MIPI_CSI2_DT_RAW16: + case MIPI_CSI2_DT_RAW20: + case MIPI_CSI2_DT_RAW24: + case MIPI_CSI2_DT_RAW28: + return true; + default: + return false; + } +} + +u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y) +{ + static const u32 code_map[] = { + MEDIA_BUS_FMT_SRGGB8_1X8, + MEDIA_BUS_FMT_SGRBG8_1X8, + MEDIA_BUS_FMT_SGBRG8_1X8, + MEDIA_BUS_FMT_SBGGR8_1X8, + MEDIA_BUS_FMT_SRGGB10_1X10, + MEDIA_BUS_FMT_SGRBG10_1X10, + MEDIA_BUS_FMT_SGBRG10_1X10, + MEDIA_BUS_FMT_SBGGR10_1X10, + MEDIA_BUS_FMT_SRGGB12_1X12, + MEDIA_BUS_FMT_SGRBG12_1X12, + MEDIA_BUS_FMT_SGBRG12_1X12, + MEDIA_BUS_FMT_SBGGR12_1X12, + }; + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(code_map); i++) + if (code_map[i] == code) + break; + + if (WARN_ON(i == ARRAY_SIZE(code_map))) + return code; + + return code_map[i ^ ((((u32)y & 1U) << 1U) | ((u32)x & 1U))]; +} + +int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + u32 code = asd->supported_codes[0]; + struct v4l2_mbus_framefmt *fmt; + u32 other_pad, other_stream; + struct v4l2_rect *crop; + unsigned int i; + int ret; + + /* No transcoding, source and sink formats must match. */ + if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) && + sd->entity.num_pads > 1) + return v4l2_subdev_get_fmt(sd, state, format); + + format->format.width = clamp(format->format.width, IPU_ISYS_MIN_WIDTH, + IPU_ISYS_MAX_WIDTH); + format->format.height = clamp(format->format.height, + IPU_ISYS_MIN_HEIGHT, + IPU_ISYS_MAX_HEIGHT); + + for (i = 0; asd->supported_codes[i]; i++) { + if (asd->supported_codes[i] == format->format.code) { + code = asd->supported_codes[i]; + break; + } + } + format->format.code = code; + format->format.field = V4L2_FIELD_NONE; + + /* Store the format and propagate it to the source pad. */ + fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK)) + return 0; + + /* propagate format to following source pad */ + fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, + format->stream); + if (!fmt) + return -EINVAL; + + *fmt = format->format; + + ret = v4l2_subdev_routing_find_opposite_end(&state->routing, + format->pad, + format->stream, + &other_pad, + &other_stream); + if (ret) + return -EINVAL; + + crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream); + /* reset crop */ + crop->left = 0; + crop->top = 0; + crop->width = fmt->width; + crop->height = fmt->height; + + return 0; +} + +int ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum *code) +{ + struct ipu7_isys_subdev *asd = to_ipu7_isys_subdev(sd); + const u32 *supported_codes = asd->supported_codes; + u32 index; + + for (index = 0; supported_codes[index]; index++) { + if (index == code->index) { + code->code = supported_codes[index]; + return 0; + } + } + + return -EINVAL; +} + +static int subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_krouting *routing) +{ + static const struct v4l2_mbus_framefmt fmt = { + .width = 4096, + .height = 3072, + .code = MEDIA_BUS_FMT_SGRBG10_1X10, + .field = V4L2_FIELD_NONE, + }; + int ret; + + ret = v4l2_subdev_routing_validate(sd, routing, + V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); + if (ret) + return ret; + + return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &fmt); +} + +int ipu7_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, + struct v4l2_mbus_framefmt *format) +{ + struct v4l2_subdev_state *state; + struct v4l2_mbus_framefmt *fmt; + + if (!sd || !format) + return -EINVAL; + + state = v4l2_subdev_lock_and_get_active_state(sd); + fmt = v4l2_subdev_state_get_format(state, pad, stream); + if (fmt) + *format = *fmt; + v4l2_subdev_unlock_state(state); + + return fmt ? 0 : -EINVAL; +} + +u32 ipu7_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) +{ + struct v4l2_subdev_state *state; + struct v4l2_subdev_route *routes; + u32 source_stream = 0; + unsigned int i; + + state = v4l2_subdev_lock_and_get_active_state(sd); + if (!state) + return 0; + + routes = state->routing.routes; + for (i = 0; i < state->routing.num_routes; i++) { + if (routes[i].source_pad == pad) { + source_stream = routes[i].source_stream; + break; + } + } + + v4l2_subdev_unlock_state(state); + + return source_stream; +} + +static int ipu7_isys_subdev_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state) +{ + struct v4l2_subdev_route route = { + .sink_pad = 0, + .sink_stream = 0, + .source_pad = 1, + .source_stream = 0, + .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, + }; + struct v4l2_subdev_krouting routing = { + .num_routes = 1, + .routes = &route, + }; + + return subdev_set_routing(sd, state, &routing); +} + +int ipu7_isys_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing) +{ + return subdev_set_routing(sd, state, routing); +} + +static const struct v4l2_subdev_internal_ops ipu7_isys_subdev_internal_ops = { + .init_state = ipu7_isys_subdev_init_state, +}; + +int ipu7_isys_subdev_init(struct ipu7_isys_subdev *asd, + const struct v4l2_subdev_ops *ops, + unsigned int nr_ctrls, + unsigned int num_sink_pads, + unsigned int num_source_pads) +{ + unsigned int num_pads = num_sink_pads + num_source_pads; + unsigned int i; + int ret; + + v4l2_subdev_init(&asd->sd, ops); + + asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | + V4L2_SUBDEV_FL_HAS_EVENTS | + V4L2_SUBDEV_FL_STREAMS; + asd->sd.owner = THIS_MODULE; + asd->sd.dev = &asd->isys->adev->auxdev.dev; + asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; + asd->sd.internal_ops = &ipu7_isys_subdev_internal_ops; + + asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, + sizeof(*asd->pad), GFP_KERNEL); + if (!asd->pad) + return -ENOMEM; + + for (i = 0; i < num_sink_pads; i++) + asd->pad[i].flags = MEDIA_PAD_FL_SINK | + MEDIA_PAD_FL_MUST_CONNECT; + + for (i = num_sink_pads; i < num_pads; i++) + asd->pad[i].flags = MEDIA_PAD_FL_SOURCE; + + ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad); + if (ret) { + pr_err("isys subdev init failed %d.\n", ret); + return ret; + } + + if (asd->ctrl_init) { + ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls); + if (ret) + goto out_media_entity_cleanup; + + asd->ctrl_init(&asd->sd); + if (asd->ctrl_handler.error) { + ret = asd->ctrl_handler.error; + goto out_v4l2_ctrl_handler_free; + } + + asd->sd.ctrl_handler = &asd->ctrl_handler; + } + + asd->source = -1; + + return 0; + +out_v4l2_ctrl_handler_free: + v4l2_ctrl_handler_free(&asd->ctrl_handler); + +out_media_entity_cleanup: + media_entity_cleanup(&asd->sd.entity); + + return ret; +} + +void ipu7_isys_subdev_cleanup(struct ipu7_isys_subdev *asd) +{ + media_entity_cleanup(&asd->sd.entity); + v4l2_ctrl_handler_free(&asd->ctrl_handler); +} diff --git a/drivers/staging/media/ipu7/ipu7-isys-subdev.h b/drivers/staging/media/ipu7/ipu7-isys-subdev.h new file mode 100644 index 000000000000..1057ec39ae39 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-subdev.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_SUBDEV_H +#define IPU7_ISYS_SUBDEV_H + +#include + +#include +#include +#include + +struct ipu7_isys; + +struct ipu7_isys_subdev { + struct v4l2_subdev sd; + struct ipu7_isys *isys; + u32 const *supported_codes; + struct media_pad *pad; + struct v4l2_ctrl_handler ctrl_handler; + void (*ctrl_init)(struct v4l2_subdev *sd); + int source; /* SSI stream source; -1 if unset */ +}; + +#define to_ipu7_isys_subdev(__sd) \ + container_of(__sd, struct ipu7_isys_subdev, sd) +unsigned int ipu7_isys_mbus_code_to_mipi(u32 code); +bool ipu7_isys_is_bayer_format(u32 code); +u32 ipu7_isys_convert_bayer_order(u32 code, int x, int y); + +int ipu7_isys_subdev_set_fmt(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_format *format); +int ipu7_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_mbus_code_enum + *code); +u32 ipu7_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); +int ipu7_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, + struct v4l2_mbus_framefmt *format); +int ipu7_isys_subdev_set_routing(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + enum v4l2_subdev_format_whence which, + struct v4l2_subdev_krouting *routing); +int ipu7_isys_subdev_init(struct ipu7_isys_subdev *asd, + const struct v4l2_subdev_ops *ops, + unsigned int nr_ctrls, + unsigned int num_sink_pads, + unsigned int num_source_pads); +void ipu7_isys_subdev_cleanup(struct ipu7_isys_subdev *asd); +#endif /* IPU7_ISYS_SUBDEV_H */ diff --git a/drivers/staging/media/ipu7/ipu7-isys-video.c b/drivers/staging/media/ipu7/ipu7-isys-video.c new file mode 100644 index 000000000000..8756da3a8fb0 --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-video.c @@ -0,0 +1,1112 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "abi/ipu7_fw_isys_abi.h" + +#include "ipu7.h" +#include "ipu7-bus.h" +#include "ipu7-buttress-regs.h" +#include "ipu7-fw-isys.h" +#include "ipu7-isys.h" +#include "ipu7-isys-video.h" +#include "ipu7-platform-regs.h" + +const struct ipu7_isys_pixelformat ipu7_isys_pfmts[] = { + {V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW16}, + {V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8, + IPU_INSYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8, + IPU_INSYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8, + IPU_INSYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, + IPU_INSYS_FRAME_FORMAT_RAW8}, + {V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12, + IPU_INSYS_FRAME_FORMAT_RAW12}, + {V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, + IPU_INSYS_FRAME_FORMAT_RAW10}, + {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, + IPU_INSYS_FRAME_FORMAT_UYVY}, + {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, + IPU_INSYS_FRAME_FORMAT_YUYV}, + {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, + IPU_INSYS_FRAME_FORMAT_RGB565}, + {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, + IPU_INSYS_FRAME_FORMAT_RGBA888}, +}; + +static int video_open(struct file *file) +{ + return v4l2_fh_open(file); +} + +const struct ipu7_isys_pixelformat *ipu7_isys_get_isys_format(u32 pixelformat) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { + const struct ipu7_isys_pixelformat *pfmt = &ipu7_isys_pfmts[i]; + + if (pfmt->pixelformat == pixelformat) + return pfmt; + } + + return &ipu7_isys_pfmts[0]; +} + +static int ipu7_isys_vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct ipu7_isys_video *av = video_drvdata(file); + + strscpy(cap->driver, IPU_ISYS_NAME, sizeof(cap->driver)); + strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card)); + + return 0; +} + +static int ipu7_isys_vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + unsigned int i, num_found; + + for (i = 0, num_found = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + continue; + + if (f->mbus_code && f->mbus_code != ipu7_isys_pfmts[i].code) + continue; + + if (num_found < f->index) { + num_found++; + continue; + } + + f->flags = 0; + f->pixelformat = ipu7_isys_pfmts[i].pixelformat; + + return 0; + } + + return -EINVAL; +} + +static int ipu7_isys_vidioc_enum_framesizes(struct file *file, void *fh, + struct v4l2_frmsizeenum *fsize) +{ + unsigned int i; + + if (fsize->index > 0) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(ipu7_isys_pfmts); i++) { + if (fsize->pixel_format != ipu7_isys_pfmts[i].pixelformat) + continue; + + fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE; + fsize->stepwise.min_width = IPU_ISYS_MIN_WIDTH; + fsize->stepwise.max_width = IPU_ISYS_MAX_WIDTH; + fsize->stepwise.min_height = IPU_ISYS_MIN_HEIGHT; + fsize->stepwise.max_height = IPU_ISYS_MAX_HEIGHT; + fsize->stepwise.step_width = 2; + fsize->stepwise.step_height = 2; + + return 0; + } + + return -EINVAL; +} + +static int ipu7_isys_vidioc_g_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu7_isys_video *av = video_drvdata(file); + + f->fmt.pix = av->pix_fmt; + + return 0; +} + +static void ipu7_isys_try_fmt_cap(struct ipu7_isys_video *av, u32 type, + u32 *format, u32 *width, u32 *height, + u32 *bytesperline, u32 *sizeimage) +{ + const struct ipu7_isys_pixelformat *pfmt = + ipu7_isys_get_isys_format(*format); + + *format = pfmt->pixelformat; + *width = clamp(*width, IPU_ISYS_MIN_WIDTH, IPU_ISYS_MAX_WIDTH); + *height = clamp(*height, IPU_ISYS_MIN_HEIGHT, IPU_ISYS_MAX_HEIGHT); + + if (pfmt->bpp != pfmt->bpp_packed) + *bytesperline = *width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); + else + *bytesperline = DIV_ROUND_UP(*width * pfmt->bpp, BITS_PER_BYTE); + + *bytesperline = ALIGN(*bytesperline, 64U); + + /* + * (height + 1) * bytesperline due to a hardware issue: the DMA unit + * is a power of two, and a line should be transferred as few units + * as possible. The result is that up to line length more data than + * the image size may be transferred to memory after the image. + * Another limitation is the GDA allocation unit size. For low + * resolution it gives a bigger number. Use larger one to avoid + * memory corruption. + */ + *sizeimage = *bytesperline * *height + + max(*bytesperline, + av->isys->pdata->ipdata->isys_dma_overshoot); +} + +static void __ipu_isys_vidioc_try_fmt_vid_cap(struct ipu7_isys_video *av, + struct v4l2_format *f) +{ + ipu7_isys_try_fmt_cap(av, f->type, &f->fmt.pix.pixelformat, + &f->fmt.pix.width, &f->fmt.pix.height, + &f->fmt.pix.bytesperline, &f->fmt.pix.sizeimage); + + f->fmt.pix.field = V4L2_FIELD_NONE; + f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW; + f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; + f->fmt.pix.quantization = V4L2_QUANTIZATION_DEFAULT; + f->fmt.pix.xfer_func = V4L2_XFER_FUNC_DEFAULT; +} + +static int ipu7_isys_vidioc_try_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu7_isys_video *av = video_drvdata(file); + + if (vb2_is_busy(&av->aq.vbq)) + return -EBUSY; + + __ipu_isys_vidioc_try_fmt_vid_cap(av, f); + + return 0; +} + +static int ipu7_isys_vidioc_s_fmt_vid_cap(struct file *file, void *fh, + struct v4l2_format *f) +{ + struct ipu7_isys_video *av = video_drvdata(file); + + ipu7_isys_vidioc_try_fmt_vid_cap(file, fh, f); + av->pix_fmt = f->fmt.pix; + + return 0; +} + +static int ipu7_isys_vidioc_reqbufs(struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct ipu7_isys_video *av = video_drvdata(file); + int ret; + + av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->type); + av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->type); + + ret = vb2_queue_change_type(&av->aq.vbq, p->type); + if (ret) + return ret; + + return vb2_ioctl_reqbufs(file, priv, p); +} + +static int ipu7_isys_vidioc_create_bufs(struct file *file, void *priv, + struct v4l2_create_buffers *p) +{ + struct ipu7_isys_video *av = video_drvdata(file); + int ret; + + av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->format.type); + av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->format.type); + + ret = vb2_queue_change_type(&av->aq.vbq, p->format.type); + if (ret) + return ret; + + return vb2_ioctl_create_bufs(file, priv, p); +} + +static int link_validate(struct media_link *link) +{ + struct ipu7_isys_video *av = + container_of(link->sink, struct ipu7_isys_video, pad); + struct device *dev = &av->isys->adev->auxdev.dev; + struct v4l2_subdev_state *s_state; + struct v4l2_mbus_framefmt *s_fmt; + struct v4l2_subdev *s_sd; + struct media_pad *s_pad; + u32 s_stream, code; + int ret = -EPIPE; + + if (!link->source->entity) + return ret; + + s_sd = media_entity_to_v4l2_subdev(link->source->entity); + s_state = v4l2_subdev_get_unlocked_active_state(s_sd); + if (!s_state) + return ret; + + dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n", + link->source->entity->name, link->source->index, + link->sink->entity->name); + + s_pad = media_pad_remote_pad_first(&av->pad); + s_stream = ipu7_isys_get_src_stream_by_src_pad(s_sd, s_pad->index); + + v4l2_subdev_lock_state(s_state); + + s_fmt = v4l2_subdev_state_get_format(s_state, s_pad->index, s_stream); + if (!s_fmt) { + dev_err(dev, "failed to get source pad format\n"); + goto unlock; + } + + code = ipu7_isys_get_isys_format(av->pix_fmt.pixelformat)->code; + + if (s_fmt->width != av->pix_fmt.width || + s_fmt->height != av->pix_fmt.height || s_fmt->code != code) { + dev_dbg(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", + s_fmt->width, s_fmt->height, s_fmt->code, + av->pix_fmt.width, av->pix_fmt.height, code); + goto unlock; + } + + v4l2_subdev_unlock_state(s_state); + + return 0; +unlock: + v4l2_subdev_unlock_state(s_state); + + return ret; +} + +static void get_stream_opened(struct ipu7_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened++; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static void put_stream_opened(struct ipu7_isys_video *av) +{ + unsigned long flags; + + spin_lock_irqsave(&av->isys->streams_lock, flags); + av->isys->stream_opened--; + spin_unlock_irqrestore(&av->isys->streams_lock, flags); +} + +static int ipu7_isys_fw_pin_cfg(struct ipu7_isys_video *av, + struct ipu7_insys_stream_cfg *cfg) +{ + struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); + struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); + struct ipu7_isys_stream *stream = av->stream; + const struct ipu7_isys_pixelformat *pfmt = + ipu7_isys_get_isys_format(av->pix_fmt.pixelformat); + struct ipu7_insys_output_pin *output_pin; + struct ipu7_insys_input_pin *input_pin; + int input_pins = cfg->nof_input_pins++; + struct ipu7_isys_queue *aq = &av->aq; + struct ipu7_isys *isys = av->isys; + struct device *dev = &isys->adev->auxdev.dev; + struct v4l2_mbus_framefmt fmt; + int output_pins; + u32 src_stream; + int ret; + + src_stream = ipu7_isys_get_src_stream_by_src_pad(sd, src_pad->index); + ret = ipu7_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream, + &fmt); + if (ret < 0) { + dev_err(dev, "can't get stream format (%d)\n", ret); + return ret; + } + + input_pin = &cfg->input_pins[input_pins]; + input_pin->input_res.width = fmt.width; + input_pin->input_res.height = fmt.height; + input_pin->dt = av->dt; + input_pin->disable_mipi_unpacking = 0; + pfmt = ipu7_isys_get_isys_format(av->pix_fmt.pixelformat); + if (pfmt->bpp == pfmt->bpp_packed && pfmt->bpp % BITS_PER_BYTE) + input_pin->disable_mipi_unpacking = 1; + input_pin->mapped_dt = N_IPU_INSYS_MIPI_DATA_TYPE; + input_pin->dt_rename_mode = IPU_INSYS_MIPI_DT_NO_RENAME; + /* if enable polling isys interrupt, the follow values maybe set */ + input_pin->sync_msg_map = IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF | + IPU_INSYS_STREAM_SYNC_MSG_SEND_RESP_SOF_DISCARDED | + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF | + IPU_INSYS_STREAM_SYNC_MSG_SEND_IRQ_SOF_DISCARDED; + + output_pins = cfg->nof_output_pins++; + aq->fw_output = output_pins; + stream->output_pins[output_pins].pin_ready = ipu7_isys_queue_buf_ready; + stream->output_pins[output_pins].aq = aq; + + output_pin = &cfg->output_pins[output_pins]; + /* output pin msg link */ + output_pin->link.buffer_lines = 0; + output_pin->link.foreign_key = IPU_MSG_LINK_FOREIGN_KEY_NONE; + output_pin->link.granularity_pointer_update = 0; + output_pin->link.msg_link_streaming_mode = + IA_GOFO_MSG_LINK_STREAMING_MODE_SOFF; + + output_pin->link.pbk_id = IPU_MSG_LINK_PBK_ID_DONT_CARE; + output_pin->link.pbk_slot_id = IPU_MSG_LINK_PBK_SLOT_ID_DONT_CARE; + output_pin->link.dest = IPU_INSYS_OUTPUT_LINK_DEST_MEM; + output_pin->link.use_sw_managed = 1; + /* TODO: set the snoop bit for metadata capture */ + output_pin->link.is_snoop = 0; + + /* output pin crop */ + output_pin->crop.line_top = 0; + output_pin->crop.line_bottom = 0; + + /* output de-compression */ + output_pin->dpcm.enable = 0; + + /* frame format type */ + pfmt = ipu7_isys_get_isys_format(av->pix_fmt.pixelformat); + output_pin->ft = (u16)pfmt->css_pixelformat; + + /* stride in bytes */ + output_pin->stride = av->pix_fmt.bytesperline; + output_pin->send_irq = 1; + output_pin->early_ack_en = 0; + + /* input pin id */ + output_pin->input_pin_id = input_pins; + + return 0; +} + +/* Create stream and start it using the CSS FW ABI. */ +static int start_stream_firmware(struct ipu7_isys_video *av, + struct ipu7_isys_buffer_list *bl) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_stream *stream = av->stream; + struct ipu7_insys_stream_cfg *stream_cfg; + struct ipu7_insys_buffset *buf = NULL; + struct isys_fw_msgs *msg = NULL; + struct ipu7_isys_queue *aq; + int ret, retout, tout; + u16 send_type; + + if (WARN_ON(!bl)) + return -EIO; + + msg = ipu7_get_fw_msg_buf(stream); + if (!msg) + return -ENOMEM; + + stream_cfg = &msg->fw_msg.stream; + stream_cfg->port_id = stream->stream_source; + stream_cfg->vc = stream->vc; + stream_cfg->stream_msg_map = IPU_INSYS_STREAM_ENABLE_MSG_SEND_RESP | + IPU_INSYS_STREAM_ENABLE_MSG_SEND_IRQ; + + list_for_each_entry(aq, &stream->queues, node) { + struct ipu7_isys_video *__av = ipu7_isys_queue_to_video(aq); + + ret = ipu7_isys_fw_pin_cfg(__av, stream_cfg); + if (ret < 0) { + ipu7_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + return ret; + } + } + + ipu7_fw_isys_dump_stream_cfg(dev, stream_cfg); + + stream->nr_output_pins = stream_cfg->nof_output_pins; + + reinit_completion(&stream->stream_open_completion); + + ret = ipu7_fw_isys_complex_cmd(av->isys, stream->stream_handle, + stream_cfg, msg->dma_addr, + sizeof(*stream_cfg), + IPU_INSYS_SEND_TYPE_STREAM_OPEN); + if (ret < 0) { + dev_err(dev, "can't open stream (%d)\n", ret); + ipu7_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + return ret; + } + + get_stream_opened(av); + + tout = wait_for_completion_timeout(&stream->stream_open_completion, + FW_CALL_TIMEOUT_JIFFIES); + + ipu7_put_fw_msg_buf(av->isys, (uintptr_t)stream_cfg); + + if (!tout) { + dev_err(dev, "stream open time out\n"); + ret = -ETIMEDOUT; + goto out_put_stream_opened; + } + if (stream->error) { + dev_err(dev, "stream open error: %d\n", stream->error); + ret = -EIO; + goto out_put_stream_opened; + } + dev_dbg(dev, "start stream: open complete\n"); + + msg = ipu7_get_fw_msg_buf(stream); + if (!msg) { + ret = -ENOMEM; + goto out_put_stream_opened; + } + buf = &msg->fw_msg.frame; + + ipu7_isys_buffer_to_fw_frame_buff(buf, stream, bl); + ipu7_isys_buffer_list_queue(bl, IPU_ISYS_BUFFER_LIST_FL_ACTIVE, 0); + + reinit_completion(&stream->stream_start_completion); + + send_type = IPU_INSYS_SEND_TYPE_STREAM_START_AND_CAPTURE; + ipu7_fw_isys_dump_frame_buff_set(dev, buf, + stream_cfg->nof_output_pins); + ret = ipu7_fw_isys_complex_cmd(av->isys, stream->stream_handle, buf, + msg->dma_addr, sizeof(*buf), + send_type); + if (ret < 0) { + dev_err(dev, "can't start streaming (%d)\n", ret); + goto out_stream_close; + } + + tout = wait_for_completion_timeout(&stream->stream_start_completion, + FW_CALL_TIMEOUT_JIFFIES); + if (!tout) { + dev_err(dev, "stream start time out\n"); + ret = -ETIMEDOUT; + goto out_stream_close; + } + if (stream->error) { + dev_err(dev, "stream start error: %d\n", stream->error); + ret = -EIO; + goto out_stream_close; + } + dev_dbg(dev, "start stream: complete\n"); + + return 0; + +out_stream_close: + reinit_completion(&stream->stream_close_completion); + + retout = ipu7_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU_INSYS_SEND_TYPE_STREAM_CLOSE); + if (retout < 0) { + dev_dbg(dev, "can't close stream (%d)\n", retout); + goto out_put_stream_opened; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_err(dev, "stream close time out with error %d\n", + stream->error); + else + dev_dbg(dev, "stream close complete\n"); + +out_put_stream_opened: + put_stream_opened(av); + + return ret; +} + +static void stop_streaming_firmware(struct ipu7_isys_video *av) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_stream *stream = av->stream; + int ret, tout; + + reinit_completion(&stream->stream_stop_completion); + + ret = ipu7_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU_INSYS_SEND_TYPE_STREAM_FLUSH); + if (ret < 0) { + dev_err(dev, "can't stop stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_stop_completion, + FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream stop time out\n"); + else if (stream->error) + dev_warn(dev, "stream stop error: %d\n", stream->error); + else + dev_dbg(dev, "stop stream: complete\n"); +} + +static void close_streaming_firmware(struct ipu7_isys_video *av) +{ + struct device *dev = &av->isys->adev->auxdev.dev; + struct ipu7_isys_stream *stream = av->stream; + int ret, tout; + + reinit_completion(&stream->stream_close_completion); + + ret = ipu7_fw_isys_simple_cmd(av->isys, stream->stream_handle, + IPU_INSYS_SEND_TYPE_STREAM_CLOSE); + if (ret < 0) { + dev_err(dev, "can't close stream (%d)\n", ret); + return; + } + + tout = wait_for_completion_timeout(&stream->stream_close_completion, + FW_CALL_TIMEOUT_JIFFIES); + if (!tout) + dev_warn(dev, "stream close time out\n"); + else if (stream->error) + dev_warn(dev, "stream close error: %d\n", stream->error); + else + dev_dbg(dev, "close stream: complete\n"); + + put_stream_opened(av); +} + +int ipu7_isys_video_prepare_stream(struct ipu7_isys_video *av, + struct media_entity *source_entity, + int nr_queues) +{ + struct ipu7_isys_stream *stream = av->stream; + struct ipu7_isys_csi2 *csi2; + + if (WARN_ON(stream->nr_streaming)) + return -EINVAL; + + stream->nr_queues = nr_queues; + atomic_set(&stream->sequence, 0); + atomic_set(&stream->buf_id, 0); + + stream->seq_index = 0; + memset(stream->seq, 0, sizeof(stream->seq)); + + if (WARN_ON(!list_empty(&stream->queues))) + return -EINVAL; + + stream->stream_source = stream->asd->source; + + csi2 = ipu7_isys_subdev_to_csi2(stream->asd); + csi2->receiver_errors = 0; + stream->source_entity = source_entity; + + dev_dbg(&av->isys->adev->auxdev.dev, + "prepare stream: external entity %s\n", + stream->source_entity->name); + + return 0; +} + +void ipu7_isys_put_stream(struct ipu7_isys_stream *stream) +{ + unsigned long flags; + struct device *dev; + unsigned int i; + + if (!stream) { + pr_err("ipu7-isys: no available stream\n"); + return; + } + + dev = &stream->isys->adev->auxdev.dev; + + spin_lock_irqsave(&stream->isys->streams_lock, flags); + for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { + if (&stream->isys->streams[i] == stream) { + if (stream->isys->streams_ref_count[i] > 0) + stream->isys->streams_ref_count[i]--; + else + dev_warn(dev, "invalid stream %d\n", i); + + break; + } + } + spin_unlock_irqrestore(&stream->isys->streams_lock, flags); +} + +static struct ipu7_isys_stream * +ipu7_isys_get_stream(struct ipu7_isys_video *av, struct ipu7_isys_subdev *asd) +{ + struct ipu7_isys_stream *stream = NULL; + struct ipu7_isys *isys = av->isys; + unsigned long flags; + unsigned int i; + u8 vc = av->vc; + + if (!isys) + return NULL; + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { + if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && + isys->streams[i].asd == asd) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + break; + } + } + + if (!stream) { + for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) { + isys->streams_ref_count[i]++; + stream = &isys->streams[i]; + stream->vc = vc; + stream->asd = asd; + break; + } + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu7_isys_stream * +ipu7_isys_query_stream_by_handle(struct ipu7_isys *isys, u8 stream_handle) +{ + unsigned long flags; + struct ipu7_isys_stream *stream = NULL; + + if (!isys) + return NULL; + + if (stream_handle >= IPU_ISYS_MAX_STREAMS) { + dev_err(&isys->adev->auxdev.dev, + "stream_handle %d is invalid\n", stream_handle); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + if (isys->streams_ref_count[stream_handle] > 0) { + isys->streams_ref_count[stream_handle]++; + stream = &isys->streams[stream_handle]; + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +struct ipu7_isys_stream * +ipu7_isys_query_stream_by_source(struct ipu7_isys *isys, int source, u8 vc) +{ + struct ipu7_isys_stream *stream = NULL; + unsigned long flags; + unsigned int i; + + if (!isys) + return NULL; + + if (source < 0) { + dev_err(&isys->adev->auxdev.dev, + "query stream with invalid port number\n"); + return NULL; + } + + spin_lock_irqsave(&isys->streams_lock, flags); + for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { + if (!isys->streams_ref_count[i]) + continue; + + if (isys->streams[i].stream_source == source && + isys->streams[i].vc == vc) { + stream = &isys->streams[i]; + isys->streams_ref_count[i]++; + break; + } + } + spin_unlock_irqrestore(&isys->streams_lock, flags); + + return stream; +} + +static u32 get_remote_pad_stream(struct media_pad *r_pad) +{ + struct v4l2_subdev_state *state; + struct v4l2_subdev *sd; + u32 stream_id = 0; + unsigned int i; + + sd = media_entity_to_v4l2_subdev(r_pad->entity); + state = v4l2_subdev_lock_and_get_active_state(sd); + if (!state) + return 0; + + for (i = 0; i < state->stream_configs.num_configs; i++) { + struct v4l2_subdev_stream_config *cfg = + &state->stream_configs.configs[i]; + if (cfg->pad == r_pad->index) { + stream_id = cfg->stream; + break; + } + } + + v4l2_subdev_unlock_state(state); + + return stream_id; +} + +int ipu7_isys_video_set_streaming(struct ipu7_isys_video *av, int state, + struct ipu7_isys_buffer_list *bl) +{ + struct ipu7_isys_stream *stream = av->stream; + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *r_pad; + struct v4l2_subdev *sd; + u32 r_stream; + int ret = 0; + + dev_dbg(dev, "set stream: %d\n", state); + + if (WARN(!stream->source_entity, "No source entity for stream\n")) + return -ENODEV; + + sd = &stream->asd->sd; + r_pad = media_pad_remote_pad_first(&av->pad); + r_stream = get_remote_pad_stream(r_pad); + if (!state) { + stop_streaming_firmware(av); + + /* stop sub-device which connects with video */ + dev_dbg(dev, "disable streams %s pad:%d mask:0x%llx\n", + sd->name, r_pad->index, BIT_ULL(r_stream)); + ret = v4l2_subdev_disable_streams(sd, r_pad->index, + BIT_ULL(r_stream)); + if (ret) { + dev_err(dev, "disable streams %s failed with %d\n", + sd->name, ret); + return ret; + } + + close_streaming_firmware(av); + } else { + ret = start_stream_firmware(av, bl); + if (ret) { + dev_err(dev, "start stream of firmware failed\n"); + return ret; + } + + /* start sub-device which connects with video */ + dev_dbg(dev, "enable streams %s pad: %d mask:0x%llx\n", + sd->name, r_pad->index, BIT_ULL(r_stream)); + ret = v4l2_subdev_enable_streams(sd, r_pad->index, + BIT_ULL(r_stream)); + if (ret) { + dev_err(dev, "enable streams %s failed with %d\n", + sd->name, ret); + goto out_media_entity_stop_streaming_firmware; + } + } + + av->streaming = state; + + return 0; + +out_media_entity_stop_streaming_firmware: + stop_streaming_firmware(av); + + return ret; +} + +static const struct v4l2_ioctl_ops ipu7_v4l2_ioctl_ops = { + .vidioc_querycap = ipu7_isys_vidioc_querycap, + .vidioc_enum_fmt_vid_cap = ipu7_isys_vidioc_enum_fmt, + .vidioc_enum_framesizes = ipu7_isys_vidioc_enum_framesizes, + .vidioc_g_fmt_vid_cap = ipu7_isys_vidioc_g_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = ipu7_isys_vidioc_s_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = ipu7_isys_vidioc_try_fmt_vid_cap, + .vidioc_reqbufs = ipu7_isys_vidioc_reqbufs, + .vidioc_create_bufs = ipu7_isys_vidioc_create_bufs, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, + .vidioc_expbuf = vb2_ioctl_expbuf, +}; + +static const struct media_entity_operations entity_ops = { + .link_validate = link_validate, +}; + +static const struct v4l2_file_operations isys_fops = { + .owner = THIS_MODULE, + .poll = vb2_fop_poll, + .unlocked_ioctl = video_ioctl2, + .mmap = vb2_fop_mmap, + .open = video_open, + .release = vb2_fop_release, +}; + +int ipu7_isys_fw_open(struct ipu7_isys *isys) +{ + struct ipu7_bus_device *adev = isys->adev; + int ret; + + ret = pm_runtime_resume_and_get(&adev->auxdev.dev); + if (ret < 0) + return ret; + + mutex_lock(&isys->mutex); + + if (isys->ref_count++) + goto unlock; + + /* + * Buffers could have been left to wrong queue at last closure. + * Move them now back to empty buffer queue. + */ + ipu7_cleanup_fw_msg_bufs(isys); + + ret = ipu7_fw_isys_open(isys); + if (ret < 0) + goto out; + +unlock: + mutex_unlock(&isys->mutex); + + return 0; +out: + isys->ref_count--; + mutex_unlock(&isys->mutex); + pm_runtime_put(&adev->auxdev.dev); + + return ret; +} + +void ipu7_isys_fw_close(struct ipu7_isys *isys) +{ + mutex_lock(&isys->mutex); + + isys->ref_count--; + + if (!isys->ref_count) + ipu7_fw_isys_close(isys); + + mutex_unlock(&isys->mutex); +} + +int ipu7_isys_setup_video(struct ipu7_isys_video *av, + struct media_entity **source_entity, int *nr_queues) +{ + const struct ipu7_isys_pixelformat *pfmt = + ipu7_isys_get_isys_format(av->pix_fmt.pixelformat); + struct device *dev = &av->isys->adev->auxdev.dev; + struct media_pad *source_pad, *remote_pad; + struct v4l2_mbus_frame_desc_entry entry; + struct v4l2_subdev_route *route = NULL; + struct v4l2_subdev_route *r; + struct v4l2_subdev_state *state; + struct ipu7_isys_subdev *asd; + struct v4l2_subdev *remote_sd; + struct media_pipeline *pipeline; + int ret = -EINVAL; + + *nr_queues = 0; + + remote_pad = media_pad_remote_pad_unique(&av->pad); + if (IS_ERR(remote_pad)) { + dev_dbg(dev, "failed to get remote pad\n"); + return PTR_ERR(remote_pad); + } + + remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); + asd = to_ipu7_isys_subdev(remote_sd); + + source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]); + if (!source_pad) { + dev_dbg(dev, "No external source entity\n"); + return -ENODEV; + } + + *source_entity = source_pad->entity; + + state = v4l2_subdev_lock_and_get_active_state(remote_sd); + for_each_active_route(&state->routing, r) { + if (r->source_pad == remote_pad->index) + route = r; + } + + if (!route) { + v4l2_subdev_unlock_state(state); + dev_dbg(dev, "Failed to find route\n"); + return -ENODEV; + } + + v4l2_subdev_unlock_state(state); + + ret = ipu7_isys_csi2_get_remote_desc(route->sink_stream, + to_ipu7_isys_csi2(asd), + *source_entity, &entry, + nr_queues); + if (ret == -ENOIOCTLCMD) { + av->vc = 0; + av->dt = ipu7_isys_mbus_code_to_mipi(pfmt->code); + if (av->dt == 0xff) + return -EINVAL; + *nr_queues = 1; + } else if (*nr_queues && !ret) { + dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", + entry.stream, entry.length, entry.bus.csi2.vc, + entry.bus.csi2.dt); + + av->vc = entry.bus.csi2.vc; + av->dt = entry.bus.csi2.dt; + } else { + dev_err(dev, "failed to get remote frame desc\n"); + return ret; + } + + pipeline = media_entity_pipeline(&av->vdev.entity); + if (!pipeline) + ret = video_device_pipeline_alloc_start(&av->vdev); + else + ret = video_device_pipeline_start(&av->vdev, pipeline); + if (ret < 0) { + dev_dbg(dev, "media pipeline start failed\n"); + return ret; + } + + av->stream = ipu7_isys_get_stream(av, asd); + if (!av->stream) { + video_device_pipeline_stop(&av->vdev); + dev_err(dev, "no available stream for firmware\n"); + return -EINVAL; + } + + return 0; +} + +/* + * Do everything that's needed to initialise things related to video + * buffer queue, video node, and the related media entity. The caller + * is expected to assign isys field and set the name of the video + * device. + */ +int ipu7_isys_video_init(struct ipu7_isys_video *av) +{ + struct v4l2_format format = { + .type = V4L2_BUF_TYPE_VIDEO_CAPTURE, + .fmt.pix = { + .width = 1920, + .height = 1080, + }, + }; + int ret; + + mutex_init(&av->mutex); + av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | + V4L2_CAP_VIDEO_CAPTURE; + av->vdev.vfl_dir = VFL_DIR_RX; + + ret = ipu7_isys_queue_init(&av->aq); + if (ret) + goto out_mutex_destroy; + + av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; + ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); + if (ret) + goto out_vb2_queue_cleanup; + + av->vdev.entity.ops = &entity_ops; + av->vdev.release = video_device_release_empty; + av->vdev.fops = &isys_fops; + av->vdev.v4l2_dev = &av->isys->v4l2_dev; + av->vdev.dev_parent = &av->isys->adev->isp->pdev->dev; + av->vdev.ioctl_ops = &ipu7_v4l2_ioctl_ops; + av->vdev.queue = &av->aq.vbq; + av->vdev.lock = &av->mutex; + + __ipu_isys_vidioc_try_fmt_vid_cap(av, &format); + av->pix_fmt = format.fmt.pix; + + set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); + video_set_drvdata(&av->vdev, av); + + ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1); + if (ret) + goto out_media_entity_cleanup; + + return ret; + +out_media_entity_cleanup: + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + +out_vb2_queue_cleanup: + vb2_queue_release(&av->aq.vbq); + +out_mutex_destroy: + mutex_destroy(&av->mutex); + + return ret; +} + +void ipu7_isys_video_cleanup(struct ipu7_isys_video *av) +{ + vb2_video_unregister_device(&av->vdev); + media_entity_cleanup(&av->vdev.entity); + mutex_destroy(&av->mutex); +} diff --git a/drivers/staging/media/ipu7/ipu7-isys-video.h b/drivers/staging/media/ipu7/ipu7-isys-video.h new file mode 100644 index 000000000000..1ac1787fabef --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys-video.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_VIDEO_H +#define IPU7_ISYS_VIDEO_H + +#include +#include +#include +#include +#include + +#include +#include + +#include "ipu7-isys-queue.h" + +#define IPU_INSYS_OUTPUT_PINS 11U +#define IPU_ISYS_MAX_PARALLEL_SOF 2U + +struct file; +struct ipu7_isys; +struct ipu7_isys_csi2; +struct ipu7_insys_stream_cfg; +struct ipu7_isys_subdev; + +struct ipu7_isys_pixelformat { + u32 pixelformat; + u32 bpp; + u32 bpp_packed; + u32 code; + u32 css_pixelformat; +}; + +struct sequence_info { + unsigned int sequence; + u64 timestamp; +}; + +struct output_pin_data { + void (*pin_ready)(struct ipu7_isys_stream *stream, + struct ipu7_insys_resp *info); + struct ipu7_isys_queue *aq; +}; + +/* + * Align with firmware stream. Each stream represents a CSI virtual channel. + * May map to multiple video devices + */ +struct ipu7_isys_stream { + struct mutex mutex; + struct media_entity *source_entity; + atomic_t sequence; + atomic_t buf_id; + unsigned int seq_index; + struct sequence_info seq[IPU_ISYS_MAX_PARALLEL_SOF]; + int stream_source; + int stream_handle; + unsigned int nr_output_pins; + struct ipu7_isys_subdev *asd; + + int nr_queues; /* Number of capture queues */ + int nr_streaming; + int streaming; + struct list_head queues; + struct completion stream_open_completion; + struct completion stream_close_completion; + struct completion stream_start_completion; + struct completion stream_stop_completion; + struct ipu7_isys *isys; + + struct output_pin_data output_pins[IPU_INSYS_OUTPUT_PINS]; + int error; + u8 vc; +}; + +struct ipu7_isys_video { + struct ipu7_isys_queue aq; + /* Serialise access to other fields in the struct. */ + struct mutex mutex; + struct media_pad pad; + struct video_device vdev; + struct v4l2_pix_format pix_fmt; + struct ipu7_isys *isys; + struct ipu7_isys_csi2 *csi2; + struct ipu7_isys_stream *stream; + unsigned int streaming; + u8 vc; + u8 dt; +}; + +#define ipu7_isys_queue_to_video(__aq) \ + container_of(__aq, struct ipu7_isys_video, aq) + +extern const struct ipu7_isys_pixelformat ipu7_isys_pfmts[]; + +const struct ipu7_isys_pixelformat *ipu7_isys_get_isys_format(u32 pixelformat); +int ipu7_isys_video_prepare_stream(struct ipu7_isys_video *av, + struct media_entity *source_entity, + int nr_queues); +int ipu7_isys_video_set_streaming(struct ipu7_isys_video *av, int state, + struct ipu7_isys_buffer_list *bl); +int ipu7_isys_fw_open(struct ipu7_isys *isys); +void ipu7_isys_fw_close(struct ipu7_isys *isys); +int ipu7_isys_setup_video(struct ipu7_isys_video *av, + struct media_entity **source_entity, int *nr_queues); +int ipu7_isys_video_init(struct ipu7_isys_video *av); +void ipu7_isys_video_cleanup(struct ipu7_isys_video *av); +void ipu7_isys_put_stream(struct ipu7_isys_stream *stream); +struct ipu7_isys_stream * +ipu7_isys_query_stream_by_handle(struct ipu7_isys *isys, + u8 stream_handle); +struct ipu7_isys_stream * +ipu7_isys_query_stream_by_source(struct ipu7_isys *isys, int source, u8 vc); +#endif /* IPU7_ISYS_VIDEO_H */ diff --git a/drivers/staging/media/ipu7/ipu7-isys.c b/drivers/staging/media/ipu7/ipu7-isys.c new file mode 100644 index 000000000000..cb2f49f3e0fa --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys.c @@ -0,0 +1,1166 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "abi/ipu7_fw_isys_abi.h" + +#include "ipu7-bus.h" +#include "ipu7-buttress-regs.h" +#include "ipu7-cpd.h" +#include "ipu7-dma.h" +#include "ipu7-fw-isys.h" +#include "ipu7-mmu.h" +#include "ipu7-isys.h" +#include "ipu7-isys-csi2.h" +#include "ipu7-isys-csi-phy.h" +#include "ipu7-isys-csi2-regs.h" +#include "ipu7-isys-video.h" +#include "ipu7-platform-regs.h" + +#define ISYS_PM_QOS_VALUE 300 + +static int +isys_complete_ext_device_registration(struct ipu7_isys *isys, + struct v4l2_subdev *sd, + struct ipu7_isys_csi2_config *csi2) +{ + struct device *dev = &isys->adev->auxdev.dev; + unsigned int i; + int ret; + + v4l2_set_subdev_hostdata(sd, csi2); + + for (i = 0; i < sd->entity.num_pads; i++) { + if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) + break; + } + + if (i == sd->entity.num_pads) { + dev_warn(dev, "no source pad in external entity\n"); + ret = -ENOENT; + goto skip_unregister_subdev; + } + + ret = media_create_pad_link(&sd->entity, i, + &isys->csi2[csi2->port].asd.sd.entity, + 0, MEDIA_LNK_FL_ENABLED | + MEDIA_LNK_FL_IMMUTABLE); + if (ret) { + dev_warn(dev, "can't create link\n"); + goto skip_unregister_subdev; + } + + isys->csi2[csi2->port].nlanes = csi2->nlanes; + if (csi2->bus_type == V4L2_MBUS_CSI2_DPHY) + isys->csi2[csi2->port].phy_mode = PHY_MODE_DPHY; + else + isys->csi2[csi2->port].phy_mode = PHY_MODE_CPHY; + + return 0; + +skip_unregister_subdev: + v4l2_device_unregister_subdev(sd); + return ret; +} + +static void isys_stream_init(struct ipu7_isys *isys) +{ + unsigned int i; + + for (i = 0; i < IPU_ISYS_MAX_STREAMS; i++) { + mutex_init(&isys->streams[i].mutex); + init_completion(&isys->streams[i].stream_open_completion); + init_completion(&isys->streams[i].stream_close_completion); + init_completion(&isys->streams[i].stream_start_completion); + init_completion(&isys->streams[i].stream_stop_completion); + INIT_LIST_HEAD(&isys->streams[i].queues); + isys->streams[i].isys = isys; + isys->streams[i].stream_handle = i; + isys->streams[i].vc = INVALID_VC_ID; + } +} + +static int isys_fw_log_init(struct ipu7_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct isys_fw_log *fw_log; + void *log_buf; + + if (isys->fw_log) + return 0; + + fw_log = devm_kzalloc(dev, sizeof(*fw_log), GFP_KERNEL); + if (!fw_log) + return -ENOMEM; + + mutex_init(&fw_log->mutex); + + log_buf = devm_kzalloc(dev, FW_LOG_BUF_SIZE, GFP_KERNEL); + if (!log_buf) + return -ENOMEM; + + fw_log->head = log_buf; + fw_log->addr = log_buf; + fw_log->count = 0; + fw_log->size = 0; + + isys->fw_log = fw_log; + + return 0; +} + +/* The .bound() notifier callback when a match is found */ +static int isys_notifier_bound(struct v4l2_async_notifier *notifier, + struct v4l2_subdev *sd, + struct v4l2_async_connection *asc) +{ + struct ipu7_isys *isys = container_of(notifier, + struct ipu7_isys, notifier); + struct sensor_async_sd *s_asd = + container_of(asc, struct sensor_async_sd, asc); + struct device *dev = &isys->adev->auxdev.dev; + int ret; + + ret = ipu_bridge_instantiate_vcm(sd->dev); + if (ret) { + dev_err(dev, "instantiate vcm failed\n"); + return ret; + } + + dev_info(dev, "bind %s nlanes is %d port is %d\n", + sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); + isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); + + return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); +} + +static int isys_notifier_complete(struct v4l2_async_notifier *notifier) +{ + struct ipu7_isys *isys = container_of(notifier, + struct ipu7_isys, notifier); + + dev_info(&isys->adev->auxdev.dev, + "All sensor registration completed.\n"); + + return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); +} + +static const struct v4l2_async_notifier_operations isys_async_ops = { + .bound = isys_notifier_bound, + .complete = isys_notifier_complete, +}; + +static int isys_notifier_init(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2 = + &isys->pdata->ipdata->csi2; + struct ipu7_device *isp = isys->adev->isp; + struct device *dev = &isp->pdev->dev; + unsigned int i; + int ret; + + v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev); + + for (i = 0; i < csi2->nports; i++) { + struct v4l2_fwnode_endpoint vep = { + .bus_type = V4L2_MBUS_UNKNOWN + }; + struct sensor_async_sd *s_asd; + struct fwnode_handle *ep; + + ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, + FWNODE_GRAPH_ENDPOINT_NEXT); + if (!ep) + continue; + + ret = v4l2_fwnode_endpoint_parse(ep, &vep); + if (ret) + goto err_parse; + + if (vep.bus_type != V4L2_MBUS_CSI2_DPHY && + vep.bus_type != V4L2_MBUS_CSI2_CPHY) { + ret = -EINVAL; + dev_err(dev, "unsupported bus type %d!\n", + vep.bus_type); + goto err_parse; + } + + s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep, + struct + sensor_async_sd); + if (IS_ERR(s_asd)) { + ret = PTR_ERR(s_asd); + goto err_parse; + } + + s_asd->csi2.port = vep.base.port; + s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; + s_asd->csi2.bus_type = vep.bus_type; + + fwnode_handle_put(ep); + + continue; + +err_parse: + fwnode_handle_put(ep); + return ret; + } + + if (list_empty(&isys->notifier.waiting_list)) { + /* isys probe could continue with async subdevs missing */ + dev_warn(dev, "no subdev found in graph\n"); + return 0; + } + + isys->notifier.ops = &isys_async_ops; + ret = v4l2_async_nf_register(&isys->notifier); + if (ret) { + dev_err(dev, "failed to register async notifier(%d)\n", ret); + v4l2_async_nf_cleanup(&isys->notifier); + } + + return ret; +} + +static void isys_notifier_cleanup(struct ipu7_isys *isys) +{ + v4l2_async_nf_unregister(&isys->notifier); + v4l2_async_nf_cleanup(&isys->notifier); +} + +static void isys_unregister_video_devices(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i, j; + + for (i = 0; i < csi2_pdata->nports; i++) + for (j = 0; j < IPU7_NR_OF_CSI2_SRC_PADS; j++) + ipu7_isys_video_cleanup(&isys->csi2[i].av[j]); +} + +static int isys_register_video_devices(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i, j; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + for (j = 0; j < IPU7_NR_OF_CSI2_SRC_PADS; j++) { + struct ipu7_isys_video *av = &isys->csi2[i].av[j]; + + snprintf(av->vdev.name, sizeof(av->vdev.name), + IPU_ISYS_ENTITY_PREFIX " ISYS Capture %u", + i * IPU7_NR_OF_CSI2_SRC_PADS + j); + av->isys = isys; + av->aq.vbq.buf_struct_size = + sizeof(struct ipu7_isys_video_buffer); + + ret = ipu7_isys_video_init(av); + if (ret) + goto fail; + } + } + + return 0; + +fail: + i = i + 1U; + while (i--) { + while (j--) + ipu7_isys_video_cleanup(&isys->csi2[i].av[j]); + j = IPU7_NR_OF_CSI2_SRC_PADS; + } + + return ret; +} + +static void isys_csi2_unregister_subdevices(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2 = + &isys->pdata->ipdata->csi2; + unsigned int i; + + for (i = 0; i < csi2->nports; i++) + ipu7_isys_csi2_cleanup(&isys->csi2[i]); +} + +static int isys_csi2_register_subdevices(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + unsigned int i; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + ret = ipu7_isys_csi2_init(&isys->csi2[i], isys, + isys->pdata->base + + csi2_pdata->offsets[i], i); + if (ret) + goto fail; + } + + isys->isr_csi2_mask = IPU7_CSI_RX_LEGACY_IRQ_MASK; + + return 0; + +fail: + while (i--) + ipu7_isys_csi2_cleanup(&isys->csi2[i]); + + return ret; +} + +static int isys_csi2_create_media_links(struct ipu7_isys *isys) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2_pdata = + &isys->pdata->ipdata->csi2; + struct device *dev = &isys->adev->auxdev.dev; + struct media_entity *sd; + unsigned int i, j; + int ret; + + for (i = 0; i < csi2_pdata->nports; i++) { + sd = &isys->csi2[i].asd.sd.entity; + + for (j = 0; j < IPU7_NR_OF_CSI2_SRC_PADS; j++) { + struct ipu7_isys_video *av = &isys->csi2[i].av[j]; + + ret = media_create_pad_link(sd, IPU7_CSI2_PAD_SRC + j, + &av->vdev.entity, 0, 0); + if (ret) { + dev_err(dev, "CSI2 can't create link\n"); + return ret; + } + + av->csi2 = &isys->csi2[i]; + } + } + + return 0; +} + +static int isys_register_devices(struct ipu7_isys *isys) +{ + struct device *dev = &isys->adev->auxdev.dev; + struct pci_dev *pdev = isys->adev->isp->pdev; + int ret; + + media_device_pci_init(&isys->media_dev, + pdev, IPU_MEDIA_DEV_MODEL_NAME); + + strscpy(isys->v4l2_dev.name, isys->media_dev.model, + sizeof(isys->v4l2_dev.name)); + + ret = media_device_register(&isys->media_dev); + if (ret < 0) + goto out_media_device_unregister; + + isys->v4l2_dev.mdev = &isys->media_dev; + isys->v4l2_dev.ctrl_handler = NULL; + + ret = v4l2_device_register(dev, &isys->v4l2_dev); + if (ret < 0) + goto out_media_device_unregister; + + ret = isys_register_video_devices(isys); + if (ret) + goto out_v4l2_device_unregister; + + ret = isys_csi2_register_subdevices(isys); + if (ret) + goto out_video_unregister_device; + + ret = isys_csi2_create_media_links(isys); + if (ret) + goto out_csi2_unregister_subdevices; + + ret = isys_notifier_init(isys); + if (ret) + goto out_csi2_unregister_subdevices; + + return 0; + +out_csi2_unregister_subdevices: + isys_csi2_unregister_subdevices(isys); + +out_video_unregister_device: + isys_unregister_video_devices(isys); + +out_v4l2_device_unregister: + v4l2_device_unregister(&isys->v4l2_dev); + +out_media_device_unregister: + media_device_unregister(&isys->media_dev); + media_device_cleanup(&isys->media_dev); + + dev_err(dev, "failed to register isys devices\n"); + + return ret; +} + +static void isys_unregister_devices(struct ipu7_isys *isys) +{ + isys_unregister_video_devices(isys); + isys_csi2_unregister_subdevices(isys); + v4l2_device_unregister(&isys->v4l2_dev); + media_device_unregister(&isys->media_dev); + media_device_cleanup(&isys->media_dev); +} + +static void enable_csi2_legacy_irq(struct ipu7_isys *isys, bool enable) +{ + u32 offset = IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + void __iomem *base = isys->pdata->base; + u32 mask = isys->isr_csi2_mask; + + if (!enable) { + writel(mask, base + offset + IRQ_CTL_CLEAR); + writel(0, base + offset + IRQ_CTL_ENABLE); + return; + } + + writel(mask, base + offset + IRQ_CTL_EDGE); + writel(mask, base + offset + IRQ_CTL_CLEAR); + writel(mask, base + offset + IRQ_CTL_MASK); + writel(mask, base + offset + IRQ_CTL_ENABLE); +} + +static void enable_to_sw_irq(struct ipu7_isys *isys, bool enable) +{ + void __iomem *base = isys->pdata->base; + u32 mask = IS_UC_TO_SW_IRQ_MASK; + u32 offset = IS_UC_CTRL_BASE; + + if (!enable) { + writel(0, base + offset + TO_SW_IRQ_CNTL_ENABLE); + return; + } + + writel(mask, base + offset + TO_SW_IRQ_CNTL_CLEAR); + writel(mask, base + offset + TO_SW_IRQ_CNTL_MASK_N); + writel(mask, base + offset + TO_SW_IRQ_CNTL_ENABLE); +} + +void ipu7_isys_setup_hw(struct ipu7_isys *isys) +{ + u32 offset; + void __iomem *base = isys->pdata->base; + + /* soft reset */ + offset = IS_IO_GPREGS_BASE; + + writel(0x0, base + offset + CLK_EN_TXCLKESC); + /* Update if ISYS freq updated (0: 400/1, 1:400/2, 63:400/64) */ + writel(0x0, base + offset + CLK_DIV_FACTOR_IS_CLK); + /* correct the initial printf configuration */ + writel(0x200, base + IS_UC_CTRL_BASE + PRINTF_AXI_CNTL); + + enable_to_sw_irq(isys, 1); + enable_csi2_legacy_irq(isys, 1); +} + +static void isys_cleanup_hw(struct ipu7_isys *isys) +{ + enable_csi2_legacy_irq(isys, 0); + enable_to_sw_irq(isys, 0); +} + +static int isys_runtime_pm_resume(struct device *dev) +{ + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + struct ipu7_isys *isys = ipu7_bus_get_drvdata(adev); + struct ipu7_device *isp = adev->isp; + unsigned long flags; + int ret; + + if (!isys) + return 0; + + ret = ipu7_mmu_hw_init(adev->mmu); + if (ret) + return ret; + + cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); + + ret = ipu_buttress_start_tsc_sync(isp); + if (ret) + return ret; + + spin_lock_irqsave(&isys->power_lock, flags); + isys->power = 1; + spin_unlock_irqrestore(&isys->power_lock, flags); + + return 0; +} + +static int isys_runtime_pm_suspend(struct device *dev) +{ + struct ipu7_bus_device *adev = to_ipu7_bus_device(dev); + struct ipu7_isys *isys = ipu7_bus_get_drvdata(adev); + unsigned long flags; + + if (!isys) + return 0; + + isys_cleanup_hw(isys); + + spin_lock_irqsave(&isys->power_lock, flags); + isys->power = 0; + spin_unlock_irqrestore(&isys->power_lock, flags); + + cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); + + ipu7_mmu_hw_cleanup(adev->mmu); + + return 0; +} + +static int isys_suspend(struct device *dev) +{ + struct ipu7_isys *isys = dev_get_drvdata(dev); + + /* If stream is open, refuse to suspend */ + if (isys->stream_opened) + return -EBUSY; + + return 0; +} + +static int isys_resume(struct device *dev) +{ + return 0; +} + +static const struct dev_pm_ops isys_pm_ops = { + .runtime_suspend = isys_runtime_pm_suspend, + .runtime_resume = isys_runtime_pm_resume, + .suspend = isys_suspend, + .resume = isys_resume, +}; + +static void isys_remove(struct auxiliary_device *auxdev) +{ + struct ipu7_isys *isys = dev_get_drvdata(&auxdev->dev); + struct isys_fw_msgs *fwmsg, *safe; + struct ipu7_bus_device *adev = auxdev_to_adev(auxdev); + + for (int i = 0; i < IPU_ISYS_MAX_STREAMS; i++) + mutex_destroy(&isys->streams[i].mutex); + + list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head) + ipu7_dma_free(adev, sizeof(struct isys_fw_msgs), + fwmsg, fwmsg->dma_addr, 0); + + list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head) + ipu7_dma_free(adev, sizeof(struct isys_fw_msgs), + fwmsg, fwmsg->dma_addr, 0); + + isys_notifier_cleanup(isys); + isys_unregister_devices(isys); + + cpu_latency_qos_remove_request(&isys->pm_qos); + + mutex_destroy(&isys->stream_mutex); + mutex_destroy(&isys->mutex); +} + +static int alloc_fw_msg_bufs(struct ipu7_isys *isys, int amount) +{ + struct ipu7_bus_device *adev = isys->adev; + struct isys_fw_msgs *addr; + dma_addr_t dma_addr; + unsigned long flags; + unsigned int i; + + for (i = 0; i < amount; i++) { + addr = ipu7_dma_alloc(adev, sizeof(struct isys_fw_msgs), + &dma_addr, GFP_KERNEL, 0); + if (!addr) + break; + addr->dma_addr = dma_addr; + + spin_lock_irqsave(&isys->listlock, flags); + list_add(&addr->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); + } + + if (i == amount) + return 0; + + spin_lock_irqsave(&isys->listlock, flags); + while (!list_empty(&isys->framebuflist)) { + addr = list_first_entry(&isys->framebuflist, + struct isys_fw_msgs, head); + list_del(&addr->head); + spin_unlock_irqrestore(&isys->listlock, flags); + ipu7_dma_free(adev, sizeof(struct isys_fw_msgs), + addr, addr->dma_addr, 0); + spin_lock_irqsave(&isys->listlock, flags); + } + spin_unlock_irqrestore(&isys->listlock, flags); + + return -ENOMEM; +} + +struct isys_fw_msgs *ipu7_get_fw_msg_buf(struct ipu7_isys_stream *stream) +{ + struct device *dev = &stream->isys->adev->auxdev.dev; + struct ipu7_isys *isys = stream->isys; + struct isys_fw_msgs *msg; + unsigned long flags; + int ret; + + spin_lock_irqsave(&isys->listlock, flags); + if (list_empty(&isys->framebuflist)) { + spin_unlock_irqrestore(&isys->listlock, flags); + dev_dbg(dev, "Frame buffer list empty\n"); + + ret = alloc_fw_msg_bufs(isys, 5); + if (ret < 0) + return NULL; + + spin_lock_irqsave(&isys->listlock, flags); + if (list_empty(&isys->framebuflist)) { + spin_unlock_irqrestore(&isys->listlock, flags); + dev_err(dev, "Frame list empty\n"); + return NULL; + } + } + msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); + list_move(&msg->head, &isys->framebuflist_fw); + spin_unlock_irqrestore(&isys->listlock, flags); + memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); + + return msg; +} + +void ipu7_cleanup_fw_msg_bufs(struct ipu7_isys *isys) +{ + struct isys_fw_msgs *fwmsg, *fwmsg0; + unsigned long flags; + + spin_lock_irqsave(&isys->listlock, flags); + list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head) + list_move(&fwmsg->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); +} + +void ipu7_put_fw_msg_buf(struct ipu7_isys *isys, uintptr_t data) +{ + struct isys_fw_msgs *msg; + void *ptr = (void *)data; + unsigned long flags; + + if (WARN_ON_ONCE(!ptr)) + return; + + spin_lock_irqsave(&isys->listlock, flags); + msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); + list_move(&msg->head, &isys->framebuflist); + spin_unlock_irqrestore(&isys->listlock, flags); +} + +static int isys_probe(struct auxiliary_device *auxdev, + const struct auxiliary_device_id *auxdev_id) +{ + const struct ipu7_isys_internal_csi2_pdata *csi2_pdata; + struct ipu7_bus_device *adev = auxdev_to_adev(auxdev); + struct ipu7_device *isp = adev->isp; + struct ipu7_isys *isys; + int ret = 0; + + if (!isp->ipu7_bus_ready_to_probe) + return -EPROBE_DEFER; + + isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL); + if (!isys) + return -ENOMEM; + + ret = pm_runtime_resume_and_get(&auxdev->dev); + if (ret < 0) + return ret; + + adev->auxdrv_data = + (const struct ipu7_auxdrv_data *)auxdev_id->driver_data; + adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); + isys->adev = adev; + isys->pdata = adev->pdata; + + INIT_LIST_HEAD(&isys->requests); + csi2_pdata = &isys->pdata->ipdata->csi2; + + isys->csi2 = devm_kcalloc(&auxdev->dev, csi2_pdata->nports, + sizeof(*isys->csi2), GFP_KERNEL); + if (!isys->csi2) { + ret = -ENOMEM; + goto out_runtime_put; + } + + ret = ipu7_mmu_hw_init(adev->mmu); + if (ret) + goto out_runtime_put; + + spin_lock_init(&isys->streams_lock); + spin_lock_init(&isys->power_lock); + isys->power = 0; + + mutex_init(&isys->mutex); + mutex_init(&isys->stream_mutex); + + spin_lock_init(&isys->listlock); + INIT_LIST_HEAD(&isys->framebuflist); + INIT_LIST_HEAD(&isys->framebuflist_fw); + + dev_set_drvdata(&auxdev->dev, isys); + + isys->icache_prefetch = 0; + isys->phy_rext_cal = 0; + + isys_stream_init(isys); + + cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); + ret = alloc_fw_msg_bufs(isys, 20); + if (ret < 0) + goto out_cleanup_isys; + + ret = ipu7_fw_isys_init(isys); + if (ret) + goto out_cleanup_isys; + + ret = isys_register_devices(isys); + if (ret) + goto out_cleanup_fw; + + ret = isys_fw_log_init(isys); + if (ret) + goto out_cleanup; + + ipu7_mmu_hw_cleanup(adev->mmu); + pm_runtime_put(&auxdev->dev); + + return 0; + +out_cleanup: + isys_unregister_devices(isys); +out_cleanup_fw: + ipu7_fw_isys_release(isys); +out_cleanup_isys: + cpu_latency_qos_remove_request(&isys->pm_qos); + + for (unsigned int i = 0; i < IPU_ISYS_MAX_STREAMS; i++) + mutex_destroy(&isys->streams[i].mutex); + + mutex_destroy(&isys->mutex); + mutex_destroy(&isys->stream_mutex); + + ipu7_mmu_hw_cleanup(adev->mmu); + +out_runtime_put: + pm_runtime_put(&auxdev->dev); + + return ret; +} + +struct ipu7_csi2_error { + const char *error_string; + bool is_info_only; +}; + +/* + * Strings corresponding to CSI-2 receiver errors are here. + * Corresponding macros are defined in the header file. + */ +static const struct ipu7_csi2_error dphy_rx_errors[] = { + { "Error handler FIFO full", false }, + { "Reserved Short Packet encoding detected", true }, + { "Reserved Long Packet encoding detected", true }, + { "Received packet is too short", false}, + { "Received packet is too long", false}, + { "Short packet discarded due to errors", false }, + { "Long packet discarded due to errors", false }, + { "CSI Combo Rx interrupt", false }, + { "IDI CDC FIFO overflow(remaining bits are reserved as 0)", false }, + { "Received NULL packet", true }, + { "Received blanking packet", true }, + { "Tie to 0", true }, + { } +}; + +static void ipu7_isys_register_errors(struct ipu7_isys_csi2 *csi2) +{ + u32 offset = IS_IO_CSI2_ERR_LEGACY_IRQ_CTL_BASE(csi2->port); + u32 status = readl(csi2->base + offset + IRQ_CTL_STATUS); + u32 mask = IPU7_CSI_RX_ERROR_IRQ_MASK; + + if (!status) + return; + + dev_dbg(&csi2->isys->adev->auxdev.dev, "csi2-%u error status 0x%08x\n", + csi2->port, status); + + writel(status & mask, csi2->base + offset + IRQ_CTL_CLEAR); + csi2->receiver_errors |= status & mask; +} + +static void ipu7_isys_csi2_error(struct ipu7_isys_csi2 *csi2) +{ + struct ipu7_csi2_error const *errors; + unsigned int i; + u32 status; + + /* Register errors once more in case of error interrupts are disabled */ + ipu7_isys_register_errors(csi2); + status = csi2->receiver_errors; + csi2->receiver_errors = 0; + errors = dphy_rx_errors; + + for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) { + if (status & BIT(i)) + dev_err_ratelimited(&csi2->isys->adev->auxdev.dev, + "csi2-%i error: %s\n", + csi2->port, + errors[i].error_string); + } +} + +struct resp_to_msg { + enum ipu7_insys_resp_type type; + const char *msg; +}; + +static const struct resp_to_msg is_fw_msg[] = { + {IPU_INSYS_RESP_TYPE_STREAM_OPEN_DONE, + "IPU_INSYS_RESP_TYPE_STREAM_OPEN_DONE"}, + {IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, + "IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK"}, + {IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK, + "IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK"}, + {IPU_INSYS_RESP_TYPE_STREAM_ABORT_ACK, + "IPU_INSYS_RESP_TYPE_STREAM_ABORT_ACK"}, + {IPU_INSYS_RESP_TYPE_STREAM_FLUSH_ACK, + "IPU_INSYS_RESP_TYPE_STREAM_FLUSH_ACK"}, + {IPU_INSYS_RESP_TYPE_STREAM_CLOSE_ACK, + "IPU_INSYS_RESP_TYPE_STREAM_CLOSE_ACK"}, + {IPU_INSYS_RESP_TYPE_PIN_DATA_READY, + "IPU_INSYS_RESP_TYPE_PIN_DATA_READY"}, + {IPU_INSYS_RESP_TYPE_FRAME_SOF, "IPU_INSYS_RESP_TYPE_FRAME_SOF"}, + {IPU_INSYS_RESP_TYPE_FRAME_EOF, "IPU_INSYS_RESP_TYPE_FRAME_EOF"}, + {IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, + "IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE"}, + {IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE, + "IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE"}, + {N_IPU_INSYS_RESP_TYPE, "N_IPU_INSYS_RESP_TYPE"}, +}; + +int isys_isr_one(struct ipu7_bus_device *adev) +{ + struct ipu7_isys *isys = ipu7_bus_get_drvdata(adev); + struct ipu7_isys_stream *stream = NULL; + struct device *dev = &adev->auxdev.dev; + struct ipu7_isys_csi2 *csi2 = NULL; + struct ia_gofo_msg_err err_info; + struct ipu7_insys_resp *resp; + u64 ts; + + if (!isys->adev->syscom) + return 1; + + resp = ipu7_fw_isys_get_resp(isys); + if (!resp) + return 1; + if (resp->type >= N_IPU_INSYS_RESP_TYPE) { + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + ipu7_fw_isys_put_resp(isys); + return 1; + } + + err_info = resp->error_info; + ts = ((u64)resp->timestamp[1] << 32) | resp->timestamp[0]; + if (err_info.err_group == INSYS_MSG_ERR_GROUP_CAPTURE && + err_info.err_code == INSYS_MSG_ERR_CAPTURE_SYNC_FRAME_DROP) { + /* receive a sp w/o command, firmware drop it */ + dev_dbg(dev, "FRAME DROP: %02u %s stream %u\n", + resp->type, is_fw_msg[resp->type].msg, + resp->stream_id); + dev_dbg(dev, "\tpin %u buf_id %llx frame %u\n", + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else if (!IA_GOFO_MSG_ERR_IS_OK(err_info)) { + dev_err(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_err(dev, "\terror group %u code %u details [%u %u]\n", + err_info.err_group, err_info.err_code, + err_info.err_detail[0], err_info.err_detail[1]); + } else { + dev_dbg(dev, "%02u %s stream %u pin %u buf_id %llx frame %u\n", + resp->type, is_fw_msg[resp->type].msg, resp->stream_id, + resp->pin_id, resp->buf_id, resp->frame_id); + dev_dbg(dev, "\tts %llu\n", ts); + } + + if (resp->stream_id >= IPU_ISYS_MAX_STREAMS) { + dev_err(dev, "bad stream handle %u\n", + resp->stream_id); + goto leave; + } + + stream = ipu7_isys_query_stream_by_handle(isys, resp->stream_id); + if (!stream) { + dev_err(dev, "stream of stream_handle %u is unused\n", + resp->stream_id); + goto leave; + } + + stream->error = err_info.err_code; + + if (stream->asd) + csi2 = ipu7_isys_subdev_to_csi2(stream->asd); + + switch (resp->type) { + case IPU_INSYS_RESP_TYPE_STREAM_OPEN_DONE: + complete(&stream->stream_open_completion); + break; + case IPU_INSYS_RESP_TYPE_STREAM_CLOSE_ACK: + complete(&stream->stream_close_completion); + break; + case IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: + complete(&stream->stream_start_completion); + break; + case IPU_INSYS_RESP_TYPE_STREAM_ABORT_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU_INSYS_RESP_TYPE_STREAM_FLUSH_ACK: + complete(&stream->stream_stop_completion); + break; + case IPU_INSYS_RESP_TYPE_PIN_DATA_READY: + /* + * firmware only release the capture msg until software + * get pin_data_ready event + */ + ipu7_put_fw_msg_buf(ipu7_bus_get_drvdata(adev), resp->buf_id); + if (resp->pin_id < IPU_INSYS_OUTPUT_PINS && + stream->output_pins[resp->pin_id].pin_ready) + stream->output_pins[resp->pin_id].pin_ready(stream, + resp); + else + dev_err(dev, "No handler for pin %u ready\n", + resp->pin_id); + if (csi2) + ipu7_isys_csi2_error(csi2); + + break; + case IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_ACK: + break; + case IPU_INSYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: + case IPU_INSYS_RESP_TYPE_STREAM_CAPTURE_DONE: + break; + case IPU_INSYS_RESP_TYPE_FRAME_SOF: + if (csi2) + ipu7_isys_csi2_sof_event_by_stream(stream); + + stream->seq[stream->seq_index].sequence = + atomic_read(&stream->sequence) - 1U; + stream->seq[stream->seq_index].timestamp = ts; + dev_dbg(dev, + "SOF: stream %u frame %u (index %u), ts 0x%16.16llx\n", + resp->stream_id, resp->frame_id, + stream->seq[stream->seq_index].sequence, ts); + stream->seq_index = (stream->seq_index + 1U) + % IPU_ISYS_MAX_PARALLEL_SOF; + break; + case IPU_INSYS_RESP_TYPE_FRAME_EOF: + if (csi2) + ipu7_isys_csi2_eof_event_by_stream(stream); + + dev_dbg(dev, "eof: stream %d(index %u) ts 0x%16.16llx\n", + resp->stream_id, + stream->seq[stream->seq_index].sequence, ts); + break; + default: + dev_err(dev, "Unknown response type %u stream %u\n", + resp->type, resp->stream_id); + break; + } + + ipu7_isys_put_stream(stream); +leave: + ipu7_fw_isys_put_resp(isys); + + return 0; +} + +static void ipu7_isys_csi2_isr(struct ipu7_isys_csi2 *csi2) +{ + struct device *dev = &csi2->isys->adev->auxdev.dev; + struct ipu7_device *isp = csi2->isys->adev->isp; + struct ipu7_isys_stream *s; + u32 sync, offset; + u32 fe = 0; + u8 vc; + + ipu7_isys_register_errors(csi2); + + offset = IS_IO_CSI2_SYNC_LEGACY_IRQ_CTL_BASE(csi2->port); + sync = readl(csi2->base + offset + IRQ_CTL_STATUS); + writel(sync, csi2->base + offset + IRQ_CTL_CLEAR); + dev_dbg(dev, "csi2-%u sync status 0x%08x\n", csi2->port, sync); + + if (!is_ipu7(isp->hw_ver)) { + fe = readl(csi2->base + offset + IRQ1_CTL_STATUS); + writel(fe, csi2->base + offset + IRQ1_CTL_CLEAR); + dev_dbg(dev, "csi2-%u FE status 0x%08x\n", csi2->port, fe); + } + + for (vc = 0; vc < IPU7_NR_OF_CSI2_VC && (sync || fe); vc++) { + s = ipu7_isys_query_stream_by_source(csi2->isys, + csi2->asd.source, vc); + if (!s) + continue; + + if (!is_ipu7(isp->hw_ver)) { + if (sync & IPU7P5_CSI_RX_SYNC_FS_VC & (1U << vc)) + ipu7_isys_csi2_sof_event_by_stream(s); + + if (fe & IPU7P5_CSI_RX_SYNC_FE_VC & (1U << vc)) + ipu7_isys_csi2_eof_event_by_stream(s); + } else { + if (sync & IPU7_CSI_RX_SYNC_FS_VC & (1U << (vc * 2))) + ipu7_isys_csi2_sof_event_by_stream(s); + + if (sync & IPU7_CSI_RX_SYNC_FE_VC & (2U << (vc * 2))) + ipu7_isys_csi2_eof_event_by_stream(s); + } + } +} + +static irqreturn_t isys_isr(struct ipu7_bus_device *adev) +{ + struct ipu7_isys *isys = ipu7_bus_get_drvdata(adev); + u32 status_csi, status_sw, csi_offset, sw_offset; + struct device *dev = &isys->adev->auxdev.dev; + void __iomem *base = isys->pdata->base; + + spin_lock(&isys->power_lock); + if (!isys->power) { + spin_unlock(&isys->power_lock); + return IRQ_NONE; + } + + csi_offset = IS_IO_CSI2_LEGACY_IRQ_CTRL_BASE; + sw_offset = IS_BASE; + + status_csi = readl(base + csi_offset + IRQ_CTL_STATUS); + status_sw = readl(base + sw_offset + TO_SW_IRQ_CNTL_STATUS); + if (!status_csi && !status_sw) { + spin_unlock(&isys->power_lock); + return IRQ_NONE; + } + + if (status_csi) + dev_dbg(dev, "status csi 0x%08x\n", status_csi); + if (status_sw) + dev_dbg(dev, "status to_sw 0x%08x\n", status_sw); + + do { + writel(status_sw, base + sw_offset + TO_SW_IRQ_CNTL_CLEAR); + writel(status_csi, base + csi_offset + IRQ_CTL_CLEAR); + + if (isys->isr_csi2_mask & status_csi) { + unsigned int i; + + for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { + /* irq from not enabled port */ + if (!isys->csi2[i].base) + continue; + if (status_csi & isys->csi2[i].legacy_irq_mask) + ipu7_isys_csi2_isr(&isys->csi2[i]); + } + } + + if (!isys_isr_one(adev)) + status_sw = TO_SW_IRQ_FW; + else + status_sw = 0; + + status_csi = readl(base + csi_offset + IRQ_CTL_STATUS); + status_sw |= readl(base + sw_offset + TO_SW_IRQ_CNTL_STATUS); + } while ((status_csi & isys->isr_csi2_mask) || + (status_sw & TO_SW_IRQ_FW)); + + writel(TO_SW_IRQ_MASK, base + sw_offset + TO_SW_IRQ_CNTL_MASK_N); + + spin_unlock(&isys->power_lock); + + return IRQ_HANDLED; +} + +static const struct ipu7_auxdrv_data ipu7_isys_auxdrv_data = { + .isr = isys_isr, + .isr_threaded = NULL, + .wake_isr_thread = false, +}; + +static const struct auxiliary_device_id ipu7_isys_id_table[] = { + { + .name = "intel_ipu7.isys", + .driver_data = (kernel_ulong_t)&ipu7_isys_auxdrv_data, + }, + { } +}; +MODULE_DEVICE_TABLE(auxiliary, ipu7_isys_id_table); + +static struct auxiliary_driver isys_driver = { + .name = IPU_ISYS_NAME, + .probe = isys_probe, + .remove = isys_remove, + .id_table = ipu7_isys_id_table, + .driver = { + .pm = &isys_pm_ops, + }, +}; + +module_auxiliary_driver(isys_driver); + +MODULE_AUTHOR("Bingbu Cao "); +MODULE_AUTHOR("Tianshu Qiu "); +MODULE_AUTHOR("Qingwu Zhang "); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Intel ipu7 input system driver"); +MODULE_IMPORT_NS("INTEL_IPU7"); +MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); diff --git a/drivers/staging/media/ipu7/ipu7-isys.h b/drivers/staging/media/ipu7/ipu7-isys.h new file mode 100644 index 000000000000..ef1ab1b42f6c --- /dev/null +++ b/drivers/staging/media/ipu7/ipu7-isys.h @@ -0,0 +1,140 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 - 2025 Intel Corporation + */ + +#ifndef IPU7_ISYS_H +#define IPU7_ISYS_H + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "abi/ipu7_fw_msg_abi.h" +#include "abi/ipu7_fw_isys_abi.h" + +#include "ipu7.h" +#include "ipu7-isys-csi2.h" +#include "ipu7-isys-video.h" + +#define IPU_ISYS_ENTITY_PREFIX "Intel IPU7" + +/* FW support max 16 streams */ +#define IPU_ISYS_MAX_STREAMS 16U + +/* + * Current message queue configuration. These must be big enough + * so that they never gets full. Queues are located in system memory + */ +#define IPU_ISYS_SIZE_RECV_QUEUE 40U +#define IPU_ISYS_SIZE_LOG_QUEUE 256U +#define IPU_ISYS_SIZE_SEND_QUEUE 40U +#define IPU_ISYS_NUM_RECV_QUEUE 1U + +#define IPU_ISYS_MIN_WIDTH 2U +#define IPU_ISYS_MIN_HEIGHT 2U +#define IPU_ISYS_MAX_WIDTH 8160U +#define IPU_ISYS_MAX_HEIGHT 8190U + +#define FW_CALL_TIMEOUT_JIFFIES \ + msecs_to_jiffies(IPU_LIB_CALL_TIMEOUT_MS) + +struct isys_fw_log { + struct mutex mutex; /* protect whole struct */ + void *head; + void *addr; + u32 count; /* running counter of log */ + u32 size; /* actual size of log content, in bits */ +}; + +/* + * struct ipu7_isys + * + * @media_dev: Media device + * @v4l2_dev: V4L2 device + * @adev: ISYS bus device + * @power: Is ISYS powered on or not? + * @isr_bits: Which bits does the ISR handle? + * @power_lock: Serialise access to power (power state in general) + * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers + * @streams_lock: serialise access to streams + * @streams: streams per firmware stream ID + * @syscom: fw communication layer context + * @ref_count: total number of callers fw open + * @mutex: serialise access isys video open/release related operations + * @stream_mutex: serialise stream start and stop, queueing requests + * @pdata: platform data pointer + * @csi2: CSI-2 receivers + */ +struct ipu7_isys { + struct media_device media_dev; + struct v4l2_device v4l2_dev; + struct ipu7_bus_device *adev; + + int power; + spinlock_t power_lock; /* Serialise access to power */ + u32 isr_csi2_mask; + u32 csi2_rx_ctrl_cached; + spinlock_t streams_lock; + struct ipu7_isys_stream streams[IPU_ISYS_MAX_STREAMS]; + int streams_ref_count[IPU_ISYS_MAX_STREAMS]; + u32 phy_rext_cal; + bool icache_prefetch; + bool csi2_cse_ipc_not_supported; + unsigned int ref_count; + unsigned int stream_opened; + + struct mutex mutex; /* Serialise isys video open/release related */ + struct mutex stream_mutex; /* Stream start, stop, queueing reqs */ + + struct ipu7_isys_pdata *pdata; + + struct ipu7_isys_csi2 *csi2; + struct isys_fw_log *fw_log; + + struct list_head requests; + struct pm_qos_request pm_qos; + spinlock_t listlock; /* Protect framebuflist */ + struct list_head framebuflist; + struct list_head framebuflist_fw; + struct v4l2_async_notifier notifier; + + struct ipu7_insys_config *subsys_config; + dma_addr_t subsys_config_dma_addr; +}; + +struct isys_fw_msgs { + union { + u64 dummy; + struct ipu7_insys_buffset frame; + struct ipu7_insys_stream_cfg stream; + } fw_msg; + struct list_head head; + dma_addr_t dma_addr; +}; + +struct ipu7_isys_csi2_config { + unsigned int nlanes; + unsigned int port; + enum v4l2_mbus_type bus_type; +}; + +struct sensor_async_sd { + struct v4l2_async_connection asc; + struct ipu7_isys_csi2_config csi2; +}; + +struct isys_fw_msgs *ipu7_get_fw_msg_buf(struct ipu7_isys_stream *stream); +void ipu7_put_fw_msg_buf(struct ipu7_isys *isys, uintptr_t data); +void ipu7_cleanup_fw_msg_bufs(struct ipu7_isys *isys); +int isys_isr_one(struct ipu7_bus_device *adev); +void ipu7_isys_setup_hw(struct ipu7_isys *isys); +#endif /* IPU7_ISYS_H */ diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index b3c4789fa5c8..4a70d3527cb7 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -2745,10 +2745,8 @@ static int ipu7_runtime_resume(struct device *dev) } static const struct dev_pm_ops ipu7_pm_ops = { - SET_SYSTEM_SLEEP_PM_OPS(&ipu7_suspend, &ipu7_resume) - SET_RUNTIME_PM_OPS(&ipu7_suspend, /* Same as in suspend flow */ - &ipu7_runtime_resume, - NULL) + SYSTEM_SLEEP_PM_OPS(&ipu7_suspend, &ipu7_resume) + RUNTIME_PM_OPS(&ipu7_suspend, &ipu7_runtime_resume, NULL) }; static const struct pci_device_id ipu7_pci_tbl[] = { From 3f9b2dc8800c9933a5b7de2cc59517815fc77e46 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:22 +0800 Subject: [PATCH 217/310] MAINTAINERS: add maintainers for Intel IPU7 input system driver Update MAINTAINERS file for Intel IPU7 input system staging driver. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- MAINTAINERS | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index cd6bf6764333..658543062bba 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12239,6 +12239,15 @@ T: git git://linuxtv.org/media.git F: Documentation/admin-guide/media/ipu6-isys.rst F: drivers/media/pci/intel/ipu6/ +INTEL IPU7 INPUT SYSTEM DRIVER +M: Sakari Ailus +R: Bingbu Cao +R: Stanislaw Gruszka +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: drivers/staging/media/ipu7/ + INTEL ISHTP ECLITE DRIVER M: Srinivas Pandruvada L: platform-driver-x86@vger.kernel.org From c5ddd03bc5c4ac92703060001d8912cacb3c2765 Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Thu, 29 May 2025 12:13:23 +0800 Subject: [PATCH 218/310] media: staging/ipu7: add Makefile, Kconfig and to-do file for IPU7 Add Kconfig and Makefile for IPU7 driver and also update the Makefile to build the IPU7 driver. Also add a to-do file to list the TODOs. Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/Kconfig | 2 ++ drivers/staging/media/Makefile | 1 + drivers/staging/media/ipu7/Kconfig | 18 ++++++++++++++++++ drivers/staging/media/ipu7/Makefile | 23 +++++++++++++++++++++++ drivers/staging/media/ipu7/TODO | 28 ++++++++++++++++++++++++++++ 5 files changed, 72 insertions(+) create mode 100644 drivers/staging/media/ipu7/Kconfig create mode 100644 drivers/staging/media/ipu7/Makefile create mode 100644 drivers/staging/media/ipu7/TODO diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index b44214854399..043ffbc472fb 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -28,6 +28,8 @@ source "drivers/staging/media/imx/Kconfig" source "drivers/staging/media/ipu3/Kconfig" +source "drivers/staging/media/ipu7/Kconfig" + source "drivers/staging/media/max96712/Kconfig" source "drivers/staging/media/meson/vdec/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index ad4e9619a9e0..177dfe95289b 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -9,4 +9,5 @@ obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive/ obj-$(CONFIG_VIDEO_SUNXI) += sunxi/ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/ obj-$(CONFIG_VIDEO_IPU3_IMGU) += ipu3/ +obj-$(CONFIG_VIDEO_INTEL_IPU7) += ipu7/ obj-$(CONFIG_DVB_AV7110) += av7110/ diff --git a/drivers/staging/media/ipu7/Kconfig b/drivers/staging/media/ipu7/Kconfig new file mode 100644 index 000000000000..5b800e9b09c4 --- /dev/null +++ b/drivers/staging/media/ipu7/Kconfig @@ -0,0 +1,18 @@ +config VIDEO_INTEL_IPU7 + tristate "Intel IPU7 driver" + depends on ACPI || COMPILE_TEST + depends on VIDEO_DEV + depends on X86 && HAS_DMA + depends on IPU_BRIDGE || !IPU_BRIDGE + select AUXILIARY_BUS + select IOMMU_IOVA + select VIDEO_V4L2_SUBDEV_API + select MEDIA_CONTROLLER + select VIDEOBUF2_DMA_SG + select V4L2_FWNODE + help + This is the 7th Gen Intel Image Processing Unit, found in Intel SoCs + and used for capturing images and video from camera sensors. + + To compile this driver, say Y here! It contains 2 modules - + intel_ipu7 and intel_ipu7_isys. diff --git a/drivers/staging/media/ipu7/Makefile b/drivers/staging/media/ipu7/Makefile new file mode 100644 index 000000000000..6d2aec219e65 --- /dev/null +++ b/drivers/staging/media/ipu7/Makefile @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2017 - 2025 Intel Corporation. + +intel-ipu7-objs += ipu7.o \ + ipu7-bus.o \ + ipu7-dma.o \ + ipu7-mmu.o \ + ipu7-buttress.o \ + ipu7-cpd.o \ + ipu7-syscom.o \ + ipu7-boot.o + +obj-$(CONFIG_VIDEO_INTEL_IPU7) += intel-ipu7.o + +intel-ipu7-isys-objs += ipu7-isys.o \ + ipu7-isys-csi2.o \ + ipu7-isys-csi-phy.o \ + ipu7-fw-isys.o \ + ipu7-isys-video.o \ + ipu7-isys-queue.o \ + ipu7-isys-subdev.o + +obj-$(CONFIG_VIDEO_INTEL_IPU7) += intel-ipu7-isys.o diff --git a/drivers/staging/media/ipu7/TODO b/drivers/staging/media/ipu7/TODO new file mode 100644 index 000000000000..7fbc37059adf --- /dev/null +++ b/drivers/staging/media/ipu7/TODO @@ -0,0 +1,28 @@ +This is a list of things that need to be done to get this driver out of the +staging directory. + +- ABI headers cleanup + Cleanup the firmware ABI headers + +- Add metadata capture support + The IPU7 hardware should support metadata capture, but it is not + fully verified with IPU7 firmware ABI so far, need to add the metadata + capture support. + +- Refine CSI2 PHY code + Refine the ipu7-isys-csi2-phy.c, move the hardware specific variant + into structure, clarify and explain the PHY registers to make it more + readable. + +- Work with the common IPU module + Sakari commented much of the driver code is the same than the IPU6 driver. + IPU7 driver is expected to work with the common IPU module in future. + +- Register definition cleanup + Cleanup the register definitions - remove some unnecessary definitions + remove 'U' suffix for hexadecimal and decimal values and add IPU7 prefix + for IPU7 specific registers. + Some ISYS IO sub-blocks register definitions are offset values from + specific sub-block base, but it is not clear and well suited for driver + to use, need to update the register definitions to make it more clear + and readable. From a8598c7de1bcd94461ca54c972efa9b4ea501fb9 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Thu, 3 Jul 2025 17:53:19 +0300 Subject: [PATCH 219/310] media: ipu7: Drop IPU8 PCI ID for now This driver is intended for IPU7 at the moment so drop the IPU8 PCI ID. Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/staging/media/ipu7/ipu7.c b/drivers/staging/media/ipu7/ipu7.c index 4a70d3527cb7..1b4f01db13ca 100644 --- a/drivers/staging/media/ipu7/ipu7.c +++ b/drivers/staging/media/ipu7/ipu7.c @@ -2752,7 +2752,6 @@ static const struct dev_pm_ops ipu7_pm_ops = { static const struct pci_device_id ipu7_pci_tbl[] = { {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU7_PCI_ID)}, {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU7P5_PCI_ID)}, - {PCI_DEVICE(PCI_VENDOR_ID_INTEL, IPU8_PCI_ID)}, {0,} }; MODULE_DEVICE_TABLE(pci, ipu7_pci_tbl); From 5bcc50cb55c2bdc24aafffef9ad1529839ddc948 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Wed, 25 Jun 2025 08:35:36 +0200 Subject: [PATCH 220/310] media: i2c: adv7604/tc358743/tda1997x: HPD low for HZ / 7 When the EDID is updated, the hotplug detect signal must remain low for 100 ms minimum. Currently these three drivers use that exact minimum, but some HDMI transmitters need the HPD to be low for a bit longer before they detect that they need to read the EDID again. Experience shows that HZ / 7 (= 143 ms) is a good value. So change HZ / 10 to HZ / 7. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/i2c/adv7604.c | 4 ++-- drivers/media/i2c/tc358743.c | 4 ++-- drivers/media/i2c/tda1997x.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/i2c/adv7604.c b/drivers/media/i2c/adv7604.c index e271782b7b70..afed38596362 100644 --- a/drivers/media/i2c/adv7604.c +++ b/drivers/media/i2c/adv7604.c @@ -2448,8 +2448,8 @@ static int adv76xx_set_edid(struct v4l2_subdev *sd, struct v4l2_edid *edid) } cec_s_phys_addr(state->cec_adap, parent_pa, false); - /* enable hotplug after 100 ms */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 10); + /* enable hotplug after 143 ms */ + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 7); return 0; } diff --git a/drivers/media/i2c/tc358743.c b/drivers/media/i2c/tc358743.c index 37ebc760f73b..1cc7636e446d 100644 --- a/drivers/media/i2c/tc358743.c +++ b/drivers/media/i2c/tc358743.c @@ -437,9 +437,9 @@ static void tc358743_enable_edid(struct v4l2_subdev *sd) v4l2_dbg(2, debug, sd, "%s:\n", __func__); - /* Enable hotplug after 100 ms. DDC access to EDID is also enabled when + /* Enable hotplug after 143 ms. DDC access to EDID is also enabled when * hotplug is enabled. See register DDC_CTL */ - schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 10); + schedule_delayed_work(&state->delayed_work_enable_hotplug, HZ / 7); tc358743_enable_interrupts(sd, true); tc358743_s_ctrl_detect_tx_5v(sd); diff --git a/drivers/media/i2c/tda1997x.c b/drivers/media/i2c/tda1997x.c index 959590afc80f..1087d2bddaf2 100644 --- a/drivers/media/i2c/tda1997x.c +++ b/drivers/media/i2c/tda1997x.c @@ -589,8 +589,8 @@ static void tda1997x_enable_edid(struct v4l2_subdev *sd) v4l2_dbg(1, debug, sd, "%s\n", __func__); - /* Enable hotplug after 100ms */ - schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 10); + /* Enable hotplug after 143ms */ + schedule_delayed_work(&state->delayed_work_enable_hpd, HZ / 7); } /* ----------------------------------------------------------------------------- From 36e713438ae3519ef46f3dd1093045627718311b Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 30 Jun 2025 13:08:46 +0200 Subject: [PATCH 221/310] media: cec: core: add glitch error injection This adds support for inserting 'glitches' after a falling and/or rising edge. This tests what happens when there are little voltage spikes after falling or rising edges, which can be caused due to noise or reflections on the CEC line. A proper CEC implementation will deglitch this, but a poor implementation can create a Low Drive pulse in response, effectively making CEC unusable. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/cec/core/cec-pin-error-inj.c | 48 +++++++++++++++++++++- drivers/media/cec/core/cec-pin-priv.h | 7 ++++ drivers/media/cec/core/cec-pin.c | 29 +++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/drivers/media/cec/core/cec-pin-error-inj.c b/drivers/media/cec/core/cec-pin-error-inj.c index 6e61a04b8168..68cbd83dfd6e 100644 --- a/drivers/media/cec/core/cec-pin-error-inj.c +++ b/drivers/media/cec/core/cec-pin-error-inj.c @@ -95,6 +95,10 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) pin->tx_custom_pulse = false; pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT; pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT; + pin->tx_glitch_low_usecs = CEC_TIM_GLITCH_DEFAULT; + pin->tx_glitch_high_usecs = CEC_TIM_GLITCH_DEFAULT; + pin->tx_glitch_falling_edge = false; + pin->tx_glitch_rising_edge = false; return true; } if (!strcmp(token, "rx-clear")) { @@ -111,6 +115,10 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) pin->tx_custom_pulse = false; pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT; pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT; + pin->tx_glitch_low_usecs = CEC_TIM_GLITCH_DEFAULT; + pin->tx_glitch_high_usecs = CEC_TIM_GLITCH_DEFAULT; + pin->tx_glitch_falling_edge = false; + pin->tx_glitch_rising_edge = false; return true; } if (!strcmp(token, "tx-ignore-nack-until-eom")) { @@ -122,6 +130,14 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) cec_pin_start_timer(pin); return true; } + if (!strcmp(token, "tx-glitch-falling-edge")) { + pin->tx_glitch_falling_edge = true; + return true; + } + if (!strcmp(token, "tx-glitch-rising-edge")) { + pin->tx_glitch_rising_edge = true; + return true; + } if (!p) return false; @@ -139,7 +155,23 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) if (kstrtou32(p, 0, &usecs) || usecs > 10000000) return false; - pin->tx_custom_high_usecs = usecs; + pin->tx_glitch_high_usecs = usecs; + return true; + } + if (!strcmp(token, "tx-glitch-low-usecs")) { + u32 usecs; + + if (kstrtou32(p, 0, &usecs) || usecs > 100) + return false; + pin->tx_glitch_low_usecs = usecs; + return true; + } + if (!strcmp(token, "tx-glitch-high-usecs")) { + u32 usecs; + + if (kstrtou32(p, 0, &usecs) || usecs > 100) + return false; + pin->tx_glitch_high_usecs = usecs; return true; } @@ -285,6 +317,10 @@ int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf) seq_puts(sf, "# tx-custom-low-usecs define the 'low' time for the custom pulse\n"); seq_puts(sf, "# tx-custom-high-usecs define the 'high' time for the custom pulse\n"); seq_puts(sf, "# tx-custom-pulse transmit the custom pulse once the bus is idle\n"); + seq_puts(sf, "# tx-glitch-low-usecs define the 'low' time for the glitch pulse\n"); + seq_puts(sf, "# tx-glitch-high-usecs define the 'high' time for the glitch pulse\n"); + seq_puts(sf, "# tx-glitch-falling-edge send the glitch pulse after every falling edge\n"); + seq_puts(sf, "# tx-glitch-rising-edge send the glitch pulse after every rising edge\n"); seq_puts(sf, "#\n"); seq_puts(sf, "# TX error injection:\n"); seq_puts(sf, "# [,] tx-no-eom don't set the EOM bit\n"); @@ -334,6 +370,10 @@ int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf) if (pin->tx_ignore_nack_until_eom) seq_puts(sf, "tx-ignore-nack-until-eom\n"); + if (pin->tx_glitch_falling_edge) + seq_puts(sf, "tx-glitch-falling-edge\n"); + if (pin->tx_glitch_rising_edge) + seq_puts(sf, "tx-glitch-rising-edge\n"); if (pin->tx_custom_pulse) seq_puts(sf, "tx-custom-pulse\n"); if (pin->tx_custom_low_usecs != CEC_TIM_CUSTOM_DEFAULT) @@ -342,5 +382,11 @@ int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf) if (pin->tx_custom_high_usecs != CEC_TIM_CUSTOM_DEFAULT) seq_printf(sf, "tx-custom-high-usecs %u\n", pin->tx_custom_high_usecs); + if (pin->tx_glitch_low_usecs != CEC_TIM_GLITCH_DEFAULT) + seq_printf(sf, "tx-glitch-low-usecs %u\n", + pin->tx_glitch_low_usecs); + if (pin->tx_glitch_high_usecs != CEC_TIM_GLITCH_DEFAULT) + seq_printf(sf, "tx-glitch-high-usecs %u\n", + pin->tx_glitch_high_usecs); return 0; } diff --git a/drivers/media/cec/core/cec-pin-priv.h b/drivers/media/cec/core/cec-pin-priv.h index 156a9f81be94..88eefcb60ab8 100644 --- a/drivers/media/cec/core/cec-pin-priv.h +++ b/drivers/media/cec/core/cec-pin-priv.h @@ -164,6 +164,9 @@ enum cec_pin_state { /* The default for the low/high time of the custom pulse */ #define CEC_TIM_CUSTOM_DEFAULT 1000 +/* The default for the low/high time of the glitch pulse */ +#define CEC_TIM_GLITCH_DEFAULT 1 + #define CEC_NUM_PIN_EVENTS 128 #define CEC_PIN_EVENT_FL_IS_HIGH (1 << 0) #define CEC_PIN_EVENT_FL_DROPPED (1 << 1) @@ -227,10 +230,14 @@ struct cec_pin { u32 tx_custom_low_usecs; u32 tx_custom_high_usecs; + u32 tx_glitch_low_usecs; + u32 tx_glitch_high_usecs; bool tx_ignore_nack_until_eom; bool tx_custom_pulse; bool tx_generated_poll; bool tx_post_eom; + bool tx_glitch_falling_edge; + bool tx_glitch_rising_edge; u8 tx_extra_bytes; u32 tx_low_drive_cnt; #ifdef CONFIG_CEC_PIN_ERROR_INJ diff --git a/drivers/media/cec/core/cec-pin.c b/drivers/media/cec/core/cec-pin.c index 59ac12113f3a..f3b0febf0f1c 100644 --- a/drivers/media/cec/core/cec-pin.c +++ b/drivers/media/cec/core/cec-pin.c @@ -142,15 +142,42 @@ static bool cec_pin_read(struct cec_pin *pin) return v; } +static void cec_pin_insert_glitch(struct cec_pin *pin, bool rising_edge) +{ + /* + * Insert a short glitch after the falling or rising edge to + * simulate reflections on the CEC line. This can be used to + * test deglitch filters, which should be present in CEC devices + * to deal with noise on the line. + */ + if (!pin->tx_glitch_high_usecs || !pin->tx_glitch_low_usecs) + return; + if (rising_edge) { + udelay(pin->tx_glitch_high_usecs); + call_void_pin_op(pin, low); + udelay(pin->tx_glitch_low_usecs); + call_void_pin_op(pin, high); + } else { + udelay(pin->tx_glitch_low_usecs); + call_void_pin_op(pin, high); + udelay(pin->tx_glitch_high_usecs); + call_void_pin_op(pin, low); + } +} + static void cec_pin_low(struct cec_pin *pin) { call_void_pin_op(pin, low); + if (pin->tx_glitch_falling_edge && pin->adap->cec_pin_is_high) + cec_pin_insert_glitch(pin, false); cec_pin_update(pin, false, false); } static bool cec_pin_high(struct cec_pin *pin) { call_void_pin_op(pin, high); + if (pin->tx_glitch_rising_edge && !pin->adap->cec_pin_is_high) + cec_pin_insert_glitch(pin, true); return cec_pin_read(pin); } @@ -1350,6 +1377,8 @@ struct cec_adapter *cec_pin_allocate_adapter(const struct cec_pin_ops *pin_ops, init_waitqueue_head(&pin->kthread_waitq); pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT; pin->tx_custom_high_usecs = CEC_TIM_CUSTOM_DEFAULT; + pin->tx_glitch_low_usecs = CEC_TIM_GLITCH_DEFAULT; + pin->tx_glitch_high_usecs = CEC_TIM_GLITCH_DEFAULT; adap = cec_allocate_adapter(&cec_pin_adap_ops, priv, name, caps | CEC_CAP_MONITOR_ALL | CEC_CAP_MONITOR_PIN, From a9fc2adc142379ffb10e28c580470bc7ddb2e06c Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 30 Jun 2025 13:08:47 +0200 Subject: [PATCH 222/310] media: cec: core: add rx-no-low-drive setting If rx-no-low-drive is set, then the CEC pin framework will disable the detection of situations where a Low Drive has to be generated. So if this is set, then we will never generate Low Drives. This helps testing whether other CEC devices generate Low Drive pulses by ensuring it is not us that is generating them. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/cec/core/cec-pin-error-inj.c | 11 +++++++++++ drivers/media/cec/core/cec-pin-priv.h | 1 + drivers/media/cec/core/cec-pin.c | 2 +- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/drivers/media/cec/core/cec-pin-error-inj.c b/drivers/media/cec/core/cec-pin-error-inj.c index 68cbd83dfd6e..d9e613c7ce3f 100644 --- a/drivers/media/cec/core/cec-pin-error-inj.c +++ b/drivers/media/cec/core/cec-pin-error-inj.c @@ -91,6 +91,7 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) if (!strcmp(token, "clear")) { memset(pin->error_inj, 0, sizeof(pin->error_inj)); pin->rx_toggle = pin->tx_toggle = false; + pin->rx_no_low_drive = false; pin->tx_ignore_nack_until_eom = false; pin->tx_custom_pulse = false; pin->tx_custom_low_usecs = CEC_TIM_CUSTOM_DEFAULT; @@ -105,6 +106,7 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) for (i = 0; i <= CEC_ERROR_INJ_OP_ANY; i++) pin->error_inj[i] &= ~CEC_ERROR_INJ_RX_MASK; pin->rx_toggle = false; + pin->rx_no_low_drive = false; return true; } if (!strcmp(token, "tx-clear")) { @@ -121,6 +123,10 @@ bool cec_pin_error_inj_parse_line(struct cec_adapter *adap, char *line) pin->tx_glitch_rising_edge = false; return true; } + if (!strcmp(token, "rx-no-low-drive")) { + pin->rx_no_low_drive = true; + return true; + } if (!strcmp(token, "tx-ignore-nack-until-eom")) { pin->tx_ignore_nack_until_eom = true; return true; @@ -305,6 +311,9 @@ int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf) seq_puts(sf, "# rx-clear clear all rx error injections for \n"); seq_puts(sf, "# tx-clear clear all tx error injections for \n"); seq_puts(sf, "#\n"); + seq_puts(sf, "# RX error injection settings:\n"); + seq_puts(sf, "# rx-no-low-drive do not generate low-drive pulses\n"); + seq_puts(sf, "#\n"); seq_puts(sf, "# RX error injection:\n"); seq_puts(sf, "# [,] rx-nack NACK the message instead of sending an ACK\n"); seq_puts(sf, "# [,] rx-low-drive force a low-drive condition at this bit position\n"); @@ -368,6 +377,8 @@ int cec_pin_error_inj_show(struct cec_adapter *adap, struct seq_file *sf) } } + if (pin->rx_no_low_drive) + seq_puts(sf, "rx-no-low-drive\n"); if (pin->tx_ignore_nack_until_eom) seq_puts(sf, "tx-ignore-nack-until-eom\n"); if (pin->tx_glitch_falling_edge) diff --git a/drivers/media/cec/core/cec-pin-priv.h b/drivers/media/cec/core/cec-pin-priv.h index 88eefcb60ab8..e7801be9adb9 100644 --- a/drivers/media/cec/core/cec-pin-priv.h +++ b/drivers/media/cec/core/cec-pin-priv.h @@ -228,6 +228,7 @@ struct cec_pin { u32 timer_max_overrun; u32 timer_sum_overrun; + bool rx_no_low_drive; u32 tx_custom_low_usecs; u32 tx_custom_high_usecs; u32 tx_glitch_low_usecs; diff --git a/drivers/media/cec/core/cec-pin.c b/drivers/media/cec/core/cec-pin.c index f3b0febf0f1c..4d7155281daa 100644 --- a/drivers/media/cec/core/cec-pin.c +++ b/drivers/media/cec/core/cec-pin.c @@ -797,7 +797,7 @@ static void cec_pin_rx_states(struct cec_pin *pin, ktime_t ts) * Go to low drive state when the total bit time is * too short. */ - if (delta < CEC_TIM_DATA_BIT_TOTAL_MIN) { + if (delta < CEC_TIM_DATA_BIT_TOTAL_MIN && !pin->rx_no_low_drive) { if (!pin->rx_data_bit_too_short_cnt++) { pin->rx_data_bit_too_short_ts = ktime_to_ns(pin->ts); pin->rx_data_bit_too_short_delta = delta; From a8c42a5bf5ea9914d662db799a1d5c93c2176a5c Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 30 Jun 2025 13:08:48 +0200 Subject: [PATCH 223/310] Documentation: media: cec: update error inj doc Document rx-no-low-drive and the new support to inject glitches. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../media/cec/cec-pin-error-inj.rst | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst b/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst index 411d42a742f3..c02790319f3f 100644 --- a/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst +++ b/Documentation/userspace-api/media/cec/cec-pin-error-inj.rst @@ -41,6 +41,9 @@ error injection status:: # rx-clear clear all rx error injections for # tx-clear clear all tx error injections for # + # RX error injection settings: + # rx-no-low-drive do not generate low-drive pulses + # # RX error injection: # [,] rx-nack NACK the message instead of sending an ACK # [,] rx-low-drive force a low-drive condition at this bit position @@ -53,6 +56,10 @@ error injection status:: # tx-custom-low-usecs define the 'low' time for the custom pulse # tx-custom-high-usecs define the 'high' time for the custom pulse # tx-custom-pulse transmit the custom pulse once the bus is idle + # tx-glitch-low-usecs define the 'low' time for the glitch pulse + # tx-glitch-high-usecs define the 'high' time for the glitch pulse + # tx-glitch-falling-edge send the glitch pulse after every falling edge + # tx-glitch-rising-edge send the glitch pulse after every rising edge # # TX error injection: # [,] tx-no-eom don't set the EOM bit @@ -193,6 +200,14 @@ Receive Messages This does not work if the remote CEC transmitter has logical address 0 ('TV') since that will always win. +``rx-no-low-drive`` + The receiver will ignore situations that would normally generate a + Low Drive pulse (3.6 ms). This is typically done if a spurious pulse is + detected when receiving a message, and it indicates to the transmitter that + the message has to be retransmitted since the receiver got confused. + Disabling this is useful to test how other CEC devices handle glitches + by ensuring we will not be the one that generates a Low Drive. + Transmit Messages ----------------- @@ -327,3 +342,30 @@ Custom Pulses ``tx-custom-pulse`` Transmit a single custom pulse as soon as the CEC bus is idle. + +Glitch Pulses +------------- + +This emulates what happens if the signal on the CEC line is seeing spurious +pulses. Typically this happens after the falling or rising edge where there +is a short voltage fluctuation that, if the CEC hardware doesn't do +deglitching, can be seen as a spurious pulse and can cause a Low Drive +condition or corrupt data. + +``tx-glitch-low-usecs `` + This defines the duration in microseconds that the glitch pulse pulls + the CEC line low. The default is 1 microsecond. The range is 0-100 + microseconds. If 0, then no glitch pulse will be generated. + +``tx-glitch-high-usecs `` + This defines the duration in microseconds that the glitch pulse keeps the + CEC line high (unless another CEC adapter pulls it low in that time). + The default is 1 microseconds. The range is 0-100 microseconds. If 0, then + no glitch pulse will be generated.The total period of the glitch pulse is + ``tx-custom-low-usecs + tx-custom-high-usecs``. + +``tx-glitch-falling-edge`` + Send the glitch pulse right after the falling edge. + +``tx-glitch-rising-edge`` + Send the glitch pulse right after the rising edge. From e068fbdf4ccb503dd05126361b6e587abc0e7d9f Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 30 Jun 2025 13:08:49 +0200 Subject: [PATCH 224/310] media: cec: cec-gpio: rename functions Ensure that the interrupt function names clearly state for which gpio they are (cec/hpd/5v). No functional changes. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../media/cec/platform/cec-gpio/cec-gpio.c | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/drivers/media/cec/platform/cec-gpio/cec-gpio.c b/drivers/media/cec/platform/cec-gpio/cec-gpio.c index 50cdc557c943..fd5d810d55a6 100644 --- a/drivers/media/cec/platform/cec-gpio/cec-gpio.c +++ b/drivers/media/cec/platform/cec-gpio/cec-gpio.c @@ -61,15 +61,15 @@ static void cec_gpio_low(struct cec_adapter *adap) gpiod_set_value(cec->cec_gpio, 0); } -static irqreturn_t cec_hpd_gpio_irq_handler_thread(int irq, void *priv) +static irqreturn_t cec_gpio_5v_irq_handler_thread(int irq, void *priv) { struct cec_gpio *cec = priv; - cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts); + cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts); return IRQ_HANDLED; } -static irqreturn_t cec_5v_gpio_irq_handler(int irq, void *priv) +static irqreturn_t cec_gpio_5v_irq_handler(int irq, void *priv) { struct cec_gpio *cec = priv; int val = gpiod_get_value(cec->v5_gpio); @@ -82,15 +82,15 @@ static irqreturn_t cec_5v_gpio_irq_handler(int irq, void *priv) return IRQ_WAKE_THREAD; } -static irqreturn_t cec_5v_gpio_irq_handler_thread(int irq, void *priv) +static irqreturn_t cec_gpio_hpd_irq_handler_thread(int irq, void *priv) { struct cec_gpio *cec = priv; - cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts); + cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts); return IRQ_HANDLED; } -static irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv) +static irqreturn_t cec_gpio_hpd_irq_handler(int irq, void *priv) { struct cec_gpio *cec = priv; int val = gpiod_get_value(cec->hpd_gpio); @@ -103,7 +103,7 @@ static irqreturn_t cec_hpd_gpio_irq_handler(int irq, void *priv) return IRQ_WAKE_THREAD; } -static irqreturn_t cec_gpio_irq_handler(int irq, void *priv) +static irqreturn_t cec_gpio_cec_irq_handler(int irq, void *priv) { struct cec_gpio *cec = priv; int val = gpiod_get_value(cec->cec_gpio); @@ -113,7 +113,7 @@ static irqreturn_t cec_gpio_irq_handler(int irq, void *priv) return IRQ_HANDLED; } -static bool cec_gpio_enable_irq(struct cec_adapter *adap) +static bool cec_gpio_cec_enable_irq(struct cec_adapter *adap) { struct cec_gpio *cec = cec_get_drvdata(adap); @@ -121,7 +121,7 @@ static bool cec_gpio_enable_irq(struct cec_adapter *adap) return true; } -static void cec_gpio_disable_irq(struct cec_adapter *adap) +static void cec_gpio_cec_disable_irq(struct cec_adapter *adap) { struct cec_gpio *cec = cec_get_drvdata(adap); @@ -164,8 +164,8 @@ static const struct cec_pin_ops cec_gpio_pin_ops = { .read = cec_gpio_read, .low = cec_gpio_low, .high = cec_gpio_high, - .enable_irq = cec_gpio_enable_irq, - .disable_irq = cec_gpio_disable_irq, + .enable_irq = cec_gpio_cec_enable_irq, + .disable_irq = cec_gpio_cec_disable_irq, .status = cec_gpio_status, .read_hpd = cec_gpio_read_hpd, .read_5v = cec_gpio_read_5v, @@ -209,7 +209,7 @@ static int cec_gpio_probe(struct platform_device *pdev) if (IS_ERR(cec->adap)) return PTR_ERR(cec->adap); - ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_irq_handler, + ret = devm_request_irq(dev, cec->cec_irq, cec_gpio_cec_irq_handler, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_NO_AUTOEN, cec->adap->name, cec); if (ret) @@ -218,8 +218,8 @@ static int cec_gpio_probe(struct platform_device *pdev) if (cec->hpd_gpio) { cec->hpd_irq = gpiod_to_irq(cec->hpd_gpio); ret = devm_request_threaded_irq(dev, cec->hpd_irq, - cec_hpd_gpio_irq_handler, - cec_hpd_gpio_irq_handler_thread, + cec_gpio_hpd_irq_handler, + cec_gpio_hpd_irq_handler_thread, IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "hpd-gpio", cec); @@ -230,8 +230,8 @@ static int cec_gpio_probe(struct platform_device *pdev) if (cec->v5_gpio) { cec->v5_irq = gpiod_to_irq(cec->v5_gpio); ret = devm_request_threaded_irq(dev, cec->v5_irq, - cec_5v_gpio_irq_handler, - cec_5v_gpio_irq_handler_thread, + cec_gpio_5v_irq_handler, + cec_gpio_5v_irq_handler_thread, IRQF_ONESHOT | IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, "v5-gpio", cec); From 5701c792c7b9348adca640f24bb01d69ba657e03 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 30 Jun 2025 13:08:50 +0200 Subject: [PATCH 225/310] media: cec: cec-gpio: reading hpd/5v is allowed to sleep Reading the hpd or 5v gpios is something that can sleep, so rework the code to allow this. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .../media/cec/platform/cec-gpio/cec-gpio.c | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/media/cec/platform/cec-gpio/cec-gpio.c b/drivers/media/cec/platform/cec-gpio/cec-gpio.c index fd5d810d55a6..3c27789d8657 100644 --- a/drivers/media/cec/platform/cec-gpio/cec-gpio.c +++ b/drivers/media/cec/platform/cec-gpio/cec-gpio.c @@ -64,7 +64,13 @@ static void cec_gpio_low(struct cec_adapter *adap) static irqreturn_t cec_gpio_5v_irq_handler_thread(int irq, void *priv) { struct cec_gpio *cec = priv; + int val = gpiod_get_value_cansleep(cec->v5_gpio); + bool is_high = val > 0; + if (val < 0 || is_high == cec->v5_is_high) + return IRQ_HANDLED; + + cec->v5_is_high = is_high; cec_queue_pin_5v_event(cec->adap, cec->v5_is_high, cec->v5_ts); return IRQ_HANDLED; } @@ -72,20 +78,21 @@ static irqreturn_t cec_gpio_5v_irq_handler_thread(int irq, void *priv) static irqreturn_t cec_gpio_5v_irq_handler(int irq, void *priv) { struct cec_gpio *cec = priv; - int val = gpiod_get_value(cec->v5_gpio); - bool is_high = val > 0; - if (val < 0 || is_high == cec->v5_is_high) - return IRQ_HANDLED; cec->v5_ts = ktime_get(); - cec->v5_is_high = is_high; return IRQ_WAKE_THREAD; } static irqreturn_t cec_gpio_hpd_irq_handler_thread(int irq, void *priv) { struct cec_gpio *cec = priv; + int val = gpiod_get_value_cansleep(cec->hpd_gpio); + bool is_high = val > 0; + if (val < 0 || is_high == cec->hpd_is_high) + return IRQ_HANDLED; + + cec->hpd_is_high = is_high; cec_queue_pin_hpd_event(cec->adap, cec->hpd_is_high, cec->hpd_ts); return IRQ_HANDLED; } @@ -93,13 +100,8 @@ static irqreturn_t cec_gpio_hpd_irq_handler_thread(int irq, void *priv) static irqreturn_t cec_gpio_hpd_irq_handler(int irq, void *priv) { struct cec_gpio *cec = priv; - int val = gpiod_get_value(cec->hpd_gpio); - bool is_high = val > 0; - if (val < 0 || is_high == cec->hpd_is_high) - return IRQ_HANDLED; cec->hpd_ts = ktime_get(); - cec->hpd_is_high = is_high; return IRQ_WAKE_THREAD; } @@ -148,7 +150,7 @@ static int cec_gpio_read_hpd(struct cec_adapter *adap) if (!cec->hpd_gpio) return -ENOTTY; - return gpiod_get_value(cec->hpd_gpio); + return gpiod_get_value_cansleep(cec->hpd_gpio); } static int cec_gpio_read_5v(struct cec_adapter *adap) @@ -157,7 +159,7 @@ static int cec_gpio_read_5v(struct cec_adapter *adap) if (!cec->v5_gpio) return -ENOTTY; - return gpiod_get_value(cec->v5_gpio); + return gpiod_get_value_cansleep(cec->v5_gpio); } static const struct cec_pin_ops cec_gpio_pin_ops = { From 0a345630c980b6a274d9d4b1e9fd833bfc1a6959 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:20:30 +0200 Subject: [PATCH 226/310] media: atomisp: Move atomisp_stop_streaming() above atomisp_start_streaming() Move atomisp_stop_streaming() above atomisp_start_streaming(), this is a preparation patch for making atomisp_start_streaming() properly cleanup if starting the sensor stream fails. No functional change, only moving a block of code up. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250505210008.152659-2-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../staging/media/atomisp/pci/atomisp_ioctl.c | 148 +++++++++--------- 1 file changed, 74 insertions(+), 74 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c index 97d99bed1560..705f104a2147 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c @@ -856,6 +856,80 @@ static void atomisp_dma_burst_len_cfg(struct atomisp_sub_device *asd) atomisp_css2_hw_store_32(DMA_BURST_SIZE_REG, 0x00); } +void atomisp_stop_streaming(struct vb2_queue *vq) +{ + struct atomisp_video_pipe *pipe = vq_to_pipe(vq); + struct atomisp_sub_device *asd = pipe->asd; + struct atomisp_device *isp = asd->isp; + struct pci_dev *pdev = to_pci_dev(isp->dev); + unsigned long flags; + int ret; + + dev_dbg(isp->dev, "Stop stream\n"); + + mutex_lock(&isp->mutex); + /* + * There is no guarantee that the buffers queued to / owned by the ISP + * will properly be returned to the queue when stopping. Set a flag to + * avoid new buffers getting queued and then wait for all the current + * buffers to finish. + */ + pipe->stopping = true; + mutex_unlock(&isp->mutex); + /* wait max 1 second */ + ret = wait_event_timeout(pipe->vb_queue.done_wq, + atomisp_buffers_in_css(pipe) == 0, HZ); + mutex_lock(&isp->mutex); + pipe->stopping = false; + if (ret == 0) + dev_warn(isp->dev, "Warning timeout waiting for CSS to return buffers\n"); + + spin_lock_irqsave(&isp->lock, flags); + asd->streaming = false; + spin_unlock_irqrestore(&isp->lock, flags); + + atomisp_clear_css_buffer_counters(asd); + atomisp_css_irq_enable(isp, IA_CSS_IRQ_INFO_CSS_RECEIVER_SOF, false); + + atomisp_css_stop(asd, false); + + atomisp_flush_video_pipe(pipe, VB2_BUF_STATE_ERROR, true); + + atomisp_subdev_cleanup_pending_events(asd); + + ret = v4l2_subdev_call(isp->inputs[asd->input_curr].csi_remote_source, + video, s_stream, 0); + if (ret) + dev_warn(isp->dev, "Stopping sensor stream failed: %d\n", ret); + + /* Disable the CSI interface on ANN B0/K0 */ + if (isp->media_dev.hw_revision >= ((ATOMISP_HW_REVISION_ISP2401 << + ATOMISP_HW_REVISION_SHIFT) | ATOMISP_HW_STEPPING_B0)) { + pci_write_config_word(pdev, MRFLD_PCI_CSI_CONTROL, + isp->saved_regs.csi_control & ~MRFLD_PCI_CSI_CONTROL_CSI_READY); + } + + if (atomisp_freq_scaling(isp, ATOMISP_DFS_MODE_LOW, false)) + dev_warn(isp->dev, "DFS failed.\n"); + + /* + * ISP work around, need to reset ISP to allow next stream on to work. + * Streams have already been destroyed by atomisp_css_stop(). + * Disable PUNIT/ISP acknowledge/handshake - SRSE=3 and then reset. + */ + pci_write_config_dword(pdev, PCI_I_CONTROL, + isp->saved_regs.i_control | MRFLD_PCI_I_CONTROL_SRSE_RESET_MASK); + atomisp_reset(isp); + + /* Streams were destroyed by atomisp_css_stop(), recreate them. */ + ret = atomisp_create_pipes_stream(&isp->asd); + if (ret) + dev_warn(isp->dev, "Recreating streams failed: %d\n", ret); + + media_pipeline_stop(&asd->video_out.vdev.entity.pads[0]); + mutex_unlock(&isp->mutex); +} + int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) { struct atomisp_video_pipe *pipe = vq_to_pipe(vq); @@ -961,80 +1035,6 @@ int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) return ret; } -void atomisp_stop_streaming(struct vb2_queue *vq) -{ - struct atomisp_video_pipe *pipe = vq_to_pipe(vq); - struct atomisp_sub_device *asd = pipe->asd; - struct atomisp_device *isp = asd->isp; - struct pci_dev *pdev = to_pci_dev(isp->dev); - unsigned long flags; - int ret; - - dev_dbg(isp->dev, "Stop stream\n"); - - mutex_lock(&isp->mutex); - /* - * There is no guarantee that the buffers queued to / owned by the ISP - * will properly be returned to the queue when stopping. Set a flag to - * avoid new buffers getting queued and then wait for all the current - * buffers to finish. - */ - pipe->stopping = true; - mutex_unlock(&isp->mutex); - /* wait max 1 second */ - ret = wait_event_timeout(pipe->vb_queue.done_wq, - atomisp_buffers_in_css(pipe) == 0, HZ); - mutex_lock(&isp->mutex); - pipe->stopping = false; - if (ret == 0) - dev_warn(isp->dev, "Warning timeout waiting for CSS to return buffers\n"); - - spin_lock_irqsave(&isp->lock, flags); - asd->streaming = false; - spin_unlock_irqrestore(&isp->lock, flags); - - atomisp_clear_css_buffer_counters(asd); - atomisp_css_irq_enable(isp, IA_CSS_IRQ_INFO_CSS_RECEIVER_SOF, false); - - atomisp_css_stop(asd, false); - - atomisp_flush_video_pipe(pipe, VB2_BUF_STATE_ERROR, true); - - atomisp_subdev_cleanup_pending_events(asd); - - ret = v4l2_subdev_call(isp->inputs[asd->input_curr].csi_remote_source, - video, s_stream, 0); - if (ret) - dev_warn(isp->dev, "Stopping sensor stream failed: %d\n", ret); - - /* Disable the CSI interface on ANN B0/K0 */ - if (isp->media_dev.hw_revision >= ((ATOMISP_HW_REVISION_ISP2401 << - ATOMISP_HW_REVISION_SHIFT) | ATOMISP_HW_STEPPING_B0)) { - pci_write_config_word(pdev, MRFLD_PCI_CSI_CONTROL, - isp->saved_regs.csi_control & ~MRFLD_PCI_CSI_CONTROL_CSI_READY); - } - - if (atomisp_freq_scaling(isp, ATOMISP_DFS_MODE_LOW, false)) - dev_warn(isp->dev, "DFS failed.\n"); - - /* - * ISP work around, need to reset ISP to allow next stream on to work. - * Streams have already been destroyed by atomisp_css_stop(). - * Disable PUNIT/ISP acknowledge/handshake - SRSE=3 and then reset. - */ - pci_write_config_dword(pdev, PCI_I_CONTROL, - isp->saved_regs.i_control | MRFLD_PCI_I_CONTROL_SRSE_RESET_MASK); - atomisp_reset(isp); - - /* Streams were destroyed by atomisp_css_stop(), recreate them. */ - ret = atomisp_create_pipes_stream(&isp->asd); - if (ret) - dev_warn(isp->dev, "Recreating streams failed: %d\n", ret); - - media_pipeline_stop(&asd->video_out.vdev.entity.pads[0]); - mutex_unlock(&isp->mutex); -} - /* * To get the current value of a control. * applications initialize the id field of a struct v4l2_control and From 41dd1f2fac325bdc39318665a91040097b2cd26c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:20:46 +0200 Subject: [PATCH 227/310] media: atomisp: Properly stop the ISP stream on sensor streamon errors When v4l2_subdev_call(sensor, s_stream, 1) fails atomisp_start_streaming() was not properly returning the buffer ownership back to the videobuf2-core code, resulting in: [ 1318.153447] ------------[ cut here ]------------ [ 1318.153499] WARNING: CPU: 0 PID: 4856 at drivers/media/common/videobuf2/videobuf2-core.c:1803 vb2_start_streaming+0xcb/0x160 [videobuf2_common] ... [ 1318.154551] Call Trace: [ 1318.154560] [ 1318.154571] ? __warn.cold+0xb7/0x14a [ 1318.154591] ? vb2_start_streaming+0xcb/0x160 [videobuf2_common] [ 1318.154617] ? report_bug+0xe0/0x180 [ 1318.154640] ? handle_bug+0x5e/0xa0 [ 1318.154652] ? exc_invalid_op+0x14/0x70 [ 1318.154665] ? asm_exc_invalid_op+0x16/0x20 [ 1318.154697] ? vb2_start_streaming+0xcb/0x160 [videobuf2_common] [ 1318.154723] ? vb2_start_streaming+0x70/0x160 [videobuf2_common] [ 1318.154748] vb2_core_streamon+0xa2/0x100 [videobuf2_common] The sensor streamon call is the last thing that atomisp_start_streaming() does and it was failing to undo all of the previous steps in general. Refactor atomisp_stop_streaming() into an atomisp_stop_stream() helper and call that on sensor streamon failure to properly clean things up. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250505210008.152659-3-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../staging/media/atomisp/pci/atomisp_ioctl.c | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c index 705f104a2147..491af67cc7a8 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c @@ -856,18 +856,15 @@ static void atomisp_dma_burst_len_cfg(struct atomisp_sub_device *asd) atomisp_css2_hw_store_32(DMA_BURST_SIZE_REG, 0x00); } -void atomisp_stop_streaming(struct vb2_queue *vq) +static void atomisp_stop_stream(struct atomisp_video_pipe *pipe, + bool stop_sensor, enum vb2_buffer_state state) { - struct atomisp_video_pipe *pipe = vq_to_pipe(vq); struct atomisp_sub_device *asd = pipe->asd; struct atomisp_device *isp = asd->isp; struct pci_dev *pdev = to_pci_dev(isp->dev); unsigned long flags; int ret; - dev_dbg(isp->dev, "Stop stream\n"); - - mutex_lock(&isp->mutex); /* * There is no guarantee that the buffers queued to / owned by the ISP * will properly be returned to the queue when stopping. Set a flag to @@ -893,14 +890,16 @@ void atomisp_stop_streaming(struct vb2_queue *vq) atomisp_css_stop(asd, false); - atomisp_flush_video_pipe(pipe, VB2_BUF_STATE_ERROR, true); + atomisp_flush_video_pipe(pipe, state, true); atomisp_subdev_cleanup_pending_events(asd); - ret = v4l2_subdev_call(isp->inputs[asd->input_curr].csi_remote_source, - video, s_stream, 0); - if (ret) - dev_warn(isp->dev, "Stopping sensor stream failed: %d\n", ret); + if (stop_sensor) { + ret = v4l2_subdev_call(isp->inputs[asd->input_curr].csi_remote_source, + video, s_stream, 0); + if (ret) + dev_warn(isp->dev, "Stopping sensor stream failed: %d\n", ret); + } /* Disable the CSI interface on ANN B0/K0 */ if (isp->media_dev.hw_revision >= ((ATOMISP_HW_REVISION_ISP2401 << @@ -927,7 +926,6 @@ void atomisp_stop_streaming(struct vb2_queue *vq) dev_warn(isp->dev, "Recreating streams failed: %d\n", ret); media_pipeline_stop(&asd->video_out.vdev.entity.pads[0]); - mutex_unlock(&isp->mutex); } int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) @@ -1023,11 +1021,7 @@ int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) video, s_stream, 1); if (ret) { dev_err(isp->dev, "Starting sensor stream failed: %d\n", ret); - spin_lock_irqsave(&isp->lock, irqflags); - asd->streaming = false; - spin_unlock_irqrestore(&isp->lock, irqflags); - ret = -EINVAL; - goto out_unlock; + atomisp_stop_stream(pipe, false, VB2_BUF_STATE_QUEUED); } out_unlock: @@ -1035,6 +1029,18 @@ int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) return ret; } +void atomisp_stop_streaming(struct vb2_queue *vq) +{ + struct atomisp_video_pipe *pipe = vq_to_pipe(vq); + struct atomisp_device *isp = pipe->asd->isp; + + dev_dbg(isp->dev, "Stop stream\n"); + + mutex_lock(&isp->mutex); + atomisp_stop_stream(pipe, true, VB2_BUF_STATE_ERROR); + mutex_unlock(&isp->mutex); +} + /* * To get the current value of a control. * applications initialize the id field of a struct v4l2_control and From 2194e6066ae092864cc5a92400826c9758f75a57 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:20:56 +0200 Subject: [PATCH 228/310] media: atomisp: Stop pipeline on atomisp_css_start() failure atomisp_start_streaming() starts the media pipeline before calling atomisp_css_start(). On atomisp_css_start() failures stop the pipeline before returning the error. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250505210008.152659-4-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/pci/atomisp_ioctl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c index 491af67cc7a8..fecba2588e38 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c @@ -982,6 +982,7 @@ int atomisp_start_streaming(struct vb2_queue *vq, unsigned int count) ret = atomisp_css_start(asd); if (ret) { atomisp_flush_video_pipe(pipe, VB2_BUF_STATE_QUEUED, true); + media_pipeline_stop(&asd->video_out.vdev.entity.pads[0]); goto out_unlock; } From f01d091c3e2b198ac0485fb83e420a0ba8d070bd Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:21:08 +0200 Subject: [PATCH 229/310] media: atomisp: Always free MIPI / CSI-receiver buffers from ia_css_uninit() The atomisp interrupt handling will free the MIPI / CSI-receiver buffers when processing a frame-completion event if the stop_requested flag is set, but only in the ISP2400 / BYT, not in the ISP2401 / CHT case. There are 2 problems with this: 1. Since this is only done in the BYT case the "mipi frames are not freed." warning always triggers on CHT devices. 2. There are 2 stop_requested flags, ia_css_pipe.stop_requested and ia_css_pipeline.stop_requested. The ISR checks the ia_css_pipe flag, but atomisp_css_stop() sets the ia_css_pipeline.stop_requested flag. So even on BYT freeing the buffers from the ISR never happens. This likely is a good thing since the buffers get freed on the first frame completion event and there might be multiple frames queued up. Fix things by completely dropping the freeing of the MIPI buffers from the ISR as well as the stop_requested flag always freeing the buffers from ia_css_uninit(). Also drop the warning since this now always is expected behavior. Note that ia_css_uninit() get called whenever streaming is stopped through atomisp_stop_stream() calling atomisp_reset() so the buffers are still freed whenever streaming is stopped. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250505210008.152659-5-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../staging/media/atomisp/pci/ia_css_pipe.h | 2 -- .../pipeline/interface/ia_css_pipeline.h | 1 - .../pci/runtime/pipeline/src/pipeline.c | 2 -- drivers/staging/media/atomisp/pci/sh_css.c | 27 ------------------- .../staging/media/atomisp/pci/sh_css_mipi.c | 11 -------- .../staging/media/atomisp/pci/sh_css_mipi.h | 2 -- 6 files changed, 45 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/ia_css_pipe.h b/drivers/staging/media/atomisp/pci/ia_css_pipe.h index c97d2ae356fd..77072694eb42 100644 --- a/drivers/staging/media/atomisp/pci/ia_css_pipe.h +++ b/drivers/staging/media/atomisp/pci/ia_css_pipe.h @@ -102,8 +102,6 @@ struct ia_css_yuvpp_settings { struct osys_object; struct ia_css_pipe { - /* TODO: Remove stop_requested and use stop_requested in the pipeline */ - bool stop_requested; struct ia_css_pipe_config config; struct ia_css_pipe_extra_config extra_config; struct ia_css_pipe_info info; diff --git a/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h b/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h index 316eaa2070ea..8b7cbf31a1a2 100644 --- a/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h +++ b/drivers/staging/media/atomisp/pci/runtime/pipeline/interface/ia_css_pipeline.h @@ -34,7 +34,6 @@ struct ia_css_pipeline_stage { struct ia_css_pipeline { enum ia_css_pipe_id pipe_id; u8 pipe_num; - bool stop_requested; struct ia_css_pipeline_stage *stages; struct ia_css_pipeline_stage *current_stage; unsigned int num_stages; diff --git a/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c b/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c index aabebe61ec77..cb8d652227a7 100644 --- a/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c +++ b/drivers/staging/media/atomisp/pci/runtime/pipeline/src/pipeline.c @@ -198,7 +198,6 @@ int ia_css_pipeline_request_stop(struct ia_css_pipeline *pipeline) ia_css_debug_dtrace(IA_CSS_DEBUG_TRACE, "ia_css_pipeline_request_stop() enter: pipeline=%p\n", pipeline); - pipeline->stop_requested = true; /* Send stop event to the sp*/ /* This needs improvement, stop on all the pipes available @@ -663,7 +662,6 @@ static void pipeline_init_defaults( pipeline->pipe_id = pipe_id; pipeline->stages = NULL; - pipeline->stop_requested = false; pipeline->current_stage = NULL; memcpy(&pipeline->in_frame, &ia_css_default_frame, diff --git a/drivers/staging/media/atomisp/pci/sh_css.c b/drivers/staging/media/atomisp/pci/sh_css.c index 5a8e8e67aa13..73bd87f43a8c 100644 --- a/drivers/staging/media/atomisp/pci/sh_css.c +++ b/drivers/staging/media/atomisp/pci/sh_css.c @@ -2002,10 +2002,6 @@ ia_css_uninit(void) sh_css_params_free_default_gdc_lut(); - /* TODO: JB: implement decent check and handling of freeing mipi frames */ - if (!mipi_is_free()) - dev_warn(atomisp_dev, "mipi frames are not freed.\n"); - /* cleanup generic data */ sh_css_params_uninit(); ia_css_refcount_uninit(); @@ -3743,23 +3739,6 @@ ia_css_pipe_dequeue_buffer(struct ia_css_pipe *pipe, case IA_CSS_BUFFER_TYPE_INPUT_FRAME: case IA_CSS_BUFFER_TYPE_OUTPUT_FRAME: case IA_CSS_BUFFER_TYPE_SEC_OUTPUT_FRAME: - if (pipe && pipe->stop_requested) { - if (!IS_ISP2401) { - /* - * free mipi frames only for old input - * system for 2401 it is done in - * ia_css_stream_destroy call - */ - return_err = free_mipi_frames(pipe); - if (return_err) { - IA_CSS_LOG("free_mipi_frames() failed"); - IA_CSS_LEAVE_ERR(return_err); - return return_err; - } - } - pipe->stop_requested = false; - } - fallthrough; case IA_CSS_BUFFER_TYPE_VF_OUTPUT_FRAME: case IA_CSS_BUFFER_TYPE_SEC_VF_OUTPUT_FRAME: frame = (struct ia_css_frame *)HOST_ADDRESS(ddr_buffer.kernel_ptr); @@ -4095,8 +4074,6 @@ sh_css_pipe_start(struct ia_css_stream *stream) return err; } - pipe->stop_requested = false; - switch (pipe_id) { case IA_CSS_PIPE_ID_PREVIEW: err = preview_start(pipe); @@ -4120,19 +4097,15 @@ sh_css_pipe_start(struct ia_css_stream *stream) for (i = 1; i < stream->num_pipes && 0 == err ; i++) { switch (stream->pipes[i]->mode) { case IA_CSS_PIPE_ID_PREVIEW: - stream->pipes[i]->stop_requested = false; err = preview_start(stream->pipes[i]); break; case IA_CSS_PIPE_ID_VIDEO: - stream->pipes[i]->stop_requested = false; err = video_start(stream->pipes[i]); break; case IA_CSS_PIPE_ID_CAPTURE: - stream->pipes[i]->stop_requested = false; err = capture_start(stream->pipes[i]); break; case IA_CSS_PIPE_ID_YUVPP: - stream->pipes[i]->stop_requested = false; err = yuvpp_start(stream->pipes[i]); break; default: diff --git a/drivers/staging/media/atomisp/pci/sh_css_mipi.c b/drivers/staging/media/atomisp/pci/sh_css_mipi.c index 42f14ed853e1..971b685cdb58 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_mipi.c +++ b/drivers/staging/media/atomisp/pci/sh_css_mipi.c @@ -185,17 +185,6 @@ mipi_init(void) ref_count_mipi_allocation[i] = 0; } -bool mipi_is_free(void) -{ - unsigned int i; - - for (i = 0; i < N_CSI_PORTS; i++) - if (ref_count_mipi_allocation[i]) - return false; - - return true; -} - /* * @brief Calculate the required MIPI buffer sizes. * Based on the stream configuration, calculate the diff --git a/drivers/staging/media/atomisp/pci/sh_css_mipi.h b/drivers/staging/media/atomisp/pci/sh_css_mipi.h index 6f7389f44baa..b3887ee3c75a 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_mipi.h +++ b/drivers/staging/media/atomisp/pci/sh_css_mipi.h @@ -14,8 +14,6 @@ void mipi_init(void); -bool mipi_is_free(void); - int allocate_mipi_frames(struct ia_css_pipe *pipe, struct ia_css_stream_info *info); From 0193cce58b196603427e925683b54f2f64ef031c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:21:34 +0200 Subject: [PATCH 230/310] media: atomisp: Fix "stop stream timeout." error Commit c7194b21809e ("media: atomisp: On streamoff wait for buffers owned by the CSS to be given back") added draining of the CSS buffer queue to the beginning of atomisp_stop_stream(). But it turns out that when telling the CSS to stop streaming it needs at least 1 buffer queued, because the CSS firmware waits for a frame to be completed before stopping and without buffers it cannot complete a frame. At the end of atomisp_stop_stream() it is always safe to return buffer ownership to the videobuf2-core. Either atomisp_css_stop() has successfully stopped the stream; or the atomisp_reset() later on which power-cycles the ISP will definitely have stopped the stream. Drop the draining of the CSS buffer queue to fix the "stop stream timeout." error and move the atomisp_flush_video_pipe() call after atomisp_reset(), passing false for the warn_on_css_frames flag since some buffers still being marked as owned by the CSS expected on stream off. Also increase the timeout in destroy_stream(), since this waits for the last frame to be completed this can take longer then 40 ms. When e.g. using a framerate of 15 fps, this could take 66ms, make the timeout 200 ms to be on the safe side. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250505210008.152659-6-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/pci/atomisp_compat_css20.c | 2 +- .../staging/media/atomisp/pci/atomisp_fops.c | 5 +---- .../staging/media/atomisp/pci/atomisp_ioctl.c | 20 ++----------------- .../media/atomisp/pci/atomisp_subdev.h | 3 --- 4 files changed, 4 insertions(+), 26 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c index bc97fa2c374c..be5f37f4a6fd 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c +++ b/drivers/staging/media/atomisp/pci/atomisp_compat_css20.c @@ -387,7 +387,7 @@ static int __destroy_stream(struct atomisp_sub_device *asd, } if (stream_env->stream_state == CSS_STREAM_STARTED) { - timeout = jiffies + msecs_to_jiffies(40); + timeout = jiffies + msecs_to_jiffies(200); while (1) { if (ia_css_stream_has_stopped(stream_env->stream)) break; diff --git a/drivers/staging/media/atomisp/pci/atomisp_fops.c b/drivers/staging/media/atomisp/pci/atomisp_fops.c index 57da7ddb1503..c7aef066f209 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_fops.c +++ b/drivers/staging/media/atomisp/pci/atomisp_fops.c @@ -234,9 +234,6 @@ static int atomisp_q_video_buffers_to_css(struct atomisp_sub_device *asd, if (WARN_ON(css_pipe_id >= IA_CSS_PIPE_ID_NUM)) return -EINVAL; - if (pipe->stopping) - return -EINVAL; - space = ATOMISP_CSS_Q_DEPTH - atomisp_buffers_in_css(pipe); while (space--) { struct ia_css_frame *frame; @@ -367,7 +364,7 @@ static void atomisp_buf_queue(struct vb2_buffer *vb) mutex_lock(&asd->isp->mutex); ret = atomisp_pipe_check(pipe, false); - if (ret || pipe->stopping) { + if (ret) { spin_lock_irqsave(&pipe->irq_lock, irqflags); atomisp_buffer_done(frame, VB2_BUF_STATE_ERROR); spin_unlock_irqrestore(&pipe->irq_lock, irqflags); diff --git a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c index fecba2588e38..bb8b2f2213b0 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_ioctl.c +++ b/drivers/staging/media/atomisp/pci/atomisp_ioctl.c @@ -865,22 +865,6 @@ static void atomisp_stop_stream(struct atomisp_video_pipe *pipe, unsigned long flags; int ret; - /* - * There is no guarantee that the buffers queued to / owned by the ISP - * will properly be returned to the queue when stopping. Set a flag to - * avoid new buffers getting queued and then wait for all the current - * buffers to finish. - */ - pipe->stopping = true; - mutex_unlock(&isp->mutex); - /* wait max 1 second */ - ret = wait_event_timeout(pipe->vb_queue.done_wq, - atomisp_buffers_in_css(pipe) == 0, HZ); - mutex_lock(&isp->mutex); - pipe->stopping = false; - if (ret == 0) - dev_warn(isp->dev, "Warning timeout waiting for CSS to return buffers\n"); - spin_lock_irqsave(&isp->lock, flags); asd->streaming = false; spin_unlock_irqrestore(&isp->lock, flags); @@ -890,8 +874,6 @@ static void atomisp_stop_stream(struct atomisp_video_pipe *pipe, atomisp_css_stop(asd, false); - atomisp_flush_video_pipe(pipe, state, true); - atomisp_subdev_cleanup_pending_events(asd); if (stop_sensor) { @@ -920,6 +902,8 @@ static void atomisp_stop_stream(struct atomisp_video_pipe *pipe, isp->saved_regs.i_control | MRFLD_PCI_I_CONTROL_SRSE_RESET_MASK); atomisp_reset(isp); + atomisp_flush_video_pipe(pipe, state, false); + /* Streams were destroyed by atomisp_css_stop(), recreate them. */ ret = atomisp_create_pipes_stream(&isp->asd); if (ret) diff --git a/drivers/staging/media/atomisp/pci/atomisp_subdev.h b/drivers/staging/media/atomisp/pci/atomisp_subdev.h index bd1a198cda30..e1d0168cb91d 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_subdev.h +++ b/drivers/staging/media/atomisp/pci/atomisp_subdev.h @@ -57,9 +57,6 @@ struct atomisp_video_pipe { /* Filled through atomisp_get_css_frame_info() on queue setup */ struct ia_css_frame_info frame_info; - /* Set from streamoff to disallow queuing further buffers in CSS */ - bool stopping; - /* * irq_lock is used to protect video buffer state change operations and * also to make activeq and capq operations atomic. From cf101966f4e283b3748c7b04f3a58026da176095 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 11:24:02 +0200 Subject: [PATCH 231/310] media: atomisp: Switch to int3472 driver sensor GPIO mapping code Replace the duplicate code for calling the special Intel camera sensor GPIO type _DSM (79234640-9e10-4fea-a5c1-b5aa8b19756f) and mapping GPIOs to the sensor with a call to int3472_discrete_parse_crs() from the int3472 driver. Besides avoiding code duplication the int3472 version of the code also supports more features, like mapping the powerdown GPIO to a regulator on the mt9m114 which is necessary to make the camera on the Asus T100TA work. Signed-off-by: Hans de Goede Acked-by: Sakari Ailus Link: https://lore.kernel.org/r/20250507184737.154747-7-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/Kconfig | 1 + .../staging/media/atomisp/pci/atomisp_csi2.h | 17 -- .../media/atomisp/pci/atomisp_csi2_bridge.c | 239 ++---------------- .../staging/media/atomisp/pci/atomisp_v4l2.c | 1 + 4 files changed, 30 insertions(+), 228 deletions(-) diff --git a/drivers/staging/media/atomisp/Kconfig b/drivers/staging/media/atomisp/Kconfig index 1265fc58a232..fbe507ae6ec6 100644 --- a/drivers/staging/media/atomisp/Kconfig +++ b/drivers/staging/media/atomisp/Kconfig @@ -12,6 +12,7 @@ menuconfig INTEL_ATOMISP config VIDEO_ATOMISP tristate "Intel Atom Image Signal Processor Driver" depends on VIDEO_DEV && INTEL_ATOMISP + depends on INTEL_SKL_INT3472 depends on IPU_BRIDGE depends on MEDIA_PCI_SUPPORT depends on PMIC_OPREGION diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2.h b/drivers/staging/media/atomisp/pci/atomisp_csi2.h index bb998c82a438..ec762f8fb922 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2.h +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2.h @@ -19,28 +19,11 @@ #define CSI2_PAD_SOURCE 1 #define CSI2_PADS_NUM 2 -#define CSI2_MAX_ACPI_GPIOS 2u - -struct acpi_device; struct v4l2_device; struct atomisp_device; struct atomisp_sub_device; -struct atomisp_csi2_acpi_gpio_map { - struct acpi_gpio_params params[CSI2_MAX_ACPI_GPIOS]; - struct acpi_gpio_mapping mapping[CSI2_MAX_ACPI_GPIOS + 1]; -}; - -struct atomisp_csi2_acpi_gpio_parsing_data { - struct acpi_device *adev; - struct atomisp_csi2_acpi_gpio_map *map; - u32 settings[CSI2_MAX_ACPI_GPIOS]; - unsigned int settings_count; - unsigned int res_count; - unsigned int map_count; -}; - struct atomisp_mipi_csi2_device { struct v4l2_subdev subdev; struct media_pad pads[CSI2_PADS_NUM]; diff --git a/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c index 6abda358a72f..2a90f86e515f 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c +++ b/drivers/staging/media/atomisp/pci/atomisp_csi2_bridge.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include @@ -24,39 +25,6 @@ #define PMC_CLK_RATE_19_2MHZ 19200000 -/* - * 79234640-9e10-4fea-a5c1-b5aa8b19756f - * This _DSM GUID returns information about the GPIO lines mapped to a sensor. - * Function number 1 returns a count of the GPIO lines that are mapped. - * Subsequent functions return 32 bit ints encoding information about the GPIO. - */ -static const guid_t intel_sensor_gpio_info_guid = - GUID_INIT(0x79234640, 0x9e10, 0x4fea, - 0xa5, 0xc1, 0xb5, 0xaa, 0x8b, 0x19, 0x75, 0x6f); - -#define INTEL_GPIO_DSM_TYPE_SHIFT 0 -#define INTEL_GPIO_DSM_TYPE_MASK GENMASK(7, 0) -#define INTEL_GPIO_DSM_PIN_SHIFT 8 -#define INTEL_GPIO_DSM_PIN_MASK GENMASK(15, 8) -#define INTEL_GPIO_DSM_SENSOR_ON_VAL_SHIFT 24 -#define INTEL_GPIO_DSM_SENSOR_ON_VAL_MASK GENMASK(31, 24) - -#define INTEL_GPIO_DSM_TYPE(x) \ - (((x) & INTEL_GPIO_DSM_TYPE_MASK) >> INTEL_GPIO_DSM_TYPE_SHIFT) -#define INTEL_GPIO_DSM_PIN(x) \ - (((x) & INTEL_GPIO_DSM_PIN_MASK) >> INTEL_GPIO_DSM_PIN_SHIFT) -#define INTEL_GPIO_DSM_SENSOR_ON_VAL(x) \ - (((x) & INTEL_GPIO_DSM_SENSOR_ON_VAL_MASK) >> INTEL_GPIO_DSM_SENSOR_ON_VAL_SHIFT) - -/* - * 822ace8f-2814-4174-a56b-5f029fe079ee - * This _DSM GUID returns a string from the sensor device, which acts as a - * module identifier. - */ -static const guid_t intel_sensor_module_guid = - GUID_INIT(0x822ace8f, 0x2814, 0x4174, - 0xa5, 0x6b, 0x5f, 0x02, 0x9f, 0xe0, 0x79, 0xee); - /* * dc2f6c4f-045b-4f1d-97b9-882a6860a4be * This _DSM GUID returns a package with n*2 strings, with each set of 2 strings @@ -323,195 +291,44 @@ static int atomisp_csi2_get_port(struct acpi_device *adev, int clock_num) return gmin_cfg_get_int(adev, "CsiPort", port); } -/* Note this always returns 1 to continue looping so that res_count is accurate */ -static int atomisp_csi2_handle_acpi_gpio_res(struct acpi_resource *ares, void *_data) -{ - struct atomisp_csi2_acpi_gpio_parsing_data *data = _data; - struct acpi_resource_gpio *agpio; - const char *name; - bool active_low; - unsigned int i; - u32 settings = 0; - u16 pin; - - if (!acpi_gpio_get_io_resource(ares, &agpio)) - return 1; /* Not a GPIO, continue the loop */ - - data->res_count++; - - pin = agpio->pin_table[0]; - for (i = 0; i < data->settings_count; i++) { - if (INTEL_GPIO_DSM_PIN(data->settings[i]) == pin) { - settings = data->settings[i]; - break; - } - } - - if (i == data->settings_count) { - acpi_handle_warn(data->adev->handle, - "%s: Could not find DSM GPIO settings for pin %u\n", - dev_name(&data->adev->dev), pin); - return 1; - } - - switch (INTEL_GPIO_DSM_TYPE(settings)) { - case 0: - name = "reset-gpios"; - break; - case 1: - name = "powerdown-gpios"; - break; - default: - acpi_handle_warn(data->adev->handle, "%s: Unknown GPIO type 0x%02lx for pin %u\n", - dev_name(&data->adev->dev), - INTEL_GPIO_DSM_TYPE(settings), pin); - return 1; - } - - /* - * Both reset and power-down need to be logical false when the sensor - * is on (sensor should not be in reset and not be powered-down). So - * when the sensor-on-value (which is the physical pin value) is high, - * then the signal is active-low. - */ - active_low = INTEL_GPIO_DSM_SENSOR_ON_VAL(settings); - - i = data->map_count; - if (i == CSI2_MAX_ACPI_GPIOS) - return 1; - - /* res_count is already incremented */ - data->map->params[i].crs_entry_index = data->res_count - 1; - data->map->params[i].active_low = active_low; - data->map->mapping[i].name = name; - data->map->mapping[i].data = &data->map->params[i]; - data->map->mapping[i].size = 1; - data->map_count++; - - acpi_handle_info(data->adev->handle, "%s: %s crs %d %s pin %u active-%s\n", - dev_name(&data->adev->dev), name, - data->res_count - 1, agpio->resource_source.string_ptr, - pin, active_low ? "low" : "high"); - - return 1; -} - /* - * Helper function to create an ACPI GPIO lookup table for sensor reset and - * powerdown signals on Intel Bay Trail (BYT) and Cherry Trail (CHT) devices, - * including setting the correct polarity for the GPIO. + * Alloc and fill an int3472_discrete_device struct so that we can re-use + * the INT3472 sensor GPIO mapping code. * - * This uses the "79234640-9e10-4fea-a5c1-b5aa8b19756f" DSM method directly - * on the sensor device's ACPI node. This is different from later Intel - * hardware which has a separate INT3472 acpi_device with this info. - * - * This function must be called before creating the sw-noded describing - * the fwnode graph endpoint. And sensor drivers used on these devices - * must return -EPROBE_DEFER when there is no endpoint description yet. - * Together this guarantees that the GPIO lookups are in place before - * the sensor driver tries to get GPIOs with gpiod_get(). - * - * Note this code uses the same DSM GUID as the int3472_gpio_guid in - * the INT3472 discrete.c code and there is some overlap, but there are - * enough differences that it is difficult to share the code. + * This gets called from ipu_bridge_init() which runs only once per boot, + * the created faux int3472 device is leaked on purpose to keep the created + * GPIO mappings around permanently. */ static int atomisp_csi2_add_gpio_mappings(struct acpi_device *adev) { - struct atomisp_csi2_acpi_gpio_parsing_data data = { }; - LIST_HEAD(resource_list); - union acpi_object *obj; - unsigned int i, j; + struct int3472_discrete_device *int3472; int ret; - obj = acpi_evaluate_dsm_typed(adev->handle, &intel_sensor_module_guid, - 0x00, 1, NULL, ACPI_TYPE_STRING); - if (obj) { - acpi_handle_info(adev->handle, "%s: Sensor module id: '%s'\n", - dev_name(&adev->dev), obj->string.pointer); - ACPI_FREE(obj); - } - - /* - * First get the GPIO-settings count and then get count GPIO-settings - * values. Note the order of these may differ from the order in which - * the GPIOs are listed on the ACPI resources! So we first store them all - * and then enumerate the ACPI resources and match them up by pin number. - */ - obj = acpi_evaluate_dsm_typed(adev->handle, - &intel_sensor_gpio_info_guid, 0x00, 1, - NULL, ACPI_TYPE_INTEGER); - if (!obj) { - acpi_handle_err(adev->handle, "%s: No _DSM entry for GPIO pin count\n", - dev_name(&adev->dev)); - return -EIO; - } - - data.settings_count = obj->integer.value; - ACPI_FREE(obj); - - if (data.settings_count > CSI2_MAX_ACPI_GPIOS) { - acpi_handle_err(adev->handle, "%s: Too many GPIOs %u > %u\n", - dev_name(&adev->dev), data.settings_count, - CSI2_MAX_ACPI_GPIOS); - return -EOVERFLOW; - } - - for (i = 0; i < data.settings_count; i++) { - /* - * i + 2 because the index of this _DSM function is 1-based - * and the first function is just a count. - */ - obj = acpi_evaluate_dsm_typed(adev->handle, - &intel_sensor_gpio_info_guid, - 0x00, i + 2, - NULL, ACPI_TYPE_INTEGER); - if (!obj) { - acpi_handle_err(adev->handle, "%s: No _DSM entry for pin %u\n", - dev_name(&adev->dev), i); - return -EIO; - } - - data.settings[i] = obj->integer.value; - ACPI_FREE(obj); - } - - /* Since we match up by pin-number the pin-numbers must be unique */ - for (i = 0; i < data.settings_count; i++) { - for (j = i + 1; j < data.settings_count; j++) { - if (INTEL_GPIO_DSM_PIN(data.settings[i]) != - INTEL_GPIO_DSM_PIN(data.settings[j])) - continue; - - acpi_handle_err(adev->handle, "%s: Duplicate pin number %lu\n", - dev_name(&adev->dev), - INTEL_GPIO_DSM_PIN(data.settings[i])); - return -EIO; - } - } - - data.map = kzalloc(sizeof(*data.map), GFP_KERNEL); - if (!data.map) + /* Max num GPIOs we've seen plus a terminator */ + int3472 = kzalloc(struct_size(int3472, gpios.table, INT3472_MAX_SENSOR_GPIOS + 1), + GFP_KERNEL); + if (!int3472) return -ENOMEM; - /* Now parse the ACPI resources and build the lookup table */ - data.adev = adev; - ret = acpi_dev_get_resources(adev, &resource_list, - atomisp_csi2_handle_acpi_gpio_res, &data); - if (ret < 0) - return ret; + /* + * On atomisp the _DSM to get the GPIO type must be made on the sensor + * adev, rather than on a separate INT3472 adev. + */ + int3472->adev = adev; + int3472->dev = &adev->dev; + int3472->sensor = adev; - acpi_dev_free_resource_list(&resource_list); + int3472->sensor_name = kasprintf(GFP_KERNEL, I2C_DEV_NAME_FORMAT, acpi_dev_name(adev)); + if (!int3472->sensor_name) { + kfree(int3472); + return -ENOMEM; + } - if (data.map_count != data.settings_count || - data.res_count != data.settings_count) - acpi_handle_warn(adev->handle, "%s: ACPI GPIO resources vs DSM GPIO-info count mismatch (dsm: %d res: %d map %d\n", - dev_name(&adev->dev), data.settings_count, - data.res_count, data.map_count); - - ret = acpi_dev_add_driver_gpios(adev, data.map->mapping); - if (ret) - acpi_handle_err(adev->handle, "%s: Error adding driver GPIOs: %d\n", - dev_name(&adev->dev), ret); + ret = int3472_discrete_parse_crs(int3472); + if (ret) { + int3472_discrete_cleanup(int3472); + kfree(int3472); + } return ret; } diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index 0bd0bfded4af..3fdaa4b7bbaf 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -1513,3 +1513,4 @@ MODULE_AUTHOR("Xiaolin Zhang "); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("Intel ATOM Platform ISP Driver"); MODULE_IMPORT_NS("INTEL_IPU_BRIDGE"); +MODULE_IMPORT_NS("INTEL_INT3472_DISCRETE"); From ed3bc38ff5cbc431f9b155610046f8f404437bb5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Sat, 3 May 2025 17:00:30 -0300 Subject: [PATCH 232/310] media: atomisp: Fix ia_css_vf.host.c coding style Fix a coding style: "ERROR: that open brace { should be on the previous line" issue reported in ia_css_vf.host.c:94. Signed-off-by: Pablo Link: https://lore.kernel.org/r/20250503200030.5982-1-pablo@pablo.ct.ws Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/pci/isp/kernels/vf/vf_1.0/ia_css_vf.host.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/vf/vf_1.0/ia_css_vf.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/vf/vf_1.0/ia_css_vf.host.c index 127f12ba2214..3c675063c4a7 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/vf/vf_1.0/ia_css_vf.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/vf/vf_1.0/ia_css_vf.host.c @@ -91,8 +91,7 @@ configure_kernel( unsigned int vf_log_ds = 0; /* First compute value */ - if (vf_info) - { + if (vf_info) { err = sh_css_vf_downscale_log2(out_info, vf_info, &vf_log_ds); if (err) return err; From e3b95e64d508d0e869d3fb7bdef6559f976bbdef Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 21:17:29 +0200 Subject: [PATCH 233/310] media: atomisp: gc0310: Rename "dev" function variable to "sensor" Many functions on the gc0310 driver use a function local variable called "dev" but these variable's type is not "struct device *" type as one would expect based on the name. Instead they point to the gc0310 driver data struct. Rename these variables to sensor to make their purpose more clear. Signed-off-by: Hans de Goede Reviewed-by: Kieran Bingham Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-2-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index d35394f1ddbb..cc74e90a1457 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -282,16 +282,16 @@ static int gc0310_write_reg_array(struct i2c_client *client, return 0; } -static int gc0310_exposure_set(struct gc0310_device *dev, u32 exp) +static int gc0310_exposure_set(struct gc0310_device *sensor, u32 exp) { - struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); return i2c_smbus_write_word_swapped(client, GC0310_AEC_PK_EXPO_H, exp); } -static int gc0310_gain_set(struct gc0310_device *dev, u32 gain) +static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) { - struct i2c_client *client = v4l2_get_subdevdata(&dev->sd); + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); u8 again, dgain; int ret; @@ -317,27 +317,27 @@ static int gc0310_gain_set(struct gc0310_device *dev, u32 gain) static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) { - struct gc0310_device *dev = + struct gc0310_device *sensor = container_of(ctrl->handler, struct gc0310_device, ctrls.handler); int ret; /* Only apply changes to the controls if the device is powered up */ - if (!pm_runtime_get_if_in_use(dev->sd.dev)) + if (!pm_runtime_get_if_in_use(sensor->sd.dev)) return 0; switch (ctrl->id) { case V4L2_CID_EXPOSURE: - ret = gc0310_exposure_set(dev, ctrl->val); + ret = gc0310_exposure_set(sensor, ctrl->val); break; case V4L2_CID_GAIN: - ret = gc0310_gain_set(dev, ctrl->val); + ret = gc0310_gain_set(sensor, ctrl->val); break; default: ret = -EINVAL; break; } - pm_runtime_put(dev->sd.dev); + pm_runtime_put(sensor->sd.dev); return ret; } @@ -346,14 +346,14 @@ static const struct v4l2_ctrl_ops ctrl_ops = { }; static struct v4l2_mbus_framefmt * -gc0310_get_pad_format(struct gc0310_device *dev, +gc0310_get_pad_format(struct gc0310_device *sensor, struct v4l2_subdev_state *state, unsigned int pad, enum v4l2_subdev_format_whence which) { if (which == V4L2_SUBDEV_FORMAT_TRY) return v4l2_subdev_state_get_format(state, pad); - return &dev->mode.fmt; + return &sensor->mode.fmt; } /* The GC0310 currently only supports 1 fixed fmt */ @@ -370,10 +370,10 @@ static int gc0310_set_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { - struct gc0310_device *dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); struct v4l2_mbus_framefmt *fmt; - fmt = gc0310_get_pad_format(dev, sd_state, format->pad, format->which); + fmt = gc0310_get_pad_format(sensor, sd_state, format->pad, format->which); gc0310_fill_format(fmt); format->format = *fmt; @@ -384,10 +384,10 @@ static int gc0310_get_fmt(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_format *format) { - struct gc0310_device *dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); struct v4l2_mbus_framefmt *fmt; - fmt = gc0310_get_pad_format(dev, sd_state, format->pad, format->which); + fmt = gc0310_get_pad_format(sensor, sd_state, format->pad, format->which); format->format = *fmt; return 0; } @@ -424,12 +424,12 @@ static int gc0310_detect(struct i2c_client *client) static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) { - struct gc0310_device *dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); int ret = 0; dev_dbg(&client->dev, "%s S enable=%d\n", __func__, enable); - mutex_lock(&dev->input_lock); + mutex_lock(&sensor->input_lock); if (enable) { ret = pm_runtime_get_sync(&client->dev); @@ -449,7 +449,7 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) goto error_power_down; /* restore value of all ctrls */ - ret = __v4l2_ctrl_handler_setup(&dev->ctrls.handler); + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); if (ret) goto error_power_down; @@ -475,14 +475,14 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) if (!enable) pm_runtime_put(&client->dev); - dev->is_streaming = enable; - mutex_unlock(&dev->input_lock); + sensor->is_streaming = enable; + mutex_unlock(&sensor->input_lock); return 0; error_power_down: pm_runtime_put(&client->dev); - dev->is_streaming = false; - mutex_unlock(&dev->input_lock); + sensor->is_streaming = false; + mutex_unlock(&sensor->input_lock); return ret; } @@ -559,21 +559,21 @@ static const struct v4l2_subdev_ops gc0310_ops = { .sensor = &gc0310_sensor_ops, }; -static int gc0310_init_controls(struct gc0310_device *dev) +static int gc0310_init_controls(struct gc0310_device *sensor) { - struct v4l2_ctrl_handler *hdl = &dev->ctrls.handler; + struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; v4l2_ctrl_handler_init(hdl, 2); /* Use the same lock for controls as for everything else */ - hdl->lock = &dev->input_lock; - dev->sd.ctrl_handler = hdl; + hdl->lock = &sensor->input_lock; + sensor->sd.ctrl_handler = hdl; - dev->ctrls.exposure = + sensor->ctrls.exposure = v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_EXPOSURE, 0, 4095, 1, 1023); /* 32 steps at base gain 1 + 64 half steps at base gain 2 */ - dev->ctrls.gain = + sensor->ctrls.gain = v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_GAIN, 0, 95, 1, 31); return hdl->error; @@ -582,21 +582,21 @@ static int gc0310_init_controls(struct gc0310_device *dev) static void gc0310_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); - struct gc0310_device *dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); dev_dbg(&client->dev, "gc0310_remove...\n"); v4l2_async_unregister_subdev(sd); - media_entity_cleanup(&dev->sd.entity); - v4l2_ctrl_handler_free(&dev->ctrls.handler); - mutex_destroy(&dev->input_lock); + media_entity_cleanup(&sensor->sd.entity); + v4l2_ctrl_handler_free(&sensor->ctrls.handler); + mutex_destroy(&sensor->input_lock); pm_runtime_disable(&client->dev); } static int gc0310_probe(struct i2c_client *client) { struct fwnode_handle *ep_fwnode; - struct gc0310_device *dev; + struct gc0310_device *sensor; int ret; /* @@ -609,25 +609,25 @@ static int gc0310_probe(struct i2c_client *client) fwnode_handle_put(ep_fwnode); - dev = devm_kzalloc(&client->dev, sizeof(*dev), GFP_KERNEL); - if (!dev) + sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); + if (!sensor) return -ENOMEM; - dev->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(dev->reset)) { - return dev_err_probe(&client->dev, PTR_ERR(dev->reset), + sensor->reset = devm_gpiod_get(&client->dev, "reset", GPIOD_OUT_HIGH); + if (IS_ERR(sensor->reset)) { + return dev_err_probe(&client->dev, PTR_ERR(sensor->reset), "getting reset GPIO\n"); } - dev->powerdown = devm_gpiod_get(&client->dev, "powerdown", GPIOD_OUT_HIGH); - if (IS_ERR(dev->powerdown)) { - return dev_err_probe(&client->dev, PTR_ERR(dev->powerdown), + sensor->powerdown = devm_gpiod_get(&client->dev, "powerdown", GPIOD_OUT_HIGH); + if (IS_ERR(sensor->powerdown)) { + return dev_err_probe(&client->dev, PTR_ERR(sensor->powerdown), "getting powerdown GPIO\n"); } - mutex_init(&dev->input_lock); - v4l2_i2c_subdev_init(&dev->sd, client, &gc0310_ops); - gc0310_fill_format(&dev->mode.fmt); + mutex_init(&sensor->input_lock); + v4l2_i2c_subdev_init(&sensor->sd, client, &gc0310_ops); + gc0310_fill_format(&sensor->mode.fmt); pm_runtime_set_suspended(&client->dev); pm_runtime_enable(&client->dev); @@ -640,23 +640,23 @@ static int gc0310_probe(struct i2c_client *client) return ret; } - dev->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; - dev->pad.flags = MEDIA_PAD_FL_SOURCE; - dev->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; + sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; + sensor->pad.flags = MEDIA_PAD_FL_SOURCE; + sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; - ret = gc0310_init_controls(dev); + ret = gc0310_init_controls(sensor); if (ret) { gc0310_remove(client); return ret; } - ret = media_entity_pads_init(&dev->sd.entity, 1, &dev->pad); + ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); if (ret) { gc0310_remove(client); return ret; } - ret = v4l2_async_register_subdev_sensor(&dev->sd); + ret = v4l2_async_register_subdev_sensor(&sensor->sd); if (ret) { gc0310_remove(client); return ret; @@ -668,22 +668,22 @@ static int gc0310_probe(struct i2c_client *client) static int gc0310_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct gc0310_device *gc0310_dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); - gpiod_set_value_cansleep(gc0310_dev->powerdown, 1); - gpiod_set_value_cansleep(gc0310_dev->reset, 1); + gpiod_set_value_cansleep(sensor->powerdown, 1); + gpiod_set_value_cansleep(sensor->reset, 1); return 0; } static int gc0310_resume(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct gc0310_device *gc0310_dev = to_gc0310_sensor(sd); + struct gc0310_device *sensor = to_gc0310_sensor(sd); usleep_range(10000, 15000); - gpiod_set_value_cansleep(gc0310_dev->reset, 0); + gpiod_set_value_cansleep(sensor->reset, 0); usleep_range(10000, 15000); - gpiod_set_value_cansleep(gc0310_dev->powerdown, 0); + gpiod_set_value_cansleep(sensor->powerdown, 0); return 0; } From 214b24b4cdb2decd61d2f79116fe4d4ad9a7dd4c Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 21:18:15 +0200 Subject: [PATCH 234/310] media: atomisp: gc0310: Drop unused GC0310_FOCAL_LENGTH_NUM define Drop the unused GC0310_FOCAL_LENGTH_NUM define, the focal-length is a property of the sensor-module, not of the raw sensor itself. Signed-off-by: Hans de Goede Reviewed-by: Kieran Bingham Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-3-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index cc74e90a1457..f253b4b494f2 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -25,8 +25,6 @@ #define GC0310_FPS 30 #define GC0310_SKIP_FRAMES 3 -#define GC0310_FOCAL_LENGTH_NUM 278 /* 2.78mm */ - #define GC0310_ID 0xa310 #define GC0310_RESET_RELATED 0xFE From 58eec766d28a59542cbc898e75fbe5a893a27174 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 21:18:27 +0200 Subject: [PATCH 235/310] media: atomisp: gc0310: Modify vblank value to run at 30 fps Currently the sensor is running 30.9 fps, increase vblank to have it actually run at 30.0 fps. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-4-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index f253b4b494f2..6b11f0ff6088 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -141,7 +141,7 @@ static const struct gc0310_reg gc0310_reset_register[] = { { 0x05, 0x00 }, { 0x06, 0xb2 }, /* 0x0a //HB */ { 0x07, 0x00 }, - { 0x08, 0x0c }, /* 0x89 //VB */ + { 0x08, 0x1b }, /* 0x89 //VB */ { 0x09, 0x00 }, /* row start */ { 0x0a, 0x00 }, { 0x0b, 0x00 }, /* col start */ From 2c3a35f27db887c0eb1944ab516e670da0765b42 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 21:20:16 +0200 Subject: [PATCH 236/310] media: atomisp: gc0310: Switch to CCI register access helpers Switch the GC0310 driver over to the CCI register access helpers. While at it also add a _REG prefix to all register address defines to make clear they are register addresses and group register value defines together with the address definition. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-5-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/Kconfig | 1 + .../media/atomisp/i2c/atomisp-gc0310.c | 143 +++++++----------- 2 files changed, 55 insertions(+), 89 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/Kconfig b/drivers/staging/media/atomisp/i2c/Kconfig index 4f46182da58b..ef2094c22347 100644 --- a/drivers/staging/media/atomisp/i2c/Kconfig +++ b/drivers/staging/media/atomisp/i2c/Kconfig @@ -31,6 +31,7 @@ config VIDEO_ATOMISP_GC0310 tristate "GC0310 sensor support" depends on ACPI depends on I2C && VIDEO_DEV + select V4L2_CCI_I2C help This is a Video4Linux2 sensor-level driver for the Galaxycore GC0310 0.3MP sensor. diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 6b11f0ff6088..91f95db94283 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -13,9 +13,11 @@ #include #include #include +#include #include #include +#include #include #include @@ -27,41 +29,32 @@ #define GC0310_ID 0xa310 -#define GC0310_RESET_RELATED 0xFE +#define GC0310_RESET_RELATED_REG CCI_REG8(0xfe) #define GC0310_REGISTER_PAGE_0 0x0 #define GC0310_REGISTER_PAGE_3 0x3 /* * GC0310 System control registers */ -#define GC0310_SW_STREAM 0x10 - -#define GC0310_SC_CMMN_CHIP_ID_H 0xf0 -#define GC0310_SC_CMMN_CHIP_ID_L 0xf1 - -#define GC0310_AEC_PK_EXPO_H 0x03 -#define GC0310_AEC_PK_EXPO_L 0x04 -#define GC0310_AGC_ADJ 0x48 -#define GC0310_DGC_ADJ 0x71 -#define GC0310_GROUP_ACCESS 0x3208 - -#define GC0310_H_CROP_START_H 0x09 -#define GC0310_H_CROP_START_L 0x0A -#define GC0310_V_CROP_START_H 0x0B -#define GC0310_V_CROP_START_L 0x0C -#define GC0310_H_OUTSIZE_H 0x0F -#define GC0310_H_OUTSIZE_L 0x10 -#define GC0310_V_OUTSIZE_H 0x0D -#define GC0310_V_OUTSIZE_L 0x0E -#define GC0310_H_BLANKING_H 0x05 -#define GC0310_H_BLANKING_L 0x06 -#define GC0310_V_BLANKING_H 0x07 -#define GC0310_V_BLANKING_L 0x08 -#define GC0310_SH_DELAY 0x11 +#define GC0310_SW_STREAM_REG CCI_REG8(0x10) #define GC0310_START_STREAMING 0x94 /* 8-bit enable */ #define GC0310_STOP_STREAMING 0x0 /* 8-bit disable */ +#define GC0310_SC_CMMN_CHIP_ID_REG CCI_REG16(0xf0) + +#define GC0310_AEC_PK_EXPO_REG CCI_REG16(0x03) +#define GC0310_AGC_ADJ_REG CCI_REG8(0x48) +#define GC0310_DGC_ADJ_REG CCI_REG8(0x71) + +#define GC0310_H_CROP_START_REG CCI_REG16(0x09) +#define GC0310_V_CROP_START_REG CCI_REG16(0x0b) +#define GC0310_H_OUTSIZE_REG CCI_REG16(0x0f) +#define GC0310_V_OUTSIZE_REG CCI_REG16(0x0d) +#define GC0310_H_BLANKING_REG CCI_REG16(0x05) +#define GC0310_V_BLANKING_REG CCI_REG16(0x07) +#define GC0310_SH_DELAY_REG CCI_REG8(0x11) + #define to_gc0310_sensor(x) container_of(x, struct gc0310_device, sd) struct gc0310_device { @@ -71,6 +64,7 @@ struct gc0310_device { struct mutex input_lock; bool is_streaming; + struct regmap *regmap; struct gpio_desc *reset; struct gpio_desc *powerdown; @@ -90,7 +84,7 @@ struct gc0310_reg { u8 val; }; -static const struct gc0310_reg gc0310_reset_register[] = { +static const struct reg_sequence gc0310_reset_register[] = { /* System registers */ { 0xfe, 0xf0 }, { 0xfe, 0xf0 }, @@ -233,7 +227,7 @@ static const struct gc0310_reg gc0310_reset_register[] = { { 0xfe, 0x00 }, }; -static const struct gc0310_reg gc0310_VGA_30fps[] = { +static const struct reg_sequence gc0310_VGA_30fps[] = { { 0xfe, 0x00 }, { 0x0d, 0x01 }, /* height */ { 0x0e, 0xf2 }, /* 0xf7 //height */ @@ -257,41 +251,10 @@ static const struct gc0310_reg gc0310_VGA_30fps[] = { { 0xfe, 0x00 }, }; -/* - * gc0310_write_reg_array - Initializes a list of GC0310 registers - * @client: i2c driver client structure - * @reglist: list of registers to be written - * @count: number of register, value pairs in the list - */ -static int gc0310_write_reg_array(struct i2c_client *client, - const struct gc0310_reg *reglist, int count) -{ - int i, err; - - for (i = 0; i < count; i++) { - err = i2c_smbus_write_byte_data(client, reglist[i].reg, reglist[i].val); - if (err) { - dev_err(&client->dev, "write error: wrote 0x%x to offset 0x%x error %d", - reglist[i].val, reglist[i].reg, err); - return err; - } - } - - return 0; -} - -static int gc0310_exposure_set(struct gc0310_device *sensor, u32 exp) -{ - struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); - - return i2c_smbus_write_word_swapped(client, GC0310_AEC_PK_EXPO_H, exp); -} - static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) { - struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); u8 again, dgain; - int ret; + int ret = 0; /* Taken from original driver, this never sets dgain lower then 32? */ @@ -306,11 +269,9 @@ static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) dgain = gain / 2; } - ret = i2c_smbus_write_byte_data(client, GC0310_AGC_ADJ, again); - if (ret) - return ret; - - return i2c_smbus_write_byte_data(client, GC0310_DGC_ADJ, dgain); + cci_write(sensor->regmap, GC0310_AGC_ADJ_REG, again, &ret); + cci_write(sensor->regmap, GC0310_DGC_ADJ_REG, dgain, &ret); + return ret; } static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) @@ -325,7 +286,8 @@ static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) switch (ctrl->id) { case V4L2_CID_EXPOSURE: - ret = gc0310_exposure_set(sensor, ctrl->val); + ret = cci_write(sensor->regmap, GC0310_AEC_PK_EXPO_REG, + ctrl->val, NULL); break; case V4L2_CID_GAIN: ret = gc0310_gain_set(sensor, ctrl->val); @@ -390,28 +352,30 @@ static int gc0310_get_fmt(struct v4l2_subdev *sd, return 0; } -static int gc0310_detect(struct i2c_client *client) +static int gc0310_detect(struct gc0310_device *sensor) { - struct i2c_adapter *adapter = client->adapter; + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); + u64 val; int ret; - if (!i2c_check_functionality(adapter, I2C_FUNC_I2C)) + if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; ret = pm_runtime_get_sync(&client->dev); if (ret >= 0) - ret = i2c_smbus_read_word_swapped(client, GC0310_SC_CMMN_CHIP_ID_H); + ret = cci_read(sensor->regmap, GC0310_SC_CMMN_CHIP_ID_REG, + &val, NULL); pm_runtime_put(&client->dev); if (ret < 0) { dev_err(&client->dev, "read sensor_id failed: %d\n", ret); return -ENODEV; } - dev_dbg(&client->dev, "sensor ID = 0x%x\n", ret); + dev_dbg(&client->dev, "sensor ID = 0x%llx\n", val); - if (ret != GC0310_ID) { - dev_err(&client->dev, "sensor ID error, read id = 0x%x, target id = 0x%x\n", - ret, GC0310_ID); + if (val != GC0310_ID) { + dev_err(&client->dev, "sensor ID error, read id = 0x%llx, target id = 0x%x\n", + val, GC0310_ID); return -ENODEV; } @@ -436,12 +400,14 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) msleep(100); - ret = gc0310_write_reg_array(client, gc0310_reset_register, + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_reset_register, ARRAY_SIZE(gc0310_reset_register)); if (ret) goto error_power_down; - ret = gc0310_write_reg_array(client, gc0310_VGA_30fps, + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_VGA_30fps, ARRAY_SIZE(gc0310_VGA_30fps)); if (ret) goto error_power_down; @@ -452,21 +418,16 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) goto error_power_down; /* enable per frame MIPI and sensor ctrl reset */ - ret = i2c_smbus_write_byte_data(client, 0xFE, 0x30); - if (ret) - goto error_power_down; + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, 0x30, &ret); } - ret = i2c_smbus_write_byte_data(client, GC0310_RESET_RELATED, GC0310_REGISTER_PAGE_3); - if (ret) - goto error_power_down; - - ret = i2c_smbus_write_byte_data(client, GC0310_SW_STREAM, - enable ? GC0310_START_STREAMING : GC0310_STOP_STREAMING); - if (ret) - goto error_power_down; - - ret = i2c_smbus_write_byte_data(client, GC0310_RESET_RELATED, GC0310_REGISTER_PAGE_0); + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_3, &ret); + cci_write(sensor->regmap, GC0310_SW_STREAM_REG, + enable ? GC0310_START_STREAMING : GC0310_STOP_STREAMING, + &ret); + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_0, &ret); if (ret) goto error_power_down; @@ -627,12 +588,16 @@ static int gc0310_probe(struct i2c_client *client) v4l2_i2c_subdev_init(&sensor->sd, client, &gc0310_ops); gc0310_fill_format(&sensor->mode.fmt); + sensor->regmap = devm_cci_regmap_init_i2c(client, 8); + if (IS_ERR(sensor->regmap)) + return PTR_ERR(sensor->regmap); + pm_runtime_set_suspended(&client->dev); pm_runtime_enable(&client->dev); pm_runtime_set_autosuspend_delay(&client->dev, 1000); pm_runtime_use_autosuspend(&client->dev); - ret = gc0310_detect(client); + ret = gc0310_detect(sensor); if (ret) { gc0310_remove(client); return ret; From 782101916bf23fdee62e2b317472678a3a3fb231 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Fri, 4 Jul 2025 22:54:19 +0200 Subject: [PATCH 237/310] media: atomisp: gc0310: Use V4L2_CID_ANALOGUE_GAIN for gain control Use V4L2_CID_ANALOGUE_GAIN for gain control, as expected by userspace. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20250517114106.43494-6-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 91f95db94283..1f1795fc625f 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -289,7 +289,7 @@ static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) ret = cci_write(sensor->regmap, GC0310_AEC_PK_EXPO_REG, ctrl->val, NULL); break; - case V4L2_CID_GAIN: + case V4L2_CID_ANALOGUE_GAIN: ret = gc0310_gain_set(sensor, ctrl->val); break; default: @@ -533,7 +533,7 @@ static int gc0310_init_controls(struct gc0310_device *sensor) /* 32 steps at base gain 1 + 64 half steps at base gain 2 */ sensor->ctrls.gain = - v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_GAIN, 0, 95, 1, 31); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_ANALOGUE_GAIN, 0, 95, 1, 31); return hdl->error; } From 5446808037f6a4db04210db3bf95b216f9588076 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Jul 2025 11:47:22 +0200 Subject: [PATCH 238/310] media: atomisp: gc0310: Add selection API support Add support for the selection API as expected by libcamera. Note the driver only supports a single fixed resolution and no cropping, so this is a simple read-only implementation. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-7-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 1f1795fc625f..15ca7bc82322 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -3,7 +3,7 @@ * Support for GalaxyCore GC0310 VGA camera sensor. * * Copyright (c) 2013 Intel Corporation. All Rights Reserved. - * Copyright (c) 2023 Hans de Goede + * Copyright (c) 2023-2025 Hans de Goede */ #include @@ -352,6 +352,28 @@ static int gc0310_get_fmt(struct v4l2_subdev *sd, return 0; } +static int gc0310_get_selection(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + struct v4l2_subdev_selection *sel) +{ + /* Only the single fixed 656x496 mode is supported, without croping */ + switch (sel->target) { + case V4L2_SEL_TGT_CROP: + case V4L2_SEL_TGT_CROP_BOUNDS: + case V4L2_SEL_TGT_CROP_DEFAULT: + case V4L2_SEL_TGT_NATIVE_SIZE: + sel->r.top = 0; + sel->r.left = 0; + sel->r.width = GC0310_NATIVE_WIDTH; + sel->r.height = GC0310_NATIVE_HEIGHT; + break; + default: + return -EINVAL; + } + + return 0; +} + static int gc0310_detect(struct gc0310_device *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); @@ -509,6 +531,8 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .enum_frame_size = gc0310_enum_frame_size, .get_fmt = gc0310_get_fmt, .set_fmt = gc0310_set_fmt, + .get_selection = gc0310_get_selection, + .set_selection = gc0310_get_selection, .get_frame_interval = gc0310_get_frame_interval, }; @@ -671,5 +695,6 @@ static struct i2c_driver gc0310_driver = { module_i2c_driver(gc0310_driver); MODULE_AUTHOR("Lai, Angie "); +MODULE_AUTHOR("Hans de Goede "); MODULE_DESCRIPTION("A low-level driver for GalaxyCore GC0310 sensors"); MODULE_LICENSE("GPL"); From cd3a72c6430c6edb77268a3653858908c5427e76 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Jul 2025 11:53:15 +0200 Subject: [PATCH 239/310] media: atomisp: gc0310: Add link-frequency and pixelrate controls Add support for the pixelrate control as expected by libcamera, while at it also add the link-frequency control. Signed-off-by: Hans de Goede Link: https://lore.kernel.org/r/20250517114106.43494-8-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 15ca7bc82322..cff1db5390bc 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -24,6 +24,17 @@ #define GC0310_NATIVE_WIDTH 656 #define GC0310_NATIVE_HEIGHT 496 +/* + * The actual PLL output rate is unknown, the datasheet + * says that the formula for the frame-time in pixels is: + * rowtime = win-width + hblank + sh-delay + 4 + * frametime = rowtime * (win-height + vblank) + * Filling this in and multiplying by 30 fps gives: + * pixelrate = (660 + 178 + 42 + 4) * (498 + 27) * 30 = 13923000 + */ +#define GC0310_PIXELRATE 13923000 +/* single lane, bus-format is 8 bpp, CSI-2 is double data rate */ +#define GC0310_LINK_FREQ (GC0310_PIXELRATE * 8 / 2) #define GC0310_FPS 30 #define GC0310_SKIP_FRAMES 3 @@ -76,6 +87,8 @@ struct gc0310_device { struct v4l2_ctrl_handler handler; struct v4l2_ctrl *exposure; struct v4l2_ctrl *gain; + struct v4l2_ctrl *link_freq; + struct v4l2_ctrl *pixel_rate; } ctrls; }; @@ -251,6 +264,10 @@ static const struct reg_sequence gc0310_VGA_30fps[] = { { 0xfe, 0x00 }, }; +static const s64 link_freq_menu_items[] = { + GC0310_LINK_FREQ, +}; + static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) { u8 again, dgain; @@ -546,7 +563,7 @@ static int gc0310_init_controls(struct gc0310_device *sensor) { struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; - v4l2_ctrl_handler_init(hdl, 2); + v4l2_ctrl_handler_init(hdl, 4); /* Use the same lock for controls as for everything else */ hdl->lock = &sensor->input_lock; @@ -559,7 +576,19 @@ static int gc0310_init_controls(struct gc0310_device *sensor) sensor->ctrls.gain = v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_ANALOGUE_GAIN, 0, 95, 1, 31); - return hdl->error; + sensor->ctrls.link_freq = + v4l2_ctrl_new_int_menu(hdl, NULL, V4L2_CID_LINK_FREQ, + 0, 0, link_freq_menu_items); + sensor->ctrls.pixel_rate = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_PIXEL_RATE, 0, + GC0310_PIXELRATE, 1, GC0310_PIXELRATE); + + if (hdl->error) + return hdl->error; + + sensor->ctrls.pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + return 0; } static void gc0310_remove(struct i2c_client *client) From c914e3971f3ee3d09463b8d0f7802ee83eb728f6 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Jul 2025 11:53:24 +0200 Subject: [PATCH 240/310] media: atomisp: gc0310: Add vblank and hblank controls Add support for the vblank and hblank controls, these controls are mandatory for using the sensor driver with libcamera. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-9-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index cff1db5390bc..dab736f5d4b3 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -62,9 +62,22 @@ #define GC0310_V_CROP_START_REG CCI_REG16(0x0b) #define GC0310_H_OUTSIZE_REG CCI_REG16(0x0f) #define GC0310_V_OUTSIZE_REG CCI_REG16(0x0d) + #define GC0310_H_BLANKING_REG CCI_REG16(0x05) +/* Hblank-register + sh-delay + H-crop + 4 (from hw) */ +#define GC0310_H_BLANK_DEFAULT (178 + 42 + 4 + 4) + #define GC0310_V_BLANKING_REG CCI_REG16(0x07) +/* Vblank needs an offset compensate for the small V-crop done */ +#define GC0310_V_BLANK_OFFSET 2 +/* Vsync start time + 1 row vsync + vsync end time + offset */ +#define GC0310_V_BLANK_MIN (9 + 1 + 4 + GC0310_V_BLANK_OFFSET) +#define GC0310_V_BLANK_DEFAULT (27 + GC0310_V_BLANK_OFFSET) +#define GC0310_V_BLANK_MAX (4095 - GC0310_NATIVE_HEIGHT) + #define GC0310_SH_DELAY_REG CCI_REG8(0x11) +#define GC0310_VS_START_TIME_REG CCI_REG8(0x12) +#define GC0310_VS_END_TIME_REG CCI_REG8(0x13) #define to_gc0310_sensor(x) container_of(x, struct gc0310_device, sd) @@ -89,6 +102,8 @@ struct gc0310_device { struct v4l2_ctrl *gain; struct v4l2_ctrl *link_freq; struct v4l2_ctrl *pixel_rate; + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; } ctrls; }; @@ -147,8 +162,7 @@ static const struct reg_sequence gc0310_reset_register[] = { { 0x04, 0xc0 }, /* 0xe8 //58 */ { 0x05, 0x00 }, { 0x06, 0xb2 }, /* 0x0a //HB */ - { 0x07, 0x00 }, - { 0x08, 0x1b }, /* 0x89 //VB */ + /* Vblank (reg 0x07 + 0x08) gets set by the vblank ctrl */ { 0x09, 0x00 }, /* row start */ { 0x0a, 0x00 }, { 0x0b, 0x00 }, /* col start */ @@ -309,6 +323,11 @@ static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) case V4L2_CID_ANALOGUE_GAIN: ret = gc0310_gain_set(sensor, ctrl->val); break; + case V4L2_CID_VBLANK: + ret = cci_write(sensor->regmap, GC0310_V_BLANKING_REG, + ctrl->val - GC0310_V_BLANK_OFFSET, + NULL); + break; default: ret = -EINVAL; break; @@ -563,7 +582,7 @@ static int gc0310_init_controls(struct gc0310_device *sensor) { struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; - v4l2_ctrl_handler_init(hdl, 4); + v4l2_ctrl_handler_init(hdl, 6); /* Use the same lock for controls as for everything else */ hdl->lock = &sensor->input_lock; @@ -583,11 +602,24 @@ static int gc0310_init_controls(struct gc0310_device *sensor) v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_PIXEL_RATE, 0, GC0310_PIXELRATE, 1, GC0310_PIXELRATE); + sensor->ctrls.vblank = + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_VBLANK, + GC0310_V_BLANK_MIN, + GC0310_V_BLANK_MAX, 1, + GC0310_V_BLANK_DEFAULT); + + sensor->ctrls.hblank = + v4l2_ctrl_new_std(hdl, NULL, V4L2_CID_HBLANK, + GC0310_H_BLANK_DEFAULT, + GC0310_H_BLANK_DEFAULT, 1, + GC0310_H_BLANK_DEFAULT); + if (hdl->error) return hdl->error; sensor->ctrls.pixel_rate->flags |= V4L2_CTRL_FLAG_READ_ONLY; sensor->ctrls.link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; + sensor->ctrls.hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; return 0; } From c9524e6b90824ccfb96eac2e88f377f50c6d13ce Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Jul 2025 11:53:40 +0200 Subject: [PATCH 241/310] media: atomisp: gc0310: Add camera orientation and sensor rotation controls Add camera orientation and sensor rotation controls using the v4l2_fwnode_device_parse() and v4l2_ctrl_new_fwnode_properties() helpers. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-10-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index dab736f5d4b3..d0e7f0f8452b 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -20,6 +20,7 @@ #include #include #include +#include #define GC0310_NATIVE_WIDTH 656 #define GC0310_NATIVE_HEIGHT 496 @@ -580,9 +581,12 @@ static const struct v4l2_subdev_ops gc0310_ops = { static int gc0310_init_controls(struct gc0310_device *sensor) { + struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; + struct v4l2_fwnode_device_properties props; + int ret; - v4l2_ctrl_handler_init(hdl, 6); + v4l2_ctrl_handler_init(hdl, 8); /* Use the same lock for controls as for everything else */ hdl->lock = &sensor->input_lock; @@ -614,6 +618,12 @@ static int gc0310_init_controls(struct gc0310_device *sensor) GC0310_H_BLANK_DEFAULT, 1, GC0310_H_BLANK_DEFAULT); + ret = v4l2_fwnode_device_parse(&client->dev, &props); + if (ret) + return ret; + + v4l2_ctrl_new_fwnode_properties(hdl, &ctrl_ops, &props); + if (hdl->error) return hdl->error; From bddd68a844f5a1c5ecc2f0409e7a2e7789169477 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 6 Jul 2025 11:53:53 +0200 Subject: [PATCH 242/310] media: atomisp: gc0310: Limit max exposure value to mode-height + vblank When an exposure value > (mode-height + vblank) gets set the sensor will automatically increase vblank, lowering the framerate. This is not desirable, limit exposure the maximum exposure to mode-height + vblank to avoid the unwanted framerate slowdown. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-11-hdegoede@redhat.com Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index d0e7f0f8452b..3e94414be6c7 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -306,12 +306,27 @@ static int gc0310_gain_set(struct gc0310_device *sensor, u32 gain) return ret; } +static int gc0310_exposure_update_range(struct gc0310_device *sensor) +{ + int exp_max = GC0310_NATIVE_HEIGHT + sensor->ctrls.vblank->val; + + return __v4l2_ctrl_modify_range(sensor->ctrls.exposure, 0, exp_max, + 1, exp_max); +} + static int gc0310_s_ctrl(struct v4l2_ctrl *ctrl) { struct gc0310_device *sensor = container_of(ctrl->handler, struct gc0310_device, ctrls.handler); int ret; + /* Update exposure range on vblank changes */ + if (ctrl->id == V4L2_CID_VBLANK) { + ret = gc0310_exposure_update_range(sensor); + if (ret) + return ret; + } + /* Only apply changes to the controls if the device is powered up */ if (!pm_runtime_get_if_in_use(sensor->sd.dev)) return 0; @@ -584,7 +599,7 @@ static int gc0310_init_controls(struct gc0310_device *sensor) struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); struct v4l2_ctrl_handler *hdl = &sensor->ctrls.handler; struct v4l2_fwnode_device_properties props; - int ret; + int exp_max, ret; v4l2_ctrl_handler_init(hdl, 8); @@ -592,8 +607,10 @@ static int gc0310_init_controls(struct gc0310_device *sensor) hdl->lock = &sensor->input_lock; sensor->sd.ctrl_handler = hdl; + exp_max = GC0310_NATIVE_HEIGHT + GC0310_V_BLANK_DEFAULT; sensor->ctrls.exposure = - v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_EXPOSURE, 0, 4095, 1, 1023); + v4l2_ctrl_new_std(hdl, &ctrl_ops, V4L2_CID_EXPOSURE, 0, + exp_max, 1, exp_max); /* 32 steps at base gain 1 + 64 half steps at base gain 2 */ sensor->ctrls.gain = From b9ea99652e973ebdcde855f10c85f690da86cd8d Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:54 +0200 Subject: [PATCH 243/310] media: atomisp: gc0310: Add check_hwcfg() function Add a check_hwcfg() function to check if the external clk-freq, CSI link-freq and lane-count match the driver's expectations. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-12-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 56 +++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 3e94414be6c7..f3dbc3583d97 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -36,6 +36,7 @@ #define GC0310_PIXELRATE 13923000 /* single lane, bus-format is 8 bpp, CSI-2 is double data rate */ #define GC0310_LINK_FREQ (GC0310_PIXELRATE * 8 / 2) +#define GC0310_MCLK_FREQ 19200000 #define GC0310_FPS 30 #define GC0310_SKIP_FRAMES 3 @@ -664,21 +665,68 @@ static void gc0310_remove(struct i2c_client *client) pm_runtime_disable(&client->dev); } -static int gc0310_probe(struct i2c_client *client) +static int gc0310_check_hwcfg(struct device *dev) { + struct v4l2_fwnode_endpoint bus_cfg = { + .bus_type = V4L2_MBUS_CSI2_DPHY, + }; struct fwnode_handle *ep_fwnode; - struct gc0310_device *sensor; + unsigned long link_freq_bitmap; + u32 mclk; int ret; /* * Sometimes the fwnode graph is initialized by the bridge driver. * Bridge drivers doing this may also add GPIO mappings, wait for this. */ - ep_fwnode = fwnode_graph_get_next_endpoint(dev_fwnode(&client->dev), NULL); + ep_fwnode = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), 0, 0, 0); if (!ep_fwnode) - return dev_err_probe(&client->dev, -EPROBE_DEFER, "waiting for fwnode graph endpoint\n"); + return dev_err_probe(dev, -EPROBE_DEFER, + "waiting for fwnode graph endpoint\n"); + ret = fwnode_property_read_u32(dev_fwnode(dev), "clock-frequency", + &mclk); + if (ret) { + fwnode_handle_put(ep_fwnode); + return dev_err_probe(dev, ret, + "reading clock-frequency property\n"); + } + + if (mclk != GC0310_MCLK_FREQ) { + fwnode_handle_put(ep_fwnode); + return dev_err_probe(dev, -EINVAL, + "external clock %u is not supported\n", + mclk); + } + + ret = v4l2_fwnode_endpoint_alloc_parse(ep_fwnode, &bus_cfg); fwnode_handle_put(ep_fwnode); + if (ret) + return dev_err_probe(dev, ret, "parsing endpoint failed\n"); + + ret = v4l2_link_freq_to_bitmap(dev, bus_cfg.link_frequencies, + bus_cfg.nr_of_link_frequencies, + link_freq_menu_items, + ARRAY_SIZE(link_freq_menu_items), + &link_freq_bitmap); + + if (ret == 0 && bus_cfg.bus.mipi_csi2.num_data_lanes != 1) + ret = dev_err_probe(dev, -EINVAL, + "number of CSI2 data lanes %u is not supported\n", + bus_cfg.bus.mipi_csi2.num_data_lanes); + + v4l2_fwnode_endpoint_free(&bus_cfg); + return ret; +} + +static int gc0310_probe(struct i2c_client *client) +{ + struct gc0310_device *sensor; + int ret; + + ret = gc0310_check_hwcfg(&client->dev); + if (ret) + return ret; sensor = devm_kzalloc(&client->dev, sizeof(*sensor), GFP_KERNEL); if (!sensor) From f934362be2165ca22c3fb460bf5ae9468e5187f5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:55 +0200 Subject: [PATCH 244/310] media: atomisp: gc0310: Fix power on/off sleep times Reduce the unnecessary long msleep(100) done on stream start to 10 ms and move this to gc0310_resume() so that it is also done on the initial power-up done by gc0310_detect(), which should fix gc0310_detect() sometimes failing. While at it switch the sleeps from msleep() / usleep_range() to fsleep(). Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-13-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index f3dbc3583d97..c89b3e9e3343 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -473,8 +473,6 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) if (ret < 0) goto error_power_down; - msleep(100); - ret = regmap_multi_reg_write(sensor->regmap, gc0310_reset_register, ARRAY_SIZE(gc0310_reset_register)); @@ -803,10 +801,11 @@ static int gc0310_resume(struct device *dev) struct v4l2_subdev *sd = dev_get_drvdata(dev); struct gc0310_device *sensor = to_gc0310_sensor(sd); - usleep_range(10000, 15000); + fsleep(10 * USEC_PER_MSEC); gpiod_set_value_cansleep(sensor->reset, 0); - usleep_range(10000, 15000); + fsleep(10 * USEC_PER_MSEC); gpiod_set_value_cansleep(sensor->powerdown, 0); + fsleep(10 * USEC_PER_MSEC); return 0; } From 9a21a940681c0ba22d7e9e341a4ab560939fce4b Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:56 +0200 Subject: [PATCH 245/310] media: atomisp: gc0310: Remove unused is_streaming variable is_streaming is only set and never read, drop it. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-14-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index c89b3e9e3343..c6a0bc746d32 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -88,7 +88,6 @@ struct gc0310_device { struct media_pad pad; /* Protect against concurrent changes to controls */ struct mutex input_lock; - bool is_streaming; struct regmap *regmap; struct gpio_desc *reset; @@ -507,13 +506,11 @@ static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) if (!enable) pm_runtime_put(&client->dev); - sensor->is_streaming = enable; mutex_unlock(&sensor->input_lock); return 0; error_power_down: pm_runtime_put(&client->dev); - sensor->is_streaming = false; mutex_unlock(&sensor->input_lock); return ret; } From 889740337f29c12f7eb32c281869654cfe3e32b5 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:57 +0200 Subject: [PATCH 246/310] media: atomisp: gc0310: Switch to {enable,disable}_streams Switch from s_stream() to enable_streams() and disable_streams() pad operations. They are preferred and required for streams support. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-15-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index c6a0bc746d32..2f1f5022e2ac 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -458,58 +458,71 @@ static int gc0310_detect(struct gc0310_device *sensor) return 0; } -static int gc0310_s_stream(struct v4l2_subdev *sd, int enable) +static int gc0310_enable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) +{ + struct gc0310_device *sensor = to_gc0310_sensor(sd); + struct i2c_client *client = v4l2_get_subdevdata(sd); + int ret; + + mutex_lock(&sensor->input_lock); + + ret = pm_runtime_resume_and_get(&client->dev); + if (ret) + goto error_unlock; + + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_reset_register, + ARRAY_SIZE(gc0310_reset_register)); + if (ret) + goto error_power_down; + + ret = regmap_multi_reg_write(sensor->regmap, + gc0310_VGA_30fps, + ARRAY_SIZE(gc0310_VGA_30fps)); + if (ret) + goto error_power_down; + + /* restore value of all ctrls */ + ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); + + /* enable per frame MIPI and sensor ctrl reset */ + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, 0x30, &ret); + + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_3, &ret); + cci_write(sensor->regmap, GC0310_SW_STREAM_REG, + GC0310_START_STREAMING, &ret); + cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, + GC0310_REGISTER_PAGE_0, &ret); + +error_power_down: + if (ret) + pm_runtime_put(&client->dev); + +error_unlock: + mutex_unlock(&sensor->input_lock); + return ret; +} + +static int gc0310_disable_streams(struct v4l2_subdev *sd, + struct v4l2_subdev_state *state, + u32 pad, u64 streams_mask) { struct gc0310_device *sensor = to_gc0310_sensor(sd); struct i2c_client *client = v4l2_get_subdevdata(sd); int ret = 0; - dev_dbg(&client->dev, "%s S enable=%d\n", __func__, enable); mutex_lock(&sensor->input_lock); - if (enable) { - ret = pm_runtime_get_sync(&client->dev); - if (ret < 0) - goto error_power_down; - - ret = regmap_multi_reg_write(sensor->regmap, - gc0310_reset_register, - ARRAY_SIZE(gc0310_reset_register)); - if (ret) - goto error_power_down; - - ret = regmap_multi_reg_write(sensor->regmap, - gc0310_VGA_30fps, - ARRAY_SIZE(gc0310_VGA_30fps)); - if (ret) - goto error_power_down; - - /* restore value of all ctrls */ - ret = __v4l2_ctrl_handler_setup(&sensor->ctrls.handler); - if (ret) - goto error_power_down; - - /* enable per frame MIPI and sensor ctrl reset */ - cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, 0x30, &ret); - } - cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, GC0310_REGISTER_PAGE_3, &ret); cci_write(sensor->regmap, GC0310_SW_STREAM_REG, - enable ? GC0310_START_STREAMING : GC0310_STOP_STREAMING, - &ret); + GC0310_STOP_STREAMING, &ret); cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, GC0310_REGISTER_PAGE_0, &ret); - if (ret) - goto error_power_down; - if (!enable) - pm_runtime_put(&client->dev); - - mutex_unlock(&sensor->input_lock); - return 0; - -error_power_down: pm_runtime_put(&client->dev); mutex_unlock(&sensor->input_lock); return ret; @@ -571,7 +584,7 @@ static const struct v4l2_subdev_sensor_ops gc0310_sensor_ops = { }; static const struct v4l2_subdev_video_ops gc0310_video_ops = { - .s_stream = gc0310_s_stream, + .s_stream = v4l2_subdev_s_stream_helper, }; static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { @@ -582,6 +595,8 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, .get_frame_interval = gc0310_get_frame_interval, + .enable_streams = gc0310_enable_streams, + .disable_streams = gc0310_disable_streams, }; static const struct v4l2_subdev_ops gc0310_ops = { From 1e29696182f68b88cf2e459cc928eecc5770b459 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:58 +0200 Subject: [PATCH 247/310] media: atomisp: gc0310: Switch to using the sub-device state lock Switch to using the sub-device state lock and properly call v4l2_subdev_init_finalize() / v4l2_subdev_cleanup() on probe() / remove(). Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-16-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 22 ++++++++----------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 2f1f5022e2ac..baf9355ade4d 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -86,8 +86,6 @@ struct gc0310_device { struct v4l2_subdev sd; struct media_pad pad; - /* Protect against concurrent changes to controls */ - struct mutex input_lock; struct regmap *regmap; struct gpio_desc *reset; @@ -466,11 +464,9 @@ static int gc0310_enable_streams(struct v4l2_subdev *sd, struct i2c_client *client = v4l2_get_subdevdata(sd); int ret; - mutex_lock(&sensor->input_lock); - ret = pm_runtime_resume_and_get(&client->dev); if (ret) - goto error_unlock; + return ret; ret = regmap_multi_reg_write(sensor->regmap, gc0310_reset_register, @@ -501,8 +497,6 @@ static int gc0310_enable_streams(struct v4l2_subdev *sd, if (ret) pm_runtime_put(&client->dev); -error_unlock: - mutex_unlock(&sensor->input_lock); return ret; } @@ -514,8 +508,6 @@ static int gc0310_disable_streams(struct v4l2_subdev *sd, struct i2c_client *client = v4l2_get_subdevdata(sd); int ret = 0; - mutex_lock(&sensor->input_lock); - cci_write(sensor->regmap, GC0310_RESET_RELATED_REG, GC0310_REGISTER_PAGE_3, &ret); cci_write(sensor->regmap, GC0310_SW_STREAM_REG, @@ -524,7 +516,6 @@ static int gc0310_disable_streams(struct v4l2_subdev *sd, GC0310_REGISTER_PAGE_0, &ret); pm_runtime_put(&client->dev); - mutex_unlock(&sensor->input_lock); return ret; } @@ -615,7 +606,6 @@ static int gc0310_init_controls(struct gc0310_device *sensor) v4l2_ctrl_handler_init(hdl, 8); /* Use the same lock for controls as for everything else */ - hdl->lock = &sensor->input_lock; sensor->sd.ctrl_handler = hdl; exp_max = GC0310_NATIVE_HEIGHT + GC0310_V_BLANK_DEFAULT; @@ -669,9 +659,9 @@ static void gc0310_remove(struct i2c_client *client) dev_dbg(&client->dev, "gc0310_remove...\n"); v4l2_async_unregister_subdev(sd); + v4l2_subdev_cleanup(sd); media_entity_cleanup(&sensor->sd.entity); v4l2_ctrl_handler_free(&sensor->ctrls.handler); - mutex_destroy(&sensor->input_lock); pm_runtime_disable(&client->dev); } @@ -754,7 +744,6 @@ static int gc0310_probe(struct i2c_client *client) "getting powerdown GPIO\n"); } - mutex_init(&sensor->input_lock); v4l2_i2c_subdev_init(&sensor->sd, client, &gc0310_ops); gc0310_fill_format(&sensor->mode.fmt); @@ -789,6 +778,13 @@ static int gc0310_probe(struct i2c_client *client) return ret; } + sensor->sd.state_lock = sensor->ctrls.handler.lock; + ret = v4l2_subdev_init_finalize(&sensor->sd); + if (ret) { + gc0310_remove(client); + return ret; + } + ret = v4l2_async_register_subdev_sensor(&sensor->sd); if (ret) { gc0310_remove(client); From 01027ffed991f8e4f05e4d73cd91f17681d1bf51 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:40:59 +0200 Subject: [PATCH 248/310] media: atomisp: gc0310: Implement internal_ops.init_state Implement internal_ops.init_state to fill in the v4l2_mbus_framefmt struct in newly allocated sd-state structs. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-17-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index baf9355ade4d..e0a031574a84 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -596,6 +596,17 @@ static const struct v4l2_subdev_ops gc0310_ops = { .sensor = &gc0310_sensor_ops, }; +static int gc0310_init_state(struct v4l2_subdev *sd, + struct v4l2_subdev_state *sd_state) +{ + gc0310_fill_format(v4l2_subdev_state_get_format(sd_state, 0)); + return 0; +} + +static const struct v4l2_subdev_internal_ops gc0310_internal_ops = { + .init_state = gc0310_init_state, +}; + static int gc0310_init_controls(struct gc0310_device *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); @@ -762,6 +773,7 @@ static int gc0310_probe(struct i2c_client *client) return ret; } + sensor->sd.internal_ops = &gc0310_internal_ops; sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; sensor->pad.flags = MEDIA_PAD_FL_SOURCE; sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; From e0e182ad80e90149d6d73e709d004a640bdce3ae Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:00 +0200 Subject: [PATCH 249/310] media: atomisp: gc0310: Use v4l2_subdev_get_fmt() as v4l2_subdev_pad_ops.get_fmt() Now that the sd-state's fmt is properly initialized by internal_ops.init_state(), the driver can be safely switched to v4l2_subdev_get_fmt(). Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-18-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index e0a031574a84..f4b302d5eead 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -390,18 +390,6 @@ static int gc0310_set_fmt(struct v4l2_subdev *sd, return 0; } -static int gc0310_get_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *format) -{ - struct gc0310_device *sensor = to_gc0310_sensor(sd); - struct v4l2_mbus_framefmt *fmt; - - fmt = gc0310_get_pad_format(sensor, sd_state, format->pad, format->which); - format->format = *fmt; - return 0; -} - static int gc0310_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) @@ -581,7 +569,7 @@ static const struct v4l2_subdev_video_ops gc0310_video_ops = { static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .enum_mbus_code = gc0310_enum_mbus_code, .enum_frame_size = gc0310_enum_frame_size, - .get_fmt = gc0310_get_fmt, + .get_fmt = v4l2_subdev_get_fmt, .set_fmt = gc0310_set_fmt, .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, From 0f8b9632fa87eab457171198e1de8430536cace4 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:01 +0200 Subject: [PATCH 250/310] media: atomisp: gc0310: Switch to using sd.active_state fmt Stop having a v4l2_mbus_framefmt mode.fmt driver-data member to store the fmt for the active-state, instead use sd.active_state fmt. This also removes the need for gc0310_get_pad_format() since v4l2_subdev_state_get_format() now will return the correct v4l2_mbus_framefmt for all whence values. Instead of switching gc0310_set_fmt() from gc0310_get_pad_format() to v4l2_subdev_state_get_format() just drop it entirely since there is only 1 fixed mode. Otherwise the new gc0310_set_fmt() would be 100% the same as v4l2_subdev_get_fmt() after this. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-19-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 32 +------------------ 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index f4b302d5eead..44e834c608f4 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -91,10 +91,6 @@ struct gc0310_device { struct gpio_desc *reset; struct gpio_desc *powerdown; - struct gc0310_mode { - struct v4l2_mbus_framefmt fmt; - } mode; - struct gc0310_ctrls { struct v4l2_ctrl_handler handler; struct v4l2_ctrl *exposure; @@ -355,17 +351,6 @@ static const struct v4l2_ctrl_ops ctrl_ops = { .s_ctrl = gc0310_s_ctrl, }; -static struct v4l2_mbus_framefmt * -gc0310_get_pad_format(struct gc0310_device *sensor, - struct v4l2_subdev_state *state, - unsigned int pad, enum v4l2_subdev_format_whence which) -{ - if (which == V4L2_SUBDEV_FORMAT_TRY) - return v4l2_subdev_state_get_format(state, pad); - - return &sensor->mode.fmt; -} - /* The GC0310 currently only supports 1 fixed fmt */ static void gc0310_fill_format(struct v4l2_mbus_framefmt *fmt) { @@ -376,20 +361,6 @@ static void gc0310_fill_format(struct v4l2_mbus_framefmt *fmt) fmt->code = MEDIA_BUS_FMT_SGRBG8_1X8; } -static int gc0310_set_fmt(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_format *format) -{ - struct gc0310_device *sensor = to_gc0310_sensor(sd); - struct v4l2_mbus_framefmt *fmt; - - fmt = gc0310_get_pad_format(sensor, sd_state, format->pad, format->which); - gc0310_fill_format(fmt); - - format->format = *fmt; - return 0; -} - static int gc0310_get_selection(struct v4l2_subdev *sd, struct v4l2_subdev_state *state, struct v4l2_subdev_selection *sel) @@ -570,7 +541,7 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .enum_mbus_code = gc0310_enum_mbus_code, .enum_frame_size = gc0310_enum_frame_size, .get_fmt = v4l2_subdev_get_fmt, - .set_fmt = gc0310_set_fmt, + .set_fmt = v4l2_subdev_get_fmt, /* Only 1 fixed mode supported */ .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, .get_frame_interval = gc0310_get_frame_interval, @@ -744,7 +715,6 @@ static int gc0310_probe(struct i2c_client *client) } v4l2_i2c_subdev_init(&sensor->sd, client, &gc0310_ops); - gc0310_fill_format(&sensor->mode.fmt); sensor->regmap = devm_cci_regmap_init_i2c(client, 8); if (IS_ERR(sensor->regmap)) From 4d697daae8d82201f57c013c745b734cf5f8fd04 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:02 +0200 Subject: [PATCH 251/310] media: atomisp: gc0310: Move and rename suspend/resume functions Move the suspend()/resume() functions to above gc0310_detect() and rename the functions to power_off()/power_on(). No functional changes, this is a preparation patch for reworking the runtime-pm handling in probe() and remove(). Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-20-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 51 ++++++++++--------- 1 file changed, 26 insertions(+), 25 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 44e834c608f4..c4d85d65737c 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -383,6 +383,30 @@ static int gc0310_get_selection(struct v4l2_subdev *sd, return 0; } +static int gc0310_power_off(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc0310_device *sensor = to_gc0310_sensor(sd); + + gpiod_set_value_cansleep(sensor->powerdown, 1); + gpiod_set_value_cansleep(sensor->reset, 1); + return 0; +} + +static int gc0310_power_on(struct device *dev) +{ + struct v4l2_subdev *sd = dev_get_drvdata(dev); + struct gc0310_device *sensor = to_gc0310_sensor(sd); + + fsleep(10 * USEC_PER_MSEC); + gpiod_set_value_cansleep(sensor->reset, 0); + fsleep(10 * USEC_PER_MSEC); + gpiod_set_value_cansleep(sensor->powerdown, 0); + fsleep(10 * USEC_PER_MSEC); + + return 0; +} + static int gc0310_detect(struct gc0310_device *sensor) { struct i2c_client *client = v4l2_get_subdevdata(&sensor->sd); @@ -764,31 +788,8 @@ static int gc0310_probe(struct i2c_client *client) return 0; } -static int gc0310_suspend(struct device *dev) -{ - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct gc0310_device *sensor = to_gc0310_sensor(sd); - - gpiod_set_value_cansleep(sensor->powerdown, 1); - gpiod_set_value_cansleep(sensor->reset, 1); - return 0; -} - -static int gc0310_resume(struct device *dev) -{ - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct gc0310_device *sensor = to_gc0310_sensor(sd); - - fsleep(10 * USEC_PER_MSEC); - gpiod_set_value_cansleep(sensor->reset, 0); - fsleep(10 * USEC_PER_MSEC); - gpiod_set_value_cansleep(sensor->powerdown, 0); - fsleep(10 * USEC_PER_MSEC); - - return 0; -} - -static DEFINE_RUNTIME_DEV_PM_OPS(gc0310_pm_ops, gc0310_suspend, gc0310_resume, NULL); +static DEFINE_RUNTIME_DEV_PM_OPS(gc0310_pm_ops, + gc0310_power_off, gc0310_power_on, NULL); static const struct acpi_device_id gc0310_acpi_match[] = { {"INT0310"}, From 4aaa74642d8c808f9602e4b9d276ea4d32c34515 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:03 +0200 Subject: [PATCH 252/310] media: atomisp: gc0310: runtime-PM fixes The i2c-client's power-domain has already been powered up when probe() gets called. So e.g. ACPI resources for regulators and clks have already been enabled and only the GPIOs need to be set to the on state. Instead of calling pm_runtime_set_suspended() while the domain is already powered up and then have detect() do a pm_runtime_get() to set the GPIOs do the following: 1. Call gc0310_power_on() to only set the GPIOs 2. Set the device's runtime-PM state to active instead of suspended 3. Avoid the device getting suspended as soon as pm_runtime_enable() gets called by calling pm_runtime_get() before _enable(), this means moving the pm_runtime_get() / _put() from detect() to probe () This fixes power_on() not getting called when runtime-PM is not enabled in the Kconfig and this keeps the sensor powered-up while registering it avoiding unnecessary power cycles. Also modify gc0310_remove() to power-off the device if it is in active state when gc0310_remove() runs. Signed-off-by: Hans de Goede Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-21-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/i2c/atomisp-gc0310.c | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index c4d85d65737c..173ddf41ad47 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -416,11 +416,7 @@ static int gc0310_detect(struct gc0310_device *sensor) if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) return -ENODEV; - ret = pm_runtime_get_sync(&client->dev); - if (ret >= 0) - ret = cci_read(sensor->regmap, GC0310_SC_CMMN_CHIP_ID_REG, - &val, NULL); - pm_runtime_put(&client->dev); + ret = cci_read(sensor->regmap, GC0310_SC_CMMN_CHIP_ID_REG, &val, NULL); if (ret < 0) { dev_err(&client->dev, "read sensor_id failed: %d\n", ret); return -ENODEV; @@ -650,13 +646,15 @@ static void gc0310_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct gc0310_device *sensor = to_gc0310_sensor(sd); - dev_dbg(&client->dev, "gc0310_remove...\n"); - v4l2_async_unregister_subdev(sd); v4l2_subdev_cleanup(sd); media_entity_cleanup(&sensor->sd.entity); v4l2_ctrl_handler_free(&sensor->ctrls.handler); pm_runtime_disable(&client->dev); + if (!pm_runtime_status_suspended(&client->dev)) { + gc0310_power_off(&client->dev); + pm_runtime_set_suspended(&client->dev); + } } static int gc0310_check_hwcfg(struct device *dev) @@ -744,16 +742,15 @@ static int gc0310_probe(struct i2c_client *client) if (IS_ERR(sensor->regmap)) return PTR_ERR(sensor->regmap); - pm_runtime_set_suspended(&client->dev); + gc0310_power_on(&client->dev); + + pm_runtime_set_active(&client->dev); + pm_runtime_get_noresume(&client->dev); pm_runtime_enable(&client->dev); - pm_runtime_set_autosuspend_delay(&client->dev, 1000); - pm_runtime_use_autosuspend(&client->dev); ret = gc0310_detect(sensor); - if (ret) { - gc0310_remove(client); - return ret; - } + if (ret) + goto err_power_down; sensor->sd.internal_ops = &gc0310_internal_ops; sensor->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; @@ -761,31 +758,32 @@ static int gc0310_probe(struct i2c_client *client) sensor->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ret = gc0310_init_controls(sensor); - if (ret) { - gc0310_remove(client); - return ret; - } + if (ret) + goto err_power_down; ret = media_entity_pads_init(&sensor->sd.entity, 1, &sensor->pad); - if (ret) { - gc0310_remove(client); - return ret; - } + if (ret) + goto err_power_down; sensor->sd.state_lock = sensor->ctrls.handler.lock; ret = v4l2_subdev_init_finalize(&sensor->sd); - if (ret) { - gc0310_remove(client); - return ret; - } + if (ret) + goto err_power_down; ret = v4l2_async_register_subdev_sensor(&sensor->sd); - if (ret) { - gc0310_remove(client); - return ret; - } + if (ret) + goto err_power_down; + + pm_runtime_set_autosuspend_delay(&client->dev, 1000); + pm_runtime_use_autosuspend(&client->dev); + pm_runtime_put_autosuspend(&client->dev); return 0; + +err_power_down: + pm_runtime_put_noidle(&client->dev); + gc0310_remove(client); + return ret; } static DEFINE_RUNTIME_DEV_PM_OPS(gc0310_pm_ops, From 3c30c8948d8143d1ed38234b3614e81229a7f6b3 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:04 +0200 Subject: [PATCH 253/310] media: atomisp: gc0310: Drop gc0310_get_frame_interval() On raw camera sensors the framerate is controlled through vblank (and optional) hblank controls. Having a get_frame_interval makes no sense in this case, drop it. Signed-off-by: Hans de Goede Reviewed-by: Kieran Bingham Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-22-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- .../staging/media/atomisp/i2c/atomisp-gc0310.c | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 173ddf41ad47..956d3213d198 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -498,23 +498,6 @@ static int gc0310_disable_streams(struct v4l2_subdev *sd, return ret; } -static int gc0310_get_frame_interval(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_frame_interval *interval) -{ - /* - * FIXME: Implement support for V4L2_SUBDEV_FORMAT_TRY, using the V4L2 - * subdev active state API. - */ - if (interval->which != V4L2_SUBDEV_FORMAT_ACTIVE) - return -EINVAL; - - interval->interval.numerator = 1; - interval->interval.denominator = GC0310_FPS; - - return 0; -} - static int gc0310_enum_mbus_code(struct v4l2_subdev *sd, struct v4l2_subdev_state *sd_state, struct v4l2_subdev_mbus_code_enum *code) @@ -564,7 +547,6 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { .set_fmt = v4l2_subdev_get_fmt, /* Only 1 fixed mode supported */ .get_selection = gc0310_get_selection, .set_selection = gc0310_get_selection, - .get_frame_interval = gc0310_get_frame_interval, .enable_streams = gc0310_enable_streams, .disable_streams = gc0310_disable_streams, }; From 4ea35dbdaf5f914ce78b5197ae2e3dcee58c1280 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 17 May 2025 13:41:05 +0200 Subject: [PATCH 254/310] media: atomisp: gc0310: Drop gc0310_g_skip_frames() The g_skip_frames sensor-op is obsolete, drop it. Signed-off-by: Hans de Goede Reviewed-by: Kieran Bingham Reviewed-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250517114106.43494-23-hansg@kernel.org Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc0310.c | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c index 956d3213d198..7af4d66f42a0 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc0310.c @@ -526,16 +526,6 @@ static int gc0310_enum_frame_size(struct v4l2_subdev *sd, return 0; } -static int gc0310_g_skip_frames(struct v4l2_subdev *sd, u32 *frames) -{ - *frames = GC0310_SKIP_FRAMES; - return 0; -} - -static const struct v4l2_subdev_sensor_ops gc0310_sensor_ops = { - .g_skip_frames = gc0310_g_skip_frames, -}; - static const struct v4l2_subdev_video_ops gc0310_video_ops = { .s_stream = v4l2_subdev_s_stream_helper, }; @@ -554,7 +544,6 @@ static const struct v4l2_subdev_pad_ops gc0310_pad_ops = { static const struct v4l2_subdev_ops gc0310_ops = { .video = &gc0310_video_ops, .pad = &gc0310_pad_ops, - .sensor = &gc0310_sensor_ops, }; static int gc0310_init_state(struct v4l2_subdev *sd, From 11d3d8dd0252c15eae380760439403d3b333ba5f Mon Sep 17 00:00:00 2001 From: Thomas Andreatta Date: Thu, 19 Jun 2025 10:44:20 +0200 Subject: [PATCH 255/310] media: atomisp: gc0310: Remove redundant debug message Checkpatch fix: deleted `dev_dbg()` printing the name of the function. ftrace can be used instead. Signed-off-by: Thomas Andreatta Link: https://lore.kernel.org/r/20250619084420.146151-2-thomas.andreatta2000@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/atomisp-gc2235.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c index 857d7175942c..6fc39ab95e46 100644 --- a/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c +++ b/drivers/staging/media/atomisp/i2c/atomisp-gc2235.c @@ -779,8 +779,6 @@ static void gc2235_remove(struct i2c_client *client) struct v4l2_subdev *sd = i2c_get_clientdata(client); struct gc2235_device *dev = to_gc2235_sensor(sd); - dev_dbg(&client->dev, "gc2235_remove...\n"); - dev->platform_data->csi_cfg(sd, 0); v4l2_device_unregister_subdev(sd); From 51c19651a7dc74a255041acc0b1c91a197088f0e Mon Sep 17 00:00:00 2001 From: Thomas Andreatta Date: Thu, 19 Jun 2025 10:44:22 +0200 Subject: [PATCH 256/310] media: atomisp: gc2235: Fix struct definition style Reorder const qualifier in array declaration. Signed-off-by: Thomas Andreatta Link: https://lore.kernel.org/r/20250619084420.146151-3-thomas.andreatta2000@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/gc2235.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/gc2235.h b/drivers/staging/media/atomisp/i2c/gc2235.h index 6c743a17f198..7dd9a676fb98 100644 --- a/drivers/staging/media/atomisp/i2c/gc2235.h +++ b/drivers/staging/media/atomisp/i2c/gc2235.h @@ -179,21 +179,21 @@ struct gc2235_write_ctrl { struct gc2235_write_buffer buffer; }; -static struct gc2235_reg const gc2235_stream_on[] = { +static const struct gc2235_reg gc2235_stream_on[] = { { GC2235_8BIT, 0xfe, 0x03}, /* switch to P3 */ { GC2235_8BIT, 0x10, 0x91}, /* start mipi */ { GC2235_8BIT, 0xfe, 0x00}, /* switch to P0 */ { GC2235_TOK_TERM, 0, 0 } }; -static struct gc2235_reg const gc2235_stream_off[] = { +static const struct gc2235_reg gc2235_stream_off[] = { { GC2235_8BIT, 0xfe, 0x03}, /* switch to P3 */ { GC2235_8BIT, 0x10, 0x01}, /* stop mipi */ { GC2235_8BIT, 0xfe, 0x00}, /* switch to P0 */ { GC2235_TOK_TERM, 0, 0 } }; -static struct gc2235_reg const gc2235_init_settings[] = { +static const struct gc2235_reg gc2235_init_settings[] = { /* System */ { GC2235_8BIT, 0xfe, 0x80 }, { GC2235_8BIT, 0xfe, 0x80 }, @@ -268,7 +268,7 @@ static struct gc2235_reg const gc2235_init_settings[] = { * Register settings for various resolution */ #if ENABLE_NON_PREVIEW -static struct gc2235_reg const gc2235_1296_736_30fps[] = { +static const struct gc2235_reg gc2235_1296_736_30fps[] = { { GC2235_8BIT, 0x8b, 0xa0 }, { GC2235_8BIT, 0x8c, 0x02 }, @@ -321,7 +321,7 @@ static struct gc2235_reg const gc2235_1296_736_30fps[] = { { GC2235_TOK_TERM, 0, 0 } }; -static struct gc2235_reg const gc2235_960_640_30fps[] = { +static const struct gc2235_reg gc2235_960_640_30fps[] = { { GC2235_8BIT, 0x8b, 0xa0 }, { GC2235_8BIT, 0x8c, 0x02 }, @@ -373,7 +373,7 @@ static struct gc2235_reg const gc2235_960_640_30fps[] = { }; #endif -static struct gc2235_reg const gc2235_1600_900_30fps[] = { +static const struct gc2235_reg gc2235_1600_900_30fps[] = { { GC2235_8BIT, 0x8b, 0xa0 }, { GC2235_8BIT, 0x8c, 0x02 }, @@ -418,7 +418,7 @@ static struct gc2235_reg const gc2235_1600_900_30fps[] = { { GC2235_TOK_TERM, 0, 0 } }; -static struct gc2235_reg const gc2235_1616_1082_30fps[] = { +static const struct gc2235_reg gc2235_1616_1082_30fps[] = { { GC2235_8BIT, 0x8b, 0xa0 }, { GC2235_8BIT, 0x8c, 0x02 }, @@ -463,7 +463,7 @@ static struct gc2235_reg const gc2235_1616_1082_30fps[] = { { GC2235_TOK_TERM, 0, 0 } }; -static struct gc2235_reg const gc2235_1616_1216_30fps[] = { +static const struct gc2235_reg gc2235_1616_1216_30fps[] = { { GC2235_8BIT, 0x8b, 0xa0 }, { GC2235_8BIT, 0x8c, 0x02 }, From 3c92c08cd413ef87ae68785cd09a2a9f940521fc Mon Sep 17 00:00:00 2001 From: Thomas Andreatta Date: Thu, 19 Jun 2025 10:44:24 +0200 Subject: [PATCH 257/310] media: atomisp: ov2722: Fix struct definition style Reorder const qualifier in array declaration. Signed-off-by: Thomas Andreatta Link: https://lore.kernel.org/r/20250619084420.146151-4-thomas.andreatta2000@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/i2c/ov2722.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/drivers/staging/media/atomisp/i2c/ov2722.h b/drivers/staging/media/atomisp/i2c/ov2722.h index bc36133f3722..00317d105305 100644 --- a/drivers/staging/media/atomisp/i2c/ov2722.h +++ b/drivers/staging/media/atomisp/i2c/ov2722.h @@ -236,7 +236,7 @@ struct ov2722_write_ctrl { * Register settings for various resolution */ #if 0 -static struct ov2722_reg const ov2722_QVGA_30fps[] = { +static const struct ov2722_reg ov2722_QVGA_30fps[] = { {OV2722_8BIT, 0x3718, 0x10}, {OV2722_8BIT, 0x3702, 0x0c}, {OV2722_8BIT, 0x373a, 0x1c}, @@ -346,7 +346,7 @@ static struct ov2722_reg const ov2722_QVGA_30fps[] = { }; -static struct ov2722_reg const ov2722_480P_30fps[] = { +static const struct ov2722_reg ov2722_480P_30fps[] = { {OV2722_8BIT, 0x3718, 0x10}, {OV2722_8BIT, 0x3702, 0x18}, {OV2722_8BIT, 0x373a, 0x3c}, @@ -455,7 +455,7 @@ static struct ov2722_reg const ov2722_480P_30fps[] = { {OV2722_TOK_TERM, 0, 0}, }; -static struct ov2722_reg const ov2722_VGA_30fps[] = { +static const struct ov2722_reg ov2722_VGA_30fps[] = { {OV2722_8BIT, 0x3718, 0x10}, {OV2722_8BIT, 0x3702, 0x18}, {OV2722_8BIT, 0x373a, 0x3c}, @@ -565,7 +565,7 @@ static struct ov2722_reg const ov2722_VGA_30fps[] = { }; #endif -static struct ov2722_reg const ov2722_1632_1092_30fps[] = { +static const struct ov2722_reg ov2722_1632_1092_30fps[] = { {OV2722_8BIT, 0x3021, 0x03}, /* For stand wait for a whole frame complete.(vblank) */ {OV2722_8BIT, 0x3718, 0x10}, @@ -667,7 +667,7 @@ static struct ov2722_reg const ov2722_1632_1092_30fps[] = { {OV2722_TOK_TERM, 0, 0} }; -static struct ov2722_reg const ov2722_1452_1092_30fps[] = { +static const struct ov2722_reg ov2722_1452_1092_30fps[] = { {OV2722_8BIT, 0x3021, 0x03}, /* For stand wait for a whole frame complete.(vblank) */ {OV2722_8BIT, 0x3718, 0x10}, @@ -769,7 +769,7 @@ static struct ov2722_reg const ov2722_1452_1092_30fps[] = { }; #if 0 -static struct ov2722_reg const ov2722_1M3_30fps[] = { +static const struct ov2722_reg ov2722_1M3_30fps[] = { {OV2722_8BIT, 0x3718, 0x10}, {OV2722_8BIT, 0x3702, 0x24}, {OV2722_8BIT, 0x373a, 0x60}, @@ -877,7 +877,7 @@ static struct ov2722_reg const ov2722_1M3_30fps[] = { }; #endif -static struct ov2722_reg const ov2722_1080p_30fps[] = { +static const struct ov2722_reg ov2722_1080p_30fps[] = { {OV2722_8BIT, 0x3021, 0x03}, /* For stand wait for a whole frame complete.(vblank) */ {OV2722_8BIT, 0x3718, 0x10}, @@ -983,7 +983,7 @@ static struct ov2722_reg const ov2722_1080p_30fps[] = { }; #if 0 /* Currently unused */ -static struct ov2722_reg const ov2722_720p_30fps[] = { +static const struct ov2722_reg ov2722_720p_30fps[] = { {OV2722_8BIT, 0x3021, 0x03}, {OV2722_8BIT, 0x3718, 0x10}, {OV2722_8BIT, 0x3702, 0x24}, From 58913d96525b266d7cf9e6ec787ad1c7df355b2b Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 May 2025 18:46:47 +0300 Subject: [PATCH 258/310] media: atomisp: Remove unused header There are some unused definitions that are using macros from math_support.h. In order to have cleaner future changes, remove them first. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250519155028.526453-2-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Tested-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- .../pci/isp/modes/interface/input_buf.isp.h | 1 - .../pci/isp/modes/interface/isp_const.h | 157 ------------------ .../media/atomisp/pci/sh_css_param_dvs.h | 3 - 3 files changed, 161 deletions(-) delete mode 100644 drivers/staging/media/atomisp/pci/isp/modes/interface/isp_const.h diff --git a/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h b/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h index c7ade6ce6c68..55de6cb02833 100644 --- a/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h +++ b/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h @@ -10,7 +10,6 @@ Copyright (c) 2010 - 2015, Intel Corporation. /* Temporary include, since IA_CSS_BINARY_MODE_COPY is still needed */ #include "sh_css_defs.h" -#include "isp_const.h" /* MAX_VECTORS_PER_INPUT_LINE */ #define INPUT_BUF_HEIGHT 2 /* double buffer */ #define INPUT_BUF_LINES 2 diff --git a/drivers/staging/media/atomisp/pci/isp/modes/interface/isp_const.h b/drivers/staging/media/atomisp/pci/isp/modes/interface/isp_const.h deleted file mode 100644 index b767b0d35bb4..000000000000 --- a/drivers/staging/media/atomisp/pci/isp/modes/interface/isp_const.h +++ /dev/null @@ -1,157 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/** -Support for Intel Camera Imaging ISP subsystem. -Copyright (c) 2010 - 2015, Intel Corporation. - -*/ - -#ifndef _COMMON_ISP_CONST_H_ -#define _COMMON_ISP_CONST_H_ - -/*#include "isp.h"*/ /* ISP_VEC_NELEMS */ - -/* Binary independent constants */ - -#ifndef NO_HOIST -# define NO_HOIST HIVE_ATTRIBUTE((no_hoist)) -#endif - -#define NO_HOIST_CSE HIVE_ATTRIBUTE((no_hoist, no_cse)) - -#define UNION struct /* Union constructors not allowed in C++ */ - -#define XMEM_WIDTH_BITS HIVE_ISP_DDR_WORD_BITS -#define XMEM_SHORTS_PER_WORD (HIVE_ISP_DDR_WORD_BITS / 16) -#define XMEM_INTS_PER_WORD (HIVE_ISP_DDR_WORD_BITS / 32) -#define XMEM_POW2_BYTES_PER_WORD HIVE_ISP_DDR_WORD_BYTES - -#define BITS8_ELEMENTS_PER_XMEM_ADDR CEIL_DIV(XMEM_WIDTH_BITS, 8) -#define BITS16_ELEMENTS_PER_XMEM_ADDR CEIL_DIV(XMEM_WIDTH_BITS, 16) - -#define ISP_NWAY_LOG2 6 - -/* ***************************** - * ISP input/output buffer sizes - * ****************************/ -/* input image */ -#define INPUT_BUF_DMA_HEIGHT 2 -#define INPUT_BUF_HEIGHT 2 /* double buffer */ -#define OUTPUT_BUF_DMA_HEIGHT 2 -#define OUTPUT_BUF_HEIGHT 2 /* double buffer */ -#define OUTPUT_NUM_TRANSFERS 4 - -/* GDC accelerator: Up/Down Scaling */ -/* These should be moved to the gdc_defs.h in the device */ -#define UDS_SCALING_N HRT_GDC_N -/* AB: This should cover the zooming up to 16MP */ -#define UDS_MAX_OXDIM 5000 -/* We support maximally 2 planes with different parameters - - luma and chroma (YUV420) */ -#define UDS_MAX_PLANES 2 -#define UDS_BLI_BLOCK_HEIGHT 2 -#define UDS_BCI_BLOCK_HEIGHT 4 -#define UDS_BLI_INTERP_ENVELOPE 1 -#define UDS_BCI_INTERP_ENVELOPE 3 -#define UDS_MAX_ZOOM_FAC 64 -/* Make it always one FPGA vector. - Four FPGA vectors are required and - four of them fit in one ASIC vector.*/ -#define UDS_MAX_CHUNKS 16 - -#define ISP_LEFT_PADDING _ISP_LEFT_CROP_EXTRA(ISP_LEFT_CROPPING) -#define ISP_LEFT_PADDING_VECS CEIL_DIV(ISP_LEFT_PADDING, ISP_VEC_NELEMS) -/* in case of continuous the croppong of the current binary doesn't matter for the buffer calculation, but the cropping of the sp copy should be used */ -#define ISP_LEFT_PADDING_CONT _ISP_LEFT_CROP_EXTRA(SH_CSS_MAX_LEFT_CROPPING) -#define ISP_LEFT_PADDING_VECS_CONT CEIL_DIV(ISP_LEFT_PADDING_CONT, ISP_VEC_NELEMS) - -#define CEIL_ROUND_DIV_STRIPE(width, stripe, padding) \ - CEIL_MUL(padding + CEIL_DIV(width - padding, stripe), ((ENABLE_RAW_BINNING || ENABLE_FIXED_BAYER_DS) ? 4 : 2)) - -/* output (Y,U,V) image, 4:2:0 */ -#define MAX_VECTORS_PER_LINE \ - CEIL_ROUND_DIV_STRIPE(CEIL_DIV(ISP_MAX_INTERNAL_WIDTH, ISP_VEC_NELEMS), \ - ISP_NUM_STRIPES, \ - ISP_LEFT_PADDING_VECS) - -/* - * ITERATOR_VECTOR_INCREMENT' explanation: - * when striping an even number of iterations, one of the stripes is - * one iteration wider than the other to account for overlap - * so the calc for the output buffer vmem size is: - * ((width[vectors]/num_of_stripes) + 2[vectors]) - */ -#define MAX_VECTORS_PER_OUTPUT_LINE \ - CEIL_DIV(CEIL_DIV(ISP_MAX_OUTPUT_WIDTH, ISP_NUM_STRIPES) + ISP_LEFT_PADDING, ISP_VEC_NELEMS) - -/* Must be even due to interlaced bayer input */ -#define MAX_VECTORS_PER_INPUT_LINE CEIL_MUL((CEIL_DIV(ISP_MAX_INPUT_WIDTH, ISP_VEC_NELEMS) + ISP_LEFT_PADDING_VECS), 2) -#define MAX_VECTORS_PER_INPUT_STRIPE CEIL_ROUND_DIV_STRIPE(MAX_VECTORS_PER_INPUT_LINE, \ - ISP_NUM_STRIPES, \ - ISP_LEFT_PADDING_VECS) - -/* Add 2 for left croppping */ -#define MAX_SP_RAW_COPY_VECTORS_PER_INPUT_LINE (CEIL_DIV(ISP_MAX_INPUT_WIDTH, ISP_VEC_NELEMS) + 2) - -#define MAX_VECTORS_PER_BUF_LINE \ - (MAX_VECTORS_PER_LINE + DUMMY_BUF_VECTORS) -#define MAX_VECTORS_PER_BUF_INPUT_LINE \ - (MAX_VECTORS_PER_INPUT_STRIPE + DUMMY_BUF_VECTORS) -#define MAX_OUTPUT_Y_FRAME_WIDTH \ - (MAX_VECTORS_PER_LINE * ISP_VEC_NELEMS) -#define MAX_OUTPUT_Y_FRAME_SIMDWIDTH \ - MAX_VECTORS_PER_LINE -#define MAX_OUTPUT_C_FRAME_WIDTH \ - (MAX_OUTPUT_Y_FRAME_WIDTH / 2) -#define MAX_OUTPUT_C_FRAME_SIMDWIDTH \ - CEIL_DIV(MAX_OUTPUT_C_FRAME_WIDTH, ISP_VEC_NELEMS) - -/* should be even */ -#define NO_CHUNKING (OUTPUT_NUM_CHUNKS == 1) - -#define MAX_VECTORS_PER_CHUNK \ - (NO_CHUNKING ? MAX_VECTORS_PER_LINE \ - : 2 * CEIL_DIV(MAX_VECTORS_PER_LINE, \ - 2 * OUTPUT_NUM_CHUNKS)) - -#define MAX_C_VECTORS_PER_CHUNK \ - (MAX_VECTORS_PER_CHUNK / 2) - -/* should be even */ -#define MAX_VECTORS_PER_OUTPUT_CHUNK \ - (NO_CHUNKING ? MAX_VECTORS_PER_OUTPUT_LINE \ - : 2 * CEIL_DIV(MAX_VECTORS_PER_OUTPUT_LINE, \ - 2 * OUTPUT_NUM_CHUNKS)) - -#define MAX_C_VECTORS_PER_OUTPUT_CHUNK \ - (MAX_VECTORS_PER_OUTPUT_CHUNK / 2) - -/* should be even */ -#define MAX_VECTORS_PER_INPUT_CHUNK \ - (INPUT_NUM_CHUNKS == 1 ? MAX_VECTORS_PER_INPUT_STRIPE \ - : 2 * CEIL_DIV(MAX_VECTORS_PER_INPUT_STRIPE, \ - 2 * OUTPUT_NUM_CHUNKS)) - -#define DEFAULT_C_SUBSAMPLING 2 - -/****** DMA buffer properties */ - -#define RAW_BUF_LINES ((ENABLE_RAW_BINNING || ENABLE_FIXED_BAYER_DS) ? 4 : 2) - -/* [isp vmem] table size[vectors] per line per color (GR,R,B,GB), - multiples of NWAY */ -#define ISP2400_SCTBL_VECTORS_PER_LINE_PER_COLOR \ - CEIL_DIV(SH_CSS_MAX_SCTBL_WIDTH_PER_COLOR, ISP_VEC_NELEMS) -#define ISP2401_SCTBL_VECTORS_PER_LINE_PER_COLOR \ - CEIL_DIV(SH_CSS_MAX_SCTBL_WIDTH_PER_COLOR, ISP_VEC_NELEMS) -/* [isp vmem] table size[vectors] per line for 4colors (GR,R,B,GB), - multiples of NWAY */ -#define SCTBL_VECTORS_PER_LINE \ - (SCTBL_VECTORS_PER_LINE_PER_COLOR * IA_CSS_SC_NUM_COLORS) - -/*************/ - -/* Format for fixed primaries */ - -#define ISP_FIXED_PRIMARY_FORMAT IA_CSS_FRAME_FORMAT_NV12 - -#endif /* _COMMON_ISP_CONST_H_ */ diff --git a/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h b/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h index b7057887adea..fd5cf6038829 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h +++ b/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h @@ -33,9 +33,6 @@ #define DVS_TABLE_IN_BLOCKDIM_Y_LUMA(X) (DVS_NUM_BLOCKS_Y(X) + 1) #define DVS_TABLE_IN_BLOCKDIM_Y_CHROMA(X) (DVS_NUM_BLOCKS_Y_CHROMA(X) + 1) -#define DVS_ENVELOPE_X(X) (((X) == 0) ? (DVS_ENV_MIN_X) : (X)) -#define DVS_ENVELOPE_Y(X) (((X) == 0) ? (DVS_ENV_MIN_Y) : (X)) - #define DVS_COORD_FRAC_BITS (10) /* ISP2400 */ From 782fc724bfa44daa2e69e364426ea67b66deb1b3 Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 May 2025 18:46:48 +0300 Subject: [PATCH 259/310] media: atomisp: Replace macros from math_support.h Replace rarely used macros by generic ones from Linux kernel headers. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250519155028.526453-3-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Tested-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- .../kernels/anr/anr_1.0/ia_css_anr_types.h | 4 ++- .../pci/isp/kernels/dpc2/ia_css_dpc2_param.h | 6 ++-- .../isp/kernels/dvs/dvs_1.0/ia_css_dvs.host.c | 4 +-- .../isp/kernels/eed1_8/ia_css_eed1_8_param.h | 22 +++++++------- .../isp/kernels/fpn/fpn_1.0/ia_css_fpn.host.c | 6 ++-- .../isp/kernels/sc/sc_1.0/ia_css_sc_param.h | 2 +- .../pci/isp/modes/interface/input_buf.isp.h | 5 +++- .../pci/runtime/debug/src/ia_css_debug.c | 1 - .../atomisp/pci/runtime/frame/src/frame.c | 29 ++++++++++--------- .../atomisp/pci/runtime/ifmtr/src/ifmtr.c | 11 ++++--- .../pci/runtime/isys/src/virtual_isys.c | 2 +- .../staging/media/atomisp/pci/sh_css_defs.h | 12 ++++---- .../media/atomisp/pci/sh_css_internal.h | 8 ++--- .../media/atomisp/pci/sh_css_param_dvs.h | 19 +++++++----- .../staging/media/atomisp/pci/sh_css_params.c | 12 ++++---- 15 files changed, 77 insertions(+), 66 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/anr/anr_1.0/ia_css_anr_types.h b/drivers/staging/media/atomisp/pci/isp/kernels/anr/anr_1.0/ia_css_anr_types.h index 6e573ceaa9ea..bc2a78dff004 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/anr/anr_1.0/ia_css_anr_types.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/anr/anr_1.0/ia_css_anr_types.h @@ -11,9 +11,11 @@ * CSS-API header file for Advanced Noise Reduction kernel v1 */ +#include + /* Application specific DMA settings */ #define ANR_BPP 10 -#define ANR_ELEMENT_BITS ((CEIL_DIV(ANR_BPP, 8)) * 8) +#define ANR_ELEMENT_BITS round_up(ANR_BPP, 8) /* Advanced Noise Reduction configuration. * This is also known as Low-Light. diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/dpc2/ia_css_dpc2_param.h b/drivers/staging/media/atomisp/pci/isp/kernels/dpc2/ia_css_dpc2_param.h index 099f32b8de1a..b1bbc283e367 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/dpc2/ia_css_dpc2_param.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/dpc2/ia_css_dpc2_param.h @@ -7,6 +7,8 @@ #ifndef __IA_CSS_DPC2_PARAM_H #define __IA_CSS_DPC2_PARAM_H +#include + #include "type_support.h" #include "vmem.h" /* for VMEM_ARRAY*/ @@ -19,12 +21,12 @@ /* 3 lines state per color plane input_line_state */ #define DPC2_STATE_INPUT_BUFFER_HEIGHT (3 * NUM_PLANES) /* Each plane has width equal to half frame line */ -#define DPC2_STATE_INPUT_BUFFER_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define DPC2_STATE_INPUT_BUFFER_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line state per color plane for local deviation state*/ #define DPC2_STATE_LOCAL_DEVIATION_BUFFER_HEIGHT (1 * NUM_PLANES) /* Each plane has width equal to half frame line */ -#define DPC2_STATE_LOCAL_DEVIATION_BUFFER_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define DPC2_STATE_LOCAL_DEVIATION_BUFFER_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* MINMAX state buffer stores 1 full input line (GR-R color line) */ #define DPC2_STATE_SECOND_MINMAX_BUFFER_HEIGHT 1 diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/dvs/dvs_1.0/ia_css_dvs.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/dvs/dvs_1.0/ia_css_dvs.host.c index 30c84639d7e8..e9d6dd0bbfe2 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/dvs/dvs_1.0/ia_css_dvs.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/dvs/dvs_1.0/ia_css_dvs.host.c @@ -141,10 +141,10 @@ convert_coords_to_ispparams( /* similar to topleft_y calculation, but round up if ymax * has any fraction bits */ - bottom_y = CEIL_DIV(ymax, 1 << DVS_COORD_FRAC_BITS); + bottom_y = DIV_ROUND_UP(ymax, BIT(DVS_COORD_FRAC_BITS)); s.in_block_height = bottom_y - topleft_y + dvs_interp_envelope; - bottom_x = CEIL_DIV(xmax, 1 << DVS_COORD_FRAC_BITS); + bottom_x = DIV_ROUND_UP(xmax, BIT(DVS_COORD_FRAC_BITS)); s.in_block_width = bottom_x - topleft_x + dvs_interp_envelope; topleft_x_frac = topleft_x << (DVS_COORD_FRAC_BITS); diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/eed1_8/ia_css_eed1_8_param.h b/drivers/staging/media/atomisp/pci/isp/kernels/eed1_8/ia_css_eed1_8_param.h index df87770446dd..a24eef965f16 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/eed1_8/ia_css_eed1_8_param.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/eed1_8/ia_css_eed1_8_param.h @@ -7,6 +7,8 @@ #ifndef __IA_CSS_EED1_8_PARAM_H #define __IA_CSS_EED1_8_PARAM_H +#include + #include "type_support.h" #include "vmem.h" /* needed for VMEM_ARRAY */ @@ -35,35 +37,35 @@ #define EED1_8_STATE_INPUT_BUFFER_HEIGHT (5 * NUM_PLANES) /* Each plane has width equal to half frame line */ -#define EED1_8_STATE_INPUT_BUFFER_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_INPUT_BUFFER_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line state per color plane LD_H state */ #define EED1_8_STATE_LD_H_HEIGHT (1 * NUM_PLANES) -#define EED1_8_STATE_LD_H_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_LD_H_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line state per color plane LD_V state */ #define EED1_8_STATE_LD_V_HEIGHT (1 * NUM_PLANES) -#define EED1_8_STATE_LD_V_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_LD_V_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line (single plane) state for D_Hr state */ #define EED1_8_STATE_D_HR_HEIGHT 1 -#define EED1_8_STATE_D_HR_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_D_HR_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line (single plane) state for D_Hb state */ #define EED1_8_STATE_D_HB_HEIGHT 1 -#define EED1_8_STATE_D_HB_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_D_HB_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 2 lines (single plane) state for D_Vr state */ #define EED1_8_STATE_D_VR_HEIGHT 2 -#define EED1_8_STATE_D_VR_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_D_VR_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 2 line (single plane) state for D_Vb state */ #define EED1_8_STATE_D_VB_HEIGHT 2 -#define EED1_8_STATE_D_VB_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_D_VB_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 2 lines state for R and B (= 2 planes) rb_zipped_state */ #define EED1_8_STATE_RB_ZIPPED_HEIGHT (2 * 2) -#define EED1_8_STATE_RB_ZIPPED_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_RB_ZIPPED_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) #if EED1_8_FC_ENABLE_MEDIAN /* 1 full input line (GR-R color line) for Yc state */ @@ -72,11 +74,11 @@ /* 1 line state per color plane Cg_state */ #define EED1_8_STATE_CG_HEIGHT (1 * NUM_PLANES) -#define EED1_8_STATE_CG_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_CG_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 line state per color plane Co_state */ #define EED1_8_STATE_CO_HEIGHT (1 * NUM_PLANES) -#define EED1_8_STATE_CO_WIDTH CEIL_DIV(MAX_FRAME_SIMDWIDTH, 2) +#define EED1_8_STATE_CO_WIDTH DIV_ROUND_UP(MAX_FRAME_SIMDWIDTH, 2) /* 1 full input line (GR-R color line) for AbsK state */ #define EED1_8_STATE_ABSK_HEIGHT 1 diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/fpn/fpn_1.0/ia_css_fpn.host.c b/drivers/staging/media/atomisp/pci/isp/kernels/fpn/fpn_1.0/ia_css_fpn.host.c index b34feba5340b..e701b7e41ef4 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/fpn/fpn_1.0/ia_css_fpn.host.c +++ b/drivers/staging/media/atomisp/pci/isp/kernels/fpn/fpn_1.0/ia_css_fpn.host.c @@ -4,6 +4,8 @@ * Copyright (c) 2015, Intel Corporation. */ +#include + #include #include #include @@ -71,9 +73,9 @@ int ia_css_fpn_configure(const struct ia_css_binary *binary, &my_info }; - my_info.res.width = CEIL_DIV(info->res.width, 2); /* Packed by 2x */ + my_info.res.width = DIV_ROUND_UP(info->res.width, 2); /* Packed by 2x */ my_info.res.height = info->res.height; - my_info.padded_width = CEIL_DIV(info->padded_width, 2); /* Packed by 2x */ + my_info.padded_width = DIV_ROUND_UP(info->padded_width, 2); /* Packed by 2x */ my_info.format = info->format; my_info.raw_bit_depth = FPN_BITS_PER_PIXEL; my_info.raw_bayer_order = info->raw_bayer_order; diff --git a/drivers/staging/media/atomisp/pci/isp/kernels/sc/sc_1.0/ia_css_sc_param.h b/drivers/staging/media/atomisp/pci/isp/kernels/sc/sc_1.0/ia_css_sc_param.h index 61e9c04d2515..b49761ad39ca 100644 --- a/drivers/staging/media/atomisp/pci/isp/kernels/sc/sc_1.0/ia_css_sc_param.h +++ b/drivers/staging/media/atomisp/pci/isp/kernels/sc/sc_1.0/ia_css_sc_param.h @@ -22,7 +22,7 @@ struct sh_css_isp_sc_params { * vec_slice is used for 2 adjacent vectors of shading gains. * The number of shift times by vec_slice is 8. * Max grid cell bqs to support the shading table centerting: N = 32 - * CEIL_DIV(N-1, ISP_SLICE_NELEMS) = CEIL_DIV(31, 4) = 8 + * DIV_ROUND_UP(N-1, ISP_SLICE_NELEMS) = DIV_ROUND_UP(31, 4) = 8 */ #define SH_CSS_SC_INTERPED_GAIN_HOR_SLICE_TIMES 8 diff --git a/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h b/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h index 55de6cb02833..6a0257359e69 100644 --- a/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h +++ b/drivers/staging/media/atomisp/pci/isp/modes/interface/input_buf.isp.h @@ -8,6 +8,8 @@ Copyright (c) 2010 - 2015, Intel Corporation. #ifndef _INPUT_BUF_ISP_H_ #define _INPUT_BUF_ISP_H_ +#include + /* Temporary include, since IA_CSS_BINARY_MODE_COPY is still needed */ #include "sh_css_defs.h" @@ -21,7 +23,8 @@ Copyright (c) 2010 - 2015, Intel Corporation. /* In continuous mode, the input buffer must be a fixed size for all binaries * and at a fixed address since it will be used by the SP. */ #define EXTRA_INPUT_VECTORS 2 /* For left padding */ -#define MAX_VECTORS_PER_INPUT_LINE_CONT (CEIL_DIV(SH_CSS_MAX_SENSOR_WIDTH, ISP_NWAY) + EXTRA_INPUT_VECTORS) +#define MAX_VECTORS_PER_INPUT_LINE_CONT \ + (DIV_ROUND_UP(SH_CSS_MAX_SENSOR_WIDTH, ISP_NWAY) + EXTRA_INPUT_VECTORS) /* The input buffer should be on a fixed address in vmem, for continuous capture */ #define INPUT_BUF_ADDR 0x0 diff --git a/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c b/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c index 84220359c957..b411ca2f415e 100644 --- a/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c +++ b/drivers/staging/media/atomisp/pci/runtime/debug/src/ia_css_debug.c @@ -51,7 +51,6 @@ #include "sp.h" #include "isp.h" #include "type_support.h" -#include "math_support.h" /* CEIL_DIV */ #include "input_system.h" /* input_formatter_reg_load */ #include "ia_css_tagger_common.h" diff --git a/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c b/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c index 4f610f57e6c1..2cb96f9a6030 100644 --- a/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c +++ b/drivers/staging/media/atomisp/pci/runtime/frame/src/frame.c @@ -4,15 +4,16 @@ * Copyright (c) 2015, Intel Corporation. */ -#include "hmm.h" +#include +#include -#include "ia_css_frame.h" -#include #include "assert_support.h" +#include "atomisp_internal.h" +#include "hmm.h" #include "ia_css_debug.h" +#include "ia_css_frame.h" #include "isp.h" #include "sh_css_internal.h" -#include "atomisp_internal.h" #define NV12_TILEY_TILE_WIDTH 128 #define NV12_TILEY_TILE_HEIGHT 32 @@ -459,15 +460,16 @@ static void frame_init_single_plane(struct ia_css_frame *frame, unsigned int stride; stride = subpixels_per_line * bytes_per_pixel; - /* Frame height needs to be even number - needed by hw ISYS2401 - In case of odd number, round up to even. - Images won't be impacted by this round up, - only needed by jpeg/embedded data. - As long as buffer allocation and release are using data_bytes, - there won't be memory leak. */ - frame->data_bytes = stride * CEIL_MUL2(height, 2); + /* + * Frame height needs to be even number - needed by hw ISYS2401. + * In case of odd number, round up to even. + * Images won't be impacted by this round up, + * only needed by jpeg/embedded data. + * As long as buffer allocation and release are using data_bytes, + * there won't be memory leak. + */ + frame->data_bytes = stride * round_up(height, 2); frame_init_plane(plane, subpixels_per_line, stride, height, 0); - return; } static void frame_init_raw_single_plane( @@ -486,7 +488,6 @@ static void frame_init_raw_single_plane( HIVE_ISP_DDR_WORD_BITS / bits_per_pixel); frame->data_bytes = stride * height; frame_init_plane(plane, subpixels_per_line, stride, height, 0); - return; } static void frame_init_nv_planes(struct ia_css_frame *frame, @@ -690,7 +691,7 @@ ia_css_elems_bytes_from_info(const struct ia_css_frame_info *info) if (info->format == IA_CSS_FRAME_FORMAT_RAW || (info->format == IA_CSS_FRAME_FORMAT_RAW_PACKED)) { if (info->raw_bit_depth) - return CEIL_DIV(info->raw_bit_depth, 8); + return BITS_TO_BYTES(info->raw_bit_depth); else return 2; /* bytes per pixel */ } diff --git a/drivers/staging/media/atomisp/pci/runtime/ifmtr/src/ifmtr.c b/drivers/staging/media/atomisp/pci/runtime/ifmtr/src/ifmtr.c index d57ffb335fc0..50b0b31d734a 100644 --- a/drivers/staging/media/atomisp/pci/runtime/ifmtr/src/ifmtr.c +++ b/drivers/staging/media/atomisp/pci/runtime/ifmtr/src/ifmtr.c @@ -4,9 +4,9 @@ * Copyright (c) 2010 - 2015, Intel Corporation. */ -#include "system_global.h" -#include +#include +#include "system_global.h" #include "ia_css_ifmtr.h" #include @@ -149,10 +149,9 @@ int ia_css_ifmtr_configure(struct ia_css_stream_config *config, left_padding = 2 * ISP_VEC_NELEMS - config->left_padding; if (left_padding) { - num_vectors = CEIL_DIV(cropped_width + left_padding, - ISP_VEC_NELEMS); + num_vectors = DIV_ROUND_UP(cropped_width + left_padding, ISP_VEC_NELEMS); } else { - num_vectors = CEIL_DIV(cropped_width, ISP_VEC_NELEMS); + num_vectors = DIV_ROUND_UP(cropped_width, ISP_VEC_NELEMS); num_vectors *= buffer_height; /* todo: in case of left padding, num_vectors is vectors per line, @@ -305,7 +304,7 @@ int ia_css_ifmtr_configure(struct ia_css_stream_config *config, if ((!binary) || config->continuous) /* !binary -> sp raw copy pipe */ buffer_height *= 2; - vectors_per_line = CEIL_DIV(cropped_width, ISP_VEC_NELEMS); + vectors_per_line = DIV_ROUND_UP(cropped_width, ISP_VEC_NELEMS); vectors_per_line = CEIL_MUL(vectors_per_line, deinterleaving); break; case ATOMISP_INPUT_FORMAT_RAW_14: diff --git a/drivers/staging/media/atomisp/pci/runtime/isys/src/virtual_isys.c b/drivers/staging/media/atomisp/pci/runtime/isys/src/virtual_isys.c index b5395aea89fc..e6c11d5f77b4 100644 --- a/drivers/staging/media/atomisp/pci/runtime/isys/src/virtual_isys.c +++ b/drivers/staging/media/atomisp/pci/runtime/isys/src/virtual_isys.c @@ -808,7 +808,7 @@ static bool calculate_isys2401_dma_port_cfg( cfg->elements = HIVE_ISP_DDR_WORD_BITS / bits_per_pixel; cfg->cropping = 0; - cfg->width = CEIL_DIV(cfg->stride, HIVE_ISP_DDR_WORD_BYTES); + cfg->width = DIV_ROUND_UP(cfg->stride, HIVE_ISP_DDR_WORD_BYTES); return true; } diff --git a/drivers/staging/media/atomisp/pci/sh_css_defs.h b/drivers/staging/media/atomisp/pci/sh_css_defs.h index 51491dfe05cc..7bfeeb8cf053 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_defs.h +++ b/drivers/staging/media/atomisp/pci/sh_css_defs.h @@ -7,12 +7,12 @@ #ifndef _SH_CSS_DEFS_H_ #define _SH_CSS_DEFS_H_ +#include + #include "isp.h" /*#include "vamem.h"*/ /* Cannot include for VAMEM properties this file is visible on ISP -> pipeline generator */ -#include "math_support.h" /* max(), min, etc etc */ - /* ID's for refcount */ #define IA_CSS_REFCOUNT_PARAM_SET_POOL 0xCAFE0001 #define IA_CSS_REFCOUNT_PARAM_BUFFER 0xCAFE0002 @@ -182,7 +182,7 @@ RGB[0,8191],coef[-8192,8191] -> RGB[0,8191] The ISP firmware needs these rules to be applied at pre-processor time, that's why these are macros, not functions. */ #define _ISP_BQS(num) ((num) / 2) -#define _ISP_VECS(width) CEIL_DIV(width, ISP_VEC_NELEMS) +#define _ISP_VECS(width) DIV_ROUND_UP(width, ISP_VEC_NELEMS) #define ISP_BQ_GRID_WIDTH(elements_per_line, deci_factor_log2) \ CEIL_SHIFT(elements_per_line / 2, deci_factor_log2) @@ -194,9 +194,9 @@ RGB[0,8191],coef[-8192,8191] -> RGB[0,8191] /* The morphing table is similar to the shading table in the sense that we have 1 more value than we have cells in the grid. */ #define _ISP_MORPH_TABLE_WIDTH(int_width) \ - (CEIL_DIV(int_width, SH_CSS_MORPH_TABLE_GRID) + 1) + (DIV_ROUND_UP(int_width, SH_CSS_MORPH_TABLE_GRID) + 1) #define _ISP_MORPH_TABLE_HEIGHT(int_height) \ - (CEIL_DIV(int_height, SH_CSS_MORPH_TABLE_GRID) + 1) + (DIV_ROUND_UP(int_height, SH_CSS_MORPH_TABLE_GRID) + 1) #define _ISP_MORPH_TABLE_ALIGNED_WIDTH(width) \ CEIL_MUL(_ISP_MORPH_TABLE_WIDTH(width), \ SH_CSS_MORPH_TABLE_ELEMS_PER_DDR_WORD) @@ -298,7 +298,7 @@ RGB[0,8191],coef[-8192,8191] -> RGB[0,8191] c_subsampling, \ num_chunks, \ pipelining) \ - CEIL_MUL2(CEIL_MUL2(MAX(__ISP_PADDED_OUTPUT_WIDTH(out_width, \ + round_up(round_up(MAX(__ISP_PADDED_OUTPUT_WIDTH(out_width, \ dvs_env_width, \ left_crop), \ __ISP_MIN_INTERNAL_WIDTH(num_chunks, \ diff --git a/drivers/staging/media/atomisp/pci/sh_css_internal.h b/drivers/staging/media/atomisp/pci/sh_css_internal.h index 7b3483585748..9155a83fcc03 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_internal.h +++ b/drivers/staging/media/atomisp/pci/sh_css_internal.h @@ -8,6 +8,7 @@ #define _SH_CSS_INTERNAL_H_ #include +#include #include #include @@ -95,7 +96,6 @@ * the SIZE_OF_XXX macro of the corresponding struct. If they are not * equal, functionality will break. */ -#define CALC_ALIGNMENT_MEMBER(x, y) (CEIL_MUL(x, y) - x) #define SIZE_OF_HRT_VADDRESS sizeof(hive_uint32) /* Number of SP's */ @@ -704,13 +704,11 @@ struct sh_css_hmm_buffer { /* Do not use sizeof(uint64_t) since that does not exist of SP */ #define SIZE_OF_SH_CSS_HMM_BUFFER_STRUCT \ - (SIZE_OF_PAYLOAD_UNION + \ - CALC_ALIGNMENT_MEMBER(SIZE_OF_PAYLOAD_UNION, 8) + \ + (round_up(SIZE_OF_PAYLOAD_UNION, 8) + \ 8 + \ 8 + \ SIZE_OF_IA_CSS_TIME_MEAS_STRUCT + \ - SIZE_OF_IA_CSS_CLOCK_TICK_STRUCT + \ - CALC_ALIGNMENT_MEMBER(SIZE_OF_IA_CSS_CLOCK_TICK_STRUCT, 8)) + round_up(SIZE_OF_IA_CSS_CLOCK_TICK_STRUCT, 8)) static_assert(sizeof(struct sh_css_hmm_buffer) == SIZE_OF_SH_CSS_HMM_BUFFER_STRUCT); diff --git a/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h b/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h index fd5cf6038829..b31a0d4e8acb 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h +++ b/drivers/staging/media/atomisp/pci/sh_css_param_dvs.h @@ -7,6 +7,8 @@ #ifndef _SH_CSS_PARAMS_DVS_H_ #define _SH_CSS_PARAMS_DVS_H_ +#include + #include #include #include "gdc_global.h" /* gdc_warp_param_mem_t */ @@ -20,16 +22,17 @@ /* ISP2400 */ /* horizontal 64x64 blocks round up to DVS_BLOCKDIM_X, make even */ -#define DVS_NUM_BLOCKS_X(X) (CEIL_MUL(CEIL_DIV((X), DVS_BLOCKDIM_X), 2)) +#define DVS_NUM_BLOCKS_X(X) round_up(DIV_ROUND_UP((X), DVS_BLOCKDIM_X), 2) +#define DVS_NUM_BLOCKS_X_CHROMA(X) DIV_ROUND_UP((X), DVS_BLOCKDIM_X) /* ISP2400 */ /* vertical 64x64 blocks round up to DVS_BLOCKDIM_Y */ -#define DVS_NUM_BLOCKS_Y(X) (CEIL_DIV((X), DVS_BLOCKDIM_Y_LUMA)) -#define DVS_NUM_BLOCKS_X_CHROMA(X) (CEIL_DIV((X), DVS_BLOCKDIM_X)) -#define DVS_NUM_BLOCKS_Y_CHROMA(X) (CEIL_DIV((X), DVS_BLOCKDIM_Y_CHROMA)) +#define DVS_NUM_BLOCKS_Y(X) DIV_ROUND_UP((X), DVS_BLOCKDIM_Y_LUMA) +#define DVS_NUM_BLOCKS_Y_CHROMA(X) DIV_ROUND_UP((X), DVS_BLOCKDIM_Y_CHROMA) -#define DVS_TABLE_IN_BLOCKDIM_X_LUMA(X) (DVS_NUM_BLOCKS_X(X) + 1) /* N blocks have N + 1 set of coords */ -#define DVS_TABLE_IN_BLOCKDIM_X_CHROMA(X) (DVS_NUM_BLOCKS_X_CHROMA(X) + 1) +/* N blocks have N + 1 set of coords */ +#define DVS_TABLE_IN_BLOCKDIM_X_LUMA(X) (DVS_NUM_BLOCKS_X(X) + 1) +#define DVS_TABLE_IN_BLOCKDIM_X_CHROMA(X) (DVS_NUM_BLOCKS_X_CHROMA(X) + 1) #define DVS_TABLE_IN_BLOCKDIM_Y_LUMA(X) (DVS_NUM_BLOCKS_Y(X) + 1) #define DVS_TABLE_IN_BLOCKDIM_Y_CHROMA(X) (DVS_NUM_BLOCKS_Y_CHROMA(X) + 1) @@ -40,8 +43,8 @@ #define XMEM_ALIGN_LOG2 (5) -#define DVS_6AXIS_COORDS_ELEMS CEIL_MUL(sizeof(gdc_warp_param_mem_t) \ - , HIVE_ISP_DDR_WORD_BYTES) +#define DVS_6AXIS_COORDS_ELEMS \ + round_up(sizeof(gdc_warp_param_mem_t), HIVE_ISP_DDR_WORD_BYTES) /* currently we only support two output with the same resolution, output 0 is th default one. */ #define DVS_6AXIS_BYTES(binary) \ diff --git a/drivers/staging/media/atomisp/pci/sh_css_params.c b/drivers/staging/media/atomisp/pci/sh_css_params.c index 0d4a936ad80f..11d62313c908 100644 --- a/drivers/staging/media/atomisp/pci/sh_css_params.c +++ b/drivers/staging/media/atomisp/pci/sh_css_params.c @@ -4,6 +4,8 @@ * Copyright (c) 2015, Intel Corporation. */ +#include + #include "gdc_device.h" /* gdc_lut_store(), ... */ #include "isp.h" /* ISP_VEC_ELEMBITS */ #include "vamem.h" @@ -21,8 +23,6 @@ #include "platform_support.h" #include "assert_support.h" -#include "misc_support.h" /* NOT_USED */ -#include "math_support.h" /* max(), min() EVEN_FLOOR()*/ #include "ia_css_stream.h" #include "sh_css_params_internal.h" @@ -4042,10 +4042,10 @@ sh_css_update_uds_and_crop_info( } /* Must enforce that the crop position is even */ - crop_x = EVEN_FLOOR(crop_x); - crop_y = EVEN_FLOOR(crop_y); - uds_xc = EVEN_FLOOR(uds_xc); - uds_yc = EVEN_FLOOR(uds_yc); + crop_x = round_down(crop_x, 2); + crop_y = round_down(crop_y, 2); + uds_xc = round_down(uds_xc, 2); + uds_yc = round_down(uds_yc, 2); uds->xc = (uint16_t)uds_xc; uds->yc = (uint16_t)uds_yc; From 379f5e1587310b7a0b12b2cd515b66271a1dfc5c Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Mon, 19 May 2025 18:46:49 +0300 Subject: [PATCH 260/310] media: atomisp: Remove no more used macros from math_support.h After recent cleanups the few macros become unused. Remove them. Signed-off-by: Andy Shevchenko Link: https://lore.kernel.org/r/20250519155028.526453-4-andriy.shevchenko@linux.intel.com Reviewed-by: Hans de Goede Tested-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- .../media/atomisp/pci/hive_isp_css_include/math_support.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/hive_isp_css_include/math_support.h b/drivers/staging/media/atomisp/pci/hive_isp_css_include/math_support.h index 6d45d0d8d060..2cb5c986790a 100644 --- a/drivers/staging/media/atomisp/pci/hive_isp_css_include/math_support.h +++ b/drivers/staging/media/atomisp/pci/hive_isp_css_include/math_support.h @@ -10,14 +10,9 @@ /* Override the definition of max/min from Linux kernel */ #include -/* force a value to a lower even value */ -#define EVEN_FLOOR(x) ((x) & ~1) - #define CEIL_DIV(a, b) (((b) != 0) ? ((a) + (b) - 1) / (b) : 0) #define CEIL_MUL(a, b) (CEIL_DIV(a, b) * (b)) -#define CEIL_MUL2(a, b) (((a) + (b) - 1) & ~((b) - 1)) #define CEIL_SHIFT(a, b) (((a) + (1 << (b)) - 1) >> (b)) -#define CEIL_SHIFT_MUL(a, b) (CEIL_SHIFT(a, b) << (b)) /* * For SP and ISP, SDK provides the definition of OP_std_modadd. From 5e2330ef08f4d7c7692ee55d272c0bb489258915 Mon Sep 17 00:00:00 2001 From: Abdelrahman Fekry Date: Fri, 27 Jun 2025 13:06:04 +0300 Subject: [PATCH 261/310] media: atomisp: Remove debug sysfs attributes active_bo and free_bo The sysfs attributes active_bo and free_bo expose internal buffer state used only for debugging purposes. These are not part of any standard kernel ABI, and need to be removed before this driver may be moved out of drivers/staging. - Remove active_bo and free_bo attributes - Remove group registration calls form hmm_init() and hmm_cleanup() Suggested-by: Hans de Goede Signed-off-by: Abdelrahman Fekry Reviewed-by: Andy Shevchenko Reviewed-by: Dan Carpenter Link: https://lore.kernel.org/r/20250627100604.29061-1-abdelrahmanfekry375@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/pci/hmm/hmm.c | 91 --------------------- 1 file changed, 91 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/hmm/hmm.c b/drivers/staging/media/atomisp/pci/hmm/hmm.c index 84102c3aaf97..f998b57f90c4 100644 --- a/drivers/staging/media/atomisp/pci/hmm/hmm.c +++ b/drivers/staging/media/atomisp/pci/hmm/hmm.c @@ -28,88 +28,6 @@ struct hmm_bo_device bo_device; static ia_css_ptr dummy_ptr = mmgr_EXCEPTION; static bool hmm_initialized; -/* - * p: private - * v: vmalloc - */ -static const char hmm_bo_type_string[] = "pv"; - -static ssize_t bo_show(struct device *dev, struct device_attribute *attr, - char *buf, struct list_head *bo_list, bool active) -{ - ssize_t ret = 0; - struct hmm_buffer_object *bo; - unsigned long flags; - int i; - long total[HMM_BO_LAST] = { 0 }; - long count[HMM_BO_LAST] = { 0 }; - int index1 = 0; - int index2 = 0; - - ret = scnprintf(buf, PAGE_SIZE, "type pgnr\n"); - if (ret <= 0) - return 0; - - index1 += ret; - - spin_lock_irqsave(&bo_device.list_lock, flags); - list_for_each_entry(bo, bo_list, list) { - if ((active && (bo->status & HMM_BO_ALLOCED)) || - (!active && !(bo->status & HMM_BO_ALLOCED))) { - ret = scnprintf(buf + index1, PAGE_SIZE - index1, - "%c %d\n", - hmm_bo_type_string[bo->type], bo->pgnr); - - total[bo->type] += bo->pgnr; - count[bo->type]++; - if (ret > 0) - index1 += ret; - } - } - spin_unlock_irqrestore(&bo_device.list_lock, flags); - - for (i = 0; i < HMM_BO_LAST; i++) { - if (count[i]) { - ret = scnprintf(buf + index1 + index2, - PAGE_SIZE - index1 - index2, - "%ld %c buffer objects: %ld KB\n", - count[i], hmm_bo_type_string[i], - total[i] * 4); - if (ret > 0) - index2 += ret; - } - } - - /* Add trailing zero, not included by scnprintf */ - return index1 + index2 + 1; -} - -static ssize_t active_bo_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - return bo_show(dev, attr, buf, &bo_device.entire_bo_list, true); -} - -static ssize_t free_bo_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - return bo_show(dev, attr, buf, &bo_device.entire_bo_list, false); -} - - -static DEVICE_ATTR_RO(active_bo); -static DEVICE_ATTR_RO(free_bo); - -static struct attribute *sysfs_attrs_ctrl[] = { - &dev_attr_active_bo.attr, - &dev_attr_free_bo.attr, - NULL -}; - -static struct attribute_group atomisp_attribute_group[] = { - {.attrs = sysfs_attrs_ctrl }, -}; - int hmm_init(void) { int ret; @@ -130,14 +48,6 @@ int hmm_init(void) */ dummy_ptr = hmm_alloc(1); - if (!ret) { - ret = sysfs_create_group(&atomisp_dev->kobj, - atomisp_attribute_group); - if (ret) - dev_err(atomisp_dev, - "%s Failed to create sysfs\n", __func__); - } - return ret; } @@ -145,7 +55,6 @@ void hmm_cleanup(void) { if (dummy_ptr == mmgr_EXCEPTION) return; - sysfs_remove_group(&atomisp_dev->kobj, atomisp_attribute_group); /* free dummy memory first */ hmm_free(dummy_ptr); From bd290dddb0d74b3cd34d2cca15d66105dc4a00d4 Mon Sep 17 00:00:00 2001 From: Abdelrahman Fekry Date: Sat, 28 Jun 2025 08:25:36 +0300 Subject: [PATCH 262/310] media: atomisp: Fix premature setting of HMM_BO_DEVICE_INITED flag The HMM_BO_DEVICE_INITED flag was being set in hmm_bo_device_init() before key initialization steps like kmem_cache_create(), kmem_cache_alloc(), and __bo_init(). This means that if any of these steps fail, the flag remains set, misleading other parts of the driver (e.g. hmm_bo_alloc()) into thinking the device is initialized. This could lead to undefined behavior or invalid memory use. Additionally, since __bo_init() is called from inside hmm_bo_device_init() after the flag was already set, its internal check for HMM_BO_DEVICE_INITED is redundant. - Move the flag assignment to the end after all allocations succeed. - Remove redundant check of the flag inside __bo_init() See the link [1] below for a backtrace which happens when deliberately triggering the problem of the flag getting set too early. Link: https://lore.kernel.org/linux-media/CAGn2d8ONZpOHXex8kjeUDgRPiMqKp8vZ=xhGbEDGphV1t7ZEFw@mail.gmail.com/ [1] Signed-off-by: Abdelrahman Fekry Link: https://lore.kernel.org/r/20250628052536.43737-1-abdelrahmanfekry375@gmail.com Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/pci/hmm/hmm_bo.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c b/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c index 224ca8d42721..5d0cd5260d3a 100644 --- a/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c +++ b/drivers/staging/media/atomisp/pci/hmm/hmm_bo.c @@ -37,8 +37,6 @@ static int __bo_init(struct hmm_bo_device *bdev, struct hmm_buffer_object *bo, unsigned int pgnr) { check_bodev_null_return(bdev, -EINVAL); - var_equal_return(hmm_bo_device_inited(bdev), 0, -EINVAL, - "hmm_bo_device not inited yet.\n"); /* prevent zero size buffer object */ if (pgnr == 0) { dev_err(atomisp_dev, "0 size buffer is not allowed.\n"); @@ -341,7 +339,6 @@ int hmm_bo_device_init(struct hmm_bo_device *bdev, spin_lock_init(&bdev->list_lock); mutex_init(&bdev->rbtree_mutex); - bdev->flag = HMM_BO_DEVICE_INITED; INIT_LIST_HEAD(&bdev->entire_bo_list); bdev->allocated_rbtree = RB_ROOT; @@ -376,6 +373,8 @@ int hmm_bo_device_init(struct hmm_bo_device *bdev, __bo_insert_to_free_rbtree(&bdev->free_rbtree, bo); + bdev->flag = HMM_BO_DEVICE_INITED; + return 0; } From 54d32a9754a096b2d449588f89c38d5dec52eb49 Mon Sep 17 00:00:00 2001 From: Abdelrahman Fekry Date: Fri, 4 Jul 2025 19:10:51 +0300 Subject: [PATCH 263/310] media: atomisp: Remove custom sysfs attributes from atomisp_drvfs.c Continue the cleanup of the AtomISP driver, as discussed with Hans and Andy in [1]. Tackle TODO item: "Remove custom sysfs files created by atomisp_drvfs.c": - Remove the sysfs attributes `dbglvl`, `dbgfun`, and `dbgopt`. - Delete their associated show/store handler functions. - Remove the corresponding attribute group definitions. Link: https://lore.kernel.org/all/836dc6b6-2821-47fc-8f24-0838f979af76@kernel.org/ [1] Suggested-by: Hans de Goede Reviewed-by: Andy Shevchenko Signed-off-by: Abdelrahman Fekry Link: https://lore.kernel.org/r/20250704161051.16733-1-abdelrahmanfekry375@gmail.com [hansg@kernel.org: Completely remove the now empty atomisp_drvfs files] Reviewed-by: Hans de Goede Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- drivers/staging/media/atomisp/Makefile | 1 - drivers/staging/media/atomisp/TODO | 2 - .../staging/media/atomisp/pci/atomisp_drvfs.c | 155 ------------------ .../staging/media/atomisp/pci/atomisp_drvfs.h | 15 -- .../staging/media/atomisp/pci/atomisp_v4l2.c | 4 - 5 files changed, 177 deletions(-) delete mode 100644 drivers/staging/media/atomisp/pci/atomisp_drvfs.c delete mode 100644 drivers/staging/media/atomisp/pci/atomisp_drvfs.h diff --git a/drivers/staging/media/atomisp/Makefile b/drivers/staging/media/atomisp/Makefile index 43116c74781d..1d0fbe22036b 100644 --- a/drivers/staging/media/atomisp/Makefile +++ b/drivers/staging/media/atomisp/Makefile @@ -17,7 +17,6 @@ atomisp-objs += \ pci/atomisp_compat_css20.o \ pci/atomisp_csi2.o \ pci/atomisp_csi2_bridge.o \ - pci/atomisp_drvfs.o \ pci/atomisp_fops.o \ pci/atomisp_ioctl.o \ pci/atomisp_subdev.o \ diff --git a/drivers/staging/media/atomisp/TODO b/drivers/staging/media/atomisp/TODO index 27cbbde93b1e..82be275b4a0a 100644 --- a/drivers/staging/media/atomisp/TODO +++ b/drivers/staging/media/atomisp/TODO @@ -7,8 +7,6 @@ TODO * Remove/disable custom v4l2-ctrls -* Remove custom sysfs files created by atomisp_drvfs.c - * Remove unnecessary/unwanted module parameters * Remove abuse of priv field in various v4l2 userspace API structs diff --git a/drivers/staging/media/atomisp/pci/atomisp_drvfs.c b/drivers/staging/media/atomisp/pci/atomisp_drvfs.c deleted file mode 100644 index 31c82c3c0d33..000000000000 --- a/drivers/staging/media/atomisp/pci/atomisp_drvfs.c +++ /dev/null @@ -1,155 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0 -/* - * Support for atomisp driver sysfs interface - * - * Copyright (c) 2014 Intel Corporation. All Rights Reserved. - */ - -#include -#include -#include - -#include "atomisp_compat.h" -#include "atomisp_internal.h" -#include "atomisp_ioctl.h" -#include "atomisp_drvfs.h" -#include "hmm/hmm.h" -#include "ia_css_debug.h" - -#define OPTION_BIN_LIST BIT(0) -#define OPTION_BIN_RUN BIT(1) -#define OPTION_VALID (OPTION_BIN_LIST | OPTION_BIN_RUN) - -/* - * dbgopt: iunit debug option: - * bit 0: binary list - * bit 1: running binary - * bit 2: memory statistic - */ -static unsigned int dbgopt = OPTION_BIN_LIST; - -static inline int iunit_dump_dbgopt(struct atomisp_device *isp, - unsigned int opt) -{ - int ret = 0; - - if (opt & OPTION_VALID) { - if (opt & OPTION_BIN_LIST) { - ret = atomisp_css_dump_blob_infor(isp); - if (ret) { - dev_err(isp->dev, "%s dump blob infor err[ret:%d]\n", - __func__, ret); - goto opt_err; - } - } - - if (opt & OPTION_BIN_RUN) { - if (isp->asd.streaming) { - atomisp_css_dump_sp_raw_copy_linecount(true); - atomisp_css_debug_dump_isp_binary(); - } else { - ret = -EPERM; - dev_err(isp->dev, "%s dump running bin err[ret:%d]\n", - __func__, ret); - goto opt_err; - } - } - } else { - ret = -EINVAL; - dev_err(isp->dev, "%s dump nothing[ret=%d]\n", __func__, ret); - } - -opt_err: - return ret; -} - -static ssize_t dbglvl_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - unsigned int dbglvl = ia_css_debug_get_dtrace_level(); - - return sysfs_emit(buf, "dtrace level:%u\n", dbglvl); -} - -static ssize_t dbglvl_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - unsigned int dbglvl; - int ret; - - ret = kstrtouint(buf, 10, &dbglvl); - if (ret) - return ret; - - if (dbglvl < 1 || dbglvl > 9) - return -ERANGE; - - ia_css_debug_set_dtrace_level(dbglvl); - return size; -} -static DEVICE_ATTR_RW(dbglvl); - -static ssize_t dbgfun_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - unsigned int dbgfun = atomisp_get_css_dbgfunc(); - - return sysfs_emit(buf, "dbgfun opt:%u\n", dbgfun); -} - -static ssize_t dbgfun_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct atomisp_device *isp = dev_get_drvdata(dev); - unsigned int opt; - int ret; - - ret = kstrtouint(buf, 10, &opt); - if (ret) - return ret; - - return atomisp_set_css_dbgfunc(isp, opt); -} -static DEVICE_ATTR_RW(dbgfun); - -static ssize_t dbgopt_show(struct device *dev, struct device_attribute *attr, - char *buf) -{ - return sysfs_emit(buf, "option:0x%x\n", dbgopt); -} - -static ssize_t dbgopt_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t size) -{ - struct atomisp_device *isp = dev_get_drvdata(dev); - unsigned int opt; - int ret; - - ret = kstrtouint(buf, 10, &opt); - if (ret) - return ret; - - dbgopt = opt; - ret = iunit_dump_dbgopt(isp, dbgopt); - if (ret) - return ret; - - return size; -} -static DEVICE_ATTR_RW(dbgopt); - -static struct attribute *dbg_attrs[] = { - &dev_attr_dbglvl.attr, - &dev_attr_dbgfun.attr, - &dev_attr_dbgopt.attr, - NULL -}; - -static const struct attribute_group dbg_attr_group = { - .attrs = dbg_attrs, -}; - -const struct attribute_group *dbg_attr_groups[] = { - &dbg_attr_group, - NULL -}; diff --git a/drivers/staging/media/atomisp/pci/atomisp_drvfs.h b/drivers/staging/media/atomisp/pci/atomisp_drvfs.h deleted file mode 100644 index 46ad59b8df28..000000000000 --- a/drivers/staging/media/atomisp/pci/atomisp_drvfs.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0 */ -/* - * Support for atomisp driver sysfs interface. - * - * Copyright (c) 2014 Intel Corporation. All Rights Reserved. - */ - -#ifndef __ATOMISP_DRVFS_H__ -#define __ATOMISP_DRVFS_H__ - -#include - -extern const struct attribute_group *dbg_attr_groups[]; - -#endif /* __ATOMISP_DRVFS_H__ */ diff --git a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c index 3fdaa4b7bbaf..900a67552d6a 100644 --- a/drivers/staging/media/atomisp/pci/atomisp_v4l2.c +++ b/drivers/staging/media/atomisp/pci/atomisp_v4l2.c @@ -29,7 +29,6 @@ #include "atomisp_internal.h" #include "atomisp-regs.h" #include "atomisp_dfs_tables.h" -#include "atomisp_drvfs.h" #include "hmm/hmm.h" #include "atomisp_trace_event.h" @@ -1497,9 +1496,6 @@ static const struct pci_device_id atomisp_pci_tbl[] = { MODULE_DEVICE_TABLE(pci, atomisp_pci_tbl); static struct pci_driver atomisp_pci_driver = { - .driver = { - .dev_groups = dbg_attr_groups, - }, .name = "atomisp-isp2", .id_table = atomisp_pci_tbl, .probe = atomisp_pci_probe, From 1610f15cba05e27ed74d42138b9bec45a315ead7 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Tue, 24 Jun 2025 08:30:46 +0200 Subject: [PATCH 264/310] .mailmap: update Hans Verkuil's email addresses Use hverkuil@kernel.org as the main address for kernel work. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- .mailmap | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.mailmap b/.mailmap index 645294560b19..928fad83a7a4 100644 --- a/.mailmap +++ b/.mailmap @@ -283,8 +283,9 @@ Gustavo Padovan Hamza Mahfooz Hanjun Guo Hans de Goede -Hans Verkuil -Hans Verkuil +Hans Verkuil +Hans Verkuil +Hans Verkuil Harry Yoo <42.hyeyoo@gmail.com> Heiko Carstens Heiko Carstens From 3e43442d4994c9e1e202c98129a87e330f7faaed Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sun, 6 Jul 2025 12:55:40 +0200 Subject: [PATCH 265/310] media: vivid: fix wrong pixel_array control size The pixel_array control size was calculated incorrectly: the dimensions were swapped (dims[0] should be the height), and the values should be the width or height divided by PIXEL_ARRAY_DIV and rounded up. So don't use roundup, but use DIV_ROUND_UP instead. This bug is harmless in the sense that nothing will break, except that it consumes way too much memory for this control. Fixes: 6bc7643d1b9c ("media: vivid: add pixel_array test control") Cc: Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- drivers/media/test-drivers/vivid/vivid-ctrls.c | 3 ++- drivers/media/test-drivers/vivid/vivid-vid-cap.c | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/drivers/media/test-drivers/vivid/vivid-ctrls.c b/drivers/media/test-drivers/vivid/vivid-ctrls.c index e340df0b6261..f94c15ff84f7 100644 --- a/drivers/media/test-drivers/vivid/vivid-ctrls.c +++ b/drivers/media/test-drivers/vivid/vivid-ctrls.c @@ -244,7 +244,8 @@ static const struct v4l2_ctrl_config vivid_ctrl_u8_pixel_array = { .min = 0x00, .max = 0xff, .step = 1, - .dims = { 640 / PIXEL_ARRAY_DIV, 360 / PIXEL_ARRAY_DIV }, + .dims = { DIV_ROUND_UP(360, PIXEL_ARRAY_DIV), + DIV_ROUND_UP(640, PIXEL_ARRAY_DIV) }, }; static const struct v4l2_ctrl_config vivid_ctrl_s32_array = { diff --git a/drivers/media/test-drivers/vivid/vivid-vid-cap.c b/drivers/media/test-drivers/vivid/vivid-vid-cap.c index 84e9155b5815..2e4c1ed37cd2 100644 --- a/drivers/media/test-drivers/vivid/vivid-vid-cap.c +++ b/drivers/media/test-drivers/vivid/vivid-vid-cap.c @@ -454,8 +454,8 @@ void vivid_update_format_cap(struct vivid_dev *dev, bool keep_controls) if (keep_controls) return; - dims[0] = roundup(dev->src_rect.width, PIXEL_ARRAY_DIV); - dims[1] = roundup(dev->src_rect.height, PIXEL_ARRAY_DIV); + dims[0] = DIV_ROUND_UP(dev->src_rect.height, PIXEL_ARRAY_DIV); + dims[1] = DIV_ROUND_UP(dev->src_rect.width, PIXEL_ARRAY_DIV); v4l2_ctrl_modify_dimensions(dev->pixel_array, dims); } From ff49672a28f3a856717f09d61380e524e243121f Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Tue, 3 Jun 2025 00:08:51 +0300 Subject: [PATCH 266/310] media: ipu6: isys: Use correct pads for xlate_streams() The pad argument to v4l2_subdev_state_xlate_streams() is incorrect, static pad number is used for the source pad even though the pad number is dependent on the stream. Fix it. Fixes: 3a5c59ad926b ("media: ipu6: Rework CSI-2 sub-device streaming control") Cc: stable@vger.kernel.org Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c index da8581a37e22..6030bd23b4b9 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c @@ -354,9 +354,9 @@ static int ipu6_isys_csi2_enable_streams(struct v4l2_subdev *sd, remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); - sink_streams = v4l2_subdev_state_xlate_streams(state, CSI2_PAD_SRC, - CSI2_PAD_SINK, - &streams_mask); + sink_streams = + v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, + &streams_mask); ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); if (ret) @@ -384,9 +384,9 @@ static int ipu6_isys_csi2_disable_streams(struct v4l2_subdev *sd, struct media_pad *remote_pad; u64 sink_streams; - sink_streams = v4l2_subdev_state_xlate_streams(state, CSI2_PAD_SRC, - CSI2_PAD_SINK, - &streams_mask); + sink_streams = + v4l2_subdev_state_xlate_streams(state, pad, CSI2_PAD_SINK, + &streams_mask); remote_pad = media_pad_remote_pad_first(&sd->entity.pads[CSI2_PAD_SINK]); remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); From 09ef95213880e32dc0e9612bae023e61c69b392e Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Fri, 9 May 2025 18:47:36 +0300 Subject: [PATCH 267/310] media: ipu6: isys: Set minimum height to 1 For image data generally 2 seems like a minimum height that surely won't cause any issues, but some sensors have metadata the height of which is just one line. Set the minimum height to 1. Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/pci/intel/ipu6/ipu6-isys.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h index f488e782c26e..0e2c8b71edfc 100644 --- a/drivers/media/pci/intel/ipu6/ipu6-isys.h +++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h @@ -40,7 +40,7 @@ struct ipu6_bus_device; #define IPU6_ISYS_NUM_RECV_QUEUE 1 #define IPU6_ISYS_MIN_WIDTH 2U -#define IPU6_ISYS_MIN_HEIGHT 2U +#define IPU6_ISYS_MIN_HEIGHT 1U #define IPU6_ISYS_MAX_WIDTH 4672U #define IPU6_ISYS_MAX_HEIGHT 3416U From 7a8212f3bcfab753602599da644fb5bdeaf8ff2d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 11 Jun 2025 15:40:42 +0300 Subject: [PATCH 268/310] media: v4l: Make media_entity_to_video_device() NULL-safe Make media_entity_to_video_device(NULL) return NULL, instead of an invalid pointer value. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- include/media/v4l2-dev.h | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/include/media/v4l2-dev.h b/include/media/v4l2-dev.h index 1b6222fab24e..a69801274800 100644 --- a/include/media/v4l2-dev.h +++ b/include/media/v4l2-dev.h @@ -313,10 +313,16 @@ struct video_device { * media_entity_to_video_device - Returns a &struct video_device from * the &struct media_entity embedded on it. * - * @__entity: pointer to &struct media_entity + * @__entity: pointer to &struct media_entity, may be NULL */ -#define media_entity_to_video_device(__entity) \ - container_of(__entity, struct video_device, entity) +#define media_entity_to_video_device(__entity) \ +({ \ + typeof(__entity) __me_vdev_ent = __entity; \ + \ + __me_vdev_ent ? \ + container_of(__me_vdev_ent, struct video_device, entity) : \ + NULL; \ +}) /** * to_video_device - Returns a &struct video_device from the From f98691668ecd644ce63d20d8dd7baf892a6b873d Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 4 Jun 2025 00:32:36 +0300 Subject: [PATCH 269/310] media: v4l2-subdev: Print early in v4l2_subdev_{enable,disable}_streams() Print debug messages early in v4l2_subdev_enable_streams() and v4l2_subdev_disable_streams(), before sanity checks take place. This can help figuring out why something goes wrong, in driver development or otherwise. Also print the name of the sub-device where streaming is to be enabled or disabled. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-subdev.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index c69d1aff701f..7b1a67a52efa 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -2285,6 +2285,9 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, bool use_s_stream; int ret; + dev_dbg(dev, "enable streams \"%s\":%u/%#llx\n", sd->entity.name, pad, + streams_mask); + /* A few basic sanity checks first. */ if (pad >= sd->entity.num_pads) return -EINVAL; @@ -2332,8 +2335,6 @@ int v4l2_subdev_enable_streams(struct v4l2_subdev *sd, u32 pad, goto done; } - dev_dbg(dev, "enable streams %u:%#llx\n", pad, streams_mask); - already_streaming = v4l2_subdev_is_streaming(sd); if (!use_s_stream) { @@ -2385,6 +2386,9 @@ int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad, bool use_s_stream; int ret; + dev_dbg(dev, "disable streams \"%s\":%u/%#llx\n", sd->entity.name, pad, + streams_mask); + /* A few basic sanity checks first. */ if (pad >= sd->entity.num_pads) return -EINVAL; @@ -2432,8 +2436,6 @@ int v4l2_subdev_disable_streams(struct v4l2_subdev *sd, u32 pad, goto done; } - dev_dbg(dev, "disable streams %u:%#llx\n", pad, streams_mask); - if (!use_s_stream) { /* Call the .disable_streams() operation. */ ret = v4l2_subdev_call(sd, pad, disable_streams, state, pad, From 0bb005ac7268cc414bc216ad78adb63c281fc862 Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Wed, 4 Jun 2025 00:57:57 +0300 Subject: [PATCH 270/310] media: v4l2-subdev: Add debug prints to v4l2_subdev_collect_streams() Print streams found by v4l2_subdev_collect_streams() at debug level. This could be useful in debugging drivers, userspace programs or the framework itself. Signed-off-by: Sakari Ailus Reviewed-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-subdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-subdev.c b/drivers/media/v4l2-core/v4l2-subdev.c index 7b1a67a52efa..4fd25fea3b58 100644 --- a/drivers/media/v4l2-core/v4l2-subdev.c +++ b/drivers/media/v4l2-core/v4l2-subdev.c @@ -2233,6 +2233,9 @@ static void v4l2_subdev_collect_streams(struct v4l2_subdev *sd, *found_streams = BIT_ULL(0); *enabled_streams = (sd->enabled_pads & BIT_ULL(pad)) ? BIT_ULL(0) : 0; + dev_dbg(sd->dev, + "collect_streams: sub-device \"%s\" does not support streams\n", + sd->entity.name); return; } @@ -2250,6 +2253,10 @@ static void v4l2_subdev_collect_streams(struct v4l2_subdev *sd, if (cfg->enabled) *enabled_streams |= BIT_ULL(cfg->stream); } + + dev_dbg(sd->dev, + "collect_streams: \"%s\":%u: found %#llx enabled %#llx\n", + sd->entity.name, pad, *found_streams, *enabled_streams); } static void v4l2_subdev_set_streams_enabled(struct v4l2_subdev *sd, From afba5eaf69c1aad199638d830af9c2eb3f8c2e6e Mon Sep 17 00:00:00 2001 From: Yemike Abhilash Chandra Date: Wed, 16 Apr 2025 17:49:37 +0530 Subject: [PATCH 271/310] dt-bindings: media: cdns,csi2rx.yaml: Add optional interrupts for cdns-csi2rx The Cadence CSI2RX IP exposes 2 interrupts [0] 12.7 camera subsystem. So, add support for optional interrupts and interrupt-names properties. [0]: http://www.ti.com/lit/pdf/spruil1 Acked-by: Krzysztof Kozlowski Reviewed-by: Jai Luthra Signed-off-by: Yemike Abhilash Chandra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/cdns,csi2rx.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Documentation/devicetree/bindings/media/cdns,csi2rx.yaml b/Documentation/devicetree/bindings/media/cdns,csi2rx.yaml index 2008a47c0580..6ed9a5621064 100644 --- a/Documentation/devicetree/bindings/media/cdns,csi2rx.yaml +++ b/Documentation/devicetree/bindings/media/cdns,csi2rx.yaml @@ -24,6 +24,14 @@ properties: reg: maxItems: 1 + interrupts: + maxItems: 2 + + interrupt-names: + items: + - const: error_irq + - const: irq + clocks: items: - description: CSI2Rx system clock From 9f2d0da9b8c3477cd5ed4e1ed1e58bb44184561b Mon Sep 17 00:00:00 2001 From: Yemike Abhilash Chandra Date: Wed, 16 Apr 2025 17:49:38 +0530 Subject: [PATCH 272/310] media: cadence: csi2rx: Enable csi2rx_err_irq interrupt and add support for VIDIOC_LOG_STATUS Enable the csi2rx_err_irq interrupt to record any errors during streaming and also add support for VIDIOC_LOG_STATUS ioctl. This allows users to retrieve detailed error information during streaming, including FIFO overflow, packet errors, and ECC errors. Signed-off-by: Yemike Abhilash Chandra Reviewed-by: Changhuang Liang Tested-by: Jai Luthra Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/cadence/cdns-csi2rx.c | 131 +++++++++++++++++++ 1 file changed, 131 insertions(+) diff --git a/drivers/media/platform/cadence/cdns-csi2rx.c b/drivers/media/platform/cadence/cdns-csi2rx.c index cebcae196eec..7f1ce95cdc3f 100644 --- a/drivers/media/platform/cadence/cdns-csi2rx.c +++ b/drivers/media/platform/cadence/cdns-csi2rx.c @@ -57,6 +57,25 @@ #define CSI2RX_LANES_MAX 4 #define CSI2RX_STREAMS_MAX 4 +#define CSI2RX_ERROR_IRQS_REG 0x28 +#define CSI2RX_ERROR_IRQS_MASK_REG 0x2C + +#define CSI2RX_STREAM3_FIFO_OVERFLOW_IRQ BIT(19) +#define CSI2RX_STREAM2_FIFO_OVERFLOW_IRQ BIT(18) +#define CSI2RX_STREAM1_FIFO_OVERFLOW_IRQ BIT(17) +#define CSI2RX_STREAM0_FIFO_OVERFLOW_IRQ BIT(16) +#define CSI2RX_FRONT_TRUNC_HDR_IRQ BIT(12) +#define CSI2RX_PROT_TRUNCATED_PACKET_IRQ BIT(11) +#define CSI2RX_FRONT_LP_NO_PAYLOAD_IRQ BIT(10) +#define CSI2RX_SP_INVALID_RCVD_IRQ BIT(9) +#define CSI2RX_DATA_ID_IRQ BIT(7) +#define CSI2RX_HEADER_CORRECTED_ECC_IRQ BIT(6) +#define CSI2RX_HEADER_ECC_IRQ BIT(5) +#define CSI2RX_PAYLOAD_CRC_IRQ BIT(4) + +#define CSI2RX_ECC_ERRORS GENMASK(7, 4) +#define CSI2RX_PACKET_ERRORS GENMASK(12, 9) + enum csi2rx_pads { CSI2RX_PAD_SINK, CSI2RX_PAD_SOURCE_STREAM0, @@ -71,9 +90,32 @@ struct csi2rx_fmt { u8 bpp; }; +struct csi2rx_event { + u32 mask; + const char *name; +}; + +static const struct csi2rx_event csi2rx_events[] = { + { CSI2RX_STREAM3_FIFO_OVERFLOW_IRQ, "Overflow of the Stream 3 FIFO detected" }, + { CSI2RX_STREAM2_FIFO_OVERFLOW_IRQ, "Overflow of the Stream 2 FIFO detected" }, + { CSI2RX_STREAM1_FIFO_OVERFLOW_IRQ, "Overflow of the Stream 1 FIFO detected" }, + { CSI2RX_STREAM0_FIFO_OVERFLOW_IRQ, "Overflow of the Stream 0 FIFO detected" }, + { CSI2RX_FRONT_TRUNC_HDR_IRQ, "A truncated header [short or long] has been received" }, + { CSI2RX_PROT_TRUNCATED_PACKET_IRQ, "A truncated long packet has been received" }, + { CSI2RX_FRONT_LP_NO_PAYLOAD_IRQ, "A truncated long packet has been received. No payload" }, + { CSI2RX_SP_INVALID_RCVD_IRQ, "A reserved or invalid short packet has been received" }, + { CSI2RX_DATA_ID_IRQ, "Data ID error in the header packet" }, + { CSI2RX_HEADER_CORRECTED_ECC_IRQ, "ECC error detected and corrected" }, + { CSI2RX_HEADER_ECC_IRQ, "Unrecoverable ECC error" }, + { CSI2RX_PAYLOAD_CRC_IRQ, "CRC error" }, +}; + +#define CSI2RX_NUM_EVENTS ARRAY_SIZE(csi2rx_events) + struct csi2rx_priv { struct device *dev; unsigned int count; + int error_irq; /* * Used to prevent race conditions between multiple, @@ -95,6 +137,7 @@ struct csi2rx_priv { u8 max_lanes; u8 max_streams; bool has_internal_dphy; + u32 events[CSI2RX_NUM_EVENTS]; struct v4l2_subdev subdev; struct v4l2_async_notifier notifier; @@ -124,6 +167,54 @@ static const struct csi2rx_fmt formats[] = { { .code = MEDIA_BUS_FMT_BGR888_1X24, .bpp = 24, }, }; +static void csi2rx_configure_error_irq_mask(void __iomem *base, + struct csi2rx_priv *csi2rx) +{ + u32 error_irq_mask = 0; + + error_irq_mask |= CSI2RX_ECC_ERRORS; + error_irq_mask |= CSI2RX_PACKET_ERRORS; + + /* + * Iterate through all source pads and check if they are linked + * to an active remote pad. If an active remote pad is found, + * calculate the corresponding bit position and set it in + * mask, enabling the stream overflow error in the mask. + */ + for (int i = CSI2RX_PAD_SOURCE_STREAM0; i < CSI2RX_PAD_MAX; i++) { + struct media_pad *remote_pad; + + remote_pad = media_pad_remote_pad_first(&csi2rx->pads[i]); + if (remote_pad) { + int pad = i - CSI2RX_PAD_SOURCE_STREAM0; + u32 bit_mask = CSI2RX_STREAM0_FIFO_OVERFLOW_IRQ << pad; + + error_irq_mask |= bit_mask; + } + } + + writel(error_irq_mask, base + CSI2RX_ERROR_IRQS_MASK_REG); +} + +static irqreturn_t csi2rx_irq_handler(int irq, void *dev_id) +{ + struct csi2rx_priv *csi2rx = dev_id; + int i; + u32 error_status, error_mask; + + error_status = readl(csi2rx->base + CSI2RX_ERROR_IRQS_REG); + error_mask = readl(csi2rx->base + CSI2RX_ERROR_IRQS_MASK_REG); + + for (i = 0; i < CSI2RX_NUM_EVENTS; i++) + if ((error_status & csi2rx_events[i].mask) && + (error_mask & csi2rx_events[i].mask)) + csi2rx->events[i]++; + + writel(error_status, csi2rx->base + CSI2RX_ERROR_IRQS_REG); + + return IRQ_HANDLED; +} + static const struct csi2rx_fmt *csi2rx_get_fmt_by_code(u32 code) { unsigned int i; @@ -220,6 +311,9 @@ static int csi2rx_start(struct csi2rx_priv *csi2rx) reset_control_deassert(csi2rx->p_rst); csi2rx_reset(csi2rx); + if (csi2rx->error_irq >= 0) + csi2rx_configure_error_irq_mask(csi2rx->base, csi2rx); + reg = csi2rx->num_lanes << 8; for (i = 0; i < csi2rx->num_lanes; i++) { reg |= CSI2RX_STATIC_CFG_DLANE_MAP(i, csi2rx->lanes[i]); @@ -332,6 +426,8 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx) reset_control_assert(csi2rx->sys_rst); clk_disable_unprepare(csi2rx->sys_clk); + writel(0, csi2rx->base + CSI2RX_ERROR_IRQS_MASK_REG); + for (i = 0; i < csi2rx->max_streams; i++) { writel(CSI2RX_STREAM_CTRL_STOP, csi2rx->base + CSI2RX_STREAM_CTRL_REG(i)); @@ -363,6 +459,21 @@ static void csi2rx_stop(struct csi2rx_priv *csi2rx) } } +static int csi2rx_log_status(struct v4l2_subdev *sd) +{ + struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(sd); + unsigned int i; + + for (i = 0; i < CSI2RX_NUM_EVENTS; i++) { + if (csi2rx->events[i]) + dev_info(csi2rx->dev, "%s events: %d\n", + csi2rx_events[i].name, + csi2rx->events[i]); + } + + return 0; +} + static int csi2rx_s_stream(struct v4l2_subdev *subdev, int enable) { struct csi2rx_priv *csi2rx = v4l2_subdev_to_csi2rx(subdev); @@ -468,7 +579,12 @@ static const struct v4l2_subdev_video_ops csi2rx_video_ops = { .s_stream = csi2rx_s_stream, }; +static const struct v4l2_subdev_core_ops csi2rx_core_ops = { + .log_status = csi2rx_log_status, +}; + static const struct v4l2_subdev_ops csi2rx_subdev_ops = { + .core = &csi2rx_core_ops, .video = &csi2rx_video_ops, .pad = &csi2rx_pad_ops, }; @@ -705,6 +821,21 @@ static int csi2rx_probe(struct platform_device *pdev) if (ret) goto err_cleanup; + csi2rx->error_irq = platform_get_irq_byname_optional(pdev, "error_irq"); + + if (csi2rx->error_irq < 0) { + dev_dbg(csi2rx->dev, "Optional interrupt not defined, proceeding without it\n"); + } else { + ret = devm_request_irq(csi2rx->dev, csi2rx->error_irq, + csi2rx_irq_handler, 0, + dev_name(&pdev->dev), csi2rx); + if (ret) { + dev_err(csi2rx->dev, + "Unable to request interrupt: %d\n", ret); + goto err_cleanup; + } + } + ret = v4l2_subdev_init_finalize(&csi2rx->subdev); if (ret) goto err_cleanup; From 2260d1fa5d0e7967d6596e26bc39d46200cede0c Mon Sep 17 00:00:00 2001 From: Devarsh Thakkar Date: Wed, 12 Mar 2025 16:51:40 +0530 Subject: [PATCH 273/310] media: ti: j721e-csi2rx: Allow passing cache hints from user-space User-space can provide cache hints to enable software managed cache operations or skip certain cache operations using memory flags and buffer flags. This is useful for SoCs such as AM62px which do not support hardware cache coherency. This is tested on AM62px with yavta capture by passing V4L2_MEMORY_FLAG_NON_COHERENT while using VIDIOC_REQBUFS and V4L2_BUF_FLAG_NO_CACHE_CLEAN while using VIDIOC_QBUF and ~5x reduction in memcpy time is seen for copying captured frame to DDR. Link: https://e2e.ti.com/support/processors-group/processors/f/processors-forum/1452545/am62p-the-ads6311-radar-camera-has-lost-frame-capture/5580472#5580472 Signed-off-by: Devarsh Thakkar Acked-by: Jai Luthra [Sakari Ailus: Reword and rewrap the commit message a little.] Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c index 6412a00be8ea..284d33b2a0ec 100644 --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -895,6 +895,7 @@ static int ti_csi2rx_init_vb2q(struct ti_csi2rx_dev *csi) q->dev = dmaengine_get_dma_device(csi->dma.chan); q->lock = &csi->mutex; q->min_queued_buffers = 1; + q->allow_cache_hints = 1; ret = vb2_queue_init(q); if (ret) From 1fff2ee377e1c2230054e65092def460dd40b587 Mon Sep 17 00:00:00 2001 From: Mehdi Djait Date: Mon, 23 Jun 2025 15:51:15 +0200 Subject: [PATCH 274/310] media: uapi: videodev2: Fix comment for 12-bit packed Bayer formats For 12-bit packed Bayer formats: every two consecutive samples are packed into three bytes. Fix the corresponding comment. Signed-off-by: Mehdi Djait Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- include/uapi/linux/videodev2.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 6f7bd38dd5aa..1bb1979f6c18 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -726,7 +726,7 @@ struct v4l2_pix_format { #define V4L2_PIX_FMT_SGBRG12 v4l2_fourcc('G', 'B', '1', '2') /* 12 GBGB.. RGRG.. */ #define V4L2_PIX_FMT_SGRBG12 v4l2_fourcc('B', 'A', '1', '2') /* 12 GRGR.. BGBG.. */ #define V4L2_PIX_FMT_SRGGB12 v4l2_fourcc('R', 'G', '1', '2') /* 12 RGRG.. GBGB.. */ - /* 12bit raw bayer packed, 6 bytes for every 4 pixels */ + /* 12bit raw bayer packed, 3 bytes for every 2 pixels */ #define V4L2_PIX_FMT_SBGGR12P v4l2_fourcc('p', 'B', 'C', 'C') #define V4L2_PIX_FMT_SGBRG12P v4l2_fourcc('p', 'G', 'C', 'C') #define V4L2_PIX_FMT_SGRBG12P v4l2_fourcc('p', 'g', 'C', 'C') From 00e0b9455fb4c74fc777289a129f646b4cb112f8 Mon Sep 17 00:00:00 2001 From: Mehdi Djait Date: Mon, 23 Jun 2025 15:51:16 +0200 Subject: [PATCH 275/310] media: v4l2-common: Add the missing Raw Bayer pixel formats Add the Raw Bayer pixel formats infos for: 10-bit packed Bayer formats 12-bit packed Bayer formats 14-bit Bayer formats 14-bit packed Bayer formats 16-bit Bayer formats Signed-off-by: Mehdi Djait Reviewed-by: Laurent Pinchart Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-common.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index df40756ac8f6..a9bd3ca0f281 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -332,6 +332,10 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_SGBRG10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGRBG10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SRGGB10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SBGGR10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGBRG10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGRBG10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SRGGB10P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 5, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SBGGR10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGBRG10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGRBG10ALAW8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, @@ -344,6 +348,22 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_SGBRG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGRBG12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SRGGB12, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SBGGR12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGBRG12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGRBG12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SRGGB12P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 3, 0, 0, 0 }, .bpp_div = { 2, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SBGGR14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGBRG14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGRBG14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SRGGB14, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SBGGR14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGBRG14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGRBG14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SRGGB14P, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 7, 0, 0, 0 }, .bpp_div = { 4, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SBGGR16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGBRG16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SGRBG16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, + { .format = V4L2_PIX_FMT_SRGGB16, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 2, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, /* Renesas Camera Data Receiver Unit formats, bayer order agnostic */ { .format = V4L2_PIX_FMT_RAW_CRU10, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 8, 0, 0, 0 }, .bpp_div = { 6, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, From 99f2211a9d89fe34b3fa847fd7a4475171406cd0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 May 2025 21:05:33 +0200 Subject: [PATCH 276/310] media: hi556: Fix reset GPIO timings probe() requests the reset GPIO to be set to high when getting it. Immeditately after this hi556_resume() is called and sets the GPIO low. If the GPIO was low before requesting it this will result in the GPIO only very briefly spiking high and the sensor not being properly reset. The same problem also happens on back to back runtime suspend + resume. Fix this by adding a sleep of 2 ms in hi556_resume() before setting the GPIO low (if there is a reset GPIO). The final sleep is kept unconditional, because if there is e.g. no reset GPIO but a controllable clock then the sensor also needs some time after enabling the clock. Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/hi556.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c index aed258211b8a..d3cc65b67855 100644 --- a/drivers/media/i2c/hi556.c +++ b/drivers/media/i2c/hi556.c @@ -1321,7 +1321,12 @@ static int hi556_resume(struct device *dev) return ret; } - gpiod_set_value_cansleep(hi556->reset_gpio, 0); + if (hi556->reset_gpio) { + /* Assert reset for at least 2ms on back to back off-on */ + usleep_range(2000, 2200); + gpiod_set_value_cansleep(hi556->reset_gpio, 0); + } + usleep_range(5000, 5500); return 0; } From 375fc903e57cb3ca4d2d5408de98d6369d4c8334 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 31 May 2025 21:05:34 +0200 Subject: [PATCH 277/310] media: hi556: Support full range of power rails Use regulator_bulk_* to get the array of potential power rails for the hi556. Previously the driver only supported avdd as only avdd is used on IPU6 designs. But other designs may also need the driver to control the other power rails and the new INT3472 handshake support also makes use of dvdd on IPU6 designs. Link: https://bugzilla.redhat.com/show_bug.cgi?id=2368506 Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/hi556.c | 40 +++++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c index d3cc65b67855..110dd00c51ae 100644 --- a/drivers/media/i2c/hi556.c +++ b/drivers/media/i2c/hi556.c @@ -624,6 +624,12 @@ static const struct hi556_mode supported_modes[] = { }, }; +static const char * const hi556_supply_names[] = { + "dovdd", /* Digital I/O power */ + "avdd", /* Analog power */ + "dvdd", /* Digital core power */ +}; + struct hi556 { struct v4l2_subdev sd; struct media_pad pad; @@ -639,7 +645,7 @@ struct hi556 { /* GPIOs, clocks, etc. */ struct gpio_desc *reset_gpio; struct clk *clk; - struct regulator *avdd; + struct regulator_bulk_data supplies[ARRAY_SIZE(hi556_supply_names)]; /* Current mode */ const struct hi556_mode *cur_mode; @@ -1289,17 +1295,10 @@ static int hi556_suspend(struct device *dev) { struct v4l2_subdev *sd = dev_get_drvdata(dev); struct hi556 *hi556 = to_hi556(sd); - int ret; gpiod_set_value_cansleep(hi556->reset_gpio, 1); - - ret = regulator_disable(hi556->avdd); - if (ret) { - dev_err(dev, "failed to disable avdd: %d\n", ret); - gpiod_set_value_cansleep(hi556->reset_gpio, 0); - return ret; - } - + regulator_bulk_disable(ARRAY_SIZE(hi556_supply_names), + hi556->supplies); clk_disable_unprepare(hi556->clk); return 0; } @@ -1314,9 +1313,10 @@ static int hi556_resume(struct device *dev) if (ret) return ret; - ret = regulator_enable(hi556->avdd); + ret = regulator_bulk_enable(ARRAY_SIZE(hi556_supply_names), + hi556->supplies); if (ret) { - dev_err(dev, "failed to enable avdd: %d\n", ret); + dev_err(dev, "failed to enable regulators: %d", ret); clk_disable_unprepare(hi556->clk); return ret; } @@ -1335,7 +1335,7 @@ static int hi556_probe(struct i2c_client *client) { struct hi556 *hi556; bool full_power; - int ret; + int i, ret; ret = hi556_check_hwcfg(&client->dev); if (ret) @@ -1358,11 +1358,15 @@ static int hi556_probe(struct i2c_client *client) return dev_err_probe(&client->dev, PTR_ERR(hi556->clk), "failed to get clock\n"); - /* The regulator core will provide a "dummy" regulator if necessary */ - hi556->avdd = devm_regulator_get(&client->dev, "avdd"); - if (IS_ERR(hi556->avdd)) - return dev_err_probe(&client->dev, PTR_ERR(hi556->avdd), - "failed to get avdd regulator\n"); + for (i = 0; i < ARRAY_SIZE(hi556_supply_names); i++) + hi556->supplies[i].supply = hi556_supply_names[i]; + + ret = devm_regulator_bulk_get(&client->dev, + ARRAY_SIZE(hi556_supply_names), + hi556->supplies); + if (ret) + return dev_err_probe(&client->dev, ret, + "failed to get regulators\n"); full_power = acpi_dev_state_d0(&client->dev); if (full_power) { From 871a99ff4d726485eefa6ab55bc84a72ef219e6a Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Thu, 3 Jul 2025 17:03:15 +0200 Subject: [PATCH 278/310] media: i2c: Automatically select common options for lens drivers In commit 7d3c7d2a2914 ("media: i2c: Add a camera sensor top level menu") a top level menu was added for sensor drivers so that all sensor drivers would depend on I2C and so that MEDIA_CONTROLLER, V4L2_FWNODE and VIDEO_V4L2_SUBDEV_API would be automatically selected for all sensor drivers. All lens drivers must depend on I2C and VIDEO_DEV and must select MEDIA_CONTROLLER, V4L2_FWNODE and VIDEO_V4L2_SUBDEV_API and most already do, but e.g. VIDEO_V4L2_SUBDEV_API is not consistenly selected. Change the "Lens drivers" menu into a menuconfig option with the necessary depends and selects. This ensures that these options are depended on / selected consistently and simplifies the Kconfig snippets for the various lens voice coil drivers. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202411061152.VKd9JYpa-lkp@intel.com/ Signed-off-by: Hans de Goede Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/Kconfig | 38 ++++++++++++-------------------------- 1 file changed, 12 insertions(+), 26 deletions(-) diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig index 4b4c199da6ea..6237fe804a5c 100644 --- a/drivers/media/i2c/Kconfig +++ b/drivers/media/i2c/Kconfig @@ -766,24 +766,25 @@ config VIDEO_THP7312 endmenu -menu "Lens drivers" - visible if MEDIA_CAMERA_SUPPORT +menuconfig VIDEO_CAMERA_LENS + bool "Lens drivers" + depends on MEDIA_CAMERA_SUPPORT && I2C + select MEDIA_CONTROLLER + select V4L2_FWNODE + select VIDEO_V4L2_SUBDEV_API + default y + +if VIDEO_CAMERA_LENS config VIDEO_AD5820 tristate "AD5820 lens voice coil support" - depends on GPIOLIB && I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select V4L2_ASYNC + depends on GPIOLIB help This is a driver for the AD5820 camera lens voice coil. It is used for example in Nokia N900 (RX-51). config VIDEO_AK7375 tristate "AK7375 lens voice coil support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC help This is a driver for the AK7375 camera lens voice coil. AK7375 is a 12 bit DAC with 120mA output current sink @@ -792,10 +793,7 @@ config VIDEO_AK7375 config VIDEO_DW9714 tristate "DW9714 lens voice coil support" - depends on GPIOLIB && I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC + depends on GPIOLIB help This is a driver for the DW9714 camera lens voice coil. DW9714 is a 10 bit DAC with 120mA output current sink @@ -804,10 +802,6 @@ config VIDEO_DW9714 config VIDEO_DW9719 tristate "DW9719 lens voice coil support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC select V4L2_CCI_I2C help This is a driver for the DW9719 camera lens voice coil. @@ -816,10 +810,6 @@ config VIDEO_DW9719 config VIDEO_DW9768 tristate "DW9768 lens voice coil support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_FWNODE help This is a driver for the DW9768 camera lens voice coil. DW9768 is a 10 bit DAC with 100mA output current sink @@ -828,17 +818,13 @@ config VIDEO_DW9768 config VIDEO_DW9807_VCM tristate "DW9807 lens voice coil support" - depends on I2C && VIDEO_DEV - select MEDIA_CONTROLLER - select VIDEO_V4L2_SUBDEV_API - select V4L2_ASYNC help This is a driver for the DW9807 camera lens voice coil. DW9807 is a 10 bit DAC with 100mA output current sink capability. This is designed for linear control of voice coil motors, controlled via I2C serial interface. -endmenu +endif menu "Flash devices" visible if MEDIA_CAMERA_SUPPORT From 020f602b068c9ce18d5056d02c8302199377d98d Mon Sep 17 00:00:00 2001 From: Bingbu Cao Date: Mon, 30 Jun 2025 17:04:20 +0800 Subject: [PATCH 279/310] media: hi556: correct the test pattern configuration Hynix hi556 support 8 test pattern modes: hi556_test_pattern_menu[] = { { "Disabled", "Solid Colour", "100% Colour Bars", "Fade To Grey Colour Bars", "PN9", "Gradient Horizontal", "Gradient Vertical", "Check Board", "Slant Pattern", } The test pattern is set by a 8-bit register according to the specification. +--------+-------------------------------+ | BIT[0] | Solid color | +--------+-------------------------------+ | BIT[1] | Color bar | +--------+-------------------------------+ | BIT[2] | Fade to grey color bar | +--------+-------------------------------+ | BIT[3] | PN9 | +--------+-------------------------------+ | BIT[4] | Gradient horizontal | +--------+-------------------------------+ | BIT[5] | Gradient vertical | +--------+-------------------------------+ | BIT[6] | Check board | +--------+-------------------------------+ | BIT[7] | Slant pattern | +--------+-------------------------------+ Based on function above, current test pattern programming is wrong. This patch fixes it by 'BIT(pattern - 1)'. If pattern is 0, driver will disable the test pattern generation and set the pattern to 0. Fixes: e62138403a84 ("media: hi556: Add support for Hi-556 sensor") Cc: stable@vger.kernel.org Signed-off-by: Bingbu Cao Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/hi556.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c index 110dd00c51ae..076c19fcf99c 100644 --- a/drivers/media/i2c/hi556.c +++ b/drivers/media/i2c/hi556.c @@ -762,21 +762,23 @@ static int hi556_test_pattern(struct hi556 *hi556, u32 pattern) int ret; u32 val; - if (pattern) { - ret = hi556_read_reg(hi556, HI556_REG_ISP, - HI556_REG_VALUE_08BIT, &val); - if (ret) - return ret; + ret = hi556_read_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, &val); + if (ret) + return ret; - ret = hi556_write_reg(hi556, HI556_REG_ISP, - HI556_REG_VALUE_08BIT, - val | HI556_REG_ISP_TPG_EN); - if (ret) - return ret; - } + val = pattern ? (val | HI556_REG_ISP_TPG_EN) : + (val & ~HI556_REG_ISP_TPG_EN); + + ret = hi556_write_reg(hi556, HI556_REG_ISP, + HI556_REG_VALUE_08BIT, val); + if (ret) + return ret; + + val = pattern ? BIT(pattern - 1) : 0; return hi556_write_reg(hi556, HI556_REG_TEST_PATTERN, - HI556_REG_VALUE_08BIT, pattern); + HI556_REG_VALUE_08BIT, val); } static int hi556_set_ctrl(struct v4l2_ctrl *ctrl) From ae42c6fe531425ef2f47e82f96851427d24bbf6b Mon Sep 17 00:00:00 2001 From: Julien Massot Date: Mon, 30 Jun 2025 12:46:43 +0200 Subject: [PATCH 280/310] media: ti: j721e-csi2rx: fix list_del corruption If ti_csi2rx_start_dma() fails in ti_csi2rx_dma_callback(), the buffer is marked done with VB2_BUF_STATE_ERROR but is not removed from the DMA queue. This causes the same buffer to be retried in the next iteration, resulting in a double list_del() and eventual list corruption. Fix this by removing the buffer from the queue before calling vb2_buffer_done() on error. This resolves a crash due to list_del corruption: [ 37.811243] j721e-csi2rx 30102000.ticsi2rx: Failed to queue the next buffer for DMA [ 37.832187] slab kmalloc-2k start ffff00000255b000 pointer offset 1064 size 2048 [ 37.839761] list_del corruption. next->prev should be ffff00000255bc28, but was ffff00000255d428. (next=ffff00000255b428) [ 37.850799] ------------[ cut here ]------------ [ 37.855424] kernel BUG at lib/list_debug.c:65! [ 37.859876] Internal error: Oops - BUG: 00000000f2000800 [#1] SMP [ 37.866061] Modules linked in: i2c_dev usb_f_rndis u_ether libcomposite dwc3 udc_core usb_common aes_ce_blk aes_ce_cipher ghash_ce gf128mul sha1_ce cpufreq_dt dwc3_am62 phy_gmii_sel sa2ul [ 37.882830] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 6.16.0-rc3+ #28 VOLUNTARY [ 37.890851] Hardware name: Bosch STLA-GSRV2-B0 (DT) [ 37.895737] pstate: 600000c5 (nZCv daIF -PAN -UAO -TCO -DIT -SSBS BTYPE=--) [ 37.902703] pc : __list_del_entry_valid_or_report+0xdc/0x114 [ 37.908390] lr : __list_del_entry_valid_or_report+0xdc/0x114 [ 37.914059] sp : ffff800080003db0 [ 37.917375] x29: ffff800080003db0 x28: 0000000000000007 x27: ffff800080e50000 [ 37.924521] x26: 0000000000000000 x25: ffff0000016abb50 x24: dead000000000122 [ 37.931666] x23: ffff0000016abb78 x22: ffff0000016ab080 x21: ffff800080003de0 [ 37.938810] x20: ffff00000255bc00 x19: ffff00000255b800 x18: 000000000000000a [ 37.945956] x17: 20747562202c3832 x16: 6362353532303030 x15: 0720072007200720 [ 37.953101] x14: 0720072007200720 x13: 0720072007200720 x12: 00000000ffffffea [ 37.960248] x11: ffff800080003b18 x10: 00000000ffffefff x9 : ffff800080f5b568 [ 37.967396] x8 : ffff800080f5b5c0 x7 : 0000000000017fe8 x6 : c0000000ffffefff [ 37.974542] x5 : ffff00000fea6688 x4 : 0000000000000000 x3 : 0000000000000000 [ 37.981686] x2 : 0000000000000000 x1 : ffff800080ef2b40 x0 : 000000000000006d [ 37.988832] Call trace: [ 37.991281] __list_del_entry_valid_or_report+0xdc/0x114 (P) [ 37.996959] ti_csi2rx_dma_callback+0x84/0x1c4 [ 38.001419] udma_vchan_complete+0x1e0/0x344 [ 38.005705] tasklet_action_common+0x118/0x310 [ 38.010163] tasklet_action+0x30/0x3c [ 38.013832] handle_softirqs+0x10c/0x2e0 [ 38.017761] __do_softirq+0x14/0x20 [ 38.021256] ____do_softirq+0x10/0x20 [ 38.024931] call_on_irq_stack+0x24/0x60 [ 38.028873] do_softirq_own_stack+0x1c/0x40 [ 38.033064] __irq_exit_rcu+0x130/0x15c [ 38.036909] irq_exit_rcu+0x10/0x20 [ 38.040403] el1_interrupt+0x38/0x60 [ 38.043987] el1h_64_irq_handler+0x18/0x24 [ 38.048091] el1h_64_irq+0x6c/0x70 [ 38.051501] default_idle_call+0x34/0xe0 (P) [ 38.055783] do_idle+0x1f8/0x250 [ 38.059021] cpu_startup_entry+0x34/0x3c [ 38.062951] rest_init+0xb4/0xc0 [ 38.066186] console_on_rootfs+0x0/0x6c [ 38.070031] __primary_switched+0x88/0x90 [ 38.074059] Code: b00037e0 91378000 f9400462 97e9bf49 (d4210000) [ 38.080168] ---[ end trace 0000000000000000 ]--- [ 38.084795] Kernel panic - not syncing: Oops - BUG: Fatal exception in interrupt [ 38.092197] SMP: stopping secondary CPUs [ 38.096139] Kernel Offset: disabled [ 38.099631] CPU features: 0x0000,00002000,02000801,0400420b [ 38.105202] Memory Limit: none [ 38.108260] ---[ end Kernel panic - not syncing: Oops - BUG: Fatal exception in interrupt ]--- Fixes: b4a3d877dc92 ("media: ti: Add CSI2RX support for J721E") Cc: stable@vger.kernel.org Suggested-by: Sjoerd Simons Signed-off-by: Sjoerd Simons Signed-off-by: Julien Massot Reviewed-by: Jai Luthra Tested-by: Dirk Behme Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c index 284d33b2a0ec..b628d6e081db 100644 --- a/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c +++ b/drivers/media/platform/ti/j721e-csi2rx/j721e-csi2rx.c @@ -619,6 +619,7 @@ static void ti_csi2rx_dma_callback(void *param) if (ti_csi2rx_start_dma(csi, buf)) { dev_err(csi->dev, "Failed to queue the next buffer for DMA\n"); + list_del(&buf->list); vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR); } else { list_move_tail(&buf->list, &dma->submitted); From 76142b137b968d47b35cdd8d1dc924677d319c8b Mon Sep 17 00:00:00 2001 From: Zhang Shurong Date: Sun, 6 Jul 2025 00:31:09 +0800 Subject: [PATCH 281/310] media: ov2659: Fix memory leaks in ov2659_probe() ov2659_probe() doesn't properly free control handler resources in failure paths, causing memory leaks. Add v4l2_ctrl_handler_free() to prevent these memory leaks and reorder the ctrl_handler assignment for better code flow. Fixes: c4c0283ab3cd ("[media] media: i2c: add support for omnivision's ov2659 sensor") Cc: stable@vger.kernel.org Signed-off-by: Zhang Shurong Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/ov2659.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/media/i2c/ov2659.c b/drivers/media/i2c/ov2659.c index 06b7896c3eaf..586b31ba076b 100644 --- a/drivers/media/i2c/ov2659.c +++ b/drivers/media/i2c/ov2659.c @@ -1469,14 +1469,15 @@ static int ov2659_probe(struct i2c_client *client) V4L2_CID_TEST_PATTERN, ARRAY_SIZE(ov2659_test_pattern_menu) - 1, 0, 0, ov2659_test_pattern_menu); - ov2659->sd.ctrl_handler = &ov2659->ctrls; if (ov2659->ctrls.error) { dev_err(&client->dev, "%s: control initialization error %d\n", __func__, ov2659->ctrls.error); + v4l2_ctrl_handler_free(&ov2659->ctrls); return ov2659->ctrls.error; } + ov2659->sd.ctrl_handler = &ov2659->ctrls; sd = &ov2659->sd; client->flags |= I2C_CLIENT_SCCB; From 2946bac895e3f1d9a1a97a726339ed3e4d439d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Thu, 3 Jul 2025 23:42:06 +0200 Subject: [PATCH 282/310] media: i2c: imx290: Remove unneeded assignment of subdev device pointer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The core helper v4l2_i2c_subdev_init() already assignees the subdevices dev pointer to the device associated with the i2c client passed to it. And 'imx290->dev' is assigned to '&client->dev' already in probe before calling imx290_subdev_init(). Remove the duplicated open-coded assignment in the driver, there is no reason to do it twice. Signed-off-by: Niklas Söderlund Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/media/i2c/imx290.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/media/i2c/imx290.c b/drivers/media/i2c/imx290.c index fbf7eba3d71d..4f3f386c5353 100644 --- a/drivers/media/i2c/imx290.c +++ b/drivers/media/i2c/imx290.c @@ -1294,7 +1294,6 @@ static int imx290_subdev_init(struct imx290 *imx290) * will already be prevented even before the delay. */ v4l2_i2c_subdev_init(&imx290->sd, client, &imx290_subdev_ops); - imx290->sd.dev = imx290->dev; pm_runtime_mark_last_busy(imx290->dev); pm_runtime_put_autosuspend(imx290->dev); From 6af7e00a92e04e0ea41bbe4b83deac9bb11fe4c5 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 9 Jul 2025 16:56:56 +0200 Subject: [PATCH 283/310] media: staging/ipu7: avoid division by 64-bit value On 32-bit targets, this causes a link failure: x86_64-linux-ld: drivers/staging/media/ipu7/ipu7-isys-csi-phy.o: in function `ipu7_isys_phy_config': ipu7-isys-csi-phy.c:(.text+0x1509): undefined reference to `__udivdi3' Note that this does not divide a 64-bit number by a 32-bit one as usual, but the other way round, which is something that the compiler should really be able to figure out but does not (as of gcc-15). A few lines higher, a similar division is done using the incorrect div_u64() that truncates the 64-bit divisor to 32 bits. Change both to use the safe but slow div64_u64() helper. Fixes: a516d36bdc3d ("media: staging/ipu7: add IPU7 input system device driver") Signed-off-by: Arnd Bergmann Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/ipu7-isys-csi-phy.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c index 4407750c7f40..b8c5db7ae300 100644 --- a/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c +++ b/drivers/staging/media/ipu7/ipu7-isys-csi-phy.c @@ -734,6 +734,7 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, u16 reg; u16 val; u32 i; + u64 r64; u32 r; if (is_ipu7p5(isys->adev->isp->hw_ver)) @@ -806,8 +807,8 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, dwc_phy_write_mask(isys, id, reg, 2, 0, 2); } - deass_thresh = (u16)div_u64_rem(7 * 1000 * 6, mbps * 5U, &r) + 1; - if (r != 0) + deass_thresh = (u16)div64_u64_rem(7 * 1000 * 6, mbps * 5U, &r64) + 1; + if (r64 != 0) deass_thresh++; reg = CORE_DIG_RW_TRIO0_2; @@ -815,8 +816,7 @@ static void ipu7_isys_cphy_config(struct ipu7_isys *isys, u8 id, u8 lanes, dwc_phy_write_mask(isys, id, reg + 0x400 * i, deass_thresh, 0, 7); - delay_thresh = - ((224U - (9U * 7U)) * 1000U) / (5U * mbps) - 7U; + delay_thresh = div64_u64((224U - (9U * 7U)) * 1000U, 5U * mbps) - 7u; if (delay_thresh < 1) delay_thresh = 1; From eb6ab8a997ad7425d118fb73d327dac7456b3621 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Wed, 9 Jul 2025 16:57:31 +0200 Subject: [PATCH 284/310] media: staging/ipu7: add CONFIG_PCI dependency This driver fails to build when PCI is disabled: drivers/staging/media/ipu7/ipu7.c: In function 'ipu7_pci_config_setup': drivers/staging/media/ipu7/ipu7.c:2260:15: error: implicit declaration of function 'pci_enable_msi'; did you mean 'pci_enable_sriov'? [-Wimplicit-function-declaration] drivers/staging/media/ipu7/ipu7.c:2775:1: error: data definition has no type or storage class [-Werror] driver(ipu7_pci_driver); drivers/staging/media/ipu7/ipu7.c:2764:26: error: 'ipu7_pci_driver' defined but not used [-Werror=unused-variable] Add the required Kconfig dependency. Signed-off-by: Arnd Bergmann Signed-off-by: Sakari Ailus Signed-off-by: Hans Verkuil --- drivers/staging/media/ipu7/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/staging/media/ipu7/Kconfig b/drivers/staging/media/ipu7/Kconfig index 5b800e9b09c4..7d831ba7501d 100644 --- a/drivers/staging/media/ipu7/Kconfig +++ b/drivers/staging/media/ipu7/Kconfig @@ -4,6 +4,7 @@ config VIDEO_INTEL_IPU7 depends on VIDEO_DEV depends on X86 && HAS_DMA depends on IPU_BRIDGE || !IPU_BRIDGE + depends on PCI select AUXILIARY_BUS select IOMMU_IOVA select VIDEO_V4L2_SUBDEV_API From 95703a099e094c00a0714f4d6fa6d9f142ff3fda Mon Sep 17 00:00:00 2001 From: Sakari Ailus Date: Mon, 23 Jun 2025 10:06:19 +0300 Subject: [PATCH 285/310] media: ivsc: Add MAINTAINERS entry The IVSC drivers seem to have never had a MAINTAINERS entry so add one now. Signed-off-by: Sakari Ailus Acked-by: Zhang Lixu Acked-by: Bingbu Cao Signed-off-by: Hans Verkuil --- MAINTAINERS | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 658543062bba..1ef99240a57e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -12533,6 +12533,16 @@ W: https://wireless.wiki.kernel.org/en/users/drivers/iwlwifi T: git git://git.kernel.org/pub/scm/linux/kernel/git/iwlwifi/iwlwifi.git F: drivers/net/wireless/intel/iwlwifi/ +INTEL VISION SENSING CONTROLLER DRIVER +M: Sakari Ailus +R: Bingbu Cao +R: Lixu Zhang +R: Stanislaw Gruszka +L: linux-media@vger.kernel.org +S: Maintained +T: git git://linuxtv.org/media.git +F: drivers/media/pci/intel/ivsc/ + INTEL WMI SLIM BOOTLOADER (SBL) FIRMWARE UPDATE DRIVER M: Jithu Joseph S: Maintained From 145437cc545271e1fa3072ef9e899551547a9822 Mon Sep 17 00:00:00 2001 From: Tomi Valkeinen Date: Fri, 30 May 2025 16:50:30 +0300 Subject: [PATCH 286/310] media: rcar-csi2: Use the pad version of v4l2_get_link_freq() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pad-aware version of v4l2_get_link_freq() tries to retrieve the link frequency from the media bus configuration using the get_mbus_config operation, and only if the subdevice does not implement this operation falls back to the old method of getting it using the V4L2_CID_LINK_FREQ or V4L2_CID_PIXEL_RATE control. Update the VIN driver to use the pad-aware version to be able to support subdevices that only provides the link frequency in the media bus configuration. As the implementation falls back to the old method if the subdevice doesn't support get_mbus_config, or doesn't provide a link frequency in the v4l2_mbus_config struct, this is fully backward compatible. Signed-off-by: Tomi Valkeinen Reviewed-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250530-rcar-streams-v3-1-026655df7138@ideasonboard.com Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/renesas/rcar-csi2.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/media/platform/renesas/rcar-csi2.c b/drivers/media/platform/renesas/rcar-csi2.c index 0350eb76df36..d1b31ab8b8c4 100644 --- a/drivers/media/platform/renesas/rcar-csi2.c +++ b/drivers/media/platform/renesas/rcar-csi2.c @@ -962,6 +962,7 @@ static int rcsi2_set_phypll(struct rcar_csi2 *priv, unsigned int mbps) static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp, unsigned int lanes) { + struct media_pad *remote_pad; struct v4l2_subdev *source; s64 freq; u64 mbps; @@ -970,8 +971,9 @@ static int rcsi2_calc_mbps(struct rcar_csi2 *priv, unsigned int bpp, return -ENODEV; source = priv->remote; + remote_pad = &source->entity.pads[priv->remote_pad]; - freq = v4l2_get_link_freq(source->ctrl_handler, bpp, 2 * lanes); + freq = v4l2_get_link_freq(remote_pad, bpp, 2 * lanes); if (freq < 0) { int ret = (int)freq; From 78c633ae02988688c80029b898ce334f3392cb42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Sat, 5 Jul 2025 10:37:39 +0200 Subject: [PATCH 287/310] media: adv7180: Remove g_pixelaspect implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are no consumer left of g_pixelaspect in the tree, remove the implementation from the adv7180 driver in preparation of removing it from struct v4l2_subdev_video_ops all together. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250705083741.77517-2-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv7180.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/drivers/media/i2c/adv7180.c b/drivers/media/i2c/adv7180.c index 6e50b14f888f..5d90b8ab9b6d 100644 --- a/drivers/media/i2c/adv7180.c +++ b/drivers/media/i2c/adv7180.c @@ -868,21 +868,6 @@ static int adv7180_get_skip_frames(struct v4l2_subdev *sd, u32 *frames) return 0; } -static int adv7180_g_pixelaspect(struct v4l2_subdev *sd, struct v4l2_fract *aspect) -{ - struct adv7180_state *state = to_state(sd); - - if (state->curr_norm & V4L2_STD_525_60) { - aspect->numerator = 11; - aspect->denominator = 10; - } else { - aspect->numerator = 54; - aspect->denominator = 59; - } - - return 0; -} - static int adv7180_g_tvnorms(struct v4l2_subdev *sd, v4l2_std_id *norm) { *norm = V4L2_STD_ALL; @@ -929,7 +914,6 @@ static const struct v4l2_subdev_video_ops adv7180_video_ops = { .querystd = adv7180_querystd, .g_input_status = adv7180_g_input_status, .s_routing = adv7180_s_routing, - .g_pixelaspect = adv7180_g_pixelaspect, .g_tvnorms = adv7180_g_tvnorms, .s_stream = adv7180_s_stream, }; From 7dd0d6d15c29a78e3cef31546e3b12f3818b63ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Sat, 5 Jul 2025 10:37:40 +0200 Subject: [PATCH 288/310] media: adv748x: Remove g_pixelaspect implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are no consumer left of g_pixelaspect in the tree, remove the implementation from the adv748x driver in preparation of removing it from struct v4l2_subdev_video_ops all together. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250705083741.77517-3-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/i2c/adv748x/adv748x-afe.c | 17 ----------------- drivers/media/i2c/adv748x/adv748x-hdmi.c | 10 ---------- 2 files changed, 27 deletions(-) diff --git a/drivers/media/i2c/adv748x/adv748x-afe.c b/drivers/media/i2c/adv748x/adv748x-afe.c index 5edb3295dc58..678199196b84 100644 --- a/drivers/media/i2c/adv748x/adv748x-afe.c +++ b/drivers/media/i2c/adv748x/adv748x-afe.c @@ -161,22 +161,6 @@ int adv748x_afe_s_input(struct adv748x_afe *afe, unsigned int input) return sdp_write(state, ADV748X_SDP_INSEL, input); } -static int adv748x_afe_g_pixelaspect(struct v4l2_subdev *sd, - struct v4l2_fract *aspect) -{ - struct adv748x_afe *afe = adv748x_sd_to_afe(sd); - - if (afe->curr_norm & V4L2_STD_525_60) { - aspect->numerator = 11; - aspect->denominator = 10; - } else { - aspect->numerator = 54; - aspect->denominator = 59; - } - - return 0; -} - /* ----------------------------------------------------------------------------- * v4l2_subdev_video_ops */ @@ -307,7 +291,6 @@ static const struct v4l2_subdev_video_ops adv748x_afe_video_ops = { .g_tvnorms = adv748x_afe_g_tvnorms, .g_input_status = adv748x_afe_g_input_status, .s_stream = adv748x_afe_s_stream, - .g_pixelaspect = adv748x_afe_g_pixelaspect, }; /* ----------------------------------------------------------------------------- diff --git a/drivers/media/i2c/adv748x/adv748x-hdmi.c b/drivers/media/i2c/adv748x/adv748x-hdmi.c index a4db9bae5f79..b154dea29ba2 100644 --- a/drivers/media/i2c/adv748x/adv748x-hdmi.c +++ b/drivers/media/i2c/adv748x/adv748x-hdmi.c @@ -382,19 +382,9 @@ static int adv748x_hdmi_s_stream(struct v4l2_subdev *sd, int enable) return ret; } -static int adv748x_hdmi_g_pixelaspect(struct v4l2_subdev *sd, - struct v4l2_fract *aspect) -{ - aspect->numerator = 1; - aspect->denominator = 1; - - return 0; -} - static const struct v4l2_subdev_video_ops adv748x_video_ops_hdmi = { .g_input_status = adv748x_hdmi_g_input_status, .s_stream = adv748x_hdmi_s_stream, - .g_pixelaspect = adv748x_hdmi_g_pixelaspect, }; /* ----------------------------------------------------------------------------- From c9596e882032827a5c7f99a5ca481cd46251a473 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20S=C3=B6derlund?= Date: Sat, 5 Jul 2025 10:37:41 +0200 Subject: [PATCH 289/310] media: v4l2-subdev: Remove g_pixelaspect operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There are no consumers or implementations left in tree for the subdevice operation g_pixelaspect, delete it. Signed-off-by: Niklas Söderlund Reviewed-by: Laurent Pinchart Link: https://lore.kernel.org/r/20250705083741.77517-4-niklas.soderlund+renesas@ragnatech.se Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- include/media/v4l2-subdev.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/include/media/v4l2-subdev.h b/include/media/v4l2-subdev.h index 57f2bcb4eb16..5dcf4065708f 100644 --- a/include/media/v4l2-subdev.h +++ b/include/media/v4l2-subdev.h @@ -460,8 +460,6 @@ enum v4l2_subdev_pre_streamon_flags { * but use the v4l2_subdev_enable_streams() and * v4l2_subdev_disable_streams() helpers. * - * @g_pixelaspect: callback to return the pixelaspect ratio. - * * @s_rx_buffer: set a host allocated memory buffer for the subdev. The subdev * can adjust @size to a lower value and must not write more data to the * buffer starting at @data than the original value of @size. @@ -491,7 +489,6 @@ struct v4l2_subdev_video_ops { int (*g_tvnorms_output)(struct v4l2_subdev *sd, v4l2_std_id *std); int (*g_input_status)(struct v4l2_subdev *sd, u32 *status); int (*s_stream)(struct v4l2_subdev *sd, int enable); - int (*g_pixelaspect)(struct v4l2_subdev *sd, struct v4l2_fract *aspect); int (*s_rx_buffer)(struct v4l2_subdev *sd, void *buf, unsigned int *size); int (*pre_streamon)(struct v4l2_subdev *sd, u32 flags); From cee06ca7a6748f7d5d7ea40876dcbc0af26bf34e Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Wed, 25 Jun 2025 10:22:32 -0500 Subject: [PATCH 290/310] media: imx8mq-mipi-csi2: Fix error code in imx8mq_mipi_csi_parse_dt() This was returning IS_ERR() where PTR_ERR() was intended. Fixes: 642b70d526ab ("media: imx8mq-mipi-csi2: Add support for i.MX8QXP") Cc: stable@vger.kernel.org Signed-off-by: Dan Carpenter Reviewed-by: Laurent Pinchart Reviewed-by: Frank Li Reviewed-by: Rui Miguel Silva Link: https://lore.kernel.org/r/9b6c7925-c9c4-44bd-acd5-1ef0e698eb87@sabinyo.mountain Signed-off-by: Laurent Pinchart Signed-off-by: Hans Verkuil --- drivers/media/platform/nxp/imx8mq-mipi-csi2.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c index 6501843ae72d..3a4645f59a44 100644 --- a/drivers/media/platform/nxp/imx8mq-mipi-csi2.c +++ b/drivers/media/platform/nxp/imx8mq-mipi-csi2.c @@ -966,7 +966,7 @@ static int imx8mq_mipi_csi_parse_dt(struct csi_state *state) base = devm_platform_ioremap_resource(to_platform_device(dev), 1); if (IS_ERR(base)) - return dev_err_probe(dev, IS_ERR(base), "Missing CSR register\n"); + return dev_err_probe(dev, PTR_ERR(base), "Missing CSR register\n"); state->phy_gpr = devm_regmap_init_mmio(dev, base, ®map_config); if (IS_ERR(state->phy_gpr)) From c93d73c9c2cfa7658f7100d201a47c4856746222 Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Mon, 16 Jun 2025 15:24:38 +0000 Subject: [PATCH 291/310] media: uvcvideo: Use vb2 ioctl and fop helpers When uvc was written the vb2 ioctl and file operation helpers didn't exist. This patch switches uvc over to those helpers, which removes a lot of boilerplate code and allows us to drop the 'privileges' scheme, since that's now handled inside the vb2 helpers. This makes it possible for uvc to fix the v4l2-compliance streaming tests: warn: v4l2-test-formats.cpp(1075): Could not set fmt2 This patch introduces a change on behavior on the uvcdriver to be aligned with the rest of the subsystem. Now S_INPUT, S_PARM and S_FORMAT do no grant exclusive ownership of the device. There are other side effects, some better than others: - Locking is now more coarse than before, the queue is locked for almost every ioctl. - vidioc_querybuf() can now work when the queue is busy. Future patches should look into the locking architecture of UVC to remove one of stream->mutex or queue->mutex. Reviewed-by: Hans de Goede Reviewed-by: Hans Verkuil Co-developed-by: Ricardo Ribalda Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250616-uvc-fop-v4-1-250286570ee7@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 37 +--- drivers/media/usb/uvc/uvc_metadata.c | 8 +- drivers/media/usb/uvc/uvc_queue.c | 143 --------------- drivers/media/usb/uvc/uvc_v4l2.c | 252 +++------------------------ drivers/media/usb/uvc/uvcvideo.h | 36 +--- 5 files changed, 34 insertions(+), 442 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index 62eb45435d8b..accfb4ca3c72 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -1961,31 +1961,7 @@ static void uvc_unregister_video(struct uvc_device *dev) if (!video_is_registered(&stream->vdev)) continue; - /* - * For stream->vdev we follow the same logic as: - * vb2_video_unregister_device(). - */ - - /* 1. Take a reference to vdev */ - get_device(&stream->vdev.dev); - - /* 2. Ensure that no new ioctls can be called. */ - video_unregister_device(&stream->vdev); - - /* 3. Wait for old ioctls to finish. */ - mutex_lock(&stream->mutex); - - /* 4. Stop streaming. */ - uvc_queue_release(&stream->queue); - - mutex_unlock(&stream->mutex); - - put_device(&stream->vdev.dev); - - /* - * For stream->meta.vdev we can directly call: - * vb2_video_unregister_device(). - */ + vb2_video_unregister_device(&stream->vdev); vb2_video_unregister_device(&stream->meta.vdev); /* @@ -2033,6 +2009,8 @@ int uvc_register_video_device(struct uvc_device *dev, vdev->ioctl_ops = ioctl_ops; vdev->release = uvc_release; vdev->prio = &stream->chain->prio; + vdev->queue = &queue->queue; + vdev->lock = &queue->mutex; if (type == V4L2_BUF_TYPE_VIDEO_OUTPUT) vdev->vfl_dir = VFL_DIR_TX; else @@ -2397,9 +2375,12 @@ static int __uvc_resume(struct usb_interface *intf, int reset) list_for_each_entry(stream, &dev->streams, list) { if (stream->intf == intf) { ret = uvc_video_resume(stream, reset); - if (ret < 0) - uvc_queue_streamoff(&stream->queue, - stream->queue.queue.type); + if (ret < 0) { + mutex_lock(&stream->queue.mutex); + vb2_streamoff(&stream->queue.queue, + stream->queue.queue.type); + mutex_unlock(&stream->queue.mutex); + } return ret; } } diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c index 82de7781f5b6..d3aab22f91ce 100644 --- a/drivers/media/usb/uvc/uvc_metadata.c +++ b/drivers/media/usb/uvc/uvc_metadata.c @@ -96,7 +96,7 @@ static int uvc_meta_v4l2_set_format(struct file *file, void *fh, */ mutex_lock(&stream->mutex); - if (uvc_queue_allocated(&stream->queue)) + if (vb2_is_busy(&stream->meta.queue.queue)) ret = -EBUSY; else stream->meta.format = fmt->dataformat; @@ -164,12 +164,6 @@ int uvc_meta_register(struct uvc_streaming *stream) stream->meta.format = V4L2_META_FMT_UVC; - /* - * The video interface queue uses manual locking and thus does not set - * the queue pointer. Set it manually here. - */ - vdev->queue = &queue->queue; - return uvc_register_video_device(dev, stream, vdev, queue, V4L2_BUF_TYPE_META_CAPTURE, &uvc_meta_fops, &uvc_meta_ioctl_ops); diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 2ee142621042..72c5494dee9f 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -242,153 +242,10 @@ int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type) return 0; } -void uvc_queue_release(struct uvc_video_queue *queue) -{ - mutex_lock(&queue->mutex); - vb2_queue_release(&queue->queue); - mutex_unlock(&queue->mutex); -} - -/* ----------------------------------------------------------------------------- - * V4L2 queue operations - */ - -int uvc_request_buffers(struct uvc_video_queue *queue, - struct v4l2_requestbuffers *rb) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_reqbufs(&queue->queue, rb); - mutex_unlock(&queue->mutex); - - return ret ? ret : rb->count; -} - -int uvc_query_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_querybuf(&queue->queue, buf); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_create_buffers(struct uvc_video_queue *queue, - struct v4l2_create_buffers *cb) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_create_bufs(&queue->queue, cb); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_queue_buffer(struct uvc_video_queue *queue, - struct media_device *mdev, struct v4l2_buffer *buf) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_qbuf(&queue->queue, mdev, buf); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_export_buffer(struct uvc_video_queue *queue, - struct v4l2_exportbuffer *exp) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_expbuf(&queue->queue, exp); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_dequeue_buffer(struct uvc_video_queue *queue, struct v4l2_buffer *buf, - int nonblocking) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_dqbuf(&queue->queue, buf, nonblocking); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_streamon(&queue->queue, type); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type) -{ - int ret; - - mutex_lock(&queue->mutex); - ret = vb2_streamoff(&queue->queue, type); - mutex_unlock(&queue->mutex); - - return ret; -} - -int uvc_queue_mmap(struct uvc_video_queue *queue, struct vm_area_struct *vma) -{ - return vb2_mmap(&queue->queue, vma); -} - -#ifndef CONFIG_MMU -unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue, - unsigned long pgoff) -{ - return vb2_get_unmapped_area(&queue->queue, 0, 0, pgoff, 0); -} -#endif - -__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file, - poll_table *wait) -{ - __poll_t ret; - - mutex_lock(&queue->mutex); - ret = vb2_poll(&queue->queue, file, wait); - mutex_unlock(&queue->mutex); - - return ret; -} - /* ----------------------------------------------------------------------------- * */ -/* - * Check if buffers have been allocated. - */ -int uvc_queue_allocated(struct uvc_video_queue *queue) -{ - int allocated; - - mutex_lock(&queue->mutex); - allocated = vb2_is_busy(&queue->queue); - mutex_unlock(&queue->mutex); - - return allocated; -} - /* * Cancel the video buffers queue. * diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 1abdf1ea3995..d06ecf3418a9 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -47,8 +47,6 @@ void uvc_pm_put(struct uvc_device *dev) usb_autopm_put_interface(dev->intf); } -static int uvc_acquire_privileges(struct uvc_fh *handle); - static int uvc_control_add_xu_mapping(struct uvc_video_chain *chain, struct uvc_control_mapping *map, const struct uvc_xu_control_mapping *xmap) @@ -436,10 +434,6 @@ static int uvc_ioctl_s_fmt(struct file *file, void *fh, const struct uvc_frame *frame; int ret; - ret = uvc_acquire_privileges(handle); - if (ret < 0) - return ret; - if (fmt->type != stream->type) return -EINVAL; @@ -448,8 +442,7 @@ static int uvc_ioctl_s_fmt(struct file *file, void *fh, return ret; mutex_lock(&stream->mutex); - - if (uvc_queue_allocated(&stream->queue)) { + if (vb2_is_busy(&stream->queue.queue)) { ret = -EBUSY; goto done; } @@ -513,10 +506,6 @@ static int uvc_ioctl_s_parm(struct file *file, void *fh, unsigned int i; int ret; - ret = uvc_acquire_privileges(handle); - if (ret < 0) - return ret; - if (parm->type != stream->type) return -EINVAL; @@ -593,63 +582,6 @@ static int uvc_ioctl_s_parm(struct file *file, void *fh, return 0; } -/* ------------------------------------------------------------------------ - * Privilege management - */ - -/* - * Privilege management is the multiple-open implementation basis. The current - * implementation is completely transparent for the end-user and doesn't - * require explicit use of the VIDIOC_G_PRIORITY and VIDIOC_S_PRIORITY ioctls. - * Those ioctls enable finer control on the device (by making possible for a - * user to request exclusive access to a device), but are not mature yet. - * Switching to the V4L2 priority mechanism might be considered in the future - * if this situation changes. - * - * Each open instance of a UVC device can either be in a privileged or - * unprivileged state. Only a single instance can be in a privileged state at - * a given time. Trying to perform an operation that requires privileges will - * automatically acquire the required privileges if possible, or return -EBUSY - * otherwise. Privileges are dismissed when closing the instance or when - * freeing the video buffers using VIDIOC_REQBUFS. - * - * Operations that require privileges are: - * - * - VIDIOC_S_INPUT - * - VIDIOC_S_PARM - * - VIDIOC_S_FMT - * - VIDIOC_CREATE_BUFS - * - VIDIOC_REQBUFS - */ -static int uvc_acquire_privileges(struct uvc_fh *handle) -{ - /* Always succeed if the handle is already privileged. */ - if (handle->state == UVC_HANDLE_ACTIVE) - return 0; - - /* Check if the device already has a privileged handle. */ - if (atomic_inc_return(&handle->stream->active) != 1) { - atomic_dec(&handle->stream->active); - return -EBUSY; - } - - handle->state = UVC_HANDLE_ACTIVE; - return 0; -} - -static void uvc_dismiss_privileges(struct uvc_fh *handle) -{ - if (handle->state == UVC_HANDLE_ACTIVE) - atomic_dec(&handle->stream->active); - - handle->state = UVC_HANDLE_PASSIVE; -} - -static int uvc_has_privileges(struct uvc_fh *handle) -{ - return handle->state == UVC_HANDLE_ACTIVE; -} - /* ------------------------------------------------------------------------ * V4L2 file operations */ @@ -671,7 +603,6 @@ static int uvc_v4l2_open(struct file *file) v4l2_fh_add(&handle->vfh); handle->chain = stream->chain; handle->stream = stream; - handle->state = UVC_HANDLE_PASSIVE; file->private_data = handle; return 0; @@ -686,19 +617,11 @@ static int uvc_v4l2_release(struct file *file) uvc_ctrl_cleanup_fh(handle); - /* Only free resources if this is a privileged handle. */ - if (uvc_has_privileges(handle)) - uvc_queue_release(&stream->queue); - if (handle->is_streaming) uvc_pm_put(stream->dev); /* Release the file handle. */ - uvc_dismiss_privileges(handle); - v4l2_fh_del(&handle->vfh); - v4l2_fh_exit(&handle->vfh); - kfree(handle); - file->private_data = NULL; + vb2_fop_release(file); return 0; } @@ -753,91 +676,6 @@ static int uvc_ioctl_try_fmt(struct file *file, void *fh, return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL); } -static int uvc_ioctl_reqbufs(struct file *file, void *fh, - struct v4l2_requestbuffers *rb) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - int ret; - - ret = uvc_acquire_privileges(handle); - if (ret < 0) - return ret; - - mutex_lock(&stream->mutex); - ret = uvc_request_buffers(&stream->queue, rb); - mutex_unlock(&stream->mutex); - if (ret < 0) - return ret; - - if (ret == 0) - uvc_dismiss_privileges(handle); - - return 0; -} - -static int uvc_ioctl_querybuf(struct file *file, void *fh, - struct v4l2_buffer *buf) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - - if (!uvc_has_privileges(handle)) - return -EBUSY; - - return uvc_query_buffer(&stream->queue, buf); -} - -static int uvc_ioctl_qbuf(struct file *file, void *fh, struct v4l2_buffer *buf) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - - if (!uvc_has_privileges(handle)) - return -EBUSY; - - return uvc_queue_buffer(&stream->queue, - stream->vdev.v4l2_dev->mdev, buf); -} - -static int uvc_ioctl_expbuf(struct file *file, void *fh, - struct v4l2_exportbuffer *exp) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - - if (!uvc_has_privileges(handle)) - return -EBUSY; - - return uvc_export_buffer(&stream->queue, exp); -} - -static int uvc_ioctl_dqbuf(struct file *file, void *fh, struct v4l2_buffer *buf) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - - if (!uvc_has_privileges(handle)) - return -EBUSY; - - return uvc_dequeue_buffer(&stream->queue, buf, - file->f_flags & O_NONBLOCK); -} - -static int uvc_ioctl_create_bufs(struct file *file, void *fh, - struct v4l2_create_buffers *cb) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - int ret; - - ret = uvc_acquire_privileges(handle); - if (ret < 0) - return ret; - - return uvc_create_buffers(&stream->queue, cb); -} - static int uvc_ioctl_streamon(struct file *file, void *fh, enum v4l2_buf_type type) { @@ -845,11 +683,6 @@ static int uvc_ioctl_streamon(struct file *file, void *fh, struct uvc_streaming *stream = handle->stream; int ret; - if (!uvc_has_privileges(handle)) - return -EBUSY; - - guard(mutex)(&stream->mutex); - if (handle->is_streaming) return 0; @@ -857,7 +690,7 @@ static int uvc_ioctl_streamon(struct file *file, void *fh, if (ret) return ret; - ret = uvc_queue_streamon(&stream->queue, type); + ret = vb2_ioctl_streamon(file, fh, type); if (ret) { uvc_pm_put(stream->dev); return ret; @@ -873,13 +706,12 @@ static int uvc_ioctl_streamoff(struct file *file, void *fh, { struct uvc_fh *handle = fh; struct uvc_streaming *stream = handle->stream; + int ret; - if (!uvc_has_privileges(handle)) - return -EBUSY; + ret = vb2_ioctl_streamoff(file, fh, type); + if (ret) + return ret; - guard(mutex)(&stream->mutex); - - uvc_queue_streamoff(&stream->queue, type); if (handle->is_streaming) { handle->is_streaming = false; uvc_pm_put(stream->dev); @@ -962,13 +794,13 @@ static int uvc_ioctl_g_input(struct file *file, void *fh, unsigned int *input) static int uvc_ioctl_s_input(struct file *file, void *fh, unsigned int input) { struct uvc_fh *handle = fh; + struct uvc_streaming *stream = handle->stream; struct uvc_video_chain *chain = handle->chain; u8 *buf; int ret; - ret = uvc_acquire_privileges(handle); - if (ret < 0) - return ret; + if (vb2_is_busy(&stream->queue.queue)) + return -EBUSY; if (chain->selector == NULL || (chain->dev->quirks & UVC_QUIRK_IGNORE_SELECTOR_UNIT)) { @@ -1469,50 +1301,6 @@ static long uvc_v4l2_unlocked_ioctl(struct file *file, return ret; } -static ssize_t uvc_v4l2_read(struct file *file, char __user *data, - size_t count, loff_t *ppos) -{ - struct uvc_fh *handle = file->private_data; - struct uvc_streaming *stream = handle->stream; - - uvc_dbg(stream->dev, CALLS, "%s: not implemented\n", __func__); - return -EINVAL; -} - -static int uvc_v4l2_mmap(struct file *file, struct vm_area_struct *vma) -{ - struct uvc_fh *handle = file->private_data; - struct uvc_streaming *stream = handle->stream; - - uvc_dbg(stream->dev, CALLS, "%s\n", __func__); - - return uvc_queue_mmap(&stream->queue, vma); -} - -static __poll_t uvc_v4l2_poll(struct file *file, poll_table *wait) -{ - struct uvc_fh *handle = file->private_data; - struct uvc_streaming *stream = handle->stream; - - uvc_dbg(stream->dev, CALLS, "%s\n", __func__); - - return uvc_queue_poll(&stream->queue, file, wait); -} - -#ifndef CONFIG_MMU -static unsigned long uvc_v4l2_get_unmapped_area(struct file *file, - unsigned long addr, unsigned long len, unsigned long pgoff, - unsigned long flags) -{ - struct uvc_fh *handle = file->private_data; - struct uvc_streaming *stream = handle->stream; - - uvc_dbg(stream->dev, CALLS, "%s\n", __func__); - - return uvc_queue_get_unmapped_area(&stream->queue, pgoff); -} -#endif - const struct v4l2_ioctl_ops uvc_ioctl_ops = { .vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt, .vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt, @@ -1525,12 +1313,13 @@ const struct v4l2_ioctl_ops uvc_ioctl_ops = { .vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt, .vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt, .vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt, - .vidioc_reqbufs = uvc_ioctl_reqbufs, - .vidioc_querybuf = uvc_ioctl_querybuf, - .vidioc_qbuf = uvc_ioctl_qbuf, - .vidioc_expbuf = uvc_ioctl_expbuf, - .vidioc_dqbuf = uvc_ioctl_dqbuf, - .vidioc_create_bufs = uvc_ioctl_create_bufs, + .vidioc_reqbufs = vb2_ioctl_reqbufs, + .vidioc_querybuf = vb2_ioctl_querybuf, + .vidioc_prepare_buf = vb2_ioctl_prepare_buf, + .vidioc_qbuf = vb2_ioctl_qbuf, + .vidioc_expbuf = vb2_ioctl_expbuf, + .vidioc_dqbuf = vb2_ioctl_dqbuf, + .vidioc_create_bufs = vb2_ioctl_create_bufs, .vidioc_streamon = uvc_ioctl_streamon, .vidioc_streamoff = uvc_ioctl_streamoff, .vidioc_enum_input = uvc_ioctl_enum_input, @@ -1557,11 +1346,10 @@ const struct v4l2_file_operations uvc_fops = { #ifdef CONFIG_COMPAT .compat_ioctl32 = uvc_v4l2_compat_ioctl32, #endif - .read = uvc_v4l2_read, - .mmap = uvc_v4l2_mmap, - .poll = uvc_v4l2_poll, + .mmap = vb2_fop_mmap, + .poll = vb2_fop_poll, #ifndef CONFIG_MMU - .get_unmapped_area = uvc_v4l2_get_unmapped_area, + .get_unmapped_area = vb2_fop_get_unmapped_area, #endif }; diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 11d6e3c2ebdf..b300487e6ec9 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -328,7 +328,10 @@ struct uvc_buffer { struct uvc_video_queue { struct vb2_queue queue; - struct mutex mutex; /* Protects queue */ + struct mutex mutex; /* + * Serializes vb2_queue and + * fops + */ unsigned int flags; unsigned int buf_used; @@ -621,16 +624,10 @@ struct uvc_device { struct uvc_entity *gpio_unit; }; -enum uvc_handle_state { - UVC_HANDLE_PASSIVE = 0, - UVC_HANDLE_ACTIVE = 1, -}; - struct uvc_fh { struct v4l2_fh vfh; struct uvc_video_chain *chain; struct uvc_streaming *stream; - enum uvc_handle_state state; unsigned int pending_async_ctrls; bool is_streaming; }; @@ -689,36 +686,11 @@ struct uvc_entity *uvc_entity_by_id(struct uvc_device *dev, int id); /* Video buffers queue management. */ int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type); -void uvc_queue_release(struct uvc_video_queue *queue); -int uvc_request_buffers(struct uvc_video_queue *queue, - struct v4l2_requestbuffers *rb); -int uvc_query_buffer(struct uvc_video_queue *queue, - struct v4l2_buffer *v4l2_buf); -int uvc_create_buffers(struct uvc_video_queue *queue, - struct v4l2_create_buffers *v4l2_cb); -int uvc_queue_buffer(struct uvc_video_queue *queue, - struct media_device *mdev, - struct v4l2_buffer *v4l2_buf); -int uvc_export_buffer(struct uvc_video_queue *queue, - struct v4l2_exportbuffer *exp); -int uvc_dequeue_buffer(struct uvc_video_queue *queue, - struct v4l2_buffer *v4l2_buf, int nonblocking); -int uvc_queue_streamon(struct uvc_video_queue *queue, enum v4l2_buf_type type); -int uvc_queue_streamoff(struct uvc_video_queue *queue, enum v4l2_buf_type type); void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect); struct uvc_buffer *uvc_queue_next_buffer(struct uvc_video_queue *queue, struct uvc_buffer *buf); struct uvc_buffer *uvc_queue_get_current_buffer(struct uvc_video_queue *queue); void uvc_queue_buffer_release(struct uvc_buffer *buf); -int uvc_queue_mmap(struct uvc_video_queue *queue, - struct vm_area_struct *vma); -__poll_t uvc_queue_poll(struct uvc_video_queue *queue, struct file *file, - poll_table *wait); -#ifndef CONFIG_MMU -unsigned long uvc_queue_get_unmapped_area(struct uvc_video_queue *queue, - unsigned long pgoff); -#endif -int uvc_queue_allocated(struct uvc_video_queue *queue); static inline int uvc_queue_streaming(struct uvc_video_queue *queue) { return vb2_is_streaming(&queue->queue); From b7ef53670321327f45de8bde5d38f1215927336f Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:24:39 +0000 Subject: [PATCH 292/310] media: uvcvideo: Handle locks in uvc_queue_return_buffers Most of the calls to uvc_queue_return_buffers() wrap the call with spin_lock_irq()/spin_unlock_irq(). Rename uvc_queue_return_buffers to __uvc_queue_return_buffers to indicate that this is the version that does not handle locks and create a new version of the function that handles the lock. Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250616-uvc-fop-v4-2-250286570ee7@chromium.org Reviewed-by: Hans Verkuil Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_queue.c | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 72c5494dee9f..8f9737ac7295 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -42,13 +42,15 @@ static inline struct uvc_buffer *uvc_vbuf_to_buffer(struct vb2_v4l2_buffer *buf) * * This function must be called with the queue spinlock held. */ -static void uvc_queue_return_buffers(struct uvc_video_queue *queue, - enum uvc_buffer_state state) +static void __uvc_queue_return_buffers(struct uvc_video_queue *queue, + enum uvc_buffer_state state) { enum vb2_buffer_state vb2_state = state == UVC_BUF_STATE_ERROR ? VB2_BUF_STATE_ERROR : VB2_BUF_STATE_QUEUED; + lockdep_assert_held(&queue->irqlock); + while (!list_empty(&queue->irqqueue)) { struct uvc_buffer *buf = list_first_entry(&queue->irqqueue, struct uvc_buffer, @@ -59,6 +61,14 @@ static void uvc_queue_return_buffers(struct uvc_video_queue *queue, } } +static void uvc_queue_return_buffers(struct uvc_video_queue *queue, + enum uvc_buffer_state state) +{ + spin_lock_irq(&queue->irqlock); + __uvc_queue_return_buffers(queue, state); + spin_unlock_irq(&queue->irqlock); +} + /* ----------------------------------------------------------------------------- * videobuf2 queue operations */ @@ -171,9 +181,7 @@ static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count) if (ret == 0) return 0; - spin_lock_irq(&queue->irqlock); uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED); - spin_unlock_irq(&queue->irqlock); return ret; } @@ -187,9 +195,7 @@ static void uvc_stop_streaming(struct vb2_queue *vq) if (vq->type != V4L2_BUF_TYPE_META_CAPTURE) uvc_video_stop_streaming(uvc_queue_to_stream(queue)); - spin_lock_irq(&queue->irqlock); uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); - spin_unlock_irq(&queue->irqlock); } static const struct vb2_ops uvc_queue_qops = { @@ -263,7 +269,7 @@ void uvc_queue_cancel(struct uvc_video_queue *queue, int disconnect) unsigned long flags; spin_lock_irqsave(&queue->irqlock, flags); - uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); + __uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); /* * This must be protected by the irqlock spinlock to avoid race * conditions between uvc_buffer_queue and the disconnection event that From 54828c0d4f909bd706e15cfaa29758f2c1d59212 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:24:40 +0000 Subject: [PATCH 293/310] media: uvcvideo: Split uvc_stop_streaming() uvc_stop_streaming() is used for meta and video nodes. Split the function in two to avoid confusion. Use this opportunity to rename uvc_start_streaming() to uvc_start_streaming_video(), as it is only called by the video nodes. Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250616-uvc-fop-v4-3-250286570ee7@chromium.org Reviewed-by: Hans Verkuil Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_queue.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index 8f9737ac7295..b11f56d62ec0 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -167,7 +167,7 @@ static void uvc_buffer_finish(struct vb2_buffer *vb) uvc_video_clock_update(stream, vbuf, buf); } -static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count) +static int uvc_start_streaming_video(struct vb2_queue *vq, unsigned int count) { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); struct uvc_streaming *stream = uvc_queue_to_stream(queue); @@ -186,14 +186,22 @@ static int uvc_start_streaming(struct vb2_queue *vq, unsigned int count) return ret; } -static void uvc_stop_streaming(struct vb2_queue *vq) +static void uvc_stop_streaming_video(struct vb2_queue *vq) { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); lockdep_assert_irqs_enabled(); - if (vq->type != V4L2_BUF_TYPE_META_CAPTURE) - uvc_video_stop_streaming(uvc_queue_to_stream(queue)); + uvc_video_stop_streaming(uvc_queue_to_stream(queue)); + + uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); +} + +static void uvc_stop_streaming_meta(struct vb2_queue *vq) +{ + struct uvc_video_queue *queue = vb2_get_drv_priv(vq); + + lockdep_assert_irqs_enabled(); uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); } @@ -203,15 +211,20 @@ static const struct vb2_ops uvc_queue_qops = { .buf_prepare = uvc_buffer_prepare, .buf_queue = uvc_buffer_queue, .buf_finish = uvc_buffer_finish, - .start_streaming = uvc_start_streaming, - .stop_streaming = uvc_stop_streaming, + .start_streaming = uvc_start_streaming_video, + .stop_streaming = uvc_stop_streaming_video, }; static const struct vb2_ops uvc_meta_queue_qops = { .queue_setup = uvc_queue_setup, .buf_prepare = uvc_buffer_prepare, .buf_queue = uvc_buffer_queue, - .stop_streaming = uvc_stop_streaming, + /* + * .start_streaming is not provided here. Metadata relies on video + * streaming being active. If video isn't streaming, then no metadata + * will arrive either. + */ + .stop_streaming = uvc_stop_streaming_meta, }; int uvc_queue_init(struct uvc_video_queue *queue, enum v4l2_buf_type type) From 7dd56c47784a466b03df62ca766207f483353cdc Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 16 Jun 2025 15:24:41 +0000 Subject: [PATCH 294/310] media: uvcvideo: Remove stream->is_streaming field The is_streaming field is used by modular PM to know if the device is currently streaming or not. With the transition to vb2 and fop helpers, we can use vb2 functions for the same functionality. The great benefit is that vb2 already tracks the streaming state for us. Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250616-uvc-fop-v4-4-250286570ee7@chromium.org Reviewed-by: Hans Verkuil Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_queue.c | 9 ++++++ drivers/media/usb/uvc/uvc_v4l2.c | 51 ++----------------------------- drivers/media/usb/uvc/uvcvideo.h | 1 - 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_queue.c b/drivers/media/usb/uvc/uvc_queue.c index b11f56d62ec0..790184c9843d 100644 --- a/drivers/media/usb/uvc/uvc_queue.c +++ b/drivers/media/usb/uvc/uvc_queue.c @@ -175,12 +175,18 @@ static int uvc_start_streaming_video(struct vb2_queue *vq, unsigned int count) lockdep_assert_irqs_enabled(); + ret = uvc_pm_get(stream->dev); + if (ret) + return ret; + queue->buf_used = 0; ret = uvc_video_start_streaming(stream); if (ret == 0) return 0; + uvc_pm_put(stream->dev); + uvc_queue_return_buffers(queue, UVC_BUF_STATE_QUEUED); return ret; @@ -189,11 +195,14 @@ static int uvc_start_streaming_video(struct vb2_queue *vq, unsigned int count) static void uvc_stop_streaming_video(struct vb2_queue *vq) { struct uvc_video_queue *queue = vb2_get_drv_priv(vq); + struct uvc_streaming *stream = uvc_queue_to_stream(queue); lockdep_assert_irqs_enabled(); uvc_video_stop_streaming(uvc_queue_to_stream(queue)); + uvc_pm_put(stream->dev); + uvc_queue_return_buffers(queue, UVC_BUF_STATE_ERROR); } diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index d06ecf3418a9..7ab1bdcfb493 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -617,9 +617,6 @@ static int uvc_v4l2_release(struct file *file) uvc_ctrl_cleanup_fh(handle); - if (handle->is_streaming) - uvc_pm_put(stream->dev); - /* Release the file handle. */ vb2_fop_release(file); @@ -676,50 +673,6 @@ static int uvc_ioctl_try_fmt(struct file *file, void *fh, return uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL); } -static int uvc_ioctl_streamon(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - int ret; - - if (handle->is_streaming) - return 0; - - ret = uvc_pm_get(stream->dev); - if (ret) - return ret; - - ret = vb2_ioctl_streamon(file, fh, type); - if (ret) { - uvc_pm_put(stream->dev); - return ret; - } - - handle->is_streaming = true; - - return 0; -} - -static int uvc_ioctl_streamoff(struct file *file, void *fh, - enum v4l2_buf_type type) -{ - struct uvc_fh *handle = fh; - struct uvc_streaming *stream = handle->stream; - int ret; - - ret = vb2_ioctl_streamoff(file, fh, type); - if (ret) - return ret; - - if (handle->is_streaming) { - handle->is_streaming = false; - uvc_pm_put(stream->dev); - } - - return 0; -} - static int uvc_ioctl_enum_input(struct file *file, void *fh, struct v4l2_input *input) { @@ -1320,8 +1273,8 @@ const struct v4l2_ioctl_ops uvc_ioctl_ops = { .vidioc_expbuf = vb2_ioctl_expbuf, .vidioc_dqbuf = vb2_ioctl_dqbuf, .vidioc_create_bufs = vb2_ioctl_create_bufs, - .vidioc_streamon = uvc_ioctl_streamon, - .vidioc_streamoff = uvc_ioctl_streamoff, + .vidioc_streamon = vb2_ioctl_streamon, + .vidioc_streamoff = vb2_ioctl_streamoff, .vidioc_enum_input = uvc_ioctl_enum_input, .vidioc_g_input = uvc_ioctl_g_input, .vidioc_s_input = uvc_ioctl_s_input, diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index b300487e6ec9..3e6d2d912f3a 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -629,7 +629,6 @@ struct uvc_fh { struct uvc_video_chain *chain; struct uvc_streaming *stream; unsigned int pending_async_ctrls; - bool is_streaming; }; /* ------------------------------------------------------------------------ From a03e32e60141058d46ea8cf4631654c43c740fdb Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 1 Jul 2025 06:45:17 +0000 Subject: [PATCH 295/310] media: uvcvideo: Turn on the camera if V4L2_EVENT_SUB_FL_SEND_INITIAL If we subscribe to an event with V4L2_EVENT_SUB_FL_SEND_INITIAL, the driver needs to report back some values that require the camera to be powered on. But VIDIOC_SUBSCRIBE_EVENT is not part of the ioctls that turn on the camera. We could unconditionally turn on the camera during VIDIOC_SUBSCRIBE_EVENT, but it is more efficient to turn it on only during V4L2_EVENT_SUB_FL_SEND_INITIAL, which we believe is not a common usecase. To avoid a list_del if uvc_pm_get() fails, we move list_add_tail to the end of the function. Reviewed-by: Hans de Goede Fixes: d1b618e79548 ("media: uvcvideo: Do not turn on the camera for some ioctls") Cc: stable@vger.kernel.org Reviewed-by: Laurent Pinchart Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250701-uvc-grannular-invert-v4-5-8003b9b89f68@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_ctrl.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/drivers/media/usb/uvc/uvc_ctrl.c b/drivers/media/usb/uvc/uvc_ctrl.c index 303b7509ec47..efe609d70877 100644 --- a/drivers/media/usb/uvc/uvc_ctrl.c +++ b/drivers/media/usb/uvc/uvc_ctrl.c @@ -2072,18 +2072,24 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems) goto done; } - list_add_tail(&sev->node, &mapping->ev_subs); if (sev->flags & V4L2_EVENT_SUB_FL_SEND_INITIAL) { struct v4l2_event ev; u32 changes = V4L2_EVENT_CTRL_CH_FLAGS; s32 val = 0; + ret = uvc_pm_get(handle->chain->dev); + if (ret) + goto done; + if (uvc_ctrl_mapping_is_compound(mapping) || __uvc_ctrl_get(handle->chain, ctrl, mapping, &val) == 0) changes |= V4L2_EVENT_CTRL_CH_VALUE; uvc_ctrl_fill_event(handle->chain, &ev, ctrl, mapping, val, changes); + + uvc_pm_put(handle->chain->dev); + /* * Mark the queue as active, allowing this initial event to be * accepted. @@ -2092,6 +2098,8 @@ static int uvc_ctrl_add_event(struct v4l2_subscribed_event *sev, unsigned elems) v4l2_event_queue_fh(sev->fh, &ev); } + list_add_tail(&sev->node, &mapping->ev_subs); + done: mutex_unlock(&handle->chain->ctrl_mutex); return ret; From 1657624a69fcfd3f27ba6223e1c8fb6a16815568 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 1 Jul 2025 06:45:18 +0000 Subject: [PATCH 296/310] media: core: export v4l2_translate_cmd video_translate_cmd() can be useful for drivers to convert between the VIDIOC_*32 and VIDIOC_ defines. Let's export it. Now that the function is exported, use this opportunity to rename the function with the v4l2_ prefix, that is less ambiguous than video_ The VIDIOC_*32 defines are not accessible by the drivers, they live in v4l2-compat-ioctl32.c. Reviewed-by: Hans Verkuil Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250701-uvc-grannular-invert-v4-6-8003b9b89f68@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-ioctl.c | 5 +++-- include/media/v4l2-ioctl.h | 1 + 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index be94a79b976e..0db05f9c7117 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -3249,7 +3249,7 @@ static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, return ret; } -static unsigned int video_translate_cmd(unsigned int cmd) +unsigned int v4l2_translate_cmd(unsigned int cmd) { #if !defined(CONFIG_64BIT) && defined(CONFIG_COMPAT_32BIT_TIME) switch (cmd) { @@ -3270,6 +3270,7 @@ static unsigned int video_translate_cmd(unsigned int cmd) return cmd; } +EXPORT_SYMBOL_GPL(v4l2_translate_cmd); static int video_get_user(void __user *arg, void *parg, unsigned int real_cmd, unsigned int cmd, @@ -3430,7 +3431,7 @@ video_usercopy(struct file *file, unsigned int orig_cmd, unsigned long arg, size_t array_size = 0; void __user *user_ptr = NULL; void **kernel_ptr = NULL; - unsigned int cmd = video_translate_cmd(orig_cmd); + unsigned int cmd = v4l2_translate_cmd(orig_cmd); const size_t ioc_size = _IOC_SIZE(cmd); /* Copy arguments into temp kernel buffer */ diff --git a/include/media/v4l2-ioctl.h b/include/media/v4l2-ioctl.h index c6ec87e88dfe..82695c3a300a 100644 --- a/include/media/v4l2-ioctl.h +++ b/include/media/v4l2-ioctl.h @@ -679,6 +679,7 @@ long int v4l2_compat_ioctl32(struct file *file, unsigned int cmd, #endif unsigned int v4l2_compat_translate_cmd(unsigned int cmd); +unsigned int v4l2_translate_cmd(unsigned int cmd); int v4l2_compat_get_user(void __user *arg, void *parg, unsigned int cmd); int v4l2_compat_put_user(void __user *arg, void *parg, unsigned int cmd); int v4l2_compat_get_array_args(struct file *file, void *mbuf, From 2acded8aa2c89b858495488f6a14cd1d04fc5784 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Tue, 1 Jul 2025 06:45:19 +0000 Subject: [PATCH 297/310] media: uvcvideo: uvc_v4l2_unlocked_ioctl: Invert PM logic Instead of listing the IOCTLs that do not need to turn on the camera, list the IOCTLs that need to turn it on. This makes the code more maintainable. This patch changes the behaviour for unsupported IOCTLs. Those IOCTLs will not turn on the camera. Suggested-by: Hans Verkuil Reviewed-by: Hans de Goede Reviewed-by: Hans Verkuil Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250701-uvc-grannular-invert-v4-7-8003b9b89f68@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_v4l2.c | 52 ++++++++++++++++---------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_v4l2.c b/drivers/media/usb/uvc/uvc_v4l2.c index 7ab1bdcfb493..160f9cf6e6db 100644 --- a/drivers/media/usb/uvc/uvc_v4l2.c +++ b/drivers/media/usb/uvc/uvc_v4l2.c @@ -1222,36 +1222,36 @@ static long uvc_v4l2_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct uvc_fh *handle = file->private_data; + unsigned int converted_cmd = v4l2_translate_cmd(cmd); int ret; - /* The following IOCTLs do not need to turn on the camera. */ - switch (cmd) { - case VIDIOC_CREATE_BUFS: - case VIDIOC_DQBUF: - case VIDIOC_ENUM_FMT: - case VIDIOC_ENUM_FRAMEINTERVALS: - case VIDIOC_ENUM_FRAMESIZES: - case VIDIOC_ENUMINPUT: - case VIDIOC_EXPBUF: - case VIDIOC_G_FMT: - case VIDIOC_G_PARM: - case VIDIOC_G_SELECTION: - case VIDIOC_QBUF: - case VIDIOC_QUERYCAP: - case VIDIOC_REQBUFS: - case VIDIOC_SUBSCRIBE_EVENT: - case VIDIOC_UNSUBSCRIBE_EVENT: - return video_ioctl2(file, cmd, arg); + /* The following IOCTLs need to turn on the camera. */ + switch (converted_cmd) { + case UVCIOC_CTRL_MAP: + case UVCIOC_CTRL_QUERY: + case VIDIOC_G_CTRL: + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_G_INPUT: + case VIDIOC_QUERYCTRL: + case VIDIOC_QUERYMENU: + case VIDIOC_QUERY_EXT_CTRL: + case VIDIOC_S_CTRL: + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_S_FMT: + case VIDIOC_S_INPUT: + case VIDIOC_S_PARM: + case VIDIOC_TRY_EXT_CTRLS: + case VIDIOC_TRY_FMT: + ret = uvc_pm_get(handle->stream->dev); + if (ret) + return ret; + ret = video_ioctl2(file, cmd, arg); + uvc_pm_put(handle->stream->dev); + return ret; } - ret = uvc_pm_get(handle->stream->dev); - if (ret) - return ret; - - ret = video_ioctl2(file, cmd, arg); - - uvc_pm_put(handle->stream->dev); - return ret; + /* The other IOCTLs can run with the camera off. */ + return video_ioctl2(file, cmd, arg); } const struct v4l2_ioctl_ops uvc_ioctl_ops = { From bda2859bff0b9596a19648f3740c697ce4c71496 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 7 Jul 2025 18:34:01 +0000 Subject: [PATCH 298/310] media: uvcvideo: Do not mark valid metadata as invalid Currently, the driver performs a length check of the metadata buffer before the actual metadata size is known and before the metadata is decided to be copied. This results in valid metadata buffers being incorrectly marked as invalid. Move the length check to occur after the metadata size is determined and is decided to be copied. Cc: stable@vger.kernel.org Fixes: 088ead255245 ("media: uvcvideo: Add a metadata device node") Reviewed-by: Laurent Pinchart Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250707-uvc-meta-v8-1-ed17f8b1218b@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_video.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index 11769a1832d2..2e377e7b9e81 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -1442,12 +1442,6 @@ static void uvc_video_decode_meta(struct uvc_streaming *stream, if (!meta_buf || length == 2) return; - if (meta_buf->length - meta_buf->bytesused < - length + sizeof(meta->ns) + sizeof(meta->sof)) { - meta_buf->error = 1; - return; - } - has_pts = mem[1] & UVC_STREAM_PTS; has_scr = mem[1] & UVC_STREAM_SCR; @@ -1468,6 +1462,12 @@ static void uvc_video_decode_meta(struct uvc_streaming *stream, !memcmp(scr, stream->clock.last_scr, 6))) return; + if (meta_buf->length - meta_buf->bytesused < + length + sizeof(meta->ns) + sizeof(meta->sof)) { + meta_buf->error = 1; + return; + } + meta = (struct uvc_meta_buf *)((u8 *)meta_buf->mem + meta_buf->bytesused); local_irq_save(flags); time = uvc_video_get_time(); From e1ad27027587cc9ea03889c63d9d402a61d8e553 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 7 Jul 2025 18:34:02 +0000 Subject: [PATCH 299/310] media: Documentation: Add note about UVCH length field The documentation currently describes the UVC length field as the "length of the rest of the block", which can be misleading. The driver limits the data copied to a maximum of 12 bytes. This change adds a clarifying sentence to the documentation to make this restriction explicit. Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250707-uvc-meta-v8-2-ed17f8b1218b@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- Documentation/userspace-api/media/v4l/metafmt-uvc.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/userspace-api/media/v4l/metafmt-uvc.rst b/Documentation/userspace-api/media/v4l/metafmt-uvc.rst index 784346d14bbd..4c05e9e54683 100644 --- a/Documentation/userspace-api/media/v4l/metafmt-uvc.rst +++ b/Documentation/userspace-api/media/v4l/metafmt-uvc.rst @@ -44,7 +44,9 @@ Each individual block contains the following fields: them * - :cspan:`1` *The rest is an exact copy of the UVC payload header:* * - __u8 length; - - length of the rest of the block, including this field + - length of the rest of the block, including this field. Please note that + regardless of this value, for V4L2_META_FMT_UVC the kernel will never + copy more than 2-12 bytes. * - __u8 flags; - Flags, indicating presence of other standard UVC fields * - __u8 buf[]; From 0bb51c8897395de5bd5f571874130aec214ef534 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 7 Jul 2025 18:34:03 +0000 Subject: [PATCH 300/310] media: uvcvideo: Introduce dev->meta_formats Right now, there driver supports devices with one or two metadata formats. Prepare it to support more than two metadata formats. This is achieved with the introduction of a new field `meta_formats`, that contains the array of metadata formats supported by the device, in the order expected by userspace. Suggested-by: Hans de Goede Signed-off-by: Ricardo Ribalda Reviewed-by: Hans de Goede Link: https://lore.kernel.org/r/20250707-uvc-meta-v8-3-ed17f8b1218b@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 2 ++ drivers/media/usb/uvc/uvc_metadata.c | 38 +++++++++++++++++++++++----- drivers/media/usb/uvc/uvcvideo.h | 6 +++++ 3 files changed, 39 insertions(+), 7 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index accfb4ca3c72..b12c95fe8b0a 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2293,6 +2293,8 @@ static int uvc_probe(struct usb_interface *intf, goto error; } + uvc_meta_init(dev); + if (dev->quirks & UVC_QUIRK_NO_RESET_RESUME) udev->quirks &= ~USB_QUIRK_RESET_RESUME; diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c index d3aab22f91ce..58691df60dd3 100644 --- a/drivers/media/usb/uvc/uvc_metadata.c +++ b/drivers/media/usb/uvc/uvc_metadata.c @@ -64,14 +64,20 @@ static int uvc_meta_v4l2_try_format(struct file *file, void *fh, struct uvc_device *dev = stream->dev; struct v4l2_meta_format *fmt = &format->fmt.meta; u32 fmeta = fmt->dataformat; + u32 i; if (format->type != vfh->vdev->queue->type) return -EINVAL; + for (i = 0; (fmeta != dev->meta_formats[i]) && dev->meta_formats[i]; + i++) + ; + if (!dev->meta_formats[i]) + fmeta = V4L2_META_FMT_UVC; + memset(fmt, 0, sizeof(*fmt)); - fmt->dataformat = fmeta == dev->info->meta_format - ? fmeta : V4L2_META_FMT_UVC; + fmt->dataformat = fmeta; fmt->buffersize = UVC_METADATA_BUF_SIZE; return 0; @@ -112,17 +118,21 @@ static int uvc_meta_v4l2_enum_formats(struct file *file, void *fh, struct v4l2_fh *vfh = file->private_data; struct uvc_streaming *stream = video_get_drvdata(vfh->vdev); struct uvc_device *dev = stream->dev; - u32 index = fdesc->index; + u32 i; - if (fdesc->type != vfh->vdev->queue->type || - index > 1U || (index && !dev->info->meta_format)) + if (fdesc->type != vfh->vdev->queue->type) + return -EINVAL; + + for (i = 0; (i < fdesc->index) && dev->meta_formats[i]; i++) + ; + if (!dev->meta_formats[i]) return -EINVAL; memset(fdesc, 0, sizeof(*fdesc)); fdesc->type = vfh->vdev->queue->type; - fdesc->index = index; - fdesc->pixelformat = index ? dev->info->meta_format : V4L2_META_FMT_UVC; + fdesc->index = i; + fdesc->pixelformat = dev->meta_formats[i]; return 0; } @@ -168,3 +178,17 @@ int uvc_meta_register(struct uvc_streaming *stream) V4L2_BUF_TYPE_META_CAPTURE, &uvc_meta_fops, &uvc_meta_ioctl_ops); } + +void uvc_meta_init(struct uvc_device *dev) +{ + unsigned int i = 0; + + dev->meta_formats[i++] = V4L2_META_FMT_UVC; + + if (dev->info->meta_format && + !WARN_ON(dev->info->meta_format == V4L2_META_FMT_UVC)) + dev->meta_formats[i++] = dev->info->meta_format; + + /* IMPORTANT: for new meta-formats update UVC_MAX_META_DATA_FORMATS. */ + dev->meta_formats[i++] = 0; +} diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 3e6d2d912f3a..81ec171fdfde 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -575,6 +575,8 @@ struct uvc_status { }; } __packed; +#define UVC_MAX_META_DATA_FORMATS 3 + struct uvc_device { struct usb_device *udev; struct usb_interface *intf; @@ -585,6 +587,9 @@ struct uvc_device { const struct uvc_device_info *info; + /* Zero-ended list of meta formats */ + u32 meta_formats[UVC_MAX_META_DATA_FORMATS + 1]; + atomic_t nmappings; /* Video control interface */ @@ -722,6 +727,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, void uvc_video_clock_update(struct uvc_streaming *stream, struct vb2_v4l2_buffer *vbuf, struct uvc_buffer *buf); +void uvc_meta_init(struct uvc_device *dev); int uvc_meta_register(struct uvc_streaming *stream); int uvc_register_video_device(struct uvc_device *dev, From 2ab4019aa34dc2aec4a0824fbf1e49300884fbbf Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 7 Jul 2025 18:34:04 +0000 Subject: [PATCH 301/310] media: uvcvideo: Introduce V4L2_META_FMT_UVC_MSXU_1_5 The UVC driver provides two metadata types V4L2_META_FMT_UVC, and V4L2_META_FMT_D4XX. The only difference between the two of them is that V4L2_META_FMT_UVC only copies PTS, SCR, size and flags, and V4L2_META_FMT_D4XX copies the whole metadata section. Now we only enable V4L2_META_FMT_D4XX for the Intel D4xx family of devices, but it is useful to have the whole metadata payload for any device where vendors include other metadata, such as the one described by Microsoft: https://learn.microsoft.com/en-us/windows-hardware/drivers/stream/mf-capture-metadata This patch introduces a new format V4L2_META_FMT_UVC_MSXU_1_5, that is identical to V4L2_META_FMT_D4XX. Let the user enable this format with a quirk for now. This way they can test if their devices provide useful metadata without rebuilding the kernel. They can later contribute patches to auto-quirk their devices. We will also work in methods to auto-detect devices compatible with this new metadata format. Suggested-by: Hans de Goede Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250707-uvc-meta-v8-4-ed17f8b1218b@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- .../userspace-api/media/v4l/meta-formats.rst | 1 + .../media/v4l/metafmt-uvc-msxu-1-5.rst | 23 +++++++++++++++++++ MAINTAINERS | 1 + drivers/media/usb/uvc/uvc_metadata.c | 4 ++++ drivers/media/usb/uvc/uvcvideo.h | 1 + drivers/media/v4l2-core/v4l2-ioctl.c | 1 + include/uapi/linux/videodev2.h | 1 + 7 files changed, 32 insertions(+) create mode 100644 Documentation/userspace-api/media/v4l/metafmt-uvc-msxu-1-5.rst diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst index bb6876cfc271..0de80328c36b 100644 --- a/Documentation/userspace-api/media/v4l/meta-formats.rst +++ b/Documentation/userspace-api/media/v4l/meta-formats.rst @@ -20,6 +20,7 @@ These formats are used for the :ref:`metadata` interface only. metafmt-pisp-fe metafmt-rkisp1 metafmt-uvc + metafmt-uvc-msxu-1-5 metafmt-vivid metafmt-vsp1-hgo metafmt-vsp1-hgt diff --git a/Documentation/userspace-api/media/v4l/metafmt-uvc-msxu-1-5.rst b/Documentation/userspace-api/media/v4l/metafmt-uvc-msxu-1-5.rst new file mode 100644 index 000000000000..dd1c3076df24 --- /dev/null +++ b/Documentation/userspace-api/media/v4l/metafmt-uvc-msxu-1-5.rst @@ -0,0 +1,23 @@ +.. SPDX-License-Identifier: GFDL-1.1-no-invariants-or-later + +.. _v4l2-meta-fmt-uvc-msxu-1-5: + +*********************************** +V4L2_META_FMT_UVC_MSXU_1_5 ('UVCM') +*********************************** + +Microsoft(R)'s UVC Payload Metadata. + + +Description +=========== + +V4L2_META_FMT_UVC_MSXU_1_5 buffers follow the metadata buffer layout of +V4L2_META_FMT_UVC with the only difference that it includes all the UVC +metadata in the `buffer[]` field, not just the first 2-12 bytes. + +The metadata format follows the specification from Microsoft(R) [1]. + +.. _1: + +[1] https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/uvc-extensions-1-5 diff --git a/MAINTAINERS b/MAINTAINERS index 1ef99240a57e..5f69c82d53ee 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -25837,6 +25837,7 @@ S: Maintained W: http://www.ideasonboard.org/uvc/ T: git git://linuxtv.org/media.git F: Documentation/userspace-api/media/drivers/uvcvideo.rst +F: Documentation/userspace-api/media/v4l/metafmt-uvc-msxu-1-5.rst F: Documentation/userspace-api/media/v4l/metafmt-uvc.rst F: drivers/media/common/uvc.c F: drivers/media/usb/uvc/ diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c index 58691df60dd3..d0ee139dbda7 100644 --- a/drivers/media/usb/uvc/uvc_metadata.c +++ b/drivers/media/usb/uvc/uvc_metadata.c @@ -189,6 +189,10 @@ void uvc_meta_init(struct uvc_device *dev) !WARN_ON(dev->info->meta_format == V4L2_META_FMT_UVC)) dev->meta_formats[i++] = dev->info->meta_format; + if (dev->quirks & UVC_QUIRK_MSXU_META && + !WARN_ON(dev->info->meta_format == V4L2_META_FMT_UVC_MSXU_1_5)) + dev->meta_formats[i++] = V4L2_META_FMT_UVC_MSXU_1_5; + /* IMPORTANT: for new meta-formats update UVC_MAX_META_DATA_FORMATS. */ dev->meta_formats[i++] = 0; } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index 81ec171fdfde..eb164d063199 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -77,6 +77,7 @@ #define UVC_QUIRK_DISABLE_AUTOSUSPEND 0x00008000 #define UVC_QUIRK_INVALID_DEVICE_SOF 0x00010000 #define UVC_QUIRK_MJPEG_NO_EOF 0x00020000 +#define UVC_QUIRK_MSXU_META 0x00040000 /* Format flags */ #define UVC_FMT_FLAG_COMPRESSED 0x00000001 diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c index 0db05f9c7117..46da373066f4 100644 --- a/drivers/media/v4l2-core/v4l2-ioctl.c +++ b/drivers/media/v4l2-core/v4l2-ioctl.c @@ -1463,6 +1463,7 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt) case V4L2_META_FMT_VSP1_HGO: descr = "R-Car VSP1 1-D Histogram"; break; case V4L2_META_FMT_VSP1_HGT: descr = "R-Car VSP1 2-D Histogram"; break; case V4L2_META_FMT_UVC: descr = "UVC Payload Header Metadata"; break; + case V4L2_META_FMT_UVC_MSXU_1_5: descr = "UVC MSXU Metadata"; break; case V4L2_META_FMT_D4XX: descr = "Intel D4xx UVC Metadata"; break; case V4L2_META_FMT_VIVID: descr = "Vivid Metadata"; break; case V4L2_META_FMT_RK_ISP1_PARAMS: descr = "Rockchip ISP1 3A Parameters"; break; diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h index 1bb1979f6c18..3dd9fa45dde1 100644 --- a/include/uapi/linux/videodev2.h +++ b/include/uapi/linux/videodev2.h @@ -867,6 +867,7 @@ struct v4l2_pix_format { #define V4L2_META_FMT_VSP1_HGT v4l2_fourcc('V', 'S', 'P', 'T') /* R-Car VSP1 2-D Histogram */ #define V4L2_META_FMT_UVC v4l2_fourcc('U', 'V', 'C', 'H') /* UVC Payload Header metadata */ #define V4L2_META_FMT_D4XX v4l2_fourcc('D', '4', 'X', 'X') /* D4XX Payload Header metadata */ +#define V4L2_META_FMT_UVC_MSXU_1_5 v4l2_fourcc('U', 'V', 'C', 'M') /* UVC MSXU metadata */ #define V4L2_META_FMT_VIVID v4l2_fourcc('V', 'I', 'V', 'D') /* Vivid Metadata */ /* Vendor specific - used for RK_ISP1 camera sub-system */ From 6cb786f040ad35b23b4a7bff8b9d772f22909d48 Mon Sep 17 00:00:00 2001 From: Ricardo Ribalda Date: Mon, 7 Jul 2025 18:34:05 +0000 Subject: [PATCH 302/310] media: uvcvideo: Auto-set UVC_QUIRK_MSXU_META If the camera supports the MSXU_CONTROL_METADATA control, auto set the MSXU_META quirk. Reviewed-by: Hans de Goede Signed-off-by: Ricardo Ribalda Link: https://lore.kernel.org/r/20250707-uvc-meta-v8-5-ed17f8b1218b@chromium.org Signed-off-by: Hans de Goede Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_driver.c | 7 ++- drivers/media/usb/uvc/uvc_metadata.c | 75 +++++++++++++++++++++++++++- drivers/media/usb/uvc/uvcvideo.h | 2 +- include/linux/usb/uvc.h | 3 ++ 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_driver.c b/drivers/media/usb/uvc/uvc_driver.c index b12c95fe8b0a..775bede0d93d 100644 --- a/drivers/media/usb/uvc/uvc_driver.c +++ b/drivers/media/usb/uvc/uvc_driver.c @@ -2293,7 +2293,12 @@ static int uvc_probe(struct usb_interface *intf, goto error; } - uvc_meta_init(dev); + ret = uvc_meta_init(dev); + if (ret < 0) { + dev_err(&dev->udev->dev, + "Error initializing the metadata formats (%d)\n", ret); + goto error; + } if (dev->quirks & UVC_QUIRK_NO_RESET_RESUME) udev->quirks &= ~USB_QUIRK_RESET_RESUME; diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c index d0ee139dbda7..12972527ab8d 100644 --- a/drivers/media/usb/uvc/uvc_metadata.c +++ b/drivers/media/usb/uvc/uvc_metadata.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -166,6 +167,71 @@ static const struct v4l2_file_operations uvc_meta_fops = { .mmap = vb2_fop_mmap, }; +static struct uvc_entity *uvc_meta_find_msxu(struct uvc_device *dev) +{ + static const u8 uvc_msxu_guid[16] = UVC_GUID_MSXU_1_5; + struct uvc_entity *entity; + + list_for_each_entry(entity, &dev->entities, list) { + if (!memcmp(entity->guid, uvc_msxu_guid, sizeof(entity->guid))) + return entity; + } + + return NULL; +} + +#define MSXU_CONTROL_METADATA 0x9 +static int uvc_meta_detect_msxu(struct uvc_device *dev) +{ + u32 *data __free(kfree) = NULL; + struct uvc_entity *entity; + int ret; + + entity = uvc_meta_find_msxu(dev); + if (!entity) + return 0; + + /* + * USB requires buffers aligned in a special way, simplest way is to + * make sure that query_ctrl will work is to kmalloc() them. + */ + data = kmalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + /* Check if the metadata is already enabled. */ + ret = uvc_query_ctrl(dev, UVC_GET_CUR, entity->id, dev->intfnum, + MSXU_CONTROL_METADATA, data, sizeof(*data)); + if (ret) + return 0; + + if (*data) { + dev->quirks |= UVC_QUIRK_MSXU_META; + return 0; + } + + /* + * We have seen devices that require 1 to enable the metadata, others + * requiring a value != 1 and others requiring a value >1. Luckily for + * us, the value from GET_MAX seems to work all the time. + */ + ret = uvc_query_ctrl(dev, UVC_GET_MAX, entity->id, dev->intfnum, + MSXU_CONTROL_METADATA, data, sizeof(*data)); + if (ret || !*data) + return 0; + + /* + * If we can set MSXU_CONTROL_METADATA, the device will report + * metadata. + */ + ret = uvc_query_ctrl(dev, UVC_SET_CUR, entity->id, dev->intfnum, + MSXU_CONTROL_METADATA, data, sizeof(*data)); + if (!ret) + dev->quirks |= UVC_QUIRK_MSXU_META; + + return 0; +} + int uvc_meta_register(struct uvc_streaming *stream) { struct uvc_device *dev = stream->dev; @@ -179,9 +245,14 @@ int uvc_meta_register(struct uvc_streaming *stream) &uvc_meta_fops, &uvc_meta_ioctl_ops); } -void uvc_meta_init(struct uvc_device *dev) +int uvc_meta_init(struct uvc_device *dev) { unsigned int i = 0; + int ret; + + ret = uvc_meta_detect_msxu(dev); + if (ret) + return ret; dev->meta_formats[i++] = V4L2_META_FMT_UVC; @@ -195,4 +266,6 @@ void uvc_meta_init(struct uvc_device *dev) /* IMPORTANT: for new meta-formats update UVC_MAX_META_DATA_FORMATS. */ dev->meta_formats[i++] = 0; + + return 0; } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index eb164d063199..b34c1914ff39 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -728,7 +728,7 @@ int uvc_query_ctrl(struct uvc_device *dev, u8 query, u8 unit, void uvc_video_clock_update(struct uvc_streaming *stream, struct vb2_v4l2_buffer *vbuf, struct uvc_buffer *buf); -void uvc_meta_init(struct uvc_device *dev); +int uvc_meta_init(struct uvc_device *dev); int uvc_meta_register(struct uvc_streaming *stream); int uvc_register_video_device(struct uvc_device *dev, diff --git a/include/linux/usb/uvc.h b/include/linux/usb/uvc.h index bce95153e5a6..ee19e9f915b8 100644 --- a/include/linux/usb/uvc.h +++ b/include/linux/usb/uvc.h @@ -29,6 +29,9 @@ #define UVC_GUID_EXT_GPIO_CONTROLLER \ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03} +#define UVC_GUID_MSXU_1_5 \ + {0xdc, 0x95, 0x3f, 0x0f, 0x32, 0x26, 0x4e, 0x4c, \ + 0x92, 0xc9, 0xa0, 0x47, 0x82, 0xf4, 0x3b, 0xc8} #define UVC_GUID_FORMAT_MJPEG \ { 'M', 'J', 'P', 'G', 0x00, 0x00, 0x10, 0x00, \ From c56c437b140ed9c9e91d82f4ca705cdb021a9343 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 8 Jul 2025 12:46:22 +0200 Subject: [PATCH 303/310] media: uvcvideo: Use a count variable for meta_formats instead of 0 terminating The code dealing with the 0 terminated meta_formats array is a bit klunky especially for the uvc_meta_v4l2_enum_formats() case. Instead of 0 terminating add an unsigned int nmeta_formats member to struct uvc_device and use that. This leads to slightly cleaner code. Signed-off-by: Hans de Goede Reviewed-by: Ricardo Ribalda Tested-by: Ricardo Ribalda # Camera with MSXU_CONTROL_METADATA Link: https://lore.kernel.org/r/20250708104622.73237-2-hansg@kernel.org Signed-off-by: Hans Verkuil --- drivers/media/usb/uvc/uvc_metadata.c | 21 +++++++++------------ drivers/media/usb/uvc/uvcvideo.h | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) diff --git a/drivers/media/usb/uvc/uvc_metadata.c b/drivers/media/usb/uvc/uvc_metadata.c index 12972527ab8d..229e08ff323e 100644 --- a/drivers/media/usb/uvc/uvc_metadata.c +++ b/drivers/media/usb/uvc/uvc_metadata.c @@ -64,17 +64,16 @@ static int uvc_meta_v4l2_try_format(struct file *file, void *fh, struct uvc_streaming *stream = video_get_drvdata(vfh->vdev); struct uvc_device *dev = stream->dev; struct v4l2_meta_format *fmt = &format->fmt.meta; - u32 fmeta = fmt->dataformat; - u32 i; + u32 fmeta = V4L2_META_FMT_UVC; if (format->type != vfh->vdev->queue->type) return -EINVAL; - for (i = 0; (fmeta != dev->meta_formats[i]) && dev->meta_formats[i]; - i++) - ; - if (!dev->meta_formats[i]) - fmeta = V4L2_META_FMT_UVC; + for (unsigned int i = 0; i < dev->nmeta_formats; i++) + if (dev->meta_formats[i] == fmt->dataformat) { + fmeta = fmt->dataformat; + break; + } memset(fmt, 0, sizeof(*fmt)); @@ -119,14 +118,12 @@ static int uvc_meta_v4l2_enum_formats(struct file *file, void *fh, struct v4l2_fh *vfh = file->private_data; struct uvc_streaming *stream = video_get_drvdata(vfh->vdev); struct uvc_device *dev = stream->dev; - u32 i; + u32 i = fdesc->index; if (fdesc->type != vfh->vdev->queue->type) return -EINVAL; - for (i = 0; (i < fdesc->index) && dev->meta_formats[i]; i++) - ; - if (!dev->meta_formats[i]) + if (i >= dev->nmeta_formats) return -EINVAL; memset(fdesc, 0, sizeof(*fdesc)); @@ -265,7 +262,7 @@ int uvc_meta_init(struct uvc_device *dev) dev->meta_formats[i++] = V4L2_META_FMT_UVC_MSXU_1_5; /* IMPORTANT: for new meta-formats update UVC_MAX_META_DATA_FORMATS. */ - dev->meta_formats[i++] = 0; + dev->nmeta_formats = i; return 0; } diff --git a/drivers/media/usb/uvc/uvcvideo.h b/drivers/media/usb/uvc/uvcvideo.h index b34c1914ff39..757254fc4fe9 100644 --- a/drivers/media/usb/uvc/uvcvideo.h +++ b/drivers/media/usb/uvc/uvcvideo.h @@ -588,8 +588,8 @@ struct uvc_device { const struct uvc_device_info *info; - /* Zero-ended list of meta formats */ - u32 meta_formats[UVC_MAX_META_DATA_FORMATS + 1]; + u32 meta_formats[UVC_MAX_META_DATA_FORMATS]; + unsigned int nmeta_formats; atomic_t nmappings; From f7546da1d6eb8928efb89b7faacbd6c2f8f0de5c Mon Sep 17 00:00:00 2001 From: Marek Szyprowski Date: Fri, 11 Jul 2025 11:41:58 +0200 Subject: [PATCH 304/310] media: v4l2: Add support for NV12M tiled variants to v4l2_format_info() Commit 6f1466123d73 ("media: s5p-mfc: Add YV12 and I420 multiplanar format support") added support for the new formats to s5p-mfc driver, what in turn required some internal calls to the v4l2_format_info() function while setting up formats. This in turn broke support for the "old" tiled NV12MT* formats, which are not recognized by this function. Fix this by adding those variants of NV12M pixel format to v4l2_format_info() function database. Fixes: 6f1466123d73 ("media: s5p-mfc: Add YV12 and I420 multiplanar format support") Cc: stable@vger.kernel.org Signed-off-by: Marek Szyprowski Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/v4l2-core/v4l2-common.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/drivers/media/v4l2-core/v4l2-common.c b/drivers/media/v4l2-core/v4l2-common.c index a9bd3ca0f281..6e585bc76367 100644 --- a/drivers/media/v4l2-core/v4l2-common.c +++ b/drivers/media/v4l2-core/v4l2-common.c @@ -323,6 +323,12 @@ const struct v4l2_format_info *v4l2_format_info(u32 format) { .format = V4L2_PIX_FMT_NV61M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 1 }, { .format = V4L2_PIX_FMT_P012M, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 2, 4, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2 }, + /* Tiled YUV formats, non contiguous variant */ + { .format = V4L2_PIX_FMT_NV12MT, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2, + .block_w = { 64, 32, 0, 0 }, .block_h = { 32, 16, 0, 0 }}, + { .format = V4L2_PIX_FMT_NV12MT_16X16, .pixel_enc = V4L2_PIXEL_ENC_YUV, .mem_planes = 2, .comp_planes = 2, .bpp = { 1, 2, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 2, .vdiv = 2, + .block_w = { 16, 8, 0, 0 }, .block_h = { 16, 8, 0, 0 }}, + /* Bayer RGB formats */ { .format = V4L2_PIX_FMT_SBGGR8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, { .format = V4L2_PIX_FMT_SGBRG8, .pixel_enc = V4L2_PIXEL_ENC_BAYER, .mem_planes = 1, .comp_planes = 1, .bpp = { 1, 0, 0, 0 }, .bpp_div = { 1, 1, 1, 1 }, .hdiv = 1, .vdiv = 1 }, From 3f6702e1be8a06e70c60720162a77c8b3a4b69d3 Mon Sep 17 00:00:00 2001 From: Hugues Fruchet Date: Thu, 21 Nov 2024 14:19:04 +0100 Subject: [PATCH 305/310] media: verisilicon: postproc: 4K support Support input larger than 4096x2048 using extended input width/height fields of swreg92. This is needed to decode large WebP or JPEG pictures. Signed-off-by: Hugues Fruchet Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/verisilicon/hantro.h | 2 ++ drivers/media/platform/verisilicon/hantro_g1_regs.h | 2 +- drivers/media/platform/verisilicon/hantro_postproc.c | 6 +++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/media/platform/verisilicon/hantro.h b/drivers/media/platform/verisilicon/hantro.h index edc217eed293..81328c63b796 100644 --- a/drivers/media/platform/verisilicon/hantro.h +++ b/drivers/media/platform/verisilicon/hantro.h @@ -323,6 +323,8 @@ struct hantro_postproc_regs { struct hantro_reg output_fmt; struct hantro_reg orig_width; struct hantro_reg display_width; + struct hantro_reg input_width_ext; + struct hantro_reg input_height_ext; }; struct hantro_vp9_decoded_buffer_info { diff --git a/drivers/media/platform/verisilicon/hantro_g1_regs.h b/drivers/media/platform/verisilicon/hantro_g1_regs.h index c623b3b0be18..7874d76c6898 100644 --- a/drivers/media/platform/verisilicon/hantro_g1_regs.h +++ b/drivers/media/platform/verisilicon/hantro_g1_regs.h @@ -350,7 +350,7 @@ #define G1_REG_PP_CONTROL_OUT_WIDTH(v) (((v) << 4) & GENMASK(14, 4)) #define G1_REG_PP_MASK1_ORIG_WIDTH G1_SWREG(88) #define G1_REG_PP_ORIG_WIDTH(v) (((v) << 23) & GENMASK(31, 23)) -#define G1_REG_PP_DISPLAY_WIDTH G1_SWREG(92) +#define G1_REG_PP_DISPLAY_WIDTH_IN_EXT G1_SWREG(92) #define G1_REG_PP_FUSE G1_SWREG(99) #endif /* HANTRO_G1_REGS_H_ */ diff --git a/drivers/media/platform/verisilicon/hantro_postproc.c b/drivers/media/platform/verisilicon/hantro_postproc.c index 9f559a13d409..e94d1ba5ef10 100644 --- a/drivers/media/platform/verisilicon/hantro_postproc.c +++ b/drivers/media/platform/verisilicon/hantro_postproc.c @@ -49,7 +49,9 @@ static const struct hantro_postproc_regs hantro_g1_postproc_regs = { .input_fmt = {G1_REG_PP_CONTROL, 29, 0x7}, .output_fmt = {G1_REG_PP_CONTROL, 26, 0x7}, .orig_width = {G1_REG_PP_MASK1_ORIG_WIDTH, 23, 0x1ff}, - .display_width = {G1_REG_PP_DISPLAY_WIDTH, 0, 0xfff}, + .display_width = {G1_REG_PP_DISPLAY_WIDTH_IN_EXT, 0, 0xfff}, + .input_width_ext = {G1_REG_PP_DISPLAY_WIDTH_IN_EXT, 26, 0x7}, + .input_height_ext = {G1_REG_PP_DISPLAY_WIDTH_IN_EXT, 29, 0x7}, }; bool hantro_needs_postproc(const struct hantro_ctx *ctx, @@ -103,6 +105,8 @@ static void hantro_postproc_g1_enable(struct hantro_ctx *ctx) HANTRO_PP_REG_WRITE(vpu, output_height, ctx->dst_fmt.height); HANTRO_PP_REG_WRITE(vpu, orig_width, MB_WIDTH(ctx->dst_fmt.width)); HANTRO_PP_REG_WRITE(vpu, display_width, ctx->dst_fmt.width); + HANTRO_PP_REG_WRITE(vpu, input_width_ext, MB_WIDTH(ctx->dst_fmt.width) >> 9); + HANTRO_PP_REG_WRITE(vpu, input_height_ext, MB_HEIGHT(ctx->dst_fmt.height >> 8)); } static int down_scale_factor(struct hantro_ctx *ctx) From 77ce8e4d6de72a4b8b8c4595a8c1c2305f3ac469 Mon Sep 17 00:00:00 2001 From: Ming Qian Date: Thu, 24 Apr 2025 18:33:24 +0800 Subject: [PATCH 306/310] media: amphion: Support dmabuf and v4l2 buffer without binding When using VB2_DMABUF, the relationship between dma-buf and v4l2 buffer may not one-to-one, a single dma-buf may be queued via different v4l2 buffers, and different dma-bufs may be queued via the same v4l2 buffer, so it's not appropriate to use the v4l2 buffer index as the frame store id. We can generate a frame store id according to the dma address. Then for a given dma-buf, the id is fixed. Driver now manages the frame store and vb2-buffer states independently. When a dmabuf is queued via another v4l2 buffer before the buffer is released by firmware, need to pend it until firmware release it. Signed-off-by: Ming Qian Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/media/platform/amphion/vdec.c | 235 +++++++++++++++++----- drivers/media/platform/amphion/vpu.h | 7 +- drivers/media/platform/amphion/vpu_dbg.c | 15 +- drivers/media/platform/amphion/vpu_v4l2.c | 11 + 4 files changed, 220 insertions(+), 48 deletions(-) diff --git a/drivers/media/platform/amphion/vdec.c b/drivers/media/platform/amphion/vdec.c index 55067d9405c2..32eef2fd1f2a 100644 --- a/drivers/media/platform/amphion/vdec.c +++ b/drivers/media/platform/amphion/vdec.c @@ -26,6 +26,7 @@ #include "vpu_cmds.h" #include "vpu_rpc.h" +#define VDEC_SLOT_CNT_DFT 32 #define VDEC_MIN_BUFFER_CAP 8 #define VDEC_MIN_BUFFER_OUT 8 @@ -41,6 +42,14 @@ struct vdec_fs_info { u32 tag; }; +struct vdec_frame_store_t { + struct vpu_vb2_buffer *curr; + struct vpu_vb2_buffer *pend; + dma_addr_t addr; + unsigned int state; + u32 tag; +}; + struct vdec_t { u32 seq_hdr_found; struct vpu_buffer udata; @@ -48,7 +57,8 @@ struct vdec_t { struct vpu_dec_codec_info codec_info; enum vpu_codec_state state; - struct vpu_vb2_buffer *slots[VB2_MAX_FRAME]; + struct vdec_frame_store_t *slots; + u32 slot_count; u32 req_frame_count; struct vdec_fs_info mbi; struct vdec_fs_info dcp; @@ -287,6 +297,63 @@ static int vdec_ctrl_init(struct vpu_inst *inst) return 0; } +static void vdec_attach_frame_store(struct vpu_inst *inst, struct vb2_buffer *vb) +{ + struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpu_vb2_buffer *vpu_buf = to_vpu_vb2_buffer(vbuf); + struct vdec_t *vdec = inst->priv; + struct vdec_frame_store_t *new_slots = NULL; + dma_addr_t addr; + int i; + + addr = vpu_get_vb_phy_addr(vb, 0); + for (i = 0; i < vdec->slot_count; i++) { + if (addr == vdec->slots[i].addr) { + if (vdec->slots[i].curr && vdec->slots[i].curr != vpu_buf) { + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_CHANGED); + vdec->slots[i].pend = vpu_buf; + } else { + vpu_set_buffer_state(vbuf, vdec->slots[i].state); + } + vpu_buf->fs_id = i; + return; + } + } + + for (i = 0; i < vdec->slot_count; i++) { + if (!vdec->slots[i].addr) { + vdec->slots[i].addr = addr; + vpu_buf->fs_id = i; + return; + } + } + + new_slots = krealloc_array(vdec->slots, vdec->slot_count * 2, + sizeof(*vdec->slots), + GFP_KERNEL | __GFP_ZERO); + if (!new_slots) { + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_ERROR); + return; + } + + vdec->slots = new_slots; + vdec->slot_count *= 2; + + vdec->slots[i].addr = addr; + vpu_buf->fs_id = i; +} + +static void vdec_reset_frame_store(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; + + if (!vdec->slots || !vdec->slot_count) + return; + + vpu_trace(inst->dev, "inst[%d] reset slots\n", inst->id); + memset(vdec->slots, 0, sizeof(*vdec->slots) * vdec->slot_count); +} + static void vdec_handle_resolution_change(struct vpu_inst *inst) { struct vdec_t *vdec = inst->priv; @@ -743,11 +810,11 @@ static int vdec_frame_decoded(struct vpu_inst *inst, void *arg) struct vb2_v4l2_buffer *src_buf; int ret = 0; - if (!info || info->id >= ARRAY_SIZE(vdec->slots)) + if (!info || info->id >= vdec->slot_count) return -EINVAL; vpu_inst_lock(inst); - vpu_buf = vdec->slots[info->id]; + vpu_buf = vdec->slots[info->id].curr; if (!vpu_buf) { dev_err(inst->dev, "[%d] decoded invalid frame[%d]\n", inst->id, info->id); ret = -EINVAL; @@ -768,11 +835,13 @@ static int vdec_frame_decoded(struct vpu_inst *inst, void *arg) if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_DECODED) dev_info(inst->dev, "[%d] buf[%d] has been decoded\n", inst->id, info->id); vpu_set_buffer_state(vbuf, VPU_BUF_STATE_DECODED); + vdec->slots[info->id].state = VPU_BUF_STATE_DECODED; vdec->decoded_frame_count++; if (vdec->params.display_delay_enable) { struct vpu_format *cur_fmt; cur_fmt = vpu_get_format(inst, inst->cap_format.type); + vdec->slots[info->id].state = VPU_BUF_STATE_READY; vpu_set_buffer_state(vbuf, VPU_BUF_STATE_READY); for (int i = 0; i < vbuf->vb2_buf.num_planes; i++) vb2_set_plane_payload(&vbuf->vb2_buf, @@ -795,11 +864,11 @@ static struct vpu_vb2_buffer *vdec_find_buffer(struct vpu_inst *inst, u32 luma) struct vdec_t *vdec = inst->priv; int i; - for (i = 0; i < ARRAY_SIZE(vdec->slots); i++) { - if (!vdec->slots[i]) + for (i = 0; i < vdec->slot_count; i++) { + if (!vdec->slots[i].curr) continue; - if (luma == vdec->slots[i]->luma) - return vdec->slots[i]; + if (luma == vdec->slots[i].addr) + return vdec->slots[i].curr; } return NULL; @@ -833,11 +902,11 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) cur_fmt = vpu_get_format(inst, inst->cap_format.type); vbuf = &vpu_buf->m2m_buf.vb; - if (vbuf->vb2_buf.index != frame->id) - dev_err(inst->dev, "[%d] buffer id(%d, %d) mismatch\n", - inst->id, vbuf->vb2_buf.index, frame->id); + if (vpu_buf->fs_id != frame->id) + dev_err(inst->dev, "[%d] buffer id(%d(%d), %d) mismatch\n", + inst->id, vpu_buf->fs_id, vbuf->vb2_buf.index, frame->id); - if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_READY && vdec->params.display_delay_enable) + if (vdec->params.display_delay_enable) return; if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_DECODED) @@ -850,10 +919,11 @@ static void vdec_buf_done(struct vpu_inst *inst, struct vpu_frame_info *frame) vbuf->sequence = vdec->sequence; dev_dbg(inst->dev, "[%d][OUTPUT TS]%32lld\n", inst->id, vbuf->vb2_buf.timestamp); - v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); vpu_inst_lock(inst); + vdec->slots[vpu_buf->fs_id].state = VPU_BUF_STATE_READY; vdec->display_frame_count++; vpu_inst_unlock(inst); + v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE); dev_dbg(inst->dev, "[%d] decoded : %d, display : %d, sequence : %d\n", inst->id, vdec->decoded_frame_count, vdec->display_frame_count, vdec->sequence); } @@ -1081,18 +1151,30 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb if (!vbuf) return -EINVAL; - if (vdec->slots[vbuf->vb2_buf.index]) { - dev_err(inst->dev, "[%d] repeat alloc fs %d\n", - inst->id, vbuf->vb2_buf.index); + vpu_buf = to_vpu_vb2_buffer(vbuf); + if (vpu_buf->fs_id < 0 || vpu_buf->fs_id >= vdec->slot_count) { + dev_err(inst->dev, "invalid fs %d for v4l2 buffer %d\n", + vpu_buf->fs_id, vbuf->vb2_buf.index); return -EINVAL; } + if (vdec->slots[vpu_buf->fs_id].curr) { + if (vdec->slots[vpu_buf->fs_id].curr != vpu_buf) { + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_CHANGED); + vdec->slots[vpu_buf->fs_id].pend = vpu_buf; + } else { + vpu_set_buffer_state(vbuf, vdec->slots[vpu_buf->fs_id].state); + } + dev_err(inst->dev, "[%d] repeat alloc fs %d (v4l2 index %d)\n", + inst->id, vpu_buf->fs_id, vbuf->vb2_buf.index); + return -EAGAIN; + } + dev_dbg(inst->dev, "[%d] state = %s, alloc fs %d, tag = 0x%x\n", inst->id, vpu_codec_state_name(inst->state), vbuf->vb2_buf.index, vdec->seq_tag); - vpu_buf = to_vpu_vb2_buffer(vbuf); memset(&info, 0, sizeof(info)); - info.id = vbuf->vb2_buf.index; + info.id = vpu_buf->fs_id; info.type = MEM_RES_FRAME; info.tag = vdec->seq_tag; info.luma_addr = vpu_get_vb_phy_addr(&vbuf->vb2_buf, 0); @@ -1107,12 +1189,13 @@ static int vdec_response_frame(struct vpu_inst *inst, struct vb2_v4l2_buffer *vb if (ret) return ret; - vpu_buf->tag = info.tag; vpu_buf->luma = info.luma_addr; vpu_buf->chroma_u = info.chroma_addr; vpu_buf->chroma_v = 0; vpu_set_buffer_state(vbuf, VPU_BUF_STATE_INUSE); - vdec->slots[info.id] = vpu_buf; + vdec->slots[info.id].tag = info.tag; + vdec->slots[info.id].curr = vpu_buf; + vdec->slots[info.id].state = VPU_BUF_STATE_INUSE; vdec->req_frame_count--; return 0; @@ -1173,25 +1256,47 @@ static void vdec_recycle_buffer(struct vpu_inst *inst, struct vb2_v4l2_buffer *v v4l2_m2m_buf_queue(inst->fh.m2m_ctx, vbuf); } -static void vdec_clear_slots(struct vpu_inst *inst) +static void vdec_release_curr_frame_store(struct vpu_inst *inst, u32 id) { struct vdec_t *vdec = inst->priv; struct vpu_vb2_buffer *vpu_buf; struct vb2_v4l2_buffer *vbuf; + + if (id >= vdec->slot_count) + return; + if (!vdec->slots[id].curr) + return; + + vpu_buf = vdec->slots[id].curr; + vbuf = &vpu_buf->m2m_buf.vb; + + vdec_response_fs_release(inst, id, vdec->slots[id].tag); + if (vpu_buf->fs_id == id) { + if (vpu_buf->state != VPU_BUF_STATE_READY) + vdec_recycle_buffer(inst, vbuf); + vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + } + + vdec->slots[id].curr = NULL; + vdec->slots[id].state = VPU_BUF_STATE_IDLE; + + if (vdec->slots[id].pend) { + vpu_set_buffer_state(&vdec->slots[id].pend->m2m_buf.vb, VPU_BUF_STATE_IDLE); + vdec->slots[id].pend = NULL; + } +} + +static void vdec_clear_slots(struct vpu_inst *inst) +{ + struct vdec_t *vdec = inst->priv; int i; - for (i = 0; i < ARRAY_SIZE(vdec->slots); i++) { - if (!vdec->slots[i]) + for (i = 0; i < vdec->slot_count; i++) { + if (!vdec->slots[i].curr) continue; - vpu_buf = vdec->slots[i]; - vbuf = &vpu_buf->m2m_buf.vb; - vpu_trace(inst->dev, "clear slot %d\n", i); - vdec_response_fs_release(inst, i, vpu_buf->tag); - vdec_recycle_buffer(inst, vbuf); - vdec->slots[i]->state = VPU_BUF_STATE_IDLE; - vdec->slots[i] = NULL; + vdec_release_curr_frame_store(inst, i); } } @@ -1322,39 +1427,29 @@ static void vdec_event_req_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) static void vdec_evnet_rel_fs(struct vpu_inst *inst, struct vpu_fs_info *fs) { struct vdec_t *vdec = inst->priv; - struct vpu_vb2_buffer *vpu_buf; - struct vb2_v4l2_buffer *vbuf; - if (!fs || fs->id >= ARRAY_SIZE(vdec->slots)) + if (!fs || fs->id >= vdec->slot_count) return; if (fs->type != MEM_RES_FRAME) return; - if (fs->id >= vpu_get_num_buffers(inst, inst->cap_format.type)) { + if (fs->id >= vdec->slot_count) { dev_err(inst->dev, "[%d] invalid fs(%d) to release\n", inst->id, fs->id); return; } vpu_inst_lock(inst); - vpu_buf = vdec->slots[fs->id]; - vdec->slots[fs->id] = NULL; - - if (!vpu_buf) { + if (!vdec->slots[fs->id].curr) { dev_dbg(inst->dev, "[%d] fs[%d] has bee released\n", inst->id, fs->id); goto exit; } - vbuf = &vpu_buf->m2m_buf.vb; - if (vpu_get_buffer_state(vbuf) == VPU_BUF_STATE_DECODED) { + if (vdec->slots[fs->id].state == VPU_BUF_STATE_DECODED) { dev_dbg(inst->dev, "[%d] frame skip\n", inst->id); vdec->sequence++; } - vdec_response_fs_release(inst, fs->id, vpu_buf->tag); - if (vpu_get_buffer_state(vbuf) != VPU_BUF_STATE_READY) - vdec_recycle_buffer(inst, vbuf); - - vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + vdec_release_curr_frame_store(inst, fs->id); vpu_process_capture_buffer(inst); exit: @@ -1544,6 +1639,11 @@ static void vdec_cleanup(struct vpu_inst *inst) return; vdec = inst->priv; + if (vdec) { + kfree(vdec->slots); + vdec->slots = NULL; + vdec->slot_count = 0; + } vfree(vdec); inst->priv = NULL; vfree(inst); @@ -1665,10 +1765,42 @@ static int vdec_stop_session(struct vpu_inst *inst, u32 type) return 0; } +static int vdec_get_slot_debug_info(struct vpu_inst *inst, char *str, u32 size, u32 i) +{ + struct vdec_t *vdec = inst->priv; + struct vpu_vb2_buffer *vpu_buf; + int num = -1; + + vpu_inst_lock(inst); + if (i >= vdec->slot_count || !vdec->slots[i].addr) + goto exit; + + vpu_buf = vdec->slots[i].curr; + + num = scnprintf(str, size, "slot[%2d] :", i); + if (vpu_buf) { + num += scnprintf(str + num, size - num, " %2d", + vpu_buf->m2m_buf.vb.vb2_buf.index); + num += scnprintf(str + num, size - num, "; state = %d", vdec->slots[i].state); + } else { + num += scnprintf(str + num, size - num, " -1"); + } + + if (vdec->slots[i].pend) + num += scnprintf(str + num, size - num, "; %d", + vdec->slots[i].pend->m2m_buf.vb.vb2_buf.index); + + num += scnprintf(str + num, size - num, "\n"); +exit: + vpu_inst_unlock(inst); + + return num; +} + static int vdec_get_debug_info(struct vpu_inst *inst, char *str, u32 size, u32 i) { struct vdec_t *vdec = inst->priv; - int num = -1; + int num; switch (i) { case 0: @@ -1723,6 +1855,7 @@ static int vdec_get_debug_info(struct vpu_inst *inst, char *str, u32 size, u32 i vdec->codec_info.vui_present); break; default: + num = vdec_get_slot_debug_info(inst, str, size, i - 10); break; } @@ -1746,6 +1879,8 @@ static struct vpu_inst_ops vdec_inst_ops = { .get_debug_info = vdec_get_debug_info, .wait_prepare = vpu_inst_unlock, .wait_finish = vpu_inst_lock, + .attach_frame_store = vdec_attach_frame_store, + .reset_frame_store = vdec_reset_frame_store, }; static void vdec_init(struct file *file) @@ -1786,6 +1921,16 @@ static int vdec_open(struct file *file) return -ENOMEM; } + vdec->slots = kmalloc_array(VDEC_SLOT_CNT_DFT, + sizeof(*vdec->slots), + GFP_KERNEL | __GFP_ZERO); + if (!vdec->slots) { + vfree(vdec); + vfree(inst); + return -ENOMEM; + } + vdec->slot_count = VDEC_SLOT_CNT_DFT; + inst->ops = &vdec_inst_ops; inst->formats = vdec_formats; inst->type = VPU_CORE_TYPE_DEC; diff --git a/drivers/media/platform/amphion/vpu.h b/drivers/media/platform/amphion/vpu.h index 1451549c9dd2..cac0f1a64fea 100644 --- a/drivers/media/platform/amphion/vpu.h +++ b/drivers/media/platform/amphion/vpu.h @@ -222,6 +222,8 @@ struct vpu_inst_ops { int (*get_debug_info)(struct vpu_inst *inst, char *str, u32 size, u32 i); void (*wait_prepare)(struct vpu_inst *inst); void (*wait_finish)(struct vpu_inst *inst); + void (*attach_frame_store)(struct vpu_inst *inst, struct vb2_buffer *vb); + void (*reset_frame_store)(struct vpu_inst *inst); }; struct vpu_inst { @@ -295,7 +297,8 @@ enum { VPU_BUF_STATE_DECODED, VPU_BUF_STATE_READY, VPU_BUF_STATE_SKIP, - VPU_BUF_STATE_ERROR + VPU_BUF_STATE_ERROR, + VPU_BUF_STATE_CHANGED }; struct vpu_vb2_buffer { @@ -304,8 +307,8 @@ struct vpu_vb2_buffer { dma_addr_t chroma_u; dma_addr_t chroma_v; unsigned int state; - u32 tag; u32 average_qp; + s32 fs_id; }; void vpu_writel(struct vpu_dev *vpu, u32 reg, u32 val); diff --git a/drivers/media/platform/amphion/vpu_dbg.c b/drivers/media/platform/amphion/vpu_dbg.c index 940e5bda5fa3..497ae4e8a229 100644 --- a/drivers/media/platform/amphion/vpu_dbg.c +++ b/drivers/media/platform/amphion/vpu_dbg.c @@ -48,6 +48,7 @@ static char *vpu_stat_name[] = { [VPU_BUF_STATE_READY] = "ready", [VPU_BUF_STATE_SKIP] = "skip", [VPU_BUF_STATE_ERROR] = "error", + [VPU_BUF_STATE_CHANGED] = "changed", }; static inline const char *to_vpu_stat_name(int state) @@ -164,6 +165,7 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) for (i = 0; i < vb2_get_num_buffers(vq); i++) { struct vb2_buffer *vb; struct vb2_v4l2_buffer *vbuf; + struct vpu_vb2_buffer *vpu_buf; vb = vb2_get_buffer(vq, i); if (!vb) @@ -173,13 +175,24 @@ static int vpu_dbg_instance(struct seq_file *s, void *data) continue; vbuf = to_vb2_v4l2_buffer(vb); + vpu_buf = to_vpu_vb2_buffer(vbuf); num = scnprintf(str, sizeof(str), - "capture[%2d] state = %10s, %8s\n", + "capture[%2d] state = %10s, %8s", i, vb2_stat_name[vb->state], to_vpu_stat_name(vpu_get_buffer_state(vbuf))); if (seq_write(s, str, num)) return 0; + + if (vpu_buf->fs_id >= 0) { + num = scnprintf(str, sizeof(str), "; fs %d", vpu_buf->fs_id); + if (seq_write(s, str, num)) + return 0; + } + + num = scnprintf(str, sizeof(str), "\n"); + if (seq_write(s, str, num)) + return 0; } num = scnprintf(str, sizeof(str), "sequence = %d\n", inst->sequence); diff --git a/drivers/media/platform/amphion/vpu_v4l2.c b/drivers/media/platform/amphion/vpu_v4l2.c index 45707931bc4f..74668fa362e2 100644 --- a/drivers/media/platform/amphion/vpu_v4l2.c +++ b/drivers/media/platform/amphion/vpu_v4l2.c @@ -501,14 +501,25 @@ static int vpu_vb2_queue_setup(struct vb2_queue *vq, call_void_vop(inst, release); } + if (V4L2_TYPE_IS_CAPTURE(vq->type)) + call_void_vop(inst, reset_frame_store); + return 0; } static int vpu_vb2_buf_init(struct vb2_buffer *vb) { struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); + struct vpu_vb2_buffer *vpu_buf = to_vpu_vb2_buffer(vbuf); + struct vpu_inst *inst = vb2_get_drv_priv(vb->vb2_queue); + vpu_buf->fs_id = -1; vpu_set_buffer_state(vbuf, VPU_BUF_STATE_IDLE); + + if (!inst->ops->attach_frame_store || V4L2_TYPE_IS_OUTPUT(vb->type)) + return 0; + + call_void_vop(inst, attach_frame_store, vb); return 0; } From c6ffb7e1fb90b28d95b054564f89a3fba0e0d077 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 23 Jun 2025 12:07:15 -0400 Subject: [PATCH 307/310] media: dt-bindings: rockchip: Document RK3588 Video Decoder bindings Document the Rockchip RK3588 Video Decoder bindings. Signed-off-by: Detlev Casanova Reviewed-by: Conor Dooley Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- .../bindings/media/rockchip,vdec.yaml | 77 ++++++++++++++++++- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml index 08b02ec16755..e9b82dc05fd1 100644 --- a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml @@ -10,13 +10,14 @@ maintainers: - Heiko Stuebner description: |- - The Rockchip rk3399 has a stateless Video Decoder that can decodes H.264, - HEVC an VP9 streams. + Rockchip SoCs have variants of the same stateless Video Decoder that can + decodes H.264, HEVC, VP9 and AV1 streams, depending on the variant. properties: compatible: oneOf: - const: rockchip,rk3399-vdec + - const: rockchip,rk3588-vdec - items: - enum: - rockchip,rk3228-vdec @@ -24,35 +25,72 @@ properties: - const: rockchip,rk3399-vdec reg: - maxItems: 1 + minItems: 1 + items: + - description: The function configuration registers base + - description: The link table configuration registers base + - description: The cache configuration registers base + + reg-names: + items: + - const: function + - const: link + - const: cache interrupts: maxItems: 1 clocks: + minItems: 4 items: - description: The Video Decoder AXI interface clock - description: The Video Decoder AHB interface clock - description: The Video Decoded CABAC clock - description: The Video Decoder core clock + - description: The Video decoder HEVC CABAC clock clock-names: + minItems: 4 items: - const: axi - const: ahb - const: cabac - const: core + - const: hevc_cabac assigned-clocks: true assigned-clock-rates: true + resets: + items: + - description: The Video Decoder AXI interface reset + - description: The Video Decoder AHB interface reset + - description: The Video Decoded CABAC reset + - description: The Video Decoder core reset + - description: The Video decoder HEVC CABAC reset + + reset-names: + items: + - const: axi + - const: ahb + - const: cabac + - const: core + - const: hevc_cabac + power-domains: maxItems: 1 iommus: maxItems: 1 + sram: + $ref: /schemas/types.yaml#/definitions/phandle + description: | + phandle to a reserved on-chip SRAM regions. + Some SoCs, like rk3588 provide on-chip SRAM to store temporary + buffers during decoding. + required: - compatible - reg @@ -61,6 +99,39 @@ required: - clock-names - power-domains +allOf: + - if: + properties: + compatible: + contains: + const: rockchip,rk3588-vdec + then: + properties: + reg: + minItems: 3 + reg-names: + minItems: 3 + clocks: + minItems: 5 + clock-names: + minItems: 5 + resets: + minItems: 5 + reset-names: + minItems: 5 + else: + properties: + reg: + maxItems: 1 + reg-names: false + clocks: + maxItems: 4 + clock-names: + maxItems: 4 + resets: false + reset-names: false + sram: false + additionalProperties: false examples: From a5c4a652647609938e0ce4ff1f3d4ad4cdac6262 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 23 Jun 2025 12:07:16 -0400 Subject: [PATCH 308/310] media: dt-bindings: rockchip: Add RK3576 Video Decoder bindings The video decoder in RK3576 (vdpu383) is described the same way as the one in RK3588 (vdpu381). A new compatible is added as the driver implementation will be different. Signed-off-by: Detlev Casanova Acked-by: Rob Herring (Arm) Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- Documentation/devicetree/bindings/media/rockchip,vdec.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml index e9b82dc05fd1..96b6c8938768 100644 --- a/Documentation/devicetree/bindings/media/rockchip,vdec.yaml +++ b/Documentation/devicetree/bindings/media/rockchip,vdec.yaml @@ -17,6 +17,7 @@ properties: compatible: oneOf: - const: rockchip,rk3399-vdec + - const: rockchip,rk3576-vdec - const: rockchip,rk3588-vdec - items: - enum: @@ -104,7 +105,9 @@ allOf: properties: compatible: contains: - const: rockchip,rk3588-vdec + enum: + - rockchip,rk3576-vdec + - rockchip,rk3588-vdec then: properties: reg: From e165f6eda53519d31ecb58603a53fcb03fd75eb5 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 23 Jun 2025 12:07:21 -0400 Subject: [PATCH 309/310] media: rkvdec: Remove TODO file 2 items are present in the TODO file: - HEVC support - Evaluate adding helper for rkvdec_request_validate Missing HEVC support is not a reason for a driver to be in staging, support for different features of the hardware can be added in drivers in the main tree. The rkvdec_request_validate function was simplified in commit 54676d5f5630 ("media: rkvdec: Do not require all controls to be present in every request") by not setting controls that have not changed. As it now basically just calls vb2_request_validate(), there is no need for a helper. Signed-off-by: Detlev Casanova Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- drivers/staging/media/rkvdec/TODO | 11 ----------- 1 file changed, 11 deletions(-) delete mode 100644 drivers/staging/media/rkvdec/TODO diff --git a/drivers/staging/media/rkvdec/TODO b/drivers/staging/media/rkvdec/TODO deleted file mode 100644 index 2c0779383276..000000000000 --- a/drivers/staging/media/rkvdec/TODO +++ /dev/null @@ -1,11 +0,0 @@ -* Support for HEVC is planned for this driver. - - Given the V4L controls for that CODEC will be part of - the uABI, it will be required to have the driver in staging. - - For this reason, we are keeping this driver in staging for now. - -* Evaluate introducing a helper to consolidate duplicated - code in rkvdec_request_validate and cedrus_request_validate. - The helper needs to the driver private data associated with - the videobuf2 queue, from a media request. From d968e50b5c26642754492dea23cbd3592bde62d8 Mon Sep 17 00:00:00 2001 From: Detlev Casanova Date: Mon, 23 Jun 2025 12:07:22 -0400 Subject: [PATCH 310/310] media: rkvdec: Unstage the driver The TODO list for unstaging being empty, the driver can now be moved to the main media folder. Also add myself as maintainer. Signed-off-by: Detlev Casanova Reviewed-by: Nicolas Dufresne Signed-off-by: Nicolas Dufresne Signed-off-by: Hans Verkuil --- MAINTAINERS | 8 ++++++++ drivers/media/platform/rockchip/Kconfig | 1 + drivers/media/platform/rockchip/Makefile | 1 + .../media => media/platform/rockchip}/rkvdec/Kconfig | 0 .../media => media/platform/rockchip}/rkvdec/Makefile | 0 .../platform/rockchip}/rkvdec/rkvdec-h264.c | 0 .../platform/rockchip}/rkvdec/rkvdec-regs.h | 0 .../media => media/platform/rockchip}/rkvdec/rkvdec-vp9.c | 0 .../media => media/platform/rockchip}/rkvdec/rkvdec.c | 0 .../media => media/platform/rockchip}/rkvdec/rkvdec.h | 0 drivers/staging/media/Kconfig | 2 -- drivers/staging/media/Makefile | 1 - 12 files changed, 10 insertions(+), 3 deletions(-) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/Kconfig (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/Makefile (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/rkvdec-h264.c (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/rkvdec-regs.h (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/rkvdec-vp9.c (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/rkvdec.c (100%) rename drivers/{staging/media => media/platform/rockchip}/rkvdec/rkvdec.h (100%) diff --git a/MAINTAINERS b/MAINTAINERS index 5f69c82d53ee..24c557ee091d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -21487,6 +21487,14 @@ S: Maintained F: Documentation/devicetree/bindings/media/rockchip-rga.yaml F: drivers/media/platform/rockchip/rga/ +ROCKCHIP RKVDEC VIDEO DECODER DRIVER +M: Detlev Casanova +L: linux-media@vger.kernel.org +L: linux-rockchip@lists.infradead.org +S: Maintained +F: Documentation/devicetree/bindings/media/rockchip,vdec.yaml +F: drivers/media/platform/rockchip/rkvdec/ + ROCKCHIP RK3308 INTERNAL AUDIO CODEC M: Luca Ceresoli S: Maintained diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig index b41d3960c1b4..9bbeec4996aa 100644 --- a/drivers/media/platform/rockchip/Kconfig +++ b/drivers/media/platform/rockchip/Kconfig @@ -4,3 +4,4 @@ comment "Rockchip media platform drivers" source "drivers/media/platform/rockchip/rga/Kconfig" source "drivers/media/platform/rockchip/rkisp1/Kconfig" +source "drivers/media/platform/rockchip/rkvdec/Kconfig" diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile index 4f782b876ac9..286dc5c53f7e 100644 --- a/drivers/media/platform/rockchip/Makefile +++ b/drivers/media/platform/rockchip/Makefile @@ -1,3 +1,4 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y += rga/ obj-y += rkisp1/ +obj-y += rkvdec/ diff --git a/drivers/staging/media/rkvdec/Kconfig b/drivers/media/platform/rockchip/rkvdec/Kconfig similarity index 100% rename from drivers/staging/media/rkvdec/Kconfig rename to drivers/media/platform/rockchip/rkvdec/Kconfig diff --git a/drivers/staging/media/rkvdec/Makefile b/drivers/media/platform/rockchip/rkvdec/Makefile similarity index 100% rename from drivers/staging/media/rkvdec/Makefile rename to drivers/media/platform/rockchip/rkvdec/Makefile diff --git a/drivers/staging/media/rkvdec/rkvdec-h264.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c similarity index 100% rename from drivers/staging/media/rkvdec/rkvdec-h264.c rename to drivers/media/platform/rockchip/rkvdec/rkvdec-h264.c diff --git a/drivers/staging/media/rkvdec/rkvdec-regs.h b/drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h similarity index 100% rename from drivers/staging/media/rkvdec/rkvdec-regs.h rename to drivers/media/platform/rockchip/rkvdec/rkvdec-regs.h diff --git a/drivers/staging/media/rkvdec/rkvdec-vp9.c b/drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c similarity index 100% rename from drivers/staging/media/rkvdec/rkvdec-vp9.c rename to drivers/media/platform/rockchip/rkvdec/rkvdec-vp9.c diff --git a/drivers/staging/media/rkvdec/rkvdec.c b/drivers/media/platform/rockchip/rkvdec/rkvdec.c similarity index 100% rename from drivers/staging/media/rkvdec/rkvdec.c rename to drivers/media/platform/rockchip/rkvdec/rkvdec.c diff --git a/drivers/staging/media/rkvdec/rkvdec.h b/drivers/media/platform/rockchip/rkvdec/rkvdec.h similarity index 100% rename from drivers/staging/media/rkvdec/rkvdec.h rename to drivers/media/platform/rockchip/rkvdec/rkvdec.h diff --git a/drivers/staging/media/Kconfig b/drivers/staging/media/Kconfig index 043ffbc472fb..ab250c89cd4d 100644 --- a/drivers/staging/media/Kconfig +++ b/drivers/staging/media/Kconfig @@ -34,8 +34,6 @@ source "drivers/staging/media/max96712/Kconfig" source "drivers/staging/media/meson/vdec/Kconfig" -source "drivers/staging/media/rkvdec/Kconfig" - source "drivers/staging/media/starfive/Kconfig" source "drivers/staging/media/sunxi/Kconfig" diff --git a/drivers/staging/media/Makefile b/drivers/staging/media/Makefile index 177dfe95289b..4a073938b2b2 100644 --- a/drivers/staging/media/Makefile +++ b/drivers/staging/media/Makefile @@ -4,7 +4,6 @@ obj-$(CONFIG_INTEL_ATOMISP) += atomisp/ obj-$(CONFIG_VIDEO_IMX_MEDIA) += imx/ obj-$(CONFIG_VIDEO_MAX96712) += max96712/ obj-$(CONFIG_VIDEO_MESON_VDEC) += meson/vdec/ -obj-$(CONFIG_VIDEO_ROCKCHIP_VDEC) += rkvdec/ obj-$(CONFIG_VIDEO_STARFIVE_CAMSS) += starfive/ obj-$(CONFIG_VIDEO_SUNXI) += sunxi/ obj-$(CONFIG_VIDEO_TEGRA) += tegra-video/