From 150f431a08317e0e0363a7f9147b6246d3b40ba6 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 19 Apr 2024 10:29:01 +0200 Subject: drm/fbdev: Add fbdev-shmem Add an fbdev emulation for SHMEM-based memory managers. The code is similar to fbdev-generic, but does not require an additional shadow buffer for mmap(). Fbdev-shmem operates directly on the buffer object's SHMEM pages. Fbdev's deferred-I/O mechanism updates the hardware state on write operations. The memory pages of GEM SHMEM cannot be detected by fbdefio. Therefore fbdev-shmem implements the .get_page() hook in struct fb_deferred_io. The fbdefio helpers call this hook to retrieve the page directly from fbdev-shmem instead of trying to detect it internally. v3: - clarify on get_page mechanism in commit description (Javier) v2: - use drm_driver_legacy_fb_format() (Geert) Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20240419083331.7761-9-tzimmermann@suse.de --- include/drm/drm_fbdev_shmem.h | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 include/drm/drm_fbdev_shmem.h (limited to 'include/drm') diff --git a/include/drm/drm_fbdev_shmem.h b/include/drm/drm_fbdev_shmem.h new file mode 100644 index 000000000000..fb43cadd1950 --- /dev/null +++ b/include/drm/drm_fbdev_shmem.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_FBDEV_SHMEM_H +#define DRM_FBDEV_SHMEM_H + +struct drm_device; + +#ifdef CONFIG_DRM_FBDEV_EMULATION +void drm_fbdev_shmem_setup(struct drm_device *dev, unsigned int preferred_bpp); +#else +static inline void drm_fbdev_shmem_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ } +#endif + +#endif -- cgit v1.2.3-58-ga151 From aae4682e5d66c1e1dc181fa341652e037237f144 Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 19 Apr 2024 10:29:35 +0200 Subject: drm/fbdev-generic: Convert to fbdev-ttm Only TTM-based drivers use fbdev-generic. Rename it to fbdev-ttm and change the symbol infix from _generic_ to _ttm_. Link the source file into TTM helpers, so that it is only build if TTM-based drivers have been selected. Select DRM_TTM_HELPER for loongson. Signed-off-by: Thomas Zimmermann Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20240419083331.7761-43-tzimmermann@suse.de --- Documentation/gpu/drm-kms-helpers.rst | 2 +- drivers/gpu/drm/Makefile | 5 +- drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c | 6 +- drivers/gpu/drm/drm_fbdev_generic.c | 349 ------------------------ drivers/gpu/drm/drm_fbdev_ttm.c | 349 ++++++++++++++++++++++++ drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c | 4 +- drivers/gpu/drm/loongson/Kconfig | 1 + drivers/gpu/drm/loongson/lsdc_drv.c | 4 +- drivers/gpu/drm/nouveau/nouveau_drm.c | 6 +- drivers/gpu/drm/qxl/qxl_drv.c | 4 +- drivers/gpu/drm/tiny/bochs.c | 4 +- drivers/gpu/drm/vboxvideo/vbox_drv.c | 4 +- drivers/gpu/drm/vmwgfx/vmwgfx_drv.c | 4 +- include/drm/drm_fbdev_generic.h | 15 - include/drm/drm_fbdev_ttm.h | 15 + 15 files changed, 386 insertions(+), 386 deletions(-) delete mode 100644 drivers/gpu/drm/drm_fbdev_generic.c create mode 100644 drivers/gpu/drm/drm_fbdev_ttm.c delete mode 100644 include/drm/drm_fbdev_generic.h create mode 100644 include/drm/drm_fbdev_ttm.h (limited to 'include/drm') diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst index 59cfe8a7a8ba..e46ab9b670ac 100644 --- a/Documentation/gpu/drm-kms-helpers.rst +++ b/Documentation/gpu/drm-kms-helpers.rst @@ -116,7 +116,7 @@ fbdev Helper Functions Reference .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.c :export: -.. kernel-doc:: drivers/gpu/drm/drm_fbdev_generic.c +.. kernel-doc:: drivers/gpu/drm/drm_fbdev_ttm.c :export: format Helper Functions Reference diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index fc793c6dd299..68cc9258ffc4 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -118,6 +118,7 @@ drm_vram_helper-y := drm_gem_vram_helper.o obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o drm_ttm_helper-y := drm_gem_ttm_helper.o +drm_ttm_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fbdev_ttm.o obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o # @@ -143,9 +144,7 @@ drm_kms_helper-y := \ drm_self_refresh_helper.o \ drm_simple_kms_helper.o drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o -drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += \ - drm_fbdev_generic.o \ - drm_fb_helper.o +drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o # diff --git a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c index ea14f1c8f430..4f76aa606ac9 100644 --- a/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c +++ b/drivers/gpu/drm/amd/amdgpu/amdgpu_drv.c @@ -24,7 +24,7 @@ #include #include -#include +#include #include #include #include @@ -2318,9 +2318,9 @@ retry_init: !list_empty(&adev_to_drm(adev)->mode_config.connector_list)) { /* select 8 bpp console on low vram cards */ if (adev->gmc.real_vram_size <= (32*1024*1024)) - drm_fbdev_generic_setup(adev_to_drm(adev), 8); + drm_fbdev_ttm_setup(adev_to_drm(adev), 8); else - drm_fbdev_generic_setup(adev_to_drm(adev), 32); + drm_fbdev_ttm_setup(adev_to_drm(adev), 32); } ret = amdgpu_debugfs_init(adev); diff --git a/drivers/gpu/drm/drm_fbdev_generic.c b/drivers/gpu/drm/drm_fbdev_generic.c deleted file mode 100644 index 97e579c33d84..000000000000 --- a/drivers/gpu/drm/drm_fbdev_generic.c +++ /dev/null @@ -1,349 +0,0 @@ -// SPDX-License-Identifier: MIT - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -/* @user: 1=userspace, 0=fbcon */ -static int drm_fbdev_generic_fb_open(struct fb_info *info, int user) -{ - struct drm_fb_helper *fb_helper = info->par; - - /* No need to take a ref for fbcon because it unbinds on unregister */ - if (user && !try_module_get(fb_helper->dev->driver->fops->owner)) - return -ENODEV; - - return 0; -} - -static int drm_fbdev_generic_fb_release(struct fb_info *info, int user) -{ - struct drm_fb_helper *fb_helper = info->par; - - if (user) - module_put(fb_helper->dev->driver->fops->owner); - - return 0; -} - -FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(drm_fbdev_generic, - drm_fb_helper_damage_range, - drm_fb_helper_damage_area); - -static void drm_fbdev_generic_fb_destroy(struct fb_info *info) -{ - struct drm_fb_helper *fb_helper = info->par; - void *shadow = info->screen_buffer; - - if (!fb_helper->dev) - return; - - fb_deferred_io_cleanup(info); - drm_fb_helper_fini(fb_helper); - vfree(shadow); - drm_client_framebuffer_delete(fb_helper->buffer); - - drm_client_release(&fb_helper->client); - drm_fb_helper_unprepare(fb_helper); - kfree(fb_helper); -} - -static const struct fb_ops drm_fbdev_generic_fb_ops = { - .owner = THIS_MODULE, - .fb_open = drm_fbdev_generic_fb_open, - .fb_release = drm_fbdev_generic_fb_release, - FB_DEFAULT_DEFERRED_OPS(drm_fbdev_generic), - DRM_FB_HELPER_DEFAULT_OPS, - .fb_destroy = drm_fbdev_generic_fb_destroy, -}; - -/* - * This function uses the client API to create a framebuffer backed by a dumb buffer. - */ -static int drm_fbdev_generic_helper_fb_probe(struct drm_fb_helper *fb_helper, - struct drm_fb_helper_surface_size *sizes) -{ - struct drm_client_dev *client = &fb_helper->client; - struct drm_device *dev = fb_helper->dev; - struct drm_client_buffer *buffer; - struct fb_info *info; - size_t screen_size; - void *screen_buffer; - u32 format; - int ret; - - drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n", - sizes->surface_width, sizes->surface_height, - sizes->surface_bpp); - - format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); - buffer = drm_client_framebuffer_create(client, sizes->surface_width, - sizes->surface_height, format); - if (IS_ERR(buffer)) - return PTR_ERR(buffer); - - fb_helper->buffer = buffer; - fb_helper->fb = buffer->fb; - - screen_size = buffer->gem->size; - screen_buffer = vzalloc(screen_size); - if (!screen_buffer) { - ret = -ENOMEM; - goto err_drm_client_framebuffer_delete; - } - - info = drm_fb_helper_alloc_info(fb_helper); - if (IS_ERR(info)) { - ret = PTR_ERR(info); - goto err_vfree; - } - - drm_fb_helper_fill_info(info, fb_helper, sizes); - - info->fbops = &drm_fbdev_generic_fb_ops; - - /* screen */ - info->flags |= FBINFO_VIRTFB | FBINFO_READS_FAST; - info->screen_buffer = screen_buffer; - info->fix.smem_len = screen_size; - - /* deferred I/O */ - fb_helper->fbdefio.delay = HZ / 20; - fb_helper->fbdefio.deferred_io = drm_fb_helper_deferred_io; - - info->fbdefio = &fb_helper->fbdefio; - ret = fb_deferred_io_init(info); - if (ret) - goto err_drm_fb_helper_release_info; - - return 0; - -err_drm_fb_helper_release_info: - drm_fb_helper_release_info(fb_helper); -err_vfree: - vfree(screen_buffer); -err_drm_client_framebuffer_delete: - fb_helper->fb = NULL; - fb_helper->buffer = NULL; - drm_client_framebuffer_delete(buffer); - return ret; -} - -static void drm_fbdev_generic_damage_blit_real(struct drm_fb_helper *fb_helper, - struct drm_clip_rect *clip, - struct iosys_map *dst) -{ - struct drm_framebuffer *fb = fb_helper->fb; - size_t offset = clip->y1 * fb->pitches[0]; - size_t len = clip->x2 - clip->x1; - unsigned int y; - void *src; - - switch (drm_format_info_bpp(fb->format, 0)) { - case 1: - offset += clip->x1 / 8; - len = DIV_ROUND_UP(len + clip->x1 % 8, 8); - break; - case 2: - offset += clip->x1 / 4; - len = DIV_ROUND_UP(len + clip->x1 % 4, 4); - break; - case 4: - offset += clip->x1 / 2; - len = DIV_ROUND_UP(len + clip->x1 % 2, 2); - break; - default: - offset += clip->x1 * fb->format->cpp[0]; - len *= fb->format->cpp[0]; - break; - } - - src = fb_helper->info->screen_buffer + offset; - iosys_map_incr(dst, offset); /* go to first pixel within clip rect */ - - for (y = clip->y1; y < clip->y2; y++) { - iosys_map_memcpy_to(dst, 0, src, len); - iosys_map_incr(dst, fb->pitches[0]); - src += fb->pitches[0]; - } -} - -static int drm_fbdev_generic_damage_blit(struct drm_fb_helper *fb_helper, - struct drm_clip_rect *clip) -{ - struct drm_client_buffer *buffer = fb_helper->buffer; - struct iosys_map map, dst; - int ret; - - /* - * We have to pin the client buffer to its current location while - * flushing the shadow buffer. In the general case, concurrent - * modesetting operations could try to move the buffer and would - * fail. The modeset has to be serialized by acquiring the reservation - * object of the underlying BO here. - * - * For fbdev emulation, we only have to protect against fbdev modeset - * operations. Nothing else will involve the client buffer's BO. So it - * is sufficient to acquire struct drm_fb_helper.lock here. - */ - mutex_lock(&fb_helper->lock); - - ret = drm_client_buffer_vmap_local(buffer, &map); - if (ret) - goto out; - - dst = map; - drm_fbdev_generic_damage_blit_real(fb_helper, clip, &dst); - - drm_client_buffer_vunmap_local(buffer); - -out: - mutex_unlock(&fb_helper->lock); - - return ret; -} - -static int drm_fbdev_generic_helper_fb_dirty(struct drm_fb_helper *helper, - struct drm_clip_rect *clip) -{ - struct drm_device *dev = helper->dev; - int ret; - - /* Call damage handlers only if necessary */ - if (!(clip->x1 < clip->x2 && clip->y1 < clip->y2)) - return 0; - - ret = drm_fbdev_generic_damage_blit(helper, clip); - if (drm_WARN_ONCE(dev, ret, "Damage blitter failed: ret=%d\n", ret)) - return ret; - - if (helper->fb->funcs->dirty) { - ret = helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, clip, 1); - if (drm_WARN_ONCE(dev, ret, "Dirty helper failed: ret=%d\n", ret)) - return ret; - } - - return 0; -} - -static const struct drm_fb_helper_funcs drm_fbdev_generic_helper_funcs = { - .fb_probe = drm_fbdev_generic_helper_fb_probe, - .fb_dirty = drm_fbdev_generic_helper_fb_dirty, -}; - -static void drm_fbdev_generic_client_unregister(struct drm_client_dev *client) -{ - struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); - - if (fb_helper->info) { - drm_fb_helper_unregister_info(fb_helper); - } else { - drm_client_release(&fb_helper->client); - drm_fb_helper_unprepare(fb_helper); - kfree(fb_helper); - } -} - -static int drm_fbdev_generic_client_restore(struct drm_client_dev *client) -{ - drm_fb_helper_lastclose(client->dev); - - return 0; -} - -static int drm_fbdev_generic_client_hotplug(struct drm_client_dev *client) -{ - struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); - struct drm_device *dev = client->dev; - int ret; - - if (dev->fb_helper) - return drm_fb_helper_hotplug_event(dev->fb_helper); - - ret = drm_fb_helper_init(dev, fb_helper); - if (ret) - goto err_drm_err; - - if (!drm_drv_uses_atomic_modeset(dev)) - drm_helper_disable_unused_functions(dev); - - ret = drm_fb_helper_initial_config(fb_helper); - if (ret) - goto err_drm_fb_helper_fini; - - return 0; - -err_drm_fb_helper_fini: - drm_fb_helper_fini(fb_helper); -err_drm_err: - drm_err(dev, "fbdev: Failed to setup generic emulation (ret=%d)\n", ret); - return ret; -} - -static const struct drm_client_funcs drm_fbdev_generic_client_funcs = { - .owner = THIS_MODULE, - .unregister = drm_fbdev_generic_client_unregister, - .restore = drm_fbdev_generic_client_restore, - .hotplug = drm_fbdev_generic_client_hotplug, -}; - -/** - * drm_fbdev_generic_setup() - Setup generic fbdev emulation - * @dev: DRM device - * @preferred_bpp: Preferred bits per pixel for the device. - * - * This function sets up generic fbdev emulation for drivers that supports - * dumb buffers with a virtual address and that can be mmap'ed. - * drm_fbdev_generic_setup() shall be called after the DRM driver registered - * the new DRM device with drm_dev_register(). - * - * Restore, hotplug events and teardown are all taken care of. Drivers that do - * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. - * Simple drivers might use drm_mode_config_helper_suspend(). - * - * In order to provide fixed mmap-able memory ranges, generic fbdev emulation - * uses a shadow buffer in system memory. The implementation blits the shadow - * fbdev buffer onto the real buffer in regular intervals. - * - * This function is safe to call even when there are no connectors present. - * Setup will be retried on the next hotplug event. - * - * The fbdev is destroyed by drm_dev_unregister(). - */ -void drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) -{ - struct drm_fb_helper *fb_helper; - int ret; - - drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); - drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); - - fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); - if (!fb_helper) - return; - drm_fb_helper_prepare(dev, fb_helper, preferred_bpp, &drm_fbdev_generic_helper_funcs); - - ret = drm_client_init(dev, &fb_helper->client, "fbdev", &drm_fbdev_generic_client_funcs); - if (ret) { - drm_err(dev, "Failed to register client: %d\n", ret); - goto err_drm_client_init; - } - - drm_client_register(&fb_helper->client); - - return; - -err_drm_client_init: - drm_fb_helper_unprepare(fb_helper); - kfree(fb_helper); - return; -} -EXPORT_SYMBOL(drm_fbdev_generic_setup); diff --git a/drivers/gpu/drm/drm_fbdev_ttm.c b/drivers/gpu/drm/drm_fbdev_ttm.c new file mode 100644 index 000000000000..bb7898cd7dc6 --- /dev/null +++ b/drivers/gpu/drm/drm_fbdev_ttm.c @@ -0,0 +1,349 @@ +// SPDX-License-Identifier: MIT + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +/* @user: 1=userspace, 0=fbcon */ +static int drm_fbdev_ttm_fb_open(struct fb_info *info, int user) +{ + struct drm_fb_helper *fb_helper = info->par; + + /* No need to take a ref for fbcon because it unbinds on unregister */ + if (user && !try_module_get(fb_helper->dev->driver->fops->owner)) + return -ENODEV; + + return 0; +} + +static int drm_fbdev_ttm_fb_release(struct fb_info *info, int user) +{ + struct drm_fb_helper *fb_helper = info->par; + + if (user) + module_put(fb_helper->dev->driver->fops->owner); + + return 0; +} + +FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(drm_fbdev_ttm, + drm_fb_helper_damage_range, + drm_fb_helper_damage_area); + +static void drm_fbdev_ttm_fb_destroy(struct fb_info *info) +{ + struct drm_fb_helper *fb_helper = info->par; + void *shadow = info->screen_buffer; + + if (!fb_helper->dev) + return; + + fb_deferred_io_cleanup(info); + drm_fb_helper_fini(fb_helper); + vfree(shadow); + drm_client_framebuffer_delete(fb_helper->buffer); + + drm_client_release(&fb_helper->client); + drm_fb_helper_unprepare(fb_helper); + kfree(fb_helper); +} + +static const struct fb_ops drm_fbdev_ttm_fb_ops = { + .owner = THIS_MODULE, + .fb_open = drm_fbdev_ttm_fb_open, + .fb_release = drm_fbdev_ttm_fb_release, + FB_DEFAULT_DEFERRED_OPS(drm_fbdev_ttm), + DRM_FB_HELPER_DEFAULT_OPS, + .fb_destroy = drm_fbdev_ttm_fb_destroy, +}; + +/* + * This function uses the client API to create a framebuffer backed by a dumb buffer. + */ +static int drm_fbdev_ttm_helper_fb_probe(struct drm_fb_helper *fb_helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct drm_client_dev *client = &fb_helper->client; + struct drm_device *dev = fb_helper->dev; + struct drm_client_buffer *buffer; + struct fb_info *info; + size_t screen_size; + void *screen_buffer; + u32 format; + int ret; + + drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n", + sizes->surface_width, sizes->surface_height, + sizes->surface_bpp); + + format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); + buffer = drm_client_framebuffer_create(client, sizes->surface_width, + sizes->surface_height, format); + if (IS_ERR(buffer)) + return PTR_ERR(buffer); + + fb_helper->buffer = buffer; + fb_helper->fb = buffer->fb; + + screen_size = buffer->gem->size; + screen_buffer = vzalloc(screen_size); + if (!screen_buffer) { + ret = -ENOMEM; + goto err_drm_client_framebuffer_delete; + } + + info = drm_fb_helper_alloc_info(fb_helper); + if (IS_ERR(info)) { + ret = PTR_ERR(info); + goto err_vfree; + } + + drm_fb_helper_fill_info(info, fb_helper, sizes); + + info->fbops = &drm_fbdev_ttm_fb_ops; + + /* screen */ + info->flags |= FBINFO_VIRTFB | FBINFO_READS_FAST; + info->screen_buffer = screen_buffer; + info->fix.smem_len = screen_size; + + /* deferred I/O */ + fb_helper->fbdefio.delay = HZ / 20; + fb_helper->fbdefio.deferred_io = drm_fb_helper_deferred_io; + + info->fbdefio = &fb_helper->fbdefio; + ret = fb_deferred_io_init(info); + if (ret) + goto err_drm_fb_helper_release_info; + + return 0; + +err_drm_fb_helper_release_info: + drm_fb_helper_release_info(fb_helper); +err_vfree: + vfree(screen_buffer); +err_drm_client_framebuffer_delete: + fb_helper->fb = NULL; + fb_helper->buffer = NULL; + drm_client_framebuffer_delete(buffer); + return ret; +} + +static void drm_fbdev_ttm_damage_blit_real(struct drm_fb_helper *fb_helper, + struct drm_clip_rect *clip, + struct iosys_map *dst) +{ + struct drm_framebuffer *fb = fb_helper->fb; + size_t offset = clip->y1 * fb->pitches[0]; + size_t len = clip->x2 - clip->x1; + unsigned int y; + void *src; + + switch (drm_format_info_bpp(fb->format, 0)) { + case 1: + offset += clip->x1 / 8; + len = DIV_ROUND_UP(len + clip->x1 % 8, 8); + break; + case 2: + offset += clip->x1 / 4; + len = DIV_ROUND_UP(len + clip->x1 % 4, 4); + break; + case 4: + offset += clip->x1 / 2; + len = DIV_ROUND_UP(len + clip->x1 % 2, 2); + break; + default: + offset += clip->x1 * fb->format->cpp[0]; + len *= fb->format->cpp[0]; + break; + } + + src = fb_helper->info->screen_buffer + offset; + iosys_map_incr(dst, offset); /* go to first pixel within clip rect */ + + for (y = clip->y1; y < clip->y2; y++) { + iosys_map_memcpy_to(dst, 0, src, len); + iosys_map_incr(dst, fb->pitches[0]); + src += fb->pitches[0]; + } +} + +static int drm_fbdev_ttm_damage_blit(struct drm_fb_helper *fb_helper, + struct drm_clip_rect *clip) +{ + struct drm_client_buffer *buffer = fb_helper->buffer; + struct iosys_map map, dst; + int ret; + + /* + * We have to pin the client buffer to its current location while + * flushing the shadow buffer. In the general case, concurrent + * modesetting operations could try to move the buffer and would + * fail. The modeset has to be serialized by acquiring the reservation + * object of the underlying BO here. + * + * For fbdev emulation, we only have to protect against fbdev modeset + * operations. Nothing else will involve the client buffer's BO. So it + * is sufficient to acquire struct drm_fb_helper.lock here. + */ + mutex_lock(&fb_helper->lock); + + ret = drm_client_buffer_vmap_local(buffer, &map); + if (ret) + goto out; + + dst = map; + drm_fbdev_ttm_damage_blit_real(fb_helper, clip, &dst); + + drm_client_buffer_vunmap_local(buffer); + +out: + mutex_unlock(&fb_helper->lock); + + return ret; +} + +static int drm_fbdev_ttm_helper_fb_dirty(struct drm_fb_helper *helper, + struct drm_clip_rect *clip) +{ + struct drm_device *dev = helper->dev; + int ret; + + /* Call damage handlers only if necessary */ + if (!(clip->x1 < clip->x2 && clip->y1 < clip->y2)) + return 0; + + ret = drm_fbdev_ttm_damage_blit(helper, clip); + if (drm_WARN_ONCE(dev, ret, "Damage blitter failed: ret=%d\n", ret)) + return ret; + + if (helper->fb->funcs->dirty) { + ret = helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, clip, 1); + if (drm_WARN_ONCE(dev, ret, "Dirty helper failed: ret=%d\n", ret)) + return ret; + } + + return 0; +} + +static const struct drm_fb_helper_funcs drm_fbdev_ttm_helper_funcs = { + .fb_probe = drm_fbdev_ttm_helper_fb_probe, + .fb_dirty = drm_fbdev_ttm_helper_fb_dirty, +}; + +static void drm_fbdev_ttm_client_unregister(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + + if (fb_helper->info) { + drm_fb_helper_unregister_info(fb_helper); + } else { + drm_client_release(&fb_helper->client); + drm_fb_helper_unprepare(fb_helper); + kfree(fb_helper); + } +} + +static int drm_fbdev_ttm_client_restore(struct drm_client_dev *client) +{ + drm_fb_helper_lastclose(client->dev); + + return 0; +} + +static int drm_fbdev_ttm_client_hotplug(struct drm_client_dev *client) +{ + struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); + struct drm_device *dev = client->dev; + int ret; + + if (dev->fb_helper) + return drm_fb_helper_hotplug_event(dev->fb_helper); + + ret = drm_fb_helper_init(dev, fb_helper); + if (ret) + goto err_drm_err; + + if (!drm_drv_uses_atomic_modeset(dev)) + drm_helper_disable_unused_functions(dev); + + ret = drm_fb_helper_initial_config(fb_helper); + if (ret) + goto err_drm_fb_helper_fini; + + return 0; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(fb_helper); +err_drm_err: + drm_err(dev, "fbdev: Failed to setup emulation (ret=%d)\n", ret); + return ret; +} + +static const struct drm_client_funcs drm_fbdev_ttm_client_funcs = { + .owner = THIS_MODULE, + .unregister = drm_fbdev_ttm_client_unregister, + .restore = drm_fbdev_ttm_client_restore, + .hotplug = drm_fbdev_ttm_client_hotplug, +}; + +/** + * drm_fbdev_ttm_setup() - Setup fbdev emulation for TTM-based drivers + * @dev: DRM device + * @preferred_bpp: Preferred bits per pixel for the device. + * + * This function sets up fbdev emulation for TTM-based drivers that support + * dumb buffers with a virtual address and that can be mmap'ed. + * drm_fbdev_ttm_setup() shall be called after the DRM driver registered + * the new DRM device with drm_dev_register(). + * + * Restore, hotplug events and teardown are all taken care of. Drivers that do + * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. + * Simple drivers might use drm_mode_config_helper_suspend(). + * + * In order to provide fixed mmap-able memory ranges, fbdev emulation + * uses a shadow buffer in system memory. The implementation blits the shadow + * fbdev buffer onto the real buffer in regular intervals. + * + * This function is safe to call even when there are no connectors present. + * Setup will be retried on the next hotplug event. + * + * The fbdev is destroyed by drm_dev_unregister(). + */ +void drm_fbdev_ttm_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ + struct drm_fb_helper *fb_helper; + int ret; + + drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); + drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); + + fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); + if (!fb_helper) + return; + drm_fb_helper_prepare(dev, fb_helper, preferred_bpp, &drm_fbdev_ttm_helper_funcs); + + ret = drm_client_init(dev, &fb_helper->client, "fbdev", &drm_fbdev_ttm_client_funcs); + if (ret) { + drm_err(dev, "Failed to register client: %d\n", ret); + goto err_drm_client_init; + } + + drm_client_register(&fb_helper->client); + + return; + +err_drm_client_init: + drm_fb_helper_unprepare(fb_helper); + kfree(fb_helper); + return; +} +EXPORT_SYMBOL(drm_fbdev_ttm_setup); diff --git a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c index 57c21ec452b7..9f9b19ea0587 100644 --- a/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c +++ b/drivers/gpu/drm/hisilicon/hibmc/hibmc_drm_drv.c @@ -17,7 +17,7 @@ #include #include #include -#include +#include #include #include #include @@ -339,7 +339,7 @@ static int hibmc_pci_probe(struct pci_dev *pdev, goto err_unload; } - drm_fbdev_generic_setup(dev, 32); + drm_fbdev_ttm_setup(dev, 32); return 0; diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig index 8e59753e532d..9ed463a76ae2 100644 --- a/drivers/gpu/drm/loongson/Kconfig +++ b/drivers/gpu/drm/loongson/Kconfig @@ -6,6 +6,7 @@ config DRM_LOONGSON depends on LOONGARCH || MIPS || COMPILE_TEST select DRM_KMS_HELPER select DRM_TTM + select DRM_TTM_HELPER select I2C select I2C_ALGOBIT help diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c index d8ff60b46abe..adc7344d2f80 100644 --- a/drivers/gpu/drm/loongson/lsdc_drv.c +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -10,7 +10,7 @@ #include #include #include -#include +#include #include #include #include @@ -314,7 +314,7 @@ static int lsdc_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret) return ret; - drm_fbdev_generic_setup(ddev, 32); + drm_fbdev_ttm_setup(ddev, 32); return 0; } diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index a947e1d5f309..a58c31089613 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -32,7 +32,7 @@ #include #include -#include +#include #include #include #include @@ -846,9 +846,9 @@ static int nouveau_drm_probe(struct pci_dev *pdev, goto fail_drm_dev_init; if (nouveau_drm(drm_dev)->client.device.info.ram_size <= 32 * 1024 * 1024) - drm_fbdev_generic_setup(drm_dev, 8); + drm_fbdev_ttm_setup(drm_dev, 8); else - drm_fbdev_generic_setup(drm_dev, 32); + drm_fbdev_ttm_setup(drm_dev, 32); quirk_broken_nv_runpm(pdev); return 0; diff --git a/drivers/gpu/drm/qxl/qxl_drv.c b/drivers/gpu/drm/qxl/qxl_drv.c index beee5563031a..5eb3f5719fdf 100644 --- a/drivers/gpu/drm/qxl/qxl_drv.c +++ b/drivers/gpu/drm/qxl/qxl_drv.c @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include #include #include @@ -118,7 +118,7 @@ qxl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret) goto modeset_cleanup; - drm_fbdev_generic_setup(&qdev->ddev, 32); + drm_fbdev_ttm_setup(&qdev->ddev, 32); return 0; modeset_cleanup: diff --git a/drivers/gpu/drm/tiny/bochs.c b/drivers/gpu/drm/tiny/bochs.c index c23c9f0cf49c..2d7ad808cc0e 100644 --- a/drivers/gpu/drm/tiny/bochs.c +++ b/drivers/gpu/drm/tiny/bochs.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -670,7 +670,7 @@ static int bochs_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent if (ret) goto err_hw_fini; - drm_fbdev_generic_setup(dev, 32); + drm_fbdev_ttm_setup(dev, 32); return ret; err_hw_fini: diff --git a/drivers/gpu/drm/vboxvideo/vbox_drv.c b/drivers/gpu/drm/vboxvideo/vbox_drv.c index cd9e66a06596..ef36834c8673 100644 --- a/drivers/gpu/drm/vboxvideo/vbox_drv.c +++ b/drivers/gpu/drm/vboxvideo/vbox_drv.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include #include #include @@ -80,7 +80,7 @@ static int vbox_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent) if (ret) goto err_irq_fini; - drm_fbdev_generic_setup(&vbox->ddev, 32); + drm_fbdev_ttm_setup(&vbox->ddev, 32); return 0; diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c index bdad93864b98..4bf6da2b15fe 100644 --- a/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_drv.c @@ -37,7 +37,7 @@ #include #include -#include +#include #include #include #include @@ -1685,7 +1685,7 @@ static int vmw_probe(struct pci_dev *pdev, const struct pci_device_id *ent) vmw_fifo_resource_inc(vmw); vmw_svga_enable(vmw); - drm_fbdev_generic_setup(&vmw->drm, 0); + drm_fbdev_ttm_setup(&vmw->drm, 0); vmw_debugfs_gem_init(vmw); vmw_debugfs_resource_managers_init(vmw); diff --git a/include/drm/drm_fbdev_generic.h b/include/drm/drm_fbdev_generic.h deleted file mode 100644 index 75799342098d..000000000000 --- a/include/drm/drm_fbdev_generic.h +++ /dev/null @@ -1,15 +0,0 @@ -/* SPDX-License-Identifier: MIT */ - -#ifndef DRM_FBDEV_GENERIC_H -#define DRM_FBDEV_GENERIC_H - -struct drm_device; - -#ifdef CONFIG_DRM_FBDEV_EMULATION -void drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp); -#else -static inline void drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) -{ } -#endif - -#endif diff --git a/include/drm/drm_fbdev_ttm.h b/include/drm/drm_fbdev_ttm.h new file mode 100644 index 000000000000..9e6c3bdf3537 --- /dev/null +++ b/include/drm/drm_fbdev_ttm.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_FBDEV_TTM_H +#define DRM_FBDEV_TTM_H + +struct drm_device; + +#ifdef CONFIG_DRM_FBDEV_EMULATION +void drm_fbdev_ttm_setup(struct drm_device *dev, unsigned int preferred_bpp); +#else +static inline void drm_fbdev_ttm_setup(struct drm_device *dev, unsigned int preferred_bpp) +{ } +#endif + +#endif -- cgit v1.2.3-58-ga151 From 18bc074c226bfecd205bf031678f5e35ee55c3da Mon Sep 17 00:00:00 2001 From: Thomas Zimmermann Date: Fri, 19 Apr 2024 10:29:36 +0200 Subject: drm/fbdev: Clean up fbdev documentation Rewrite some docs that are not up-to-date any longer. Remove the TODO item for fbdev-generic conversion, as the helper has been replaced. Make documentation for DMA, SHMEM and TTM emulation available. Signed-off-by: Thomas Zimmermann Cc: Jonathan Corbet Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20240419083331.7761-44-tzimmermann@suse.de --- Documentation/gpu/drm-kms-helpers.rst | 12 +++++++++--- Documentation/gpu/todo.rst | 13 ------------- drivers/gpu/drm/drm_drv.c | 2 +- drivers/gpu/drm/drm_fb_helper.c | 11 ++--------- include/drm/drm_mode_config.h | 4 ++-- 5 files changed, 14 insertions(+), 28 deletions(-) (limited to 'include/drm') diff --git a/Documentation/gpu/drm-kms-helpers.rst b/Documentation/gpu/drm-kms-helpers.rst index e46ab9b670ac..8435e8621cc0 100644 --- a/Documentation/gpu/drm-kms-helpers.rst +++ b/Documentation/gpu/drm-kms-helpers.rst @@ -110,15 +110,21 @@ fbdev Helper Functions Reference .. kernel-doc:: drivers/gpu/drm/drm_fb_helper.c :doc: fbdev helpers -.. kernel-doc:: include/drm/drm_fb_helper.h - :internal: +.. kernel-doc:: drivers/gpu/drm/drm_fbdev_dma.c + :export: -.. kernel-doc:: drivers/gpu/drm/drm_fb_helper.c +.. kernel-doc:: drivers/gpu/drm/drm_fbdev_shmem.c :export: .. kernel-doc:: drivers/gpu/drm/drm_fbdev_ttm.c :export: +.. kernel-doc:: include/drm/drm_fb_helper.h + :internal: + +.. kernel-doc:: drivers/gpu/drm/drm_fb_helper.c + :export: + format Helper Functions Reference ================================= diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index fb9ad120b141..e2a0585915b3 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -243,19 +243,6 @@ Contact: Maintainer of the driver you plan to convert Level: Intermediate -Convert drivers to use drm_fbdev_generic_setup() ------------------------------------------------- - -Most drivers can use drm_fbdev_generic_setup(). Driver have to implement -atomic modesetting and GEM vmap support. Historically, generic fbdev emulation -expected the framebuffer in system memory or system-like memory. By employing -struct iosys_map, drivers with frambuffers in I/O memory can be supported -as well. - -Contact: Maintainer of the driver you plan to convert - -Level: Intermediate - Reimplement functions in drm_fbdev_fb_ops without fbdev ------------------------------------------------------- diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index 535b624d4c9d..b8186cf16230 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -346,7 +346,7 @@ void drm_minor_release(struct drm_minor *minor) * if (ret) * return ret; * - * drm_fbdev_generic_setup(drm, 32); + * drm_fbdev_{...}_setup(drm, 32); * * return 0; * } diff --git a/drivers/gpu/drm/drm_fb_helper.c b/drivers/gpu/drm/drm_fb_helper.c index d612133e2cf7..e2e19f49342e 100644 --- a/drivers/gpu/drm/drm_fb_helper.c +++ b/drivers/gpu/drm/drm_fb_helper.c @@ -85,12 +85,8 @@ static DEFINE_MUTEX(kernel_fb_helper_lock); * The fb helper functions are useful to provide an fbdev on top of a drm kernel * mode setting driver. They can be used mostly independently from the crtc * helper functions used by many drivers to implement the kernel mode setting - * interfaces. - * - * Drivers that support a dumb buffer with a virtual address and mmap support, - * should try out the generic fbdev emulation using drm_fbdev_generic_setup(). - * It will automatically set up deferred I/O if the driver requires a shadow - * buffer. + * interfaces. Drivers that use one of the shared memory managers, TTM, SHMEM, + * DMA, should instead use the corresponding fbdev emulation. * * Existing fbdev implementations should restore the fbdev console by using * drm_fb_helper_lastclose() as their &drm_driver.lastclose callback. @@ -126,9 +122,6 @@ static DEFINE_MUTEX(kernel_fb_helper_lock); * atomic context. If drm_fb_helper_deferred_io() is used as the deferred_io * callback it will also schedule dirty_work with the damage collected from the * mmap page writes. - * - * Deferred I/O is not compatible with SHMEM. Such drivers should request an - * fbdev shadow buffer and call drm_fbdev_generic_setup() instead. */ static void drm_fb_helper_restore_lut_atomic(struct drm_crtc *crtc) diff --git a/include/drm/drm_mode_config.h b/include/drm/drm_mode_config.h index 8de3c9a5f61b..ab0f167474b1 100644 --- a/include/drm/drm_mode_config.h +++ b/include/drm/drm_mode_config.h @@ -106,8 +106,8 @@ struct drm_mode_config_funcs { * Drivers implementing fbdev emulation use drm_kms_helper_hotplug_event() * to call this hook to inform the fbdev helper of output changes. * - * This hook is deprecated, drivers should instead use - * drm_fbdev_generic_setup() which takes care of any necessary + * This hook is deprecated, drivers should instead implement fbdev + * support with struct drm_client, which takes care of any necessary * hotplug event forwarding already without further involvement by * the driver. */ -- cgit v1.2.3-58-ga151 From 7fb8af6798e8d013017e4607505f58d9942fd671 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 29 Apr 2024 19:43:36 +0300 Subject: drm: deprecate driver date The driver date serves no useful purpose, because it's hardly ever updated. The information is misleading at best. As described in Documentation/gpu/drm-internals.rst: The driver date, formatted as YYYYMMDD, is meant to identify the date of the latest modification to the driver. However, as most drivers fail to update it, its value is mostly useless. The DRM core prints it to the kernel log at initialization time and passes it to userspace through the DRM_IOCTL_VERSION ioctl. Stop printing the driver date at init, and start returning the empty string "" as driver date through the DRM_IOCTL_VERSION ioctl. The driver date initialization in drivers and the struct drm_driver date member can be removed in follow-up. Reviewed-by: Hamza Mahfooz Acked-by: Simon Ser Reviewed-by: Javier Martinez Canillas Link: https://patchwork.freedesktop.org/patch/msgid/20240429164336.1406480-1-jani.nikula@intel.com Signed-off-by: Jani Nikula --- Documentation/gpu/drm-internals.rst | 10 ++-------- drivers/gpu/drm/drm_drv.c | 4 ++-- drivers/gpu/drm/drm_ioctl.c | 5 +++-- include/drm/drm_drv.h | 2 +- 4 files changed, 8 insertions(+), 13 deletions(-) (limited to 'include/drm') diff --git a/Documentation/gpu/drm-internals.rst b/Documentation/gpu/drm-internals.rst index 335de7fcddee..11d9a5730fb2 100644 --- a/Documentation/gpu/drm-internals.rst +++ b/Documentation/gpu/drm-internals.rst @@ -57,8 +57,8 @@ is larger than the driver minor, the DRM_IOCTL_SET_VERSION call will return an error. Otherwise the driver's set_version() method will be called with the requested version. -Name, Description and Date -~~~~~~~~~~~~~~~~~~~~~~~~~~ +Name and Description +~~~~~~~~~~~~~~~~~~~~ char \*name; char \*desc; char \*date; The driver name is printed to the kernel log at initialization time, @@ -69,12 +69,6 @@ The driver description is a purely informative string passed to userspace through the DRM_IOCTL_VERSION ioctl and otherwise unused by the kernel. -The driver date, formatted as YYYYMMDD, is meant to identify the date of -the latest modification to the driver. However, as most drivers fail to -update it, its value is mostly useless. The DRM core prints it to the -kernel log at initialization time and passes it to userspace through the -DRM_IOCTL_VERSION ioctl. - Module Initialization --------------------- diff --git a/drivers/gpu/drm/drm_drv.c b/drivers/gpu/drm/drm_drv.c index b8186cf16230..93543071a500 100644 --- a/drivers/gpu/drm/drm_drv.c +++ b/drivers/gpu/drm/drm_drv.c @@ -947,9 +947,9 @@ int drm_dev_register(struct drm_device *dev, unsigned long flags) } drm_panic_register(dev); - DRM_INFO("Initialized %s %d.%d.%d %s for %s on minor %d\n", + DRM_INFO("Initialized %s %d.%d.%d for %s on minor %d\n", driver->name, driver->major, driver->minor, - driver->patchlevel, driver->date, + driver->patchlevel, dev->dev ? dev_name(dev->dev) : "virtual device", dev->primary ? dev->primary->index : dev->accel->index); diff --git a/drivers/gpu/drm/drm_ioctl.c b/drivers/gpu/drm/drm_ioctl.c index e368fc084c77..89feb7306e47 100644 --- a/drivers/gpu/drm/drm_ioctl.c +++ b/drivers/gpu/drm/drm_ioctl.c @@ -529,9 +529,10 @@ int drm_version(struct drm_device *dev, void *data, version->version_patchlevel = dev->driver->patchlevel; err = drm_copy_field(version->name, &version->name_len, dev->driver->name); + + /* Driver date is deprecated. Return the empty string. */ if (!err) - err = drm_copy_field(version->date, &version->date_len, - dev->driver->date); + err = drm_copy_field(version->date, &version->date_len, ""); if (!err) err = drm_copy_field(version->desc, &version->desc_len, dev->driver->desc); diff --git a/include/drm/drm_drv.h b/include/drm/drm_drv.h index 8878260d7529..cd37936c3926 100644 --- a/include/drm/drm_drv.h +++ b/include/drm/drm_drv.h @@ -411,7 +411,7 @@ struct drm_driver { char *name; /** @desc: driver description */ char *desc; - /** @date: driver date */ + /** @date: driver date, unused, to be removed */ char *date; /** -- cgit v1.2.3-58-ga151 From 3dbfbd101a5844f851da9ae6e90f59753c10ff42 Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Mon, 13 May 2024 23:27:23 +0300 Subject: drm/edid: remove drm_do_get_edid() All users of drm_do_get_edid() have been converted to drm_edid_read_custom(). Remove the unused function to prevent new users from creeping in. Reviewed-by: Thomas Zimmermann Link: https://patchwork.freedesktop.org/patch/msgid/20240513202723.261440-1-jani.nikula@intel.com Signed-off-by: Jani Nikula --- drivers/gpu/drm/drm_edid.c | 28 ---------------------------- include/drm/drm_edid.h | 4 ---- 2 files changed, 32 deletions(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 4f54c91b31b2..0f7c4c5b14b9 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -2464,34 +2464,6 @@ fail: return NULL; } -/** - * drm_do_get_edid - get EDID data using a custom EDID block read function - * @connector: connector we're probing - * @read_block: EDID block read function - * @context: private data passed to the block read function - * - * When the I2C adapter connected to the DDC bus is hidden behind a device that - * exposes a different interface to read EDID blocks this function can be used - * to get EDID data using a custom block read function. - * - * As in the general case the DDC bus is accessible by the kernel at the I2C - * level, drivers must make all reasonable efforts to expose it as an I2C - * adapter and use drm_get_edid() instead of abusing this function. - * - * The EDID may be overridden using debugfs override_edid or firmware EDID - * (drm_edid_load_firmware() and drm.edid_firmware parameter), in this priority - * order. Having either of them bypasses actual EDID reads. - * - * Return: Pointer to valid EDID or NULL if we couldn't find any. - */ -struct edid *drm_do_get_edid(struct drm_connector *connector, - read_block_fn read_block, - void *context) -{ - return _drm_do_get_edid(connector, read_block, context, NULL); -} -EXPORT_SYMBOL_GPL(drm_do_get_edid); - /** * drm_edid_raw - Get a pointer to the raw EDID data. * @drm_edid: drm_edid container diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h index b085525e53e2..6bdfa254a1c1 100644 --- a/include/drm/drm_edid.h +++ b/include/drm/drm_edid.h @@ -423,10 +423,6 @@ static inline void drm_edid_decode_panel_id(u32 panel_id, char vend[4], u16 *pro } bool drm_probe_ddc(struct i2c_adapter *adapter); -struct edid *drm_do_get_edid(struct drm_connector *connector, - int (*get_edid_block)(void *data, u8 *buf, unsigned int block, - size_t len), - void *data); struct edid *drm_get_edid(struct drm_connector *connector, struct i2c_adapter *adapter); struct edid *drm_get_edid_switcheroo(struct drm_connector *connector, -- cgit v1.2.3-58-ga151 From 0b03829fdece47beba9ecb7dbcbde4585ee3663e Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 14 May 2024 10:20:51 -0700 Subject: drm/mipi-dsi: Fix theoretical int overflow in mipi_dsi_dcs_write_seq() The mipi_dsi_dcs_write_seq() macro makes a call to mipi_dsi_dcs_write_buffer() which returns a type ssize_t. The macro then stores it in an int and checks to see if it's negative. This could theoretically be a problem if "ssize_t" is larger than "int". To see the issue, imagine that "ssize_t" is 32-bits and "int" is 16-bits, you could see a problem if there was some code out there that looked like: mipi_dsi_dcs_write_seq(dsi, cmd, <32767 bytes as arguments>); ...since we'd get back that 32768 bytes were transferred and 32768 stored in a 16-bit int would look negative. Though there are no callsites where we'd actually hit this (even if "int" was only 16-bit), it's cleaner to make the types match so let's fix it. Fixes: 2a9e9daf7523 ("drm/mipi-dsi: Introduce mipi_dsi_dcs_write_seq macro") Reviewed-by: Neil Armstrong Reviewed-by: Linus Walleij Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20240514102056.v5.1.I30fa4c8348ea316c886ef8a522a52fed617f930d@changeid Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240514102056.v5.1.I30fa4c8348ea316c886ef8a522a52fed617f930d@changeid --- include/drm/drm_mipi_dsi.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'include/drm') diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 82b1cc434ea3..70ce0b8cbc68 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -333,18 +333,18 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, * @cmd: Command * @seq: buffer containing data to be transmitted */ -#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) \ - do { \ - static const u8 d[] = { cmd, seq }; \ - struct device *dev = &dsi->dev; \ - int ret; \ - ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ - if (ret < 0) { \ - dev_err_ratelimited( \ - dev, "sending command %#02x failed: %d\n", \ - cmd, ret); \ - return ret; \ - } \ +#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) \ + do { \ + static const u8 d[] = { cmd, seq }; \ + struct device *dev = &dsi->dev; \ + ssize_t ret; \ + ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) { \ + dev_err_ratelimited( \ + dev, "sending command %#02x failed: %zd\n", \ + cmd, ret); \ + return ret; \ + } \ } while (0) /** -- cgit v1.2.3-58-ga151 From 24acbcce5cc673886c2f4f9b3f6f89a9c6a53b7e Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 14 May 2024 10:20:52 -0700 Subject: drm/mipi-dsi: Fix theoretical int overflow in mipi_dsi_generic_write_seq() The mipi_dsi_generic_write_seq() macro makes a call to mipi_dsi_generic_write() which returns a type ssize_t. The macro then stores it in an int and checks to see if it's negative. This could theoretically be a problem if "ssize_t" is larger than "int". To see the issue, imagine that "ssize_t" is 32-bits and "int" is 16-bits, you could see a problem if there was some code out there that looked like: mipi_dsi_generic_write_seq(dsi, <32768 bytes as arguments>); ...since we'd get back that 32768 bytes were transferred and 32768 stored in a 16-bit int would look negative. Though there are no callsites where we'd actually hit this (even if "int" was only 16-bit), it's cleaner to make the types match so let's fix it. Fixes: a9015ce59320 ("drm/mipi-dsi: Add a mipi_dsi_dcs_write_seq() macro") Reviewed-by: Neil Armstrong Reviewed-by: Linus Walleij Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20240514102056.v5.2.Iadb65b8add19ed3ae3ed6425011beb97e380a912@changeid Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240514102056.v5.2.Iadb65b8add19ed3ae3ed6425011beb97e380a912@changeid --- include/drm/drm_mipi_dsi.h | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) (limited to 'include/drm') diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 70ce0b8cbc68..e0f56564bf97 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -314,17 +314,17 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, * @dsi: DSI peripheral device * @seq: buffer containing the payload */ -#define mipi_dsi_generic_write_seq(dsi, seq...) \ - do { \ - static const u8 d[] = { seq }; \ - struct device *dev = &dsi->dev; \ - int ret; \ - ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ - if (ret < 0) { \ - dev_err_ratelimited(dev, "transmit data failed: %d\n", \ - ret); \ - return ret; \ - } \ +#define mipi_dsi_generic_write_seq(dsi, seq...) \ + do { \ + static const u8 d[] = { seq }; \ + struct device *dev = &dsi->dev; \ + ssize_t ret; \ + ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) { \ + dev_err_ratelimited(dev, "transmit data failed: %zd\n", \ + ret); \ + return ret; \ + } \ } while (0) /** -- cgit v1.2.3-58-ga151 From 7d3f6acaf87c7db6dcd868694a2f65e7040478dc Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 14 May 2024 10:20:53 -0700 Subject: drm/mipi-dsi: mipi_dsi_*_write functions don't need to ratelimit prints We really don't expect these errors to be printed over and over again. When a driver hits the error it should bail out. Just use a normal error print. This gives a nice space savings for users of these functions: $ scripts/bloat-o-meter \ .../before/panel-novatek-nt36672e.ko \ .../after/panel-novatek-nt36672e.ko add/remove: 0/1 grow/shrink: 0/1 up/down: 0/-16760 (-16760) Function old new delta nt36672e_1080x2408_60hz_init 17080 10640 -6440 nt36672e_1080x2408_60hz_init._rs 10320 - -10320 Total: Before=31815, After=15055, chg -52.68% Reviewed-by: Neil Armstrong Reviewed-by: Linus Walleij Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20240514102056.v5.3.I9982cd5d8014de7a4513f5619f66f88da49ce4ec@changeid Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240514102056.v5.3.I9982cd5d8014de7a4513f5619f66f88da49ce4ec@changeid --- include/drm/drm_mipi_dsi.h | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) (limited to 'include/drm') diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index e0f56564bf97..67967be48dbd 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -314,17 +314,16 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, * @dsi: DSI peripheral device * @seq: buffer containing the payload */ -#define mipi_dsi_generic_write_seq(dsi, seq...) \ - do { \ - static const u8 d[] = { seq }; \ - struct device *dev = &dsi->dev; \ - ssize_t ret; \ - ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ - if (ret < 0) { \ - dev_err_ratelimited(dev, "transmit data failed: %zd\n", \ - ret); \ - return ret; \ - } \ +#define mipi_dsi_generic_write_seq(dsi, seq...) \ + do { \ + static const u8 d[] = { seq }; \ + struct device *dev = &dsi->dev; \ + ssize_t ret; \ + ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) { \ + dev_err(dev, "transmit data failed: %zd\n", ret); \ + return ret; \ + } \ } while (0) /** @@ -340,8 +339,7 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, ssize_t ret; \ ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ if (ret < 0) { \ - dev_err_ratelimited( \ - dev, "sending command %#02x failed: %zd\n", \ + dev_err(dev, "sending command %#02x failed: %zd\n", \ cmd, ret); \ return ret; \ } \ -- cgit v1.2.3-58-ga151 From 3b724909a380fddb44dfa0072fc459c698a52658 Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 14 May 2024 10:20:54 -0700 Subject: drm/mipi-dsi: Reduce driver bloat of mipi_dsi_*_write_seq() Through a cooperative effort between Hsin-Yi Wang and Dmitry Baryshkov, we have realized the dev_err() in the mipi_dsi_*_write_seq() macros was causing quite a bit of bloat to the kernel. Let's hoist this call into drm_mipi_dsi.c by adding a "chatty" version of the functions that includes the print. While doing this, add a bit more comments to these macros making it clear that they print errors and also that they return out of _the caller's_ function. Without any changes to clients this gives a nice savings. Specifically the macro was inlined and thus the error report call was inlined into every call to mipi_dsi_dcs_write_seq() and mipi_dsi_generic_write_seq(). By using a call to a "chatty" function, the usage is reduced to one call in the chatty function and a function call at the invoking site. Building with my build system shows one example: $ scripts/bloat-o-meter \ .../before/panel-novatek-nt36672e.ko \ .../after/panel-novatek-nt36672e.ko add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-4404 (-4404) Function old new delta nt36672e_1080x2408_60hz_init 10640 6236 -4404 Total: Before=15055, After=10651, chg -29.25% Note that given the change in location of the print it's harder to include the "cmd" in the printout for mipi_dsi_dcs_write_seq() since, theoretically, someone could call the new chatty function with a zero-size array and it would be illegal to dereference data[0]. There's a printk format to print the whole buffer and this is probably more useful for debugging anyway. Given that we're doing this for mipi_dsi_dcs_write_seq(), let's also print the buffer for mipi_dsi_generic_write_seq() in the error case. It should be noted that the current consensus of DRM folks is that the mipi_dsi_*_write_seq() should be deprecated due to the non-intuitive return behavior. A future patch will formally mark them as deprecated and provide an alternative. Reviewed-by: Dmitry Baryshkov Reviewed-by: Neil Armstrong Reviewed-by: Linus Walleij Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20240514102056.v5.4.Id15fae80582bc74a0d4f1338987fa375738f45b9@changeid Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240514102056.v5.4.Id15fae80582bc74a0d4f1338987fa375738f45b9@changeid --- drivers/gpu/drm/drm_mipi_dsi.c | 56 ++++++++++++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dsi.h | 47 +++++++++++++++++++---------------- 2 files changed, 82 insertions(+), 21 deletions(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 795001bb7ff1..4d2685d5a6e0 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -764,6 +764,34 @@ ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload, } EXPORT_SYMBOL(mipi_dsi_generic_write); +/** + * mipi_dsi_generic_write_chatty() - mipi_dsi_generic_write() w/ an error log + * @dsi: DSI peripheral device + * @payload: buffer containing the payload + * @size: size of payload buffer + * + * Like mipi_dsi_generic_write() but includes a dev_err() + * call for you and returns 0 upon success, not the number of bytes sent. + * + * Return: 0 on success or a negative error code on failure. + */ +int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi, + const void *payload, size_t size) +{ + struct device *dev = &dsi->dev; + ssize_t ret; + + ret = mipi_dsi_generic_write(dsi, payload, size); + if (ret < 0) { + dev_err(dev, "sending generic data %*ph failed: %zd\n", + (int)size, payload, ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(mipi_dsi_generic_write_chatty); + /** * mipi_dsi_generic_read() - receive data using a generic read packet * @dsi: DSI peripheral device @@ -852,6 +880,34 @@ ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi, } EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer); +/** + * mipi_dsi_dcs_write_buffer_chatty - mipi_dsi_dcs_write_buffer() w/ an error log + * @dsi: DSI peripheral device + * @data: buffer containing data to be transmitted + * @len: size of transmission buffer + * + * Like mipi_dsi_dcs_write_buffer() but includes a dev_err() + * call for you and returns 0 upon success, not the number of bytes sent. + * + * Return: 0 on success or a negative error code on failure. + */ +int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi, + const void *data, size_t len) +{ + struct device *dev = &dsi->dev; + ssize_t ret; + + ret = mipi_dsi_dcs_write_buffer(dsi, data, len); + if (ret < 0) { + dev_err(dev, "sending dcs data %*ph failed: %zd\n", + (int)len, data, ret); + return ret; + } + + return 0; +} +EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_chatty); + /** * mipi_dsi_dcs_write() - send DCS write command * @dsi: DSI peripheral device diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 67967be48dbd..6d68d9927f46 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -256,6 +256,8 @@ int mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi, ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload, size_t size); +int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi, + const void *payload, size_t size); ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params, size_t num_params, void *data, size_t size); @@ -279,6 +281,8 @@ enum mipi_dsi_dcs_tear_mode { ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi, const void *data, size_t len); +int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi, + const void *data, size_t len); ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd, const void *data, size_t len); ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data, @@ -311,38 +315,39 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, /** * mipi_dsi_generic_write_seq - transmit data using a generic write packet + * + * This macro will print errors for you and will RETURN FROM THE CALLING + * FUNCTION (yes this is non-intuitive) upon error. + * * @dsi: DSI peripheral device * @seq: buffer containing the payload */ -#define mipi_dsi_generic_write_seq(dsi, seq...) \ - do { \ - static const u8 d[] = { seq }; \ - struct device *dev = &dsi->dev; \ - ssize_t ret; \ - ret = mipi_dsi_generic_write(dsi, d, ARRAY_SIZE(d)); \ - if (ret < 0) { \ - dev_err(dev, "transmit data failed: %zd\n", ret); \ - return ret; \ - } \ +#define mipi_dsi_generic_write_seq(dsi, seq...) \ + do { \ + static const u8 d[] = { seq }; \ + int ret; \ + ret = mipi_dsi_generic_write_chatty(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ } while (0) /** * mipi_dsi_dcs_write_seq - transmit a DCS command with payload + * + * This macro will print errors for you and will RETURN FROM THE CALLING + * FUNCTION (yes this is non-intuitive) upon error. + * * @dsi: DSI peripheral device * @cmd: Command * @seq: buffer containing data to be transmitted */ -#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) \ - do { \ - static const u8 d[] = { cmd, seq }; \ - struct device *dev = &dsi->dev; \ - ssize_t ret; \ - ret = mipi_dsi_dcs_write_buffer(dsi, d, ARRAY_SIZE(d)); \ - if (ret < 0) { \ - dev_err(dev, "sending command %#02x failed: %zd\n", \ - cmd, ret); \ - return ret; \ - } \ +#define mipi_dsi_dcs_write_seq(dsi, cmd, seq...) \ + do { \ + static const u8 d[] = { cmd, seq }; \ + int ret; \ + ret = mipi_dsi_dcs_write_buffer_chatty(dsi, d, ARRAY_SIZE(d)); \ + if (ret < 0) \ + return ret; \ } while (0) /** -- cgit v1.2.3-58-ga151 From 966e397e4f6032b73438f8d775756541513e7daf Mon Sep 17 00:00:00 2001 From: Douglas Anderson Date: Tue, 14 May 2024 10:20:55 -0700 Subject: drm/mipi-dsi: Introduce mipi_dsi_*_write_seq_multi() The current mipi_dsi_*_write_seq() macros are non-intutitive because they contain a hidden "return" statement that will return out of the _caller_ of the macro. Let's mark them as deprecated and instead introduce some new macros that are more intuitive. These new macros are less optimal when an error occurs but should behave more optimally when there is no error. Specifically these new macros cause smaller code to get generated and the code size savings (less to fetch from RAM, less cache space used, less RAM used) are important. Since the error case isn't something we need to optimize for and these new macros are easier to understand and more flexible, they should be used. After converting to use these new functions, one example shows some nice savings while also being easier to understand. $ scripts/bloat-o-meter \ ...after/panel-novatek-nt36672e.ko \ ...ctx/panel-novatek-nt36672e.ko add/remove: 0/0 grow/shrink: 0/1 up/down: 0/-988 (-988) Function old new delta nt36672e_1080x2408_60hz_init 6236 5248 -988 Total: Before=10651, After=9663, chg -9.28% Reviewed-by: Neil Armstrong Reviewed-by: Linus Walleij Signed-off-by: Douglas Anderson Link: https://lore.kernel.org/r/20240514102056.v5.5.Ie94246c30fe95101e0e26dd5f96e976dbeb8f242@changeid Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240514102056.v5.5.Ie94246c30fe95101e0e26dd5f96e976dbeb8f242@changeid --- Documentation/gpu/todo.rst | 18 ++++++++++++ drivers/gpu/drm/drm_mipi_dsi.c | 56 ++++++++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dsi.h | 62 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 136 insertions(+) (limited to 'include/drm') diff --git a/Documentation/gpu/todo.rst b/Documentation/gpu/todo.rst index e2a0585915b3..2734b8a34541 100644 --- a/Documentation/gpu/todo.rst +++ b/Documentation/gpu/todo.rst @@ -494,6 +494,24 @@ Contact: Douglas Anderson Level: Starter/Intermediate +Transition away from using mipi_dsi_*_write_seq() +------------------------------------------------- + +The macros mipi_dsi_generic_write_seq() and mipi_dsi_dcs_write_seq() are +non-intuitive because, if there are errors, they return out of the *caller's* +function. We should move all callers to use mipi_dsi_generic_write_seq_multi() +and mipi_dsi_dcs_write_seq_multi() macros instead. + +Once all callers are transitioned, the macros and the functions that they call, +mipi_dsi_generic_write_chatty() and mipi_dsi_dcs_write_buffer_chatty(), can +probably be removed. Alternatively, if people feel like the _multi() variants +are overkill for some use cases, we could keep the mipi_dsi_*_write_seq() +variants but change them not to return out of the caller. + +Contact: Douglas Anderson + +Level: Starter + Core refactorings ================= diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 4d2685d5a6e0..26c7383406c1 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -792,6 +792,34 @@ int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi, } EXPORT_SYMBOL(mipi_dsi_generic_write_chatty); +/** + * mipi_dsi_generic_write_multi() - mipi_dsi_generic_write_chatty() w/ accum_err + * @ctx: Context for multiple DSI transactions + * @payload: buffer containing the payload + * @size: size of payload buffer + * + * Like mipi_dsi_generic_write_chatty() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx, + const void *payload, size_t size) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_generic_write(dsi, payload, size); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending generic data %*ph failed: %d\n", + (int)size, payload, ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_generic_write_multi); + /** * mipi_dsi_generic_read() - receive data using a generic read packet * @dsi: DSI peripheral device @@ -908,6 +936,34 @@ int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi, } EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_chatty); +/** + * mipi_dsi_dcs_write_buffer_multi - mipi_dsi_dcs_write_buffer_chatty() w/ accum_err + * @ctx: Context for multiple DSI transactions + * @data: buffer containing data to be transmitted + * @len: size of transmission buffer + * + * Like mipi_dsi_dcs_write_buffer_chatty() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx, + const void *data, size_t len) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_write_buffer(dsi, data, len); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending dcs data %*ph failed: %d\n", + (int)len, data, ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_write_buffer_multi); + /** * mipi_dsi_dcs_write() - send DCS write command * @dsi: DSI peripheral device diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 6d68d9927f46..5e9cad541bd6 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -197,6 +197,27 @@ struct mipi_dsi_device { struct drm_dsc_config *dsc; }; +/** + * struct mipi_dsi_multi_context - Context to call multiple MIPI DSI funcs in a row + */ +struct mipi_dsi_multi_context { + /** + * @dsi: Pointer to the MIPI DSI device + */ + struct mipi_dsi_device *dsi; + + /** + * @accum_err: Storage for the accumulated error over the multiple calls + * + * Init to 0. If a function encounters an error then the error code + * will be stored here. If you call a function and this points to a + * non-zero value then the function will be a noop. This allows calling + * a function many times in a row and just checking the error at the + * end to see if any of them failed. + */ + int accum_err; +}; + #define MIPI_DSI_MODULE_PREFIX "mipi-dsi:" #define to_mipi_dsi_device(__dev) container_of_const(__dev, struct mipi_dsi_device, dev) @@ -258,6 +279,8 @@ ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload, size_t size); int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi, const void *payload, size_t size); +void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx, + const void *payload, size_t size); ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params, size_t num_params, void *data, size_t size); @@ -283,6 +306,8 @@ ssize_t mipi_dsi_dcs_write_buffer(struct mipi_dsi_device *dsi, const void *data, size_t len); int mipi_dsi_dcs_write_buffer_chatty(struct mipi_dsi_device *dsi, const void *data, size_t len); +void mipi_dsi_dcs_write_buffer_multi(struct mipi_dsi_multi_context *ctx, + const void *data, size_t len); ssize_t mipi_dsi_dcs_write(struct mipi_dsi_device *dsi, u8 cmd, const void *data, size_t len); ssize_t mipi_dsi_dcs_read(struct mipi_dsi_device *dsi, u8 cmd, void *data, @@ -319,6 +344,9 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, * This macro will print errors for you and will RETURN FROM THE CALLING * FUNCTION (yes this is non-intuitive) upon error. * + * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED. + * Please replace calls of it with mipi_dsi_generic_write_seq_multi(). + * * @dsi: DSI peripheral device * @seq: buffer containing the payload */ @@ -331,12 +359,30 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, return ret; \ } while (0) +/** + * mipi_dsi_generic_write_seq_multi - transmit data using a generic write packet + * + * This macro will print errors for you and error handling is optimized for + * callers that call this multiple times in a row. + * + * @ctx: Context for multiple DSI transactions + * @seq: buffer containing the payload + */ +#define mipi_dsi_generic_write_seq_multi(ctx, seq...) \ + do { \ + static const u8 d[] = { seq }; \ + mipi_dsi_generic_write_multi(ctx, d, ARRAY_SIZE(d)); \ + } while (0) + /** * mipi_dsi_dcs_write_seq - transmit a DCS command with payload * * This macro will print errors for you and will RETURN FROM THE CALLING * FUNCTION (yes this is non-intuitive) upon error. * + * Because of the non-intuitive return behavior, THIS MACRO IS DEPRECATED. + * Please replace calls of it with mipi_dsi_dcs_write_seq_multi(). + * * @dsi: DSI peripheral device * @cmd: Command * @seq: buffer containing data to be transmitted @@ -350,6 +396,22 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, return ret; \ } while (0) +/** + * mipi_dsi_dcs_write_seq_multi - transmit a DCS command with payload + * + * This macro will print errors for you and error handling is optimized for + * callers that call this multiple times in a row. + * + * @ctx: Context for multiple DSI transactions + * @cmd: Command + * @seq: buffer containing data to be transmitted + */ +#define mipi_dsi_dcs_write_seq_multi(ctx, cmd, seq...) \ + do { \ + static const u8 d[] = { cmd, seq }; \ + mipi_dsi_dcs_write_buffer_multi(ctx, d, ARRAY_SIZE(d)); \ + } while (0) + /** * struct mipi_dsi_driver - DSI driver * @driver: device driver model driver -- cgit v1.2.3-58-ga151 From f79d6d28d8fe77b14beeaebe5393d9f294f8d09d Mon Sep 17 00:00:00 2001 From: Dmitry Baryshkov Date: Sun, 12 May 2024 02:00:19 +0300 Subject: drm/mipi-dsi: wrap more functions for streamline handling Follow the pattern of mipi_dsi_dcs_*_multi() and wrap several existing MIPI DSI functions to use the context for processing. This simplifies and streamlines driver code to use simpler code pattern. Note, msleep function is also wrapped in this way as it is frequently called inbetween other mipi_dsi_dcs_*() functions. Signed-off-by: Dmitry Baryshkov Reviewed-by: Douglas Anderson Reviewed-by: Neil Armstrong Link: https://lore.kernel.org/r/20240512-dsi-panels-upd-api-v2-2-e31ca14d102e@linaro.org Signed-off-by: Neil Armstrong Link: https://patchwork.freedesktop.org/patch/msgid/20240512-dsi-panels-upd-api-v2-2-e31ca14d102e@linaro.org --- drivers/gpu/drm/drm_mipi_dsi.c | 210 +++++++++++++++++++++++++++++++++++++++++ include/drm/drm_mipi_dsi.h | 21 +++++ 2 files changed, 231 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 26c7383406c1..a471c46f5ca6 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -1429,6 +1429,216 @@ int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, } EXPORT_SYMBOL(mipi_dsi_dcs_get_display_brightness_large); +/** + * mipi_dsi_picture_parameter_set_multi() - transmit the DSC PPS to the peripheral + * @ctx: Context for multiple DSI transactions + * @pps: VESA DSC 1.1 Picture Parameter Set + * + * Like mipi_dsi_picture_parameter_set() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_picture_parameter_set_multi(struct mipi_dsi_multi_context *ctx, + const struct drm_dsc_picture_parameter_set *pps) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_picture_parameter_set(dsi, pps); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending PPS failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_picture_parameter_set_multi); + +/** + * mipi_dsi_compression_mode_ext_multi() - enable/disable DSC on the peripheral + * @ctx: Context for multiple DSI transactions + * @enable: Whether to enable or disable the DSC + * @algo: Selected compression algorithm + * @pps_selector: Select PPS from the table of pre-stored or uploaded PPS entries + * + * Like mipi_dsi_compression_mode_ext() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_compression_mode_ext_multi(struct mipi_dsi_multi_context *ctx, + bool enable, + enum mipi_dsi_compression_algo algo, + unsigned int pps_selector) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_compression_mode_ext(dsi, enable, algo, pps_selector); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending COMPRESSION_MODE failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_compression_mode_ext_multi); + +/** + * mipi_dsi_dcs_nop_multi() - send DCS NOP packet + * @ctx: Context for multiple DSI transactions + * + * Like mipi_dsi_dcs_nop() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_nop_multi(struct mipi_dsi_multi_context *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_nop(dsi); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS NOP failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_nop_multi); + +/** + * mipi_dsi_dcs_enter_sleep_mode_multi() - send DCS ENTER_SLEEP_MODE packet + * @ctx: Context for multiple DSI transactions + * + * Like mipi_dsi_dcs_enter_sleep_mode() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_enter_sleep_mode_multi(struct mipi_dsi_multi_context *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_enter_sleep_mode(dsi); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS ENTER_SLEEP_MODE failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_enter_sleep_mode_multi); + +/** + * mipi_dsi_dcs_exit_sleep_mode_multi() - send DCS EXIT_SLEEP_MODE packet + * @ctx: Context for multiple DSI transactions + * + * Like mipi_dsi_dcs_exit_sleep_mode() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_exit_sleep_mode_multi(struct mipi_dsi_multi_context *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_exit_sleep_mode(dsi); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS EXIT_SLEEP_MODE failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_exit_sleep_mode_multi); + +/** + * mipi_dsi_dcs_set_display_off_multi() - send DCS SET_DISPLAY_OFF packet + * @ctx: Context for multiple DSI transactions + * + * Like mipi_dsi_dcs_set_display_off() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_set_display_off_multi(struct mipi_dsi_multi_context *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_set_display_off(dsi); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS SET_DISPLAY_OFF failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_set_display_off_multi); + +/** + * mipi_dsi_dcs_set_display_on_multi() - send DCS SET_DISPLAY_ON packet + * @ctx: Context for multiple DSI transactions + * + * Like mipi_dsi_dcs_set_display_on() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_set_display_on_multi(struct mipi_dsi_multi_context *ctx) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_set_display_on(dsi); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS SET_DISPLAY_ON failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_set_display_on_multi); + +/** + * mipi_dsi_dcs_set_tear_on_multi() - send DCS SET_TEAR_ON packet + * @ctx: Context for multiple DSI transactions + * @mode: the Tearing Effect Output Line mode + * + * Like mipi_dsi_dcs_set_tear_on() but deals with errors in a way that + * makes it convenient to make several calls in a row. + */ +void mipi_dsi_dcs_set_tear_on_multi(struct mipi_dsi_multi_context *ctx, + enum mipi_dsi_dcs_tear_mode mode) +{ + struct mipi_dsi_device *dsi = ctx->dsi; + struct device *dev = &dsi->dev; + ssize_t ret; + + if (ctx->accum_err) + return; + + ret = mipi_dsi_dcs_set_tear_on(dsi, mode); + if (ret < 0) { + ctx->accum_err = ret; + dev_err(dev, "sending DCS SET_TEAR_ON failed: %d\n", + ctx->accum_err); + } +} +EXPORT_SYMBOL(mipi_dsi_dcs_set_tear_on_multi); + static int mipi_dsi_drv_probe(struct device *dev) { struct mipi_dsi_driver *drv = to_mipi_dsi_driver(dev->driver); diff --git a/include/drm/drm_mipi_dsi.h b/include/drm/drm_mipi_dsi.h index 5e9cad541bd6..bd5a0b6d0711 100644 --- a/include/drm/drm_mipi_dsi.h +++ b/include/drm/drm_mipi_dsi.h @@ -275,6 +275,13 @@ int mipi_dsi_compression_mode_ext(struct mipi_dsi_device *dsi, bool enable, int mipi_dsi_picture_parameter_set(struct mipi_dsi_device *dsi, const struct drm_dsc_picture_parameter_set *pps); +void mipi_dsi_compression_mode_ext_multi(struct mipi_dsi_multi_context *ctx, + bool enable, + enum mipi_dsi_compression_algo algo, + unsigned int pps_selector); +void mipi_dsi_picture_parameter_set_multi(struct mipi_dsi_multi_context *ctx, + const struct drm_dsc_picture_parameter_set *pps); + ssize_t mipi_dsi_generic_write(struct mipi_dsi_device *dsi, const void *payload, size_t size); int mipi_dsi_generic_write_chatty(struct mipi_dsi_device *dsi, @@ -284,6 +291,12 @@ void mipi_dsi_generic_write_multi(struct mipi_dsi_multi_context *ctx, ssize_t mipi_dsi_generic_read(struct mipi_dsi_device *dsi, const void *params, size_t num_params, void *data, size_t size); +#define mipi_dsi_msleep(ctx, delay) \ + do { \ + if (!ctx.accum_err) \ + msleep(delay); \ + } while (0) + /** * enum mipi_dsi_dcs_tear_mode - Tearing Effect Output Line mode * @MIPI_DSI_DCS_TEAR_MODE_VBLANK: the TE output line consists of V-Blanking @@ -338,6 +351,14 @@ int mipi_dsi_dcs_set_display_brightness_large(struct mipi_dsi_device *dsi, int mipi_dsi_dcs_get_display_brightness_large(struct mipi_dsi_device *dsi, u16 *brightness); +void mipi_dsi_dcs_nop_multi(struct mipi_dsi_multi_context *ctx); +void mipi_dsi_dcs_enter_sleep_mode_multi(struct mipi_dsi_multi_context *ctx); +void mipi_dsi_dcs_exit_sleep_mode_multi(struct mipi_dsi_multi_context *ctx); +void mipi_dsi_dcs_set_display_off_multi(struct mipi_dsi_multi_context *ctx); +void mipi_dsi_dcs_set_display_on_multi(struct mipi_dsi_multi_context *ctx); +void mipi_dsi_dcs_set_tear_on_multi(struct mipi_dsi_multi_context *ctx, + enum mipi_dsi_dcs_tear_mode mode); + /** * mipi_dsi_generic_write_seq - transmit data using a generic write packet * -- cgit v1.2.3-58-ga151 From 8a0a7b98d4b6eeeab337ec25daa4bc0a5e710a15 Mon Sep 17 00:00:00 2001 From: Wayne Lin Date: Thu, 7 Mar 2024 14:29:57 +0800 Subject: drm/mst: Fix NULL pointer dereference at drm_dp_add_payload_part2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Why] Commit: - commit 5aa1dfcdf0a4 ("drm/mst: Refactor the flow for payload allocation/removement") accidently overwrite the commit - commit 54d217406afe ("drm: use mgr->dev in drm_dbg_kms in drm_dp_add_payload_part2") which cause regression. [How] Recover the original NULL fix and remove the unnecessary input parameter 'state' for drm_dp_add_payload_part2(). Fixes: 5aa1dfcdf0a4 ("drm/mst: Refactor the flow for payload allocation/removement") Reported-by: Leon Weiß Link: https://lore.kernel.org/r/38c253ea42072cc825dc969ac4e6b9b600371cc8.camel@ruhr-uni-bochum.de/ Cc: lyude@redhat.com Cc: imre.deak@intel.com Cc: stable@vger.kernel.org Cc: regressions@lists.linux.dev Reviewed-by: Harry Wentland Acked-by: Jani Nikula Signed-off-by: Wayne Lin Signed-off-by: Alex Deucher Link: https://patchwork.freedesktop.org/patch/msgid/20240307062957.2323620-1-Wayne.Lin@amd.com --- drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c | 2 +- drivers/gpu/drm/display/drm_dp_mst_topology.c | 4 +--- drivers/gpu/drm/i915/display/intel_dp_mst.c | 2 +- drivers/gpu/drm/nouveau/dispnv50/disp.c | 2 +- include/drm/display/drm_dp_mst_helper.h | 1 - 5 files changed, 4 insertions(+), 7 deletions(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c index c27063305a13..2c36f3d00ca2 100644 --- a/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c +++ b/drivers/gpu/drm/amd/display/amdgpu_dm/amdgpu_dm_helpers.c @@ -363,7 +363,7 @@ void dm_helpers_dp_mst_send_payload_allocation( mst_state = to_drm_dp_mst_topology_state(mst_mgr->base.state); new_payload = drm_atomic_get_mst_payload_state(mst_state, aconnector->mst_output_port); - ret = drm_dp_add_payload_part2(mst_mgr, mst_state->base.state, new_payload); + ret = drm_dp_add_payload_part2(mst_mgr, new_payload); if (ret) { amdgpu_dm_set_mst_status(&aconnector->mst_status, diff --git a/drivers/gpu/drm/display/drm_dp_mst_topology.c b/drivers/gpu/drm/display/drm_dp_mst_topology.c index 3577786b5db2..7f8e1cfbe19d 100644 --- a/drivers/gpu/drm/display/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/display/drm_dp_mst_topology.c @@ -3421,7 +3421,6 @@ EXPORT_SYMBOL(drm_dp_remove_payload_part2); /** * drm_dp_add_payload_part2() - Execute payload update part 2 * @mgr: Manager to use. - * @state: The global atomic state * @payload: The payload to update * * If @payload was successfully assigned a starting time slot by drm_dp_add_payload_part1(), this @@ -3430,14 +3429,13 @@ EXPORT_SYMBOL(drm_dp_remove_payload_part2); * Returns: 0 on success, negative error code on failure. */ int drm_dp_add_payload_part2(struct drm_dp_mst_topology_mgr *mgr, - struct drm_atomic_state *state, struct drm_dp_mst_atomic_payload *payload) { int ret = 0; /* Skip failed payloads */ if (payload->payload_allocation_status != DRM_DP_MST_PAYLOAD_ALLOCATION_DFP) { - drm_dbg_kms(state->dev, "Part 1 of payload creation for %s failed, skipping part 2\n", + drm_dbg_kms(mgr->dev, "Part 1 of payload creation for %s failed, skipping part 2\n", payload->port->connector->name); return -EIO; } diff --git a/drivers/gpu/drm/i915/display/intel_dp_mst.c b/drivers/gpu/drm/i915/display/intel_dp_mst.c index c772ba19c547..715d2f59f565 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_mst.c +++ b/drivers/gpu/drm/i915/display/intel_dp_mst.c @@ -1241,7 +1241,7 @@ static void intel_mst_enable_dp(struct intel_atomic_state *state, if (first_mst_stream) intel_ddi_wait_for_fec_status(encoder, pipe_config, true); - drm_dp_add_payload_part2(&intel_dp->mst_mgr, &state->base, + drm_dp_add_payload_part2(&intel_dp->mst_mgr, drm_atomic_get_mst_payload_state(mst_state, connector->port)); if (DISPLAY_VER(dev_priv) >= 12) diff --git a/drivers/gpu/drm/nouveau/dispnv50/disp.c b/drivers/gpu/drm/nouveau/dispnv50/disp.c index 0c3d88ad0b0e..88728a0b2c25 100644 --- a/drivers/gpu/drm/nouveau/dispnv50/disp.c +++ b/drivers/gpu/drm/nouveau/dispnv50/disp.c @@ -915,7 +915,7 @@ nv50_msto_cleanup(struct drm_atomic_state *state, msto->disabled = false; drm_dp_remove_payload_part2(mgr, new_mst_state, old_payload, new_payload); } else if (msto->enabled) { - drm_dp_add_payload_part2(mgr, state, new_payload); + drm_dp_add_payload_part2(mgr, new_payload); msto->enabled = false; } } diff --git a/include/drm/display/drm_dp_mst_helper.h b/include/drm/display/drm_dp_mst_helper.h index 3546b58a121b..cfe096389d94 100644 --- a/include/drm/display/drm_dp_mst_helper.h +++ b/include/drm/display/drm_dp_mst_helper.h @@ -871,7 +871,6 @@ int drm_dp_add_payload_part1(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, struct drm_dp_mst_atomic_payload *payload); int drm_dp_add_payload_part2(struct drm_dp_mst_topology_mgr *mgr, - struct drm_atomic_state *state, struct drm_dp_mst_atomic_payload *payload); void drm_dp_remove_payload_part1(struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_state *mst_state, -- cgit v1.2.3-58-ga151 From 3d9d313d518c5bc9e5ab6aeab86c9fa4bece095c Mon Sep 17 00:00:00 2001 From: Jani Nikula Date: Fri, 10 May 2024 18:08:13 +0300 Subject: drm/connector: update edid_blob_ptr documentation Accessing the EDID via edid_blob_ptr causes chicken-and-egg problems. Keep edid_blob_ptr as the userspace interface that should be accessed via dedicated functions. Reviewed-by: Chaitanya Kumar Borah Acked-by: Alex Deucher Link: https://patchwork.freedesktop.org/patch/msgid/b6aa1ea30ae85ef9e9814315d3437e82f0ba6754.1715353572.git.jani.nikula@intel.com Signed-off-by: Jani Nikula --- include/drm/drm_connector.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'include/drm') diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index fe88d7fc6b8f..58ee9adf9091 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1636,8 +1636,12 @@ struct drm_connector { /** * @edid_blob_ptr: DRM property containing EDID if present. Protected by - * &drm_mode_config.mutex. This should be updated only by calling + * &drm_mode_config.mutex. + * + * This must be updated only by calling drm_edid_connector_update() or * drm_connector_update_edid_property(). + * + * This must not be used by drivers directly. */ struct drm_property_blob *edid_blob_ptr; -- cgit v1.2.3-58-ga151 From 582d79f34330db4ccee85620cf95ee7ccb9a0d01 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:57:50 +0200 Subject: drm/connector: Introduce an HDMI connector initialization function A lot of the various HDMI drivers duplicate some logic that depends on the HDMI spec itself and not really a particular hardware implementation. Output BPC or format selection, infoframe generation are good examples of such areas. This creates a lot of boilerplate, with a lot of variations, which makes it hard for userspace to rely on, and makes it difficult to get it right for drivers. In the next patches, we'll add a lot of infrastructure around the drm_connector and drm_connector_state structures, which will allow to abstract away the duplicated logic. This infrastructure comes with a few requirements though, and thus we need a new initialization function. Hopefully, this will make drivers simpler to handle, and their behaviour more consistent. Reviewed-by: Dave Stevenson Reviewed-by: Sui Jingfeng Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-1-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/drm_connector.c | 39 +++++++++++++++++++++++++++++++++++++++ include/drm/drm_connector.h | 5 +++++ 2 files changed, 44 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 4d2df7f64dc5..8c471bdfbfff 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -452,6 +452,45 @@ int drmm_connector_init(struct drm_device *dev, } EXPORT_SYMBOL(drmm_connector_init); +/** + * drmm_connector_hdmi_init - Init a preallocated HDMI connector + * @dev: DRM device + * @connector: A pointer to the HDMI connector to init + * @funcs: callbacks for this connector + * @connector_type: user visible type of the connector + * @ddc: optional pointer to the associated ddc adapter + * + * Initialises a preallocated HDMI connector. Connectors can be + * subclassed as part of driver connector objects. + * + * Cleanup is automatically handled with a call to + * drm_connector_cleanup() in a DRM-managed action. + * + * The connector structure should be allocated with drmm_kzalloc(). + * + * Returns: + * Zero on success, error code on failure. + */ +int drmm_connector_hdmi_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc) +{ + int ret; + + if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector_type == DRM_MODE_CONNECTOR_HDMIB)) + return -EINVAL; + + ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(drmm_connector_hdmi_init); + /** * drm_connector_attach_edid_property - attach edid property. * @connector: the connector diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 58ee9adf9091..7ef1ff83d885 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1908,6 +1908,11 @@ int drmm_connector_init(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc); +int drmm_connector_hdmi_init(struct drm_device *dev, + struct drm_connector *connector, + const struct drm_connector_funcs *funcs, + int connector_type, + struct i2c_adapter *ddc); void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); void drm_connector_unregister(struct drm_connector *connector); -- cgit v1.2.3-58-ga151 From 54cb39e2293b1e221708d3ac157ecc59086e1b46 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:57:52 +0200 Subject: drm/connector: hdmi: Create an HDMI sub-state The next features we will need to share across drivers will need to store some parameters for drivers to use, such as the selected output format. Let's create a new connector sub-state dedicated to HDMI controllers, that will eventually store everything we need. Reviewed-by: Dave Stevenson Reviewed-by: Sui Jingfeng Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-3-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/Kconfig | 7 +++++ drivers/gpu/drm/display/Makefile | 2 ++ drivers/gpu/drm/display/drm_hdmi_state_helper.c | 41 +++++++++++++++++++++++++ include/drm/display/drm_hdmi_state_helper.h | 16 ++++++++++ include/drm/drm_connector.h | 7 +++++ 5 files changed, 73 insertions(+) create mode 100644 drivers/gpu/drm/display/drm_hdmi_state_helper.c create mode 100644 include/drm/display/drm_hdmi_state_helper.h (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/Kconfig b/drivers/gpu/drm/display/Kconfig index 864a6488bfdf..14114b597ef4 100644 --- a/drivers/gpu/drm/display/Kconfig +++ b/drivers/gpu/drm/display/Kconfig @@ -70,3 +70,10 @@ config DRM_DISPLAY_HDMI_HELPER depends on DRM_DISPLAY_HELPER help DRM display helpers for HDMI. + +config DRM_DISPLAY_HDMI_STATE_HELPER + bool + depends on DRM_DISPLAY_HELPER + depends on DRM_DISPLAY_HDMI_HELPER + help + DRM KMS state helpers for HDMI. diff --git a/drivers/gpu/drm/display/Makefile b/drivers/gpu/drm/display/Makefile index 17d2cc73ff56..629df2f4d322 100644 --- a/drivers/gpu/drm/display/Makefile +++ b/drivers/gpu/drm/display/Makefile @@ -14,6 +14,8 @@ drm_display_helper-$(CONFIG_DRM_DISPLAY_HDCP_HELPER) += drm_hdcp_helper.o drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_HELPER) += \ drm_hdmi_helper.o \ drm_scdc_helper.o +drm_display_helper-$(CONFIG_DRM_DISPLAY_HDMI_STATE_HELPER) += \ + drm_hdmi_state_helper.o drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_AUX_CHARDEV) += drm_dp_aux_dev.o drm_display_helper-$(CONFIG_DRM_DISPLAY_DP_AUX_CEC) += drm_dp_cec.o diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c new file mode 100644 index 000000000000..1e92c1108d23 --- /dev/null +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT + +#include +#include + +#include + +/** + * __drm_atomic_helper_connector_hdmi_reset() - Initializes all HDMI @drm_connector_state resources + * @connector: DRM connector + * @new_conn_state: connector state to reset + * + * Initializes all HDMI resources from a @drm_connector_state without + * actually allocating it. This is useful for HDMI drivers, in + * combination with __drm_atomic_helper_connector_reset() or + * drm_atomic_helper_connector_reset(). + */ +void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, + struct drm_connector_state *new_conn_state) +{ +} +EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); + +/** + * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state + * @connector: DRM Connector + * @state: the DRM State object + * + * Provides a default connector state check handler for HDMI connectors. + * Checks that a desired connector update is valid, and updates various + * fields of derived state. + * + * RETURNS: + * Zero on success, or an errno code otherwise. + */ +int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + return 0; +} +EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check); diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h new file mode 100644 index 000000000000..837899db315a --- /dev/null +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: MIT */ + +#ifndef DRM_HDMI_STATE_HELPER_H_ +#define DRM_HDMI_STATE_HELPER_H_ + +struct drm_atomic_state; +struct drm_connector; +struct drm_connector_state; + +void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, + struct drm_connector_state *new_conn_state); + +int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, + struct drm_atomic_state *state); + +#endif // DRM_HDMI_STATE_HELPER_H_ diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 7ef1ff83d885..5fe3f6a519cd 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1031,6 +1031,13 @@ struct drm_connector_state { * DRM blob property for HDR output metadata */ struct drm_property_blob *hdr_output_metadata; + + /** + * @hdmi: HDMI-related variable and properties. Filled by + * @drm_atomic_helper_connector_hdmi_check(). + */ + struct { + } hdmi; }; /** -- cgit v1.2.3-58-ga151 From aadb3e16b8f30cc1c1efdfe162f400e026385bfb Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:57:53 +0200 Subject: drm/connector: hdmi: Add output BPC to the connector state We'll add automatic selection of the output BPC in a following patch, but let's add it to the HDMI connector state already. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-4-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 20 ++++++++++++++++++++ drivers/gpu/drm/drm_atomic.c | 5 +++++ drivers/gpu/drm/drm_connector.c | 20 +++++++++++++++++++- drivers/gpu/drm/tests/drm_connector_test.c | 12 ++++++++---- include/drm/drm_connector.h | 12 +++++++++++- 5 files changed, 63 insertions(+), 6 deletions(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 1e92c1108d23..82293d93b5f8 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -18,6 +18,10 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, struct drm_connector_state *new_conn_state) { + unsigned int max_bpc = connector->max_bpc; + + new_conn_state->max_bpc = max_bpc; + new_conn_state->max_requested_bpc = max_bpc; } EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); @@ -36,6 +40,22 @@ EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, struct drm_atomic_state *state) { + struct drm_connector_state *old_conn_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_conn_state = + drm_atomic_get_new_connector_state(state, connector); + + if (old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc) { + struct drm_crtc *crtc = new_conn_state->crtc; + struct drm_crtc_state *crtc_state; + + crtc_state = drm_atomic_get_crtc_state(state, crtc); + if (IS_ERR(crtc_state)) + return PTR_ERR(crtc_state); + + crtc_state->mode_changed = true; + } + return 0; } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check); diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index a91737adf8e7..4e11cfb4518b 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1143,6 +1143,11 @@ static void drm_atomic_connector_print_state(struct drm_printer *p, drm_printf(p, "\tmax_requested_bpc=%d\n", state->max_requested_bpc); drm_printf(p, "\tcolorspace=%s\n", drm_get_colorspace_name(state->colorspace)); + if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || + connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { + drm_printf(p, "\toutput_bpc=%u\n", state->hdmi.output_bpc); + } + if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) if (state->writeback_job && state->writeback_job->fb) drm_printf(p, "\tfb=%d\n", state->writeback_job->fb->base.id); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 8c471bdfbfff..e5c840987854 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -459,6 +459,7 @@ EXPORT_SYMBOL(drmm_connector_init); * @funcs: callbacks for this connector * @connector_type: user visible type of the connector * @ddc: optional pointer to the associated ddc adapter + * @max_bpc: Maximum bits per char the HDMI connector supports * * Initialises a preallocated HDMI connector. Connectors can be * subclassed as part of driver connector objects. @@ -475,7 +476,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, const struct drm_connector_funcs *funcs, int connector_type, - struct i2c_adapter *ddc) + struct i2c_adapter *ddc, + unsigned int max_bpc) { int ret; @@ -483,10 +485,26 @@ int drmm_connector_hdmi_init(struct drm_device *dev, connector_type == DRM_MODE_CONNECTOR_HDMIB)) return -EINVAL; + if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12)) + return -EINVAL; + ret = drmm_connector_init(dev, connector, funcs, connector_type, ddc); if (ret) return ret; + /* + * drm_connector_attach_max_bpc_property() requires the + * connector to have a state. + */ + if (connector->funcs->reset) + connector->funcs->reset(connector); + + drm_connector_attach_max_bpc_property(connector, 8, max_bpc); + connector->max_bpc = max_bpc; + + if (max_bpc > 8) + drm_connector_attach_hdr_output_metadata_property(connector); + return 0; } EXPORT_SYMBOL(drmm_connector_hdmi_init); diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index 261d4109946d..2661eb64a5cd 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -184,7 +184,8 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, - &priv->ddc); + &priv->ddc, + 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -200,7 +201,8 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, - NULL); + NULL, + 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -217,7 +219,8 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, connector_type, - &priv->ddc); + &priv->ddc, + 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -248,7 +251,8 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, connector_type, - &priv->ddc); + &priv->ddc, + 8); KUNIT_EXPECT_LT(test, ret, 0); } diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 5fe3f6a519cd..4ddb438ea2dd 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1037,6 +1037,10 @@ struct drm_connector_state { * @drm_atomic_helper_connector_hdmi_check(). */ struct { + /** + * @output_bpc: Bits per color channel to output. + */ + unsigned int output_bpc; } hdmi; }; @@ -1686,6 +1690,11 @@ struct drm_connector { */ struct drm_property_blob *path_blob_ptr; + /** + * @max_bpc: Maximum bits per color channel the connector supports. + */ + unsigned int max_bpc; + /** * @max_bpc_property: Default connector property for the max bpc to be * driven out of the connector. @@ -1919,7 +1928,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, const struct drm_connector_funcs *funcs, int connector_type, - struct i2c_adapter *ddc); + struct i2c_adapter *ddc, + unsigned int max_bpc); void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); void drm_connector_unregister(struct drm_connector *connector); -- cgit v1.2.3-58-ga151 From 948f01d5e5595023c2e7cfc0184a322be00ef214 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:57:56 +0200 Subject: drm/connector: hdmi: Add support for output format Just like BPC, we'll add support for automatic selection of the output format for HDMI connectors. Let's add the needed defaults and fields for now. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-7-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 3 ++- drivers/gpu/drm/drm_atomic.c | 2 ++ drivers/gpu/drm/drm_connector.c | 31 ++++++++++++++++++++++ drivers/gpu/drm/tests/drm_connector_test.c | 9 +++++++ drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 22 +++++++++++---- include/drm/drm_connector.h | 20 ++++++++++++++ 6 files changed, 81 insertions(+), 6 deletions(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 82293d93b5f8..f6cd0612ea2c 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -45,7 +45,8 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, struct drm_connector_state *new_conn_state = drm_atomic_get_new_connector_state(state, connector); - if (old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc) { + if (old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc || + old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) { struct drm_crtc *crtc = new_conn_state->crtc; struct drm_crtc_state *crtc_state; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 4e11cfb4518b..8730137baa86 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1146,6 +1146,8 @@ static void drm_atomic_connector_print_state(struct drm_printer *p, if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { drm_printf(p, "\toutput_bpc=%u\n", state->hdmi.output_bpc); + drm_printf(p, "\toutput_format=%s\n", + drm_hdmi_connector_get_output_format_name(state->hdmi.output_format)); } if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index e5c840987854..f554bf703e13 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -459,6 +459,7 @@ EXPORT_SYMBOL(drmm_connector_init); * @funcs: callbacks for this connector * @connector_type: user visible type of the connector * @ddc: optional pointer to the associated ddc adapter + * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats * @max_bpc: Maximum bits per char the HDMI connector supports * * Initialises a preallocated HDMI connector. Connectors can be @@ -477,6 +478,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc, + unsigned long supported_formats, unsigned int max_bpc) { int ret; @@ -485,6 +487,9 @@ int drmm_connector_hdmi_init(struct drm_device *dev, connector_type == DRM_MODE_CONNECTOR_HDMIB)) return -EINVAL; + if (!supported_formats || !(supported_formats & BIT(HDMI_COLORSPACE_RGB))) + return -EINVAL; + if (!(max_bpc == 8 || max_bpc == 10 || max_bpc == 12)) return -EINVAL; @@ -492,6 +497,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, if (ret) return ret; + connector->hdmi.supported_formats = supported_formats; + /* * drm_connector_attach_max_bpc_property() requires the * connector to have a state. @@ -1201,6 +1208,30 @@ static const u32 dp_colorspaces = BIT(DRM_MODE_COLORIMETRY_BT2020_CYCC) | BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); +static const char * const output_format_str[] = { + [HDMI_COLORSPACE_RGB] = "RGB", + [HDMI_COLORSPACE_YUV420] = "YUV 4:2:0", + [HDMI_COLORSPACE_YUV422] = "YUV 4:2:2", + [HDMI_COLORSPACE_YUV444] = "YUV 4:4:4", +}; + +/* + * drm_hdmi_connector_get_output_format_name() - Return a string for HDMI connector output format + * @fmt: Output format to compute name of + * + * Returns: the name of the output format, or NULL if the type is not + * valid. + */ +const char * +drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt) +{ + if (fmt >= ARRAY_SIZE(output_format_str)) + return NULL; + + return output_format_str[fmt]; +} +EXPORT_SYMBOL(drm_hdmi_connector_get_output_format_name); + /** * DOC: standard connector properties * diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index 9465fa33f3b6..37e6efd46d7e 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -187,6 +187,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -204,6 +205,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, NULL, + BIT(HDMI_COLORSPACE_RGB), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -221,6 +223,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 9); KUNIT_EXPECT_LT(test, ret, 0); } @@ -238,6 +241,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 0); KUNIT_EXPECT_LT(test, ret, 0); } @@ -260,6 +264,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 8); KUNIT_EXPECT_EQ(test, ret, 0); @@ -298,6 +303,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 10); KUNIT_EXPECT_EQ(test, ret, 0); @@ -336,6 +342,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) &dummy_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 12); KUNIT_EXPECT_EQ(test, ret, 0); @@ -370,6 +377,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) &dummy_funcs, connector_type, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 8); KUNIT_EXPECT_EQ(test, ret, 0); } @@ -402,6 +410,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) &dummy_funcs, connector_type, &priv->ddc, + BIT(HDMI_COLORSPACE_RGB), 8); KUNIT_EXPECT_LT(test, ret, 0); } diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index a82f2a52ae0f..69ffaa4507c1 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -149,6 +149,7 @@ static const struct drm_connector_funcs dummy_connector_funcs = { static struct drm_atomic_helper_connector_hdmi_priv * drm_atomic_helper_connector_hdmi_init(struct kunit *test, + unsigned int formats, unsigned int max_bpc) { struct drm_atomic_helper_connector_hdmi_priv *priv; @@ -192,6 +193,7 @@ drm_atomic_helper_connector_hdmi_init(struct kunit *test, &dummy_connector_funcs, DRM_MODE_CONNECTOR_HDMIA, NULL, + formats, max_bpc); KUNIT_ASSERT_EQ(test, ret, 0); @@ -227,7 +229,9 @@ static void drm_test_check_output_bpc_crtc_mode_changed(struct kunit *test) struct drm_crtc *crtc; int ret; - priv = drm_atomic_helper_connector_hdmi_init(test, 10); + priv = drm_atomic_helper_connector_hdmi_init(test, + BIT(HDMI_COLORSPACE_RGB), + 10); KUNIT_ASSERT_NOT_NULL(test, priv); ctx = drm_kunit_helper_acquire_ctx_alloc(test); @@ -294,7 +298,9 @@ static void drm_test_check_output_bpc_crtc_mode_not_changed(struct kunit *test) struct drm_crtc *crtc; int ret; - priv = drm_atomic_helper_connector_hdmi_init(test, 10); + priv = drm_atomic_helper_connector_hdmi_init(test, + BIT(HDMI_COLORSPACE_RGB), + 10); KUNIT_ASSERT_NOT_NULL(test, priv); ctx = drm_kunit_helper_acquire_ctx_alloc(test); @@ -363,7 +369,9 @@ static void drm_test_check_bpc_8_value(struct kunit *test) struct drm_connector_state *conn_state; struct drm_connector *conn; - priv = drm_atomic_helper_connector_hdmi_init(test, 8); + priv = drm_atomic_helper_connector_hdmi_init(test, + BIT(HDMI_COLORSPACE_RGB), + 8); KUNIT_ASSERT_NOT_NULL(test, priv); conn = &priv->connector; @@ -385,7 +393,9 @@ static void drm_test_check_bpc_10_value(struct kunit *test) struct drm_connector_state *conn_state; struct drm_connector *conn; - priv = drm_atomic_helper_connector_hdmi_init(test, 10); + priv = drm_atomic_helper_connector_hdmi_init(test, + BIT(HDMI_COLORSPACE_RGB), + 10); KUNIT_ASSERT_NOT_NULL(test, priv); conn = &priv->connector; @@ -407,7 +417,9 @@ static void drm_test_check_bpc_12_value(struct kunit *test) struct drm_connector_state *conn_state; struct drm_connector *conn; - priv = drm_atomic_helper_connector_hdmi_init(test, 12); + priv = drm_atomic_helper_connector_hdmi_init(test, + BIT(HDMI_COLORSPACE_RGB), + 12); KUNIT_ASSERT_NOT_NULL(test, priv); conn = &priv->connector; diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 4ddb438ea2dd..4b6b1117c7d0 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -368,6 +368,9 @@ enum drm_panel_orientation { DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, }; +const char * +drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt); + /** * struct drm_monitor_range_info - Panel's Monitor range in EDID for * &drm_display_info @@ -1041,6 +1044,11 @@ struct drm_connector_state { * @output_bpc: Bits per color channel to output. */ unsigned int output_bpc; + + /** + * @output_format: Pixel format to output in. + */ + enum hdmi_colorspace output_format; } hdmi; }; @@ -1906,6 +1914,17 @@ struct drm_connector { /** @hdr_sink_metadata: HDR Metadata Information read from sink */ struct hdr_sink_metadata hdr_sink_metadata; + + /** + * @hdmi: HDMI-related variable and properties. + */ + struct { + /** + * @supported_formats: Bitmask of @hdmi_colorspace + * supported by the controller. + */ + unsigned long supported_formats; + } hdmi; }; #define obj_to_connector(x) container_of(x, struct drm_connector, base) @@ -1929,6 +1948,7 @@ int drmm_connector_hdmi_init(struct drm_device *dev, const struct drm_connector_funcs *funcs, int connector_type, struct i2c_adapter *ddc, + unsigned long supported_formats, unsigned int max_bpc); void drm_connector_attach_edid_property(struct drm_connector *connector); int drm_connector_register(struct drm_connector *connector); -- cgit v1.2.3-58-ga151 From 40167bcbd19c241fc30a912fa8a8276b9ed1a12e Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:57:58 +0200 Subject: drm/display: hdmi: Add HDMI compute clock helper A lot of HDMI drivers have some variation of the formula to calculate the TMDS character rate from a mode, but few of them actually take all parameters into account. Let's create a helper to provide that rate taking all parameters into account. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-9-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_helper.c | 61 +++++++++++++++++++++++++++++++ include/drm/display/drm_hdmi_helper.h | 4 ++ 2 files changed, 65 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_helper.c b/drivers/gpu/drm/display/drm_hdmi_helper.c index faf5e9efa7d3..74dd4d01dd9b 100644 --- a/drivers/gpu/drm/display/drm_hdmi_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_helper.c @@ -195,3 +195,64 @@ void drm_hdmi_avi_infoframe_content_type(struct hdmi_avi_infoframe *frame, frame->itc = conn_state->content_type != DRM_MODE_CONTENT_TYPE_NO_DATA; } EXPORT_SYMBOL(drm_hdmi_avi_infoframe_content_type); + +/** + * drm_hdmi_compute_mode_clock() - Computes the TMDS Character Rate + * @mode: Display mode to compute the clock for + * @bpc: Bits per character + * @fmt: Output Pixel Format used + * + * Returns the TMDS Character Rate for a given mode, bpc count and output format. + * + * RETURNS: + * The TMDS Character Rate, in Hertz, or 0 on error. + */ +unsigned long long +drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, + unsigned int bpc, enum hdmi_colorspace fmt) +{ + unsigned long long clock = mode->clock * 1000ULL; + unsigned int vic = drm_match_cea_mode(mode); + + /* + * CTA-861-G Spec, section 5.4 - Color Coding and Quantization + * mandates that VIC 1 always uses 8 bpc. + */ + if (vic == 1 && bpc != 8) + return 0; + + if (fmt == HDMI_COLORSPACE_YUV422) { + /* + * HDMI 1.0 Spec, section 6.5 - Pixel Encoding states that + * YUV422 sends 24 bits over three channels, with Cb and Cr + * components being sent on odd and even pixels, respectively. + * + * If fewer than 12 bpc are sent, data are left justified. + */ + if (bpc > 12) + return 0; + + /* + * HDMI 1.0 Spec, section 6.5 - Pixel Encoding + * specifies that YUV422 sends two 12-bits components over + * three TMDS channels per pixel clock, which is equivalent to + * three 8-bits components over three channels used by RGB as + * far as the clock rate goes. + */ + bpc = 8; + } + + /* + * HDMI 2.0 Spec, Section 7.1 - YCbCr 4:2:0 Pixel Encoding + * specifies that YUV420 encoding is carried at a TMDS Character Rate + * equal to half the pixel clock rate. + */ + if (fmt == HDMI_COLORSPACE_YUV420) + clock = clock / 2; + + if (mode->flags & DRM_MODE_FLAG_DBLCLK) + clock = clock * 2; + + return DIV_ROUND_CLOSEST_ULL(clock * bpc, 8); +} +EXPORT_SYMBOL(drm_hdmi_compute_mode_clock); diff --git a/include/drm/display/drm_hdmi_helper.h b/include/drm/display/drm_hdmi_helper.h index 76d234826e22..57e3b18c15ec 100644 --- a/include/drm/display/drm_hdmi_helper.h +++ b/include/drm/display/drm_hdmi_helper.h @@ -24,4 +24,8 @@ drm_hdmi_infoframe_set_hdr_metadata(struct hdmi_drm_infoframe *frame, void drm_hdmi_avi_infoframe_content_type(struct hdmi_avi_infoframe *frame, const struct drm_connector_state *conn_state); +unsigned long long +drm_hdmi_compute_mode_clock(const struct drm_display_mode *mode, + unsigned int bpc, enum hdmi_colorspace fmt); + #endif -- cgit v1.2.3-58-ga151 From f035f4097f1e0a35a457b72427bb0c06ca0c81c4 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:58:00 +0200 Subject: drm/connector: hdmi: Calculate TMDS character rate Most HDMI drivers have some code to calculate the TMDS character rate, usually to adjust an internal clock to match what the mode requires. Since the TMDS character rates mostly depends on the resolution, whether we need to repeat pixels or not, the bpc count and the format, we can now derive it from the HDMI connector state that stores all those infos and remove the duplication from drivers. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-11-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 67 ++++++++++++++++++++++ drivers/gpu/drm/drm_atomic.c | 1 + drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 3 + include/drm/drm_connector.h | 5 ++ 4 files changed, 76 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index f6cd0612ea2c..08630561d864 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -3,6 +3,7 @@ #include #include +#include #include /** @@ -25,6 +26,63 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, } EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); +static const struct drm_display_mode * +connector_state_get_mode(const struct drm_connector_state *conn_state) +{ + struct drm_atomic_state *state; + struct drm_crtc_state *crtc_state; + struct drm_crtc *crtc; + + state = conn_state->state; + if (!state) + return NULL; + + crtc = conn_state->crtc; + if (!crtc) + return NULL; + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + if (!crtc_state) + return NULL; + + return &crtc_state->mode; +} + +static enum drm_mode_status +hdmi_clock_valid(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long clock) +{ + const struct drm_display_info *info = &connector->display_info; + + if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) + return MODE_CLOCK_HIGH; + + return MODE_OK; +} + +static int +hdmi_compute_clock(const struct drm_connector *connector, + struct drm_connector_state *conn_state, + const struct drm_display_mode *mode, + unsigned int bpc, enum hdmi_colorspace fmt) +{ + enum drm_mode_status status; + unsigned long long clock; + + clock = drm_hdmi_compute_mode_clock(mode, bpc, fmt); + if (!clock) + return -EINVAL; + + status = hdmi_clock_valid(connector, mode, clock); + if (status != MODE_OK) + return -EINVAL; + + conn_state->hdmi.tmds_char_rate = clock; + + return 0; +} + /** * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state * @connector: DRM Connector @@ -44,6 +102,15 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, drm_atomic_get_old_connector_state(state, connector); struct drm_connector_state *new_conn_state = drm_atomic_get_new_connector_state(state, connector); + const struct drm_display_mode *mode = + connector_state_get_mode(new_conn_state); + int ret; + + ret = hdmi_compute_clock(connector, new_conn_state, mode, + new_conn_state->hdmi.output_bpc, + new_conn_state->hdmi.output_format); + if (ret) + return ret; if (old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc || old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) { diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 8730137baa86..26f9e525c0a0 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1148,6 +1148,7 @@ static void drm_atomic_connector_print_state(struct drm_printer *p, drm_printf(p, "\toutput_bpc=%u\n", state->hdmi.output_bpc); drm_printf(p, "\toutput_format=%s\n", drm_hdmi_connector_get_output_format_name(state->hdmi.output_format)); + drm_printf(p, "\ttmds_char_rate=%llu\n", state->hdmi.tmds_char_rate); } if (connector->connector_type == DRM_MODE_CONNECTOR_WRITEBACK) diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index 99fed169ea4d..71cfc7450134 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -72,6 +72,9 @@ static int light_up_connector(struct kunit *test, conn_state = drm_atomic_get_connector_state(state, connector); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, conn_state); + conn_state->hdmi.output_bpc = connector->max_bpc; + conn_state->hdmi.output_format = HDMI_COLORSPACE_RGB; + ret = drm_atomic_set_crtc_for_connector(conn_state, crtc); KUNIT_EXPECT_EQ(test, ret, 0); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 4b6b1117c7d0..3bdcf904a11b 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1049,6 +1049,11 @@ struct drm_connector_state { * @output_format: Pixel format to output in. */ enum hdmi_colorspace output_format; + + /** + * @tmds_char_rate: TMDS Character Rate, in Hz. + */ + unsigned long long tmds_char_rate; } hdmi; }; -- cgit v1.2.3-58-ga151 From e5030a74f976b4e808e28e78805c87203ac1a48d Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:58:02 +0200 Subject: drm/connector: hdmi: Add custom hook to filter TMDS character rate Most of the HDMI controllers have an upper TMDS character rate limit they can't exceed. On "embedded"-grade display controllers, it will typically be lower than what high-grade monitors can provide these days, so drivers will filter the TMDS character rate based on the controller capabilities. To make that easier to handle for drivers, let's provide an optional hook to be implemented by drivers so they can tell the HDMI controller helpers if a given TMDS character rate is reachable for them or not. This will then be useful to figure out the best format and bpc count for a given mode. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-13-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 9 +++++++ drivers/gpu/drm/drm_connector.c | 4 +++ drivers/gpu/drm/tests/drm_connector_test.c | 14 ++++++++++ drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 4 +++ include/drm/drm_connector.h | 31 ++++++++++++++++++++++ 5 files changed, 62 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 08630561d864..063421835dba 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -53,11 +53,20 @@ hdmi_clock_valid(const struct drm_connector *connector, const struct drm_display_mode *mode, unsigned long long clock) { + const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; const struct drm_display_info *info = &connector->display_info; if (info->max_tmds_clock && clock > info->max_tmds_clock * 1000) return MODE_CLOCK_HIGH; + if (funcs && funcs->tmds_char_rate_valid) { + enum drm_mode_status status; + + status = funcs->tmds_char_rate_valid(connector, mode, clock); + if (status != MODE_OK) + return status; + } + return MODE_OK; } diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index f554bf703e13..39e9ff6c4f61 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -457,6 +457,7 @@ EXPORT_SYMBOL(drmm_connector_init); * @dev: DRM device * @connector: A pointer to the HDMI connector to init * @funcs: callbacks for this connector + * @hdmi_funcs: HDMI-related callbacks for this connector * @connector_type: user visible type of the connector * @ddc: optional pointer to the associated ddc adapter * @supported_formats: Bitmask of @hdmi_colorspace listing supported output formats @@ -476,6 +477,7 @@ EXPORT_SYMBOL(drmm_connector_init); int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, const struct drm_connector_funcs *funcs, + const struct drm_connector_hdmi_funcs *hdmi_funcs, int connector_type, struct i2c_adapter *ddc, unsigned long supported_formats, @@ -512,6 +514,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, if (max_bpc > 8) drm_connector_attach_hdr_output_metadata_property(connector); + connector->hdmi.funcs = hdmi_funcs; + return 0; } EXPORT_SYMBOL(drmm_connector_hdmi_init); diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index 8557f722080c..e44dfe2e9455 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -24,6 +24,9 @@ struct drm_connector_init_priv { struct i2c_adapter ddc; }; +static const struct drm_connector_hdmi_funcs dummy_hdmi_funcs = { +}; + static const struct drm_connector_funcs dummy_funcs = { .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, @@ -189,6 +192,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -207,6 +211,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, NULL, BIT(HDMI_COLORSPACE_RGB), @@ -225,6 +230,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -243,6 +249,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -266,6 +273,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -305,6 +313,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -344,6 +353,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -378,6 +388,7 @@ static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, 0, @@ -396,6 +407,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, &priv->ddc, BIT(HDMI_COLORSPACE_YUV422), @@ -415,6 +427,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, connector_type, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), @@ -448,6 +461,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, &dummy_funcs, + &dummy_hdmi_funcs, connector_type, &priv->ddc, BIT(HDMI_COLORSPACE_RGB), diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index 0731f244f977..23449a4786ce 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -112,6 +112,9 @@ static int set_connector_edid(struct kunit *test, struct drm_connector *connecto return 0; } +static const struct drm_connector_hdmi_funcs dummy_connector_hdmi_funcs = { +}; + static int dummy_connector_get_modes(struct drm_connector *connector) { struct drm_atomic_helper_connector_hdmi_priv *priv = @@ -194,6 +197,7 @@ drm_atomic_helper_connector_hdmi_init(struct kunit *test, conn = &priv->connector; ret = drmm_connector_hdmi_init(drm, conn, &dummy_connector_funcs, + &dummy_connector_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, NULL, formats, diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 3bdcf904a11b..ffef967869d9 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -38,6 +38,7 @@ struct drm_connector_helper_funcs; struct drm_modeset_acquire_ctx; struct drm_device; struct drm_crtc; +struct drm_display_mode; struct drm_encoder; struct drm_panel; struct drm_property; @@ -1057,6 +1058,30 @@ struct drm_connector_state { } hdmi; }; +/** + * struct drm_connector_hdmi_funcs - drm_hdmi_connector control functions + */ +struct drm_connector_hdmi_funcs { + /** + * @tmds_char_rate_valid: + * + * This callback is invoked at atomic_check time to figure out + * whether a particular TMDS character rate is supported by the + * driver. + * + * The @tmds_char_rate_valid callback is optional. + * + * Returns: + * + * Either &drm_mode_status.MODE_OK or one of the failure reasons + * in &enum drm_mode_status. + */ + enum drm_mode_status + (*tmds_char_rate_valid)(const struct drm_connector *connector, + const struct drm_display_mode *mode, + unsigned long long tmds_rate); +}; + /** * struct drm_connector_funcs - control connectors on a given device * @@ -1929,6 +1954,11 @@ struct drm_connector { * supported by the controller. */ unsigned long supported_formats; + + /** + * @funcs: HDMI connector Control Functions + */ + const struct drm_connector_hdmi_funcs *funcs; } hdmi; }; @@ -1951,6 +1981,7 @@ int drmm_connector_init(struct drm_device *dev, int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, const struct drm_connector_funcs *funcs, + const struct drm_connector_hdmi_funcs *hdmi_funcs, int connector_type, struct i2c_adapter *ddc, unsigned long supported_formats, -- cgit v1.2.3-58-ga151 From ab52af4ba7c7dc2e226ede5935a0587743b747d3 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:58:07 +0200 Subject: drm/connector: hdmi: Add Broadcast RGB property The i915 driver has a property to force the RGB range of an HDMI output. The vc4 driver then implemented the same property with the same semantics. KWin has support for it, and a PR for mutter is also there to support it. Both drivers implementing the same property with the same semantics, plus the userspace having support for it, is proof enough that it's pretty much a de-facto standard now and we can provide helpers for it. Let's plumb it into the newly created HDMI connector. Reviewed-by: Dave Stevenson Acked-by: Pekka Paalanen Reviewed-by: Sebastian Wick Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-18-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- Documentation/gpu/kms-properties.csv | 1 - drivers/gpu/drm/display/drm_hdmi_state_helper.c | 4 +- drivers/gpu/drm/drm_atomic.c | 2 + drivers/gpu/drm/drm_atomic_uapi.c | 4 ++ drivers/gpu/drm/drm_connector.c | 88 +++++++++++++++++++++++++ include/drm/drm_connector.h | 36 ++++++++++ 6 files changed, 133 insertions(+), 2 deletions(-) (limited to 'include/drm') diff --git a/Documentation/gpu/kms-properties.csv b/Documentation/gpu/kms-properties.csv index c3c50a277cc9..bfbfbf4f102d 100644 --- a/Documentation/gpu/kms-properties.csv +++ b/Documentation/gpu/kms-properties.csv @@ -17,7 +17,6 @@ Owner Module/Drivers,Group,Property Name,Type,Property Values,Object attached,De ,Virtual GPU,“suggested X”,RANGE,"Min=0, Max=0xffffffff",Connector,property to suggest an X offset for a connector ,,“suggested Y”,RANGE,"Min=0, Max=0xffffffff",Connector,property to suggest an Y offset for a connector ,Optional,"""aspect ratio""",ENUM,"{ ""None"", ""4:3"", ""16:9"" }",Connector,TDB -i915,Generic,"""Broadcast RGB""",ENUM,"{ ""Automatic"", ""Full"", ""Limited 16:235"" }",Connector,"When this property is set to Limited 16:235 and CTM is set, the hardware will be programmed with the result of the multiplication of CTM by the limited range matrix to ensure the pixels normally in the range 0..1.0 are remapped to the range 16/255..235/255." ,,“audio”,ENUM,"{ ""force-dvi"", ""off"", ""auto"", ""on"" }",Connector,TBD ,SDVO-TV,“mode”,ENUM,"{ ""NTSC_M"", ""NTSC_J"", ""NTSC_443"", ""PAL_B"" } etc.",Connector,TBD ,,"""left_margin""",RANGE,"Min=0, Max= SDVO dependent",Connector,TBD diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 1623b96cd97c..57c66beec5bc 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -25,6 +25,7 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, new_conn_state->max_bpc = max_bpc; new_conn_state->max_requested_bpc = max_bpc; + new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO; } EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset); @@ -332,7 +333,8 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, if (ret) return ret; - if (old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc || + if (old_conn_state->hdmi.broadcast_rgb != new_conn_state->hdmi.broadcast_rgb || + old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc || old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) { struct drm_crtc *crtc = new_conn_state->crtc; struct drm_crtc_state *crtc_state; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 26f9e525c0a0..3e57d98d8418 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1145,6 +1145,8 @@ static void drm_atomic_connector_print_state(struct drm_printer *p, if (connector->connector_type == DRM_MODE_CONNECTOR_HDMIA || connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { + drm_printf(p, "\tbroadcast_rgb=%s\n", + drm_hdmi_connector_get_broadcast_rgb_name(state->hdmi.broadcast_rgb)); drm_printf(p, "\toutput_bpc=%u\n", state->hdmi.output_bpc); drm_printf(p, "\toutput_format=%s\n", drm_hdmi_connector_get_output_format_name(state->hdmi.output_format)); diff --git a/drivers/gpu/drm/drm_atomic_uapi.c b/drivers/gpu/drm/drm_atomic_uapi.c index fc16fddee5c5..22bbb2d83e30 100644 --- a/drivers/gpu/drm/drm_atomic_uapi.c +++ b/drivers/gpu/drm/drm_atomic_uapi.c @@ -776,6 +776,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector, state->max_requested_bpc = val; } else if (property == connector->privacy_screen_sw_state_property) { state->privacy_screen_sw_state = val; + } else if (property == connector->broadcast_rgb_property) { + state->hdmi.broadcast_rgb = val; } else if (connector->funcs->atomic_set_property) { return connector->funcs->atomic_set_property(connector, state, property, val); @@ -859,6 +861,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector, *val = state->max_requested_bpc; } else if (property == connector->privacy_screen_sw_state_property) { *val = state->privacy_screen_sw_state; + } else if (property == connector->broadcast_rgb_property) { + *val = state->hdmi.broadcast_rgb; } else if (connector->funcs->atomic_get_property) { return connector->funcs->atomic_get_property(connector, state, property, val); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index 39e9ff6c4f61..debb5a77ad89 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -1212,6 +1212,29 @@ static const u32 dp_colorspaces = BIT(DRM_MODE_COLORIMETRY_BT2020_CYCC) | BIT(DRM_MODE_COLORIMETRY_BT2020_YCC); +static const struct drm_prop_enum_list broadcast_rgb_names[] = { + { DRM_HDMI_BROADCAST_RGB_AUTO, "Automatic" }, + { DRM_HDMI_BROADCAST_RGB_FULL, "Full" }, + { DRM_HDMI_BROADCAST_RGB_LIMITED, "Limited 16:235" }, +}; + +/* + * drm_hdmi_connector_get_broadcast_rgb_name - Return a string for HDMI connector RGB broadcast selection + * @broadcast_rgb: Broadcast RGB selection to compute name of + * + * Returns: the name of the Broadcast RGB selection, or NULL if the type + * is not valid. + */ +const char * +drm_hdmi_connector_get_broadcast_rgb_name(enum drm_hdmi_broadcast_rgb broadcast_rgb) +{ + if (broadcast_rgb >= ARRAY_SIZE(broadcast_rgb_names)) + return NULL; + + return broadcast_rgb_names[broadcast_rgb].name; +} +EXPORT_SYMBOL(drm_hdmi_connector_get_broadcast_rgb_name); + static const char * const output_format_str[] = { [HDMI_COLORSPACE_RGB] = "RGB", [HDMI_COLORSPACE_YUV420] = "YUV 4:2:0", @@ -1708,6 +1731,38 @@ EXPORT_SYMBOL(drm_connector_attach_dp_subconnector_property); /** * DOC: HDMI connector properties * + * Broadcast RGB (HDMI specific) + * Indicates the Quantization Range (Full vs Limited) used. The color + * processing pipeline will be adjusted to match the value of the + * property, and the Infoframes will be generated and sent accordingly. + * + * This property is only relevant if the HDMI output format is RGB. If + * it's one of the YCbCr variant, it will be ignored. + * + * The CRTC attached to the connector must be configured by user-space to + * always produce full-range pixels. + * + * The value of this property can be one of the following: + * + * Automatic: + * The quantization range is selected automatically based on the + * mode according to the HDMI specifications (HDMI 1.4b - Section + * 6.6 - Video Quantization Ranges). + * + * Full: + * Full quantization range is forced. + * + * Limited 16:235: + * Limited quantization range is forced. Unlike the name suggests, + * this works for any number of bits-per-component. + * + * Property values other than Automatic can result in colors being off (if + * limited is selected but the display expects full), or a black screen + * (if full is selected but the display expects limited). + * + * Drivers can set up this property by calling + * drm_connector_attach_broadcast_rgb_property(). + * * content type (HDMI specific): * Indicates content type setting to be used in HDMI infoframes to indicate * content type for the external device, so that it adjusts its display @@ -2570,6 +2625,39 @@ int drm_connector_attach_hdr_output_metadata_property(struct drm_connector *conn } EXPORT_SYMBOL(drm_connector_attach_hdr_output_metadata_property); +/** + * drm_connector_attach_broadcast_rgb_property - attach "Broadcast RGB" property + * @connector: connector to attach the property on. + * + * This is used to add support for forcing the RGB range on a connector + * + * Returns: + * Zero on success, negative errno on failure. + */ +int drm_connector_attach_broadcast_rgb_property(struct drm_connector *connector) +{ + struct drm_device *dev = connector->dev; + struct drm_property *prop; + + prop = connector->broadcast_rgb_property; + if (!prop) { + prop = drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, + "Broadcast RGB", + broadcast_rgb_names, + ARRAY_SIZE(broadcast_rgb_names)); + if (!prop) + return -EINVAL; + + connector->broadcast_rgb_property = prop; + } + + drm_object_attach_property(&connector->base, prop, + DRM_HDMI_BROADCAST_RGB_AUTO); + + return 0; +} +EXPORT_SYMBOL(drm_connector_attach_broadcast_rgb_property); + /** * drm_connector_attach_colorspace_property - attach "Colorspace" property * @connector: connector to attach the property on. diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index ffef967869d9..207635715466 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -369,6 +369,29 @@ enum drm_panel_orientation { DRM_MODE_PANEL_ORIENTATION_RIGHT_UP, }; +/** + * enum drm_hdmi_broadcast_rgb - Broadcast RGB Selection for an HDMI @drm_connector + */ +enum drm_hdmi_broadcast_rgb { + /** + * @DRM_HDMI_BROADCAST_RGB_AUTO: The RGB range is selected + * automatically based on the mode. + */ + DRM_HDMI_BROADCAST_RGB_AUTO, + + /** + * @DRM_HDMI_BROADCAST_RGB_FULL: Full range RGB is forced. + */ + DRM_HDMI_BROADCAST_RGB_FULL, + + /** + * @DRM_HDMI_BROADCAST_RGB_LIMITED: Limited range RGB is forced. + */ + DRM_HDMI_BROADCAST_RGB_LIMITED, +}; + +const char * +drm_hdmi_connector_get_broadcast_rgb_name(enum drm_hdmi_broadcast_rgb broadcast_rgb); const char * drm_hdmi_connector_get_output_format_name(enum hdmi_colorspace fmt); @@ -1041,6 +1064,12 @@ struct drm_connector_state { * @drm_atomic_helper_connector_hdmi_check(). */ struct { + /** + * @broadcast_rgb: Connector property to pass the + * Broadcast RGB selection value. + */ + enum drm_hdmi_broadcast_rgb broadcast_rgb; + /** * @output_bpc: Bits per color channel to output. */ @@ -1757,6 +1786,12 @@ struct drm_connector { */ struct drm_property *privacy_screen_hw_state_property; + /** + * @broadcast_rgb_property: Connector property to set the + * Broadcast RGB selection to output with. + */ + struct drm_property *broadcast_rgb_property; + #define DRM_CONNECTOR_POLL_HPD (1 << 0) #define DRM_CONNECTOR_POLL_CONNECT (1 << 1) #define DRM_CONNECTOR_POLL_DISCONNECT (1 << 2) @@ -2096,6 +2131,7 @@ int drm_connector_attach_scaling_mode_property(struct drm_connector *connector, u32 scaling_mode_mask); int drm_connector_attach_vrr_capable_property( struct drm_connector *connector); +int drm_connector_attach_broadcast_rgb_property(struct drm_connector *connector); int drm_connector_attach_colorspace_property(struct drm_connector *connector); int drm_connector_attach_hdr_output_metadata_property(struct drm_connector *connector); bool drm_connector_atomic_hdr_metadata_equal(struct drm_connector_state *old_state, -- cgit v1.2.3-58-ga151 From 027d435906490812d4568ff371a8b63c24a36bcd Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:58:09 +0200 Subject: drm/connector: hdmi: Add RGB Quantization Range to the connector state HDMI controller drivers will need to figure out the RGB range they need to configure based on a mode and property values. Let's expose that in the HDMI connector state so drivers can just use that value. Reviewed-by: Dave Stevenson Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-20-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 29 +++++++++++++++++++++++++ drivers/gpu/drm/drm_atomic.c | 1 + include/drm/drm_connector.h | 6 +++++ 3 files changed, 36 insertions(+) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index 57c66beec5bc..f18020cfe4ea 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -51,6 +51,33 @@ connector_state_get_mode(const struct drm_connector_state *conn_state) return &crtc_state->mode; } +static bool hdmi_is_limited_range(const struct drm_connector *connector, + const struct drm_connector_state *conn_state) +{ + const struct drm_display_info *info = &connector->display_info; + const struct drm_display_mode *mode = + connector_state_get_mode(conn_state); + + /* + * The Broadcast RGB property only applies to RGB format, and + * i915 just assumes limited range for YCbCr output, so let's + * just do the same. + */ + if (conn_state->hdmi.output_format != HDMI_COLORSPACE_RGB) + return true; + + if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_FULL) + return false; + + if (conn_state->hdmi.broadcast_rgb == DRM_HDMI_BROADCAST_RGB_LIMITED) + return true; + + if (!info->is_hdmi) + return false; + + return drm_default_rgb_quant_range(mode) == HDMI_QUANTIZATION_RANGE_LIMITED; +} + static bool sink_supports_format_bpc(const struct drm_connector *connector, const struct drm_display_info *info, @@ -329,6 +356,8 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, connector_state_get_mode(new_conn_state); int ret; + new_conn_state->hdmi.is_limited_range = hdmi_is_limited_range(connector, new_conn_state); + ret = hdmi_compute_config(connector, new_conn_state, mode); if (ret) return ret; diff --git a/drivers/gpu/drm/drm_atomic.c b/drivers/gpu/drm/drm_atomic.c index 3e57d98d8418..07b4b394e3bf 100644 --- a/drivers/gpu/drm/drm_atomic.c +++ b/drivers/gpu/drm/drm_atomic.c @@ -1147,6 +1147,7 @@ static void drm_atomic_connector_print_state(struct drm_printer *p, connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) { drm_printf(p, "\tbroadcast_rgb=%s\n", drm_hdmi_connector_get_broadcast_rgb_name(state->hdmi.broadcast_rgb)); + drm_printf(p, "\tis_limited_range=%c\n", state->hdmi.is_limited_range ? 'y' : 'n'); drm_printf(p, "\toutput_bpc=%u\n", state->hdmi.output_bpc); drm_printf(p, "\toutput_format=%s\n", drm_hdmi_connector_get_output_format_name(state->hdmi.output_format)); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 207635715466..dcad2d46fabe 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -1070,6 +1070,12 @@ struct drm_connector_state { */ enum drm_hdmi_broadcast_rgb broadcast_rgb; + /** + * @is_full_range: Is the output supposed to use a full + * RGB Quantization Range or not? + */ + bool is_limited_range; + /** * @output_bpc: Bits per color channel to output. */ -- cgit v1.2.3-58-ga151 From f378b77227bc4732922c57f92be89438bb1018a1 Mon Sep 17 00:00:00 2001 From: Maxime Ripard Date: Mon, 27 May 2024 15:58:11 +0200 Subject: drm/connector: hdmi: Add Infoframes generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Infoframes in KMS is usually handled by a bunch of low-level helpers that require quite some boilerplate for drivers. This leads to discrepancies with how drivers generate them, and which are actually sent. Now that we have everything needed to generate them in the HDMI connector state, we can generate them in our common logic so that drivers can simply reuse what we precomputed. Cc: Ville Syrjälä Reviewed-by: Dmitry Baryshkov Link: https://patchwork.freedesktop.org/patch/msgid/20240527-kms-hdmi-connector-state-v15-22-c5af16c3aae2@kernel.org Signed-off-by: Maxime Ripard --- drivers/gpu/drm/display/drm_hdmi_state_helper.c | 336 +++++++++++++++++++++ drivers/gpu/drm/drm_connector.c | 14 + drivers/gpu/drm/tests/drm_connector_test.c | 12 + drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c | 1 + include/drm/display/drm_hdmi_state_helper.h | 6 + include/drm/drm_connector.h | 111 ++++++- 6 files changed, 479 insertions(+), 1 deletion(-) (limited to 'include/drm') diff --git a/drivers/gpu/drm/display/drm_hdmi_state_helper.c b/drivers/gpu/drm/display/drm_hdmi_state_helper.c index f18020cfe4ea..437270c29210 100644 --- a/drivers/gpu/drm/display/drm_hdmi_state_helper.c +++ b/drivers/gpu/drm/display/drm_hdmi_state_helper.c @@ -333,6 +333,144 @@ hdmi_compute_config(const struct drm_connector *connector, return -EINVAL; } +static int hdmi_generate_avi_infoframe(const struct drm_connector *connector, + struct drm_connector_state *conn_state) +{ + const struct drm_display_mode *mode = + connector_state_get_mode(conn_state); + struct drm_connector_hdmi_infoframe *infoframe = + &conn_state->hdmi.infoframes.avi; + struct hdmi_avi_infoframe *frame = + &infoframe->data.avi; + bool is_limited_range = conn_state->hdmi.is_limited_range; + enum hdmi_quantization_range rgb_quant_range = + is_limited_range ? HDMI_QUANTIZATION_RANGE_LIMITED : HDMI_QUANTIZATION_RANGE_FULL; + int ret; + + ret = drm_hdmi_avi_infoframe_from_display_mode(frame, connector, mode); + if (ret) + return ret; + + frame->colorspace = conn_state->hdmi.output_format; + + /* + * FIXME: drm_hdmi_avi_infoframe_quant_range() doesn't handle + * YUV formats at all at the moment, so if we ever support YUV + * formats this needs to be revised. + */ + drm_hdmi_avi_infoframe_quant_range(frame, connector, mode, rgb_quant_range); + drm_hdmi_avi_infoframe_colorimetry(frame, conn_state); + drm_hdmi_avi_infoframe_bars(frame, conn_state); + + infoframe->set = true; + + return 0; +} + +static int hdmi_generate_spd_infoframe(const struct drm_connector *connector, + struct drm_connector_state *conn_state) +{ + struct drm_connector_hdmi_infoframe *infoframe = + &conn_state->hdmi.infoframes.spd; + struct hdmi_spd_infoframe *frame = + &infoframe->data.spd; + int ret; + + ret = hdmi_spd_infoframe_init(frame, + connector->hdmi.vendor, + connector->hdmi.product); + if (ret) + return ret; + + frame->sdi = HDMI_SPD_SDI_PC; + + infoframe->set = true; + + return 0; +} + +static int hdmi_generate_hdr_infoframe(const struct drm_connector *connector, + struct drm_connector_state *conn_state) +{ + struct drm_connector_hdmi_infoframe *infoframe = + &conn_state->hdmi.infoframes.hdr_drm; + struct hdmi_drm_infoframe *frame = + &infoframe->data.drm; + int ret; + + if (connector->max_bpc < 10) + return 0; + + if (!conn_state->hdr_output_metadata) + return 0; + + ret = drm_hdmi_infoframe_set_hdr_metadata(frame, conn_state); + if (ret) + return ret; + + infoframe->set = true; + + return 0; +} + +static int hdmi_generate_hdmi_vendor_infoframe(const struct drm_connector *connector, + struct drm_connector_state *conn_state) +{ + const struct drm_display_info *info = &connector->display_info; + const struct drm_display_mode *mode = + connector_state_get_mode(conn_state); + struct drm_connector_hdmi_infoframe *infoframe = + &conn_state->hdmi.infoframes.hdmi; + struct hdmi_vendor_infoframe *frame = + &infoframe->data.vendor.hdmi; + int ret; + + if (!info->has_hdmi_infoframe) + return 0; + + ret = drm_hdmi_vendor_infoframe_from_display_mode(frame, connector, mode); + if (ret) + return ret; + + infoframe->set = true; + + return 0; +} + +static int +hdmi_generate_infoframes(const struct drm_connector *connector, + struct drm_connector_state *conn_state) +{ + const struct drm_display_info *info = &connector->display_info; + int ret; + + if (!info->is_hdmi) + return 0; + + ret = hdmi_generate_avi_infoframe(connector, conn_state); + if (ret) + return ret; + + ret = hdmi_generate_spd_infoframe(connector, conn_state); + if (ret) + return ret; + + /* + * Audio Infoframes will be generated by ALSA, and updated by + * drm_atomic_helper_connector_hdmi_update_audio_infoframe(). + */ + + ret = hdmi_generate_hdr_infoframe(connector, conn_state); + if (ret) + return ret; + + ret = hdmi_generate_hdmi_vendor_infoframe(connector, conn_state); + if (ret) + return ret; + + return 0; +} + /** * drm_atomic_helper_connector_hdmi_check() - Helper to check HDMI connector atomic state * @connector: DRM Connector @@ -362,6 +500,10 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, if (ret) return ret; + ret = hdmi_generate_infoframes(connector, new_conn_state); + if (ret) + return ret; + if (old_conn_state->hdmi.broadcast_rgb != new_conn_state->hdmi.broadcast_rgb || old_conn_state->hdmi.output_bpc != new_conn_state->hdmi.output_bpc || old_conn_state->hdmi.output_format != new_conn_state->hdmi.output_format) { @@ -378,3 +520,197 @@ int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, return 0; } EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_check); + +#define HDMI_MAX_INFOFRAME_SIZE 29 + +static int clear_device_infoframe(struct drm_connector *connector, + enum hdmi_infoframe_type type) +{ + const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; + struct drm_device *dev = connector->dev; + int ret; + + drm_dbg_kms(dev, "Clearing infoframe type 0x%x\n", type); + + if (!funcs || !funcs->clear_infoframe) { + drm_dbg_kms(dev, "Function not implemented, bailing.\n"); + return 0; + } + + ret = funcs->clear_infoframe(connector, type); + if (ret) { + drm_dbg_kms(dev, "Call failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int clear_infoframe(struct drm_connector *connector, + struct drm_connector_hdmi_infoframe *old_frame) +{ + int ret; + + ret = clear_device_infoframe(connector, old_frame->data.any.type); + if (ret) + return ret; + + return 0; +} + +static int write_device_infoframe(struct drm_connector *connector, + union hdmi_infoframe *frame) +{ + const struct drm_connector_hdmi_funcs *funcs = connector->hdmi.funcs; + struct drm_device *dev = connector->dev; + u8 buffer[HDMI_MAX_INFOFRAME_SIZE]; + int ret; + int len; + + drm_dbg_kms(dev, "Writing infoframe type %x\n", frame->any.type); + + if (!funcs || !funcs->write_infoframe) { + drm_dbg_kms(dev, "Function not implemented, bailing.\n"); + return -EINVAL; + } + + len = hdmi_infoframe_pack(frame, buffer, sizeof(buffer)); + if (len < 0) + return len; + + ret = funcs->write_infoframe(connector, frame->any.type, buffer, len); + if (ret) { + drm_dbg_kms(dev, "Call failed: %d\n", ret); + return ret; + } + + return 0; +} + +static int write_infoframe(struct drm_connector *connector, + struct drm_connector_hdmi_infoframe *new_frame) +{ + int ret; + + ret = write_device_infoframe(connector, &new_frame->data); + if (ret) + return ret; + + return 0; +} + +static int write_or_clear_infoframe(struct drm_connector *connector, + struct drm_connector_hdmi_infoframe *old_frame, + struct drm_connector_hdmi_infoframe *new_frame) +{ + if (new_frame->set) + return write_infoframe(connector, new_frame); + + if (old_frame->set && !new_frame->set) + return clear_infoframe(connector, old_frame); + + return 0; +} + +/** + * drm_atomic_helper_connector_hdmi_update_infoframes - Update the Infoframes + * @connector: A pointer to the HDMI connector + * @state: The HDMI connector state to generate the infoframe from + * + * This function is meant for HDMI connector drivers to write their + * infoframes. It will typically be used in a + * @drm_connector_helper_funcs.atomic_enable implementation. + * + * Returns: + * Zero on success, error code on failure. + */ +int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct drm_connector_state *old_conn_state = + drm_atomic_get_old_connector_state(state, connector); + struct drm_connector_state *new_conn_state = + drm_atomic_get_new_connector_state(state, connector); + struct drm_display_info *info = &connector->display_info; + int ret; + + if (!info->is_hdmi) + return 0; + + mutex_lock(&connector->hdmi.infoframes.lock); + + ret = write_or_clear_infoframe(connector, + &old_conn_state->hdmi.infoframes.avi, + &new_conn_state->hdmi.infoframes.avi); + if (ret) + goto out; + + if (connector->hdmi.infoframes.audio.set) { + ret = write_infoframe(connector, + &connector->hdmi.infoframes.audio); + if (ret) + goto out; + } + + ret = write_or_clear_infoframe(connector, + &old_conn_state->hdmi.infoframes.hdr_drm, + &new_conn_state->hdmi.infoframes.hdr_drm); + if (ret) + goto out; + + ret = write_or_clear_infoframe(connector, + &old_conn_state->hdmi.infoframes.spd, + &new_conn_state->hdmi.infoframes.spd); + if (ret) + goto out; + + if (info->has_hdmi_infoframe) { + ret = write_or_clear_infoframe(connector, + &old_conn_state->hdmi.infoframes.hdmi, + &new_conn_state->hdmi.infoframes.hdmi); + if (ret) + goto out; + } + +out: + mutex_unlock(&connector->hdmi.infoframes.lock); + return ret; +} +EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_infoframes); + +/** + * drm_atomic_helper_connector_hdmi_update_audio_infoframe - Update the Audio Infoframe + * @connector: A pointer to the HDMI connector + * @frame: A pointer to the audio infoframe to write + * + * This function is meant for HDMI connector drivers to update their + * audio infoframe. It will typically be used in one of the ALSA hooks + * (most likely prepare). + * + * Returns: + * Zero on success, error code on failure. + */ +int +drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector, + struct hdmi_audio_infoframe *frame) +{ + struct drm_connector_hdmi_infoframe *infoframe = + &connector->hdmi.infoframes.audio; + struct drm_display_info *info = &connector->display_info; + int ret; + + if (!info->is_hdmi) + return 0; + + mutex_lock(&connector->hdmi.infoframes.lock); + + memcpy(&infoframe->data, frame, sizeof(infoframe->data)); + infoframe->set = true; + + ret = write_infoframe(connector, infoframe); + + mutex_unlock(&connector->hdmi.infoframes.lock); + + return ret; +} +EXPORT_SYMBOL(drm_atomic_helper_connector_hdmi_update_audio_infoframe); diff --git a/drivers/gpu/drm/drm_connector.c b/drivers/gpu/drm/drm_connector.c index debb5a77ad89..3d73a981004c 100644 --- a/drivers/gpu/drm/drm_connector.c +++ b/drivers/gpu/drm/drm_connector.c @@ -278,6 +278,7 @@ static int __drm_connector_init(struct drm_device *dev, INIT_LIST_HEAD(&connector->modes); mutex_init(&connector->mutex); mutex_init(&connector->edid_override_mutex); + mutex_init(&connector->hdmi.infoframes.lock); connector->edid_blob_ptr = NULL; connector->epoch_counter = 0; connector->tile_blob_ptr = NULL; @@ -456,6 +457,8 @@ EXPORT_SYMBOL(drmm_connector_init); * drmm_connector_hdmi_init - Init a preallocated HDMI connector * @dev: DRM device * @connector: A pointer to the HDMI connector to init + * @vendor: HDMI Controller Vendor name + * @product: HDMI Controller Product name * @funcs: callbacks for this connector * @hdmi_funcs: HDMI-related callbacks for this connector * @connector_type: user visible type of the connector @@ -476,6 +479,7 @@ EXPORT_SYMBOL(drmm_connector_init); */ int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, + const char *vendor, const char *product, const struct drm_connector_funcs *funcs, const struct drm_connector_hdmi_funcs *hdmi_funcs, int connector_type, @@ -485,6 +489,13 @@ int drmm_connector_hdmi_init(struct drm_device *dev, { int ret; + if (!vendor || !product) + return -EINVAL; + + if ((strlen(vendor) > DRM_CONNECTOR_HDMI_VENDOR_LEN) || + (strlen(product) > DRM_CONNECTOR_HDMI_PRODUCT_LEN)) + return -EINVAL; + if (!(connector_type == DRM_MODE_CONNECTOR_HDMIA || connector_type == DRM_MODE_CONNECTOR_HDMIB)) return -EINVAL; @@ -500,6 +511,8 @@ int drmm_connector_hdmi_init(struct drm_device *dev, return ret; connector->hdmi.supported_formats = supported_formats; + strtomem_pad(connector->hdmi.vendor, vendor, 0); + strtomem_pad(connector->hdmi.product, product, 0); /* * drm_connector_attach_max_bpc_property() requires the @@ -652,6 +665,7 @@ void drm_connector_cleanup(struct drm_connector *connector) connector->funcs->atomic_destroy_state(connector, connector->state); + mutex_destroy(&connector->hdmi.infoframes.lock); mutex_destroy(&connector->mutex); memset(connector, 0, sizeof(*connector)); diff --git a/drivers/gpu/drm/tests/drm_connector_test.c b/drivers/gpu/drm/tests/drm_connector_test.c index a4ea170a2575..2e631617c391 100644 --- a/drivers/gpu/drm/tests/drm_connector_test.c +++ b/drivers/gpu/drm/tests/drm_connector_test.c @@ -191,6 +191,7 @@ static void drm_test_connector_hdmi_init_valid(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -210,6 +211,7 @@ static void drm_test_connector_hdmi_init_null_ddc(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -229,6 +231,7 @@ static void drm_test_connector_hdmi_init_bpc_invalid(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -248,6 +251,7 @@ static void drm_test_connector_hdmi_init_bpc_null(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -272,6 +276,7 @@ static void drm_test_connector_hdmi_init_bpc_8(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -312,6 +317,7 @@ static void drm_test_connector_hdmi_init_bpc_10(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -352,6 +358,7 @@ static void drm_test_connector_hdmi_init_bpc_12(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -387,6 +394,7 @@ static void drm_test_connector_hdmi_init_formats_empty(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -406,6 +414,7 @@ static void drm_test_connector_hdmi_init_formats_no_rgb(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, @@ -426,6 +435,7 @@ static void drm_test_connector_hdmi_init_type_valid(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, connector_type, @@ -460,6 +470,7 @@ static void drm_test_connector_hdmi_init_type_invalid(struct kunit *test) int ret; ret = drmm_connector_hdmi_init(&priv->drm, &priv->connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, connector_type, @@ -725,6 +736,7 @@ static void drm_test_drm_connector_attach_broadcast_rgb_property_hdmi_connector( int ret; ret = drmm_connector_hdmi_init(&priv->drm, connector, + "Vendor", "Product", &dummy_funcs, &dummy_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, diff --git a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c index 2eda21228d75..2d3abc71dc16 100644 --- a/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c +++ b/drivers/gpu/drm/tests/drm_hdmi_state_helper_test.c @@ -206,6 +206,7 @@ drm_atomic_helper_connector_hdmi_init(struct kunit *test, conn = &priv->connector; ret = drmm_connector_hdmi_init(drm, conn, + "Vendor", "Product", &dummy_connector_funcs, &dummy_connector_hdmi_funcs, DRM_MODE_CONNECTOR_HDMIA, diff --git a/include/drm/display/drm_hdmi_state_helper.h b/include/drm/display/drm_hdmi_state_helper.h index 837899db315a..eb162ff24de0 100644 --- a/include/drm/display/drm_hdmi_state_helper.h +++ b/include/drm/display/drm_hdmi_state_helper.h @@ -6,6 +6,7 @@ struct drm_atomic_state; struct drm_connector; struct drm_connector_state; +struct hdmi_audio_infoframe; void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, struct drm_connector_state *new_conn_state); @@ -13,4 +14,9 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector, int drm_atomic_helper_connector_hdmi_check(struct drm_connector *connector, struct drm_atomic_state *state); +int drm_atomic_helper_connector_hdmi_update_audio_infoframe(struct drm_connector *connector, + struct hdmi_audio_infoframe *frame); +int drm_atomic_helper_connector_hdmi_update_infoframes(struct drm_connector *connector, + struct drm_atomic_state *state); + #endif // DRM_HDMI_STATE_HELPER_H_ diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index dcad2d46fabe..e04a8a0d1bbd 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -914,6 +914,21 @@ struct drm_tv_connector_state { unsigned int hue; }; +/** + * struct drm_connector_hdmi_infoframe - HDMI Infoframe container + */ +struct drm_connector_hdmi_infoframe { + /** + * @data: HDMI Infoframe structure + */ + union hdmi_infoframe data; + + /** + * @set: Is the content of @data valid? + */ + bool set; +}; + /** * struct drm_connector_state - mutable connector state */ @@ -1071,7 +1086,36 @@ struct drm_connector_state { enum drm_hdmi_broadcast_rgb broadcast_rgb; /** - * @is_full_range: Is the output supposed to use a full + * @infoframes: HDMI Infoframes matching that state + */ + struct { + /** + * @avi: AVI Infoframes structure matching our + * state. + */ + struct drm_connector_hdmi_infoframe avi; + + /** + * @hdr_drm: DRM (Dynamic Range and Mastering) + * Infoframes structure matching our state. + */ + struct drm_connector_hdmi_infoframe hdr_drm; + + /** + * @spd: SPD Infoframes structure matching our + * state. + */ + struct drm_connector_hdmi_infoframe spd; + + /** + * @vendor: HDMI Vendor Infoframes structure + * matching our state. + */ + struct drm_connector_hdmi_infoframe hdmi; + } infoframes; + + /** + * @is_limited_range: Is the output supposed to use a limited * RGB Quantization Range or not? */ bool is_limited_range; @@ -1115,6 +1159,41 @@ struct drm_connector_hdmi_funcs { (*tmds_char_rate_valid)(const struct drm_connector *connector, const struct drm_display_mode *mode, unsigned long long tmds_rate); + + /** + * @clear_infoframe: + * + * This callback is invoked through + * @drm_atomic_helper_connector_hdmi_update_infoframes during a + * commit to clear the infoframes into the hardware. It will be + * called multiple times, once for every disabled infoframe + * type. + * + * The @clear_infoframe callback is optional. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*clear_infoframe)(struct drm_connector *connector, + enum hdmi_infoframe_type type); + + /** + * @write_infoframe: + * + * This callback is invoked through + * @drm_atomic_helper_connector_hdmi_update_infoframes during a + * commit to program the infoframes into the hardware. It will + * be called multiple times, once for every updated infoframe + * type. + * + * The @write_infoframe callback is mandatory. + * + * Returns: + * 0 on success, a negative error code otherwise + */ + int (*write_infoframe)(struct drm_connector *connector, + enum hdmi_infoframe_type type, + const u8 *buffer, size_t len); }; /** @@ -1990,6 +2069,18 @@ struct drm_connector { * @hdmi: HDMI-related variable and properties. */ struct { +#define DRM_CONNECTOR_HDMI_VENDOR_LEN 8 + /** + * @vendor: HDMI Controller Vendor Name + */ + unsigned char vendor[DRM_CONNECTOR_HDMI_VENDOR_LEN] __nonstring; + +#define DRM_CONNECTOR_HDMI_PRODUCT_LEN 16 + /** + * @product: HDMI Controller Product Name + */ + unsigned char product[DRM_CONNECTOR_HDMI_PRODUCT_LEN] __nonstring; + /** * @supported_formats: Bitmask of @hdmi_colorspace * supported by the controller. @@ -2000,6 +2091,23 @@ struct drm_connector { * @funcs: HDMI connector Control Functions */ const struct drm_connector_hdmi_funcs *funcs; + + /** + * @infoframes: Current Infoframes output by the connector + */ + struct { + /** + * @lock: Mutex protecting against concurrent access to + * the infoframes, most notably between KMS and ALSA. + */ + struct mutex lock; + + /** + * @audio: Current Audio Infoframes structure. Protected + * by @lock. + */ + struct drm_connector_hdmi_infoframe audio; + } infoframes; } hdmi; }; @@ -2021,6 +2129,7 @@ int drmm_connector_init(struct drm_device *dev, struct i2c_adapter *ddc); int drmm_connector_hdmi_init(struct drm_device *dev, struct drm_connector *connector, + const char *vendor, const char *product, const struct drm_connector_funcs *funcs, const struct drm_connector_hdmi_funcs *hdmi_funcs, int connector_type, -- cgit v1.2.3-58-ga151 From c7ce956bb6d0f32ab921b6ffba1a6a834df96f21 Mon Sep 17 00:00:00 2001 From: MarileneGarcia Date: Sun, 19 May 2024 00:10:27 -0300 Subject: drm/dp: Fix documentation warning It fixes the following warnings when the kernel documentation is generated: ./include/drm/display/drm_dp_helper.h:126: warning: Function parameter or struct member 'mode' not described in 'drm_dp_as_sdp' ./include/drm/display/drm_dp_helper.h:126: warning: Excess struct member 'operation_mode' description in 'drm_dp_as_sdp' Signed-off-by: MarileneGarcia Fixes: 0bbb8f594e33 ("drm/dp: Add Adaptive Sync SDP logging") Reviewed-by: Dmitry Baryshkov Reported-by: Stephen Rothwell Closes: https://lore.kernel.org/r/20240405141640.09b0bdbf@canb.auug.org.au Reviewed-by: Douglas Anderson Signed-off-by: Douglas Anderson Link: https://patchwork.freedesktop.org/patch/msgid/20240519031027.433751-1-marilene.agarcia@gmail.com --- include/drm/display/drm_dp_helper.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'include/drm') diff --git a/include/drm/display/drm_dp_helper.h b/include/drm/display/drm_dp_helper.h index 8bed890eec2c..8defcc399f42 100644 --- a/include/drm/display/drm_dp_helper.h +++ b/include/drm/display/drm_dp_helper.h @@ -112,7 +112,7 @@ struct drm_dp_vsc_sdp { * @target_rr: Target Refresh * @duration_incr_ms: Successive frame duration increase * @duration_decr_ms: Successive frame duration decrease - * @operation_mode: Adaptive Sync Operation Mode + * @mode: Adaptive Sync Operation Mode */ struct drm_dp_as_sdp { unsigned char sdp_type; -- cgit v1.2.3-58-ga151